ambit 0.9.1 → 0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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]
|