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