Terms
Symbolic Algebra in Pure Perl: terms.
See user manual "NAME".
A term represents a product of: variables, coefficents, divisors, square roots, exponentials, and logs.
PhilipRBrenan@yahoo.com, 2004, Perl License.
package Math::Algebra::Symbols::Term;
$VERSION=1.21;
use Carp;
use Math::BigInt;
#HashUtil use Hash::Util qw(lock_hash);
use Scalar::Util qw(weaken);
Constructors
new
Constructor
sub new
{bless {c=>1, d=>1, i=>0, v=>{}, sqrt=>undef, divide=>undef, exp=>undef, log=>undef};
}
newFromString
New from String
sub newFromString($)
{my ($a) = @_;
return $zero unless $a;
my $A = $a;
for(;$A =~ /(\d+)\.(\d+)/;)
{my $i = $1;
my $j = $2;
my $l = '0' x length($j);
# carp "Replacing $i.$j with $i$j\/1$l in $A";
$A =~ s/$i\.$j/$i$j\/1$l/;
}
if ($A =~ /^\s*([+-])?(\d+)?(?:\/(\d+))?(i)?(?:\*)?(.*)$/)
{my $c = '';
$c = '-'.$c if $1 and $1 eq '-';
$c .= $2 if $2;
$c = '1' if $c eq '';
$c = '-1' if $c eq '-';
my $d = '';
$d = $3 if $3;
$d = 1 if $d eq '';
my $i = 0;
$i = 1 if $4;
my $z = new()->c($c)->d($d)->i($i);
my $b = $5;
for (;$b =~ /^([a-z]+)(?:\*\*)?(\d+)?(?:\*)?(.*)$/i;)
{$b = $3;
$z->{v}{$1} = $2 if defined($2);
$z->{v}{$1} = 1 unless defined($2);
}
croak "Cannot parse: $a" if $A eq $b;
croak "Cannot parse: $b in $a" if $b;
return $z->z;
}
croak "Unable to parse $a";
}
n
Short name for "newFromString"
sub n($)
{newFromString($_[0]);
}
newFromStrings
New from Strings
sub newFromStrings(@)
{return $zero->clone() unless scalar(@_);
map {newFromString($_)} @_;
}
gcd
Greatest Common Divisor.
sub gcd($$)
{my $x = abs($_[0]);
my $y = abs($_[1]);
return 1 if $x == 1 or $y == 1;
my ($a, $b) = ($x, $y); $a = $y, $b = $x if $y < $a;
for(my $r;;)
{$r = $b % $a;
return $a if $r == 0;
($a, $b) = ($r, $a);
}
}
lcm
Least common multiple.
sub lcm($$)
{my $x = abs($_[0]);
my $y = abs($_[1]);
return $x*$y if $x == 1 or $y == 1;
$x*$y / gcd($x, $y);
}
isTerm
Confirm type
sub isTerm($) {1};
intCheck
Integer check
sub intCheck($$)
{my ($i, $m) = @_;
return $i if $i == 1;
$i =~ /^[\+\-]?\d+/ or die "Integer required for $m not $i";
return Math::BigInt->new($i) if $i > 10_000_000;
$i;
}
c
Coefficient
sub c($;$)
{my ($t) = @_;
return $t->{c} unless @_ > 1;
$t->{c} = ($_[1] == 1 ? $_[1] : intCheck($_[1], 'c'));
$t;
}
d
Divisor
sub d($;$)
{my ($t) = @_;
return $t->{d} unless @_ > 1;
$t->{d} = ($_[1] == 1 ? $_[1] : intCheck($_[1], 'd'));
$t;
}
timesInt
Multiply term by integer
sub timesInt($$)
{my ($t) = @_;
my $m = ($_[1] ? $_[1] : intCheck($_[1], 'times'));
$t->{c} *= $m;
if ($t->{d} > 1)
{my $g = gcd($t->{c}, $t->{d});
if ($g > 1)
{$t->{d} /= $g;
$t->{c} /= $g;
}
}
$t;
}
divideInt
Divide term by integer
sub divideInt($$)
{my ($t) = @_;
my $d = ($_[1] == 1 ? $_[1] : intCheck($_[1], 'divide'));
$d != 0 or die "Cannot divide by zero";
$t->{d} *= abs($d);
my $g = gcd($t->{d}, $t->{c});
if ($g > 1)
{$t->{d} /= $g;
$t->{c} /= $g;
}
$t->{c} = - $t->{c} if $d < 0;
$t;
}
negate
Negate term
sub negate($)
{my ($t) = @_;
$t->{c} = -$t->{c};
$t;
}
isZero
Zero?
sub isZero($)
{my ($t) = @_;
exists $t->{z} or die "Testing unfinalized term";
$t->{id} == $zero->{id};
}
notZero
Not Zero?
sub notZero($) {return !isZero($_[0])}
isOne
One?
sub isOne($)
{my ($t) = @_;
exists $t->{z} or die "Testing unfinalized term";
$t->{id} == $one->{id};
}
notOne
Not One?
sub notOne($) {return !isOne($_[0])}
isMinusOne
Minus One?
sub isMinusOne($)
{my ($t) = @_;
exists $t->{z} or die "Testing unfinalized term";
$t->{id} == $mOne->{id};
}
notMinusOne
Not Minus One?
sub notMinusOne($) {return !isMinusOne($_[0])}
i
Get/Set i - sqrt(-1)
sub i($;$)
{my ($t) = @_;
return $t->{i} unless(@_) > 1;
my $i = ($_[1] == 1 ? $_[1] : intCheck($_[1], 'i'));
my $i4 = $i % 4;
$t->{i} = $i % 2;
$t->{c} = -$t->{c} if $i4 == 2 or $i4 == 3;
$t;
}
iby
i by power: multiply a term by a power of i
sub iby($$)
{my ($t, $p) = @_;
$t->i($p+$t->{i});
$t;
}
Divide
Get/Set divide by.
sub Divide($;$)
{my ($t, $d) = @_;
return $t->{divide} unless @_ > 1;
$t->{divide} = $d;
$t;
}
removeDivide
Remove divide
sub removeDivide($)
{my ($t) = @_;
my $z = $t->clone;
delete $z->{divide};
$z->z;
}
Sqrt
Get/Set square root.
sub Sqrt($;$)
{my ($t, $s) = @_;
return $t->{sqrt} unless @_ > 1;
$t->{sqrt} = $s;
$t;
}
removeSqrt
Remove square root.
sub removeSqrt($)
{my ($t) = @_;
my $z = $t->clone;
delete $z->{sqrt};
$z->z;
}
Exp
Get/Set exp
sub Exp($;$)
{my ($t, $e) = @_;
return $t->{exp} unless @_ > 1;
$t->{exp} = $e;
$t;
}
Log
# Get/Set log
sub Log($$)
{my ($t, $l) = @_;
return $t->{log} unless @_ > 1;
$t->{log} = $l;
$t;
}
vp
Get/Set variable power.
On get: returns the power of a variable, or zero if the variable is not present in the term.
On set: Sets the power of a variable. If the power is zero, removes the variable from the term. =cut
sub vp($$;$)
{my ($t, $v) = @_;
# $v =~ /^[a-z]+$/i or die "Bad variable name $v";
return exists($t->{v}{$v}) ? $t->{v}{$v} : 0 if @_ == 2;
my $p = ($_[2] == 1 ? $_[2] : intCheck($_[2], 'vp'));
$t->{v}{$v} = $p if $p;
delete $t->{v}{$v} unless $p;
$t;
}
v
Get all variables mentioned in the term. Variables to power zero should have been removed by "vp".
sub v($)
{my ($t) = @_;
return keys %{$t->{v}};
}
clone
Clone a term. The existing term must be finalized, see "z": the new term will not be finalized, allowing modifications to be made to it.
sub clone($)
{my ($t) = @_;
$t->{z} or die "Attempt to clone unfinalized term";
my $c = bless {%$t};
$c->{v} = {%{$t->{v}}};
delete @$c{qw(id s z)};
$c;
}
split
Split a term into its components
sub split($)
{my ($t) = @_;
my $c = $t->clone;
my @c = @$c{qw(sqrt divide exp log)};
@$c{qw(sqrt divide exp log)} = ((undef()) x 4);
(t=>$c, s=>$c[0], d=>$c[1], e=>$c[2], l=>$c[3]);
}
signature
Sign the term. Used to optimize addition. Fix the problem of adding different logs
sub signature($)
{my ($t) = @_;
my $s = '';
$s .= sprintf("%010d", $t->{v}{$_}) . $_ for keys %{$t->{v}};
$s .= '(divide'. $t->{divide} .')' if defined($t->{divide});
$s .= '(sqrt'. $t->{sqrt} .')' if defined($t->{sqrt});
$s .= '(exp'. $t->{exp} .')' if defined($t->{exp});
$s .= '(log'. $t->{log} .')' if defined($t->{log});
$s .= 'i' if $t->{i} == 1;
$s = '1' if $s eq '';
$s;
}
getSignature
Get the signature of a term
sub getSignature($)
{my ($t) = @_;
exists $t->{z} ? $t->{z} : die "Attempt to get signature of unfinalized term";
}
add
Add two finalized terms, return result in new term or undef.
sub add($$)
{my ($a, $b) = @_;
$a->{z} and $b->{z} or
die "Attempt to add unfinalized terms";
return undef unless $a->{z} eq $b->{z};
return $a->clone->timesInt(2)->z if $a == $b;
my $z = $a->clone;
my $c = $a->{c} * $b->{d}
+ $b->{c} * $a->{d};
my $d = $a->{d} * $b->{d};
return $zero if $c == 0;
$z->c($c)->d(1)->divideInt($d)->z;
}
subtract
Subtract two finalized terms, return result in new term or undef.
sub subtract($$)
{my ($a, $b) = @_;
$a->{z} and $b->{z} or
die "Attempt to subtract unfinalized terms";
return $zero if $a == $b;
return $a if $b == $zero;
return $b->clone->negate->z if $a == $zero;
return undef unless $a->{z} eq $b->{z};
my $z = $a->clone;
my $c = $a->{c} * $b->{d}
- $b->{c} * $a->{d};
my $d = $a->{d} * $b->{d};
$z->c($c)->d(1)->divideInt($d)->z;
}
multiply
Multiply two finalized terms, return the result in a new term or undef
sub multiply($$)
{my ($a, $b) = @_;
$a->{z} and $b->{z} or
die "Attempt to multiply unfinalized terms";
# Check
return undef if
(defined($a->{divide}) and defined($b->{divide})) or
(defined($a->{sqrt} ) and defined($b->{sqrt})) or
(defined($a->{exp} ) and defined($b->{exp})) or
(defined($a->{log} ) and defined($b->{log}));
# cdi
my $c = $a->{c} * $b->{c};
my $d = $a->{d} * $b->{d};
my $i = $a->{i} + $b->{i};
$c = -$c, $i = 0 if $i == 2;
my $z = $a->clone->c($c)->d(1)->divideInt($d)->i($i);
# v
# for my $v($b->v)
# {$z->vp($v, $z->vp($v)+$b->vp($v));
# }
for my $v(keys(%{$b->{v}}))
{$z->vp($v, (exists($z->{v}{$v}) ? $z->{v}{$v} : 0)+$b->{v}{$v});
}
# Divide, sqrt, exp, log
$z->{divide} = $b->{divide} unless defined($a->{divide});
$z->{sqrt} = $b->{sqrt} unless defined($a->{sqrt});
$z->{exp} = $b->{exp} unless defined($a->{exp});
$z->{log} = $b->{log} unless defined($a->{log});
# Result
$z->z;
}
divide2
Divide two finalized terms, return the result in a new term or undef
sub divide2($$)
{my ($a, $b) = @_;
$a->{z} and $b->{z} or
die "Attempt to divide unfinalized terms";
# Check
return undef if
(defined($b->{divide}) and (!defined($a->{divide}) or $a->{divide}->id != $b->{divide}->id));
return undef if
(defined($b->{sqrt} ) and (!defined($a->{sqrt} ) or $a->{sqrt} ->id != $b->{sqrt} ->id));
return undef if
(defined($b->{exp} ) and (!defined($a->{exp} ) or $a->{exp} ->id != $b->{exp} ->id));
return undef if
(defined($b->{log} ) and (!defined($a->{log} ) or $a->{log} ->id != $b->{log} ->id));
# cdi
my $c = $a->{c} * $b->{d};
my $d = $a->{d} * $b->{c};
my $i = $a->{i} - $b->{i};
$c = -$c, $i = 1 if $i == -1;
my $g = gcd($c, $d);
$c /= $g;
$d /= $g;
my $z = $a->clone->c($c)->d(1)->divideInt($d)->i($i);
# v
for my $v($b->v)
{$z->vp($v, $z->vp($v)-$b->vp($v));
}
# Sqrt, divide, exp, log
delete $z->{divide} if defined($a->{divide}) and defined($b->{divide});
delete $z->{sqrt } if defined($a->{sqrt }) and defined($b->{sqrt });
delete $z->{exp } if defined($a->{exp }) and defined($b->{exp });
delete $z->{log } if defined($a->{log }) and defined($b->{log });
# Result
$z->z;
}
invert
Invert a term
sub invert($)
{my ($t) = @_;
$t->{z} or die "Attempt to invert unfinalized term";
# Check
return undef if
$t->{divide} or
$t->{sqrt} or
$t->{exp} or
$t->{log};
# cdi
my ($c, $d, $i) = ($t->{c}, $t->{d}, $t->{i});
$c = -$c if $i;
my $z = clone($t)->c($d)->d(1)->divideInt($c)->i($i);
# v
for my $v($z->v)
{$z->vp($v, $z->vp($v));
}
# Result
$z->z;
}
power
Take power of term
sub power($$)
{my ($a, $b) = @_;
$a->{z} and $b->{z} or die "Attempt to take power of unfinalized term";
# Check
return $one if $a == $one or $b == $zero;
return undef if
$a->{divide} or
$a->{sqrt} or
$a->{exp} or
$a->{log};
return undef if
$b->{d} != 1 or
$b->{i} == 1 or
$b->{divide} or
$b->{sqrt} or
$b->{exp} or
$b->{log};
# cdi
my ($c, $d, $i) = ($a->{c}, $a->{d}, $a->{i});
my $p = $b->{c};
if ($p < 0)
{$a = invert($a);
return undef unless $a;
$p = -$p;
return $a if $p == 1;
}
my $z = $a->clone->z;
$z = $z->multiply($a) for (2..$p);
$i *= $p;
$z = $z->clone->i($i);
# v
# for my $v($z->v)
# {$z->vp($v, $p*$z->vp($v));
# }
# Result
$z->z;
}
sqrt2
Square root of a term
sub sqrt2($)
{my ($t) = @_;
$t->{z} or die "Attempt to sqrt unfinalized term";
# Check
return undef if $t->{i} or
$t->{divide} or
$t->{sqrt} or
$t->{exp} or
$t->{log};
# cd
my ($c, $d, $i) = ($t->{c}, $t->{d}, 0);
$c = -$c, $i = 1 if $c < 0;
my $c2 = sqrt($c); return undef unless $c2*$c2 == $c;
my $d2 = sqrt($d); return undef unless $d2*$d2 == $d;
my $z = clone($t)->c($c2)->d($d2)->i($i);
# v
for my $v($t->v)
{my $p = $z->vp($v);
return undef unless $p % 2 == 0;
$z->vp($v, $p/2);
}
# Result
$z->z;
}
exp2
Exponential of a term
sub exp2($)
{my ($t) = @_;
$t->{z} or die "Attempt to use unfinalized term in exp";
return $one if $t == $zero;
return undef if $t->{divide} or
$t->{sqrt} or
$t->{exp} or
$t->{log};
return undef unless $t->{i} == 1;
return undef unless $t->{d} == 1 or
$t->{d} == 2 or
$t->{d} == 4;
return undef unless scalar(keys(%{$t->{v}})) == 1 and
exists($t->{v}{pi}) and
$t->{v}{pi} == 1;
my $c = $t->{c};
my $d = $t->{d};
$c *= 2 if $d == 1;
$c %= 4;
return $one if $c == 0;
return $i if $c == 1;
return $mOne if $c == 2;
return $mI if $c == 3;
}
sin2
Sine of a term
sub sin2($)
{my ($t) = @_;
$t->{z} or die "Attempt to use unfinalized term in sin";
return $zero if $t == $zero;
return undef if $t->{divide} or
$t->{sqrt} or
$t->{exp} or
$t->{log};
return undef unless $t->{i} == 0;
return undef unless scalar(keys(%{$t->{v}})) == 1;
return undef unless exists($t->{v}{pi});
return undef unless $t->{v}{pi} == 1;
my $c = $t->{c};
my $d = $t->{d};
return undef unless $d== 1 or $d == 2 or $d == 3 or $d == 6;
$c *= 6 if $d == 1;
$c *= 3 if $d == 2;
$c *= 2 if $d == 3;
$c = $c % 12;
return $zero if $c == 0;
return $half if $c == 1;
return undef if $c == 2;
return $one if $c == 3;
return undef if $c == 4;
return $half if $c == 5;
return $zero if $c == 6;
return $mHalf if $c == 7;
return $undef if $c == 8;
return $mOne if $c == 9;
return $undef if $c == 10;
return $mHalf if $c == 11;
return $zero if $c == 12;
}
cos2
Cosine of a term
sub cos2($)
{my ($t) = @_;
$t->{z} or die "Attempt to use unfinalized term in cos";
return $one if $t == $zero;
return undef if $t->{divide} or
$t->{sqrt} or
$t->{exp} or
$t->{log};
return undef unless $t->{i} == 0;
return undef unless scalar(keys(%{$t->{v}})) == 1;
return undef unless exists($t->{v}{pi});
return undef unless $t->{v}{pi} == 1;
my $c = $t->{c};
my $d = $t->{d};
return undef unless $d== 1 or $d == 2 or $d == 3 or $d == 6;
$c *= 6 if $d == 1;
$c *= 3 if $d == 2;
$c *= 2 if $d == 3;
$c = $c % 12;
return $half if $c == 10;
return $undef if $c == 11;
return $one if $c == 12;
return $one if $c == 0;
return undef if $c == 1;
return $half if $c == 2;
return $zero if $c == 3;
return $mHalf if $c == 4;
return $undef if $c == 5;
return $mOne if $c == 6;
return $undef if $c == 7;
return $mHalf if $c == 8;
return $zero if $c == 9;
}
log2
Log of a term
sub log2($)
{my ($a) = @_;
$a->{z} or die "Attempt to use unfinalized term in log";
return $zero if $a == $one;
return undef;
}
id
Get Id of a term
sub id($)
{my ($t) = @_;
$t->{id} or die "Term $t not yet finalized";
$t->{id};
}
zz
# Check term finalized
sub zz($)
{my ($t) = @_;
$t->{z} or die "Term $t not yet finalized";
$t;
}
z
Finalize creation of the term. Once a term has been finalized, it becomes readonly, which allows optimization to be performed. =cut
my $lock = 0; # Hash locking
my $z = 0; # Term counter
my %z; # Terms finalized
sub z($)
{my ($t) = @_;
!exists($t->{z}) or die "Already finalized this term";
my $p = $t->print;
return $z{$p} if defined($z{$p});
$z{$p} = $t;
weaken($z{$p}); # Greatly reduces memory usage
$t->{s} = $p;
$t->{z} = $t->signature;
$t->{id} = ++$z;
#HashUtil lock_hash(%{$t->{v}}) if $lock;
#HashUtil lock_hash %$t if $lock;
$t;
}
#sub DESTROY($)
# {my ($t) = @_;
# delete $z{$t->{s}} if defined($t) and exists $t->{s};
# }
sub lockHashes()
{my ($l) = @_;
#HashUtil for my $t(values %z)
#HashUtil {lock_hash(%{$t->{v}});
#HashUtil lock_hash %$t;
#HashUtil }
$lock = 1;
}
sub print($)
{my ($t) = @_;
return $t->{s} if defined($t->{s});
my @k = keys %{$t->{v}};
my $v = $t->{v};
my $s = '';
$s .= $t->{c};
$s .= '/'.$t->{d} if $t->{d} != 1;
$s .= '*i' if $t->{i} == 1;
$s .= '*$'.$_ for grep {$v->{$_} == 1} @k;
$s .= '/$'.$_ for grep {$v->{$_} == -1} @k;
$s .= '*$'.$_.'**'. $v->{$_} for grep {$v->{$_} > 1} @k;
$s .= '/$'.$_.'**'.-$v->{$_} for grep {$v->{$_} < -1} @k;
$s .= '/('. $t->{divide} .')' if $t->{divide};
$s .= '*sqrt('. $t->{sqrt} .')' if $t->{sqrt};
$s .= '*exp('. $t->{exp} .')' if $t->{exp};
$s .= '*log('. $t->{log} .')' if $t->{log};
$s;
}
constants
Useful constants
$zero = new()->c(0)->z; sub zero () {$zero}
$one = new()->z; sub one () {$one}
$two = new()->c(2)->z; sub two () {$two}
$mOne = new()->c(-1)->z; sub mOne () {$mOne}
$i = new()->i(1)->z; sub pI () {$pI}
$mI = new()->c(-1)->i(1)->z; sub mI () {$mI}
$half = new()->c( 1)->d(2)->z; sub half () {$half}
$mHalf = new()->c(-1)->d(2)->z; sub mHalf() {$mHalf}
$pi = new()->vp('pi', 1)->z; sub pi () {$pi}
import
Export "newFromStrings" to calling package with a name specifed by the caller, or as term() by default. =cut
sub import
{my %P = (program=>@_);
my %p; $p{lc()} = $P{$_} for(keys(%P));
#_______________________________________________________________________
# New symbols term constructor - export to calling package.
#_______________________________________________________________________
my $s = "pack"."age XXXX;\n". <<'END';
no warnings 'redefine';
sub NNNN
{return SSSSnewFromStrings(@_);
}
use warnings 'redefine';
END
#_______________________________________________________________________
# Export to calling package.
#_______________________________________________________________________
my $name = 'term';
$name = $p{term} if exists($p{term});
my ($main) = caller();
my $pack = __PACKAGE__.'::';
$s=~ s/XXXX/$main/g;
$s=~ s/NNNN/$name/g;
$s=~ s/SSSS/$pack/g;
eval($s);
#_______________________________________________________________________
# Check options supplied by user
#_______________________________________________________________________
delete @p{qw(program terms)};
croak "Unknown option(s) for ". __PACKAGE__ .": ". join(' ', keys(%p))."\n\n". <<'END' if keys(%p);
Valid options are:
terms=>'name' Desired name of the constructor routine for creating
new terms. The default is 'term'.
END
}
Operators
Operator Overloads
Operator Overloads
use overload
'+' =>\&add3,
'-' =>\&negate3,
'*' =>\&multiply3,
'/' =>\÷3,
'**' =>\&power3,
'==' =>\&equals3,
'sqrt' =>\&sqrt3,
'exp' =>\&exp3,
'log' =>\&log3,
'sin' =>\&sin3,
'cos' =>\&cos3,
'""' =>\&print3,
fallback=>1;
add3
Add operator.
sub add3
{my ($a, $b) = @_;
$b = newFromString("$b") unless ref($b) eq __PACKAGE__;
$a->{z} and $b->{z} or die "Add using unfinalized terms";
$a->add($b);
}
negate3
Negate operator.
sub negate3
{my ($a, $b, $c) = @_;
if (defined($b))
{$b = newFromString("$b") unless ref($b) eq __PACKAGE__;
$a->{z} and $b->{z} or die "Negate using unfinalized terms";
return $b->subtract($a) if $c;
return $a->subtract($b) unless $c;
}
else
{$a->{z} or die "Negate single unfinalized terms";
return $a->negate;
}
}
multiply3
Multiply operator.
sub multiply3
{my ($a, $b) = @_;
$b = newFromString("$b") unless ref($b) eq __PACKAGE__;
$a->{z} and $b->{z} or die "Multiply using unfinalized terms";
$a->multiply($b);
}
divide3
Divide operator.
sub divide3
{my ($a, $b, $c) = @_;
$b = newFromString("$b") unless ref($b) eq __PACKAGE__;
$a->{z} and $b->{z} or die "Divide using unfinalized terms";
return $b->divide2($a) if $c;
return $a->divide2($b) unless $c;
}
power3
Power operator.
sub power3
{my ($a, $b) = @_;
$b = newFromString("$b") unless ref($b) eq __PACKAGE__;
$a->{z} and $b->{z} or die "Power using unfinalized terms";
$a->power($b);
}
equals3
Equals operator.
sub equals3
{my ($a, $b) = @_;
if (ref($b) eq __PACKAGE__)
{$a->{z} and $b->{z} or die "Equals using unfinalized terms";
return $a->{id} == $b->{id};
}
else
{$a->{z} or die "Equals using unfinalized terms";
return $a->print eq "$b";
}
}
print3
Print operator.
sub print3
{my ($a) = @_;
$a->{z} or die "Print of unfinalized term";
$a->print();
}
sqrt3
Square root operator.
sub sqrt3
{my ($a) = @_;
$a->{z} or die "Sqrt of unfinalized term";
$a->sqrt2();
}
exp3
Exponential operator.
sub exp3
{my ($a) = @_;
$a->{z} or die "Exp of unfinalized term";
$a->exp2();
}
sin3
Sine operator.
sub sin3
{my ($a) = @_;
$a->{z} or die "Sin of unfinalized term";
$a->sin2();
}
cos3
Cosine operator.
sub cos3
{my ($a) = @_;
$a->{z} or die "Cos of unfinalized term";
$a->cos2();
}
log3
Log operator.
sub log3
{my ($a) = @_;
$a->{z} or die "Log of unfinalized term";
$a->log2();
}
test
Tests
sub test()
{my ($a, $b, $c);
# lockHashes();
$a = n(0); $a == $zero or die "100";
$a = n(1); $a == $one or die "101";
$a = n(2); $a == $two or die "102";
$b = n(3); $b == 3 or die "103";
$c = $a+$a; $c == 4 or die "104";
$c = $a+$b; $c == 5 or die "105";
$c = $a+$b+$a+$b; $c == 10 or die "106";
$c = $a+1; $c == 3 or die "107";
$c = $a+2; $c == 4 or die "108";
$c = $b-1; $c == 2 or die "109";
$c = $b-2; $c == 1 or die "110";
$c = $b-9; $c == -6 or die "111";
$c = $a/2; $c == $one or die "112";
$c = $a/4; $c == '1/2' or die "113";
$c = $a*2/2; $c == $two or die "114";
$c = $a*2/4; $c == $one or die "115";
$c = $a**2; $c == 4 or die "116";
$c = $a**10; $c == 1024 or die "117";
$c = sqrt($a**2); $c == $a or die "118";
$d = n(-1); $d == -1 or die "119";
$c = sqrt($d); $c == '1*i' or die "120";
$d = n(4); $d == 4 or die "121";
$c = sqrt($d); $c == 2 or die "122";
$c = n('x*y2')/n('a*b2'); $c == '1*$x/$a*$y**2/$b**2' or die "122";
$a = n('x'); $a == '1*$x' or die "21";
$b = n('2*x**2'); $b == '2*$x**2' or die "22";
$c = $a+$a; $c == '2*$x' or die "23";
$c = $a+$a+$a; $c == '3*$x' or die "24";
$c = $a-$a; $c == $zero or die "25";
$c = $a-$a-$a; $c == '-1*$x' or die "26";
$c = $a*$b; $c == '2*$x**3' or die "27";
$c = $a*$b*$a*$b; $c == '4*$x**6' or die "28";
$c = $b/$a; $c == '2*$x' or die "29";
$c = $a**2/$b;
$c == '1/2' or die "29";
$c = sqrt($a**4/($b/2)); $c == $a or die "29";
$a = sin($zero); $a == -0 or die "301";
$a = sin($pi/6); $a == $half or die "302";
$a = sin($pi/2); $a == 1 or die "303";
$a = sin(5*$pi/6); $a == $half or die "304";
$a = sin(120*$pi/120); $a == $zero or die "305";
$a = sin(7*$pi/6); $a == -$half or die "306";
$a = sin(3*$pi/2); $a == -1 or die "307";
$a = sin(110*$pi/ 60); $a == '-1/2' or die "308";
$a = sin(2*$pi); $a == $zero or die "309";
$a = sin(-$zero); $a == $zero or die "311";
$a = sin(-$pi/6); $a == -$half or die "312";
$a = sin(-$pi/2); $a == -$one or die "313";
$a = sin(-5*$pi/6); $a == -$half or die "314";
$a = sin(-120*$pi/120); $a == -$zero or die "315";
$a = sin(-7*$pi/6); $a == $half or die "316";
$a = sin(-3*$pi/2); $a == $one or die "317";
$a = sin(-110*$pi/ 60); $a == $half or die "318";
$a = sin(-2*$pi); $a == $zero or die "319";
$a = cos($zero); $a == $one or die "321";
$a = cos($pi/3); $a == $half or die "322";
$a = cos($pi/2); $a == $zero or die "323";
$a = cos(4*$pi/6); $a == -$half or die "324";
$a = cos(120*$pi/120); $a == -$one or die "325";
$a = cos(8*$pi/6); $a == -$half or die "326";
$a = cos(3*$pi/2); $a == $zero or die "327";
$a = cos(100*$pi/ 60); $a == $half or die "328";
$a = cos(2*$pi); $a == $one or die "329";
$a = cos(-$zero); $a == $one or die "331";
$a = cos(-$pi/3); $a == +$half or die "332";
$a = cos(-$pi/2); $a == $zero or die "333";
$a = cos(-4*$pi/6); $a == -$half or die "334";
$a = cos(-120*$pi/120); $a == -$one or die "335";
$a = cos(-8*$pi/6); $a == -$half or die "336";
$a = cos(-3*$pi/2); $a == $zero or die "337";
$a = cos(-100*$pi/ 60); $a == $half or die "338";
$a = cos(-2*$pi); $a == $one or die "339";
$a = exp($zero); $a == $one or die "340";
$a = exp($i*$pi/2); $a == $i or die "341";
$a = exp($i*$pi); $a == -$one or die "342";
$a = exp(3*$i*$pi/2); $a == -$i or die "343";
$a = exp(4*$i*$pi/2); $a == $one or die "344";
}
test unless caller;
#_______________________________________________________________________
# Package installed successfully
#_______________________________________________________________________
1;
__DATA__
#______________________________________________________________________
# User guide.
#______________________________________________________________________
NAME
Math::Algebra::Symbols - Symbolic Algebra in Pure Perl.
User guide.
SYNOPSIS
Example symbols.pl
#!perl -w -I..
#______________________________________________________________________
# Symbolic algebra.
# Perl License.
# PhilipRBrenan@yahoo.com, 2004.
#______________________________________________________________________
use Math::Algebra::Symbols hyper=>1;
use Test::Simple tests=>5;
($n, $x, $y) = symbols(qw(n x y));
$a += ($x**8 - 1)/($x-1);
$b += sin($x)**2 + cos($x)**2;
$c += (sin($n*$x) + cos($n*$x))->d->d->d->d / (sin($n*$x)+cos($n*$x));
$d = tanh($x+$y) == (tanh($x)+tanh($y))/(1+tanh($x)*tanh($y));
($e,$f) = @{($x**2 eq 5*$x-6) > $x};
print "$a\n$b\n$c\n$d\n$e,$f\n";
ok("$a" eq '$x+$x**2+$x**3+$x**4+$x**5+$x**6+$x**7+1');
ok("$b" eq '1');
ok("$c" eq '$n**4');
ok("$d" eq '1');
ok("$e,$f" eq '2,3');
DESCRIPTION
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
Symbols are created with the exported symbols() constructor routine:
Example t/constants.t
#!perl -w
#______________________________________________________________________
# Symbolic algebra: examples: constants.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols;
use Test::Simple tests=>1;
my ($x, $y, $i, $o, $pi) = symbols(qw(x y i 1 pi));
ok( "$x $y $i $o $pi" eq '$x $y i 1 $pi' );
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:
Example t/ipi.t
#!perl -w
#______________________________________________________________________
# Symbolic algebra: examples: constants.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols;
use Test::Simple tests=>2;
my ($i, $pi) = symbols(qw(i pi));
ok( exp($i*$pi) == -1 );
ok( exp($i*$pi) <=> '-1' );
Constructor Routine Name
If you wish to use a different name for the constructor routine, say S:
Example t/ipi2.t
#!perl -w
#______________________________________________________________________
# Symbolic algebra: examples: constants.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols symbols=>'S';
use Test::Simple tests=>2;
my ($i, $pi) = S(qw(i pi));
ok( exp($i*$pi) == -1 );
ok( exp($i*$pi) <=> '-1' );
Big Integers
Symbols automatically uses big integers if needed.
Example t/bigInt.t
#!perl -w
#______________________________________________________________________
# Symbolic algebra: examples: bigInt.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols;
use Test::Simple tests=>1;
my $z = symbols('1234567890987654321/1234567890987654321');
ok( eval $z eq '1');
Operators
"Symbols" can be combined with "Operators" to create symbolic expressions:
Arithmetic operators
Arithmetic Operators: + - * / **
Example t/x2y2.t
#!perl -w
#______________________________________________________________________
# Symbolic algebra: examples: simplification.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols;
use Test::Simple tests=>3;
my ($x, $y) = symbols(qw(x y));
ok( ($x**2-$y**2)/($x-$y) == $x+$y );
ok( ($x**2-$y**2)/($x-$y) != $x-$y );
ok( ($x**2-$y**2)/($x-$y) <=> '$x+$y' );
The operators: += -= *= /= are overloaded to work symbolically rather than numerically. If you need numeric results, you can always eval() the resulting symbolic expression.
Square root Operator: sqrt
Example t/ix.t
#!perl -w
#______________________________________________________________________
# Symbolic algebra: examples: sqrt(-1).
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols;
use Test::Simple tests=>2;
my ($x, $i) = symbols(qw(x i));
ok( sqrt(-$x**2) == $i*$x );
ok( sqrt(-$x**2) <=> 'i*$x' );
The square root is represented by the symbol i, which allows complex expressions to be processed by Math::Complex.
Exponential Operator: exp
Example t/expd.t
#!perl -w
#______________________________________________________________________
# Symbolic algebra: examples: exp.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols;
use Test::Simple tests=>2;
my ($x, $i) = symbols(qw(x i));
ok( exp($x)->d($x) == exp($x) );
ok( exp($x)->d($x) <=> 'exp($x)' );
The exponential operator.
Logarithm Operator: log
Example t/logExp.t
#!perl -w
#______________________________________________________________________
# Symbolic algebra: examples: log: need better example.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols;
use Test::Simple tests=>1;
my ($x) = symbols(qw(x));
ok( log($x) <=> 'log($x)' );
Logarithm to base e.
Note: the above result is only true for x > 0. Symbols does not include domain and range specifications of the functions it uses.
Sine and Cosine Operators: sin and cos
Example t/sinCos.t
#!perl -w
#______________________________________________________________________
# Symbolic algebra: examples: simplification.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols;
use Test::Simple tests=>3;
my ($x) = symbols(qw(x));
ok( sin($x)**2 + cos($x)**2 == 1 );
ok( sin($x)**2 + cos($x)**2 != 0 );
ok( sin($x)**2 + cos($x)**2 <=> '1' );
This famous trigonometric identity is not preprogrammed into Symbols as it is in commercial products.
Instead: an expression for sin() is constructed using the complex exponential: "exp", said expression is algebraically multiplied out to prove the identity. The proof steps involve large intermediate expressions in each step, as yet I have not provided a means to neatly lay out these intermediate steps and thus provide a more compelling demonstration of the ability of Symbols to verify such statements from first principles.
Relational operators
Relational operators: ==, !=
Example t/x2y2.t
#!perl -w
#______________________________________________________________________
# Symbolic algebra: examples: simplification.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols;
use Test::Simple tests=>3;
my ($x, $y) = symbols(qw(x y));
ok( ($x**2-$y**2)/($x-$y) == $x+$y );
ok( ($x**2-$y**2)/($x-$y) != $x-$y );
ok( ($x**2-$y**2)/($x-$y) <=> '$x+$y' );
The relational equality operator == compares two symbolic expressions and returns TRUE(1) or FALSE(0) accordingly. != produces the opposite result.
Relational operator: eq
Example t/eq.t
#!perl -w
#______________________________________________________________________
# Symbolic algebra: examples: solving.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols;
use Test::Simple tests=>3;
my ($x, $v, $t) = symbols(qw(x v t));
ok( ($v eq $x / $t)->solve(qw(x in terms of v t)) == $v*$t );
ok( ($v eq $x / $t)->solve(qw(x in terms of v t)) != $v+$t );
ok( ($v eq $x / $t)->solve(qw(x in terms of v t)) <=> '$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.
Complex operators
Complex operators: the dot operator: ^
Example t/dot.t
#!perl -w
#______________________________________________________________________
# Symbolic algebra: examples: dot operator. Note the low priority
# of the ^ operator.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols;
use Test::Simple tests=>3;
my ($a, $b, $i) = symbols(qw(a b i));
ok( (($a+$i*$b)^($a-$i*$b)) == $a**2-$b**2 );
ok( (($a+$i*$b)^($a-$i*$b)) != $a**2+$b**2 );
ok( (($a+$i*$b)^($a-$i*$b)) <=> '$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.
Complex operators: the cross operator: x
Example t/cross.t
#!perl -w
#______________________________________________________________________
# Symbolic algebra: examples: cross operator.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols;
use Test::Simple tests=>3;
my ($x, $i) = symbols(qw(x i));
ok( $i*$x x $x == $x**2 );
ok( $i*$x x $x != $x**3 );
ok( $i*$x x $x <=> '$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.
Complex operators: the conjugate operator: ~
Example t/conjugate.t
#!perl -w
#______________________________________________________________________
# Symbolic algebra: examples: dot operator. Note the low priority
# of the ^ operator.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols;
use Test::Simple tests=>3;
my ($x, $y, $i) = symbols(qw(x y i));
ok( ~($x+$i*$y) == $x-$i*$y );
ok( ~($x-$i*$y) == $x+$i*$y );
ok( (($x+$i*$y)^($x-$i*$y)) <=> '$x**2-$y**2' );
The ~ operator returns the complex conjugate of its right hand side.
Complex operators: the modulus operator: abs
Example t/abs.t
#!perl -w
#______________________________________________________________________
# Symbolic algebra: examples: dot operator. Note the low priority
# of the ^ operator.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols;
use Test::Simple tests=>3;
my ($x, $i) = symbols(qw(x i));
ok( abs($x+$i*$x) == sqrt(2*$x**2) );
ok( abs($x+$i*$x) != sqrt(2*$x**3) );
ok( abs($x+$i*$x) <=> 'sqrt(2*$x**2)' );
The abs operator returns the modulus (length) of its right hand side.
Complex operators: the unit operator: !
Example t/unit.t
#!perl -w
#______________________________________________________________________
# Symbolic algebra: examples: unit operator.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols;
use Test::Simple tests=>4;
my ($i) = symbols(qw(i));
ok( !$i == $i );
ok( !$i <=> 'i' );
ok( !($i+1) <=> '1/(sqrt(2))+i/(sqrt(2))' );
ok( !($i-1) <=> '-1/(sqrt(2))+i/(sqrt(2))' );
The ! operator returns a complex number of unit length pointing in the same direction as its right hand side.
Equation Manipulation Operators
Equation Manipulation Operators: Simplify operator: +=
Example t/simplify.t
#!perl -w
#______________________________________________________________________
# Symbolic algebra: examples: simplify.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols;
use Test::Simple tests=>2;
my ($x) = symbols(qw(x));
ok( ($x**8 - 1)/($x-1) == $x+$x**2+$x**3+$x**4+$x**5+$x**6+$x**7+1 );
ok( ($x**8 - 1)/($x-1) <=> '$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.
Equation Manipulation Operators: Solve operator: >
Example t/solve2.t
#!perl -w
#______________________________________________________________________
# Symbolic algebra: examples: simplify.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols;
use Test::Simple tests=>2;
my ($t) = symbols(qw(t));
my $rabbit = 10 + 5 * $t;
my $fox = 7 * $t * $t;
my ($a, $b) = @{($rabbit eq $fox) > $t};
ok( "$a" eq '1/14*sqrt(305)+5/14' );
ok( "$b" eq '-1/14*sqrt(305)+5/14' );
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.
Functions
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".
Trigonometric and Hyperbolic functions
Trigonometric functions
Example t/sinCos2.t
#!perl -w
#______________________________________________________________________
# Symbolic algebra: examples: methods.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols;
use Test::Simple tests=>1;
my ($x, $y) = symbols(qw(x y));
ok( (sin($x)**2 == (1-cos(2*$x))/2) );
The trigonometric functions cos, sin, tan, sec, csc, cot are available, either as exports to the caller's name space, or as methods.
Hyperbolic functions
Example t/tanh.t
#!perl -w
#______________________________________________________________________
# Symbolic algebra: examples: methods.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols hyper=>1;
use Test::Simple tests=>1;
my ($x, $y) = symbols(qw(x y));
ok( tanh($x+$y)==(tanh($x)+tanh($y))/(1+tanh($x)*tanh($y)));
The hyperbolic functions cosh, sinh, tanh, sech, csch, coth are available, either as exports to the caller's name space, or as methods.
Complex functions
Complex functions: re and im
use Math::Algebra::Symbols complex=>1;
Example t/reIm.t
#!perl -w
#______________________________________________________________________
# Symbolic algebra: examples: methods.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols;
use Test::Simple tests=>2;
my ($x, $i) = symbols(qw(x i));
ok( ($i*$x)->re <=> 0 );
ok( ($i*$x)->im <=> '$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.
Complex functions: dot and cross
Example t/dotCross.t
#!perl -w
#______________________________________________________________________
# Symbolic algebra: examples: methods.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols;
use Test::Simple tests=>2;
my $i = symbols(qw(i));
ok( ($i+1)->cross($i-1) <=> 2 );
ok( ($i+1)->dot ($i-1) <=> 0 );
The dot and cross operators are available as functions, either as exports to the caller's name space, or as methods.
Complex functions: conjugate, modulus and unit
Example t/conjugate2.t
#!perl -w
#______________________________________________________________________
# Symbolic algebra: examples: methods.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols;
use Test::Simple tests=>3;
my $i = symbols(qw(i));
ok( ($i+1)->unit <=> '1/(sqrt(2))+i/(sqrt(2))' );
ok( ($i+1)->modulus <=> 'sqrt(2)' );
ok( ($i+1)->conjugate <=> '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.
Methods
Methods for manipulating Equations
Simplifying equations: simplify()
Example t/simplify2.t
#!perl -w
#______________________________________________________________________
# Symbolic algebra: examples: simplify.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols;
use Test::Simple tests=>2;
my ($x) = symbols(qw(x));
my $y = (($x**8 - 1)/($x-1))->simplify(); # Simplify method
my $z += ($x**8 - 1)/($x-1); # Simplify via +=
ok( "$y" eq '$x+$x**2+$x**3+$x**4+$x**5+$x**6+$x**7+1' );
ok( "$z" eq '$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.
Substituting into equations: sub()
Example t/sub.t
#!perl -w
#______________________________________________________________________
# Symbolic algebra: examples: expression substitution for a variable.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols;
use Test::Simple tests=>2;
my ($x, $y) = symbols(qw(x y));
my $e = 1+$x+$x**2/2+$x**3/6+$x**4/24+$x**5/120;
ok( $e->sub(x=>$y**2, z=>2) <=> '$y**2+1/2*$y**4+1/6*$y**6+1/24*$y**8+1/120*$y**10+1' );
ok( $e->sub(x=>1) <=> '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:
my $e2 = $e->sub(x=>1);
$result = eval "$e2";
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
Solving equations: solve()
Example t/solve1.t
#!perl -w
#______________________________________________________________________
# Symbolic algebra: examples: simplify.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols;
use Test::Simple tests=>3;
my ($x, $v, $t) = symbols(qw(x v t));
ok( ($v eq $x / $t)->solve(qw(x in terms of v t)) == $v*$t );
ok( ($v eq $x / $t)->solve(qw(x in terms of v t)) != $v/$t );
ok( ($v eq $x / $t)->solve(qw(x in terms of v t)) <=> '$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.
Example t/solve.t
#!perl -w -I..
#______________________________________________________________________
# Symbolic algebra: quadratic equation.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols;
use Test::Simple tests => 2;
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
ok($a == 2);
ok($b == 3);
If there are multiple solutions, (as in the case of polynomials), solve() returns an array of symbolic expressions containing the solutions.
Methods for performing Calculus
Differentiation: d()
Example t/differentiation.t
#!perl -w -I..
#______________________________________________________________________
# Symbolic algebra.
# PhilipRBrenan@yahoo.com, 2004, Perl License.
#______________________________________________________________________
use Math::Algebra::Symbols;
use Test::More tests => 5;
$x = symbols(qw(x));
ok( sin($x) == sin($x)->d->d->d->d);
ok( cos($x) == cos($x)->d->d->d->d);
ok( exp($x) == exp($x)->d($x)->d('x')->d->d);
ok( (1/$x)->d == -1/$x**2);
ok( exp($x)->d->d->d->d <=> '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.
Example of Equation Solving: the focii of a hyperbola:
use Math::Algebra::Symbols;
my ($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.
EXPORTS
use Math::Algebra::Symbols
symbols=>'S',
trig => 1,
hyper => 1,
complex=> 1;
- trig=>0
-
The default, do not export trigonometric functions.
- trig=>1
-
Export trigonometric functions: tan, sec, csc, cot to the caller's namespace. sin, cos are created by default by overloading the existing Perl sin and cos operators.
- trigonometric
-
Alias of trig
- hyperbolic=>0
-
The default, do not export hyperbolic functions.
- hyper=>1
-
Export hyperbolic functions: sinh, cosh, tanh, sech, csch, coth to the caller's namespace.
- hyperbolic
-
Alias of hyper
- complex=>0
-
The default, do not export complex functions
- complex=>1
-
Export complex functions: conjugate, cross, dot, im, modulus, re, unit to the caller's namespace.
PACKAGES
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 Symbols::Sum and Symbols::Term packages.
Math::Algebra::Symbols::Term
Symbols::Term represents a product term. A product term consists of the number 1, optionally multiplied by:
- Variables
-
any number of variables raised to integer powers,
- Coefficient
-
An integer coefficient optionally divided by a positive integer divisor, both represented as BigInts if necessary.
- Sqrt
-
The sqrt of of any symbolic expression representable by the Symbols package, including minus one: represented as i.
- Reciprocal
-
The multiplicative inverse of any symbolic expression representable by the Symbols package: i.e. a SymbolsTerm may be divided by any symbolic expression representable by the Symbols package.
- Exp
-
The number e raised to the power of any symbolic expression representable by the Symbols package.
- Log
-
The logarithm to base e of any symbolic expression representable by the Symbols package.
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 Symbols::Sum is required.
Math::Algebra::Symbols::Sum
Symbols::Sum represents a sum of product terms supplied by Symbols::Term and thus behaves as a polynomial. Operations such as equation solving and differentiation are applied at this level.
The main benefit of programming Symbols::Term and Symbols::Sum 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.
Math::Algebra::Symbols
Packaging the user functionality alone and separately in package Symbols allows the internal functions to be conveniently hidden from user scripts.
AUTHOR
Philip R Brenan at philiprbrenan@yahoo.com
Credits
Author
philiprbrenan@yahoo.com
Copyright
philiprbrenan@yahoo.com, 2004
License
Perl License.