foscat 3.3.3__tar.gz → 3.3.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. {foscat-3.3.3/src/foscat.egg-info → foscat-3.3.5}/PKG-INFO +8 -1
  2. {foscat-3.3.3 → foscat-3.3.5}/pyproject.toml +5 -1
  3. {foscat-3.3.3 → foscat-3.3.5}/src/foscat/CircSpline.py +49 -5
  4. {foscat-3.3.3 → foscat-3.3.5}/src/foscat/FoCUS.py +1 -1
  5. foscat-3.3.5/src/foscat/alm.py +450 -0
  6. foscat-3.3.5/src/foscat/alm_tools.py +192 -0
  7. {foscat-3.3.3 → foscat-3.3.5/src/foscat.egg-info}/PKG-INFO +8 -1
  8. {foscat-3.3.3 → foscat-3.3.5}/src/foscat.egg-info/SOURCES.txt +1 -0
  9. {foscat-3.3.3 → foscat-3.3.5}/src/foscat.egg-info/requires.txt +1 -0
  10. foscat-3.3.3/src/foscat/alm.py +0 -140
  11. {foscat-3.3.3 → foscat-3.3.5}/LICENCE +0 -0
  12. {foscat-3.3.3 → foscat-3.3.5}/README.md +0 -0
  13. {foscat-3.3.3 → foscat-3.3.5}/setup.cfg +0 -0
  14. {foscat-3.3.3 → foscat-3.3.5}/src/foscat/CNN.py +0 -0
  15. {foscat-3.3.3 → foscat-3.3.5}/src/foscat/GCNN.py +0 -0
  16. {foscat-3.3.3 → foscat-3.3.5}/src/foscat/Softmax.py +0 -0
  17. {foscat-3.3.3 → foscat-3.3.5}/src/foscat/Spline1D.py +0 -0
  18. {foscat-3.3.3 → foscat-3.3.5}/src/foscat/Synthesis.py +0 -0
  19. {foscat-3.3.3 → foscat-3.3.5}/src/foscat/__init__.py +0 -0
  20. {foscat-3.3.3 → foscat-3.3.5}/src/foscat/backend.py +0 -0
  21. {foscat-3.3.3 → foscat-3.3.5}/src/foscat/backend_tens.py +0 -0
  22. {foscat-3.3.3 → foscat-3.3.5}/src/foscat/loss_backend_tens.py +0 -0
  23. {foscat-3.3.3 → foscat-3.3.5}/src/foscat/loss_backend_torch.py +0 -0
  24. {foscat-3.3.3 → foscat-3.3.5}/src/foscat/scat.py +0 -0
  25. {foscat-3.3.3 → foscat-3.3.5}/src/foscat/scat1D.py +0 -0
  26. {foscat-3.3.3 → foscat-3.3.5}/src/foscat/scat2D.py +0 -0
  27. {foscat-3.3.3 → foscat-3.3.5}/src/foscat/scat_cov.py +0 -0
  28. {foscat-3.3.3 → foscat-3.3.5}/src/foscat/scat_cov1D.py +0 -0
  29. {foscat-3.3.3 → foscat-3.3.5}/src/foscat/scat_cov2D.py +0 -0
  30. {foscat-3.3.3 → foscat-3.3.5}/src/foscat/scat_cov_map.py +0 -0
  31. {foscat-3.3.3 → foscat-3.3.5}/src/foscat/scat_cov_map2D.py +0 -0
  32. {foscat-3.3.3 → foscat-3.3.5}/src/foscat.egg-info/dependency_links.txt +0 -0
  33. {foscat-3.3.3 → foscat-3.3.5}/src/foscat.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: foscat
3
- Version: 3.3.3
3
+ Version: 3.3.5
4
4
  Summary: Generate synthetic Healpix or 2D data using Cross Scattering Transform
5
5
  Author-email: Jean-Marc DELOUIS <jean.marc.delouis@ifremer.fr>
6
6
  Maintainer-email: Theo Foulquier <theo.foulquier@ifremer.fr>
@@ -19,6 +19,13 @@ Classifier: Programming Language :: Python :: 3.12
19
19
  Requires-Python: >=3.9
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENCE
22
+ Requires-Dist: imageio
23
+ Requires-Dist: imagecodecs
24
+ Requires-Dist: matplotlib
25
+ Requires-Dist: numpy
26
+ Requires-Dist: tensorflow
27
+ Requires-Dist: healpy
28
+ Requires-Dist: spherical
22
29
 
23
30
  # foscat
24
31
 
@@ -1,15 +1,17 @@
1
1
  [project]
2
2
  name = "foscat"
3
- version = "3.3.3"
3
+ version = "3.3.5"
4
4
  description = "Generate synthetic Healpix or 2D data using Cross Scattering Transform"
5
5
  readme = "README.md"
6
6
  license = { text = "BSD-3-Clause" }
7
7
  authors = [
8
8
  { name = "Jean-Marc DELOUIS", email = "jean.marc.delouis@ifremer.fr" },
9
9
  ]
10
+
10
11
  maintainers = [
11
12
  { name = "Theo Foulquier", email = "theo.foulquier@ifremer.fr" },
12
13
  ]
