lattices/
algebra.rs

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