spliny 0.3.0

b-Spline Curves
Documentation
use std::iter::repeat;

use crate::SplineCurve;

use super::Result;

/// A `const`-friendly collection of named B-Spline curves sharing a degree `K` and dimension `N`.
///
/// `NK` is the number of named curves, `NT` the total number of knot values across all curves,
/// and `NC` the total number of coefficients across all curves.
///
/// Knots are stored as `i32` to allow `const` initialization. Each entry in `keys` holds the
/// curve name and `[start, end)` index ranges into the shared `t` and `c` arrays.
///
/// The stored knots are the **interior** knot sequence (without boundary repetitions). When a
/// curve is retrieved via [`spline_curve`](SplineCurves::spline_curve), `K` copies of the first
/// and last knot value are prepended/appended automatically to form a clamped knot vector.
pub struct SplineCurves<const K: usize, const N: usize, const NK: usize, const NT:usize, const NC: usize> {
    keys: [(&'static str, [usize;2], [usize;2]); NK],
    t: [i32; NT], // Knot values (interior knots; boundary repetitions added in spline_curve)
    c: [f64; NC], // b-Spline coefficients
}

impl<const K: usize, const N: usize, const NK: usize, const NC: usize, const NT: usize> SplineCurves<K, N, NK, NT, NC> {
    /// Creates a new `SplineCurves` collection. Intended for use in `static` or `const` contexts.
    ///
    /// See the struct-level documentation for the expected layout of `keys`, `t`, and `c`.
    pub const fn new(keys: [(&'static str, [usize;2], [usize;2]); NK], t: [i32; NT], c: [f64; NC]) -> Self {
        Self { keys, t, c }
    }

    /// Looks up a named curve and returns it as a [`SplineCurve`].
    ///
    /// The stored interior knots are expanded into a full clamped knot vector by prepending and
    /// appending `K` repetitions of the first and last knot values respectively.
    ///
    /// # Errors
    /// Returns an error if `key` does not match any entry in the collection.
    pub fn spline_curve(&self, key: &str) -> Result<SplineCurve<K,N>> {
        for &(s, [ts,te], [cs, ce]) in &self.keys {
            if key == s {
                let ti = &self.t[ts..te];
                let t: Vec<f64> = 
                    repeat(ti[0] as f64)
                    .take(K)
                    .chain(
                        ti.iter()
                        .cloned()
                        .map(|v| v as f64)
                    ).chain(
                        repeat(ti[ti.len()-1] as f64)
                        .take(K)
                    ).collect();

                return Ok(SplineCurve::new(t, self.c[cs..ce].to_owned()))
            }
        }
        
        Err("Key not found".into())
    }

    /// Looks up a named curve and evaluates it at the given parameter values `u`.
    ///
    /// Equivalent to calling [`spline_curve`](Self::spline_curve) followed by
    /// [`SplineCurve::evaluate`].
    pub fn evaluate(&self, key: &str, u: &[f64]) -> Result<Vec<f64>> {
        let sc = self.spline_curve(key)?;
        sc.evaluate(u)
    }
}



#[cfg(test)]
mod tests {
    use crate::{SplineCurves, Result};
    static MUNSELL_MATT: SplineCurves<3,1,11,131,153> = SplineCurves::new(
        [("2.5R9/2", [0, 14], [0, 16]), ("2.5R8/2", [14, 28], [16, 32]), ("2.5R7/2", [28, 41], [32, 47]), ("2.5R6/2",
            [41, 54], [47, 62]), ("2.5R5/2", [54, 63], [62, 73]), ("2.5R4/2", [63, 72], [73, 84]), ("2.5R3/2", [72, 78],
            [84, 92]), ("2.5R2.5/2", [78, 84], [92, 100]), ("2.5R8/4", [84, 102], [100, 120]), ("2.5R7/4", [102, 118], [120,
            138]), ("2.5R6/4", [118, 131], [138, 153])],
        [380, 387, 394, 407, 420, 433, 485, 499, 512, 538, 564, 590, 695, 800, 380, 407, 420, 433, 485, 499, 512, 538,
            551, 564, 590, 695, 748, 800, 380, 394, 407, 420, 433, 485, 499, 512, 538, 564, 590, 695, 800, 380, 394, 407,
            433, 485, 512, 538, 564, 590, 695, 748, 774, 800, 380, 394, 407, 433, 485, 538, 564, 590, 800, 380, 394, 407,
            433, 485, 538, 590, 695, 800, 380, 485, 538, 590, 695, 800, 380, 485, 590, 643, 695, 800, 380, 394, 407, 420,
            433, 459, 485, 499, 512, 538, 564, 571, 577, 590, 643, 695, 748, 800, 380, 394, 407, 420, 433, 485, 499, 512,
            538, 564, 590, 643, 695, 748, 774, 800, 380, 394, 407, 433, 485, 499, 512, 538, 564, 590, 643, 695, 800],
        [0.13330, 0.13330, 0.17848, 0.30266, 0.64668, 0.70978, 0.68903, 0.70561, 0.66204, 0.68016, 0.61283, 0.77384,
            0.76221, 0.75914, 0.75380, 0.75380, 0.14040, 0.14040, 0.52772, 0.54173, 0.49789, 0.52461, 0.48275, 0.49955,
            0.43894, 0.52302, 0.58575, 0.57301, 0.56327, 0.55764, 0.54630, 0.54630, 0.13020, 0.13020, 0.27409, 0.40744,
            0.38642, 0.36947, 0.37360, 0.34371, 0.35934, 0.30423, 0.44270, 0.42473, 0.42475, 0.40430, 0.40430, 0.12780,
            0.12780, 0.24118, 0.28935, 0.24863, 0.25052, 0.23383, 0.20104, 0.31539, 0.30003, 0.29116, 0.28374, 0.28620,
            0.27330, 0.27330, 0.10560, 0.10560, 0.17269, 0.19525, 0.15397, 0.17851, 0.11508, 0.20444, 0.20213, 0.19000,
            0.19000, 0.09320, 0.09320, 0.10888, 0.11993, 0.10334, 0.10100, 0.08108, 0.16608, 0.12225, 0.13990, 0.13990,
            0.06380, 0.06380, 0.06145, 0.05032, 0.09808, 0.08075, 0.09050, 0.09050, 0.04920, 0.04920, 0.03963, 0.03536,
            0.08938, 0.12467, 0.14650, 0.14650, 0.13600, 0.13600, 0.28004, 0.52005, 0.50859, 0.48294, 0.47849, 0.47462,
            0.42971, 0.46243, 0.35094, 0.55213, 0.60521, 0.64302, 0.68945, 0.66423, 0.66990, 0.65781, 0.66550, 0.66550,
            0.12950, 0.12950, 0.27210, 0.39673, 0.37335, 0.33738, 0.35767, 0.30755, 0.32708, 0.24423, 0.46545, 0.51521,
            0.49129, 0.48705, 0.47926, 0.47884, 0.47020, 0.47020, 0.13200, 0.13200, 0.24932, 0.28483, 0.22491, 0.25315,
            0.20612, 0.22180, 0.16336, 0.30942, 0.40350, 0.36494, 0.36795, 0.34800, 0.34800],
    );

    #[test]
    fn key_not_found_returns_error() {
        assert!(MUNSELL_MATT.spline_curve("nonexistent").is_err());
    }

    #[test]
    fn static_munsell_matt() -> Result<()>{
        let sc1 =  MUNSELL_MATT.spline_curve("2.5R9/2").unwrap();
        println!("{:?}", sc1);
        #[cfg(feature = "plot")]
        sc1.plot("sc1.png", (2000,1000))?;
        let sc2 =  MUNSELL_MATT.spline_curve("2.5R6/4").unwrap();
        println!("{:?}", sc2);
        #[cfg(feature = "plot")]
        sc2.plot("sc2.png", (2000,1000))?;
        Ok(())
    }

}