gecoder 0.3.0 → 0.4.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.
Files changed (56) hide show
  1. data/CHANGES +17 -2
  2. data/README +7 -1
  3. data/Rakefile +4 -0
  4. data/lib/gecoder/interface/constraints/bool/boolean.rb +1 -4
  5. data/lib/gecoder/interface/constraints/int/arithmetic.rb +77 -0
  6. data/lib/gecoder/interface/constraints/int/domain.rb +50 -0
  7. data/lib/gecoder/interface/constraints/int/linear.rb +12 -44
  8. data/lib/gecoder/interface/constraints/int_enum/arithmetic.rb +72 -0
  9. data/lib/gecoder/interface/constraints/int_enum/channel.rb +32 -0
  10. data/lib/gecoder/interface/constraints/int_enum/count.rb +90 -0
  11. data/lib/gecoder/interface/constraints/int_enum/distinct.rb +3 -8
  12. data/lib/gecoder/interface/constraints/int_enum/element.rb +75 -0
  13. data/lib/gecoder/interface/constraints/int_enum/equality.rb +31 -0
  14. data/lib/gecoder/interface/constraints/int_enum/sort.rb +104 -0
  15. data/lib/gecoder/interface/constraints/int_enum_constraints.rb +6 -0
  16. data/lib/gecoder/interface/constraints/int_var_constraints.rb +2 -0
  17. data/lib/gecoder/interface/constraints/reifiable_constraints.rb +21 -0
  18. data/lib/gecoder/interface/constraints.rb +57 -6
  19. data/lib/gecoder/interface/enum_matrix.rb +64 -0
  20. data/lib/gecoder/interface/enum_wrapper.rb +33 -5
  21. data/lib/gecoder/interface/model.rb +36 -6
  22. data/lib/gecoder/interface.rb +1 -0
  23. data/lib/gecoder/version.rb +4 -0
  24. data/lib/gecoder.rb +1 -0
  25. data/specs/binding_changes.rb +72 -0
  26. data/specs/bool_var.rb +20 -0
  27. data/specs/branch.rb +104 -0
  28. data/specs/constraints/arithmetic.rb +227 -0
  29. data/specs/constraints/boolean.rb +132 -0
  30. data/specs/constraints/channel.rb +55 -0
  31. data/specs/constraints/constraint_helper.rb +48 -0
  32. data/specs/constraints/constraints.rb +28 -0
  33. data/specs/constraints/count.rb +99 -0
  34. data/specs/constraints/distinct.rb +99 -0
  35. data/specs/constraints/domain.rb +56 -0
  36. data/specs/constraints/element.rb +128 -0
  37. data/specs/constraints/equality.rb +30 -0
  38. data/specs/constraints/linear.rb +166 -0
  39. data/specs/constraints/reification_sugar.rb +92 -0
  40. data/specs/constraints/relation.rb +72 -0
  41. data/specs/constraints/sort.rb +173 -0
  42. data/specs/enum_matrix.rb +43 -0
  43. data/specs/enum_wrapper.rb +100 -0
  44. data/specs/int_var.rb +108 -0
  45. data/specs/model.rb +84 -0
  46. data/specs/search.rb +157 -0
  47. data/specs/spec_helper.rb +63 -0
  48. data/specs/tmp +135 -0
  49. data/tasks/all_tasks.rb +1 -0
  50. data/tasks/distribution.rake +64 -0
  51. data/tasks/rcov.rake +17 -0
  52. data/tasks/specs.rake +16 -0
  53. data/tasks/svn.rake +11 -0
  54. data/tasks/website.rake +58 -0
  55. data/vendor/rust/include/rust_conversions.hh +1 -2
  56. metadata +53 -2
