Files
astro/basic/sidereal_memo_test.go
T
b612 2bf8478639 feat: 完善日月食与月掩几何链路并扩展历法接口
- 新增日月食中心带、偏食带、阴影足迹、等时线、食分线及升落边界计算,支持极区与混合食拓扑
- 新增日食单时刻阴影求解器、站心状态查询、批量采样和 ΔT 覆盖接口
- 重构恒星与行星月掩路径,补充有限盘面接触、站心修正、掩带宽度、极区投影及升落边界
- 扩展 SVG 与 GeoJSON 输出,支持详细面板、全球/极区/地球投影、边界闭合、时间标记和拓扑签名
- 扩展日月食候选搜索、局地搜索、沙罗序列预计算与范围外推,补充系列锚点和成员一致性校验
- 补齐古历纪年、儒略历独有闰日、多公历候选、历法改革跨日及精确日期运算接口
- 优化 ΔT、章动、恒星时、月球地平线、事件根搜索和本地星历缓存,降低重复计算开销并提升边界稳定
2026-09-17 12:27:40 +08:00

137 lines
5.0 KiB
Go

package basic
import (
"math"
"sync"
"testing"
)
// directApparentSiderealTime2006 是记忆化之前的原始表达式,作为逐位对照。
// directApparentSiderealTime2006 is the pre-memo expression, kept as a bit-exact reference.
func directApparentSiderealTime2006(jd float64) float64 {
return MeanSiderealTime2006(jd) + Nutation2000Bi(jd)*math.Cos(TrueObliquity(jd)*math.Pi/180)/15
}
// directApparentSiderealTime1982 是 1982 模型的原始表达式,同样作为逐位对照。
// directApparentSiderealTime1982 is the pre-refactor 1982 expression, kept as a bit-exact reference.
func directApparentSiderealTime1982(jd float64) float64 {
return MeanSiderealTime1982(jd) + Nutation2000Bi(jd)*math.Cos(TrueObliquity(jd)*math.Pi/180)/15
}
func siderealMemoTestTimes() []float64 {
var times []float64
for _, year := range []int{-720, -100, 0, 1000, 1582, 1900, 2025, 2026, 3000, 5000} {
times = append(times, JDECalc(year, 3, 7.25), JDECalc(year, 9, 20.5), JDECalc(year, 12, 31.75))
}
return times
}
func TestApparentSiderealTimeMemoMatchesDirectSeries(t *testing.T) {
times := siderealMemoTestTimes()
firstPass := make([]float64, len(times))
for index, jd := range times {
got := ApparentSiderealTime2006(jd)
if want := directApparentSiderealTime2006(jd); got != want {
t.Fatalf("jd=%.6f sidereal=%v, want %v", jd, got, want)
}
if got1982, want1982 := ApparentSiderealTime1982(jd), directApparentSiderealTime1982(jd); got1982 != want1982 {
t.Fatalf("jd=%.6f sidereal1982=%v, want %v", jd, got1982, want1982)
}
firstPass[index] = got
}
// 逆序再查一遍:命中记忆表也必须与首次计算逐位一致。
// Query again in reverse: memo hits must stay bit-identical to the first evaluation.
for index := len(times) - 1; index >= 0; index-- {
if got := ApparentSiderealTime2006(times[index]); got != firstPass[index] {
t.Errorf("jd=%.6f memoized sidereal=%v, want %v", times[index], got, firstPass[index])
}
}
}
func TestApparentSiderealTimeMemoInvalidatedByDeltaTOverride(t *testing.T) {
original := GetDeltaTFn()
t.Cleanup(func() { SetDeltaTFn(original) })
jd := JDECalc(2025, 1, 5.5)
baseline := ApparentSiderealTime2006(jd)
if repeat := ApparentSiderealTime2006(jd); repeat != baseline {
t.Fatalf("memoized sidereal=%v, want stable %v", repeat, baseline)
}
SetDeltaTFn(func(date float64, isJDE bool) float64 { return 6000 })
shifted := ApparentSiderealTime2006(jd)
if shifted == baseline {
t.Fatalf("ΔT override replayed the memoized value %v", shifted)
}
if want := directApparentSiderealTime2006(jd); shifted != want {
t.Errorf("sidereal after ΔT override=%v, want %v", shifted, want)
}
SetDeltaTFn(DefaultDeltaTv2)
if restored := ApparentSiderealTime2006(jd); restored != baseline {
t.Errorf("sidereal after restoring ΔT=%v, want %v", restored, baseline)
}
}
func TestApparentSiderealTimeMemoIsRaceFree(t *testing.T) {
base := JDECalc(2025, 1, 5.5)
var wait sync.WaitGroup
for worker := 0; worker < 4; worker++ {
wait.Add(1)
go func(offset int) {
defer wait.Done()
for step := 0; step < 256; step++ {
jd := base + float64(step)*1e-4 + float64(offset)*1e-7
if got := ApparentSiderealTime2006(jd); got != directApparentSiderealTime2006(jd) {
t.Errorf("jd=%.9f sidereal=%v, want %v", jd, got, directApparentSiderealTime2006(jd))
return
}
}
}(worker)
}
wait.Wait()
}
// 记忆表只在 ΔT 世代未变时写入:世代必须在求值前采样,否则求值期间发生的 SetDeltaTFn
// 会把旧 ΔT 的结果打上新世代并长期回放。
func TestSiderealMemoRejectsStaleGenerationWrite(t *testing.T) {
original := GetDeltaTFn()
defer SetDeltaTFn(original)
resetSiderealMemo()
jd := 2460310.5
generation := siderealMemoGeneration()
SetDeltaTFn(func(date float64, isJd bool) float64 { return 200 })
siderealMemoStore(jd, 12345.0, generation)
if _, ok := siderealMemoLoad(jd); ok {
t.Fatalf("stale-generation write was accepted")
}
// 世代未变时正常写入并命中。
current := siderealMemoGeneration()
siderealMemoStore(jd, 12345.0, current)
value, ok := siderealMemoLoad(jd)
if !ok || value != 12345.0 {
t.Fatalf("current-generation write missed: value=%v ok=%v", value, ok)
}
}
// 同一批瞬时重复求值必须命中记忆表(否则扩表/世代逻辑等于没生效)。
func TestSiderealMemoHitsRepeatedInstants(t *testing.T) {
resetSiderealMemo()
const distinct = 8192
for offset := 0; offset < distinct; offset++ {
_ = ApparentSiderealTime2006(2460310.5 + float64(offset)*0.25)
}
hitsBefore, missesBefore, _ := siderealMemoStats()
if missesBefore < distinct || hitsBefore != 0 {
t.Fatalf("first pass should miss every distinct instant: hits=%d misses=%d", hitsBefore, missesBefore)
}
for offset := 0; offset < distinct; offset++ {
_ = ApparentSiderealTime2006(2460310.5 + float64(offset)*0.25)
}
hits, misses, _ := siderealMemoStats()
if hits-hitsBefore < distinct*9/10 {
t.Fatalf("second pass hit rate too low: hits=%d misses=%d (want >= 90%% of %d)",
hits-hitsBefore, misses-missesBefore, distinct)
}
}