Skip to main content

karpal_algebra/
group.rs

1// Copyright (C) 2026 Industrial Algebra
2// SPDX-License-Identifier: Apache-2.0
3
4use karpal_core::Monoid;
5
6/// A `Monoid` where every element has an inverse.
7///
8/// Law: `a.combine(a.invert()) == empty()`
9/// Law: `a.invert().combine(a) == empty()`
10pub trait Group: Monoid {
11    fn invert(self) -> Self;
12
13    fn combine_inverse(self, other: Self) -> Self
14    where
15        Self: Sized,
16    {
17        self.combine(other.invert())
18    }
19}
20
21macro_rules! impl_signed_group {
22    ($($t:ty),*) => {
23        $(
24            impl Group for $t {
25                fn invert(self) -> Self {
26                    -self
27                }
28            }
29        )*
30    };
31}
32
33impl_signed_group!(i8, i16, i32, i64, i128, f32, f64);
34
35impl<A: Group, B: Group> Group for (A, B) {
36    fn invert(self) -> Self {
37        (self.0.invert(), self.1.invert())
38    }
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    #[test]
46    fn i32_invert() {
47        assert_eq!(5i32.invert(), -5);
48        assert_eq!((-3i32).invert(), 3);
49    }
50
51    #[test]
52    fn i32_combine_inverse() {
53        assert_eq!(10i32.combine_inverse(3), 7);
54    }
55
56    #[test]
57    fn f64_invert() {
58        assert!((1.5f64.invert() - (-1.5)).abs() < 1e-10);
59    }
60}
61
62#[cfg(test)]
63mod law_tests {
64    use super::*;
65    use karpal_core::Semigroup;
66    use proptest::prelude::*;
67
68    proptest! {
69        #[test]
70        fn left_inverse(a in -100i16..100i16) {
71            prop_assert_eq!(a.invert().combine(a), i16::empty());
72        }
73
74        #[test]
75        fn right_inverse(a in -100i16..100i16) {
76            prop_assert_eq!(a.combine(a.invert()), i16::empty());
77        }
78
79        #[test]
80        fn combine_inverse_is_combine_invert(a in -50i16..50i16, b in -50i16..50i16) {
81            prop_assert_eq!(a.combine_inverse(b), a.combine(b.invert()));
82        }
83    }
84}