forked from gsn/predictor
feat: refactor
This commit is contained in:
parent
82ef1cb3b8
commit
51bbf3c579
44 changed files with 8589 additions and 0 deletions
188
internal/prediction/models.go
Normal file
188
internal/prediction/models.go
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
package prediction
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"predictor-refactored/internal/dataset"
|
||||
"predictor-refactored/internal/elevation"
|
||||
)
|
||||
|
||||
// Exact port of the reference flight models (models.py).
|
||||
|
||||
const (
|
||||
pi180 = math.Pi / 180.0
|
||||
_180pi = 180.0 / math.Pi
|
||||
)
|
||||
|
||||
// --- Up/Down Models ---
|
||||
|
||||
// ConstantAscent returns a model with constant vertical velocity (m/s).
|
||||
func ConstantAscent(ascentRate float64) Model {
|
||||
return func(t, lat, lng, alt float64) (dlat, dlng, dalt float64) {
|
||||
return 0, 0, ascentRate
|
||||
}
|
||||
}
|
||||
|
||||
// DragDescent returns a descent-under-parachute model.
|
||||
// seaLevelDescentRate is the descent rate at sea level (m/s, positive value).
|
||||
// Uses the NASA atmosphere model for density at altitude.
|
||||
func DragDescent(seaLevelDescentRate float64) Model {
|
||||
dragCoefficient := seaLevelDescentRate * 1.1045
|
||||
|
||||
return func(t, lat, lng, alt float64) (dlat, dlng, dalt float64) {
|
||||
return 0, 0, -dragCoefficient / math.Sqrt(nasaDensity(alt))
|
||||
}
|
||||
}
|
||||
|
||||
// nasaDensity computes air density using the NASA atmosphere model.
|
||||
// Reference: http://www.grc.nasa.gov/WWW/K-12/airplane/atmosmet.html
|
||||
func nasaDensity(alt float64) float64 {
|
||||
var temp, pressure float64
|
||||
|
||||
switch {
|
||||
case alt > 25000:
|
||||
temp = -131.21 + 0.00299*alt
|
||||
pressure = 2.488 * math.Pow((temp+273.1)/216.6, -11.388)
|
||||
case alt > 11000:
|
||||
temp = -56.46
|
||||
pressure = 22.65 * math.Exp(1.73-0.000157*alt)
|
||||
default:
|
||||
temp = 15.04 - 0.00649*alt
|
||||
pressure = 101.29 * math.Pow((temp+273.1)/288.08, 5.256)
|
||||
}
|
||||
|
||||
return pressure / (0.2869 * (temp + 273.1))
|
||||
}
|
||||
|
||||
// --- Sideways Models ---
|
||||
|
||||
// WindVelocity returns a model that gives lateral movement at the wind velocity.
|
||||
// ds is the wind dataset, dsEpoch is the dataset start time as UNIX timestamp.
|
||||
func WindVelocity(ds *dataset.File, dsEpoch float64, warnings *Warnings) Model {
|
||||
return func(t, lat, lng, alt float64) (dlat, dlng, dalt float64) {
|
||||
tHours := (t - dsEpoch) / 3600.0
|
||||
u, v, err := GetWind(ds, warnings, tHours, lat, lng, alt)
|
||||
if err != nil {
|
||||
return 0, 0, 0
|
||||
}
|
||||
|
||||
R := 6371009.0 + alt
|
||||
dlat = _180pi * v / R
|
||||
dlng = _180pi * u / (R * math.Cos(lat*pi180))
|
||||
return dlat, dlng, 0
|
||||
}
|
||||
}
|
||||
|
||||
// --- Model Combinations ---
|
||||
|
||||
// LinearModel returns a model that sums all component models.
|
||||
func LinearModel(models ...Model) Model {
|
||||
return func(t, lat, lng, alt float64) (dlat, dlng, dalt float64) {
|
||||
for _, m := range models {
|
||||
d1, d2, d3 := m(t, lat, lng, alt)
|
||||
dlat += d1
|
||||
dlng += d2
|
||||
dalt += d3
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// --- Termination Criteria ---
|
||||
|
||||
// BurstTermination returns a terminator that fires when altitude >= burstAltitude.
|
||||
func BurstTermination(burstAltitude float64) Terminator {
|
||||
return func(t, lat, lng, alt float64) bool {
|
||||
return alt >= burstAltitude
|
||||
}
|
||||
}
|
||||
|
||||
// SeaLevelTermination fires when altitude <= 0.
|
||||
func SeaLevelTermination(t, lat, lng, alt float64) bool {
|
||||
return alt <= 0
|
||||
}
|
||||
|
||||
// TimeTermination returns a terminator that fires when t > maxTime.
|
||||
func TimeTermination(maxTime float64) Terminator {
|
||||
return func(t, lat, lng, alt float64) bool {
|
||||
return t > maxTime
|
||||
}
|
||||
}
|
||||
|
||||
// ElevationTermination returns a terminator that fires when alt < ground level.
|
||||
// Uses ruaumoko-compatible elevation data. Longitude is normalised internally.
|
||||
func ElevationTermination(elev *elevation.Dataset) Terminator {
|
||||
return func(t, lat, lng, alt float64) bool {
|
||||
return elev.Get(lat, lng) > alt
|
||||
}
|
||||
}
|
||||
|
||||
// --- Pre-Defined Profiles ---
|
||||
|
||||
// Stage pairs a model with its termination criterion.
|
||||
type Stage struct {
|
||||
Model Model
|
||||
Terminator Terminator
|
||||
}
|
||||
|
||||
// StandardProfile creates the chain for a standard high-altitude balloon flight:
|
||||
// ascent at constant rate → burst → descent under parachute.
|
||||
// If elev is non-nil, descent terminates at ground level; otherwise at sea level.
|
||||
func StandardProfile(ascentRate, burstAltitude, descentRate float64,
|
||||
ds *dataset.File, dsEpoch float64, warnings *Warnings,
|
||||
elev *elevation.Dataset) []Stage {
|
||||
|
||||
wind := WindVelocity(ds, dsEpoch, warnings)
|
||||
|
||||
modelUp := LinearModel(ConstantAscent(ascentRate), wind)
|
||||
termUp := BurstTermination(burstAltitude)
|
||||
|
||||
modelDown := LinearModel(DragDescent(descentRate), wind)
|
||||
var termDown Terminator
|
||||
if elev != nil {
|
||||
termDown = ElevationTermination(elev)
|
||||
} else {
|
||||
termDown = Terminator(SeaLevelTermination)
|
||||
}
|
||||
|
||||
return []Stage{
|
||||
{Model: modelUp, Terminator: termUp},
|
||||
{Model: modelDown, Terminator: termDown},
|
||||
}
|
||||
}
|
||||
|
||||
// FloatProfile creates the chain for a floating balloon flight:
|
||||
// ascent to float altitude → float until stop time.
|
||||
func FloatProfile(ascentRate, floatAltitude float64, stopTime time.Time,
|
||||
ds *dataset.File, dsEpoch float64, warnings *Warnings) []Stage {
|
||||
|
||||
wind := WindVelocity(ds, dsEpoch, warnings)
|
||||
|
||||
modelUp := LinearModel(ConstantAscent(ascentRate), wind)
|
||||
termUp := BurstTermination(floatAltitude)
|
||||
|
||||
modelFloat := wind
|
||||
termFloat := TimeTermination(float64(stopTime.Unix()))
|
||||
|
||||
return []Stage{
|
||||
{Model: modelUp, Terminator: termUp},
|
||||
{Model: modelFloat, Terminator: termFloat},
|
||||
}
|
||||
}
|
||||
|
||||
// RunPrediction runs a prediction with the given profile stages.
|
||||
// launchTime is a UNIX timestamp.
|
||||
func RunPrediction(launchTime float64, lat, lng, alt float64, stages []Stage) []StageResult {
|
||||
chain := make([]struct {
|
||||
Model Model
|
||||
Terminator Terminator
|
||||
}, len(stages))
|
||||
|
||||
for i, s := range stages {
|
||||
chain[i].Model = s.Model
|
||||
chain[i].Terminator = s.Terminator
|
||||
}
|
||||
|
||||
return Solve(launchTime, lat, lng, alt, chain)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue