Theory Linear_Temporal_Logic_on_Streams

theory Linear_Temporal_Logic_on_Streams
imports Stream Sublist Extended_Nat
(*  Title:      HOL/Library/Linear_Temporal_Logic_on_Streams.thy
    Author:     Andrei Popescu, TU Muenchen
    Author:     Dmitriy Traytel, TU Muenchen
*)

section ‹Linear Temporal Logic on Streams›

theory Linear_Temporal_Logic_on_Streams
  imports Stream Sublist Extended_Nat Infinite_Set
begin

section‹Preliminaries›

lemma shift_prefix:
assumes "xl @- xs = yl @- ys" and "length xl ≤ length yl"
shows "prefixeq xl yl"
using assms proof(induct xl arbitrary: yl xs ys)
  case (Cons x xl yl xs ys)
  thus ?case by (cases yl) auto
qed auto

lemma shift_prefix_cases:
assumes "xl @- xs = yl @- ys"
shows "prefixeq xl yl ∨ prefixeq yl xl"
using shift_prefix[OF assms]
by (cases "length xl ≤ length yl") (metis, metis assms nat_le_linear shift_prefix)


section‹Linear temporal logic›

(* Propositional connectives: *)
abbreviation (input) IMPL (infix "impl" 60)
where "φ impl ψ ≡ λ xs. φ xs ⟶ ψ xs"

abbreviation (input) OR (infix "or" 60)
where "φ or ψ ≡ λ xs. φ xs ∨ ψ xs"

abbreviation (input) AND (infix "aand" 60)
where "φ aand ψ ≡ λ xs. φ xs ∧ ψ xs"

abbreviation (input) "not φ ≡ λ xs. ¬ φ xs"

abbreviation (input) "true ≡ λ xs. True"

abbreviation (input) "false ≡ λ xs. False"

lemma impl_not_or: "φ impl ψ = (not φ) or ψ"
by blast

lemma not_or: "not (φ or ψ) = (not φ) aand (not ψ)"
by blast

lemma not_aand: "not (φ aand ψ) = (not φ) or (not ψ)"
by blast

lemma non_not[simp]: "not (not φ) = φ" by simp

(* Temporal (LTL) connectives: *)
fun holds where "holds P xs ⟷ P (shd xs)"
fun nxt where "nxt φ xs = φ (stl xs)"

definition "HLD s = holds (λx. x ∈ s)"

abbreviation HLD_nxt (infixr "⋅" 65) where
  "s ⋅ P ≡ HLD s aand nxt P"

context
  notes [[inductive_internals]]
begin

inductive ev for φ where
base: "φ xs ⟹ ev φ xs"
|
step: "ev φ (stl xs) ⟹ ev φ xs"

coinductive alw for φ where
alw: "⟦φ xs; alw φ (stl xs)⟧ ⟹ alw φ xs"

(* weak until: *)
coinductive UNTIL (infix "until" 60) for φ ψ where
base: "ψ xs ⟹ (φ until ψ) xs"
|
step: "⟦φ xs; (φ until ψ) (stl xs)⟧ ⟹ (φ until ψ) xs"

end

lemma holds_mono:
assumes holds: "holds P xs" and 0: "⋀ x. P x ⟹ Q x"
shows "holds Q xs"
using assms by auto

lemma holds_aand:
"(holds P aand holds Q) steps ⟷ holds (λ step. P step ∧ Q step) steps" by auto

lemma HLD_iff: "HLD s ω ⟷ shd ω ∈ s"
  by (simp add: HLD_def)

lemma HLD_Stream[simp]: "HLD X (x ## ω) ⟷ x ∈ X"
  by (simp add: HLD_iff)

lemma nxt_mono:
assumes nxt: "nxt φ xs" and 0: "⋀ xs. φ xs ⟹ ψ xs"
shows "nxt ψ xs"
using assms by auto

declare ev.intros[intro]
declare alw.cases[elim]

lemma ev_induct_strong[consumes 1, case_names base step]:
  "ev φ x ⟹ (⋀xs. φ xs ⟹ P xs) ⟹ (⋀xs. ev φ (stl xs) ⟹ ¬ φ xs ⟹ P (stl xs) ⟹ P xs) ⟹ P x"
  by (induct rule: ev.induct) auto

