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