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.
- data/CHANGES +21 -0
- data/README +9 -1
- data/ext/missing.cpp +1 -1
- data/ext/vararray.cpp +4 -0
- data/ext/vararray.h +2 -1
- data/lib/gecoder/bindings/bindings.rb +55 -5
- data/lib/gecoder/interface.rb +1 -0
- data/lib/gecoder/interface/binding_changes.rb +183 -81
- data/lib/gecoder/interface/branch.rb +1 -1
- data/lib/gecoder/interface/constraints.rb +121 -5
- data/lib/gecoder/interface/constraints/bool/boolean.rb +160 -0
- data/lib/gecoder/interface/constraints/bool_var_constraints.rb +23 -0
- data/lib/gecoder/interface/constraints/int/linear.rb +258 -0
- data/lib/gecoder/interface/constraints/int_enum/distinct.rb +66 -0
- data/lib/gecoder/interface/constraints/int_enum_constraints.rb +31 -0
- data/lib/gecoder/interface/constraints/int_var_constraints.rb +22 -0
- data/lib/gecoder/interface/constraints/reifiable_constraints.rb +57 -0
- data/lib/gecoder/interface/enum_wrapper.rb +39 -33
- data/lib/gecoder/interface/model.rb +44 -64
- data/lib/gecoder/interface/search.rb +40 -3
- data/lib/gecoder/interface/variables.rb +62 -0
- metadata +19 -8
- data/lib/gecoder/interface/constraints/distinct.rb +0 -15
- data/lib/gecoder/interface/constraints/linear.rb +0 -158
- data/lib/gecoder/interface/constraints/relation.rb +0 -76
@@ -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
|
-
|
13
|
-
|
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
|
23
|
-
|
24
|
-
|
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
|
-
#
|
53
|
-
# variables
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
#
|
58
|
-
def
|
59
|
-
|
60
|
-
|
61
|
-
|
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.
|
3
|
-
# constructor must be called.
|
2
|
+
# Model is the base class that all models must inherit from.
|
4
3
|
class Model
|
5
|
-
|
6
|
-
|
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?
|
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
|
-
|
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
|