File Coverage

blib/lib/Logic/Minimizer.pm
Criterion Covered Total %
statement 14 119 11.7
branch 0 70 0.0
condition 0 15 0.0
subroutine 5 12 41.6
pod 5 7 71.4
total 24 223 10.7


line stmt bran cond sub pod time code
1             package Logic::Minimizer;
2              
3 1     1   64511 use 5.016001;
  1         4  
4              
5 1     1   644 use Moose;
  1         457755  
  1         5  
6 1     1   7949 use namespace::autoclean;
  1         7947  
  1         5  
7              
8 1     1   60 use Carp;
  1         2  
  1         63  
9              
10 1     1   740 use List::Compare::Functional qw(get_intersection);
  1         10287  
  1         1797  
11              
12             our $VERSION = '1.00';
13              
14              
15             #
16             # Required attributes to create the object.
17             #
18             # 1. 'width' is absolutely required (handled via Moose).
19             #
20             # 2. If 'columnstring' is provided, 'minterms', 'maxterms', and
21             # 'dontcares' can't be used.
22             #
23             # 3. Either 'minterms' or 'maxterms' is used, but not both.
24             #
25             # 4. 'dontcares' are used with either 'minterms' or 'maxterms', but
26             # cannot be used by itself.
27             #
28             has 'width' => (
29             isa => 'Int', is => 'ro', required => 1,
30             );
31              
32             has 'minterms' => (
33             isa => 'ArrayRef[Int]', is => 'rw', required => 0,
34             predicate => 'has_minterms',
35             );
36             has 'maxterms' => (
37             isa => 'ArrayRef[Int]', is => 'rw', required => 0,
38             predicate => 'has_maxterms',
39             );
40             has 'dontcares' => (
41             isa => 'ArrayRef[Int]', is => 'rw', required => 0,
42             clearer => 'clear_dontcares',
43             predicate => 'has_dontcares',
44             );
45             has 'columnstring' => (
46             isa => 'Str', is => 'ro', required => 0,
47             predicate => 'has_columnstring',
48             lazy => 1,
49             builder => 'to_columnstring',
50             );
51             has 'columnlist' => (
52             isa => 'ArrayRef[Int]', is => 'ro', required => 0,
53             predicate => 'has_columnlist',
54             lazy => 1,
55             builder => 'to_columnlist',
56             );
57              
58             has 'algorithm' => (
59             isa => 'Str', is => 'rw',
60             builder => 'extract_algorithm',
61             );
62              
63             #
64             # Optional attributes.
65             #
66             has 'title' => (
67             isa => 'Str', is => 'rw', required => 0,
68             predicate => 'has_title'
69             );
70             has 'dc' => (
71             isa => 'Str', is => 'rw',
72             default => '-'
73             );
74             has 'vars' => (
75             isa => 'ArrayRef[Str]', is => 'rw', required => 0,
76             default => sub{['A' .. 'Z']}
77             );
78              
79              
80             #
81             # Prime implicants, essentials, and covers are all "lazy"
82             # attributes and calculated when asked for in code or by
83             # the user. All are created via builder functions that
84             # are provided by the child class.
85             #
86              
87             #
88             # The prime implicants. The hash is of the form
89             # 'implicant' => [terms that are covered].
90             #
91             has 'primes' => (
92             isa => 'HashRef', is => 'ro', required => 0,
93             init_arg => undef,
94             reader => 'get_primes',
95             writer => '_set_primes',
96             predicate => 'has_primes',
97             clearer => 'clear_primes',
98             lazy => 1,
99             builder => 'generate_primes',
100             );
101              
102             #
103             # The essential prime implicants (for informational
104             # purposes only, algorithms generally don't need
105             # it for minimizing).
106             #
107             has 'essentials' => (
108             isa => 'ArrayRef', is => 'ro', required => 0,
109             init_arg => undef,
110             reader => 'get_essentials',
111             writer => '_set_essentials',
112             predicate => 'has_essentials',
113             clearer => 'clear_essentials',
114             lazy => 1,
115             builder => 'generate_essentials',
116             );
117              
118             #
119             # The covers are the building blocks of the final form
120             # of the solution to the equations and are used to create
121             # the text form of the equations.
122             #
123             # The terms are listed in an array, but as there may be
124             # more than one way to cover the solution, it is an array
125             # of cover arrays.
126             #
127             has 'covers' => (
128             isa => 'ArrayRef[ArrayRef[Str]]', is => 'ro', required => 0,
129             init_arg => undef,
130             reader => 'get_covers',
131             writer => '_set_covers',
132             predicate => 'has_covers',
133             clearer => 'clear_covers',
134             lazy => 1,
135             builder => 'generate_covers',
136             );
137              
138             #
139             # The print options.
140             #
141             has 'and_symbol' => (
142             isa => 'Str', is => 'rw',
143             reader => 'get_and_symbol',
144             writer => 'set_and_symbol',
145             default => ''
146             );
147              
148             has 'or_symbol' => (
149             isa => 'Str', is => 'rw',
150             reader => 'get_or_symbol',
151             writer => 'set_or_symbol',
152             default => ' + '
153             );
154              
155             has 'group_symbols' => (
156             isa => 'ArrayRef[Str]', is => 'rw',
157             reader => 'get_group_symbols',
158             writer => 'set_group_symbols',
159             default => sub{['(', ')']}
160             );
161              
162             has 'var_formats' => (
163             isa => 'ArrayRef[Str]', is => 'rw',
164             reader => 'get_var_formats',
165             writer => 'set_var_formats',
166             default => sub{["%s", "%s'"]}
167             );
168              
169             #
170             # Change behavior.
171             #
172             has 'order_by' => (
173             isa => 'Str', is => 'rw',
174             reader => 'get_order_by',
175             writer => 'set_order_by',
176             default => 'none',
177             );
178              
179             has 'minonly' => (
180             isa => 'Bool', is => 'rw',
181             default => 1
182             );
183              
184             #
185             # $self->catch_errors();
186             #
187             # Sanity checking for parameters that contradict each other
188             # or which aren't sufficient to create the object.
189             #
190             # These are fatal errors. No return value needs to be checked,
191             # because any error results in a using croak().
192             #
193             sub catch_errors
194             {
195 0     0 1   my $self = shift;
196 0           my $w = $self->width;
197              
198             #
199             # Catch errors involving minterms, maxterms, and don't-cares.
200             #
201 0 0 0       croak "Mixing minterms and maxterms not allowed"
202             if ($self->has_minterms and $self->has_maxterms);
203              
204             #
205             # Does the width make sense?
206             #
207 0 0         croak "'width' must be at least 1" if ($w < 1);
208              
209 0           my $wp2 = 1 << $w;
210              
211 0 0 0       if ($self->has_columnstring or $self->has_columnlist)
212             {
213 0 0 0       croak "Other terms are redundant when using the columnstring or columnlist attributes"
      0        
214             if ($self->has_minterms or $self->has_maxterms or $self->has_dontcares);
215 0 0 0       croak "Use only one of the columnstring or columnlist attributes"
216             if ($self->has_columnstring and $self->has_columnlist);
217              
218             my $cl = $wp2 - (($self->has_columnstring)?
219             length $self->columnstring:
220 0 0         $#{$self->columnlist});
  0            
221              
222 0 0         if ($cl != 0)
223             {
224 0 0         my $attr = ($self->has_columnlist)? "Columnlist": "Columnstring";
225              
226 0 0         croak "$attr length is too short by ", $cl if ($cl > 0);
227 0           croak "$attr length is too long by ", -$cl;
228             }
229             }
230             else
231             {
232 0           my @terms;
233              
234 0 0         if ($self->has_minterms)
    0          
235             {
236 0           @terms = @{ $self->minterms };
  0            
237 0 0         croak "Empty 'minterm' array reference." unless (scalar @terms);
238             }
239             elsif ($self->has_maxterms)
240             {
241 0           @terms = @{ $self->maxterms };
  0            
242 0 0         croak "Empty 'maxterm' array reference" unless (scalar @terms);
243             }
244             else
245             {
246 0           croak "Must supply either minterms or maxterms";
247             }
248              
249 0 0         if ($self->has_dontcares)
250             {
251 0           my @dcs = @{ $self->dontcares };
  0            
252 0 0         unless (scalar @dcs)
253             {
254 0           carp "Empty 'dontcare' array reference";
255 0           $self->clear_dontcares();
256             }
257             else
258             {
259 0           my @intersect = get_intersection([\@dcs, \@terms]);
260 0 0         if (scalar @intersect != 0)
261             {
262 0           croak "Term(s) ", join(", ", @intersect),
263             " are in both the don't-care list and the term list.";
264             }
265              
266 0           push @terms, @dcs;
267             }
268             }
269              
270             #
271             # Can those terms be expressed in 'width' bits?
272             #
273 0 0         my @outside = grep {$_ >= $wp2 or $_ <= -1} @terms;
  0            
274              
275 0 0         if (scalar @outside)
276             {
277 0           croak "Terms (" . join(", ", @outside) . ") are larger than $w bits";
278             }
279             }
280              
281             #
282             # Do we really need to check if they've set the
283             # don't-care character to '0' or '1'? Oh well...
284             #
285 0 0         croak "Don't-care must be a single character" if (length $self->dc != 1);
286 0 0         croak "The don't-care character cannot be '0' or '1'" if ($self->dc =~ qr([01]));
287              
288             #
289             # Make sure we have enough variable names.
290             #
291 0 0         croak "Not enough variable names for your width" if (scalar @{$self->vars} < $w);
  0            
292              
293 0           return 1;
294             }
295              
296             #
297             # Return an array reference made up of the function column.
298             # Position 0 in the array is the 0th row of the column, and so on.
299             #
300             sub to_columnlist
301             {
302 0     0 1   my $self = shift;
303 0 0         my ($dfltbit, $setbit) = ($self->has_min_bits)? qw(0 1): qw(1 0);
304 0           my @bitlist = ($dfltbit) x (1 << $self->width);
305              
306 0           my @terms;
307              
308 0 0         push @terms, @{$self->minterms} if ($self->has_minterms);
  0            
309 0 0         push @terms, @{$self->maxterms} if ($self->has_maxterms);
  0            
310              
311 0           map {$bitlist[$_] = $setbit} @terms;
  0            
312              
313 0 0         if ($self->has_dontcares)
314             {
315 0           map {$bitlist[$_] = $self->dc} (@{ $self->dontcares});
  0            
  0            
316             }
317              
318 0           return \@bitlist;
319             }
320              
321             #
322             # Return a string made up of the function column. Position 0 in the string is
323             # the 0th row of the column, and so on.
324             #
325             sub to_columnstring
326             {
327 0     0 1   my $self = shift;
328              
329 0           return join "", @{ $self->to_columnlist };
  0            
330             }
331              
332             #
333             # Take a column list and return array refs usable as parameters for
334             # minterm, maxterm, and don't-care attributes.
335             #
336             sub list_to_terms
337             {
338 0     0 0   my $self = shift;
339 0           my(@bitlist) = @_;
340 0           my $x = 0;
341              
342 0           my(@maxterms, @minterms, @dontcares);
343              
344 0           for (@bitlist)
345             {
346 0 0         if ($_ eq '1')
    0          
347             {
348 0           push @minterms, $x;
349             }
350             elsif ($_ eq '0')
351             {
352 0           push @maxterms, $x;
353             }
354             else
355             {
356 0           push @dontcares, $x;
357             }
358 0           $x++;
359             }
360              
361 0           return (\@minterms, \@maxterms, \@dontcares);
362             }
363              
364             #
365             # Get the algorithm name from the algorithm package name, suitable for
366             # using in the 'algorithm' parameter of Logic::TruthTable->new().
367             #
368             sub extract_algorithm
369             {
370 0     0 0   my $self = shift;
371 0           my $al = ref $self;
372              
373             #
374             # There is probably a better way to do this.
375             #
376 0           $al=~ s/^Algorithm:://;
377 0           $al =~ s/::/-/g;
378 0           return $al;
379             }
380              
381             sub to_boolean
382             {
383 0     0 1   my $self = shift;
384 0           my($cref) = @_;
385 0           my $is_sop = $self->has_min_bits;
386 0           my $w = $self->width;
387              
388             #
389             # Group separators, and the group joiner string (set according
390             # to whether this is a sum-of-products or product-of-sums).
391             #
392 0           my($gsb, $gse) = @{$self->get_group_symbols()};
  0            
393 0 0         my $gj = $is_sop ? $self->get_or_symbol() : $self->get_and_symbol();
394              
395 0           my @covers = @$cref;
396              
397             #
398             # Check for the special case where the covers are reduced to
399             # a single expression of nothing but dc characters
400             # (e.g., "----"). This happens when all of the terms are
401             # covered, resulting in an equation that would be simply
402             # "(1)" (or "(0)" if using maxterms). Since the normal
403             # translation will return "()", this has to checked.
404             #
405 0 0         if ($#covers == 0)
406             {
407 0 0         if ($covers[0] =~ /[^01]{$w}/)
408             {
409 0 0         return $gsb . (($is_sop)? "1": "0") . $gse;
410             }
411             else
412             {
413 0           return $gsb . $self->to_boolean_term($covers[0], $is_sop) . $gse;
414             }
415             }
416              
417 0 0         @covers = sort @covers if ($self->get_order_by eq 'covers');
418              
419 0           my @exprns = map {$gsb . $self->to_boolean_term($_, $is_sop) . $gse} @covers;
  0            
420 0 0         @exprns = sort @exprns if ($self->get_order_by eq 'vars');
421              
422 0           return join $gj, @exprns;
423             }
424              
425             #
426             # Convert an individual term or prime implicant to a boolean variable string.
427             #
428             sub to_boolean_term
429             {
430 0     0 1   my $self = shift;
431 0           my($term, $is_sop) = @_;
432              
433             #
434             # The variable and complemented variable formats.
435             #
436 0           my($vf, $nvf) = @{$self->get_var_formats()};
  0            
437              
438             #
439             # Element joiner and match condition
440             #
441 0 0         my($ej, $cond) =
442             $is_sop ? ($self->get_and_symbol(), 1):
443             ($self->get_or_symbol(), 0);
444              
445 0           my @trits = split //, $term;
446 0           my @indices = grep{$trits[$_] ne $self->dc} (0 .. $#trits);
  0            
447              
448 0           my @vars = @{$self->vars};
  0            
449              
450             return join $ej, map {
451 0 0         sprintf(($trits[$_] == $cond ? $vf: $nvf), $vars[$_])
  0            
452             } @indices;
453             }
454              
455             =head1 NAME
456              
457             Logic::Minimizer - The parent class of Boolean minimizers.
458              
459             =head1 SYNOPSIS
460              
461             This is the base class for logic minimizers that are used by
462             L<Logic::TruthTable>. You do not need to use this class (or
463             indeed read any further) unless you are creating a logic
464             minimizer package.
465              
466             package Algorithm::SomethingNiftyLikeEspresso;
467             extends 'Logic::Minimizer';
468              
469             (C<Algorithm::SomethingNiftyLikeEspresso> has C<Logic::Minimizer>
470             as its base class, which is required if C<Logic::TruthTable> is to
471             use it.)
472              
473             Then, either use the package directly in your program:
474              
475             my $fn = Algorithm::SomethingNiftyLikeEspresso->new(
476             width => 4,
477             minterms => [1, 8, 9, 14, 15],
478             dontcares => [2, 3, 11, 12]
479             );
480             ...
481              
482             or as a algorithm choice in C<Logic::TruthTable>:
483              
484             my $tt = Logic::TruthTable->new(
485             width => 4,
486             algorithm => 'SomethingNiftyLikeEspresso',
487             columns => [
488             {
489             minterms => [1, 8, 9, 14, 15],
490             dontcares => [2, 3, 11, 12],
491             }
492             {
493             minterms => [4, 5, 6, 10, 13],
494             dontcares => [2, 3, 11, 12],
495             }
496             ],
497             );
498              
499             This class provides the attributes and some of the methods for your
500             minimizer class.
501              
502             =head3 Minimizer Attributes
503              
504             These are the attributes provided by C<Logic::Minimizer> to
505             create and reduce the object. Some attributes are required
506             to be set from the program, some are optional, and others are
507             generated internally.
508              
509             The attributes required to create the object are:
510              
511             =over 4
512              
513             =item 'width'
514              
515             C<width> is absolutely required -- it tells the object how many
516             variables are used in the Boolean equation.
517              
518             =item 'minterms'
519              
520             =item 'maxterms'
521              
522             The set (if using minterms) or unset (if using maxterms) terms in
523             the Boolean equation. The choice affects the output of the equation:
524             using minterms results in a sum-of-products form of the equation, while
525             using maxterms results in a product-of-sums form.
526              
527             =item 'dontcares'
528              
529             The terms that don't affect the output of the equation at all; that
530             one literally doesn't care about.
531              
532             =item 'columnstring'
533              
534             =item 'columnlist'
535              
536             Alternate ways of providing minterms, maxterms, and don't-care terms.
537             Position 0 in the string or list represents the 0th row of the column,
538             position 1 represents the next row, and so on. In either case, the
539             output is in sum-of-product form.
540              
541             =back
542              
543             Some common sense rules apply. If 'columnstring' or 'columnlist' is
544             provided, 'minterms', 'maxterms', and 'dontcares' can't be used.
545              
546             Likewise, 'minterms' and 'maxterms' can not be used together, and
547             'dontcares' can't be used by itself.
548              
549             Some attributes are optional, and are used to changed the look
550             of the output.
551              
552             =over 4
553              
554             =item 'title'
555              
556             A name or description of the problem. Useful for tracking
557             which object one is working with, if there's more than one.
558              
559             =item 'dc'
560              
561             The don't-care character. May be changed for both aesthetic
562             and inter-program communication purposes. Used in columnstring
563             or columnlist attributes. Also used in output files by
564             C<Logic::TruthTable>, and in 'covers' output (see below).
565             Defaults to the character '-'.
566              
567             =item 'vars'
568              
569             The variable names used to output the Boolean equation. By default,
570             uses 'A' to 'Z'. The names do not have to be single characters, e.g.:
571             C<vars => ['a1', 'a0', 'b1', 'b0']>
572              
573             =back
574              
575             The attributes that are set during the minimizing process.
576              
577             These are all "lazy" attributes, and calculated when asked
578             for in code or by the user. All are created by builder
579             functions that the child class needs to provide.
580              
581             =over 4
582              
583             =item 'primes'
584              
585             The prime implicants. A hash in the form
586             C<'implicant' => [covered terms]>.
587              
588             =item 'covers'
589              
590             The covers are the building blocks of the final form
591             of the solution to the equation, and are used to create
592             the text form of the equation.
593              
594             The terms are listed in an array, but as there may be
595             more than one way to cover the solution, it is an array
596             of cover arrays.
597              
598             =item 'essentials'
599              
600             The essential prime implicants (for informational purposes
601             only, algorithms generally don't need it for minimizing).
602              
603             =item 'algorithm'
604              
605             The class of the child object. for example, if the
606             class used to solve the set of terms is Algorithm::QuineMcCluskey,
607             then 'algorithm' would be set to 'QuineMcCluskey'.
608              
609             =back
610              
611             =head3 Minimizer Methods
612              
613             These are the methods needed for error checking, or to read from or
614             write to the attributes.
615              
616             Some are provided by C<Logic::Minimizer>, while others (the builder
617             methods of the attributes 'primes', 'covers', and 'essentials') are
618             merely method definitions, and are expected to be provided by the
619             child class.
620              
621             =over 4
622              
623             =item to_columnstring
624              
625             Provided by this module. Converts the column into a single string.
626              
627             =item to_columnlist
628              
629             Provided by this module. Converts the column into an array reference.
630              
631             =item catch_errors
632              
633             Provided by this module. Finds basic errors such as empty term arrays,
634             insufficient variables for the width, terms that don't fit within the
635             table size, and so forth.
636              
637             =item to_boolean
638              
639             Provided by this module. Converts a set of covers to a boolean equation.
640              
641             =item to_boolean_term
642              
643             Provided by this module. Converts a cover term into a boolean expression.
644             Called by C<to_boolean()>.
645              
646             =item generate_primes
647              
648             =item generate_covers
649              
650             =item generate_essentials
651              
652             The primes, covers, and essentials attributes need methods to create
653             those attributes. The child module of C<Logic::Minimizer> should provide these.
654              
655             =back
656              
657              
658             =head1 AUTHOR
659              
660             John M. Gamble, C<< <jgamble at cpan.org> >>
661              
662             =head1 BUGS
663              
664             Please report any bugs or feature requests to C<bug-logic-minimizer at rt.cpan.org>,
665             or through the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Logic-Minimizer>.
666             I will be notified, and then you'll automatically be notified of progress on
667             your bug as I make changes.
668              
669              
670             =head1 SUPPORT
671              
672             You can find documentation for this module with the perldoc command.
673              
674             perldoc Logic::Minimizer
675              
676             You can also look for information at:
677              
678             =over 4
679              
680             =item * RT: CPAN's request tracker (report bugs here)
681              
682             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Logic-Minimizer>
683              
684             =item * AnnoCPAN: Annotated CPAN documentation
685              
686             L<http://annocpan.org/dist/Logic-Minimizer>
687              
688             =item * CPAN Ratings
689              
690             L<http://cpanratings.perl.org/d/Logic-Minimizer>
691              
692             =item * Search CPAN
693              
694             L<http://search.cpan.org/dist/Logic-Minimizer/>
695              
696             =back
697              
698              
699             =head1 ACKNOWLEDGEMENTS
700              
701              
702             =head1 LICENSE AND COPYRIGHT
703              
704             Copyright 2015 John M. Gamble.
705              
706             This program is free software; you can redistribute it and/or modify it
707             under the terms of either: the GNU General Public License as published
708             by the Free Software Foundation; or the Artistic License.
709              
710             See L<http://dev.perl.org/licenses/> for more information.
711              
712              
713             =cut
714              
715             1;