Compare commits

...

2 Commits

Author SHA1 Message Date
345bc0f126 add a few tests for monomials and polynomials ops 2026-04-22 10:39:10 +02:00
09a9613870 add contains function for monomials 2026-04-22 10:22:32 +02:00
5 changed files with 188 additions and 10 deletions

View File

@@ -7,9 +7,20 @@ fn main() {
+ (3 * ((var!("x", 1, 9) ^ 5) * (var!("x", 1, 2) ^ 5) * (var!("x", 2, 5) ^ 1))); + (3 * ((var!("x", 1, 9) ^ 5) * (var!("x", 1, 2) ^ 5) * (var!("x", 2, 5) ^ 1)));
let x = var!("x"); let x = var!("x");
let y = var!("y"); let y = var!("x\u{0304}");
let z = var!("z");
let other = -3 * ((&x ^ 2) * (&y ^ 4)); let other = -3 * ((&x ^ 2) * (&y ^ 4));
let mono = (&x^2)*(&y^4);
let inside = (&x^2)*(&y^2)*(&z^1);
if mono.contains(&inside){
println!("{inside}\u{2286}{mono}");
}else{
println!("{inside}\u{2284}{mono}");
}
println!("{poly}"); println!("{poly}");
let z = poly - other; let z = poly - other;

34
src/fmt.rs Normal file
View File

@@ -0,0 +1,34 @@
pub fn num_to_subscript(s:String)->String{
s.chars().map(|c| match c{
'0'=>'\u{2080}',
'1'=>'\u{2081}',
'2'=>'\u{2082}',
'3'=>'\u{2083}',
'4'=>'\u{2084}',
'5'=>'\u{2085}',
'6'=>'\u{2086}',
'7'=>'\u{2087}',
'8'=>'\u{2088}',
'9'=>'\u{2089}',
_=>c
}).collect()
}
pub fn num_to_superscript(s:String)->String{
s.chars().map(|c| match c{
'0'=>'\u{2070}',
'1'=>'\u{20B9}',
'2'=>'\u{00B2}',
'3'=>'\u{00B3}',
'4'=>'\u{2074}',
'5'=>'\u{2075}',
'6'=>'\u{2076}',
'7'=>'\u{2077}',
'8'=>'\u{2078}',
'9'=>'\u{2079}',
_=>c
}).collect()
}

View File

@@ -1 +1,2 @@
pub mod poly; pub mod poly;
pub mod fmt;

View File

@@ -1,12 +1,13 @@
use itertools::Itertools; use itertools::Itertools;
use std::fmt::{self, Display}; use std::fmt::{self, Display};
use std::ops::{Add, Sub, BitXor, Mul}; use std::ops::{Add, BitXor, Mul, Sub};
use super::var::{StaticVar, Var}; use super::var::{StaticVar, Var};
use crate::fmt::num_to_superscript;
use std::collections::HashMap; use std::collections::HashMap;
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq)]
pub struct Poly<V: Var> { pub struct Poly<V: Var> {
mono: HashMap<Mono<V>, i32>, mono: HashMap<Mono<V>, i32>,
} }
@@ -57,6 +58,33 @@ pub struct Mono<V: Var> {
pub term: Vec<(V, u32)>, pub term: Vec<(V, u32)>,
} }
impl<V: Var> Mono<V> {
pub fn contains(&self, other: &Mono<V>) -> bool {
let mut self_it = self.term.iter().peekable();
let mut other_it = other.term.iter().peekable();
while let Some((o_term, o_exp)) = other_it.peek() {
if let Some((s_term, s_exp)) = self_it.peek() {
if s_term < o_term {
self_it.next();
continue;
} else if s_term > o_term {
return false;
} else if o_exp <= s_exp {
self_it.next();
other_it.next();
} else {
return false;
}
} else {
return false;
}
}
true
}
}
impl<V: Var, T: IntoIterator> From<T> for Mono<V> impl<V: Var, T: IntoIterator> From<T> for Mono<V>
where where
Mono<V>: FromIterator<<T as IntoIterator>::Item>, Mono<V>: FromIterator<<T as IntoIterator>::Item>,
@@ -75,11 +103,9 @@ impl<V: Var, U: Into<V>> FromIterator<(U, u32)> for Mono<V> {
term.sort(); term.sort();
// Check duplicate variables // Check duplicate variables
assert!( assert!((term[..])
(term[..])
.windows(2) .windows(2)
.all(|window| window[0].0 != window[1].0) .all(|window| window[0].0 != window[1].0));
);
Mono { term } Mono { term }
} }
@@ -94,7 +120,7 @@ impl<V: Var> Display for Mono<V> {
.iter() .iter()
.map(|(t, p)| match p { .map(|(t, p)| match p {
1 => format!("{t}"), 1 => format!("{t}"),
_ => format!("{t}^{p}"), _ => format!("{t}{}", num_to_superscript(p.to_string())),
}) })
.join("") .join("")
) )
@@ -194,3 +220,107 @@ impl<V: Var> Sub for Poly<V> {
self self
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mono_contains() {
let a: Mono<StaticVar> = [("x", 2), ("y", 1)].into();
// Lower exponent of same variable is contained
assert!(a.contains(&Mono::from([("x", 1)])));
// Higher exponent of same variable is not contained
assert!(!a.contains(&Mono::from([("x", 3)])));
// Identical monomial is contained
assert!(a.contains(&Mono::from([("x", 2), ("y", 1)])));
// Variable absent from self is not contained
assert!(!a.contains(&Mono::from([("x", 2), ("z", 1)])));
// Subset of variables with lower exponents is contained
assert!(a.contains(&Mono::from([("x", 1), ("y", 1)])));
// Single variable with exact exponent is contained
assert!(a.contains(&Mono::from([("x", 2)])));
// Insufficient exponent in self means not contained
assert!(!Mono::<StaticVar>::from([("x", 1)]).contains(&Mono::from([("x", 2)])));
// Missing variable in self means not contained
assert!(!Mono::<StaticVar>::from([("x", 1), ("y", 1)]).contains(&Mono::from([("x", 2)])));
assert!(!Mono::<StaticVar>::from([("x", 1)]).contains(&Mono::from([("x", 1), ("y", 1)])));
}
#[test]
fn test_mono_mul() {
// Same variable: exponents add
let a: Mono<StaticVar> = [("x", 2)].into();
let b: Mono<StaticVar> = [("x", 3)].into();
assert_eq!(a * b, Mono::from([("x", 5)]));
// Disjoint variables: both appear in result
let a: Mono<StaticVar> = [("x", 2)].into();
let b: Mono<StaticVar> = [("y", 3)].into();
assert_eq!(a * b, Mono::from([("x", 2), ("y", 3)]));
// Mixed: shared and disjoint variables
let a: Mono<StaticVar> = [("x", 1), ("y", 2)].into();
let b: Mono<StaticVar> = [("y", 1), ("z", 3)].into();
assert_eq!(a * b, Mono::from([("x", 1), ("y", 3), ("z", 3)]));
// Commutativity
let a: Mono<StaticVar> = [("x", 2), ("z", 1)].into();
let b: Mono<StaticVar> = [("y", 3)].into();
assert_eq!(a.clone() * b.clone(), b * a);
// Multiply by constant monomial (empty term vec = 1)
let a: Mono<StaticVar> = [("x", 4)].into();
let one: Mono<StaticVar> = Mono { term: vec![] };
assert_eq!(a.clone() * one, a);
}
#[test]
fn test_poly_add() {
// Distinct monomials are collected as separate terms
let a: Poly<StaticVar> = [(1, [("x", 2)]), (2, [("y", 1)])].into();
let b: Poly<StaticVar> = [(3, [("z", 1)])].into();
let expected: Poly<StaticVar> = [(1, [("x", 2)]), (2, [("y", 1)]), (3, [("z", 1)])].into();
assert_eq!(a + b, expected);
// Coefficients of matching monomials are summed
let a: Poly<StaticVar> = [(2, [("x", 1)])].into();
let b: Poly<StaticVar> = [(3, [("x", 1)])].into();
let expected: Poly<StaticVar> = [(5, [("x", 1)])].into();
assert_eq!(a + b, expected);
// Terms that cancel sum to zero are dropped
let a: Poly<StaticVar> = [(1, [("x", 1)])].into();
let b: Poly<StaticVar> = [(-1, [("x", 1)])].into();
let expected: Poly<StaticVar> = Poly::default();
assert_eq!(a + b, expected);
}
#[test]
fn test_poly_sub() {
// Distinct monomials are collected as separate terms with negated rhs coefficients
let a: Poly<StaticVar> = [(3, [("x", 2)])].into();
let b: Poly<StaticVar> = [(1, [("y", 1)])].into();
let expected: Poly<StaticVar> = [(3, [("x", 2)]), (-1, [("y", 1)])].into();
assert_eq!(a - b, expected);
// Coefficients of matching monomials are subtracted
let a: Poly<StaticVar> = [(5, [("x", 1)])].into();
let b: Poly<StaticVar> = [(3, [("x", 1)])].into();
let expected: Poly<StaticVar> = [(2, [("x", 1)])].into();
assert_eq!(a - b, expected);
// Subtracting equal polynomials yields zero
let a: Poly<StaticVar> = [(4, [("x", 2)]), (1, [("y", 1)])].into();
let b: Poly<StaticVar> = [(4, [("x", 2)]), (1, [("y", 1)])].into();
assert_eq!(a - b, Poly::default());
}
}

View File

@@ -2,6 +2,8 @@ use itertools::Itertools;
use std::fmt::{self, Debug, Display, Formatter}; use std::fmt::{self, Debug, Display, Formatter};
use std::hash::Hash; use std::hash::Hash;
use crate::fmt::num_to_subscript;
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct StaticVar { pub struct StaticVar {
name: &'static str, name: &'static str,
@@ -15,7 +17,7 @@ impl Display for StaticVar {
let num_indices = self.indices.len(); let num_indices = self.indices.len();
match num_indices { match num_indices {
0 => write!(fmt, "{}", self.name), 0 => write!(fmt, "{}", self.name),
_ => write!(fmt, "{}_{{{}}}", self.name, self.indices.iter().join(",")), _ => write!(fmt, "{}{}", self.name, self.indices.iter().map(|x| num_to_subscript(x.to_string())).join(",")),
} }
} }
} }