gecoder-with-gecode 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -56,3 +56,4 @@ end
56
56
  require 'gecoder/interface/constraints/int/linear'
57
57
  require 'gecoder/interface/constraints/int/domain'
58
58
  require 'gecoder/interface/constraints/int/arithmetic'
59
+ require 'gecoder/interface/constraints/int/channel'
@@ -6,6 +6,9 @@ module Gecode
6
6
  unless enum.kind_of? Enumerable
7
7
  raise TypeError, 'Only enumerables can be wrapped.'
8
8
  end
9
+ if enum.kind_of? Gecode::EnumMethods
10
+ raise ArgumentError, 'The enumration is already wrapped.'
11
+ end
9
12
  elements = enum.to_a
10
13
  if elements.empty?
11
14
  raise ArgumentError, 'Enumerable must not be empty.'
@@ -4,7 +4,9 @@ module Gecode
4
4
  # to that solution. Returns the model if a solution was found, nil
5
5
  # otherwise.
6
6
  def solve!
7
- space = dfs_engine.next
7
+ dfs = dfs_engine
8
+ space = dfs.next
9
+ @statistics = dfs.statistics
8
10
  return nil if space.nil?
9
11
  self.active_space = space
10
12
  return self
@@ -14,6 +16,7 @@ module Gecode
14
16
  # might have been performed). Returns the reset model.
15
17
  def reset!
16
18
  self.active_space = base_space
19
+ @statistics = nil
17
20
  return self
18
21
  end
19
22
 
@@ -33,11 +36,33 @@ module Gecode
33
36
  next_solution = nil
34
37
  while not (next_solution = dfs.next).nil?
35
38
  self.active_space = next_solution
39
+ @statistics = dfs.statistics
36
40
  yield self
37
41
  end
38
42
  self.reset!
39
43
  end
40
44
 
45
+ # Returns search statistics providing various information from Gecode about
46
+ # the search that resulted in the model's current variable state. If the
47
+ # model's variables have not undegone any search then nil is returned. The
48
+ # statistics is a hash with the following keys:
49
+ # [:propagations] The number of propagation steps performed.
50
+ # [:failures] The number of failed nodes in the search tree.
51
+ # [:clones] The number of clones created.
52
+ # [:commits] The number of commit operations performed.
53
+ # [:memory] The peak memory allocated to Gecode.
54
+ def search_stats
55
+ return nil if @statistics.nil?
56
+
57
+ return {
58
+ :propagations => @statistics.propagate,
59
+ :failures => @statistics.fail,
60
+ :clones => @statistics.clone,
61
+ :commits => @statistics.commit,
62
+ :memory => @statistics.memory
63
+ }
64
+ end
65
+
41
66
  # Finds the optimal solution. Optimality is defined by the provided block
42
67
  # which is given one parameter, a solution to the problem. The block should
43
68
  # constrain the solution so that that only "better" solutions can be new
@@ -70,7 +95,14 @@ module Gecode
70
95
  options.c_d = Gecode::Raw::Search::Config::MINIMAL_DISTANCE
71
96
  options.a_d = Gecode::Raw::Search::Config::ADAPTIVE_DISTANCE
72
97
  options.stop = nil
73
- result = Gecode::Raw::bab(selected_space, options)
98
+ bab = Gecode::Raw::BAB.new(selected_space, options)
99
+
100
+ result = nil
101
+ previous_solution = nil
102
+ until (previous_solution = bab.next).nil?
103
+ result = previous_solution
104
+ end
105
+ @statistics = bab.statistics
74
106
 
75
107
  # Reset the method used constrain calls and return the result.
76
108
  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.8.1'
3
+ VERSION = '0.8.2'
4
4
  end
@@ -17,6 +17,55 @@ class ArithmeticSampleProblem < Gecode::Model
17
17
  end
18
18
  end
19
19
 
