use std::ops::{
Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign,
};
use super::taylor_std_ops::impl_promote_scalar_ops;
use crate::float::Float;
use crate::laurent::Laurent;
use crate::taylor_ops;
#[inline]
fn align_pole_orders<F: Float, const K: usize>(
lhs: &Laurent<F, K>,
rhs: &Laurent<F, K>,
op_name: &str,
) -> ([F; K], [F; K], i32) {
let p1 = lhs.pole_order();
let p2 = rhs.pole_order();
let p_out = p1.min(p2);
let gap1 = i64::from(p1) - i64::from(p_out);
let gap2 = i64::from(p2) - i64::from(p_out);
assert!(
gap1 < K as i64 && gap2 < K as i64,
"Laurent {op_name}: pole-order gap ({}) exceeds K-1 ({}), coefficients would be silently truncated",
gap1.max(gap2),
K - 1,
);
let shift1 = gap1 as usize;
let shift2 = gap2 as usize;
let a: [F; K] = std::array::from_fn(|i| {
if i >= shift1 && i - shift1 < K {
lhs.coeff(p_out + i as i32)
} else {
F::zero()
}
});
let b: [F; K] = std::array::from_fn(|i| {
if i >= shift2 && i - shift2 < K {
rhs.coeff(p_out + i as i32)
} else {
F::zero()
}
});
(a, b, p_out)
}
impl<F: Float, const K: usize> Add for Laurent<F, K> {
type Output = Self;
#[inline]
fn add(self, rhs: Self) -> Self {
if rhs.is_all_zero_pub() {
return self;
}
if self.is_all_zero_pub() {
return rhs;
}
let (a, b, p_out) = align_pole_orders(&self, &rhs, "Add");
let mut c = [F::zero(); K];
taylor_ops::taylor_add(&a, &b, &mut c);
Laurent::new(c, p_out)
}
}
impl<F: Float, const K: usize> Sub for Laurent<F, K> {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self {
if rhs.is_all_zero_pub() {
return self;
}
if self.is_all_zero_pub() {
return -rhs;
}
let (a, b, p_out) = align_pole_orders(&self, &rhs, "Sub");
let mut c = [F::zero(); K];
taylor_ops::taylor_sub(&a, &b, &mut c);
Laurent::new(c, p_out)
}
}
impl<F: Float, const K: usize> Mul for Laurent<F, K> {
type Output = Self;
#[inline]
fn mul(self, rhs: Self) -> Self {
let mut c = [F::zero(); K];
taylor_ops::taylor_mul(&self.leading_coeffs(), &rhs.leading_coeffs(), &mut c);
let p = match self.pole_order().checked_add(rhs.pole_order()) {
Some(p) => p,
None => return Laurent::nan_pub(),
};
Laurent::new(c, p)
}
}
impl<F: Float, const K: usize> Div for Laurent<F, K> {
type Output = Self;
#[inline]
fn div(self, rhs: Self) -> Self {
if rhs.is_all_zero_pub() {
return Laurent::nan_pub();
}
let mut c = [F::zero(); K];
taylor_ops::taylor_div(&self.leading_coeffs(), &rhs.leading_coeffs(), &mut c);
let p = match self.pole_order().checked_sub(rhs.pole_order()) {
Some(p) => p,
None => return Laurent::nan_pub(),
};
Laurent::new(c, p)
}
}
impl<F: Float, const K: usize> Neg for Laurent<F, K> {
type Output = Self;
#[inline]
fn neg(self) -> Self {
let mut c = [F::zero(); K];
taylor_ops::taylor_neg(&self.leading_coeffs(), &mut c);
Laurent::new(c, self.pole_order())
}
}
impl<F: Float, const K: usize> Rem for Laurent<F, K> {
type Output = Self;
#[inline]
fn rem(self, rhs: Self) -> Self {
let quotient = (self / rhs).trunc();
self - quotient * rhs
}
}
impl<F: Float, const K: usize> AddAssign for Laurent<F, K> {
#[inline]
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
}
}
impl<F: Float, const K: usize> SubAssign for Laurent<F, K> {
#[inline]
fn sub_assign(&mut self, rhs: Self) {
*self = *self - rhs;
}
}
impl<F: Float, const K: usize> MulAssign for Laurent<F, K> {
#[inline]
fn mul_assign(&mut self, rhs: Self) {
*self = *self * rhs;
}
}
impl<F: Float, const K: usize> DivAssign for Laurent<F, K> {
#[inline]
fn div_assign(&mut self, rhs: Self) {
*self = *self / rhs;
}
}
impl<F: Float, const K: usize> RemAssign for Laurent<F, K> {
#[inline]
fn rem_assign(&mut self, rhs: Self) {
*self = *self % rhs;
}
}
impl_promote_scalar_ops!([const K: usize] Laurent<f32, K>, f32);
impl_promote_scalar_ops!([const K: usize] Laurent<f64, K>, f64);
impl<F: Float, const K: usize> PartialEq for Laurent<F, K> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.value() == other.value()
}
}
impl<F: Float, const K: usize> PartialOrd for Laurent<F, K> {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.value().partial_cmp(&other.value())
}
}