Files
astro/basic/planet_input_guard_test.go
T

231 lines
7.9 KiB
Go
Raw Normal View History

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)
}
}
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)
if got.JD != 0 || got.Declination != 0 {
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)
if !(north.JD > jd) || north.Declination == 0 {
t.Fatalf("NextMoonMaximumNorthDeclination(%v) = %+v, want a later event", jd, north)
}
south := LastMoonMaximumSouthDeclination(jd)
if !(south.JD <= jd) || south.Declination == 0 {
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 {
event := tc.fn(JDCalc(2025, 1, 1))
if math.IsNaN(event) {
t.Fatalf("%s: no station event found", tc.name)
}
query := event + offsetSeconds/86400.0
got := tc.fn(UTC2TT(query))
if math.IsNaN(got) {
t.Fatalf("%s: second candidate rejected as NaN at %s", tc.name, JD2Date(query))
}
if got < query-stationQueryToleranceUT {
t.Fatalf("%s: returned %.9f before the query %.9f", tc.name, got, query)
}
}
}