Math::Algebra::Symbols - Symbolic Algebra using Perl
use Math::Algebra::Symbols hyper=>1;
my ($n, $x, $y) = symbols(qw(n x y));
my $a += ($x**8 - 1)/($x-1); my $b += sin($x)**2 + cos($x)**2; my $c += (sin($n*$x) + cos($n*$x))->d->d->d->d / (sin($n*$x)+cos($n*$x)); my $d = tanh($x+$y) == (tanh($x)+tanh($y))/(1+tanh($x)*tanh($y)); my ($e,$f) = @{($x**2 eq 5*$x-6) > $x};
print "$a\n$b\n$c\n$d\n$e,$f\n";
# $x+$x**2+$x**3+$x**4+$x**5+$x**6+$x**7+1 # 1 # $n**4 # 1 # 2,3
This package supplies a set of functions and operators to manipulate operator expressions algebraically using the familiar Perl syntax.
These expressions are constructed from Symbols, Operators, and Functions, and processed via Methods. For examples, see: Examples.
Symbols are created with the exported symbols() constructor routine:
use Math::Algebra::Symbols;
my ($x, $y, $i, $o, $pi) = symbols(qw(x y i 1 pi));
print "$x $y $i $o\n";
# $x $y $i 1
The symbols() routine constructs references to symbolic variables and symbolic constants from a list of names and integer constants.
The special symbol i is recognized as the square root of -1.
The special symbol pi is recognized as the smallest positive real that satisfies:
use Math::Algebra::Symbols;
($i, $pi) = symbols(qw(i pi));
print exp($i*$pi), "\n";
# -1
If you wish to use a different name for the constructor routine, say S:
use Math::Algebra::Symbols symbols=>'S';
my ($x, $y, $i, $o) = S(qw(x y i 1));
print "$x $y $i $o\n";
# $x $y $i 1
If you wish to use Math::Algebra::Symbols constructed with big integers from the Math::BigInt manpage:
use Math::Algebra::Symbols BigInt=>1;
my $z = symbols('1234567890987654321/1234567890987654321');
print "$z\n";
# 1
Symbols can be combined with Operators to create symbolic expressions:
use Math::Algebra::Symbols;
($x, $y) = symbols(qw(x y));
$z = ($x**2-$y**2)/($x-$y);
print "$z\n";
# $x+$y
The auto assign versions of these operators: += -= *= /= all work courtesy of Perl Auto-Magical Operator Generation.
use Math::Algebra::Symbols;
$x = symbols(qw(x));
$z = sqrt(-$x**2);
print "$z\n";
# $i*$x
use Math::Algebra::Symbols;
$x = symbols(qw(x));
$z = exp($x)->d($x);
print "$z\n";
# exp($x)
use Math::Algebra::Symbols;
$x = symbols(qw(x));
$z = log(exp($x)*exp($x));
print "$z\n";
# 2*$x
use Math::Algebra::Symbols;
$x = symbols(qw(x));
$z = sin($x)**2 + cos($x)**2;
print "$z\n";
# 1
use Math::Algebra::Symbols;
($x, $y) = symbols(qw(x y));
$z = ($x**2-$y**2)/($x+$y) == $x - $y;
print "$z\n";
# 1
The relational equality operator == compares two symbolic expressions
and returns TRUE(1)
or FALSE(0)
accordingly. != produces the opposite
result.
use Math::Algebra::Symbols;
($x, $v, $t) = symbols(qw(x v t));
$z = ($v eq $x / $t)->solve(qw(x in terms of v t));
print "x=$z\n";
# x=$v*$t
The relational operator eq is a synonym for the minus - operator, with the expectation that later on the solve() function will be used to simplify and rearrange the equation. You may prefer to use eq instead of - to enhance readability, there is no functional difference.
use Math::Algebra::Symbols;
($a, $b, $i) = symbols(qw(a b i));
$z = ($a+$i*$b)^($a-$i*$b);
print "$z\n";
# $a**2-$b**2
Note the use of brackets. The ^ operator has low priority.
The ^ operator treats its left hand and right hand arguments as complex numbers, which in turn are regarded as two dimensional vectors to which the vector dot product is applied.
use Math::Algebra::Symbols;
($x, $i) = symbols(qw(x i));
$z = $i*$x x $x;
print "$z\n";
# $x**2
The x operator treats its left hand and right hand arguments as complex numbers, which in turn are regarded as two dimensional vectors defining the sides of a parallelogram. The x operator returns the area of this parallelogram.
Note the space before the x, otherwise Perl is unable to disambiguate the expression correctly.
use Math::Algebra::Symbols;
($x, $y, $i) = symbols(qw(x y i));
$z = $x+$i*$y;
print ~$z, "\n";
# $x-$i*$y
The ~ operator returns the complex conjugate of its right hand side.
use Math::Algebra::Symbols;
($x, $i) = symbols(qw(x i));
$z = abs($x+$i*$x);
print "$z\n";
# sqrt(2)*$x
The abs operator returns the modulus (length) of its right hand side.
use Math::Algebra::Symbols;
$i = symbols(qw(i));
$z = !($i+1);
print "$z\n";
# $i*sqrt(1/2)+sqrt(1/2)
The ! operator returns a complex number of unit length pointing in the same direction as its right hand side.
use Math::Algebra::Symbols; ($x) = symbols(qw(x)); $z += ($x**8 - 1)/($x-1);
print "$z\n";
# $x+$x**2+$x**3+$x**4+$x**5+$x**6+$x**7+1
The simplify operator += is a synonym for the simplify() method, if and only if, the target on the left hand side initially has a value of undef. Admittedly this is very strange behavior: it arises due to the shortage of over-rideable operators in Perl: in particular it arises due to the shortage of over-rideable unary operators in Perl. Never-the-less: this operator is useful as can be seen in the Synopsis, and the desired pre-condition can always achieved by using my.
use Math::Algebra::Symbols;
($x, $v, $t) = symbols(qw(x v t));
$z = ($v eq $x / $t) > [qw(x in terms of v t)];
print "x=$z\n";
# x=$v*$t
The solve operator > is a synonym for the solve() method.
The priority of > is higher than that of eq, so the brackets around the equation to be solved are necessary until Perl provides a mechanism for adjusting operator priority (cf. Algol 68).
If the equation is in a single variable, the single variable may be named after the > operator without the use of [...]:
use Math::Algebra::Symbols;
my $rabbit = 10 + 5 * $t; my $fox = 7 * $t * $t; my ($a, $b) = @{($rabbit eq $fox) > $t};
print "$a\n";
# 1/14*sqrt(305)+5/14
If there are multiple solutions, (as in the case of polynomials), > returns an array of symbolic expressions containing the solutions.
This example was provided by Mike Schilli m@perlmeister.com.
Perl operator overloading is very useful for producing compact representations of algebraic expressions. Unfortunately there are only a small number of operators that Perl allows to be overloaded. The following functions are used to provide capabilities not easily expressed via Perl operator overloading.
These functions may either be called as methods from symbols constructed by the Symbols construction routine, or they may be exported into the user's namespace as described in EXPORT.
use Math::Algebra::Symbols trig=>1;
($x, $y) = symbols(qw(x y));
$z = sin($x)**2 == (1-cos(2*$x))/2;
print "$z\n";
# 1
The trigonometric functions cos, sin, tan, sec, csc, cot are available, either as exports to the caller's name space, or as methods.
use Math::Algebra::Symbols hyper=>1;
($x, $y) = symbols(qw(x y));
$z = tanh($x+$y)==(tanh($x)+tanh($y))/(1+tanh($x)*tanh($y));
print "$z\n";
# 1
The hyperbolic functions cosh, sinh, tanh, sech, csch, coth are available, either as exports to the caller's name space, or as methods.
use Math::Algebra::Symbols complex=>1;
($x, $i) = symbols(qw(x i));
$R = re($i*$x); $I = im($i*$x);
print "$R $I\n";
# 0 $x
The re and im functions return an expression which represents the real and imaginary parts of the expression, assuming that symbolic variables represent real numbers.
use Math::Algebra::Symbols complex=>1;
$i = symbols(qw(i));
$c = cross($i+1, $i-1); $d = dot ($i+1, $i-1);
print "$c $d\n";
# 2 0
The dot and cross operators are available as functions, either as exports to the caller's name space, or as methods.
use Math::Algebra::Symbols complex=>1;
$i = symbols(qw(i));
$x = unit($i+1); $y = modulus($i+1); $z = conjugate($i+1);
print "$x\n$y\n$z\n";
# $i*sqrt(1/2)+sqrt(1/2) # sqrt(2) # 1-$i
The conjugate, abs and unit operators are available as functions: conjugate, modulus and unit, either as exports to the caller's name space, or as methods. The confusion over the naming of: the abs operator being the same as the modulus complex function; arises over the limited set of Perl operator names available for overloading.
use Math::Algebra::Symbols; ($x) = symbols(qw(x)); $y = (($x**8 - 1)/($x-1))->simplify(); # Simplify method $z += ($x**8 - 1)/($x-1); # Simplify via +=
print "$y\n$z\n";
# $x+$x**2+$x**3+$x**4+$x**5+$x**6+$x**7+1 # $x+$x**2+$x**3+$x**4+$x**5+$x**6+$x**7+1
Simplify() attempts to simplify an expression. There is no general simplification algorithm: consequently simplifications are carried out on ad hoc basis. You may not even agree that the proposed simplification for a given expressions is indeed any simpler than the original. It is for these reasons that simplification has to be explicitly requested rather than being performed automagically.
At the moment, simplifications consist of polynomial division: when the expression consists, in essence, of one polynomial divided by another, an attempt is made to perform polynomial division, the result is returned if there is no remainder.
The += operator may be used to simplify and assign an expression to a Perl variable. Perl operator overloading precludes the use of = in this manner.
use Math::Algebra::Symbols; ($x, $y) = symbols(qw(x y)); $e = 1+$x+$x**2/2+$x**3/6+$x**4/24+$x**5/120;
$e2 = $e->sub(x=>$y**2, z=>2); #1 $e3 = $e->sub(x=>1); #2
print "$e2\n\n$e3\n\n";
# 1+$y**2+1/2*$y**4+1/6*$y**6+1/24*$y**8+1/120*$y**10
# 163/60
The sub() function example on line #1 demonstrates replacing variables with expressions. The replacement specified for z has no effect as z is not present in this equation.
Line #2 demonstrates the resulting rational fraction that arises when all the variables have been replaced by constants. This package does not convert fractions to decimal expressions in case there is a loss of accuracy, however:
$e3 =~ /^(\d+)\/(\d+)$/; $result = $1/$2;
or similar will produce approximate results.
At the moment only variables can be replaced by expressions. Mike Schilli, m@perlmeister.com, has proposed that substitutions for expressions should also be allowed, as in:
$x/$y => $z
use Math::Algebra::Symbols;
($x, $v, $t) = symbols(qw(x v t));
$z = ($v eq $x / $t)->solve(qw(x in terms of v t)); #1
print "x=$z\n";
# x=$v*$t
solve() assumes that the equation on the left hand side is equal to zero, applies various simplifications, then attempts to rearrange the equation to obtain an equation for the first variable in the parameter list assuming that the other terms mentioned in the parameter list are known constants. There may of course be other unknown free variables in the equation to be solved: the proposed solution is automatically tested against the original equation to check that the proposed solution removes these variables, an error is reported via die() if it does not.
use Math::Algebra::Symbols; use symbols;
my ($x) = symbols(qw(x));
my $p = $x**2-5*$x+6; # Quadratic polynomial my ($a, $b) = @{($p > $x )}; # Solve for x
print "x=$a,$b\n"; # Roots
# x=2,3
If there are multiple solutions, (as in the case of polynomials), solve() returns an array of symbolic expressions containing the solutions.
use Math::Algebra::Symbols;
($x, $i) = S(qw(x i));
$z = exp($x)->d->d('x')->d($x)->d();
print "$z\n";
# exp($x)
d() differentiates the equation on the left hand side by the named variable.
The variable to be differentiated by may be explicitly specified, either as a string or as single symbol; or it may be heuristically guessed as follows:
If the equation to be differentiated refers to only one symbol, then that symbol is used. If several symbols are present in the equation, but only one of t, x, y, z is present, then that variable is used in honor of Newton, Leibnitz, Cauchy.
use Math::Algebra::Symbols;
($a, $b, $x, $y, $i) = symbols(qw(a b x y i));
print $i x 1, "\n"; # Cross product # 1
print $i^1, "\n"; # Dot product - different vectors # 0
print $i^$i, "\n"; # Dot product - same vector # 1
print abs $i, "\n"; # Length of unit vector # 1
print ~($a+$b) == ~$a+~$b, "\n"; # Conjugation is distributive # 1 over addition
print ~($a*$b) == ~$a*~$b, "\n"; # Conjugation is distributive # 1 over multiplication
print ~($a**2) == (~$a)**2,"\n"; # Conjugation is distributive # 1 over power
print abs(!($x+$y*$i))==1,"\n"; # Length of unit vector # 1
print # Length of product = product of lengths abs($a+$i*$b)*abs($x+$i*$y) == abs(($a+$i*$b)* ($x+$i*$y)), "\n"; # 1
use Math::Algebra::Symbols; ($a, $b, $x, $y, $i, $o) = symbols(qw(a b x y i 1));
print "Hyperbola: Constant difference between distances from focii to locus of y=1/x", "\n Assume by symmetry the focii are on ", "\n the line y=x: ", $f1 = $x + $i * $x, "\n and equidistant from the origin: ", $f2 = -$f1, "\n Choose a convenient point on y=1/x: ", $a = $o+$i, "\n and a general point on y=1/x: ", $b = $y+$i/$y, "\n Difference in distances from focii", "\n From convenient point: ", $A = abs($a - $f2) - abs($a - $f1), "\n From general point: ", $B = abs($b - $f2) + abs($b - $f1), "\n\n Solving for x we get: x=", ($A - $B) > $x, "\n (should be: sqrt(2))", "\n Which is indeed constant, as was to be demonstrated\n";
This example demonstrates the power of symbolic processing by finding the focii of the curve y=1/x, and incidentally, demonstrating that this curve is a hyperbola.
use Math::Algebra::Symbols;
$x = symbols(qw(x));
$x->test();
The test() method performs many tests which are useful in validating this package and as examples of the capabilities of this package. These tests may also be run as:
perl symbols.pm
use Math::Algebra::Symbols symbols=>'S', BigInt => 0, trig => 1, hyper => 1, complex=> 1;
The Symbols packages manipulate a sum of products representation of an algebraic equation. The Symbols package is the user interface to the functionality supplied by the SymbolsSum and SymbolsTerm packages.
SymbolsTerm represents a product term. A product term consists of the number 1, optionally multiplied by:
Thus SymbolsTerm can represent expressions like:
2/3*$x**2*$y**-3*exp($i*$pi)*sqrt($z**3) / $x
but not:
$x + $y
for which package SymbolsSum is required.
SymbolsSum represents a sum of product terms supplied by SymbolsTerm and thus behaves as a polynomial. Operations such as equation solving and differentiation are applied at this level.
The main benefit of programming SymbolsTerm and SymbolsSum as two separate but related packages is Object Oriented Polymorphism. I.e. both packages need to multiply items together: each package has its own multiply method, with Perl method lookup selecting the appropriate one as required.
Packaging the user functionality alone and separately in package Symbols allows the internal functions to be conveniently hidden from user scripts.
Philip R Brenan at philiprbrenan@yahoo.com