1 2=encoding utf8 3 4=head1 NAME 5 6Math::Symbolic::VectorCalculus - Symbolically comp. grad, Jacobi matrices etc. 7 8=head1 SYNOPSIS 9 10 use Math::Symbolic qw/:all/; 11 use Math::Symbolic::VectorCalculus; # not loaded by Math::Symbolic 12 13 @gradient = grad 'x+y*z'; 14 # or: 15 $function = parse_from_string('a*b^c'); 16 @gradient = grad $function; 17 # or: 18 @signature = qw(x y z); 19 @gradient = grad 'a*x+b*y+c*z', @signature; # Gradient only for x, y, z 20 # or: 21 @gradient = grad $function, @signature; 22 23 # Similar syntax variations as with the gradient: 24 $divergence = div @functions; 25 $divergence = div @functions, @signature; 26 27 # Again, similar DWIM syntax variations as with grad: 28 @rotation = rot @functions; 29 @rotation = rot @functions, @signature; 30 31 # Signatures always inferred from the functions here: 32 @matrix = Jacobi @functions; 33 # $matrix is now array of array references. These hold 34 # Math::Symbolic trees. Or: 35 @matrix = Jacobi @functions, @signature; 36 37 # Similar to Jacobi: 38 @matrix = Hesse $function; 39 # or: 40 @matrix = Hesse $function, @signature; 41 42 $wronsky_determinant = WronskyDet @functions, @vars; 43 # or: 44 $wronsky_determinant = WronskyDet @functions; # functions of 1 variable 45 46 $differential = TotalDifferential $function; 47 $differential = TotalDifferential $function, @signature; 48 $differential = TotalDifferential $function, @signature, @point; 49 50 $dir_deriv = DirectionalDerivative $function, @vector; 51 $dir_deriv = DirectionalDerivative $function, @vector, @signature; 52 53 $taylor = TaylorPolyTwoDim $function, $var1, $var2, $degree; 54 $taylor = TaylorPolyTwoDim $function, $var1, $var2, 55 $degree, $var1_0, $var2_0; 56 # example: 57 $taylor = TaylorPolyTwoDim 'sin(x)*cos(y)', 'x', 'y', 2; 58 59=head1 DESCRIPTION 60 61This module provides several subroutines related to 62vector calculus such as computing gradients, divergence, rotation, 63and Jacobi/Hesse Matrices of Math::Symbolic trees. 64Furthermore it provides means of computing directional derivatives 65and the total differential of a scalar function and the 66Wronsky Determinant of a set of n scalar functions. 67 68Please note that the code herein may or may not be refactored into 69the OO-interface of the Math::Symbolic module in the future. 70 71=head2 EXPORT 72 73None by default. 74 75You may choose to have any of the following routines exported to the 76calling namespace. ':all' tag exports all of the following: 77 78 grad 79 div 80 rot 81 Jacobi 82 Hesse 83 WronskyDet 84 TotalDifferential 85 DirectionalDerivative 86 TaylorPolyTwoDim 87 88=head1 SUBROUTINES 89 90=cut 91 92package Math::Symbolic::VectorCalculus; 93 94use 5.006; 95use strict; 96use warnings; 97 98use Carp; 99 100use Math::Symbolic qw/:all/; 101use Math::Symbolic::MiscAlgebra qw/det/; 102 103require Exporter; 104our @ISA = qw(Exporter); 105our %EXPORT_TAGS = ( 106 'all' => [ 107 qw( 108 grad 109 div 110 rot 111 Jacobi 112 Hesse 113 TotalDifferential 114 DirectionalDerivative 115 TaylorPolyTwoDim 116 WronskyDet 117 ) 118 ] 119); 120 121our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); 122 123our $VERSION = '0.612'; 124 125=begin comment 126 127_combined_signature returns the combined signature of unique variable names 128of all Math::Symbolic trees passed to it. 129 130=end comment 131 132=cut 133 134sub _combined_signature { 135 my %seen = map { ( $_, undef ) } map { ( $_->signature() ) } @_; 136 return [ sort keys %seen ]; 137} 138 139=head2 grad 140 141This subroutine computes the gradient of a Math::Symbolic tree representing 142a function. 143 144The gradient of a function f(x1, x2, ..., xn) is defined as the vector: 145 146 ( df(x1, x2, ..., xn) / d(x1), 147 df(x1, x2, ..., xn) / d(x2), 148 ..., 149 df(x1, x2, ..., xn) / d(xn) ) 150 151(These are all partial derivatives.) Any good book on calculus will have 152more details on this. 153 154grad uses prototypes to allow for a variety of usages. In its most basic form, 155it accepts only one argument which may either be a Math::Symbolic tree or a 156string both of which will be interpreted as the function to compute the 157gradient for. Optionally, you may specify a second argument which must 158be a (literal) array of Math::Symbolic::Variable objects or valid 159Math::Symbolic variable names (strings). These variables will the be used for 160the gradient instead of the x1, ..., xn inferred from the function signature. 161 162=cut 163 164sub grad ($;\@) { 165 my $original = shift; 166 $original = parse_from_string($original) 167 unless ref($original) =~ /^Math::Symbolic/; 168 my $signature = shift; 169 170 my @funcs; 171 my @signature = 172 ( defined $signature ? @$signature : $original->signature() ); 173 174 foreach (@signature) { 175 my $var = Math::Symbolic::Variable->new($_); 176 my $func = Math::Symbolic::Operator->new( 177 { 178 type => U_P_DERIVATIVE, 179 operands => [ $original->new(), $var ], 180 } 181 ); 182 push @funcs, $func; 183 } 184 return @funcs; 185} 186 187=head2 div 188 189This subroutine computes the divergence of a set of Math::Symbolic trees 190representing a vectorial function. 191 192The divergence of a vectorial function 193F = (f1(x1, ..., xn), ..., fn(x1, ..., xn)) is defined like follows: 194 195 sum_from_i=1_to_n( dfi(x1, ..., xn) / dxi ) 196 197That is, the sum of all partial derivatives of the i-th component function 198to the i-th coordinate. See your favourite book on calculus for details. 199Obviously, it is important to keep in mind that the number of function 200components must be equal to the number of variables/coordinates. 201 202Similar to grad, div uses prototypes to offer a comfortable interface. 203First argument must be a (literal) array of strings and Math::Symbolic trees 204which represent the vectorial function's components. If no second argument 205is passed, the variables used for computing the divergence will be 206inferred from the functions. That means the function signatures will be 207joined to form a signature for the vectorial function. 208 209If the optional second argument is specified, it has to be a (literal) 210array of Math::Symbolic::Variable objects and valid variable names (strings). 211These will then be interpreted as the list of variables for computing the 212divergence. 213 214=cut 215 216sub div (\@;\@) { 217 my @originals = 218 map { ( ref($_) =~ /^Math::Symbolic/ ) ? $_ : parse_from_string($_) } 219 @{ +shift }; 220 221 my $signature = shift; 222 $signature = _combined_signature(@originals) 223 if not defined $signature; 224 225 if ( @$signature != @originals ) { 226 die "Variable count does not function count for divergence."; 227 } 228 229 my @signature = map { Math::Symbolic::Variable->new($_) } @$signature; 230 231 my $div = Math::Symbolic::Operator->new( 232 { 233 type => U_P_DERIVATIVE, 234 operands => [ shift(@originals)->new(), shift @signature ], 235 } 236 ); 237 238 foreach (@originals) { 239 $div = Math::Symbolic::Operator->new( 240 '+', $div, 241 Math::Symbolic::Operator->new( 242 { 243 type => U_P_DERIVATIVE, 244 operands => [ $_->new(), shift @signature ], 245 } 246 ) 247 ); 248 } 249 return $div; 250} 251 252=head2 rot 253 254This subroutine computes the rotation of a set of three Math::Symbolic trees 255representing a vectorial function. 256 257The rotation of a vectorial function 258F = (f1(x1, x2, x3), f2(x1, x2, x3), f3(x1, x2, x3)) is defined as the 259following vector: 260 261 ( ( df3/dx2 - df2/dx3 ), 262 ( df1/dx3 - df3/dx1 ), 263 ( df2/dx1 - df1/dx2 ) ) 264 265Or "nabla x F" for short. Again, I have to refer to the literature for 266the details on what rotation is. Please note that there have to be 267exactly three function components and three coordinates because the cross 268product and hence rotation is only defined in three dimensions. 269 270As with the previously introduced subroutines div and grad, rot 271offers a prototyped interface. 272First argument must be a (literal) array of strings and Math::Symbolic trees 273which represent the vectorial function's components. If no second argument 274is passed, the variables used for computing the rotation will be 275inferred from the functions. That means the function signatures will be 276joined to form a signature for the vectorial function. 277 278If the optional second argument is specified, it has to be a (literal) 279array of Math::Symbolic::Variable objects and valid variable names (strings). 280These will then be interpreted as the list of variables for computing the 281rotation. (And please excuse my copying the last two paragraphs from above.) 282 283=cut 284 285sub rot (\@;\@) { 286 my $originals = shift; 287 my @originals = 288 map { ( ref($_) =~ /^Math::Symbolic/ ) ? $_ : parse_from_string($_) } 289 @$originals; 290 291 my $signature = shift; 292 $signature = _combined_signature(@originals) 293 unless defined $signature; 294 295 if ( @originals != 3 ) { 296 die "Rotation only defined for functions of three components."; 297 } 298 if ( @$signature != 3 ) { 299 die "Rotation only defined for three variables."; 300 } 301 302 return ( 303 Math::Symbolic::Operator->new( 304 '-', 305 Math::Symbolic::Operator->new( 306 { 307 type => U_P_DERIVATIVE, 308 operands => [ $originals[2]->new(), $signature->[1] ], 309 } 310 ), 311 Math::Symbolic::Operator->new( 312 { 313 type => U_P_DERIVATIVE, 314 operands => [ $originals[1]->new(), $signature->[2] ], 315 } 316 ) 317 ), 318 Math::Symbolic::Operator->new( 319 '-', 320 Math::Symbolic::Operator->new( 321 { 322 type => U_P_DERIVATIVE, 323 operands => [ $originals[0]->new(), $signature->[2] ], 324 } 325 ), 326 Math::Symbolic::Operator->new( 327 { 328 type => U_P_DERIVATIVE, 329 operands => [ $originals[2]->new(), $signature->[0] ], 330 } 331 ) 332 ), 333 Math::Symbolic::Operator->new( 334 '-', 335 Math::Symbolic::Operator->new( 336 { 337 type => U_P_DERIVATIVE, 338 operands => [ $originals[1]->new(), $signature->[0] ], 339 } 340 ), 341 Math::Symbolic::Operator->new( 342 { 343 type => U_P_DERIVATIVE, 344 operands => [ $originals[0]->new(), $signature->[1] ], 345 } 346 ) 347 ) 348 ); 349} 350 351=head2 Jacobi 352 353Jacobi() returns the Jacobi matrix of a given vectorial function. 354It expects any number of arguments (strings and/or Math::Symbolic trees) 355which will be interpreted as the vectorial function's components. 356Variables used for computing the matrix are, by default, inferred from the 357combined signature of the components. By specifying a second literal 358array of variable names as (second) argument, you may override this 359behaviour. 360 361The Jacobi matrix is the vector of gradient vectors of the vectorial 362function's components. 363 364=cut 365 366sub Jacobi (\@;\@) { 367 my @funcs = 368 map { ( ref($_) =~ /^Math::Symbolic/ ) ? $_ : parse_from_string($_) } 369 @{ +shift() }; 370 371 my $signature = shift; 372 my @signature = ( 373 defined $signature 374 ? ( 375 map { 376 ( ref($_) =~ /^Math::Symbolic/ ) 377 ? $_ 378 : parse_from_string($_) 379 } @$signature 380 ) 381 : ( @{ +_combined_signature(@funcs) } ) 382 ); 383 384 return map { [ grad $_, @signature ] } @funcs; 385} 386 387=head2 Hesse 388 389Hesse() returns the Hesse matrix of a given scalar function. First 390argument must be a string (to be parsed as a Math::Symbolic tree) 391or a Math::Symbolic tree. As with Jacobi(), Hesse() optionally 392accepts an array of signature variables as second argument. 393 394The Hesse matrix is the Jacobi matrix of the gradient of a scalar function. 395 396=cut 397 398sub Hesse ($;\@) { 399 my $function = shift; 400 $function = parse_from_string($function) 401 unless ref($function) =~ /^Math::Symbolic/; 402 my $signature = shift; 403 my @signature = ( 404 defined $signature 405 ? ( 406 map { 407 ( ref($_) =~ /^Math::Symbolic/ ) 408 ? $_ 409 : parse_from_string($_) 410 } @$signature 411 ) 412 : $function->signature() 413 ); 414 415 my @gradient = grad $function, @signature; 416 return Jacobi @gradient, @signature; 417} 418 419=head2 TotalDifferential 420 421This function computes the total differential of a scalar function of 422multiple variables in a certain point. 423 424First argument must be the function to derive. The second argument is 425an optional (literal) array of variable names (strings) and 426Math::Symbolic::Variable objects to be used for deriving. If the argument 427is not specified, the functions signature will be used. The third argument 428is also an optional array and denotes the set of variable (names) to use for 429indicating the point for which to evaluate the differential. It must have 430the same number of elements as the second argument. 431If not specified the variable names used as coordinated (the second argument) 432with an appended '_0' will be used as the point's components. 433 434=cut 435 436sub TotalDifferential ($;\@\@) { 437 my $function = shift; 438 $function = parse_from_string($function) 439 unless ref($function) =~ /^Math::Symbolic/; 440 441 my $sig = shift; 442 $sig = [ $function->signature() ] if not defined $sig; 443 my @sig = map { Math::Symbolic::Variable->new($_) } @$sig; 444 445 my $point = shift; 446 $point = [ map { $_->name() . '_0' } @sig ] if not defined $point; 447 my @point = map { Math::Symbolic::Variable->new($_) } @$point; 448 449 if ( @point != @sig ) { 450 croak "Signature dimension does not match point dimension."; 451 } 452 453 my @grad = grad $function, @sig; 454 if ( @grad != @sig ) { 455 croak "Signature dimension does not match function grad dim."; 456 } 457 458 foreach (@grad) { 459 my @point_copy = @point; 460 $_->implement( map { ( $_->name() => shift(@point_copy) ) } @sig ); 461 } 462 463 my $d = 464 Math::Symbolic::Operator->new( '*', shift(@grad), 465 Math::Symbolic::Operator->new( '-', shift(@sig), shift(@point) ) ); 466 467 $d += 468 Math::Symbolic::Operator->new( '*', shift(@grad), 469 Math::Symbolic::Operator->new( '-', shift(@sig), shift(@point) ) ) 470 while @grad; 471 472 return $d; 473} 474 475=head2 DirectionalDerivative 476 477DirectionalDerivative computes the directional derivative of a scalar function 478in the direction of a specified vector. With f being the function and X, A being 479vectors, it looks like this: (this is a partial derivative) 480 481 df(X)/dA = grad(f(X)) * (A / |A|) 482 483First argument must be the function to derive (either a string or a valid 484Math::Symbolic tree). Second argument must be vector into whose direction to 485derive. It is to be specified as an array of variable names and objects. 486Third argument is the optional signature to be used for computing the gradient. 487Please see the documentation of the grad function for details. It's 488dimension must match that of the directional vector. 489 490=cut 491 492sub DirectionalDerivative ($\@;\@) { 493 my $function = shift; 494 $function = parse_from_string($function) 495 unless ref($function) =~ /^Math::Symbolic/; 496 497 my $vec = shift; 498 my @vec = map { Math::Symbolic::Variable->new($_) } @$vec; 499 500 my $sig = shift; 501 $sig = [ $function->signature() ] if not defined $sig; 502 my @sig = map { Math::Symbolic::Variable->new($_) } @$sig; 503 504 if ( @vec != @sig ) { 505 croak "Signature dimension does not match vector dimension."; 506 } 507 508 my @grad = grad $function, @sig; 509 if ( @grad != @sig ) { 510 croak "Signature dimension does not match function grad dim."; 511 } 512 513 my $two = Math::Symbolic::Constant->new(2); 514 my @squares = 515 map { Math::Symbolic::Operator->new( '^', $_, $two ) } @vec; 516 517 my $abs_vec = shift @squares; 518 $abs_vec += shift(@squares) while @squares; 519 520 $abs_vec = 521 Math::Symbolic::Operator->new( '^', $abs_vec, 522 Math::Symbolic::Constant->new( 1 / 2 ) ); 523 524 @vec = map { $_ / $abs_vec } @vec; 525 526 my $dd = Math::Symbolic::Operator->new( '*', shift(@grad), shift(@vec) ); 527 528 $dd += Math::Symbolic::Operator->new( '*', shift(@grad), shift(@vec) ) 529 while @grad; 530 531 return $dd; 532} 533 534=begin comment 535 536This computes the taylor binomial 537 538 (d/dx*(x-x0)+d/dy*(y-y0))^n * f(x0, y0) 539 540=end comment 541 542=cut 543 544sub _taylor_binomial { 545 my $f = shift; 546 my $a = shift; 547 my $b = shift; 548 my $a0 = shift; 549 my $b0 = shift; 550 my $n = shift; 551 552 $f = $f->new(); 553 my $da = $a - $a0; 554 my $db = $b - $b0; 555 556 $f->implement( $a->name() => $a0, $b->name() => $b0 ); 557 558 return Math::Symbolic::Constant->one() if $n == 0; 559 return $da * 560 Math::Symbolic::Operator->new( 'partial_derivative', $f->new(), $a0 ) + 561 $db * 562 Math::Symbolic::Operator->new( 'partial_derivative', $f->new(), $b0 ) 563 if $n == 1; 564 565 my $n_obj = Math::Symbolic::Constant->new($n); 566 567 my $p_a_deriv = $f->new(); 568 $p_a_deriv = 569 Math::Symbolic::Operator->new( 'partial_derivative', $p_a_deriv, $a0 ) 570 for 1 .. $n; 571 572 my $res = 573 Math::Symbolic::Operator->new( '*', $p_a_deriv, 574 Math::Symbolic::Operator->new( '^', $da, $n_obj ) ); 575 576 foreach my $k ( 1 .. $n - 1 ) { 577 $p_a_deriv = $p_a_deriv->op1()->new(); 578 579 my $deriv = $p_a_deriv; 580 $deriv = 581 Math::Symbolic::Operator->new( 'partial_derivative', $deriv, $b0 ) 582 for 1 .. $k; 583 584 my $k_obj = Math::Symbolic::Constant->new($k); 585 $res += Math::Symbolic::Operator->new( 586 '*', 587 Math::Symbolic::Constant->new( _over( $n, $k ) ), 588 Math::Symbolic::Operator->new( 589 '*', $deriv, 590 Math::Symbolic::Operator->new( 591 '*', 592 Math::Symbolic::Operator->new( 593 '^', $da, Math::Symbolic::Constant->new( $n - $k ) 594 ), 595 Math::Symbolic::Operator->new( '^', $db, $k_obj ) 596 ) 597 ) 598 ); 599 } 600 601 my $p_b_deriv = $f->new(); 602 $p_b_deriv = 603 Math::Symbolic::Operator->new( 'partial_derivative', $p_b_deriv, $b0 ) 604 for 1 .. $n; 605 606 $res += 607 Math::Symbolic::Operator->new( '*', $p_b_deriv, 608 Math::Symbolic::Operator->new( '^', $db, $n_obj ) ); 609 610 return $res; 611} 612 613=begin comment 614 615This computes 616 617 / n \ 618 | | 619 \ k / 620 621=end comment 622 623=cut 624 625sub _over { 626 my $n = shift; 627 my $k = shift; 628 629 return 1 if $k == 0; 630 return _over( $n, $n - $k ) if $k > $n / 2; 631 632 my $prod = 1; 633 my $i = $n; 634 my $j = $k; 635 while ( $i > $k ) { 636 $prod *= $i; 637 $prod /= $j if $j > 1; 638 $i--; 639 $j--; 640 } 641 642 return ($prod); 643} 644 645=begin comment 646 647_faculty() computes the product that is the faculty of the 648first argument. 649 650=end comment 651 652=cut 653 654sub _faculty { 655 my $num = shift; 656 croak "Cannot calculate faculty of negative numbers." 657 if $num < 0; 658 my $fac = Math::Symbolic::Constant->one(); 659 return $fac if $num <= 1; 660 for ( my $i = 2 ; $i <= $num ; $i++ ) { 661 $fac *= Math::Symbolic::Constant->new($i); 662 } 663 return $fac; 664} 665 666=head2 TaylorPolyTwoDim 667 668This subroutine computes the Taylor Polynomial for functions of two 669variables. Please refer to the documentation of the TaylorPolynomial 670function in the Math::Symbolic::MiscCalculus package for an explanation 671of single dimensional Taylor Polynomials. This is the counterpart in 672two dimensions. 673 674First argument must be the function to approximate with the Taylor Polynomial 675either as a string or a Math::Symbolic tree. Second and third argument 676must be the names of the two coordinates. (These may alternatively be 677Math::Symbolic::Variable objects.) Fourth argument must be 678the degree of the Taylor Polynomial. Fifth and Sixth arguments are optional 679and specify the names of the variables to introduce as the point of 680approximation. These default to the names of the coordinates with '_0' 681appended. 682 683=cut 684 685sub TaylorPolyTwoDim ($$$$;$$) { 686 my $function = shift; 687 $function = parse_from_string($function) 688 unless ref($function) =~ /^Math::Symbolic/; 689 690 my $x1 = shift; 691 $x1 = Math::Symbolic::Variable->new($x1) 692 unless ref($x1) eq 'Math::Symbolic::Variable'; 693 my $x2 = shift; 694 $x2 = Math::Symbolic::Variable->new($x2) 695 unless ref($x2) eq 'Math::Symbolic::Variable'; 696 697 my $n = shift; 698 699 my $x1_0 = shift; 700 $x1_0 = $x1->name() . '_0' if not defined $x1_0; 701 $x1_0 = Math::Symbolic::Variable->new($x1_0) 702 unless ref($x1_0) eq 'Math::Symbolic::Variable'; 703 704 my $x2_0 = shift; 705 $x2_0 = $x2->name() . '_0' if not defined $x2_0; 706 $x2_0 = Math::Symbolic::Variable->new($x2_0) 707 unless ref($x2_0) eq 'Math::Symbolic::Variable'; 708 709 my $x1_n = $x1->name(); 710 my $x2_n = $x2->name(); 711 712 my $dx1 = $x1 - $x1_0; 713 my $dx2 = $x2 - $x2_0; 714 715 my $copy = $function->new(); 716 $copy->implement( $x1_n => $x1_0, $x2_n => $x2_0 ); 717 718 my $taylor = $copy; 719 720 return $taylor if $n == 0; 721 722 foreach my $k ( 1 .. $n ) { 723 $taylor += 724 Math::Symbolic::Operator->new( '/', 725 _taylor_binomial( $function->new(), $x1, $x2, $x1_0, $x2_0, $k ), 726 _faculty($k) ); 727 } 728 729 return $taylor; 730} 731 732=head2 WronskyDet 733 734WronskyDet() computes the Wronsky Determinant of a set of n functions. 735 736First argument is required and a (literal) array of n functions. Second 737argument is optional and a (literal) array of n variables or variable names. 738If the second argument is omitted, the variables used for deriving are inferred 739from function signatures. This requires, however, that the function signatures 740have exactly one element. (And the function this exactly one variable.) 741 742=cut 743 744sub WronskyDet (\@;\@) { 745 my $functions = shift; 746 my @functions = 747 map { ( ref($_) =~ /^Math::Symbolic/ ) ? $_ : parse_from_string($_) } 748 @$functions; 749 my $vars = shift; 750 my @vars = ( defined $vars ? @$vars : () ); 751 @vars = map { 752 my @sig = $_->signature(); 753 croak "Cannot infer function signature for WronskyDet." 754 if @sig != 1; 755 shift @sig; 756 } @functions if not defined $vars; 757 @vars = map { Math::Symbolic::Variable->new($_) } @vars; 758 croak "Number of vars doesn't match num of functions in WronskyDet." 759 if not @vars == @functions; 760 761 my @matrix; 762 push @matrix, [@functions]; 763 foreach ( 2 .. @functions ) { 764 my $i = 0; 765 @functions = map { 766 Math::Symbolic::Operator->new( 'partial_derivative', $_, 767 $vars[ $i++ ] ) 768 } @functions; 769 push @matrix, [@functions]; 770 } 771 return det @matrix; 772} 773 7741; 775__END__ 776 777=head1 AUTHOR 778 779Please send feedback, bug reports, and support requests to the Math::Symbolic 780support mailing list: 781math-symbolic-support at lists dot sourceforge dot net. Please 782consider letting us know how you use Math::Symbolic. Thank you. 783 784If you're interested in helping with the development or extending the 785module's functionality, please contact the developers' mailing list: 786math-symbolic-develop at lists dot sourceforge dot net. 787 788List of contributors: 789 790 Steffen M�ller, symbolic-module at steffen-mueller dot net 791 Stray Toaster, mwk at users dot sourceforge dot net 792 Oliver Ebenh�h 793 794=head1 SEE ALSO 795 796New versions of this module can be found on 797http://steffen-mueller.net or CPAN. The module development takes place on 798Sourceforge at http://sourceforge.net/projects/math-symbolic/ 799 800L<Math::Symbolic> 801 802=cut 803 804