Documentation

VCVio.OracleComp.Constructions.SampleableType

Uniform Selection Over a Type #

This file defines a typeclass SampleableType β for types β with a canonical uniform selection operation, using the ProbComp monad.

As compared to HasUniformSelect this provides much more structure on the behavior, enforcing that every possible output has the same output probability never fails.

class SampleableType (β : Type) :

A SampleableType β instance means that β is a finite inhabited type, with a computation selectElem that selects uniformly at random from the type. This generally requires choosing some "canonical" ordering for the type, so we include this to get a computable version of selection. We also require that each element has the same probability of being chosen from by selectElem, see SampleableType.probOutput_uniformSample for the reduction when α has a fintype instance involving the explicit cardinality of the type.

Instances
    def uniformSample (β : Type) [h : SampleableType β] :

    Select uniformly from the type β using a type-class provided definition. NOTE: naming is somewhat strange now that Fintype isn't explicitly required.

    Instances For
      @[simp]
      theorem probOutput_uniformSample (α : Type) [ : SampleableType α] [Fintype α] (x : α) :
      Pr[= x | $ᵗ α] = (↑(Fintype.card α))⁻¹
      theorem probOutput_uniformSample_inj (α : Type) [ : SampleableType α] (x y : α) :
      Pr[= x | $ᵗ α] = Pr[= y | $ᵗ α]
      theorem probOutput_map_bijective_uniformSample (α : Type) [ : SampleableType α] {f : αα} (hf : Function.Bijective f) (x : α) :
      Pr[= x | f <$> ($ᵗ α)] = Pr[= x | $ᵗ α]
      theorem probOutput_map_bijective_uniform_cross (α : Type) [ : SampleableType α] {β : Type} [SampleableType β] [Finite α] (f : αβ) (hf : Function.Bijective f) (y : β) :
      Pr[= y | f <$> ($ᵗ α)] = Pr[= y | $ᵗ β]

      Pushing forward uniform sampling along a bijection preserves output probabilities.

      theorem probOutput_bind_bijective_uniform_cross (α : Type) [ : SampleableType α] {β γ : Type} [SampleableType β] [Finite α] (f : αβ) (hf : Function.Bijective f) (g : βProbComp γ) (z : γ) :
      Pr[= z | do let x$ᵗ α g (f x)] = Pr[= z | do let y$ᵗ β g y]

      Binding after pushing forward uniform sampling along a bijection preserves output probabilities.

      theorem probOutput_add_left_uniform (α : Type) [ : SampleableType α] [AddGroup α] (m x : α) :
      Pr[= x | (fun (x : α) => m + x) <$> ($ᵗ α)] = Pr[= x | $ᵗ α]
      theorem probOutput_bind_add_left_uniform (α : Type) [ : SampleableType α] [AddGroup α] {β : Type} (m : α) (f : αProbComp β) (z : β) :
      Pr[= z | do let y$ᵗ α f (m + y)] = Pr[= z | do let y$ᵗ α f y]
      theorem probOutput_add_right_uniform (α : Type) [ : SampleableType α] [AddGroup α] (m x : α) :
      Pr[= x | (fun (x : α) => x + m) <$> ($ᵗ α)] = Pr[= x | $ᵗ α]

      Right-translation analogue of probOutput_add_left_uniform: right-adding a constant to a uniform sample in AddGroup α preserves the output distribution, since (· + m) is a bijection on α with inverse (· + (-m)).

      theorem probOutput_bind_add_right_uniform (α : Type) [ : SampleableType α] [AddGroup α] {β : Type} (m : α) (f : αProbComp β) (z : β) :
      Pr[= z | do let y$ᵗ α f (y + m)] = Pr[= z | do let y$ᵗ α f y]
      @[simp]
      theorem evalDist_add_left_uniform (α : Type) [ : SampleableType α] [AddGroup α] (m : α) :
      𝒟[(fun (x : α) => m + x) <$> ($ᵗ α)] = 𝒟[$ᵗ α]

      Translating a uniform additive sample preserves the full evaluation distribution.

      theorem evalDist_add_left_uniform_eq (α : Type) [ : SampleableType α] [AddGroup α] (m₁ m₂ : α) :
      𝒟[(fun (x : α) => m₁ + x) <$> ($ᵗ α)] = 𝒟[(fun (x : α) => m₂ + x) <$> ($ᵗ α)]

      Two additive translations of a uniform sample have the same evaluation distribution.

      @[simp]
      theorem evalDist_add_right_uniform (α : Type) [ : SampleableType α] [AddGroup α] (m : α) :
      𝒟[(fun (x : α) => x + m) <$> ($ᵗ α)] = 𝒟[$ᵗ α]

      Right-translation analogue of evalDist_add_left_uniform: right-adding a constant to a uniform sample in AddGroup α preserves the full evaluation distribution.

      theorem evalDist_add_right_uniform_eq (α : Type) [ : SampleableType α] [AddGroup α] (m₁ m₂ : α) :
      𝒟[(fun (x : α) => x + m₁) <$> ($ᵗ α)] = 𝒟[(fun (x : α) => x + m₂) <$> ($ᵗ α)]

      Two right-translations of a uniform sample have the same evaluation distribution.

      theorem evalDist_map_bijective_uniform_cross (α : Type) [ : SampleableType α] {β : Type} [SampleableType β] [Finite α] (f : αβ) (hf : Function.Bijective f) :

      Pushing forward uniform sampling via a bijection preserves the full evaluation distribution.

      theorem evalDist_bind_bijective_add_right_uniform (α : Type) [ : SampleableType α] {β γ : Type} [AddGroup β] [SampleableType β] [Finite α] (f : αβ) (hf : Function.Bijective f) (m : β) (cont : βProbComp γ) :
      𝒟[do let x$ᵗ α cont (f x + m)] = 𝒟[do let y$ᵗ β cont y]

      Bijective uniform + right-translation gives uniform. Sampling x ← $ᵗ α, transporting through a bijection f : α → β, and right-adding any fixed m : β yields the same distribution as sampling y ← $ᵗ β directly, as observed by any continuation cont : β → ProbComp γ.

      This is the "one-time pad" fact underlying many cryptographic reductions: bijective transport makes f x uniform on β, and in any AddGroup β right-translation (· + m) is a bijection on the uniform measure, so the sum is again uniform.

      theorem evalDist_bind_bijective_add_right_eq (α : Type) [ : SampleableType α] {β γ : Type} [AddGroup β] [SampleableType β] [Finite α] (f : αβ) (hf : Function.Bijective f) (m₁ m₂ : β) (cont : βProbComp γ) :
      𝒟[do let x$ᵗ α cont (f x + m₁)] = 𝒟[do let x$ᵗ α cont (f x + m₂)]

      Constant-irrelevance form of evalDist_bind_bijective_add_right_uniform: sampling through a bijection and right-adding a constant has a distribution independent of the constant. Any two offsets produce the same evaluation distribution.

      @[simp]
      theorem mem_support_uniformSample (α : Type) [ : SampleableType α] {x : α} :
      @[simp]
      theorem probEvent_uniformSample (α : Type) [ : SampleableType α] [Fintype α] (p : αProp) [DecidablePred p] :
      @[reducible]
      Instances For
        @[implicit_reducible]
        @[implicit_reducible]
        @[implicit_reducible]
        @[implicit_reducible]
        instance instSampleableTypeRangeSumHAddOracleSpec {ι : Type u_1} {ι' : Type u_2} {spec : OracleSpec ι} {spec' : OracleSpec ι'} [h : (t : ι) → SampleableType (spec.Range t)] [h' : (t : ι') → SampleableType (spec'.Range t)] (t : ι ι') :
        SampleableType ((spec + spec').Range t)

        A sum of oracle specs with sampleable ranges again has sampleable ranges.

        @[implicit_reducible]

        Select a uniform element from α × β by independently selecting from α and β.

        @[reducible]
        def SampleableType.ofEquiv {α β : Type} [SampleableType α] (e : α β) :

        A type equivalent to a SampleableType is also SampleableType.

        Instances For
          @[implicit_reducible]

          Any finitely enumerable type can be sampled uniformly using the underlying equivalence.

          @[reducible]
          noncomputable def SampleableType.ofFintype (α : Type) [_root_.Fintype α] [Nonempty α] :

          Noncomputable bridge from a nonempty Fintype with decidable equality to SampleableType, via Fintype.equivFin. Used by downstream instances (e.g. Sym α n, Equiv.Perm α, β ↪ α) whose Mathlib Fintype instances are not paired with a FinEnum. Provided as a def rather than an instance to avoid overlap with FinEnum.SampleableType.

          Instances For

            This typeclass shouldn't cause diamonds since Finite is propositional.

            @[reducible]
            noncomputable def SampleableType.Fintype (α : Type) [h : SampleableType α] [DecidableEq α] :

            We avoid making this an instance globally as many types already have a Fintype instance that would not be definitionally equal to this one.

            Instances For
              @[implicit_reducible]
              @[implicit_reducible]
              @[implicit_reducible]
              @[implicit_reducible]

              Select a uniform element from Vector α n by independently selecting α at each index.

              @[implicit_reducible]
              instance instFintypeVector (α : Type u) (n : ) [Fintype α] :

              Vector α n is finite when α is finite, via the equivalence with Fin n → α.

              @[implicit_reducible]
              instance instSampleableTypeFinFunc {n : } {α : Type} [SampleableType α] :
              SampleableType (Fin nα)

              A function from Fin n to a SampleableType is also SampleableType. This is the base case used by the general FinEnum-indexed instSampleableTypeFunc below.

              @[implicit_reducible]
              instance instSampleableTypeFunc {β α : Type} [FinEnum β] [SampleableType α] :
              SampleableType (βα)

              A function β → α for β finitely enumerable and α sampleable is itself sampleable. This generalizes the Fin n → α instance above: the FinEnum.fin instance recovers it.

              @[implicit_reducible]

              Select a uniform element from List.Vector α n by independently selecting α at each index. The construction goes through the equivalence with Fin n → α.

              @[implicit_reducible]
              instance instSampleableTypeMatrix {α ι κ : Type} [FinEnum ι] [FinEnum κ] [SampleableType α] :

              Select a uniform element from Matrix ι κ α by independently selecting an entry for each (i, j). Both index types only need to be FinEnum; the previous Fin n × Fin m-indexed instance is recovered through FinEnum.fin.

              @[implicit_reducible]
              instance instSampleableTypeSum {α β : Type} [FinEnum α] [FinEnum β] [Nonempty (α β)] :

              Discoverability wrapper: SampleableType (α ⊕ β) follows from FinEnum on each side plus nonemptiness of the sum, via Mathlib's FinEnum.sum instance and FinEnum.SampleableType. Listed explicitly so users can see it in the instance set rather than relying on a multi-step search.

              @[implicit_reducible]

              Discoverability wrapper: SampleableType (Finset α) for FinEnum α. Uniform sampling draws every subset of α with the same probability (2^|α| outcomes). Finset α is always inhabited by , so no Nonempty hypothesis is needed.

              @[implicit_reducible]
              noncomputable instance instSampleableTypeSym {α : Type} {n : } [Fintype α] [DecidableEq α] [Nonempty α] :

              Uniform sampling of size-n multisets over α. Sym α n is the correct finite analogue of Multiset α: a plain Multiset α is unbounded in multiplicity and thus not Fintype, while Sym α n is a Fintype whenever α is. The Mathlib instance lives in Mathlib.Data.Fintype.Vector; we lift it through SampleableType.ofFintype.

              @[implicit_reducible]
              noncomputable instance instSampleableTypePerm {α : Type} [Fintype α] [DecidableEq α] :

              Uniform sampling of permutations of a finite type. Equiv.Perm α has n! elements when Fintype.card α = n. Mathlib provides Fintype (Equiv.Perm α) via Mathlib.Data.Fintype.Perm; we lift through SampleableType.ofFintype. Useful for shuffle-based protocols and oblivious-permutation games.

              @[implicit_reducible]
              noncomputable instance instSampleableTypeEmbedding {β α : Type} [Fintype β] [Fintype α] [DecidableEq β] [DecidableEq α] [Nonempty (β α)] :

              Uniform sampling of injections β ↪ α for finite types. The number of such embeddings is α.card! / (α.card - β.card)! when β.card ≤ α.card, else 0; the Nonempty (β ↪ α) hypothesis rules out the latter case. Mathlib provides Fintype (β ↪ α) via Mathlib.Data.Fintype.Pi; we lift through SampleableType.ofFintype.

              theorem evalDist_uniformSample_bind_update {D R : Type} [Finite D] [DecidableEq D] [Finite R] [Nonempty R] [SampleableType R] [SampleableType (DR)] (t : D) :
              𝒟[do let u$ᵗ R let g$ᵗ (DR) pure (Function.update g t u)] = 𝒟[$ᵗ (DR)]

              Overwriting one coordinate of a uniform function table is measure-preserving.

              Drawing a value u uniformly from R, then a full function table g : D → R uniformly, and returning Function.update g t u yields the same distribution as drawing the table directly.

              This is the t-marginal independence of the uniform (product) distribution on D → R: the value at coordinate t is uniform and independent of the others, so replacing it with a fresh independent uniform draw leaves the joint distribution unchanged. It is the marginalization step behind eager-sampling reformulations of oracle responses.

              The first coordinate of a uniform pair is uniform.

              Mapping the uniform distribution on α × β through Prod.fst yields the uniform distribution on α: the Prod.fst-marginal of a uniform (product) distribution is uniform.

              theorem evalDist_uniformSample_map_comp_injective {A B R : Type} [Finite A] [Finite B] [Finite R] [Nonempty R] [SampleableType R] [SampleableType (AR)] [SampleableType (BR)] {e : AB} (he : Function.Injective e) :
              𝒟[do let g$ᵗ (BR) pure (g e)] = 𝒟[$ᵗ (AR)]

              Restricting a uniform function table to a subdomain along an injection is uniform.

              For an injection e : A → B between finite types, drawing a uniform table g : B → R and restricting it along e (i.e. g ∘ e) yields the uniform distribution on A → R.

              This is the marginalization of the uniform (product) distribution on B → R onto the block of coordinates indexed by Set.range e: those coordinates are jointly uniform and independent of the rest, and e reindexes the block by A. It underlies eager-sampling reformulations that project a fine-grained random-oracle table onto a coarser one.

              noncomputable def patchTable {D R : Type} [DecidableEq D] [SampleableType R] :
              List D(DR)ProbComp (DR)

              Patch a uniform function table at every point of a list l, drawing one fresh uniform value per list entry. With l = [] the table is returned unchanged; with l = d :: ds the tail is patched first and the head point d is then overwritten with a fresh uniform draw.

              This is the iterated form of Function.update used by evalDist_uniformSample_patchList: the outermost update is at the head, so the list is consumed head-first.

              Instances For
                @[simp]
                theorem patchTable_nil {D R : Type} [DecidableEq D] [SampleableType R] (g : DR) :
                theorem patchTable_cons {D R : Type} [DecidableEq D] [SampleableType R] (d : D) (ds : List D) (g : DR) :
                patchTable (d :: ds) g = do let g'patchTable ds g let u$ᵗ R pure (Function.update g' d u)
                theorem evalDist_uniformSample_patchList {D R : Type} [Finite D] [DecidableEq D] [Finite R] [Nonempty R] [SampleableType R] [SampleableType (DR)] (l : List D) :
                𝒟[do let g$ᵗ (DR) patchTable l g] = 𝒟[$ᵗ (DR)]

                Patching a uniform function table at finitely many points preserves uniformity.

                Drawing a uniform table g : D → R and then patchTable l g — overwriting g at every point of l with independent fresh uniform draws — yields the same distribution as drawing the table directly. The points of l need not be distinct: each Function.update is the outermost operation of its recursion step, so evalDist_uniformSample_bind_update applies regardless of overlap. This is the marginalization step behind trace-conditioned eager-table reformulations, where the patched points are determined only after the table is sampled.

                theorem probOutput_uniformBool_not_decide_eq_decide {ob : ProbComp Bool} :
                Pr[= true | do let b$ᵗ Bool let b'ob pure !decide (b = b')] = Pr[= true | do let b$ᵗ Bool let b'ob pure (decide (b = b'))]

                Given an independent probabilistic computation ob : ProbComp Bool, the probability that its output b' differs from a uniformly chosen boolean b is the same as the probability that they are equal. In other words, P(b ≠ b') = P(b = b') where b is uniform.

                theorem probOutput_bind_uniformBool {α : Type} (f : BoolProbComp α) (x : α) :
                Pr[= x | do let b$ᵗ Bool f b] = (Pr[= x | f true] + Pr[= x | f false]) / 2

                Conditioning on a uniform boolean averages the two branch probabilities.

                theorem probOutput_uniformBool_branch_toReal_sub_half (real rand : ProbComp Bool) :
                Pr[= true | do let b$ᵗ Bool have __do_jp : BoolProbComp Bool := fun (z : Bool) => pure (b == z) if b = true then do let yreal __do_jp y else do let yrand __do_jp y].toReal - 1 / 2 = (Pr[= true | real].toReal - Pr[= true | rand].toReal) / 2

                Guessing a uniformly random bit after branching between real and rand decomposes into the difference of the branch success probabilities.

                theorem probOutput_decide_eq_uniformBool_half (f : BoolProbComp Bool) (heq : 𝒟[f true] = 𝒟[f false]) :
                Pr[= true | do let b$ᵗ Bool let b'f b pure (decide (b = b'))] = 1 / 2

                If the distribution of f b is independent of b, then guessing a uniformly random bit by running f has success probability exactly 1/2. This is the core lemma behind "all-random hybrid has probability 1/2" arguments.

                def uniformSampleImpl {ι : Type u_1} {spec : OracleSpec ι} [(i : ι) → SampleableType (spec.Range i)] :

                Given that the output type of all oracles has a SampleableType instance, replace all queries with uniformly random responses by calling the corresponding uniformSample at each query.

                Instances For
                  @[simp]
                  theorem uniformSampleImpl.evalDist_simulateQ {ι : Type u_1} {spec : OracleSpec ι} [(i : ι) → SampleableType (spec.Range i)] [spec.IsUniformSpec] {α : Type} (oa : OracleComp spec α) :
                  @[simp]
                  theorem uniformSampleImpl.probOutput_simulateQ {ι : Type u_1} {spec : OracleSpec ι} [(i : ι) → SampleableType (spec.Range i)] [spec.IsUniformSpec] {α : Type} (oa : OracleComp spec α) (x : α) :
                  @[simp]
                  theorem uniformSampleImpl.probEvent_simulateQ {ι : Type u_1} {spec : OracleSpec ι} [(i : ι) → SampleableType (spec.Range i)] [spec.IsUniformSpec] {α : Type} (oa : OracleComp spec α) (p : αProp) :
                  @[simp]
                  theorem uniformSampleImpl.support_simulateQ {ι : Type u_1} {spec : OracleSpec ι} [(i : ι) → SampleableType (spec.Range i)] [spec.IsUniformSpec] {α : Type} (oa : OracleComp spec α) :
                  @[simp]
                  theorem uniformSampleImpl.finSupport_simulateQ {ι : Type u_1} {spec : OracleSpec ι} [(i : ι) → SampleableType (spec.Range i)] [spec.IsUniformSpec] {α : Type} [DecidableEq α] (oa : OracleComp spec α) :