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