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.
@@ -1,6 +1,6 @@
1
1
  require "winston"
2
2
 
3
- describe Winston::Backtrack do
3
+ describe Winston::Solvers::Backtrack do
4
4
  let(:csp) { Winston::CSP.new }
5
5
  subject { described_class.new(csp) }
6
6
 
@@ -44,5 +44,38 @@ describe Winston::Backtrack do
44
44
  end
45
45
  end
46
46
  end
47
+
48
+ context "with heuristics" do
49
+ it "uses MRV to pick the smallest remaining domain first" do
50
+ csp.add_variable :a, domain: [1, 2, 3]
51
+ csp.add_variable :b, domain: [1]
52
+ csp.add_variable :c, domain: [1, 2]
53
+
54
+ solver = described_class.new(csp, variable_strategy: :mrv)
55
+ result = solver.search
56
+
57
+ expect(result.keys).to eq([:b, :c, :a])
58
+ end
59
+
60
+ it "uses LCV to prefer values that leave more options" do
61
+ csp.add_variable :a, domain: [2, 1]
62
+ csp.add_variable :b, domain: [2, 3]
63
+ csp.add_constraint(:a, :b) { |a, b| b > a }
64
+
65
+ solver = described_class.new(csp, value_strategy: :lcv)
66
+ result = solver.search
67
+
68
+ expect(result[:a]).to eq(1)
69
+ end
70
+
71
+ it "supports forward checking" do
72
+ csp.add_variable :a, domain: [1]
73
+ csp.add_variable :b, domain: [1]
74
+ csp.add_constraint(:a, :b) { |a, b| a != b }
75
+
76
+ solver = described_class.new(csp, forward_checking: true)
77
+ expect(solver.search).to be(false)
78
+ end
79
+ end
47
80
  end
48
81
  end
@@ -87,8 +87,32 @@ describe Winston::CSP do
87
87
  end
88
88
 
89
89
  it "should pass a collection of preset variables to the solver" do
90
- expect(solver).to receive(:search).with(a: 1)
90
+ expect(solver).to receive(:search).with({ a: 1 })
91
91
  subject.solve(solver)
92
92
  end
93
+
94
+ it "builds a solver by name with options" do
95
+ subject.add_constraint(:a, :b) { |a, b| a < b }
96
+ result = subject.solve(:backtrack, variable_strategy: :mrv)
97
+
98
+ expect(result).to eq({ a: 1, b: 2 })
99
+ end
100
+
101
+ it "raises for an unknown solver name" do
102
+ expect { subject.solve(:unknown) }.to raise_error(ArgumentError, /Unknown solver/)
103
+ end
104
+
105
+ it "returns false when preset assignments violate a constraint" do
106
+ subject.add_constraint(:a) { |a| a > 2 }
107
+ expect(solver).to_not receive(:search)
108
+ expect(subject.solve(solver)).to be(false)
109
+ end
110
+
111
+ it "returns false when a constraint among preset variables is violated" do
112
+ subject.add_variable :c, value: 2
113
+ subject.add_constraint(:a, :c) { |a, c| a == c }
114
+ expect(solver).to_not receive(:search)
115
+ expect(subject.solve(solver)).to be(false)
116
+ end
93
117
  end
94
118
  end
