mxlib
c++ tools for analyzing astronomical data and other tasks by Jared R. Males. [git repo]
Loading...
Searching...
No Matches
zernikeCovariance.hpp
Go to the documentation of this file.
1/** \file zernikeCovariance.hpp
2 * \author Jared R. Males (jaredmales@gmail.com)
3 * \brief Calculation of the modal covariance in the zernike basis.
4 * \ingroup mxAO_files
5 *
6 */
7
8#ifndef zernikeCovariance_hpp
9#define zernikeCovariance_hpp
10
11#include <gsl/gsl_integration.h>
12#include <gsl/gsl_errno.h>
13
14#include "../../math/constants.hpp"
15#include "../../math/func/jinc.hpp"
16#include "../../sigproc/zernike.hpp"
17#include "../../ioutils/fits/fitsFile.hpp"
18#include "../../improc/eigenImage.hpp"
19#include "../../improc/eigenCube.hpp"
20#include "../../mxlib.hpp"
21#include "../../ipc/ompLoopWatcher.hpp"
22#include "../../sys/timeUtils.hpp"
23#include "../../math/eigenLapack.hpp"
24
25#include "../../math/func/airyPattern.hpp"
26
27#include "aoAtmosphere.hpp"
28#include "aoPSDs.hpp"
29#include "aoSystem.hpp"
30#include "varmapToImage.hpp"
31
32namespace mx
33{
34namespace AO
35{
36namespace analysis
37{
38
39/// Structure to manage the zernike mode covariance calculation, passed to integration functions
40/**
41 * \tparam realT a floating point type used for all calculations. As of Dec 2023 must be double due to gsl_integration.
42 * \tparam aosysT the type of the AO system structure
43 */
44template <typename realT, typename aosysT>
46{
47 /// Pointer to an AO system, which contains the relevant spatial PSD of turbulence.
48 aosysT *m_aosys{ nullptr };
49
50 /// The n-index of the unprimed mode.
51 realT m_n;
52
53 /// The m-index of the unprimed mode.
54 realT m_m;
55
56 /// The n-indexof the primed mode, corresponding to the \f$ k_v = n/D \f$ component of spatial frequency.
57 realT m_np;
58
59 /// The m-index of the primed mode, corresponding to the \f$ k_u = m/D \f$ component of spatial frequency.
60 realT m_mp;
61
62 /// Spatial frequency being calculated, passed for use in the integrand worker functions.
63 realT m_k;
64
65 /// Absolute tolerance for the radial integral. Default is 1e-10.
66 realT m_kIntEpsAbs{ 1e-10 };
67
68 /// Relative tolerance for the radial integral. Default is 0, meaning absolute is used.
69 realT m_kIntEpsRel{ 0 };
70
71 /// Absolute tolerance for the azimuthal integral. Default is 1e-10.
72 realT m_phiIntEpsAbs{ 1e-10 };
73
74 /// Relative tolerance for the azimuthal integral. Default is 0, meaning absolute is used.
75 realT m_phiIntEpsRel{ 0 };
76
77 protected:
78 size_t m_workspaceSize{ 1000000 };
79
80 /// Working memory for the azimuthal integral.
81 gsl_integration_workspace *m_workspacePhi{ nullptr };
82
83 /// Working memory for the radial integral.
84 gsl_integration_workspace *m_workspaceK{ nullptr };
85
86 public:
87 /// Constructor
89 {
90 m_workspacePhi = gsl_integration_workspace_alloc( m_workspaceSize );
91 m_workspaceK = gsl_integration_workspace_alloc( m_workspaceSize );
92 }
93
94 /// Destructor
96 {
97 gsl_integration_workspace_free( m_workspacePhi );
98 gsl_integration_workspace_free( m_workspaceK );
99 }
100
101 void workspaceSize( size_t wsz )
102 {
103 gsl_integration_workspace_free( m_workspacePhi );
104 gsl_integration_workspace_free( m_workspaceK );
105
106 m_workspacePhi = gsl_integration_workspace_alloc( m_workspaceSize );
107 m_workspaceK = gsl_integration_workspace_alloc( m_workspaceSize );
108 }
109
110 size_t workspaceSize()
111 {
112 return m_workspaceSize;
113 }
114
115 gsl_integration_workspace *workspacePhi()
116 {
117 return m_workspacePhi;
118 }
119
120 /// Calculate the covariance between the two modes.
121 /** \todo document me
122 * \todo handle gsl errors
123 */
124 realT getCovariance( realT &error )
125 {
126 realT result;
127
128 if( m_aosys == nullptr )
129 {
131 err::paramnotset, "zernikeCovariance::getCovariance", "AO system not setup (aosys is nullptr)" );
132 }
133
134 gsl_function func;
135 func.function = &kInt;
136 func.params = this;
137
138 gsl_set_error_handler_off();
139
140 int ec = gsl_integration_qagiu(
141 &func, 0, m_kIntEpsAbs, m_kIntEpsRel, m_workspaceSize, m_workspaceK, &result, &error );
142
143 return result;
144 }
145
146 /// Worker function for the radial integral in the covariance calculation
147 /**
148 * \returns the value of the integrand at spatial frequency \p k.
149 */
150 static realT kInt( realT k, ///< [in] the spatial frequency at which to evaluate the integrand, meters
151 void *params ///< [in] a pointer to a object of type zernikeCovariance<realT, aosyT>
152 );
153
154 /// Worker for the azimuthal integral (in phi) for the zernike mode covariance.
155 /**
156 * \return the value of the integrand at the angle \p phi
157 */
158 static realT phiInt( realT phi, ///< [in] the angle at which to evaluate the integrand, radians
159 void *params ///< [in] a pointer to a object of type zernikeCovariance<realT, aosyT>
160 );
161};
162
163template <typename realT, typename aosysT>
164realT zernikeCovariance<realT, aosysT>::kInt( realT k, void *params )
165{
167
168 realT result, error;
169
170 gsl_function func;
171
172 func.function = &phiInt;
173
174 func.params = Pp;
175
176 Pp->m_k = k;
177
178 gsl_integration_qag( &func,
179 0.,
180 math::two_pi<double>(),
181 Pp->m_phiIntEpsAbs,
182 Pp->m_phiIntEpsRel,
183 Pp->workspaceSize(),
184 GSL_INTEG_GAUSS61,
185 Pp->workspacePhi(),
186 &result,
187 &error );
188
189 return result;
190}
191
192template <typename realT, typename aosysT>
193realT zernikeCovariance<realT, aosysT>::phiInt( realT phi, void *params )
194{
196
197 realT D = Pp->m_aosys->D();
198
199 realT k = Pp->m_k;
200
201 /*** no prime ***/
202 realT m = Pp->m_m;
203 realT n = Pp->m_n;
204
205 std::complex<realT> Q_mn = zernikeQ( k * D / 2.0, phi, n, m );
206
207 /*** primed ***/
208 realT mp = Pp->m_mp;
209 realT np = Pp->m_np;
210
211 std::complex<realT> Q_mpnp = zernikeQ( k * D / 2.0, phi, np, mp );
212
213 /*** The product ***/
214 realT QQ = real( conj( Q_mn ) * Q_mpnp );
215
216 realT P = Pp->m_aosys->psd( Pp->m_aosys->atm, 0, k, Pp->m_aosys->lam_sci(), Pp->m_aosys->lam_wfs(), 1.0 );
217
218 return P * k * QQ;
219}
220
221#if 0
222
223///Calculate a vector of zernike mode variances.
224template<typename realT, typename aosysT>
225int zernikeVarVec( const std::string & fname,
226 int N,
227 aosysT & aosys,
228 realT absTol,
229 realT relTol,
230 bool modifed=true
231 )
232{
233
234 std::vector<realT> var(N,0);
235
236 ipc::ompLoopWatcher<> watcher(N, std::cerr);
237
238 realT mnCon = 0;
239 if( aosys.d_min(0) > 0)
240 {
241 mnCon = floor( aosys.D()/aosys.d_min(0)/2.0);
242 }
243
244#pragma omp parallel
245 {
246 zernikeCovariance<realT, aosysT> Pp;
247 Pp.absTol = absTol;
248 Pp.relTol = relTol;
249 Pp.aosys = &aosys;
250
251 Pp.mnCon = mnCon;
252
253 realT result, error;
254
255#pragma omp for schedule( dynamic, 5 )
256 for(int i=0; i< N; ++i)
257 {
258 Pp.p = +1;
259 Pp.m = i+1;
260 Pp.n = 0;
261
262 Pp.pp = +1;
263 Pp.mp = i+1;
264 Pp.np = 0;
265
266 result = Pp.getVariance(error);
267
268 var[i] = result;
269
270 watcher.incrementAndOutputStatus();
271 }
272 }
273
274 std::ofstream fout;
275 fout.open(fname);
276
277 realT D = aosys.D();
278 realT r_0 = aosys.atm.r_0();
279 realT tot = 1.0299*pow(D/r_0, 5./3.);
280
281 realT sum = 0;
282
283 std::cout << 0 << " " << 0 << " " << 0 << " " << tot << "\n";
284 for(int i=0; i<N; ++i)
285 {
286 realT k = (i+1)/D;
287
288 realT P = aosys.psd(aosys.atm, 0, k, 1.0);// aosys.lam_sci(), 0, aosys.lam_wfs(), 1.0);
289
290 if(mnCon > 0 )
291 {
292 if( k*D < mnCon )
293 {
294 P *= pow(math::two_pi<realT>()*aosys.atm.v_wind()* k * (aosys.minTauWFS(0)+aosys.deltaTau()),2);
295 }
296 }
297
298 sum += var[i];
299 fout << i+1 << " " << var[i] << " " << P/pow(D,2) << " " << tot-sum << "\n";
300 }
301
302 fout.close();
303
304 return 0;
305}
306
307///Calculate a map of zernike variances by convolution with the PSD
308/** Uses the Airy pattern for the circularly unobstructed aperture.
309 *
310 * \returns 0 on success
311 * \returns -1 on error
312 */
313template<typename realT, typename aosysT>
314int zernikePSDMap( improc::eigenImage<realT> & var, ///< [out] The variance estimated by convolution with the PSD
315 improc::eigenImage<realT> & psd, ///< [out] the PSD map
316 int N, ///< [in] the number of components to analyze
317 int overSample,
318 aosysT & aosys ///< [in[ the AO system defining the PSD characteristics.
319 )
320{
321 N *= overSample;
322 psd.resize(2*N + 1, 2*N+1);
323
324 realT mnCon = 0;
325 if( aosys.d_min() > 0)
326 {
327 mnCon = floor( aosys.D()/aosys.d_min()/2.0);
328 }
329
330 for(int i=0; i<=N; ++i)
331 {
332 for(int j=-N; j<=N; ++j)
333 {
334
335 realT D = aosys.D();
336 realT k = sqrt( pow(i,2) + pow(j,2))/D/overSample;
337
338 realT P = aosys.psd(aosys.atm, k, aosys.lam_sci(), 0, aosys.lam_wfs(), 1.0);
339
340 if(mnCon > 0 )
341 {
342 if( k*D < mnCon )
343 {
344 P *= pow(math::two_pi<realT>()*aosys.atm.v_wind()* k * (aosys.minTauWFS()+aosys.deltaTau()),2);
345 }
346 }
347
348 psd(N+i, N + j) = P/pow(D*overSample,2);
349 psd(N-i, N - j) = P/pow(D*overSample,2);
350 }
351 }
352
353 //Create Airy PSF for convolution with PSD psd.
354 Eigen::Array<realT, -1,-1> psf;
355 psf.resize(2*N+3,2*N+3);
356 for(int i=0;i<psf.rows();++i)
357 {
358 for(int j=0;j<psf.cols();++j)
359 {
360 psf(i,j) = mx::math::func::airyPattern(sqrt( pow( i-floor(.5*psf.rows()),2) + pow(j-floor(.5*psf.cols()),2))/overSample);
361 }
362 }
363
364 mx::AO::analysis::varmapToImage(var, psd, psf);
365
366 return 0;
367}
368
369template<typename realT>
370int zernikeCovarMap( const std::string & fname, ///< [out] the path where the output FITS file will be written
371 int N, ///< [in] the linear number of zernike modes across the aperture. The Nyquist frequency is set by N/2.
372 realT D,
373 realT L_0,
374 bool subPist,
375 bool subTilt,
376 realT absTol,
377 realT relTol,
378 bool modified=true
379 )
380{
381 std::vector<mx::sigproc::zernikeModeDef> ml;
382 mx::sigproc::makezernikeModeFreqs_Rect(ml, N);
383
384
385 aoSystem<realT, vonKarmanSpectrum<realT>> aosys;
386
387 aosys.loadMagAOX();
388
389 //This is just a normalization parameter in this context.
390 aosys.atm.r_0(1.0, 0.5e-6);
391
392 aosys.D( D );
393 aosys.atm.L_0( L_0 );
394 aosys.psd.subPiston( subPist );
395 aosys.psd.subTipTilt( subTilt );
396
397 int psz = ml.size();
398
399 Eigen::Array<realT,-1,-1> covar( psz, psz);
400 covar.setZero();
401
402 //int ncalc = 0.5*( psz*psz - psz);
403
404 ipc::ompLoopWatcher<> watcher(psz, std::cout);
405
406 std::cerr << "Starting . . .\n";
407#pragma omp parallel
408 {
409 zernikeCovariance<realT, aoSystem<realT, vonKarmanSpectrum<realT> > > Pp;
410 Pp.absTol = absTol;
411 Pp.relTol = relTol;
412 Pp.aosys = &aosys;
413
414 if(!modified) Pp.useBasic = true;
415
416 realT result, error;
417
418#pragma omp for schedule( static, 5 )
419 for(int i=0; i< psz; ++i)
420 {
421 for(int j=i; j< psz; ++j)
422 {
423 Pp.p = ml[i].p;
424 Pp.m = ml[i].m;
425 Pp.n = ml[i].n;
426
427 Pp.pp = ml[j].p;
428 Pp.mp = ml[j].m;
429 Pp.np = ml[j].n;
430 result = Pp.getVariance(error);
431
432 covar(i,j) = result;
433
434 }
435 watcher.incrementAndOutputStatus();
436 }
437 }
438
439 fits::fitsHeader head;
440 head.append("DIAMETER", aosys.D(), "Diameter in meters");
441 head.append("NSUBAP", N, "Linear number of s.f. sampled");
442 head.append("L0", aosys.atm.L_0(0), "Outer scale (L_0) in meters");
443 head.append("SUBPIST", aosys.psd.subPiston(), "Piston subtractioon true/false flag");
444 head.append("SUBTILT", aosys.psd.subTipTilt(), "Tip/Tilt subtractioon true/false flag");
445 head.append("ABSTOL", absTol, "Absolute tolerance in qagiu");
446 head.append("RELTOL", relTol, "Relative tolerance in qagiu");
447
448 fitsHeaderGitStatus( head, "mxlib", mxlib_comp_current_sha1(), mxlib_comp_repo_modified() );
449
450 fits::fitsFile<realT> ff;
451 ff.write(fname + ".fits", covar, head);
452
453 return 0;
454}
455
456
457
458
459template<typename realT, typename aosysT>
460int zernikeCovarMapSeparated( const std::string & fname,
461 int N,
462 aosysT & aosys,
463 realT absTol,
464 realT relTol,
465 bool modified=true)
466{
467 std::vector<mx::sigproc::zernikeModeDef> ml;
468 mx::sigproc::makezernikeModeFreqs_Rect(ml, N);
469
470 int psz = 0.5*ml.size();
471
472 Eigen::Array<realT,-1,-1> covar_pp( (int) (0.5*psz), (int)(.5*psz)), covar_ppp( (int) (0.5*psz), (int)(0.5*psz));
473 covar_pp.setZero();
474 covar_ppp.setZero();
475
476 realT mnCon = 0;
477 if( aosys.d_min() > 0)
478 {
479 mnCon = floor( aosys.D()/aosys.d_min()/2.0);
480 }
481
482 ipc::ompLoopWatcher<> watcher((psz+1)*0.125*(psz+1)*2, std::cout);
483
484#pragma omp parallel
485 {
486
487 zernikeCovariance<realT, aosysT > Pp;
488 Pp.absTol = absTol;
489 Pp.relTol = relTol;
490 Pp.aosys = &aosys;
491
492 if(!modified) Pp.useBasic = true;
493
494 Pp.mnCon = mnCon;
495
496 realT result, error;
497
498#pragma omp for schedule( dynamic, 5 )
499 for(int i=0; i< psz; i+=2)
500 {
501 for(int j=0; j<= 0.5*i; ++j)
502 {
503 for(int k=0; k< 2; ++k)
504 {
505 Pp.p = ml[i].p;
506 Pp.m = ml[i].m;
507 Pp.n = ml[i].n;
508
509 Pp.pp = ml[2*j + k].p;
510 Pp.mp = ml[2*j + k].m;
511 Pp.np = ml[2*j + k].n;
512 result = Pp.getVariance(error);
513
514 if( Pp.p == Pp.pp)
515 {
516 covar_pp(i/2, j) = result;
517 }
518 else
519 {
520 covar_ppp(i/2, j) = result;
521 }
522 watcher.incrementAndOutputStatus();
523 }
524 }
525 }
526 }
527
528 fits::fitsHeader head;
529 head.append("DIAMETER", aosys.D(), "Diameter in meters");
530 head.append("L0", aosys.atm.L_0(), "Outer scale (L_0) in meters");
531 head.append("SUBPIST", aosys.psd.subPiston(), "Piston subtractioon true/false flag");
532 head.append("SUBTILT", aosys.psd.subTipTilt(), "Tip/Tilt subtractioon true/false flag");
533 head.append("ABSTOL", absTol, "Absolute tolerance in qagiu");
534 head.append("RELTOL", relTol, "Relative tolerance in qagiu");
535
536 fitsHeaderGitStatus( head, "mxlib", mxlib_comp_current_sha1(), mxlib_comp_repo_modified() );
537
538 fits::fitsFile<realT> ff;
539 ff.write(fname + "_pp.fits", covar_pp, head);
540 ff.write(fname + "_ppp.fits", covar_ppp, head);
541
542 return 0;
543}
544
545template<typename realT>
546void calcKLCoeffs( const std::string & outFile,
547 const std::string & cvFile )
548{
549 fits::fitsFile<realT> ff;
550
551 Eigen::Array<realT,-1,-1> cvT, cv, evecs, evals;
552
553 ff.read(cv, cvFile);
554
555 //cvT = cv.block(0,0, 1000,1000);//.transpose();
556
557 std::cerr << cvT.rows() << " " << cvT.cols() << "\n";
558 std::cerr << "1\n";
560
561 double t0 = sys::get_curr_time();
562
563 int info = math::eigenSYEVR<double,double>(evecs, evals, cv, 0, -1, 'U', &mem);
564
565 double t1 = sys::get_curr_time();
566
567 std::cerr << "2\n";
568
569 if(info !=0 )
570 {
571 std::cerr << "info =" << info << "\n";
572 exit(0);
573 }
574
575 std::cerr << "Time = " << t1-t0 << " secs\n";
576
577 //Normalize the eigenvectors
578 for(int i=0;i< evecs.cols(); ++i)
579 {
580 evecs.col(i) = evecs.col(i)/sqrt(fabs(evals(i)));
581 }
582
583 ff.write(outFile, evecs);
584}
585
586template<typename eigenArrT1, typename eigenArrT2, typename eigenArrT3>
587void makeKL( eigenArrT1 & kl,
588 eigenArrT2 & evecs,
589 eigenArrT3 && rvecs )
590{
591
592 int tNims = evecs.rows();
593 int tNpix = rvecs.rows();
594
595 int n_modes = tNims;
596
597 //Now calculate KL images
598 /*
599 * KL = E^T * R ==> C = A^T * B
600 */
601 math::gemm<typename eigenArrT1::Scalar>(CblasColMajor, CblasTrans, CblasTrans, n_modes, tNpix,
602 tNims, 1., evecs.data(), evecs.rows(), rvecs.data(), rvecs.rows(),
603 0., kl.data(), kl.rows());
604
605}
606
607template<typename realT>
608void makeFKL( const std::string & outFile,
609 const std::string & coeffs,
610 int N,
611 int pupSize )
612{
613 fits::fitsFile<realT> ff;
614 Eigen::Array<realT, -1, -1> evecs;
615
616 ff.read(evecs, coeffs);
617
618 improc::eigenCube<realT> Rims;
619 sigproc::makezernikeBasis_Rect(Rims, pupSize, N, MX_zernike_MODIFIED);
620
621 std::cout << Rims.planes() << " " << evecs.cols() << "\n";
622
623 Eigen::Array<realT,-1,-1> kl;
624 kl.resize( Rims.planes(), Rims.rows()*Rims.cols());
625
626 std::cerr << 1 << "\n";
627 makeKL( kl, evecs, Rims.cube());
628 std::cerr << 2 << "\n";
629
630 Eigen::Array<realT,-1,-1> klT = kl.transpose();
631 //kl.resize(0,0);
632 //Rims.resize(0,0);
633 //evecs.resize(0,0);
634
635 improc::eigenCube<realT> klims(klT.data(), Rims.rows(), Rims.cols(), Rims.planes());
636
637 improc::eigenCube<realT> klimsR;
638 klimsR.resize( klims.rows(), klims.cols(), klims.planes());
639
640 for(int i=0; i< klims.planes(); ++i)
641 {
642 klimsR.image(i) = klims.image(klims.planes()-1-i);
643 }
644
645 ff.write(outFile, klimsR);
646 std::cerr << 3 << "\n";
647}
648
649#endif
650
651} // namespace analysis
652} // namespace AO
653} // namespace mx
654
655#endif // zernikeCovariance_hpp
Provides a class to specify atmosphere parameters.
Spatial power spectra used in adaptive optics.
Declares and defines an analytical AO system.
mxException for parameters which aren't set
A class to track the number of iterations in an OMP parallelized loop.
constexpr units::realT k()
Boltzmann Constant.
Definition constants.hpp:69
@ error
A general error has occurred.
#define mxThrowException(extype, src, expl)
Throw an exception. This macro takes care of the file and line.
void fitsHeaderGitStatus(fitsHeader &head, const std::string &repoName, const char *sha1, int modified)
Write the status of a Git repository to HISTORY in a FITS header.
realT airyPattern(realT x)
The classical Airy pattern.
constexpr floatT six_fifths()
Return 6/5 in the specified precision.
void varmapToImage(imageT &im, imageT &varmap, imageT &psf)
Convert a wavefront variance map to an intensity image by convolving with the PSF.
typeT get_curr_time()
Get the current system time in seconds.
Definition timeUtils.hpp:93
The mxlib c++ namespace.
Definition mxError.hpp:40
Structure to manage the zernike mode covariance calculation, passed to integration functions.
realT m_kIntEpsAbs
Absolute tolerance for the radial integral. Default is 1e-10.
aosysT * m_aosys
Pointer to an AO system, which contains the relevant spatial PSD of turbulence.
realT m_k
Spatial frequency being calculated, passed for use in the integrand worker functions.
realT m_m
The m-index of the unprimed mode.
realT m_mp
The m-index of the primed mode, corresponding to the component of spatial frequency.
realT m_np
The n-indexof the primed mode, corresponding to the component of spatial frequency.
gsl_integration_workspace * m_workspacePhi
Working memory for the azimuthal integral.
realT m_phiIntEpsRel
Relative tolerance for the azimuthal integral. Default is 0, meaning absolute is used.
static realT phiInt(realT phi, void *params)
Worker for the azimuthal integral (in phi) for the zernike mode covariance.
realT getCovariance(realT &error)
Calculate the covariance between the two modes.
realT m_n
The n-index of the unprimed mode.
realT m_phiIntEpsAbs
Absolute tolerance for the azimuthal integral. Default is 1e-10.
gsl_integration_workspace * m_workspaceK
Working memory for the radial integral.
realT m_kIntEpsRel
Relative tolerance for the radial integral. Default is 0, meaning absolute is used.
static realT kInt(realT k, void *params)
Worker function for the radial integral in the covariance calculation.
A utility to convert a wavefront variance map to an intensity image.