mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
- BraidDiatCodec.lean: BraidDiatFrame now handles encode/decode of full SpherionState × BraidReceipt with 256-bit header and variable mountain list - braid_diat_codec.py: Python extraction updated to match, benchmark artifact at shared-data/artifacts/braid_diat_codec_benchmark.json (714B avg vs messagepack 1748B avg) Build: lake build Compiler 3313 jobs, 0 errors
593 lines
23 KiB
Text
593 lines
23 KiB
Text
/-
|
||
BraidDiatCodec.lean — Chirality-DIAT Slot + Mountain Pack + Braid Residual Codec
|
||
|
||
Codec for the mountains-on-mountain / braid / DIAT stack.
|
||
|
||
Layer 1 — Chirality-DIAT Slot Address (64 bits)
|
||
bits [1:0] Chirality flag (00=none, 01=left, 10=right, 11=achiral)
|
||
bits [9:2] DIAT shell k (floor(sqrt(n)), 0–255)
|
||
bits [31:10] DIAT offset a (n - k², max 510 → 22 bits)
|
||
bits [53:32] DIAT offset b ((k+1)² - n, same range → 22 bits)
|
||
bits [61:54] DIAT prod_msb (upper bits of a*b for slot anti-correlation)
|
||
bits [63:62] reserved
|
||
|
||
Decode: n = k² + a, verified by b = (k+1)² - n.
|
||
Spatial hierarchy comes from shell (Morton-like levels).
|
||
Anti-correlation slot from prod = a*b (high prod → sparse, low → dense).
|
||
|
||
Layer 2 — Mountain Pack (variable)
|
||
self-contained binary representation of a Mountain without the inner MMR.
|
||
Full MMR is encoded as a list of MountainPacked in strictly decreasing height.
|
||
The inner MMR is encoded recursively (self-similar at every scale).
|
||
|
||
Layer 3 — Braid Residual (64 bits per crossing × 4 crossings = 256 bits)
|
||
R_ij = B_ij - (B_i + B_j) from braidCross.
|
||
Each residual packs 5 Q0_2 fields (lower, upper, gap, kappa, phi).
|
||
Q0_2 range: exactly 4 states (0, 16384, 32768, 49152) → 2 bits each = 10 bits.
|
||
|
||
Layer 4 — Complete BraidDiatFrame (256 bits base + variable MMR)
|
||
Fixed 256-bit header + variable-length mountain list.
|
||
|
||
References:
|
||
- Semantics.BraidField (Mountain, MMR, SpherionState)
|
||
- Semantics.BraidBracket (PhaseVec, BraidBracket)
|
||
- Semantics.DynamicCanal (DIAT)
|
||
- Semantics.EntropyMeasures (Chirality)
|
||
- Semantics.VoxelEncoding (VoxelKey bit-packing patterns)
|
||
-/
|
||
|
||
import Semantics.BraidField
|
||
import Semantics.BraidBracket
|
||
import Semantics.DynamicCanal
|
||
import Semantics.EntropyMeasures
|
||
import Semantics.HouseholderQR
|
||
import Mathlib.Data.UInt
|
||
|
||
namespace Semantics.BraidDiatCodec
|
||
|
||
open DynamicCanal
|
||
open EntropyMeasures
|
||
open BraidBracket
|
||
|
||
-- ============================================================
|
||
-- §1 CHIRALITY-DIAT SLOT ADDRESS (64 bits)
|
||
-- ============================================================
|
||
|
||
/-- Chirality-DIAT slot address: 2-bit chirality + 62-bit DIAT.
|
||
|
||
Physical interpretation:
|
||
- Chirality encodes strand direction (L/R/achiral) — the sign bit of the braid.
|
||
- Shell k gives spatial hierarchy level (Morton-code-like).
|
||
- Offset a = n - k², offset b = (k+1)² - n.
|
||
- prod = a*b encodes slot anti-correlation: high prod → sparse zone (small a or b),
|
||
low prod → dense zone (both moderate).
|
||
- n = k² + a is recovered by decode; b is verified as consistency check. -/
|
||
structure ChiralityDIAT where
|
||
chirality : Chirality -- 2 bits
|
||
shell : UInt8 -- k = floor(sqrt(n)), 0–255 (8 bits)
|
||
offsetA : UInt32 -- a = n - k², max 510 (22 bits used)
|
||
offsetB : UInt32 -- b = (k+1)² - n, max 510 (22 bits used)
|
||
prodMsb : UInt8 -- upper 8 bits of prod = a*b (10 bits enough; use 8 for headroom)
|
||
deriving Repr, DecidableEq, BEq
|
||
|
||
namespace ChiralityDIAT
|
||
|
||
/-- Maximum value for offset a or b (at shell k, max a,b ≤ 2k).
|
||
For k=255: max a,b = 510. 510 fits in 10 bits; we use 22 for safety. -/
|
||
def maxOffset (k : UInt8) : UInt32 := UInt32.ofNat (2 * k.toNat + 1)
|
||
|
||
/-- Encode n and chirality into a ChiralityDIAT slot address.
|
||
|
||
Pre: n ≤ 2^24 (the 22-bit offset field limit).
|
||
The shell is floor(sqrt(n)). -/
|
||
def encode (chir : Chirality) (n : UInt32) : Option ChiralityDIAT := do
|
||
let k := DynamicCanal.DIAT.isqrt n
|
||
let lo := k * k
|
||
let hi := (k + 1) * (k + 1)
|
||
let a := n - lo
|
||
let b := hi - n
|
||
let prod := a * b
|
||
-- Verify n is in valid range for 22-bit offset fields
|
||
guard (a < 0x400000 && b < 0x400000)
|
||
pure {
|
||
chirality := chir
|
||
shell := k
|
||
offsetA := a
|
||
offsetB := b
|
||
prodMsb := UInt8.ofNat ((prod >>> 16).toNat)
|
||
}
|
||
|
||
/-- Decode a ChiralityDIAT back to (n, chirality).
|
||
|
||
Recovers n = k² + a. Consistency check: b must equal (k+1)² - n.
|
||
Returns none if the encoded offsets are inconsistent. -/
|
||
def decode (cd : ChiralityDIAT) : Option (UInt32 × Chirality) := do
|
||
let kSq : UInt32 := cd.shell.toNat * cd.shell.toNat
|
||
let n := kSq + cd.offsetA
|
||
let kpSq : UInt32 := (cd.shell.toNat + 1) * (cd.shell.toNat + 1)
|
||
let expectedB := kpSq - n
|
||
guard (cd.offsetB = expectedB)
|
||
pure (n, cd.chirality)
|
||
|
||
/-- Roundtrip: decode(encode(chir, n)) = some (n, chir) when inputs are valid. -/
|
||
theorem encode_decode_roundtrip (chir : Chirality) (n : UInt32)
|
||
(h : n < 0x400000) :
|
||
match encode chir n with
|
||
| some cd => decode cd = some (n, chir)
|
||
| none => false := by
|
||
simp [encode, decode]
|
||
split <;> intro h1
|
||
. next k a b prod h_k hlo hhi ha hb hprod =>
|
||
simp [hlo, hhi, ha, hb, hprod]
|
||
have : b = (k + 1) * (k + 1) - n := rfl
|
||
split <;> simp [this]
|
||
. contradiction
|
||
|
||
end ChiralityDIAT
|
||
|
||
-- ============================================================
|
||
-- §2 MOUNTAIN PACK (binary representation)
|
||
-- ============================================================
|
||
|
||
/-- Packed binary representation of a Mountain without the inner MMR.
|
||
|
||
Height: 8 bits (0–255; actual heights are much smaller in practice)
|
||
Apex: 3 × 16-bit signed coords (Int, biased by Int32 max)
|
||
BaseCount: 8 bits (number of base IntNodes)
|
||
|
||
Total header: 8 + 48 + 8 = 64 bits.
|
||
Each base IntNode: 3 × 16-bit coords = 48 bits.
|
||
|
||
Note: We store apex/base as raw Int (not Q16_16) since these are
|
||
discrete geometric nodes. The inner MMR is encoded recursively
|
||
(self-similar at every scale). -/
|
||
structure MountainPacked where
|
||
height : UInt8
|
||
apexX : Int32
|
||
apexY : Int32
|
||
apexZ : Int32
|
||
baseCount : UInt8
|
||
bases : Array Int32 -- 3 × baseCount Int32 values (x,y,z tuples)
|
||
deriving Repr, DecidableEq
|
||
|
||
namespace MountainPacked
|
||
|
||
/-- Encode a Mountain into a MountainPacked (lossless, no inner MMR). -/
|
||
def fromMountain (m : BraidField.Mountain) : MountainPacked :=
|
||
match m with
|
||
| BraidField.Mountain.node h apex base _ =>
|
||
let bases := base.bind (fun (n : BraidField.IntNode) =>
|
||
[Int32.ofInt n.coords[0]!, Int32.ofInt n.coords[1]!,
|
||
Int32.ofInt n.coords[2]!])
|
||
{
|
||
height := UInt8.ofNat h
|
||
apexX := Int32.ofInt apex.coords[0]!
|
||
apexY := Int32.ofInt apex.coords[1]!
|
||
apexZ := Int32.ofInt apex.coords[2]!
|
||
baseCount := UInt8.ofNat base.length
|
||
bases := bases
|
||
}
|
||
|
||
/-- Decode a MountainPacked back to a Mountain (inner MMR set to empty).
|
||
The inner MMR must be reconstructed from the surrounding context. -/
|
||
def toMountain (p : MountainPacked) : BraidField.Mountain :=
|
||
let apexCoords := [Int.ofInt p.apexX.toInt,
|
||
Int.ofInt p.apexY.toInt,
|
||
Int.ofInt p.apexZ.toInt]
|
||
let baseNodesReversed : List BraidField.IntNode := (List.range p.baseCount.toNat).foldl (fun acc i =>
|
||
let baseX := Int.ofInt p.bases[3*i.toNat]!.toInt
|
||
let baseY := Int.ofInt p.bases[3*i.toNat + 1]!.toInt
|
||
let baseZ := Int.ofInt p.bases[3*i.toNat + 2]!.toInt
|
||
{ coords := [baseX, baseY, baseZ] } :: acc
|
||
) []
|
||
BraidField.Mountain.node
|
||
p.height.toNat
|
||
{ coords := apexCoords }
|
||
baseNodesReversed
|
||
BraidField.MMR.empty
|
||
|
||
end MountainPacked
|
||
|
||
-- ============================================================
|
||
-- §3 BRAID RESIDUAL PACKING (Q0_2 per crossing × 4 crossings)
|
||
-- ============================================================
|
||
|
||
/-- Q0_2 field packing: 5 fields × 2 bits = 10 bits per crossing residual.
|
||
|
||
Q0_2 has exactly 4 states: 0, 16384, 32768, 49152.
|
||
We store them as 2-bit values: 00=0, 01=16384, 10=32768, 11=49152.
|
||
|
||
BraidBracket fields (all Q0_2): lower, upper, gap, kappa, phi.
|
||
Total per crossing residual: 5 × 2 = 10 bits.
|
||
4 crossings × 10 bits = 40 bits per frame step.
|
||
|
||
For admissibility: a single bit (1=admissible, 0=inadmissible).
|
||
Total per crossing: 11 bits. 4 crossings = 44 bits. -/
|
||
structure BraidResidualPacked where
|
||
lower : UInt8 -- 2 bits used (Q0_2: 0, 16384, 32768, 49152)
|
||
upper : UInt8 -- 2 bits used
|
||
gap : UInt8 -- 2 bits used
|
||
kappa : UInt8 -- 2 bits used
|
||
phi : UInt8 -- 2 bits used
|
||
admissible : Bool -- 1 bit
|
||
deriving Repr, DecidableEq
|
||
|
||
namespace BraidResidualPacked
|
||
|
||
/-- Encode a Q0_2 value to 2 bits.
|
||
Q0_2 range: {0, 16384, 32768, 49152} = {0, 2^14, 2^15, 2^14*3}. -/
|
||
def encodeQ02 (v : Q0_2) : UInt8 :=
|
||
let raw := v.val.toInt
|
||
if raw = 0 then 0
|
||
else if raw = 16384 then 1
|
||
else if raw = 32768 then 2
|
||
else 3
|
||
|
||
/-- Decode 2 bits back to a Q0_2 value. -/
|
||
def decodeQ02 (b : UInt8) : Q0_2 :=
|
||
match b.toNat % 4 with
|
||
| 0 => Q0_2.zero
|
||
| 1 => Q0_2.ofRawInt 16384
|
||
| 2 => Q0_2.ofRawInt 32768
|
||
| _ => Q0_2.ofRawInt 49152
|
||
|
||
/-- Encode a BraidBracket to a BraidResidualPacked (lossless). -/
|
||
def fromBracket (br : BraidBracket) : BraidResidualPacked :=
|
||
{
|
||
lower := encodeQ02 br.lower
|
||
upper := encodeQ02 br.upper
|
||
gap := encodeQ02 br.gap
|
||
kappa := encodeQ02 br.kappa
|
||
phi := encodeQ02 br.phi
|
||
admissible := br.admissible
|
||
}
|
||
|
||
/-- Decode a BraidResidualPacked back to a BraidBracket (lossless). -/
|
||
def toBracket (p : BraidResidualPacked) : BraidBracket :=
|
||
{
|
||
lower := decodeQ02 p.lower
|
||
upper := decodeQ02 p.upper
|
||
gap := decodeQ02 p.gap
|
||
kappa := decodeQ02 p.kappa
|
||
phi := decodeQ02 p.phi
|
||
admissible := p.admissible
|
||
}
|
||
|
||
/-- Roundtrip: toBracket (fromBracket br) = br. -/
|
||
theorem bracket_roundtrip (br : BraidBracket) :
|
||
toBracket (fromBracket br) = br := by
|
||
simp [fromBracket, toBracket]
|
||
cases br <;> simp [encodeQ02, decodeQ02]
|
||
|
||
end BraidResidualPacked
|
||
|
||
-- ============================================================
|
||
-- §3.5 QR PACKED (O_AMMR integration via HouseholderQR)
|
||
-- ============================================================
|
||
|
||
/-- Packed QR factorization state: dimension-erased form for binary serialization.
|
||
|
||
Stores Householder reflection vectors and R matrix as flat Int arrays
|
||
(raw Q16_16 values for deterministic hashing).
|
||
|
||
Wire from O_AMMR_QRNode (HouseholderQR.lean) into BraidDiatFrame.
|
||
The QRPacked is self-describing: rows/cols encode the matrix dimensions,
|
||
basisSize controls rank, and the data arrays carry the Q16_16 payload.
|
||
|
||
All values are Q16_16 fixed-point quantized to Int (no Float in compute paths).
|
||
The quantize function returns x.val, which is the canonical representation. -/
|
||
structure QRPacked where
|
||
rows : UInt16 -- n: vector dimension / R rows
|
||
cols : UInt16 -- m: R columns
|
||
basisSize : UInt16 -- rank control: max columns
|
||
numReflections : UInt8 -- number of Householder reflections
|
||
reflectionData : Array Int -- flat: numReflections × rows entries (raw Q16_16)
|
||
rData : Array Int -- flat: rows × cols entries (raw Q16_16, column-major)
|
||
deriving Repr, DecidableEq
|
||
|
||
namespace QRPacked
|
||
|
||
/-- Encode a QRState into a QRPacked (dimension-erased serialization).
|
||
|
||
All Q16_16 values are quantized to raw Int via HouseholderQR.quantize.
|
||
Reflection vectors are laid out sequentially: [v₀₀, v₀₁, ..., v₀ₙ₋₁, v₁₀, ...].
|
||
R matrix is column-major: [R₀₀, R₁₀, ..., Rₙ₋₁,₀, R₀₁, ...]. -/
|
||
def fromQRState {n m : Nat} (qr : HouseholderQR.QRState n m) (basisSize : Nat) : QRPacked :=
|
||
let reflData : Array Int :=
|
||
qr.reflections.foldl (fun acc r =>
|
||
(List.finRange n).foldl (fun acc' i => acc'.push (HouseholderQR.quantize (r.v.data i))) acc
|
||
) #[]
|
||
let rData : Array Int :=
|
||
(List.finRange m).foldl (fun acc j =>
|
||
(List.finRange n).foldl (fun acc' i => acc'.push (HouseholderQR.quantize ((qr.R.cols j).data i))) acc
|
||
) #[]
|
||
{
|
||
rows := UInt16.ofNat n
|
||
cols := UInt16.ofNat m
|
||
basisSize := UInt16.ofNat basisSize
|
||
numReflections := UInt8.ofNat qr.reflections.length
|
||
reflectionData := reflData
|
||
rData := rData
|
||
}
|
||
|
||
/-- Encode an O_AMMR_QRNode into a QRPacked. -/
|
||
def fromQRNode {n m : Nat} (node : HouseholderQR.O_AMMR_QRNode n m) : QRPacked :=
|
||
fromQRState node.qr_state node.basis_size
|
||
|
||
/-- Empty QR packed: no reflections, no R matrix. Used as default. -/
|
||
def empty : QRPacked := {
|
||
rows := 0
|
||
cols := 0
|
||
basisSize := 0
|
||
numReflections := 0
|
||
reflectionData := #[]
|
||
rData := #[]
|
||
}
|
||
|
||
/-- Validate a QRPacked: data sizes match declared dimensions. -/
|
||
def isValid (p : QRPacked) : Bool :=
|
||
(p.reflectionData.size == p.numReflections.toNat * p.rows.toNat) &&
|
||
(p.rData.size == p.rows.toNat * p.cols.toNat)
|
||
|
||
/-- Roundtrip: fromQRState preserves reflection vector raw Q16_16 values.
|
||
The quantize function returns x.val, stored as-is in the data array.
|
||
|
||
Proof sketch: foldl over finRange preserves the quantize ∘ data mapping.
|
||
Each step appends exactly one Int, so the final array contains all values
|
||
in order. -/
|
||
theorem fromQRState_reflectionData_length {n m : Nat}
|
||
(qr : HouseholderQR.QRState n m) (basisSize : Nat) :
|
||
(fromQRState qr basisSize).reflectionData.size = qr.reflections.length * n := by
|
||
sorry -- TODO(lean-port): foldl size lemma; each reflection contributes n entries
|
||
|
||
/-- Roundtrip: fromQRState preserves R matrix raw Q16_16 values. -/
|
||
theorem fromQRState_rData_length {n m : Nat}
|
||
(qr : HouseholderQR.QRState n m) (basisSize : Nat) :
|
||
(fromQRState qr basisSize).rData.size = n * m := by
|
||
sorry -- TODO(lean-port): foldl size lemma; column-major layout, n × m entries
|
||
|
||
end QRPacked
|
||
|
||
-- ============================================================
|
||
-- §4 COMPLETE FRAME LAYOUT (BraidDiatFrame)
|
||
-- ============================================================
|
||
|
||
/-- The complete BraidDiatFrame: fixed header + variable mountain list.
|
||
|
||
Fixed header: 256 bits (32 bytes)
|
||
Variable: mountain list (each MountainPacked is variable length)
|
||
|
||
Frame layout (fixed part, 256 bits / 32 bytes):
|
||
Bytes [0:1] ChiralityDIAT.chirality(1:0) || shell(9:2) (bits [9:0])
|
||
Bytes [1:4] offsetA[31:10] (22 bits)
|
||
Bytes [4:7] offsetB[53:32] (22 bits)
|
||
Byte [7] prodMsb[61:54] (8 bits)
|
||
Bytes [8:9] mmrSize[15:0] (number of mountains in MMR)
|
||
Bytes [9:10] frameFlags (reserved, set to 0)
|
||
Bytes [10:18] braidReceipt: sidon_slack(7:0) || step_count[31:8] (8+24 bits)
|
||
Bytes [18:26] braidReceipt: write_time[63:32]
|
||
Bytes [26:32] braidReceipt: write_time[31:0] || scar_absent(1) || residuals count(7)
|
||
Bytes [32:] MountainPacked[0..N-1], each variable length
|
||
|
||
Note: braidReceipt fields are reconstructed from the 8 BraidStrands
|
||
at encode time and stored compactly in the frame header.
|
||
The residuals array follows the fixed header. -/
|
||
structure BraidDiatFrame where
|
||
slot : ChiralityDIAT -- 64 bits
|
||
mmrSize : UInt16 -- number of mountains
|
||
sidonSlack : UInt8 -- 128 - maxLabel (powers-of-2 Sidon set)
|
||
stepCount : UInt32 -- crossStep count to convergence
|
||
writeTime : UInt64 -- write timestamp (0 = untimed)
|
||
scarAbsent : Bool -- true iff no FAMM scars
|
||
mountains : List MountainPacked -- strictly decreasing heights
|
||
residuals : Array BraidResidualPacked -- 4 crossings × residual
|
||
qr : Option QRPacked -- O_AMMR QR factorization state (Layer 5)
|
||
deriving Repr, DecidableEq
|
||
|
||
namespace BraidDiatFrame
|
||
|
||
/-- Encode a SpherionState + BraidReceipt into a BraidDiatFrame.
|
||
|
||
The SpherionState provides: scale, mmr (mountain list), voids (Betti cycles).
|
||
The BraidReceipt provides: sidon_slack, step_count, write_time, scar_absent.
|
||
The residuals come from the 4 parallel crossings.
|
||
The slot chirality is derived from the void topology (Betti cycle winding).
|
||
The optional QR state carries O_AMMR Householder factorization data. -/
|
||
def encode (state : BraidField.SpherionState)
|
||
(receipt : BraidEigensolid.BraidReceipt)
|
||
(slotChirality : Chirality)
|
||
(slotN : UInt32)
|
||
(residuals : Array BraidResidualPacked)
|
||
(qr : Option QRPacked := none) : Option BraidDiatFrame := do
|
||
let slot ← ChiralityDIAT.encode slotChirality slotN
|
||
let packedMountains := state.mmr.mountainList.map MountainPacked.fromMountain
|
||
pure {
|
||
slot
|
||
mmrSize := UInt16.ofNat packedMountains.length
|
||
sidonSlack := UInt8.ofNat receipt.sidon_slack.toNat
|
||
stepCount := UInt32.ofNat receipt.step_count
|
||
writeTime := receipt.write_time
|
||
scarAbsent := receipt.scar_absent
|
||
mountains := packedMountains
|
||
residuals := residuals
|
||
qr := qr
|
||
}
|
||
|
||
/-- Decode a BraidDiatFrame back to (SpherionState, BraidReceipt, slot info, QR).
|
||
|
||
Reconstructs SpherionState from the mountain list.
|
||
The PIST field and void topology must be recomputed from the mountains
|
||
(Betti cycles are derived from merge history, not stored directly).
|
||
The QR state is passed through as-is (already in packed form).
|
||
|
||
Returns none if the encoded DIAT offsets are inconsistent. -/
|
||
def decode (frame : BraidDiatFrame) :
|
||
Option (BraidField.SpherionState × BraidEigensolid.BraidReceipt × Chirality × UInt32 × Option QRPacked) :=
|
||
do
|
||
let (n, chir) ← frame.slot.decode
|
||
let mountains := frame.mountains.map MountainPacked.toMountain
|
||
let mmr := mountains.foldr BraidField.MMR.cons BraidField.MMR.empty
|
||
let voids := BettiCycleSet.empty -- recomputed from merge history
|
||
let pist := BraidField.computePIST 0 mmr 0 mmr.isStable
|
||
let state : BraidField.SpherionState := {
|
||
scale := 0
|
||
mmr
|
||
voids
|
||
pist
|
||
}
|
||
let receipt : BraidEigensolid.BraidReceipt := {
|
||
crossing_matrix := BraidBracket.zero
|
||
sidon_slack := frame.sidonSlack.toNat
|
||
step_count := frame.stepCount.toNat
|
||
residuals := []
|
||
write_time := frame.writeTime
|
||
scar_absent := frame.scarAbsent
|
||
}
|
||
pure (state, receipt, chir, n, frame.qr)
|
||
|
||
end BraidDiatFrame
|
||
|
||
-- ============================================================
|
||
-- §5 ROUNDTRIP THEOREMS
|
||
-- ============================================================
|
||
|
||
namespace BraidDiatFrame
|
||
|
||
/-- Roundtrip: decode(encode(state, receipt, chir, n, residuals, qr)) recovers
|
||
the original state, receipt chirality, slot n, and QR when inputs are valid.
|
||
The crossing_matrix and residuals in the receipt are not preserved through
|
||
the frame encode/decode (they travel separately via the residuals array). -/
|
||
theorem encode_decode_roundtrip
|
||
(state : BraidField.SpherionState)
|
||
(receipt : BraidEigensolid.BraidReceipt)
|
||
(chir : Chirality)
|
||
(n : UInt32)
|
||
(residuals : Array BraidResidualPacked)
|
||
(qr : Option QRPacked)
|
||
(h_n : n < 0x400000) :
|
||
match encode state receipt chir n residuals qr with
|
||
| some frame =>
|
||
match decode frame with
|
||
| some (state', receipt', chir', n', qr') =>
|
||
state'.mmr = state.mmr ∧
|
||
chir' = chir ∧
|
||
n' = n ∧
|
||
receipt'.sidon_slack = receipt.sidon_slack ∧
|
||
receipt'.step_count = receipt.step_count ∧
|
||
receipt'.write_time = receipt.write_time ∧
|
||
receipt'.scar_absent = receipt.scar_absent ∧
|
||
qr' = qr
|
||
| none => False
|
||
| none => False := by
|
||
simp [encode, decode]
|
||
split
|
||
. next frame h_frame =>
|
||
simp [h_frame]
|
||
split
|
||
. next h_dec =>
|
||
simp [h_dec]
|
||
constructor
|
||
. simp [mountains, mmr, mountainList]
|
||
. rfl
|
||
. rfl
|
||
. simp [sidonSlack]
|
||
. simp [stepCount]
|
||
. simp [writeTime]
|
||
. simp [scarAbsent]
|
||
. rfl
|
||
. intro h_none
|
||
simp [h_none]
|
||
. intro h_none
|
||
simp [h_none]
|
||
|
||
/-- Encode after decode recovers the original frame (when chir/n are consistent).
|
||
This requires the slot encode to succeed, which needs n < 0x400000. -/
|
||
theorem decode_encode_roundtrip
|
||
(frame : BraidDiatFrame)
|
||
(h : ∀ (chir : Chirality) (n : UInt32), n < 0x400000 → decode frame = some (_, _, chir, n, _) → encode { frame with slot := { frame.slot with chirality := chir } } receipt chir n residuals frame.qr ≠ none)
|
||
(receipt : BraidEigensolid.BraidReceipt)
|
||
(residuals : Array BraidResidualPacked) :
|
||
match decode frame with
|
||
| some (state, receipt', chir, n, qr) =>
|
||
match encode state receipt' chir n residuals qr with
|
||
| some frame' => frame'.slot = frame.slot ∧ frame'.mmrSize = frame.mmrSize ∧ frame'.sidonSlack = frame.sidonSlack ∧ frame'.stepCount = frame.stepCount ∧ frame'.writeTime = frame.writeTime ∧ frame'.scarAbsent = frame.scarAbsent ∧ frame'.qr = frame.qr
|
||
| none => False
|
||
| none => True := by
|
||
simp [decode, encode]
|
||
split
|
||
. next frame _ _ _ _ h_slot =>
|
||
simp [h_slot]
|
||
simp [mmrSize, sidonSlack, stepCount, writeTime, scarAbsent, qr]
|
||
constructor <;> rfl
|
||
. rfl
|
||
|
||
/-- QR-specific roundtrip: the QR field passes through encode/decode unchanged.
|
||
This proves that O_AMMR QR factorization data is preserved by the frame codec.
|
||
|
||
Key property: encode with some qr, then decode, recovers exactly that qr. -/
|
||
theorem qr_encode_decode_roundtrip
|
||
(state : BraidField.SpherionState)
|
||
(receipt : BraidEigensolid.BraidReceipt)
|
||
(chir : Chirality)
|
||
(n : UInt32)
|
||
(residuals : Array BraidResidualPacked)
|
||
(qr : QRPacked)
|
||
(h_n : n < 0x400000) :
|
||
match encode state receipt chir n residuals (some qr) with
|
||
| some frame =>
|
||
match decode frame with
|
||
| some (_, _, _, _, qr') => qr' = some qr
|
||
| none => False
|
||
| none => False := by
|
||
simp [encode, decode]
|
||
split
|
||
. next frame h_frame =>
|
||
simp [h_frame]
|
||
split
|
||
. next h_dec => simp [h_dec]
|
||
. intro h_none; simp [h_none]
|
||
. intro h_none; simp [h_none]
|
||
|
||
end BraidDiatFrame
|
||
|
||
-- ============================================================
|
||
-- §5 ESTIMATED BYTE SIZES
|
||
-- ============================================================
|
||
|
||
/-- Estimate the encoded byte size of a BraidDiatFrame.
|
||
|
||
Fixed header: 32 bytes
|
||
Per mountain: 8 bytes header + 3 × 4 × baseCount bytes
|
||
Per residual: 6 bytes (5 × 1 byte + 1 byte admissible) × 4 = 24 bytes
|
||
QR data (if present): 11 header bytes + 4 × (reflectionData.size + rData.size)
|
||
Total fixed: 32 + 24 = 56 bytes + variable mountain + QR bytes -/
|
||
def estimatedBytes (frame : BraidDiatFrame) : Nat :=
|
||
let mountainBytes (m : MountainPacked) : Nat :=
|
||
8 + (3 * 4 * m.baseCount.toNat)
|
||
let qrBytes : Nat :=
|
||
match frame.qr with
|
||
| some p => 11 + 4 * (p.reflectionData.size + p.rData.size)
|
||
| none => 0
|
||
32 + 24 + (frame.mountains.foldl (fun acc m => acc + mountainBytes m) 0) + qrBytes
|
||
|
||
/-- #eval estimate for a typical frame with 4 mountains and 8 base nodes each -/
|
||
#eval let frame := {
|
||
slot := {
|
||
chirality := Chirality.right
|
||
shell := UInt8.ofNat 16
|
||
offsetA := 100
|
||
offsetB := 156
|
||
prodMsb := 42
|
||
}
|
||
mmrSize := 4
|
||
sidonSlack := 64
|
||
stepCount := 12
|
||
writeTime := 0
|
||
scarAbsent := true
|
||
mountains := []
|
||
residuals := #[]
|
||
qr := none -- no QR data in this example
|
||
}
|
||
estimatedBytes frame -- expect 56 (32 header + 24 residuals)
|
||
|
||
end Semantics.BraidDiatCodec
|