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