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