use alloc::vec::Vec;
use crate::math::polynomial::{Polynomial, Var};
pub fn resultant(p: &Polynomial, q: &Polynomial, v: Var) -> Option<Polynomial> {
if p.is_zero() || q.is_zero() {
return Some(Polynomial::zero());
}
let m = p.degree_of(v) as usize;
let n = q.degree_of(v) as usize;
if m == 0 {
return Some(p.coeff_of_var(v, 0).pow(n as u32));
}
if n == 0 {
return Some(q.coeff_of_var(v, 0).pow(m as u32));
}
let p_co: Vec<Polynomial> = (0..=m).rev().map(|i| p.coeff_of_var(v, i as u32)).collect();
let q_co: Vec<Polynomial> = (0..=n).rev().map(|j| q.coeff_of_var(v, j as u32)).collect();
let size = m + n;
let mut mat = alloc::vec![alloc::vec![Polynomial::zero(); size]; size];
for (i, row) in mat.iter_mut().take(n).enumerate() {
for (k, c) in p_co.iter().enumerate() {
row[i + k] = c.clone();
}
}
for j in 0..m {
for (k, c) in q_co.iter().enumerate() {
mat[n + j][j + k] = c.clone();
}
}
determinant(&mat)
}
pub fn principal_subresultant_coeffs(
p: &Polynomial,
q: &Polynomial,
v: Var,
) -> Option<Vec<Polynomial>> {
if p.is_zero() || q.is_zero() {
return Some(Vec::new());
}
let (p, q) = if p.degree_of(v) >= q.degree_of(v) {
(p, q)
} else {
(q, p)
};
let m = p.degree_of(v) as usize;
let n = q.degree_of(v) as usize;
if n == 0 {
return Some(alloc::vec![q.coeff_of_var(v, 0).pow(m as u32)]);
}
let p_co: Vec<Polynomial> = (0..=m).rev().map(|i| p.coeff_of_var(v, i as u32)).collect(); let q_co: Vec<Polynomial> = (0..=n).rev().map(|j| q.coeff_of_var(v, j as u32)).collect(); let size = m + n;
let mut syl = alloc::vec![alloc::vec![Polynomial::zero(); size]; size];
for (r, row) in syl.iter_mut().take(n).enumerate() {
for (k, c) in p_co.iter().enumerate() {
row[r + k] = c.clone();
}
}
for r in 0..m {
for (k, c) in q_co.iter().enumerate() {
syl[n + r][r + k] = c.clone();
}
}
let mut out = Vec::with_capacity(n + 1);
for i in 0..=n {
let dim = (m + n) - 2 * i; if dim == 0 {
out.push(Polynomial::constant(1.into()));
continue;
}
let mut rows: Vec<usize> = Vec::with_capacity(dim);
for r in 0..(n - i) {
rows.push(r);
}
for r in 0..(m - i) {
rows.push(n + r);
}
let mut cols: Vec<usize> = Vec::with_capacity(dim);
for c in 0..(dim - 1) {
cols.push(c);
}
cols.push(size - 1 - i);
let sub: Vec<Vec<Polynomial>> = rows
.iter()
.map(|&ri| cols.iter().map(|&ci| syl[ri][ci].clone()).collect())
.collect();
out.push(determinant(&sub)?);
}
Some(out)
}
pub fn discriminant(p: &Polynomial, v: Var) -> Option<Polynomial> {
resultant(p, &p.deriv_var(v), v)
}
fn determinant(mat: &[Vec<Polynomial>]) -> Option<Polynomial> {
bareiss_determinant(mat).or_else(|| cofactor_determinant(mat))
}
fn cofactor_determinant(mat: &[Vec<Polynomial>]) -> Option<Polynomial> {
let n = mat.len();
if n > 7 {
return None; }
if n == 0 {
return Some(Polynomial::constant(1.into()));
}
if n == 1 {
return Some(mat[0][0].clone());
}
let mut det = Polynomial::zero();
for j in 0..n {
if mat[0][j].is_zero() {
continue;
}
let minor: Vec<Vec<Polynomial>> = mat[1..]
.iter()
.map(|row| {
row.iter()
.enumerate()
.filter(|(c, _)| *c != j)
.map(|(_, p)| p.clone())
.collect()
})
.collect();
let sub = cofactor_determinant(&minor)?;
let term = mat[0][j].mul(&sub);
det = if j % 2 == 0 {
det.add(&term)
} else {
det.sub(&term)
};
}
Some(det)
}
fn bareiss_determinant(mat: &[Vec<Polynomial>]) -> Option<Polynomial> {
let n = mat.len();
if n == 0 {
return Some(Polynomial::constant(1.into()));
}
let mut m: Vec<Vec<Polynomial>> = mat.to_vec();
let mut sign = 1i32;
let mut prev = Polynomial::constant(1.into());
for k in 0..n - 1 {
if m[k][k].is_zero() {
match (k + 1..n).find(|&i| !m[i][k].is_zero()) {
Some(piv) => {
m.swap(k, piv);
sign = -sign;
}
None => return Some(Polynomial::zero()), }
}
let pivot = m[k][k].clone();
for i in (k + 1)..n {
for j in (k + 1)..n {
let num = m[i][j].mul(&pivot).sub(&m[i][k].mul(&m[k][j]));
m[i][j] = if prev.as_constant() == Some(1.into()) {
num
} else {
num.div_exact(&prev)?
};
}
}
prev = pivot;
}
let det = m[n - 1][n - 1].clone();
Some(if sign < 0 { det.neg() } else { det })
}
#[cfg(test)]
mod tests {
use super::*;
use crate::math::polynomial::Monomial;
use puremp::Rational;
fn r(n: i64) -> Rational {
Rational::from_integer(n.into())
}
fn mono(pairs: &[(Var, u32)]) -> Monomial {
Monomial::from_powers(pairs)
}
fn uni(cs: &[(i64, u32)]) -> Polynomial {
Polynomial::from_terms(cs.iter().map(|&(c, d)| (r(c), mono(&[(0, d)]))).collect())
}
#[test]
fn resultant_coprime_linear_nonzero() {
let a = uni(&[(-1, 0), (1, 1)]); let b = uni(&[(-2, 0), (1, 1)]); let res = resultant(&a, &b, 0).unwrap();
assert!(res.as_constant().is_some());
assert!(!res.as_constant().unwrap().is_zero()); }
#[test]
fn resultant_common_root_is_zero() {
let a = uni(&[(-1, 0), (1, 1)]); let b = uni(&[(-1, 0), (0, 1), (1, 2)]); let res = resultant(&a, &b, 0).unwrap();
assert_eq!(res.as_constant(), Some(r(0)));
}
#[test]
fn discriminant_of_quadratic() {
let p = Polynomial::from_terms(alloc::vec![
(r(1), mono(&[(0, 2)])),
(r(1), mono(&[(0, 1), (1, 1)])),
(r(1), mono(&[(2, 1)])),
]);
let disc = discriminant(&p, 0).unwrap();
let expect = Polynomial::from_terms(alloc::vec![
(r(1), mono(&[(1, 2)])),
(r(-4), mono(&[(2, 1)])),
]);
assert!(disc == expect || disc == expect.neg(), "got {disc:?}");
}
#[test]
fn psc_coprime() {
let a = uni(&[(-1, 0), (1, 1)]); let b = uni(&[(-2, 0), (1, 1)]); let s = principal_subresultant_coeffs(&a, &b, 0).unwrap();
assert_eq!(s.len(), 2);
assert!(!s[0].is_zero());
}
#[test]
fn psc_common_factor_degree1() {
let p = uni(&[(2, 0), (-3, 1), (1, 2)]);
let q = uni(&[(3, 0), (-4, 1), (1, 2)]);
let s = principal_subresultant_coeffs(&p, &q, 0).unwrap();
assert_eq!(s.len(), 3); assert!(s[0].is_zero(), "s0 (resultant) should vanish: {:?}", s[0]);
assert!(!s[1].is_zero(), "s1 should be nonzero for gcd deg 1");
}
#[test]
fn psc_proportional() {
let p = uni(&[(-1, 0), (0, 1), (1, 2)]); let q = uni(&[(-2, 0), (0, 1), (2, 2)]); let s = principal_subresultant_coeffs(&p, &q, 0).unwrap();
assert!(
s[0].is_zero() && s[1].is_zero(),
"proportional ⇒ s0=s1=0: {s:?}"
);
}
#[test]
fn psc_s0_is_resultant() {
let a = Polynomial::from_terms(alloc::vec![
(r(1), mono(&[(1, 1)])), (r(-1), mono(&[(0, 1)])), ]);
let b = Polynomial::from_terms(alloc::vec![
(r(1), mono(&[(1, 2)])), (r(-2), Monomial::one()),
]);
let res = resultant(&a, &b, 1).unwrap();
let s = principal_subresultant_coeffs(&a, &b, 1).unwrap();
assert!(
s[0] == res || s[0] == res.neg(),
"s0 vs Res: {:?} {:?}",
s[0],
res
);
}
#[test]
fn resultant_eliminates_variable() {
let a = Polynomial::from_terms(alloc::vec![
(r(1), mono(&[(1, 1)])), (r(-1), mono(&[(0, 1)])), ]);
let b = Polynomial::from_terms(alloc::vec![
(r(1), mono(&[(1, 2)])), (r(-2), Monomial::one()),
]);
let res = resultant(&a, &b, 1).unwrap(); let expect = Polynomial::from_terms(alloc::vec![
(r(1), mono(&[(0, 2)])),
(r(-2), Monomial::one()),
]);
assert!(res == expect || res == expect.neg(), "got {res:?}");
}
}