Theory Voblint_Analysis_Interval.Interval_Backward
theory Interval_Backward
imports Interval_Arithmetic "Voblint_Nonrelational.Exec_Backward"
"Voblint_Nonrelational.Abstract_Arithmetic" Interval_Numeric_Queries
begin
section ‹Interval backward filtering›
text ‹
The interval comparison queries, the evaluator ‹aval_ivl›, and the inverse
operators that narrow interval operands against a guard's outcome, packaged as
‹ivl_refine_ops› with its refinement certificate.
›
subsection ‹Comparison and truthiness queries›
text ‹
‹Voblint_Nonrelational.Abstract_Arithmetic›'s ‹sound_arith_ops› locale
expects three-valued ‹bool option› queries: ‹Some True›/‹Some False›
when the bound-based table decides it, ‹None› otherwise. The check layer's
\<^const>‹interval_less›/\<^const>‹interval_eq› are exactly those queries, so the
evaluator uses them too. ‹interval_tobool› is truthiness against the point
interval ‹[0,0]›.
›
lemma interval_less_unfold:
"interval_less a b =
(if interval_less_true a b then Some True
else if interval_less_false a b then Some False
else None)"
by (fact interval_numeric_queries.query_less_def)
lemma interval_eq_unfold:
"interval_eq a b =
(if interval_eq_true a b then Some True
else if interval_eq_false a b then Some False
else None)"
by (fact interval_numeric_queries.query_eq_def)
definition interval_tobool :: "ivl ⇒ bool option" where
"interval_tobool a =
(if interval_eq_false a (Ivl (Fin 0) (Fin 0)) then Some True
else if interval_eq_true a (Ivl (Fin 0) (Fin 0)) then Some False
else None)"
lemma interval_less_ivl_sound:
assumes "interval_less p q = Some b" and "i ∈ gamma_ivl p" and "j ∈ gamma_ivl q"
shows "(i < j) = b"
using assms unfolding interval_less_unfold
by (auto split: if_splits dest: interval_less_true_sound interval_less_false_sound)
lemma interval_eq_ivl_sound:
assumes "interval_eq p q = Some b" and "i ∈ gamma_ivl p" and "j ∈ gamma_ivl q"
shows "(i = j) = b"
using assms unfolding interval_eq_unfold
by (auto split: if_splits dest: interval_eq_true_sound interval_eq_false_sound)
lemma interval_tobool_sound:
assumes "interval_tobool p = Some b" and "i ∈ gamma_ivl p"
shows "truthy i = b"
proof -
have z: "(0::int) ∈ gamma_ivl (Ivl (Fin 0) (Fin 0))" by simp
show ?thesis
using assms z
unfolding interval_tobool_def truthy_def
by (auto split: if_splits dest: interval_eq_false_sound interval_eq_true_sound)
qed
lemma interval_less_mono:
assumes hp: "¬ is_empty (p1::ivl)" and hq: "¬ is_empty q1"
and hpm: "p1 ≤ p2" and hqm: "q1 ≤ q2"
and hwide: "interval_less p2 q2 = Some b"
shows "interval_less p1 q1 = Some b"
proof -
obtain l1 u1 where p1_def: "p1 = Ivl l1 u1" by (cases p1)
obtain l1' u1' where p2_def: "p2 = Ivl l1' u1'" by (cases p2)
obtain l2 u2 where q1_def: "q1 = Ivl l2 u2" by (cases q1)
obtain l2' u2' where q2_def: "q2 = Ivl l2' u2'" by (cases q2)
have ne_p1: "l1 ≤ u1" using hp unfolding p1_def is_empty_ivl is_bottom_ivl_def by auto
have ne_q1: "l2 ≤ u2" using hq unfolding q1_def is_empty_ivl is_bottom_ivl_def by auto
have bnds: "l1' ≤ l1" "u1 ≤ u1'" "l2' ≤ l2" "u2 ≤ u2'"
using hpm hqm unfolding p1_def p2_def q1_def q2_def less_eq_ivl_def by auto
have ne_p2: "l1' ≤ u1'" and ne_q2: "l2' ≤ u2'"
using bnds ne_p1 ne_q1 by order+
show ?thesis
proof (cases "interval_less_true p2 q2")
case True
then have "u1' < l2'" using p2_def q2_def ne_p2 ne_q2 by simp
then have "u1 < l2" using bnds by order
then have p1q1: "interval_less_true p1 q1" using p1_def q1_def ne_p1 ne_q1 by simp
have "interval_less p2 q2 = Some True" using True unfolding interval_less_unfold by simp
with hwide have "b = True" by simp
then show ?thesis using p1q1 unfolding interval_less_unfold by simp
next
case False
then have hlf: "interval_less_false p2 q2"
using hwide unfolding interval_less_unfold by (auto split: if_splits)
then have "u2' ≤ l1'" using p2_def q2_def ne_p2 ne_q2 by simp
then have hle: "u2 ≤ l1" using bnds by order
then have p1q1f: "interval_less_false p1 q1" using p1_def q1_def ne_p1 ne_q1 by simp
have p1q1nt: "¬ interval_less_true p1 q1"
proof
assume "interval_less_true p1 q1"
then have "u1 < l2" using p1_def q1_def ne_p1 ne_q1 by simp
with hle ne_p1 ne_q1 show False by order
qed
have "interval_less p2 q2 = Some False" using False hlf unfolding interval_less_unfold by simp
with hwide have "b = False" by simp
then show ?thesis using p1q1f p1q1nt unfolding interval_less_unfold by simp
qed
qed
lemma interval_eq_mono:
assumes hp: "¬ is_empty (p1::ivl)" and hq: "¬ is_empty q1"
and hpm: "p1 ≤ p2" and hqm: "q1 ≤ q2"
and hwide: "interval_eq p2 q2 = Some b"
shows "interval_eq p1 q1 = Some b"
proof -
obtain l1 u1 where p1_def: "p1 = Ivl l1 u1" by (cases p1)
obtain l1' u1' where p2_def: "p2 = Ivl l1' u1'" by (cases p2)
obtain l2 u2 where q1_def: "q1 = Ivl l2 u2" by (cases q1)
obtain l2' u2' where q2_def: "q2 = Ivl l2' u2'" by (cases q2)
have ne_p1: "l1 ≤ u1" using hp unfolding p1_def is_empty_ivl is_bottom_ivl_def by auto
have ne_q1: "l2 ≤ u2" using hq unfolding q1_def is_empty_ivl is_bottom_ivl_def by auto
have bnds: "l1' ≤ l1" "u1 ≤ u1'" "l2' ≤ l2" "u2 ≤ u2'"
using hpm hqm unfolding p1_def p2_def q1_def q2_def less_eq_ivl_def by auto
have ne_p2: "l1' ≤ u1'" and ne_q2: "l2' ≤ u2'"
using bnds ne_p1 ne_q1 by order+
show ?thesis
using hwide ne_p1 ne_q1 ne_p2 ne_q2 bnds
unfolding interval_eq_unfold p1_def p2_def q1_def q2_def
by (auto split: if_splits; order)
qed
lemma interval_tobool_mono:
assumes hp: "¬ is_empty (p1::ivl)" and hpm: "p1 ≤ p2"
and hwide: "interval_tobool p2 = Some b"
shows "interval_tobool p1 = Some b"
proof -
obtain l1 u1 where p1_def: "p1 = Ivl l1 u1" by (cases p1)
obtain l1' u1' where p2_def: "p2 = Ivl l1' u1'" by (cases p2)
have ne_p1: "l1 ≤ u1" using hp unfolding p1_def is_empty_ivl is_bottom_ivl_def by auto
have bnds: "l1' ≤ l1" "u1 ≤ u1'"
using hpm unfolding p1_def p2_def less_eq_ivl_def by auto
have ne_p2: "l1' ≤ u1'" using bnds ne_p1 by order
show ?thesis
using hwide ne_p1 ne_p2 bnds
unfolding interval_tobool_def p1_def p2_def
by (auto split: if_splits; order)
qed
subsection ‹Abstract expression evaluation›
text ‹
‹aval_ivl› evaluates an expression bottom-up with the interval operations above.
A comparison is ‹bot› when either operand is empty; otherwise it is the
literal its three-valued query decides, or the join of ‹0› and ‹1› on
‹None›. These are the equations ‹sound_arith_ops› expects of an evaluator.
›
fun aval_ivl :: "exp => (vname => ivl) => ivl" where
"aval_ivl (N n) d = ivl_of_int n"
| "aval_ivl (V x) d = d x"
| "aval_ivl (Plus a b) d = aval_ivl a d + aval_ivl b d"
| "aval_ivl (Minus a b) d = aval_ivl a d - aval_ivl b d"
| "aval_ivl (Times a b) d = aval_ivl a d * aval_ivl b d"
| "aval_ivl (Div a b) d = ivl_div (aval_ivl a d) (aval_ivl b d)"
| "aval_ivl (Mod a b) d = ivl_mod (aval_ivl a d) (aval_ivl b d)"
| "aval_ivl (Less a b) d =
(if is_empty (aval_ivl a d) ∨ is_empty (aval_ivl b d) then bot
else of_bool_option ivl_of_int (interval_less (aval_ivl a d) (aval_ivl b d)))"
| "aval_ivl (LessEq a b) d =
(if is_empty (aval_ivl a d) ∨ is_empty (aval_ivl b d) then bot
else of_bool_option ivl_of_int
(map_option HOL.Not (interval_less (aval_ivl b d) (aval_ivl a d))))"
| "aval_ivl (Greater a b) d =
(if is_empty (aval_ivl a d) ∨ is_empty (aval_ivl b d) then bot
else of_bool_option ivl_of_int (interval_less (aval_ivl b d) (aval_ivl a d)))"
| "aval_ivl (GreaterEq a b) d =
(if is_empty (aval_ivl a d) ∨ is_empty (aval_ivl b d) then bot
else of_bool_option ivl_of_int
(map_option HOL.Not (interval_less (aval_ivl a d) (aval_ivl b d))))"
| "aval_ivl (NotEq a b) d =
(if is_empty (aval_ivl a d) ∨ is_empty (aval_ivl b d) then bot
else of_bool_option ivl_of_int
(map_option HOL.Not (interval_eq (aval_ivl a d) (aval_ivl b d))))"
| "aval_ivl (exp.Eq a b) d =
(if is_empty (aval_ivl a d) ∨ is_empty (aval_ivl b d) then bot
else of_bool_option ivl_of_int (interval_eq (aval_ivl a d) (aval_ivl b d)))"
| "aval_ivl (exp.Not a) d =
(if is_empty (aval_ivl a d) then bot
else of_bool_option ivl_of_int (map_option HOL.Not (interval_tobool (aval_ivl a d))))"
| "aval_ivl (And a b) d =
(if is_empty (aval_ivl a d) ∨ is_empty (aval_ivl b d) then bot
else of_bool_option ivl_of_int
(and_opt (interval_tobool (aval_ivl a d)) (interval_tobool (aval_ivl b d))))"
| "aval_ivl (Or a b) d =
(if is_empty (aval_ivl a d) ∨ is_empty (aval_ivl b d) then bot
else of_bool_option ivl_of_int
(or_opt (interval_tobool (aval_ivl a d)) (interval_tobool (aval_ivl b d))))"
interpretation ivl_arith: mono_arith_ops
aval_ivl ivl_of_int "(+)" "(-)" "(*)" ivl_div ivl_mod
interval_less interval_eq interval_tobool
by unfold_locales
(simp_all add: ivl_plus_sound ivl_minus_sound ivl_times_sound ivl_div_sound ivl_mod_sound
ivl_plus_mono ivl_minus_mono ivl_times_mono ivl_div_mono ivl_mod_mono
interval_less_ivl_sound interval_eq_ivl_sound
interval_tobool_sound[unfolded truthy_def]
interval_less_mono interval_eq_mono interval_tobool_mono
sup_ivl_def)
subsection ‹Backward inverse operators›
text ‹
Inverse operators for backward analysis over intervals.
@{text inv_less_ivl} performs precise interval narrowing: on the true branch
of @{text "n1 < n2"}, the upper bound of @{text n1} tightens to one below
the upper bound of @{text n2}, and the lower bound of @{text n2} tightens to
one above the lower bound of @{text n1}. On the false branch (@{text "n1 ≥ n2"}),
@{text n1}‹s› lower bound tightens to the lower bound of @{text n2}, and @{text n2}‹s›
upper bound tightens to the upper bound of @{text n1}. Plus/minus/times
instantiate the shared @{const inv_conservative} (identity) instead of a
per-domain no-op.
›
fun inv_less_ivl :: "bool => ivl => ivl => ivl * ivl" where
"inv_less_ivl True (Ivl l1 u1) (Ivl l2 u2) =
(Ivl l1 u1 ⊓ Ivl MinInf (u2 - Fin 1),
Ivl l2 u2 ⊓ Ivl (l1 + Fin 1) PlusInf)"
| "inv_less_ivl False (Ivl l1 u1) (Ivl l2 u2) =
(Ivl l1 u1 ⊓ Ivl l2 PlusInf,
Ivl l2 u2 ⊓ Ivl MinInf u1)"
lemma inv_less_ivl_n1_ub:
"n2 ∈ gamma_ivl (Ivl l2 u2) ⟹ n1 < n2
⟹ n1 ∈ gamma_ivl (Ivl MinInf (u2 - Fin 1))"
by (cases u2; auto; linarith)
lemma inv_less_ivl_n2_lb:
"n1 ∈ gamma_ivl (Ivl l1 u1) ⟹ n1 < n2
⟹ n2 ∈ gamma_ivl (Ivl (l1 + Fin 1) PlusInf)"
by (cases l1; auto; linarith)
lemma inv_less_ivl_n1_ge_lb:
"n2 ∈ gamma_ivl (Ivl l2 u2) ⟹ ¬ n1 < n2
⟹ n1 ∈ gamma_ivl (Ivl l2 PlusInf)"
by (cases l2; auto; linarith)
lemma inv_less_ivl_n2_le_ub:
"n1 ∈ gamma_ivl (Ivl l1 u1) ⟹ ¬ n1 < n2
⟹ n2 ∈ gamma_ivl (Ivl MinInf u1)"
by (cases u1; auto; linarith)
lemma inv_less_ivl_sound:
assumes g1: "n1 ∈ gamma_ivl a1" and g2: "n2 ∈ gamma_ivl a2" and eq: "(n1 < n2) = res"
shows "n1 ∈ gamma_ivl (fst (inv_less_ivl res a1 a2))
∧ n2 ∈ gamma_ivl (snd (inv_less_ivl res a1 a2))"
proof -
obtain l1 u1 where ha1: "a1 = Ivl l1 u1" by (rule ivl_exhaustE)
obtain l2 u2 where ha2: "a2 = Ivl l2 u2" by (rule ivl_exhaustE)
show ?thesis
proof (cases res)
case True
have lt: "n1 < n2" using eq True by simp
have p1: "n1 ∈ gamma_ivl (Ivl l1 u1 ⊓ Ivl MinInf (u2 - Fin 1))"
by (rule meet_ivl_gamma[OF g1[unfolded ha1] inv_less_ivl_n1_ub[OF g2[unfolded ha2] lt]])
have p2: "n2 ∈ gamma_ivl (Ivl l2 u2 ⊓ Ivl (l1 + Fin 1) PlusInf)"
by (rule meet_ivl_gamma[OF g2[unfolded ha2] inv_less_ivl_n2_lb[OF g1[unfolded ha1] lt]])
show ?thesis using p1 p2 by (simp add: True ha1 ha2)
next
case False
have nlt: "¬ n1 < n2" using eq False by simp
have p1: "n1 ∈ gamma_ivl (Ivl l1 u1 ⊓ Ivl l2 PlusInf)"
by (rule meet_ivl_gamma[OF g1[unfolded ha1] inv_less_ivl_n1_ge_lb[OF g2[unfolded ha2] nlt]])
have p2: "n2 ∈ gamma_ivl (Ivl l2 u2 ⊓ Ivl MinInf u1)"
by (rule meet_ivl_gamma[OF g2[unfolded ha2] inv_less_ivl_n2_le_ub[OF g1[unfolded ha1] nlt]])
show ?thesis using p1 p2 by (simp add: False ha1 ha2)
qed
qed
subsection ‹Backward inverse operator for equality›
text ‹
@{text inv_eq_ivl} narrows on a guard @{text ‹e1 = e2›} known true or false.
The true branch narrows both operands to their intersection, the same
argument as the sign instance via ‹meet_ivl_gamma›. The false
branch is the sound identity: a precise refinement is possible in specific
cases (e.g. excluding a known point value from one bound of the other
operand when that point sits exactly at that bound), but @{typ ivl}'s
order makes boundary-exclusion tests incompatible with the monotonicity shape
required by the backward-domain interface: widening an operand can invalidate
a test that matched its old endpoint. The false branch therefore remains the
identity. This is a documented precision gap, not a soundness one: ‹bfilter›'s @{text
‹Eq _ _ False›} case under this instance narrows exactly as much for
Interval as it already does today (not at all), while Sign gains real
precision from its own instance.
›
fun inv_eq_ivl :: "bool => ivl => ivl => ivl * ivl" where
"inv_eq_ivl True a1 a2 = (a1 ⊓ a2, a1 ⊓ a2)"
| "inv_eq_ivl False a1 a2 = (a1, a2)"
lemma inv_eq_ivl_sound:
assumes "n1 ∈ gamma_ivl a1" and "n2 ∈ gamma_ivl a2" and "(n1 = n2) = res"
shows "n1 ∈ gamma_ivl (fst (inv_eq_ivl res a1 a2))
∧ n2 ∈ gamma_ivl (snd (inv_eq_ivl res a1 a2))"
proof (cases res)
case True
then have "n1 = n2" using assms(3) by simp
then have "n1 ∈ gamma_ivl a2" using assms(2) by simp
then have "n1 ∈ gamma_ivl (a1 ⊓ a2)" using meet_ivl_gamma[OF assms(1)] by simp
then show ?thesis using True ‹n1 = n2› by simp
next
case False
then show ?thesis using assms(1,2) by simp
qed
lemma inv_eq_ivl_mono:
assumes A1: "a1 ≤ (a1' :: ivl)" and A2: "a2 ≤ a2'"
shows
"le_pair (inv_eq_ivl r a1 a2) (inv_eq_ivl r a1' a2')"
proof (cases r)
case True
have "a1 ⊓ a2 ≤ a1' ⊓ a2'" by (rule inf_mono[OF A1 A2])
then show ?thesis using True by simp
next
case False
then show ?thesis using A1 A2 by simp
qed
lemma inv_less_ivl_mono:
assumes a1: "(a1 :: ivl) ≤ a1'" and a2: "(a2 :: ivl) ≤ a2'"
shows "le_pair (inv_less_ivl res a1 a2) (inv_less_ivl res a1' a2')"
proof -
obtain l1 u1 where ha1: "a1 = Ivl l1 u1" by (rule ivl_exhaustE)
obtain l2 u2 where ha2: "a2 = Ivl l2 u2" by (rule ivl_exhaustE)
obtain l1' u1' where ha1': "a1' = Ivl l1' u1'" by (rule ivl_exhaustE)
obtain l2' u2' where ha2': "a2' = Ivl l2' u2'" by (rule ivl_exhaustE)
from a1[unfolded ha1 ha1' less_eq_ivl_def] have ord1: "eint_le l1' l1" "eint_le u1 u1'" by auto
from a2[unfolded ha2 ha2' less_eq_ivl_def] have ord2: "eint_le l2' l2" "eint_le u2 u2'" by auto
show ?thesis
proof (cases res)
case True
then have r: "res = True" by simp
have aux1: "Ivl MinInf (u2 - Fin 1) ≤ Ivl MinInf (u2' - Fin (1::int))"
by (simp add: less_eq_ivl_def eint_minus_mono[OF ord2(2) eint_le_refl])
have aux2: "Ivl (l1 + Fin 1) PlusInf ≤ Ivl (l1' + Fin (1::int)) PlusInf"
by (simp add: less_eq_ivl_def eint_plus_mono[OF ord1(1) eint_le_refl])
show ?thesis
unfolding r ha1 ha2 ha1' ha2' inv_less_ivl.simps fst_conv snd_conv
proof (intro conjI)
show "Ivl l1 u1 ⊓ Ivl MinInf (u2 - Fin 1) ≤ Ivl l1' u1' ⊓ Ivl MinInf (u2' - Fin 1)"
by (intro inf_mono a1[unfolded ha1 ha1'] aux1)
show "Ivl l2 u2 ⊓ Ivl (l1 + Fin 1) PlusInf ≤ Ivl l2' u2' ⊓ Ivl (l1' + Fin 1) PlusInf"
by (intro inf_mono a2[unfolded ha2 ha2'] aux2)
qed
next
case False
then have r: "res = False" by simp
have aux3: "Ivl l2 PlusInf ≤ Ivl l2' PlusInf"
by (simp add: less_eq_ivl_def ord2(1))
have aux4: "Ivl MinInf u1 ≤ Ivl MinInf u1'"
by (simp add: less_eq_ivl_def ord1(2))
show ?thesis
unfolding r ha1 ha2 ha1' ha2' inv_less_ivl.simps fst_conv snd_conv
proof (intro conjI)
show "Ivl l1 u1 ⊓ Ivl l2 PlusInf ≤ Ivl l1' u1' ⊓ Ivl l2' PlusInf"
by (intro inf_mono a1[unfolded ha1 ha1'] aux3)
show "Ivl l2 u2 ⊓ Ivl MinInf u1 ≤ Ivl l2' u2' ⊓ Ivl MinInf u1'"
by (intro inf_mono a2[unfolded ha2 ha2'] aux4)
qed
qed
qed
subsection ‹The refinement operations and their certificate›
text ‹
Interval's refinement choices: the normalising intersection, the
bound-tightening comparison inverses, and the conservative identity for
arithmetic. The guard filters and the branch are derived from this record and
the evaluator in ‹Interval_Transfer›.
The intersection is \<^const>‹intersect_ivl›, not the lattice \<^const>‹inf›. The
certificate only requires it to preserve concretizations, to be reductive in
both arguments, and to be monotone --- never that it is the greatest lower
bound of the representation order --- and the normalising variant additionally
keeps every filtered state canonical, so an infeasible guard stores
\<^const>‹bot› instead of a reversed bound pair. \<^const>‹inf› itself cannot be
normalised without losing the greatest-lower-bound law;
@{thm [source] meet_ivl_normalized_breaks_greatest} is that counterexample.
›
definition ivl_refine_ops :: "ivl refine_ops" where
"ivl_refine_ops =
⦇r_tobool = interval_tobool, r_inv_less = inv_less_ivl, r_inv_eq = inv_eq_ivl,
r_inv_plus = inv_conservative, r_inv_minus = inv_conservative,
r_inv_times = inv_conservative, r_intersect = intersect_ivl⦈"
lemma ivl_refine_ops_simps [simp]:
"r_tobool ivl_refine_ops = interval_tobool"
"r_inv_less ivl_refine_ops = inv_less_ivl"
"r_inv_eq ivl_refine_ops = inv_eq_ivl"
"r_inv_plus ivl_refine_ops = inv_conservative"
"r_inv_minus ivl_refine_ops = inv_conservative"
"r_inv_times ivl_refine_ops = inv_conservative"
"r_intersect ivl_refine_ops = intersect_ivl"
by (simp_all add: ivl_refine_ops_def)
text ‹
The certificate discharges soundness, monotonicity, and reductiveness together
against @{locale mono_refinement}; each ‹inv_*›'s mono/reductive obligation
is one @{const le_pair} fact, built from the componentwise per-operator lemmas
above.
›
lemma ivl_backward_domain:
"mono_refinement intersect_ivl aval_ivl interval_tobool
inv_less_ivl inv_eq_ivl inv_conservative inv_conservative inv_conservative"
proof unfold_locales
fix n :: int and a b :: ivl
assume "n ∈ γ a" and "n ∈ γ b"
then have "n ∈ gamma_ivl a" and "n ∈ gamma_ivl b" by simp_all
then show "n ∈ γ (intersect_ivl a b)" using intersect_ivl_gamma by simp
qed (simp_all add: inv_less_ivl_sound inv_eq_ivl_sound
interval_tobool_sound[unfolded truthy_def] intersect_ivl_mono ivl_arith.aval_dom_mono
inv_less_ivl_mono inv_eq_ivl_mono inv_conservative_def
intersect_ivl_le1 intersect_ivl_le2 interval_tobool_mono
del: intersect_ivl_def)
end