mathhook_core/core/
commutativity.rs

1//! Commutativity tracking for expressions
2//!
3//! Supports noncommutative algebra (matrices, operators, quaternions) while
4//! maintaining default commutative behavior for scalars.
5//!
6//! Commutativity is computed on-demand from symbol types, not stored in expressions.
7
8/// Commutativity of an expression or operation
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum Commutativity {
11    /// Operation is commutative (a*b = b*a)
12    /// Examples: scalar multiplication, addition
13    Commutative,
14
15    /// Operation is noncommutative (a*b may not equal b*a)
16    /// Examples: matrix multiplication, operator multiplication, quaternion multiplication
17    Noncommutative,
18}
19
20impl Commutativity {
21    /// Can factors be sorted during canonicalization?
22    ///
23    /// Returns true only if commutativity is guaranteed.
24    pub fn can_sort(self) -> bool {
25        matches!(self, Commutativity::Commutative)
26    }
27
28    /// Combine commutativity of multiple factors
29    ///
30    /// Rule: If ANY factor is noncommutative, the entire product is noncommutative.
31    /// Only if ALL factors are commutative is the product commutative.
32    ///
33    /// # Examples
34    ///
35    /// ```
36    /// use mathhook_core::core::commutativity::Commutativity;
37    ///
38    /// // All commutative → result commutative
39    /// let factors = vec![Commutativity::Commutative, Commutativity::Commutative];
40    /// assert_eq!(Commutativity::combine(factors), Commutativity::Commutative);
41    ///
42    /// // Any noncommutative → result noncommutative
43    /// let factors = vec![Commutativity::Commutative, Commutativity::Noncommutative];
44    /// assert_eq!(Commutativity::combine(factors), Commutativity::Noncommutative);
45    /// ```
46    pub fn combine<I>(factors: I) -> Self
47    where
48        I: IntoIterator<Item = Self>,
49    {
50        for comm in factors {
51            if comm == Commutativity::Noncommutative {
52                return Commutativity::Noncommutative;
53            }
54        }
55        Commutativity::Commutative
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62
63    #[test]
64    fn test_commutativity_can_sort() {
65        assert!(Commutativity::Commutative.can_sort());
66        assert!(!Commutativity::Noncommutative.can_sort());
67    }
68
69    #[test]
70    fn test_combine_all_commutative() {
71        let factors = vec![Commutativity::Commutative, Commutativity::Commutative];
72        assert_eq!(Commutativity::combine(factors), Commutativity::Commutative);
73    }
74
75    #[test]
76    fn test_combine_any_noncommutative() {
77        let factors = vec![Commutativity::Commutative, Commutativity::Noncommutative];
78        assert_eq!(
79            Commutativity::combine(factors),
80            Commutativity::Noncommutative
81        );
82    }
83
84    #[test]
85    fn test_combine_all_noncommutative() {
86        let factors = vec![Commutativity::Noncommutative, Commutativity::Noncommutative];
87        assert_eq!(
88            Commutativity::combine(factors),
89            Commutativity::Noncommutative
90        );
91    }
92
93    #[test]
94    fn test_combine_empty() {
95        let factors: Vec<Commutativity> = vec![];
96        assert_eq!(Commutativity::combine(factors), Commutativity::Commutative);
97    }
98}