File Coverage

blib/lib/Math/Symbolic/Custom/Collect.pm
Criterion Covered Total %
statement 840 1066 78.8
branch 437 602 72.5
condition 384 546 70.3
subroutine 28 35 80.0
pod 6 26 23.0
total 1695 2275 74.5


line stmt bran cond sub pod time code
1             package Math::Symbolic::Custom::Collect;
2              
3 3     3   488664 use 5.006;
  3         20  
4 3     3   20 use strict;
  3         6  
  3         111  
5 3     3   17 use warnings;
  3         6  
  3         209  
6 3     3   14 no warnings 'recursion';
  3         24  
  3         338  
7              
8             =pod
9              
10             =encoding utf8
11              
12             =head1 NAME
13              
14             Math::Symbolic::Custom::Collect - Collect up Math::Symbolic expressions
15              
16             =head1 VERSION
17              
18             Version 0.4
19              
20             =cut
21              
22             require Exporter;
23              
24             our @ISA = qw(Exporter);
25             our @EXPORT = qw/symbolic_complex/;
26              
27             our $VERSION = '0.4';
28              
29 3     3   820 use Math::Symbolic qw(:all);
  3         172366  
  3         688  
30 3     3   23 use Math::Symbolic::Derivative qw//;
  3         6  
  3         83  
31 3     3   12 use Math::Symbolic::Custom::Base;
  3         5  
  3         142  
32              
33 3     3   281 BEGIN {*import = \&Math::Symbolic::Custom::Base::aggregate_import}
34              
35             our $Aggregate_Export = [qw/to_collected to_terms to_derivative test_complex to_complex_conjugate/];
36             Math::Symbolic::Custom::Collect->export_to_level(1, undef, 'symbolic_complex');
37              
38 3     3   36 use Carp;
  3         18  
  3         66524  
