src/HOL/Tools/sat_funcs.ML
author wenzelm
Sun Mar 07 12:19:47 2010 +0100 (2010-03-07)
changeset 35625 9c818cab0dd0
parent 35408 b48ab741683b
child 38549 d0385f2764d8
permissions -rw-r--r--
modernized structure Object_Logic;
webertj@17618
     1
(*  Title:      HOL/Tools/sat_funcs.ML
webertj@17618
     2
    Author:     Stephan Merz and Alwen Tiu, QSL Team, LORIA (http://qsl.loria.fr)
wenzelm@29269
     3
    Author:     Tjark Weber, TU Muenchen
webertj@17618
     4
webertj@17618
     5
Proof reconstruction from SAT solvers.
webertj@17618
     6
webertj@17618
     7
  Description:
webertj@17618
     8
    This file defines several tactics to invoke a proof-producing
webertj@17618
     9
    SAT solver on a propositional goal in clausal form.
webertj@17618
    10
webertj@17618
    11
    We use a sequent presentation of clauses to speed up resolution
webertj@17695
    12
    proof reconstruction.
webertj@17695
    13
    We call such clauses "raw clauses", which are of the form
webertj@20440
    14
          [x1, ..., xn, P] |- False
webertj@19236
    15
    (note the use of |- instead of ==>, i.e. of Isabelle's (meta-)hyps here),
webertj@20440
    16
    where each xi is a literal (see also comments in cnf_funcs.ML).
webertj@17618
    17
webertj@19236
    18
    This does not work for goals containing schematic variables!
webertj@19236
    19
webertj@20039
    20
      The tactic produces a clause representation of the given goal
webertj@20039
    21
      in DIMACS format and invokes a SAT solver, which should return
webertj@20039
    22
      a proof consisting of a sequence of resolution steps, indicating
webertj@20039
    23
      the two input clauses, and resulting in new clauses, leading to
webertj@20039
    24
      the empty clause (i.e. "False").  The tactic replays this proof
webertj@20039
    25
      in Isabelle and thus solves the overall goal.
webertj@17618
    26
webertj@20440
    27
  There are three SAT tactics available.  They differ in the CNF transformation
webertj@20039
    28
  used. "sat_tac" uses naive CNF transformation to transform the theorem to be
webertj@20039
    29
  proved before giving it to the SAT solver.  The naive transformation in the
webertj@20440
    30
  worst case can lead to an exponential blow up in formula size.  Another
webertj@20039
    31
  tactic, "satx_tac", uses "definitional CNF transformation" which attempts to
webertj@20039
    32
  produce a formula of linear size increase compared to the input formula, at
webertj@20039
    33
  the cost of possibly introducing new variables.  See cnf_funcs.ML for more
webertj@20440
    34
  comments on the CNF transformation.  "rawsat_tac" should be used with
webertj@20440
    35
  caution: no CNF transformation is performed, and the tactic's behavior is
webertj@20440
    36
  undefined if the subgoal is not already given as [| C1; ...; Cn |] ==> False,
webertj@20440
    37
  where each Ci is a disjunction.
webertj@17618
    38
webertj@20039
    39
  The SAT solver to be used can be set via the "solver" reference.  See
webertj@20039
    40
  sat_solvers.ML for possible values, and etc/settings for required (solver-
webertj@20039
    41
  dependent) configuration settings.  To replay SAT proofs in Isabelle, you
webertj@20039
    42
  must of course use a proof-producing SAT solver in the first place.
webertj@20039
    43
webertj@20039
    44
  Proofs are replayed only if "!quick_and_dirty" is false.  If
webertj@20039
    45
  "!quick_and_dirty" is true, the theorem (in case the SAT solver claims its
webertj@20039
    46
  negation to be unsatisfiable) is proved via an oracle.
webertj@17618
    47
*)
webertj@17618
    48
webertj@17618
    49
signature SAT =
webertj@17618
    50
sig
wenzelm@32740
    51
	val trace_sat: bool Unsynchronized.ref    (* input: print trace messages *)
wenzelm@32740
    52
	val solver: string Unsynchronized.ref  (* input: name of SAT solver to be used *)
wenzelm@32740
    53
	val counter: int Unsynchronized.ref     (* output: number of resolution steps during last proof replay *)
wenzelm@32432
    54
	val rawsat_thm: Proof.context -> cterm list -> thm
wenzelm@32232
    55
	val rawsat_tac: Proof.context -> int -> tactic
wenzelm@32232
    56
	val sat_tac: Proof.context -> int -> tactic
wenzelm@32232
    57
	val satx_tac: Proof.context -> int -> tactic
webertj@17618
    58
end
webertj@17618
    59
wenzelm@32232
    60
functor SATFunc(cnf : CNF) : SAT =
webertj@17622
    61
struct
webertj@17618
    62
wenzelm@32740
    63
val trace_sat = Unsynchronized.ref false;
webertj@17622
    64
wenzelm@32740
    65
val solver = Unsynchronized.ref "zchaff_with_proofs";
wenzelm@32740
    66
  (*see HOL/Tools/sat_solver.ML for possible values*)
webertj@20039
    67
wenzelm@32740
    68
val counter = Unsynchronized.ref 0;
webertj@17622
    69
wenzelm@32010
    70
val resolution_thm =
wenzelm@32010
    71
  @{lemma "(P ==> False) ==> (~ P ==> False) ==> False" by (rule case_split)}
webertj@17622
    72
wenzelm@32010
    73
val cP = cterm_of @{theory} (Var (("P", 0), HOLogic.boolT));
webertj@20278
    74
webertj@20278
    75
(* ------------------------------------------------------------------------- *)
webertj@21768
    76
(* lit_ord: an order on integers that considers their absolute values only,  *)
webertj@21768
    77
(*      thereby treating integers that represent the same atom (positively   *)
webertj@21768
    78
(*      or negatively) as equal                                              *)
webertj@21768
    79
(* ------------------------------------------------------------------------- *)
webertj@21768
    80
webertj@21768
    81
fun lit_ord (i, j) =
webertj@21768
    82
	int_ord (abs i, abs j);
webertj@21768
    83
webertj@21768
    84
(* ------------------------------------------------------------------------- *)
webertj@20278
    85
(* CLAUSE: during proof reconstruction, three kinds of clauses are           *)
webertj@20278
    86
(*      distinguished:                                                       *)
webertj@20278
    87
(*      1. NO_CLAUSE: clause not proved (yet)                                *)
webertj@20278
    88
(*      2. ORIG_CLAUSE: a clause as it occurs in the original problem        *)
webertj@20278
    89
(*      3. RAW_CLAUSE: a raw clause, with additional precomputed information *)
webertj@20278
    90
(*         (a mapping from int's to its literals) for faster proof           *)
webertj@20278
    91
(*         reconstruction                                                    *)
webertj@20278
    92
(* ------------------------------------------------------------------------- *)
webertj@20278
    93
webertj@20278
    94
datatype CLAUSE = NO_CLAUSE
wenzelm@33243
    95
                | ORIG_CLAUSE of thm
wenzelm@33243
    96
                | RAW_CLAUSE of thm * (int * cterm) list;
webertj@20278
    97
webertj@17622
    98
(* ------------------------------------------------------------------------- *)
webertj@17809
    99
(* resolve_raw_clauses: given a non-empty list of raw clauses, we fold       *)
webertj@17809
   100
(*      resolution over the list (starting with its head), i.e. with two raw *)
webertj@17809
   101
(*      clauses                                                              *)
webertj@20440
   102
(*        [P, x1, ..., a, ..., xn] |- False                                  *)
webertj@17622
   103
(*      and                                                                  *)
webertj@20440
   104
(*        [Q, y1, ..., a', ..., ym] |- False                                 *)
webertj@19976
   105
(*      (where a and a' are dual to each other), we convert the first clause *)
webertj@19976
   106
(*      to                                                                   *)
webertj@20440
   107
(*        [P, x1, ..., xn] |- a ==> False ,                                  *)
webertj@19976
   108
(*      the second clause to                                                 *)
webertj@20440
   109
(*        [Q, y1, ..., ym] |- a' ==> False                                   *)
webertj@19976
   110
(*      and then perform resolution with                                     *)
webertj@19976
   111
(*        [| ?P ==> False; ~?P ==> False |] ==> False                        *)
webertj@19976
   112
(*      to produce                                                           *)
webertj@20440
   113
(*        [P, Q, x1, ..., xn, y1, ..., ym] |- False                          *)
webertj@21768
   114
(*      Each clause is accompanied with an association list mapping integers *)
webertj@21768
   115
(*      (positive for positive literals, negative for negative literals, and *)
webertj@21768
   116
(*      the same absolute value for dual literals) to the actual literals as *)
webertj@21768
   117
(*      cterms.                                                              *)
webertj@17622
   118
(* ------------------------------------------------------------------------- *)
webertj@17618
   119
webertj@21768
   120
(* (Thm.thm * (int * Thm.cterm) list) list -> Thm.thm * (int * Thm.cterm) list *)
webertj@17622
   121
webertj@17809
   122
fun resolve_raw_clauses [] =
wenzelm@32091
   123
      raise THM ("Proof reconstruction failed (empty list of resolvents)!", 0, [])
webertj@17809
   124
  | resolve_raw_clauses (c::cs) =
webertj@17809
   125
	let
webertj@21768
   126
		(* merges two sorted lists wrt. 'lit_ord', suppressing duplicates *)
webertj@21768
   127
		fun merge xs      []      = xs
webertj@21768
   128
		  | merge []      ys      = ys
webertj@21768
   129
		  | merge (x::xs) (y::ys) =
webertj@21768
   130
			(case (lit_ord o pairself fst) (x, y) of
webertj@21768
   131
			  LESS    => x :: merge xs (y::ys)
webertj@21768
   132
			| EQUAL   => x :: merge xs ys
webertj@21768
   133
			| GREATER => y :: merge (x::xs) ys)
webertj@21768
   134
webertj@20278
   135
		(* find out which two hyps are used in the resolution *)
webertj@21768
   136
		(* (int * Thm.cterm) list * (int * Thm.cterm) list -> (int * Thm.cterm) list -> bool * Thm.cterm * Thm.cterm * (int * Thm.cterm) list *)
webertj@21768
   137
		fun find_res_hyps ([], _) _ =
wenzelm@32091
   138
          raise THM ("Proof reconstruction failed (no literal for resolution)!", 0, [])
webertj@21768
   139
		  | find_res_hyps (_, []) _ =
wenzelm@32091
   140
          raise THM ("Proof reconstruction failed (no literal for resolution)!", 0, [])
webertj@21768
   141
		  | find_res_hyps (h1 :: hyps1, h2 :: hyps2) acc =
webertj@21768
   142
			(case (lit_ord o pairself fst) (h1, h2) of
webertj@21768
   143
			  LESS  => find_res_hyps (hyps1, h2 :: hyps2) (h1 :: acc)
webertj@21768
   144
			| EQUAL => let
webertj@21768
   145
				val (i1, chyp1) = h1
webertj@21768
   146
				val (i2, chyp2) = h2
webertj@21768
   147
			in
webertj@21768
   148
				if i1 = ~ i2 then
webertj@21768
   149
					(i1 < 0, chyp1, chyp2, rev acc @ merge hyps1 hyps2)
webertj@21768
   150
				else (* i1 = i2 *)
webertj@21768
   151
					find_res_hyps (hyps1, hyps2) (h1 :: acc)
webertj@21768
   152
			end
webertj@21768
   153
			| GREATER => find_res_hyps (h1 :: hyps1, hyps2) (h2 :: acc))
webertj@19976
   154
webertj@21768
   155
		(* Thm.thm * (int * Thm.cterm) list -> Thm.thm * (int * Thm.cterm) list -> Thm.thm * (int * Thm.cterm) list *)
webertj@21768
   156
		fun resolution (c1, hyps1) (c2, hyps2) =
webertj@17622
   157
		let
wenzelm@32091
   158
			val _ =
wenzelm@32091
   159
			  if ! trace_sat then
wenzelm@32091
   160
					tracing ("Resolving clause: " ^ Display.string_of_thm_without_context c1 ^
wenzelm@32091
   161
					  " (hyps: " ^ ML_Syntax.print_list (Syntax.string_of_term_global (theory_of_thm c1)) (#hyps (rep_thm c1))
wenzelm@32091
   162
						^ ")\nwith clause: " ^ Display.string_of_thm_without_context c2 ^
wenzelm@32091
   163
						" (hyps: " ^ ML_Syntax.print_list (Syntax.string_of_term_global (theory_of_thm c2)) (#hyps (rep_thm c2)) ^ ")")
webertj@17622
   164
				else ()
webertj@17809
   165
webertj@20278
   166
			(* the two literals used for resolution *)
webertj@21768
   167
			val (hyp1_is_neg, hyp1, hyp2, new_hyps) = find_res_hyps (hyps1, hyps2) []
webertj@19236
   168
webertj@20278
   169
			val c1' = Thm.implies_intr hyp1 c1  (* Gamma1 |- hyp1 ==> False *)
webertj@20278
   170
			val c2' = Thm.implies_intr hyp2 c2  (* Gamma2 |- hyp2 ==> False *)
webertj@17618
   171
webertj@20278
   172
			val res_thm =  (* |- (lit ==> False) ==> (~lit ==> False) ==> False *)
webertj@19236
   173
				let
webertj@20278
   174
					val cLit = snd (Thm.dest_comb (if hyp1_is_neg then hyp2 else hyp1))  (* strip Trueprop *)
webertj@19236
   175
				in
webertj@19976
   176
					Thm.instantiate ([], [(cP, cLit)]) resolution_thm
webertj@19236
   177
				end
webertj@19236
   178
wenzelm@32091
   179
			val _ =
wenzelm@32091
   180
			  if !trace_sat then
wenzelm@32091
   181
					tracing ("Resolution theorem: " ^ Display.string_of_thm_without_context res_thm)
webertj@19236
   182
				else ()
webertj@19236
   183
webertj@20440
   184
			(* Gamma1, Gamma2 |- False *)
webertj@20440
   185
			val c_new = Thm.implies_elim
webertj@20440
   186
				(Thm.implies_elim res_thm (if hyp1_is_neg then c2' else c1'))
webertj@20440
   187
				(if hyp1_is_neg then c1' else c2')
webertj@20278
   188
wenzelm@32091
   189
			val _ =
wenzelm@32091
   190
			  if !trace_sat then
wenzelm@32091
   191
					tracing ("Resulting clause: " ^ Display.string_of_thm_without_context c_new ^
wenzelm@32091
   192
					  " (hyps: " ^ ML_Syntax.print_list
wenzelm@32091
   193
					      (Syntax.string_of_term_global (theory_of_thm c_new)) (#hyps (rep_thm c_new)) ^ ")")
webertj@19236
   194
				else ()
wenzelm@32740
   195
			val _ = Unsynchronized.inc counter
webertj@17809
   196
		in
webertj@21768
   197
			(c_new, new_hyps)
webertj@17809
   198
		end
webertj@17809
   199
	in
webertj@17809
   200
		fold resolution cs c
webertj@17809
   201
	end;
webertj@17618
   202
webertj@17622
   203
(* ------------------------------------------------------------------------- *)
webertj@17809
   204
(* replay_proof: replays the resolution proof returned by the SAT solver;    *)
webertj@17809
   205
(*      cf. SatSolver.proof for details of the proof format.  Updates the    *)
webertj@17809
   206
(*      'clauses' array with derived clauses, and returns the derived clause *)
webertj@17809
   207
(*      at index 'empty_id' (which should just be "False" if proof           *)
webertj@17809
   208
(*      reconstruction was successful, with the used clauses as hyps).       *)
webertj@20278
   209
(*      'atom_table' must contain an injective mapping from all atoms that   *)
webertj@20278
   210
(*      occur (as part of a literal) in 'clauses' to positive integers.      *)
webertj@17622
   211
(* ------------------------------------------------------------------------- *)
webertj@17622
   212
webertj@20278
   213
(* int Termtab.table -> CLAUSE Array.array -> SatSolver.proof -> Thm.thm *)
webertj@17618
   214
webertj@20278
   215
fun replay_proof atom_table clauses (clause_table, empty_id) =
webertj@17622
   216
let
webertj@20278
   217
	(* Thm.cterm -> int option *)
webertj@20278
   218
	fun index_of_literal chyp = (
webertj@21586
   219
		case (HOLogic.dest_Trueprop o Thm.term_of) chyp of
webertj@20278
   220
		  (Const ("Not", _) $ atom) =>
wenzelm@33035
   221
			SOME (~ (the (Termtab.lookup atom_table atom)))
webertj@20278
   222
		| atom =>
wenzelm@33035
   223
			SOME (the (Termtab.lookup atom_table atom))
webertj@20278
   224
	) handle TERM _ => NONE;  (* 'chyp' is not a literal *)
webertj@20278
   225
webertj@21768
   226
	(* int -> Thm.thm * (int * Thm.cterm) list *)
webertj@17623
   227
	fun prove_clause id =
webertj@17623
   228
		case Array.sub (clauses, id) of
webertj@20278
   229
		  RAW_CLAUSE clause =>
webertj@20278
   230
			clause
webertj@20278
   231
		| ORIG_CLAUSE thm =>
webertj@20278
   232
			(* convert the original clause *)
webertj@17623
   233
			let
webertj@21768
   234
				val _      = if !trace_sat then tracing ("Using original clause #" ^ string_of_int id) else ()
webertj@21768
   235
				val raw    = cnf.clause2raw_thm thm
webertj@21768
   236
				val hyps   = sort (lit_ord o pairself fst) (map_filter (fn chyp =>
webertj@21768
   237
					Option.map (rpair chyp) (index_of_literal chyp)) (#hyps (Thm.crep_thm raw)))
webertj@21768
   238
				val clause = (raw, hyps)
webertj@21768
   239
				val _      = Array.update (clauses, id, RAW_CLAUSE clause)
webertj@17623
   240
			in
webertj@20278
   241
				clause
webertj@20278
   242
			end
webertj@20278
   243
		| NO_CLAUSE =>
webertj@20278
   244
			(* prove the clause, using information from 'clause_table' *)
webertj@20278
   245
			let
webertj@20278
   246
				val _      = if !trace_sat then tracing ("Proving clause #" ^ string_of_int id ^ " ...") else ()
wenzelm@33035
   247
				val ids    = the (Inttab.lookup clause_table id)
webertj@20278
   248
				val clause = resolve_raw_clauses (map prove_clause ids)
webertj@20278
   249
				val _      = Array.update (clauses, id, RAW_CLAUSE clause)
webertj@20278
   250
				val _      = if !trace_sat then tracing ("Replay chain successful; clause stored at #" ^ string_of_int id) else ()
webertj@20278
   251
			in
webertj@20278
   252
				clause
webertj@17623
   253
			end
webertj@17618
   254
webertj@17809
   255
	val _            = counter := 0
webertj@20278
   256
	val empty_clause = fst (prove_clause empty_id)
webertj@17809
   257
	val _            = if !trace_sat then tracing ("Proof reconstruction successful; " ^ string_of_int (!counter) ^ " resolution step(s) total.") else ()
webertj@17622
   258
in
webertj@17809
   259
	empty_clause
webertj@17622
   260
end;
webertj@17622
   261
webertj@20278
   262
(* ------------------------------------------------------------------------- *)
webertj@20278
   263
(* string_of_prop_formula: return a human-readable string representation of  *)
webertj@20278
   264
(*      a 'prop_formula' (just for tracing)                                  *)
webertj@20278
   265
(* ------------------------------------------------------------------------- *)
webertj@20278
   266
webertj@17809
   267
(* PropLogic.prop_formula -> string *)
webertj@20278
   268
webertj@17809
   269
fun string_of_prop_formula PropLogic.True             = "True"
webertj@17809
   270
  | string_of_prop_formula PropLogic.False            = "False"
webertj@17809
   271
  | string_of_prop_formula (PropLogic.BoolVar i)      = "x" ^ string_of_int i
webertj@17809
   272
  | string_of_prop_formula (PropLogic.Not fm)         = "~" ^ string_of_prop_formula fm
webertj@17809
   273
  | string_of_prop_formula (PropLogic.Or (fm1, fm2))  = "(" ^ string_of_prop_formula fm1 ^ " v " ^ string_of_prop_formula fm2 ^ ")"
webertj@17809
   274
  | string_of_prop_formula (PropLogic.And (fm1, fm2)) = "(" ^ string_of_prop_formula fm1 ^ " & " ^ string_of_prop_formula fm2 ^ ")";
webertj@17622
   275
webertj@17622
   276
(* ------------------------------------------------------------------------- *)
webertj@21267
   277
(* take_prefix:                                                              *)
webertj@21267
   278
(*      take_prefix n [x_1, ..., x_k] = ([x_1, ..., x_n], [x_n+1, ..., x_k]) *)
webertj@17622
   279
(* ------------------------------------------------------------------------- *)
webertj@17618
   280
webertj@21267
   281
(* int -> 'a list -> 'a list * 'a list *)
webertj@21267
   282
webertj@21267
   283
fun take_prefix n xs =
webertj@21267
   284
let
webertj@21267
   285
	fun take 0 (rxs, xs)      = (rev rxs, xs)
webertj@21267
   286
	  | take _ (rxs, [])      = (rev rxs, [])
webertj@21267
   287
	  | take n (rxs, x :: xs) = take (n-1) (x :: rxs, xs)
webertj@21267
   288
in
webertj@21267
   289
	take n ([], xs)
webertj@21267
   290
end;
webertj@17809
   291
webertj@21267
   292
(* ------------------------------------------------------------------------- *)
webertj@21267
   293
(* rawsat_thm: run external SAT solver with the given clauses.  Reconstructs *)
webertj@21267
   294
(*      a proof from the resulting proof trace of the SAT solver.  The       *)
webertj@21267
   295
(*      theorem returned is just "False" (with some of the given clauses as  *)
webertj@21267
   296
(*      hyps).                                                               *)
webertj@21267
   297
(* ------------------------------------------------------------------------- *)
webertj@21267
   298
wenzelm@32432
   299
fun rawsat_thm ctxt clauses =
webertj@17622
   300
let
webertj@17809
   301
	(* remove premises that equal "True" *)
webertj@21267
   302
	val clauses' = filter (fn clause =>
webertj@21586
   303
		(not_equal HOLogic.true_const o HOLogic.dest_Trueprop o Thm.term_of) clause
webertj@21267
   304
			handle TERM ("dest_Trueprop", _) => true) clauses
webertj@17809
   305
	(* remove non-clausal premises -- of course this shouldn't actually   *)
webertj@21267
   306
	(* remove anything as long as 'rawsat_tac' is only called after the   *)
webertj@17809
   307
	(* premises have been converted to clauses                            *)
webertj@21267
   308
	val clauses'' = filter (fn clause =>
webertj@21586
   309
		((cnf.is_clause o HOLogic.dest_Trueprop o Thm.term_of) clause
webertj@21267
   310
			handle TERM ("dest_Trueprop", _) => false)
webertj@21267
   311
		orelse (
wenzelm@32432
   312
			warning ("Ignoring non-clausal premise " ^ Syntax.string_of_term ctxt (Thm.term_of clause));
webertj@21267
   313
			false)) clauses'
webertj@17809
   314
	(* remove trivial clauses -- this is necessary because zChaff removes *)
webertj@17809
   315
	(* trivial clauses during preprocessing, and otherwise our clause     *)
webertj@17809
   316
	(* numbering would be off                                             *)
webertj@21586
   317
	val nontrivial_clauses = filter (not o cnf.clause_is_trivial o HOLogic.dest_Trueprop o Thm.term_of) clauses''
webertj@21586
   318
	(* sort clauses according to the term order -- an optimization,       *)
webertj@21586
   319
	(* useful because forming the union of hypotheses, as done by         *)
wenzelm@23533
   320
	(* Conjunction.intr_balanced and fold Thm.weaken below, is quadratic for *)
webertj@21586
   321
	(* terms sorted in descending order, while only linear for terms      *)
webertj@21586
   322
	(* sorted in ascending order                                          *)
wenzelm@35408
   323
	val sorted_clauses = sort (Term_Ord.fast_term_ord o pairself Thm.term_of) nontrivial_clauses
webertj@21267
   324
	val _ = if !trace_sat then
wenzelm@32432
   325
			tracing ("Sorted non-trivial clauses:\n" ^
wenzelm@32432
   326
			  cat_lines (map (Syntax.string_of_term ctxt o Thm.term_of) sorted_clauses))
webertj@19534
   327
		else ()
webertj@17809
   328
	(* translate clauses from HOL terms to PropLogic.prop_formula *)
webertj@21586
   329
	val (fms, atom_table) = fold_map (PropLogic.prop_formula_of_term o HOLogic.dest_Trueprop o Thm.term_of) sorted_clauses Termtab.empty
webertj@21267
   330
	val _ = if !trace_sat then
wenzelm@26931
   331
			tracing ("Invoking SAT solver on clauses:\n" ^ cat_lines (map string_of_prop_formula fms))
webertj@17809
   332
		else ()
webertj@21586
   333
	val fm = PropLogic.all fms
webertj@17842
   334
	(* unit -> Thm.thm *)
webertj@21267
   335
	fun make_quick_and_dirty_thm () =
webertj@21267
   336
	let
webertj@21267
   337
		val _ = if !trace_sat then
webertj@21267
   338
				tracing "'quick_and_dirty' is set: proof reconstruction skipped, using oracle instead."
webertj@21267
   339
			else ()
wenzelm@32970
   340
		val False_thm = Skip_Proof.make_thm @{theory} (HOLogic.Trueprop $ HOLogic.false_const)
webertj@21267
   341
	in
webertj@21586
   342
		(* 'fold Thm.weaken (rev sorted_clauses)' is linear, while 'fold    *)
webertj@21586
   343
		(* Thm.weaken sorted_clauses' would be quadratic, since we sorted   *)
webertj@21586
   344
		(* clauses in ascending order (which is linear for                  *)
wenzelm@23533
   345
		(* 'Conjunction.intr_balanced', used below)                         *)
webertj@21586
   346
		fold Thm.weaken (rev sorted_clauses) False_thm
webertj@21267
   347
	end
webertj@17618
   348
in
wenzelm@33228
   349
	case
wenzelm@33228
   350
	  let val the_solver = ! solver
wenzelm@33228
   351
	  in (tracing ("Invoking solver " ^ the_solver); SatSolver.invoke_solver the_solver fm) end
wenzelm@33228
   352
	of
webertj@17842
   353
	  SatSolver.UNSATISFIABLE (SOME (clause_table, empty_id)) => (
webertj@17842
   354
		if !trace_sat then
webertj@17842
   355
			tracing ("Proof trace from SAT solver:\n" ^
wenzelm@33228
   356
				"clauses: " ^ ML_Syntax.print_list
wenzelm@33228
   357
				  (ML_Syntax.print_pair Int.toString (ML_Syntax.print_list Int.toString))
wenzelm@33228
   358
				  (Inttab.dest clause_table) ^ "\n" ^
webertj@21586
   359
				"empty clause: " ^ Int.toString empty_id)
webertj@17842
   360
		else ();
webertj@17842
   361
		if !quick_and_dirty then
webertj@17842
   362
			make_quick_and_dirty_thm ()
webertj@17842
   363
		else
webertj@17842
   364
			let
webertj@21267
   365
				(* optimization: convert the given clauses to "[c_1 && ... && c_n] |- c_i";  *)
webertj@21267
   366
				(*               this avoids accumulation of hypotheses during resolution    *)
webertj@21267
   367
				(* [c_1, ..., c_n] |- c_1 && ... && c_n *)
wenzelm@23533
   368
				val clauses_thm = Conjunction.intr_balanced (map Thm.assume sorted_clauses)
webertj@20440
   369
				(* [c_1 && ... && c_n] |- c_1 && ... && c_n *)
webertj@21267
   370
				val cnf_cterm = cprop_of clauses_thm
webertj@20440
   371
				val cnf_thm   = Thm.assume cnf_cterm
webertj@20440
   372
				(* [[c_1 && ... && c_n] |- c_1, ..., [c_1 && ... && c_n] |- c_n] *)
wenzelm@23533
   373
				val cnf_clauses = Conjunction.elim_balanced (length sorted_clauses) cnf_thm
webertj@20278
   374
				(* initialize the clause array with the given clauses *)
wenzelm@33035
   375
				val max_idx    = the (Inttab.max_key clause_table)
webertj@20440
   376
				val clause_arr = Array.array (max_idx + 1, NO_CLAUSE)
webertj@21267
   377
				val _          = fold (fn thm => fn idx => (Array.update (clause_arr, idx, ORIG_CLAUSE thm); idx+1)) cnf_clauses 0
webertj@19236
   378
				(* replay the proof to derive the empty clause *)
webertj@20440
   379
				(* [c_1 && ... && c_n] |- False *)
webertj@21268
   380
				val raw_thm = replay_proof atom_table clause_arr (clause_table, empty_id)
webertj@17842
   381
			in
webertj@20440
   382
				(* [c_1, ..., c_n] |- False *)
webertj@21267
   383
				Thm.implies_elim (Thm.implies_intr cnf_cterm raw_thm) clauses_thm
webertj@17842
   384
			end)
webertj@17622
   385
	| SatSolver.UNSATISFIABLE NONE =>
webertj@17842
   386
		if !quick_and_dirty then (
webertj@17842
   387
			warning "SAT solver claims the formula to be unsatisfiable, but did not provide a proof";
webertj@17842
   388
			make_quick_and_dirty_thm ()
webertj@17842
   389
		) else
webertj@17842
   390
			raise THM ("SAT solver claims the formula to be unsatisfiable, but did not provide a proof", 0, [])
webertj@17622
   391
	| SatSolver.SATISFIABLE assignment =>
webertj@17622
   392
		let
webertj@17622
   393
			val msg = "SAT solver found a countermodel:\n"
webertj@17809
   394
				^ (commas
webertj@17809
   395
					o map (fn (term, idx) =>
wenzelm@33228
   396
						Syntax.string_of_term_global @{theory} term ^ ": " ^
wenzelm@33228
   397
						  (case assignment idx of NONE => "arbitrary"
wenzelm@33228
   398
							| SOME true => "true" | SOME false => "false")))
webertj@17809
   399
					(Termtab.dest atom_table)
webertj@17622
   400
		in
webertj@17622
   401
			raise THM (msg, 0, [])
webertj@17622
   402
		end
webertj@17622
   403
	| SatSolver.UNKNOWN =>
webertj@17622
   404
		raise THM ("SAT solver failed to decide the formula", 0, [])
webertj@17622
   405
end;
webertj@17618
   406
webertj@17622
   407
(* ------------------------------------------------------------------------- *)
webertj@17622
   408
(* Tactics                                                                   *)
webertj@17622
   409
(* ------------------------------------------------------------------------- *)
webertj@17618
   410
webertj@17809
   411
(* ------------------------------------------------------------------------- *)
webertj@17809
   412
(* rawsat_tac: solves the i-th subgoal of the proof state; this subgoal      *)
webertj@17809
   413
(*      should be of the form                                                *)
webertj@17809
   414
(*        [| c1; c2; ...; ck |] ==> False                                    *)
webertj@17809
   415
(*      where each cj is a non-empty clause (i.e. a disjunction of literals) *)
webertj@17809
   416
(*      or "True"                                                            *)
webertj@17809
   417
(* ------------------------------------------------------------------------- *)
webertj@17809
   418
wenzelm@32232
   419
fun rawsat_tac ctxt i =
wenzelm@32432
   420
  Subgoal.FOCUS (fn {context = ctxt', prems, ...} =>
wenzelm@32432
   421
    rtac (rawsat_thm ctxt' (map cprop_of prems)) 1) ctxt i;
webertj@17618
   422
webertj@17809
   423
(* ------------------------------------------------------------------------- *)
webertj@17809
   424
(* pre_cnf_tac: converts the i-th subgoal                                    *)
webertj@17809
   425
(*        [| A1 ; ... ; An |] ==> B                                          *)
webertj@17809
   426
(*      to                                                                   *)
webertj@17809
   427
(*        [| A1; ... ; An ; ~B |] ==> False                                  *)
webertj@17809
   428
(*      (handling meta-logical connectives in B properly before negating),   *)
webertj@17809
   429
(*      then replaces meta-logical connectives in the premises (i.e. "==>",  *)
webertj@17809
   430
(*      "!!" and "==") by connectives of the HOL object-logic (i.e. by       *)
webertj@19553
   431
(*      "-->", "!", and "="), then performs beta-eta-normalization on the    *)
webertj@19553
   432
(*      subgoal                                                              *)
webertj@17809
   433
(* ------------------------------------------------------------------------- *)
webertj@17809
   434
wenzelm@23533
   435
val pre_cnf_tac =
wenzelm@23533
   436
        rtac ccontr THEN'
wenzelm@35625
   437
        Object_Logic.atomize_prems_tac THEN'
wenzelm@23533
   438
        CONVERSION Drule.beta_eta_conversion;
webertj@17809
   439
webertj@17809
   440
(* ------------------------------------------------------------------------- *)
webertj@17809
   441
(* cnfsat_tac: checks if the empty clause "False" occurs among the premises; *)
webertj@17809
   442
(*      if not, eliminates conjunctions (i.e. each clause of the CNF formula *)
webertj@17809
   443
(*      becomes a separate premise), then applies 'rawsat_tac' to solve the  *)
webertj@17809
   444
(*      subgoal                                                              *)
webertj@17809
   445
(* ------------------------------------------------------------------------- *)
webertj@17697
   446
wenzelm@32232
   447
fun cnfsat_tac ctxt i =
wenzelm@32232
   448
	(etac FalseE i) ORELSE (REPEAT_DETERM (etac conjE i) THEN rawsat_tac ctxt i);
webertj@17618
   449
webertj@17809
   450
(* ------------------------------------------------------------------------- *)
webertj@17809
   451
(* cnfxsat_tac: checks if the empty clause "False" occurs among the          *)
webertj@17809
   452
(*      premises; if not, eliminates conjunctions (i.e. each clause of the   *)
webertj@17809
   453
(*      CNF formula becomes a separate premise) and existential quantifiers, *)
webertj@17809
   454
(*      then applies 'rawsat_tac' to solve the subgoal                       *)
webertj@17809
   455
(* ------------------------------------------------------------------------- *)
webertj@17809
   456
wenzelm@32232
   457
fun cnfxsat_tac ctxt i =
webertj@20278
   458
	(etac FalseE i) ORELSE
wenzelm@32232
   459
		(REPEAT_DETERM (etac conjE i ORELSE etac exE i) THEN rawsat_tac ctxt i);
webertj@17618
   460
webertj@17809
   461
(* ------------------------------------------------------------------------- *)
webertj@17809
   462
(* sat_tac: tactic for calling an external SAT solver, taking as input an    *)
webertj@17809
   463
(*      arbitrary formula.  The input is translated to CNF, possibly causing *)
webertj@17809
   464
(*      an exponential blowup.                                               *)
webertj@17809
   465
(* ------------------------------------------------------------------------- *)
webertj@17809
   466
wenzelm@32232
   467
fun sat_tac ctxt i =
wenzelm@32232
   468
	pre_cnf_tac i THEN cnf.cnf_rewrite_tac ctxt i THEN cnfsat_tac ctxt i;
webertj@17809
   469
webertj@17809
   470
(* ------------------------------------------------------------------------- *)
webertj@17809
   471
(* satx_tac: tactic for calling an external SAT solver, taking as input an   *)
webertj@17809
   472
(*      arbitrary formula.  The input is translated to CNF, possibly         *)
webertj@17809
   473
(*      introducing new literals.                                            *)
webertj@17809
   474
(* ------------------------------------------------------------------------- *)
webertj@17809
   475
wenzelm@32232
   476
fun satx_tac ctxt i =
wenzelm@32232
   477
	pre_cnf_tac i THEN cnf.cnfx_rewrite_tac ctxt i THEN cnfxsat_tac ctxt i;
webertj@17618
   478
wenzelm@23533
   479
end;