Theory Linear_Programming

theory Linear_Programming
  imports
    "HOL-Library.Code_Target_Int"
    "LP_Preliminaries"
    Farkas.Simplex_for_Reals 
begin


section ‹ Abstract LPs ›

(* A b c, where A leq b are the constraints and c is the objective function *)

text ‹ Primal Problem ›
definition "sat_primal A b = { x. [A *v x]≤b }"

text ‹ Dual Problem ›
definition "sat_dual A c  = {y. [y v* A]=c  (i<dim_vec y. y $ i  0)}"

definition "optimal_set f S = {x  S. (y S. f x y)}"

abbreviation max_lp where
  "max_lp A b c  optimal_set (λx y. (y  c)  (x  c)) (sat_primal A b)"

abbreviation min_lp where
  "min_lp A b c  optimal_set (λx y. (y  c)  (x  c)) (sat_dual A c)"


lemma optimal_setI[intro]:
  assumes "x  S"
  assumes "y. y  S  (λx y. (y  c)  (x  c)) x y"
  shows "x  optimal_set (λx y. (y  c)  (x  c)) S"
  unfolding optimal_set_def using assms 
  by blast

lemma max_lpI [intro]:
  assumes "[A *v x]≤b"
  assumes "(y. [A *v y]≤b  (λx y. (y  c)  (x  c)) y x)"
  shows "x  max_lp A b c"
  using optimal_setI[of x "{ x. [A *v x]≤b }" c] 
  unfolding optimal_set_def optimal_setI 
  by (simp add: assms(1) assms(2) sat_primal_def)

lemma min_lpI [intro]:
  assumes "[y v* A]=c"
    and "(i. i<dim_vec y  y $ i  0)"
  assumes "(x. x  sat_dual A c  (λx y. (y  c)  (x  c)) y x)"
  shows "y  min_lp A b c"
  using optimal_setI[of y "sat_dual A c" c] 
  unfolding optimal_set_def optimal_setI sat_dual_def
  by (simp add: assms(1) assms(2) assms(3) sat_dual_def)

lemma sat_primalD [dest]:
  assumes "x  sat_primal A b"
  shows "[A *v x]≤b"
  using assms sat_primal_def by force

lemma sat_primalI [intro]:
  assumes "[A *v x]≤b"
  shows "x  sat_primal A b"
  using assms sat_primal_def by force

lemma sat_dualD [dest]:
  assumes "y  sat_dual A c"
  shows "[y v* A]=c" "(i<dim_vec y. y $ i  0)"
  using assms sat_dual_def apply force
  using assms sat_dual_def by force

lemma sat_dualI [intro]:
  assumes "[y v* A]=c" "(i<dim_vec y. y $ i  0)"
  shows "y  sat_dual A c"
  using assms sat_dual_def by auto

lemma sol_dim_in_sat_primal: "x  sat_primal A b  dim_vec x = dim_col A"
  unfolding mat_times_vec_leq_def by (simp add: mat_times_vec_leq_def sat_primal_def)

lemma sol_dim_in_max_lp: "x  max_lp A b c  dim_vec x = dim_col A"
  unfolding optimal_set_def using sol_dim_in_sat_primal[of x A b] by blast

lemma sol_dim_in_sat_dual: "x  sat_dual A c  dim_vec x = dim_row A"
  unfolding mat_times_vec_leq_def by (simp add: sat_dual_def vec_times_mat_eq_def)

lemma sol_dim_in_min_lp: "x  min_lp A b c  dim_vec x = dim_row A"
  unfolding optimal_set_def using sol_dim_in_sat_dual[of x A] by blast

lemma min_lp_in_sat_dual: "x  min_lp A b c  x  sat_dual A c"
  unfolding optimal_set_def using sol_dim_in_sat_dual[of x A] by blast

lemma max_lp_in_sat_primal: "x  max_lp A b c  x  sat_primal A b"
  unfolding optimal_set_def using sol_dim_in_sat_dual[of x A] by blast


locale abstract_LP =
  fixes A :: "('a::{comm_semiring_1,ordered_semiring,linorder}) mat"
  fixes b :: "'a vec"
  fixes c :: "'a vec"
  fixes m
  fixes n
  assumes "b  carrier_vec m"
  assumes "c  carrier_vec n"
  assumes "A  carrier_mat m n"
begin

lemma dim_b_row_A: "dim_vec b = dim_row A"
  using abstract_LP_axioms abstract_LP_def carrier_matD(1) carrier_vecD
  by metis

lemma dim_b_col_A: "dim_vec c = dim_col A"
  using abstract_LP_axioms abstract_LP_def carrier_matD(2) carrier_vecD 
  by metis

lemma weak_duality_aux:
  fixes i j
  assumes "i  {c  x |x. x  sat_primal A b}"
    and "j  {b  y |y. y  sat_dual A c}" 
  shows "i  j"
proof -
  obtain x where x: "i = c  x" "[A *v x]≤b"
    using assms by blast
  obtain y where y: "j = b  y" "[y v* A]=c" "(i<dim_vec y. 0  y $ i)"
    using assms by blast
  have d1: "dim_vec x = n" using mat_times_vec_leq_def[of A x b] x
    by (metis abstract_LP_axioms abstract_LP_def carrier_matD(2))
  have d2: "dim_vec y = m"
    by (metis abstract_LP_axioms abstract_LP_def carrier_matD(1) index_transpose_mat(3) vec_times_mat_eq_def y(2))
  have "i = c  x" using x by auto
  also have "... = (AT *v y)  x"
    using mult_right_eq carrier_vecD y abstract_LP_def 
    by (metis abstract_LP_axioms calculation d1)
  also have "... = (A *v x)  y" 
    using assoc_scalar_prod_mult_mat_vec[symmetric, of y m x n A] abstract_LP_axioms abstract_LP_def d1 d2 
      carrier_vec_dim_vec by blast
  also have "...  b  y"
    using mult_right_leq
    by (metis index_transpose_mat(3) mat_times_vec_leq_def vec_times_mat_eq_def x(2) y(2) y(3))
  also have "... = j" using y by simp
  finally show "i  j" .
qed

theorem weak_duality_theorem:
  assumes "x  max_lp A b c"
  assumes "y  min_lp A b c"
  shows "x  c  y  b"
proof -
  define i where i: "i = x  c"
  define j where j: "j = y  b"
  have dx: "dim_vec x = n"
    using sol_dim_in_max_lp[of x c A b, OF assms(1)] abstract_LP_axioms abstract_LP_def 
      carrier_matD(2) by blast
  have dy: "dim_vec y = m"
    using sol_dim_in_min_lp[of y c A, OF assms(2)]  abstract_LP_axioms abstract_LP_def 
      carrier_matD(1) by blast
  have *: "i  {c  x |x. [A *v x]≤b}" using assms(1) unfolding optimal_set_def dx sat_primal_def
    using abstract_LP_axioms abstract_LP_def carrier_vec_dim_vec comm_scalar_prod dx i by blast
  have **: "j  {b  y |y. [y v* A]=c  (i < dim_vec y. y$i  0)}"
    using assms(2) unfolding optimal_set_def using abstract_LP_axioms abstract_LP_def 
      carrier_vec_dim_vec comm_scalar_prod dy j by blast
  from weak_duality_aux[of i j] have "i  j" unfolding sat_primal_def sat_dual_def
    using "*" "**" by blast
  then show ?thesis using i j by auto
qed

end

fun create_optimal_solutions where
  "create_optimal_solutions A b c = 
        (case simplex (x_times_c_geq_y_times_b c b #
                                      mat_leqb_eqc A b c @
                                      from_index_geq0_vector (dim_vec c) (0v (dim_vec b)))
                                    of
                                      Unsat X  Unsat X
                                      | Sat X  Sat X)"

fun optimize_no_cond where "optimize_no_cond A b c = (case create_optimal_solutions A b c of 
                          Unsat X  Unsat X 
                        | Sat X   Sat (fst (split_n_m_x (dim_vec c) (dim_vec b) X)))"

lemma create_opt_sol_satisfies:
  assumes "create_optimal_solutions A b c = Sat X"
  shows "X cs set ((x_times_c_geq_y_times_b c b # mat_leqb_eqc A b c @
                      from_index_geq0_vector (dim_vec c) (0v (dim_vec b))))"
proof -
  have "simplex (x_times_c_geq_y_times_b c b # mat_leqb_eqc A b c @
        from_index_geq0_vector (dim_vec c) (0v (dim_vec b))) = Sat X"
  proof (rule ccontr)
    assume "simplex (x_times_c_geq_y_times_b c b # mat_leqb_eqc A b c @ from_index_geq0_vector (dim_vec c) (0v (dim_vec b)))  Inr X"
    then have "e. simplex (x_times_c_geq_y_times_b c b # mat_leqb_eqc A b c @ from_index_geq0_vector (dim_vec c) (0v (dim_vec b))) = Unsat e"
      by (metis assms create_optimal_solutions.simps sum.case(2) sumE)
    then have "e. create_optimal_solutions A b c = Unsat e"
      using assms option.split by force
    then show False using assms(1) assms by auto
  qed
  then show ?thesis using simplex(3) by blast
qed

lemma create_opt_sol_sat_leq_mat:
  assumes "dim_vec b = dim_row A"
  assumes "create_optimal_solutions A b c = Sat X"
    and "(x, y) = split_i_j_x (dim_col A) (dim_vec b) X"
  shows "[A *v x]≤b"
proof -
  have *: "X cs set (mat_leqb_eqc A b c)"
    using create_opt_sol_satisfies[of A b c X] sat_mono[of "(mat_leqb_eqc A b c)" _ X]
    using assms(2) by (metis append_Cons append_assoc in_set_conv_decomp)
  then show ?thesis using mat_leqb_eqc_split_correct1[of b A c X x y, OF assms(1) *] assms
    by blast
qed

lemma create_opt_sol_sat_eq_mat:
  assumes "dim_vec c = dim_row AT" 
    and "dim_vec b = dim_col AT"
  assumes "create_optimal_solutions A b c = Sat X"
    and "(x, y) = split_i_j_x (dim_vec c) (dim_vec c + dim_vec b) X"
  shows "[y v* A]=c"
proof -
  have *: "X cs set (mat_leqb_eqc A b c)"
    using create_opt_sol_satisfies[of A b c X] sat_mono[of "(mat_leqb_eqc A b c)" _ X]
      assms(2) assms by (metis UnCI list.set_intros(2) set_append)
  have "dim_row AT = dim_vec c"
    using assms(1) by linarith
  moreover have "dim_col AT = dim_vec b"
    by (simp add: assms(2))
  ultimately show ?thesis
    using assms by (metis mat_leqb_eqc_split_correct2[of c A b X x y, OF assms(1) assms(2) *]
        dim_vec b = dim_col AT dim_vec c = dim_row AT)
qed 

lemma create_opt_sol_satisfies_leq:
  assumes "create_optimal_solutions A b c = Sat X"
  assumes "(x, y) = split_n_m_x (dim_vec c) (dim_vec b) X"
  shows "x  c  y  b"
  using create_opt_sol_satisfies[of A b c X]
  by (metis assms(1) assms(2) carrier_vec_dim_vec comm_scalar_prod list.set_intros(1) 
      split_n_m_x_abbrev_dims(2) split_vec_dims(1) x_times_c_geq_y_times_b_split_dotP)

lemma create_opt_sol_satisfies_geq0:
  assumes "create_optimal_solutions A b c = Sat X"
  assumes "(x, y) = split_n_m_x (dim_vec c) (dim_vec b) X"
  shows "i. i < dim_vec y  y$i  0"
proof -
  fix i
  assume "i < dim_vec y"
  have *: "X cs set (from_index_geq0_vector (dim_vec c) (0v (dim_vec b)))"
    using assms(1) create_opt_sol_satisfies by (metis UnCI append_Cons set_append)
  have **: "i < dim_vec b"
    by (metis i < dim_vec y assms(2) split_n_m_x_abbrev_dims(2))
  then show "0  y $ i" 
    using from_index_geq0_vector_split_snd[of "dim_vec c" "0v (dim_vec b)" X x y  
        "dim_vec b" i, OF * assms(2)] by simp
qed

locale rat_LP = abstract_LP A b c m n 
  for A ::"rat mat" 
    and b :: "rat vec" 
    and c :: "rat vec"
    and m  :: "nat"
    and n  :: "nat"
begin

lemma create_opt_sol_in_LP:
  assumes "create_optimal_solutions A b c = Sat X"
  assumes "(x, y) = split_n_m_x (dim_vec c) (dim_vec b) X"
  shows "[A *v x]≤b" "[y v* A]=c" "x  c  y  b" "i. i < dim_vec y  y$i  0"
  apply (metis Pair_inject assms(1) assms(2) create_opt_sol_sat_leq_mat dim_b_col_A dim_b_row_A split_i_j_x_def)
  using assms(1) assms(2) create_opt_sol_sat_eq_mat dim_b_col_A dim_b_row_A
    apply (metis index_transpose_mat(2) index_transpose_mat(3))
  using assms(1) assms(2) create_opt_sol_satisfies_leq apply blast
  using assms(1) assms(2) create_opt_sol_satisfies_geq0 by blast


lemma create_optim_in_sols:
  assumes "create_optimal_solutions A b c = Sat X"
  assumes "(x, y) = split_n_m_x (dim_vec c) (dim_vec b) X"
  shows "c  x  {c  x |x. [A *v x]≤b}"
    "b  y  {b  y |y. [y v* A]=c  (i < dim_vec y. y$i  0)}"
  using assms(1) assms(2) create_opt_sol_in_LP(1) apply blast
  using assms(1) assms(2) create_opt_sol_in_LP(2) create_opt_sol_in_LP(4) by blast

lemma cx_leq_bx_in_creating_opt:
  assumes "create_optimal_solutions A b c = Sat X"
  assumes "(x, y) = split_n_m_x (dim_vec c) (dim_vec b) X"
  shows "c  x  b  y"
  using weak_duality_aux[of "c  x" "b  y"]  create_optim_in_sols[of X x y, OF assms] by auto

lemma min_max_for_sol:
  assumes "create_optimal_solutions A b c = Sat X"
  assumes "(x, y) = split_n_m_x (dim_vec c) (dim_vec b) X"
  shows "c  x = b  y"
  using create_opt_sol_in_LP(3)[of X x y, OF assms] cx_leq_bx_in_creating_opt[OF assms]
    comm_scalar_prod[of c "dim_vec c" x] comm_scalar_prod[of b "dim_vec b" y]
  by (metis add_diff_cancel_left' antisym assms(2) carrier_vec_dim_vec split_vec_dims(1) split_vec_dims(2))

lemma create_opt_solutions_correct:
  assumes "create_optimal_solutions A b c = Sat X"
  assumes "(x, y) = split_n_m_x (dim_vec c) (dim_vec b) X"
  shows "x  max_lp A b c"
proof 
  show "[A *v x]≤b"
    using assms(1) assms(2) create_opt_sol_in_LP(1) by blast
  fix z
  assume a: "[A *v z]≤b"
  have 1: "c  z  {c  x |x. x  sat_primal A b}"
    using sat_primalI[of A z b, OF a] by blast
  have 2: "b  y  {b  y |y. y  sat_dual A c}"
    using sat_dualI
    by (metis (mono_tags, lifting) assms(1) assms(2) create_opt_sol_in_LP(2)
        mem_Collect_eq rat_LP.create_opt_sol_in_LP(4) rat_LP_axioms)
  then have "c  z  b  y"
    using weak_duality_aux[of "c  z" "b  y", OF 1 2] sat_primalI[of A z b, OF a] by blast      
  also have "... = x  c"
    by (metis assms(1) assms(2) carrier_vec_dim_vec comm_scalar_prod 
        min_max_for_sol split_n_m_x_abbrev_dims(1))
  finally show "z  c  x  c"
    by (metis a carrier_vec_dim_vec comm_scalar_prod dim_b_col_A mat_times_vec_leqD(2))
qed

lemma optimize_no_cond_correct:
  assumes "optimize_no_cond A b c = Sat x"
  shows "x  max_lp A b c"
proof -
  obtain X where X: "create_optimal_solutions A b c = Sat X"
    by (metis Inr_Inl_False assms old.sum.exhaust old.sum.simps(5) optimize_no_cond.simps)
  have "x = (fst (split_n_m_x (dim_vec c) (dim_vec b) X))"
    using X assms by (metis old.sum.inject(2) old.sum.simps(6) optimize_no_cond.simps)
  then show ?thesis
    using create_opt_solutions_correct[of X x] by (metis X fst_conv old.prod.exhaust)
qed

lemma optimize_no_cond_sol_sat:
  assumes "optimize_no_cond A b c = Sat x"
  shows "x  sat_primal A b"
  using max_lp_in_sat_primal[OF optimize_no_cond_correct[OF assms]] by auto
  

end (* end of ‹ rat_LP › *)


fun maximize where 
  "maximize A b c = (if dim_vec b = dim_row A  dim_vec c = dim_col A then
                      Some (optimize_no_cond A b c)
                    else None)"

lemma optimize_sound: 
  assumes "maximize A b c = Some (Sat x)"
  shows "x  max_lp A b c"
proof -
  have *: "dim_vec b = dim_row A  dim_vec c = dim_col A"
    by (metis assms maximize.simps option.distinct(1))
  then interpret rat: rat_LP A b c "dim_vec b" "dim_vec c"
    by (metis abstract_LP_def carrier_mat_triv carrier_vec_dim_vec rat_LP.intro)
  have "Sat x = optimize_no_cond A b c"
    using assms * by auto
  then show ?thesis
    by (simp add: rat.optimize_no_cond_correct)
qed

lemma maximize_option_elim:
  assumes "maximize A b c = Some x"
  shows "dim_vec b = dim_row A" "dim_vec c = dim_col A"
  by (metis assms maximize.simps option.distinct(1))+

lemma optimize_sol_dimension: 
  assumes "maximize A b c = Some (Sat x)"
  shows "x  carrier_vec (dim_col A)"
  using assms carrier_dim_vec max_lp_in_sat_primal optimize_sound sol_dim_in_sat_primal by blast

lemma optimize_sat:
  assumes "maximize A b c = Some (Sat x)"
  shows "[A *v x]≤b"
  using assms maximize_option_elim[OF assms]
    max_lp_in_sat_primal[OF optimize_sound[of A b c x, OF assms]] by blast


derive (eq) ceq rat
derive (linorder) compare rat
derive (compare) ccompare rat
derive (rbt) set_impl rat

derive (eq) ceq atom QDelta
derive (linorder) compare_order QDelta
derive compare_order atom
derive ccompare atom QDelta
derive (rbt) set_impl atom QDelta


(*export_code maximize mat_of_cols_list mat_of_rows_list mat_of_cols vec_of_list quotient_of Inr Inl Some None list_of_vec nat
Rat.Fract int_of_integer in Haskell module_name LP  file "/home/julian/coding/ForGET/LPBenchmarks/HaskellLP/src" 
*)
(*
export_code  maximize mat_of_cols_list mat_of_rows_list mat_of_cols vec_of_list quotient_of Inr Inl Some None list_of_vec nat
Rat.Fract int_of_integer in Scala
*)


(*
section ‹ Testing ›
lemma "(let A = mat_of_rows 3 (map vec_of_list [[1, 12, 2], [1, 5, 9], [-1,0,0], [0,-1,0],[0,0,-1]]) in
       (let b = vec_of_list [10000, 8539,0,0,0] in
       (let c = vec_of_list [100, 600, 400] in
       maximize A b c))) =  Some (Inr (vec_of_list [52468 / 7, 1461 / 7, 0]))"
  by eval

lemma "(let A = mat_of_cols 4 (map vec_of_list [[2::rat, -1, -1, 1/2], [1, 2, -1, -1/2]]) in
       (let b = vec_of_list [5, 2, -1, 1/2] in
       (let c = vec_of_list [7, 1::rat] in
       maximize A b c))) = Some (Inr (vec_of_list [2::rat, 1]))"
  by eval
*)

(*
value "(let A = mat_of_cols_list 4 [[-1::rat, -1, -0, 1], [1, -1, -1, -2]] in
       (let b = vec_of_list [1, -2, -0, 4] in
       (let c = vec_of_list [-2, 1::rat] in
       maximize A b c)))"

value "(let A = mat_of_cols 4 (map vec_of_list [[2::rat, 2, -1, 0], [3, 1, 0, -1]]) in
       (let b = vec_of_list [1500::rat,1000,0,0] in
       (let c = vec_of_list [50, 40::rat] in
       maximize A b c)))"
 (*  Sol should be 375, 250 *)

value "(let A = mat_of_cols 2 (map vec_of_list [[-1::rat, 1], [-1,-1]]) in
       (let b = vec_of_list [2::rat, 2] in
       (let c = vec_of_list [0, 1::rat] in
       maximize A b c)))"
  (* Sol should be 375, 250 *)

value "(let A = mat_of_rows 2 (map vec_of_list [[2::rat,3],[2, 1], [-1,0],[0,-1]]) in
       (let b = vec_of_list [1500::rat,1000,0,0] in
       (let c = vec_of_list [50, 40::rat] in
       maximize A b c)))"
  (* Sol should be 375, 250 *)
*)


lemma of_rat_val: "simplex cs = (Sat v)  of_rat_val v rcs set cs"
  using of_rat_val_constraint simplex_real(3) by blast


end