lemma alw_coinduct[consumes 1, case_names alw stl]:
  "X x ⟹ (⋀x. X x ⟹ φ x) ⟹ (⋀x. X x ⟹ ¬ alw φ (stl x) ⟹ X (stl x)) ⟹ alw φ x"
  using alw.coinduct[of X x φ] by auto

lemma ev_mono:
assumes ev: "ev φ xs" and 0: "⋀ xs. φ xs ⟹ ψ xs"
shows "ev ψ xs"
using ev by induct (auto simp: 0)

lemma alw_mono:
assumes alw: "alw φ xs" and 0: "⋀ xs. φ xs ⟹ ψ xs"
shows "alw ψ xs"
using alw by coinduct (auto simp: 0)

lemma until_monoL:
assumes until: "(φ1 until ψ) xs" and 0: "⋀ xs. φ1 xs ⟹ φ2 xs"
shows "(φ2 until ψ) xs"
using until by coinduct (auto elim: UNTIL.cases simp: 0)

lemma until_monoR:
assumes until: "(φ until ψ1) xs" and 0: "⋀ xs. ψ1 xs ⟹ ψ2 xs"
shows "(φ until ψ2) xs"
using until by coinduct (auto elim: UNTIL.cases simp: 0)

lemma until_mono:
assumes until: "(φ1 until ψ1) xs" and
0: "⋀ xs. φ1 xs ⟹ φ2 xs" "⋀ xs. ψ1 xs ⟹ ψ2 xs"
shows "(φ2 until ψ2) xs"
using until by coinduct (auto elim: UNTIL.cases simp: 0)

lemma until_false: "φ until false = alw φ"
proof-
  {fix xs assume "(φ until false) xs" hence "alw φ xs"
   by coinduct (auto elim: UNTIL.cases)
  }
  moreover
  {fix xs assume "alw φ xs" hence "(φ until false) xs"
   by coinduct auto
  }
  ultimately show ?thesis by blast
qed

lemma ev_nxt: "ev φ = (φ or nxt (ev φ))"
by (rule ext) (metis ev.simps nxt.simps)

lemma alw_nxt: "alw φ = (φ aand nxt (alw φ))"
by (rule ext) (metis alw.simps nxt.simps)

lemma ev_ev[simp]: "ev (ev φ) = ev φ"
proof-
  {fix xs
   assume "ev (ev φ) xs" hence "ev φ xs"
   by induct auto
  }
  thus ?thesis by auto
qed

lemma alw_alw[simp]: "alw (alw φ) = alw φ"
proof-
  {fix xs
   assume "alw φ xs" hence "alw (alw φ) xs"
   by coinduct auto
  }
  thus ?thesis by auto
qed

lemma ev_shift:
assumes "ev φ xs"
shows "ev φ (xl @- xs)"
using assms by (induct xl) auto

lemma ev_imp_shift:
assumes "ev φ xs"  shows "∃ xl xs2. xs = xl @- xs2 ∧ φ xs2"
using assms by induct (metis shift.simps(1), metis shift.simps(2) stream.collapse)+

lemma alw_ev_shift: "alw φ xs1 ⟹ ev (alw φ) (xl @- xs1)"
by (auto intro: ev_shift)

lemma alw_shift:
assumes "alw φ (xl @- xs)"
shows "alw φ xs"
using assms by (induct xl) auto

lemma ev_ex_nxt:
assumes "ev φ xs"
shows "∃ n. (nxt ^^ n) φ xs"
using assms proof induct
  case (base xs) thus ?case by (intro exI[of _ 0]) auto
next
  case (step xs)
  then obtain n where "(nxt ^^ n) φ (stl xs)" by blast
  thus ?case by (intro exI[of _ "Suc n"]) (metis funpow.simps(2) nxt.simps o_def)
qed

lemma alw_sdrop:
assumes "alw φ xs"  shows "alw φ (sdrop n xs)"
by (metis alw_shift assms stake_sdrop)

lemma nxt_sdrop: "(nxt ^^ n) φ xs ⟷ φ (sdrop n xs)"
by (induct n arbitrary: xs) auto

definition "wait φ xs ≡ LEAST n. (nxt ^^ n) φ xs"

