48 (* ------------------------------------------------------------------------- *) |
49 (* ------------------------------------------------------------------------- *) |
49 |
50 |
50 type assignment = int -> bool option; |
51 type assignment = int -> bool option; |
51 |
52 |
52 (* ------------------------------------------------------------------------- *) |
53 (* ------------------------------------------------------------------------- *) |
|
54 (* a proof of unsatisfiability, to be interpreted as follows: each integer *) |
|
55 (* is a clause ID, each list 'xs' stored under the key 'x' in the table *) |
|
56 (* contains the IDs of clauses that must be resolved (in the given *) |
|
57 (* order) to obtain the new clause 'x'. Each list 'xs' must be *) |
|
58 (* non-empty, and the literal to be resolved upon must always be unique *) |
|
59 (* (e.g. "A | ~B" must not be resolved with "~A| B"). Circular *) |
|
60 (* dependencies of clauses are not allowed. (At least) one of the *) |
|
61 (* clauses in the table must be the empty clause (i.e. contain no *) |
|
62 (* literals); its ID is given by the second component of the proof. *) |
|
63 (* The clauses of the original problem passed to the SAT solver have *) |
|
64 (* consecutive IDs starting with 0. Clause IDs must be non-negative, *) |
|
65 (* but do not need to be consecutive. *) |
|
66 (* ------------------------------------------------------------------------- *) |
|
67 |
|
68 type proof = int list Inttab.table * int; |
|
69 |
|
70 (* ------------------------------------------------------------------------- *) |
53 (* return type of SAT solvers: if the result is 'SATISFIABLE', a satisfying *) |
71 (* return type of SAT solvers: if the result is 'SATISFIABLE', a satisfying *) |
54 (* assignment must be returned as well *) |
72 (* assignment must be returned as well; if the result is *) |
|
73 (* 'UNSATISFIABLE', a proof of unsatisfiability may be returned *) |
55 (* ------------------------------------------------------------------------- *) |
74 (* ------------------------------------------------------------------------- *) |
56 |
75 |
57 datatype result = SATISFIABLE of assignment |
76 datatype result = SATISFIABLE of assignment |
58 | UNSATISFIABLE |
77 | UNSATISFIABLE of proof option |
59 | UNKNOWN; |
78 | UNKNOWN; |
60 |
79 |
61 (* ------------------------------------------------------------------------- *) |
80 (* ------------------------------------------------------------------------- *) |
62 (* type of SAT solvers: given a propositional formula, a satisfying *) |
81 (* type of SAT solvers: given a propositional formula, a satisfying *) |
63 (* assignment may be returned *) |
82 (* assignment may be returned *) |
525 |
542 |
526 (* ------------------------------------------------------------------------- *) |
543 (* ------------------------------------------------------------------------- *) |
527 (* zChaff (http://www.princeton.edu/~chaff/zchaff.html) *) |
544 (* zChaff (http://www.princeton.edu/~chaff/zchaff.html) *) |
528 (* ------------------------------------------------------------------------- *) |
545 (* ------------------------------------------------------------------------- *) |
529 |
546 |
|
547 (* ------------------------------------------------------------------------- *) |
|
548 (* 'zchaff_with_proofs' applies the "zchaff" prover to a formula, and if *) |
|
549 (* zChaff finds that the formula is unsatisfiable, a proof of this is read *) |
|
550 (* from a file "resolve_trace" that was generated by zChaff. See the code *) |
|
551 (* below for the expected format of the "resolve_trace" file. Aside from *) |
|
552 (* some basic syntactic checks, no verification of the proof is performed. *) |
|
553 (* ------------------------------------------------------------------------- *) |
|
554 |
|
555 (* add "zchaff_with_proofs" _before_ "zchaff" to the available solvers, so *) |
|
556 (* that the latter is preferred by the "auto" solver *) |
|
557 |
|
558 let |
|
559 exception INVALID_PROOF of string |
|
560 fun zchaff_with_proofs fm = |
|
561 case SatSolver.invoke_solver "zchaff" fm of |
|
562 SatSolver.UNSATISFIABLE NONE => |
|
563 (let |
|
564 (* string list *) |
|
565 val proof_lines = ((split_lines o File.read) (Path.unpack "resolve_trace")) |
|
566 handle _ => raise INVALID_PROOF "Could not read file \"resolve_trace\"" |
|
567 val _ = try File.rm (Path.unpack "resolve_trace") |
|
568 (* PropLogic.prop_formula -> int *) |
|
569 fun cnf_number_of_clauses (PropLogic.And (fm1, fm2)) = cnf_number_of_clauses fm1 + cnf_number_of_clauses fm2 |
|
570 | cnf_number_of_clauses _ = 1 |
|
571 (* string -> int *) |
|
572 fun int_from_string s = ( |
|
573 case Int.fromString s of |
|
574 SOME i => i |
|
575 | NONE => raise INVALID_PROOF ("File format error: number expected (" ^ quote s ^ " encountered).") |
|
576 ) |
|
577 (* parse the "resolve_trace" file *) |
|
578 (* int ref *) |
|
579 val clause_offset = ref ~1 |
|
580 (* string list -> proof -> proof *) |
|
581 fun process_tokens [] proof = |
|
582 proof |
|
583 | process_tokens (tok::toks) (clause_table, conflict_id) = |
|
584 if tok="CL:" then ( |
|
585 case toks of |
|
586 id::sep::ids => |
|
587 let |
|
588 val _ = if !clause_offset = ~1 then () else raise INVALID_PROOF ("File format error: \"CL:\" disallowed after \"VAR:\".") |
|
589 val cid = int_from_string id |
|
590 val _ = if sep = "<=" then () else raise INVALID_PROOF ("File format error: \"<=\" expected (" ^ quote sep ^ " encountered).") |
|
591 val rs = map int_from_string ids |
|
592 in |
|
593 (* update clause table *) |
|
594 (Inttab.update_new ((cid, rs), clause_table), conflict_id) |
|
595 handle Inttab.DUP _ => raise INVALID_PROOF ("File format error: clause " ^ id ^ " defined more than once.") |
|
596 end |
|
597 | _ => |
|
598 raise INVALID_PROOF "File format error: \"CL:\" followed by an insufficient number of tokens." |
|
599 ) else if tok="VAR:" then ( |
|
600 case toks of |
|
601 id::levsep::levid::valsep::valid::antesep::anteid::litsep::lits => |
|
602 let |
|
603 (* set 'clause_offset' to the largest used clause ID *) |
|
604 val _ = if !clause_offset = ~1 then clause_offset := |
|
605 (case Inttab.max_key clause_table of |
|
606 SOME id => id |
|
607 | NONE => cnf_number_of_clauses (PropLogic.defcnf fm)) |
|
608 else |
|
609 () |
|
610 val vid = int_from_string id |
|
611 val _ = if levsep = "L:" then () else raise INVALID_PROOF ("File format error: \"L:\" expected (" ^ quote levsep ^ " encountered).") |
|
612 val _ = int_from_string levid |
|
613 val _ = if valsep = "V:" then () else raise INVALID_PROOF ("File format error: \"V:\" expected (" ^ quote valsep ^ " encountered).") |
|
614 val _ = int_from_string valid |
|
615 val _ = if antesep = "A:" then () else raise INVALID_PROOF ("File format error: \"A:\" expected (" ^ quote antesep ^ " encountered).") |
|
616 val aid = int_from_string anteid |
|
617 val _ = if litsep = "Lits:" then () else raise INVALID_PROOF ("File format error: \"Lits:\" expected (" ^ quote litsep ^ " encountered).") |
|
618 val ls = map int_from_string lits |
|
619 (* convert the data provided by zChaff to our resolution-style proof format *) |
|
620 (* each "VAR:" line defines a unit clause, the resolvents are implicitly *) |
|
621 (* given by the literals in the antecedent clause *) |
|
622 (* we use the sum of '!clause_offset' and the variable ID as clause ID for the unit clause *) |
|
623 val cid = !clause_offset + vid |
|
624 (* the low bit of each literal gives its sign (positive/negative), therefore *) |
|
625 (* we have to divide each literal by 2 to obtain the proper variable ID; then *) |
|
626 (* we add '!clause_offset' to obtain the ID of the corresponding unit clause *) |
|
627 val vids = filter (not_equal vid) (map (fn l => l div 2) ls) |
|
628 val rs = aid :: map (fn v => !clause_offset + v) vids |
|
629 in |
|
630 (* update clause table *) |
|
631 (Inttab.update_new ((cid, rs), clause_table), conflict_id) |
|
632 handle Inttab.DUP _ => raise INVALID_PROOF ("File format error: clause " ^ string_of_int cid ^ " (antecedent for variable " ^ id ^ ") defined more than once.") |
|
633 end |
|
634 | _ => |
|
635 raise INVALID_PROOF "File format error: \"VAR:\" followed by an insufficient number of tokens." |
|
636 ) else if tok="CONF:" then ( |
|
637 case toks of |
|
638 id::sep::ids => |
|
639 let |
|
640 val cid = int_from_string id |
|
641 val _ = if sep = "==" then () else raise INVALID_PROOF ("File format error: \"==\" expected (" ^ quote sep ^ " encountered).") |
|
642 val _ = map int_from_string ids |
|
643 val _ = if conflict_id = ~1 then () else raise INVALID_PROOF "File format error: more than one conflicting clause specified." |
|
644 in |
|
645 (* update conflict id *) |
|
646 (clause_table, cid) |
|
647 end |
|
648 | _ => |
|
649 raise INVALID_PROOF "File format error: \"CONF:\" followed by an insufficient number of tokens." |
|
650 ) else |
|
651 raise INVALID_PROOF ("File format error: unknown token " ^ quote tok ^ " encountered.") |
|
652 (* string list -> proof -> proof *) |
|
653 fun process_lines [] proof = |
|
654 proof |
|
655 | process_lines (l::ls) proof = |
|
656 process_lines ls (process_tokens (String.tokens (fn c => c = #" " orelse c = #"\t") l) proof) |
|
657 (* proof *) |
|
658 val (clause_table, conflict_id) = process_lines proof_lines (Inttab.empty, ~1) |
|
659 val _ = if conflict_id <> ~1 then () else raise INVALID_PROOF "File format error: no conflicting clause specified." |
|
660 in |
|
661 SatSolver.UNSATISFIABLE (SOME (clause_table, conflict_id)) |
|
662 end handle INVALID_PROOF reason => (warning reason; SatSolver.UNSATISFIABLE NONE)) |
|
663 | result => |
|
664 result |
|
665 in |
|
666 SatSolver.add_solver ("zchaff_with_proofs", zchaff_with_proofs) |
|
667 end; |
|
668 |
530 let |
669 let |
531 fun zchaff fm = |
670 fun zchaff fm = |
532 let |
671 let |
533 val _ = if (getenv "ZCHAFF_HOME") = "" then raise SatSolver.NOT_CONFIGURED else () |
672 val _ = if (getenv "ZCHAFF_HOME") = "" then raise SatSolver.NOT_CONFIGURED else () |
534 val _ = if (getenv "ZCHAFF_VERSION") <> "2004.5.13" andalso |
673 val _ = if (getenv "ZCHAFF_VERSION") <> "2004.5.13" andalso |