@@ -0,0 +1,71 @@
1
+ require "winston"
2
+
3
+ describe Winston::DSL do
4
+ describe ".define" do
5
+ it "builds a CSP with variables and constraints" do
6
+ csp = Winston.define do
7
+ var :a, domain: [1, 2, 3]
8
+ var :b, domain: [1, 2, 3]
9
+ constraint(:a, :b) { |a, b| a > b }
10
+ end
11
+
12
+ expect(csp.solve).to eq({ a: 2, b: 1 })
13
+ end
14
+
15
+ it "supports named domains" do
16
+ csp = Winston.define do
17
+ domain :digits, [1, 2, 3]
18
+ var :a, domain: :digits
19
+ var :b, domain: :digits
20
+ constraint(:a, :b) { |a, b| a != b }
21
+ end
22
+
23
+ result = csp.solve
24
+ expect([1, 2, 3]).to include(result[:a])
25
+ expect([1, 2, 3]).to include(result[:b])
26
+ expect(result[:a]).to_not eq(result[:b])
27
+ end
28
+
29
+ it "uses named constraints via use_constraint" do
30
+ csp = Winston.define do
31
+ var :a, domain: [1, 2]
32
+ var :b, domain: [1, 2]
33
+ use_constraint :all_different, :a, :b
34
+ end
35
+
36
+ expect(csp.solve).to eq({ a: 1, b: 2 })
37
+ end
38
+
39
+ it "passes options to named constraints" do
40
+ csp = Winston.define do
41
+ var :a, domain: [1, 2, 3]
42
+ use_constraint :not_in_list, :a, list: [1, 2]
43
+ end
44
+
45
+ expect(csp.solve).to eq({ a: 3 })
46
+ end
47
+ end
48
+
49
+ describe ".register_constraint" do
50
+ it "registers and uses a custom constraint by name" do
51
+ custom = Class.new(Winston::Constraint) do
52
+ def validate(assignments)
53
+ values = values_at(assignments)
54
+ values.all? { |v| v == 2 }
55
+ end
56
+ end
57
+
58
+ Winston.register_constraint(:all_twos) do |variables, allow_nil, **_options|
59
+ custom.new(variables: variables, allow_nil: allow_nil)
60
+ end
61
+
62
+ csp = Winston.define do
63
+ var :a, domain: [1, 2]
64
+ var :b, domain: [1, 2]
65
+ use_constraint :all_twos, :a, :b
66
+ end
67
+
68
+ expect(csp.solve).to eq({ a: 2, b: 2 })
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,47 @@
1
+ require "winston"
2
+
3
+ describe Winston::Heuristics do
4
+ let(:csp) { Winston::CSP.new }
5
+
6
+ describe ".mrv" do
7
+ it "picks the variable with the fewest remaining values" do
8
+ csp.add_variable :a, domain: [1, 2, 3]
9
+ csp.add_variable :b, domain: [1, 2]
10
+ csp.add_variable :c, domain: [1, 2, 3]
11
+ csp.add_constraint(:a) { |a| a == 1 }
12
+
13
+ vars = csp.variables.each_value.to_a
14
+ chosen = described_class.mrv.call(vars, {}, csp)
15
+
16
+ expect(chosen.name).to eq(:a)
17
+ end
18
+ end
19
+
20
+ describe ".lcv" do
21
+ it "orders values to leave more options for other variables" do
22
+ csp.add_variable :a, domain: [2, 1]
23
+ csp.add_variable :b, domain: [2, 3]
24
+ csp.add_constraint(:a, :b) { |a, b| b > a }
25
+
26
+ values = csp.domain_for(:a)
27
+ ordered = described_class.lcv.call(values, csp.variables[:a], {}, csp)
28
+
29
+ expect(ordered).to eq([1, 2])
30
+ end
31
+ end
32
+
33
+ describe ".in_order" do
34
+ it "returns values as-is" do
35
+ values = [3, 1, 2]
36
+ ordered = described_class.in_order.call(values, nil, {}, csp)
37
+
38
+ expect(ordered).to eq(values)
39
+ end
40
+ end
41
+
42
+ describe ".forward_checking" do
43
+ it "returns true to enable forward checking" do
44
+ expect(described_class.forward_checking).to be(true)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,44 @@
1
+ require "winston"
2
+
3
+ describe Winston::Solvers::MAC do
4
+ let(:csp) { Winston::CSP.new }
5
+
6
+ def valid_solution?(csp, assignments)
7
+ csp.constraints.all? { |constraint| constraint.validate(assignments) }
8
+ end
9
+
10
+ it "solves a simple constraint problem" do
11
+ csp.add_variable :a, domain: [1, 2, 3]
12
+ csp.add_variable :b, domain: [1, 2, 3]
13
+ csp.add_variable :c, domain: [1, 2, 3]
14
+ csp.add_constraint(:a, :b) { |a, b| a > b }
15
+ csp.add_constraint(:b, :c) { |b, c| b > c }
16
+
17
+ solver = described_class.new(csp)
18
+ result = solver.search
19
+
20
+ expect(result).to be_a(Hash)
21
+ expect(valid_solution?(csp, result)).to be(true)
22
+ end
23
+
24
+ it "respects preset assignments" do
25
+ csp.add_variable :a, value: 2
26
+ csp.add_variable :b, domain: [1, 2, 3]
27
+ csp.add_constraint(:a, :b) { |a, b| a > b }
28
+
29
+ solver = described_class.new(csp)
30
+ result = solver.search
31
+
32
+ expect(result[:a]).to eq(2)
33
+ expect(valid_solution?(csp, result)).to be(true)
34
+ end
35
+
36
+ it "returns false when constraints are impossible" do
37
+ csp.add_variable :a, domain: [1]
38
+ csp.add_variable :b, domain: [1]
39
+ csp.add_constraint(:a, :b) { |a, b| a != b }
40
+
41
+ solver = described_class.new(csp)
42
+ expect(solver.search).to be(false)
43
+ end
44
+ end
@@ -0,0 +1,44 @@
1
+ require "winston"
2
+
3
+ describe Winston::Solvers::MinConflicts do
4
+ let(:csp) { Winston::CSP.new }
5
+
6
+ def valid_solution?(csp, assignments)
7
+ csp.constraints.all? { |constraint| constraint.validate(assignments) }
8
+ end
9
+
10
+ it "finds a valid solution for a simple problem" do
11
+ csp.add_variable :a, domain: [1, 2, 3]
12
+ csp.add_variable :b, domain: [1, 2, 3]
13
+ csp.add_variable :c, domain: [1, 2, 3]
14
+ csp.add_constraint(:a, :b) { |a, b| a > b }
15
+ csp.add_constraint(:b, :c) { |b, c| b > c }
16
+
17
+ solver = described_class.new(csp, max_steps: 1_000, random: Random.new(1))
18
+ result = solver.search
19
+
20
+ expect(result).to be_a(Hash)
21
+ expect(valid_solution?(csp, result)).to be(true)
22
+ end
23
+
24
+ it "respects preset assignments" do
25
+ csp.add_variable :a, value: 2
26
+ csp.add_variable :b, domain: [1, 2, 3]
27
+ csp.add_constraint(:a, :b) { |a, b| a > b }
28
+
29
+ solver = described_class.new(csp, max_steps: 1_000, random: Random.new(1))
30
+ result = solver.search
31
+
32
+ expect(result[:a]).to eq(2)
33
+ expect(valid_solution?(csp, result)).to be(true)
34
+ end
35
+
36
+ it "returns false when no solution is found within max_steps" do
37
+ csp.add_variable :a, domain: [1]
38
+ csp.add_variable :b, domain: [1]
39
+ csp.add_constraint(:a, :b) { |a, b| a != b }
40
+
41
+ solver = described_class.new(csp, max_steps: 10, random: Random.new(1))
42
+ expect(solver.search).to be(false)
43
+ end
44
+ end
data/winston.gemspec CHANGED
@@ -1,19 +1,20 @@
1
1
  Gem::Specification.new do |gem|