@@ -0,0 +1,227 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require File.dirname(__FILE__) + '/constraint_helper'
3
+
4
+ class ArithmeticSampleProblem < Gecode::Model
5
+ attr :numbers
6
+ attr :var
7
+ attr :var2
8
+
9
+ def initialize
10
+ @numbers = int_var_array(4, 0..9)
11
+ @var = int_var(-9..9)
12
+ @var2 = int_var(0..9)
13
+ branch_on @numbers
14
+ branch_on wrap_enum([@var, @var2])
15
+ end
16
+ end
17
+
18
+ describe 'arithmetic constraint', :shared => true do
19
+ situations = {
20
+ 'variable bound' => nil,
21
+ 'constant bound' => 5
22
+ }.each_pair do |description, bound|
23
+ Gecode::Constraints::Util::RELATION_TYPES.each_pair do |relation, type|
24
+ it "should translate #{relation} with #{description}" do
25
+ bound = @var if bound.nil?
26
+ @expect.call(type, bound, Gecode::Raw::ICL_DEF, nil)
27
+ @stub.must.send(relation, bound)
28
+ @model.solve!
29
+ end
30
+ end
31
+ Gecode::Constraints::Util::NEGATED_RELATION_TYPES.each_pair do |relation, type|
32
+ it "should translate negated #{relation} with #{description}" do
33
+ bound = @var if bound.nil?
34
+ @expect.call(type, bound, Gecode::Raw::ICL_DEF, nil)
35
+ @stub.must_not.send(relation, bound)
36
+ @model.solve!
37
+ end
38
+ end
39
+ end
40
+
41
+ it 'should raise error if the right hand side is of the wrong type' do
42
+ lambda{ @stub.must == 'hello' }.should raise_error(TypeError)
43
+ end
44
+
45
+ it_should_behave_like 'constraint with options'
46
+ end
47
+
48
+ describe Gecode::Constraints::IntEnum::Arithmetic, ' (max)' do
49
+ before do
50
+ @model = ArithmeticSampleProblem.new
51
+ @numbers = @model.numbers
52
+ @var = @model.var
53
+ @stub = @numbers.max
54
+
55
+ # Creates an expectation corresponding to the specified input.
56
+ @expect = lambda do |relation, rhs, strength, reif_var|
57
+ rhs = rhs.bind if rhs.respond_to? :bind
58
+ if reif_var.nil?
59
+ Gecode::Raw.should_receive(:max).once.with(@model.active_space,
60
+ an_instance_of(Gecode::Raw::IntVarArray),
61
+ an_instance_of(Gecode::Raw::IntVar), an_instance_of(Fixnum))
62
+ Gecode::Raw.should_receive(:rel).once.with(@model.active_space,
63
+ an_instance_of(Gecode::Raw::IntVar), relation, rhs, strength)
64
+ else
65
+ Gecode::Raw.should_receive(:max).once.with(@model.active_space,
66
+ an_instance_of(Gecode::Raw::IntVarArray),
67
+ an_instance_of(Gecode::Raw::IntVar), an_instance_of(Fixnum))
68
+ Gecode::Raw.should_receive(:rel).once.with(@model.active_space,
69
+ an_instance_of(Gecode::Raw::IntVar), relation, rhs, reif_var.bind,
70
+ strength)
71
+ end
72
+ end
73
+
74
+ # For constraint option spec.
75
+ @invoke_options = lambda do |hash|
76
+ @numbers.max.must_be.greater_than(@var, hash)
77
+ @model.solve!
78
+ end
79
+ @expect_options = lambda do |strength, reif_var|
80
+ @expect.call(Gecode::Raw::IRT_GR, @var, strength, reif_var)
81
+ end
82
+ end
83
+
84
+ it 'should constrain the maximum value' do
85
+ @numbers.max.must > 5
86
+ @model.solve!.numbers.map{ |n| n.val }.max.should > 5
87
+ end
88
+
89
+ it_should_behave_like 'arithmetic constraint'
90
+ end
91
+
92
+ describe Gecode::Constraints::IntEnum::Arithmetic, ' (min)' do
93
+ before do
94
+ @model = ArithmeticSampleProblem.new
95
+ @numbers = @model.numbers
96
+ @var = @model.var
97
+ @stub = @numbers.min
98
+
99
+ # Creates an expectation corresponding to the specified input.
100
+ @expect = lambda do |relation, rhs, strength, reif_var|
101
+ rhs = rhs.bind if rhs.respond_to? :bind
102
+ if reif_var.nil?
103
+ Gecode::Raw.should_receive(:min).once.with(@model.active_space,
104
+ an_instance_of(Gecode::Raw::IntVarArray),
105
+ an_instance_of(Gecode::Raw::IntVar), an_instance_of(Fixnum))
106
+ Gecode::Raw.should_receive(:rel).once.with(@model.active_space,
107
+ an_instance_of(Gecode::Raw::IntVar), relation, rhs, strength)
108
+ else
109
+ Gecode::Raw.should_receive(:min).once.with(@model.active_space,
110
+ an_instance_of(Gecode::Raw::IntVarArray),
111
+ an_instance_of(Gecode::Raw::IntVar), an_instance_of(Fixnum))
112
+ Gecode::Raw.should_receive(:rel).once.with(@model.active_space,
113
+ an_instance_of(Gecode::Raw::IntVar), relation, rhs, reif_var.bind,
114
+ strength)
115
+ end
116
+ end
117
+
118
+ # For constraint option spec.
119
+ @invoke_options = lambda do |hash|
120
+ @numbers.min.must_be.greater_than(@var, hash)
121
+ @model.solve!
122
+ end
123
+ @expect_options = lambda do |strength, reif_var|
124
+ @expect.call(Gecode::Raw::IRT_GR, @var, strength, reif_var)
125
+ end
126
+ end
127
+
128
+ it 'should constrain the minimum value' do
129
+ @numbers.min.must > 5
130
+ @model.solve!.numbers.map{ |n| n.val }.min.should > 5
131
+ end
132
+
133
+ it_should_behave_like 'arithmetic constraint'
134
+ end
135
+
136
+ describe Gecode::Constraints::Int::Arithmetic, ' (abs)' do
137
+ before do
138
+ @model = ArithmeticSampleProblem.new
139
+ @var = @model.var
140
+ @stub = @var.abs
141
+
142
+ # Creates an expectation corresponding to the specified input.
143
+ @expect = lambda do |relation, rhs, strength, reif_var|
144
+ rhs = rhs.bind if rhs.respond_to? :bind
145
+ if reif_var.nil?
146
+ Gecode::Raw.should_receive(:abs).once.with(@model.active_space,
147
+ @var.bind, an_instance_of(Gecode::Raw::IntVar),
148
+ an_instance_of(Fixnum))
149
+ Gecode::Raw.should_receive(:rel).once.with(@model.active_space,
150
+ an_instance_of(Gecode::Raw::IntVar), relation, rhs, strength)
151
+ else
152
+ Gecode::Raw.should_receive(:abs).once.with(@model.active_space,
153
+ @var.bind, an_instance_of(Gecode::Raw::IntVar),
154
+ an_instance_of(Fixnum))
155
+ Gecode::Raw.should_receive(:rel).once.with(@model.active_space,
156
+ an_instance_of(Gecode::Raw::IntVar), relation, rhs, reif_var.bind,
157
+ strength)
158
+ end
159
+ end
160
+
161
+ # For constraint option spec.
162
+ @invoke_options = lambda do |hash|
163
+ @var.abs.must_be.greater_than(@var, hash)
164
+ @model.solve!
165
+ end
166
+ @expect_options = lambda do |strength, reif_var|
167
+ @expect.call(Gecode::Raw::IRT_GR, @var, strength, reif_var)
168
+ end
169
+ end
170
+
171
+ it 'should constrain the absolute value' do
172
+ @var.must < 0
173
+ @var.abs.must == 5
174
+ @model.solve!.var.val.should == -5
175
+ end
176
+
177
+ it_should_behave_like 'arithmetic constraint'
178
+ end
179
+
180
+ describe Gecode::Constraints::Int::Arithmetic, ' (multiplication)' do
181
+ before do
182
+ @model = ArithmeticSampleProblem.new
183
+ @var = @model.var
184
+ @var2 = @model.var2
185
+ @stub = @var * @var2
186
+
187
+ # Creates an expectation corresponding to the specified input.
188
+ @expect = lambda do |relation, rhs, strength, reif_var|
189
+ rhs = rhs.bind if rhs.respond_to? :bind
190
+ if reif_var.nil?
191
+ Gecode::Raw.should_receive(:mult).once.with(@model.active_space,
192
+ @var.bind, @var2.bind, an_instance_of(Gecode::Raw::IntVar),
193
+ an_instance_of(Fixnum))
194
+ Gecode::Raw.should_receive(:rel).once.with(@model.active_space,
195
+ an_instance_of(Gecode::Raw::IntVar), relation, rhs, strength)
196
+ else
197
+ Gecode::Raw.should_receive(:mult).once.with(@model.active_space,
198
+ @var.bind, @var2.bind, an_instance_of(Gecode::Raw::IntVar),
199
+ an_instance_of(Fixnum))
200
+ Gecode::Raw.should_receive(:rel).once.with(@model.active_space,
201
+ an_instance_of(Gecode::Raw::IntVar), relation, rhs, reif_var.bind,
202
+ strength)
203
+ end
204
+ end
205
+
206
+ # For constraint option spec.
207
+ @invoke_options = lambda do |hash|
208
+ (@var * @var2).must_be.greater_than(@var, hash)
209
+ @model.solve!
210
+ end
211
+ @expect_options = lambda do |strength, reif_var|
212
+ @expect.call(Gecode::Raw::IRT_GR, @var, strength, reif_var)
213
+ end
214
+ end
215
+
216
+ it 'should constrain the value of the multiplication' do
217
+ (@var * @var2).must == 56
218
+ sol = @model.solve!
219
+ [sol.var.val, sol.var2.val].sort.should == [7, 8]
220
+ end
221
+
222
+ it 'should not interfere with other defined multiplication methods' do
223
+ (@var * :foo).should be_nil
224
+ end
225
+
226
+ it_should_behave_like 'arithmetic constraint'
227
+ end
@@ -0,0 +1,132 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ class BoolSampleProblem < Gecode::Model
4
+ attr :b1
5
+ attr :b2
6
+ attr :b3
7
+
8
+ def initialize
9
+ @b1 = self.bool_var
10
+ @b2 = self.bool_var
11
+ @b3 = self.bool_var
12
+ end
13
+ end
14
+
15
+ describe Gecode::Constraints::Bool do
16
+ before do
17
+ @model = BoolSampleProblem.new
18
+ @b1 = @model.b1
19
+ @b2 = @model.b2
20
+ @b3 = @model.b3
21
+ end
22
+
23
+ it 'should handle single variables constrainted to be true' do
24
+ @b1.must_be.true
25
+ b1 = @model.solve!.b1
26
+ b1.should be_assigned
27
+ b1.true?.should be_true
28
+ end
29
+
30
+ it 'should handle single variables constrainted to be false' do
31
+ @b1.must_be.false
32
+ b1 = @model.solve!.b1
33
+ b1.should be_assigned
34
+ b1.true?.should_not be_true
35
+ end
36
+
37
+ it 'should handle single variables constrainted not to be true' do
38
+ @b1.must_not_be.false
39
+ b1 = @model.solve!.b1
40
+ b1.should be_assigned
41
+ b1.true?.should be_true
42
+ end
43
+
44
+ it 'should handle single variables constrainted not to be false' do
45
+ @b1.must_not_be.true
46
+ b1 = @model.solve!.b1
47
+ b1.should be_assigned
48
+ b1.true?.should_not be_true
49
+ end
50
+
51
+ it 'should handle disjunction' do
52
+ @b1.must_be.false
53
+ (@b1 | @b2).must_be.true
54
+ sol = @model.solve!
55
+ sol.b1.true?.should_not be_true
56
+ sol.b2.true?.should be_true
57
+ end
58
+
59
+ it 'should handle negated disjunction' do
60
+ @b1.must_be.false
61
+ (@b1 | @b2).must_not_be.true
62
+ sol = @model.solve!
63
+ sol.b1.true?.should_not be_true
64
+ sol.b2.true?.should_not be_true
65
+ end
66
+
67
+ it 'should handle conjunction' do
68
+ (@b1 & @b2).must_be.true
69
+ sol = @model.solve!
70
+ sol.b1.true?.should be_true
71
+ sol.b2.true?.should be_true
72
+ end
73
+
74
+ it 'should handle negated conjunction' do
75
+ @b1.must_be.true
76
+ (@b1 & @b2).must_not_be.true
77
+ sol = @model.solve!
78
+ sol.b1.true?.should be_true
79
+ sol.b2.true?.should_not be_true
80
+ end
81
+
82
+ it 'should handle single variables as right hand side' do
83
+ @b1.must == @b2
84
+ @b2.must_be.false
85
+ sol = @model.solve!
86
+ sol.b1.true?.should_not be_true
87
+ sol.b2.true?.should_not be_true
88
+ end
89
+
90
+ it 'should handle single variables with negation as right hand side' do
91
+ @b1.must_not == @b2
92
+ @b2.must_be.false
93
+ sol = @model.solve!
94
+ sol.b1.true?.should be_true
95
+ sol.b2.true?.should_not be_true
96
+ end
97
+
98
+ it 'should handle expressions as right hand side' do
99
+ @b1.must == (@b2 | @b3)
100
+ @b2.must_be.true
101
+ sol = @model.solve!
102
+ sol.b1.true?.should be_true
103
+ sol.b2.true?.should be_true
104
+ end
105
+
106
+ it 'should handle nested expressions as left hand side' do
107
+ ((@b1 & @b2) | @b3 | (@b1 & @b3)).must_be.true
108
+ @b1.must_be.false
109
+ sol = @model.solve!
110
+ sol.b1.true?.should_not be_true
111
+ sol.b3.true?.should be_true
112
+ end
113
+
114
+ it 'should handle nested expressions on both side' do
115
+ ((@b1 & @b1) | @b3).must == ((@b1 & @b3) & @b2)
116
+ @b1.must_be.true
117
+ sol = @model.solve!
118
+ sol.b1.true?.should be_true
119
+ sol.b2.true?.should be_true
120
+ sol.b3.true?.should be_true
121
+ end
122
+
123
+ it 'should handle nested expressions on both sides with negation' do
124
+ ((@b1 & @b1) | @b3).must_not == ((@b1 | @b3) & @b2)
125
+ @b1.must_be.true
126
+ @b3.must_be.true
127
+ sol = @model.solve!
128
+ sol.b1.true?.should be_true
129
+ sol.b2.true?.should_not be_true
130
+ sol.b3.true?.should be_true
131
+ end
132
+ end
@@ -0,0 +1,55 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require File.dirname(__FILE__) + '/constraint_helper'
3
+
4
+ class ChannelSampleProblem < Gecode::Model
5
+ attr :elements
6
+ attr :positions
7
+
8
+ def initialize
9
+ @elements = int_var_array(4, 0..3)
10
+ @elements.must_be.distinct
11
+ @positions = int_var_array(4, 0..3)
12
+ @positions.must_be.distinct
13
+ branch_on @elements
14
+ end
15
+ end
16
+
17
+ describe Gecode::Constraints::IntEnum::Channel do
18
+ before do
19
+ @model = ChannelSampleProblem.new
20
+ @positions = @model.positions
21
+ @elements = @model.elements
22
+ @invoke_options = lambda do |hash|
23
+ @positions.must.channel @elements, hash
24
+ @model.solve!
25
+ end
26
+ @expect_options = lambda do |strength, reif_var|
27
+ Gecode::Raw.should_receive(:channel).once.with(@model.active_space,
28
+ an_instance_of(Gecode::Raw::IntVarArray),
29
+ an_instance_of(Gecode::Raw::IntVarArray), strength)
30
+ end
31
+ end
32
+
33
+ it 'should translate into a channel constraint' do
34
+ Gecode::Raw.should_receive(:channel).once.with(@model.active_space,
35
+ anything, anything, Gecode::Raw::ICL_DEF)
36
+ @invoke_options.call({})
37
+ end
38
+
39
+ it 'should constrain variables to be channelled' do
40
+ @elements.must.channel @positions
41
+ @model.solve!
42
+ elements = @model.elements.map{ |e| e.val }
43
+ positions = @model.elements.map{ |p| p.val }
44
+ elements.each_with_index do |element, i|
45
+ element.should equal(positions.index(i))
46
+ end
47
+ end
48
+
49
+ it 'should not allow negation' do
50
+ lambda{ @elements.must_not.channel @positions }.should raise_error(
51
+ Gecode::MissingConstraintError)
52
+ end
53
+
54
+ it_should_behave_like 'constraint with strength option'
55
+ end
@@ -0,0 +1,48 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ # This requires that the constraint spec has instance variables @invoke_options
4
+ # and @expect_options .
5
+ describe 'constraint with strength option', :shared => true do
6
+ { :default => Gecode::Raw::ICL_DEF,
7
+ :value => Gecode::Raw::ICL_VAL,
8
+ :bounds => Gecode::Raw::ICL_BND,
9
+ :domain => Gecode::Raw::ICL_DOM
10
+ }.each_pair do |name, gecode_value|
11
+ it "should translate propagation strength #{name}" do
12
+ @expect_options.call(gecode_value, nil)
13
+ @invoke_options.call(:strength => name)
14
+ end
15
+ end
16
+
17
+ it 'should default to using default as propagation strength' do
18
+ @expect_options.call(Gecode::Raw::ICL_DEF, nil)
19
+ @invoke_options.call({})
20
+ end
21
+
22
+ it 'should raise errors for unrecognized options' do
23
+ lambda{ @invoke_options.call(:does_not_exist => :foo) }.should(
24
+ raise_error(ArgumentError))
25
+ end
26
+
27
+ it 'should raise errors for unrecognized propagation strengths' do
28
+ lambda{ @invoke_options.call(:strength => :does_not_exist) }.should(
29
+ raise_error(ArgumentError))
30
+ end
31
+
32
+ it 'should raise errors for reification variables of incorrect type' do
33
+ lambda{ @invoke_options.call(:reify => 'foo') }.should(
34
+ raise_error(TypeError))
35
+ end
36
+ end
37
+
38
+ # This requires that the constraint spec has instance variables @invoke_options
39
+ # and @expect_options .
40
+ describe 'constraint with options', :shared => true do
41
+ it 'should translate reification' do
42
+ var = @model.bool_var
43
+ @expect_options.call(Gecode::Raw::ICL_DEF, var)
44
+ @invoke_options.call(:reify => var)
45
+ end
46
+
47
+ it_should_behave_like 'constraint with strength option'
48
+ end
@@ -0,0 +1,28 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Gecode::Constraints::Expression do
4
+ it 'should raise error if it doesn\'t get all parameters for initialization' do
5
+ lambda do
6
+ Gecode::Constraints::Expression.new(Gecode::Model.new, :negate => false)
7
+ end.should raise_error(ArgumentError)
8
+ end
9
+ end
10
+
11
+ describe Gecode::Constraints::IntEnum::Expression do
12
+ it 'should raise error unless lhs is an enum' do
13
+ lambda do
14
+ Gecode::Constraints::IntEnum::Expression.new(Gecode::Model.new,
15
+ :lhs => 'foo', :negate => false)
16
+ end.should raise_error(TypeError)
17
+ end
18
+ end
19
+
20
+ describe Gecode::Constraints::Constraint, ' (not subclassed)' do
21
+ before do
22
+ @con = Gecode::Constraints::Constraint.new(Gecode::Model.new, {})
23
+ end
24
+
25
+ it 'should raise error when calling #post because it\'s not overridden' do
26
+ lambda{ @con.post }.should raise_error(NoMethodError)
27
+ end
28
+ end
@@ -0,0 +1,99 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require File.dirname(__FILE__) + '/constraint_helper'
3
+
4
+ class CountSampleProblem < Gecode::Model
5
+ attr :list
6
+ attr :element
7
+ attr :target
8
+
9
+ def initialize
10
+ @list = int_var_array(4, 0..3)
11
+ @element = int_var(0..3)
12
+ @target = int_var(0..4)
13
+ branch_on @list
14
+ end
15
+ end
16
+
17
+ describe Gecode::Constraints::IntEnum::Count do
18
+ before do
19
+ @model = CountSampleProblem.new
20
+ @list = @model.list
21
+ @element = @model.element
22
+ @target = @model.target
23
+
24
+ # Creates an expectation corresponding to the specified input.
25
+ @expect = lambda do |element, relation, target, strength, reif_var|
26
+ target = target.bind if target.respond_to? :bind
27
+ element = element.bind if element.respond_to? :bind
28
+ if reif_var.nil?
29
+ Gecode::Raw.should_receive(:count).once.with(@model.active_space,
30
+ an_instance_of(Gecode::Raw::IntVarArray),
31
+ element, relation, target, strength)
32
+ else
33
+ Gecode::Raw.should_receive(:count).once.with(@model.active_space,
34
+ an_instance_of(Gecode::Raw::IntVarArray),
35
+ element, Gecode::Raw::IRT_EQ,
36
+ an_instance_of(Gecode::Raw::IntVar), strength)
37
+ Gecode::Raw.should_receive(:rel).once.with(@model.active_space,
38
+ an_instance_of(Gecode::Raw::IntVar), relation,
39
+ target, reif_var.bind, strength)
40
+ end
41
+ end
42
+
43
+ # For constraint option spec.
44
+ @invoke_options = lambda do |hash|
45
+ @list.count(@element).must_be.greater_than(@target, hash)
46
+ @model.solve!
47
+ end
48
+ @expect_options = lambda do |strength, reif_var|
49
+ @expect.call(@element, Gecode::Raw::IRT_GR, @target, strength, reif_var)
50
+ end
51
+ end
52
+
53
+ # Various situations that must be handled, nil denotes that a variable should
54
+ # be used.
55
+ situations = {
56
+ 'variable element and target' => [nil, nil],
57
+ 'variable element and constant target' => [nil, 2],
58
+ 'constant element and variable target' => [1, nil],
59
+ 'constant element and constant target' => [1, 2]
60
+ }.each_pair do |description, element_and_target|
61
+ element, target = element_and_target
62
+ Gecode::Constraints::Util::RELATION_TYPES.each_pair do |relation, type|
63
+ it "should translate #{relation} with #{description}" do
64
+ element = @element if element.nil?
65
+ target = @target if target.nil?
66
+ @expect.call(element, type, target, Gecode::Raw::ICL_DEF, nil)
67
+ @list.count(element).must.send(relation, target)
68
+ @model.solve!
69
+ end
70
+ end
71
+ Gecode::Constraints::Util::NEGATED_RELATION_TYPES.each_pair do |relation, type|
72
+ it "should translate negated #{relation} with #{description}" do
73
+ element = @element if element.nil?
74
+ target = @target if target.nil?
75
+ @expect.call(element, type, target, Gecode::Raw::ICL_DEF, nil)
76
+ @list.count(element).must_not.send(relation, target)
77
+ @model.solve!
78
+ end
79
+ end
80
+ end
81
+
82
+ it 'should raise error if the target is of the wrong type' do
83
+ lambda{ @list.count(@element).must == 'hello' }.should raise_error(
84
+ TypeError)
85
+ end
86
+
87
+ it 'should raise error on element is of the wrong type' do
88
+ lambda{ @list.count('foo').must == @target }.should raise_error(
89
+ TypeError)
90
+ end
91
+
92
+ it 'should constrain the count' do
93
+ @list.must_be.distinct
94
+ @list.count(0).must <= 0
95
+ @model.solve!.should be_nil
96
+ end
97
+
98
+ it_should_behave_like 'constraint with options'
99
+ end
@@ -0,0 +1,99 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require File.dirname(__FILE__) + '/constraint_helper'
3
+
4
+ class DistinctSampleProblem < Gecode::Model
5
+ attr :vars
6
+
7
+ def initialize
8
+ @vars = int_var_array(2, 1)
9
+ end
10
+ end
11
+
12
+ describe Gecode::Constraints::IntEnum::Distinct do
13
+ before do
14
+ @model = DistinctSampleProblem.new
15
+ @invoke_options = lambda do |hash|
16
+ @model.vars.must_be.distinct(hash)
17
+ @model.solve!
18
+ end
19
+ @expect_options = lambda do |strength, reif_var|
20
+ Gecode::Raw.should_receive(:distinct).once.with(@model.active_space,
21
+ an_instance_of(Gecode::Raw::IntVarArray), strength)
22
+ end
23
+ end
24
+
25
+ it 'should translate into a distinct constraint' do
26
+ Gecode::Raw.should_receive(:distinct).once.with(@model.active_space,
27
+ anything, Gecode::Raw::ICL_DEF)
28
+ @invoke_options.call({})
29
+ end
30
+
31
+ it 'should constrain variables to be distinct' do
32
+ # This won't work well without branching or propagation strengths. So this
33
+ # just shows that the distinct constraint will cause trivially unsolvable
34
+ # problems to directly fail.
35
+ @model.vars.must_be.distinct
36
+ @model.solve!.should be_nil
37
+ end
38
+
39
+ it 'should not allow negation' do
40
+ lambda{ @model.vars.must_not_be.distinct }.should raise_error(
41
+ Gecode::MissingConstraintError)
42
+ end
43
+
44
+ it_should_behave_like 'constraint with strength option'
45
+ end
46
+
47
+ describe Gecode::Constraints::IntEnum::Distinct, ' (with offsets)' do
48
+ before do
49
+ @model = DistinctSampleProblem.new
50
+ @invoke_options = lambda do |hash|
51
+ @model.vars.with_offsets(1,2).must_be.distinct(hash)
52
+ @model.solve!
53
+ end
54
+ @expect_options = lambda do |strength, reif_var|
55
+ if reif_var.nil?
56
+ Gecode::Raw.should_receive(:distinct).once.with(@model.active_space,
57
+ anything, an_instance_of(Gecode::Raw::IntVarArray), strength)
58
+ else
59
+ Gecode::Raw.should_receive(:distinct).once.with(@model.active_space,
60
+ anything, an_instance_of(Gecode::Raw::IntVarArray), strength,
61
+ an_instance_of(Gecode::Raw::BoolVar))
62
+ end
63
+ end
64
+ end
65
+
66
+ it 'should translate into a distinct constraint with offsets' do
67
+ Gecode::Raw.should_receive(:distinct).once.with(@model.active_space,
68
+ anything, anything, Gecode::Raw::ICL_DEF)
69
+ @invoke_options.call({})
70
+ end
71
+
72
+ it 'should consider offsets when making variables distinct' do
73
+ @model.vars.with_offsets(-1,0).must_be.distinct
74
+ x,y = @model.solve!.vars
75
+ x.val.should equal(1)
76
+ y.val.should equal(1)
77
+ end
78
+
79
+ # This tests two distinct in conjunction. It's here because of a bug found.
80
+ it 'should play nice with normal distinct' do
81
+ @model.vars.with_offsets(-1,0).must_be.distinct
82
+ @model.vars.must_be.distinct
83
+ @model.solve!.should be_nil
84
+ end
85
+
86
+ it 'should accept an array as offsets' do
87
+ @model.vars.with_offsets([-1,0]).must_be.distinct
88
+ x,y = @model.solve!.vars
89
+ x.val.should equal(1)
90
+ y.val.should equal(1)
91
+ end
92
+
93
+ it 'should not allow negation' do
94
+ lambda{ @model.vars.with_offsets(1,2).must_not_be.distinct }.should
95
+ raise_error(Gecode::MissingConstraintError)
96
+ end
97
+
98
+ it_should_behave_like 'constraint with strength option'
99
+ end