Skip to main content

BilinearSurface

Struct BilinearSurface 

Source
pub struct BilinearSurface<const NX: usize, const NY: usize, X: AxisLookup<NX> = BinaryAxis<NX>, Y: AxisLookup<NY> = BinaryAxis<NY>> { /* private fields */ }
Expand description

A static rectilinear u16 × u16 → i32 surface.

The handle references static tables and never owns or copies them. NX is the number of X knots and NY the number of Y knots.

§Orientation

The value grid is row-major with Y selecting the row and X selecting the column, so a value is addressed as values[y][x]. Because the grid type is &'static [[i32; NX]; NY], a transposed grid is a type error when NX != NY, and there is no reachable dimension-mismatch outcome. For a square grid, the transposed shape has the same type, so preserving orientation remains the caller’s responsibility.

§Lookup strategies

The last two type parameters select how each axis locates a coordinate, and they default to BinaryAxis — the general-purpose choice, and the only one BilinearSurface::new can produce. BilinearSurface<NX, NY> is therefore the binary-knotted surface.

A firmware that wants a different trade names it in the type and builds the surface with BilinearSurface::from_axes: LinearAxis for a tiny axis, UniformAxis for evenly spaced knots that then need not be stored at all, or BucketedAxis to buy a smaller search bound on a long irregular axis with a few static index bytes. The two axes choose independently, and the choice is a type rather than a value: there is no runtime discriminant and no branch among strategies.

Choose LinearAxis for a tiny axis when the minimum auxiliary structure is what matters; BinaryAxis as the general default; UniformAxis when knots are evenly spaced, so the knot arrays can be dropped and location is constant work; BucketedAxis for a long irregular axis when 2*B extra index bytes buy a smaller local bound.

Whichever strategies a surface names, it locates the same cell, evaluates the same value, and reports the same errors. Only the stored bytes and the search work differ.

§Validation

BilinearSurface::new is a const fn that rejects fewer than two knots on either axis and any axis that is not strictly increasing. A definition that violates those invariants fails to compile. Every axis strategy validates itself the same way in its own const fn constructor, so a surface built with BilinearSurface::from_axes is validated before it exists.

§Storage

The referenced table element payload is exactly BilinearSurface::PAYLOAD_BYTES: the two axes’ AxisLookup::KNOT_BYTES and AxisLookup::INDEX_BYTES plus BilinearSurface::VALUE_BYTES (4*NX*NY). For the default binary surface that equals 2*NX + 2*NY + 4*NX*NY bytes. That figure excludes this handle, alignment, linker effects, code, stack, and binary or flash placement. It is not a total memory cost.

The handle always contains the value-grid reference and the four-byte boundary policy. Its remaining fields depend on the axis strategies: UniformAxis stores no reference, LinearAxis and BinaryAxis each store one knot-array reference, and BucketedAxis stores a knot-array and an index-array reference. The default binary/binary handle is therefore three thin references plus the policy and alignment padding. Its size is BilinearSurface::HANDLE_BYTES, which is target-dependent.

§Equality

The derived PartialEq and Hash implementations compare and hash the referenced tables, not their addresses. Two handles over distinct but equal tables therefore compare equal, at a cost proportional to NX * NY.

§Examples

use ph_surfaces::{BilinearSurface, Boundary, BoundaryPolicy};

static X: [u16; 2] = [0, 100];
static Y: [u16; 3] = [0, 100, 200];
static VALUES: [[i32; 2]; 3] = [[0, 1], [2, 3], [4, 5]];

static SURFACE: BilinearSurface<2, 3> = BilinearSurface::new(&X, &Y, &VALUES)
    .with_policy(BoundaryPolicy::new().with_y_above(Boundary::Clamp));

assert_eq!(SURFACE.nx(), 2);
assert_eq!(SURFACE.ny(), 3);
assert_eq!(SURFACE.values()[2][1], 5);
assert_eq!(SURFACE.policy().y_above(), Boundary::Clamp);
assert_eq!(SURFACE.policy().y_below(), Boundary::Error);

A grid whose dimensions are swapped does not compile. Here the axes call for [[i32; 2]; 3] but the grid is [[i32; 3]; 2]:

use ph_surfaces::BilinearSurface;

static X: [u16; 2] = [0, 100];
static Y: [u16; 3] = [0, 100, 200];
static VALUES: [[i32; 3]; 2] = [[0, 1, 2], [3, 4, 5]];

static SURFACE: BilinearSurface<2, 3> = BilinearSurface::new(&X, &Y, &VALUES);

The same declaration with the correct [[i32; NX]; NY] shape compiles:

use ph_surfaces::BilinearSurface;

static X: [u16; 2] = [0, 100];
static Y: [u16; 3] = [0, 100, 200];
static VALUES: [[i32; 2]; 3] = [[0, 1], [2, 3], [4, 5]];

static SURFACE: BilinearSurface<2, 3> = BilinearSurface::new(&X, &Y, &VALUES);
assert_eq!(SURFACE.values()[2][1], 5);

An axis whose knot count disagrees with the value grid does not compile either, because the axis type carries that count:

use ph_surfaces::{BilinearSurface, BinaryAxis};

static X: [u16; 3] = [0, 50, 100];
static Y: [u16; 2] = [0, 100];
static VALUES: [[i32; 2]; 2] = [[0, 1], [2, 3]];

static SURFACE: BilinearSurface<2, 2, BinaryAxis<3>, BinaryAxis<2>> =
    BilinearSurface::from_axes(BinaryAxis::new(&X), BinaryAxis::new(&Y), &VALUES);

Implementations§

Source§

impl<const NX: usize, const NY: usize, X: AxisLookup<NX>, Y: AxisLookup<NY>> BilinearSurface<NX, NY, X, Y>

Source

pub fn evaluate(&self, x: u16, y: u16) -> Result<i32, SurfaceError>

Evaluates this surface at (x, y) with deterministic X-then-Y bilinear interpolation.

§Order

The composition is normative, not an implementation detail:

  1. interpolate along X on the lower-Y row;
  2. interpolate along X on the upper-Y row;
  3. interpolate those two already rounded results along Y.

Because every step rounds to nearest with exact half-way values away from zero, a Y-then-X implementation would return different values, so this order is observable. For the axes [0, 2] with rows [[0, 0], [1, 3]], this order returns 1 at (1, 1) where Y-then-X would return 2:

use ph_surfaces::BilinearSurface;

static AXIS: [u16; 2] = [0, 2];
static VALUES: [[i32; 2]; 2] = [[0, 0], [1, 3]];
static SURFACE: BilinearSurface<2, 2> = BilinearSurface::new(&AXIS, &AXIS, &VALUES);

// X on the lower row: 0. X on the upper row: (1 + 3) / 2 = 2.
// Y between them: (0 + 2) / 2 = 1.
assert_eq!(SURFACE.evaluate(1, 1), Ok(1));
§Domain

X is resolved before Y. If both coordinates leave the domain on sides selecting Boundary::Error, the X-side error is the one reported. If the X side clamps, Y is still resolved under its own two selections, so a clamped X can be followed by a Y error.