lemma nxt_wait:
assumes "ev φ xs"  shows "(nxt ^^ (wait φ xs)) φ xs"
unfolding wait_def using ev_ex_nxt[OF assms] by(rule LeastI_ex)

lemma nxt_wait_least:
assumes ev: "ev φ xs" and nxt: "(nxt ^^ n) φ xs"  shows "wait φ xs ≤ n"
unfolding wait_def using ev_ex_nxt[OF ev] by (metis Least_le nxt)

lemma sdrop_wait:
assumes "ev φ xs"  shows "φ (sdrop (wait φ xs) xs)"
using nxt_wait[OF assms] unfolding nxt_sdrop .

lemma sdrop_wait_least:
assumes ev: "ev φ xs" and nxt: "φ (sdrop n xs)"  shows "wait φ xs ≤ n"
using assms nxt_wait_least unfolding nxt_sdrop by auto

lemma nxt_ev: "(nxt ^^ n) φ xs ⟹ ev φ xs"
by (induct n arbitrary: xs) auto

lemma not_ev: "not (ev φ) = alw (not φ)"
proof(rule ext, safe)
  fix xs assume "not (ev φ) xs" thus "alw (not φ) xs"
  by (coinduct) auto
next
  fix xs assume "ev φ xs" and "alw (not φ) xs" thus False
  by (induct) auto
qed

lemma not_alw: "not (alw φ) = ev (not φ)"
proof-
  have "not (alw φ) = not (alw (not (not φ)))" by simp
  also have "... = ev (not φ)" unfolding not_ev[symmetric] by simp
  finally show ?thesis .
qed

lemma not_ev_not[simp]: "not (ev (not φ)) = alw φ"
unfolding not_ev by simp

lemma not_alw_not[simp]: "not (alw (not φ)) = ev φ"
unfolding not_alw by simp

lemma alw_ev_sdrop:
assumes "alw (ev φ) (sdrop m xs)"
shows "alw (ev φ) xs"
using assms
by coinduct (metis alw_nxt ev_shift funpow_swap1 nxt.simps nxt_sdrop stake_sdrop)

lemma ev_alw_imp_alw_ev:
assumes "ev (alw φ) xs"  shows "alw (ev φ) xs"
using assms by induct (metis (full_types) alw_mono ev.base, metis alw alw_nxt ev.step)

lemma alw_aand: "alw (φ aand ψ) = alw φ aand alw ψ"
proof-
  {fix xs assume "alw (φ aand ψ) xs" hence "(alw φ aand alw ψ) xs"
   by (auto elim: alw_mono)
  }
  moreover
  {fix xs assume "(alw φ aand alw ψ) xs" hence "alw (φ aand ψ) xs"
   by coinduct auto
  }
  ultimately show ?thesis by blast
qed

lemma ev_or: "ev (φ or ψ) = ev φ or ev ψ"
proof-
  {fix xs assume "(ev φ or ev ψ) xs" hence "ev (φ or ψ) xs"
   by (auto elim: ev_mono)
  }
  moreover
  {fix xs assume "ev (φ or ψ) xs" hence "(ev φ or ev ψ) xs"
   by induct auto
  }
  ultimately show ?thesis by blast
qed

lemma ev_alw_aand:
assumes φ: "ev (alw φ) xs" and ψ: "ev (alw ψ) xs"
shows "ev (alw (φ aand ψ)) xs"
proof-
  obtain xl xs1 where xs1: "xs = xl @- xs1" and φφ: "alw φ xs1"
  using φ by (metis ev_imp_shift)
  moreover obtain yl ys1 where xs2: "xs = yl @- ys1" and ψψ: "alw ψ ys1"
  using ψ by (metis ev_imp_shift)
  ultimately have 0: "xl @- xs1 = yl @- ys1" by auto
  hence "prefixeq xl yl ∨ prefixeq yl xl" using shift_prefix_cases by auto
  thus ?thesis proof
    assume "prefixeq xl yl"
    then obtain yl1 where yl: "yl = xl @ yl1" by (elim prefixeqE)
    have xs1': "xs1 = yl1 @- ys1" using 0 unfolding yl by simp
    have "alw φ ys1" using φφ unfolding xs1' by (metis alw_shift)
    hence "alw (φ aand ψ) ys1" using ψψ unfolding alw_aand by auto
    thus ?thesis unfolding xs2 by (auto intro: alw_ev_shift)
  next
    assume "prefixeq yl xl"
    then obtain xl1 where xl: "xl = yl @ xl1" by (elim prefixeqE)
    have ys1': "ys1 = xl1 @- xs1" using 0 unfolding xl by simp
    have "alw ψ xs1" using ψψ unfolding ys1' by (metis alw_shift)
    hence "alw (φ aand ψ) xs1" using φφ unfolding alw_aand by auto
    thus ?thesis unfolding xs1 by (auto intro: alw_ev_shift)
  qed
