mxlib
c++ tools for analyzing astronomical data and other tasks by Jared R. Males. [git repo]
Loading...
Searching...
No Matches
turbSubHarmonic.hpp
Go to the documentation of this file.
1/** \file turbSubHarmonic.hpp
2 * \brief A class to manage low-frequency sub-harmonic phase screen generation in atmospheric turbulence.
3 *
4 * \author Jared R. Males (jaredmales@gmail.com)
5 *
6 * \ingroup mxAO_sim_files
7 *
8 */
9
10//***********************************************************************//
11// Copyright 2023 Jared R. Males (jaredmales@gmail.com)
12//
13// This file is part of mxlib.
14//
15// mxlib is free software: you can redistribute it and/or modify
16// it under the terms of the GNU General Public License as published by
17// the Free Software Foundation, either version 3 of the License, or
18// (at your option) any later version.
19//
20// mxlib is distributed in the hope that it will be useful,
21// but WITHOUT ANY WARRANTY; without even the implied warranty of
22// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23// GNU General Public License for more details.
24//
25// You should have received a copy of the GNU General Public License
26// along with mxlib. If not, see <http://www.gnu.org/licenses/>.
27//***********************************************************************//
28
29#ifndef mx_AO_sim_turbSubHarm_hpp
30#define mx_AO_sim_turbSubHarm_hpp
31
32#include <vector>
33
35
38
39#include "../../math/constants.hpp"
41
42namespace mx
43{
44namespace AO
45{
46namespace sim
47{
48
49/// A class to manage low-frequency sub-harmonic phase screen generation in atmospheric turbulence.
50/** Implements the method of Johansson and Gavel (1994)\cite johansson_gavel_1994. This is
51 * needed to generate adequate tip/tilt variance with large outer scales.
52 *
53 * \ingroup mxAOSim
54 */
55template <typename _turbAtmosphereT, class _verboseT = mx::verbose::d>
56class turbSubHarmonic : public base::changeable<turbSubHarmonic<_turbAtmosphereT>>
57{
58
59 public:
60 typedef _turbAtmosphereT turbAtmosphereT;
61 typedef _verboseT verboseT;
62
63 typedef typename turbAtmosphereT::realT realT;
64
65 protected:
66 /** \name Configuration Parameters
67 * @{
68 */
69
70 turbAtmosphereT *m_turbAtmo{ nullptr }; ///< Pointer to the parent atmosphere object.
71
72 unsigned m_level{ 1 }; ///< The subharmonic level to apply.
73
74 bool m_outerSubHarmonics{ true }; ///< Whether or not to include the outer subharmonics
75
76 bool m_preCalc{ false }; ///< whether or not the modes are pre-calculated.
77
78 ///@}
79
80 /** \name Internal State
81 * @{
82 */
83
84 uint32_t m_scrnSz; ///< The wavefront screen size from the layer being simulated.
85
86 /// Gaussian cosine coefficients prepared for each screen generation.
87 std::vector<realT> m_cosNoise;
88
89 /// Gaussian sine coefficients prepared for each screen generation.
90 std::vector<realT> m_sinNoise;
91
92 std::vector<realT> m_m; ///< m-coordinate fractional spatial frequency indices of the subharmonics
93 std::vector<realT> m_n; ///< n-coordinate fractional spatial frequency indices of the subharmonics
94
95 /// Square root of the PSD-cell variance for each unique conjugate pair.
96 std::vector<realT> m_sqrtPSD;
97
98 /// Pre-calculated cosine modes followed by their sine counterparts.
100
101 ///@}
102
103 public:
104 /** \name Construction
105 * @{
106 */
107 turbSubHarmonic();
108
109 ///@}
110
111 /** \name Configuration
112 * @{
113 */
114
115 /// Set the pointer to the turbulent atmosphere
116 void turbAtmo( turbAtmosphereT *atm /**< [in] the new pointer to an AO atmosphere*/ );
117
118 /// Get the pointer to the AO atmosphere
119 /**
120 * \returns the the current pointer to the AO atmosphere
121 */
122 turbAtmosphereT *turbAtmo();
123
124 /// Set the subharmonic level to apply
125 void level( uint32_t ml /**< [in] the new level*/ );
126
127 /// Get the subharmonic level
128 /**
129 * \returns the current subharmonic level
130 */
131 uint32_t level();
132
133 /// Set whether or not the outer subharmonics are included
134 void outerSubHarmonics( bool osh /**< [in] the new value of the \ref m_outerSubHarmonics flag */ );
135
136 /// Get whether or not the outer subharmonics are included
137 /**
138 * \returns the current value of the \ref m_outerSubHarmonics flag
139 */
140 bool outerSubHarmonics();
141
142 /// Set whether or not to pre-calculate the modes
143 void preCalc( bool pc /**< [in] the new value of the \ref m_preCalc flag*/ );
144
145 /// Get whether or not the modes are pre-calculated
146 /**
147 * \returns the current value of the preCalc flag
148 */
149 bool preCalc();
150
151 ///@}
152
153 /** \name Screen Generation
154 * @{
155 */
156
157 /// Allocate needed memory and initialize the subharmonic transform
158 void initGrid( uint32_t layerNo );
159
160 /// Generate a realization of the subharmonic phase screen and add it to the input screen
161 void screen( improc::eigenImage<realT> & scrn /**< [in] the input phase screen to which to add the low-frequency screen. Must be m_scrnSz X m_scrnsz*/ );
162
163 /// Deallocate memory
164 void deinit();
165
166 ///@}
167};
168
169template <typename turbAtmosphereT, class verboseT>
170turbSubHarmonic<turbAtmosphereT, verboseT>::turbSubHarmonic()
171{
172}
173
174template <typename turbAtmosphereT, class verboseT>
176{
177 if( turbatm != m_turbAtmo )
178 {
179 m_turbAtmo = turbatm;
180 this->changed();
181 }
182}
183
184template <typename turbAtmosphereT, class verboseT>
189
190template <typename turbAtmosphereT, class verboseT>
192{
193 if( ml != m_level )
194 {
195 m_level = ml;
196 this->changed();
197 }
198}
199
200template <typename turbAtmosphereT, class verboseT>
205
206template <typename turbAtmosphereT, class verboseT>
208{
209 if( osh != m_outerSubHarmonics )
210 {
212 this->changed();
213 }
214}
215
216template <typename turbAtmosphereT, class verboseT>
221
222template <typename turbAtmosphereT, class verboseT>
224{
225 if( pc != m_preCalc )
226 {
227 m_preCalc = pc;
228 this->changed();
229 }
230}
231
232template <typename turbAtmosphereT, class verboseT>
237
238template <typename turbAtmosphereT, class verboseT>
240{
241 if( m_turbAtmo == nullptr )
242 {
243 throw mx::exception<verboseT>( error_t::paramnotset, "atmosphere is not set (m_turbAtmo is nullptr)" );
244 }
245
246 if( m_turbAtmo->aosys() == nullptr )
247 {
248 throw mx::exception<verboseT>( error_t::paramnotset, "ao system is not set (m_turbAtmo->m_aosys is nullptr)" );
249 }
250
251 if( m_turbAtmo->nLayers() <= layerNo )
252 {
254 "atmosphere is not setup (m_turbAtmo->m_layers size is <= layerNo)" );
255 }
256
257 m_m.resize( 0 ); // resized dynamically below
258 m_n.resize( 0 );
259 m_sqrtPSD.resize( 0 );
260 m_cosNoise.resize( 0 );
261 m_sinNoise.resize( 0 );
262
263 m_scrnSz = m_turbAtmo->layer( layerNo ).scrnSz();
264
265 if( m_level == 0 )
266 {
267
268 m_modes.clear();
269 this->setChangePoint();
270 return;
271 }
272
273 realT r0 = m_turbAtmo->aosys()->atm.r_0( m_turbAtmo->aosys()->lam_sci() );
274 realT D = m_turbAtmo->aosys()->D();
275 uint32_t wfSz = m_turbAtmo->wfSz();
276
277 realT beta = 0.0218 / pow( r0, 5. / 3. ) / pow( ( D / wfSz ) * m_scrnSz, 2 );
278
279 realT sqrt_alpha = 0.5 * 11. / 3.;
280
281 realT L0 = m_turbAtmo->aosys()->atm.L_0( layerNo );
282
283 realT L02;
284 if( L0 > 0 )
285 L02 = 1.0 / pow( L0, 2 );
286 else
287 L02 = 0;
288
289 std::vector<realT> scs;
290
292 {
293 realT sc = 0.25;
294
295 m_m = std::vector<realT>( { -1.25, -0.75, -0.25, 0.25, 0.75, 1.25, -1.25, 1.25, -1.25, 1.25,
296 -1.25, 1.25, -1.25, 1.25, -1.25, -0.75, -0.25, 0.25, 0.75, 1.25 } );
297 m_n = std::vector<realT>( { -1.25, -1.25, -1.25, -1.25, -1.25, -1.25, -0.75, -0.75, -0.25, -0.25,
298 0.25, 0.25, 0.75, 0.75, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25 } );
299 scs.resize( m_m.size(), sc );
300 }
301
302 for( int nl = 1; nl <= m_level; ++nl )
303 {
304 realT sc = pow( 3.0, -nl );
305
306 for( int mp = -3; mp < 3; ++mp )
307 {
308 for( int np = -3; np < 3; ++np )
309 {
310 if( nl < m_level )
311 {
312 if( mp == -1 )
313 {
314 if( np == -1 || np == 0 )
315 continue;
316 }
317 else if( mp == 0 )
318 {
319 if( np == -1 || np == 0 )
320 continue;
321 }
322 }
323
324 m_m.push_back( sc * ( mp + 0.5 ) );
325 m_n.push_back( sc * ( np + 0.5 ) );
326 scs.push_back( sc );
327 }
328 }
329 }
330
331 // Retain one member of each +/-k pair. A real phase screen requires both
332 // quadratures for that pair, rather than two identical cosine modes.
333 std::vector<realT> uniqueM;
334 std::vector<realT> uniqueN;
335 std::vector<realT> uniqueScs;
336 for( size_t n = 0; n < m_m.size(); ++n )
337 {
338 if( m_m[n] > 0 || ( m_m[n] == 0 && m_n[n] > 0 ) )
339 {
340 uniqueM.push_back( m_m[n] );
341 uniqueN.push_back( m_n[n] );
342 uniqueScs.push_back( scs[n] );
343 }
344 }
345
346 m_m.swap( uniqueM );
347 m_n.swap( uniqueN );
348 scs.swap( uniqueScs );
349 m_sqrtPSD.resize( m_m.size() );
350 m_cosNoise.resize( m_m.size() );
351 m_sinNoise.resize( m_m.size() );
352
353 if( m_preCalc )
354 {
355 m_modes.resize( m_scrnSz, m_scrnSz, 2 * m_m.size() );
356 }
357
358 for( size_t n = 0; n < m_m.size(); ++n )
359 {
360 realT k = sqrt( ( pow( m_m[n], 2 ) + pow( m_n[n], 2 ) ) ) / ( ( D / wfSz ) * m_scrnSz );
361
362 realT tpsd = beta / pow( k * k + L02, sqrt_alpha );
363
364 realT Ppiston = 0;
365 realT Ptiptilt = 0;
366 if( m_turbAtmo->aosys()->psd.subPiston() )
367 {
368 Ppiston = pow( 2 * math::func::jinc( math::pi<realT>() * k * D ), 2 );
369 }
370
371 if( m_turbAtmo->aosys()->psd.subTipTilt() )
372 {
373 Ptiptilt = pow( 4 * math::func::jincN( 2, math::pi<realT>() * k * D ), 2 );
374 }
375
376 // The representative stands for both members of its conjugate pair.
377 // The factor sqrt(2) gives the pair's total PSD-cell variance when
378 // independent unit-variance cosine and sine coefficients are used.
379 m_sqrtPSD[n] = sqrt( 2 ) * scs[n] * sqrt( tpsd * ( 1 - Ppiston - Ptiptilt ) );
380
381 if( m_preCalc )
382 {
383 for( int cc = 0; cc < m_scrnSz; ++cc )
384 {
385 realT np = cc - 0.5 * m_scrnSz;
386 for( int rr = 0; rr < m_scrnSz; ++rr )
387 {
388 realT mp = rr - 0.5 * m_scrnSz;
389 m_modes.image( n )( rr, cc ) =
390 m_sqrtPSD[n] * cos( math::two_pi<realT>() * ( m_m[n] * mp + m_n[n] * np ) / m_scrnSz );
391 m_modes.image( n + m_m.size() )( rr, cc ) =
392 m_sqrtPSD[n] * sin( math::two_pi<realT>() * ( m_m[n] * mp + m_n[n] * np ) / m_scrnSz );
393 }
394 }
395 }
396 }
397
398 this->setChangePoint();
399}
400
401template <typename turbAtmosphereT, class verboseT>
403{
404 if( m_level == 0 )
405 {
406 return;
407 }
408
409 if( m_turbAtmo == nullptr )
410 {
411 throw mx::exception<verboseT>( error_t::paramnotset, "atmosphere is not set (m_turbAtmo is nullptr)" );
412 }
413
414 if( this->isChanged() )
415 {
416 throw mx::exception<verboseT>( error_t::invalidconfig, "configuration has changed but not re-initialized" );
417 }
418
419 if( scrn.rows() != m_scrnSz || scrn.cols() != m_scrnSz )
420 {
421 throw mx::exception<verboseT>( error_t::sizeerr, "input screen is not the right size" );
422 }
423
424 // Check that we're allocated
425 if( m_preCalc )
426 {
427 if( m_modes.rows() != scrn.rows() || m_modes.cols() != scrn.cols() ||
428 m_modes.planes() != 2 * m_cosNoise.size() )
429 {
430 throw mx::exception<verboseT>( error_t::sizeerr, "modes cube wrong size, call initGrid()." );
431 }
432 }
433 else
434 {
435 if( m_cosNoise.size() != m_m.size() || m_sinNoise.size() != m_m.size() || m_m.size() != m_n.size() ||
436 m_sqrtPSD.size() != m_m.size() )
437 {
438 throw mx::exception<verboseT>( error_t::sizeerr, "vectors not allocated, call initGrid()." );
439 }
440 }
441
442 // Now fill in the noise
443 for( size_t n = 0; n < m_cosNoise.size(); ++n )
444 {
445 m_cosNoise[n] = m_turbAtmo->normVar();
446 m_sinNoise[n] = m_turbAtmo->normVar();
447 }
448
449#pragma omp parallel for
450 for( int cc = 0; cc < m_scrnSz; ++cc )
451 {
452 realT np = cc - 0.5 * m_scrnSz;
453 for( int rr = 0; rr < m_scrnSz; ++rr )
454 {
455 realT mp = rr - 0.5 * m_scrnSz;
456
457 if( m_preCalc )
458 {
459 for( unsigned n = 0; n < m_cosNoise.size(); ++n )
460 {
461 scrn( rr, cc ) += m_cosNoise[n] * m_modes.image( n )( rr, cc ) +
462 m_sinNoise[n] * m_modes.image( n + m_cosNoise.size() )( rr, cc );
463 }
464 }
465 else
466 {
467 for( unsigned n = 0; n < m_m.size(); ++n )
468 {
469 realT arg = math::two_pi<realT>() * ( m_m[n] * mp + m_n[n] * np ) / m_scrnSz;
470 scrn( rr, cc ) += m_sqrtPSD[n] * ( m_cosNoise[n] * cos( arg ) + m_sinNoise[n] * sin( arg ) );
471 }
472 }
473 }
474 }
475}
476
477template <typename turbAtmosphereT, class verboseT>
479{
480 m_m.clear();
481 m_n.clear();
482 m_sqrtPSD.clear();
483 m_cosNoise.clear();
484 m_sinNoise.clear();
485 m_modes.resize( 0, 0, 0 );
486
487 this->changed();
488}
489
490} // namespace sim
491} // namespace AO
492} // namespace mx
493
494#endif // mx_AO_sim_turbSubHarm_hpp
A simple class to track member data changes.
bool m_outerSubHarmonics
Whether or not to include the outer subharmonics.
void deinit()
Deallocate memory.
improc::eigenCube< realT > m_modes
Pre-calculated cosine modes followed by their sine counterparts.
uint32_t level()
Get the subharmonic level.
bool preCalc()
Get whether or not the modes are pre-calculated.
std::vector< realT > m_cosNoise
Gaussian cosine coefficients prepared for each screen generation.
uint32_t m_scrnSz
The wavefront screen size from the layer being simulated.
std::vector< realT > m_m
m-coordinate fractional spatial frequency indices of the subharmonics
std::vector< realT > m_sqrtPSD
Square root of the PSD-cell variance for each unique conjugate pair.
bool outerSubHarmonics()
Get whether or not the outer subharmonics are included.
turbAtmosphereT * turbAtmo()
Get the pointer to the AO atmosphere.
std::vector< realT > m_n
n-coordinate fractional spatial frequency indices of the subharmonics
turbAtmosphereT * m_turbAtmo
Pointer to the parent atmosphere object.
std::vector< realT > m_sinNoise
Gaussian sine coefficients prepared for each screen generation.
unsigned m_level
The subharmonic level to apply.
bool m_preCalc
whether or not the modes are pre-calculated.
void initGrid(uint32_t layerNo)
Allocate needed memory and initialize the subharmonic transform.
void screen(improc::eigenImage< realT > &scrn)
Generate a realization of the subharmonic phase screen and add it to the input screen.
A simple class to track member data changes.
An image cube with an Eigen-like API.
Definition eigenCube.hpp:33
An image cube with an Eigen API.
Tools for using the eigen library for image processing.
Eigen::Array< scalarT, -1, -1 > eigenImage
Definition of the eigenImage type, which is an alias for Eigen::Array.
@ sizeerr
A size was invalid or calculated incorrectly.
Definition error_t.hpp:35
@ exception
An exception was thrown.
Definition error_t.hpp:51
@ paramnotset
A parameter was not set.
Definition error_t.hpp:32
@ invalidconfig
A config setting was invalid.
Definition error_t.hpp:30
T2 jincN(const T1 &v, const T2 &x)
The JincN function.
Definition jinc.hpp:112
T jinc(const T &x)
The Jinc function.
Definition jinc.hpp:61
constexpr T pi()
Get the value of pi.
Definition constants.hpp:62
constexpr T two_pi()
Get the value of 2pi.
Declares and defines the Jinc and Jinc2 functions.
The mxlib c++ namespace.
Definition mxlib.hpp:37