File Coverage

blib/lib/Algorithm/SAT/Backtracking/DPLL.pm
Criterion Covered Total %
statement 94 103 91.2
branch 31 42 73.8
condition 16 32 50.0
subroutine 14 15 93.3
pod 1 1 100.0
total 156 193 80.8


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