dpll_solver 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +2 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +62 -0
  7. data/Rakefile +9 -0
  8. data/dpll_solver.gemspec +35 -0
  9. data/lib/dpll_solver.rb +115 -0
  10. data/lib/dpll_solver/atomic_formula.rb +34 -0
  11. data/lib/dpll_solver/formulas/and.rb +54 -0
  12. data/lib/dpll_solver/formulas/binary_formula.rb +33 -0
  13. data/lib/dpll_solver/formulas/clause.rb +52 -0
  14. data/lib/dpll_solver/formulas/falsum.rb +18 -0
  15. data/lib/dpll_solver/formulas/literal.rb +25 -0
  16. data/lib/dpll_solver/formulas/not.rb +84 -0
  17. data/lib/dpll_solver/formulas/or.rb +64 -0
  18. data/lib/dpll_solver/formulas/variable.rb +26 -0
  19. data/lib/dpll_solver/formulas/verum.rb +18 -0
  20. data/lib/dpll_solver/heuristics/most_frequent_literal.rb +22 -0
  21. data/lib/dpll_solver/parsers/dimacs_parser.rb +46 -0
  22. data/lib/dpll_solver/parsers/parser.rb +53 -0
  23. data/lib/dpll_solver/version.rb +3 -0
  24. data/spec/dpll/dpll_solver_spec.rb +114 -0
  25. data/spec/formulas/and_spec.rb +75 -0
  26. data/spec/formulas/clause_spec.rb +55 -0
  27. data/spec/formulas/falsum_spec.rb +42 -0
  28. data/spec/formulas/formulas_spec.rb +47 -0
  29. data/spec/formulas/literal_spec.rb +26 -0
  30. data/spec/formulas/not_spec.rb +77 -0
  31. data/spec/formulas/or_spec.rb +74 -0
  32. data/spec/formulas/variable_spec.rb +44 -0
  33. data/spec/formulas/verum_spec.rb +42 -0
  34. data/spec/heuristics/most_frequent_literal_spec.rb +19 -0
  35. data/spec/parsers/dimacs_parser_spec.rb +23 -0
  36. data/spec/parsers/grammar_spec.rb +54 -0
  37. data/spec/parsers/parser_spec.rb +23 -0
  38. data/spec/parsers/transformer_spec.rb +30 -0
  39. data/spec/spec_helper.rb +4 -0
  40. metadata +183 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6b1fe3e41c37f472f5e8dca7543336d2450aa8a1
