Theory Interval_Utilities

(***********************************************************************************
 * Copyright (c) University of Exeter, UK
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this
 *
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 ***********************************************************************************)

chapter‹Interval Utilities›
theory
  Interval_Utilities
imports
  "HOL-Library.Interval"
  "HOL-Analysis.Analysis"
  "HOL-Library.Interval_Float"
begin

section‹Preliminaries›

lemma compact_set_of:
  fixes X::‹'a::{preorder,topological_space, ordered_euclidean_space} interval›
  shows ‹compact (set_of X)›
  by(simp add:set_of_eq compact_interval[of "lower X" "upper X"])

lemma bounded_set_of:
  fixes X::‹'a::{preorder,topological_space, ordered_euclidean_space} interval›
  shows ‹bounded (set_of X)›
  by(simp add:set_of_eq compact_interval[of "lower X" "upper X"])

lemma compact_img_set_of:
  fixes X :: ‹real interval› and f::‹real ⇒ real›
  assumes ‹continuous_on (set_of X) f›
  shows ‹compact (f ` set_of X)›
  using compact_continuous_image[of "set_of X" f, simplified assms] compact_interval
  unfolding set_of_eq
  by simp

lemma sup_inf_dist_bounded:
  fixes X::‹real set›
  shows ‹bdd_below X ⟹ bdd_above X ==>  ∀ x ∈ X. ∀ x' ∈ X. dist x  x' ≤ Sup X - Inf X›
  using cInf_lower[of _ X] cSup_upper[of _ X]
  apply(auto simp add:dist_real_def)[1]
  by (smt (z3))

lemma set_of_nonempty[simp]: ‹set_of X ≠ {}›
  by(simp add:set_of_eq)

lemma lower_in_interval[simp]:‹lower X ∈i X›
  by(simp add: in_intervalI)

lemma upper_in_interval[simp]:‹upper X ∈i X›
  by(simp add: in_intervalI)

lemma bdd_below_set_of: ‹bdd_below (set_of X)›
  by (metis atLeastAtMost_iff bdd_below.unfold set_of_eq)

lemma bdd_above_set_of: ‹bdd_above (set_of X)›
  by (metis atLeastAtMost_iff bdd_above.unfold set_of_eq)

lemma closed_set_of: ‹closed (set_of (X::real interval))›
  by (metis  closed_real_atLeastAtMost set_of_eq)

lemma set_f_nonempty: ‹f ` set_of X ≠ {}›
  apply(simp add:image_def set_of_eq)
  by fastforce

lemma interval_linorder_case_split[case_names LeftOf Including  RightOf]:
assumes ‹(upper x < c ⟹ P (x::('a::linorder interval)))›
   ‹( c ∈i x ⟹ P x)›
   ‹( c < lower x ⟹ P x)›
 shows‹ P x›
proof(insert assms, cases " upper x < c")
  case True
  then show ?thesis
    using assms(1) by blast
next
  case False
  then show ?thesis
  proof(cases "c ∈i x")
    case True
    then show ?thesis
      using assms(2) by blast
  next
    case False
    then show ?thesis
      by (meson assms(1) assms(3) in_intervalI not_le_imp_less)
  qed
qed

lemma foldl_conj_True:
‹foldl (∧) x xs = list_all (λ e. e = True)  (x#xs)›
  by(induction xs rule:rev_induct, auto)

lemma foldl_conj_set_True:
‹foldl (∧) x xs = (∀ e ∈ set (x#xs) . e = True)›
  by(induction xs rule:rev_induct, auto)


section "Interval Bounds and Set Conversion "

lemma sup_set_of:
  fixes X :: "'a::{conditionally_complete_lattice} interval"
  shows "Sup (set_of X) = upper X"
  unfolding set_of_def upper_def
  using cSup_atLeastAtMost lower_le_upper[of X]
  by (simp add: upper_def lower_def)

lemma inf_set_of:
  fixes X :: "'a::{conditionally_complete_lattice} interval"
  shows "Inf (set_of X) = lower X"
  unfolding set_of_def lower_def
  using cInf_atLeastAtMost lower_le_upper[of X]
  by (simp add: upper_def lower_def)

lemma inf_le_sup_set_of:
  fixes X :: "'a::{conditionally_complete_lattice} interval"
  shows"Inf (set_of X) ≤ Sup (set_of X)"
  using sup_set_of inf_set_of lower_le_upper
  by metis

lemma in_bounds: ‹x ∈i X ⟹ lower X ≤ x ∧ x ≤ upper X›
  by (simp add: set_of_eq)

lemma lower_bounds[simp]:
  assumes ‹L ≤ U›
  shows ‹lower (Interval(L,U)) = L ›
  using assms
  apply (simp add: lower.rep_eq)
  by (simp add: bounds_of_interval_eq_lower_upper lower_Interval upper_Interval)

lemma upper_bounds [simp]:
  assumes ‹L ≤ U›
  shows ‹upper (Interval(L,U)) = U›
  using assms
  apply (simp add: upper.rep_eq)
  by (simp add: bounds_of_interval_eq_lower_upper lower_Interval upper_Interval)

lemma lower_point_interval[simp]: ‹lower (Interval (x,x)) = x›
  by (simp)
lemma upper_point_interval[simp]: ‹upper (Interval (x,x)) = x›
  by (simp)

lemma map2_nth:
  assumes  ‹length xs = length ys›
      and      ‹n < length xs›
    shows ‹(map2 f xs ys)!n = f (xs!n) (ys!n)›
  using assms  by simp

lemma map_set: ‹a ∈ set (map f X) ⟹  (∃ x ∈ set X . f x = a)›
  by auto
lemma map_pair_set_left: ‹(a,b) ∈ set (zip (map f X) (map f Y)) ⟹ (∃ x ∈ set X . f x = a)›
  by (meson map_set set_zip_leftD)
lemma map_pair_set_right: ‹(a,b) ∈ set (zip (map f X) (map f Y)) ⟹  (∃ y ∈ set Y . f y = b)›
  by (meson map_set set_zip_rightD)
lemma map_pair_set: ‹(a,b) ∈ set (zip (map f X) (map f Y)) ⟹  (∃ x ∈ set X . f x = a) ∧  (∃ y ∈ set Y . f y = b)›
  by (meson map_pair_set_left set_zip_rightD zip_same)

lemma map_pair_f_all:
  assumes ‹length X = length Y›
  shows ‹(∀(x, y)∈set (zip (map f X) (map f Y)). x ≤ y) = (∀(x, y)∈set (zip X Y ). f x ≤ f y)›
  by(insert assms(1), induction X Y rule:list_induct2, auto)

definition   map_interval_swap :: ‹('a::linorder × 'a) list ⇒ 'a interval list› where
‹map_interval_swap = map (λ (x,y). Interval (if x ≤ y then (x,y) else (y,x)))›

definition  mk_interval :: ‹('a::linorder × 'a) ⇒ 'a interval › where
‹mk_interval = (λ (x,y). Interval (if x ≤ y then (x,y) else (y,x)))›

definition  mk_interval' :: ‹('a::linorder × 'a) ⇒ 'a interval › where
‹mk_interval' = (λ (x,y). (if x ≤ y then Interval(x,y) else Interval(y,x)))›

lemma map_interval_swap_code[code]:
  ‹ map_interval_swap = map (λ (x,y). the (if x ≤ y then Interval' x y else Interval' y x))›
  unfolding map_interval_swap_def by(rule ext, rule arg_cong, auto simp add: Interval'.abs_eq)

lemma  mk_interval_code[code]:
  ‹mk_interval = (λ (x,y). the (if x ≤ y then Interval' x y else Interval' y x))›
  unfolding mk_interval_def by(rule ext, rule arg_cong, auto simp add: Interval'.abs_eq)

lemma  mk_interval':
  ‹mk_interval = (λ (x,y). (if x ≤ y then Interval(x, y) else Interval(y, x)))›
  unfolding mk_interval_def by(rule ext, rule arg_cong, auto)

lemma mk_interval_lower[simp]: ‹ lower (mk_interval (x,y)) = (if x ≤ y then x else y)›
  by(simp add: lower_def Interval_inverse mk_interval_def)

lemma mk_interval_upper[simp]: ‹ upper (mk_interval (x,y)) = (if x ≤ y then y else x)›
  by(simp add: upper_def Interval_inverse mk_interval_def)

section‹Linear Order on List of Intervals›

definition
  le_interval_list :: ‹('a::linorder) interval list ⇒ 'a interval list ⇒ bool› (‹(_/ ≤I _)› [51, 51] 50)
  where
    ‹le_interval_list Xs Ys ≡ (length Xs = length Ys) ∧ (foldl (∧) True (map2 (≤) Xs Ys))›

lemma le_interval_single: ‹(x ≤ y) = ([x] ≤I [y])›
  unfolding le_interval_list_def by simp

lemma le_intervall_empty[simp]: ‹[] ≤I []›
  unfolding le_interval_list_def by simp

lemma le_interval_list_rev: ‹(is ≤I js) = (rev is ≤I rev js)›
  unfolding le_interval_list_def
  by(safe, simp_all add: foldl_conj_set_True zip_rev)

lemma le_interval_list_imp_length:
  assumes ‹Xs ≤I Ys› shows ‹length Xs = length Ys›
using assms unfolding le_interval_list_def
  by simp


lemma lsplit_left: assumes ‹length (xs) = length (ys)›
  and ‹(∀n<length (x # xs). (x # xs) ! n ≤ (y # ys) ! n)› shows ‹
      ((∀ n < length xs. xs!n ≤ ys!n) ∧ x ≤ y)›
  using assms  by auto

lemma lsplit_right: assumes ‹length (xs) = length (ys)›
  and  ‹((∀ n < length xs. xs!n ≤ ys!n) ∧ x ≤ y)›
shows ‹(n<length (x # xs) ⟶ (x # xs) ! n ≤ (y # ys) ! n)›
proof(cases "n=0")
  case True
  then show ?thesis using assms by(simp)
next
  case False
  then show ?thesis using assms by simp
qed

lemma lsplit: assumes ‹length (xs) = length (ys)›
  shows ‹(∀n<length (x # xs). (x # xs) ! n ≤ (y # ys) ! n) =
      ((∀ n < length xs. xs!n ≤ ys!n) ∧ x ≤ y)›
  using assms lsplit_left lsplit_right by metis

lemma le_interval_list_all':
  assumes ‹length Xs = length Ys› and ‹Xs ≤I Ys› shows ‹∀ n < length Xs. Xs!n ≤ Ys!n›
proof(insert assms, induction rule:list_induct2)
  case Nil
  then show ?case by simp
next
  case (Cons x xs y ys)
  then show ?case
    using lsplit[of xs ys x y] le_interval_list_def
    unfolding le_interval_list_def
    by(auto simp add: foldl_conj_True)
qed

lemma le_interval_list_all2:
  assumes ‹length Xs = length Ys›
    and ‹∀ n<length Xs . (Xs!n  ≤ Ys!n)›
  shows  ‹Xs ≤I Ys›
proof(insert assms, induction rule:list_induct2)
  case Nil
  then show ?case by simp
next
  case (Cons x xs y ys)
  then show ?case
    using lsplit[of xs ys x y] le_interval_list_def
    unfolding le_interval_list_def
    by(auto simp add: foldl_conj_True)
qed

lemma le_interval_list_all:
  assumes ‹Xs ≤I Ys› shows ‹∀ n < length Xs. Xs!n ≤ Ys!n›
  using assms le_interval_list_all' le_interval_list_imp_length
  by auto

lemma le_interval_list_imp:
  assumes ‹Xs ≤I Ys› shows ‹n < length Xs ⟶  Xs!n ≤ Ys!n›
  using assms le_interval_list_all' le_interval_list_imp_length
  by auto

lemma  interval_set_leq_eq: ‹(X ≤ Y) = (set_of X ⊆ set_of Y)›
  for X :: ‹'a::linordered_ring interval›
  by (simp add: less_eq_interval_def set_of_eq)

lemma times_interval_right:
  fixes X Y C ::‹'a::linordered_ring interval›
  assumes ‹X ≤ Y›
  shows ‹C * X ≤ C * Y›
  using assms[simplified interval_set_leq_eq]
  apply(subst interval_set_leq_eq)
  by (simp add: set_of_mul_inc_right)


lemma times_interval_left:
  fixes X Y C ::‹'a::{real_normed_algebra,linordered_ring,linear_continuum_topology} interval›
  assumes ‹X ≤ Y›
  shows ‹X * C ≤ Y * C›
  using assms[simplified interval_set_leq_eq]
  apply(subst interval_set_leq_eq)
  by (simp add: set_of_mul_inc_left)

section‹Support for Lists of Intervals ›

abbreviation in_interval_list::‹('a::preorder) list ⇒ 'a interval list ⇒ bool› (‹(_/ ∈I _)› [51, 51] 50)
  where ‹in_interval_list xs Xs ≡ foldl (∧) True (map2 (in_interval) xs Xs)›

lemma interval_of_in_interval_list[simp]: ‹xs ∈I map interval_of xs›
proof(induction xs)
  case Nil
  then show ?case by simp
next
  case (Cons a xs)
  then show ?case
    by (simp add: in_interval_to_interval)
qed

lemma interval_of_in_eq:  ‹interval_of x ≤ X = (x ∈i X)›
  by (simp add: less_eq_interval_def set_of_eq)

lemma interval_of_list_in:
  assumes ‹length inputs = length Inputs›
shows ‹(map interval_of inputs ≤I Inputs) = (inputs ∈I Inputs)›
unfolding le_interval_list_def
proof(insert assms, induction inputs Inputs rule:list_induct2)
  case Nil
  then show ?case by simp
next
  case (Cons x xs y ys)
  then show ?case
  proof(cases "interval_of x ≤ y")
    case True
    then show ?thesis
      using Cons by(simp add: interval_of_in_eq)
  next
    case False
    then show ?thesis using foldl_conj_True interval_of_in_eq by auto
  qed
qed

section ‹Interval Width and Arithmetic Operations›

lemma interval_width_addition:
  fixes A:: "'a::{linordered_ring} interval"
  shows ‹width (A + B) = width A + width B›
  by (simp add: width_def)

lemma interval_width_times:
  fixes a :: "'a::{linordered_ring}" and A :: "'a interval"
  shows "width (interval_of a * A) = ¦a¦ * width A"
proof -
  have "width (interval_of a * A) = upper (interval_of a * A) - lower (interval_of a * A)"
    by (simp add: width_def)
  also have "... = (if a > 0 then a * upper A - a * lower A else a * lower A - a * upper A)"
    proof -
      have "upper (interval_of a * A) = max (a * lower A) (a * upper A)"
        by (simp add: upper_times)
      moreover have "lower (interval_of a * A) = min (a * lower A) (a * upper A)"
        by (simp add: lower_times)
      ultimately show ?thesis
        by (simp add: mult_left_mono mult_left_mono_neg)
    qed
  also have "... = ¦a¦ * (upper A - lower A)"
    by (simp add: right_diff_distrib)
  finally show ?thesis
    by (simp add: width_def)
qed

lemma interval_sup_width:
  fixes X Y :: "'a::{linordered_ring, lattice} interval"
  shows "width (sup X Y) = max (upper X) (upper Y) - min (lower X) (lower Y)"
proof -
  have a0: "∀i ia. lower (sup i ia) = min (lower i::real) (lower ia)"
    by (simp add: inf_min)
  have a1: "∀i ia. upper (sup i ia) = max (upper i::real) (upper ia)"
    by (simp add: sup_max)
  show ?thesis
    using a0 a1 by (simp add: inf_min sup_max width_def)
qed

lemma width_expanded: ‹interval_of (width Y) = Interval(upper Y - lower Y, upper Y - lower Y)›
  unfolding width_def interval_of_def by simp

lemma interval_width_positive:
  fixes X :: ‹'a::{linordered_ring} interval›
  shows ‹0 ≤ width X›
  using lower_le_upper
  by (simp add: width_def)

section ‹Interval Multiplication›

lemma interval_interval_times:
‹X * Y = Interval(Min {(lower X * lower Y), (lower X * upper Y), (upper X * lower Y), (upper X * upper Y)},
                  Max {(lower X * lower Y), (lower X * upper Y), (upper X * lower Y), (upper X * upper Y)})›
  by (simp add: times_interval_def bounds_of_interval_eq_lower_upper Let_def min_def)

lemma interval_times_scalar: ‹interval_of a * A = mk_interval(a * lower A, a * upper A)›
 proof -
    have "upper (interval_of a * A) = max (a * lower A) (a * upper A)"
      by (simp add: upper_times )
    moreover have "lower (interval_of a * A) = min (a * lower A) (a * upper A)"
      by (simp add: lower_times)
    ultimately show ?thesis
      by (simp add: interval_eq_iff)
  qed

lemma interval_times_scalar_pos_l:
  fixes a :: "'a::{ordered_semiring,times,linorder}"
  assumes ‹0 ≤ a›
  shows ‹interval_of a * A = Interval(a * lower A, a * upper A)›
 proof -
   have "upper (interval_of a * A) = a * upper A"
     using assms
     by (simp add: upper_times mult_left_mono)
   moreover have "lower (interval_of a * A) = a * lower A"
     using assms
     by (simp add: lower_times mult_left_mono)
   ultimately show ?thesis
     using assms
     by (metis bounds_of_interval_eq_lower_upper bounds_of_interval_inverse lower_le_upper)
 qed

lemma interval_times_scalar_pos_r:
  fixes a :: "'a::{linordered_idom}"
  assumes ‹0 ≤ a›
  shows ‹A * interval_of a = Interval(a * lower A, a * upper A)›
 proof -
   have "upper (A * interval_of a) = a * upper A"
     using assms
     by (simp add: mult.commute mult_left_mono upper_times)
   moreover have "lower (A * interval_of a ) = a * lower A"
     using assms
     by (simp add: lower_times mult_right_mono)
   ultimately show ?thesis
     using assms
     by (metis bounds_of_interval_eq_lower_upper bounds_of_interval_inverse lower_le_upper)
 qed


section ‹Distance-based Properties of Intervals›

text ‹Given two real intervals @{term ‹X›} and @{term ‹Y›}, and two real numbers  @{term ‹a›}
and @{term ‹b›}, the width of the sum of the scaled intervals is equivalent to the width of the
two individual intervals.›

lemma width_of_scaled_interval_sum:
  fixes  X :: "'a::{linordered_ring} interval"
  shows ‹width (interval_of a * X + interval_of b * Y) = ¦a¦ * width X + ¦b¦ * width Y›
  using interval_width_addition interval_width_times by metis

lemma width_of_product_interval_bound_real:
  fixes X :: "real interval"
  shows ‹interval_of (width (X * Y)) ≤ abs_interval(X) * interval_of (width Y) + abs_interval(Y) * interval_of (width X)›
proof -
  have a0: "lower X ≤ upper X" ‹lower Y ≤ upper Y›
    using lower_le_upper by simp_all
  have a1: ‹width Y ≥ 0› and a1': ‹width X ≥ 0›
    using a0 interval_width_positive by simp_all
  have a2: ‹abs_interval(X) * interval_of (width Y) = Interval (width Y * lower (abs_interval X), width Y * upper (abs_interval X))›
    using interval_times_scalar_pos_r a0 a1 by blast
  have  a3: ‹abs_interval(Y) * interval_of (width X) = Interval (width X * lower (abs_interval Y), width X * upper (abs_interval Y))›
    using interval_times_scalar_pos_r a0 interval_width_positive by blast
  have a4: ‹abs_interval(X) * interval_of (width Y) + abs_interval(Y) * interval_of (width X) = Interval (width Y * lower (abs_interval X) + width X * lower (abs_interval Y), width Y * upper (abs_interval X) + width X * upper (abs_interval Y))›
    using a0 a2 a3 unfolding plus_interval_def
    apply (simp add:bounds_of_interval_eq_lower_upper mult_left_mono interval_width_positive)
    by (auto simp add:bounds_of_interval_eq_lower_upper mult_left_mono interval_width_positive)
  have a5: ‹width(X * Y) = Max {(lower X * lower Y), (lower X * upper Y), (upper X * lower Y), (upper X * upper Y)} - Min {(lower X * lower Y), (lower X * upper Y), (upper X * lower Y), (upper X * upper Y)}›
    using lower_times upper_times unfolding  width_def by metis
  have a6: ‹Max {(lower X * lower Y), (lower X * upper Y), (upper X * lower Y), (upper X * upper Y)} - Min {(lower X * lower Y), (lower X * upper Y), (upper X * lower Y), (upper X * upper Y)} ≤ width Y * upper (abs_interval X) + width X * upper (abs_interval Y)›
    using a0 a1 a1' unfolding width_def
    by (simp, smt (verit) a0  mult.commute mult_less_cancel_left_pos mult_minus_left right_diff_distrib)  (* takes long *)
    have a7: ‹width Y * lower (abs_interval X) + width X * lower (abs_interval Y) ≤ Max {(lower X * lower Y), (lower X * upper Y), (upper X * lower Y), (upper X * upper Y)} - Min {(lower X * lower Y), (lower X * upper Y), (upper X * lower Y), (upper X * upper Y)}›
    using a0 a1 a1' unfolding width_def
    by (simp, smt (verit) a0 mult.commute mult_less_cancel_left_pos mult_minus_left right_diff_distrib) (* takes long *)
  have ‹interval_of (Max {(lower X * lower Y), (lower X * upper Y), (upper X * lower Y), (upper X * upper Y)} -
                     Min {(lower X * lower Y), (lower X * upper Y), (upper X * lower Y), (upper X * upper Y)}) ≤
        Interval (width Y * lower (abs_interval X) + width X * lower (abs_interval Y),
                  width Y * upper (abs_interval X) + width X * upper (abs_interval Y))›
    using a6 a7 unfolding less_eq_interval_def
    by (smt (verit, ccfv_threshold) lower_bounds lower_interval_of upper_bounds upper_interval_of)
  then show ?thesis using a4 a5 a6 by presburger
qed


lemma width_of_product_interval_bound_int:
  fixes X :: "int interval"
  shows ‹interval_of (width (X * Y)) ≤ abs_interval(X) * interval_of (width Y) + abs_interval(Y) * interval_of (width X)›
proof -
  have a0: "lower X ≤ upper X" ‹lower Y ≤ upper Y›
    using lower_le_upper by simp_all
  have a1: ‹width Y ≥ 0› and ‹width X ≥ 0›
    using a0 interval_width_positive by simp_all
   have a2: ‹abs_interval(X) * interval_of (width Y) = Interval (width Y * lower (abs_interval X), width Y * upper (abs_interval X))›
    using interval_times_scalar_pos_r a0 a1 by blast
  have  a3: ‹abs_interval(Y) * interval_of (width X) = Interval (width X * lower (abs_interval Y), width X * upper (abs_interval Y))›
    using interval_times_scalar_pos_r a0 interval_width_positive by blast
  have a4: ‹abs_interval(X) * interval_of (width Y) + abs_interval(Y) * interval_of (width X) = Interval (width Y * lower (abs_interval X) + width X * lower (abs_interval Y), width Y * upper (abs_interval X) + width X * upper (abs_interval Y))›
    using a0 a2 a3 unfolding plus_interval_def
    by (auto simp add:bounds_of_interval_eq_lower_upper mult_left_mono interval_width_positive) (* takes long *)
  have a5: ‹width(X * Y) = Max {(lower X * lower Y), (lower X * upper Y), (upper X * lower Y), (upper X * upper Y)} - Min {(lower X * lower Y), (lower X * upper Y), (upper X * lower Y), (upper X * upper Y)}›
    using lower_times upper_times unfolding  width_def by metis
  have a6: ‹Max {(lower X * lower Y), (lower X * upper Y), (upper X * lower Y), (upper X * upper Y)} - Min {(lower X * lower Y), (lower X * upper Y), (upper X * lower Y), (upper X * upper Y)} ≤ width Y * upper (abs_interval X) + width X * upper (abs_interval Y)›
    using a0 unfolding width_def
    by (simp, smt (verit) a0  mult.commute mult_less_cancel_left_pos mult_minus_left right_diff_distrib)  (* takes long *)
  have a7: ‹width Y * lower (abs_interval X) + width X * lower (abs_interval Y) ≤ Max {(lower X * lower Y), (lower X * upper Y), (upper X * lower Y), (upper X * upper Y)} - Min {(lower X * lower Y), (lower X * upper Y), (upper X * lower Y), (upper X * upper Y)}›
    using a0 unfolding width_def
    apply(auto)[1]
    by (smt (verit) a0(1) a0(2) left_diff_distrib mult_less_cancel_left_pos int_distrib(3)
        mult_less_cancel_left mult_minus_right mult.commute mult_le_0_iff right_diff_distrib'
        mult_left_mono mult_le_cancel_right right_diff_distrib zero_less_mult_iff )+
  have ‹interval_of (Max {(lower X * lower Y), (lower X * upper Y), (upper X * lower Y), (upper X * upper Y)} -
                     Min {(lower X * lower Y), (lower X * upper Y), (upper X * lower Y), (upper X * upper Y)}) ≤
        Interval (width Y * lower (abs_interval X) + width X * lower (abs_interval Y),
                  width Y * upper (abs_interval X) + width X * upper (abs_interval Y))›
    using a6 a7 unfolding less_eq_interval_def
    by (smt (verit, ccfv_threshold) lower_bounds lower_interval_of upper_bounds upper_interval_of)
  then show ?thesis using a4 a5 a6 by presburger
qed

end