qed

lemma ev_alw_alw_impl:
assumes "ev (alw φ) xs" and "alw (alw φ impl ev ψ) xs"
shows "ev ψ xs"
using assms by induct auto

lemma ev_alw_stl[simp]: "ev (alw φ) (stl x) ⟷ ev (alw φ) x"
by (metis (full_types) alw_nxt ev_nxt nxt.simps)

lemma alw_alw_impl_ev:
"alw (alw φ impl ev ψ) = (ev (alw φ) impl alw (ev ψ))" (is "?A = ?B")
proof-
  {fix xs assume "?A xs ∧ ev (alw φ) xs" hence "alw (ev ψ) xs"
    by coinduct (auto elim: ev_alw_alw_impl)
  }
  moreover
  {fix xs assume "?B xs" hence "?A xs"
   by coinduct auto
  }
  ultimately show ?thesis by blast
qed

lemma ev_alw_impl:
assumes "ev φ xs" and "alw (φ impl ψ) xs"  shows "ev ψ xs"
using assms by induct auto

lemma ev_alw_impl_ev:
assumes "ev φ xs" and "alw (φ impl ev ψ) xs"  shows "ev ψ xs"
using ev_alw_impl[OF assms] by simp

lemma alw_mp:
assumes "alw φ xs" and "alw (φ impl ψ) xs"
shows "alw ψ xs"
proof-
  {assume "alw φ xs ∧ alw (φ impl ψ) xs" hence ?thesis
   by coinduct auto
  }
  thus ?thesis using assms by auto
qed

lemma all_imp_alw:
assumes "⋀ xs. φ xs"  shows "alw φ xs"
proof-
  {assume "∀ xs. φ xs"
   hence ?thesis by coinduct auto
  }
  thus ?thesis using assms by auto
qed

lemma alw_impl_ev_alw:
assumes "alw (φ impl ev ψ) xs"
shows "alw (ev φ impl ev ψ) xs"
using assms by coinduct (auto dest: ev_alw_impl)

lemma ev_holds_sset:
"ev (holds P) xs ⟷ (∃ x ∈ sset xs. P x)" (is "?L ⟷ ?R")
proof safe
  assume ?L thus ?R by induct (metis holds.simps stream.set_sel(1), metis stl_sset)
next
  fix x assume "x ∈ sset xs" "P x"
  thus ?L by (induct rule: sset_induct) (simp_all add: ev.base ev.step)
qed

(* LTL as a program logic: *)
lemma alw_invar:
assumes "φ xs" and "alw (φ impl nxt φ) xs"
shows "alw φ xs"
proof-
  {assume "φ xs ∧ alw (φ impl nxt φ) xs" hence ?thesis
   by coinduct auto
  }
  thus ?thesis using assms by auto
qed

lemma variance:
assumes 1: "φ xs" and 2: "alw (φ impl (ψ or nxt φ)) xs"
shows "(alw φ or ev ψ) xs"
proof-
  {assume "¬ ev ψ xs" hence "alw (not ψ) xs" unfolding not_ev[symmetric] .
   moreover have "alw (not ψ impl (φ impl nxt φ)) xs"
   using 2 by coinduct auto
   ultimately have "alw (φ impl nxt φ) xs" by(auto dest: alw_mp)
   with 1 have "alw φ xs" by(rule alw_invar)
  }
  thus ?thesis by blast
qed

