ambit 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,23 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'autotest/restart'
4
+
5
+ # Autotest.add_hook :initialize do |at|
6
+ # at.extra_files << "../some/external/dependency.rb"
7
+ #
8
+ # at.libs << ":../some/external"
9
+ #
10
+ # at.add_exception 'vendor'
11
+ #
12
+ # at.add_mapping(/dependency.rb/) do |f, _|
13
+ # at.files_matching(/test_.*rb$/)
14
+ # end
15
+ #
16
+ # %w(TestA TestB).each do |klass|
17
+ # at.extra_class_map[klass] = "test/test_misc.rb"
18
+ # end
19
+ # end
20
+
21
+ # Autotest.add_hook :run_command do |at|
22
+ # system "rake build"
23
+ # end
File without changes
@@ -0,0 +1,3 @@
1
+ === 0.9 / 2011-04-27
2
+
3
+ * First public release of ambit
@@ -0,0 +1,28 @@
1
+ == LICENSE:
2
+
3
+ (The BSD 2-clause License)
4
+
5
+ Copyright (c) 2011 Jim Wise
6
+ All rights reserved.
7
+
8
+ Redistribution and use in source and binary forms, with or without
9
+ modification, are permitted provided that the following conditions
10
+ are met:
11
+
12
+ 1. Redistributions of source code must retain the above copyright
13
+ notice, this list of conditions and the following disclaimer.
14
+ 2. Redistributions in binary form must reproduce the above copyright
15
+ notice, this list of conditions and the following disclaimer in the
16
+ documentation and/or other materials provided with the distribution.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
+ PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
22
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
+ POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,11 @@
1
+ .autotest
2
+ History.txt
3
+ LICENSE.txt
4
+ Manifest.txt
5
+ README.rdoc
6
+ README.txt
7
+ Rakefile
8
+ examples/example.rb
9
+ examples/queens.rb
10
+ lib/ambit.rb
11
+ test/test_ambit.rb
@@ -0,0 +1,427 @@
1
+ = ambit
2
+
3
+ https://github.com/jimwise/ruby/tree/master/ambit
4
+
5
+ Author:: Jim Wise (mailto:jwise@draga.com)
6
+ Copyright:: Copyright (c) 2011 Jim Wise
7
+ License:: 2-clause BSD-Style (see LICENSE.txt)
8
+
9
+ == DESCRIPTION:
10
+
11
+ This is an all-ruby implementation of choose/fail nondeterministic
12
+ programming with branch cut, as described in Chapter 22 of Paul Graham's
13
+ <em>On Lisp</em>[1], Chapter, or Section 4.3 of <em>SICP</em>[2].
14
+
15
+ Due to Ruby containing a true call/cc, this is a much straighter port of
16
+ Paul Graham's scheme version of this code than his Common Lisp or my C
17
+ versions are. :-)
18
+
19
+ == REQUIREMENTS:
20
+
21
+ <b>This code will not work in JRuby or MacRuby (no callcc). It should work
22
+ in Ruby 1.9 with minor changes (callcc has moved to the 'continuation'
23
+ stdlib).</b>
24
+
25
+ == INSTALL:
26
+
27
+ To install:
28
+
29
+ $ gem install ambit
30
+
31
+ == DEVELOPERS:
32
+
33
+ After checking out the source, run:
34
+
35
+ $ rake newb
36
+
37
+ This task will install any missing dependencies, run the tests/specs,
38
+ and generate the RDoc.
39
+
40
+ == SYNOPSIS:
41
+
42
+ === What is Nondeterministic Programming?
43
+
44
+ Nondeterministic programming is a novel approach to problems where a
45
+ program must find a working solution out of many possible choices. It
46
+ greatly simplifies problems such graph searching, testing combinations of
47
+ values, and so on, where there are many possible values to consider, often
48
+ in some sort of hierarchical order, but the right combination is not known
49
+ in advance.
50
+
51
+ In such a situation, it can be useful to develop a program by pretending
52
+ our programming language includes knowledge of the future -- and is thus
53
+ able to _choose_ the right answer off the bat, and simply programming as
54
+ if this is the case.
55
+
56
+ A language with support for nondeterministic programming (such as Ruby
57
+ with this gem) helps us keep up this pretense by saving the state of
58
+ computation (with some limits) whenever we make an important choice. If
59
+ we later determine that we did _not_, in fact, make the correct choice
60
+ (lacking true language support for knowing the future), we can _fail_ the
61
+ current computation, which <em>causes computation to rewind to the last
62
+ choice made, and continue as if a different choice had been made</em>.
63
+
64
+ When all possible choices have been tried, the next time computation
65
+ _fails_, computation will be rewound to the previous choice point, and
66
+ will continue with the next possible choice from there.
67
+
68
+ Imagine, for instance, that we wish to test a combination lock with a
69
+ three-number combination, with each number between 1 and 10, inclusive:
70
+ Instead of writing code ourself to try every possible combination, we
71
+ simply proceed as if each choice was the correct one, failing if the lock
72
+ fails to open. In short:
73
+
74
+ first = Ambit.choose(1..10)
75
+ second = Ambit.choose(1..10)
76
+ third = Ambit.choose(1..10)
77
+
78
+ Ambit.fail! unless open_lock(first, second, third)
79
+
80
+ # when we get here, lock is open!
81
+
82
+ As our language does not actually implement knowledge of the future, this
83
+ will still try as many combinations as are needed to find the right one --
84
+ but we can program as if it has chosen the right one on the first try!
85
+
86
+ === How to Use This Gem
87
+
88
+ To get started, include this gem using
89
+
90
+ require 'rubygems'
91
+ require 'ambit
92
+
93
+ This gem provides the Ambit module. This module provides several methods
94
+ which implement nondeterministic programming.
95
+
96
+ ==== Choosing and Failing
97
+
98
+ The central method of Ambit is Ambit::choose.
99
+
100
+ Ambit::choose takes any enumerable (actually, any object which responds to
101
+ #each) as an argument, and begins a nondeterministic generate-and-test
102
+ process with the members of this object.
103
+
104
+ Ambit::choose immediately returns the first member of the enumerable, or
105
+ calls Ambit::fail! if the enumerable is empty:
106
+
107
+ a = Ambit::choose([1, 2, 3])
108
+ puts a
109
+ prints
110
+ 1
111
+
112
+ If, later, Ambit::fail! is called, <em>computation is rewound until the
113
+ point when Ambit::choose was last called</em>, and the next member of the
114
+ enumerable is returned <em>from the same call to Ambit::choose</em>:
115
+
116
+ a = Ambit::choose([1, 2, 3])
117
+ Ambit::fail! unless a.even?
118
+ puts a
119
+ prints
120
+ 2
121
+ (and only "2")
122
+
123
+ As an alternative, Ambit::assert can be used to fail if a condition holds;
124
+ Ambit::assert will rewind to the previous invocation of Ambit::choose if
125
+ and only if it's (single) argument is false:
126
+
127
+ a = Ambit::choose([1, 2, 3])
128
+ Ambit::assert a.even?
129
+ puts a
130
+ prints
131
+ 2
132
+ (and only "2")
133
+
134
+ Note that this call to Ambit::fail! (or Ambit::assert) can occur any amount
135
+ of time later, and works <em>even if the function which called choose has
136
+ since exited</em>. Execution is still rewound as needed to allow the next
137
+ value to be returned from the same call to Ambit::choose.
138
+
139
+ Calls to Ambit::choose can be nested to arbitrary depth -- each call to
140
+ Ambit::fail! will rewind to the <em>most recent</em> call to Ambit::choose.
141
+ If that set of choices has already returned every member of its enumerable,
142
+ execution is instead rewound to the previous invocation of Ambit::choose,
143
+ and execution continues with the next choice from that invocation's
144
+ enumerable:
145
+
146
+ a = Ambit::choose([1, 3, 5, 7, 9, 11, 13, 15])
147
+ b = Ambit::choose([0, 5, 10, 15])
148
+ Ambit::assert a == b
149
+ puts a
150
+ prints
151
+ 5
152
+ (and only "5")
153
+
154
+ If all choices from all past calls to Ambit::choose have been exhausted (or
155
+ if Ambit::fail! is called before any call to Ambit::choose), an exception of
156
+ type Ambit::ChoicesExhausted is raised instead.
157
+
158
+ ==== Side Effects
159
+
160
+ We've talked a lot above about "rewinding" computation to a previous choice
161
+ point. Not all computations can be rewound, however -- if the computation
162
+ we have performed since the choice point we are rewinding to has had side
163
+ effects (other than the choices made), those side effects will not
164
+ themselves be rewound. While some side effects (setting of variables) could
165
+ theoretically be tracked and undone, this would require very careful
166
+ semantics. And other side effects could not be undone by any level of
167
+ complexity added to our language -- if we have printed output to the user,
168
+ for instance, no amount of rewinding will make the user forget what he has
169
+ seen -- while we simulate the ability to see the future and to change the
170
+ past, we can, in fact, do neither.
171
+
172
+ This can sometimes cause confusion. This code, for instance:
173
+
174
+ a = Ambit::choose([1, 2, 3])
175
+ puts a
176
+ Ambit::fail! unless a.even?
177
+
178
+ prints
179
+
180
+ 1
181
+ 2
182
+
183
+ instead of only "2" -- the printing has already been done by the time we
184
+ call Ambit::fail!.
185
+
186
+ Such side effects can also be useful, however. This code:
187
+
188
+ i = 0
189
+ first = Ambit.choose(1..10)
190
+ second = Ambit.choose(1..10)
191
+ third = Ambit.choose(1..10)
192
+ i += 1
193
+ Ambit.fail! unless open_lock(first, second, third)
194
+ puts i
195
+
196
+ prints out the number of combinations which were tried in total (since +i+
197
+ remains incremented even when we rewind computation).
198
+
199
+ ==== More Than One Answer
200
+
201
+ Often, more than one combination of choices is interesting to consider -- it
202
+ may be useful, for instance, to see *all* combinations which do not fail,
203
+ instead of only the first.
204
+
205
+ Since Ambit::fail! will always rewind to the previous choice point, getting
206
+ more possible combinations is as easy as calling Ambit::fail! in order to
207
+ try the next combination, even though we have not, strictly, failed -- when
208
+ no more successful combinations are available, this call to Ambit::fail!
209
+ will instead raise an exception of type Ambit::ChoicesExhausted
210
+
211
+ begin
212
+ a = Ambit::choose([1, 3, 5, 7, 9, 11, 13, 15])
213
+ b = Ambit::choose([0, 5, 10, 15])
214
+ Ambit::assert a == b
215
+ puts a
216
+ Ambit::fail!
217
+ rescue Ambit::ChoicesExhausted
218
+ puts "Done."
219
+ end
220
+ prints
221
+ 5
222
+ 15
223
+ Done.
224
+
225
+ Note that this code, too depends on a side effect -- +a+ is output each time
226
+ we get a match, even though we then call Ambit::fail! to rewind computation
227
+ and try the next combination.
228
+
229
+ ==== Cleaning up
230
+
231
+ Ambit::clear! can be called at any time to eliminate all outstanding choices
232
+ on the default Generator, ending nondeterminism (and allowing any
233
+ outstanding alternate paths of execution to be garbage collected). This is
234
+ most useful when a given computation is finished, so that future invocations
235
+ of Ambit::fail! will not restart the now-finished computation with another
236
+ choice.
237
+
238
+ ==== Marking and Cutting
239
+
240
+ While Ambit::clear! can be used to abandon an entire set of nondeterministic
241
+ computations, sometimes it is useful to abandon only one branch of a
242
+ computation, while still keeping the ability to rewind to the choice which
243
+ first took us down that branch.
244
+
245
+ Suppose, for instance, that we are trying to guess a word with five letters:
246
+
247
+ a = Ambit::choose('a'..'z')
248
+ b = Ambit::choose('a'..'z')
249
+ c = Ambit::choose('a'..'z')
250
+ d = Ambit::choose('a'..'z')
251
+ Ambit::assert good_word(a, b, c, d)
252
+ print a, b, c, d
253
+
254
+ This works. But what if we were able to determine, once all four letters
255
+ were chosen, whether the first letter was correct? How would we proceed?
256
+
257
+ If we failed because the first letter was incorrect, we would continue trying
258
+ every possible value for the second, third and fourth letters -- even though none of
259
+ them could be correct. We need a way to rewind to an earlier choice point.
260
+
261
+ To work around this, Ambit provides a method, Ambit::cut! which "locks in" a
262
+ set of past choices, preventing them from being revisited later:
263
+
264
+ a = Ambit::choose('a'..'z')
265
+ Ambit::mark
266
+ b = Ambit::choose('a'..'z')
267
+ c = Ambit::choose('a'..'z')
268
+ d = Ambit::choose('a'..'z')
269
+ if !good_first_letter(a, b, c, d)
270
+ Ambit::cut!
271
+ Ambit::fail!
272
+ end
273
+ Ambit::assert good_word(a, b, c, d)
274
+ print a, b, c, d
275
+
276
+ When Ambit::cut! is called in the code above, all choices back to the
277
+ <em>most recent</em> call of Ambit::mark are wiped out -- the next call to
278
+ Ambit::fail! will rewind to the most recent Ambit::choose invocation
279
+ <em>before</em> the most recent call to Ambit::mark.
280
+
281
+ Ambit::cut! can also be used without Ambit::fail! to "commit" to all choices
282
+ since the last call to Ambit::mark -- in this case, we are saying that we
283
+ know these choices are good, so if we (later) fail, we want to rewind out of
284
+ the whole current branch of computation.
285
+
286
+ ==== Private Generators
287
+
288
+ In addition to using methods of the Ambit module directly, another option is
289
+ to allocate a Ambit::Generator object explicitly. All methods of the Ambit
290
+ module are also available as methods of Ambit::Generator (and in fact, the
291
+ module allocates a default Generator object to handle all calls made at the
292
+ module level).
293
+
294
+ Ambit::Generator::new can be used to allocate a new Generator:
295
+
296
+ nd = Ambit::Generator::new
297
+ nd.choose('a' .. 'e')
298
+
299
+ each object allocated in this fashion has its own set of choices, and
300
+ failing one will not directly affect others. Nesting choices from different
301
+ Generators is a good way to make code confusing, however, and should be
302
+ avoided -- this capability is mainly provided to allow multi-threaded
303
+ programs to safely use Ambit from more than one thread (see below).
304
+
305
+ Ambit::Generator#clear! is provided for the same reason as Ambit::clear!,
306
+ but it is often clearer to use a new Ambit::Generator object for each
307
+ unrelated set of nondeterministic computations.
308
+
309
+ ==== Compatibility
310
+
311
+ For historical reasons, Ambit::amb and Ambit::Generator#amb are provided as
312
+ aliases for Ambit::choose and Ambit::Generator#choose. Likewise, for
313
+ historical reasons, calling Ambit::amb (and Ambit::Generator#amb) with
314
+ no arguments is equivalent to calling Ambit::fail! (or
315
+ Ambit::Generator#fail!).
316
+
317
+ For the same reason, Ambit::require and Ambit::Generator#require are
318
+ provided as aliases for Ambit::assert and Ambit::Generator#assert.
319
+
320
+ These aliases allow for a more direct translation of programs written with
321
+ the *amb* operator discussed in *SICP* and elsewhere.
322
+
323
+ ==== Interaction with Threading
324
+
325
+ Given the strong modifications to flow of control which occur when a path of
326
+ computation is _failed_, care must be taken when using nondeterministic
327
+ programming in a multi-threaded program. The two main ways to do this are:
328
+
329
+ * perform all nondeterministic programming from a single thread of execution
330
+
331
+ * give each thread which will be using nondeterministic programming its own Ambit::Generator object. This can be done easily using thread local variables:
332
+
333
+ def nd_begin
334
+ Thread.current[:AMB] = Ambit::Generator.new
335
+ end
336
+
337
+ def nd_choose choices
338
+ Thread.current[:AMB].choose choices
339
+ end
340
+
341
+ def nd_fail!
342
+ Thread.current[:AMB].fail!
343
+ end
344
+
345
+ def nd_clear!
346
+ Thread.current[:AMB].clear!
347
+ end
348
+
349
+ === Longer example
350
+
351
+ This solution to the N queens problem is inspired by the prolog version in
352
+ <em>The Art of Prolog</em> by Leon Sterling and Ehud Shapiro[3], but is less
353
+ elegant, as this is not prolog (and I am not Sterling or Shapiro).
354
+
355
+ # we want to place N queens on an NxN chess board. Since we know no two queens
356
+ # can be in the same row, an array of N integers between 0 and N-1 will do to
357
+ # represent the placement. Since we know no two queens can be in the same column,
358
+ # each number from 1 .. N will appear once in this array; this means the solution
359
+ # is a permutation of 1 .. N
360
+
361
+ # Here is the complete board generator. Next is the test if a position is safe.
362
+
363
+ def queens n, board = []
364
+ if board.size == n
365
+ board
366
+ else
367
+ c = Ambit.choose(1..n)
368
+ Ambit.fail! unless safe board, c
369
+ queens n, board + [c]
370
+ end
371
+ end
372
+
373
+ # board is the first M columns of an NxN board, and is valid so far.
374
+ # piece is a proposed piece for the M+1th row of the board.
375
+ # returns true if piece is a valid placement, false otherwise
376
+
377
+ def safe board, piece
378
+ board.each_with_index do |c, r|
379
+ return false if c == piece # same column
380
+ # they're on the same diagonal if the distance in columns == the distance in rows
381
+ rdist = board.size - r
382
+ cdist = (piece - c).abs
383
+ return false if rdist == cdist
384
+ end
385
+ true
386
+ end
387
+
388
+ The file examples/queens.rb, installed with this gem, contains a version of
389
+ this with display code, and a command-line driver to print all solutions for
390
+ a given N.
391
+
392
+ === References
393
+
394
+ [1] Graham, Paul, <em>On Lisp</em>, Prentice Hall, 1993. Available online at http://www.paulgraham.com/onlisp.html
395
+
396
+ [2] Abelson, Harold and Gerald Jay Sussman, <em>Structure and Interpretation of Computer Programs, 2nd Edition</em>, MIT Press, 1996. Available online at http://mitpress.mit.edu/sicp/
397
+
398
+ [3] Sterling, Leon and Ehud Shapiro, <em>The Art of Prolog</em>, MIT Press, 1994
399
+
400
+ == LICENSE:
401
+
402
+ (The BSD 2-clause License)
403
+
404
+ Copyright (c) 2011 Jim Wise
405
+ All rights reserved.
406
+
407
+ Redistribution and use in source and binary forms, with or without
408
+ modification, are permitted provided that the following conditions
409
+ are met:
410
+
411
+ 1. Redistributions of source code must retain the above copyright
412
+ notice, this list of conditions and the following disclaimer.
413
+ 2. Redistributions in binary form must reproduce the above copyright
414
+ notice, this list of conditions and the following disclaimer in the
415
+ documentation and/or other materials provided with the distribution.
416
+
417
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
418
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
419
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
420
+ PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
421
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
422
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
423
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
424
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
425
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
426
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
427
+ POSSIBILITY OF SUCH DAMAGE.