use crate::polynomial::{Polynomial, Var};
#[allow(unused_imports)]
use crate::prelude::*;
use num_rational::BigRational;
use num_traits::{One, Signed};
#[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;
Polynomial::one()
}
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() {
disc
} 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();
}
}
#[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);
}
}