ravensat 0.3.2 → 1.0.2
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.
- checksums.yaml +4 -4
- data/README.md +21 -11
- data/lib/ravensat/ast/and_node.rb +8 -0
- data/lib/ravensat/ast/node.rb +51 -16
- data/lib/ravensat/ast/not_node.rb +4 -0
- data/lib/ravensat/ast/or_node.rb +9 -0
- data/lib/ravensat/ast/var_node.rb +6 -1
- data/lib/ravensat/claw.rb +7 -6
- data/lib/ravensat/dimacs/dimacs_decoder.rb +6 -12
- data/lib/ravensat/dimacs/dimacs_encoder.rb +10 -13
- data/lib/ravensat/solver.rb +5 -3
- data/lib/ravensat/version.rb +1 -1
- metadata +2 -3
- data/Gemfile.lock +0 -45
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 78a4fe449b52b526589311a1d8bd4203e121e0d490ff86a870f08833328bda05
|
4
|
+
data.tar.gz: f7dfe20367b98c416ed30cdbf4061b1662268181007f188942d6f393d0aff687
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c7a341443b901bb3d3717a380af641956d7d9552d217e88250c6146f5fa9f1c0dd81fef9db5f5f38b37dca40f092030c6c99ef967ba534816e7a160b3525074e
|
7
|
+
data.tar.gz: 4d994c5983f2d66d988140aec41385533039dac96e62c6952416604efa2ecf9ede311241a6e4c5eb322da8396128f44ff6b5fcf3f8a55e36ea4ce5d8a0ea6be2
|
data/README.md
CHANGED
@@ -1,24 +1,26 @@
|
|
1
|
-
# Ravensat
|
2
|
-
|
3
1
|
[](https://github.com/matsuda0528/ravensat/actions/workflows/main.yml)
|
4
2
|
[](https://badge.fury.io/rb/ravensat)
|
5
3
|
[](https://opensource.org/licenses/MIT)
|
6
4
|
|
7
|
-
[](https://matsuda0528.github.io/ravensat/)
|
5
|
+
<!-- [](https://matsuda0528.github.io/ravensat/) -->
|
8
6
|
|
9
|
-
Ravensat
|
7
|
+
# Ravensat
|
10
8
|
|
9
|
+
Ravensat provides an intuitive interface for working with SAT solver.
|
10
|
+
SAT solver is a useful tool for solving various problems, but it is not user-friendly.
|
11
|
+
Ravensat wraps the SAT solver and makes it easier to use.
|
11
12
|
In order to use Ravensat, you need to install SAT solver.
|
12
13
|
If you do not install SAT solver, it will use the one bundled in the gem.
|
13
14
|
|
14
15
|
About [SAT](https://en.wikipedia.org/wiki/Boolean_satisfiability_problem), [SAT solver](https://en.wikipedia.org/wiki/SAT_solver)
|
15
16
|
|
16
17
|
## Description
|
17
|
-
To solve SAT, we usually use SAT solver.
|
18
|
+
To solve SAT(Boolean Satisfiability Problem), we usually use SAT solver.
|
18
19
|
Now, let's solve the following SAT with SAT solver.
|
19
20
|
|
20
21
|
$$(p_{1} \lor \lnot p_{5} \lor p_{4}) \land (\lnot p_{1} \lor p_{5} \lor p_{3} \lor p_{4}) \land (\lnot p_{3} \lor \lnot p_{4})$$
|
21
22
|
|
23
|
+
To solve the above SAT, give it to the SAT solver.
|
22
24
|
Most SAT solvers are input in [DIMACS Format](https://www.cs.utexas.edu/users/moore/acl2/manuals/current/manual/index-seo.php/SATLINK____DIMACS).
|
23
25
|
Converting the example SAT to DIMACS Format yields the following.
|
24
26
|
|
@@ -34,22 +36,31 @@ However, when solving a large SAT, the following problems occur.
|
|
34
36
|
- Need to create a file with thousands of lines.
|
35
37
|
- Confusion arises because of the inability to name variables meaningfully.
|
36
38
|
|
37
|
-
|
39
|
+
Therefore, we need an interface that can flexibly determine the names of variables and imperatively write loginal expressions.
|
40
|
+
To achieve these requirements, we are developing Ravensat.
|
38
41
|
Using Ravensat, propositional variables can be defined as local variables in Ruby.
|
39
42
|
|
40
43
|
```ruby
|
41
|
-
|
44
|
+
John_is_a_male = Ravensat::VarNode.new
|
42
45
|
```
|
43
46
|
|
44
|
-
In addition, you can write logical expressions intuitively.
|
47
|
+
In addition, you can write logical expressions intuitively and imperatively.
|
45
48
|
|
46
49
|
```ruby
|
47
50
|
x = Ravensat::VarNode.new
|
48
51
|
y = Ravensat::VarNode.new
|
49
52
|
|
50
|
-
(x | y) & (~x | y)
|
53
|
+
(x | y) & (~x | y)
|
51
54
|
```
|
52
55
|
|
56
|
+
```ruby
|
57
|
+
x = Ravensat::VarNode.new
|
58
|
+
y = Ravensat::VarNode.new
|
59
|
+
z = Ravensat::VarNode.new
|
60
|
+
|
61
|
+
# (~x | ~y) & (~x | ~z) & (~y | ~z)
|
62
|
+
Ravensat::Claw.pairwise_amo [x,y,z]
|
63
|
+
```
|
53
64
|
|
54
65
|
## Installation
|
55
66
|
|
@@ -102,7 +113,7 @@ At least, we have confirmed that it works properly with [MiniSat](https://github
|
|
102
113
|
If you do not use an external SAT solver, create a SAT solver object without any constructor arguments.
|
103
114
|
In that case, **Arcteryx**(the very simple SAT solver built into Ravensat) will launch.
|
104
115
|
|
105
|
-
### Extension Usage
|
116
|
+
### Extension Usage(prototype)
|
106
117
|
In Ravensat::Extension, C-like variable definitions are available.
|
107
118
|
|
108
119
|
*Note: In Ravensat::Extension, all undefined variables and methods are caught by method_missing method.*
|
@@ -124,7 +135,6 @@ module Ravensat
|
|
124
135
|
end
|
125
136
|
```
|
126
137
|
|
127
|
-
### Extension Usage(CSP; Constraint Satisfaction Problem)
|
128
138
|
It is possible to define integer variables and to describe some integer constraints.
|
129
139
|
|
130
140
|
```ruby
|
data/lib/ravensat/ast/node.rb
CHANGED
@@ -7,25 +7,57 @@ module Ravensat
|
|
7
7
|
@children = []
|
8
8
|
end
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
10
|
+
def each_by_descriptive
|
11
|
+
node_stack = [[self, self.children.clone]] #[[parent, children], ...]
|
12
|
+
|
13
|
+
until node_stack.empty?
|
14
|
+
current_parent, current_children = node_stack.pop
|
15
|
+
current_node = current_children.shift
|
16
|
+
|
17
|
+
case current_node
|
18
|
+
when AndNode
|
19
|
+
node_stack.push [current_parent, current_children.clone]
|
20
|
+
node_stack.push [current_node, current_node.children.clone]
|
21
|
+
when OrNode
|
22
|
+
node_stack.push [current_parent, current_children.clone]
|
23
|
+
node_stack.push [current_node, current_node.children.clone]
|
24
|
+
when NotNode
|
25
|
+
yield(current_node)
|
26
|
+
yield(current_node.children.first)
|
27
|
+
|
28
|
+
if current_children.empty?
|
29
|
+
yield(node_stack.last.first)
|
30
|
+
else
|
31
|
+
yield(current_parent)
|
32
|
+
node_stack.push [current_parent, current_children.clone]
|
33
|
+
end
|
34
|
+
when VarNode, Extension::BooleanVariable
|
35
|
+
yield(current_node)
|
36
|
+
|
37
|
+
if current_children.empty?
|
38
|
+
yield(node_stack.last.first)
|
39
|
+
else
|
40
|
+
yield(current_parent)
|
41
|
+
node_stack.push [current_parent, current_children.clone]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
16
46
|
|
17
47
|
def each
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
48
|
+
return to_enum unless block_given?
|
49
|
+
node_stack = [self]
|
50
|
+
|
51
|
+
until node_stack.empty?
|
52
|
+
current_node = node_stack.shift
|
53
|
+
next unless current_node
|
54
|
+
|
55
|
+
yield current_node
|
56
|
+
|
57
|
+
node_stack = node_stack.concat(current_node.children)
|
28
58
|
end
|
59
|
+
|
60
|
+
self if block_given?
|
29
61
|
end
|
30
62
|
|
31
63
|
def &(object)
|
@@ -40,6 +72,9 @@ module Ravensat
|
|
40
72
|
self.class.name
|
41
73
|
end
|
42
74
|
|
75
|
+
def to_dimacs
|
76
|
+
end
|
77
|
+
|
43
78
|
def cnf?
|
44
79
|
@children.map(&:cnf?).reduce(:&)
|
45
80
|
end
|
data/lib/ravensat/ast/or_node.rb
CHANGED
@@ -1,8 +1,17 @@
|
|
1
1
|
module Ravensat
|
2
2
|
class OrNode < OprNode
|
3
|
+
def |(object)
|
4
|
+
@children.append object
|
5
|
+
self
|
6
|
+
end
|
7
|
+
|
3
8
|
def cnf?
|
4
9
|
return false if @children.any?{|node| node.is_a? AndNode}
|
5
10
|
@children.map(&:cnf?).reduce(:&)
|
6
11
|
end
|
12
|
+
|
13
|
+
def to_dimacs
|
14
|
+
" "
|
15
|
+
end
|
7
16
|
end
|
8
17
|
end
|
@@ -1,9 +1,10 @@
|
|
1
1
|
module Ravensat
|
2
2
|
class VarNode < Node
|
3
|
-
attr_accessor :value
|
3
|
+
attr_accessor :value, :dimacs_name
|
4
4
|
def initialize
|
5
5
|
@value
|
6
6
|
@children = []
|
7
|
+
@dimacs_name
|
7
8
|
end
|
8
9
|
|
9
10
|
def ~@
|
@@ -17,5 +18,9 @@ module Ravensat
|
|
17
18
|
def result
|
18
19
|
@value
|
19
20
|
end
|
21
|
+
|
22
|
+
def to_dimacs
|
23
|
+
@dimacs_name
|
24
|
+
end
|
20
25
|
end
|
21
26
|
end
|
data/lib/ravensat/claw.rb
CHANGED
@@ -10,22 +10,23 @@ module Ravensat
|
|
10
10
|
end.reduce(:&)
|
11
11
|
end
|
12
12
|
|
13
|
+
# NOTE: Klieber, W. and Kwon, G.: Efficient CNF Encoding for Selecting 1 from N Objects (2007).
|
13
14
|
def self.commander_amo(bool_vars)
|
14
15
|
m = bool_vars.size / 2
|
15
|
-
|
16
|
+
commander_variables = []
|
16
17
|
formula = Ravensat::InitialNode.new
|
17
|
-
bool_vars.each_slice(2) do |
|
18
|
+
bool_vars.each_slice(2) do |g|
|
18
19
|
c = Ravensat::VarNode.new
|
19
|
-
subset =
|
20
|
+
subset = g << ~c
|
20
21
|
formula &= pairwise_amo(subset)
|
21
22
|
formula &= alo(subset)
|
22
|
-
|
23
|
+
commander_variables << c
|
23
24
|
end
|
24
25
|
|
25
26
|
if m < 6
|
26
|
-
formula &= pairwise_amo(
|
27
|
+
formula &= pairwise_amo(commander_variables)
|
27
28
|
else
|
28
|
-
formula &= commander_amo(
|
29
|
+
formula &= commander_amo(commander_variables)
|
29
30
|
end
|
30
31
|
end
|
31
32
|
|
@@ -1,19 +1,13 @@
|
|
1
1
|
module Ravensat
|
2
2
|
class DimacsDecoder
|
3
|
-
def decode(model,
|
4
|
-
|
3
|
+
def decode(model, cnf)
|
4
|
+
prop_vars = cnf.vars
|
5
5
|
case model.first
|
6
6
|
when "SAT"
|
7
|
-
model.last.split.
|
8
|
-
if e == '0'
|
9
|
-
|
10
|
-
|
11
|
-
index = e.slice(1..-1)
|
12
|
-
inverted_name_table[index].value = false
|
13
|
-
else
|
14
|
-
index = e
|
15
|
-
inverted_name_table[index].value = true
|
16
|
-
end
|
7
|
+
model.last.split.each_with_index do |e,i|
|
8
|
+
break if e == '0'
|
9
|
+
var = prop_vars[i]
|
10
|
+
var.value = !(e[0] == '-')
|
17
11
|
end
|
18
12
|
true
|
19
13
|
when "UNSAT"
|
@@ -6,26 +6,23 @@ module Ravensat
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def to_dimacs(formula)
|
9
|
-
return nil unless formula.cnf?
|
10
|
-
|
11
9
|
dimacs_header = "p cnf #{formula.vars_size} #{formula.clauses_size}\n"
|
12
10
|
dimacs_body = ""
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
when VarNode then dimacs_body << @name_table[node]
|
20
|
-
end
|
11
|
+
return nil unless formula.cnf?
|
12
|
+
|
13
|
+
set_dimacs_name(formula)
|
14
|
+
|
15
|
+
formula.each_by_descriptive do |node|
|
16
|
+
dimacs_body << node.to_dimacs
|
21
17
|
end
|
22
|
-
dimacs_body << " 0\n"
|
23
18
|
|
24
19
|
dimacs_header + dimacs_body
|
25
20
|
end
|
26
21
|
|
27
|
-
def
|
28
|
-
|
22
|
+
def set_dimacs_name(formula)
|
23
|
+
formula.vars.each_with_index do |node, i|
|
24
|
+
node.dimacs_name = (i+1).to_s
|
25
|
+
end
|
29
26
|
end
|
30
27
|
end
|
31
28
|
end
|
data/lib/ravensat/solver.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'tempfile'
|
2
|
+
require 'open3'
|
2
3
|
|
3
4
|
module Ravensat
|
4
5
|
class Solver
|
@@ -7,7 +8,7 @@ module Ravensat
|
|
7
8
|
@name = default_solver_name
|
8
9
|
end
|
9
10
|
|
10
|
-
def solve( cnf )
|
11
|
+
def solve( cnf , solver_log: false)
|
11
12
|
encoder = DimacsEncoder.new
|
12
13
|
@input_file = Tempfile.open(["ravensat",".cnf"])
|
13
14
|
@output_file = Tempfile.open(["ravensat",".mdl"])
|
@@ -19,12 +20,13 @@ module Ravensat
|
|
19
20
|
when "arcteryx"
|
20
21
|
Arcteryx.solve(@input_file.to_path, @output_file.to_path)
|
21
22
|
else
|
22
|
-
|
23
|
+
result, err, status = Open3.capture3("#{@name} #{@input_file.to_path} #{@output_file.to_path}")
|
24
|
+
puts result if solver_log
|
23
25
|
end
|
24
26
|
|
25
27
|
decoder = DimacsDecoder.new
|
26
28
|
model = @output_file.read.split("\n")
|
27
|
-
decoder.decode(model,
|
29
|
+
decoder.decode(model, cnf)
|
28
30
|
end
|
29
31
|
|
30
32
|
end
|
data/lib/ravensat/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ravensat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- rikuto matsuda
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-07-29 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
@@ -23,7 +23,6 @@ files:
|
|
23
23
|
- ".rspec"
|
24
24
|
- ".ruby-version"
|
25
25
|
- Gemfile
|
26
|
-
- Gemfile.lock
|
27
26
|
- LICENSE.txt
|
28
27
|
- README.md
|
29
28
|
- Rakefile
|
data/Gemfile.lock
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
ravensat (0.1.1)
|
5
|
-
|
6
|
-
GEM
|
7
|
-
remote: https://rubygems.org/
|
8
|
-
specs:
|
9
|
-
byebug (11.1.3)
|
10
|
-
coderay (1.1.3)
|
11
|
-
diff-lcs (1.4.4)
|
12
|
-
method_source (1.0.0)
|
13
|
-
pry (0.14.0)
|
14
|
-
coderay (~> 1.1)
|
15
|
-
method_source (~> 1.0)
|
16
|
-
pry-byebug (3.8.0)
|
17
|
-
byebug (~> 11.0)
|
18
|
-
pry (~> 0.10)
|
19
|
-
rake (13.0.3)
|
20
|
-
rspec (3.10.0)
|
21
|
-
rspec-core (~> 3.10.0)
|
22
|
-
rspec-expectations (~> 3.10.0)
|
23
|
-
rspec-mocks (~> 3.10.0)
|
24
|
-
rspec-core (3.10.1)
|
25
|
-
rspec-support (~> 3.10.0)
|
26
|
-
rspec-expectations (3.10.1)
|
27
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
28
|
-
rspec-support (~> 3.10.0)
|
29
|
-
rspec-mocks (3.10.2)
|
30
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
31
|
-
rspec-support (~> 3.10.0)
|
32
|
-
rspec-support (3.10.2)
|
33
|
-
|
34
|
-
PLATFORMS
|
35
|
-
x86_64-linux
|
36
|
-
|
37
|
-
DEPENDENCIES
|
38
|
-
pry
|
39
|
-
pry-byebug
|
40
|
-
rake (~> 13.0)
|
41
|
-
ravensat!
|
42
|
-
rspec (~> 3.0)
|
43
|
-
|
44
|
-
BUNDLED WITH
|
45
|
-
2.2.24
|