39              
40             =head1 DESCRIPTION
41              
42             Provides some methods for working with Math::Symbolic expressions through the Math::Symbolic module extension class.
43              
44             =head1 EXAMPLES
45              
46             use strict;
47             use Math::Symbolic qw(:all);
48             use Math::Symbolic::Custom::Collect;
49              
50             my $t1 = "0.125";
51             print "Output: ", parse_from_string($t1)->to_collected()->to_string(), "\n";
52             # Output: 1 / 8
53              
54             my $t2 = "25/100";
55             print "Output: ", parse_from_string($t2)->to_collected()->to_string(), "\n";
56             # Output: 1 / 4
57              
58             my $t3 = "10^100 + 1 - 10^100";
59             print "Output: ", parse_from_string($t3)->to_collected()->to_string(), "\n";
60             # Output: 1
61              
62             my $t4 = "((1/4)+(1/2))*3*x";
63             print "Output: ", parse_from_string($t4)->to_collected()->to_string(), "\n";
64             # Output: (9 * x) / 4
65              
66             my $t5 = "1/(1-(1/x))";
67             print "Output: ", parse_from_string($t5)->to_collected()->to_string(), "\n";
68             # Output: x / (x - 1)
69              
70             my $t6 = "sin(x^2+y)*sin(y+x^2)";
71             print "Output: ", parse_from_string($t6)->to_collected()->to_string(), "\n";
72             # Output: (sin((x ^ 2) + y)) ^ 2
73              
74             my $t7 = "x + x^2 + 3*x^3 + 2*x - x^2";
75             print "Output: ", parse_from_string($t7)->to_collected()->to_string(), "\n";
76             # Output: (3 * x) + (3 * (x ^ 3))
77              
78             my $t8 = "((1/(3*a))-(1/(3*b)))/((a/b)-(b/a))";
79             print "Output: ", parse_from_string($t8)->to_collected()->to_string(), "\n";
80             # Output: (b - a) / ((3 * (a ^ 2)) - (3 * (b ^ 2)))
81              
82             my $t9 = "(x+y+z)/2";
83             my @terms = parse_from_string($t9)->to_terms();
84             print "Terms: (", join("), (", @terms), ")\n";
85             # Terms: (x / 2), (y / 2), (z / 2)
86              
87             $Math::Symbolic::Custom::Collect::COMPLEX_VAR = 'j'; # default is 'i'
88             my $t10 = "j*(3-7*j)*(2-j)";
89             print "Output: ", parse_from_string($t10)->to_collected()->to_string(), "\n";
90             # Output: 17 - j
91              
92             my $t11 = "(3*x - 1)^2";
93             print "Output: ", parse_from_string($t11)->to_derivative()->to_string(), "\n";
94             # Output: (18 * x) - 6
95              
96             my $t12 = "u*t + (1/2)*a*t^2";
97             print "Output: ", parse_from_string($t12)->to_derivative('t')->to_string(), "\n";
98             # Output: u + (a * t)
99              
100             =cut
101              
102             # this symbol represents the solution to x^2 = -1. If for some reason 'i' is being used as a variable for a different
103             # purpose in the expression, this should be changed (e.g. to 'j'). Otherwise things will get very confusing
104             our $COMPLEX_VAR = "i";
105              
106             # variable constraints.
107             our %VARIABLE_CONSTRAINTS;
108             # e.g. %VARIABLE_CONSTRAINTS = (
109             # 'x' => { nonzero => 1, positive => 1}, # x is > 0
110             # 'y' => { nonzero => 1 }, # y != 0
111             # );
112              
113             # if evaluating a constant exponent expression gives a result higher than this, it won't bother and keep the exponent expression
114             our $EXP_MAX = "1000000";
115              
116             =head2 Method to_collected()
117              
118             'to_collected' performs the following operations on the inputted Math::Symbolic tree:-
119              
120             =over
121              
122             =item * Folds constants
123              
124             =item * Converts decimal numbers to rational numbers
125              
126             =item * Combines fractions
127              
128             =item * Expands brackets
129              
130             =item * Collects like terms
131              
132             =item * Cancels down
133              
134             =back
135              
136             The result is often a more concise expression. See EXAMPLES above.
137              
138             =head3 $EXP_MAX
139              
140             C<$EXP_MAX> is a package variable which can be used to set a maximum value on evaluating/folding constants
141             with a constant exponent. By default it is set to 1,000,000.
142              
143             use strict;
144             use Math::Symbolic 0.613 qw(:all);
145             use Math::Symbolic::Custom::Collect 0.35;
146              
147             # some expression with a power
148             my $expr = parse_from_string("1 + 32^3");
149              
150             my $output = $expr->to_collected();
151             print "\$output = $output\n"; # $output = 32769
152              
153             # decide to keep it as 32^3
154             $Math::Symbolic::Custom::Collect::EXP_MAX = 1000;
155              
156             $output = $expr->to_collected();
157             print "\$output = $output\n"; # $output = 1 + (32 ^ 3)
158              
159             =cut
160              
161             sub to_collected {
162 681     681 1 20129044 my ($t1) = @_;
163              
164 681 50       2596 return undef unless defined wantarray;
165              
166             # 1. recursion step.
167             # Fold constants, convert decimal to rational, combine fractions, expand brackets
168 681         2652 my $t2 = prepare($t1);
169 681 50       2019 if (!defined $t2) {
170 0         0 return undef;
171             }
172            
173             # 2. collect like terms
174 681 100 100     1823 if ( ($t2->term_type() == T_OPERATOR) && ($t2->type() == B_DIVISION) ) {
175            
176 173         2134 my $numerator = $t2->op1();
177 173         1094 my $denominator = $t2->op2();
178 173         872 my ($n_hr, $d_hr);
179            
180 173         757 my ($c_n, $c_n_cth) = collect_like_terms($numerator);
181 173         2376 my ($c_d, $c_d_cth) = collect_like_terms($denominator);
182              
183 173 50 33     4907 if ( defined($c_n_cth) && defined($c_d_cth) ) {
184             # 3. attempt to cancel down
185 173         784 ($numerator, $n_hr, $denominator, $d_hr) = cancel_down($c_n, $c_n_cth, $c_d, $c_d_cth);
186             }
187             else {
188 0 0       0 if ( defined $c_n ) {
189 0         0 $numerator = $c_n;
190 0         0 $n_hr = $c_n_cth;
191             }
192 0 0       0 if ( defined $c_d ) {
193 0         0 $denominator = $c_d;
194 0         0 $d_hr = $c_d_cth;
195             }
196             }
197            
198             # check denominator
199 173 100 100     769 if ( ($denominator->term_type() == T_CONSTANT) && ($denominator->value() == 1) ) {
    50 66        
200 11 100       347 return wantarray ? ($numerator, $n_hr) : $numerator;
201             }
202             elsif ( ($denominator->term_type() == T_CONSTANT) && ($denominator->value() == 0) ) {
203             # FIXME: divide by zero at this point?!
204 0         0 return $t2;
205             }
206             else {
207            
208 162         4338 my ($Re, $Im) = $denominator->test_complex();
209             # Rationalize a complex denominator
210 162 100 66     3198 if ( defined($Im) && !(($Im->term_type() == T_CONSTANT) && ($Im->value() == 0)) ) {
      66        
211              
212             # construct complex conjugate
213 9         141 my $ccj = symbolic_complex($Re, Math::Symbolic::Operator->new('*', Math::Symbolic::Constant->new(-1), $Im));
214              
215             # multiply with numerator and denominator
216 9         674 my $new_num = Math::Symbolic::Operator->new('*', $numerator, $ccj);
217 9         201 my $new_den = Math::Symbolic::Operator->new('*', $denominator, $ccj);
218              
219             # Reconstruct the expression and collect up again
220 9         194 my $t3 = Math::Symbolic::Operator->new( '/', $new_num, $new_den );
221 9         308 my ($t4, $n_hr2, $d_hr2) = $t3->to_collected();
222              
223 9 50       315 return wantarray ? ($t4, $n_hr2, $d_hr2) : $t4;
224             }
225             else {
226              
227 153         2221 my $t3 = Math::Symbolic::Operator->new( '/', $numerator, $denominator );
228 153 100       7108 return wantarray ? ($t3, $n_hr, $d_hr) : $t3;
229             }
230             }
231             }
232             else {
233 508         4977 my ($collected, $ct_href) = collect_like_terms($t2);
234              
235 508 100       6107 if ( defined $collected ) {
236 498 100       5310 return wantarray ? ($collected, $ct_href) : $collected;
237             }
238             else {
239 10         49 return $t2;
240             }
241             }
242             }
243              
244             =head2 Method to_terms()
245              
246             'to_terms()' uses 'to_collected()' and returns the expression as a list of terms, that is a list of sub-expressions that can be summed to create an expression which is (numerically) equivalent to the original expression.
247              
248             Called in a scalar context, returns the number of terms.
249              
250             =cut
251              
252             sub to_terms {
253 186     186 1 3522 my ($t1) = @_;
254              
255 186 50       615 return undef unless defined wantarray;
256              
257 186         649 my ($t2, $n_hr, $d_hr) = to_collected($t1);
258              
259 186 50       603 return undef unless defined $t2;
260              
261 186         381 my @terms;
262 186 50 33     934 if ( exists($d_hr->{terms}) && exists($n_hr->{terms}) ) {
    50          
263              
264 0         0 my $terms = $n_hr->{terms};
265 0         0 my $trees = $n_hr->{trees};
266              
267 0         0 my $denominator = build_summation_tree($d_hr);
268            
269 0         0 my $const_acc = $terms->{constant_accumulator};
270 0 0       0 $const_acc = 0 if not defined $const_acc;
271 0 0       0 push @terms, Math::Symbolic::Constant->new($const_acc) / $denominator if $const_acc != 0;
272 0         0 delete $terms->{constant_accumulator};
273 0         0 while ( my ($k, $v) = each %{$terms} ) {
  0         0  
274 0         0 my $numerator = build_summation_tree({ terms => { constant_accumulator => 0, $k => $v }, trees => $trees });
275 0         0 my $expr = $numerator / $denominator;
276 0         0 my $expr2 = to_collected($expr);
277 0 0       0 $expr = $expr2 if defined $expr2;
278 0         0 push @terms, $expr;
279             }
280              
281             }
282             elsif ( exists $n_hr->{terms} ) {
283              
284 186         431 my $terms = $n_hr->{terms};
285 186         362 my $trees = $n_hr->{trees};
286              
287 186         353 my $const_acc = $terms->{constant_accumulator};
288 186 50       508 $const_acc = 0 if not defined $const_acc;
289 186 100       727 push @terms, Math::Symbolic::Constant->new($const_acc) if $const_acc != 0;
290 186         2593 delete $terms->{constant_accumulator};
291 186         350 while ( my ($k, $v) = each %{$terms} ) {
  262         1107  
292 76         439 push @terms, build_summation_tree({ terms => { constant_accumulator => 0, $k => $v }, trees => $trees });
293             }
294             }
295             else {
296 0         0 push @terms, $t2;
297             }
298              
299 186 50       591 if ( scalar(@terms) == 0 ) {
300 0         0 push @terms, Math::Symbolic::Constant->new(0);
301             }
302              
303 186 50       1377 return wantarray ? @terms : scalar(@terms);
304             }
305              
306             =head2 Method to_derivative()
307              
308             This is a convenience method to differentiate the inputted Math::Symbolic expression. It calls to_collected() before passing the results through to L's partial_derivative().
309              
310             Takes one parameter, the variable of differentiation. If not provided it will check if the expression and if there is only one variable it will use that.
311              
312             Using to_collected() on an expression before differentiating it, often yields better results (because to_collected() reformats the expression, and preparing the expression is half the battle in calculus). For example, from L:-
313              
314             use strict;
315             use Math::Symbolic qw(:all);
316             use Math::Symbolic::Derivative qw(:all);
317             use Math::Symbolic::Custom::Collect;
318              
319             # the expression from the bug report
320             my $f = parse_from_string('A*(x - x_0)^2 + y_0');
321              
322             # try differentiating it directly, introduces a pole at x-x_0=0
323             my $f_d1 = partial_derivative($f, 'x_0');
324             print "$f_d1\n"; # A * ((2 * ((x - x_0) ^ 2)) * ((1 * (-1)) / (x - x_0)))
325              
326             # try with to_derivative()
327             my $f_d2 = $f->to_derivative('x_0');
328             print "$f_d2\n"; # ((2 * A) * x_0) - ((2 * A) * x)
329             # i.e., no pole.
330              
331             =cut
332              
333             sub to_derivative {
334 0     0 1 0 my ($t1, $var) = @_;
335              
336 0 0       0 if ( not defined $var ) {
337 0         0 my @vars = $t1->explicit_signature();
338 0 0       0 if ( scalar(@vars) == 1 ) {
339 0         0 $var = $vars[0];
340             }
341             else {
342 0         0 return undef;
343             }
344             }
345            
346 0         0 my $t2 = $t1->to_collected();
347 0 0       0 return undef unless defined $t2;
348            
349 0 0       0 $var = Math::Symbolic::Variable->new($var) unless ref($var) =~ /^Math::Symbolic::Variable/;
350 0         0 my $diff = Math::Symbolic::Derivative::partial_derivative( $t2, $var );
351 0         0 return $diff->to_collected();
352             }
353              
354             =head1 COMPLEX NUMBERS
355              
356             From version 0.2, there is some support for complex numbers. The symbol in C<$Math::Symbolic::Custom::Collect::COMPLEX_VAR> (set to 'i' by default) is considered by the module to be the symbol for the imaginary unit and treated as such when collecting up the expression. It is a Math::Symbolic variable to permit easy conversion to Math::Complex numbers using the value() method, for example:
357              
358             use strict;
359             use Math::Symbolic qw(:all);
360             use Math::Symbolic::Custom::Collect;
361             use Math::Complex;
362              
363             my $t = "x+sqrt(-100)+y*i";
364             my $M_S = parse_from_string($t)->to_collected();
365             print "$M_S\n"; # ((10 * i) + x) + (i * y)
366              
367             # we want some kind of actual number from this expression
368             my $M_C = $M_S->value(
369             'x' => 2,
370             'y' => 3,
371             'i' => i, # glue Math::Symbolic and Math::Complex
372             );
373              
374             # $M_C is a Math::Complex number
375             print "$M_C\n"; # 2+13i
376              
377             If you are not going to be using complex numbers and want the symbol C available to use as a normal Math::Symbolic variable then you will have to override C<$Math::Symbolic::Custom::Collect::COMPLEX_VAR> to something else at the beginning of your program, e.g.:
378              
379             use strict;
380             use Math::Symbolic qw(:all);
381             use Math::Symbolic::Custom::Collect;
382             $Math::Symbolic::Custom::Collect::COMPLEX_VAR = 'blahblahblah';
383             # note: remember not to use 'blahblahblah' as a Math::Symbolic variable name
384              
385             my $coord = parse_from_string("5*i + 2*j + 6*k"); # 'i' is just a normal variable here
386              
387             It is not a good idea to change the contents of C<$Math::Symbolic::Custom::Collect::COMPLEX_VAR> mid-program, for example if you define some expressions using 'i' and then want to start using 'j', the module is not going to remember that 'i' was also intended to be the imaginary unit. Best thing is to pick something at the beginning of the program and stick to it.
388              
389             From version 0.3 there are some helper methods and routines for working with complex (number) expressions:-
390              
391             =head2 symbolic_complex()
392              
393             Pass in an expression for the real component and an expression for the imaginary component and symbolic_complex will return a Math::Symbolic expression with the imaginary parameter multiplied by the contents of C<$Math::Symbolic::Custom::Collect::COMPLEX_VAR>, and summed with the real parameter. For example:-
394              
395             use strict;
396             use Math::Symbolic qw(:all);
397             use Math::Symbolic::Custom::Collect;
398              
399             my $Re = 1;
400             my $Im = 2;
401             my $e = symbolic_complex($Re, $Im);
402             print "$e\n"; # 1 + (2 * i)
403              
404             $e = symbolic_complex('exp(2)', 'sin(x)');
405             print "$e\n"; # (e ^ 2) + ((sin(x)) * i)
406            
407             =cut
408              
409             sub symbolic_complex {
410 33     33 1 24487 my ($Re, $Im) = @_;
411            
412 33 100       271 $Re = parse_from_string($Re) unless ref($Re) =~ /^Math::Symbolic/;
413 33 100       40975 $Im = parse_from_string($Im) unless ref($Im) =~ /^Math::Symbolic/;
414            
415 33         34143 return Math::Symbolic::Operator->new('+', $Re,
416             Math::Symbolic::Operator->new('*', $Im, Math::Symbolic::Variable->new($COMPLEX_VAR)));
417             }
418              
419             =head2 Method test_complex()
420              
421             Returns the real and imaginary components of the expression as an array (the imaginary component has C<$Math::Symbolic::Custom::Collect::COMPLEX_VAR> removed). For example:-
422              
423             use strict;
424             use Math::Symbolic qw(:all);
425             use Math::Symbolic::Custom::Collect;
426              
427             my ($Re, $Im) = parse_from_string('1+i')->test_complex();
428             print "$Re\n"; # 1
429             print "$Im\n"; # 1
430              
431             ($Re, $Im) = parse_from_string('x^2 + sin(x) - sqrt(-49)')->test_complex();
432             print "$Re\n"; # (x ^ 2) + (sin(x))
433             print "$Im\n"; # -7
434              
435             =cut
436              
437             sub test_complex {
438 186     186 1 154773 my ($t1) = @_;
439            
440             # use to_terms() to get all the terms
441 186         1033 my @terms = $t1->to_terms();
442            
443 186 50       658 if ( scalar(@terms) == 0 ) { # ?? TODO: possible error in to_terms()
444 0         0 return (Math::Symbolic::Constant->new(0), Math::Symbolic::Constant->new(0));
445             }
446            
447             # check each one using explicit_signature() to see if it contains the imaginary unit
448             # put in the real or imaginary arrays @Re and @Im as appropriate
449 186         459 my @Re;
450             my @Im;
451 186         447 foreach my $term (@terms) {
452 235         880 my @vars = $term->explicit_signature();
453 235         3968 my @cvar = grep { $_ eq $COMPLEX_VAR } @vars;
  88         277  
454 235 100       558 if ( scalar @cvar ) {
455 32         138 push @Im, $term;
456             }
457             else {
458 203         580 push @Re, $term;
459             }
460             }
461              
462             # create an expression for the real part
463 186         357 my $ntr;
464 186 100       505 if ( scalar(@Re) == 0 ) { # no real part
465 3         12 $ntr = Math::Symbolic::Constant->new(0);
466             }
467             else {
468             # sum the @Re array
469 183         351 $ntr = shift @Re;
470 183         1762 while (@Re) {
471 20         120 my $e = shift @Re;
472 20         93 $ntr = Math::Symbolic::Operator->new('+', $ntr, $e);
473             }
474             }
475            
476 186 100       969 if ( scalar(@Im) == 0 ) { # no imaginary part
477 155         475 return ($ntr, Math::Symbolic::Constant->new(0));
478             }
479            
480             # Remove the imaginary unit from the imaginary part
481             # Sum the imaginary terms
482 31         69 my $nti = shift @Im;
483 31         97 while (@Im) {
484 1         3 my $e = shift @Im;
485 1         25 $nti = Math::Symbolic::Operator->new('+', $nti, $e);
486             }
487              
488             # run to_collected() to get useful data structures
489 31         277 my ($c, $nhr, $dhr) = $nti->to_collected();
490            
491 31         101 foreach my $hr ($nhr, $dhr) {
492 62 100       200 next unless defined $hr;
493            
494             # figure out the variable name in the data structure with the imaginary unit
495 31         61 my @vars = grep { $hr->{trees}{$_}{name} eq $COMPLEX_VAR } grep { /^VAR/ } keys %{$hr->{trees}};
  35         284  
  35         136  
  31         134  
496 31 50       92 next if scalar(@vars) == 0; # could not find imaginary unit # TODO: fatal error if none present at all
497 31         72 my $k = $vars[0]; # should only be one instance anyway
498            
499 31         58 my @t_keys = keys %{$hr->{terms}};
  31         100  
500 31         77 foreach my $kss (@t_keys) {
501 63         154 my $css = $hr->{terms}{$kss};
502 63         172 my @tkss = split(/,/, $kss);
503 63         226 my @n_ss = grep { $_ !~ /^$k/ } @tkss; # remove imaginary unit from this term
  67         426  
504              
505             # update the data structure
506 63         146 delete $hr->{terms}{$kss};
507 63 100       134 if ( scalar @n_ss ) {
508 35         193 $hr->{terms}{ join(",", @n_ss) } += $css;
509             }
510             else {
511 28         121 $hr->{terms}{constant_accumulator} += $css;
512             }
513             }
514             }
515              
516             # recombine
517 31         91 my $new_n = build_summation_tree( $nhr );
518              
519 31 50       811 if ( defined $dhr ) {
520 0         0 my $new_d = build_summation_tree( $dhr );
521 0         0 my $t3 = Math::Symbolic::Operator->new( '/', $new_n, $new_d );
522            
523 0         0 return ($ntr, $t3);
524             }
525             else {
526 31         412 return ($ntr, $new_n);
527             }
528             }
529              
530             =head2 Method to_complex_conjugate()
531              
532             Returns the complex conjugate of the expression, essentially by multiplying the imaginary component by -1.
533              
534             use strict;
535             use Math::Symbolic qw(:all);
536             use Math::Symbolic::Custom::Collect;
537              
538             my $cc = parse_from_string('1+i')->to_complex_conjugate();
539             print "$cc\n"; # 1 - i
540              
541             $cc = parse_from_string('5*x+i*sin(y+z)')->to_complex_conjugate();
542             print "$cc\n"; # (5 * x) - ((sin(y + z)) * i)
543              
544             =cut
545              
546             sub to_complex_conjugate {
547 10     10 1 89449 my ($t1) = @_;
548            
549             # extract the real and imaginary parts
550 10         78 my ($Re, $Im) = $t1->test_complex();
551             # multiple the imaginary part by -1 and use it to create a new complex expression
552 10         54 my $ccj = symbolic_complex($Re, Math::Symbolic::Operator->new('*', Math::Symbolic::Constant->new(-1), $Im));
553 10         596 return $ccj->to_collected();
554             }
555              
556              
557             #our %VARIABLE_CONSTRAINTS;
558             # e.g. %VARIABLE_CONSTRAINTS = (
559             # 'x' => { nonzero => 1, positive => 1}, # x is > 0
560             # 'y' => { nonzero => 1 }, # y != 0
561             # );
562              
563             sub set_constraints {
564 0     0 0 0 my ($var, @constraints) = @_;
565              
566 0         0 my %con;
567 0         0 $con{$_} = 1 for @constraints;
568              
569 0         0 $VARIABLE_CONSTRAINTS{$var} = \%con;
570              
571 0         0 return;
572             }
573              
574             sub add_constraints {
575 0     0 0 0 my ($var, @constraints) = @_;
576              
577 0         0 my %con;
578 0 0       0 if ( exists $VARIABLE_CONSTRAINTS{$var} ) {
579 0         0 $con{$_} = 1 for keys %{$VARIABLE_CONSTRAINTS{$var}};
  0         0  
580             }
581              
582 0         0 $con{$_} = 1 for @constraints;
583              
584 0         0 $VARIABLE_CONSTRAINTS{$var} = \%con;
585              
586 0         0 return;
587             }
588              
589             sub remove_constraints {
590 0     0 0 0 my ($var, @constraints) = @_;
591              
592 0         0 my %con;
593 0 0       0 if ( exists $VARIABLE_CONSTRAINTS{$var} ) {
594 0         0 $con{$_} = 1 for keys %{$VARIABLE_CONSTRAINTS{$var}};
  0         0  
595             }
596              
597 0         0 delete $con{$_} for @constraints;
598              
599 0         0 $VARIABLE_CONSTRAINTS{$var} = \%con;
600              
601 0         0 return;
602             }
603              
604             sub remove_all_constraints {
605 0     0 0 0 my ($var) = @_;
606            
607 0         0 delete $VARIABLE_CONSTRAINTS{$var};
608             }
609              
610             sub get_constraints {
611 0     0 0 0 my ($var) = @_;
612              
613 0   0     0 return $VARIABLE_CONSTRAINTS{$var} || {};
614             }
615              
616             sub get_constraints_as_list {
617 0     0 0 0 my ($var) = @_;
618              
619 0         0 my @constraints;
620            
621 0 0       0 if ( exists $VARIABLE_CONSTRAINTS{$var} ) {
622 0         0 my $con = $VARIABLE_CONSTRAINTS{$var};
623 0         0 @constraints = grep { $con->{$_} == 1 } keys %{$con};
  0         0  
  0         0  
624             }
625              
626 0         0 return @constraints;
627             }
628              
629             sub test_constraint {
630 3     3 0 55 my ($var, $constraint) = @_;
631              
632 3 50       17 if ( exists $VARIABLE_CONSTRAINTS{$var}{$constraint} ) {
633 0         0 return 1;
634             }
635              
636 3         14 return 0;
637             }
638              
639              
640             #### cancel_down.
641             # Checks numerator and denominator expressions for constants and variables which can cancel.
642             sub cancel_down {
643 173     173 0 510 my ($c_n, $n_cth, $c_d, $d_cth) = @_;
644              
645 173         305 my %n_ct = %{$n_cth};
  173         604  
646 173         475 my %d_ct = %{$d_cth};
  173         565  
647              
648 173         357 my %n_terms = %{ $n_ct{terms} };
  173         690  
649 173         319 my %n_funcs = %{ $n_ct{trees} };
  173         599  
650              
651 173         342 my %d_terms = %{ $d_ct{terms} };
  173         547  
652 173         359 my %d_funcs = %{ $d_ct{trees} };
  173         460  
653              
654 173   100     678 my $n_acc = $n_terms{constant_accumulator} || 0;
655 173   100     607 my $d_acc = $d_terms{constant_accumulator} || 0;
656              
657 173         387 delete $n_terms{constant_accumulator};
658 173         373 delete $d_terms{constant_accumulator};
659              
660 173         327 my $did_some_cancellation = 0;
661              
662 173         374 my %constants;
663 173 100       1961 $constants{$n_acc}++ if $n_acc != 0;
664 173 100       721 $constants{$d_acc}++ if $d_acc != 0;
665 173         769 $constants{$_}++ for values %n_terms;
666 173         539 $constants{$_}++ for values %d_terms;
667 173         582 my @con = sort {$a <=> $b} map { abs } keys %constants;
  295         825  
  407         1494  
668 173         455 my @con_int = grep { $_ eq int($_) } @con;
  407         1479  
669            
670 173 50       581 if ( scalar(@con) == scalar(@con_int) ) {
671              
672 173         337 my $min = $con[0];
673              
674 173         303 my $GCF;
675 173         635 FIND_GCF: foreach my $div (reverse(2..$min)) {
676 866         1123 my $div_ok = 1;
677 866         1178 DIV_TEST: foreach my $num (@con) {
678 974 100       1664 if ( $num % $div != 0 ) {
679 839         980 $div_ok = 0;
680 839         1151 last DIV_TEST;
681             }
682             }
683 866 100       1501 if ( $div_ok ) {
684 27         66 $GCF = $div;
685 27         75 last FIND_GCF;
686             }
687             }
688            
689 173 100       620 if ( defined $GCF ) {
690 27         69 $n_acc /= $GCF;
691 27         88 $d_acc /= $GCF;
692 27         120 $n_terms{$_} /= $GCF for keys %n_terms;
693 27         87 $d_terms{$_} /= $GCF for keys %d_terms;
694 27         68 $did_some_cancellation = 1;
695             }
696             }
697              
698 173 100 100     718 if ( ($n_acc == 0) && ($d_acc == 0) ) {
699              
700             # try to cancel vars
701             # see if there are any common variables we can cancel
702             # count up the number of unique vars within numerator and denominator
703 18         42 my %c_vars;
704             my %c_pow;
705 18         59 foreach my $e (\%n_terms, \%d_terms) {
706 36         72 foreach my $key (keys %{$e}) {
  36         101  
707 60         142 my @v1 = split(/,/, $key);
708 60         100 foreach my $v2 (@v1) {
709 84         245 my ($v, $c) = split(/:/, $v2);
710 84 100 100     411 if ( ($v =~ /^CONST/) or ($v =~ /^VAR/) ) {
711 80         760 $c_vars{$v}++;
712 80 100       166 if ( exists $c_pow{$v} ) {
713 40 100       149 if ( $c_pow{$v} > $c ) {
714 3         14 $c_pow{$v} = $c;
715             }
716             }
717             else {
718 40         136 $c_pow{$v} = $c;
719             }
720             }
721             }
722             }
723             }
724              
725             # if a variable exists in each term, perhaps we can cancel it
726 18         42 my @all_terms;
727 18         113 while ( my ($v, $c) = each %c_vars ) {
728 40 100       175 if ( $c == (scalar(keys %n_terms)+scalar(keys %d_terms)) ) {
729              
730 12 100       75 if ( $v =~ /^CONST/ ) {
    50          
731 2         10 push @all_terms, $v;
732             }
733             elsif ( $v =~ /^VAR/ ) {
734 10 50       60 if ( $n_funcs{$v}->{name} ne $COMPLEX_VAR ) {
735 10         50 push @all_terms, $v;
736             }
737             }
738             }
739             }
740              
741 18         82 while ( my $v = pop @all_terms ) {
742              
743 12         25 my %n_ct_new;
744             my %d_ct_new;
745              
746 12         30 my $num_d_terms = scalar(keys %d_terms);
747              
748 12         21 $did_some_cancellation = 0;
749              
750             # cancel from denominator
751 12         50 while ( my ($t, $c) = each %d_terms ) {
752 18         49 my @v1 = split(/,/, $t);
753 18         35 my @nt;
754 18         41 foreach my $v2 (@v1) {
755 27         102 my ($vv, $cc) = split(/:/, $v2);
756 27 100       74 if ($vv eq $v) {
757 18 100 100     100 if ( ($num_d_terms == 1) && ($cc == 1) && ($v =~ /^VAR/) && !test_constraint($d_funcs{$v}->{name}, 'nonzero') ) {
      66        
      66        
758             # refuse to cancel all instances of a variable from the denominator
759 2         8 push @nt, $v2;
760             }
761             else {
762 16         38 my $c_sub = $c_pow{$v};
763 16 50       44 if ( $cc < $c_sub ) {
764 0         0 croak "cancel_down: Variable $v has index $cc but want to cancel $c_sub";
765             }
766 16         30 $cc -= $c_sub;
767 16 100       59 if ($cc > 0) {
    100          
768 5         16 push @nt, "$vv:$cc";
769 5         13 $did_some_cancellation = 1;
770             }
771             elsif ( scalar(@v1) == 1 ) {
772 3         8 $d_acc = $c;
773 3         10 $did_some_cancellation = 1;
774             }
775             }
776             }
777             else {
778 9         24 push @nt, $v2;
779             }
780             }
781 18 100       60 if ( scalar(@nt) ) {
782 15         118 $d_ct_new{join(",", @nt)} = $c;
783             }
784             }
785              
786 12 100       71 if ( $did_some_cancellation ) {
787            
788             # cancel from numerator
789 7         28 while ( my ($t, $c) = each %n_terms ) {
790 10         29 my @v1 = split(/,/, $t);
791 10         15 my @nt;
792 10         27 foreach my $v2 (@v1) {
793 13         35 my ($vv, $cc) = split(/:/, $v2);
794 13 100       37 if ($vv eq $v) {
795 10         24 my $c_sub = $c_pow{$v};
796 10 50       30 if ( $cc < $c_sub ) {
797 0         0 croak "cancel_down: Variable $v has index $cc but want to cancel $c_sub";
798             }
799 10         21 $cc -= $c_sub;
800 10 100       42 if ($cc > 0) {
    100          
801 3         12 push @nt, "$vv:$cc";
802             }
803             elsif ( scalar(@v1) == 1 ) {
804 5         17 $n_acc = $c;
805             }
806             }
807             else {
808 3         8 push @nt, $v2;
809             }
810             }
811 10 100       34 if ( scalar(@nt) ) {
812 5         29 $n_ct_new{join(",", @nt)} = $c;
813             }
814             }
815            
816 7         27 %n_terms = %n_ct_new;
817 7         47 %d_terms = %d_ct_new;
818             }
819             }
820             }
821              
822             # postprocess for constant exponents
823 173         483 EXP_LOOP_n: foreach my $n_key (keys %n_terms) {
824 147         378 my $coeff = $n_terms{$n_key};
825 147 50       567 next EXP_LOOP_n unless $n_key =~ /\A CONST_(\d+):(\d+) \z/msx;
826 0         0 my ($const, $exp) = ($1, $2);
827            
828 0         0 my $result = $const ** $exp;
829            
830 0 0       0 if ( ($result < $EXP_MAX) ) {
831 0         0 $n_acc += $coeff * $result;
832 0         0 delete $n_terms{$n_key};
833             }
834             }
835            
836 173         548 EXP_LOOP_d: foreach my $d_key (keys %d_terms) {
837 57         264 my $coeff = $d_terms{$d_key};
838 57 50       217 next EXP_LOOP_d unless $d_key =~ /\A CONST_(\d+):(\d+) \z/msx;
839 0         0 my ($const, $exp) = ($1, $2);
840            
841 0         0 my $result = $const ** $exp;
842            
843 0 0       0 if ( ($result < $EXP_MAX) ) {
844 0         0 $d_acc += $coeff * $result;
845 0         0 delete $d_terms{$d_key};
846             }
847             }
848              
849 173 100 100     867 if ( (scalar(keys %n_terms) == 0) && (scalar(keys %d_terms) == 0) ) {
850             # do some tidying up of constant fractions with negative denominators
851 92 100       244 if ( $d_acc < 0 ) {
852 18         44 $n_acc *= -1;
853 18         35 $d_acc = abs($d_acc);
854 18         66 $did_some_cancellation = 1;
855             }
856             # cancel down constant fraction if necessary
857 92 50 33     388 if ( ($n_acc == int($n_acc)) && ($d_acc == int($d_acc)) ) {
858 92         286 my $GCF = get_frac_GCF( abs($n_acc), abs($d_acc) );
859 92         196 $n_acc /= $GCF;
860 92         175 $d_acc /= $GCF;
861 92         165 $did_some_cancellation = 1;
862             }
863             }
864              
865 173         538 $n_terms{constant_accumulator} = $n_acc;
866 173         433 $d_terms{constant_accumulator} = $d_acc;
867              
868 173         756 my $n_hr = { terms => \%n_terms, trees => \%n_funcs };
869 173         600 my $d_hr = { terms => \%d_terms, trees => \%d_funcs };
870              
871 173 100       432 if ( $did_some_cancellation ) {
872              
873 115         274 my $new_n = build_summation_tree( $n_hr );
874 115         1921 my $new_d = build_summation_tree( $d_hr );
875              
876 115         2320 return ($new_n, $n_hr, $new_d, $d_hr);
877             }
878              
879 58         607 return ($c_n, $n_cth, $c_d, $d_cth);
880             }
881              
882             #### collect_like_terms
883             sub collect_like_terms {
884 854     854 0 1958 my ($t) = @_;
885            
886 854         1506 my @elements;
887 854         3321 my $ok = get_elements_collect( \@elements, '+', $t, 1 );
888              
889 854 100       2719 if ( $ok ) {
890 844         2691 my $ct_href = collect_terms(\@elements);
891 844 50       2197 if ( defined $ct_href ) {
892 844         2684 return (build_summation_tree($ct_href), $ct_href);
893             }
894             }
895            
896 10         35 return undef;
897             }
898              
899             sub get_elements_collect {
900 2054     2054 0 10829 my ($l, $s, $tree) = @_;
901              
902 2054 100 66     4633 if ( $tree->term_type() == T_VARIABLE ) {
    100 66        
    100 66        
    100 66        
    100 33        
    100 33        
    50          
    50          
903 147         903 my $r = { type => 'variable', object => $tree };
904 147 50       472 if ( $s eq '+' ) {
    0          
905 147         281 push @{$l}, $r;
  147         321  
906             }
907             elsif ( $s eq '-' ) {
908 0         0 push @{$l}, { type => 'products', list => [ { type => 'constant', object => Math::Symbolic::Constant->new(-1) }, $r ] };
  0         0  
909             }
910 147         384 return 1;
911             }
912             elsif ( $tree->term_type() == T_CONSTANT ) {
913 539         4449 my $r = { type => 'constant', object => $tree };
914 539 50       1561 if ( $s eq '+' ) {
    0          
915 539         830 push @{$l}, $r;
  539         1262  
916             }
917             elsif ( $s eq '-' ) {
918 0         0 push @{$l}, { type => 'products', list => [ { type => 'constant', object => Math::Symbolic::Constant->new(-1) }, $r ] };
  0         0  
919             }
920 539         1375 return 1;
921             }
922             elsif ( ($tree->term_type() == T_OPERATOR) && ($tree->arity() == 1) ) {
923              
924             # walk through functions e.g. sin, cos
925 32         628 my $tree2 = $tree->new();
926 32         6907 my ($ctree) = to_collected($tree->op1());
927 32 50       203 if ( defined $ctree ) {
928 32         263 $tree2->{operands}[0] = $ctree;
929             }
930 32         159 my $r = { type => 'function', object => $tree2 };
931 32 50       139 if ( $s eq '+' ) {
    0          
932 32         91 push @{$l}, $r;
  32         79  
933             }
934             elsif ( $s eq '-' ) {
935 0         0 push @{$l}, { type => 'products', list => [ { type => 'constant', object => Math::Symbolic::Constant->new(-1) }, $r ] };
  0         0  
936             }
937 32         105 return 1;
938             }
939             elsif ( ($tree->term_type() == T_OPERATOR) && ($tree->type() == B_LOG) ) {
940              
941             # walk through log
942 1         37 my $tree2 = $tree->new();
943 1         244 my ($ctree1) = to_collected($tree->op1());
944 1         8 my ($ctree2) = to_collected($tree->op2());
945 1 50 33     12 if ( defined($ctree1) and defined($ctree2) ) {
946 1         5 $tree2->{operands}[0] = $ctree1;
947 1         37 $tree2->{operands}[1] = $ctree2;
948             }
949 1         7 my $r = { type => 'function', object => $tree2 };
950 1 50       7 if ( $s eq '+' ) {
    0          
951 1         2 push @{$l}, $r;
  1         4  
952             }
953             elsif ( $s eq '-' ) {
954 0         0 push @{$l}, { type => 'products', list => [ { type => 'constant', object => Math::Symbolic::Constant->new(-1) }, $r ] };
  0         0  
955             }
956 1         4 return 1;
957             }
958             elsif ( ($tree->term_type() == T_OPERATOR) && ($tree->type() == B_PRODUCT) ) {
959 719         21277 my @product_elements;
960 719         2128 my $ok1 = get_product_elements_collect(\@product_elements, $tree->op1());
961 719         2083 my $ok2 = get_product_elements_collect(\@product_elements, $tree->op2());
962 719         2984 my @sorted = sort { $a->{type} cmp $b->{type} } @product_elements;
  2042         13022  
963              
964 719 100 100     3080 if ( $ok1 && $ok2 ) {
965 713 50       2008 if ( $s eq '-' ) {
966 0         0 push @sorted, { type => 'constant', object => Math::Symbolic::Constant->new(-1) };
967             }
968 713         1114 push @{$l}, { type => 'products', list => \@sorted };
  713         2718  
969 713         2399 return 1;
970             }
971 6         23 return 0;
972             }
973             elsif ( ($tree->term_type() == T_OPERATOR) && ($tree->type() == B_SUM) ) {
974 600         29613 my $ok1 = get_elements_collect($l, '+', $tree->op1());
975 600         1998 my $ok2 = get_elements_collect($l, '+', $tree->op2());
976 600         2122 return $ok1 & $ok2;
977             }
978             elsif ( ($tree->term_type() == T_OPERATOR) && ($tree->type() == B_DIFFERENCE) ) {
979 0         0 my $ok1 = get_elements_collect($l, '+', $tree->op1());
980 0         0 my $ok2 = get_elements_collect($l, '-', $tree->op2());
981 0         0 return $ok1 & $ok2;
982             }
983             elsif ( ($tree->term_type() == T_OPERATOR) && ($tree->type() == B_EXP) ) {
984             # op1 must be a variable. op2 must be an int > 0
985 16         1000 my $op1 = $tree->op1();
986 16         114 my $exp = $tree->op2();
987 16 50 66     138 if ( ($op1->term_type() == T_VARIABLE) &&
    100 33        
      33        
      66        
      66        
      33        
988             ($exp->term_type() == T_CONSTANT) &&
989             ($exp->value() eq int($exp->value())) &&
990             ($exp->value() > 0) ) {
991            
992 0         0 my @v_list;
993 0         0 for (0..$exp->value()-1) {
994 0         0 push @v_list, { type => 'variable', object => $op1->new() };
995             }
996 0 0       0 if ( $s eq '-' ) {
997 0         0 push @v_list, { type => 'constant', object => Math::Symbolic::Constant->new(-1) };
998             }
999 0         0 push @{$l}, { type => 'products', list => \@v_list };
  0         0  
1000 0         0 return 1;
1001             }
1002             # do a list of constants
1003             elsif ( ($op1->term_type() == T_CONSTANT) &&
1004             ($exp->term_type() == T_CONSTANT) &&
1005             ($exp->value() eq int($exp->value())) &&
1006             ($exp->value() > 0) ) {
1007            
1008 12         462 my @v_list;
1009 12         35 my $result = $op1->value() ** $exp->value();
1010 12 100       307 if ( $result < $EXP_MAX ) {
1011 4         13 push @v_list, { type => 'constant', object => Math::Symbolic::Constant->new($result) };
1012             }
1013             else {
1014 8         42 for (0..$exp->value()-1) {
1015 1596         3641 my $obj = $op1->new();
1016 1596         38373 $obj->{special} = $op1->value();
1017 1596         14272 $obj->{name} = q{};
1018 1596         4889 push @v_list, { type => 'constant', object => $obj };
1019             }
1020             }
1021 12 50       113 if ( $s eq '-' ) {
1022 0         0 push @v_list, { type => 'constant', object => Math::Symbolic::Constant->new(-1) };
1023             }
1024 12         37 push @{$l}, { type => 'products', list => \@v_list };
  12         71  
1025 12         68 return 1;
1026             }
1027             }
1028              
1029 4         87 return 0;
1030             }
1031              
1032             sub get_product_elements_collect {
1033 2400     2400 0 13929 my ($l, $tree) = @_;
1034              
1035 2400 100 66     6992 if ( $tree->term_type() == T_VARIABLE ) {
    100 33        
    100 33        
    50 33        
    50 66        
    100 66        
    100          
1036 1250         4023 push @{$l}, { type => 'variable', object => $tree, };
  1250         3941  
1037 1250         2710 return 1;
1038             }
1039             elsif ( $tree->term_type() == T_CONSTANT ) {
1040 561         3214 push @{$l}, { type => 'constant', object => $tree, };
  561         2057  
1041 561         1380 return 1;
1042             }
1043             elsif ( ($tree->term_type() == T_OPERATOR) && ($tree->arity() == 1) ) {
1044 94         1671 my $tree2 = $tree->new();
1045 94         10215 my $ctree = to_collected( $tree->op1() );
1046 94 50       336 if ( defined $ctree ) {
1047 94         531 $tree2->{operands}[0] = $ctree;
1048             }
1049 94         185 push @{$l}, { type => 'function', object => $tree2, };
  94         408  
1050 94         357 return 1;
1051             }
1052             elsif ( ($tree->term_type() == T_OPERATOR) && ($tree->type() == B_LOG) ) {
1053 0         0 my $tree2 = $tree->new();
1054 0         0 my $ctree1 = to_collected( $tree->op1() );
1055 0         0 my $ctree2 = to_collected( $tree->op2() );
1056 0 0 0     0 if ( defined($ctree2) and defined($ctree1) ) {
1057 0         0 $tree2->{operands}[0] = $ctree1;
1058 0         0 $tree2->{operands}[1] = $ctree2;
1059             }
1060 0         0 push @{$l}, { type => 'function', object => $tree2, };
  0         0  
1061 0         0 return 1;
1062             }
1063             elsif ( ($tree->term_type() == T_OPERATOR) && ($tree->type() == U_MINUS) && ($tree->op1()->term_type() == T_CONSTANT) ) {
1064             # Fold U_MINUS of constant into constant
1065 0         0 push @{$l}, { type => 'constant', object => Math::Symbolic::Constant->new(-1), };
  0         0  
1066 0         0 push @{$l}, { type => 'constant', object => $tree->op1(), };
  0         0  
1067 0         0 return 1;
1068             }
1069             elsif ( ($tree->term_type() == T_OPERATOR) && ($tree->type() == B_EXP) ) {
1070             # op1 must be a variable. op2 must be an int > 0
1071 10         470 my $op1 = $tree->op1();
1072 10         63 my $exp = $tree->op2();
1073 10 50 66     63 if ( ($op1->term_type() == T_VARIABLE) &&
    100 33        
      33        
      66        
      66        
      33        
1074             ($exp->term_type() == T_CONSTANT) &&
1075             ($exp->value() eq int($exp->value())) &&
1076             ($exp->value() > 0) ) {
1077            
1078 0         0 for (0..$exp->value()-1) {
1079 0         0 push @{$l}, { type => 'variable', object => $op1->new() };
  0         0  
1080             }
1081 0         0 return 1;
1082             }
1083             elsif ( ($op1->term_type() == T_CONSTANT) &&
1084             ($exp->term_type() == T_CONSTANT) &&
1085             ($exp->value() eq int($exp->value())) &&
1086             ($exp->value() > 0) ) {
1087            
1088 4         181 my $result = $op1->value() ** $exp->value();
1089 4 50       66 if ( $result < $EXP_MAX ) {
1090 0         0 push @{$l}, { type => 'constant', object => Math::Symbolic::Constant->new($result) };
  0         0  
1091             }
1092             else {
1093 4         14 for (0..$exp->value()-1) {
1094 598         1446 my $obj = $op1->new();
1095 598         14579 $obj->{special} = $op1->value();
1096 598         4201 $obj->{name} = q{};
1097 598         1005 push @{$l}, { type => 'constant', object => $obj };
  598         2296  
1098             }
1099             }
1100 4         25 return 1;
1101             }
1102             }
1103             elsif ( ($tree->term_type() == T_OPERATOR) && ($tree->type() == B_PRODUCT) ) {
1104 481         22360 my $ok1 = get_product_elements_collect($l, $tree->op1());
1105 481         1279 my $ok2 = get_product_elements_collect($l, $tree->op2());
1106 481         1394 return $ok1 & $ok2;
1107             }
1108              
1109 10         277 return 0;
1110             }
1111              
1112             sub collect_terms {
1113 844     844 0 1793 my ($e) = @_;
1114              
1115 844         1306 my @elements = @{$e};
  844         2111  
1116              
1117 844         1578 my $accumulator = 0;
1118 844         1426 my %collected_terms;
1119 844         1452 my $tree_num = 1;
1120 844         1570 my %trees;
1121 844         2047 foreach my $e (@elements) {
1122 1444 100       9992 if ( ($e->{type} eq 'constant') ) {
    100          
    100          
    50          
1123 539 50       1755 if ( $e->{object}->special() eq '' ) {
1124 539         4553 $accumulator += $e->{object}->value();
1125             }
1126             else {
1127 0         0 my $name;
1128 0         0 GET_CONST_NAME_1: foreach my $n (grep { /^CONST/ } keys %trees) {
  0         0  
1129 0 0       0 if ( $e->{object}->is_identical($trees{$n}) ) {
1130 0         0 $name = $n;
1131 0         0 last GET_CONST_NAME_1;
1132             }
1133             }
1134 0 0       0 if ( not defined $name ) {
1135 0         0 $name = 'CONST' . "_" . $e->{object}->{special};
1136 0         0 $trees{$name} = $e->{object};
1137 0         0 $tree_num++;
1138             }
1139 0         0 $collected_terms{terms}{$name . ":1"}++;
1140             }
1141             }
1142             elsif ( $e->{type} eq 'variable' ) {
1143 147         288 my $name;
1144 147         539 GET_VAR_NAME_1: foreach my $n (grep { /^VAR/ } keys %trees) {
  15         87  
1145 15 100       223 if ( $e->{object}->is_identical($trees{$n}) ) {
1146 5         731 $name = $n;
1147 5         18 last GET_VAR_NAME_1;
1148             }
1149             }
1150 147 100       1050 if ( not defined $name ) {
1151 142         425 $name = 'VAR' . "_" . $e->{object}->{name};
1152 142         392 $trees{$name} = $e->{object};
1153 142         282 $tree_num++;
1154             }
1155 147         729 $collected_terms{terms}{$name . ":1"}++;
1156             }
1157             elsif ( $e->{type} eq 'function' ) {
1158 33         63 my $name;
1159 33         112 GET_FUNC_NAME_1: foreach my $n (grep { /^FUNC/ } keys %trees) {
  4         17  
1160 4 50       43 if ( $e->{object}->is_identical($trees{$n}) ) {
1161 0         0 $name = $n;
1162 0         0 last GET_FUNC_NAME_1;
1163             }
1164             }
1165 33 50       408 if ( not defined $name ) {
1166 33         86 $name = 'FUNC' . $tree_num;
1167 33         123 $trees{$name} = $e->{object};
1168 33         64 $tree_num++;
1169             }
1170 33         193 $collected_terms{terms}{$name . ":1"}++;
1171             }
1172             elsif ( $e->{type} eq 'products' ) {
1173 725         1213 my @list = @{$e->{list}};
  725         2217  
1174             # if it's a list of constants, fold them into the accumulator
1175 725 100       1511 my @con_list = grep { ($_->{type} eq 'constant') && ($_->{object}->special() eq '') } @list;
  4099         29487  
1176 725 100       1932 if (scalar(@con_list) == scalar(@list)) {
1177 4         6 my $c_n = 1;
1178 4         14 $c_n *= $_->{object}->value() for @list;
1179 4         23 $accumulator += $c_n;
1180             }
1181             else {
1182 721         1364 my $num_coeff = 1;
1183 721         1263 my %hist;
1184 721         1566 foreach my $l (@list) {
1185 4095 100       15499 if ( ($l->{type} eq 'constant') ) {
    100          
    50          
1186 2751 100       10619 if ( $l->{object}->special() eq '' ) {
1187 557         4598 $num_coeff *= $l->{object}->value();
1188             }
1189             else {
1190 2194         14136 my $name;
1191 2194         5026 GET_CONST_NAME_2: foreach my $n (grep { /^CONST/ } keys %trees) {
  2186         13845  
1192 2186 50       10956 if ( $l->{object}->is_identical($trees{$n}) ) {
1193 2186         123798 $name = $n;
1194 2186         4117 last GET_CONST_NAME_2;
1195             }
1196             }
1197 2194 100       5189 if ( not defined $name ) {
1198 8         34 $name = 'CONST' . "_" . $l->{object}->{special};
1199 8         40 $trees{$name} = $l->{object};
1200 8         21 $tree_num++;
1201             }
1202 2194         5585 $hist{$name}++;
1203             }
1204             }
1205             elsif ($l->{type} eq 'variable') {
1206 1250         2020 my $name;
1207 1250         3312 GET_VAR_NAME_2: foreach my $n (grep { /^VAR/ } keys %trees) {
  2328         5780  
1208 1731 100       37708 if ( $l->{object}->is_identical($trees{$n}) ) {
1209 855         73708 $name = $n;
1210 855         2041 last GET_VAR_NAME_2;
1211             }
1212             }
1213 1250 100       11319 if ( not defined $name ) {
1214 395         1182 $name = 'VAR' . "_" . $l->{object}->{name};
1215 395         1141 $trees{$name} = $l->{object};
1216 395         715 $tree_num++;
1217             }
1218 1250         3403 $hist{$name}++;
1219             }
1220             elsif ($l->{type} eq 'function') {
1221 94         170 my $name;
1222 94         310 GET_FUNC_NAME_2: foreach my $n (grep { /^FUNC/ } keys %trees) {
  117         347  
1223 82 100       2239 if ( $l->{object}->is_identical($trees{$n}) ) {
1224 44         8094 $name = $n;
1225 44         119 last GET_FUNC_NAME_2;
1226             }
1227             }
1228 94 100       1383 if ( not defined $name ) {
1229 50         143 $name = 'FUNC' . $tree_num;
1230 50         193 $trees{$name} = $l->{object};
1231 50         108 $tree_num++;
1232             }
1233 94         326 $hist{$name}++;
1234             }
1235             else {
1236 0         0 return (undef, undef);
1237             }
1238             }
1239 721         1317 my @str_elems;
1240 721         2230 foreach my $k (sort keys %hist) {
1241 1030         3784 push @str_elems, join(":", $k, $hist{$k});
1242             }
1243 721         1893 my $key = join(",", @str_elems);
1244 721         4271 $collected_terms{terms}{$key} += $num_coeff;
1245             }
1246             }
1247             }
1248            
1249             # Post-process for complex numbers
1250             # see if expression contains the designated complex variable.
1251 844         3682 my $contains_complex = 0;
1252 844         1387 my $complex_name;
1253 844         5361 GET_COMPLEX: foreach my $k (grep { /^VAR/ } keys %trees) {
  628         2231  
1254 522 100       2001 if ( $trees{$k}->{name} eq $COMPLEX_VAR ) {
1255 131         281 $contains_complex = 1;
1256 131         228 $complex_name = $k;
1257 131         341 last GET_COMPLEX;
1258             }
1259             }
1260              
1261 844 100       2998 if ( $contains_complex ) {
1262              
1263 131         238 my %c_ct_new;
1264              
1265 131         282 while ( my ($t, $c) = each %{$collected_terms{terms}} ) {
  308         1150  
1266 177         578 my @v1 = split(/,/, $t);
1267 177         370 my @nt;
1268 177         354 foreach my $v2 (@v1) {
1269 187         687 my ($vv, $cc) = split(/:/, $v2);
1270 187 100 100     1070 if (($vv eq $complex_name) && ($cc > 1) && ($cc == int($cc))) {
      66        
1271             # various results from different powers of the imaginary unit
1272 43         110 my $pmod = $cc % 4;
1273 43 100       238 if ( $pmod == 0 ) {
    100          
    100          
    50          
1274 2 50       8 if ( scalar(@v1) == 1 ) {
1275 2         6 $accumulator += $c;
1276             }
1277             }
1278             elsif ( $pmod == 1 ) {
1279 2         8 push @nt, "$vv:1";
1280             }
1281             elsif ( $pmod == 2 ) {
1282 30         64 $c *= -1;
1283 30 50       105 if ( scalar(@v1) == 1 ) {
1284 30         78 $accumulator += $c;
1285             }
1286             }
1287             elsif ( $pmod == 3 ) {
1288 9         17 $c *= -1;
1289 9         33 push @nt, "$vv:1";
1290             }
1291             }
1292             else {
1293 144         387 push @nt, $v2;
1294             }
1295             }
1296 177 100       504 if ( scalar(@nt) ) {
1297 145         370 my $nk = join(",", @nt);
1298 145 100       365 if ( exists $c_ct_new{$nk} ) {
1299 8         24 $c_ct_new{$nk} += $c;
1300             }
1301             else {
1302 137         494 $c_ct_new{$nk} = $c;
1303             }
1304             }
1305             }
1306              
1307 131         435 $collected_terms{terms} = \%c_ct_new;
1308             }
1309              
1310             # put the accumulator into the data structure
1311 844         2976 $collected_terms{terms}{constant_accumulator} = $accumulator;
1312             # and the functions
1313 844         2057 $collected_terms{trees} = \%trees;
1314              
1315 844         2573 return \%collected_terms;
1316             }
1317              
1318             sub get_term_name {
1319 1980     1980 0 4010 my ($tn, $thr) = @_;
1320              
1321 1980 100       4434 if ( $tn =~ /^VAR/ ) {
1322              
1323 1824         4418 my ($n,$p) = split(/:/, $tn);
1324 1824 50       4248 if ( exists $thr->{$n} ) {
1325 1824         3877 $tn = $thr->{$n}{name} . $p;
1326             }
1327             }
1328              
1329 1980         9318 return $tn;
1330             }
1331              
1332              
1333             sub build_summation_tree {
1334 1181     1181 0 2504 my ($ct) = @_;
1335 1181         3272 my %ct = %{$ct};
  1181         3621  
1336 1181         2250 my %collected_terms = %{$ct{terms}};
  1181         3789  
1337 1181         2083 my %trees = %{$ct{trees}};
  1181         2835  
1338              
1339 1181         2448 my $accumulator = $collected_terms{constant_accumulator};
1340 1181         2320 delete $collected_terms{constant_accumulator};
1341            
1342             # check if all coefficients are zero
1343 1181         2499 my @coeffs = values %collected_terms;
1344 1181         2208 my @zero = grep { $_ == 0 } @coeffs;
  850         2199  
1345 1181 100       2889 if ( scalar(@zero) == scalar(@coeffs) ) {
1346 655         1968 return Math::Symbolic::Constant->new( $accumulator );
1347             }
1348              
1349             # try to put the terms in a neat consistent order
1350 526         2036 my @sorted_terms = sort { length(get_term_name($a, \%trees)) <=> length(get_term_name($b, \%trees)) ||
1351             get_term_name($a, \%trees) cmp get_term_name($b, \%trees) ||
1352 556 50 100     1511 $collected_terms{$a} <=> $collected_terms{$b} }
1353             keys %collected_terms;
1354              
1355 526         1251 my @negative = grep { $_ <= 0 } @coeffs;
  838         1993  
1356 526         979 my $all_neg = 0;
1357 526 100       1493 if ( scalar(@negative) == scalar(@sorted_terms) ) {
1358 106         196 $all_neg = 1;
1359             }
1360              
1361             # generate the Math::Symbolic tree
1362 526         996 my @to_sum;
1363 526 100       1770 if ( $accumulator > 0 ) {
    100          
1364 71         346 push @to_sum, ['+', Math::Symbolic::Constant->new($accumulator)];
1365             }
1366             elsif ( $accumulator < 0 ) {
1367 44         225 push @to_sum, ['-', Math::Symbolic::Constant->new(abs($accumulator))];
1368             }
1369              
1370 526         3735 my $c = 0;
1371 526         1263 TERM_LOOP: foreach my $term (@sorted_terms) {
1372            
1373 838         1803 my $const = $collected_terms{$term};
1374 838 100       2449 next TERM_LOOP if $const == 0;
1375              
1376 832         1448 my @product_list;
1377 832         1493 my $sign = '+';
1378 832 100 100     3929 if ( $all_neg && ($c == 0) && ($accumulator == 0) ) {
    100 100        
1379             # keep first one negative
1380             }
1381             elsif ( ($const < 0) ) {
1382 177         396 $sign = '-';
1383 177         391 $const = abs($const);
1384             }
1385 832 100       1908 if ( $const != 1 ) {
1386 363         1418 push @product_list, Math::Symbolic::Constant->new( $const );
1387             }
1388              
1389 832         8802 my @vars = split(/,/, $term);
1390 832         1773 VAR_LOOP: foreach my $v (@vars) {
1391            
1392 1123         10205 my ($var, $pow) = split(/:/, $v);
1393 1123 50       3056 next VAR_LOOP if $pow == 0; #?? how would that get there?
1394              
1395 1123 50       2638 if ( exists $trees{$var} ) {
1396 1123 100       2354 if ( $pow == 1 ) {
1397 930         3139 push @product_list, $trees{$var}->new();
1398             }
1399             else {
1400 193         834 push @product_list, Math::Symbolic::Operator->new('^', $trees{$var}->new(), Math::Symbolic::Constant->new($pow));
1401             }
1402             }
1403             else {
1404 0         0 croak "build_summation_tree: Found something without an associated Math::Symbolic object!: $var";
1405             }
1406             }
1407              
1408 832         33908 my $ntp = shift @product_list;
1409 832         2199 while (@product_list) {
1410 654         4506 my $e = shift @product_list;
1411 654         2133 $ntp = Math::Symbolic::Operator->new( '*', $ntp, $e );
1412             }
1413              
1414 832         15874 push @to_sum, [$sign, $ntp];
1415 832         2468 $c++;
1416             }
1417              
1418 526 100       2885 @to_sum = sort { $a->[0] cmp $b->[0] } @to_sum if !$all_neg;
  536         1331  
1419              
1420 526         1784 my $first = shift @to_sum;
1421 526         1247 my $nt = $first->[1];
1422 526 100       1560 if ( $first->[0] eq '-' ) {
1423 11 50 33     53 if ( ($first->[1]->term_type() == T_CONSTANT) && ($first->[1]->special() eq '') ) {
1424             # folding -1 into constant
1425 11         157 $nt = Math::Symbolic::Constant->new(-1 * $first->[1]->value());
1426             }
1427             else {
1428             # FIXME: this feels like a bodge
1429 0         0 $nt = Math::Symbolic::Operator->new('neg', $nt);
1430             }
1431             }
1432              
1433 526         1775 while (@to_sum) {
1434 421         5092 my $e = shift @to_sum;
1435 421 100       1588 if ( $e->[0] eq '+' ) {
    50          
1436 211         688 $nt = Math::Symbolic::Operator->new( '+', $nt, $e->[1] );
1437             }
1438             elsif ( $e->[0] eq '-' ) {
1439 210         771 $nt = Math::Symbolic::Operator->new( '-', $nt, $e->[1] );
1440             }
1441             }
1442              
1443 526         13742 return $nt;
1444             }
1445              
1446             ###########################
1447              
1448             sub get_frac_GCF {
1449 373     373 0 3085 my ($n, $d) = @_;
1450              
1451 373 100       873 my $min = ($n < $d ? $n : $d);
1452 373         618 my $GCF = 1;
1453 373         1339 DIV_GCF: foreach my $div (reverse(2..$min)) {
1454 3157 100 100     7356 if ( (($n % $div) == 0) && (($d % $div) == 0) ) {
1455 55         93 $GCF = $div;
1456 55         146 last DIV_GCF;
1457             }
1458             }
1459              
1460 373         979 return $GCF;
1461             }
1462              
1463             sub prepare {
1464 12458     12458 0 80338 my ($t, $d) = @_;
1465              
1466 12458 100       25373 if ( defined $d ) {
1467 11777         18000 $d++;
1468             }
1469             else {
1470 681         1533 $d = 0;
1471             }
1472            
1473 12458         20660 my $op_arity = 0;
1474 12458 100       33943 if ( $t->term_type() == T_OPERATOR ) {
1475 5748         25747 $op_arity = $t->arity();
1476             }
1477            
1478 12458         54699 my $return_t;
1479            
1480 12458 100       27750 if ( $t->term_type() == T_VARIABLE ) {
    100          
    100          
    50          
1481 3459         21911 $return_t = $t->new();
1482             }
1483             elsif ( $t->term_type() == T_CONSTANT ) {
1484             # convert (non-integer decimal) constants into rational numbers where possible
1485 3251         20266 my $val = $t->value();
1486 3251 100 66     29207 if ( ($val eq int($val)) || length($t->special()) ) {
1487 3226         8139 $return_t = $t->new();
1488             }
1489             else {
1490 25         353 my (undef, $frac) = split(/\./, $val);
1491 25 50 33     200 if ( defined($frac) && (length($frac)>=1) && (length($frac)<10) ) {
      33        
1492 25         104 my $mult = 10**length($frac);
1493 25         60 my $n = $val*$mult;
1494 25         92 my $GCF = get_frac_GCF($n, $mult);
1495 25         179 $return_t = Math::Symbolic::Operator->new( '/', Math::Symbolic::Constant->new($n/$GCF), Math::Symbolic::Constant->new($mult/$GCF) );
1496             }
1497             else {
1498 0         0 $return_t = $t->new();
1499             }
1500             }
1501             }
1502             elsif ( $op_arity == 2 ) {
1503              
1504             # recursion.
1505 5346         38481 my $op1 = prepare( $t->op1(), $d );
1506 5346         14746 my $op2 = prepare( $t->op2(), $d );
1507            
1508             # collect some obvious constant expressions while we are in here.
1509             # also combine fractions to make it easier to collect like terms.
1510             # expand out brackets and indices where possible.
1511             #
1512             # here come the "hardly readable if-else blocks" Steffen warned of in Math::Symbolic::Custom::Transformation
1513 5346 100 0     14427 if ( $t->type() == B_SUM ) {
    100          
    100          
    100          
    100          
    50          
    0          
1514 1551 100 66     11421 if ( ($op1->term_type() == T_CONSTANT) && ($op1->special() eq '') && ($op2->term_type() == T_CONSTANT) && ($op2->special() eq '') ) {
    100 100        
    50 66        
    100 100        
    100 66        
    100 100        
      100        
      100        
      100        
      100        
1515             # Executing addition of two constants
1516 95         1953 $return_t = Math::Symbolic::Constant->new($op1->value() + $op2->value());
1517             }
1518             elsif ( ($op1->term_type() == T_CONSTANT) && ($op1->value() == 0) ) {
1519             # Removing addition with 0
1520 4         104 $return_t = $op2;
1521             }
1522             elsif ( ($op2->term_type() == T_CONSTANT) && ($op2->value() == 0) ) {
1523             # Removing addition with 0
1524 0         0 $return_t = $op1;
1525             }
1526             elsif ( ($op1->term_type() == T_OPERATOR) && ($op1->type() == B_DIVISION) &&
1527             ($op2->term_type() == T_OPERATOR) && ($op2->type() == B_DIVISION) ) {
1528             # Adding two fractions into one
1529 24         775 my $frac1 = $op1;
1530 24         50 my $frac2 = $op2;
1531 24         86 my $denom_left = $frac1->op2();
1532 24         140 my $denom_right = $frac2->op2();
1533              
1534 24 100       247 if ( $denom_left->is_identical($denom_right) ) {
1535 11         746 my $numerator = Math::Symbolic::Operator->new( '+', $frac1->op1(), $frac2->op1() );
1536 11         295 $return_t = prepare(Math::Symbolic::Operator->new( '/', $numerator, $denom_right ), $d);
1537             }
1538             else {
1539 13         1156 my $num_left = Math::Symbolic::Operator->new( '*', $frac1->op1(), $denom_right );
1540 13         542 my $num_right = Math::Symbolic::Operator->new( '*', $frac2->op1(), $denom_left );
1541 13         365 my $numerator = Math::Symbolic::Operator->new( '+', $num_left, $num_right );
1542 13         276 my $denominator = Math::Symbolic::Operator->new( '*', $denom_left, $denom_right );
1543 13         275 $return_t = prepare(Math::Symbolic::Operator->new( '/', $numerator, $denominator ), $d);
1544             }
1545             }
1546             elsif ( ($op1->term_type() == T_OPERATOR) && ($op1->type() == B_DIVISION) ) {
1547             # Merging sum into fraction
1548 10         602 my $numerator = $op1->op1();
1549 10         89 my $denominator = $op1->op2();
1550 10         95 my $m1 = Math::Symbolic::Operator->new( '*', $op2, $denominator );
1551 10         315 my $new_numerator = Math::Symbolic::Operator->new( '+', $numerator, $m1 );
1552 10         247 $return_t = prepare(Math::Symbolic::Operator->new( '/', $new_numerator, $denominator ), $d);
1553             }
1554             elsif ( ($op2->term_type() == T_OPERATOR) && ($op2->type() == B_DIVISION) ) {
1555             # Merging sum into fraction
1556 3         288 my $numerator = $op2->op1();
1557 3         24 my $denominator = $op2->op2();
1558 3         25 my $m1 = Math::Symbolic::Operator->new( '*', $op1, $denominator );
1559 3         122 my $new_numerator = Math::Symbolic::Operator->new( '+', $numerator, $m1 );
1560 3         102 $return_t = prepare(Math::Symbolic::Operator->new( '/', $new_numerator, $denominator ), $d);
1561             }
1562             else {
1563             # Passing through addition
1564 1415         56339 $return_t = Math::Symbolic::Operator->new('+', $op1, $op2) ;
1565             }
1566             }
1567             elsif ( $t->type() == B_DIFFERENCE ) {
1568 205 100 66     2560 if ( ($op1->term_type() == T_CONSTANT) && ($op1->special() eq '') && ($op2->term_type() == T_CONSTANT) && ($op2->special() eq '') ) {
    50 100        
    50 66        
    100 66        
    100 66        
    100 100        
      100        
      100        
      100        
      100        
1569             # Executing subtraction of two constants
1570 54         1002 $return_t = Math::Symbolic::Constant->new( $op1->value() - $op2->value() );
1571             }
1572             elsif ( ($op2->term_type() == T_CONSTANT) && ($op2->value() == 0) ) {
1573             # Removing subtraction of 0
1574 0         0 $return_t = $op1;
1575             }
1576             elsif ( ($op1->term_type() == T_CONSTANT) && ($op1->value() == 0) ) {
1577             # Changing subtraction from 0 to multiplication by -1
1578 0         0 my $ntp = Math::Symbolic::Operator->new( '*', Math::Symbolic::Constant->new(-1), $op2 );
1579 0         0 $return_t = prepare($ntp, $d);
1580             }
1581             elsif ( ($op1->term_type() == T_OPERATOR) && ($op1->type() == B_DIVISION) &&
1582             ($op2->term_type() == T_OPERATOR) && ($op2->type() == B_DIVISION) ) {
1583             # Subtracting two fractions into one
1584 19         786 my $frac1 = $op1;
1585 19         46 my $frac2 = $op2;
1586 19         67 my $denom_left = $frac1->op2();
1587 19         121 my $denom_right = $frac2->op2();
1588              
1589 19 100       234 if ( $denom_left->is_identical($denom_right) ) {
1590 3         592 my $numerator = Math::Symbolic::Operator->new( '-', $frac1->op1(), $frac2->op1() );
1591 3         110 $return_t = prepare(Math::Symbolic::Operator->new( '/', $numerator, $denom_right ), $d);
1592             }
1593             else {
1594 16         1483 my $num_left = Math::Symbolic::Operator->new( '*', $frac1->op1(), $denom_right );
1595 16         547 my $num_right = Math::Symbolic::Operator->new( '*', $frac2->op1(), $denom_left );
1596 16         437 my $numerator = Math::Symbolic::Operator->new( '-', $num_left, $num_right );
1597 16         441 my $denominator = Math::Symbolic::Operator->new( '*', $denom_left, $denom_right );
1598 16         358 $return_t = prepare(Math::Symbolic::Operator->new( '/', $numerator, $denominator ), $d);
1599             }
1600             }
1601             elsif ( ($op1->term_type() == T_OPERATOR) && ($op1->type() == B_DIVISION) ) {
1602             # Merging subtraction into fraction
1603 3         146 my $numerator = $op1->op1();
1604 3         20 my $denominator = $op1->op2();
1605 3         18 my $m1 = Math::Symbolic::Operator->new( '*', $op2, $denominator );
1606 3         69 my $new_numerator = Math::Symbolic::Operator->new( '-', $numerator, $m1 );
1607 3         58 $return_t = prepare(Math::Symbolic::Operator->new( '/', $new_numerator, $denominator ), $d);
1608             }
1609             elsif ( ($op2->term_type() == T_OPERATOR) && ($op2->type() == B_DIVISION) ) {
1610             # Merging subtraction into fraction
1611 4         222 my $numerator = $op2->op1();
1612 4         27 my $denominator = $op2->op2();
1613 4         25 my $m1 = Math::Symbolic::Operator->new( '*', $op1, $denominator );
1614 4         90 my $new_numerator = Math::Symbolic::Operator->new( '-', $m1, $numerator );
1615 4         71 $return_t = prepare(Math::Symbolic::Operator->new( '/', $new_numerator, $denominator ), $d);
1616             }
1617             else {
1618             # Converting subtraction into addition
1619 125         6314 my $ntp = Math::Symbolic::Operator->new('*', Math::Symbolic::Constant->new(-1), $op2);
1620 125         6133 $ntp = Math::Symbolic::Operator->new('+', $op1, $ntp);
1621 125         3354 $return_t = prepare($ntp, $d);
1622             }
1623             }
1624             elsif ( $t->type() == B_DIVISION ) {
1625 485 50 66     8688 if ( ($op2->term_type() == T_CONSTANT) && ($op2->value() == 0) ) {
    100 100        
    100 100        
    50 100        
    100 100        
    100 66        
    100 66        
    50 100        
    100 66        
    100 66        
    100 100        
    100 100        
    50 100        
      66        
      100        
      100        
      66        
      33        
      33        
      100        
      100        
      100        
      100        
      100        
      100        
      66        
      66        
      33        
      66        
      33        
      33        
      0        
      0        
1626             # Division by zero found
1627 0         0 croak "prepare: Division by zero found. Refusing to proceed."; # TODO
1628             }
1629             elsif ( ($op1->term_type() == T_CONSTANT) && ($op1->value() == 0) ) {
1630             # Dividing something into 0. Returning 0
1631 4         100 $return_t = Math::Symbolic::Constant->new(0);
1632             }
1633             elsif ( ($op2->term_type() == T_CONSTANT) && ($op2->value() == 1) ) {
1634             # Division by unity found, removing division
1635 7         345 $return_t = $op1;
1636             }
1637             elsif ( ($op1->term_type() == T_VARIABLE) && ($op2->term_type() == T_VARIABLE) &&
1638             ($op1->name() eq $op2->name()) && test_constraint($op1->name(), 'nonzero') ) {
1639 0         0 $return_t = Math::Symbolic::Constant->new(1);
1640             }
1641             # TODO: more complicated expressions, e.g. (y*x)/x
1642             elsif ( ($op1->term_type() == T_CONSTANT) && ($op1->special() eq '') && ($op2->term_type() == T_CONSTANT) && ($op2->special() eq '') ) {
1643 274 100 33     14207 if ( ($op1->value()/$op2->value()) eq int($op1->value()/$op2->value()) ) {
    50          
1644             # Denominator evenly divides into numerator, removing division
1645 18         392 $return_t = Math::Symbolic::Constant->new($op1->value()/$op2->value())
1646             }
1647             elsif ( ($op1->value() == int($op1->value())) && ($op2->value() == int($op2->value())) ) {
1648             # Cancel down constant fraction
1649 256         11120 my $GCF = get_frac_GCF( abs($op1->value()), abs($op2->value()) );
1650 256         924 $return_t = Math::Symbolic::Operator->new('/', Math::Symbolic::Constant->new($op1->value()/$GCF), Math::Symbolic::Constant->new($op2->value()/$GCF));
1651             }
1652             else {
1653             # Passing through division
1654 0         0 $return_t = Math::Symbolic::Operator->new('/', $op1, $op2);
1655             }
1656             }
1657             elsif ( ($op2->term_type() == T_CONSTANT) && ($op2->special() eq '') && ($op2->value() < 0) ) {
1658             # Pulling negative out of denominator
1659 9         477 my $numerator = Math::Symbolic::Operator->new( '*', Math::Symbolic::Constant->new(-1), $op1 );
1660 9         414 $return_t = prepare(Math::Symbolic::Operator->new( '/', $numerator, Math::Symbolic::Constant->new(abs($op2->value())) ), $d);
1661             }
1662             elsif ( ($op2->term_type() == T_OPERATOR) && ($op2->type() == B_PRODUCT) &&
1663             ($op2->op1()->term_type() == T_CONSTANT) && ($op2->op1()->special() eq '') && ($op2->op1()->value() < 0) ) {
1664             # Pulling negative out of denominator
1665 3         302 my $numerator = Math::Symbolic::Operator->new( '*', Math::Symbolic::Constant->new(-1), $op1 );
1666 3         120 my $denominator = Math::Symbolic::Operator->new( '*', Math::Symbolic::Constant->new(abs($op2->op1()->value())), $op2->op2()->new() );
1667 3         160 $return_t = prepare(Math::Symbolic::Operator->new( '/', $numerator, $denominator ), $d);
1668             }
1669             elsif ( ($op2->term_type() == T_OPERATOR) && ($op2->type() == B_PRODUCT) &&
1670             ($op2->op2()->term_type() == T_CONSTANT) && ($op2->op2()->special() eq '') && ($op2->op2()->value() < 0) ) {
1671             # Pulling negative out of denominator
1672 0         0 my $numerator = Math::Symbolic::Operator->new( '*', Math::Symbolic::Constant->new(-1), $op1->new() );
1673 0         0 my $denominator = Math::Symbolic::Operator->new( '*', $op2->op1()->new(), Math::Symbolic::Constant->new(abs($op2->op2()->value())) );
1674 0         0 $return_t = prepare(Math::Symbolic::Operator->new( '/', $numerator, $denominator ), $d);
1675             }
1676             elsif ( ($op1->term_type() == T_OPERATOR) && ($op1->type() == B_DIVISION) &&
1677             ($op2->term_type() == T_OPERATOR) && ($op2->type() == B_DIVISION) ) {
1678             # Dividing two fractions into one fraction
1679 4         475 my $numerator = Math::Symbolic::Operator->new( '*', $op1->op1(), $op2->op2() );
1680 4         169 my $denominator = Math::Symbolic::Operator->new( '*', $op1->op2(), $op2->op1() );
1681 4         109 $return_t = prepare(Math::Symbolic::Operator->new( '/', $numerator, $denominator ), $d);
1682             }
1683             elsif ( ($op1->term_type() == T_OPERATOR) && ($op1->type() == B_DIVISION) ) {
1684             # Numerator is fraction, dividing into one fraction
1685 28         2072 $return_t = prepare(Math::Symbolic::Operator->new('/', $op1->op1(), Math::Symbolic::Operator->new('*', $op1->op2(), $op2)), $d);
1686             }
1687             elsif ( ($op2->term_type() == T_OPERATOR) && ($op2->type() == B_DIVISION) ) {
1688             # Denominator is fraction, dividing into one fraction
1689 2         124 $return_t = prepare(Math::Symbolic::Operator->new('/', Math::Symbolic::Operator->new('*', $op1, $op2->op2()), $op2->op1()), $d);
1690             }
1691             # TODO: check these rules
1692             elsif ( ($op1->term_type() == T_OPERATOR) && ($op1->type() == B_EXP) &&
1693             ($op2->term_type() == T_OPERATOR) && ($op2->type() == B_EXP) &&
1694             $op1->op1()->is_identical($op2->op1())
1695             ) {
1696             # x^m / x^n = x^(m-n)
1697 2         324 my $sum = Math::Symbolic::Operator->new('-', $op1->op2(), $op2->op2());
1698 2         74 $return_t = prepare(Math::Symbolic::Operator->new( '^', $op1->op1(), $sum->to_collected() ), $d);
1699             }
1700             elsif ( ($op1->term_type() == T_OPERATOR) && ($op1->type() == B_EXP) &&
1701             ($op2->term_type() == T_CONSTANT) && ($op2->value() != 0) &&
1702             ($op1->op1()->term_type() == T_CONSTANT) && ($op1->op1()->value() == $op2->value())
1703             ) {
1704              
1705             # x^m / x = x^m-1
1706 0         0 my $sum = Math::Symbolic::Operator->new('-', $op1->op2(), Math::Symbolic::Constant->new(1));
1707 0         0 $return_t = prepare(Math::Symbolic::Operator->new( '^', $op1->op1(), $sum->to_collected() ), $d);
1708             }
1709             else {
1710             # Passing through division
1711 152         21746 $return_t = Math::Symbolic::Operator->new('/', $op1, $op2);
1712             }
1713             }
1714             elsif ( $t->type() == B_PRODUCT ) {
1715 2924 100 100     64542 if ( (($op1->term_type() == T_CONSTANT) && ($op1->value() == 0)) ||
    100 100        
    100 100        
    100 100        
    100 100        
    100 66        
    100 100        
    100 66        
    50 100        
    100 100        
    100 100        
      100        
      100        
      100        
      100        
      100        
      66        
      100        
      66        
      100        
      66        
      100        
      100        
      100        
      100        
      66        
      66        
      66        
      66        
      66        
1716             (($op2->term_type() == T_CONSTANT) && ($op2->value() == 0))
1717             ) {
1718             # Multiplication by 0. Returning 0
1719 2         22 $return_t = Math::Symbolic::Constant->new(0);
1720             }
1721             elsif ( ($op1->term_type() == T_CONSTANT) && ($op1->value() == 1) ) {
1722             # Removing multiply by unity
1723 88         2542 $return_t = $op2;
1724             }
1725             elsif ( ($op2->term_type() == T_CONSTANT) && ($op2->value() == 1) ) {
1726             # Removing multiply by unity
1727 17         757 $return_t = $op1;
1728             }
1729             elsif ( ($op1->term_type() == T_CONSTANT) && ($op1->special() eq '') && ($op2->term_type() == T_CONSTANT) && ($op2->special() eq '') ) {
1730             # Executing multiplication of two constants
1731 172         9397 $return_t = Math::Symbolic::Constant->new($op1->value() * $op2->value());
1732             }
1733             elsif ( ($op1->term_type() == T_OPERATOR) && ($op1->type() == B_DIVISION) &&
1734             ($op2->term_type() == T_OPERATOR) && ($op2->type() == B_DIVISION) ) {
1735             # Multiplying two fractions into one fraction
1736 16         627 my $numerator = Math::Symbolic::Operator->new( '*', $op1->op1(), $op2->op1() );
1737 16         707 my $denominator = Math::Symbolic::Operator->new( '*', $op1->op2(), $op2->op2() );
1738 16         436 $return_t = Math::Symbolic::Operator->new( '/', prepare($numerator, $d), prepare($denominator, $d) );
1739             }
1740             elsif ( ($op1->term_type() == T_OPERATOR) && ($op1->type() == B_DIVISION) ) {
1741             # Multiplying with a fraction
1742 38         3610 my $numerator = Math::Symbolic::Operator->new( '*', $op1->op1(), $op2 );
1743 38         1390 $return_t = Math::Symbolic::Operator->new( '/', prepare($numerator, $d), $op1->op2() );
1744             }
1745             elsif ( ($op2->term_type() == T_OPERATOR) && ($op2->type() == B_DIVISION) ) {
1746             # Multiplying with a fraction
1747 37         2354 my $numerator = Math::Symbolic::Operator->new( '*', $op2->op1(), $op1 );
1748 37         1456 $return_t = Math::Symbolic::Operator->new( '/', prepare($numerator, $d), $op2->op2() );
1749             }
1750             elsif ( ($op1->term_type() == T_OPERATOR) && ($op1->type() == B_EXP) &&
1751             ($op2->term_type() == T_OPERATOR) && ($op2->type() == B_EXP) &&
1752             $op1->op1()->is_identical($op2->op1())
1753             ) {
1754             # x^m * x^n = x^(m+n)
1755 6         1044 my $sum = Math::Symbolic::Operator->new('+', $op1->op2(), $op2->op2());
1756 6         163 $return_t = prepare(Math::Symbolic::Operator->new( '^', $op1->op1(), $sum->to_collected() ), $d);
1757             }
1758             elsif ( ($op1->term_type() == T_OPERATOR) && ($op1->type() == B_EXP) &&
1759             (($op2->term_type() == T_CONSTANT) || ($op2->term_type() == T_VARIABLE)) && # FIXME: could it be any expression? (and below)
1760             $op1->op1()->is_identical($op2)
1761             ) {
1762             # x^m * x = x^(m+1)
1763 0         0 my $sum = Math::Symbolic::Operator->new('+', $op1->op2(), 1);
1764 0         0 $return_t = prepare(Math::Symbolic::Operator->new( '^', $op1->op1(), $sum->to_collected() ), $d);
1765             }
1766             elsif ( ($op2->term_type() == T_OPERATOR) && ($op2->type() == B_EXP) &&
1767             (($op1->term_type() == T_CONSTANT) || ($op1->term_type() == T_VARIABLE)) &&
1768             $op2->op1()->is_identical($op1)
1769             ) {
1770             # x * x^m = x^(m+1)
1771 2         272 my $sum = Math::Symbolic::Operator->new('+', $op2->op2(), 1);
1772 2         3654 $return_t = prepare(Math::Symbolic::Operator->new( '^', $op2->op1(), $sum->to_collected() ), $d);
1773             }
1774             elsif ( (($op1->term_type() == T_OPERATOR) && (($op1->type() == B_SUM) || ($op1->type() == B_DIFFERENCE))) ||
1775             (($op2->term_type() == T_OPERATOR) && (($op2->type() == B_SUM) || ($op2->type() == B_DIFFERENCE))) ) {
1776             # Attempting to multiply out brackets
1777 183         15901 my @elements1;
1778             my @elements2;
1779 183         945 my $good = get_elements( \@elements1, '+', $op1 );
1780              
1781 183 100       697 if ( $good ) {
1782 181         436 my $sign = '+';
1783 181 50 66     598 $sign = '-' if ($op2->term_type() == T_OPERATOR) && ($op2->type() == B_DIFFERENCE);
1784 181         2225 $good = get_elements( \@elements2, $sign, $op2 );
1785             }
1786            
1787 183 100       501 if ( $good ) {
1788             # Contents of operands look okay for multiplying out
1789 179         391 my @to_sum;
1790 179         763 foreach my $elem1 (sort { $a->{type} cmp $b->{type} } @elements1) {
  254         887  
1791 385         1560 foreach my $elem2 (sort { $a->{type} cmp $b->{type} } @elements2) {
  504         1487  
1792 784 50 66     5671 next if ($elem1->{type} eq 'constant') && ($elem1->{object}->value() == 0);
1793 784 50 66     4904 next if ($elem2->{type} eq 'constant') && ($elem2->{object}->value() == 0);
1794              
1795 784 100 100     6132 if ( exists($elem1->{list}) && exists($elem2->{list}) ) {
    100          
    100          
1796 118         201 my $num_entries = scalar(@{$elem2->{list}});
  118         260  
1797 118         250 push @{$elem1->{list}}, @{$elem2->{list}};
  118         252  
  118         349  
1798 118         324 push @to_sum, create_element( $elem1 );
1799 118         399 pop @{$elem1->{list}} for (1..$num_entries);
  245         642  
1800             }
1801             elsif ( exists($elem1->{list}) ) {
1802 185         363 push @{$elem1->{list}}, $elem2;
  185         580  
1803 185         572 push @to_sum, create_element( $elem1 );
1804 185         429 pop @{$elem1->{list}};
  185         592  
1805            
1806             }
1807             elsif ( exists($elem2->{list}) ) {
1808 216         371 push @{$elem2->{list}}, $elem1;
  216         497  
1809 216         561 push @to_sum, create_element( $elem2 );
1810 216         442 pop @{$elem2->{list}};
  216         712  
1811             }
1812             else {
1813 265         1211 my $e = { type => 'products', list => [ $elem1, $elem2 ] };
1814 265         896 push @to_sum, create_element( $e );
1815             }
1816             }
1817             }
1818              
1819 179 50       577 if ( scalar(@to_sum) == 0 ) {
1820 0         0 $return_t = Math::Symbolic::Constant->new(0);
1821             }
1822             else {
1823 179         386 my $ntp = shift @to_sum;
1824 179         623 while (@to_sum) {
1825 605         9852 my $e = shift @to_sum;
1826 605         1554 $ntp = Math::Symbolic::Operator->new( '+', $ntp, $e );
1827             }
1828 179         6250 $return_t = prepare($ntp, $d);
1829             }
1830             }
1831             else {
1832             # Contents of operands NOT ready for multiplying out. Passing through
1833 4         11 $return_t = Math::Symbolic::Operator->new('*', $op1, $op2);
1834             }
1835             }
1836             else {
1837             # Passing through multiplication
1838 2363         158816 $return_t = Math::Symbolic::Operator->new('*', $op1, $op2);
1839             }
1840             }
1841             elsif ( $t->type() == B_LOG ) {
1842            
1843 5 100 100     145 if ( ($op2->term_type() == T_CONSTANT) && ($op2->value() == 1) ) {
    50          
1844             # Zero rule: log(b,1) = 0
1845 1         19 $return_t = Math::Symbolic::Constant->new(0);
1846             }
1847             elsif ( $op1->term_type() == T_CONSTANT ) {
1848             # detect invalid bases
1849 4 50 66     56 if ($op1->value() <= 0) {
    50 66        
    100          
    100          
1850 0         0 croak "Base in logarithm is not greater than zero. [$t]";
1851             }
1852             elsif ( $op1->value() == 1 ) {
1853 0         0 croak "Base in logarithm equals 1. [$t]";
1854             }
1855             # for the moment, only apply these rules to constant bases
1856             elsif ( ($op2->term_type() == T_CONSTANT) && ($op1->value() == $op2->value()) ) {
1857             # Identity rule: log(b,b) = 1
1858 1         42 $return_t = Math::Symbolic::Constant->new(1);
1859             }
1860             elsif ( ($t->op2()->term_type() == T_OPERATOR) && ($t->op2()->type() == B_EXP) ) {
1861 1 50 33     30 if ( ($t->op2()->op1()->term_type == T_CONSTANT) && ($t->op2()->op1()->value() == $op1->value()) ) {
1862             # Inverse rule: log(b, b^x) = x
1863 1         23 $return_t = $op2->op2()->new();
1864             }
1865             else {
1866             # Power rule: log(b, x^n) = n*log(b,x)
1867 0         0 my $mul_op1 = prepare($t->op2()->op2(), $d);
1868 0         0 my $mul_op2 = prepare($t->op2()->op1(), $d);
1869 0         0 $return_t = Math::Symbolic::Operator->new('*', $mul_op1, Math::Symbolic::Operator->new('log', $t->op1()->new(), $mul_op2));
1870             }
1871             }
1872             }
1873              
1874 5 100       148 unless ( defined $return_t ) {
1875             # Passing through logarithm
1876 2         9 $return_t = Math::Symbolic::Operator->new('log', $op1, $op2);
1877             }
1878             }
1879             elsif ( $t->type() == B_EXP ) {
1880              
1881 176 100 100     6428 if ( ($op1->term_type() == T_OPERATOR) && ($op1->type() == B_EXP) ) {
    100 100        
    100 100        
    100 66        
      66        
      66        
      66        
      66        
      33        
      66        
      66        
      66        
      33        
1882 1         17 $return_t = prepare( Math::Symbolic::Operator->new('^', $op1->op1(), Math::Symbolic::Operator->new('*', $op1->op2(), $op2)), $d);
1883             }
1884             elsif ( ($op1->term_type() == T_CONSTANT) && ($op2->term_type() == T_OPERATOR) && ($op2->type() == B_LOG) &&
1885             ($op2->op1()->term_type() == T_CONSTANT) && ($op2->op1()->value() == $op1->value()) ) {
1886             # Inverse rule of exponents of logs: b^log(b,x) = x
1887 1         35 $return_t = $op2->op2()->new();
1888             }
1889             elsif ( ($op2->term_type() == T_CONSTANT) && ($op2->special() eq '') ) {
1890            
1891 122         3070 my $val = $op2->value();
1892              
1893 122 50 66     1697 if ( $val == 0 ) {
    100          
    100          
    50          
    50          
1894 0         0 $return_t = Math::Symbolic::Constant->new(1);
1895             }
1896             elsif ( $val == 1 ) {
1897             # Found expression^1. Return the expression
1898 5         17 $return_t = $op1;
1899             }
1900             elsif ( ($val > 1) && ($val eq int($val)) ) {
1901              
1902 111 100 66     327 if ( ($op1->term_type() == T_CONSTANT) && ($op1->special() eq '') ) {
1903 36         380 $return_t = Math::Symbolic::Operator->new('^', $op1->new(), $op2->new());
1904             }
1905             else {
1906              
1907             # Found constant positive integer power. Removing exponent through multiplication
1908 75         677 my @product_list = ($op1) x $val;
1909 75         234 my $ntp = shift @product_list;
1910 75         325 while (@product_list) {
1911 102         920 my $e = shift @product_list;
1912 102         431 $ntp = Math::Symbolic::Operator->new( '*', $ntp, $e );
1913             }
1914 75         2328 $return_t = prepare($ntp, $d);
1915             }
1916             }
1917             elsif ( $val == -1 ) {
1918             # remove negative index
1919 0         0 $return_t = prepare( Math::Symbolic::Operator->new('/', Math::Symbolic::Constant->new(1), $op1), $d );
1920             }
1921             elsif ( $val < -1 ) {
1922 6         45 $return_t = prepare( Math::Symbolic::Operator->new( '/',
1923             Math::Symbolic::Constant->new(1),
1924             Math::Symbolic::Operator->new('^', $op1, Math::Symbolic::Constant->new(abs($val)))
1925             ), $d);
1926             }
1927             else {
1928             # Passing through exponentiation
1929 0         0 my $op1_col;
1930 0 0       0 if ( $op1->term_type() == T_OPERATOR ) {
1931 0         0 $op1_col = $op1->to_collected(); # try to collect up the subexpression
1932             }
1933 0 0 0     0 if ( defined($op1_col) && !$op1->is_identical($op1_col) ) {
1934 0         0 $op1 = $op1_col;
1935             }
1936              
1937 0         0 $return_t = Math::Symbolic::Operator->new('^', $op1, $op2);
1938             }
1939             }
1940             elsif ( ($op1->term_type() == T_CONSTANT) && ($op1->special() eq '') &&
1941             ($op2->term_type() == T_OPERATOR) && ($op2->type() == B_DIVISION) &&
1942             ($op2->op1()->term_type == T_CONSTANT) && ($op2->op1()->value() == 1) &&
1943             ($op2->op2()->term_type == T_CONSTANT) && ($op2->op2()->value() == 2)
1944             ) {
1945              
1946 24 100       2198 if ( $op1->value() < 0 ) {
    50          
1947 10         133 $return_t = Math::Symbolic::Operator->new('*', Math::Symbolic::Variable->new($COMPLEX_VAR),
1948             prepare(Math::Symbolic::Operator->new('^', Math::Symbolic::Constant->new(abs($op1->value())), $op2), $d));
1949             }
1950             elsif ( $op1->value() == 0 ) {
1951 0         0 $return_t = Math::Symbolic::Constant->new(0);
1952             }
1953             else {
1954             # sqrt of a positive constant.
1955 14         214 my $sqrt = sqrt($op1->value());
1956 14 50       114 if ( $sqrt == int($sqrt) ) {
1957 14         49 $return_t = Math::Symbolic::Constant->new($sqrt);
1958             }
1959             else {
1960             # Passing through exponentiation
1961 0         0 $return_t = Math::Symbolic::Operator->new('^', $op1, $op2 );
1962             }
1963             }
1964             }
1965             else {
1966             # Passing through exponentiation
1967 28         616 my $op1_col;
1968 28 100       87 if ( $op1->term_type() == T_OPERATOR ) {
1969 3         46 $op1_col = $op1->to_collected();
1970             }
1971 28 50 66     213 if ( defined($op1_col) && !$op1->is_identical($op1_col) ) {
1972 0         0 $op1 = $op1_col;
1973             }
1974              
1975 28         943 my $op2_col;
1976 28 100       90 if ( $op2->term_type() == T_OPERATOR ) {
1977 25         230 $op2_col = $op2->to_collected();
1978             }
1979 28 100 100     310 if ( defined($op2_col) && !$op2->is_identical($op2_col) ) {
1980 5         408 $op2 = $op2_col;
1981             }
1982              
1983 28         2955 $return_t = Math::Symbolic::Operator->new('^', $op1, $op2);
1984             }
1985             }
1986             elsif ( ($t->type() == U_P_DERIVATIVE) || ($t->type() == U_T_DERIVATIVE) ) {
1987             # pass through derivative operators, but try collect up the internal expression
1988 0         0 my $op1_col = $op1->to_collected();
1989 0 0       0 if ( defined $op1_col ) {
1990 0         0 $op1 = $op1_col;
1991             }
1992 0         0 my $op_type = 'partial_derivative';
1993 0 0       0 $op_type = 'total_derivative' if $t->type() == U_T_DERIVATIVE;
1994 0         0 $return_t = Math::Symbolic::Operator->new($op_type, $op1, $op2);
1995             }
1996             else {
1997 0         0 my $o = $t->new();
1998 0         0 $o->{operands}[0] = $op1;
1999 0         0 $o->{operands}[1] = $op2;
2000 0         0 $return_t = $o;
2001             }
2002             }
2003             elsif ( $op_arity == 1 ) {
2004              
2005 402         3837 my $op1 = prepare($t->op1(), $d);
2006            
2007 402 100       1363 if ( $t->type() == U_MINUS ) {
2008 160 100 66     1450 if ( ($op1->term_type() == T_CONSTANT) && ($op1->special() eq '') ) {
2009             # Removing negation of a constant by directly folding multiplication of -1 into that constant
2010 99         1652 $return_t = Math::Symbolic::Constant->new( -1*$op1->value() );
2011             }
2012             else {
2013             # Replacing negation by multiplication of subexpression by -1
2014 61         583 my $ntp = Math::Symbolic::Operator->new('*', Math::Symbolic::Constant->new(-1), $op1);
2015 61         2706 $return_t = prepare($ntp, $d);
2016             }
2017             }
2018             else {
2019 242         2060 my $o = $t->new();
2020 242         23597 $o->{operands}[0] = $op1;
2021 242         595 $return_t = $o;
2022             }
2023             }
2024             else {
2025 0         0 croak "prepare: cannot process operator with arity [$op_arity]";
2026             }
2027              
2028 12458 50       301265 if ( not defined $return_t ) {
2029 0         0 croak "prepare: reached end. Cannot process";
2030             }
2031              
2032 12458         33120 return $return_t;
2033             }
2034              
2035             # unfortunately, these routines are slightly different to the ones for collecting like terms
2036             sub get_elements {
2037 1200     1200 0 6542 my ($l, $s, $tree) = @_;
2038              
2039 1200 100 66     2922 if ( $tree->term_type() == T_VARIABLE ) {
    100 33        
    100 66        
    50 66        
    100 33        
    100          
    50          
2040 210         1148 my $r = { type => 'variable', object => $tree };
2041 210 50       512 if ( $s eq '+' ) {
    0          
2042 210         346 push @{$l}, $r;
  210         546  
2043             }
2044             elsif ( $s eq '-' ) {
2045 0         0 push @{$l}, { type => 'products', list => [ { type => 'constant', object => Math::Symbolic::Constant->new(-1) }, $r ] };
  0         0  
2046             }
2047 210         523 return 1;
2048             }
2049             elsif ( $tree->term_type() == T_CONSTANT ) {
2050 200         1765 my $r = { type => 'constant', object => $tree };
2051 200 50       663 if ( $s eq '+' ) {
    0          
2052 200         367 push @{$l}, $r;
  200         489  
2053             }
2054             elsif ( $s eq '-' ) {
2055 0         0 push @{$l}, { type => 'products', list => [ { type => 'constant', object => Math::Symbolic::Constant->new(-1) }, $r ] };
  0         0  
2056             }
2057 200         604 return 1;
2058             }
2059             elsif ( ($tree->term_type() == T_OPERATOR) && ($tree->arity() == 1) ) {
2060 25         478 my $r = { type => 'function', object => $tree };
2061 25 50       89 if ( $s eq '+' ) {
    0          
2062 25         48 push @{$l}, $r;
  25         64  
2063             }
2064             elsif ( $s eq '-' ) {
2065 0         0 push @{$l}, { type => 'products', list => [ { type => 'constant', object => Math::Symbolic::Constant->new(-1) }, $r ] };
  0         0  
2066             }
2067 25         81 return 1;
2068             }
2069             elsif ( ($tree->term_type() == T_OPERATOR) && ($tree->type() == B_DIVISION) ) {
2070 0         0 my $r = { type => 'fraction', num => $tree->op1(), den => $tree->op2() };
2071 0 0       0 if ( $s eq '+' ) {
    0          
2072 0         0 push @{$l}, $r;
  0         0  
2073             }
2074             elsif ( $s eq '-' ) {
2075 0         0 push @{$l}, { type => 'products', list => [ { type => 'constant', object => Math::Symbolic::Constant->new(-1) }, $r ] };
  0         0  
2076             }
2077 0         0 return 1;
2078             }
2079             elsif ( ($tree->term_type() == T_OPERATOR) && ($tree->type() == B_PRODUCT) ) {
2080 344         10817 my @product_elements;
2081 344         917 my $ok1 = get_product_elements(\@product_elements, $tree->op1());
2082 344         1038 my $ok2 = get_product_elements(\@product_elements, $tree->op2());
2083 344         1648 my @sorted = sort { $a->{type} cmp $b->{type} } @product_elements;
  527         1984  
2084              
2085 344 100 66     2279 if ( $ok1 && $ok2 ) {
2086 343 50       1046 if ( $s eq '-' ) {
2087 0         0 push @sorted, { type => 'constant', object => Math::Symbolic::Constant->new(-1) };
2088             }
2089 343         1198 push @{$l}, { type => 'products', list => \@sorted };
  343         1360  
2090 343         1198 return 1;
2091             }
2092 1         4 return 0;
2093             }
2094             elsif ( ($tree->term_type() == T_OPERATOR) && ($tree->type() == B_SUM) ) {
2095 418         19086 my $ok1 = get_elements($l, '+', $tree->op1());
2096 418         1230 my $ok2 = get_elements($l, '+', $tree->op2());
2097 418         1421 return $ok1 & $ok2;
2098             }
2099             elsif ( ($tree->term_type() == T_OPERATOR) && ($tree->type() == B_DIFFERENCE) ) {
2100 0         0 my $ok1 = get_elements($l, '+', $tree->op1());
2101 0         0 my $ok2 = get_elements($l, '-', $tree->op2());
2102 0         0 return $ok1 & $ok2;
2103             }
2104              
2105 3         144 return 0;
2106             }
2107              
2108             sub get_product_elements {
2109 942     942 0 5605 my ($l, $tree) = @_;
2110              
2111 942 100 66     3083 if ( $tree->term_type() == T_VARIABLE ) {
    100 33        
    100 66        
    50 66        
    100          
    100          
2112 528         11297 push @{$l}, { type => 'variable', object => $tree, };
  528         1832  
2113 528         1207 return 1;
2114             }
2115             elsif ( $tree->term_type() == T_CONSTANT ) {
2116 274         1873 push @{$l}, { type => 'constant', object => $tree, };
  274         1094  
2117 274         690 return 1;
2118             }
2119             elsif ( ($tree->term_type() == T_OPERATOR) && ($tree->arity() == 1) ) {
2120 11         188 push @{$l}, { type => 'function', object => $tree, };
  11         49  
2121 11         40 return 1;
2122             }
2123             elsif ( ($tree->term_type() == T_OPERATOR) && ($tree->type() == B_DIVISION) ) {
2124 0         0 push @{$l}, { type => 'fraction', num => $tree->op1(), den => $tree->op2() };
  0         0  
2125 0         0 return 1;
2126             }
2127             elsif ( ($tree->term_type() == T_OPERATOR) && ($tree->type() == B_EXP) ) {
2128 1         21 my $op1 = $tree->op1();
2129 1         24 my $exp = $tree->op2();
2130 1 0 33     9 if ( ($op1->term_type() == T_VARIABLE) &&
      33        
      33        
2131             ($exp->term_type() == T_CONSTANT) &&
2132             ($exp->value() eq int($exp->value())) &&
2133             ($exp->value() > 0) ) {
2134            
2135 0         0 my @v_list;
2136 0         0 for (0..$exp->value()-1) {
2137 0         0 push @{$l}, { type => 'variable', object => $op1->new() };
  0         0  
2138             }
2139 0         0 return 1;
2140             }
2141             }
2142             elsif ( ($tree->term_type() == T_OPERATOR) && ($tree->type() == B_PRODUCT) ) {
2143 127         5942 my $ok1 = get_product_elements($l, $tree->op1());
2144 127         349 my $ok2 = get_product_elements($l, $tree->op2());
2145 127         414 return $ok1 & $ok2;
2146             }
2147              
2148 2         60 return 0;
2149             }
2150              
2151             sub create_element {
2152 784     784 0 1657 my ($e) = @_;
2153              
2154 784 50 33     6253 if ( ($e->{type} eq 'variable') || ($e->{type} eq 'constant') || ($e->{type} eq 'function') ) {
    50 33        
    50          
2155 0         0 return $e->{object}->new();
2156             }
2157             elsif ( $e->{type} eq 'fraction' ) {
2158 0         0 return Math::Symbolic::Operator->new('/', $e->{num}->new(), $e->{den}->new());
2159             }
2160             elsif ( $e->{type} eq 'products' ) {
2161 784         2024 return create_product_tree($e->{list});
2162             }
2163              
2164 0         0 croak "Unrecognized type in create_element: $e->{type}";
2165             }
2166              
2167             sub create_product_tree {
2168 784     784 0 1460 my ($elements) = @_;
2169            
2170 784         1422 my $const = 1;
2171 784         1286 my @v_e;
2172 784         1254 foreach my $c (@{$elements}) {
  784         1828  
2173 2400 100 66     10839 if ( ($c->{type} eq 'constant') && ($c->{object}->special() eq '') ) {
2174 989         8085 $const *= $c->{object}->value();
2175             }
2176             else {
2177 1411         2859 push @v_e, $c;
2178             }
2179             }
2180              
2181 784 100       3906 if ( scalar(@v_e) == 0 ) {
2182 65         303 return Math::Symbolic::Constant->new($const);
2183             }
2184              
2185 719 100       1746 if ( $const != 1 ) {
2186 520         1701 push @v_e, { type => 'constant', object => Math::Symbolic::Constant->new($const) };
2187             }
2188            
2189 719         10887 my @num_to_mul;
2190 719         1492 foreach my $e (@v_e) {
2191 1931 50       25557 if ( $e->{type} eq 'fraction' ) {
2192 0         0 push @num_to_mul, $e->{num}->new();
2193             }
2194             else {
2195 1931         5280 push @num_to_mul, $e->{object}->new();
2196             }
2197             }
2198            
2199             # extract denominator elements, if any
2200 719         15518 my @den_to_mul;
2201 719         1585 foreach my $frac (grep { $_->{type} eq 'fraction' } @v_e) {
  1931         6246  
2202 0         0 push @den_to_mul, $frac->{den}->new();
2203             }
2204              
2205             # multiply all numerator elements with each other
2206 719         1367 my $ntp1 = shift @num_to_mul;
2207 719         1677 while (@num_to_mul) {
2208 1212         20502 my $e = shift @num_to_mul;
2209 1212         3409 $ntp1 = Math::Symbolic::Operator->new( '*', $ntp1, $e );
2210             }
2211            
2212             # deal with various fraction situations
2213             # no denominator
2214 719 50       21784 if ( scalar(@den_to_mul) == 0 ) {
2215 719         4103 return $ntp1;
2216             }
2217            
2218 0 0         if ( scalar(@den_to_mul) == 1 ) {
2219 0           my $den = pop @den_to_mul;
2220             # denominator is unity
2221 0 0 0       if ( ($den->term_type() == T_CONSTANT) && ($den->value() == 1) ) {
2222 0           return $ntp1;
2223             }
2224             # just one element in denominator (don't need to check for 0 again do I?)
2225 0           return Math::Symbolic::Operator->new( '/', $ntp1, $den );
2226             }
2227            
2228             # multiply all denominator elements with each other
2229 0           my $ntp2 = shift @den_to_mul;
2230 0           while (@den_to_mul) {
2231 0           my $e = shift @den_to_mul;
2232 0           $ntp2 = Math::Symbolic::Operator->new( '*', $ntp2, $e );
2233             }
2234            
2235             # returned the combined fraction
2236 0           return Math::Symbolic::Operator->new( '/', $ntp1, $ntp2 );
2237             }
2238              
2239             =head1 SEE ALSO
2240              
2241             L
2242              
2243             L
2244              
2245             =head1 AUTHOR
2246              
2247             Matt Johnson, C<< >>
2248              
2249             =head1 ACKNOWLEDGEMENTS
2250              
2251             Steffen Mueller, author of Math::Symbolic
2252              
2253             =head1 LICENSE AND COPYRIGHT
2254              
2255             This software is copyright (c) 2024 by Matt Johnson.
2256              
2257             This is free software; you can redistribute it and/or modify it under
2258             the same terms as the Perl 5 programming language system itself.
2259              
2260             =cut
2261              
2262             1;
2263             __END__