root/trunk/ProjectFortress/demos/aStar.fss

Revision 4131, 14.2 KB (checked in by sukyoungryu, 3 months ago)

[demos] Fixed more getter call syntax in demos.

Line 
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
18component aStar
19
20import Heap.{...}
21import List.{...}
22import File.{...}
23import System.{getProperty, toDirectoryName}
24export 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
44trait 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)
71end
72
73aStarPair[\T extends SearchInstance[\T,C\],C\](a:T):(C,T) = (a.cost(),a)
74
75(** Perform an A* search starting from root. **)
76aStar[\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
114trait BoardState
115        comprises { No, Perhaps, Yes }
116    getter cand(): Boolean = true
117    getter candNum(): ZZ32 = 1
118    getter working(): Boolean = false
119end
120
121object No extends BoardState
122    getter cand(): Boolean = false
123    getter candNum(): ZZ32 = 0
124end
125
126object Perhaps extends BoardState
127    getter working(): Boolean = true
128end
129
130object Yes extends BoardState end
131
132object 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())
287end
288
289legalState(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
296emptySudoku(): Sudoku = do
297    st0 = array3[\BoardState,9,9,9\]().fill(Perhaps)
298    Sudoku(9^3,0,st0)
299  end
300
301verifySudoku(st0) =
302    if legalState(st0.state) then
303        st0
304    else
305        fail("Bad initial state:" // st0)
306    end
307
308sudoku(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
316sudoku(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
340timing(x:()->Any): (Any,NanoInterval) = do
341    start = nanoTime()
342    r = x()
343    finish = nanoTime()
344    (r,NanoInterval(finish-start))
345  end
346
347value 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)
361end
362
363run():()=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
383end
Note: See TracBrowser for help on using the browser.