Theory Pseudorandom_Combinators

section ‹Combinators for Pseudo-random Objects›

text ‹This section introduces a combinator library for pseudo-random objects. Each object can be
described as a sample space, a function from an initial segment of the natural numbers that selects
a value (or data structure.) Semantically they are multisets with the natural interpretation as a
probability space (each element is selected with a probability proportional to its occurence count
in the multiset). Operationally the selection procedure describes an algorithm to sample from the
space.

After general definitions and lemmas basic sample spaces, such as chosing a natural uniformly in
an initial segment, a product construction the main pseudo-random objects: hash families and
expander graphs are introduced. In both cases the range is itself an arbitrary sample space, such
that it is for example possible to construct a pseudo-random object that samples seeds for hash
families using an expander walk.

The definitions @{term "Ψ"} in Section~\ref{sec:inner_algorithm} and @{term "Θ"} in
Section~\ref{sec:outer_algorithm} are good examples.

A nice introduction into such constructions has been published by Goldreich~\cite{goldreich2011}.›

subsection ‹Definitions and General Lemmas›

theory Pseudorandom_Combinators
  imports
    Finite_Fields.Card_Irreducible_Polynomials
    Universal_Hash_Families.Carter_Wegman_Hash_Family
    Frequency_Moments.Product_PMF_Ext
    Distributed_Distinct_Elements_Preliminary
    Expander_Graphs.Expander_Graphs_Strongly_Explicit
begin

unbundle intro_cong_syntax
hide_const "Quantum.T"
hide_const "Discrete_Topology.discrete"
hide_const "Polynomial.order"
no_notation Digraph.dominates ("_ ı _" [100,100] 40)

record 'a sample_space =
  size :: "nat"
  sample_space_select :: "nat  'a"

definition sample_pmf
  where "sample_pmf S = map_pmf (sample_space_select S) (pmf_of_set {..<size S})"

definition "sample_space S  size S > 0"

definition "select S k = (sample_space_select S (if k < size S then k else 0))"

definition "sample_set S = select S ` {..<size S}"

lemma sample_space_imp_ne:
  assumes "sample_space S"
  shows "{..<size S}  {}"
  using assms unfolding sample_space_def by auto

lemma sample_pmf_alt:
  assumes "sample_space S"
  shows "sample_pmf S = map_pmf (select S) (pmf_of_set {..<size S})"
  using sample_space_imp_ne[OF assms] unfolding sample_pmf_def select_def
  by (intro map_pmf_cong refl) simp

lemma sample_space_alt:
  assumes "sample_space S"
  shows "sample_set S = set_pmf (sample_pmf S)"
  using sample_space_imp_ne[OF assms]
  unfolding sample_set_def sample_pmf_alt[OF assms]
  by simp

lemma sample_set_alt:
  assumes "sample_space S"
  shows "sample_set S = sample_space_select S ` {..<size S}"
  unfolding sample_set_def select_def
  by (intro image_cong) auto

lemma select_range:
  assumes "sample_space S"
  shows "select S i  sample_set S"
  using assms unfolding sample_space_def select_def sample_set_def by auto

declare [[coercion sample_pmf]]

lemma integrable_sample_pmf[simp]:
  fixes f :: "'a  'c::{banach, second_countable_topology}"
  assumes "sample_space S"
  shows "integrable (measure_pmf (sample_pmf S)) f"
proof -
  have "finite (set_pmf (pmf_of_set {..<size S}))"
    using assms sample_space_def
    by (subst set_pmf_of_set) auto
  hence "finite (set_pmf (sample_pmf S))"
    unfolding sample_pmf_def by simp
  thus ?thesis
    by (intro integrable_measure_pmf_finite)
qed

subsection ‹Basic sample spaces›

text ‹Sample space for uniformly selecting a natural number less than a given bound:›

definition nat_sample_space :: "nat  nat sample_space" ("[_]S")
  where "nat_sample_space n =  size = n, select = id "

lemma nat_sample_pmf:
  "sample_pmf ([x]S) = pmf_of_set {..<x}"
  unfolding nat_sample_space_def sample_pmf_def by simp

lemma nat_sample_space[simp]:
  assumes "n > 0"
  shows "sample_space [n]S"
  using assms
  unfolding sample_space_def nat_sample_space_def by simp

text ‹Sample space for the product of two sample spaces:›

definition prod_sample_space ::
  "'a sample_space  'b sample_space  ('a × 'b) sample_space" (infixr "×S" 65)
  where
    "prod_sample_space s t =
       size = size s * size t,
        select = (λi. (select s (i mod (size s)), select t (i div (size s)))) "

