gecoder 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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). A range for
71
- # the allowed cardinality of the set can also be specified, if none is
72
- # specified, or nil is given, then the default range (anything) will be
73
- # used. If only a single Fixnum is specified as cardinality_range then it's
74
- # used as lower bound.
75
- def set_var(glb_domain, lub_domain, cardinality_range = nil)
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
@@ -1,4 +1,4 @@
1
1
  module GecodeR
2
2
  # A string representation of the Gecode/R version.
3
- VERSION = '0.6.1'
3
+ VERSION = '0.7.0'
4
4
  end
@@ -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 and right hand side as arguments and
52
- # sets up the corresponding expectations. It also requires @invoke_relation and
53
- # @invoke_negated_relation with the same arguments. The spec is also required to
54
- # provide an int var @target.
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