gecoder 0.6.1 → 0.7.0
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/CHANGES +7 -0
- data/README +7 -7
- data/lib/gecoder/interface/constraints.rb +45 -3
- data/lib/gecoder/interface/constraints/int/domain.rb +1 -2
- data/lib/gecoder/interface/constraints/int_enum/element.rb +4 -2
- data/lib/gecoder/interface/constraints/set/operation.rb +101 -0
- data/lib/gecoder/interface/constraints/set_enum/operation.rb +36 -0
- data/lib/gecoder/interface/constraints/set_enum/selection.rb +143 -0
- data/lib/gecoder/interface/constraints/set_enum_constraints.rb +2 -0
- data/lib/gecoder/interface/constraints/set_var_constraints.rb +30 -0
- data/lib/gecoder/interface/enum_wrapper.rb +16 -1
- data/lib/gecoder/interface/model.rb +10 -6
- data/lib/gecoder/interface/search.rb +0 -6
- data/lib/gecoder/version.rb +1 -1
- data/specs/constraints/constraint_helper.rb +45 -4
- data/specs/constraints/constraints.rb +6 -0
- data/specs/constraints/selection.rb +292 -0
- data/specs/constraints/set_operation.rb +285 -0
- data/specs/enum_wrapper.rb +5 -0
- data/specs/model.rb +7 -0
- data/vendor/rust/configure.rb +6 -0
- data/vendor/rust/out.rb +627 -0
- metadata +14 -2
@@ -76,9 +76,10 @@ module Gecode
|
|
76
76
|
# variables involved.
|
77
77
|
def domain_range
|
78
78
|
inject(nil) do |range, var|
|
79
|
-
next var.min..var.max if range.nil?
|
80
79
|
min = var.min
|
81
80
|
max = var.max
|
81
|
+
next min..max if range.nil?
|
82
|
+
|
82
83
|
range = min..range.last if min < range.first
|
83
84
|
range = range.first..max if max > range.last
|
84
85
|
range
|
@@ -122,6 +123,20 @@ module Gecode
|
|
122
123
|
return @bound_arr
|
123
124
|
end
|
124
125
|
alias_method :to_var_array, :to_set_var_array
|
126
|
+
|
127
|
+
# Returns the range of the union of the contained sets' upper bounds.
|
128
|
+
def upper_bound_range
|
129
|
+
inject(nil) do |range, var|
|
130
|
+
upper_bound = var.upper_bound
|
131
|
+
min = upper_bound.min
|
132
|
+
max = upper_bound.max
|
133
|
+
next min..max if range.nil?
|
134
|
+
|
135
|
+
range = min..range.last if min < range.first
|
136
|
+
range = range.first..max if max > range.last
|
137
|
+
range
|
138
|
+
end
|
139
|
+
end
|
125
140
|
end
|
126
141
|
|
127
142
|
# A module containing the methods needed by enumerations containing fixnums.
|
@@ -67,12 +67,15 @@ module Gecode
|
|
67
67
|
end
|
68
68
|
|
69
69
|
# Creates a set variable with the specified domain for greatest lower bound
|
70
|
-
# and least upper bound (specified as either a range or enum).
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
# used
|
75
|
-
|
70
|
+
# and least upper bound (specified as either a range or enum). If no bounds
|
71
|
+
# are specified then the empty set is used as greates lower bound and the
|
72
|
+
# universe as least upper bound. A range for the allowed cardinality of the
|
73
|
+
# set can also be specified, if none is specified, or nil is given, then the
|
74
|
+
# default range (anything) will be used. If only a single Fixnum is
|
75
|
+
# specified as cardinality_range then it's used as lower bound.
|
76
|
+
def set_var(glb_domain = [], lub_domain =
|
77
|
+
Gecode::Raw::Limits::Set::INT_MIN..Gecode::Raw::Limits::Set::INT_MAX,
|
78
|
+
cardinality_range = nil)
|
76
79
|
check_set_bounds(glb_domain, lub_domain)
|
77
80
|
|
78
81
|
index = variable_creation_space.new_set_vars(glb_domain, lub_domain,
|
@@ -213,6 +216,7 @@ module Gecode
|
|
213
216
|
# Returns whether the greatest lower bound is a subset of least upper
|
214
217
|
# bound.
|
215
218
|
def valid_set_bounds?(glb, lub)
|
219
|
+
return true if glb.respond_to?(:empty?) and glb.empty?
|
216
220
|
if glb.kind_of?(Range) and lub.kind_of?(Range)
|
217
221
|
glb.first >= lub.first and glb.last <= lub.last
|
218
222
|
else
|
@@ -4,9 +4,7 @@ module Gecode
|
|
4
4
|
# to that solution. Returns the model if a solution was found, nil
|
5
5
|
# otherwise.
|
6
6
|
def solve!
|
7
|
-
GC.disable
|
8
7
|
space = dfs_engine.next
|
9
|
-
GC.enable
|
10
8
|
return nil if space.nil?
|
11
9
|
@active_space = space
|
12
10
|
return self
|
@@ -32,11 +30,9 @@ module Gecode
|
|
32
30
|
# Yields each solution that the model has.
|
33
31
|
def each_solution(&block)
|
34
32
|
dfs = dfs_engine
|
35
|
-
GC.disable
|
36
33
|
while not (@active_space = dfs.next).nil?
|
37
34
|
yield self
|
38
35
|
end
|
39
|
-
GC.enable
|
40
36
|
self.reset!
|
41
37
|
end
|
42
38
|
|
@@ -68,12 +64,10 @@ module Gecode
|
|
68
64
|
end
|
69
65
|
|
70
66
|
# Perform the search.
|
71
|
-
GC.disable
|
72
67
|
result = Gecode::Raw::bab(selected_space,
|
73
68
|
Gecode::Raw::Search::Config::MINIMAL_DISTANCE,
|
74
69
|
Gecode::Raw::Search::Config::ADAPTIVE_DISTANCE,
|
75
70
|
nil)
|
76
|
-
GC.enable
|
77
71
|
|
78
72
|
# Reset the method used constrain calls and return the result.
|
79
73
|
Model.constrain_proc = nil
|
data/lib/gecoder/version.rb
CHANGED
@@ -48,10 +48,10 @@ describe 'constraint with options', :shared => true do
|
|
48
48
|
end
|
49
49
|
|
50
50
|
# This requires that the constraint spec has the instance variable
|
51
|
-
# @expect_relation which takes a relation
|
52
|
-
# sets up the corresponding expectations. It also
|
53
|
-
# @
|
54
|
-
# provide
|
51
|
+
# @expect_relation which takes a relation, right hand side and whether it's
|
52
|
+
# negated as arguments and sets up the corresponding expectations. It also
|
53
|
+
# requires @invoke_relation with the same arguments. The spec is also required
|
54
|
+
# to provide a variable @target.
|
55
55
|
describe 'composite constraint', :shared => true do
|
56
56
|
Gecode::Constraints::Util::RELATION_TYPES.each_pair do |relation, type|
|
57
57
|
it "should translate #{relation} with constant target" do
|
@@ -88,6 +88,47 @@ describe 'composite constraint', :shared => true do
|
|
88
88
|
end
|
89
89
|
end
|
90
90
|
|
91
|
+
# This requires that the constraint spec has the instance variable
|
92
|
+
# @expect_relation which takes a relation, right hand side and whether it's
|
93
|
+
# negated as arguments and sets up the corresponding expectations. It also
|
94
|
+
# requires @invoke_relation with the same arguments. The spec is also required
|
95
|
+
# to provide a variable @target.
|
96
|
+
describe 'composite set constraint', :shared => true do
|
97
|
+
Gecode::Constraints::Util::SET_RELATION_TYPES.each_pair do |relation, type|
|
98
|
+
it "should translate #{relation} with constant target" do
|
99
|
+
@expect_relation.call(type, [1], false)
|
100
|
+
@invoke_relation.call(relation, [1], false)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
Gecode::Constraints::Util::SET_RELATION_TYPES.each_pair do |relation, type|
|
105
|
+
it "should translate #{relation} with variable target" do
|
106
|
+
@expect_relation.call(type, @target, false)
|
107
|
+
@invoke_relation.call(relation, @target, false)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
Gecode::Constraints::Util::NEGATED_SET_RELATION_TYPES.each_pair do |relation, type|
|
112
|
+
it "should translate negated #{relation} with constant target" do
|
113
|
+
@expect_relation.call(type, [1], true)
|
114
|
+
@invoke_relation.call(relation, [1], true)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
Gecode::Constraints::Util::NEGATED_SET_RELATION_TYPES.each_pair do |relation, type|
|
119
|
+
it "should translate negated #{relation} with variable target" do
|
120
|
+
@expect_relation.call(type, @target, true)
|
121
|
+
@invoke_relation.call(relation, @target, true)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'should raise error if the target is of the wrong type' do
|
126
|
+
lambda do
|
127
|
+
@invoke_relation.call(:==, 'hello', false)
|
128
|
+
end.should raise_error(TypeError)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
91
132
|
# Requires @invoke_options and @model.
|
92
133
|
describe 'non-reifiable set constraint', :shared => true do
|
93
134
|
it 'should not accept strength option' do
|
@@ -54,6 +54,12 @@ describe Gecode::Constraints::Util do
|
|
54
54
|
Gecode::Constraints::Util.constant_set_to_params('hello')
|
55
55
|
end.should raise_error(TypeError)
|
56
56
|
end
|
57
|
+
|
58
|
+
it 'should raise error when giving incorrect set to #constant_set_to_int_set' do
|
59
|
+
lambda do
|
60
|
+
Gecode::Constraints::Util.constant_set_to_int_set('hello')
|
61
|
+
end.should raise_error(TypeError)
|
62
|
+
end
|
57
63
|
end
|
58
64
|
|
59
65
|
describe Gecode::Constraints::CompositeExpression do
|
@@ -0,0 +1,292 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
require File.dirname(__FILE__) + '/constraint_helper'
|
3
|
+
|
4
|
+
class SelectionSampleProblem < Gecode::Model
|
5
|
+
attr :sets
|
6
|
+
attr :set
|
7
|
+
attr :target
|
8
|
+
attr :index
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@sets = set_var_array(3, [], 0..20)
|
12
|
+
@set = set_var([], 0...3)
|
13
|
+
@target = set_var([], 0..20)
|
14
|
+
@index = int_var(0...3)
|
15
|
+
branch_on wrap_enum([@index])
|
16
|
+
branch_on @sets
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Requires everything that composite behaviour spec requires in addition to
|
21
|
+
# @stub and @expect_constrain_equal .
|
22
|
+
describe 'selection constraint', :shared => true do
|
23
|
+
before do
|
24
|
+
@expect = lambda do |index, relation, target, reif_var, negated|
|
25
|
+
@model.allow_space_access do
|
26
|
+
if target.respond_to? :bind
|
27
|
+
expected_target = [an_instance_of(Gecode::Raw::SetVar)]
|
28
|
+
relation_constraint = :rel
|
29
|
+
else
|
30
|
+
expected_target = expect_constant_set(target)
|
31
|
+
relation_constraint = :dom
|
32
|
+
end
|
33
|
+
if reif_var.nil?
|
34
|
+
if !negated and relation == Gecode::Raw::IRT_EQ and
|
35
|
+
!target.kind_of? Enumerable
|
36
|
+
@expect_constrain_equal.call
|
37
|
+
Gecode::Raw.should_receive(:rel).exactly(0).times
|
38
|
+
Gecode::Raw.should_receive(:dom).exactly(0).times
|
39
|
+
else
|
40
|
+
@expect_constrain_equal.call
|
41
|
+
if relation_constraint == :dom
|
42
|
+
# We can't seem to get any more specific than this with mocks.
|
43
|
+
Gecode::Raw.should_receive(relation_constraint).at_most(:twice)
|
44
|
+
else
|
45
|
+
Gecode::Raw.should_receive(relation_constraint).once.with(
|
46
|
+
an_instance_of(Gecode::Raw::Space),
|
47
|
+
an_instance_of(Gecode::Raw::SetVar), relation, *expected_target)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
else
|
51
|
+
@expect_constrain_equal.call
|
52
|
+
if relation_constraint == :dom
|
53
|
+
Gecode::Raw.should_receive(relation_constraint).at_least(:twice)
|
54
|
+
else
|
55
|
+
expected_target << an_instance_of(Gecode::Raw::BoolVar)
|
56
|
+
Gecode::Raw.should_receive(relation_constraint).once.with(
|
57
|
+
an_instance_of(Gecode::Raw::Space),
|
58
|
+
an_instance_of(Gecode::Raw::SetVar), relation, *expected_target)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# For composite spec.
|
65
|
+
@invoke_relation = lambda do |relation, target, negated|
|
66
|
+
if negated
|
67
|
+
@stub.must_not.send(relation, target)
|
68
|
+
else
|
69
|
+
@stub.must.send(relation, target)
|
70
|
+
end
|
71
|
+
@model.solve!
|
72
|
+
end
|
73
|
+
@expect_relation = lambda do |relation, target, negated|
|
74
|
+
@expect.call(@index, relation, target, nil, negated)
|
75
|
+
end
|
76
|
+
|
77
|
+
# For options spec.
|
78
|
+
@invoke_options = lambda do |hash|
|
79
|
+
@stub.must_be.subset_of(@target, hash)
|
80
|
+
@model.solve!
|
81
|
+
end
|
82
|
+
@expect_options = lambda do |strength, reif_var|
|
83
|
+
@expect.call(17, Gecode::Raw::SRT_SUB, @target, reif_var, false)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should not disturb normal array access' do
|
88
|
+
@sets[0].should be_kind_of(Gecode::FreeSetVar)
|
89
|
+
end
|
90
|
+
|
91
|
+
it_should_behave_like 'reifiable set constraint'
|
92
|
+
it_should_behave_like 'composite set constraint'
|
93
|
+
end
|
94
|
+
|
95
|
+
describe Gecode::Constraints::SetEnum::Selection, ' (select)' do
|
96
|
+
include GecodeR::Specs::SetHelper
|
97
|
+
|
98
|
+
before do
|
99
|
+
@model = SelectionSampleProblem.new
|
100
|
+
@sets = @model.sets
|
101
|
+
@target = @set = @model.target
|
102
|
+
@index = @model.index
|
103
|
+
@model.branch_on @model.wrap_enum([@set])
|
104
|
+
@stub = @sets[@index]
|
105
|
+
|
106
|
+
@expect_constrain_equal = lambda do
|
107
|
+
Gecode::Raw.should_receive(:selectSet).once.with(
|
108
|
+
an_instance_of(Gecode::Raw::Space),
|
109
|
+
an_instance_of(Gecode::Raw::SetVarArray),
|
110
|
+
an_instance_of(Gecode::Raw::IntVar),
|
111
|
+
an_instance_of(Gecode::Raw::SetVar))
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'should constrain the specified element of an enum of sets' do
|
116
|
+
@sets[@index].must_be.superset_of([5,7,9])
|
117
|
+
@model.solve!
|
118
|
+
@sets[@index.value].value.should include(5,7,9)
|
119
|
+
end
|
120
|
+
|
121
|
+
it_should_behave_like 'selection constraint'
|
122
|
+
end
|
123
|
+
|
124
|
+
describe Gecode::Constraints::SetEnum::Selection, ' (union)' do
|
125
|
+
include GecodeR::Specs::SetHelper
|
126
|
+
|
127
|
+
before do
|
128
|
+
@model = SelectionSampleProblem.new
|
129
|
+
@sets = @model.sets
|
130
|
+
@set = @model.set
|
131
|
+
@target = @model.target
|
132
|
+
@model.branch_on @model.wrap_enum([@target, @set])
|
133
|
+
@stub = @sets[@set].union
|
134
|
+
|
135
|
+
@expect_constrain_equal = lambda do
|
136
|
+
Gecode::Raw.should_receive(:selectUnion).once.with(
|
137
|
+
an_instance_of(Gecode::Raw::Space),
|
138
|
+
an_instance_of(Gecode::Raw::SetVarArray),
|
139
|
+
an_instance_of(Gecode::Raw::SetVar),
|
140
|
+
an_instance_of(Gecode::Raw::SetVar))
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'should constrain the selected union of an enum of sets' do
|
145
|
+
@sets[@set].union.must_be.subset_of([5,7,9])
|
146
|
+
@sets[@set].union.must_be.superset_of([5])
|
147
|
+
@model.solve!
|
148
|
+
union = @set.value.inject([]) do |union, i|
|
149
|
+
union += @sets[i].value.to_a
|
150
|
+
end.uniq
|
151
|
+
union.should include(5)
|
152
|
+
(union - [5,7,9]).should be_empty
|
153
|
+
end
|
154
|
+
|
155
|
+
it_should_behave_like 'selection constraint'
|
156
|
+
end
|
157
|
+
|
158
|
+
describe Gecode::Constraints::SetEnum::Selection, ' (intersection)' do
|
159
|
+
include GecodeR::Specs::SetHelper
|
160
|
+
|
161
|
+
before do
|
162
|
+
@model = SelectionSampleProblem.new
|
163
|
+
@sets = @model.sets
|
164
|
+
@set = @model.set
|
165
|
+
@target = @model.target
|
166
|
+
@model.branch_on @model.wrap_enum([@target, @set])
|
167
|
+
@stub = @sets[@set].intersection
|
168
|
+
|
169
|
+
@expect_constrain_equal = lambda do
|
170
|
+
Gecode::Raw.should_receive(:selectInter).once.with(
|
171
|
+
an_instance_of(Gecode::Raw::Space),
|
172
|
+
an_instance_of(Gecode::Raw::SetVarArray),
|
173
|
+
an_instance_of(Gecode::Raw::SetVar),
|
174
|
+
an_instance_of(Gecode::Raw::SetVar))
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'should constrain the selected intersection of an enum of sets' do
|
179
|
+
@sets[@set].intersection.must_be.subset_of([5,7,9])
|
180
|
+
@sets[@set].intersection.must_be.superset_of([5])
|
181
|
+
@model.solve!
|
182
|
+
intersection = @set.value.inject(nil) do |intersection, i|
|
183
|
+
elements = @sets[i].value.to_a
|
184
|
+
next elements if intersection.nil?
|
185
|
+
intersection &= elements
|
186
|
+
end.uniq
|
187
|
+
intersection.should include(5)
|
188
|
+
(intersection - [5,7,9]).should be_empty
|
189
|
+
end
|
190
|
+
|
191
|
+
it_should_behave_like 'selection constraint'
|
192
|
+
end
|
193
|
+
|
194
|
+
describe Gecode::Constraints::SetEnum::Selection, ' (intersection with universe)' do
|
195
|
+
include GecodeR::Specs::SetHelper
|
196
|
+
|
197
|
+
before do
|
198
|
+
@model = SelectionSampleProblem.new
|
199
|
+
@sets = @model.sets
|
200
|
+
@set = @model.set
|
201
|
+
@target = @model.target
|
202
|
+
@model.branch_on @model.wrap_enum([@target, @set])
|
203
|
+
@universe = [1,2]
|
204
|
+
@stub = @sets[@set].intersection(:with => @universe)
|
205
|
+
|
206
|
+
@expect_constrain_equal = lambda do
|
207
|
+
Gecode::Raw.should_receive(:selectInterIn).once.with(
|
208
|
+
an_instance_of(Gecode::Raw::Space),
|
209
|
+
an_instance_of(Gecode::Raw::SetVarArray),
|
210
|
+
an_instance_of(Gecode::Raw::SetVar),
|
211
|
+
an_instance_of(Gecode::Raw::SetVar),
|
212
|
+
an_instance_of(Gecode::Raw::IntSet))
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'should constrain the selected intersection of an enum of sets in a universe' do
|
217
|
+
@sets[@set].intersection(:with => @universe).must_be.subset_of([2])
|
218
|
+
@model.solve!
|
219
|
+
intersection = @set.value.inject(@universe) do |intersection, i|
|
220
|
+
intersection &= @sets[i].value.to_a
|
221
|
+
end.uniq
|
222
|
+
intersection.should include(2)
|
223
|
+
(intersection - [1,2]).should be_empty
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'should allow the universe to be specified as a range' do
|
227
|
+
@sets[@set].intersection(:with => 1..2).must_be.subset_of([2])
|
228
|
+
@model.solve!
|
229
|
+
intersection = @set.value.inject(@universe) do |intersection, i|
|
230
|
+
intersection &= @sets[i].value.to_a
|
231
|
+
end.uniq
|
232
|
+
intersection.should include(2)
|
233
|
+
(intersection - [1,2]).should be_empty
|
234
|
+
end
|
235
|
+
|
236
|
+
it 'should raise error if unknown options are specified' do
|
237
|
+
lambda do
|
238
|
+
@sets[@set].intersection(:does_not_exist => nil).must_be.subset_of([2])
|
239
|
+
end.should raise_error(ArgumentError)
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'should raise error if the universe is of the wrong type' do
|
243
|
+
lambda do
|
244
|
+
@sets[@set].intersection(:with => 'foo').must_be.subset_of([2])
|
245
|
+
end.should raise_error(TypeError)
|
246
|
+
end
|
247
|
+
|
248
|
+
it_should_behave_like 'selection constraint'
|
249
|
+
end
|
250
|
+
|
251
|
+
describe Gecode::Constraints::SetEnum::Selection, ' (disjoint)' do
|
252
|
+
include GecodeR::Specs::SetHelper
|
253
|
+
|
254
|
+
before do
|
255
|
+
@model = SelectionSampleProblem.new
|
256
|
+
@sets = @model.sets
|
257
|
+
@set = @model.set
|
258
|
+
@target = @model.target
|
259
|
+
@model.branch_on @model.wrap_enum([@target, @set])
|
260
|
+
|
261
|
+
@expect = lambda do |index|
|
262
|
+
Gecode::Raw.should_receive(:selectDisjoint)
|
263
|
+
end
|
264
|
+
|
265
|
+
# For options spec.
|
266
|
+
@invoke_options = lambda do |hash|
|
267
|
+
@sets[@set].must_be.disjoint(hash)
|
268
|
+
@model.solve!
|
269
|
+
end
|
270
|
+
@expect_options = lambda do |strength, reif_var|
|
271
|
+
@expect.call(@set)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
it 'should constrain the selected sets to be disjoint' do
|
276
|
+
@sets[0].must_be.superset_of([7,8])
|
277
|
+
@sets[1].must_be.superset_of([5,7,9])
|
278
|
+
@sets[2].must_be.superset_of([6,8,10])
|
279
|
+
@sets[@set].must_be.disjoint
|
280
|
+
@set.size.must > 1
|
281
|
+
@model.solve!.should_not be_nil
|
282
|
+
|
283
|
+
@set.value.to_a.sort.should == [1,2]
|
284
|
+
end
|
285
|
+
|
286
|
+
it 'should not allow negation' do
|
287
|
+
lambda{ @sets[@set].must_not_be.disjoint }.should raise_error(
|
288
|
+
Gecode::MissingConstraintError)
|
289
|
+
end
|
290
|
+
|
291
|
+
it_should_behave_like 'non-reifiable set constraint'
|
292
|
+
end
|