use crate::DType;
use crate::interpolate::error::InterpolateResult;
use numr::ops::{ScalarOps, TensorOps};
use numr::runtime::{Runtime, RuntimeClient};
use numr::tensor::Tensor;
pub fn pchip_slopes<R, C>(client: &C, x: &Tensor<R>, y: &Tensor<R>) -> InterpolateResult<Tensor<R>>
where
R: Runtime<DType = DType>,
C: TensorOps<R> + ScalarOps<R> + RuntimeClient<R>,
{
let n = x.shape()[0];
let device = client.device();
if n == 2 {
let x0 = x.narrow(0, 0, 1)?;
let x1 = x.narrow(0, 1, 1)?;
let y0 = y.narrow(0, 0, 1)?;
let y1 = y.narrow(0, 1, 1)?;
let dx = client.sub(&x1, &x0)?;
let dy = client.sub(&y1, &y0)?;
let secant = client.div(&dy, &dx)?;
return Ok(client.cat(&[&secant, &secant], 0)?);
}
let x_lo = x.narrow(0, 0, n - 1)?; let x_hi = x.narrow(0, 1, n - 1)?; let y_lo = y.narrow(0, 0, n - 1)?; let y_hi = y.narrow(0, 1, n - 1)?;
let dx = client.sub(&x_hi, &x_lo)?; let dy = client.sub(&y_hi, &y_lo)?;
let secants = client.div(&dy, &dx)?;
let s0 = secants.narrow(0, 0, n - 2)?.contiguous()?; let s1 = secants.narrow(0, 1, n - 2)?.contiguous()?; let h0 = dx.narrow(0, 0, n - 2)?.contiguous()?; let h1 = dx.narrow(0, 1, n - 2)?.contiguous()?;
let interior_len = n - 2;
let epsilon_data = vec![1e-14; interior_len];
let epsilon = Tensor::<R>::from_slice(&epsilon_data, &[interior_len], device);
let product = client.mul(&s0, &s1)?;
let product_abs = client.abs(&product)?;
let sum_prod = client.add(&product, &product_abs)?;
let abs_2 = client.mul_scalar(&product_abs, 2.0)?;
let safe_denom = client.add(&abs_2, &epsilon)?;
let monotonic_indicator = client.div(&sum_prod, &safe_denom)?;
let h1_2 = client.mul_scalar(&h1, 2.0)?;
let h0_2 = client.mul_scalar(&h0, 2.0)?;
let w1 = client.add(&h1_2, &h0)?; let w2 = client.add(&h1, &h0_2)?;
let s0_safe = client.add(&s0, &client.mul_scalar(&epsilon, 1e-100)?)?; let s1_safe = client.add(&s1, &client.mul_scalar(&epsilon, 1e-100)?)?;
let w_sum = client.add(&w1, &w2)?;
let w1_over_s0 = client.div(&w1, &s0_safe)?;
let w2_over_s1 = client.div(&w2, &s1_safe)?;
let harm_denom = client.add(&w1_over_s0, &w2_over_s1)?;
let harm_denom_safe = client.add(&harm_denom, &epsilon)?;
let slope_harmonic = client.div(&w_sum, &harm_denom_safe)?;
let interior_slopes = client.mul(&slope_harmonic, &monotonic_indicator)?;
let left_slope = compute_endpoint_slope_tensor(
client,
&secants.narrow(0, 0, 1)?,
&secants.narrow(0, 1, 1)?,
&dx.narrow(0, 0, 1)?,
&dx.narrow(0, 1, 1)?,
)?;
let right_slope = compute_endpoint_slope_tensor(
client,
&secants.narrow(0, n - 2, 1)?,
&secants.narrow(0, n - 3, 1)?,
&dx.narrow(0, n - 2, 1)?,
&dx.narrow(0, n - 3, 1)?,
)?;
let slopes = client.cat(&[&left_slope, &interior_slopes, &right_slope], 0)?;
Ok(slopes)
}
fn compute_endpoint_slope_tensor<R, C>(
client: &C,
s1: &Tensor<R>,
s2: &Tensor<R>,
h1: &Tensor<R>,
h2: &Tensor<R>,
) -> InterpolateResult<Tensor<R>>
where
R: Runtime<DType = DType>,
C: TensorOps<R> + ScalarOps<R> + RuntimeClient<R>,
{
let device = client.device();
let epsilon = Tensor::<R>::from_slice(&[1e-14], &[1], device);
let s1 = s1.contiguous()?;
let s2 = s2.contiguous()?;
let h1 = h1.contiguous()?;
let h2 = h2.contiguous()?;
let h1_2 = client.mul_scalar(&h1, 2.0)?;
let coef = client.add(&h1_2, &h2)?; let term1 = client.mul(&coef, &s1)?; let term2 = client.mul(&h1, &s2)?; let numer = client.sub(&term1, &term2)?;
let denom = client.add(&h1, &h2)?;
let denom_safe = client.add(&denom, &epsilon)?;
let d = client.div(&numer, &denom_safe)?;
let d_s1_prod = client.mul(&d, &s1)?;
let d_s1_prod_abs = client.abs(&d_s1_prod)?;
let d_s1_sum = client.add(&d_s1_prod, &d_s1_prod_abs)?;
let d_s1_denom = client.add(&client.mul_scalar(&d_s1_prod_abs, 2.0)?, &epsilon)?;
let d_s1_same_sign = client.div(&d_s1_sum, &d_s1_denom)?;
let s1_s2_prod = client.mul(&s1, &s2)?;
let s1_s2_prod_abs = client.abs(&s1_s2_prod)?;
let s1_s2_sum = client.add(&s1_s2_prod, &s1_s2_prod_abs)?;
let s1_s2_denom = client.add(&client.mul_scalar(&s1_s2_prod_abs, 2.0)?, &epsilon)?;
let s1_s2_same_sign = client.div(&s1_s2_sum, &s1_s2_denom)?;
let ones = Tensor::<R>::from_slice(&[1.0], &[1], device);
let s1_s2_diff_sign = client.sub(&ones, &s1_s2_same_sign)?;
let d_abs = client.abs(&d)?;
let s1_abs = client.abs(&s1)?;
let s1_3 = client.mul_scalar(&s1_abs, 3.0)?;
let diff = client.sub(&d_abs, &s1_3)?;
let diff_abs = client.abs(&diff)?;
let excess = client.mul_scalar(&client.add(&diff, &diff_abs)?, 0.5)?; let excess_safe = client.add(&excess, &epsilon)?;
let d_too_large = client.div(&excess, &excess_safe)?;
let rule2_applies = client.mul(&s1_s2_diff_sign, &d_too_large)?;
let s1_times_3 = client.mul_scalar(&s1, 3.0)?;
let one_minus_rule2 = client.sub(&ones, &rule2_applies)?;
let d_contrib = client.mul(&d, &one_minus_rule2)?;
let s1_3_contrib = client.mul(&s1_times_3, &rule2_applies)?;
let result_base = client.add(&d_contrib, &s1_3_contrib)?;
let result = client.mul(&result_base, &d_s1_same_sign)?;
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
use numr::runtime::cpu::{CpuClient, CpuDevice, CpuRuntime};
fn setup() -> (CpuDevice, CpuClient) {
let device = CpuDevice::new();
let client = CpuClient::new(device.clone());
(device, client)
}
#[test]
fn test_pchip_slopes_linear_data() {
let (device, client) = setup();
let x = Tensor::<CpuRuntime>::from_slice(&[0.0, 1.0, 2.0, 3.0], &[4], &device);
let y = Tensor::<CpuRuntime>::from_slice(&[1.0, 3.0, 5.0, 7.0], &[4], &device);
let slopes = pchip_slopes(&client, &x, &y).unwrap();
let slopes_vec: Vec<f64> = slopes.to_vec();
assert_eq!(slopes_vec.len(), 4);
for &slope in &slopes_vec {
assert!((slope - 2.0).abs() < 1e-10);
}
}
}