File Coverage

blib/lib/Algorithm/SAT/Backtracking.pm
Criterion Covered Total %
statement 56 56 100.0
branch 25 28 89.2
condition 12 17 70.5
subroutine 9 9 100.0
pod 4 5 80.0
total 106 115 92.1


line stmt bran cond sub pod time code
1             package Algorithm::SAT::Backtracking;
2 7     7   68186 use strict;
  7         15  
  7         287  
3 7     7   33 use warnings;
  7         10  
  7         206  
4 7     7   4837 use Storable qw(dclone);
  7         21267  
  7         3917  
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.11";
19              
20             sub new {
21 32     32 0 23820 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 2562 my $self = shift;
32 34         34 my $variables = shift;
33 34         31 my $clauses = shift;
34 34 100       59 my $model = defined $_[0] ? shift : {};
35              
36             # If every clause is satisfiable, return the model which worked.
37              
38 120 100 66     219 return $model
39             if (
40             ( grep {
41 34         54 ( defined $self->satisfiable( $_, $model )
42             and $self->satisfiable( $_, $model ) == 1 )
43             ? 0
44             : 1
45 34 100       26 } @{$clauses}
46             ) == 0
47             );
48              
49             # If any clause is **exactly** false, return `false`; this model will not
50             # work.
51 96 50 66     149 return 0
52             if (
53             ( grep {
54 27         31 ( defined $self->satisfiable( $_, $model )
55             and $self->satisfiable( $_, $model ) == 0 )
56             ? 1
57             : 0
58 27 50       44 } @{$clauses}
59             ) > 0
60             );
61              
62             # Choose a new value to test by simply looping over the possible variables
63             # and checking to see if the variable has been given a value yet.
64              
65 27         72 my $choice = $self->_choice( $variables, $model );
66              
67              
68             # If there are no more variables to try, return false.
69              
70 27 50       45 return 0 if ( !$choice );
71              
72             # Recurse into two cases. The variable we chose will need to be either
73             # true or false for the expression to be satisfied.
74 27   33     58 return $self->solve( $variables, $clauses,
75             $self->update( $model, $choice, 1 ) ) #true
76             || $self->solve( $variables, $clauses,
77             $self->update( $model, $choice, 0 ) ); #false
78             }
79              
80             sub _choice {
81 33     33   30 my $self = shift;
82 33         31 my $variables = shift;
83 33         27 my $model = shift;
84 33         39 my $choice;
85 33         25 foreach my $variable ( @{$variables} ) {
  33         56  
86 81 100 50     173 $choice = $variable and last if ( !exists $model->{$variable} );
87             }
88 33         57 return $choice;
89             }
90              
91              
92             # ### update
93             # Copies the model, then sets `choice` = `value` in the model, and returns it.
94             sub update {
95 53     53 1 947 my $self = shift;
96 53         1098 my $copy = dclone(shift);
97 53         63 my $choice = shift;
98 53         47 my $value = shift;
99 53         76 $copy->{$choice} = $value;
100 53         171 return $copy;
101             }
102              
103             # ### resolve
104             # Resolve some variable to its actual value, or undefined.
105             sub resolve {
106 7008     7008 1 5439 my $self = shift;
107 7008         4797 my $var = shift;
108 7008         4806 my $model = shift;
109 7008 100       8106 if ( substr( $var, 0, 1 ) eq "-" ) {
110 2735         2483 my $value = $model->{ substr( $var, 1 ) };
111 2735 100       9446 return !defined $value ? undef : $value == 0 ? 1 : 0;
    100          
112             }
113             else {
114 4273         11116 return $model->{$var};
115             }
116             }
117              
118             # ### satisfiable
119             # Determines whether a clause is satisfiable given a certain model.
120             sub satisfiable {
121 1220     1220 1 8295 my $self = shift;
122 1220         867 my $clauses = shift;
123 1220         841 my $model = shift;
124 1220         911 my @clause = @{$clauses};
  1220         1666  
125              
126             # If every variable is false, then the clause is false.
127 3390 100 100     5433 return 0
128             if (
129             ( grep {
130 1220         1135 ( defined $self->resolve( $_, $model )
131             and $self->resolve( $_, $model ) == 0 )
132             ? 0
133             : 1
134 1220 100       993 } @{$clauses}
135             ) == 0
136             );
137              
138             #If any variable is true, then the clause is true.
139 3388 100 100     5479 return 1
140             if (
141             ( grep {
142 1219         1143 ( defined $self->resolve( $_, $model )
143             and $self->resolve( $_, $model ) == 1 )
144             ? 1
145             : 0
146 1219 100       1761 } @{$clauses}
147             ) > 0
148             );
149              
150             # Otherwise, we don't know what the clause is.
151 393         1562 return undef;
152             }
153              
154             1;
155             __END__