File Coverage

blib/lib/Algorithm/SAT/Backtracking.pm
Criterion Covered Total %
statement 46 46 100.0
branch 25 28 89.2
condition 12 17 70.5
subroutine 9 9 100.0
pod 4 5 80.0
total 96 105 91.4


line stmt bran cond sub pod time code
1             package Algorithm::SAT::Backtracking;
2 7     7   58453 use strict;
  7         16  
  7         251  
3 7     7   25 use warnings;
  7         10  
  7         181  
4 7     7   4384 use Storable qw(dclone);
  7         20350  
  7         3897  
5              
6             # This is an extremely simple implementation of the 'backtracking' algorithm for
7             # solving boolean satisfiability problems. It contains no optimizations.
8              
9             # The input consists of a boolean expression in Conjunctive Normal Form.
10             # This means it looks something like this:
11             #
12             # `(blue OR green) AND (green OR NOT yellow)`
13             #
14             # We encode this as an array of strings with a `-` in front for negation:
15             #
16             # `[['blue', 'green'], ['green', '-yellow']]`
17              
18             our $VERSION = "0.13";
19              
20             sub new {
21 32     32 0 32545 return bless {}, shift;
22             }
23              
24             sub solve {
25              
26             # ### solve
27             #
28             # * `variables` is the list of all variables
29             # * `clauses` is an array of clauses.
30             # * `model` is a set of variable assignments.
31 34     34 1 2881 my ( $self, $variables, $clauses, $model ) = @_;
32 34 100       65 $model = {} if !defined $model;
33              
34             # If every clause is satisfiable, return the model which worked.
35              
36 120 100 66     232 return $model
37             if (
38             ( grep {
39 34         66 ( defined $self->satisfiable( $_, $model )
40             and $self->satisfiable( $_, $model ) == 1 )
41             ? 0
42             : 1
43 34 100       29 } @{$clauses}
44             ) == 0
45             );
46              
47             # If any clause is **exactly** false, return `false`; this model will not
48             # work.
49 96 50 66     189 return 0
50             if (
51             ( grep {
52 27         35 ( defined $self->satisfiable( $_, $model )
53             and $self->satisfiable( $_, $model ) == 0 )
54             ? 1
55             : 0
56 27 50       49 } @{$clauses}
57             ) > 0
58             );
59              
60             # Choose a new value to test by simply looping over the possible variables
61             # and checking to see if the variable has been given a value yet.
62              
63 27         75 my $choice = $self->_choice( $variables, $model );
64              
65             # If there are no more variables to try, return false.
66              
67 27 50       39 return 0 if ( !$choice );
68              
69             # Recurse into two cases. The variable we chose will need to be either
70             # true or false for the expression to be satisfied.
71 27   33     51 return $self->solve( $variables, $clauses,
72             $self->update( $model, $choice, 1 ) ) #true
73             || $self->solve( $variables, $clauses,
74             $self->update( $model, $choice, 0 ) ); #false
75             }
76              
77             sub _choice {
78 33     33   47 my ( undef, $variables, $model ) = @_;
79              
80 33         26 my $choice;
81 33         29 foreach my $variable ( @{$variables} ) {
  33         51  
82 81 100 50     176 $choice = $variable and last if ( !exists $model->{$variable} );
83             }
84 33         62 return $choice;
85             }
86              
87             # ### update
88             # Copies the model, then sets `choice` = `value` in the model, and returns it.
89             sub update {
90 54     54 1 998 my ( $self, $copy, $choice, $value ) = @_;
91 54         1148 $copy = dclone($copy);
92              
93 54         94 $copy->{$choice} = $value;
94 54         211 return $copy;
95             }
96              
97             # ### resolve
98             # Resolve some variable to its actual value, or undefined.
99             sub resolve {
100 7336     7336 1 7321 my ( undef, $var, $model ) = @_;
101              
102 7336 100       9189 if ( substr( $var, 0, 1 ) eq "-" ) {
103 2873         2756 my $value = $model->{ substr( $var, 1 ) };
104 2873 100       10225 return !defined $value ? undef : $value == 0 ? 1 : 0;
    100          
105             }
106             else {
107 4463         13190 return $model->{$var};
108             }
109             }
110              
111             # ### satisfiable
112             # Determines whether a clause is satisfiable given a certain model.
113             sub satisfiable {
114 1265     1265 1 9061 my ( $self, $clauses, $model ) = @_;
115              
116 1265         926 my @clause = @{$clauses};
  1265         1926  
117              
118             # If every variable is false, then the clause is false.
119 3492 100 100     6521 return 0
120             if (
121             ( grep {
122 1265         1313 ( defined $self->resolve( $_, $model )
123             and $self->resolve( $_, $model ) == 0 )
124             ? 0
125             : 1
126 1265 100       1034 } @{$clauses}
127             ) == 0
128             );
129              
130             #If any variable is true, then the clause is true.
131 3490 100 100     6388 return 1
132             if (
133             ( grep {
134 1264         1336 ( defined $self->resolve( $_, $model )
135             and $self->resolve( $_, $model ) == 1 )
136             ? 1
137             : 0
138 1264 100       1935 } @{$clauses}
139             ) > 0
140             );
141              
142             # Otherwise, we don't know what the clause is.
143 372         1668 return undef;
144             }
145              
146             1;
147             __END__