Skip to main content

gam_runtime/
span.rs

1/// Select the span containing `value`, using `[left, right)` for every span
2/// except the final span, which is right-closed.
3pub fn span_index_for_breakpoints(
4    breakpoints: &[f64],
5    value: f64,
6    label: &str,
7) -> Result<usize, String> {
8    if !value.is_finite() {
9        return Err(format!("{label} requires finite value, got {value}"));
10    }
11    if breakpoints.len() < 2 {
12        return Err(format!("{label} requires at least two breakpoints"));
13    }
14    let last_idx = breakpoints.len() - 1;
15    if value <= breakpoints[0] {
16        return Ok(0);
17    }
18    if value >= breakpoints[last_idx] {
19        return Ok(last_idx - 1);
20    }
21    let insertion_idx = breakpoints.partition_point(|point| *point <= value);
22    Ok((insertion_idx - 1).min(last_idx - 1))
23}
24
25#[cfg(test)]
26mod tests {
27    use super::span_index_for_breakpoints;
28
29    /// Documents the `span_index_for_breakpoints` helper convention only.
30    /// Specific design evaluators may override this for endpoint convention.
31    /// Anchored deviation runtimes apply a LEFT-bias at interior breakpoints so
32    /// span-local third derivatives are reported from the left span; their
33    /// cubic basis is C², so value, first derivative, and second derivative are
34    /// unaffected by that choice.
35    #[test]
36    fn internal_breakpoints_use_right_hand_span() {
37        let breakpoints = [-1.5, -0.9, 0.4, 2.0];
38        assert_eq!(
39            span_index_for_breakpoints(&breakpoints, -1.5, "test span lookup").unwrap(),
40            0
41        );
42        assert_eq!(
43            span_index_for_breakpoints(&breakpoints, -0.9, "test span lookup").unwrap(),
44            1
45        );
46        assert_eq!(
47            span_index_for_breakpoints(&breakpoints, 0.4, "test span lookup").unwrap(),
48            2
49        );
50        assert_eq!(
51            span_index_for_breakpoints(&breakpoints, 2.0, "test span lookup").unwrap(),
52            2
53        );
54    }
55}