Skip to content

Commit ea5bb05

Browse files
committed
test/alignment: implement physical regression and time-shift tests
- Add JD-Shift tests to verify sidereal time handling over several hours. - Add physical encoder mapping tests for Alt-Az and Equatorial mounts. - Add Southern Hemisphere regression to verify geometric symmetry. - Refactor buildEntry to support arbitrary Julian Dates in simulations.
1 parent 742db84 commit ea5bb05

1 file changed

Lines changed: 189 additions & 20 deletions

File tree

test/alignment/test_alignment_plugins.cpp

Lines changed: 189 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -196,15 +196,15 @@ class AlignmentPluginTest : public ::testing::Test
196196

197197
static void buildEntry(::Alignment &gen,
198198
TelescopeDirectionVectorSupportFunctions *pSupport,
199-
double ra, double dec, double lst_hrs,
199+
double ra, double dec, double lst_hrs, double jd,
200200
::Alignment::MOUNT_TYPE mountType,
201201
AlignmentDatabaseEntry &entry)
202202
{
203203
Angle ha(get_local_hour_angle(lst_hrs, ra), Angle::HOURS), adec(dec);
204204

205205
entry.RightAscension = ra;
206206
entry.Declination = dec;
207-
entry.ObservationJulianDate = fixedJD;
207+
entry.ObservationJulianDate = jd;
208208

209209
if (mountType == ::Alignment::ALTAZ)
210210
{
@@ -260,8 +260,8 @@ class AlignmentPluginTest : public ::testing::Test
260260
getSkyPoint(raw.first, raw.second, minDec, maxDec, ra, dec);
261261

262262
AlignmentDatabaseEntry entry;
263-
buildEntry(generator, pSupport, ra, dec, lst, mountType, entry);
264-
db.GetAlignmentDatabase().push_back(entry);
263+
buildEntry(generator, pSupport, ra, dec, lst, fixedJD, mountType, entry);
264+
db.GetAlignmentDatabase().push_back(entry);
265265
}
266266

267267
ASSERT_TRUE(plugin.Initialise(&db));
@@ -322,7 +322,7 @@ class AlignmentPluginTest : public ::testing::Test
322322
getSkyPoint(raw.first, raw.second, minDec, maxDec, ra, dec);
323323

324324
AlignmentDatabaseEntry entry;
325-
buildEntry(generator, pSupport, ra, dec, lst, mountType, entry);
325+
buildEntry(generator, pSupport, ra, dec, lst, fixedJD, mountType, entry);
326326
alignDb.GetAlignmentDatabase().push_back(entry);
327327
}
328328

@@ -423,7 +423,7 @@ class AlignmentPluginTest : public ::testing::Test
423423
getSkyPoint(raw.first, raw.second, minDec, maxDec, ra, dec);
424424

425425
AlignmentDatabaseEntry entry;
426-
buildEntry(generator, pSupport, ra, dec, lst, mountType, entry);
426+
buildEntry(generator, pSupport, ra, dec, lst, fixedJD, mountType, entry);
427427
cleanDb.GetAlignmentDatabase().push_back(entry);
428428

429429
addNoiseToDirection(entry.TelescopeDirection, noiseSigmaDeg, rng);
@@ -477,6 +477,80 @@ class AlignmentPluginTest : public ::testing::Test
477477
<< "Plugin failed noise-stress P95 target " << targetRMSArcsec * 1.5 << "\"";
478478
}
479479

