gecoder 0.2.0 → 0.3.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.
@@ -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