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
@@ -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