diff --git a/Complexitylib.lean b/Complexitylib.lean index b0814aeb..0bb073c4 100644 --- a/Complexitylib.lean +++ b/Complexitylib.lean @@ -6,6 +6,7 @@ Authors: Samuel Schlesinger module public import Complexitylib.Models +public import Complexitylib.Encoding public import Complexitylib.Asymptotics public import Complexitylib.TimeConstructible public import Complexitylib.Classes diff --git a/Complexitylib/Classes/NP/Internal/PairBuildTM.lean b/Complexitylib/Classes/NP/Internal/PairBuildTM.lean index 8b401373..a0bdde43 100644 --- a/Complexitylib/Classes/NP/Internal/PairBuildTM.lean +++ b/Complexitylib/Classes/NP/Internal/PairBuildTM.lean @@ -1023,7 +1023,7 @@ private theorem pairBuild_copyY_loop {k : ℕ} (yIdx pIdx : Fin k) /-- Helper: `pair (b :: xs) y = b :: b :: pair xs y`. -/ private theorem pair_build_cons_eq (b : Bool) (xs y : List Bool) : pair (b :: xs) y = b :: b :: pair xs y := by - simp [pair, List.append_assoc] + simp [pair] /-- Shift lemma: accessing `pair (b :: xs) y` at index `k+2` is the same as accessing `pair xs y` at index `k`. -/ diff --git a/Complexitylib/Classes/PPoly/Advice.lean b/Complexitylib/Classes/PPoly/Advice.lean index d9d3c2ae..e791f562 100644 --- a/Complexitylib/Classes/PPoly/Advice.lean +++ b/Complexitylib/Classes/PPoly/Advice.lean @@ -45,7 +45,7 @@ namespace Advice input. -/ theorem fixedPrefix_append (a : Advice) (x : List Bool) : a.fixedPrefix x.length ++ x = advisedInput a x := by - simp [fixedPrefix, advisedInput, pair, List.append_assoc] + simp [fixedPrefix, advisedInput, pair] /-- The fixed-prefix bit string serializes to the self-delimiting advised machine input. -/ diff --git a/Complexitylib/Encoding.lean b/Complexitylib/Encoding.lean new file mode 100644 index 00000000..89101f59 --- /dev/null +++ b/Complexitylib/Encoding.lean @@ -0,0 +1,20 @@ +/- +Copyright (c) 2026 Bolton Bailey. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bolton Bailey +-/ + +module +public import Complexitylib.Encoding.Delimit +public import Complexitylib.Encoding.Pairing +public import Complexitylib.Encoding.Bitstring + +/-! +# Encodings + +Aggregation module for the machine-independent encoding layer: the shared +self-delimiting block framing and its parsers +(`Complexitylib.Encoding.Delimit`), the pairing codec used by machine inputs +(`Complexitylib.Encoding.Pairing`), and the `BitstringEncoding` typeclass with +instances for common types (`Complexitylib.Encoding.Bitstring`). +-/ diff --git a/Complexitylib/Encoding/Bitstring.lean b/Complexitylib/Encoding/Bitstring.lean new file mode 100644 index 00000000..2fe88cfe --- /dev/null +++ b/Complexitylib/Encoding/Bitstring.lean @@ -0,0 +1,234 @@ +/- +Copyright (c) 2026 Bolton Bailey. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bolton Bailey +-/ + +module +public import Complexitylib.Encoding.Delimit +public import Mathlib.Computability.Encoding +public import Mathlib.Data.PNat.Defs +public import Mathlib.Algebra.Field.Rat + +/-! +# Bitstring Encoding Typeclass + +This file provides a typeclass-inferrable version of Mathlib's `Computability.Encoding`, +specialized to the alphabet `Bool`. + +Making it a `class` makes it easier to quickly ask if a function is computable in polynomial +time, without having to explicitly pass around the encoding. + +The idea of this class is to set up instances for common types like `Bool`, `ℕ`, `ℤ`, `ℚ`, +and instance derivations for product and list types, so that the resulting instance for any +common type will be polytime-transcodable with any other reasonable binary encoding of that +type. + +Thus, while it may not be obvious without looking carefully which of several essentially +equivalent encodings of a type is being used, we can at least be sure that for functions +between types with `BitstringEncoding` instances, questions of polynomial-time computability +are well-defined, in the sense that the formalization will capture the intended meaning. + +Multipartite data is concatenated out of *self-delimiting blocks*, framed by the single +delimiting operation `Complexity.delimit` from `Complexitylib.Encoding.Delimit` (each payload +bit doubled, terminated by the separator `[false, true]`) and parsed back by its `unpair?` — +the same framing underlying the machine-input pairing codec `Complexity.pair` +(`pair x y = delimit x ++ y`) of `Complexitylib.Encoding.Pairing`. + +This layer is deliberately independent of the machine model. The type `Bitstring` below is +unrelated to the fixed-length `BitString n := Fin n → Bool` used by the circuit layers. +-/ + + +@[expose] public section + +namespace Complexity + +/-- A canonical encoding of a type as bitstrings (`List Bool`). + +This is a class version of Mathlib's (bundled) `Computability.Encoding`, specialized to the +alphabet `Bool`. -/ +class BitstringEncoding (α : Type*) where + /-- The encoding function. -/ + encode : α → List Bool + /-- The decoding function; `none` on bitstrings that encode nothing. -/ + decode : List Bool → Option α + /-- Decoding is a left inverse of encoding. -/ + decode_encode : ∀ x, decode (encode x) = some x + +attribute [simp] BitstringEncoding.decode_encode + +namespace BitstringEncoding + +variable {α β : Type*} + +theorem encode_injective [BitstringEncoding α] : + Function.Injective (encode : α → List Bool) := fun _ _ h => + Option.some_injective _ (by rw [← decode_encode, ← decode_encode, h]) + +/-- Transport a `BitstringEncoding` along an injection `f` with partial inverse `g`. -/ +@[reducible] +def ofLeftInverse [BitstringEncoding β] (f : α → β) (g : β → Option α) + (h : ∀ x, g (f x) = some x) : BitstringEncoding α where + encode a := encode (f a) + decode l := (decode l).bind g + decode_encode a := by simp [h] + +/- ## Ground instances -/ + +/-- `ℕ` is encoded by its (little-endian) binary representation, as in +`Computability.encodeNat`. -/ +instance : BitstringEncoding ℕ where + encode := Computability.encodeNat + decode l := some (Computability.decodeNat l) + decode_encode n := congrArg some (Computability.decode_encodeNat n) + +/-- `Bool` is encoded as a singleton bitstring. -/ +instance : BitstringEncoding Bool where + encode b := [b] + decode l := match l with + | [b] => some b + | _ => none + decode_encode _ := rfl + +/-- `Unit` is encoded as the empty bitstring. This is the tensor unit for the pair encoding: +`encode ((), x) = false :: true :: encode x` and `encode (x, ()) = delimit (encode x)`. -/ +instance : BitstringEncoding Unit where + encode _ := [] + decode l := match l with + | [] => some () + | _ => none + decode_encode _ := rfl + +/-- Decode every block in a list of bitstrings, failing if any block fails to decode. + +(This is `List.mapM decode` in the `Option` monad, written out by hand so that it works +for `α` in any universe.) -/ +def decodeAll [BitstringEncoding α] : List (List Bool) → Option (List α) + | [] => some [] + | b :: t => + match decode b, decodeAll t with + | some a, some l => some (a :: l) + | _, _ => none + +@[simp] +theorem decodeAll_map_encode [BitstringEncoding α] (l : List α) : + decodeAll (l.map encode) = some l := by + induction l with + | nil => rfl + | cons a t ih => simp [decodeAll, ih] + +/- ## Derived instances -/ + +/-- An encoding of `Option α` is obtained encoding `none` as the empty bitstring +and `some a` as the encoding of `a` prefixed by a `true`. -/ +instance [BitstringEncoding α] : BitstringEncoding (Option α) where + encode + | none => [] + | some a => true :: encode a + decode input := + match input with + | [] => some none + | true :: rest => (decode rest).map some + | false :: _ => none + decode_encode + | none => rfl + | some a => by simp + +/-- Decode a bitstring as a pair: parse one self-delimiting block off the front for the first +component, and decode the remainder as the second. This is the `decode` of the `α × β` instance, +given as a standalone definition so that it can be unfolded and reasoned about by name. -/ +def decodePair [BitstringEncoding α] [BitstringEncoding β] (input : List Bool) : + Option (α × β) := + match unpair? input with + | none => none + | some (block, rest) => + match decode block, decode rest with + | some a, some b => some (a, b) + | _, _ => none + +/-- A pair is encoded as a self-delimiting block for the first component followed by the +encoding of the second — the same layout as the machine-input pairing codec +`Complexity.pair`. -/ +instance [BitstringEncoding α] [BitstringEncoding β] : BitstringEncoding (α × β) where + encode p := delimit (encode p.1) ++ encode p.2 + decode := decodePair + decode_encode p := by simp [decodePair] + +theorem decode_prod_eq_decodePair [BitstringEncoding α] [BitstringEncoding β] : + (decode : List Bool → Option (α × β)) = decodePair := rfl + +/-- A list is encoded as the concatenation of self-delimiting blocks for its elements. -/ +instance [BitstringEncoding α] : BitstringEncoding (List α) where + encode l := ((l.map encode).map delimit).flatten + decode input := + match undelimitBlocks input with + | none => none + | some blocks => decodeAll blocks + decode_encode l := by + rw [undelimitBlocks_flatten_delimit (l.map encode)] + exact decodeAll_map_encode l + +/-- A subtype inherits the encoding of the ambient type; decoding additionally checks the +defining predicate. This yields encodings for `ℕ+` and friends for free. -/ +instance {p : α → Prop} [BitstringEncoding α] [DecidablePred p] : + BitstringEncoding (Subtype p) where + encode x := encode x.val + decode input := (decode input).bind fun a => if h : p a then some ⟨a, h⟩ else none + decode_encode x := by simp [x.property] + +/-- `ℕ+` is encoded as the subtype `{n : ℕ // 0 < n}` it is defined to be. -/ +instance : BitstringEncoding ℕ+ := + inferInstanceAs (BitstringEncoding {n : ℕ // 0 < n}) + +/-- `ℤ` is encoded via the pair `(n.toNat, (-n).toNat)` (one component is always `0`). -/ +instance : BitstringEncoding ℤ := + ofLeftInverse (fun n : ℤ => (n.toNat, (-n).toNat)) + (fun p => some ((p.1 : ℤ) - (p.2 : ℤ))) (fun n => congrArg some (by dsimp only; omega)) + +/-- `ℚ` is encoded as its (reduced) numerator-denominator pair. -/ +instance : BitstringEncoding ℚ := + ofLeftInverse (fun q : ℚ => (q.num, q.den)) + (fun p => some ((p.1 : ℚ) / (p.2 : ℚ))) (fun q => by simp [Rat.num_div_den]) + +end BitstringEncoding + +/-- A bitstring: definitionally `List Bool`, but kept as a distinct (semireducible) type-level +name so that it can carry the identity `BitstringEncoding` without overlapping the generic +`List α` instance. + +Data that is conceptually a raw bitstring should be typed as `Bitstring`, and is encoded as +itself; a `List Bool` that is conceptually a list which happens to contain booleans keeps the +generic self-delimiting list encoding (four bits per element). Because `Bitstring` is not +reducible, instance search never sees through it, so the two encodings cannot be confused: a +lemma about the generic `List α` encoding instantiated at `α := Bool` and a lemma about +`Bitstring` can never silently disagree about which encoding is meant. + +(Not to be confused with the fixed-length `BitString n := Fin n → Bool` of the circuit +layers.) -/ +def Bitstring : Type := List Bool + +/-- A bitstring is encoded as itself. -/ +instance : BitstringEncoding Bitstring where + encode := id + decode := some + decode_encode _ := rfl + +/-- Convert a `List Bool` to a `Bitstring`. Definitionally the identity; useful for stating +lemmas that mix the two types without relying on definitional unfolding. -/ +def Bitstring.ofList (l : List Bool) : Bitstring := l + +/-- Convert a `Bitstring` to a `List Bool`. Definitionally the identity. -/ +def Bitstring.toList (l : Bitstring) : List Bool := l + +namespace BitstringEncoding + +@[simp] +theorem encode_ofList (l : List Bool) : encode (Bitstring.ofList l) = l := rfl + +theorem decode_bitstring (l : List Bool) : + (decode l : Option Bitstring) = some (Bitstring.ofList l) := rfl + +end BitstringEncoding + +end Complexity diff --git a/Complexitylib/Encoding/Delimit.lean b/Complexitylib/Encoding/Delimit.lean new file mode 100644 index 00000000..ffb897e2 --- /dev/null +++ b/Complexitylib/Encoding/Delimit.lean @@ -0,0 +1,209 @@ +/- +Copyright (c) 2026 Bolton Bailey. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Bolton Bailey +-/ + +module +public import Mathlib.Data.List.Basic +public import Mathlib.Data.Nat.Init +public import Aesop.BuiltinRules +public import Mathlib.Tactic.Attr.Core +public import Mathlib.Tactic.Basic +public import Mathlib.Tactic.Push +public import Mathlib.Tactic.Widget.Calc +public import Std.Tactic.BVDecide.Normalize.Prop + +/-! +# Self-delimiting blocks + +To concatenate binary strings into a single binary string, each piece must announce its own +end. This file defines the library's single framing operation and its parsers: + +- `delimit` frames a payload: each payload bit is doubled (`false ↦ [false, false]`, + `true ↦ [true, true]`) and the block is terminated by the separator `[false, true]`, + which no run of doubled bits can produce. +- `unpair?` parses one block off the front of the input, returning the payload and the + remaining suffix (`none` on malformed input). It is named for its role in the pairing + codec `Complexity.pair` (see `Complexitylib.Encoding.Pairing`), which is + `pair x y = delimit x ++ y`. +- `undelimitBlock`, `takeFirstBlock`, `hasBlock`, `tagBlock`, and `undelimitBlocks` are the + total helper functions machines compute when working with framed data. + +This file deliberately has no dependency on the machine or complexity-class layers, so both +the machine-input pairing codec (`Complexitylib.Encoding.Pairing`) and the encoding layer +(`Complexitylib.Encoding.Bitstring`) can build on it without import cycles. +-/ + + +@[expose] public section + +namespace Complexity + +/-- Frame a binary string as a self-delimiting block: each payload bit is doubled + (`false ↦ [false, false]`, `true ↦ [true, true]`) and the block is terminated by the + separator `[false, true]`, which no run of doubled bits can produce. -/ +def delimit (x : List Bool) : List Bool := + (x.flatMap fun b => [b, b]) ++ [false, true] + +@[simp] theorem delimit_nil : delimit [] = [false, true] := rfl + +@[simp] theorem delimit_cons (b : Bool) (l : List Bool) : + delimit (b :: l) = b :: b :: delimit l := by + simp [delimit] + +@[simp] theorem delimit_length (l : List Bool) : (delimit l).length = 2 * l.length + 2 := by + induction l with + | nil => rfl + | cons b l ih => simp only [delimit_cons, List.length_cons, ih]; omega + +/-- Parse one self-delimiting block off the front of the input. It scans doubled bits until + the first separator `[false, true]`, returning the decoded payload together with the + remaining suffix. Invalid doubled prefixes return `none`. -/ +def unpair? : List Bool → Option (List Bool × List Bool) + | [] => none + | false :: true :: y => some ([], y) + | false :: false :: z => + Option.map (fun (xy : List Bool × List Bool) => (false :: xy.1, xy.2)) (unpair? z) + | true :: true :: z => + Option.map (fun (xy : List Bool × List Bool) => (true :: xy.1, xy.2)) (unpair? z) + | _ => none + +/-- `unpair?` reads back the framing written by `delimit`: parsing one block off the front + of any input recovers the payload and the remaining suffix. -/ +@[simp] theorem unpair?_delimit_append (x y : List Bool) : + unpair? (delimit x ++ y) = some (x, y) := by + induction x with + | nil => simp [unpair?] + | cons b x ih => cases b <;> simp [unpair?, ih] + +/-- Soundness of the parser: a successful parse decomposes the input as the parsed payload's + framing followed by the leftover suffix. -/ +theorem eq_delimit_append_of_unpair?_eq_some : + ∀ {z x y : List Bool}, unpair? z = some (x, y) → z = delimit x ++ y + | [], _, _, h => by simp [unpair?] at h + | [b], _, _, h => by cases b <;> simp [unpair?] at h + | false :: true :: rest, x, y, h => by + simp only [unpair?, Option.some.injEq, Prod.mk.injEq] at h + obtain ⟨rfl, rfl⟩ := h + rfl + | false :: false :: rest, x, y, h => by + simp only [unpair?, Option.map_eq_some_iff] at h + obtain ⟨⟨p₁, p₂⟩, hp, heq⟩ := h + obtain ⟨rfl, rfl⟩ : false :: p₁ = x ∧ p₂ = y := by simpa [Prod.ext_iff] using heq + simp only [eq_delimit_append_of_unpair?_eq_some hp, delimit_cons, List.cons_append] + | true :: true :: rest, x, y, h => by + simp only [unpair?, Option.map_eq_some_iff] at h + obtain ⟨⟨p₁, p₂⟩, hp, heq⟩ := h + obtain ⟨rfl, rfl⟩ : true :: p₁ = x ∧ p₂ = y := by simpa [Prod.ext_iff] using heq + simp only [eq_delimit_append_of_unpair?_eq_some hp, delimit_cons, List.cons_append] + | true :: false :: rest, _, _, h => by simp [unpair?] at h + +/- ## Total block helpers -/ + +/-- Strip the framing of a single self-delimiting block, returning its payload. On `delimit P` +this returns `P`. Unlike `unpair?`, this is total: it ignores any data trailing the first block +and maps malformed input to `[]`. -/ +def undelimitBlock : List Bool → List Bool + | false :: true :: _ => [] + | false :: false :: rest => false :: undelimitBlock rest + | true :: _ :: rest => true :: undelimitBlock rest + | _ => [] + +@[simp] +theorem undelimitBlock_delimit (P : List Bool) : + undelimitBlock (delimit P) = P := by + induction P with + | nil => rfl + | cons b P ih => cases b <;> simp [undelimitBlock, ih] + +/-- Keep the leading self-delimiting block of a bitstring, dropping everything after it. On a +pair encoding `delimit x ++ w` this returns `delimit x`. -/ +def takeFirstBlock : List Bool → List Bool + | false :: true :: _ => [false, true] + | false :: false :: rest => false :: false :: takeFirstBlock rest + | true :: c :: rest => true :: c :: takeFirstBlock rest + | l => l + +@[simp] +theorem takeFirstBlock_delimit_append (P Q : List Bool) : + takeFirstBlock (delimit P ++ Q) = delimit P := by + induction P with + | nil => rfl + | cons b P ih => cases b <;> simp [takeFirstBlock, ih] + +/-- Does the bitstring begin with a well-formed self-delimiting block? -/ +def hasBlock : List Bool → Bool + | false :: true :: _ => true + | false :: false :: rest => hasBlock rest + | true :: true :: rest => hasBlock rest + | _ => false + +theorem hasBlock_eq_isSome_unpair? : + ∀ l : List Bool, hasBlock l = (unpair? l).isSome + | [] => rfl + | [b] => by cases b <;> rfl + | false :: true :: _ => rfl + | false :: false :: rest => by + simp only [hasBlock, unpair?, hasBlock_eq_isSome_unpair? rest] + cases unpair? rest <;> rfl + | true :: true :: rest => by + simp only [hasBlock, unpair?, hasBlock_eq_isSome_unpair? rest] + cases unpair? rest <;> rfl + | true :: false :: _ => rfl + +/-- Tag a bitstring with a leading `true` if it begins with a well-formed self-delimiting +block, and return the empty bitstring otherwise. On pair encodings this computes +`encode ∘ decode`. -/ +def tagBlock (l : List Bool) : List Bool := + bif hasBlock l then true :: l else [] + +/- ## Parsing a sequence of blocks -/ + +/-- Parse a sequence of self-delimiting blocks, using `fuel` to bound the number of blocks. + +This is the auxiliary, fuel-carrying implementation of `undelimitBlocks`; since every block +is nonempty, `input.length` is always enough fuel. -/ +def undelimitBlocksAux : ℕ → List Bool → Option (List (List Bool)) + | _, [] => some [] + | 0, _ :: _ => none + | fuel + 1, input => do + let (block, rest) ← unpair? input + let blocks ← undelimitBlocksAux fuel rest + return block :: blocks + +/-- Parse a sequence of self-delimiting blocks off the front of the input. + +Since every block is nonempty, `input.length` bounds the number of blocks, so it always +suffices as fuel for `undelimitBlocksAux`. -/ +def undelimitBlocks (input : List Bool) : Option (List (List Bool)) := + undelimitBlocksAux input.length input + +theorem length_le_length_flatten_delimit (l : List (List Bool)) : + l.length ≤ ((l.map delimit).flatten).length := by + induction l with + | nil => simp + | cons b t ih => + simp only [List.map_cons, List.flatten_cons, List.length_append, List.length_cons, + delimit_length] + omega + +private theorem undelimitBlocksAux_flatten_delimit (l : List (List Bool)) : + ∀ fuel, l.length ≤ fuel → undelimitBlocksAux fuel ((l.map delimit).flatten) = some l := by + induction l with + | nil => intro fuel _; cases fuel <;> rfl + | cons b t ih => + intro fuel hfuel + rw [List.length_cons] at hfuel + obtain ⟨fuel, rfl⟩ : ∃ f, fuel = f + 1 := ⟨fuel - 1, by omega⟩ + obtain ⟨hd, tl, hcons⟩ : ∃ hd tl, delimit b ++ (t.map delimit).flatten = hd :: tl := by + cases b <;> exact ⟨_, _, rfl⟩ + simp only [List.map_cons, List.flatten_cons, hcons, undelimitBlocksAux] + rw [← hcons, unpair?_delimit_append] + simp [ih fuel (by omega)] + +theorem undelimitBlocks_flatten_delimit (l : List (List Bool)) : + undelimitBlocks ((l.map delimit).flatten) = some l := + undelimitBlocksAux_flatten_delimit l _ (length_le_length_flatten_delimit l) + +end Complexity diff --git a/Complexitylib/Encoding/Pairing.lean b/Complexitylib/Encoding/Pairing.lean index c09dfea6..297ff174 100644 --- a/Complexitylib/Encoding/Pairing.lean +++ b/Complexitylib/Encoding/Pairing.lean @@ -5,6 +5,7 @@ Authors: Samuel Schlesinger -/ module +public import Complexitylib.Encoding.Delimit public import Mathlib.Data.Nat.Init public import Aesop.BuiltinRules public import Mathlib.Tactic.Attr.Core @@ -17,9 +18,11 @@ public import Std.Tactic.BVDecide.Normalize.Prop # Pairing binary strings This file defines the low-level self-delimiting pairing codec used by machine -inputs throughout Complexitylib. It deliberately has no dependency on the -machine or complexity-class layers, so parsers and encoders can reuse it -without introducing an import cycle. +inputs throughout Complexitylib: `pair x y = delimit x ++ y`, framed by the +shared delimiting operation of `Complexitylib.Encoding.Delimit` and parsed +back by its `unpair?`. It deliberately has no dependency on the machine or +complexity-class layers, so parsers and encoders can reuse it without +introducing an import cycle. -/ @@ -27,24 +30,11 @@ without introducing an import cycle. namespace Complexity -/-- Encode a pair of binary strings as a single binary string. - Each bit of `x` is doubled (`false ↦ [false, false]`, `true ↦ [true, true]`), - followed by the separator `[false, true]`, followed by `y` verbatim. +/-- Encode a pair of binary strings as a single binary string: the self-delimiting block + `delimit x` followed by `y` verbatim. `unpair?` is the partial inverse. This encoding is injective and computable in linear time. -/ def pair (x y : List Bool) : List Bool := - (x.flatMap fun b => [b, b]) ++ [false, true] ++ y - -/-- Partial inverse to `pair`. It scans doubled bits until the first - separator `[false, true]`, returning the decoded left component together - with the remaining suffix. Invalid doubled prefixes return `none`. -/ -def unpair? : List Bool → Option (List Bool × List Bool) - | [] => none - | false :: true :: y => some ([], y) - | false :: false :: z => - Option.map (fun (xy : List Bool × List Bool) => (false :: xy.1, xy.2)) (unpair? z) - | true :: true :: z => - Option.map (fun (xy : List Bool × List Bool) => (true :: xy.1, xy.2)) (unpair? z) - | _ => none + delimit x ++ y private theorem pair_nil_eq (y : List Bool) : pair [] y = false :: true :: y := by @@ -54,7 +44,7 @@ private theorem pair_nil_eq (y : List Bool) : doubled bit `b, b`. -/ theorem pair_cons_eq (b : Bool) (x y : List Bool) : pair (b :: x) y = b :: b :: pair x y := by - simp [pair, List.append_assoc] + simp [pair] /-- `|pair x y| = 2·|x| + 2 + |y|`. The `2·|x|` comes from doubling every bit of `x`; the `+2` is the separator `[false, true]`. -/ @@ -101,55 +91,14 @@ theorem pair_inj {x₁ x₂ : List Bool} {y₁ y₂ : List Bool} /-- `unpair?` is a left inverse of `pair`: decoding an encoded pair recovers exactly its two components. -/ @[simp] theorem unpair?_pair (x y : List Bool) : - unpair? (pair x y) = some (x, y) := by - induction x with - | nil => - simp [unpair?, pair_nil_eq] - | cons b xs ih => - rw [pair_cons_eq] - cases b <;> simp [unpair?, ih] + unpair? (pair x y) = some (x, y) := + unpair?_delimit_append x y /-- Soundness of the decoder: if `unpair?` succeeds on `z`, producing `(x, y)`, then `z` was exactly the encoding `pair x y`. -/ theorem eq_pair_of_unpair?_eq_some {z x y : List Bool} (h : unpair? z = some (x, y)) : - z = pair x y := by - have hsound : - ∀ z x y, unpair? z = some (x, y) → z = pair x y := by - intro z x y h - induction hlen : z.length using Nat.strong_induction_on generalizing z x y with - | h n ih => - cases z with - | nil => - simp [unpair?] at h - | cons a z1 => - cases z1 with - | nil => - cases a <;> simp [unpair?] at h - | cons b z2 => - cases a - · cases b - · simp [unpair?] at h - rcases h with ⟨x', htail, rfl⟩ - have hz2lt : z2.length < n := by - rw [← hlen] - have : z2.length < z2.length + 1 + 1 := by omega - exact this - have hz2 : z2 = pair x' y := ih z2.length hz2lt z2 x' y htail rfl - simpa [pair_cons_eq] using congrArg (fun t => false :: false :: t) hz2 - · simp [unpair?] at h - rcases h with ⟨rfl, rfl⟩ - simp [pair_nil_eq] - · cases b - · simp [unpair?] at h - · simp [unpair?] at h - rcases h with ⟨x', htail, rfl⟩ - have hz2lt : z2.length < n := by - rw [← hlen] - have : z2.length < z2.length + 1 + 1 := by omega - exact this - have hz2 : z2 = pair x' y := ih z2.length hz2lt z2 x' y htail rfl - simpa [pair_cons_eq] using congrArg (fun t => true :: true :: t) hz2 - exact hsound z x y h + z = pair x y := + eq_delimit_append_of_unpair?_eq_some h /-- `unpair? z` returns `some (x, y)` if and only if `z = pair x y`, characterizing exactly which strings are valid pair encodings. -/ diff --git a/Complexitylib/Models/TuringMachine/Subroutines/PairEmit/Internal.lean b/Complexitylib/Models/TuringMachine/Subroutines/PairEmit/Internal.lean index 287298a8..c8799d0d 100644 --- a/Complexitylib/Models/TuringMachine/Subroutines/PairEmit/Internal.lean +++ b/Complexitylib/Models/TuringMachine/Subroutines/PairEmit/Internal.lean @@ -320,7 +320,7 @@ theorem pairInputWorkTM_reachesIn_internal {n : ℕ} · intro i hi rw [hwork₂] exact hother₁ i hi - · simpa [pair, doubled, List.append_assoc] using houtput₂ + · simpa [pair, delimit, doubled, List.append_assoc] using houtput₂ /-- Internal compact Hoare contract for pair emission. -/ theorem pairInputWorkTM_hoareTime_internal {n : ℕ} diff --git a/Complexitylib/Models/TuringMachine/UTM/Internal/Init.lean b/Complexitylib/Models/TuringMachine/UTM/Internal/Init.lean index a5c8e76f..2a9ac856 100644 --- a/Complexitylib/Models/TuringMachine/UTM/Internal/Init.lean +++ b/Complexitylib/Models/TuringMachine/UTM/Internal/Init.lean @@ -1276,7 +1276,7 @@ private theorem initTM_hoareTime_core (α x : List Bool) {c₁ : Cfg 6 initTM.Q} (c'.work 5).HoldsExact [] ∧ (c'.work 5).head = 1 ∧ c'.output.cells = (Tape.init []).cells ∧ c'.output.head = 1 := by have hP : pair α x = (α.flatMap fun b => [b, b]) ++ false :: true :: x := by - simp [pair] + simp [pair, delimit] have hwf := Tape.StartInvariant.init_ofBool (pair α x) have hblank1 : (Tape.init []).cells 1 = Γ.blank := Tape.init_nil_cells_succ 0 have hwo₁ : ∀ i, (c₁.work i).read ≠ Γ.start ∧ 1 ≤ (c₁.work i).head := by diff --git a/Complexitylib/SAT/ThreeSAT/Verifier.lean b/Complexitylib/SAT/ThreeSAT/Verifier.lean index 02388b4a..8f2e58a7 100644 --- a/Complexitylib/SAT/ThreeSAT/Verifier.lean +++ b/Complexitylib/SAT/ThreeSAT/Verifier.lean @@ -97,7 +97,7 @@ left component belongs to the exact-3 syntax language. -/ pair z witness ∈ language ↔ z ∈ Syntax.language := by change accept ((pair z witness).foldl step initial) = true ↔ Syntax.accept (z.foldl Syntax.bitStep Syntax.bitStart) = true - rw [pair, List.foldl_append, List.foldl_append] + rw [pair, delimit, List.foldl_append, List.foldl_append] simp only [initial] rw [foldl_doubled] cases hsyntax : Syntax.accept (z.foldl Syntax.bitStep Syntax.bitStart) <;>