Files
astro/basic/local_ephemeris_test.go
T

158 lines
6.8 KiB
Go
Raw Normal View History

package basic
import (
"math"
"testing"
"time"
)
func TestSolarEclipseLocalEphemerisBoundedError(t *testing.T) {
events := []struct {
name string
seed float64
}{
{"2010-01-15", JDCalc(2010, 1, 15)},
{"2014-04-29", JDCalc(2014, 4, 29)},
{"2023-04-20", JDCalc(2023, 4, 20)},
{"2031-05-21", JDCalc(2031, 5, 21)},
{"2309-06-09", JDCalc(2309, 6, 9)},
}
for _, event := range events {
t.Run(event.name, func(t *testing.T) {
center := CalcMoonSHByJDE(event.seed, 0)
ephemeris := newSolarEclipseLocalEphemeris(center)
for offset := -4.5; offset <= 4.5; offset += 0.25 {
jd := center + offset/24
approxSun, approxMoon, ok := ephemeris.equatorialAt(jd)
if !ok {
t.Fatalf("interpolator rejected in-window time %.3fh", offset)
}
exactSun, exactMoon := solarEclipseSunMoonEquatorial(jd)
for name, pair := range map[string][2][3]float64{
"sun": {solarEclipseLLRToXYZ(approxSun[0], approxSun[1], approxSun[2]), solarEclipseLLRToXYZ(exactSun[0], exactSun[1], exactSun[2])},
"moon": {solarEclipseLLRToXYZ(approxMoon[0], approxMoon[1], approxMoon[2]), solarEclipseLLRToXYZ(exactMoon[0], exactMoon[1], exactMoon[2])},
} {
if errorKM := vectorDifferenceKM(pair[0], pair[1]); errorKM > 1 {
t.Fatalf("%s interpolation error at %.3fh = %.6f km", name, offset, errorKM)
}
}
}
if _, _, ok := ephemeris.equatorialAt(center + 6.1/24); ok {
t.Fatal("interpolator should reject times outside its bounded window")
}
})
}
}
func TestOccultationLocalEphemerisBoundedError(t *testing.T) {
star := hr4799OccultationCoordinateForTest()
starTT := occultationTimeToTT(time.Date(2025, 6, 5, 20, 0, 0, 0, time.UTC))
starEphemeris := newStarOccultationLocalEphemeris(starTT, star)
for offset := -4.5; offset <= 4.5; offset += 0.25 {
jd := starTT + offset/24
got, ok := starEphemeris.stateAt(jd)
if !ok {
t.Fatalf("star interpolator rejected in-window time %.3fh", offset)
}
want := starOccultationEphemerisStateAt(jd, star)
if difference := angularSeparationDegrees(got.moonRA, got.moonDec, want.moonRA, want.moonDec); difference > 1e-5 {
t.Fatalf("moon direction interpolation error at %.3fh = %.9f deg", offset, difference)
}
if difference := angularSeparationDegrees(got.starRA, got.starDec, want.starRA, want.starDec); difference > 1e-5 {
t.Fatalf("star direction interpolation error at %.3fh = %.9f deg", offset, difference)
}
}
config, ok := planetOccultationConfigFor(OccultationSaturn)
if !ok {
t.Fatal("Saturn occultation config is unavailable")
}
planetTT := occultationTimeToTT(time.Date(2024, 8, 21, 2, 40, 0, 0, time.UTC))
planetEphemeris := newPlanetOccultationLocalEphemeris(planetTT, config)
for offset := -4.5; offset <= 4.5; offset += 0.25 {
jd := planetTT + offset/24
got, ok := planetEphemeris.stateAt(jd)
if !ok {
t.Fatalf("planet interpolator rejected in-window time %.3fh", offset)
}
want := planetOccultationEphemerisStateAt(jd, config)
if difference := angularSeparationDegrees(got.moonRA, got.moonDec, want.moonRA, want.moonDec); difference > 1e-5 {
t.Fatalf("moon direction interpolation error at %.3fh = %.9f deg", offset, difference)
}
if difference := angularSeparationDegrees(got.planetRA, got.planetDec, want.planetRA, want.planetDec); difference > 1e-5 {
t.Fatalf("planet direction interpolation error at %.3fh = %.9f deg", offset, difference)
}
if math.Abs(got.planetDistanceKM-want.planetDistanceKM) > 100 {
t.Fatalf("planet distance interpolation error at %.3fh = %.6f km", offset, math.Abs(got.planetDistanceKM-want.planetDistanceKM))
}
}
}
func TestOccultationLocalEphemerisFullWindowBoundedError(t *testing.T) {
star := hr4799OccultationCoordinateForTest()
starTT := occultationTimeToTT(time.Date(2025, 6, 5, 20, 0, 0, 0, time.UTC))
starEphemeris := newStarOccultationLocalEphemeris(starTT, star)
config, ok := planetOccultationConfigFor(OccultationSaturn)
if !ok {
t.Fatal("Saturn occultation config is unavailable")
}
planetTT := occultationTimeToTT(time.Date(2024, 8, 21, 2, 40, 0, 0, time.UTC))
planetEphemeris := newPlanetOccultationLocalEphemeris(planetTT, config)
for _, offset := range []float64{-47.5, -40, -24, 24, 40, 47.5} {
starJD := starTT + offset/24
gotStar, starOK := starEphemeris.stateAt(starJD)
wantStar := starOccultationEphemerisStateAt(starJD, star)
if !starOK {
t.Fatalf("star interpolator rejected in-window time %.3fh", offset)
}
if difference := angularSeparationDegrees(gotStar.moonRA, gotStar.moonDec, wantStar.moonRA, wantStar.moonDec); difference > 1e-5 {
t.Fatalf("star-event moon direction error at %.3fh = %.9f deg", offset, difference)
}
if difference := angularSeparationDegrees(gotStar.starRA, gotStar.starDec, wantStar.starRA, wantStar.starDec); difference > 1e-5 {
t.Fatalf("star direction error at %.3fh = %.9f deg", offset, difference)
}
planetJD := planetTT + offset/24
gotPlanet, planetOK := planetEphemeris.stateAt(planetJD)
wantPlanet := planetOccultationEphemerisStateAt(planetJD, config)
if !planetOK {
t.Fatalf("planet interpolator rejected in-window time %.3fh", offset)
}
if difference := angularSeparationDegrees(gotPlanet.moonRA, gotPlanet.moonDec, wantPlanet.moonRA, wantPlanet.moonDec); difference > 1e-5 {
t.Fatalf("planet-event moon direction error at %.3fh = %.9f deg", offset, difference)
}
if difference := angularSeparationDegrees(gotPlanet.planetRA, gotPlanet.planetDec, wantPlanet.planetRA, wantPlanet.planetDec); difference > 1e-5 {
t.Fatalf("planet direction error at %.3fh = %.9f deg", offset, difference)
}
if difference := math.Abs(gotPlanet.planetDistanceKM - wantPlanet.planetDistanceKM); difference > 100 {
t.Fatalf("planet distance error at %.3fh = %.6f km", offset, difference)
}
}
}
func vectorDifferenceKM(a, b [3]float64) float64 {
return math.Sqrt((a[0]-b[0])*(a[0]-b[0]) + (a[1]-b[1])*(a[1]-b[1]) + (a[2]-b[2])*(a[2]-b[2]))
}
// 矢量回读的赤经必须落在 [0,360):atan2 给 (−180,180],而精确分支
// (LoBoToRaDec、starMeanToApparentRaDec)与 rise/set 的矢量回读都在 [0,360),
// 不归一会让同一时刻的密集/精确状态相差 360 度,误导比较与日志。
func TestOccultationPathVectorRaDecKeepsNormalizedRA(t *testing.T) {
for _, want := range []float64{0.5, 90, 189.1975, 270, 359.9} {
vector := occultationPathRaDecVector(want, -5.8, 2.5)
got, dec, distance, ok := occultationPathVectorRaDec(vector)
if !ok {
t.Fatalf("RA %.4f 的回读失败", want)
}
if math.Abs(got-want) > 1e-9 {
t.Fatalf("RA %.4f 回读为 %.4f,want [0,360) 内的同一读数", want, got)
}
if math.Abs(dec+5.8) > 1e-9 || math.Abs(distance-2.5) > 1e-12 {
t.Fatalf("RA %.4f 的赤纬/距离 = %.6f / %.6f,want -5.8 / 2.5", want, dec, distance)
}
}
if _, _, _, ok := occultationPathVectorRaDec(occultationPathVector{}); ok {
t.Fatal("零矢量应判为无效")
}
}