lemma ev_alw_imp_nxt:
assumes e: "ev φ xs" and a: "alw (φ impl (nxt φ)) xs"
shows "ev (alw φ) xs"
proof-
  obtain xl xs1 where xs: "xs = xl @- xs1" and φ: "φ xs1"
  using e by (metis ev_imp_shift)
  have "φ xs1 ∧ alw (φ impl (nxt φ)) xs1" using a φ unfolding xs by (metis alw_shift)
  hence "alw φ xs1" by(coinduct xs1 rule: alw.coinduct) auto
  thus ?thesis unfolding xs by (auto intro: alw_ev_shift)
qed


inductive ev_at :: "('a stream ⇒ bool) ⇒ nat ⇒ 'a stream ⇒ bool" for P :: "'a stream ⇒ bool" where
  base: "P ω ⟹ ev_at P 0 ω"
| step:"¬ P ω ⟹ ev_at P n (stl ω) ⟹ ev_at P (Suc n) ω"

inductive_simps ev_at_0[simp]: "ev_at P 0 ω"
inductive_simps ev_at_Suc[simp]: "ev_at P (Suc n) ω"

lemma ev_at_imp_snth: "ev_at P n ω ⟹ P (sdrop n ω)"
  by (induction n arbitrary: ω) auto

lemma ev_at_HLD_imp_snth: "ev_at (HLD X) n ω ⟹ ω !! n ∈ X"
  by (auto dest!: ev_at_imp_snth simp: HLD_iff)

lemma ev_at_HLD_single_imp_snth: "ev_at (HLD {x}) n ω ⟹ ω !! n = x"
  by (drule ev_at_HLD_imp_snth) simp

lemma ev_at_unique: "ev_at P n ω ⟹ ev_at P m ω ⟹ n = m"
proof (induction arbitrary: m rule: ev_at.induct)
  case (base ω) then show ?case
    by (simp add: ev_at.simps[of _ _ ω])
next
  case (step ω n) from step.prems step.hyps step.IH[of "m - 1"] show ?case
    by (auto simp add: ev_at.simps[of _ _ ω])
qed

lemma ev_iff_ev_at: "ev P ω ⟷ (∃n. ev_at P n ω)"
proof
  assume "ev P ω" then show "∃n. ev_at P n ω"
    by (induction rule: ev_induct_strong) (auto intro: ev_at.intros)
next
  assume "∃n. ev_at P n ω"
  then obtain n where "ev_at P n ω"
    by auto
  then show "ev P ω"
    by induction auto
qed

lemma ev_at_shift: "ev_at (HLD X) i (stake (Suc i) ω @- ω' :: 's stream) ⟷ ev_at (HLD X) i ω"
  by (induction i arbitrary: ω) (auto simp: HLD_iff)

lemma ev_iff_ev_at_unqiue: "ev P ω ⟷ (∃!n. ev_at P n ω)"
  by (auto intro: ev_at_unique simp: ev_iff_ev_at)

lemma alw_HLD_iff_streams: "alw (HLD X) ω ⟷ ω ∈ streams X"
proof
  assume "alw (HLD X) ω" then show "ω ∈ streams X"
  proof (coinduction arbitrary: ω)
    case (streams ω) then show ?case by (cases ω) auto
  qed
next
  assume "ω ∈ streams X" then show "alw (HLD X) ω"
  proof (coinduction arbitrary: ω)
    case (alw ω) then show ?case by (cases ω) auto
  qed
qed

lemma not_HLD: "not (HLD X) = HLD (- X)"
  by (auto simp: HLD_iff)

lemma not_alw_iff: "¬ (alw P ω) ⟷ ev (not P) ω"
  using not_alw[of P] by (simp add: fun_eq_iff)

lemma not_ev_iff: "¬ (ev P ω) ⟷ alw (not P) ω"
  using not_alw_iff[of "not P" ω, symmetric]  by simp

lemma ev_Stream: "ev P (x ## s) ⟷ P (x ## s) ∨ ev P s"
  by (auto elim: ev.cases)

lemma alw_ev_imp_ev_alw:
  assumes "alw (ev P) ω" shows "ev (P aand alw (ev P)) ω"
proof -
  have "ev P ω" using assms by auto
  from this assms show ?thesis
    by induct auto
qed

lemma ev_False: "ev (λx. False) ω ⟷ False"
proof
  assume "ev (λx. False) ω" then show False
    by induct auto
qed auto

lemma alw_False: "alw (λx. False) ω ⟷ False"
  by auto

