src/HOL/Tools/sat_solver.ML
changeset 21267 5294ecae6708
parent 20463 062c4e9bf3bb
child 21268 7a6299a17386
equal deleted inserted replaced
21266:288a504c24d6 21267:5294ecae6708
     1 (*  Title:      HOL/Tools/sat_solver.ML
     1 (*  Title:      HOL/Tools/sat_solver.ML
     2     ID:         $Id$
     2     ID:         $Id$
     3     Author:     Tjark Weber
     3     Author:     Tjark Weber
     4     Copyright   2004-2005
     4     Copyright   2004-2006
     5 
     5 
     6 Interface to external SAT solvers, and (simple) built-in SAT solvers.
     6 Interface to external SAT solvers, and (simple) built-in SAT solvers.
     7 *)
     7 *)
     8 
     8 
     9 signature SAT_SOLVER =
     9 signature SAT_SOLVER =
   578 		val _          = try File.rm outpath
   578 		val _          = try File.rm outpath
   579 	in  case result of
   579 	in  case result of
   580 	  SatSolver.UNSATISFIABLE NONE =>
   580 	  SatSolver.UNSATISFIABLE NONE =>
   581 		(let
   581 		(let
   582 			(* string list *)
   582 			(* string list *)
   583 			val proof_lines = ((split_lines o File.read) proofpath)
   583 			val proof_lines = (split_lines o File.read) proofpath
   584 				handle IO.Io _ => raise INVALID_PROOF "Could not read file \"result.prf\""
   584 				handle IO.Io _ => raise INVALID_PROOF "Could not read file \"result.prf\""
   585 			(* a simple representation of the CNF formula as list of clauses (paired with their ID), *)
   585 			(* representation of clauses as ordered lists of literals (with duplicates removed) *)
   586 			(* where each clause is a sorted list of literals, where each literal is an int          *)
       
   587 			(* removes duplicates from an ordered list *)
       
   588 			(* int list -> int list *)
       
   589 			fun remove_dups []             = []
       
   590 			  | remove_dups [x]            = [x]
       
   591 			  | remove_dups (x :: y :: xs) = if x = y then remove_dups (y :: xs) else x :: remove_dups (y :: xs)
       
   592 			(* prop_formula -> int list *)
   586 			(* prop_formula -> int list *)
   593 			fun clause_to_lit_list (PropLogic.Or (fm1, fm2))             = clause_to_lit_list fm1 @ clause_to_lit_list fm2
   587 			fun clause_to_lit_list (PropLogic.Or (fm1, fm2)) =
   594 			  | clause_to_lit_list (PropLogic.BoolVar i)                 = [i]
   588 				OrdList.union int_ord (clause_to_lit_list fm1) (clause_to_lit_list fm2)
   595 			  | clause_to_lit_list (PropLogic.Not (PropLogic.BoolVar i)) = [~i]
   589 			  | clause_to_lit_list (PropLogic.BoolVar i) =
   596 			  | clause_to_lit_list _                 = raise INVALID_PROOF "Error: invalid clause in CNF formula."
   590 				[i]
   597 			(* prop_formula -> int list list *)
   591 			  | clause_to_lit_list (PropLogic.Not (PropLogic.BoolVar i)) =
   598 			fun cnf_to_clause_list (PropLogic.And (fm1, fm2)) = cnf_to_clause_list fm1 @ cnf_to_clause_list fm2
   592 				[~i]
   599 			  | cnf_to_clause_list fm                         = [(remove_dups o sort int_ord o clause_to_lit_list) fm]
   593 			  | clause_to_lit_list _ =
   600 			(* (int list * int) list * int *)
   594 				raise INVALID_PROOF "Error: invalid clause in CNF formula."
   601 			val (clauses, length_clauses) = fold_map (fn clause => fn n => ((clause, n), n+1)) (cnf_to_clause_list cnf) 0
   595 			(* prop_formula -> int *)
       
   596 			fun cnf_number_of_clauses (PropLogic.And (fm1, fm2)) =
       
   597 				cnf_number_of_clauses fm1 + cnf_number_of_clauses fm2
       
   598 			  | cnf_number_of_clauses _ =
       
   599 				1
       
   600 			val number_of_clauses = cnf_number_of_clauses cnf
       
   601 			(* int list array *)
       
   602 			val clauses = Array.array (number_of_clauses, [])
       
   603 			(* initialize the 'clauses' array *)
       
   604 			(* prop_formula * int -> int *)
       
   605 			fun init_array (PropLogic.And (fm1, fm2), n) =
       
   606 				init_array (fm2, init_array (fm1, n))
       
   607 			  | init_array (fm, n) =
       
   608 				(Array.update (clauses, n, clause_to_lit_list fm); n+1)
       
   609 			val _ = init_array (cnf, 0)
       
   610 			(* optimization for the common case where MiniSat "R"s clauses in their *)
       
   611 			(* original order:                                                      *)
       
   612 			val last_ref_clause = ref (number_of_clauses - 1)
       
   613 			(* search the 'clauses' array for the given list of literals 'lits', *)
       
   614 			(* starting at index '!last_ref_clause + 1'                          *)
       
   615 			(* int list -> int option *)
       
   616 			fun original_clause_id lits =
       
   617 			let
       
   618 				fun original_clause_id_from index =
       
   619 					if index = number_of_clauses then
       
   620 						(* search from beginning again *)
       
   621 						original_clause_id_from 0
       
   622 					(* both 'lits' and the list of literals used in 'clauses' are sorted, so *)
       
   623 					(* testing for equality should suffice -- barring duplicate literals     *)
       
   624 					else if Array.sub (clauses, index) = lits then (
       
   625 						(* success *)
       
   626 						last_ref_clause := index;
       
   627 						SOME index
       
   628 					) else if index = !last_ref_clause then
       
   629 						(* failure *)
       
   630 						NONE
       
   631 					else
       
   632 						(* continue search *)
       
   633 						original_clause_id_from (index + 1)
       
   634 			in
       
   635 				original_clause_id_from (!last_ref_clause + 1)
       
   636 			end
   602 			(* string -> int *)
   637 			(* string -> int *)
   603 			fun int_from_string s = (
   638 			fun int_from_string s = (
   604 				case Int.fromString s of
   639 				case Int.fromString s of
   605 				  SOME i => i
   640 				  SOME i => i
   606 				| NONE   => raise INVALID_PROOF ("File format error: number expected (" ^ quote s ^ " encountered).")
   641 				| NONE   => raise INVALID_PROOF ("File format error: number expected (" ^ quote s ^ " encountered).")
   607 			)
   642 			)
   608 			(* parse the proof file *)
   643 			(* parse the proof file *)
       
   644 			val clause_table  = ref (Inttab.empty : int list Inttab.table)
       
   645 			val empty_id      = ref ~1
   609 			(* contains a mapping from clause IDs as used by MiniSat to clause IDs in *)
   646 			(* contains a mapping from clause IDs as used by MiniSat to clause IDs in *)
   610 			(* our proof format, where original clauses are numbered starting from 0  *)
   647 			(* our proof format, where original clauses are numbered starting from 0  *)
   611 			val clause_id_map = ref (Inttab.empty : int Inttab.table)
   648 			val clause_id_map = ref (Inttab.empty : int Inttab.table)
   612 			fun sat_to_proof id = (
   649 			fun sat_to_proof id = (
   613 				case Inttab.lookup (!clause_id_map) id of
   650 				case Inttab.lookup (!clause_id_map) id of
   614 				  SOME id' => id'
   651 				  SOME id' => id'
   615 				| NONE     => raise INVALID_PROOF ("Clause ID " ^ Int.toString id ^ " used, but not defined.")
   652 				| NONE     => raise INVALID_PROOF ("Clause ID " ^ Int.toString id ^ " used, but not defined.")
   616 			)
   653 			)
   617 			val next_id = ref (length_clauses - 1)
   654 			val next_id = ref (number_of_clauses - 1)
   618 			(* string list -> proof -> proof *)
   655 			(* string list -> unit *)
   619 			fun process_tokens [] proof =
   656 			fun process_tokens [] =
   620 				proof
   657 				()
   621 			  | process_tokens (tok::toks) (clause_table, empty_id) =
   658 			  | process_tokens (tok::toks) =
   622 				if tok="R" then (
   659 				if tok="R" then (
   623 					case toks of
   660 					case toks of
   624 					  id::sep::lits =>
   661 					  id::sep::lits =>
   625 						let
   662 						let
   626 							val _        = if empty_id = ~1 then () else raise INVALID_PROOF "File format error: \"R\" disallowed after \"X\"."
   663 							val _        = if !empty_id = ~1 then () else raise INVALID_PROOF "File format error: \"R\" disallowed after \"X\"."
   627 							val cid      = int_from_string id
   664 							val cid      = int_from_string id
   628 							val _        = if sep = "<=" then () else raise INVALID_PROOF ("File format error: \"<=\" expected (" ^ quote sep ^ " encountered).")
   665 							val _        = if sep = "<=" then () else raise INVALID_PROOF ("File format error: \"<=\" expected (" ^ quote sep ^ " encountered).")
   629 							val ls       = sort int_ord (map int_from_string lits)
   666 							val ls       = sort int_ord (map int_from_string lits)
   630 							val proof_id = (* both 'ls' and the list of literals used as key in 'clauses' are sorted, *)
   667 							val proof_id = case original_clause_id ls of
   631 							               (* so testing for equality should suffice -- barring duplicate literals    *)
       
   632 							               case AList.lookup (op =) clauses ls of
       
   633 							                 SOME orig_id => orig_id
   668 							                 SOME orig_id => orig_id
   634 						                   | NONE         => raise INVALID_PROOF ("Original clause (new ID is " ^ id ^ ") not found.")
   669 							               | NONE         => raise INVALID_PROOF ("Original clause (new ID is " ^ id ^ ") not found.")
       
   670 						in
   635 							(* extend the mapping of clause IDs with this newly defined ID *)
   671 							(* extend the mapping of clause IDs with this newly defined ID *)
   636 							val _        = clause_id_map := Inttab.update_new (cid, proof_id) (!clause_id_map)
   672 							clause_id_map := Inttab.update_new (cid, proof_id) (!clause_id_map)
   637 							                 handle Inttab.DUP _ => raise INVALID_PROOF ("File format error: clause " ^ id ^ " defined more than once (in \"R\").")
   673 								handle Inttab.DUP _ => raise INVALID_PROOF ("File format error: clause " ^ id ^ " defined more than once (in \"R\").")
   638 						in
       
   639 							(* the proof itself doesn't change *)
   674 							(* the proof itself doesn't change *)
   640 							(clause_table, empty_id)
       
   641 						end
   675 						end
   642 					| _ =>
   676 					| _ =>
   643 						raise INVALID_PROOF "File format error: \"R\" followed by an insufficient number of tokens."
   677 						raise INVALID_PROOF "File format error: \"R\" followed by an insufficient number of tokens."
   644 				) else if tok="C" then (
   678 				) else if tok="C" then (
   645 					case toks of
   679 					case toks of
   646 					  id::sep::ids =>
   680 					  id::sep::ids =>
   647 						let
   681 						let
   648 							val _        = if empty_id = ~1 then () else raise INVALID_PROOF "File format error: \"C\" disallowed after \"X\"."
   682 							val _        = if !empty_id = ~1 then () else raise INVALID_PROOF "File format error: \"C\" disallowed after \"X\"."
   649 							val cid      = int_from_string id
   683 							val cid      = int_from_string id
   650 							val _        = if sep = "<=" then () else raise INVALID_PROOF ("File format error: \"<=\" expected (" ^ quote sep ^ " encountered).")
   684 							val _        = if sep = "<=" then () else raise INVALID_PROOF ("File format error: \"<=\" expected (" ^ quote sep ^ " encountered).")
   651 							(* ignore the pivot literals in MiniSat's trace *)
   685 							(* ignore the pivot literals in MiniSat's trace *)
   652 							fun unevens []             = raise INVALID_PROOF "File format error: \"C\" followed by an even number of IDs."
   686 							fun unevens []             = raise INVALID_PROOF "File format error: \"C\" followed by an even number of IDs."
   653 							  | unevens (x :: [])      = x :: []
   687 							  | unevens (x :: [])      = x :: []
   657 							val proof_id = inc next_id
   691 							val proof_id = inc next_id
   658 							val _        = clause_id_map := Inttab.update_new (cid, proof_id) (!clause_id_map)
   692 							val _        = clause_id_map := Inttab.update_new (cid, proof_id) (!clause_id_map)
   659 							                 handle Inttab.DUP _ => raise INVALID_PROOF ("File format error: clause " ^ id ^ " defined more than once (in \"C\").")
   693 							                 handle Inttab.DUP _ => raise INVALID_PROOF ("File format error: clause " ^ id ^ " defined more than once (in \"C\").")
   660 						in
   694 						in
   661 							(* update clause table *)
   695 							(* update clause table *)
   662 							(Inttab.update_new (proof_id, rs) clause_table, empty_id)
   696 							clause_table := Inttab.update_new (proof_id, rs) (!clause_table)
   663 								handle Inttab.DUP _ => raise INVALID_PROOF ("Error: internal ID for clause " ^ id ^ " already used.")
   697 								handle Inttab.DUP _ => raise INVALID_PROOF ("Error: internal ID for clause " ^ id ^ " already used.")
   664 						end
   698 						end
   665 					| _ =>
   699 					| _ =>
   666 						raise INVALID_PROOF "File format error: \"C\" followed by an insufficient number of tokens."
   700 						raise INVALID_PROOF "File format error: \"C\" followed by an insufficient number of tokens."
   667 				) else if tok="D" then (
   701 				) else if tok="D" then (
   668 					case toks of
   702 					case toks of
   669 					  [id] =>
   703 					  [id] =>
   670 						let
   704 						let
   671 							val _ = if empty_id = ~1 then () else raise INVALID_PROOF "File format error: \"D\" disallowed after \"X\"."
   705 							val _ = if !empty_id = ~1 then () else raise INVALID_PROOF "File format error: \"D\" disallowed after \"X\"."
   672 							val _ = sat_to_proof (int_from_string id)
   706 							val _ = sat_to_proof (int_from_string id)
   673 						in
   707 						in
   674 							(* simply ignore "D" *)
   708 							(* simply ignore "D" *)
   675 							(clause_table, empty_id)
   709 							()
   676 						end
   710 						end
   677 					| _ =>
   711 					| _ =>
   678 						raise INVALID_PROOF "File format error: \"D\" followed by an illegal number of tokens."
   712 						raise INVALID_PROOF "File format error: \"D\" followed by an illegal number of tokens."
   679 				) else if tok="X" then (
   713 				) else if tok="X" then (
   680 					case toks of
   714 					case toks of
   681 					  [id1, id2] =>
   715 					  [id1, id2] =>
   682 						let
   716 						let
   683 							val _            = if empty_id = ~1 then () else raise INVALID_PROOF "File format error: more than one end-of-proof statement."
   717 							val _            = if !empty_id = ~1 then () else raise INVALID_PROOF "File format error: more than one end-of-proof statement."
   684 							val _            = sat_to_proof (int_from_string id1)
   718 							val _            = sat_to_proof (int_from_string id1)
   685 							val new_empty_id = sat_to_proof (int_from_string id2)
   719 							val new_empty_id = sat_to_proof (int_from_string id2)
   686 						in
   720 						in
   687 							(* update conflict id *)
   721 							(* update conflict id *)
   688 							(clause_table, new_empty_id)
   722 							empty_id := new_empty_id
   689 						end
   723 						end
   690 					| _ =>
   724 					| _ =>
   691 						raise INVALID_PROOF "File format error: \"X\" followed by an illegal number of tokens."
   725 						raise INVALID_PROOF "File format error: \"X\" followed by an illegal number of tokens."
   692 				) else
   726 				) else
   693 					raise INVALID_PROOF ("File format error: unknown token " ^ quote tok ^ " encountered.")
   727 					raise INVALID_PROOF ("File format error: unknown token " ^ quote tok ^ " encountered.")
   694 			(* string list -> proof -> proof *)
   728 			(* string list -> unit *)
   695 			fun process_lines [] proof =
   729 			fun process_lines [] =
   696 				proof
   730 				()
   697 			  | process_lines (l::ls) proof =
   731 			  | process_lines (l::ls) = (
   698 				process_lines ls (process_tokens (String.tokens (fn c => c = #" " orelse c = #"\t") l) proof)
   732 					process_tokens (String.tokens (fn c => c = #" " orelse c = #"\t") l);
       
   733 					process_lines ls
       
   734 				)
   699 			(* proof *)
   735 			(* proof *)
   700 			val (clause_table, empty_id) = process_lines proof_lines (Inttab.empty, ~1)
   736 			val _ = process_lines proof_lines
   701 			val _                        = if empty_id <> ~1 then () else raise INVALID_PROOF "File format error: no conflicting clause specified."
   737 			val _ = if !empty_id <> ~1 then () else raise INVALID_PROOF "File format error: no conflicting clause specified."
   702 		in
   738 		in
   703 			SatSolver.UNSATISFIABLE (SOME (clause_table, empty_id))
   739 			SatSolver.UNSATISFIABLE (SOME (!clause_table, !empty_id))
   704 		end handle INVALID_PROOF reason => (warning reason; SatSolver.UNSATISFIABLE NONE))
   740 		end handle INVALID_PROOF reason => (warning reason; SatSolver.UNSATISFIABLE NONE))
   705 	| result =>
   741 	| result =>
   706 		result
   742 		result
   707 	end
   743 	end
   708 in
   744 in
   854 			  | process_lines (l::ls) = (
   890 			  | process_lines (l::ls) = (
   855 					process_tokens (String.tokens (fn c => c = #" " orelse c = #"\t") l);
   891 					process_tokens (String.tokens (fn c => c = #" " orelse c = #"\t") l);
   856 					process_lines ls
   892 					process_lines ls
   857 				)
   893 				)
   858 			(* proof *)
   894 			(* proof *)
   859 			val _ = process_lines proof_lines
   895 			val _ = tracing "process_lines"  (*TODO*)
       
   896 			val _ = timeap process_lines proof_lines  (*TODO*)
   860 			val _ = if !empty_id <> ~1 then () else raise INVALID_PROOF "File format error: no conflicting clause specified."
   897 			val _ = if !empty_id <> ~1 then () else raise INVALID_PROOF "File format error: no conflicting clause specified."
   861 		in
   898 		in
   862 			SatSolver.UNSATISFIABLE (SOME (!clause_table, !empty_id))
   899 			SatSolver.UNSATISFIABLE (SOME (!clause_table, !empty_id))
   863 		end handle INVALID_PROOF reason => (warning reason; SatSolver.UNSATISFIABLE NONE))
   900 		end handle INVALID_PROOF reason => (warning reason; SatSolver.UNSATISFIABLE NONE))
   864 	| result =>
   901 	| result =>