use num_bigint::BigInt;
use num_rational::Ratio;
use num_traits::{Signed, Zero};
use crate::api::expr::{Ex, Expr, ExprType, Numeric};
use crate::base::arena::Arena;
use crate::base::errors::SymplexError;
use crate::base::node::{ExprId, ExprNode};
use crate::poly::Poly;
use crate::poly::groebner::{groebner_basis, groebner_basis_lex};
use crate::poly::multipoly::{GrevLex, Lex, MultiPoly};
use crate::poly::polybridge::{expr_to_multipoly, expr_to_poly, multipoly_to_expr, poly_to_expr};
use crate::poly::sturm::SturmChain;
pub use crate::poly::multipoly::MonomialOrder;
fn invalid(operation: &'static str, reason: impl Into<String>) -> SymplexError {
SymplexError::invalid_argument(operation, reason)
}
fn integer_primitive(p: &Poly) -> Poly {
let prim = p.primitive_part();
match prim.leading_coeff() {
Some(lc) if lc.is_negative() => -&prim,
_ => prim,
}
}
fn shared_generators(arena: &Arena, exprs: &[ExprId]) -> Vec<ExprId> {
let mut gens: Vec<ExprId> = Vec::new();
for &e in exprs {
for s in crate::base::walk::free_symbols(arena, e) {
if !gens.contains(&s) {
gens.push(s);
}
}
}
gens.sort_by(|a, b| {
arena
.sort_key(*a)
.cmp(arena.sort_key(*b))
.then_with(|| a.0.cmp(&b.0))
});
gens
}
fn validate_vars(
probe: &Ex,
vars: &[Ex],
operation: &'static str,
) -> Result<Vec<ExprId>, SymplexError> {
if vars.is_empty() {
return Err(invalid(operation, "at least one variable is required"));
}
let mut ids: Vec<ExprId> = Vec::with_capacity(vars.len());
for v in vars {
let id = probe.checked_id(v);
if v.expr_type() != ExprType::Symbol {
return Err(invalid(
operation,
format!("variable `{v}` is not a symbol"),
));
}
if ids.contains(&id) {
return Err(invalid(operation, format!("duplicate variable `{v}`")));
}
ids.push(id);
}
Ok(ids)
}
fn to_multipolys(
arena: &Arena,
exprs: &[ExprId],
vars: &[ExprId],
operation: &'static str,
) -> Result<Vec<MultiPoly<GrevLex>>, SymplexError> {
exprs
.iter()
.map(|&e| {
expr_to_multipoly(arena, e, vars).ok_or_else(|| {
invalid(
operation,
format!(
"`{}` is not a polynomial in the given variables with rational coefficients",
arena.display(e)
),
)
})
})
.collect()
}
fn groebner_in_order(
polys: &[MultiPoly<GrevLex>],
order: MonomialOrder,
) -> Vec<MultiPoly<GrevLex>> {
match order {
MonomialOrder::GrevLex => groebner_basis(polys),
MonomialOrder::Lex => groebner_basis_lex(polys)
.iter()
.map(MultiPoly::convert_order)
.collect(),
}
}
fn reduce_in_order(
f: &MultiPoly<GrevLex>,
basis: &[MultiPoly<GrevLex>],
order: MonomialOrder,
) -> MultiPoly<GrevLex> {
match order {
MonomialOrder::GrevLex => {
let refs: Vec<&MultiPoly<GrevLex>> = basis.iter().collect();
f.reduce(&refs)
}
MonomialOrder::Lex => {
let lex_basis: Vec<MultiPoly<Lex>> =
basis.iter().map(MultiPoly::convert_order).collect();
let refs: Vec<&MultiPoly<Lex>> = lex_basis.iter().collect();
f.convert_order::<Lex>().reduce(&refs).convert_order()
}
}
}
enum RealRoot {
Rational(Ratio<BigInt>),
RootOf { factor: usize, index: usize },
}
fn real_roots_of(f: &Poly) -> Option<(Vec<Poly>, Vec<RealRoot>)> {
let (_content, parts, complete) = crate::poly::factor_zassenhaus::factor_zassenhaus_checked(f);
if !complete {
return None;
}
let mut factors: Vec<Poly> = Vec::with_capacity(parts.len());
let mut chains: Vec<SturmChain> = Vec::with_capacity(parts.len());
let mut square_free = Poly::from_int(1);
for (g, _mult) in &parts {
if g.degree()? == 0 {
continue;
}
square_free = &square_free * g;
chains.push(SturmChain::new(g));
factors.push(g.clone());
}
if square_free.degree().unwrap_or(0) == 0 {
return None;
}
let intervals = SturmChain::new(&square_free).isolate_all_real_roots();
let mut out: Vec<RealRoot> = Vec::with_capacity(intervals.len());
for iv in intervals {
let owner = factors.iter().zip(&chains).position(|(g, chain)| {
if iv.is_point() {
g.eval(&iv.lower).is_zero()
} else {
chain.count_roots_in(&iv.lower, &iv.upper) == 1
}
})?;
let g = &factors[owner];
if g.degree() == Some(1) {
out.push(RealRoot::Rational(-(g.coeff(0) / g.coeff(1))));
} else {
let index = crate::poly::roots::real_root_index(g, &iv.lower, &iv.upper)?;
out.push(RealRoot::RootOf {
factor: owner,
index,
});
}
}
Some((factors, out))
}
fn sylvester_resultant(ctx: &crate::api::context::Context, f: &[Ex], g: &[Ex]) -> Option<Ex> {
let m = f.len().checked_sub(1)?;
let n = g.len().checked_sub(1)?;
if m == 0 && n == 0 {
return Some(ctx.one());
}
if m == 0 {
return Some(f[0].powi(n as i64));
}
if n == 0 {
return Some(g[0].powi(m as i64));
}
let size = m + n;
let zero = ctx.zero();
let mut rows: Vec<Vec<Ex>> = Vec::with_capacity(size);
for i in 0..n {
let mut row = vec![zero.clone(); size];
for (j, c) in f.iter().enumerate() {
row[i + j] = c.clone();
}
rows.push(row);
}
for i in 0..m {
let mut row = vec![zero.clone(); size];
for (j, c) in g.iter().enumerate() {
row[i + j] = c.clone();
}
rows.push(row);
}
let matrix = crate::domains::matrix::Matrix::new(rows);
debug_assert!(matrix.is_ok(), "Sylvester matrix is square by construction");
let det = matrix.ok()?.det();
debug_assert!(det.is_ok(), "determinant of a square matrix over Ex");
Some(det.ok()?.expand())
}
impl Expr<Numeric> {
#[must_use]
pub fn minimal_polynomial(&self, var: &Ex) -> Option<Ex> {
use crate::poly::algebraic::{AlgExpr, minimal_polynomial_of};
let var_id = self.checked_id(var);
let alg = {
let inner = self.inner.read();
AlgExpr::from_arena(&inner.arena, self.raw_id())
};
let mp = alg.and_then(|a| minimal_polynomial_of(&a)).or_else(|| {
let expanded = self.expand();
if expanded == *self {
return None;
}
let alg = {
let inner = self.inner.read();
AlgExpr::from_arena(&inner.arena, expanded.raw_id())
}?;
minimal_polynomial_of(&alg)
})?;
let id = {
let mut inner = self.inner.write();
poly_to_expr(&mut inner.arena, &integer_primitive(&mp), var_id)
};
Some(self.wrap(id))
}
#[must_use]
pub fn gcd_all(&self, other: &Ex) -> Option<Ex> {
self.multipoly_binary(other, MultiPoly::gcd)
}
#[must_use]
pub fn lcm_all(&self, other: &Ex) -> Option<Ex> {
self.multipoly_binary(other, MultiPoly::lcm)
}
fn multipoly_binary(
&self,
other: &Ex,
op: fn(&MultiPoly<GrevLex>, &MultiPoly<GrevLex>) -> MultiPoly<GrevLex>,
) -> Option<Ex> {
let other_id = self.checked_id(other);
let (gens, a, b) = {
let inner = self.inner.read();
let arena = &inner.arena;
let gens = shared_generators(arena, &[self.raw_id(), other_id]);
let a = expr_to_multipoly(arena, self.raw_id(), &gens)?;
let b = expr_to_multipoly(arena, other_id, &gens)?;
(gens, a, b)
};
let result = op(&a, &b);
let id = {
let mut inner = self.inner.write();
multipoly_to_expr(&mut inner.arena, &result, &gens)
};
Some(self.wrap(id))
}
pub fn groebner(
polys: &[Ex],
vars: &[Ex],
order: MonomialOrder,
) -> Result<Vec<Ex>, SymplexError> {
const OP: &str = "groebner";
let probe = vars
.first()
.ok_or_else(|| invalid(OP, "at least one variable is required"))?;
let var_ids = validate_vars(probe, vars, OP)?;
let poly_ids: Vec<ExprId> = polys.iter().map(|p| probe.checked_id(p)).collect();
let mps = {
let inner = probe.inner.read();
to_multipolys(&inner.arena, &poly_ids, &var_ids, OP)?
};
let basis = groebner_in_order(&mps, order);
let ids: Vec<ExprId> = {
let mut inner = probe.inner.write();
let arena = &mut inner.arena;
basis
.iter()
.map(|g| multipoly_to_expr(arena, g, &var_ids))
.collect()
};
Ok(ids.into_iter().map(|id| probe.wrap(id)).collect())
}
pub fn reduce_modulo(
&self,
basis: &[Ex],
vars: &[Ex],
order: MonomialOrder,
) -> Result<Ex, SymplexError> {
const OP: &str = "reduce_modulo";
let var_ids = validate_vars(self, vars, OP)?;
let basis_ids: Vec<ExprId> = basis.iter().map(|b| self.checked_id(b)).collect();
let (f, divisors) = {
let inner = self.inner.read();
let arena = &inner.arena;
let Some(f) = to_multipolys(arena, &[self.raw_id()], &var_ids, OP)?
.into_iter()
.next()
else {
return Err(SymplexError::ComputationFailed {
operation: OP,
reason: "to_multipolys returned no polynomial for one input".into(),
});
};
(f, to_multipolys(arena, &basis_ids, &var_ids, OP)?)
};
let r = reduce_in_order(&f, &divisors, order);
let id = {
let mut inner = self.inner.write();
multipoly_to_expr(&mut inner.arena, &r, &var_ids)
};
Ok(self.wrap(id))
}
#[must_use]
pub fn real_roots(&self, var: &Ex) -> Option<Vec<Ex>> {
let var_id = self.checked_id(var);
let f = {
let inner = self.inner.read();
expr_to_poly(&inner.arena, self.raw_id(), var_id)?
};
if f.degree().unwrap_or(0) == 0 {
return None;
}
let (factors, roots) = real_roots_of(&f)?;
let ids: Vec<ExprId> = {
let mut inner = self.inner.write();
let arena = &mut inner.arena;
let mut factor_ids: Vec<Option<ExprId>> = vec![None; factors.len()];
roots
.into_iter()
.map(|root| match root {
RealRoot::Rational(r) => arena.num_ratio(r),
RealRoot::RootOf { factor, index } => {
let g_expr = *factor_ids[factor]
.get_or_insert_with(|| poly_to_expr(arena, &factors[factor], var_id));
let idx = arena.int(index as i64);
arena.intern(ExprNode::RootOf(g_expr, idx))
}
})
.collect()
};
Some(ids.into_iter().map(|id| self.wrap(id)).collect())
}
#[must_use]
pub fn root_of(&self, var: &Ex, index: usize) -> Option<Ex> {
self.real_roots(var)?.into_iter().nth(index)
}
pub fn factor_mod(&self, var: &Ex, p: u64) -> Result<(Ex, Vec<(Ex, u32)>), SymplexError> {
const OP: &str = "factor_mod";
let var_id = self.checked_id(var);
if !crate::domains::ntheory::isprime(p) {
return Err(invalid(OP, format!("modulus {p} is not prime")));
}
if p == 2 || p >= crate::poly::factor_zassenhaus::MAX_PRIME {
return Err(invalid(
OP,
format!(
"modulus {p} is not supported: p must be an odd prime below {}",
crate::poly::factor_zassenhaus::MAX_PRIME
),
));
}
let f = {
let inner = self.inner.read();
expr_to_poly(&inner.arena, self.raw_id(), var_id).ok_or_else(|| {
invalid(
OP,
"expression is not a polynomial in the given variable with rational coefficients",
)
})?
};
let pb = BigInt::from(p);
if f.coeffs().iter().any(|c| (c.denom() % &pb).is_zero()) {
return Err(invalid(
OP,
format!(
"a coefficient has a denominator divisible by {p}, so it has no inverse mod {p}"
),
));
}
let (lc, factors) =
crate::poly::factor_zassenhaus::factor_mod_p(&f, p).ok_or_else(|| {
SymplexError::ComputationFailed {
operation: OP,
reason: "factor_mod_p rejected a valid modulus and polynomial".into(),
}
})?;
let (lc_id, factor_ids) = {
let mut inner = self.inner.write();
let arena = &mut inner.arena;
let lc_id = arena.int(lc as i64);
let factor_ids: Vec<(ExprId, u32)> = factors
.iter()
.map(|(coeffs, m)| {
let g = Poly::from_coeffs(
coeffs
.iter()
.map(|&c| Ratio::from_integer(BigInt::from(c)))
.collect(),
);
(poly_to_expr(arena, &g, var_id), *m)
})
.collect();
(lc_id, factor_ids)
};
Ok((
self.wrap(lc_id),
factor_ids
.into_iter()
.map(|(id, m)| (self.wrap(id), m))
.collect(),
))
}
#[must_use]
pub fn resultant_symbolic(&self, other: &Ex, var: &Ex) -> Option<Ex> {
let _ = self.checked_id(other);
let _ = self.checked_id(var);
let f = crate::api::poly_ex::Poly::new(self, &[var])?;
let g = crate::api::poly_ex::Poly::new(other, &[var])?;
if f.is_zero() || g.is_zero() {
return Some(self.context().zero());
}
let fc = f.all_coeffs()?;
let gc = g.all_coeffs()?;
sylvester_resultant(&self.context(), &fc, &gc)
}
#[must_use]
pub fn discriminant_symbolic(&self, var: &Ex) -> Option<Ex> {
let _ = self.checked_id(var);
let f = crate::api::poly_ex::Poly::new(self, &[var])?;
let coeffs = f.all_coeffs()?; let n = coeffs.len().checked_sub(1)?;
if n == 0 {
return None;
}
let ctx = self.context();
if n == 1 {
return Some(ctx.one());
}
let deriv: Vec<Ex> = coeffs[..n]
.iter()
.enumerate()
.map(|(j, c)| c * ((n - j) as i64))
.collect();
let size = 2 * n - 1;
let zero = ctx.zero();
let mut rows: Vec<Vec<Ex>> = Vec::with_capacity(size);
for i in 0..n - 1 {
let mut row = vec![zero.clone(); size];
for (j, c) in coeffs.iter().enumerate() {
row[i + j] = c.clone();
}
rows.push(row);
}
for i in 0..n {
let mut row = vec![zero.clone(); size];
for (j, c) in deriv.iter().enumerate() {
row[i + j] = c.clone();
}
rows.push(row);
}
for (j, (slot, c)) in rows[n - 1].iter_mut().zip(coeffs.iter()).enumerate() {
*slot = -&(c * (j as i64));
}
let minor: Vec<Vec<Ex>> = rows[1..].iter().map(|row| row[1..].to_vec()).collect();
let matrix = crate::domains::matrix::Matrix::new(minor);
debug_assert!(
matrix.is_ok(),
"discriminant minor is square by construction"
);
let det = matrix.ok()?.det();
debug_assert!(det.is_ok(), "determinant of a square matrix over Ex");
let det = det.ok()?.expand();
Some(if (n * (n - 1) / 2) % 2 == 1 {
-det
} else {
det
})
}
}