2bf8478639
- 新增日月食中心带、偏食带、阴影足迹、等时线、食分线及升落边界计算,支持极区与混合食拓扑 - 新增日食单时刻阴影求解器、站心状态查询、批量采样和 ΔT 覆盖接口 - 重构恒星与行星月掩路径,补充有限盘面接触、站心修正、掩带宽度、极区投影及升落边界 - 扩展 SVG 与 GeoJSON 输出,支持详细面板、全球/极区/地球投影、边界闭合、时间标记和拓扑签名 - 扩展日月食候选搜索、局地搜索、沙罗序列预计算与范围外推,补充系列锚点和成员一致性校验 - 补齐古历纪年、儒略历独有闰日、多公历候选、历法改革跨日及精确日期运算接口 - 优化 ΔT、章动、恒星时、月球地平线、事件根搜索和本地星历缓存,降低重复计算开销并提升边界稳定
95 lines
4.5 KiB
Go
95 lines
4.5 KiB
Go
package basic
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestOccultationPathFrameBoundaryCacheMatchesUncachedSearch 固定 frame 级边界记忆化的语义:
|
|
// 记忆化只是复用同一 frame 上纯函数的计算结果,因此切点、可见 θ 区间与按中心角的 θ 区间
|
|
// 都必须与直接调用未缓存实现逐位一致,重复调用也必须稳定。
|
|
// TestOccultationPathFrameBoundaryCacheMatchesUncachedSearch pins the frame-level boundary memo
|
|
// semantics: memoization only reuses pure results for the same frame, so the tangency, the
|
|
// visible theta intervals and the per-center-angle interval must stay bit-identical to the
|
|
// uncached implementations, and repeated calls must be stable.
|
|
func TestOccultationPathFrameBoundaryCacheMatchesUncachedSearch(t *testing.T) {
|
|
config, ok := planetOccultationConfigFor(OccultationSaturn)
|
|
if !ok {
|
|
t.Fatal("Saturn occultation config is unavailable")
|
|
}
|
|
tt := occultationTimeToTT(time.Date(2024, time.August, 21, 2, 41, 36, 0, time.UTC))
|
|
cache := newPlanetOccultationEventCache(config)
|
|
|
|
cachedFrame, cachedOK := cache.outerFrameAt(tt)
|
|
directFrame, directOK := planetOccultationPathFrameAt(tt, config)
|
|
if cachedOK != directOK || !cachedOK {
|
|
t.Fatalf("frame availability cached=%v direct=%v, want both true", cachedOK, directOK)
|
|
}
|
|
if !occultationPathFrameGeometryEqual(cachedFrame, directFrame) {
|
|
t.Fatal("cached frame geometry differs from a direct frame")
|
|
}
|
|
if cachedFrame.boundary == nil {
|
|
t.Fatal("cached frame is missing its boundary memo")
|
|
}
|
|
|
|
wantPoint, wantTheta, wantOK := occultationPathBoundaryTangentUncached(directFrame)
|
|
gotPoint, gotTheta, gotOK := occultationPathBoundaryTangent(cachedFrame)
|
|
if gotOK != wantOK || gotPoint != wantPoint || gotTheta != wantTheta {
|
|
t.Fatalf("memoized tangency = (%.15g, %.15g, %v), want (%.15g, %.15g, %v)",
|
|
gotPoint, gotTheta, gotOK, wantPoint, wantTheta, wantOK)
|
|
}
|
|
againPoint, againTheta, againOK := occultationPathBoundaryTangent(cachedFrame)
|
|
if againOK != gotOK || againPoint != gotPoint || againTheta != gotTheta {
|
|
t.Fatal("repeated tangency query is not stable")
|
|
}
|
|
|
|
wantIntervals := occultationPathBoundaryThetaIntervalsUncached(directFrame)
|
|
gotIntervals := occultationPathBoundaryThetaIntervals(cachedFrame)
|
|
if len(gotIntervals) != len(wantIntervals) {
|
|
t.Fatalf("memoized theta intervals = %v, want %v", gotIntervals, wantIntervals)
|
|
}
|
|
for index := range gotIntervals {
|
|
if gotIntervals[index] != wantIntervals[index] {
|
|
t.Fatalf("memoized theta interval %d = %v, want %v", index, gotIntervals[index], wantIntervals[index])
|
|
}
|
|
}
|
|
if againIntervals := occultationPathBoundaryThetaIntervals(cachedFrame); len(againIntervals) != len(gotIntervals) {
|
|
t.Fatal("repeated theta-interval query is not stable")
|
|
}
|
|
|
|
cachedLeft, cachedRight, cachedIntervalOK := occultationPathBoundaryThetaInterval(cachedFrame, wantTheta)
|
|
directLeft, directRight, directIntervalOK := occultationPathBoundaryThetaInterval(directFrame, wantTheta)
|
|
if cachedIntervalOK != directIntervalOK || cachedLeft != directLeft || cachedRight != directRight {
|
|
t.Fatalf("memoized interval = (%.15g, %.15g, %v), want (%.15g, %.15g, %v)",
|
|
cachedLeft, cachedRight, cachedIntervalOK, directLeft, directRight, directIntervalOK)
|
|
}
|
|
repeatLeft, repeatRight, repeatOK := occultationPathBoundaryThetaInterval(cachedFrame, wantTheta)
|
|
if repeatOK != cachedIntervalOK || repeatLeft != cachedLeft || repeatRight != cachedRight {
|
|
t.Fatal("repeated per-angle interval query is not stable")
|
|
}
|
|
}
|
|
|
|
// TestOccultationPathZeroFrameComputesBoundaryWithoutCache 保证未挂记忆化的零值 frame 仍然
|
|
// 直接计算,不会被 nil 缓存跳过。
|
|
// TestOccultationPathZeroFrameComputesBoundaryWithoutCache keeps a frame without a memo working:
|
|
// a nil boundary cache must fall back to direct computation, not skip it.
|
|
func TestOccultationPathZeroFrameComputesBoundaryWithoutCache(t *testing.T) {
|
|
frame := occultationPathFrame{
|
|
moon: occultationPathVector{x: 384000},
|
|
axis: occultationPathVector{x: -1},
|
|
first: occultationPathVector{y: 1},
|
|
second: occultationPathVector{z: 1},
|
|
moonRadius: 0.0045,
|
|
targetRadius: 1e-5,
|
|
}
|
|
if frame.boundary != nil {
|
|
t.Fatal("fixture frame unexpectedly carries a boundary memo")
|
|
}
|
|
point, theta, ok := occultationPathBoundaryTangent(frame)
|
|
wantPoint, wantTheta, wantOK := occultationPathBoundaryTangentUncached(frame)
|
|
if ok != wantOK || point != wantPoint || theta != wantTheta {
|
|
t.Fatalf("uncached tangency = (%.15g, %.15g, %v), want (%.15g, %.15g, %v)",
|
|
point, theta, ok, wantPoint, wantTheta, wantOK)
|
|
}
|
|
}
|