2bf8478639
- 新增日月食中心带、偏食带、阴影足迹、等时线、食分线及升落边界计算,支持极区与混合食拓扑 - 新增日食单时刻阴影求解器、站心状态查询、批量采样和 ΔT 覆盖接口 - 重构恒星与行星月掩路径,补充有限盘面接触、站心修正、掩带宽度、极区投影及升落边界 - 扩展 SVG 与 GeoJSON 输出,支持详细面板、全球/极区/地球投影、边界闭合、时间标记和拓扑签名 - 扩展日月食候选搜索、局地搜索、沙罗序列预计算与范围外推,补充系列锚点和成员一致性校验 - 补齐古历纪年、儒略历独有闰日、多公历候选、历法改革跨日及精确日期运算接口 - 优化 ΔT、章动、恒星时、月球地平线、事件根搜索和本地星历缓存,降低重复计算开销并提升边界稳定
173 lines
9.2 KiB
Go
173 lines
9.2 KiB
Go
// 低精度日月基线与高精度派生量的 n 截断收敛契约。
|
|
package astro_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"math"
|
|
"os"
|
|
"testing"
|
|
|
|
"b612.me/astro/basic"
|
|
"b612.me/astro/planet"
|
|
)
|
|
|
|
type lowPrecisionSunSnapshot struct {
|
|
LoBits uint64 `json:"lo_bits"`
|
|
MBits uint64 `json:"m_bits"`
|
|
EccBits uint64 `json:"ecc_bits"`
|
|
PeriBits uint64 `json:"peri_bits"`
|
|
MidBits uint64 `json:"mid_bits"`
|
|
TrueLoBits uint64 `json:"true_lo_bits"`
|
|
ApparentLoBits uint64 `json:"apparent_lo_bits"`
|
|
ApparentRaBits uint64 `json:"apparent_ra_bits"`
|
|
ApparentDecBits uint64 `json:"apparent_dec_bits"`
|
|
TrueRaBits uint64 `json:"true_ra_bits"`
|
|
TrueDecBits uint64 `json:"true_dec_bits"`
|
|
DistanceBits uint64 `json:"distance_bits"`
|
|
}
|
|
|
|
type lowPrecisionMoonSnapshot struct {
|
|
LoBits uint64 `json:"lo_bits"`
|
|
SunAngleBits uint64 `json:"sun_angle_bits"`
|
|
MBits uint64 `json:"m_bits"`
|
|
LonXBits uint64 `json:"lonx_bits"`
|
|
IBits uint64 `json:"i_bits"`
|
|
RBits uint64 `json:"r_bits"`
|
|
BBits uint64 `json:"b_bits"`
|
|
TrueLoBits uint64 `json:"true_lo_bits"`
|
|
TrueBoBits uint64 `json:"true_bo_bits"`
|
|
AwayBits uint64 `json:"away_bits"`
|
|
ApparentLoBits uint64 `json:"apparent_lo_bits"`
|
|
TrueRaBits uint64 `json:"true_ra_bits"`
|
|
TrueDecBits uint64 `json:"true_dec_bits"`
|
|
}
|
|
|
|
type lowPrecisionSample struct {
|
|
UTC string `json:"utc"`
|
|
TTJD float64 `json:"tt_jd"`
|
|
Sun lowPrecisionSunSnapshot `json:"sun"`
|
|
Moon lowPrecisionMoonSnapshot `json:"moon"`
|
|
}
|
|
|
|
func loadLowPrecisionSamples(t *testing.T) []lowPrecisionSample {
|
|
t.Helper()
|
|
|
|
data, err := os.ReadFile("testdata/low_precision_sun_moon_baseline.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var samples []lowPrecisionSample
|
|
if err := json.Unmarshal(data, &samples); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(samples) == 0 {
|
|
t.Fatal("empty low precision baseline samples")
|
|
}
|
|
return samples
|
|
}
|
|
|
|
func TestLowPrecisionSunMoonBaselineRegression(t *testing.T) {
|
|
samples := loadLowPrecisionSamples(t)
|
|
|
|
assertBits := func(t *testing.T, name, utc string, got float64, want uint64) {
|
|
t.Helper()
|
|
if math.Float64bits(got) != want {
|
|
t.Fatalf("%s regression at %s", name, utc)
|
|
}
|
|
}
|
|
|
|
for _, sample := range samples {
|
|
jd := sample.TTJD
|
|
|
|
assertBits(t, "planet.SunLo", sample.UTC, planet.SunLo(jd), sample.Sun.LoBits)
|
|
assertBits(t, "basic.SunLo", sample.UTC, basic.SunLo(jd), sample.Sun.LoBits)
|
|
assertBits(t, "planet.SunM", sample.UTC, planet.SunM(jd), sample.Sun.MBits)
|
|
assertBits(t, "basic.SunM", sample.UTC, basic.SunM(jd), sample.Sun.MBits)
|
|
assertBits(t, "planet.Earthe", sample.UTC, planet.Earthe(jd), sample.Sun.EccBits)
|
|
assertBits(t, "basic.Earthe", sample.UTC, basic.Earthe(jd), sample.Sun.EccBits)
|
|
assertBits(t, "planet.EarthPI", sample.UTC, planet.EarthPI(jd), sample.Sun.PeriBits)
|
|
assertBits(t, "basic.EarthPI", sample.UTC, basic.EarthPI(jd), sample.Sun.PeriBits)
|
|
assertBits(t, "planet.SunMidFun", sample.UTC, planet.SunMidFun(jd), sample.Sun.MidBits)
|
|
assertBits(t, "basic.SunMidFun", sample.UTC, basic.SunMidFun(jd), sample.Sun.MidBits)
|
|
assertBits(t, "planet.SunTrueLo", sample.UTC, planet.SunTrueLo(jd), sample.Sun.TrueLoBits)
|
|
assertBits(t, "basic.SunTrueLo", sample.UTC, basic.SunTrueLo(jd), sample.Sun.TrueLoBits)
|
|
assertBits(t, "planet.SunApparentLo", sample.UTC, planet.SunApparentLo(jd), sample.Sun.ApparentLoBits)
|
|
assertBits(t, "basic.SunApparentLo", sample.UTC, basic.SunApparentLo(jd), sample.Sun.ApparentLoBits)
|
|
assertBits(t, "basic.SunApparentRa", sample.UTC, basic.SunApparentRa(jd), sample.Sun.ApparentRaBits)
|
|
assertBits(t, "basic.SunApparentDec", sample.UTC, basic.SunApparentDec(jd), sample.Sun.ApparentDecBits)
|
|
assertBits(t, "basic.SunTrueRa", sample.UTC, basic.SunTrueRa(jd), sample.Sun.TrueRaBits)
|
|
assertBits(t, "basic.SunTrueDec", sample.UTC, basic.SunTrueDec(jd), sample.Sun.TrueDecBits)
|
|
assertBits(t, "planet.Distance", sample.UTC, planet.Distance(jd), sample.Sun.DistanceBits)
|
|
assertBits(t, "basic.Distance", sample.UTC, basic.Distance(jd), sample.Sun.DistanceBits)
|
|
assertBits(t, "planet.MoonLo", sample.UTC, planet.MoonLo(jd), sample.Moon.LoBits)
|
|
assertBits(t, "basic.MoonLo", sample.UTC, basic.MoonLo(jd), sample.Moon.LoBits)
|
|
assertBits(t, "planet.SunMoonAngle", sample.UTC, planet.SunMoonAngle(jd), sample.Moon.SunAngleBits)
|
|
assertBits(t, "basic.SunMoonAngle", sample.UTC, basic.SunMoonAngle(jd), sample.Moon.SunAngleBits)
|
|
assertBits(t, "planet.MoonM", sample.UTC, planet.MoonM(jd), sample.Moon.MBits)
|
|
assertBits(t, "basic.MoonM", sample.UTC, basic.MoonM(jd), sample.Moon.MBits)
|
|
assertBits(t, "planet.MoonLonX", sample.UTC, planet.MoonLonX(jd), sample.Moon.LonXBits)
|
|
assertBits(t, "basic.MoonLonX", sample.UTC, basic.MoonLonX(jd), sample.Moon.LonXBits)
|
|
assertBits(t, "planet.MoonI", sample.UTC, planet.MoonI(jd), sample.Moon.IBits)
|
|
assertBits(t, "basic.MoonI", sample.UTC, basic.MoonI(jd), sample.Moon.IBits)
|
|
assertBits(t, "planet.MoonR", sample.UTC, planet.MoonR(jd), sample.Moon.RBits)
|
|
assertBits(t, "basic.MoonR", sample.UTC, basic.MoonR(jd), sample.Moon.RBits)
|
|
assertBits(t, "planet.MoonB", sample.UTC, planet.MoonB(jd), sample.Moon.BBits)
|
|
assertBits(t, "basic.MoonB", sample.UTC, basic.MoonB(jd), sample.Moon.BBits)
|
|
assertBits(t, "planet.MoonTrueLo", sample.UTC, planet.MoonTrueLo(jd), sample.Moon.TrueLoBits)
|
|
assertBits(t, "basic.MoonTrueLo", sample.UTC, basic.MoonTrueLo(jd), sample.Moon.TrueLoBits)
|
|
assertBits(t, "planet.MoonTrueBo", sample.UTC, planet.MoonTrueBo(jd), sample.Moon.TrueBoBits)
|
|
assertBits(t, "basic.MoonTrueBo", sample.UTC, basic.MoonTrueBo(jd), sample.Moon.TrueBoBits)
|
|
assertBits(t, "planet.MoonAway", sample.UTC, planet.MoonAway(jd), sample.Moon.AwayBits)
|
|
assertBits(t, "basic.MoonAway", sample.UTC, basic.MoonAway(jd), sample.Moon.AwayBits)
|
|
assertBits(t, "basic.MoonApparentLo", sample.UTC, basic.MoonApparentLo(jd), sample.Moon.ApparentLoBits)
|
|
assertBits(t, "basic.MoonTrueRa", sample.UTC, basic.MoonTrueRa(jd), sample.Moon.TrueRaBits)
|
|
assertBits(t, "basic.MoonTrueDec", sample.UTC, basic.MoonTrueDec(jd), sample.Moon.TrueDecBits)
|
|
}
|
|
}
|
|
|
|
func derivedTruncationCases(samples []lowPrecisionSample) []truncationCase {
|
|
jdOf := func(index int) float64 { return samples[index].TTJD }
|
|
lon := 116.391
|
|
lat := 39.907
|
|
tz := 8.0
|
|
// withSite 把带观测点经纬度与时区的入口适配成 jd 探针。
|
|
withSite := func(f func(jd, lon, lat, tz float64, n int) float64) func(float64, int) float64 {
|
|
return func(jd float64, n int) float64 { return f(jd, lon, lat, tz, n) }
|
|
}
|
|
withSiteRaDec := func(f func(jd, lon, lat, tz float64, n int) (float64, float64)) func(float64, int) (float64, float64) {
|
|
return func(jd float64, n int) (float64, float64) { return f(jd, lon, lat, tz, n) }
|
|
}
|
|
|
|
// 上限按 48 个采样点实测最坏值放宽 5 倍以上给出,随 n 递减。
|
|
return []truncationCase{
|
|
scalarTruncation("HSunTrueLoN", jdOf, basic.HSunTrueLoN, true, []float64{0.15, 0.05, 0.02, 0.006}),
|
|
scalarTruncation("HSunTrueBoN", jdOf, basic.HSunTrueBoN, true, []float64{1e-3, 5e-4, 3e-4, 1.5e-4}),
|
|
scalarTruncation("HSunApparentLoN", jdOf, basic.HSunApparentLoN, true, []float64{0.15, 0.05, 0.02, 0.006}),
|
|
scalarTruncation("SunLoGXCN", jdOf, basic.SunLoGXCN, true, []float64{1e-5, 2e-6, 1e-6, 3e-7}),
|
|
scalarTruncation("EarthAwayN", jdOf, basic.EarthAwayN, false, []float64{1e-3, 3e-4, 1e-4, 3e-5}),
|
|
scalarTruncation("HSunApparentRaN", jdOf, basic.HSunApparentRaN, true, []float64{0.15, 0.05, 0.02, 0.006}),
|
|
scalarTruncation("HSunTrueRaN", jdOf, basic.HSunTrueRaN, true, []float64{0.15, 0.05, 0.02, 0.006}),
|
|
scalarTruncation("HSunApparentDecN", jdOf, basic.HSunApparentDecN, true, []float64{0.05, 0.02, 0.008, 0.003}),
|
|
scalarTruncation("HSunTrueDecN", jdOf, basic.HSunTrueDecN, true, []float64{0.05, 0.02, 0.008, 0.003}),
|
|
pairTruncation("HSunApparentRaDecN", jdOf, basic.HSunApparentRaDecN, []float64{0.15, 0.05, 0.02, 0.006}),
|
|
scalarTruncation("HMoonTrueLoN", jdOf, basic.HMoonTrueLoN, true, []float64{5, 2.5, 1, 0.25}),
|
|
scalarTruncation("HMoonTrueBoN", jdOf, basic.HMoonTrueBoN, true, []float64{3, 0.75, 0.15, 0.06}),
|
|
scalarTruncation("HMoonAwayN", jdOf, basic.HMoonAwayN, false, []float64{40000, 7000, 2500, 400}),
|
|
scalarTruncation("HMoonApparentLoN", jdOf, basic.HMoonApparentLoN, true, []float64{5, 2.5, 1, 0.25}),
|
|
scalarTruncation("HMoonTrueRaN", jdOf, basic.HMoonTrueRaN, true, []float64{5, 2.5, 1, 0.3}),
|
|
scalarTruncation("HMoonTrueDecN", jdOf, basic.HMoonTrueDecN, true, []float64{3, 1, 0.4, 0.1}),
|
|
pairTruncation("HMoonTrueRaDecN", jdOf, basic.HMoonTrueRaDecN, []float64{5, 2.5, 1, 0.3}),
|
|
scalarTruncation("HMoonApparentRaN", jdOf, withSite(basic.HMoonApparentRaN), true, []float64{5, 2.5, 1, 0.3}),
|
|
scalarTruncation("HMoonApparentDecN", jdOf, withSite(basic.HMoonApparentDecN), true, []float64{4, 1, 0.3, 0.1}),
|
|
pairTruncation("HMoonApparentRaDecN", jdOf, withSiteRaDec(basic.HMoonApparentRaDecN), []float64{5, 2.5, 1, 0.3}),
|
|
scalarTruncation("HMoonAzimuthN", jdOf, withSite(basic.HMoonAzimuthN), true, []float64{9, 4, 2, 0.9}),
|
|
scalarTruncation("HMoonHeightN", jdOf, withSite(basic.HMoonHeightN), true, []float64{4, 2, 0.7, 0.15}),
|
|
}
|
|
}
|
|
|
|
func TestDerivedHighPrecisionTruncationConverges(t *testing.T) {
|
|
samples := loadLowPrecisionSamples(t)
|
|
assertTruncationConverges(t, derivedTruncationCases(samples), len(samples))
|
|
}
|