lemma ev_iff_sdrop: "ev P ω ⟷ (∃m. P (sdrop m ω))"
proof safe
  assume "ev P ω" then show "∃m. P (sdrop m ω)"
    by (induct rule: ev_induct_strong) (auto intro: exI[of _ 0] exI[of _ "Suc n" for n])
next
  fix m assume "P (sdrop m ω)" then show "ev P ω"
    by (induct m arbitrary: ω) auto
qed

lemma alw_iff_sdrop: "alw P ω ⟷ (∀m. P (sdrop m ω))"
proof safe
  fix m assume "alw P ω" then show "P (sdrop m ω)"
    by (induct m arbitrary: ω) auto
next
  assume "∀m. P (sdrop m ω)" then show "alw P ω"
    by (coinduction arbitrary: ω) (auto elim: allE[of _ 0] allE[of _ "Suc n" for n])
qed

lemma infinite_iff_alw_ev: "infinite {m. P (sdrop m ω)} ⟷ alw (ev P) ω"
  unfolding infinite_nat_iff_unbounded_le alw_iff_sdrop ev_iff_sdrop
  by simp (metis le_Suc_ex le_add1)

lemma alw_inv:
  assumes stl: "⋀s. f (stl s) = stl (f s)"
  shows "alw P (f s) ⟷ alw (λx. P (f x)) s"
proof
  assume "alw P (f s)" then show "alw (λx. P (f x)) s"
    by (coinduction arbitrary: s rule: alw_coinduct)
       (auto simp: stl)
next
  assume "alw (λx. P (f x)) s" then show "alw P (f s)"
    by (coinduction arbitrary: s rule: alw_coinduct) (auto simp: stl[symmetric])
qed

lemma ev_inv:
  assumes stl: "⋀s. f (stl s) = stl (f s)"
  shows "ev P (f s) ⟷ ev (λx. P (f x)) s"
proof
  assume "ev P (f s)" then show "ev (λx. P (f x)) s"
    by (induction "f s" arbitrary: s) (auto simp: stl)
next
  assume "ev (λx. P (f x)) s" then show "ev P (f s)"
    by induction (auto simp: stl[symmetric])
qed

lemma alw_smap: "alw P (smap f s) ⟷ alw (λx. P (smap f x)) s"
  by (rule alw_inv) simp

lemma ev_smap: "ev P (smap f s) ⟷ ev (λx. P (smap f x)) s"
  by (rule ev_inv) simp

lemma alw_cong:
  assumes P: "alw P ω" and eq: "⋀ω. P ω ⟹ Q1 ω ⟷ Q2 ω"
  shows "alw Q1 ω ⟷ alw Q2 ω"
proof -
  from eq have "(alw P aand Q1) = (alw P aand Q2)" by auto
  then have "alw (alw P aand Q1) ω = alw (alw P aand Q2) ω" by auto
  with P show "alw Q1 ω ⟷ alw Q2 ω"
    by (simp add: alw_aand)
qed

lemma ev_cong:
  assumes P: "alw P ω" and eq: "⋀ω. P ω ⟹ Q1 ω ⟷ Q2 ω"
  shows "ev Q1 ω ⟷ ev Q2 ω"
proof -
  from P have "alw (λxs. Q1 xs ⟶ Q2 xs) ω" by (rule alw_mono) (simp add: eq)
  moreover from P have "alw (λxs. Q2 xs ⟶ Q1 xs) ω" by (rule alw_mono) (simp add: eq)
  moreover note ev_alw_impl[of Q1 ω Q2] ev_alw_impl[of Q2 ω Q1]
  ultimately show "ev Q1 ω ⟷ ev Q2 ω"
    by auto
qed

lemma alwD: "alw P x ⟹ P x"
  by auto

lemma alw_alwD: "alw P ω ⟹ alw (alw P) ω"
  by simp

lemma alw_ev_stl: "alw (ev P) (stl ω) ⟷ alw (ev P) ω"
  by (auto intro: alw.intros)

lemma holds_Stream: "holds P (x ## s) ⟷ P x"
  by simp

lemma holds_eq1[simp]: "holds (op = x) = HLD {x}"
  by rule (auto simp: HLD_iff)

lemma holds_eq2[simp]: "holds (λy. y = x) = HLD {x}"
  by rule (auto simp: HLD_iff)

