ruvector_math/optimization/
polynomial.rs

1//! Multivariate Polynomials
2//!
3//! Representation and operations for multivariate polynomials.
4
5use std::collections::HashMap;
6
7/// A monomial: product of variables with powers
8/// Represented as sorted list of (variable_index, power)
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
10pub struct Monomial {
11    /// (variable_index, power) pairs, sorted by variable index
12    pub powers: Vec<(usize, usize)>,
13}
14
15impl Monomial {
16    /// Create constant monomial (1)
17    pub fn one() -> Self {
18        Self { powers: vec![] }
19    }
20
21    /// Create single variable monomial x_i
22    pub fn var(i: usize) -> Self {
23        Self { powers: vec![(i, 1)] }
24    }
25
26    /// Create from powers (will be sorted)
27    pub fn new(mut powers: Vec<(usize, usize)>) -> Self {
28        // Sort and merge
29        powers.sort_by_key(|&(i, _)| i);
30
31        // Merge duplicate variables
32        let mut merged = Vec::new();
33        for (i, p) in powers {
34            if p == 0 {
35                continue;
36            }
37            if let Some(&mut (last_i, ref mut last_p)) = merged.last_mut() {
38                if last_i == i {
39                    *last_p += p;
40                    continue;
41                }
42            }
43            merged.push((i, p));
44        }
45
46        Self { powers: merged }
47    }
48
49    /// Total degree
50    pub fn degree(&self) -> usize {
51        self.powers.iter().map(|&(_, p)| p).sum()
52    }
53
54    /// Is this the constant monomial?
55    pub fn is_constant(&self) -> bool {
56        self.powers.is_empty()
57    }
58
59    /// Maximum variable index (or None if constant)
60    pub fn max_var(&self) -> Option<usize> {
61        self.powers.last().map(|&(i, _)| i)
62    }
63
64    /// Multiply two monomials
65    pub fn mul(&self, other: &Monomial) -> Monomial {
66        let mut combined = self.powers.clone();
67        combined.extend(other.powers.iter().copied());
68        Monomial::new(combined)
69    }
70
71    /// Evaluate at point
72    pub fn eval(&self, x: &[f64]) -> f64 {
73        let mut result = 1.0;
74        for &(i, p) in &self.powers {
75            if i < x.len() {
76                result *= x[i].powi(p as i32);
77            }
78        }
79        result
80    }
81
82    /// Check divisibility: does self divide other?
83    pub fn divides(&self, other: &Monomial) -> bool {
84        let mut j = 0;
85        for &(i, p) in &self.powers {
86            // Find matching variable in other
87            while j < other.powers.len() && other.powers[j].0 < i {
88                j += 1;
89            }
90            if j >= other.powers.len() || other.powers[j].0 != i || other.powers[j].1 < p {
91                return false;
92            }
93            j += 1;
94        }
95        true
96    }
97}
98
99impl std::fmt::Display for Monomial {
100    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101        if self.powers.is_empty() {
102            write!(f, "1")
103        } else {
104            let parts: Vec<String> = self
105                .powers
106                .iter()
107                .map(|&(i, p)| {
108                    if p == 1 {
109                        format!("x{}", i)
110                    } else {
111                        format!("x{}^{}", i, p)
112                    }
113                })
114                .collect();
115            write!(f, "{}", parts.join("*"))
116        }
117    }
118}
119
120/// A term: coefficient times monomial
121#[derive(Debug, Clone)]
122pub struct Term {
123    /// Coefficient
124    pub coeff: f64,
125    /// Monomial
126    pub monomial: Monomial,
127}
128
129impl Term {
130    /// Create term from coefficient and powers
131    pub fn new(coeff: f64, powers: Vec<(usize, usize)>) -> Self {
132        Self {
133            coeff,
134            monomial: Monomial::new(powers),
135        }
136    }
137
138    /// Create constant term
139    pub fn constant(c: f64) -> Self {
140        Self {
141            coeff: c,
142            monomial: Monomial::one(),
143        }
144    }
145
146    /// Degree
147    pub fn degree(&self) -> usize {
148        self.monomial.degree()
149    }
150}
151
152/// Multivariate polynomial
153#[derive(Debug, Clone)]
154pub struct Polynomial {
155    /// Terms indexed by monomial
156    terms: HashMap<Monomial, f64>,
157    /// Cached degree
158    degree: usize,
159    /// Number of variables
160    num_vars: usize,
161}
162
163impl Polynomial {
164    /// Create zero polynomial
165    pub fn zero() -> Self {
166        Self {
167            terms: HashMap::new(),
168            degree: 0,
169            num_vars: 0,
170        }
171    }
172
173    /// Create constant polynomial
174    pub fn constant(c: f64) -> Self {
175        if c == 0.0 {
176            return Self::zero();
177        }
178        let mut terms = HashMap::new();
179        terms.insert(Monomial::one(), c);
180        Self {
181            terms,
182            degree: 0,
183            num_vars: 0,
184        }
185    }
186
187    /// Create single variable polynomial x_i
188    pub fn var(i: usize) -> Self {
189        let mut terms = HashMap::new();
190        terms.insert(Monomial::var(i), 1.0);
191        Self {
192            terms,
193            degree: 1,
194            num_vars: i + 1,
195        }
196    }
197
198    /// Create from terms
199    pub fn from_terms(term_list: Vec<Term>) -> Self {
200        let mut terms = HashMap::new();
201        let mut degree = 0;
202        let mut num_vars = 0;
203
204        for term in term_list {
205            if term.coeff.abs() < 1e-15 {
206                continue;
207            }
208
209            degree = degree.max(term.degree());
210            if let Some(max_v) = term.monomial.max_var() {
211                num_vars = num_vars.max(max_v + 1);
212            }
213
214            *terms.entry(term.monomial).or_insert(0.0) += term.coeff;
215        }
216
217        // Remove zero terms
218        terms.retain(|_, &mut c| c.abs() >= 1e-15);
219
220        Self { terms, degree, num_vars }
221    }
222
223    /// Total degree
224    pub fn degree(&self) -> usize {
225        self.degree
226    }
227
228    /// Number of variables (max variable index + 1)
229    pub fn num_variables(&self) -> usize {
230        self.num_vars
231    }
232
233    /// Number of terms
234    pub fn num_terms(&self) -> usize {
235        self.terms.len()
236    }
237
238    /// Is zero polynomial?
239    pub fn is_zero(&self) -> bool {
240        self.terms.is_empty()
241    }
242
243    /// Get coefficient of monomial
244    pub fn coeff(&self, m: &Monomial) -> f64 {
245        *self.terms.get(m).unwrap_or(&0.0)
246    }
247
248    /// Get all terms
249    pub fn terms(&self) -> impl Iterator<Item = (&Monomial, &f64)> {
250        self.terms.iter()
251    }
252
253    /// Evaluate at point
254    pub fn eval(&self, x: &[f64]) -> f64 {
255        self.terms
256            .iter()
257            .map(|(m, &c)| c * m.eval(x))
258            .sum()
259    }
260
261    /// Add two polynomials
262    pub fn add(&self, other: &Polynomial) -> Polynomial {
263        let mut terms = self.terms.clone();
264
265        for (m, &c) in &other.terms {
266            *terms.entry(m.clone()).or_insert(0.0) += c;
267        }
268
269        terms.retain(|_, &mut c| c.abs() >= 1e-15);
270
271        let degree = terms.keys().map(|m| m.degree()).max().unwrap_or(0);
272        let num_vars = terms
273            .keys()
274            .filter_map(|m| m.max_var())
275            .max()
276            .map(|v| v + 1)
277            .unwrap_or(0);
278
279        Polynomial { terms, degree, num_vars }
280    }
281
282    /// Subtract polynomials
283    pub fn sub(&self, other: &Polynomial) -> Polynomial {
284        self.add(&other.neg())
285    }
286
287    /// Negate polynomial
288    pub fn neg(&self) -> Polynomial {
289        Polynomial {
290            terms: self.terms.iter().map(|(m, &c)| (m.clone(), -c)).collect(),
291            degree: self.degree,
292            num_vars: self.num_vars,
293        }
294    }
295
296    /// Multiply by scalar
297    pub fn scale(&self, s: f64) -> Polynomial {
298        if s.abs() < 1e-15 {
299            return Polynomial::zero();
300        }
301
302        Polynomial {
303            terms: self.terms.iter().map(|(m, &c)| (m.clone(), s * c)).collect(),
304            degree: self.degree,
305            num_vars: self.num_vars,
306        }
307    }
308
309    /// Multiply two polynomials
310    pub fn mul(&self, other: &Polynomial) -> Polynomial {
311        let mut terms = HashMap::new();
312
313        for (m1, &c1) in &self.terms {
314            for (m2, &c2) in &other.terms {
315                let m = m1.mul(m2);
316                *terms.entry(m).or_insert(0.0) += c1 * c2;
317            }
318        }
319
320        terms.retain(|_, &mut c| c.abs() >= 1e-15);
321
322        let degree = terms.keys().map(|m| m.degree()).max().unwrap_or(0);
323        let num_vars = terms
324            .keys()
325            .filter_map(|m| m.max_var())
326            .max()
327            .map(|v| v + 1)
328            .unwrap_or(0);
329
330        Polynomial { terms, degree, num_vars }
331    }
332
333    /// Square polynomial
334    pub fn square(&self) -> Polynomial {
335        self.mul(self)
336    }
337
338    /// Power
339    pub fn pow(&self, n: usize) -> Polynomial {
340        if n == 0 {
341            return Polynomial::constant(1.0);
342        }
343        if n == 1 {
344            return self.clone();
345        }
346
347        let mut result = self.clone();
348        for _ in 1..n {
349            result = result.mul(self);
350        }
351        result
352    }
353
354    /// Generate all monomials up to given degree
355    pub fn monomials_up_to_degree(num_vars: usize, max_degree: usize) -> Vec<Monomial> {
356        let mut result = vec![Monomial::one()];
357
358        if max_degree == 0 || num_vars == 0 {
359            return result;
360        }
361
362        // Generate systematically using recursion
363        fn generate(
364            var: usize,
365            num_vars: usize,
366            remaining_degree: usize,
367            current: Vec<(usize, usize)>,
368            result: &mut Vec<Monomial>,
369        ) {
370            if var >= num_vars {
371                result.push(Monomial::new(current));
372                return;
373            }
374
375            for p in 0..=remaining_degree {
376                let mut next = current.clone();
377                if p > 0 {
378                    next.push((var, p));
379                }
380                generate(var + 1, num_vars, remaining_degree - p, next, result);
381            }
382        }
383
384        for d in 1..=max_degree {
385            generate(0, num_vars, d, vec![], &mut result);
386        }
387
388        // Deduplicate
389        result.sort_by(|a, b| {
390            a.degree()
391                .cmp(&b.degree())
392                .then_with(|| a.powers.cmp(&b.powers))
393        });
394        result.dedup();
395
396        // Ensure only one constant monomial
397        let const_count = result.iter().filter(|m| m.is_constant()).count();
398        if const_count > 1 {
399            let mut seen_const = false;
400            result.retain(|m| {
401                if m.is_constant() {
402                    if seen_const {
403                        return false;
404                    }
405                    seen_const = true;
406                }
407                true
408            });
409        }
410
411        result
412    }
413}
414
415impl std::fmt::Display for Polynomial {
416    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
417        if self.terms.is_empty() {
418            return write!(f, "0");
419        }
420
421        let mut sorted: Vec<_> = self.terms.iter().collect();
422        sorted.sort_by(|a, b| a.0.degree().cmp(&b.0.degree()).then_with(|| a.0.powers.cmp(&b.0.powers)));
423
424        let parts: Vec<String> = sorted
425            .iter()
426            .map(|(m, &c)| {
427                if m.is_constant() {
428                    format!("{:.4}", c)
429                } else if (c - 1.0).abs() < 1e-10 {
430                    format!("{}", m)
431                } else if (c + 1.0).abs() < 1e-10 {
432                    format!("-{}", m)
433                } else {
434                    format!("{:.4}*{}", c, m)
435                }
436            })
437            .collect();
438
439        write!(f, "{}", parts.join(" + "))
440    }
441}
442
443#[cfg(test)]
444mod tests {
445    use super::*;
446
447    #[test]
448    fn test_monomial() {
449        let m1 = Monomial::var(0);
450        let m2 = Monomial::var(1);
451        let m3 = m1.mul(&m2);
452
453        assert_eq!(m3.degree(), 2);
454        assert_eq!(m3.powers, vec![(0, 1), (1, 1)]);
455    }
456
457    #[test]
458    fn test_polynomial_eval() {
459        // p = x² + 2xy + y²
460        let p = Polynomial::from_terms(vec![
461            Term::new(1.0, vec![(0, 2)]),
462            Term::new(2.0, vec![(0, 1), (1, 1)]),
463            Term::new(1.0, vec![(1, 2)]),
464        ]);
465
466        // At (1, 1): 1 + 2 + 1 = 4
467        assert!((p.eval(&[1.0, 1.0]) - 4.0).abs() < 1e-10);
468
469        // At (2, 3): 4 + 12 + 9 = 25 = (2+3)²
470        assert!((p.eval(&[2.0, 3.0]) - 25.0).abs() < 1e-10);
471    }
472
473    #[test]
474    fn test_polynomial_mul() {
475        // (x + y)²  = x² + 2xy + y²
476        let x = Polynomial::var(0);
477        let y = Polynomial::var(1);
478        let sum = x.add(&y);
479        let squared = sum.square();
480
481        assert!((squared.coeff(&Monomial::new(vec![(0, 2)])) - 1.0).abs() < 1e-10);
482        assert!((squared.coeff(&Monomial::new(vec![(0, 1), (1, 1)])) - 2.0).abs() < 1e-10);
483        assert!((squared.coeff(&Monomial::new(vec![(1, 2)])) - 1.0).abs() < 1e-10);
484    }
485
486    #[test]
487    fn test_monomials_generation() {
488        let monoms = Polynomial::monomials_up_to_degree(2, 2);
489
490        // Should have: 1, x0, x1, x0², x0*x1, x1²
491        assert!(monoms.len() >= 6);
492    }
493}