20
+ # Construct a method placing expectations for an arithmetic constraint with the
21
+ # specified arity (number of variables before must) and the specified name in
22
+ # Gecode.
23
+ def arithmetic_expectation(gecode_name, arity)
24
+ lambda do |relation, rhs, strength, kind, reif_var, negated|
25
+ # Construct the arguments expected to be passed to the Gecode variant of
26
+ # the constraint.
27
+ rhs = an_instance_of(Gecode::Raw::IntVar) if rhs.respond_to? :bind
28
+ expected_gecode_arguments = [an_instance_of(Gecode::Raw::Space)]
29
+ arity.times do
30
+ expected_gecode_arguments << an_instance_of(Gecode::Raw::IntVar)
31
+ end
32
+ can_use_single_gecode_constraint = reif_var.nil? && !negated &&
33
+ relation == Gecode::Raw::IRT_EQ && !rhs.kind_of?(Fixnum)
34
+ if can_use_single_gecode_constraint
35
+ expected_gecode_arguments << rhs
36
+ else
37
+ expected_gecode_arguments << an_instance_of(Gecode::Raw::IntVar)
38
+ end
39
+ expected_gecode_arguments.concat([strength, kind])
40
+
41
+ # Create the actual method producing the expectation.
42
+ @model.allow_space_access do
43
+ if reif_var.nil?
44
+ if can_use_single_gecode_constraint
45
+ Gecode::Raw.should_receive(gecode_name).once.with(
46
+ *expected_gecode_arguments)
47
+ Gecode::Raw.should_receive(:rel).exactly(0).times
48
+ else
49
+ Gecode::Raw.should_receive(gecode_name).once.with(
50
+ *expected_gecode_arguments)
51
+ Gecode::Raw.should_receive(:rel).once.with(
52
+ an_instance_of(Gecode::Raw::Space),
53
+ an_instance_of(Gecode::Raw::IntVar),
54
+ relation, rhs, strength, kind)
55
+ end
56
+ else
57
+ Gecode::Raw.should_receive(gecode_name).once.with(
58
+ *expected_gecode_arguments)
59
+ Gecode::Raw.should_receive(:rel).once.with(
60
+ an_instance_of(Gecode::Raw::Space),
61
+ an_instance_of(Gecode::Raw::IntVar), relation, rhs,
62
+ an_instance_of(Gecode::Raw::BoolVar),
63
+ strength, kind)
64
+ end
65
+ end
66
+ end
67
+ end
68
+
20
69
  # Requires @stub, @target, @model and @expect.
21
70
  describe 'arithmetic constraint', :shared => true do
22
71
  before do
@@ -228,46 +277,7 @@ describe Gecode::Constraints::Int::Arithmetic, ' (multiplication)' do
228
277
  @stub = @var * @var2
229
278
  @target = @model.var3
230
279
 
231
- # Creates an expectation corresponding to the specified input.
232
- @expect = lambda do |relation, rhs, strength, kind, reif_var, negated|
233
- @model.allow_space_access do
234
- rhs = an_instance_of(Gecode::Raw::IntVar) if rhs.respond_to? :bind
235
- if reif_var.nil?
236
- if !negated and relation == Gecode::Raw::IRT_EQ and
237
- !rhs.kind_of? Fixnum
238
- Gecode::Raw.should_receive(:mult).once.with(
239
- an_instance_of(Gecode::Raw::Space),
240
- an_instance_of(Gecode::Raw::IntVar),
241
- an_instance_of(Gecode::Raw::IntVar),
242
- rhs, strength, kind)
243
- Gecode::Raw.should_receive(:rel).exactly(0).times
244
- else
245
- Gecode::Raw.should_receive(:mult).once.with(
246
- an_instance_of(Gecode::Raw::Space),
247
- an_instance_of(Gecode::Raw::IntVar),
248
- an_instance_of(Gecode::Raw::IntVar),
249
- an_instance_of(Gecode::Raw::IntVar),
250
- strength, kind)
251
- Gecode::Raw.should_receive(:rel).once.with(
252
- an_instance_of(Gecode::Raw::Space),
253
- an_instance_of(Gecode::Raw::IntVar),
254
- relation, rhs, strength, kind)
255
- end
256
- else
257
- Gecode::Raw.should_receive(:mult).once.with(
258
- an_instance_of(Gecode::Raw::Space),
259
- an_instance_of(Gecode::Raw::IntVar),
260
- an_instance_of(Gecode::Raw::IntVar),
261
- an_instance_of(Gecode::Raw::IntVar),
262
- strength, kind)
263
- Gecode::Raw.should_receive(:rel).once.with(
264
- an_instance_of(Gecode::Raw::Space),
265
- an_instance_of(Gecode::Raw::IntVar), relation, rhs,
266
- an_instance_of(Gecode::Raw::BoolVar),
267
- strength, kind)
268
- end
269
- end
270
- end
280
+ @expect = arithmetic_expectation(:mult, 2)
271
281
  end
272
282
 
