Created
June 20, 2026 21:46
-
-
Save hacklex/ba6bd5d4e54ebff7947098d68de8e8a9 to your computer and use it in GitHub Desktop.
test for TC-based modular arithmetic
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| module Core.Modular.Test | |
| (* | |
| Diamond-free foundational algebra tower for CuteCAS. | |
| Design principles (see core/AGENTS.md §1): | |
| - Bundle fields tagged [@@@TC.no_method] so TC search never goes | |
| through them; resolution flows through explicit top-level | |
| `instance` declarations only. | |
| - Forest invariant: exactly one declared instance per ordered | |
| (Source, Target) class pair. Multi-step climbs compose | |
| automatically; skip-level instances are forbidden. | |
| - Marker classes (mul_is_commutative, mul_is_group) take the | |
| underlying structure as an explicit dependent param so coherence | |
| is statically forced. | |
| - Plain `instance` only; no `unfold instance` on records. | |
| *) | |
| module TC = FStar.Tactics.Typeclasses | |
| class equatable (t:Type) = { | |
| eq: t -> t -> bool; | |
| reflexivity: (x:t) -> Lemma (eq x x); | |
| symmetry: (x:t) -> (y:t) -> Lemma (eq x y <==> eq y x); | |
| transitivity: (x:t) -> (y:t) -> (z:t) -> | |
| Lemma (requires eq x y /\ eq y z) (ensures eq x z); | |
| } | |
| instance default_equatable (t:eqtype) : equatable t = { | |
| eq = Prims.op_Equality; | |
| reflexivity = (fun _ -> ()); | |
| symmetry = (fun _ _ -> ()); | |
| transitivity = (fun _ _ _ -> ()) | |
| } | |
| class add_comm_group (t:Type) = { | |
| [@@@TC.no_method] acg_eq: equatable t; | |
| zero: t; | |
| add: t -> t -> t; | |
| add_congruence: (a:t) -> (b:t) -> (x:t) -> (y:t) -> | |
| Lemma (requires eq a x /\ eq b y) | |
| (ensures add a b `eq` add x y); | |
| add_commutativity: (a:t) -> (b:t) -> | |
| Lemma (add a b `eq` add b a); | |
| add_associativity: (a:t) -> (b:t) -> (c:t) -> | |
| Lemma ((add (add a b) c) `eq` (add a (add b c))); | |
| add_zero: (x:t) -> | |
| Lemma ((eq (add x zero) x) /\ (eq (add zero x) x)); | |
| neg: t -> t; | |
| neg_congruence: (a:t) -> (b:t) -> | |
| Lemma (requires eq a b) (ensures eq (neg a) (neg b)); | |
| add_negation: (x:t) -> | |
| Lemma ((eq (add (neg x) x) zero) /\ | |
| (eq (add x (neg x)) zero)); | |
| } | |
| unfold instance eq_of_acg (t:Type) {| acg: add_comm_group t |} : equatable t = acg.acg_eq | |
| class ring (t:Type) = { | |
| [@@@TC.no_method] r_add: add_comm_group t; | |
| one: t; | |
| mul: t -> t -> t; | |
| mul_congruence: (a:t) -> (b:t) -> (x:t) -> (y:t) -> | |
| Lemma (requires eq a x /\ eq b y) | |
| (ensures eq (mul a b) (mul x y)); | |
| mul_associativity: (a:t) -> (b:t) -> (c:t) -> | |
| Lemma (eq (mul (mul a b) c) (mul a (mul b c))); | |
| mul_one: (x:t) -> | |
| Lemma ((eq (mul x one) x) /\ (eq (mul one x) x)); | |
| left_distributivity: (x:t) -> (y:t) -> (z:t) -> | |
| Lemma (eq (mul x (add y z)) (add (mul x y) (mul x z))); | |
| right_distributivity: (x:t) -> (y:t) -> (z:t) -> | |
| Lemma (eq (mul (add y z) x) (add (mul y x) (mul z x))); | |
| } | |
| unfold instance acg_of_r (t:Type) {| r: ring t |} : add_comm_group t = r.r_add | |
| class mul_is_commutative (t:Type) {| r: ring t |} = { | |
| mul_commutativity: (x:t) -> (y:t) -> | |
| Lemma (eq (mul x y) (mul y x)); | |
| } | |
| unfold let is_nonzero (#t:Type) {| add_comm_group t |} (x:t) : bool = | |
| not (eq x zero) | |
| class mul_is_group (t:Type) {| r: ring t |} = { | |
| inv: (x:t) -> Pure t (requires is_nonzero x) | |
| (ensures fun y -> is_nonzero y); | |
| inv_congr: (a: t) -> (b: t) -> | |
| Lemma (requires is_nonzero a /\ is_nonzero b /\ eq a b) | |
| (ensures eq (inv a) (inv b)); | |
| inversion_lemma: (x: t) -> | |
| Lemma (requires is_nonzero x) | |
| (ensures (eq (mul x (inv x)) one) /\ | |
| (eq (mul (inv x) x) one)); | |
| } | |
| class domain (t:Type) = { | |
| [@@@TC.no_method] d_r: ring t; | |
| domain_law: (x:t) -> (y:t) -> | |
| Lemma ((eq (mul x y) zero) <==> | |
| ((eq x zero) \/ (eq y zero))); | |
| } | |
| unfold instance r_of_d (t:Type) {| d: domain t |} : ring t = d.d_r | |
| let domain_nonzero_mul_nonzero (#t:Type) {| d: domain t |} | |
| (x y: t) | |
| : Lemma (requires is_nonzero x /\ is_nonzero y) | |
| (ensures is_nonzero (mul x y)) | |
| = d.domain_law x y | |
| class skewfield (t:Type) = { | |
| [@@@TC.no_method] sf_r: ring t; | |
| [@@@TC.no_method] sf_mig: mul_is_group t #sf_r; | |
| } | |
| unfold instance mig_of_sf (t:Type) {| sf: skewfield t |} : mul_is_group t = sf.sf_mig | |
| unfold instance r_of_sf (t:Type) {| sf: skewfield t |} : ring t = sf.sf_r | |
| private let zero_mul_z (#t:Type) {|r: ring t|} (z: t) : Lemma (eq (mul zero z) zero) | |
| = | |
| Classical.forall_intro #t reflexivity; | |
| Classical.forall_intro_2 #t symmetry; | |
| Classical.forall_intro_3 #t (Classical.move_requires_3 transitivity); | |
| let (+), ( * ), op_Minus = add, mul, neg in | |
| add_zero #t zero; | |
| mul_congruence zero z (zero + zero) z; | |
| right_distributivity z zero zero; | |
| add_congruence (zero*z) (-(zero*z)) ((zero*z)+(zero*z)) (-(zero*z)); | |
| add_negation (zero*z); | |
| add_associativity (zero*z) (zero*z) (-(zero*z)); | |
| add_congruence (zero*z) (zero*z + (-(zero*z))) (zero*z) zero; | |
| add_zero (zero*z) | |
| private let z_mul_zero (#t:Type) {|r: ring t|} (z: t) : Lemma (eq (mul z zero) zero) | |
| = | |
| Classical.forall_intro #t reflexivity; | |
| Classical.forall_intro_2 #t symmetry; | |
| Classical.forall_intro_3 #t (Classical.move_requires_3 transitivity); | |
| let (+), ( * ), op_Minus = add, mul, neg in | |
| add_zero #t zero; | |
| mul_congruence z zero z (zero + zero); | |
| left_distributivity z zero zero; | |
| add_congruence (z*zero) (-(z*zero)) ((z*zero)+(z*zero)) (-(z*zero)); | |
| add_negation (z*zero); | |
| add_associativity (z*zero) (z*zero) (-(z*zero)); | |
| add_congruence (z*zero) (z*zero + (-(z*zero))) (z*zero) zero; | |
| add_zero (z*zero) | |
| private let mul_zero (#t:Type) {|r: ring t|} (z: t) : Lemma (eq (mul z zero) zero /\ eq (mul zero z) zero) | |
| = z_mul_zero z; zero_mul_z z | |
| (* Proof that mul_is_group implies the no-zero-divisors law. | |
| Self-contained: uses only ring/equatable axiom fields directly. *) | |
| private let mul_is_group_means_domain (#t:Type) (#r: ring t) (mig: mul_is_group t #r) (x y: t) | |
| : Lemma ((eq (mul x y) zero) <==> ((eq x zero) \/ (eq y zero))) = | |
| Classical.forall_intro #t reflexivity; | |
| Classical.forall_intro_2 #t symmetry; | |
| Classical.forall_intro_3 #t (Classical.move_requires_3 transitivity); | |
| (* Step 2: backward direction — x=0 ∨ y=0 → xy=0 *) | |
| let backward () : Lemma (requires (eq x zero) \/ (eq y zero)) | |
| (ensures eq (mul x y) zero) = | |
| mul_zero x; | |
| mul_zero y; | |
| if (eq x zero) | |
| then mul_congruence x y zero y | |
| else mul_congruence x y x zero | |
| in | |
| (* Step 3: forward direction — xy=0 ∧ x≠0 → y=0 *) | |
| let forward () : Lemma (requires eq (mul x y) zero) | |
| (ensures (eq x zero) \/ (eq y zero)) = | |
| if not (eq x (zero <: t)) then ( | |
| mig.inversion_lemma x; | |
| let ix = mig.inv x in | |
| mul_associativity ix x y; | |
| mul_congruence (mul ix x) y one y; | |
| mul_one y; | |
| mul_congruence ix (mul x y) ix (zero <: t); | |
| z_mul_zero ix | |
| ) else () | |
| in | |
| Classical.move_requires backward (); | |
| Classical.move_requires forward () | |
| unfold instance d_of_sf (t:Type) {| sf: skewfield t |} : domain t = { | |
| d_r = sf.sf_r; | |
| domain_law = mul_is_group_means_domain sf.sf_mig; | |
| } | |
| class commutative_ring (t:Type) = { | |
| [@@@TC.no_method] cr_r: ring t; | |
| [@@@TC.no_method] cr_mic: mul_is_commutative t #cr_r; | |
| } | |
| unfold instance r_of_cr (t:Type) {| cr: commutative_ring t |} : ring t = cr.cr_r | |
| unfold instance mic_of_cr (t:Type) {| cr: commutative_ring t |} | |
| : mul_is_commutative t #(r_of_cr t) = cr.cr_mic | |
| class integral_domain (t:Type) = { | |
| [@@@TC.no_method] id_d: domain t; | |
| [@@@TC.no_method] id_mic: mul_is_commutative t #id_d.d_r; | |
| (* Classical theory: every integral domain has 1 ≠ 0. Baking this | |
| in (rather than as a separate marker class) since we never | |
| construct non-commutative domains or "trivial" rings as ID | |
| instances. Any field requires this too. | |
| The axiom shape is factored into the helper `one_ne_zero_axiom` | |
| so we can write it with bare names — the helper takes the domain | |
| as an explicit instance and resolves `eq`/`one`/`zero` cleanly. *) | |
| [@@@TC.no_method] id_one_ne_zero: squash(not(one `eq` zero #t)); | |
| } | |
| unfold instance d_of_id (t:Type) {| id: integral_domain t |} : domain t = id.id_d | |
| unfold instance cr_of_id (t:Type) {| id: integral_domain t |} : commutative_ring t = { | |
| cr_r = id.id_d.d_r; | |
| cr_mic = id.id_mic; | |
| } | |
| class field (t:Type) = { | |
| [@@@TC.no_method] f_sf: skewfield t; | |
| [@@@TC.no_method] f_mic: mul_is_commutative t #f_sf.sf_r; | |
| [@@@TC.no_method] f_one_ne_zero: squash (not (one `eq` zero #t)); | |
| } | |
| unfold instance sf_of_f (t:Type) {| f: field t |} : skewfield t = f.f_sf | |
| instance id_of_f (t:Type) {| f: field t |} : integral_domain t = { | |
| id_d = d_of_sf t; | |
| id_mic = f.f_mic; | |
| id_one_ne_zero = f.f_one_ne_zero; | |
| } | |
| let fin (n: pos) = x:nat{x < n} | |
| open FStar.Math.Fermat | |
| open FStar.Math.Euclid | |
| open FStar.Math.Lemmas | |
| open FStar.List.Tot | |
| module TC = FStar.Tactics.Typeclasses | |
| let residue (n: pos {n > 1}) = fin n | |
| let prime_residue (n: pos {n>1 /\ is_prime n}) = fin n | |
| let residue_add (#p:int{p > 1}) (a b: residue p) : residue p = | |
| lemma_mod_lt (a + b) p; (a + b) % p | |
| let residue_add_associativity (#p:int{p > 1}) (a b c: residue p) | |
| : Lemma (residue_add (residue_add a b) c == residue_add a (residue_add b c)) | |
| = (* ((a+b)%p + c)%p = (a+b+c)%p = (a + (b+c)%p)%p *) | |
| lemma_mod_add_distr c (a + b) p; (* (c + (a+b)%p)%p = (c+a+b)%p *) | |
| lemma_mod_add_distr a (b + c) p (* (a + (b+c)%p)%p = (a+b+c)%p *) | |
| let residue_mul (#p:int{p > 1}) (a b: residue p) : residue p = | |
| lemma_mod_lt (a * b) p; (a * b) % p | |
| let residue_neg (#n:int{n > 1}) (a: residue n) : residue n = | |
| lemma_mod_lt (n - a) n; (n - a) % n | |
| let residue_add_negation (#p:int{p > 1}) (x: residue p) | |
| : Lemma (residue_add (residue_neg x) x == 0 /\ residue_add x (residue_neg x) == 0) | |
| = (* ((p-x)%p + x) % p = (p-x+x)%p = p%p = 0 *) | |
| lemma_mod_add_distr x (p - x) p; (* (x + (p-x)%p)%p = (x+p-x)%p = p%p *) | |
| lemma_mod_add_distr x ((p - x)) p; | |
| cancel_mul_mod 1 p; (* (1*p)%p = 0 *) | |
| assert ((x + (p - x)) == 1 * p) | |
| let residue_mul_associativity (#p:int{p > 1}) (a b c: residue p) | |
| : Lemma (residue_mul (residue_mul a b) c == residue_mul a (residue_mul b c)) | |
| = (* ((a*b)%p * c)%p = (a*b*c)%p = (a * (b*c)%p)%p *) | |
| lemma_mod_mul_distr_l (a * b) c p; (* ((a*b)%p * c)%p = (a*b*c)%p *) | |
| lemma_mod_mul_distr_r a (b * c) p; (* (a * (b*c)%p)%p = (a*b*c)%p *) | |
| assert (residue_mul (residue_mul a b) c == residue_mul a (residue_mul b c)) | |
| let residue_left_distributivity (#p:int{p > 1}) (x y z: residue p) | |
| : Lemma (residue_mul x (residue_add y z) == residue_add (residue_mul x y) (residue_mul x z)) | |
| = (* x * ((y+z)%p) % p = (x*(y+z))%p = (x*y + x*z)%p | |
| = ((x*y)%p + (x*z)%p)%p *) | |
| lemma_mod_mul_distr_r x (y + z) p; | |
| modulo_distributivity (x * y) (x * z) p (* (x*y + x*z)%p = ((x*y)%p + (x*z)%p)%p *) | |
| let residue_right_distributivity (#p:int{p > 1}) (x y z: residue p) | |
| : Lemma (residue_mul (residue_add y z) x == residue_add (residue_mul y x) (residue_mul z x)) | |
| = lemma_mod_mul_distr_l (y + z) x p; | |
| modulo_distributivity (y * x) (z * x) p | |
| instance residue_ring (n: pos{n>1}) : commutative_ring (residue n) = { | |
| cr_r = { | |
| r_add = { | |
| acg_eq = default_equatable (residue n); | |
| zero = 0; | |
| add = residue_add; | |
| add_commutativity = (fun _ _ -> ()); | |
| add_associativity = residue_add_associativity; | |
| add_zero = (fun _ -> ()); | |
| neg = residue_neg; | |
| add_negation = (residue_add_negation); | |
| add_congruence = (fun _ _ _ _ -> ()); | |
| neg_congruence = (fun _ _ -> ()) | |
| }; | |
| one = 1; | |
| mul = residue_mul; | |
| mul_associativity = residue_mul_associativity; | |
| mul_one = (fun _ -> ()); | |
| left_distributivity = residue_left_distributivity; | |
| right_distributivity = residue_right_distributivity; | |
| mul_congruence = (fun _ _ _ _ -> ()) | |
| }; | |
| cr_mic = { | |
| mul_commutativity = (fun _ _ -> ()) | |
| } | |
| } | |
| (* For 0 < a < p with p prime, bezout_prime gives (r,s) with | |
| r*p + s*a = 1. Normalize s into [0,p) to get the inverse. *) | |
| let residue_inv (#p:int{is_prime p}) (a: residue p {a <> 0}) : residue p = | |
| let (r, s) = bezout_prime p a in | |
| lemma_mod_lt s p; (((s % p) + p) % p) | |
| (* The Bezout coefficient s satisfies (s * a) % p = 1. *) | |
| let residue_bezout_sa_mod (#p:int{is_prime p}) (a: residue p {a <> 0}) | |
| : Lemma (let (r, s) = bezout_prime p a in (s * a) % p == 1) | |
| = let (r, s) = bezout_prime p a in | |
| lemma_mod_plus 1 (- r) p | |
| (* (inv a * a) % p = 1 and (a * inv a) % p = 1 *) | |
| let residue_inv_correct (#p:int{is_prime p}) (a: residue p {a <> 0}) | |
| : Lemma (residue_mul (residue_inv a) a == 1 /\ residue_mul a (residue_inv a) == 1) | |
| = let (r, s) = bezout_prime p a in | |
| let ia = residue_inv a in | |
| residue_bezout_sa_mod a; | |
| lemma_mod_mul_distr_l ((s % p) + p) a p; | |
| lemma_mod_mul_distr_l s a p | |
| let residue_inv_nonzero (#p:pos{is_prime p}) (a: residue p {a <> 0}) | |
| : Lemma (residue_inv a <> 0) = residue_inv_correct a | |
| (* ---------------------------------------------------------------- *) | |
| (* mul_is_group / skewfield / field assembly *) | |
| (* ---------------------------------------------------------------- *) | |
| (* `is_nonzero x` for the residue ring unfolds to `not (eq x zero)`, i.e. | |
| `not (x = 0)`, i.e. `x <> 0`. | |
| Wrap inv at the bare element type so the lambda binder is not refined | |
| by the `<> 0` preconditions of residue_inv / residue_inv_nonzero (which would | |
| leak into the instance-argument subtyping check on `residue_ring p`). *) | |
| let residue_inv_member (p:int{is_prime p}) (x: residue p) | |
| : Pure (residue p) (requires x <> 0) (ensures fun y -> y <> 0) | |
| = residue_inv_nonzero x; residue_inv x | |
| let residue_mig (p:int{is_prime p}) : mul_is_group (residue p) = { | |
| inv = residue_inv_member p; | |
| inv_congr = (fun _ _ -> ()); | |
| inversion_lemma = (fun (x: residue p) -> residue_inv_correct x); | |
| } | |
| let residue_skewfield (p:int{is_prime p}) : skewfield (residue p) = { | |
| sf_r = (residue_ring p).cr_r; | |
| sf_mig = residue_mig p; | |
| } | |
| let residue_one_ne_zero (p:int{is_prime p}) | |
| : Lemma (not (1 = 0)) | |
| = () | |
| instance residue_field (p:int{is_prime p}) : field (prime_residue p) = | |
| { | |
| f_sf = residue_skewfield p; | |
| f_mic = (residue_ring p).cr_mic; | |
| f_one_ne_zero = (); | |
| } | |
| let test_inverse #t {|field t|} (x: t{is_nonzero x}) = inv x | |
| let solve_mig #t {|field t|} : mul_is_group t = TC.solve // mul_is_group solves without fail | |
| let test_inverse2 (#p: int{is_prime p}) (x: prime_residue p{x <> 0}) = test_inverse x | |
| let test_lemmas (#n:pos{n > 1}) (x: residue n) = | |
| if (is_prime n /\ x <> 0) then begin | |
| let sf : skewfield (prime_residue n) = TC.solve in | |
| let mig : mul_is_group (prime_residue n) = solve_mig #(prime_residue n) in | |
| mig.inv x | |
| end else begin | |
| 0 | |
| end | |
| // why does this fail? | |
| let test_inverse3 (#p: int{is_prime p}) (x: prime_residue p{x <> 0}) = inv x |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment