Theory SearchSolver
section ‹Search-Based Solver Implementation and Verification›
theory SearchSolver
  imports PCNF
begin
subsection ‹Formalisation of PCNF Assignment›
fun lit_neg :: "literal ⇒ literal" where
  "lit_neg (P l) = N l"
| "lit_neg (N l) = P l"
fun lit_var :: "literal ⇒ nat" where
  "lit_var (P l) = l"
| "lit_var (N l) = l"
fun remove_lit_neg :: "literal ⇒ clause ⇒ clause" where
  "remove_lit_neg lit clause = filter (λl. l ≠ lit_neg lit) clause"
fun remove_lit_clauses :: "literal ⇒ matrix ⇒ matrix" where
  "remove_lit_clauses lit matrix = filter (λcl. ¬(list_ex (λl. l = lit) cl)) matrix"
fun matrix_assign :: "literal ⇒ matrix ⇒ matrix" where
  "matrix_assign lit matrix = remove_lit_clauses lit (map (remove_lit_neg lit) matrix)"
fun prefix_pop :: "prefix ⇒ prefix" where
  "prefix_pop Empty = Empty"
| "prefix_pop (UniversalFirst (x, Nil) Nil) = Empty"
| "prefix_pop (UniversalFirst (x, Nil) (Cons (y, ys) qs)) = ExistentialFirst (y, ys) qs"
| "prefix_pop (UniversalFirst (x, (Cons xx xs)) qs) = UniversalFirst (xx, xs) qs"
| "prefix_pop (ExistentialFirst (x, Nil) Nil) = Empty"
| "prefix_pop (ExistentialFirst (x, Nil) (Cons (y, ys) qs)) = UniversalFirst (y, ys) qs"
| "prefix_pop (ExistentialFirst (x, (Cons xx xs)) qs) = ExistentialFirst (xx, xs) qs"
fun add_universal_to_prefix :: "nat ⇒ prefix ⇒ prefix" where
  "add_universal_to_prefix x Empty = UniversalFirst (x, []) []"
| "add_universal_to_prefix x (UniversalFirst (y, ys) qs) = UniversalFirst (x, y # ys) qs"
| "add_universal_to_prefix x (ExistentialFirst (y, ys) qs) = UniversalFirst (x, []) ((y, ys) # qs)"
fun add_existential_to_prefix :: "nat ⇒ prefix ⇒ prefix" where
  "add_existential_to_prefix x Empty = ExistentialFirst (x, []) []"
| "add_existential_to_prefix x (ExistentialFirst (y, ys) qs) = ExistentialFirst (x, y # ys) qs"
| "add_existential_to_prefix x (UniversalFirst (y, ys) qs) = ExistentialFirst (x, []) ((y, ys) # qs)"
fun quant_sets_measure :: "quant_sets ⇒ nat" where
  "quant_sets_measure Nil = 0"
| "quant_sets_measure (Cons (x, xs) qs) = 1 + length xs + quant_sets_measure qs"
fun prefix_measure :: "prefix ⇒ nat" where
  "prefix_measure Empty = 0"
| "prefix_measure (UniversalFirst q qs) = quant_sets_measure (Cons q qs)"
| "prefix_measure (ExistentialFirst q qs) = quant_sets_measure (Cons q qs)"
lemma prefix_pop_decreases_measure:
  "prefix ≠ Empty ⟹ prefix_measure (prefix_pop prefix) < prefix_measure prefix"
  by (induction rule: prefix_pop.induct) auto
function remove_var_prefix :: "nat ⇒ prefix ⇒ prefix" where
  "remove_var_prefix x Empty = Empty"
| "remove_var_prefix x (UniversalFirst (y, ys) qs) = (if x = y
    then remove_var_prefix x (prefix_pop (UniversalFirst (y, ys) qs))
    else add_universal_to_prefix y (remove_var_prefix x (prefix_pop (UniversalFirst (y, ys) qs))))"
| "remove_var_prefix x (ExistentialFirst (y, ys) qs) = (if x = y
    then remove_var_prefix x (prefix_pop (ExistentialFirst (y, ys) qs))
    else add_existential_to_prefix y (remove_var_prefix x (prefix_pop (ExistentialFirst (y, ys) qs))))"
  by pat_completeness auto
termination
  by (relation "measure (λ(x, pre). prefix_measure pre)")
     (auto simp add: prefix_pop_decreases_measure simp del: prefix_measure.simps)
fun pcnf_assign :: "literal ⇒ pcnf ⇒ pcnf" where
  "pcnf_assign lit (prefix, matrix) =
     (remove_var_prefix (lit_var lit) prefix, matrix_assign lit matrix)"
text ‹Simple tests.›
value "the (convert_inv test_qbf)"
value "pcnf_assign (P 1) (the (convert_inv test_qbf))"
value "pcnf_assign (P 3) (the (convert_inv test_qbf))"
subsection ‹Effect of PCNF Assignments on the Set of all Free Variables›
subsubsection ‹Variables, Prefix Variables, and Free Variables›
fun variables_aux :: "QBF ⇒ nat list" where
  "variables_aux (Var x) = [x]"
| "variables_aux (Neg qbf) = variables_aux qbf"
| "variables_aux (Conj list) = concat (map variables_aux list)"
| "variables_aux (Disj list) = concat (map variables_aux list)"
| "variables_aux (Ex x qbf) = variables_aux qbf"
| "variables_aux (All x qbf) = variables_aux qbf"
fun variables :: "QBF ⇒ nat list" where
  "variables qbf = sort (remdups (variables_aux qbf))"
fun prefix_variables_aux :: "QBF ⇒ nat list" where
  "prefix_variables_aux (All y qbf) = Cons y (prefix_variables_aux qbf)"
| "prefix_variables_aux (Ex x qbf) = Cons x (prefix_variables_aux qbf)"
| "prefix_variables_aux _ = Nil"
fun prefix_variables :: "QBF ⇒ nat list" where
  "prefix_variables qbf = sort (remdups (prefix_variables_aux qbf))"
fun pcnf_variables :: "pcnf ⇒ nat list" where
  "pcnf_variables pcnf = variables (convert pcnf)"
fun pcnf_prefix_variables :: "pcnf ⇒ nat list" where
  "pcnf_prefix_variables pcnf = prefix_variables (convert pcnf)"
fun pcnf_free_variables :: "pcnf ⇒ nat list" where
  "pcnf_free_variables pcnf = free_variables (convert pcnf)"
lemma free_assgn_proof_skeleton:
  "free = var - pre ⟹ free_assgn = var_assgn - pre_assgn
  ⟹ var_assgn ⊆ var - lit
  ⟹ pre_assgn = pre - lit
  ⟹ free_assgn ⊆ free - lit"
  by auto
subsubsection ‹Free Variables is Variables without Prefix Variables›
lemma lit_p_free_eq_vars:
  "literal_p qbf ⟹ set (free_variables qbf) = set (variables qbf)"
  by (induction qbf rule: literal_p.induct) auto
lemma cl_p_free_eq_vars:
  assumes "clause_p qbf"
  shows "set (free_variables qbf) = set (variables qbf)"
proof -
  obtain qbf_list where list_def: "qbf = Disj qbf_list"
    using assms by (induction qbf rule: clause_p.induct) auto
  moreover from this have "list_all literal_p qbf_list" using assms by simp
  ultimately show ?thesis using lit_p_free_eq_vars by (induction qbf_list arbitrary: qbf) auto
qed
lemma cnf_p_free_eq_vars:
  assumes "cnf_p qbf"
  shows "set (free_variables qbf) = set (variables qbf)"
proof -
  obtain qbf_list where list_def: "qbf = Conj qbf_list"
    using assms by (induction qbf rule: cnf_p.induct) auto
  moreover from this have "list_all clause_p qbf_list" using assms by simp
  ultimately show ?thesis using cl_p_free_eq_vars by (induction qbf_list arbitrary: qbf) auto
qed
lemma pcnf_p_free_eq_vars_minus_prefix_aux:
  "pcnf_p qbf ⟹ set (free_variables qbf) = set (variables qbf) - set (prefix_variables_aux qbf)"
proof (induction qbf rule: prefix_variables_aux.induct)
  case (1 y qbf)
  thus ?case using bound_subtract_equiv[of "{}" "{y}" qbf] by force
next
  case (2 x qbf)
  thus ?case using bound_subtract_equiv[of "{}" "{x}" qbf] by force
next
  case ("3_3" v)
  hence "cnf_p (Conj v)" by (induction "Conj v" rule: pcnf_p.induct) auto
  thus ?case using cnf_p_free_eq_vars by fastforce
qed auto
lemma pcnf_p_free_eq_vars_minus_prefix:
  "pcnf_p qbf ⟹ set (free_variables qbf) = set (variables qbf) - set (prefix_variables qbf)"
  using pcnf_p_free_eq_vars_minus_prefix_aux by simp
lemma pcnf_free_eq_vars_minus_prefix:
  "set (pcnf_free_variables pcnf)
  = set (pcnf_variables pcnf) - set (pcnf_prefix_variables pcnf)"
  using pcnf_p_free_eq_vars_minus_prefix convert_pcnf_p by simp
subsubsection ‹Set of Matrix Variables is Non-increasing under PCNF Assignments›
lemma lit_not_in_matrix_assign_variables:
  "lit_var lit ∉ set (variables (convert_matrix (matrix_assign lit matrix)))"
