problemreductions/
polynomial.rs

1//! Polynomial representation for reduction overhead.
2
3use crate::types::ProblemSize;
4use std::ops::Add;
5
6/// A monomial: coefficient × Π(variable^exponent)
7#[derive(Clone, Debug, PartialEq)]
8pub struct Monomial {
9    pub coefficient: f64,
10    pub variables: Vec<(&'static str, u8)>,
11}
12
13impl Monomial {
14    pub fn constant(c: f64) -> Self {
15        Self { coefficient: c, variables: vec![] }
16    }
17
18    pub fn var(name: &'static str) -> Self {
19        Self { coefficient: 1.0, variables: vec![(name, 1)] }
20    }
21
22    pub fn var_pow(name: &'static str, exp: u8) -> Self {
23        Self { coefficient: 1.0, variables: vec![(name, exp)] }
24    }
25
26    pub fn scale(mut self, c: f64) -> Self {
27        self.coefficient *= c;
28        self
29    }
30
31    pub fn evaluate(&self, size: &ProblemSize) -> f64 {
32        let var_product: f64 = self.variables.iter()
33            .map(|(name, exp)| {
34                let val = size.get(name).unwrap_or(0) as f64;
35                val.powi(*exp as i32)
36            })
37            .product();
38        self.coefficient * var_product
39    }
40}
41
42/// A polynomial: Σ monomials
43#[derive(Clone, Debug, PartialEq)]
44pub struct Polynomial {
45    pub terms: Vec<Monomial>,
46}
47
48impl Polynomial {
49    pub fn zero() -> Self {
50        Self { terms: vec![] }
51    }
52
53    pub fn constant(c: f64) -> Self {
54        Self { terms: vec![Monomial::constant(c)] }
55    }
56
57    pub fn var(name: &'static str) -> Self {
58        Self { terms: vec![Monomial::var(name)] }
59    }
60
61    pub fn var_pow(name: &'static str, exp: u8) -> Self {
62        Self { terms: vec![Monomial::var_pow(name, exp)] }
63    }
64
65    pub fn scale(mut self, c: f64) -> Self {
66        for term in &mut self.terms {
67            term.coefficient *= c;
68        }
69        self
70    }
71
72    pub fn evaluate(&self, size: &ProblemSize) -> f64 {
73        self.terms.iter().map(|m| m.evaluate(size)).sum()
74    }
75}
76
77impl Add for Polynomial {
78    type Output = Self;
79
80    fn add(mut self, other: Self) -> Self {
81        self.terms.extend(other.terms);
82        self
83    }
84}
85
86/// Convenience macro for building polynomials.
87#[macro_export]
88macro_rules! poly {
89    // Single variable: poly!(n)
90    ($name:ident) => {
91        $crate::polynomial::Polynomial::var(stringify!($name))
92    };
93    // Variable with exponent: poly!(n^2)
94    ($name:ident ^ $exp:literal) => {
95        $crate::polynomial::Polynomial::var_pow(stringify!($name), $exp)
96    };
97    // Constant: poly!(5)
98    ($c:literal) => {
99        $crate::polynomial::Polynomial::constant($c as f64)
100    };
101    // Scaled variable: poly!(3 * n)
102    ($c:literal * $name:ident) => {
103        $crate::polynomial::Polynomial::var(stringify!($name)).scale($c as f64)
104    };
105    // Scaled variable with exponent: poly!(9 * n^2)
106    ($c:literal * $name:ident ^ $exp:literal) => {
107        $crate::polynomial::Polynomial::var_pow(stringify!($name), $exp).scale($c as f64)
108    };
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn test_monomial_constant() {
117        let m = Monomial::constant(5.0);
118        let size = ProblemSize::new(vec![("n", 10)]);
119        assert_eq!(m.evaluate(&size), 5.0);
120    }
121
122    #[test]
123    fn test_monomial_variable() {
124        let m = Monomial::var("n");
125        let size = ProblemSize::new(vec![("n", 10)]);
126        assert_eq!(m.evaluate(&size), 10.0);
127    }
128
129    #[test]
130    fn test_monomial_var_pow() {
131        let m = Monomial::var_pow("n", 2);
132        let size = ProblemSize::new(vec![("n", 5)]);
133        assert_eq!(m.evaluate(&size), 25.0);
134    }
135
136    #[test]
137    fn test_polynomial_add() {
138        // 3n + 2m
139        let p = Polynomial::var("n").scale(3.0)
140            + Polynomial::var("m").scale(2.0);
141
142        let size = ProblemSize::new(vec![("n", 10), ("m", 5)]);
143        assert_eq!(p.evaluate(&size), 40.0);  // 3*10 + 2*5
144    }
145
146    #[test]
147    fn test_polynomial_complex() {
148        // n^2 + 3m
149        let p = Polynomial::var_pow("n", 2)
150            + Polynomial::var("m").scale(3.0);
151
152        let size = ProblemSize::new(vec![("n", 4), ("m", 2)]);
153        assert_eq!(p.evaluate(&size), 22.0);  // 16 + 6
154    }
155
156    #[test]
157    fn test_poly_macro() {
158        let size = ProblemSize::new(vec![("n", 5), ("m", 3)]);
159
160        assert_eq!(poly!(n).evaluate(&size), 5.0);
161        assert_eq!(poly!(n^2).evaluate(&size), 25.0);
162        assert_eq!(poly!(3 * n).evaluate(&size), 15.0);
163        assert_eq!(poly!(2 * m^2).evaluate(&size), 18.0);
164    }
165
166    #[test]
167    fn test_missing_variable() {
168        let p = Polynomial::var("missing");
169        let size = ProblemSize::new(vec![("n", 10)]);
170        assert_eq!(p.evaluate(&size), 0.0); // missing var = 0
171    }
172
173    #[test]
174    fn test_polynomial_zero() {
175        let p = Polynomial::zero();
176        let size = ProblemSize::new(vec![("n", 100)]);
177        assert_eq!(p.evaluate(&size), 0.0);
178    }
179
180    #[test]
181    fn test_polynomial_constant() {
182        let p = Polynomial::constant(42.0);
183        let size = ProblemSize::new(vec![("n", 100)]);
184        assert_eq!(p.evaluate(&size), 42.0);
185    }
186
187    #[test]
188    fn test_monomial_scale() {
189        let m = Monomial::var("n").scale(3.0);
190        let size = ProblemSize::new(vec![("n", 10)]);
191        assert_eq!(m.evaluate(&size), 30.0);
192    }
193
194    #[test]
195    fn test_polynomial_scale() {
196        let p = Polynomial::var("n").scale(5.0);
197        let size = ProblemSize::new(vec![("n", 10)]);
198        assert_eq!(p.evaluate(&size), 50.0);
199    }
200
201    #[test]
202    fn test_monomial_multi_variable() {
203        // n * m^2
204        let m = Monomial {
205            coefficient: 1.0,
206            variables: vec![("n", 1), ("m", 2)],
207        };
208        let size = ProblemSize::new(vec![("n", 2), ("m", 3)]);
209        assert_eq!(m.evaluate(&size), 18.0); // 2 * 9
210    }
211}