| 1 | (******************************************************************************* |
|---|
| 2 | Copyright 2009 Sun Microsystems, Inc., |
|---|
| 3 | 4150 Network Circle, Santa Clara, California 95054, U.S.A. |
|---|
| 4 | All rights reserved. |
|---|
| 5 | |
|---|
| 6 | U.S. Government Rights - Commercial software. |
|---|
| 7 | Government users are subject to the Sun Microsystems, Inc. standard |
|---|
| 8 | license agreement and applicable provisions of the FAR and its supplements. |
|---|
| 9 | |
|---|
| 10 | Use is subject to license terms. |
|---|
| 11 | |
|---|
| 12 | This distribution may include materials developed by third parties. |
|---|
| 13 | |
|---|
| 14 | Sun, Sun Microsystems, the Sun logo and Java are trademarks or registered |
|---|
| 15 | trademarks of Sun Microsystems, Inc. in the U.S. and other countries. |
|---|
| 16 | ******************************************************************************) |
|---|
| 17 | |
|---|
| 18 | component aStar |
|---|
| 19 | |
|---|
| 20 | import Heap.{...} |
|---|
| 21 | import List.{...} |
|---|
| 22 | import File.{...} |
|---|
| 23 | import System.{getProperty, toDirectoryName} |
|---|
| 24 | export Executable |
|---|
| 25 | |
|---|
| 26 | (** To perform a search, define an object containing the search state. |
|---|
| 27 | This object must extend SearchInstance[\Cost\] for some |
|---|
| 28 | totally-ordered type Cost. We're searching for a min-cost solution. |
|---|
| 29 | Example instantiations for Cost are ZZ32 and RR64 (disallowing NaN). |
|---|
| 30 | |
|---|
| 31 | The AStar code asks a SearchInstance to generate its children. |
|---|
| 32 | You must write either the children() method, or the generate() |
|---|
| 33 | method; in either case the SearchInstances can (should) be |
|---|
| 34 | generated in parallel, and you should make sure that more than one |
|---|
| 35 | SearchInstance can exist at a time. |
|---|
| 36 | |
|---|
| 37 | For example, if you're using this to solve something like Sudoku, |
|---|
| 38 | you'd want the children() to represent distinct Sudoku boards, |
|---|
| 39 | rather than updating a single board in place. You can do this |
|---|
| 40 | either by copying the board state or by using some kind of |
|---|
| 41 | updatable board data structure. |
|---|
| 42 | **) |
|---|
| 43 | |
|---|
| 44 | trait SearchInstance[\Self extends SearchInstance[\Self,Cost\], |
|---|
| 45 | Cost (* extends StandardTotalOrder[\Cost\] *)\] |
|---|
| 46 | extends Generator[\Self\] |
|---|
| 47 | (** isComplete() returns true if no further searching is possible |
|---|
| 48 | from this point, and returns false if further searching is |
|---|
| 49 | required. **) |
|---|
| 50 | getter isComplete(): Boolean |
|---|
| 51 | |
|---|
| 52 | (** cost returns an estimate of the *total* cost of this search |
|---|
| 53 | instance from start to finish (note: NOT the cost from this |
|---|
| 54 | point to the final solution, but the total cost from the start |
|---|
| 55 | of time). When isComplete() is true this should give the |
|---|
| 56 | precise cost. **) |
|---|
| 57 | getter cost(): Cost |
|---|
| 58 | |
|---|
| 59 | (** children() returns the children of this particular search |
|---|
| 60 | state. If you wish to generate the children in parallel, you |
|---|
| 61 | should use a parallel generator. If isComplete() is true, |
|---|
| 62 | this probably ought to return an empty generator. If you |
|---|
| 63 | write the children() getter you do not need to write the |
|---|
| 64 | generate() method. **) |
|---|
| 65 | getter children(): Generator[\Self\] = self |
|---|
| 66 | |
|---|
| 67 | (** If you don't write the children() getter you should write the |
|---|
| 68 | generate() method. **) |
|---|
| 69 | generate[\R\](r: Reduction[\R\], body: Self->R): R = |
|---|
| 70 | self.children.generate[\R\](r,body) |
|---|
| 71 | end |
|---|
| 72 | |
|---|
| 73 | aStarPair[\T extends SearchInstance[\T,C\],C\](a:T):(C,T) = (a.cost(),a) |
|---|
| 74 | |
|---|
| 75 | (** Perform an A* search starting from root. **) |
|---|
| 76 | aStar[\T extends SearchInstance[\T,C\],C\](root: T): Maybe[\T\] = |
|---|
| 77 | label succeed |
|---|
| 78 | q : Heap[\C,T\] := singletonHeap[\C,T\](aStarPair[\T,C\](root)) |
|---|
| 79 | while (c,best,q') <- q.extractMinimum() do |
|---|
| 80 | println("************************************************************") |
|---|
| 81 | println(best) |
|---|
| 82 | println("Cost " c) |
|---|
| 83 | (complete, completeTime) = timing( fn () => best.isComplete()) |
|---|
| 84 | println("Completeness check time: " completeTime) |
|---|
| 85 | if complete then |
|---|
| 86 | exit succeed with Just(best) |
|---|
| 87 | end |
|---|
| 88 | (kids, kidTime) = timing( fn () => best.children()) |
|---|
| 89 | println("Generated " |kids| " children") |
|---|
| 90 | println("Children generation time: " kidTime) |
|---|
| 91 | (_, updTime) = timing( fn () => |
|---|
| 92 | q := q'.merge(heap(kids.map[\(C,T)\](aStarPair[\T,C\])))) |
|---|
| 93 | println("Queue update time: " updTime) |
|---|
| 94 | end |
|---|
| 95 | Nothing[\T\] (** If we get here the search failed. **) |
|---|
| 96 | end succeed |
|---|
| 97 | |
|---|
| 98 | (************************************************************ |
|---|
| 99 | * Sample problem |
|---|
| 100 | ************************************************************) |
|---|
| 101 | |
|---|
| 102 | (* Stupid, really inefficient but complete Sudoku solver. We maintain |
|---|
| 103 | a 9x9x9-space of BoardStates: Perhaps or Yes at (i,j,k) indicates |
|---|
| 104 | that k is a candidate value for point (i,j) in the Sukoku grid, Yes |
|---|
| 105 | indicating that the candidacy has been propagated (crossing out |
|---|
| 106 | candidates in square, row, column, and patch). The cost metric is |
|---|
| 107 | currently dubious. A step consists of choosing one Perhaps entry |
|---|
| 108 | to propagate and set to Yes. Infeasible solutions are pruned. We |
|---|
| 109 | do forward propagation lazily when we're checking if the puzzle has |
|---|
| 110 | been solved. This only occurs when a candidate solution looks |
|---|
| 111 | promising, so we avoid propagation cost for non-promising |
|---|
| 112 | candidates. *) |
|---|
| 113 | |
|---|
| 114 | trait BoardState |
|---|
| 115 | comprises { No, Perhaps, Yes } |
|---|
| 116 | getter cand(): Boolean = true |
|---|
| 117 | getter candNum(): ZZ32 = 1 |
|---|
| 118 | getter working(): Boolean = false |
|---|
| 119 | end |
|---|
| 120 | |
|---|
| 121 | object No extends BoardState |
|---|
| 122 | getter cand(): Boolean = false |
|---|
| 123 | getter candNum(): ZZ32 = 0 |
|---|
| 124 | end |
|---|
| 125 | |
|---|
| 126 | object Perhaps extends BoardState |
|---|
| 127 | getter working(): Boolean = true |
|---|
| 128 | end |
|---|
| 129 | |
|---|
| 130 | object Yes extends BoardState end |
|---|
| 131 | |
|---|
| 132 | object Sudoku(var cands : ZZ32, var props : ZZ32, |
|---|
| 133 | state : Array3[\BoardState,0,9,0,9,0,9\]) |
|---|
| 134 | extends SearchInstance[\Sudoku, ZZ32\] |
|---|
| 135 | (* legalState check is expensive, so we compute it lazily and cache the result. *) |
|---|
| 136 | consistent : BoardState := Perhaps |
|---|
| 137 | iMax : ZZ32 := -1 (* Index of square with maximum options *) |
|---|
| 138 | jMax : ZZ32 := -1 (* Computed incrementally by isConsistent. *) |
|---|
| 139 | getter isConsistent(): Boolean = |
|---|
| 140 | (* Also does propagation of trivialities. *) |
|---|
| 141 | if NOT consistent.working() then consistent.cand() |
|---|
| 142 | elif cands < 81 then |
|---|
| 143 | consistent := No |
|---|
| 144 | false |
|---|
| 145 | else |
|---|
| 146 | res = computeConsistent() |
|---|
| 147 | if NOT res then |
|---|
| 148 | println(self) |
|---|
| 149 | println("inconsistent, ruled out") |
|---|
| 150 | end |
|---|
| 151 | res |
|---|
| 152 | end |
|---|
| 153 | getter cost(): ZZ32 = 6 81 - 5 props - cands |
|---|
| 154 | getter isComplete(): ZZ32 = self.isConsistent AND: props = 81 |
|---|
| 155 | getter longString(): String = |
|---|
| 156 | (BIG ||[i<-0#9] ( |
|---|
| 157 | BIG ||[j<-0#9] ( |
|---|
| 158 | (BIG ||[k<-0#9] if state[i,j,k].working() then "." |
|---|
| 159 | elif state[i,j,k].cand() then "" (k+1) |
|---|
| 160 | else " " end) "|") |
|---|
| 161 | )) // "Remaining: " cands " Fixed: " props |
|---|
| 162 | getter asString(): String = |
|---|
| 163 | (BIG ||[i<-0#9] ( |
|---|
| 164 | BIG ||[j<-0#9] do |
|---|
| 165 | str : String := "X" |
|---|
| 166 | for k<-0#9 do |
|---|
| 167 | if state[i,j,k].working() then str := "." |
|---|
| 168 | elif state[i,j,k].cand() then str := "" (k+1) |
|---|
| 169 | end |
|---|
| 170 | end |
|---|
| 171 | str |
|---|
| 172 | end)) |
|---|
| 173 | getter children(): Generator[\Sudoku\] = |
|---|
| 174 | if self.isConsistent then |
|---|
| 175 | incons : Sudoku = Sudoku(0,props+1,state) |
|---|
| 176 | <| if state[iMax,jMax,k].working() then |
|---|
| 177 | child = self.copy() |
|---|
| 178 | child.fillState(iMax,jMax,k) |
|---|
| 179 | child |
|---|
| 180 | else |
|---|
| 181 | incons |
|---|
| 182 | end |
|---|
| 183 | | k <- 0#9 |>.filter(fn (s) => s.cands >= 81) |
|---|
| 184 | else |
|---|
| 185 | <|[\Sudoku\] |> |
|---|
| 186 | end |
|---|
| 187 | |
|---|
| 188 | computeConsistent(): Boolean = |
|---|
| 189 | label inconsistent |
|---|
| 190 | keepTrying : Boolean := true |
|---|
| 191 | updated : Boolean := false |
|---|
| 192 | chk(s,v,i,j,k): Boolean = |
|---|
| 193 | if s=0 then |
|---|
| 194 | consistent := No |
|---|
| 195 | exit inconsistent with false |
|---|
| 196 | elif s=1 AND v>=0 then |
|---|
| 197 | fillState(i,j,k) |
|---|
| 198 | else false |
|---|
| 199 | end |
|---|
| 200 | while props < 81 AND keepTrying do |
|---|
| 201 | options : ZZ32 := 0 |
|---|
| 202 | keepTrying := |
|---|
| 203 | (BIG OR [i<-0#9, j<-0#9] do |
|---|
| 204 | k' : ZZ32 := -1 |
|---|
| 205 | s = SUM [k<-0#9] |
|---|
| 206 | if state[i,j,k].working() then k' := k; 1 |
|---|
| 207 | else state[i,j,k].candNum() end |
|---|
| 208 | (* Remember square with MAX # candidates *) |
|---|
| 209 | atomic if s > options then |
|---|
| 210 | options := s |
|---|
| 211 | iMax := i |
|---|
| 212 | jMax := j |
|---|
| 213 | end |
|---|
| 214 | chk(s,k',i,j,k') |
|---|
| 215 | end) OR |
|---|
| 216 | (BIG OR [i<-0#9, k<-0#9] do |
|---|
| 217 | j' : ZZ32 := -1 |
|---|
| 218 | s = SUM [j<-0#9] |
|---|
| 219 | if state[i,j,k].working() then j' := j; 1 |
|---|
| 220 | else state[i,j,k].candNum() end |
|---|
| 221 | chk(s,j',i,j',k) |
|---|
| 222 | end) OR |
|---|
| 223 | (BIG OR [j<-0#9, k<-0#9] do |
|---|
| 224 | i' : ZZ32 := -1 |
|---|
| 225 | s = SUM [i<-0#9] |
|---|
| 226 | if state[i,j,k].working() then i' := i; 1 |
|---|
| 227 | else state[i,j,k].candNum() end |
|---|
| 228 | chk(s,i',i',j,k) |
|---|
| 229 | end) OR |
|---|
| 230 | (BIG OR [i0<-0#3, j0<-0#3, k<-0#9] do |
|---|
| 231 | i' : ZZ32 := -1 |
|---|
| 232 | j' : ZZ32 := -1 |
|---|
| 233 | s = SUM [i<-(3 i0)#3, j<-(3 j0)#3] |
|---|
| 234 | if state[i,j,k].working() then |
|---|
| 235 | i':=i; j':=j; 1 |
|---|
| 236 | else state[i,j,k].candNum() end |
|---|
| 237 | chk(s,i',i',j',k) |
|---|
| 238 | end) |
|---|
| 239 | updated := updated OR keepTrying |
|---|
| 240 | end |
|---|
| 241 | if updated then |
|---|
| 242 | println(self) |
|---|
| 243 | println("Cost " self.cost()) |
|---|
| 244 | end |
|---|
| 245 | if cands < 81 then |
|---|
| 246 | consistent := No |
|---|
| 247 | false |
|---|
| 248 | else |
|---|
| 249 | consistent := Yes |
|---|
| 250 | true |
|---|
| 251 | end |
|---|
| 252 | end inconsistent |
|---|
| 253 | |
|---|
| 254 | (* fillState commits to i,j,k being chosen, assuming a race hasn't |
|---|
| 255 | prevented or preempted this choice. It also updates props and |
|---|
| 256 | cands as appropriate. *) |
|---|
| 257 | fillState(i:ZZ32,j:ZZ32,k:ZZ32):ZZ32 = |
|---|
| 258 | if atomic (if state[i,j,k].working() then |
|---|
| 259 | state[i,j,k] := Yes |
|---|
| 260 | true |
|---|
| 261 | else false end) then |
|---|
| 262 | atomic props += 1 |
|---|
| 263 | afill(cond,i',j',k'):ZZ32 = |
|---|
| 264 | if cond then |
|---|
| 265 | atomic do |
|---|
| 266 | if state[i',j',k'].cand() then |
|---|
| 267 | state[i',j',k'] := No |
|---|
| 268 | 1 |
|---|
| 269 | else |
|---|
| 270 | 0 |
|---|
| 271 | end |
|---|
| 272 | end |
|---|
| 273 | else 0 end |
|---|
| 274 | delta = |
|---|
| 275 | (SUM [i' <- 0#9] afill(i =/= i', i',j,k)) + |
|---|
| 276 | (SUM [j' <- 0#9] afill(j =/= j', i,j',k)) + |
|---|
| 277 | (SUM [k' <- 0#9] afill(k =/= k', i,j,k')) + |
|---|
| 278 | (SUM [i' <- (3 (i DIV 3))#3, j' <- (3 (j DIV 3))#3] |
|---|
| 279 | afill(i'=/=i AND j'=/=j, i',j',k)) |
|---|
| 280 | if (delta > 0) then atomic cands -= delta end |
|---|
| 281 | true |
|---|
| 282 | else |
|---|
| 283 | println("Beaten to the punch at " (i,j,k)) |
|---|
| 284 | false |
|---|
| 285 | end |
|---|
| 286 | copy(): Sudoku = Sudoku(cands,props,state.copy()) |
|---|
| 287 | end |
|---|
| 288 | |
|---|
| 289 | legalState(state:Array3[\BoardState,0,9,0,9,0,9\]): Boolean = |
|---|
| 290 | (BIG AND [i<-0#9,j<-0#9] (BIG OR [k<-0#9] state[i,j,k].cand())) AND |
|---|
| 291 | (BIG AND [i<-0#9,k<-0#9] (BIG OR [j<-0#9] state[i,j,k].cand())) AND |
|---|
| 292 | (BIG AND [j<-0#9,k<-0#9] (BIG OR [i<-0#9] state[i,j,k].cand())) AND |
|---|
| 293 | (BIG AND [i0<-0#3,j0<-0#3,k<-0#9] |
|---|
| 294 | (BIG OR [i<- (3 i0)#3,j<-(3 j0)#3] state[i,j,k].cand())) |
|---|
| 295 | |
|---|
| 296 | emptySudoku(): Sudoku = do |
|---|
| 297 | st0 = array3[\BoardState,9,9,9\]().fill(Perhaps) |
|---|
| 298 | Sudoku(9^3,0,st0) |
|---|
| 299 | end |
|---|
| 300 | |
|---|
| 301 | verifySudoku(st0) = |
|---|
| 302 | if legalState(st0.state) then |
|---|
| 303 | st0 |
|---|
| 304 | else |
|---|
| 305 | fail("Bad initial state:" // st0) |
|---|
| 306 | end |
|---|
| 307 | |
|---|
| 308 | sudoku(givens:(ZZ32,ZZ32,ZZ32)...): Sudoku = do |
|---|
| 309 | st0 = emptySudoku() |
|---|
| 310 | for (i,j,k) <- givens do |
|---|
| 311 | st0.fillState(i-1,j-1,k-1) |
|---|
| 312 | end |
|---|
| 313 | verifySudoku(st0) |
|---|
| 314 | end |
|---|
| 315 | |
|---|
| 316 | sudoku(compact:String): Sudoku = do |
|---|
| 317 | st0 = emptySudoku() |
|---|
| 318 | for n<-compact.bounds do |
|---|
| 319 | i=n DIV 9 |
|---|
| 320 | j=n REM 9 |
|---|
| 321 | if i<9 AND j<9 then |
|---|
| 322 | case compact[n#1] of |
|---|
| 323 | "1" => st0.fillState(i,j,0) |
|---|
| 324 | "2" => st0.fillState(i,j,1) |
|---|
| 325 | "3" => st0.fillState(i,j,2) |
|---|
| 326 | "4" => st0.fillState(i,j,3) |
|---|
| 327 | "5" => st0.fillState(i,j,4) |
|---|
| 328 | "6" => st0.fillState(i,j,5) |
|---|
| 329 | "7" => st0.fillState(i,j,6) |
|---|
| 330 | "8" => st0.fillState(i,j,7) |
|---|
| 331 | "9" => st0.fillState(i,j,8) |
|---|
| 332 | else => false |
|---|
| 333 | end |
|---|
| 334 | () |
|---|
| 335 | end |
|---|
| 336 | end |
|---|
| 337 | verifySudoku(st0) |
|---|
| 338 | end |
|---|
| 339 | |
|---|
| 340 | timing(x:()->Any): (Any,NanoInterval) = do |
|---|
| 341 | start = nanoTime() |
|---|
| 342 | r = x() |
|---|
| 343 | finish = nanoTime() |
|---|
| 344 | (r,NanoInterval(finish-start)) |
|---|
| 345 | end |
|---|
| 346 | |
|---|
| 347 | value object NanoInterval(i:ZZ64) |
|---|
| 348 | getter sec(): RR64 = i / 10.0^9 |
|---|
| 349 | getter ms(): RR64 = i / 10.0^6 |
|---|
| 350 | getter us(): RR64 = i / 10.0^3 |
|---|
| 351 | getter ns(): RR64 = 1.0 i |
|---|
| 352 | getter asString(): String = |
|---|
| 353 | if i = 0 then "0" |
|---|
| 354 | elif i >= 10^9 then self.sec "s" |
|---|
| 355 | elif i >= 10^6 then self.ms "ms" |
|---|
| 356 | elif i >= 10^3 then self.us "µs" |
|---|
| 357 | else self.ns "ns" |
|---|
| 358 | end |
|---|
| 359 | opr +(self, other:NanoInterval): NanoInterval = |
|---|
| 360 | NanoInterval(i+other.i) |
|---|
| 361 | end |
|---|
| 362 | |
|---|
| 363 | run():()=do |
|---|
| 364 | spuriousInconsistency : Boolean := false |
|---|
| 365 | path = toDirectoryName(getProperty("fortress.autohome","")) |
|---|
| 366 | println("path=" path) |
|---|
| 367 | for problem <- seq(FileReadStream(path "ProjectFortress/demos/aStarSudokuData.txt").lines()) do |
|---|
| 368 | println("************************************************************") |
|---|
| 369 | (init,initTime) = timing (fn ():Sudoku => sudoku(problem)) |
|---|
| 370 | println("initTime = " initTime) |
|---|
| 371 | (res,searchTime) = timing (fn ():Maybe[\Sudoku\] => aStar[\Sudoku,ZZ32\](init)) |
|---|
| 372 | println("searchTime = " searchTime) |
|---|
| 373 | if r <- res then |
|---|
| 374 | println("Final result:" // r) |
|---|
| 375 | else |
|---|
| 376 | spuriousInconsistency := true |
|---|
| 377 | println("FAIL: INCONSISTENT:" // init) |
|---|
| 378 | end |
|---|
| 379 | end |
|---|
| 380 | if spuriousInconsistency then fail("Spurious inconsistencies found.") end |
|---|
| 381 | end |
|---|
| 382 | |
|---|
| 383 | end |
|---|