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 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'