# Theory MDP_RP_Certification

```section ‹Certification of Reachability Problems on MDPs›

theory MDP_RP_Certification
imports
"../MDP_Reachability_Problem"
"HOL-Library.IArray"
"HOL-Library.Code_Target_Numeral"
begin

context Reachability_Problem
begin

lemma p_ub':
fixes x
assumes 1: "s ∈ S" "⋀s D. s ∈ S1 ⟹ D ∈ K s ⟹ (∑t∈S. pmf D t * x t) ≤ x s"
assumes 2: "⋀s. s ∈ S1 ⟹ x s ≠ 0 ⟹ (∃t∈S2. (s, t) ∈ (SIGMA s:S1. ⋃D∈K s. set_pmf D)⇧*)"
assumes 3: "⋀s. s ∈ S - S1 - S2 ⟹ x s = 0"
assumes 4: "⋀s. s ∈ S2 ⟹ x s = 1"
shows "enn2real (p s) ≤ x s"
proof (rule p_ub[OF 1 _ 4])
fix s assume "s ∈ S" "p s = 0" with 2[of s] p_pos[of s] p_S2[of s] 3[of s] show "x s = 0"
by (cases "x s = 0") auto
qed

lemma n_lb':
fixes x
assumes "wf R"
assumes 1: "s ∈ S" "⋀s D. s ∈ S1 ⟹ D ∈ K s ⟹ x s ≤ (∑t∈S. pmf D t * x t)"
assumes 2: "⋀s D. s ∈ S1 ⟹ D ∈ K s ⟹ x s ≠ 0 ⟹ ∃t∈D. ((t, s) ∈ R ∧ t ∈ S1 ∧ x t ≠ 0) ∨ t ∈ S2"
assumes 3: "⋀s. s ∈ S - S1 - S2 ⟹ x s = 0"
assumes 4: "⋀s. s ∈ S2 ⟹ x s = 1"
shows "x s ≤ enn2real (n s)"
proof (rule n_lb[OF 1 _ 4])
fix s assume *: "s ∈ S" "n s = 0"
show "x s = 0"
proof (rule ccontr)
assume "x s ≠ 0"
with * n_S2[of s] n_nS12[of s] 3[of s] have "s ∈ S1"
by (metis DiffI zero_neq_one)
have "0 < n s"
by (intro n_pos[of "λs. x s ≠ 0", OF ‹x s ≠ 0› ‹s ∈ S1› ‹wf R›])
(metis zero_less_one n_S2 2)
with ‹n s = 0› show False by auto
qed
qed

end

no_notation Stream.snth (infixl "!!" 100) ― ‹we use @{text "!!"} for IArray›

subsection ‹Computable representation›

record mdp_reachability_problem =
state_count :: nat
distrs :: "(nat × rat) list list iarray"
states1 :: "bool iarray"
states2 :: "bool iarray"

record 'a RP_sub_cert =
solution :: "rat iarray"
witness :: "('a × nat) iarray"

record RP_cert =
pos_cert :: "(nat × nat) RP_sub_cert"
neg_cert :: "nat list RP_sub_cert"

definition "sparse_mult sx y = sum_list (map (λ(n, x). x * y !! n) sx)"

primrec lookup where
"lookup d [] x = d"
| "lookup d (y#ys) x = (if fst y = x then snd y else lookup d ys x)"

lemma lookup_eq_map_of: "lookup d xs x = (case map_of xs x of Some x ⇒ x | None ⇒ d)"
by (induct xs) simp_all

lemma lookup_in_set:
"distinct (map fst xs) ⟹ x ∈ set xs ⟹ lookup d xs (fst x) = snd x"
unfolding lookup_eq_map_of by (subst map_of_is_SomeI[where y="snd x"]) simp_all

lemma lookup_not_in_set:
"x ∉ fst ` set xs ⟹ lookup d xs x = d"
unfolding lookup_eq_map_of
by (subst map_of_eq_None_iff[of xs x, THEN iffD2]) auto

lemma lookup_nonneg:
"(⋀x v. (x, v) ∈ set xs ⟹ 0 ≤ v) ⟹ (0::'a::ordered_comm_monoid_add) ≤ lookup 0 xs x"
apply (induction xs)
apply simp
apply force
done

