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   53627 use strict;
  7         16  
  7         264  
3 7     7   30 use warnings;
  7         9  
  7         184  
4 7     7   4478 use Storable qw(dclone);
  7         18396  
  7         3399  
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.12";
19              
20             sub new {
21 32     32 0 28072 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 3580 my $self = shift;
32 34         35 my $variables = shift;
33 34         33 my $clauses = shift;
34 34 100       60 my $model = defined $_[0] ? shift : {};
35              
36             # If every clause is satisfiable, return the model which worked.
37              
38 120 100 66     209 return $model
39             if (
40             ( grep {
41 34         50 ( defined $self->satisfiable( $_, $model )
42             and $self->satisfiable( $_, $model ) == 1 )
43             ? 0
44             : 1
45 34 100       30 } @{$clauses}
46             ) == 0
47             );
48              
49             # If any clause is **exactly** false, return `false`; this model will not
50             # work.
51 96 50 66     173 return 0
52             if (
53             ( grep {
54 27         30 ( defined $self->satisfiable( $_, $model )
55             and $self->satisfiable( $_, $model ) == 0 )
56             ? 1
57             : 0
58 27 50       45 } @{$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         66 my $choice = $self->_choice( $variables, $model );
66              
67              
68             # If there are no more variables to try, return false.
69              
70 27 50       47 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     54 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   32 my $self = shift;
82 33         29 my $variables = shift;
83 33         26 my $model = shift;
84 33         41 my $choice;
85 33         29 foreach my $variable ( @{$variables} ) {
  33         43  
86 81 100 50     164 $choice = $variable and last if ( !exists $model->{$variable} );
87             }
88 33         52 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 50     50 1 1360 my $self = shift;
96 50         898 my $copy = dclone(shift);
97 50         57 my $choice = shift;
98 50         47 my $value = shift;
99 50         73 $copy->{$choice} = $value;
100 50         156 return $copy;
101             }
102              
103             # ### resolve
104             # Resolve some variable to its actual value, or undefined.
105             sub resolve {
106 6530     6530 1 5453 my $self = shift;
107 6530         4999 my $var = shift;
108 6530         4575 my $model = shift;
109 6530 100       8213 if ( substr( $var, 0, 1 ) eq "-" ) {
110 2549         2356 my $value = $model->{ substr( $var, 1 ) };
111 2549 100       8939 return !defined $value ? undef : $value == 0 ? 1 : 0;
    100          
112             }
113             else {
114 3981         10788 return $model->{$var};
115             }
116             }
117              
118             # ### satisfiable
119             # Determines whether a clause is satisfiable given a certain model.
120             sub satisfiable {
121 1184     1184 1 8919 my $self = shift;
122 1184         840 my $clauses = shift;
123 1184         866 my $model = shift;
124 1184         840 my @clause = @{$clauses};
  1184         1646  
125              
126             # If every variable is false, then the clause is false.
127 3264 100 100     5325 return 0
128             if (
129             ( grep {
130 1184         1171 ( defined $self->resolve( $_, $model )
131             and $self->resolve( $_, $model ) == 0 )
132             ? 0
133             : 1
134 1184 100       890 } @{$clauses}
135             ) == 0
136             );
137              
138             #If any variable is true, then the clause is true.
139 3262 100 100     5260 return 1
140             if (
141             ( grep {
142 1183         1122 ( defined $self->resolve( $_, $model )
143             and $self->resolve( $_, $model ) == 1 )
144             ? 1
145             : 0
146 1183 100       1674 } @{$clauses}
147             ) > 0
148             );
149              
150             # Otherwise, we don't know what the clause is.
151 363         1550 return undef;
152             }
153              
154             1;
155             __END__