proof (induction matrix)
  case (Cons cl cls)
  then show ?case
  proof (induction cl)
    case (Cons l ls)
    then show ?case
    proof (induction l)
      case (P x)
      then show ?case
      proof (induction lit)
        case (P x')
        then show ?case by (cases "x = x'") auto
      next
        case (N x')
        then show ?case by (cases "x = x'") auto
      qed
    next
      case (N x)
      then show ?case
      proof (induction lit)
        case (P x')
        then show ?case by (cases "x = x'") auto
      next
        case (N x')
        then show ?case by (cases "x = x'") auto
      qed
    qed
  qed auto
qed auto
lemma matrix_assign_vars_subseteq_matrix_vars_minus_lit:
  "set (variables (convert_matrix (matrix_assign lit matrix)))
  ⊆ set (variables (convert_matrix matrix)) - {lit_var lit}"
  using lit_not_in_matrix_assign_variables by force
lemma pcnf_vars_eq_matrix_vars:
  "set (pcnf_variables (prefix, matrix))
  = set (variables (convert_matrix matrix))"
  by (induction "(prefix, matrix)" arbitrary: prefix rule: convert.induct) auto
lemma pcnf_assign_vars_subseteq_vars_minus_lit:
  "set (pcnf_variables (pcnf_assign x pcnf))
  ⊆ set (pcnf_variables pcnf) - {lit_var x}"
  using matrix_assign_vars_subseteq_matrix_vars_minus_lit pcnf_vars_eq_matrix_vars
  by (induction pcnf) simp
subsubsection ‹PCNF Assignment Removes Variable from Prefix›
lemma add_ex_adds_prefix_var:
  "set (pcnf_prefix_variables (add_existential_to_front x pcnf))
  = set (pcnf_prefix_variables pcnf) ∪ {x}"
  using convert_add_ex bound_subtract_equiv[of "{}" "{x}" "convert pcnf"] by auto
lemma add_ex_to_prefix_eq_add_to_front:
  "(add_existential_to_prefix x prefix, matrix) = add_existential_to_front x (prefix, matrix)"
  by (induction prefix) auto
lemma add_all_adds_prefix_var:
  "set (pcnf_prefix_variables (add_universal_to_front x pcnf))
  = set (pcnf_prefix_variables pcnf) ∪ {x}"
  using convert_add_all bound_subtract_equiv[of "{}" "{x}" "convert pcnf"] by auto
lemma add_all_to_prefix_eq_add_to_front:
  "(add_universal_to_prefix x prefix, matrix) = add_universal_to_front x (prefix, matrix)"
  by (induction prefix) auto
lemma prefix_assign_vars_eq_prefix_vars_minus_lit:
  "set (pcnf_prefix_variables (remove_var_prefix x prefix, matrix))
  = set (pcnf_prefix_variables (prefix, matrix)) - {x}"
proof (induction "(prefix, matrix)" arbitrary: prefix rule: convert.induct)
  case (4 x q qs)
  then show ?case
    using add_all_adds_prefix_var add_all_to_prefix_eq_add_to_front by (induction q) auto
next
  case (5 x q qs)
  then show ?case using add_ex_adds_prefix_var add_ex_to_prefix_eq_add_to_front by (induction q) auto
next
  case (6 x y ys qs)
  then show ?case using add_all_adds_prefix_var add_all_to_prefix_eq_add_to_front by auto
next
  case (7 x y ys qs)
  then show ?case using add_ex_adds_prefix_var add_ex_to_prefix_eq_add_to_front by auto
qed auto
lemma prefix_vars_matrix_inv:
  "set (pcnf_prefix_variables (prefix, matrix1))
  = set (pcnf_prefix_variables (prefix, matrix2))"
  by (induction "(prefix, matrix1)" arbitrary: prefix rule: convert.induct) auto
lemma pcnf_prefix_vars_eq_prefix_minus_lit:
  "set (pcnf_prefix_variables (pcnf_assign x pcnf))
  = set (pcnf_prefix_variables pcnf) - {lit_var x}"
  using prefix_assign_vars_eq_prefix_vars_minus_lit prefix_vars_matrix_inv
  by (induction pcnf) fastforce
subsubsection ‹Set of Free Variables is Non-increasing under PCNF Assignments›
theorem pcnf_assign_free_subseteq_free_minus_lit:
  "set (pcnf_free_variables (pcnf_assign x pcnf)) ⊆ set (pcnf_free_variables pcnf) - {lit_var x}"
  using free_assgn_proof_skeleton[OF
      pcnf_free_eq_vars_minus_prefix[of pcnf]
      pcnf_free_eq_vars_minus_prefix[of "pcnf_assign x pcnf"]
      pcnf_assign_vars_subseteq_vars_minus_lit[of x pcnf]
      pcnf_prefix_vars_eq_prefix_minus_lit[of x pcnf]] .
subsection ‹PCNF Existential Closure›
subsubsection ‹Formalization of PCNF Existential Closure›
fun pcnf_existential_closure :: "pcnf ⇒ pcnf" where
"pcnf_existential_closure pcnf = the (convert_inv (existential_closure (convert pcnf)))"
subsubsection ‹PCNF Existential Closure Preserves Satisfiability›
lemma ex_closure_aux_pcnf_p_inv:
  "pcnf_p qbf ⟹ pcnf_p (existential_closure_aux qbf vars)"
  by (induction qbf vars rule: existential_closure_aux.induct) auto
lemma ex_closure_pcnf_p_inv:
  "pcnf_p qbf ⟹ pcnf_p (existential_closure qbf)"
  using ex_closure_aux_pcnf_p_inv by simp
theorem pcnf_sat_iff_ex_close_sat:
  "satisfiable (convert pcnf) = satisfiable (convert (pcnf_existential_closure pcnf))"
  using convert_inv_inv convert_pcnf_p ex_closure_pcnf_p_inv sat_iff_ex_close_sat by auto
subsubsection ‹No Free Variables in PCNF Existential Closure›
theorem pcnf_ex_closure_no_free:
  "pcnf_free_variables (pcnf_existential_closure pcnf) = []"
  using convert_inv_inv convert_pcnf_p ex_closure_pcnf_p_inv ex_closure_no_free by auto
subsection ‹Search Solver (Part 1: Preliminaries)›
subsubsection ‹Conditions for True and False PCNF Formulas›
lemma single_clause_variables:
  "set (pcnf_variables (Empty, [cl])) = set (map lit_var cl)"
proof (induction cl)
  case (Cons l ls)
  then show ?case by (induction l) auto
qed auto
lemma empty_prefix_cons_matrix_variables:
  "set (pcnf_variables (Empty, Cons cl cls))
  = set (pcnf_variables (Empty, cls)) ∪ set (map lit_var cl)"
  using single_clause_variables by auto
lemma false_if_empty_clause_in_matrix:
  "[] ∈ set matrix ⟹ pcnf_semantics I (prefix, matrix) = False"
  by (induction I "(prefix, matrix)" arbitrary: prefix rule: pcnf_semantics.induct)
     (induction matrix, auto)
lemma true_if_matrix_empty:
  "matrix = [] ⟹ pcnf_semantics I (prefix, matrix) = True"
  by (induction I "(prefix, matrix)" arbitrary: prefix rule: pcnf_semantics.induct) auto
lemma matrix_shape_if_no_variables:
  "pcnf_variables (Empty, matrix) = [] ⟹ (∃n. matrix = replicate n [])"
proof (induction matrix)
  case (Cons cl cls)
  show ?case
  proof (cases "cl = Nil")
    case True
    from this obtain n where "cls = replicate n []" using Cons by fastforce
    hence "cl # cls = replicate (Suc n) []" using True by simp
    then show ?thesis by (rule exI) 
  next
    case False
    hence "set (pcnf_variables (Empty, cl # cls)) ≠ {}"
      using empty_prefix_cons_matrix_variables by simp
    hence False using Cons by blast
    then show ?thesis by simp
  qed
qed auto
lemma empty_clause_or_matrix_if_no_variables:
  "pcnf_variables (Empty, matrix) = [] ⟹ [] ∈ set matrix ∨ matrix = []"
  using matrix_shape_if_no_variables by fastforce
subsubsection ‹Satisfiability Equivalences for First Variable in Prefix›
lemma clause_semantics_inv_remove_false:
  "clause_semantics (I(z := True)) cl = clause_semantics (I(z := True)) (remove_lit_neg (P z) cl)"
  by (induction cl) auto
lemma clause_semantics_inv_remove_true:
  "clause_semantics (I(z := False)) cl = clause_semantics (I(z := False)) (remove_lit_neg (N z) cl)"
  by (induction cl) auto
lemma matrix_semantics_inv_remove_true:
  "matrix_semantics (I(z := True)) (matrix_assign (P z) matrix)
  = matrix_semantics (I(z := True)) matrix"
proof (induction matrix)
  case (Cons cl cls)
  then show ?case
  proof (cases "P z ∈ set cl")
    case True
    hence 0: "clause_semantics (I(z := True)) cl" by (induction cl) auto
    have "matrix_semantics (I(z := True)) (matrix_assign (P z) (cl # cls))
         = matrix_semantics (I(z := True)) (matrix_assign (P z) cls)"
      using 0 clause_semantics_inv_remove_false by simp
    moreover have "matrix_semantics (I(z := True)) (cl # cls)
                  = matrix_semantics (I(z := True)) cls"
      using 0 by simp
    ultimately show ?thesis using Cons by blast
  next
    case False
    hence "matrix_assign (P z) (cl # cls) = remove_lit_neg (P z) cl # matrix_assign (P z) cls"
      by (induction cl) auto
    hence "matrix_semantics (I(z := True)) (matrix_assign (P z) (cl # cls))
          ⟷ clause_semantics (I(z := True)) (remove_lit_neg (P z) cl)
            ∧ matrix_semantics (I(z := True)) (matrix_assign (P z) cls)" by simp
    moreover have "matrix_semantics (I(z := True)) (cl # cls)
                  ⟷ clause_semantics (I(z := True)) cl
                    ∧ matrix_semantics (I(z := True)) cls" by simp
    ultimately show ?thesis using Cons clause_semantics_inv_remove_false by blast
  qed
qed auto
lemma matrix_semantics_inv_remove_true':
  assumes "y ≠ z"
  shows "matrix_semantics (I(z := True, y := b)) (matrix_assign (P z) matrix)
        = matrix_semantics (I(z := True, y := b)) matrix"
  using assms matrix_semantics_inv_remove_true fun_upd_twist by metis
lemma matrix_semantics_inv_remove_false:
  "matrix_semantics (I(z := False)) (matrix_assign (N z) matrix)
  = matrix_semantics (I(z := False)) matrix"
proof (induction matrix)
  case (Cons cl cls)
  then show ?case
  proof (cases "N z ∈ set cl")
    case True
    hence 0: "clause_semantics (I(z := False)) cl" by (induction cl) auto
    have "matrix_semantics (I(z := False)) (matrix_assign (N z) (cl # cls))
         = matrix_semantics (I(z := False)) (matrix_assign (N z) cls)"
      using 0 clause_semantics_inv_remove_true by simp
    moreover have "matrix_semantics (I(z := False)) (cl # cls)
                  = matrix_semantics (I(z := False)) cls"
      using 0 by simp
    ultimately show ?thesis using Cons by blast
  next
    case False
    hence "matrix_assign (N z) (cl # cls) = remove_lit_neg (N z) cl # matrix_assign (N z) cls"
      by (induction cl) auto
    hence "matrix_semantics (I(z := False)) (matrix_assign (N z) (cl # cls))
          ⟷ clause_semantics (I(z := False)) (remove_lit_neg (N z) cl)
            ∧ matrix_semantics (I(z := False)) (matrix_assign (N z) cls)" by simp
    moreover have "matrix_semantics (I(z := False)) (cl # cls)
                  ⟷ clause_semantics (I(z := False)) cl
                    ∧ matrix_semantics (I(z := False)) cls" by simp
    ultimately show ?thesis using Cons clause_semantics_inv_remove_true by blast
  qed
qed auto
lemma matrix_semantics_inv_remove_false':
  assumes "y ≠ z"
  shows "matrix_semantics (I(z := False, y := b)) (matrix_assign (N z) matrix)
        = matrix_semantics (I(z := False, y := b)) matrix"
  using assms matrix_semantics_inv_remove_false fun_upd_twist by metis
lemma matrix_semantics_disj_iff_true_assgn:
  "(∃b. matrix_semantics (I(z := b)) matrix)
  ⟷ matrix_semantics (I(z := True)) (matrix_assign (P z) matrix)
    ∨ matrix_semantics (I(z := False)) (matrix_assign (N z) matrix)"
  using matrix_semantics_inv_remove_true matrix_semantics_inv_remove_false by (metis (full_types))
lemma matrix_semantics_conj_iff_true_assgn:
  "(∀b. matrix_semantics (I(z := b)) matrix)
  ⟷ matrix_semantics (I(z := True)) (matrix_assign (P z) matrix)
    ∧ matrix_semantics (I(z := False)) (matrix_assign (N z) matrix)"
  using matrix_semantics_inv_remove_true matrix_semantics_inv_remove_false by (metis (full_types))
lemma pcnf_assign_free_eq_matrix_assgn':
  assumes "lit_var lit ∉ set (prefix_variables_aux (convert (prefix, matrix)))"
  shows "pcnf_assign lit (prefix, matrix) = (prefix, matrix_assign lit matrix)"
  using assms
  by (induction "(prefix, matrix)" arbitrary: prefix rule: convert.induct) auto
lemma pcnf_assign_free_eq_matrix_assgn:
  assumes "lit_var lit ∉ set (pcnf_prefix_variables (prefix, matrix))"
  shows "pcnf_assign lit (prefix, matrix) = (prefix, matrix_assign lit matrix)"
  using assms pcnf_assign_free_eq_matrix_assgn' by simp
lemma neq_first_if_notin_all_prefix:
  "z ∉ set (pcnf_prefix_variables (UniversalFirst (y, ys) qs, matrix)) ⟹ z ≠ y"
  by (induction "(UniversalFirst (y, ys) qs, matrix)" rule: convert.induct) auto
lemma neq_first_if_notin_ex_prefix:
  "z ∉ set (pcnf_prefix_variables (ExistentialFirst (x, xs) qs, matrix)) ⟹ z ≠ x"
  by (induction "(ExistentialFirst (x, xs) qs, matrix)" rule: convert.induct) auto
lemma notin_pop_prefix_if_notin_prefix:
  assumes "z ∉ set (pcnf_prefix_variables (prefix, matrix))"
  shows "z ∉ set (pcnf_prefix_variables (prefix_pop prefix, matrix))"
  using assms
proof (induction prefix)
  case (UniversalFirst q qs)
  then show ?case
  proof (induction q)
    case (Pair y ys)
    then show ?case
      by (induction "(UniversalFirst (y, ys) qs, matrix)" rule: convert.induct) auto
  qed
next
  case (ExistentialFirst q qs)
  then show ?case
  proof (induction q)
    case (Pair x xs)
    then show ?case
      by (induction "(ExistentialFirst (x, xs) qs, matrix)" rule: convert.induct) auto
  qed
qed auto
lemma pcnf_semantics_inv_matrix_assign_true:
  assumes "z ∉ set (pcnf_prefix_variables (prefix, matrix))"
  shows "pcnf_semantics (I(z := True)) (prefix, matrix_assign (P z) matrix)
        = pcnf_semantics (I(z := True)) (prefix, matrix)"
  using assms
proof (induction I "(prefix, matrix)" arbitrary: I prefix matrix rule: pcnf_semantics.induct)
  case (1 I matrix)
  then show ?case using matrix_semantics_inv_remove_true by simp
next
  case (2 I y matrix)
  then show ?case using matrix_semantics_inv_remove_true' by simp
next
  case (3 I x matrix)
  then show ?case using matrix_semantics_inv_remove_true' by simp
next
  case (4 I y q qs matrix)
  hence neq: "z ≠ y" using neq_first_if_notin_all_prefix by blast
  have "prefix_pop (UniversalFirst (y, []) (q # qs)) = ExistentialFirst q qs"
    by (induction q) auto
  hence "z ∉ set (pcnf_prefix_variables (ExistentialFirst q qs, matrix))"
    using 4(3) notin_pop_prefix_if_notin_prefix by metis
  hence "pcnf_semantics (I(z := True)) (ExistentialFirst q qs, matrix) =
    pcnf_semantics (I(z := True)) (ExistentialFirst q qs, matrix_assign (P z) matrix)"
    for I using 4 by blast
  then show ?case using neq by (simp add: fun_upd_twist)
next
  case (5 I x q qs matrix)
  hence neq: "z ≠ x" using neq_first_if_notin_ex_prefix by blast
  have "prefix_pop (ExistentialFirst (x, []) (q # qs)) = UniversalFirst q qs"
    by (induction q) auto
  hence "z ∉ set (pcnf_prefix_variables (UniversalFirst q qs, matrix))"
    using 5(3) notin_pop_prefix_if_notin_prefix by metis
  hence "pcnf_semantics (I(z := True)) (UniversalFirst q qs, matrix) =
    pcnf_semantics (I(z := True)) (UniversalFirst q qs, matrix_assign (P z) matrix)"
    for I using 5 by blast
  then show ?case using neq by (simp add: fun_upd_twist)
next
  case (6 I y yy ys qs matrix)
  hence neq: "z ≠ y" using neq_first_if_notin_all_prefix by blast
  have "z ∉ set (pcnf_prefix_variables (UniversalFirst (yy, ys) qs, matrix))"
    using 6(3) notin_pop_prefix_if_notin_prefix by fastforce
  hence "pcnf_semantics (I(z := True)) (UniversalFirst (yy, ys) qs, matrix) =
    pcnf_semantics (I(z := True)) (UniversalFirst (yy, ys) qs, matrix_assign (P z) matrix)"
    for I using 6 by blast
  then show ?case using neq by (simp add: fun_upd_twist)
next
  case (7 I x xx xs qs matrix)
  hence neq: "z ≠ x" using neq_first_if_notin_ex_prefix by blast
  have "z ∉ set (pcnf_prefix_variables (ExistentialFirst (xx, xs) qs, matrix))"
    using 7(3) notin_pop_prefix_if_notin_prefix by fastforce
  hence "pcnf_semantics (I(z := True)) (ExistentialFirst (xx, xs) qs, matrix) =
    pcnf_semantics (I(z := True)) (ExistentialFirst (xx, xs) qs, matrix_assign (P z) matrix)"
    for I using 7 by blast
  then show ?case using neq by (simp add: fun_upd_twist)
qed
lemma pcnf_semantics_inv_matrix_assign_false:
  assumes "z ∉ set (pcnf_prefix_variables (prefix, matrix))"
  shows "pcnf_semantics (I(z := False)) (prefix, matrix_assign (N z) matrix)
        = pcnf_semantics (I(z := False)) (prefix, matrix)"
  using assms
proof (induction I "(prefix, matrix)" arbitrary: I prefix matrix rule: pcnf_semantics.induct)
  case (1 I matrix)
  then show ?case using matrix_semantics_inv_remove_false by simp
next
  case (2 I y matrix)
  then show ?case using matrix_semantics_inv_remove_false' by simp
next
  case (3 I x matrix)
  then show ?case using matrix_semantics_inv_remove_false' by simp
next
  case (4 I y q qs matrix)
  hence neq: "z ≠ y" using neq_first_if_notin_all_prefix by blast
  have "prefix_pop (UniversalFirst (y, []) (q # qs)) = ExistentialFirst q qs"
    by (induction q) auto
  hence "z ∉ set (pcnf_prefix_variables (ExistentialFirst q qs, matrix))"
    using 4(3) notin_pop_prefix_if_notin_prefix by metis
  hence "pcnf_semantics (I(z := False)) (ExistentialFirst q qs, matrix) =
    pcnf_semantics (I(z := False)) (ExistentialFirst q qs, matrix_assign (N z) matrix)"
    for I using 4 by blast
  then show ?case using neq by (simp add: fun_upd_twist)
next
  case (5 I x q qs matrix)
  hence neq: "z ≠ x" using neq_first_if_notin_ex_prefix by blast
  have "prefix_pop (ExistentialFirst (x, []) (q # qs)) = UniversalFirst q qs"
    by (induction q) auto
  hence "z ∉ set (pcnf_prefix_variables (UniversalFirst q qs, matrix))"
    using 5(3) notin_pop_prefix_if_notin_prefix by metis
  hence "pcnf_semantics (I(z := False)) (UniversalFirst q qs, matrix) =
    pcnf_semantics (I(z := False)) (UniversalFirst q qs, matrix_assign (N z) matrix)"
    for I using 5 by blast
  then show ?case using neq by (simp add: fun_upd_twist)
next
  case (6 I y yy ys qs matrix)
  hence neq: "z ≠ y" using neq_first_if_notin_all_prefix by blast
  have "z ∉ set (pcnf_prefix_variables (UniversalFirst (yy, ys) qs, matrix))"
    using 6(3) notin_pop_prefix_if_notin_prefix by fastforce
  hence "pcnf_semantics (I(z := False)) (UniversalFirst (yy, ys) qs, matrix) =
    pcnf_semantics (I(z := False)) (UniversalFirst (yy, ys) qs, matrix_assign (N z) matrix)"
    for I using 6 by blast
  then show ?case using neq by (simp add: fun_upd_twist)
next
  case (7 I x xx xs qs matrix)
  hence neq: "z ≠ x" using neq_first_if_notin_ex_prefix by blast
  have "z ∉ set (pcnf_prefix_variables (ExistentialFirst (xx, xs) qs, matrix))"
    using 7(3) notin_pop_prefix_if_notin_prefix by fastforce
  hence "pcnf_semantics (I(z := False)) (ExistentialFirst (xx, xs) qs, matrix) =
    pcnf_semantics (I(z := False)) (ExistentialFirst (xx, xs) qs, matrix_assign (N z) matrix)"
    for I using 7 by blast
  then show ?case using neq by (simp add: fun_upd_twist)
qed
lemma pcnf_semantics_disj_iff_matrix_assign_disj:
  assumes "z ∉ set (pcnf_prefix_variables (prefix, matrix))"
  shows "pcnf_semantics (I(z := True)) (prefix, matrix)
        ∨ pcnf_semantics (I(z := False)) (prefix, matrix)
        ⟷
        pcnf_semantics (I(z := True)) (prefix, matrix_assign (P z) matrix)
        ∨ pcnf_semantics (I(z := False)) (prefix, matrix_assign (N z) matrix)"
  using assms
proof (induction I "(prefix, matrix_assign (P z) matrix)"
    arbitrary: I prefix matrix rule: pcnf_semantics.induct)
  case (1 I)
  then show ?case using ex_bool_eq matrix_semantics_disj_iff_true_assgn by simp
next
  case (2 I y)
  hence neq: "y ≠ z" by simp
  show ?case using ex_bool_eq matrix_semantics_inv_remove_true'
      matrix_semantics_inv_remove_false' neq by simp
next
  case (3 I x)
  hence neq: "x ≠ z" by simp
  show ?case using ex_bool_eq matrix_semantics_inv_remove_true'
      matrix_semantics_inv_remove_false' neq by simp
next
  case (4 I y q qs)
  hence neq: "y ≠ z" using neq_first_if_notin_all_prefix by blast
  have "prefix_pop (UniversalFirst (y, []) (q # qs)) = ExistentialFirst q qs"
    by (induction q) auto
  hence nin: "z ∉ set (pcnf_prefix_variables (ExistentialFirst q qs, matrix))"
    using 4(3) notin_pop_prefix_if_notin_prefix by metis
  show ?case using nin neq pcnf_semantics_inv_matrix_assign_true
      pcnf_semantics_inv_matrix_assign_false by (simp add: fun_upd_twist)
next
  case (5 I x q qs)
  hence neq: "x ≠ z" using neq_first_if_notin_ex_prefix by blast
  have "prefix_pop (ExistentialFirst (x, []) (q # qs)) = UniversalFirst q qs"
    by (induction q) auto
  hence nin: "z ∉ set (pcnf_prefix_variables (UniversalFirst q qs, matrix))"
    using 5(3) notin_pop_prefix_if_notin_prefix by metis
  show ?case using nin neq pcnf_semantics_inv_matrix_assign_true
      pcnf_semantics_inv_matrix_assign_false by (simp add: fun_upd_twist)
next
  case (6 I y yy ys qs)
  hence neq: "y ≠ z" using neq_first_if_notin_all_prefix by blast
  have nin: "z ∉ set (pcnf_prefix_variables (UniversalFirst (yy, ys) qs, matrix))"
    using 6(3) notin_pop_prefix_if_notin_prefix by fastforce
  show ?case using nin neq pcnf_semantics_inv_matrix_assign_true
      pcnf_semantics_inv_matrix_assign_false by (simp add: fun_upd_twist)
next
  case (7 I x xx xs qs)
  hence neq: "x ≠ z" using neq_first_if_notin_ex_prefix by blast
  have nin: "z ∉ set (pcnf_prefix_variables (ExistentialFirst (xx, xs) qs, matrix))"
    using 7(3) notin_pop_prefix_if_notin_prefix by fastforce
  show ?case using nin neq pcnf_semantics_inv_matrix_assign_true
      pcnf_semantics_inv_matrix_assign_false by (simp add: fun_upd_twist)
qed
lemma pcnf_semantics_conj_iff_matrix_assign_conj:
  assumes "z ∉ set (pcnf_prefix_variables (prefix, matrix))"
  shows "pcnf_semantics (I(z := True)) (prefix, matrix)
        ∧ pcnf_semantics (I(z := False)) (prefix, matrix)
        ⟷
        pcnf_semantics (I(z := True)) (prefix, matrix_assign (P z) matrix)
        ∧ pcnf_semantics (I(z := False)) (prefix, matrix_assign (N z) matrix)"
  using assms
proof (induction I "(prefix, matrix_assign (P z) matrix)"
    arbitrary: I prefix matrix rule: pcnf_semantics.induct)
  case (1 I)
  then show ?case using all_bool_eq matrix_semantics_conj_iff_true_assgn by simp
next
  case (2 I y)
  hence neq: "y ≠ z" by simp
  show ?case using matrix_semantics_inv_remove_true'
      matrix_semantics_inv_remove_false' neq by simp
next
  case (3 I x)
  hence neq: "x ≠ z" by simp
  show ?case using matrix_semantics_inv_remove_true'
      matrix_semantics_inv_remove_false' neq by simp
next
  case (4 I y q qs)
  hence neq: "y ≠ z" using neq_first_if_notin_all_prefix by blast
  have "prefix_pop (UniversalFirst (y, []) (q # qs)) = ExistentialFirst q qs"
    by (induction q) auto
  hence nin: "z ∉ set (pcnf_prefix_variables (ExistentialFirst q qs, matrix))"
    using 4(3) notin_pop_prefix_if_notin_prefix by metis
  show ?case using nin neq pcnf_semantics_inv_matrix_assign_true
      pcnf_semantics_inv_matrix_assign_false by (simp add: fun_upd_twist)
next
  case (5 I x q qs)
  hence neq: "x ≠ z" using neq_first_if_notin_ex_prefix by blast
  have "prefix_pop (ExistentialFirst (x, []) (q # qs)) = UniversalFirst q qs"
    by (induction q) auto
  hence nin: "z ∉ set (pcnf_prefix_variables (UniversalFirst q qs, matrix))"
    using 5(3) notin_pop_prefix_if_notin_prefix by metis
  show ?case using nin neq pcnf_semantics_inv_matrix_assign_true
      pcnf_semantics_inv_matrix_assign_false by (simp add: fun_upd_twist)
next
  case (6 I y yy ys qs)
  hence neq: "y ≠ z" using neq_first_if_notin_all_prefix by blast
  have nin: "z ∉ set (pcnf_prefix_variables (UniversalFirst (yy, ys) qs, matrix))"
    using 6(3) notin_pop_prefix_if_notin_prefix by fastforce
  show ?case using nin neq pcnf_semantics_inv_matrix_assign_true
      pcnf_semantics_inv_matrix_assign_false by (simp add: fun_upd_twist)
next
  case (7 I x xx xs qs)
  hence neq: "x ≠ z" using neq_first_if_notin_ex_prefix by blast
  have nin: "z ∉ set (pcnf_prefix_variables (ExistentialFirst (xx, xs) qs, matrix))"
    using 7(3) notin_pop_prefix_if_notin_prefix by fastforce
  show ?case using nin neq pcnf_semantics_inv_matrix_assign_true
      pcnf_semantics_inv_matrix_assign_false by (simp add: fun_upd_twist)
qed
lemma semantics_eq_if_free_vars_eq:
  assumes "∀x ∈ set (free_variables qbf). I(x) = J(x)"
  shows "qbf_semantics I qbf = qbf_semantics J qbf" using assms
proof (induction I qbf rule: qbf_semantics.induct)
  case (3 I qbf_list)
  then show ?case by (induction qbf_list) auto
next
  case (4 I qbf_list)
  then show ?case by (induction qbf_list) auto
next
  case (5 I x qbf)
  hence "qbf_semantics I (substitute_var x b qbf)
        = qbf_semantics J (substitute_var x b qbf)"
    for b using set_free_vars_subst_ex_eq by (metis (full_types))
  then show ?case by simp
next
  case (6 I x qbf)
  hence "qbf_semantics I (substitute_var x b qbf)
        = qbf_semantics J (substitute_var x b qbf)"
    for b using set_free_vars_subst_all_eq by (metis (full_types))
  then show ?case by simp
qed auto
lemma pcnf_semantics_eq_if_free_vars_eq:
  assumes "∀x ∈ set (pcnf_free_variables pcnf). I(x) = J(x)"
  shows "pcnf_semantics I pcnf = pcnf_semantics J pcnf"
  using assms semantics_eq_if_free_vars_eq qbf_semantics_eq_pcnf_semantics by simp
lemma x_notin_assign_P_x:
  "x ∉ set (pcnf_variables (pcnf_assign (P x) pcnf))"
  using pcnf_assign_vars_subseteq_vars_minus_lit by fastforce
lemma x_notin_assign_N_x:
  "x ∉ set (pcnf_variables (pcnf_assign (N x) pcnf))"
  using pcnf_assign_vars_subseteq_vars_minus_lit by fastforce
lemma interp_value_ignored_for_pcnf_P_assign:
  "pcnf_semantics (I(x := b)) (pcnf_assign (P x) pcnf)
  = pcnf_semantics I (pcnf_assign (P x) pcnf)"
  using pcnf_semantics_eq_if_free_vars_eq x_notin_assign_P_x
    pcnf_free_eq_vars_minus_prefix by simp
lemma interp_value_ignored_for_pcnf_N_assign:
  "pcnf_semantics (I(x := b)) (pcnf_assign (N x) pcnf)
  = pcnf_semantics I (pcnf_assign (N x) pcnf)"
  using pcnf_semantics_eq_if_free_vars_eq x_notin_assign_N_x
    pcnf_free_eq_vars_minus_prefix by simp
lemma sat_ex_first_iff_one_assign_sat:
  assumes "x ∉ set (pcnf_prefix_variables (prefix_pop (ExistentialFirst (x, xs) qs), matrix))"
  shows "satisfiable (convert (ExistentialFirst (x, xs) qs, matrix))
  ⟷ satisfiable (convert (pcnf_assign (P x) (ExistentialFirst (x, xs) qs, matrix)))
    ∨ satisfiable (convert (pcnf_assign (N x) (ExistentialFirst (x, xs) qs, matrix)))"
proof -
  let ?pre = "ExistentialFirst (x, xs) qs"
  have "satisfiable (convert (?pre, matrix))
       = (∃I. pcnf_semantics I (?pre, matrix))"
    using satisfiable_def qbf_semantics_eq_pcnf_semantics by simp
  also have "... =
       (∃I. pcnf_semantics (I(x := True)) (prefix_pop ?pre, matrix) ∨
            pcnf_semantics (I(x := False)) (prefix_pop ?pre, matrix))"
    by (induction "?pre" rule: prefix_pop.induct) auto
  also have "... =
    (∃I. pcnf_semantics (I(x := True)) (prefix_pop ?pre, matrix_assign (P x) matrix) ∨
         pcnf_semantics (I(x := False)) (prefix_pop ?pre, matrix_assign (N x) matrix))"
    using pcnf_semantics_disj_iff_matrix_assign_disj assms by blast
  also have "... ⟷
    (∃I. pcnf_semantics (I(x := True)) (pcnf_assign (P x) (?pre, matrix))) ∨
    (∃I. pcnf_semantics (I(x := False)) (pcnf_assign (N x) (?pre, matrix)))"
    using pcnf_assign_free_eq_matrix_assgn[of "P x"] pcnf_assign_free_eq_matrix_assgn[of "N x"]
      assms by auto
  also have "... ⟷
    (∃I. pcnf_semantics I (pcnf_assign (P x) (?pre, matrix))) ∨
    (∃I. pcnf_semantics I (pcnf_assign (N x) (?pre, matrix)))"
    using interp_value_ignored_for_pcnf_N_assign interp_value_ignored_for_pcnf_P_assign
    by blast
  also have "... ⟷
    satisfiable (convert (pcnf_assign (P x) (?pre, matrix))) ∨
    satisfiable (convert (pcnf_assign (N x) (?pre, matrix)))"
    using satisfiable_def qbf_semantics_eq_pcnf_semantics by simp
  finally show ?thesis .
qed
theorem sat_ex_first_iff_assign_disj_sat:
  assumes "x ∉ set (pcnf_prefix_variables (prefix_pop (ExistentialFirst (x, xs) qs), matrix))"
  shows "satisfiable (convert (ExistentialFirst (x, xs) qs, matrix))
  ⟷ satisfiable (Disj
    [convert (pcnf_assign (P x) (ExistentialFirst (x, xs) qs, matrix)),
     convert (pcnf_assign (N x) (ExistentialFirst (x, xs) qs, matrix))])"
  using assms sat_ex_first_iff_one_assign_sat satisfiable_def
    qbf_semantics_eq_pcnf_semantics by auto
theorem sat_all_first_iff_assign_conj_sat:
  assumes "y ∉ set (pcnf_prefix_variables (prefix_pop (UniversalFirst (y, ys) qs), matrix))"
  shows "satisfiable (convert (UniversalFirst (y, ys) qs, matrix))
  ⟷ satisfiable (Conj
    [convert (pcnf_assign (P y) (UniversalFirst (y, ys) qs, matrix)),
     convert (pcnf_assign (N y) (UniversalFirst (y, ys) qs, matrix))])"
proof -
  let ?pre = "UniversalFirst (y, ys) qs"
  have "satisfiable (convert (?pre, matrix))
       = (∃I. pcnf_semantics I (?pre, matrix))"
    using satisfiable_def qbf_semantics_eq_pcnf_semantics by simp
  also have "... =
    (∃I. pcnf_semantics (I(y := True)) (prefix_pop ?pre, matrix) ∧
         pcnf_semantics (I(y := False)) (prefix_pop ?pre, matrix))"
    by (induction "?pre" rule: prefix_pop.induct) auto
  also have "... =
    (∃I. pcnf_semantics (I(y := True)) (prefix_pop ?pre, matrix_assign (P y) matrix) ∧
         pcnf_semantics (I(y := False)) (prefix_pop ?pre, matrix_assign (N y) matrix))"
    using pcnf_semantics_conj_iff_matrix_assign_conj assms by blast
  also have "... =
    (∃I. pcnf_semantics (I(y := True)) (pcnf_assign (P y) (?pre, matrix)) ∧
         pcnf_semantics (I(y := False)) (pcnf_assign (N y) (?pre, matrix)))"
    using pcnf_assign_free_eq_matrix_assgn[of "P y"] pcnf_assign_free_eq_matrix_assgn[of "N y"]
      assms by simp
  also have "... =
    (∃I. pcnf_semantics I (pcnf_assign (P y) (?pre, matrix)) ∧
         pcnf_semantics I (pcnf_assign (N y) (?pre, matrix)))"
    using interp_value_ignored_for_pcnf_N_assign interp_value_ignored_for_pcnf_P_assign by blast
  also have "... =
    (∃I. qbf_semantics I (convert (pcnf_assign (P y) (?pre, matrix))) ∧
         qbf_semantics I (convert (pcnf_assign (N y) (?pre, matrix))))"
      using qbf_semantics_eq_pcnf_semantics by blast
  also have "... =
    satisfiable (Conj
      [convert (pcnf_assign (P y) (?pre, matrix)),
       convert (pcnf_assign (N y) (?pre, matrix))])"
    unfolding satisfiable_def by simp
  finally show ?thesis .
qed
subsection ‹Cleansed PCNF Formulas›
subsubsection ‹Predicate for Cleansed Formulas›
fun cleansed_p :: "pcnf ⇒ bool" where
  "cleansed_p pcnf = distinct (prefix_variables_aux (convert pcnf))"
lemma prefix_pop_cleansed_if_cleansed:
  "cleansed_p (prefix, matrix) ⟹ cleansed_p (prefix_pop prefix, matrix)"
  by (induction prefix rule: prefix_pop.induct) auto
lemma prefix_variables_aux_matrix_inv:
  "prefix_variables_aux (convert (prefix, matrix1))
  = prefix_variables_aux (convert (prefix, matrix2))"
  by (induction "(prefix, matrix1)" arbitrary: prefix rule: convert.induct) auto
lemma eq_prefix_cleansed_p_add_all_inv:
  "cleansed_p (add_universal_to_front y (prefix, matrix1))
  = cleansed_p (add_universal_to_front y (prefix, matrix2))"
proof (induction y "(prefix, matrix1)" arbitrary: prefix rule: add_universal_to_front.induct)
  case (1 x)
  then show ?case by simp
next
  case (2 x y ys qs)
  have "prefix_variables_aux (convert (UniversalFirst (y, ys) qs, matrix1))
       = prefix_variables_aux (convert (UniversalFirst (y, ys) qs, matrix2))"
    using prefix_variables_aux_matrix_inv by simp
  then show ?case by simp
next
  case (3 x y ys qs)
    have "prefix_variables_aux (convert (ExistentialFirst (y, ys) qs, matrix1))
       = prefix_variables_aux (convert (ExistentialFirst (y, ys) qs, matrix2))"
    using prefix_variables_aux_matrix_inv by simp
  then show ?case by simp
qed
lemma eq_prefix_cleansed_p_add_ex_inv:
  "cleansed_p (add_existential_to_front x (prefix, matrix1))
  = cleansed_p (add_existential_to_front x (prefix, matrix2))"
proof (induction x "(prefix, matrix1)" arbitrary: prefix rule: add_universal_to_front.induct)
  case (1 x)
  then show ?case by simp
next
  case (2 x y ys qs)
  have "prefix_variables_aux (convert (UniversalFirst (y, ys) qs, matrix1))
       = prefix_variables_aux (convert (UniversalFirst (y, ys) qs, matrix2))"
    using prefix_variables_aux_matrix_inv by simp
  then show ?case by simp
next
  case (3 x y ys qs)
    have "prefix_variables_aux (convert (ExistentialFirst (y, ys) qs, matrix1))
       = prefix_variables_aux (convert (ExistentialFirst (y, ys) qs, matrix2))"
    using prefix_variables_aux_matrix_inv by simp
  then show ?case by simp
qed
lemma cleansed_p_matrix_inv:
  "cleansed_p (prefix, matrix1) = cleansed_p (prefix, matrix2)"
proof (induction "(prefix, matrix1)" arbitrary: prefix rule: convert.induct)
  case (4 x q qs)
  have "(UniversalFirst (x, []) (q # qs), matrix)
       = add_universal_to_front x (ExistentialFirst q qs, matrix)"
    for matrix by (induction q) auto
  then show ?case using eq_prefix_cleansed_p_add_all_inv by simp 
next
  case (5 x q qs)
    have "(ExistentialFirst (x, []) (q # qs), matrix)
       = add_existential_to_front x (UniversalFirst q qs, matrix)"
    for matrix by (induction q) auto
  then show ?case using eq_prefix_cleansed_p_add_ex_inv by simp
next
  case (6 x y ys qs)
  have "(UniversalFirst (x, y # ys) qs, matrix)
       = add_universal_to_front x (UniversalFirst (y, ys) qs, matrix)"
    for matrix by simp
  then show ?case using eq_prefix_cleansed_p_add_all_inv by metis
next
  case (7 x y ys qs)
  have "(ExistentialFirst (x, y # ys) qs, matrix)
       = add_existential_to_front x (ExistentialFirst (y, ys) qs, matrix)"
    for matrix by simp
  then show ?case using eq_prefix_cleansed_p_add_ex_inv by metis
qed auto
lemma cleansed_prefix_first_ex_unique:
  assumes "cleansed_p (ExistentialFirst (x, xs) qs, matrix)"
  shows "x ∉ set (pcnf_prefix_variables (prefix_pop (ExistentialFirst (x, xs) qs), matrix))"
  using assms by (induction "ExistentialFirst (x, xs) qs" rule: prefix_pop.induct) auto
lemma cleansed_prefix_first_all_unique:
  assumes "cleansed_p (UniversalFirst (y, ys) qs, matrix)"
  shows "y ∉ set (pcnf_prefix_variables (prefix_pop (UniversalFirst (y, ys) qs), matrix))"
  using assms by (induction "UniversalFirst (y, ys) qs" rule: prefix_pop.induct) auto
subsubsection ‹The Cleansed Predicate is Invariant under PCNF Assignment›
lemma cleansed_add_new_ex_to_front:
  assumes "cleansed_p pcnf"
    and "x ∉ set (pcnf_prefix_variables pcnf)"
  shows "cleansed_p (add_existential_to_front x pcnf)"
  using assms by (induction x pcnf rule: add_existential_to_front.induct) auto
lemma cleansed_add_new_all_to_front:
  assumes "cleansed_p pcnf"
    and "y ∉ set (pcnf_prefix_variables pcnf)"
  shows "cleansed_p (add_universal_to_front y pcnf)"
  using assms by (induction y pcnf rule: add_existential_to_front.induct) auto
lemma pcnf_assign_p_ex_eq:
  assumes "cleansed_p (ExistentialFirst (x, xs) qs, matrix)"
  shows "pcnf_assign (P x) (ExistentialFirst (x, xs) qs, matrix)
        = (prefix_pop (ExistentialFirst (x, xs) qs), matrix_assign (P x) matrix)"
  using assms by (metis cleansed_prefix_first_ex_unique lit_var.simps(1)
      pcnf_assign.simps pcnf_assign_free_eq_matrix_assgn remove_var_prefix.simps(3))
lemma pcnf_assign_p_all_eq:
  assumes "cleansed_p (UniversalFirst (y, ys) qs, matrix)"
  shows "pcnf_assign (P y) (UniversalFirst (y, ys) qs, matrix)
        = (prefix_pop (UniversalFirst (y, ys) qs), matrix_assign (P y) matrix)"
  using assms by (metis cleansed_prefix_first_all_unique lit_var.simps(1)
      pcnf_assign.simps pcnf_assign_free_eq_matrix_assgn remove_var_prefix.simps(2))
lemma pcnf_assign_n_ex_eq:
  assumes "cleansed_p (ExistentialFirst (x, xs) qs, matrix)"
  shows "pcnf_assign (N x) (ExistentialFirst (x, xs) qs, matrix)
        = (prefix_pop (ExistentialFirst (x, xs) qs), matrix_assign (N x) matrix)"
  using assms by (metis cleansed_prefix_first_ex_unique lit_var.simps(2)
      pcnf_assign.simps pcnf_assign_free_eq_matrix_assgn remove_var_prefix.simps(3))
lemma pcnf_assign_n_all_eq:
  assumes "cleansed_p (UniversalFirst (y, ys) qs, matrix)"
  shows "pcnf_assign (N y) (UniversalFirst (y, ys) qs, matrix)
        = (prefix_pop (UniversalFirst (y, ys) qs), matrix_assign (N y) matrix)"
  using assms by (metis cleansed_prefix_first_all_unique lit_var.simps(2)
      pcnf_assign.simps pcnf_assign_free_eq_matrix_assgn remove_var_prefix.simps(2))
theorem pcnf_assign_cleansed_inv:
  "cleansed_p pcnf ⟹ cleansed_p (pcnf_assign lit pcnf)"
proof (induction pcnf rule: convert.induct)
  case (4 x q qs matrix)
  let ?z = "lit_var lit"
  show ?case
  proof (cases "x = ?z")
    case True
    then show ?thesis using 4 cleansed_p_matrix_inv
        pcnf_assign_n_all_eq[of ?z] pcnf_assign_p_all_eq[of ?z]
        prefix_pop_cleansed_if_cleansed lit_var.elims by metis
  next
    case False
    let ?mat = "matrix_assign lit matrix"
    have "cleansed_p (remove_var_prefix ?z (ExistentialFirst q qs), ?mat)"
      using 4 by simp
    moreover have "x ∉ set (pcnf_prefix_variables (remove_var_prefix ?z (ExistentialFirst q qs), ?mat))"
      using 4 False prefix_assign_vars_eq_prefix_vars_minus_lit[of ?z] prefix_vars_matrix_inv
      by fastforce
    ultimately have "cleansed_p (add_universal_to_prefix x (remove_var_prefix ?z (ExistentialFirst q qs)), ?mat)"
      using cleansed_add_new_all_to_front add_all_to_prefix_eq_add_to_front by simp
    then have "cleansed_p (remove_var_prefix ?z (UniversalFirst (x, []) (q # qs)), ?mat)"
      using False by (induction q) auto
    then show ?thesis by simp
  qed
next
  case (5 x q qs matrix)
  let ?z = "lit_var lit"
  show ?case
  proof (cases "x = ?z")
    case True
    then show ?thesis using 5 cleansed_p_matrix_inv
        pcnf_assign_n_ex_eq[of ?z] pcnf_assign_p_ex_eq[of ?z]
        prefix_pop_cleansed_if_cleansed lit_var.elims by metis
  next
    case False
    let ?mat = "matrix_assign lit matrix"
    have "cleansed_p (remove_var_prefix ?z (UniversalFirst q qs), ?mat)"
      using 5 by simp
    moreover have "x ∉ set (pcnf_prefix_variables (remove_var_prefix ?z (UniversalFirst q qs), ?mat))"
      using 5 False prefix_assign_vars_eq_prefix_vars_minus_lit[of ?z] prefix_vars_matrix_inv
      by fastforce
    ultimately have "cleansed_p (add_existential_to_prefix x (remove_var_prefix ?z (UniversalFirst q qs)), ?mat)"
      using cleansed_add_new_ex_to_front add_ex_to_prefix_eq_add_to_front by simp
    then have "cleansed_p (remove_var_prefix ?z (ExistentialFirst (x, []) (q # qs)), ?mat)"
      using False by (induction q) auto
    then show ?thesis by simp
  qed
next
  case (6 x y ys qs matrix)
  let ?z = "lit_var lit"
  show ?case
  proof (cases "x = ?z")
    case True
    then show ?thesis using 6 cleansed_p_matrix_inv
        pcnf_assign_n_all_eq[of ?z] pcnf_assign_p_all_eq[of ?z]
        prefix_pop_cleansed_if_cleansed lit_var.elims by metis
  next
    case False
    let ?mat = "matrix_assign lit matrix"
    have "cleansed_p (remove_var_prefix ?z (UniversalFirst (y, ys) qs), ?mat)"
      using 6 by simp
    moreover have "x ∉ set (pcnf_prefix_variables (remove_var_prefix ?z (UniversalFirst (y, ys) qs), ?mat))"
      using 6(2) False prefix_assign_vars_eq_prefix_vars_minus_lit[of ?z] prefix_vars_matrix_inv
      by fastforce
    ultimately have "cleansed_p (add_universal_to_prefix x (remove_var_prefix ?z (UniversalFirst (y, ys) qs)), ?mat)"
      using cleansed_add_new_all_to_front add_all_to_prefix_eq_add_to_front by simp
    then have "cleansed_p (remove_var_prefix ?z (UniversalFirst (x, (y # ys)) qs), ?mat)"
      using False by simp
    then show ?thesis by simp
  qed
next
  case (7 x y ys qs matrix)
  let ?z = "lit_var lit"
  show ?case
  proof (cases "x = ?z")
    case True
    then show ?thesis using 7 cleansed_p_matrix_inv
        pcnf_assign_n_ex_eq[of ?z] pcnf_assign_p_ex_eq[of ?z]
        prefix_pop_cleansed_if_cleansed lit_var.elims by metis
  next
    case False
    let ?mat = "matrix_assign lit matrix"
    have "cleansed_p (remove_var_prefix ?z (ExistentialFirst (y, ys) qs), ?mat)"
      using 7 by simp
    moreover have "x ∉ set (pcnf_prefix_variables (remove_var_prefix ?z (ExistentialFirst (y, ys) qs), ?mat))"
      using 7(2) False prefix_assign_vars_eq_prefix_vars_minus_lit[of ?z] prefix_vars_matrix_inv
      by fastforce
    ultimately have "cleansed_p (add_existential_to_prefix x (remove_var_prefix ?z (ExistentialFirst (y, ys) qs)), ?mat)"
      using cleansed_add_new_ex_to_front add_ex_to_prefix_eq_add_to_front by simp
    then have "cleansed_p (remove_var_prefix ?z (ExistentialFirst (x, (y # ys)) qs), ?mat)"
      using False by simp
    then show ?thesis by simp
  qed
qed auto
subsubsection ‹Cleansing PCNF Formulas›
function pcnf_cleanse :: "pcnf ⇒ pcnf" where
  "pcnf_cleanse (Empty, matrix) = (Empty, matrix)"
| "pcnf_cleanse (UniversalFirst (y, ys) qs, matrix) =
    (if y ∈ set (pcnf_prefix_variables (prefix_pop (UniversalFirst (y, ys) qs), matrix))
      then pcnf_cleanse (prefix_pop (UniversalFirst (y, ys) qs), matrix)
      else add_universal_to_front y
        (pcnf_cleanse (prefix_pop (UniversalFirst (y, ys) qs), matrix)))"
| "pcnf_cleanse (ExistentialFirst (x, xs) qs, matrix) =
    (if x ∈ set (pcnf_prefix_variables (prefix_pop (ExistentialFirst (x, xs) qs), matrix))
      then pcnf_cleanse (prefix_pop (ExistentialFirst (x, xs) qs), matrix)
      else add_existential_to_front x
        (pcnf_cleanse (prefix_pop (ExistentialFirst (x, xs) qs), matrix)))"
  by pat_completeness auto
termination
  by (relation "measure (λ(pre, mat). prefix_measure pre)")
     (auto simp add: prefix_pop_decreases_measure simp del: prefix_measure.simps)
text ‹Simple tests.›
value "pcnf_cleanse (UniversalFirst (0, [0]) [(0, [1, 2, 0, 1])], [])"
subsubsection ‹Cleansing Yields a Cleansed Formula›
lemma prefix_pop_all_prefix_vars_set:
  "set (pcnf_prefix_variables (UniversalFirst (y, ys) qs, matrix))
  = {y} ∪ set (pcnf_prefix_variables (prefix_pop (UniversalFirst (y, ys) qs), matrix))"
  by (induction "(UniversalFirst (y, ys) qs, matrix)" rule: convert.induct, induction qs) auto
lemma prefix_pop_ex_prefix_vars_set:
  "set (pcnf_prefix_variables (ExistentialFirst (x, xs) qs, matrix))
  = {x} ∪ set (pcnf_prefix_variables (prefix_pop (ExistentialFirst (x, xs) qs), matrix))"
  by (induction "(ExistentialFirst (x, xs) qs, matrix)" rule: convert.induct, induction qs) auto
lemma cleanse_prefix_vars_inv:
  "set (pcnf_prefix_variables (prefix, matrix))
  = set (pcnf_prefix_variables (pcnf_cleanse (prefix, matrix)))"
  using add_all_adds_prefix_var prefix_pop_all_prefix_vars_set
    add_ex_adds_prefix_var prefix_pop_ex_prefix_vars_set
  by (induction "(prefix, matrix)" arbitrary: prefix rule: pcnf_cleanse.induct) auto
theorem pcnf_cleanse_cleanses:
  "cleansed_p (pcnf_cleanse pcnf)"
  using cleanse_prefix_vars_inv cleansed_add_new_all_to_front cleansed_add_new_ex_to_front
  by (induction pcnf rule: pcnf_cleanse.induct) auto
subsubsection ‹Cleansing Preserves the Set of Free Variables›
lemma prefix_pop_all_vars_inv:
  "set (pcnf_variables (UniversalFirst (y, ys) qs, matrix))
  = set (pcnf_variables (prefix_pop (UniversalFirst (y, ys) qs), matrix))"
  by (induction "(UniversalFirst (y, ys) qs, matrix)" rule: convert.induct, induction qs) auto
lemma prefix_pop_ex_vars_inv:
  "set (pcnf_variables (ExistentialFirst (x, xs) qs, matrix))
  = set (pcnf_variables (prefix_pop (ExistentialFirst (x, xs) qs), matrix))"
  by (induction "(ExistentialFirst (x, xs) qs, matrix)" rule: convert.induct, induction qs) auto
lemma add_all_vars_inv:
  "set (pcnf_variables (add_universal_to_front y pcnf))
  = set (pcnf_variables pcnf)"
  using convert_add_all by auto
lemma add_ex_vars_inv:
  "set (pcnf_variables (add_existential_to_front x pcnf))
  = set (pcnf_variables pcnf)"
  using convert_add_ex by auto
lemma cleanse_vars_inv:
  "set (pcnf_variables (prefix, matrix))
  = set (pcnf_variables (pcnf_cleanse (prefix, matrix)))"
  using add_all_vars_inv prefix_pop_all_vars_inv
    add_ex_vars_inv prefix_pop_ex_vars_inv
  by (induction "(prefix, matrix)" arbitrary: prefix rule: pcnf_cleanse.induct) auto
theorem cleanse_free_vars_inv:
  "set (pcnf_free_variables pcnf)
  = set (pcnf_free_variables (pcnf_cleanse pcnf))"
  using cleanse_prefix_vars_inv cleanse_vars_inv pcnf_free_eq_vars_minus_prefix
  by (induction pcnf) simp_all
subsubsection ‹Cleansing Preserves Semantics›
lemma pop_redundant_ex_prefix_var_semantics_eq:
  assumes "x ∈ set (pcnf_prefix_variables (prefix_pop (ExistentialFirst (x, xs) qs), matrix))"
  shows "pcnf_semantics I (ExistentialFirst (x, xs) qs, matrix)
        = pcnf_semantics I (prefix_pop (ExistentialFirst (x, xs) qs), matrix)"
proof -
  let ?pcnf = "(ExistentialFirst (x, xs) qs, matrix)"
  let ?pop = "(prefix_pop (ExistentialFirst (x, xs) qs), matrix)"
  have "set (pcnf_prefix_variables ?pcnf) = set (pcnf_prefix_variables ?pop)"
    using assms prefix_pop_ex_prefix_vars_set by auto
  hence "x ∉ set (pcnf_free_variables ?pop)"
    using assms pcnf_free_eq_vars_minus_prefix by simp
  hence 0: "∀z ∈ set (pcnf_free_variables ?pop). (I(x := b)) z = I z"
    for b by simp
  moreover have "pcnf_semantics I ?pcnf
       ⟷ pcnf_semantics (I(x := True)) ?pop
         ∨ pcnf_semantics (I(x := False)) ?pop"
    by (induction "ExistentialFirst (x, xs) qs" rule: prefix_pop.induct) auto
  ultimately show ?thesis using pcnf_semantics_eq_if_free_vars_eq by blast
qed
lemma pop_redundant_all_prefix_var_semantics_eq:
  assumes "y ∈ set (pcnf_prefix_variables (prefix_pop (UniversalFirst (y, ys) qs), matrix))"
  shows "pcnf_semantics I (UniversalFirst (y, ys) qs, matrix)
        = pcnf_semantics I (prefix_pop (UniversalFirst (y, ys) qs), matrix)"
proof -
  let ?pcnf = "(UniversalFirst (y, ys) qs, matrix)"
  let ?pop = "(prefix_pop (UniversalFirst (y, ys) qs), matrix)"
  have "set (pcnf_prefix_variables ?pcnf) = set (pcnf_prefix_variables ?pop)"
    using assms prefix_pop_all_prefix_vars_set by auto
  hence "y ∉ set (pcnf_free_variables ?pop)"
    using assms pcnf_free_eq_vars_minus_prefix by simp
  hence 0: "∀z ∈ set (pcnf_free_variables ?pop). (I(y := b)) z = I z"
    for b by simp
  moreover have "pcnf_semantics I ?pcnf
       ⟷ pcnf_semantics (I(y := True)) ?pop
         ∧ pcnf_semantics (I(y := False)) ?pop"
    by (induction "ExistentialFirst (y, ys) qs" rule: prefix_pop.induct) auto
  ultimately show ?thesis using pcnf_semantics_eq_if_free_vars_eq by blast
qed
lemma pcnf_semantics_disj_eq_add_ex:
  "pcnf_semantics (I(y := True)) pcnf ∨ pcnf_semantics (I(y := False)) pcnf
  ⟷ pcnf_semantics I (add_existential_to_front y pcnf)"
  using convert_add_ex qbf_semantics_eq_pcnf_semantics qbf_semantics_substitute_eq_assign by simp
lemma pcnf_semantics_conj_eq_add_all:
  "pcnf_semantics (I(y := True)) pcnf ∧ pcnf_semantics (I(y := False)) pcnf
  ⟷ pcnf_semantics I (add_universal_to_front y pcnf)"
  using convert_add_all qbf_semantics_eq_pcnf_semantics qbf_semantics_substitute_eq_assign by simp
theorem pcnf_cleanse_preserves_semantics:
  "pcnf_semantics I pcnf = pcnf_semantics I (pcnf_cleanse pcnf)"
proof (induction pcnf arbitrary: I rule: pcnf_cleanse.induct)
  case (2 y ys qs matrix)
  hence 0: "pcnf_semantics I (prefix_pop (UniversalFirst (y, ys) qs), matrix) =
    pcnf_semantics I (pcnf_cleanse (prefix_pop (UniversalFirst (y, ys) qs), matrix))"
    for I by cases auto
  show ?case
  proof (cases "y ∈ set (pcnf_prefix_variables (prefix_pop (UniversalFirst (y, ys) qs), matrix))")
    case True
    then show ?thesis
      using 0 pop_redundant_all_prefix_var_semantics_eq by simp
  next
    case False
    moreover have "pcnf_semantics I (UniversalFirst (y, ys) qs, matrix)
       ⟷ pcnf_semantics (I(y := True)) (prefix_pop (UniversalFirst (y, ys) qs), matrix)
         ∧ pcnf_semantics (I(y := False)) (prefix_pop (UniversalFirst (y, ys) qs), matrix)"
      by (induction "UniversalFirst (y, ys) qs" rule: prefix_pop.induct) auto
    ultimately show ?thesis using 0 pcnf_semantics_conj_eq_add_all by simp
  qed
next
  case (3 x xs qs matrix)
  hence 0: "pcnf_semantics I (prefix_pop (ExistentialFirst (x, xs) qs), matrix) =
    pcnf_semantics I (pcnf_cleanse (prefix_pop (ExistentialFirst (x, xs) qs), matrix))"
    for I by cases auto
  show ?case
  proof (cases "x ∈ set (pcnf_prefix_variables (prefix_pop (ExistentialFirst (x, xs) qs), matrix))")
    case True
    then show ?thesis
      using 0 pop_redundant_ex_prefix_var_semantics_eq by simp
  next
    case False
    moreover have "pcnf_semantics I (ExistentialFirst (x, xs) qs, matrix)
       ⟷ pcnf_semantics (I(x := True)) (prefix_pop (ExistentialFirst (x, xs) qs), matrix)
         ∨ pcnf_semantics (I(x := False)) (prefix_pop (ExistentialFirst (x, xs) qs), matrix)"
      by (induction "ExistentialFirst (x, xs) qs" rule: prefix_pop.induct) auto
    ultimately show ?thesis using 0 pcnf_semantics_disj_eq_add_ex by simp
  qed
qed auto
theorem sat_ex_first_iff_assign_disj_sat':
  assumes "cleansed_p (ExistentialFirst (x, xs) qs, matrix)"
  shows "satisfiable (convert (ExistentialFirst (x, xs) qs, matrix))
  ⟷ satisfiable (Disj
    [convert (pcnf_assign (P x) (ExistentialFirst (x, xs) qs, matrix)),
     convert (pcnf_assign (N x) (ExistentialFirst (x, xs) qs, matrix))])"
  using assms cleansed_prefix_first_ex_unique sat_ex_first_iff_assign_disj_sat by auto
theorem sat_all_first_iff_assign_conj_sat':
  assumes "cleansed_p (UniversalFirst (y, ys) qs, matrix)"
  shows "satisfiable (convert (UniversalFirst (y, ys) qs, matrix))
  ⟷ satisfiable (Conj
    [convert (pcnf_assign (P y) (UniversalFirst (y, ys) qs, matrix)),
     convert (pcnf_assign (N y) (UniversalFirst (y, ys) qs, matrix))])"
  using assms cleansed_prefix_first_all_unique sat_all_first_iff_assign_conj_sat by auto
subsection ‹Search Solver (Part 2: The Solver)›
lemma add_all_inc_prefix_measure:
  "prefix_measure (add_universal_to_prefix y prefix) = Suc (prefix_measure prefix)"
  by (induction y prefix rule: add_universal_to_prefix.induct) auto
lemma add_ex_inc_prefix_measure:
  "prefix_measure (add_existential_to_prefix x prefix) = Suc (prefix_measure prefix)"
  by (induction x prefix rule: add_universal_to_prefix.induct) auto
lemma remove_var_non_increasing_measure:
  "prefix_measure (remove_var_prefix z prefix) ≤ prefix_measure prefix"
proof (induction z prefix rule: remove_var_prefix.induct)
  case (2 x y ys qs)
  hence 0: "prefix_measure (remove_var_prefix x (prefix_pop (UniversalFirst (y, ys) qs)))
    ≤ prefix_measure (prefix_pop (UniversalFirst (y, ys) qs))"
  by (cases "x = y") (cases "prefix_pop (UniversalFirst (y, ys) qs) = Empty",simp_all)+
  show ?case
  proof (cases "x = y")
    case True
    hence "prefix_measure (remove_var_prefix x (UniversalFirst (y, ys) qs))
          = prefix_measure (remove_var_prefix x (prefix_pop (UniversalFirst (y, ys) qs)))" by simp
    also have "... ≤ prefix_measure (prefix_pop (UniversalFirst (y, ys) qs))" using 0 by simp
    also have "... ≤ prefix_measure (UniversalFirst (y, ys) qs)"
      using prefix_pop_decreases_measure less_imp_le_nat by blast
    finally show ?thesis .
  next
    case False
    hence "prefix_measure (remove_var_prefix x (UniversalFirst (y, ys) qs))
          = prefix_measure (add_universal_to_prefix y
              (remove_var_prefix x (prefix_pop (UniversalFirst (y, ys) qs))))" by simp
    also have "... ≤ Suc (prefix_measure (prefix_pop (UniversalFirst (y, ys) qs)))"
      using 0 add_all_inc_prefix_measure by simp
    also have "... ≤ prefix_measure (UniversalFirst (y, ys) qs)"
      using Suc_leI prefix_pop_decreases_measure by blast
    finally show ?thesis .
  qed
next
  case (3 x y ys qs)
  hence 0: "prefix_measure (remove_var_prefix x (prefix_pop (ExistentialFirst (y, ys) qs)))
    ≤ prefix_measure (prefix_pop (ExistentialFirst (y, ys) qs))"
  by (cases "x = y") (cases "prefix_pop (ExistentialFirst (y, ys) qs) = Empty",simp_all)+
  show ?case
  proof (cases "x = y")
    case True
    hence "prefix_measure (remove_var_prefix x (ExistentialFirst (y, ys) qs))
          = prefix_measure (remove_var_prefix x (prefix_pop (ExistentialFirst (y, ys) qs)))" by simp
    also have "... ≤ prefix_measure (prefix_pop (ExistentialFirst (y, ys) qs))" using 0 by simp
    also have "... ≤ prefix_measure (ExistentialFirst (y, ys) qs)"
      using le_eq_less_or_eq prefix_pop_decreases_measure by blast
    finally show ?thesis .
  next
    case False
    hence "prefix_measure (remove_var_prefix x (ExistentialFirst (y, ys) qs))
          = prefix_measure (add_existential_to_prefix y
              (remove_var_prefix x (prefix_pop (ExistentialFirst (y, ys) qs))))" by simp
    also have "... ≤ Suc (prefix_measure (prefix_pop (ExistentialFirst (y, ys) qs)))"
      using 0 add_ex_inc_prefix_measure by simp
    also have "... ≤ prefix_measure (ExistentialFirst (y, ys) qs)"
      using Suc_leI prefix_pop_decreases_measure by blast
    finally show ?thesis .
  qed
qed auto
fun first_var :: "prefix ⇒ nat option" where
  "first_var (ExistentialFirst (x, xs) qs) = Some x"
| "first_var (UniversalFirst (y, ys) qs) = Some y"
| "first_var Empty = None"
lemma remove_first_var_decreases_measure:
  assumes "prefix ≠ Empty"
  shows "prefix_measure (remove_var_prefix (the (first_var prefix)) prefix) < prefix_measure prefix"
  using assms
proof (induction prefix)
  case (UniversalFirst q qs)
  then show ?case
  proof (induction q)
    case (Pair y ys)
    let ?pre = "UniversalFirst (y, ys) qs"
    let ?var = "the (first_var ?pre)"
    have "prefix_measure (remove_var_prefix ?var ?pre)
         ≤ prefix_measure (prefix_pop ?pre)"
      using remove_var_non_increasing_measure by simp
    also have "... < prefix_measure ?pre"
      using prefix_pop_decreases_measure by blast
    finally show ?case .
  qed
next
  case (ExistentialFirst q qs)
  then show ?case
  proof (induction q)
    case (Pair x xs)
    let ?pre = "ExistentialFirst (x, xs) qs"
    let ?var = "the (first_var ?pre)"
    have "prefix_measure (remove_var_prefix ?var ?pre)
         ≤ prefix_measure (prefix_pop ?pre)"
      using remove_var_non_increasing_measure by simp
    also have "... < prefix_measure ?pre"
      using prefix_pop_decreases_measure by blast
    finally show ?case .
  qed
qed auto
fun first_existential :: "prefix ⇒ bool option" where
  "first_existential (ExistentialFirst q qs) = Some True"
| "first_existential (UniversalFirst q qs) = Some False"
| "first_existential Empty = None"
function search :: "pcnf ⇒ bool option" where
  "search (prefix, matrix) =
    (if [] ∈ set matrix then Some False
    else if matrix = [] then Some True
    else Option.bind (first_var prefix) (λz.
      Option.bind (first_existential prefix) (λe. if e
        then combine_options (∨)
          (search (pcnf_assign (P z) (prefix, matrix)))
          (search (pcnf_assign (N z) (prefix, matrix)))
        else combine_options (∧)
          (search (pcnf_assign (P z) (prefix, matrix)))
          (search (pcnf_assign (N z) (prefix, matrix))))))"
  by pat_completeness auto
termination
  apply (relation "measure (λ(pre, mat). prefix_measure pre)")
      apply simp
     apply (metis first_existential.simps(3) in_measure lit_var.simps(1) option.sel option.simps(3) pcnf_assign.simps prod.simps(2) remove_first_var_decreases_measure)
    apply (metis case_prod_conv first_existential.simps(3) in_measure lit_var.simps(2) option.sel option.simps(3) pcnf_assign.simps remove_first_var_decreases_measure)
   apply (metis case_prod_conv first_existential.simps(3) in_measure lit_var.simps(1) option.sel option.simps(3) pcnf_assign.simps remove_first_var_decreases_measure)
  by (metis case_prod_conv first_existential.simps(3) in_measure lit_var.simps(2) option.sel option.simps(3) pcnf_assign.simps remove_first_var_decreases_measure)
text ‹Simple tests.›
value "search (UniversalFirst (1, []) [(2, [3])], [])"
value "search (UniversalFirst (1, []) [(2, [3])], [[]])"
value "search (UniversalFirst (1, []) [(2, [3])], [[P 1]])"
value "search (UniversalFirst (1, []) [(2, [3])], [[P 1, N 2]])"
value "search (UniversalFirst (1, []) [(2, [3])], [[P 1, N 2], [N 1, P 3]])"
fun search_solver :: "pcnf ⇒ bool" where
  "search_solver pcnf = the (search (pcnf_cleanse (pcnf_existential_closure pcnf)))"
text ‹Simple tests.›
value "search_solver (UniversalFirst (1, []) [(2, [3])], [])"
value "search_solver (UniversalFirst (1, []) [(2, [3])], [[]])"
value "search_solver (UniversalFirst (1, []) [(2, [3])], [[P 1]])"
value "search_solver (UniversalFirst (1, []) [(2, [3])], [[P 1, N 2]])"
value "search_solver (UniversalFirst (1, []) [(2, [3])], [[P 1, N 2], [N 1, P 3]])"
value "search_solver (UniversalFirst (1, []) [(2, [3])], [[P 1, N 2], [N 1, P 3], [P 4]])"
value "search_solver (UniversalFirst (1, []) [(2, [3, 3, 3])], [[P 1, N 2], [N 1, P 3], [P 4]])"
subsubsection ‹Correctness of the Search Function›
lemma no_vars_if_no_free_no_prefix_vars:
  "pcnf_free_variables pcnf = [] ⟹ pcnf_prefix_variables pcnf = [] ⟹ pcnf_variables pcnf = []"
  by (metis Diff_iff list.set_intros(1) neq_Nil_conv pcnf_free_eq_vars_minus_prefix)
lemma no_vars_if_no_free_empty_prefix:
  "pcnf_free_variables (Empty, matrix) = [] ⟹ pcnf_variables (Empty, matrix) = []"
  using no_vars_if_no_free_no_prefix_vars by fastforce
lemma search_cleansed_closed_yields_Some:
  assumes "cleansed_p pcnf" and "pcnf_free_variables pcnf = []"
  shows "(∃b. search pcnf = Some b)" using assms
proof (induction pcnf rule: search.induct)
  case (1 prefix matrix)
  then show ?case
  proof (cases "[] ∈ set matrix")
    case 2: False
    then show ?thesis
    proof (cases "matrix = []")
      case 3: False
      then show ?thesis
      proof (cases "first_var prefix")
        case None
        hence "prefix = Empty" by (induction prefix) auto
        hence False using `matrix ≠ []` `[] ∉ set matrix`
            `pcnf_free_variables (prefix, matrix) = []`
            empty_clause_or_matrix_if_no_variables
            no_vars_if_no_free_empty_prefix by blast
        then show ?thesis by simp
      next
        case 4: (Some z)
        then show ?thesis
        proof (cases "first_existential prefix")
          case None
          hence "prefix = Empty" by (induction prefix) auto
          hence False using `matrix ≠ []` `[] ∉ set matrix`
              `pcnf_free_variables (prefix, matrix) = []`
              empty_clause_or_matrix_if_no_variables
              no_vars_if_no_free_empty_prefix by blast
          then show ?thesis by simp
        next
          case 5: (Some e)
          have 6: "pcnf_free_variables (pcnf_assign lit (prefix, matrix)) = []"
            for lit using pcnf_assign_free_subseteq_free_minus_lit 1(6)
              Diff_empty set_empty subset_Diff_insert subset_empty
              by metis
          then show ?thesis
          proof (cases e)
            case 7: True
            have "search (prefix, matrix)
                  = combine_options (∨)
                      (search (pcnf_assign (P z) (prefix, matrix)))
                      (search (pcnf_assign (N z) (prefix, matrix)))"
              using 2 3 4 5 7 by simp
            moreover have "∃b. search (pcnf_assign (P z) (prefix, matrix)) = Some b"
              using 2 3 4 5 6 7 1(5,6) pcnf_assign_cleansed_inv 1(1)[of z e] by blast
            moreover have "∃b. search (pcnf_assign (N z) (prefix, matrix)) = Some b"
              using 2 3 4 5 6 7 1(5,6) pcnf_assign_cleansed_inv 1(2)[of z e] by blast
            ultimately show ?thesis by force
          next
            case 7: False
            have "search (prefix, matrix)
                  = combine_options (∧)
                      (search (pcnf_assign (P z) (prefix, matrix)))
                      (search (pcnf_assign (N z) (prefix, matrix)))"
              using 2 3 4 5 7 by simp
            moreover have "∃b. search (pcnf_assign (P z) (prefix, matrix)) = Some b"
              using 2 3 4 5 6 7 1(5,6) pcnf_assign_cleansed_inv 1(3)[of z e] by blast
            moreover have "∃b. search (pcnf_assign (N z) (prefix, matrix)) = Some b"
              using 2 3 4 5 6 7 1(5,6) pcnf_assign_cleansed_inv 1(4)[of z e] by blast
            ultimately show ?thesis by force
          qed
        qed
      qed
    qed auto
  qed auto
qed
theorem search_cleansed_closed_correct:
  assumes "cleansed_p pcnf" and "pcnf_free_variables pcnf = []"
  shows "search pcnf = Some (satisfiable (convert pcnf))" using assms
proof (induction pcnf rule: search.induct)
  case (1 prefix matrix)
  then show ?case
  proof (cases "[] ∈ set matrix")
    case True
    then show ?thesis
      using false_if_empty_clause_in_matrix qbf_semantics_eq_pcnf_semantics satisfiable_def by simp
  next
    case 2: False
    then show ?thesis
    proof (cases "matrix = []")
      case True
      then show ?thesis
        using true_if_matrix_empty qbf_semantics_eq_pcnf_semantics satisfiable_def by simp
    next
      case 3: False
      then show ?thesis
      proof (cases "first_var prefix")
        case None
        hence "prefix = Empty" by (induction prefix) auto
        hence False using `matrix ≠ []` `[] ∉ set matrix`
            `pcnf_free_variables (prefix, matrix) = []`
            empty_clause_or_matrix_if_no_variables
            no_vars_if_no_free_empty_prefix by blast
        then show ?thesis by simp
      next
        case 4: (Some z)
        then show ?thesis
        proof (cases "first_existential prefix")
          case None
          hence "prefix = Empty" by (induction prefix) auto
          hence False using `matrix ≠ []` `[] ∉ set matrix`
              `pcnf_free_variables (prefix, matrix) = []`
              empty_clause_or_matrix_if_no_variables
              no_vars_if_no_free_empty_prefix by blast
          then show ?thesis by simp
        next
          case 5: (Some e)
          have 6: "pcnf_free_variables (pcnf_assign lit (prefix, matrix)) = []"
            for lit using pcnf_assign_free_subseteq_free_minus_lit 1(6)
              Diff_empty set_empty subset_Diff_insert subset_empty
            by metis
          hence 7: "∃b. search (pcnf_assign lit (prefix, matrix)) = Some b" for lit
            using search_cleansed_closed_yields_Some pcnf_assign_cleansed_inv 6 1(5,6) by blast
          then show ?thesis
          proof (cases e)
            case 8: True
            from this obtain x xs qs where prefix_def: "prefix = ExistentialFirst (x, xs) qs"
              using 5 by (induction prefix) auto
            have "search (prefix, matrix)
                  = combine_options (∨)
                      (search (pcnf_assign (P z) (prefix, matrix)))
                      (search (pcnf_assign (N z) (prefix, matrix)))"
              using 2 3 4 5 8 by simp
            hence 9: "the (search (prefix, matrix))
                  ⟷ the (search (pcnf_assign (P z) (prefix, matrix)))
                    ∨ the (search (pcnf_assign (N z) (prefix, matrix)))"
              using 7 combine_options_simps(3) option.sel by metis
            have "search (pcnf_assign (P z) (prefix, matrix))
                 = Some (satisfiable (convert (pcnf_assign (P z) (prefix, matrix))))"
              using 2 3 4 5 6 8 1(5,6) pcnf_assign_cleansed_inv 1(1)[of z e] by blast
            moreover have "set (free_variables (convert (pcnf_assign (P z) (prefix, matrix)))) = {}"
              using 6[of "P z"] by simp
            ultimately have 10: "∀I. the (search (pcnf_assign (P z) (prefix, matrix)))
                  = qbf_semantics I (convert (pcnf_assign (P z) (prefix, matrix)))"
              using semantics_eq_if_free_vars_eq[of "convert (pcnf_assign (P z) (prefix, matrix))"]
              by (auto simp add: satisfiable_def)
            have "search (pcnf_assign (N z) (prefix, matrix))
                 = Some (satisfiable (convert (pcnf_assign (N z) (prefix, matrix))))"
              using 2 3 4 5 6 8 1(5,6) pcnf_assign_cleansed_inv 1(2)[of z e] by blast
            moreover have "set (free_variables (convert (pcnf_assign (N z) (prefix, matrix)))) = {}"
              using 6[of "N z"] by simp
            ultimately have 11: "∀I. the (search (pcnf_assign (N z) (prefix, matrix)))
                  = qbf_semantics I (convert (pcnf_assign (N z) (prefix, matrix)))"
              using semantics_eq_if_free_vars_eq[of "convert (pcnf_assign (N z) (prefix, matrix))"]
              by (auto simp add: satisfiable_def)
            have "the (search (prefix, matrix))
                 = satisfiable (Disj
                   [convert (pcnf_assign (P z) (prefix, matrix)),
                    convert (pcnf_assign (N z) (prefix, matrix))])"
              using 9 10 11 satisfiable_def by simp
            hence "search (prefix, matrix)
                 = Some (satisfiable (Disj
                   [convert (pcnf_assign (P z) (prefix, matrix)),
                    convert (pcnf_assign (N z) (prefix, matrix))]))"
              using 1(5,6) search_cleansed_closed_yields_Some by fastforce
            moreover have "z = x" using prefix_def 4 by simp
            ultimately show ?thesis using sat_ex_first_iff_assign_disj_sat' prefix_def 1(5) by simp
          next
            case 8: False
            from this obtain y ys qs where prefix_def: "prefix = UniversalFirst (y, ys) qs"
              using 5 by (induction prefix) auto
            have "search (prefix, matrix)
                  = combine_options (∧)
                      (search (pcnf_assign (P z) (prefix, matrix)))
                      (search (pcnf_assign (N z) (prefix, matrix)))"
              using 2 3 4 5 8 by simp
            hence 9: "the (search (prefix, matrix))
                  ⟷ the (search (pcnf_assign (P z) (prefix, matrix)))
                    ∧ the (search (pcnf_assign (N z) (prefix, matrix)))"
              using 7 combine_options_simps(3) option.sel by metis
            have "search (pcnf_assign (P z) (prefix, matrix))
                 = Some (satisfiable (convert (pcnf_assign (P z) (prefix, matrix))))"
              using 2 3 4 5 6 8 1(5,6) pcnf_assign_cleansed_inv 1(3)[of z e] by blast
            moreover have "set (free_variables (convert (pcnf_assign (P z) (prefix, matrix)))) = {}"
              using 6[of "P z"] by simp
            ultimately have 10: "∀I. the (search (pcnf_assign (P z) (prefix, matrix)))
                  = qbf_semantics I (convert (pcnf_assign (P z) (prefix, matrix)))"
              using semantics_eq_if_free_vars_eq[of "convert (pcnf_assign (P z) (prefix, matrix))"]
              by (auto simp add: satisfiable_def)
            have "search (pcnf_assign (N z) (prefix, matrix))
                 = Some (satisfiable (convert (pcnf_assign (N z) (prefix, matrix))))"
              using 2 3 4 5 6 8 1(5,6) pcnf_assign_cleansed_inv 1(4)[of z e] by blast
            moreover have "set (free_variables (convert (pcnf_assign (N z) (prefix, matrix)))) = {}"
              using 6[of "N z"] by simp
            ultimately have 11: "∀I. the (search (pcnf_assign (N z) (prefix, matrix)))
                  = qbf_semantics I (convert (pcnf_assign (N z) (prefix, matrix)))"
              using semantics_eq_if_free_vars_eq[of "convert (pcnf_assign (N z) (prefix, matrix))"]
              by (auto simp add: satisfiable_def)
            have "the (search (prefix, matrix))
                 = satisfiable (Conj
                   [convert (pcnf_assign (P z) (prefix, matrix)),
                    convert (pcnf_assign (N z) (prefix, matrix))])"
              using 9 10 11 satisfiable_def by simp
            hence "search (prefix, matrix)
                 = Some (satisfiable (Conj
                   [convert (pcnf_assign (P z) (prefix, matrix)),
                    convert (pcnf_assign (N z) (prefix, matrix))]))"
              using 1(5,6) search_cleansed_closed_yields_Some by fastforce
            moreover have "z = y" using prefix_def 4 by simp
            ultimately show ?thesis using sat_all_first_iff_assign_conj_sat' prefix_def 1(5) by simp
          qed
        qed
      qed
    qed
  qed
qed
subsubsection ‹Correctness of the Search Solver›
theorem search_solver_correct:
  "search_solver pcnf ⟷ satisfiable (convert pcnf)"
proof -
  have "satisfiable (convert pcnf)
       = satisfiable (convert (pcnf_cleanse (pcnf_existential_closure pcnf)))"
    using pcnf_sat_iff_ex_close_sat pcnf_cleanse_preserves_semantics
      qbf_semantics_eq_pcnf_semantics satisfiable_def by simp
  moreover have "pcnf_free_variables (pcnf_cleanse (pcnf_existential_closure pcnf)) = []"
    using pcnf_ex_closure_no_free cleanse_free_vars_inv set_empty by metis
  moreover have "cleansed_p (pcnf_cleanse (pcnf_existential_closure pcnf))"
    using pcnf_cleanse_cleanses by blast
  ultimately show ?thesis using search_cleansed_closed_correct by simp
qed
end