273
283
  it 'should constrain the value of the multiplication' do
@@ -281,4 +291,61 @@ describe Gecode::Constraints::Int::Arithmetic, ' (multiplication)' do
281
291
  end
282
292
 
283
293
  it_should_behave_like 'arithmetic constraint'
284
- end
294
+ end
295
+
296
+ describe Gecode::Constraints::Int::Arithmetic, ' (squared)' do
297
+ before do
298
+ @model = ArithmeticSampleProblem.new
299
+ @var = @model.var
300
+ @stub = @var.squared
301
+ @target = @model.var2
302
+
303
+ @expect = arithmetic_expectation(:sqr, 1)
304
+ end
305
+
306
+ it 'should constrain the value of the variable squared' do
307
+ @var.squared.must == 9
308
+ sol = @model.solve!
309
+ sol.var.value.abs.should == 3
310
+ end
311
+
312
+ it_should_behave_like 'arithmetic constraint'
313
+ end
314
+
315
+ describe Gecode::Constraints::Int::Arithmetic, ' (square root)' do
316
+ before do
317
+ @model = ArithmeticSampleProblem.new
318
+ @var = @model.var
319
+ @stub = @var.square_root
320
+ @target = @model.var2
321
+
322
+ @expect = arithmetic_expectation(:sqrt, 1)
323
+ end
324
+
325
+ it 'should constrain the square root of the variable' do
326
+ @var.square_root.must == 3
327
+ sol = @model.solve!
328
+ Math.sqrt(sol.var.value).floor.should == 3
329
+ end
330
+
331
+ it 'should constrain the square root of the variable (2)' do
332
+ @var.square_root.must == 0
333
+ sol = @model.solve!
334
+ Math.sqrt(sol.var.value).floor.should == 0
335
+ end
336
+
337
+ it 'should constrain the square root of the variable (3)' do
338
+ @var.must < 0
339
+ @var.square_root.must == 0
340
+ @model.solve!.should be_nil
341
+ end
342
+
343
+ it 'should round down the square root' do
344
+ @var.must > 4
345
+ @var.square_root.must == 2
346
+ sol = @model.solve!
347
+ sol.var.value.should be_between(5,8)
348
+ end
349
+
350
+ it_should_behave_like 'arithmetic constraint'
351
+ end
@@ -228,5 +228,9 @@ describe Gecode::Constraints::Bool do
228
228
  sol.b3.value.should be_true
229
229
  end
230
230
 
231
+ it 'should raise error on right hand sides of the wrong type' do
232
+ lambda{ @b1.must == 'hello' }.should raise_error(TypeError)
233
+ end
234
+
231
235
  it_should_behave_like 'reifiable constraint'
232
236
  end
@@ -16,6 +16,20 @@ class ChannelSampleProblem < Gecode::Model
16
16
  end
17
17
  end
18
18
 
19
+ class BoolChannelSampleProblem < Gecode::Model
20
+ attr :bool_enum
21
+ attr :bool
22
+ attr :int
23
+
24
+ def initialize
25
+ @bool_enum = bool_var_array(4)
26
+ @int = int_var(0..3)
27
+ @bool = bool_var
28
+
29
+ branch_on wrap_enum([@int])
30
+ end
31
+ end
32
+
19
33
  describe Gecode::Constraints::IntEnum::Channel, ' (two int enums)' do
20
34
  before do
21
35
  @model = ChannelSampleProblem.new
@@ -67,13 +81,21 @@ describe Gecode::Constraints::IntEnum::Channel, ' (one int enum and one set enum
67
81
  @model = ChannelSampleProblem.new
68
82
  @positions = @model.positions
69
83
  @sets = @model.sets
84
+
85
+ @invoke_options = lambda do |hash|
86
+ @positions.must.channel @sets, hash
87
+ @model.solve!
88
+ end
89
+ @expect_options = option_expectation do |strength, kind, reif_var|
90
+ Gecode::Raw.should_receive(:channel).once.with(
91
+ an_instance_of(Gecode::Raw::Space),
92
+ an_instance_of(Gecode::Raw::IntVarArray),
93
+ an_instance_of(Gecode::Raw::SetVarArray))
94
+ end
70
95
  end
71
96
 
72
97
  it 'should translate into a channel constraint' do
73
- Gecode::Raw.should_receive(:channel).once.with(
74
- an_instance_of(Gecode::Raw::Space),
75
- an_instance_of(Gecode::Raw::IntVarArray),
76
- an_instance_of(Gecode::Raw::SetVarArray))
98
+ @expect_options.call({})
77
99
  @positions.must.channel @sets