lemma split_pmf_mod_div':
  assumes "a > (0::nat)"
  assumes "b > 0"
  shows "map_pmf (λx. (x mod a, x div a)) (pmf_of_set {..<a * b}) = pmf_of_set ({..<a} × {..<b})"
proof -
  have "x + a * y < a * b" if "x < a" "y < b" for x y
  proof -
    have a:"y+1  b" using that by simp
    have "x + a * y < a + a * y"
      using that by simp
    also have "... = a * (y+1)"
      by simp
    also have "...  a * b"
      by (intro mult_left_mono a) auto
    finally show ?thesis by simp
  qed

  hence "bij_betw (λx. (x mod a, x div a)) {..<a * b} ({..<a} × {..<b})"
    using assms less_mult_imp_div_less
    by (intro bij_betwI[where g="(λx. fst x + a * snd x)"])
     (auto simp add:mult.commute)

  moreover have "a * b > 0" using assms by simp
  hence "{..<a * b}  {}" by blast
  ultimately show "?thesis"
    by (intro map_pmf_of_set_bij_betw) auto
qed

lemma pmf_of_set_prod_eq:
  assumes "A  {}" "finite A"
  assumes "B  {}" "finite B"
  shows  "pmf_of_set (A × B) = pair_pmf (pmf_of_set A) (pmf_of_set B)"
proof -
  have "indicat_real (A × B) (i, j) = indicat_real A i * indicat_real B j" for i j
    by (cases "i  A"; cases "j  B") auto
  hence "pmf (pmf_of_set (A × B)) (i,j) = pmf (pair_pmf (pmf_of_set A) (pmf_of_set B)) (i,j)"
    for i j using assms by (simp add:pmf_pair)
  thus ?thesis
    by (intro pmf_eqI) auto
qed

