File Coverage

blib/lib/Math/Symbolic/Custom/DefaultMods.pm
Criterion Covered Total %
statement 111 120 92.5
branch 68 92 73.9
condition 26 38 68.4
subroutine 14 14 100.0
pod 4 4 100.0
total 223 268 83.2


line stmt bran cond sub pod time code
1              
2             =encoding utf8
3              
4             =head1 NAME
5              
6             Math::Symbolic::Custom::DefaultMods - Default Math::Symbolic transformations
7              
8             =head1 SYNOPSIS
9              
10             use Math::Symbolic;
11              
12             =head1 DESCRIPTION
13              
14             This is a class of default transformations for Math::Symbolic trees. Likewise,
15             Math::Symbolic::Custom::DefaultTests defines default tree testing
16             routines.
17             For details on how the custom method delegation model works, please have
18             a look at the Math::Symbolic::Custom and Math::Symbolic::Custom::Base
19             classes.
20              
21             =head2 EXPORT
22              
23             Please see the docs for Math::Symbolic::Custom::Base for details, but
24             you should not try to use the standard Exporter semantics with this
25             class.
26              
27             =head1 SUBROUTINES
28              
29             =cut
30              
31             package Math::Symbolic::Custom::DefaultMods;
32              
33 23     23   530 use 5.006;
  23         80  
  23         990  
34 23     23   137 use strict;
  23         46  
  23         795  
35 23     23   118 use warnings;
  23         48  
  23         682  
36 23     23   138 no warnings 'recursion';
  23         54  
  23         1183  
37              
38             our $VERSION = '0.612';
39              
40 23     23   133 use Math::Symbolic::Custom::Base;
  23         41  
  23         1532  
41 23     23   555 BEGIN { *import = \&Math::Symbolic::Custom::Base::aggregate_import }
42              
43 23     23   128 use Math::Symbolic::ExportConstants qw/:all/;
  23         45  
  23         5940  
44              
45 23     23   139 use Carp;
  23         195  
  23         48551  
