winston 0.0.2 → 0.1.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,69 @@
1
+ module Winston
2
+ class DSL
3
+ attr_reader :csp
4
+
5
+ def initialize(csp = CSP.new)
6
+ @csp = csp
7
+ @domains = {}
8
+ end
9
+
10
+ def domain(name, values)
11
+ @domains[name] = values
12
+ end
13
+
14
+ def var(name, domain: nil, value: nil, &block)
15
+ resolved_domain = resolve_domain(domain)
16
+ csp.add_variable(name, value: value, domain: resolved_domain, &block)
17
+ end
18
+
19
+ def constraint(*variables, allow_nil: false, &block)
20
+ csp.add_constraint(*variables, allow_nil: allow_nil, &block)
21
+ end
22
+
23
+ def use_constraint(name, *variables, allow_nil: false, **options)
24
+ factory = constraint_factory_for(name)
25
+ constraint = factory.call(variables, allow_nil, **options)
26
+ csp.add_constraint(constraint: constraint)
27
+ end
28
+
29
+ private
30
+
31
+ def resolve_domain(domain)
32
+ return domain unless domain.is_a?(Symbol)
33
+ return @domains[domain] if @domains.key?(domain)
34
+
35
+ raise ArgumentError, "Unknown domain :#{domain}"
36
+ end
37
+
38
+ def constraint_factory_for(name)
39
+ registry = Winston.constraint_registry
40
+ return registry[name] if registry.key?(name)
41
+
42
+ raise ArgumentError, "Unknown constraint :#{name}"
43
+ end
44
+ end
45
+
46
+ def self.constraint_registry
47
+ @constraint_registry ||= {
48
+ all_different: lambda do |variables, allow_nil, **options|
49
+ Winston::Constraints::AllDifferent.new(variables: variables, allow_nil: allow_nil, **options)
50
+ end,
51
+ not_in_list: lambda do |variables, allow_nil, **options|
52
+ Winston::Constraints::NotInList.new(variables: variables, allow_nil: allow_nil, **options)
53
+ end
54
+ }
55
+ end
56
+
57
+ def self.register_constraint(name, factory = nil, &block)
58
+ factory ||= block
59
+ raise ArgumentError, "Constraint factory required for :#{name}" unless factory
60
+
61
+ constraint_registry[name] = factory
62
+ end
63
+
64
+ def self.define(&block)
65
+ builder = DSL.new
66
+ builder.instance_eval(&block) if block
67
+ builder.csp
68
+ end
69
+ end
@@ -0,0 +1,44 @@
1
+ module Winston
2
+ module Heuristics
3
+ def self.mrv
4
+ lambda do |vars, assignments, csp|
5
+ vars.min_by { |var| remaining_values(var, assignments, csp).size }
6
+ end
7
+ end
8
+
9
+ def self.lcv
10
+ lambda do |values, var, assignments, csp|
11
+ other_vars = vars_without(var, assignments, csp)
12
+ scored = values.map do |value|
13
+ score = other_vars.sum do |other|
14
+ csp.domain_for(other.name).count do |other_value|
15
+ csp.validate(other.name, assignments.merge(var.name => value, other.name => other_value))
16
+ end
17
+ end
18
+ [value, score]
19
+ end
20
+ scored.sort_by { |(_, score)| -score }.map(&:first)
21
+ end
22
+ end
23
+
24
+ def self.in_order
25
+ ->(values, _var, _assignments, _csp) { values }
26
+ end
27
+
28
+ def self.forward_checking
29
+ true
30
+ end
31
+
32
+ def self.remaining_values(var, assignments, csp)
33
+ csp.domain_for(var.name).select do |value|
34
+ csp.validate(var.name, assignments.merge(var.name => value))
35
+ end
36
+ end
37
+
38
+ def self.vars_without(var, assignments, csp)
39
+ csp.variables.reject { |k, _| assignments.include?(k) || k == var.name }.each_value.to_a
40
+ end
41
+
42
+ private_class_method :remaining_values, :vars_without
43
+ end
44
+ end
@@ -0,0 +1,91 @@
1
+ module Winston
2
+ module Solvers
3
+ class Backtrack
4
+ def initialize(csp, variable_strategy: :first, value_strategy: :in_order, forward_checking: false)
5
+ @csp = csp
6
+ @variable_strategy = variable_strategy
7
+ @value_strategy = value_strategy
8
+ @forward_checking = forward_checking
9
+ end
10
+
11
+ def search(assignments = {})
12
+ return assignments if complete?(assignments)
13
+ var = select_unassigned_variable(assignments)
14
+ domain_values(var, assignments).each do |value|
15
+ assigned = assignments.merge(var.name => value)
16
+ if valid?(var.name, assigned)
17
+ result = search(assigned)
18
+ return result if result
19
+ end
20
+ end
21
+ false
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :csp, :variable_strategy, :value_strategy, :forward_checking
27
+
28
+ def complete?(assignments)
29
+ assignments.size == csp.variables.size
30
+ end
31
+
32
+ def valid?(changed, assignments)
33
+ return false unless csp.validate(changed, assignments)
34
+ return true unless forward_checking
35
+
36
+ forward_check(assignments)
37
+ end
38
+
39
+ def select_unassigned_variable(assignments)
40
+ vars = unassigned_variables(assignments)
41
+ return vars.first if variable_strategy == :first
42
+ return variable_strategy.call(vars, assignments, csp) if variable_strategy.respond_to?(:call)
43
+ return vars.min_by { |var| remaining_values(var, assignments).size } if variable_strategy == :mrv
44
+
45
+ vars.first
46
+ end
47
+
48
+ def domain_values(var, assignments)
49
+ values = remaining_values(var, assignments)
50
+ return values if value_strategy == :in_order
51
+ return value_strategy.call(values, var, assignments, csp) if value_strategy.respond_to?(:call)
52
+ return order_lcv(values, var, assignments) if value_strategy == :lcv
53
+
54
+ values
55
+ end
56
+
57
+ def remaining_values(var, assignments)
58
+ csp.domain_for(var.name).select do |value|
59
+ csp.validate(var.name, assignments.merge(var.name => value))
60
+ end
61
+ end
62
+
63
+ def order_lcv(values, var, assignments)
64
+ other_vars = unassigned_variables(assignments).reject { |v| v.name == var.name }
65
+ scored = values.map do |value|
66
+ score = other_vars.sum do |other|
67
+ csp.domain_for(other.name).count do |other_value|
68
+ csp.validate(other.name, assignments.merge(var.name => value, other.name => other_value))
69
+ end
70
+ end
71
+ [value, score]
72
+ end
73
+ scored.sort_by { |(_, score)| -score }.map(&:first)
74
+ end
75
+
76
+ def unassigned_variables(assignments)
77
+ csp.variables.reject { |k, _| assignments.include?(k) }.each_value.to_a
78
+ end
79
+
80
+ def forward_check(assignments)
81
+ unassigned_variables(assignments).all? do |var|
82
+ csp.domain_for(var.name).any? do |value|
83
+ csp.validate(var.name, assignments.merge(var.name => value))
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ Backtrack = Solvers::Backtrack
91
+ end