Theory Prediction_Utils

(***********************************************************************************
 * 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-2-Clause
 ***********************************************************************************)

section‹Useful Definitions for Analyzing Predictions›

theory
  Prediction_Utils 
imports 
  Complex_Main
begin 


paragraph‹Utilities›

definition maxlist :: ‹'a::linorder list ⇒ 'a› where 
          ‹maxlist = Max o set›

definition minlist :: ‹'a::linorder list ⇒ 'a› where 
          ‹minlist = Min o set›

lemma maxlist_is_element: ‹l ≠ [] ⟹  maxlist l ∈ set l › 
  by (metis List.finite_set comp_eq_dest_lhs eq_Max_iff maxlist_def set_empty) 
lemma minlist_is_element: ‹l ≠ [] ⟹  minlist l ∈ set l › 
  by (metis List.finite_set comp_eq_dest_lhs eq_Min_iff minlist_def set_empty) 

lemma maxlist_append_eq: ‹maxlist (xs @ [x]) = maxlist xs ∨ maxlist (xs @ [x]) = x›
proof(cases "maxlist (xs @ [x]) = x")
  case True
  then show ?thesis by blast
next
  case False
  then show ?thesis
    by (metis (no_types, lifting) List.finite_set Max.subset_imp Max_eq_iff comp_def insertE 
               list.discI list.set(1) list.simps(15) maxlist_def maxlist_is_element order_antisym 
               rotate1.simps(2) set_rotate1 set_subset_Cons singletonD)
qed
lemma minlist_append_eq: ‹minlist (xs @ [x]) = minlist xs ∨ minlist (xs @ [x]) = x›
proof(cases "minlist (xs @ [x]) = x")
  case True
  then show ?thesis by blast
next
  case False
  then show ?thesis
    by (metis (no_types, lifting) List.finite_set Min.subset_imp Min_eq_iff comp_def insertE 
               list.discI list.set(1) list.simps(15) minlist_def minlist_is_element order_antisym 
               rotate1.simps(2) set_rotate1 set_subset_Cons singletonD)
qed

