use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use std::sync::Arc;
use num_bigint::BigInt;
use num_integer::Integer;
use num_rational::Ratio;
use num_traits::{One, Signed, Zero};
use crate::api::context::Context;
use crate::api::expr::{Ex, ExprType};
use crate::base::arena::Arena;
use crate::base::errors::SymplexError;
use crate::base::node::{ExprId, ExprNode};
use crate::domains::matrix::Matrix;
use crate::poly::multipoly::{GrevLex, MultiPoly};
use crate::poly::polybridge;
fn invalid(operation: &'static str, reason: impl Into<String>) -> SymplexError {
SymplexError::InvalidArgument {
operation,
reason: reason.into(),
}
}
fn wrap(ctx: &Context, id: ExprId) -> Ex {
Ex::from_raw_parts(ctx.id, Arc::clone(&ctx.inner), id)
}
fn norm_coeff(arena: &mut Arena, id: ExprId) -> ExprId {
let e = crate::transforms::expand::expand(arena, id);
crate::transforms::eval::eval(arena, e)
}
fn mentions_any(arena: &Arena, expr: ExprId, gens: &[ExprId]) -> bool {
gens.iter()
.any(|&g| crate::base::walk::contains(arena, expr, g))
}
fn normalize_terms(arena: &mut Arena, raw: Vec<(Vec<u32>, ExprId)>) -> Vec<(Vec<u32>, ExprId)> {
let mut buckets: BTreeMap<Vec<u32>, Vec<ExprId>> = BTreeMap::new();
for (e, c) in raw {
buckets.entry(e).or_default().push(c);
}
let mut out = Vec::with_capacity(buckets.len());
for (e, cs) in buckets {
let c = if cs.len() == 1 { cs[0] } else { arena.add(&cs) };
let c = norm_coeff(arena, c);
if !arena.is_zero_structural(c) {
out.push((e, c));
}
}
out
}
fn monomial_expr(arena: &mut Arena, c: ExprId, exps: &[u32], gens: &[ExprId]) -> ExprId {
let mut factors: Vec<ExprId> = Vec::with_capacity(exps.len() + 1);
if c != arena.one {
factors.push(c);
}
for (&e, &g) in exps.iter().zip(gens) {
match e {
0 => {}
1 => factors.push(g),
_ => {
let e_id = arena.int(i64::from(e));
factors.push(arena.pow(g, e_id));
}
}
}
match factors.len() {
0 => arena.one,
1 => factors[0],
_ => arena.mul(&factors),
}
}
fn validate_gens(
ctx: &Context,
gens: &[&Ex],
operation: &'static str,
) -> Result<Vec<Ex>, SymplexError> {
let probe = ctx.zero();
let mut ids: Vec<ExprId> = Vec::with_capacity(gens.len());
for g in gens {
let id = probe.checked_id(g);
if g.expr_type() != ExprType::Symbol {
return Err(invalid(
operation,
format!("generator `{g}` is not a symbol"),
));
}
if ids.contains(&id) {
return Err(invalid(operation, format!("duplicate generator `{g}`")));
}
ids.push(id);
}
Ok(gens.iter().map(|g| (*g).clone()).collect())
}
#[derive(Clone, Debug)]
pub struct Poly {
ctx: Context,
gens: Vec<Ex>,
terms: BTreeMap<Vec<u32>, Ex>,
}
impl Poly {
fn from_normalized(ctx: &Context, gens: Vec<Ex>, ids: Vec<(Vec<u32>, ExprId)>) -> Poly {
let terms = ids.into_iter().map(|(e, c)| (e, wrap(ctx, c))).collect();
Poly {
ctx: ctx.clone(),
gens,
terms,
}
}
fn from_raw(ctx: &Context, gens: Vec<Ex>, raw: Vec<(Vec<u32>, ExprId)>) -> Poly {
let ids = ctx.with_arena_mut(|arena| normalize_terms(arena, raw));
Self::from_normalized(ctx, gens, ids)
}
fn gen_ids(&self) -> Vec<ExprId> {
self.gens.iter().map(Ex::raw_id).collect()
}
fn raw_terms(&self) -> Vec<(Vec<u32>, ExprId)> {
self.terms
.iter()
.map(|(e, c)| (e.clone(), c.raw_id()))
.collect()
}
#[must_use]
pub fn new(expr: &Ex, gens: &[&Ex]) -> Option<Poly> {
Self::try_new(expr, gens).ok()
}
pub fn try_new(expr: &Ex, gens: &[&Ex]) -> Result<Poly, SymplexError> {
const OP: &str = "Poly::new";
if gens.is_empty() {
return Err(invalid(OP, "at least one generator is required"));
}
let ctx = expr.context();
let gens = validate_gens(&ctx, gens, OP)?;
let gen_ids: Vec<ExprId> = gens.iter().map(Ex::raw_id).collect();
let ids = ctx.with_arena_mut(|arena| {
let raw = polybridge::symbolic_multipoly_terms(arena, expr.raw_id(), &gen_ids)?;
Some(normalize_terms(arena, raw))
});
match ids {
Some(ids) => Ok(Self::from_normalized(&ctx, gens, ids)),
None => {
let reason = ctx.with_arena_mut(|arena| {
let expanded = crate::transforms::expand::expand(arena, expr.raw_id());
polybridge::non_polynomial_reason(arena, expanded, &gen_ids).or_else(|| {
polybridge::non_polynomial_reason(arena, expr.raw_id(), &gen_ids)
})
});
Err(invalid(
OP,
reason.unwrap_or_else(|| {
format!("`{expr}` is not a polynomial in the generators")
}),
))
}
}
}
pub fn from_terms(
ctx: &Context,
gens: &[&Ex],
terms: Vec<(Vec<u32>, Ex)>,
) -> Result<Poly, SymplexError> {
const OP: &str = "Poly::from_terms";
if gens.is_empty() {
return Err(invalid(OP, "at least one generator is required"));
}
let gens = validate_gens(ctx, gens, OP)?;
let gen_ids: Vec<ExprId> = gens.iter().map(Ex::raw_id).collect();
let probe = ctx.zero();
let mut raw: Vec<(Vec<u32>, ExprId)> = Vec::with_capacity(terms.len());
for (e, c) in terms {
if e.len() != gen_ids.len() {
return Err(invalid(
OP,
format!(
"exponent vector has length {} but there are {} generators",
e.len(),
gen_ids.len()
),
));
}
let cid = probe.checked_id(&c);
let mentions = ctx.with_arena_mut(|arena| mentions_any(arena, cid, &gen_ids));
if mentions {
return Err(invalid(
OP,
format!("coefficient `{c}` mentions a generator"),
));
}
raw.push((e, cid));
}
Ok(Self::from_raw(ctx, gens, raw))
}
pub fn zero(ctx: &Context, gens: &[&Ex]) -> Result<Poly, SymplexError> {
if gens.is_empty() {
return Err(invalid("Poly::zero", "at least one generator is required"));
}
let gens = validate_gens(ctx, gens, "Poly::zero")?;
Ok(Self::from_normalized(ctx, gens, vec![]))
}
pub fn one(ctx: &Context, gens: &[&Ex]) -> Result<Poly, SymplexError> {
Self::constant(ctx, gens, &ctx.one())
}
pub fn constant(ctx: &Context, gens: &[&Ex], c: &Ex) -> Result<Poly, SymplexError> {
if gens.is_empty() {
return Err(invalid(
"Poly::constant",
"at least one generator is required",
));
}
let n = gens.len();
Self::from_terms(ctx, gens, vec![(vec![0; n], c.clone())])
}
pub fn from_multipoly(
ctx: &Context,
gens: &[&Ex],
mp: &MultiPoly<GrevLex>,
) -> Result<Poly, SymplexError> {
const OP: &str = "Poly::from_multipoly";
if gens.is_empty() {
return Err(invalid(OP, "at least one generator is required"));
}
if gens.len() != mp.num_vars() {
return Err(invalid(
OP,
format!(
"{} generators but the polynomial has {} variables",
gens.len(),
mp.num_vars()
),
));
}
let gens = validate_gens(ctx, gens, OP)?;
let ids: Vec<(Vec<u32>, ExprId)> = ctx.with_arena_mut(|arena| {
mp.terms()
.map(|(e, c)| {
let nid = arena.intern_num(c.clone());
(e.to_vec(), arena.intern(ExprNode::Num(nid)))
})
.collect()
});
Ok(Self::from_normalized(ctx, gens, ids))
}
}
impl<O: crate::poly::multipoly::MonomialOrd> MultiPoly<O> {
pub fn to_ex(&self, ctx: &Context, gens: &[&Ex]) -> Result<Ex, SymplexError> {
Ok(Poly::from_multipoly(ctx, gens, &self.convert_order())?.to_ex())
}
}
impl Poly {
#[must_use]
pub fn gens(&self) -> &[Ex] {
&self.gens
}
#[must_use]
pub fn num_gens(&self) -> usize {
self.gens.len()
}
#[must_use]
pub fn context(&self) -> Context {
self.ctx.clone()
}
#[must_use]
pub fn is_zero(&self) -> bool {
self.terms.is_empty()
}
#[must_use]
pub fn is_ground(&self) -> bool {
self.terms.keys().all(|e| e.iter().all(|&x| x == 0))
}
#[must_use]
pub fn is_univariate(&self) -> bool {
self.gens.len() == 1
}
#[must_use]
pub fn is_linear(&self) -> bool {
self.terms.keys().all(|e| e.iter().sum::<u32>() <= 1)
}
#[must_use]
pub fn is_homogeneous(&self) -> bool {
let mut degs = self.terms.keys().map(|e| e.iter().sum::<u32>());
match degs.next() {
None => true,
Some(d) => degs.all(|x| x == d),
}
}
#[must_use]
pub fn has_rational_coeffs(&self) -> bool {
self.terms.values().all(|c| c.as_rational().is_some())
}
#[must_use]
pub fn num_terms(&self) -> usize {
self.terms.len()
}
#[must_use]
pub fn terms(&self) -> Vec<(Vec<u32>, Ex)> {
self.terms
.iter()
.rev()
.map(|(e, c)| (e.clone(), c.clone()))
.collect()
}
pub fn terms_iter(&self) -> impl Iterator<Item = (&[u32], &Ex)> + '_ {
self.terms.iter().rev().map(|(e, c)| (e.as_slice(), c))
}
#[must_use]
pub fn coeffs_rational(&self) -> Option<Vec<Ratio<BigInt>>> {
self.terms.values().rev().map(Ex::as_rational).collect()
}
#[must_use]
pub fn monoms(&self) -> Vec<Vec<u32>> {
self.terms.keys().rev().cloned().collect()
}
#[must_use]
pub fn coeffs(&self) -> Vec<Ex> {
self.terms.values().rev().cloned().collect()
}
pub fn coeff_monomial(&self, exps: &[u32]) -> Result<Ex, SymplexError> {
if exps.len() != self.gens.len() {
return Err(invalid(
"Poly::coeff_monomial",
format!(
"exponent vector has length {} but there are {} generators",
exps.len(),
self.gens.len()
),
));
}
Ok(self
.terms
.get(exps)
.cloned()
.unwrap_or_else(|| self.ctx.zero()))
}
#[must_use]
pub fn total_degree(&self) -> Option<u32> {
self.terms.keys().map(|e| e.iter().sum::<u32>()).max()
}
#[must_use]
pub fn degree_in(&self, var: &Ex) -> Option<u32> {
let i = self.gens.iter().position(|g| g == var)?;
self.terms.keys().map(|e| e[i]).max()
}
#[must_use]
pub fn degree_list(&self) -> Vec<u32> {
let mut out = vec![0u32; self.gens.len()];
for e in self.terms.keys() {
for (o, &x) in out.iter_mut().zip(e) {
*o = (*o).max(x);
}
}
out
}
#[must_use]
pub fn leading_term(&self) -> Option<(Vec<u32>, Ex)> {
self.terms
.last_key_value()
.map(|(e, c)| (e.clone(), c.clone()))
}
#[must_use]
pub fn leading_coeff(&self) -> Ex {
self.terms
.last_key_value()
.map_or_else(|| self.ctx.zero(), |(_, c)| c.clone())
}
#[must_use]
pub fn leading_monomial(&self) -> Option<Vec<u32>> {
self.terms.last_key_value().map(|(e, _)| e.clone())
}
#[must_use]
pub fn all_coeffs(&self) -> Option<Vec<Ex>> {
if self.gens.len() != 1 {
return None;
}
let deg = self.terms.keys().map(|e| e[0]).max().unwrap_or(0);
let zero = self.ctx.zero();
let mut out = vec![zero; deg as usize + 1];
for (e, c) in &self.terms {
out[(deg - e[0]) as usize] = c.clone();
}
Some(out)
}
#[must_use]
pub fn equals(&self, other: &Poly) -> bool {
self.gens == other.gens && self.terms == other.terms
}
}
impl Poly {
#[must_use]
pub fn to_ex(&self) -> Ex {
let gens = self.gen_ids();
let raw = self.raw_terms();
let id = self.ctx.with_arena_mut(|arena| {
let terms: Vec<ExprId> = raw
.iter()
.map(|(e, c)| monomial_expr(arena, *c, e, &gens))
.collect();
match terms.len() {
0 => arena.zero,
1 => terms[0],
_ => arena.add(&terms),
}
});
wrap(&self.ctx, id)
}
pub fn eval(&self, values: &[&Ex]) -> Result<Ex, SymplexError> {
if values.len() != self.gens.len() {
return Err(invalid(
"Poly::eval",
format!("{} values for {} generators", values.len(), self.gens.len()),
));
}
let probe = self.ctx.zero();
let value_ids: Vec<ExprId> = values.iter().map(|v| probe.checked_id(v)).collect();
let raw = self.raw_terms();
let id = self.ctx.with_arena_mut(|arena| {
let terms: Vec<ExprId> = raw
.iter()
.map(|(e, c)| monomial_expr(arena, *c, e, &value_ids))
.collect();
match terms.len() {
0 => arena.zero,
1 => terms[0],
_ => arena.add(&terms),
}
});
Ok(wrap(&self.ctx, id).eval())
}
pub fn eval_gen(&self, var: &Ex, value: &Ex) -> Result<Poly, SymplexError> {
const OP: &str = "Poly::eval_gen";
let probe = self.ctx.zero();
let gen_id = probe.checked_id(var);
let value_id = probe.checked_id(value);
let Some(i) = self.gens.iter().position(|g| g == var) else {
return Err(invalid(OP, format!("`{var}` is not a generator")));
};
let remaining: Vec<Ex> = self
.gens
.iter()
.enumerate()
.filter(|(k, _)| *k != i)
.map(|(_, g)| g.clone())
.collect();
let remaining_ids: Vec<ExprId> = remaining.iter().map(Ex::raw_id).collect();
let raw = self.raw_terms();
let result: Result<Vec<(Vec<u32>, ExprId)>, SymplexError> =
self.ctx.with_arena_mut(|arena| {
if crate::base::walk::contains(arena, value_id, gen_id) {
return Err(invalid(
OP,
format!(
"value `{}` mentions the generator itself",
arena.display(value_id)
),
));
}
let mut subst: Vec<ExprId> = Vec::with_capacity(raw.len());
for (e, c) in &raw {
let mut rest: Vec<u32> = e.clone();
let k = rest.remove(i);
let mut factors = vec![*c];
match k {
0 => {}
1 => factors.push(value_id),
_ => {
let k_id = arena.int(i64::from(k));
factors.push(arena.pow(value_id, k_id));
}
}
let coeff = if factors.len() == 1 {
factors[0]
} else {
arena.mul(&factors)
};
subst.push(monomial_expr(arena, coeff, &rest, &remaining_ids));
}
let total = match subst.len() {
0 => arena.zero,
1 => subst[0],
_ => arena.add(&subst),
};
if remaining_ids.is_empty() {
let c = norm_coeff(arena, total);
return Ok(if arena.is_zero_structural(c) {
vec![]
} else {
vec![(vec![], c)]
});
}
match polybridge::symbolic_multipoly_terms(arena, total, &remaining_ids) {
Some(terms) => Ok(normalize_terms(arena, terms)),
None => Err(invalid(
OP,
"value is not polynomial in the remaining generators",
)),
}
});
Ok(Self::from_normalized(&self.ctx, remaining, result?))
}
#[must_use]
pub fn to_multipoly(&self) -> Option<MultiPoly<GrevLex>> {
let mut terms: Vec<(Vec<u32>, Ratio<BigInt>)> = Vec::with_capacity(self.terms.len());
for (e, c) in &self.terms {
terms.push((e.clone(), c.as_rational()?));
}
MultiPoly::from_terms(self.gens.len(), terms)
}
pub fn nroots(&self, digits: u32) -> Result<Vec<(f64, f64)>, SymplexError> {
if self.gens.len() != 1 {
return Err(invalid("Poly::nroots", "polynomial must be univariate"));
}
if !self.has_rational_coeffs() {
return Err(invalid(
"Poly::nroots",
"polynomial must have rational coefficients",
));
}
self.to_ex().nroots(&self.gens[0], digits)
}
fn univariate_rational_gen(&self) -> Option<&Ex> {
if self.gens.len() == 1 && self.has_rational_coeffs() {
self.gens.first()
} else {
None
}
}
#[must_use]
pub fn count_real_roots(&self) -> Option<usize> {
let x = self.univariate_rational_gen()?;
self.to_ex().count_real_roots(x)
}
#[must_use]
pub fn count_real_roots_in(&self, lo: &Ex, hi: &Ex) -> Option<usize> {
let x = self.univariate_rational_gen()?;
self.to_ex().count_real_roots_in(x, lo, hi)
}
#[must_use]
pub fn real_roots_isolate(&self) -> Vec<(Ex, Ex)> {
match self.univariate_rational_gen() {
Some(x) => self.to_ex().real_roots_isolate(x),
None => Vec::new(),
}
}
#[must_use]
pub fn is_nonnegative_on(&self, lo: &Ex, hi: &Ex) -> Option<bool> {
let x = self.univariate_rational_gen()?;
self.to_ex().poly_is_nonnegative_on(x, lo, hi)
}
#[must_use]
pub fn is_positive_on(&self, lo: &Ex, hi: &Ex) -> Option<bool> {
let x = self.univariate_rational_gen()?;
self.to_ex().poly_is_positive_on(x, lo, hi)
}
pub fn shift(&self, var: &Ex, a: &Ex) -> Result<Poly, SymplexError> {
let probe = self.ctx.zero();
let gen_id = probe.checked_id(var);
if !self.gens.iter().any(|g| g.raw_id() == gen_id) {
return Err(invalid("Poly::shift", "not a generator of this polynomial"));
}
for g in &self.gens {
if a.contains(g) {
return Err(invalid(
"Poly::shift",
"the shift must not mention a generator",
));
}
}
let replacement = var + a;
let shifted = self.to_ex().subs(var, &replacement);
let gens: Vec<&Ex> = self.gens.iter().collect();
Poly::new(&shifted, &gens).ok_or_else(|| SymplexError::ComputationFailed {
operation: "Poly::shift",
reason: "shifted expression is not polynomial in the generators".into(),
})
}
}
impl Poly {
fn check_same_gens(&self, other: &Poly, operation: &'static str) -> Result<(), SymplexError> {
if self.gens != other.gens {
return Err(invalid(operation, "polynomials have different generators"));
}
Ok(())
}
pub fn add(&self, other: &Poly) -> Result<Poly, SymplexError> {
self.check_same_gens(other, "Poly::add")?;
let mut raw = self.raw_terms();
raw.extend(other.raw_terms());
Ok(Self::from_raw(&self.ctx, self.gens.clone(), raw))
}
pub fn sub(&self, other: &Poly) -> Result<Poly, SymplexError> {
self.check_same_gens(other, "Poly::sub")?;
let mine = self.raw_terms();
let theirs = other.raw_terms();
let ids = self.ctx.with_arena_mut(|arena| {
let mut raw = mine;
for (e, c) in theirs {
raw.push((e, arena.neg(c)));
}
normalize_terms(arena, raw)
});
Ok(Self::from_normalized(&self.ctx, self.gens.clone(), ids))
}
pub fn mul(&self, other: &Poly) -> Result<Poly, SymplexError> {
self.check_same_gens(other, "Poly::mul")?;
let mine = self.raw_terms();
let theirs = other.raw_terms();
let ids: Result<Vec<(Vec<u32>, ExprId)>, SymplexError> = self.ctx.with_arena_mut(|arena| {
let mut raw: Vec<(Vec<u32>, ExprId)> = Vec::with_capacity(mine.len() * theirs.len());
for (e1, c1) in &mine {
for (e2, c2) in &theirs {
let mut e = Vec::with_capacity(e1.len());
for (a, b) in e1.iter().zip(e2) {
e.push(
a.checked_add(*b)
.ok_or_else(|| invalid("Poly::mul", "exponent overflow"))?,
);
}
raw.push((e, arena.mul(&[*c1, *c2])));
}
}
Ok(normalize_terms(arena, raw))
});
Ok(Self::from_normalized(&self.ctx, self.gens.clone(), ids?))
}
#[must_use]
pub fn neg(&self) -> Poly {
let raw = self.raw_terms();
let ids = self.ctx.with_arena_mut(|arena| {
let negated: Vec<(Vec<u32>, ExprId)> =
raw.into_iter().map(|(e, c)| (e, arena.neg(c))).collect();
normalize_terms(arena, negated)
});
Self::from_normalized(&self.ctx, self.gens.clone(), ids)
}
pub fn scale(&self, c: &Ex) -> Result<Poly, SymplexError> {
let cid = self.ctx.zero().checked_id(c);
let gens = self.gen_ids();
let raw = self.raw_terms();
let ids: Result<Vec<(Vec<u32>, ExprId)>, SymplexError> = self.ctx.with_arena_mut(|arena| {
if mentions_any(arena, cid, &gens) {
return Err(invalid("Poly::scale", "scale factor mentions a generator"));
}
let scaled: Vec<(Vec<u32>, ExprId)> = raw
.into_iter()
.map(|(e, coeff)| (e, arena.mul(&[cid, coeff])))
.collect();
Ok(normalize_terms(arena, scaled))
});
Ok(Self::from_normalized(&self.ctx, self.gens.clone(), ids?))
}
pub fn pow(&self, n: u32) -> Result<Poly, SymplexError> {
let gens: Vec<&Ex> = self.gens.iter().collect();
let mut result = Self::one(&self.ctx, &gens)?;
let mut base = self.clone();
let mut k = n;
while k > 0 {
if k & 1 == 1 {
result = result.mul(&base)?;
}
k >>= 1;
if k > 0 {
base = base.mul(&base)?;
}
}
Ok(result)
}
pub fn derivative(&self, var: &Ex) -> Result<Poly, SymplexError> {
let Some(i) = self.gens.iter().position(|g| g == var) else {
return Err(invalid(
"Poly::derivative",
format!("`{var}` is not a generator"),
));
};
let raw = self.raw_terms();
let ids = self.ctx.with_arena_mut(|arena| {
let mut out: Vec<(Vec<u32>, ExprId)> = Vec::with_capacity(raw.len());
for (mut e, c) in raw {
let k = e[i];
if k == 0 {
continue;
}
e[i] -= 1;
let k_id = arena.int(i64::from(k));
out.push((e, arena.mul(&[k_id, c])));
}
normalize_terms(arena, out)
});
Ok(Self::from_normalized(&self.ctx, self.gens.clone(), ids))
}
#[must_use]
pub fn content_and_primitive(&self) -> Option<(Ex, Poly)> {
let mp = self.to_multipoly()?;
if mp.is_zero() {
return Some((self.ctx.zero(), self.clone()));
}
let mut num = BigInt::zero();
let mut den = BigInt::one();
for (_, c) in mp.terms() {
num = num.gcd(c.numer());
den = den.lcm(c.denom());
}
let mut content = Ratio::new(num, den);
if self
.leading_coeff()
.as_rational()
.is_some_and(|lc| lc.is_negative())
{
content = -content;
}
let inv = Ratio::one() / &content;
let prim = self.scale(&self.ctx.from_ratio(inv)).ok()?;
Some((self.ctx.from_ratio(content), prim))
}
#[must_use]
pub fn monic(&self) -> Option<Poly> {
if self.is_zero() {
return None;
}
let lc = self.leading_coeff().as_rational()?;
if !self.has_rational_coeffs() {
return None;
}
let inv = Ratio::one() / lc;
self.scale(&self.ctx.from_ratio(inv)).ok()
}
}
impl Poly {
pub fn monomial_basis(polys: &[&Poly]) -> Result<Vec<Vec<u32>>, SymplexError> {
let Some(first) = polys.first() else {
return Err(invalid(
"Poly::monomial_basis",
"at least one polynomial is required",
));
};
let mut set: BTreeSet<Vec<u32>> = BTreeSet::new();
for p in polys {
first.check_same_gens(p, "Poly::monomial_basis")?;
set.extend(p.terms.keys().cloned());
}
Ok(set.into_iter().rev().collect())
}
pub fn coefficient_matrix(polys: &[&Poly], monos: &[Vec<u32>]) -> Result<Matrix, SymplexError> {
const OP: &str = "Poly::coefficient_matrix";
let Some(first) = polys.first() else {
return Err(invalid(OP, "at least one polynomial is required"));
};
if monos.is_empty() {
return Err(invalid(OP, "at least one monomial is required"));
}
for p in polys {
first.check_same_gens(p, OP)?;
}
let mut rows: Vec<Vec<Ex>> = Vec::with_capacity(monos.len());
for m in monos {
let mut row = Vec::with_capacity(polys.len());
for p in polys {
row.push(p.coeff_monomial(m)?);
}
rows.push(row);
}
Matrix::new(rows)
}
}
impl fmt::Display for Poly {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Poly(")?;
if self.terms.is_empty() {
write!(f, "0")?;
}
for (i, (exps, coeff)) in self.terms.iter().rev().enumerate() {
let monomial: Vec<String> = exps
.iter()
.zip(&self.gens)
.filter(|(e, _)| **e > 0)
.map(|(e, g)| {
if *e == 1 {
g.to_string()
} else {
format!("{g}^{e}")
}
})
.collect();
let monomial = monomial.join("*");
let (negative, magnitude) = match coeff.as_rational() {
Some(r) => {
let abs = r.abs();
let s = if abs.is_integer() {
abs.numer().to_string()
} else {
format!("{}/{}", abs.numer(), abs.denom())
};
(r.is_negative(), s)
}
None => {
let s = coeff.to_string();
if coeff.expr_type() == ExprType::Add && !monomial.is_empty() {
(false, format!("({s})"))
} else if let Some(rest) = s.strip_prefix('-') {
(true, rest.to_string())
} else {
(false, s)
}
}
};
match (i, negative) {
(0, false) => {}
(0, true) => write!(f, "-")?,
(_, false) => write!(f, " + ")?,
(_, true) => write!(f, " - ")?,
}
if monomial.is_empty() {
write!(f, "{magnitude}")?;
} else if magnitude == "1" {
write!(f, "{monomial}")?;
} else {
write!(f, "{magnitude}*{monomial}")?;
}
}
for g in &self.gens {
write!(f, ", {g}")?;
}
write!(f, ")")
}
}
impl Ex {
#[must_use]
pub fn as_poly(&self, gens: &[&Ex]) -> Option<Poly> {
Poly::new(self, gens)
}
}