use self::SplineError::{NotEnoughNodes, NotEqualNodes, NotEqualSlopes, RedundantNodeX};
#[allow(unused_imports)]
use crate::structure::matrix::*;
#[allow(unused_imports)]
use crate::structure::polynomial::*;
#[allow(unused_imports)]
use crate::structure::vector::*;
use crate::traits::matrix::LinearAlgebra;
#[allow(unused_imports)]
use crate::util::non_macro::*;
use crate::util::useful::zip_range;
use anyhow::{bail, Result};
use peroxide_num::PowOps;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::cmp::{max, min};
use std::convert::From;
use std::ops::{Index, Range};
pub trait Spline<T> {
fn eval(&self, t: f64) -> T;
fn eval_vec(&self, v: &[f64]) -> Vec<T> {
v.iter().map(|&t| self.eval(t)).collect()
}
fn eval_with_cond<F: Fn(T) -> T>(&self, t: f64, cond: F) -> T {
cond(self.eval(t))
}
fn eval_vec_with_cond<F: Fn(T) -> T + Copy>(&self, t: &[f64], cond: F) -> Vec<T> {
t.iter().map(|&x| self.eval_with_cond(x, cond)).collect()
}
}
impl<P: PolynomialSpline> Spline<f64> for P {
fn eval(&self, x: f64) -> f64 {
self.polynomial_at(x).eval(x)
}
}
pub trait PolynomialSpline {
fn polynomial_at<T: Into<f64> + Copy>(&self, x: T) -> &Polynomial {
let x = x.into();
let poly = self.get_ranged_polynomials();
let index = match poly.binary_search_by(|(range, _)| {
if range.contains(&x) {
core::cmp::Ordering::Equal
} else if x < range.start {
core::cmp::Ordering::Greater
} else {
core::cmp::Ordering::Less
}
}) {
Ok(index) => index,
Err(index) => max(0, min(index, poly.len() - 1)),
};
&poly[index].1
}
fn number_of_polynomials(&self) -> usize {
self.get_ranged_polynomials().len()
}
fn get_ranged_polynomials(&self) -> &Vec<(Range<f64>, Polynomial)>;
}
pub fn cubic_spline(node_x: &[f64], node_y: &[f64]) -> Result<CubicSpline> {
CubicSpline::from_nodes(node_x, node_y)
}
pub fn cubic_hermite_spline(
node_x: &[f64],
node_y: &[f64],
slope_method: SlopeMethod,
) -> Result<CubicHermiteSpline> {
CubicHermiteSpline::from_nodes(node_x, node_y, slope_method)
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
)]
pub struct CubicSpline {
polynomials: Vec<(Range<f64>, Polynomial)>,
}
impl PolynomialSpline for CubicSpline {
fn get_ranged_polynomials(&self) -> &Vec<(Range<f64>, Polynomial)> {
&self.polynomials
}
}
#[derive(Debug, Copy, Clone)]
pub enum SplineError {
NotEnoughNodes,
NotEqualNodes,
NotEqualSlopes,
RedundantNodeX,
}
impl std::fmt::Display for SplineError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SplineError::NotEnoughNodes => write!(f, "node_x has less than 3 elements"),
SplineError::NotEqualNodes => write!(f, "node_x and node_y have different lengths"),
SplineError::NotEqualSlopes => write!(f, "nodes and slopes have different lengths"),
SplineError::RedundantNodeX => write!(f, "there are redundant nodes in node_x"),
}
}
}
impl CubicSpline {
pub fn from_nodes(node_x: &[f64], node_y: &[f64]) -> Result<Self> {
let polynomials = CubicSpline::cubic_spline(node_x, node_y)?;
Ok(CubicSpline {
polynomials: zip_range(node_x, &polynomials),
})
}
fn cubic_spline(node_x: &[f64], node_y: &[f64]) -> Result<Vec<Polynomial>> {
let n = node_x.len() - 1;
if n < 2 {
bail!(NotEnoughNodes);
}
if n != node_y.len() - 1 {
bail!(NotEqualNodes);
}
let mut h = vec![0f64; n];
let mut b = vec![0f64; n];
let mut v = vec![0f64; n];
let mut u = vec![0f64; n];
for i in 0..n {
if i == 0 {
h[i] = node_x[i + 1] - node_x[i];
b[i] = (node_y[i + 1] - node_y[i]) / h[i];
} else {
h[i] = node_x[i + 1] - node_x[i];
b[i] = (node_y[i + 1] - node_y[i]) / h[i];
v[i] = 2. * (h[i] + h[i - 1]);
u[i] = 6. * (b[i] - b[i - 1]);
}
}
let mut m = matrix(vec![0f64; (n - 1) * (n - 1)], n - 1, n - 1, Col);
for i in 0..n - 2 {
m[(i, i)] = v[i + 1];
m[(i + 1, i)] = h[i + 1];
m[(i, i + 1)] = h[i + 1];
}
m[(n - 2, n - 2)] = v[n - 1];
let z_inner = m.inv() * Vec::from(&u[1..]);
let mut z = vec![0f64];
z.extend(&z_inner);
z.push(0f64);
let mut s: Vec<Polynomial> = Vec::new();
for i in 0..n {
let t_i = node_x[i];
let t_i1 = node_x[i + 1];
let z_i = z[i];
let z_i1 = z[i + 1];
let h_i = h[i];
let y_i = node_y[i];
let y_i1 = node_y[i + 1];
let temp1 = poly(vec![1f64, -t_i]);
let temp2 = poly(vec![1f64, -t_i1]);
let term1 = temp1.powi(3) * (z_i1 / (6f64 * h_i));
let term2 = temp2.powi(3) * (-z_i / (6f64 * h_i));
let term3 = temp1 * (y_i1 / h_i - z_i1 * h_i / 6.);
let term4 = temp2 * (-y_i / h_i + h_i * z_i / 6.0);
s.push(term1 + term2 + term3 + term4);
}
Ok(s)
}
pub fn extend_with_nodes(&mut self, node_x: Vec<f64>, node_y: Vec<f64>) -> Result<()> {
let mut ext_node_x = Vec::with_capacity(node_x.len() + 1);
let mut ext_node_y = Vec::with_capacity(node_x.len() + 1);
let (r, polynomial) = &self.polynomials[self.polynomials.len() - 1];
ext_node_x.push(0.0f64);
ext_node_y.push(polynomial.eval(r.end));
let tx = r.end;
ext_node_x.extend(node_x.into_iter().map(|x| x - tx));
ext_node_y.extend(node_y);
let polynomials = zip_range(
&ext_node_x,
&CubicSpline::cubic_spline(&ext_node_x, &ext_node_y)?,
);
self.polynomials
.extend(polynomials.into_iter().map(|(r, p)| {
(
Range {
start: r.start + tx,
end: r.end + tx,
},
p.translate_x(tx),
)
}));
Ok(())
}
}
impl From<CubicSpline> for Vec<Polynomial> {
fn from(val: CubicSpline) -> Self {
val.polynomials
.into_iter()
.map(|(_, polynomial)| polynomial)
.collect()
}
}
impl From<Vec<(Range<f64>, Polynomial)>> for CubicSpline {
fn from(polynomials: Vec<(Range<f64>, Polynomial)>) -> Self {
CubicSpline { polynomials }
}
}
impl From<CubicSpline> for Vec<(Range<f64>, Polynomial)> {
fn from(val: CubicSpline) -> Self {
val.polynomials
}
}
impl Index<usize> for CubicSpline {
type Output = (Range<f64>, Polynomial);
fn index(&self, index: usize) -> &Self::Output {
&self.polynomials[index]
}
}
impl Calculus for CubicSpline {
fn derivative(&self) -> Self {
let mut polynomials: Vec<(Range<f64>, Polynomial)> = self.clone().into();
polynomials = polynomials
.into_iter()
.map(|(r, poly)| (r, poly.derivative()))
.collect();
Self::from(polynomials)
}
fn integral(&self) -> Self {
let mut polynomials: Vec<(Range<f64>, Polynomial)> = self.clone().into();
polynomials = polynomials
.into_iter()
.map(|(r, poly)| (r, poly.integral()))
.collect();
Self::from(polynomials)
}
fn integrate<T: Into<f64> + Copy>(&self, interval: (T, T)) -> f64 {
let (a, b) = interval;
let a = a.into();
let b = b.into();
let mut s = 0f64;
for (r, p) in self.polynomials.iter() {
if r.start > b {
break;
} else if r.end < a {
continue;
} else {
let x = r.start.max(a);
let y = r.end.min(b);
s += p.integrate((x, y));
}
}
s
}
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
)]
pub struct CubicHermiteSpline {
polynomials: Vec<(Range<f64>, Polynomial)>,
}
impl PolynomialSpline for CubicHermiteSpline {
fn get_ranged_polynomials(&self) -> &Vec<(Range<f64>, Polynomial)> {
&self.polynomials
}
}
impl CubicHermiteSpline {
pub fn from_nodes_with_slopes(node_x: &[f64], node_y: &[f64], m: &[f64]) -> Result<Self> {
let n = node_x.len();
if n < 3 {
bail!(NotEnoughNodes);
}
if n != node_y.len() {
bail!(NotEqualNodes);
}
if n != m.len() {
bail!(NotEqualSlopes);
}
let mut r = vec![Range::default(); node_x.len() - 1];
let mut u = vec![Polynomial::default(); node_x.len() - 1];
for i in 0..node_x.len() - 1 {
let a_i = node_y[i];
let b_i = m[i];
let dx = node_x[i + 1] - node_x[i];
let dy = node_y[i + 1] - node_y[i];
let c_i = (3f64 * dy / dx - 2f64 * m[i] - m[i + 1]) / dx;
let d_i = (m[i] + m[i + 1] - 2f64 * dy / dx) / dx.powi(2);
let p = Polynomial::new(vec![1f64, -node_x[i]]);
r[i] = Range {
start: node_x[i],
end: node_x[i + 1],
};
u[i] = p.powi(3) * d_i + p.powi(2) * c_i + p.clone() * b_i;
u[i].coef[3] += a_i;
}
Ok(CubicHermiteSpline {
polynomials: r.into_iter().zip(u).collect(),
})
}
pub fn from_nodes(node_x: &[f64], node_y: &[f64], slope_method: SlopeMethod) -> Result<Self> {
match slope_method {
SlopeMethod::Akima => CubicHermiteSpline::from_nodes_with_slopes(
node_x,
node_y,
&akima_slopes(node_x, node_y)?,
),
SlopeMethod::Quadratic => CubicHermiteSpline::from_nodes_with_slopes(
node_x,
node_y,
&quadratic_slopes(node_x, node_y)?,
),
}
}
}
impl From<CubicHermiteSpline> for Vec<Polynomial> {
fn from(val: CubicHermiteSpline) -> Self {
val.polynomials
.into_iter()
.map(|(_, polynomial)| polynomial)
.collect()
}
}
impl From<Vec<(Range<f64>, Polynomial)>> for CubicHermiteSpline {
fn from(polynomials: Vec<(Range<f64>, Polynomial)>) -> Self {
CubicHermiteSpline { polynomials }
}
}
impl From<CubicHermiteSpline> for Vec<(Range<f64>, Polynomial)> {
fn from(val: CubicHermiteSpline) -> Self {
val.polynomials
}
}
impl Index<usize> for CubicHermiteSpline {
type Output = (Range<f64>, Polynomial);
fn index(&self, index: usize) -> &Self::Output {
&self.polynomials[index]
}
}
impl Calculus for CubicHermiteSpline {
fn derivative(&self) -> Self {
let mut polynomials: Vec<(Range<f64>, Polynomial)> = self.clone().into();
polynomials = polynomials
.into_iter()
.map(|(r, poly)| (r, poly.derivative()))
.collect();
Self::from(polynomials)
}
fn integral(&self) -> Self {
let mut polynomials: Vec<(Range<f64>, Polynomial)> = self.clone().into();
polynomials = polynomials
.into_iter()
.map(|(r, poly)| (r, poly.integral()))
.collect();
Self::from(polynomials)
}
fn integrate<T: Into<f64> + Copy>(&self, interval: (T, T)) -> f64 {
let (a, b) = interval;
let a = a.into();
let b = b.into();
let mut s = 0f64;
for (r, p) in self.polynomials.iter() {
if r.start > b {
break;
} else if r.end < a {
continue;
} else {
let x = r.start.max(a);
let y = r.end.min(b);
s += p.integrate((x, y));
}
}
s
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum SlopeMethod {
Akima,
Quadratic,
}
fn akima_slopes(x: &[f64], y: &[f64]) -> Result<Vec<f64>> {
if x.len() < 3 {
bail!(NotEnoughNodes);
}
let mut m = vec![0f64; x.len()];
let mut s = vec![0f64; x.len() + 3];
let l_i = lagrange_polynomial(x[0..3].to_vec(), y[0..3].to_vec());
let l_f = lagrange_polynomial(x[x.len() - 3..].to_vec(), y[y.len() - 3..].to_vec());
let x_i = x[0] - (x[1] - x[0]) / 10f64;
let x_ii = x_i - (x[1] - x[0]) / 10f64;
let x_f = x[x.len() - 1] + (x[x.len() - 1] - x[x.len() - 2]) / 10f64;
let x_ff = x_f + (x[x.len() - 1] - x[x.len() - 2]) / 10f64;
let y_i = l_i.eval(x_i);
let y_ii = l_i.eval(x_ii);
let y_f = l_f.eval(x_f);
let y_ff = l_f.eval(x_ff);
let new_x = concat(&concat(&[x_ii, x_i], x), &[x_f, x_ff]);
let new_y = concat(&concat(&[y_ii, y_i], y), &[y_f, y_ff]);
for i in 0..new_x.len() - 1 {
let dx = new_x[i + 1] - new_x[i];
if dx == 0f64 {
bail!(RedundantNodeX);
}
s[i] = (new_y[i + 1] - new_y[i]) / dx;
}
for (i, m_i) in m.iter_mut().enumerate() {
let j = i + 2;
let ds_f = (s[j + 1] - s[j]).abs();
let ds_i = (s[j - 1] - s[j - 2]).abs();
*m_i = if ds_f == 0f64 && ds_i == 0f64 {
(s[j - 1] + s[j]) / 2f64
} else {
(ds_f * s[j - 1] + ds_i * s[j]) / (ds_f + ds_i)
};
}
Ok(m)
}
fn quadratic_slopes(x: &[f64], y: &[f64]) -> Result<Vec<f64>> {
if x.len() < 3 {
bail!(NotEnoughNodes);
}
let mut m = vec![0f64; x.len()];
let q_i = lagrange_polynomial(x[0..3].to_vec(), y[0..3].to_vec());
let q_f = lagrange_polynomial(x[x.len() - 3..].to_vec(), y[y.len() - 3..].to_vec());
m[0] = q_i.derivative().eval(x[0]);
m[x.len() - 1] = q_f.derivative().eval(x[x.len() - 1]);
for i in 1..x.len() - 1 {
let q = lagrange_polynomial(x[i - 1..i + 2].to_vec(), y[i - 1..i + 2].to_vec());
m[i] = q.derivative().eval(x[i]);
}
Ok(m)
}
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
)]
pub struct UnitCubicBasis {
pub x_min: f64,
pub x_max: f64,
pub scale: f64,
}
impl UnitCubicBasis {
pub fn new(x_min: f64, x_max: f64, scale: f64) -> Self {
Self {
x_min,
x_max,
scale,
}
}
pub fn eval(&self, x: f64) -> f64 {
let t = 4f64 * (x - self.x_min) / (self.x_max - self.x_min);
let result = if (0f64..1f64).contains(&t) {
t.powi(3) / 6f64
} else if (1f64..2f64).contains(&t) {
(-3f64 * t.powi(3) + 12f64 * t.powi(2) - 12f64 * t + 4f64) / 6f64
} else if (2f64..3f64).contains(&t) {
(3f64 * t.powi(3) - 24f64 * t.powi(2) + 60f64 * t - 44f64) / 6f64
} else if (3f64..4f64).contains(&t) {
(4f64 - t).powi(3) / 6f64
} else {
0f64
};
self.scale * result
}
pub fn eval_vec(&self, x: &[f64]) -> Vec<f64> {
x.iter().map(|x| self.eval(*x)).collect()
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
)]
pub struct CubicBSplineBases {
pub ranges: Vec<Range<f64>>,
pub bases: Vec<UnitCubicBasis>,
}
impl CubicBSplineBases {
pub fn new(ranges: Vec<Range<f64>>, bases: Vec<UnitCubicBasis>) -> Self {
Self { ranges, bases }
}
pub fn from_interval((a, b): (f64, f64), num_bases: usize) -> Self {
let nodes = linspace(a, b, num_bases + 4);
let (ranges, bases) = nodes
.iter()
.zip(nodes.iter().skip(4))
.map(|(a, b)| {
(
Range { start: *a, end: *b },
UnitCubicBasis::new(*a, *b, 1f64),
)
})
.unzip();
Self::new(ranges, bases)
}
pub fn rescale(&mut self, scale_vec: &[f64]) -> Result<()> {
if scale_vec.len() != self.bases.len() {
bail!("The number of scales should be equal to the number of basis functions");
}
for (basis, scale) in self.bases.iter_mut().zip(scale_vec) {
basis.scale = *scale;
}
Ok(())
}
pub fn eval(&self, x: f64) -> f64 {
self.ranges
.iter()
.enumerate()
.filter(|(_, range)| range.contains(&x))
.fold(0f64, |acc, (i, _)| {
let basis = &self.bases[i];
acc + basis.eval(x)
})
}
pub fn eval_vec(&self, x: &[f64]) -> Vec<f64> {
x.iter().map(|x| self.eval(*x)).collect()
}
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
)]
pub struct BSpline {
pub degree: usize,
pub knots: Vec<f64>,
pub control_points: Vec<Vec<f64>>,
}
impl BSpline {
pub fn open(degree: usize, knots: Vec<f64>, control_points: Vec<Vec<f64>>) -> Result<Self> {
if knots.len() != control_points.len() + degree + 1 {
bail!("The number of knots ({}) should be equal to the number of control points ({}) + degree ({}) + 1", knots.len(), control_points.len(), degree);
}
Ok(Self {
degree,
knots,
control_points,
})
}
pub fn clamped(degree: usize, knots: Vec<f64>, control_points: Vec<Vec<f64>>) -> Result<Self> {
let mut knots = knots.clone();
if knots.len() != control_points.len() + 1 - degree {
bail!("For clamped, the number of knots ({}) should be equal to the number of control points ({}) + 1 - degree ({})", knots.len(), control_points.len(), degree);
}
for _ in 0..degree {
knots.insert(0, knots[0]);
knots.push(knots[knots.len() - 1]);
}
if knots.len() != control_points.len() + degree + 1 {
bail!("The number of knots ({}) should be equal to the number of control points ({}) + degree ({}) + 1", knots.len(), control_points.len(), degree);
}
Ok(Self {
degree,
knots,
control_points,
})
}
#[allow(non_snake_case)]
pub fn cox_de_boor(&self, t: f64, i: usize) -> f64 {
let p = self.degree;
let mut B = vec![vec![0f64; p + 1]; p + 1];
for (j, B_j) in B.iter_mut().enumerate() {
if (self.knots[i + j] <= t && t < self.knots[i + j + 1])
|| (i + j == self.knots.len() - (p + 2) && t == self.knots[i + j + 1])
{
B_j[0] = 1f64;
} else {
B_j[0] = 0f64;
}
}
for k in 1..=p {
for j in 0..=(p - k) {
let a = if self.knots[i + j + k] == self.knots[i + j] {
0f64
} else {
(t - self.knots[i + j]) / (self.knots[i + j + k] - self.knots[i + j])
};
let b = if self.knots[i + j + k + 1] == self.knots[i + j + 1] {
0f64
} else {
(self.knots[i + j + k + 1] - t)
/ (self.knots[i + j + k + 1] - self.knots[i + j + 1])
};
B[j][k] = a * B[j][k - 1] + b * B[j + 1][k - 1];
}
}
B[0][p]
}
pub fn derivative(&self) -> Result<Self> {
if self.degree == 0 {
bail!("Cannot take the derivative of a B-spline with degree 0.");
}
let p = self.degree;
let c = self.control_points.len();
let mut new_control_points = Vec::with_capacity(c - 1);
for i in 0..c - 1 {
let p_i = &self.control_points[i];
let p_i1 = &self.control_points[i + 1];
let denominator = self.knots[i + p + 1] - self.knots[i + 1];
if denominator == 0.0 {
new_control_points.push(vec![0.0; p_i.len()]);
} else {
let factor = p as f64 / denominator;
let q_i = p_i1
.iter()
.zip(p_i)
.map(|(a, b)| factor * (a - b))
.collect();
new_control_points.push(q_i);
}
}
let new_knots = self.knots[1..self.knots.len() - 1].to_vec();
let new_degree = self.degree - 1;
BSpline::open(new_degree, new_knots, new_control_points)
}
pub fn integral(&self) -> Result<Self> {
let p = self.degree;
let c = self.control_points.len();
let dim = self.control_points[0].len();
let mut new_knots = self.knots.clone();
new_knots.insert(0, self.knots[0]);
new_knots.push(self.knots[self.knots.len() - 1]);
let mut new_control_points = vec![vec![0.0; dim]; c + 1];
for i in 1..=c {
let factor = (self.knots[i + p] - self.knots[i - 1]) / ((p + 1) as f64);
let prev_r = new_control_points[i - 1].clone();
let p_im1 = &self.control_points[i - 1];
let mut r_i = Vec::with_capacity(dim);
for j in 0..dim {
r_i.push(prev_r[j] + factor * p_im1[j]);
}
new_control_points[i] = r_i;
}
let new_degree = self.degree + 1;
BSpline::open(new_degree, new_knots, new_control_points)
}
}
impl Spline<(f64, f64)> for BSpline {
#[allow(non_snake_case)]
fn eval(&self, t: f64) -> (f64, f64) {
let n = self.control_points.len();
let mut x = 0f64;
let mut y = 0f64;
for i in 0..n {
let B = self.cox_de_boor(t, i);
x += B * self.control_points[i][0];
y += B * self.control_points[i][1];
}
(x, y)
}
}