ambit 0.9.1 → 0.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -6,6 +6,7 @@ README.rdoc
6
6
  README.txt
7
7
  Rakefile
8
8
  examples/example.rb
9
+ examples/mapcolor.rb
9
10
  examples/queens.rb
10
11
  lib/ambit.rb
11
12
  test/test_ambit.rb
@@ -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 -- nd other side effects could not be undone by any level of
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 work around this, Ambit provides a method, Ambit::cut! which "locks in" a
264
- set of past choices, preventing them from being revisited later:
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
- if !good_first_letter(a, b, c, d)
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 -- nd other side effects could not be undone by any level of
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 work around this, Ambit provides a method, Ambit::cut! which "locks in" a
264
- set of past choices, preventing them from being revisited later:
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
- if !good_first_letter(a, b, c, d)
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
@@ -7,7 +7,7 @@
7
7
 
8
8
  module Ambit
9
9
 
10
- VERSION = '0.9.1'
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 from #mark or a Continuation from #choose
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
- # Commit to all choices since the last #mark! operation.
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}
@@ -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: 57
4
+ hash: 31
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 9
9
- - 1
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-04-26 00:00:00 Z
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: 35
27
+ hash: 27
29
28
  segments:
30
29
  - 2
31
- - 9
32
- - 4
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.7.2
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]