ravensat 0.2.1 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76d71945818d257f977fde25685896cc7e8c7da0d290f329525beb5d7e5c2ca2
4
- data.tar.gz: 2c201e0b4815822e40f7b15fe8b1492a532c6665bc456a87c8c6f42227468f38
3
+ metadata.gz: 47afc062227efddad35ed4643e28c4eea8957ecb32c8aa34d3c69a9ff52d3a88
4
+ data.tar.gz: a39c4f15fb17321ded803e79b702c55150197b3a613a340ca4a728e0a6a27402
5
5
  SHA512:
6
- metadata.gz: 4a25a24ca9553e5ea66737f21d5cf9942b9202f106f3c0063d3a4fa84816b665797a761ac2399ef0de374e36bfa29a23c5dccaf7f78f0adfa73cd9e45558e591
7
- data.tar.gz: 74baa2d28e39e50dca6d280a411cc0fd5727f15dca74ada2ec94eb4a1c1c7d785e686293de0202d0e249c03a78685be3c823917519936d79438740c0bd861b90
6
+ metadata.gz: f29375d0971be0c3a721e9932fec0b69ad9db5b6df089e9d07fad557a82fac176faf7b71eea0ca73b8ee76ba60b413bfc799fd2de798c0b0fc513e39c8c0388d
7
+ data.tar.gz: a596fa29bfb55341a016ddde64b2e7d788a4877d4504cd5921cee81e486ee28207fd14027c81c0e113c8485edd0cc9bd5921beac75728da85e3390c1eab4d23f
@@ -0,0 +1,18 @@
1
+ name: Ruby
2
+
3
+ on: [push,pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v2
10
+ - name: Set up Ruby
11
+ uses: ruby/setup-ruby@v1
12
+ with:
13
+ ruby-version: 3.0.0
14
+ - name: Run the default task
15
+ run: |
16
+ gem install bundler -v 2.2.3
17
+ bundle install
18
+ bundle exec rake
data/README.md CHANGED
@@ -1,8 +1,50 @@
1
1
  # Ravensat
2
2
 
3
- Ravensat is an interface to SAT Solver .
4
- In order to use Ravensat, you need to install SAT Solver and specify the name of the Solver .
5
- (If you do not specify SAT Solver, it will use the one bundled in the gem .)
3
+ [![Ruby](https://github.com/matsuda0528/ravensat/actions/workflows/main.yml/badge.svg)](https://github.com/matsuda0528/ravensat/actions/workflows/main.yml)
4
+ [![Gem Version](https://badge.fury.io/rb/ravensat.svg)](https://badge.fury.io/rb/ravensat)
5
+ [![LICENSE](https://img.shields.io/github/license/matsuda0528/ravensat)](https://opensource.org/licenses/MIT)
6
+
7
+ Ravensat is an interface to SAT solver in Ruby.
8
+
9
+ In order to use Ravensat, you need to install SAT solver.
10
+ If you do not install SAT solver, it will use the one bundled in the gem.
11
+
12
+ About [SAT](https://en.wikipedia.org/wiki/Boolean_satisfiability_problem), [SAT solver](https://en.wikipedia.org/wiki/SAT_solver)
13
+
14
+ ## Description
15
+ To solve SAT, we usually use SAT solver.
16
+ Now, let's solve the following SAT with SAT solver.
17
+ <p align="center">
18
+ <img src="https://latex.codecogs.com/svg.image?\inline&space;\large&space;\bg{white}(1&space;\lor&space;\lnot&space;5&space;\lor&space;4)&space;\land&space;(\lnot&space;1&space;\lor&space;5&space;\lor&space;3&space;\lor&space;4)&space;\land&space;(\lnot&space;3&space;\lor&space;\lnot&space;4)" style="background-color:white;"/>
19
+ </p>
20
+
21
+ Most SAT solvers are input in [DIMACS Format](https://www.cs.utexas.edu/users/moore/acl2/manuals/current/manual/index-seo.php/SATLINK____DIMACS).
22
+ Converting the example SAT to DIMACS Format yields the following.
23
+
24
+ ```DIMACS Format
25
+ p cnf 5 3
26
+ 1 -5 4 0
27
+ -1 5 3 4 0
28
+ -3 -4 0
29
+ ```
30
+ DIMACS Format is widely distributed as an I/O format for SAT solver.
31
+ However, when solving a large SAT, the following problems occur.
32
+ - Need to create a file with thousands of lines.
33
+ - Confusion arises because of the inability to name variables meaningfully.
34
+
35
+ To solve these problems, Ravensat can be used.
36
+ Using Ravensat, propositional variables can be defined as local variables in Ruby.
37
+ ```ruby
38
+ fuji_is_the_highest_mountain_in_japan = Ravensat::VarNode.new
39
+ ```
40
+ In addition, you can write logical expressions intuitively.
41
+ ```ruby
42
+ x = Ravensat::VarNode.new
43
+ y = Ravensat::VarNode.new
44
+
45
+ # (x or y) and (not x or y)
46
+ (x | y) & (~x | y)
47
+ ```
6
48
 
7
49
 
8
50
  ## Installation
@@ -22,23 +64,81 @@ Or install it yourself as:
22
64
  $ gem install ravensat
23
65
 
24
66
  ## Usage
25
-
67
+ ### Basic Usage
68
+ This is a basic usage example of the library.
26
69
  ```ruby
27
70
  require 'ravensat'
28
71
 
72
+ # Define propositional variables
29
73
  a = Ravensat::VarNode.new
30
74
  b = Ravensat::VarNode.new
31
75
 
32
- a.value #=> nil
33
- b.value #=> nil
76
+ a.result #=> nil
77
+ b.result #=> nil
34
78
 
79
+ # Generate logical expressions as CNF
35
80
  logic = (a | b) & (~a | b) & (a | ~b)
36
81
 
82
+ # Launch SAT solver
37
83
  solver = Ravensat::Solver.new
38
84
  solver.solve logic #=> true(SAT)
39
85
 
40
- a.value #=> true
41
- b.value #=> true
86
+ # Refer to the satisfiability
87
+ a.result #=> true
88
+ b.result #=> true
89
+ ```
90
+
91
+ If you have SAT solver installed, you can write:
92
+ ```ruby
93
+ # Launch SAT solver
94
+ solver = Ravensat::Solver.new("<solver_name>")
95
+ solver.solve logic
96
+ ```
97
+ The available solvers are assumed to be those that can be I/O in the DIMACS Format.
98
+ At least, we have confirmed that it works properly with [MiniSat](https://github.com/niklasso/minisat).
99
+
100
+ If you do not use an external SAT solver, create a SAT solver object without any constructor arguments.
101
+ In that case, **Arcteryx**(the very simple SAT solver built into Ravensat) will launch.
102
+
103
+ ### Extension Usage
104
+ In Ravensat::Extension, C-like variable definitions are available.
105
+
106
+ *Note: In Ravensat::Extension, all undefined variables and methods are caught by method_missing method.*
107
+
108
+ ```ruby
109
+ require 'ravensat'
110
+
111
+ module Ravensat
112
+ module Extension
113
+ bool a, b
114
+ logic = (a | b) & (~a | b) & (a | ~b)
115
+
116
+ solver = Ravensat::Solver.new
117
+ solver.solve logic #=> true
118
+
119
+ a.result #=> true
120
+ b.result #=> true
121
+ end
122
+ end
123
+ ```
124
+
125
+ ### Extension Usage(CSP; Constraint Satisfaction Problem)
126
+ It is possible to define integer variables and to describe some integer constraints.
127
+ ```ruby
128
+ require 'ravensat'
129
+
130
+ module Ravensat
131
+ module Extension
132
+ int a(1..10), b(1..10)
133
+ constraint = (a.only_one & b.only_one & (a != b))
134
+
135
+ solver = Ravensat::Solver.new
136
+ solver.solve constraint #=> true
137
+
138
+ a.result #=> 1
139
+ b.result #=> 2
140
+ end
141
+ end
42
142
  ```
43
143
 
44
144
  ## Development
data/exe/ravensat ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
4
+ require 'ravensat'
5
+ require 'pry'
6
+
7
+ Pry.start(Ravensat::Extension)
@@ -0,0 +1,11 @@
1
+ module Ravensat
2
+ class InitialNode < Node
3
+ def &(object)
4
+ object
5
+ end
6
+
7
+ def |(object)
8
+ object
9
+ end
10
+ end
11
+ end
@@ -14,6 +14,20 @@ module Ravensat
14
14
  end
15
15
  end
16
16
 
17
+ def each_with_clause
18
+ case self
19
+ when AndNode, OrNode
20
+ @children.first.each_with_clause{|c| yield(c)}
21
+ yield(self)
22
+ @children.last.each_with_clause{|c| yield(c)}
23
+ when NotNode
24
+ yield(self)
25
+ @children.first.each_with_clause{|c| yield(c)}
26
+ when VarNode
27
+ yield(self)
28
+ end
29
+ end
30
+
17
31
  def &(object)
18
32
  AndNode.new(self, object)
19
33
  end
@@ -22,12 +36,6 @@ module Ravensat
22
36
  OrNode.new(self, object)
23
37
  end
24
38
 
25
- # def tree_text
26
- # self.each do |child|
27
- # child.to_s
28
- # end
29
- # end
30
-
31
39
  def to_s
32
40
  self.class.name
33
41
  end
@@ -13,5 +13,9 @@ module Ravensat
13
13
  def cnf?
14
14
  true
15
15
  end
16
+
17
+ def result
18
+ @value
19
+ end
16
20
  end
17
21
  end
@@ -0,0 +1,11 @@
1
+ module Ravensat
2
+ dir = File.dirname(__FILE__) + '/ast'
3
+
4
+ autoload :Node, dir + '/node.rb'
5
+ autoload :VarNode, dir + '/var_node.rb'
6
+ autoload :OprNode, dir + '/opr_node.rb'
7
+ autoload :AndNode, dir + '/and_node.rb'
8
+ autoload :OrNode, dir + '/or_node.rb'
9
+ autoload :NotNode, dir + '/not_node.rb'
10
+ autoload :InitialNode, dir + '/initial_node.rb'
11
+ end
@@ -8,19 +8,18 @@ module Ravensat
8
8
  def to_dimacs(formula)
9
9
  return nil unless formula.cnf?
10
10
 
11
- dimacs_header = "p cnf #{formula.vars_size} #{formula.clauses_size}"
11
+ dimacs_header = "p cnf #{formula.vars_size} #{formula.clauses_size}\n"
12
12
  dimacs_body = ""
13
13
  create_table(formula)
14
- formula.each do |node|
14
+ formula.each_with_clause do |node|
15
15
  case node
16
- when AndNode
17
- when OrNode then dimacs_body << "\n"
16
+ when AndNode then dimacs_body << " 0\n"
17
+ when OrNode then dimacs_body << " "
18
18
  when NotNode then dimacs_body << "-"
19
- when VarNode then dimacs_body << @name_table[node] << " "
19
+ when VarNode then dimacs_body << @name_table[node]
20
20
  end
21
21
  end
22
-
23
- dimacs_body.gsub!(/\n{2,}/, "\n").gsub!(/\n/, "0\n") << '0'
22
+ dimacs_body << " 0\n"
24
23
 
25
24
  dimacs_header + dimacs_body
26
25
  end
@@ -0,0 +1,6 @@
1
+ module Ravensat
2
+ dir = File.dirname(__FILE__) + '/dimacs'
3
+
4
+ autoload :DimacsEncoder, dir + '/dimacs_encoder.rb'
5
+ autoload :DimacsDecoder, dir + '/dimacs_decoder.rb'
6
+ end
@@ -0,0 +1,24 @@
1
+ module Ravensat
2
+ module Extension
3
+ module Domain
4
+ LOCAL_VARIABLE_TABLE = {}
5
+ def int(*vars)
6
+ vars.each do |var|
7
+ next if var.is_defined?
8
+ LOCAL_VARIABLE_TABLE[var.name] = Ravensat::Extension::IntegerVariable.new(var.name, var.args)
9
+ end
10
+ end
11
+
12
+ def bool(*vars)
13
+ vars.each do |var|
14
+ next if var.is_defined?
15
+ LOCAL_VARIABLE_TABLE[var.name] = Ravensat::Extension::BooleanVariable.new(var.name, var.args)
16
+ end
17
+ end
18
+
19
+ def method_missing(name, *args)
20
+ LOCAL_VARIABLE_TABLE[name] || UndefinedVariable.new(name, args)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ require 'forwardable'
2
+
3
+ module Ravensat
4
+ module Extension
5
+ class BooleanVariable < Variable
6
+ extend Forwardable
7
+ delegate Node.public_instance_methods(false) => :@var_node
8
+ delegate OprNode.public_instance_methods(false) => :@var_node
9
+ delegate VarNode.public_instance_methods(false) => :@var_node
10
+
11
+ def initialize(name, args)
12
+ super
13
+ @var_node = VarNode.new
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,63 @@
1
+ module Ravensat
2
+ module Extension
3
+ class IntegerVariable < Variable
4
+
5
+ attr_reader :var_nodes
6
+ def initialize(name, args)
7
+ super
8
+ return unless args.first.is_a? Range
9
+ @var_nodes = args.first.zip(Array.new(args.first.size){Ravensat::VarNode.new}).to_h
10
+ end
11
+
12
+ def ==(object)
13
+ case object
14
+ when Integer
15
+ return @var_nodes[object]
16
+ when IntegerVariable
17
+ result_formula = Ravensat::InitialNode.new
18
+ duplicated_keys = self.var_nodes.keys & object.var_nodes.keys
19
+
20
+ [self.var_nodes, object.var_nodes].repeated_permutation(duplicated_keys.size) do |var_nodes|
21
+ result_formula &= Ravensat::RavenClaw.alo(var_nodes.zip(duplicated_keys).map{|arr| arr.first[arr.last]})
22
+ end
23
+ return result_formula
24
+ else
25
+ raise ArgumentError
26
+ end
27
+ end
28
+
29
+ def !=(object)
30
+ case object
31
+ when Integer
32
+ return ~(@var_nodes[object])
33
+ when IntegerVariable
34
+ result_formula = Ravensat::InitialNode.new
35
+ duplicated_keys = self.var_nodes.keys & object.var_nodes.keys
36
+
37
+ duplicated_keys.each do |index|
38
+ result_formula &= ~(self.var_nodes[index]) | ~(object.var_nodes[index])
39
+ end
40
+ return result_formula
41
+ else
42
+ raise ArgumentError
43
+ end
44
+ end
45
+
46
+ def only_one
47
+ result_formula = Ravensat::InitialNode.new
48
+ result_formula &= Ravensat::RavenClaw.alo @var_nodes.values
49
+ result_formula &= Ravensat::RavenClaw.amo @var_nodes.values
50
+ result_formula
51
+ end
52
+
53
+ def result
54
+ result = @var_nodes.select{|key, var_node| var_node.value}.keys
55
+ if result.one?
56
+ result.first
57
+ else
58
+ result
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,9 @@
1
+ module Ravensat
2
+ module Extension
3
+ class UndefinedVariable < Variable
4
+ def is_defined?
5
+ false
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ module Ravensat
2
+ module Extension
3
+ class Variable
4
+
5
+ attr_reader :name, :args
6
+ def initialize(name, args)
7
+ @name = name
8
+ @args = args
9
+ end
10
+
11
+ def is_defined?
12
+ true
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ module Ravensat
2
+ module Extension
3
+ dir = File.dirname(__FILE__) + '/extension'
4
+
5
+ autoload :Domain, dir + '/domain.rb'
6
+ autoload :Variable, dir + '/variable/variable.rb'
7
+ autoload :BooleanVariable, dir + '/variable/boolean_variable.rb'
8
+ autoload :IntegerVariable, dir + '/variable/integer_variable.rb'
9
+ autoload :UndefinedVariable, dir + '/variable/undefined_variable.rb'
10
+
11
+ extend Domain
12
+ end
13
+ end
@@ -1,15 +1,19 @@
1
1
  module Ravensat
2
- class RavenClaw
3
- def initialize; end
4
-
5
- def self.alo(prop_vars)
6
- prop_vars.reduce(:|)
2
+ module RavenClaw
3
+ def self.alo(bool_vars)
4
+ bool_vars.reduce(:|)
7
5
  end
8
6
 
9
- def self.amo(prop_vars)
10
- prop_vars.combination(2).map do |e|
7
+ def self.amo(bool_vars)
8
+ bool_vars.combination(2).map do |e|
11
9
  e.map(&:~@).reduce(:|)
12
10
  end.reduce(:&)
13
11
  end
12
+
13
+ def self.all_different(*int_vars)
14
+ int_vars.combination(2).map do |int_var|
15
+ int_var.reduce(:!=)
16
+ end.reduce(:&)
17
+ end
14
18
  end
15
19
  end
@@ -5,15 +5,8 @@ module Ravensat
5
5
  attr_accessor :name
6
6
  def initialize( default_solver_name = "arcteryx" )
7
7
  @name = default_solver_name
8
- # @cnf = Array.new
9
- # @nr_vars
10
- # @nr_clses
11
8
  end
12
9
 
13
- # def <<( clause )
14
- # 'this is << method'
15
- # end
16
-
17
10
  def solve( cnf )
18
11
  encoder = DimacsEncoder.new
19
12
  @input_file = Tempfile.open(["ravensat",".cnf"])
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ravensat
4
- VERSION = "0.2.1"
4
+ VERSION = "0.3.1"
5
5
  end
data/lib/ravensat.rb CHANGED
@@ -7,17 +7,12 @@ module Ravensat
7
7
  ast = File.dirname(__FILE__) + '/ravensat/ast'
8
8
  arcteryx = File.dirname(__FILE__) + '/arcteryx'
9
9
 
10
+ require_relative ravensat + "/ast.rb"
11
+ require_relative ravensat + "/dimacs.rb"
12
+
10
13
  autoload :Solver, ravensat + '/solver.rb'
11
- autoload :DimacsEncoder, ravensat + '/dimacs_encoder.rb'
12
- autoload :DimacsDecoder, ravensat + '/dimacs_decoder.rb'
13
14
  autoload :RavenClaw, ravensat + '/ravenclaw.rb'
14
-
15
- autoload :Node, ast + '/node.rb'
16
- autoload :VarNode, ast + '/var_node.rb'
17
- autoload :OprNode, ast + '/opr_node.rb'
18
- autoload :AndNode, ast + '/and_node.rb'
19
- autoload :OrNode, ast + '/or_node.rb'
20
- autoload :NotNode, ast + '/not_node.rb'
15
+ autoload :Extension, ravensat + '/extension.rb'
21
16
 
22
17
  autoload :Arcteryx, arcteryx + '/arcteryx.rb'
23
18
  end
metadata CHANGED
@@ -1,22 +1,24 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ravensat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.1
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-05-16 00:00:00.000000000 Z
11
+ date: 2022-05-24 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
15
15
  - p99y92og@s.okayama-u.ac.jp
16
- executables: []
16
+ executables:
17
+ - ravensat
17
18
  extensions: []
18
19
  extra_rdoc_files: []
19
20
  files:
21
+ - ".github/workflows/main.yml"
20
22
  - ".gitignore"
21
23
  - ".rspec"
22
24
  - ".ruby-version"
@@ -28,17 +30,27 @@ files:
28
30
  - bin/console
29
31
  - bin/setup
30
32
  - example/magic_square_3x3.rb
33
+ - exe/ravensat
31
34
  - lib/arcteryx/arcteryx.rb
32
35
  - lib/arcteryx/cnf.rb
33
36
  - lib/ravensat.rb
37
+ - lib/ravensat/ast.rb
34
38
  - lib/ravensat/ast/and_node.rb
39
+ - lib/ravensat/ast/initial_node.rb
35
40
  - lib/ravensat/ast/node.rb
36
41
  - lib/ravensat/ast/not_node.rb
37
42
  - lib/ravensat/ast/opr_node.rb
38
43
  - lib/ravensat/ast/or_node.rb
39
44
  - lib/ravensat/ast/var_node.rb
40
- - lib/ravensat/dimacs_decoder.rb
41
- - lib/ravensat/dimacs_encoder.rb
45
+ - lib/ravensat/dimacs.rb
46
+ - lib/ravensat/dimacs/dimacs_decoder.rb
47
+ - lib/ravensat/dimacs/dimacs_encoder.rb
48
+ - lib/ravensat/extension.rb
49
+ - lib/ravensat/extension/domain.rb
50
+ - lib/ravensat/extension/variable/boolean_variable.rb
51
+ - lib/ravensat/extension/variable/integer_variable.rb
52
+ - lib/ravensat/extension/variable/undefined_variable.rb
53
+ - lib/ravensat/extension/variable/variable.rb
42
54
  - lib/ravensat/ravenclaw.rb
43
55
  - lib/ravensat/solver.rb
44
56
  - lib/ravensat/version.rb