use crate::DType;
use numr::error::{Error, Result};
use numr::ops::{ScalarOps, TensorOps};
use numr::runtime::{Runtime, RuntimeClient};
use numr::tensor::Tensor;
pub fn trapezoid_impl<R, C>(client: &C, y: &Tensor<R>, x: &Tensor<R>) -> Result<Tensor<R>>
where
R: Runtime<DType = DType>,
C: TensorOps<R> + ScalarOps<R> + RuntimeClient<R>,
{
let y_shape = y.shape();
let x_shape = x.shape();
if y_shape.is_empty() || x_shape.is_empty() {
return Err(Error::InvalidArgument {
arg: "y/x",
reason: "trapezoid: tensors must be at least 1D".to_string(),
});
}
let n = y_shape[y_shape.len() - 1];
let x_n = x_shape[x_shape.len() - 1];
if n != x_n {
return Err(Error::InvalidArgument {
arg: "x",
reason: format!(
"trapezoid: x and y must have same length in last dimension (got {} and {})",
x_n, n
),
});
}
if n < 2 {
return Err(Error::InvalidArgument {
arg: "y",
reason: "trapezoid: need at least 2 points".to_string(),
});
}
let last_dim = y_shape.len() - 1;
let x_last_dim = x_shape.len() - 1;
let x_left = x.narrow(x_last_dim as isize, 0, n - 1)?.contiguous()?;
let x_right = x.narrow(x_last_dim as isize, 1, n - 1)?.contiguous()?;
let dx = client.sub(&x_right, &x_left)?;
let y_left = y.narrow(last_dim as isize, 0, n - 1)?.contiguous()?;
let y_right = y.narrow(last_dim as isize, 1, n - 1)?.contiguous()?;
let y_sum = client.add(&y_left, &y_right)?;
let scaled_y = client.mul_scalar(&y_sum, 0.5)?;
let areas = client.mul(&dx, &scaled_y)?;
client.sum(&areas, &[last_dim], false)
}
pub fn trapezoid_uniform_impl<R, C>(client: &C, y: &Tensor<R>, dx: f64) -> Result<Tensor<R>>
where
R: Runtime<DType = DType>,
C: TensorOps<R> + ScalarOps<R> + RuntimeClient<R>,
{
let y_shape = y.shape();
if y_shape.is_empty() {
return Err(Error::InvalidArgument {
arg: "y",
reason: "trapezoid_uniform: tensor must be at least 1D".to_string(),
});
}
let n = y_shape[y_shape.len() - 1];
if n < 2 {
return Err(Error::InvalidArgument {
arg: "y",
reason: "trapezoid_uniform: need at least 2 points".to_string(),
});
}
let last_dim = y_shape.len() - 1;
let total_sum = client.sum(y, &[last_dim], false)?;
let y_first = y.narrow(last_dim as isize, 0, 1)?.contiguous()?;
let y_last = y.narrow(last_dim as isize, n - 1, 1)?.contiguous()?;
let endpoints = client.add(&y_first, &y_last)?;
let endpoints_sum = client.sum(&endpoints, &[last_dim], false)?;
let scaled_total = client.mul_scalar(&total_sum, dx)?;
let endpoint_correction = client.mul_scalar(&endpoints_sum, 0.5 * dx)?;
client.sub(&scaled_total, &endpoint_correction)
}
pub fn cumulative_trapezoid_impl<R, C>(
client: &C,
y: &Tensor<R>,
x: Option<&Tensor<R>>,
dx: f64,
) -> Result<Tensor<R>>
where
R: Runtime<DType = DType>,
C: TensorOps<R> + ScalarOps<R> + RuntimeClient<R>,
{
let y_shape = y.shape();
if y_shape.is_empty() {
return Err(Error::InvalidArgument {
arg: "y",
reason: "cumulative_trapezoid: tensor must be at least 1D".to_string(),
});
}
let n = y_shape[y_shape.len() - 1];
if n < 2 {
return Err(Error::InvalidArgument {
arg: "y",
reason: "cumulative_trapezoid: need at least 2 points".to_string(),
});
}
let last_dim = y_shape.len() - 1;
let y_left = y.narrow(last_dim as isize, 0, n - 1)?.contiguous()?;
let y_right = y.narrow(last_dim as isize, 1, n - 1)?.contiguous()?;
let y_sum = client.add(&y_left, &y_right)?;
let areas = if let Some(x_tensor) = x {
let x_shape = x_tensor.shape();
let x_last_dim = x_shape.len() - 1;
let x_left = x_tensor
.narrow(x_last_dim as isize, 0, n - 1)?
.contiguous()?;
let x_right = x_tensor
.narrow(x_last_dim as isize, 1, n - 1)?
.contiguous()?;
let dx_tensor = client.sub(&x_right, &x_left)?;
let scaled_y = client.mul_scalar(&y_sum, 0.5)?;
client.mul(&dx_tensor, &scaled_y)?
} else {
client.mul_scalar(&y_sum, 0.5 * dx)?
};
client.cumsum(&areas, last_dim as isize)
}
#[cfg(test)]
mod tests {
use super::*;
use numr::runtime::cpu::{CpuClient, CpuDevice};
fn get_client() -> CpuClient {
let device = CpuDevice::new();
CpuClient::new(device)
}
#[test]
fn test_trapezoid_uniform() {
let client = get_client();
let y = Tensor::from_slice(&[0.0, 0.25, 0.5, 0.75, 1.0], &[5], client.device());
let result = trapezoid_uniform_impl(&client, &y, 0.25).unwrap();
let values: Vec<f64> = result.to_vec();
assert!((values[0] - 0.5).abs() < 1e-10);
}
#[test]
fn test_trapezoid_variable() {
let client = get_client();
let x = Tensor::from_slice(&[0.0, 0.25, 0.5, 0.75, 1.0], &[5], client.device());
let y = Tensor::from_slice(&[0.0, 0.25, 0.5, 0.75, 1.0], &[5], client.device());
let result = trapezoid_impl(&client, &y, &x).unwrap();
let values: Vec<f64> = result.to_vec();
assert!((values[0] - 0.5).abs() < 1e-10);
}
#[test]
fn test_trapezoid_batch() {
let client = get_client();
let x = Tensor::from_slice(&[0.0, 0.5, 1.0], &[3], client.device());
let y = Tensor::from_slice(&[0.0, 0.5, 1.0, 0.0, 1.0, 2.0], &[2, 3], client.device());
let result = trapezoid_impl(&client, &y, &x).unwrap();
let values: Vec<f64> = result.to_vec();
assert!((values[0] - 0.5).abs() < 1e-10);
assert!((values[1] - 1.0).abs() < 1e-10);
}
#[test]
fn test_cumulative_trapezoid() {
let client = get_client();
let y = Tensor::from_slice(&[1.0, 1.0, 1.0, 1.0, 1.0], &[5], client.device());
let result = cumulative_trapezoid_impl(&client, &y, None, 1.0).unwrap();
let values: Vec<f64> = result.to_vec();
assert_eq!(values.len(), 4);
assert!((values[0] - 1.0).abs() < 1e-10);
assert!((values[1] - 2.0).abs() < 1e-10);
assert!((values[2] - 3.0).abs() < 1e-10);
assert!((values[3] - 4.0).abs() < 1e-10);
}
#[test]
fn test_cumulative_trapezoid_variable() {
let client = get_client();
let x = Tensor::from_slice(&[0.0, 1.0, 3.0, 6.0], &[4], client.device());
let y = Tensor::from_slice(&[1.0, 1.0, 1.0, 1.0], &[4], client.device());
let result = cumulative_trapezoid_impl(&client, &y, Some(&x), 1.0).unwrap();
let values: Vec<f64> = result.to_vec();
assert!((values[0] - 1.0).abs() < 1e-10);
assert!((values[1] - 3.0).abs() < 1e-10);
assert!((values[2] - 6.0).abs() < 1e-10);
}
}