lemma sparse_mult_eq_sum_lookup:
fixes xs :: "(nat × 'a::comm_semiring_1) list"
assumes "list_all (λ(n, x). n < M) xs" "distinct (map fst xs)"
shows "sparse_mult xs y = (∑i<M. lookup 0 xs i * y !! i)"
proof -
from ‹distinct (map fst xs)› have "distinct xs" "inj_on fst (set xs)"
then have "sparse_mult xs y = (∑x∈set xs. snd x * y !! fst x)"
by (auto intro!: sum.cong simp add: sparse_mult_def sum_list_distinct_conv_sum_set)
also have "… = (∑x∈set xs. lookup 0 xs (fst x) * y !! fst x)"
by (intro sum.cong refl arg_cong2[where f="(*)"]) (simp add: lookup_in_set assms)
also have "… = (∑x∈fst ` set xs. lookup 0 xs x * y !! x)"
using ‹inj_on fst (set xs)› by (simp add: sum.reindex)
also have "… = (∑x<M. lookup 0 xs x * y !! x)"
using assms(1)
by (intro sum.mono_neutral_cong_left)
(auto simp: list_all_iff lookup_eq_map_of map_of_eq_None_iff[THEN iffD2])
finally show ?thesis .
qed

lemma sum_list_eq_sum_lookup:
fixes xs :: "(nat × 'a::comm_semiring_1) list"
assumes "list_all (λ(n, x). n < M) xs" "distinct (map fst xs)"
shows "sum_list (map snd xs) = (∑i<M. lookup 0 xs i)"
proof -
from ‹distinct (map fst xs)› have "distinct xs" "inj_on fst (set xs)"
then have "sum_list (map snd xs) = (∑x∈set xs. snd x)"
by (auto intro!: sum.cong simp add: sparse_mult_def sum_list_distinct_conv_sum_set)
also have "… = (∑x∈set xs. lookup 0 xs (fst x))"
by (intro sum.cong refl arg_cong2[where f="(*)"]) (simp add: lookup_in_set assms)
also have "… = (∑x∈fst ` set xs. lookup 0 xs x)"
using ‹inj_on fst (set xs)› by (simp add: sum.reindex)
also have "… = (∑x<M. lookup 0 xs x)"
using assms(1)
by (intro sum.mono_neutral_cong_left)
(auto simp: list_all_iff lookup_eq_map_of map_of_eq_None_iff[THEN iffD2])
finally show ?thesis .
qed

definition
"valid_mdp_rp mdp ⟷
0 < state_count mdp ∧
IArray.length (distrs mdp) = state_count mdp ∧
IArray.length (states1 mdp) = state_count mdp ∧
IArray.length (states2 mdp) = state_count mdp ∧
(∀i<state_count mdp. ¬ (states1 mdp !! i ∧ states2 mdp !! i) ∧
list_all (λds. distinct (map fst ds) ∧ list_all (λ(n, x). 0 ≤ x ∧ n < state_count mdp) ds ∧
sum_list (map snd ds) = 1) (distrs mdp !! i) ∧
¬ List.null (distrs mdp !! i))"

definition
"valid_sub_cert mdp c ord check ⟷
IArray.length (witness c) = state_count mdp ∧
IArray.length (solution c) = state_count mdp ∧
(∀i<state_count mdp.
if states2 mdp !! i then solution c !! i = 1
else if states1 mdp !! i then 0 ≤ solution c !! i ∧
(list_all (λds. ord (sparse_mult ds (solution c)) (solution c !! i)) (distrs mdp !! i)) ∧
(0 < solution c !! i ⟶ check (distrs mdp !! i) (witness c !! i))
else solution c !! i = 0)"

definition
"valid_pos_cert mdp c ⟷
valid_sub_cert mdp c (≤)
(λD ((j, a), n). j < state_count mdp ∧ snd (witness c !! j) < n ∧ 0 < solution c !! j ∧
a < length D ∧ lookup 0 (D ! a) j ≠ 0)"

definition
"valid_neg_cert mdp c ⟷
valid_sub_cert mdp c (≥)
(λD (J, n). list_all2 (λj d. j < state_count mdp ∧ snd (witness c !! j) < n ∧
lookup 0 d j ≠ 0 ∧ 0 < solution c !! j) J D)"

definition
"valid_cert mdp c ⟷ valid_pos_cert mdp (pos_cert c) ∧ valid_neg_cert mdp (neg_cert c)"

lemma valid_mdp_rpD_length:
assumes "valid_mdp_rp mdp"
shows "0 < state_count mdp" "IArray.length (distrs mdp) = state_count mdp"
"IArray.length (states1 mdp) = state_count mdp" "IArray.length (states2 mdp) = state_count mdp"
using assms by (auto simp: valid_mdp_rp_def)