A clamped coordinate is replaced by the nearest declared endpoint knot and then evaluated through this same path. Nothing extrapolates: the result of a clamped evaluation is a value the surface actually declares on its boundary.

use ph_surfaces::{BilinearSurface, Boundary, BoundaryPolicy};

static X: [u16; 2] = [0, 10];
static Y: [u16; 2] = [0, 10];
static VALUES: [[i32; 2]; 2] = [[0, 100], [200, 300]];

static SURFACE: BilinearSurface<2, 2> = BilinearSurface::new(&X, &Y, &VALUES)
    .with_policy(BoundaryPolicy::new().with_x_above(Boundary::Clamp));

// X clamps to 10 and evaluates the boundary column, never past it.
assert_eq!(SURFACE.evaluate(4_000, 0), Ok(100));

// Y still errors on its own side, even though X clamped.
assert_eq!(
    SURFACE.evaluate(4_000, 11),
    Err(ph_surfaces::SurfaceError::YAbove { coordinate: 11, bound: 10 }),
);
§Errors

Returns the SurfaceError variant naming the side the coordinate fell off, carrying the coordinate as supplied and the applicable first or last knot of that axis. Only a side selecting Boundary::Error can produce one.

§Cost

A successful evaluation performs exactly BilinearSurface::SUCCESS_INTERPOLATIONS scalar interpolations and exactly BilinearSurface::SUCCESS_GRID_READS reads of the value grid. Each in-domain axis lookup costs two endpoint comparisons plus the search work of that axis’s strategy — ceil(log2(len)) probes for the default BinaryAxis, and at most AxisLookup::MAX_SEARCH_COMPARISONS comparisons for any of them. A clamped lookup costs one or two endpoint comparisons and performs no probes: the endpoint path, not a search. A rejected coordinate returns before any interpolation or value-grid read, and an X rejection also skips the Y lookup because X is resolved first.

The value grid is never scanned and its size affects only in-domain lookup cost. Evaluation allocates nothing, keeps no state, and has no warm-up, reset, cache, or lifecycle behaviour: the same handle and the same coordinates always produce the same result.

The arithmetic cannot overflow for any surface this crate can define. That is the bound proven for the private scalar helper: both weights are nonnegative and sum to a span of at most 65_535, and each rounded result stays inside the convex hull of its two endpoints. The Y step therefore receives two i32 values drawn from the hull of the four corner values and returns one from the same hull, so there is no overflow outcome to report.

§Examples
use ph_surfaces::{BilinearSurface, SurfaceError};

static X: [u16; 3] = [0, 10, 30];
static Y: [u16; 2] = [0, 100];
static VALUES: [[i32; 3]; 2] = [[0, 10, 30], [100, 110, 130]];

static SURFACE: BilinearSurface<3, 2> = BilinearSurface::new(&X, &Y, &VALUES);

// A declared knot returns its stored value exactly.
assert_eq!(SURFACE.evaluate(10, 100), Ok(110));

// An interior point of the plane.
assert_eq!(SURFACE.evaluate(20, 50), Ok(70));

