use crate::array::Array;
use crate::error::{NumRs2Error, Result};
use num_traits::{Float, One, Zero};
use std::ops::{Add, Div, Mul, Sub};
use super::core::Polynomial;
pub fn polycompanion<T>(c: &Array<T>) -> Result<Array<T>>
where
T: Clone + Zero + One + std::ops::Neg<Output = T> + Div<Output = T> + PartialEq,
{
if c.ndim() != 1 {
return Err(NumRs2Error::DimensionMismatch(
"polycompanion requires 1D array".to_string(),
));
}
let coeffs = c.to_vec();
if coeffs.is_empty() {
return Err(NumRs2Error::InvalidOperation(
"Empty coefficient array".to_string(),
));
}
let leading = coeffs[0].clone();
if leading == T::zero() {
return Err(NumRs2Error::InvalidOperation(
"Leading coefficient cannot be zero".to_string(),
));
}
let n = coeffs.len() - 1;
if n == 0 {
return Ok(Array::zeros(&[0, 0]));
}
let mut companion = vec![T::zero(); n * n];
for i in 1..n {
companion[i * n + (i - 1)] = T::one();
}
for i in 0..n {
companion[i * n + (n - 1)] = -coeffs[i + 1].clone() / leading.clone();
}
Ok(Array::from_vec(companion).reshape(&[n, n]))
}
pub fn polytrim<T>(c: &Array<T>, tol: Option<T>) -> Result<Array<T>>
where
T: Clone + Zero + PartialOrd + Float,
{
if c.ndim() != 1 {
return Err(NumRs2Error::DimensionMismatch(
"polytrim requires 1D array".to_string(),
));
}
let coeffs = c.to_vec();
let tolerance =
tol.unwrap_or_else(|| T::from(1e-13).expect("1e-13 should convert to float type"));
let mut start = 0;
for (i, &coeff) in coeffs.iter().enumerate() {
if coeff.abs() > tolerance {
start = i;
break;
}
}
if start == 0 && coeffs[0].abs() <= tolerance {
let all_zero = coeffs.iter().all(|&x| x.abs() <= tolerance);
if all_zero {
return Ok(Array::from_vec(vec![T::zero()]));
}
}
Ok(Array::from_vec(coeffs[start..].to_vec()))
}
pub fn polyscale<T>(c: &Array<T>, domain: &Array<T>, window: &Array<T>) -> Result<Array<T>>
where
T: Clone
+ Zero
+ One
+ Add<Output = T>
+ Sub<Output = T>
+ Mul<Output = T>
+ Div<Output = T>
+ PartialEq
+ Float,
{
if c.ndim() != 1 || domain.ndim() != 1 || window.ndim() != 1 {
return Err(NumRs2Error::DimensionMismatch(
"polyscale requires 1D arrays".to_string(),
));
}
if domain.size() != 2 || window.size() != 2 {
return Err(NumRs2Error::InvalidOperation(
"Domain and window must have exactly 2 elements".to_string(),
));
}
let domain_vec = domain.to_vec();
let window_vec = window.to_vec();
let a = domain_vec[0];
let b = domain_vec[1];
let window_c = window_vec[0];
let d = window_vec[1];
let _scale = (d - window_c) / (b - a);
let _shift = window_c - _scale * a;
Ok(c.clone())
}
pub fn polyvander<T>(x: &Array<T>, deg: usize) -> Result<Array<T>>
where
T: Clone + Zero + One + Mul<Output = T>,
{
if x.ndim() != 1 {
return Err(NumRs2Error::DimensionMismatch(
"polyvander requires 1D array".to_string(),
));
}
let x_vec = x.to_vec();
let n = x_vec.len();
let cols = deg + 1;
let mut result = Vec::with_capacity(n * cols);
for xi in &x_vec {
let mut x_pow = T::one();
for _ in 0..cols {
result.push(x_pow.clone());
x_pow = x_pow * xi.clone();
}
}
Ok(Array::from_vec(result).reshape(&[n, cols]))
}
pub fn polyvander2d<T>(x: &Array<T>, y: &Array<T>, deg: (usize, usize)) -> Result<Array<T>>
where
T: Clone + Zero + One + Mul<Output = T>,
{
if x.ndim() != 1 || y.ndim() != 1 {
return Err(NumRs2Error::DimensionMismatch(
"polyvander2d requires 1D arrays".to_string(),
));
}
if x.size() != y.size() {
return Err(NumRs2Error::ShapeMismatch {
expected: x.shape(),
actual: y.shape(),
});
}
let x_vec = x.to_vec();
let y_vec = y.to_vec();
let n = x_vec.len();
let (deg_x, deg_y) = deg;
let cols = (deg_x + 1) * (deg_y + 1);
let mut result = Vec::with_capacity(n * cols);
for i in 0..n {
let xi = &x_vec[i];
let yi = &y_vec[i];
let mut x_powers = Vec::with_capacity(deg_x + 1);
let mut x_pow = T::one();
for _ in 0..=deg_x {
x_powers.push(x_pow.clone());
x_pow = x_pow.clone() * xi.clone();
}
let mut y_powers = Vec::with_capacity(deg_y + 1);
let mut y_pow = T::one();
for _ in 0..=deg_y {
y_powers.push(y_pow.clone());
y_pow = y_pow.clone() * yi.clone();
}
for j in 0..=deg_y {
for k in 0..=deg_x {
result.push(x_powers[k].clone() * y_powers[j].clone());
}
}
}
Ok(Array::from_vec(result).reshape(&[n, cols]))
}
pub fn polypower<T>(c: &Array<T>, pow: usize) -> Result<Array<T>>
where
T: Clone + Zero + One + Add<Output = T> + Mul<Output = T> + PartialEq,
{
if c.ndim() != 1 {
return Err(NumRs2Error::DimensionMismatch(
"polypower requires 1D array".to_string(),
));
}
if pow == 0 {
return Ok(Array::from_vec(vec![T::one()]));
}
let poly = Polynomial::new(c.to_vec());
let mut result = poly.clone();
for _ in 1..pow {
result = result * poly.clone();
}
Ok(Array::from_vec(result.coefficients().to_vec()))
}
pub fn polymulx<T>(c: &Array<T>) -> Result<Array<T>>
where
T: Clone + Zero,
{
if c.ndim() != 1 {
return Err(NumRs2Error::DimensionMismatch(
"polymulx requires 1D array".to_string(),
));
}
let mut coeffs = c.to_vec();
coeffs.push(T::zero()); Ok(Array::from_vec(coeffs))
}
pub fn polygrid2d<T>(c: &Array<T>, x: &Array<T>, y: &Array<T>) -> Result<Array<T>>
where
T: Clone + Zero + One + Add<Output = T> + Mul<Output = T> + PartialEq,
{
if c.ndim() != 1 || x.ndim() != 1 || y.ndim() != 1 {
return Err(NumRs2Error::DimensionMismatch(
"polygrid2d requires 1D arrays".to_string(),
));
}
let poly = Polynomial::new(c.to_vec());
let x_vec = x.to_vec();
let y_vec = y.to_vec();
let nx = x_vec.len();
let ny = y_vec.len();
let mut result = Vec::with_capacity(nx * ny);
for yi in &y_vec {
for xi in &x_vec {
let val = poly.evaluate(xi.clone() * yi.clone());
result.push(val);
}
}
Ok(Array::from_vec(result).reshape(&[ny, nx]))
}
pub fn polyval2d<T>(c: &Array<T>, x: &Array<T>, y: &Array<T>) -> Result<Array<T>>
where
T: Clone + Zero + One + Add<Output = T> + Mul<Output = T>,
{
if x.ndim() != 1 || y.ndim() != 1 {
return Err(NumRs2Error::DimensionMismatch(
"polyval2d requires 1D coordinate arrays".to_string(),
));
}
if x.size() != y.size() {
return Err(NumRs2Error::ShapeMismatch {
expected: x.shape(),
actual: y.shape(),
});
}
let c_shape = c.shape();
if c_shape.len() != 2 {
return Err(NumRs2Error::DimensionMismatch(
"polyval2d requires 2D coefficient array".to_string(),
));
}
let x_vec = x.to_vec();
let y_vec = y.to_vec();
let n = x_vec.len();
let deg_y = c_shape[0];
let deg_x = c_shape[1];
let mut result = Vec::with_capacity(n);
for i in 0..n {
let xi = &x_vec[i];
let yi = &y_vec[i];
let mut x_powers = Vec::with_capacity(deg_x);
let mut x_pow = T::one();
for _ in 0..deg_x {
x_powers.push(x_pow.clone());
x_pow = x_pow.clone() * xi.clone();
}
let mut y_powers = Vec::with_capacity(deg_y);
let mut y_pow = T::one();
for _ in 0..deg_y {
y_powers.push(y_pow.clone());
y_pow = y_pow.clone() * yi.clone();
}
let mut sum = T::zero();
for j in 0..deg_y {
for k in 0..deg_x {
let coeff = c.get(&[j, k])?;
sum = sum + coeff * x_powers[k].clone() * y_powers[j].clone();
}
}
result.push(sum);
}
Ok(Array::from_vec(result))
}
pub fn polygcd<T>(p1: &Array<T>, p2: &Array<T>) -> Result<Array<T>>
where
T: Clone
+ Zero
+ One
+ Add<Output = T>
+ Sub<Output = T>
+ Mul<Output = T>
+ Div<Output = T>
+ PartialEq
+ Float,
{
if p1.ndim() != 1 || p2.ndim() != 1 {
return Err(NumRs2Error::DimensionMismatch(
"polygcd requires 1D arrays".to_string(),
));
}
let mut a = Polynomial::new(p1.to_vec());
let mut b = Polynomial::new(p2.to_vec());
while b.degree() > 0
|| b.coefficients()[0].abs() > T::from(1e-14).expect("1e-14 should convert to float type")
{
let (_, remainder) = a.divide(&b)?;
a = b;
b = remainder;
}
let leading = a.coefficients()[0];
let mut coeffs = a.coefficients().to_vec();
for coeff in &mut coeffs {
*coeff = *coeff / leading;
}
Ok(Array::from_vec(coeffs))
}
pub fn polycompose<T>(p: &Array<T>, q: &Array<T>) -> Result<Array<T>>
where
T: Clone + Zero + One + Add<Output = T> + Mul<Output = T> + PartialEq,
{
if p.ndim() != 1 || q.ndim() != 1 {
return Err(NumRs2Error::DimensionMismatch(
"polycompose requires 1D arrays".to_string(),
));
}
let p_coeffs = p.to_vec();
let q_poly = Polynomial::new(q.to_vec());
let mut result = Polynomial::new(vec![p_coeffs[0].clone()]);
for i in 1..p_coeffs.len() {
result = result * q_poly.clone();
let mut result_coeffs = result.coefficients().to_vec();
*result_coeffs
.last_mut()
.expect("result_coeffs should not be empty") = result_coeffs
.last()
.expect("result_coeffs should not be empty")
.clone()
+ p_coeffs[i].clone();
result = Polynomial::new(result_coeffs);
}
Ok(Array::from_vec(result.coefficients().to_vec()))
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_polyvander() {
let x = Array::from_vec(vec![1.0, 2.0, 3.0]);
let v = polyvander(&x, 2).expect("test: polyvander should succeed for valid 1D input");
assert_eq!(v.shape(), vec![3, 3]);
let data = v.to_vec();
assert_relative_eq!(data[0], 1.0, epsilon = 1e-10);
assert_relative_eq!(data[1], 1.0, epsilon = 1e-10);
assert_relative_eq!(data[2], 1.0, epsilon = 1e-10);
assert_relative_eq!(data[3], 1.0, epsilon = 1e-10);
assert_relative_eq!(data[4], 2.0, epsilon = 1e-10);
assert_relative_eq!(data[5], 4.0, epsilon = 1e-10);
assert_relative_eq!(data[6], 1.0, epsilon = 1e-10);
assert_relative_eq!(data[7], 3.0, epsilon = 1e-10);
assert_relative_eq!(data[8], 9.0, epsilon = 1e-10);
}
#[test]
fn test_polyvander_degree_0() {
let x = Array::from_vec(vec![1.0, 2.0, 3.0]);
let v = polyvander(&x, 0).expect("test: polyvander should succeed for degree 0");
assert_eq!(v.shape(), vec![3, 1]);
let data = v.to_vec();
assert_relative_eq!(data[0], 1.0, epsilon = 1e-10);
assert_relative_eq!(data[1], 1.0, epsilon = 1e-10);
assert_relative_eq!(data[2], 1.0, epsilon = 1e-10);
}
#[test]
fn test_polypower() {
let c = Array::from_vec(vec![1.0, 2.0]); let c2 = polypower(&c, 2).expect("test: polypower should succeed for valid polynomial");
let data = c2.to_vec();
assert_relative_eq!(data[0], 1.0, epsilon = 1e-10); assert_relative_eq!(data[1], 4.0, epsilon = 1e-10); assert_relative_eq!(data[2], 4.0, epsilon = 1e-10); }
#[test]
fn test_polypower_zero() {
let c = Array::from_vec(vec![1.0, 2.0]);
let c0 = polypower(&c, 0).expect("test: polypower with power 0 should return 1");
assert_eq!(c0.len(), 1);
assert_relative_eq!(c0.to_vec()[0], 1.0, epsilon = 1e-10);
}
#[test]
fn test_polymulx() {
let c = Array::from_vec(vec![1.0, 3.0, 2.0]); let xc = polymulx(&c).expect("test: polymulx should succeed for valid 1D input");
let data = xc.to_vec();
assert_eq!(data.len(), 4);
assert_relative_eq!(data[0], 1.0, epsilon = 1e-10);
assert_relative_eq!(data[1], 3.0, epsilon = 1e-10);
assert_relative_eq!(data[2], 2.0, epsilon = 1e-10);
assert_relative_eq!(data[3], 0.0, epsilon = 1e-10);
}
#[test]
fn test_polygcd() {
let p1 = Array::from_vec(vec![1.0, 0.0, -1.0]); let p2 = Array::from_vec(vec![1.0, -1.0]);
let gcd = polygcd(&p1, &p2).expect("test: polygcd should succeed for valid polynomials");
let data = gcd.to_vec();
if data.len() == 2 {
let ratio = data[0] / 1.0;
assert_relative_eq!(data[1] / (-1.0), ratio, epsilon = 1e-8);
}
}
#[test]
fn test_polycompose() {
let p = Array::from_vec(vec![1.0, 0.0, 0.0]); let q = Array::from_vec(vec![1.0, 1.0]);
let comp =
polycompose(&p, &q).expect("test: polycompose should succeed for valid polynomials");
let data = comp.to_vec();
assert_eq!(data.len(), 3);
assert_relative_eq!(data[0], 1.0, epsilon = 1e-10);
assert_relative_eq!(data[1], 2.0, epsilon = 1e-10);
assert_relative_eq!(data[2], 1.0, epsilon = 1e-10);
}
#[test]
fn test_polycompose_linear() {
let p = Array::from_vec(vec![2.0, 1.0]); let q = Array::from_vec(vec![3.0, 2.0]);
let comp = polycompose(&p, &q).expect("test: polycompose linear should succeed");
let data = comp.to_vec();
assert_eq!(data.len(), 2);
assert_relative_eq!(data[0], 6.0, epsilon = 1e-10);
assert_relative_eq!(data[1], 5.0, epsilon = 1e-10);
}
#[test]
fn test_polyval2d() {
let c = Array::from_vec(vec![1.0, 1.0, 1.0, 0.0]).reshape(&[2, 2]); let x = Array::from_vec(vec![0.0, 1.0, 2.0]);
let y = Array::from_vec(vec![0.0, 0.0, 0.0]);
let result =
polyval2d(&c, &x, &y).expect("test: polyval2d should succeed for valid inputs");
let data = result.to_vec();
assert_relative_eq!(data[0], 1.0, epsilon = 1e-10);
assert_relative_eq!(data[1], 2.0, epsilon = 1e-10);
assert_relative_eq!(data[2], 3.0, epsilon = 1e-10);
}
#[test]
fn test_polygrid2d() {
let c = Array::from_vec(vec![1.0]); let x = Array::from_vec(vec![0.0, 1.0]);
let y = Array::from_vec(vec![0.0, 1.0, 2.0]);
let result = polygrid2d(&c, &x, &y)
.expect("test: polygrid2d should succeed for constant polynomial");
assert_eq!(result.shape(), vec![3, 2]); for val in result.to_vec() {
assert_relative_eq!(val, 1.0, epsilon = 1e-10);
}
}
#[test]
fn test_polyvander2d() {
let x = Array::from_vec(vec![1.0, 2.0]);
let y = Array::from_vec(vec![1.0, 2.0]);
let v = polyvander2d(&x, &y, (1, 1))
.expect("test: polyvander2d should succeed for valid inputs");
assert_eq!(v.shape(), vec![2, 4]);
let data = v.to_vec();
assert_relative_eq!(data[0], 1.0, epsilon = 1e-10);
assert_relative_eq!(data[1], 1.0, epsilon = 1e-10);
assert_relative_eq!(data[2], 1.0, epsilon = 1e-10);
assert_relative_eq!(data[3], 1.0, epsilon = 1e-10);
assert_relative_eq!(data[4], 1.0, epsilon = 1e-10);
assert_relative_eq!(data[5], 2.0, epsilon = 1e-10);
assert_relative_eq!(data[6], 2.0, epsilon = 1e-10);
assert_relative_eq!(data[7], 4.0, epsilon = 1e-10);
}
}