gecoder-with-gecode 0.8.1-mswin32 → 0.8.2-mswin32

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