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