14
+
13
15
  dependencies = [
14
16
  "imageio",
15
17
  "imagecodecs",
@@ -17,7 +19,9 @@ dependencies = [
17
19
  "numpy",
18
20
  "tensorflow",
19
21
  "healpy",
22
+ "spherical",
20
23
  ]
24
+
21
25
  requires-python = ">= 3.9"
22
26
  keywords = ["scattering transform", "component separation", "denoising"]
23
27
  classifiers = [
@@ -61,13 +61,57 @@ class CircSpline:
61
61
  coefficients[0] = self.cubic_spline_function(0.5 - fractional_part / 2) / 2
62
62
 
63
63
  # Assign indices for the support points
64
- indices[3] = (base_idx + 3)%N
65
- indices[2] = (base_idx + 2)%N
66
- indices[1] = (base_idx + 1)%N
67
- indices[0] = base_idx
64
+ indices[3] = (base_idx + 2+N)%N
65
+ indices[2] = (base_idx + 1+N)%N
66
+ indices[1] = (base_idx + N )%N
67
+ indices[0] = (base_idx + N-1)%N
68
+
69
+ # Square coefficients and normalize
70
+ coefficients = coefficients * coefficients
71
+ coefficients /= np.sum(coefficients, axis=0)
72
+
73
+ return indices, coefficients
74
+
75
+
76
+ def eval_N(self,x,N):
77
+ """
78
+ Compute a 3rd-degree cubic spline with 4-point support.
79
+
80
+ Args:
81
+ x (float or array): Input value(s) to compute the spline.
82
+
83
+ Returns:
84
+ indices (array): Indices of the spline support points.
85
+ coefficients (array): Normalized spline coefficients.
86
+ """
87
+
88
+ if isinstance(x, float):
89
+ # Single scalar input
90
+ base_idx = int(x * (N))
91
+ indices = np.zeros([4], dtype="int")
92
+ coefficients = np.zeros([4])
93
+ else:
94
+ # Array input
95
+ base_idx = (x * (N)).astype("int")
96
+ indices = np.zeros([4, x.shape[0]], dtype="int")
97
+ coefficients = np.zeros([4, x.shape[0]])
98
+
99
+ # Compute the fractional part of the input
100
+ fractional_part = x * (N) - base_idx
101
+
102
+ # Compute spline coefficients for 4 support points
103
+ coefficients[3] = self.cubic_spline_function(fractional_part / 2) / 2
104
+ coefficients[2] = self.cubic_spline_function(0.5 + fractional_part / 2) / 2
105
+ coefficients[1] = self.cubic_spline_function(1 - fractional_part / 2) / 2
106
+ coefficients[0] = self.cubic_spline_function(0.5 - fractional_part / 2) / 2
107
+
108
+ # Assign indices for the support points
109
+ indices[3] = (base_idx + 2+N)%N
110
+ indices[2] = (base_idx + 1+N)%N
111
+ indices[1] = (base_idx + N )%N
112
+ indices[0] =( base_idx + N-1)%N
68
113
 
69
114
  # Adjust indices to start from 0
70
- indices = indices - 1
71
115
  # Square coefficients and normalize
72
116
  coefficients = coefficients * coefficients
73
117
  coefficients /= np.sum(coefficients, axis=0)
@@ -38,7 +38,7 @@ class FoCUS:
38
38
  mpi_rank=0,
39
39
  ):
40
40
 
41
- self.__version__ = "3.3.0"
41
+ self.__version__ = "3.3.5"
42
42
  # P00 coeff for normalization for scat_cov
43
43
  self.TMPFILE_VERSION = TMPFILE_VERSION
44
44
  self.P1_dic = None
@@ -0,0 +1,450 @@
1
+ import healpy as hp
2
+ import numpy as np
3
+
4
+ class alm():
5
+
6
+ def __init__(self,backend=None,lmax=24,nside=None,limit_range=1E10):
7
+ self._logtab={}
8
+ self.lth={}
9
+ if nside is not None:
10
+ self.lmax=3*nside
11
+ th,ph=hp.pix2ang(nside,np.arange(12*nside*nside))
12
+
13
+ lth=np.unique(th)
14
+
15
+ self.lth[nside]=lth
16
+ else:
17
+ self.lmax=lmax
18
+
19
+ for k in range(1,2*self.lmax+1):
20
+ self._logtab[k]=np.log(k)
21
+ self._logtab[0]=0.0
22
+ self._limit_range=1/limit_range
23
+ self._log_limit_range=np.log(limit_range)
24
+
25
+ if backend is None:
26
+ import foscat.scat_cov as sc
27
+ self.sc=sc.funct()
28
+ self.backend=self.sc.backend
29
+ else:
30
+ self.backend=backend.backend
31
+
32
+ self.Yp={}
33
+ self.Ym={}
34
+
35
+ def ring_th(self,nside):
36
+ if nside not in self.lth:
37
+ th,ph=hp.pix2ang(nside,np.arange(12*nside*nside))
38
+
39
+ lth=np.unique(th)
40
+
41
+ self.lth[nside]=lth
42
+ return self.lth[nside]
43
+
44
+
45
+ def init_Ys(self,s,nside):
46
+
47
+ if (s,nside) not in self.Yp:
48
+ import quaternionic
49
+ import spherical
50
+
51
+ ell_max = 3*nside-1 # Use the largest ℓ value you expect to need
52
+ wigner = spherical.Wigner(ell_max)
53
+
54
+ th,ph=hp.pix2ang(nside,np.arange(12*nside*nside))
55
+
56
+ lth=self.ring_th(nside)
57
+
58
+ R = quaternionic.array.from_spherical_coordinates(lth, 0*lth)
59
+ self.Yp[s,nside] = {}
60
+ self.Ym[s,nside] = {}
61
+ iplus = (wigner.sYlm( s, R)*(4*np.pi/(12*nside**2))).T.real
62
+ imoins = (wigner.sYlm(-s, R)*(4*np.pi/(12*nside**2))).T.real
63
+
64
+ for m in range(ell_max+1):
65
+ idx=np.array([wigner.Yindex(k, m) for k in range(m,ell_max+1)])
66
+ self.Yp[s,nside][m] = iplus[idx]
67
+ self.Ym[s,nside][m] = imoins[idx]
68
+
69
+ del(iplus)
70
+ del(imoins)
71
+ del(wigner)
72
+
73
+ def log(self,v):
74
+ #return np.log(v)
75
+ if isinstance(v,np.ndarray):
76
+ return np.array([self.log(k) for k in v])
77
+ if v<self.lmax*2+1:
78
+ return self._logtab[v]
79
+ else:
80
+ self._logtab[v]=np.log(v)
81
+ return self._logtab[v]
82
+
83
+ # Fonction pour calculer la double factorielle
84
+ def double_factorial_log(self,n):
85
+ if n <= 0:
86
+ return 0.0
87
+ result = 0.0
88
+ for i in range(n, 0, -2):
89
+ result += self.log(i)
90
+ return result
91
+
92
+ # Calcul des P_{lm}(x) pour tout l inclus dans [m,lmax]
93
+ def compute_legendre_m(self,x,m,lmax):
94
+ result=np.zeros([lmax-m+1,x.shape[0]])
95
+ ratio=np.zeros([lmax-m+1,1])
96
+
97
+ ratio[0,0] = self.double_factorial_log(2*m - 1)-0.5*np.sum(self.log(1+np.arange(2*m)))
98
+
99
+ # Étape 1 : Calcul de P_{mm}(x)
100
+ if m == 0:
101
+ Pmm = 1.0
102
+ else:
103
+ #Pmm = (-1)**m * (1 - x**2)**(m/2)
104
+ Pmm = (0.5-m%2)*2 * (1 - x**2)**(m/2)
105
+
106
+
107
+ # Si l == m, c'est directement P_{mm}
108
+ result[0] = Pmm
109
+
110
+ if m == lmax:
111
+ return result*np.exp(ratio)*np.sqrt(4*np.pi*(2*(np.arange(lmax-m+1)+m)+1)).reshape(lmax+1-m,1)
112
+
113
+ # Étape 2 : Calcul de P_{l+1, m}(x)
114
+ result[1] = x * (2*m + 1) * result[0]
115
+
116
+ ratio[1,0] = ratio[0,0]-0.5*self.log(2*m+1)
117
+
118
+ # Étape 3 : Récurence pour l > m + 1
119
+ for l in range(m + 2, lmax+1):
120
+ result[l-m] = ((2*l - 1) * x * result[l-m-1] - (l + m - 1) * result[l-m-2]) / (l - m)
121
+ ratio[l-m,0] = 0.5*self.log(l-m)-0.5*self.log(l+m)+ratio[l-m-1,0]
122
+ if np.max(abs(result[l-m]))>self._limit_range:
123
+ result[l-m-1]*= self._limit_range
124
+ result[l-m]*= self._limit_range
125
+ ratio[l-m-1,0]+= self._log_limit_range
126
+ ratio[l-m,0]+= self._log_limit_range
127
+
128
+ return result*np.exp(ratio)*(np.sqrt(4*np.pi*(2*(np.arange(lmax-m+1)+m)+1))).reshape(lmax+1-m,1)
129
+
130
+
131
+ # Calcul des s_P_{lm}(x) pour tout l inclus dans [m,lmax]
132
+ def compute_legendre_spin2_m(self,co_th,si_th,m,lmax):
133
+ result=np.zeros([lmax-m+2,co_th.shape[0]])
134
+ ratio =np.zeros([lmax-m+2,1])
135
+
136
+ ratio[1,0] = self.double_factorial_log(2*m - 1)-0.5*np.sum(self.log(1+np.arange(2*m)))
137
+ # Étape 1 : Calcul de P_{mm}(x)
138
+ if m == 0:
139
+ Pmm = 1.0
140
+ else:
141
+ #Pmm = (-1)**m * (1 - x**2)**(m/2)
142
+ Pmm = (0.5-m%2)*2 * (1 - co_th**2)**(m/2)
143
+
144
+
145
+ # Si l == m, c'est directement P_{mm}
146
+ result[1] = Pmm
147
+
148
+ if m == lmax:
149
+ ylm=result*np.exp(ratio)
150
+ ylm[1:]*=(np.sqrt(4*np.pi*(2*(np.arange(lmax-m+1)+m)+1))).reshape(lmax+1-m,1)
151
+
152
+ else:
153
+ # Étape 2 : Calcul de P_{l+1, m}(x)
154
+ result[2] = co_th * (2*m + 1) * result[0]
155
+
156
+ ratio[2,0] = ratio[1,0]-self.log(2*m+1)/2
157
+
158
+ # Étape 3 : Récurence pour l > m + 1
159
+ for l in range(m + 2, lmax+1):
160
+ result[l-m+1] = ((2*l - 1) * co_th * result[l-m] - (l + m - 1) * result[l-m-1]) / (l - m)
161
+ ratio[l-m+1,0] = (self.log(l-m)-self.log(l+m))/2+ratio[l-m,0]
162
+ if np.max(abs(result[l-m+1]))>self._limit_range:
163
+ result[l-m]*= self._limit_range
164
+ result[l-m+1]*= self._limit_range
165
+ ratio[l-m,0]+= self._log_limit_range
166
+ ratio[l-m+1,0]+= self._log_limit_range
167
+
168
+ ylm=result*np.exp(ratio)
169
+ ylm[1:]*=(np.sqrt(4*np.pi*(2*(np.arange(lmax-m+1)+m)+1))).reshape(lmax+1-m,1)
170
+
171
+ ell=(np.arange(lmax+1-m)+m).reshape(lmax+1-m,1)
172
+
173
+ cot_th=co_th/si_th
174
+ si2_th=si_th*si_th
175
+
176
+ a = (2*m**2-ell*(ell+1))/(si2_th.reshape(1,si2_th.shape[0]))+ell*(ell-1)*cot_th*cot_th
177
+ b = 2*m*(ell-1)*cot_th/si_th
178
+ w=np.zeros([lmax+1-m,1])
179
+ l=ell[ell>1]
180
+ w[ell>1]=np.sqrt(1/((l+2)*(l+1)*(l)*(l-1)))
181
+ w=w.reshape(lmax+1-m,1)
182
+
183
+ alpha_plus=w*(a+b)
184
+ alpha_moins=w*(a-b)
185
+
186
+ a=2*np.sqrt((2*ell+1)/(2*ell-1)*(ell*ell-m*m))
187
+ b=m/si2_th
188
+
189
+ beta_plus=w*a*(cot_th/si_th+b)
190
+ beta_moins=w*a*(cot_th/si_th-b)
191
+
192
+ ylm_plus = alpha_plus*ylm[1:]+ beta_plus*ylm[:-1]
193
+ ylm_moins = alpha_moins*ylm[1:] + beta_moins*ylm[:-1]
194
+
195
+ return ylm_plus,ylm_moins
196
+
197
+ def comp_tf(self,im,ph):
198
+ nside=int(np.sqrt(im.shape[0]//12))
199
+ n=0
200
+ ii=0
201
+ ft_im=[]
202
+ for k in range(nside-1):
203
+ N=4*(k+1)
204
+ l_n=N
205
+ if l_n>3*nside:
206
+ l_n=3*nside
207
+ tmp=self.backend.bk_fft(im[n:n+N])[0:l_n]
208
+ ft_im.append(tmp*np.exp(-1J*np.arange(l_n)*ph[n]))
209
+ ft_im.append(self.backend.bk_zeros((3*nside-l_n),dtype=self.backend.all_cbk_type))
210
+ # if N<3*nside fill the tf with rotational values to mimic alm_tools.F90 of healpix (Minor effect)
211
+ #for m in range(l_n,3*nside,l_n):
212
+ # ft_im.append(tmp[0:np.min([3*nside-m,l_n])])
213
+ n+=N
214
+ ii+=1
215
+ for k in range(2*nside+1):
216
+ N=4*nside
217
+ ft_im.append(self.backend.bk_fft(im[n:n+N])[:3*nside]*np.exp(-1J*np.arange(3*nside)*ph[n]))
218
+ n+=N
219
+ ii+=1
220
+ for k in range(nside-1):
221
+ N=4*(nside-1-k)
222
+ l_n=N
223
+ if l_n>3*nside:
224
+ l_n=3*nside
225
+ tmp=self.backend.bk_fft(im[n:n+N])[0:l_n]
226
+ ft_im.append(tmp*np.exp(-1J*np.arange(l_n)*ph[n]))
227
+ ft_im.append(self.backend.bk_zeros((3*nside-l_n),dtype=self.backend.all_cbk_type))
228
+ # if N<3*nside fill the tf with rotational values to mimic alm_tools.F90 of healpix (Minor effect)
229
+ #for m in range(l_n,3*nside,l_n):
230
+ # ft_im.append(tmp[0:np.min([3*nside-m,l_n])])
231
+ n+=N
232
+ ii+=1
233
+ return self.backend.bk_reshape(self.backend.bk_concat(ft_im,axis=0),[4*nside-1,3*nside])
234
+
235
+ def anafast(self,im,map2=None,nest=False):
236
+ """The `anafast` function computes the L1 and L2 norm power spectra.
237
+
238
+ Currently, it is not optimized for single-pass computation due to the relatively inefficient computation of \(Y_{lm}\).
239
+ Nonetheless, it utilizes TensorFlow and can be integrated into gradient computations.
240
+
241
+ Input:
242
+ - `im`: a vector of size \([12 \times \text{Nside}^2]\) for scalar data, or of size \([3, 12 \times \text{Nside}^2]\) for polar data.
243
+ - `map2` (optional): a vector of size \([12 \times \text{Nside}^2]\) for scalar data, or of size
244
+ \([3, 12 \times \text{Nside}^2]\) for polar data. If provided, cross power spectra will be computed.
245
+ - `nest=True`: alters the ordering of the input maps.
246
+
247
+ Output:
248
+ -A tensor of size \([l_{\text{max}} \times (l_{\text{max}}-1)]\) formatted as \([6, \ldots]\),
249
+ ordered as TT, EE, BB, TE, EB.TBanafast function computes L1 and L2 norm powerspctra.
250
+
251
+ """
252
+ if len(im.shape)==1: # nopol
253
+ nside=int(np.sqrt(im.shape[0]//12))
254
+ else:
255
+ nside=int(np.sqrt(im.shape[1]//12))
256
+ th,ph=hp.pix2ang(nside,np.arange(12*nside*nside))
257
+ if nest:
258
+ idx=hp.ring2nest(nside,np.arange(12*nside**2))
259
+ if len(im.shape)==1: # nopol
260
+ ft_im=self.comp_tf(self.backend.bk_complex(self.backend.bk_gather(im,idx),0*im),ph)
261
+ if map2 is not None:
262
+ ft_im2=self.comp_tf(self.backend.bk_complex(self.backend.bk_gather(map2,idx),0*im),ph)
263
+ else:
264
+ ft_im=self.comp_tf(self.backend.bk_complex(self.backend.bk_gather(im[0],idx),0*im[0]),ph)
265
+ if map2 is not None:
266
+ ft_im2=self.comp_tf(self.backend.bk_complex(self.backend.bk_gather(map2[0],idx),0*im[0]),ph)
267
+ else:
268
+ if len(im.shape)==1: # nopol
269
+ ft_im=self.comp_tf(self.backend.bk_complex(im,0*im),ph)
270
+ if map2 is not None:
271
+ ft_im2=self.comp_tf(self.backend.bk_complex(map2,0*im),ph)
272
+ else:
273
+ ft_im=self.comp_tf(self.backend.bk_complex(im[0],0*im[0]),ph)
274
+ if map2 is not None:
275
+ ft_im2=self.comp_tf(self.backend.bk_complex(map2[0],0*im[0]),ph)
276
+
277
+ lth=self.ring_th(nside)
278
+
279
+ co_th=np.cos(lth)
280
+
281
+ lmax=3*nside-1
282
+
283
+ cl2=None
284
+ cl2_L1=None
285
+
286
+
287
+ if len(im.shape)==2: # nopol
288
+
289
+ spin=2
290
+
291
+ self.init_Ys(spin,nside)
292
+
293
+ if nest:
294
+ idx=hp.ring2nest(nside,np.arange(12*nside**2))
295
+ l_Q=self.backend.bk_gather(im[1],idx)
296
+ l_U=self.backend.bk_gather(im[2],idx)
297
+ ft_im_Pp=self.comp_tf(self.backend.bk_complex(l_Q,l_U),ph)
298
+ ft_im_Pm=self.comp_tf(self.backend.bk_complex(l_Q,-l_U),ph)
299
+ else:
300
+ ft_im_Pp=self.comp_tf(self.backend.bk_complex(im[1],im[2]),ph)
301
+ ft_im_Pm=self.comp_tf(self.backend.bk_complex(im[1],-im[2]),ph)
302
+
303
+ for m in range(lmax+1):
304
+
305
+ plm=self.compute_legendre_m(co_th,m,3*nside-1)/(12*nside**2)
306
+
307
+ tmp=self.backend.bk_reduce_sum(plm*ft_im[:,m],1)
308
+
309
+ if map2 is not None:
310
+ tmp2=self.backend.bk_reduce_sum(plm*ft_im2[:,m],1)
311
+ else:
312
+ tmp2=tmp
313
+
314
+ if len(im.shape)==2: # pol
315
+ plmp=self.Yp[spin,nside][m]
316
+ plmm=self.Ym[spin,nside][m]
317
+
318
+ tmpp=self.backend.bk_reduce_sum(plmp*ft_im_Pp[:,m],1)
319
+ tmpm=self.backend.bk_reduce_sum(plmm*ft_im_Pm[:,m],1)
320
+
321
+ almE=-(tmpp+tmpm)/2.0
322
+ almB=(tmpp-tmpm)/(2J)
323
+
324
+ if map2 is not None:
325
+ tmpp2=self.backend.bk_reduce_sum(plmp*ft_im2_Pp[:,m],1)
326
+ tmpm2=self.backend.bk_reduce_sum(plmm*ft_im2_Pm[:,m],1)
327
+
328
+ almE2=-(tmpp+tmpm)/2.0
329
+ almB2=(tmpp-tmpm)/(2J)
330
+ else:
331
+ almE2=almE
332
+ almB2=almB
333
+
334
+ tmpTT=self.backend.bk_real((tmp*self.backend.bk_conjugate(tmp2)))
335
+ tmpEE=self.backend.bk_real((almE*self.backend.bk_conjugate(almE2)))
336
+ tmpBB=self.backend.bk_real((almB*self.backend.bk_conjugate(almB2)))
337
+ tmpTE=self.backend.bk_real((tmp*self.backend.bk_conjugate(almE2)))
338
+ tmpTB=-self.backend.bk_real((tmp*self.backend.bk_conjugate(almB2)))
339
+ tmpEB=-self.backend.bk_real((almE*self.backend.bk_conjugate(almB2)))
340
+ if map2 is not None:
341
+ tmpTE=(tmpTE+self.backend.bk_real((tmp2*self.backend.bk_conjugate(almE))))/2
342
+ tmpTB=(tmpTB-self.backend.bk_real((tmp2*self.backend.bk_conjugate(almB))))/2
343
+ tmpEB=(tmpEB-self.backend.bk_real((almE2*self.backend.bk_conjugate(almB))))/2
344
+
345
+ if m==0:
346
+ l_cl=self.backend.bk_concat([tmpTT,tmpEE,tmpBB,tmpTE,tmpEB,tmpTB],0)
347
+ else:
348
+ offset_tensor=self.backend.bk_zeros((m),dtype=self.backend.all_bk_type)
349
+ l_cl=self.backend.bk_concat([self.backend.bk_concat([offset_tensor,tmpTT],axis=0),
350
+ self.backend.bk_concat([offset_tensor,tmpEE],axis=0),
351
+ self.backend.bk_concat([offset_tensor,tmpBB],axis=0),
352
+ self.backend.bk_concat([offset_tensor,tmpTE],axis=0),
353
+ self.backend.bk_concat([offset_tensor,tmpEB],axis=0),
354
+ self.backend.bk_concat([offset_tensor,tmpTB],axis=0)],axis=0)
355
+
356
+ l_cl=self.backend.bk_reshape(l_cl,[6,lmax+1])
357
+ else:
358
+ tmp=self.backend.bk_real((tmp*self.backend.bk_conjugate(tmp2)))
359
+ if m==0:
360
+ l_cl=tmp
361
+ else:
362
+ offset_tensor=self.backend.bk_zeros((m),dtype=self.backend.all_bk_type)
363
+ l_cl=self.backend.bk_concat([offset_tensor,tmp],axis=0)
364
+
365
+ if cl2 is None:
366
+ cl2=l_cl
367
+ cl2_l1=self.backend.bk_L1(l_cl)
368
+ else:
369
+ cl2+=2*l_cl
370
+ cl2_l1+=2*self.backend.bk_L1(l_cl)
371
+
372
+ if len(im.shape)==1: # nopol
373
+ cl2=cl2/(2*np.arange(cl2.shape[0])+1)
374
+ cl2_l1=cl2_l1/(2*np.arange(cl2.shape[0])+1)
375
+ else:
376
+ cl2=cl2/np.expand_dims(2*np.arange(cl2.shape[1])+1,0)
377
+ cl2_l1=cl2_l1/np.expand_dims(2*np.arange(cl2.shape[1])+1,0)
378
+ return cl2,cl2_l1
379
+
380
+ def map2alm(self,im,nest=False):
381
+ nside=int(np.sqrt(im.shape[0]//12))
382
+ th,ph=hp.pix2ang(nside,np.arange(12*nside*nside))
383
+ if nest:
384
+ idx=hp.ring2nest(nside,np.arange(12*nside**2))
385
+ ft_im=self.comp_tf(self.backend.bk_complex(self.backend.bk_gather(im,idx),0*im),ph)
386
+ else:
387
+ ft_im=self.comp_tf(self.backend.bk_complex(im,0*im),ph)
388
+
389
+ co_th=np.cos(self.ring_th(nside))
390
+
391
+ lmax=3*nside-1
392
+
393
+ alm=None
394
+ for m in range(lmax+1):
395
+ plm=self.compute_legendre_m(co_th,m,3*nside-1)/(12*nside**2)
396
+
397
+ tmp=self.backend.bk_reduce_sum(plm*ft_im[:,m],1)
398
+ if m==0:
399
+ alm=tmp
400
+ else:
401
+ alm=self.backend.bk_concat([alm,tmp],axis=0)
402
+
403
+ return alm
404
+
405
+ def map2alm_spin(self,im_Q,im_U,spin=2,nest=False):
406
+
407
+ if spin==0:
408
+ return self.map2alm(im_Q,nest=nest),self.map2alm(im_U,nest=nest)
409
+
410
+
411
+ nside=int(np.sqrt(im_Q.shape[0]//12))
412
+ th,ph=hp.pix2ang(nside,np.arange(12*nside*nside))
413
+
414
+ self.init_Ys(spin,nside)
415
+
416
+ if nest:
417
+ idx=hp.ring2nest(nside,np.arange(12*nside**2))
418
+ l_Q=self.backend.bk_gather(im_Q,idx)
419
+ l_U=self.backend.bk_gather(im_U,idx)
420
+ ft_im_1=self.comp_tf(self.backend.bk_complex(l_Q,l_U),ph)
421
+ ft_im_2=self.comp_tf(self.backend.bk_complex(l_Q,-l_U),ph)
422
+ else:
423
+ ft_im_1=self.comp_tf(self.backend.bk_complex(im_Q,im_U),ph)
424
+ ft_im_2=self.comp_tf(self.backend.bk_complex(im_Q,-im_U),ph)
425
+
426
+ #co_th=np.cos(self.ring_th[nside])
427
+ #si_th=np.sin(self.ring_th[nside])
428
+
429
+ lmax=3*nside-1
430
+
431
+ alm=None
432
+ for m in range(lmax+1):
433
+ #not yet debug use spherical
434
+ #plmp1,plmm1=self.compute_legendre_spin2_m(co_th,si_th,m,3*nside-1)
435
+ #plmp1/=(12*nside**2)
436
+ #plmm1/=(12*nside**2)
437
+
438
+ plmp=self.Yp[spin,nside][m]
439
+ plmm=self.Ym[spin,nside][m]
440
+
441
+ tmpp=self.backend.bk_reduce_sum(plmp*ft_im_1[:,m],1)
442
+ tmpm=self.backend.bk_reduce_sum(plmm*ft_im_2[:,m],1)
443
+ if m==0:
444
+ almE=-(tmpp+tmpm)/2.0
445
+ almB=(tmpp-tmpm)/(2J)
446
+ else:
447
+ almE=self.backend.bk_concat([almE,-(tmpp+tmpm)/2],axis=0)
448
+ almB=self.backend.bk_concat([almB,(tmpp-tmpm)/(2J)],axis=0)
449
+
450
+ return almE,almB
@@ -0,0 +1,192 @@
1
+ import numpy as np
2
+
3
+ #====================================================================================================================
4
+ # This class is an automatic traduction of the fortran healpix software
5
+ #====================================================================================================================
6
+
7
+
8
+ class alm_tools():
9
+ def __init__(self):
10
+ pass
11
+
12
+ @staticmethod
13
+ def gen_recfac(l_max, m):
14
+ """
15
+ Generate recursion factors used to compute the Ylm of degree m for all l in m <= l <= l_max.
16
+
17
+ Parameters:
18
+ l_max (int): Maximum degree l.
19
+ m (int): Degree m.
20
+
21
+ Returns:
22
+ np.ndarray: Recursion factors as a 2D array of shape (2, l_max + 1).
23
+ """
24
+ recfac = np.zeros((2, l_max + 1), dtype=np.float64)
25
+ fm2 = float(m)**2
26
+
27
+ for l in range(m, l_max + 1):
28
+ fl2 = float(l + 1)**2
29
+ recfac[0, l] = np.sqrt((4.0 * fl2 - 1.0) / (fl2 - fm2))
30
+
31
+ recfac[1, m:l_max + 1] = 1.0 / recfac[0, m:l_max + 1]
32
+
33
+ return recfac
34
+
35
+ @staticmethod
36
+ def gen_recfac_spin(l_max, m, spin):
37
+ """
38
+ Generate recursion factors for spin-weighted spherical harmonics.
39
+
40
+ Parameters:
41
+ l_max (int): Maximum degree l.
42
+ m (int): Degree m.
43
+ spin (int): Spin weight.
44
+
45
+ Returns:
46
+ np.ndarray: Recursion factors as a 2D array of shape (2, l_max + 1).
47
+ """
48
+ recfac_spin = np.zeros((2, l_max + 1), dtype=np.float64)
49
+ fm2 = float(m)**2
50
+ s2 = float(spin)**2
51
+
52
+ for l in range(m, l_max + 1):
53
+ fl2 = float(l + 1)**2
54
+ recfac_spin[0, l] = np.sqrt((4.0 * fl2 - 1.0) / (fl2 - fm2))
55
+
56
+ recfac_spin[1, m:l_max + 1] = (1.0 - s2 / (float(m) + 1.0)**2) / recfac_spin[0, m:l_max + 1]
57
+
58
+ return recfac_spin
59
+
60
+ @staticmethod
61
+ def gen_lamfac(l_max):
62
+ """
63
+ Generate lambda factors for spherical harmonics.
64
+
65
+ Parameters:
66
+ l_max (int): Maximum degree l.
67
+
68
+ Returns:
69
+ np.ndarray: Lambda factors as a 1D array of size l_max + 1.
70
+ """
71
+ lamfac = np.zeros(l_max + 1, dtype=np.float64)
72
+
73
+ for l in range(1, l_max + 1):
74
+ lamfac[l] = np.sqrt(2.0 * l + 1.0)
75
+
76
+ return lamfac
77
+
78
+ @staticmethod
79
+ def gen_lamfac_der(l_max):
80
+ """
81
+ Generate the derivatives of lambda factors.
82
+
83
+ Parameters:
84
+ l_max (int): Maximum degree l.
85
+
86
+ Returns:
87
+ np.ndarray: Lambda factor derivatives as a 1D array of size l_max + 1.
88
+ """
89
+ lamfac_der = np.zeros(l_max + 1, dtype=np.float64)
90
+
91
+ for l in range(1, l_max + 1):
92
+ lamfac_der[l] = (2.0 * l + 1.0) / np.sqrt(2.0 * l + 1.0)
93
+
94
+ return lamfac_der
95
+
96
+ @staticmethod
97
+ def gen_mfac(m_max):
98
+ """
99
+ Generate m factors for spherical harmonics.
100
+
101
+ Parameters:
102
+ m_max (int): Maximum degree m.
103
+
104
+ Returns:
105
+ np.ndarray: M factors as a 1D array of size m_max + 1.
106
+ """
107
+ mfac = np.zeros(m_max + 1, dtype=np.float64)
108
+
109
+ for m in range(1, m_max + 1):
110
+ mfac[m] = np.sqrt(2.0 * m)
111
+
112
+ return mfac
113
+
114
+ @staticmethod
115
+ def gen_mfac_spin(m_max, spin):
116
+ """
117
+ Generate m factors for spin-weighted spherical harmonics.
118
+
119
+ Parameters:
120
+ m_max (int): Maximum degree m.
121
+ spin (int): Spin weight.
122
+
123
+ Returns:
124
+ np.ndarray: Spin-weighted m factors as a 1D array of size m_max + 1.
125
+ """
126
+ mfac_spin = np.zeros(m_max + 1, dtype=np.float64)
127
+
128
+ for m in range(1, m_max + 1):
129
+ mfac_spin[m] = np.sqrt(2.0 * m) * (1.0 - spin**2 / (m + 1)**2)
130
+
131
+ return mfac_spin
132
+
133
+ @staticmethod
134
+ def compute_lam_mm(l_max, m):
135
+ """
136
+ Compute lambda values for specific m.
137
+
138
+ Parameters:
139
+ l_max (int): Maximum degree l.
140
+ m (int): Degree m.
141
+
142
+ Returns:
143
+ np.ndarray: Lambda values as a 1D array of size l_max + 1.
144
+ """
145
+ lam_mm = np.zeros(l_max + 1, dtype=np.float64)
146
+
147
+ for l in range(m, l_max + 1):
148
+ lam_mm[l] = (2.0 * l + 1.0) * (1.0 - (m / (l + 1.0))**2)
149
+
150
+ return lam_mm
151
+
152
+ @staticmethod
153
+ def do_lam_lm(l_max, m):
154
+ """
155
+ Perform computations for lambda values for all l, m.
156
+
157
+ Parameters:
158
+ l_max (int): Maximum degree l.
159
+ m (int): Degree m.
160
+
161
+ Returns:
162
+ np.ndarray: Computed lambda values as a 2D array of size (l_max + 1, l_max + 1).
163
+ """
164
+ lam_lm = np.zeros((l_max + 1, l_max + 1), dtype=np.float64)
165
+
166
+ for l in range(m, l_max + 1):
167
+ for mp in range(m, l + 1):
168
+ lam_lm[l, mp] = (2.0 * l + 1.0) * (1.0 - (mp / (l + 1.0))**2)
169
+
170
+ return lam_lm
171
+
172
+ @staticmethod
173
+ def do_lam_lm_spin(l_max, m, spin):
174
+ """
175
+ Perform computations for spin-weighted lambda values for all l, m.
176
+
177
+ Parameters:
178
+ l_max (int): Maximum degree l.
179
+ m (int): Degree m.
180
+ spin (int): Spin weight.
181
+
182
+ Returns:
183
+ np.ndarray: Computed spin-weighted lambda values as a 2D array of size (l_max + 1, l_max + 1).
184
+ """
185
+ lam_lm_spin = np.zeros((l_max + 1, l_max + 1), dtype=np.float64)
186
+
187
+ for l in range(m, l_max + 1):
188
+ for mp in range(m, l + 1):
189
+ lam_lm_spin[l, mp] = (2.0 * l + 1.0) * (1.0 - spin**2 / (mp + 1.0)**2)
190
+
191
+ return lam_lm_spin
192
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: foscat
3
- Version: 3.3.3
3
+ Version: 3.3.5
4
4
  Summary: Generate synthetic Healpix or 2D data using Cross Scattering Transform
5
5
  Author-email: Jean-Marc DELOUIS <jean.marc.delouis@ifremer.fr>
6
6
  Maintainer-email: Theo Foulquier <theo.foulquier@ifremer.fr>
@@ -19,6 +19,13 @@ Classifier: Programming Language :: Python :: 3.12
19
19
  Requires-Python: >=3.9
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENCE
22
+ Requires-Dist: imageio
23
+ Requires-Dist: imagecodecs
24
+ Requires-Dist: matplotlib
25
+ Requires-Dist: numpy
26
+ Requires-Dist: tensorflow
27
+ Requires-Dist: healpy
28
+ Requires-Dist: spherical
22
29
 
23
30
  # foscat
24
31
 
@@ -10,6 +10,7 @@ src/foscat/Spline1D.py
10
10
  src/foscat/Synthesis.py
11
11
  src/foscat/__init__.py
12
12
  src/foscat/alm.py
13
+ src/foscat/alm_tools.py
13
14
  src/foscat/backend.py
14
15
  src/foscat/backend_tens.py
15
16
  src/foscat/loss_backend_tens.py
@@ -4,3 +4,4 @@ matplotlib
4
4
  numpy
5
5
  tensorflow
6
6
  healpy
7
+ spherical
@@ -1,140 +0,0 @@
1
- import healpy as hp
2
- import numpy as np
3
-
4
- class alm():
5
-
6
- def __init__(self,backend=None,lmax=24,limit_range=1E7):
7
- self._logtab={}
8
- self.lmax=0
9
- for k in range(1,2*lmax+1):
10
- self._logtab[k]=np.log(k)
11
- self._limit_range=1/limit_range
12
- self._log_limit_range=np.log(limit_range)
13
- if backend is None:
14
- import foscat.scat_cov as sc
15
- self.sc=sc.funct()
16
- self.backend=self.sc.backend
17
- else:
18
- self.backend=backend.backend
19
-
20
- def log(self,v):
21
- #return np.log(v)
22
- if isinstance(v,np.ndarray):
23
- return np.array([self.log(k) for k in v])
24
- if v<self.lmax*2+1:
25
- return self._logtab[v]
26
- else:
27
- self._logtab[v]=np.log(v)
28
- return self._logtab[v]
29
-
30
- # Fonction pour calculer la double factorielle
31
- def double_factorial_log(self,n):
32
- if n <= 0:
33
- return 0.0
34
- result = 0.0
35
- for i in range(n, 0, -2):
36
- result += self.log(i)
37
- return result
38
-
39
- # Calcul des P_{lm}(x) pour tout l inclus dans [m,lmax]
40
- def compute_legendre_m(self,x,m,lmax):
41
- # Étape 1 : Calcul de P_{mm}(x)
42
- if m == 0:
43
- Pmm = 1.0
44
- else:
45
- Pmm = (-1)**m * (1 - x**2)**(m/2)
46
-
47
- result=np.zeros([lmax-m+1,x.shape[0]])
48
- ratio=np.zeros([lmax-m+1,1])
49
-
50
- # Si l == m, c'est directement P_{mm}
51
- result[0]=Pmm
52
- ratio[0,0]= self.double_factorial_log(2*m - 1)-0.5*np.sum(self.log(1+np.arange(2*m)))
53
-
54
- if m == lmax:
55
- return result*np.exp(ratio)*np.sqrt((2*(np.arange(lmax-m+1)-m))/(4*np.pi)).reshape(lmax+1-m,1)
56
-
57
- # Étape 2 : Calcul de P_{l+1, m}(x)
58
- result[1] = x * (2*m + 1) * result[0]
59
-
60
- ratio[1,0]=ratio[0,0]-0.5*self.log(2*m+1)
61
-
62
- # Étape 3 : Récurence pour l > m + 1
63
- for l in range(m + 2, lmax+1):
64
- result[l-m] = ((2*l - 1) * x * result[l-m-1] - (l + m - 1) * result[l-m-2]) / (l - m)
65
- ratio[l-m,0] = 0.5*self.log(l-m)-0.5*self.log(l+m)+ratio[l-m-1,0]
66
- if np.max(abs(result[l-m]))>self._limit_range:
67
- result[l-m-1]*=self._limit_range
68
- result[l-m]*=self._limit_range
69
- ratio[l-m-1,0]+=self._log_limit_range
70
- ratio[l-m,0]+=self._log_limit_range
71
-
72
- return result*np.exp(ratio)*(np.sqrt(4*np.pi*(2*(np.arange(lmax-m+1)+m)+1))).reshape(lmax+1-m,1)
73
-
74
- def comp_tf(self,im,ph):
75
- nside=int(np.sqrt(im.shape[0]//12))
76
- n=0
77
- ii=0
78
- ft_im=[]
79
- for k in range(nside-1):
80
- N=4*(k+1)
81
- ft_im.append(self.backend.bk_fft(im[n:n+N])[:N//2+1]*np.exp(-1J*np.arange(N//2+1)/N*ph[n]))
82
- ft_im.append(self.backend.bk_zeros((3*nside-N//2-1),dtype=self.backend.all_cbk_type))
83
- n+=N
84
- ii+=1
85
- for k in range(2*nside+1):
86
- N=4*nside
87
- ft_im.append(self.backend.bk_fft(im[n:n+N])[:N//2+1]*np.exp(-1J*np.arange(N//2+1)/N*ph[n]))
88
- ft_im.append(self.backend.bk_zeros((3*nside-N//2-1),dtype=self.backend.all_cbk_type))
89
- n+=N
90
- ii+=1
91
- for k in range(nside-1):
92
- N=4*(nside-1-k)
93
- ft_im.append(self.backend.bk_fft(im[n:n+N])[:N//2+1]*np.exp(-1J*np.arange(N//2+1)/N*ph[n]))
94
- ft_im.append(self.backend.bk_zeros((3*nside-N//2-1),dtype=self.backend.all_cbk_type))
95
- n+=N
96
- ii+=1
97
- return self.backend.bk_reshape(self.backend.bk_concat(ft_im,axis=0),[4*nside-1,3*nside])
98
-
99
- def anafast(self,im,map2=None,nest=True):
100
- nside=int(np.sqrt(im.shape[0]//12))
101
- th,ph=hp.pix2ang(nside,np.arange(12*nside*nside))
102
- if nest:
103
- idx=hp.ring2nest(nside,np.arange(12*nside**2))
104
- ft_im=self.comp_tf(self.backend.bk_complex(self.backend.bk_gather(im,idx),0*im),ph)
105
- if map2 is not None:
106
- ft_im2=self.comp_tf(self.backend.bk_complex(self.backend.bk_gather(map2,idx),0*im),ph)
107
- else:
108
- ft_im=self.comp_tf(self.backend.bk_complex(im,0*im),ph)
109
- if map2 is not None:
110
- ft_im2=self.comp_tf(self.backend.bk_complex(map2,0*im),ph)
111
-
112
- co_th=np.cos(np.unique(th))
113
-
114
- lmax=3*nside-1
115
-
116
- cl2=None
117
- cl2_L1=None
118
- for m in range(lmax+1):
119
- plm=self.compute_legendre_m(co_th,m,3*nside-1)/(12*nside**2)
120
-
121
- tmp=self.backend.bk_reduce_sum(plm*ft_im[:,m],1)
122
-
123
- if map2 is not None:
124
- tmp2=self.backend.bk_reduce_sum(plm*ft_im2[:,m],1)
125
- else:
126
- tmp2=tmp
127
-
128
- tmp=self.backend.bk_real((tmp*self.backend.bk_conjugate(tmp2)))
129
- if cl2 is None:
130
- cl2=tmp
131
- cl2_l1=self.backend.bk_L1(tmp)
132
- else:
133
- tmp=self.backend.bk_concat([self.backend.bk_zeros((m),dtype=self.backend.all_bk_type),tmp],axis=0)
134
- cl2+=2*tmp
135
- cl2_l1+=2*self.backend.bk_L1(tmp)
136
- cl2=cl2*(1+np.clip((np.arange(cl2.shape[0])-2*nside)/(3*nside),0,1))/(2*np.arange(cl2.shape[0])+1)* \
137
- (1+np.clip((np.arange(cl2.shape[0])-2.4*nside)/(2.5*nside),0,1))
138
- cl2_l1=cl2_l1*(1+np.clip((np.arange(cl2.shape[0])-2*nside)/(3*nside),0,1))/(2*np.arange(cl2.shape[0])+1)* \
139
- (1+np.clip((np.arange(cl2.shape[0])-2.4*nside)/(2.5*nside),0,1))
140
- return cl2,cl2_l1
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes