ConstraintSolver 0.1
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/bin/ConstraintSolver +24 -0
- data/doc/classes/Array.html +209 -0
- data/doc/classes/ConstraintSolver.html +242 -0
- data/doc/classes/ConstraintSolver/AbstractConstraint.html +317 -0
- data/doc/classes/ConstraintSolver/AllDifferentConstraint.html +451 -0
- data/doc/classes/ConstraintSolver/AllDifferentConstraintTest.html +397 -0
- data/doc/classes/ConstraintSolver/BinaryConstraint.html +483 -0
- data/doc/classes/ConstraintSolver/BinaryConstraintTest.html +367 -0
- data/doc/classes/ConstraintSolver/BinaryRelation.html +276 -0
- data/doc/classes/ConstraintSolver/BinaryRelationTest.html +194 -0
- data/doc/classes/ConstraintSolver/ConstraintList.html +208 -0
- data/doc/classes/ConstraintSolver/ConstraintListTest.html +252 -0
- data/doc/classes/ConstraintSolver/ConstraintSolver.html +353 -0
- data/doc/classes/ConstraintSolver/ConstraintSolverTest.html +403 -0
- data/doc/classes/ConstraintSolver/Domain.html +522 -0
- data/doc/classes/ConstraintSolver/DomainTest.html +356 -0
- data/doc/classes/ConstraintSolver/DomainWipeoutException.html +158 -0
- data/doc/classes/ConstraintSolver/Problem.html +239 -0
- data/doc/classes/ConstraintSolver/ProblemTest.html +227 -0
- data/doc/classes/ConstraintSolver/Solution.html +342 -0
- data/doc/classes/ConstraintSolver/SolutionTest.html +250 -0
- data/doc/classes/ConstraintSolver/UndoStackEmptyException.html +158 -0
- data/doc/classes/ConstraintSolver/Variable.html +418 -0
- data/doc/classes/ConstraintSolver/VariableTest.html +284 -0
- data/doc/classes/ExtensionsTest.html +233 -0
- data/doc/classes/Fixnum.html +153 -0
- data/doc/created.rid +1 -0
- data/doc/dot/f_0.dot +38 -0
- data/doc/dot/f_0.png +0 -0
- data/doc/dot/f_1.dot +392 -0
- data/doc/dot/f_1.png +0 -0
- data/doc/dot/f_10.dot +392 -0
- data/doc/dot/f_10.png +0 -0
- data/doc/dot/f_11.dot +38 -0
- data/doc/dot/f_11.png +0 -0
- data/doc/dot/f_12.dot +392 -0
- data/doc/dot/f_12.png +0 -0
- data/doc/dot/f_13.dot +392 -0
- data/doc/dot/f_13.png +0 -0
- data/doc/dot/f_14.dot +392 -0
- data/doc/dot/f_14.png +0 -0
- data/doc/dot/f_15.dot +392 -0
- data/doc/dot/f_15.png +0 -0
- data/doc/dot/f_16.dot +392 -0
- data/doc/dot/f_16.png +0 -0
- data/doc/dot/f_17.dot +392 -0
- data/doc/dot/f_17.png +0 -0
- data/doc/dot/f_18.dot +392 -0
- data/doc/dot/f_18.png +0 -0
- data/doc/dot/f_19.dot +392 -0
- data/doc/dot/f_19.png +0 -0
- data/doc/dot/f_2.dot +392 -0
- data/doc/dot/f_2.png +0 -0
- data/doc/dot/f_3.dot +392 -0
- data/doc/dot/f_3.png +0 -0
- data/doc/dot/f_4.dot +392 -0
- data/doc/dot/f_4.png +0 -0
- data/doc/dot/f_5.dot +392 -0
- data/doc/dot/f_5.png +0 -0
- data/doc/dot/f_6.dot +14 -0
- data/doc/dot/f_6.png +0 -0
- data/doc/dot/f_7.dot +392 -0
- data/doc/dot/f_7.png +0 -0
- data/doc/dot/f_8.dot +392 -0
- data/doc/dot/f_8.png +0 -0
- data/doc/dot/f_9.dot +392 -0
- data/doc/dot/f_9.png +0 -0
- data/doc/dot/m_10_0.dot +392 -0
- data/doc/dot/m_10_0.png +0 -0
- data/doc/dot/m_12_0.dot +392 -0
- data/doc/dot/m_12_0.png +0 -0
- data/doc/dot/m_13_0.dot +392 -0
- data/doc/dot/m_13_0.png +0 -0
- data/doc/dot/m_14_0.dot +392 -0
- data/doc/dot/m_14_0.png +0 -0
- data/doc/dot/m_15_0.dot +392 -0
- data/doc/dot/m_15_0.png +0 -0
- data/doc/dot/m_16_0.dot +392 -0
- data/doc/dot/m_16_0.png +0 -0
- data/doc/dot/m_17_0.dot +392 -0
- data/doc/dot/m_17_0.png +0 -0
- data/doc/dot/m_18_0.dot +392 -0
- data/doc/dot/m_18_0.png +0 -0
- data/doc/dot/m_19_0.dot +392 -0
- data/doc/dot/m_19_0.png +0 -0
- data/doc/dot/m_1_0.dot +392 -0
- data/doc/dot/m_1_0.png +0 -0
- data/doc/dot/m_2_0.dot +392 -0
- data/doc/dot/m_2_0.png +0 -0
- data/doc/dot/m_3_0.dot +392 -0
- data/doc/dot/m_3_0.png +0 -0
- data/doc/dot/m_4_0.dot +392 -0
- data/doc/dot/m_4_0.png +0 -0
- data/doc/dot/m_5_0.dot +392 -0
- data/doc/dot/m_5_0.png +0 -0
- data/doc/dot/m_7_0.dot +392 -0
- data/doc/dot/m_7_0.png +0 -0
- data/doc/dot/m_8_0.dot +392 -0
- data/doc/dot/m_8_0.png +0 -0
- data/doc/dot/m_9_0.dot +392 -0
- data/doc/dot/m_9_0.png +0 -0
- data/doc/files/lib/AbstractConstraint_rb.html +148 -0
- data/doc/files/lib/AllDifferentConstraint_rb.html +156 -0
- data/doc/files/lib/BinaryConstraint_rb.html +155 -0
- data/doc/files/lib/ConstraintList_rb.html +148 -0
- data/doc/files/lib/ConstraintSolver_rb.html +162 -0
- data/doc/files/lib/Domain_rb.html +155 -0
- data/doc/files/lib/Problem_rb.html +148 -0
- data/doc/files/lib/Solution_rb.html +148 -0
- data/doc/files/lib/Variable_rb.html +148 -0
- data/doc/files/lib/extensions_rb.html +108 -0
- data/doc/files/test/AllDifferentConstraintTest_rb.html +158 -0
- data/doc/files/test/BinaryConstraintTest_rb.html +158 -0
- data/doc/files/test/ConstraintListTest_rb.html +160 -0
- data/doc/files/test/ConstraintSolverTest_rb.html +164 -0
- data/doc/files/test/DomainTest_rb.html +156 -0
- data/doc/files/test/ProblemTest_rb.html +160 -0
- data/doc/files/test/SolutionTest_rb.html +159 -0
- data/doc/files/test/TestSuite_rb.html +113 -0
- data/doc/files/test/VariableTest_rb.html +157 -0
- data/doc/files/test/extensionsTest_rb.html +118 -0
- data/doc/fr_class_index.html +51 -0
- data/doc/fr_file_index.html +46 -0
- data/doc/fr_method_index.html +133 -0
- data/doc/index.html +24 -0
- data/examples/example.rb +7 -0
- data/examples/queens.rb +13 -0
- data/examples/soft.rb +14 -0
- data/lib/AbstractConstraint.rb +45 -0
- data/lib/AllDifferentConstraint.rb +160 -0
- data/lib/BinaryConstraint.rb +187 -0
- data/lib/ConstraintList.rb +31 -0
- data/lib/ConstraintSolver.rb +213 -0
- data/lib/Domain.rb +100 -0
- data/lib/GraphUtils.rb +293 -0
- data/lib/OneOfEqualsConstraint.rb +81 -0
- data/lib/Problem.rb +30 -0
- data/lib/Solution.rb +56 -0
- data/lib/TupleConstraint.rb +111 -0
- data/lib/Variable.rb +74 -0
- data/lib/extensions.rb +55 -0
- data/test/AllDifferentConstraintTest.rb +140 -0
- data/test/BinaryConstraintTest.rb +108 -0
- data/test/ConstraintListTest.rb +41 -0
- data/test/ConstraintSolverTest.rb +274 -0
- data/test/DomainTest.rb +83 -0
- data/test/GraphUtilsTest.rb +83 -0
- data/test/OneOfEqualsConstraintTest.rb +82 -0
- data/test/ProblemTest.rb +35 -0
- data/test/SolutionTest.rb +35 -0
- data/test/TestSuite.rb +10 -0
- data/test/TupleConstraintTest.rb +151 -0
- data/test/VariableTest.rb +47 -0
- data/test/extensionsTest.rb +57 -0
- metadata +212 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/ruby
|
|
2
|
+
|
|
3
|
+
require 'AbstractConstraint'
|
|
4
|
+
require 'extensions'
|
|
5
|
+
|
|
6
|
+
module ConstraintSolver
|
|
7
|
+
# Represents a one-of-equals constraint.
|
|
8
|
+
class OneOfEqualsConstraint < AbstractConstraint
|
|
9
|
+
attr_reader :variables, :value, :violationCost
|
|
10
|
+
# Initialises a new one-of-equals constraint.
|
|
11
|
+
# The arguments are the list of variables involved in the constraint and
|
|
12
|
+
# the value at least one of the variables should be equal to.
|
|
13
|
+
# Optionally, a cost for violating the constraint can be specified.
|
|
14
|
+
def initialize(variables, value, violationCost=1)
|
|
15
|
+
if variables.empty?
|
|
16
|
+
raise ArgumentError, "List of variables must not be empty!"
|
|
17
|
+
end
|
|
18
|
+
@variables = variables
|
|
19
|
+
@value = value
|
|
20
|
+
@violationCost = violationCost
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def holds?
|
|
24
|
+
if not allAssigned? or not @variables.find { |var| var.value == @value }.nil?
|
|
25
|
+
return true
|
|
26
|
+
else
|
|
27
|
+
return false
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def allAssigned?
|
|
32
|
+
@variables.each { |var|
|
|
33
|
+
return false if not var.assigned?
|
|
34
|
+
}
|
|
35
|
+
return true
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def include?(variable)
|
|
39
|
+
@variables.include?(variable)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def to_s
|
|
43
|
+
@variables.collect { |var| var.name }.join(" != ")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def to_s_full
|
|
47
|
+
@variables.collect { |var| var.to_s }.join(" != ")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def ==(constraint)
|
|
51
|
+
return false unless constraint.kind_of?(OneOfEqualsConstraint)
|
|
52
|
+
(@variables == constraint.variables) and (@value == constraint.value) and
|
|
53
|
+
(@violationCost == constraint.violationCost)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def each
|
|
57
|
+
@variables.each { |var|
|
|
58
|
+
yield var
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def revise
|
|
63
|
+
checks = 0
|
|
64
|
+
revisedVariables = []
|
|
65
|
+
wipeout = false
|
|
66
|
+
if @variables.find_all { |var| var.assigned? and not var.value == @value }.size == @variables.size - 1
|
|
67
|
+
unassigned = @variables.find { |var| not var.assigned? }
|
|
68
|
+
pruneList = unassigned.domain.values - [ @value ]
|
|
69
|
+
if not pruneList.empty?
|
|
70
|
+
begin
|
|
71
|
+
unassigned.domain.prune(pruneList)
|
|
72
|
+
rescue DomainWipeoutException
|
|
73
|
+
wipeout = true
|
|
74
|
+
end
|
|
75
|
+
revisedVariables << unassigned
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
return revisedVariables, checks, wipeout
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
data/lib/Problem.rb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/ruby
|
|
2
|
+
|
|
3
|
+
module ConstraintSolver
|
|
4
|
+
# Representation of a constraint satisfaction problem.
|
|
5
|
+
class Problem
|
|
6
|
+
attr_reader :variables, :constraints, :meritMap, :firstSolution, :maxViolation
|
|
7
|
+
# Creates a new instances of a constraint satisfaction problem.
|
|
8
|
+
# The constructor takes the lists of variables and constraints as parameters.
|
|
9
|
+
# Optionally, a map that maps domain elements to their merit,
|
|
10
|
+
# a boolean indicating whether to sop solving after a solution has
|
|
11
|
+
# been found, and a number designating the maximum cost for constraint
|
|
12
|
+
# violation that can be accepted can be specified.
|
|
13
|
+
def initialize(variables, constraints, meritMap={}, firstSolution=false, maxViolation=0)
|
|
14
|
+
if not variables.kind_of?(Array)
|
|
15
|
+
variables = [ variables ]
|
|
16
|
+
end
|
|
17
|
+
if not constraints.kind_of?(ConstraintList)
|
|
18
|
+
constraints = ConstraintList.new([ constraints ])
|
|
19
|
+
end
|
|
20
|
+
if variables.empty? or constraints.empty?
|
|
21
|
+
raise ArgumentError, "Variables and constraints must not be empty!"
|
|
22
|
+
end
|
|
23
|
+
@variables = variables
|
|
24
|
+
@constraints = constraints
|
|
25
|
+
@meritMap = meritMap
|
|
26
|
+
@firstSolution = firstSolution
|
|
27
|
+
@maxViolation = maxViolation
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/Solution.rb
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/ruby
|
|
2
|
+
|
|
3
|
+
module ConstraintSolver
|
|
4
|
+
# This class represents a solution to a constraint satisfaction problem.
|
|
5
|
+
class Solution
|
|
6
|
+
attr_reader :variables, :merit, :violation
|
|
7
|
+
# Initialises a new solution with a list of variables. The variables
|
|
8
|
+
# must all have values assigned to them.
|
|
9
|
+
# Optionally, a map to map domain values of variables to their merit and
|
|
10
|
+
# a number designating the cost of violating constraints in the solution can
|
|
11
|
+
# be specified.
|
|
12
|
+
def initialize(variables, meritMap={}, violation=0)
|
|
13
|
+
if not variables.kind_of?(Array)
|
|
14
|
+
variables = Array.new([ variables ])
|
|
15
|
+
end
|
|
16
|
+
if variables.inject(true) { |res,var| res & var.assigned? }
|
|
17
|
+
@variables = Array.new
|
|
18
|
+
variables.each { |variable|
|
|
19
|
+
@variables << variable.dup
|
|
20
|
+
}
|
|
21
|
+
else
|
|
22
|
+
raise ArgumentError, "All variables must have values assigned to them!"
|
|
23
|
+
end
|
|
24
|
+
@merit = 0
|
|
25
|
+
@variables.each { |var|
|
|
26
|
+
@merit += meritMap[var.value] ? var.merit * meritMap[var.value] : var.merit
|
|
27
|
+
}
|
|
28
|
+
@violation = violation
|
|
29
|
+
@merit -= @violation
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def each
|
|
33
|
+
@variables.each { |variable|
|
|
34
|
+
yield variable
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def <=>(solution)
|
|
39
|
+
@value <=> solution.value
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def ==(solution)
|
|
43
|
+
return false unless solution.kind_of?(Solution)
|
|
44
|
+
@variables == solution.variables and @merit == solution.merit and @violation == solution.violation
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_s
|
|
48
|
+
s = (@variables.collect { |variable|
|
|
49
|
+
variable.to_s
|
|
50
|
+
}).join(", ")
|
|
51
|
+
s += ", merit " + @merit.to_s unless @merit.nil?
|
|
52
|
+
s += ", violation " + @violation.to_s unless @violation.nil?
|
|
53
|
+
return s
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/ruby
|
|
2
|
+
|
|
3
|
+
require 'AbstractConstraint'
|
|
4
|
+
require 'extensions'
|
|
5
|
+
|
|
6
|
+
module ConstraintSolver
|
|
7
|
+
# Represents a constraint which is specified by the allowed or disallowed
|
|
8
|
+
# tuples of arbitrary arity.
|
|
9
|
+
class TupleConstraint < AbstractConstraint
|
|
10
|
+
attr_reader :variables, :tuples, :allowedTuples, :violationCost
|
|
11
|
+
# Initialises a new tuple constraint.
|
|
12
|
+
# The first argument is the list of variables involved in the
|
|
13
|
+
# constraint, the second argument is an array of tuples. Each element of
|
|
14
|
+
# the array is an array with exactly as many elements as the array of
|
|
15
|
+
# variables. The third argument specifies whether the array of tuples
|
|
16
|
+
# lists the allowed or disallowed tuples. Default is allowed tuples.
|
|
17
|
+
# Optionally, a cost for violating the constraint can be specified.
|
|
18
|
+
def initialize(variables, tuples, allowedTuples=true, violationCost=1)
|
|
19
|
+
@variables = variables
|
|
20
|
+
unless tuples.inject(true) { |bool,tuple| bool & (tuple.size == variables.size) }
|
|
21
|
+
raise ArgumentError, "All tuples must have " + variables.size.to_s + " elements!"
|
|
22
|
+
end
|
|
23
|
+
@tuples = tuples
|
|
24
|
+
@violationCost = violationCost
|
|
25
|
+
@allowedTuples = allowedTuples
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Checks whether the assignments to the variables are allowed tuples.
|
|
29
|
+
def holds?
|
|
30
|
+
tups = @tuples
|
|
31
|
+
@variables.each_with_index { |var,i|
|
|
32
|
+
next unless var.assigned?
|
|
33
|
+
tups = tups.find_all { |tup| tup[i] == var.value }
|
|
34
|
+
if @allowedTuples
|
|
35
|
+
return false if tups.empty?
|
|
36
|
+
end
|
|
37
|
+
}
|
|
38
|
+
if not @allowedTuples and allAssigned? and tuples.include?(@variables.collect { |var| var.value })
|
|
39
|
+
return false
|
|
40
|
+
end
|
|
41
|
+
return true
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def allAssigned?
|
|
45
|
+
@variables.each { |var|
|
|
46
|
+
return false if not var.assigned?
|
|
47
|
+
}
|
|
48
|
+
return true
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def include?(variable)
|
|
52
|
+
@variables.include?(variable)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def to_s
|
|
56
|
+
@variables.collect { |var| var.name }.join(", ") + ": " + tuples.join(", ")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def to_s_full
|
|
60
|
+
@variables.collect { |var| var.to_s }.join(", ") + ": " + tuples.join(", ")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def ==(constraint)
|
|
64
|
+
return false unless constraint.kind_of?(TupleConstraint)
|
|
65
|
+
(@variables == constraint.variables) and (@tuples == constraint.tuples) and
|
|
66
|
+
(@allowedTuples == constraint.allowedTuples) and (@violationCost == constraint.violationCost)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def each
|
|
70
|
+
@variables.each { |var|
|
|
71
|
+
yield var
|
|
72
|
+
}
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def revise
|
|
76
|
+
revisedVariables = []
|
|
77
|
+
checks = 0
|
|
78
|
+
wipeout = false
|
|
79
|
+
assignedIndices, unassignedIndices = (0..(@variables.size - 1)).partition { |i| @variables[i].assigned? }
|
|
80
|
+
pruneTuples = (@allowedTuples ? @tuples : [])
|
|
81
|
+
assignedIndices.each { |i|
|
|
82
|
+
if @allowedTuples
|
|
83
|
+
pruneTuples = pruneTuples.find_all { |tup| tup[i] == @variables[i].value }
|
|
84
|
+
else
|
|
85
|
+
(@tuples - pruneTuples).each { |tup|
|
|
86
|
+
if @variables[i].value == tup[i]
|
|
87
|
+
pruneTuples << tup
|
|
88
|
+
end
|
|
89
|
+
}
|
|
90
|
+
end
|
|
91
|
+
}
|
|
92
|
+
unassignedIndices.each { |i|
|
|
93
|
+
var = @variables[i]
|
|
94
|
+
pruneList = pruneTuples.collect { |tup| tup[i] }.to_set
|
|
95
|
+
if @allowedTuples
|
|
96
|
+
pruneList = var.domain.values - pruneList
|
|
97
|
+
end
|
|
98
|
+
if var.domain.include_any?(pruneList)
|
|
99
|
+
begin
|
|
100
|
+
var.domain.prune(pruneList)
|
|
101
|
+
rescue DomainWipeoutException
|
|
102
|
+
wipeout = true
|
|
103
|
+
end
|
|
104
|
+
revisedVariables << var
|
|
105
|
+
break if wipeout
|
|
106
|
+
end
|
|
107
|
+
}
|
|
108
|
+
return revisedVariables, checks, wipeout
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
data/lib/Variable.rb
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/ruby
|
|
2
|
+
|
|
3
|
+
module ConstraintSolver
|
|
4
|
+
# This class represents a variable in a constraint satisfaction problem.
|
|
5
|
+
class Variable
|
|
6
|
+
attr_reader :name, :domain, :value, :merit
|
|
7
|
+
# Initialises a new variable. The name of the variable and at least one
|
|
8
|
+
# of domain or value are required.
|
|
9
|
+
# If domain is nil and a value is given, a new Domain is constructed
|
|
10
|
+
# with only the value specified in it.
|
|
11
|
+
# The fourth argument is the merit of the variable.
|
|
12
|
+
def initialize(name, domain=nil, value=nil, merit=0)
|
|
13
|
+
if not name.kind_of?(String)
|
|
14
|
+
raise ArgumentError, "The name of the variable has to be specified."
|
|
15
|
+
end
|
|
16
|
+
if domain.nil? and value.nil?
|
|
17
|
+
raise ArgumentError, "At least one of domain or value are required."
|
|
18
|
+
end
|
|
19
|
+
@name = name
|
|
20
|
+
@value = value
|
|
21
|
+
if not domain.nil? and not domain.kind_of?(Domain)
|
|
22
|
+
domain = Domain.new([ domain ].to_set);
|
|
23
|
+
end
|
|
24
|
+
if value.nil?
|
|
25
|
+
@domain = domain.nil? ? Domain.new : domain
|
|
26
|
+
else
|
|
27
|
+
if domain.nil?
|
|
28
|
+
@domain = Domain.new([ value ].to_set)
|
|
29
|
+
else
|
|
30
|
+
if domain.include?(value)
|
|
31
|
+
@domain = domain
|
|
32
|
+
else
|
|
33
|
+
raise ArgumentError, "Value " + value.to_s + " not in domain " + domain.to_s + "!"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
@merit = merit
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Assigns a new value to the variable.
|
|
41
|
+
# The new value has to be in the domain of the variable.
|
|
42
|
+
def value=(value)
|
|
43
|
+
if value.nil?
|
|
44
|
+
return
|
|
45
|
+
end
|
|
46
|
+
if @domain.include?(value)
|
|
47
|
+
@value = value
|
|
48
|
+
else
|
|
49
|
+
raise ArgumentError, "The domain of " + @name.to_s + " (" + @domain.to_s +
|
|
50
|
+
") does not contain " + value.to_s + "!"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def values
|
|
55
|
+
domain.values
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def to_s
|
|
59
|
+
@name.to_s + " = " + @value.to_s + " \\in " + @domain.to_s
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def ==(variable)
|
|
63
|
+
variable.kind_of?(Variable) and (@name == variable.name) and (@value == variable.value)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def assigned?
|
|
67
|
+
not @value.nil?
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def reset
|
|
71
|
+
@value = nil
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
data/lib/extensions.rb
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/ruby
|
|
2
|
+
|
|
3
|
+
class Array
|
|
4
|
+
def rest
|
|
5
|
+
retval = self.clone
|
|
6
|
+
retval.shift
|
|
7
|
+
return retval
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def foldLeft(function)
|
|
11
|
+
if self.size == 1
|
|
12
|
+
return self.first
|
|
13
|
+
else
|
|
14
|
+
return function.call(self.first, self.rest.foldLeft(function))
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def eachAfter(element, &block)
|
|
19
|
+
self[((self.index(element) or -1) + 1)..-1].each { |x|
|
|
20
|
+
yield x
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def eachStartWith(element, &block)
|
|
25
|
+
self[(self.index(element) or 0)..-1].each { |x|
|
|
26
|
+
yield x
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class Fixnum
|
|
32
|
+
def not_equal?(num)
|
|
33
|
+
self != num
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class Set
|
|
38
|
+
def include_any?(enum)
|
|
39
|
+
enum.each { |e|
|
|
40
|
+
if self.include?(e)
|
|
41
|
+
return true
|
|
42
|
+
end
|
|
43
|
+
}
|
|
44
|
+
return false
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class Hash
|
|
49
|
+
def include_hash?(hash)
|
|
50
|
+
hash.each { |k,v|
|
|
51
|
+
return false unless self[k] == v
|
|
52
|
+
}
|
|
53
|
+
return true
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/ruby
|
|
2
|
+
|
|
3
|
+
$:.unshift File.join(File.dirname(__FILE__), "..", "lib")
|
|
4
|
+
|
|
5
|
+
require 'test/unit'
|
|
6
|
+
require 'AllDifferentConstraint'
|
|
7
|
+
require 'Variable'
|
|
8
|
+
require 'Domain'
|
|
9
|
+
|
|
10
|
+
module ConstraintSolver
|
|
11
|
+
class AllDifferentConstraintTest < Test::Unit::TestCase
|
|
12
|
+
def setup
|
|
13
|
+
@x = Variable.new("x", Domain.new([ 1, 2, 3 ].to_set))
|
|
14
|
+
@y = Variable.new("y", Domain.new([ 1, 2, 3 ].to_set))
|
|
15
|
+
@z = Variable.new("z", Domain.new([ 1, 2, 3 ].to_set))
|
|
16
|
+
@constraint = AllDifferentConstraint.new([ @x, @y, @z ])
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def testConstructor
|
|
20
|
+
assert_raise(ArgumentError) { AllDifferentConstraint.new }
|
|
21
|
+
assert_raise(ArgumentError) { AllDifferentConstraint.new([]) }
|
|
22
|
+
assert_raise(ArgumentError) { AllDifferentConstraint.new([ @x ]) }
|
|
23
|
+
assert_nothing_raised { AllDifferentConstraint.new([ @x, @y, @z ]) }
|
|
24
|
+
assert_nothing_raised { AllDifferentConstraint.new([ @x, @y, @z ], 0) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def testAssigned
|
|
28
|
+
assert_equal(false, @constraint.allAssigned?)
|
|
29
|
+
@y.value = 1
|
|
30
|
+
@z.value = 1
|
|
31
|
+
assert_equal(false, @constraint.allAssigned?)
|
|
32
|
+
@x.value = 1
|
|
33
|
+
assert_equal(true, @constraint.allAssigned?)
|
|
34
|
+
@x.reset
|
|
35
|
+
@y.reset
|
|
36
|
+
@z.reset
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def testInclude
|
|
40
|
+
assert_equal(true, @constraint.include?(@x))
|
|
41
|
+
assert_equal(true, @constraint.include?(@y))
|
|
42
|
+
assert_equal(true, @constraint.include?(@z))
|
|
43
|
+
assert_equal(false, @constraint.include?(Variable.new("foobar", 1)))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def testEquals
|
|
47
|
+
assert_not_equal(AllDifferentConstraint.new([ @x, @y ]), @constraint)
|
|
48
|
+
assert_equal(AllDifferentConstraint.new([ @x, @y, @z ]), @constraint)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def testHolds
|
|
52
|
+
@x.value = 1
|
|
53
|
+
@y.value = 2
|
|
54
|
+
@z.value = 2
|
|
55
|
+
assert_equal(false, @constraint.holds?)
|
|
56
|
+
@z.value = 3
|
|
57
|
+
assert_equal(true, @constraint.holds?)
|
|
58
|
+
@constraint.variables.each { |var| var.reset }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def testRevise_decomposed_local
|
|
62
|
+
assert_equal([ [], 0, false ], @constraint.revise_decomposed_local)
|
|
63
|
+
@x.value = 1
|
|
64
|
+
assert_equal([ [ @y, @z ], 0, false ], @constraint.revise_decomposed_local)
|
|
65
|
+
assert_equal([ 2, 3 ].to_set, @y.values)
|
|
66
|
+
assert_equal([ 2, 3 ].to_set, @z.values)
|
|
67
|
+
@x.reset
|
|
68
|
+
|
|
69
|
+
x = Variable.new("x", Domain.new([ 1, 3 ].to_set))
|
|
70
|
+
y = Variable.new("y", Domain.new([ 2, 3 ].to_set))
|
|
71
|
+
z = Variable.new("z", Domain.new([ 2, 3 ].to_set))
|
|
72
|
+
constraint = AllDifferentConstraint.new([ x, y, z ])
|
|
73
|
+
assert_equal([ [], 0, false ], constraint.revise_decomposed_local)
|
|
74
|
+
assert_equal([ 1, 3 ].to_set, x.values)
|
|
75
|
+
assert_equal([ 2, 3 ].to_set, y.values)
|
|
76
|
+
assert_equal([ 2, 3 ].to_set, z.values)
|
|
77
|
+
|
|
78
|
+
a = Variable.new("a", Domain.new([ 1 ].to_set))
|
|
79
|
+
b = Variable.new("b", Domain.new([ 1, 2 ].to_set))
|
|
80
|
+
c = Variable.new("c", Domain.new([ 1, 2, 3 ].to_set))
|
|
81
|
+
constraint = AllDifferentConstraint.new([ a, b, c ])
|
|
82
|
+
assert_equal([ [ b, c, c ], 0, false ], constraint.revise_decomposed_local)
|
|
83
|
+
assert_equal([ 1 ].to_set, a.values)
|
|
84
|
+
assert_equal([ 2 ].to_set, b.values)
|
|
85
|
+
assert_equal([ 3 ].to_set, c.values)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def testDomainWipeout_decomposed_local
|
|
89
|
+
x = Variable.new("x", Domain.new([ 1, 2, 3 ].to_set))
|
|
90
|
+
y = Variable.new("y", Domain.new([ 1, 2, 3 ].to_set))
|
|
91
|
+
z = Variable.new("z", Domain.new([ 1, 2, 3 ].to_set))
|
|
92
|
+
a = Variable.new("a", Domain.new([ 1, 2, 3 ].to_set))
|
|
93
|
+
constraint = AllDifferentConstraint.new([ x, y, z, a ])
|
|
94
|
+
a.value = 1
|
|
95
|
+
assert_equal([ [ x, y, z ], 0, true ], constraint.revise_decomposed_local)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def testRevise_hyperarc
|
|
99
|
+
x = Variable.new("x", Domain.new([ 1, 3 ].to_set))
|
|
100
|
+
y = Variable.new("y", Domain.new([ 2, 3 ].to_set))
|
|
101
|
+
z = Variable.new("z", Domain.new([ 2, 3 ].to_set))
|
|
102
|
+
constraint = AllDifferentConstraint.new([ x, y, z ])
|
|
103
|
+
assert_equal([ [ x ], 0, false ], constraint.revise_hyperarc)
|
|
104
|
+
assert_equal([ 1 ].to_set, x.values)
|
|
105
|
+
assert_equal([ 2, 3 ].to_set, y.values)
|
|
106
|
+
assert_equal([ 2, 3 ].to_set, z.values)
|
|
107
|
+
|
|
108
|
+
a = Variable.new("a", Domain.new([ 1 ].to_set))
|
|
109
|
+
b = Variable.new("b", Domain.new([ 1, 2 ].to_set))
|
|
110
|
+
c = Variable.new("c", Domain.new([ 1, 2, 3 ].to_set))
|
|
111
|
+
constraint = AllDifferentConstraint.new([ a, b, c ])
|
|
112
|
+
revisedVariables, checks, wipeout = constraint.revise_hyperarc
|
|
113
|
+
assert_equal(false, wipeout)
|
|
114
|
+
assert_equal(Set.new, [ b, c ].to_set - revisedVariables.to_set)
|
|
115
|
+
assert_equal(0, checks)
|
|
116
|
+
assert_equal([ 1 ].to_set, a.values)
|
|
117
|
+
assert_equal([ 2 ].to_set, b.values)
|
|
118
|
+
assert_equal([ 3 ].to_set, c.values)
|
|
119
|
+
|
|
120
|
+
assert_equal([ [], 0, false ], constraint.revise_hyperarc)
|
|
121
|
+
|
|
122
|
+
x = Variable.new("x", Domain.new([ 1, 3 ].to_set))
|
|
123
|
+
y = Variable.new("y", Domain.new([ 2, 3 ].to_set))
|
|
124
|
+
z = Variable.new("z", Domain.new([ 2, 3, 4 ].to_set))
|
|
125
|
+
constraint = AllDifferentConstraint.new([ x, y, z ])
|
|
126
|
+
assert_equal([ [], 0, false ], constraint.revise_hyperarc)
|
|
127
|
+
assert_equal([ 1, 3 ].to_set, x.values)
|
|
128
|
+
assert_equal([ 2, 3 ].to_set, y.values)
|
|
129
|
+
assert_equal([ 2, 3, 4 ].to_set, z.values)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def testDomainWipeout_hyperarc
|
|
133
|
+
x = Variable.new("x", Domain.new([ 1, 2 ].to_set))
|
|
134
|
+
y = Variable.new("y", Domain.new([ 1, 2 ].to_set))
|
|
135
|
+
z = Variable.new("z", Domain.new([ 1, 2 ].to_set))
|
|
136
|
+
constraint = AllDifferentConstraint.new([ x, y, z ])
|
|
137
|
+
assert_equal([ [], 0, true ], constraint.revise_hyperarc)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|