480+
void RunTimeShiftValidate(MathPlugin &plugin, const MountErrors &errors,
481+
int numAlign, int numValidate,
482+
double shiftHours, double targetRMSArcsec,
483+
INDI::IGeographicCoordinates site = kLosAngeles,
484+
::Alignment::MOUNT_TYPE mountType = ::Alignment::EQ_GEM)
485+
{
486+
double minDec = (mountType == ::Alignment::ALTAZ) ? kAZ_MinEl : kEQ_MinDec;
487+
double maxDec = (mountType == ::Alignment::ALTAZ) ? kAZ_MaxEl : kEQ_MaxDec;
488+
489+
InMemoryDatabase alignDb;
490+
alignDb.SetDatabaseReferencePosition(site);
491+
492+
MountAlignment_t alignment = (mountType == ::Alignment::ALTAZ) ? ZENITH :
493+
site.latitude >= 0 ? NORTH_CELESTIAL_POLE
494+
: SOUTH_CELESTIAL_POLE;
495+
plugin.SetApproximateMountAlignment(alignment);
496+
497+
::Alignment generator = makeGenerator(errors, site, mountType);
498+
auto *pSupport = dynamic_cast<TelescopeDirectionVectorSupportFunctions *>(&plugin);
499+
ASSERT_NE(pSupport, nullptr);
500+
501+
// Training Phase: JD = fixedJD
502+
double gmst = ln_get_apparent_sidereal_time(fixedJD);
503+
double lst = range24(gmst + DEG_TO_HOURS(site.longitude));
504+
505+
HaltonSequence alignSeq(2, 3);
506+
for (int i = 1; i <= numAlign; ++i)
507+
{
508+
double ra, dec;
509+
auto raw = alignSeq.getRaw(i);
510+
getSkyPoint(raw.first, raw.second, minDec, maxDec, ra, dec);
511+
512+
AlignmentDatabaseEntry entry;
513+
buildEntry(generator, pSupport, ra, dec, lst, fixedJD, mountType, entry);
514+
alignDb.GetAlignmentDatabase().push_back(entry);
515+
}
516+
517+
ASSERT_TRUE(plugin.Initialise(&alignDb));
518+
519+
// Validation Phase: JD = fixedJD + shiftHours/24
520+
double valJD = fixedJD + shiftHours / 24.0;
521+
double joff = valJD - ln_get_julian_from_sys();
522+
523+
HaltonSequence validateSeq(5, 7);
524+
std::vector<double> residuals;
525+
526+
for (int i = 1; i <= numValidate; ++i)
527+
{
528+
double ra, dec;
529+
auto raw = validateSeq.getRaw(i);
530+
getSkyPoint(raw.first, raw.second, minDec, maxDec, ra, dec);
531+
532+
TelescopeDirectionVector resultV;
533+
// The plugin must correctly interpret ra, dec relative to the new joff (time)
534+
ASSERT_TRUE(plugin.TransformCelestialToTelescope(ra, dec, joff, resultV));
535+
536+
double outRA, outDec;
537+
plugin.TransformTelescopeToCelestial(resultV, outRA, outDec, joff);
538+
539+
double dRA = DEG_TO_ARCSEC(rangeHA(outRA - ra) * 15.0) * std::cos(DEG_TO_RAD(dec));
540+
double dDec = DEG_TO_ARCSEC(outDec - dec);
541+
double residual = std::sqrt(dRA * dRA + dDec * dDec);
542+
543+
residuals.push_back(residual);
544+
}
545+
546+
ErrorStats stats = ErrorStats::compute(residuals);
547+
GTEST_LOG_(INFO) << "[ TIME SHIFT " << shiftHours << "h ] RMS: " << stats.rms << "\" Target: " << targetRMSArcsec
548+
<< "\" P95: " << stats.p95 << "\" Peak: " << stats.max << "\"";
549+
550+
EXPECT_LT(stats.rms, targetRMSArcsec)
551+
<< "Plugin failed time-shift RMS quality target " << targetRMSArcsec << "\"";
552+
}
553+
480554
// -----------------------------------------------------------------------
481555
// RunGotoConventionRegression -- AltAz C->T convention check.
482556
//
@@ -514,7 +588,7 @@ class AlignmentPluginTest : public ::testing::Test
514588
auto raw = seq.getRaw(i);
515589
getSkyPoint(raw.first, raw.second, kAZ_MinEl, kAZ_MaxEl, ra, dec);
516590
AlignmentDatabaseEntry entry;
517-
buildEntry(gen, pSupport, ra, dec, lst, ::Alignment::ALTAZ, entry);
591+
buildEntry(gen, pSupport, ra, dec, lst, fixedJD, ::Alignment::ALTAZ, entry);
518592
db.GetAlignmentDatabase().push_back(entry);
519593
}
520594
ASSERT_TRUE(plugin.Initialise(&db));
@@ -572,7 +646,7 @@ class AlignmentPluginTest : public ::testing::Test
572646
auto raw = seq.getRaw(i);
573647
getSkyPoint(raw.first, raw.second, kEQ_MinDec, kEQ_MaxDec, ra, dec);
574648
AlignmentDatabaseEntry entry;
575-
buildEntry(gen, pSupport, ra, dec, lst, ::Alignment::EQ_GEM, entry);
649+
buildEntry(gen, pSupport, ra, dec, lst, fixedJD, ::Alignment::EQ_GEM, entry);
576650
db.GetAlignmentDatabase().push_back(entry);
577651
}
578652
ASSERT_TRUE(plugin.Initialise(&db));
@@ -609,6 +683,64 @@ class AlignmentPluginTest : public ::testing::Test
609683
EXPECT_LT(totalDiff, tolArcsec) << "Encoder position differs by " << totalDiff << "\" — likely ME/MA mapping bug";
610684
}
611685