2
2
  gem.name = 'winston'
3
- gem.version = '0.0.2'
3
+ gem.version = '0.1.0'
4
4
  gem.authors = ['David Michael Nelson']
5
5
  gem.homepage = 'http://github.com/dmnelson/winston'
6
6
 
7
7
  gem.summary = 'Constraint Satisfaction Problem (CSP) implementation for Ruby'
8
- gem.description = gem.summary
8
+ gem.description = 'A small, practical CSP library for Ruby with multiple solvers, heuristics, and a DSL.'
9
9
  gem.license = 'MIT'
10
+ gem.required_ruby_version = ">= 3.0"
10
11
 
11
12
  gem.files = `git ls-files`.split($/)
12
13
  gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
13
14
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
15
  gem.require_paths = ["lib"]
15
16
 
16
- gem.add_development_dependency "bundler", "~> 1.3"
17
- gem.add_development_dependency "rake"
18
- gem.add_development_dependency 'rspec'
17
+ gem.add_development_dependency "bundler", "~> 2.4", ">= 2.4.22"
18
+ gem.add_development_dependency "rake", "~> 13.1"
19
+ gem.add_development_dependency "rspec", "~> 3.13"
19
20
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: winston
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Michael Nelson
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-03 00:00:00.000000000 Z
11
+ date: 2026-02-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -16,63 +16,78 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.3'
19
+ version: '2.4'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 2.4.22
20
23
  type: :development
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
27
  - - "~>"
25
28
  - !ruby/object:Gem::Version
26
- version: '1.3'
29
+ version: '2.4'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 2.4.22
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: rake
29
35
  requirement: !ruby/object:Gem::Requirement
30
36
  requirements:
31
- - - ">="
37
+ - - "~>"
32
38
  - !ruby/object:Gem::Version
33
- version: '0'
39
+ version: '13.1'
34
40
  type: :development
35
41
  prerelease: false
36
42
  version_requirements: !ruby/object:Gem::Requirement
37
43
  requirements:
38
- - - ">="
44
+ - - "~>"
39
45
  - !ruby/object:Gem::Version
40
- version: '0'
46
+ version: '13.1'
41
47
  - !ruby/object:Gem::Dependency
42
48
  name: rspec
43
49
  requirement: !ruby/object:Gem::Requirement
44
50
  requirements:
45
- - - ">="
51
+ - - "~>"
46
52
  - !ruby/object:Gem::Version
47
- version: '0'
53
+ version: '3.13'
48
54
  type: :development
49
55
  prerelease: false
50
56
  version_requirements: !ruby/object:Gem::Requirement
51
57
  requirements:
52
- - - ">="
58
+ - - "~>"
53
59
  - !ruby/object:Gem::Version
54
- version: '0'
55
- description: Constraint Satisfaction Problem (CSP) implementation for Ruby
56
- email:
60
+ version: '3.13'
61
+ description: A small, practical CSP library for Ruby with multiple solvers, heuristics,
62
+ and a DSL.
63
+ email:
57
64
  executables: []
