Skip to main content

lattices/
algebra.rs

1use crate::test::cartesian_power;
2
3/// Defines a monoid structure.
4///
5/// A monoid is a set of items along with an associative binary operation `f` and an identity element `zero`.
6/// The `f` operation combines two items and the `zero` element acts as the identity for `f`.
7pub fn monoid<S: PartialEq + Clone, const N: usize>(
8    items: &[S; N],
9    f: &impl Fn(S, S) -> S,
10    zero: S, // zero is the identity element of f
11) -> Result<(), &'static str> {
12    semigroup(items, f)?;
13    identity(items, f, zero)?;
14    Ok(())
15}
16
17/// Defines a semigroup structure.
18///
19/// A semigroup is a set of items along with an associative binary operation `f`.
20pub fn semigroup<S: PartialEq + Clone, const N: usize>(
21    items: &[S; N],
22    f: &impl Fn(S, S) -> S,
23) -> Result<(), &'static str> {
24    associativity(items, f)?;
25    Ok(())
26}
27
28/// Defines a semiring structure.
29///
30/// A semiring is a set of items along with two associative binary operations `f` and `g`,
31/// and two identity elements `zero` and `one`.
32/// f must be commutative and g must distribute over f.
33/// the zero of f must also be absorbing with respect to g.
34pub fn semiring<S: PartialEq + Clone, const N: usize>(
35    items: &[S; N],
36    f: &impl Fn(S, S) -> S,
37    g: &impl Fn(S, S) -> S,
38    zero: S, // zero is the identity element of f
39    one: S,  // one is the identity element of g
40) -> Result<(), &'static str> {
41    commutative_monoid(items, f, zero.clone())?;
42    monoid(items, g, one)?;
43
44    absorbing_element(items, g, zero)?;
45
46    distributive(items, f, g)?;
47
48    Ok(())
49}
50
51/// Defines a ring structure.
52///
53/// A ring is a semiring with an inverse operation on f.
54pub fn ring<S: PartialEq + Clone, const N: usize>(
55    items: &[S; N],
56    f: &impl Fn(S, S) -> S,
57    g: &impl Fn(S, S) -> S,
58    zero: S, // zero is the identity element of f
59    one: S,  // one is the identity element of g
60    b: &impl Fn(S) -> S,
61) -> Result<(), &'static str> {
62    semiring(items, f, g, zero.clone(), one)?;
63    inverse(items, f, zero, b)?;
64    Ok(())
65}
66
67/// Defines an integral domain structure.
68///
69/// An integral domain is a nonzero commutative ring with no nonzero zero divisors.
70pub fn integral_domain<S: PartialEq + Clone, const N: usize>(
71    items: &[S; N],
72    f: &impl Fn(S, S) -> S,
73    g: &impl Fn(S, S) -> S,
74    zero: S,                     // zero is the identity element of f
75    one: S,                      // one is the identity element of g
76    inverse_f: &impl Fn(S) -> S, /* the function to compute the inverse element of an element with respect to f */
77) -> Result<(), &'static str> {
78    commutative_ring(items, f, g, zero.clone(), one, inverse_f)?;
79    no_nonzero_zero_divisors(items, g, zero)?;
80    Ok(())
81}
82
83/// Defines a no-nonzero-zero-divisors property.
84///
85/// x is a nonzero divisor if xy = 0 and y is also a nonzero element.
86pub fn no_nonzero_zero_divisors<S: PartialEq + Clone, const N: usize>(
87    items: &[S; N],
88    f: &impl Fn(S, S) -> S,
89    zero: S,
90) -> Result<(), &'static str> {
91    for a in items {
92        for b in items {
93            if *a != zero && *b != zero {
94                if f(a.clone(), b.clone()) == zero {
95                    return Err("No nonzero zero divisors check failed.");
96                };
97                if f(b.clone(), a.clone()) == zero {
98                    return Err("No nonzero zero divisors check failed.");
99                };
100            }
101        }
102    }
103    Ok(())
104}
105
106/// Defines a commutative ring structure.
107///
108/// A commutative ring is a ring where the multiplication operation g is commutative.
109pub fn commutative_ring<S: PartialEq + Clone, const N: usize>(
110    items: &[S; N],
111    f: &impl Fn(S, S) -> S, // addition operation
112    g: &impl Fn(S, S) -> S, // multiplication operation
113    zero: S,                // zero is the identity element of f
114    one: S,                 // one is the identity element of g
115    inverse_f: &impl Fn(S) -> S,
116) -> Result<(), &'static str> {
117    semiring(items, f, g, zero.clone(), one)?;
118    inverse(items, f, zero, inverse_f)?;
119    commutativity(items, g)?;
120    Ok(())
121}
122
123/// Defines a field structure.
124///
125/// A field is a commutative ring where every element has a multiplicative inverse.
126pub fn field<S: PartialEq + Clone, const N: usize>(
127    items: &[S; N],
128    f: &impl Fn(S, S) -> S,
129    g: &impl Fn(S, S) -> S,
130    zero: S,                     // zero is the identity element of f
131    one: S,                      // one is the identity element of g
132    inverse_f: &impl Fn(S) -> S, /* inverse_f is the function that given x computes x' such that f(x,x') = zero. */
133    inverse_g: &impl Fn(S) -> S, /* //inverse_g is the function that given x computes x' such that g(x,x') = one. */
134) -> Result<(), &'static str> {
135    commutative_ring(items, f, g, zero.clone(), one.clone(), inverse_f)?;
136    nonzero_inverse(items, g, one, zero, inverse_g)?;
137    Ok(())
138}
139
140/// Defines a commutative monoid structure.
141///
142/// A commutative monoid is a monoid where the operation f is commutative.
143pub fn commutative_monoid<S: PartialEq + Clone, const N: usize>(
144    items: &[S; N],
145    f: &impl Fn(S, S) -> S,
146    zero: S,
147) -> Result<(), &'static str> {
148    monoid(items, f, zero)?;
149    commutativity(items, f)?;
150    Ok(())
151}
152
153/// Defines a group structure.
154///
155/// A group is a set of items along with an associative binary operation `f`
156/// an identity element `zero`
157/// and every element has an inverse element with respect to `f`
158pub fn group<S: PartialEq + Clone, const N: usize>(
159    items: &[S; N],
160    f: &impl Fn(S, S) -> S,
161    zero: S,             // zero is the identity element of f
162    b: &impl Fn(S) -> S, /* b is the function to compute the inverse element of an element with respect to f */
163) -> Result<(), &'static str> {
164    monoid(items, f, zero.clone())?;
165    inverse(items, f, zero, b)?;
166    Ok(())
167}
168
169/// Defines an abelian group structure.
170///
171/// An abelian group is a group where the operation f is commutative.
172pub fn abelian_group<S: PartialEq + Clone, const N: usize>(
173    items: &[S; N],
174    f: &impl Fn(S, S) -> S,
175    zero: S,
176    b: &impl Fn(S) -> S, /* b is the function to compute the inverse element of an element with respect to f */
177) -> Result<(), &'static str> {
178    group(items, f, zero, b)?;
179    commutativity(items, f)?;
180    Ok(())
181}
182
183// Algebraic Properties
184/// Defines the distributive property
185///
186/// a(b+c) = ab + ac
187/// and (b+c)a = ba + ca
188pub fn distributive<S: PartialEq + Clone, const N: usize>(
189    items: &[S; N],
190    f: &impl Fn(S, S) -> S,
191    g: &impl Fn(S, S) -> S,
192) -> Result<(), &'static str> {
193    left_distributes(items, f, g)?;
194    right_distributes(items, f, g)?;
195    Ok(())
196}
197
198/// Defines the left distributive property
199///
200/// a(b+c) = ab + ac
201pub fn left_distributes<S: PartialEq + Clone, const N: usize>(
202    items: &[S; N],
203    f: impl Fn(S, S) -> S,
204    g: impl Fn(S, S) -> S,
205) -> Result<(), &'static str> {
206    for [a, b, c] in cartesian_power(items) {
207        if g(a.clone(), f(b.clone(), c.clone()))
208            != f(g(a.clone(), b.clone()), g(a.clone(), c.clone()))
209        {
210            return Err("Left distributive property check failed.");
211        }
212    }
213    Ok(())
214}
215
216/// Defines the right distributive property.
217///
218/// (b+c)a = ba + ca
219pub fn right_distributes<S: PartialEq + Clone, const N: usize>(
220    items: &[S; N],
221    f: impl Fn(S, S) -> S,
222    g: impl Fn(S, S) -> S,
223) -> Result<(), &'static str> {
224    for [a, b, c] in cartesian_power(items) {
225        if g(f(b.clone(), c.clone()), a.clone())
226            != f(g(b.clone(), a.clone()), g(c.clone(), a.clone()))
227        {
228            return Err("Right distributive property check failed.");
229        }
230    }
231    Ok(())
232}
233
234/// Defines the absorbing_element property.
235///
236/// An element z is absorbing if az = z and za = z for all a.
237pub fn absorbing_element<S: PartialEq + Clone, const N: usize>(
238    items: &[S; N],
239    f: impl Fn(S, S) -> S,
240    z: S, // absorbing element (anything multiplied by z is z e.g. 0 in integers)
241) -> Result<(), &'static str> {
242    for a in items {
243        // az = z
244        if f(a.clone(), z.clone()) != z.clone() {
245            return Err("Absorbing element property check failed.");
246        }
247
248        // za = z
249        if f(z.clone(), a.clone()) != z.clone() {
250            return Err("Absorbing element property check failed.");
251        }
252    }
253    Ok(())
254}
255
256/// Defines the inverse property.
257///
258/// An element b is the inverse of a if ab = e and ba = e for some identity element e.
259pub fn inverse<S: PartialEq + Clone, const N: usize>(
260    items: &[S; N],
261    f: impl Fn(S, S) -> S,
262    e: S,               // e is the identity element of f
263    b: impl Fn(S) -> S, /* b is the function to compute the inverse element of an element with respect to f */
264) -> Result<(), &'static str> {
265    // ∃b: ab = e, ba = e
266    for a in items {
267        if f(a.clone(), b(a.clone())) != e {
268            return Err("Inverse check failed.");
269        }
270        if f(b(a.clone()), a.clone()) != e {
271            return Err("Inverse check failed.");
272        }
273    }
274    Ok(())
275}
276
277/// Defines the non_zero inverse property.
278///
279/// Every element except zero must have an inverse.
280pub fn nonzero_inverse<S: PartialEq + Clone, const N: usize>(
281    items: &[S; N],
282    f: impl Fn(S, S) -> S,
283    e: S,
284    zero: S,
285    b: impl Fn(S) -> S,
286) -> Result<(), &'static str> {
287    // ∃b: ab = e, ba = e
288    for a in items {
289        if *a != zero {
290            if f(a.clone(), b(a.clone())) != e {
291                return Err("Nonzero inverse check failed.");
292            }
293            if f(b(a.clone()), a.clone()) != e {
294                return Err("Nonzero inverse check failed.");
295            }
296        }
297    }
298    Ok(())
299}
300
301/// Defines the identity property.
302///
303/// An element e is the identity of f if ae = a and ea = a for all a.
304pub fn identity<S: PartialEq + Clone, const N: usize>(
305    items: &[S; N],
306    f: impl Fn(S, S) -> S,
307    e: S,
308) -> Result<(), &'static str> {
309    // ea = a, ae = a
310    for a in items {
311        if f(e.clone(), a.clone()) != a.clone() {
312            return Err("Left Identity check failed.");
313        }
314        if f(a.clone(), e.clone()) != a.clone() {
315            return Err("Right Identity check failed.");
316        }
317    }
318    Ok(())
319}
320
321/// Defines the associativity property.
322///
323/// a(bc) = (ab)c
324pub fn associativity<S: PartialEq + Clone, const N: usize>(
325    items: &[S; N],
326    f: impl Fn(S, S) -> S,
327) -> Result<(), &'static str> {
328    for [a, b, c] in cartesian_power(items) {
329        if f(a.clone(), f(b.clone(), c.clone())) != // f(a, f(b,c)) ie a + (b + c)
330            f(f(a.clone(), b.clone()), c.clone())
331        // f(f(a,b),c) ie (a + b) + c
332        {
333            return Err("Associativity check failed.");
334        }
335    }
336    Ok(())
337}
338
339/// Defines the commutativity property.
340///
341/// xy = yx
342pub fn commutativity<S: PartialEq + Clone, const N: usize>(
343    items: &[S; N],
344    f: impl Fn(S, S) -> S,
345) -> Result<(), &'static str> {
346    for [x, y] in cartesian_power(items) {
347        if f(x.clone(), y.clone()) != f(y.clone(), x.clone()) {
348            // a + b = b + a
349            return Err("Commutativity check failed.");
350        }
351    }
352    Ok(())
353}
354
355/// Defines the idempotency property.
356///
357/// xx = x
358pub fn idempotency<S: PartialEq + Clone, const N: usize>(
359    items: &[S; N],
360    f: impl Fn(S, S) -> S,
361) -> Result<(), &'static str> {
362    for x in items {
363        if f(x.clone(), x.clone()) != x.clone() {
364            return Err("Idempotency check failed.");
365        }
366    }
367    Ok(())
368}
369
370/// Defines the linearity property
371///
372/// q is linear with respect to some group operation + if q(a+b) = q(a) + q(b)
373/// This is the same as q being a group homomorphism
374/// As defined in the paper "DBSP: Automatic Incremental View Maintenance for Rich Query Languages"
375/// Input parameters f, g, and q represent (f) the base operation of the algebraic structure for state,
376/// (g) the base operation of the algebraic structure the query q outputs to
377/// and (q) the query over f that we want to check for linearity (to incrementalize) respectively
378pub fn linearity<S: PartialEq + Clone, R: PartialEq + Clone>(
379    items: &[S],
380    f: impl Fn(S, S) -> S,
381    g: impl Fn(R, R) -> R,
382    q: impl Fn(S) -> R,
383) -> Result<(), &'static str> {
384    for [a, b] in cartesian_power(items) {
385        if q(f(a.clone(), b.clone())) != g(q(b.clone()), q(a.clone())) {
386            // q(f(a,b)) = f(q(a), q(b))
387            return Err("Linearity check failed.");
388        }
389    }
390    Ok(())
391}
392
393/// Defines the bilinearity property
394///
395/// q is bilinear with respect to + if q(a + b, c) = q(a,c) + q(b,c) and q(a,c + d) = q(a,c) + q(a,d)
396/// This is the same as q being distributive over the addition operation of the three groups S, T, and R in q:S x T --> R
397/// As defined in the paper "DBSP: Automatic Incremental View Maintenance for Rich Query Languages
398/// Input parameters f, h, g, and q represent (f) the base operation of the algebraic structure on the left input to the query q,
399/// (h) the base operation of the algebraic structure on the right input to the query q,
400/// (g) the base operation of the algebraic structure the query q outputs to,
401/// and (q) The query over (f,g) that we want to check for bilinearity (to incrementalize)
402pub fn bilinearity<S: PartialEq + Clone, R: PartialEq + Clone, T: PartialEq + Clone>(
403    items_f: &[S],
404    items_h: &[T],
405    f: impl Fn(S, S) -> S,
406    h: impl Fn(T, T) -> T,
407    g: impl Fn(R, R) -> R,
408    q: impl Fn(S, T) -> R,
409) -> Result<(), &'static str> {
410    for [a, b] in cartesian_power(items_f) {
411        for [c, d] in cartesian_power(items_h) {
412            if q(f(a.clone(), b.clone()), c.clone())
413                != g(q(a.clone(), c.clone()), q(b.clone(), c.clone()))
414                || q(a.clone(), h(c.clone(), d.clone()))
415                    != g(q(a.clone(), c.clone()), q(a.clone(), d.clone()))
416            {
417                // q(a + b, c) = q(a,c) + q(b,c) AND
418                // q(a,c + d) = q(a,c + q(c,d)
419                return Err("Bilinearity check failed.");
420            }
421        }
422    }
423    Ok(())
424}
425
426// Functions for testing out whether user defined code satisfies different properties
427
428// A list of algebraic properties of a single function that we support
429// static SINGLE_FUNCTION_PROPERTIES: [(&str, fn(&[S; N], impl Fn(S, S) -> S)); 6] = [
430//     ("associativity", associativity),
431//     ("commutativity", commutativity),
432//     ("idempotency", idempotency),
433//     ("identity", identity),
434//     ("inverse", inverse),
435//     ("absorbing_element", absorbing_element)];
436
437/// Loop through each algebraic property in SINGLE_FUNCTION_PROPERTIES and test for them.
438#[cfg(feature = "alloc")]
439pub fn get_single_function_properties<S: PartialEq + Clone, const N: usize>(
440    items: &[S; N],
441    f: impl Fn(S, S) -> S,
442    // identity element (TODO make optional parameter)
443    e: S,
444    // inverse function (TODO make optional parameter)
445    b: impl Fn(S) -> S,
446    // absorbing element (TODO make optional parameter)
447    z: S,
448) -> alloc::vec::Vec<&'static str> {
449    // store the list of properties (strings) that are satisfied to be returned
450    let mut properties_satisfied = alloc::vec::Vec::new();
451
452    // TODO make this a loop through the SINGLE_FUNCTION_PROPERTIES array
453    if associativity(items, &f).is_ok() {
454        properties_satisfied.push("associativity");
455    }
456    if commutativity(items, &f).is_ok() {
457        properties_satisfied.push("commutativity");
458    }
459    if idempotency(items, &f).is_ok() {
460        properties_satisfied.push("idempotency");
461    }
462    if identity(items, &f, e.clone()).is_ok() {
463        properties_satisfied.push("identity");
464    }
465    if inverse(items, &f, e, b).is_ok() {
466        properties_satisfied.push("inverse");
467    }
468    if absorbing_element(items, &f, z).is_ok() {
469        properties_satisfied.push("absorbing_element");
470    }
471
472    properties_satisfied
473}
474
475// TODO write a function to take in a set of functions and check which pairs satisfy different pairwise properties (e.g. distributivity
476
477// Tests
478
479#[cfg(test)]
480mod test {
481    use std::collections::HashSet;
482
483    use crate::algebra::*;
484
485    static TEST_ITEMS: &[u32; 14] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
486    static TEST_ITEMS_NONZERO: &[u32; 13] = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
487    static TEST_MOD_PRIME_7: &[u32; 7] = &[0, 1, 2, 3, 4, 5, 6];
488    static TEST_BOOLS: &[bool; 2] = &[false, true];
489
490    #[test]
491    fn test_associativity() {
492        // Test that max() is associative and exponentiation isn't
493        assert!(associativity(TEST_ITEMS, u32::max).is_ok());
494        assert!(associativity(TEST_ITEMS, u32::wrapping_pow).is_err());
495    }
496
497    #[test]
498    fn test_left_distributes() {
499        // Test that multiplication and subtraction are left distributive  a(b-c) = ab - ac.
500        // but exponentiation and subtraction isn't since a^(b-c) != a^b - a^c.
501        assert!(left_distributes(TEST_ITEMS, u32::wrapping_sub, u32::wrapping_mul).is_ok());
502        assert!(left_distributes(TEST_ITEMS, u32::wrapping_sub, u32::wrapping_pow).is_err());
503    }
504
505    #[test]
506    fn test_right_distributes() {
507        // Test that multiplication and subtraction are right distributive (b-c)a = ba - ca.
508        // but exponentiation and subtraction isn't since (b-c)^a != b^a - c^a.
509        assert!(right_distributes(TEST_ITEMS, u32::wrapping_sub, u32::wrapping_mul).is_ok());
510        assert!(right_distributes(TEST_ITEMS, u32::wrapping_sub, u32::wrapping_pow).is_err());
511    }
512
513    #[test]
514    fn test_nonzero_inverse() {
515        // Test that addition and subtraction has a nonzero inverse and that multiplication does not.
516        assert!(
517            nonzero_inverse(TEST_ITEMS, u32::wrapping_add, 0, 0, |x| {
518                0u32.wrapping_sub(x)
519            })
520            .is_ok()
521        );
522        assert!(
523            nonzero_inverse(TEST_ITEMS, u32::wrapping_sub, 0, 0, |x| {
524                0u32.wrapping_add(x)
525            })
526            .is_ok()
527        );
528        assert!(
529            right_distributes(TEST_ITEMS_NONZERO, u32::wrapping_div, u32::wrapping_mul).is_err()
530        );
531    }
532
533    #[test]
534    fn test_idempotency() {
535        // Test that max() is idempotent and addition is non-idempotent
536        assert!(idempotency(TEST_ITEMS, u32::max).is_ok());
537
538        assert!(idempotency(TEST_ITEMS, u32::wrapping_add).is_err());
539    }
540
541    #[test]
542    fn test_commutativity() {
543        // Test that max() is commutative and division is non-commutative
544        assert!(commutativity(TEST_ITEMS, u32::max).is_ok());
545        assert!(commutativity(TEST_ITEMS_NONZERO, u32::wrapping_div).is_err());
546        // Test items non-zero to avoid a divide by zero exception
547    }
548
549    #[test]
550    fn test_commutative_ring() {
551        // Test that (Z, +, *) is a commutative ring.
552        assert!(
553            commutative_ring(
554                TEST_ITEMS,
555                &u32::wrapping_add,
556                &u32::wrapping_mul,
557                0,
558                1,
559                &|x| 0u32.wrapping_sub(x),
560            )
561            .is_ok()
562        );
563
564        // Test that (Z, +, ^) is not a commutative ring.
565        assert!(
566            commutative_ring(
567                TEST_ITEMS,
568                &u32::wrapping_add,
569                &u32::wrapping_pow,
570                0,
571                1,
572                &|x| 0u32.wrapping_sub(x),
573            )
574            .is_err()
575        );
576
577        // Test that matrix multiplication is not a commutative ring.
578        assert!(
579            commutative_ring(
580                &[[[1, 2], [3, 4]], [[5, 6], [7, 8]], [[9, 10], [11, 12]]],
581                &|a, b| {
582                    [
583                        [a[0][0] + b[0][0], a[0][1] + b[0][1]],
584                        [a[1][0] + b[1][0], a[1][0] + b[1][1]],
585                    ]
586                },
587                &|a, b| {
588                    [
589                        [
590                            a[0][0] * b[0][0] + a[0][1] * b[1][0],
591                            a[0][0] * b[0][1] + a[0][1] * b[1][1],
592                        ],
593                        [
594                            a[1][0] * b[0][0] + a[1][1] * b[1][0],
595                            a[1][0] * b[0][1] + a[1][1] * b[1][1],
596                        ],
597                    ]
598                },
599                [[0, 0], [0, 0]],
600                [[1, 0], [0, 1]],
601                &|a| {
602                    [
603                        [
604                            -a[0][0] / (a[0][0] * a[1][1] - a[0][1] * a[1][1]),
605                            -a[0][1] / (a[0][0] * a[1][1] - a[0][1] * a[1][1]),
606                        ],
607                        [
608                            -a[1][0] / (a[0][0] * a[1][1] - a[0][1] * a[1][1]),
609                            -a[1][1] / (a[0][0] * a[1][1] - a[0][1] * a[1][1]),
610                        ],
611                    ]
612                },
613            )
614            .is_err()
615        );
616    }
617
618    #[test]
619    fn test_commutative_monoid() {
620        // Test that (Z, +) is commutative monoid since every abelian group is commutative monoid.
621        assert!(commutative_monoid(TEST_ITEMS, &u32::wrapping_add, 0).is_ok());
622
623        // Test that  set of natural numbers N = {0, 1, 2, ...} is a commutative monoid under addition (identity element 0) or multiplication (identity element 1).
624        assert!(commutative_monoid(TEST_ITEMS, &u32::wrapping_mul, 1).is_ok());
625        assert!(commutative_monoid(TEST_ITEMS, &u32::wrapping_add, 0).is_ok());
626
627        // Test that ({true, false}, ∧) is a commutative monoid with identity element true.
628        assert!(commutative_monoid(TEST_BOOLS, &|a, b| a & b, true).is_ok()); // logical AND
629
630        // Test that (Z, -) is not a commutative monoid.
631        assert!(commutative_monoid(TEST_ITEMS, &u32::wrapping_sub, 0).is_err());
632
633        // Test that (N, +) is not a commutative monoid since it doesn't have an identity element (0 is missing).
634        assert!(commutative_monoid(TEST_ITEMS_NONZERO, &u32::wrapping_add, 1).is_err()); // Note that 1 is an arbitrary identity element in TEST_ITEMS_NONZERO since it doesn't have an identity element 0.
635
636        // Test that (Z, ^) is not a commutative monoid.
637        assert!(commutative_monoid(TEST_ITEMS, &u32::wrapping_pow, 3).is_err());
638    }
639
640    #[test]
641    fn test_semigroup() {
642        // Test that N := {1, 2, . . .} together with addition is a semigroup.
643        assert!(semigroup(TEST_ITEMS_NONZERO, &u32::wrapping_add).is_ok());
644        // Test that set of all natural numbers N = {0, 1, 2, ...} is a semigroup under addition.
645        assert!(semigroup(TEST_ITEMS, &u32::wrapping_add).is_ok());
646        // Test that set of all natural numbers N = {0, 1, 2, ...} is a semigroup under multiplication.
647        assert!(semigroup(TEST_ITEMS, &u32::wrapping_mul).is_ok());
648        // Test that ({true, false}, ∧) is a semigroup.
649        assert!(semigroup(TEST_BOOLS, &|a, b| a & b).is_ok()); // logical AND
650        // Test that matrix multiplication is a semigroup.
651        assert!(
652            semigroup(
653                &[[[1, 2], [3, 4]], [[5, 6], [7, 8]], [[9, 10], [11, 12]]],
654                &|a, b| {
655                    [
656                        [
657                            a[0][0] * b[0][0] + a[0][1] * b[1][0],
658                            a[0][0] * b[0][1] + a[0][1] * b[1][1],
659                        ],
660                        [
661                            a[1][0] * b[0][0] + a[1][1] * b[1][0],
662                            a[1][0] * b[0][1] + a[1][1] * b[1][1],
663                        ],
664                    ]
665                },
666            )
667            .is_ok()
668        );
669        // Test that set of all natural numbers N = {0, 1, 2, ...} is not a semigroup under exponentiation.
670        assert!(semigroup(TEST_ITEMS, &u32::wrapping_pow).is_err());
671    }
672
673    #[test]
674    fn test_identity() {
675        // Test that 0 is the identity for addition and 5 is not
676        assert!(identity(TEST_ITEMS, u32::wrapping_add, 0).is_ok());
677
678        assert!(identity(TEST_ITEMS, u32::wrapping_add, 5).is_err());
679    }
680
681    #[test]
682    fn test_inverse() {
683        // Test that subtraction is the inverse of addition and that addition is not the inverse of addition
684        assert!(inverse(TEST_ITEMS, u32::wrapping_add, 0, |x| 0u32.wrapping_sub(x)).is_ok());
685
686        assert!(inverse(TEST_ITEMS, u32::wrapping_add, 0, |x| 0u32.wrapping_add(x)).is_err());
687    }
688
689    #[test]
690    fn test_distributive() {
691        // Test that addition and multiplication are distributive and that addition and max() are not
692        assert!(distributive(TEST_ITEMS, &u32::wrapping_add, &u32::wrapping_mul).is_ok());
693        assert!(distributive(TEST_ITEMS, &u32::wrapping_add, &u32::max).is_err());
694    }
695
696    #[test]
697    fn test_linearity() {
698        // Test that multiplication over the (Z,+) group is linear
699        // but exponentiation over the (Z,+) group is not linear
700        assert!(
701            linearity(TEST_ITEMS, u32::wrapping_add, u32::wrapping_add, |x| {
702                u32::wrapping_mul(x, 5)
703            })
704            .is_ok()
705        );
706        assert!(
707            linearity(TEST_ITEMS, u32::wrapping_add, u32::wrapping_add, |x| {
708                u32::pow(x, 5)
709            })
710            .is_err()
711        );
712    }
713
714    #[test]
715    fn test_bilinearity() {
716        // Test that multiplication over the (Z,+) group is bilinear
717        // but exponentiation over the (Z,+) group is not bilinear
718        assert!(
719            bilinearity(
720                TEST_ITEMS,
721                TEST_ITEMS,
722                u32::wrapping_add,
723                u32::wrapping_add,
724                u32::wrapping_add,
725                u32::wrapping_mul
726            )
727            .is_ok()
728        );
729        assert!(
730            bilinearity(
731                TEST_ITEMS,
732                TEST_ITEMS,
733                u32::wrapping_add,
734                u32::wrapping_add,
735                u32::wrapping_add,
736                u32::pow
737            )
738            .is_err()
739        );
740    }
741
742    #[test]
743    fn test_group() {
744        // Test that (Z, +) form a group.
745        assert!(group(TEST_ITEMS, &u32::wrapping_add, 0, &|x| 0u32.wrapping_sub(x)).is_ok());
746        // Test that (Z/7Z, +) form a group.
747        assert!(group(TEST_MOD_PRIME_7, &modulo_add_7, 0, &modulo_sub_7).is_ok());
748        // Test that (Z/14Z, +) form a group.
749        assert!(group(TEST_ITEMS, &modulo_add_14, 0, &modulo_sub_14).is_ok());
750        // Test that (Z, *) do not form a group since it has no inverse.
751        assert!(
752            group(TEST_ITEMS_NONZERO, &u32::wrapping_mul, 1, &|x| 1u32
753                .wrapping_div(x))
754            .is_err()
755        );
756    }
757
758    #[test]
759    fn test_abelian_group() {
760        // Test that (Z, +) form an abelian group.
761        assert!(
762            abelian_group(TEST_ITEMS, &u32::wrapping_add, 0, &|x| 0u32.wrapping_sub(x)).is_ok()
763        );
764        // Test that (Z/7Z, +) form an abelian group.
765        assert!(abelian_group(TEST_MOD_PRIME_7, &modulo_add_7, 0, &modulo_sub_7).is_ok());
766        // Test that (Z, *) do not form an abelian group.
767        assert!(
768            abelian_group(TEST_ITEMS_NONZERO, &u32::wrapping_mul, 1, &|x| 1u32
769                .wrapping_div(x))
770            .is_err()
771        );
772        // Test that matrix multiplication is not an abelian group.
773        assert!(
774            abelian_group(
775                &[[[1, 2], [3, 4]], [[5, 6], [7, 8]], [[9, 10], [11, 12]]],
776                &|a, b| {
777                    [
778                        [
779                            a[0][0] * b[0][0] + a[0][1] * b[1][0],
780                            a[0][0] * b[0][1] + a[0][1] * b[1][1],
781                        ],
782                        [
783                            a[1][0] * b[0][0] + a[1][1] * b[1][0],
784                            a[1][0] * b[0][1] + a[1][1] * b[1][1],
785                        ],
786                    ]
787                },
788                [[1, 0], [0, 1]],
789                &|a| {
790                    [
791                        [
792                            -a[0][0] / (a[0][0] * a[1][1] - a[0][1] * a[1][1]),
793                            -a[0][1] / (a[0][0] * a[1][1] - a[0][1] * a[1][1]),
794                        ],
795                        [
796                            -a[1][0] / (a[0][0] * a[1][1] - a[0][1] * a[1][1]),
797                            -a[1][1] / (a[0][0] * a[1][1] - a[0][1] * a[1][1]),
798                        ],
799                    ]
800                },
801            )
802            .is_err()
803        );
804    }
805
806    #[test]
807    fn test_monoid() {
808        // Test that N = {0, 1, 2, . . .} is a monoid with respect to addition
809        assert!(monoid(TEST_ITEMS, &u32::wrapping_add, 0).is_ok());
810        // Test that N+ = N − {0} and N are both monoids with respect to multiplication
811        assert!(monoid(TEST_ITEMS_NONZERO, &u32::wrapping_mul, 1).is_ok());
812        assert!(monoid(TEST_ITEMS, &u32::wrapping_mul, 1).is_ok());
813        // Test that the set of nxn matrix with matrix multiplication is a monoid.
814        assert!(
815            monoid(
816                &[[[1, 2], [3, 4]], [[5, 6], [7, 8]], [[9, 10], [11, 12]]],
817                &|a, b| {
818                    [
819                        [
820                            a[0][0] * b[0][0] + a[0][1] * b[1][0],
821                            a[0][0] * b[0][1] + a[0][1] * b[1][1],
822                        ],
823                        [
824                            a[1][0] * b[0][0] + a[1][1] * b[1][0],
825                            a[1][0] * b[0][1] + a[1][1] * b[1][1],
826                        ],
827                    ]
828                },
829                [[1, 0], [0, 1]],
830            )
831            .is_ok()
832        );
833        // Test that N+ = N − {0} is not a monoid with respect to addition since it doesn't have an identity element (0 is missing).
834        assert!(monoid(TEST_ITEMS_NONZERO, &u32::wrapping_add, 1).is_err());
835    }
836
837    #[test]
838    fn test_absorbing() {
839        // Test that 0 is absorbing for multiplication and 5 is not
840        assert!(absorbing_element(TEST_ITEMS, u32::wrapping_mul, 0).is_ok());
841        assert!(absorbing_element(TEST_ITEMS, u32::wrapping_mul, 5).is_err());
842    }
843
844    // Performs addition modulo 7, ensuring the result remains within the range of 0 to 6.
845    // This function is used to compute addition modulo 7 within the context of testing integral domains.
846    fn modulo_add_7(a: u32, b: u32) -> u32 {
847        u32::wrapping_add(a, b) % 7
848    }
849
850    // Performs addition modulo 14, ensuring the result remains within the range of 0 to 13.
851    // This function is used to compute addition modulo 14 within the context of testing integral domains.
852    fn modulo_add_14(a: u32, b: u32) -> u32 {
853        u32::wrapping_add(a, b) % 14
854    }
855
856    // Performs subtraction modulo 7, ensuring the result remains within the range of 0 to 6.
857    // This function is used to compute subtraction modulo 7 within the context of testing integral domains.
858    fn modulo_sub_7(a: u32) -> u32 {
859        u32::wrapping_sub(7, a) % 7
860    }
861
862    // Performs subtraction modulo 14, ensuring the result remains within the range of 0 to 13.
863    // This function is used to compute subtraction modulo 14 within the context of testing integral domains.
864    fn modulo_sub_14(a: u32) -> u32 {
865        u32::wrapping_sub(14, a) % 14
866    }
867
868    // Performs multiplication modulo 7, ensuring the result remains within the range of 0 to 6.
869    // This function is used to compute multiplication modulo 7 within the context of testing integral domains.
870    fn modulo_mult_7(a: u32, b: u32) -> u32 {
871        u32::wrapping_mul(a, b) % 7
872    }
873
874    // Performs multiplication modulo 14, ensuring the result remains within the range of 0 to 13.
875    // This function is used to compute multiplication modulo 14 within the context of testing integral domains.
876    fn modulo_mult_14(a: u32, b: u32) -> u32 {
877        u32::wrapping_mul(a, b) % 14
878    }
879
880    #[test]
881    fn test_additive_inverse_7() {
882        // Tests that the additive inverse of each element in the ring of integers modulo 7 is correct.
883        assert_eq!(0, modulo_sub_7(0));
884        assert_eq!(1, modulo_sub_7(6));
885        assert_eq!(2, modulo_sub_7(5));
886        assert_eq!(3, modulo_sub_7(4));
887        assert_eq!(4, modulo_sub_7(3));
888        assert_eq!(6, modulo_sub_7(1));
889    }
890
891    #[test]
892    fn test_modulo_mu14() {
893        // Tests that the multiplication modulo 14 is correct.
894        assert_eq!(0, modulo_mult_14(2, 7));
895        assert_eq!(3, modulo_mult_14(1, 3));
896        assert_eq!(2, modulo_mult_14(2, 1));
897        assert_eq!(3, modulo_mult_14(3, 1));
898        assert_eq!(4, modulo_mult_14(2, 2));
899        assert_eq!(6, modulo_mult_14(2, 3));
900        assert_eq!(9, modulo_mult_14(3, 3));
901    }
902    #[test]
903    fn test_modulo_mu7() {
904        // Tests that the multiplication modulo 7 is correct.
905        assert_eq!(0, modulo_mult_7(0, 0));
906        assert_eq!(3, modulo_mult_7(1, 3));
907        assert_eq!(2, modulo_mult_7(2, 1));
908        assert_eq!(2, modulo_mult_7(3, 3));
909        assert_eq!(2, modulo_mult_7(3, 3));
910        assert_eq!(5, modulo_mult_7(3, 4));
911        assert_eq!(1, modulo_mult_7(3, 5));
912    }
913
914    #[test]
915    fn test_no_nonzero_zero_divisors() {
916        // The ring of integer mod prime number has no nonzero zero divisors.
917        assert!(no_nonzero_zero_divisors(TEST_MOD_PRIME_7, &modulo_mult_7, 0).is_ok());
918        // The ring of integers with multiplication mod prime number has nonzero zero divisors. (e.g. 1 * 7 = 0 mod 7)
919        assert!(no_nonzero_zero_divisors(TEST_ITEMS, &modulo_mult_7, 0).is_err());
920    }
921
922    #[test]
923    fn test_integral_domain() {
924        // The ring of integers modulo a prime number is an integral domain.
925        assert!(
926            integral_domain(
927                TEST_MOD_PRIME_7,
928                &modulo_add_7,
929                &modulo_mult_7,
930                0,
931                1,
932                &modulo_sub_7,
933            )
934            .is_ok()
935        );
936        // The ring of integers modulo a composite number is not an integral domain.
937        assert!(
938            integral_domain(
939                TEST_ITEMS,
940                &modulo_add_14,
941                &modulo_mult_14,
942                0,
943                1,
944                &modulo_sub_14,
945            )
946            .is_err()
947        );
948    }
949
950    #[test]
951    fn test_field() {
952        // Test that GF2 (0, 1, XOR, AND) is a field and  +, x, 0, 1, - is not a field (no multiplicative inverses)
953        // Note GF2 is the Galois Field with 2 elements.
954
955        assert!(
956            field(
957                TEST_BOOLS,
958                &|a, b| a ^ b, // logical XOR
959                &|a, b| a & b, // a & b, // logical AND
960                false,
961                true,
962                &|x| x, // XOR(x,x) = false, the identity for XOR
963                &|_x| true /* AND(x,true) = true, the identity for AND. Note that the inverse doesn't need to work for the additive identity (false)
964                            */
965            )
966            .is_ok()
967        );
968
969        assert!(
970            field(
971                TEST_ITEMS,
972                &u32::wrapping_add,
973                &u32::wrapping_mul,
974                0,
975                1,
976                &|x| 0u32.wrapping_sub(x),
977                &|x| 0u32.wrapping_sub(x) //Note there is no valid inverse function for multiplication over the integers so we just pick some function
978            )
979            .is_err()
980        );
981    }
982
983    #[test]
984    fn test_ring() {
985        // Test that +, x, 0, 1, - are a ring and +, x, 0, 5 are not (5 isn't a multiplicative identity)
986        assert!(
987            ring(
988                TEST_ITEMS,
989                &u32::wrapping_add,
990                &u32::wrapping_mul,
991                0,
992                1,
993                &|x| 0u32.wrapping_sub(x),
994            )
995            .is_ok()
996        );
997        assert!(
998            ring(
999                TEST_ITEMS,
1000                &u32::wrapping_add,
1001                &u32::wrapping_mul,
1002                0,
1003                5,
1004                &|x| 0u32.wrapping_sub(x),
1005            )
1006            .is_err()
1007        );
1008    }
1009
1010    #[cfg(feature = "alloc")]
1011    #[test]
1012    fn test_semiring() {
1013        use alloc::borrow::ToOwned;
1014        use alloc::format;
1015
1016        // Test +, x is a semiring
1017        assert!(semiring(TEST_ITEMS, &u32::wrapping_add, &u32::wrapping_mul, 0, 1).is_ok());
1018
1019        // Test boolean semiring with AND as + and OR as x
1020        assert!(semiring(&[false, true], &|x, y| x | y, &|x, y| x & y, false, true).is_ok());
1021
1022        // Test min plus semiring. + is min and x is plus. Also known as the "tropical semiring"
1023        assert!(
1024            semiring(
1025                &[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, f64::INFINITY],
1026                &f64::min,
1027                &|x, y| x + y,
1028                f64::INFINITY,
1029                0.0,
1030            )
1031            .is_ok()
1032        );
1033
1034        // Test max plus semiring. + is max and x is plus.
1035        assert!(
1036            semiring(
1037                &[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, f64::NEG_INFINITY],
1038                &f64::max,
1039                &|x, y| x + y,
1040                f64::NEG_INFINITY,
1041                0.0,
1042            )
1043            .is_ok()
1044        );
1045
1046        // Test sets of strings semiring with union as + and concatenation as x
1047        assert!(
1048            semiring(
1049                &[
1050                    HashSet::from([]),
1051                    HashSet::from(["".to_owned()]),
1052                    HashSet::from(["a".to_owned()]),
1053                    HashSet::from(["aa".to_owned(), "bb".to_owned()]),
1054                    HashSet::from(["ab".to_owned(), "bb".to_owned(), "cc".to_owned()]),
1055                    HashSet::from(["ba".to_owned()]),
1056                    HashSet::from(["bb".to_owned()]),
1057                ],
1058                &|x, y| x.union(&y).cloned().collect(),
1059                &|x, y| {
1060                    let mut new_set = HashSet::new();
1061
1062                    #[expect(
1063                        clippy::disallowed_methods,
1064                        reason = "nondeterministic iteration order, fine to collect into set"
1065                    )]
1066                    for a in x.iter() {
1067                        for b in y.iter() {
1068                            new_set.insert(format!("{a}{b}"));
1069                        }
1070                    }
1071                    new_set
1072                },
1073                HashSet::from([]),
1074                HashSet::from(["".to_owned()]),
1075            )
1076            .is_ok()
1077        );
1078    }
1079
1080    #[test]
1081    fn test_get_single_function_properties() {
1082        // Test that get single function properties on addition returns associative, commutative, identity, and inverses.
1083        let test_properties_satisfied = get_single_function_properties(
1084            TEST_ITEMS,
1085            u32::wrapping_add,
1086            0,
1087            |x| 0u32.wrapping_sub(x),
1088            0,
1089        );
1090        let correct_properties = &["associativity", "commutativity", "identity", "inverse"];
1091        assert_eq!(test_properties_satisfied, correct_properties);
1092
1093        // Test that get single function properties on max returns associative, commutative, idempotent, identity, and absorbing element.
1094        let test_properties_satisfied = get_single_function_properties(
1095            &[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, f64::INFINITY],
1096            f64::max,
1097            0.0,
1098            |x| x,
1099            f64::INFINITY,
1100        );
1101        let correct_properties = &[
1102            "associativity",
1103            "commutativity",
1104            "idempotency",
1105            "identity",
1106            "absorbing_element",
1107        ];
1108        assert_eq!(test_properties_satisfied, correct_properties);
1109
1110        // Define a function that takes in two u32s and returns the first one
1111        let f = |x: u32, _y: u32| x;
1112        let test_properties_satisfied =
1113            get_single_function_properties(TEST_ITEMS, f, 0, |x| 0u32.wrapping_sub(x), 0);
1114        let correct_properties = &["associativity", "idempotency"];
1115        assert_eq!(test_properties_satisfied, correct_properties);
1116    }
1117}