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 |