dpll_solver 0.0.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.
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
@@ -0,0 +1,18 @@
1
+ module DpllSolver
2
+ module Formulas
3
+ class Falsum
4
+ extend AtomicFormula
5
+ def self.to_s
6
+ '0'
7
+ end
8
+
9
+ def self.falsum?
10
+ true
11
+ end
12
+ def self.verum?
13
+ false
14
+ end
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,25 @@
1
+ module DpllSolver
2
+ module Formulas
3
+ class Literal
4
+ attr_accessor :var, :phase
5
+ def initialize(var, phase)
6
+ @var = var
7
+ @phase = phase
8
+ end
9
+
10
+ def negate
11
+ self.class.new(@var, !@phase)
12
+ end
13
+
14
+ def to_s
15
+ phase ? @var.to_s : "-#{@var.to_s}"
16
+ end
17
+
18
+ def ==(other)
19
+ other.class == self.class && other.var == @var && other.phase == @phase
20
+ end
21
+
22
+ alias eql? ==
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,84 @@
1
+ module DpllSolver
2
+ module Formulas
3
+ class Not
4
+ attr_accessor :f
5
+
6
+ def initialize(f)
7
+ @f = f
8
+ end
9
+
10
+ def ==(other)
11
+ other.class == self.class && other.f == @f
12
+ end
13
+
14
+ alias eql? ==
15
+
16
+ def to_s
17
+ "-#{@f.to_s}"
18
+ end
19
+
20
+ def literal?
21
+ @f.atomic_formula?
22
+ end
23
+
24
+ def atomic_formula?
25
+ false
26
+ end
27
+ alias :nnf? :literal?
28
+ alias :clause? :literal?
29
+ alias :min_term? :literal?
30
+ alias :cnf? :nnf?
31
+ alias :dnf? :nnf?
32
+
33
+ def not?
34
+ true
35
+ end
36
+
37
+ def verum?
38
+ false
39
+ end
40
+ alias falsum? verum?
41
+ alias and? verum?
42
+ alias or? verum?
43
+ alias variable? verum?
44
+
45
+ def simplify
46
+ result = @f.simplify
47
+ if result.not?
48
+ result = result.f.simplify
49
+ elsif result.verum?
50
+ result = DpllSolver::Formulas::Falsum
51
+ elsif result.falsum?
52
+ result = DpllSolver::Formulas::Verum
53
+ else
54
+ result = self.class.new(result)
55
+ end
56
+ result
57
+ end
58
+
59
+ def nnf
60
+ if @f.and? || @f.or?
61
+ return apply_DeMorgan.nnf
62
+ elsif @f.not?
63
+ return @f.f.nnf
64
+ else
65
+ return self
66
+ end
67
+ end
68
+
69
+ def cnf
70
+ nnf? ? self : nnf.cnf
71
+ end
72
+
73
+ def apply_DeMorgan
74
+ if @f.and?
75
+ return DpllSolver::Formulas::Or.new(self.class.new(@f.f1), self.class.new(@f.f2))
76
+ elsif @f.or?
77
+ return DpllSolver::Formulas::And.new(self.class.new(@f.f1), self.class.new(@f.f2))
78
+ else
79
+ raise ArgumentError, 'DeMorgan can not be applied unless @f is either AND or OR'
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,64 @@
1
+ module DpllSolver
2
+ module Formulas
3
+ class Or < BinaryFormula
4
+ def to_s
5
+ "(#{@f1.to_s} OR #{@f2.to_s})"
6
+ end
7
+
8
+ def clause?
9
+ @f1.clause? && @f2.clause?
10
+ end
11
+
12
+ def min_term?
13
+ false
14
+ end
15
+
16
+ alias cnf? clause?
17
+
18
+ def or?
19
+ true
20
+ end
21
+
22
+ def verum?
23
+ false
24
+ end
25
+ alias falsum? verum?
26
+ alias not? verum?
27
+ alias and? verum?
28
+ alias variable? verum?
29
+
30
+ def simplify
31
+ res_f1 = @f1.simplify
32
+ res_f2 = @f2.simplify
33
+ if res_f1.falsum? #identity
34
+ return res_f2
35
+ elsif res_f2.falsum? #identity
36
+ return res_f1
37
+ elsif res_f1.verum? || res_f2.verum? #anihilator
38
+ return DpllSolver::Formulas::Verum
39
+ elsif res_f1 == res_f2 #idempotence
40
+ return res_f1
41
+ else
42
+ return self.class.new(res_f1, res_f2)
43
+ end
44
+ end
45
+
46
+ def cnf
47
+ if cnf?
48
+ return self
49
+ elsif nnf?
50
+ if @f1.and?
51
+ return DpllSolver::Formulas::And.new(self.class.new(@f1.f1, @f2).cnf, self.class.new(@f1.f2, @f2).cnf)
52
+ elsif @f2.and?
53
+ return DpllSolver::Formulas::And.new(self.class.new(@f1, @f2.f1).cnf, self.class.new(@f1, @f2.f2).cnf)
54
+ else
55
+ return self.class.new(@f1.cnf, @f2.cnf).cnf
56
+ end
57
+ else
58
+ return nnf.cnf
59
+ end
60
+ end
61
+
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,26 @@
1
+ module DpllSolver
2
+ module Formulas
3
+ class Variable
4
+ include AtomicFormula
5
+ attr_accessor :name
6
+ alias :to_s :name
7
+ def initialize(name)
8
+ @name = name
9
+ end
10
+
11
+ def ==(other)
12
+ other.class == self.class && other.to_s == self.to_s
13
+ end
14
+ alias :eql? :==
15
+
16
+ def variable?
17
+ true
18
+ end
19
+ def verum?
20
+ false
21
+ end
22
+ alias falsum? verum?
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,18 @@
1
+ module DpllSolver
2
+ module Formulas
3
+ class Verum
4
+ extend AtomicFormula
5
+ def self.to_s
6
+ '1'
7
+ end
8
+
9
+ def self.verum?
10
+ true
11
+ end
12
+ def self.falsum?
13
+ false
14
+ end
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,22 @@
1
+ module DpllSolver
2
+ module Heuristics
3
+ class MostFrequentLiteral
4
+ def self.choose_literal(clauseset)
5
+ raise ArgumentError, "clauseset must be a set of Clauses and not be empty!" if clauseset.empty?
6
+ counter = Hash.new(0)
7
+ most_frequent = nil
8
+ clauseset.each do |clause|
9
+ clause.literals.each do |literal|
10
+ counter[literal] += 1
11
+ if most_frequent
12
+ most_frequent = literal if counter[literal] > counter[most_frequent]
13
+ else
14
+ most_frequent = literal
15
+ end
16
+ end
17
+ end
18
+ most_frequent
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,46 @@
1
+ module DpllSolver
2
+ module Parsers
3
+ class DimacsParser
4
+ attr_accessor :file, :num_vars, :num_clauses, :clauseset
5
+ def initialize(file_path)
6
+ @file = file_path
7
+ @clauseset = Set.new
8
+ @num_clauses = 0
9
+ @num_vars = 0
10
+ parse
11
+ end
12
+
13
+ private
14
+ def parse
15
+ begin
16
+ lines = open(@file).readlines
17
+ lines.each do |line|
18
+ line = line.gsub("\n", "").strip.split
19
+ next if line[0] == "c"
20
+ if line[0] == "p"
21
+ @num_vars = line[2].to_i
22
+ @num_clauses = line[3].to_i
23
+ else
24
+ line.pop
25
+ if line.count > 0
26
+ clause = DpllSolver::Formulas::Clause.new
27
+ line.each do |num|
28
+ clause.add(create_literal(num))
29
+ end
30
+ @clauseset.add(clause)
31
+ end
32
+ end
33
+ end
34
+ rescue Exception => e
35
+ puts e
36
+ raise IOError, "Could not parse file #{@file}!"
37
+ end
38
+ end
39
+
40
+ def create_literal(num)
41
+ var = DpllSolver::Formulas::Variable.new("x#{num.to_i.abs}")
42
+ DpllSolver::Formulas::Literal.new(var, num.to_i > 0)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,53 @@
1
+ module DpllSolver
2
+ module Parsers
3
+ class Grammar < Parslet::Parser
4
+ root :formula
5
+ rule(:formula) { falsum.as(:falsum) | verum.as(:verum) | variable.as(:var) | not_operation.as(:not) | and_operation.as(:and) | or_operation.as(:or) }
6
+ rule(:space) { str(" ").maybe }
7
+ rule(:verum) { match('[1T]') }
8
+ rule(:falsum) { match('[0F]') }
9
+ rule(:variable) { match('[a-z]') >> match('[0-9]').repeat(0,3) }
10
+ rule(:not_operation) { str("-") >> formula.as(:root_formula) }
11
+ rule(:or_operation) { str("(") >> formula.as(:left) >> space >> (match('[+|]') | str('OR')) >> space >> formula.as(:right) >> str(")") }
12
+ rule(:and_operation) { str("(") >> formula.as(:left) >> space >> (match('[*&]') | str('AND')) >> space >> formula.as(:right) >> str(")") }
13
+ end
14
+
15
+ class Transformer < Parslet::Transform
16
+ rule(:verum => simple(:verum)) { DpllSolver::Formulas::Verum }
17
+ rule(:falsum => simple(:falsum)) { DpllSolver::Formulas::Falsum }
18
+ rule(:var => simple(:var)) { DpllSolver::Formulas::Variable.new(var) }
19
+ rule(:not => { root_formula: subtree(:root_formula) }) { DpllSolver::Formulas::Not.new(root_formula) }
20
+ rule(:or => {left: subtree(:left), right: subtree(:right) }) { DpllSolver::Formulas::Or.new(left, right) }
21
+ rule(:and => {left: subtree(:left), right: subtree(:right) }) { DpllSolver::Formulas::And.new(left, right) }
22
+ end
23
+
24
+ class Parser
25
+
26
+ def self.formula_to_clause(formula)
27
+ f_cnf = formula.simplify.cnf
28
+ if f_cnf.verum?
29
+ return Set.new
30
+ elsif f_cnf.falsum?
31
+ return Set.new([DpllSolver::Formulas::Clause.new])
32
+ elsif f_cnf.variable?
33
+ literal = DpllSolver::Formulas::Literal.new(f_cnf, true)
34
+ return Set.new([DpllSolver::Formulas::Clause.new(literal)])
35
+ elsif f_cnf.not?
36
+ literal = DpllSolver::Formulas::Literal.new(f_cnf.f, false)
37
+ return Set.new([DpllSolver::Formulas::Clause.new(literal)])
38
+ elsif f_cnf.and?
39
+ c1 = self.formula_to_clause(f_cnf.f1)
40
+ c2 = self.formula_to_clause(f_cnf.f2)
41
+ return c1 + c2
42
+ elsif f_cnf.or?
43
+ c1 = self.formula_to_clause(f_cnf.f1)
44
+ c2 = self.formula_to_clause(f_cnf.f2)
45
+ return Set.new([c1.first.union(c2.first)])
46
+ else
47
+ raise TypeError, "Formula has to be one of these classes: Verum, Falsum, Variable, And, Or, Not"
48
+ end
49
+ end
50
+
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,3 @@
1
+ module DpllSolver
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,114 @@
1
+ require 'spec_helper'
2
+
3
+ describe DpllSolver::DPLL do
4
+ let(:solver) { DpllSolver::DPLL.new(DpllSolver::Heuristics::MostFrequentLiteral) }
5
+ let(:var1) { DpllSolver::Formulas::Variable.new("x1") }
6
+ let(:var2) { DpllSolver::Formulas::Variable.new("x2") }
7
+ let(:lit1) { DpllSolver::Formulas::Literal.new(var1, true)}
8
+ let(:n_lit1) { DpllSolver::Formulas::Literal.new(var1, false)}
9
+ let(:lit2) { DpllSolver::Formulas::Literal.new(var2, true)}
10
+ let(:c1) { Set.new([DpllSolver::Formulas::Clause.new(lit1, lit2), DpllSolver::Formulas::Clause.new(lit1)])}
11
+ let(:c2) { Set.new()}
12
+ let(:c3) { Set.new([DpllSolver::Formulas::Clause.new(n_lit1, lit2), DpllSolver::Formulas::Clause.new(n_lit1)])}
13
+ let(:c4) { Set.new([DpllSolver::Formulas::Clause.new(n_lit1), DpllSolver::Formulas::Clause.new(lit1)])}
14
+ let(:c5) { Set.new([DpllSolver::Formulas::Clause.new, DpllSolver::Formulas::Clause.new(lit1)])}
15
+
16
+ it 'should return the literal of a uni clause' do
17
+ expect(solver.get_unit_clause_literal(c2)).to eql false
18
+ expect(solver.get_unit_clause_literal(c1)).to eql lit1
19
+ expect(solver.get_unit_clause_literal(c3)).to eql n_lit1
20
+ end
21
+
22
+ it 'should apply unit propagation on clauseset' do
23
+ expect(solver.unit_propagation(c1, lit1)).to eql Set.new
24
+ expect(solver.get_unit_clause_literal(solver.unit_propagation(c1, lit2))).to eql lit1
25
+ expect(solver.unit_propagation(c3, lit1).count).to eql 2
26
+ expect(solver.contains_empty_clause?(solver.unit_propagation(c3, lit1))).to eql true
27
+ end
28
+
29
+ it 'should apply dpll algorithm' do
30
+ expect(solver.apply_dpll(c1)).to eql true
31
+ expect(solver.apply_dpll(c2)).to eql true
32
+ expect(solver.apply_dpll(c3)).to eql true
33
+ expect(solver.apply_dpll(c4)).to eql false
34
+ expect(solver.apply_dpll(c5)).to eql false
35
+ l1 = DpllSolver::Formulas::Literal.new(DpllSolver::Formulas::Variable.new("x1"), true)
36
+ l2 = DpllSolver::Formulas::Literal.new(DpllSolver::Formulas::Variable.new("x1"), true)
37
+ l3 = DpllSolver::Formulas::Literal.new(DpllSolver::Formulas::Variable.new("x4"), true)
38
+ c = Set.new([DpllSolver::Formulas::Clause.new(l1), DpllSolver::Formulas::Clause.new(l2, l3)])
39
+ expect(solver.apply_dpll(c)).to eql true
40
+ end
41
+
42
+ end
43
+
44
+ class Set
45
+ def to_s
46
+ map(&:to_s)
47
+ end
48
+ end
49
+
50
+ describe DpllSolver::Util do
51
+ let(:util) { DpllSolver::Util }
52
+ let(:verum) { DpllSolver::Formulas::Verum }
53
+ let(:falsum) { DpllSolver::Formulas::Falsum }
54
+ let(:var) { DpllSolver::Formulas::Variable.new("x1") }
55
+ let(:not_var) { DpllSolver::Formulas::Not.new(var) }
56
+ let(:f1) { "resources/dimacs/yes/uf20-01.cnf" }
57
+ let(:f2) { "resources/dimacs/yes/uf20-02.cnf" }
58
+ let(:f3) { "resources/dimacs/yes/uf20-03.cnf" }
59
+ let(:f4) { "resources/dimacs/yes/uf20-04.cnf" }
60
+ let(:f5) { "resources/dimacs/no/uf20-01.cnf" }
61
+ let(:f6) { "resources/dimacs/no/uf20-02.cnf" }
62
+
63
+ it 'should transform a formula string into cnf' do
64
+ expect(util.to_cnf('1')).to eql verum
65
+ expect(util.to_cnf('0')).to eql falsum
66
+ expect(util.to_cnf('x1')).to eql var
67
+ expect(util.to_cnf('-x1')).to eql not_var
68
+ expect(util.to_cnf('((a OR -b) AND (0 OR c))').to_s).to eql '((a OR -b) AND c)'
69
+ expect(util.to_cnf('((a AND -b) OR (1 AND c))').to_s).to eql '((a OR c) AND (-b OR c))'
70
+ end
71
+
72
+ it 'should transform a formula string into clause' do
73
+ expect(util.to_clause('1')).to eql Set.new
74
+ expect(util.to_clause('0').to_s).to eql ["{}"]
75
+ expect(util.to_clause('a').to_s).to eql ["{a}"]
76
+ expect(util.to_clause('(a OR b)').to_s).to eql ["{a, b}"]
77
+ expect(util.to_clause('(a AND b)').to_s).to eql ["{a}", "{b}"]
78
+ expect(util.to_clause('(a AND (-b OR c))').to_s).to eql ["{a}", "{-b, c}"]
79
+ expect(util.to_clause('((a AND -b) OR (1 AND c))').to_s).to eql ["{a, c}", "{-b, c}"]
80
+ end
81
+
82
+ it 'should check wether a formula is satisfiable' do
83
+ expect(util.SAT?('1')).to eql true
84
+ expect(util.SAT?('0')).to eql false
85
+ expect(util.SAT?('x1')).to eql true
86
+ expect(util.SAT?('(0 AND x1)')).to eql false
87
+ expect(util.SAT?('-(0 AND x1)')).to eql true
88
+ expect(util.SAT?('-(1 OR x1)')).to eql false
89
+ end
90
+
91
+ it 'should check wether a formula is tautology' do
92
+ expect(util.tautology?('1')).to eql true
93
+ expect(util.tautology?('x1')).to eql false
94
+ expect(util.tautology?('-(0 AND x1)')).to eql true
95
+ expect(util.tautology?('-(1 OR x1)')).to eql false
96
+ end
97
+
98
+ it 'should check wether a formula is contradiction' do
99
+ expect(util.contradiction?('0')).to eql true
100
+ expect(util.contradiction?('x1')).to eql false
101
+ expect(util.contradiction?('(0 AND x1)')).to eql true
102
+ expect(util.contradiction?('-(0 AND x1)')).to eql false
103
+ expect(util.contradiction?('-(1 OR x1)')).to eql true
104
+ end
105
+
106
+ it 'should solve SAT problem of given file' do
107
+ expect(util.dimacs_SAT?(f1)).to eql true
108
+ expect(util.dimacs_SAT?(f2)).to eql true
109
+ expect(util.dimacs_SAT?(f3)).to eql true
110
+ expect(util.dimacs_SAT?(f4)).to eql true
111
+ expect(util.dimacs_SAT?(f5)).to eql false
112
+ expect(util.dimacs_SAT?(f6)).to eql false
113
+ end
114
+ end