lemma maxlist_cons_eq: ‹maxlist (x#xs) = maxlist xs ∨ maxlist (x#xs) = x›
proof(cases "maxlist (x#xs) = x")
  case True
  then show ?thesis by blast
next
  case False
  then show ?thesis
    by (metis (no_types, lifting) List.finite_set Max.subset_imp Max_eq_iff comp_def insertE 
               list.discI list.set(1) list.simps(15) maxlist_def maxlist_is_element order_antisym 
               set_subset_Cons singletonD)
qed
lemma minlist_cons_eq: ‹minlist (x#xs) = minlist xs ∨ minlist (x#xs) = x›
proof(cases "minlist (x#xs) = x")
  case True
  then show ?thesis by blast
next
  case False
  then show ?thesis
    by (metis (no_types, lifting) List.finite_set Min.subset_imp Min_eq_iff comp_def insertE 
               list.discI list.set(1) list.simps(15) minlist_def minlist_is_element order_antisym 
               set_subset_Cons singletonD)
qed

lemma maxlist_append_limit: assumes ‹xs ≠ []› shows ‹maxlist xs ≤ maxlist (xs @ [x])›
proof(cases "maxlist (xs @ [x]) = x")
  case True 
  then show ?thesis using assms
    by (metis List.finite_set Max_ge comp_eq_dest_lhs list.set_intros(2) maxlist_def 
              maxlist_is_element rotate1.simps(2) set_rotate1)
next
  case False
  then show ?thesis 
    by (metis dual_order.refl maxlist_append_eq)
qed 
lemma minlist_append_limit: assumes ‹xs ≠ []› shows ‹minlist (xs @ [x]) ≤ minlist xs›
proof(cases "minlist (xs @ [x]) = x")
  case True 
  then show ?thesis using assms
    by (metis List.finite_set Min_antimono comp_eq_dest_lhs minlist_def rotate1.simps(2) set_empty 
             set_rotate1 set_subset_Cons)
next
  case False
  then show ?thesis 
    by (metis dual_order.refl minlist_append_eq)
qed 

lemma maxlist_cons_limit: assumes ‹xs ≠ []› shows ‹maxlist xs ≤ maxlist (x#xs)›
proof(cases "maxlist (x#xs) = x")
  case True 
  then show ?thesis using assms
    by (metis List.finite_set Max_ge comp_eq_dest_lhs list.set_intros(2) maxlist_def 
              maxlist_is_element)
next
  case False
  then show ?thesis
    by (metis dual_order.refl maxlist_cons_eq) 
qed 
lemma minlist_cons_limit: assumes ‹xs ≠ []› shows ‹minlist (x#xs) ≤ minlist xs›
proof(cases "minlist (x#xs) = x")
  case True 
  then show ?thesis using assms
    by (metis comp_eq_dest_lhs minlist_append_limit minlist_def rotate1.simps(2) set_rotate1)
next
  case False
  then show ?thesis
    by (metis dual_order.refl minlist_cons_eq) 
qed 


paragraph‹Converting Predictions to Percentages›

definition prediction2percentage :: ‹real list ⇒ real list› where
          ‹prediction2percentage l = (let m = maxlist l in map (λ e. e / m *  100.0) l)›

lemma prediction2percentage_is_percentage:
  assumes ‹0 < maxlist l› 
  shows ‹∀ x ∈ set (prediction2percentage l). x ≤ 100.0›
proof(insert assms, induction "l" rule: rev_induct)
  case Nil
  then show ?case 
    unfolding prediction2percentage_def Let_def maxlist_def o_def 
    by(simp)
next
  case (snoc x xs)
  then show ?case 
    unfolding prediction2percentage_def Let_def maxlist_def o_def 
    by(simp add: divide_le_eq) 
qed
  
lemma prediction2percentage_id: assumes ‹maxlist p = 100› shows ‹prediction2percentage p = p›
  unfolding prediction2percentage_def 
  using assms by(simp)

lemma prediction2percentage_min_id: 
  assumes ‹0 < maxlist p› 
  shows ‹(0 ≤ minlist (prediction2percentage p))= (0 ≤ minlist p)›
proof(insert assms, induction "p" rule: rev_induct)
  case Nil
  then show ?case
  using assms unfolding prediction2percentage_def Let_def minlist_def maxlist_def o_def 
  by(simp)
next
  case (snoc x xs)
  then show ?case 
  using assms unfolding prediction2percentage_def Let_def minlist_def maxlist_def o_def 
    apply(simp,safe) 
    subgoal 
      by (metis linorder_not_le not_numeral_le_zero zero_le_divide_iff zero_le_mult_iff)
    subgoal
      by (metis linorder_not_le not_numeral_le_zero zero_le_divide_iff zero_le_mult_iff)
    subgoal by simp
    subgoal by simp 
    done
qed

paragraph‹Maximum Prediction›

definition posmax_of :: ‹'a::linorder list ⇒ (nat × 'a) option› where
          ‹posmax_of l = (let m = maxlist l in find (λ e. snd e = m) (indexed_from 0 l))›
definition pos_of_max :: ‹'a::linorder list ⇒ nat option› where
          ‹pos_of_max l = map_option fst (posmax_of l)›

definition posmax_of' :: ‹'a::linorder list ⇒ (nat × 'a) option› where
          ‹posmax_of' l = (if l = [] then None else Some ((hd o rev o (sort_key snd) o (indexed_from 0)) l))›
definition pos_of_max' :: ‹'a::linorder list ⇒ nat option› where
          ‹pos_of_max' l = map_option fst (posmax_of' l)›

paragraph‹Minimum Prediction›

definition posmin_of :: ‹'a::linorder list ⇒ (nat × 'a) option› where
          ‹posmin_of l = (let m = minlist l in find (λ e. snd e = m) (indexed_from 0 l))›
definition pos_of_min :: ‹'a::linorder list ⇒ nat option› where
          ‹pos_of_min l = map_option fst (posmin_of l)›

definition posmin_of' :: ‹'a::linorder list ⇒ (nat × 'a) option› where
          ‹posmin_of' l = (if l = [] then None else Some ((hd o rev o (sort_key snd) o (indexed_from 0)) l))›
definition pos_of_min' :: ‹'a::linorder list ⇒ nat option› where
          ‹pos_of_min' l = map_option fst (posmin_of' l)›

lemma find_append_eq: ‹find P (xs@[x]) = (if find P xs = None then find P [x] else find P xs)›
proof (induction xs)
  case Nil
  then show ?case by simp 
next
  case (Cons a xs)
  then show ?case by simp 
qed

lemma posmax_of_split: ‹posmax_of (xs @ [x]) = posmax_of (xs) ∨ posmax_of (xs @ [x]) = Some (length xs,x)› 
proof(cases "posmax_of (xs @ [x]) = Some (length xs,x)")
  case True
  then show ?thesis by simp 
next
  case False note * = this
  then show ?thesis 
    apply(simp)
    unfolding posmax_of_def Let_def o_def maxlist_def
    apply clarsimp
    apply (simp add: * maxlist_append_eq indexed_from_append_eq find_append_eq)
    apply(cases "xs=[]", simp) 
    apply (cases " find (λe. snd e = Max (insert x (set xs))) (indexed_from 0 xs)" , simp_all)
    subgoal by (metis max_def)
    subgoal using  comp_def list.simps(15) maxlist_append_eq maxlist_def rotate1.simps(2) set_rotate1 
      by (smt (verit) List.finite_set Max.in_idem indexed_from_eq_zip find_None_iff find_cong in_set_conv_nth in_set_zip max_def
          option.discI) 
    done
qed

lemma posmin_of_split: ‹posmin_of (xs @ [x]) = posmin_of (xs) ∨ posmin_of (xs @ [x]) = Some (length xs,x)› 
proof(cases "posmin_of (xs @ [x]) = Some (length xs,x)")
  case True
  then show ?thesis by simp 
next
  case False note * = this
  then show ?thesis 
  apply(simp)
    unfolding posmin_of_def Let_def o_def minlist_def
    apply (simp add: *  minlist_append_eq indexed_from_append_eq find_append_eq)
    apply (cases "xs = []")
     apply simp
    apply (cases " find (λe. snd e = Min (insert x (set xs))) (indexed_from 0 xs)", simp_all )
    subgoal
      by (metis linorder_not_le min.absorb4 min.orderE) 
    subgoal using 
       List.finite_set Min.insert Min.insert_remove Min.remove indexed_from_eq_zip
          find_None_iff find_cong in_set_conv_nth in_set_zip length_append length_map 
          list.size(3) map_nth min.orderE min_def option.simps(3) set_append set_empty 
      by (smt (verit) insort_insert_triv set_insort_insert)  
    done 
qed

lemma pos_of_max_split: 
  ‹pos_of_max (xs @ [x]) = pos_of_max (xs) ∨ pos_of_max (xs @ [x]) = Some (length xs)›
  using  pos_of_max_def posmax_of_split
  by (metis fst_eqD option.simps(9))

lemma pos_of_min_split: 
  ‹pos_of_min (xs @ [x]) = pos_of_min (xs) ∨ pos_of_min (xs @ [x]) = Some (length xs)›
  using  pos_of_min_def posmin_of_split
  by (metis fst_eqD option.simps(9))

lemma posmax_of_none: ‹(posmax_of xs = None) = (xs = []) ›
proof(induction "xs" rule:List.rev_induct)
  case Nil
  then show ?case 
    by (simp add: posmax_of_def)
next
  case (snoc x xs) note * = this
  then show ?case 
    unfolding posmax_of_def Let_def
    apply(clarsimp simp add: indexed_from_append_eq find_append_eq)
    subgoal using  maxlist_append_eq
      by (metis append_is_Nil_conv in_set_conv_decomp_last list.discI maxlist_is_element 
          not_Some_eq old.prod.exhaust self_append_conv2 set_ConsD)
    done
qed

lemma posmin_of_none: ‹(posmin_of xs = None) = (xs = []) ›
proof(induction "xs" rule:List.rev_induct)
  case Nil
  then show ?case 
    by (simp add: posmin_of_def)
next
  case (snoc x xs) note * = this
  then show ?case 
    unfolding posmin_of_def Let_def
    apply(clarsimp simp add: indexed_from_append_eq find_append_eq)[1]
    using minlist_append_eq
    by (metis append_Nil eq_snd_iff in_set_conv_nth list.distinct(1) list.size(3) minlist_is_element 
        not_less_zero option.exhaust set_ConsD)
qed

lemma posmax_of_in_snd: ‹(posmax_of xs) = Some p ⟹ snd p ∈ set xs›
  by (metis (mono_tags, lifting) List.finite_set comp_def eq_Max_iff find_Some_iff maxlist_def 
                                 option.discI posmax_of_def posmax_of_none set_empty) 

lemma posmin_of_in_snd: ‹(posmin_of xs) = Some p ⟹ snd p ∈ set xs›
  by (metis (mono_tags, lifting) find_Some_iff minlist_is_element option.discI 
                                 posmin_of_def posmin_of_none)

lemma pos_of_max_none: ‹(pos_of_max xs = None) = (xs = []) ›
  by(simp add:pos_of_max_def posmax_of_none)

lemma pos_of_min_none: ‹(pos_of_min xs = None) = (xs = []) ›
  by(simp add:pos_of_min_def posmin_of_none)

lemma take_nth_drop_eq:
  assumes ‹xs ≠ []›
  and ‹n < length xs›
shows ‹xs = ((take n xs)@[xs!n]@(drop (n+1) xs))›
  by (simp add: Cons_nth_drop_Suc assms(2))

lemma max_in: 
  assumes ‹xs ≠ []›
  and ‹n < length xs›
  and ‹∀ x ∈ set ((take n xs)@(drop (n+1) xs)). xs!n > x›
shows ‹Max (set ((take n xs)@[xs!n]@(drop (n+1) xs))) = ((take n xs)@[xs!n]@(drop (n+1) xs))!n›
  using assms 
  apply(simp)
  by (metis List.finite_set Max_insert2 id_take_nth_drop order_le_less set_append) 

lemma min_in: 
  assumes ‹xs ≠ []›
  and ‹n < length xs›
  and ‹∀ x ∈ set ((take n xs)@(drop (n+1) xs)). xs!n < x›
shows ‹Min (set ((take n xs)@[xs!n]@(drop (n+1) xs))) = ((take n xs)@[xs!n]@(drop (n+1) xs))!n›
  using assms 
  apply(simp)
  by (metis List.finite_set Min_insert2 id_take_nth_drop nless_le set_append)

lemma max_in':
  assumes ‹xs ≠ []›
  and ‹n < length xs›
  and ‹∀ x ∈ set ((take n xs)@(drop (n+1) xs)). xs!n > x›
shows ‹Max (set xs) = xs!n›
  using assms max_in take_nth_drop_eq by metis

lemma min_in':
  assumes ‹xs ≠ []›
  and ‹n < length xs›
  and ‹∀ x ∈ set ((take n xs)@(drop (n+1) xs)). xs!n < x›
shows ‹Min (set xs) = xs!n›
  using assms min_in take_nth_drop_eq by metis

lemma snd_numerate_eq: 
  "xs ≠ [] ⟹ n < length xs  ⟹ j < n ⟹ snd (indexed_from 0 xs ! j) = xs ! j"
  by (simp add: nth_indexed_from_eq) 

lemma nth_lower_max: 
  assumes ‹xs ≠ []›
  and ‹n < length xs›
  and ‹∀ x ∈ set ((take n xs)@(drop (n+1) xs)). x < xs!n›
shows ‹∀ j < n. xs!j < xs!n›
proof - 
  have  x1: ‹∀ x ∈ set ((take n xs)). x < xs!n› using assms by simp 
  then have x2: "(∀ j < n . (xs!j) ∈ set ((take n xs)))" 
    using assms(2) in_set_conv_nth by fastforce
  then show ?thesis 
    using x1 assms nth_take[of _ "n" "xs"]  by simp 
qed

lemma nth_higher_min: 
  assumes ‹xs ≠ []›
  and ‹n < length xs›
  and ‹∀ x ∈ set ((take n xs)@(drop (n+1) xs)). x > xs!n›
shows ‹∀ j < n. xs!j > xs!n›
proof - 
  have  x1: ‹∀ x ∈ set ((take n xs)). x > xs!n› using assms by simp 
  then have x2: "(∀ j < n . (xs!j) ∈ set ((take n xs)))" 
    using assms(2) in_set_conv_nth by fastforce
  then show ?thesis 
    using x1 assms nth_take[of _ "n" "xs"]  by simp 
qed

lemma posmax_of_le: 
  assumes ‹xs ≠ []›
  and ‹n < length xs›
  and ‹∀ x ∈ set ((take n xs)@(drop (n+1) xs)). x < xs!n›
shows ‹posmax_of xs = Some (n,xs!n)›
unfolding posmax_of_def maxlist_def o_def Let_def
  apply(simp add: List.find_Some_iff)  
  apply(rule exI[of _ "n"])
  apply(simp add: max_in' assms nth_indexed_from_eq)
  apply(rule conjI)
  subgoal using assms max_in' by metis  
  subgoal
    apply(simp add: assms max_in'[of "xs" "n"])      
    apply(simp add: assms snd_numerate_eq[of "xs" "n"])  
    using assms nth_lower_max[of "xs" "n"] by auto 
  done 

lemma posmin_of_le: 
  assumes ‹xs ≠ []›
  and ‹n < length xs›
  and ‹∀ x ∈ set ((take n xs)@(drop (n+1) xs)). x > xs!n›
shows ‹posmin_of xs = Some (n,xs!n)›
unfolding posmin_of_def minlist_def o_def Let_def
  apply(simp add: List.find_Some_iff)  
  apply(rule exI[of _ "n"])
  apply(simp add: min_in' assms nth_indexed_from_eq)
  apply(rule conjI)
  subgoal using assms min_in' by metis  
  subgoal
    apply(simp add: assms min_in'[of "xs" "n"])      
    apply(simp add: assms snd_numerate_eq[of "xs" "n"])  
    using assms nth_higher_min[of "xs" "n"] by auto 
  done 
     
lemma pos_max_le: 
  assumes ‹xs ≠ []›
  and ‹n < length xs›
  and ‹∀ x ∈ set ((take n xs)@(drop (n+1) xs)). x < xs!n›
shows ‹(pos_of_max xs = Some n)›
  using posmax_of_le[of "xs" "n"] assms unfolding pos_of_max_def 
  by simp 

lemma pos_min_le: 
  assumes ‹xs ≠ []›
  and ‹n < length xs›
  and ‹∀ x ∈ set ((take n xs)@(drop (n+1) xs)). x > xs!n›
shows ‹(pos_of_min xs = Some n)›
  using posmin_of_le[of "xs" "n"] assms unfolding pos_of_min_def 
  by simp 

paragraph‹Distance of Maximum Prediction to Next Highest Prediction›
definition δmin :: "real list ⇒ real" where 
          ‹δmin l = (let m = maxlist l in let m' = maxlist (remove1 m l) in ¦m - m'¦)›

lemma leq_linear_real:
  assumes b_bound: ‹(b::real) ∈ {lb..up}› 
  and is_leq_at_bounds: ‹((c1 * lb + c0 ≤ c1' * lb + c0') ∧ (c1 * up + c0 ≤ c1' * up + c0'))›
shows ‹c1 * b + c0 ≤ c1' * b + c0'›
proof(cases ‹lb ≤ up›)
  case True
  then show ?thesis 
    using assms 
    by auto (smt (verit, best) Groups.mult_ac(2) left_diff_distrib mult_right_mono) 
next
  case False
  then show ?thesis using assms by(simp)
qed
  
lemma leq_linear_real':
  assumes b_bound: ‹(b::real) ∈ {lb..up}› 
  and is_leq_at_bounds: ‹((c1 * up + c0 ≤ c1' * up + c0') ∧ (c1 * lb + c0 ≤ c1' * lb + c0'))›
shows ‹c1 * b + c0 ≤ c1' * b + c0'›
proof(cases ‹lb ≤ up›)
  case True
  then show ?thesis using assms 
    using leq_linear_real by blast
next
  case False
  then show ?thesis using assms by(simp)
qed
lemma le_linear_real:
  assumes b_bound: ‹(b::real) ∈ {lb..up}› 
  and is_leq_at_bounds: ‹((c1 * lb + c0 < c1' * lb + c0') ∧ (c1 * up + c0 < c1' * up + c0'))›
shows ‹c1 * b + c0 < c1' * b + c0'›
proof(cases ‹lb < up›)
  case True
  then show ?thesis 
    using assms 
    by auto (smt (verit, best) Groups.mult_ac(2) left_diff_distrib mult_right_mono) 
next
  case False
  then show ?thesis using assms
    by (metis atLeastAtMost_iff nless_le order_le_less_trans)
qed
  
lemma le_linear_real':
  assumes b_bound: ‹(b::real) ∈ {lb..up}› 
  and is_leq_at_bounds: ‹((c1 * up + c0 < c1' * up + c0') ∧ (c1 * lb + c0 < c1' * lb + c0'))›
shows ‹c1 * b + c0 < c1' * b + c0'›
proof(cases ‹lb ≤ up›)
  case True
  then show ?thesis using assms 
    using le_linear_real by blast
next
  case False
  then show ?thesis using assms by(simp)
qed

lemma  pos_max_leq': ‹pos_of_max xs = Some n ⟹ ∀ x ∈ set xs. x ≤ xs!n›
  by (auto simp add: pos_of_max_def posmax_of_def maxlist_def find_Some_iff nth_indexed_from_eq) 

end