gecoder 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,66 @@
1
+ module Gecode
2
+ module IntEnumMethods
3
+ # Specifies offsets to be used with a distinct constraint. The offsets can
4
+ # be specified one by one or as an array of offsets.
5
+ def with_offsets(*offsets)
6
+ if offsets.kind_of? Enumerable
7
+ offsets = *offsets
8
+ end
9
+ params = {:lhs => self, :offsets => offsets}
10
+ return Gecode::Constraints::IntEnum::Distinct::OffsetExpression.new(
11
+ @model, params)
12
+ end
13
+ end
14
+ end
15
+
16
+ module Gecode::Constraints::IntEnum
17
+ class Expression
18
+ # Posts a distinct constraint on the variables in the enum.
19
+ def distinct(options = {})
20
+ if @params[:negate]
21
+ # The best we could implement it as from here would be a bunch of
22
+ # reified pairwise equality constraints.
23
+ raise Gecode::MissingConstraintError, 'A negated distinct is not ' +
24
+ 'implemented.'
25
+ end
26
+
27
+ @model.add_constraint Distinct::DistinctConstraint.new(@model,
28
+ @params.update(Gecode::Constraints::OptionUtil.decode_options(options)))
29
+ end
30
+ end
31
+
32
+ # A module that gathers the classes and modules used in distinct constraints.
33
+ module Distinct
34
+ # Describes an expression started with an int var enum following with
35
+ # #with_offsets .
36
+ class OffsetExpression < Gecode::Constraints::IntEnum::Expression
37
+ include Gecode::Constraints::LeftHandSideMethods
38
+
39
+ def initialize(model, params)
40
+ @model = model
41
+ @params = params
42
+ end
43
+
44
+ private
45
+
46
+ # Produces an expression with offsets for the lhs module.
47
+ def expression(params)
48
+ params.update(@params)
49
+ Gecode::Constraints::IntEnum::Expression.new(@model, params)
50
+ end
51
+ end
52
+
53
+ # Describes a distinct constraint (optionally with offsets).
54
+ class DistinctConstraint < Gecode::Constraints::Constraint
55
+ def post
56
+ # Bind lhs.
57
+ @params[:lhs] = @params[:lhs].to_int_var_array
58
+
59
+ # Fetch the parameters to Gecode.
60
+ params = @params.values_at(:offsets, :lhs, :strength)
61
+ params.delete_if{ |x| x.nil? }
62
+ Gecode::Raw::distinct(@model.active_space, *params)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,31 @@
1
+ module Gecode
2
+ module IntEnumMethods
3
+ include Gecode::Constraints::LeftHandSideMethods
4
+
5
+ private
6
+
7
+ # Produces an expression for the lhs module.
8
+ def expression(params)
9
+ params.update(:lhs => self)
10
+ Constraints::IntEnum::Expression.new(@model, params)
11
+ end
12
+ end
13
+
14
+ # A module containing constraints that have enumerations of integer
15
+ # variables as left hand side.
16
+ module Constraints::IntEnum
17
+ # Expressions with int enums as left hand sides.
18
+ class Expression < Gecode::Constraints::Expression
19
+ # Raises TypeError unless the left hand side is an int enum.
20
+ def initialize(model, params)
21
+ super
22
+
23
+ unless params[:lhs].respond_to? :to_int_var_array
24
+ raise TypeError, 'Must have int enum as left hand side.'
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ require 'gecoder/interface/constraints/int_enum/distinct'
@@ -0,0 +1,22 @@
1
+ module Gecode
2
+ class FreeIntVar
3
+ include Gecode::Constraints::LeftHandSideMethods
4
+
5
+ private
6
+
7
+ # Produces an expression for the lhs module.
8
+ def expression(params)
9
+ params.update(:lhs => self)
10
+ Constraints::Int::Expression.new(@model, params)
11
+ end
12
+ end
13
+
14
+ # A module containing constraints that have int variables as left hand side
15
+ # (but not enumerations).
16
+ module Constraints::Int
17
+ class Expression < Gecode::Constraints::Expression
18
+ end
19
+ end
20
+ end
21
+
22
+ require 'gecoder/interface/constraints/int/linear'
@@ -0,0 +1,57 @@
1
+ module Gecode::Constraints
2
+ # Base class for all reifiable constraints.
3
+ class ReifiableConstraint < Constraint
4
+ # Gets the reification variable of the constraint, nil if none exists.
5
+ def reification_var
6
+ @params[:reif]
7
+ end
8
+
9
+ # Sets the reification variable of the constraint, nil if none should be
10
+ # used.
11
+ def reification_var=(new_var)
12
+ @params[:reif] = new_var
13
+ end
14
+
15
+ # Produces a disjunction of two reifiable constraints, producing a new
16
+ # reifiable constraint.
17
+ def |(constraint)
18
+ with_reification_variables(constraint) do |b1, b2|
19
+ # Create the disjunction constraint.
20
+ (b1 | b2).must_be.true
21
+ end
22
+ end
23
+
24
+ # Produces a conjunction of two reifiable constraints, producing a new
25
+ # reifiable constraint.
26
+ def &(constraint)
27
+ with_reification_variables(constraint) do |b1, b2|
28
+ # Create the conjunction constraint.
29
+ (b1 & b2).must_be.true
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ # Yields two boolean variables to the specified block. The first one is
36
+ # self's reification variable and the second one is the reification variable
37
+ # of the specified constraint. Reuses reification variables if possible,
38
+ # otherwise creates new ones.
39
+ def with_reification_variables(constraint, &block)
40
+ raise TypeError unless constraint.kind_of? ReifiableConstraint
41
+
42
+ # Set up the reification variables, using existing variables if they
43
+ # exist.
44
+ con1_holds = self.reification_var
45
+ con2_holds = constraint.reification_var
46
+ if con1_holds.nil?
47
+ con1_holds = @model.bool_var
48
+ self.reification_var = con1_holds
49
+ end
50
+ if con2_holds.nil?
51
+ con2_holds = @model.bool_var
52
+ constraint.reification_var = con2_holds
53
+ end
54
+ yield(con1_holds, con2_holds)
55
+ end
56
+ end
57
+ end
@@ -8,32 +8,41 @@ module Gecode
8
8
  unless enum.kind_of? Enumerable
