mod add;
mod approx;
mod arbitrary;
mod binary_ids;
mod convert;
mod degree;
mod evaluate;
mod linear;
mod mul;
mod parse;
mod polynomial;
mod quadratic;
mod serialize;
mod substitute;
pub use binary_ids::*;
pub use degree::*;
pub use linear::*;
pub use parse::*;
pub use polynomial::*;
pub use quadratic::*;
use crate::{coeff, v1::State, Coefficient, VariableID, VariableIDSet};
use anyhow::{Context, Result};
use fnv::{FnvHashMap, FnvHashSet};
use num::{
integer::{gcd, lcm},
One,
};
use proptest::strategy::BoxedStrategy;
use std::{fmt::Debug, hash::Hash};
pub trait Monomial: Into<MonomialDyn> + Debug + Clone + Hash + Eq + Default + 'static {
type Parameters: Default;
fn degree(&self) -> Degree;
fn max_degree() -> Degree;
fn as_linear(&self) -> Option<VariableID>;
fn as_quadratic(&self) -> Option<VariableIDPair>;
fn reduce_binary_power(&mut self, binary_ids: &VariableIDSet) -> bool;
fn ids(&self) -> Box<dyn Iterator<Item = VariableID> + '_>;
fn from_ids(ids: impl Iterator<Item = VariableID>) -> Option<Self>;
fn partial_evaluate(self, state: &State) -> (Self, f64);
fn arbitrary_uniques(parameters: Self::Parameters) -> BoxedStrategy<FnvHashSet<Self>>;
}
#[derive(Clone, PartialEq, Eq)]
pub struct PolynomialBase<M: Monomial> {
terms: FnvHashMap<M, Coefficient>,
}
impl<M: Monomial> Default for PolynomialBase<M> {
fn default() -> Self {
Self {
terms: Default::default(),
}
}
}
impl<M: Monomial> From<M> for PolynomialBase<M> {
fn from(monomial: M) -> Self {
let mut terms = FnvHashMap::default();
terms.insert(monomial, Coefficient::one());
Self { terms }
}
}
impl<M: Monomial> PolynomialBase<M> {
pub fn one() -> Self {
let mut terms = FnvHashMap::default();
terms.insert(M::default(), coeff!(1.0));
Self { terms }
}
pub fn add_term(&mut self, term: M, coefficient: Coefficient) {
use std::collections::hash_map::Entry;
match self.terms.entry(term) {
Entry::Vacant(e) => {
e.insert(coefficient);
}
Entry::Occupied(mut e) => {
if let Some(added) = *e.get() + coefficient {
e.insert(added);
} else {
e.remove();
}
}
}
}
pub fn num_terms(&self) -> usize {
self.terms.len()
}
pub fn degree(&self) -> Degree {
let max_degree = M::max_degree();
let mut current: Degree = 0.into();
for term in self.terms.keys() {
current = current.max(term.degree());
if current == max_degree {
break;
}
}
current
}
pub fn contains(&self, term: &M) -> bool {
self.terms.contains_key(term)
}
pub fn get(&self, term: &M) -> Option<Coefficient> {
self.terms.get(term).copied()
}
pub fn iter(&self) -> impl Iterator<Item = (&M, &Coefficient)> {
self.terms.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&M, &mut Coefficient)> {
self.terms.iter_mut()
}
pub fn values(&self) -> impl Iterator<Item = &Coefficient> {
self.terms.values()
}
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut Coefficient> {
self.terms.values_mut()
}
pub fn keys(&self) -> impl Iterator<Item = &M> {
self.terms.keys()
}
pub fn constant_term(&self) -> f64 {
self.get(&M::default())
.map(|c| c.into_inner())
.unwrap_or(0.0)
}
pub fn terms_by_degree(&self, degree: Degree) -> impl Iterator<Item = (&M, &Coefficient)> {
self.iter()
.filter(move |(monomial, _)| monomial.degree() == degree)
}
pub fn linear_terms(&self) -> impl Iterator<Item = (VariableID, Coefficient)> + '_ {
self.iter()
.filter_map(|(monomial, coeff)| monomial.as_linear().map(|id| (id, *coeff)))
}
pub fn quadratic_terms(&self) -> impl Iterator<Item = (VariableIDPair, Coefficient)> + '_ {
self.iter()
.filter_map(|(monomial, coeff)| monomial.as_quadratic().map(|pair| (pair, *coeff)))
}
pub fn reduce_binary_power(&mut self, binary_ids: &VariableIDSet) -> bool {
let mut reduced = false;
let mut new = Self::default();
for (monomial, coefficient) in &self.terms {
let mut m = monomial.clone();
reduced |= m.reduce_binary_power(binary_ids);
new.add_term(m, *coefficient);
}
*self = new;
reduced
}
pub fn max_coefficient_abs(&self) -> Option<Coefficient> {
self.terms
.values()
.map(|coefficient| coefficient.abs())
.max()
}
pub fn content_factor(&self) -> Result<Coefficient> {
let mut numer_gcd = 0;
let mut denom_lcm: i64 = 1;
for coefficient in self.terms.values() {
let r = num::Rational64::approximate_float(coefficient.into_inner())
.context("Cannot approximate coefficient in 64-bit rational")?;
numer_gcd = gcd(numer_gcd, *r.numer());
denom_lcm
.checked_mul(*r.denom())
.context("Overflow detected while evaluating minimal integer coefficient multiplier. This means it is hard to make the all coefficient integer")?;
denom_lcm = lcm(denom_lcm, *r.denom());
}
if numer_gcd == 0 {
Ok(Coefficient::one())
} else {
let result = (denom_lcm as f64 / numer_gcd as f64).abs();
Coefficient::try_from(result).context("Content factor should be non-zero")
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::random::random_deterministic;
#[test]
fn test_deterministic() {
let p: Linear = random_deterministic(LinearParameters::new(3, 10.into()).unwrap());
insta::assert_debug_snapshot!(p.iter().collect::<Vec<_>>(), @r###"
[
(
Variable(
VariableID(8),
),
Coefficient(-1),
),
(
Variable(
VariableID(7),
),
Coefficient(-0.27550031881072173),
),
(
Variable(
VariableID(10),
),
Coefficient(4.520657493715473),
),
]
"###);
}
#[test]
fn test_polynomial_base_reduce_binary_power() {
use crate::{linear, quadratic};
use ::approx::assert_abs_diff_eq;
let original_linear = coeff!(2.0) * linear!(1) + coeff!(3.0) * linear!(2) + coeff!(1.0);
let mut linear_poly = original_linear.clone();
let mut binary_ids = crate::variable_ids!(1);
let changed = linear_poly.reduce_binary_power(&binary_ids);
assert!(!changed); assert_abs_diff_eq!(linear_poly, original_linear);
let mut quad_poly = quadratic!(1, 1)
+ coeff!(2.0) * quadratic!(1, 2)
+ coeff!(3.0) * quadratic!(2, 2)
+ coeff!(4.0);
let expected = quadratic!(1)
+ coeff!(2.0) * quadratic!(1, 2)
+ coeff!(3.0) * quadratic!(2, 2)
+ coeff!(4.0);
let changed2 = quad_poly.reduce_binary_power(&binary_ids);
assert!(changed2);
assert_abs_diff_eq!(quad_poly, expected);
binary_ids.extend(crate::variable_ids!(2, 3));
let mut quad_poly2 = coeff!(5.0) * quadratic!(1, 1)
+ coeff!(6.0) * quadratic!(2, 2)
+ coeff!(7.0) * quadratic!(3, 3)
+ coeff!(8.0) * quadratic!(1, 2);
let expected2 = coeff!(5.0) * quadratic!(1) + coeff!(6.0) * quadratic!(2) + coeff!(7.0) * quadratic!(3) + coeff!(8.0) * quadratic!(1, 2);
let changed3 = quad_poly2.reduce_binary_power(&binary_ids);
assert!(changed3);
assert_abs_diff_eq!(quad_poly2, expected2);
let original_quad3 = quadratic!(4, 4) + coeff!(2.0) * quadratic!(5, 5);
let mut quad_poly3 = original_quad3.clone();
let changed4 = quad_poly3.reduce_binary_power(&binary_ids);
assert!(!changed4); assert_abs_diff_eq!(quad_poly3, original_quad3);
let mut empty_poly = Quadratic::default();
let expected_empty = Quadratic::default();
let changed5 = empty_poly.reduce_binary_power(&binary_ids);
assert!(!changed5); assert_abs_diff_eq!(empty_poly, expected_empty);
}
#[test]
fn test_content_factor() {
use crate::linear;
use ::approx::assert_abs_diff_eq;
let p = coeff!(0.5) * linear!(1) + Coefficient::try_from(1.0 / 3.0).unwrap() * linear!(2);
let factor = p.content_factor().unwrap();
assert_abs_diff_eq!(factor.into_inner(), 6.0);
let p = coeff!(2.0) * linear!(1) + coeff!(3.0) * linear!(2);
let factor = p.content_factor().unwrap();
assert_eq!(factor, Coefficient::one());
let p: Linear = PolynomialBase::default();
let factor = p.content_factor().unwrap();
assert_eq!(factor, Coefficient::one());
let p = Coefficient::try_from(2.0 / 3.0).unwrap() * linear!(1)
+ Coefficient::try_from(2.0 / 5.0).unwrap() * linear!(2);
let factor = p.content_factor().unwrap();
assert_abs_diff_eq!(factor.into_inner(), 15.0 / 2.0);
use std::f64::consts::PI;
let p = Coefficient::try_from(PI).unwrap() * linear!(1)
+ Coefficient::try_from(2.0 * PI).unwrap() * linear!(2);
let factor = p.content_factor().unwrap();
assert_abs_diff_eq!(factor.into_inner(), 1.0 / PI);
}
}