amb 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +3 -0
- data/MIT-LICENSE +20 -0
- data/README.md +74 -0
- data/lib/amb.rb +2 -0
- data/lib/amb/amb.rb +160 -0
- data/lib/amb/amb_operator.rb +68 -0
- data/lib/amb/version.rb +6 -0
- metadata +63 -0
data/CHANGELOG.md
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2010 Jean-Denis Vauguet
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
This gem is a compilation of several implementations of the **ambiguous function/operator** pattern, useful for constraint programming. Each implementation comes with its own copyright notice and authors, so be sure to check the CREDITS file!
|
2
|
+
|
3
|
+
Ever wanted to:
|
4
|
+
|
5
|
+
* write straightforward parsers?
|
6
|
+
* solve crosswords and sudokus or the n-queens problem?
|
7
|
+
* design an equation-solver or a modelizer?
|
8
|
+
* understand how logic programming languages (Prolog…) work?
|
9
|
+
|
10
|
+
Them amb may be of interest to you.
|
11
|
+
|
12
|
+
## Synopsis
|
13
|
+
|
14
|
+
*You may want to jump straight to the Examples section if formal stuff annoys you.*
|
15
|
+
|
16
|
+
Ambiguous functions were [defined in John McCarty's 1963 paper](http://www-formal.stanford.edu/jmc/basis1/node7.html) *A basis for a mathematical theory of computation*. They allow for writing nondeterministic programs which contain various alternatives for the program flow.
|
17
|
+
|
18
|
+
Typically, the programmer would specify a limited number of alternatives (eg. different values for a variable), so that the program must later choose between them at runtime to find which one(s) are valid predicates to solve the issue at stake. The "issue" is often formalized as a constraint or a set of constraints referencing the variables some alternatives have been provided for. The evaluation may result in the discovery of dead ends, in which case it must "switch" to a previous branching point and start over with a different alternative.
|
19
|
+
|
20
|
+
To discover which alternatives groups are valid, a check/discard/switch strategy must be enforced by the program. Most often it will make use of a ambiguous operator, `amb()` which implements this strategy. The most basic version of `amb` would be `amb(x,y)`, which returns, *in an unpredictible way*, either x or y when both are defined, or if only one is defined, whichever is defined. If none is defined, it will terminate the program and complain no solution can be found given these alternatives and constraint(s). Using some recursivity, `amb()` may be used to define arbitrary, complex ambiguous functions. It is quite difficult to implement a good `amb()` operator matching that formal definition. A simpler (yet functional) version has the operator return its first defined argument, then pass over the next defined one in case of a dead-end.
|
21
|
+
|
22
|
+
The most common strategy used for implementing `amb()`'s logic (check/discard) is backtracking, which is "a general algorithm for finding all (or some) solutions to some computational problem, that incrementally builds candidates to the solutions, and abandons each partial candidate *c* ("backtracks") as soon as it determines that *c* cannot possibly be completed to a valid solution". It almost always relies on some sort of continuations. It may be turned into backjumping for more efficiency, depending on the problem at stake. Another strategy is reinforcement learning or constraint learning, as used in some AI systems. This library implements simple backtracking only.
|
23
|
+
|
24
|
+
More details on all of this under the `doc/`` folder (*pending*).
|
25
|
+
|
26
|
+
## Examples
|
27
|
+
|
28
|
+
``` ruby
|
29
|
+
# Amb is a module
|
30
|
+
A = Class.new { include Amb }.new
|
31
|
+
|
32
|
+
x = A.choose(1,2,3,4) # register each alternative as a backtrack point
|
33
|
+
y = A.choose(1,2,3,4) # same for y, so we have 16 backtracking branches
|
34
|
+
|
35
|
+
puts "examining x = #{x}, y = #{y}"
|
36
|
+
|
37
|
+
# assertions will backtrack if a dead-end is discovered
|
38
|
+
A.assert x + y == 5
|
39
|
+
A.assert x - y == 1
|
40
|
+
#A.assert x == 2 # if this line is uncommented, then no solution can be found
|
41
|
+
# and the program raises a Amb::ExhaustedError
|
42
|
+
|
43
|
+
puts "> solution: x = #{x}, y = #{y}"
|
44
|
+
```
|
45
|
+
|
46
|
+
will produce:
|
47
|
+
|
48
|
+
``` ruby
|
49
|
+
examining x = 1, y = 1
|
50
|
+
examining x = 1, y = 2
|
51
|
+
examining x = 1, y = 3
|
52
|
+
examining x = 1, y = 4
|
53
|
+
examining x = 2, y = 1
|
54
|
+
examining x = 2, y = 2
|
55
|
+
examining x = 2, y = 3
|
56
|
+
examining x = 2, y = 4
|
57
|
+
examining x = 3, y = 1
|
58
|
+
examining x = 3, y = 2
|
59
|
+
solution: x = 3, y = 2
|
60
|
+
```
|
61
|
+
This illustrates the incremental, backtracking pattern. Many more examples under the `examples/` directory.
|
62
|
+
|
63
|
+
## Installation
|
64
|
+
|
65
|
+
gem install amb
|
66
|
+
|
67
|
+
## Usage
|
68
|
+
|
69
|
+
## TODO
|
70
|
+
|
71
|
+
## See also
|
72
|
+
|
73
|
+
* the `doc/` and `examples/` directories.
|
74
|
+
* Continuations and fibers concepts.
|
data/lib/amb.rb
ADDED
data/lib/amb/amb.rb
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
# Copyright 2006 by Jim Weirich <jim@weirichhouse.org>. All rights reserved.
|
2
|
+
# Permission is granted for use, modification and distribution as
|
3
|
+
# long as the above copyright notice is included.
|
4
|
+
# Modified by Jean-Denis Vauguet <jd@vauguet.fr> for Amb gem release.
|
5
|
+
|
6
|
+
# Amb is an ambiguous choice maker. You can ask an Amb object to
|
7
|
+
# select from a discrete list of choices, and then specify a set of
|
8
|
+
# constraints on those choices. After the constraints have been
|
9
|
+
# specified, you are guaranteed that the choices made earlier by amb
|
10
|
+
# will obey the constraints.
|
11
|
+
#
|
12
|
+
# For example, consider the following code:
|
13
|
+
#
|
14
|
+
# amb = Class.new { include Amb }.new
|
15
|
+
# x = amb.choose(1,2,3,4)
|
16
|
+
#
|
17
|
+
# At this point, amb may have chosen any of the four numbers (1
|
18
|
+
# through 4) to be assigned to x. But, now we can assert some
|
19
|
+
# conditions:
|
20
|
+
#
|
21
|
+
# amb.assert (x % 2) == 0
|
22
|
+
#
|
23
|
+
# This asserts that x must be even, so we know that the choice made by
|
24
|
+
# amb will be either 2 or 4. Next we assert:
|
25
|
+
#
|
26
|
+
# amb.assert x >= 3
|
27
|
+
#
|
28
|
+
# This further constrains our choice to 4.
|
29
|
+
#
|
30
|
+
# puts x # prints '4'
|
31
|
+
#
|
32
|
+
# Amb works by saving a contination at each choice point and
|
33
|
+
# backtracking to previousl choices if the contraints are not
|
34
|
+
# satisfied. In actual terms, the choice reconsidered and all the
|
35
|
+
# code following the choice is re-run after failed assertion.
|
36
|
+
#
|
37
|
+
# You can print out all the solutions by printing the solution and
|
38
|
+
# then explicitly failing to force another choice. For example:
|
39
|
+
#
|
40
|
+
# amb = Class.new { include Amb }.new
|
41
|
+
# x = Amb.choose(*(1..10))
|
42
|
+
# y = Amb.choose(*(1..10))
|
43
|
+
# amb.assert x + y == 15
|
44
|
+
#
|
45
|
+
# puts "x = #{x}, y = #{y}"
|
46
|
+
#
|
47
|
+
# amb.failure
|
48
|
+
#
|
49
|
+
# The above code will print all the solutions to the equation x + y ==
|
50
|
+
# 15 where x and y are integers between 1 and 10.
|
51
|
+
#
|
52
|
+
# The Amb class has two convience functions, solve and solve_all for
|
53
|
+
# encapsulating the use of Amb.
|
54
|
+
#
|
55
|
+
# This example finds the first solution to a set of constraints:
|
56
|
+
#
|
57
|
+
# Amb.solve do |amb|
|
58
|
+
# x = amb.choose(1,2,3,4)
|
59
|
+
# amb.assert (x % 2) == 0
|
60
|
+
# puts x
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# This example finds all the solutions to a set of constraints:
|
64
|
+
#
|
65
|
+
# Amb.solve_all do |amb|
|
66
|
+
# x = amb.choose(1,2,3,4)
|
67
|
+
# amb.assert (x % 2) == 0
|
68
|
+
# puts x
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
module Amb
|
72
|
+
require 'continuation'
|
73
|
+
|
74
|
+
class ExhaustedError < RuntimeError; end
|
75
|
+
|
76
|
+
def self.included(base)
|
77
|
+
base.extend(ClassMethods)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Memoize and return the alternatives continuations.
|
81
|
+
#
|
82
|
+
# @return [Array<Proc, Continuation>]
|
83
|
+
#
|
84
|
+
def back_amb
|
85
|
+
@__back_amb ||= [Proc.new { fail ExhaustedError, "amb tree exhausted" }]
|
86
|
+
end
|
87
|
+
|
88
|
+
# Make a choice amoung a set of discrete values.
|
89
|
+
#
|
90
|
+
# @param choices
|
91
|
+
#
|
92
|
+
def choose(*choices)
|
93
|
+
choices.each do |choice|
|
94
|
+
callcc do |fk|
|
95
|
+
back_amb << fk
|
96
|
+
return choice
|
97
|
+
end
|
98
|
+
end
|
99
|
+
failure
|
100
|
+
end
|
101
|
+
|
102
|
+
# Unconditional failure of a constraint, causing the last choice to be
|
103
|
+
# retried. This is equivalent to saying `assert(false)`.
|
104
|
+
#
|
105
|
+
def failure
|
106
|
+
back_amb.pop.call
|
107
|
+
end
|
108
|
+
|
109
|
+
# Assert the given condition is true. If the condition is false,
|
110
|
+
# cause a failure and retry the last choice. One may specify the condition
|
111
|
+
# either as an argument or as a block. If a block is provided, it will be
|
112
|
+
# passed the arguments, whereas without a block, the first argument will
|
113
|
+
# be used as the condition.
|
114
|
+
#
|
115
|
+
# @param cond
|
116
|
+
# @yield
|
117
|
+
#
|
118
|
+
def assert(*args)
|
119
|
+
cond = block_given? ? yield(*args) : args.first
|
120
|
+
failure unless cond
|
121
|
+
end
|
122
|
+
|
123
|
+
# Report the given failure message. This is called by solve in the event
|
124
|
+
# that no solutions are found, and by +solve_all+ when no more solutions
|
125
|
+
# are to be found. Report will simply display the message to standard
|
126
|
+
# output, but you may override this method in a derived class if you need
|
127
|
+
# different behavior.
|
128
|
+
#
|
129
|
+
# @param [#to_s] failure_message
|
130
|
+
#
|
131
|
+
def report(failure_message)
|
132
|
+
puts failure_message
|
133
|
+
end
|
134
|
+
|
135
|
+
module ClassMethods
|
136
|
+
# Class convenience method to search for the first solution to the
|
137
|
+
# constraints.
|
138
|
+
#
|
139
|
+
def solve(failure_message="No Solution")
|
140
|
+
amb = self.new
|
141
|
+
yield(amb)
|
142
|
+
rescue Amb::ExhaustedError => ex
|
143
|
+
amb.report(failure_message)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Class convenience method to search for all the solutions to the
|
147
|
+
# constraints.
|
148
|
+
#
|
149
|
+
def solve_all(failure_message="No More Solutions")
|
150
|
+
amb = self.new
|
151
|
+
yield(amb)
|
152
|
+
amb.failure
|
153
|
+
rescue Amb::ExhaustedError => ex
|
154
|
+
amb.report(failure_message)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
extend self
|
159
|
+
extend self::ClassMethods
|
160
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# By Eric Kidd.
|
2
|
+
# See http://www.randomhacks.net/articles/2005/10/11/amb-operator.
|
3
|
+
module Amb
|
4
|
+
# Use the ambiguous computation pattern as a stand-alone operator.
|
5
|
+
module Operator
|
6
|
+
require 'continuation'
|
7
|
+
|
8
|
+
# A list of places we can "rewind" to if we encounter amb with no arguments.
|
9
|
+
$backtrack_points = []
|
10
|
+
|
11
|
+
# Rewind to our most recent backtrack point.
|
12
|
+
#
|
13
|
+
def backtrack
|
14
|
+
if $backtrack_points.empty?
|
15
|
+
raise "Can't backtrack"
|
16
|
+
else
|
17
|
+
$backtrack_points.pop.call
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Recursive implementation of the amb operator, as defined by McCarty[63].
|
22
|
+
#
|
23
|
+
# When choices are provided as arguments, it will save a backtracking save
|
24
|
+
# point for each and wait for being resumed. Resuming (starting the
|
25
|
+
# check/discard process) is triggered by calling amb without any arguments.
|
26
|
+
# It is expected you'll attach a conditionnal stating the constraint.
|
27
|
+
#
|
28
|
+
# @param choices a set of alternatives
|
29
|
+
# @example
|
30
|
+
#
|
31
|
+
# # amb will (appear to) choose values
|
32
|
+
# # for x and y that prevent future trouble.
|
33
|
+
# x = amb 1, 2, 3
|
34
|
+
# y = amb 4, 5, 6
|
35
|
+
#
|
36
|
+
# # Ooops! If x*y isn't 8, amb would
|
37
|
+
# # get angry. You wouldn't like
|
38
|
+
# # amb when it's angry.
|
39
|
+
# amb if x*y != 8
|
40
|
+
#
|
41
|
+
# # Sure enough, x is 2 and y is 4.
|
42
|
+
# puts "x = #{x}, y = #{y}"
|
43
|
+
#
|
44
|
+
def amb *choices
|
45
|
+
# Fail if we have no arguments.
|
46
|
+
backtrack if choices.empty?
|
47
|
+
|
48
|
+
callcc do |cc|
|
49
|
+
# cc contains the "current continuation". When called, it will make the
|
50
|
+
# program rewind to the end of this block.
|
51
|
+
$backtrack_points << cc
|
52
|
+
|
53
|
+
# Return our first argument.
|
54
|
+
return choices[0]
|
55
|
+
end
|
56
|
+
|
57
|
+
# We only get here if we backtrack by resuming the stored value of cc.
|
58
|
+
# We call amb recursively with the arguments we didn't use.
|
59
|
+
amb(*choices[1...choices.length])
|
60
|
+
end
|
61
|
+
|
62
|
+
# Backtracking beyond a call to cut is strictly forbidden.
|
63
|
+
#
|
64
|
+
def cut
|
65
|
+
$backtrack_points = []
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/amb/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: amb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jean-Denis Vauguet <jd@vauguet.fr>
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-08-28 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: amb
|
16
|
+
requirement: &73260010 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *73260010
|
25
|
+
description: This gem is a compilation of several implementations of the ambiguous
|
26
|
+
function/operator, useful for constraint programming.
|
27
|
+
email: jd@vauguet.fr
|
28
|
+
executables: []
|
29
|
+
extensions: []
|
30
|
+
extra_rdoc_files: []
|
31
|
+
files:
|
32
|
+
- lib/amb.rb
|
33
|
+
- lib/amb/amb_operator.rb
|
34
|
+
- lib/amb/amb.rb
|
35
|
+
- lib/amb/version.rb
|
36
|
+
- MIT-LICENSE
|
37
|
+
- README.md
|
38
|
+
- CHANGELOG.md
|
39
|
+
homepage: http://www.github.com/chikamichi/amb
|
40
|
+
licenses: []
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options: []
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - ! '>='
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ! '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
requirements: []
|
58
|
+
rubyforge_project:
|
59
|
+
rubygems_version: 1.8.6
|
60
|
+
signing_key:
|
61
|
+
specification_version: 3
|
62
|
+
summary: McCarty's ambiguous function/operator implementations
|
63
|
+
test_files: []
|