154 lines
6.0 KiB
Go
154 lines
6.0 KiB
Go
|
|
package eclipse
|
||
|
|
|
||
|
|
import (
|
||
|
|
"math"
|
||
|
|
"reflect"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"b612.me/astro/basic"
|
||
|
|
"b612.me/astro/internal/ephem"
|
||
|
|
)
|
||
|
|
|
||
|
|
type p3LunarShiftEngine struct {
|
||
|
|
base *ephem.Instance
|
||
|
|
offset float64
|
||
|
|
}
|
||
|
|
|
||
|
|
func (engine p3LunarShiftEngine) State(target, center ephem.Body, jde float64) ([3]float64, [3]float64, error) {
|
||
|
|
return engine.base.State(target, center, jde+engine.offset)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (engine p3LunarShiftEngine) Apparent(target ephem.Body, jde float64) ([3]float64, float64, float64, error) {
|
||
|
|
return engine.base.Apparent(target, jde+engine.offset)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (engine p3LunarShiftEngine) Coverage(target ephem.Body) (float64, float64, bool) {
|
||
|
|
start, end, ok := engine.base.Coverage(target)
|
||
|
|
return start - engine.offset, end - engine.offset, ok
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestP3LunarQueriesPreserveNilAndLegacy(t *testing.T) {
|
||
|
|
previous := ephem.Active()
|
||
|
|
t.Cleanup(func() { ephem.SetActive(previous) })
|
||
|
|
date := time.Date(2022, 11, 8, 12, 0, 0, 0, time.UTC)
|
||
|
|
for _, model := range []ephem.Model{ephem.Legacy, ephem.Semianalytic} {
|
||
|
|
ephem.SetActive(model)
|
||
|
|
inst := ephem.NewModelInstance(model)
|
||
|
|
for _, query := range []struct {
|
||
|
|
old func(time.Time) (LunarEclipseInfo, bool)
|
||
|
|
new func(time.Time, *ephem.Instance) (LunarEclipseInfo, bool)
|
||
|
|
}{
|
||
|
|
{LunarEclipseOnDate, LunarEclipseOnDateWithInstance},
|
||
|
|
{LunarEclipseOnDateDanjon, LunarEclipseOnDateDanjonWithInstance},
|
||
|
|
{LunarEclipseOnDateChauvenet, LunarEclipseOnDateChauvenetWithInstance},
|
||
|
|
} {
|
||
|
|
want, wantOK := query.old(date)
|
||
|
|
got, gotOK := query.new(date, nil)
|
||
|
|
if gotOK != wantOK || !reflect.DeepEqual(got, want) {
|
||
|
|
t.Error("nil changed local-date query")
|
||
|
|
}
|
||
|
|
if model == ephem.Legacy {
|
||
|
|
got, gotOK = query.new(date, inst)
|
||
|
|
if gotOK != wantOK || !reflect.DeepEqual(got, want) {
|
||
|
|
t.Error("explicit Legacy changed local-date query")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for _, query := range []struct {
|
||
|
|
old func(time.Time) LunarEclipseInfo
|
||
|
|
new func(time.Time, *ephem.Instance) LunarEclipseInfo
|
||
|
|
}{
|
||
|
|
{LastLunarEclipse, LastLunarEclipseWithInstance},
|
||
|
|
{LastLunarEclipseDanjon, LastLunarEclipseDanjonWithInstance},
|
||
|
|
{LastLunarEclipseChauvenet, LastLunarEclipseChauvenetWithInstance},
|
||
|
|
{NextLunarEclipse, NextLunarEclipseWithInstance},
|
||
|
|
{NextLunarEclipseDanjon, NextLunarEclipseDanjonWithInstance},
|
||
|
|
{NextLunarEclipseChauvenet, NextLunarEclipseChauvenetWithInstance},
|
||
|
|
{ClosestLunarEclipse, ClosestLunarEclipseWithInstance},
|
||
|
|
{ClosestLunarEclipseDanjon, ClosestLunarEclipseDanjonWithInstance},
|
||
|
|
{ClosestLunarEclipseChauvenet, ClosestLunarEclipseChauvenetWithInstance},
|
||
|
|
} {
|
||
|
|
want := query.old(date)
|
||
|
|
if got := query.new(date, nil); !reflect.DeepEqual(got, want) {
|
||
|
|
t.Error("nil changed directional search")
|
||
|
|
}
|
||
|
|
if model == ephem.Legacy {
|
||
|
|
if got := query.new(date, inst); !reflect.DeepEqual(got, want) {
|
||
|
|
t.Error("explicit Legacy changed directional search")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestP3LunarQueriesUseInstanceThroughout(t *testing.T) {
|
||
|
|
previous := ephem.Active()
|
||
|
|
t.Cleanup(func() { ephem.SetActive(previous) })
|
||
|
|
base := ephem.NewModelInstance(ephem.Semianalytic)
|
||
|
|
first := ephem.NewInstance(p3LunarShiftEngine{base: base, offset: 0.1})
|
||
|
|
second := ephem.NewInstance(p3LunarShiftEngine{base: base, offset: 0.2})
|
||
|
|
date := time.Date(2022, 11, 8, 12, 0, 0, 0, time.FixedZone("UTC+8", 8*3600))
|
||
|
|
global, model, generation := ephem.Global(), ephem.Active(), ephem.Generation()
|
||
|
|
firstInfo, firstOK := LunarEclipseOnDateWithInstance(date, first)
|
||
|
|
secondInfo, secondOK := LunarEclipseOnDateWithInstance(date, second)
|
||
|
|
if !firstOK || !secondOK || !firstInfo.HasTotal || !secondInfo.HasTotal {
|
||
|
|
t.Fatalf("explicit query lost total eclipse: %+v / %+v", firstInfo, secondInfo)
|
||
|
|
}
|
||
|
|
if firstInfo.HasSaros || secondInfo.HasSaros {
|
||
|
|
t.Error("custom engines must not inherit built-in Saros metadata")
|
||
|
|
}
|
||
|
|
if math.Abs(firstInfo.GreatestJDE-secondInfo.GreatestJDE-0.1) > 0.001 {
|
||
|
|
t.Errorf("event time did not follow instances: %v / %v", firstInfo.GreatestJDE, secondInfo.GreatestJDE)
|
||
|
|
}
|
||
|
|
if firstInfo.Maximum.Location() != date.Location() || len(firstInfo.ContactPoints) != 6 {
|
||
|
|
t.Error("local timezone or contacts were lost")
|
||
|
|
}
|
||
|
|
if ephem.Global() != global || ephem.Active() != model || ephem.Generation() != generation {
|
||
|
|
t.Error("query changed global ephemeris state")
|
||
|
|
}
|
||
|
|
for _, active := range []ephem.Model{ephem.Legacy, ephem.Semianalytic} {
|
||
|
|
ephem.SetActive(active)
|
||
|
|
got, ok := LunarEclipseOnDateWithInstance(date, first)
|
||
|
|
if !ok || !reflect.DeepEqual(got, firstInfo) {
|
||
|
|
t.Error("result, contact orientation or shadow geometry read the global model")
|
||
|
|
}
|
||
|
|
for _, query := range []func(time.Time, *ephem.Instance) LunarEclipseInfo{LastLunarEclipseWithInstance, NextLunarEclipseWithInstance, ClosestLunarEclipseWithInstance} {
|
||
|
|
got := query(firstInfo.Maximum.Add(-time.Minute), first)
|
||
|
|
if got.Type == LunarEclipseNone || got.Maximum.IsZero() {
|
||
|
|
t.Error("directional search lost instance")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestP3LunarSarosSelectsInstanceModel(t *testing.T) {
|
||
|
|
previous := ephem.Active()
|
||
|
|
t.Cleanup(func() { ephem.SetActive(previous) })
|
||
|
|
for _, model := range []ephem.Model{ephem.Legacy, ephem.Semianalytic} {
|
||
|
|
inst := ephem.NewModelInstance(model)
|
||
|
|
ephem.SetActive(model)
|
||
|
|
for _, seed := range []float64{basic.JDCalc(2022, 11, 8), basic.JDCalc(7000, 1, 1), basic.JDCalc(-5000, 1, 1)} {
|
||
|
|
want, wantOK := lunarSarosInfoWithInstance(seed, inst)
|
||
|
|
ephem.SetActive(ephem.Model(1 - int(model)))
|
||
|
|
got, gotOK := lunarSarosInfoWithInstance(seed, inst)
|
||
|
|
if gotOK != wantOK || got != want {
|
||
|
|
t.Errorf("Saros read active model instead of instance: %+v / %+v", got, want)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestP3LunarQueriesStopOnEngineFailure(t *testing.T) {
|
||
|
|
inst := ephem.NewInstance(nil)
|
||
|
|
date := time.Date(2022, 11, 8, 12, 0, 0, 0, time.UTC)
|
||
|
|
if info, ok := LunarEclipseOnDateWithInstance(date, inst); ok || info.Type != "" {
|
||
|
|
t.Error("failed engine produced a local-date eclipse")
|
||
|
|
}
|
||
|
|
for _, query := range []func(time.Time, *ephem.Instance) LunarEclipseInfo{LastLunarEclipseWithInstance, NextLunarEclipseWithInstance, ClosestLunarEclipseWithInstance} {
|
||
|
|
if info := query(date, inst); !info.Maximum.IsZero() {
|
||
|
|
t.Error("failed engine fell back during directional search")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|