LCOV - code coverage report
Current view: top level - include/ao/sim - turbSubHarmonic.hpp (source / functions) Coverage Total Hit
Test: mxlib Lines: 74.8 % 135 101
Test Date: 2026-08-30 22:23:37 Functions: 87.5 % 8 7

            Line data    Source code
       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              : 
      34              : #include "../../base/changeable.hpp"
      35              : 
      36              : #include "../../improc/eigenImage.hpp"
      37              : #include "../../improc/eigenCube.hpp"
      38              : 
      39              : #include "../../math/constants.hpp"
      40              : #include "../../math/func/jinc.hpp"
      41              : 
      42              : namespace mx
      43              : {
      44              : namespace AO
      45              : {
      46              : namespace 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              :  */
      55              : template <typename _turbAtmosphereT, class _verboseT = mx::verbose::d>
      56              : class 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.
      99              :     improc::eigenCube<realT> m_modes;
     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              : 
     169              : template <typename turbAtmosphereT, class verboseT>
     170            4 : turbSubHarmonic<turbAtmosphereT, verboseT>::turbSubHarmonic()
     171              : {
     172            4 : }
     173              : 
     174              : template <typename turbAtmosphereT, class verboseT>
     175            4 : void turbSubHarmonic<turbAtmosphereT, verboseT>::turbAtmo( turbAtmosphereT *turbatm )
     176              : {
     177            4 :     if( turbatm != m_turbAtmo )
     178              :     {
     179            4 :         m_turbAtmo = turbatm;
     180            4 :         this->changed();
     181              :     }
     182            4 : }
     183              : 
     184              : template <typename turbAtmosphereT, class verboseT>
     185              : turbAtmosphereT *turbSubHarmonic<turbAtmosphereT, verboseT>::turbAtmo()
     186              : {
     187              :     return m_turbAtmo;
     188              : }
     189              : 
     190              : template <typename turbAtmosphereT, class verboseT>
     191            4 : void turbSubHarmonic<turbAtmosphereT, verboseT>::level( uint32_t ml )
     192              : {
     193            4 :     if( ml != m_level )
     194              :     {
     195            0 :         m_level = ml;
     196            0 :         this->changed();
     197              :     }
     198            4 : }
     199              : 
     200              : template <typename turbAtmosphereT, class verboseT>
     201              : uint32_t turbSubHarmonic<turbAtmosphereT, verboseT>::level()
     202              : {
     203              :     return m_level;
     204              : }
     205              : 
     206              : template <typename turbAtmosphereT, class verboseT>
     207            4 : void turbSubHarmonic<turbAtmosphereT, verboseT>::outerSubHarmonics( bool osh )
     208              : {
     209            4 :     if( osh != m_outerSubHarmonics )
     210              :     {
     211            0 :         m_outerSubHarmonics = osh;
     212            0 :         this->changed();
     213              :     }
     214            4 : }
     215              : 
     216              : template <typename turbAtmosphereT, class verboseT>
     217              : bool turbSubHarmonic<turbAtmosphereT, verboseT>::outerSubHarmonics()
     218              : {
     219              :     return m_outerSubHarmonics;
     220              : }
     221              : 
     222              : template <typename turbAtmosphereT, class verboseT>
     223            4 : void turbSubHarmonic<turbAtmosphereT, verboseT>::preCalc( bool pc )
     224              : {
     225            4 :     if( pc != m_preCalc )
     226              :     {
     227            3 :         m_preCalc = pc;
     228            3 :         this->changed();
     229              :     }
     230            4 : }
     231              : 
     232              : template <typename turbAtmosphereT, class verboseT>
     233              : bool turbSubHarmonic<turbAtmosphereT, verboseT>::preCalc()
     234              : {
     235              :     return m_preCalc;
     236              : }
     237              : 
     238              : template <typename turbAtmosphereT, class verboseT>
     239            4 : void turbSubHarmonic<turbAtmosphereT, verboseT>::initGrid( uint32_t layerNo )
     240              : {
     241            4 :     if( m_turbAtmo == nullptr )
     242              :     {
     243            0 :         throw mx::exception<verboseT>( error_t::paramnotset, "atmosphere is not set (m_turbAtmo is nullptr)" );
     244              :     }
     245              : 
     246            4 :     if( m_turbAtmo->aosys() == nullptr )
     247              :     {
     248            0 :         throw mx::exception<verboseT>( error_t::paramnotset, "ao system is not set (m_turbAtmo->m_aosys is nullptr)" );
     249              :     }
     250              : 
     251            4 :     if( m_turbAtmo->nLayers() <= layerNo )
     252              :     {
     253            0 :         throw mx::exception<verboseT>( error_t::invalidconfig,
     254            0 :                                        "atmosphere is not setup (m_turbAtmo->m_layers size is <= layerNo)" );
     255              :     }
     256              : 
     257            4 :     m_m.resize( 0 ); // resized dynamically below
     258            4 :     m_n.resize( 0 );
     259            4 :     m_sqrtPSD.resize( 0 );
     260            4 :     m_cosNoise.resize( 0 );
     261            4 :     m_sinNoise.resize( 0 );
     262              : 
     263            4 :     m_scrnSz = m_turbAtmo->layer( layerNo ).scrnSz();
     264              : 
     265            4 :     if( m_level == 0 )
     266              :     {
     267              : 
     268            0 :         m_modes.clear();
     269            0 :         this->setChangePoint();
     270            0 :         return;
     271              :     }
     272              : 
     273            4 :     realT r0 = m_turbAtmo->aosys()->atm.r_0( m_turbAtmo->aosys()->lam_sci() );
     274            4 :     realT D = m_turbAtmo->aosys()->D();
     275            4 :     uint32_t wfSz = m_turbAtmo->wfSz();
     276              : 
     277            4 :     realT beta = 0.0218 / pow( r0, 5. / 3. ) / pow( ( D / wfSz ) * m_scrnSz, 2 );
     278              : 
     279            4 :     realT sqrt_alpha = 0.5 * 11. / 3.;
     280              : 
     281            4 :     realT L0 = m_turbAtmo->aosys()->atm.L_0( layerNo );
     282              : 
     283              :     realT L02;
     284            4 :     if( L0 > 0 )
     285            0 :         L02 = 1.0 / pow( L0, 2 );
     286              :     else
     287            4 :         L02 = 0;
     288              : 
     289            4 :     std::vector<realT> scs;
     290              : 
     291            4 :     if( m_outerSubHarmonics )
     292              :     {
     293            4 :         realT sc = 0.25;
     294              : 
     295            8 :         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            4 :         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            4 :         scs.resize( m_m.size(), sc );
     300              :     }
     301              : 
     302            8 :     for( int nl = 1; nl <= m_level; ++nl )
     303              :     {
     304            4 :         realT sc = pow( 3.0, -nl );
     305              : 
     306           28 :         for( int mp = -3; mp < 3; ++mp )
     307              :         {
     308          168 :             for( int np = -3; np < 3; ++np )
     309              :             {
     310          144 :                 if( nl < m_level )
     311              :                 {
     312            0 :                     if( mp == -1 )
     313              :                     {
     314            0 :                         if( np == -1 || np == 0 )
     315            0 :                             continue;
     316              :                     }
     317            0 :                     else if( mp == 0 )
     318              :                     {
     319            0 :                         if( np == -1 || np == 0 )
     320            0 :                             continue;
     321              :                     }
     322              :                 }
     323              : 
     324          144 :                 m_m.push_back( sc * ( mp + 0.5 ) );
     325          144 :                 m_n.push_back( sc * ( np + 0.5 ) );
     326          144 :                 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            4 :     std::vector<realT> uniqueM;
     334            4 :     std::vector<realT> uniqueN;
     335            4 :     std::vector<realT> uniqueScs;
     336          228 :     for( size_t n = 0; n < m_m.size(); ++n )
     337              :     {
     338          224 :         if( m_m[n] > 0 || ( m_m[n] == 0 && m_n[n] > 0 ) )
     339              :         {
     340          112 :             uniqueM.push_back( m_m[n] );
     341          112 :             uniqueN.push_back( m_n[n] );
     342          112 :             uniqueScs.push_back( scs[n] );
     343              :         }
     344              :     }
     345              : 
     346            4 :     m_m.swap( uniqueM );
     347            4 :     m_n.swap( uniqueN );
     348            4 :     scs.swap( uniqueScs );
     349            4 :     m_sqrtPSD.resize( m_m.size() );
     350            4 :     m_cosNoise.resize( m_m.size() );
     351            4 :     m_sinNoise.resize( m_m.size() );
     352              : 
     353            4 :     if( m_preCalc )
     354              :     {
     355            3 :         m_modes.resize( m_scrnSz, m_scrnSz, 2 * m_m.size() );
     356              :     }
     357              : 
     358          116 :     for( size_t n = 0; n < m_m.size(); ++n )
     359              :     {
     360          112 :         realT k = sqrt( ( pow( m_m[n], 2 ) + pow( m_n[n], 2 ) ) ) / ( ( D / wfSz ) * m_scrnSz );
     361              : 
     362          112 :         realT tpsd = beta / pow( k * k + L02, sqrt_alpha );
     363              : 
     364          112 :         realT Ppiston = 0;
     365          112 :         realT Ptiptilt = 0;
     366          112 :         if( m_turbAtmo->aosys()->psd.subPiston() )
     367              :         {
     368          112 :             Ppiston = pow( 2 * math::func::jinc( math::pi<realT>() * k * D ), 2 );
     369              :         }
     370              : 
     371          112 :         if( m_turbAtmo->aosys()->psd.subTipTilt() )
     372              :         {
     373            0 :             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          112 :         m_sqrtPSD[n] = sqrt( 2 ) * scs[n] * sqrt( tpsd * ( 1 - Ppiston - Ptiptilt ) );
     380              : 
     381          112 :         if( m_preCalc )
     382              :         {
     383         9044 :             for( int cc = 0; cc < m_scrnSz; ++cc )
     384              :             {
     385         8960 :                 realT np = cc - 0.5 * m_scrnSz;
     386      1270528 :                 for( int rr = 0; rr < m_scrnSz; ++rr )
     387              :                 {
     388      1261568 :                     realT mp = rr - 0.5 * m_scrnSz;
     389      1261568 :                     m_modes.image( n )( rr, cc ) =
     390      1261568 :                         m_sqrtPSD[n] * cos( math::two_pi<realT>() * ( m_m[n] * mp + m_n[n] * np ) / m_scrnSz );
     391      1261568 :                     m_modes.image( n + m_m.size() )( rr, cc ) =
     392      1261568 :                         m_sqrtPSD[n] * sin( math::two_pi<realT>() * ( m_m[n] * mp + m_n[n] * np ) / m_scrnSz );
     393              :                 }
     394              :             }
     395              :         }
     396              :     }
     397              : 
     398            4 :     this->setChangePoint();
     399            4 : }
     400              : 
     401              : template <typename turbAtmosphereT, class verboseT>
     402           11 : void turbSubHarmonic<turbAtmosphereT, verboseT>::screen( improc::eigenImage<realT> &scrn )
     403              : {
     404           11 :     if( m_level == 0 )
     405              :     {
     406            0 :         return;
     407              :     }
     408              : 
     409           11 :     if( m_turbAtmo == nullptr )
     410              :     {
     411            0 :         throw mx::exception<verboseT>( error_t::paramnotset, "atmosphere is not set (m_turbAtmo is nullptr)" );
     412              :     }
     413              : 
     414           11 :     if( this->isChanged() )
     415              :     {
     416            0 :         throw mx::exception<verboseT>( error_t::invalidconfig, "configuration has changed but not re-initialized" );
     417              :     }
     418              : 
     419           11 :     if( scrn.rows() != m_scrnSz || scrn.cols() != m_scrnSz )
     420              :     {
     421            0 :         throw mx::exception<verboseT>( error_t::sizeerr, "input screen is not the right size" );
     422              :     }
     423              : 
     424              :     // Check that we're allocated
     425           11 :     if( m_preCalc )
     426              :     {
     427           20 :         if( m_modes.rows() != scrn.rows() || m_modes.cols() != scrn.cols() ||
     428           10 :             m_modes.planes() != 2 * m_cosNoise.size() )
     429              :         {
     430            0 :             throw mx::exception<verboseT>( error_t::sizeerr, "modes cube wrong size, call initGrid()." );
     431              :         }
     432              :     }
     433              :     else
     434              :     {
     435            2 :         if( m_cosNoise.size() != m_m.size() || m_sinNoise.size() != m_m.size() || m_m.size() != m_n.size() ||
     436            1 :             m_sqrtPSD.size() != m_m.size() )
     437              :         {
     438            0 :             throw mx::exception<verboseT>( error_t::sizeerr, "vectors not allocated, call initGrid()." );
     439              :         }
     440              :     }
     441              : 
     442              :     // Now fill in the noise
     443          319 :     for( size_t n = 0; n < m_cosNoise.size(); ++n )
     444              :     {
     445          308 :         m_cosNoise[n] = m_turbAtmo->normVar();
     446          308 :         m_sinNoise[n] = m_turbAtmo->normVar();
     447              :     }
     448              : 
     449           11 : #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              : 
     477              : template <typename turbAtmosphereT, class verboseT>
     478            0 : void turbSubHarmonic<turbAtmosphereT, verboseT>::deinit()
     479              : {
     480            0 :     m_m.clear();
     481            0 :     m_n.clear();
     482            0 :     m_sqrtPSD.clear();
     483            0 :     m_cosNoise.clear();
     484            0 :     m_sinNoise.clear();
     485            0 :     m_modes.resize( 0, 0, 0 );
     486              : 
     487            0 :     this->changed();
     488            0 : }
     489              : 
     490              : } // namespace sim
     491              : } // namespace AO
     492              : } // namespace mx
     493              : 
     494              : #endif // mx_AO_sim_turbSubHarm_hpp
        

Generated by: LCOV version 2.0-1