File Coverage

blib/lib/Math/Symbolic/Custom/Transformation.pm
Criterion Covered Total %
statement 112 136 82.3
branch 32 54 59.2
condition 8 21 38.1
subroutine 15 17 88.2
pod 5 5 100.0
total 172 233 73.8


line stmt bran cond sub pod time code
1             package Math::Symbolic::Custom::Transformation;
2              
3 1     1   193409 use 5.006;
  1         7  
  1         48  
4 1     1   6 use strict;
  1         2  
  1         38  
5 1     1   6 use warnings;
  1         7  
  1         40  
6              
7 1     1   6 use Carp qw/croak carp/;
  1         1  
  1         87  
8 1     1   6 use Math::Symbolic qw/:all/;
  1         2  
  1         260  
9 1     1   1067 use Math::Symbolic::Custom::Pattern;
  1         9857  
  1         887  
10             require Math::Symbolic::Custom::Transformation::Group;
11              
12             require Exporter;
13              
14             our @ISA = qw(Exporter);
15              
16             our $VERSION = '2.02';
17              
18             =encoding utf8
19              
20             =head1 NAME
21              
22             Math::Symbolic::Custom::Transformation - Transform Math::Symbolic trees
23              
24             =head1 SYNOPSIS
25              
26             use Math::Symbolic::Custom::Transformation;
27             my $trafo = Math::Symbolic::Custom::Transformation->new(
28             'TREE_x + TREE_x' => '2 * TREE_x'
29             );
30            
31             my $modified = $trafo->apply($math_symbolic_tree);
32             if (defined $modified) {
33             print "Outermost operator is a sum of two identical trees.\n";
34             print "Transformed it into a product. ($modified)\n";
35             }
36             else {
37             print "Transformation could not be applied.\n";
38             }
39            
40             # shortcut: new_trafo
41             use Math::Symbolic::Custom::Transformation qw/new_trafo/;
42              
43             # use the value() function to have the transformation compute the value
44             # of the expression after the replacements. simplify{} works similar.
45             my $another_trafo = new_trafo(
46             'TREE_foo / CONST_bar' => 'value{1/CONST_bar} * TREE_foo'
47             );
48            
49             # If you'll need the same transformation but don't want to keep it around in
50             # an object, just do this:
51             use Memoize;
52             memoize('new_trafo');
53             # Then, passing the same transformation strings will result in a speedup of
54             # about a factor 130 (on my machine) as compared to complete recreation
55             # from strings. This is only 20% slower than using an existing
56             # transformation.
57              
58             =head1 DESCRIPTION
59              
60             Math::Symbolic::Custom::Transformation is an extension to the Math::Symbolic
61             module. You're assumed to be remotely familiar with that module throughout
62             the documentation.
63              
64             This package implements transformations of Math::Symbolic trees using
65             Math::Symbolic trees. I'll try to explain what this means in the following
66             paragraphs.
67              
68             Until now, in order to be able to inspect a Math::Symbolic tree, one had to
69             use the low-level Math::Symbolic interface like comparing the top node's
70             term type with a constant (such as C) and then its operator type
71             with more constants. This has changed with the release of
72             Math::Symbolic::Custom::Pattern.
73              
74             To modify the tree, you had to use equally low-level or even
75             encapsulation-breaking methods. This is meant to be changed by this
76             distribution.
77              
78             =head2 EXAMPLE
79              
80             Say you want to change any tree that is a sum of two identical
81             trees into two times one such tree. Let's assume the original object is in
82             the variable C<$tree>. The old way was: (strictures and warnings assumed)
83              
84             use Math::Symbolic qw/:all/;
85            
86             sub sum_to_product {
87             if ( $tree->term_type() == T_OPERATOR
88             and $tree->type() == B_SUM
89             and $tree->op1()->is_identical($tree->op2()) )
90             {
91             $tree = Math::Symbolic::Operator->new(
92             '*', Math::Symbolic::Constant->new(2), $tree->op1()->new()
93             );
94             }
95             return $tree;
96             }
97              
98             What you'd do with this package is significantly more readable:
99              
100             use Math::Symbolic::Custom::Transformation qw/new_trafo/;
101            
102             my $Sum_To_Product_Rule = new_trafo('TREE_a + TREE_a' => '2 * TREE_a');
103            
104             sub sum_to_product {
105             my $tree = shift;
106             return( $Sum_To_Product_Rule->apply($tree) || $tree );
107             }
108              
109             Either version could be shortened, of course. The significant improvement,
110             however, isn't shown by this example. If you're doing introspection beyond
111             the outermost operator, you will end up with giant, hardly readable
112             if-else blocks when using the old style transformations. With this package,
113             however, such introspection scales well:
114              
115             use Math::Symbolic::Custom::Transformation qw/new_trafo/;
116            
117             my $Sum_Of_Const_Products_Rule = new_trafo(
118             'CONST_a * TREE_b + CONST_c * TREE_b'
119             => 'value{CONST_a + CONST_c} * TREE_b'
120             );
121            
122             sub sum_to_product {
123             my $tree = shift;
124             return( $Sum_Of_Const_Products_Rule->apply($tree) || $tree );
125             }
126              
127             For details on the C construct in the transformation string, see
128             the L section.
129              
130             =head2 EXPORT
131              
132             None by default, but you may choose to import the C subroutine
133             as an alternative constructor for Math::Symbolic::Custom::Transformation
134             objects.
135              
136             =head2 PERFORMANCE
137              
138             The performance of transformations isn't astonishing by itself, but if you
139             take into account that they leave the original tree intact, we end up with
140             a speed hit of only 16% as compared to the literal code. (That's the
141             huge if-else block I was talking about.)
142              
143             You may be tempted to recreate the transformation objects from strings
144             whenever you need them. There's one thing to say about that: Don't!
145             The construction of transformations is really slow because they have
146             been optimised for performance on application, not creation.
147             (Application should be around 40 times faster than creation from strings!)
148              
149             I Starting with version 2.00, this module also supports the new-ish
150             Math::Symbolic::Parser::Yapp parser implementation which is significantly
151             faster than the old Parse::RecDescent based implementation. Replacement
152             strings are parsed using Yapp by default now, which means a performance
153             increase of about 20%. The search patterns are still parsed using the default
154             Math::Symbolic parser which will be switched to Yapp at some point in the
155             future. If you force the use of the Yapp parser globally, the parser
156             performance will improve by about an order of magnitude! You can do so by
157             adding the following before using Math::Symbolic::Custom::Transformation:
158              
159             use Math::Symbolic;
160             BEGIN {
161             $Math::Symbolic::Parser = Math::Symbolic::Parser->new(
162             implementation => 'Yapp'
163             );
164             }
165             use Math::Symbolic::Custom::Transformation;
166             #...
167              
168             If you absolutely must include the source strings where the transformation
169             is used, consider using the L module which is part of the standard
170             Perl distribution these days.
171              
172             use Memoize;
173             use Math::Symbolic::Custom::Transformation qw/new_trafo/;
174             memoize('new_trafo');
175              
176             sub apply_some_trafo {
177             my $source = shift;
178             my $trafo = new_trafo(...some pattern... => ...some transformation...);
179             return $trafo->apply($source);
180             }
181              
182             This usage has the advantage of putting the transformation source strings
183             right where they make the most sense in terms of readability. The
184             memoized subroutine C only constructs the transformation the first
185             time it is called and returns the cached object every time thereafter.
186              
187             =head2 SYNTAX EXTENSIONS
188              
189             The strings from which you can create transformations are basically those that
190             can be parsed as Math::Symbolic trees. The first argument to the transformation
191             constructor will, in fact, be parsed as a Math::Symbolic::Custom::Pattern
192             object. The second, however, may include some extensions to the default
193             Math::Symbolic syntax. These extensions are the two functions C
194             and C. The curly braces serve the purpose to show the
195             distinction from algebraic parenthesis. When finding a C
196             directive, the module will calculate the value of C when the
197             transformation is applied. (That is, after the C, C and
198             C placeholders have been inserted!) The result is then inserted
199             into the transformed tree.
200              
201             Similarily, the C directive will use the Math::Symbolic
202             simplification routines on C when the transformation is being applied
203             (and again, after replacing the placeholders with the matched sub-trees.
204              
205             =cut
206              
207             our %EXPORT_TAGS = ( 'all' => [ qw(
208             new_trafo new_trafo_group
209             ) ] );
210              
211             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
212              
213             our @EXPORT = qw();
214              
215             our $Predicates = [
216             qw/simplify value/
217             ];
218              
219             # We have some class data. Namely, the parser for the transformation strings
220             # which aren't quite ordinary Math::Symbolic strings.
221             our $Parser;
222             {
223             my $pred = join '|', @$Predicates;
224             $Parser = Math::Symbolic::Parser->new(
225             implementation => 'Yapp',
226             yapp_predicates => qr/$pred/o,
227             );
228             }
229              
230             if ($Parser->isa('Parse::RecDescent')) {
231             # This is left in for reference.
232             my $pred = join '|', @$Predicates;
233             $Parser->Extend(<<"HERE");
234             function: /(?:$pred)\{/ expr '}'
235             {
236             my \$function_name = \$item[1];
237             \$function_name =~ s/\{\$//;
238              
239             my \$inner = \$item[2];
240              
241             my \$name = 'TRANSFORMATION_HOOK';
242              
243             # Since we need to evaluate both 'simplify' and 'value'
244             # at the time we apply the transformation, we just replace
245             # the function occurrance with a special variable that is
246             # recognized later. The function name and argument is stored
247             # in an array as the value of the special variable.
248             Math::Symbolic::Variable->new(
249             \$name, [\$function_name, \$inner]
250             );
251             }
252             HERE
253             }
254             elsif ($Parser->isa('Math::Symbolic::Parser::Yapp')) {
255             # This is a no-op since the logic had to be built into
256             # the Yapp parser. *sigh*
257             }
258             else {
259             die "Unsupported Math::Symbolic::Parser implementation.";
260             }
261              
262             =head2 METHODS
263              
264             This is a list of public methods.
265              
266             =over 2
267              
268             =cut
269              
270             =item new
271              
272             This is the constructor for Math::Symbolic::Custom::Transformation objects.
273             It takes two arguments: A pattern to look for and a replacement.
274              
275             The pattern may either be a Math::Symbolic::Custom::Pattern object (fastest),
276             or a Math::Symbolic tree which will internally be transformed into a pattern
277             or even just a string which will be parsed as a pattern.
278              
279             The replacement for the pattern may either be a Math::Symbolic tree or a
280             string to be parsed as such.
281              
282             =cut
283              
284             sub new {
285 114     114 1 61970 my $proto = shift;
286 114   33     656 my $class = ref($proto)||$proto;
287              
288 114         207 my $pattern = shift;
289 114         225 my $replacement = shift;
290              
291             # parameter checking
292 114 50 33     556 if (not defined $pattern or not defined $replacement) {
293 0         0 croak("Arguments to ".__PACKAGE__."->new() must be a valid pattern and a replacement for matched patterns.");
294             }
295              
296 114 50       287 if (not ref($pattern)) {
297 114         215 my $copy = $pattern;
298 114         484 $pattern = parse_from_string($pattern);
299 114 100       1447846 if (not ref($pattern)) {
300 2         425 croak("Failed to parse pattern '$copy' as a Math::Symbolic tree.");
301             }
302             }
303              
304 112 50       1730 if (not $pattern->isa('Math::Symbolic::Custom::Pattern')) {
305 112         198 eval {$pattern = Math::Symbolic::Custom::Pattern->new($pattern);};
  112         748  
306 112 50 33     30590 if ( $@ or not ref($pattern)
      33        
307             or not $pattern->isa('Math::Symbolic::Custom::Pattern') )
308             {
309 0 0       0 croak(
310             "Could not transform pattern source into a pattern object."
311             . ($@?" Error: $@":"")
312             );
313             }
314             }
315              
316 112 50       413 if (not ref($replacement) =~ /^Math::Symbolic/) {
317 112         205 my $copy = $replacement;
318 112         988 $replacement = $Parser->parse($replacement);
319 112 50       56535 if (not ref($replacement) =~ /^Math::Symbolic/) {
320 0         0 croak(
321             "Failed to parse replacement '$copy' as a Math::Symbolic tree."
322             );
323             }
324             }
325              
326 112         439 my $self = {
327             pattern => $pattern,
328             replacement => $replacement,
329             };
330              
331 112         447 bless $self => $class;
332              
333 112         479 return $self;
334             }
335              
336              
337             =item apply
338              
339             Applies the transformation to a Math::Symbolic tree. First argument must be
340             a Math::Symbolic tree to transform. The tree is not transformed in-place,
341             but its matched subtrees are contained in the transformed tree, so if you plan
342             to use the original tree as well as the transformed tree, take
343             care to clone one of the trees.
344              
345             C returns the transformed tree if the transformation pattern matched
346             and a false value otherwise.
347              
348             On errors, it throws a fatal error.
349              
350             =cut
351              
352             sub apply {
353 138     138 1 2161590 my $self = shift;
354 138         340 my $tree = shift;
355              
356 138 50       960 if (not ref($tree) =~ /^Math::Symbolic/) {
357 0         0 croak("First argument to apply() must be a Math::Symbolic tree.");
358             }
359              
360 138         374 my $pattern = $self->{pattern};
361 138         321 my $repl = $self->{replacement};
362              
363 138         813 my $matched = $pattern->match($tree);
364              
365 138 100       22222 return undef if not $matched;
366              
367 72         164 my $match_vars = $matched->{vars};
368 72         161 my $match_trees = $matched->{trees};
369 72         203 my $match_consts = $matched->{constants};
370              
371 72         310 my $new = $repl->new();
372              
373 1     1   11 no warnings 'recursion';
  1         3  
  1         1303  
374            
375 72         3388 my $subroutine;
376             my @descend_options;
377              
378             $subroutine = sub {
379 185     185   4435 my $tree = shift;
380 185 100       515 if ($tree->term_type() == T_VARIABLE) {
381 105         545 my $name = $tree->{name};
382 105 100       674 if ($name eq 'TRANSFORMATION_HOOK') {
    100          
383              
384 14         56 my $hook = $tree->value();
385 14 50 33     157 if (not ref($hook) eq 'ARRAY' and @$hook == 2) {
386 0         0 croak("Found invalid transformation hook in replacement tree. Did you use a variable named 'TRANSFORMATION_HOOK'? If so, please change its name since that name is used internally.");
387             }
388             else {
389 14         29 my $type = $hook->[0];
390 14         51 my $operand = $hook->[1]->new();
391 14         948 $operand->descend(
392             @descend_options
393             );
394              
395 14 100       421 if ($type eq 'simplify') {
    50          
396 13         57 my $simplified = $operand->simplify();
397 13         6632 $tree->replace($simplified);
398 13         209 return undef;
399             }
400             elsif ($type eq 'value') {
401 1         5 my $value = $operand->value();
402 1 50       206 if (not defined $value) {
403 0         0 croak("Tried to evaluate transformation subroutine value() but it evaluated to an undefined value.");
404             }
405 1         4 $value = Math::Symbolic::Constant->new($value);
406 1         19 $tree->replace($value);
407 1         15 return undef;
408             }
409             else {
410 0         0 die("Invalid TRANSFORMATION_HOOK type '$type'.");
411             }
412             }
413             }
414             elsif ($name =~ /^(VAR|CONST|TREE)_(\w+)/) {
415 90         206 my $type = $1;
416 90         194 my $name = $2;
417 90 50       253 if ($type eq 'VAR') {
    100          
418 0 0       0 if (exists $match_vars->{$name}) {
419 0         0 $tree->replace(
420             Math::Symbolic::Variable->new(
421             $match_vars->{$name}
422             )
423             );
424             }
425             }
426             elsif ($type eq 'TREE') {
427 86 50       234 if (exists $match_trees->{$name}) {
428 86         313 $tree->replace($match_trees->{$name});
429             }
430             }
431             else {
432 4 50       15 if (exists $match_consts->{$name}) {
433 4         22 $tree->replace(
434             Math::Symbolic::Constant->new(
435             $match_consts->{$name}
436             )
437             );
438             }
439             }
440            
441 90         1301 return undef;
442             }
443 1         4 return();
444             }
445             else {
446 80         469 return();
447             }
448 72         684 };
449             @descend_options = (
450             in_place => 1,
451             operand_finder => sub {
452 52 50   52   1313 if ($_[0]->term_type == T_OPERATOR) {
453 52         247 return @{$_[0]->{operands}};
  52         243  
454             }
455             else {
456 0         0 return();
457             }
458             },
459 72         502 before => $subroutine,
460             );
461 72         487 $new->descend(@descend_options);
462 72         2113 return $new;
463             }
464              
465             =item apply_recursive
466              
467             "Recursively" applies the transformation. The Math::Symbolic tree
468             passed in as argument B.
469              
470             Hold on: This does not mean
471             that the transformation is applied again and again, but that the
472             Math::Symbolic tree you are applying to is descended into and while walking
473             back up the tree, the transformation is tried for every node.
474              
475             Basically, it's applied bottom-up. Top-down would not usually make much sense.
476             If the application to any sub-tree throws a fatal error, this error is silently
477             caught and the application to other sub-trees is continued.
478              
479             Usage is the same as with the "shallow" C method.
480              
481             =cut
482              
483             sub apply_recursive {
484 0     0 1 0 my $self = shift;
485 0         0 my $tree = shift;
486              
487 0         0 my $matched = 0;
488             $tree->descend(
489             after => sub {
490 0     0   0 my $node = shift;
491 0         0 my $res;
492 0         0 eval { $res = $self->apply($node); };
  0         0  
493 0 0 0     0 if (defined $res and not $@) {
494 0         0 $matched = 1;
495 0         0 $node->replace($res);
496             }
497 0         0 return();
498             },
499 0         0 in_place => 1
500             );
501              
502 0 0       0 return $tree if $matched;
503 0         0 return();
504             }
505              
506             =item to_string
507              
508             Returns a string representation of the transformation.
509             In presence of the C or C hooks, this may
510             fail to return the correct represenation. It does not round-trip!
511              
512             (Generally, it should work if only one hook is present, but fails if
513             more than one hook is found.)
514              
515             =cut
516              
517             sub to_string {
518 56     56 1 94268 my $self = shift;
519 56         366 my $pattern_str = $self->{pattern}->to_string();
520 56         348 my $repl = $self->{replacement};
521              
522 56         195 my $repl_str = _repl_to_string($repl);
523            
524 56         267 return $pattern_str . ' -> ' . $repl_str;
525             }
526              
527             sub _repl_to_string {
528 67     67   122 my $repl = shift;
529 67         290 my $repl_str = $repl->to_string();
530 67 100       2265 if ($repl_str =~ /TRANSFORMATION_HOOK/) {
531 11         21 my @hooks;
532             $repl->descend(
533             before => sub {
534 33     33   1261 my $node = shift;
535 33 100 100     196 if (
536             ref($node) =~ /^Math::Symbolic::Variable$/
537             and $node->name() eq 'TRANSFORMATION_HOOK'
538             )
539             {
540 11         98 push @hooks, $node;
541             }
542 33         174 return();
543             },
544 11         121 in_place => 1, # won't change anything
545             );
546              
547 11         386 $repl_str =~ s{TRANSFORMATION_HOOK}!
548 11         24 my $node = shift @hooks;
549 11         48 my $value = $node->value();
550 11         97 my $operand = _repl_to_string($value->[1]);
551 11         25 my $name = $value->[0];
552 11         60 "$name\{ $operand }"
553             !ge;
554             }
555              
556 67         182 return $repl_str;
557             }
558              
559             =back
560              
561             =head2 SUBROUTINES
562              
563             This is a list of public subroutines.
564              
565             =over 2
566              
567             =cut
568              
569             =item new_trafo
570              
571             This subroutine is an alternative to the C constructor for
572             Math::Symbolic::Custom::Transformation objects that uses a hard coded
573             package name. (So if you want to subclass this module, you should be aware
574             of that!)
575              
576             =cut
577              
578             =item new_trafo_group
579              
580             This subroutine is the equivalent of C, but for creation
581             of new transformation groups. See L.
582              
583             =cut
584              
585             *new_trafo_group = *Math::Symbolic::Custom::Transformation::Group::new_trafo_group;
586              
587             sub new_trafo {
588 57     57 1 564 unshift @_, __PACKAGE__;
589 57         289 goto &new;
590             }
591              
592             1;
593             __END__