// Out of domain on the default Error policy.
assert_eq!(
    SURFACE.evaluate(31, 0),
    Err(SurfaceError::XAbove { coordinate: 31, bound: 30 }),
);
Examples found in repository?
examples/firmware_quickstart.rs (line 28)
25fn main() {
26    // Declared knot: the three interpolation steps land exactly on the stored
27    // value, so it is recovered without numerical drift.
28    assert_eq!(SURFACE.evaluate(100, 10), Ok(0));
29    // Interior operating point from docs/interpolation-walkthrough.md:
30    // lower-X 25, upper-X 75, Y 50.
31    assert_eq!(SURFACE.evaluate(125, 20), Ok(50));
32    assert_eq!(
33        SURFACE.evaluate(0, 20),
34        Err(SurfaceError::XBelow {
35            coordinate: 0,
36            bound: 100
37        })
38    );
39
40    assert_eq!(BilinearSurface::<2, 2>::VALUE_BYTES, 16);
41    assert_eq!(BilinearSurface::<2, 2>::PAYLOAD_BYTES, 24);
42    assert_eq!(BilinearSurface::<2, 2>::SUCCESS_INTERPOLATIONS, 3);
43    assert_eq!(BilinearSurface::<2, 2>::SUCCESS_GRID_READS, 4);
44    assert_eq!(<BinaryAxis<2>>::MAX_SEARCH_COMPARISONS, 1);
45    assert_eq!(
46        BilinearSurface::<2, 2>::HANDLE_BYTES,
47        core::mem::size_of::<BilinearSurface<2, 2>>()
48    );
49}
More examples
Hide additional examples
examples/uniform_sensor_compensation.rs (line 35)
28fn main() {
29    // Endpoint accessors reconstruct the arithmetic progression.
30    assert_eq!(SURFACE.x_knot(0), 0);
31    assert_eq!(SURFACE.x_knot(2), 200);
32    assert_eq!(SURFACE.y_knot(1), 50);
33
34    // Interior: lower-X 10, upper-X 20, Y 15. Hand-computable plane.
35    assert_eq!(SURFACE.evaluate(50, 25), Ok(15));
36    assert_eq!(SURFACE.evaluate(200, 100), Ok(60));
37    for x in [0u16, 50, 100, 199, 200] {
38        for y in [0u16, 25, 50, 99, 100] {
39            assert_eq!(SURFACE.evaluate(x, y), DEFAULT.evaluate(x, y));
40        }
41    }
42
43    assert_eq!(<UniformAxis<3, 0, 100>>::KNOT_BYTES, 0);
44    assert_eq!(<UniformAxis<3, 0, 50>>::KNOT_BYTES, 0);
45    assert_eq!(<UniformAxis<3, 0, 100>>::MAX_SEARCH_COMPARISONS, 0);
46    assert_eq!(Compensation::VALUE_BYTES, 36);
47    assert_eq!(Compensation::PAYLOAD_BYTES, 36);
48    assert_eq!(BilinearSurface::<3, 3>::PAYLOAD_BYTES, 48);
49    assert_eq!(
50        Compensation::HANDLE_BYTES,
51        core::mem::size_of::<Compensation>()
52    );
53}
examples/mixed_calibration_map.rs (line 46)
38fn main() {
39    // Nested tuning: discard indexes that do not beat Binary's bound of 5.
40    assert_eq!(max_local_comparisons(&X, &X_INDEX_2), 9); // 4 index bytes; worse
41    assert_eq!(max_local_comparisons(&X, &X_INDEX_4), 5); // 8 bytes; no improvement
42    assert_eq!(max_local_comparisons(&X, &X_INDEX_8), 3); // 16 bytes; meets bound 3
43    assert_eq!(max_local_comparisons(&X, &X_INDEX_16), 2); // 32 bytes; only if 3 is not enough
44    assert_eq!(<BinaryAxis<17>>::MAX_SEARCH_COMPARISONS, 5);
45
46    assert_eq!(MIXED.evaluate(610, 400), DEFAULT.evaluate(610, 400));
47    assert_eq!(MIXED.evaluate(610, 400), Ok(0));
48    assert_eq!(MIXED.y_knot(8), 1_600); // described, not stored
49    for x in [0u16, 100, 610, 1_205, 1_600] {
50        for y in [0u16, 200, 800, 1_600] {
51            assert_eq!(MIXED.evaluate(x, y), DEFAULT.evaluate(x, y));
52        }
53    }
54
55    assert_eq!(<BucketedAxis<17, 8>>::KNOT_BYTES, 34);
56    assert_eq!(<BucketedAxis<17, 8>>::INDEX_BYTES, 16);
57    assert_eq!(<UniformAxis<9, 0, 200>>::KNOT_BYTES, 0);
58    assert_eq!(Mixed::PAYLOAD_BYTES, 662);
59    assert_eq!(AllBinary::PAYLOAD_BYTES, 664);
60}
examples/fail_safe_boundaries.rs (line 39)
32fn main() {
33    assert_eq!(FAIL_SAFE.policy().x_below(), Boundary::Error);
34    assert_eq!(FAIL_SAFE.policy().x_above(), Boundary::Clamp);
35    assert_eq!(FAIL_SAFE.policy().y_below(), Boundary::Error);
36    assert_eq!(FAIL_SAFE.policy().y_above(), Boundary::Clamp);
37
38    // In-domain: policy is idle.
39    assert_eq!(FAIL_SAFE.evaluate(125, 20), Ok(50));
40    assert_eq!(FAIL_SAFE.evaluate(125, 20), STRICT.evaluate(125, 20));
41
42    // Reject uncharacterized low codes.
43    assert_eq!(
44        FAIL_SAFE.evaluate(0, 20),
45        Err(SurfaceError::XBelow {
46            coordinate: 0,
47            bound: 100
48        })
49    );
50    assert_eq!(
51        FAIL_SAFE.evaluate(125, 0),
52        Err(SurfaceError::YBelow {
53            coordinate: 0,
54            bound: 10
55        })
56    );
57
58    // Hold the last characterized high edge; nothing is extrapolated.
59    assert_eq!(FAIL_SAFE.evaluate(4_000, 20), FAIL_SAFE.evaluate(200, 20));
60    assert_eq!(FAIL_SAFE.evaluate(125, 4_000), FAIL_SAFE.evaluate(125, 30));
61    assert_eq!(FAIL_SAFE.evaluate(4_000, 20), Ok(140));
62    assert_eq!(FAIL_SAFE.evaluate(125, 4_000), Ok(75));
63
64    // Both outside Error sides: X wins and Y is not reported.
65    assert_eq!(
66        STRICT.evaluate(0, 0),
67        Err(SurfaceError::XBelow {
68            coordinate: 0,
69            bound: 100
70        })
71    );
72    // X clamps, Y still errors.
73    assert_eq!(
74        FAIL_SAFE.evaluate(4_000, 0),
75        Err(SurfaceError::YBelow {
76            coordinate: 0,
77            bound: 10
78        })
79    );
80}
examples/firmware_cost_budget.rs (line 34)
20fn tiny_linear_linear() {
21    // 3×2 firmware table. Linear is the "tiny axis" starting point; Binary
22    // stores the same knots and, on this shape, the same comparison bound.
23    static X: [u16; 3] = [0, 10, 20];
24    static Y: [u16; 2] = [0, 100];
25    static VALUES: [[i32; 3]; 2] = [[0, 1, 2], [10, 11, 12]];
26
27    type TinyLinear = BilinearSurface<3, 2, LinearAxis<3>, LinearAxis<2>>;
28    type TinyBinary = BilinearSurface<3, 2>;
29
30    static LINEAR: TinyLinear =
31        BilinearSurface::from_axes(LinearAxis::new(&X), LinearAxis::new(&Y), &VALUES);
32    static BINARY: TinyBinary = BilinearSurface::new(&X, &Y, &VALUES);
33
34    assert_eq!(LINEAR.evaluate(10, 100), Ok(11));
35    assert_eq!(LINEAR.evaluate(10, 100), BINARY.evaluate(10, 100));
36
37    // Referenced payload: 4*3*2 values + 2*3 + 2*2 knots = 24 + 10 = 34.
38    assert_eq!(TinyLinear::VALUE_BYTES, 24);
39    assert_eq!(TinyLinear::PAYLOAD_BYTES, 34);
40    assert_eq!(TinyBinary::PAYLOAD_BYTES, 34);
41    assert_eq!(TinyLinear::HANDLE_BYTES, core::mem::size_of::<TinyLinear>());
42
43    // Work: four successful endpoint comparisons plus (3-1)+(2-1) = 3 search
44    // comparisons = 7 knot comparisons. Binary is ceil(log2(3))+ceil(log2(2))
45    // = 2+1 = 3 search comparisons as well. Choosing Linear vs Binary on this
46    // shape requires target code/timing evidence, not a universal threshold.
47    assert_eq!(<LinearAxis<3>>::MAX_SEARCH_COMPARISONS, 2);
48    assert_eq!(<LinearAxis<2>>::MAX_SEARCH_COMPARISONS, 1);
49    assert_eq!(<BinaryAxis<3>>::MAX_SEARCH_COMPARISONS, 2);
50    assert_eq!(<BinaryAxis<2>>::MAX_SEARCH_COMPARISONS, 1);
51    assert_eq!(TinyLinear::SUCCESS_INTERPOLATIONS, 3);
52    assert_eq!(TinyLinear::SUCCESS_GRID_READS, 4);
53}
54
55fn uniform_uniform_17x9() {
56    // Both axes are exact arithmetic progressions, so Uniform stores no knots.
57    // Location is a subtraction and a division by a compile-time STEP — zero
58    // knot comparisons, not zero cycles.
59    type UniformPair = BilinearSurface<17, 9, UniformAxis<17, 0, 100>, UniformAxis<9, 0, 200>>;
60    type AllBinary = BilinearSurface<17, 9>;
61
62    static VALUES: [[i32; 17]; 9] = [[0; 17]; 9];
63    static SURFACE: UniformPair =
64        BilinearSurface::from_axes(UniformAxis::new(), UniformAxis::new(), &VALUES);
65
66    assert_eq!(SURFACE.evaluate(100, 200), Ok(0));
67    assert_eq!(SURFACE.x_knot(16), 1_600);
68    assert_eq!(SURFACE.y_knot(8), 1_600);
69
70    assert_eq!(UniformPair::VALUE_BYTES, 612);
71    assert_eq!(UniformPair::PAYLOAD_BYTES, 612);
72    assert_eq!(AllBinary::PAYLOAD_BYTES, 664);
73    assert_eq!(UniformPair::PAYLOAD_BYTES + 52, AllBinary::PAYLOAD_BYTES);
74    assert_eq!(<UniformAxis<17, 0, 100>>::KNOT_BYTES, 0);
75    assert_eq!(<UniformAxis<17, 0, 100>>::MAX_SEARCH_COMPARISONS, 0);
76    assert_eq!(<UniformAxis<9, 0, 200>>::MAX_SEARCH_COMPARISONS, 0);
77    assert_eq!(UniformPair::SUCCESS_INTERPOLATIONS, 3);
78    assert_eq!(UniformPair::SUCCESS_GRID_READS, 4);
79    assert_eq!(
80        UniformPair::HANDLE_BYTES,
81        core::mem::size_of::<UniformPair>()
82    );
83}
84
85fn mixed_bucketed_uniform_17x9() {
86    static X: [u16; 17] = [
87        0, 100, 210, 300, 405, 500, 610, 700, 805, 900, 1_010, 1_100, 1_205, 1_300, 1_410, 1_500,
88        1_600,
89    ];
90    static X_INDEX: [u16; 8] = bucket_index(&X);
91    static VALUES: [[i32; 17]; 9] = [[0; 17]; 9];
92
93    type Mixed = BilinearSurface<17, 9, BucketedAxis<17, 8>, UniformAxis<9, 0, 200>>;
94    type AllBinary = BilinearSurface<17, 9>;
95
96    static MIXED: Mixed =
97        BilinearSurface::from_axes(BucketedAxis::new(&X, &X_INDEX), UniformAxis::new(), &VALUES);
98
99    assert_eq!(MIXED.evaluate(1_600, 1_600), Ok(0));
100
101    // Payload: 612 grid + 34 X knots + 16 X index + 0 Y = 662. Binary is 664.
102    assert_eq!(Mixed::VALUE_BYTES, 612);
103    assert_eq!(<BucketedAxis<17, 8>>::KNOT_BYTES, 34);
104    assert_eq!(<BucketedAxis<17, 8>>::INDEX_BYTES, 16);
105    assert_eq!(<UniformAxis<9, 0, 200>>::KNOT_BYTES, 0);
106    assert_eq!(Mixed::PAYLOAD_BYTES, 662);
107    assert_eq!(AllBinary::PAYLOAD_BYTES, 664);
108    assert_eq!(max_local_comparisons(&X, &X_INDEX), 3);
109
110    // Work: four endpoint comparisons, plus at most 3 X local comparisons and
111    // 0 Y comparisons = 7 knot comparisons, versus 4 + 5 + 4 = 13 for
112    // Binary/Binary. Each Bucketed search also reads one bucket and maps the
113    // coordinate arithmetically; that is not included in the comparison count
114    // and is not a cycle count.
115    assert_eq!(<BinaryAxis<17>>::MAX_SEARCH_COMPARISONS, 5);
116    assert_eq!(<BinaryAxis<9>>::MAX_SEARCH_COMPARISONS, 4);
117    assert_eq!(<UniformAxis<9, 0, 200>>::MAX_SEARCH_COMPARISONS, 0);
118    assert_eq!(Mixed::SUCCESS_INTERPOLATIONS, 3);
119    assert_eq!(Mixed::SUCCESS_GRID_READS, 4);
120    assert_eq!(Mixed::HANDLE_BYTES, core::mem::size_of::<Mixed>());
121}
Source§

