Probabilistic lens-ranges (Footprint) #
The sub-probability analogue of DetermFootprint. A region of the state m is a set
of sub-probability kernels m → SubProbability m, closed under Kleisli composition
(*, with pure as identity — see the Monoid (m → SubProbability m) instance in
GaudisCrypt.Language.SubProbability) and equal to its own double commutant.
The whole lattice/complement tower (Compl, from, PartialOrder, Lattice,
BoundedOrder, compl_compl, CompleteLattice) is built purely from generic
monoid–centralizer facts, so it mirrors DetermFootprint verbatim, only over the
Kleisli monoid of kernels instead of Function.End. The genuinely probabilistic
content — relating a ProgramDenotation to a Footprint — lives in ProgramDenotation.inFootprint
and ProgramDenotation.footprint at the bottom of this file.
- updates : Set (m → SubProbability m)
Instances For
Equations
- GaudisCrypt.instComplFootprint = { compl := fun (range : GaudisCrypt.Footprint m) => { updates := range.updates.centralizer, id := ⋯, comp := ⋯, double_commutant := ⋯ } }
Equations
- GaudisCrypt.Footprint.from generators = { updates := generators.centralizer.centralizer, id := ⋯, comp := ⋯, double_commutant := ⋯ }
Instances For
Equations
- GaudisCrypt.instPartialOrderFootprint = { le := fun (x y : GaudisCrypt.Footprint m) => x.updates ≤ y.updates, le_refl := ⋯, le_trans := ⋯, lt_iff_le_not_ge := ⋯, le_antisymm := ⋯ }
Equations
- One or more equations did not get rendered due to their size.
Equations
- GaudisCrypt.instBoundedOrderFootprint = { top := { updates := ⊤, id := ⋯, comp := ⋯, double_commutant := ⋯ }, le_top := ⋯, bot := GaudisCrypt.Footprint.from ∅, bot_le := ⋯ }
A range equals the centralizer of its own complement (double-commutant closure, stated with the commutant on the inside).
Equations
- One or more equations did not get rendered due to their size.
Equations
- One or more equations did not get rendered due to their size.
Equations
- One or more equations did not get rendered due to their size.
Equations
- lens.liftSubProbability κ x = do let a_1 ← κ (lens.get x) pure (lens.set a_1 x)
Instances For
Equations
- lens.liftFootprint range = GaudisCrypt.Footprint.from (lens.liftSubProbability '' range.updates)
Instances For
Programs and probabilistic ranges #
A program p lies in the probabilistic range R iff it commutes with every
kernel outside R (i.e. in the commutant Rᶜ): running an outside kernel f
on the state and then p is the same as running p and then f on the
resulting state. This is the sub-probability analogue of ProgramDenotation.inRange.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The probabilistic range of a Unit-returning program: the Footprint
generated by its single induced state kernel (run p, forget the result).
Ported from the rangeUnit2 sketch in Language/Semantics.lean.
Equations
- p.footprintUnit = GaudisCrypt.Footprint.from {fun (st : s) => do let __discr ← p st match __discr with | (fst, st') => pure st'}
Instances For
The probabilistic range of a program p : ProgramDenotation s a: the Footprint
generated by the family of return-value-conditioned state kernels. For each
possible return value y : a, the kernel runs p, keeps only the mass that
returns y (killing the rest with ⊥), and forgets the result, leaving a
kernel s → SubProbability s. Indexing by y records how the final state
correlates with what p returns. Ported from the range2 sketch in
Language/Semantics.lean.
Equations
Instances For
Litmus test: p.inFootprint R ↔ p.footprint ≤ R #
The probabilistic analogue of the bicommutant litmus test. The key device is the
return-value slice projK y: post-composing a kernel into a × s with projK y
keeps only the mass that returns y and projects to the state. Slicing turns the
joint commutation equation defining inFootprint into per-y commutations of the
conditioned kernels kᵧ (the generators of footprint) with the commutant Rᶜ,
i.e. kᵧ ∈ centralizer Rᶜ = R.updates. The forward direction is pure slicing; the
backward direction reassembles the joint kernel from its slices, which needs the
return type a to be Countable.
Litmus test, forward (soundness): if p commutes with the commutant Rᶜ,
its constructive footprint is contained in R. No countability needed — this is
pure slicing of the commutation equation.
Litmus test, backward (completeness): if p's constructive footprint is
contained in R, then p commutes with the commutant Rᶜ. Countability-free (subtask 4):
the joint kernel is reassembled from its slices via the discreteness invariant
(ext_of_slices), not from countability of the return type.
Litmus test: a program lies in the range R (commutes with the commutant)
iff its constructive footprint is ≤ R. Ported from the Litmus test note in
Language/Semantics.lean.
Closure properties of inFootprint / footprint #
Clean reformulation of inFootprint: strip the trailing pure-repack from the
"run outside-kernel first" side via bind_pure.
Monotonicity: a larger range still contains the program.
Commutation composes through bind: if p and every q x commute with the
commutant Rᶜ, so does p >>= q. Pure Kleisli algebra — no countability needed.
The slogan is pre/post (run-f-first / run-f-last) compose via bind_assoc,
and the hypotheses swap pre ↔ post at p and at each q x.
Range of a bind: (p >>= q).footprint ≤ p.footprint ⊔ ⨆ x, (q x).footprint.
The footprint of a sequenced computation is contained in p's footprint together
with the union of the continuations' footprints. Countability-free (subtask 4): the
self-range step (inFootprint_of_footprint_le) no longer needs countable return types.
Parity primitives: Lens.footprint and primitive ranges #
The probabilistic analogues of Lens.range / ProgramDenotation.inRange_pure/set/get, mirroring
the
DetermFootprint leaves so consumers can migrate. A deterministic state update embeds as a Dirac
kernel via diracKer; Lens.footprint is generated by the lens-localized ones.
A deterministic state update f : Function.End s as a Dirac kernel. The Kleisli embedding
Function.End s ↪ (s → SubProbability s).
Equations
- GaudisCrypt.diracKer f st = pure (f st)
Instances For
The R-orbit equivalence on m: s ~ s' iff s' is reachable from s via the
deterministic updates of R — Dirac kernels diracKer f ∈ R.updates. The Footprint
analogue of DetermFootprint.orbit_setoid.
Equations
- R.orbit_setoid = { r := Relation.EqvGen fun (s s' : m) => ∃ (f : Function.End m), GaudisCrypt.diracKer f ∈ R.updates ∧ f s = s', iseqv := ⋯ }
Instances For
The "global getter" of a Footprint: the quotient projection onto R-orbit classes.
Two states read equal iff they lie in the same R-orbit (differ only within R).
Equations
- R.global_getter = { get := Quotient.mk R.orbit_setoid }
Instances For
The "touched" getter: global_getter of the commutant Rᶜ. Two states read equal iff they
differ only in Rᶜ — i.e. they agree on the content R owns. For R = fvP_proc A this
is glob A (EasyCrypt's ={glob A} is exactly touched_getter x = touched_getter y).
Equations
Instances For
A single deterministic Rᶜ-update cannot move the touched getter: f σ and σ lie in
the same Rᶜ-orbit. The pointwise engine for "={glob A} is preserved by writes outside
A's footprint" (e.g. oracle writes, for an oracle-disjoint A).
A Footprint S is resettable at σ if it admits an S-update that overwrites its own
content (S.touched_getter) with σ's value while fixing σ. This is the "S is a genuine,
overwritable memory region" property: every lens footprint has it (Lens.footprint_hasReset),
an abelian bicommutant one need not. It is the frame's faithfulness witness, living on the
(lens-derived) oracle region rather than on the adversary.
Equations
- S.HasReset σ = ∃ (f : Function.End m), GaudisCrypt.diracKer f ∈ S.updates ∧ f σ = σ ∧ ∀ (s : m), S.touched_getter.get (f s) = S.touched_getter.get σ
Instances For
Observational indistinguishability through a footprint #
An R-test observes a state by running one R-update and reading off its acceptance
probability — the total weight SubProbability.mass of the result. Footprint.indistinguishable
is the induced observational equivalence: no R-test separates the two states
(indistinguishable_iff_testsOf). The touched getter is sound for it
(indistinguishable_of_touched_getter_eq): states agreeing on the content R owns pass every
R-test with the same probability. (Tests comparing the weight against an interval rather than
a single value separate exactly as well as the exact-weight ones formalized here.)
Two states are indistinguishable through R when every update of R accepts both with
the same total weight (SubProbability.mass).
Instances For
Footprint.indistinguishable is an equivalence relation.
Footprint.indistinguishable is antitone in the footprint: a larger footprint has more
tests, hence a finer indistinguishability.
The tests of a footprint: the state predicates decided by comparing the acceptance
probability of a single R-update against a fixed weight.
Equations
Instances For
Indistinguishability is exactly "passing the same tests".
Soundness of the touched getter for tests: states with equal R-owned content (equal
R.touched_getter — EasyCrypt's ={glob}) are indistinguishable through R. Each
Rᶜ-orbit step is a deterministic outside update; every R-update commutes with it (the
centralizer equation), and deterministic post-composition preserves mass
(SubProbability.mass_bind_dirac).
The probabilistic range of a lens: generated by the Dirac kernels of its localized
deterministic updates lens.liftFunction g. The sub-probability analogue of Lens.range.
Equations
- lens.footprint = GaudisCrypt.Footprint.from (Set.range lens.liftSubProbability)
Instances For
Lifting a Dirac kernel along a lens is the Dirac kernel of the lifted function.
diracKer (lens.liftFunction g) is a lens.footprint generator: it equals
lens.liftSubProbability (diracKer g), hence lies in lens.footprint.updates.
Kernel-shift extraction: a program in range R commutes with a deterministic
outside-update f (as a Dirac kernel). The inFootprint analogue of
ProgramDenotation.inRange_subprob.
pure x is in every probabilistic range — it touches no state.
ProgramDenotation.set v x lives in v.footprint.
ProgramDenotation.get v lives in v.footprint: it reads v, never writes. The extraction
hstar says any commutant kernel f preserves v.get almost surely.
diracKer is a monoid homomorphism Function.End s → (s → SubProbability s).
Commute two binds — a Fubini swap for sub-probability kernels.
Disjoint lenses' localized kernels commute (Fubini via bind_swap).
Every lens footprint is resettable — the probabilistic HasReset analogue of
Lens.range_hasOrbitCollapse. The reset is the lens overwrite l.set (l.get σ); it lands every
state in σ's (l.footprint)ᶜ-orbit, so touched_getter collapses to σ's value.
A lens with subsingleton content has trivial footprint. Its localized kernels can only
resample the unique content value, so they are scaled identities — central in the kernel
monoid, hence inside every footprint, in particular ⊥.
A lens footprint inside its own commutant is trivial. Self-commutation makes any two
constant writes commute, which (evaluated at a state and read back through the lens) forces
all content values to coincide — so the content is a subsingleton and
Lens.footprint_eq_bot_of_subsingleton applies.
A lens footprint's touched content is its lens getter. For a lens l, the opaque orbit
quotient (l.footprint).touched_getter collapses to l.get: two states have equal touched
content iff they agree on l.get. Lets glob endpoints state their premises via the concrete
l.get instead of the quotient.
A lens footprint's complement touched content is the complement lens's getter. For a lens
l, ((l.footprint)ᶜ).touched_getter collapses to l.compl.get: two states have equal
outside-l content iff they agree on l.compl.get. The Oᶜ companion of
Lens.footprint_touched_getter_eq_iff (folds in compl_compl).
The lens converse: tests recover the lens content #
For a lens footprint the observational equivalence coincides with the touched getter: the
conditional abort Lens.testKer l x₀ (keep the state iff the lens reads x₀) lies in
l.footprint — it commutes with everything commuting with the lens writes — and its acceptance
mass reads the lens. So Footprint.indistinguishable pins the lens content exactly: this is the
tomography converse of Footprint.indistinguishable_of_touched_getter_eq, which
CounterExamples/IndistinguishableVsGlob.lean shows fails for general (abelian) footprints.
The conditional-abort test of a lens at x₀: keep the state if the lens reads x₀,
abort otherwise. Acceptance probability = "the lens reads x₀".
Instances For
The conditional abort is an honest l-test: it lies in the lens footprint. It commutes with
any kernel k commuting with the constant writes, because such a k satisfies
k σ >>= (pure ∘ l.set c) = k (l.set c σ) — its output's l-content is pinned by a write —
so the abort filter passes k's output through untouched (accept branch) or kills it
entirely (reject branch).
Tests recover the lens content: states indistinguishable through a lens footprint have
equal lens reads — apply the conditional abort at l.get σ.
On lens footprints the two notions agree: observational indistinguishability = equal
touched getter (= equal lens content, via Lens.footprint_touched_getter_eq_iff). This is
the tomography converse that fails for general footprints — for a genuine memory region,
what the tests see is exactly what the getter reads.
The lens-content form of the agreement.
The agreement transfers along an identification of a footprint with a lens region — the form
consumed for syntactic adversaries, whose assigned region (FVP.fvP_proc) is a variable
(lens) region.
Tests pin the lens content pointwise: any footprint merely containing the
conditional-abort tests of l (not necessarily all of l.footprint) already separates
states by l.get.
touched_getter equality is antitone in the footprint: a smaller footprint has a coarser
touched getter, so S-touched equality descends to R-touched equality along R ≤ S.
Pointwise sandwich agreement: for a footprint R that (i) contains l's tests and
(ii) is bounded by l's region, indistinguishability through R is touched-getter
equality — no identification R = l.footprint needed. This is the form for syntactic
over-approximations (FVP.fvP_proc): (ii) is the standard upper-bound computation, and (i)
is a single generator membership (the reduced read-slices are the tests).
Disjointness bridge #
ProgramDenotation.set v x lives in L.footprintᶜ when v is disjoint from L.
ProgramDenotation.get v lives in L.footprintᶜ when v is disjoint from L.
Sampling: ProgramDenotation.uniform #
ProgramDenotation.uniform lives in the trivial range ⊥ — it samples a value without touching the
state.
Because ⊥ᶜ = univ, this means it commutes with every kernel, which is a Fubini swap between the
sampling and an arbitrary state-kernel. The swap (bind_swap) is countability-free (subtask 4): it
goes through the discreteness invariant, so neither the sampled type nor the (possibly uncountable)
state need be countable.
ProgramDenotation.uniform lives in the trivial range ⊥ — it samples a value, touching no
state.
Needs only Fintype α (the sampled type), not countability of the state.
Localized kernels lie in the lens's range #
An M-localized kernel lies in M.footprint. A kernel that reads only M.get, samples a
new M-value, and writes it back (ρ (M.get st) >>= fun mc' => pure (M.set mc' st)) commutes
with the commutant M.footprintᶜ — using that any such f preserves M.get a.s. and commutes
with M.set, plus the Fubini swap bind_swap (countability-free since subtask 4).
Disjoint programs commute (no orbit machinery) #
Programs with disjoint probabilistic ranges can be run in either order with the same joint
(output, state) distribution. Unlike the DetermFootprint version (commute_of_disjoint, which
needs HasOrbitCollapse preconditions and [Countable s]), this follows directly from the
constructive footprint + litmus: slicing the joint by the return value (x₀, y₀) collapses each
side to a product of the return-conditioned kernels kp/kq, which commute because they live in
the disjoint ranges R, R'. After subtask 4 this needs no countability at all — neither the
state s nor the return types — since slice-reassembly (ext_of_slices) goes through the
discreteness invariant.
Disjoint programs commute. If p lives in R, q in R', and R ≤ R'ᶜ, then p and
q may be run in either order with the same (output, state) distribution. The probabilistic
analogue of ProgramDenotation.commute_of_disjoint — but with no HasOrbitCollapse
hypotheses and,
after subtask 4, no countability whatsoever (the joint kernel is reassembled from its
slices via the discreteness invariant, not from countable state or return types).
Lens-range specialisation of commute_of_disjoint_footprint. A thin wrapper (no
HasOrbitCollapse to discharge, unlike the DetermFootprint commute_of_disjoint_lens),
matching that API for drop-in migration.
When the lenses l, l' are disjoint, the disjointness of their probabilistic ranges is
automatic (Lens.footprint_le_compl_of_disjoint), so the caller supplies only the two
inFootprint confinement proofs.
Corollaries: disjoint reads/writes commute #
End-to-end payoff of the toolkit — the primitives (inFootprint_set/get) feed straight into
commute_of_disjoint_lenses, so independent operations on disjoint lenses may be reordered.
while_loop confinement (fixpoint) #
A while loop whose guard and body are confined to R is itself confined to R. The loop is the
least fixpoint of while_iteration; each Kleene iterate is confined and confinement is closed under
ω-suprema of chains.
⊥ (the always-diverging program) lies in every footprint: it commutes with all kernels.
The "run outside kernel first" side of the inFootprint equation, as a map of the program p,
is ω-Scott-continuous (rewritten as a ProgramDenotation bind so bind_ωScottContinuous
applies).
The "run outside kernel last" side of the inFootprint equation is ω-Scott-continuous.
inFootprint R is closed under ω-suprema of chains. Both sides of the clean commutation
equation are ω-Scott-continuous in the program, so if every chain element self-commutes, the
supremum does too — the admissibility needed for the while_loop fixpoint.
One unrolling of the while_iteration operator preserves inFootprint R (given the guard and
body do).
while_loop confinement. A while loop whose guard and body are confined to R is itself
confined to R. The loop is the least fixpoint ⨆ₙ Fⁿ⊥ of while_iteration; each Kleene
iterate is confined (inFootprint_bot/while_iter_inFootprint), and inFootprint_ωSup passes
this to the supremum.
Reconstructing lenses from footprints #
Equations
- F.FromLens = ∃ (l : GaudisCrypt.Lens (Quotient Fᶜ.orbit_setoid) s), F = l.footprint
Instances For
Equations
- h.lens = Classical.choose h
Instances For
Lifting the top footprint through a lens recovers the lens's own footprint.
Corner / slice machinery (relocated from FV.lean) #
Given a joint kernel f on a × b,
an input distribution i on b, and a weighting o on the b-output, produce the a-kernel
that feeds i, runs f, and weights/discards the b-component via o.
Equations
Instances For
Reading the focus of a reconstructed state recovers the focus component.
Reading the complement of a reconstructed state recovers the complement component.
Overwriting the focus of a reconstructed state is the same as reconstructing with a new focus.
Left Fubini identity. Pre-composing a reduced generator with h equals reducing the joint
kernel pre-composed with the lift lens.liftSubProbability h.
Right Fubini identity. Post-composing a reduced generator with h equals reducing the
joint kernel post-composed with the lift lens.liftSubProbability h.
Slice determination. A kernel K : b → SubProbability b is determined by all its reduced
generators for a fixed lens: feeding a point input i = δ_β and an indicator weight o = [· = γ]
recovers K on the slice splitSpace.invFun (·, β) restricted to complement-output γ. Ranging
over all (β, γ) pins down K on every state. This is the one genuinely measure-theoretic
ingredient (discreteMeasure.ext on singletons).
Footprint of a chained lens is the outer lens's lift of the inner footprint.
Lens.chain lens1 lens2 threads through lens2 first and then lens1; the region it
touches in the outer state is lens1.liftFootprint applied to lens2's footprint.
Only the ≤ direction is proved here (closure-monotonicity: the chain's generator range is
the lens1-image of lens2's generator range, which sits inside the double-centralizer
closure). The ≥ direction is open: it needs the corner/bicommutant-splitting structure of
liftSubProbability, i.e. that lens1.liftSubProbability maps the double-centralizer closure
of a generator set into the closure of its image.
Bicommutant scaffolding, lens-corner extraction, and Footprint.lens_pair #
Every Footprint is its own bicommutant (the double_commutant field, in Set form).
The updates of a join is the double centralizer of the union of the updates.
lens.liftSubProbability is multiplicative, hence a monoid homomorphism on kernels. The lens
laws (set_get, set_set) make the two localizations of a Kleisli composition agree.
The bicommutant closure of the full set of lens-localized kernels is exactly
lens.footprint. Since Lens.footprint is now generated by all localized kernels
(Set.range lens.liftSubProbability = lens.liftSubProbability '' univ), this is definitional
— what used to be the hard half of the lens-corner double-commutant theorem.
Lens.liftFootprint is exactly the lens-image of the footprint ([Nonempty b]). The ⊇
half is the generic X ⊆ CC X; the ⊆ half: Lens.liftFootprint lands in lens.footprint,
every such element extracts as lens.liftSubProbability q, and (updateK being an injective
hom) q inherits the commutation defining range.updates. Over a lens corner the bicommutant
closure does not enlarge the image.
A lens footprint's complement is its complement lens's footprint.
The ≤ inclusion l.compl.footprint ≤ (l.footprint)ᶜ already exists
(Lens.footprint_le_compl_of_disjoint l.compl l, used in footprint_equivariant);
the reverse (l.footprint)ᶜ ≤ l.compl.footprint is the substantive half.
The footprint of a paired lens is the join of the components' footprints.
The ≥ direction is elementary: each component factors through the pair
(pair_fst/pair_snd), so its footprint is a liftFootprint of a sub-⊤
footprint, hence ≤ the pair's own footprint.
The ≤ direction is the product/"corner"-structure theorem: lifting through the
pair distributes over pair_footprint_fst_snd via Lens.liftFootprint_sup, and the
two lifted corners are the component footprints by chain_footprint + pair_fst/pair_snd.
FromLens closure properties #
Moved here from Language/Granularity.lean: these are general Footprint facts. They live below
Footprint.lens_pair because Footprint.fromLens_sup needs it.
Converse of Lens.footprint_le_compl_of_disjoint: lenses whose footprints lie in each
other's commutant have commuting setters. Both constant writes are Dirac kernels in their
lens's footprint, so the commutant hypothesis makes them commute as kernels; evaluating at a
state and stripping pure yields the plain set-commutation law.
Lens-derived footprints are closed under disjoint joins: pair the two lenses (the
disjointness instance comes from Lens.disjoint_of_footprint_le_compl) and read off
Footprint.lens_pair.
Lens.reduceFootprint (relocated from FV.lean) #
Equations
- lens.reduceFootprint range = GaudisCrypt.Footprint.from (lens.reduceSubProbability '' range.updates ×ˢ Set.univ ×ˢ Set.univ)
Instances For
Lens.reduceFootprint is monotone: a larger range gives a larger reduced range.
The lift of an update commutes with every R-update, when the update commutes with
the L-reduction of R (membership form: f ∈ (Lens.reduceFootprint L R)ᶜ.updates). The
reduced generators reduceSubProbability L (k, i, o) of k ∈ R.updates lie in
(Lens.reduceFootprint L R).updates, so hf makes them commute with f; the Fubini identities
(Lens.reduceSubProbability_mul_left/_right) turn that into commutation of
L.liftSubProbability f with k (via reduceSubProbability_ext).
Lens.reduceFootprint in commutant form. (Lens.reduceFootprint L R).updates is the
centralizer of the base kernels whose L-lift lands in Rᶜ (folding
Lens.reduceFootprint_alt_def through Footprint.from).