Commit | Line | Data |
---|---|---|
86530b38 AT |
1 | package Cudd; |
2 | ||
3 | use strict; | |
4 | use Carp; | |
5 | use vars qw($VERSION @ISA @EXPORT $AUTOLOAD); | |
6 | ||
7 | require Exporter; | |
8 | require DynaLoader; | |
9 | require AutoLoader; | |
10 | ||
11 | @ISA = qw(Exporter DynaLoader); | |
12 | # Items to export into callers namespace by default. Note: do not export | |
13 | # names by default without a very good reason. Use EXPORT_OK instead. | |
14 | # Do not simply export all your public functions/methods/constants. | |
15 | @EXPORT = qw( | |
16 | APA_BASE | |
17 | APA_BITS | |
18 | APA_MASK | |
19 | CACHE_SLOTS | |
20 | MAXINDEX | |
21 | MTR_DEFAULT | |
22 | MTR_FIXED | |
23 | OUT_OF_MEM | |
24 | REORDER_SAME | |
25 | REORDER_NONE | |
26 | REORDER_RANDOM | |
27 | REORDER_RANDOM_PIVOT | |
28 | REORDER_SIFT | |
29 | REORDER_SIFT_CONVERGE | |
30 | REORDER_SYMM_SIFT | |
31 | REORDER_SYMM_SIFT_CONV | |
32 | REORDER_WINDOW2 | |
33 | REORDER_WINDOW3 | |
34 | REORDER_WINDOW4 | |
35 | REORDER_WINDOW2_CONV | |
36 | REORDER_WINDOW3_CONV | |
37 | REORDER_WINDOW4_CONV | |
38 | REORDER_GROUP_SIFT | |
39 | REORDER_GROUP_SIFT_CONV | |
40 | REORDER_ANNEALING | |
41 | REORDER_GENETIC | |
42 | REORDER_LINEAR | |
43 | REORDER_LINEAR_CONVERGE | |
44 | REORDER_EXACT | |
45 | RESIDUE_DEFAULT | |
46 | RESIDUE_MSB | |
47 | RESIDUE_TC | |
48 | SIZEOF_INT | |
49 | SIZEOF_LONG | |
50 | SIZEOF_VOID_P | |
51 | UNIQUE_SLOTS | |
52 | ); | |
53 | $VERSION = '0.06'; | |
54 | ||
55 | sub AUTOLOAD { | |
56 | # This AUTOLOAD is used to 'autoload' constants from the constant() | |
57 | # XS function. If a constant is not found then control is passed | |
58 | # to the AUTOLOAD in AutoLoader. | |
59 | ||
60 | my $constname; | |
61 | ($constname = $AUTOLOAD) =~ s/.*:://; | |
62 | my $val = constant($constname, @_ ? $_[0] : 0); | |
63 | if ($! != 0) { | |
64 | if ($! =~ /Invalid/) { | |
65 | $AutoLoader::AUTOLOAD = $AUTOLOAD; | |
66 | goto &AutoLoader::AUTOLOAD; | |
67 | } | |
68 | else { | |
69 | croak "Your vendor has not defined Cudd macro $constname"; | |
70 | } | |
71 | } | |
72 | eval "sub $AUTOLOAD { $val }"; | |
73 | goto &$AUTOLOAD; | |
74 | } | |
75 | ||
76 | bootstrap Cudd $VERSION; | |
77 | ||
78 | # Preloaded methods go here. | |
79 | ||
80 | =head1 NAME | |
81 | ||
82 | Cudd - Perl extension for BDDs | |
83 | ||
84 | =head1 SYNOPSIS | |
85 | ||
86 | C<use Cudd;> | |
87 | ||
88 | C<my $manager = new Cudd;> | |
89 | ||
90 | and so on | |
91 | ||
92 | =head1 DESCRIPTION | |
93 | ||
94 | Cudd extends Perl so that it manipulates Binary Decision Diagrams | |
95 | (BDDs) by providing an object-oriented encapsulation of the CUDD | |
96 | package. The CUDD package is written in C and contains a large | |
97 | collection of efficient BDD manipulation functions. Only a small | |
98 | fraction of those functions are currently made available by the Cudd | |
99 | module. Those that are available, however, allow one to accomplish | |
100 | most tasks. | |
101 | ||
102 | =head2 | |
103 | ||
104 | The methods defined by Cudd are divided into two packages. The first | |
105 | package, Cudd, defines methods that create and manipulate BDD managers | |
106 | and BDD variables. These are the methods defined in package Cudd: | |
107 | ||
108 | =over 4 | |
109 | =cut | |
110 | ||
111 | =item new | |
112 | ||
113 | Creates a new BDD manager. | |
114 | ||
115 | =cut | |
116 | ||
117 | sub new { | |
118 | my $class = shift; | |
119 | my $numVars = shift || 0; | |
120 | my $numVarsZ = shift || 0; | |
121 | my $numSlots = shift || &Cudd::UNIQUE_SLOTS; | |
122 | my $cacheSize = shift || &Cudd::CACHE_SLOTS; | |
123 | my $maxMemory = shift || 0; | |
124 | my $manager = Init($numVars,$numVarsZ,$numSlots,$cacheSize,$maxMemory); | |
125 | bless \$manager, $class; | |
126 | return \$manager; | |
127 | } | |
128 | ||
129 | sub DESTROY { | |
130 | my $manager = shift; | |
131 | my $retval = CheckZeroRef($$manager); | |
132 | if ($retval != 0) { | |
133 | print "In Cudd::DESTROY $retval non-zero references\n"; | |
134 | } | |
135 | Quit($$manager); | |
136 | } | |
137 | ||
138 | =item BddVar | |
139 | ||
140 | Creates a new variable in a BDD manager. | |
141 | ||
142 | =cut | |
143 | ||
144 | sub BddVar { | |
145 | my $manager = shift; | |
146 | croak "Invalid reference" unless ref $manager; | |
147 | my $node = bddNewVar($$manager); | |
148 | Ref($node); | |
149 | my $BDD = Cudd::BDD->new($manager,$node); | |
150 | return $BDD; | |
151 | } | |
152 | ||
153 | =item BddOne | |
154 | ||
155 | Returns the constant 1 of a BDD manager. | |
156 | ||
157 | =cut | |
158 | ||
159 | sub BddOne { | |
160 | my $manager = shift; | |
161 | croak "Invalid reference" unless ref $manager; | |
162 | my $node = ReadOne($$manager); | |
163 | Ref($node); | |
164 | my $BDD = Cudd::BDD->new($manager,$node); | |
165 | return $BDD; | |
166 | } | |
167 | ||
168 | =item Reorder | |
169 | ||
170 | Reorders the BDDs of a manager. | |
171 | ||
172 | =cut | |
173 | ||
174 | sub Reorder { | |
175 | my $manager = shift; | |
176 | my $method = shift || &Cudd::REORDER_SAME; | |
177 | my $minsize = shift || 1; | |
178 | croak "Invalid reference" unless ref $manager; | |
179 | Cudd::ReduceHeap($$manager,$method,$minsize); | |
180 | } | |
181 | ||
182 | =item EnableDyn | |
183 | ||
184 | Enables dynamic reordering for a BDD manager. | |
185 | ||
186 | =cut | |
187 | ||
188 | sub EnableDyn { | |
189 | my $manager = shift; | |
190 | my $method = shift || &Cudd::REORDER_SAME; | |
191 | croak "Invalid reference" unless ref $manager; | |
192 | Cudd::AutodynEnable($$manager,$method); | |
193 | } | |
194 | ||
195 | =item DisableDyn | |
196 | ||
197 | Disables dynamic reordering for a BDD manager. | |
198 | ||
199 | =cut | |
200 | ||
201 | sub DisableDyn { | |
202 | my $manager = shift; | |
203 | croak "Invalid reference" unless ref $manager; | |
204 | Cudd::AutodynDisable($$manager); | |
205 | } | |
206 | ||
207 | =item ReportReordering | |
208 | ||
209 | Enables reporting of dynamic reordering. | |
210 | ||
211 | =cut | |
212 | ||
213 | sub ReportReordering { | |
214 | my $manager = shift; | |
215 | croak "Invalid reference" unless ref $manager; | |
216 | Cudd::EnableReorderingReporting($$manager); | |
217 | } | |
218 | ||
219 | ||
220 | =item DoNotReportReordering | |
221 | ||
222 | Disables reporting of dynamic reordering. | |
223 | ||
224 | =cut | |
225 | ||
226 | sub DoNotReportReordering { | |
227 | my $manager = shift; | |
228 | croak "Invalid reference" unless ref $manager; | |
229 | Cudd::DisableReorderingReporting($$manager); | |
230 | } | |
231 | ||
232 | =item Info | |
233 | ||
234 | Prints statistical information on a BDD manager. | |
235 | ||
236 | =cut | |
237 | ||
238 | sub Info { | |
239 | my $manager = shift; | |
240 | croak "Invalid reference" unless ref $manager; | |
241 | my $retval = Cudd::PrintInfo($$manager); | |
242 | croak "Error while printing manager info" if ($retval != 1); | |
243 | } | |
244 | ||
245 | =item PeakLiveNodes | |
246 | ||
247 | Returns the peak number of live nodes in the manager. | |
248 | ||
249 | =cut | |
250 | ||
251 | sub PeakLiveNodes { | |
252 | my $manager = shift; | |
253 | croak "Invalid reference" unless ref $manager; | |
254 | my $retval = Cudd::ReadPeakLiveNodeCount($$manager); | |
255 | return $retval; | |
256 | } | |
257 | ||
258 | =item LiveNodes | |
259 | ||
260 | Returns the number of live nodes in the manager. | |
261 | ||
262 | =cut | |
263 | ||
264 | sub LiveNodes { | |
265 | my $manager = shift; | |
266 | croak "Invalid reference" unless ref $manager; | |
267 | my $retval = Cudd::ReadNodeCount($$manager); | |
268 | return $retval; | |
269 | } | |
270 | ||
271 | =item DynReorderingStatus | |
272 | ||
273 | Returns a list with two elements. The first is 1 if reordering is | |
274 | currently enabled and 0 otherwise; the second is the reordering method | |
275 | currently selected. | |
276 | ||
277 | =cut | |
278 | ||
279 | sub DynReorderingStatus { | |
280 | my $manager = shift; | |
281 | croak "Invalid reference" unless ref $manager; | |
282 | my @retval = Cudd::ReorderingStatus($$manager); | |
283 | return @retval; | |
284 | } | |
285 | ||
286 | =back 4 | |
287 | ||
288 | =cut | |
289 | ||
290 | package Cudd::BDD; | |
291 | ||
292 | use Carp; | |
293 | use overload | |
294 | '+' => "Or", | |
295 | '*' => "And", | |
296 | '/' => "Cofactor", | |
297 | '^' => "Xor", | |
298 | '!' => "Not", | |
299 | '==' => "Equal", | |
300 | '!=' => "Unequal", | |
301 | '<=' => "Leq", | |
302 | '>=' => "Geq", | |
303 | '""' => sub { return "OK"; }, | |
304 | 'bool' => sub { my $self = shift; return $self->Unequal($self->Zero); } | |
305 | ; | |
306 | ||
307 | =head2 | |
308 | ||
309 | The second package in module Cudd is Cudd::BDD. It defines methods | |
310 | that manipulate BDDs. Starting with a set of variables belonging to a | |
311 | manager, the functions in this package allow one to create BDDs for | |
312 | arbitrary functions over those variables. These are the methods | |
313 | defined by Cudd::BDD: | |
314 | ||
315 | =over 4 | |
316 | ||
317 | =cut | |
318 | ||
319 | sub new { | |
320 | my ($class,$manager,$node) = @_; | |
321 | croak "Invalid reference" unless ref $manager; | |
322 | # carp "In Cudd::BDD::new: creating $node\n"; | |
323 | bless { manager => $manager, | |
324 | node => $node | |
325 | }, $class; | |
326 | } | |
327 | ||
328 | sub DESTROY { | |
329 | my $self = shift; | |
330 | my $manager = $self->{manager}; | |
331 | croak "Invalid reference" unless ref $manager; | |
332 | # carp "In Cudd::BDD::DESTROY: dereferencing $self->{node}\n"; | |
333 | Cudd::IterDerefBdd($$manager,$self->{node}); | |
334 | } | |
335 | ||
336 | =item One | |
337 | ||
338 | Returns the constant 1 BDD from the manager of $f. | |
339 | ||
340 | =cut | |
341 | ||
342 | sub One { | |
343 | my $f = shift; | |
344 | my $manager = $f->{'manager'}; | |
345 | croak "Invalid reference" unless ref $manager; | |
346 | return $manager->BddOne; | |
347 | } | |
348 | ||
349 | =item Zero | |
350 | ||
351 | Returns the constant 0 BDD from the manager of $f. | |
352 | ||
353 | =cut | |
354 | ||
355 | sub Zero { | |
356 | my $f = shift; | |
357 | my $manager = $f->{'manager'}; | |
358 | croak "Invalid reference" unless ref $manager; | |
359 | return !$manager->BddOne; | |
360 | } | |
361 | ||
362 | =item And | |
363 | ||
364 | Computes the conjunction of two BDDs. Overloads '*'. | |
365 | ||
366 | =cut | |
367 | ||
368 | sub And { | |
369 | my ($f,$g) = @_; | |
370 | my $manager = $f->{'manager'}; | |
371 | croak "Invalid reference" unless ref $manager; | |
372 | my $node = Cudd::bddAnd($$manager,$f->{'node'},$g->{'node'}); | |
373 | Cudd::Ref($node); | |
374 | my $BDD = new Cudd::BDD($manager,$node); | |
375 | return $BDD; | |
376 | } | |
377 | ||
378 | =item Or | |
379 | ||
380 | Computes the disjunction of two BDDs. Overloads '+'. | |
381 | ||
382 | =cut | |
383 | ||
384 | sub Or { | |
385 | my ($f,$g) = @_; | |
386 | my $manager = $f->{'manager'}; | |
387 | croak "Invalid reference" unless ref $manager; | |
388 | my $node = Cudd::bddOr($$manager,$f->{'node'},$g->{'node'}); | |
389 | Cudd::Ref($node); | |
390 | my $BDD = new Cudd::BDD($manager,$node); | |
391 | return $BDD; | |
392 | } | |
393 | ||
394 | =item Xor | |
395 | ||
396 | Computes the exclusive or of two BDDs. Overloads '^'. | |
397 | ||
398 | =cut | |
399 | ||
400 | sub Xor { | |
401 | my ($f,$g) = @_; | |
402 | my $manager = $f->{'manager'}; | |
403 | croak "Invalid reference" unless ref $manager; | |
404 | my $node = Cudd::bddXor($$manager,$f->{'node'},$g->{'node'}); | |
405 | Cudd::Ref($node); | |
406 | my $BDD = new Cudd::BDD($manager,$node); | |
407 | return $BDD; | |
408 | } | |
409 | ||
410 | sub Xnor { | |
411 | my ($f,$g) = @_; | |
412 | my $manager = $f->{'manager'}; | |
413 | croak "Invalid reference" unless ref $manager; | |
414 | my $node = Cudd::bddXnor($$manager,$f->{'node'},$g->{'node'}); | |
415 | Cudd::Ref($node); | |
416 | my $BDD = new Cudd::BDD($manager,$node); | |
417 | return $BDD; | |
418 | } | |
419 | ||
420 | =item Nand | |
421 | ||
422 | Computes the NAND of two BDDs. | |
423 | ||
424 | =cut | |
425 | ||
426 | sub Nand { | |
427 | my ($f,$g) = @_; | |
428 | my $manager = $f->{'manager'}; | |
429 | croak "Invalid reference" unless ref $manager; | |
430 | my $node = Cudd::bddNand($$manager,$f->{'node'},$g->{'node'}); | |
431 | Cudd::Ref($node); | |
432 | my $BDD = new Cudd::BDD($manager,$node); | |
433 | return $BDD; | |
434 | } | |
435 | ||
436 | =item Nor | |
437 | ||
438 | Computes the NOR of two BDDs. | |
439 | ||
440 | =cut | |
441 | ||
442 | sub Nor { | |
443 | my ($f,$g) = @_; | |
444 | my $manager = $f->{'manager'}; | |
445 | croak "Invalid reference" unless ref $manager; | |
446 | my $node = Cudd::bddNor($$manager,$f->{'node'},$g->{'node'}); | |
447 | Cudd::Ref($node); | |
448 | my $BDD = new Cudd::BDD($manager,$node); | |
449 | return $BDD; | |
450 | } | |
451 | ||
452 | =item Cofactor | |
453 | ||
454 | Computes the cofactor of a BDD with respect to another. Uses the constrain | |
455 | algorithm. Overloads '/'. | |
456 | ||
457 | =cut | |
458 | ||
459 | sub Cofactor { | |
460 | my ($f,$c) = @_; | |
461 | my $manager = $f->{'manager'}; | |
462 | my $node = Cudd::bddConstrain($$manager,$f->{'node'},$c->{'node'}); | |
463 | Cudd::Ref($node); | |
464 | my $BDD = new Cudd::BDD($manager,$node); | |
465 | return $BDD; | |
466 | } | |
467 | ||
468 | =item Restrict | |
469 | ||
470 | Computes the cofactor of a BDD with respect to another. Uses the restrict | |
471 | algorithm. | |
472 | ||
473 | =cut | |
474 | ||
475 | sub Restrict { | |
476 | my ($f,$c) = @_; | |
477 | my $manager = $f->{'manager'}; | |
478 | my $node = Cudd::bddRestrict($$manager,$f->{'node'},$c->{'node'}); | |
479 | Cudd::Ref($node); | |
480 | my $BDD = new Cudd::BDD($manager,$node); | |
481 | return $BDD; | |
482 | } | |
483 | ||
484 | =item Squeeze | |
485 | ||
486 | Finds a small BDD in an interval. | |
487 | ||
488 | =cut | |
489 | ||
490 | sub Squeeze { | |
491 | my ($l,$u) = @_; | |
492 | my $manager = $l->{'manager'}; | |
493 | my $node = Cudd::bddSqueeze($$manager,$l->{'node'},$u->{'node'}); | |
494 | Cudd::Ref($node); | |
495 | my $BDD = new Cudd::BDD($manager,$node); | |
496 | return $BDD; | |
497 | } | |
498 | ||
499 | =item Exists | |
500 | ||
501 | Existentially quantifies the variables in a cube from a BDD. | |
502 | ||
503 | =cut | |
504 | ||
505 | sub Exists { | |
506 | my ($cube,$f) = @_; | |
507 | my $manager = $f->{'manager'}; | |
508 | croak "Invalid reference" unless ref $manager; | |
509 | my $node = Cudd::bddExistAbstract($$manager,$f->{'node'},$cube->{'node'}); | |
510 | Cudd::Ref($node); | |
511 | my $BDD = new Cudd::BDD($manager,$node); | |
512 | return $BDD; | |
513 | } | |
514 | ||
515 | =item Forall | |
516 | ||
517 | Universally quantifies the variables in a cube from a BDD. | |
518 | ||
519 | =cut | |
520 | ||
521 | sub Forall { | |
522 | my ($cube,$f) = @_; | |
523 | my $manager = $f->{'manager'}; | |
524 | croak "Invalid reference" unless ref $manager; | |
525 | my $node = Cudd::bddUnivAbstract($$manager,$f->{'node'},$cube->{'node'}); | |
526 | Cudd::Ref($node); | |
527 | my $BDD = new Cudd::BDD($manager,$node); | |
528 | return $BDD; | |
529 | } | |
530 | ||
531 | =item AndExists | |
532 | ||
533 | Existentially quantifies the variables in a cube from the conjunction of | |
534 | two BDDs. | |
535 | ||
536 | =cut | |
537 | ||
538 | sub AndExists { | |
539 | my ($cube,$f,$g) = @_; | |
540 | my $manager = $f->{'manager'}; | |
541 | croak "Invalid reference" unless ref $manager; | |
542 | my $node = Cudd::bddAndAbstract($$manager,$f->{'node'},$g->{'node'}, | |
543 | $cube->{'node'}); | |
544 | Cudd::Ref($node); | |
545 | my $BDD = new Cudd::BDD($manager,$node); | |
546 | return $BDD; | |
547 | } | |
548 | ||
549 | =item Compose | |
550 | ||
551 | Composes a BDD $g into a BDD $f substituting variable $var. | |
552 | ||
553 | =cut | |
554 | ||
555 | sub Compose { | |
556 | my ($f,$var,$g) = @_; | |
557 | my $manager = $f->{'manager'}; | |
558 | croak "Invalid reference" unless ref $manager; | |
559 | my $index = Cudd::NodeReadIndex($var->{'node'}); | |
560 | my $node = Cudd::bddCompose($$manager,$f->{'node'},$g->{'node'},$index); | |
561 | Cudd::Ref($node); | |
562 | my $BDD = new Cudd::BDD($manager,$node); | |
563 | return $BDD; | |
564 | } | |
565 | ||
566 | =item BooleanDiff | |
567 | ||
568 | Computes the boolean difference of a BDD with respect to one variable. | |
569 | ||
570 | =cut | |
571 | ||
572 | sub BooleanDiff { | |
573 | my ($f,$var) = @_; | |
574 | my $manager = $f->{'manager'}; | |
575 | croak "Invalid reference" unless ref $manager; | |
576 | my $index = Cudd::NodeReadIndex($var->{'node'}); | |
577 | my $node = Cudd::bddBooleanDiff($$manager,$f->{'node'},$index); | |
578 | Cudd::Ref($node); | |
579 | my $BDD = new Cudd::BDD($manager,$node); | |
580 | return $BDD; | |
581 | } | |
582 | ||
583 | =item Correlation | |
584 | ||
585 | Computes the correlation of two BDDs. The result is a number between 0 | |
586 | and 1. A value of 1 indicates that the functions $f and $g are | |
587 | identical. A value of 0 indicates that they are complementary. | |
588 | ||
589 | =cut | |
590 | ||
591 | sub Correlation { | |
592 | my ($f,$g) = @_; | |
593 | my $manager = $f->{'manager'}; | |
594 | croak "Invalid reference" unless ref $manager; | |
595 | my $correl = Cudd::bddCorrelation($$manager,$f->{'node'},$g->{'node'}); | |
596 | return $correl; | |
597 | } | |
598 | ||
599 | =item Decreasing | |
600 | ||
601 | Returns 1 if the BDD $f is monotonic decreasing (negative unate) in | |
602 | variable $var; 0 otherwise. | |
603 | ||
604 | =cut | |
605 | ||
606 | sub Decreasing { | |
607 | my ($f,$var) = @_; | |
608 | my $manager = $f->{'manager'}; | |
609 | croak "Invalid reference" unless ref $manager; | |
610 | my $index = Cudd::NodeReadIndex($var->{'node'}); | |
611 | my $retval = Cudd::Decreasing($$manager,$f->{'node'},$index); | |
612 | return $retval; | |
613 | } | |
614 | ||
615 | =item Increasing | |
616 | ||
617 | Returns 1 if the BDD $f is monotonic increasing (positive unate) in | |
618 | variable $var; 0 otherwise. | |
619 | ||
620 | =cut | |
621 | ||
622 | sub Increasing { | |
623 | my ($f,$var) = @_; | |
624 | my $manager = $f->{'manager'}; | |
625 | croak "Invalid reference" unless ref $manager; | |
626 | my $index = Cudd::NodeReadIndex($var->{'node'}); | |
627 | my $retval = Cudd::Increasing($$manager,$f->{'node'},$index); | |
628 | return $retval; | |
629 | } | |
630 | ||
631 | =item Essentials | |
632 | ||
633 | Finds the essential variables of a BDD. Returns an array of all essentials. | |
634 | Negative essentials correspond to negative literals. | |
635 | ||
636 | =cut | |
637 | ||
638 | sub Essentials { | |
639 | my ($f) = @_; | |
640 | my $manager = $f->{'manager'}; | |
641 | croak "Invalid reference" unless ref $manager; | |
642 | my $essref = Cudd::FindEssential($$manager,$f->{'node'}); | |
643 | my @essentials = (); | |
644 | foreach (@$essref) { | |
645 | my $ess = $_; | |
646 | Cudd::Ref($ess); | |
647 | my $BDD = new Cudd::BDD($manager,$ess); | |
648 | push(@essentials,$BDD); | |
649 | } | |
650 | return \@essentials; | |
651 | } | |
652 | ||
653 | =item Support | |
654 | ||
655 | Returns a cube of all the variables in the support of $f. | |
656 | ||
657 | =cut | |
658 | ||
659 | sub Support { | |
660 | my ($f) = @_; | |
661 | my $manager = $f->{'manager'}; | |
662 | croak "Invalid reference" unless ref $manager; | |
663 | my $support = Cudd::Support($$manager,$f->{'node'}); | |
664 | Cudd::Ref($support); | |
665 | my $BDD = new Cudd::BDD($manager,$support); | |
666 | return $BDD; | |
667 | } | |
668 | ||
669 | =item LiteralSetIntersection | |
670 | ||
671 | Computes the intersection of two literal sets represented by cube BDDs. | |
672 | ||
673 | =cut | |
674 | ||
675 | sub LiteralSetIntersection { | |
676 | my ($f,$g) = @_; | |
677 | my $manager = $f->{'manager'}; | |
678 | croak "Invalid reference" unless ref $manager; | |
679 | my $node = Cudd::bddLiteralSetIntersection($$manager,$f->{'node'},$g->{'node'}); | |
680 | Cudd::Ref($node); | |
681 | my $BDD = new Cudd::BDD($manager,$node); | |
682 | return $BDD; | |
683 | } | |
684 | ||
685 | =item SwapVariables | |
686 | ||
687 | Swaps two sets of variables in a BDD. | |
688 | ||
689 | =cut | |
690 | ||
691 | sub SwapVariables { | |
692 | my ($f,$xref,$yref) = @_; | |
693 | my $manager = $f->{'manager'}; | |
694 | croak "Invalid reference" unless ref $manager; | |
695 | my @xvars = map $_->{'node'}, (@$xref); | |
696 | my @yvars = map $_->{'node'}, (@$yref); | |
697 | my $node = Cudd::bddSwapVariables($$manager,$f->{'node'},\@xvars,\@yvars); | |
698 | Cudd::Ref($node); | |
699 | my $BDD = new Cudd::BDD($manager,$node); | |
700 | return $BDD; | |
701 | } | |
702 | ||
703 | =item SolveEqn | |
704 | ||
705 | Solves a boolean equation. The first argument $f is the left-hand side | |
706 | of the equation $f = 0. The second argument is a reference to the | |
707 | array of the unknowns. The variables that are not unknowns are | |
708 | parameters. | |
709 | ||
710 | =cut | |
711 | ||
712 | sub SolveEqn { | |
713 | my ($f,$varref) = @_; | |
714 | my $manager = $f->{'manager'}; | |
715 | croak "Invalid reference" unless ref $manager; | |
716 | my $cube = $manager->BddOne; | |
717 | foreach (@$varref) { | |
718 | my $var = $_; | |
719 | $cube *= $var; | |
720 | } | |
721 | my $numvar = @$varref; | |
722 | my ($consist,$solref) = Cudd::SolveEqn($$manager,$f->{'node'}, | |
723 | $cube->{'node'},$numvar); | |
724 | Cudd::Ref($consist); | |
725 | my $BDD; | |
726 | $BDD = new Cudd::BDD($manager,$consist); | |
727 | my @solutions = (); | |
728 | foreach (@$solref) { | |
729 | my $sol = $_; | |
730 | my $BDD = new Cudd::BDD($manager,$sol); | |
731 | push(@solutions,$BDD); | |
732 | } | |
733 | return((\@solutions,$BDD)); | |
734 | } | |
735 | ||
736 | =item Signatures | |
737 | ||
738 | Computes the variable signatures in a BDD. The signature of a variable | |
739 | is the fraction of minterms in the on-set of the cofactor w.r.t. to | |
740 | that variable. The function returns an array of as many signatures as | |
741 | there are variables plus one. The last element of the array is the | |
742 | fraction of minterms in the ON-set of the BDD, and can be used to | |
743 | normalize the variable signatures. | |
744 | ||
745 | =cut | |
746 | ||
747 | sub Signatures { | |
748 | my $f = shift; | |
749 | my $manager = $f->{'manager'}; | |
750 | croak "Invalid reference" unless ref $manager; | |
751 | return Cudd::CofMinterm($$manager,$f->{'node'}); | |
752 | } | |
753 | ||
754 | =item NormSignatures | |
755 | ||
756 | Returns signatures normalized between 0 and 2. See also Signatures. | |
757 | ||
758 | =cut | |
759 | ||
760 | sub NormSignatures { | |
761 | my $f = shift; | |
762 | my $raw = Cudd::BDD::Signatures($f); | |
763 | my $size = @$raw; | |
764 | my $result = [map {$$raw[$_] / $$raw[$size-1]} (0..$size-2)]; | |
765 | } | |
766 | ||
767 | =item SubsetHB | |
768 | ||
769 | Computes a subset of a BDD with a maximum number of nodes. Uses the | |
770 | heavy branch approach. | |
771 | ||
772 | =cut | |
773 | ||
774 | sub SubsetHB { | |
775 | my ($f,$th) = @_; | |
776 | my $manager = $f->{'manager'}; | |
777 | croak "Invalid reference" unless ref $manager; | |
778 | my $numVars = Cudd::ReadSize($$manager); | |
779 | my $node = Cudd::SubsetHeavyBranch($$manager,$f->{'node'},$numVars,$th); | |
780 | Cudd::Ref($node); | |
781 | my $BDD = new Cudd::BDD($manager,$node); | |
782 | } | |
783 | ||
784 | =item SubsetSP | |
785 | ||
786 | Computes a subset of a BDD with a maximum number of nodes. Uses the | |
787 | short paths approach. | |
788 | ||
789 | =cut | |
790 | ||
791 | sub SubsetSP { | |
792 | my ($f,$th) = @_; | |
793 | my $manager = $f->{'manager'}; | |
794 | croak "Invalid reference" unless ref $manager; | |
795 | my $numVars = Cudd::ReadSize($$manager); | |
796 | my $node = Cudd::SubsetShortPaths($$manager,$f->{'node'},$numVars,$th,0); | |
797 | Cudd::Ref($node); | |
798 | my $BDD = new Cudd::BDD($manager,$node); | |
799 | } | |
800 | ||
801 | =item SupersetHB | |
802 | ||
803 | Computes a superset of a BDD with a maximum number of nodes. Uses the | |
804 | heavy branch approach. | |
805 | ||
806 | =cut | |
807 | ||
808 | sub SupersetHB { | |
809 | my ($f,$th) = @_; | |
810 | my $manager = $f->{'manager'}; | |
811 | croak "Invalid reference" unless ref $manager; | |
812 | my $numVars = Cudd::ReadSize($$manager); | |
813 | my $node = Cudd::SupersetHeavyBranch($$manager,$f->{'node'},$numVars,$th); | |
814 | Cudd::Ref($node); | |
815 | my $BDD = new Cudd::BDD($manager,$node); | |
816 | } | |
817 | ||
818 | =item SupersetSP | |
819 | ||
820 | Computes a superset of a BDD with a maximum number of nodes. Uses the | |
821 | short paths approach. | |
822 | ||
823 | =cut | |
824 | ||
825 | sub SupersetSP { | |
826 | my ($f,$th) = @_; | |
827 | my $manager = $f->{'manager'}; | |
828 | croak "Invalid reference" unless ref $manager; | |
829 | my $numVars = Cudd::ReadSize($$manager); | |
830 | my $node = Cudd::SupersetShortPaths($$manager,$f->{'node'},$numVars,$th,0); | |
831 | Cudd::Ref($node); | |
832 | my $BDD = new Cudd::BDD($manager,$node); | |
833 | } | |
834 | ||
835 | =item NewAdjVar | |
836 | ||
837 | Creates a new variable adjacent to the given variable. Useful in creating the next state variables of FSMs. | |
838 | ||
839 | =cut | |
840 | ||
841 | sub NewAdjVar { | |
842 | my $var = shift; | |
843 | my $manager = $var->{manager}; | |
844 | croak "Invalid reference" unless ref $manager; | |
845 | my $index = Cudd::NodeReadIndex($var->{node}); | |
846 | my $level = Cudd::ReadPerm($$manager,$index); | |
847 | my $node = Cudd::bddNewVarAtLevel($$manager,$level); | |
848 | Cudd::Ref($node); | |
849 | my $BDD = new Cudd::BDD($manager,$node); | |
850 | return $BDD; | |
851 | } | |
852 | ||
853 | =item GroupVars | |
854 | ||
855 | Creates a group of variables to be kept adjacent during reordering. | |
856 | ||
857 | =cut | |
858 | ||
859 | sub GroupVars { | |
860 | my $self = shift; | |
861 | my $size = shift || 2; | |
862 | my $method = shift || &Cudd::MTR_DEFAULT; | |
863 | my $manager = $self->{manager}; | |
864 | croak "Invalid reference" unless ref $manager; | |
865 | my $index = Cudd::NodeReadIndex($self->{node}); | |
866 | my $retval = Cudd::MakeTreeNode($$manager,$index,$size,$method); | |
867 | return $retval; | |
868 | } | |
869 | ||
870 | =item Not | |
871 | ||
872 | Returns the complement of a BDD. Overloads '!'. | |
873 | ||
874 | =cut | |
875 | ||
876 | sub Not { | |
877 | my ($f) = @_; | |
878 | my $manager = $f->{'manager'}; | |
879 | croak "Invalid reference" unless ref $manager; | |
880 | my $node = Cudd::Not($f->{'node'}); | |
881 | Cudd::Ref($node); | |
882 | my $BDD = new Cudd::BDD($manager,$node); | |
883 | return $BDD; | |
884 | } | |
885 | ||
886 | =item Equal | |
887 | ||
888 | Returns 1 if the two arguments are equivalent; 0 otherwise. Overloads '=='. | |
889 | ||
890 | =cut | |
891 | ||
892 | sub Equal { | |
893 | my ($f,$g) = @_; | |
894 | my $manager = $f->{'manager'}; | |
895 | croak "Invalid reference" unless ref $manager; | |
896 | return (${$f->{'node'}} == ${$g->{'node'}}) ? 1 : 0; | |
897 | } | |
898 | ||
899 | =item Unequal | |
900 | ||
901 | Returns 1 if the two arguments are not equivalent; 0 otherwise. Overloads '!='. | |
902 | ||
903 | =cut | |
904 | ||
905 | sub Unequal { | |
906 | my ($f,$g) = @_; | |
907 | my $manager = $f->{'manager'}; | |
908 | croak "Invalid reference" unless ref $manager; | |
909 | return (${$f->{'node'}} != ${$g->{'node'}}) ? 1 : 0; | |
910 | } | |
911 | ||
912 | =item Leq | |
913 | ||
914 | Compares two BDDs $f and $g and returns 1 if $f is less than or equal to $g. | |
915 | Overloads '<='. | |
916 | ||
917 | =cut | |
918 | ||
919 | sub Leq { | |
920 | my ($f,$g) = @_; | |
921 | my $manager = $f->{'manager'}; | |
922 | croak "Invalid reference" unless ref $manager; | |
923 | return Cudd::bddLeq($$manager, $f->{'node'}, $g->{'node'}); | |
924 | } | |
925 | ||
926 | =item Geq | |
927 | ||
928 | Compares two BDDs $f and $g and returns 1 if $f is greater than or equal to $g. | |
929 | Overloads '>='. | |
930 | ||
931 | =cut | |
932 | ||
933 | sub Geq { | |
934 | my ($f,$g) = @_; | |
935 | my $manager = $f->{'manager'}; | |
936 | croak "Invalid reference" unless ref $manager; | |
937 | return Cudd::bddLeq($$manager, $g->{'node'}, $f->{'node'}); | |
938 | } | |
939 | ||
940 | =item IsComplement | |
941 | ||
942 | Returns 1 if the BDD node is complemented, that is, if the BDD evaluates to 0 when all variables are assigned 1. | |
943 | ||
944 | =cut | |
945 | ||
946 | sub IsComplement { | |
947 | my $f = shift; | |
948 | return Cudd::IsComplement($f->{'node'}); | |
949 | } | |
950 | ||
951 | =item ShortestPath | |
952 | ||
953 | Computes a shortest path in a BDD. | |
954 | ||
955 | =cut | |
956 | ||
957 | sub ShortestPath { | |
958 | my ($f) = @_; | |
959 | my $manager = $f->{'manager'}; | |
960 | croak "Invalid reference" unless ref $manager; | |
961 | my $length = 0; | |
962 | my $node = Cudd::ShortestPath($$manager, $f->{'node'}, $length); | |
963 | Cudd::Ref($node); | |
964 | my $BDD = new Cudd::BDD($manager,$node); | |
965 | return $BDD; | |
966 | } | |
967 | ||
968 | =item Size | |
969 | ||
970 | Returns the number of nodes in the BDD or BDDs passed as arguments. | |
971 | ||
972 | =cut | |
973 | ||
974 | sub Size { | |
975 | my ($f) = @_; | |
976 | if (ref($f) eq "Cudd::BDD") { | |
977 | $f = [$f->{'node'}]; | |
978 | } elsif (ref($f) eq "ARRAY") { | |
979 | $f = [map $_->{'node'}, (@$f)]; | |
980 | } elsif (ref($f) eq "HASH") { | |
981 | $f = [map $_->{'node'}, values(%$f)]; | |
982 | } | |
983 | my $size = Cudd::SharingSize($f); | |
984 | return $size; | |
985 | } | |
986 | ||
987 | =item Minterms | |
988 | ||
989 | Returns the number of minterms of the BDD. The function of the BDD is | |
990 | supposed to depend on C<$n> variables. | |
991 | ||
992 | =cut | |
993 | ||
994 | sub Minterms { | |
995 | my ($f,$n) = @_; | |
996 | my $manager = $f->{'manager'}; | |
997 | croak "Invalid reference" unless ref $manager; | |
998 | my $retval = Cudd::CountMinterm($$manager, $f->{'node'}, $n); | |
999 | return $retval; | |
1000 | } | |
1001 | ||
1002 | =item Print | |
1003 | ||
1004 | Prints information about a BDD $f. The $pr parameter controls how much | |
1005 | information is printed. The $n parameter specifies how many inputs $f depends | |
1006 | on. This information is necessary to compute the number of minterms. | |
1007 | ||
1008 | =cut | |
1009 | ||
1010 | sub Print { | |
1011 | my ($f,$n,$pr) = @_; | |
1012 | my $manager = $f->{'manager'}; | |
1013 | croak "Invalid reference" unless ref $manager; | |
1014 | my $retval = Cudd::PrintDebug($$manager,$f->{'node'},$n,$pr); | |
1015 | return $retval; | |
1016 | } | |
1017 | ||
1018 | =item Dot | |
1019 | ||
1020 | Prints a description of a set of BDDs in dot format. The function has | |
1021 | one mandatory parameter, the list of BDDs for which the dot | |
1022 | description is desired, and two optional parameters, the array of | |
1023 | input names and the array of output names. All parameters are array | |
1024 | references. If the input names are omitted but the output names are | |
1025 | not, a reference to an empty array ([]) should be passed for the input | |
1026 | names. | |
1027 | ||
1028 | =cut | |
1029 | ||
1030 | sub Dot { | |
1031 | my $fref = shift; | |
1032 | my ($inameref,$onameref); | |
1033 | if (@_) { | |
1034 | $inameref = shift; | |
1035 | } else { | |
1036 | $inameref = []; | |
1037 | } | |
1038 | if (@_) { | |
1039 | $onameref = shift; | |
1040 | } else { | |
1041 | $onameref = []; | |
1042 | } | |
1043 | my $first = $$fref[0]; | |
1044 | my $manager = $first->{'manager'}; | |
1045 | croak "Invalid reference" unless ref $manager; | |
1046 | my @f = (); | |
1047 | foreach (@$fref) { | |
1048 | my $f = $_; | |
1049 | push(@f,$f->{'node'}); | |
1050 | } | |
1051 | my $retval = Cudd::DumpDot($$manager,\@f,$onameref,$inameref); | |
1052 | warn "Error while writing dot file" if ($retval != 1); | |
1053 | return $retval; | |
1054 | } | |
1055 | ||
1056 | =item Intersect | |
1057 | ||
1058 | Computes a function included in the intersection of $f and $g. (That | |
1059 | is, a witness that the intersection is not empty.) Intersect tries to | |
1060 | build as few new nodes as possible. | |
1061 | ||
1062 | =cut | |
1063 | ||
1064 | sub Intersect { | |
1065 | my ($f,$g) = @_; | |
1066 | my $manager = $f->{'manager'}; | |
1067 | croak "Invalid reference" unless ref $manager; | |
1068 | my $node = Cudd::bddIntersect($$manager,$f->{'node'},$g->{'node'}); | |
1069 | Cudd::Ref($node); | |
1070 | my $BDD = new Cudd::BDD($manager,$node); | |
1071 | return $BDD; | |
1072 | } | |
1073 | ||
1074 | =item ConstrainDecomp | |
1075 | ||
1076 | BDD conjunctive decomposition as in the CAV96 paper by McMillan. The | |
1077 | decomposition is canonical only for a given variable order. If | |
1078 | canonicity is required, variable ordering must be disabled after the | |
1079 | decomposition has been computed. Returns an array with one entry for | |
1080 | each BDD variable. | |
1081 | ||
1082 | =cut | |
1083 | ||
1084 | sub ConstrainDecomp { | |
1085 | my ($f) = @_; | |
1086 | my $manager = $f->{'manager'}; | |
1087 | croak "Invalid reference" unless ref $manager; | |
1088 | my $decref = Cudd::bddConstrainDecomp($$manager,$f->{'node'}); | |
1089 | my @decomp = (); | |
1090 | foreach (@$decref) { | |
1091 | my $dec = $_; | |
1092 | my $BDD = new Cudd::BDD($manager,$dec); | |
1093 | push(@decomp,$BDD); | |
1094 | } | |
1095 | return \@decomp; | |
1096 | } | |
1097 | ||
1098 | =item CharToVect | |
1099 | ||
1100 | Computes a vector of functions such that their image equals a non-zero | |
1101 | function. Returns an array with one entry for each BDD variable. | |
1102 | ||
1103 | =cut | |
1104 | ||
1105 | sub CharToVect { | |
1106 | my ($f) = @_; | |
1107 | my $manager = $f->{'manager'}; | |
1108 | croak "Invalid reference" unless ref $manager; | |
1109 | my $vecref = Cudd::bddCharToVect($$manager,$f->{'node'}); | |
1110 | my @vect = (); | |
1111 | foreach (@$vecref) { | |
1112 | my $vec = $_; | |
1113 | my $BDD = new Cudd::BDD($manager,$vec); | |
1114 | push(@vect,$BDD); | |
1115 | } | |
1116 | return \@vect; | |
1117 | } | |
1118 | ||
1119 | =item PrioritySelect | |
1120 | ||
1121 | Selects pairs from a relation C<R(x,y)> (given as a BDD) | |
1122 | in such a way that a given x appears in one pair only. Uses a | |
1123 | priority function to determine which y should be paired to a given x. | |
1124 | ||
1125 | =cut | |
1126 | ||
1127 | sub PrioritySelect { | |
1128 | my ($f,$Pi,$xref,$yref,$zref) = @_; | |
1129 | my $manager = $f->{'manager'}; | |
1130 | croak "Invalid reference" unless ref $manager; | |
1131 | my @xvars = map $_->{'node'}, (@$xref); | |
1132 | my @yvars = map $_->{'node'}, (@$yref); | |
1133 | my @zvars = map $_->{'node'}, (@$zref); | |
1134 | my $node = Cudd::PrioritySelect($$manager,$f->{'node'},$Pi->{'node'}, | |
1135 | \@xvars,\@yvars,\@zvars); | |
1136 | Cudd::Ref($node); | |
1137 | my $BDD = new Cudd::BDD($manager,$node); | |
1138 | return $BDD; | |
1139 | } | |
1140 | ||
1141 | =item PiDxygtdxz | |
1142 | ||
1143 | Returns the priority function that is 1 if the distance of x from y is greater than the distance of x from z. | |
1144 | ||
1145 | =cut | |
1146 | ||
1147 | sub PiDxygtdxz { | |
1148 | my ($xref,$yref,$zref) = @_; | |
1149 | my $first = $$xref[0]; | |
1150 | my $manager = $first->{'manager'}; | |
1151 | croak "Invalid reference" unless ref $manager; | |
1152 | my @xvars = map $_->{'node'}, (@$xref); | |
1153 | my @yvars = map $_->{'node'}, (@$yref); | |
1154 | my @zvars = map $_->{'node'}, (@$zref); | |
1155 | my $node = Cudd::Dxygtdxz($$manager,\@xvars,\@yvars,\@zvars); | |
1156 | Cudd::Ref($node); | |
1157 | my $BDD = new Cudd::BDD($manager,$node); | |
1158 | return $BDD; | |
1159 | } | |
1160 | ||
1161 | =item PiDxygtdyz | |
1162 | ||
1163 | Returns the priority function that is 1 if the distance of x from y is greater than the distance of y from z. | |
1164 | ||
1165 | =cut | |
1166 | ||
1167 | sub PiDxygtdyz { | |
1168 | my ($xref,$yref,$zref) = @_; | |
1169 | my $first = $$xref[0]; | |
1170 | my $manager = $first->{'manager'}; | |
1171 | croak "Invalid reference" unless ref $manager; | |
1172 | my @xvars = map $_->{'node'}, (@$xref); | |
1173 | my @yvars = map $_->{'node'}, (@$yref); | |
1174 | my @zvars = map $_->{'node'}, (@$zref); | |
1175 | my $node = Cudd::Dxygtdyz($$manager,\@xvars,\@yvars,\@zvars); | |
1176 | Cudd::Ref($node); | |
1177 | my $BDD = new Cudd::BDD($manager,$node); | |
1178 | return $BDD; | |
1179 | } | |
1180 | ||
1181 | =item PiXgty | |
1182 | ||
1183 | Returns the priority function that is 1 if x is greater than y. | |
1184 | ||
1185 | =cut | |
1186 | ||
1187 | sub PiXgty { | |
1188 | my ($zref,$xref,$yref) = @_; | |
1189 | my $first = $$xref[0]; | |
1190 | my $manager = $first->{'manager'}; | |
1191 | croak "Invalid reference" unless ref $manager; | |
1192 | my @zvars = map $_->{'node'}, (@$zref); | |
1193 | my @xvars = map $_->{'node'}, (@$xref); | |
1194 | my @yvars = map $_->{'node'}, (@$yref); | |
1195 | my $node = Cudd::Xgty($$manager,\@zvars,\@xvars,\@yvars); | |
1196 | Cudd::Ref($node); | |
1197 | my $BDD = new Cudd::BDD($manager,$node); | |
1198 | return $BDD; | |
1199 | } | |
1200 | ||
1201 | =item CProjection | |
1202 | ||
1203 | Applies the C-Projection to a relation R. | |
1204 | ||
1205 | =cut | |
1206 | ||
1207 | sub CProjection { | |
1208 | my ($R,$Y) = @_; | |
1209 | my $manager = $R->{'manager'}; | |
1210 | croak "Invalid reference" unless ref $manager; | |
1211 | my $node = Cudd::CProjection($$manager,$R->{'node'},$Y->{'node'}); | |
1212 | Cudd::Ref($node); | |
1213 | my $BDD = new Cudd::BDD($manager,$node); | |
1214 | return $BDD; | |
1215 | } | |
1216 | ||
1217 | =item Shuffle | |
1218 | ||
1219 | Imposes a given order. | |
1220 | ||
1221 | =cut | |
1222 | ||
1223 | sub Shuffle { | |
1224 | my $varref = shift; | |
1225 | my $first = $$varref[0]; | |
1226 | my $manager = $first->{'manager'}; | |
1227 | croak "Invalid reference" unless ref $manager; | |
1228 | my @permutation = (); | |
1229 | foreach (@$varref) { | |
1230 | my $var = $_; | |
1231 | my $index = Cudd::NodeReadIndex($var->{node}); | |
1232 | push(@permutation,$index); | |
1233 | } | |
1234 | return Cudd::ShuffleHeap($$manager,\@permutation); | |
1235 | } | |
1236 | ||
1237 | package Cudd; | |
1238 | ||
1239 | # Autoload methods go after =cut, and are processed by the autosplit program. | |
1240 | ||
1241 | 1; | |
1242 | __END__ | |
1243 | # Documentation. | |
1244 | ||
1245 | ||
1246 | =back 4 | |
1247 | ||
1248 | Package Cudd:BDD overloads common operators as follows: | |
1249 | ||
1250 | C<+ > -> Or | |
1251 | ||
1252 | C<* > -> And | |
1253 | ||
1254 | C<^ > -> Xor | |
1255 | ||
1256 | C<! > -> Not | |
1257 | ||
1258 | C</ > -> Cofactor | |
1259 | ||
1260 | C<==> -> Equal | |
1261 | ||
1262 | C<!=> -> Unequal | |
1263 | ||
1264 | C<E<lt>=> -> Leq | |
1265 | ||
1266 | C<E<gt>=> -> Geq | |
1267 | ||
1268 | =head1 EXAMPLE | |
1269 | ||
1270 | The following code fragment creates a manager, two variables C<$x> and | |
1271 | C<$y> in it, and then builds and prints the conjunction of the two | |
1272 | variables. | |
1273 | ||
1274 | my $manager = new Cudd; | |
1275 | my $x = $manager->BddVar; | |
1276 | my $y = $manager->BddVar; | |
1277 | my $z = $x * $y; | |
1278 | $z->Print(2,1); # (2 variables, verbosity 1) | |
1279 | ||
1280 | =head1 AUTHOR | |
1281 | ||
1282 | Fabio Somenzi, Fabio@Colorado.EDU | |
1283 | ||
1284 | =head1 SEE ALSO | |
1285 | ||
1286 | perl(1). | |
1287 | ||
1288 | =cut |