astro/basic/jupiter_satellite_contact_events_test.go
starainrt 3ffdbe0034
feat: 扩展天文计算能力
- 新增日食、月食、本地可见性、中心线、半影区域、SVG 图示与沙罗周期信息
- 新增行星冲合、留、方照、物理星历、视直径、相位、亮肢角、轨道节点等计算
- 新增木星伽利略卫星位置、现象与接触事件计算
- 新增恒星星表、星座判定、自行修正与观测辅助能力
- 新增 coord、formula、orbit、sundial、lite/sun、lite/moon 等扩展包
- 完善农历年号、月相英文别名、视差角、大气质量、折射、日晷与双星计算
- 增加 NASA、JPL Horizons、IMCCE 等回归测试数据与基线测试
- 重构基础算法文件组织,补充大量公开 API 注释和语义回归测试
- 更新中文和英文 README,补充示例、精度说明、SVG 配图
2026-05-01 22:38:44 +08:00

102 lines
3.9 KiB
Go

package basic
import (
"math"
"testing"
"time"
)
func TestJupiterGalileanPhenomenonContactEventsAgainstIMCCEBaseline(t *testing.T) {
records := loadGalileanEventBaseline(t)
maxStartDiff := 0.0
maxEndDiff := 0.0
maxStartDurationDiff := 0.0
maxEndDurationDiff := 0.0
for _, record := range records {
startUTC := mustParseRFC3339Nano(t, record.StartUTC)
endUTC := mustParseRFC3339Nano(t, record.EndUTC)
queryMid := startUTC.Add(endUTC.Sub(startUTC) / 2)
phenomenonType := parseBasicGalileanPhenomenonType(t, record.Type)
event := ClosestJupiterGalileanPhenomenonContactEvent(Date2JDE(queryMid.UTC()), record.Satellite, phenomenonType)
if !event.Valid {
t.Fatalf("%s invalid contact event", record.Label)
}
gotStart := JDE2DateByZone(event.Disappearance.Start, time.UTC, false)
gotEnd := JDE2DateByZone(event.Reappearance.Start, time.UTC, false)
startDiff := math.Abs(gotStart.Sub(startUTC).Seconds())
endDiff := math.Abs(gotEnd.Sub(endUTC).Seconds())
startDurationDiff := math.Abs((event.Disappearance.End-event.Disappearance.Start)*86400 - record.StartDurationMinutes*60)
endDurationDiff := math.Abs((event.Reappearance.End-event.Reappearance.Start)*86400 - record.EndDurationMinutes*60)
if startDiff > maxStartDiff {
maxStartDiff = startDiff
}
if endDiff > maxEndDiff {
maxEndDiff = endDiff
}
if startDurationDiff > maxStartDurationDiff {
maxStartDurationDiff = startDurationDiff
}
if endDurationDiff > maxEndDurationDiff {
maxEndDurationDiff = endDurationDiff
}
if startDiff > galileanContactTimeToleranceSeconds(phenomenonType) {
t.Fatalf("%s disappearance start mismatch: got %s want %s", record.Label, gotStart.Format(time.RFC3339Nano), startUTC.Format(time.RFC3339Nano))
}
if endDiff > galileanContactTimeToleranceSeconds(phenomenonType) {
t.Fatalf("%s reappearance start mismatch: got %s want %s", record.Label, gotEnd.Format(time.RFC3339Nano), endUTC.Format(time.RFC3339Nano))
}
if startDurationDiff > galileanContactDurationToleranceSeconds(phenomenonType) {
t.Fatalf("%s disappearance duration mismatch: got %.1fs want %.1fs", record.Label, (event.Disappearance.End-event.Disappearance.Start)*86400, record.StartDurationMinutes*60)
}
if endDurationDiff > galileanContactDurationToleranceSeconds(phenomenonType) {
t.Fatalf("%s reappearance duration mismatch: got %.1fs want %.1fs", record.Label, (event.Reappearance.End-event.Reappearance.Start)*86400, record.EndDurationMinutes*60)
}
if !(event.Disappearance.Start <= event.Disappearance.ModelCrossing && event.Disappearance.ModelCrossing <= event.Disappearance.End) {
t.Fatalf("%s contact ordering invalid", record.Label)
}
if !(event.Reappearance.Start <= event.Reappearance.ModelCrossing && event.Reappearance.ModelCrossing <= event.Reappearance.End) {
t.Fatalf("%s reappearance ordering invalid", record.Label)
}
fullEvent := ClosestJupiterGalileanPhenomenonEvent(Date2JDE(queryMid.UTC()), record.Satellite, phenomenonType)
if phenomenonType != JupiterGalileanShadowTransit {
if math.Abs(event.Disappearance.ModelCrossing-fullEvent.Start)*86400 > 2 {
t.Fatalf("%s disappearance model crossing mismatch", record.Label)
}
if math.Abs(event.Reappearance.ModelCrossing-fullEvent.End)*86400 > 2 {
t.Fatalf("%s reappearance model crossing mismatch", record.Label)
}
}
}
t.Logf(
"galilean contact baseline max diff: start=%.1fs end=%.1fs startDur=%.1fs endDur=%.1fs",
maxStartDiff,
maxEndDiff,
maxStartDurationDiff,
maxEndDurationDiff,
)
}
func galileanContactTimeToleranceSeconds(phenomenonType JupiterGalileanPhenomenonType) float64 {
switch phenomenonType {
case JupiterGalileanShadowTransit:
return 90.0
case JupiterGalileanEclipse:
return 180.0
default:
return 120.0
}
}
func galileanContactDurationToleranceSeconds(phenomenonType JupiterGalileanPhenomenonType) float64 {
switch phenomenonType {
case JupiterGalileanShadowTransit:
return 15.0
default:
return 90.0
}
}