29namespace strehlEstimatorTest
41 strehlEstimator_test(
const std::string &device )
43 m_configName = device;
60 int initializePublishedProperties()
62 if( createCurrentEstimatedProperty( m_indiP_mag,
"star_mag",
"Star Magnitude",
"Error Budget" ) < 0 )
67 if( createCurrentEstimatedProperty( m_indiP_seeing_magaox,
"seeing",
"Seeing",
"Error Budget" ) < 0 )
72 if( createStandardIndiSelectionSw( m_indiP_windSpeed,
74 windSpeedSelectionElements(),
75 windSpeedSelectionLabels(),
77 "Error Budget" ) < 0 )
82 if( createStandardIndiToggleSw( m_indiP_useEstimates,
"use_estimates",
"Use Estimates",
"Error Budget" ) < 0 )
87 if( createROIndiNumber( m_indiP_strehl,
"strehl_optimal",
"Strehl",
"Error Budget" ) < 0 )
91 m_indiP_strehl.add( pcf::IndiElement(
"pyramid", 0.0f ) );
93 if( createROIndiNumber( m_indiP_wfe,
"wfe_predicted",
"WFE",
"Error Budget" ) < 0 )
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 ) );
102 if( createROIndiNumber( m_indiP_loopSpeedOptimum,
"loop_speed_optimum",
"Optimum Loop Speed",
"Error Budget" ) <
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 ) );
114 updatePlanningProperties();
115 updatePredictionOutputs();
121 void refreshPredictions()
123 updatePredictionOutputs();
127 void setPhotometry(
float counts,
int npix )
135 float liveMag()
const
141 float estimatedMag()
const
143 return m_magEstimated;
147 float liveSeeing()
const
153 float estimatedSeeing()
const
155 return m_seeingEstimated;
159 float windSpeed()
const
165 std::string windSpeedSelection()
const
167 return windSpeedSelectionName( m_windSpeed );
171 bool useEstimates()
const
173 return m_useEstimates;
177 float selectedMagDirect()
const
179 return selectedStarMag();
183 float selectedSeeingDirect()
const
185 return selectedSeeing();
189 const pcf::IndiProperty &starMagProperty()
const
195 const pcf::IndiProperty &seeingProperty()
const
197 return m_indiP_seeing_magaox;
201 const pcf::IndiProperty &windSpeedProperty()
const
203 return m_indiP_windSpeed;
207 const pcf::IndiProperty &useEstimatesProperty()
const
209 return m_indiP_useEstimates;
213 const pcf::IndiProperty &optimumLoopSpeedProperty()
const
215 return m_indiP_loopSpeedOptimum;
219 float predictedStrehl()
const
221 return m_indiP_strehl[
"pyramid"].get<
float>();
225 float optimumFPS()
const
227 return m_indiP_loopSpeedOptimum[
"fps"].get<
float>();
231 float optimumStrehl()
const
233 return m_indiP_loopSpeedOptimum[
"strehl"].get<
float>();
237 float predictedStrehlAtFps(
float fps,
bool optimizeTau )
239 predictionInputs inputs = snapshotPredictionInputs();
240 configureAoSystem( m_aosysScan, inputs, fps, optimizeTau );
241 return m_aosysScan.strehl();
247pcf::IndiProperty makeLocalNumberProperty(
const std::string &device,
const std::string &name )
249 pcf::IndiProperty ip( pcf::IndiProperty::Number );
250 ip.setDevice( device );
256pcf::IndiProperty makeLocalSwitchProperty(
const std::string &device,
const std::string &name )
258 pcf::IndiProperty ip( pcf::IndiProperty::Switch );
259 ip.setDevice( device );
265pcf::IndiProperty makeRemoteNumberProperty(
const std::string &device,
const std::string &name )
267 pcf::IndiProperty ip( pcf::IndiProperty::Number );
268 ip.setDevice( device );
279TEST_CASE(
"strehlEstimator callbacks validate the property identity",
"[strehlEstimator][indi]" )
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 &>() ) );
312TEST_CASE(
"strehlEstimator startup publishes planning and optimum-speed properties",
"[strehlEstimator]" )
314 strehlEstimator_test app(
"strehlEstimator_test" );
316 REQUIRE( app.initializePublishedProperties() == 0 );
318 REQUIRE( app.starMagProperty().find(
"current" ) );
319 REQUIRE( app.starMagProperty().find(
"estimated" ) );
321 REQUIRE( app.seeingProperty().find(
"current" ) );
322 REQUIRE( app.seeingProperty().find(
"estimated" ) );
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 );
330 REQUIRE( app.useEstimatesProperty().find(
"toggle" ) );
331 REQUIRE( app.useEstimatesProperty()[
"toggle"].getSwitchState() == pcf::IndiElement::Off );
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" ) );
346TEST_CASE(
"strehlEstimator planning inputs honor estimated writes and wind-speed selections",
"[strehlEstimator]" )
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 &>() ) );
358 strehlEstimator_test app(
"right" );
360 REQUIRE( app.initializePublishedProperties() == 0 );
362 app.setPhotometry( 25000.0f, 144 );
363 REQUIRE( app.estimatedMag() == Approx( app.liveMag() ) );
365 SECTION(
"star magnitude current writes are ignored" )
367 pcf::IndiProperty ip = makeLocalNumberProperty(
"right",
"star_mag" );
368 ip.add( pcf::IndiElement(
"current" ) );
369 ip[
"current"].set( app.liveMag() + 2.0f );
371 REQUIRE( app.newCallBack_m_indiP_mag( ip ) == 0 );
372 REQUIRE( app.estimatedMag() == Approx( app.liveMag() ) );
375 SECTION(
"star magnitude estimated writes are accepted" )
377 pcf::IndiProperty ip = makeLocalNumberProperty(
"right",
"star_mag" );
378 ip.add( pcf::IndiElement(
"estimated" ) );
379 ip[
"estimated"].set( app.liveMag() + 2.0f );
381 REQUIRE( app.newCallBack_m_indiP_mag( ip ) == 0 );
382 REQUIRE( app.estimatedMag() == Approx( app.liveMag() + 2.0f ) );
385 SECTION(
"live TCS seeing updates the current value" )
387 pcf::IndiProperty ip = makeRemoteNumberProperty(
"tcsi",
"seeing" );
388 ip.add( pcf::IndiElement(
"dimm_fwhm_corr" ) );
389 ip[
"dimm_fwhm_corr"].set( 0.83f );
391 REQUIRE( app.setCallBack_m_indiP_tcsi_seeing( ip ) == 0 );
392 REQUIRE( app.liveSeeing() == Approx( 0.83f ) );
393 REQUIRE( app.estimatedSeeing() == Approx( 0.83f ) );
396 SECTION(
"local seeing current writes are ignored and estimated writes are accepted" )
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 );
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 ) );
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 ) );
416 SECTION(
"wind-speed selection writes update the planning wind speed" )
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 );
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 );
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 );
446TEST_CASE(
"strehlEstimator freezes auto-tracked estimates while use_estimates is enabled",
"[strehlEstimator]" )
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 &>() ) );
456 strehlEstimator_test app(
"right" );
458 REQUIRE( app.initializePublishedProperties() == 0 );
460 app.setPhotometry( 30000.0f, 196 );
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 );
467 const float frozenMag = app.estimatedMag();
468 const float frozenSeeing = app.estimatedSeeing();
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 );
475 const float frozenPredictedStrehl = app.predictedStrehl();
476 const float frozenOptimumFPS = app.optimumFPS();
478 app.setPhotometry( 12000.0f, 196 );
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 );
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 ) );
501TEST_CASE(
"strehlEstimator uses estimates when requested and scans the fixed loop-speed grid",
"[strehlEstimator]" )
504 #ifdef STREHLESTIMATOR_TEST_DOXYGEN_REF
507 XWCTEST_DOXYGEN_REF( strehlEstimator::newCallBack_m_indiP_useEstimates( std::declval<const pcf::IndiProperty &>() ) );
511 strehlEstimator_test app(
"right" );
513 REQUIRE( app.initializePublishedProperties() == 0 );
515 app.setPhotometry( 30000.0f, 196 );
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 );
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 );
527 const float liveSelectedMag = app.selectedMagDirect();
528 const float liveSelectedSeeing = app.selectedSeeingDirect();
529 const float livePredictedStrehl = app.predictedStrehl();
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 );
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 );
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 );
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 );
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 ) );
558 float bruteForceBestFps = 0.0f;
559 float bruteForceBestStrehl = -1.0f;
560 for(
int fps = 100; fps <= 3000; fps += 100 )
562 float strehl = app.predictedStrehlAtFps(
static_cast<float>( fps ),
false );
563 if( bruteForceBestFps == 0.0f || strehl > bruteForceBestStrehl )
565 bruteForceBestFps =
static_cast<float>( fps );
566 bruteForceBestStrehl = strehl;
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 ) );