step one
This commit is contained in:
parent
7a8d5d13fa
commit
9e663db9dc
68 changed files with 5647 additions and 2958 deletions
160
docs/numerics.tex
Normal file
160
docs/numerics.tex
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
\documentclass[a4paper,11pt]{article}
|
||||
\usepackage[margin=1in]{geometry}
|
||||
\usepackage{amsmath, amssymb}
|
||||
\usepackage{algorithm, algpseudocode}
|
||||
\usepackage{hyperref}
|
||||
|
||||
\title{Numerics Library: Mathematical Reference}
|
||||
\author{stratoflights-predictor}
|
||||
\date{}
|
||||
|
||||
\begin{document}
|
||||
\maketitle
|
||||
|
||||
This document describes every numerical primitive in the
|
||||
\verb|internal/numerics| package. Each section pairs the mathematical
|
||||
definition with a pointer to the Go implementation and at least one
|
||||
worked example that can be reproduced manually.
|
||||
|
||||
\section{Regular-grid bracketing}
|
||||
|
||||
\paragraph{Definition.} An \emph{axis} is the regularly-spaced sequence
|
||||
$x_i = \ell + i \cdot s$ for $i = 0, 1, \ldots, N - 1$, parameterised by
|
||||
the left edge $\ell$, the step $s > 0$, and the point count $N$.
|
||||
|
||||
Given a query $v$, the \emph{bracket} is the pair $(i_0, i_1)$ with
|
||||
$x_{i_0} \le v < x_{i_1}$ and the dimensionless position
|
||||
\[
|
||||
f = \frac{v - x_{i_0}}{s} \in [0, 1).
|
||||
\]
|
||||
Implemented as \verb|Axis.Locate| (\verb|internal/numerics/grid.go|).
|
||||
|
||||
\paragraph{Wrapping axes.} For periodic axes (e.g.\ longitude), the
|
||||
sequence is extended by the convention $x_N = x_0$ so a value approaching
|
||||
$x_N$ from below brackets $(N{-}1, 0)$ with fraction
|
||||
$f = (v - x_{N-1})/s$.
|
||||
|
||||
\paragraph{Domain.} The bracket is undefined when $v$ falls outside the
|
||||
half-open interval $[\ell, \ell + (N{-}1)\,s)$ (for non-wrapping axes) or
|
||||
$[\ell, \ell + N\,s)$ (for wrapping axes); the implementation returns
|
||||
an \verb|AxisError| in those cases.
|
||||
|
||||
\paragraph{Worked example.} Latitude axis $\ell = -90$, $s = 0{.}5$,
|
||||
$N = 361$. Query $v = -89{.}75$ yields
|
||||
$p = (-89{.}75 - (-90))/0{.}5 = 0{.}5$, so $i_0 = 0$, $i_1 = 1$, $f = 0{.}5$.
|
||||
|
||||
\section{Multilinear interpolation}
|
||||
|
||||
\paragraph{Definition.} For a scalar field $u$ defined at the grid nodes
|
||||
of three axes, the trilinear interpolant at brackets $b_a, b_b, b_c$ is
|
||||
\[
|
||||
\tilde u = \sum_{i, j, k \in \{0, 1\}} w_{a,i} \, w_{b,j} \, w_{c,k}
|
||||
\; u\bigl(b_a^i, b_b^j, b_c^k\bigr),
|
||||
\]
|
||||
where $w_{\bullet, 0} = 1 - f_\bullet$ and $w_{\bullet, 1} = f_\bullet$.
|
||||
Implemented as \verb|EvalTrilinear|.
|
||||
|
||||
\paragraph{Linear exactness.} For any affine field
|
||||
$u(i, j, k) = \alpha i + \beta j + \gamma k + \delta$, the formula returns
|
||||
$\alpha \cdot p_a + \beta \cdot p_b + \gamma \cdot p_c + \delta$ exactly
|
||||
(modulo floating-point rounding), where $p_\bullet = b_\bullet^0 + f_\bullet$.
|
||||
|
||||
\paragraph{Evaluation order.} The eight corner terms are accumulated in
|
||||
the order $(0,0,0), (0,0,1), \ldots, (1,1,1)$, matching the reference
|
||||
Tawhiri implementation \emph{exactly} so that double-precision results
|
||||
agree bit-for-bit.
|
||||
|
||||
\section{Monotone bisection}
|
||||
|
||||
\paragraph{Definition.} For an integer-indexed monotone non-decreasing
|
||||
sequence $f : \{i_{\min}, \ldots, i_{\max}\} \to \mathbb{R}$ and a target
|
||||
$t$, $\mathrm{Bisect}$ returns the largest index $i^\star$ with
|
||||
$f(i^\star) < t$. The implementation evaluates $f$ on a midpoint
|
||||
$m = \lceil(i_{\min} + i_{\max})/2\rceil$ each iteration and halves the
|
||||
interval, taking $\mathcal{O}(\log(i_{\max} - i_{\min}))$ evaluations.
|
||||
|
||||
\paragraph{Boundary behaviour.} If $t \le f(i_{\min})$, the function
|
||||
returns $i_{\min}$; if $t > f(i_{\max})$, it returns $i_{\max}$.
|
||||
|
||||
\paragraph{Usage in this codebase.} The pressure-level search in the GFS
|
||||
wind field locates the largest level whose interpolated geopotential
|
||||
height is below the query altitude; vertical interpolation then runs
|
||||
between that level and its successor.
|
||||
|
||||
\section{Classical Runge--Kutta--4 integrator}
|
||||
|
||||
\paragraph{Definition.} For a state $y$, derivative $\dot y = f(t, y)$,
|
||||
and step $\Delta t$, \verb|RK4Step| applies the classical RK4 update
|
||||
\[
|
||||
\begin{aligned}
|
||||
k_1 &= f(t, y), \\
|
||||
k_2 &= f\bigl(t + \tfrac{\Delta t}{2}, \; y + \tfrac{\Delta t}{2} k_1\bigr), \\
|
||||
k_3 &= f\bigl(t + \tfrac{\Delta t}{2}, \; y + \tfrac{\Delta t}{2} k_2\bigr), \\
|
||||
k_4 &= f\bigl(t + \Delta t, \; y + \Delta t \cdot k_3\bigr), \\
|
||||
y(t + \Delta t) &= y + \tfrac{\Delta t}{6}\bigl(k_1 + 2 k_2 + 2 k_3 + k_4\bigr).
|
||||
\end{aligned}
|
||||
\]
|
||||
|
||||
\paragraph{Reverse-time integration.} Passing $\Delta t < 0$ integrates
|
||||
backwards in time. The derivative $f$ is treated as direction-independent:
|
||||
all sign accounting lives in the integrator. The implementation contains
|
||||
no explicit branch on the sign of $\Delta t$.
|
||||
|
||||
\paragraph{Vector state.} \verb|RK4Step| is generic on the state type.
|
||||
Domain-specific vector arithmetic (in particular longitude wrap on the
|
||||
$0\!:\!360$ degree circle) is injected via the \verb|VecAdd| operation
|
||||
$\mathrm{add}(y, k, \delta y) = y + k \cdot \delta y$.
|
||||
|
||||
\section{Termination-point refinement}
|
||||
|
||||
After each integration step the propagator checks one or more
|
||||
constraints. When a constraint reports a violation between $(t_1, y_1)$
|
||||
(not violated) and $(t_2, y_2)$ (violated), \verb|RefineTrigger|
|
||||
locates the crossing within tolerance $\tau \in (0, 1)$ by binary
|
||||
search in the linear interpolation parameter $\lambda$:
|
||||
|
||||
\begin{algorithm}[H]
|
||||
\caption{RefineTrigger}\label{alg:refine}
|
||||
\begin{algorithmic}[1]
|
||||
\State $L \gets 0,\; R \gets 1$
|
||||
\State $t_3 \gets t_2,\; y_3 \gets y_2$
|
||||
\While{$R - L > \tau$}
|
||||
\State $m \gets (L + R)/2$
|
||||
\State $t_3 \gets (1 - m)\,t_1 + m\,t_2$
|
||||
\State $y_3 \gets \mathrm{lerp}(y_1, y_2, m)$
|
||||
\If{constraint violated at $(t_3, y_3)$}
|
||||
\State $R \gets m$
|
||||
\Else
|
||||
\State $L \gets m$
|
||||
\EndIf
|
||||
\EndWhile
|
||||
\State \Return $(t_3, y_3)$
|
||||
\end{algorithmic}
|
||||
\end{algorithm}
|
||||
|
||||
\paragraph{Termination guarantee.} After $\lceil \log_2 \tau^{-1} \rceil$
|
||||
iterations, $R - L \le \tau$. With $\tau = 0{.}01$ and $\Delta t = 60$~s,
|
||||
the returned point is within $0{.}6$~s of the true crossing in parameter
|
||||
space; the corresponding altitude error is bounded by $0{.}6\,|\dot y|$,
|
||||
which for typical balloon ascent and parachute descent rates is at most
|
||||
$\sim 3$~m.
|
||||
|
||||
\paragraph{Quirk.} The returned $(t_3, y_3)$ is the \emph{last midpoint
|
||||
sampled} rather than guaranteed to lie on the triggered side; this
|
||||
matches the reference Tawhiri implementation byte-for-byte.
|
||||
|
||||
\paragraph{Vector lerp.} As with \verb|RK4Step|, the per-coordinate
|
||||
linear interpolation is delegated to the caller's \verb|VecLerp| to keep
|
||||
the integrator agnostic of state semantics. The engine package's
|
||||
\verb|lerpState| applies the shorter-arc convention for longitudes
|
||||
crossing the $0\!:\!360$ boundary.
|
||||
|
||||
\section{Implementation notes}
|
||||
|
||||
The library is intentionally small (under 300 lines of Go) and uses no
|
||||
runtime allocations on the hot path. The type-generic \verb|RK4Step| and
|
||||
\verb|RefineTrigger| compile to per-type specialisations under Go's
|
||||
generics, so a future C or Rust port can mirror the implementation
|
||||
verbatim without changing the call sites in the trajectory engine.
|
||||
|
||||
\end{document}
|
||||
Loading…
Add table
Add a link
Reference in a new issue