feat(decart): interp

This commit is contained in:
gili8420 2026-08-03 22:10:10 +09:00
parent 1bd9143186
commit 19557f3a62
14 changed files with 681 additions and 208 deletions

View file

@ -15,10 +15,10 @@ func Sum(models ...Model) Model {
if len(models) == 1 {
return models[0]
}
return func(t float64, s State) State {
var sum State
return func(t float64, s State) numerics.Rate {
var sum numerics.Rate
for _, m := range models {
sum = numerics.AddGeo(sum, m(t, s))
sum = numerics.AddRate(sum, m(t, s))
}
return sum
}
@ -27,7 +27,7 @@ func Sum(models ...Model) Model {
// ConstantRate returns a model with a constant vertical velocity (m/s).
// Positive rates are upward.
func ConstantRate(rate float64) Model {
return func(_ float64, _ State) State { return State{Altitude: rate} }
return func(_ float64, _ State) numerics.Rate { return numerics.Rate{Vertical: rate} }
}
// ParachuteDescent returns a model where vertical velocity grows with
@ -40,8 +40,8 @@ func ConstantRate(rate float64) Model {
//
// using the NASA atmosphere model for rho. Equivalent to Tawhiri's drag_descent.
func ParachuteDescent(seaLevelRate float64) Model {
return func(_ float64, s State) State {
return State{Altitude: numerics.DragTerminalVelocity(seaLevelRate, s.Altitude)}
return func(_ float64, s State) numerics.Rate {
return numerics.Rate{Vertical: numerics.DragTerminalVelocity(seaLevelRate, s.Altitude)}
}
}
@ -64,33 +64,34 @@ func Piecewise(segments []RateSegment) Model {
sort.Slice(sorted, func(i, j int) bool { return sorted[i].Until < sorted[j].Until })
finalRate := sorted[len(sorted)-1].Rate
return func(t float64, _ State) State {
return func(t float64, _ State) numerics.Rate {
idx := sort.Search(len(sorted), func(i int) bool { return sorted[i].Until > t })
if idx == len(sorted) {
return State{Altitude: finalRate}
return numerics.Rate{Vertical: finalRate}
}
return State{Altitude: sorted[idx].Rate}
return numerics.Rate{Vertical: sorted[idx].Rate}
}
}
// WindTransport returns a model that moves laterally at the wind velocity
// sampled from field. The vertical component is zero. Sampling and the
// non-fatal "above_model" event live here (orchestration); the m/s → deg/s
// conversion is numerics.WindToGeoRate.
// wind is handed straight to the integrator as a horizontal velocity.
//
// If events is non-nil, an "above_model" event is emitted whenever the
// wind field reports altitude above the highest pressure level.
func WindTransport(field weather.WindField, events *EventSink) Model {
return func(t float64, s State) State {
return func(t float64, s State) numerics.Rate {
sample, err := field.Wind(t, s.Lat, s.Lng, s.Altitude)
if err != nil {
return State{}
return numerics.Rate{}
}
if sample.AboveModel && events != nil {
events.Emit("above_model", t, s,
"altitude exceeded the highest pressure level of the wind dataset; samples extrapolated")
}
dLat, dLng := numerics.WindToGeoRate(sample.U, sample.V, s.Lat, s.Altitude)
return State{Lat: dLat, Lng: dLng}
// The wind is already a horizontal velocity; it is handed over as-is.
// Converting it to deg/s here is what used to blow up near the poles.
return numerics.Rate{East: sample.U, North: sample.V}
}
}