78
100
  @model.solve!
79
101
  end
@@ -87,6 +109,8 @@ describe Gecode::Constraints::IntEnum::Channel, ' (one int enum and one set enum
87
109
  sets[position].value.should include(i)
88
110
  end
89
111
  end
112
+
113
+ it_should_behave_like 'non-reifiable set constraint'
90
114
  end
91
115
 
92
116
  describe Gecode::Constraints::SetEnum, ' (channel with set as left hand side)' do
@@ -123,4 +147,210 @@ describe Gecode::Constraints::SetEnum, ' (channel with set as left hand side)' d
123
147
  end
124
148
 
125
149
  it_should_behave_like 'non-reifiable set constraint'
126
- end
150
+ end
151
+
152
+ # Requires @model, @bool and @int. Also requires @place_constraint which is a
153
+ # method that takes five variables: a boolean variable, an integer variable,
154
+ # the name of the equality method to use, whether or not the constraint should
155
+ # be negated and a hash of options, and places the channel constraint on them.
156
+ describe 'channel constraint between one int and one bool variable', :shared => true do
157
+ before do
158
+ @invoke_options = lambda do |hash|
159
+ @place_constraint.call(@bool, @int, :==, false, hash)
160
+ @model.solve!
161
+ end
162
+ @expect_options = option_expectation do |strength, kind, reif_var|
163
+ Gecode::Raw.should_receive(:channel).once.with(
164
+ an_instance_of(Gecode::Raw::Space),
165
+ an_instance_of(Gecode::Raw::IntVar),
166
+ an_instance_of(Gecode::Raw::BoolVar),
167
+ strength, kind)
168
+ end
169
+ end
170
+
171
+ ([:==] + Gecode::Constraints::Util::COMPARISON_ALIASES[:==]).each do |ali|
172
+ it "should translate #{ali} into a channel constraint" do
173
+ @expect_options.call({})
174
+ @place_constraint.call(@bool, @int, ali, false, {})
175
+ @model.solve!
176
+ end
177
+ end
178
+
179
+ it 'should constrain the int variable to be 1 when the boolean variable is true' do
180
+ @bool.must_be.true
181
+ @place_constraint.call(@bool, @int, :==, false, {})
182
+ @model.solve!
183
+ @int.value.should == 1
184
+ end
185
+
186
+ it 'should constrain the int variable to be 0 when the boolean variable is false' do
187
+ @bool.must_be.false
188
+ @place_constraint.call(@bool, @int, :==, false, {})
189
+ @model.solve!
190
+ @int.value.should == 0
191
+ end
192
+
193
+ it 'should not allow negation' do
194
+ lambda do
195
+ @place_constraint.call(@bool, @int, :==, true, {})
196
+ end.should raise_error(Gecode::MissingConstraintError)
197
+ end
198
+
199
+ it_should_behave_like 'non-reifiable constraint'
200
+ end
201
+
202
+ describe Gecode::Constraints::Int::Channel, ' (one int and one bool variable)' do
203
+ before do
204
+ @model = BoolChannelSampleProblem.new
205
+ @bool = @model.bool_var
206
+ @int = @model.int_var
207
+
208
+ @place_constraint = lambda do |bool, int, equals_method_name, negate, options|
209
+ if negate
210
+ int.must_not.method(equals_method_name).call(bool, options)
211
+ else
212
+ int.must.method(equals_method_name).call(bool, options)
213
+ end
214
+ end
215
+ end
216
+
217
+ it 'should not shadow linear boolean constraints' do
218
+ lambda do
219
+ (@bool + @bool).must == @bool
220
+ @model.solve!
221
+ end.should_not raise_error
222
+ end
223
+
224
+ it 'should raise error for unsupported right hand sides' do
225
+ lambda{ @int.must == 'hello' }.should raise_error(TypeError)
226
+ end
227
+
228
+ it_should_behave_like 'channel constraint between one int and one bool variable'
229
+ end
230
+
231
+ describe Gecode::Constraints::Int::Channel, ' (one bool and one int variable)' do
232
+ before do
233
+ @model = BoolChannelSampleProblem.new
234
+ @bool = @model.bool_var
235
+ @int = @model.int_var
236
+
237
+ @place_constraint = lambda do |bool, int, equals_method_name, negate, options|
238
+ if negate
239
+ bool.must_not.method(equals_method_name).call(int, options)
240
+ else
241
+ bool.must.method(equals_method_name).call(int, options)
242
+ end
243
+ end
244
+ end
245
+
246
+ it 'should not shadow linear boolean constraints' do
247
+ lambda do
248
+ @bool.must == @bool + @bool
249
+ @model.solve!
250
+ end.should_not raise_error
251
+ end
252
+
253
+ it 'should raise error for unsupported right hand sides' do
254
+ lambda{ @bool.must == 'hello' }.should raise_error(TypeError)
255
+ end
256
+
257
+ it_should_behave_like 'channel constraint between one int and one bool variable'
258
+ end
259
+
260
+ # Requires @model, @bool_enum and @int. Also requires @place_constraint which
261
+ # is a method that takes four variables: a boolean enum, an integer variable,
262
+ # whether or not the constraint should be negated and a hash of options, and
263
+ # places the channel constraint on them.
264
+ describe 'channel constraint between bool enum and int variable', :shared => true do
265
+ before do
266
+ @invoke_options = lambda do |hash|
267
+ @place_constraint.call(@bools, @int, false, hash)
268
+ @model.solve!
269
+ end
270
+ @expect_options = option_expectation do |strength, kind, reif_var|
271
+ Gecode::Raw.should_receive(:channel).once.with(
272
+ an_instance_of(Gecode::Raw::Space),
273
+ an_instance_of(Gecode::Raw::BoolVarArray),
274
+ an_instance_of(Gecode::Raw::IntVar), 0,
275
+ strength, kind)
276
+ end
277
+ end
278
+
279
+ it 'should channel the bool enum with the integer variable' do
280
+ @int.must > 2
281
+ @place_constraint.call(@bools, @int, false, {})
282
+ @model.solve!.should_not be_nil
283
+ int_val = @int.value
284
+ @bools.values.each_with_index do |bool, index|
285
+ bool.should == (index == int_val)
286
+ end
287
+ end
288
+
289
+ it 'should take the offset into account when channeling' do
290
+ @int.must > 2
291
+ offset = 1
292
+ @place_constraint.call(@bools, @int, false, :offset => offset)
293
+ @model.solve!.should_not be_nil
294
+ int_val = @int.value
295
+ @bools.values.each_with_index do |bool, index|
296
+ bool.should == (index + offset == int_val)
297
+ end
298
+ end
299
+
300
+ it 'should not allow negation' do
301
+ lambda do
302
+ @place_constraint.call(@bools, @int, true, {})
303
+ end.should raise_error(Gecode::MissingConstraintError)
304
+ end
305
+
306
+ it_should_behave_like 'non-reifiable constraint'
307
+ end
308
+
309
+ describe Gecode::Constraints::BoolEnum::Channel, ' (bool enum as lhs with int variable)' do
310
+ before do
311
+ @model = BoolChannelSampleProblem.new
312
+ @bools = @model.bool_enum
313
+ @int = @model.int
314
+
315
+ @place_constraint = lambda do |bools, int, negate, options|
316
+ unless negate
317
+ bools.must.channel(int, options)
318
+ else
319
+ bools.must_not.channel(int, options)
320
+ end
321
+ end
322
+ end
323
+
324
+ it 'should raise error if an integer variable is not given as right hand side' do
325
+ lambda do
326
+ @bools.must.channel 'hello'
327
+ end.should raise_error(TypeError)
328
+ end
329
+
330
+ it_should_behave_like 'channel constraint between bool enum and int variable'
331
+ end
332
+
333
+
334
+ describe Gecode::Constraints::BoolEnum::Channel, ' (int variable as lhs with bool enum)' do
335
+ before do
336
+ @model = BoolChannelSampleProblem.new
337
+ @bools = @model.bool_enum
338
+ @int = @model.int
339
+
340
+ @place_constraint = lambda do |bools, int, negate, options|
341
+ unless negate
342
+ int.must.channel(bools, options)
343
+ else
344
+ int.must_not.channel(bools, options)
345
+ end
346
+ end
347
+ end
348
+
349
+ it 'should raise error if a boolean enum is not given as right hand side' do
350
+ lambda do
351
+ @int.must.channel 'hello'
352
+ end.should raise_error(TypeError)
353
+ end
354
+
355
+ it_should_behave_like 'channel constraint between bool enum and int variable'
356
+ end