5#include <gtest/gtest.h>
25#include "../../drivers/telescope/scopesim_helper.h"
27#include <libnova/julian_day.h>
28#include <libnova/sidereal_time.h>
60 if (residuals.empty())
return s;
64 for (
double r : residuals)
70 s.
rms = std::sqrt(sumSq / residuals.size());
71 s.
mean = sum / residuals.size();
73 std::sort(residuals.begin(), residuals.end());
74 size_t idx95 =
static_cast<size_t>(0.95 * (residuals.size() - 1));
75 s.
p95 = residuals[idx95];
85static void getSkyPoint(
double rawRA,
double rawDec,
double minDec,
double maxDec,
double &
ra,
double &
dec)
90 dec =
RAD_TO_DEG(std::asin(sinMin + rawDec * (sinMax - sinMin)));
97 if (sigmaDeg <= 0.0)
return;
99 std::normal_distribution<double> dist(0.0,
DEG_TO_RAD(sigmaDeg));
102 double ax = std::abs(tdv.
x), ay = std::abs(tdv.
y), az = std::abs(tdv.
z);
104 if (ax <= ay && ax <= az)
105 e1 = {0, -tdv.
z, tdv.
y};
107 e1 = {tdv.
z, 0, -tdv.
x};
109 e1 = {-tdv.
y, tdv.
x, 0};
111 double len1 = std::sqrt(e1.
x * e1.
x + e1.
y * e1.
y + e1.
z * e1.
z);
112 e1.
x /= len1; e1.
y /= len1; e1.
z /= len1;
115 e2.
x = tdv.
y * e1.
z - tdv.
z * e1.
y;
116 e2.
y = tdv.
z * e1.
x - tdv.
x * e1.
z;
117 e2.
z = tdv.
x * e1.
y - tdv.
y * e1.
x;
119 double d1 = dist(rng), d2 = dist(rng);
120 tdv.
x += d1 * e1.
x + d2 * e2.
x;
121 tdv.
y += d1 * e1.
y + d2 * e2.
y;
122 tdv.
z += d1 * e1.
z + d2 * e2.
z;
124 double len = std::sqrt(tdv.
x * tdv.
x + tdv.
y * tdv.
y + tdv.
z * tdv.
z);
125 tdv.
x /= len; tdv.
y /= len; tdv.
z /= len;
134 .
ih = 35.0, .id = -20.0, .ch = 1.5,
135 .np = 0.5, .ma = 2.0, .me = 2.0,
153static constexpr double kEQ_MinDec = -60.0;
154static constexpr double kEQ_MaxDec = 80.0;
155static constexpr double kAZ_MinEl = 20.0;
156static constexpr double kAZ_MaxEl = 85.0;
162static constexpr double kRoundTripTolDeg = 0.05;
163static constexpr double kRoundTripTolHours = kRoundTripTolDeg / 15.0;
173 static constexpr double fixedJD = 2461113.81667;
199 double ra,
double dec,
double lst_hrs,
double jd,
211 Angle primary, secondary;
234 double minDecOverride = NAN,
double maxDecOverride = NAN)
236 double minDec = std::isnan(minDecOverride) ? ((mountType ==
::Alignment::ALTAZ) ? kAZ_MinEl : kEQ_MinDec) : minDecOverride;
237 double maxDec = std::isnan(maxDecOverride) ? ((mountType ==
::Alignment::ALTAZ) ? kAZ_MaxEl : kEQ_MaxDec) : maxDecOverride;
250 ASSERT_NE(pSupport,
nullptr);
252 double gmst = ln_get_apparent_sidereal_time(
fixedJD);
256 for (
int i = 1; i <= numPoints; ++i)
260 getSkyPoint(raw.first, raw.second, minDec, maxDec,
ra,
dec);
269 double testRA = 12.0;
275 double outRA, outDec;
278 ASSERT_NEAR(testRA, outRA, kRoundTripTolHours);
279 ASSERT_NEAR(testDec, outDec, kRoundTripTolDeg);
289 int numAlign,
int numValidate,
290 double targetRMSArcsec,
293 double minDecOverride = NAN,
double maxDecOverride = NAN,
294 double *maxResidualOut =
nullptr)
296 double minDec = std::isnan(minDecOverride) ? ((mountType ==
::Alignment::ALTAZ) ? kAZ_MinEl : kEQ_MinDec) : minDecOverride;
297 double maxDec = std::isnan(maxDecOverride) ? ((mountType ==
::Alignment::ALTAZ) ? kAZ_MaxEl : kEQ_MaxDec) : maxDecOverride;
310 ASSERT_NE(pSupport,
nullptr);
312 double gmst = ln_get_apparent_sidereal_time(
fixedJD);
317 for (
int i = 1; i <= numAlign; ++i)
320 auto raw = alignSeq.
getRaw(i);
321 getSkyPoint(raw.first, raw.second, minDec, maxDec,
ra,
dec);
332 std::vector<double> residuals;
334 for (
int i = 1; i <= numValidate; ++i)
337 auto raw = validateSeq.
getRaw(i);
338 getSkyPoint(raw.first, raw.second, minDec, maxDec,
ra,
dec);
343 double outRA, outDec;
348 double residual = std::sqrt(dRA * dRA + dDec * dDec);
350 residuals.push_back(residual);
354 GTEST_LOG_(INFO) <<
" validate[" << i <<
"] ra=" <<
ra
356 <<
" dRA=" << dRA <<
"\" dDec=" << dDec
357 <<
"\" residual=" << residual <<
"\"";
362 GTEST_LOG_(INFO) <<
"[ STATS ] RMS: " << stats.
rms <<
"\" Target: " << targetRMSArcsec
363 <<
"\" P95: " << stats.
p95 <<
"\" Peak: " << stats.
max <<
"\"";
365 if (maxResidualOut) *maxResidualOut = stats.
max;
367 EXPECT_LT(stats.
rms, targetRMSArcsec)
368 <<
"Plugin failed to meet RMS quality target " << targetRMSArcsec <<
"\"";
371 EXPECT_LT(stats.
p95, targetRMSArcsec * 1.5)
372 <<
"Plugin failed to meet P95 quality target " << targetRMSArcsec * 1.5 <<
"\"";
389 int numAlign,
int numValidate,
390 double noiseSigmaDeg,
double targetRMSArcsec,
404 ASSERT_NE(pSupport,
nullptr);
406 double gmst = ln_get_apparent_sidereal_time(
fixedJD);
409 std::mt19937 rng(42);
416 for (
int i = 1; i <= numAlign; ++i)
419 auto raw = alignSeq.
getRaw(i);
420 getSkyPoint(raw.first, raw.second, minDec, maxDec,
ra,
dec);
431 ASSERT_TRUE(noisyPlugin.
Initialise(&noisyDb));
434 std::vector<double> residuals;
436 for (
int i = 1; i <= numValidate; ++i)
439 auto raw = validateSeq.
getRaw(i);
440 getSkyPoint(raw.first, raw.second, minDec, maxDec,
ra,
dec);
451 double dot = noisyV.
x * refV.
x + noisyV.
y * refV.
y + noisyV.
z * refV.
z;
452 dot = std::max(-1.0, std::min(1.0, dot));
455 residuals.push_back(residual);
459 GTEST_LOG_(INFO) <<
" noise_validate[" << i <<
"] ra=" <<
ra
461 <<
" residual=" << residual <<
"\"";
466 GTEST_LOG_(INFO) <<
"[ STATS ] RMS: " << stats.
rms <<
"\" Target: " << targetRMSArcsec
467 <<
"\" P95: " << stats.
p95 <<
"\" Peak: " << stats.
max <<
"\"";
469 EXPECT_LT(stats.
rms, targetRMSArcsec)
470 <<
"Plugin failed noise-stress RMS target " << targetRMSArcsec <<
"\"";
471 EXPECT_LT(stats.
p95, targetRMSArcsec * 1.5)
472 <<
"Plugin failed noise-stress P95 target " << targetRMSArcsec * 1.5 <<
"\"";
476 int numAlign,
int numValidate,
477 double shiftHours,
double targetRMSArcsec,
494 ASSERT_NE(pSupport,
nullptr);
497 double gmst = ln_get_apparent_sidereal_time(
fixedJD);
501 for (
int i = 1; i <= numAlign; ++i)
504 auto raw = alignSeq.
getRaw(i);
505 getSkyPoint(raw.first, raw.second, minDec, maxDec,
ra,
dec);
515 double valJD =
fixedJD + shiftHours / 24.0;
518 std::vector<double> residuals;
520 for (
int i = 1; i <= numValidate; ++i)
523 auto raw = validateSeq.
getRaw(i);
524 getSkyPoint(raw.first, raw.second, minDec, maxDec,
ra,
dec);
529 double outRA, outDec;
534 double residual = std::sqrt(dRA * dRA + dDec * dDec);
536 residuals.push_back(residual);
540 GTEST_LOG_(INFO) <<
"[ TIME SHIFT " << shiftHours <<
"h ] RMS: " << stats.
rms <<
"\" Target: " << targetRMSArcsec
541 <<
"\" P95: " << stats.
p95 <<
"\" Peak: " << stats.
max <<
"\"";
543 EXPECT_LT(stats.
rms, targetRMSArcsec)
544 <<
"Plugin failed time-shift RMS quality target " << targetRMSArcsec <<
"\"";
561 double valRA,
double valDec,
572 ASSERT_NE(pSupport,
nullptr);
574 double gmst = ln_get_apparent_sidereal_time(
fixedJD);
578 for (
int i = 1; i <= numAlign; ++i)
582 getSkyPoint(raw.first, raw.second, kAZ_MinEl, kAZ_MaxEl,
ra,
dec);
599 pSupport->AltitudeAzimuthFromTelescopeDirectionVector(tdv, hor);
601 double azDiff = std::fmod(hor.
azimuth - encAz.
Degrees() + 540.0, 360.0) - 180.0;
602 azDiff = std::abs(azDiff);
605 GTEST_LOG_(INFO) <<
" encAz=" << encAz.
Degrees() <<
" pluginAz=" << hor.
azimuth
606 <<
" azDiff=" << azDiff * 3600.0 <<
"\"";
607 GTEST_LOG_(INFO) <<
" encAlt=" << encAlt.
Degrees() <<
" pluginAlt=" << hor.
altitude
608 <<
" altDiff=" << altDiff * 3600.0 <<
"\"";
610 EXPECT_LT(azDiff, azTolDeg) <<
"Az differs by " << azDiff <<
"deg — likely Az convention bug";
611 EXPECT_LT(altDiff, azTolDeg) <<
"Alt differs by " << altDiff <<
"deg";
617 double valRA,
double valDec,
628 ASSERT_NE(pSupport,
nullptr);
630 double gmst = ln_get_apparent_sidereal_time(
fixedJD);
634 for (
int i = 1; i <= numAlign; ++i)
638 getSkyPoint(raw.first, raw.second, kEQ_MinDec, kEQ_MaxDec,
ra,
dec);
656 pSupport->EquatorialCoordinatesFromTelescopeDirectionVector(tdv, raCoords);
660 if (std::abs(rawRaDiff) > 6.0)
661 rawRaDiff =
rangeHA(rawRaDiff - 12.0);
665 double totalDiff = std::sqrt(raDiff * raDiff + decDiff * decDiff);
667 GTEST_LOG_(INFO) <<
" encRA=" << instRA <<
" pluginRA=" << raCoords.
rightascension
668 <<
" raDiff=" << raDiff <<
"\"";
669 GTEST_LOG_(INFO) <<
" encDec=" << encDec.
Degrees() <<
" pluginDec=" << raCoords.
declination
670 <<
" decDiff=" << decDiff <<
"\"";
672 EXPECT_LT(totalDiff, tolArcsec) <<
"Encoder position differs by " << totalDiff <<
"\" — likely ME/MA mapping bug";
678 double valRA,
double valDec,
689 ASSERT_NE(pSupport,
nullptr);
691 double gmst = ln_get_apparent_sidereal_time(
fixedJD);
695 for (
int i = 1; i <= numAlign; ++i)
699 getSkyPoint(raw.first, raw.second, kAZ_MinEl, kAZ_MaxEl,
ra,
dec);
716 pSupport->AltitudeAzimuthFromTelescopeDirectionVector(tdv, horCoords);
718 double azDiff = std::fmod(horCoords.
azimuth - encAz.
Degrees() + 540.0, 360.0) - 180.0;
720 double totalDiff = std::sqrt(std::pow(azDiff * std::cos(
DEG_TO_RAD(horCoords.
altitude)), 2) + altDiff * altDiff);
723 GTEST_LOG_(INFO) <<
" encAz=" << encAz.
Degrees() <<
" pluginAz=" << horCoords.
azimuth
724 <<
" azDiff=" << azDiff * 3600.0 <<
"\"";
725 GTEST_LOG_(INFO) <<
" encAlt=" << encAlt.
Degrees() <<
" pluginAlt=" << horCoords.
altitude
726 <<
" altDiff=" << altDiff * 3600.0 <<
"\"";
728 EXPECT_LT(totalDiff, tolArcsec) <<
"Encoder position differs by " << totalDiff <<
"\" — likely AN/AW mapping bug";
734 template <
typename T>
738 double targetRMSArcsec = 40.0)
740 plugin.SetApproximateMountAlignment(pole);
744 ASSERT_NE(pSupport,
nullptr);
749 double gmst = ln_get_apparent_sidereal_time(
fixedJD);
754 for (
int i = 1; i <= 3; ++i)
757 auto raw = alignSeq.
getRaw(i);
758 getSkyPoint(raw.first, raw.second, kEQ_MinDec, kEQ_MaxDec,
ra,
dec);
765 ASSERT_TRUE(plugin.Initialise(&alignDb));
769 std::vector<double> residuals;
771 for (
int i = 1; i <= 100; ++i)
774 auto raw = validateSeq.
getRaw(i);
775 getSkyPoint(raw.first, raw.second, kEQ_MinDec, kEQ_MaxDec,
ra,
dec);
778 ASSERT_TRUE(plugin.TransformCelestialToTelescopeJD(
ra,
dec,
fixedJD, resultV));
780 double outRA, outDec;
781 plugin.TransformTelescopeToCelestialJD(resultV, outRA, outDec,
fixedJD);
785 double residual = std::sqrt(dRA * dRA + dDec * dDec);
787 residuals.push_back(residual);
791 GTEST_LOG_(INFO) <<
" flip_validate[" << i <<
"] ra=" <<
ra
793 <<
" dRA=" << dRA <<
"\" dDec=" << dDec
794 <<
"\" residual=" << residual <<
"\"";
799 GTEST_LOG_(INFO) <<
"[ STATS ] RMS: " << stats.
rms <<
"\" Target: " << targetRMSArcsec
800 <<
"\" P95: " << stats.
p95 <<
"\" Peak: " << stats.
max <<
"\"";
802 EXPECT_LT(stats.
rms, targetRMSArcsec)
803 <<
"Plugin failed meridian flip RMS quality target " << targetRMSArcsec <<
"\"";
805 EXPECT_LT(stats.
p95, targetRMSArcsec * 1.5)
806 <<
"Plugin failed meridian flip P95 quality target " << targetRMSArcsec * 1.5 <<
"\"";
831 double gmst = ln_get_apparent_sidereal_time(
fixedJD);
837 for (
int i = 1; i <= n; i++)
841 getSkyPoint(raw.first, raw.second, minDec, maxDec,
ra,
dec);
858 const double tol = 5e-5;
859 for (
double testRA : {3.0, 8.0, 15.0, 21.0})
863 ? std::vector<double>{25.0, 40.0, 60.0, 80.0}
864 : std::vector<double>{-30.0, 0.0, 30.0, 60.0};
865 for (
double testDec : decs)
870 EXPECT_NEAR(hotV.
x, coldV.
x, tol) <<
"RA=" << testRA <<
" Dec/El=" << testDec;
871 EXPECT_NEAR(hotV.
y, coldV.
y, tol) <<
"RA=" << testRA <<
" Dec/El=" << testDec;
872 EXPECT_NEAR(hotV.
z, coldV.
z, tol) <<
"RA=" << testRA <<
" Dec/El=" << testDec;
960 RunPluginAlignValidate(plugin, {.np =
ARCMIN_TO_DEG(2)}, 3, 100, 1.0);
966 RunPluginAlignValidate(plugin, {.ih =
ARCMIN_TO_DEG(15)}, 3, 100, 1.0);
972 RunPluginAlignValidate(plugin, {.ch =
ARCMIN_TO_DEG(20)}, 3, 100, 1.0);
978 RunPluginAlignValidate(plugin, kMixed, 3, 100, 1.0);
984 RunPluginAlignValidate(plugin, kMixed, 3, 100, 1.0, kTokyo);
990 RunPluginAlignValidate(plugin, kMixed, 3, 100, 1.0, kSydney);
996 RunPluginAlignValidate(plugin, kMixed, 12, 100, 1.0, kArctic);
1002 RunPluginAlignValidate(plugin, kMixed, 12, 100, 1.0, kAntarctic);
1020 RunPluginAlignValidate(plugin, kMixed, 3, 100, 1.0);
1029 RunPluginAllInAlignment(plugin, kMixed, 20);
1039 RunPluginAlignValidate(plugin, kMixed, 3, 100, 35000.0);
1063 RunPluginAlignValidate(plugin, {.np =
ARCMIN_TO_DEG(2)}, 3, 100, 20.0,
1070 RunPluginAlignValidate(plugin, kMixed, 3, 100, 20.0,
1077 RunPluginAlignValidate(plugin, kMixed, 3, 100, 20.0,
1088 3, 12.0, 50.0, 2.0);
1105 3, 12.0, 50.0, 2.0);
1130 ASSERT_NE(pSupport,
nullptr);
1132 double gmst = ln_get_apparent_sidereal_time(fixedJD);
1139 double valRA = 0, valDec = 0;
1141 for (
int i = 1; i <= 5; ++i)
1144 auto raw = seq.
getRaw(i);
1145 getSkyPoint(raw.first, raw.second, kAZ_MinEl, kAZ_MaxEl,
ra,
dec);
1146 if (i == 1) { valRA =
ra; valDec =
dec; }
1155 Angle encAz, encAlt;
1161 pSupport->AltitudeAzimuthFromTelescopeDirectionVector(tdv, hor);
1163 double azDiff = std::fmod(hor.
azimuth - encAz.
Degrees() + 540.0, 360.0) - 180.0;
1164 azDiff = std::abs(azDiff);
1167 GTEST_LOG_(INFO) <<
" encAz=" << encAz.
Degrees() <<
" pluginAz=" << hor.
azimuth
1168 <<
" azDiff=" << azDiff * 3600.0 <<
"\"";
1169 GTEST_LOG_(INFO) <<
" encAlt=" << encAlt.
Degrees() <<
" pluginAlt=" << hor.
altitude
1170 <<
" altDiff=" << altDiff * 3600.0 <<
"\"";
1172 EXPECT_LT(azDiff, 1.0) <<
"Az differs by " << azDiff <<
"deg — likely Az convention bug";
1173 EXPECT_LT(altDiff, 1.0) <<
"Alt differs by " << altDiff <<
"deg";
1188 ASSERT_NE(pSupport,
nullptr);
1193 double gmst = ln_get_apparent_sidereal_time(fixedJD);
1199 for (
int i = 1; alignCount < 6; ++i)
1202 auto raw = seq.
getRaw(i);
1203 getSkyPoint(raw.first, raw.second, kEQ_MinDec, kEQ_MaxDec,
ra,
dec);
1205 if (
ra >= 6.0 &&
ra < 18.0)
1216 int validateCount = 0;
1217 for (
int i = 1; validateCount < 6; ++i)
1220 auto raw = seq.
getRaw(i);
1221 getSkyPoint(raw.first, raw.second, kEQ_MinDec, kEQ_MaxDec,
ra,
dec);
1223 if (ra < 6.0 || ra >= 18.0)
1230 double outRA, outDec;
1235 double residual = std::sqrt(dRA * dRA + dDec * dDec);
1237 GTEST_LOG_(INFO) <<
" fallback[" << validateCount <<
"] ra=" <<
ra
1238 <<
" dec=" <<
dec <<
" res=" << residual;
1239 EXPECT_LT(residual, 180.0);
1251 RunPluginAlignValidate(plugin, kMixed, 3, 100, 1.0,
1258 RunPluginAlignValidate(plugin, kMixed, 3, 100, 1.0,
1321 RunPluginAlignValidate(plugin, kMixed, 3, 100, 1.0, kNairobi);
1373 RunPluginAlignValidate(plugin, {.ih =
ARCMIN_TO_DEG(15)}, 3, 100, 1.0);
1380 RunPluginAlignValidate(plugin, {.ch =
ARCMIN_TO_DEG(20)}, 5, 100, 1.0);
1393 RunPluginAlignValidate(plugin, {.np =
ARCMIN_TO_DEG(2)}, 3, 100, 1.0);
1401 RunPluginAlignValidate(plugin, {.id =
ARCMIN_TO_DEG(5)}, 3, 100, 1.0);
1407 RunPluginAlignValidate(plugin, {.ma =
ARCMIN_TO_DEG(10)}, 3, 100, 1.0);
1413 RunPluginAlignValidate(plugin, {.me =
ARCMIN_TO_DEG(5)}, 3, 100, 1.0);
1421 RunPluginAlignValidate(plugin, {}, 3, 100, 1.0);
1427 RunPluginAlignValidate(plugin, kMixed, 3, 100, 1.0, kTokyo);
1445 RunPluginAlignValidate(plugin, kMixed, 3, 100, 1.0, kArctic);
1451 RunPluginAlignValidate(plugin, kMixed, 3, 100, 1.0, kAntarctic);
1469 RunPluginAlignValidate(plugin, kMixed, 3, 100, 1.0, kNairobi);
1478 RunPluginAlignValidate(plugin, kHugeIndex, 7, 100, 30000.0);
1485 RunPluginAlignValidate(plugin, kMixed, 7, 100, 100.0, kLosAngeles,
::Alignment::EQ_GEM, 80.0, 89.0);
1491 RunPluginAlignValidate(plugin, kMixed, 3, 100, 1.0,
1498 RunPluginAlignValidate(plugin, kMixed, 3, 100, 1.0,
1510 RunPluginAlignValidate(plugin, kMixed, 6, 100, 1.0);
1526 RunPluginAllInAlignment(plugin, {.ih =
ARCMIN_TO_DEG(5)}, 1);
1576 3, 12.0, 50.0, 0.1);
1585 10, 18.0, 45.0, 1.0);
1593 10, 18.0, 45.0, 1.0);
1601 10, 18.0, -45.0, 1.0, kSydney);
1610 10, 18.0, 45.0, 1.0);
1616 RunPluginAlignValidate(plugin, {.np =
ARCMIN_TO_DEG(2)}, 3, 100, 1.0,
1623 RunPluginAlignValidate(plugin, kMixed, 3, 100, 1.0,
1630 RunPluginAlignValidate(plugin, kMixed, 3, 100, 1.0,
1650static constexpr double kNoiseSigma30arcsec =
ARCSEC_TO_DEG(30.0);
1655 RunTimeShiftValidate(plugin, kMixed, 6, 100, 3.0, 1.0);
1661 RunTimeShiftValidate(plugin, kMixed, 3, 100, 3.0, 1.0);
1667 RunPluginNoiseValidate(noisyPlugin, refPlugin, kMixed, 3, 100, kNoiseSigma30arcsec, 300.0);
1673 RunPluginNoiseValidate(noisyPlugin, refPlugin, kMixed, 3, 100, kNoiseSigma30arcsec, 300.0);
1679 RunPluginNoiseValidate(noisyPlugin, refPlugin, kMixed, 12, 100, kNoiseSigma30arcsec, 120.0);
1685 RunPluginNoiseValidate(noisyPlugin, refPlugin, kMixed, 12, 100, kNoiseSigma30arcsec, 120.0);
1718static void RunPolarDegeneracyTestImpl(
1722 double fixedJD = 2461113.81667)
1737 double ra,
double dec,
double lst_hrs,
double jd,
1742 entry.RightAscension =
ra;
1743 entry.Declination =
dec;
1744 entry.ObservationJulianDate = jd;
1747 Angle primary, secondary;
1750 entry.TelescopeDirection = pSupport->TelescopeDirectionVectorFromAltitudeAzimuth(horCoords);
1758 entry.TelescopeDirection = pSupport->TelescopeDirectionVectorFromEquatorialCoordinates(raCoords);
1769 ::Alignment gen = makeGenerator(errors, site, mountType);
1772 ASSERT_NE(pSupport,
nullptr);
1774 double gmst = ln_get_apparent_sidereal_time(fixedJD);
1778 double syncRA, syncDec;
1793 buildEntryLocal(gen, pSupport, syncRA, syncDec, lst, fixedJD, mountType, entry);
1799 double testRA = 12.0;
1809 pSupport->AltitudeAzimuthFromTelescopeDirectionVector(resultV, hor);
1814 <<
"AltAz polar degeneracy: El=" << hor.
altitude
1815 <<
" -- forward transform produced sub-horizon encoder position";
1817 <<
"AltAz polar degeneracy: El=" << hor.
altitude;
1822 pSupport->EquatorialCoordinatesFromTelescopeDirectionVector(resultV,
eq);
1823 double encoderRA =
eq.rightascension;
1827 double dRA =
rangeHA(encoderRA - testRA) * 15.0;
1832 EXPECT_LT(std::abs(dRA), 15.0)
1833 <<
"EQ polar degeneracy: encoder RA=" << encoderRA
1834 <<
"h vs target RA=" << testRA
1835 <<
"h (dRA=" << dRA <<
" deg)"
1836 <<
" -- forward transform produced garbage encoder RA";
1837 EXPECT_LT(std::abs(
eq.declination - testDec), 15.0)
1838 <<
"EQ polar degeneracy: encoder Dec=" <<
eq.declination
1839 <<
" vs target Dec=" << testDec
1840 <<
" -- forward transform produced garbage encoder Dec";
1910 auto generator = buildIncrementalDb(hotPlugin, kMixed, 6, db);
1913 double gmst = ln_get_apparent_sidereal_time(fixedJD);
1918 getSkyPoint(raw.first, raw.second, kEQ_MinDec, kEQ_MaxDec,
ra,
dec);
1929 verifyHotMatchesCold(hotPlugin, coldPlugin);
1939 double gmst = ln_get_apparent_sidereal_time(fixedJD);
1944 getSkyPoint(raw.first, raw.second, kAZ_MinEl, kAZ_MaxEl,
ra,
dec);
1965 auto generator = buildIncrementalDb(plugin, kMixed, 5, db);
1968 double gmst = ln_get_apparent_sidereal_time(fixedJD);
1971 for (
int i : {6, 7})
1975 getSkyPoint(raw.first, raw.second, kEQ_MinDec, kEQ_MaxDec,
ra,
dec);
1984 double outRA, outDec;
1986 EXPECT_NEAR(12.0, outRA, kRoundTripTolHours);
1987 EXPECT_NEAR(45.0, outDec, kRoundTripTolDeg);
1996 buildIncrementalDb(plugin, kMixed, 6, db);
2006 double outRA, outDec;
2008 EXPECT_NEAR(12.0, outRA, kRoundTripTolHours);
2009 EXPECT_NEAR(60.0, outDec, kRoundTripTolDeg);
2031 double outRA, outDec;
2058 double outRA, outDec;
2082 double outRA, outDec;
2084 EXPECT_NEAR(12.0, outRA, kRoundTripTolHours);
2085 EXPECT_NEAR(30.0, outDec, kRoundTripTolDeg);
2091 EXPECT_NEAR(6.0, outRA, kRoundTripTolHours);
2092 EXPECT_NEAR(45.0, outDec, kRoundTripTolDeg);
2123 double gmst = ln_get_apparent_sidereal_time(
fixedJD);
2134 double lst = getLST(kLosAngeles);
2136 for (
auto [testRA, testDec] : kTestPoints)
2139 Angle primary, secondary;
2143 Angle instHA, instDec;
2147 EXPECT_NEAR(testRA, recoveredRA, 0.001) <<
"RA round-trip failed";
2148 EXPECT_NEAR(testDec, instDec.
Degrees(), 0.001) <<
"Dec round-trip failed";
2159 double lst = getLST(kLosAngeles);
2161 for (
auto [testRA, testDec] : kTestPoints)
2165 Angle primary, secondary;
2169 Angle apparentHa, apparentDec;
2172 EXPECT_NEAR(
range24(inputHA), apparentHa.
Hours(), 0.001) <<
"HA apparent round-trip failed at RA=" << testRA;
2173 EXPECT_NEAR(testDec, apparentDec.
Degrees(), 0.001) <<
"Dec apparent round-trip failed at RA=" << testRA;
2185 double lst = getLST(kLosAngeles);
2187 for (
auto [testRA, testDec] : kTestPoints)
2190 Angle primary, secondary;
2196 pSupport->TelescopeDirectionVectorFromAltitudeAzimuth(horCoords);
2204 pSupport->TelescopeDirectionVectorFromAltitudeAzimuth(refAltAz);
2207 EXPECT_NEAR(refTDV.
x, driverTDV.
x, 1e-4) <<
"TDV.x mismatch at RA=" << testRA;
2208 EXPECT_NEAR(refTDV.
y, driverTDV.
y, 1e-4) <<
"TDV.y mismatch at RA=" << testRA;
2209 EXPECT_NEAR(refTDV.
z, driverTDV.
z, 1e-4) <<
"TDV.z mismatch at RA=" << testRA;
2217 double lst = getLST(kLosAngeles);
2219 for (
auto [testRA, testDec] : kTestPoints)
2222 Angle primary, secondary;
2225 Angle instHA, instDec;
2229 EXPECT_NEAR(testRA, recoveredRA, 0.001) <<
"RA round-trip failed";
2230 EXPECT_NEAR(testDec, instDec.
Degrees(), 0.001) <<
"Dec round-trip failed";
2238 double lst = getLST(kSydney);
2240 for (
auto [testRA, testDec] : kTestPoints)
2243 Angle primary, secondary;
2246 Angle instHA, instDec;
2250 EXPECT_NEAR(testRA, recoveredRA, 0.001) <<
"RA round-trip failed";
2251 EXPECT_NEAR(testDec, instDec.
Degrees(), 0.001) <<
"Dec round-trip failed";
2271 double lastOffset = 999.0;
2274 double JulianOffset,
2277 lastOffset = JulianOffset;
2282 double &,
double &,
double JulianOffset)
override
2284 lastOffset = JulianOffset;
2293 LegacyPlugin plugin;
2295 plugin.Initialise(&db);
2298 double t1 = ln_get_julian_from_sys();
2299 plugin.TransformCelestialToTelescopeJD(1.0, 45.0, fixedJD, tdv);
2300 double t2 = ln_get_julian_from_sys();
2307 EXPECT_GE(plugin.lastOffset, fixedJD - t2) <<
"offset too small";
2308 EXPECT_LE(plugin.lastOffset, fixedJD - t1) <<
"offset too large (was 0.0 before fix)";
2313 LegacyPlugin plugin;
2315 plugin.Initialise(&db);
2318 double ra = 0,
dec = 0;
2319 double t1 = ln_get_julian_from_sys();
2320 plugin.TransformTelescopeToCelestialJD(tdv,
ra,
dec, fixedJD);
2321 double t2 = ln_get_julian_from_sys();
2323 EXPECT_GE(plugin.lastOffset, fixedJD - t2) <<
"offset too small";
2324 EXPECT_LE(plugin.lastOffset, fixedJD - t1) <<
"offset too large (was 0.0 before fix)";
2329 ::testing::InitGoogleTest(&argc, argv);
2330 return RUN_ALL_TESTS();
Generic mathematical utility for generating 2-D Halton quasi-random sequences.
void RunPluginAlignValidate(MathPlugin &plugin, const MountErrors &errors, int numAlign, int numValidate, double targetRMSArcsec, INDI::IGeographicCoordinates site=kLosAngeles, ::Alignment::MOUNT_TYPE mountType=::Alignment::EQ_GEM, double minDecOverride=NAN, double maxDecOverride=NAN, double *maxResidualOut=nullptr)
static void buildEntry(::Alignment &gen, TelescopeDirectionVectorSupportFunctions *pSupport, double ra, double dec, double lst_hrs, double jd, ::Alignment::MOUNT_TYPE mountType, AlignmentDatabaseEntry &entry)
void RunTimeShiftValidate(MathPlugin &plugin, const MountErrors &errors, int numAlign, int numValidate, double shiftHours, double targetRMSArcsec, INDI::IGeographicCoordinates site=kLosAngeles, ::Alignment::MOUNT_TYPE mountType=::Alignment::EQ_GEM)
void RunPluginAllInAlignment(MathPlugin &plugin, const MountErrors &errors, int numPoints, INDI::IGeographicCoordinates site=kLosAngeles, ::Alignment::MOUNT_TYPE mountType=::Alignment::EQ_GEM, double minDecOverride=NAN, double maxDecOverride=NAN)
::Alignment makeGenerator(const MountErrors &e, INDI::IGeographicCoordinates site, ::Alignment::MOUNT_TYPE mountType=::Alignment::EQ_GEM)
Build and configure a scopesim_helper Alignment generator.
void RunEqPolarRegression(SPKMathPlugin &plugin, const MountErrors &errors, int numAlign, double valRA, double valDec, double tolArcsec, INDI::IGeographicCoordinates site=kLosAngeles)
void verifyHotMatchesCold(SPKMathPlugin &hotPlugin, SPKMathPlugin &coldPlugin, ::Alignment::MOUNT_TYPE mountType=::Alignment::EQ_GEM)
void RunAltAzPolarRegression(SPKMathPlugin &plugin, const MountErrors &errors, int numAlign, double valRA, double valDec, double tolArcsec, INDI::IGeographicCoordinates site=kLosAngeles)
void RunMeridianFlipValidate(T &plugin, const MountErrors &errors, const INDI::IGeographicCoordinates &location, const MountAlignment_t pole, double targetRMSArcsec=40.0)
void RunPluginNoiseValidate(MathPlugin &noisyPlugin, MathPlugin &refPlugin, const MountErrors &errors, int numAlign, int numValidate, double noiseSigmaDeg, double targetRMSArcsec, INDI::IGeographicCoordinates site=kLosAngeles, ::Alignment::MOUNT_TYPE mountType=::Alignment::EQ_GEM)
void RunGotoConventionRegression(MathPlugin &plugin, const MountErrors &errors, int numAlign, double valRA, double valDec, double azTolDeg, INDI::IGeographicCoordinates site=kLosAngeles)
static constexpr double fixedJD
::Alignment buildIncrementalDb(SPKMathPlugin &plugin, const MountErrors &errors, int n, InMemoryDatabase &db, ::Alignment::MOUNT_TYPE mountType=::Alignment::EQ_GEM, INDI::IGeographicCoordinates site=kLosAngeles)
The Alignment class This converts between the mount axis angles and the sky hour angle and declinatio...
void setCorrections(double ih, double id, double ch, double np, double ma, double me)
setCorrections set the values of the six corrections
void instrumentHaDecToMount(Angle instrumentHa, Angle instrumentDec, Angle *primary, Angle *secondary)
void mountToApparentHaDec(Angle primary, Angle secondary, Angle *apparentHa, Angle *apparentDec)
mountToApparentHaDec: convert mount position to apparent Ha, Dec
void apparentHaDecToMount(Angle apparentHa, Angle apparentDec, Angle *primary, Angle *secondary)
apparentHaDecToMount
void observedToInstrument(Angle observedHa, Angle observedDec, Angle *instrumentHa, Angle *instrumentDec)
void mountToInstrumentHaDec(Angle primary, Angle secondary, Angle *instrumentHa, Angle *instrumentDec)
mountToInstrumentHaDec: convert mount axis positions to instrument (encoder) HA/Dec without applying ...
The Angle class This class implements an angle type. This holds an angle that is always in the range ...
static double getLST(INDI::IGeographicCoordinates site)
static constexpr SkyPoint kTestPoints[]
virtual bool TransformTelescopeToCelestialJD(const TelescopeDirectionVector &ApparentTelescopeDirectionVector, double &RightAscension, double &Declination, double JulianDate) override
Override for the base class virtual function.
virtual bool TransformCelestialToTelescopeJD(double RightAscension, double Declination, double JulianDate, TelescopeDirectionVector &ApparentTelescopeDirectionVector) override
Override for the base class virtual function.
virtual bool Initialise(InMemoryDatabase *pInMemoryDatabase) override
Override for the base class virtual function.
This class implements the default math plugin.
Generic 2-D Halton quasi-random generator.
std::pair< double, double > getRaw(int n) const
Get the n-th 2-D point in the sequence.
This class provides the driver side API to the in memory alignment database.
void SetDatabaseReferencePosition(double Latitude, double Longitude)
Set the database reference position.
AlignmentDatabaseType & GetAlignmentDatabase()
Get a reference to the in memory database.
Provides alignment subsystem functions to INDI alignment math plugins.
virtual bool TransformCelestialToTelescope(const double RightAscension, const double Declination, double JulianOffset, TelescopeDirectionVector &ApparentTelescopeDirectionVector)=0
Get the alignment corrected telescope pointing direction for the supplied celestial coordinates.
virtual bool Initialise(InMemoryDatabase *pInMemoryDatabase)
Initialise or re-initialise the math plugin. Re-reading the in memory database as necessary.
virtual void SetApproximateMountAlignment(MountAlignment_t ApproximateAlignment)
Set the approximate alignment of the mount.
virtual bool TransformTelescopeToCelestialJD(const TelescopeDirectionVector &ApparentTelescopeDirectionVector, double &RightAscension, double &Declination, double JulianDate)
Get the true celestial coordinates for the supplied telescope pointing direction.
virtual MountAlignment_t GetApproximateMountAlignment()
Get the approximate alignment of the mount.
virtual bool TransformTelescopeToCelestial(const TelescopeDirectionVector &ApparentTelescopeDirectionVector, double &RightAscension, double &Declination, double JulianOffset=0)=0
Get the true celestial coordinates for the supplied telescope pointing direction.
virtual bool TransformCelestialToTelescopeJD(double RightAscension, double Declination, double JulianDate, TelescopeDirectionVector &ApparentTelescopeDirectionVector)
Get the alignment corrected telescope pointing direction for the supplied celestial coordinates.
virtual bool TransformCelestialToTelescopeJD(double RightAscension, double Declination, double JulianDate, TelescopeDirectionVector &ApparentTelescopeDirectionVector) override
Get the alignment corrected telescope pointing direction for the supplied celestial coordinates.
virtual bool Initialise(InMemoryDatabase *pInMemoryDatabase) override
Initialise or re-initialise the math plugin. Re-reading the in memory database as necessary.
virtual bool TransformTelescopeToCelestialJD(const TelescopeDirectionVector &ApparentTelescopeDirectionVector, double &RightAscension, double &Declination, double JulianDate) override
Get the true celestial coordinates for the supplied telescope pointing direction.
virtual bool TransformCelestialToTelescopeJD(double RightAscension, double Declination, double JulianDate, TelescopeDirectionVector &ApparentTelescopeDirectionVector) override
Get the alignment corrected telescope pointing direction for the supplied celestial coordinates.
virtual bool Initialise(InMemoryDatabase *pInMemoryDatabase) override
Initialise or re-initialise the math plugin. Re-reading the in memory database as necessary.
This class implements the SVD math plugin.
These functions are used to convert different coordinate systems to and from the telescope direction ...
const TelescopeDirectionVector TelescopeDirectionVectorFromAltitudeAzimuth(INDI::IHorizontalCoordinates HorizontalCoordinates)
Calculates a normalised direction vector from the supplied altitude and azimuth.
const TelescopeDirectionVector TelescopeDirectionVectorFromEquatorialCoordinates(INDI::IEquatorialCoordinates EquatorialCoordinates)
Calculates a telescope direction vector from the supplied equatorial coordinates.
double rangeHA(double r)
rangeHA Limits the hour angle value to be between -12 —> 12
double range24(double r)
range24 Limits a number to be between 0-24 range.
double get_local_hour_angle(double sideral_time, double ra)
get_local_hour_angle Returns local hour angle of an object
Implementations for common driver routines.
#define ARCSEC_TO_DEG(as)
#define ARCMIN_TO_DEG(am)
Namespace to encapsulate the INDI Alignment Subsystem classes. For more information see "INDI Alignme...
enum INDI::AlignmentSubsystem::MountAlignment MountAlignment_t
void EquatorialToHorizontal(IEquatorialCoordinates *object, IGeographicCoordinates *observer, double JD, IHorizontalCoordinates *position)
EquatorialToHorizontal Calculate horizontal coordinates from equatorial coordinates.
static ErrorStats compute(std::vector< double > residuals)
Entry in the in memory alignment database.
double RightAscension
Right ascension in decimal hours. N.B. libnova works in decimal degrees so conversion is always neede...
double ObservationJulianDate
TelescopeDirectionVector TelescopeDirection
Normalised vector giving telescope pointing direction. This is referred to elsewhere as the "apparent...
double Declination
Declination in decimal degrees.
Holds a normalised direction vector (direction cosines)
static const loggerConf file_off
void configure(const std::string &outputFile, const loggerConf configuration, const int fileVerbosityLevel, const int screenVerbosityLevel)
Method to configure the logger. Called by the DEBUG_CONF() macro. To make implementation easier,...
static Logger & getInstance()
Method to get a reference to the object (i.e., Singleton) It is a static method.
double ma
Polar Azimuth error (degrees)
double me
Polar Elevation error (degrees)
double np
Non-perpendicularity (degrees)
double ch
Collimation/cone error (degrees)
double id
Dec index error (degrees)
double ih
HA index error (degrees)
TEST_F(AlignmentPluginTest, Test_BuiltIn_ErrorRecovery)