lemma valid_mdp_rpD:
assumes "valid_mdp_rp mdp" "i < state_count mdp"
shows "¬ (states1 mdp !! i ∧ states2 mdp !! i)"
and "⋀ds n x. ds ∈ set (distrs mdp !! i) ⟹ (n, x) ∈ set ds ⟹ n < state_count mdp"
and "⋀ds n x. ds ∈ set (distrs mdp !! i) ⟹ (n, x) ∈ set ds ⟹ 0 ≤ x"
and "⋀ds. ds ∈ set (distrs mdp !! i) ⟹ sum_list (map snd ds) = 1"
and "⋀ds. ds ∈ set (distrs mdp !! i) ⟹ distinct (map fst ds)"
and "distrs mdp !! i ≠ []"
using assms by (auto simp: valid_mdp_rp_def list_all_iff List.null_def elim!: allE[of _ i])

lemma valid_mdp_rp_sparse_mult:
assumes "valid_mdp_rp mdp" "i < state_count mdp" "ds ∈ set (distrs mdp !! i)"
shows "sparse_mult ds y = (∑i<state_count mdp. lookup 0 ds i * y !! i)"
using valid_mdp_rpD(2,5)[OF assms] by (intro sparse_mult_eq_sum_lookup) (auto simp: list_all_iff)

lemma valid_sub_certD:
assumes "valid_mdp_rp mdp" "valid_sub_cert mdp c ord check" "i < state_count mdp"
shows "¬ states1 mdp !! i ⟹ ¬ states2 mdp !! i ⟹ solution c !! i = 0"
and "states2 mdp !! i ⟹ solution c !! i = 1"
and "states1 mdp !! i ⟹ 0 ≤ solution c !! i"
and "⋀ds. states1 mdp !! i ⟹ ds ∈ set (distrs mdp !! i) ⟹ ord (sparse_mult ds (solution c)) (solution c !! i)"
and "⋀ds. states1 mdp !! i ⟹ 0 < solution c !! i ⟶ check (distrs mdp !! i) (witness c !! i)"
using assms(2,3) valid_mdp_rpD(1)[OF assms(1,3)]
by (auto simp add: valid_sub_cert_def list_all_iff)

lemma valid_pos_certD:
assumes "valid_mdp_rp mdp" "valid_pos_cert mdp c" "i < state_count mdp" "states1 mdp !! i"
"0 < solution c !! i" "witness c !! i = ((j, a), n)"
shows "snd (witness c !! j) < n ∧ j < state_count mdp ∧ a < length (distrs mdp !! i) ∧
lookup 0 ((distrs mdp !! i) ! a) j ≠ 0 ∧ 0 < solution c !! j"
using valid_sub_certD(5)[OF assms(1) assms(2)[unfolded valid_pos_cert_def] assms(3,4)] assms(5-) by auto

lemma valid_neg_certD:
assumes "valid_mdp_rp mdp" "valid_neg_cert mdp c" "i < state_count mdp" "states1 mdp !! i"
"0 < solution c !! i" "witness c !! i = (js, n)"
shows "list_all2 (λj ds. j < state_count mdp ∧ snd (witness c !! j) < n ∧ lookup 0 ds j ≠ 0 ∧ 0 < solution c !! j) js (distrs mdp !! i)"
using valid_sub_certD(5)[OF assms(1) assms(2)[unfolded valid_neg_cert_def] assms(3)] assms(4-) by auto

context
fixes mdp c
assumes rp: "valid_mdp_rp mdp"
assumes cert: "valid_cert mdp c"
begin

interpretation pmf_as_function .

abbreviation "S ≡ {..< state_count mdp}"
abbreviation "S1 ≡ {i. i < state_count mdp ∧ (states1 mdp) !! i}"
abbreviation "S2 ≡ {i. i < state_count mdp ∧ (states2 mdp) !! i}"

