|
| 1 | +#include<gtest/gtest.h> |
| 2 | + |
| 3 | +#include<array> |
| 4 | +#include<string> |
| 5 | +#include<vector> |
| 6 | + |
| 7 | +#include "lunar/calendar.hpp" |
| 8 | +#include "lunar/core.hpp" |
| 9 | +#include "lunar/lunar_eclipse.hpp" |
| 10 | +#include "lunar/solar_eclipse.hpp" |
| 11 | +#include "lunar/time_scale.hpp" |
| 12 | + |
| 13 | +#include "test_common.hpp" |
| 14 | + |
| 15 | +namespace{ |
| 16 | + |
| 17 | +constexpr double kSolarTermTolSec=15.0*60.0; |
| 18 | +constexpr double kLunarPhaseTolSec=20.0*60.0; |
| 19 | +constexpr double kLunarMonthTolSec=20.0*60.0; |
| 20 | +constexpr double kIllPctTol=0.2; |
| 21 | +constexpr double kAngleTolDeg=0.1; |
| 22 | +constexpr double kEclipseTimeTolSec=20.0*60.0; |
| 23 | +constexpr double kEclipseScalarTol=0.02; |
| 24 | +constexpr std::array<const char*,6> kStableDates={{ |
| 25 | + "2025-01-30", |
| 26 | + "2025-03-06", |
| 27 | + "2025-04-25", |
| 28 | + "2025-06-10", |
| 29 | + "2025-08-25", |
| 30 | + "2025-11-10", |
| 31 | +}}; |
| 32 | + |
| 33 | +double cst_to_utc_jd(int year,int month,int day,int hour=0,int minute=0, |
| 34 | + double second=0.0){ |
| 35 | + return greg2jd(year,month,day,hour,minute,second)-UTC8DAY; |
| 36 | +} |
| 37 | + |
| 38 | +void expect_close_jd(double lhs,double rhs,double tol_sec){ |
| 39 | + EXPECT_NEAR(lhs,rhs,tol_sec/SEC_DAY); |
| 40 | +} |
| 41 | + |
| 42 | +lunar::core::DayComputeOptions make_day_opt(const std::string&ephem, |
| 43 | + const std::string&date_text){ |
| 44 | + lunar::core::DayComputeOptions opt; |
| 45 | + opt.ephem=ephem; |
| 46 | + opt.date_text=date_text; |
| 47 | + opt.at_time="12:00:00"; |
| 48 | + opt.tz="+08:00"; |
| 49 | + opt.lunar_day_tz="+08:00"; |
| 50 | + opt.include_events=false; |
| 51 | + opt.include_astro=false; |
| 52 | + return opt; |
| 53 | +} |
| 54 | + |
| 55 | +lunar::core::GanzhiComputeOptions make_ganzhi_opt(const std::string&ephem, |
| 56 | + const std::string&date_text){ |
| 57 | + lunar::core::GanzhiComputeOptions opt; |
| 58 | + opt.ephem=ephem; |
| 59 | + opt.date_text=date_text; |
| 60 | + opt.at_time="12:00:00"; |
| 61 | + opt.tz="+08:00"; |
| 62 | + opt.lunar_day_tz="+08:00"; |
| 63 | + return opt; |
| 64 | +} |
| 65 | + |
| 66 | +void expect_same_lunar_date(const LunDate&lhs,const LunDate&rhs){ |
| 67 | + EXPECT_EQ(lhs.lunar_year,rhs.lunar_year); |
| 68 | + EXPECT_EQ(lhs.lun_mno,rhs.lun_mno); |
| 69 | + EXPECT_EQ(lhs.is_leap,rhs.is_leap); |
| 70 | + EXPECT_EQ(lhs.lunar_day,rhs.lunar_day); |
| 71 | + EXPECT_EQ(lhs.lun_mlab,rhs.lun_mlab); |
| 72 | + EXPECT_EQ(lhs.lun_label,rhs.lun_label); |
| 73 | +} |
| 74 | + |
| 75 | +void expect_same_gz(const GzNode&lhs,const GzNode&rhs){ |
| 76 | + EXPECT_EQ(lhs.stem,rhs.stem); |
| 77 | + EXPECT_EQ(lhs.branch,rhs.branch); |
| 78 | + EXPECT_EQ(lhs.text,rhs.text); |
| 79 | +} |
| 80 | + |
| 81 | +} |
| 82 | + |
| 83 | +TEST(SeriesVsBspCalendar, SolarTermsTrackReference){ |
| 84 | + if(!has_reference_bsp()){ |
| 85 | + GTEST_SKIP()<<"requires LUNAR_TEST_BSP or a repo-local BSP file"; |
| 86 | + } |
| 87 | +#if !LUNAR_ENABLE_SERIES_FALLBACK |
| 88 | + GTEST_SKIP()<<"requires series fallback"; |
| 89 | +#else |
| 90 | + const std::string ref_bsp=reference_bsp(); |
| 91 | + EphRead series_eph("series"); |
| 92 | + EphRead bsp_eph(ref_bsp); |
| 93 | + SolLunCal series_solver(series_eph); |
| 94 | + SolLunCal bsp_solver(bsp_eph); |
| 95 | + |
| 96 | + for(const auto&it : SolLunCal::st_defs()){ |
| 97 | + const std::string&code=it.first; |
| 98 | + const LocalDT series_dt=series_solver.find_st(code,2025); |
| 99 | + const LocalDT bsp_dt=bsp_solver.find_st(code,2025); |
| 100 | + SCOPED_TRACE(code); |
| 101 | + expect_close_jd(series_dt.toUtcJD(),bsp_dt.toUtcJD(),kSolarTermTolSec); |
| 102 | + } |
| 103 | +#endif |
| 104 | +} |
| 105 | + |
| 106 | +TEST(SeriesVsBspCalendar, LunarPhasesTrackReference){ |
| 107 | + if(!has_reference_bsp()){ |
| 108 | + GTEST_SKIP()<<"requires LUNAR_TEST_BSP or a repo-local BSP file"; |
| 109 | + } |
| 110 | +#if !LUNAR_ENABLE_SERIES_FALLBACK |
| 111 | + GTEST_SKIP()<<"requires series fallback"; |
| 112 | +#else |
| 113 | + const std::string ref_bsp=reference_bsp(); |
| 114 | + EphRead series_eph("series"); |
| 115 | + EphRead bsp_eph(ref_bsp); |
| 116 | + SolLunCal series_solver(series_eph); |
| 117 | + SolLunCal bsp_solver(bsp_eph); |
| 118 | + |
| 119 | + const YearResult series_year=series_solver.compute_year(2025,nullptr); |
| 120 | + const YearResult bsp_year=bsp_solver.compute_year(2025,nullptr); |
| 121 | + ASSERT_EQ(series_year.lun_phase.size(),bsp_year.lun_phase.size()); |
| 122 | + |
| 123 | + for(std::size_t i=0;i<series_year.lun_phase.size();++i){ |
| 124 | + const MoonPhMon&series_item=series_year.lun_phase[i]; |
| 125 | + const MoonPhMon&bsp_item=bsp_year.lun_phase[i]; |
| 126 | + SCOPED_TRACE(i); |
| 127 | + expect_close_jd(series_item.new_moon.toUtcJD(),bsp_item.new_moon.toUtcJD(), |
| 128 | + kLunarPhaseTolSec); |
| 129 | + expect_close_jd(series_item.fst_qtr.toUtcJD(),bsp_item.fst_qtr.toUtcJD(), |
| 130 | + kLunarPhaseTolSec); |
| 131 | + expect_close_jd(series_item.full_moon.toUtcJD(),bsp_item.full_moon.toUtcJD(), |
| 132 | + kLunarPhaseTolSec); |
| 133 | + expect_close_jd(series_item.lst_qtr.toUtcJD(),bsp_item.lst_qtr.toUtcJD(), |
| 134 | + kLunarPhaseTolSec); |
| 135 | + } |
| 136 | +#endif |
| 137 | +} |
| 138 | + |
| 139 | +TEST(SeriesVsBspCalendar, LunarMonthsTrackReference){ |
| 140 | + if(!has_reference_bsp()){ |
| 141 | + GTEST_SKIP()<<"requires LUNAR_TEST_BSP or a repo-local BSP file"; |
| 142 | + } |
| 143 | +#if !LUNAR_ENABLE_SERIES_FALLBACK |
| 144 | + GTEST_SKIP()<<"requires series fallback"; |
| 145 | +#else |
| 146 | + const std::string ref_bsp=reference_bsp(); |
| 147 | + EphRead series_eph("series"); |
| 148 | + EphRead bsp_eph(ref_bsp); |
| 149 | + LunCal6 series_calc(series_eph); |
| 150 | + LunCal6 bsp_calc(bsp_eph); |
| 151 | + |
| 152 | + const std::vector<LunarMonth>&series_months=series_calc.get_months(2025); |
| 153 | + const std::vector<LunarMonth>&bsp_months=bsp_calc.get_months(2025); |
| 154 | + ASSERT_EQ(series_months.size(),bsp_months.size()); |
| 155 | + |
| 156 | + for(std::size_t i=0;i<series_months.size();++i){ |
| 157 | + const LunarMonth&series_item=series_months[i]; |
| 158 | + const LunarMonth&bsp_item=bsp_months[i]; |
| 159 | + SCOPED_TRACE(series_item.label); |
| 160 | + EXPECT_EQ(series_item.month_no,bsp_item.month_no); |
| 161 | + EXPECT_EQ(series_item.is_leap,bsp_item.is_leap); |
| 162 | + EXPECT_EQ(series_item.label,bsp_item.label); |
| 163 | + expect_close_jd(series_item.start_dt.toUtcJD(),bsp_item.start_dt.toUtcJD(), |
| 164 | + kLunarMonthTolSec); |
| 165 | + expect_close_jd(series_item.end_dt.toUtcJD(),bsp_item.end_dt.toUtcJD(), |
| 166 | + kLunarMonthTolSec); |
| 167 | + } |
| 168 | +#endif |
| 169 | +} |
| 170 | + |
| 171 | +TEST(SeriesVsBspCoreDay, StableDatesTrackReference){ |
| 172 | + if(!has_reference_bsp()){ |
| 173 | + GTEST_SKIP()<<"requires LUNAR_TEST_BSP or a repo-local BSP file"; |
| 174 | + } |
| 175 | +#if !LUNAR_ENABLE_SERIES_FALLBACK |
| 176 | + GTEST_SKIP()<<"requires series fallback"; |
| 177 | +#else |
| 178 | + const std::string ref_bsp=reference_bsp(); |
| 179 | + for(const char*date_text : kStableDates){ |
| 180 | + const DayResult series_day= |
| 181 | + lunar::core::compute_day(make_day_opt("series",date_text)); |
| 182 | + const DayResult bsp_day= |
| 183 | + lunar::core::compute_day(make_day_opt(ref_bsp,date_text)); |
| 184 | + SCOPED_TRACE(date_text); |
| 185 | + expect_same_lunar_date(series_day.at_data.lunar_date,bsp_day.at_data.lunar_date); |
| 186 | + EXPECT_EQ(series_day.at_data.phase_name,bsp_day.at_data.phase_name); |
| 187 | + EXPECT_EQ(series_day.at_data.waxing,bsp_day.at_data.waxing); |
| 188 | + EXPECT_NEAR(series_day.at_data.ill_pct,bsp_day.at_data.ill_pct,kIllPctTol); |
| 189 | + EXPECT_NEAR(series_day.at_data.elong_deg,bsp_day.at_data.elong_deg, |
| 190 | + kAngleTolDeg); |
| 191 | + } |
| 192 | +#endif |
| 193 | +} |
| 194 | + |
| 195 | +TEST(SeriesVsBspGanzhi, StableDatesTrackReference){ |
| 196 | + if(!has_reference_bsp()){ |
| 197 | + GTEST_SKIP()<<"requires LUNAR_TEST_BSP or a repo-local BSP file"; |
| 198 | + } |
| 199 | +#if !LUNAR_ENABLE_SERIES_FALLBACK |
| 200 | + GTEST_SKIP()<<"requires series fallback"; |
| 201 | +#else |
| 202 | + const std::string ref_bsp=reference_bsp(); |
| 203 | + for(const char*date_text : kStableDates){ |
| 204 | + const lunar::core::GanzhiSummary series_sum= |
| 205 | + lunar::core::compute_ganzhi(make_ganzhi_opt("series",date_text)); |
| 206 | + const lunar::core::GanzhiSummary bsp_sum= |
| 207 | + lunar::core::compute_ganzhi(make_ganzhi_opt(ref_bsp,date_text)); |
| 208 | + SCOPED_TRACE(date_text); |
| 209 | + expect_same_gz(series_sum.year,bsp_sum.year); |
| 210 | + expect_same_gz(series_sum.month,bsp_sum.month); |
| 211 | + expect_same_gz(series_sum.day,bsp_sum.day); |
| 212 | + } |
| 213 | +#endif |
| 214 | +} |
| 215 | + |
| 216 | +TEST(SeriesVsBspLunarEclipse, TotalEclipseTracksReference){ |
| 217 | + if(!has_reference_bsp()){ |
| 218 | + GTEST_SKIP()<<"requires LUNAR_TEST_BSP or a repo-local BSP file"; |
| 219 | + } |
| 220 | +#if !LUNAR_ENABLE_SERIES_FALLBACK |
| 221 | + GTEST_SKIP()<<"requires series fallback"; |
| 222 | +#else |
| 223 | + const double jd_tdb=TimeScale::utc_to_tdb(cst_to_utc_jd(2025,9,7)); |
| 224 | + const std::string ref_bsp=reference_bsp(); |
| 225 | + EphRead series_eph("series"); |
| 226 | + EphRead bsp_eph(ref_bsp); |
| 227 | + LunarEclipse series_ecl; |
| 228 | + LunarEclipse bsp_ecl; |
| 229 | + ASSERT_TRUE(calc_lunar_eclipse(series_eph,jd_tdb,&series_ecl)); |
| 230 | + ASSERT_TRUE(calc_lunar_eclipse(bsp_eph,jd_tdb,&bsp_ecl)); |
| 231 | + ASSERT_TRUE(series_ecl.has); |
| 232 | + ASSERT_TRUE(bsp_ecl.has); |
| 233 | + EXPECT_EQ(series_ecl.type,bsp_ecl.type); |
| 234 | + expect_close_jd(series_ecl.jd_tdb_p1,bsp_ecl.jd_tdb_p1,kEclipseTimeTolSec); |
| 235 | + expect_close_jd(series_ecl.jd_tdb_u1,bsp_ecl.jd_tdb_u1,kEclipseTimeTolSec); |
| 236 | + expect_close_jd(series_ecl.jd_tdb_max,bsp_ecl.jd_tdb_max,kEclipseTimeTolSec); |
| 237 | + expect_close_jd(series_ecl.jd_tdb_u4,bsp_ecl.jd_tdb_u4,kEclipseTimeTolSec); |
| 238 | + expect_close_jd(series_ecl.jd_tdb_p4,bsp_ecl.jd_tdb_p4,kEclipseTimeTolSec); |
| 239 | + EXPECT_NEAR(series_ecl.pen_mag,bsp_ecl.pen_mag,kEclipseScalarTol); |
| 240 | + EXPECT_NEAR(series_ecl.umb_mag,bsp_ecl.umb_mag,kEclipseScalarTol); |
| 241 | + EXPECT_NEAR(series_ecl.gamma,bsp_ecl.gamma,kEclipseScalarTol); |
| 242 | + EXPECT_NEAR(series_ecl.lib.l_deg,bsp_ecl.lib.l_deg,kAngleTolDeg); |
| 243 | + EXPECT_NEAR(series_ecl.lib.b_deg,bsp_ecl.lib.b_deg,kAngleTolDeg); |
| 244 | + EXPECT_NEAR(series_ecl.lib.c_deg,bsp_ecl.lib.c_deg,kAngleTolDeg); |
| 245 | +#endif |
| 246 | +} |
| 247 | + |
| 248 | +TEST(SeriesVsBspSolarEclipse, TotalEclipseTracksReference){ |
| 249 | + if(!has_reference_bsp()){ |
| 250 | + GTEST_SKIP()<<"requires LUNAR_TEST_BSP or a repo-local BSP file"; |
| 251 | + } |
| 252 | +#if !LUNAR_ENABLE_SERIES_FALLBACK |
| 253 | + GTEST_SKIP()<<"requires series fallback"; |
| 254 | +#else |
| 255 | + const double jd_tdb=TimeScale::utc_to_tdb(cst_to_utc_jd(2026,8,12)); |
| 256 | + const std::string ref_bsp=reference_bsp(); |
| 257 | + EphRead series_eph("series"); |
| 258 | + EphRead bsp_eph(ref_bsp); |
| 259 | + SolarEclipse series_ecl; |
| 260 | + SolarEclipse bsp_ecl; |
| 261 | + ASSERT_TRUE(calc_solar_eclipse(series_eph,jd_tdb,&series_ecl)); |
| 262 | + ASSERT_TRUE(calc_solar_eclipse(bsp_eph,jd_tdb,&bsp_ecl)); |
| 263 | + ASSERT_TRUE(series_ecl.has); |
| 264 | + ASSERT_TRUE(bsp_ecl.has); |
| 265 | + EXPECT_EQ(series_ecl.type,bsp_ecl.type); |
| 266 | + expect_close_jd(series_ecl.jd_tdb_c1,bsp_ecl.jd_tdb_c1,kEclipseTimeTolSec); |
| 267 | + expect_close_jd(series_ecl.jd_tdb_c2,bsp_ecl.jd_tdb_c2,kEclipseTimeTolSec); |
| 268 | + expect_close_jd(series_ecl.jd_tdb_max,bsp_ecl.jd_tdb_max,kEclipseTimeTolSec); |
| 269 | + expect_close_jd(series_ecl.jd_tdb_c3,bsp_ecl.jd_tdb_c3,kEclipseTimeTolSec); |
| 270 | + expect_close_jd(series_ecl.jd_tdb_c4,bsp_ecl.jd_tdb_c4,kEclipseTimeTolSec); |
| 271 | + EXPECT_NEAR(series_ecl.mag,bsp_ecl.mag,kEclipseScalarTol); |
| 272 | + EXPECT_NEAR(series_ecl.obscuration,bsp_ecl.obscuration,kEclipseScalarTol); |
| 273 | + EXPECT_NEAR(series_ecl.gamma,bsp_ecl.gamma,kEclipseScalarTol); |
| 274 | + EXPECT_NEAR(series_ecl.sep_max_deg,bsp_ecl.sep_max_deg,kAngleTolDeg); |
| 275 | +#endif |
| 276 | +} |
0 commit comments