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