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