2026-08-06 12:00:56 +08:00
|
|
|
package basic
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"math"
|
|
|
|
|
"testing"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestPlanetEventsRejectNonFiniteQuery(t *testing.T) {
|
|
|
|
|
nan := math.NaN()
|
|
|
|
|
events := []struct {
|
|
|
|
|
name string
|
|
|
|
|
fn func(float64) float64
|
|
|
|
|
}{
|
|
|
|
|
{"Mars conjunction", NextMarsConjunction},
|
|
|
|
|
{"Jupiter conjunction", NextJupiterConjunction},
|
|
|
|
|
{"Saturn conjunction", NextSaturnConjunction},
|
|
|
|
|
{"Uranus conjunction", NextUranusConjunction},
|
|
|
|
|
{"Neptune conjunction", NextNeptuneConjunction},
|
|
|
|
|
{"Mercury conjunction", NextMercuryConjunction},
|
|
|
|
|
{"Mercury greatest elongation", NextMercuryGreatestElongation},
|
|
|
|
|
{"Venus greatest elongation", NextVenusGreatestElongation},
|
|
|
|
|
{"Venus station", NextVenusProgradeToRetrograde},
|
|
|
|
|
}
|
|
|
|
|
for _, tc := range events {
|
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
if got := tc.fn(nan); !math.IsNaN(got) {
|
|
|
|
|
t.Fatalf("got %v, want NaN", got)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPlanetObservationRejectsNonFiniteQuery(t *testing.T) {
|
|
|
|
|
nan := math.NaN()
|
|
|
|
|
culminations := []struct {
|
|
|
|
|
name string
|
|
|
|
|
fn func(float64, float64, float64) float64
|
|
|
|
|
}{
|
|
|
|
|
{"Mercury", MercuryCulminationTime},
|
|
|
|
|
{"Venus", VenusCulminationTime},
|
|
|
|
|
{"Mars", MarsCulminationTime},
|
|
|
|
|
{"Jupiter", JupiterCulminationTime},
|
|
|
|
|
{"Saturn", SaturnCulminationTime},
|
|
|
|
|
{"Uranus", UranusCulminationTime},
|
|
|
|
|
{"Neptune", NeptuneCulminationTime},
|
|
|
|
|
}
|
|
|
|
|
for _, tc := range culminations {
|
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
if got := tc.fn(nan, 0, 0); !math.IsNaN(got) {
|
|
|
|
|
t.Fatalf("got %v, want NaN", got)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
if got := OrbitCulminationTime(nan, 0, 0, 0, 0, OrbitElements{}); !math.IsNaN(got) {
|
|
|
|
|
t.Fatalf("OrbitCulminationTime got %v, want NaN", got)
|
|
|
|
|
}
|
|
|
|
|
if got := MercuryCulminationTimeN(nan, 0, 0, 16); !math.IsNaN(got) {
|
|
|
|
|
t.Fatalf("MercuryCulminationTimeN got %v, want NaN", got)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if _, err := MarsRiseTime(nan, 0, 0, 0, 0, 0); err != ErrInvalidObservationInput {
|
|
|
|
|
t.Fatalf("MarsRiseTime error = %v, want ErrInvalidObservationInput", err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := MercuryRiseTimeN(nan, 0, 0, 0, 0, 0, 16); err != ErrInvalidObservationInput {
|
|
|
|
|
t.Fatalf("MercuryRiseTimeN error = %v, want ErrInvalidObservationInput", err)
|
|
|
|
|
}
|
|
|
|
|
if got := JupiterGalileanSatelliteState(nan, 1); !math.IsNaN(got.X) {
|
|
|
|
|
t.Fatalf("JupiterGalileanSatelliteState X = %v, want NaN", got.X)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSharedObservationRejectsNonFiniteQuery(t *testing.T) {
|
|
|
|
|
nan := math.NaN()
|
|
|
|
|
if got := MoonCulminationTime(nan, 0, 0, 0); !math.IsNaN(got) {
|
|
|
|
|
t.Fatalf("MoonCulminationTime got %v, want NaN", got)
|
|
|
|
|
}
|
|
|
|
|
if got := StarCulminationTime(nan, 0, 0, 0); !math.IsNaN(got) {
|
|
|
|
|
t.Fatalf("StarCulminationTime got %v, want NaN", got)
|
|
|
|
|
}
|
|
|
|
|
if got := CalcMoonSHByJDE(nan, 0); !math.IsNaN(got) {
|
|
|
|
|
t.Fatalf("CalcMoonSHByJDE got %v, want NaN", got)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if _, err := GetSunRiseTime(nan, 0, 0, 0, 0, 0); err != ErrInvalidObservationInput {
|
|
|
|
|
t.Fatalf("GetSunRiseTime error = %v, want ErrInvalidObservationInput", err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := GetMoonRiseTime(nan, 0, 0, 0, 0, 0); err != ErrInvalidObservationInput {
|
|
|
|
|
t.Fatalf("GetMoonRiseTime error = %v, want ErrInvalidObservationInput", err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := StarRiseTime(nan, 0, 0, 0, 0, 0, 0, false); err != ErrInvalidObservationInput {
|
|
|
|
|
t.Fatalf("StarRiseTime error = %v, want ErrInvalidObservationInput", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-09-17 12:27:40 +08:00
|
|
|
|
|
|
|
|
func TestMoonMaximumDeclinationEventsRejectNonFiniteQuery(t *testing.T) {
|
|
|
|
|
nonFinite := []struct {
|
|
|
|
|
name string
|
|
|
|
|
jd float64
|
|
|
|
|
}{
|
|
|
|
|
{"NaN", math.NaN()},
|
|
|
|
|
{"+Inf", math.Inf(1)},
|
|
|
|
|
{"-Inf", math.Inf(-1)},
|
|
|
|
|
}
|
|
|
|
|
events := []struct {
|
|
|
|
|
name string
|
|
|
|
|
fn func(float64) DeclinationEvent
|
|
|
|
|
}{
|
|
|
|
|
{"LastMoonMaximumNorthDeclination", LastMoonMaximumNorthDeclination},
|
|
|
|
|
{"NextMoonMaximumNorthDeclination", NextMoonMaximumNorthDeclination},
|
|
|
|
|
{"ClosestMoonMaximumNorthDeclination", ClosestMoonMaximumNorthDeclination},
|
|
|
|
|
{"LastMoonMaximumSouthDeclination", LastMoonMaximumSouthDeclination},
|
|
|
|
|
{"NextMoonMaximumSouthDeclination", NextMoonMaximumSouthDeclination},
|
|
|
|
|
{"ClosestMoonMaximumSouthDeclination", ClosestMoonMaximumSouthDeclination},
|
|
|
|
|
}
|
|
|
|
|
for _, event := range events {
|
|
|
|
|
for _, query := range nonFinite {
|
|
|
|
|
// A non-finite query used to seed an out-of-range cycle index and spin in the
|
|
|
|
|
// sampling sweep forever; it must return the zero event instead.
|
|
|
|
|
got := event.fn(query.jd)
|
2026-09-23 18:55:12 +08:00
|
|
|
if got.JD != 0 || got.Declination != 0 {
|
2026-09-17 12:27:40 +08:00
|
|
|
t.Fatalf("%s(%s) = %+v, want zero event", event.name, query.name, got)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The finite path must keep working.
|
|
|
|
|
const jd = 2460310.5
|
|
|
|
|
north := NextMoonMaximumNorthDeclination(jd)
|
2026-09-23 18:55:12 +08:00
|
|
|
if !(north.JD > jd) || north.Declination == 0 {
|
2026-09-17 12:27:40 +08:00
|
|
|
t.Fatalf("NextMoonMaximumNorthDeclination(%v) = %+v, want a later event", jd, north)
|
|
|
|
|
}
|
|
|
|
|
south := LastMoonMaximumSouthDeclination(jd)
|
2026-09-23 18:55:12 +08:00
|
|
|
if !(south.JD <= jd) || south.Declination == 0 {
|
2026-09-17 12:27:40 +08:00
|
|
|
t.Fatalf("LastMoonMaximumSouthDeclination(%v) = %+v, want an earlier event", jd, south)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestOuterPlanetStationEventsRejectNonFiniteQuery(t *testing.T) {
|
|
|
|
|
nonFinite := []struct {
|
|
|
|
|
name string
|
|
|
|
|
jd float64
|
|
|
|
|
}{
|
|
|
|
|
{"NaN", math.NaN()},
|
|
|
|
|
{"+Inf", math.Inf(1)},
|
|
|
|
|
{"-Inf", math.Inf(-1)},
|
|
|
|
|
}
|
|
|
|
|
events := []struct {
|
|
|
|
|
name string
|
|
|
|
|
fn func(float64) float64
|
|
|
|
|
}{
|
|
|
|
|
{"Mercury", NextMercuryProgradeToRetrograde},
|
|
|
|
|
{"Mercury retrograde", NextMercuryRetrograde},
|
|
|
|
|
{"Venus", NextVenusProgradeToRetrograde},
|
|
|
|
|
{"Venus retrograde", NextVenusRetrograde},
|
|
|
|
|
{"Mars", NextMarsProgradeToRetrograde},
|
|
|
|
|
{"Jupiter", NextJupiterProgradeToRetrograde},
|
|
|
|
|
{"Saturn", NextSaturnProgradeToRetrograde},
|
|
|
|
|
{"Uranus", NextUranusProgradeToRetrograde},
|
|
|
|
|
{"Neptune", NextNeptuneProgradeToRetrograde},
|
|
|
|
|
{"Mars opposition", NextMarsOpposition},
|
|
|
|
|
{"Jupiter opposition", NextJupiterOpposition},
|
|
|
|
|
{"Saturn opposition", NextSaturnOpposition},
|
|
|
|
|
{"Uranus opposition", NextUranusOpposition},
|
|
|
|
|
{"Neptune opposition", NextNeptuneOpposition},
|
|
|
|
|
{"Mars eastern quadrature", NextMarsEasternQuadrature},
|
|
|
|
|
{"Jupiter eastern quadrature", NextJupiterEasternQuadrature},
|
|
|
|
|
{"Saturn eastern quadrature", NextSaturnEasternQuadrature},
|
|
|
|
|
{"Uranus eastern quadrature", NextUranusEasternQuadrature},
|
|
|
|
|
{"Neptune eastern quadrature", NextNeptuneEasternQuadrature},
|
|
|
|
|
}
|
|
|
|
|
for _, event := range events {
|
|
|
|
|
for _, query := range nonFinite {
|
|
|
|
|
if got := event.fn(query.jd); !math.IsNaN(got) {
|
|
|
|
|
t.Fatalf("%s(%s) = %v, want NaN", event.name, query.name, got)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestOuterPlanetLastStationEventsRejectNonFiniteQuery(t *testing.T) {
|
|
|
|
|
events := []struct {
|
|
|
|
|
name string
|
|
|
|
|
fn func(float64) float64
|
|
|
|
|
}{
|
|
|
|
|
{"LastMarsProgradeToRetrograde", LastMarsProgradeToRetrograde},
|
|
|
|
|
{"LastMarsRetrogradeToPrograde", LastMarsRetrogradeToPrograde},
|
|
|
|
|
{"LastJupiterProgradeToRetrograde", LastJupiterProgradeToRetrograde},
|
|
|
|
|
{"LastJupiterRetrogradeToPrograde", LastJupiterRetrogradeToPrograde},
|
|
|
|
|
{"LastSaturnProgradeToRetrograde", LastSaturnProgradeToRetrograde},
|
|
|
|
|
{"LastSaturnRetrogradeToPrograde", LastSaturnRetrogradeToPrograde},
|
|
|
|
|
{"LastUranusProgradeToRetrograde", LastUranusProgradeToRetrograde},
|
|
|
|
|
{"LastUranusRetrogradeToPrograde", LastUranusRetrogradeToPrograde},
|
|
|
|
|
{"LastNeptuneProgradeToRetrograde", LastNeptuneProgradeToRetrograde},
|
|
|
|
|
{"LastNeptuneRetrogradeToPrograde", LastNeptuneRetrogradeToPrograde},
|
|
|
|
|
}
|
|
|
|
|
for _, event := range events {
|
|
|
|
|
if got := event.fn(math.NaN()); !math.IsNaN(got) {
|
|
|
|
|
t.Fatalf("%s(NaN) = %v, want NaN", event.name, got)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 查询落在事件之后 1 秒:首候选必须被侧向不变量拒绝,第二候选也必须通过同一不变量。
|
|
|
|
|
func TestOuterPlanetStationSecondCandidateKeepsSideInvariant(t *testing.T) {
|
|
|
|
|
const offsetSeconds = 1.0
|
|
|
|
|
cases := []struct {
|
|
|
|
|
name string
|
|
|
|
|
fn func(float64) float64
|
|
|
|
|
}{
|
|
|
|
|
{"Mars", NextMarsProgradeToRetrograde},
|
|
|
|
|
{"Jupiter", NextJupiterProgradeToRetrograde},
|
|
|
|
|
{"Saturn", NextSaturnProgradeToRetrograde},
|
|
|
|
|
{"Uranus", NextUranusProgradeToRetrograde},
|
|
|
|
|
{"Neptune", NextNeptuneProgradeToRetrograde},
|
|
|
|
|
}
|
|
|
|
|
for _, tc := range cases {
|
2026-09-23 18:55:12 +08:00
|
|
|
event := tc.fn(JDCalc(2025, 1, 1))
|
2026-09-17 12:27:40 +08:00
|
|
|
if math.IsNaN(event) {
|
|
|
|
|
t.Fatalf("%s: no station event found", tc.name)
|
|
|
|
|
}
|
|
|
|
|
query := event + offsetSeconds/86400.0
|
2026-09-23 18:55:12 +08:00
|
|
|
got := tc.fn(UTC2TT(query))
|
2026-09-17 12:27:40 +08:00
|
|
|
if math.IsNaN(got) {
|
2026-09-23 18:55:12 +08:00
|
|
|
t.Fatalf("%s: second candidate rejected as NaN at %s", tc.name, JD2Date(query))
|
2026-09-17 12:27:40 +08:00
|
|
|
}
|
|
|
|
|
if got < query-stationQueryToleranceUT {
|
|
|
|
|
t.Fatalf("%s: returned %.9f before the query %.9f", tc.name, got, query)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|