lemma not_holds_eq[simp]: "holds (- op = x) = not (HLD {x})"
  by rule (auto simp: HLD_iff)

text ‹Strong until›

context
  notes [[inductive_internals]]
begin

inductive suntil (infix "suntil" 60) for φ ψ where
  base: "ψ ω ⟹ (φ suntil ψ) ω"
| step: "φ ω ⟹ (φ suntil ψ) (stl ω) ⟹ (φ suntil ψ) ω"

inductive_simps suntil_Stream: "(φ suntil ψ) (x ## s)"

end

lemma suntil_induct_strong[consumes 1, case_names base step]:
  "(φ suntil ψ) x ⟹
    (⋀ω. ψ ω ⟹ P ω) ⟹
    (⋀ω. φ ω ⟹ ¬ ψ ω ⟹ (φ suntil ψ) (stl ω) ⟹ P (stl ω) ⟹ P ω) ⟹ P x"
  using suntil.induct[of φ ψ x P] by blast

lemma ev_suntil: "(φ suntil ψ) ω ⟹ ev ψ ω"
  by (induct rule: suntil.induct) auto

lemma suntil_inv:
  assumes stl: "⋀s. f (stl s) = stl (f s)"
  shows "(P suntil Q) (f s) ⟷ ((λx. P (f x)) suntil (λx. Q (f x))) s"
proof
  assume "(P suntil Q) (f s)" then show "((λx. P (f x)) suntil (λx. Q (f x))) s"
    by (induction "f s" arbitrary: s) (auto simp: stl intro: suntil.intros)
next
  assume "((λx. P (f x)) suntil (λx. Q (f x))) s" then show "(P suntil Q) (f s)"
    by induction (auto simp: stl[symmetric] intro: suntil.intros)
qed

lemma suntil_smap: "(P suntil Q) (smap f s) ⟷ ((λx. P (smap f x)) suntil (λx. Q (smap f x))) s"
  by (rule suntil_inv) simp

lemma hld_smap: "HLD x (smap f s) = holds (λy. f y ∈ x) s"
  by (simp add: HLD_def)

lemma suntil_mono:
  assumes eq: "⋀ω. P ω ⟹ Q1 ω ⟹ Q2 ω" "⋀ω. P ω ⟹ R1 ω ⟹ R2 ω"
  assumes *: "(Q1 suntil R1) ω" "alw P ω" shows "(Q2 suntil R2) ω"
  using * by induct (auto intro: eq suntil.intros)

lemma suntil_cong:
  "alw P ω ⟹ (⋀ω. P ω ⟹ Q1 ω ⟷ Q2 ω) ⟹ (⋀ω. P ω ⟹ R1 ω ⟷ R2 ω) ⟹
    (Q1 suntil R1) ω ⟷ (Q2 suntil R2) ω"
  using suntil_mono[of P Q1 Q2 R1 R2 ω] suntil_mono[of P Q2 Q1 R2 R1 ω] by auto

lemma ev_suntil_iff: "ev (P suntil Q) ω ⟷ ev Q ω"
proof
  assume "ev (P suntil Q) ω" then show "ev Q ω"
   by induct (auto dest: ev_suntil)
next
  assume "ev Q ω" then show "ev (P suntil Q) ω"
    by induct (auto intro: suntil.intros)
qed

lemma true_suntil: "((λ_. True) suntil P) = ev P"
  by (simp add: suntil_def ev_def)

lemma suntil_lfp: "(φ suntil ψ) = lfp (λP s. ψ s ∨ (φ s ∧ P (stl s)))"
  by (simp add: suntil_def)

lemma sfilter_P[simp]: "P (shd s) ⟹ sfilter P s = shd s ## sfilter P (stl s)"
  using sfilter_Stream[of P "shd s" "stl s"] by simp

lemma sfilter_not_P[simp]: "¬ P (shd s) ⟹ sfilter P s = sfilter P (stl s)"
  using sfilter_Stream[of P "shd s" "stl s"] by simp

