use crate::scalar::{Rational, Scalar};
use std::cmp::Ordering;
use std::fmt::{self, Debug};
use std::marker::PhantomData;
pub trait Semiring: Clone + PartialEq + Debug {
fn zero() -> Self;
fn one() -> Self;
fn add(&self, rhs: &Self) -> Self;
fn mul(&self, rhs: &Self) -> Self;
fn is_zero(&self) -> bool {
*self == Self::zero()
}
}
mod sealed {
pub trait Sealed {}
}
pub trait TropicalConvention: sealed::Sealed + Clone + PartialEq + Debug + 'static {
const MAX: bool;
const ZERO_GLYPH: &'static str;
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub struct MaxPlus;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub struct MinPlus;
impl sealed::Sealed for MaxPlus {}
impl sealed::Sealed for MinPlus {}
impl TropicalConvention for MaxPlus {
const MAX: bool = true;
const ZERO_GLYPH: &'static str = "−∞";
}
impl TropicalConvention for MinPlus {
const MAX: bool = false;
const ZERO_GLYPH: &'static str = "∞";
}
#[derive(Clone, PartialEq)]
enum TropVal {
Finite(Rational),
Infinity,
}
#[derive(Clone, PartialEq)]
pub struct Tropical<C: TropicalConvention = MaxPlus> {
inner: TropVal,
_c: PhantomData<C>,
}
impl<C: TropicalConvention> Tropical<C> {
pub fn finite(r: Rational) -> Self {
Tropical {
inner: TropVal::Finite(r),
_c: PhantomData,
}
}
pub fn infinity() -> Self {
Tropical {
inner: TropVal::Infinity,
_c: PhantomData,
}
}
pub fn int(n: i128) -> Self {
Self::finite(Rational::from_int(n))
}
pub fn value(&self) -> Option<Rational> {
match &self.inner {
TropVal::Finite(r) => Some(r.clone()),
TropVal::Infinity => None,
}
}
pub fn is_infinity(&self) -> bool {
matches!(self.inner, TropVal::Infinity)
}
pub fn zero() -> Self {
Self::infinity()
}
pub fn one() -> Self {
Self::int(0)
}
pub fn add(&self, rhs: &Self) -> Self {
match (&self.inner, &rhs.inner) {
(TropVal::Infinity, _) => rhs.clone(),
(_, TropVal::Infinity) => self.clone(),
(TropVal::Finite(a), TropVal::Finite(b)) => {
let keep_self = if C::MAX {
a.cmp(b) != Ordering::Less } else {
a.cmp(b) != Ordering::Greater };
if keep_self {
self.clone()
} else {
rhs.clone()
}
}
}
}
pub fn mul(&self, rhs: &Self) -> Self {
match (&self.inner, &rhs.inner) {
(TropVal::Infinity, _) | (_, TropVal::Infinity) => Self::infinity(),
(TropVal::Finite(a), TropVal::Finite(b)) => Self::finite(a.add(b)),
}
}
pub fn is_zero(&self) -> bool {
self.is_infinity()
}
fn fmt_inner(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.inner {
TropVal::Infinity => f.write_str(C::ZERO_GLYPH),
TropVal::Finite(r) => write!(f, "{r}"),
}
}
}
impl<C: TropicalConvention> fmt::Display for Tropical<C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.fmt_inner(f)
}
}
impl<C: TropicalConvention> fmt::Debug for Tropical<C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.fmt_inner(f)
}
}
impl<C: TropicalConvention> Semiring for Tropical<C> {
fn zero() -> Self {
Tropical::zero()
}
fn one() -> Self {
Tropical::one()
}
fn add(&self, rhs: &Self) -> Self {
Tropical::add(self, rhs)
}
fn mul(&self, rhs: &Self) -> Self {
Tropical::mul(self, rhs)
}
fn is_zero(&self) -> bool {
Tropical::is_zero(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn rat(n: i128, d: i128) -> Rational {
Rational::new(n, d)
}
#[test]
fn identities() {
let five = Tropical::<MaxPlus>::int(5);
assert_eq!(five.add(&Tropical::zero()), five);
assert_eq!(Tropical::<MaxPlus>::zero().add(&five), five);
assert_eq!(five.mul(&Tropical::one()), five);
assert_eq!(Tropical::<MaxPlus>::one().mul(&five), five);
let m = Tropical::<MinPlus>::int(5);
assert_eq!(m.add(&Tropical::zero()), m);
assert_eq!(m.mul(&Tropical::one()), m);
}
#[test]
fn zero_absorbs_under_otimes() {
let a = Tropical::<MaxPlus>::int(7);
assert!(Tropical::<MaxPlus>::zero().mul(&a).is_zero());
assert!(a.mul(&Tropical::<MaxPlus>::zero()).is_zero());
let b = Tropical::<MinPlus>::int(7);
assert!(Tropical::<MinPlus>::zero().mul(&b).is_zero());
}
#[test]
fn oplus_is_idempotent() {
let a = Tropical::<MaxPlus>::finite(rat(3, 2));
assert_eq!(a.add(&a), a);
let b = Tropical::<MinPlus>::finite(rat(3, 2));
assert_eq!(b.add(&b), b);
assert!(Tropical::<MaxPlus>::zero().add(&Tropical::zero()).is_zero());
}
#[test]
fn max_vs_min_hand_checked() {
let (two_x, five_x) = (Tropical::<MaxPlus>::int(2), Tropical::<MaxPlus>::int(5));
let (two_n, five_n) = (Tropical::<MinPlus>::int(2), Tropical::<MinPlus>::int(5));
assert_eq!(two_x.add(&five_x), five_x);
assert_eq!(two_n.add(&five_n), two_n);
assert_eq!(two_x.mul(&five_x), Tropical::<MaxPlus>::int(7));
assert_eq!(two_n.mul(&five_n), Tropical::<MinPlus>::int(7));
}
#[test]
fn distributivity() {
let a = Tropical::<MaxPlus>::int(2);
let b = Tropical::<MaxPlus>::int(5);
let c = Tropical::<MaxPlus>::int(3);
assert_eq!(a.mul(&b.add(&c)), a.mul(&b).add(&a.mul(&c)));
let inf = Tropical::<MaxPlus>::zero();
assert_eq!(a.mul(&b.add(&inf)), a.mul(&b).add(&a.mul(&inf)));
}
#[test]
fn display_smoke() {
assert_eq!(format!("{}", Tropical::<MaxPlus>::int(3)), "3");
assert_eq!(format!("{}", Tropical::<MaxPlus>::finite(rat(3, 2))), "3/2");
assert_eq!(format!("{}", Tropical::<MaxPlus>::infinity()), "−∞");
assert_eq!(format!("{}", Tropical::<MinPlus>::infinity()), "∞");
assert_eq!(format!("{:?}", Tropical::<MinPlus>::int(-4)), "-4");
}
#[test]
fn value_and_infinity_accessors() {
assert_eq!(
Tropical::<MaxPlus>::int(9).value(),
Some(Rational::from_int(9))
);
assert_eq!(Tropical::<MaxPlus>::infinity().value(), None);
assert!(Tropical::<MinPlus>::infinity().is_infinity());
assert!(!Tropical::<MinPlus>::int(0).is_infinity());
}
}