58
65
  extensions: []
59
66
  extra_rdoc_files: []
60
67
  files:
61
68
  - ".gitignore"
69
+ - ".tool-versions"
62
70
  - ".travis.yml"
63
71
  - CHANGELOG.md
64
72
  - Gemfile
73
+ - Gemfile.lock
65
74
  - LICENSE
66
75
  - README.md
67
76
  - Rakefile
77
+ - bench/run.rb
68
78
  - lib/winston.rb
69
- - lib/winston/backtrack.rb
70
79
  - lib/winston/constraint.rb
71
80
  - lib/winston/constraints/all_different.rb
72
81
  - lib/winston/constraints/not_in_list.rb
73
82
  - lib/winston/csp.rb
74
83
  - lib/winston/domain.rb
84
+ - lib/winston/dsl.rb
85
+ - lib/winston/heuristics.rb
86
+ - lib/winston/solvers/backtrack.rb
87
+ - lib/winston/solvers/mac.rb
88
+ - lib/winston/solvers/min_conflicts.rb
75
89
  - lib/winston/variable.rb
90
+ - spec/examples/map_coloring_spec.rb
76
91
  - spec/examples/numbers_spec.rb
77
92
  - spec/examples/sudoku_spec.rb
78
93
  - spec/winston/backtrack_spec.rb
@@ -81,13 +96,17 @@ files:
81
96
  - spec/winston/constraints/not_in_list_spec.rb
82
97
  - spec/winston/csp_spec.rb
83
98
  - spec/winston/domain_spec.rb
99
+ - spec/winston/dsl_spec.rb
100
+ - spec/winston/heuristics_spec.rb
101
+ - spec/winston/mac_spec.rb
102
+ - spec/winston/min_conflicts_spec.rb
84
103
  - spec/winston/variable_spec.rb
85
104
  - winston.gemspec
86
105
  homepage: http://github.com/dmnelson/winston
87
106
  licenses:
88
107
  - MIT
89
108
  metadata: {}
90
- post_install_message:
109
+ post_install_message:
91
110
  rdoc_options: []
92
111
  require_paths:
93
112
  - lib
@@ -95,19 +114,19 @@ required_ruby_version: !ruby/object:Gem::Requirement
95
114
  requirements:
96
115
  - - ">="
97
116
  - !ruby/object:Gem::Version
98
- version: '0'
117
+ version: '3.0'
99
118
  required_rubygems_version: !ruby/object:Gem::Requirement
100
119
  requirements:
101
120
  - - ">="
102
121
  - !ruby/object:Gem::Version
103
122
  version: '0'
104
123
  requirements: []
105
- rubyforge_project:
106
- rubygems_version: 2.2.2
107
- signing_key:
124
+ rubygems_version: 3.5.22
125
+ signing_key:
108
126
  specification_version: 4
109
127
  summary: Constraint Satisfaction Problem (CSP) implementation for Ruby
110
128
  test_files:
129
+ - spec/examples/map_coloring_spec.rb
111
130
  - spec/examples/numbers_spec.rb
112
131
  - spec/examples/sudoku_spec.rb
113
132
  - spec/winston/backtrack_spec.rb
@@ -116,4 +135,8 @@ test_files:
116
135
  - spec/winston/constraints/not_in_list_spec.rb
117
136
  - spec/winston/csp_spec.rb
118
137
  - spec/winston/domain_spec.rb
138
+ - spec/winston/dsl_spec.rb
139
+ - spec/winston/heuristics_spec.rb
140
+ - spec/winston/mac_spec.rb
141
+ - spec/winston/min_conflicts_spec.rb
119
142
  - spec/winston/variable_spec.rb
@@ -1,40 +0,0 @@
1
- module Winston
2
- class Backtrack
3
- def initialize(csp)
4
- @csp = csp
5
- end
6
-
7
- def search(assignments = {})
8
- return assignments if complete?(assignments)
9
- var = select_unassigned_variable(assignments)
10
- domain_values(var).each do |value|
11
- assigned = assignments.merge(var.name => value)
12
- if valid?(var.name, assigned)
13
- result = search(assigned)
14
- return result if result
15
- end
16
- end
17
- false
18
- end
19
-
20
- private
21
-
22
- attr_reader :csp
23
-
24
- def complete?(assignments)
25
- assignments.size == csp.variables.size
26
- end
27
-
28
- def valid?(changed, assignments)
29
- csp.validate(changed, assignments)
30
- end
31
-
32
- def select_unassigned_variable(assignments)
33
- csp.variables.reject { |k,v| assignments.include?(k) }.each_value.first
34
- end
35
-
36
- def domain_values(var)
37
- csp.domain_for(var.name)
38
- end
39
- end
40
- end