(* 
   note that IMP2 is not part of the Isabelle distribution,
   but of the "Archive of Formal Proofs (AFP)";
   
   so to run this theory, 
   - download the AFP from https://www.isa-afp.org
   - extract it, e.g., to ~/afp/2025
   - start Isabelle with 
    
     isabelle jedit -d ~/afp/2025/thys -l IMP2 Demo06.thy 

     this will precompile IMP2 once, and then load the demo.
*)
theory Demo06
  imports IMP2.IMP2
begin

fun fact_int :: "int \<Rightarrow> int" where
  "fact_int x = (if x < 0 then undefined else fact (nat x))" 

lemma nat_decr[simp]: "x > 0 \<Longrightarrow> nat x = Suc (nat (x - 1))" by auto

fun binom_int :: "int \<Rightarrow> int \<Rightarrow> int" where
  "binom_int n k = (if n \<ge> 0 \<and> k \<ge> 0 then int (nat n choose nat k) else undefined)" 

(* example with invariant that is not strong enough *)
procedure_spec (partial) fact_prog(x) 
  returns y 
  assumes \<open>x \<ge> 0\<close> 
    ensures "y = fact_int x\<^sub>0"
  defines \<open>
    y = 1;
    while (x > 0) 
      @invariant \<open>y * fact_int x = fact_int x\<^sub>0\<close>
    {
      y = y * x;
      x = x - 1
    }
  \<close>
  apply vcg
    apply simp
   apply simp
  oops (* we have to abort attempt *)


procedure_spec (partial) fact_prog(x) 
  returns y 
  assumes \<open>x \<ge> 0\<close> 
    ensures "y = fact_int x\<^sub>0"
  defines \<open>
    y = 1;
    while (x > 0) 
    @invariant \<open>y * fact_int x = fact_int x\<^sub>0 \<and> x \<ge> 0\<close>
    {
      y = y * x;
      x = x - 1
    }
  \<close>
  apply vcg
    apply simp
   apply simp
  apply simp
  done


