use rug::ops::Pow as _;
use rug::Rational;
use std::collections::BTreeMap;
pub type Axis = usize;
pub const AXIS_N: Axis = 0;
pub type Exp = Vec<u32>;
fn exp_with_axis(mut e: Exp, axis: Axis, v: u32) -> Exp {
e[axis] = v;
e
}
fn exp_add(a: &Exp, b: &Exp) -> Exp {
debug_assert_eq!(a.len(), b.len(), "PolyM operands must share num_axes");
a.iter().zip(b.iter()).map(|(x, y)| x + y).collect()
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct PolyM {
pub terms: BTreeMap<Exp, Rational>,
}
impl PolyM {
pub fn zero() -> Self {
PolyM {
terms: BTreeMap::new(),
}
}
pub fn one(num_axes: usize) -> Self {
PolyM::constant(Rational::from(1), num_axes)
}
pub fn constant(q: Rational, num_axes: usize) -> Self {
let mut terms = BTreeMap::new();
if q != 0 {
terms.insert(vec![0u32; num_axes], q);
}
PolyM { terms }
}
pub fn from_i64(v: i64, num_axes: usize) -> Self {
PolyM::constant(Rational::from(v), num_axes)
}
pub fn var(axis: Axis, num_axes: usize) -> Self {
let e = exp_with_axis(vec![0u32; num_axes], axis, 1);
let mut terms = BTreeMap::new();
terms.insert(e, Rational::from(1));
PolyM { terms }
}
pub fn is_zero(&self) -> bool {
self.terms.is_empty()
}
pub fn is_one(&self) -> bool {
self.terms.len() == 1
&& self
.terms
.iter()
.next()
.map(|(e, c)| e.iter().all(|&x| x == 0) && *c == 1)
.unwrap_or(false)
}
pub fn as_constant(&self) -> Option<Rational> {
if self.terms.is_empty() {
return Some(Rational::from(0));
}
if self.terms.len() == 1 {
if let Some((e, c)) = self.terms.iter().next() {
if e.iter().all(|&x| x == 0) {
return Some(c.clone());
}
}
}
None
}
#[allow(dead_code)]
pub fn degree(&self, axis: Axis) -> i64 {
self.terms
.keys()
.map(|e| e[axis] as i64)
.max()
.unwrap_or(-1)
}
pub fn neg(&self) -> PolyM {
PolyM {
terms: self
.terms
.iter()
.map(|(e, c)| (e.clone(), Rational::from(-c)))
.collect(),
}
}
pub fn add(&self, other: &PolyM) -> PolyM {
let mut out = self.terms.clone();
for (e, c) in &other.terms {
let entry = out.entry(e.clone()).or_insert_with(|| Rational::from(0));
*entry += c;
}
out.retain(|_, c| *c != 0);
PolyM { terms: out }
}
pub fn sub(&self, other: &PolyM) -> PolyM {
self.add(&other.neg())
}
pub fn mul(&self, other: &PolyM) -> PolyM {
if self.is_zero() || other.is_zero() {
return PolyM::zero();
}
let mut out: BTreeMap<Exp, Rational> = BTreeMap::new();
for (ea, ca) in &self.terms {
for (eb, cb) in &other.terms {
let e = exp_add(ea, eb);
let entry = out.entry(e).or_insert_with(|| Rational::from(0));
*entry += Rational::from(ca * cb);
}
}
out.retain(|_, c| *c != 0);
PolyM { terms: out }
}
pub fn scale(&self, q: &Rational) -> PolyM {
if *q == 0 {
return PolyM::zero();
}
PolyM {
terms: self
.terms
.iter()
.map(|(e, c)| (e.clone(), Rational::from(c * q)))
.collect(),
}
}
pub fn pow_u32(&self, e: u32) -> PolyM {
let num_axes = self.terms.keys().next().map(|k| k.len());
let mut acc = match num_axes {
Some(na) => PolyM::one(na),
None => PolyM::zero(),
};
if e == 0 && num_axes.is_none() {
return PolyM::zero();
}
let mut base = self.clone();
let mut e = e;
while e > 0 {
if e & 1 == 1 {
acc = acc.mul(&base);
}
base = base.mul(&base);
e >>= 1;
}
acc
}
pub fn shift(&self, axis: Axis, delta: i64) -> PolyM {
if delta == 0 || self.is_zero() {
return self.clone();
}
let num_axes = self.terms.keys().next().unwrap().len();
let repl = PolyM::var(axis, num_axes).add(&PolyM::from_i64(delta, num_axes));
self.eliminate_axis(axis, &repl)
}
pub fn eliminate_axis(&self, axis: Axis, replacement: &PolyM) -> PolyM {
if self.is_zero() {
return PolyM::zero();
}
let mut pow_cache: BTreeMap<u32, PolyM> = BTreeMap::new();
let num_axes = self.terms.keys().next().unwrap().len();
pow_cache.insert(0, PolyM::one(num_axes));
let mut acc = PolyM::zero();
for (e, c) in &self.terms {
let ax_e = e[axis];
let rest = exp_with_axis(e.clone(), axis, 0);
let repl_pow = pow_cache
.entry(ax_e)
.or_insert_with(|| replacement.pow_u32(ax_e))
.clone();
let rest_mono = PolyM::monomial(rest, c.clone());
acc = acc.add(&rest_mono.mul(&repl_pow));
}
acc
}
fn monomial(e: Exp, c: Rational) -> PolyM {
let mut terms = BTreeMap::new();
if c != 0 {
terms.insert(e, c);
}
PolyM { terms }
}
#[allow(dead_code)]
pub fn eval(&self, vals: &[Rational]) -> Rational {
let mut acc = Rational::from(0);
for (e, c) in &self.terms {
let mut term = c.clone();
for (ax, &exp) in e.iter().enumerate() {
term *= vals[ax].clone().pow(exp);
}
acc += term;
}
acc
}
pub fn to_expr(
&self,
pool: &crate::kernel::ExprPool,
n: crate::kernel::ExprId,
indices: &[crate::kernel::ExprId],
) -> crate::kernel::ExprId {
if self.terms.is_empty() {
return pool.integer(0_i32);
}
let mut terms = Vec::with_capacity(self.terms.len());
for (e, c) in &self.terms {
let mut factors = Vec::new();
let ce = rational_to_expr(pool, c);
let is_unit = *c == 1;
let all_zero = e.iter().all(|&x| x == 0);
if !is_unit || all_zero {
factors.push(ce);
}
push_pow(pool, &mut factors, n, e[0]);
for (t, &idx_expr) in indices.iter().enumerate() {
push_pow(pool, &mut factors, idx_expr, e[t + 1]);
}
terms.push(if factors.len() == 1 {
factors[0]
} else {
pool.mul(factors)
});
}
if terms.len() == 1 {
terms[0]
} else {
pool.add(terms)
}
}
}
fn push_pow(
pool: &crate::kernel::ExprPool,
factors: &mut Vec<crate::kernel::ExprId>,
var: crate::kernel::ExprId,
e: u32,
) {
match e {
0 => {}
1 => factors.push(var),
_ => factors.push(pool.pow(var, pool.integer(e as i64))),
}
}
fn rational_to_expr(pool: &crate::kernel::ExprPool, q: &Rational) -> crate::kernel::ExprId {
let (num, den) = (q.numer().clone(), q.denom().clone());
if den == 1 {
pool.integer(num)
} else {
pool.rational(num, den)
}
}
#[derive(Clone, Debug)]
pub struct RatM {
pub num: PolyM,
pub den: PolyM,
}
impl RatM {
pub fn one(num_axes: usize) -> Self {
RatM {
num: PolyM::one(num_axes),
den: PolyM::one(num_axes),
}
}
pub fn from_poly(p: PolyM, num_axes: usize) -> Self {
RatM {
num: p,
den: PolyM::one(num_axes),
}
}
pub fn from_rational(q: Rational, num_axes: usize) -> Self {
RatM::from_poly(PolyM::constant(q, num_axes), num_axes)
}
pub fn add(&self, other: &RatM) -> RatM {
RatM {
num: self.num.mul(&other.den).add(&other.num.mul(&self.den)),
den: self.den.mul(&other.den),
}
}
pub fn neg(&self) -> RatM {
RatM {
num: self.num.neg(),
den: self.den.clone(),
}
}
pub fn sub(&self, other: &RatM) -> RatM {
self.add(&other.neg())
}
pub fn mul(&self, other: &RatM) -> RatM {
RatM {
num: self.num.mul(&other.num),
den: self.den.mul(&other.den),
}
}
pub fn inv(&self) -> Option<RatM> {
if self.num.is_zero() {
return None;
}
Some(RatM {
num: self.den.clone(),
den: self.num.clone(),
})
}
pub fn div(&self, other: &RatM) -> Option<RatM> {
Some(self.mul(&other.inv()?))
}
pub fn pow_i32(&self, e: i32) -> Option<RatM> {
if e == 0 {
let num_axes = self
.num
.terms
.keys()
.next()
.or_else(|| self.den.terms.keys().next())
.map(|k| k.len())
.unwrap_or(1);
return Some(RatM::one(num_axes));
}
let base = if e < 0 { self.inv()? } else { self.clone() };
Some(RatM {
num: base.num.pow_u32(e.unsigned_abs()),
den: base.den.pow_u32(e.unsigned_abs()),
})
}
pub fn shift(&self, axis: Axis, delta: i64) -> RatM {
RatM {
num: self.num.shift(axis, delta),
den: self.den.shift(axis, delta),
}
}
pub fn to_expr(
&self,
pool: &crate::kernel::ExprPool,
n: crate::kernel::ExprId,
indices: &[crate::kernel::ExprId],
) -> crate::kernel::ExprId {
let num = self.num.to_expr(pool, n, indices);
if self.den.is_one() {
return num;
}
let den = self.den.to_expr(pool, n, indices);
pool.mul(vec![num, pool.pow(den, pool.integer(-1_i32))])
}
pub fn eq_rat(&self, other: &RatM) -> bool {
self.num
.mul(&other.den)
.sub(&other.num.mul(&self.den))
.is_zero()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn polym_basic_arithmetic() {
let n = PolyM::var(0, 3);
let j = PolyM::var(1, 3);
let one = PolyM::one(3);
let p = n.add(&j).add(&one); let q = n.sub(&j); let prod = p.mul(&q); assert_eq!(prod.degree(0), 2);
assert_eq!(prod.degree(1), 2);
let vals = [Rational::from(3), Rational::from(2), Rational::from(0)];
assert_eq!(prod.eval(&vals), Rational::from(6));
}
#[test]
fn shift_matches_direct_eval() {
let n = PolyM::var(0, 3);
let j = PolyM::var(1, 3);
let k = PolyM::var(2, 3);
let p = n.mul(&k).add(&j);
let shifted = p.shift(2, 2);
let a = shifted.eval(&[Rational::from(2), Rational::from(3), Rational::from(5)]);
let b = p.eval(&[Rational::from(2), Rational::from(3), Rational::from(7)]);
assert_eq!(a, b);
}
#[test]
fn eliminate_axis_matches_composition() {
let j = PolyM::var(1, 3);
let k = PolyM::var(2, 3);
let n = PolyM::var(0, 3);
let p = j.mul(&j).add(&k);
let repl = n.scale(&Rational::from(2)).add(&PolyM::one(3));
let sub = p.eliminate_axis(1, &repl);
let a = sub.eval(&[Rational::from(3), Rational::from(0), Rational::from(5)]);
let b = p.eval(&[Rational::from(3), Rational::from(7), Rational::from(5)]);
assert_eq!(a, b);
}
#[test]
fn ratm_cross_mul_zero_test() {
let n = PolyM::var(0, 3);
let one = PolyM::one(3);
let a = RatM::from_poly(n.clone(), 3)
.div(&RatM::from_poly(n.add(&one), 3))
.unwrap();
let b = RatM {
num: n.mul(&PolyM::from_i64(2, 3)),
den: n.add(&one).mul(&PolyM::from_i64(2, 3)),
};
assert!(a.eq_rat(&b));
}
#[test]
fn four_axis_arithmetic_works() {
let n = PolyM::var(0, 4);
let x1 = PolyM::var(1, 4);
let x2 = PolyM::var(2, 4);
let x3 = PolyM::var(3, 4);
let p = n.add(&x1).add(&x2).add(&x3); let vals = [
Rational::from(1),
Rational::from(2),
Rational::from(3),
Rational::from(4),
];
assert_eq!(p.eval(&vals), Rational::from(10));
let shifted = p.shift(3, 5); assert_eq!(shifted.eval(&vals), Rational::from(15));
}
}