package numerics import "fmt" // Axis describes a regularly-spaced grid axis with N grid points, // values left, left+step, left+2*step, ..., left+(N-1)*step. // // If Wrap is true, the axis is periodic with period N*step (e.g. longitude). // A query value at left+N*step wraps to the value at left+0*step. Locate // returns Hi = 0 in that case. type Axis struct { Left float64 Step float64 N int Wrap bool Name string } // AxisError is returned by Axis.Locate when value lies outside a non-wrapping axis. type AxisError struct { Axis string Value float64 } func (e *AxisError) Error() string { return fmt.Sprintf("%s=%v out of range", e.Axis, e.Value) } // 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). 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). 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 { return Bracket{}, &AxisError{Axis: a.Name, Value: value} } hi := lo + 1 if a.Wrap && hi == a.N { hi = 0 } return Bracket{Lo: lo, Hi: hi, Frac: pos - float64(lo)}, nil } // EvalTrilinear samples a 3D field via f at the eight corners defined by b3 // and returns the trilinearly interpolated value. // // The corners are visited in the order (axis0 outer, axis2 inner), matching // the Cython reference. With f(i,j,k) = a*i + b*j + c*k + d this returns // a*pos0 + b*pos1 + c*pos2 + d exactly, modulo floating-point rounding. func EvalTrilinear(b3 [3]Bracket, f func(i, j, k int) float64) float64 { wa0, wa1 := 1-b3[0].Frac, b3[0].Frac wb0, wb1 := 1-b3[1].Frac, b3[1].Frac wc0, wc1 := 1-b3[2].Frac, b3[2].Frac a0, a1 := b3[0].Lo, b3[0].Hi bb0, bb1 := b3[1].Lo, b3[1].Hi c0, c1 := b3[2].Lo, b3[2].Hi return wa0*wb0*wc0*f(a0, bb0, c0) + wa0*wb0*wc1*f(a0, bb0, c1) + wa0*wb1*wc0*f(a0, bb1, c0) + wa0*wb1*wc1*f(a0, bb1, c1) + wa1*wb0*wc0*f(a1, bb0, c0) + wa1*wb0*wc1*f(a1, bb0, c1) + wa1*wb1*wc0*f(a1, bb1, c0) + wa1*wb1*wc1*f(a1, bb1, c1) } // Lerp returns (1-l)*a + l*b. func Lerp(a, b, l float64) float64 { return (1-l)*a + l*b }