4
+ data.tar.gz: 34ac78a5ab53680c20c94ba820763cd5b054e599
5
+ SHA512:
6
+ metadata.gz: a6313282bb0ff87f335a751a5bf0bab610b0df0d81f8c2a074c0919a34151dc59548849107e4060bc9a70ddd1d01fab316c96e29dd5bfe4ed4dfe59c2d50530f
7
+ data.tar.gz: 54390da348247318cd7657eaef7fa1024cdc23ddf216f0bc8cd8fe236b1b67a1de2cf7cdbb485df515fac26fb69a78493d149ef841b1bb1fc3576198f4a4cd97
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.sw?
2
+ *.gem
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 thebluber
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # DpllSolver
2
+
3
+ This is a small SAT solving tool using [DPLL algorithm](http://en.wikipedia.org/wiki/DPLL_algorithm). It can evaluate both a DIMACS file (max. 20 variables, 80 lines) and a boolean expression in string format i.e. '(x1 * (x3 + x4))' and determin wether the input is satisfiable.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'dpll_solver'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install dpll_solver
18
+
19
+ ## Usage
20
+
21
+ #### Utilities
22
+ ```ruby
23
+ require 'dpll_solver'
24
+
25
+ #to_cnf converts a boolean string to formula in conjunctive normal form
26
+ DpllSolver::Util.to_cnf('(x1 + -x6)').to_s # => "(x1 OR -x6)"
27
+
28
+ #to_clause converts a boolean expression to a set of clauses
29
+ class Set
30
+ def to_s
31
+ map(&:to_s)
32
+ end
33
+ end
34
+ DpllSolver::Util.to_clause('(x1 + -x6)').to_s # => ["{x1, -x6}"]
35
+
36
+ #SAT? determins wether a boolean expression is satisfiable
37
+ DpllSolver::Util.SAT?('T') # => true
38
+ DpllSolver::Util.SAT?('1') # => true
39
+ DpllSolver::Util.SAT?('F') # => false
40
+ DpllSolver::Util.SAT?('0') # => false
41
+ DpllSolver::Util.SAT?('(x1 + -x6)') # => true
42
+
43
+ #dimacs_SAT? determins wether a cnf file in DIMACS format is satisfiable
44
+ DpllSolver::Util.dimacs_SAT?('resources/dimacs/yes/uf20-01.cnf') # => true
45
+ DpllSolver::Util.dimacs_SAT?('resources/dimacs/no/uf20-01.cnf') # => false
46
+
47
+ #tautology?
48
+ DpllSolver::Util.tautology?('1') # => true
49
+ DpllSolver::Util.tautology?('(x1 + -x6)') # => false
50
+
51
+ #contradiction?
52
+ DpllSolver::Util.contradiction?('0') # => true
53
+ DpllSolver::Util.contradiction?('(x1 + -x6)') # => false
54
+ ```
55
+
56
+ ## Contributing
57
+
58
+ 1. Fork it
59
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
60
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
61
+ 4. Push to the branch (`git push origin my-new-feature`)
62
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'bundler'
2
+ require "rspec/core/rake_task"
3
+
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ desc "Run specs"
7
+ RSpec::Core::RakeTask.new(:spec) do |t|
8
+ t.pattern = "./spec/**/*_spec.rb"
9
+ end
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'dpll_solver/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "dpll_solver"
8
+ spec.version = DpllSolver::VERSION
9
+ spec.authors = ["thebluber"]
10
+ spec.email = ["thebluber@gmail.com"]
11
+ spec.description = "This is a small SAT solving tool for either DIMACS file input (max. 20 variables, 80 lines) or boolean expressions in string format i.e. '(x1 * (x3 + x4))'."
12
+ spec.summary = "SAT solver using DPLL algorithm"
13
+ spec.homepage = "https://github.com/thebluber/dpll_solver"
14
+ spec.license = "MIT"
15
+
16
+ resource_files = [
17
+ 'resources/dimacs/no/uf20-01.cnf',
18
+ 'resources/dimacs/no/uf20-02.cnf',
19
+ 'resources/dimacs/yes/uf20-01.cnf',
20
+ 'resources/dimacs/yes/uf20-02.cnf',
21
+ 'resources/dimacs/yes/uf20-03.cnf',
22
+ 'resources/dimacs/yes/uf20-04.cnf'
23
+ ]
24
+ spec.files = `git ls-files`.split($/).reject{|file| resource_files.include? file}
25
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
26
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
27
+ spec.require_paths = ["lib"]
28
+
29
+ spec.add_development_dependency "bundler"
30
+ spec.add_development_dependency "rake"
31
+ spec.add_development_dependency "rspec"
32
+ spec.add_development_dependency "binding_of_caller"
33
+ spec.add_development_dependency "pry"
34
+ spec.add_dependency "parslet"
35
+ end
@@ -0,0 +1,115 @@
1
+ # Require all of the Ruby files in the given directory.
2
+ # #
3
+ # # path - The String relative path from here to the directory.
4
+ # #
5
+ # # Returns nothing.
6
+ def require_all(path)
7
+ glob = File.join(File.dirname(__FILE__), path, '*.rb')
8
+ Dir[glob].each do |f|
9
+ require f
10
+ end
11
+ end
12
+ require 'parslet'
13
+ require 'set'
14
+ require 'dpll_solver/version'
15
+ require 'dpll_solver/atomic_formula'
16
+ require_all 'dpll_solver/formulas'
17
+ require_all 'dpll_solver/parsers'
18
+ require_all 'dpll_solver/heuristics'
19
+
20
+ module DpllSolver
21
+ class Util
22
+ @@grammar = DpllSolver::Parsers::Grammar.new
23
+ @@transformer = DpllSolver::Parsers::Transformer.new
24
+ def self.to_cnf(str)
25
+ formula = @@grammar.parse(str)
26
+ @@transformer.apply(formula).simplify().cnf()
27
+ end
28
+
29
+ def self.to_clause(str)
30
+ formula = @@grammar.parse(str)
31
+ DpllSolver::Parsers::Parser.formula_to_clause(@@transformer.apply(formula))
32
+ end
33
+
34
+ def self.dimacs_to_clause(file)
35
+ parser = DpllSolver::Parsers::DimacsParser.new(file)
36
+ parser.clauseset
37
+ end
38
+
39
+ def self.dimacs_SAT?(file)
40
+ solver = DPLL.new(DpllSolver::Heuristics::MostFrequentLiteral)
41
+ solver.apply_dpll(self.dimacs_to_clause(file))
42
+ end
43
+
44
+ def self.SAT?(str)
45
+ solver = DPLL.new(DpllSolver::Heuristics::MostFrequentLiteral)
46
+ solver.apply_dpll(self.to_clause(str))
47
+ end
48
+
49
+ def self.contradiction?(str)
50
+ !self.SAT?(str)
51
+ end
52
+
53
+ def self.tautology?(str)
54
+ formula = @@grammar.parse(str)
55
+ #negate formula
56
+ clause = DpllSolver::Parsers::Parser.formula_to_clause(DpllSolver::Formulas::Not.new(@@transformer.apply(formula)))
57
+ solver = DPLL.new(DpllSolver::Heuristics::MostFrequentLiteral)
58
+ !solver.apply_dpll(clause)
59
+ end
60
+ end
61
+
62
+ class DPLL
63
+ attr_accessor :heuristics
64
+ def initialize(heuristics)
65
+ @heuristics = heuristics
66
+ end
67
+
68
+ def apply_dpll(clauseset)
69
+ literal = get_unit_clause_literal(clauseset)
70
+ while literal do
71
+ clauseset = unit_propagation(clauseset, literal)
72
+ literal = get_unit_clause_literal(clauseset)
73
+ end
74
+ if clauseset.empty?
75
+ return true
76
+ elsif contains_empty_clause?(clauseset)
77
+ return false
78
+ end
79
+ literal = @heuristics.choose_literal(clauseset)
80
+ return apply_dpll(union_unit_clause(clauseset, literal)) || apply_dpll(union_unit_clause(clauseset, literal.negate()))
81
+ end
82
+
83
+ def contains_empty_clause?(clauseset)
84
+ clauseset.each do |clause|
85
+ return true if clause.empty?
86
+ end
87
+ return false
88
+ end
89
+
90
+ def get_unit_clause_literal(clauseset)
91
+ clauseset.each do |clause|
92
+ if clause.unit?
93
+ return clause.literals.first
94
+ end
95
+ end
96
+ return false
97
+ end
98
+
99
+ def unit_propagation(clauseset, literal)
100
+ result = Set.new
101
+ clauseset.each do |clause|
102
+ if clause.include?(literal.negate())
103
+ result.add(clause.delete(literal.negate()))
104
+ elsif !clause.include?(literal)
105
+ result.add(clause)
106
+ end
107
+ end
108
+ result
109
+ end
110
+
111
+ def union_unit_clause(clauseset, literal)
112
+ clauseset.dup.add(DpllSolver::Formulas::Clause.new(literal))
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,34 @@
1
+ module AtomicFormula
2
+ #syntactic equivalenz
3
+ def ==(other)
4
+ other.class == self.class
5
+ end
6
+
7
+ def atomic_formula?
8
+ true
9
+ end
10
+
11
+ alias literal? atomic_formula?
12
+ alias min_term? atomic_formula?
13
+ alias clause? atomic_formula?
14
+ alias nnf? atomic_formula?
15
+ alias cnf? atomic_formula?
16
+ alias dnf? atomic_formula?
17
+
18
+ def simplify
19
+ self
20
+ end
21
+
22
+ alias nnf simplify
23
+ alias cnf simplify
24
+ alias dnf simplify
25
+
26
+ def not?
27
+ false
28
+ end
29
+
30
+ alias and? not?
31
+ alias or? not?
32
+ alias variable? not?
33
+ end
34
+
@@ -0,0 +1,54 @@
1
+ module DpllSolver
2
+ module Formulas
3
+ class And < BinaryFormula
4
+ def to_s
5
+ "(#{@f1.to_s} AND #{@f2.to_s})"
6
+ end
7
+
8
+ def clause?
9
+ false
10
+ end
11
+
12
+ def min_term?
13
+ @f1.min_term? && @f2.min_term?
14
+ end
15
+
16
+ def cnf?
17
+ @f1.cnf? && @f2.cnf?
18
+ end
19
+
20
+ def and?
21
+ true
22
+ end
23
+
24
+ def verum?
25
+ false
26
+ end
27
+ alias falsum? verum?
28
+ alias not? verum?
29
+ alias or? verum?
30
+ alias variable? verum?
31
+
32
+ def simplify
33
+ res_f1 = @f1.simplify
34
+ res_f2 = @f2.simplify
35
+ if res_f1.verum? #identity
36
+ return res_f2
37
+ elsif res_f2.verum? #identity
38
+ return res_f1
39
+ elsif res_f1.falsum? || res_f2.falsum? #anihilator
40
+ return DpllSolver::Formulas::Falsum
41
+ elsif res_f1 == res_f2 #idempotence
42
+ return res_f1
43
+ else
44
+ return self.class.new(res_f1, res_f2)
45
+ end
46
+ end
47
+
48
+ def cnf
49
+ nnf? ? self.class.new(@f1.cnf, @f2.cnf) : nnf.cnf
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,33 @@
1
+ class BinaryFormula
2
+ attr_accessor :f1, :f2
3
+ def initialize(f1, f2)
4
+ @f1 = f1
5
+ @f2 = f2
6
+ end
7
+ #syntactic equivalenz
8
+ def ==(other)
9
+ other.class == self.class && other.f1 == @f1 && other.f2 == @f2
10
+ end
11
+ alias eql? ==
12
+
13
+ def literal?
14
+ false
15
+ end
16
+
17
+ def atomic_formula?
18
+ false
19
+ end
20
+
21
+ def nnf?
22
+ @f1.nnf? && @f2.nnf?
23
+ end
24
+
25
+ def dnf?
26
+ @f1.dnf? && @f2.dnf?
27
+ end
28
+
29
+ def nnf
30
+ self.class.new(@f1.nnf, @f2.nnf)
31
+ end
32
+ end
33
+
@@ -0,0 +1,52 @@
1
+ module DpllSolver
2
+ module Formulas
3
+ class Clause
4
+ attr_accessor :literals
5
+ def initialize(*args)
6
+ @literals = Set.new(args)
7
+ end
8
+
9
+ def add(literal)
10
+ @literals.add(literal)
11
+ self
12
+ end
13
+
14
+ def delete(literal)
15
+ literal_array = @literals.to_a
16
+ literal_array.delete(literal)
17
+ @literals = Set.new(literal_array)
18
+ self
19
+ end
20
+
21
+ def unit?
22
+ @literals.count == 1
23
+ end
24
+
25
+ def empty?
26
+ @literals.empty?
27
+ end
28
+
29
+ def include?(literal)
30
+ @literals.to_a.include?(literal)
31
+ end
32
+
33
+ def union(other)
34
+ @literals = @literals + other.literals
35
+ self
36
+ end
37
+
38
+ def get_unit_literal
39
+ @literals.first if unit?
40
+ end
41
+
42
+ def ==(other)
43
+ other.class == self.class && other.literals == @literals
44
+ end
45
+
46
+ def to_s
47
+ "{#{@literals.to_a.map(&:to_s).join(', ')}}"
48
+ end
49
+ alias eql? ==
50
+ end
51
+ end
52
+ end