lemma sfilter_eq: 
  assumes "ev (holds P) s"
  shows "sfilter P s = x ## s' ⟷
    P x ∧ (not (holds P) suntil (HLD {x} aand nxt (λs. sfilter P s = s'))) s"
  using assms
  by (induct rule: ev_induct_strong)
     (auto simp add: HLD_iff intro: suntil.intros elim: suntil.cases)

lemma sfilter_streams:
  "alw (ev (holds P)) ω ⟹ ω ∈ streams A ⟹ sfilter P ω ∈ streams {x∈A. P x}"
proof (coinduction arbitrary: ω)
  case (streams ω)
  then have "ev (holds P) ω" by blast
  from this streams show ?case
    by (induct rule: ev_induct_strong) (auto elim: streamsE)
qed

lemma alw_sfilter:
  assumes *: "alw (ev (holds P)) s"
  shows "alw Q (sfilter P s) ⟷ alw (λx. Q (sfilter P x)) s"
proof
  assume "alw Q (sfilter P s)" with * show "alw (λx. Q (sfilter P x)) s"
  proof (coinduction arbitrary: s rule: alw_coinduct)
    case (stl s) 
    then have "ev (holds P) s"
      by blast
    from this stl show ?case
      by (induct rule: ev_induct_strong) auto
  qed auto
next
  assume "alw (λx. Q (sfilter P x)) s" with * show "alw Q (sfilter P s)"
  proof (coinduction arbitrary: s rule: alw_coinduct)
    case (stl s) 
    then have "ev (holds P) s"
      by blast
    from this stl show ?case
      by (induct rule: ev_induct_strong) auto
  qed auto
qed

lemma ev_sfilter:
  assumes *: "alw (ev (holds P)) s"
  shows "ev Q (sfilter P s) ⟷ ev (λx. Q (sfilter P x)) s"
proof
  assume "ev Q (sfilter P s)" from this * show "ev (λx. Q (sfilter P x)) s"
  proof (induction "sfilter P s" arbitrary: s rule: ev_induct_strong)
    case (step s)
    then have "ev (holds P) s"
      by blast
    from this step show ?case
      by (induct rule: ev_induct_strong) auto
  qed auto
next
  assume "ev (λx. Q (sfilter P x)) s" then show "ev Q (sfilter P s)"
  proof (induction rule: ev_induct_strong)
    case (step s) then show ?case
      by (cases "P (shd s)") auto
  qed auto
qed

lemma holds_sfilter:
  assumes "ev (holds Q) s" shows "holds P (sfilter Q s) ⟷ (not (holds Q) suntil (holds (Q aand P))) s"
proof
  assume "holds P (sfilter Q s)" with assms show "(not (holds Q) suntil (holds (Q aand P))) s"
    by (induct rule: ev_induct_strong) (auto intro: suntil.intros)
next
  assume "(not (holds Q) suntil (holds (Q aand P))) s" then show "holds P (sfilter Q s)"
    by induct auto
qed

lemma suntil_aand_nxt:
  "(φ suntil (φ aand nxt ψ)) ω ⟷ (φ aand nxt (φ suntil ψ)) ω"
proof
  assume "(φ suntil (φ aand nxt ψ)) ω" then show "(φ aand nxt (φ suntil ψ)) ω"
    by induction (auto intro: suntil.intros)
next
  assume "(φ aand nxt (φ suntil ψ)) ω"
  then have "(φ suntil ψ) (stl ω)" "φ ω"
    by auto
  then show "(φ suntil (φ aand nxt ψ)) ω"
    by (induction "stl ω" arbitrary: ω)
       (auto elim: suntil.cases intro: suntil.intros)
qed

lemma alw_sconst: "alw P (sconst x) ⟷ P (sconst x)"
proof
  assume "P (sconst x)" then show "alw P (sconst x)"
    by coinduction auto
qed auto

lemma ev_sconst: "ev P (sconst x) ⟷ P (sconst x)"
proof
  assume "ev P (sconst x)" then show "P (sconst x)"
    by (induction "sconst x") auto
qed auto

lemma suntil_sconst: "(φ suntil ψ) (sconst x) ⟷ ψ (sconst x)"
proof
  assume "(φ suntil ψ) (sconst x)" then show "ψ (sconst x)"
    by (induction "sconst x") auto
qed (auto intro: suntil.intros)

lemma hld_smap': "HLD x (smap f s) = HLD (f -` x) s"
  by (simp add: HLD_def)

end