impl<const NX: usize, const NY: usize> BilinearSurface<NX, NY, BinaryAxis<NX>, BinaryAxis<NY>>

Source

pub const fn new( x_axis: &'static [u16; NX], y_axis: &'static [u16; NY], values: &'static [[i32; NX]; NY], ) -> Self

Declares a surface over static axes and a static row-major value grid, with both axes located by binary search.

This is the general-purpose constructor and the one to reach for unless an axis has a reason to choose otherwise; see BilinearSurface::from_axes for the surfaces that do.

Every domain side defaults to Boundary::Error; use BilinearSurface::with_policy to select clamping on any side.

§Panics

Panics unless both axes declare at least two knots and both axes are strictly increasing. In a constant or static definition that panic is a compile error, so an invalid surface cannot be defined.

A single X knot is rejected:

use ph_surfaces::BilinearSurface;

static X: [u16; 1] = [0];
static Y: [u16; 2] = [0, 10];
static VALUES: [[i32; 1]; 2] = [[0], [1]];

static SURFACE: BilinearSurface<1, 2> = BilinearSurface::new(&X, &Y, &VALUES);

An empty Y axis is rejected:

use ph_surfaces::BilinearSurface;

static X: [u16; 2] = [0, 10];
static Y: [u16; 0] = [];
static VALUES: [[i32; 2]; 0] = [];

static SURFACE: BilinearSurface<2, 0> = BilinearSurface::new(&X, &Y, &VALUES);

A duplicated X knot is rejected:

use ph_surfaces::BilinearSurface;

static X: [u16; 2] = [5, 5];
static Y: [u16; 2] = [0, 10];
static VALUES: [[i32; 2]; 2] = [[0, 1], [2, 3]];

static SURFACE: BilinearSurface<2, 2> = BilinearSurface::new(&X, &Y, &VALUES);

A descending X axis is rejected:

use ph_surfaces::BilinearSurface;

static X: [u16; 2] = [10, 0];
static Y: [u16; 2] = [0, 10];
static VALUES: [[i32; 2]; 2] = [[0, 1], [2, 3]];

static SURFACE: BilinearSurface<2, 2> = BilinearSurface::new(&X, &Y, &VALUES);

A duplicated Y knot is rejected independently of the X axis:

use ph_surfaces::BilinearSurface;

static X: [u16; 2] = [0, 10];
static Y: [u16; 2] = [5, 5];
static VALUES: [[i32; 2]; 2] = [[0, 1], [2, 3]];

static SURFACE: BilinearSurface<2, 2> = BilinearSurface::new(&X, &Y, &VALUES);

A descending Y axis is rejected independently of the X axis:

use ph_surfaces::BilinearSurface;

static X: [u16; 2] = [0, 10];
static Y: [u16; 2] = [10, 0];
static VALUES: [[i32; 2]; 2] = [[0, 1], [2, 3]];

static SURFACE: BilinearSurface<2, 2> = BilinearSurface::new(&X, &Y, &VALUES);
Examples found in repository?
examples/mixed_calibration_map.rs (line 36)
36static DEFAULT: AllBinary = BilinearSurface::new(&X, &Y, &VALUES);
More examples
Hide additional examples
examples/firmware_cost_budget.rs (line 32)
32    static BINARY: TinyBinary = BilinearSurface::new(&X, &Y, &VALUES);
examples/fail_safe_boundaries.rs (line 22)
22static STRICT: BilinearSurface<2, 2> = BilinearSurface::new(&X, &Y, &VALUES);
23
24static FAIL_SAFE: BilinearSurface<2, 2> = BilinearSurface::new(&X, &Y, &VALUES).with_policy(
25    BoundaryPolicy::new()
26        .with_x_below(Boundary::Error)
27        .with_x_above(Boundary::Clamp)
28        .with_y_below(Boundary::Error)
29        .with_y_above(Boundary::Clamp),
30);
examples/firmware_quickstart.rs (line 23)
23static SURFACE: BilinearSurface<2, 2> = BilinearSurface::new(&X, &Y, &VALUES);
examples/uniform_sensor_compensation.rs (line 26)
26static DEFAULT: BilinearSurface<3, 3> = BilinearSurface::new(&X, &Y, &VALUES);
Source§

impl<const NX: usize, const NY: usize, X: AxisLookup<NX>, Y: AxisLookup<NY>> BilinearSurface<NX, NY, X, Y>

Source

pub const VALUE_BYTES: usize

Bytes of the referenced value grid: NX*NY elements of i32.

Exact and target-independent. It excludes the axis tables, the handle, alignment, code, and stack, and it is not a total memory figure.

Source

pub const PAYLOAD_BYTES: usize

Bytes of referenced table elements this surface names: X::KNOT_BYTES + X::INDEX_BYTES + Y::KNOT_BYTES + Y::INDEX_BYTES + VALUE_BYTES.

Exact and target-independent. For the default binary pairing it equals 2*NX + 2*NY + 4*NX*NY. It is only the referenced element payload: not total RAM, flash, binary, or linker cost.

Source

pub const HANDLE_BYTES: usize

Size of this handle on the current target, including alignment padding.

Target-dependent: it follows pointer width and the selected strategies’ fields (Uniform stores no axis reference, Linear/Binary one, Bucketed two), plus the value-grid reference and the four-byte policy. It does not grow with NX or NY for a fixed pairing, and it is not a flash or binary cost.

Source

pub const SUCCESS_INTERPOLATIONS: u32 = 3

Scalar interpolations a successful evaluate performs.

Always three: X on the lower-Y row, X on the upper-Y row, then Y between those two already-rounded results. A rejected evaluation returns before any of them. This is operation structure, not a cycle count.

Source

pub const SUCCESS_GRID_READS: u32 = 4

Value-grid reads a successful evaluate performs.

Always four: the corners of the located cell. A rejected evaluation returns before any of them. The grid is never scanned. This is operation structure, not a cycle count.

Source

pub const fn from_axes(x: X, y: Y, values: &'static [[i32; NX]; NY]) -> Self

Declares a surface over two axes that have already chosen their lookup strategies, and a static row-major value grid.

Each axis validated itself when it was declared, so there is nothing left to reject here: the knot counts are carried in the axis types, so an axis that does not match the grid is a type error rather than a runtime one.

Every domain side defaults to Boundary::Error; use BilinearSurface::with_policy to select clamping on any side.

§Examples

A surface whose X axis is evenly spaced — so its knots are described rather than stored — and whose Y axis keeps the default strategy:

use ph_surfaces::{BilinearSurface, BinaryAxis, UniformAxis};

static Y: [u16; 2] = [0, 10];
static VALUES: [[i32; 5]; 2] = [[0, 25, 50, 75, 100], [10, 35, 60, 85, 110]];

static SURFACE: BilinearSurface<5, 2, UniformAxis<5, 0, 25>, BinaryAxis<2>> =
    BilinearSurface::from_axes(UniformAxis::new(), BinaryAxis::new(&Y), &VALUES);

assert_eq!(SURFACE.evaluate(50, 0), Ok(50));
assert_eq!(SURFACE.x_knot(1), 25);
Examples found in repository?
examples/uniform_sensor_compensation.rs (line 21)
20static SURFACE: Compensation =
21    BilinearSurface::from_axes(UniformAxis::new(), UniformAxis::new(), &VALUES);
More examples
Hide additional examples
examples/firmware_cost_budget.rs (line 31)
30    static LINEAR: TinyLinear =
31        BilinearSurface::from_axes(LinearAxis::new(&X), LinearAxis::new(&Y), &VALUES);
32    static BINARY: TinyBinary = BilinearSurface::new(&X, &Y, &VALUES);
33
34    assert_eq!(LINEAR.evaluate(10, 100), Ok(11));
35    assert_eq!(LINEAR.evaluate(10, 100), BINARY.evaluate(10, 100));
36
37    // Referenced payload: 4*3*2 values + 2*3 + 2*2 knots = 24 + 10 = 34.
38    assert_eq!(TinyLinear::VALUE_BYTES, 24);
39    assert_eq!(TinyLinear::PAYLOAD_BYTES, 34);
40    assert_eq!(TinyBinary::PAYLOAD_BYTES, 34);
41    assert_eq!(TinyLinear::HANDLE_BYTES, core::mem::size_of::<TinyLinear>());
42
43    // Work: four successful endpoint comparisons plus (3-1)+(2-1) = 3 search
44    // comparisons = 7 knot comparisons. Binary is ceil(log2(3))+ceil(log2(2))
45    // = 2+1 = 3 search comparisons as well. Choosing Linear vs Binary on this
46    // shape requires target code/timing evidence, not a universal threshold.
47    assert_eq!(<LinearAxis<3>>::MAX_SEARCH_COMPARISONS, 2);
48    assert_eq!(<LinearAxis<2>>::MAX_SEARCH_COMPARISONS, 1);
49    assert_eq!(<BinaryAxis<3>>::MAX_SEARCH_COMPARISONS, 2);
50    assert_eq!(<BinaryAxis<2>>::MAX_SEARCH_COMPARISONS, 1);
51    assert_eq!(TinyLinear::SUCCESS_INTERPOLATIONS, 3);
52    assert_eq!(TinyLinear::SUCCESS_GRID_READS, 4);
53}
54
55fn uniform_uniform_17x9() {
56    // Both axes are exact arithmetic progressions, so Uniform stores no knots.
57    // Location is a subtraction and a division by a compile-time STEP — zero
58    // knot comparisons, not zero cycles.
59    type UniformPair = BilinearSurface<17, 9, UniformAxis<17, 0, 100>, UniformAxis<9, 0, 200>>;
60    type AllBinary = BilinearSurface<17, 9>;
61
62    static VALUES: [[i32; 17]; 9] = [[0; 17]; 9];
63    static SURFACE: UniformPair =
64        BilinearSurface::from_axes(UniformAxis::new(), UniformAxis::new(), &VALUES);
65
66    assert_eq!(SURFACE.evaluate(100, 200), Ok(0));
67    assert_eq!(SURFACE.x_knot(16), 1_600);
68    assert_eq!(SURFACE.y_knot(8), 1_600);
69
70    assert_eq!(UniformPair::VALUE_BYTES, 612);
71    assert_eq!(UniformPair::PAYLOAD_BYTES, 612);
72    assert_eq!(AllBinary::PAYLOAD_BYTES, 664);
73    assert_eq!(UniformPair::PAYLOAD_BYTES + 52, AllBinary::PAYLOAD_BYTES);
74    assert_eq!(<UniformAxis<17, 0, 100>>::KNOT_BYTES, 0);
75    assert_eq!(<UniformAxis<17, 0, 100>>::MAX_SEARCH_COMPARISONS, 0);
76    assert_eq!(<UniformAxis<9, 0, 200>>::MAX_SEARCH_COMPARISONS, 0);
77    assert_eq!(UniformPair::SUCCESS_INTERPOLATIONS, 3);
78    assert_eq!(UniformPair::SUCCESS_GRID_READS, 4);
79    assert_eq!(
80        UniformPair::HANDLE_BYTES,
81        core::mem::size_of::<UniformPair>()
82    );
83}
84
85fn mixed_bucketed_uniform_17x9() {
86    static X: [u16; 17] = [
87        0, 100, 210, 300, 405, 500, 610, 700, 805, 900, 1_010, 1_100, 1_205, 1_300, 1_410, 1_500,
88        1_600,
89    ];
90    static X_INDEX: [u16; 8] = bucket_index(&X);
91    static VALUES: [[i32; 17]; 9] = [[0; 17]; 9];
92
93    type Mixed = BilinearSurface<17, 9, BucketedAxis<17, 8>, UniformAxis<9, 0, 200>>;
94    type AllBinary = BilinearSurface<17, 9>;
95
96    static MIXED: Mixed =
97        BilinearSurface::from_axes(BucketedAxis::new(&X, &X_INDEX), UniformAxis::new(), &VALUES);
examples/mixed_calibration_map.rs (lines 31-35)
31static MIXED: Mixed = BilinearSurface::from_axes(
32    BucketedAxis::new(&X, &X_INDEX_8),
33    UniformAxis::new(),
34    &VALUES,
35);
Source

pub const fn with_policy(self, policy: BoundaryPolicy) -> Self

Returns this surface with its boundary policy replaced.

The referenced tables are unchanged; only the four domain-side selections differ.

Examples found in repository?
examples/fail_safe_boundaries.rs (lines 24-30)
24static FAIL_SAFE: BilinearSurface<2, 2> = BilinearSurface::new(&X, &Y, &VALUES).with_policy(
25    BoundaryPolicy::new()
26        .with_x_below(Boundary::Error)
27        .with_x_above(Boundary::Clamp)
28        .with_y_below(Boundary::Error)
29        .with_y_above(Boundary::Clamp),
30);
Source

pub const fn x(&self) -> &X

Returns the X axis together with its lookup strategy.

This is the generic route to the axis: through it, code bounded on AxisLookup (or KnotArray for the stored strategies) can read the domain bounds, individual knots, and cost constants of any surface’s axis without carrying the knot arrays separately. The strategy-specific x_knot / x_min / x_max accessors remain the constant-context conveniences.

Source

pub const fn y(&self) -> &Y

Returns the Y axis together with its lookup strategy.

The Y counterpart of x; see there.

Source

pub const fn values(&self) -> &'static [[i32; NX]; NY]

Returns the declared row-major value grid, addressed as values[y][x].

Source

pub const fn nx(&self) -> usize

Returns the number of X knots.

Source

pub const fn ny(&self) -> usize

Returns the number of Y knots.

Source

pub const fn policy(&self) -> BoundaryPolicy

Returns the four domain-side selections.

Examples found in repository?
examples/fail_safe_boundaries.rs (line 33)
32fn main() {
33    assert_eq!(FAIL_SAFE.policy().x_below(), Boundary::Error);
34    assert_eq!(FAIL_SAFE.policy().x_above(), Boundary::Clamp);
35    assert_eq!(FAIL_SAFE.policy().y_below(), Boundary::Error);
36    assert_eq!(FAIL_SAFE.policy().y_above(), Boundary::Clamp);
37
38    // In-domain: policy is idle.
39    assert_eq!(FAIL_SAFE.evaluate(125, 20), Ok(50));
40    assert_eq!(FAIL_SAFE.evaluate(125, 20), STRICT.evaluate(125, 20));
41
42    // Reject uncharacterized low codes.
43    assert_eq!(
44        FAIL_SAFE.evaluate(0, 20),
45        Err(SurfaceError::XBelow {
46            coordinate: 0,
47            bound: 100
48        })
49    );
50    assert_eq!(
51        FAIL_SAFE.evaluate(125, 0),
52        Err(SurfaceError::YBelow {
53            coordinate: 0,
54            bound: 10
55        })
56    );
57
58    // Hold the last characterized high edge; nothing is extrapolated.
59    assert_eq!(FAIL_SAFE.evaluate(4_000, 20), FAIL_SAFE.evaluate(200, 20));
60    assert_eq!(FAIL_SAFE.evaluate(125, 4_000), FAIL_SAFE.evaluate(125, 30));
61    assert_eq!(FAIL_SAFE.evaluate(4_000, 20), Ok(140));
62    assert_eq!(FAIL_SAFE.evaluate(125, 4_000), Ok(75));
63
64    // Both outside Error sides: X wins and Y is not reported.
65    assert_eq!(
66        STRICT.evaluate(0, 0),
67        Err(SurfaceError::XBelow {
68            coordinate: 0,
69            bound: 100
70        })
71    );
72    // X clamps, Y still errors.
73    assert_eq!(
74        FAIL_SAFE.evaluate(4_000, 0),
75        Err(SurfaceError::YBelow {
76            coordinate: 0,
77            bound: 10
78        })
79    );
80}
Source§

impl<const NX: usize, const NY: usize, Y: AxisLookup<NY>> BilinearSurface<NX, NY, BinaryAxis<NX>, Y>

Source

pub const fn x_knot(&self, index: usize) -> u16

Returns the X knot at index.

§Panics

Panics if index >= NX.

Source

pub const fn x_min(&self) -> u16

Returns the first X knot: the inclusive lower bound of the X domain.

Source

pub const fn x_max(&self) -> u16

Returns the last X knot: the inclusive upper bound of the X domain.

Source

pub const fn x_axis(&self) -> &'static [u16; NX]

Returns the declared X axis.

Source§

impl<const NX: usize, const NY: usize, Y: AxisLookup<NY>> BilinearSurface<NX, NY, LinearAxis<NX>, Y>

Source

pub const fn x_knot(&self, index: usize) -> u16

Returns the X knot at index.

§Panics

Panics if index >= NX.

Source

pub const fn x_min(&self) -> u16

Returns the first X knot: the inclusive lower bound of the X domain.

Source

pub const fn x_max(&self) -> u16

Returns the last X knot: the inclusive upper bound of the X domain.

Source

pub const fn x_axis(&self) -> &'static [u16; NX]

Returns the declared X axis.

Source§

impl<const NX: usize, const NY: usize, X: AxisLookup<NX>> BilinearSurface<NX, NY, X, BinaryAxis<NY>>

Source

pub const fn y_knot(&self, index: usize) -> u16

Returns the Y knot at index.

§Panics

Panics if index >= NY.

Source

pub const fn y_min(&self) -> u16

Returns the first Y knot: the inclusive lower bound of the Y domain.

Source

pub const fn y_max(&self) -> u16

Returns the last Y knot: the inclusive upper bound of the Y domain.

Source

pub const fn y_axis(&self) -> &'static [u16; NY]

Returns the declared Y axis.

Source§

impl<const NX: usize, const NY: usize, X: AxisLookup<NX>> BilinearSurface<NX, NY, X, LinearAxis<NY>>

Source

pub const fn y_knot(&self, index: usize) -> u16

Returns the Y knot at index.

§Panics

Panics if index >= NY.

Source

pub const fn y_min(&self) -> u16

Returns the first Y knot: the inclusive lower bound of the Y domain.

Source

pub const fn y_max(&self) -> u16

Returns the last Y knot: the inclusive upper bound of the Y domain.

Source

pub const fn y_axis(&self) -> &'static [u16; NY]

Returns the declared Y axis.

Source§

impl<const NX: usize, const NY: usize, const B: usize, Y: AxisLookup<NY>> BilinearSurface<NX, NY, BucketedAxis<NX, B>, Y>

Source

pub const fn x_knot(&self, index: usize) -> u16

Returns the X knot at index.

§Panics

Panics if index >= NX.

Source

pub const fn x_min(&self) -> u16

Returns the first X knot: the inclusive lower bound of the X domain.

Source

pub const fn x_max(&self) -> u16

Returns the last X knot: the inclusive upper bound of the X domain.

Source

pub const fn x_axis(&self) -> &'static [u16; NX]

Returns the declared X axis.

Source§

impl<const NX: usize, const NY: usize, const B: usize, X: AxisLookup<NX>> BilinearSurface<NX, NY, X, BucketedAxis<NY, B>>

Source

pub const fn y_knot(&self, index: usize) -> u16

Returns the Y knot at index.

§Panics

Panics if index >= NY.

Source

pub const fn y_min(&self) -> u16

Returns the first Y knot: the inclusive lower bound of the Y domain.

Source

pub const fn y_max(&self) -> u16

Returns the last Y knot: the inclusive upper bound of the Y domain.

Source

pub const fn y_axis(&self) -> &'static [u16; NY]

Returns the declared Y axis.

Source§

impl<const NX: usize, const NY: usize, const ORIGIN: u16, const STEP: u16, Y: AxisLookup<NY>> BilinearSurface<NX, NY, UniformAxis<NX, ORIGIN, STEP>, Y>

Source

pub const fn x_knot(&self, index: usize) -> u16

Returns the X knot at index, calculated from the uniform descriptor.

§Panics

Panics if index >= NX.

Examples found in repository?
examples/uniform_sensor_compensation.rs (line 30)
28fn main() {
29    // Endpoint accessors reconstruct the arithmetic progression.
30    assert_eq!(SURFACE.x_knot(0), 0);
31    assert_eq!(SURFACE.x_knot(2), 200);
32    assert_eq!(SURFACE.y_knot(1), 50);
33
34    // Interior: lower-X 10, upper-X 20, Y 15. Hand-computable plane.
35    assert_eq!(SURFACE.evaluate(50, 25), Ok(15));
36    assert_eq!(SURFACE.evaluate(200, 100), Ok(60));
37    for x in [0u16, 50, 100, 199, 200] {
38        for y in [0u16, 25, 50, 99, 100] {
39            assert_eq!(SURFACE.evaluate(x, y), DEFAULT.evaluate(x, y));
40        }
41    }
42
43    assert_eq!(<UniformAxis<3, 0, 100>>::KNOT_BYTES, 0);
44    assert_eq!(<UniformAxis<3, 0, 50>>::KNOT_BYTES, 0);
45    assert_eq!(<UniformAxis<3, 0, 100>>::MAX_SEARCH_COMPARISONS, 0);
46    assert_eq!(Compensation::VALUE_BYTES, 36);
47    assert_eq!(Compensation::PAYLOAD_BYTES, 36);
48    assert_eq!(BilinearSurface::<3, 3>::PAYLOAD_BYTES, 48);
49    assert_eq!(
50        Compensation::HANDLE_BYTES,
51        core::mem::size_of::<Compensation>()
52    );
53}
More examples
Hide additional examples
examples/firmware_cost_budget.rs (line 67)
55fn uniform_uniform_17x9() {
56    // Both axes are exact arithmetic progressions, so Uniform stores no knots.
57    // Location is a subtraction and a division by a compile-time STEP — zero
58    // knot comparisons, not zero cycles.
59    type UniformPair = BilinearSurface<17, 9, UniformAxis<17, 0, 100>, UniformAxis<9, 0, 200>>;
60    type AllBinary = BilinearSurface<17, 9>;
61
62    static VALUES: [[i32; 17]; 9] = [[0; 17]; 9];
63    static SURFACE: UniformPair =
64        BilinearSurface::from_axes(UniformAxis::new(), UniformAxis::new(), &VALUES);
65
66    assert_eq!(SURFACE.evaluate(100, 200), Ok(0));
67    assert_eq!(SURFACE.x_knot(16), 1_600);
68    assert_eq!(SURFACE.y_knot(8), 1_600);
69
70    assert_eq!(UniformPair::VALUE_BYTES, 612);
71    assert_eq!(UniformPair::PAYLOAD_BYTES, 612);
72    assert_eq!(AllBinary::PAYLOAD_BYTES, 664);
73    assert_eq!(UniformPair::PAYLOAD_BYTES + 52, AllBinary::PAYLOAD_BYTES);
74    assert_eq!(<UniformAxis<17, 0, 100>>::KNOT_BYTES, 0);
75    assert_eq!(<UniformAxis<17, 0, 100>>::MAX_SEARCH_COMPARISONS, 0);
76    assert_eq!(<UniformAxis<9, 0, 200>>::MAX_SEARCH_COMPARISONS, 0);
77    assert_eq!(UniformPair::SUCCESS_INTERPOLATIONS, 3);
78    assert_eq!(UniformPair::SUCCESS_GRID_READS, 4);
79    assert_eq!(
80        UniformPair::HANDLE_BYTES,
81        core::mem::size_of::<UniformPair>()
82    );
83}
Source

pub const fn x_min(&self) -> u16

Returns the first X knot: the inclusive lower bound of the X domain.

Source

pub const fn x_max(&self) -> u16

Returns the last X knot: the inclusive upper bound of the X domain.

Source§

impl<const NX: usize, const NY: usize, X: AxisLookup<NX>, const ORIGIN: u16, const STEP: u16> BilinearSurface<NX, NY, X, UniformAxis<NY, ORIGIN, STEP>>

Source

pub const fn y_knot(&self, index: usize) -> u16

Returns the Y knot at index, calculated from the uniform descriptor.

§Panics

Panics if index >= NY.

Examples found in repository?
examples/uniform_sensor_compensation.rs (line 32)
28fn main() {
29    // Endpoint accessors reconstruct the arithmetic progression.
30    assert_eq!(SURFACE.x_knot(0), 0);
31    assert_eq!(SURFACE.x_knot(2), 200);
32    assert_eq!(SURFACE.y_knot(1), 50);
33
34    // Interior: lower-X 10, upper-X 20, Y 15. Hand-computable plane.
35    assert_eq!(SURFACE.evaluate(50, 25), Ok(15));
36    assert_eq!(SURFACE.evaluate(200, 100), Ok(60));
37    for x in [0u16, 50, 100, 199, 200] {
38        for y in [0u16, 25, 50, 99, 100] {
39            assert_eq!(SURFACE.evaluate(x, y), DEFAULT.evaluate(x, y));
40        }
41    }
42
43    assert_eq!(<UniformAxis<3, 0, 100>>::KNOT_BYTES, 0);
44    assert_eq!(<UniformAxis<3, 0, 50>>::KNOT_BYTES, 0);
45    assert_eq!(<UniformAxis<3, 0, 100>>::MAX_SEARCH_COMPARISONS, 0);
46    assert_eq!(Compensation::VALUE_BYTES, 36);
47    assert_eq!(Compensation::PAYLOAD_BYTES, 36);
48    assert_eq!(BilinearSurface::<3, 3>::PAYLOAD_BYTES, 48);
49    assert_eq!(
50        Compensation::HANDLE_BYTES,
51        core::mem::size_of::<Compensation>()
52    );
53}
More examples
Hide additional examples
examples/mixed_calibration_map.rs (line 48)
38fn main() {
39    // Nested tuning: discard indexes that do not beat Binary's bound of 5.
40    assert_eq!(max_local_comparisons(&X, &X_INDEX_2), 9); // 4 index bytes; worse
41    assert_eq!(max_local_comparisons(&X, &X_INDEX_4), 5); // 8 bytes; no improvement
42    assert_eq!(max_local_comparisons(&X, &X_INDEX_8), 3); // 16 bytes; meets bound 3
43    assert_eq!(max_local_comparisons(&X, &X_INDEX_16), 2); // 32 bytes; only if 3 is not enough
44    assert_eq!(<BinaryAxis<17>>::MAX_SEARCH_COMPARISONS, 5);
45
46    assert_eq!(MIXED.evaluate(610, 400), DEFAULT.evaluate(610, 400));
47    assert_eq!(MIXED.evaluate(610, 400), Ok(0));
48    assert_eq!(MIXED.y_knot(8), 1_600); // described, not stored
49    for x in [0u16, 100, 610, 1_205, 1_600] {
50        for y in [0u16, 200, 800, 1_600] {
51            assert_eq!(MIXED.evaluate(x, y), DEFAULT.evaluate(x, y));
52        }
53    }
54
55    assert_eq!(<BucketedAxis<17, 8>>::KNOT_BYTES, 34);
56    assert_eq!(<BucketedAxis<17, 8>>::INDEX_BYTES, 16);
57    assert_eq!(<UniformAxis<9, 0, 200>>::KNOT_BYTES, 0);
58    assert_eq!(Mixed::PAYLOAD_BYTES, 662);
59    assert_eq!(AllBinary::PAYLOAD_BYTES, 664);
60}
examples/firmware_cost_budget.rs (line 68)
55fn uniform_uniform_17x9() {
56    // Both axes are exact arithmetic progressions, so Uniform stores no knots.
57    // Location is a subtraction and a division by a compile-time STEP — zero
58    // knot comparisons, not zero cycles.
59    type UniformPair = BilinearSurface<17, 9, UniformAxis<17, 0, 100>, UniformAxis<9, 0, 200>>;
60    type AllBinary = BilinearSurface<17, 9>;
61
62    static VALUES: [[i32; 17]; 9] = [[0; 17]; 9];
63    static SURFACE: UniformPair =
64        BilinearSurface::from_axes(UniformAxis::new(), UniformAxis::new(), &VALUES);
65
66    assert_eq!(SURFACE.evaluate(100, 200), Ok(0));
67    assert_eq!(SURFACE.x_knot(16), 1_600);
68    assert_eq!(SURFACE.y_knot(8), 1_600);
69
70    assert_eq!(UniformPair::VALUE_BYTES, 612);
71    assert_eq!(UniformPair::PAYLOAD_BYTES, 612);
72    assert_eq!(AllBinary::PAYLOAD_BYTES, 664);
73    assert_eq!(UniformPair::PAYLOAD_BYTES + 52, AllBinary::PAYLOAD_BYTES);
74    assert_eq!(<UniformAxis<17, 0, 100>>::KNOT_BYTES, 0);
75    assert_eq!(<UniformAxis<17, 0, 100>>::MAX_SEARCH_COMPARISONS, 0);
76    assert_eq!(<UniformAxis<9, 0, 200>>::MAX_SEARCH_COMPARISONS, 0);
77    assert_eq!(UniformPair::SUCCESS_INTERPOLATIONS, 3);
78    assert_eq!(UniformPair::SUCCESS_GRID_READS, 4);
79    assert_eq!(
80        UniformPair::HANDLE_BYTES,
81        core::mem::size_of::<UniformPair>()
82    );
83}
Source

pub const fn y_min(&self) -> u16

Returns the first Y knot: the inclusive lower bound of the Y domain.

Source

pub const fn y_max(&self) -> u16

Returns the last Y knot: the inclusive upper bound of the Y domain.

Trait Implementations§

Source§

impl<const NX: usize, const NY: usize, X: Clone + AxisLookup<NX>, Y: Clone + AxisLookup<NY>> Clone for BilinearSurface<NX, NY, X, Y>

Source§

fn clone(&self) -> BilinearSurface<NX, NY, X, Y>

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<const NX: usize, const NY: usize, X: Copy + AxisLookup<NX>, Y: Copy + AxisLookup<NY>> Copy for BilinearSurface<NX, NY, X, Y>

Source§

impl<const NX: usize, const NY: usize, X: Debug + AxisLookup<NX>, Y: Debug + AxisLookup<NY>> Debug for BilinearSurface<NX, NY, X, Y>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<const NX: usize, const NY: usize, X: Eq + AxisLookup<NX>, Y: Eq + AxisLookup<NY>> Eq for BilinearSurface<NX, NY, X, Y>

Source§

impl<const NX: usize, const NY: usize, X: Hash + AxisLookup<NX>, Y: Hash + AxisLookup<NY>> Hash for BilinearSurface<NX, NY, X, Y>

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl<const NX: usize, const NY: usize, X: PartialEq + AxisLookup<NX>, Y: PartialEq + AxisLookup<NY>> PartialEq for BilinearSurface<NX, NY, X, Y>

Source§

fn eq(&self, other: &BilinearSurface<NX, NY, X, Y>) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl<const NX: usize, const NY: usize, X: PartialEq + AxisLookup<NX>, Y: PartialEq + AxisLookup<NY>> StructuralPartialEq for BilinearSurface<NX, NY, X, Y>

Auto Trait Implementations§

§

impl<const NX: usize, const NY: usize, X, Y> Freeze for BilinearSurface<NX, NY, X, Y>
where X: Freeze, Y: Freeze, &'static [[i32; NX]; NY]: Freeze,

§

impl<const NX: usize, const NY: usize, X, Y> RefUnwindSafe for BilinearSurface<NX, NY, X, Y>
where X: RefUnwindSafe, Y: RefUnwindSafe, &'static [[i32; NX]; NY]: RefUnwindSafe,

§

impl<const NX: usize, const NY: usize, X, Y> Send for BilinearSurface<NX, NY, X, Y>
where X: Send, Y: Send, &'static [[i32; NX]; NY]: Send,

§

impl<const NX: usize, const NY: usize, X, Y> Sync for BilinearSurface<NX, NY, X, Y>
where X: Sync, Y: Sync, &'static [[i32; NX]; NY]: Sync,

§

impl<const NX: usize, const NY: usize, X, Y> Unpin for BilinearSurface<NX, NY, X, Y>
where X: Unpin, Y: Unpin, &'static [[i32; NX]; NY]: Unpin,

§

impl<const NX: usize, const NY: usize, X, Y> UnsafeUnpin for BilinearSurface<NX, NY, X, Y>
where X: UnsafeUnpin, Y: UnsafeUnpin, &'static [[i32; NX]; NY]: UnsafeUnpin,

§

impl<const NX: usize, const NY: usize, X, Y> UnwindSafe for BilinearSurface<NX, NY, X, Y>
where X: UnwindSafe, Y: UnwindSafe, &'static [[i32; NX]; NY]: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.