ravensat 0.3.2 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f369c1036cde3ea2ff754109809d5b4ce62f826a2ab409e9ec19fdd07ff341dc
4
- data.tar.gz: 76df72fb98e5140fcd1b2b394643f23de5218ea47f4ec03f450dc0a05074f801
3
+ metadata.gz: 78a4fe449b52b526589311a1d8bd4203e121e0d490ff86a870f08833328bda05
4
+ data.tar.gz: f7dfe20367b98c416ed30cdbf4061b1662268181007f188942d6f393d0aff687
5
5
  SHA512:
6
- metadata.gz: b59a47f2b7a899e49d506f839c892178523ea3920e611278e0f31399dc1c0e5c868bb8f8a74adab5a848f26f2d2efb8779f6391a20ec566ec177a1fdacfb4565
7
- data.tar.gz: 9ed85ceb8abc589197c9241679bb5c5486ce525e427d91f84568dcc4b4ab7a58b0d110688cba999d78dacc9fd0e067d6e4f3908ef87d2d3851ed7c30331d22ca
6
+ metadata.gz: c7a341443b901bb3d3717a380af641956d7d9552d217e88250c6146f5fa9f1c0dd81fef9db5f5f38b37dca40f092030c6c99ef967ba534816e7a160b3525074e
7
+ data.tar.gz: 4d994c5983f2d66d988140aec41385533039dac96e62c6952416604efa2ecf9ede311241a6e4c5eb322da8396128f44ff6b5fcf3f8a55e36ea4ce5d8a0ea6be2
data/README.md CHANGED
@@ -1,24 +1,26 @@
1
- # Ravensat
2
-
3
1
  [![GitHub Actions](https://github.com/matsuda0528/ravensat/actions/workflows/main.yml/badge.svg)](https://github.com/matsuda0528/ravensat/actions/workflows/main.yml)
4
2
  [![Gem Version](https://badge.fury.io/rb/ravensat.svg)](https://badge.fury.io/rb/ravensat)
5
3
  [![LICENSE](https://img.shields.io/github/license/matsuda0528/ravensat)](https://opensource.org/licenses/MIT)
6
4
 
7
- [![GitHub Pages](https://img.shields.io/badge/GitHub%20Pages--brightgreen.svg?logo=github&style=social)](https://matsuda0528.github.io/ravensat/)
5
+ <!-- [![GitHub Pages](https://img.shields.io/badge/GitHub%20Pages--brightgreen.svg?logo=github&style=social)](https://matsuda0528.github.io/ravensat/) -->
8
6
 
9
- Ravensat is an interface to SAT solver in Ruby.
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
- To solve these problems, Ravensat can be used.
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
- fuji_is_the_highest_mountain_in_japan = Ravensat::VarNode.new
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) # (x or y) and (not x or 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
@@ -1,4 +1,12 @@
1
1
  module Ravensat
2
2
  class AndNode < OprNode
3
+ def &(object)
4
+ @children.append object
5
+ self
6
+ end
7
+
8
+ def to_dimacs
9
+ " 0\n"
10
+ end
3
11
  end
4
12
  end
@@ -7,25 +7,57 @@ module Ravensat
7
7
  @children = []
8
8
  end
9
9
 
10
- # def each
11
- # yield(self)
12
- # @children.each do |child|
13
- # child.each {|c| yield(c)}
14
- # end
15
- # end
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
- case self
19
- when AndNode, OrNode
20
- @children.first.each{|c| yield(c)}
21
- yield(self)
22
- @children.last.each{|c| yield(c)}
23
- when NotNode
24
- yield(self)
25
- @children.first.each{|c| yield(c)}
26
- when VarNode
27
- yield(self)
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
@@ -3,5 +3,9 @@ module Ravensat
3
3
  def ~@
4
4
  @children.first
5
5
  end
6
+
7
+ def to_dimacs
8
+ "-"
9
+ end
6
10
  end
7
11
  end
@@ -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
- introductory_variables = []
16
+ commander_variables = []
16
17
  formula = Ravensat::InitialNode.new
17
- bool_vars.each_slice(2) do |e|
18
+ bool_vars.each_slice(2) do |g|
18
19
  c = Ravensat::VarNode.new
19
- subset = e << ~c
20
+ subset = g << ~c
20
21
  formula &= pairwise_amo(subset)
21
22
  formula &= alo(subset)
22
- introductory_variables << c
23
+ commander_variables << c
23
24
  end
24
25
 
25
26
  if m < 6
26
- formula &= pairwise_amo(introductory_variables)
27
+ formula &= pairwise_amo(commander_variables)
27
28
  else
28
- formula &= commander_amo(introductory_variables)
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, name_table)
4
- inverted_name_table = name_table.invert
3
+ def decode(model, cnf)
4
+ prop_vars = cnf.vars
5
5
  case model.first
6
6
  when "SAT"
7
- model.last.split.each do |e|
8
- if e == '0'
9
- next
10
- elsif e[0] == "-"
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
- create_table(formula)
14
- formula.each do |node|
15
- case node
16
- when AndNode then dimacs_body << " 0\n"
17
- when OrNode then dimacs_body << " "
18
- when NotNode then dimacs_body << "-"
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 create_table(formula)
28
- @name_table = formula.vars.zip((1..formula.vars.size).map(&:to_s)).to_h
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
@@ -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
- system("#{@name} #{@input_file.to_path} #{@output_file.to_path}")
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, encoder.name_table)
29
+ decoder.decode(model, cnf)
28
30
  end
29
31
 
30
32
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ravensat
4
- VERSION = "0.3.2"
4
+ VERSION = "1.0.2"
5
5
  end
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.3.2
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-06-15 00:00:00.000000000 Z
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