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