From 94c45c3f0004e03d1476d97ed4537324b472aaa5 Mon Sep 17 00:00:00 2001 From: Tobias Grosser Date: Mon, 12 Jan 2026 08:52:18 +0000 Subject: [PATCH] feat: add BitVec induction cons|concat induction principles (#11767) This PR introduces two induction principles for bitvectors, based on the concat and cons operations. We show how this principle can be useful to reason about bitvectors by refactoring two population count lemmas (`cpopNatRec_zero_le` and `toNat_cpop_append`) and introducing a new lemma (`toNat_cpop_not`). To use the induction principle we also move `cpopNatRec_cons_of_le` and `cpopNatRec_cons_of_lt` earlier in the popcount section (they are the building blocks enabling us to take advantage of the new induction principle). --------- Co-authored-by: luisacicolini Co-authored-by: Luisa Cicolini <48860705+luisacicolini@users.noreply.github.com> --- src/Init/Data/BitVec/Bootstrap.lean | 13 +++ src/Init/Data/BitVec/Lemmas.lean | 171 ++++++++++++++++------------ 2 files changed, 111 insertions(+), 73 deletions(-) diff --git a/src/Init/Data/BitVec/Bootstrap.lean b/src/Init/Data/BitVec/Bootstrap.lean index b62add87c8..c7c385ef69 100644 --- a/src/Init/Data/BitVec/Bootstrap.lean +++ b/src/Init/Data/BitVec/Bootstrap.lean @@ -159,4 +159,17 @@ theorem setWidth_neg_of_le {x : BitVec v} (h : w ≤ v) : BitVec.setWidth w (-x) omega omega +@[induction_eliminator, elab_as_elim] +theorem cons_induction {motive : (w : Nat) → BitVec w → Prop} (nil : motive 0 .nil) + (cons : ∀ {w : Nat} (b : Bool) (bv : BitVec w), motive w bv → motive (w + 1) (.cons b bv)) : + ∀ {w : Nat} (x : BitVec w), motive w x := by + intros w x + induction w + case zero => + simp only [BitVec.eq_nil x, nil] + case succ wl ih => + rw [← cons_msb_setWidth x] + apply cons + apply ih + end BitVec diff --git a/src/Init/Data/BitVec/Lemmas.lean b/src/Init/Data/BitVec/Lemmas.lean index d54e589117..e47572283f 100644 --- a/src/Init/Data/BitVec/Lemmas.lean +++ b/src/Init/Data/BitVec/Lemmas.lean @@ -3362,6 +3362,26 @@ theorem extractLsb'_concat {x : BitVec (w + 1)} {y : Bool} : · simp · simp [show i - 1 < t by omega] +theorem concat_extractLsb'_getLsb {x : BitVec (w + 1)} : + BitVec.concat (x.extractLsb' 1 w) (x.getLsb 0) = x := by + ext i hw + by_cases h : i = 0 + · simp [h] + · simp [h, hw, show (1 + (i - 1)) = i by omega, getElem_concat] + +@[elab_as_elim] +theorem concat_induction {motive : (w : Nat) → BitVec w → Prop} (nil : motive 0 .nil) + (concat : ∀ {w : Nat} (bv : BitVec w) (b : Bool), motive w bv → motive (w + 1) (bv.concat b)) : + ∀ {w : Nat} (x : BitVec w), motive w x := by + intros w x + induction w + case zero => + simp only [BitVec.eq_nil x, nil] + case succ wl ih => + rw [← concat_extractLsb'_getLsb (x := x)] + apply concat + apply ih + /-! ### shiftConcat -/ @[grind =] @@ -6383,73 +6403,6 @@ theorem cpopNatRec_add {x : BitVec w} {acc n : Nat} : x.cpopNatRec n (acc + acc') = x.cpopNatRec n acc + acc' := by rw [cpopNatRec_eq (acc := acc + acc'), cpopNatRec_eq (acc := acc), Nat.add_assoc] -theorem cpopNatRec_le {x : BitVec w} (n : Nat) : - x.cpopNatRec n acc ≤ acc + n := by - induction n generalizing acc - · case zero => - simp - · case succ n ihn => - have : (x.getLsbD n).toNat ≤ 1 := by cases x.getLsbD n <;> simp - specialize ihn (acc := acc + (x.getLsbD n).toNat) - simp - omega - -@[simp] -theorem cpopNatRec_of_le {x : BitVec w} (k n : Nat) (hn : w ≤ n) : - x.cpopNatRec (n + k) acc = x.cpopNatRec n acc := by - induction k - · case zero => - simp - · case succ k ihk => - simp [show n + (k + 1) = (n + k) + 1 by omega, ihk, show w ≤ n + k by omega] - -theorem cpopNatRec_zero_le (x : BitVec w) (n : Nat) : - x.cpopNatRec n 0 ≤ w := by - induction n - · case zero => - simp - · case succ n ihn => - by_cases hle : n ≤ w - · by_cases hx : x.getLsbD n - · have := cpopNatRec_le (x := x) (acc := 1) (by omega) - have := lt_of_getLsbD hx - simp [hx] - omega - · have := cpopNatRec_le (x := x) (acc := 0) (by omega) - simp [hx] - omega - · simp [show w ≤ n by omega] - omega - -@[simp] -theorem cpopNatRec_allOnes (h : n ≤ w) : - (allOnes w).cpopNatRec n acc = acc + n := by - induction n - · case zero => - simp - · case succ n ihn => - specialize ihn (by omega) - simp [show n < w by omega, ihn, - cpopNatRec_add (acc := acc) (acc' := 1)] - omega - -@[simp] -theorem cpop_allOnes : - (allOnes w).cpop = BitVec.ofNat w w := by - simp [cpop, cpopNatRec_allOnes] - -@[simp] -theorem cpop_zero : - (0#w).cpop = 0#w := by - simp [cpop] - -theorem toNat_cpop_le (x : BitVec w) : - x.cpop.toNat ≤ w := by - have hlt := Nat.lt_two_pow_self (n := w) - have hle := cpopNatRec_zero_le (x := x) (n := w) - simp only [cpop, toNat_ofNat, ge_iff_le] - rw [Nat.mod_eq_of_lt (by omega)] - exact hle @[simp] theorem cpopNatRec_cons_of_le {x : BitVec w} {b : Bool} (hn : n ≤ w) : @@ -6475,6 +6428,68 @@ theorem cpopNatRec_cons_of_lt {x : BitVec w} {b : Bool} (hn : w < n) : · simp [show w = n by omega, getElem_cons, cpopNatRec_add (acc := acc) (acc' := b.toNat), Nat.add_comm] +theorem cpopNatRec_le {x : BitVec w} (n : Nat) : + x.cpopNatRec n acc ≤ acc + n := by + induction n generalizing acc + · case zero => + simp + · case succ n ihn => + have : (x.getLsbD n).toNat ≤ 1 := by cases x.getLsbD n <;> simp + specialize ihn (acc := acc + (x.getLsbD n).toNat) + simp + omega + +@[simp] +theorem cpopNatRec_of_le {x : BitVec w} (k n : Nat) (hn : w ≤ n) : + x.cpopNatRec (n + k) acc = x.cpopNatRec n acc := by + induction k + · case zero => + simp + · case succ k ihk => + simp [show n + (k + 1) = (n + k) + 1 by omega, ihk, show w ≤ n + k by omega] + +@[simp] +theorem cpopNatRec_allOnes (h : n ≤ w) : + (allOnes w).cpopNatRec n acc = acc + n := by + induction n + · case zero => + simp + · case succ n ihn => + specialize ihn (by omega) + simp [show n < w by omega, ihn, + cpopNatRec_add (acc := acc) (acc' := 1)] + omega + +@[simp] +theorem cpop_allOnes : + (allOnes w).cpop = BitVec.ofNat w w := by + simp [cpop, cpopNatRec_allOnes] + +@[simp] +theorem cpop_zero : + (0#w).cpop = 0#w := by + simp [cpop] + +theorem cpopNatRec_zero_le (x : BitVec w) (n : Nat) : + x.cpopNatRec n 0 ≤ w := by + induction x + · case nil => simp + · case cons w b bv ih => + by_cases hle : n ≤ w + · have := cpopNatRec_cons_of_le (b := b) (x := bv) (n := n) (acc := 0) hle + omega + · rw [cpopNatRec_cons_of_lt (by omega)] + have : b.toNat ≤ 1 := by cases b <;> simp + omega + +theorem toNat_cpop_le (x : BitVec w) : + x.cpop.toNat ≤ w := by + have hlt := Nat.lt_two_pow_self (n := w) + have hle := cpopNatRec_zero_le (x := x) (n := w) + simp only [cpop, toNat_ofNat, ge_iff_le] + rw [Nat.mod_eq_of_lt (by omega)] + exact hle + theorem cpopNatRec_concat_of_lt {x : BitVec w} {b : Bool} (hn : 0 < n) : (concat x b).cpopNatRec n acc = b.toNat + x.cpopNatRec (n - 1) acc := by induction n generalizing acc @@ -6572,12 +6587,12 @@ theorem cpop_cast (x : BitVec w) (h : w = v) : @[simp] theorem toNat_cpop_append {x : BitVec w} {y : BitVec u} : (x ++ y).cpop.toNat = x.cpop.toNat + y.cpop.toNat := by - induction w generalizing u - · case zero => - simp [cpop] - · case succ w ihw => - rw [← cons_msb_setWidth x, toNat_cpop_cons, cons_append, cpop_cast, toNat_cast, - toNat_cpop_cons, ihw, ← Nat.add_assoc] + induction x generalizing y + · case nil => + simp + · case cons w b bv ih => + simp [cons_append, ih] + omega theorem cpop_append {x : BitVec w} {y : BitVec u} : (x ++ y).cpop = x.cpop.setWidth (w + u) + y.cpop.setWidth (w + u) := by @@ -6588,4 +6603,14 @@ theorem cpop_append {x : BitVec w} {y : BitVec u} : simp only [toNat_cpop_append, toNat_add, toNat_setWidth, Nat.add_mod_mod, Nat.mod_add_mod] rw [Nat.mod_eq_of_lt (by omega)] +theorem toNat_cpop_not {x : BitVec w} : + (~~~x).cpop.toNat = w - x.cpop.toNat := by + induction x + · case nil => + simp + · case cons b x ih => + have := toNat_cpop_le x + cases b + <;> (simp [ih]; omega) + end BitVec