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.
- data/CHANGES +7 -0
- data/README +7 -7
- data/lib/gecoder/interface/constraints.rb +45 -3
- data/lib/gecoder/interface/constraints/int/domain.rb +1 -2
- data/lib/gecoder/interface/constraints/int_enum/element.rb +4 -2
- data/lib/gecoder/interface/constraints/set/operation.rb +101 -0
- data/lib/gecoder/interface/constraints/set_enum/operation.rb +36 -0
- data/lib/gecoder/interface/constraints/set_enum/selection.rb +143 -0
- data/lib/gecoder/interface/constraints/set_enum_constraints.rb +2 -0
- data/lib/gecoder/interface/constraints/set_var_constraints.rb +30 -0
- data/lib/gecoder/interface/enum_wrapper.rb +16 -1
- data/lib/gecoder/interface/model.rb +10 -6
- data/lib/gecoder/interface/search.rb +0 -6
- data/lib/gecoder/version.rb +1 -1
- data/specs/constraints/constraint_helper.rb +45 -4
- data/specs/constraints/constraints.rb +6 -0
- data/specs/constraints/selection.rb +292 -0
- data/specs/constraints/set_operation.rb +285 -0
- data/specs/enum_wrapper.rb +5 -0
- data/specs/model.rb +7 -0
- data/vendor/rust/configure.rb +6 -0
- data/vendor/rust/out.rb +627 -0
- metadata +14 -2
data/CHANGES
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
== Version 0.7.0
|
2
|
+
This release adds the set selection and operation constraints.
|
3
|
+
|
4
|
+
* Added set selection constraints (set array access, select union, select intersection (optionally with specified universe) and select disjoint).
|
5
|
+
* Added set operation constraints (union, minus, disjoint_union and intersection) on pairs of set variables or set constants with variable or constant right hand side.
|
6
|
+
* Added set operation constraints on enumerations of sets.
|
7
|
+
|
1
8
|
== Version 0.6.1
|
2
9
|
This release fixes various bugs introduced in 0.6.0 and changes the way that
|
3
10
|
int variable domains are specified (breaking backward-compatibility).
|
data/README
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
== Gecode/R
|
2
2
|
|
3
|
-
Gecode/R is a Ruby interface to Gecode
|
4
|
-
for
|
3
|
+
Gecode/R is a Ruby interface to the Gecode constraint programming library.
|
4
|
+
Gecode/R is intended for people with no previous experience of constraint
|
5
|
+
programming, aiming to be easy to pick up and use.
|
5
6
|
|
6
7
|
== Warning
|
7
8
|
|
8
|
-
Gecode/R is still in
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
feedback.
|
9
|
+
Gecode/R is still in a development stage, the syntax is by no means final
|
10
|
+
and backwards compatibility will be broken time and time again. Don't use
|
11
|
+
Gecode/R in production-code yet, it's merely available at this point to allow
|
12
|
+
people to play with it and give feedback.
|
13
13
|
|
14
14
|
== Installation
|
15
15
|
|
@@ -75,6 +75,14 @@ module Gecode
|
|
75
75
|
NEGATED_SET_RELATION_TYPES = {
|
76
76
|
:== => Gecode::Raw::SRT_NQ
|
77
77
|
}
|
78
|
+
# Maps the names of the methods to the corresponding set operation type in
|
79
|
+
# Gecode.
|
80
|
+
SET_OPERATION_TYPES = {
|
81
|
+
:union => Gecode::Raw::SOT_UNION,
|
82
|
+
:disjoint_union => Gecode::Raw::SOT_DUNION,
|
83
|
+
:intersection => Gecode::Raw::SOT_INTER,
|
84
|
+
:minus => Gecode::Raw::SOT_MINUS
|
85
|
+
}
|
78
86
|
|
79
87
|
# Various method aliases for comparison methods. Maps the original
|
80
88
|
# (symbol) name to an array of aliases.
|
@@ -134,18 +142,52 @@ module Gecode
|
|
134
142
|
# other enumerations.
|
135
143
|
# * Enumeration of integers (set contaning all numbers in set).
|
136
144
|
def constant_set_to_params(constant_set)
|
145
|
+
unless constant_set?(constant_set)
|
146
|
+
raise TypeError, "Expected a constant set, got: #{constant_set}."
|
147
|
+
end
|
148
|
+
|
137
149
|
if constant_set.kind_of? Range
|
138
150
|
return constant_set.first, constant_set.last
|
139
151
|
elsif constant_set.kind_of? Fixnum
|
140
152
|
return constant_set
|
141
153
|
else
|
142
154
|
constant_set = constant_set.to_a
|
143
|
-
unless constant_set.all?{ |e| e.kind_of? Fixnum }
|
144
|
-
raise TypeError, "Not a constant set: #{constant_set}."
|
145
|
-
end
|
146
155
|
return Gecode::Raw::IntSet.new(constant_set, constant_set.size)
|
147
156
|
end
|
148
157
|
end
|
158
|
+
|
159
|
+
# Converts the different ways to specify constant sets in the interface
|
160
|
+
# to an instance of Gecode::Raw::IntSet. The different forms accepted are:
|
161
|
+
# * Single instance of Fixnum (singleton set).
|
162
|
+
# * Range (set containing all numbers in range), treated differently from
|
163
|
+
# other enumerations.
|
164
|
+
# * Enumeration of integers (set contaning all numbers in set).
|
165
|
+
def constant_set_to_int_set(constant_set)
|
166
|
+
unless constant_set?(constant_set)
|
167
|
+
raise TypeError, "Expected a constant set, got: #{constant_set}."
|
168
|
+
end
|
169
|
+
|
170
|
+
if constant_set.kind_of? Range
|
171
|
+
return Gecode::Raw::IntSet.new(constant_set.first, constant_set.last)
|
172
|
+
elsif constant_set.kind_of? Fixnum
|
173
|
+
return Gecode::Raw::IntSet.new([constant_set], 1)
|
174
|
+
else
|
175
|
+
constant_set = constant_set.to_a
|
176
|
+
return Gecode::Raw::IntSet.new(constant_set, constant_set.size)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Checks whether the specified expression is regarded as a constant set.
|
181
|
+
# Returns true if it is, false otherwise.
|
182
|
+
def constant_set?(expression)
|
183
|
+
return (
|
184
|
+
expression.kind_of?(Range) && # It's a range.
|
185
|
+
expression.first.kind_of?(Fixnum) &&
|
186
|
+
expression.last.kind_of?(Fixnum)) ||
|
187
|
+
expression.kind_of?(Fixnum) || # It's a single fixnum.
|
188
|
+
(expression.kind_of?(Enumerable) && # It's an enum of fixnums.
|
189
|
+
expression.all?{ |e| e.kind_of? Fixnum })
|
190
|
+
end
|
149
191
|
end
|
150
192
|
|
151
193
|
# Describes a constraint expressions. An expression is produced by calling
|
@@ -38,10 +38,9 @@ module Gecode::Constraints::Int
|
|
38
38
|
|
39
39
|
var, domain, reif_var, strength = @params.values_at(:lhs, :domain,
|
40
40
|
:reif, :strength)
|
41
|
-
domain = domain.to_a
|
42
41
|
|
43
42
|
(params = []) << var.bind
|
44
|
-
params << Gecode::
|
43
|
+
params << Gecode::Constraints::Util.constant_set_to_int_set(domain)
|
45
44
|
params << reif_var.bind if reif_var.respond_to? :bind
|
46
45
|
params << strength
|
47
46
|
Gecode::Raw::dom(space, *params)
|
@@ -5,8 +5,10 @@ module Gecode::Constraints::IntEnum::Element
|
|
5
5
|
class ExpressionStub < Gecode::Constraints::Int::CompositeStub
|
6
6
|
def constrain_equal(variable, params, constrain)
|
7
7
|
enum, position, strength = @params.values_at(:lhs, :position, :strength)
|
8
|
-
|
9
|
-
|
8
|
+
if constrain
|
9
|
+
variable.must_be.in enum.domain_range
|
10
|
+
end
|
11
|
+
|
10
12
|
# The enum can be a constant array.
|
11
13
|
enum = enum.to_int_var_array if enum.respond_to? :to_int_var_array
|
12
14
|
Gecode::Raw::element(@model.active_space, enum,
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Gecode
|
2
|
+
class FreeSetVar
|
3
|
+
Gecode::Constraints::Util::SET_OPERATION_TYPES.each_pair do |name, type|
|
4
|
+
module_eval <<-"end_code"
|
5
|
+
# Starts a constraint on this set #{name} the specified set.
|
6
|
+
def #{name}(operand)
|
7
|
+
unless operand.kind_of?(Gecode::FreeSetVar) or
|
8
|
+
Gecode::Constraints::Util::constant_set?(operand)
|
9
|
+
raise TypeError, 'Expected set variable or constant set as ' +
|
10
|
+
"operand, got \#{operand.class}."
|
11
|
+
end
|
12
|
+
|
13
|
+
params = {:lhs => self, :op2 => operand, :operation => #{type}}
|
14
|
+
Gecode::Constraints::SimpleExpressionStub.new(@model, params) do |m, ps|
|
15
|
+
Gecode::Constraints::Set::Operation::Expression.new(m, ps)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end_code
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module FixnumEnumMethods
|
23
|
+
Gecode::Constraints::Util::SET_OPERATION_TYPES.each_pair do |name, type|
|
24
|
+
module_eval <<-"end_code"
|
25
|
+
# Starts a constraint on this set union the specified set.
|
26
|
+
def #{name}(operand)
|
27
|
+
unless operand.kind_of?(Gecode::FreeSetVar) or
|
28
|
+
Gecode::Constraints::Util::constant_set?(operand)
|
29
|
+
raise TypeError, 'Expected set variable or constant set as ' +
|
30
|
+
"operand, got \#{operand.class}."
|
31
|
+
end
|
32
|
+
|
33
|
+
params = {:lhs => self, :op2 => operand, :operation => #{type}}
|
34
|
+
Gecode::Constraints::SimpleExpressionStub.new(@model, params) do |m, ps|
|
35
|
+
Gecode::Constraints::Set::Operation::Expression.new(m, ps)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end_code
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module Gecode::Constraints::Set
|
44
|
+
# A module that gathers the classes and modules used in operation constraints.
|
45
|
+
module Operation
|
46
|
+
# An expression with a set operand and two operands followed by must.
|
47
|
+
class Expression < Gecode::Constraints::Expression
|
48
|
+
Gecode::Constraints::Util::SET_RELATION_TYPES.each_pair do |name, type|
|
49
|
+
module_eval <<-"end_code"
|
50
|
+
# Creates an operation constraint using the specified expression.
|
51
|
+
def #{name}(expression)
|
52
|
+
if @params[:negate]
|
53
|
+
# We do not allow negation.
|
54
|
+
raise Gecode::MissingConstraintError, 'A negated set operation ' +
|
55
|
+
'constraint is not implemented.'
|
56
|
+
end
|
57
|
+
unless expression.kind_of?(Gecode::FreeSetVar) or
|
58
|
+
Gecode::Constraints::Util::constant_set?(expression)
|
59
|
+
raise TypeError, 'Expected set variable or constant set, got ' +
|
60
|
+
"\#{expression.class}."
|
61
|
+
end
|
62
|
+
|
63
|
+
@params[:rhs] = expression
|
64
|
+
@params[:relation] = #{type}
|
65
|
+
unless @params.values_at(:lhs, :op2, :rhs).any?{ |element|
|
66
|
+
element.kind_of? Gecode::FreeSetVar}
|
67
|
+
# At least one variable must be involved in the constraint.
|
68
|
+
raise ArgumentError, 'At least one variable must be involved ' +
|
69
|
+
'in the constraint, but all given were constants.'
|
70
|
+
end
|
71
|
+
|
72
|
+
@model.add_constraint OperationConstraint.new(@model, @params)
|
73
|
+
end
|
74
|
+
end_code
|
75
|
+
end
|
76
|
+
alias_set_methods
|
77
|
+
end
|
78
|
+
|
79
|
+
# Describes a constraint involving a set operator operating on two set
|
80
|
+
# operands.
|
81
|
+
class OperationConstraint < Gecode::Constraints::Constraint
|
82
|
+
def post
|
83
|
+
op1, op2, operation, relation, rhs, negate = @params.values_at(:lhs,
|
84
|
+
:op2, :operation, :relation, :rhs, :negate)
|
85
|
+
|
86
|
+
op1, op2, rhs = [op1, op2, rhs].map do |expression|
|
87
|
+
# The expressions can either be set variables or constant sets,
|
88
|
+
# convert them appropriately.
|
89
|
+
if expression.respond_to? :bind
|
90
|
+
expression.bind
|
91
|
+
else
|
92
|
+
Gecode::Constraints::Util::constant_set_to_int_set(expression)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
Gecode::Raw::rel(@model.active_space, op1, operation, op2, relation,
|
97
|
+
rhs)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Gecode::SetEnumMethods
|
2
|
+
Gecode::Constraints::Util::SET_OPERATION_TYPES.each_pair do |name, type|
|
3
|
+
next if type == Gecode::Raw::SOT_MINUS # Does not support this constraint?
|
4
|
+
|
5
|
+
module_eval <<-"end_code"
|
6
|
+
# Starts a constraint on the #{name} of the sets.
|
7
|
+
def #{name}
|
8
|
+
params = {:lhs => self, :operation => #{type}}
|
9
|
+
Gecode::Constraints::SetEnum::Operation::ExpressionStub.new(
|
10
|
+
@model, params)
|
11
|
+
end
|
12
|
+
end_code
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# A module that gathers the classes and modules used by operation constaints.
|
17
|
+
module Gecode::Constraints::SetEnum::Operation
|
18
|
+
# Describes a stub started with a set enumeration followed by a set operation.
|
19
|
+
class ExpressionStub < Gecode::Constraints::Set::CompositeStub
|
20
|
+
def constrain_equal(variable, params, constrain)
|
21
|
+
enum, operation = @params.values_at(:lhs, :operation)
|
22
|
+
|
23
|
+
if constrain
|
24
|
+
if operation == Gecode::Raw::SOT_INTER or
|
25
|
+
operation == Gecode::Raw::SOT_MINUS
|
26
|
+
variable.must_be.subset_of enum.first.upper_bound
|
27
|
+
else
|
28
|
+
variable.must_be.subset_of enum.upper_bound_range
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
Gecode::Raw::rel(@model.active_space, operation, enum.to_set_var_array,
|
33
|
+
variable.bind)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module Gecode::SetEnumMethods
|
2
|
+
# This adds the adder for the methods in the modules including it. The
|
3
|
+
# reason for doing it so indirect is that the first #[] won't be defined
|
4
|
+
# before the module that this is mixed into is mixed into an enum.
|
5
|
+
def self.included(mod)
|
6
|
+
mod.module_eval do
|
7
|
+
# Now we enter the module that the module possibly defining #[]
|
8
|
+
# is mixed into.
|
9
|
+
if instance_methods.include?('[]') and
|
10
|
+
not instance_methods.include?('pre_selection_access')
|
11
|
+
alias_method :pre_selection_access, :[]
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](*vars)
|
15
|
+
# Hook in an element constraint if a variable is used for array
|
16
|
+
# access.
|
17
|
+
if vars.first.kind_of? Gecode::FreeIntVar
|
18
|
+
params = {:lhs => self, :index => vars.first}
|
19
|
+
Gecode::Constraints::SetEnum::Selection::SelectExpressionStub.new(
|
20
|
+
@model, params)
|
21
|
+
elsif vars.first.kind_of? Gecode::FreeSetVar
|
22
|
+
params = {:lhs => self, :indices => vars.first}
|
23
|
+
Gecode::Constraints::SetEnum::Selection::SetAccessStub.new(
|
24
|
+
@model, params)
|
25
|
+
else
|
26
|
+
pre_selection_access(*vars) if respond_to? :pre_selection_access
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# A module that gathers the classes and modules used by selection constraints.
|
34
|
+
module Gecode::Constraints::SetEnum::Selection
|
35
|
+
# Describes an expression stub started with a set var enum following with an
|
36
|
+
# array access using an integer variable.
|
37
|
+
class SelectExpressionStub < Gecode::Constraints::Set::CompositeStub
|
38
|
+
def constrain_equal(variable, params, constrain)
|
39
|
+
enum, index = @params.values_at(:lhs, :index)
|
40
|
+
if constrain
|
41
|
+
variable.must_be.subset_of enum.upper_bound_range
|
42
|
+
end
|
43
|
+
|
44
|
+
Gecode::Raw::selectSet(@model.active_space, enum.to_set_var_array,
|
45
|
+
index.bind, variable.bind)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Describes an expression stub started with a set var enum followed with an
|
50
|
+
# array access using a set variable.
|
51
|
+
class SetAccessStub < Gecode::Constraints::ExpressionStub
|
52
|
+
include Gecode::Constraints::LeftHandSideMethods
|
53
|
+
|
54
|
+
# Starts a union selection constraint on the selected sets.
|
55
|
+
def union
|
56
|
+
UnionExpressionStub.new(@model, @params)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Starts a intersection selection constraint on the selected sets. The
|
60
|
+
# option :with may optionally be specified, in which case the value should
|
61
|
+
# be an enumeration of the elements in the universe.
|
62
|
+
def intersection(options = {})
|
63
|
+
unless options.empty?
|
64
|
+
unless options.size == 1 and options.has_key?(:with)
|
65
|
+
raise ArgumentError, "Expected option key :with, got #{options.keys}."
|
66
|
+
else
|
67
|
+
universe = options[:with]
|
68
|
+
unless universe.kind_of?(Enumerable) and
|
69
|
+
universe.all?{ |element| element.kind_of? Fixnum }
|
70
|
+
raise TypeError, "Expected the universe to be specified as " +
|
71
|
+
"an enumeration of fixnum, got #{universe.class}."
|
72
|
+
end
|
73
|
+
@params.update(:universe => universe)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
IntersectionExpressionStub.new(@model, @params)
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
# Produces an expression with position for the lhs module.
|
83
|
+
def expression(params)
|
84
|
+
SetAccessExpression.new(@model, @params.update(params))
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Describes an expression stub started with a set var enum following with an
|
89
|
+
# array access using a set variable followed by #union.
|
90
|
+
class UnionExpressionStub < Gecode::Constraints::Set::CompositeStub
|
91
|
+
def constrain_equal(variable, params, constrain)
|
92
|
+
enum, indices = @params.values_at(:lhs, :indices)
|
93
|
+
if constrain
|
94
|
+
variable.must_be.subset_of enum.upper_bound_range
|
95
|
+
end
|
96
|
+
|
97
|
+
Gecode::Raw::selectUnion(@model.active_space, enum.to_set_var_array,
|
98
|
+
indices.bind, variable.bind)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Describes an expression stub started with a set var enum following with an
|
103
|
+
# array access using a set variable followed by #intersection.
|
104
|
+
class IntersectionExpressionStub < Gecode::Constraints::Set::CompositeStub
|
105
|
+
def constrain_equal(variable, params, constrain)
|
106
|
+
enum, indices, universe = @params.values_at(:lhs, :indices, :universe)
|
107
|
+
# We can't do any useful constraining here since the empty intersection
|
108
|
+
# is the universe.
|
109
|
+
|
110
|
+
if universe.nil?
|
111
|
+
Gecode::Raw::selectInter(@model.active_space, enum.to_set_var_array,
|
112
|
+
indices.bind, variable.bind)
|
113
|
+
else
|
114
|
+
Gecode::Raw::selectInterIn(@model.active_space, enum.to_set_var_array,
|
115
|
+
indices.bind, variable.bind,
|
116
|
+
Gecode::Constraints::Util.constant_set_to_int_set(universe))
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Describes an expression that starts with an set variable enum followed with
|
122
|
+
# an array access using a set variable followed by some form of must.
|
123
|
+
class SetAccessExpression < Gecode::Constraints::Set::Expression
|
124
|
+
# Constrains the selected sets to be disjoint.
|
125
|
+
def disjoint
|
126
|
+
if @params[:negate]
|
127
|
+
raise Gecode::MissingConstraintError, 'A negated set selection ' +
|
128
|
+
'disjoint is not implemented.'
|
129
|
+
end
|
130
|
+
|
131
|
+
@model.add_constraint DisjointConstraint.new(@model, @params)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Describes a disjoint constraint produced by sets[set].must_be.disjoint .
|
136
|
+
class DisjointConstraint < Gecode::Constraints::Constraint
|
137
|
+
def post
|
138
|
+
enum, indices = @params.values_at(:lhs, :indices)
|
139
|
+
Gecode::Raw.selectDisjoint(@model.active_space, enum.to_set_var_array,
|
140
|
+
indices.bind)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -29,6 +29,35 @@ module Gecode
|
|
29
29
|
Gecode::Constraints::Util.decode_options(options)
|
30
30
|
end
|
31
31
|
end
|
32
|
+
|
33
|
+
# A composite expression which is an set expression with a left hand side
|
34
|
+
# resulting from a previous constraint.
|
35
|
+
class CompositeExpression < Gecode::Constraints::CompositeExpression
|
36
|
+
# The block given should take three parameters. The first is the variable
|
37
|
+
# that should be the left hand side, if it's nil then a new one should be
|
38
|
+
# created. The second is the has of parameters. The block should return
|
39
|
+
# the variable used as left hand side.
|
40
|
+
def initialize(model, params, &block)
|
41
|
+
super(Expression, Gecode::FreeSetVar, lambda{ model.set_var }, model,
|
42
|
+
params, &block)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Describes a stub that produces a set variable, which can then be used with
|
47
|
+
# the normal set variable constraints. An example of a set composite
|
48
|
+
# constraints would be set selection.
|
49
|
+
#
|
50
|
+
# sets[int_var].must_be.subset_of(another_set)
|
51
|
+
#
|
52
|
+
# "sets[int_var]" produces a bool variable which the constraint
|
53
|
+
# ".must_be.subset_of(another_set)" is then applied to.In the above case
|
54
|
+
# two constraints (and one temporary variable) are required, but in the
|
55
|
+
# case of equality only one constraint is required.
|
56
|
+
class CompositeStub < Gecode::Constraints::CompositeStub
|
57
|
+
def initialize(model, params)
|
58
|
+
super(CompositeExpression, model, params)
|
59
|
+
end
|
60
|
+
end
|
32
61
|
end
|
33
62
|
end
|
34
63
|
|
@@ -36,3 +65,4 @@ require 'gecoder/interface/constraints/set/domain'
|
|
36
65
|
require 'gecoder/interface/constraints/set/relation'
|
37
66
|
require 'gecoder/interface/constraints/set/cardinality'
|
38
67
|
require 'gecoder/interface/constraints/set/connection'
|
68
|
+
require 'gecoder/interface/constraints/set/operation'
|