9
9
  raise TypeError, 'Only enumerables can be wrapped.'
10
10
  end
11
+ if enum.empty?
12
+ raise ArgumentError, 'Enumerable must not be empty.'
13
+ end
11
14
 
12
- class <<enum
13
- include Gecode::IntEnumConstraintMethods
15
+ if enum.first.kind_of? FreeIntVar
16
+ class <<enum
17
+ include Gecode::IntEnumMethods
18
+ end
19
+ elsif enum.first.kind_of? FreeBoolVar
20
+ class <<enum
21
+ include Gecode::BoolEnumMethods
22
+ end
23
+ else
24
+ raise TypeError, "Enumerable doesn't contain variables #{enum.inspect}."
14
25
  end
26
+
15
27
  enum.model = self
16
28
  return enum
17
29
  end
18
30
  end
19
31
 
32
+ # A module containing the methods needed by enumerations containing variables.
33
+ module EnumMethods
34
+ attr_accessor :model
35
+ # Gets the current space of the model the array is connected to.
36
+ def active_space
37
+ @model.active_space
38
+ end
39
+ end
40
+
20
41
  # A module containing the methods needed by enumerations containing int
21
42
  # variables. Requires that it's included in an enumerable.
22
- module IntEnumConstraintMethods
23
- # Specifies that a constraint must hold for the integer variable enum.
24
- def must
25
- IntVarEnumConstraintExpression.new(active_space, to_int_var_array)
26
- end
27
- alias_method :must_be, :must
28
-
29
- # Specifies that the negation of a constraint must hold for the integer
30
- # variable.
31
- def must_not
32
- IntVarEnumConstraintExpression.new(active_space, to_int_var_array,
33
- true)
34
- end
35
- alias_method :must_not_be, :must_not
36
-
43
+ module IntEnumMethods
44
+ include EnumMethods
45
+
37
46
  # Returns an int variable array with all the bound variables.