procedure_spec (partial) binom_prog(n,k) 
  returns r
  assumes \<open>n \<ge> 0 \<and> k \<ge> 0 \<and> n \<ge> k\<close> 
    ensures "r = binom_int n\<^sub>0 k\<^sub>0"
  defines \<open>
    a = fact_prog(n);
    b = fact_prog(k);
    c = fact_prog (n - k);
    r = a / (b * c)
  \<close>
  apply vcg
  subgoal by simp (* precond fact *)
  subgoal by simp (* precond fact *)
  subgoal by simp (* precond fact *)
  subgoal for n k a b c unfolding binom_int.simps
    apply (subst binomial_fact')
    subgoal by linarith
    subgoal apply simp sledgehammer
      by (metis nat_diff_distrib' of_nat_fact of_nat_mult zdiv_int)
    done
  done


definition S :: "(int \<Rightarrow> int) \<Rightarrow> int \<Rightarrow> int \<Rightarrow> int" where
  "S a i j = sum a {i .. j}"

definition Inv1 :: "(int \<Rightarrow> int) \<Rightarrow> int \<Rightarrow> int \<Rightarrow> bool" where
  "Inv1 a s k = (\<forall> i j. 0 \<le> i \<longrightarrow> i \<le> j \<longrightarrow> j < k \<longrightarrow> s \<le> S a i j)" 

definition Inv2 :: "(int \<Rightarrow> int) \<Rightarrow> int \<Rightarrow> int \<Rightarrow> bool" where
  "Inv2 a t k = (\<forall> i. 0 \<le> i \<longrightarrow> i < k \<longrightarrow> t \<le> S a i (k - 1))" 

(* prepare algorithm: language does not have any built-in min-operation,
   so, we program it *)
procedure_spec min_alg (a,b) 
  returns a
  assumes \<open>True\<close> 
    ensures \<open>a = min a\<^sub>0 b\<^sub>0\<close> 
  defines \<open>if (b < a) {a = b}\<close>
  by vcg_cs

procedure_spec (partial) minsum_section(a,n) 
  returns s 
  assumes "n > 0" 
    ensures "Inv1 a\<^sub>0 s n\<^sub>0"
  defines \<open>
    k = 1;
    t = a[0];
    s = a[0];
    while (k \<noteq> n) 
    @invariant \<open>Inv1 a\<^sub>0 s k \<and> Inv2 a\<^sub>0 t k\<close>
    {
      t = min_alg (t + a[k], a[k]);
      s = min_alg (s, t);
      k = k + 1
    }
  \<close>
  apply vcg_cs
  subgoal for n n0 
  proof (auto simp add: Inv1_def Inv2_def S_def, goal_cases)
    case (1 i j)
    hence "i = 0" "j = 0" by linarith+
    thus ?case by auto
  next
    case (2 i)
    hence "i = 0" by linarith
    thus ?case by auto
  qed
  subgoal for a n k s t
  (* note that min_alg is already turned into min *) 
  proof goal_cases
    case 1
    {
      fix i
      assume "0 \<le> i" "i < k + 1" 
      have "min (t + a k) (a k) \<le> S a i k" 
      proof (cases "i < k")
        case True
        hence "{i..k} = {i .. k - 1} \<union> {k}" by auto
        hence "S a i k = S a i (k - 1) + a k" unfolding S_def by auto
        moreover from \<open>Inv2 a t k\<close> have "t \<le> S a i (k - 1)" 
          using True \<open>0 \<le> i\<close> unfolding Inv2_def by auto
        ultimately show ?thesis by auto
      next
        case False
        with \<open>i < k + 1\<close> have i: "i = k" by auto
        thus ?thesis unfolding S_def by auto
      qed
    }
    hence Inv2: "Inv2 a (min (t + a k) (a k)) (k + 1)" unfolding Inv2_def by auto
    {
      fix i j
      assume *: "0 \<le> i" "i \<le> j" "j < k + 1" 
      have "min s (min (t + a k) (a k)) \<le> S a i j" 
      proof (cases "j < k")
        case True
        with \<open>Inv1 a s k\<close>[unfolded Inv1_def, rule_format, OF *(1-2) True]
        show ?thesis by auto
      next
        case False
        with * have "j = k" by auto
        with Inv2[unfolded Inv2_def, rule_format, of i] * 
        show ?thesis by auto
      qed
    }
    hence Inv1: "Inv1 a (min s (min (t + a k) (a k))) (k + 1)" unfolding Inv1_def by auto
    from Inv1 Inv2 show ?thesis by auto
  qed
  done


procedure_spec (partial) fact_partial_prog(x) 
  returns y 
  assumes \<open>x \<ge> 0\<close> 
    ensures "y = fact_int x\<^sub>0"
  defines \<open>
    y = 1;
    z = 0;
    while (x \<noteq> z) 
    @invariant \<open>y = fact_int z \<and> 0 \<le> z\<close>
    {
      z = z + 1;
      y = y * z
    }
  \<close>
  apply vcg
    apply simp
   apply simp
  apply simp
  done

text \<open>Slight deviation to PV-lecture w.r.t. how termination is proved: 
  here we need to enlarge invariant for successful termination proof\<close>
procedure_spec fact_total_prog(x) 
  returns y 
  assumes \<open>x \<ge> 0\<close> 
    ensures "y = fact_int x\<^sub>0"
  defines \<open>
    y = 1;
    z = 0;
    while (x \<noteq> z) 
    @invariant \<open>y = fact_int z \<and> 0 \<le> z \<and> z \<le> x\<close>
    @variant \<open>x - z\<close>
    {
      z = z + 1;
      y = y * z
    }
  \<close>
  apply vcg
      apply simp
     apply simp
    apply simp
   apply simp
  apply simp
  done


end