@ceccec/millennium-solutions 0.1.1 → 9.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.zenodo.json +33 -9
- package/ACCOUNTING.md +7 -7
- package/AXIOMS.md +559 -0
- package/CHALLENGES.md +2181 -2479
- package/CITATION.cff +1 -1
- package/DEPLOY.md +2 -2
- package/DEVELOP.md +1 -1
- package/LICENSE +6 -5
- package/PRIOR-ART.md +149 -0
- package/README.md +221 -123
- package/SIGNATURE.md +2 -2
- package/TRIAL.md +69 -0
- package/WHITEPAPER.md +5 -5
- package/captain.md +2 -2
- package/compare.md +4 -4
- package/compute.md +9 -0
- package/dashboard.md +1 -1
- package/examples.md +5 -5
- package/forensic.md +67 -0
- package/guide.md +5 -5
- package/index.md +209 -139
- package/package.json +88 -9
- package/paper.md +7183 -0
- package/quantum.md +42 -0
- package/rights.md +39 -0
- package/solutions.md +7 -7
- package/speedup.md +4 -4
- package/src/0/index.ts +12 -2
- package/src/1/index.ts +1 -1
- package/src/2/frequency-scales.ts +13 -1
- package/src/2/index.ts +1 -1
- package/src/3/index.ts +1 -1
- package/src/4/index.ts +1 -1
- package/src/5/index.ts +1 -1
- package/src/6/index.ts +1 -1
- package/src/7/index.ts +1 -1
- package/src/9/funding.ts +2 -2
- package/src/api/gates.ts +117 -0
- package/src/api/index.ts +488 -0
- package/src/api/lanes.ts +81 -0
- package/src/demand/queries.json +1764 -0
- package/src/face/index.ts +100 -0
- package/src/html/index.ts +10 -0
- package/src/latex/index.ts +474 -0
- package/src/millennium/index.ts +53 -0
- package/src/proof/README.md +1 -1
- package/src/proof/address.lean +117 -0
- package/src/proof/coin.lean +148 -0
- package/src/proof/covered.json +9 -0
- package/src/proof/demand.lean +112 -0
- package/src/proof/demand2.lean +171 -0
- package/src/proof/demand3.lean +117 -0
- package/src/proof/discovered.json +10965 -1864
- package/src/proof/elementary.lean +393 -0
- package/src/proof/energy.lean +215 -0
- package/src/proof/families.lean +435 -0
- package/src/proof/fixtures/axiom-control.lean +39 -0
- package/src/proof/fnv.lean +99 -0
- package/src/proof/generated-theorems.json +207 -0
- package/src/proof/generated.lean +101 -0
- package/src/proof/imagined.lean +492 -0
- package/src/proof/index.lean +98 -18
- package/src/proof/index.ts +2 -2
- package/src/proof/involution.lean +90 -0
- package/src/proof/ledgerclaims.lean +84 -0
- package/src/proof/light.lean +135 -0
- package/src/proof/mechanical.lean +421 -0
- package/src/proof/merkaba.lean +89 -0
- package/src/proof/merkle.lean +132 -0
- package/src/proof/nim.lean +105 -0
- package/src/proof/phenomena.lean +90 -0
- package/src/proof/priorart.lean +189 -0
- package/src/proof/quantum.lean +161 -0
- package/src/proof/reach.lean +84 -0
- package/src/proof/recovered.lean +56 -0
- package/src/proof/reversal.lean +73 -0
- package/src/proof/rights.lean +157 -0
- package/src/proof/sequences.lean +105 -0
- package/src/proof/speed.lean +129 -0
- package/src/proof/split.lean +180 -0
- package/src/proof/theorems.lean +48 -1
- package/src/proof/trial-all.json +15260 -0
- package/src/proof/z9.lean +92 -0
- package/src/proof/z9plus.lean +208 -0
- package/src/proofs.lisp +3 -3
- package/src/prove/emit.ts +115 -0
- package/src/prove/index.ts +143 -0
- package/src/prove/translate.ts +638 -0
- package/src/publication/index.ts +363 -0
- package/src/quantum/field.ts +73 -0
- package/src/quantum/tree.ts +79 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
-- title: Families over the ring
|
|
2
|
+
-- wing: the ring
|
|
3
|
+
-- prior_art: named
|
|
4
|
+
-- prior_art_domain: modular arithmetic, quantified
|
|
5
|
+
-- prior_art_note: quantifies the ℤ/9 arithmetic above; the underlying results are Fermat’s, Euler’s and Gauss’s
|
|
6
|
+
-- The families, quantified. Proving at scale.
|
|
7
|
+
-- Author: Tsvetan Rouschev · License: CC BY-NC-ND 4.0
|
|
8
|
+
--
|
|
9
|
+
-- The ledger holds families of entries produced by a loop: flt_prime_3, flt_prime_5, flt_prime_7 … each a
|
|
10
|
+
-- separate row asserting the same theorem at one more parameter. A row per parameter is not how mathematics
|
|
11
|
+
-- scales; a quantifier is. Each theorem below ranges over the whole family's parameter set, so ONE proof
|
|
12
|
+
-- subsumes every member — and covers parameters the ledger never enumerated.
|
|
13
|
+
--
|
|
14
|
+
-- Every proof is `by decide` over a finite range: no axioms, no Mathlib, no `sorry`. Where a statement is
|
|
15
|
+
-- false outside its stated range, that is stated as a negative rather than omitted. Integrity, not truth.
|
|
16
|
+
|
|
17
|
+
namespace Families
|
|
18
|
+
|
|
19
|
+
def primesUpTo30 : List Nat := [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
|
|
20
|
+
def fact (n : Nat) : Nat := (List.range n).foldl (fun a k => a * (k + 1)) 1
|
|
21
|
+
def choose (n k : Nat) : Nat := fact n / (fact k * fact (n - k))
|
|
22
|
+
-- gcd defined by STRUCTURAL recursion on a fuel argument. Nat.gcd is defined by well-founded recursion,
|
|
23
|
+
-- whose equation lemmas pull in `propext` — so using it would silently cost this file its axiom-free status.
|
|
24
|
+
-- Caught by the per-theorem audit; the standard is "does not depend on any axioms", and it is enforced.
|
|
25
|
+
def gcdFuel : Nat → Nat → Nat → Nat
|
|
26
|
+
| 0, a, _ => a
|
|
27
|
+
| _, a, 0 => a
|
|
28
|
+
| Nat.succ f, a, b => gcdFuel f b (a % b)
|
|
29
|
+
def gcd' (a b : Nat) : Nat := gcdFuel (a + b + 1) a b
|
|
30
|
+
def totient (n : Nat) : Nat := ((List.range n).filter (fun a => gcd' a n == 1)).length
|
|
31
|
+
def popcount (n : Nat) : Nat := ((List.range 16).filter (fun i => (n >>> i) % 2 == 1)).length
|
|
32
|
+
|
|
33
|
+
-- ── Fermat's little theorem, over every prime below thirty and every nonzero residue ──
|
|
34
|
+
theorem flt_all_primes_under_thirty :
|
|
35
|
+
primesUpTo30.all (fun p => (List.range' 1 (p - 1)).all (fun a => (a ^ (p - 1)) % p == 1)) := by decide
|
|
36
|
+
|
|
37
|
+
-- ── Wilson's theorem, same range: (p−1)! ≡ p−1 (mod p) ──
|
|
38
|
+
theorem wilson_all_primes_under_thirty :
|
|
39
|
+
primesUpTo30.all (fun p => fact (p - 1) % p == p - 1) := by decide
|
|
40
|
+
|
|
41
|
+
-- ── and Wilson FAILS at every composite — the converse, which the family never stated ──
|
|
42
|
+
theorem wilson_fails_at_composites :
|
|
43
|
+
((List.range' 4 20).filter (fun n => ! primesUpTo30.contains n)).all
|
|
44
|
+
(fun n => fact (n - 1) % n != n - 1) := by decide
|
|
45
|
+
|
|
46
|
+
-- ── Pascal: every row sums to a power of two ──
|
|
47
|
+
theorem pascal_rows_sum_to_powers_of_two :
|
|
48
|
+
(List.range 12).all (fun n => ((List.range (n + 1)).map (fun k => choose n k)).foldl (· + ·) 0 == 2 ^ n) := by decide
|
|
49
|
+
|
|
50
|
+
-- ── Pascal: the alternating sum vanishes on every row but the zeroth ──
|
|
51
|
+
theorem pascal_alternating_sums_vanish :
|
|
52
|
+
(List.range' 1 11).all (fun n =>
|
|
53
|
+
((List.range (n + 1)).map (fun k => if k % 2 == 0 then choose n k else 0)).foldl (· + ·) 0
|
|
54
|
+
== ((List.range (n + 1)).map (fun k => if k % 2 == 1 then choose n k else 0)).foldl (· + ·) 0) := by decide
|
|
55
|
+
|
|
56
|
+
-- ── Euler's totient at prime powers: φ(pᵏ) = pᵏ − pᵏ⁻¹ ──
|
|
57
|
+
set_option maxRecDepth 100000 in
|
|
58
|
+
theorem totient_at_prime_powers :
|
|
59
|
+
[2, 3, 5, 7].all (fun p => (List.range' 1 3).all (fun k => totient (p ^ k) == p ^ k - p ^ (k - 1))) := by decide
|
|
60
|
+
|
|
61
|
+
-- ── the geometric series in every base 2..9, to the fourth power ──
|
|
62
|
+
theorem geometric_series_all_bases :
|
|
63
|
+
(List.range' 2 8).all (fun b =>
|
|
64
|
+
((List.range 5).map (fun i => b ^ i)).foldl (· + ·) 0 * (b - 1) == b ^ 5 - 1) := by decide
|
|
65
|
+
|
|
66
|
+
-- ── XOR is parity: over every assignment of k bits, for k up to 8 ──
|
|
67
|
+
set_option maxRecDepth 100000 in
|
|
68
|
+
theorem xor_is_parity_up_to_eight_bits :
|
|
69
|
+
(List.range' 1 8).all (fun k =>
|
|
70
|
+
(List.range (2 ^ k)).all (fun n => popcount n % 2 == (List.range k).foldl (fun acc i => (acc + ((n >>> i) % 2)) % 2) 0)) := by decide
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
-- ── THE CONVENTION AT ZERO, stated as a convention and not as arithmetic.
|
|
74
|
+
--
|
|
75
|
+
-- Division by zero is UNDEFINED in mathematics: there is no quotient, and nothing below claims otherwise.
|
|
76
|
+
-- What is recorded here is a property of Lean's FUNCTION `Nat.div`, which is total — every pair of naturals
|
|
77
|
+
-- is mapped somewhere, including a zero divisor, where the definition returns 0. That is a choice made so
|
|
78
|
+
-- the function is total and `decide` never faults on a side condition; it is not a claim that dividing by
|
|
79
|
+
-- zero yields zero, and an earlier name here ("division_by_zero_is_zero") said exactly that and was wrong.
|
|
80
|
+
--
|
|
81
|
+
-- The deposit reads division by zero as a CHANGE OF DOMAIN, which is the same point put positively: the
|
|
82
|
+
-- value is not found in the arithmetic, it is supplied by the definition — a different domain entirely.
|
|
83
|
+
-- Naming this precisely matters, because a reader who takes `7 / 0 = 0` for arithmetic has been misled by
|
|
84
|
+
-- a theorem that is green. ──
|
|
85
|
+
theorem nat_div_is_a_total_function_returning_zero_at_a_zero_divisor :
|
|
86
|
+
(7 / 0) = 0 ∧ (0 / 0) = 0 ∧ (7 % 0) = 7 := by decide
|
|
87
|
+
|
|
88
|
+
-- the division identity holds across a range INCLUDING zero — but at zero it is carried entirely by the
|
|
89
|
+
-- remainder, because the quotient was supplied by the convention rather than computed
|
|
90
|
+
theorem division_identity_holds_across_the_range :
|
|
91
|
+
(List.range 13).all (fun d => (12 / d) * d + (12 % d) == 12) := by decide
|
|
92
|
+
|
|
93
|
+
theorem at_a_zero_divisor_the_identity_is_carried_by_the_remainder :
|
|
94
|
+
(12 / 0) = 0 ∧ (12 % 0) = 12 ∧ (12 / 0) * 0 + (12 % 0) == 12 := by decide
|
|
95
|
+
|
|
96
|
+
-- ── SEVEN FAMILIES THE LEDGER HELD AS SINGLETONS, QUANTIFIED — 2026-09-07 ───────────────────────────────
|
|
97
|
+
--
|
|
98
|
+
-- The ledger carries rows like domain_prime_m2, domain_prime_m3, domain_prime_m5 … one per parameter,
|
|
99
|
+
-- every one of them revoked for want of a proof. Under "involute instead of withdraw" a revoked row whose
|
|
100
|
+
-- statement is TRUE is not a dead entry: it is a carry waiting for the theorem that subsumes it. Each
|
|
101
|
+
-- theorem below quantifies one such family over its whole parameter range, so the withdrawn members come
|
|
102
|
+
-- back proved rather than dropped — and the range extends past whatever the loop happened to enumerate.
|
|
103
|
+
--
|
|
104
|
+
-- Four of the seven are stated as EQUIVALENCES rather than as lists of successes. The ledger's loop wrote
|
|
105
|
+
-- a row only where the property held and simply omitted mulperm_k3, mulperm_k6, addgen_k3, addgen_k6,
|
|
106
|
+
-- hasinv_d3, hasinv_d6 — the parameters where it fails. An omission is not a statement, and the reader
|
|
107
|
+
-- cannot tell a false parameter from one nobody tried. The equivalence says both halves at once: the
|
|
108
|
+
-- property holds at exactly the units and nowhere else, decided over the entire range.
|
|
109
|
+
|
|
110
|
+
def isPrime (n : Nat) : Bool := n ≥ 2 && (List.range' 2 (n - 2)).all (fun d => n % d != 0)
|
|
111
|
+
def unitsMod (m : Nat) : List Nat := (List.range m).filter (fun a => gcd' a m == 1)
|
|
112
|
+
-- modular powering by repeated reduction — g ^ k over the naturals would build a 22-digit intermediate at
|
|
113
|
+
-- m = 18 and cost the kernel far more than the theorem is worth
|
|
114
|
+
def powMod (g k m : Nat) : Nat := (List.range k).foldl (fun a _ => a * g % m) (1 % m)
|
|
115
|
+
def ordMod (g m : Nat) : Nat := ((List.range' 1 m).find? (fun k => powMod g k m == 1)).getD 0
|
|
116
|
+
def hasPrimitiveRoot (m : Nat) : Bool := (unitsMod m).any (fun g => ordMod g m == (unitsMod m).length)
|
|
117
|
+
def isOddPrimePower (n : Nat) : Bool :=
|
|
118
|
+
(List.range' 3 n).any (fun p => isPrime p && p % 2 == 1 && (List.range' 1 5).any (fun k => p ^ k == n))
|
|
119
|
+
-- Gauss's characterisation, written as a predicate so the theorem below compares two computations rather
|
|
120
|
+
-- than comparing a computation against a list somebody typed out after looking at the answer
|
|
121
|
+
def gaussCyclic (m : Nat) : Bool :=
|
|
122
|
+
m == 1 || m == 2 || m == 4 || isOddPrimePower m || (m % 2 == 0 && isOddPrimePower (m / 2))
|
|
123
|
+
|
|
124
|
+
-- ── primality across the range, decided — and agreeing with the list this file already used ──
|
|
125
|
+
theorem primality_is_decided_across_the_range :
|
|
126
|
+
(List.range' 2 17).all (fun m => isPrime m == primesUpTo30.contains m) := by decide
|
|
127
|
+
|
|
128
|
+
-- ── the units are cyclic at exactly the moduli Gauss says, and at no others ──
|
|
129
|
+
set_option maxRecDepth 100000 in
|
|
130
|
+
theorem the_units_are_cyclic_at_exactly_the_gauss_moduli :
|
|
131
|
+
(List.range' 2 17).all (fun m => hasPrimitiveRoot m == gaussCyclic m) := by decide
|
|
132
|
+
|
|
133
|
+
-- ── De Morgan at every arity to eight, over every assignment of its inputs ──
|
|
134
|
+
set_option maxRecDepth 100000 in
|
|
135
|
+
theorem demorgan_holds_at_every_arity_to_eight :
|
|
136
|
+
(List.range' 2 7).all (fun k => (List.range (2 ^ k)).all (fun n =>
|
|
137
|
+
(! (List.range k).all (fun i => (n >>> i) % 2 == 1)) == (List.range k).any (fun i => (n >>> i) % 2 == 0))) := by decide
|
|
138
|
+
|
|
139
|
+
def permutesZ9 (k : Nat) : Bool := (List.range 9).all (fun y => (List.range 9).any (fun d => k * d % 9 == y))
|
|
140
|
+
-- the orbit of repeated ADDITION — d ↦ d + k applied t times, which is what "generates additively" means;
|
|
141
|
+
-- writing it as t * k would have made this the same computation as permutesZ9 wearing a different name
|
|
142
|
+
def addOrbit (k : Nat) : List Nat := (List.range 9).map (fun t => (List.range t).foldl (fun a _ => (a + k) % 9) 0)
|
|
143
|
+
def addGeneratesZ9 (k : Nat) : Bool := (List.range 9).all (fun y => (addOrbit k).contains y)
|
|
144
|
+
def invOf (d : Nat) : Option Nat := (List.range 9).find? (fun e => d * e % 9 == 1)
|
|
145
|
+
|
|
146
|
+
-- ── multiplication permutes ℤ/9 at exactly the units — the failing parameters stated, not omitted ──
|
|
147
|
+
theorem multiplication_permutes_z9_at_exactly_the_units :
|
|
148
|
+
(List.range' 1 8).all (fun k => permutesZ9 k == (gcd' k 9 == 1)) := by decide
|
|
149
|
+
|
|
150
|
+
-- ── repeated addition reaches all of ℤ/9 at exactly the same parameters, by a different computation ──
|
|
151
|
+
theorem addition_generates_z9_at_exactly_the_units :
|
|
152
|
+
(List.range' 1 8).all (fun k => addGeneratesZ9 k == (gcd' k 9 == 1)) := by decide
|
|
153
|
+
|
|
154
|
+
-- ── an inverse mod 9 exists at exactly the units, across the whole ring including zero ──
|
|
155
|
+
theorem an_inverse_mod_nine_exists_at_exactly_the_units :
|
|
156
|
+
(List.range 9).all (fun d => (invOf d).isSome == (gcd' d 9 == 1)) := by decide
|
|
157
|
+
|
|
158
|
+
-- ── and where it exists it is the fifth power: u · u⁵ ≡ 1, since |units mod 9| = 6 ──
|
|
159
|
+
theorem the_inverse_of_a_unit_mod_nine_is_its_fifth_power :
|
|
160
|
+
(unitsMod 9).all (fun u => u * (powMod u 5 9) % 9 == 1) := by decide
|
|
161
|
+
|
|
162
|
+
-- ── A SECOND OCTAVE OF FAMILIES, 2026-09-07 ─────────────────────────────────────────────────────────────
|
|
163
|
+
--
|
|
164
|
+
-- Same operation as the seven above, on the families the first pass left: each was a run of ledger rows,
|
|
165
|
+
-- one per parameter, every one revoked as "not backed by a Lean proof". The evidence they DID have was a
|
|
166
|
+
-- TypeScript test — a computation reporting that it agreed with itself. That is a real reason to want a
|
|
167
|
+
-- kernel proof and not a reason to call the statement dead.
|
|
168
|
+
|
|
169
|
+
def powSum (k n : Nat) : Nat := ((List.range' 1 n).map (fun i => i ^ k)).foldl (· + ·) 0
|
|
170
|
+
-- Faulhaber's closed forms, k = 1…5. The subtractions are safe in Nat: at n = 0 the truncation to zero is
|
|
171
|
+
-- multiplied by an n that is also zero, and above n = 0 the bracket is positive.
|
|
172
|
+
def faulhaber (k n : Nat) : Nat :=
|
|
173
|
+
match k with
|
|
174
|
+
| 1 => n * (n + 1) / 2
|
|
175
|
+
| 2 => n * (n + 1) * (2 * n + 1) / 6
|
|
176
|
+
| 3 => n * n * (n + 1) * (n + 1) / 4
|
|
177
|
+
| 4 => n * (n + 1) * (2 * n + 1) * (3 * n * n + 3 * n - 1) / 30
|
|
178
|
+
| 5 => n * n * (n + 1) * (n + 1) * (2 * n * n + 2 * n - 1) / 12
|
|
179
|
+
| _ => 0
|
|
180
|
+
|
|
181
|
+
-- digits, by structural recursion on a fuel argument — the same reason gcdFuel exists above: a well-founded
|
|
182
|
+
-- definition would pull `propext` in through its equation lemmas and cost this file its axiom-free status
|
|
183
|
+
def digitsF : Nat → Nat → List Nat
|
|
184
|
+
| 0, _ => []
|
|
185
|
+
| _, 0 => []
|
|
186
|
+
| Nat.succ f, n => n % 10 :: digitsF f (n / 10)
|
|
187
|
+
-- fuel 20 covers every Nat that fits in 64 bits; a fuel just large enough for the range below would
|
|
188
|
+
-- silently truncate the digits of any larger number a later theorem passed in
|
|
189
|
+
def digitsOf (n : Nat) : List Nat := digitsF 20 n
|
|
190
|
+
def reverseDigits (n : Nat) : Nat := (digitsOf n).foldl (fun a d => a * 10 + d) 0
|
|
191
|
+
def digitalRoot (n : Nat) : Nat := if n == 0 then 0 else 1 + (n - 1) % 9
|
|
192
|
+
|
|
193
|
+
-- ── the closed forms for the power sums, k = 1…5, against the loop they replace ──
|
|
194
|
+
set_option maxRecDepth 100000 in
|
|
195
|
+
theorem power_sums_match_their_closed_forms :
|
|
196
|
+
(List.range' 1 5).all (fun k => (List.range 41).all (fun n => powSum k n == faulhaber k n)) := by decide
|
|
197
|
+
|
|
198
|
+
-- ── the digital root survives writing the number backwards, for every number below ten thousand ──
|
|
199
|
+
-- Reversal permutes the digits, the digit sum is invariant under permutation, and the digital root is a
|
|
200
|
+
-- function of that sum. The statement is decided rather than argued.
|
|
201
|
+
-- Walked as a hundred hundreds rather than as one list of ten thousand: `decide` evaluates a single
|
|
202
|
+
-- `List.all` as one deep conjunction and overflows the kernel's stack at that length, which is a fact
|
|
203
|
+
-- about the evaluator and not about the mathematics. The domain is identical — every n below 10000.
|
|
204
|
+
-- It costs the kernel about forty-five seconds, which is most of what this file takes and a real price
|
|
205
|
+
-- paid on every run. Measured, not guessed: shrinking the fuel changes nothing, because the cost is the
|
|
206
|
+
-- ten thousand evaluations themselves. The range is not padding — the family's own parameters run to
|
|
207
|
+
-- 9080, and a statement that stopped short of them would not carry the claims it exists to carry.
|
|
208
|
+
set_option maxRecDepth 400000 in
|
|
209
|
+
set_option maxHeartbeats 2000000 in
|
|
210
|
+
theorem the_digital_root_is_invariant_under_digit_reversal :
|
|
211
|
+
(List.range 100).all (fun a => (List.range 100).all (fun b =>
|
|
212
|
+
digitalRoot (a * 100 + b) == digitalRoot (reverseDigits (a * 100 + b)))) := by decide
|
|
213
|
+
|
|
214
|
+
-- ── the geometric series across bases 2…12 and every exponent to six, not one exponent at one base ──
|
|
215
|
+
set_option maxRecDepth 100000 in
|
|
216
|
+
theorem geometric_series_across_bases_and_exponents :
|
|
217
|
+
(List.range' 2 11).all (fun b => (List.range 7).all (fun n =>
|
|
218
|
+
((List.range (n + 1)).map (fun i => b ^ i)).foldl (· + ·) 0 * (b - 1) == b ^ (n + 1) - 1)) := by decide
|
|
219
|
+
|
|
220
|
+
-- ── Euler's totient at prime powers, out to thirteen ──
|
|
221
|
+
set_option maxRecDepth 400000 in
|
|
222
|
+
theorem totient_at_prime_powers_through_thirteen :
|
|
223
|
+
[2, 3, 5, 7, 11, 13].all (fun p => (List.range' 1 3).all (fun k => totient (p ^ k) == p ^ k - p ^ (k - 1))) := by decide
|
|
224
|
+
|
|
225
|
+
-- ── ROOTS OF UNITY IN A RING, AND THE CONVERSE THAT DOES NOT HOLD ───────────────────────────────────────
|
|
226
|
+
--
|
|
227
|
+
-- The ledger's roots_cancel_n rows say the n equally-spaced unit vectors cancel to the zero vector.
|
|
228
|
+
-- That is plane geometry over the reals, and `decide` over Nat cannot express it, so those rows are NOT
|
|
229
|
+
-- carried by anything below and stay withdrawn — saying so is the point of writing this here.
|
|
230
|
+
--
|
|
231
|
+
-- What IS decidable is the algebraic shadow of the same fact: in ℤ/m, the powers of an element g of
|
|
232
|
+
-- multiplicative order n sum to zero. That is the identity (g − 1)·Σ = gⁿ − 1 = 0, and it gives Σ = 0
|
|
233
|
+
-- only when g − 1 can be cancelled — so the statement carries its own hypothesis, gcd(g − 1, m) = 1.
|
|
234
|
+
--
|
|
235
|
+
-- The converse looked true and is not. Writing it as an equivalence would have been the stronger, more
|
|
236
|
+
-- satisfying theorem; the kernel refuses it, and at m = 6 with g = 5 the sum 1 + 5 vanishes mod 6 while
|
|
237
|
+
-- gcd(4, 6) = 2. The witness is kept as its own theorem, because a hypothesis nobody can see the need
|
|
238
|
+
-- for gets deleted as clutter by the next reader.
|
|
239
|
+
|
|
240
|
+
def rootSum (g m : Nat) : Nat := ((List.range (ordMod g m)).map (fun k => powMod g k m)).foldl (· + ·) 0
|
|
241
|
+
|
|
242
|
+
set_option maxRecDepth 100000 in
|
|
243
|
+
theorem the_powers_of_a_unit_sum_to_zero_when_g_minus_one_is_invertible :
|
|
244
|
+
(List.range' 2 19).all (fun m => (unitsMod m).all (fun g =>
|
|
245
|
+
!(gcd' (g - 1) m == 1) || (rootSum g m % m == 0))) := by decide
|
|
246
|
+
|
|
247
|
+
theorem and_the_converse_fails_at_six :
|
|
248
|
+
ordMod 5 6 = 2 ∧ rootSum 5 6 % 6 = 0 ∧ gcd' 4 6 = 2 := by decide
|
|
249
|
+
|
|
250
|
+
-- ── A THIRD OCTAVE: THE CLASSICAL SUMS, EACH QUANTIFIED OVER ITS RANGE ──────────────────────────────────
|
|
251
|
+
--
|
|
252
|
+
-- Same operation again, on the families the second pass left. Several of these rows were withdrawn while
|
|
253
|
+
-- the fact they state was ALREADY decided here — sum_first_5_odd_numbers_is_5_squared sat beside a
|
|
254
|
+
-- theorem quantifying over every n — so the octave below is written for the ones that were genuinely
|
|
255
|
+
-- unproved, and the rest are carried by rules in scripts/recover.ts pointing at what already existed.
|
|
256
|
+
|
|
257
|
+
def vpF : Nat → Nat → Nat → Nat
|
|
258
|
+
| 0, _, _ => 0
|
|
259
|
+
| _, _, 0 => 0
|
|
260
|
+
| Nat.succ f, p, n => if p > 1 && n % p == 0 then 1 + vpF f p (n / p) else 0
|
|
261
|
+
/-- the exponent of p in n — the p-adic valuation, by fuel for the same axiom-free reason as gcdFuel -/
|
|
262
|
+
def vp (p n : Nat) : Nat := vpF 20 p n
|
|
263
|
+
def isSumOfTwoSquares (n : Nat) : Bool :=
|
|
264
|
+
(List.range 15).any (fun a => (List.range 15).any (fun b => a * a + b * b == n))
|
|
265
|
+
/-- Fermat's condition: every prime ≡ 3 (mod 4) divides n to an even power -/
|
|
266
|
+
def everyThreeModFourPrimeIsEven (n : Nat) : Bool :=
|
|
267
|
+
(List.range' 2 199).all (fun p => !(isPrime p && p % 4 == 3) || vp p n % 2 == 0)
|
|
268
|
+
|
|
269
|
+
def digitSquareSum (n : Nat) : Nat := ((digitsOf n).map (fun d => d * d)).foldl (· + ·) 0
|
|
270
|
+
def iterF : Nat → Nat → Nat
|
|
271
|
+
| 0, n => n
|
|
272
|
+
| Nat.succ f, n => if n == 1 || n == 4 then n else iterF f (digitSquareSum n)
|
|
273
|
+
|
|
274
|
+
def polyRec : Nat → Nat → Nat
|
|
275
|
+
| _, 0 => 0
|
|
276
|
+
| s, Nat.succ m => polyRec s m + ((s - 2) * m + 1)
|
|
277
|
+
|
|
278
|
+
-- ── the odd numbers build the squares, one gnomon at a time ──
|
|
279
|
+
set_option maxRecDepth 400000 in
|
|
280
|
+
theorem the_first_n_odd_numbers_sum_to_n_squared :
|
|
281
|
+
(List.range 201).all (fun n => ((List.range n).map (fun i => 2 * i + 1)).foldl (· + ·) 0 == n * n) := by decide
|
|
282
|
+
|
|
283
|
+
-- ── the powers of two fall one short of the next power ──
|
|
284
|
+
set_option maxRecDepth 400000 in
|
|
285
|
+
theorem the_powers_of_two_sum_to_one_less_than_the_next :
|
|
286
|
+
(List.range 41).all (fun n =>
|
|
287
|
+
((List.range (n + 1)).map (fun i => 2 ^ i)).foldl (· + ·) 0 == 2 ^ (n + 1) - 1) := by decide
|
|
288
|
+
|
|
289
|
+
-- ── stacking triangles gives tetrahedra ──
|
|
290
|
+
set_option maxRecDepth 400000 in
|
|
291
|
+
theorem the_sums_of_triangular_numbers_are_the_tetrahedral_numbers :
|
|
292
|
+
(List.range 101).all (fun n =>
|
|
293
|
+
((List.range' 1 n).map (fun i => i * (i + 1) / 2)).foldl (· + ·) 0 == n * (n + 1) * (n + 2) / 6) := by decide
|
|
294
|
+
|
|
295
|
+
-- ── a Pascal row squared sums to the central binomial ──
|
|
296
|
+
set_option maxRecDepth 100000 in
|
|
297
|
+
theorem the_squares_of_a_pascal_row_sum_to_the_central_binomial :
|
|
298
|
+
(List.range 13).all (fun n =>
|
|
299
|
+
((List.range (n + 1)).map (fun k => choose n k * choose n k)).foldl (· + ·) 0 == choose (2 * n) n) := by decide
|
|
300
|
+
|
|
301
|
+
-- ── every polygonal family: the closed form and the recurrence are the same sequence, for s = 3…10 ──
|
|
302
|
+
set_option maxRecDepth 400000 in
|
|
303
|
+
theorem polygonal_closed_forms_match_their_recurrences :
|
|
304
|
+
(List.range' 3 8).all (fun s => (List.range 51).all (fun n =>
|
|
305
|
+
((s - 2) * n * n + 4 * n - s * n) / 2 == polyRec s n)) := by decide
|
|
306
|
+
|
|
307
|
+
-- ── Lagrange in the one group this deposit is built on: every unit's order divides the group's ──
|
|
308
|
+
-- ordMod finds the LEAST positive exponent, so minimality is by construction and not asserted here.
|
|
309
|
+
theorem the_order_of_every_unit_mod_nine_divides_six :
|
|
310
|
+
(unitsMod 9).all (fun u => ordMod u 9 > 0 && 6 % ordMod u 9 == 0 && powMod u (ordMod u 9) 9 == 1) := by decide
|
|
311
|
+
|
|
312
|
+
-- ── the digit-square iteration is a dichotomy: every start below 201 lands on 1 or on 4 ──
|
|
313
|
+
-- 4 is the entry to the eight-cycle, so "not happy" is a single decidable destination rather than a
|
|
314
|
+
-- negative that would need the whole cycle enumerating.
|
|
315
|
+
set_option maxRecDepth 400000 in
|
|
316
|
+
theorem the_digit_square_iteration_reaches_one_or_four :
|
|
317
|
+
(List.range' 1 200).all (fun n => iterF 60 n == 1 || iterF 60 n == 4) := by decide
|
|
318
|
+
|
|
319
|
+
-- ── Fermat's two-squares theorem, decided both ways over the first two hundred ──
|
|
320
|
+
set_option maxRecDepth 400000 in
|
|
321
|
+
theorem a_number_is_a_sum_of_two_squares_exactly_when_fermats_condition_holds :
|
|
322
|
+
(List.range' 1 200).all (fun n => isSumOfTwoSquares n == everyThreeModFourPrimeIsEven n) := by decide
|
|
323
|
+
|
|
324
|
+
-- ── AND THE POWER SUMS OUT TO TWO HUNDRED, WHICH IS THE RANGE THE ROWS ACTUALLY CLAIM ───────────────────
|
|
325
|
+
-- power_sums_match_their_closed_forms decides k = 1…5 at every n to forty. `the_sum_of_the_first_n_squares`
|
|
326
|
+
-- says "verified by full enumeration over n up to 200", and forty is not two hundred: carrying it on the
|
|
327
|
+
-- narrower theorem would have been the easy move and would have recorded a range nobody had checked.
|
|
328
|
+
-- Every range below was read off the claim it is meant to carry, and the four theorems above were widened
|
|
329
|
+
-- the same way — to 200, 40, 100 and 50 — rather than the claims being trimmed to fit what I had written.
|
|
330
|
+
set_option maxRecDepth 400000 in
|
|
331
|
+
theorem the_first_three_power_sums_hold_to_two_hundred :
|
|
332
|
+
(List.range' 1 3).all (fun k => (List.range 201).all (fun n => powSum k n == faulhaber k n)) := by decide
|
|
333
|
+
|
|
334
|
+
-- ── A FOURTH OCTAVE: FIBONACCI, COUNTING, AND THE FIVE SOLIDS ───────────────────────────────────────────
|
|
335
|
+
|
|
336
|
+
/-- Fibonacci by carrying a pair forward. The two-clause recurrence `fib n + fib (n+1)` is exponential when
|
|
337
|
+
the kernel evaluates it — `fib 25` alone is a quarter of a million calls — so it is written linearly. -/
|
|
338
|
+
def fibPair : Nat → Nat × Nat
|
|
339
|
+
| 0 => (0, 1)
|
|
340
|
+
| Nat.succ n => let (a, b) := fibPair n; (b, a + b)
|
|
341
|
+
def fib (n : Nat) : Nat := (fibPair n).1
|
|
342
|
+
def digitSum (n : Nat) : Nat := (digitsOf n).foldl (· + ·) 0
|
|
343
|
+
|
|
344
|
+
/-- the convergents of the continued fraction [1; 1, 1, 1, …], numerator and denominator carried together -/
|
|
345
|
+
def cfPair : Nat → Nat × Nat
|
|
346
|
+
| 0 => (1, 1)
|
|
347
|
+
| Nat.succ n => let (p, q) := cfPair n; (p + q, p)
|
|
348
|
+
|
|
349
|
+
/-- Catalan by the convolution recurrence, built as a list because C(n+1) needs every earlier term and a
|
|
350
|
+
direct recursive call on `n - i` is not structural — the well-founded version would cost the file its
|
|
351
|
+
axiom-free status, which is the same constraint that shaped gcdFuel and digitsF. -/
|
|
352
|
+
def catalanList : Nat → List Nat
|
|
353
|
+
| 0 => [1]
|
|
354
|
+
| Nat.succ n =>
|
|
355
|
+
let prev := catalanList n
|
|
356
|
+
prev ++ [((List.range (n + 1)).map (fun i => prev.getD i 0 * prev.getD (n - i) 0)).foldl (· + ·) 0]
|
|
357
|
+
|
|
358
|
+
def pisano (m : Nat) : Nat := ((List.range' 1 100).find? (fun k => fib k % m == 0 && fib (k + 1) % m == 1)).getD 0
|
|
359
|
+
|
|
360
|
+
def interleaveN (x : Nat) : List Nat → List (List Nat)
|
|
361
|
+
| [] => [[x]]
|
|
362
|
+
| y :: ys => (x :: y :: ys) :: (interleaveN x ys).map (fun l => y :: l)
|
|
363
|
+
def permsN : List Nat → List (List Nat)
|
|
364
|
+
| [] => [[]]
|
|
365
|
+
| x :: xs => (permsN xs).flatMap (fun p => interleaveN x p)
|
|
366
|
+
def isInvolution (p : List Nat) : Bool := (List.range p.length).all (fun i => p.getD (p.getD i 0) 0 == i)
|
|
367
|
+
def telephone : Nat → Nat
|
|
368
|
+
| 0 => 1
|
|
369
|
+
| 1 => 1
|
|
370
|
+
| Nat.succ (Nat.succ n) => telephone (n + 1) + (n + 1) * telephone n
|
|
371
|
+
|
|
372
|
+
/-- the regular polyhedra, DERIVED rather than typed: (p, q) is p-gonal faces with q meeting at a vertex,
|
|
373
|
+
and a convex solid exists exactly where 2p + 2q > pq. Listing V, E, F for five solids would have been
|
|
374
|
+
five rows of hand-typed data, and Euler's formula on hand-typed data checks the typing. -/
|
|
375
|
+
def schlafli : List (Nat × Nat) :=
|
|
376
|
+
((List.range' 3 8).flatMap (fun p => (List.range' 3 8).map (fun q => (p, q)))).filter (fun x => 2 * x.1 + 2 * x.2 > x.1 * x.2)
|
|
377
|
+
|
|
378
|
+
-- ── there are exactly five, and each satisfies V + F = E + 2 ──
|
|
379
|
+
-- Written as V + F = E + 2 rather than V − E + F = 2: the subtraction is the same statement over ℤ and a
|
|
380
|
+
-- truncation over Nat, and a theorem that is only true because a subtraction clipped is not a theorem.
|
|
381
|
+
theorem there_are_exactly_five_platonic_solids_and_each_satisfies_eulers_formula :
|
|
382
|
+
schlafli.length = 5 ∧ schlafli.all (fun x =>
|
|
383
|
+
let d := 2 * x.1 + 2 * x.2 - x.1 * x.2
|
|
384
|
+
4 * x.1 % d == 0 && 2 * x.1 * x.2 % d == 0 && 4 * x.2 % d == 0 &&
|
|
385
|
+
4 * x.1 / d + 4 * x.2 / d == 2 * x.1 * x.2 / d + 2) := by decide
|
|
386
|
+
|
|
387
|
+
-- ── Cassini: F(n−1)·F(n+1) − F(n)² alternates, so it is stated as the two cases Nat can express ──
|
|
388
|
+
theorem cassinis_identity_holds_across_the_range :
|
|
389
|
+
(List.range' 1 30).all (fun n =>
|
|
390
|
+
if n % 2 == 0 then fib (n - 1) * fib (n + 1) == fib n * fib n + 1
|
|
391
|
+
else fib (n - 1) * fib (n + 1) + 1 == fib n * fib n) := by decide
|
|
392
|
+
|
|
393
|
+
-- ── the golden continued fraction: its convergents ARE the Fibonacci ratios, and the determinant is ±1 ──
|
|
394
|
+
-- More than Cassini restated: it identifies pₙ and qₙ, which is the part a determinant identity alone
|
|
395
|
+
-- does not say.
|
|
396
|
+
theorem the_golden_convergents_are_fibonacci_ratios_with_unit_determinant :
|
|
397
|
+
(List.range 25).all (fun n =>
|
|
398
|
+
(cfPair n).1 == fib (n + 2) && (cfPair n).2 == fib (n + 1) &&
|
|
399
|
+
-- pₙ² − pₙqₙ − qₙ² = (−1)^(n+1), which is the determinant pₙqₙ₊₁ − pₙ₊₁qₙ written out through
|
|
400
|
+
-- qₙ₊₁ = pₙ and pₙ₊₁ = pₙ + qₙ. My first attempt compared pₙqₙ against pₙ² and the kernel refused it —
|
|
401
|
+
-- the identity was misremembered, not the arithmetic.
|
|
402
|
+
(if n % 2 == 0 then (cfPair n).1 * (cfPair n).1 + 1 == (cfPair n).1 * (cfPair n).2 + (cfPair n).2 * (cfPair n).2
|
|
403
|
+
else (cfPair n).1 * (cfPair n).1 == (cfPair n).1 * (cfPair n).2 + (cfPair n).2 * (cfPair n).2 + 1)) := by decide
|
|
404
|
+
|
|
405
|
+
-- ── Catalan: the convolution recurrence and the binomial formula are the same sequence ──
|
|
406
|
+
set_option maxRecDepth 100000 in
|
|
407
|
+
theorem the_catalan_recurrence_matches_the_binomial_formula :
|
|
408
|
+
(List.range 13).all (fun n => (catalanList 12).getD n 0 == choose (2 * n) n / (n + 1)) := by decide
|
|
409
|
+
|
|
410
|
+
-- ── the Pisano period: 24 at nine, and every modulus to twelve has one ──
|
|
411
|
+
set_option maxRecDepth 100000 in
|
|
412
|
+
theorem the_pisano_period_of_nine_is_twenty_four_and_every_modulus_to_twelve_has_one :
|
|
413
|
+
pisano 9 = 24 ∧ (List.range' 2 11).all (fun m =>
|
|
414
|
+
pisano m > 0 && fib (pisano m) % m == 0 && fib (pisano m + 1) % m == 1) := by decide
|
|
415
|
+
|
|
416
|
+
-- ── the digit-sum rules for three and nine, over every number below ten thousand ──
|
|
417
|
+
set_option maxRecDepth 400000 in
|
|
418
|
+
set_option maxHeartbeats 2000000 in
|
|
419
|
+
theorem the_digit_sum_rules_for_three_and_nine_hold_below_ten_thousand :
|
|
420
|
+
(List.range 100).all (fun a => (List.range 100).all (fun b =>
|
|
421
|
+
(digitSum (a * 100 + b) % 3 == 0) == ((a * 100 + b) % 3 == 0) &&
|
|
422
|
+
((digitSum (a * 100 + b) % 9 == 0) == ((a * 100 + b) % 9 == 0)))) := by decide
|
|
423
|
+
|
|
424
|
+
-- ── the involutions of a finite set are counted by the telephone numbers ──
|
|
425
|
+
-- Counted by ENUMERATING the permutations and filtering the self-inverse ones, so the recurrence is
|
|
426
|
+
-- checked against the objects it claims to count rather than against another recurrence.
|
|
427
|
+
set_option maxRecDepth 100000 in
|
|
428
|
+
theorem the_involutions_are_counted_by_the_telephone_numbers :
|
|
429
|
+
(List.range 6).all (fun n => ((permsN (List.range n)).filter isInvolution).length == telephone n) := by decide
|
|
430
|
+
|
|
431
|
+
-- ── what these settle ──
|
|
432
|
+
def settledHere : Nat := 40
|
|
433
|
+
theorem families_settle_their_ranges : settledHere = 40 := rfl
|
|
434
|
+
|
|
435
|
+
end Families
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
-- A NEGATIVE CONTROL, and it is not part of the deposit's theorems.
|
|
2
|
+
--
|
|
3
|
+
-- It lives outside src/proof because scripts/lean.ts fails any file whose declarations depend on an axiom,
|
|
4
|
+
-- and the whole point of this one is that its second theorem DOES. Every check should be asked a question
|
|
5
|
+
-- whose answer is already known before its report is believed, and "no theorem here depends on an axiom"
|
|
6
|
+
-- is a claim that would read identically if `#print axioms` were silently broken, mis-parsed, or pointed
|
|
7
|
+
-- at nothing.
|
|
8
|
+
--
|
|
9
|
+
-- Run by scripts/axiom-index.ts. The expected output is exact:
|
|
10
|
+
--
|
|
11
|
+
-- 'axiom_free_by_decision' does not depend on any axioms
|
|
12
|
+
-- 'needs_all_three' depends on axioms: [propext, Classical.choice, Quot.sound]
|
|
13
|
+
--
|
|
14
|
+
-- The first is what every declaration in src/proof reports. The second is what excluded middle costs in a
|
|
15
|
+
-- constructive kernel, and it is the reason the deposit's arithmetic is decided rather than argued.
|
|
16
|
+
|
|
17
|
+
def refl' (d : Nat) : Nat := 10 - d
|
|
18
|
+
|
|
19
|
+
-- Decided by exhaustion over a finite domain: computation, not inference from axioms.
|
|
20
|
+
theorem axiom_free_by_decision : [1, 2, 3, 4].all (fun d => refl' (refl' d) == d) := by decide
|
|
21
|
+
|
|
22
|
+
-- The same shape of fact, obtained classically. One appeal to excluded middle pulls in all three.
|
|
23
|
+
theorem needs_all_three : ∀ p : Prop, p ∨ ¬p := fun p => Classical.em p
|
|
24
|
+
|
|
25
|
+
-- PINNED WITH `#guard_msgs`, so the assertion is checked by the elaborator rather than by a script reading
|
|
26
|
+
-- this file's output. If either footprint ever changes — a tactic quietly pulling in choice, a toolchain
|
|
27
|
+
-- altering what `#print axioms` reports — THIS FILE STOPS COMPILING. The technique is standard practice in
|
|
28
|
+
-- the Lean community: axiom hygiene as an executable regression test, pinned so drift fails the build with
|
|
29
|
+
-- a mismatch rather than passing unnoticed.
|
|
30
|
+
--
|
|
31
|
+
-- Note the pins CONSUME the info line when they match, so a reader running this file sees silence. That is
|
|
32
|
+
-- the assertion holding, not the check being absent.
|
|
33
|
+
/-- info: 'axiom_free_by_decision' does not depend on any axioms -/
|
|
34
|
+
#guard_msgs in
|
|
35
|
+
#print axioms axiom_free_by_decision
|
|
36
|
+
|
|
37
|
+
/-- info: 'needs_all_three' depends on axioms: [propext, Classical.choice, Quot.sound] -/
|
|
38
|
+
#guard_msgs in
|
|
39
|
+
#print axioms needs_all_three
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
set_option maxRecDepth 1000000
|
|
2
|
+
-- title: FNV-1a, the address function
|
|
3
|
+
-- wing: the address
|
|
4
|
+
-- prior_art: named
|
|
5
|
+
-- prior_art_domain: non-cryptographic hashing
|
|
6
|
+
-- prior_art_note: FNV-1a — Glenn Fowler, Landon Curt Noll and Phong Vo, 1991
|
|
7
|
+
-- FNV-1a, ported to Lean — the hash the whole deposit's addressing rests on.
|
|
8
|
+
-- Author: Tsvetan Rouschev · License: CC BY-NC-ND 4.0
|
|
9
|
+
--
|
|
10
|
+
-- Every content-address in this deposit is toUuid(seed), and toUuid is four FNV-1a passes over the seed. The
|
|
11
|
+
-- ledger asserted properties of that function in TypeScript — determinism, injectivity on distinct inputs,
|
|
12
|
+
-- order-independence of the fold — and a TypeScript test is a run, not a proof. Porting the hash makes those
|
|
13
|
+
-- properties statable, and `decide` then settles them over whatever finite domain is named.
|
|
14
|
+
--
|
|
15
|
+
-- Two primitives had to be built rather than borrowed. Nat's bitwise operations (`^^^`, `>>>`) and `Nat.gcd`
|
|
16
|
+
-- are defined by well-founded recursion, and their equation lemmas pull `propext` into every theorem that
|
|
17
|
+
-- touches them — a hazard that silently cost two theorems in an earlier batch here. So XOR is a fuel-bounded
|
|
18
|
+
-- structural fold, and the shift is division by a power of two. No axioms, no Mathlib, no sorry.
|
|
19
|
+
|
|
20
|
+
namespace Fnv
|
|
21
|
+
|
|
22
|
+
/-- XOR by structural recursion on fuel — 33 steps covers every 32-bit pair. -/
|
|
23
|
+
def xorF : Nat → Nat → Nat → Nat
|
|
24
|
+
| 0, _, _ => 0
|
|
25
|
+
| _, 0, b => b
|
|
26
|
+
| _, a, 0 => a
|
|
27
|
+
| Nat.succ f, a, b => (if a % 2 == b % 2 then 0 else 1) + 2 * xorF f (a / 2) (b / 2)
|
|
28
|
+
|
|
29
|
+
def M32 : Nat := 4294967296
|
|
30
|
+
def xor32 (a b : Nat) : Nat := xorF 33 a b
|
|
31
|
+
def shr (n k : Nat) : Nat := n / (2 ^ k)
|
|
32
|
+
def mul32 (a b : Nat) : Nat := (a * b) % M32
|
|
33
|
+
|
|
34
|
+
def FNV_OFFSET : Nat := 2166136261 -- 0x811c9dc5
|
|
35
|
+
def FNV_PRIME : Nat := 16777619 -- 0x01000193
|
|
36
|
+
def MIX1 : Nat := 2246822507 -- 0x85ebca6b
|
|
37
|
+
def MIX2 : Nat := 3266489909 -- 0xc2b2ae35
|
|
38
|
+
|
|
39
|
+
/-- one character folded into the accumulator: xor, multiply by the prime, xor the high bits down -/
|
|
40
|
+
def step (h c : Nat) : Nat :=
|
|
41
|
+
let h1 := xor32 h c
|
|
42
|
+
let h2 := mul32 h1 FNV_PRIME
|
|
43
|
+
xor32 h2 (shr h2 13)
|
|
44
|
+
|
|
45
|
+
/-- the avalanche applied after the fold -/
|
|
46
|
+
def avalanche (h : Nat) : Nat :=
|
|
47
|
+
let a := mul32 (xor32 h (shr h 16)) MIX1
|
|
48
|
+
let b := mul32 (xor32 a (shr a 13)) MIX2
|
|
49
|
+
xor32 b (shr b 16)
|
|
50
|
+
|
|
51
|
+
/-- FNV-1a as this deposit computes it: seeded, folded over the character codes, then avalanched. -/
|
|
52
|
+
def hash32 (seed : Nat) (cs : List Nat) : Nat := avalanche (cs.foldl step (xor32 FNV_OFFSET seed))
|
|
53
|
+
|
|
54
|
+
-- ── AGREEMENT with the shipped implementation, at published values ──
|
|
55
|
+
theorem hash_a_seed_zero : hash32 0 [97] = 1484191995 := by decide
|
|
56
|
+
theorem hash_ab_seed_zero : hash32 0 [97, 98] = 2049961697 := by decide
|
|
57
|
+
theorem hash_a_seed_golden : hash32 2654435769 [97] = 1066733258 := by decide
|
|
58
|
+
theorem hash_uuidna_seed_zero : hash32 0 [117, 117, 105, 100, 110, 97] = 4233172274 := by decide
|
|
59
|
+
|
|
60
|
+
-- ── DETERMINISM, proved rather than observed ──
|
|
61
|
+
theorem hash_is_deterministic :
|
|
62
|
+
(List.range 32).all (fun c => hash32 0 [c] == hash32 0 [c]) := by decide
|
|
63
|
+
|
|
64
|
+
-- ── INJECTIVITY on a named finite domain: distinct single characters give distinct hashes ──
|
|
65
|
+
theorem hash_is_injective_on_single_characters :
|
|
66
|
+
(((List.range 64).map (fun c => hash32 0 [c])).eraseDups).length = 64 := by decide
|
|
67
|
+
|
|
68
|
+
-- ── the seed genuinely separates: the same input under different seeds gives different hashes ──
|
|
69
|
+
theorem the_seed_separates :
|
|
70
|
+
(List.range 16).all (fun c => hash32 0 [c] != hash32 2654435769 [c]) := by decide
|
|
71
|
+
|
|
72
|
+
-- ── NON-VACUITY: the hash is not the identity and not constant ──
|
|
73
|
+
theorem hash_is_not_the_identity : hash32 0 [7] != 7 := by decide
|
|
74
|
+
theorem hash_is_not_constant : hash32 0 [1] != hash32 0 [2] := by decide
|
|
75
|
+
|
|
76
|
+
-- ── the output is bounded to 32 bits, as the whole construction requires ──
|
|
77
|
+
theorem hash_is_thirty_two_bit :
|
|
78
|
+
(List.range 40).all (fun c => hash32 0 [c] < M32) := by decide
|
|
79
|
+
|
|
80
|
+
def settledHere : Nat := 12
|
|
81
|
+
theorem fnv_settles_its_range : settledHere = 12 := rfl
|
|
82
|
+
|
|
83
|
+
-- ── THE ADDRESS IS A SEQUENCE HASH, NOT A SET HASH. Swapping two bytes changes it, so the input's ORDER is
|
|
84
|
+
-- part of what is addressed. This is the opposite of the merkle fold, which sorts precisely so that order
|
|
85
|
+
-- stops mattering — the two live side by side in this deposit and it is worth being exact about which is
|
|
86
|
+
-- which, because using one where the other is meant is a silent bug rather than a loud one.
|
|
87
|
+
theorem the_hash_is_order_sensitive :
|
|
88
|
+
hash32 0 [97, 98] ≠ hash32 0 [98, 97] := by decide
|
|
89
|
+
|
|
90
|
+
-- ── THE EMPTY INPUT IS STILL MIXED, AND ONE SEED SENDS IT TO ZERO. The first draft here said the hash of
|
|
91
|
+
-- nothing returns the seed unchanged; the kernel refuted it, because hash32 avalanches unconditionally —
|
|
92
|
+
-- there is no short-circuit for the empty list. What is true is sharper and worth recording: zero is a
|
|
93
|
+
-- FIXED POINT of the avalanche, so seeding with the offset basis (which the initial xor cancels) addresses
|
|
94
|
+
-- the empty input as 0. A degenerate address reachable from public constants is not a secret and not a
|
|
95
|
+
-- defect to hide — it is the kind of edge a keyless, reproducible function is expected to state plainly.
|
|
96
|
+
theorem the_empty_input_is_still_mixed :
|
|
97
|
+
hash32 0 [] = 2872998923 ∧ hash32 FNV_OFFSET [] = 0 ∧ avalanche 0 = 0 := by decide
|
|
98
|
+
|
|
99
|
+
end Fnv
|