Files
astro/basic/sky_events_test.go
T

141 lines
5.2 KiB
Go
Raw Normal View History

// 被测契约:通用天象搜索与各行星专用事件一致,并能找到大合与行星近星。
package basic
import (
"math"
"testing"
"time"
"b612.me/astro/internal/geodata"
)
func TestSkyAspectMatchesMarsOpposition(t *testing.T) {
start := 2460000.5
// 专用函数返回 UT。
want := UTC2TT(NextMarsOppositionWithInstance(start, nil))
got := SkyAspectCrossingsWithInstance(SkyMars, SkySun, 180, start, start+800, nil)
if len(got) == 0 || math.Abs(got[0].JDE-want)*86400 > 1 {
t.Fatalf("opposition %v, want %.6f", got, want)
}
}
// 专用函数按赤经留且返回 UT,通用搜索按黄经留,二者相差数小时属口径不同;这里只核对黄经速度确实变号。
func TestSkyStationsFlipLongitudeRate(t *testing.T) {
got := SkyStationsWithInstance(SkyMars, 2460000.5, 2460800.5, nil)
if len(got) != 2 {
t.Fatalf("stations %v", got)
}
for _, e := range got {
before, _ := SkyBodyApparentLoBoWithInstance(SkyMars, e.JDE-1, nil)
at, _ := SkyBodyApparentLoBoWithInstance(SkyMars, e.JDE, nil)
after, _ := SkyBodyApparentLoBoWithInstance(SkyMars, e.JDE+1, nil)
if geodata.NormalizeLongitude(at-before)*geodata.NormalizeLongitude(after-at) >= 0 {
t.Errorf("no rate flip at %.5f", e.JDE)
}
ut := UTC2TT(NextMarsProgradeToRetrogradeWithInstance(e.JDE-30, nil))
if math.Abs(ut-e.JDE) > 5 && math.Abs(UTC2TT(NextMarsRetrogradeToProgradeWithInstance(e.JDE-30, nil))-e.JDE) > 5 {
t.Errorf("station %.5f far from RA stations", e.JDE)
}
}
}
func TestSkyGreatConjunction2020(t *testing.T) {
got := SkySeparationMinimaWithInstance(SkyJupiter, SkySaturn, 1, 2459100.5, 2459300.5, nil)
if len(got) != 1 {
t.Fatalf("minima %v", got)
}
when := JD2Date(TT2UTC(got[0].JDE))
if when.Year() != 2020 || when.Month() != time.December || when.Day() < 21 || when.Day() > 22 || got[0].Value > 0.11 || got[0].Value < 0.09 {
t.Errorf("great conjunction at %v separation %.4f°", when, got[0].Value)
}
}
func TestSkyStarApproachRegulus(t *testing.T) {
regulus := StarCoordinate{ID: "Regulus", RA: 152.09296, Dec: 11.96721, Epoch: time.Date(2000, 1, 1, 12, 0, 0, 0, time.UTC), Frame: CoordinateFrameJ2000}
got := SkyStarSeparationMinimaWithInstance(SkyVenus, regulus, 1.5, 2460400.5, 2460600.5, nil)
if len(got) == 0 {
t.Fatal("no Venus–Regulus approach")
}
for _, e := range got {
if e.Value > 1.5 || e.Value < 0 {
t.Errorf("approach %.4f° at %.5f", e.Value, e.JDE)
}
}
}
func TestSkySeparationMinimaFindsNarrowWindow(t *testing.T) {
wide := SkySeparationMinimaWithInstance(SkyJupiter, SkySaturn, 1, 2459190, 2459220, nil)
if len(wide) == 0 {
t.Fatal("wide window returned no minimum")
}
center := wide[0].JDE
for _, halfWidth := range []float64{0.1, 0.25, 0.49, 0.99, 1.5, 3} {
got := SkySeparationMinimaWithInstance(SkyJupiter, SkySaturn, 1, center-halfWidth, center+halfWidth, nil)
if len(got) == 0 {
t.Errorf("halfWidth %v: no minimum", halfWidth)
continue
}
if math.Abs(got[0].JDE-center) > 1e-4 {
t.Errorf("halfWidth %v: JDE = %.6f, want %.6f", halfWidth, got[0].JDE, center)
}
}
}
func TestSkySeparationMinimaFindsEdgeOfWindow(t *testing.T) {
wide := SkySeparationMinimaWithInstance(SkyJupiter, SkySaturn, 1, 2459190, 2459220, nil)
if len(wide) == 0 {
t.Fatal("wide window returned no minimum")
}
center := wide[0].JDE
windows := [][2]float64{
{center - 1.0/1440, center + 3.0/24},
{center - 3.0/24, center + 1.0/1440},
{center - 1.0/24, center + 1.0/24},
}
for _, window := range windows {
got := SkySeparationMinimaWithInstance(SkyJupiter, SkySaturn, 1, window[0], window[1], nil)
if len(got) != 1 || math.Abs(got[0].JDE-center) > 1e-4 {
t.Errorf("window [%+.4f, %+.4f]: got %+v, want one minimum at %.6f", window[0]-center, window[1]-center, got, center)
}
}
if got := SkySeparationMinimaWithInstance(SkyJupiter, SkySaturn, 1, center+0.5, center+3, nil); len(got) != 0 {
t.Errorf("window after the minimum: got %+v, want none", got)
}
}
func TestSkyScansTerminateOnDegenerateWindows(t *testing.T) {
start := 2459205.26539
windows := [][2]float64{
{start, math.Nextafter(start, math.Inf(1))},
{start, start + 26e-6/86400},
{start, math.Inf(1)},
{math.Inf(-1), start},
{math.NaN(), start},
}
for _, window := range windows {
SkySeparationMinimaWithInstance(SkyJupiter, SkySaturn, 1, window[0], window[1], nil)
SkyLongitudeCrossingsWithInstance(SkySun, 280, window[0], window[1], nil)
SkyAspectCrossingsWithInstance(SkyJupiter, SkySaturn, 0, window[0], window[1], nil)
}
}
func TestSkyGridRejectsLimitInclusive(t *testing.T) {
if _, _, ok := skyGrid(0, float64(skyGridMaxIntervals), 1, 1); ok {
t.Error("grid at limit accepted")
}
if count, _, ok := skyGrid(0, float64(skyGridMaxIntervals-1), 1, 1); !ok || count != skyGridMaxIntervals-1 {
t.Errorf("grid below limit: count=%d ok=%v", count, ok)
}
}
func TestSkyLongitudeCrossingsIncludesClosedEndpoints(t *testing.T) {
const jde = 2451545.0
lo, _ := SkyBodyApparentLoBoWithInstance(SkySun, jde, nil)
for _, window := range [][2]float64{{jde - 2, jde}, {jde, jde + 2}} {
got := SkyLongitudeCrossingsWithInstance(SkySun, lo, window[0], window[1], nil)
if len(got) != 1 || math.Abs(got[0].JDE-jde) > 1e-5 {
t.Errorf("window [%v, %v]: got %+v, want one crossing at %v", window[0], window[1], got, jde)
}
}
}