686+
void RunAltAzPolarRegression(SPKMathPlugin &plugin,
687+
const MountErrors &errors,
688+
int numAlign,
689+
double valRA, double valDec,
690+
double tolArcsec,
691+
INDI::IGeographicCoordinates site = kLosAngeles)
692+
{
693+
plugin.SetApproximateMountAlignment(ZENITH);
694+
695+
InMemoryDatabase db;
696+
db.SetDatabaseReferencePosition(site);
697+
698+
::Alignment gen = makeGenerator(errors, site, ::Alignment::ALTAZ);
699+
auto *pSupport = dynamic_cast<TelescopeDirectionVectorSupportFunctions *>(&plugin);
700+
ASSERT_NE(pSupport, nullptr);
701+
702+
double gmst = ln_get_apparent_sidereal_time(fixedJD);
703+
double lst = range24(gmst + DEG_TO_HOURS(site.longitude));
704+
705+
HaltonSequence seq(2, 3);
706+
for (int i = 1; i <= numAlign; ++i)
707+
{
708+
double ra, dec;
709+
auto raw = seq.getRaw(i);
710+
getSkyPoint(raw.first, raw.second, kAZ_MinEl, kAZ_MaxEl, ra, dec);
711+
AlignmentDatabaseEntry entry;
712+
buildEntry(gen, pSupport, ra, dec, lst, fixedJD, ::Alignment::ALTAZ, entry);
713+
db.GetAlignmentDatabase().push_back(entry);
714+
}
715+
ASSERT_TRUE(plugin.Initialise(&db));
716+
717+
double joff = fixedJD - ln_get_julian_from_sys();
718+
719+
// Ground truth encoder position (Alt/Az) from scopesim
720+
Angle ha(get_local_hour_angle(lst, valRA), Angle::HOURS);
721+
Angle adec(valDec);
722+
Angle encAz, encAlt;
723+
gen.apparentHaDecToMount(ha, adec, &encAz, &encAlt);
724+
725+
// Plugin C->T prediction, decoded to Alt/Az without T->C
726+
TelescopeDirectionVector tdv;
727+
ASSERT_TRUE(plugin.TransformCelestialToTelescope(valRA, valDec, joff, tdv));
728+
INDI::IHorizontalCoordinates horCoords;
729+
pSupport->AltitudeAzimuthFromTelescopeDirectionVector(tdv, horCoords);
730+
731+
double azDiff = std::fmod(horCoords.azimuth - encAz.Degrees() + 540.0, 360.0) - 180.0;
732+
double altDiff = std::abs(horCoords.altitude - encAlt.Degrees());
733+
double totalDiff = std::sqrt(std::pow(azDiff * std::cos(DEG_TO_RAD(horCoords.altitude)), 2) + altDiff * altDiff);
734+
totalDiff *= 3600.0; // arcsec
735+
736+
GTEST_LOG_(INFO) << " encAz=" << encAz.Degrees() << " pluginAz=" << horCoords.azimuth
737+
<< " azDiff=" << azDiff * 3600.0 << "\"";
738+
GTEST_LOG_(INFO) << " encAlt=" << encAlt.Degrees() << " pluginAlt=" << horCoords.altitude
739+
<< " altDiff=" << altDiff * 3600.0 << "\"";
740+
741+
EXPECT_LT(totalDiff, tolArcsec) << "Encoder position differs by " << totalDiff << "\" — likely AN/AW mapping bug";
742+
}
743+
612744
// -----------------------------------------------------------------------
613745
// RunMeridianFlipValidate -- exercises GEM pier flips in both hemispheres
614746
// -----------------------------------------------------------------------
@@ -639,7 +771,7 @@ class AlignmentPluginTest : public ::testing::Test
639771
getSkyPoint(raw.first, raw.second, kEQ_MinDec, kEQ_MaxDec, ra, dec);
640772

641773
AlignmentDatabaseEntry entry;
642-
buildEntry(generator, pSupport, ra, dec, lst, ::Alignment::EQ_GEM, entry);
774+
buildEntry(generator, pSupport, ra, dec, lst, fixedJD, ::Alignment::EQ_GEM, entry);
643775
alignDb.GetAlignmentDatabase().push_back(entry);
644776
}
645777

