1package Math::Symbolic::Custom::Transformation;
2
3use 5.006;
4use strict;
5use warnings;
6
7use Carp qw/croak carp/;
8use Math::Symbolic qw/:all/;
9use Math::Symbolic::Custom::Pattern;
10require Math::Symbolic::Custom::Transformation::Group;
11
12require Exporter;
13
14our @ISA = qw(Exporter);
15
16our $VERSION = '2.02';
17
18=encoding utf8
19
20=head1 NAME
21
22Math::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
60Math::Symbolic::Custom::Transformation is an extension to the Math::Symbolic
61module. You're assumed to be remotely familiar with that module throughout
62the documentation.
63
64This package implements transformations of Math::Symbolic trees using
65Math::Symbolic trees. I'll try to explain what this means in the following
66paragraphs.
67
68Until now, in order to be able to inspect a Math::Symbolic tree, one had to
69use the low-level Math::Symbolic interface like comparing the top node's
70term type with a constant (such as C<T_OPERATOR>) and then its operator type
71with more constants. This has changed with the release of
72Math::Symbolic::Custom::Pattern.
73
74To modify the tree, you had to use equally low-level or even
75encapsulation-breaking methods. This is meant to be changed by this
76distribution.
77
78=head2 EXAMPLE
79
80Say you want to change any tree that is a sum of two identical
81trees into two times one such tree. Let's assume the original object is in
82the 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
98What 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
109Either version could be shortened, of course. The significant improvement,
110however, isn't shown by this example. If you're doing introspection beyond
111the outermost operator, you will end up with giant, hardly readable
112if-else blocks when using the old style transformations. With this package,
113however, 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
127For details on the C<value{}> construct in the transformation string, see
128the L<SYNTAX EXTENSIONS> section.
129
130=head2 EXPORT
131
132None by default, but you may choose to import the C<new_trafo> subroutine
133as an alternative constructor for Math::Symbolic::Custom::Transformation
134objects.
135
136=head2 PERFORMANCE
137
138The performance of transformations isn't astonishing by itself, but if you
139take into account that they leave the original tree intact, we end up with
140a speed hit of only 16% as compared to the literal code. (That's the
141huge if-else block I was talking about.)
142
143You may be tempted to recreate the transformation objects from strings
144whenever you need them. There's one thing to say about that: Don't!
145The construction of transformations is really slow because they have
146been optimised for performance on application, not creation.
147(Application should be around 40 times faster than creation from strings!)
148
149I<Note:> Starting with version 2.00, this module also supports the new-ish
150Math::Symbolic::Parser::Yapp parser implementation which is significantly
151faster than the old Parse::RecDescent based implementation. Replacement
152strings are parsed using Yapp by default now, which means a performance
153increase of about 20%. The search patterns are still parsed using the default
154Math::Symbolic parser which will be switched to Yapp at some point in the
155future. If you force the use of the Yapp parser globally, the parser
156performance will improve by about an order of magnitude! You can do so by
157adding 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
168If you absolutely must include the source strings where the transformation
169is used, consider using the L<Memoize> module which is part of the standard
170Perl 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
182This usage has the advantage of putting the transformation source strings
183right where they make the most sense in terms of readability. The
184memoized subroutine C<new_trafo> only constructs the transformation the first
185time it is called and returns the cached object every time thereafter.
186
187=head2 SYNTAX EXTENSIONS
188
189The strings from which you can create transformations are basically those that
190can be parsed as Math::Symbolic trees. The first argument to the transformation
191constructor will, in fact, be parsed as a Math::Symbolic::Custom::Pattern
192object. The second, however, may include some extensions to the default
193Math::Symbolic syntax. These extensions are the two functions C<value{...}>
194and C<simplify{...}>. The curly braces serve the purpose to show the
195distinction from algebraic parenthesis. When finding a C<value{EXPR}>
196directive, the module will calculate the value of C<EXPR> when the
197transformation is applied. (That is, after the C<TREE_foo>, C<CONST_bar> and
198C<VAR_baz> placeholders have been inserted!) The result is then inserted
199into the transformed tree.
200
201Similarily, the C<simplify{EXPR}> directive will use the Math::Symbolic
202simplification routines on C<EXPR> when the transformation is being applied
203(and again, after replacing the placeholders with the matched sub-trees.
204
205=cut
206
207our %EXPORT_TAGS = ( 'all' => [ qw(
208    new_trafo new_trafo_group
209) ] );
210
211our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
212
213our @EXPORT = qw();
214
215our $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.
221our $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
230if ($Parser->isa('Parse::RecDescent')) {
231    # This is left in for reference.
232    my $pred = join '|', @$Predicates;
233    $Parser->Extend(<<"HERE");
234function: /(?:$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    }
252HERE
253}
254elsif ($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}
258else {
259    die "Unsupported Math::Symbolic::Parser implementation.";
260}
261
262=head2 METHODS
263
264This is a list of public methods.
265
266=over 2
267
268=cut
269
270=item new
271
272This is the constructor for Math::Symbolic::Custom::Transformation objects.
273It takes two arguments: A pattern to look for and a replacement.
274
275The pattern may either be a Math::Symbolic::Custom::Pattern object (fastest),
276or a Math::Symbolic tree which will internally be transformed into a pattern
277or even just a string which will be parsed as a pattern.
278
279The replacement for the pattern may either be a Math::Symbolic tree or a
280string to be parsed as such.
281
282=cut
283
284sub new {
285    my $proto = shift;
286    my $class = ref($proto)||$proto;
287
288    my $pattern = shift;
289    my $replacement = shift;
290
291    # parameter checking
292    if (not defined $pattern or not defined $replacement) {
293        croak("Arguments to ".__PACKAGE__."->new() must be a valid pattern and a replacement for matched patterns.");
294    }
295
296    if (not ref($pattern)) {
297        my $copy = $pattern;
298        $pattern = parse_from_string($pattern);
299        if (not ref($pattern)) {
300            croak("Failed to parse pattern '$copy' as a Math::Symbolic tree.");
301        }
302    }
303
304    if (not $pattern->isa('Math::Symbolic::Custom::Pattern')) {
305        eval {$pattern = Math::Symbolic::Custom::Pattern->new($pattern);};
306        if ( $@ or not ref($pattern)
307             or not $pattern->isa('Math::Symbolic::Custom::Pattern')    )
308        {
309            croak(
310                "Could not transform pattern source into a pattern object."
311                . ($@?" Error: $@":"")
312            );
313        }
314    }
315
316    if (not ref($replacement) =~ /^Math::Symbolic/) {
317        my $copy = $replacement;
318        $replacement = $Parser->parse($replacement);
319        if (not ref($replacement) =~ /^Math::Symbolic/) {
320            croak(
321                "Failed to parse replacement '$copy' as a Math::Symbolic tree."
322            );
323        }
324    }
325
326    my $self = {
327        pattern => $pattern,
328        replacement => $replacement,
329    };
330
331    bless $self => $class;
332
333    return $self;
334}
335
336
337=item apply
338
339Applies the transformation to a Math::Symbolic tree. First argument must be
340a Math::Symbolic tree to transform. The tree is not transformed in-place,
341but its matched subtrees are contained in the transformed tree, so if you plan
342to use the original tree as well as the transformed tree, take
343care to clone one of the trees.
344
345C<apply()> returns the transformed tree if the transformation pattern matched
346and a false value otherwise.
347
348On errors, it throws a fatal error.
349
350=cut
351
352sub apply {
353    my $self = shift;
354    my $tree = shift;
355
356    if (not ref($tree) =~ /^Math::Symbolic/) {
357        croak("First argument to apply() must be a Math::Symbolic tree.");
358    }
359
360    my $pattern = $self->{pattern};
361    my $repl = $self->{replacement};
362
363    my $matched = $pattern->match($tree);
364
365    return undef if not $matched;
366
367    my $match_vars = $matched->{vars};
368    my $match_trees = $matched->{trees};
369    my $match_consts = $matched->{constants};
370
371    my $new = $repl->new();
372
373    no warnings 'recursion';
374
375    my $subroutine;
376    my @descend_options;
377
378    $subroutine = sub {
379        my $tree = shift;
380        if ($tree->term_type() == T_VARIABLE) {
381            my $name = $tree->{name};
382            if ($name eq 'TRANSFORMATION_HOOK') {
383
384        my $hook = $tree->value();
385                if (not ref($hook) eq 'ARRAY' and @$hook == 2) {
386                    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                    my $type = $hook->[0];
390                    my $operand = $hook->[1]->new();
391                    $operand->descend(
392                        @descend_options
393                    );
394
395                    if ($type eq 'simplify') {
396                        my $simplified = $operand->simplify();
397                        $tree->replace($simplified);
398                        return undef;
399                    }
400                    elsif ($type eq 'value') {
401                        my $value = $operand->value();
402                        if (not defined $value) {
403                            croak("Tried to evaluate transformation subroutine value() but it evaluated to an undefined value.");
404                        }
405                        $value = Math::Symbolic::Constant->new($value);
406                        $tree->replace($value);
407                        return undef;
408                    }
409                    else {
410                        die("Invalid TRANSFORMATION_HOOK type '$type'.");
411                    }
412                }
413            }
414            elsif ($name =~ /^(VAR|CONST|TREE)_(\w+)/) {
415                my $type = $1;
416                my $name = $2;
417                if ($type eq 'VAR') {
418                    if (exists $match_vars->{$name}) {
419                        $tree->replace(
420                            Math::Symbolic::Variable->new(
421                                $match_vars->{$name}
422                            )
423                        );
424                    }
425                }
426                elsif ($type eq 'TREE') {
427                    if (exists $match_trees->{$name}) {
428                        $tree->replace($match_trees->{$name});
429                    }
430                }
431                else {
432                    if (exists $match_consts->{$name}) {
433                        $tree->replace(
434                            Math::Symbolic::Constant->new(
435                                $match_consts->{$name}
436                            )
437                        );
438                    }
439                }
440
441                return undef;
442            }
443            return();
444        }
445        else {
446            return();
447        }
448    };
449    @descend_options = (
450        in_place => 1,
451        operand_finder => sub {
452            if ($_[0]->term_type == T_OPERATOR) {
453                return @{$_[0]->{operands}};
454            }
455            else {
456                return();
457            }
458        },
459        before => $subroutine,
460    );
461    $new->descend(@descend_options);
462    return $new;
463}
464
465=item apply_recursive
466
467"Recursively" applies the transformation. The Math::Symbolic tree
468passed in as argument B<will be modified in-place>.
469
470Hold on: This does not mean
471that the transformation is applied again and again, but that the
472Math::Symbolic tree you are applying to is descended into and while walking
473back up the tree, the transformation is tried for every node.
474
475Basically, it's applied bottom-up. Top-down would not usually make much sense.
476If the application to any sub-tree throws a fatal error, this error is silently
477caught and the application to other sub-trees is continued.
478
479Usage is the same as with the "shallow" C<apply()> method.
480
481=cut
482
483sub apply_recursive {
484    my $self = shift;
485    my $tree = shift;
486
487    my $matched = 0;
488    $tree->descend(
489        after => sub {
490            my $node = shift;
491            my $res;
492            eval { $res = $self->apply($node); };
493            if (defined $res and not $@) {
494                $matched = 1;
495                $node->replace($res);
496            }
497            return();
498        },
499        in_place => 1
500    );
501
502    return $tree if $matched;
503    return();
504}
505
506=item to_string
507
508Returns a string representation of the transformation.
509In presence of the C<simplify> or C<value> hooks, this may
510fail 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
513more than one hook is found.)
514
515=cut
516
517sub to_string {
518    my $self = shift;
519    my $pattern_str = $self->{pattern}->to_string();
520    my $repl = $self->{replacement};
521
522    my $repl_str = _repl_to_string($repl);
523
524    return $pattern_str . ' -> ' . $repl_str;
525}
526
527sub _repl_to_string {
528    my $repl = shift;
529    my $repl_str = $repl->to_string();
530    if ($repl_str =~ /TRANSFORMATION_HOOK/) {
531        my @hooks;
532        $repl->descend(
533            before => sub {
534                my $node = shift;
535                if (
536                    ref($node) =~ /^Math::Symbolic::Variable$/
537                    and $node->name() eq 'TRANSFORMATION_HOOK'
538                   )
539                {
540                   push @hooks, $node;
541                }
542                return();
543            },
544            in_place => 1, # won't change anything
545        );
546
547        $repl_str =~ s{TRANSFORMATION_HOOK}!
548            my $node = shift @hooks;
549            my $value = $node->value();
550            my $operand = _repl_to_string($value->[1]);
551            my $name = $value->[0];
552            "$name\{ $operand }"
553        !ge;
554    }
555
556    return $repl_str;
557}
558
559=back
560
561=head2 SUBROUTINES
562
563This is a list of public subroutines.
564
565=over 2
566
567=cut
568
569=item new_trafo
570
571This subroutine is an alternative to the C<new()> constructor for
572Math::Symbolic::Custom::Transformation objects that uses a hard coded
573package name. (So if you want to subclass this module, you should be aware
574of that!)
575
576=cut
577
578=item new_trafo_group
579
580This subroutine is the equivalent of C<new_trafo>, but for creation
581of new transformation groups. See L<Math::Symbolic::Custom::Transformation::Group>.
582
583=cut
584
585*new_trafo_group = *Math::Symbolic::Custom::Transformation::Group::new_trafo_group;
586
587sub new_trafo {
588    unshift @_, __PACKAGE__;
589    goto &new;
590}
591
5921;
593__END__
594
595=back
596
597=head1 SEE ALSO
598
599New versions of this module can be found on http://steffen-mueller.net or CPAN.
600
601This module uses the L<Math::Symbolic> framework for symbolic computations.
602
603L<Math::Symbolic::Custom::Pattern> implements the pattern matching routines.
604
605=head1 AUTHOR
606
607Steffen Müller, E<lt>smueller@cpan.orgE<gt>
608
609=head1 COPYRIGHT AND LICENSE
610
611Copyright (C) 2005, 2006, 2007, 2008, 2009, 2013 by Steffen Mueller
612
613This library is free software; you can redistribute it and/or modify
614it under the same terms as Perl itself, either Perl version 5.6.1 or,
615at your option, any later version of Perl 5 you may have available.
616
617=cut
618