Math::Symbolic::Custom::Transformation - Transform Math::Symbolic trees
use Math::Symbolic::Custom::Transformation; my $trafo = Math::Symbolic::Custom::Transformation->new( 'TREE_x + TREE_x' => '2 * TREE_x' ); my $modified = $trafo->apply($math_symbolic_tree); if (defined $modified) { print "Outermost operator is a sum of two identical trees.\n"; print "Transformed it into a product. ($modified)\n"; } else { print "Transformation could not be applied.\n"; } # shortcut: new_trafo use Math::Symbolic::Custom::Transformation qw/new_trafo/; # use the value() function to have the transformation compute the value # of the expression after the replacements. simplify{} works similar. my $another_trafo = new_trafo( 'TREE_foo / CONST_bar' => 'value{1/CONST_bar} * TREE_foo' ); # If you'll need the same transformation but don't want to keep it around in # an object, just do this: use Memoize; memoize('new_trafo'); # Then, passing the same transformation strings will result in a speedup of # about a factor 130 (on my machine) as compared to complete recreation # from strings. This is only 20% slower than using an existing # transformation.
Math::Symbolic::Custom::Transformation is an extension to the Math::Symbolic module. You're assumed to be remotely familiar with that module throughout the documentation.
This package implements transformations of Math::Symbolic trees using Math::Symbolic trees. I'll try to explain what this means in the following paragraphs.
Until now, in order to be able to inspect a Math::Symbolic tree, one had to use the low-level Math::Symbolic interface like comparing the top node's term type with a constant (such as T_OPERATOR
) and then its operator type with more constants. This has changed with the release of Math::Symbolic::Custom::Pattern.
To modify the tree, you had to use equally low-level or even encapsulation-breaking methods. This is meant to be changed by this distribution.
Say you want to change any tree that is a sum of two identical trees into two times one such tree. Let's assume the original object is in the variable $tree
. The old way was: (strictures and warnings assumed)
use Math::Symbolic qw/:all/; sub sum_to_product { if ( $tree->term_type() == T_OPERATOR and $tree->type() == B_SUM and $tree->op1()->is_identical($tree->op2()) ) { $tree = Math::Symbolic::Operator->new( '*', Math::Symbolic::Constant->new(2), $tree->op1()->new() ); } return $tree; }
What you'd do with this package is significantly more readable:
use Math::Symbolic::Custom::Transformation qw/new_trafo/; my $Sum_To_Product_Rule = new_trafo('TREE_a + TREE_a' => '2 * TREE_a'); sub sum_to_product { my $tree = shift; return( $Sum_To_Product_Rule->apply($tree) || $tree ); }
Either version could be shortened, of course. The significant improvement, however, isn't shown by this example. If you're doing introspection beyond the outermost operator, you will end up with giant, hardly readable if-else blocks when using the old style transformations. With this package, however, such introspection scales well:
use Math::Symbolic::Custom::Transformation qw/new_trafo/; my $Sum_Of_Const_Products_Rule = new_trafo( 'CONST_a * TREE_b + CONST_c * TREE_b' => 'value{CONST_a + CONST_c} * TREE_b' ); sub sum_to_product { my $tree = shift; return( $Sum_Of_Const_Products_Rule->apply($tree) || $tree ); }
For details on the value{}
construct in the transformation string, see the "SYNTAX EXTENSIONS" section.
None by default, but you may choose to import the new_trafo
subroutine as an alternative constructor for Math::Symbolic::Custom::Transformation objects.
The performance of transformations isn't astonishing by itself, but if you take into account that they leave the original tree intact, we end up with a speed hit of only 16% as compared to the literal code. (That's the huge if-else block I was talking about.)
You may be tempted to recreate the transformation objects from strings whenever you need them. There's one thing to say about that: Don't! The construction of transformations is really slow because they have been optimised for performance on application, not creation. (Application should be around 40 times faster than creation from strings!)
Note: Starting with version 2.00, this module also supports the new-ish Math::Symbolic::Parser::Yapp parser implementation which is significantly faster than the old Parse::RecDescent based implementation. Replacement strings are parsed using Yapp by default now, which means a performance increase of about 20%. The search patterns are still parsed using the default Math::Symbolic parser which will be switched to Yapp at some point in the future. If you force the use of the Yapp parser globally, the parser performance will improve by about an order of magnitude! You can do so by adding the following before using Math::Symbolic::Custom::Transformation:
use Math::Symbolic; BEGIN { $Math::Symbolic::Parser = Math::Symbolic::Parser->new( implementation => 'Yapp' ); } use Math::Symbolic::Custom::Transformation; #...
If you absolutely must include the source strings where the transformation is used, consider using the Memoize module which is part of the standard Perl distribution these days.
use Memoize; use Math::Symbolic::Custom::Transformation qw/new_trafo/; memoize('new_trafo'); sub apply_some_trafo { my $source = shift; my $trafo = new_trafo(...some pattern... => ...some transformation...); return $trafo->apply($source); }
This usage has the advantage of putting the transformation source strings right where they make the most sense in terms of readability. The memoized subroutine new_trafo
only constructs the transformation the first time it is called and returns the cached object every time thereafter.
The strings from which you can create transformations are basically those that can be parsed as Math::Symbolic trees. The first argument to the transformation constructor will, in fact, be parsed as a Math::Symbolic::Custom::Pattern object. The second, however, may include some extensions to the default Math::Symbolic syntax. These extensions are the two functions value{...}
and simplify{...}
. The curly braces serve the purpose to show the distinction from algebraic parenthesis. When finding a value{EXPR}
directive, the module will calculate the value of EXPR
when the transformation is applied. (That is, after the TREE_foo
, CONST_bar
and VAR_baz
placeholders have been inserted!) The result is then inserted into the transformed tree.
Similarily, the simplify{EXPR}
directive will use the Math::Symbolic simplification routines on EXPR
when the transformation is being applied (and again, after replacing the placeholders with the matched sub-trees.
This is a list of public methods.
This is the constructor for Math::Symbolic::Custom::Transformation objects. It takes two arguments: A pattern to look for and a replacement.
The pattern may either be a Math::Symbolic::Custom::Pattern object (fastest), or a Math::Symbolic tree which will internally be transformed into a pattern or even just a string which will be parsed as a pattern.
The replacement for the pattern may either be a Math::Symbolic tree or a string to be parsed as such.
Applies the transformation to a Math::Symbolic tree. First argument must be a Math::Symbolic tree to transform. The tree is not transformed in-place, but its matched subtrees are contained in the transformed tree, so if you plan to use the original tree as well as the transformed tree, take care to clone one of the trees.
apply()
returns the transformed tree if the transformation pattern matched and a false value otherwise.
On errors, it throws a fatal error.
"Recursively" applies the transformation. The Math::Symbolic tree passed in as argument will be modified in-place.
Hold on: This does not mean that the transformation is applied again and again, but that the Math::Symbolic tree you are applying to is descended into and while walking back up the tree, the transformation is tried for every node.
Basically, it's applied bottom-up. Top-down would not usually make much sense. If the application to any sub-tree throws a fatal error, this error is silently caught and the application to other sub-trees is continued.
Usage is the same as with the "shallow" apply()
method.
Returns a string representation of the transformation. In presence of the simplify
or value
hooks, this may fail to return the correct represenation. It does not round-trip!
(Generally, it should work if only one hook is present, but fails if more than one hook is found.)
This is a list of public subroutines.
This subroutine is an alternative to the new()
constructor for Math::Symbolic::Custom::Transformation objects that uses a hard coded package name. (So if you want to subclass this module, you should be aware of that!)
This subroutine is the equivalent of new_trafo
, but for creation of new transformation groups. See Math::Symbolic::Custom::Transformation::Group.
New versions of this module can be found on http://steffen-mueller.net or CPAN.
This module uses the Math::Symbolic framework for symbolic computations.
Math::Symbolic::Custom::Pattern implements the pattern matching routines.
Steffen Müller, <smueller@cpan.org>
Copyright (C) 2005, 2006, 2007, 2008, 2009, 2013 by Steffen Mueller
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.6.1 or, at your option, any later version of Perl 5 you may have available.