@@ -723,8 +855,8 @@ class AlignmentPluginTest : public ::testing::Test
723855
auto raw = seq.getRaw(i);
724856
getSkyPoint(raw.first, raw.second, minDec, maxDec, ra, dec);
725857
AlignmentDatabaseEntry entry;
726-
buildEntry(generator, pSupport, ra, dec, lst, mountType, entry);
727-
db.GetAlignmentDatabase().push_back(entry);
858+
buildEntry(generator, pSupport, ra, dec, lst, fixedJD, mountType, entry);
859+
db.GetAlignmentDatabase().push_back(entry);
728860
}
729861
EXPECT_TRUE(plugin.Initialise(&db));
730862
return generator;
@@ -1029,7 +1161,7 @@ TEST_F(AlignmentPluginTest, Nearest_AltAz_Goto_Convention_Regression)
10291161
getSkyPoint(raw.first, raw.second, kAZ_MinEl, kAZ_MaxEl, ra, dec);
10301162
if (i == 1) { valRA = ra; valDec = dec; }
10311163
AlignmentDatabaseEntry entry;
1032-
buildEntry(gen, pSupport, ra, dec, lst, ::Alignment::ALTAZ, entry);
1164+
buildEntry(gen, pSupport, ra, dec, lst, fixedJD, ::Alignment::ALTAZ, entry);
10331165
db.GetAlignmentDatabase().push_back(entry);
10341166
}
10351167
ASSERT_TRUE(plugin.Initialise(&db));
@@ -1091,7 +1223,7 @@ TEST_F(AlignmentPluginTest, SVD_AlignValidate_ConvexHullFallback)
10911223
if (ra >= 6.0 && ra < 18.0)
10921224
continue;
10931225
AlignmentDatabaseEntry entry;
1094-
buildEntry(generator, pSupport, ra, dec, lst, ::Alignment::EQ_GEM, entry);
1226+
buildEntry(generator, pSupport, ra, dec, lst, fixedJD, ::Alignment::EQ_GEM, entry);
10951227
alignDb.GetAlignmentDatabase().push_back(entry);
10961228
++alignCount;
10971229
}
@@ -1473,6 +1605,31 @@ TEST_F(AlignmentPluginTest, SPK_EQ_Polar_Mapping_Regression)
14731605
10, 18.0, 45.0, 1.0);
14741606
}
14751607