lemma split_pmf_mod_div:
  assumes "a > (0::nat)"
  assumes "b > 0"
  shows "map_pmf (λx. (x mod a, x div a)) (pmf_of_set {..<a * b}) =
    pair_pmf (pmf_of_set {..<a}) (pmf_of_set {..<b})"
  using assms by (auto intro!: pmf_of_set_prod_eq simp add:split_pmf_mod_div')

lemma split_pmf_mod:
  assumes "a > (0::nat)"
  assumes "b > 0"
  shows "map_pmf (λx. x mod a) (pmf_of_set {..<a * b}) = pmf_of_set {..<a}"
proof -
  have "map_pmf (λx. x mod a) (pmf_of_set {..<a * b}) =
   map_pmf (fst  (λx. (x mod a, x div a))) (pmf_of_set {..<a * b})"
    by (simp add:comp_def)
  also have "... = map_pmf fst (pair_pmf (pmf_of_set {..<a}) (pmf_of_set {..<b}))"
    by (simp add:map_pmf_compose split_pmf_mod_div[OF assms])
  also have "... = pmf_of_set {..<a}"
    by (simp add:map_fst_pair_pmf)
  finally show ?thesis by simp
qed

lemma prod_sample_pmf:
  assumes "sample_space S"
  assumes "sample_space T"
  shows "sample_pmf (S ×S T) = pair_pmf (sample_pmf S) (sample_pmf T)" (is "?L = ?R")
proof -
  have size: "size S * size T > 0"
    using assms sample_space_def by (metis nat_0_less_mult_iff)
  hence a:"{..<size S * size T}  {}" "finite {..<size S * size T}"
    using lessThan_empty_iff by auto
  have b:"x div size S mod size T = x div size S" if "x < size S * size T" for x
    by (simp add: algebra_simps less_mult_imp_div_less that)

  have "?L = map_pmf (λi. (select S (i mod size S), select T (i div size S)))
    (pmf_of_set {..<size S * size T})"
    unfolding sample_pmf_def prod_sample_space_def by simp
  also have "... = map_pmf ((λ(x,y). (select S x, select T y))  (λi. (i mod size S, i div size S)))
    (pmf_of_set {..<size S * size T})"
    by (simp add:comp_def)
  also have "... = map_pmf (λ(x,y). (select S x, select T y))
    (map_pmf (λi. (i mod size S, i div size S)) (pmf_of_set {..<size S * size T}))"
    by (subst map_pmf_compose)  simp
  also have "... = map_pmf (λ(x,y). (select S x, select T y))
    (pair_pmf (pmf_of_set {..<size S}) (pmf_of_set {..<size T}))"
    using size by (subst split_pmf_mod_div) auto
  also have "... = ?R"
    unfolding sample_pmf_alt[OF assms(1)] sample_pmf_alt[OF assms(2)] map_pair by simp
  finally show ?thesis
    by simp
qed

lemma prod_sample_space[simp]:
  assumes "sample_space S" "sample_space T"
  shows "sample_space (S ×S T)"
  using assms
  unfolding sample_space_def prod_sample_space_def by simp

lemma prod_sample_set:
  assumes "sample_space S"
  assumes "sample_space T"
  shows "sample_set (S ×S T) = sample_set S × sample_set T" (is "?L = ?R")
  using assms by (simp add:sample_space_alt prod_sample_pmf)

subsection ‹Hash Families›

lemma indep_vars_map_pmf:
  assumes "prob_space.indep_vars (measure_pmf p) (λ_. discrete) (λi ω. X' i (f ω)) I"
  shows "prob_space.indep_vars (measure_pmf (map_pmf f p)) (λ_. discrete) X' I"
proof -
  have "prob_space.indep_vars (measure_pmf p) (λ_. discrete) (λi. X' i  f) I"
    using assms by (simp add:comp_def)
  hence "prob_space.indep_vars (distr (measure_pmf p) discrete f) (λ_. discrete) X' I"
    by (intro prob_space.indep_vars_distr prob_space_measure_pmf) auto
  thus ?thesis
    using map_pmf_rep_eq by metis
qed

lemma k_wise_indep_vars_map_pmf:
  assumes "prob_space.k_wise_indep_vars (measure_pmf p) k (λ_. discrete) (λi ω. X' i (f ω)) I"
  shows "prob_space.k_wise_indep_vars (measure_pmf (map_pmf f p)) k (λ_. discrete) X' I"
  using assms indep_vars_map_pmf
  unfolding prob_space.k_wise_indep_vars_def[OF prob_space_measure_pmf]
  by blast

lemma (in prob_space) k_wise_indep_subset:
  assumes "J  I"
  assumes "k_wise_indep_vars k M' X' I"
  shows "k_wise_indep_vars k M' X' J"
  using assms unfolding k_wise_indep_vars_def by simp

lemma (in prob_space) k_wise_indep_vars_reindex:
  assumes "inj_on f I"
  assumes "k_wise_indep_vars k M' X' (f ` I)"
  shows "k_wise_indep_vars k (M'  f) (λk ω. X' (f k) ω) I"
proof -
  have "indep_vars (M'  f) (λk. X' (f k)) J" if "finite J" "card J  k" "J  I" for J
  proof -
    have "f ` J  f ` I" using that by auto
    moreover have "card (f ` J)  k"
      using card_image_le[OF that(1)] that(2) order.trans by auto
    moreover have "finite (f ` J)" using that by auto
    ultimately have "indep_vars M' X' (f ` J)"
      using assms(2) unfolding k_wise_indep_vars_def by simp
    thus ?thesis
      using that assms(1) inj_on_subset
      by (intro indep_vars_reindex)
  qed
  thus ?thesis
    unfolding k_wise_indep_vars_def by simp
qed

definition GF :: "nat  int set list set ring"
  where "GF n = (SOME F. finite_field F  order F = n)"

definition is_prime_power :: "nat  bool"
  where "is_prime_power n  (p k. Factorial_Ring.prime p  k > 0  n = p^k)"

lemma
  assumes "is_prime_power n"
  shows GF: "finite_field (GF n)" "order (GF n) = n"
proof -
  obtain p k where p_k: "Factorial_Ring.prime p" "k > 0" "n = p^k"
    using assms unfolding is_prime_power_def by blast
  have a:"(F :: int set list set ring). finite_field F  order F = n"
    using existence[OF p_k(2,1)] p_k(3) by simp
  show  "finite_field (GF n)" "order (GF n) = n"
    unfolding GF_def
    using someI_ex[OF a]
    by auto
qed

lemma is_prime_power: "Factorial_Ring.prime p  k > 0  is_prime_power (p^k)"
  unfolding is_prime_power_def by auto

definition split_prime_power :: "nat  (nat × nat)"
  where "split_prime_power n = (THE (p, k). p^k = n  Factorial_Ring.prime p  k > 0)"

lemma split_prime_power:
  assumes "Factorial_Ring.prime p"
  assumes "k > 0"
  shows "split_prime_power (p^k) = (p,k)"
proof -
  have "q = p  l = k" if "q^l = p^k" "Factorial_Ring.prime q" "l > 0" for q l
  proof -
    have "q dvd p^k" using that by (metis dvd_power)
    hence "q dvd p" using prime_dvd_power that by auto
    moreover have "p dvd q^l" using that assms(2) by (metis dvd_power)
    hence "p dvd q" using prime_dvd_power that assms by blast
    ultimately have a:"p = q" by auto
    hence "l = k" using that prime_power_inj by auto
    thus ?thesis using a by simp
  qed
  thus ?thesis
    unfolding split_prime_power_def using assms
    by (intro the_equality) auto
qed

definition  :: "nat  nat  'a sample_space  (nat  'a) sample_space"
  where " k d R = (
    let (p,n) = split_prime_power (size R);
        m = (LEAST j. d  p^j  j  n);
        f = from_nat_into (carrier (GF (p^m)));
        f' = to_nat_on (carrier (GF (p^m)));
        g = from_nat_into (bounded_degree_polynomials (GF (p^m)) k) in
     size = p^(m*k), select = (λi x. select R ((f' (ring.hash (GF (p^m)) (f x) (g i))) mod p^n)))"

locale hash_sample_space =
  fixes k d p n :: nat
  fixes R :: "'a sample_space"
  assumes p_prime: "Factorial_Ring.prime p"
  assumes size_R: "size R = p ^ n"
  assumes k_gt_0: "k > 0"
  assumes n_gt_0: "n > 0"
begin

abbreviation S where "S   k d R"

lemma p_n_def: "(p,n) = split_prime_power (size R)"
  unfolding size_R
  by (intro split_prime_power[symmetric] n_gt_0 p_prime)

definition m where "m = (LEAST j. d  p^j  j  n)"
definition f where "f = from_nat_into (carrier (GF (p^m)))"
definition f' where "f' = to_nat_on (carrier (GF (p^m)))"

lemma n_lt_m: "n  m" and d_lt_p_m: "d  p^m"
proof -
  define j :: nat where "j = max n d"
  have "d  2^d" by simp
  also have "...  2^j"
    unfolding j_def
    by (intro iffD2[OF power_increasing_iff]) auto
  also have "...  p^j"
    using p_prime prime_ge_2_nat
    by (intro power_mono) auto
  finally have "d  p^j" by simp
  moreover have "n  j" unfolding j_def by simp
  ultimately have "d  p^m  m  n"
    unfolding m_def
    by (intro LeastI[where P="λx. d  p^ x  x  n" and k="j"]) auto
  thus "n  m" "d  p^m"
    by auto
qed

lemma
  is_field: "finite_field (GF (p^m))" (is "?A") and
  field_order: "order (GF(p^m)) = p^m"  (is "?B")
proof -
  have "is_prime_power (p^m)"
    using n_gt_0 n_lt_m
    by (intro is_prime_power p_prime) auto

  thus "?A" "?B"
    using GF by auto
qed

interpretation cw: carter_wegman_hash_family "GF (p^m)" "k"
  using finite_field_def is_field finite_field_axioms_def
  by (intro carter_wegman_hash_familyI k_gt_0) auto

lemma field_size: "cw.field_size = p^m"
  using field_order unfolding Coset.order_def by simp

lemma f_bij: "bij_betw f {..<p^m} (carrier (GF (p^m)))"
  unfolding f_def using field_size  bij_betw_from_nat_into_finite[where S="carrier (GF (p^m))"]
  by simp

definition g where "g = from_nat_into cw.space"

lemma p_n_gt_0: "p^n > 0"
  by (metis p_prime gr0I not_prime_0 power_not_zero)

lemma p_m_gt_0: "p^m > 0"
  by (metis p_prime gr0I not_prime_0 power_not_zero)

lemma S_eq: "S =  size = p^(m*k), sample_space_select = (λ i x. select R (f' (cw.hash (f x) (g i)) mod p^n )) "
  unfolding ℋ_def
  by (simp add:p_n_def[symmetric] m_def[symmetric] f_def[symmetric] g_def f'_def Let_def cw.space_def)

lemma ℋ_size: "size S > 0"
  unfolding S_eq using p_m_gt_0 k_gt_0 by simp

lemma sample_space: "sample_space S"
  using ℋ_size unfolding sample_space_def by simp

lemma sample_space_R: "sample_space R"
  using size_R p_n_gt_0 unfolding sample_space_def by auto

lemma range: "range (select S i)  sample_set R"
proof -
  define α where "α = select S i"
  have "α x  sample_set R" for x
  proof -
    have "α  sample_set S"
      unfolding α_def by (intro select_range sample_space)
    then obtain j where α_alt: "α = (λx. select R (f' (cw.hash (f x) (g j)) mod p^n))" "j < p^(m*k)"
      unfolding sample_set_alt[OF sample_space] unfolding S_eq by auto
    thus "α x  sample_set R"
      unfolding α_alt
      by (intro select_range sample_space_R)
  qed
  thus ?thesis
    unfolding α_def by auto
qed

lemma cw_space: "map_pmf g (pmf_of_set {..<p^(m*k)}) = pmf_of_set cw.space"
proof-
  have card_cw_space: "p ^ (m * k) = card (cw.space)"
    unfolding cw.space_def cw.bounded_degree_polynomials_card  field_size
    by (simp add:power_mult)
  have card_cw_space_gt_0: "card (cw.space) > 0"
    using card_gt_0_iff cw.finite_space cw.non_empty_bounded_degree_polynomials by blast

  show ?thesis
    unfolding g_def using card_cw_space card_cw_space_gt_0
      bij_betw_from_nat_into_finite[where S="cw.space"]
    by (intro map_pmf_of_set_bij_betw) auto
qed

lemma single:
  assumes "x < d"
  shows "map_pmf (λω. ω x) (sample_pmf S) = sample_pmf R" (is "?L = ?R")
proof -
  have f_x_carr: "f x  carrier (GF (p^m))"
    using assms d_lt_p_m
    by (intro bij_betw_apply[OF f_bij]) auto

  have "pmf (map_pmf (cw.hash (f x)) (pmf_of_set cw.space)) i =
    pmf (pmf_of_set (carrier (GF (p ^ m)))) i" (is "?L1 = ?R1") for i
  proof -
    have "?L1 = cw.prob (cw.hash (f x) -` {i})"
      unfolding cw.M_def by (simp add:pmf_map)
    also have "... = real (card ({i}  carrier (GF (p ^ m)))) / real cw.field_size"
      using cw.prob_range[OF f_x_carr, where A="{i}" ] by (simp add:vimage_def)
    also have "... = ?R1"
      by (cases "i  carrier (GF (p^m))", auto)
    finally  show ?thesis by simp
  qed

  hence b: "map_pmf (cw.hash (f x)) (pmf_of_set cw.space) = pmf_of_set (carrier (GF (p^m)))"
    by (intro pmf_eqI) simp

  have c: "map_pmf f' (pmf_of_set (carrier (GF (p^m)))) = pmf_of_set {..<p^m}"
    unfolding f'_def using to_nat_on_finite[where S="carrier (GF (p^m))"] field_size
    by (intro map_pmf_of_set_bij_betw) auto

  have "n  m" "p > 0"
    using n_lt_m p_prime prime_gt_0_nat by auto
  hence d: "map_pmf (λx. x mod p^n) (pmf_of_set {..<p^m}) = pmf_of_set {..<p^n}"
    using split_pmf_mod[where a = "p^n" and b="p^(m-n)"]
    by (simp add:power_add[symmetric])

  have "?L = map_pmf ((λω. ω x)  (sample_space_select S)) (pmf_of_set {..<size S})"
    unfolding sample_pmf_def by (simp add:map_pmf_compose)
  also have "... = map_pmf (λω. sample_space_select S ω x) (pmf_of_set {..<size S})"
    by (simp add:comp_def)
  also have "... = map_pmf (select R  (λx. x mod p^n)  f'  (cw.hash (f x))  g) (pmf_of_set {..<p^(m*k)})"
    unfolding S_eq by (simp add:comp_def)
  also have "... = map_pmf (select R) (pmf_of_set {..<p^n})"
    by (simp add:map_pmf_compose cw_space b c d)
  also have "... = ?R"
    unfolding sample_pmf_alt[OF sample_space_R] size_R by simp
  finally show ?thesis by simp
qed

lemma indep:
  "prob_space.k_wise_indep_vars (sample_pmf S) k (λ_. discrete) (λi ω. ω i) {..<d}"
proof -
  let ?p = "map_pmf g (pmf_of_set {..<p ^ (m * k)})"
  let ?h = "(λi x. select R (f' (cw.hash (f x) i) mod p ^ n))"

  have a:"cw.k_wise_indep_vars k (λ_. discrete) cw.hash (f ` {..<d})"
    using d_lt_p_m
    by (intro cw.k_wise_indep_subset[OF _ cw.k_wise_indep] image_subsetI bij_betw_apply[OF f_bij])
    auto

  have "cw.k_wise_indep_vars k (λ_. discrete) (λi ω. select R (f' (cw.hash i ω) mod p^n)) (f ` {..<d})"
    by (intro cw.k_wise_indep_vars_compose[OF a]) auto
  moreover
  have "inj_on f {..<p^m}"
    using f_bij bij_betw_def by auto
  hence "inj_on f {..<d}"
    using inj_on_subset d_lt_p_m by blast
  ultimately have "cw.k_wise_indep_vars k (λ_. discrete) (λi ω. select R (f' (cw.hash (f i) ω) mod p ^ n)) {..<d}"
    using cw.k_wise_indep_vars_reindex[where f="f"] unfolding comp_def by auto

  hence "prob_space.k_wise_indep_vars (measure_pmf ((map_pmf ?h  map_pmf g) (pmf_of_set {..<p^(m*k)}))) k
     (λ_. discrete) (λi ω. ω i) {..<d}"
    unfolding cw.M_def cw_space[symmetric] comp_def by (intro k_wise_indep_vars_map_pmf[where p="?p"]) auto

  hence "prob_space.k_wise_indep_vars (measure_pmf (map_pmf (λi x. ?h (g i) x) (pmf_of_set {..<p^(m*k)}))) k
     (λ_. discrete) (λi ω. ω i) {..<d}"
    unfolding map_pmf_compose[symmetric] by (simp add:comp_def)

  thus ?thesis
    unfolding sample_pmf_def S_eq by simp
qed

lemma size:
  fixes m :: nat
  assumes "d > 0"
  defines m_altdef: "m  max n (nat log p d)"
  shows "size S = p^(m*k)"
proof -
  have "real d = p powr (log p d)"
    using assms prime_gt_1_nat[OF p_prime]
    by (intro powr_log_cancel[symmetric]) auto
  also have "...  p powr (nat log p d)"
    using prime_gt_1_nat[OF p_prime] by (intro powr_mono) linarith+
  also have "... = p^ (nat log p d)"
    using prime_gt_1_nat[OF p_prime] by (subst powr_realpow) auto
  also have "...  p^m"
    using prime_gt_1_nat[OF p_prime] unfolding m_altdef
    by (intro power_increasing Nat.of_nat_mono) auto
  finally have "d  p ^ m"
    by simp

  moreover have "n  m"
    unfolding m_altdef by simp
  moreover have "m  y" if "d  p ^ y" "n  y" for y
  proof -
    have "log p d  log p (p ^ y)"
      using assms prime_gt_1_nat[OF p_prime]
      by (intro iffD2[OF log_le_cancel_iff] that(1) Nat.of_nat_mono) auto
    also have "... = log p (p powr (real y))"
      using prime_gt_1_nat[OF p_prime] by (subst powr_realpow) auto
    also have "... = y"
      using prime_gt_1_nat[OF p_prime] by (intro log_powr_cancel) auto
    finally have "log p d  y" by simp
    hence "nat log p d  y"
      by simp
    thus "m  y"
      using that(2) unfolding m_altdef by simp
  qed
  ultimately have m_eq: "m = (LEAST j. d  p ^ j  n  j)"
    by (intro Least_equality[symmetric]) auto

  show ?thesis
    unfolding S_eq m_def m_eq by simp
qed

end

text ‹Sample space with a geometric distribution›

fun count_zeros :: "nat  nat  nat" where
  "count_zeros 0 k = 0" |
  "count_zeros (Suc n) k = (if odd k then 0 else 1 + count_zeros n (k div 2))"

lemma count_zeros_iff: "j  n  count_zeros n k  j  2^j dvd k"
proof (induction j arbitrary: n k)
  case 0
  then show ?case by simp
next
  case (Suc j)
  then obtain n' where n_def: "n = Suc n'" using Suc_le_D by presburger
  show ?case using Suc unfolding n_def by auto
qed

lemma count_zeros_max:
  "count_zeros n k  n"
  by (induction n arbitrary: k) auto

definition 𝒢 :: "nat  nat sample_space" where
  "𝒢 n =  size = 2^n, sample_space_select = count_zeros n "

lemma 𝒢_sample_space[simp]: "sample_space (𝒢 n)"
  unfolding sample_space_def 𝒢_def by simp

lemma 𝒢_range: "sample_set (𝒢 n)  {..n}"
  using count_zeros_max
  unfolding sample_set_alt[OF 𝒢_sample_space] unfolding 𝒢_def by auto

lemma 𝒢_prob:
  "measure  (sample_pmf (𝒢 n)) {ω. ω  j} = of_bool (j  n) / 2^j" (is "?L = ?R")
proof (cases "j  n")
  case True
  have a:"{..<(2^n)::nat}  {}"
    by (simp add: lessThan_empty_iff)
  have b:"finite {..<(2^n)::nat}" by simp

  define f :: "nat  nat" where "f = (λx. x * 2^j)"
  have d:"inj_on  f {..<2^(n-j)}" unfolding f_def by (intro inj_onI) simp

  have e:"2^j > (0::nat)" by simp

  have "y  f ` {..< 2^(n-j)}  y  {x. x < 2^n  2^j dvd x}" for y :: nat
  proof -
    have "y  f ` {..< 2^(n-j)}  (x. x < 2 ^ (n - j)  y = 2 ^ j * x)"
      unfolding f_def by auto
    also have "...  (x. 2^j * x < 2^j * 2 ^ (n-j)  y = 2 ^ j * x)"
      using e by simp
    also have "...  (x. 2^j * x < 2^n  y = 2 ^ j * x)"
      using True by (subst power_add[symmetric]) simp
    also have "...  (x. y < 2^n  y = x * 2 ^ j)"
      by (metis Groups.mult_ac(2))
    also have "...  y  {x. x < 2^n  2^j dvd x}" by auto
    finally show ?thesis by simp
  qed

  hence c:"f ` {..< 2^(n-j)} = {x. x < 2^n  2^j dvd x}" by auto

  have "?L = measure (pmf_of_set {..<2^n}) {ω. count_zeros n ω  j}"
    unfolding sample_pmf_def 𝒢_def by simp
  also have "... = real (card {x::nat. x < 2^n  2^j dvd x}) / 2^n"
    by (simp add: measure_pmf_of_set[OF a b] count_zeros_iff[OF True])
     (simp add:lessThan_def Collect_conj_eq)
  also have "... = real (card (f ` {..<2^(n-j)})) / 2^n"
    by (simp add:c)
  also have "... = real (card ({..<(2^(n-j)::nat)})) / 2^n"
    by (simp add: card_image[OF d])
  also have "... = ?R"
    using True by (simp add:frac_eq_eq power_add[symmetric])
  finally show ?thesis by simp
next
  case False
  have "set_pmf (sample_pmf (𝒢 n))  {..n}"
    unfolding sample_space_alt[OF 𝒢_sample_space, symmetric]
    using 𝒢_range by simp
  hence "?L = measure (sample_pmf (𝒢 n)) {}"
    using False by (intro measure_pmf_cong) auto
  also have "... = ?R"
    using False by simp
  finally show ?thesis
    by simp
qed

lemma 𝒢_prob_single:
  "measure  (sample_pmf (𝒢 n)) {j}  1 / 2^j" (is "?L  ?R")
proof -
  have "?L = measure (sample_pmf (𝒢 n)) ({j..}-{j+1..})"
    by (intro measure_pmf_cong) auto
  also have "... = measure (sample_pmf (𝒢 n)) {j..} - measure (sample_pmf (𝒢 n)) {j+1..}"
    by (intro measure_Diff) auto
  also have "... = measure (sample_pmf (𝒢 n)) {ω. ω  j}-measure (sample_pmf (𝒢 n)) {ω. ω  (j+1)}"
    by (intro arg_cong2[where f="(-)"] measure_pmf_cong) auto
  also have "... = of_bool (j  n) * 1 / 2 ^ j - of_bool (j + 1  n) / 2 ^ (j + 1)"
    unfolding 𝒢_prob by simp
  also have "...  1/2^j - 0"
    by (intro diff_mono) auto
  also have "... = ?R" by simp
  finally show ?thesis by simp
qed

subsection ‹Expander Walks›

definition  :: "nat  real  'a sample_space  (nat  'a) sample_space"
  where " l Λ S = (let e = see_standard (size S) Λ in
     size = see_size e * see_degree e^(l-1),
      sample_space_select = (λi j. select S (see_sample_walk e (l-1) i ! j)) ) "

locale expander_sample_space =
  fixes l :: nat
  fixes Λ :: real
  fixes S :: "'a sample_space"
  assumes l_gt_0: "l > 0"
  assumes Λ_gt_0: "Λ > 0"
  assumes sample_space_S: "sample_space S"
begin

definition e where "e = see_standard (size S) Λ"

lemma size_S_gt_0: "size S > 0"
  using sample_space_S unfolding sample_space_def by simp

lemma ℰ_alt: "( l Λ S) =
   size = see_size e * see_degree e^(l-1),
    sample_space_select = (λi j. select S (see_sample_walk e (l-1) i ! j)) "
  unfolding ℰ_def e_def[symmetric] by (simp add:Let_def)

lemmas see_standard = see_standard[OF size_S_gt_0 Λ_gt_0]

sublocale E: regular_graph "graph_of e"
  using see_standard(1) unfolding is_expander_def e_def by auto

lemma e_deg_gt_0: "see_degree e > 0"
  unfolding e_def see_standard by simp

lemma e_size_gt_0: "see_size e > 0"
  unfolding e_def see_standard using size_S_gt_0 by simp

lemma sample_space: "sample_space ( l Λ S)"
  unfolding sample_space_def ℰ_alt using e_size_gt_0 e_deg_gt_0 by simp

lemma range: "select ( l Λ S) i j  sample_set S"
proof -
  define α where "α = select ( l Λ S) i"
  have "α  sample_set ( l Λ S)"
    unfolding α_def by (intro select_range sample_space)
  then obtain k where "α = sample_space_select  ( l Λ S) k"
    using sample_set_alt[OF sample_space] by auto
  hence "α j  sample_set S"
    unfolding ℰ_alt using select_range[OF sample_space_S] by simp
  thus ?thesis
    unfolding α_def by simp
qed

lemma sample_set: "sample_set ( l Λ S)  (UNIV  sample_set S)"
proof (rule subsetI)
  fix x assume "x  sample_set ( l Λ S)"

  then obtain i where "x = select ( l Λ S) i"
    unfolding sample_set_def by auto
  thus "x  UNIV  sample_set S"
    using range by auto
qed

lemma walks:
  defines "R  map_pmf (λxs i. select S (xs ! i)) (pmf_of_multiset (walks (graph_of e) l))"
  shows "sample_pmf ( l Λ S) = R"
proof -
  let ?S = "{..<see_size e * see_degree e ^ (l-1)}"
  let ?T = "(map_pmf (see_sample_walk e (l-1)) (pmf_of_set ?S))"

  have "0  ?S"
    using e_size_gt_0 e_deg_gt_0 l_gt_0 by auto
  hence "?S  {}"
    by blast
  hence "?T = pmf_of_multiset {#see_sample_walk e (l-1) i. i ∈# mset_set ?S#}"
    by (subst map_pmf_of_set) simp_all
  also have "... = pmf_of_multiset (walks' (graph_of e) (l-1))"
    by (subst see_sample_walk) auto
  also have "... = pmf_of_multiset (walks (graph_of e) l)"
    unfolding walks_def using l_gt_0 by (cases l, simp_all)
  finally have 0:"?T = pmf_of_multiset (walks (graph_of e) l)"
    by simp

  have "sample_pmf ( l Λ S) = map_pmf (λxs j. select S (xs ! j)) ?T"
    unfolding map_pmf_comp sample_pmf_def ℰ_alt by simp
  also have "... = R"
    unfolding 0 R_def by simp
  finally show ?thesis by simp
qed

lemma uniform_property:
  assumes "i < l"
  shows "map_pmf (λw. w i) ( l Λ S) = sample_pmf S" (is "?L = ?R")
proof -
  have "?L = map_pmf (select S) (map_pmf (λxs. (xs ! i)) (pmf_of_multiset (walks (graph_of e) l)))"
    unfolding walks by (simp add: map_pmf_comp)
  also have "... = map_pmf (select S) (pmf_of_set (verts (graph_of e)))"
    unfolding E.uniform_property[OF assms] by simp
  also have "... = ?R"
    unfolding sample_pmf_alt[OF sample_space_S] e_def graph_of_def using see_standard by simp
  finally show ?thesis
    by simp
qed

lemma size:
  "size ( l Λ S) =  size S * (16 ^ ((l-1) * nat ln Λ / ln (19 / 20)))" (is "?L = ?R")
proof -
  have "?L = see_size e * see_degree e ^ (l - 1)"
    unfolding ℰ_alt by simp
  also have "... = size S *  (16 ^ nat ln Λ / ln (19 / 20)) ^ (l - 1)"
    using see_standard unfolding e_def by simp
  also have "... = size S * (16 ^ ((l-1) * nat ln Λ / ln (19 / 20)))"
    unfolding power_mult[symmetric] by (simp add:ac_simps)
  finally show ?thesis
    by simp
qed

end

end