API
 
Loading...
Searching...
No Matches
strehlEstimator_test.cpp
Go to the documentation of this file.
1/** \file strehlEstimator_test.cpp
2 * \brief Catch2 tests for the strehlEstimator app.
3 * \author OpenAI Codex
4 *
5 * \ingroup strehlEstimator_files
6 */
7
8/** \defgroup strehlEstimator_unit_test strehlEstimator Unit Tests
9 * \brief Unit tests for the strehlEstimator application.
10 *
11 * \ingroup application_unit_test
12 */
13
14#include "../strehlEstimator.hpp"
15
16#include "../../../tests/testMacrosINDI.hpp"
17#include "../../../tests/testXWC.hpp"
18
19#include <cmath>
20
21using namespace MagAOX::app;
22
23namespace libXWCTest
24{
25
26/// Namespace for `strehlEstimator` unit tests.
27/** \ingroup strehlEstimator_unit_test
28 */
29namespace strehlEstimatorTest
30{
31
32namespace
33{
34
35/// \cond DOXYGEN_SUPPRESS_TEST_HARNESS
36/// Test harness exposing the `strehlEstimator` internals needed by the unit tests.
37class strehlEstimator_test : public strehlEstimator
38{
39 public:
40 /// Construct a testable `strehlEstimator` instance with callback keys pre-seeded.
41 strehlEstimator_test( const std::string &device )
42 {
43 m_configName = device;
44
45 setupConfig();
46
47 XWCTEST_SETUP_INDI_ARB_PROP( m_indiP_fps, camwfs, fps );
48 XWCTEST_SETUP_INDI_ARB_PROP( m_indiP_emg, camwfs, emgain );
49 XWCTEST_SETUP_INDI_ARB_PROP( m_indiP_stage, stagebs, presetName );
50 XWCTEST_SETUP_INDI_ARB_PROP( m_indiP_tcsi_seeing, tcsi, seeing );
51 XWCTEST_SETUP_INDI_ARB_PROP( m_indiP_tcsi_telpos, tcsi, telpos );
52
53 XWCTEST_SETUP_INDI_ARB_NEW_PROP( m_indiP_mag, star_mag );
54 XWCTEST_SETUP_INDI_ARB_NEW_PROP( m_indiP_seeing_magaox, seeing );
55 XWCTEST_SETUP_INDI_ARB_NEW_PROP( m_indiP_windSpeed, wind_speed );
56 XWCTEST_SETUP_INDI_ARB_NEW_PROP( m_indiP_useEstimates, use_estimates );
57 }
58
59 /// Initialize the published INDI properties without starting the shmim-monitor threads.
60 int initializePublishedProperties()
61 {
62 if( createCurrentEstimatedProperty( m_indiP_mag, "star_mag", "Star Magnitude", "Error Budget" ) < 0 )
63 {
64 return -1;
65 }
66
67 if( createCurrentEstimatedProperty( m_indiP_seeing_magaox, "seeing", "Seeing", "Error Budget" ) < 0 )
68 {
69 return -1;
70 }
71
72 if( createStandardIndiSelectionSw( m_indiP_windSpeed,
73 "wind_speed",
74 windSpeedSelectionElements(),
75 windSpeedSelectionLabels(),
76 "Wind Speed",
77 "Error Budget" ) < 0 )
78 {
79 return -1;
80 }
81
82 if( createStandardIndiToggleSw( m_indiP_useEstimates, "use_estimates", "Use Estimates", "Error Budget" ) < 0 )
83 {
84 return -1;
85 }
86
87 if( createROIndiNumber( m_indiP_strehl, "strehl_optimal", "Strehl", "Error Budget" ) < 0 )
88 {
89 return -1;
90 }
91 m_indiP_strehl.add( pcf::IndiElement( "pyramid", 0.0f ) );
92
93 if( createROIndiNumber( m_indiP_wfe, "wfe_predicted", "WFE", "Error Budget" ) < 0 )
94 {
95 return -1;
96 }
97 m_indiP_wfe.add( pcf::IndiElement( "total", 0.0f ) );
98 m_indiP_wfe.add( pcf::IndiElement( "measurement", 0.0f ) );
99 m_indiP_wfe.add( pcf::IndiElement( "time_delay", 0.0f ) );
100 m_indiP_wfe.add( pcf::IndiElement( "fitting", 0.0f ) );
101
102 if( createROIndiNumber( m_indiP_loopSpeedOptimum, "loop_speed_optimum", "Optimum Loop Speed", "Error Budget" ) <
103 0 )
104 {
105 return -1;
106 }
107 m_indiP_loopSpeedOptimum.add( pcf::IndiElement( "fps", 0.0f ) );
108 m_indiP_loopSpeedOptimum.add( pcf::IndiElement( "strehl", 0.0f ) );
109 m_indiP_loopSpeedOptimum.add( pcf::IndiElement( "wfe_total", 0.0f ) );
110 m_indiP_loopSpeedOptimum.add( pcf::IndiElement( "wfe_measurement", 0.0f ) );
111 m_indiP_loopSpeedOptimum.add( pcf::IndiElement( "wfe_time_delay", 0.0f ) );
112 m_indiP_loopSpeedOptimum.add( pcf::IndiElement( "wfe_fitting", 0.0f ) );
113
114 updatePlanningProperties();
115 updatePredictionOutputs();
116
117 return 0;
118 }
119
120 /// Recompute the public prediction properties immediately.
121 void refreshPredictions()
122 {
123 updatePredictionOutputs();
124 }
125
126 /// Seed the live photometry inputs and recalculate the live guide-star magnitude.
127 void setPhotometry( float counts, int npix )
128 {
129 m_counts = counts;
130 m_npix = npix;
131 calcMag();
132 }
133
134 /// Return the current live guide-star magnitude.
135 float liveMag() const
136 {
137 return m_mag;
138 }
139
140 /// Return the estimated guide-star magnitude.
141 float estimatedMag() const
142 {
143 return m_magEstimated;
144 }
145
146 /// Return the current live seeing.
147 float liveSeeing() const
148 {
149 return m_seeing;
150 }
151
152 /// Return the estimated seeing.
153 float estimatedSeeing() const
154 {
155 return m_seeingEstimated;
156 }
157
158 /// Return the selected wind speed value in m/s.
159 float windSpeed() const
160 {
161 return m_windSpeed;
162 }
163
164 /// Return the selected wind-speed switch element name.
165 std::string windSpeedSelection() const
166 {
167 return windSpeedSelectionName( m_windSpeed );
168 }
169
170 /// Return whether estimate overrides are currently enabled.
171 bool useEstimates() const
172 {
173 return m_useEstimates;
174 }
175
176 /// Return the selected star magnitude used by the AO model.
177 float selectedMagDirect() const
178 {
179 return selectedStarMag();
180 }
181
182 /// Return the selected seeing used by the AO model.
183 float selectedSeeingDirect() const
184 {
185 return selectedSeeing();
186 }
187
188 /// Return the published star-magnitude property.
189 const pcf::IndiProperty &starMagProperty() const
190 {
191 return m_indiP_mag;
192 }
193
194 /// Return the published seeing property.
195 const pcf::IndiProperty &seeingProperty() const
196 {
197 return m_indiP_seeing_magaox;
198 }
199
200 /// Return the published wind-speed property.
201 const pcf::IndiProperty &windSpeedProperty() const
202 {
203 return m_indiP_windSpeed;
204 }
205
206 /// Return the published `use_estimates` toggle property.
207 const pcf::IndiProperty &useEstimatesProperty() const
208 {
209 return m_indiP_useEstimates;
210 }
211
212 /// Return the published optimum-loop-speed property.
213 const pcf::IndiProperty &optimumLoopSpeedProperty() const
214 {
215 return m_indiP_loopSpeedOptimum;
216 }
217
218 /// Return the published current-prediction Strehl.
219 float predictedStrehl() const
220 {
221 return m_indiP_strehl["pyramid"].get<float>();
222 }
223
224 /// Return the published optimum-loop-speed FPS.
225 float optimumFPS() const
226 {
227 return m_indiP_loopSpeedOptimum["fps"].get<float>();
228 }
229
230 /// Return the published optimum-loop-speed Strehl.
231 float optimumStrehl() const
232 {
233 return m_indiP_loopSpeedOptimum["strehl"].get<float>();
234 }
235
236 /// Evaluate the configured AO model at one FPS sample for test-side brute-force comparisons.
237 float predictedStrehlAtFps( float fps, bool optimizeTau )
238 {
239 predictionInputs inputs = snapshotPredictionInputs();
240 configureAoSystem( m_aosysScan, inputs, fps, optimizeTau );
241 return m_aosysScan.strehl();
242 }
243};
244/// \endcond
245
246/// Build a number-property update payload for the local app.
247pcf::IndiProperty makeLocalNumberProperty( const std::string &device, const std::string &name )
248{
249 pcf::IndiProperty ip( pcf::IndiProperty::Number );
250 ip.setDevice( device );
251 ip.setName( name );
252 return ip;
253}
254
255/// Build a switch-property update payload for the local app.
256pcf::IndiProperty makeLocalSwitchProperty( const std::string &device, const std::string &name )
257{
258 pcf::IndiProperty ip( pcf::IndiProperty::Switch );
259 ip.setDevice( device );
260 ip.setName( name );
261 return ip;
262}
263
264/// Build a set-property payload from another device.
265pcf::IndiProperty makeRemoteNumberProperty( const std::string &device, const std::string &name )
266{
267 pcf::IndiProperty ip( pcf::IndiProperty::Number );
268 ip.setDevice( device );
269 ip.setName( name );
270 return ip;
271}
272
273} // namespace
274
275/// Verify the `strehlEstimator` callbacks reject mismatched property identities.
276/**
277 * \ingroup strehlEstimator_unit_test
278 */
279TEST_CASE( "strehlEstimator callbacks validate the property identity", "[strehlEstimator][indi]" )
280{
281 // clang-format off
282 #ifdef STREHLESTIMATOR_TEST_DOXYGEN_REF
283 XWCTEST_DOXYGEN_REF( strehlEstimator::setCallBack_m_indiP_fps( std::declval<const pcf::IndiProperty &>() ) );
284 XWCTEST_DOXYGEN_REF( strehlEstimator::setCallBack_m_indiP_emg( std::declval<const pcf::IndiProperty &>() ) );
285 XWCTEST_DOXYGEN_REF( strehlEstimator::setCallBack_m_indiP_stage( std::declval<const pcf::IndiProperty &>() ) );
286 XWCTEST_DOXYGEN_REF( strehlEstimator::setCallBack_m_indiP_tcsi_seeing( std::declval<const pcf::IndiProperty &>() ) );
287 XWCTEST_DOXYGEN_REF( strehlEstimator::setCallBack_m_indiP_tcsi_telpos( std::declval<const pcf::IndiProperty &>() ) );
288 XWCTEST_DOXYGEN_REF( strehlEstimator::newCallBack_m_indiP_mag( std::declval<const pcf::IndiProperty &>() ) );
289 XWCTEST_DOXYGEN_REF( strehlEstimator::newCallBack_m_indiP_seeing_magaox( std::declval<const pcf::IndiProperty &>() ) );
290 XWCTEST_DOXYGEN_REF( strehlEstimator::newCallBack_m_indiP_windSpeed( std::declval<const pcf::IndiProperty &>() ) );
291 XWCTEST_DOXYGEN_REF( strehlEstimator::newCallBack_m_indiP_useEstimates( std::declval<const pcf::IndiProperty &>() ) );
292 #endif
293 // clang-format on
294
295 XWCTEST_INDI_SET_CALLBACK( strehlEstimator, m_indiP_fps, camwfs, fps );
296 XWCTEST_INDI_SET_CALLBACK( strehlEstimator, m_indiP_emg, camwfs, emgain );
297 XWCTEST_INDI_SET_CALLBACK( strehlEstimator, m_indiP_stage, stagebs, presetName );
298 XWCTEST_INDI_SET_CALLBACK( strehlEstimator, m_indiP_tcsi_seeing, tcsi, seeing );
299 XWCTEST_INDI_SET_CALLBACK( strehlEstimator, m_indiP_tcsi_telpos, tcsi, telpos );
300
301 XWCTEST_INDI_ARBNEW_CALLBACK( strehlEstimator, newCallBack_m_indiP_mag, star_mag );
302 XWCTEST_INDI_ARBNEW_CALLBACK( strehlEstimator, newCallBack_m_indiP_seeing_magaox, seeing );
303 XWCTEST_INDI_ARBNEW_CALLBACK( strehlEstimator, newCallBack_m_indiP_windSpeed, wind_speed );
304 XWCTEST_INDI_ARBNEW_CALLBACK( strehlEstimator, newCallBack_m_indiP_useEstimates, use_estimates );
305}
306
307/// Verify published-property initialization creates the planning-input and optimum-speed properties with the expected
308/// elements.
309/**
310 * \ingroup strehlEstimator_unit_test
311 */
312TEST_CASE( "strehlEstimator startup publishes planning and optimum-speed properties", "[strehlEstimator]" )
313{
314 strehlEstimator_test app( "strehlEstimator_test" );
315
316 REQUIRE( app.initializePublishedProperties() == 0 );
317
318 REQUIRE( app.starMagProperty().find( "current" ) );
319 REQUIRE( app.starMagProperty().find( "estimated" ) );
320
321 REQUIRE( app.seeingProperty().find( "current" ) );
322 REQUIRE( app.seeingProperty().find( "estimated" ) );
323
324 REQUIRE( app.windSpeedProperty().find( "slow" ) );
325 REQUIRE( app.windSpeedProperty().find( "normal" ) );
326 REQUIRE( app.windSpeedProperty().find( "fast" ) );
327 REQUIRE( app.windSpeedProperty().find( "very-fast" ) );
328 REQUIRE( app.windSpeedProperty()[app.windSpeedSelection()].getSwitchState() == pcf::IndiElement::On );
329
330 REQUIRE( app.useEstimatesProperty().find( "toggle" ) );
331 REQUIRE( app.useEstimatesProperty()["toggle"].getSwitchState() == pcf::IndiElement::Off );
332
333 REQUIRE( app.optimumLoopSpeedProperty().find( "fps" ) );
334 REQUIRE( app.optimumLoopSpeedProperty().find( "strehl" ) );
335 REQUIRE( app.optimumLoopSpeedProperty().find( "wfe_total" ) );
336 REQUIRE( app.optimumLoopSpeedProperty().find( "wfe_measurement" ) );
337 REQUIRE( app.optimumLoopSpeedProperty().find( "wfe_time_delay" ) );
338 REQUIRE( app.optimumLoopSpeedProperty().find( "wfe_fitting" ) );
339}
340
341/// Verify local star-magnitude and seeing writes only honor `estimated`, while the wind-speed selector updates the
342/// planning wind state.
343/**
344 * \ingroup strehlEstimator_unit_test
345 */
346TEST_CASE( "strehlEstimator planning inputs honor estimated writes and wind-speed selections", "[strehlEstimator]" )
347{
348 // clang-format off
349 #ifdef STREHLESTIMATOR_TEST_DOXYGEN_REF
351 XWCTEST_DOXYGEN_REF( strehlEstimator::newCallBack_m_indiP_mag( std::declval<const pcf::IndiProperty &>() ) );
352 XWCTEST_DOXYGEN_REF( strehlEstimator::newCallBack_m_indiP_seeing_magaox( std::declval<const pcf::IndiProperty &>() ) );
353 XWCTEST_DOXYGEN_REF( strehlEstimator::newCallBack_m_indiP_windSpeed( std::declval<const pcf::IndiProperty &>() ) );
354 XWCTEST_DOXYGEN_REF( strehlEstimator::setCallBack_m_indiP_tcsi_seeing( std::declval<const pcf::IndiProperty &>() ) );
355 #endif
356 // clang-format on
357
358 strehlEstimator_test app( "right" );
359
360 REQUIRE( app.initializePublishedProperties() == 0 );
361
362 app.setPhotometry( 25000.0f, 144 );
363 REQUIRE( app.estimatedMag() == Approx( app.liveMag() ) );
364
365 SECTION( "star magnitude current writes are ignored" )
366 {
367 pcf::IndiProperty ip = makeLocalNumberProperty( "right", "star_mag" );
368 ip.add( pcf::IndiElement( "current" ) );
369 ip["current"].set( app.liveMag() + 2.0f );
370
371 REQUIRE( app.newCallBack_m_indiP_mag( ip ) == 0 );
372 REQUIRE( app.estimatedMag() == Approx( app.liveMag() ) );
373 }
374
375 SECTION( "star magnitude estimated writes are accepted" )
376 {
377 pcf::IndiProperty ip = makeLocalNumberProperty( "right", "star_mag" );
378 ip.add( pcf::IndiElement( "estimated" ) );
379 ip["estimated"].set( app.liveMag() + 2.0f );
380
381 REQUIRE( app.newCallBack_m_indiP_mag( ip ) == 0 );
382 REQUIRE( app.estimatedMag() == Approx( app.liveMag() + 2.0f ) );
383 }
384
385 SECTION( "live TCS seeing updates the current value" )
386 {
387 pcf::IndiProperty ip = makeRemoteNumberProperty( "tcsi", "seeing" );
388 ip.add( pcf::IndiElement( "dimm_fwhm_corr" ) );
389 ip["dimm_fwhm_corr"].set( 0.83f );
390
391 REQUIRE( app.setCallBack_m_indiP_tcsi_seeing( ip ) == 0 );
392 REQUIRE( app.liveSeeing() == Approx( 0.83f ) );
393 REQUIRE( app.estimatedSeeing() == Approx( 0.83f ) );
394 }
395
396 SECTION( "local seeing current writes are ignored and estimated writes are accepted" )
397 {
398 pcf::IndiProperty live = makeRemoteNumberProperty( "tcsi", "seeing" );
399 live.add( pcf::IndiElement( "dimm_fwhm_corr" ) );
400 live["dimm_fwhm_corr"].set( 0.76f );
401 REQUIRE( app.setCallBack_m_indiP_tcsi_seeing( live ) == 0 );
402
403 pcf::IndiProperty currentWrite = makeLocalNumberProperty( "right", "seeing" );
404 currentWrite.add( pcf::IndiElement( "current" ) );
405 currentWrite["current"].set( 0.40f );
406 REQUIRE( app.newCallBack_m_indiP_seeing_magaox( currentWrite ) == 0 );
407 REQUIRE( app.estimatedSeeing() == Approx( 0.76f ) );
408
409 pcf::IndiProperty estimatedWrite = makeLocalNumberProperty( "right", "seeing" );
410 estimatedWrite.add( pcf::IndiElement( "estimated" ) );
411 estimatedWrite["estimated"].set( 1.15f );
412 REQUIRE( app.newCallBack_m_indiP_seeing_magaox( estimatedWrite ) == 0 );
413 REQUIRE( app.estimatedSeeing() == Approx( 1.15f ) );
414 }
415
416 SECTION( "wind-speed selection writes update the planning wind speed" )
417 {
418 pcf::IndiProperty normal = makeLocalSwitchProperty( "right", "wind_speed" );
419 normal.add( pcf::IndiElement( "normal" ) );
420 normal["normal"].setSwitchState( pcf::IndiElement::On );
421 REQUIRE( app.newCallBack_m_indiP_windSpeed( normal ) == 0 );
422 REQUIRE( app.windSpeed() == Approx( 18.7f ) );
423 REQUIRE( app.windSpeedProperty()["normal"].getSwitchState() == pcf::IndiElement::On );
424
425 pcf::IndiProperty fast = makeLocalSwitchProperty( "right", "wind_speed" );
426 fast.add( pcf::IndiElement( "fast" ) );
427 fast["fast"].setSwitchState( pcf::IndiElement::On );
428 REQUIRE( app.newCallBack_m_indiP_windSpeed( fast ) == 0 );
429 REQUIRE( app.windSpeed() == Approx( 23.4f ) );
430 REQUIRE( app.windSpeedProperty()["fast"].getSwitchState() == pcf::IndiElement::On );
431
432 pcf::IndiProperty veryFast = makeLocalSwitchProperty( "right", "wind_speed" );
433 veryFast.add( pcf::IndiElement( "very-fast" ) );
434 veryFast["very-fast"].setSwitchState( pcf::IndiElement::On );
435 REQUIRE( app.newCallBack_m_indiP_windSpeed( veryFast ) == 0 );
436 REQUIRE( app.windSpeed() == Approx( 30.0f ) );
437 REQUIRE( app.windSpeedProperty()["very-fast"].getSwitchState() == pcf::IndiElement::On );
438 }
439}
440
441/// Verify estimate selection changes the published predictions and the optimum-loop-speed summary matches the fixed FPS
442/// grid.
443/**
444 * \ingroup strehlEstimator_unit_test
445 */
446TEST_CASE( "strehlEstimator freezes auto-tracked estimates while use_estimates is enabled", "[strehlEstimator]" )
447{
448 // clang-format off
449 #ifdef STREHLESTIMATOR_TEST_DOXYGEN_REF
451 XWCTEST_DOXYGEN_REF( strehlEstimator::setCallBack_m_indiP_tcsi_seeing( std::declval<const pcf::IndiProperty &>() ) );
452 XWCTEST_DOXYGEN_REF( strehlEstimator::newCallBack_m_indiP_useEstimates( std::declval<const pcf::IndiProperty &>() ) );
453 #endif
454 // clang-format on
455
456 strehlEstimator_test app( "right" );
457
458 REQUIRE( app.initializePublishedProperties() == 0 );
459
460 app.setPhotometry( 30000.0f, 196 );
461
462 pcf::IndiProperty initialSeeing = makeRemoteNumberProperty( "tcsi", "seeing" );
463 initialSeeing.add( pcf::IndiElement( "dimm_fwhm_corr" ) );
464 initialSeeing["dimm_fwhm_corr"].set( 0.55f );
465 REQUIRE( app.setCallBack_m_indiP_tcsi_seeing( initialSeeing ) == 0 );
466
467 const float frozenMag = app.estimatedMag();
468 const float frozenSeeing = app.estimatedSeeing();
469
470 pcf::IndiProperty useEstimates = makeLocalSwitchProperty( "right", "use_estimates" );
471 useEstimates.add( pcf::IndiElement( "toggle" ) );
472 useEstimates["toggle"].setSwitchState( pcf::IndiElement::On );
473 REQUIRE( app.newCallBack_m_indiP_useEstimates( useEstimates ) == 0 );
474
475 const float frozenPredictedStrehl = app.predictedStrehl();
476 const float frozenOptimumFPS = app.optimumFPS();
477
478 app.setPhotometry( 12000.0f, 196 );
479
480 pcf::IndiProperty updatedSeeing = makeRemoteNumberProperty( "tcsi", "seeing" );
481 updatedSeeing.add( pcf::IndiElement( "dimm_fwhm_corr" ) );
482 updatedSeeing["dimm_fwhm_corr"].set( 0.92f );
483 REQUIRE( app.setCallBack_m_indiP_tcsi_seeing( updatedSeeing ) == 0 );
484
485 REQUIRE( app.useEstimates() == true );
486 REQUIRE( app.estimatedMag() == Approx( frozenMag ) );
487 REQUIRE( app.estimatedSeeing() == Approx( frozenSeeing ) );
488 REQUIRE( app.selectedMagDirect() == Approx( frozenMag ) );
489 REQUIRE( app.selectedSeeingDirect() == Approx( frozenSeeing ) );
490 REQUIRE( app.predictedStrehl() == Approx( frozenPredictedStrehl ) );
491 REQUIRE( app.optimumFPS() == Approx( frozenOptimumFPS ) );
492 REQUIRE( app.liveMag() != Approx( frozenMag ) );
493 REQUIRE( app.liveSeeing() != Approx( frozenSeeing ) );
494}
495
496/// Verify estimate selection changes the published predictions and the optimum-loop-speed summary matches the fixed FPS
497/// grid.
498/**
499 * \ingroup strehlEstimator_unit_test
500 */
501TEST_CASE( "strehlEstimator uses estimates when requested and scans the fixed loop-speed grid", "[strehlEstimator]" )
502{
503 // clang-format off
504 #ifdef STREHLESTIMATOR_TEST_DOXYGEN_REF
507 XWCTEST_DOXYGEN_REF( strehlEstimator::newCallBack_m_indiP_useEstimates( std::declval<const pcf::IndiProperty &>() ) );
508 #endif
509 // clang-format on
510
511 strehlEstimator_test app( "right" );
512
513 REQUIRE( app.initializePublishedProperties() == 0 );
514
515 app.setPhotometry( 30000.0f, 196 );
516
517 pcf::IndiProperty liveSeeing = makeRemoteNumberProperty( "tcsi", "seeing" );
518 liveSeeing.add( pcf::IndiElement( "dimm_fwhm_corr" ) );
519 liveSeeing["dimm_fwhm_corr"].set( 0.55f );
520 REQUIRE( app.setCallBack_m_indiP_tcsi_seeing( liveSeeing ) == 0 );
521
522 pcf::IndiProperty liveElevation = makeRemoteNumberProperty( "tcsi", "telpos" );
523 liveElevation.add( pcf::IndiElement( "el" ) );
524 liveElevation["el"].set( 72.0f );
525 REQUIRE( app.setCallBack_m_indiP_tcsi_telpos( liveElevation ) == 0 );
526
527 const float liveSelectedMag = app.selectedMagDirect();
528 const float liveSelectedSeeing = app.selectedSeeingDirect();
529 const float livePredictedStrehl = app.predictedStrehl();
530
531 pcf::IndiProperty magEstimate = makeLocalNumberProperty( "right", "star_mag" );
532 magEstimate.add( pcf::IndiElement( "estimated" ) );
533 magEstimate["estimated"].set( liveSelectedMag + 4.0f );
534 REQUIRE( app.newCallBack_m_indiP_mag( magEstimate ) == 0 );
535
536 pcf::IndiProperty seeingEstimate = makeLocalNumberProperty( "right", "seeing" );
537 seeingEstimate.add( pcf::IndiElement( "estimated" ) );
538 seeingEstimate["estimated"].set( 1.10f );
539 REQUIRE( app.newCallBack_m_indiP_seeing_magaox( seeingEstimate ) == 0 );
540
541 pcf::IndiProperty windSelection = makeLocalSwitchProperty( "right", "wind_speed" );
542 windSelection.add( pcf::IndiElement( "normal" ) );
543 windSelection["normal"].setSwitchState( pcf::IndiElement::On );
544 REQUIRE( app.newCallBack_m_indiP_windSpeed( windSelection ) == 0 );
545
546 pcf::IndiProperty useEstimates = makeLocalSwitchProperty( "right", "use_estimates" );
547 useEstimates.add( pcf::IndiElement( "toggle" ) );
548 useEstimates["toggle"].setSwitchState( pcf::IndiElement::On );
549 REQUIRE( app.newCallBack_m_indiP_useEstimates( useEstimates ) == 0 );
550
551 REQUIRE( app.useEstimates() == true );
552 REQUIRE( app.selectedMagDirect() == Approx( app.estimatedMag() ) );
553 REQUIRE( app.selectedSeeingDirect() == Approx( app.estimatedSeeing() ) );
554 REQUIRE( app.selectedMagDirect() != Approx( liveSelectedMag ) );
555 REQUIRE( app.selectedSeeingDirect() != Approx( liveSelectedSeeing ) );
556 REQUIRE( app.predictedStrehl() != Approx( livePredictedStrehl ) );
557
558 float bruteForceBestFps = 0.0f;
559 float bruteForceBestStrehl = -1.0f;
560 for( int fps = 100; fps <= 3000; fps += 100 )
561 {
562 float strehl = app.predictedStrehlAtFps( static_cast<float>( fps ), false );
563 if( bruteForceBestFps == 0.0f || strehl > bruteForceBestStrehl )
564 {
565 bruteForceBestFps = static_cast<float>( fps );
566 bruteForceBestStrehl = strehl;
567 }
568 }
569
570 REQUIRE( app.optimumFPS() >= 100.0f );
571 REQUIRE( app.optimumFPS() <= 3000.0f );
572 REQUIRE( std::fmod( app.optimumFPS(), 100.0f ) == Approx( 0.0f ).margin( 1.0e-4 ) );
573 REQUIRE( app.optimumFPS() == Approx( bruteForceBestFps ) );
574 REQUIRE( app.optimumStrehl() == Approx( bruteForceBestStrehl ) );
575}
576
577} // namespace strehlEstimatorTest
578
579} // namespace libXWCTest
Predicts Strehl and WFE from live WFS telemetry and optional planning overrides.
void updateOptimumLoopSpeed(const predictionInputs &inputs)
Refresh the fixed-grid optimum-loop-speed summary property.
void updatePredictionOutputs()
Refresh the predicted Strehl, WFE, and optimum-loop-speed properties.
void calcMag()
Recalculate the live guide-star magnitude from the current WFS counts.
TEST_CASE("strehlEstimator callbacks validate the property identity", "[strehlEstimator][indi]")
Verify the strehlEstimator callbacks reject mismatched property identities.
#define XWCTEST_INDI_SET_CALLBACK(testclass, varname, device, propname)
Catch-2 tests for whether a SET callback properly validates the input property properly.
#define XWCTEST_INDI_ARBNEW_CALLBACK(testclass, callback, propname)
Catch-2 tests for whether an arbitrary callback properly validates the input property properly.
#define XWCTEST_DOXYGEN_REF(fxn)
This inserts an unused call to a function signature to make doxygen make the link.
Definition testXWC.hpp:18
Namespace for all libXWC tests.
#define XWCTEST_SETUP_INDI_ARB_PROP(varname, device, propname)
#define XWCTEST_SETUP_INDI_ARB_NEW_PROP(varname, propname)