mxlib
c++ tools for analyzing astronomical data and other tasks by Jared R. Males. [git repo]
Loading...
Searching...
No Matches
aoAtmosphere_test.cpp
Go to the documentation of this file.
1/** \file aoAtmosphere_test.cpp
2 * \brief Tests adaptive-optics atmosphere configuration and validation.
3 */
4#include "../../../catch2/catch.hpp"
5
6#define MX_NO_ERROR_REPORTS
7
9
10#include <limits>
11
12typedef double realT;
13
14using namespace mx::app;
15using namespace mx::AO::analysis;
16
17/// Verify parsing and validation of atmosphere configuration settings.
18/** Exercises mx::AO::analysis::aoAtmosphere::setupConfig and
19 * mx::AO::analysis::aoAtmosphere::loadConfig. */
20/**
21 * \ingroup aoAtmosphere_unit_tests
22 */
23TEST_CASE( "Loading aoAtmosphere config settings", "[ao::analysis::aoAtmosphere]" )
24{
25 GIVEN( "a valid config file" )
26 {
28 atm.setSingleLayer( 0.2, 0.5e-6, 25.0, 0.0, 0.0, 10.0, 0.0 );
29
30 WHEN( "all normal settings" )
31 {
32 appConfigurator config;
33
35 "aoAtmosphere.conf",
36 { "atm", "atm", "atm", "atm", "atm", "atm", "atm", "atm", "atm", "atm" },
37 { "r_0", "lam_0", "L_0", "l_0", "layer_z", "h_obs", "H", "layer_Cn2", "layer_v_wind", "layer_dir" },
38 { "0.25",
39 "0.4e-9",
40 "10,15,22.5",
41 "0.1,0.01,0.001",
42 "1000,2000,5000",
43 "3002",
44 "2.3",
45 "1,1,3.0",
46 "10.5,11.2,23.7",
47 "0.1,0.6,1.6" } );
48
49 atm.setupConfig( config );
50 config.readConfig( "aoAtmosphere.conf" );
51 REQUIRE( atm.loadConfig( config ) == mx::error_t::noerror );
52
53 REQUIRE( atm.r_0() == 0.25 );
54 REQUIRE( atm.lam_0() == 0.4e-9 );
55
56 REQUIRE( atm.L_0( 0 ) == 10 );
57 REQUIRE( atm.L_0( 1 ) == 15 );
58 REQUIRE( atm.L_0( 2 ) == 22.5 );
59
60 REQUIRE( atm.l_0( 0 ) == 0.1 );
61 REQUIRE( atm.l_0( 1 ) == 0.01 );
62 REQUIRE( atm.l_0( 2 ) == 0.001 );
63
64 REQUIRE( atm.layer_z( 0 ) == 1000 );
65 REQUIRE( atm.layer_z( 1 ) == 2000 );
66 REQUIRE( atm.layer_z( 2 ) == 5000 );
67
68 REQUIRE( atm.h_obs() == 3002 );
69
70 REQUIRE( atm.H() == 2.3 );
71
72 REQUIRE( atm.layer_Cn2( 0 ) == Approx( 0.2 ) );
73 REQUIRE( atm.layer_Cn2( 1 ) == Approx( 0.2 ) );
74 REQUIRE( atm.layer_Cn2( 2 ) == Approx( 0.6 ) );
75
76 REQUIRE( atm.layer_v_wind( 0 ) == Approx( 10.5 ) );
77 REQUIRE( atm.layer_v_wind( 1 ) == Approx( 11.2 ) );
78 REQUIRE( atm.layer_v_wind( 2 ) == Approx( 23.7 ) );
79
80 REQUIRE( atm.layer_dir( 0 ) == Approx( 0.1 ) );
81 REQUIRE( atm.layer_dir( 1 ) == Approx( 0.6 ) );
82 REQUIRE( atm.layer_dir( 2 ) == Approx( 1.6 ) );
83
84 REQUIRE( atm.v_wind() == Approx( 19.278644205193533 ) );
85 REQUIRE( atm.z_mean() == Approx( 3886.4496373530733 ) );
86
87 REQUIRE( atm.nonKolmogorov() == false );
88 }
89 WHEN( "setting v_wind and z_mean" )
90 {
91 atm.L_0( { 25.0, 25.0, 25.0 } );
92 atm.l_0( { 0.0, 0.0, 0.0 } );
93 appConfigurator config;
94
95 writeConfigFile( "aoAtmosphere.conf",
96 { "atm", "atm", "atm", "atm", "atm", "atm" },
97 { "layer_z", "layer_Cn2", "layer_v_wind", "layer_dir", "v_wind", "z_mean" },
98 { "1000,2000,5000", "1,1,3.0", "10.5,11.2,23.7", "0.1,0.6,1.6", "15.0", "5001.0" } );
99
100 atm.setupConfig( config );
101 config.readConfig( "aoAtmosphere.conf" );
102 REQUIRE( atm.loadConfig( config ) == mx::error_t::noerror );
103
104 REQUIRE( atm.v_wind() == Approx( 15.0 ) );
105 REQUIRE( atm.z_mean() == Approx( 5001.0 ) );
106
107 REQUIRE( atm.nonKolmogorov() == false );
108 }
109 WHEN( "setting nonKolmogorov to false" )
110 {
111 appConfigurator config;
112
113 writeConfigFile( "aoAtmosphere.conf", { "atm" }, { "nonKolmogorov" }, { "false" } );
114
115 atm.setupConfig( config );
116 config.readConfig( "aoAtmosphere.conf" );
117 REQUIRE( atm.loadConfig( config ) == mx::error_t::noerror );
118
119 REQUIRE( atm.nonKolmogorov() == false );
120 }
121 WHEN( "setting nonKolmogorov to false" )
122 {
123 appConfigurator config;
124
125 writeConfigFile( "aoAtmosphere.conf", { "atm" }, { "nonKolmogorov" }, { "true" } );
126
127 atm.setupConfig( config );
128 config.readConfig( "aoAtmosphere.conf" );
129 REQUIRE( atm.loadConfig( config ) == mx::error_t::noerror );
130
131 REQUIRE( atm.nonKolmogorov() == true );
132 }
133
134 WHEN( "setting nonKolmogorov alpha" )
135 {
136 atm.nonKolmogorov( false );
137 REQUIRE( atm.nonKolmogorov() == false );
138
139 appConfigurator config;
140
141 writeConfigFile( "aoAtmosphere.conf", { "atm" }, { "alpha" }, { "4.7" } );
142
143 atm.setupConfig( config );
144 config.readConfig( "aoAtmosphere.conf" );
145 REQUIRE( atm.loadConfig( config ) == mx::error_t::noerror );
146
147 REQUIRE( atm.alpha( 0 ) == Approx( 4.7 ) );
148 REQUIRE( atm.nonKolmogorov() == true );
149 }
150
151 WHEN( "setting nonKolmogorov beta" )
152 {
153 atm.nonKolmogorov( false );
154 REQUIRE( atm.nonKolmogorov() == false );
155
156 appConfigurator config;
157
158 writeConfigFile( "aoAtmosphere.conf", { "atm" }, { "beta" }, { "0.026" } );
159
160 atm.setupConfig( config );
161 config.readConfig( "aoAtmosphere.conf" );
162 REQUIRE( atm.loadConfig( config ) == mx::error_t::noerror );
163
164 REQUIRE( atm.beta( 0 ) == Approx( 0.026 ) );
165 REQUIRE( atm.nonKolmogorov() == true );
166 }
167
168 WHEN( "setting nonKolmogorov beta_0" )
169 {
170 atm.nonKolmogorov( false );
171 REQUIRE( atm.nonKolmogorov() == false );
172
173 appConfigurator config;
174
175 writeConfigFile( "aoAtmosphere.conf", { "atm" }, { "beta_0" }, { "1e-7" } );
176
177 atm.setupConfig( config );
178 config.readConfig( "aoAtmosphere.conf" );
179 REQUIRE( atm.loadConfig( config ) == mx::error_t::noerror );
180
181 REQUIRE( atm.beta_0( 0 ) == Approx( 1e-7 ) );
182 REQUIRE( atm.nonKolmogorov() == true );
183 }
184 }
185 GIVEN( "command line options" )
186 {
188 atm.setSingleLayer( 0.2, 0.5e-6, 25.0, 0.0, 0.0, 10.0, 0.0 );
189
190 WHEN( "setting nonKolmogorov to true" )
191 {
192 appConfigurator config;
193 std::vector<std::string> argvs( 2 );
194
195 argvs[0] = "test";
196 argvs[1] = "--atm.nonKolmogorov=true";
197
198 char *argv[2];
199 argv[0] = (char *)argvs[0].data();
200 argv[1] = (char *)argvs[1].data();
201
202 REQUIRE( atm.nonKolmogorov() == false );
203 atm.setupConfig( config );
204 config.parseCommandLine( argvs.size(), argv );
205 REQUIRE( atm.loadConfig( config ) == mx::error_t::noerror );
206 REQUIRE( atm.nonKolmogorov() == true );
207 }
208
209 WHEN( "setting nonKolmogorov to true" )
210 {
211 appConfigurator config;
212 std::vector<std::string> argvs( 2 );
213
214 argvs[0] = "test";
215 argvs[1] = "--atm.nonKolmogorov=false";
216
217 char *argv[2];
218 argv[0] = (char *)argvs[0].data();
219 argv[1] = (char *)argvs[1].data();
220
221 atm.nonKolmogorov( true );
222 REQUIRE( atm.nonKolmogorov() == true );
223 atm.setupConfig( config );
224 config.parseCommandLine( argvs.size(), argv );
225 REQUIRE( atm.loadConfig( config ) == mx::error_t::noerror );
226 REQUIRE( atm.nonKolmogorov() == false );
227 }
228 }
229}
230
231/// Verify that layer-strength normalization rejects invalid inputs without changing state.
232/** Exercises mx::AO::analysis::aoAtmosphere::layer_Cn2. */
233/**
234 * \ingroup aoAtmosphere_unit_tests
235 */
236TEST_CASE( "Atmosphere layer-strength normalization is transactional", "[ao::analysis::aoAtmosphere]" )
237{
238 aoAtmosphere<realT> atmosphere;
239 atmosphere.setSingleLayer( 0.2, 0.5e-6, 25.0, 0.0, 0.0, 10.0, 0.0 );
240 const std::vector<realT> originalStrength = atmosphere.layer_Cn2();
241
242 SECTION( "empty strengths" )
243 {
244 REQUIRE( atmosphere.layer_Cn2( std::vector<realT>{} ) == mx::error_t::invalidarg );
245 }
246
247 SECTION( "negative strength" )
248 {
249 REQUIRE( atmosphere.layer_Cn2( { 1.0, -1.0 } ) == mx::error_t::invalidarg );
250 }
251
252 SECTION( "nonfinite strength" )
253 {
254 REQUIRE( atmosphere.layer_Cn2( { 1.0, std::numeric_limits<realT>::quiet_NaN() } ) == mx::error_t::invalidarg );
255 }
256
257 SECTION( "nonfinite sum" )
258 {
259 REQUIRE( atmosphere.layer_Cn2( { std::numeric_limits<realT>::max(), std::numeric_limits<realT>::max() } ) ==
261 }
262
263 SECTION( "zero sum" )
264 {
265 REQUIRE( atmosphere.layer_Cn2( { 0.0, 0.0 } ) == mx::error_t::invalidarg );
266 }
267
268 SECTION( "invalid reference wavelength" )
269 {
270 REQUIRE( atmosphere.layer_Cn2( { 1.0 }, -1.0 ) == mx::error_t::invalidarg );
271 }
272
273 REQUIRE( atmosphere.layer_Cn2() == originalStrength );
274 REQUIRE( atmosphere.validate() == mx::error_t::noerror );
275}
276
277/// Verify that complete atmosphere validation rejects every layer-vector mismatch.
278/** Exercises mx::AO::analysis::aoAtmosphere::validate with each core and non-Kolmogorov vector setter. */
279/**
280 * \ingroup aoAtmosphere_unit_tests
281 */
282TEST_CASE( "Atmosphere validation rejects mismatched layer vectors", "[ao::analysis::aoAtmosphere]" )
283{
284 aoAtmosphere<realT> atmosphere;
285 atmosphere.setSingleLayer( 0.2, 0.5e-6, 25.0, 0.0, 0.0, 10.0, 0.0 );
286
287 SECTION( "layer strengths" )
288 {
289 REQUIRE( atmosphere.layer_Cn2( { 1.0, 1.0 } ) == mx::error_t::noerror );
290 }
291
292 SECTION( "outer scales" )
293 {
294 atmosphere.L_0( { 25.0, 25.0 } );
295 }
296
297 SECTION( "inner scales" )
298 {
299 atmosphere.l_0( { 0.0, 0.0 } );
300 }
301
302 SECTION( "layer heights" )
303 {
304 atmosphere.layer_z( { 0.0, 0.0 } );
305 }
306
307 SECTION( "wind speeds" )
308 {
309 atmosphere.layer_v_wind( { 10.0, 10.0 } );
310 }
311
312 SECTION( "wind directions" )
313 {
314 atmosphere.layer_dir( { 0.0, 0.0 } );
315 }
316
317 SECTION( "non-Kolmogorov exponents" )
318 {
319 atmosphere.alpha( { 11.0 / 3.0, 11.0 / 3.0 } );
320 }
321
322 SECTION( "non-Kolmogorov normalizations" )
323 {
324 atmosphere.beta( { 1.0, 1.0 } );
325 }
326
327 SECTION( "non-Kolmogorov constants" )
328 {
329 atmosphere.beta_0( { 0.0, 0.0 } );
330 }
331
332 REQUIRE( atmosphere.validate() == mx::error_t::sizeerr );
333}
334
335/// Verify that atmosphere validation rejects nonphysical scalar and layer values.
336/** Exercises mx::AO::analysis::aoAtmosphere::validate after mutation through its public setters. */
337/**
338 * \ingroup aoAtmosphere_unit_tests
339 */
340TEST_CASE( "Atmosphere validation rejects invalid physical values", "[ao::analysis::aoAtmosphere]" )
341{
342 aoAtmosphere<realT> atmosphere;
343 atmosphere.setSingleLayer( 0.2, 0.5e-6, 25.0, 0.0, 0.0, 10.0, 0.0 );
344
345 SECTION( "invalid Fried parameter" )
346 {
347 atmosphere.r_0( 0.0, 0.5e-6 );
348 }
349
350 SECTION( "invalid observatory height" )
351 {
352 atmosphere.h_obs( -1.0 );
353 }
354
355 SECTION( "invalid atmospheric scale height" )
356 {
357 atmosphere.H( 0.0 );
358 }
359
360 SECTION( "nonfinite outer scale" )
361 {
362 atmosphere.L_0( std::vector<realT>{ std::numeric_limits<realT>::infinity() } );
363 }
364
365 SECTION( "negative inner scale" )
366 {
367 atmosphere.l_0( std::vector<realT>{ -1.0 } );
368 }
369
370 SECTION( "negative layer height" )
371 {
372 atmosphere.layer_z( std::vector<realT>{ -1.0 } );
373 }
374
375 SECTION( "negative wind speed" )
376 {
377 atmosphere.layer_v_wind( std::vector<realT>{ -1.0 } );
378 }
379
380 SECTION( "nonfinite wind direction" )
381 {
382 atmosphere.layer_dir( std::vector<realT>{ std::numeric_limits<realT>::quiet_NaN() } );
383 }
384
385 SECTION( "invalid non-Kolmogorov normalization" )
386 {
387 atmosphere.nonKolmogorov( true );
388 atmosphere.beta( std::vector<realT>{ 0.0 } );
389 }
390
391 REQUIRE( atmosphere.validate() == mx::error_t::invalidconfig );
392}
393
394/// Verify that atmosphere validation preserves model-specific physical conventions.
395/** Exercises mx::AO::analysis::aoAtmosphere::validate for static layers, infinite outer scale, and explicit
396 * non-Kolmogorov normalization. */
397/**
398 * \ingroup aoAtmosphere_unit_tests
399 */
400TEST_CASE( "Atmosphere validation accepts supported physical conventions", "[ao::analysis::aoAtmosphere]" )
401{
402 aoAtmosphere<realT> atmosphere;
403 atmosphere.setSingleLayer( 0.2, 0.5e-6, 25.0, 0.0, 0.0, 10.0, 0.0 );
404
405 SECTION( "static layer" )
406 {
407 atmosphere.layer_v_wind( std::vector<realT>{ 0.0 } );
408 }
409
410 SECTION( "infinite outer scale" )
411 {
412 atmosphere.L_0( std::vector<realT>{ 0.0 } );
413 }
414
415 SECTION( "non-Kolmogorov model without a Fried parameter" )
416 {
417 atmosphere.r_0( 0.0, 0.5e-6 );
418 atmosphere.nonKolmogorov( true );
419 }
420
421 REQUIRE( atmosphere.validate() == mx::error_t::noerror );
422}
423
424/// Verify that configuration loading returns layer-strength validation failures.
425/** Exercises mx::AO::analysis::aoAtmosphere::loadConfig and
426 * mx::AO::analysis::aoAtmosphere::layer_Cn2. */
427/**
428 * \ingroup aoAtmosphere_unit_tests
429 */
430TEST_CASE( "Atmosphere configuration propagates invalid layer strengths", "[ao::analysis::aoAtmosphere]" )
431{
432 aoAtmosphere<realT> atmosphere;
433 atmosphere.setSingleLayer( 0.2, 0.5e-6, 25.0, 0.0, 0.0, 10.0, 0.0 );
434
435 appConfigurator config;
436 writeConfigFile( "aoAtmosphere.conf", { "atm" }, { "layer_Cn2" }, { "0,0" } );
437 atmosphere.setupConfig( config );
438 config.readConfig( "aoAtmosphere.conf" );
439
440 REQUIRE( atmosphere.loadConfig( config ) == mx::error_t::invalidarg );
441 REQUIRE( atmosphere.validate() == mx::error_t::noerror );
442 REQUIRE( atmosphere.layer_Cn2() == std::vector<realT>{ 1.0 } );
443}
Provides a class to specify atmosphere parameters.
void writeConfigFile(const std::string &fname, const std::vector< std::string > &sections, const std::vector< std::string > &keywords, const std::vector< std::string > &values)
A simple config file writing function, useful for testing.
A class to specify atmosphere parameters and perform related calculations.
realT lam_0()
Get the current value of the reference wavelength.
realT h_obs()
Get the height of the observatory.
realT l_0(const size_t &n)
Get the value of the inner scale for a single layer.
error_t validate() const
Validate the complete atmosphere configuration for calculation.
realT layer_dir(const int n)
Get the wind direction of a single layer.
realT L_0(const size_t &n)
Get the value of the outer scale for a single layer.
void nonKolmogorov(const bool &nk)
Set the value of m_nonKolmogorov.
realT beta_0(const size_t &n)
Return the PSD constant for a single layer.
realT beta(const size_t &n)
Return the PSD normalization for a single layer.
void setupConfig(app::appConfigurator &config)
Setup the configurator to configure this class.
realT H()
Get the atmospheric scale height.
void setSingleLayer(realT r0, realT lam0, realT L0, realT l0, realT lz, realT vw, realT dir)
Set a single layer model.
realT v_wind()
Get the 5/3 moment weighted mean wind speed.
realT layer_Cn2(const int n)
Get the strength of a single layer.
realT alpha(const size_t &n)
Return the PSD index for a single layer.
realT layer_v_wind(const int n)
Get the wind speed of a single layer.
realT z_mean()
Get the weighted mean layer height.
realT r_0()
Get the value of Fried's parameter r_0 at the reference wavelength lam_0.
error_t loadConfig(app::appConfigurator &config)
Load the configuration of this class from a configurator.
realT layer_z(const size_t n)
Get the height of a single layer.
TEST_CASE("Loading aoAtmosphere config settings", "[ao::analysis::aoAtmosphere]")
Verify parsing and validation of atmosphere configuration settings.
@ noerror
No error has occurred.
Definition error_t.hpp:27
@ sizeerr
A size was invalid or calculated incorrectly.
Definition error_t.hpp:35
@ invalidconfig
A config setting was invalid.
Definition error_t.hpp:30
@ invalidarg
An argument was invalid.
Definition error_t.hpp:29
Class to manage a set of configurable values, and read their values from config/ini files and the com...
int readConfig(const std::string &fname, bool reportFileNotFound=true)
Read and parse a config/ini file, updating the targets.
void parseCommandLine(int argc, char **argv, const std::string &oneTarget="")
Parse the command line, updating the targets.