feat(decart): interp

This commit is contained in:
gili8420 2026-08-03 22:10:10 +09:00
parent 1bd9143186
commit 19557f3a62
14 changed files with 681 additions and 208 deletions

View file

@ -1,6 +1,9 @@
package numerics
import "fmt"
import (
"fmt"
"math"
)
// Axis describes a regularly-spaced grid axis with N grid points,
// values left, left+step, left+2*step, ..., left+(N-1)*step.
@ -28,27 +31,43 @@ func (e *AxisError) Error() string {
// Bracket holds the two surrounding grid indices and the fractional position
// of a value within an axis. The weight at Lo is (1 - Frac); the weight at Hi
// is Frac. Frac lies in [0, 1).
// is Frac. Frac lies in [0, 1].
type Bracket struct {
Lo, Hi int
Frac float64
}
// Locate returns the bracket containing value within the axis.
// For a non-wrapping axis, value must lie in [Left, Left + (N-1)*Step);
// for a wrapping axis, value must lie in [Left, Left + N*Step).
// The accepted range is closed at both ends: [Left, Left + (N-1)*Step] for a
// non-wrapping axis, [Left, Left + N*Step] for a wrapping one.
//
// The upper end is closed deliberately. On the GFS latitude axis it is the
// north pole, whose row holds real data — NCEP resolves it per longitude, so it
// is a usable grid row like any other. Rejecting it used to abort the wind
// lookup, and because the caller discarded that error the whole prediction
// silently froze at latitude 90. The same argument applies to the last forecast
// hour and the topmost pressure level.
func (a Axis) Locate(value float64) (Bracket, error) {
pos := (value - a.Left) / a.Step
lo := int(pos) // truncates toward zero; pos is non-negative for valid inputs
maxLo := a.N - 2
if a.Wrap {
maxLo = a.N - 1
}
if lo < 0 || lo > maxLo {
// Bound-check in float space. Checking the truncated index instead would
// let values just below Left through: int() truncates toward zero, so a pos
// of -0.002 became index 0 rather than -1 and extrapolated off the end.
if pos < 0 || pos > float64(maxLo+1) {
return Bracket{}, &AxisError{Axis: a.Name, Value: value}
}
lo := int(math.Floor(pos))
// The exact upper bound belongs to the top cell at Frac 1, rather than
// opening a cell that has no neighbour above it.
if lo > maxLo {
lo = maxLo
}
hi := lo + 1
if a.Wrap && hi == a.N {
hi = 0