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 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, an open, free and efficient environment
4
- for developing constraint-based systems and applications.
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 an early development stage, the syntax is by no means
9
- final and backwards compatibility will be broken time and time again until
10
- later in the development process. Don’t use Gecode/R in production-code yet,
11
- it’s merely available this early to allow people to play with it and give
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::Raw::IntSet.new(domain, domain.size)
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
- variable.must_be.in enum.domain_range
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
@@ -30,3 +30,5 @@ end
30
30
 
31
31
  require 'gecoder/interface/constraints/set_enum/channel'
32
32
  require 'gecoder/interface/constraints/set_enum/distinct'
33
+ require 'gecoder/interface/constraints/set_enum/selection'
34
+ require 'gecoder/interface/constraints/set_enum/operation'
@@ -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'