1
2structure dimacsTools = struct
3
4local
5
6open Lib boolLib Globals Parse Term Type Thm Drule Conv Feedback
7open satCommonTools
8
9
10in
11
12
13(*
14** Mapping from HOL variable names to DIMACS  variable numbers
15** is stored in a global assignable (i.e. reference) variable sat_var_map.
16** The type of sat_var_map is (int * (term * int) set) ref and
17** the integer first component is the next available number
18** (i.e. it is one plus the number of elements in the set)
19** in th second component (t,n), if n<0 then the literal represented is ~t
20   (the stored t is never negated)
21*)
22
23(*
24** Initialise sat_var_map to integer 1 paired with the empty set
25** (in DIMACS  variable numbering starts from 1 because 0
26** is the clause separator)
27*)
28
29structure SVM = Redblackmap
30
31val var_to_string = fst o dest_var
32
33(*
34** Reinitialise sat_var_map.
35** Needs to be done for each translation of a term to DIMACS
36** as numbers must be an initial segment of 1,2,3,...
37** (otherwise grasp, zchaff etc may crash)
38*)
39
40val ttt0 = ref T
41val ttt1 = ref T
42
43fun rbmcomp (t0,t1) =
44    Term.compare(t0,t1)
45    handle Out_of_memory => (ttt0:=t0;ttt1:=t1; print "rbmcomp\n"; raise Out_of_memory)
46
47
48
49(*
50** Lookup the var number corresponding to a +ve literal s, possibly extending sat_var_map
51*)
52
53fun lookup_sat_var svm sva s =
54 let val (c,svm1) = svm
55     val respeek = SVM.peek(svm1,s)
56 in
57 (case respeek of
58   SOME(_,n) => (n,svm)
59 | NONE      => let val svm2 = SVM.insert(svm1,s,(s,c))
60                    val svm'    =  (c+1,svm2)
61                    val _    = Array.update(sva,c,s)
62                        handle Subscript =>
63                               (failwith ("lookup_sat_varError: "^(term_to_string s)^"::"
64                                          ^(int_to_string c)^"\n"))
65                in (c,svm') end)
66 end;
67
68(*
69** Lookup the +ve lit corresponding to a var number
70*)
71fun lookup_sat_num sva n = Array.sub(sva,n)
72    handle Subscript => failwith ("lookup_sat_numError: "^(int_to_string n)^"\n")
73
74
75(*
76** Show sat_var_map as a list of its elements
77*)
78
79fun showSatVarMap svm =
80 let val (c,st) = svm
81 in
82  (c, List.map snd (SVM.listItems st))
83 end;
84
85(*
86** Print a term showing types
87*)
88
89val print_all_term = with_flag(show_types,true)print_term;
90
91(*
92** Convert a literal to a (bool * integer) pair, where
93** the boolean is true iff the literal is negated,
94** if necessary extend sat_var_map
95*)
96
97exception literalToIntError;
98fun literalToInt svm sva t =
99 let val (sign,v) =
100      if is_neg t
101       then
102        let val t1 = dest_neg t
103        in if type_of t1 = bool
104            then (true, t1)
105            else (print "``"; print_all_term t; print "``";
106                  print " is not a literal"; raise literalToIntError)
107        end
108       else if type_of t = bool then (false, t)
109       else (print "``"; print_all_term t; print "``\n";
110             print " is not a clause or literal\n"; raise literalToIntError)
111     val (v_num,svm') = lookup_sat_var svm sva v
112 in
113  ((sign, v_num),svm')
114 end;
115
116(*
117** Convert an integer (a possibly negated var number) to a literal,
118** raising lookup_sat_numError if the absolute value of
119** the integer isn't in sat_var_map
120*)
121fun intToLiteral sva n =
122    let val t = lookup_sat_num sva (abs n)
123    in if n>=0 then t else mk_neg t end
124
125(*
126** termToDimacs t
127** checks t is CNF of the form
128**  ``(v11 \/ ... \/ v1p) /\ (v21 \/ ... \/ v2q) /\ ... /\ (vr1 \/ ... \/vrt)``
129** where vij is a literal, i.e. a boolean variable or a negated
130** boolean variable.
131** If t is such a CNF then termToDimacs t returns a list of lists of integers
132** [[n11,...,n1p],[n21,...,n2q], ... , [nr1,...,nrt]]
133** If vij is a boolean variable ``v`` then nij is the entry
134** for v in sat_var_map. If vij is ``~v``, then nij is the negation
135** of the entry for v in sat_var_map
136** N.B. Definition of termToDimacs processes last clause first,
137**      so variables are not numbered in the left-to-right order.
138**      Not clear if this matters.
139*)
140(* FIXME: if is_cnf, then assume the .cnf file is already present, and use that *)
141fun termToDimacs0 svm sva t =
142    foldr
143    (fn (c,(d,svm)) =>
144        let val (l,svm') = (foldr (fn (p,(d,svm)) =>
145                                      let val (n,svm') = literalToInt svm sva p
146                                      in (n::d,svm') end) ([],svm) (strip_disj c))
147        in (l :: d,svm') end)
148            ([],svm)
149            (strip_conj t)
150
151(* tail recursive version*)
152fun termToDimacs svm sva clauses numclauses = let
153  fun foldthis (ci,c,svm) = let
154    fun lfoldfn (p,(d,svm)) = let
155      val (n,svm') = literalToInt svm sva p
156    in
157      (n::d,svm')
158    end
159    val (l,svm') = List.foldl lfoldfn ([],svm) (List.rev (strip_disj c))
160  in
161    Array.update(numclauses,ci,l);
162    svm'
163  end
164in
165  Array.foldli foldthis svm clauses
166end
167
168(*
169** reference containing prefix used to make variables from numbers
170** when reading DIMACS
171*)
172val prefix = ref "v";
173
174fun intToPrefixedLiteral n =
175 if n >= 0
176  then mk_var(((!prefix) ^ Int.toString n), Type.bool)
177  else mk_neg(mk_var(((!prefix) ^ Int.toString(Int.abs n)), Type.bool));
178
179(*
180** buildClause [n1,...,np] builds
181** ``(!prefix)np /\ ... /\ (!prefix)n1``
182** Raises exception Empty on the empty list
183*)
184
185fun buildClause l =
186 foldl
187  (fn (n,t) => mk_disj(intToPrefixedLiteral n, t))
188  (intToPrefixedLiteral (hd l))
189  (tl l);
190
191(*
192** dimacsToTerm l
193** converts a list of integers
194** [n11,...,n1p,0,n21,...,n2q,0, ... , 0,nr1,...,nrt,0]
195** into a term in CNF of the form
196**  ``(v11 \/ ... \/ v1p) /\ (v21 \/ ... \/ v2q) /\ ... /\ (vr1 \/ ... \/vrt)``
197** where vij is a literal, i.e. a boolean variable or a negated boolena variable.
198** If nij is non-negative then vij is ``(!prefix)nij``;
199** If nij is negative ~mij then vij is ``~(!prefix)mij``;
200*)
201
202local (* dimacsToTerm_aux splits off one clause, dimacsToTerm iterates it *)
203fun dimacsToTerm_aux []     acc = (buildClause acc,[])
204 |  dimacsToTerm_aux (0::l) acc = (buildClause acc,l)
205 |  dimacsToTerm_aux (x::l) acc = dimacsToTerm_aux l (x::acc)
206in
207fun dimacsToTerm l =
208 let val (t,l1) = dimacsToTerm_aux l []
209 in
210  if null l1 then t else mk_conj(t, dimacsToTerm l1)
211 end
212end;
213
214(*
215** Convert (true,n) to "-n" and (false,n) to "n"
216*)
217
218fun LiteralToString(b,n) = if b then ("-" ^ (Int.toString n)) else Int.toString n;
219
220
221(*
222** Refererence containing name of temporary file used
223** for last invocation of a SAT solver
224*)
225
226val tmp_name = ref "undefined";
227
228(*
229** termToDimacsFile t, where t is in CNF,
230** converts t to DIMACS  and then writes out a
231** file into the temporary directory.
232** the name of the temporary file (without extension ".cnf") is returned,
233** as well as a map from vars to DIMACS numbers, and an array inverting the map
234*)
235
236fun termToDimacsFile fname clause_count var_count clauses =
237    let open TextIO;
238        val svm          = (1,  SVM.mkDict rbmcomp) (* sat_var_map *)
239        val sva          = Array.array(var_count+1,T) (* sat_var_arr *)
240    in if var_count <> 0 then (* so we don't waste time creating empty cnf file *)
241           let val numclauses = Array.array(clause_count,[(false,0)])
242               val svm'  = termToDimacs svm sva clauses numclauses
243               val tmp          = FileSys.tmpName()
244               val cnfname      = if isSome fname then (valOf fname) else tmp^".cnf";
245               val outstr       = TextIO.openOut cnfname
246               fun out s        = output(outstr,s)
247               val res = (out "c File "; out cnfname; out " generated by HolSatLib\n";
248                          out "c\n";
249                          out "p cnf ";
250                          out (Int.toString var_count); out " ";
251                          out (Int.toString clause_count); out "\n";
252                          Array.app
253                              (fn l => (List.app (fn p => (out(LiteralToString p); out " ")) l;
254                                        out "\n0\n"))
255                              numclauses;
256                          flushOut outstr;
257                          closeOut outstr;
258                          tmp_name := tmp;
259                          (tmp,cnfname,svm',sva))
260           in res end else
261       ("","",svm,sva)
262    end
263
264(*Write out DIMACS file and build svm and sva*)
265fun generateDimacs vc t clauseth nr =
266    let
267        val var_count  = if isSome vc then valOf vc else length(all_vars t)
268        val clauses = if isSome clauseth
269                      then Array.tabulate(Array.length (valOf clauseth),
270                                       fn i => fst (Array.sub(valOf clauseth,i)))
271                      else Array.fromList (strip_conj t)
272        val clause_count = if isSome nr then valOf nr else Array.length clauses
273        val (tmp,cnf,svm,sva) = termToDimacsFile NONE clause_count var_count clauses
274    in (tmp,cnf,sva,svm) end
275
276(*
277** readDimacs filename
278** reads a DIMACS  file called filename and returns
279** a term in CNF in which each number n in the DIMACS file
280** is a boolean variable (!prefix)n
281** Code below by Ken Larsen (replaces earlier implementation by MJCG)
282** Changed by HA to not reverse order of clauses, and to return var count
283*)
284
285
286fun isNewline #"\n" = true
287  | isNewline _     = false;
288
289fun dropLine get src =
290    StringCvt.dropl isNewline get (StringCvt.dropl (not o isNewline) get src);
291
292fun skip_p get src i =
293    if i=5 then SOME src
294    else case get src of
295             SOME (c,src') => skip_p get src' (i+1)
296           | NONE => NONE
297
298fun getInt get = Int.scan StringCvt.DEC get;
299
300fun stripPreamble get src =
301    case get src of
302        SOME(c, src') =>
303        (case c of
304             #"c" => stripPreamble get (dropLine get src')
305           | #"p" =>
306             let val src'' = skip_p get src 0
307                 val res = getInt get (valOf src'')
308             in case res of
309                    SOME (vc,src') => SOME (dropLine get src',vc)
310                  | _ =>  NONE
311             end
312           | _ => NONE)
313      | _ => NONE
314
315fun update_maps svm sva s i =
316    if is_T (Array.sub(sva,i))
317    then let val (c,svm1) = svm
318             val svm2 = SVM.insert(svm1,s,(s,i))
319             val c' = if i>c then i else c
320             val svm'    =  (c'+1,svm2)
321             val _    = Array.update(sva,i,s)
322                 handle Subscript =>
323                        (failwith ("update_mapsError: "^(term_to_string s)^"::"
324                                   ^(int_to_string i)^"\n"))
325         in svm' end
326    else svm
327
328
329fun getIntClause sva svm get src =
330    let fun loop src (acc,svm) =
331            case getInt get src of
332                SOME(i, src) => if i = 0 then SOME((acc,svm), src)
333                                else
334                                    let val ai = (if i<0 then ~i else i)
335                                        val v = intToPrefixedLiteral ai
336                                        val svm' = update_maps svm sva v ai
337                                    in loop src ((i::acc),svm') end
338              | NONE         => if List.null acc then NONE
339                                else SOME((acc,svm), src)
340    in  loop src ([],svm)
341    end
342
343(* This implementation is inspired by (and hopefully faithful to)
344** dimacsToTerm.
345*)
346
347fun getTerms sva svm get src =
348    let fun loop src (acc,svm,sva) =
349            let val res =  getIntClause sva svm get src
350            in case res of
351                SOME((ns,svm'), src) => loop src (buildClause ns::acc,svm',sva)
352              | NONE          => SOME((acc,svm,sva), src)
353            end
354    in case getIntClause sva svm get src of
355           SOME((ns,svm'), src) => loop src ([buildClause ns],svm',sva)
356         | NONE          => NONE
357    end;
358
359fun readTerms get src =
360    case stripPreamble get src of
361        SOME (src,var_count) =>
362                  let val svm = (0,  SVM.mkDict rbmcomp) (* sat_var_map *)
363                      val sva = Array.array(var_count+1,T) (* sat_var_arr *)
364                  in getTerms sva svm get src end
365      | NONE     => NONE
366
367
368exception readDimacsError;
369
370fun genReadDimacs filename =
371 let val fullfilename = Path.mkAbsolute{path = filename,
372                                        relativeTo = FileSys.getDir()}
373     val ins          = TextIO.openIn fullfilename
374     val res = TextIO.scanStream readTerms ins
375 in  (TextIO.closeIn ins;
376      case res of
377          SOME (cs,svm,sva) => (list_mk_conj (List.rev cs),svm,sva)
378        | NONE => raise readDimacsError)
379 end;
380
381fun readDimacs filename = #1 (genReadDimacs filename)
382
383end
384end
385