46              
47             # Class Data: Special variable required by Math::Symbolic::Custom
48             # importing/exporting functionality.
49             # All subroutines that are to be exported to the Math::Symbolic::Custom
50             # namespace should be listed here.
51              
52             our $Aggregate_Export = [
53             qw/
54             apply_derivatives
55             apply_constant_fold
56             mod_add_constant
57             mod_multiply_constant
58             /
59             ];
60              
61             =head2 apply_derivatives()
62              
63             Never modifies the tree in-place, but returns a modified copy of the
64             original tree instead.
65              
66             Applied to variables and constants, this method just clones.
67              
68             Applied to operators and if the operator is a derivative, this applies
69             the derivative to the derivative's first operand.
70              
71             Regardless what kind of operator this is called on, apply_derivatives
72             will be applied recursively on its operands.
73              
74             If the first parameter to this function is an integer, at maximum that
75             number of derivatives are applied (from top down the tree if possible).
76              
77             =cut
78              
79             sub apply_derivatives {
80 93     93 1 168 my $tree = shift;
81 93   50     480 my $n = shift || -1;
82              
83             return $tree->descend(
84             in_place => 0,
85             before => sub {
86 1523     1523   1934 my $tree = shift;
87 1523         4173 my $ttype = $tree->term_type();
88 1523 100 100     6253 if ( $ttype == T_CONSTANT || $ttype == T_VARIABLE ) {
    50          
89 695         2180 return undef;
90             }
91             elsif ( $ttype == T_OPERATOR ) {
92 828         993 my $max_derivatives = $n;
93 828         2165 my $type = $tree->type();
94              
95 828   100     4730 while (
      33        
96             $n
97             && ( $type == U_P_DERIVATIVE
98             or $type == U_T_DERIVATIVE )
99             )
100             {
101 74         157 my $op = $Math::Symbolic::Operator::Op_Types[$type];
102              
103 74         139 my $operands = $tree->{operands};
104 74         148 my $application = $op->{application};
105              
106 74 100 100     293 if ( $type == U_T_DERIVATIVE
107             and $operands->[0]->term_type() == T_VARIABLE )
108             {
109 4         17 my @sig = $operands->[0]->signature();
110              
111 4         19 my $name = $operands->[1]->name();
112              
113 4 50 33     8 if (
      33        
114 8         45 ( grep { $_ eq $name } @sig ) > 0
115             and not(@sig == 1
116             and $sig[0] eq $name )
117             )
118             {
119 4         16 return undef;
120             }
121             }
122 70         629 $tree->replace( $application->(@$operands) );
123             return undef
124 70 100       319 unless $tree->term_type() == T_OPERATOR;
125              
126 69         236 $type = $tree->type();
127 69         1276 $n--;
128             }
129 823         3458 return ();
130             }
131             else {
132 0         0 croak "apply_derivatives called on invalid " . "tree type.";
133             }
134              
135 0         0 die "Sanity check in apply_derivatives() should not "
136             . "be reached.";
137             },
138 93         899 );
139             }
140              
141             =head2 apply_constant_fold()
142              
143             Does not modify the tree in-place by default, but returns a modified copy
144             of the original tree instead. If the first argument is true, the tree will
145             not be cloned. If it is false or not existant, the tree will be cloned.
146              
147             Applied to variables and constants, this method just clones.
148              
149             Applied to operators, all tree segments that contain constants and
150             operators only will be replaced with Constant objects.
151              
152             =cut
153              
154             sub apply_constant_fold {
155 5     5 1 11 my $tree = shift;
156 5         8 my $in_place = shift;
157              
158             return $tree->descend(
159             in_place => $in_place,
160             before => sub {
161 11     11   18 my $tree = shift;
162 11 100       70 if ( $tree->is_simple_constant() ) {
163 4 50       11 $tree->replace( $tree->apply() )
164             unless $tree->term_type() == T_CONSTANT;
165 4         18 return undef;
166             }
167              
168 7 100       20 return undef if $tree->term_type() == T_VARIABLE;
169 3         19 return { in_place => 1, descend_into => [] };
170             }
171 5         54 );
172              
173 0         0 return $tree;
174             }
175              
176             =head2 mod_add_constant
177              
178             Given a constant (object or number) as argument, this method tries
179             hard to fold it into an existing constant of the object this is called
180             on is already a sum or a difference.
181              
182             Basically, this is the same as C<$tree + $constant> but does some
183             simplification.
184              
185             =cut
186              
187             sub mod_add_constant {
188 32     32 1 64 my $tree = shift;
189 32         83 my $constant = shift;
190              
191 32 100       129 return $tree if not $constant;
192 31 100       117 $constant = $constant->value() if ref($constant);
193            
194 31         122 my $tt = $tree->term_type();
195 31 50       195 if ($tt == T_CONSTANT) {
    50          
196 0         0 return Math::Symbolic::Constant->new($tree->{value}+$constant);
197             }
198             elsif ($tt == T_OPERATOR) {
199 31         103 my $type = $tree->type();
200              
201 31 100 100     159 if ($type == B_SUM || $type == B_DIFFERENCE) {
202 16         35 my $ops = $tree->{operands};
203 16         19 my $const_op;
204 16 100       126 if ($ops->[0]->is_simple_constant()) {
    100          
205 4         12 $const_op = 0;
206             } elsif ($ops->[1]->is_simple_constant()) {
207 5         12 $const_op = 1;
208             }
209 16 100       60 if (defined $const_op) {
210 9         47 my $value = $ops->[$const_op]->value();
211 9         66 my $other = $ops->[($const_op+1)%2];
212              
213 9 100       46 if ($const_op == 0) {
214 4         15 $value += $constant;
215             }
216             else { # second
217 5 50       68 $value = $type==B_SUM ? $value + $constant : $value - $constant;
218             }
219              
220 9 100       32 if ($value == 0) {
221 4 50 66     43 return $other if $const_op == 1 or $type == B_SUM;
222 0         0 return Math::Symbolic::Constant->new(-$other->{value});
223             }
224 5 50       39 return Math::Symbolic::Operator->new(
    100          
225             ($type == B_DIFFERENCE ? '-' : '+'), # op-type
226             $const_op == 0 # order of ops
227             ?($value, $other)
228             :($other, $value)
229             );
230             }
231 7 50       28 if ($ops->[1]->term_type() == T_OPERATOR) {
232 7         27 my $otype = $ops->[1]->type();
233 7 100 66     46 if ($otype == B_SUM || $otype == B_DIFFERENCE) {
234 4 50       37 return Math::Symbolic::Operator->new(
235             ($type == B_SUM ? '+' : '-'),
236             $ops->[0],
237             $ops->[1]->mod_add_constant($constant)
238             );
239             }
240             }
241             else {
242 0 0       0 return Math::Symbolic::Operator->new(
243             ($type == B_SUM ? '+' : '-'),
244             $ops->[0]->mod_add_constant($constant),
245             $ops->[1],
246             );
247             }
248             }
249             }
250              
251             # fallback: variable, didn't apply, etc.
252 18         78 return Math::Symbolic::Operator->new(
253             '+', Math::Symbolic::Constant->new($constant), $tree
254             );
255             }
256              
257              
258             =head2 mod_multiply_constant
259              
260             Given a constant (object or number) as argument, this method tries
261             hard to fold it into an existing constant of the object this is called
262             on is already a product or a division.
263              
264             Basically, this is the same as C<$tree * $constant> but does some
265             simplification.
266              
267             =cut
268              
269             sub mod_multiply_constant {
270 16     16 1 36 my $tree = shift;
271 16         28 my $constant = shift;
272              
273 16 50       53 return $tree if not defined $constant;
274 16 50       46 $constant = $constant->value() if ref($constant);
275 16 100       52 return $tree if $constant == 1;
276 15 100       50 return Math::Symbolic::Constant->zero() if $constant == 0;
277            
278 14         67 my $tt = $tree->term_type();
279 14 50       64 if ($tt == T_CONSTANT) {
    50          
280 0         0 return Math::Symbolic::Constant->new($tree->{value}*$constant);
281             }
282             elsif ($tt == T_OPERATOR) {
283 14         51 my $type = $tree->type();
284              
285 14 50 66     57 if ($type == B_PRODUCT || $type == B_DIVISION) {
286 14         30 my $ops = $tree->{operands};
287 14         17 my $const_op;
288 14 100       99 if ($ops->[0]->is_simple_constant()) {
    100          
289 3         7 $const_op = 0;
290             } elsif ($ops->[1]->is_simple_constant()) {
291 5         14 $const_op = 1;
292             }
293 14 100       51 if (defined $const_op) {
294 8         43 my $value = $ops->[$const_op]->value();
295 8         36 my $other = $ops->[($const_op+1)%2];
296              
297 8 100       26 if ($const_op == 0) {
298 3         12 $value *= $constant;
299             }
300             else { # second
301 5 50       34 $value = $type==B_PRODUCT ? $value * $constant : $value / $constant;
302             }
303              
304 8 100       33 if ($value == 1) {
305 3 50 66     27 return $other if $const_op == 1 or $type == B_PRODUCT;
306 0         0 return Math::Symbolic::Constant->new(1/$other->{value});
307             }
308 5 50       48 return Math::Symbolic::Operator->new(
    100          
309             ($type == B_DIVISION ? '/' : '*'), # op-type
310             $const_op == 0 # order of ops
311             ?($value, $other)
312             :($other, $value)
313             );
314             }
315 6 50       22 if ($ops->[1]->term_type() == T_OPERATOR) {
316 6         18 my $otype = $ops->[1]->type();
317 6 100 66     34 if ($otype == B_PRODUCT || $otype == B_DIVISION) {
318 4 50       35 return Math::Symbolic::Operator->new(
319             ($type == B_PRODUCT ? '*' : '/'),
320             $ops->[0],
321             $ops->[1]->mod_multiply_constant($constant)
322             );
323             }
324             }
325             else {
326 0 0       0 return Math::Symbolic::Operator->new(
327             ($type == B_PRODUCT ? '*' : '('),
328             $ops->[0]->mod_multiply_constant($constant),
329             $ops->[1],
330             );
331             }
332             }
333             }
334              
335             # fallback: variable, didn't apply, etc.
336 2         9 return Math::Symbolic::Operator->new(
337             '*', Math::Symbolic::Constant->new($constant), $tree
338             );
339             }
340              
341             =begin comment
342              
343             warn "mod_join_simple to be implemented in DefaultMods!";
344             sub mod_join_simple {
345             my $o1 = shift;
346             my $o2 = shift;
347             my $type = shift;
348              
349             if ( $type == B_PRODUCT ) {
350             return undef
351             unless Math::Symbolic::Custom::is_identical_base( $o1, $o2 );
352              
353             my $tt1 = $o1->term_type();
354             my $tt2 = $o2->term_type();
355             my ( $base, $exp1 ) =
356             ( $tt1 == T_OPERATOR and $o1->type() == B_EXP )
357             ? ( $o1->op1(), $o1->op2() )
358             : ( $o1, Math::Symbolic::Constant->one() );
359              
360             my $exp2 =
361             ( $tt2 == T_OPERATOR and $o2->type() == B_EXP )
362             ? $o2->op2()
363             : Math::Symbolic::Constant->one();
364              
365             return Math::Symbolic::Operator->new( '^', $base,
366             Math::Symbolic::Operator->new( '+', $exp1, $exp2 )->simplify() );
367             }
368             }
369              
370             =end comment
371              
372             =cut
373              
374             1;
375             __END__