dpll_solver 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +22 -0
- data/README.md +62 -0
- data/Rakefile +9 -0
- data/dpll_solver.gemspec +35 -0
- data/lib/dpll_solver.rb +115 -0
- data/lib/dpll_solver/atomic_formula.rb +34 -0
- data/lib/dpll_solver/formulas/and.rb +54 -0
- data/lib/dpll_solver/formulas/binary_formula.rb +33 -0
- data/lib/dpll_solver/formulas/clause.rb +52 -0
- data/lib/dpll_solver/formulas/falsum.rb +18 -0
- data/lib/dpll_solver/formulas/literal.rb +25 -0
- data/lib/dpll_solver/formulas/not.rb +84 -0
- data/lib/dpll_solver/formulas/or.rb +64 -0
- data/lib/dpll_solver/formulas/variable.rb +26 -0
- data/lib/dpll_solver/formulas/verum.rb +18 -0
- data/lib/dpll_solver/heuristics/most_frequent_literal.rb +22 -0
- data/lib/dpll_solver/parsers/dimacs_parser.rb +46 -0
- data/lib/dpll_solver/parsers/parser.rb +53 -0
- data/lib/dpll_solver/version.rb +3 -0
- data/spec/dpll/dpll_solver_spec.rb +114 -0
- data/spec/formulas/and_spec.rb +75 -0
- data/spec/formulas/clause_spec.rb +55 -0
- data/spec/formulas/falsum_spec.rb +42 -0
- data/spec/formulas/formulas_spec.rb +47 -0
- data/spec/formulas/literal_spec.rb +26 -0
- data/spec/formulas/not_spec.rb +77 -0
- data/spec/formulas/or_spec.rb +74 -0
- data/spec/formulas/variable_spec.rb +44 -0
- data/spec/formulas/verum_spec.rb +42 -0
- data/spec/heuristics/most_frequent_literal_spec.rb +19 -0
- data/spec/parsers/dimacs_parser_spec.rb +23 -0
- data/spec/parsers/grammar_spec.rb +54 -0
- data/spec/parsers/parser_spec.rb +23 -0
- data/spec/parsers/transformer_spec.rb +30 -0
- data/spec/spec_helper.rb +4 -0
- metadata +183 -0
@@ -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,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,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
|