use
5.006;
no
warnings
'recursion'
;
our
@ISA
=
qw(Exporter)
;
our
%EXPORT_TAGS
= (
'all'
=> [
qw(
&total_derivative
&partial_derivative
)
]
);
our
@EXPORT_OK
= ( @{
$EXPORT_TAGS
{
'all'
} } );
our
@EXPORT
=
qw()
;
our
$VERSION
=
'0.613'
;
our
%Rules
= (
'each operand'
=> \
&_each_operand
,
'product rule'
=> \
&_product_rule
,
'quotient rule'
=> \
&_quotient_rule
,
'logarithmic chain rule after ln'
=> \
&_logarithmic_chain_rule_after_ln
,
'logarithmic chain rule'
=> \
&_logarithmic_chain_rule
,
'derivative commutation'
=> \
&_derivative_commutation
,
'trigonometric derivatives'
=> \
&_trigonometric_derivatives
,
'inverse trigonometric derivatives'
=> \
&_inverse_trigonometric_derivatives
,
'inverse atan2'
=> \
&_inverse_atan2
,
);
our
$Partial_Sub
;
our
$Total_Sub
;
our
@Constant_Simplify
= (
sub
{
my
$tree
=
shift
;
my
(
$op1
,
$op2
) = @{
$tree
->{operands}};
my
(
$t1
,
$t2
) = (
$op1
->term_type(),
$op2
->term_type());
if
(
$t1
== T_CONSTANT) {
return
$op2
if
$op1
->{value} == 0;
if
(
$t2
== T_CONSTANT) {
return
Math::Symbolic::Constant->new(
$op1
->{value} +
$op2
->{value});
}
}
elsif
(
$t2
== T_CONSTANT) {
return
$op1
if
$op2
->{value} == 0;
}
return
$tree
;
},
sub
{
my
$tree
=
shift
;
my
(
$op1
,
$op2
) = @{
$tree
->{operands}};
my
(
$t1
,
$t2
) = (
$op1
->term_type(),
$op2
->term_type());
if
(
$t1
== T_CONSTANT) {
$op2
*= -1,
return
$op2
if
$op1
->{value} == 0;
if
(
$t2
== T_CONSTANT) {
return
Math::Symbolic::Constant->new(
$op1
->{value} -
$op2
->{value});
}
}
elsif
(
$t2
== T_CONSTANT) {
return
$op1
if
$op2
->{value} == 0;
$op2
->{value} *= -1;
return
Math::Symbolic::Operator->new(
'+'
,
$op1
,
$op2
);
}
return
$tree
;
},
undef
,
undef
,
sub
{
my
$tree
=
shift
;
my
$op
=
$tree
->{operands}[0];
if
(
$op
->term_type == T_CONSTANT) {
return
Math::Symbolic::Constant->new(-
$op
->{value});
}
return
$tree
;
},
);
sub
_each_operand {
my
(
$tree
,
$var
,
$cloned
,
$d_sub
) =
@_
;
foreach
( @{
$tree
->{operands} } ) {
$_
=
$d_sub
->(
$_
,
$var
, 1 );
}
my
$type
=
$tree
->type();
my
$simplifier
=
$Constant_Simplify
[
$type
];
return
$simplifier
->(
$tree
)
if
$simplifier
;
return
$tree
;
}
sub
_product_rule {
my
(
$tree
,
$var
,
$cloned
,
$d_sub
) =
@_
;
my
$ops
=
$tree
->{operands};
my
(
$o1
,
$o2
) =
@$ops
;
my
(
$to1
,
$to2
) = (
$o1
->term_type(),
$o2
->term_type());
if
(
$to1
== T_CONSTANT) {
return
Math::Symbolic::Constant->zero()
if
$o1
->{value} == 0;
my
$deriv
=
$d_sub
->(
$o2
,
$var
, 0 );
return
$deriv
if
$o1
->{value} == 0;
return
Math::Symbolic::Constant->new(
$deriv
->{value}
*$o1
->{value})
if
$deriv
->term_type == T_CONSTANT;
}
if
(
$to2
== T_CONSTANT) {
return
Math::Symbolic::Constant->zero()
if
$o2
->{value} == 0;
my
$deriv
=
$d_sub
->(
$o1
,
$var
, 0 );
return
$deriv
if
$o2
->{value} == 0;
return
Math::Symbolic::Constant->new(
$deriv
->{value}
*$o2
->{value})
if
$deriv
->term_type == T_CONSTANT;
}
my
$do1
=
$d_sub
->(
$o1
,
$var
, 0 );
my
$do2
=
$d_sub
->(
$o2
,
$var
, 0 );
my
(
$tdo1
,
$tdo2
) = (
$do1
->term_type(),
$do2
->term_type());
my
(
$m1
,
$m2
);
if
(
$tdo1
== T_CONSTANT) {
if
(
$to2
== T_CONSTANT) {
$m1
=
$do1
->new(
$o2
->{value} *
$do1
->{value});
}
elsif
(
$do1
->{value} == 0) {
$m1
=
$do1
->zero();
}
elsif
(
$do1
->{value} == 1) {
$m1
=
$o2
;
}
else
{
$m1
=
$do1
*$o2
;
}
}
else
{
$m1
=
$o2
*$do1
;
}
if
(
$tdo2
== T_CONSTANT) {
if
(
$to1
== T_CONSTANT) {
$m2
=
$do2
->new(
$o1
->{value} *
$do2
->{value});
}
elsif
(
$do2
->{value} == 0) {
$m2
=
$do2
->zero();
}
elsif
(
$do2
->{value} == 1) {
$m2
=
$o1
;
}
else
{
$m2
=
$do2
*$o1
;
}
}
else
{
$m2
=
$o1
*$do2
;
}
if
(
$m1
->term_type == T_CONSTANT) {
return
$m2
if
$m1
->{value} == 0;
if
(
$m2
->term_type == T_CONSTANT) {
return
$m2
->new(
$m1
->{value}
*$m2
->{value});
}
}
elsif
(
$m2
->term_type == T_CONSTANT) {
return
$m1
if
$m2
->{value} == 0;
}
return
Math::Symbolic::Operator->new(
'+'
,
$m1
,
$m2
);
}
sub
_quotient_rule {
my
(
$tree
,
$var
,
$cloned
,
$d_sub
) =
@_
;
my
(
$op1
,
$op2
) = @{
$tree
->{operands}};
my
(
$do1
,
$do2
);
if
(
$op2
->is_simple_constant()) {
$do1
=
$d_sub
->(
$op1
,
$var
, 0 );
my
$val
=
$op2
->value();
if
(
$val
== 0) {
return
$tree
->new(
'/'
,
$do1
,
$op2
->new());
}
elsif
(
$val
== 1) {
return
$do1
;
}
return
$tree
->new(
'*'
, Math::Symbolic::Constant->new(1/
$val
),
$do1
);
}
elsif
(
$op1
->is_simple_constant()) {
$do2
=
$d_sub
->(
$op2
,
$var
, 0 );
my
$val
=
$op1
->value();
if
(
$val
== 0) {
return
Math::Symbolic::Constant->zero();
}
my
$tdo2
=
$do2
->term_type();
if
(
$tdo2
== T_CONSTANT) {
return
$do2
->zero()
if
$do2
->{value} == 0;
return
$tree
->new(
'/'
,
$do2
->new(-1.
*$val
*$do2
->{value}),
$tree
->new(
'^'
,
$op2
, 2)
);
}
else
{
return
$tree
->new(
'*'
, Math::Symbolic::Constant->new(-1
*$val
),
$tree
->new(
'/'
,
$do2
,
$tree
->new(
'^'
,
$op2
, Math::Symbolic::Constant->new(2)))
)
}
}
$do1
=
$d_sub
->(
$op1
,
$var
, 0 )
if
not
$do1
;
$do2
=
$d_sub
->(
$op2
,
$var
, 0 )
if
not
$do2
;
my
$m1
= Math::Symbolic::Operator->new(
'*'
,
$do1
,
$op2
);
my
$m2
= Math::Symbolic::Operator->new(
'*'
,
$op1
,
$do2
);
if
(
$do1
->is_zero()) {
$m1
=
undef
;
}
elsif
(
$do1
->is_one()) {
$m1
=
$op2
->new();
}
if
(
$do2
->is_zero()) {
$m2
=
undef
;
}
elsif
(
$do2
->is_one()) {
$m2
=
$op1
->new();
}
my
$upper
;
if
(not
defined
$m1
) {
return
Math::Symbolic::Constant->zero()
if
not
defined
$m2
;
$upper
=
$tree
->new(
'neg'
,
$m2
);
}
elsif
(not
defined
$m2
) {
return
$tree
->new(
'/'
,
$do1
,
$op2
);
}
my
$m3
=
$tree
->new(
'^'
,
$op2
, Math::Symbolic::Constant->new(2));
if
(not
defined
$upper
) {
$upper
= Math::Symbolic::Operator->new(
'-'
,
$m1
,
$m2
);
}
return
Math::Symbolic::Operator->new(
'/'
,
$upper
,
$m3
);
}
sub
_logarithmic_chain_rule_after_ln {
my
(
$tree
,
$var
,
$cloned
,
$d_sub
) =
@_
;
my
(
$u
,
$v
) = @{
$tree
->{operands}};
if
(
$v
->term_type() == T_CONSTANT) {
if
(
$u
->term_type() == T_VARIABLE) {
my
$d
=
$d_sub
->(
$u
,
$var
, 0);
my
$dtt
=
$d
->term_type();
if
(
$dtt
== T_CONSTANT) {
return
Math::Symbolic::Constant->zero()
if
$d
->{value} == 0;
return
Math::Symbolic::Constant->one()
if
$v
->{value} == 1;
return
$tree
->new(
'*'
,
$v
->new(),
$u
->new())
if
$v
->{value} == 2;
return
$tree
->new(
'*'
,
$v
->new(),
$tree
->new(
'^'
,
$u
->new(),
$v
->new(
$v
->{value}-1)));
}
}
return
Math::Symbolic::Operator->new(
'*'
,
Math::Symbolic::Operator->new(
'*'
,
$v
->new(),
$tree
),
Math::Symbolic::Operator->new(
'/'
,
$d_sub
->(
$u
,
$var
, 0),
$u
->new()
)
);
}
my
$e
= Math::Symbolic::Constant->euler();
my
$ln
= Math::Symbolic::Operator->new(
'log'
,
$e
,
$u
);
my
$mul1
=
$ln
->new(
'*'
,
$v
,
$ln
);
my
$dmul
=
$d_sub
->(
$mul1
,
$var
, 0 );
$tree
=
$ln
->new(
'*'
,
$tree
,
$dmul
);
return
$tree
;
}
sub
_logarithmic_chain_rule {
my
(
$tree
,
$var
,
$cloned
,
$d_sub
) =
@_
;
my
(
$a
,
$y
) = @{
$tree
->{operands}};
my
$dy
=
$d_sub
->(
$y
,
$var
, 0 );
if
(
$a
->term_type() == T_CONSTANT and
$a
->{special} eq
'euler'
) {
return
Math::Symbolic::Operator->new(
'/'
,
$dy
,
$y
);
}
my
$e
= Math::Symbolic::Constant->euler();
my
$ln
= Math::Symbolic::Operator->new(
'log'
,
$e
,
$a
);
my
$mul1
=
$ln
->new(
'*'
,
$ln
,
$y
->new() );
$tree
=
$ln
->new(
'/'
,
$dy
,
$mul1
);
return
$tree
;
}
sub
_derivative_commutation {
my
(
$tree
,
$var
,
$cloned
,
$d_sub
) =
@_
;
$tree
->{operands}[0] =
$d_sub
->(
$tree
->{operands}[0],
$var
, 0 );
return
$tree
;
}
sub
_trigonometric_derivatives {
my
(
$tree
,
$var
,
$cloned
,
$d_sub
) =
@_
;
my
$op
= Math::Symbolic::Operator->new();
my
$d_inner
=
$d_sub
->(
$tree
->{operands}[0],
$var
, 0 );
my
$trig
;
my
$type
=
$tree
->type();
if
(
$type
== U_SINE ) {
$trig
=
$op
->new(
'cos'
,
$tree
->{operands}[0] );
}
elsif
(
$type
== U_COSINE ) {
$trig
=
$op
->new(
'neg'
,
$op
->new(
'sin'
,
$tree
->{operands}[0] ) );
}
elsif
(
$type
== U_SINE_H ) {
$trig
=
$op
->new(
'cosh'
,
$tree
->{operands}[0] );
}
elsif
(
$type
== U_COSINE_H ) {
$trig
=
$op
->new(
'sinh'
,
$tree
->{operands}[0] );
}
elsif
(
$type
== U_TANGENT or
$type
== U_COTANGENT ) {
$trig
=
$op
->new(
'/'
,
Math::Symbolic::Constant->one(),
$op
->new(
'^'
,
$op
->new(
'cos'
,
$tree
->op1() ),
Math::Symbolic::Constant->new(2)
)
);
$trig
=
$op
->new(
'neg'
,
$trig
)
if
$type
== U_COTANGENT;
}
else
{
die
"Trigonometric derivative applied to invalid operator."
;
}
if
(
$d_inner
->term_type() == T_CONSTANT) {
my
$spec
=
$d_inner
->special();
if
(
$spec
eq
'zero'
) {
return
$d_inner
;
}
elsif
(
$spec
eq
'one'
) {
return
$trig
;
}
}
return
$op
->new(
'*'
,
$d_inner
,
$trig
);
}
sub
_inverse_trigonometric_derivatives {
my
(
$tree
,
$var
,
$cloned
,
$d_sub
) =
@_
;
my
$op
= Math::Symbolic::Operator->new();
my
$d_inner
=
$d_sub
->(
$tree
->{operands}[0],
$var
, 0 );
my
$trig
;
my
$type
=
$tree
->type();
if
(
$type
== U_ARCSINE or
$type
== U_ARCCOSINE ) {
my
$one
=
$type
== U_ARCSINE
? Math::Symbolic::Constant->one()
: Math::Symbolic::Constant->new(-1);
$trig
=
$op
->new(
'/'
,
$one
,
$op
->new(
'-'
,
$one
->new(1),
$op
->new(
'^'
,
$tree
->op1(),
$one
->new(2) ) )
);
}
elsif
(
$type
== U_ARCTANGENT
or
$type
== U_ARCCOTANGENT )
{
my
$one
=
$type
== U_ARCTANGENT
? Math::Symbolic::Constant->one()
: Math::Symbolic::Constant->new(-1);
$trig
=
$op
->new(
'/'
,
$one
,
$op
->new(
'+'
,
$one
->new(1),
$op
->new(
'^'
,
$tree
->op1(),
$one
->new(2) ) )
);
}
elsif
(
$type
== U_AREASINE_H
or
$type
== U_AREACOSINE_H )
{
my
$one
= Math::Symbolic::Constant->one();
$trig
=
$op
->new(
'/'
,
$one
,
$op
->new(
'^'
,
$op
->new(
(
$tree
->type() == U_AREASINE_H ?
'+'
:
'-'
),
$op
->new(
'^'
,
$tree
->op1(),
$one
->new(2) ),
$one
),
$one
->new(0.5)
)
);
}
else
{
die
"Inverse trig. derivative applied to invalid operator."
;
}
if
(
$d_inner
->term_type() == T_CONSTANT) {
my
$spec
=
$d_inner
->special();
if
(
$spec
eq
'zero'
) {
return
$d_inner
;
}
elsif
(
$spec
eq
'one'
) {
return
$trig
;
}
}
return
$op
->new(
'*'
,
$d_inner
,
$trig
);
}
sub
_inverse_atan2 {
my
(
$tree
,
$var
,
$cloned
,
$d_sub
) =
@_
;
my
(
$op1
,
$op2
) = @{
$tree
->{operands}};
my
$inner
=
$d_sub
->(
$op1
->new()/
$op2
->new(),
$var
, 0 );
my
$two
= Math::Symbolic::Constant->new(2);
my
$op
= Math::Symbolic::Operator->new(
'+'
,
$two
,
$two
);
my
$result
=
$op
->new(
'*'
,
$op
->new(
'/'
,
$op
->new(
'^'
,
$op2
->new(),
$two
->new()),
$op
->new(
'+'
,
$op
->new(
'^'
,
$op2
->new(),
$two
->new()),
$op
->new(
'^'
,
$op1
->new(),
$two
->new())
)
),
$inner
);
return
$result
;
}
sub
partial_derivative {
my
$tree
=
shift
;
my
$var
=
shift
;
defined
$var
or
die
"Cannot derive using undefined variable."
;
if
(
ref
(
$var
) eq
''
) {
$var
= Math::Symbolic::parse_from_string(
$var
);
croak
"2nd argument to partial_derivative must be variable."
if
(
ref
(
$var
) ne
'Math::Symbolic::Variable'
);
}
else
{
croak
"2nd argument to partial_derivative must be variable."
if
(
ref
(
$var
) ne
'Math::Symbolic::Variable'
);
}
my
$cloned
=
shift
;
if
( not
$cloned
) {
$tree
=
$tree
->new();
$cloned
= 1;
}
if
(
$tree
->term_type() == T_OPERATOR ) {
my
$rulename
=
$Math::Symbolic::Operator::Op_Types
[
$tree
->type() ]->{derive};
my
$subref
=
$Rules
{
$rulename
};
die
"Cannot derive using rule '$rulename'."
unless
defined
$subref
;
$tree
=
$subref
->(
$tree
,
$var
,
$cloned
,
$Partial_Sub
);
}
elsif
(
$tree
->term_type() == T_CONSTANT ) {
$tree
= Math::Symbolic::Constant->zero();
}
elsif
(
$tree
->term_type() == T_VARIABLE ) {
if
(
$tree
->name() eq
$var
->name() ) {
$tree
= Math::Symbolic::Constant->one;
}
else
{
$tree
= Math::Symbolic::Constant->zero;
}
}
else
{
die
"Cannot apply partial derivative to anything but a tree."
;
}
return
$tree
;
}
sub
total_derivative {
my
$tree
=
shift
;
my
$var
=
shift
;
defined
$var
or
die
"Cannot derive using undefined variable."
;
if
(
ref
(
$var
) eq
''
) {
$var
= Math::Symbolic::parse_from_string(
$var
);
croak
"Second argument to total_derivative must be variable."
if
(
ref
(
$var
) ne
'Math::Symbolic::Variable'
);
}
else
{
croak
"Second argument to total_derivative must be variable."
if
(
ref
(
$var
) ne
'Math::Symbolic::Variable'
);
}
my
$cloned
=
shift
;
if
( not
$cloned
) {
$tree
=
$tree
->new();
$cloned
= 1;
}
if
(
$tree
->term_type() == T_OPERATOR ) {
my
$var_name
=
$var
->name();
my
@tree_sig
=
$tree
->signature();
if
( (
grep
{
$_
eq
$var_name
}
@tree_sig
) > 0 ) {
my
$rulename
=
$Math::Symbolic::Operator::Op_Types
[
$tree
->type() ]->{derive};
my
$subref
=
$Rules
{
$rulename
};
die
"Cannot derive using rule '$rulename'."
unless
defined
$subref
;
$tree
=
$subref
->(
$tree
,
$var
,
$cloned
,
$Total_Sub
);
}
else
{
$tree
= Math::Symbolic::Constant->zero();
}
}
elsif
(
$tree
->term_type() == T_CONSTANT ) {
$tree
= Math::Symbolic::Constant->zero();
}
elsif
(
$tree
->term_type() == T_VARIABLE ) {
my
$name
=
$tree
->name();
my
$var_name
=
$var
->name();
if
(
$name
eq
$var_name
) {
$tree
= Math::Symbolic::Constant->one;
}
else
{
my
@tree_sig
=
$tree
->signature();
my
$is_dependent
;
foreach
my
$ident
(
@tree_sig
) {
if
(
$ident
eq
$var_name
) {
$is_dependent
= 1;
last
;
}
}
if
(
$is_dependent
) {
$tree
=
Math::Symbolic::Operator->new(
'total_derivative'
,
$tree
,
$var
);
}
else
{
$tree
= Math::Symbolic::Constant->zero;
}
}
}
else
{
die
"Cannot apply total derivative to anything but a tree."
;
}
return
$tree
;
}
$Partial_Sub
= \
&partial_derivative
;
$Total_Sub
= \
&total_derivative
;
1;