1 /*
2 * This file is part of DUCC.
3 *
4 * DUCC is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * DUCC is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with DUCC; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18
19 /*
20 * DUCC is being developed at the Max-Planck-Institut fuer Astrophysik
21 */
22
23 /*
24 * Copyright (C) 2017-2021 Max-Planck-Society
25 * Author: Martin Reinecke
26 */
27
28 #include <pybind11/pybind11.h>
29 #include <pybind11/numpy.h>
30 #include <vector>
31 #include <complex>
32
33 #include "ducc0/sht/sht.h"
34 #include "ducc0/sht/alm.h"
35 #include "ducc0/infra/string_utils.h"
36 #include "ducc0/infra/error_handling.h"
37 #include "ducc0/math/constants.h"
38 #include "ducc0/bindings/pybind_utils.h"
39
40 namespace ducc0 {
41
42 namespace detail_pymodule_sht {
43
44 using namespace std;
45
46 namespace py = pybind11;
47
48 auto None = py::none();
49
Py_GL_weights(size_t nlat,size_t nlon)50 py::array Py_GL_weights(size_t nlat, size_t nlon)
51 {
52 auto res = make_Pyarr<double>({nlat});
53 auto res2 = to_vmav<double,1>(res);
54 GL_Integrator integ(nlat);
55 auto wgt = integ.weights();
56 for (size_t i=0; i<res2.shape(0); ++i)
57 res2(i) = wgt[i]*twopi/nlon;
58 return move(res);
59 }
60
Py_GL_thetas(size_t nlat)61 py::array Py_GL_thetas(size_t nlat)
62 {
63 auto res = make_Pyarr<double>({nlat});
64 auto res2 = to_vmav<double,1>(res);
65 GL_Integrator integ(nlat);
66 auto x = integ.coords();
67 for (size_t i=0; i<res2.shape(0); ++i)
68 res2(i) = acos(-x[i]);
69 return move(res);
70 }
71
Py2_rotate_alm(const py::array & alm_,int64_t lmax,double psi,double theta,double phi,size_t nthreads)72 template<typename T> py::array Py2_rotate_alm(const py::array &alm_, int64_t lmax,
73 double psi, double theta, double phi, size_t nthreads)
74 {
75 auto a1 = to_cmav<complex<T>,1>(alm_);
76 auto alm = make_Pyarr<complex<T>>({a1.shape(0)});
77 auto a2 = to_vmav<complex<T>,1>(alm);
78 {
79 py::gil_scoped_release release;
80 for (size_t i=0; i<a1.shape(0); ++i) a2(i)=a1(i);
81 Alm_Base base(lmax,lmax);
82 rotate_alm(base, a2, psi, theta, phi, nthreads);
83 }
84 return move(alm);
85 }
Py_rotate_alm(const py::array & alm,int64_t lmax,double psi,double theta,double phi,size_t nthreads)86 py::array Py_rotate_alm(const py::array &alm, int64_t lmax,
87 double psi, double theta, double phi, size_t nthreads)
88 {
89 if (isPyarr<complex<float>>(alm))
90 return Py2_rotate_alm<float>(alm, lmax, psi, theta, phi, nthreads);
91 if (isPyarr<complex<double>>(alm))
92 return Py2_rotate_alm<double>(alm, lmax, psi, theta, phi, nthreads);
93 MR_fail("type matching failed: 'alm' has neither type 'c8' nor 'c16'");
94 }
95
getmstuff(size_t lmax,const py::object & mval_,const py::object & mstart_,vmav<size_t,1> & mval,vmav<size_t,1> & mstart)96 void getmstuff(size_t lmax, const py::object &mval_, const py::object &mstart_,
97 vmav<size_t,1> &mval, vmav<size_t,1> &mstart)
98 {
99 MR_assert(mval_.is_none()==mstart_.is_none(), "mval and mstart must be supplied together");
100 if (mval_.is_none())
101 {
102 vmav<size_t,1> tmv({lmax+1});
103 mval.assign(tmv);
104 vmav<size_t,1> tms({lmax+1});
105 mstart.assign(tms);
106 for (size_t m=0, idx=0; m<=lmax; ++m, idx+=lmax+1-m)
107 {
108 mval(m) = m;
109 mstart(m) = idx;
110 }
111 }
112 else
113 {
114 auto tmval = to_cmav<int64_t,1>(mval_);
115 auto tmstart = to_cmav<int64_t,1>(mstart_);
116 size_t nm = tmval.shape(0);
117 MR_assert(nm==tmstart.shape(0), "size mismatch between mval and mstart");
118 vmav<size_t,1> tmv({nm});
119 mval.assign(tmv);
120 vmav<size_t,1> tms({nm});
121 mstart.assign(tms);
122 for (size_t i=0; i<nm; ++i)
123 {
124 auto m = tmval(i);
125 MR_assert((m>=0) && (m<=int64_t(lmax)), "bad m value");
126 mval(i) = size_t(m);
127 mstart(i) = size_t(tmstart(i));
128 }
129 }
130 }
get_mstart(size_t lmax,const py::object & mstart_)131 cmav<size_t,1> get_mstart(size_t lmax, const py::object &mstart_)
132 {
133 if (mstart_.is_none())
134 {
135 vmav<size_t,1> mstart({lmax+1});
136 for (size_t m=0, idx=0; m<=lmax; ++m, idx+=lmax+1-m)
137 mstart(m) = idx;
138 return mstart;
139 }
140 auto mstart = to_cmav<size_t,1>(mstart_);
141 MR_assert(mstart.shape(0)==lmax+1, "bad mstart size");
142 return mstart;
143 }
144
Py_get_gridweights(const string & type,size_t ntheta)145 py::array Py_get_gridweights(const string &type, size_t ntheta)
146 {
147 auto wgt_ = make_Pyarr<double>({ntheta});
148 auto wgt = to_vmav<double,1>(wgt_);
149 get_gridweights(type, wgt);
150 return wgt_;
151 }
152
min_almdim(size_t lmax,const cmav<size_t,1> & mval,const cmav<size_t,1> & mstart,ptrdiff_t lstride)153 size_t min_almdim(size_t lmax, const cmav<size_t,1> &mval, const cmav<size_t,1> &mstart, ptrdiff_t lstride)
154 {
155 size_t res=0;
156 for (size_t i=0; i<mval.shape(0); ++i)
157 {
158 auto ifirst = ptrdiff_t(mstart(i)) + ptrdiff_t(mval(i))*lstride;
159 MR_assert(ifirst>=0, "impossible a_lm memory layout");
160 auto ilast = ptrdiff_t(mstart(i)) + ptrdiff_t(lmax)*lstride;
161 MR_assert(ilast>=0, "impossible a_lm memory layout");
162 res = max(res, size_t(max(ifirst, ilast)));
163 }
164 return res+1;
165 }
min_mapdim(const cmav<size_t,1> & nphi,const cmav<size_t,1> & ringstart,ptrdiff_t pixstride)166 size_t min_mapdim(const cmav<size_t,1> &nphi, const cmav<size_t,1> &ringstart, ptrdiff_t pixstride)
167 {
168 size_t res=0;
169 for (size_t i=0; i<nphi.shape(0); ++i)
170 {
171 auto ilast = ptrdiff_t(ringstart(i)) + ptrdiff_t(nphi(i)-1)*pixstride;
172 MR_assert(ilast>=0, "impossible map memory layout");
173 res = max(res, max(ringstart(i), size_t(ilast)));
174 }
175 return res+1;
176 }
177
Py2_alm2leg(const py::array & alm_,size_t spin,size_t lmax,const py::object & mval_,const py::object & mstart_,ptrdiff_t lstride,const py::array & theta_,size_t nthreads,py::object & leg__)178 template<typename T> py::array Py2_alm2leg(const py::array &alm_, size_t spin, size_t lmax, const py::object &mval_, const py::object &mstart_, ptrdiff_t lstride, const py::array &theta_, size_t nthreads, py::object &leg__)
179 {
180 auto alm = to_cmav<complex<T>,2>(alm_);
181 auto theta = to_cmav<double,1>(theta_);
182 vmav<size_t,1> mval, mstart;
183 getmstuff(lmax, mval_, mstart_, mval, mstart);
184 MR_assert(alm.shape(1)>=min_almdim(lmax, mval, mstart, lstride), "bad a_lm array size");
185 auto leg_ = get_optional_Pyarr<complex<T>>(leg__, {alm.shape(0),theta.shape(0),mval.shape(0)});
186 auto leg = to_vmav<complex<T>,3>(leg_);
187 {
188 py::gil_scoped_release release;
189 alm2leg(alm, leg, spin, lmax, mval, mstart, lstride, theta, nthreads, ALM2MAP);
190 }
191 return leg_;
192 }
Py_alm2leg(const py::array & alm,size_t lmax,const py::array & theta,size_t spin,const py::object & mval,const py::object & mstart,ptrdiff_t lstride,size_t nthreads,py::object & leg)193 py::array Py_alm2leg(const py::array &alm, size_t lmax, const py::array &theta, size_t spin, const py::object &mval, const py::object &mstart, ptrdiff_t lstride, size_t nthreads, py::object &leg)
194 {
195 if (isPyarr<complex<float>>(alm))
196 return Py2_alm2leg<float>(alm, spin, lmax, mval, mstart, lstride, theta, nthreads, leg);
197 if (isPyarr<complex<double>>(alm))
198 return Py2_alm2leg<double>(alm, spin, lmax, mval, mstart, lstride, theta, nthreads, leg);
199 MR_fail("type matching failed: 'alm' has neither type 'c8' nor 'c16'");
200 }
Py2_alm2leg_deriv1(const py::array & alm_,size_t lmax,const py::object & mval_,const py::object & mstart_,ptrdiff_t lstride,const py::array & theta_,size_t nthreads,py::object & leg__)201 template<typename T> py::array Py2_alm2leg_deriv1(const py::array &alm_, size_t lmax, const py::object &mval_, const py::object &mstart_, ptrdiff_t lstride, const py::array &theta_, size_t nthreads, py::object &leg__)
202 {
203 auto alm = to_cmav<complex<T>,2>(alm_);
204 auto theta = to_cmav<double,1>(theta_);
205 vmav<size_t,1> mval, mstart;
206 getmstuff(lmax, mval_, mstart_, mval, mstart);
207 MR_assert(alm.shape(0)==1, "need exactly 1 a_lm component");
208 MR_assert(alm.shape(1)>=min_almdim(lmax, mval, mstart, lstride), "bad a_lm array size");
209 auto leg_ = get_optional_Pyarr<complex<T>>(leg__, {2,theta.shape(0),mval.shape(0)});
210 auto leg = to_vmav<complex<T>,3>(leg_);
211 {
212 py::gil_scoped_release release;
213 alm2leg(alm, leg, 0, lmax, mval, mstart, lstride, theta, nthreads, ALM2MAP_DERIV1);
214 }
215 return leg_;
216 }
Py_alm2leg_deriv1(const py::array & alm,size_t lmax,const py::array & theta,const py::object & mval,const py::object & mstart,ptrdiff_t lstride,size_t nthreads,py::object & leg)217 py::array Py_alm2leg_deriv1(const py::array &alm, size_t lmax, const py::array &theta, const py::object &mval, const py::object &mstart, ptrdiff_t lstride, size_t nthreads, py::object &leg)
218 {
219 if (isPyarr<complex<float>>(alm))
220 return Py2_alm2leg_deriv1<float>(alm, lmax, mval, mstart, lstride, theta, nthreads, leg);
221 if (isPyarr<complex<double>>(alm))
222 return Py2_alm2leg_deriv1<double>(alm, lmax, mval, mstart, lstride, theta, nthreads, leg);
223 MR_fail("type matching failed: 'alm' has neither type 'c8' nor 'c16'");
224 }
Py2_leg2alm(const py::array & leg_,const py::array & theta_,size_t spin,size_t lmax,const py::object & mval_,const py::object & mstart_,ptrdiff_t lstride,size_t nthreads,py::object & alm__)225 template<typename T> py::array Py2_leg2alm(const py::array &leg_, const py::array &theta_, size_t spin, size_t lmax, const py::object &mval_, const py::object &mstart_, ptrdiff_t lstride, size_t nthreads, py::object &alm__)
226 {
227 auto leg = to_cmav<complex<T>,3>(leg_);
228 auto theta = to_cmav<double,1>(theta_);
229 MR_assert(leg.shape(1)==theta.shape(0), "bad leg array size");
230 vmav<size_t,1> mval, mstart;
231 getmstuff(lmax, mval_, mstart_, mval, mstart);
232 auto alm_ = get_optional_Pyarr_minshape<complex<T>>(alm__, {leg.shape(0),min_almdim(lmax, mval, mstart, lstride)});
233 auto alm = to_vmav<complex<T>,2>(alm_);
234 MR_assert(alm.shape(0)==leg.shape(0), "bad number of components in a_lm array");
235 {
236 py::gil_scoped_release release;
237 leg2alm(alm, leg, spin, lmax, mval, mstart, lstride, theta, nthreads);
238 }
239 return alm_;
240 }
Py_leg2alm(const py::array & leg,size_t lmax,const py::array & theta,size_t spin,const py::object & mval,const py::object & mstart,ptrdiff_t lstride,size_t nthreads,py::object & alm)241 py::array Py_leg2alm(const py::array &leg, size_t lmax, const py::array &theta, size_t spin, const py::object &mval, const py::object &mstart, ptrdiff_t lstride, size_t nthreads, py::object &alm)
242 {
243 if (isPyarr<complex<float>>(leg))
244 return Py2_leg2alm<float>(leg, theta, spin, lmax, mval, mstart, lstride, nthreads, alm);
245 if (isPyarr<complex<double>>(leg))
246 return Py2_leg2alm<double>(leg, theta, spin, lmax, mval, mstart, lstride, nthreads, alm);
247 MR_fail("type matching failed: 'leg' has neither type 'c8' nor 'c16'");
248 }
Py2_map2leg(const py::array & map_,const py::array & nphi_,const py::array & phi0_,const py::array & ringstart_,size_t mmax,ptrdiff_t pixstride,size_t nthreads,py::object & leg__)249 template<typename T> py::array Py2_map2leg(const py::array &map_, const py::array &nphi_, const py::array &phi0_, const py::array &ringstart_, size_t mmax, ptrdiff_t pixstride, size_t nthreads, py::object &leg__)
250 {
251 auto map = to_cmav<T,2>(map_);
252 auto nphi = to_cmav<size_t,1>(nphi_);
253 auto phi0 = to_cmav<double,1>(phi0_);
254 auto ringstart = to_cmav<size_t,1>(ringstart_);
255 MR_assert(map.shape(1)>=min_mapdim(nphi, ringstart, pixstride), "bad map array size");
256 auto leg_ = get_optional_Pyarr<complex<T>>(leg__, {map.shape(0),nphi.shape(0),mmax+1});
257 auto leg = to_vmav<complex<T>,3>(leg_);
258 {
259 py::gil_scoped_release release;
260 map2leg(map, leg, nphi, phi0, ringstart, pixstride, nthreads);
261 }
262 return leg_;
263 }
Py_map2leg(const py::array & map,const py::array & nphi,const py::array & phi0,const py::array & ringstart,size_t mmax,ptrdiff_t pixstride,size_t nthreads,py::object & leg)264 py::array Py_map2leg(const py::array &map, const py::array &nphi, const py::array &phi0, const py::array &ringstart, size_t mmax, ptrdiff_t pixstride, size_t nthreads, py::object &leg)
265 {
266 if (isPyarr<float>(map))
267 return Py2_map2leg<float>(map, nphi, phi0, ringstart, mmax, pixstride, nthreads, leg);
268 if (isPyarr<double>(map))
269 return Py2_map2leg<double>(map, nphi, phi0, ringstart, mmax, pixstride, nthreads, leg);
270 MR_fail("type matching failed: 'map' has neither type 'f4' nor 'f8'");
271 }
Py2_leg2map(const py::array & leg_,const py::array & nphi_,const py::array & phi0_,const py::array & ringstart_,ptrdiff_t pixstride,size_t nthreads,py::object & map__)272 template<typename T> py::array Py2_leg2map(const py::array &leg_, const py::array &nphi_, const py::array &phi0_, const py::array &ringstart_, ptrdiff_t pixstride, size_t nthreads, py::object &map__)
273 {
274 auto leg = to_cmav<complex<T>,3>(leg_);
275 auto nphi = to_cmav<size_t,1>(nphi_);
276 auto phi0 = to_cmav<double,1>(phi0_);
277 auto ringstart = to_cmav<size_t,1>(ringstart_);
278 auto map_ = get_optional_Pyarr_minshape<T>(map__, {leg.shape(0),min_mapdim(nphi, ringstart, pixstride)});
279 auto map = to_vmav<T,2>(map_);
280 MR_assert(map.shape(0)==leg.shape(0), "bad number of components in map array");
281 {
282 py::gil_scoped_release release;
283 leg2map(map, leg, nphi, phi0, ringstart, pixstride, nthreads);
284 }
285 return map_;
286 }
Py_leg2map(const py::array & leg,const py::array & nphi,const py::array & phi0,const py::array & ringstart,ptrdiff_t pixstride,size_t nthreads,py::object & map)287 py::array Py_leg2map(const py::array &leg, const py::array &nphi, const py::array &phi0, const py::array &ringstart, ptrdiff_t pixstride, size_t nthreads, py::object &map)
288 {
289 if (isPyarr<complex<float>>(leg))
290 return Py2_leg2map<float>(leg, nphi, phi0, ringstart, pixstride, nthreads, map);
291 if (isPyarr<complex<double>>(leg))
292 return Py2_leg2map<double>(leg, nphi, phi0, ringstart, pixstride, nthreads, map);
293 MR_fail("type matching failed: 'leg' has neither type 'c8' nor 'c16'");
294 }
295
296 // FIXME: open questions
297 // - do we build mstart automatically and just take mmax?
298 // - phi0, ringstart = None => build assuming phi0=0, rings sequential?
299 // - accept scalar nphi, phi0?
Py2_synthesis(const py::array & alm_,py::object & map__,size_t spin,size_t lmax,const py::object & mstart_,ptrdiff_t lstride,const py::array & theta_,const py::array & nphi_,const py::array & phi0_,const py::array & ringstart_,ptrdiff_t pixstride,size_t nthreads)300 template<typename T> py::array Py2_synthesis(const py::array &alm_,
301 py::object &map__, size_t spin, size_t lmax,
302 const py::object &mstart_, ptrdiff_t lstride,
303 const py::array &theta_,
304 const py::array &nphi_,
305 const py::array &phi0_, const py::array &ringstart_,
306 ptrdiff_t pixstride, size_t nthreads)
307 {
308 auto alm = to_cmav<complex<T>,2>(alm_);
309 auto mstart = get_mstart(lmax, mstart_);
310 auto theta = to_cmav<double,1>(theta_);
311 auto phi0 = to_cmav<double,1>(phi0_);
312 auto nphi = to_cmav<size_t,1>(nphi_);
313 auto ringstart = to_cmav<size_t,1>(ringstart_);
314 auto map_ = get_optional_Pyarr_minshape<T>(map__, {alm.shape(0), min_mapdim(nphi, ringstart, pixstride)});
315 auto map = to_vmav<T,2>(map_);
316 MR_assert(map.shape(0)==alm.shape(0), "bad number of components in map array");
317 {
318 py::gil_scoped_release release;
319 synthesis(alm, map, spin, lmax, mstart, lstride, theta, nphi, phi0, ringstart, pixstride, nthreads, ALM2MAP);
320 }
321 return map_;
322 }
Py_synthesis(const py::array & alm,const py::array & theta,size_t lmax,const py::object & mstart,const py::array & nphi,const py::array & phi0,const py::array & ringstart,size_t spin,ptrdiff_t lstride,ptrdiff_t pixstride,size_t nthreads,py::object & map)323 py::array Py_synthesis(const py::array &alm, const py::array &theta,
324 size_t lmax, const py::object &mstart,
325 const py::array &nphi,
326 const py::array &phi0, const py::array &ringstart, size_t spin, ptrdiff_t lstride, ptrdiff_t pixstride,
327 size_t nthreads, py::object &map)
328 {
329 if (isPyarr<complex<float>>(alm))
330 return Py2_synthesis<float>(alm, map, spin, lmax, mstart, lstride, theta,
331 nphi, phi0, ringstart, pixstride, nthreads);
332 else if (isPyarr<complex<double>>(alm))
333 return Py2_synthesis<double>(alm, map, spin, lmax, mstart, lstride, theta,
334 nphi, phi0, ringstart, pixstride, nthreads);
335 MR_fail("type matching failed: 'alm' has neither type 'c8' nor 'c16'");
336 }
Py2_synthesis_deriv1(const py::array & alm_,py::object & map__,size_t lmax,const py::object & mstart_,ptrdiff_t lstride,const py::array & theta_,const py::array & nphi_,const py::array & phi0_,const py::array & ringstart_,ptrdiff_t pixstride,size_t nthreads)337 template<typename T> py::array Py2_synthesis_deriv1(const py::array &alm_,
338 py::object &map__, size_t lmax,
339 const py::object &mstart_, ptrdiff_t lstride,
340 const py::array &theta_,
341 const py::array &nphi_,
342 const py::array &phi0_, const py::array &ringstart_,
343 ptrdiff_t pixstride, size_t nthreads)
344 {
345 auto alm = to_cmav<complex<T>,2>(alm_);
346 auto mstart = get_mstart(lmax, mstart_);
347 auto theta = to_cmav<double,1>(theta_);
348 auto phi0 = to_cmav<double,1>(phi0_);
349 auto nphi = to_cmav<size_t,1>(nphi_);
350 auto ringstart = to_cmav<size_t,1>(ringstart_);
351 auto map_ = get_optional_Pyarr_minshape<T>(map__, {alm.shape(0), min_mapdim(nphi, ringstart, pixstride)});
352 auto map = to_vmav<T,2>(map_);
353 MR_assert(map.shape(0)==2, "bad number of components in map array");
354 {
355 py::gil_scoped_release release;
356 synthesis(alm, map, 1, lmax, mstart, lstride, theta, nphi, phi0, ringstart, pixstride, nthreads, ALM2MAP_DERIV1);
357 }
358 return map_;
359 }
Py_synthesis_deriv1(const py::array & alm,const py::array & theta,size_t lmax,const py::object & mstart,const py::array & nphi,const py::array & phi0,const py::array & ringstart,ptrdiff_t lstride,ptrdiff_t pixstride,size_t nthreads,py::object & map)360 py::array Py_synthesis_deriv1(const py::array &alm, const py::array &theta,
361 size_t lmax, const py::object &mstart,
362 const py::array &nphi,
363 const py::array &phi0, const py::array &ringstart, ptrdiff_t lstride, ptrdiff_t pixstride,
364 size_t nthreads, py::object &map)
365 {
366 if (isPyarr<complex<float>>(alm))
367 return Py2_synthesis_deriv1<float>(alm, map, lmax, mstart, lstride, theta,
368 nphi, phi0, ringstart, pixstride, nthreads);
369 else if (isPyarr<complex<double>>(alm))
370 return Py2_synthesis_deriv1<double>(alm, map, lmax, mstart, lstride, theta,
371 nphi, phi0, ringstart, pixstride, nthreads);
372 MR_fail("type matching failed: 'alm' has neither type 'c8' nor 'c16'");
373 }
374
375
check_build_map(const py::object & map,size_t ncomp,const py::object & ntheta,const py::object & nphi)376 template<typename T> py::array_t<T> check_build_map(const py::object &map, size_t ncomp, const py::object &ntheta, const py::object &nphi)
377 {
378 if (map.is_none())
379 {
380 MR_assert((!ntheta.is_none()) && (!nphi.is_none()),
381 "you need to specify either 'map' or 'ntheta' and 'nphi'");
382 return make_Pyarr<T>({ncomp, ntheta.cast<size_t>(), nphi.cast<size_t>()});
383 }
384 else
385 {
386 py::array_t<T> tmap = map;
387 MR_assert((size_t(tmap.ndim())==3) && (size_t(tmap.shape(0))==ncomp), "map size mismatch");
388 if (!ntheta.is_none())
389 MR_assert(size_t(tmap.shape(1))==ntheta.cast<size_t>(), "ntheta mismatch");
390 if (!nphi.is_none())
391 MR_assert(size_t(tmap.shape(2))==nphi.cast<size_t>(), "nphi mismatch");
392 return tmap;
393 }
394 }
check_build_alm(const py::object & alm,size_t ncomp,size_t lmax,size_t mmax)395 template<typename T> py::array_t<complex<T>> check_build_alm(const py::object &alm, size_t ncomp, size_t lmax, size_t mmax)
396 {
397 size_t nalm = ((mmax+1)*(mmax+2))/2 + (mmax+1)*(lmax-mmax);
398 if (alm.is_none())
399 {
400 MR_assert(lmax>=mmax, "mmax must not be larger than lmax");
401 return make_Pyarr<complex<T>>({ncomp, nalm});
402 }
403 else
404 {
405 py::array_t<complex<T>> talm = alm;
406 MR_assert((size_t(talm.ndim())==2) && (size_t(talm.shape(0))==ncomp)
407 && (size_t(talm.shape(1))==nalm), "alm size mismatch");
408 return talm;
409 }
410 }
411
Py2_synthesis_2d(const py::array & alm_,size_t spin,size_t lmax,const string & geometry,const py::object & ntheta,const py::object & nphi,size_t mmax,size_t nthreads,py::object & map__)412 template<typename T> py::array Py2_synthesis_2d(const py::array &alm_,
413 size_t spin, size_t lmax, const string &geometry, const py::object & ntheta,
414 const py::object &nphi, size_t mmax, size_t nthreads, py::object &map__)
415 {
416 auto alm = to_cmav<complex<T>,2>(alm_);
417 auto map_ = check_build_map<T>(map__, alm.shape(0), ntheta, nphi);
418 auto map = to_vmav<T,3>(map_);
419 MR_assert(map.shape(0)==alm.shape(0), "bad number of components in map array");
420 {
421 py::gil_scoped_release release;
422 synthesis_2d(alm, map, spin, lmax, mmax, geometry, nthreads);
423 }
424 return map_;
425 }
Py_synthesis_2d(const py::array & alm,size_t spin,size_t lmax,const string & geometry,const py::object & ntheta,const py::object & nphi,const py::object & mmax_,size_t nthreads,py::object & map)426 py::array Py_synthesis_2d(const py::array &alm, size_t spin, size_t lmax, const string &geometry, const py::object &ntheta, const py::object &nphi, const py::object &mmax_, size_t nthreads, py::object &map)
427 {
428 size_t mmax = mmax_.is_none() ? lmax : mmax_.cast<size_t>();
429 if (isPyarr<complex<float>>(alm))
430 return Py2_synthesis_2d<float>(alm, spin, lmax, geometry, ntheta, nphi, mmax, nthreads, map);
431 else if (isPyarr<complex<double>>(alm))
432 return Py2_synthesis_2d<double>(alm, spin, lmax, geometry, ntheta, nphi, mmax, nthreads, map);
433 MR_fail("type matching failed: 'alm' has neither type 'c8' nor 'c16'");
434 }
Py2_adjoint_synthesis_2d(const py::array & map_,size_t spin,size_t lmax,const string & geometry,size_t mmax,size_t nthreads,py::object & alm__)435 template<typename T> py::array Py2_adjoint_synthesis_2d(
436 const py::array &map_, size_t spin, size_t lmax, const string &geometry, size_t mmax, size_t nthreads, py::object &alm__)
437 {
438 auto map = to_cmav<T,3>(map_);
439 auto alm_ = check_build_alm<T>(alm__, map.shape(0), lmax, mmax);
440 auto alm = to_vmav<complex<T>,2>(alm_);
441 MR_assert(map.shape(0)==alm.shape(0), "bad number of components in map array");
442 {
443 py::gil_scoped_release release;
444 adjoint_synthesis_2d(alm, map, spin, lmax, mmax, geometry, nthreads);
445 }
446 return alm_;
447 }
Py_adjoint_synthesis_2d(const py::array & map,size_t spin,size_t lmax,const string & geometry,const py::object & mmax_,size_t nthreads,py::object & alm)448 py::array Py_adjoint_synthesis_2d(
449 const py::array &map, size_t spin, size_t lmax, const string &geometry, const py::object &mmax_, size_t nthreads, py::object &alm)
450 {
451 size_t mmax = mmax_.is_none() ? lmax : mmax_.cast<size_t>();
452 if (isPyarr<float>(map))
453 return Py2_adjoint_synthesis_2d<float>(map, spin, lmax, geometry, mmax, nthreads, alm);
454 else if (isPyarr<double>(map))
455 return Py2_adjoint_synthesis_2d<double>(map, spin, lmax, geometry, mmax, nthreads, alm);
456 MR_fail("type matching failed: 'alm' has neither type 'c8' nor 'c16'");
457 }
Py2_synthesis_2d_deriv1(const py::array & alm_,size_t lmax,const string & geometry,const py::object & ntheta,const py::object & nphi,size_t mmax,size_t nthreads,py::object & map__)458 template<typename T> py::array Py2_synthesis_2d_deriv1(const py::array &alm_,
459 size_t lmax, const string &geometry, const py::object & ntheta,
460 const py::object &nphi, size_t mmax, size_t nthreads, py::object &map__)
461 {
462 auto alm = to_cmav<complex<T>,2>(alm_);
463 auto map_ = check_build_map<T>(map__, 2, ntheta, nphi);
464 auto map = to_vmav<T,3>(map_);
465 MR_assert((map.shape(0)==2) && (alm.shape(0)==1), "incorrect number of components");
466 {
467 py::gil_scoped_release release;
468 synthesis_2d(alm, map, 1, lmax, mmax, geometry, nthreads, ALM2MAP_DERIV1);
469 }
470 return map_;
471 }
Py_synthesis_2d_deriv1(const py::array & alm,size_t lmax,const string & geometry,const py::object & ntheta,const py::object & nphi,const py::object & mmax_,size_t nthreads,py::object & map)472 py::array Py_synthesis_2d_deriv1(const py::array &alm, size_t lmax, const string &geometry, const py::object &ntheta, const py::object &nphi, const py::object &mmax_, size_t nthreads, py::object &map)
473 {
474 size_t mmax = mmax_.is_none() ? lmax : mmax_.cast<size_t>();
475 if (isPyarr<complex<float>>(alm))
476 return Py2_synthesis_2d_deriv1<float>(alm, lmax, geometry, ntheta, nphi, mmax, nthreads, map);
477 else if (isPyarr<complex<double>>(alm))
478 return Py2_synthesis_2d_deriv1<double>(alm, lmax, geometry, ntheta, nphi, mmax, nthreads, map);
479 MR_fail("type matching failed: 'alm' has neither type 'c8' nor 'c16'");
480 }
481
Py2_adjoint_synthesis(py::object & alm__,size_t lmax,const py::object & mstart_,ptrdiff_t lstride,const py::array & map_,const py::array & theta_,const py::array & phi0_,const py::array & nphi_,const py::array & ringstart_,size_t spin,ptrdiff_t pixstride,size_t nthreads)482 template<typename T> py::array Py2_adjoint_synthesis(py::object &alm__,
483 size_t lmax, const py::object &mstart_, ptrdiff_t lstride,
484 const py::array &map_, const py::array &theta_, const py::array &phi0_,
485 const py::array &nphi_, const py::array &ringstart_, size_t spin, ptrdiff_t pixstride,
486 size_t nthreads)
487 {
488 auto mstart = get_mstart(lmax, mstart_);
489 auto map = to_cmav<T,2>(map_);
490 auto theta = to_cmav<double,1>(theta_);
491 auto phi0 = to_cmav<double,1>(phi0_);
492 auto nphi = to_cmav<size_t,1>(nphi_);
493 auto ringstart = to_cmav<size_t,1>(ringstart_);
494 vmav<size_t,1> mval(mstart.shape());
495 for (size_t i=0; i<mval.shape(0); ++i)
496 mval(i) = i;
497 auto alm_ = get_optional_Pyarr_minshape<complex<T>>(alm__, {map.shape(0),min_almdim(lmax, mval, mstart, lstride)});
498 auto alm = to_vmav<complex<T>,2>(alm_);
499 MR_assert(alm.shape(0)==map.shape(0), "bad number of components in a_lm array");
500 {
501 py::gil_scoped_release release;
502 adjoint_synthesis(alm, map, spin, lmax, mstart, lstride, theta, nphi, phi0, ringstart, pixstride, nthreads);
503 }
504 return alm_;
505 }
Py_adjoint_synthesis(const py::array & map,const py::array & theta,size_t lmax,const py::object & mstart,const py::array & nphi,const py::array & phi0,const py::array & ringstart,size_t spin,ptrdiff_t lstride,ptrdiff_t pixstride,size_t nthreads,py::object & alm)506 py::array Py_adjoint_synthesis(const py::array &map, const py::array &theta,
507 size_t lmax,
508 const py::object &mstart,
509 const py::array &nphi,
510 const py::array &phi0, const py::array &ringstart, size_t spin, ptrdiff_t lstride, ptrdiff_t pixstride,
511 size_t nthreads,
512 py::object &alm)
513 {
514 if (isPyarr<float>(map))
515 return Py2_adjoint_synthesis<float>(alm, lmax, mstart, lstride, map, theta,
516 phi0, nphi, ringstart, spin, pixstride, nthreads);
517 else if (isPyarr<double>(map))
518 return Py2_adjoint_synthesis<double>(alm, lmax, mstart, lstride, map, theta,
519 phi0, nphi, ringstart, spin, pixstride, nthreads);
520 MR_fail("type matching failed: 'alm' has neither type 'c8' nor 'c16'");
521 }
522
Py2_analysis_2d(const py::array & map_,size_t spin,size_t lmax,const string & geometry,size_t mmax,size_t nthreads,py::object & alm__)523 template<typename T> py::array Py2_analysis_2d(
524 const py::array &map_, size_t spin, size_t lmax, const string &geometry, size_t mmax, size_t nthreads, py::object &alm__)
525 {
526 auto map = to_cmav<T,3>(map_);
527 auto alm_ = check_build_alm<T>(alm__, map.shape(0), lmax, mmax);
528 auto alm = to_vmav<complex<T>,2>(alm_);
529 MR_assert(map.shape(0)==alm.shape(0), "bad number of components in map array");
530 {
531 py::gil_scoped_release release;
532 analysis_2d(alm, map, spin, lmax, mmax, geometry, nthreads);
533 }
534 return alm_;
535 }
Py_analysis_2d(const py::array & map,size_t spin,size_t lmax,const string & geometry,py::object & mmax_,size_t nthreads,py::object & alm)536 py::array Py_analysis_2d(
537 const py::array &map, size_t spin, size_t lmax, const string &geometry, py::object &mmax_, size_t nthreads, py::object &alm)
538 {
539 size_t mmax = mmax_.is_none() ? lmax : mmax_.cast<size_t>();
540 if (isPyarr<float>(map))
541 return Py2_analysis_2d<float>(map, spin, lmax, geometry, mmax, nthreads, alm);
542 else if (isPyarr<double>(map))
543 return Py2_analysis_2d<double>(map, spin, lmax, geometry, mmax, nthreads, alm);
544 MR_fail("type matching failed: 'alm' has neither type 'c8' nor 'c16'");
545 }
546
Py2_adjoint_analysis_2d(const py::array & alm_,size_t spin,size_t lmax,const string & geometry,const py::object & ntheta,const py::object & nphi,size_t mmax,size_t nthreads,py::object & map__)547 template<typename T> py::array Py2_adjoint_analysis_2d(const py::array &alm_,
548 size_t spin, size_t lmax, const string &geometry, const py::object & ntheta,
549 const py::object &nphi, size_t mmax, size_t nthreads, py::object &map__)
550 {
551 auto alm = to_cmav<complex<T>,2>(alm_);
552 auto map_ = check_build_map<T>(map__, alm.shape(0), ntheta, nphi);
553 auto map = to_vmav<T,3>(map_);
554 MR_assert(map.shape(0)==alm.shape(0), "bad number of components in map array");
555 {
556 py::gil_scoped_release release;
557 adjoint_analysis_2d(alm, map, spin, lmax, mmax, geometry, nthreads);
558 }
559 return map_;
560 }
Py_adjoint_analysis_2d(const py::array & alm,size_t spin,size_t lmax,const string & geometry,const py::object & ntheta,const py::object & nphi,const py::object & mmax_,size_t nthreads,py::object & map)561 py::array Py_adjoint_analysis_2d(const py::array &alm, size_t spin, size_t lmax, const string &geometry, const py::object &ntheta, const py::object &nphi, const py::object &mmax_, size_t nthreads, py::object &map)
562 {
563 size_t mmax = mmax_.is_none() ? lmax : mmax_.cast<size_t>();
564 if (isPyarr<complex<float>>(alm))
565 return Py2_adjoint_analysis_2d<float>(alm, spin, lmax, geometry, ntheta, nphi, mmax, nthreads, map);
566 else if (isPyarr<complex<double>>(alm))
567 return Py2_adjoint_analysis_2d<double>(alm, spin, lmax, geometry, ntheta, nphi, mmax, nthreads, map);
568 MR_fail("type matching failed: 'alm' has neither type 'c8' nor 'c16'");
569 }
570
571
572 template<typename T> class Py_sharpjob
573 {
574 private:
575 int64_t lmax_, mmax_, ntheta_, nphi_, nside_, npix_;
576 string geom;
577 int nthreads;
578
579 public:
Py_sharpjob()580 Py_sharpjob () : lmax_(0), mmax_(0), ntheta_(0), nphi_(0), nside_(0),
581 npix_(0), nthreads(1) {}
582
repr() const583 string repr() const
584 {
585 return "<sharpjob_d: lmax=" + dataToString(lmax_) +
586 ", mmax=" + dataToString(mmax_) + ", npix=", dataToString(npix_) +".>";
587 }
588
set_nthreads(int64_t nthreads_)589 void set_nthreads(int64_t nthreads_)
590 { nthreads = int(nthreads_); }
set_gauss_geometry(int64_t ntheta,int64_t nphi)591 void set_gauss_geometry(int64_t ntheta, int64_t nphi)
592 {
593 MR_assert((ntheta>0)&&(nphi>0),"bad grid dimensions");
594 geom = "GL";
595 ntheta_ = ntheta;
596 nphi_ = nphi;
597 npix_=ntheta*nphi;
598 }
set_healpix_geometry(int64_t nside)599 void set_healpix_geometry(int64_t nside)
600 {
601 MR_assert(nside>0,"bad Nside value");
602 geom = "HP";
603 nside_ = nside;
604 npix_=12*nside*nside;
605 }
set_fejer1_geometry(int64_t ntheta,int64_t nphi)606 void set_fejer1_geometry(int64_t ntheta, int64_t nphi)
607 {
608 MR_assert((ntheta>0)&&(nphi>0),"bad grid dimensions");
609 geom = "F1";
610 ntheta_ = ntheta;
611 nphi_ = nphi;
612 npix_=ntheta*nphi;
613 }
set_fejer2_geometry(int64_t ntheta,int64_t nphi)614 void set_fejer2_geometry(int64_t ntheta, int64_t nphi)
615 {
616 MR_assert((ntheta>0)&&(nphi>0),"bad grid dimensions");
617 geom = "F2";
618 ntheta_ = ntheta;
619 nphi_ = nphi;
620 npix_=ntheta*nphi;
621 }
set_cc_geometry(int64_t ntheta,int64_t nphi)622 void set_cc_geometry(int64_t ntheta, int64_t nphi)
623 {
624 MR_assert((ntheta>0)&&(nphi>0),"bad grid dimensions");
625 geom = "CC";
626 ntheta_ = ntheta;
627 nphi_ = nphi;
628 npix_=ntheta*nphi;
629 }
set_dh_geometry(int64_t ntheta,int64_t nphi)630 void set_dh_geometry(int64_t ntheta, int64_t nphi)
631 {
632 MR_assert((ntheta>0)&&(nphi>0),"bad grid dimensions");
633 geom = "DH";
634 ntheta_ = ntheta;
635 nphi_ = nphi;
636 npix_=ntheta*nphi;
637 }
set_mw_geometry(int64_t ntheta,int64_t nphi)638 void set_mw_geometry(int64_t ntheta, int64_t nphi)
639 {
640 MR_assert((ntheta>0)&&(nphi>0),"bad grid dimensions");
641 geom = "MW";
642 ntheta_ = ntheta;
643 nphi_ = nphi;
644 npix_=ntheta*nphi;
645 }
set_triangular_alm_info(int64_t lmax,int64_t mmax)646 void set_triangular_alm_info (int64_t lmax, int64_t mmax)
647 {
648 MR_assert(mmax>=0,"negative mmax");
649 MR_assert(mmax<=lmax,"mmax must not be larger than lmax");
650 lmax_=lmax; mmax_=mmax;
651 }
652
n_alm() const653 int64_t n_alm() const
654 { return ((mmax_+1)*(mmax_+2))/2 + (mmax_+1)*(lmax_-mmax_); }
655
alm2map(const py::array_t<complex<double>> & alm_) const656 py::array alm2map (const py::array_t<complex<double>> &alm_) const
657 {
658 MR_assert(npix_>0,"no map geometry specified");
659 MR_assert (alm_.size()==n_alm(),
660 "incorrect size of a_lm array");
661 auto map_=make_Pyarr<double>({size_t(npix_)});
662 auto map=to_vmav<double,1>(map_);
663 auto alm=to_cmav<complex<double>,1>(alm_);
664 cmav<complex<double>,2> ar(alm.data(), {1, size_t(n_alm())}, {0, alm.stride(0)});
665 if (geom=="HP")
666 {
667 auto mstart = get_mstart(lmax_, None);
668 Healpix_Base2 base(nside_, RING, SET_NSIDE);
669 auto nrings = size_t(4*nside_-1);
670 auto theta_= make_Pyarr<double>({nrings});
671 vmav<double,1> theta({nrings}), phi0({nrings});
672 vmav<size_t,1> nphi({nrings}), ringstart({nrings});
673 for (size_t r=0, rs=nrings-1; r<=rs; ++r, --rs)
674 {
675 int64_t startpix, ringpix;
676 double ringtheta;
677 bool shifted;
678 base.get_ring_info2 (r+1, startpix, ringpix, ringtheta, shifted);
679 theta(r) = ringtheta;
680 theta(rs) = pi-ringtheta;
681 nphi(r) = nphi(rs) = size_t(ringpix);
682 phi0(r) = phi0(rs) = shifted ? (pi/ringpix) : 0.;
683 ringstart(r) = size_t(startpix);
684 ringstart(rs) = size_t(base.Npix() - startpix - ringpix);
685 }
686 vmav<double,2> mr(map.data(), {1, size_t(npix_)}, {0, map.stride(0)});
687 synthesis(ar, mr, 0, lmax_, mstart, 1, theta, nphi, phi0, ringstart, 1, nthreads);
688 }
689 else
690 {
691 vmav<double,3> mr(map.data(), {1, size_t(ntheta_), size_t(nphi_)}, {0, map.stride(0)*nphi_, map.stride(0)});
692 synthesis_2d(ar, mr, 0, lmax_, mmax_, geom, nthreads);
693 }
694 return map_;
695 }
alm2map_adjoint(const py::array_t<double> & map_) const696 py::array alm2map_adjoint (const py::array_t<double> &map_) const
697 {
698 MR_assert(npix_>0,"no map geometry specified");
699 MR_assert (map_.size()==npix_,"incorrect size of map array");
700 auto alm_=make_Pyarr<complex<double>>({size_t(n_alm())});
701 auto alm=to_vmav<complex<double>,1>(alm_);
702 vmav<complex<double>,2> ar(alm.data(), {1, size_t(n_alm())}, {0, alm.stride(0)});
703 auto map=to_cmav<double,1>(map_);
704 if (geom=="HP")
705 {
706 auto mstart = get_mstart(lmax_, None);
707 Healpix_Base2 base(nside_, RING, SET_NSIDE);
708 auto nrings = size_t(4*nside_-1);
709 auto theta_= make_Pyarr<double>({nrings});
710 vmav<double,1> theta({nrings}), phi0({nrings});
711 vmav<size_t,1> nphi({nrings}), ringstart({nrings});
712 for (size_t r=0, rs=nrings-1; r<=rs; ++r, --rs)
713 {
714 int64_t startpix, ringpix;
715 double ringtheta;
716 bool shifted;
717 base.get_ring_info2 (r+1, startpix, ringpix, ringtheta, shifted);
718 theta(r) = ringtheta;
719 theta(rs) = pi-ringtheta;
720 nphi(r) = nphi(rs) = size_t(ringpix);
721 phi0(r) = phi0(rs) = shifted ? (pi/ringpix) : 0.;
722 ringstart(r) = size_t(startpix);
723 ringstart(rs) = size_t(base.Npix() - startpix - ringpix);
724 }
725 cmav<double,2> mr(map.data(), {1, size_t(npix_)}, {0, map.stride(0)});
726 adjoint_synthesis(ar, mr, 0, lmax_, mstart, 1, theta, nphi, phi0, ringstart, 1, nthreads);
727 }
728 else
729 {
730 cmav<double,3> mr(map.data(), {1, size_t(ntheta_), size_t(nphi_)}, {0, map.stride(0)*nphi_, map.stride(0)});
731 adjoint_synthesis_2d(ar, mr, 0, lmax_, mmax_, geom, nthreads);
732 }
733 return alm_;
734 }
map2alm(const py::array_t<double> & map_) const735 py::array map2alm (const py::array_t<double> &map_) const
736 {
737 MR_assert(npix_>0,"no map geometry specified");
738 MR_assert (map_.size()==npix_,"incorrect size of map array");
739 auto alm_=make_Pyarr<complex<double>>({size_t(n_alm())});
740 auto alm=to_vmav<complex<double>,1>(alm_);
741 vmav<complex<double>,2> ar(alm.data(), {1, size_t(n_alm())}, {0, alm.stride(0)});
742 auto map=to_cmav<double,1>(map_);
743 cmav<double,3> mr(map.data(), {1, size_t(ntheta_), size_t(nphi_)}, {0, map.stride(0)*nphi_, map.stride(0)});
744 analysis_2d(ar, mr, 0, lmax_, mmax_, geom, nthreads);
745 return alm_;
746 }
alm2map_spin(const py::array_t<complex<double>> & alm_,int64_t spin) const747 py::array alm2map_spin (const py::array_t<complex<double>> &alm_, int64_t spin) const
748 {
749 MR_assert(npix_>0,"no map geometry specified");
750 auto map_=make_Pyarr<double>({2, size_t(npix_)});
751 auto map=to_vmav<double,2>(map_);
752 auto alm=to_cmav<complex<double>,2>(alm_);
753 MR_assert((alm.shape(0)==2)&&(alm.shape(1)==size_t(n_alm())),
754 "incorrect size of a_lm array");
755 if (geom=="HP")
756 {
757 auto mstart = get_mstart(lmax_, None);
758 Healpix_Base2 base(nside_, RING, SET_NSIDE);
759 auto nrings = size_t(4*nside_-1);
760 auto theta_= make_Pyarr<double>({nrings});
761 vmav<double,1> theta({nrings}), phi0({nrings});
762 vmav<size_t,1> nphi({nrings}), ringstart({nrings});
763 for (size_t r=0, rs=nrings-1; r<=rs; ++r, --rs)
764 {
765 int64_t startpix, ringpix;
766 double ringtheta;
767 bool shifted;
768 base.get_ring_info2 (r+1, startpix, ringpix, ringtheta, shifted);
769 theta(r) = ringtheta;
770 theta(rs) = pi-ringtheta;
771 nphi(r) = nphi(rs) = size_t(ringpix);
772 phi0(r) = phi0(rs) = shifted ? (pi/ringpix) : 0.;
773 ringstart(r) = size_t(startpix);
774 ringstart(rs) = size_t(base.Npix() - startpix - ringpix);
775 }
776 synthesis(alm, map, spin, lmax_, mstart, 1, theta, nphi, phi0, ringstart, 1, nthreads);
777 }
778 else
779 {
780 vmav<double,3> mr(map.data(), {2, size_t(ntheta_), size_t(nphi_)}, {map.stride(0), map.stride(1)*nphi_, map.stride(1)});
781 synthesis_2d(alm, mr, spin, lmax_, mmax_, geom, nthreads);
782 }
783 return map_;
784 }
map2alm_spin(const py::array_t<double> & map_,int64_t spin) const785 py::array map2alm_spin (const py::array_t<double> &map_, int64_t spin) const
786 {
787 MR_assert(npix_>0,"no map geometry specified");
788 MR_assert (map_.shape(1)==npix_,"incorrect size of map array");
789 auto alm_=make_Pyarr<complex<double>>({2, size_t(n_alm())});
790 auto alm=to_vmav<complex<double>,2>(alm_);
791 auto map=to_cmav<double,2>(map_);
792 cmav<double,3> mr(map.data(), {2, size_t(ntheta_), size_t(nphi_)}, {map.stride(0), map.stride(1)*nphi_, map.stride(1)});
793 analysis_2d(alm, mr, spin, lmax_, mmax_, geom, nthreads);
794 return alm_;
795 }
796 };
797
798 constexpr const char *sht_DS = R"""(
799 Python interface for spherical harmonic transforms and manipulation of
800 spherical harmonic coefficients.
801
802 Error conditions are reported by raising exceptions.
803 )""";
804 constexpr const char *sht_experimental_DS = R"""(
805 Experimental interface to the SHT functionality.
806
807 Notes
808 -----
809
810 The functionality in this module is not considered to have a stable interface
811 and also may be moved to other modules in the future. If you use it, be prepared
812 to adjust your code at some point ion the future!
813 )""";
814
815 constexpr const char *rotate_alm_DS = R"""(
816 Rotates a set of spherical harmonic coefficients according to the given Euler angles.
817
818 Parameters
819 ----------
820 alm: numpy.ndarray(((lmax+1)*(lmax=2)/2,), dtype=numpy complex64 or numpy.complex128)
821 the spherical harmonic coefficients, in the order
822 (0,0), (1,0), (2,0), ... (lmax,0), (1,1), (2,1), ..., (lmax, lmax)
823 lmax : int >= 0
824 Maximum multipole order l of the data set.
825 psi : float
826 First rotation angle about the z-axis. All angles are in radians,
827 the rotations are active and the referential system is assumed to be
828 right handed.
829 theta : float
830 Second rotation angl about the original (unrotated) y-axis
831 phi : float
832 Third rotation angle about the original (unrotated) z-axis.
833 nthreads: int >= 0
834 the number of threads to use for the computation
835 if 0, use as many threads as there are hardware threads available on the system
836
837 Returns
838 -------
839 numpy.ndarray(same shape and dtype as alm)
840 )""";
841
842 constexpr const char *alm2leg_DS = R"""(
843 Transforms a set of spherical harmonic coefficients to Legendre coefficients
844 dependent on theta and m.
845
846 Parameters
847 ----------
848 alm: numpy.ndarray((ncomp, x), dtype=numpy.complex64 or numpy.complex128)
849 the set of spherical harmonic coefficients.
850 ncomp must be 1 if spin is 0, else 2.
851 The second dimension must be large enough to accommodate all entries, which
852 are stored according to the parameters `lmax`, 'mval`, `mstart`, and `lstride`.
853 leg: None or numpy.ndarray((ncomp, ntheta, nm), same dtype as `alm`)
854 output array containing the Legendre coefficients
855 if `None`, a new suitable array is allocated
856 spin: int >= 0
857 the spin to use for the transform
858 if spin==0, ncomp must be 1, otherwise 2
859 lmax: int >= 0
860 the maximum l moment of the transform (inclusive)
861 mval: numpy.ndarray((nm,), dtype = numpy.uint64)
862 the m moments for which the transform should be carried out
863 entries must be unique and <= lmax
864 mstart: numpy.ndarray((nm,), dtype = numpy.uint64)
865 the (hypothetical) index in the second dimension of `alm` on which the
866 entry with l=0, m=mval[mi] would be stored, for mi in mval
867 lstride: int
868 the index stride in the second dimension of `alm` between the entries for
869 `l` and `l+1`, but the same `m`.
870 theta: numpy.ndarray((ntheta,), dtype=numpy.float64)
871 the colatitudes of the map rings
872 nthreads: int >= 0
873 the number of threads to use for the computation
874 if 0, use as many threads as there are hardware threads available on the system
875
876 Returns
877 -------
878 numpy.ndarray((ncomp, ntheta, nm), same dtype as `alm`)
879 the Legendre coefficients. If `leg` was supplied, this will be the same object.
880 )""";
881
882 constexpr const char *alm2leg_deriv1_DS = R"""(
883 Transforms a set of spin-0 spherical harmonic coefficients to Legendre
884 coefficients of the first derivatives with respect to colatiude and longitude,
885 dependent on theta and m.
886
887 Parameters
888 ----------
889 alm: numpy.ndarray((1, x), dtype=numpy.complex64 or numpy.complex128)
890 the set of spherical harmonic coefficients.
891 The second dimension must be large enough to accommodate all entries, which
892 are stored according to the parameters `lmax`, 'mval`, `mstart`, and `lstride`.
893 leg: None or numpy.ndarray((2, ntheta, nm), same dtype as `alm`)
894 output array containing the Legendre coefficients
895 if `None`, a new suitable array is allocated
896 lmax: int >= 0
897 the maximum l moment of the transform (inclusive)
898 mval: numpy.ndarray((nm,), dtype = numpy.uint64)
899 the m moments for which the transform should be carried out
900 entries must be unique and <= lmax
901 mstart: numpy.ndarray((nm,), dtype = numpy.uint64)
902 the (hypothetical) index in the second dimension of `alm` on which the
903 entry with l=0, m=mval[mi] would be stored, for mi in mval
904 lstride: int
905 the index stride in the second dimension of `alm` between the entries for
906 `l` and `l+1`, but the same `m`.
907 theta: numpy.ndarray((ntheta,), dtype=numpy.float64)
908 the colatitudes of the map rings
909 nthreads: int >= 0
910 the number of threads to use for the computation
911 if 0, use as many threads as there are hardware threads available on the system
912
913 Returns
914 -------
915 numpy.ndarray((2, ntheta, nm), same dtype as `alm`)
916 the Legendre coefficients. If `leg` was supplied, this will be the same object.
917 The first component contains coefficients representing a map of df/dtheta;
918 the second component those of 1./sin(theta) df/dphi.
919 )""";
920
921 constexpr const char *leg2alm_DS = R"""(
922 Transforms a set of Legendre coefficients to spherical harmonic coefficients
923
924 Parameters
925 ----------
926 leg: numpy.ndarray((ncomp, ntheta, nm), dtype=numpy.complex64 or numpy.complex128)
927 ncomp must be 1 if spin is 0, else 2
928 alm: None or numpy.ndarray((ncomp, x), same dtype as `leg`)
929 the set of spherical harmonic coefficients.
930 The second dimension must be large enough to accommodate all entries, which
931 are stored according to the parameters `lmax`, 'mval`, `mstart`, and `lstride`.
932 if `None`, a new suitable array is allocated
933 spin: int >= 0
934 the spin to use for the transform
935 if spin==0, ncomp must be 1, otherwise 2
936 lmax: int >= 0
937 the maximum l moment of the transform (inclusive)
938 mval: numpy.ndarray((nm,), dtype = numpy.uint64)
939 the m moments for which the transform should be carried out
940 entries must be unique and <= lmax
941 mstart: numpy.ndarray((nm,), dtype = numpy.uint64)
942 the (hypothetical) index in the second dimension of `alm` on which the
943 entry with l=0, m=mval[mi] would be stored, for mi in mval
944 lstride: int
945 the index stride in the second dimension of `alm` between the entries for
946 `l` and `l+1`, but the same `m`.
947 theta: numpy.ndarray((ntheta,), dtype=numpy.float64)
948 the colatitudes of the map rings
949 nthreads: int >= 0
950 the number of threads to use for the computation
951 if 0, use as many threads as there are hardware threads available on the system
952
953 Returns
954 -------
955 numpy.ndarray((ncomp, *), same dtype as `leg`)
956 the Legendre coefficients.
957 if `alm` was supplied, this will be the same object
958 If newly allocated, the smallest possible second dimensions will be chosen.
959 )""";
960
961 constexpr const char *map2leg_DS = R"""(
962 Transforms a map or several maps to Legendre coefficients
963 dependent on theta and m.
964
965 Parameters
966 ----------
967 map: numpy.ndarray((ncomp, x), dtype=numpy.float32 or numpy.float64)
968 the map pixel data.
969 The second dimension must be large enough to accommodate all pixels, which
970 are stored according to the parameters `nphi`, 'ringstart`, and `pixstride`.
971 leg: None or numpy.ndarray((ncomp, ntheta, mmax+1), dtype=numpy.complex of same accuracy as `map`)
972 output array containing the Legendre coefficients
973 if `None`, a new suitable array is allocated
974 nphi: numpy.ndarray((ntheta,), dtype=numpy.uint64)
975 number of pixels in every ring
976 phi0: numpy.ndarray((ntheta,), dtype=numpy.float64)
977 azimuth (in radians) of the first pixel in every ring
978 ringstart: numpy.ndarray((ntheta,), dtype=numpy.uint64)
979 the index in the second dimension of `map` at which the first pixel of every
980 ring is stored
981 pixstride: int
982 the index stride in the second dimension of `map` between two subsequent
983 pixels in a ring
984 mmax: int
985 the maximum m moment to compute in this transform. If `leg`
986 is provided, `mmax` must be equal to `leg.shape[2]=1`.
987 nthreads: int >= 0
988 the number of threads to use for the computation
989 if 0, use as many threads as there are hardware threads available on the system
990
991 Returns
992 -------
993 numpy.ndarray((ncomp, ntheta, nm), dtype=numpy.complex of same accuracy as `map`)
994 the Legendre coefficients
995 if `leg` was supplied, this will be the same object
996
997 Notes
998 -----
999 In contrast to `leg2alm` and `alm2leg` the `m` values are assumed to form a
1000 range from 0 to mmax, inclusively.
1001 )""";
1002
1003 constexpr const char *leg2map_DS = R"""(
1004 Transforms one or more sets of Legendre coefficients to maps.
1005
1006 Parameters
1007 ----------
1008 leg: numpy.ndarray((ncomp, ntheta, mmax+1), numppy.complex64 or numpy.complex128)
1009 input array containing the Legendre coefficients
1010 map: None or numpy.ndarray((ncomp, x), dtype=numpy.float of same accuracy as `leg`
1011 the map pixel data.
1012 The second dimension must be large enough to accommodate all pixels, which
1013 are stored according to the parameters `nphi`, 'ringstart`, and `pixstride`.
1014 if `None`, a new suitable array is allocated
1015 nphi: numpy.ndarray((ntheta,), dtype=numpy.uint64)
1016 number of pixels in every ring
1017 phi0: numpy.ndarray((ntheta,), dtype=numpy.float64)
1018 azimuth (in radians) of the first pixel in every ring
1019 ringstart: numpy.ndarray((ntheta,), dtype=numpy.uint64)
1020 the index in the second dimension of `map` at which the first pixel of every
1021 ring is stored
1022 pixstride: int
1023 the index stride in the second dimension of `map` between two subsequent
1024 pixels in a ring
1025 nthreads: int >= 0
1026 the number of threads to use for the computation
1027 if 0, use as many threads as there are hardware threads available on the system
1028
1029 Returns
1030 -------
1031 numpy.ndarray((ncomp, x), dtype=numpy.float of same accuracy as `leg`)
1032 the map pixel data.
1033 If `map` was supplied, this will be the same object
1034 If newly allocated, the smallest possible second dimensions will be chosen.
1035
1036 Notes
1037 -----
1038 In contrast to `leg2alm` and `alm2leg` the `m` values are assumed to form a
1039 range from 0 to mmax, inclusively.
1040 )""";
1041
1042 constexpr const char *synthesis_2d_DS = R"""(
1043 Transforms one or two sets of spherical harmonic coefficients to 2D maps.
1044
1045 Parameters
1046 ----------
1047 alm: numpy.ndarray((ncomp, x), dtype=numpy.complex64 or numpy.complex128)
1048 the set of spherical harmonic coefficients.
1049 The second dimension must be large enough to accommodate all entries, which
1050 are stored according to the healpy convention.
1051 map: numpy.ndarray((ncomp, ntheta, nphi), dtype=numpy.float of same accuracy as alm)
1052 storage for the output map.
1053 If not supplied, a new array is allocated.
1054 ntheta, nphi: int > 0
1055 dimensions of the output map.
1056 If not supplied, `map` must be supplied.
1057 If supplied, and `map` is also supplied, must match with the array dimensions
1058 spin: int >= 0
1059 the spin to use for the transform.
1060 If spin==0, ncomp must be 1, otherwise 2
1061 lmax: int >= 0
1062 the maximum l moment of the transform (inclusive).
1063 mmax: int >= 0 and <= lmax
1064 the maximum m moment of the transform (inclusive).
1065 If not supplied, mmax is assumed to be equal to lmax
1066 geometry: one of "CC", "F1", "MW", "MWflip", "GL", "DH", "F2"
1067 the distribution of rings over the theta range
1068 - CC: Clenshaw-Curtis, equidistant, first and last ring on poles
1069 - F1: Fejer's first rule, equidistant, first and last ring half a ring
1070 width from the poles
1071 - MW: McEwen & Wiaux scheme, equidistant, first ring half a ring width from
1072 the north pole, last ring on the south pole
1073 - MWflip: flipped McEwen & Wiaux scheme, equidistant, first ring on the
1074 north pole, last ring half a ring width from the south pole
1075 - GL: Gauss-Legendre, non-equidistant
1076 - DH: Driscoll-Healy, equidistant, first ring on north pole, last ring one
1077 ring width from the south pole
1078 - F2: Fejer's second rule, equidistant, first and last ring one ring width
1079 from the poles.
1080 nthreads: int >= 0
1081 the number of threads to use for the computation.
1082 If 0, use as many threads as there are hardware threads available on the system
1083
1084 Returns
1085 -------
1086 numpy.ndarray((ncomp, ntheta, nphi), dtype=numpy.float of same accuracy as alm)
1087 the computed map. If the map parameter was specified, this is identical with
1088 map.
1089 )""";
1090
1091 constexpr const char *synthesis_2d_deriv1_DS = R"""(
1092 Transforms a set of spherical harmonic coefficients to two 2D maps containing
1093 the derivatives with respect to theta and phi.
1094
1095 Parameters
1096 ----------
1097 alm: numpy.ndarray((1, x), dtype=numpy.complex64 or numpy.complex128)
1098 the set of spherical harmonic coefficients.
1099 The second dimension must be large enough to accommodate all entries, which
1100 are stored according to the healpy convention.
1101 map: numpy.ndarray((2, ntheta, nphi), dtype=numpy.float of same accuracy as alm)
1102 storage for the output map.
1103 If not supplied, a new array is allocated.
1104 ntheta, nphi: int > 0
1105 dimensions of the output map.
1106 If not supplied, `map` must be supplied.
1107 If supplied, and `map` is also supplied, must match with the array dimensions
1108 lmax: int >= 0
1109 the maximum l (and m) moment of the transform (inclusive)
1110 mmax: int >= 0 and <= lmax
1111 the maximum m moment of the transform (inclusive).
1112 If not supplied, mmax is assumed to be equal to lmax
1113 geometry: one of "CC", "F1", "MW", "MWflip", "GL", "DH", "F2"
1114 the distribution of rings over the theta range
1115 - CC: Clenshaw-Curtis, equidistant, first and last ring on poles
1116 - F1: Fejer's first rule, equidistant, first and last ring half a ring
1117 width from the poles
1118 - MW: McEwen & Wiaux scheme, equidistant, first ring half a ring width from
1119 the north pole, last ring on the south pole
1120 - MWflip: flipped McEwen & Wiaux scheme, equidistant, first ring on the
1121 north pole, last ring half a ring width from the south pole
1122 - GL: Gauss-Legendre, non-equidistant
1123 - DH: Driscoll-Healy, equidistant, first ring on north pole, last ring one
1124 ring width from the south pole
1125 - F2: Fejer's second rule, equidistant, first and last ring one ring width
1126 from the poles.
1127 nthreads: int >= 0
1128 the number of threads to use for the computation.
1129 If 0, use as many threads as there are hardware threads available on the system
1130
1131 Returns
1132 -------
1133 numpy.ndarray((ncomp, ntheta, nphi), dtype=numpy.float of same accuracy as alm)
1134 the maps containing the derivatives with respect to theta and phi.
1135 If the map parameter was specified, this is identical with map.
1136 )""";
1137
1138 constexpr const char *adjoint_synthesis_2d_DS = R"""(
1139 Transforms one or two 2D maps to spherical harmonic coefficients.
1140 This is the adjoint operation of `synthesis_2D`.
1141
1142 Parameters
1143 ----------
1144 alm: numpy.ndarray((ncomp, x), dtype=numpy.complex64 or numpy.complex128)
1145 storage for the spherical harmonic coefficients.
1146 The second dimension must be large enough to accommodate all entries, which
1147 are stored according to the healpy convention.
1148 If not supplied, a new array is allocated.
1149 map: numpy.ndarray((ncomp, ntheta, nphi), dtype=numpy.float of same accuracy as alm)
1150 The input map.
1151 spin: int >= 0
1152 the spin to use for the transform.
1153 If spin==0, ncomp must be 1, otherwise 2
1154 lmax: int >= 0
1155 the maximum l (and m) moment of the transform (inclusive)
1156 mmax: int >= 0 and <= lmax
1157 the maximum m moment of the transform (inclusive).
1158 If not supplied, mmax is assumed to be equal to lmax
1159 geometry: one of "CC", "F1", "MW", "MWflip", "GL", "DH", "F2"
1160 the distribution of rings over the theta range
1161 - CC: Clenshaw-Curtis, equidistant, first and last ring on poles
1162 - F1: Fejer's first rule, equidistant, first and last ring half a ring
1163 width from the poles
1164 - MW: McEwen & Wiaux scheme, equidistant, first ring half a ring width from
1165 the north pole, last ring on the south pole
1166 - MWflip: flipped McEwen & Wiaux scheme, equidistant, first ring on the
1167 north pole, last ring half a ring width from the south pole
1168 - GL: Gauss-Legendre, non-equidistant
1169 - DH: Driscoll-Healy, equidistant, first ring on north pole, last ring one
1170 ring width from the south pole
1171 - F2: Fejer's second rule, equidistant, first and last ring one ring width
1172 from the poles.
1173 nthreads: int >= 0
1174 the number of threads to use for the computation.
1175 If 0, use as many threads as there are hardware threads available on the system
1176
1177 Returns
1178 -------
1179 numpy.ndarray((ncomp, x), dtype=numpy.complex64 or numpy.complex128)
1180 the computed spherical harmonic coefficients
1181 If the `alm` parameter was specified, this is identical to `alm`.
1182 )""";
1183
1184 constexpr const char *analysis_2d_DS = R"""(
1185 Transforms one or two 2D maps to spherical harmonic coefficients.
1186 This is the inverse operation of `synthesis_2D`.
1187
1188 Parameters
1189 ----------
1190 alm: numpy.ndarray((ncomp, x), dtype=numpy.complex64 or numpy.complex128)
1191 storage for the spherical harmonic coefficients.
1192 The second dimension must be large enough to accommodate all entries, which
1193 are stored according to the healpy convention
1194 If not supplied, a new array is allocated.
1195 map: numpy.ndarray((ncomp, ntheta, nphi), dtype=numpy.float of same accuracy as alm)
1196 The input map.
1197 spin: int >= 0
1198 the spin to use for the transform.
1199 If spin==0, ncomp must be 1, otherwise 2
1200 lmax: int >= 0
1201 the maximum l (and m) moment of the transform (inclusive)
1202 mmax: int >= 0 and <= lmax
1203 the maximum m moment of the transform (inclusive).
1204 If not supplied, mmax is assumed to be equal to lmax
1205 geometry: one of "CC", "F1", "MW", "MWflip", "GL", "DH", "F2"
1206 the distribution of rings over the theta range
1207 - CC: Clenshaw-Curtis, equidistant, first and last ring on poles
1208 - F1: Fejer's first rule, equidistant, first and last ring half a ring
1209 width from the poles
1210 - MW: McEwen & Wiaux scheme, equidistant, first ring half a ring width from
1211 the north pole, last ring on the south pole
1212 - MWflip: flipped McEwen & Wiaux scheme, equidistant, first ring on the
1213 north pole, last ring half a ring width from the south pole
1214 - GL: Gauss-Legendre, non-equidistant
1215 - DH: Driscoll-Healy, equidistant, first ring on north pole, last ring one
1216 ring width from the south pole
1217 - F2: Fejer's second rule, equidistant, first and last ring one ring width
1218 from the poles.
1219 nthreads: int >= 0
1220 the number of threads to use for the computation.
1221 If 0, use as many threads as there are hardware threads available on the system
1222
1223 Returns
1224 -------
1225 numpy.ndarray((ncomp, x), dtype=numpy.complex64 or numpy.complex128)
1226 the computed spherical harmonic coefficients
1227 If the `alm` parameter was specified, this is identical to `alm`.
1228
1229 Notes
1230 -----
1231 The maximum ``m`` moment to which this function can analyze its input map is
1232 ``min(lmax, (nphi-1)//2)``.
1233
1234 The maximum ``l`` moment to which this function can analyze its input map
1235 depends on the geometry, and is
1236
1237 - ``ntheta-2`` for CC
1238 - ``ntheta-1`` for F1, MW, MWflip, and GL
1239 - ``(ntheta-2)//2`` for DH
1240 - ``(ntheta-1)//2`` for F2
1241
1242 For the CC and F1 geometries this limit is considerably higher than the one
1243 obtainable by simply applying quadrature weights. This improvement is achieved
1244 by temporary upsampling along meridians to apply the weights at a higher
1245 resolution.
1246 )""";
1247
1248 constexpr const char *adjoint_analysis_2d_DS = R"""(
1249 Transforms one or two sets of spherical harmonic coefficients to 2D maps.
1250 This is the adjoint operation of `analysis_2D`.
1251
1252 Parameters
1253 ----------
1254 alm: numpy.ndarray((ncomp, x), dtype=numpy.complex64 or numpy.complex128)
1255 the set of spherical harmonic coefficients.
1256 The second dimension must be large enough to accommodate all entries, which
1257 are stored according to the healpy convention.
1258 map: numpy.ndarray((ncomp, ntheta, nphi), dtype=numpy.float of same accuracy as alm)
1259 storage for the output map.
1260 If not supplied, a new array is allocated.
1261 ntheta, nphi: int > 0
1262 dimensions of the output map.
1263 If not supplied, `map` must be supplied.
1264 If supplied, and `map` is also supplied, must match with the array dimensions
1265 spin: int >= 0
1266 the spin to use for the transform.
1267 If spin==0, ncomp must be 1, otherwise 2
1268 lmax: int >= 0
1269 the maximum l moment of the transform (inclusive).
1270 mmax: int >= 0 and <= lmax
1271 the maximum m moment of the transform (inclusive).
1272 If not supplied, mmax is assumed to be equal to lmax
1273 geometry: one of "CC", "F1", "MW", "MWflip", "GL", "DH", "F2"
1274 the distribution of rings over the theta range
1275 - CC: Clenshaw-Curtis, equidistant, first and last ring on poles
1276 - F1: Fejer's first rule, equidistant, first and last ring half a ring
1277 width from the poles
1278 - MW: McEwen & Wiaux scheme, equidistant, first ring half a ring width from
1279 the north pole, last ring on the south pole
1280 - MWflip: flipped McEwen & Wiaux scheme, equidistant, first ring on the
1281 north pole, last ring half a ring width from the south pole
1282 - GL: Gauss-Legendre, non-equidistant
1283 - DH: Driscoll-Healy, equidistant, first ring on north pole, last ring one
1284 ring width from the south pole
1285 - F2: Fejer's second rule, equidistant, first and last ring one ring width
1286 from the poles.
1287 nthreads: int >= 0
1288 the number of threads to use for the computation.
1289 If 0, use as many threads as there are hardware threads available on the system
1290
1291 Returns
1292 -------
1293 numpy.ndarray((ncomp, ntheta, nphi), dtype=numpy.float of same accuracy as alm)
1294 the computed map. If the map parameter was specified, this is identical with
1295 map.
1296
1297 Notes
1298 -----
1299 For limits on ``lmax`` and ``mmax`` see the documentation of ``analysis_2d``.
1300 )""";
1301
1302 constexpr const char *synthesis_DS = R"""(
1303 Transforms one or two sets of spherical harmonic coefficients to maps on the sphere.
1304
1305 Parameters
1306 ----------
1307 alm: numpy.ndarray((ncomp, x), dtype=numpy.complex64 or numpy.complex128)
1308 the set of spherical harmonic coefficients.
1309 The second dimension must be large enough to accommodate all entries, which
1310 are stored according to the healpy convention.
1311 map: None or numpy.ndarray((ncomp, x), dtype=numpy.float of same accuracy as `alm`
1312 the map pixel data.
1313 The second dimension must be large enough to accommodate all pixels, which
1314 are stored according to the parameters `nphi`, 'ringstart`, and `pixstride`.
1315 if `None`, a new suitable array is allocated
1316 theta: numpy.ndarray((ntheta,), dtype=numpy.float64)
1317 the colatitudes of the map rings
1318 nphi: numpy.ndarray((ntheta,), dtype=numpy.uint64)
1319 number of pixels in every ring
1320 phi0: numpy.ndarray((ntheta,), dtype=numpy.float64)
1321 azimuth (in radians) of the first pixel in every ring
1322 mstart: numpy.ndarray((mmax+1,), dtype = numpy.uint64)
1323 the (hypothetical) index in the second dimension of `alm` on which the
1324 entry with (l=0, m) would be stored. If not supplied, a contiguous storage
1325 scheme in the order m=0,1,2,... is assumed.
1326 ringstart: numpy.ndarray((ntheta,), dtype=numpy.uint64)
1327 the index in the second dimension of `map` at which the first pixel of every
1328 ring is stored
1329 lstride: int
1330 the index stride in the second dimension of `alm` between the entries for
1331 `l` and `l+1`, but the same `m`.
1332 pixstride: int
1333 the index stride in the second dimension of `map` between two subsequent
1334 pixels in a ring
1335 nthreads: int >= 0
1336 the number of threads to use for the computation
1337 if 0, use as many threads as there are hardware threads available on the system
1338 spin: int >= 0
1339 the spin to use for the transform.
1340 If spin==0, ncomp must be 1, otherwise 2
1341 lmax: int >= 0
1342 the maximum l moment of the transform (inclusive).
1343
1344 Returns
1345 -------
1346 numpy.ndarray((ncomp, x), dtype=numpy.float of same accuracy as `alm`)
1347 the map pixel data.
1348 If `map` was supplied, this will be the same object
1349 If newly allocated, the smallest possible second dimensions will be chosen.
1350 )""";
1351
1352 constexpr const char *adjoint_synthesis_DS = R"""(
1353 Transforms one or two maps to spherical harmonic coefficients.
1354 This is the adjoint operation of `synthesis`.
1355
1356 Parameters
1357 ----------
1358 alm: None or numpy.ndarray((ncomp, x), dtype=numpy.complex of same precision as `map`)
1359 the set of spherical harmonic coefficients.
1360 The second dimension must be large enough to accommodate all entries, which
1361 are stored according to the healpy convention.
1362 if `None`, a new suitable array is allocated
1363 map: numpy.ndarray((ncomp, x), dtype=numpy.float32 or numpy.float64
1364 The second dimension must be large enough to accommodate all pixels, which
1365 are stored according to the parameters `nphi`, 'ringstart`, and `pixstride`.
1366 theta: numpy.ndarray((ntheta,), dtype=numpy.float64)
1367 the colatitudes of the map rings
1368 nphi: numpy.ndarray((ntheta,), dtype=numpy.uint64)
1369 number of pixels in every ring
1370 phi0: numpy.ndarray((ntheta,), dtype=numpy.float64)
1371 azimuth (in radians) of the first pixel in every ring
1372 mstart: numpy.ndarray((mmax+1,), dtype = numpy.uint64)
1373 the (hypothetical) index in the second dimension of `alm` on which the
1374 entry with (l=0, m) would be stored. If not supplied, a contiguous storage
1375 scheme in the order m=0,1,2,... is assumed.
1376 ringstart: numpy.ndarray((ntheta,), dtype=numpy.uint64)
1377 the index in the second dimension of `map` at which the first pixel of every
1378 ring is stored
1379 lstride: int
1380 the index stride in the second dimension of `alm` between the entries for
1381 `l` and `l+1`, but the same `m`.
1382 pixstride: int
1383 the index stride in the second dimension of `map` between two subsequent
1384 pixels in a ring
1385 nthreads: int >= 0
1386 the number of threads to use for the computation
1387 if 0, use as many threads as there are hardware threads available on the system
1388 spin: int >= 0
1389 the spin to use for the transform.
1390 If spin==0, ncomp must be 1, otherwise 2
1391 lmax: int >= 0
1392 the maximum l moment of the transform (inclusive).
1393
1394 Returns
1395 -------
1396 numpy.ndarray((ncomp, x), dtype=numpy.complex of same accuracy as `map`)
1397 the set of spherical harmonic coefficients.
1398 The second dimension must be large enough to accommodate all entries, which
1399 are stored according to the healpy convention.
1400 If newly allocated, the smallest possible second dimensions will be chosen.
1401 )""";
1402
1403 constexpr const char *synthesis_deriv1_DS = R"""(
1404 Transforms a set of spherical harmonic coefficients to two maps containing
1405 the derivatives with respect to theta and phi.
1406
1407 Parameters
1408 ----------
1409 alm: numpy.ndarray((1, x), dtype=numpy.complex64 or numpy.complex128)
1410 the set of spherical harmonic coefficients.
1411 The second dimension must be large enough to accommodate all entries, which
1412 are stored according to the healpy convention.
1413 map: None or numpy.ndarray((2, x), dtype=numpy.float of same accuracy as `alm`
1414 the map pixel data.
1415 The second dimension must be large enough to accommodate all pixels, which
1416 are stored according to the parameters `nphi`, 'ringstart`, and `pixstride`.
1417 if `None`, a new suitable array is allocated
1418 theta: numpy.ndarray((ntheta,), dtype=numpy.float64)
1419 the colatitudes of the map rings
1420 nphi: numpy.ndarray((ntheta,), dtype=numpy.uint64)
1421 number of pixels in every ring
1422 phi0: numpy.ndarray((ntheta,), dtype=numpy.float64)
1423 azimuth (in radians) of the first pixel in every ring
1424 mstart: numpy.ndarray((mmax+1,), dtype = numpy.uint64)
1425 the (hypothetical) index in the second dimension of `alm` on which the
1426 entry with (l=0, m) would be stored. If not supplied, a contiguous storage
1427 scheme in the order m=0,1,2,... is assumed.
1428 ringstart: numpy.ndarray((ntheta,), dtype=numpy.uint64)
1429 the index in the second dimension of `map` at which the first pixel of every
1430 ring is stored
1431 lstride: int
1432 the index stride in the second dimension of `alm` between the entries for
1433 `l` and `l+1`, but the same `m`.
1434 pixstride: int
1435 the index stride in the second dimension of `map` between two subsequent
1436 pixels in a ring
1437 nthreads: int >= 0
1438 the number of threads to use for the computation
1439 if 0, use as many threads as there are hardware threads available on the system
1440 lmax: int >= 0
1441 the maximum l moment of the transform (inclusive).
1442
1443 Returns
1444 -------
1445 numpy.ndarray((2, x), dtype=numpy.float of same accuracy as `alm`)
1446 the map pixel data.
1447 If `map` was supplied, this will be the same object
1448 If newly allocated, the smallest possible second dimensions will be chosen.
1449 )""";
1450
1451 constexpr const char *sharpjob_d_DS = R"""(
1452 Interface class to some of libsharp2's functionality.
1453
1454 Notes
1455 -----
1456 This class is considered obsolescent and will be removed in the future.
1457 )""";
1458
add_sht(py::module_ & msup)1459 void add_sht(py::module_ &msup)
1460 {
1461 using namespace pybind11::literals;
1462 auto m = msup.def_submodule("sht");
1463 m.doc() = sht_DS;
1464 auto m2 = m.def_submodule("experimental");
1465 m2.doc() = sht_experimental_DS;
1466
1467 m2.def("synthesis", &Py_synthesis, synthesis_DS, py::kw_only(), "alm"_a, "theta"_a,
1468 "lmax"_a, "mstart"_a=None, "nphi"_a, "phi0"_a, "ringstart"_a, "spin"_a,
1469 "lstride"_a=1, "pixstride"_a=1, "nthreads"_a=1, "map"_a=None);
1470 m2.def("adjoint_synthesis", &Py_adjoint_synthesis, adjoint_synthesis_DS, py::kw_only(), "map"_a, "theta"_a,
1471 "lmax"_a, "mstart"_a=None, "nphi"_a, "phi0"_a, "ringstart"_a, "spin"_a,
1472 "lstride"_a=1, "pixstride"_a=1, "nthreads"_a=1, "alm"_a=None);
1473 m2.def("synthesis_deriv1", &Py_synthesis_deriv1, synthesis_deriv1_DS, py::kw_only(), "alm"_a, "theta"_a,
1474 "lmax"_a, "mstart"_a=None, "nphi"_a, "phi0"_a, "ringstart"_a,
1475 "lstride"_a=1, "pixstride"_a=1, "nthreads"_a=1, "map"_a=None);
1476
1477 m2.def("synthesis_2d", &Py_synthesis_2d, synthesis_2d_DS, py::kw_only(), "alm"_a, "spin"_a, "lmax"_a, "geometry"_a, "ntheta"_a=None, "nphi"_a=None, "mmax"_a=None, "nthreads"_a=1, "map"_a=None);
1478 m2.def("adjoint_synthesis_2d", &Py_adjoint_synthesis_2d, adjoint_synthesis_2d_DS, py::kw_only(), "map"_a, "spin"_a, "lmax"_a, "geometry"_a, "mmax"_a=None, "nthreads"_a=1, "alm"_a=None);
1479 m2.def("synthesis_2d_deriv1", &Py_synthesis_2d_deriv1, synthesis_2d_deriv1_DS, py::kw_only(), "alm"_a, "lmax"_a, "geometry"_a, "ntheta"_a=None, "nphi"_a=None, "mmax"_a=None, "nthreads"_a=1, "map"_a=None);
1480 m2.def("analysis_2d", &Py_analysis_2d, analysis_2d_DS, py::kw_only(), "map"_a, "spin"_a, "lmax"_a, "geometry"_a, "mmax"_a=None, "nthreads"_a=1, "alm"_a=None);
1481 m2.def("adjoint_analysis_2d", &Py_adjoint_analysis_2d, adjoint_analysis_2d_DS, py::kw_only(), "alm"_a, "spin"_a, "lmax"_a, "geometry"_a, "ntheta"_a=None, "nphi"_a=None, "mmax"_a=None, "nthreads"_a=1, "map"_a=None);
1482
1483 m2.def("GL_weights",&Py_GL_weights, "nlat"_a, "nlon"_a);
1484 m2.def("GL_thetas",&Py_GL_thetas, "nlat"_a);
1485 m2.def("get_gridweights", &Py_get_gridweights, "type"_a, "ntheta"_a);
1486 m2.def("alm2leg", &Py_alm2leg, alm2leg_DS, py::kw_only(), "alm"_a, "lmax"_a, "theta"_a, "spin"_a=0, "mval"_a=None, "mstart"_a=None, "lstride"_a=1, "nthreads"_a=1, "leg"_a=None);
1487 m2.def("alm2leg_deriv1", &Py_alm2leg_deriv1, alm2leg_deriv1_DS, py::kw_only(), "alm"_a, "lmax"_a, "theta"_a, "mval"_a=None, "mstart"_a=None, "lstride"_a=1, "nthreads"_a=1, "leg"_a=None);
1488 m2.def("leg2alm", &Py_leg2alm, leg2alm_DS, py::kw_only(), "leg"_a, "lmax"_a, "theta"_a, "spin"_a=0, "mval"_a=None, "mstart"_a=None, "lstride"_a=1, "nthreads"_a=1, "alm"_a=None);
1489 m2.def("map2leg", &Py_map2leg, map2leg_DS, py::kw_only(), "map"_a, "nphi"_a, "phi0"_a, "ringstart"_a, "mmax"_a, "pixstride"_a=1, "nthreads"_a=1, "leg"_a=None);
1490 m2.def("leg2map", &Py_leg2map, leg2map_DS, py::kw_only(), "leg"_a, "nphi"_a, "phi0"_a, "ringstart"_a, "pixstride"_a=1, "nthreads"_a=1, "map"_a=None);
1491 m.def("rotate_alm", &Py_rotate_alm, rotate_alm_DS, "alm"_a, "lmax"_a, "psi"_a, "theta"_a,
1492 "phi"_a, "nthreads"_a=1);
1493
1494 py::class_<Py_sharpjob<double>> (m, "sharpjob_d", py::module_local(),sharpjob_d_DS)
1495 .def(py::init<>())
1496 .def("set_nthreads", &Py_sharpjob<double>::set_nthreads, "nthreads"_a)
1497 .def("set_gauss_geometry", &Py_sharpjob<double>::set_gauss_geometry,
1498 "ntheta"_a,"nphi"_a)
1499 .def("set_healpix_geometry", &Py_sharpjob<double>::set_healpix_geometry,
1500 "nside"_a)
1501 .def("set_fejer1_geometry", &Py_sharpjob<double>::set_fejer1_geometry,
1502 "ntheta"_a, "nphi"_a)
1503 .def("set_fejer2_geometry", &Py_sharpjob<double>::set_fejer2_geometry,
1504 "ntheta"_a, "nphi"_a)
1505 .def("set_cc_geometry", &Py_sharpjob<double>::set_cc_geometry,
1506 "ntheta"_a, "nphi"_a)
1507 .def("set_dh_geometry", &Py_sharpjob<double>::set_dh_geometry,
1508 "ntheta"_a, "nphi"_a)
1509 .def("set_mw_geometry", &Py_sharpjob<double>::set_mw_geometry,
1510 "ntheta"_a, "nphi"_a)
1511 .def("set_triangular_alm_info",
1512 &Py_sharpjob<double>::set_triangular_alm_info, "lmax"_a, "mmax"_a)
1513 .def("n_alm", &Py_sharpjob<double>::n_alm)
1514 .def("alm2map", &Py_sharpjob<double>::alm2map,"alm"_a)
1515 .def("alm2map_adjoint", &Py_sharpjob<double>::alm2map_adjoint,"map"_a)
1516 .def("map2alm", &Py_sharpjob<double>::map2alm,"map"_a)
1517 .def("alm2map_spin", &Py_sharpjob<double>::alm2map_spin,"alm"_a,"spin"_a)
1518 .def("map2alm_spin", &Py_sharpjob<double>::map2alm_spin,"map"_a,"spin"_a)
1519 .def("__repr__", &Py_sharpjob<double>::repr);
1520 }
1521
1522 }
1523
1524 using detail_pymodule_sht::add_sht;
1525
1526 }
1527
1528