Skip to main content

cova_algebra/modules/
tropical.rs

1//! Implementation of tropical algebra, sometimes called max-plus algebra.
2//!
3//! A tropical algebra is a semiring where addition is replaced by maximum
4//! and multiplication is replaced by addition. The tropical semiring is defined as:
5//! (ℝ ∪ {-∞}, max, +)
6//!
7//! In this implementation, we use the max-plus variant where:
8//! - Addition (⊕) is defined as maximum: a ⊕ b = max(a, b)
9//! - Multiplication (⊗) is defined as addition: a ⊗ b = a + b
10//! - Zero is -∞ (f64::NEG_INFINITY)
11//! - One is 0
12//!
13//! Tropical algebra is useful in various applications including:
14//! - Optimization problems
15//! - Scheduling
16//! - Network analysis
17//! - Discrete event systems
18//! - Algebraic geometry
19//!
20//! # Examples
21//!
22//! ```rust, ignore
23//! use cova_algebra::semimodule::tropical::{BilinearForm, TropicalAlgebra, TropicalElement};
24//!
25//! // Create tropical elements
26//! let a = TropicalElement::new(3.0);
27//! let b = TropicalElement::new(5.0);
28//!
29//! // Addition is max: 3 ⊕ 5 = 5
30//! let sum = a + b;
31//! assert_eq!(sum.value(), TropicalElement::Element(5.0));
32//!
33//! // Multiplication is +: 3 ⊗ 5 = 8
34//! let product = a * b;
35//! assert_eq!(product.value(), TropicalElement::Element(8.0));
36//!
37//! // Create a tropical algebra with a bilinear form
38//! let matrix = [[TropicalElement::new(1.0), TropicalElement::new(2.0)], [
39//!   TropicalElement::new(2.0),
40//!   TropicalElement::new(1.0),
41//! ]];
42//! let bilinear_form = BilinearForm::new(matrix);
43//! let algebra = TropicalAlgebra::new(bilinear_form);
44//!
45//! // Evaluate the bilinear form on vectors
46//! let x = [TropicalElement::new(3.0), TropicalElement::new(4.0)];
47//! let y = [TropicalElement::new(5.0), TropicalElement::new(6.0)];
48//! let result = algebra.evaluate(&x, &y);
49//! assert_eq!(result.value(), TropicalElement::Element(11.0));
50//! ```
51
52use std::fmt::Debug;
53
54use super::*;
55use crate::rings::Semiring;
56
57/// An element of the tropical algebra.
58///
59/// In tropical algebra:
60/// - Addition (⊕) is defined as maximum: a ⊕ b = max(a, b)
61/// - Multiplication (⊗) is defined as addition: a ⊗ b = a + b
62/// - Zero is -∞
63/// - One is 0
64#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
65pub enum TropicalElement<F: Field> {
66  /// A finite element of the tropical algebra.
67  Element(F),
68  /// Represents negative infinity, the zero element in tropical algebra.
69  NegInfinity,
70}
71
72impl<F: Field> Eq for TropicalElement<F> {}
73impl<F: Field> TropicalElement<F> {
74  /// Creates a new tropical element with the given value.
75  pub fn new(value: F) -> Self { TropicalElement::Element(value) }
76
77  /// Returns the value of this tropical element.
78  pub fn value(&self) -> TropicalElement<F> { *self }
79}
80
81impl<F: Field + PartialOrd> Add for TropicalElement<F> {
82  type Output = Self;
83
84  fn add(self, other: Self) -> Self::Output {
85    match self {
86      TropicalElement::Element(a) => {
87        match other {
88          TropicalElement::Element(b) => {
89            // In tropical algebra, addition is maximum
90            Self::Element(if a > b { a } else { b })
91          },
92          TropicalElement::NegInfinity => Self::Element(a),
93        }
94      },
95      TropicalElement::NegInfinity => match other {
96        TropicalElement::Element(b) => Self::Element(b),
97        TropicalElement::NegInfinity => Self::NegInfinity,
98      },
99    }
100  }
101}
102
103impl<F: Field + PartialOrd> AddAssign for TropicalElement<F> {
104  fn add_assign(&mut self, rhs: Self) { *self = *self + rhs; }
105}
106
107impl<F: Field + PartialOrd> Mul for TropicalElement<F> {
108  type Output = Self;
109
110  fn mul(self, other: Self) -> Self::Output {
111    match self {
112      TropicalElement::Element(a) => {
113        match other {
114          TropicalElement::Element(b) => {
115            // In tropical algebra, multiplication is addition
116            #[allow(clippy::suspicious_arithmetic_impl)]
117            Self::Element(a + b)
118          },
119          TropicalElement::NegInfinity => Self::NegInfinity,
120        }
121      },
122      TropicalElement::NegInfinity => Self::NegInfinity,
123    }
124  }
125}
126
127impl<F: Field + PartialOrd> MulAssign for TropicalElement<F> {
128  fn mul_assign(&mut self, rhs: Self) { *self = *self * rhs; }
129}
130
131impl<F: Field + PartialOrd> Zero for TropicalElement<F> {
132  fn zero() -> Self { TropicalElement::NegInfinity }
133
134  fn is_zero(&self) -> bool { *self == TropicalElement::NegInfinity }
135}
136
137impl<F: Field + PartialOrd> One for TropicalElement<F> {
138  fn one() -> Self { TropicalElement::Element(F::zero()) }
139}
140
141impl<F: Field + PartialOrd> Additive for TropicalElement<F> {}
142impl<F: Field + PartialOrd> Multiplicative for TropicalElement<F> {}
143impl<F: Field + PartialOrd> Semiring for TropicalElement<F> {}
144
145/// Symmetric bilinear form
146#[derive(Debug, PartialEq, Eq)]
147pub struct BilinearForm<const N: usize, F>
148where
149  F: Field + PartialOrd,
150  [(); N * (N + 1) / 2]:, {
151  // Store only the upper triangular part of the matrix
152  // The elements are stored in row-major order, only including elements where i <= j
153  matrix: [TropicalElement<F>; N * (N + 1) / 2],
154}
155
156impl<const N: usize, F> BilinearForm<N, F>
157where
158  F: Field + PartialOrd,
159  [(); N * (N + 1) / 2]:, // Ensure the array size is valid at compile time
160{
161  /// Creates a new bilinear form with the given matrix.
162  /// The input matrix should be symmetric.
163  pub fn new(matrix: [[TropicalElement<F>; N]; N]) -> Self {
164    let mut upper_triangular = [TropicalElement::NegInfinity; N * (N + 1) / 2];
165    let mut idx = 0;
166    for (i, row) in matrix.iter().enumerate() {
167      for (_j, &element) in row.iter().enumerate().skip(i) {
168        upper_triangular[idx] = element;
169        idx += 1;
170      }
171    }
172    Self { matrix: upper_triangular }
173  }
174
175  /// Gets the element at position (i,j) in the matrix.
176  /// Since the matrix is symmetric, we only store the upper triangular part.
177  fn get(&self, i: usize, j: usize) -> TropicalElement<F> {
178    if i <= j {
179      // For upper triangular part, use the stored value
180      // Calculate index in the flattened upper triangular matrix:
181      // idx = (i * (2N - i + 1)) / 2 + (j - i)
182      let n = N;
183      let idx = i
184        .checked_mul(2 * n - i + 1)
185        .and_then(|x| x.checked_div(2))
186        .and_then(|x| x.checked_add(j - i))
187        .expect("Index calculation overflow");
188      self.matrix[idx]
189    } else {
190      // For lower triangular part, use symmetry
191      self.get(j, i)
192    }
193  }
194
195  /// Evaluates the bilinear form on two vectors.
196  pub fn evaluate(
197    &self,
198    x: &[TropicalElement<F>; N],
199    y: &[TropicalElement<F>; N],
200  ) -> TropicalElement<F> {
201    let mut result = TropicalElement::<F>::zero();
202
203    for (i, &xi) in x.iter().enumerate() {
204      for (j, &yj) in y.iter().enumerate() {
205        let term = xi * self.get(i, j) * yj;
206        // In tropical algebra, we want to take the maximum
207        // If result is NegInfinity, we should always take the term
208        // Otherwise, take the maximum of term and result
209        result = match (term, result) {
210          (TropicalElement::Element(_), TropicalElement::NegInfinity) => term,
211          (TropicalElement::NegInfinity, TropicalElement::Element(_)) => result,
212          (TropicalElement::Element(t), TropicalElement::Element(r)) =>
213            if t > r {
214              term
215            } else {
216              result
217            },
218          (TropicalElement::NegInfinity, TropicalElement::NegInfinity) => result,
219        };
220      }
221    }
222
223    result
224  }
225}
226
227/// A tropical algebra.
228pub struct TropicalAlgebra<const N: usize, F>
229where
230  F: Field + PartialOrd,
231  [(); N * (N + 1) / 2]:, {
232  /// The bilinear form defining the algebra
233  bilinear_form: BilinearForm<N, F>,
234}
235
236impl<const N: usize, F> TropicalAlgebra<N, F>
237where
238  F: Field + PartialOrd,
239  [(); N * (N + 1) / 2]:,
240{
241  /// Creates a new tropical algebra with the given bilinear form.
242  pub fn new(bilinear_form: BilinearForm<N, F>) -> Self { Self { bilinear_form } }
243
244  /// Evaluates the bilinear form on two vectors.
245  pub fn evaluate(
246    &self,
247    x: &[TropicalElement<F>; N],
248    y: &[TropicalElement<F>; N],
249  ) -> TropicalElement<F> {
250    self.bilinear_form.evaluate(x, y)
251  }
252}
253
254#[cfg(test)]
255mod tests {
256  use super::*;
257
258  #[test]
259  fn test_tropical_element_operations() {
260    let a = TropicalElement::new(3.0);
261    let b = TropicalElement::new(5.0);
262    let c = TropicalElement::new(2.0);
263
264    // Test addition (max)
265    assert_eq!(a + b, TropicalElement::new(5.0));
266    assert_eq!(a + c, TropicalElement::new(3.0));
267    assert_eq!(b + c, TropicalElement::new(5.0));
268
269    // Test multiplication (addition)
270    assert_eq!(a * b, TropicalElement::new(8.0));
271    assert_eq!(a * c, TropicalElement::new(5.0));
272    assert_eq!(b * c, TropicalElement::new(7.0));
273
274    // Test zero and one
275    assert_eq!(TropicalElement::<f64>::zero().value(), TropicalElement::NegInfinity);
276    assert_eq!(TropicalElement::<f64>::one().value(), TropicalElement::Element(0.0));
277
278    // Test additive identity
279    assert_eq!(a + TropicalElement::<f64>::zero(), a);
280    assert_eq!(TropicalElement::<f64>::zero() + a, a);
281
282    // Test multiplicative identity
283    assert_eq!(a * TropicalElement::<f64>::one(), a);
284    assert_eq!(TropicalElement::<f64>::one() * a, a);
285
286    // Test additive assignment
287    let mut x = a;
288    x += b;
289    assert_eq!(x, TropicalElement::new(5.0));
290
291    // Test multiplicative assignment
292    let mut y = a;
293    y *= b;
294    assert_eq!(y, TropicalElement::new(8.0));
295  }
296
297  #[test]
298  fn test_bilinear_form_evaluation() {
299    let matrix = [[TropicalElement::new(1.0), TropicalElement::new(2.0)], [
300      TropicalElement::new(2.0),
301      TropicalElement::new(1.0),
302    ]];
303    let bilinear_form = BilinearForm::new(matrix);
304
305    // Test with simple vectors
306    let x = [TropicalElement::new(3.0), TropicalElement::new(4.0)];
307    let y = [TropicalElement::new(5.0), TropicalElement::new(6.0)];
308
309    // B(x,y) = max(x₁ + M₁₁ + y₁, x₁ + M₁₂ + y₂, x₂ + M₂₁ + y₁, x₂ + M₂₂ + y₂)
310    // = max(3 + 1 + 5, 3 + 2 + 6, 4 + 2 + 5, 4 + 1 + 6)
311    // = max(9, 11, 11, 11)
312    // = 11
313    assert_eq!(bilinear_form.evaluate(&x, &y), TropicalElement::new(11.0));
314
315    // Test with zero vector
316    let zero = [TropicalElement::<f64>::zero(), TropicalElement::<f64>::zero()];
317    assert_eq!(bilinear_form.evaluate(&zero, &y), TropicalElement::<f64>::zero());
318    assert_eq!(bilinear_form.evaluate(&x, &zero), TropicalElement::<f64>::zero());
319
320    // Test with one vector
321    let one = [TropicalElement::<f64>::one(), TropicalElement::<f64>::one()];
322    assert_eq!(bilinear_form.evaluate(&one, &one), TropicalElement::new(2.0));
323  }
324
325  #[test]
326  fn test_tropical_algebra() {
327    let matrix = [[TropicalElement::new(1.0), TropicalElement::new(2.0)], [
328      TropicalElement::new(2.0),
329      TropicalElement::new(1.0),
330    ]];
331    let bilinear_form = BilinearForm::new(matrix);
332    let algebra = TropicalAlgebra::new(bilinear_form);
333
334    // Test evaluation through algebra
335    let x = [TropicalElement::new(3.0), TropicalElement::new(4.0)];
336    let y = [TropicalElement::new(5.0), TropicalElement::new(6.0)];
337    assert_eq!(algebra.evaluate(&x, &y), TropicalElement::new(11.0));
338
339    // Test with different vectors
340    let a = [TropicalElement::new(0.0), TropicalElement::new(1.0)];
341    let b = [TropicalElement::new(2.0), TropicalElement::new(3.0)];
342    assert_eq!(algebra.evaluate(&a, &b), TropicalElement::new(5.0));
343  }
344
345  #[test]
346  fn test_tropical_element_ordering() {
347    let a = TropicalElement::new(3.0);
348    let b = TropicalElement::new(5.0);
349    let c = TropicalElement::new(3.0);
350
351    assert!(a < b);
352    assert!(b > a);
353    assert!(a <= c);
354    assert!(a >= c);
355    assert_eq!(a, c);
356  }
357
358  #[test]
359  fn test_tropical_element_zero_one_properties() {
360    let a = TropicalElement::new(3.0);
361    let zero = TropicalElement::<f64>::zero();
362    let one = TropicalElement::<f64>::one();
363
364    // Test zero properties
365    assert!(zero.is_zero());
366    dbg!(a);
367    dbg!(zero);
368    assert_eq!(a + zero, a);
369    assert_eq!(zero + a, a);
370    assert_eq!(a * zero, zero);
371    assert_eq!(zero * a, zero);
372
373    // Test one properties
374    assert_eq!(a * one, a);
375    assert_eq!(one * a, a);
376  }
377}