#![forbid(unsafe_code)]
use std::collections::{BTreeMap, btree_map::Entry};
use num_rational::BigRational;
use num_traits::{One, Zero};
use crate::rational::canonicalize_rational;
use crate::{LaError, NonFiniteLocation, NonFiniteOrigin, PolynomialWork};
pub const MAX_POLYNOMIAL_MATRIX_DIM: usize = 8;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[must_use]
pub struct SparsePolynomial {
terms: BTreeMap<u128, BigRational>,
}
impl SparsePolynomial {
pub const fn zero() -> Self {
Self {
terms: BTreeMap::new(),
}
}
pub fn try_from_terms(
terms: impl IntoIterator<Item = (u128, BigRational)>,
) -> Result<Self, LaError> {
let mut polynomial = Self::zero();
for (index, (exponent, coefficient)) in terms.into_iter().enumerate() {
if coefficient.denom().is_zero() {
return Err(LaError::NonFinite {
location: NonFiniteLocation::PolynomialTerm { index, exponent },
origin: NonFiniteOrigin::Input,
});
}
polynomial.add_term(exponent, canonicalize_rational(coefficient));
}
Ok(polynomial)
}
#[must_use]
pub const fn terms(&self) -> &BTreeMap<u128, BigRational> {
&self.terms
}
#[must_use]
pub fn is_zero(&self) -> bool {
self.terms.is_empty()
}
#[must_use]
pub fn leading_term(&self) -> Option<(u128, &BigRational)> {
self.terms
.first_key_value()
.map(|(&exponent, coefficient)| (exponent, coefficient))
}
fn add_term(&mut self, exponent: u128, coefficient: BigRational) {
if coefficient.is_zero() {
return;
}
match self.terms.entry(exponent) {
Entry::Vacant(entry) => {
entry.insert(coefficient);
}
Entry::Occupied(mut entry) => {
*entry.get_mut() += coefficient;
if entry.get().is_zero() {
entry.remove();
}
}
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[must_use]
pub struct PolynomialDeterminantBudget {
pub max_subset_transitions: usize,
pub max_term_products: usize,
}
pub fn polynomial_determinant<const D: usize>(
matrix: &[[SparsePolynomial; D]; D],
budget: PolynomialDeterminantBudget,
) -> Result<SparsePolynomial, LaError> {
if D > MAX_POLYNOMIAL_MATRIX_DIM {
return Err(LaError::unsupported_dimension(D, MAX_POLYNOMIAL_MATRIX_DIM));
}
let state_count = 1usize << D;
let transitions = D * (state_count / 2);
if transitions > budget.max_subset_transitions {
return Err(LaError::PolynomialBudgetExceeded {
work: PolynomialWork::SubsetTransitions,
limit: budget.max_subset_transitions,
completed: 0,
requested: transitions,
});
}
let mut partials = vec![SparsePolynomial::zero(); state_count];
partials[0].add_term(0, BigRational::one());
let mut products = 0;
for mask in 0..state_count - 1 {
let row = mask.count_ones() as usize;
let partial = std::mem::take(&mut partials[mask]);
if partial.is_zero() {
continue;
}
for (col, entry) in matrix[row].iter().enumerate() {
if mask & (1 << col) != 0 {
continue;
}
let negative = (mask >> (col + 1)).count_ones() % 2 != 0;
let next = &mut partials[mask | (1 << col)];
for (&left, left_coefficient) in &partial.terms {
for (&right, right_coefficient) in &entry.terms {
if products == budget.max_term_products {
return Err(LaError::PolynomialBudgetExceeded {
work: PolynomialWork::TermProducts,
limit: budget.max_term_products,
completed: products,
requested: 1,
});
}
products += 1;
let exponent =
left.checked_add(right)
.ok_or(LaError::PolynomialExponentOverflow {
row,
col,
left,
right,
})?;
let coefficient = left_coefficient * right_coefficient;
next.add_term(exponent, if negative { -coefficient } else { coefficient });
}
}
}
}
Ok(std::mem::take(&mut partials[state_count - 1]))
}