lift_definition K :: "nat ⇒ nat pmf set" is
"λi. if i < state_count mdp then
{ (λj. of_rat (lookup 0 D j) :: real) | D. D ∈ set (distrs mdp !! i) }
else { indicator {0} }"
proof (auto split: if_split_asm simp del: IArray.sub_def)
fix n D assume n: "n < state_count mdp" and D: "D ∈ set (distrs mdp !! n)"
from valid_mdp_rpD(3)[OF rp this] show nn: "⋀i. 0 ≤ lookup 0 D i"
by (auto simp add: lookup_eq_map_of split: option.split dest: map_of_SomeD)
show "(∫⇧+ x. ennreal (real_of_rat (lookup 0 D x)) ∂count_space UNIV) = 1"
using valid_mdp_rpD(2,3,4,5)[OF rp n D]
apply (subst nn_integral_count_space'[of "{..< state_count mdp}"])
apply (auto intro: nn lookup_not_in_set simp: of_rat_sum[symmetric] lookup_nonneg)
apply (subst sum_list_eq_sum_lookup[symmetric])
apply (auto simp: list_all_iff lookup_eq_map_of split: option.split)
done
next
show "(∫⇧+ x. ennreal (indicator {0} x) ∂count_space UNIV) = 1"
by (subst nn_integral_count_space'[of "{0}"]) auto
qed

interpretation MDP: Reachability_Problem K S S1 S2
proof
show "S1 ∩ S2 = {}" "S1 ⊆ S" "S2 ⊆ S"
using valid_mdp_rpD(1)[OF rp] by auto
show "finite S" "S ≠ {}"
using ‹valid_mdp_rp mdp› by (auto simp add: valid_mdp_rp_def)
show "⋀s. K s ≠ {}"
using valid_mdp_rpD(6)[OF rp] by transfer simp
show "⋀s. finite (K s)"
by transfer simp

fix s assume "s ∈ S" then show "(⋃D∈K s. set_pmf D) ⊆ S"
using valid_mdp_rpD(2)[OF rp]
by transfer (auto simp: lookup_eq_map_of split: option.splits dest!: map_of_SomeD)
qed

definition "P_max s = enn2real (MDP.p s)"
definition "P_min s = enn2real (MDP.n s)"

lemma
assumes "i < state_count mdp"
shows P_max: "P_max i ≤ real_of_rat (solution (pos_cert c) !! i)" (is ?max)
and P_min: "P_min i ≥ real_of_rat (solution (neg_cert c) !! i)" (is ?min)
proof -
have "valid_pos_cert mdp (pos_cert c)" "valid_neg_cert mdp (neg_cert c)"
using ‹valid_cert mdp c› by (auto simp: valid_cert_def)
note pos = this(1)[unfolded valid_pos_cert_def] and neg = this(2)[unfolded valid_neg_cert_def]

let ?x = "λs. real_of_rat (solution (pos_cert c) !! s)"
have "enn2real (MDP.p i) ≤ ?x i"
proof (rule MDP.p_ub')
show "i ∈ S" using assms by simp
next
fix s D assume "s ∈ S1" "D ∈ K s"
then obtain j where j: "j < length (distrs mdp !! s)"
"⋀i. i < state_count mdp ⟹ pmf D i = real_of_rat (lookup 0 (distrs mdp !! s ! j) i)"
by transfer (auto simp: in_set_conv_nth)
with valid_sub_certD(4)[OF ‹valid_mdp_rp mdp› pos, of s "distrs mdp !! s ! j"] ‹s ∈ S1›
valid_mdp_rp_sparse_mult[OF ‹valid_mdp_rp mdp›, of s "distrs mdp !! s ! j" "solution (pos_cert c)"]
show "(∑t∈S. pmf D t * ?x t) ≤ ?x s"
by (simp add: of_rat_mult[symmetric] of_rat_sum[symmetric] of_rat_less_eq j)
next
fix s a assume "s ∈ S2" then show "?x s = 1"
using valid_sub_certD[OF ‹valid_mdp_rp mdp› pos] by simp
next
fix s define X where "X = (SIGMA s:S1. ⋃D∈K s. set_pmf D)"
assume "s ∈ S1" "?x s ≠ 0"
with valid_sub_certD(3)[OF rp pos, of s]
have "0 < ?x s"
by simp
with ‹s∈S1› show "∃t∈S2. (s, t) ∈ X⇧*"
proof (induction n≡"snd (witness (pos_cert c) !! s)" arbitrary: s rule: less_induct)
case (less s)
obtain t a n where eq: "witness (pos_cert c) !! s = ((t, a), n)"
by (metis prod.exhaust)
from valid_pos_certD[OF rp ‹valid_pos_cert mdp (pos_cert c)› _ _ _ this] less.prems
have ord: "snd (witness (pos_cert c) !! t) < snd (witness (pos_cert c) !! s)"
and t: "lookup 0 (distrs mdp !! s ! a) t ≠ 0" "0 < ?x t" "t∈S" "a < length (distrs mdp !! s)"
unfolding eq by auto
with ‹s∈S1› have X: "(s, t) ∈ X"
unfolding X_def
by (transfer fixing: s t a c)
(auto simp: X_def in_set_conv_nth
intro!: exI[of _ "λj. real_of_rat (lookup 0 (distrs mdp !! s ! a) j)"]
exI[of _ "distrs mdp !! s ! a"] exI[of _ a])
show ?case
proof cases
assume "t ∈ S1"
with less.hyps[OF ord _ ‹0 < ?x t›] X show ?thesis
by auto
next
assume "t ∉ S1"
with valid_sub_certD[OF ‹valid_mdp_rp mdp› pos, of t] ‹0 < ?x t› ‹t∈S›
have "t ∈ S2"
by auto
with X show ?thesis
by auto
qed
qed
next
fix s assume "s ∈ S - S1 - S2" then show "?x s = 0"
using valid_sub_certD(1)[OF ‹valid_mdp_rp mdp› pos, of s] by simp
qed
then show ?max

let ?x = "λs. real_of_rat (solution (neg_cert c) !! s)"
have "?x i ≤ enn2real (MDP.n i)"
proof (rule MDP.n_lb')
show "i ∈ S" using assms by simp
next
fix s D assume "s ∈ S1" "D ∈ K s"
then obtain j where j: "j < length (distrs mdp !! s)"
"⋀i. i < state_count mdp ⟹ pmf D i = real_of_rat (lookup 0 (distrs mdp !! s ! j) i)"
by transfer (auto simp: in_set_conv_nth)
with valid_sub_certD(4)[OF ‹valid_mdp_rp mdp› neg, of s "distrs mdp !! s ! j"] ‹s ∈ S1›
valid_mdp_rp_sparse_mult[OF ‹valid_mdp_rp mdp›, of s "distrs mdp !! s ! j" "solution (neg_cert c)"]
show "?x s ≤ (∑t∈S. pmf D t * ?x t)"
by (simp add: of_rat_mult[symmetric] of_rat_sum[symmetric] of_rat_less_eq j)
next
fix s a assume "s ∈ S2" then show "?x s = 1"
using valid_sub_certD[OF ‹valid_mdp_rp mdp› neg] by simp
next
show "wf ((S × S ∩ {(s, t). snd (witness (neg_cert c) !! t) < snd (witness (neg_cert c) !! s)})¯)" (is "wf ?F")
using MDP.S_finite
by (intro finite_acyclic_wf_converse acyclicI_order[where f="λs. snd (witness (neg_cert c) !! s)"]) auto

fix s D assume 2: "s ∈ S1" "D ∈ K s" and "?x s ≠ 0"
then have "0 < ?x s"
using valid_sub_certD(3)[OF ‹valid_mdp_rp mdp› neg, of s] by auto

from 2 obtain a where a: "a < length (distrs mdp !! s)"
"⋀i. i < state_count mdp ⟹ pmf D i = real_of_rat (lookup 0 (distrs mdp !! s ! a) i)"
by transfer (auto simp: in_set_conv_nth)

obtain js n where eq: "witness (neg_cert c) !! s = (js, n)"
by (metis prod.exhaust)
from valid_neg_certD[OF ‹valid_mdp_rp mdp› ‹valid_neg_cert mdp (neg_cert c)› _ _ _ eq] a ‹s ∈ S1› ‹0 < ?x s›
have *: "length js = length (distrs mdp !! s)" "js ! a ∈ S"
"snd (witness (neg_cert c) !! (js ! a)) < snd (witness (neg_cert c) !! s)"
"lookup 0 (distrs mdp !! s ! a) (js ! a) ≠ 0"
"0 < ?x (js ! a)"
unfolding eq by (auto dest: list_all2_nthD2 list_all2_lengthD)
with a ‹s ∈ S1› have js_a: "js ! a ∈ D" "(js ! a, s) ∈ ?F"
by (auto simp: set_pmf_iff)

show "∃t∈D. (t, s) ∈ ?F ∧ t ∈ S1 ∧ ?x t ≠ 0 ∨ t ∈ S2"
proof cases
assume "js ! a ∈ S1" with js_a ‹0 < ?x (js ! a)› show ?thesis by auto
next
assume "js ! a ∉ S1"
with ‹0 < ?x (js ! a)› ‹js!a ∈ S› valid_sub_certD[OF rp neg, of "js ! a"]
have "js ! a ∈ S2"
by (auto simp:  less_le)
with ‹js ! a ∈ D› show ?thesis
by auto
qed
next
fix s assume "s ∈ S - S1 - S2" then show "?x s = 0"
using valid_sub_certD(1)[OF ‹valid_mdp_rp mdp› neg, of s] by simp
qed
then show ?min