2bf8478639
- 新增日月食中心带、偏食带、阴影足迹、等时线、食分线及升落边界计算,支持极区与混合食拓扑 - 新增日食单时刻阴影求解器、站心状态查询、批量采样和 ΔT 覆盖接口 - 重构恒星与行星月掩路径,补充有限盘面接触、站心修正、掩带宽度、极区投影及升落边界 - 扩展 SVG 与 GeoJSON 输出,支持详细面板、全球/极区/地球投影、边界闭合、时间标记和拓扑签名 - 扩展日月食候选搜索、局地搜索、沙罗序列预计算与范围外推,补充系列锚点和成员一致性校验 - 补齐古历纪年、儒略历独有闰日、多公历候选、历法改革跨日及精确日期运算接口 - 优化 ΔT、章动、恒星时、月球地平线、事件根搜索和本地星历缓存,降低重复计算开销并提升边界稳定
94 lines
3.8 KiB
Go
94 lines
3.8 KiB
Go
package basic
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// 折点补根的判据只能统计查询窗口内部的未成对端点:窗口边界上的裁剪端点没有任何折点
|
|
// 可以配对,把它们计入会让“未成对端点数严格下降”永远不成立。
|
|
func TestOccultationRiseSetFoldRecoveryCountsInteriorEndpoints(t *testing.T) {
|
|
zone := time.FixedZone("UTC+8", 8*3600)
|
|
start := time.Date(2025, time.July, 29, 0, 0, 0, 0, zone)
|
|
options := OccultationPathOptions{
|
|
Step: 20 * time.Minute, TargetSpacingKM: 900, RiseSetStep: time.Minute,
|
|
DisableFootprints: true,
|
|
}
|
|
paths, err := FindPlanetOccultationPaths(start, start.Add(24*time.Hour), OccultationMars, options)
|
|
if err != nil || len(paths) != 1 || !paths[0].HasTotalBand {
|
|
t.Fatalf("paths=%d err=%v, want one total-band event", len(paths), err)
|
|
}
|
|
path := paths[0]
|
|
config, _ := planetOccultationConfigFor(OccultationMars)
|
|
cache := newPlanetOccultationEventCache(config)
|
|
greatestTT := occultationTimeToTT(path.Greatest.Time)
|
|
cache.preparePathEphemeris(greatestTT, OccultationPathAlgorithmOptimized)
|
|
firstTT := occultationTimeToTT(path.TotalStart.Time)
|
|
lastTT := occultationTimeToTT(path.TotalEnd.Time)
|
|
curves, base, recovered := occultationRiseSetCurvesWithRecoveryReport(
|
|
firstTT, lastTT, greatestTT, options, start.Location(), cache.totalRiseSetCache,
|
|
)
|
|
if recovered == nil {
|
|
t.Fatal("fold recovery was never attempted on a graph with interior unpaired endpoints")
|
|
}
|
|
baseInterior := occultationRiseSetUnclosedEndpointCount(base, true, firstTT, lastTT)
|
|
recoveredInterior := occultationRiseSetUnclosedEndpointCount(recovered, true, firstTT, lastTT)
|
|
if baseInterior == 0 || recoveredInterior >= baseInterior {
|
|
t.Fatalf("interior unpaired endpoints base=%d recovered=%d, want a strict decrease",
|
|
baseInterior, recoveredInterior)
|
|
}
|
|
// 全局计数被窗口裁剪端点抬平:按它判定会丢弃这次重建。
|
|
baseGlobal := occultationRiseSetUnclosedEndpointCount(base, false, firstTT, lastTT)
|
|
recoveredGlobal := occultationRiseSetUnclosedEndpointCount(recovered, false, firstTT, lastTT)
|
|
if recoveredGlobal < baseGlobal {
|
|
t.Fatalf("global unpaired endpoints base=%d recovered=%d, sample no longer shows a window-truncated endpoint",
|
|
baseGlobal, recoveredGlobal)
|
|
}
|
|
if !reflect.DeepEqual(curves, recovered) {
|
|
t.Fatal("interior-endpoint test did not select the rebuilt phase graph")
|
|
}
|
|
}
|
|
|
|
func TestOccultationRiseSetWindowClippedEndpointIgnoredByInteriorCount(t *testing.T) {
|
|
firstTT := 2460000.0
|
|
lastTT := firstTT + 0.25
|
|
point := func(tt float64) OccultationPathPoint {
|
|
return OccultationPathPoint{Time: occultationTTToLocation(tt, time.UTC)}
|
|
}
|
|
curves := []OccultationRiseSetCurve{
|
|
{
|
|
Phase: RiseSetPhaseStart, Direction: RiseSetDirectionRise,
|
|
Segments: [][]OccultationPathPoint{
|
|
{point(lastTT), point(lastTT + 0.01)},
|
|
{point(firstTT + 0.02), point(firstTT + 0.03)},
|
|
},
|
|
},
|
|
}
|
|
if got := occultationRiseSetUnclosedEndpointCount(curves, false, firstTT, lastTT); got != 4 {
|
|
t.Fatalf("global unpaired endpoints=%d, want 4", got)
|
|
}
|
|
if got := occultationRiseSetUnclosedEndpointCount(curves, true, firstTT, lastTT); got != 2 {
|
|
t.Fatalf("interior unpaired endpoints=%d, want 2", got)
|
|
}
|
|
}
|
|
|
|
// stateAt 的分配预算:同一上下文内换站点只复用已分配的站点表,不再额外分配。
|
|
func BenchmarkOccultationRiseSetStateAt(b *testing.B) {
|
|
context := newOccultationRiseSetContext(
|
|
occultationTimeToTT(time.Date(2025, time.July, 29, 12, 0, 0, 0, time.UTC)),
|
|
100, 10, 384400, 200, -15, 149597870.7*2, 3389.5,
|
|
)
|
|
coordinates := make([][2]float64, 0, 64)
|
|
for index := 0; index < 64; index++ {
|
|
coordinates = append(coordinates, [2]float64{float64(index) * 1.7, float64(index%30) - 15})
|
|
}
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for index := 0; index < b.N; index++ {
|
|
for _, coordinate := range coordinates {
|
|
_ = context.stateAt(coordinate[0], coordinate[1])
|
|
}
|
|
}
|
|
}
|