use ocas_domain::EuclideanDomain;
use ocas_poly::DenseUnivariatePolynomial;
#[derive(Debug, Clone)]
pub struct PartialFractionTerm<D: EuclideanDomain> {
pub numer: DenseUnivariatePolynomial<D>,
pub denom: DenseUnivariatePolynomial<D>,
pub exp: usize,
}
pub fn apart<D: EuclideanDomain>(
num: &DenseUnivariatePolynomial<D>,
den: &DenseUnivariatePolynomial<D>,
) -> (
Option<DenseUnivariatePolynomial<D>>,
Vec<PartialFractionTerm<D>>,
) {
let _d = num.domain();
if den.is_zero() {
return (Some(num.clone()), Vec::new());
}
if num.is_zero() {
return (None, Vec::new());
}
let (quotient, remainder) = match num.div_rem(den) {
Some(v) => v,
None => return (Some(num.clone()), Vec::new()),
};
let poly_part = if quotient.is_zero() {
None
} else {
Some(quotient)
};
if remainder.is_zero() {
return (poly_part, Vec::new());
}
let sq_free = den.square_free_factorization();
if sq_free.is_empty() {
return (
poly_part,
vec![PartialFractionTerm {
numer: remainder,
denom: den.clone(),
exp: 1,
}],
);
}
let mut terms = Vec::new();
if sq_free.len() == 1 {
let (factor, exp) = &sq_free[0];
if *exp == 1 {
terms.push(PartialFractionTerm {
numer: remainder.clone(),
denom: factor.clone(),
exp: 1,
});
} else {
let expanded = remainder.p_adic_expansion(factor);
for (k, coeff) in expanded.into_iter().enumerate() {
if !coeff.is_zero() {
terms.push(PartialFractionTerm {
numer: coeff,
denom: factor.clone(),
exp: k + 1,
});
}
}
}
} else {
let mut moduli: Vec<DenseUnivariatePolynomial<D>> =
sq_free.iter().map(|(f, e)| f.pow(*e as u32)).collect();
let deltas = DenseUnivariatePolynomial::diophantine(&mut moduli, &remainder);
for (i, delta) in deltas.into_iter().enumerate() {
let (factor, exp) = &sq_free[i];
if delta.is_zero() {
continue;
}
if *exp == 1 {
terms.push(PartialFractionTerm {
numer: delta,
denom: factor.clone(),
exp: 1,
});
} else {
let expanded = delta.p_adic_expansion(factor);
for (k, coeff) in expanded.into_iter().enumerate() {
if !coeff.is_zero() {
terms.push(PartialFractionTerm {
numer: coeff,
denom: factor.clone(),
exp: k + 1,
});
}
}
}
}
}
(poly_part, terms)
}
pub fn together<D: EuclideanDomain>(
poly_part: Option<&DenseUnivariatePolynomial<D>>,
terms: &[PartialFractionTerm<D>],
) -> (DenseUnivariatePolynomial<D>, DenseUnivariatePolynomial<D>) {
if terms.is_empty() {
let zero = DenseUnivariatePolynomial::new(
terms
.first()
.map(|t| t.numer.domain().clone())
.unwrap_or_else(|| {
poly_part.unwrap().domain().clone()
}),
);
let one = zero.one();
let pp = poly_part.cloned().unwrap_or(zero);
return (pp, one);
}
let _domain = terms[0].numer.domain();
let mut common_den = terms[0].denom.one();
for term in terms {
let factor = term.denom.pow(term.exp as u32);
common_den = common_den.mul(&factor);
}
let mut combined_num = common_den.zero();
for term in terms {
let factor = term.denom.pow(term.exp as u32);
let cofactor = common_den
.div_rem(&factor)
.map(|(q, _)| q)
.unwrap_or(common_den.clone());
let contribution = term.numer.mul(&cofactor);
combined_num = combined_num.add(&contribution);
}
if let Some(pp) = poly_part {
let pp_contribution = pp.mul(&common_den);
combined_num = combined_num.add(&pp_contribution);
}
(combined_num, common_den)
}
#[cfg(test)]
mod tests {
use super::*;
use ocas_domain::{Rational, RationalDomain};
fn rat_poly(coeffs: &[i64]) -> DenseUnivariatePolynomial<RationalDomain> {
DenseUnivariatePolynomial::from_coeffs(
RationalDomain,
coeffs.iter().map(|&c| Rational::new(c, 1)).collect(),
)
}
fn rat_poly_r(coeffs: &[(i64, i64)]) -> DenseUnivariatePolynomial<RationalDomain> {
DenseUnivariatePolynomial::from_coeffs(
RationalDomain,
coeffs.iter().map(|&(n, d)| Rational::new(n, d)).collect(),
)
}
#[test]
fn apart_simple() {
let num = rat_poly(&[1]); let den = rat_poly(&[-1, 0, 1]);
let (poly_part, terms) = apart(&num, &den);
assert!(poly_part.is_none());
assert_eq!(terms.len(), 1);
assert_eq!(terms[0].exp, 1);
let (n, _d) = together(None, &terms);
assert!(!n.is_zero());
}
#[test]
fn apart_with_polynomial_part() {
let num = rat_poly(&[1, 0, 1]); let den = rat_poly(&[-1, 1]);
let (poly_part, terms) = apart(&num, &den);
assert!(poly_part.is_some());
let pp = poly_part.unwrap();
assert_eq!(pp.degree(), Some(1));
assert_eq!(pp.coeff(0), Some(&Rational::new(1, 1)));
assert_eq!(pp.coeff(1), Some(&Rational::new(1, 1)));
assert_eq!(terms.len(), 1);
}
#[test]
fn apart_repeated_factor() {
let num = rat_poly(&[1]); let den = rat_poly(&[1, -2, 1]);
let (poly_part, terms) = apart(&num, &den);
assert!(poly_part.is_none());
assert!(!terms.is_empty());
}
#[test]
fn apart_trivial() {
let num = rat_poly(&[0, 1]); let den = rat_poly(&[0, 1]);
let (poly_part, terms) = apart(&num, &den);
assert!(poly_part.is_some());
assert!(terms.is_empty());
}
#[test]
fn together_roundtrip() {
let terms = vec![
PartialFractionTerm {
numer: rat_poly_r(&[(1, 2)]), denom: rat_poly(&[-1, 1]), exp: 1,
},
PartialFractionTerm {
numer: rat_poly_r(&[(-1, 2)]), denom: rat_poly(&[1, 1]), exp: 1,
},
];
let (n, d) = together(None, &terms);
assert!(!n.is_zero());
assert_eq!(d.degree(), Some(2));
}
}