use std::sync::Arc;
use crate::coordinate::CoordinateMap;
use crate::element::{Element, Segment, Shape};
use crate::error::{Result, invalid};
use crate::integrate::{Weight, integral};
use crate::profile::Profile;
#[derive(Debug, Clone)]
pub struct Space {
elements: Vec<Element>,
lower: Vec<f64>,
upper: Vec<f64>,
by_lower: Vec<usize>,
sorted_lower: Vec<f64>,
max_width: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Interpolation {
Linear,
Pchip,
}
fn broadcast<'a, T: Copy>(
name: &str,
values: &'a [T],
n: usize,
) -> Result<impl Fn(usize) -> T + 'a> {
let m = values.len();
if m != 1 && m != n {
return invalid(format!("{name} has length {m}, expected 1 or {n}"));
}
Ok(move |i: usize| if m == 1 { values[0] } else { values[i] })
}
fn check_increasing(name: &str, x: &[f64], min_len: usize) -> Result<()> {
if x.len() < min_len {
return invalid(format!("{name} needs at least {min_len} values"));
}
if x.iter().any(|v| !v.is_finite()) {
return invalid(format!("{name} must be finite"));
}
if x.windows(2).any(|w| w[1] <= w[0]) {
return invalid(format!("{name} must be strictly increasing"));
}
Ok(())
}
fn bspline_value(t: &[f64], i: usize, d: usize, x: f64) -> f64 {
let mut n = [0.0; 4];
for j in 0..=d {
n[j] = if t[i + j] <= x && x < t[i + j + 1] {
1.0
} else {
0.0
};
}
for k in 1..=d {
for j in 0..=d - k {
let (a, b, c, e) = (t[i + j], t[i + j + k], t[i + j + 1], t[i + j + k + 1]);
let left = if b > a { (x - a) / (b - a) * n[j] } else { 0.0 };
let right = if e > c {
(e - x) / (e - c) * n[j + 1]
} else {
0.0
};
n[j] = left + right;
}
}
n[0]
}
fn fit_polynomial(s: &[f64], v: &[f64]) -> [f64; 4] {
let n = s.len();
let mut a = [[0.0; 5]; 4];
for r in 0..n {
for c in 0..n {
a[r][c] = s[r].powi(c as i32);
}
a[r][4] = v[r];
}
for col in 0..n {
let piv = (col..n)
.max_by(|&x, &y| a[x][col].abs().total_cmp(&a[y][col].abs()))
.unwrap();
a.swap(col, piv);
for r in 0..n {
if r != col {
let f = a[r][col] / a[col][col];
for c in col..5 {
a[r][c] -= f * a[col][c];
}
}
}
}
let mut q = [0.0; 4];
for r in 0..n {
q[r] = a[r][4] / a[r][r];
}
q
}
impl Space {
pub fn new(elements: Vec<Element>) -> Self {
let lower: Vec<f64> = elements.iter().map(Element::lower).collect();
let upper: Vec<f64> = elements.iter().map(Element::upper).collect();
let mut by_lower: Vec<usize> = (0..elements.len()).collect();
by_lower.sort_by(|&i, &j| lower[i].total_cmp(&lower[j]));
let sorted_lower = by_lower.iter().map(|&i| lower[i]).collect();
let max_width = lower
.iter()
.zip(&upper)
.map(|(l, u)| u - l)
.fold(0.0, f64::max);
Space {
elements,
lower,
upper,
by_lower,
sorted_lower,
max_width,
}
}
pub fn deltas(x: &[f64]) -> Result<Self> {
Ok(Space::new(
x.iter()
.map(|&v| Element::delta(v))
.collect::<Result<_>>()?,
))
}
pub fn boxes(edges: &[f64]) -> Result<Self> {
check_increasing("edges", edges, 2)?;
Ok(Space::new(
edges
.windows(2)
.map(|w| Element::Box { a: w[0], b: w[1] })
.collect(),
))
}
pub fn hats(nodes: &[f64]) -> Result<Self> {
check_increasing("nodes", nodes, 2)?;
let n = nodes.len();
Ok(Space::new(
(0..n)
.map(|i| Element::Hat {
a: nodes[i.saturating_sub(1)],
c: nodes[i],
b: nodes[(i + 1).min(n - 1)],
})
.collect(),
))
}
pub fn bsplines(knots: &[f64], degree: usize) -> Result<Self> {
if degree > 3 {
return invalid(format!("B-spline degree must be 0..=3, got {degree}"));
}
if knots.len() < degree + 2 {
return invalid(format!(
"degree {degree} B-splines need at least {} knots",
degree + 2
));
}
if knots.iter().any(|v| !v.is_finite()) || knots.windows(2).any(|w| w[1] < w[0]) {
return invalid("knots must be finite and nondecreasing");
}
let n = knots.len() - degree - 1;
let s: Vec<f64> = (0..=degree)
.map(|k| (k as f64 + 0.5) / (degree as f64 + 1.0))
.collect();
let mut elements = Vec::with_capacity(n);
for i in 0..n {
let (lo, hi) = (knots[i], knots[i + degree + 1]);
if hi <= lo {
return invalid(format!(
"B-spline {i} has empty support (knot multiplicity > degree + 1)"
));
}
let center = 0.5 * (lo + hi);
let mut segments = Vec::with_capacity(degree + 1);
for j in i..=i + degree {
let (a, b) = (knots[j], knots[j + 1]);
if b <= a {
continue;
}
let h = b - a;
let v: Vec<f64> = s
.iter()
.map(|&sk| bspline_value(knots, i, degree, a + sk * h))
.collect();
let q = fit_polynomial(&s, &v);
let mut coeffs = [0.0; 4];
for m in 0..=degree {
coeffs[m] = q[m] / h.powi(m as i32);
}
segments.push(Segment {
a: a - center,
b: b - center,
coeffs,
});
}
elements.push(Element::piecewise(
Arc::new(Shape::new(segments)?),
center,
1.0,
)?);
}
Ok(Space::new(elements))
}
pub fn bsplines_clamped(breakpoints: &[f64], degree: usize) -> Result<Self> {
check_increasing("breakpoints", breakpoints, 2)?;
let m = breakpoints.len();
let mut knots = vec![breakpoints[0]; degree];
knots.extend_from_slice(breakpoints);
knots.extend(std::iter::repeat_n(breakpoints[m - 1], degree));
Space::bsplines(&knots, degree)
}
pub fn profiles(centers: &[f64], profiles: &[Profile], cutoff: f64) -> Result<Self> {
let p = broadcast("profiles", profiles, centers.len())?;
Ok(Space::new(
centers
.iter()
.enumerate()
.map(|(i, &c)| Element::profile(p(i), c, cutoff))
.collect::<Result<_>>()?,
))
}
pub fn gaussians(centers: &[f64], sigma: &[f64], cutoff: f64) -> Result<Self> {
let profiles = sigma
.iter()
.map(|&s| Profile::gaussian(s))
.collect::<Result<Vec<_>>>()?;
Space::profiles(centers, &profiles, cutoff)
}
pub fn rectangles(centers: &[f64], widths: &[f64]) -> Result<Self> {
let w = broadcast("widths", widths, centers.len())?;
Ok(Space::new(
centers
.iter()
.enumerate()
.map(|(i, &c)| Element::boxcar(c - 0.5 * w(i), c + 0.5 * w(i)))
.collect::<Result<_>>()?,
))
}
pub fn triangles(centers: &[f64], hwhm: &[f64]) -> Result<Self> {
let h = broadcast("hwhm", hwhm, centers.len())?;
Ok(Space::new(
centers
.iter()
.enumerate()
.map(|(i, &c)| Element::hat(c - 2.0 * h(i), c, c + 2.0 * h(i)))
.collect::<Result<_>>()?,
))
}
pub fn trapezoids(centers: &[f64], top: &[f64], bottom: &[f64]) -> Result<Self> {
let (t, b) = (
broadcast("top", top, centers.len())?,
broadcast("bottom", bottom, centers.len())?,
);
let shared = (top.len() == 1 && bottom.len() == 1)
.then(|| Shape::trapezoid(top[0], bottom[0]).map(Arc::new))
.transpose()?;
Ok(Space::new(
centers
.iter()
.enumerate()
.map(|(i, &c)| {
let shape = match &shared {
Some(s) => s.clone(),
None => Arc::new(Shape::trapezoid(t(i), b(i))?),
};
Element::piecewise(shape, c, 1.0)
})
.collect::<Result<_>>()?,
))
}
pub fn shifted(shape: Shape, centers: &[f64], stretch: &[f64]) -> Result<Self> {
let stretch = broadcast("stretch", stretch, centers.len())?;
let shape = Arc::new(shape);
Ok(Space::new(
centers
.iter()
.enumerate()
.map(|(i, &c)| Element::piecewise(shape.clone(), c, stretch(i)))
.collect::<Result<_>>()?,
))
}
pub fn tabulated(
centers: &[f64],
x: &[Vec<f64>],
y: &[Vec<f64>],
interpolation: Interpolation,
) -> Result<Self> {
let n = centers.len();
if y.len() != n {
return invalid(format!("y has {} shapes, expected {n}", y.len()));
}
if x.len() != 1 && x.len() != n {
return invalid(format!("x has {} shapes, expected 1 or {n}", x.len()));
}
Ok(Space::new(
centers
.iter()
.enumerate()
.map(|(i, &c)| {
let xi = if x.len() == 1 { &x[0] } else { &x[i] };
let shape = match interpolation {
Interpolation::Linear => Shape::piecewise_linear(xi, &y[i])?,
Interpolation::Pchip => Shape::pchip(xi, &y[i])?,
};
Element::piecewise(Arc::new(shape), c, 1.0)
})
.collect::<Result<_>>()?,
))
}
pub fn mapped(&self, map: CoordinateMap, jacobian: bool) -> Result<Space> {
Ok(Space::new(
self.elements
.iter()
.map(|e| Element::mapped(e.clone(), map, jacobian))
.collect::<Result<_>>()?,
))
}
pub fn derivative(&self, order: usize) -> Result<Space> {
Ok(Space::new(
self.elements
.iter()
.map(|e| e.derivative(order))
.collect::<Result<_>>()?,
))
}
pub fn len(&self) -> usize {
self.elements.len()
}
pub fn is_empty(&self) -> bool {
self.elements.is_empty()
}
pub fn elements(&self) -> &[Element] {
&self.elements
}
pub fn lower(&self) -> &[f64] {
&self.lower
}
pub fn upper(&self) -> &[f64] {
&self.upper
}
pub fn centers(&self) -> Vec<f64> {
self.elements.iter().map(Element::center).collect()
}
pub fn integrals(&self, weight: Weight) -> Vec<f64> {
let mut knots = Vec::new();
self.elements
.iter()
.map(|e| integral(e, weight, &mut knots))
.collect()
}
pub(crate) fn candidates(&self, lo: f64, hi: f64, out: &mut Vec<usize>) {
out.clear();
let start = self
.sorted_lower
.partition_point(|&l| l < lo - self.max_width);
let end = self.sorted_lower.partition_point(|&l| l <= hi);
for &j in &self.by_lower[start..end.max(start)] {
if self.upper[j] >= lo {
out.push(j);
}
}
out.sort_unstable();
}
}