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
212 .iter()
213 .zip(x.iter())
214 .map(|(&e, &xi)| e as f64 * xi)
215 .sum();
216 coeff + linear
217 }
218 })
219 .fold(f64::NEG_INFINITY, f64::max)
220 }
221
222 pub fn num_terms(&self) -> usize {
224 self.terms.len()
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231
232 #[test]
233 fn test_tropical_polynomial_eval() {
234 let p = TropicalPolynomial::from_coeffs(&[2.0, 1.0, -1.0]);
236
237 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); }
241
242 #[test]
243 fn test_tropical_roots() {
244 let p = TropicalPolynomial::from_coeffs(&[0.0, 0.0]);
246 let roots = p.roots();
247
248 assert_eq!(roots.len(), 1);
249 assert!(roots[0].abs() < 1e-10);
250 }
251
252 #[test]
253 fn test_tropical_mul() {
254 let p = TropicalPolynomial::from_coeffs(&[1.0, 2.0]); let q = TropicalPolynomial::from_coeffs(&[0.0, 1.0]); let pq = p.mul(&q);
258
259 assert!(pq.num_terms() > 0);
262 }
263
264 #[test]
265 fn test_multivariate() {
266 let p = MultivariateTropicalPolynomial::new(
268 2,
269 vec![(0.0, vec![0, 0]), (0.0, vec![1, 0]), (0.0, vec![0, 1])],
270 );
271
272 assert!((p.eval(&[1.0, 2.0]) - 2.0).abs() < 1e-10);
273 assert!((p.eval(&[3.0, 1.0]) - 3.0).abs() < 1e-10);
274 }
275}