\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}