Theory Voblint_Analysis_Interval.Interval_Warrowing
theory Interval_Warrowing
imports "Voblint_Domain.Interval_Lattice"
begin
section ‹Interval widening and narrowing›
text ‹
The interval lattice has infinite ascending chains, so the solver needs widening
to terminate and narrowing to recover precision afterwards. This theory defines
both and instantiates intervals as a numeric domain.
›
subsection ‹Widening›
text ‹
Standard interval widening: keep a bound if it did not move outward, otherwise
push it to the infinite end. Every widen-ascending chain therefore stabilises
after at most two steps (each bound takes at most one outward jump).
›
fun widen_ivl_core :: "ivl => ivl => ivl" where
"widen_ivl_core (Ivl l1 u1) (Ivl l2 u2) =
Ivl (if l1 ≤ l2 then l1 else MinInf)
(if u2 ≤ u1 then u1 else PlusInf)"
lemma a_le_widen_ivl_core: "(a :: ivl) ≤ widen_ivl_core a b"
proof (cases a; cases b)
fix l1 u1 l2 u2 :: eint
assume "a = Ivl l1 u1" "b = Ivl l2 u2"
then show "a ≤ widen_ivl_core a b"
unfolding less_eq_ivl_def by auto
qed
lemma b_le_widen_ivl_core: "(b :: ivl) ≤ widen_ivl_core a b"
proof (cases a; cases b)
fix l1 u1 l2 u2 :: eint
assume "a = Ivl l1 u1" "b = Ivl l2 u2"
then show "b ≤ widen_ivl_core a b"
unfolding less_eq_ivl_def
using eint_le_linear[of l1 l2] eint_le_linear[of u2 u1]
by auto
qed
text ‹
Widening termination: every widen-ascending chain stabilises. At each step
each bound either stays or jumps to @{text MinInf} / @{text PlusInf}, and once
a bound reaches the infinite end it remains there. After at most two
transitions the chain is constant.
›
lemma widen_ivl_core_terminates:
assumes "∀i. widen_ivl_core (f i) (f (Suc i)) = f (Suc i)"
shows "∃n. ∀j. n ≤ j ⟶ f j = f n"
proof -
define lb where "lb i = ivl_lower (f i)" for i
define ub where "ub i = ivl_upper (f i)" for i
have f_eq: "f i = Ivl (lb i) (ub i)" for i
unfolding lb_def ub_def by (cases "f i") simp
have step:
"lb (Suc i) = lb i ∨ lb (Suc i) = MinInf"
"ub (Suc i) = ub i ∨ ub (Suc i) = PlusInf"
for i
proof -
have "widen_ivl_core (Ivl (lb i) (ub i)) (Ivl (lb (Suc i)) (ub (Suc i))) =
Ivl (lb (Suc i)) (ub (Suc i))"
using assms[rule_format, of i] f_eq[of i] f_eq[of "Suc i"] by simp
then have eqs:
"lb (Suc i) = (if eint_le (lb i) (lb (Suc i)) then lb i else MinInf)"
"ub (Suc i) = (if eint_le (ub (Suc i)) (ub i) then ub i else PlusInf)"
by simp_all
from eqs(1) show "lb (Suc i) = lb i ∨ lb (Suc i) = MinInf"
by (auto split: if_splits)
from eqs(2) show "ub (Suc i) = ub i ∨ ub (Suc i) = PlusInf"
by (auto split: if_splits)
qed
have lb_stay: "lb i = MinInf ⟹ lb (Suc i) = MinInf" for i
using step(1) by metis
have ub_stay: "ub i = PlusInf ⟹ ub (Suc i) = PlusInf" for i
using step(2) by metis
have lb_stay_le: "lb i = MinInf ⟹ i ≤ j ⟹ lb j = MinInf" for i j
proof (induct j)
case 0 thus ?case by simp
next
case (Suc j) thus ?case using lb_stay le_SucE by metis
qed
have ub_stay_le: "ub i = PlusInf ⟹ i ≤ j ⟹ ub j = PlusInf" for i j
proof (induct j)
case 0 thus ?case by simp
next
case (Suc j) thus ?case using ub_stay le_SucE by metis
qed
have lb_const_if_no_MinInf:
"(∀k. lb k ≠ MinInf) ⟹ lb j = lb 0" for j
proof (induct j)
case 0 thus ?case by simp
next
case (Suc j) thus ?case using step(1)[of j] by auto
qed
have ub_const_if_no_PlusInf:
"(∀k. ub k ≠ PlusInf) ⟹ ub j = ub 0" for j
proof (induct j)
case 0 thus ?case by simp
next
case (Suc j) thus ?case using step(2)[of j] by auto
qed
obtain nl where nl: "∀j ≥ nl. lb j = lb nl"
proof (cases "∃k. lb k = MinInf")
case True
then obtain k where "lb k = MinInf" by blast
then have "∀j ≥ k. lb j = MinInf" using lb_stay_le by blast
then have "∀j ≥ k. lb j = lb k" using ‹lb k = MinInf› by simp
then show ?thesis by (rule that)
next
case False
then have "∀k. lb k ≠ MinInf" by blast
then have "∀j. lb j = lb 0" using lb_const_if_no_MinInf by blast
then have "∀j ≥ 0. lb j = lb 0" by blast
then show ?thesis by (rule that)
qed
obtain nu where nu: "∀j ≥ nu. ub j = ub nu"
proof (cases "∃k. ub k = PlusInf")
case True
then obtain k where "ub k = PlusInf" by blast
then have "∀j ≥ k. ub j = PlusInf" using ub_stay_le by blast
then have "∀j ≥ k. ub j = ub k" using ‹ub k = PlusInf› by simp
then show ?thesis by (rule that)
next
case False
then have "∀k. ub k ≠ PlusInf" by blast
then have "∀j. ub j = ub 0" using ub_const_if_no_PlusInf by blast
then have "∀j ≥ 0. ub j = ub 0" by blast
then show ?thesis by (rule that)
qed
let ?n = "max nl nu"
have "∀j. ?n ≤ j ⟶ f j = f ?n"
proof (intro allI impI)
fix j assume jn: "?n ≤ j"
have lb_eq: "lb j = lb ?n"
proof -
from jn have "nl ≤ j" "nl ≤ ?n" by auto
then have "lb j = lb nl" "lb ?n = lb nl"
using nl by blast+
then show ?thesis by simp
qed
have ub_eq: "ub j = ub ?n"
proof -
from jn have "nu ≤ j" "nu ≤ ?n" by auto
then have "ub j = ub nu" "ub ?n = ub nu"
using nu by blast+
then show ?thesis by simp
qed
show "f j = f ?n"
using f_eq[of j] f_eq[of ?n] lb_eq ub_eq by simp
qed
then show ?thesis by blast
qed
subsection ‹Type-class widening for TD warrowing solver›
text ‹Standard interval narrowing: when the recomputed value ‹b› refines the widened value
‹a› (‹b ≤ a›), keep ‹a›'s finite bounds and only fill an infinite bound of ‹a› from ‹b›.
A bound moves off an infinite endpoint at most once, so the narrowing descent is finite. Combined with
the backward guard filters this recovers loop bounds under ∗‹every› update rule (a bounded
local counter reads ‹[0, 20]› under ‹join›, ‹per_origin› and ‹warrow› alike). It does not
help a ∗‹flow-insensitive global›: there the guard never bounds the value written back to
the slot, so ‹[0, +inf]› is a genuine fixpoint and narrowing has nothing smaller to descend
to.›
fun narrow_ivl_td :: "ivl ⇒ ivl ⇒ ivl" where
"narrow_ivl_td (Ivl l1 u1) (Ivl l2 u2) =
Ivl (if l1 = MinInf then l2 else l1) (if u1 = PlusInf then u2 else u1)"
instantiation ivl :: warrowing begin
text ‹Widening carries the standard bot-law ‹bot ∇ x = x›, ‹x ∇ bot = x›: since
\<^term>‹bot :: ivl› is the empty interval \<^term>‹Ivl PlusInf MinInf›, an unguarded
\<^const>‹widen_ivl_core› from bot would jump straight to the top interval, topping any
unknown on its first stabilisation. The guard keeps the first contribution exact.›
definition "((a :: ivl) ∇ b) =
(if a = bot then b else if b = bot then a else widen_ivl_core a b)"
definition "((a :: ivl) Δ b) = narrow_ivl_td a b"
instance proof intro_classes
fix a b :: ivl
show "a ≤ (a ∇ b)"
proof (cases "a = bot")
case True thus ?thesis by (simp add: widen_ivl_def)
next
case False thus ?thesis
by (cases "b = bot") (simp_all add: widen_ivl_def a_le_widen_ivl_core)
qed
show "b ≤ (a ∇ b)"
proof (cases "a = bot")
case True thus ?thesis by (simp add: widen_ivl_def)
next
case False thus ?thesis
by (cases "b = bot") (simp_all add: widen_ivl_def b_le_widen_ivl_core)
qed
show "b ≤ a ⟹ b ≤ (a Δ b)"
unfolding narrow_ivl_def
by (cases a; cases b) (auto simp: less_eq_ivl_def split: if_splits)
show "b ≤ a ⟹ (a Δ b) ≤ a"
unfolding narrow_ivl_def
by (cases a; cases b) (auto simp: less_eq_ivl_def split: if_splits)
qed
end
subsection ‹Numeric domain instance›
text ‹‹numeric_domain› extends ‹executable_domain›, which includes the solver's
‹warrowing›, so the instance follows the widening and narrowing above.›
instantiation ivl :: numeric_domain begin
definition gamma_abs_ivl [simp]: "numeric_domain_class.gamma (a :: ivl) = gamma_ivl a"
definition is_empty_ivl [simp]: "is_empty (a :: ivl) = is_bottom_ivl a"
definition to_string_ivl [simp]:
"to_string (a :: ivl) = (if is_top_ivl a then sym_top else string_of_ivl a)"
instance proof intro_classes
show "numeric_domain_class.gamma (bot :: ivl) = {}"
by (simp add: gamma_ivl_bot)
next
show "numeric_domain_class.gamma (top :: ivl) = UNIV"
by (simp add: gamma_ivl_top top_ivl_def)
next
fix a b :: ivl
assume H: "a ≤ b"
show "numeric_domain_class.gamma a ⊆ numeric_domain_class.gamma b"
proof -
have "gamma_ivl a ⊆ gamma_ivl b" using H by (rule gamma_ivl_mono)
then show ?thesis by simp
qed
next
fix a :: ivl
show "is_empty a ⟷ numeric_domain_class.gamma a = {}"
by (simp add: is_bottom_ivl_correct)
qed
end
end