1structure OmegaMLShadow :> OmegaMLShadow =
2struct
3
4(* ----------------------------------------------------------------------
5    Implementation of "shadow chasing" in ML.  This procedure can find
6    a real shadow refutation without forcing all of the shadow to be
7    computed in the logic; a path to false can be found that only
8    involves a small proportion of the possible logical inferences.
9
10    Alternatively, if the shadow is exact, then this procedure can
11    also generate witnesses for the logic to exploit, again saving it
12    from having to do a lot of calculation internally.
13   ---------------------------------------------------------------------- *)
14
15open HolKernel boolLib
16
17open intSyntax
18
19(* ----------------------------------------------------------------------
20    The factoid datatype
21
22    A factoid c[0..n] represents the term
23       0 <= c[0] * v_0 + .. + c[n-1] * v_{n-1} + c[n] * 1
24    The factoid "key" is the vector of variable coefficients, leaving
25    out the constant part.
26   ---------------------------------------------------------------------- *)
27
28datatype factoid =
29         ALT of Arbint.int Vector.vector
30
31val fromArbList = ALT o Vector.fromList
32val fromList = fromArbList o map Arbint.fromInt
33
34fun extract x = VectorSlice.vector(VectorSlice.slice x)
35
36
37type factoid_key = Arbint.int Vector.vector
38
39fun factoid_key f =
40    case f of
41      ALT av => extract(av, 0, SOME (Vector.length av - 1))
42fun factoid_constant f =
43    case f of
44      ALT av => Vector.sub(av, Vector.length av - 1)
45
46fun factoid_size f =
47    case f of ALT av => Vector.length av
48
49fun split_factoid f = (factoid_key f, factoid_constant f)
50
51fun zero_var_factoid f =
52    Vector.foldl (fn (ai, b) => b andalso ai = Arbint.zero) true
53                 (factoid_key f)
54
55val the_false_factoid = ALT (Vector.fromList [Arbint.fromInt ~1])
56fun false_factoid f =
57    zero_var_factoid f andalso Arbint.<(factoid_constant f, Arbint.zero)
58fun true_factoid f =
59    zero_var_factoid f andalso Arbint.<=(Arbint.zero, factoid_constant f)
60
61fun eval_factoid_rhs vmap f = let
62  val maxdex = factoid_size f - 1
63  open Arbint Vector
64  fun foldthis (i, ai, acc) =
65      if ai = zero then acc
66      else if i = maxdex then ai + acc
67      else ai * PIntMap.find i vmap + acc
68in
69  case f of
70    ALT fv => foldli foldthis zero fv
71end
72
73fun eval_factoid_except vmap i f = let
74  val maxdex = factoid_size f - 1
75  open Arbint Vector
76  fun foldthis (j, ai, acc) =
77      if ai = zero orelse i = j then acc
78      else if j = maxdex then ai + acc
79      else ai * PIntMap.find j vmap + acc
80in
81  case f of
82    ALT fv => foldli foldthis zero fv
83end
84
85val negate_key = Vector.map Arbint.~
86
87local
88  open Arbint
89  fun add_hash (i, n, h) = (n * fromInt i) + h
90
91  fun hash (v : Arbint.int Vector.vector) : Arbint.int =
92    Vector.foldli add_hash zero v
93
94  val make_it_fit : Arbint.int -> Int.int =
95      case Int.maxInt of
96        NONE => (fn i => toInt i)
97      | SOME mx => (fn i => toInt (i mod fromInt mx))
98in
99
100
101fun keyhash (v : Arbint.int Vector.vector) : Int.int =
102    if zero <= Vector.sub(v, 0) then make_it_fit (hash v)
103    else make_it_fit (~(hash (Vector.map Arbint.~ v)))
104end;
105
106
107(* "prints" factoids *)
108fun factoid_pairings vars f =
109    case f of
110      ALT av => Vector.foldri
111                    (fn (i, ai, list) =>
112                        (Arbint.toString ai, List.nth(vars, i)) :: list)
113                    []
114                    av
115
116(* ----------------------------------------------------------------------
117    combine_real_factoids i f1 f2
118
119    takes two factoids and produces a fresh one by "variable
120    elimination" over the i'th variable in the vector.  It requires
121    that the coefficient of v_i is strictly positive in f1, and
122    strictly negative in f2.  The combination may produce a factoid where
123    the coefficients can all be divided through by some number, but
124    this factor won't be the gcd of the coefficients of v_i; this
125    factor is eliminated from the outset.
126   ---------------------------------------------------------------------- *)
127
128fun combine_real_factoids i f1 f2 =
129    case (f1, f2) of
130      (ALT av1, ALT av2) => let
131        open Arbint Vector CooperMath
132        val c0 = sub(av1, i)
133        val d0 = ~ (sub(av2, i))
134        val l = lcm (c0, d0)
135        val c = l div d0
136        val d = l div c0
137        fun gen j = c * sub(av2, j) + sub(av1, j) * d
138      in
139        ALT (tabulate(length av1, gen))
140      end
141
142(* ----------------------------------------------------------------------
143    combine_dark_factoids i lower upper
144
145    takes two factoids and combines them to produce a new, "dark shadow"
146    factoid.  As above, the first one must have a positive coefficient of
147    i, and the second a negative coefficient.
148   ---------------------------------------------------------------------- *)
149
150fun combine_dark_factoids i low up =
151    case (low, up) of
152      (ALT L, ALT U) => let
153        open Arbint Vector
154        (* have ~L <= ax /\ bx <= U *)
155        val a = sub(L, i)
156        val b = ~(sub(U, i))
157        val maxdex = Int.-(length L, 1)
158        fun gen j = let
159          val base =  a * sub(U, j) + b * sub(L, j)
160        in
161          if j = maxdex then base - ((a - one) * (b - one))
162          else base
163        end
164      in
165        ALT (tabulate(Int.+(maxdex, 1), gen))
166      end
167
168
169(* ----------------------------------------------------------------------
170    factoid_gcd f
171
172    eliminates common factors in the variable coefficients of a factoid,
173    or raises the exception no_gcd, if there is no common factor.
174   ---------------------------------------------------------------------- *)
175
176structure VS = VectorSlice
177
178exception no_gcd
179fun factoid_gcd f =
180    case f of
181      ALT av => let
182        open CooperMath Vector
183        val g = VS.foldli (fn (_, c, g0) => gcd (Arbint.abs c, g0))
184                          Arbint.zero
185                          (VS.slice(av, 0, SOME (length av - 1)))
186      in
187        if Arbint.<=(g, Arbint.one) then raise no_gcd
188        else ALT (map (fn c => Arbint.div(c, g)) av)
189      end
190
191(* ----------------------------------------------------------------------
192    term_to_factoid vars tm
193
194    returns the factoid corresponding to tm.  tm is thus of the form
195      0 <= c1 * v1 + ... + cn * vn + num
196    Assumes that the variables are in the "correct" order (as given in the
197    list vars), but that all are not necessarily present.  Omission
198    indicates a coefficient of zero, of course.
199   ---------------------------------------------------------------------- *)
200
201fun term_to_factoid vars tm = let
202  val summands = strip_plus (rand tm)
203  fun mk_coeffs vlist slist =
204    case (vlist,slist) of
205      ([],[]) => [Arbint.zero]
206    | ([],[s]) => [int_of_term s]
207    | ([], _) => raise HOL_ERR { origin_function = "term_to_factoid",
208                                origin_structure = "OmegaMLShadow",
209                                message = "Too many summands in term"}
210    | (v::vs, []) => Arbint.zero :: mk_coeffs vs []
211    | (v::vs, s::ss) =>
212        if is_mult s then let
213          val (c, mv) = dest_mult s
214        in
215          if mv = v then int_of_term c :: mk_coeffs vs ss
216          else Arbint.zero :: mk_coeffs vs slist
217        end
218        else Arbint.zero :: mk_coeffs vs slist
219in
220  ALT (Vector.fromList (mk_coeffs vars summands))
221end
222
223(* ----------------------------------------------------------------------
224    factoid_to_term vars f
225
226    returns the term corresponding to f, interpreting f over the list of
227    variables vars.
228   ---------------------------------------------------------------------- *)
229
230fun factoid_to_term vars f =
231    case f of
232      ALT av => let
233        open Vector
234        fun varcoeff (i, c, t0) =
235            if c = Arbint.zero then t0
236            else let
237                val cv = mk_mult(term_of_int c, List.nth(vars, i))
238              in
239                case t0 of NONE => SOME cv | SOME t => SOME (mk_plus(t, cv))
240              end
241        val varpart = VS.foldli varcoeff NONE
242                                (VS.slice(av, 0, SOME (length av - 1)))
243        val numpart = term_of_int (sub(av,length av - 1))
244      in
245        case varpart of
246          NONE => numpart
247        | SOME t => mk_plus(t, numpart)
248      end
249
250(* ----------------------------------------------------------------------
251    The derivation datatype
252   ---------------------------------------------------------------------- *)
253
254(* a derivation represents a proof of a factoid *)
255datatype 'a derivation =
256         ASM of 'a
257       | REAL_COMBIN of int * 'a derivation * 'a derivation
258       | GCD_CHECK of 'a derivation
259       | DIRECT_CONTR of 'a derivation * 'a derivation
260type 'a dfactoid = (factoid * 'a derivation)
261
262datatype 'a result = CONTR of 'a derivation
263                   | SATISFIABLE of Arbint.int PIntMap.t
264                   | NO_CONCL
265
266fun direct_contradiction(d1, d2) = CONTR (DIRECT_CONTR(d1, d2))
267
268fun gcd_check_dfactoid (df as (f, d)) =
269    (factoid_gcd f, GCD_CHECK d) handle no_gcd => df
270
271fun split_dfactoid (f, d) = (factoid_key f, (factoid_constant f, d))
272fun dfactoid_key (f, d) = factoid_key f
273fun dfactoid_derivation (f, d) = d
274
275fun term_to_dfactoid vars t = (term_to_factoid vars t, ASM t)
276
277(* ----------------------------------------------------------------------
278    The "elimination mode" datatype.
279
280    This records what sort of shadow we're currently working on.
281   ---------------------------------------------------------------------- *)
282
283datatype elimmode = REAL | DARK | EXACT | EDARK
284  (* REAL when we're looking for a contradiction (only)
285     DARK when we're looking for satisfiability and the problem is not
286          exact
287     EXACT when we're looking for either a contradiction or satisfiability.
288     EDARK when we're looking for satisfiability (i.e., have switched from
289           a REAL search, but where the problem is still EXACT) *)
290
291
292
293
294fun inexactify EXACT = REAL
295  | inexactify EDARK = DARK
296  | inexactify x = x
297
298fun mode_result em result =
299    case em of
300      EXACT => result
301    | REAL => (case result of SATISFIABLE _ => NO_CONCL | x => x)
302    | DARK => (case result of CONTR _ => NO_CONCL | x => x)
303    | EDARK => (case result of CONTR _ => NO_CONCL | x => x)
304
305fun combine_dfactoids em i ((f1, d1), (f2, d2)) =
306    case em of
307      DARK => (combine_dark_factoids i f1 f2, d1)
308    | _ => (combine_real_factoids i f1 f2, REAL_COMBIN(i, d1, d2))
309
310
311(* ----------------------------------------------------------------------
312    The "db" datatype
313
314    So far, I'm using a Patricia tree to store my sets of constraints,
315    so the function parameters of type "database" are often called
316    ptree.  The keys are the hashes of the constraint keys.  The items
317    are lists (buckets) of dfactoids.
318   ---------------------------------------------------------------------- *)
319
320fun fkassoc k alist =
321    case List.find (fn (f, data) => k = factoid_key f) alist of
322      NONE => raise PIntMap.NotFound
323    | SOME x => x
324
325fun lookup_fkey (ptree,w) fk =
326  fkassoc fk (PIntMap.find (keyhash fk) ptree)
327
328type 'a cstdb = ((factoid * 'a derivation) list) PIntMap.t * int
329
330fun dbempty i = (PIntMap.empty, i)
331
332fun dbfold (f : ((factoid * 'b derivation) * 'a) -> 'a) (acc:'a) (ptree,w) =
333    PIntMap.fold (fn (i,b,acc) => List.foldl f acc b) acc ptree
334
335fun dbapp (f : factoid * 'a derivation -> unit) (ptree,w) =
336    PIntMap.app (fn (i,b) => List.app f b) ptree
337
338(* does a raw insert, with no checking; the dfactoid really should have
339   width w *)
340fun dbinsert (df as (f,d)) (ptree,w) =
341    (#1 (PIntMap.addf (fn b => df :: b) (keyhash (factoid_key f)) [df] ptree),
342     w)
343
344fun dbchoose (ptree,w) = let
345  val (_, (_, a_bucket)) = PIntMap.choose ptree
346in
347  hd a_bucket
348end
349
350fun dbwidth (ptree,w) = w
351
352fun dbsize (ptree,w) = PIntMap.size ptree
353
354(* ----------------------------------------------------------------------
355    dbadd df ptree
356
357    Precondition: df is neither a trivially true nor false factoid.
358
359    adds a dfactoid (i.e., a factoid along with its derivation) to a
360    database of accumulating factoids.  If the factoid is
361    actually redundant, because there is already a factoid with the
362    same constraint key in the tree with a less or equal constant,
363    then the exception RedundantAddition is raised.  Otherwise the
364    return value is the new tree.
365   ---------------------------------------------------------------------- *)
366
367exception RedundantAddition
368fun dbadd df (ptree, w) = let
369  fun merge alist = let
370    val (fk, (fc, _)) = split_dfactoid df
371    val ((f',_), samehashes) =
372        Lib.pluck (fn (f', _) => factoid_key f' = fk) alist
373  in
374    if Arbint.<(fc, factoid_constant f') then
375      df :: samehashes
376    else raise RedundantAddition
377  end handle HOL_ERR _ => df :: alist (* HOL_ERR might come from Lib.pluck *)
378in
379  (#1 (PIntMap.addf merge (keyhash (dfactoid_key df)) [df] ptree), w)
380end
381
382(* ----------------------------------------------------------------------
383    add_check_factoid ptree df next kont
384
385    Precondition: df is neither a trivially true nor false factoid.
386    Types:
387      next:     ptree -> (result -> 'a) -> 'a
388      kont:     result -> 'a
389
390   ---------------------------------------------------------------------- *)
391
392fun add_check_factoid ptree0 (df as (f,d)) next kont = let
393  val ptree = dbadd df ptree0
394in
395  let
396    val (fk,fc) = split_factoid f
397    val (negdf as (negf, negd)) = lookup_fkey ptree (negate_key fk)
398    val negc = factoid_constant negf
399    open Arbint
400  in
401    if negc < ~fc then kont (direct_contradiction(d, negd))
402    else next ptree kont
403  end handle PIntMap.NotFound => next ptree kont
404end handle RedundantAddition => next ptree0 kont
405
406
407(* ----------------------------------------------------------------------
408    has_one_var ptree
409
410    returns true if all of ptree's factoids are over just one variable
411    (and they're all the same variable)
412   ---------------------------------------------------------------------- *)
413
414fun has_one_var ptree = let
415  exception return_false
416  fun checkkey (i,c,acc) =
417      if c = Arbint.zero then acc
418      else
419        case acc of
420          NONE => SOME i
421        | _ => raise return_false
422  fun check ((f,d),a) = let
423    val fk = factoid_key f
424    val fk_single = valOf (Vector.foldli checkkey NONE fk)
425  in
426    case a of
427      NONE => SOME fk_single
428    | SOME i => if i = fk_single then a else raise return_false
429  end
430in
431  (dbfold check NONE ptree; true) handle return_false => false
432end
433
434
435(* ----------------------------------------------------------------------
436    one_var_analysis ptree em
437
438    Precondition: the dfactoids in ptree have just one variable with a
439    non-zero coefficient, and its everywhere the same variable.  Our aim
440    is to either derive a contradiction, or to return a satisfying
441    assignment.  Our gcd_checks will have ensured that our variable
442    (call it x) will only have coefficients of one at this point, so
443    we just need to check that the maximum of the lower bounds is less
444    than or equal to the minimum of the lower bounds.  If it is then
445    return either as a satisfying valuation for x.  Otherwise return
446    false, combining the maximum lower and the minimum upper constraint.
447   ---------------------------------------------------------------------- *)
448
449fun one_var_analysis ptree em = let
450  val (a_constraint, _) = dbchoose ptree
451  fun find_var (i, ai, NONE) = if ai <> Arbint.zero then SOME i else NONE
452    | find_var (_, _, v as SOME _) = v
453  val x_var =
454      valOf (Vector.foldli find_var NONE (factoid_key a_constraint))
455  open Arbint
456  fun assign_factoid (df, acc as (upper, lower)) = let
457    val (fk, (fc, d)) = split_dfactoid df
458  in
459    if Vector.sub(fk, x_var) < zero then
460      case upper of
461        NONE => (SOME (fc, d), lower)
462      | SOME (c', d') => if fc < c' then (SOME (fc, d), lower) else acc
463    else
464      case lower of
465        NONE => (upper, SOME (~fc, d))
466      | SOME (c', d') => if ~fc > c' then (SOME (~fc,d), lower) else acc
467  end
468  val (upper, lower) = dbfold assign_factoid (NONE,NONE) ptree
469  open PIntMap
470in
471  case (upper, lower) of
472    (NONE, NONE) => raise Fail "one_var_analysis: this can't happen"
473  | (SOME (c, _), NONE) => if em = REAL then NO_CONCL
474                           else SATISFIABLE (add x_var c empty)
475  | (NONE, SOME (c, _)) => if em = REAL then NO_CONCL
476                           else SATISFIABLE (add x_var c empty)
477  | (SOME (u,du), SOME (l, dl)) =>
478    if u < l then
479      if em = DARK orelse em = EDARK then NO_CONCL
480      else direct_contradiction(du,dl)
481    else
482      if em = REAL then NO_CONCL
483      else SATISFIABLE (PIntMap.add x_var u PIntMap.empty)
484end
485
486(* ----------------------------------------------------------------------
487    throwaway_redundant_factoids ptree nextstage kont
488
489    checks ptree for variables that are constrained only in one sense
490    (i.e., with upper or lower bounds only).
491
492    The function nextstage takes a ptree and a continuation; it is
493    called when ptree has run out of constraints that can be thrown
494    away.
495
496    The continuation function kont is of type result -> 'a, and will be
497    called when the whole process eventually gets an answer.  This code
498    will not call it directly, but if it does throw anything away, it will
499    modify it so that a satisfying value can be calculated for the variables
500    that are chucked.
501   ---------------------------------------------------------------------- *)
502
503fun throwaway_redundant_factoids ptree nextstage kont = let
504  val dwidth = dbwidth ptree
505  val numvars = dwidth - 1
506  val has_low = Array.array(numvars, false)
507  val has_up = Array.array(numvars, false)
508
509  fun assess fk = let
510    fun appthis (i, ai) = let
511      open Arbint
512    in
513      case compare(ai,zero) of
514        LESS => Array.update(has_up, i, true)
515      | EQUAL => ()
516      | GREATER => Array.update(has_low, i, true)
517    end
518  in
519    Vector.appi appthis fk
520  end
521  val () = dbapp (fn (f,d) => assess (factoid_key f)) ptree
522
523  fun find_redundant_var i =
524      if i = numvars then NONE
525      else if Array.sub(has_up, i) = Array.sub(has_low, i) then
526        find_redundant_var (i + 1)
527      else SOME (i, Array.sub(has_up, i))
528in
529  case find_redundant_var 0 of
530    NONE => nextstage ptree kont
531  | SOME(i, hasupper) => let
532      fun partition (df as (f,d), (pt, elim)) = let
533        open Arbint
534      in
535        if Vector.sub(factoid_key f, i) = zero then
536          (dbinsert df pt, elim)
537        else
538          (pt, df :: elim)
539      end
540      val (newptree, elim) = dbfold partition (dbempty dwidth, []) ptree
541      fun handle_result r =
542          case r of
543            SATISFIABLE vmap => let
544              open Arbint
545              fun mapthis (f,_) = (eval_factoid_except vmap i f) div
546                                  abs (Vector.sub(factoid_key f, i))
547              val evaluated = if hasupper then map mapthis elim
548                              else map (~ o mapthis) elim
549              val foldfn = if hasupper then Arbint.min else Arbint.max
550            in
551              SATISFIABLE (PIntMap.add i (foldl foldfn (hd evaluated)
552                                                (tl evaluated))
553                                       vmap)
554            end
555          | x => x
556    in
557      throwaway_redundant_factoids newptree nextstage (kont o handle_result)
558    end
559end
560
561(* ----------------------------------------------------------------------
562    exact_var ptree
563
564    An exact var is one that has coefficients of one in either all of its
565    upper bounds or all of its lower bounds.  This function returns
566    SOME v if v is an exact var in ptree, or NONE if there is no exact
567    var.
568   ---------------------------------------------------------------------- *)
569
570fun exact_var ptree = let
571  val up_coeffs_unit = Array.array(dbwidth ptree - 1, true)
572  val low_coeffs_unit = Array.array(dbwidth ptree - 1, true)
573  val coeffs_all_zero = Array.array(dbwidth ptree - 1, true)
574  fun appthis (f,d) = let
575    open Arbint
576    fun examine_key (i, ai) =
577        case compare(ai,zero) of
578          LESS => (if abs ai <> one then
579                     Array.update(up_coeffs_unit, i, false)
580                   else ();
581                   Array.update(coeffs_all_zero, i, false))
582        | EQUAL => ()
583        | GREATER => (if ai <> one then
584                        Array.update(low_coeffs_unit, i, false)
585                      else ();
586                      Array.update(coeffs_all_zero, i, false))
587  in
588    Vector.appi examine_key (factoid_key f)
589  end
590  val () = dbapp appthis ptree
591  fun check_index (i, b, acc) =
592      if b andalso not (Array.sub(coeffs_all_zero,i)) then SOME i else acc
593in
594  case Array.foldli check_index NONE low_coeffs_unit of
595    NONE => Array.foldli check_index NONE up_coeffs_unit
596  | x => x
597end
598
599(* ----------------------------------------------------------------------
600    least_coeff_var ptree
601
602    Returns the variable whose coefficients' absolute values sum to the
603    least amount (that isn't zero).
604   ---------------------------------------------------------------------- *)
605
606fun least_coeff_var ptree = let
607  val sums = Array.array(dbwidth ptree - 1, Arbint.zero)
608  fun appthis df = let
609    open Arbint
610    fun add_in_c (i, ai) =
611        Array.update(sums, i, Array.sub(sums, i) + abs ai)
612  in
613    Vector.appi add_in_c (dfactoid_key df)
614  end
615  val () = dbapp appthis ptree
616  fun find_min (i,ai,NONE) = if ai <> Arbint.zero then SOME(i,ai) else NONE
617    | find_min (i,ai, acc as SOME(mini, minai)) =
618      if Arbint.<(ai, minai) andalso ai <> Arbint.zero then SOME(i,ai) else acc
619in
620  #1 (valOf (Array.foldli find_min NONE sums))
621end
622
623(* ----------------------------------------------------------------------
624    generate_row (pt0, em, i, up, lows, next, kont)
625
626    Types:
627      pt0       :ptree
628      em        :elimmode
629      i         :int  (a variable index)
630      up        :dfactoid
631      lows      :dfactoid list
632      next      :ptree -> (result -> 'a) -> 'a
633      kont      :result -> 'a
634
635    "Resolves" dfactoid against all the factoids in lows, producing new
636    factoids, which get added to the ptree.  If a factoid is directly
637    contradictory, then return it immediately, using kont.  If a factoid
638    is vacuous, drop it.
639   ---------------------------------------------------------------------- *)
640
641fun generate_row (pt0, em, i, up, lows, next, kont) =
642    case lows of
643      [] => next pt0 kont
644    | low::lowtl => let
645        val (df as (f,d)) =
646            gcd_check_dfactoid (combine_dfactoids em i (low, up))
647        fun after_add pt k = generate_row(pt, em, i, up, lowtl, next, k)
648      in
649        if true_factoid f then after_add pt0 kont
650        else if false_factoid f then kont (CONTR d)
651        else add_check_factoid pt0 df after_add kont
652      end
653
654(* ----------------------------------------------------------------------
655    generate_crossproduct (pt0, em, i, ups, lows, next, kont)
656
657    Types:
658      pt0       :ptree
659      em        :elimmode
660      i         :integer (a variable index)
661      ups       :dfactoid list
662      lows      :dfactoid list
663      next      :ptree -> (result -> 'a) -> 'a
664      kont      :result -> 'a
665   ---------------------------------------------------------------------- *)
666
667fun generate_crossproduct (pt0, em, i, ups, lows, next, kont) =
668    case ups of
669      [] => next pt0 kont
670    | u::us => let
671        fun after pt k = generate_crossproduct(pt, em, i, us, lows, next, k)
672      in
673        generate_row(pt0, em, i, u, lows, after, kont)
674      end
675
676(* ----------------------------------------------------------------------
677    extend_vmap ptree i vmap
678
679    vmap provides values for all of the variables present in ptree's
680    factoids except i.  Use it to evaluate all of the factoids, except
681    at variable i and to then return vmap extended with a value for
682    variable i that respects all of the factoids.
683
684    Note definite parallels with code in one_var_analysis.
685   ---------------------------------------------------------------------- *)
686
687fun extend_vmap ptree i vmap = let
688  fun categorise ((f, _), (acc as (lower, upper))) = let
689    val c0 = eval_factoid_except vmap i f
690    val fk = factoid_key f
691    open Arbint
692    val coeff = Vector.sub(fk, i)
693  in
694    case compare(coeff, zero) of
695      LESS => let (* upper *)
696        val c = c0 div ~coeff
697      in
698        case upper of
699          NONE => (lower, SOME c)
700        | SOME c' => if c < c' then (lower, SOME c) else acc
701      end
702    | EQUAL => acc
703    | GREATER => let (* lower *)
704        val c = ~(c0 div coeff)
705      in
706        case lower of
707          NONE => (SOME c, upper)
708        | SOME c' => if c > c' then (SOME c, upper) else acc
709      end
710  end
711  val (lower, upper) = dbfold categorise (NONE, NONE) ptree
712  open Arbint
713  val _ = valOf lower <= valOf upper orelse raise Fail "urk in extend map"
714in
715  PIntMap.add i (valOf lower) vmap
716end
717
718(* ----------------------------------------------------------------------
719    zero_upto n
720
721    Returns an integer map that maps the integers from 0..n (inclusive)
722    to Arbint.zero.
723   ---------------------------------------------------------------------- *)
724
725fun zero_upto n =
726    if n < 0 then raise Fail "zero_upto called with negative argument"
727    else if n = 0 then PIntMap.add 0 Arbint.zero PIntMap.empty
728    else PIntMap.add n Arbint.zero (zero_upto (n - 1))
729
730
731(* ----------------------------------------------------------------------
732    one_step ptree em nextstage kont
733
734    Types:
735      em         :elimmode
736      nextstage  :ptree -> elimmode -> (result -> 'a) -> 'a
737      kont       :result -> 'a
738
739    Assume that ptree doesn't contain anything directly contradictory,
740    and that there aren't any redundant constraints around (these have
741    been thrown away by throwaway_redundant_factoids).
742
743    Perform one step of the shadow calculation algorithm:
744      (a) if there is only one variable left in all of the constraints
745          in ptree do one_var_analysis to return some sort of result,
746          and pass this result to kont.
747      (b) otherwise, decide on a variable to eliminate.  If there's a
748          variable that allows for an exact shadow (has coefficients of
749          one in all of its lower-bound or upper-bound occurrences),
750          pick this.  Otherwise, take the variable whose coefficients'
751          absolute values sum to the least amount.
752      (c) the initial tree of the result contains all of those
753          constraints from the original which do not mention the
754          variable to be eliminated.
755      (d) divide the constraints that do mention the variable to be
756          eliminated into upper and lower bound sets.
757    Then (in generate_crossproduct):
758      (e) work through all of the possible combinations in
759            upper x lower
760          using combine_dfactoids.  Each new dfactoid has to be added
761          to the accumulating tree.  This may produce a direct
762          contradiction, in which case, stop and return this (again,
763          using the kont function).  Otherwise keep going.
764      (f) pass the completed new ptree to nextstage, with an augmented
765          continuation that copes with satisfiable results (though
766          satisfiable results won't come back if the mode is REAL
767          of course).
768   ---------------------------------------------------------------------- *)
769
770fun one_step ptree em nextstage kont = let
771  val (var_to_elim, mode) =
772      case exact_var ptree of
773        SOME i => (i, em)
774      | NONE => (least_coeff_var ptree, inexactify em)
775  fun categorise (df, (notmentioned, uppers, lowers)) = let
776    open Arbint
777  in
778    case compare(Vector.sub(dfactoid_key df, var_to_elim), zero) of
779      LESS => (notmentioned, df::uppers, lowers)
780    | EQUAL => (dbinsert df notmentioned, uppers, lowers)
781    | GREATER => (notmentioned, uppers, df::lowers)
782  end
783  val (newtree0, uppers, lowers) =
784      dbfold categorise (dbempty (dbwidth ptree), [], []) ptree
785  fun drop_contr (CONTR _) = NO_CONCL
786    | drop_contr x = x
787  fun extend_satisfiable r =
788      case r of
789        SATISFIABLE vmap =>
790        SATISFIABLE (extend_vmap ptree var_to_elim vmap)
791      | _ => r
792  fun newkont r =
793      case (em, mode) of
794        (EXACT, EXACT) => kont (extend_satisfiable r)
795      | (EXACT, REAL) => let
796        in
797          case r of
798            CONTR _ => kont r
799          | _ => one_step ptree EDARK nextstage kont
800        end
801      | (REAL, REAL) => kont r
802      | (EDARK, EDARK) => kont (drop_contr (extend_satisfiable r))
803      | (EDARK, DARK) => kont (drop_contr (extend_satisfiable r))
804      | (DARK, DARK) => kont (drop_contr (extend_satisfiable r))
805      | _ => raise Fail "Can't happen - in newkont calculation"
806  fun newnextstage pt k = nextstage pt mode k
807in
808  generate_crossproduct(newtree0, mode, var_to_elim, uppers, lowers,
809                        newnextstage, newkont)
810end
811
812(* ----------------------------------------------------------------------
813    toplevel ptree em kont
814
815   ---------------------------------------------------------------------- *)
816
817fun toplevel ptree em kont = let
818  fun after_throwaway pt k =
819      if dbsize pt = 0 then
820        k (SATISFIABLE (zero_upto (dbwidth pt - 2)))
821      else if has_one_var pt then
822        k (mode_result em (one_var_analysis ptree em))
823      else one_step pt em toplevel k
824in
825  throwaway_redundant_factoids ptree after_throwaway kont
826end
827
828(* ----------------------------------------------------------------------
829    work ptree kont
830
831   ---------------------------------------------------------------------- *)
832
833fun work ptree k = toplevel ptree EXACT k
834
835(* ----------------------------------------------------------------------
836   simple tests
837
838   fun failkont r = raise Fail "kont"
839   fun next1 pt em k = pt
840
841   fun fromList l = let
842     open intSyntax
843     val clist = map Arbint.fromInt l
844     val lim = length clist - 1
845     fun clist2term (x, (acc, n)) = let
846       val c = intSyntax.term_of_int x
847       val cx = if n = lim then c
848                else mk_mult(c, mk_var("x"^Int.toString n, int_ty))
849     in
850       case acc of
851         NONE => (SOME cx, n + 1)
852       | SOME a => (SOME(mk_plus(a, cx)), n + 1)
853     end
854     val t = valOf (#1 (List.foldl clist2term (NONE, 0) clist))
855   in
856     (ALT (Vector.fromList clist), ASM (mk_leq(zero_tm, t)))
857   end
858
859   datatype 'a prettyr = SAT of bool * (string * Arbint.int) list
860                    | PCONTR of 'a derivation
861                    | PNOCONC
862   fun v2list vs = #1 (List.foldr (fn(v,(acc,n)) =>
863                                     (("x"^Int.toString n, v)::acc, n - 1))
864                                  ([], length vs - 1) vs)
865
866   fun display_result orig r =
867       case r of
868         SATISFIABLE v => let
869           val limit = length (hd orig)
870           val satlist = List.tabulate(limit,
871                                       (fn i => if i = limit - 1 then
872                                                  Arbint.one
873                                                else PIntMap.find i v))
874           val vslistlist = map (fn c => ListPair.zip(c, satlist)) orig
875           open Arbint
876           val sumprod = List.foldl (fn((x,y),acc) => acc + fromInt x * y) zero
877         in
878           SAT (List.all (fn l => zero <= sumprod l) vslistlist,
879                v2list satlist)
880         end
881       | CONTR x => PCONTR x
882       | NO_CONCL => PNOCONC;
883
884fun test csts = let
885  fun add_csts normalc csts db exc =
886      case csts of
887        [] => normalc db
888      | (c::cs) => add_check_factoid db
889                                     (gcd_check_dfactoid(fromList c))
890                                     (add_csts normalc cs)
891                                     exc
892in add_csts (fn db => work db (display_result csts))
893            csts
894            (dbempty (length (hd csts)))
895            (display_result csts)
896end
897
898val test = time test
899
900(* returns the contents of a patricia tree *)
901fun pt_list t = PIntMap.fold (fn (k,v,acc) => (k,v)::acc) [] t
902
903   test [[2,3,6],[~1,~4,7]];  (* exact elimination, sat: [~9,4] *)
904   test [[2,3,4],[~3,~4,7]];  (* dark shadow test, sat: [34, ~24] *)
905   test [[2,3,4],[~3,~4,7],[4,5,~10]];  (* another dark shadow, sat: [13,~8] *)
906   test [[2,3,4],[~3,~4,7],[4,~5,~10]];  (* also satisfiable, sat: [2, ~1] *)
907   test [[1,0,~1], [0,1,~1], [~1,0,1]]; (* satisfiable, sat: [1,1] *)
908   test [[~3,~1,6],[2,1,~5],[3,~1,~3]];  (* a contradiction *)
909   test [[11,13,~27], [~11,~13,45],[7,~9,10],[~7,9,4]];  (* no conclusion *)
910
911   (* satisfiable after throwing away everything because of var 1: [0,2,0] *)
912   test [[1,2,3,4],[2,1,4,3],[5,6,7,~8],[~3,2,~1,6]];
913
914   (* satisfiable: [2,0,2] *)
915   test [[1,2,3,4],[2,~2,3,~10],[2,3,~5,6],[~3,~2,1,7]];
916
917
918   (* satisfiable by: [0, ~1, 2, 2] *)
919   test [[~9, ~11, ~8, 9, 11], [15, 0, 8, ~7, 8], [4, 3, 11, ~2, ~13],
920         [~13, ~8, ~14, 15, 8], [~10, 9, 15, ~13, 9], [~15, ~14, ~3, 2, 5],
921         [2, ~1, ~5, 14, ~7]];
922
923
924   (* very poor performance (311s on a 2GHz machine) exhibited here: *)
925   (* sat: [~19, 0, 0, 29, ~30, ~10, 22] *)
926   test [[13, ~5, ~12, 11, 1, ~5, ~3, ~6], [14, ~6, ~8, ~1, 4, ~10, 15, 6],
927         [9, ~5, 1, 10, 2, ~1, ~2, 0], [~1, ~13, 14, ~3, 11, ~9, 14, 9],
928         [~13, 1, 5, ~14, ~6, ~3, 14, 12], [11, 8, 9, 12, 1, ~2, ~6, 8],
929         [~14, ~8, ~4, ~2, ~3, 13, ~7, ~10], [~3, 3, ~14, ~3, ~7, 4, ~6, ~6]];
930
931   fun print_test (l:int list list) = ignore (printVal l)
932
933
934   fun gentree l =
935       List.foldl (fn (df,t) => dbinsert (fromList df) t)
936                  (dbempty (length (hd l))) l;
937
938   (* for generation of random problems *)
939   val seed = Random.newgen()
940   fun gen_int() = Random.range(~15,16) seed
941   fun gen_constraint n  = List.tabulate(n, fn n => gen_int())
942   fun gen_test m n = List.tabulate(m, fn _ => gen_constraint n)
943
944
945   val current_goal = ref ([] : int list list)
946   val slow_goals = ref  ([] : int list list list)
947   fun sattest i timelimit numcsts numvars = if i <= 0 then ()
948                       else let
949                           val t = gen_test numcsts numvars
950                           val _ = current_goal := t
951                           val ctimer = Timer.startCPUTimer()
952                           val result = test t
953                           val timetaken = Timer.checkCPUTimer ctimer
954                           val _ = if Time.>(#usr timetaken,
955                                             Time.fromSeconds timelimit) then
956                                     slow_goals := t :: (!slow_goals)
957                                   else ()
958                         in
959                           case result of
960                             SAT (b, _) => if b then print "SAT - OK\n"
961                                           else (print "SAT - FAILED\n";
962                                                 print_test t;
963                                                 print "SAT - FAILED\n\n")
964                           | PCONTR _ => print "CONTR\n"
965                           | PNOCONC => print "PNOCONC\n";
966                           sattest (i - 1) timelimit numcsts numvars
967                         end
968
969
970   fun can_throwaway ptree = let
971     val next = throwaway_redundant_factoids ptree (fn p => fn k => p) failkont
972   in
973     dbsize next < dbsize ptree
974   end
975
976   fun dbonestep ptree em =
977     throwaway_redundant_factoids ptree
978                                  (fn p =>
979                                      fn k =>
980                                         one_step ptree em
981                                                  (fn pt =>
982                                                      fn em =>
983                                                         fn k => (pt, em))
984                                                  k) failkont
985
986   ---------------------------------------------------------------------- *)
987
988
989end (* struct *)
990