File Coverage

blib/lib/Algorithm/SAT/Backtracking/DPLL.pm
Criterion Covered Total %
statement 100 109 91.7
branch 31 42 73.8
condition 19 35 54.2
subroutine 16 17 94.1
pod 1 1 100.0
total 167 204 81.8


line stmt bran cond sub pod time code
1             package Algorithm::SAT::Backtracking::DPLL;
2 9     9   1017 use Storable qw(dclone);
  9         10  
  9         297  
3 7     7   14217 use Data::Dumper;
  7         21857  
  7         349  
4 4     4   27 use strict;
  4         4  
  4         128  
5 4     4   16 use warnings;
  4         6  
  4         2915  
6             our $VERSION = "0.12";
7              
8             # this allow to switch the parent implementation (needed for the Ordered alternative)
9             sub import {
10 22     22   83 my $class = shift;
11 22         20 my $flag = shift;
12 22 100       51 if ($flag) {
13 2     2   10 eval "use base '$flag'";
  2         3  
  2         168  
  2         117  
14             }
15             else {
16 4     4   19 eval "use base 'Algorithm::SAT::Backtracking'";
  4     4   5  
  4     4   5349  
  4         24  
  4         8  
  4         150  
  4         19  
  4         6  
  4         109  
  20         1333  
17             }
18             }
19              
20             sub solve {
21              
22             # ### solve
23             #
24             # * `variables` is the list of all variables
25             # * `clauses` is an array of clauses.
26             # * `model` is a set of variable assignments.
27 56     56 1 105 my $self = shift;
28 56         45 my $variables = shift;
29 56         46 my $clauses = shift;
30 56 100       109 my $model = defined $_[0] ? shift : {};
31 56         1214 my $impurity = dclone($clauses);
32              
33 56 100       155 if ( !exists $self->{_impurity} ) {
34 12         15 $self->{_impurity}->{$_}++ for ( map { @{$_} } @{$impurity} );
  45         39  
  45         196  
  12         24  
35             }
36              
37             # If every clause is satisfiable, return the model which worked.
38 211 100 66     502 return $model
39             if (
40             ( grep {
41 56         68 ( defined $self->satisfiable( $_, $model )
42             and $self->satisfiable( $_, $model ) == 1 )
43             ? 0
44             : 1
45 56 100       64 } @{$clauses}
46             ) == 0
47             );
48              
49             # If any clause is **exactly** false, return `false`; this model will not
50             # work.
51 44 50       112 return 0 if !$self->_consistency_check( $clauses, $model );
52              
53 44         101 $model = $self->_up( $variables, $clauses, $model )
54             ; # find unit clauses and sets them
55              
56 44 50       66 return 0 if !$self->_consistency_check( $clauses, $model );
57              
58             # TODO: pure unit optimization
59             # XXX: not working
60              
61             # $self->_pure($_)
62             # ? ( $model->{$_} = 1 and $self->_remove_clause_if_contains( $_, $clauses ) )
63             # : $self->_pure( "-" . $_ )
64             # ? ( $model->{$_} = 0 and $self->_remove_clause_if_contains( $_, $clauses ) )
65             # : ()
66             # for @{$variables};
67             # return $model if ( @{$clauses} == 0 ); #we were lucky
68              
69             # XXX: end
70              
71             # Choose a new value to test by simply looping over the possible variables
72             # and checking to see if the variable has been given a value yet.
73              
74 44         103 my $choice = $self->_choice( $variables, $model );
75              
76             # If there are no more variables to try, return false.
77              
78 44 50       96 return 0 if ( !$choice );
79              
80             # Recurse into two cases. The variable we chose will need to be either
81             # true or false for the expression to be satisfied.
82 44   33     110 return $self->solve( $variables, $clauses,
83             $self->update( $model, $choice, 1 ) ) #true
84             || $self->solve( $variables, $clauses,
85             $self->update( $model, $choice, 0 ) ); #false
86             }
87              
88             sub _consistency_check {
89 88     88   74 my $self = shift;
90 88         66 my $clauses = shift;
91 88         71 my $model = shift;
92 332 50 66     629 return 0
93             if (
94             ( grep {
95 88         102 ( defined $self->satisfiable( $_, $model )
96             and $self->satisfiable( $_, $model ) == 0 )
97             ? 1
98             : 0
99 88 50       73 } @{$clauses}
100             ) > 0
101             );
102 88         206 return 1;
103              
104             }
105              
106             sub _pure {
107 24     24   120 my $self = shift;
108 24         28 my $literal = shift;
109              
110             # Pure literal rule
111              
112             # if a variable only occurs positively in a formula, set it to true
113             # if a variable only occurs negated in a formula, set it to false
114              
115 24 50       80 my $opposite
116             = substr( $literal, 0, 1 ) eq "-"
117             ? substr( $literal, 1 )
118             : "-" . $literal;
119 24 100 66     232 return 1
      66        
      66        
120             if (
121             ( exists $self->{_impurity}->{$literal}
122             and $self->{_impurity}->{$literal} != 0
123             )
124             and (
125             !exists $self->{_impurity}->{$opposite}
126             or ( exists $self->{_impurity}->{$opposite}
127             and $self->{_impurity}->{$opposite} == 0 )
128             )
129             );
130              
131             # print STDERR "$literal is IMpure\n" and
132 18         68 return 0;
133             }
134              
135             sub _up {
136 32     32   52 my $self = shift;
137 32         28 my $variables = shift;
138 32         27 my $clauses = shift;
139 32 100       47 my $model = defined $_[0] ? shift : {};
140              
141             #Finding single clauses that must be true, and updating the model
142 32         51 ( @{$_} != 1 )
  121         251  
143             ? ()
144             : ( substr( $_->[0], 0, 1 ) eq "-" ) ? (
145             $self->_remove_literal( substr( $_->[0], 1 ), $clauses, $model
146             ) #remove the positive clause form OR's and add it to the model with a false value
147             )
148             : ( $self->_add_literal( "-" . $_->[0], $clauses )
149             and $model->{ $_->[0] }
150             = 1
151             ) # if the literal is present, remove it from SINGLE ARRAYS in $clauses and add it to the model with a true value
152 32 50 0     27 for ( @{$clauses} );
    100          
153 32         47 return $model;
154             }
155              
156             sub _remove_literal {
157 18     18   49 my $self = shift;
158 18         38 my $literal = shift;
159 18         16 my $clauses = shift;
160 18         17 my $model = shift;
161             return
162 18 100 100     119 if $model
      66        
163             and exists $model->{$literal}
164             and $model->{$literal} == 0; #avoid cycle if already set
165             #remove the literal from the model (set to false)
166 9         21 $model->{$literal} = 0;
167              
168             #remove the literal from the model (set to false) and delete it from index
169              
170 9         21 $self->_delete_from_index( $literal, $clauses );
171              
172 9         23 return 1;
173             }
174              
175             sub _add_literal {
176 0     0   0 my $self = shift;
177 0         0 my $literal = shift;
178 0         0 my $clauses = shift;
179 0         0 my $model = shift;
180 0 0       0 $literal
181             = ( substr( $literal, 0, 1 ) eq "-" )
182             ? $literal
183             : substr( $literal, 1 );
184             return
185 0 0 0     0 if $model
      0        
186             and exists $model->{$literal}
187             and $model->{$literal} == 1; #avoid cycle if already set
188             #remove the literal from the model (set to false) and delete it from index
189 0         0 $model->{$literal} = 1;
190 0         0 $self->_delete_from_index( $literal, $clauses );
191 0         0 return 1;
192             }
193              
194             sub _delete_from_index {
195 16     16   18 my $self = shift;
196 16         19 my $string = shift;
197 16         16 my $list = shift;
198 16         19 foreach my $c ( @{$list} ) {
  16         29  
199 64 100       64 next if @{$c} <= 1;
  64         145  
200 48         37 for ( my $index = scalar( @{$c} ); $index >= 0; --$index ) {
  48         92  
201 230 100 100     804 do {
202 12         12 splice( @{$c}, $index, 1 );
  12         16  
203 12         34 $self->{_impurity}->{$string}--;
204             }
205             if $c->[$index]
206             and $c->[$index] eq $string; # remove certain elements
207             }
208             }
209             }
210              
211             sub _remove_clause_if_contains {
212 12     12   8189 my $self = shift;
213 12         16 my $literal = shift;
214 12         14 my $list = shift;
215 12         13 my $index = 0;
216 12         15 while ( $index < scalar @{$list} ) {
  48         112  
217 15         23 splice( @{$list}, $index, 1 )
  102         175  
218 36 100       31 if grep { $_ eq $literal } @{ $list->[$index] };
  36         48  
219 36         36 $index++;
220             }
221              
222             }
223              
224             1;
225              
226             =encoding utf-8
227              
228             =head1 NAME
229              
230             Algorithm::SAT::Backtracking::DPLL - A DPLL Backtracking SAT solver written in pure Perl
231              
232             =head1 SYNOPSIS
233              
234              
235             # You can use it with Algorithm::SAT::Expression
236             use Algorithm::SAT::Expression;
237              
238             my $expr = Algorithm::SAT::Expression->new->with("Algorithm::SAT::Backtracking::DPLL"); #Uses Algorithm::SAT::Backtracking by default, you can use "with()" to specify other implementations
239             $expr->or( '-foo@2.1', 'bar@2.2' );
240             $expr->or( '-foo@2.3', 'bar@2.2' );
241             $expr->or( '-baz@2.3', 'bar@2.3' );
242             $expr->or( '-baz@1.2', 'bar@2.2' );
243             my $model = $exp->solve();
244              
245             # Or you can use it directly:
246             use Algorithm::SAT::Backtracking;
247             my $solver = Algorithm::SAT::Backtracking->new;
248             my $variables = [ 'blue', 'green', 'yellow', 'pink', 'purple' ];
249             my $clauses = [
250             [ 'blue', 'green', '-yellow' ],
251             [ '-blue', '-green', 'yellow' ],
252             [ 'pink', 'purple', 'green', 'blue', '-yellow' ]
253             ];
254              
255             my $model = $solver->solve( $variables, $clauses );
256              
257             =head1 DESCRIPTION
258              
259             Algorithm::SAT::Backtracking::DPLL is a pure Perl implementation of a SAT Backtracking solver.
260              
261             Look at L for a theory description.
262              
263             The DPLL variant applies the "unit propagation" and the "pure literal" technique to be faster.
264              
265             Look also at the tests file for an example of usage.
266              
267             L use this module to solve Boolean expressions.
268              
269             =head1 METHODS
270              
271             Inherits all the methods from L and implements new private methods to use the unit propagation and pure literal rule techniques.
272              
273             =head1 LICENSE
274              
275             Copyright (C) mudler.
276              
277             This library is free software; you can redistribute it and/or modify
278             it under the same terms as Perl itself.
279              
280             =head1 AUTHOR
281              
282             mudler Emudler@dark-lab.netE
283              
284             =head1 SEE ALSO
285              
286             L, L, L, L
287              
288             =cut
289