use crate::polynomial::{Polynomial, Var};
#[allow(unused_imports)]
use crate::prelude::*;
use num_rational::BigRational;
use num_traits::{One, Signed, Zero};
#[derive(Debug, Clone)]
pub struct ResultantConfig {
pub method: ResultantMethod,
pub use_modular: bool,
pub max_dense_degree: usize,
}
impl Default for ResultantConfig {
fn default() -> Self {
Self {
method: ResultantMethod::Subresultant,
use_modular: false,
max_dense_degree: 100,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResultantMethod {
Sylvester,
Subresultant,
Bezout,
}
#[derive(Debug, Clone, Default)]
pub struct ResultantStats {
pub resultants_computed: u64,
pub discriminants_computed: u64,
pub sylvester_determinants: u64,
pub subresultant_prs: u64,
pub avg_result_degree: f64,
}
pub struct ResultantComputer {
config: ResultantConfig,
stats: ResultantStats,
}
impl ResultantComputer {
pub fn new(config: ResultantConfig) -> Self {
Self {
config,
stats: ResultantStats::default(),
}
}
pub fn default_config() -> Self {
Self::new(ResultantConfig::default())
}
pub fn resultant(&mut self, p: &Polynomial, q: &Polynomial, var: Var) -> Polynomial {
self.stats.resultants_computed += 1;
let deg_p = p.degree(var);
let deg_q = q.degree(var);
if deg_p == 0 || deg_q == 0 {
return self.handle_constant_case(p, q, var);
}
let result = match self.config.method {
ResultantMethod::Sylvester => self.resultant_sylvester(p, q, var),
ResultantMethod::Subresultant => self.resultant_subresultant(p, q, var),
ResultantMethod::Bezout => {
if p.is_univariate() && q.is_univariate() {
self.resultant_bezout(p, q, var)
} else {
self.resultant_subresultant(p, q, var)
}
}
};
self.update_degree_stats(result.total_degree() as usize);
result
}
fn handle_constant_case(&self, p: &Polynomial, q: &Polynomial, var: Var) -> Polynomial {
let deg_p = p.degree(var);
let deg_q = q.degree(var);
if deg_p == 0 && deg_q == 0 {
Polynomial::one()
} else if deg_p == 0 {
let mut result = Polynomial::one();
for _ in 0..deg_q {
result = &result * p;
}
result
} else {
let mut result = Polynomial::one();
for _ in 0..deg_p {
result = &result * q;
}
result
}
}
fn resultant_sylvester(&mut self, p: &Polynomial, q: &Polynomial, var: Var) -> Polynomial {
self.stats.sylvester_determinants += 1;
let deg_p = p.degree(var) as usize;
let deg_q = q.degree(var) as usize;
let n = deg_p + deg_q;
if n > self.config.max_dense_degree {
return self.resultant_subresultant(p, q, var);
}
let coeff_p: Vec<Polynomial> = (0..=deg_p).map(|i| p.coeff(var, i as u32)).collect();
let coeff_q: Vec<Polynomial> = (0..=deg_q).map(|i| q.coeff(var, i as u32)).collect();
let mut mat: Vec<Vec<Polynomial>> = (0..n)
.map(|_| (0..n).map(|_| Polynomial::zero()).collect())
.collect();
for r in 0..deg_q {
for j in 0..n {
if j >= r && j - r <= deg_p {
mat[r][j] = coeff_p[j - r].clone();
}
}
}
for r in 0..deg_p {
let row = deg_q + r;
for j in 0..n {
if j >= r && j - r <= deg_q {
mat[row][j] = coeff_q[j - r].clone();
}
}
}
bareiss_det(mat)
}
fn resultant_subresultant(&mut self, p: &Polynomial, q: &Polynomial, var: Var) -> Polynomial {
self.stats.subresultant_prs += 1;
let mut a = p.clone();
let mut b = q.clone();
let mut sign_correction = BigRational::one();
while !b.is_zero() {
let deg_a = a.degree(var);
let deg_b = b.degree(var);
if deg_a < deg_b {
core::mem::swap(&mut a, &mut b);
if deg_a % 2 == 1 && deg_b % 2 == 1 {
sign_correction = -sign_correction;
}
}
let r = a.pseudo_remainder(&b, var);
a = b;
b = r;
}
if sign_correction.is_negative() { -a } else { a }
}
fn resultant_bezout(&mut self, p: &Polynomial, q: &Polynomial, var: Var) -> Polynomial {
self.resultant_subresultant(p, q, var)
}
pub fn discriminant(&mut self, p: &Polynomial, var: Var) -> Polynomial {
self.stats.discriminants_computed += 1;
let p_prime = p.derivative(var);
let res = self.resultant(p, &p_prime, var);
let n = p.degree(var);
let sign_exp = (n * (n - 1) / 2) % 2;
if sign_exp == 1 { -res } else { res }
}
pub fn discriminant_normalized(&mut self, p: &Polynomial, var: Var) -> Polynomial {
let disc = self.discriminant(p, var);
let lc = p.leading_coeff_wrt(var);
if lc.is_one() {
return disc;
}
if lc.is_constant() {
let lc_val = lc.constant_value();
if lc_val.is_zero() {
return disc;
}
let inv_lc = lc_val.recip();
disc.scale(&inv_lc)
} else {
disc
}
}
pub fn have_common_root(&mut self, p: &Polynomial, q: &Polynomial, var: Var) -> bool {
if p == q && p.degree(var) > 0 {
return true;
}
let res = self.resultant(p, q, var);
res.is_zero()
}
fn update_degree_stats(&mut self, degree: usize) {
let count = self.stats.resultants_computed + self.stats.discriminants_computed;
let old_avg = self.stats.avg_result_degree;
self.stats.avg_result_degree =
(old_avg * (count - 1) as f64 + degree as f64) / count as f64;
}
pub fn stats(&self) -> &ResultantStats {
&self.stats
}
pub fn reset_stats(&mut self) {
self.stats = ResultantStats::default();
}
}
fn bareiss_det(mut mat: Vec<Vec<Polynomial>>) -> Polynomial {
let n = mat.len();
if n == 0 {
return Polynomial::one();
}
if n == 1 {
return mat.remove(0).remove(0);
}
let mut sign = Polynomial::one();
let neg_one = Polynomial::constant(num_rational::BigRational::from_integer(
num_bigint::BigInt::from(-1),
));
for col in 0..n {
let pivot_row = (col..n).find(|&r| !mat[r][col].is_zero());
let pivot_row = match pivot_row {
Some(r) => r,
None => return Polynomial::zero(),
};
if pivot_row != col {
mat.swap(col, pivot_row);
sign = Polynomial::mul(&sign, &neg_one);
}
let pivot = mat[col][col].clone();
for row in (col + 1)..n {
for j in (col + 1)..n {
let prod1 = Polynomial::mul(&pivot, &mat[row][j]);
let prod2 = Polynomial::mul(&mat[row][col], &mat[col][j]);
let diff = Polynomial::sub(&prod1, &prod2);
if col == 0 {
mat[row][j] = diff;
} else {
let prev_pivot = mat[col - 1][col - 1].clone();
if prev_pivot.is_zero() {
mat[row][j] = Polynomial::zero();
} else if prev_pivot.is_one() {
mat[row][j] = diff;
} else if diff.is_zero() {
mat[row][j] = Polynomial::zero();
} else if prev_pivot.is_constant() && diff.is_constant() {
let num = diff.constant_value();
let den = prev_pivot.constant_value();
mat[row][j] = Polynomial::constant(num / den);
} else {
let (q, _r) = diff.pseudo_div_univariate(&prev_pivot);
mat[row][j] = q;
}
}
}
mat[row][col] = Polynomial::zero();
}
}
let raw = mat[n - 1][n - 1].clone();
Polynomial::mul(&sign, &raw)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_computer_creation() {
let computer = ResultantComputer::default_config();
assert_eq!(computer.stats().resultants_computed, 0);
}
#[test]
fn test_constant_resultant() {
let mut computer = ResultantComputer::default_config();
let var = 0;
let p = Polynomial::constant(BigRational::from_integer(2.into()));
let q = Polynomial::constant(BigRational::from_integer(3.into()));
let res = computer.resultant(&p, &q, var);
assert!(res.is_one());
}
#[test]
fn test_discriminant_linear() {
let mut computer = ResultantComputer::default_config();
let var = 0;
let p = Polynomial::linear(&[(BigRational::one(), var)], -BigRational::one());
let _disc = computer.discriminant(&p, var);
assert_eq!(computer.stats().discriminants_computed, 1);
}
#[test]
fn test_have_common_root() {
let mut computer = ResultantComputer::default_config();
let var = 0;
let p = Polynomial::from_var(var); let q = Polynomial::from_var(var);
assert!(computer.have_common_root(&p, &q, var));
}
#[test]
fn test_stats() {
let mut computer = ResultantComputer::default_config();
let var = 0;
let p = Polynomial::one();
let q = Polynomial::one();
computer.resultant(&p, &q, var);
assert_eq!(computer.stats().resultants_computed, 1);
}
#[test]
fn test_resultant_sylvester_linear() {
let cfg = ResultantConfig {
method: ResultantMethod::Sylvester,
..Default::default()
};
let mut computer = ResultantComputer::new(cfg);
let var: Var = 0;
let p = Polynomial::linear(
&[(BigRational::one(), var)],
-BigRational::from_integer(2.into()),
);
let q = Polynomial::linear(
&[(BigRational::one(), var)],
-BigRational::from_integer(3.into()),
);
let res = computer.resultant(&p, &q, var);
assert!(
res.is_constant(),
"resultant of two linears must be constant; got {res:?}"
);
assert!(
!res.is_zero(),
"resultant of coprime linears must be non-zero"
);
let val = res.constant_value();
assert_eq!(
val,
BigRational::from_integer(1.into()),
"Res(x-2, x-3) via Sylvester should be 1, got {val}"
);
assert_eq!(computer.stats().sylvester_determinants, 1);
}
#[test]
fn test_resultant_sylvester_quadratics() {
let cfg = ResultantConfig {
method: ResultantMethod::Sylvester,
..Default::default()
};
let mut computer = ResultantComputer::new(cfg);
let var: Var = 0;
let p = Polynomial::univariate(
var,
&[
-BigRational::from_integer(5.into()),
BigRational::zero(),
BigRational::one(),
],
);
let q = Polynomial::univariate(
var,
&[
-BigRational::from_integer(2.into()),
BigRational::zero(),
BigRational::one(),
],
);
let res = computer.resultant(&p, &q, var);
assert!(
res.is_constant(),
"resultant of two quadratics (same var) must be constant; got {res:?}"
);
let val = res.constant_value();
assert_eq!(
val,
BigRational::from_integer(9.into()),
"Res(x^2-5, x^2-2) should be 9, got {val}"
);
}
}