38
47
  def to_int_var_array
39
48
  elements = to_a
@@ -41,24 +50,21 @@ module Gecode
41
50
  elements.each_with_index{ |var, index| arr[index] = var.bind }
42
51
  return arr
43
52
  end
44
-
45
- attr_accessor :model
46
- # Gets the current space of the model the array is connected to.
47
- def active_space
48
- @model.active_space
49
- end
53
+ alias_method :to_var_array, :to_int_var_array
50
54
  end
51
55
 
52
- # Describes a constraint expression that starts with an enumeration of int
53
- # variables followed by must or must_not.
54
- class IntVarEnumConstraintExpression
55
- # Constructs a new expression with the specified space and int var array
56
- # with the (bound) variables as source. The expression can optionally be
57
- # negated.
58
- def initialize(space, var_array, negate = false)
59
- @space = space
60
- @var_array = var_array
61
- @negate = negate
56
+ # A module containing the methods needed by enumerations containing boolean
57
+ # variables. Requires that it's included in an enumerable.
58
+ module BoolEnumMethods
59
+ include EnumMethods
60
+
61
+ # Returns a bool variable array with all the bound variables.
62
+ def to_bool_var_array
63
+ elements = to_a
64
+ arr = Gecode::Raw::BoolVarArray.new(active_space, elements.size)
65
+ elements.each_with_index{ |var, index| arr[index] = var.bind }
66
+ return arr
62
67
  end
68
+ alias_method :to_var_array, :to_bool_var_array
63
69
  end
64
70
  end
@@ -1,21 +1,9 @@
1
1
  module Gecode
2
- # Model is the base class that all models must inherit from. The superclass
3
- # constructor must be called.
2
+ # Model is the base class that all models must inherit from.
4
3
  class Model
5
- # Design notes: Only one model per problem is used. A model has multiple
6
- # spaces. A model has a base space in which it sets up during the
7
- # initialization. The model binds the int variables to the current space
8
- # upon use.
4
+ attr :constraints
5
+ protected :constraints
9
6
 
10
- # The base from which searches are made.
11
- attr :base_space
12
- # The currently active space (the one which variables refer to).
13
- attr :active_space
14
-
15
- def initialize
16
- @active_space = @base_space = Gecode::Raw::Space.new
17
- end
18
-
19
7
  # Creates a new integer variable with the specified domain. The domain can
20
8
  # either be a range or a number of elements.
21
9
  def int_var(*domain_args)
@@ -38,6 +26,44 @@ module Gecode
38
26
  return wrap_enum(variables)
39
27
  end
40
28
 
29
+ # Creates a new boolean variable.
30
+ def bool_var(*domain_args)
31
+ index = active_space.new_bool_vars.first
32
+ FreeBoolVar.new(self, index)
33
+ end
34
+
35
+ # Creates an array containing the specified number of boolean variables.
36
+ def bool_var_array(count)
37
+ variables = []
38
+ active_space.new_bool_vars(count).each do |index|
39
+ variables << FreeBoolVar.new(self, index)
40
+ end
41
+ return wrap_enum(variables)
42
+ end
43
+
44
+ # Retrieves the currently active space (the one which variables refer to).
45
+ def active_space
46
+ @active_space ||= base_space
47
+ end
48
+
49
+ # Retrieves the base from which searches are made.
50
+ def base_space
51
+ @base_space ||= Gecode::Raw::Space.new
52
+ end
53
+
54
+ # Adds the specified constraint to the model. Returns the newly added
55
+ # constraint.
56
+ def add_constraint(constraint)
57
+ constraints << constraint
58
+ return constraint
59
+ end
60
+
61
+ protected
62
+
63
+ def constraints
64
+ @constraints ||= []
65
+ end
66
+
41
67
  private
