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.
@@ -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