1608+
TEST_F(AlignmentPluginTest, SPK_EQ_ConeError_Mapping_Regression)
1609+
{
1610+
SPKMathPlugin plugin;
1611+
// Inject significant CH and verify the plugin maps it correctly.
1612+
RunEqPolarRegression(plugin, {.ch = ARCMIN_TO_DEG(10)},
1613+
10, 18.0, 45.0, 1.0);
1614+
}
1615+
1616+
TEST_F(AlignmentPluginTest, SPK_EQ_Sydney_Mapping_Regression)
1617+
{
1618+
SPKMathPlugin plugin;
1619+
// Verify Southern Hemisphere polar mapping (Sydney, lat=-33.9)
1620+
RunEqPolarRegression(plugin, {.ma = ARCMIN_TO_DEG(10), .me = ARCMIN_TO_DEG(5)},
1621+
10, 18.0, -45.0, 1.0, kSydney);
1622+
}
1623+
1624+
TEST_F(AlignmentPluginTest, SPK_AltAz_Polar_Mapping_Regression)
1625+
{
1626+
SPKMathPlugin plugin;
1627+
// Inject significant AN and AW and verify the plugin maps them to the correct axes.
1628+
// Use 10 points to ensure a full 6-term fit is well-conditioned.
1629+
RunAltAzPolarRegression(plugin, {.ma = ARCMIN_TO_DEG(10), .me = ARCMIN_TO_DEG(5)},
1630+
10, 18.0, 45.0, 1.0);
1631+
}
1632+
14761633
TEST_F(AlignmentPluginTest, SPK_AlignValidate_AltAz_NonPerp)
14771634
{
14781635
SPKMathPlugin plugin;
@@ -1512,6 +1669,18 @@ TEST_F(AlignmentPluginTest, SPK_AlignValidate_AltAz_Southern)
15121669

15131670
static constexpr double kNoiseSigma30arcsec = ARCSEC_TO_DEG(30.0); // 30" per axis
15141671

1672+
TEST_F(AlignmentPluginTest, SPK_TimeShift_Regression)
1673+
{
1674+
SPKMathPlugin plugin;
1675+
RunTimeShiftValidate(plugin, kMixed, 6, 100, 3.0, 1.0);
1676+
}
1677+
1678+
TEST_F(AlignmentPluginTest, SVD_TimeShift_Regression)
1679+
{
1680+
SVDMathPlugin plugin;
1681+
RunTimeShiftValidate(plugin, kMixed, 3, 100, 3.0, 1.0);
1682+
}
1683+
15151684
TEST_F(AlignmentPluginTest, NoiseStress_SPK_3pts)
15161685
{
15171686
SPKMathPlugin noisyPlugin, refPlugin;
@@ -1583,16 +1752,16 @@ static void RunPolarDegeneracyTestImpl(
15831752
return gen;
15841753
};
15851754

1586-
auto buildEntry = [&](::Alignment &gen,
1755+
auto buildEntryLocal = [&](::Alignment &gen,
15871756
TelescopeDirectionVectorSupportFunctions *pSupport,
1588-
double ra, double dec, double lst_hrs,
1757+
double ra, double dec, double lst_hrs, double jd,
15891758
::Alignment::MOUNT_TYPE mt,
15901759
AlignmentDatabaseEntry &entry)
15911760
{
15921761
Angle ha(get_local_hour_angle(lst_hrs, ra), Angle::HOURS), adec(dec);
15931762
entry.RightAscension = ra;
15941763
entry.Declination = dec;
1595-
entry.ObservationJulianDate = fixedJD;
1764+
entry.ObservationJulianDate = jd;
15961765
if (mt == ::Alignment::ALTAZ)
15971766
{
15981767
Angle primary, secondary;
@@ -1641,7 +1810,7 @@ static void RunPolarDegeneracyTestImpl(
16411810
}
16421811

16431812
AlignmentDatabaseEntry entry;
1644-
buildEntry(gen, pSupport, syncRA, syncDec, lst, mountType, entry);
1813+
buildEntryLocal(gen, pSupport, syncRA, syncDec, lst, fixedJD, mountType, entry);
16451814
db.GetAlignmentDatabase().push_back(entry);
16461815

16471816
ASSERT_TRUE(plugin.Initialise(&db));
@@ -1770,7 +1939,7 @@ TEST_F(AlignmentPluginTest, SPK_Incremental_MatchesCold_Equatorial)
17701939
auto raw = HaltonSequence(2, 3).getRaw(7);
17711940
getSkyPoint(raw.first, raw.second, kEQ_MinDec, kEQ_MaxDec, ra, dec);
17721941
AlignmentDatabaseEntry entry;
1773-
buildEntry(generator, pSupport, ra, dec, lst, ::Alignment::EQ_GEM, entry);
1942+
buildEntry(generator, pSupport, ra, dec, lst, fixedJD, ::Alignment::EQ_GEM, entry);
17741943
db.GetAlignmentDatabase().push_back(entry);
17751944
}
17761945
ASSERT_TRUE(hotPlugin.Initialise(&db));
@@ -1796,7 +1965,7 @@ TEST_F(AlignmentPluginTest, SPK_Incremental_MatchesCold_AltAz)
17961965
auto raw = HaltonSequence(2, 3).getRaw(7);
17971966
getSkyPoint(raw.first, raw.second, kAZ_MinEl, kAZ_MaxEl, ra, dec);
17981967
AlignmentDatabaseEntry entry;
1799-
buildEntry(generator, pSupport, ra, dec, lst, ::Alignment::ALTAZ, entry);
1968+
buildEntry(generator, pSupport, ra, dec, lst, fixedJD, ::Alignment::ALTAZ, entry);
18001969
db.GetAlignmentDatabase().push_back(entry);
18011970
}
18021971
ASSERT_TRUE(hotPlugin.Initialise(&db));
@@ -1827,7 +1996,7 @@ TEST_F(AlignmentPluginTest, SPK_Incremental_InvalidStateNotUsed)
18271996
auto raw = HaltonSequence(2, 3).getRaw(i);
18281997
getSkyPoint(raw.first, raw.second, kEQ_MinDec, kEQ_MaxDec, ra, dec);
18291998
AlignmentDatabaseEntry entry;
1830-
buildEntry(generator, pSupport, ra, dec, lst, ::Alignment::EQ_GEM, entry);
1999+
buildEntry(generator, pSupport, ra, dec, lst, fixedJD, ::Alignment::EQ_GEM, entry);
18312000
db.GetAlignmentDatabase().push_back(entry);
18322001
ASSERT_TRUE(plugin.Initialise(&db));
18332002
}

0 commit comments

Comments
 (0)