ambit 0.9.1 → 0.10
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +11 -0
- data/Manifest.txt +1 -0
- data/README.rdoc +26 -6
- data/README.txt +26 -6
- data/examples/mapcolor.rb +129 -0
- data/lib/ambit.rb +45 -3
- data/test/test_ambit.rb +27 -1
- metadata +10 -11
data/History.txt
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
=== 0.10 / 2011-04-26
|
2
|
+
|
3
|
+
* Add Ambit::unmark! and Ambit::unmark_all!, which can be used to undo the
|
4
|
+
effects of the Ambit::mark operation -- see "Marking and Cutting" in
|
5
|
+
README for details.
|
6
|
+
|
7
|
+
* Add Ambit::trace and Ambit::untrace to turn on or off tracing of Ambit
|
8
|
+
operations to STDERR. In Ambit test cases, check for the environment
|
9
|
+
variable AMBIT_TRACE, and turn on tracing during execution of tests if it
|
10
|
+
is set.
|
11
|
+
|
1
12
|
=== 0.9.1 / 2011-04-26
|
2
13
|
|
3
14
|
* Minor documentation improvements
|
data/Manifest.txt
CHANGED
data/README.rdoc
CHANGED
@@ -165,7 +165,7 @@ we have performed since the choice point we are rewinding to has had side
|
|
165
165
|
effects (other than the choices made), those side effects will not
|
166
166
|
themselves be rewound. While some side effects (setting of variables) could
|
167
167
|
theoretically be tracked and undone, this would require very careful
|
168
|
-
semantics --
|
168
|
+
semantics -- and other side effects could not be undone by any level of
|
169
169
|
complexity added to our language. If we have printed output to the user,
|
170
170
|
for instance, no amount of rewinding will make the user forget what he has
|
171
171
|
seen; while we simulate the ability to see the future and to change the
|
@@ -176,9 +176,7 @@ This can sometimes cause confusion. This code, for instance:
|
|
176
176
|
a = Ambit::choose([1, 2, 3])
|
177
177
|
puts a
|
178
178
|
Ambit::fail! unless a.even?
|
179
|
-
|
180
179
|
prints
|
181
|
-
|
182
180
|
1
|
183
181
|
2
|
184
182
|
|
@@ -260,15 +258,15 @@ If we failed because the first letter was incorrect, we would continue trying
|
|
260
258
|
every possible value for the second, third and fourth letters -- even though none of
|
261
259
|
them could be correct. We need a way to rewind to an earlier choice point.
|
262
260
|
|
263
|
-
To
|
264
|
-
|
261
|
+
To allow this, Ambit provides a method, Ambit::cut! which "locks in" a set
|
262
|
+
of past choices, preventing them from being revisited later:
|
265
263
|
|
266
264
|
a = Ambit::choose('a'..'z')
|
267
265
|
Ambit::mark
|
268
266
|
b = Ambit::choose('a'..'z')
|
269
267
|
c = Ambit::choose('a'..'z')
|
270
268
|
d = Ambit::choose('a'..'z')
|
271
|
-
|
269
|
+
unless good_first_letter(a, b, c, d)
|
272
270
|
Ambit::cut!
|
273
271
|
Ambit::fail!
|
274
272
|
end
|
@@ -285,6 +283,23 @@ since the last call to Ambit::mark -- in this case, we are saying that we
|
|
285
283
|
know these choices are good, so if we (later) fail, we want to rewind out of
|
286
284
|
the whole current branch of computation.
|
287
285
|
|
286
|
+
Finally, Ambit::unmark! can be used to remove the most recent mark (making
|
287
|
+
the next Ambit::cut! operation cut back to an earlier mark (or commit to all
|
288
|
+
choices if no other mark exists), and the Ambit::unmark_all! operation can
|
289
|
+
be used to remove all current marks, making the next Ambit::cut! operation
|
290
|
+
commit to all choices made so far.
|
291
|
+
|
292
|
+
==== Watching Ambit work
|
293
|
+
|
294
|
+
The methods Ambit::trace and Ambit::untrace can be used to enable debug
|
295
|
+
tracing of Ambit operations. Repeated calls to Ambit::trace increase the
|
296
|
+
verbosity of trace output (though this has no effect in the current
|
297
|
+
version), and a specific trace level (as an integer) may also be passed to
|
298
|
+
Ambit::trace as an optional argument.
|
299
|
+
|
300
|
+
Trace output is written to STDERR. Trace output can be disabled by
|
301
|
+
specifying a trace level of 0, or by calling Ambit::untrace.
|
302
|
+
|
288
303
|
==== Private Generators
|
289
304
|
|
290
305
|
In addition to using methods of the Ambit module directly, another option is
|
@@ -308,6 +323,11 @@ Ambit::Generator#clear! is provided for the same reason as Ambit::clear!,
|
|
308
323
|
but it is often clearer to use a new Ambit::Generator object for each
|
309
324
|
unrelated set of nondeterministic computations.
|
310
325
|
|
326
|
+
As with other module-level operations, Ambit::trace and Ambit::untrace do
|
327
|
+
not turn on or off tracing for private generators -- the generator's own
|
328
|
+
Ambit::Generator#trace and Ambit::Generator#untrace must be used to enable
|
329
|
+
tracing of a private generator's operation.
|
330
|
+
|
311
331
|
==== Compatibility
|
312
332
|
|
313
333
|
For historical reasons, Ambit::amb and Ambit::Generator#amb are provided as
|
data/README.txt
CHANGED
@@ -165,7 +165,7 @@ we have performed since the choice point we are rewinding to has had side
|
|
165
165
|
effects (other than the choices made), those side effects will not
|
166
166
|
themselves be rewound. While some side effects (setting of variables) could
|
167
167
|
theoretically be tracked and undone, this would require very careful
|
168
|
-
semantics --
|
168
|
+
semantics -- and other side effects could not be undone by any level of
|
169
169
|
complexity added to our language. If we have printed output to the user,
|
170
170
|
for instance, no amount of rewinding will make the user forget what he has
|
171
171
|
seen; while we simulate the ability to see the future and to change the
|
@@ -176,9 +176,7 @@ This can sometimes cause confusion. This code, for instance:
|
|
176
176
|
a = Ambit::choose([1, 2, 3])
|
177
177
|
puts a
|
178
178
|
Ambit::fail! unless a.even?
|
179
|
-
|
180
179
|
prints
|
181
|
-
|
182
180
|
1
|
183
181
|
2
|
184
182
|
|
@@ -260,15 +258,15 @@ If we failed because the first letter was incorrect, we would continue trying
|
|
260
258
|
every possible value for the second, third and fourth letters -- even though none of
|
261
259
|
them could be correct. We need a way to rewind to an earlier choice point.
|
262
260
|
|
263
|
-
To
|
264
|
-
|
261
|
+
To allow this, Ambit provides a method, Ambit::cut! which "locks in" a set
|
262
|
+
of past choices, preventing them from being revisited later:
|
265
263
|
|
266
264
|
a = Ambit::choose('a'..'z')
|
267
265
|
Ambit::mark
|
268
266
|
b = Ambit::choose('a'..'z')
|
269
267
|
c = Ambit::choose('a'..'z')
|
270
268
|
d = Ambit::choose('a'..'z')
|
271
|
-
|
269
|
+
unless good_first_letter(a, b, c, d)
|
272
270
|
Ambit::cut!
|
273
271
|
Ambit::fail!
|
274
272
|
end
|
@@ -285,6 +283,23 @@ since the last call to Ambit::mark -- in this case, we are saying that we
|
|
285
283
|
know these choices are good, so if we (later) fail, we want to rewind out of
|
286
284
|
the whole current branch of computation.
|
287
285
|
|
286
|
+
Finally, Ambit::unmark! can be used to remove the most recent mark (making
|
287
|
+
the next Ambit::cut! operation cut back to an earlier mark (or commit to all
|
288
|
+
choices if no other mark exists), and the Ambit::unmark_all! operation can
|
289
|
+
be used to remove all current marks, making the next Ambit::cut! operation
|
290
|
+
commit to all choices made so far.
|
291
|
+
|
292
|
+
==== Watching Ambit work
|
293
|
+
|
294
|
+
The methods Ambit::trace and Ambit::untrace can be used to enable debug
|
295
|
+
tracing of Ambit operations. Repeated calls to Ambit::trace increase the
|
296
|
+
verbosity of trace output (though this has no effect in the current
|
297
|
+
version), and a specific trace level (as an integer) may also be passed to
|
298
|
+
Ambit::trace as an optional argument.
|
299
|
+
|
300
|
+
Trace output is written to STDERR. Trace output can be disabled by
|
301
|
+
specifying a trace level of 0, or by calling Ambit::untrace.
|
302
|
+
|
288
303
|
==== Private Generators
|
289
304
|
|
290
305
|
In addition to using methods of the Ambit module directly, another option is
|
@@ -308,6 +323,11 @@ Ambit::Generator#clear! is provided for the same reason as Ambit::clear!,
|
|
308
323
|
but it is often clearer to use a new Ambit::Generator object for each
|
309
324
|
unrelated set of nondeterministic computations.
|
310
325
|
|
326
|
+
As with other module-level operations, Ambit::trace and Ambit::untrace do
|
327
|
+
not turn on or off tracing for private generators -- the generator's own
|
328
|
+
Ambit::Generator#trace and Ambit::Generator#untrace must be used to enable
|
329
|
+
tracing of a private generator's operation.
|
330
|
+
|
311
331
|
==== Compatibility
|
312
332
|
|
313
333
|
For historical reasons, Ambit::amb and Ambit::Generator#amb are provided as
|
@@ -0,0 +1,129 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'ambit'
|
5
|
+
|
6
|
+
# yes, I know, no monaco, luxembourg, or andorra
|
7
|
+
WesternEurope = {
|
8
|
+
:portugal => [:spain],
|
9
|
+
:spain => [:france, :portugal, :andorra],
|
10
|
+
:france => [:spain, :belgium, :germany, :switzerland, :italy, :luxembourg, :andorra],
|
11
|
+
:belgium => [:france, :netherlands, :germany, :luxembourg],
|
12
|
+
:netherlands => [:belgium, :germany],
|
13
|
+
:germany => [:france, :belgium, :netherlands, :switzerland, :denmark, :austria, :luxembourg],
|
14
|
+
:denmark => [:germany],
|
15
|
+
:switzerland => [:france, :germany, :austria, :italy],
|
16
|
+
:italy => [:france, :switzerland, :austria],
|
17
|
+
:austria => [:germany, :switzerland, :italy],
|
18
|
+
:luxembourg => [:france, :belgium, :germany],
|
19
|
+
:andorra => [:spain, :france],
|
20
|
+
}
|
21
|
+
|
22
|
+
ThreeByThree = {
|
23
|
+
:a => [:b, :d],
|
24
|
+
:b => [:a, :c, :e],
|
25
|
+
:c => [:b, :f],
|
26
|
+
:d => [:a, :e, :g],
|
27
|
+
:e => [:b, :d, :f, :h],
|
28
|
+
:f => [:c, :e, :i],
|
29
|
+
:g => [:d, :h],
|
30
|
+
:h => [:e, :g, :i],
|
31
|
+
:i => [:f, :h],
|
32
|
+
}
|
33
|
+
|
34
|
+
# from http://spicerack.sr.unh.edu/~student/tutorial/fourColor/FourColor.html
|
35
|
+
Example = {
|
36
|
+
:inner => [:ne, :se, :sw, :nw],
|
37
|
+
:ne => [:inner, :se, :nw, :outer],
|
38
|
+
:se => [:inner, :ne, :sw, :outer],
|
39
|
+
:sw => [:inner, :se, :nw, :outer],
|
40
|
+
:nw => [:inner, :ne, :sw, :outer],
|
41
|
+
:outer => [:ne, :se, :sw, :nw],
|
42
|
+
}
|
43
|
+
|
44
|
+
# Example which forces more than one level of backtrack:
|
45
|
+
# | |
|
46
|
+
# +-------+ |
|
47
|
+
# |a|b|c|d| |
|
48
|
+
# +-------+ |
|
49
|
+
# | e | |
|
50
|
+
# +-------+ |
|
51
|
+
# | f | g |
|
52
|
+
|
53
|
+
BackTrack = {
|
54
|
+
:a => [:b, :e, :g],
|
55
|
+
:b => [:a, :c, :e, :g],
|
56
|
+
:c => [:b, :d, :e, :g],
|
57
|
+
:d => [:c, :e, :g],
|
58
|
+
:e => [:a, :b, :c, :d, :f, :g],
|
59
|
+
:f => [:e, :g],
|
60
|
+
:g => [:a, :b, :c, :d, :e, :f],
|
61
|
+
}
|
62
|
+
Colors = [:red, :yellow, :blue, :green]
|
63
|
+
|
64
|
+
# when called as colorize2(map), we start from the beginning colorizing that
|
65
|
+
# map. when recursively called as colorize2(map, countries, colorized),
|
66
|
+
# countries are the countries left to colorize, and colorized are the
|
67
|
+
# assignments made so far.
|
68
|
+
|
69
|
+
def colorize map, countries=map.keys, colorized={}
|
70
|
+
country=countries.first
|
71
|
+
return colorized if country.nil?
|
72
|
+
|
73
|
+
color = Ambit.choose Colors
|
74
|
+
#puts "considering #{color} for #{country}"
|
75
|
+
map[country].each {|n| Ambit.assert colorized[n] != color}
|
76
|
+
|
77
|
+
colorized_new = colorized.clone # fake a functional view of hash
|
78
|
+
colorized_new[country] = color
|
79
|
+
colorize map, countries.drop(1), colorized_new
|
80
|
+
end
|
81
|
+
|
82
|
+
# an alternative version, using iteration instead of recursion
|
83
|
+
def colorize_iterating map
|
84
|
+
# map from country to its color
|
85
|
+
colorized = {}
|
86
|
+
map.each do |country, neighbors|
|
87
|
+
local_colorized = colorized.clone # fake a functional view of colorized
|
88
|
+
color = Ambit.choose Colors
|
89
|
+
#puts "considering #{color} for #{country}"
|
90
|
+
neighbors.each {|n| Ambit.assert colorized[n] != color}
|
91
|
+
local_colorized[country] = color
|
92
|
+
colorized = local_colorized
|
93
|
+
end
|
94
|
+
colorized
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
# a deterministic check, for comparison purposes
|
99
|
+
def check map, colorized
|
100
|
+
map.each do |country, neighbors|
|
101
|
+
color = colorized[country]
|
102
|
+
neighbors.each do |neighbor|
|
103
|
+
if colorized[neighbor] == color
|
104
|
+
raise "country #{country} and neighbor #{neighbor} have the same color!"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_map map
|
111
|
+
colorized = colorize map
|
112
|
+
check map, colorized
|
113
|
+
|
114
|
+
colorized.each do |country, color|
|
115
|
+
puts "#{country} => #{color}"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
puts "Simple example:"
|
120
|
+
test_map Example
|
121
|
+
puts ""
|
122
|
+
puts "Complex example:"
|
123
|
+
test_map BackTrack
|
124
|
+
puts ""
|
125
|
+
puts "Three-by-three grid:"
|
126
|
+
test_map ThreeByThree
|
127
|
+
puts ""
|
128
|
+
puts "Western Europe:"
|
129
|
+
test_map WesternEurope
|
data/lib/ambit.rb
CHANGED
@@ -7,7 +7,7 @@
|
|
7
7
|
|
8
8
|
module Ambit
|
9
9
|
|
10
|
-
VERSION = '0.
|
10
|
+
VERSION = '0.10'
|
11
11
|
|
12
12
|
# A ChoicesExhausted exception is raised if the outermost choose invocation of
|
13
13
|
# a Generator has run out of choices, indicating that no (more) solutions are possible.
|
@@ -20,6 +20,24 @@ module Ambit
|
|
20
20
|
# See "Private Generators" in the README for details
|
21
21
|
def initialize
|
22
22
|
@paths = []
|
23
|
+
@trace = 0
|
24
|
+
end
|
25
|
+
|
26
|
+
# Turn on tracing (to standard error) of Ambit operations
|
27
|
+
#
|
28
|
+
# The optional level argument sets the verbosity -- if not passed, each
|
29
|
+
# call to this method increases verbosity
|
30
|
+
def trace lvl=false
|
31
|
+
if lvl
|
32
|
+
@trace = lvl
|
33
|
+
else
|
34
|
+
@trace = @trace + 1
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Turn off tracing (to standard error) of Ambit operations
|
39
|
+
def untrace
|
40
|
+
@trace = 0
|
23
41
|
end
|
24
42
|
|
25
43
|
# Clear all outstanding choices registered with this generator.
|
@@ -46,7 +64,9 @@ module Ambit
|
|
46
64
|
ch = choices.clone # clone it in case it's modified by the caller
|
47
65
|
ch.each do |choice|
|
48
66
|
callcc do |cc|
|
67
|
+
STDERR.print "choosing from " + choices.inspect + ": " if @trace > 0
|
49
68
|
@paths.unshift cc
|
69
|
+
STDERR.puts choice.inspect if @trace > 0
|
50
70
|
return choice
|
51
71
|
end
|
52
72
|
end
|
@@ -60,10 +80,12 @@ module Ambit
|
|
60
80
|
def fail!
|
61
81
|
raise ChoicesExhausted.new if @paths.empty?
|
62
82
|
cc = @paths.shift
|
63
|
-
# if it quacks (or can be called) like a duck, call it -- it's either a Proc
|
83
|
+
# if it quacks (or can be called) like a duck, call it -- it's either a Proc
|
84
|
+
# from #mark or a Continuation from #choose
|
64
85
|
cc.call
|
65
86
|
end
|
66
87
|
|
88
|
+
# Fail unless a condition holds.
|
67
89
|
def assert cond
|
68
90
|
fail! unless cond
|
69
91
|
end
|
@@ -77,10 +99,30 @@ module Ambit
|
|
77
99
|
@paths.unshift Proc.new {self.fail!}
|
78
100
|
end
|
79
101
|
|
80
|
-
#
|
102
|
+
# Remove the most recent mark
|
103
|
+
#
|
104
|
+
# See "Marking and Cutting" in README for details
|
105
|
+
def unmark!
|
106
|
+
STDERR.puts "unmark!" if @trace > 0
|
107
|
+
return if @paths.empty?
|
108
|
+
n = @paths.rindex {|x| x.instance_of? Proc}
|
109
|
+
n and @paths.delete_at(n)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Remove all marks
|
113
|
+
#
|
114
|
+
# See "Marking and Cutting" in README for details
|
115
|
+
def unmark_all!
|
116
|
+
STDERR.puts "unmark_all!" if @trace > 0
|
117
|
+
return if @paths.empty?
|
118
|
+
@paths = @paths.reject {|x| x.instance_of? Proc}
|
119
|
+
end
|
120
|
+
|
121
|
+
# Commit to all choices since the last #mark operation.
|
81
122
|
#
|
82
123
|
# See "Marking and Cutting" in README for details
|
83
124
|
def cut!
|
125
|
+
STDERR.puts "cut!" if @trace > 0
|
84
126
|
return if @paths.empty?
|
85
127
|
# rewind paths back to the last mark
|
86
128
|
@paths = @paths.drop_while {|x| x.instance_of? Continuation}
|
data/test/test_ambit.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
require "test/unit"
|
2
2
|
require "ambit"
|
3
3
|
|
4
|
+
Ambit::trace if ENV['AMBIT_TRACE']
|
5
|
+
|
6
|
+
|
4
7
|
class TestAmbit < Test::Unit::TestCase
|
5
8
|
|
6
9
|
def test_simple_default
|
@@ -64,5 +67,28 @@ class TestAmbit < Test::Unit::TestCase
|
|
64
67
|
rescue Ambit::ChoicesExhausted
|
65
68
|
assert(i==15)
|
66
69
|
end
|
67
|
-
|
70
|
+
|
71
|
+
def test_unmark_all
|
72
|
+
a = Ambit::choose(1..3)
|
73
|
+
Ambit::mark
|
74
|
+
b = Ambit::choose(1..3)
|
75
|
+
Ambit::unmark_all!
|
76
|
+
# if we hadn't unmarked here, a cut would leave us choices
|
77
|
+
Ambit::cut!
|
78
|
+
assert_raise Ambit::ChoicesExhausted do
|
79
|
+
Ambit::fail!
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_unmark
|
84
|
+
a = Ambit::choose(1..3)
|
85
|
+
Ambit::mark
|
86
|
+
Ambit::unmark!
|
87
|
+
# if we hadn't unmarked here, a cut would leave us choices
|
88
|
+
Ambit::cut!
|
89
|
+
assert_raise Ambit::ChoicesExhausted do
|
90
|
+
Ambit::fail!
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
68
94
|
end
|
metadata
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ambit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 31
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
|
10
|
-
version: 0.9.1
|
8
|
+
- 10
|
9
|
+
version: "0.10"
|
11
10
|
platform: ruby
|
12
11
|
authors:
|
13
12
|
- Jim Wise
|
@@ -15,7 +14,7 @@ autorequire:
|
|
15
14
|
bindir: bin
|
16
15
|
cert_chain: []
|
17
16
|
|
18
|
-
date: 2011-
|
17
|
+
date: 2011-09-22 00:00:00 Z
|
19
18
|
dependencies:
|
20
19
|
- !ruby/object:Gem::Dependency
|
21
20
|
name: hoe
|
@@ -23,14 +22,13 @@ dependencies:
|
|
23
22
|
requirement: &id001 !ruby/object:Gem::Requirement
|
24
23
|
none: false
|
25
24
|
requirements:
|
26
|
-
- -
|
25
|
+
- - ~>
|
27
26
|
- !ruby/object:Gem::Version
|
28
|
-
hash:
|
27
|
+
hash: 27
|
29
28
|
segments:
|
30
29
|
- 2
|
31
|
-
-
|
32
|
-
|
33
|
-
version: 2.9.4
|
30
|
+
- 12
|
31
|
+
version: "2.12"
|
34
32
|
type: :development
|
35
33
|
version_requirements: *id001
|
36
34
|
description: |-
|
@@ -61,6 +59,7 @@ files:
|
|
61
59
|
- README.txt
|
62
60
|
- Rakefile
|
63
61
|
- examples/example.rb
|
62
|
+
- examples/mapcolor.rb
|
64
63
|
- examples/queens.rb
|
65
64
|
- lib/ambit.rb
|
66
65
|
- test/test_ambit.rb
|
@@ -95,7 +94,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
95
94
|
requirements: []
|
96
95
|
|
97
96
|
rubyforge_project: ambit
|
98
|
-
rubygems_version: 1.
|
97
|
+
rubygems_version: 1.8.5
|
99
98
|
signing_key:
|
100
99
|
specification_version: 3
|
101
100
|
summary: This is an all-ruby implementation of choose/fail nondeterministic programming with branch cut, as described in Chapter 22 of Paul Graham's <em>On Lisp</em>[1], or Section 4.3 of <em>SICP</em>[2]
|