1(* ========================================================================= *) 2(* PACKAGING UP SOLVERS TO ALLOW THEM TO COOPERATE UNIFORMLY *) 3(* Copyright (c) 2002-2004 Joe Hurd. *) 4(* ========================================================================= *) 5 6(* 7app load ["mlibUseful", "Mosml", "mlibThm", "mlibCanon", "mlibMatch", "mlibMeter", "mlibUnits"]; 8*) 9 10(* 11*) 12structure mlibSolver :> mlibSolver = 13struct 14 15open mlibUseful mlibTerm mlibMatch mlibThm mlibMeter; 16 17structure S = mlibStream; local open mlibStream in end; 18structure U = mlibUnits; local open mlibUnits in end; 19 20type 'a stream = 'a S.stream; 21type units = U.units; 22 23val |<>| = mlibSubst.|<>|; 24val op ::> = mlibSubst.::>; 25 26(* ------------------------------------------------------------------------- *) 27(* Chatting. *) 28(* ------------------------------------------------------------------------- *) 29 30val module = "mlibSolver"; 31val () = add_trace {module = module, alignment = I} 32fun chatting l = tracing {module = module, level = l}; 33fun chat s = (trace s; true) 34 35(* ------------------------------------------------------------------------- *) 36(* Helper functions. *) 37(* ------------------------------------------------------------------------- *) 38 39fun drop_after p = 40 let 41 fun f S.NIL = S.NIL 42 | f (S.CONS (x,xs)) = S.CONS (x, if p x then K S.NIL else g xs) 43 and g xs () = f (xs ()) 44 in 45 f 46 end; 47 48fun time_to_string t = 49 let val dp = if t < 9.95 then 1 else 0 50 in Real.fmt (StringCvt.FIX (SOME dp)) t 51 end; 52 53fun infs_to_string i = 54 if i < 10000 then int_to_string i 55 else if i < 10000000 then int_to_string (i div 1000) ^ "K" 56 else int_to_string (i div 1000000) ^ "M"; 57 58fun option_case n _ NONE = n 59 | option_case _ s (SOME _) = s; 60 61(* ------------------------------------------------------------------------- *) 62(* The type of a generic solver. *) 63(* ------------------------------------------------------------------------- *) 64 65type solver = formula list -> thm list option stream; 66 67local 68 fun contr th [False] = [th] 69 | contr th gs = map (C CONTR th) gs; 70in 71 fun contradiction_solver th = 72 (assert (is_contradiction th) (Error "contradiction_solver: thm not |- F"); 73 fn gs => S.CONS (SOME (contr th gs), K S.NIL)); 74end; 75 76(* ------------------------------------------------------------------------- *) 77(* Filters to cut off searching or drop subsumed solutions. *) 78(* ------------------------------------------------------------------------- *) 79 80local 81 fun concl [] = False 82 | concl [lit] = lit 83 | concl _ = raise Bug "concl: not a literal"; 84in 85 fun solved_filter solver goals = 86 let 87 fun solves goals' = can (matchl_literals |<>|) (zip goals' goals) 88 fun final NONE = false 89 | final (SOME ths) = solves (map (concl o clause) ths) 90 in 91 drop_after final (solver goals) 92 end; 93end; 94 95local 96 fun munge s n = "MUNGED__" ^ int_to_string n ^ "__" ^ s; 97 fun munge_lit (n, Atom (Fn (p, a))) = Atom (Fn (munge p n, a)) 98 | munge_lit (n, Not (Atom (Fn (p, a)))) = Not (Atom (Fn (munge p n, a))) 99 | munge_lit _ = raise Bug "munge_lit: bad literal"; 100 fun distinctivize fms = map munge_lit (enumerate 0 fms); 101 fun advance NONE s = (SOME NONE, s) 102 | advance (SOME ths) s = 103 let 104 val fms = distinctivize (List.mapPartial (total dest_unit) ths) 105 in 106 if non S.null (mlibSubsume.subsumes s fms) then (NONE, s) 107 else (SOME (SOME ths), mlibSubsume.add (fms |-> ()) s) 108 end 109 handle Error _ => raise Bug "advance: shouldn't fail"; 110in 111 fun subsumed_filter s g = S.partial_maps advance (mlibSubsume.empty ()) (s g); 112end; 113 114(* ------------------------------------------------------------------------- *) 115(* User-friendly interface to generic solvers *) 116(* ------------------------------------------------------------------------- *) 117 118fun raw_solve s = S.partial_map I o (subsumed_filter (solved_filter s)); 119 120local 121 fun tk 0 _ = [] | tk n t = stk n (t ()) 122 and stk _ S.NIL = [] | stk n (S.CONS (h, t)) = h :: tk (n - 1) t; 123in 124 fun solve s n q = tk n (fn () => raw_solve s q); 125end; 126 127fun find s q = case solve s 1 q of [x] => SOME x | _ => NONE; 128 129fun refute s = Option.map hd (find s [False]); 130 131(* ------------------------------------------------------------------------- *) 132(* mlibSolver nodes must construct themselves from the following form. *) 133(* ------------------------------------------------------------------------- *) 134 135type form = 136 {slice : meter ref, (* A meter to stop after each slice *) 137 units : units ref, (* mlibSolvers share a unit cache *) 138 thms : thm list, (* Context, assumed consistent *) 139 hyps : thm list}; (* Hypothesis, no assumptions *) 140 141(* ------------------------------------------------------------------------- *) 142(* mlibSolver nodes also incorporate a name. *) 143(* ------------------------------------------------------------------------- *) 144 145type node_data = {name : string, solver_con : form -> solver}; 146 147datatype solver_node = mlibSolver_node of node_data; 148 149val mk_solver_node = mlibSolver_node; 150 151val pp_solver_node = pp_map (fn mlibSolver_node {name, ...} => name) pp_string; 152 153(* ------------------------------------------------------------------------- *) 154(* At each step we schedule a time slice to the least cost solver node. *) 155(* ------------------------------------------------------------------------- *) 156 157val SLICE : real ref = ref (1.0 / 3.0); 158 159datatype cost_fn = Time of real | Infs of real; 160 161fun calc_cost (Time x) ({time,...} : meter_reading) = time / x 162 | calc_cost (Infs x) {infs,...} = Real.fromInt infs / x; 163 164val pp_cost_fn = 165 pp_map (fn Time x => "time coeff " ^ real_to_string x 166 | Infs x => "infs coeff " ^ real_to_string x) pp_string; 167 168(* ------------------------------------------------------------------------- *) 169(* This allows us to hierarchically arrange solver nodes. *) 170(* ------------------------------------------------------------------------- *) 171 172local 173 fun name (mlibSolver_node {name, ...}) = name; 174 fun seq f [] = "" 175 | seq f (h :: t) = foldl (fn (n, s) => s ^ "," ^ f n) (f h) t; 176in 177 fun combine_names csolvers = "[" ^ seq (name o snd) csolvers ^ "]"; 178end; 179 180datatype subnode = Subnode of 181 {name : string, 182 used : meter_reading, 183 cost : cost_fn, 184 solns : (unit -> thm list option stream) option}; 185 186fun init_subnode (cost, (name, solver : solver)) goal = 187 Subnode 188 {name = name, 189 used = zero_reading, 190 cost = cost, 191 solns = SOME (fn () => solver goal)}; 192 193local 194 fun order ((r,_),(s,_)) = Real.compare (r,s); 195 fun munge_node (n, Subnode {solns, cost, used, ...}) = 196 if Option.isSome solns then SOME (calc_cost cost used, n) else NONE; 197in 198 fun choose_subnode l = 199 case List.mapPartial munge_node (enumerate 0 l) of [] => NONE 200 | l => SOME (snd (fst (min order l))) 201end; 202 203fun used_to_string {time,infs} = 204 "(" ^ real_to_string time ^ "," ^ int_to_string infs ^ ")"; 205 206fun subnode_info verbose (Subnode {name, used, solns, ...}) = 207 name ^ (if verbose then used_to_string used else time_to_string (#time used)) 208 ^ (case solns of NONE => "*" | SOME _ => ""); 209 210local 211 fun seq f [] = "" 212 | seq f (h :: t) = foldl (fn (n, s) => s ^ "--" ^ f n) (f h) t; 213in 214 fun status_info verbose subnodes units = 215 "[" ^ seq (subnode_info verbose) subnodes ^ "]--u=" ^ U.info units ^ "--"; 216end; 217 218fun schedule check slice read stat = 219 let 220 fun sched nodes = 221 (chatting 3 andalso chat (stat false nodes); 222 if not (check ()) then 223 (chatting 1 andalso chat "?\n"; S.CONS (NONE, fn () => sched nodes)) 224 else 225 case choose_subnode nodes of 226 NONE => (chatting 1 andalso chat "!\n"; S.NIL) 227 | SOME n => 228 let 229 val Subnode {name, used, solns, cost} = List.nth (nodes,n) 230 val _ = chatting 1 andalso chat name 231 val () = slice cost 232 val seq = (Option.valOf solns) () 233 val r = read () 234 val _ = chatting 3 andalso 235 chat ("--t=" ^ time_to_string (#time r) ^ "\n") 236 val used = add_readings used r 237 val (res, solns) = 238 case seq of S.NIL => (NONE, NONE) | S.CONS (a, r) => (a, SOME r) 239 val node = 240 Subnode {name = name, used = used, cost = cost, solns = solns} 241 val nodes = update_nth (K node) n nodes 242 in 243 if not (Option.isSome res) then sched nodes else 244 (chatting 3 andalso chat (stat true nodes); 245 chatting 1 andalso chat "#\n"; 246 S.CONS (res, fn () => sched nodes)) 247 end) 248 in 249 sched 250 end; 251 252fun combine_solvers n csolvers {slice, units, thms, hyps} = 253 let 254 val _ = chatting 3 andalso chat 255 (n ^ "--initializing--#thms=" ^ int_to_string (length thms) 256 ^ "--#hyps=" ^ int_to_string (length hyps) ^ ".\n") 257 val meter = ref (new_meter expired) 258 fun f (mlibSolver_node {name, solver_con}) = 259 (name, solver_con {slice=meter, units=units, thms=thms, hyps=hyps}) 260 val cnodes = map (I ## f) csolvers 261 fun check () = check_meter (!slice) 262 fun slce cost = 263 meter := sub_meter (!slice) (* OK *) 264 (case cost of Time x => {time = SOME (!SLICE * x), infs = NONE} 265 | Infs x => {time = NONE, infs = SOME (Real.round (!SLICE * x))}) 266 fun read () = read_meter (!meter) 267 fun stat v s = status_info v s (!units) 268 in 269 fn goal => schedule check slce read stat (map (C init_subnode goal) cnodes) 270 end; 271 272fun combine csolvers = 273 let val n = combine_names csolvers 274 in mlibSolver_node {name = n, solver_con = combine_solvers n csolvers} 275 end; 276 277(* ------------------------------------------------------------------------- *) 278(* Overriding the 'natural' set of support from the problem. *) 279(* ------------------------------------------------------------------------- *) 280 281type sos_filter = {name : string, filter : thm -> bool}; 282 283fun apply_sos_filter {name = sos, filter} (mlibSolver_node {name, solver_con}) = 284 let 285 fun solver_con' {slice, units, thms, hyps} = 286 let 287 val _ = chatting 3 andalso chat 288 (name ^ "--initializing--#thms=" ^ int_to_string (length thms) ^ 289 "--#hyps=" ^ int_to_string (length hyps) ^ 290 "--apply sos (" ^ sos ^ ").\n") 291 val (hyps',thms') = 292 if String.isPrefix "?" sos andalso not (null thms) then (hyps,thms) 293 else List.partition filter (hyps @ thms) 294 in 295 solver_con {slice = slice, units = units, thms = thms', hyps = hyps'} 296 end 297 in 298 mlibSolver_node {name = name, solver_con = solver_con'} 299 end; 300 301fun only_if_everything {name, filter} : sos_filter = 302 {name = "?" ^ name, filter = filter}; 303 304val everything : sos_filter = {name = "everything", filter = K true}; 305 306val one_negative : sos_filter = 307 {name = "one negative", 308 filter = (fn x => null x orelse List.exists negative x) o clause}; 309 310val one_positive : sos_filter = 311 {name = "one positive", 312 filter = (fn x => null x orelse List.exists positive x) o clause}; 313 314val all_negative : sos_filter = 315 {name = "all negative", filter = List.all negative o clause}; 316 317val all_positive : sos_filter = 318 {name = "all positive", filter = List.all positive o clause}; 319 320(* ------------------------------------------------------------------------- *) 321(* Initializing a solver node makes it ready for action. *) 322(* ------------------------------------------------------------------------- *) 323 324type init_data = {limit : limit, thms : thm list, hyps : thm list} 325 326fun initialize (mlibSolver_node {solver_con, ...}) {limit, thms, hyps} = 327 case List.find is_contradiction (thms @ hyps) of SOME th 328 => contradiction_solver th 329 | NONE => 330 let 331 val meter = ref (new_meter expired) 332 val units = ref U.empty 333 val solver = 334 solver_con {slice = meter, units = units, thms = thms, hyps = hyps} 335 in 336 fn g => 337 let val () = meter := new_meter limit (* OK *) 338 in drop_after (fn _ => not (check_meter (!meter))) (solver g) 339 end 340 end; 341 342end 343