mxlib
c++ tools for analyzing astronomical data and other tasks by Jared R. Males. [git repo]
Loading...
Searching...
No Matches
fourierTemporalPSD_test.cpp
Go to the documentation of this file.
1/** \file fourierTemporalPSD_test.cpp
2 * \brief Tests of Fourier-mode temporal power spectral densities.
3 */
4
5#include "../../../catch2/catch.hpp"
6
7#define MX_NO_ERROR_REPORTS
8
10
11#include <algorithm>
12#include <cmath>
13#include <filesystem>
14#include <limits>
15#include <sstream>
16#include <string>
17#include <type_traits>
18#include <utility>
19#include <vector>
20
21#include <unistd.h>
22
24
25/// AO-system test type that forces local template instantiation under sanitizers.
26struct aoSystemT : public aoSystemBaseT
27{
28};
29
31
32namespace
33{
34/// \cond fourierTemporalPSD_test_detail
35
36/// Sentinel GSL error handler used to verify restoration after PSD calculations.
37void testGslErrorHandler( const char *reason, /**< [in] GSL diagnostic text */
38 const char *file, /**< [in] GSL source file */
39 int line, /**< [in] GSL source line */
40 int errorNumber /**< [in] GSL status code */ )
41{
42 static_cast<void>( reason );
43 static_cast<void>( file );
44 static_cast<void>( line );
45 static_cast<void>( errorNumber );
46}
47
48/// Return a null workspace to exercise allocation-failure propagation.
49gsl_integration_workspace *failWorkspaceAllocation( size_t size /**< [in] requested workspace size */ )
50{
51 static_cast<void>( size );
52 return nullptr;
53}
54
55/// Test evaluator exposing the protected allocator-injection constructor.
56struct allocationFailureTemporalPsdT : public temporalPsdT
57{
58 /// Construct an evaluator whose workspace allocation always fails.
59 allocationFailureTemporalPsdT() : temporalPsdT( &failWorkspaceAllocation )
60 {
61 }
62};
63
64/// Test evaluator exposing workspace allocation and ownership state.
65struct workspaceOwnershipTemporalPsdT : public temporalPsdT
66{
67 /// Preserve move construction for the derived test evaluator.
68 workspaceOwnershipTemporalPsdT( workspaceOwnershipTemporalPsdT && ) noexcept = default;
69
70 /// Construct a test evaluator without allocating a workspace.
71 workspaceOwnershipTemporalPsdT() = default;
72
73 /// Allocate the workspace through the production helper.
74 mx::error_t allocateTestWorkspace()
75 {
76 return allocateWorkspace();
77 }
78
79 /// Return whether this evaluator currently owns a workspace.
80 bool ownsWorkspace() const
81 {
82 return m_workspace != nullptr;
83 }
84};
85
86/// \endcond
87
88} // namespace
89
90/// Verify that Fourier temporal PSD evaluators uniquely own movable GSL workspaces.
91/** Exercises the ownership contract of mx::AO::analysis::fourierTemporalPSD. */
92/**
93 * \ingroup fourierTemporalPSD_unit_tests
94 */
95TEST_CASE( "Fourier temporal PSD workspace ownership is move-only", "[ao::analysis::fourierTemporalPSD]" )
96{
97 STATIC_REQUIRE_FALSE( std::is_copy_constructible_v<temporalPsdT> );
98 STATIC_REQUIRE_FALSE( std::is_copy_assignable_v<temporalPsdT> );
99 STATIC_REQUIRE( std::is_nothrow_move_constructible_v<temporalPsdT> );
100 STATIC_REQUIRE( std::is_nothrow_move_assignable_v<temporalPsdT> );
101
102 workspaceOwnershipTemporalPsdT source;
103 REQUIRE( source.allocateTestWorkspace() == mx::error_t::noerror );
104 REQUIRE( source.ownsWorkspace() );
105
106 workspaceOwnershipTemporalPsdT destination( std::move( source ) );
107 REQUIRE_FALSE( source.ownsWorkspace() );
108 REQUIRE( destination.ownsWorkspace() );
109 REQUIRE( destination.m_aosys == nullptr );
110}
111
112/// Verify that public Fourier temporal PSD calculations reject malformed inputs before modifying output.
113/** Exercises mx::AO::analysis::fourierTemporalPSD::singleLayerPSD and
114 * mx::AO::analysis::fourierTemporalPSD::multiLayerPSD precondition handling. */
115/**
116 * \ingroup fourierTemporalPSD_unit_tests
117 */
118TEST_CASE( "Fourier temporal PSD validates public calculation inputs", "[ao::analysis::fourierTemporalPSD]" )
119{
120 aoSystemT aoSystem;
121 aoSystem.D( 6.5 );
122 aoSystem.lam_sci( 1.0e-6 );
123 aoSystem.lam_wfs( 0.8e-6 );
124 aoSystem.atm.setSingleLayer( 0.16, 500e-9, 25.0, 0.0, 0.0, 10.0, 0.0 );
125
126 temporalPsdT temporalPsd;
127 temporalPsd.m_aosys = &aoSystem;
129
130 std::vector<double> frequency{ 0.0, 1.0 };
131 std::vector<double> psd( frequency.size(), 7.0 );
132 const std::vector<double> unchanged = psd;
134 report.record( GSL_SUCCESS, 0, 0.0, 0.0, 0.0, 1.0, 1.0 );
135
136 SECTION( "null AO system" )
137 {
138 temporalPsd.m_aosys = nullptr;
139 REQUIRE( temporalPsd.singleLayerPSD( psd, frequency, 1.0, 0.0, 0, 1, frequency.back(), &report ) ==
141 }
142
143 SECTION( "empty vectors" )
144 {
145 std::vector<double> empty;
146 REQUIRE( temporalPsd.singleLayerPSD( empty, empty, 1.0, 0.0, 0, 1, 0, &report ) == mx::error_t::sizeerr );
147 }
148
149 SECTION( "mismatched vector sizes" )
150 {
151 std::vector<double> undersizedPsd{ 7.0 };
152 REQUIRE( temporalPsd.singleLayerPSD( undersizedPsd, frequency, 1.0, 0.0, 0, 1, frequency.back(), &report ) ==
154 REQUIRE( undersizedPsd == std::vector<double>{ 7.0 } );
155 }
156
157 SECTION( "nonmonotone frequency grid" )
158 {
159 frequency[1] = frequency[0];
160 REQUIRE( temporalPsd.singleLayerPSD( psd, frequency, 1.0, 0.0, 0, 1, 0, &report ) == mx::error_t::invalidarg );
161 }
162
163 SECTION( "negative frequency" )
164 {
165 frequency[0] = -1.0;
166 REQUIRE( temporalPsd.singleLayerPSD( psd, frequency, 1.0, 0.0, 0, 1, 0, &report ) == mx::error_t::invalidarg );
167 }
168
169 SECTION( "nonfinite frequency grid" )
170 {
171 frequency[1] = std::numeric_limits<double>::quiet_NaN();
172 REQUIRE( temporalPsd.singleLayerPSD( psd, frequency, 1.0, 0.0, 0, 1, 0, &report ) == mx::error_t::invalidarg );
173 }
174
175 SECTION( "nonfinite mode" )
176 {
177 REQUIRE(
178 temporalPsd
179 .singleLayerPSD( psd, frequency, std::numeric_limits<double>::infinity(), 0.0, 0, 1, 0, &report ) ==
181 }
182
183 SECTION( "invalid parity" )
184 {
185 REQUIRE( temporalPsd.singleLayerPSD( psd, frequency, 1.0, 0.0, 0, 0, 0, &report ) == mx::error_t::invalidarg );
186 }
187
188 SECTION( "negative cutoff" )
189 {
190 REQUIRE( temporalPsd.singleLayerPSD( psd, frequency, 1.0, 0.0, 0, 1, -1.0, &report ) ==
192 }
193
194 SECTION( "invalid layer index" )
195 {
196 REQUIRE( temporalPsd.singleLayerPSD( psd, frequency, 1.0, 0.0, 1, 1, 0, &report ) == mx::error_t::invalidarg );
197 }
198
199 SECTION( "invalid tolerance" )
200 {
201 temporalPsd.absTol( 0 );
202 REQUIRE( temporalPsd.singleLayerPSD( psd, frequency, 1.0, 0.0, 0, 1, 0, &report ) ==
204 }
205
206 SECTION( "invalid relative tolerance" )
207 {
208 temporalPsd.relTol( 1 );
209 REQUIRE( temporalPsd.singleLayerPSD( psd, frequency, 1.0, 0.0, 0, 1, 0, &report ) ==
211 }
212
213 SECTION( "nonpositive aperture" )
214 {
215 aoSystem.D( 0 );
216 REQUIRE( temporalPsd.singleLayerPSD( psd, frequency, 1.0, 0.0, 0, 1, 0, &report ) ==
218 }
219
220 SECTION( "zero wind" )
221 {
222 aoSystem.atm.layer_v_wind( std::vector<double>{ 0.0 } );
223 REQUIRE( temporalPsd.multiLayerPSD<false>( psd, frequency, 1.0, 0.0, 1, 0, &report ) ==
225 }
226
227 SECTION( "mismatched atmosphere vectors" )
228 {
229 aoSystem.atm.layer_dir( std::vector<double>{} );
230 REQUIRE( temporalPsd.multiLayerPSD<false>( psd, frequency, 1.0, 0.0, 1, 0, &report ) == mx::error_t::sizeerr );
231 }
232
233 REQUIRE( psd == unchanged );
234 REQUIRE( report.integrationsAttempted == 0 );
235}
236
237/// Verify that GSL workspace allocation failure is returned without evaluating or modifying the PSD.
238/** Exercises mx::AO::analysis::fourierTemporalPSD::singleLayerPSD with an injected failing workspace allocator. */
239/**
240 * \ingroup fourierTemporalPSD_unit_tests
241 */
242TEST_CASE( "Fourier temporal PSD reports workspace allocation failure", "[ao::analysis::fourierTemporalPSD]" )
243{
244 aoSystemT aoSystem;
245 aoSystem.D( 6.5 );
246 aoSystem.atm.setSingleLayer( 0.16, 500e-9, 25.0, 0.0, 0.0, 10.0, 0.0 );
247
248 allocationFailureTemporalPsdT temporalPsd;
249 temporalPsd.m_aosys = &aoSystem;
250 temporalPsd._useBasis = mx::AO::analysis::basis::basic;
251
252 std::vector<double> frequency{ 1.0 };
253 std::vector<double> psd{ 7.0 };
255 REQUIRE( temporalPsd.singleLayerPSD( psd, frequency, 1.0, 0.0, 0, 1, 0, &report ) == mx::error_t::allocerr );
256 REQUIRE( psd == std::vector<double>{ 7.0 } );
257 REQUIRE( report.integrationsAttempted == 0 );
258}
259
260/// Verify that PSD-grid generation validates its public inputs before creating output.
261/** Exercises mx::AO::analysis::fourierTemporalPSD::makePSDGrid precondition handling. */
262/**
263 * \ingroup fourierTemporalPSD_unit_tests
264 */
265TEST_CASE( "Fourier temporal PSD validates grid-generation inputs", "[ao::analysis::fourierTemporalPSD]" )
266{
267 aoSystemT aoSystem;
268 aoSystem.D( 6.5 );
269 aoSystem.atm.setSingleLayer( 0.16, 500e-9, 25.0, 0.0, 0.0, 10.0, 0.0 );
270
271 temporalPsdT temporalPsd;
272 temporalPsd.m_aosys = &aoSystem;
274
275 const std::filesystem::path outputDirectory =
276 std::filesystem::temp_directory_path() /
277 ( "mxlib_fourierTemporalPSD_invalid_grid_" + std::to_string( static_cast<long long>( getpid() ) ) );
278 std::error_code filesystemError;
279 std::filesystem::remove_all( outputDirectory, filesystemError );
280 REQUIRE_FALSE( filesystemError );
281
282 SECTION( "empty output directory" )
283 {
284 REQUIRE( temporalPsd.makePSDGrid( "", 1, 1.0, 1.0 ) == mx::error_t::invalidarg );
285 }
286
287 SECTION( "nonpositive spatial extent" )
288 {
289 REQUIRE( temporalPsd.makePSDGrid( outputDirectory.string(), 0, 1.0, 1.0 ) == mx::error_t::invalidarg );
290 }
291
292 SECTION( "nonpositive frequency spacing" )
293 {
294 REQUIRE( temporalPsd.makePSDGrid( outputDirectory.string(), 1, 0.0, 1.0 ) == mx::error_t::invalidarg );
295 }
296
297 SECTION( "nonfinite frequency spacing" )
298 {
299 REQUIRE(
300 temporalPsd.makePSDGrid( outputDirectory.string(), 1, std::numeric_limits<double>::quiet_NaN(), 1.0 ) ==
302 }
303
304 SECTION( "nonpositive maximum frequency" )
305 {
306 REQUIRE( temporalPsd.makePSDGrid( outputDirectory.string(), 1, 1.0, 0.0 ) == mx::error_t::invalidarg );
307 }
308
309 SECTION( "negative exact-calculation cutoff" )
310 {
311 REQUIRE( temporalPsd.makePSDGrid( outputDirectory.string(), 1, 1.0, 1.0, -1.0 ) == mx::error_t::invalidarg );
312 }
313
314 SECTION( "unrepresentable sample count" )
315 {
316 REQUIRE( temporalPsd.makePSDGrid( outputDirectory.string(), 1, std::numeric_limits<double>::min(), 1.0 ) ==
318 }
319
320 SECTION( "invalid AO system" )
321 {
322 temporalPsd.m_aosys = nullptr;
323 REQUIRE( temporalPsd.makePSDGrid( outputDirectory.string(), 1, 1.0, 1.0 ) == mx::error_t::invalidconfig );
324 }
325
326 REQUIRE_FALSE( std::filesystem::exists( outputDirectory ) );
327}
328
329/// Verify that PSD-grid generation propagates calculation and output failures.
330/** Exercises mx::AO::analysis::fourierTemporalPSD::makePSDGrid failure handling after successful preflight. */
331/**
332 * \ingroup fourierTemporalPSD_unit_tests
333 */
334TEST_CASE( "Fourier temporal PSD reports grid-generation failures", "[ao::analysis::fourierTemporalPSD]" )
335{
336 aoSystemT aoSystem;
337 aoSystem.D( 6.5 );
338 aoSystem.atm.setSingleLayer( 0.16, 500e-9, 25.0, 0.0, 0.0, 10.0, 0.0 );
339
340 const std::filesystem::path outputPath =
341 std::filesystem::temp_directory_path() /
342 ( "mxlib_fourierTemporalPSD_grid_failure_" + std::to_string( static_cast<long long>( getpid() ) ) );
343 std::error_code filesystemError;
344 std::filesystem::remove_all( outputPath, filesystemError );
345 REQUIRE_FALSE( filesystemError );
346
347 SECTION( "mode calculation failure" )
348 {
349 allocationFailureTemporalPsdT temporalPsd;
350 temporalPsd.m_aosys = &aoSystem;
351 temporalPsd._useBasis = mx::AO::analysis::basis::basic;
352
353 REQUIRE( temporalPsd.makePSDGrid( outputPath.string(), 1, 1.0, 1.0 ) == mx::error_t::allocerr );
354 REQUIRE( std::filesystem::exists( outputPath / "params.txt" ) );
355 REQUIRE( std::filesystem::exists( outputPath / "psds" / "freq.binv" ) );
356 }
357
358 SECTION( "output path is a regular file" )
359 {
360 std::ofstream outputFile( outputPath );
361 REQUIRE( outputFile.is_open() );
362 outputFile.close();
363
364 temporalPsdT temporalPsd;
365 temporalPsd.m_aosys = &aoSystem;
367 REQUIRE( temporalPsd.makePSDGrid( outputPath.string(), 1, 1.0, 1.0 ) == mx::error_t::enotdir );
368 }
369
370 std::filesystem::remove_all( outputPath, filesystemError );
371 REQUIRE_FALSE( filesystemError );
372}
373
374/// Verify that Fourier PSD calculation requires a valid atmosphere and accepts every complete preset.
375/** Exercises mx::AO::analysis::aoAtmosphere::validate,
376 * mx::AO::analysis::aoAtmosphere::loadGuyon2005, mx::AO::analysis::aoAtmosphere::loadLCO,
377 * mx::AO::analysis::aoAtmosphere::setSingleLayer, and
378 * mx::AO::analysis::fourierTemporalPSD::singleLayerPSD. */
379/**
380 * \ingroup fourierTemporalPSD_unit_tests
381 */
382TEST_CASE( "Fourier temporal PSD enforces atmosphere validity at calculation", "[ao::analysis::fourierTemporalPSD]" )
383{
384 aoSystemT aoSystem;
385 aoSystem.D( 6.5 );
386
387 temporalPsdT temporalPsd;
388 temporalPsd.m_aosys = &aoSystem;
390
391 std::vector<double> frequency{ 1.0 };
392 std::vector<double> psd{ 7.0 };
393
394 SECTION( "default atmosphere" )
395 {
396 REQUIRE( aoSystem.atm.validate() == mx::error_t::sizeerr );
397 REQUIRE( temporalPsd.singleLayerPSD( psd, frequency, 1.0, 0.0, 0, 1, frequency.back() ) ==
399 REQUIRE( psd == std::vector<double>{ 7.0 } );
400 }
401
402 SECTION( "Guyon 2005 preset with infinite outer scale" )
403 {
404 aoSystem.atm.loadGuyon2005();
405 REQUIRE( aoSystem.atm.L_0( 0 ) == 0.0 );
406 REQUIRE( aoSystem.atm.validate() == mx::error_t::noerror );
407 REQUIRE( temporalPsd.singleLayerPSD( psd, frequency, 1.0, 0.0, 0, 1, frequency.back() ) ==
409 }
410
411 SECTION( "LCO preset" )
412 {
413 aoSystem.atm.loadLCO();
414 REQUIRE( aoSystem.atm.validate() == mx::error_t::noerror );
415 REQUIRE( temporalPsd.singleLayerPSD( psd, frequency, 1.0, 0.0, 0, 1, frequency.back() ) ==
417 }
418
419 SECTION( "single-layer preset" )
420 {
421 aoSystem.atm.setSingleLayer( 0.2, 0.5e-6, 25.0, 0.0, 0.0, 10.0, 0.0 );
422 REQUIRE( aoSystem.atm.validate() == mx::error_t::noerror );
423 REQUIRE( temporalPsd.singleLayerPSD( psd, frequency, 1.0, 0.0, 0, 1, frequency.back() ) ==
425 }
426}
427
428/// Verify temporal-PSD tail initialization at and below its nominal averaging width.
429/** Exercises zero, one, 49, and 50 exactly integrated frequency bins. */
430/**
431 * \ingroup fourierTemporalPSD_unit_tests
432 */
433TEST_CASE( "Fourier temporal PSD handles short exact tails", "[ao::analysis::fourierTemporalPSD]" )
434{
435 aoSystemT aoSystem;
436 aoSystem.D( 6.5 );
437 aoSystem.atm.setSingleLayer( 0.16, 500e-9, 25.0, 0.0, 0.0, 10.0, 0.0 );
438
439 temporalPsdT temporalPsd;
440 temporalPsd.m_aosys = &aoSystem;
442
443 constexpr std::size_t frequencyCount = 55;
444 std::vector<double> frequency( frequencyCount );
445 for( std::size_t index = 0; index < frequency.size(); ++index )
446 {
447 frequency[index] = static_cast<double>( index + 1 );
448 }
449
450 for( const std::size_t exactCount : { 0U, 1U, 49U, 50U } )
451 {
452 DYNAMIC_SECTION( exactCount << " exact bins" )
453 {
454 std::vector<double> psd( frequency.size(), 0.0 );
455 const double maximumExactFrequency = exactCount == 0 ? 0.5 : frequency[exactCount - 1];
457
458 const mx::error_t result =
459 temporalPsd.singleLayerPSD( psd, frequency, 1.0, 0.0, 0, 1, maximumExactFrequency, &report );
460
461 if( exactCount == 0 )
462 {
463 REQUIRE( result == mx::error_t::invalidarg );
464 REQUIRE( report.integrationsAttempted == 0 );
465 continue;
466 }
467
468 REQUIRE( result == mx::error_t::noerror );
469 REQUIRE( report.integrationsAttempted == exactCount );
470
471 const std::size_t averageCount = std::min<std::size_t>( exactCount, 50 );
472 const double exponent = aoSystem.atm.alpha( 0 ) + 2.0;
473 double expected = 0.0;
474 for( std::size_t offset = averageCount; offset > 0; --offset )
475 {
476 const std::size_t index = exactCount - offset;
477 expected += psd[index] * std::pow( frequency[index] / frequency[exactCount], exponent );
478 }
479 expected /= static_cast<double>( averageCount );
480
481 REQUIRE( psd[exactCount] == Approx( expected ).epsilon( 1e-12 ) );
482 REQUIRE( std::isfinite( psd[exactCount] ) );
483 REQUIRE( psd[exactCount] >= 0.0 );
484
485 const double expectedLast =
486 psd[exactCount] * std::pow( frequency[exactCount] / frequency.back(), exponent );
487 REQUIRE( psd.back() == Approx( expectedLast ).epsilon( 1e-12 ) );
488 }
489 }
490}
491
492/// Verify aggregation and formatting of Fourier temporal-PSD quadrature diagnostics.
493/** Exercises `fourierTemporalPSDReport::record`, `fourierTemporalPSDReport::merge`, and
494 * `fourierTemporalPSDReport::write` with multiple GSL statuses and atmospheric layers. */
495/**
496 * \ingroup fourierTemporalPSD_unit_tests
497 */
498TEST_CASE( "Fourier temporal PSD aggregates quadrature diagnostics", "[ao::analysis::fourierTemporalPSD]" )
499{
501 report.record( GSL_SUCCESS, 0, 1.0, 2.0, 0.1, 0.1, 0.01 );
502 report.record( GSL_EROUND, 0, 2.0, 2.0, 0.4, 0.1, 0.01 );
503 report.record( GSL_EROUND, 1, 3.0, 4.0, 1.0, 0.1, 0.01 );
504
506 other.record( GSL_EDIVERGE, 2, 4.0, 1.0, 0.5, 0.1, 0.01 );
507 report.merge( other );
508
509 REQUIRE( report.integrationsAttempted == 4 );
510 REQUIRE( report.integrationsConverged == 1 );
511 REQUIRE( report.failureCount() == 3 );
512 REQUIRE( report.gslStatus.at( GSL_EROUND ).count == 2 );
513 REQUIRE( report.gslStatus.at( GSL_EROUND ).countByLayer.at( 0 ) == 1 );
514 REQUIRE( report.gslStatus.at( GSL_EROUND ).countByLayer.at( 1 ) == 1 );
515 REQUIRE( report.gslStatus.at( GSL_EROUND ).maximumAbsoluteError == Approx( 1.0 ) );
516 REQUIRE( report.gslStatus.at( GSL_EROUND ).maximumToleranceRatio == Approx( 10.0 ) );
517 REQUIRE( report.gslStatus.at( GSL_EROUND ).worstLayer == 1 );
518 REQUIRE( report.gslStatus.at( GSL_EROUND ).worstFrequency == Approx( 3.0 ) );
519 REQUIRE( report.gslStatus.at( GSL_EDIVERGE ).count == 1 );
520
521 std::ostringstream summary;
522 report.write( summary );
523 REQUIRE( summary.str().find( "1/4 converged" ) != std::string::npos );
524 REQUIRE( summary.str().find( gsl_strerror( GSL_EROUND ) ) != std::string::npos );
525 REQUIRE( summary.str().find( gsl_strerror( GSL_EDIVERGE ) ) != std::string::npos );
526 REQUIRE( summary.str().find( "layers {0: 1, 1: 1}" ) != std::string::npos );
527}
528
529/// Verify permissive and strict handling of GSL quadrature statuses.
530/** Exercises the status policy used by `fourierTemporalPSD::singleLayerPSD`. */
531/**
532 * \ingroup fourierTemporalPSD_unit_tests
533 */
534TEST_CASE( "Fourier temporal PSD applies quadrature policy", "[ao::analysis::fourierTemporalPSD]" )
535{
537 using mx::AO::analysis::fourierTemporalPSD_detail::applyPolicy;
538
539 REQUIRE( applyPolicy( GSL_SUCCESS, fourierTemporalPSDPolicy::permissive ) == mx::error_t::noerror );
540 for( const int status : { GSL_EMAXITER, GSL_EROUND, GSL_ESING, GSL_EDIVERGE } )
541 {
542 REQUIRE( applyPolicy( status, fourierTemporalPSDPolicy::permissive ) == mx::error_t::noerror );
543 REQUIRE( applyPolicy( status, fourierTemporalPSDPolicy::strict ) == mx::error_t::liberr );
544 }
545 REQUIRE( applyPolicy( GSL_EDOM, fourierTemporalPSDPolicy::permissive ) == mx::error_t::invalidconfig );
546 REQUIRE( applyPolicy( GSL_EINVAL, fourierTemporalPSDPolicy::permissive ) == mx::error_t::invalidconfig );
547 REQUIRE( applyPolicy( GSL_ENOMEM, fourierTemporalPSDPolicy::permissive ) == mx::error_t::allocerr );
548 REQUIRE( applyPolicy( GSL_EFAILED, fourierTemporalPSDPolicy::permissive ) == mx::error_t::liberr );
549}
550
551/// Verify multilayer error propagation and restoration of the caller's GSL handler.
552/** Exercises `fourierTemporalPSD::multiLayerPSD` with an invalid basis. */
553/**
554 * \ingroup fourierTemporalPSD_unit_tests
555 */
556TEST_CASE( "Fourier temporal PSD propagates multilayer errors", "[ao::analysis::fourierTemporalPSD]" )
557{
558 aoSystemT aoSystem;
559 aoSystem.D( 6.5 );
560 aoSystem.atm.setSingleLayer( 0.16, 500e-9, 25.0, 0.0, 0.0, 10.0, 0.0 );
561
562 temporalPsdT temporalPsd;
563 temporalPsd.m_aosys = &aoSystem;
564 temporalPsd._useBasis = -1;
565
566 std::vector<double> frequency{ 1.0 };
567 std::vector<double> psd( frequency.size(), 0.0 );
569
570 gsl_error_handler_t *previousHandler = gsl_set_error_handler( &testGslErrorHandler );
571 const mx::error_t result =
572 temporalPsd.multiLayerPSD<false>( psd,
573 frequency,
574 1.0,
575 0.0,
576 1,
577 frequency.back(),
578 &report,
580 gsl_error_handler_t *observedHandler = gsl_set_error_handler( previousHandler );
581
582 REQUIRE( result == mx::error_t::invalidarg );
583 REQUIRE( report.integrationsAttempted == 0 );
584 REQUIRE( observedHandler == &testGslErrorHandler );
585}
void loadLCO()
Load parameters corresponding to the median atmosphere of the GMT site survey at LCO.
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 loadGuyon2005()
Load the default atmosphere model from Guyon (2005).
void setSingleLayer(realT r0, realT lam0, realT L0, realT l0, realT lz, realT vw, realT dir)
Set a single layer model.
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.
Describes an analytic adaptive optics (AO) system.
Definition aoSystem.hpp:64
void D(realT nD)
Set the value of the primary mirror diameter.
void lam_wfs(realT nlam)
Set the value of the WFS wavelength.
void lam_sci(realT nlam)
Set the science wavelength.
Calculation of the temporal PSD of Fourier modes.
@ basic
The basic sine and cosine Fourier modes.
fourierTemporalPSDPolicy
Policy for handling GSL quadrature non-convergence statuses.
@ permissive
Retain the best finite approximation and record the status.
error_t
The mxlib error codes.
Definition error_t.hpp:26
@ noerror
No error has occurred.
Definition error_t.hpp:27
@ sizeerr
A size was invalid or calculated incorrectly.
Definition error_t.hpp:35
@ enotdir
Not a directory (ENOTDIR).
Definition error_t.hpp:123
@ allocerr
An error occurred during memory allocation.
Definition error_t.hpp:36
@ invalidconfig
A config setting was invalid.
Definition error_t.hpp:30
@ invalidarg
An argument was invalid.
Definition error_t.hpp:29
@ liberr
An error was returned by a library.
Definition error_t.hpp:50
TEST_CASE("Fourier temporal PSD workspace ownership is move-only", "[ao::analysis::fourierTemporalPSD]")
Verify that Fourier temporal PSD evaluators uniquely own movable GSL workspaces.
AO-system test type that forces local template instantiation under sanitizers.
size_t integrationsConverged
Number of quadrature calls returning GSL_SUCCESS.
size_t failureCount() const
Return the total number of non-successful integrations.
void write(std::ostream &output) const
Write a human-readable summary of the accumulated quadrature diagnostics.
void record(int status, size_t layer, realT frequency, realT result, realT absoluteError, realT absoluteTolerance, realT relativeTolerance)
Record one quadrature result.
size_t integrationsAttempted
Total number of quadrature calls.
std::map< int, statusSummary > gslStatus
Summaries keyed by the raw GSL status code.
void merge(const fourierTemporalPSDReport &other)
Merge another report into this report.
Class to manage the calculation of temporal PSDs of the Fourier modes in atmospheric turbulence.
error_t singleLayerPSD(std::vector< realT > &PSD, std::vector< realT > &freq, realT m, realT n, int layer_i, int p, realT fmax=0, reportT *report=nullptr, fourierTemporalPSDPolicy policy=fourierTemporalPSDPolicy::permissive)
Calculate the temporal PSD for a Fourier mode for a single layer.
aosysT * m_aosys
Pointer to an AO system structure.
void absTol(realT at)
Set absolute tolerance.
error_t makePSDGrid(const std::string &dir, int mnMax, realT dFreq, realT maxFreq, realT fmax=0)
Calculate PSDs over a grid of spatial frequencies.
error_t multiLayerPSD(std::vector< realT > &PSD, std::vector< realT > &freq, realT m, realT n, int p, realT fmax=0, reportT *report=nullptr, fourierTemporalPSDPolicy policy=fourierTemporalPSDPolicy::permissive)
Calculate the temporal PSD for a Fourier mode in a multi-layer model.
void relTol(realT rt)
Set relative tolerance.