Theory Oracle_Programs

section Oracle_Programs› -- Oracle programs and their execution›

theory Oracle_Programs imports
  CO_Operations
  Invariant_Preservation
  Compressed_Oracle_Is_RO
begin

subsection ‹Oracle programs›

datatype ('mem, 'x, 'y) program_step = ProgramStep 'mem update | QueryStep 'x update  'mem update 'y update  'mem update
type_synonym ('mem, 'x, 'y) program = ('mem, 'x, 'y) program_step list


inductive is_QueryStep :: ('mem,'x,'y::ab_group_add) program_step  bool where is_QueryStep_QueryStep[iff]: is_QueryStep (QueryStep X Y)
inductive is_ProgramStep :: ('mem,'x,'y::ab_group_add) program_step  bool where is_ProgramStep_ProgramStep[iff]: is_ProgramStep (ProgramStep U)

lemma is_QueryStep_ProgramStep[iff]: ¬ is_QueryStep (ProgramStep U)
  using is_QueryStep.cases by blast

lemma is_ProgramStep_QueryStep[iff]: ¬ is_ProgramStep (QueryStep X Y)
  by (simp add: is_ProgramStep.simps)

fun valid_program_step where valid_program_step (QueryStep X Y) = compatible X Y | valid_program_step (ProgramStep U) = isometry U
definition valid_program where valid_program prog = list_all valid_program_step prog

lemma valid_program_cons[simp]: valid_program (p # ps)  valid_program_step p  valid_program ps
  by (simp add: valid_program_def)

lemma valid_program_append: valid_program (p @ q)  valid_program p  valid_program q
  by (simp add: valid_program_def)

lemma valid_program_empty[iff]: valid_program []
  by (simp add: valid_program_def)

fun exec_program_step :: ('x  'y)  ('mem,'x,'y::ab_group_add) program_step  'mem ell2  'mem ell2 where
  exec_program_step h (ProgramStep U) ψ = U *V ψ
| exec_program_step h (QueryStep X Y) ψ = (X;Y) (function_oracle h) *V ψ

fun exec_program_step_with :: ('x × 'y × 'o) update  ('mem,'x,'y) program_step  ('mem × 'o) ell2  ('mem × 'o) ell2 where
  exec_program_step_with Q (ProgramStep U) ψ = Fst U *V ψ
| exec_program_step_with Q (QueryStep X Y) ψ = (Fst o X; (Fst o Y; Snd)) Q ψ

definition exec_program :: ('x  'y::ab_group_add)  ('mem,'x,'y) program  'mem ell2  'mem ell2 where
  exec_program h program ψ = fold (exec_program_step h) program ψ
definition exec_program_with :: ('x × 'y × 'o) update  ('mem,'x,'y) program  ('mem×'o) ell2  ('mem×'o) ell2 where
  exec_program_with Q program ψ = fold (exec_program_step_with Q) program ψ

lemma bounded_clinear_exec_program_step_with[bounded_clinear]: bounded_clinear (exec_program_step_with Q step)
  apply (cases step)
  by (auto intro!: cblinfun.bounded_clinear_right simp add: exec_program_step_with.simps[abs_def])

lemma exec_program_empty[simp]: exec_program h [] ψ = ψ
  by (simp add: exec_program_def)
lemma exec_program_with_empty[simp]: exec_program_with Q [] ψ = ψ
  by (simp add: exec_program_with_def)
lemma exec_program_append: exec_program h (p @ q) ψ = exec_program h q (exec_program h p ψ)
  by (simp add: exec_program_def)
lemma exec_program_with_append: exec_program_with Q (p @ q) ψ = exec_program_with Q q (exec_program_with Q p ψ)
  by (simp add: exec_program_with_def)
lemma exec_program_cons[simp]: exec_program h (step#prog) ψ = exec_program h prog (exec_program_step h step ψ)
  by (simp add: exec_program_def)
lemma exec_program_with_cons[simp]: exec_program_with Q (step#prog) ψ = exec_program_with Q prog (exec_program_step_with Q step ψ)
  by (simp add: exec_program_with_def)


lemma norm_exec_program_step_with: norm (exec_program_step_with oracle program_step ψ)  norm ψ if valid_program_step program_step and norm oracle  1
proof (cases program_step)
  case (ProgramStep U)
  with that have isometry U
    by simp
  then have norm (Fst U) = 1
    by (simp add: register_norm norm_isometry)
  then show ?thesis
    apply (simp add: ProgramStep)
    by (smt (verit, del_insts) norm (Fst U) = 1 mult_cancel_right1 norm_cblinfun)
next
  case (QueryStep X Y)
  with that
  have [register]: compatible X Y
    using valid_program_step.simps by blast
  have [register]: register (Fst  X;(Fst  Y;Snd))
    by simp
  have norm ((Fst  X;(Fst  Y;Snd)) oracle *V ψ)  norm ((Fst  X;(Fst  Y;Snd)) oracle) * norm ψ
    using norm_cblinfun by blast
  also have  = norm oracle * norm ψ
    by (simp add: register_norm)
  also have   norm ψ
  by (simp add: mult_left_le_one_le that(2))
  finally show ?thesis
    by (simp add: QueryStep)
qed

lemma norm_exec_program_with: 
  norm (exec_program_with oracle program ψ)  norm ψ if norm oracle  1 and valid_program program for program
proof (insert that(2), induction program rule: rev_induct)
  case Nil
  then show ?case
    by simp
next
  case (snoc program_step program)
  then have valid_program_step program_step and valid_program program
    by (auto simp: valid_program_append)
  have norm (exec_program_step_with oracle program_step (exec_program_with oracle program ψ))  norm (exec_program_with oracle program ψ)
    by (smt (verit, del_insts) valid_program_step program_step mult_left_le_one_le norm_exec_program_step_with norm_ge_zero that(1))
  also have   norm ψ
    using valid_program program snoc.IH by force
  finally show ?case
    by (simp add: exec_program_with_append)
qed

lemma norm_exec_program_step_with_isometry:
  assumes valid_program_step program_step
  assumes isometry query
  shows norm (exec_program_step_with query program_step ψ) = norm ψ
proof (cases program_step)
  case (ProgramStep U)
  with assms have isometry U
    by simp
  with ProgramStep show ?thesis
    by (simp add: isometry_preserves_norm register_isometry)
next
  case (QueryStep X Y)
  with assms have [register]: compatible X Y
    by simp
  have register (Fst  X;(Fst  Y;Snd))
    by simp
  with assms have isometry ((Fst  X;(Fst  Y;Snd)) query)
    using register_isometry by blast
  then show ?thesis
    by (simp add: QueryStep isometry_preserves_norm)
qed

(* (* TODO move *)
lemma norm_exec_program_step_with:
  assumes ‹norm Q ≤ 1›
  assumes ‹valid_program_step step›
  assumes ‹norm ψ ≤ 1›
  shows ‹norm (exec_program_step_with Q step ψ) ≤ 1›
proof -
  have aux: ‹norm (X *V ψ) ≤ 1› if ‹norm X ≤ 1› and ‹norm ψ ≤ 1› for X ψ
    by (smt (verit) mult_le_one norm_cblinfun norm_ge_zero that(1,2))
  show ?thesis
  proof (cases step)
    case (ProgramStep U)
    then show ?thesis
    using assms
    by (auto intro!: aux simp: register_norm norm_isometry)
  next
    case (QueryStep X Y)
    with assms have [register]: ‹compatible X Y›
      by simp
    then have reg: ‹register (Fst ∘ X;(Fst ∘ Y;Snd))›
      by simp
    then show ?thesis
      by (auto intro!: aux assms simp: register_norm[OF ] norm_isometry QueryStep)
  qed
qed

(* TODO move *)
lemma norm_exec_program_with:
  assumes ‹norm U ≤ 1›
  assumes ‹valid_program program›
  assumes ‹norm ψ ≤ 1›
  shows ‹norm (exec_program_with U program ψ) ≤ 1›
  using ‹valid_program program›
  apply (induction program rule:rev_induct)
   apply (simp add: assms)
  by (simp add: assms exec_program_with_append valid_program_append norm_exec_program_step_with) *)


subsection ‹Lifting›

fun lift_program_step :: ('a update  'mem update)  ('a,'x,'y::ab_group_add) program_step  ('mem,'x,'y) program_step where
  lift_program_step Q (ProgramStep U) = ProgramStep (Q U)
| lift_program_step Q (QueryStep X Y) = QueryStep (Q o X) (Q o Y)

definition lift_program :: ('a update  'mem update)  ('a,'x,'y::ab_group_add) program_step list  ('mem,'x,'y) program where
  lift_program Q p = map (lift_program_step Q) p

lemma valid_program_step_lift:
  assumes register Q and valid_program_step p
  shows valid_program_step (lift_program_step Q p)
proof (cases p)
  case (ProgramStep U)
  then have isometry (Q U)
    using assms register_isometry valid_program_step.simps(2) by blast
  then show ?thesis
    using ProgramStep by auto
next
  case (QueryStep X Y)
  with assms show ?thesis
    by simp
qed

lemma valid_program_lift:
  assumes register Q and valid_program p
  shows valid_program (lift_program Q p)
  using assms(2)
  by (auto simp add: valid_program_def lift_program_def list.pred_map list_all_length valid_program_step_lift assms(1))

lemma lift_program_empty[simp]: lift_program Q [] = []
  by (simp add: lift_program_def)

lemma lift_program_cons: lift_program Q (program_step # program) = lift_program_step Q program_step # lift_program Q program
  by (simp add: lift_program_def)

lemma lift_program_append: lift_program Q (program1 @ program2) = lift_program Q program1 @ lift_program Q program2
  by (simp add: lift_program_def)

lemma is_QueryStep_lift_program_step[simp]: is_QueryStep (lift_program_step Q program_step)  is_QueryStep program_step
  apply (cases program_step)
  by simp_all

lemma filter_is_QueryStep_lift_program: filter is_QueryStep (lift_program Q program) = lift_program Q (filter is_QueryStep program)
  apply (induction program)
  by (auto simp: lift_program_def)

lemma length_lift_program[simp]: length (lift_program Q program) = length program
  apply (induction program)
  by (auto simp: lift_program_def)

definition query_count program = length (filter is_QueryStep program)

lemma query_count_append[simp]: query_count (p @ q) = query_count p + query_count q
  by (simp add: query_count_def)
lemma query_count_nil[simp]: query_count [] = 0
  by (simp add: query_count_def)
lemma query_count_cons_QueryStep[simp]: query_count (QueryStep X Y # p) = query_count p + 1
  by (simp add: query_count_def)
lemma query_count_cons_ProgramStep[simp]: query_count (ProgramStep U # p) = query_count p
  by (simp add: query_count_def)
lemma query_count_lift_program[simp]: query_count (lift_program Q p) = query_count p
  by (simp add: query_count_def filter_is_QueryStep_lift_program)

lemma exec_lift_program_step_Fst:
  assumes valid_program_step program_step
  shows exec_program_step h (lift_program_step Fst program_step) (ψ s φ) = exec_program_step h program_step ψ s φ
proof (cases program_step)
  case (ProgramStep U)
  then show ?thesis
    by (simp add: Fst_def tensor_op_ell2)
next
  case (QueryStep X Y)
  with assms have [register]: compatible X Y
    using valid_program_step.simps(1) by blast
  have (Fst  (X;Y)) (function_oracle h) *V ψ s φ = ((X;Y) (function_oracle h) *V ψ) s φ
    by (simp add: Fst_def tensor_op_ell2)
  then show ?thesis
    by (simp add: QueryStep register_comp_pair)
qed

lemma exec_lift_program_Fst:
  assumes valid_program program
  shows exec_program h (lift_program Fst program) (ψ s φ) = exec_program h program ψ s φ
  apply (insert assms, induction program rule:rev_induct)
  by (simp_all add: lift_program_append exec_program_append lift_program_cons valid_program_append exec_lift_program_step_Fst)

subsection ‹Final measurement›

definition measurement_probability :: ('a update  'mem update)  'mem ell2  'a  real where 
  measurement_probability Q ψ x = (norm (Q (proj (ket x)) ψ))2

lemma measurement_probability_nonneg: measurement_probability Q ψ x  0
  by (simp add: measurement_probability_def)

lemma norm_register_Proj_ket_invariant_union:
  ― ‹Helper lemma›
  assumes register Q and A  B = {}
  shows (norm (Q (Proj (ket_invariant (A  B))) ψ))2 = (norm (Q (Proj (ket_invariant A)) ψ))2 + (norm (Q (Proj (ket_invariant B)) ψ))2
proof -
  have ortho1: orthogonal_spaces (ket_invariant A) (ket_invariant B)
    using assms(2) by force
  have ortho2: is_orthogonal (Q (Proj (ket_invariant A)) *V ψ) (Q (Proj (ket_invariant B)) *V ψ)
  proof -
    from ortho1 have orthogonal_spaces (lift_invariant Q (ket_invariant A)) (lift_invariant Q (ket_invariant B))
      by (simp add: orthogonal_spaces_lift_invariant assms)
    then have is_orthogonal (Proj (lift_invariant Q (ket_invariant A)) ψ) (Proj (lift_invariant Q (ket_invariant B)) ψ)
      by (metis Proj_lift_invariant assms(1) cblinfun_apply_in_image lift_invariant_def orthogonal_spaces_def)
    moreover have Proj (lift_invariant Q (ket_invariant A)) = Q (Proj (ket_invariant A))
      by (simp add: Proj_ket_invariant_butterfly Proj_lift_invariant assms(1) butterfly_eq_proj)
    moreover have Proj (lift_invariant Q (ket_invariant B)) = Q (Proj (ket_invariant B))
      by (simp add: Proj_lift_invariant assms(1) ket_invariant_def)
    ultimately show ?thesis
      by simp
  qed    
  have (norm (Q (Proj (ket_invariant (A  B))) ψ))2 = (norm (Q (Proj (ket_invariant A  ket_invariant B)) ψ))2
    by (simp add: ket_invariant_union)
  also have  = (norm (Q (Proj (ket_invariant A) + (Proj (ket_invariant B))) ψ))2
    by (metis Proj_sup ortho1) 
  also have  = (norm (Q (Proj (ket_invariant A)) ψ + Q (Proj (ket_invariant B)) ψ))2
    by (simp add: complex_vector.linear_add clinear_register register Q cblinfun.add_left)
  also have  = (norm (Q (Proj (ket_invariant A)) ψ))2 + (norm (Q (Proj (ket_invariant B)) ψ))2
    by (simp add: pythagorean_theorem ortho2)
  finally show ?thesis
    by -
qed



lemma measurement_probability_sum:
  assumes register Q and finite F
  shows (xF. measurement_probability Q ψ x) = (norm (Q (Proj (ket_invariant F)) ψ))2
proof (use finite F in induction)
  case empty
  show ?case
    apply simp
    by (metis (no_types, lifting) assms(1) cancel_comm_monoid_add_class.diff_cancel cblinfun.zero_left register_minus)
next
  case (insert x F)
  have (norm (Q (Proj (ket_invariant (insert x F))) *V ψ))2 = (norm (Q (Proj (ket_invariant F)) *V ψ))2 + (norm (Q (Proj (ket_invariant {x})) *V ψ))2
    apply (rewrite at insert x F to F  {x} DEADID.rel_mono_strong)
     apply simp
    apply (subst norm_register_Proj_ket_invariant_union)
    by (simp_all add: assms insert.hyps)
  also have  = (norm (Q (proj (ket x)) ψ))2 + (norm (Q (Proj (ccspan (ket ` F))) ψ))2
    by (simp add: ket_invariant_def)
  also have  = (norm (Q (proj (ket x)) ψ))2 + sum (measurement_probability Q ψ) F
    by (simp add: insert.IH ket_invariant_def)
  also have  = sum (measurement_probability Q ψ) (insert x F)
    by (simp add: insert.hyps measurement_probability_def)
  finally show ?case
    by simp
qed

lemma
  assumes register Q
  shows measurement_probability_summable: measurement_probability Q ψ summable_on A
    and measurement_probability_infsum_leq: (xA. measurement_probability Q ψ x)  (norm (Q (Proj (ket_invariant A)) ψ))2
proof -
  define m s where m x = measurement_probability Q ψ x and s A = (norm (Q (Proj (ket_invariant A)) ψ))2 for A x
  have sum_m_fin: sum m F = s F if finite F for F
    by (simp add: measurement_probability_sum m_def s_def that assms)
  have s_mono: s A  s B if A  B for A B
  proof -
    have [simp]: A  B = B
      using that by blast
    have s A  s A + s (B-A)
      by (simp add: s_def)
    also have  = s B
      apply (simp add: s_def)
      apply (subst norm_register_Proj_ket_invariant_union[symmetric])
      using that
      by (auto simp: assms)
    finally show ?thesis
      by -
  qed
  have m_pos: m x  0 for x
    by (simp add: m_def measurement_probability_nonneg)
  show summable: m summable_on A for A
    apply (rule nonneg_bounded_partial_sums_imp_summable_on[where C=s UNIV])
    using s_mono[of _ UNIV] sum_m_fin
    by (auto intro!: eventually_finite_subsets_at_top_weakI simp: m_pos)
  then show infsum m A  s A
    apply (rule infsum_le_finite_sums)
    using s_mono[of _ A] sum_m_fin
    by auto
qed

lemma dist_inv_measurement_probability:
  fixes I :: 'i::finite set
  assumes [register]: register Q
  shows (xI. measurement_probability Q ψ x) = (dist_inv Q (ket_invariant (-I)) ψ)2
proof -
  have (xI. measurement_probability Q ψ x) = (norm (Q (Proj (ket_invariant I)) *V ψ))2
    by (simp add: measurement_probability_sum)
  then show ?thesis
    by (simp add: dist_inv_def ket_invariant_compl)
qed

lemma dist_inv_avg_measurement_probability:
  fixes I :: 'h::finite  'i::finite set
  assumes [register]: register Q
  shows (hUNIV. xI h. measurement_probability Q (ψ h) x) / CARD('h)
             = (dist_inv_avg Q (λh. ket_invariant (- I h)) ψ)2
  by (simp add: dist_inv_avg_def real_sqrt_pow2 divide_nonneg_pos
      sum_nonneg dist_inv_measurement_probability)



subsection ‹Preservation›



lemma dist_inv_avg_exec_compatible:
  fixes prog
  assumes valid_program prog
  assumes [register]: compatible Q R
  shows dist_inv_avg Q I (λh::'x::finite'y::{finite,ab_group_add}. exec_program h (lift_program R prog) (ψ h))
        dist_inv_avg Q I ψ
proof (insert valid_program prog, induction prog rule:rev_induct)
  case Nil
  with assms show ?case
    by simp
next
  case (snoc program_step prog)
  show ?case
  proof (cases program_step)
    case (ProgramStep U)
    have dist_inv_avg Q I (λh. exec_program h (lift_program R (prog @ [program_step])) (ψ h))
          (MAX h::'x'y. norm U) * dist_inv_avg Q I (λh. exec_program h (lift_program R prog) (ψ h))
      apply (simp add: lift_program_append lift_program_cons exec_program_append ProgramStep del: range_constant Max_const)
      apply (rule dist_inv_avg_apply_compatible[where R=λ_. R])
      by simp
    also have   dist_inv_avg Q I ψ
      using snoc
      by (simp_all add: ProgramStep norm_isometry valid_program_append)
    finally show ?thesis
      by -
  next
    case (QueryStep X Y)
    with snoc have [register]: compatible X Y by (simp add: valid_program_append)
    have dist_inv_avg Q I (λh. exec_program h (lift_program R (prog @ [program_step])) (ψ h)) 
       (MAX h::'x'y. norm (function_oracle h)) * dist_inv_avg Q I (λh. exec_program h (lift_program R prog) (ψ h))
      apply (simp add: lift_program_append lift_program_cons exec_program_append QueryStep 
          del: range_constant Max_const norm_function_oracle)
       apply (rule dist_inv_avg_apply_compatible[where R=λ_. (R  X;R  Y)])
      by simp
    also have   dist_inv_avg Q I ψ
      using snoc
      by (simp_all add: QueryStep valid_program_append)
    finally show ?thesis
      by -
  qed
qed

lemma dist_inv_exec'_compatible:
  fixes prog
  assumes valid_program prog
  assumes normU: norm U  1
  assumes [register]: register R
  assumes compatQ1[register]: compatible Q (Fst o R)
  assumes compatQ2[register]: compatible Q Snd
  shows dist_inv Q I (exec_program_with U (lift_program R prog) ψ)  dist_inv Q I ψ
proof (insert valid_program prog, induction prog rule:rev_induct)
  case Nil
  with assms show ?case
    by simp
next
  case (snoc program_step prog)
  show ?case
  proof (cases program_step)
    case (ProgramStep V)
    have dist_inv Q I (exec_program_with U (lift_program R (prog @ [program_step])) ψ)
       norm V * dist_inv Q I (exec_program_with U (lift_program R prog) ψ)
      apply (simp add: lift_program_append exec_program_with_append lift_program_cons ProgramStep)
      using dist_inv_apply_compatible[OF compatQ1]
      by simp
    also have   dist_inv Q I ψ
      using ProgramStep norm_isometry snoc(1) snoc.prems valid_program_append exec_program_with_append by fastforce
    finally show ?thesis
      by -
  next
    case (QueryStep X Y)
    with snoc have [register]: compatible X Y by (simp add: valid_program_append)
    then have compat[register]: compatible Q (Fst  (R  X);(Fst  (R  Y);Snd))
      by (auto intro!: compatible3' compatible_comp_inner simp flip: comp_assoc)
    have dist_inv Q I (exec_program_with U (lift_program R (prog @ [program_step])) ψ)
       norm U * dist_inv Q I (exec_program_with U (lift_program R prog) ψ)
      apply (simp add: lift_program_append lift_program_cons QueryStep exec_program_with_append)
      by (rule dist_inv_apply_compatible[OF compat])
    also have   dist_inv Q I ψ
      by (smt (verit, ccfv_SIG) assms(2) dist_inv_pos mult_cancel_right1
          mult_left_le_one_le snoc(1) snoc.prems valid_program_append zero_le_mult_iff)
    finally show ?thesis
      by -
  qed
qed




subsection ‹Misc›

(* TODO sort me *)

lemma dist_inv_induct:
  fixes "oracle" :: ('x × 'y::ab_group_add × ('x  'y option)) update
  assumes compatible R Fst
  assumes (i<query_count program. g i)  ε
  assumes init: ψ0  space_as_set (lift_invariant R (J 0))
  assumes J (query_count program)  I
  assumes valid_program program
  assumes X Y i. compatible X Y  preserves ((Fst  X;(Fst  Y;Snd)) oracle :: ('m × _) update) (lift_invariant R (J i))
           (lift_invariant R (J (Suc i))) (g i)
  assumes norm oracle  1
  assumes norm ψ0  1
  shows dist_inv R I (exec_program_with oracle program ψ0)  ε
proof -
  note [[simproc del: Laws_Quantum.compatibility_warn]]
  define f where f n = (i<n. g i) for n
  from compatible R Fst have [register]: register R
    using compatible_register1 by blast
  have dist_inv R (J (query_count program)) (exec_program_with oracle program ψ0)  f (query_count program)
  proof (insert valid_program program, induction program rule:rev_induct)
    case Nil
    from init
    have dist_inv R (J 0) ψ0 = 0
      by (simp add: dist_inv_0_iff)
    then show ?case
      by (simp add: assms(2) query_count_def f_def)
  next
    case (snoc program_step program)
    from snoc.prems have valid_program program
      using valid_program_append by blast
    from snoc.prems have valid_program_step program_step
      by (simp add: valid_program_append)
    define i where i = query_count program
    show ?case
    proof (cases program_step)
      case (ProgramStep U)
      with valid_program_step program_step
      have [iff]: isometry U
        by simp
      have preserves (Fst U) (lift_invariant R (J i)) (lift_invariant R (J i)) 0
        apply (rule_tac preserves_compatible[where F=Fst])
        using compatible R Fst compatible_register_invariant_compatible_register compatible_sym apply blast
        by simp
      then have dist_inv R (J i) (Fst U *V exec_program_with oracle program ψ0)  f i
        apply (rule dist_inv_leq_if_preserves[THEN order_trans])
        using snoc.IH[OF valid_program program]
        by (simp_all add: norm_isometry register_isometry query_count_def i_def)
      then show ?thesis
        by (simp add: ProgramStep exec_program_with_append i_def query_count_def)
    next
      case (QueryStep X Y)
      with valid_program_step program_step
      have [register]: compatible X Y
        by simp
      with assms have pres: preserves ((Fst  X;(Fst  Y;Snd)) oracle) (lift_invariant R (J i))
           (lift_invariant R (J (Suc i))) (g i)
        by fast
      have fg': norm ((Fst  X;(Fst  Y;Snd)) oracle) * f i + g i * norm (exec_program_with oracle program ψ0)  f (Suc i)
      proof -
        have norm ((Fst  X;(Fst  Y;Snd)) oracle)  1
          apply (subst register_norm[where a="oracle"])
          by (simp_all add: assms)
        moreover have norm (exec_program_with oracle program ψ0)  1
          apply (rule norm_exec_program_with[THEN order_trans])
          using valid_program program assms by simp_all
        moreover have f i + g i  f (Suc i)
          by (simp add: f_def)
        moreover have gpos: g i  0 for i
          using compatible X Y assms(6) preserves_def by blast
        moreover have f i  0
          by (auto intro!: sum_nonneg gpos simp: f_def)
        ultimately  
        show ?thesis
          by (smt (verit) mult_left_le mult_left_le_one_le norm_ge_zero)
      qed
      show ?thesis
        apply (simp add: QueryStep exec_program_with_append flip: i_def)
        using pres apply (rule dist_inv_leq_if_preserves[THEN order_trans])
          apply (simp, simp)
        using snoc.IH[OF valid_program program, folded i_def]
        by (smt (verit, ccfv_SIG) fg' mult_left_mono norm_ge_zero)
    qed
  qed
  with assms show ?thesis
    using register R
    by (smt (verit, best) dist_inv_mono f_def)
qed

subsection ‹Random Oracles›

lemma standard_query_for_fixed_function_generic:
  fixes standard_query
  assumes H x y z. H x = Some z  standard_query *V (ket (x,y,H)) = ket (x, y + z, H) (* e.g. standard_query_ket_full_Some *)
  assumes valid_program program
  shows exec_program h program initial_state s ket (Some o h) 
       = exec_program_with standard_query program (initial_state s ket (Some  h))
proof (insert valid_program program, induction program rule: rev_induct)
  case Nil
  then show ?case
    by simp
next
  case (snoc program_step prog)
  then have [simp]: valid_program prog
    using list_all_append valid_program_def by blast
  show ?case
  proof (cases program_step)
    case (ProgramStep U)
    have exec_program h (prog @ [program_step]) initial_state s ket (Some  h) = (U *V exec_program h prog initial_state) s ket (Some  h)
      by (simp add: ProgramStep exec_program_append)
    also have  = Fst U *V exec_program h prog initial_state s ket (Some  h)
      by (simp add: Fst_def tensor_op_ell2)
    also have  = Fst U *V exec_program_with standard_query prog (initial_state s ket (Some  h))
      by (subst snoc.IH, simp_all)
    also have  = exec_program_with standard_query (prog @ [program_step]) (initial_state s ket (Some  h))
      by (simp add: ProgramStep exec_program_with_append)
    finally show ?thesis
      by -
  next
    case (QueryStep X Y)
    then have [register]: compatible X Y
      using snoc.prems valid_program_def by force
    have exec_program h (prog @ [program_step]) initial_state s ket (Some  h) = ((X;Y) (function_oracle h) *V exec_program h prog initial_state) s ket (Some  h)
      by (simp add: QueryStep exec_program_append)
    also have  = Fst ((X;Y) (function_oracle h)) *V (exec_program h prog initial_state s ket (Some  h))
      by (simp add: Fst_def tensor_op_ell2)
    also have  = (Fst o X; (Fst o Y; Snd)) standard_query *V (exec_program h prog initial_state s ket (Some  h))
      by (simp add: standard_query_for_fixed_func_generic assms)
    also have  = (Fst o X; (Fst o Y; Snd)) standard_query *V exec_program_with standard_query prog (initial_state s ket (Some  h))
      by (subst snoc.IH, simp_all)
    also have  = exec_program_with standard_query (prog @ [program_step]) (initial_state s ket (Some  h))
      by (simp add: QueryStep exec_program_with_append)
    finally show ?thesis
      by -
  qed
qed

lemma standard_query_for_fixed_function_dist_inv_generic:
  assumes H x y z. H x = Some z  standard_query *V (ket (x,y,H)) = ket (x, y + z, H) (* e.g. standard_query_ket_full_Some *)
  assumes valid_program program
  assumes compat: compatible_invariants ( S ccspan {ket (Some  h)}) J
  assumes IJ: J  ( S ccspan{ket (Some o h)}) = I S ccspan{ket (Some o h)}
  assumes [register]: register Q
  shows dist_inv Q I (exec_program h program initial_state) =
    dist_inv (Fst o Q; Snd) J (exec_program_with standard_query program (initial_state s ket (Some  h)))
proof -
  define e1 e2 where e1 = exec_program h program initial_state and e2 = exec_program_with standard_query program (initial_state s ket (Some  h))
  define keth where keth = ket (Some o h)
  have e2e1: e2 = e1 s keth
    unfolding e1_def e2_def keth_def
    using standard_query_for_fixed_function_generic assms
    by fastforce
  have dist_inv Q I e1 = norm ((id_cblinfun - Q (Proj I)) *V e1)
    by (simp add: dist_inv_def Proj_ortho_compl register_minus)
  also have  = norm (e1 s keth - (Q (Proj I) *V e1) s keth)
    by (simp add: norm_tensor_ell2 keth_def cblinfun.diff_left flip: tensor_ell2_diff1)
  also have  = norm ((id_cblinfun - Fst (Q (Proj I)) oCL Snd (proj keth)) *V e1 s keth)
    by (simp add: Fst_def Snd_def comp_tensor_op tensor_op_ell2 cblinfun.diff_left)
  also have  = dist_inv (Fst o Q; Snd) (I S ccspan{keth}) (e1 s keth)
    by (simp add: dist_inv_def Proj_ortho_compl register_minus tensor_ccsubspace_via_Proj
        Proj_on_own_range is_Proj_tensor_op register_pair_apply)
  also have  = dist_inv (Fst o Q; Snd) (J  ( S ccspan{ket (Some o h)})) (e1 s keth)
    by (simp add: IJ keth_def)
  also have  = dist_inv (Fst o Q; Snd) J (e1 s keth)
    using compat apply (rule dist_inv_intersect_onesided)
     apply simp
    by (simp add: dist_inv_def Proj_ortho_compl register_minus tensor_ccsubspace_via_Proj
        Proj_on_own_range is_Proj_tensor_op register_pair_apply cblinfun.diff_left keth_def)
  also have  = dist_inv (Fst o Q; Snd) J e2
    by (simp add: e2e1)
  finally show dist_inv Q I e1 = 
    by -
qed



lemma standard_query_is_ro_generic:
  fixes standard_query
  assumes H x y z. H x = Some z  standard_query *V (ket (x,y,H)) = ket (x, y + z, H) (* e.g. standard_query_ket_full_Some *)
  assumes valid_program program
  shows exec_program_with standard_query program (initial_state s (superpos_total :: ('x::finite  'y::{finite,ab_group_add} option) ell2))
      = (hUNIV. (exec_program h program initial_state s ket (Some o h)) /R sqrt CARD('x  'y))
proof (insert assms(2), induction program rule: rev_induct)
  case Nil
  have sum ket (total_functions :: ('x  'y option) set) = (hUNIV. ket (λa. Some (h a)))
    apply (simp add: total_functions_def2 sum.reindex fun.inj_map)
    by (simp add: o_def)
  then show ?case
    by (simp add: uniform_superpos_def2 scaleR_scaleC card_fun card_total_functions tensor_ell2_scaleC2
        flip: scaleC_sum_right tensor_ell2_sum_right)
next
  case (snoc step prog)
  then have valid_program_step step and [iff]: valid_program prog
    by (simp_all add: valid_program_append)
  have exec_program_step_with standard_query step (ψ s ket (Some o h)) =
        exec_program_step h step ψ s ket (Some o h) for h ψ
  proof (cases step)
    case (ProgramStep U)
    then show ?thesis
      by (simp add: Fst_def tensor_op_ell2)
  next
    case (QueryStep X Y)
    with valid_program_step step have [register]: compatible X Y
      by simp
    have exec_program_step_with standard_query step (ψ s ket (Some  h))
        = (Fst  X;(Fst  Y;Snd)) standard_query *V (ψ s ket (Some  h))
      by (simp add: QueryStep)
    also have  = ((Fst  X;(Fst  Y;Snd)) standard_query oCL Snd (selfbutter (ket (Some o h)))) *V (ψ s ket (Some  h))
      by simp
    also have  = (Fst  X;(Fst  Y;Snd)) (standard_query oCL (Snd o Snd) (selfbutter (ket (Some o h)))) *V (ψ s ket (Some  h))
      by (simp add: register_mult[symmetric, where F=(_;_)] register_pair_Snd[unfolded o_def, THEN fun_cong])
    also have  = (Fst  X;(Fst  Y;Snd)) ((Fst; Snd o Fst) (function_oracle h) oCL (Snd o Snd) (selfbutter (ket (Some o h)))) *V (ψ s ket (Some  h))
      apply (rewrite at (Fst;Snd  Fst) (function_oracle h) to assoc (function_oracle h o id_cblinfun) DEADID.rel_mono_strong)
       apply (simp add: assoc_def register_pair_Fst[unfolded o_def, THEN fun_cong] flip: Fst_def)
      apply (rule arg_cong[where f=λx. (_;_) x *V _])
      by (auto intro!: equal_ket simp: Snd_def tensor_op_ket cinner_ket tensor_ell2_ket assms
          assoc_ell2_sandwich sandwich_apply function_oracle_apply)
    also have  = ((Fst  X;(Fst  Y;Snd)) ((Fst; Snd o Fst) (function_oracle h)) oCL Snd (selfbutter (ket (Some o h)))) *V (ψ s ket (Some  h))
      by (simp add: register_mult[symmetric, where F=(_;_)] register_pair_Snd[unfolded o_def, THEN fun_cong])
    also have  = (Fst  X;(Fst  Y;Snd)) ((Fst; Snd o Fst) (function_oracle h)) *V (ψ s ket (Some  h))
      by simp
    also have  = Fst ((X;Y) (function_oracle h)) *V (ψ s ket (Some  h))
      apply (rewrite at (Fst  X;(Fst  Y;Snd)) ((Fst;Snd  Fst) _) to ((Fst  X;(Fst  Y;Snd)) o (Fst;Snd  Fst)) _ DEADID.rel_mono_strong)
       apply simp
      apply (subst register_comp_pair[symmetric])
        apply (simp, simp)
      by (simp add: register_pair_Snd register_pair_Fst register_comp_pair flip: comp_assoc)
    also have  = ((X;Y) (function_oracle h) *V ψ) s ket (Some  h)
      by (simp add: Fst_def tensor_op_ell2)
    also have  = exec_program_step h step ψ s ket (Some  h)
      by (simp add: QueryStep)
    finally show ?thesis
      by -
  qed
  then show ?case
    by (simp add: exec_program_with_append exec_program_append snoc.IH o_def
        complex_vector.linear_sum[where f=exec_program_step_with standard_query step]
        bounded_clinear.clinear bounded_clinear_exec_program_step_with scaleR_scaleC
        clinear.scaleC)
qed



lemma standard_query_is_ro_dist_inv_generic:
  fixes standard_query :: ('x::finite × 'y::{finite,ab_group_add} × ('x  'y)) ell2 CL _
  assumes H x y z. H x = Some z  standard_query *V (ket (x,y,H)) = ket (x, y + z, H) (* e.g. standard_query_ket_full_Some *)
  assumes valid_program program
  assumes [register]: register Q
  shows dist_inv_avg Q (λ_. I) (λh. exec_program h program initial_state) =
    dist_inv (Fst o Q) I (exec_program_with standard_query program (initial_state s superpos_total)) (is ?lhs = ?rhs)
proof -
  have ?rhs2 = (dist_inv (Fst  Q) I (hUNIV. (exec_program h program initial_state s ket (Some  h)) /R sqrt (real CARD('x  'y))))2
    apply (subst standard_query_is_ro_generic)
    using assms by simp_all
  also have  = (norm (iUNIV. ((Q (Proj (- I)) *V exec_program i program initial_state) s ket (Some  i)) /R sqrt (real CARD('x  'y))))2
    by (simp add: dist_inv_def cblinfun.sum_right Fst_def tensor_op_ell2 cblinfun.scaleR_right)
  also have  = (aUNIV. (norm (((Q (Proj (- I)) *V exec_program a program initial_state) s ket (Some  a)) /R sqrt (real CARD('x  'y))))2)
    apply (subst pythagorean_theorem_sum)
      apply (simp, metis fun.inj_map_strong option.inject)
     apply simp
    by simp
  also have  = (aUNIV. (dist_inv (Fst o Q) I (exec_program a program initial_state s ket (Some  a)))2 /R real CARD('x  'y))
    by (auto intro!: sum.cong simp: dist_inv_def Fst_def tensor_op_ell2 power_mult_distrib real_inv_sqrt_pow2)
  also have  = (xUNIV. (dist_inv Q I (exec_program x program initial_state))2) /R real CARD('x  'y)
    by (metis (no_types, lifting) dist_inv_Fst_tensor norm_ket scaleR_right.sum sum.cong)
  also have  = ?lhs2
    by (simp add: dist_inv_avg_def real_sqrt_pow2 sum_nonneg divide_inverse flip: sum_distrib_left)
  finally show ?thesis
    by simp
qed

lemma (in compressed_oracle) standard_query_is_ro_dist_inv:
  assumes valid_program program
  assumes [register]: register Q
  shows dist_inv_avg Q (λ_. I) (λh. exec_program h program initial_state) =
    dist_inv (Fst o Q) I (exec_program_with standard_query program (initial_state s superpos_total)) (is ?lhs = ?rhs)
  using standard_query_ket_full_Some assms by (rule standard_query_is_ro_dist_inv_generic)

lemma (in compressed_oracle) standard_query'_is_ro_dist_inv:
  assumes valid_program program
  assumes [register]: register Q
  shows dist_inv_avg Q (λ_. I) (λh. exec_program h program initial_state) =
    dist_inv (Fst o Q) I (exec_program_with standard_query' program (initial_state s superpos_total)) (is ?lhs = ?rhs)
  using standard_query'_ket_full_Some assms by (rule standard_query_is_ro_dist_inv_generic)



lemma (in compressed_oracle) compress_query_is_standard_query_generic:
  fixes query standard_query
  assumes valid_program program
  assumes standard_query oCL reg_3_3 compress = reg_3_3 compress oCL query
  shows exec_program_with standard_query program (initial_state s superpos_total)
      =  Snd compress *V exec_program_with query program (initial_state s ket (λx. None))
proof (insert valid_program program, induction program rule: rev_induct)
  case Nil
  then show ?case
    by (simp add: compress_empty)
next
  case (snoc program_step prog)
  then have [simp]: valid_program prog
    by (simp add: valid_program_def)
  show ?case
  proof (cases program_step)
    case (ProgramStep U)
    have exec_program_with standard_query (prog @ [program_step]) (initial_state s superpos_total)
         = Fst U *V Snd compress *V exec_program_with query prog (initial_state s ket Map.empty)
      by (simp add: ProgramStep snoc.IH exec_program_with_append)
    also have  = Snd compress *V Fst U *V exec_program_with query prog (initial_state s ket Map.empty)
      by (simp flip: cblinfun_apply_cblinfun_compose swap_registers)
    also have  = Snd compress *V exec_program_with query (prog @ [program_step]) (initial_state s ket Map.empty)
      by (simp add: ProgramStep exec_program_with_append)
    finally show ?thesis
      by -
  next
    case (QueryStep X Y)
    with snoc.prems have [register]: compatible X Y
      by (simp add: valid_program_def)
    have aux: (Fst  X;(Fst  Y;Snd)) (reg_3_3 compress) = Snd compress
      by (simp add: reg_3_3_def register_pair_Snd[unfolded o_def, THEN fun_cong])
    have exec_program_with standard_query (prog @ [program_step]) (initial_state s superpos_total)
          = (Fst  X;(Fst  Y;Snd)) standard_query *V Snd compress *V exec_program_with query prog (initial_state s ket Map.empty)
      by (simp add: QueryStep snoc.IH exec_program_with_append)
    also have  = (Fst  X;(Fst  Y;Snd)) (standard_query oCL reg_3_3 compress) *V exec_program_with query prog (initial_state s ket Map.empty)
      by (simp_all add: aux flip: register_mult)
    also have  = (Fst  X;(Fst  Y;Snd)) (reg_3_3 compress oCL query) *V exec_program_with query prog (initial_state s ket Map.empty)
      by (simp add: assms)
    also have  = Snd compress *V (Fst  X;(Fst  Y;Snd)) query *V (exec_program_with query) prog (initial_state s ket Map.empty)
      by (simp_all add: aux flip: register_mult)
    also have  = Snd compress *V (exec_program_with query) (prog @ [program_step]) (initial_state s ket (λx. None))
      by (simp add: QueryStep Cons exec_program_with_append)
    finally show ?thesis
      by -
  qed
qed



lemma (in compressed_oracle) query_is_standard_query_generic:
  fixes query standard_query
  assumes valid_program program
  assumes standard_query oCL reg_3_3 compress = reg_3_3 compress oCL query
  shows dist_inv Fst I (exec_program_with standard_query program (initial_state s superpos_total))
       = dist_inv Fst I (exec_program_with query program (initial_state s ket (λx. None)))
proof -
  have dist_inv Fst I (exec_program_with standard_query program (initial_state s superpos_total))
       = norm (Fst (Proj (- I)) *V Snd compress *V exec_program_with query program (initial_state s ket (λx. None)))
    by (simp add: compress_query_is_standard_query_generic assms dist_inv_def Proj_on_own_range register_projector)
  also have  = norm (Snd compress *V Fst (Proj (- I)) *V exec_program_with query program (initial_state s ket (λx. None)))
    by (simp flip: cblinfun_apply_cblinfun_compose swap_registers)
  also have  = norm (Fst (Proj (- I)) *V exec_program_with query program (initial_state s ket (λx. None)))
    by (simp add: isometry_preserves_norm register_isometry[where F=Snd])
  also have  = dist_inv Fst I (exec_program_with query program (initial_state s ket (λx. None)))
    by (simp add: dist_inv_def Proj_on_own_range register_projector)
  finally show ?thesis
    by -
qed



lemma (in compressed_oracle) query_is_standard_query:
  assumes valid_program program
  shows
  dist_inv Fst I (exec_program_with standard_query program (initial_state s superpos_total)) =
   dist_inv Fst I (exec_program_with query program (initial_state s ket (λx. None)))
  using query_is_standard_query_generic standard_query_compress assms by blast

lemma (in compressed_oracle) query'_is_standard_query:
  assumes valid_program program
  shows
  dist_inv Fst I (exec_program_with standard_query' program (initial_state s superpos_total)) =
   dist_inv Fst I (exec_program_with query' program (initial_state s ket (λx. None)))
  using query_is_standard_query_generic standard_query'_compress assms by blast

lemma (in compressed_oracle) query_is_random_oracle:
  assumes valid_program program
  shows dist_inv_avg id (λ_. I) (λh. exec_program h program initial_state) =
         dist_inv Fst I (exec_program_with query program (initial_state s ket (λ_. None)))
  by (simp add: standard_query_is_ro_dist_inv assms query_is_standard_query)


lemma (in compressed_oracle) query'_is_random_oracle:
  assumes valid_program program
  shows dist_inv_avg id (λ_. I) (λh. exec_program h program initial_state) =
         dist_inv Fst I (exec_program_with query' program (initial_state s ket (λ_. None)))
  by (simp add: standard_query'_is_ro_dist_inv assms query'_is_standard_query)


lemma (in compressed_oracle) dist_inv_exec_query_exec_fixed:
  fixes program :: ('mem, 'x::finite, 'y::{finite,ab_group_add}) program_step list
  fixes Q :: 'a ell2 CL 'a ell2  'mem ell2 CL 'mem ell2
  assumes valid_program program
  assumes [register]: register Q
  shows dist_inv (Fst  Q) I (exec_program_with query program (ψ s ket (λ_. None)))
       = dist_inv_avg Q (λ_. I) (λh. exec_program h program ψ)
proof -
  have dist_inv (Fst  Q) I (exec_program_with query program (ψ s ket (λ_. None)))
      = dist_inv Fst (lift_invariant Q I) (exec_program_with query program (ψ s ket (λ_. None)))
    by (metis (no_types, lifting) Proj_lift_invariant assms(2) dist_inv_def lift_invariant_compl o_apply)
  also have  = dist_inv_avg id (λh. lift_invariant Q I) (λh. exec_program h program ψ)
    by (simp add: query_is_random_oracle assms)
  also have  = dist_inv_avg Q (λ_. I) (λh. exec_program h program ψ)
    by (simp add: dist_inv_avg_register_rewrite)
  finally show ?thesis
    by -
qed

lemma (in compressed_oracle) dist_inv_exec_query'_exec_fixed:
  fixes program :: ('mem, 'x::finite, 'y::{finite,ab_group_add}) program_step list
  fixes Q :: 'a ell2 CL 'a ell2  'mem ell2 CL 'mem ell2
  assumes valid_program program
  assumes [register]: register Q
  shows dist_inv (Fst  Q) I (exec_program_with query' program (ψ s ket (λ_. None)))
       = dist_inv_avg Q (λ_. I) (λh. exec_program h program ψ)
proof -
  have dist_inv (Fst  Q) I (exec_program_with query' program (ψ s ket (λ_. None)))
      = dist_inv Fst (lift_invariant Q I) (exec_program_with query' program (ψ s ket (λ_. None)))
    by (metis (no_types, lifting) Proj_lift_invariant assms(2) dist_inv_def lift_invariant_compl o_apply)
  also have  = dist_inv_avg id (λh. lift_invariant Q I) (λh. exec_program h program ψ)
    by (simp add: query'_is_random_oracle assms)
  also have  = dist_inv_avg Q (λ_. I) (λh. exec_program h program ψ)
    by (simp add: dist_inv_avg_register_rewrite)
  finally show ?thesis
    by -
qed


end