package numerics import ( "math" "testing" ) func TestRK4ExponentialDecay(t *testing.T) { // dAlt/dt = -Alt → exact: Alt(t) = Alt0 * exp(-t). f := func(_ float64, y GeoVec) GeoVec { return GeoVec{Altitude: -y.Altitude} } y := GeoVec{Altitude: 1} tnow, dt := 0.0, 0.01 for range 100 { y = RK4Step(tnow, y, dt, f) tnow += dt } want := math.Exp(-1.0) if math.Abs(y.Altitude-want) > 1e-8 { t.Errorf("RK4 exp decay at t=1: got %v, want %v", y.Altitude, want) } } func TestRK4ReverseTime(t *testing.T) { // dAlt/dt = Alt → exact: Alt(t) = Alt0 * exp(t). f := func(_ float64, y GeoVec) GeoVec { return GeoVec{Altitude: y.Altitude} } y := GeoVec{Altitude: math.E} tnow, dt := 1.0, -0.01 for range 100 { y = RK4Step(tnow, y, dt, f) tnow += dt } if math.Abs(y.Altitude-1.0) > 1e-8 { t.Errorf("RK4 reverse: got %v, want 1.0", y.Altitude) } } func TestRefineCrossing(t *testing.T) { y1 := GeoVec{Altitude: 1} y2 := GeoVec{Altitude: -1.5} crossed := func(_ float64, y GeoVec) bool { return y.Altitude <= 0 } tr, yr := RefineCrossing(0, y1, 1, y2, crossed, 0.001) if math.Abs(tr-0.4) > 0.01 { t.Errorf("refined t = %v, want ~0.4", tr) } if math.Abs(yr.Altitude) > 0.01 { t.Errorf("refined alt = %v, want ~0", yr.Altitude) } } func TestGeoAddWrapsLongitude(t *testing.T) { y := GeoAdd(GeoVec{Lng: 350}, 1, GeoVec{Lng: 20}) if math.Abs(y.Lng-10) > 1e-9 { t.Errorf("GeoAdd wrap: lng = %v, want 10", y.Lng) } } func TestGeoLerpWrap(t *testing.T) { mid := GeoLerp(GeoVec{Lng: 350}, GeoVec{Lng: 10}, 0.5) if math.Abs(mid.Lng) > 1e-9 && math.Abs(mid.Lng-360) > 1e-9 { t.Errorf("GeoLerp lng wrap: %v, want 0 or 360", mid.Lng) } } func TestPathSoA(t *testing.T) { p := NewPath(4) p.Append(0, GeoVec{Lat: 1, Lng: 2, Altitude: 3}) p.Append(60, GeoVec{Lat: 4, Lng: 5, Altitude: 6}) if p.Len() != 2 { t.Fatalf("len = %d, want 2", p.Len()) } tt, last := p.Last() if tt != 60 || last.Lat != 4 { t.Errorf("last = %v, %+v", tt, last) } }