1-- The residues coprime to n as a list, Euler's totient as its length, the2-- list scaled by a coprime factor, and what scaling does to products and to3-- multiplicities: it permutes the units4-- Part of Cumulus, the Grace17 mathematics library.5-- Copyright (c) 2026 Grant Passmore.6-- Released under the Apache License 2.0. See LICENSE in the repository root.7-- Authors: Grant Passmore89module Grace.Cumulus.NumberTheory.ResidueLists10import ModularInverse1112-- Prepend x exactly when the test holds.13def consIf (test : Bool, x : Nat, xs : List(ℕ)) → List(ℕ)14| Bool.false, x, xs => xs15| Bool.true, x, xs => x :: xs1617theorem consIf.false (x : Nat) (xs : List(ℕ))18shows consIf(Bool.false, x, xs) = xs19proof20simp [consIf]2122theorem consIf.true (x : Nat) (xs : List(ℕ))23shows consIf(Bool.true, x, xs) = x :: xs24proof25simp [consIf]2627-- The residues below m that are coprime to n.28def unitsBelow (n m : Nat) → List(ℕ)29| n, zero => []30| n, succ(m) => consIf(equalNat(gcd(m, n), 1), m, unitsBelow(n, m))31descends m3233theorem unitsBelow.zero (n : Nat)34shows unitsBelow(n, 0) = []35proof36simp [unitsBelow]3738theorem unitsBelow.succ_unit (n m : Nat)39requires coprime : gcd(m, n) =140shows unitsBelow(n, succ(m)) = m :: unitsBelow(n, m)41proof42simp [unitsBelow, coprime, equalNat.reflexive, consIf]4344theorem unitsBelow.succ_other (n m : Nat)45requires notCoprime : gcd(m, n) ≠146shows unitsBelow(n, succ(m)) = unitsBelow(n, m)47proof48have test := equalNat.false_iff.backward with notCoprime49simp [unitsBelow, test, consIf]5051theorem unitsBelow.list (n m : Nat)52shows List(ℕ, unitsBelow(n, m))53proof54induction m55| zero =>56rw unitsBelow.zero57typecheck58| succ(j) using ih =>59have alternatives : gcd(j, n) =1∨ gcd(j, n) ≠1bytauto60cases alternatives with coprime | notCoprime61 . have step := unitsBelow.succ_unit at n, j with coprime62rw step63typecheck64 . have step := unitsBelow.succ_other at n, j with notCoprime65rw step66exact ih6768-- A residue that is not below m, or not coprime to n, does not occur.69theorem unitsBelow.multiplicity_other (n m : Nat)70shows∀ k, Nat(k) →¬(k < m ∧ gcd(k, n) =1) → multiplicity(k, unitsBelow(n, m)) =071proof72induction m73| zero =>74intros k kNat notBoth75rw unitsBelow.zero76exact multiplicity.nil at k77| succ(j) using ih =>78intros k kNat notBoth79have restList := unitsBelow.list at n, j80have smaller : ¬(k < j ∧ gcd(k, n) =1)81intro both82obtain⟨kBelow, kCoprime⟩:= both83apply notBoth84constructor85 . have below : k < succ(j) byarith86exact below87 . exact kCoprime88have previous := ih with k, kNat, smaller89have alternatives : gcd(j, n) =1∨ gcd(j, n) ≠1bytauto90cases alternatives with coprime | notCoprime91 . have step := unitsBelow.succ_unit at n, j with coprime92rw step93have different : k ≠ j94intro same95apply notBoth96rw same97constructor98 . arith99 . exact coprime100have other := multiplicity.cons_other at k, j, unitsBelow(n, j) with different101rw other102exact previous103 . have step := unitsBelow.succ_other at n, j with notCoprime104rw step105exact previous106107-- A coprime residue below m occurs exactly once.108theorem unitsBelow.multiplicity_unit (n m : Nat)109shows∀ k, Nat(k) → k < m → gcd(k, n) =1→ multiplicity(k, unitsBelow(n, m)) =1110proof111induction m112| zero =>113intros k kNat below coprime114exfalso115have impossible : ¬k < 0byarith116exact impossible with below117| succ(j) using ih =>118intros k kNat below coprime119have restList := unitsBelow.list at n, j120have trichotomy := Less.trichotomy at k, j121rcases trichotomy with smaller | same | larger122 . have previous := ih with k, kNat, smaller, coprime123have alternatives : gcd(j, n) =1∨ gcd(j, n) ≠1bytauto124cases alternatives with coprimeJ | notCoprimeJ125 . have step := unitsBelow.succ_unit at n, j with coprimeJ126rw step127have different : k ≠ j128intro equal129rw equal at smaller130exact Less.irreflexive at j with*, smaller131have other := multiplicity.cons_other at k, j, unitsBelow(n, j) with different132rw other133exact previous134 . have step := unitsBelow.succ_other at n, j with notCoprimeJ135rw step136exact previous137 . rw same at coprime138have step := unitsBelow.succ_unit at n, j with coprime139rw [same, step]140haveself:= multiplicity.cons_self at j, unitsBelow(n, j) with*141rwself142have notBoth : ¬(j < j ∧ gcd(j, n) =1)143intro both144exact Less.irreflexive at j with*, both.left145have others := unitsBelow.multiplicity_other at n, j146have jNat : Nat(j) bytypecheck147have zero := others with j, jNat, notBoth148rw zero149ring150 . exfalso151have impossible : ¬j < k byarith152exact impossible with larger153154theorem unitsBelow.member (n m k : Nat)155requires positive : 0 < multiplicity(k, unitsBelow(n, m))156shows k < m ∧ gcd(k, n) =1157proof158by_contra none159have others := unitsBelow.multiplicity_other at n, m160have kNat : Nat(k) bytypecheck161have zero := others with k, none162rw zero at positive163exact Less.irreflexive with*, positive164165-- The units mod n and Euler's totient.166def units (n : Nat) : Set:= unitsBelow(n, n)167168def totient (n : Nat) : Nat:= length{ℕ}(unitsBelow(n, n))169170theorem units.definition (n : Nat)171shows units(n) = unitsBelow(n, n)172proof173unfold units174rfl175176theorem totient.definition (n : Nat)177shows totient(n) = length{ℕ}(units(n))178proof179unfold totient180rw units.definition181rfl182183theorem units.list (n : Nat)184shows List(ℕ, units(n))185proof186rw units.definition187exact unitsBelow.list188189theorem units.multiplicity (n k : Nat)190requires below : k < n191requires coprime : gcd(k, n) =1192shows multiplicity(k, units(n)) =1193proof194rw units.definition195have all := unitsBelow.multiplicity_unit at n, n196have kNat : Nat(k) bytypecheck197exact all with k, below, coprime198199theorem units.multiplicity_other (n k : Nat)200requires notBoth : ¬(k < n ∧ gcd(k, n) =1)201shows multiplicity(k, units(n)) =0202proof203rw units.definition204have all := unitsBelow.multiplicity_other at n, n205have kNat : Nat(k) bytypecheck206exact all with k, notBoth207208theorem units.member (n k : Nat)209requires positive : 0 < multiplicity(k, units(n))210shows k < n ∧ gcd(k, n) =1211proof212rw units.definition at positive213exact unitsBelow.member with positive214215-- Scaling every entry by a and reducing mod n.216def scaledList (a n : Nat, xs : List(ℕ)) → List(ℕ)217| a, n, [] => []218| a, n, x :: rest => mul(a, x) % n :: scaledList(a, n, rest)219220theorem scaledList.nil (a n : Nat)221shows scaledList(a, n, []) = []222proof223simp [scaledList]224225theorem scaledList.cons (a n x : Nat) (rest : List(ℕ))226shows scaledList(a, n, x :: rest) = ((a * x) % n) :: scaledList(a, n, rest)227proof228simp [scaledList]229230theorem scaledList.list (a n : Nat) (xs : List(ℕ))231shows List(ℕ, scaledList(a, n, xs))232proof233induction xs234| List.nil =>235rw scaledList.nil236typecheck237| List.cons(x, rest) using ih =>238rw scaledList.cons239typecheck240241theorem scaledList.length (a n : Nat) (xs : List(ℕ))242shows length{ℕ}(scaledList(a, n, xs)) = length{ℕ}(xs)243proof244induction xs245| List.nil =>246rw scaledList.nil247rfl248| List.cons(x, rest) using ih =>249have scaledRest := scaledList.list at a, n, rest250have step := scaledList.cons at a, n, x, rest251haveleft:= length.cons at ℕ, (a * x) % n, scaledList(a, n, rest) with*252haveright:= length.cons at ℕ, x, rest with*253rw step254rwleft255rwright256rw ih257rfl258259-- The product of the scaled list is a^length times the product, mod n.260theorem scaledList.product (a n : Nat) (xs : List(ℕ))261requires nonzero : n ≠0262shows (∏ scaledList(a, n, xs)) % n = (a ^ length{ℕ}(xs) *∏ xs) % n263proof264induction xs265| List.nil =>266have one : 1*1=1byring267rw [scaledList.nil, listProduct.nil, length.nil, pow.zero, one]268rfl269| List.cons(x, rest) using ih =>270have scaledRest := scaledList.list at a, n, rest271have expandScaled := listProduct.cons at (a * x) % n, scaledList(a, n, rest) with*272have expandRest := listProduct.cons at x, rest with*273have lengthRest := length.cons with*274have scaledProduct : Nat(∏ scaledList(a, n, rest)) bytypecheck275have restProduct : Nat(∏ rest) bytypecheck276have powerNat := pow.closed at a, length{ℕ}(rest)277have first := mod.mul_left at n, a * x, ∏ scaledList(a, n, rest) with nonzero278have second := mod.mul_right at n, a * x, ∏ scaledList(a, n, rest) with nonzero279have third := mod.mul_right at n, a * x, a ^ length{ℕ}(rest) *∏ rest with nonzero280have rearrange : a * x * (a ^ length{ℕ}(rest) *∏ rest) = a * a ^ length{ℕ}(rest) * (x *∏ rest) byring281rw [scaledList.cons, expandScaled, expandRest, lengthRest, pow.succ, first, ← second, ih, third, rearrange]282rfl283284-- Every entry of the scaled list is a residue below n.285theorem scaledList.member (a n : Nat) (xs : List(ℕ))286requires nonzero : n ≠0287shows∀ v, Nat(v) →0 < multiplicity(v, scaledList(a, n, xs)) → v < n288proof289induction xs290| List.nil =>291intros v vNat positive292rw [scaledList.nil, multiplicity.nil] at positive293exfalso294exact Less.irreflexive at0with*, positive295| List.cons(x, rest) using ih =>296intros v vNat positive297have scaledRest := scaledList.list at a, n, rest298rw scaledList.cons at positive299have alternatives : v = (a * x) % n ∨ v ≠ (a * x) % n bytauto300cases alternatives with same | different301 . rw same302exact mod.less at a * x, n with nonzero303 . have other := multiplicity.cons_other at v, (a * x) % n, scaledList(a, n, rest) with different304rw other at positive305exact ih with v, vNat, positive306307-- With b inverse to a, the entry v of the scaled list is counted as many308-- times as b·v is counted in the original list of residues.309theorem scaledList.multiplicity (a b n v : Nat) (xs : List(ℕ))310requires nonzero : n ≠0311requires inverse : (a * b) % n =1312requires vBelow : v < n313shows (∀ x, Nat(x) →0 < multiplicity(x, xs) → x < n) → multiplicity(v, scaledList(a, n, xs)) = multiplicity((b * v) % n, xs)314proof315induction xs316| List.nil =>317intro residues318rw [scaledList.nil, multiplicity.nil, multiplicity.nil]319rfl320| List.cons(x, rest) using ih =>321intro residues322have scaledRest := scaledList.list at a, n, rest323have xPositive : 0 < multiplicity(x, x :: rest)324haveself:= multiplicity.cons_self at x, rest with*325rwself326arith327have xNat : Nat(x) bytypecheck328have xBelow := residues with x, xNat, xPositive329have restResidues : ∀ y, Nat(y) →0 < multiplicity(y, rest) → y < n330intros y yNat positive331have lifted := multiplicity.positive_cons at y, x, rest with positive332exact residues with y, yNat, lifted333have previous := ih with restResidues334have bvNat : Nat((b * v) % n) bytypecheck335have cancelLeft : (b * ((a * x) % n)) % n = x336have step1 := mod.mul_right at n, b, a * x with nonzero337have rearrange : b * (a * x) = (a * b) * x byring338have step2 := mod.mul_left at n, a * b, x with nonzero339have one : 1* x = x byring340haveself:= mod.of_less at n, x with xBelow341rw [step1, rearrange, ← step2, inverse, one, self]342rfl343have cancelRight : (a * ((b * v) % n)) % n = v344have step1 := mod.mul_right at n, a, b * v with nonzero345have rearrange : a * (b * v) = (a * b) * v byring346have step2 := mod.mul_left at n, a * b, v with nonzero347have one : 1* v = v byring348haveself:= mod.of_less at n, v with vBelow349rw [step1, rearrange, ← step2, inverse, one, self]350rfl351rw scaledList.cons352have alternatives : x = (b * v) % n ∨ x ≠ (b * v) % n bytauto353cases alternatives with same | different354 . have hit : (a * x) % n = v355rw same356exact cancelRight357rw hit358have leftSelf := multiplicity.cons_self at v, scaledList(a, n, rest) with*359rw← same360have rightSelf := multiplicity.cons_self at x, rest with*361rw [leftSelf, rightSelf]362rw same363rw previous364rfl365 . have miss : v ≠ (a * x) % n366intro equal367apply different368rw [← cancelLeft, ← equal]369rfl370have leftOther := multiplicity.cons_other at v, (a * x) % n, scaledList(a, n, rest) with miss371have differentReversed : (b * v) % n ≠ x372intro equal373apply different374rw equal375rfl376have rightOther := multiplicity.cons_other at (b * v) % n, x, rest with differentReversed377rw [leftOther, rightOther]378exact previous379380-- Lists with the same multiplicities have the same product.381theorem listProduct.of_multiplicities (xs : List(ℕ))382shows∀ ys, List(ℕ, ys) → (∀ q, Nat(q) → multiplicity(q, xs) = multiplicity(q, ys)) →∏ xs =∏ ys383proof384induction xs385| List.nil =>386intros ys ysList same387have shape := List.cases with ysList388cases shape with nil | cons389 . rw nil390rfl391 . exfalso392obtain⟨y, yIn, rest, restList, equal⟩:= cons393have yNat : Nat(y) bytypecheck394have count := same with y395haveself:= multiplicity.cons_self at y, rest with*396rw [multiplicity.nil, equal, self] at count397have impossible : ¬(0:N) =1+ multiplicity(y, rest) byarith398exact impossible with count399| List.cons(x, rest) using ih =>400intros ys ysList same401have positive : 0 < multiplicity(x, ys)402have count := same with x, *403haveself:= multiplicity.cons_self at x, rest with*404rwselfat count405rw← count406arith407have remove := multiplicity.remove at x, ys with*408obtain⟨zs, zsList, product, counts⟩:= remove with positive409have restCounts : ∀ q, Nat(q) → multiplicity(q, rest) = multiplicity(q, zs)410intros q qNat411have count := same with q, qNat412have shifted := counts with q, qNat413haveleft:= multiplicity.cons at q, x, rest with*414haveright:= multiplicity.cons at q, x, zs with*415rw [left, shifted, right] at count416have cancel := add.left_cancel at multiplicity(q, rest), multiplicity(q, zs), chooseNat(equalNat(q, x), 1, 0)417exact cancel with count418have previous := ih with zs, zsList, restCounts419have expandRest := listProduct.cons at x, rest with*420have expandZs := listProduct.cons at x, zs with*421rw [expandRest, previous, ← expandZs, product]422rfl423424-- A product of residues coprime to n is coprime to n.425theorem listProduct.coprime (n : Nat) (xs : List(ℕ))426requires nonzero : n ≠0427shows (∀ x, Nat(x) →0 < multiplicity(x, xs) → gcd(x, n) =1) → gcd(∏ xs, n) =1428proof429induction xs430| List.nil =>431intro coprime432rw listProduct.nil433have divides := gcd.divides_left at1, n434have natural := gcd.natural at1, n435exact divides.one_only at gcd(1, n) with natural, divides436| List.cons(x, rest) using ih =>437intro coprime438have xPositive : 0 < multiplicity(x, x :: rest)439haveself:= multiplicity.cons_self at x, rest with*440rwself441arith442have xNat : Nat(x) bytypecheck443have xCoprime := coprime with x, xNat, xPositive444have restCoprime : ∀ y, Nat(y) →0 < multiplicity(y, rest) → gcd(y, n) =1445intros y yNat positive446have lifted := multiplicity.positive_cons at y, x, rest with positive447exact coprime with y, yNat, lifted448have previous := ih with restCoprime449rw listProduct.cons450exact coprime.mul at x, ∏ rest, n with nonzero, xCoprime, previous451