winston 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +32 -0
- data/Gemfile +2 -0
- data/LICENSE +22 -0
- data/README.md +130 -0
- data/Rakefile +1 -0
- data/lib/winston.rb +7 -0
- data/lib/winston/backtrack.rb +40 -0
- data/lib/winston/constraint.rb +19 -0
- data/lib/winston/constraints/all_different.rb +9 -0
- data/lib/winston/csp.rb +41 -0
- data/lib/winston/domain.rb +14 -0
- data/lib/winston/variable.rb +12 -0
- data/spec/examples/numbers_spec.rb +38 -0
- data/spec/winston/backtrack_spec.rb +48 -0
- data/spec/winston/constraint_spec.rb +99 -0
- data/spec/winston/constraints/all_different_spec.rb +22 -0
- data/spec/winston/csp_spec.rb +75 -0
- data/spec/winston/domain_spec.rb +35 -0
- data/spec/winston/variable_spec.rb +14 -0
- data/winston.gemspec +19 -0
- metadata +112 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b136df3119babb39b3950a0cc96b393d3344453e
|
4
|
+
data.tar.gz: c10c1db2ceda4d4a553b209e7fceb433375eeabd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d47e3a5f91c608f5c5ad49a649bc9aabf9fd29a243ce0efffa89610a2ba1ad37c68ea026cefbf0b42a1fd0574887af20a50820bd357a0c85f5bd8fe5670ea0a9
|
7
|
+
data.tar.gz: f4a9e5f2c31ef671a50618eb0236d15f59861cf39c2659c980dc34e2e7123f4a84eff5c7c4602220ef0da16ca82eb4541050ffbdeadde0b623601e147b848469
|
data/.gitignore
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/test/tmp/
|
9
|
+
/test/version_tmp/
|
10
|
+
/tmp/
|
11
|
+
|
12
|
+
## Specific to RubyMotion:
|
13
|
+
.dat*
|
14
|
+
.repl_history
|
15
|
+
build/
|
16
|
+
|
17
|
+
## Documentation cache and generated files:
|
18
|
+
/.yardoc/
|
19
|
+
/_yardoc/
|
20
|
+
/doc/
|
21
|
+
/rdoc/
|
22
|
+
|
23
|
+
## Environment normalisation:
|
24
|
+
/.bundle/
|
25
|
+
/lib/bundler/man/
|
26
|
+
|
27
|
+
Gemfile.lock
|
28
|
+
.ruby-version
|
29
|
+
.ruby-gemset
|
30
|
+
|
31
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
32
|
+
.rvmrc
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 David Michael Nelson
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
22
|
+
|
data/README.md
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
# Winston
|
2
|
+
|
3
|
+
[Constraint Satisfaction Problem](http://en.wikipedia.org/wiki/Constraint_satisfaction_problem) (CSP) implementation for Ruby.
|
4
|
+
It provides a useful way to solve problems like resource allocation or planning though a set of constraints.
|
5
|
+
|
6
|
+
The most common example of usage for CSPs is probably the game [Sudoku](http://en.wikipedia.org/wiki/Sudoku).
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
gem 'winston'
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install winston
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
The problem consists of three sets of information: Domain, Variables and Constraints. It will try to determine a value
|
25
|
+
from the given domain for each variable that will attend all the constraints.
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
require 'winston'
|
29
|
+
|
30
|
+
csp = Winston::CSP.new
|
31
|
+
|
32
|
+
csp.add_variable :a, domain: [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
33
|
+
csp.add_variable :b, domain: [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
34
|
+
csp.add_variable :c, domain: [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
35
|
+
|
36
|
+
csp.add_constraint(:a, :c) { |a, c| a == c * 2 } # 'a' has to be double of 'c'.
|
37
|
+
csp.add_constraint(:a, :b) { |a, b| a > b } # 'a' has to be greater than 'b'.
|
38
|
+
csp.add_constraint(:b, :c) { |b, c| b > c } # 'b' has to be greater than 'c'.
|
39
|
+
csp.add_constraint(:b) { |b| b % 2 == 0 } # 'b' has to be even.
|
40
|
+
|
41
|
+
csp.solve
|
42
|
+
= { a: 6, b: 4, c: 3 }
|
43
|
+
```
|
44
|
+
|
45
|
+
### Variables and Domain
|
46
|
+
|
47
|
+
It's possible to preset values for variables, and in that case the problem would not try to determine values for
|
48
|
+
it, but it will take those values into account for validating the constraints.
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
csp.add_variable "my_var", value: "predefined value"
|
52
|
+
```
|
53
|
+
|
54
|
+
And it's also possible to set the domain as `Proc` so it'd be evaluated on-demand.
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
csp.add_variable("other_var") { |var_name, csp| [:a, :b, :c ] }
|
58
|
+
# same as
|
59
|
+
csp.add_variable("other_var", domain: proc { |var_name, csp| [:a, :b, :c ] })
|
60
|
+
```
|
61
|
+
|
62
|
+
### Constraints
|
63
|
+
|
64
|
+
Constraints can be set for specific variables and would be evaluated only when all those variables are set and one
|
65
|
+
of them has changed; Or globals, in which case, they'd evaluated for every assignment.
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
csp.add_constraint(:a) { |a| a > 0 } # positive value
|
69
|
+
|
70
|
+
# the last argument passed to the block is always a map of assignments, in other words, the current
|
71
|
+
# state of the solution
|
72
|
+
|
73
|
+
csp.add_constraint(:a) do |a, assignments|
|
74
|
+
!assignments.reject { |name, value| name == :a }.values.include?(a) #checks if the value is not present on other variables
|
75
|
+
end
|
76
|
+
|
77
|
+
# a global constraint is evaluated for every assignment and the only argument it receives is a
|
78
|
+
# hash with all current assignments
|
79
|
+
|
80
|
+
csp.add_constraint do |assignments|
|
81
|
+
assignmets.values.uniq.size == assignments.keys.size # checks if every value is unique
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
Constraint can be also set as their own objects, that's great for reusability.
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
csp.add_constraint constraint: MyConstraint.new(...)
|
89
|
+
# ...
|
90
|
+
csp.add_constraint constraint: Winston::Constraints::AllDifferent.new # built-in constraint, checks if all values are different from each other
|
91
|
+
```
|
92
|
+
|
93
|
+
### Problems without solution
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
require 'winston'
|
97
|
+
|
98
|
+
csp = Winston::CSP.new
|
99
|
+
|
100
|
+
csp.add_variable :a, domain: [1, 2]
|
101
|
+
csp.add_variable :b, domain: [1, 2]
|
102
|
+
csp.add_variable :c, domain: [1, 2]
|
103
|
+
|
104
|
+
csp.add_constraint constraint: Winston::Constraints::AllDifferent.new
|
105
|
+
|
106
|
+
csp.solve
|
107
|
+
= false
|
108
|
+
```
|
109
|
+
|
110
|
+
**IMPORTANT NOTE: Depending on the number of variables and the size of the domain it can take a long time to test all different possibilities.
|
111
|
+
In that case it'll be recomendable to use of ways to reduce the number of iterations, for example, removing an item from a shared domain
|
112
|
+
when it is tested ( A `queue` type of structure, that would `pop` the value on the `each` block).**
|
113
|
+
|
114
|
+
### More examples
|
115
|
+
|
116
|
+
Check the folder `specs/examples` for more usage examples.
|
117
|
+
|
118
|
+
## TODOs / Nice-to-haves
|
119
|
+
|
120
|
+
- Create a DSL for setting up the problem
|
121
|
+
- Currently only algorithm to solve the CSP is Backtracking, implement other like Local search, Constraint propagation, ...
|
122
|
+
- Implement heuristics to improve search time (least constraining value, minimum remaining values,...)
|
123
|
+
|
124
|
+
## Contributing
|
125
|
+
|
126
|
+
1. Fork it
|
127
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
128
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
129
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
130
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/winston.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
module Winston
|
2
|
+
class Backtrack
|
3
|
+
def initialize(csp)
|
4
|
+
@csp = csp
|
5
|
+
end
|
6
|
+
|
7
|
+
def search(assignments = {})
|
8
|
+
return assignments if complete?(assignments)
|
9
|
+
var = select_unassigned_variable(assignments)
|
10
|
+
domain_values(var).each do |value|
|
11
|
+
assigned = assignments.merge(var.name => value)
|
12
|
+
if valid?(var.name, assigned)
|
13
|
+
result = search(assigned)
|
14
|
+
return result if result
|
15
|
+
end
|
16
|
+
end
|
17
|
+
false
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :csp
|
23
|
+
|
24
|
+
def complete?(assignments)
|
25
|
+
assignments.size == csp.variables.size
|
26
|
+
end
|
27
|
+
|
28
|
+
def valid?(changed, assignments)
|
29
|
+
csp.validate(changed, assignments)
|
30
|
+
end
|
31
|
+
|
32
|
+
def select_unassigned_variable(assignments)
|
33
|
+
csp.variables.reject { |k,v| assignments.include?(k) }.each_value.first
|
34
|
+
end
|
35
|
+
|
36
|
+
def domain_values(var)
|
37
|
+
csp.variables[var.name].domain.values
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Winston
|
2
|
+
class Constraint
|
3
|
+
|
4
|
+
def initialize(variables = nil, predicate = nil)
|
5
|
+
@variables = variables || []
|
6
|
+
@predicate = predicate
|
7
|
+
@global = @variables.empty?
|
8
|
+
end
|
9
|
+
|
10
|
+
def elegible_for?(changed_var, assignments)
|
11
|
+
@global || (@variables.include?(changed_var) && @variables.all? { |v| assignments.key? v })
|
12
|
+
end
|
13
|
+
|
14
|
+
def validate(assignments)
|
15
|
+
return false unless @predicate
|
16
|
+
@predicate.call(*assignments.values_at(*@variables), assignments)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/winston/csp.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module Winston
|
2
|
+
class CSP
|
3
|
+
|
4
|
+
attr_reader :variables, :constraints
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@variables = {}
|
8
|
+
@constraints = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def solve(solver = Backtrack.new(self))
|
12
|
+
solver.search(var_assignments)
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_variable(name, value: nil, domain: nil, &block)
|
16
|
+
domain = Domain.new(self, name, domain || block) unless value
|
17
|
+
variables[name] = Variable.new(name, value: value, domain: domain)
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_constraint(*variables, constraint: nil, &block)
|
21
|
+
constraint ||= Constraint.new(variables, block)
|
22
|
+
constraints << constraint
|
23
|
+
end
|
24
|
+
|
25
|
+
def validate(changed_var, assignments)
|
26
|
+
constraints.each do |constraint|
|
27
|
+
return false if constraint.elegible_for?(changed_var, assignments) && !constraint.validate(assignments)
|
28
|
+
end
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def var_assignments
|
35
|
+
@variables.reduce({}) do |assignments, (name, variable)|
|
36
|
+
assignments[name] = variable.value unless variable.value.nil?
|
37
|
+
assignments
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "winston"
|
2
|
+
|
3
|
+
describe "Numbers Example" do
|
4
|
+
let(:csp) { Winston::CSP.new }
|
5
|
+
|
6
|
+
describe "'a' has to be double of 'c' and higher than 'b', 'b' has to be even and higher than 'c'" do
|
7
|
+
before do
|
8
|
+
csp.add_variable :a, domain: [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
9
|
+
csp.add_variable :b, domain: [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
10
|
+
csp.add_variable :c, domain: [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
11
|
+
|
12
|
+
csp.add_constraint(:a, :c) { |a, c| a == c * 2 }
|
13
|
+
csp.add_constraint(:a, :b) { |a, b| a > b }
|
14
|
+
csp.add_constraint(:b, :c) { |b, c| b > c }
|
15
|
+
csp.add_constraint(:b) { |b| b % 2 == 0 }
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should return a valid solution" do
|
19
|
+
expect(csp.solve).to eq(a: 6, b: 4, c: 3)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "has to have different values" do
|
24
|
+
before do
|
25
|
+
csp.add_variable :a, domain: [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
26
|
+
csp.add_variable :b, domain: [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
27
|
+
csp.add_variable :c, domain: [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
28
|
+
csp.add_variable :d, domain: [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
29
|
+
csp.add_variable :e, domain: [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
30
|
+
|
31
|
+
csp.add_constraint constraint: Winston::Constraints::AllDifferent.new
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should return a valid solution" do
|
35
|
+
expect(csp.solve).to eq(a: 1, b: 2, c: 3, d: 4, e: 5)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require "winston"
|
2
|
+
|
3
|
+
describe Winston::Backtrack do
|
4
|
+
let(:csp) { Winston::CSP.new }
|
5
|
+
subject { described_class.new(csp) }
|
6
|
+
|
7
|
+
describe "#search" do
|
8
|
+
context "simple assigment" do
|
9
|
+
before do
|
10
|
+
csp.add_variable :a, domain: [1, 2, 3, 4]
|
11
|
+
csp.add_variable :b, domain: [1, 2, 3, 4]
|
12
|
+
csp.add_variable :c, domain: [1, 2, 3, 4]
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should assign variables to the first acceptable value" do
|
16
|
+
expect(subject.search).to eq(a: 1, b: 1, c: 1)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should assign variables to the first acceptable value respecting variables that already have a value" do
|
20
|
+
expect(subject.search(b: 3)).to eq(a: 1, b: 3, c: 1)
|
21
|
+
end
|
22
|
+
|
23
|
+
context "with constraints" do
|
24
|
+
before do
|
25
|
+
csp.add_constraint(:a, :b) { |a,b| a > b }
|
26
|
+
csp.add_constraint(:b, :c) { |b,c| b > c }
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should assign variables to the first acceptable value" do
|
30
|
+
expect(subject.search).to eq(a: 3, b: 2, c: 1)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should assign variables to the first acceptable value" do
|
34
|
+
expect(subject.search(b: 3)).to eq(a: 4, b: 3, c: 1)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should return false when it cannot fufill the constraints" do
|
38
|
+
expect(subject.search(c: 3)).to be(false)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should return false when it cannot fufill the constraints" do
|
42
|
+
csp.add_constraint(:a, :c) { |a,c| a == c * 10 }
|
43
|
+
expect(subject.search).to be(false)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require "winston"
|
2
|
+
|
3
|
+
describe Winston::Constraint do
|
4
|
+
|
5
|
+
let(:variables) { nil }
|
6
|
+
let(:predicate) { nil }
|
7
|
+
subject { described_class.new(variables, predicate) }
|
8
|
+
|
9
|
+
describe "#elegible_for?" do
|
10
|
+
context "global" do
|
11
|
+
it "should be elegible for everything" do
|
12
|
+
expect(subject.elegible_for?(:a, {})).to be(true)
|
13
|
+
expect(subject.elegible_for?(:b, { a: 1 })).to be(true)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "for specific variable" do
|
18
|
+
let(:variables) { [:a] }
|
19
|
+
|
20
|
+
it "should return true when that variable is changed" do
|
21
|
+
expect(subject.elegible_for?(:a, { a: nil })).to be(true)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should return false when that variable isn't the one changed" do
|
25
|
+
expect(subject.elegible_for?(:b, { a: 1, b: 2 })).to be(false)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should return false when that variable is changed but doesn't have a value" do
|
29
|
+
expect(subject.elegible_for?(:a, { b: 2 })).to be(false)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "for multiple variables" do
|
34
|
+
let(:variables) { [:a, :b] }
|
35
|
+
|
36
|
+
it "should return true when one of those variables is changed" do
|
37
|
+
expect(subject.elegible_for?(:a, { a: 1, b: nil })).to be(true)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should return false when one of those variables isn't the one changed" do
|
41
|
+
expect(subject.elegible_for?(:c, { a: 1, b: 2 })).to be(false)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should return false when one of those variables is changed but doesn't have a value for every one of them" do
|
45
|
+
expect(subject.elegible_for?(:b, { b: 2 })).to be(false)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should return false when one of those variables is changed but doesn't have a value for every one of them" do
|
49
|
+
expect(subject.elegible_for?(:a, { b: 2 })).to be(false)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "#validate" do
|
55
|
+
context "no predicate is given" do
|
56
|
+
it "should be considerated invalid" do
|
57
|
+
expect(subject.validate(a: 1)).to be(false)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "when a predicate is given" do
|
62
|
+
context "on a global constraint" do
|
63
|
+
let(:predicate) do
|
64
|
+
proc { |assignments| assignments[:a] > assignments[:b] }
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should evaluate that predicate" do
|
68
|
+
expect(subject.validate(a: 2, b: 1)).to be(true)
|
69
|
+
expect(subject.validate(a: 1, b: 2)).to be(false)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "on an unary constraint" do
|
74
|
+
let(:variables) { [:a] }
|
75
|
+
let(:predicate) do
|
76
|
+
proc { |a, assignments| a % 2 == 0 && !assignments.reject { |k,_| k == :a }.values.include?(a) }
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should evaluate that predicate" do
|
80
|
+
expect(subject.validate(a: 2, b: 1)).to be(true)
|
81
|
+
expect(subject.validate(a: 1, b: 2)).to be(false)
|
82
|
+
expect(subject.validate(a: 2, b: 2)).to be(false)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context "on a n-ary constraint" do
|
87
|
+
let(:variables) { [:a, :b, :c] }
|
88
|
+
let(:predicate) do
|
89
|
+
proc { |a, b, c, assignments| a > b && b > c }
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should evaluate that predicate" do
|
93
|
+
expect(subject.validate(a: 2, b: 1, c: 0)).to be(true)
|
94
|
+
expect(subject.validate(a: 1, b: 2, c: 1)).to be(false)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "winston"
|
2
|
+
|
3
|
+
describe Winston::Constraints::AllDifferent do
|
4
|
+
subject { described_class.new }
|
5
|
+
|
6
|
+
describe "#elegible_for?" do
|
7
|
+
it "should be elegible for everything" do
|
8
|
+
expect(subject.elegible_for?(:a, {})).to be(true)
|
9
|
+
expect(subject.elegible_for?(:b, { a: 1 })).to be(true)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#validate" do
|
14
|
+
it "should return 'true' when all values are unique" do
|
15
|
+
expect(subject.validate(a: 1, b: 2, c: 3, d: 4)).to be(true)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should return 'false' when not all values are unique" do
|
19
|
+
expect(subject.validate(a: 1, b: 2, c: 2, d: 4)).to be(false)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require "winston"
|
2
|
+
|
3
|
+
describe Winston::CSP do
|
4
|
+
subject { described_class.new }
|
5
|
+
|
6
|
+
describe "#add_variable" do
|
7
|
+
let(:domain) { double("Domain") }
|
8
|
+
|
9
|
+
it "should add variable definitions to the problem" do
|
10
|
+
expect(Winston::Domain).to_not receive(:new)
|
11
|
+
subject.add_variable :a, value: 1
|
12
|
+
expect(subject.variables[:a]).to have_attributes(value: 1, domain: nil)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should add without value and a static domain when it is given" do
|
16
|
+
expect(Winston::Domain).to receive(:new).with(subject, :b, [2, 3]).once.and_return(domain)
|
17
|
+
subject.add_variable :b, domain: [2, 3]
|
18
|
+
expect(subject.variables[:b]).to have_attributes(value: nil, domain: domain)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should add without value and a dynamic domain when it is given" do
|
22
|
+
expect(Winston::Domain).to receive(:new).with(subject, :c, an_instance_of(Proc)).once
|
23
|
+
subject.add_variable(:c) { |csp| [2, 3, 4] }
|
24
|
+
expect(subject.variables[:c]).to have_attributes(value: nil)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#add_constraint" do
|
29
|
+
let(:constraint) { double("Constraint") }
|
30
|
+
it "should build a constraint for the given block" do
|
31
|
+
expect(Winston::Constraint).to receive(:new).with([:a, :b], an_instance_of(Proc)).once.and_return(constraint)
|
32
|
+
subject.add_constraint(:a, :b) { true }
|
33
|
+
expect(subject.constraints).to include(constraint)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should add the already build constraint when given" do
|
37
|
+
expect(Winston::Constraint).to_not receive(:new)
|
38
|
+
subject.add_constraint(constraint: constraint)
|
39
|
+
expect(subject.constraints).to include(constraint)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#validate" do
|
44
|
+
it "should return true when there aren't any constraints" do
|
45
|
+
expect(subject.validate(:var, {})).to be(true)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should return false if one of the constraints returns invalid" do
|
49
|
+
subject.add_constraint(:a) { true }
|
50
|
+
subject.add_constraint(:a) { false }
|
51
|
+
|
52
|
+
expect(subject.validate(:a, {a: 1})).to be(false)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should not attempt to validate ineligible constraints" do
|
56
|
+
subject.add_constraint(:a) { true }
|
57
|
+
subject.add_constraint(:b) { false }
|
58
|
+
|
59
|
+
expect(subject.validate(:a, { a: 1 , b: 2 })).to be(true)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#solve" do
|
64
|
+
let(:solver) { double("Solver") }
|
65
|
+
before do
|
66
|
+
subject.add_variable :a, value: 1
|
67
|
+
subject.add_variable :b, domain: [1, 2]
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should pass a collection of preset variables to the solver" do
|
71
|
+
expect(solver).to receive(:search).with(a: 1)
|
72
|
+
subject.solve(solver)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "winston"
|
2
|
+
|
3
|
+
describe Winston::Domain do
|
4
|
+
let(:csp) { double("CSP") }
|
5
|
+
let(:variable) { :var }
|
6
|
+
|
7
|
+
subject { described_class.new(csp, variable, values) }
|
8
|
+
|
9
|
+
describe "#values" do
|
10
|
+
context "when given 'values' is a collection" do
|
11
|
+
let(:values) { [1, 2, 3] }
|
12
|
+
|
13
|
+
it "should return that collection" do
|
14
|
+
expect(subject.values).to eq([1, 2, 3])
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context "when given 'values' is a proc" do
|
19
|
+
let(:values) do
|
20
|
+
i = 0
|
21
|
+
proc do |given_variable, given_csp|
|
22
|
+
expect(given_variable).to eq(variable)
|
23
|
+
expect(given_csp).to eq(csp)
|
24
|
+
|
25
|
+
[i += 1]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should return the result of the evatualtion of that proc" do
|
30
|
+
expect(subject.values).to eq([1])
|
31
|
+
expect(subject.values).to eq([2])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "winston"
|
2
|
+
|
3
|
+
describe Winston::Variable do
|
4
|
+
|
5
|
+
subject { described_class.new("my_var", value: 1, domain: [1, 2, 3]) }
|
6
|
+
|
7
|
+
it { is_expected.to have_attributes(name: "my_var", value: 1, domain: [1, 2, 3]) }
|
8
|
+
|
9
|
+
describe "#value=" do
|
10
|
+
before { subject.value = "new value" }
|
11
|
+
|
12
|
+
it { is_expected.to have_attributes(value: "new value") }
|
13
|
+
end
|
14
|
+
end
|
data/winston.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Gem::Specification.new do |gem|
|
2
|
+
gem.name = 'winston'
|
3
|
+
gem.version = '0.0.1'
|
4
|
+
gem.authors = ['David Michael Nelson']
|
5
|
+
gem.homepage = 'http://github.com/dmnelson/winston'
|
6
|
+
|
7
|
+
gem.summary = 'Constraint Satisfaction Problem (CSP) implementation for Ruby'
|
8
|
+
gem.description = gem.summary
|
9
|
+
gem.license = 'MIT'
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($/)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.require_paths = ["lib"]
|
15
|
+
|
16
|
+
gem.add_development_dependency "bundler", "~> 1.3"
|
17
|
+
gem.add_development_dependency "rake"
|
18
|
+
gem.add_development_dependency 'rspec'
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: winston
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David Michael Nelson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-02-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Constraint Satisfaction Problem (CSP) implementation for Ruby
|
56
|
+
email:
|
57
|
+
executables: []
|
58
|
+
extensions: []
|
59
|
+
extra_rdoc_files: []
|
60
|
+
files:
|
61
|
+
- ".gitignore"
|
62
|
+
- Gemfile
|
63
|
+
- LICENSE
|
64
|
+
- README.md
|
65
|
+
- Rakefile
|
66
|
+
- lib/winston.rb
|
67
|
+
- lib/winston/backtrack.rb
|
68
|
+
- lib/winston/constraint.rb
|
69
|
+
- lib/winston/constraints/all_different.rb
|
70
|
+
- lib/winston/csp.rb
|
71
|
+
- lib/winston/domain.rb
|
72
|
+
- lib/winston/variable.rb
|
73
|
+
- spec/examples/numbers_spec.rb
|
74
|
+
- spec/winston/backtrack_spec.rb
|
75
|
+
- spec/winston/constraint_spec.rb
|
76
|
+
- spec/winston/constraints/all_different_spec.rb
|
77
|
+
- spec/winston/csp_spec.rb
|
78
|
+
- spec/winston/domain_spec.rb
|
79
|
+
- spec/winston/variable_spec.rb
|
80
|
+
- winston.gemspec
|
81
|
+
homepage: http://github.com/dmnelson/winston
|
82
|
+
licenses:
|
83
|
+
- MIT
|
84
|
+
metadata: {}
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options: []
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
requirements: []
|
100
|
+
rubyforge_project:
|
101
|
+
rubygems_version: 2.2.2
|
102
|
+
signing_key:
|
103
|
+
specification_version: 4
|
104
|
+
summary: Constraint Satisfaction Problem (CSP) implementation for Ruby
|
105
|
+
test_files:
|
106
|
+
- spec/examples/numbers_spec.rb
|
107
|
+
- spec/winston/backtrack_spec.rb
|
108
|
+
- spec/winston/constraint_spec.rb
|
109
|
+
- spec/winston/constraints/all_different_spec.rb
|
110
|
+
- spec/winston/csp_spec.rb
|
111
|
+
- spec/winston/domain_spec.rb
|
112
|
+
- spec/winston/variable_spec.rb
|