1-- The least common multiple: defined from the greatest common divisor by2-- gcd(a, b) · lcm(a, b) = a · b, characterised as the least common multiple,3-- with its units, commutativity, associativity, and behaviour on coprime4-- numbers5-- Part of Cumulus, the Grace17 mathematics library.6-- Copyright (c) 2026 Grant Passmore.7-- Released under the Apache License 2.0. See LICENSE in the repository root.8-- Authors: Grant Passmore910module Grace.Cumulus.NumberTheory.LCM11import GCDMore1213predicate IsLCM (a b m : Nat) :=14 a ∣ m ∧ b ∣ m ∧ (∀ c, Nat(c) → a ∣ c → b ∣ c → m ∣ c)1516theorem IsLCM.intro (a b m : Nat)17requiresleft : a ∣ m18requiresright : b ∣ m19requires least : ∀ c, Nat(c) → a ∣ c → b ∣ c → m ∣ c20shows IsLCM(a, b, m)21proof22exact⟨left, right, least⟩2324theorem IsLCM.divides_left (a b m : Nat)25requires lcm : IsLCM(a, b, m)26shows a ∣ m27proof28exact lcm.left2930theorem IsLCM.divides_right (a b m : Nat)31requires lcm : IsLCM(a, b, m)32shows b ∣ m33proof34exact lcm.right.left3536theorem IsLCM.least (a b m c : Nat)37requires lcm : IsLCM(a, b, m)38requiresleft : a ∣ c39requiresright : b ∣ c40shows m ∣ c41proof42exact lcm.right.right with c, left, right4344theorem IsLCM.unique (a b m n : Nat)45requires first : IsLCM(a, b, m)46requires second : IsLCM(a, b, n)47shows m = n48proof49have forward := IsLCM.least at a, b, m, n50with first, second.left, second.right.left51have backward := IsLCM.least at a, b, n, m52with second, first.left, first.right.left53exact divides.antisymmetric with forward, backward5455theorem IsLCM.symmetric (a b m : Nat)56requires lcm : IsLCM(a, b, m)57shows IsLCM(b, a, m)58proof59rcases lcm with⟨left, right, least⟩60refine⟨right, left, _⟩61intros c cNat first second62exact least with c, second, first6364def lcm (a b : Nat) : Nat:= quotientNat(a * b, gcd(a, b))6566theorem lcm.natural (a b : Nat)67showsNat(lcm(a, b))68proof69unfold lcm70exact quotientNat.natural at gcd(a, b), a * b7172theorem lcm.zero_left (b : Nat)73shows lcm(0, b) =074proof75unfold lcm76have zero : 0* b =0byring77rw zero78simp [quotientNat]7980theorem lcm.zero_right (a : Nat)81shows lcm(a, 0) =082proof83unfold lcm84have zero : a *0=0byring85rw zero86simp [quotientNat]8788theorem lcm.commutative (a b : Nat)89shows lcm(a, b) = lcm(b, a)90proof91unfold lcm92have commute := gcd.commutative at a, b93have product : a * b = b * a byring94rw [commute, product]95rfl9697theorem gcd.mul_lcm (a b : Nat)98shows gcd(a, b) * lcm(a, b) = a * b99proof100havesplit : gcd(a, b) =0∨ gcd(a, b) ≠0bytauto101casessplitwith zero | nonzero102 . have both := gcd.zero_iff at a, b103have zeros := both.forward with zero104rcases zeros with⟨aZero, bZero⟩105rw [zero, aZero, bZero]106ring107 . have multiple := gcd.divides_mul at a, b, b108have equation := divides.quotient_equation at gcd(a, b), a * b109with nonzero, multiple110unfold lcm111cc112113theorem lcm.mul_gcd (a b : Nat)114shows lcm(a, b) * gcd(a, b) = a * b115proof116have product := gcd.mul_lcm at a, b117have commute : lcm(a, b) * gcd(a, b) = gcd(a, b) * lcm(a, b) byring118rw commute119exact product120121-- The cofactors of a and b against their greatest common divisor are coprime.122theorem gcd.cofactor_coprime (a b p q : Nat)123requires nonzero : gcd(a, b) ≠0124requiresleft : gcd(a, b) * p = a125requiresright : gcd(a, b) * q = b126shows gcd(p, q) =1127proof128have distribute := gcd.mul_left_distribute at p, q, gcd(a, b)129rw [left, right] at distribute130have products : gcd(a, b) * gcd(p, q) = gcd(a, b) *1131have one : gcd(a, b) *1= gcd(a, b) byring132rw one133 symm134exact distribute135exact mul.left_cancel_positive at gcd(p, q), 1, gcd(a, b)136with nonzero, products137138theorem lcm.cofactor_form (a b p q : Nat)139requires nonzero : gcd(a, b) ≠0140requiresleft : gcd(a, b) * p = a141requiresright : gcd(a, b) * q = b142shows lcm(a, b) = gcd(a, b) * p * q143proof144have product := gcd.mul_lcm at a, b145have form : gcd(a, b) * lcm(a, b) = gcd(a, b) * (gcd(a, b) * p * q)146linear_combination product - a *right - (gcd(a, b) * q) *left147exact mul.left_cancel_positive at lcm(a, b), gcd(a, b) * p * q, gcd(a, b)148with nonzero, form149150theorem lcm.divides_left (a b : Nat)151shows a ∣ lcm(a, b)152proof153havesplit : gcd(a, b) =0∨ gcd(a, b) ≠0bytauto154casessplitwith zero | nonzero155 . have both := gcd.zero_iff at a, b156have zeros := both.forward with zero157rcases zeros with⟨aZero, bZero⟩158rw [aZero, bZero]159have value := lcm.zero_left at0160rw value161exact divides.reflexive at0162 . have product := gcd.mul_lcm at a, b163obtain⟨k, kNat, equation⟩:= gcd.divides_right at a, b164have rearranged : gcd(a, b) * lcm(a, b) = gcd(a, b) * (a * k)165linear_combination product - a * equation166have cancelled := mul.left_cancel_positive at lcm(a, b), a * k, gcd(a, b)167with nonzero, rearranged168witness k169 symm170exact cancelled171172theorem lcm.divides_right (a b : Nat)173shows b ∣ lcm(a, b)174proof175have commute := lcm.commutative at a, b176rw commute177exact lcm.divides_left at b, a178179theorem lcm.least (a b c : Nat)180requiresleft : a ∣ c181requiresright : b ∣ c182shows lcm(a, b) ∣ c183proof184havesplit : gcd(a, b) =0∨ gcd(a, b) ≠0bytauto185casessplitwith zero | nonzero186 . have both := gcd.zero_iff at a, b187have zeros := both.forward with zero188rcases zeros with⟨aZero, bZero⟩189rw aZero atleft190rw [aZero, bZero]191have value := lcm.zero_left at0192rw value193exactleft194 . obtain⟨p, pNat, pEquation⟩:= gcd.divides_left at a, b195obtain⟨q, qNat, qEquation⟩:= gcd.divides_right at a, b196have cofactor := gcd.cofactor_coprime at a, b, p, q197with nonzero, pEquation, qEquation198have form := lcm.cofactor_form at a, b, p, q199with nonzero, pEquation, qEquation200obtain⟨k, kNat, kEquation⟩:=left201have scaled : gcd(a, b) * q ∣ gcd(a, b) * (p * k)202rw qEquation203have shape : gcd(a, b) * (p * k) = c204linear_combination k * pEquation + kEquation205rw shape206exactright207have cancelled := divides.mul_cancel_left at gcd(a, b), q, p * k208with nonzero209have reduced := cancelled.forward with scaled210have dividesK := coprime.cancel at p, q, k with cofactor, reduced211obtain⟨m, mNat, mEquation⟩:= dividesK212witness m213rw form214linear_combination (q * m) * pEquation + a * mEquation + kEquation215216theorem lcm.correct (a b : Nat)217shows IsLCM(a, b, lcm(a, b))218proof219haveleft:= lcm.divides_left at a, b220haveright:= lcm.divides_right at a, b221refine⟨left, right, _⟩222intros c cNat first second223exact lcm.least at a, b, c with first, second224225theorem lcm.unique (a b m : Nat)226requires certificate : IsLCM(a, b, m)227shows lcm(a, b) = m228proof229have correct := lcm.correct at a, b230exact IsLCM.unique with correct, certificate231232theorem lcm.divides_iff (a b c : Nat)233shows lcm(a, b) ∣ c ↔ a ∣ c ∧ b ∣ c234proof235constructor236 . intro h237haveleft:= lcm.divides_left at a, b238haveright:= lcm.divides_right at a, b239constructor240 . exact divides.transitive at a, lcm(a, b), c withleft, h241 . exact divides.transitive at b, lcm(a, b), c withright, h242 . intro both243rcases both with⟨left, right⟩244exact lcm.least at a, b, c withleft, right245246theorem lcm.of_divides (a b : Nat)247requires hab : a ∣ b248shows lcm(a, b) = b249proof250have reflexive := divides.reflexive at b251have certificate : IsLCM(a, b, b)252refine⟨hab, reflexive, _⟩253intros c cNat first second254exact second255exact lcm.unique with certificate256257theorem lcm.of_divides_right (a b : Nat)258requires hba : b ∣ a259shows lcm(a, b) = a260proof261have commute := lcm.commutative at a, b262rw commute263exact lcm.of_divides at b, a with hba264265theorem lcm.one_left (a : Nat)266shows lcm(1, a) = a267proof268have one := one.divides at a269exact lcm.of_divides with one270271theorem lcm.one_right (a : Nat)272shows lcm(a, 1) = a273proof274have one := one.divides at a275exact lcm.of_divides_right with one276277theorem lcm.self (a : Nat)278shows lcm(a, a) = a279proof280have reflexive := divides.reflexive at a281exact lcm.of_divides with reflexive282283theorem lcm.divides_mul (a b : Nat)284shows lcm(a, b) ∣ a * b285proof286haveleft:= divides.mul_left_self at a, b287haveright:= divides.mul_right_self at b, a288exact lcm.least at a, b, a * b withleft, right289290theorem lcm.zero_iff (a b : Nat)291shows lcm(a, b) =0↔ a =0∨ b =0292proof293constructor294 . intro zero295have product := gcd.mul_lcm at a, b296rw zero at product297have vanish : a * b =0298rw← product299ring300havesplit:= mul.eq_zero_iff at a, b301exactsplit.forward with vanish302 . introsplit303casessplitwith aZero | bZero304 . rw aZero305exact lcm.zero_left at b306 . rw bZero307exact lcm.zero_right at a308309theorem lcm.nonzero (a b : Nat)310requiresleft : a ≠0311requiresright : b ≠0312shows lcm(a, b) ≠0313proof314intro zero315havesplit:= lcm.zero_iff at a, b316have alternatives :=split.forward with zero317cases alternatives with aZero | bZero318 . exactleftwith aZero319 . exactrightwith bZero320321theorem lcm.positive (a b : Nat)322requiresleft : a ≠0323requiresright : b ≠0324shows0 < lcm(a, b)325proof326have distinct := lcm.nonzero withleft, right327exact Less.positive_of_ne_zero with distinct328329theorem lcm.ge_left (a b : Nat)330requiresleft : a ≠0331requiresright : b ≠0332shows a ≤ lcm(a, b)333proof334have multiple := lcm.divides_left at a, b335have distinct := lcm.nonzero withleft, right336exact divides.le at a, lcm(a, b) with multiple, distinct337338theorem lcm.ge_right (a b : Nat)339requiresleft : a ≠0340requiresright : b ≠0341shows b ≤ lcm(a, b)342proof343have multiple := lcm.divides_right at a, b344have distinct := lcm.nonzero withleft, right345exact divides.le at b, lcm(a, b) with multiple, distinct346347theorem lcm.le_mul (a b : Nat)348requiresleft : a ≠0349requiresright : b ≠0350shows lcm(a, b) ≤ a * b351proof352have multiple := lcm.divides_mul at a, b353have distinct := mul.nonzero withleft, right354exact divides.le at lcm(a, b), a * b with multiple, distinct355356theorem lcm.coprime (a b : Nat)357requires coprimeAB : gcd(a, b) =1358shows lcm(a, b) = a * b359proof360have product := gcd.mul_lcm at a, b361rw coprimeAB at product362have one : 1* lcm(a, b) = lcm(a, b) byring363rw one at product364exact product365366theorem lcm.eq_mul_iff (a b : Nat)367requiresleft : a ≠0368requiresright : b ≠0369shows lcm(a, b) = a * b ↔ gcd(a, b) =1370proof371constructor372 . intro same373have product := gcd.mul_lcm at a, b374rw same at product375have distinct := mul.nonzero withleft, right376have unit : gcd(a, b) * (a * b) =1* (a * b)377rw product378ring379have swapLeft : gcd(a, b) * (a * b) = (a * b) * gcd(a, b) byring380have swapRight : 1* (a * b) = (a * b) *1byring381rw [swapLeft, swapRight] at unit382exact mul.left_cancel_positive at gcd(a, b), 1, a * b with distinct, unit383 . intro coprimeAB384exact lcm.coprime with coprimeAB385386theorem gcd.divides_lcm (a b : Nat)387shows gcd(a, b) ∣ lcm(a, b)388proof389haveleft:= gcd.divides_left at a, b390have multiple := lcm.divides_left at a, b391exact divides.transitive at gcd(a, b), a, lcm(a, b) withleft, multiple392393theorem lcm.associative (a b c : Nat)394shows lcm(lcm(a, b), c) = lcm(a, lcm(b, c))395proof396have innerLeft := lcm.divides_left at a, b397have innerRight := lcm.divides_right at a, b398have outerLeft := lcm.divides_left at lcm(a, b), c399have outerRight := lcm.divides_right at lcm(a, b), c400have dividesA := divides.transitive at a, lcm(a, b), lcm(lcm(a, b), c)401with innerLeft, outerLeft402have dividesB := divides.transitive at b, lcm(a, b), lcm(lcm(a, b), c)403with innerRight, outerLeft404have dividesBC := lcm.least at b, c, lcm(lcm(a, b), c)405with dividesB, outerRight406have certificate : IsLCM(a, lcm(b, c), lcm(lcm(a, b), c))407refine⟨dividesA, dividesBC, _⟩408intros d dNat dA dBC409haveleft:= lcm.divides_left at b, c410haveright:= lcm.divides_right at b, c411have dB := divides.transitive at b, lcm(b, c), d withleft, dBC412have dC := divides.transitive at c, lcm(b, c), d withright, dBC413have dAB := lcm.least at a, b, d with dA, dB414exact lcm.least at lcm(a, b), c, d with dAB, dC415 symm416exact lcm.unique with certificate417418theorem lcm.mul_right_distribute (a b c : Nat)419shows lcm(a * c, b * c) = lcm(a, b) * c420proof421havesplit : c =0∨ c ≠0bytauto422casessplitwith zero | nonzero423 . rw zero424haveleft : a *0=0byring425haveright : b *0=0byring426rw [left, right]427have value := lcm.zero_left at0428rw value429ring430 . have innerLeft := lcm.divides_left at a, b431have innerRight := lcm.divides_right at a, b432have reflexive := divides.reflexive at c433have scaledLeft := divides.mul_mono at a, lcm(a, b), c, c434with innerLeft, reflexive435have scaledRight := divides.mul_mono at b, lcm(a, b), c, c436with innerRight, reflexive437have certificate : IsLCM(a * c, b * c, lcm(a, b) * c)438refine⟨scaledLeft, scaledRight, _⟩439intros d dNat dA dB440have cDivides := divides.of_mul_right at a, c, d with dA441obtain⟨e, eNat, eEquation⟩:= cDivides442have shapeA : a * c ∣ e * c443have commute : e * c = c * e byring444rw [commute, eEquation]445exact dA446have shapeB : b * c ∣ e * c447have commute : e * c = c * e byring448rw [commute, eEquation]449exact dB450have cancelA := divides.mul_cancel_right at c, a, e with nonzero451have cancelB := divides.mul_cancel_right at c, b, e with nonzero452have eA := cancelA.forward with shapeA453have eB := cancelB.forward with shapeB454have least := lcm.least at a, b, e with eA, eB455have scaled := divides.mul_mono at lcm(a, b), e, c, c456with least, reflexive457have commute : e * c = c * e byring458rw [commute, eEquation] at scaled459exact scaled460exact lcm.unique with certificate461462theorem lcm.mul_left_distribute (a b c : Nat)463shows lcm(c * a, c * b) = c * lcm(a, b)464proof465haveleft : c * a = a * c byring466haveright : c * b = b * c byring467have product : lcm(a, b) * c = c * lcm(a, b) byring468rw [left, right, ← product]469exact lcm.mul_right_distribute at a, b, c470