feat: polish & windviz & deploy
This commit is contained in:
parent
81b8e763bd
commit
465ad00f7b
78 changed files with 20622 additions and 2154 deletions
|
|
@ -1,8 +1,6 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"predictor-refactored/internal/numerics"
|
||||
)
|
||||
import "predictor-refactored/internal/numerics"
|
||||
|
||||
// Propagator advances state under one Model, checking a set of Constraints
|
||||
// after every integration step.
|
||||
|
|
@ -11,9 +9,12 @@ import (
|
|||
// violation point and emits it as its final trajectory point. The Action of
|
||||
// the triggering constraint controls what the surrounding Profile does
|
||||
// next: stop the profile, transfer to Fallback, or clip and continue.
|
||||
//
|
||||
// The per-step numerics (RK4 stepping, crossing refinement) are delegated to
|
||||
// the numerics package; this type owns only the orchestration: constraint
|
||||
// evaluation, action dispatch, and trajectory assembly.
|
||||
type Propagator struct {
|
||||
// Name identifies the propagator in trajectory metadata. Optional —
|
||||
// callers using sequential profile chains may leave it empty.
|
||||
// Name identifies the propagator in trajectory metadata. Optional.
|
||||
Name string
|
||||
|
||||
// Step is the magnitude of the integration step in seconds (always positive).
|
||||
|
|
@ -39,6 +40,18 @@ type Propagator struct {
|
|||
Tolerance float64
|
||||
}
|
||||
|
||||
// estimatedSteps is the initial Path capacity; a typical balloon stage is a
|
||||
// few hundred 60-second steps.
|
||||
const estimatedSteps = 256
|
||||
|
||||
// DefaultMaxSteps bounds the number of integration steps a single propagator
|
||||
// may take. It is a safety backstop, not a physical limit: a profile whose
|
||||
// constraints never fire (e.g. a stage with no effective terminator) would
|
||||
// otherwise integrate forever and exhaust memory. At the default 60-second
|
||||
// step this allows ~8 simulated years, far beyond any real flight, so it only
|
||||
// ever trips on a misconfigured profile.
|
||||
const DefaultMaxSteps = 1_000_000
|
||||
|
||||
// run integrates the model from (t0, s0) in direction dir, returning a Result.
|
||||
// globals are constraints injected by the Profile and checked alongside the
|
||||
// propagator's local Constraints. events receives non-fatal observations.
|
||||
|
|
@ -58,70 +71,53 @@ func (p *Propagator) run(ctx StageContext, t0 float64, s0 State, globals []Const
|
|||
constraints = p.BuildConstraints(ctx)
|
||||
}
|
||||
|
||||
deriv := numerics.Deriv[State](func(t float64, s State) State { return model(t, s) })
|
||||
add := numerics.VecAdd[State](stateAdd)
|
||||
lerp := numerics.VecLerp[State](stateLerp)
|
||||
field := numerics.Field(model)
|
||||
|
||||
out := Result{
|
||||
Propagator: p.Name,
|
||||
Outcome: OutcomeContinued,
|
||||
Points: []TrajectoryPoint{{
|
||||
Time: t0, Lat: s0.Lat, Lng: s0.Lng, Altitude: s0.Altitude,
|
||||
}},
|
||||
}
|
||||
out := Result{Propagator: p.Name, Outcome: OutcomeContinued, Path: numerics.NewPath(estimatedSteps)}
|
||||
out.Path.Append(t0, s0)
|
||||
|
||||
t := t0
|
||||
s := s0
|
||||
|
||||
for {
|
||||
s2 := numerics.RK4Step(t, s, dt, deriv, add)
|
||||
t, s := t0, s0
|
||||
for range DefaultMaxSteps {
|
||||
s2 := numerics.RK4Step(t, s, dt, field)
|
||||
t2 := t + dt
|
||||
|
||||
c, fired := firstFiring(constraints, globals, t2, s2)
|
||||
if !fired {
|
||||
t, s = t2, s2
|
||||
out.Points = append(out.Points, TrajectoryPoint{
|
||||
Time: t, Lat: s.Lat, Lng: s.Lng, Altitude: s.Altitude,
|
||||
})
|
||||
out.Path.Append(t, s)
|
||||
continue
|
||||
}
|
||||
|
||||
// Record the unrefined violation.
|
||||
out.ViolationTime = t2
|
||||
out.ViolationState = s2
|
||||
out.ViolationTime, out.ViolationState = t2, s2
|
||||
t3, s3 := numerics.RefineCrossing(t, s, t2, s2, c.Violated, tol)
|
||||
out.Constraint, out.ConstraintName = c, c.Name()
|
||||
|
||||
trig := numerics.Trigger[State](func(tt float64, ss State) bool { return c.Violated(tt, ss) })
|
||||
t3, s3 := numerics.RefineTrigger(t, s, t2, s2, trig, lerp, tol)
|
||||
out.RefinedTime = t3
|
||||
out.RefinedState = s3
|
||||
out.Constraint = c
|
||||
out.ConstraintName = c.Name()
|
||||
|
||||
switch c.Action() {
|
||||
case ActionClip:
|
||||
if c.Action() == ActionClip {
|
||||
s3 = clipToConstraint(c, s3)
|
||||
out.RefinedState = s3
|
||||
out.Points = append(out.Points, TrajectoryPoint{
|
||||
Time: t3, Lat: s3.Lat, Lng: s3.Lng, Altitude: s3.Altitude,
|
||||
})
|
||||
out.RefinedTime, out.RefinedState = t3, s3
|
||||
out.Path.Append(t3, s3)
|
||||
t, s = t3, s3
|
||||
continue
|
||||
case ActionFallback:
|
||||
out.Points = append(out.Points, TrajectoryPoint{
|
||||
Time: t3, Lat: s3.Lat, Lng: s3.Lng, Altitude: s3.Altitude,
|
||||
})
|
||||
out.Outcome = OutcomeFallback
|
||||
out.Events = events.Snapshot()
|
||||
return out
|
||||
default: // ActionStop
|
||||
out.Points = append(out.Points, TrajectoryPoint{
|
||||
Time: t3, Lat: s3.Lat, Lng: s3.Lng, Altitude: s3.Altitude,
|
||||
})
|
||||
out.Outcome = OutcomeStopped
|
||||
out.Events = events.Snapshot()
|
||||
return out
|
||||
}
|
||||
|
||||
out.RefinedTime, out.RefinedState = t3, s3
|
||||
out.Path.Append(t3, s3)
|
||||
if c.Action() == ActionFallback {
|
||||
out.Outcome = OutcomeFallback
|
||||
} else {
|
||||
out.Outcome = OutcomeStopped
|
||||
}
|
||||
out.Events = events.Snapshot()
|
||||
return out
|
||||
}
|
||||
|
||||
// Step cap reached without any constraint firing — the profile has no
|
||||
// effective terminator for this stage. Stop safely rather than loop forever.
|
||||
events.Emit("max_steps", t, s,
|
||||
"integration step limit reached without a constraint firing; check the stage's terminator")
|
||||
out.Outcome = OutcomeContinued
|
||||
out.Events = events.Snapshot()
|
||||
return out
|
||||
}
|
||||
|
||||
// firstFiring scans local then global constraints for the first one whose
|
||||
|
|
@ -140,9 +136,9 @@ func firstFiring(local, globals []Constraint, t float64, s State) (Constraint, b
|
|||
return nil, false
|
||||
}
|
||||
|
||||
// clipToConstraint adjusts s so that the given constraint is exactly
|
||||
// satisfied (not violated). Defined only for constraints with a
|
||||
// well-defined coordinate boundary; others fall through unchanged.
|
||||
// clipToConstraint adjusts s so the given constraint is exactly satisfied.
|
||||
// Defined only for constraints with a well-defined coordinate boundary;
|
||||
// others fall through unchanged.
|
||||
func clipToConstraint(c Constraint, s State) State {
|
||||
if alt, ok := c.(Altitude); ok {
|
||||
s.Altitude = alt.Limit
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue