1#[must_use]
6pub fn is_closed_under<T, F>(elements: &[T], operation: F) -> bool
7where
8 T: Copy + PartialEq,
9 F: Fn(T, T) -> T + Copy,
10{
11 for left in elements.iter().copied() {
12 for right in elements.iter().copied() {
13 let result = operation(left, right);
14
15 if !elements.contains(&result) {
16 return false;
17 }
18 }
19 }
20
21 true
22}
23
24#[must_use]
26pub fn is_associative<T, F>(elements: &[T], operation: F) -> bool
27where
28 T: Copy + PartialEq,
29 F: Fn(T, T) -> T + Copy,
30{
31 for first in elements.iter().copied() {
32 for second in elements.iter().copied() {
33 for third in elements.iter().copied() {
34 if operation(operation(first, second), third)
35 != operation(first, operation(second, third))
36 {
37 return false;
38 }
39 }
40 }
41 }
42
43 true
44}
45
46#[must_use]
48pub fn is_commutative<T, F>(elements: &[T], operation: F) -> bool
49where
50 T: Copy + PartialEq,
51 F: Fn(T, T) -> T + Copy,
52{
53 for left in elements.iter().copied() {
54 for right in elements.iter().copied() {
55 if operation(left, right) != operation(right, left) {
56 return false;
57 }
58 }
59 }
60
61 true
62}
63
64#[must_use]
67pub fn identity_element<T, F>(elements: &[T], operation: F) -> Option<T>
68where
69 T: Copy + PartialEq,
70 F: Fn(T, T) -> T + Copy,
71{
72 'candidate: for candidate in elements.iter().copied() {
73 for value in elements.iter().copied() {
74 if operation(candidate, value) != value || operation(value, candidate) != value {
75 continue 'candidate;
76 }
77 }
78
79 return Some(candidate);
80 }
81
82 None
83}
84
85#[must_use]
88pub fn has_inverses<T, F>(elements: &[T], operation: F, identity: T) -> bool
89where
90 T: Copy + PartialEq,
91 F: Fn(T, T) -> T + Copy,
92{
93 if !elements.contains(&identity) {
94 return false;
95 }
96
97 for value in elements.iter().copied() {
98 let mut found_inverse = false;
99
100 for candidate in elements.iter().copied() {
101 if operation(value, candidate) == identity && operation(candidate, value) == identity {
102 found_inverse = true;
103 break;
104 }
105 }
106
107 if !found_inverse {
108 return false;
109 }
110 }
111
112 true
113}
114
115#[must_use]
117pub fn is_monoid<T, F>(elements: &[T], operation: F) -> bool
118where
119 T: Copy + PartialEq,
120 F: Fn(T, T) -> T + Copy,
121{
122 is_closed_under(elements, operation)
123 && is_associative(elements, operation)
124 && identity_element(elements, operation).is_some()
125}
126
127#[must_use]
129pub fn is_group<T, F>(elements: &[T], operation: F) -> bool
130where
131 T: Copy + PartialEq,
132 F: Fn(T, T) -> T + Copy,
133{
134 let Some(identity) = identity_element(elements, operation) else {
135 return false;
136 };
137
138 is_closed_under(elements, operation)
139 && is_associative(elements, operation)
140 && has_inverses(elements, operation, identity)
141}
142
143#[must_use]
145pub fn is_abelian_group<T, F>(elements: &[T], operation: F) -> bool
146where
147 T: Copy + PartialEq,
148 F: Fn(T, T) -> T + Copy,
149{
150 is_group(elements, operation) && is_commutative(elements, operation)
151}
152
153#[must_use]
156pub fn is_distributive_over<T, Mul, Add>(elements: &[T], multiply: Mul, add: Add) -> bool
157where
158 T: Copy + PartialEq,
159 Mul: Fn(T, T) -> T + Copy,
160 Add: Fn(T, T) -> T + Copy,
161{
162 for left in elements.iter().copied() {
163 for middle in elements.iter().copied() {
164 for right in elements.iter().copied() {
165 if multiply(left, add(middle, right))
166 != add(multiply(left, middle), multiply(left, right))
167 {
168 return false;
169 }
170
171 if multiply(add(left, middle), right)
172 != add(multiply(left, right), multiply(middle, right))
173 {
174 return false;
175 }
176 }
177 }
178 }
179
180 true
181}
182
183#[must_use]
185pub fn is_ring<T, Add, Mul>(elements: &[T], add: Add, multiply: Mul) -> bool
186where
187 T: Copy + PartialEq,
188 Add: Fn(T, T) -> T + Copy,
189 Mul: Fn(T, T) -> T + Copy,
190{
191 is_abelian_group(elements, add)
192 && is_closed_under(elements, multiply)
193 && is_associative(elements, multiply)
194 && is_distributive_over(elements, multiply, add)
195}
196
197#[cfg(test)]
198mod tests {
199 use super::{
200 has_inverses, identity_element, is_abelian_group, is_associative, is_closed_under,
201 is_commutative, is_distributive_over, is_group, is_monoid, is_ring,
202 };
203
204 #[test]
205 fn validates_modular_group_and_ring_examples() {
206 let residues = [0_u8, 1, 2];
207 let add_mod_3 = |left, right| (left + right) % 3;
208 let mul_mod_3 = |left, right| (left * right) % 3;
209
210 assert!(is_closed_under(&residues, add_mod_3));
211 assert!(is_associative(&residues, add_mod_3));
212 assert!(is_commutative(&residues, add_mod_3));
213 assert_eq!(identity_element(&residues, add_mod_3), Some(0));
214 assert!(has_inverses(&residues, add_mod_3, 0));
215 assert!(is_monoid(&residues, add_mod_3));
216 assert!(is_group(&residues, add_mod_3));
217 assert!(is_abelian_group(&residues, add_mod_3));
218 assert!(is_distributive_over(&residues, mul_mod_3, add_mod_3));
219 assert!(is_ring(&residues, add_mod_3, mul_mod_3));
220 }
221
222 #[test]
223 fn rejects_operations_that_do_not_form_the_expected_structures() {
224 let booleans = [false, true];
225 let and = |left, right| left && right;
226 let values = [0_u8, 1];
227 let add = |left, right| left + right;
228
229 assert_eq!(identity_element(&booleans, and), Some(true));
230 assert!(is_monoid(&booleans, and));
231 assert!(!is_group(&booleans, and));
232 assert!(!is_closed_under(&values, add));
233 assert!(!is_ring(&values, add, add));
234 }
235}