// 被测契约:半解析模型在 1800–2200 年与旧级数相差不超过旧级数自身误差量级;n ≥ 0 走快速档且不劣于旧级数 n=16;越界返回 NaN,切回后逐位还原。 package planet import ( "math" "strconv" "testing" "b612.me/astro/internal/ephem" "b612.me/astro/internal/ephemtest" ) func useSemianalytic(t testing.TB) { prev := ephem.Active() ephem.SetActive(ephem.Semianalytic) t.Cleanup(func() { ephem.SetActive(prev) }) } func angleDiffArcsec(a, b float64) float64 { return math.Abs(math.Mod(a-b+540, 360)-180) * 3600 } type lbrDeg struct{ l, b, r float64 } func sampleAll(jds []float64) map[[2]int]lbrDeg { out := map[[2]int]lbrDeg{} for i, jd := range jds { for xt := -1; xt <= 7; xt++ { out[[2]int{xt, i}] = lbrDeg{WherePlanet(xt, 0, jd), WherePlanet(xt, 1, jd), WherePlanet(xt, 2, jd)} } out[[2]int{-9, i}] = lbrDeg{ math.Mod(WhereMoon(0, jd)*180/math.Pi, 360), WhereMoon(1, jd) * 180 / math.Pi, WhereMoon(2, jd), } } return out } func TestSemianalyticNearLegacyModernEra(t *testing.T) { var jds []float64 for jd := 2378496.5; jd <= 2524593.5; jd += 1461.37 { jds = append(jds, jd) } ephemtest.PinLegacy(t) legacy := sampleAll(jds) useSemianalytic(t) semi := sampleAll(jds) // 角秒;距离 AU(行星)或 km(月球,-9)。 tolA := map[int]float64{-1: 0.5, 0: 0.5, 1: 1, 2: 1, 3: 2, 4: 2, 5: 3, 6: 6, 7: 10, -9: 10} tolR := map[int]float64{-1: 2e-7, 0: 2e-7, 1: 2e-7, 2: 3e-7, 3: 1e-6, 4: 4e-6, 5: 1e-5, 6: 2e-4, 7: 2e-4, -9: 5} worst := map[int][3]float64{} for k, want := range legacy { got := semi[k] w := worst[k[0]] w[0] = math.Max(w[0], angleDiffArcsec(got.l, want.l)*math.Cos(want.b*math.Pi/180)) w[1] = math.Max(w[1], math.Abs(got.b-want.b)*3600) w[2] = math.Max(w[2], math.Abs(got.r-want.r)) worst[k[0]] = w } for xt, w := range worst { t.Logf("xt %d: max ΔL %.3f″ ΔB %.3f″ ΔR %.3g", xt, w[0], w[1], w[2]) if w[0] > tolA[xt] || w[1] > tolA[xt] || w[2] > tolR[xt] { t.Errorf("xt %d differs from legacy beyond expected truncation error", xt) } } } func TestSemianalyticDomainAndRestore(t *testing.T) { ephemtest.PinLegacy(t) jd := 2460000.5 before := WherePlanetN(3, 0, jd, 16) moonBefore := WhereMoonN(0, jd, 8) useSemianalytic(t) for _, c := range [][2]int{{-2, 0}, {8, 0}, {0, -1}, {0, 3}} { if v := WherePlanet(c[0], c[1], jd); !math.IsNaN(v) { t.Errorf("xt %d zn %d: want NaN, got %v", c[0], c[1], v) } } if WherePlanetN(3, 0, jd, 16) != WherePlanetN(3, 0, jd, 8) || WhereMoonN(0, jd, 16) != WhereMoonN(0, jd, 0) { t.Error("every n >= 0 must use the same fast tier") } if WherePlanetN(3, 0, jd, 16) == WherePlanetN(3, 0, jd, -1) { t.Error("n >= 0 did not switch to the fast tier") } ephem.SetActive(ephem.Legacy) if WherePlanetN(3, 0, jd, 16) != before || WhereMoonN(0, jd, 8) != moonBefore { t.Error("switching back did not restore legacy values bit for bit") } } // 快速档相对全表的最大差(角秒;月球另有 km),全表本身对 DE441 已在 0.02″ 内。 func TestSemianalyticFastTierNearFull(t *testing.T) { useSemianalytic(t) var worst [9]float64 worstKM := 0.0 for jd := -1931455.0; jd <= 5373545.0; jd += 3652.5 * 7.3 { for xt := 0; xt <= 7; xt++ { d := angleDiffArcsec(WherePlanetN(xt, 0, jd, 0), WherePlanetN(xt, 0, jd, -1))*math.Cos(WherePlanetN(xt, 1, jd, -1)*math.Pi/180) + math.Abs(WherePlanetN(xt, 1, jd, 0)-WherePlanetN(xt, 1, jd, -1))*3600 worst[xt] = math.Max(worst[xt], d) } ml := angleDiffArcsec(WhereMoonN(0, jd, 0)*180/math.Pi, WhereMoonN(0, jd, -1)*180/math.Pi) + math.Abs(WhereMoonN(1, jd, 0)-WhereMoonN(1, jd, -1))*206264.806 worst[8] = math.Max(worst[8], ml) worstKM = math.Max(worstKM, math.Abs(WhereMoonN(2, jd, 0)-WhereMoonN(2, jd, -1))) } limits := [9]float64{2, 0.6, 1, 8, 15, 60, 75, 20, 60} for i, w := range worst { t.Logf("slot %d: max %.2f″", i, w) if w > limits[i] { t.Errorf("slot %d: fast tier differs from full table by %.2f″ (limit %.0f″)", i, w, limits[i]) } } if worstKM > 20 { t.Errorf("moon distance: fast tier differs by %.1f km", worstKM) } } func BenchmarkWherePlanetSemianalytic(b *testing.B) { useSemianalytic(b) for _, n := range []int{-1, 16} { n := n b.Run("n="+strconv.Itoa(n), func(b *testing.B) { for xt := 0; xt <= 7; xt++ { xt := xt b.Run(strconv.Itoa(xt), func(b *testing.B) { for i := 0; i < b.N; i++ { jd := 2451545.0 + float64(i%7300)*100 _ = WherePlanetN(xt, 0, jd, n) + WherePlanetN(xt, 1, jd, n) + WherePlanetN(xt, 2, jd, n) } }) } b.Run("moon", func(b *testing.B) { for i := 0; i < b.N; i++ { jd := 2451545.0 + float64(i%7300)*10.3 _ = WhereMoonN(0, jd, n) + WhereMoonN(1, jd, n) + WhereMoonN(2, jd, n) } }) }) } }