gecoder 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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