42
68
 
43
69
  # Returns the range of the specified domain arguments, which can either be
@@ -52,9 +78,11 @@ module Gecode
52
78
  max = domain_args.max
53
79
  else
54
80
  element = domain_args.first
55
- if element.respond_to? :begin and element.respond_to? :end
81
+ if element.respond_to?(:begin) and element.respond_to?(:end) and
82
+ element.respond_to?(:exclude_end?)
56
83
  min = element.begin
57
84
  max = element.end
85
+ max -= 1 if element.exclude_end?
58
86
  else
59
87
  min = max = element
60
88
  end
@@ -79,52 +107,4 @@ module Gecode
79
107
  return var
80
108
  end
81
109
  end
82
-
83
- # An IntVar that is bound to a model, but not to a particular space.
84
- class FreeIntVar
85
- attr_accessor :model
86
-
87
- # Creates an int variable with the specified index.
88
- def initialize(model, index)
89
- @model = model
90
- @index = index
91
- @bound_space = @bound_var = nil
92
- end
93
-
94
- # Delegate methods we can't handle to the bound int variable if possible.
95
- def method_missing(name, *args)
96
- if Gecode::Raw::IntVar.instance_methods.include? name.to_s
97
- bind.send(name, *args)
98
- else
99
- super
100
- end
101
- end
102
-
103
- # Binds the int variable to the currently active space of the model,
104
- # returning the bound int variable.
105
- def bind
106
- space = active_space
107
- unless @bound_space == space
108
- # We have not bound the variable to this space, so we do it now.
109
- @bound = space.int_var(@index)
110
- @bound_space = space
111
- end
112
- return @bound
113
- end
114
-
115
- def inspect
116
- if assigned?
117
- "#<FreeIntVar range: val.to_s>"
118
- else
119
- "#<FreeIntVar range: #{min}..#{max}>"
120
- end
121
- end
122
-
123
- private
124
-
125
- # Returns the space that the int variable should bind to when needed.
126
- def active_space
127
- @model.active_space
128
- end
129
- end
130
110
  end
@@ -12,12 +12,49 @@ module Gecode
12
12
  # to that solution. Returns the model if a solution was found, nil
13
13
  # otherwise.
14
14
  def solve!
15
- stop = Gecode::Raw::Search::Stop.new
16
- dfs = Gecode::Raw::DFS.new(@base_space, COPY_DIST, ADAPTATION_DIST, stop)
17
- space = dfs.next
15
+ space = dfs_engine.next
18
16
  return nil if space.nil?
19
17
  @active_space = space
20
18
  return self
21
19
  end
20
+
21
+ # Returns to the original state, before any search was made (but propagation
22
+ # might have been performed). Returns the reset model.
23
+ def reset!
24
+ @active_space = base_space
25
+ return self
26
+ end
27
+
28
+ # Yields the first solution (if any) to the block. If no solution is found
29
+ # then the block is not used. Returns the result of the block (nil in case
30
+ # the block wasn't run).
31
+ def solution(&block)
32
+ solution = self.solve!
33
+ res = yield solution unless solution.nil?
34
+ self.reset!
35
+ return res
36
+ end
37
+
38
+ # Yields each solution that the model has.
39
+ def each_solution(&block)
40
+ dfs = dfs_engine
41
+ while not (@active_space = dfs.next).nil?
42
+ yield self
43
+ end
44
+ self.reset!
45
+ end
46
+
47
+ private
48
+
49
+ # Creates an DFS engine for the search, executing any unexecuted
50
+ # constraints first.
51
+ def dfs_engine
52
+ # Execute constraints.
53
+ constraints.each{ |con| con.post }
54
+ constraints.clear # Empty the queue.
55
+
56
+ stop = Gecode::Raw::Search::Stop.new
57
+ Gecode::Raw::DFS.new(active_space, COPY_DIST, ADAPTATION_DIST, stop)
58
+ end
22
59
  end
23
60
  end