use std::collections::BTreeMap;
use num_bigint::BigInt;
use crate::differences::{DiffGraph, DiffNode};
use crate::error::Error;
use crate::graph::{Graph, Node};
use crate::parse::{Position, Predicate};
type Variable = (usize, u8);
type Monomial = Vec<(Variable, u32)>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Polynomial {
terms: BTreeMap<Monomial, BigInt>,
}
impl Polynomial {
fn zero() -> Self {
Polynomial {
terms: BTreeMap::new(),
}
}
fn constant(value: i64) -> Self {
let mut p = Polynomial::zero();
if value != 0 {
p.terms.insert(Vec::new(), BigInt::from(value));
}
p
}
fn variable(point: usize, axis: u8) -> Self {
let mut p = Polynomial::zero();
p.terms.insert(vec![((point, axis), 1)], BigInt::from(1));
p
}
fn add_scaled(&mut self, other: &Polynomial, negate: bool) -> u64 {
let mut bits = 0;
for (monomial, coefficient) in &other.terms {
let entry = self.terms.entry(monomial.clone()).or_default();
if negate {
*entry -= coefficient;
} else {
*entry += coefficient;
}
bits = bits.max(entry.bits());
if *entry == BigInt::ZERO {
self.terms.remove(monomial);
}
}
bits
}
fn multiply(&self, other: &Polynomial) -> Result<Polynomial, String> {
let mut result = Polynomial::zero();
for (m1, c1) in &self.terms {
for (m2, c2) in &other.terms {
let entry = result.terms.entry(multiply_monomials(m1, m2)?).or_default();
*entry += c1 * c2;
}
}
result.terms.retain(|_, c| *c != BigInt::ZERO);
Ok(result)
}
pub fn degree(&self) -> Option<u32> {
self.terms.keys().map(monomial_degree).max()
}
pub fn is_homogeneous(&self) -> bool {
let mut degrees = self.terms.keys().map(monomial_degree);
match degrees.next() {
None => true,
Some(first) => degrees.all(|d| d == first),
}
}
pub fn terms(&self) -> usize {
self.terms.len()
}
pub fn is_zero(&self) -> bool {
self.terms.is_empty()
}
}
fn monomial_degree(monomial: &Monomial) -> u32 {
monomial.iter().map(|&(_, e)| e).sum()
}
fn multiply_monomials(a: &Monomial, b: &Monomial) -> Result<Monomial, String> {
let degree = u64::from(monomial_degree(a)) + u64::from(monomial_degree(b));
if degree > u64::from(u32::MAX) {
return Err(format!(
"the expression is too large to expand: a term would have a degree above {}",
u32::MAX
));
}
let mut out = Vec::with_capacity(a.len() + b.len());
let (mut i, mut j) = (0, 0);
while i < a.len() && j < b.len() {
match a[i].0.cmp(&b[j].0) {
std::cmp::Ordering::Less => {
out.push(a[i]);
i += 1;
}
std::cmp::Ordering::Greater => {
out.push(b[j]);
j += 1;
}
std::cmp::Ordering::Equal => {
out.push((a[i].0, a[i].1 + b[j].1));
i += 1;
j += 1;
}
}
}
out.extend_from_slice(&a[i..]);
out.extend_from_slice(&b[j..]);
Ok(out)
}
pub(crate) const MAX_TERMS: usize = 1 << 16;
pub(crate) const MAX_WORK: u64 = 1 << 20;
pub(crate) const MAX_COEFFICIENT_BITS: u64 = 1 << 16;
#[derive(Clone)]
struct Value {
polynomial: Polynomial,
bits: u64,
}
struct Budget {
work: u64,
}
impl Budget {
fn spend(&mut self, pairs: u64, bits: u64) -> Result<(), String> {
if bits > MAX_COEFFICIENT_BITS {
return Err(format!(
"the expression is too large to expand: a coefficient would have \
more than {MAX_COEFFICIENT_BITS} bits"
));
}
let cost = pairs.saturating_mul(1 + bits / 64);
self.work = self.work.saturating_add(cost);
if self.work > MAX_WORK {
return Err(format!(
"the expression is too large to expand: expanding it would take \
more than {MAX_WORK} operations on its terms"
));
}
Ok(())
}
}
fn coefficient_bits(p: &Polynomial) -> u64 {
p.terms.values().map(BigInt::bits).max().unwrap_or(0)
}
pub fn canonical(graph: &Graph, file: &str, position: Position) -> Result<Polynomial, Error> {
expand(graph).map_err(|message| Error::at(file, position, message))
}
fn expand(graph: &Graph) -> Result<Polynomial, String> {
let mut uses = vec![0usize; graph.nodes.len()];
uses[graph.root] += 1;
for node in &graph.nodes {
if let Node::Add(x, y) | Node::Sub(x, y) | Node::Mul(x, y) = *node {
uses[x] += 1;
uses[y] += 1;
}
}
let mut budget = Budget { work: 0 };
let mut values: Vec<Option<Value>> = vec![None; graph.nodes.len()];
let mut take = |values: &mut Vec<Option<Value>>, budget: &mut Budget, id: usize| {
uses[id] -= 1;
if uses[id] == 0 {
Ok(values[id].take().expect("children come before parents"))
} else {
let value = values[id].as_ref().expect("children come before parents");
budget.spend(value.polynomial.terms() as u64, value.bits)?;
Ok::<Value, String>(value.clone())
}
};
for (id, node) in graph.nodes.iter().enumerate() {
let value = match *node {
Node::Coord { point, axis } => Value {
polynomial: Polynomial::variable(point, axis),
bits: 1,
},
Node::Int(n) => {
let polynomial = Polynomial::constant(n);
let bits = coefficient_bits(&polynomial);
Value { polynomial, bits }
}
Node::Add(x, y) | Node::Sub(x, y) => {
let mut left = take(&mut values, &mut budget, x)?;
let right = take(&mut values, &mut budget, y)?;
budget.spend(
right.polynomial.terms() as u64,
left.bits.max(right.bits) + 1,
)?;
let changed = left
.polynomial
.add_scaled(&right.polynomial, matches!(node, Node::Sub(..)));
left.bits = left.bits.max(changed);
left
}
Node::Mul(x, y) => {
let left = take(&mut values, &mut budget, x)?;
let right = take(&mut values, &mut budget, y)?;
let (l, r) = (
left.polynomial.terms() as u64,
right.polynomial.terms() as u64,
);
let bits = left.bits + right.bits + u64::from(64 - l.min(r).leading_zeros());
budget.spend(l.saturating_mul(r), bits)?;
let polynomial = left.polynomial.multiply(&right.polynomial)?;
let bits = coefficient_bits(&polynomial);
Value { polynomial, bits }
}
};
if value.polynomial.terms() > MAX_TERMS {
return Err(format!(
"the expression is too large to expand: a subexpression has more \
than {MAX_TERMS} terms"
));
}
values[id] = Some(value);
}
Ok(take(&mut values, &mut budget, graph.root)?.polynomial)
}
pub fn check_same_polynomial(predicate: &Predicate, file: &str) -> Result<(), Error> {
let main = canonical(&predicate.main, file, predicate.position)?;
if main.is_zero() {
return Err(Error::at(
file,
predicate.position,
format!(
"the polynomial of `{}` is identically zero, so its sign is always zero",
predicate.name
),
));
}
if let Some(exact_graph) = &predicate.exact {
let position = predicate.exact_position.unwrap_or(predicate.position);
let exact = canonical(exact_graph, file, position)?;
if exact != main {
return Err(Error::at(
file,
position,
format!(
"the exact form of `{}` differs from its main form: \
they are not the same polynomial (main form has {} terms, exact form has {})",
predicate.name,
main.terms(),
exact.terms()
),
));
}
}
Ok(())
}
pub fn canonical_of_differences(
graph: &DiffGraph,
file: &str,
position: Position,
) -> Result<Polynomial, Error> {
let mut nodes = vec![Node::Int(0)];
let mut ids = Vec::with_capacity(graph.nodes.len());
for node in &graph.nodes {
let id = match *node {
DiffNode::Leaf {
axis,
minuend,
subtrahend,
} => {
nodes.push(Node::Coord {
point: minuend,
axis,
});
nodes.push(Node::Coord {
point: subtrahend,
axis,
});
nodes.push(Node::Sub(nodes.len() - 2, nodes.len() - 1));
nodes.len() - 1
}
DiffNode::Neg(x) => {
nodes.push(Node::Sub(0, ids[x]));
nodes.len() - 1
}
DiffNode::Add(x, y) => {
nodes.push(Node::Add(ids[x], ids[y]));
nodes.len() - 1
}
DiffNode::Sub(x, y) => {
nodes.push(Node::Sub(ids[x], ids[y]));
nodes.len() - 1
}
DiffNode::Mul(x, y) => {
nodes.push(Node::Mul(ids[x], ids[y]));
nodes.len() - 1
}
};
ids.push(id);
}
let graph = Graph {
root: ids[graph.root],
nodes,
};
canonical(&graph, file, position)
}