ruvector_math/tropical/
polynomial.rs1use super::semiring::Tropical;
9
10#[derive(Debug, Clone, Copy)]
12pub struct TropicalMonomial {
13 pub coeff: f64,
15 pub exp: i32,
17}
18
19impl TropicalMonomial {
20 pub fn new(coeff: f64, exp: i32) -> Self {
22 Self { coeff, exp }
23 }
24
25 #[inline]
27 pub fn eval(&self, x: f64) -> f64 {
28 if self.coeff == f64::NEG_INFINITY {
29 f64::NEG_INFINITY
30 } else {
31 self.coeff + self.exp as f64 * x
32 }
33 }
34
35 pub fn mul(&self, other: &Self) -> Self {
37 Self {
38 coeff: self.coeff + other.coeff,
39 exp: self.exp + other.exp,
40 }
41 }
42}
43
44#[derive(Debug, Clone)]
48pub struct TropicalPolynomial {
49 terms: Vec<TropicalMonomial>,
51}
52
53impl TropicalPolynomial {
54 pub fn from_coeffs(coeffs: &[f64]) -> Self {
56 let terms: Vec<TropicalMonomial> = coeffs
57 .iter()
58 .enumerate()
59 .filter(|(_, &c)| c != f64::NEG_INFINITY)
60 .map(|(i, &c)| TropicalMonomial::new(c, i as i32))
61 .collect();
62
63 Self { terms }
64 }
65
66 pub fn from_monomials(terms: Vec<TropicalMonomial>) -> Self {
68 let mut sorted = terms;
69 sorted.sort_by_key(|m| m.exp);
70 Self { terms: sorted }
71 }
72
73 pub fn num_terms(&self) -> usize {
75 self.terms.len()
76 }
77
78 pub fn eval(&self, x: f64) -> f64 {
80 self.terms
81 .iter()
82 .map(|m| m.eval(x))
83 .fold(f64::NEG_INFINITY, f64::max)
84 }
85
86 pub fn roots(&self) -> Vec<f64> {
89 if self.terms.len() < 2 {
90 return vec![];
91 }
92
93 let mut roots = Vec::new();
94
95 for i in 0..self.terms.len() - 1 {
97 for j in i + 1..self.terms.len() {
98 let m1 = &self.terms[i];
99 let m2 = &self.terms[j];
100
101 if m1.exp != m2.exp {
104 let x = (m1.coeff - m2.coeff) / (m2.exp - m1.exp) as f64;
105
106 let val = m1.eval(x);
108 let max_val = self.eval(x);
109
110 if (val - max_val).abs() < 1e-10 {
111 roots.push(x);
112 }
113 }
114 }
115 }
116
117 roots.sort_by(|a, b| a.partial_cmp(b).unwrap());
118 roots.dedup_by(|a, b| (*a - *b).abs() < 1e-10);
119 roots
120 }
121
122 pub fn num_linear_regions(&self) -> usize {
125 1 + self.roots().len()
126 }
127
128 pub fn mul(&self, other: &Self) -> Self {
130 let mut new_terms = Vec::new();
131
132 for m1 in &self.terms {
133 for m2 in &other.terms {
134 new_terms.push(m1.mul(m2));
135 }
136 }
137
138 new_terms.sort_by_key(|m| m.exp);
140
141 let mut simplified = Vec::new();
142 let mut i = 0;
143 while i < new_terms.len() {
144 let exp = new_terms[i].exp;
145 let mut max_coeff = new_terms[i].coeff;
146
147 while i < new_terms.len() && new_terms[i].exp == exp {
148 max_coeff = max_coeff.max(new_terms[i].coeff);
149 i += 1;
150 }
151
152 simplified.push(TropicalMonomial::new(max_coeff, exp));
153 }
154
155 Self { terms: simplified }
156 }
157
158 pub fn add(&self, other: &Self) -> Self {
160 let mut combined: Vec<TropicalMonomial> = Vec::new();
161 combined.extend(self.terms.iter().cloned());
162 combined.extend(other.terms.iter().cloned());
163
164 combined.sort_by_key(|m| m.exp);
165
166 let mut simplified = Vec::new();
168 let mut i = 0;
169 while i < combined.len() {
170 let exp = combined[i].exp;
171 let mut max_coeff = combined[i].coeff;
172
173 while i < combined.len() && combined[i].exp == exp {
174 max_coeff = max_coeff.max(combined[i].coeff);
175 i += 1;
176 }
177
178 simplified.push(TropicalMonomial::new(max_coeff, exp));
179 }
180
181 Self { terms: simplified }
182 }
183}
184
185#[derive(Debug, Clone)]
188pub struct MultivariateTropicalPolynomial {
189 nvars: usize,
191 terms: Vec<(f64, Vec<i32>)>,
193}
194
195impl MultivariateTropicalPolynomial {
196 pub fn new(nvars: usize, terms: Vec<(f64, Vec<i32>)>) -> Self {
198 Self { nvars, terms }
199 }
200
201 pub fn eval(&self, x: &[f64]) -> f64 {
203 assert_eq!(x.len(), self.nvars);
204
205 self.terms
206 .iter()
207 .map(|(coeff, exp)| {
208 if *coeff == f64::NEG_INFINITY {
209 f64::NEG_INFINITY
210 } else {
211 let linear: f64 = exp.iter().zip(x.iter()).map(|(&e, &xi)| e as f64 * xi).sum();
212 coeff + linear
213 }
214 })
215 .fold(f64::NEG_INFINITY, f64::max)
216 }
217
218 pub fn num_terms(&self) -> usize {
220 self.terms.len()
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227
228 #[test]
229 fn test_tropical_polynomial_eval() {
230 let p = TropicalPolynomial::from_coeffs(&[2.0, 1.0, -1.0]);
232
233 assert!((p.eval(0.0) - 2.0).abs() < 1e-10); assert!((p.eval(1.0) - 2.0).abs() < 1e-10); assert!((p.eval(3.0) - 5.0).abs() < 1e-10); }
237
238 #[test]
239 fn test_tropical_roots() {
240 let p = TropicalPolynomial::from_coeffs(&[0.0, 0.0]);
242 let roots = p.roots();
243
244 assert_eq!(roots.len(), 1);
245 assert!(roots[0].abs() < 1e-10);
246 }
247
248 #[test]
249 fn test_tropical_mul() {
250 let p = TropicalPolynomial::from_coeffs(&[1.0, 2.0]); let q = TropicalPolynomial::from_coeffs(&[0.0, 1.0]); let pq = p.mul(&q);
254
255 assert!(pq.num_terms() > 0);
258 }
259
260 #[test]
261 fn test_multivariate() {
262 let p = MultivariateTropicalPolynomial::new(2, vec![
264 (0.0, vec![0, 0]),
265 (0.0, vec![1, 0]),
266 (0.0, vec![0, 1]),
267 ]);
268
269 assert!((p.eval(&[1.0, 2.0]) - 2.0).abs() < 1e-10);
270 assert!((p.eval(&[3.0, 1.0]) - 3.0).abs() < 1e-10);
271 }
272}