16c62a97d5
- 新增时标、ΔT 模型、质心时间与 UT1 支持 - 改进日月食、月掩、行星事件及路径边界计算 - 完善恒星三维自行与动态距离传播 - 扩展 SVG、GeoJSON、KML 输出与底层距离换算工具 - 整理中英文手册、示例资源及回归测试
157 lines
5.7 KiB
Go
157 lines
5.7 KiB
Go
package basic
|
|
|
|
import (
|
|
"math"
|
|
"testing"
|
|
)
|
|
|
|
// 大距口径契约:返回时刻必须是公开“视距角”函数(MercurySunElongation/VenusSunElongation)的极大,
|
|
// 而不是忽略光行差与视位置修正的真距角极大。
|
|
|
|
type elongationObjectiveCase struct {
|
|
name string
|
|
elongate func(float64) float64
|
|
next func(float64) float64
|
|
last func(float64) float64
|
|
}
|
|
|
|
var elongationObjectiveCases = []elongationObjectiveCase{
|
|
{"Mercury", MercurySunElongation, NextMercuryGreatestElongation, LastMercuryGreatestElongation},
|
|
{"Venus", VenusSunElongation, NextVenusGreatestElongation, LastVenusGreatestElongation},
|
|
}
|
|
|
|
func elongationObjectiveSeeds() []float64 {
|
|
seeds := make([]float64, 0, 24)
|
|
for year := 2024; year <= 2027; year++ {
|
|
for month := 1; month <= 12; month += 2 {
|
|
seeds = append(seeds, JDCalc(year, month, 1))
|
|
}
|
|
}
|
|
return seeds
|
|
}
|
|
|
|
func elongationObjectiveTruth(elongate func(float64) float64, eventUT float64) float64 {
|
|
left, right := eventUT-30.0/1440.0, eventUT+30.0/1440.0
|
|
for i := 0; i < 200; i++ {
|
|
third := (right - left) / 3
|
|
if elongate(UTC2TT(left+third)) <= elongate(UTC2TT(right-third)) {
|
|
left += third
|
|
continue
|
|
}
|
|
right -= third
|
|
}
|
|
return (left + right) / 2
|
|
}
|
|
|
|
func TestGreatestElongationMatchesPublicElongationExtremum(t *testing.T) {
|
|
const toleranceMinutes = 2.0
|
|
for _, tc := range elongationObjectiveCases {
|
|
for _, seed := range elongationObjectiveSeeds() {
|
|
for _, direction := range []struct {
|
|
name string
|
|
fn func(float64) float64
|
|
}{{"next", tc.next}, {"last", tc.last}} {
|
|
eventUT := direction.fn(seed)
|
|
if math.IsNaN(eventUT) {
|
|
t.Fatalf("%s %s at %.1f returned NaN", tc.name, direction.name, seed)
|
|
}
|
|
truth := elongationObjectiveTruth(tc.elongate, eventUT)
|
|
if deviation := math.Abs(eventUT-truth) * 1440; deviation > toleranceMinutes {
|
|
t.Fatalf("%s %s at %.1f = %.9f deviates %.2f min from the public elongation maximum",
|
|
tc.name, direction.name, seed, eventUT, deviation)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 金星大距的侧向判定必须与水星同口径(0.1 s 事件容差),不能沿用站事件的 0.5 s。
|
|
//
|
|
// 这些入口的查询参数是力学时,返回时刻是世界时:
|
|
// 查询“事件后 0.3 秒”要把世界时事件先换成力学时再加偏移。
|
|
func TestGreatestElongationLateralToleranceIsEventTolerance(t *testing.T) {
|
|
const offsetSeconds = 0.3
|
|
cases := []struct {
|
|
name string
|
|
next func(float64) float64
|
|
last func(float64) float64
|
|
}{
|
|
{"MercuryEast", NextMercuryGreatestElongationEast, LastMercuryGreatestElongationEast},
|
|
{"MercuryWest", NextMercuryGreatestElongationWest, LastMercuryGreatestElongationWest},
|
|
{"VenusEast", NextVenusGreatestElongationEast, LastVenusGreatestElongationEast},
|
|
{"VenusWest", NextVenusGreatestElongationWest, LastVenusGreatestElongationWest},
|
|
}
|
|
seeds := []float64{JDCalc(2024, 3, 1), JDCalc(2025, 7, 1), JDCalc(2026, 11, 1)}
|
|
tolerance := exactQueryTTToleranceUT
|
|
for _, tc := range cases {
|
|
for _, seed := range seeds {
|
|
event := tc.next(seed)
|
|
if math.IsNaN(event) {
|
|
t.Fatalf("%s at %.1f returned NaN", tc.name, seed)
|
|
}
|
|
eventTT := UTC2TT(event)
|
|
afterUT := event + offsetSeconds/86400.0
|
|
if got := tc.next(eventTT + offsetSeconds/86400.0); got < afterUT-tolerance {
|
|
t.Fatalf("%s: Next at event+%.1f s = %.9f returned an event %.3f s before the query",
|
|
tc.name, offsetSeconds, got, (afterUT-got)*86400)
|
|
}
|
|
beforeUT := event - offsetSeconds/86400.0
|
|
if got := tc.last(eventTT - offsetSeconds/86400.0); got > beforeUT+tolerance {
|
|
t.Fatalf("%s: Last at event-%.1f s = %.9f returned an event %.3f s after the query",
|
|
tc.name, offsetSeconds, got, (got-beforeUT)*86400)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 无东西侧参数的大距只算查询所在窗口那一侧;这里用「两侧都算再取极值」作差分对照,输出必须逐位一致。
|
|
func TestGreatestElongationAnySideKeepsBothSideExtremum(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
next func(float64) float64
|
|
last func(float64) float64
|
|
eastNext, westNext func(float64) float64
|
|
eastLast, westLast func(float64) float64
|
|
}{
|
|
{"Mercury", NextMercuryGreatestElongation, LastMercuryGreatestElongation,
|
|
NextMercuryGreatestElongationEast, NextMercuryGreatestElongationWest,
|
|
LastMercuryGreatestElongationEast, LastMercuryGreatestElongationWest},
|
|
{"Venus", NextVenusGreatestElongation, LastVenusGreatestElongation,
|
|
NextVenusGreatestElongationEast, NextVenusGreatestElongationWest,
|
|
LastVenusGreatestElongationEast, LastVenusGreatestElongationWest},
|
|
}
|
|
for _, tc := range cases {
|
|
for jd := JDCalc(2024, 1, 1); jd <= JDCalc(2027, 1, 1); jd += 14 {
|
|
east, west := tc.eastNext(jd), tc.westNext(jd)
|
|
want := east
|
|
if !sameEventJD(east, west) {
|
|
want = earliestFiniteEventUT(east, west)
|
|
}
|
|
if got := tc.next(jd); got != want {
|
|
t.Fatalf("%s Next(%.1f) = %.12f, want %.12f (both-side reference)", tc.name, jd, got, want)
|
|
}
|
|
east, west = tc.eastLast(jd), tc.westLast(jd)
|
|
want = east
|
|
if !sameEventJD(east, west) {
|
|
want = latestFiniteEventUT(east, west)
|
|
}
|
|
if got := tc.last(jd); got != want {
|
|
t.Fatalf("%s Last(%.1f) = %.12f, want %.12f (both-side reference)", tc.name, jd, got, want)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEventAggregationKeepsFiniteCandidate(t *testing.T) {
|
|
const jd = 2460310.5
|
|
if got := earliestFiniteEventUT(math.NaN(), jd); got != jd {
|
|
t.Fatalf("earliestFiniteEventUT(NaN, jd) = %v, want %v", got, jd)
|
|
}
|
|
if got := latestFiniteEventUT(jd, math.NaN()); got != jd {
|
|
t.Fatalf("latestFiniteEventUT(jd, NaN) = %v, want %v", got, jd)
|
|
}
|
|
if got := earliestFiniteEventUT(math.NaN(), math.Inf(1)); !math.IsNaN(got) {
|
|
t.Fatalf("earliestFiniteEventUT(NaN, +Inf) = %v, want NaN", got)
|
|
}
|
|
}
|