File Coverage

blib/lib/Math/Algebra/Symbols/Sum.pm
Criterion Covered Total %
statement 735 804 91.4
branch 342 484 70.6
condition 74 146 50.6
subroutine 88 101 87.1
pod 71 93 76.3
total 1310 1628 80.4


line stmt bran cond sub pod time code
1             #!perl -w
2            
3             =head1 Sums
4            
5             Symbolic Algebra using Pure Perl: sums.
6            
7             Operations on sums of terms.
8            
9             PhilipRBrenan@yahoo.com, 2004, Perl License.
10             PhilipRBrenan@gmail.com, 2016, Perl License. www.appaapps.com
11            
12             =cut
13            
14            
15             package Math::Algebra::Symbols::Sum;
16             $VERSION=1.26;
17 45     45   32997 use Math::Algebra::Symbols::Term;
  45         137  
  45         215  
18 45     45   42533 use IO::Handle;
  45         338223  
  45         3143  
19 45     45   1994 use Carp;
  45         1499  
  45         3659  
20             #HashUtil use Hash::Util qw(lock_hash);
21 45     45   1839 use Scalar::Util qw(weaken);
  45         914  
  45         455292  
22            
23            
24             =head2 Constructors
25            
26            
27             =head3 new
28            
29             Constructor
30            
31             =cut
32            
33            
34             sub new
35 10011     10011 1 27574 {bless {t=>{}};
36             }
37            
38            
39             =head3 newFromString
40            
41             New from String
42            
43             =cut
44            
45            
46             sub newFromString($)
47 797     797 1 1375 {my ($a) = @_;
48 797 100       1869 return $zero unless $a;
49 701         1051 $a .='+';
50 701         3163 my @a = $a =~ /(.+?)[\+\-]/g;
51 701         1306 my @t = map {term($_)} @a;
  701         23117  
52 701         1744 sigma(@t);
53             }
54            
55            
56             =head3 n
57            
58             New from Strings
59            
60             =cut
61            
62            
63             sub n(@)
64 66 50   66 1 285 {return $zero unless @_;
65 66         176 my @a = map {newFromString($_)} @_;
  156         424  
66 66 100       404 return @a if wantarray;
67 18         74 $a[0];
68             }
69            
70            
71             =head3 sigma
72            
73             Create a sum from a list of terms.
74            
75             =cut
76            
77            
78             sub sigma(@)
79 10070 100   10070 1 22580 {return $zero unless scalar(@_);
80 10011         17730 my $z = new();
81 10011         19000 for my $t(@_)
82 140510         377469 {my $s = $t->signature;
83 140510 100       386639 if (exists($z->{t}{$s}))
84 102282         346090 {my $a = $z->{t}{$s}->add($t);
85 102282 100       302299 if ($a->c == 0)
86 3863         12004 {delete $z->{t}{$s};
87             }
88             else
89 98419         519939 {$z->{t}{$s} = $a;
90             }
91             }
92             else
93 38228         120438 {$z->{t}{$s} = $t
94             }
95             }
96 10011         21547 $z->z;
97             }
98            
99            
100             =head3 makeInt
101            
102             Construct an integer
103            
104             =cut
105            
106            
107             sub makeInt($)
108 13     13 1 406 {sigma(term()->one->clone->c(shift())->z)
109             }
110            
111            
112             =head2 Methods
113            
114            
115             =head3 isSum
116            
117             Confirm type
118            
119             =cut
120            
121            
122 8     8 1 11 sub isSum($) {1};
123            
124            
125             =head3 t
126            
127             Get list of terms from existing sum
128            
129             =cut
130            
131            
132             sub t($)
133 28498     28498 1 36867 {my ($a) = @_;
134 28498         31535 (map {$a->{t}{$_}} sort(keys(%{$a->{t}})));
  107556         219371  
  28498         150502  
135             }
136            
137            
138             =head3 count
139            
140             Count terms in sum
141            
142             =cut
143            
144            
145             sub count($)
146 0     0 1 0 {my ($a) = @_;
147 0         0 scalar(keys(%{$a->{t}}));
  0         0  
148             }
149            
150            
151             =head3 st
152            
153             Get the single term from a sum containing just one term
154            
155             =cut
156            
157            
158             sub st($)
159 4022     4022 1 5293 {my ($a) = @_;
160 4022 100       4283 return (values(%{$a->{t}}))[0] if scalar(keys(%{$a->{t}})) == 1;
  1546         3668  
  4022         12034  
161 2476         4210 undef;
162             }
163            
164            
165             =head3 negate
166            
167             Multiply each term in a sum by -1
168            
169             =cut
170            
171            
172             sub negate($)
173 828     828 1 1250 {my ($s) = @_;
174 828         955 my @t;
175 828         1707 for my $t($s->t)
176 3433         9917 {push @t, $t->clone->timesInt(-1)->z;
177             }
178 828         2656 sigma(@t);
179             }
180            
181            
182             =head3 add
183            
184             Add two sums together to make a new sum
185            
186             =cut
187            
188            
189             sub add($$)
190 3961     3961 1 5783 {my ($a, $b) = @_;
191 3961         7861 sigma($a->t, $b->t);
192             }
193            
194            
195             =head3 subtract
196            
197             Subtract one sum from another
198            
199             =cut
200            
201            
202             sub subtract($$)
203 529     529 1 910 {my ($a, $b) = @_;
204 529 100       1535 return $b->negate if $a->{id} == $zero->{id};
205 454         1063 $a->add($b->negate);
206             }
207            
208            
209             =head3 Conditional Multiply
210            
211             Multiply two sums if both sums are defined, otherwise return
212             the defined sum. Assumes that at least one sum is defined.
213            
214             =cut
215            
216            
217             sub multiplyC($$)
218 4664     4664 0 6189 {my ($a, $b) = @_;
219 4664 100       17383 return $a unless defined($b);
220 747 100       2576 return $b unless defined($a);
221 183         464 $a->multiply($b);
222             }
223            
224            
225             =head3 multiply
226            
227             Multiply two sums together
228            
229             =cut
230            
231            
232             my %M; # Memoize multiplication
233            
234             sub multiply($$)
235 2815     2815 1 4183 {my ($A, $B) = @_;
236            
237 2815 100       11643 my $m = $M{$A->{id}}{$B->{id}}; return $m if defined($m);
  2815         8172  
238            
239 1758 100 100     10080 return $A if $A->{id} == $zero->{id} or $B->{id} == $one->{id};
240 1598 100 100     7977 return $B if $B->{id} == $zero->{id} or $A->{id} == $one->{id};
241            
242 1503         1804 my @t;
243            
244             # Check for divides that match multiplier
245 1503         3168 my @a = $A->t;
246 1503         3628 for my $a(@a)
247 7071         17941 {my $d = $a->Divide;
248 7071 100       17454 next unless $d;
249 1061 100       2997 if ($d->{id} == $B->{id})
250 725         1977 {push @t, $a->removeDivide;
251 725         1426 $a = undef;
252             }
253             }
254            
255 1503         3193 my @b = $B->t;
256 1503         3171 for my $b(@b)
257 5463         13387 {my $d = $b->Divide;
258 5463 100       12950 next unless $d;
259 136 100       448 if ($d->{id} == $A->{id})
260 1         6 {push @t, $b->removeDivide;
261 1         3 $b = undef;
262             }
263             }
264            
265             # Simple multiply
266 1503         2373 for my $aa(@a)
267 7071 100       19152 {next unless $aa;
268 6346         11937 for my $bb(@b)
269 105920 100       269985 {next unless $bb;
270 105919         302274 my $m = $aa->multiply($bb);
271 105919 100       309136 push (@t, $m), next if $m;
272            
273             # Complicated multiply
274 3191         8289 my %a = $aa->split; my %b = $bb->split;
  3191         9996  
275 3191         6383 my $a = $a{t}; my $b = $b{t};
  3191         4491  
276            
277             # Sqrt
278 3191         3743 my $s = 0;
279 3191 100 66     6640 $s = $a{s} if $a{s} and $b{s} and $a{s}->{id} == $b{s}->{id}; # Equal sqrts
      66        
280 3191 100       9716 $a->Sqrt(multiplyC($a{s}, $b{s})) unless $s;
281            
282             # Divide
283 3191 100 66     10108 $a->Divide(multiplyC($a{d}, $b{d})) if $a{d} or $b{d};
284            
285             # Exp
286 3191 50 75     8409 $a->Exp($a{e} ? $a{e} : $b{e}) if $a{e} xor $b{e};
    100          
287 3191         4702 my $e;
288 3191 50 66     6406 if ($a{e} and $b{e})
289 2923         6797 {my $s = $a{e}->add($b{e});
290 2923         8409 $e = $s->st; # Check for single term
291 2923 100       7685 $e = $e->exp2 if defined($e); # Simplify single term if possible
292 2923 100       10444 $a->Exp($s) unless defined($e); # Reinstate Exp as sum of terms if no simplification possible
293             }
294             # Log
295 3191 0 25     15906 $a->Log($a{l} ? $a{l} : $b{l}) if $a{l} xor $b{l};
    50          
296 3191 0 33     6613 die "Cannot multiply logs yet" if $a{l} and $b{l};
297            
298             # Combine results
299 3191         7995 $a = $a->z;
300 3191         8413 $b = $b->z;
301 3191         8976 $a = $a->multiply($b);
302 3191 100       6864 $a = $a->multiply($e) if defined($e);
303 3191 50       8738 $a or die "Bad multiply";
304            
305 3191 100       8257 push @t, $a unless $s;
306 3191 100       24306 push @t, sigma($a)->multiply($s)->t if $s;
307             }
308             }
309            
310             # Result
311 1503         7247 my $C = sigma(@t);
312 1503         5143 $M{$A->{id}}{$B->{id}} = $C;
313 1503         228932 $C;
314             }
315            
316            
317             =head3 divide
318            
319             Divide one sum by another
320            
321             =cut
322            
323            
324             sub divide($$)
325 351     351 1 570 {my ($A, $B) = @_;
326            
327             # Obvious cases
328 351 50       1017 $B->{id} == $zero->{id} and croak "Cannot divide by zero";
329 351 50       934 return $zero if $A->{id} == $zero->{id};
330 351 100       998 return $A if $B->{id} == $one->{id};
331 308 50       812 return $A->negate if $B->{id} == $mOne->{id};
332            
333             # Divide term by term
334 308         768 my $a = $A->st; my $b = $B->st;
  308         700  
335 308 100 100     1599 if (defined($a) and defined($b))
    100          
336 106         369 {my $c = $a->divide2($b);
337 106 100       396 return sigma($c) if $c;
338             }
339            
340             # Divide sum by term
341             elsif ($b)
342 55         146 {ST: for(1..1)
343 55         82 {my @t;
344 55         137 for my $t($A->t)
345 101         324 {my $c = $t->divide2($b);
346 101 100       328 last ST unless $c;
347 85         209 push @t, $c;
348             }
349 39         112 return sigma(@t);
350             }
351             }
352            
353             # Divide sum by sum
354 176         314 my @t;
355 176         381 for my $aa($A->t)
356 314         962 {my $a = $aa->clone;
357 314         903 my $d = $a->Divide;
358 314 100       712 $a->Divide($d->multiply($B)) if $d;
359 314 100       1101 $a->Divide($B) unless $d;
360 314         840 push @t, $a->z;
361             }
362            
363             # Result
364 176         638 sigma(@t);
365             }
366            
367            
368             =head3 sub
369            
370             Substitute a sum for a variable.
371            
372             =cut
373            
374            
375             sub sub($@)
376 7     7 1 29 {my $E = shift();
377 7         16 my @R = @_;
378            
379             # Each replacement
380 7         120 for(;@R > 0;)
381 8         15 {my $s = shift @R; # Replace this variable
382 8         12 my $w = shift @R; # With this expression
383 8         13 my $Z = $zero;
384            
385 8 50       36 $s =~ /^[a-z]+$/i or croak "Can only substitute an expression for a variable, not $s";
386 8 100       28 $w = newFromString($w) unless ref($w);
387 8         21 $w->isSum;
388            
389             # Each term of the sum comprising the replacement expression.
390 8         19 for my $t($E->t)
391 26         72 {my $n = $t->vp($s);
392 26         75 my %t = $t->split;
393 26         92 my $S = sigma($t{t}->vp($s, 0)->z); # Remove substitution variable
394 26 100       99 $S = $S->multiply(($t{s}->sub(@_))->Sqrt) if defined($t{s});
395 26 100       59 $S = $S->divide ($t{d}->sub(@_)) if defined($t{d});
396 26 50       57 $S = $S->multiply(($t{e}->sub(@_))->Exp) if defined($t{e});
397 26 50       54 $S = $S->multiply(($t{l}->sub(@_))->Log) if defined($t{l});
398 26 100       71 $S = $S->multiply($w->power(makeInt($n))) if $n;
399 26         103 $Z = $Z->add($S);
400             }
401 8         30 $E = $Z;
402             }
403            
404             # Result
405 7         25 $E;
406             }
407            
408            
409             =head3 isEqual
410            
411             Check whether one sum is equal to another after multiplying out all
412             divides and divisors.
413            
414             =cut
415            
416            
417             sub isEqual($)
418 91     91 1 172 {my ($C) = @_;
419            
420             # Until there are no more divides
421 91         141 for(;;)
422 176         257 {my (%c, $D, $N); $N = 0;
  176         327  
423            
424             # Most frequent divisor
425 176         481 for my $t($C->t)
426 1550         4079 {my $d = $t->Divide;
427 1550 100       3616 next unless $d;
428 958         2144 my $s = $d->getSignature;
429 958 100       3356 if (++$c{$s} > $N)
430 675         1398 {$N = $c{$s};
431 675         1135 $D = $d;
432             }
433             }
434 176 100       676 last unless $N;
435 85         212 $C = $C->multiply($D);
436             }
437            
438             # Until there are no more negative powers
439 91         138 for(;;)
440 100         164 {my %v;
441 100         337 for my $t($C->t)
442 161         510 {for my $v($t->v)
443 124         336 {my $p = $t->vp($v);
444 124 100       432 next unless $p < 0;
445 12         24 $p = -$p;
446 12 100 100     70 $v{$v} = $p if !defined($v{$v}) or $v{$v} < $p;
447             }
448             }
449 100 100       391 last unless scalar(keys(%v));
450 9         336 my $m = term()->one->clone;
451 9         71 $m->vp($_, $v{$_}) for keys(%v);
452 9         34 my $M = sigma($m->z);
453 9         31 $C = $C->multiply($M);
454             }
455            
456             # Result
457 91         754 $C;
458             }
459            
460            
461             =head3 normalizeSqrts
462            
463             Normalize sqrts in a sum.
464            
465             This routine needs fixing.
466            
467             It should simplify square roots.
468            
469             =cut
470            
471            
472             sub normalizeSqrts($)
473 108     108 1 169 {my ($s) = @_;
474 108         236 return $s;
475 0         0 my (@t, @s);
476            
477             # Find terms with single simple sqrts that can be normalized.
478 0         0 for my $t($s->t)
479 0         0 {push @t, $t;
480 0 0       0 my $S = $t->Sqrt; next unless $S; # Check for sqrt
  0         0  
481 0 0       0 my $St = $S->st; next unless $St; # Check for single term sqrt
  0         0  
482            
483 0         0 my %T = $St->split; # Split single term sqrt
484 0 0 0     0 next if $T{s} or $T{d} or $T{e} or $T{l};
      0        
      0        
485 0         0 pop @t;
486 0         0 push @s, {t=>$t, s=>$T{t}->z}; # Sqrt with simple single term
487             }
488            
489             # Already normalized unless there are several such terms
490 0 0       0 return $s unless scalar(@s) > 1;
491            
492             # Remove divisor for each normalized term
493 0         0 for my $r(@s)
494 0 0       0 {my $d = $r->{t}->d; next unless $d > 1;
  0         0  
495 0         0 for my $s(@s)
496 0         0 {$s->{t} = $s->{t}->clone->divideInt($d) ->z;
497 0         0 $s->{s} = $s->{s}->clone->timesInt ($d*$d)->z;
498             }
499             }
500            
501             # Eliminate duplicate squared factors
502 0         0 for my $s(@s)
503 0         0 {my $F = factorize($s->{s}->c);
504 0         0 my $p = 1;
505 0         0 for my $f(keys(%$F))
506 0 0       0 {$p *= $f**(int($F->{$f}/2)) if $F->{$f} > 1;
507             }
508 0         0 $s->{t} = $s->{t}->clone->timesInt ($p) ->z;
509 0         0 $s->{s} = $s->{s}->clone->divideInt($p*$p)->z;
510            
511 0 0       0 if ($s->{s}->isOne)
512 0         0 {push @t, $s->{t}->removeSqrt;
513             }
514             else
515 0         0 {push @t, $s->{t}->clone->Sqrt($s->{$s})->z;
516             }
517             }
518            
519             # Result
520 0         0 sigma(@t);
521             }
522            
523            
524             =head3 isEqualSqrt
525            
526             Check whether one sum is equal to another after multiplying out sqrts.
527            
528             =cut
529            
530            
531             sub isEqualSqrt($)
532 91     91 1 149 {my ($C) = @_;
533            
534             #_______________________________________________________________________
535             # Each sqrt
536             #_______________________________________________________________________
537            
538 91         284 for(1..99)
539 108         453 {$C = $C->normalizeSqrts;
540 108         276 my @s = grep { defined($_->Sqrt)} $C->t;
  335         994  
541 108         345 my @n = grep {!defined($_->Sqrt)} $C->t;
  335         950  
542 108 100       460 last unless scalar(@s) > 0;
543            
544             #_______________________________________________________________________
545             # Partition by square roots.
546             #_______________________________________________________________________
547            
548 17         42 my %S = ();
549 17         58 for my $t(@s)
550 47         126 {my $s = $t->Sqrt;
551 47         109 my $S = $s->signature;
552 47         75 push @{$S{$S}}, $t;
  47         245  
553             }
554            
555             #_______________________________________________________________________
556             # Square each partitions, as required by the formulae below.
557             #_______________________________________________________________________
558            
559 17         29 my @t;
560 17 100       61 push @t, sigma(@n)->power($two) if scalar(@n); # Non sqrt partition
561 17         65 for my $s(keys(%S))
562 32         58 {push @t, sigma(@{$S{$s}})->power($two); # Sqrt partition
  32         89  
563             }
564            
565             #_______________________________________________________________________
566             # I can multiply out upto 4 square roots using the formulae below.
567             # There are formula to multiply out more than 4 sqrts, but they are big.
568             # These formulae are obtained by squaring out and rearranging:
569             # sqrt(a)+sqrt(b)+sqrt(c)+sqrt(d) == 0 until no sqrts remain, and
570             # then matching terms to produce optimal execution.
571             # This remarkable result was obtained with the help of this package:
572             # demonstrating its utility in optimizing complex calculations written
573             # in Perl: which in of itself cannot optimize broadly.
574             #_______________________________________________________________________
575            
576 17         40 my $ns = scalar(@t);
577 17 50       66 $ns < 5 or die "There are $ns square roots present. I can handle less than 5";
578            
579 17         40 my ($a, $b, $c, $d) = @t;
580            
581 17 50       93 if ($ns == 1)
    100          
    100          
    50          
582 0         0 {$C = $a;
583             }
584             elsif ($ns == 2)
585 11         34 {$C = $a-$b;
586             }
587             elsif ($ns == 3)
588 4         16 {$C = -$a**2+2*$a*$b-$b**2+2*$c*$a+2*$c*$b-$c**2;
589             }
590             elsif ($ns == 4)
591 2         5 {my $a2 = $a * $a;
592 2         8 my $a3 = $a2 * $a;
593 2         14 my $a4 = $a3 * $a;
594 2         103 my $b2 = $b * $b;
595 2         9 my $b3 = $b2 * $b;
596 2         10 my $b4 = $b3 * $b;
597 2         13 my $c2 = $c * $c;
598 2         9 my $c3 = $c2 * $c;
599 2         13 my $c4 = $c3 * $c;
600 2         283 my $d2 = $d * $d;
601 2         8 my $d3 = $d2 * $d;
602 2         13 my $d4 = $d3 * $d;
603 2         20 my $bpd = $b + $d;
604 2         12 my $bpc = $b + $c;
605 2         12 my $cpd = $c + $d;
606 2         14 $C =
607             - ($a4 + $b4 + $c4 + $d4)
608             + 4*(
609             +$a3*($b+$cpd)+$b3*($a+$cpd)+$c3*($a+$bpd)+$d3*($a+$bpc)
610             -$a2*($b *($cpd)+ $c*$d)
611             -$a *($b2*($cpd)+$d2*($bpc))
612             )
613            
614             - 6*($a2*$b2+($a2+$b2)*($c2+$d2)+$c2*$d2)
615            
616             - 4*$c*($b2*$d+$b*$d2)
617             - 4*$c2*($a*($bpd)+$b*$d)
618             +40*$c*$a*$b*$d
619             ;
620             }
621             }
622            
623             #________________________________________________________________________
624             # Test result
625             #________________________________________________________________________
626            
627             # $C->isEqual($zero);
628 91         356 $C;
629             }
630            
631            
632             =head3 isZero
633            
634             Transform a sum assuming that it is equal to zero
635            
636             =cut
637            
638            
639             sub isZero($)
640 91     91 1 186 {my ($C) = @_;
641 91         293 $C->isEqualSqrt->isEqual;
642             }
643            
644            
645             =head3 powerOfTwo
646            
647             Check that a number is a power of two
648            
649             =cut
650            
651            
652             sub powerof2($)
653 1     1 0 2 {my ($N) = @_;
654 1         2 my $n = 0;
655 1 50       4 return undef unless $N > 0;
656 1         2 for (;;)
657 2 100       8 {return $n if $N == 1;
658 1 50       4 return undef unless $N % 2 == 0;
659 1         1 ++$n; $N /= 2;
  1         3  
660             }
661             }
662            
663            
664             =head3 solve
665            
666             Solve an equation known to be equal to zero for a specified variable.
667            
668             =cut
669            
670            
671             sub solve($$)
672 17     17 1 57 {my ($A, @x) = @_;
673 17 50       64 croak 'Need variable to solve for' unless scalar(@x) > 0;
674            
675 17 100 100     115 @x = @{$x[0]} if scalar(@x) == 1 and ref($x[0]) eq 'ARRAY'; # Array of variables supplied
  1         5  
676 17         34 my %x;
677 17         34 for my $x(@x)
678 56 100       141 {if (!ref $x)
    50          
679 51 50       225 {$x =~ /^[a-z]+$/i or croak "Cannot solve for: $x, not a variable name";
680             }
681             elsif (ref $x eq __PACKAGE__)
682 5 50       17 {my $t = $x->st; $t or die "Cannot solve for multiple terms";
  5         20  
683 5 50       30 my @b = $t->v; scalar(@b) == 1 or die "Can only solve for one variable";
  5         21  
684 5 50       23 my $p = $t->vp($b[0]); $p == 1 or die "Can only solve by variable to power 1";
  5         25  
685 5         13 $x = $b[0];
686             }
687             else
688 0         0 {die "$x is not a variable name";
689             }
690 56         130 $x{$x} = 1;
691             }
692 17         30 my $x = $x[0];
693            
694 17         59 $B = $A->isZero; # Eliminate sqrts and negative powers
695            
696             # Strike all terms with free variables other than x: i.e. not x and not one of the named constants
697 17         56 my @t = ();
698 17         47 for my $t($B->t)
699 52         158 {my @v = $t->v;
700 52         83 push @t, $t;
701 52         142 for my $v($t->v)
702 59 100       191 {next if exists($x{$v});
703 8         9 pop @t;
704 8         16 last;
705             }
706             }
707 17         59 my $C = sigma(@t);
708            
709             # Find highest and lowest power of x
710 17         102 my $n = 0; my $N;
  17         25  
711 17         47 for my $t($C->t)
712 44         137 {my $p = $t->vp($x);
713 44 100       119 $n = $p if $p > $n;
714 44 100 100     244 $N = $p if !defined($N) or $p < $N;
715             }
716 17         38 my $D = $C;
717 17 100       79 $D = $D->multiply(sigma(term()->one->clone->vp($x, -$N)->z)) if $N;
718 17 100       53 $n -= $N if $N;
719            
720             # Find number of terms in x
721 17         24 my $c = 0;
722 17         45 for my $t($D->t)
723 44 100       133 {++$c if $t->vp($x) > 0;
724             }
725            
726 17 50       58 $n == 0 and croak "Equation not dependant on $x, so cannot solve for $x";
727 17 50 33     64 $n > 4 and $c > 1 and croak "Unable to solve polynomial or power $n > 4 in $x (Galois)";
728 17 50 33     56 ($n > 2 and $c > 1) and die "Need solver for polynomial of degree $n in $x";
729            
730             # Solve linear equation
731 17 100 100     103 if ($n == 1 or $c == 1)
732 8         14 {my (@c, @v);
733 8         21 for my $t($D->t)
734 17 100       63 {push(@c, $t), next if $t->vp($x) == 0; # Constants
735 8         21 push @v, $t; # Powers of x
736             }
737 8         29 my $d = sigma(@v)->multiply(sigma(term()->one->clone->vp($x, -$n)->negate->z));
738 8         74 $D = sigma(@c)->divide($d);
739            
740 8 100       62 return $D if $n == 1;
741            
742 1         4 my $p = powerof2($n);
743 1 50       3 $p or croak "Fractional power 1/$n of $x unconstructable by sqrt";
744 1         6 $D = $D->Sqrt for(1..$p);
745 1         40 return $D;
746             }
747            
748             # Solve quadratic equation
749 9 50       30 if ($n == 2)
750 9         19 {my @c = ($one, $one, $one);
751 9         21 $c[$_->vp($x)] = $_ for $D->t;
752 9         40 $_ = sigma($_->clone->vp($x, 0)->z) for (@c);
753 9         19 my ($c, $b, $a) = @c;
754             return
755 9         31 [ (-$b->add (($b->power($two)->subtract($four->multiply($a)->multiply($c)))->Sqrt))->divide($two->multiply($a)),
756             (-$b->subtract(($b->power($two)->subtract($four->multiply($a)->multiply($c)))->Sqrt))->divide($two->multiply($a))
757             ]
758             }
759            
760             # Check that it works
761            
762             # my $yy = $e->sub($x=>$xx);
763             # $yy == 0 or die "Proposed solution \$$x=$xx does not zero equation $e";
764             # $xx;
765             }
766            
767            
768             =head3 power
769            
770             Raise a sum to an integer power or an integer/2 power.
771            
772             =cut
773            
774            
775             sub power($$)
776 352     352 1 580 {my ($a, $b) = @_;
777            
778 352 50       1078 return $one if $b->{id} == $zero->{id};
779 352 100       1281 return $a->multiply($a) if $b->{id} == $two->{id};
780 61 100       195 return $a if $b->{id} == $one->{id};
781 57 50       165 return $one->divide($a) if $b->{id} == $mOne->{id};
782 57 50       185 return $a->sqrt if $b->{id} == $half->{id};
783 57 50       174 return $one->divide($a->sqrt) if $b->{id} == $mHalf->{id};
784            
785 57         204 my $T = $b->st;
786 57 50       220 $T or croak "Power by expression too complicated";
787            
788 57         203 my %t = $T->split;
789 57 50 33     537 croak "Power by term too complicated" if $t{s} or $t{d} or $t{e} or $t{l};
      33        
      33        
790            
791 57         103 my $t = $t{t};
792 57 50       186 $t->i == 0 or croak "Complex power not allowed yet";
793            
794 57         183 my ($p, $d) = ($t->c, $t->d);
795 57 50 33     255 $d == 1 or $d == 2 or croak "Fractional power other than /2 not allowed yet";
796            
797 57 50       164 $a = $a->sqrt if $d == 2;
798            
799 57 50       153 return $one->divide($a)->power(sigma(term()->c($p)->z)) if $p < 0;
800            
801 57         87 $p = abs($p);
802 57         95 my $r = $a; $r = $r->multiply($a) for (2..$p);
  57         247  
803 57         533 $r;
804             }
805            
806            
807             =head3 d
808            
809             Differentiate.
810            
811             =cut
812            
813            
814             sub d($;$);
815             sub d($;$)
816 195     195 1 289 {my $c = $_[0]; # Differentiate this sum
817 195         275 my $b = $_[1]; # With this variable
818            
819             #_______________________________________________________________________
820             # Get differentrix. Assume 'x', 'y', 'z' or 't' if appropriate.
821             #_______________________________________________________________________
822            
823 195 100       382 if (defined($b))
824 139 100       303 {if (!ref $b)
    50          
825 136 50       567 {$b =~ /^[a-z]+$/i or croak "Cannot differentiate by $b";
826             }
827             elsif (ref $b eq __PACKAGE__)
828 3 50       12 {my $t = $b->st; $t or die "Cannot differentiate by multiple terms";
  3         12  
829 3 50       13 my @b = $t->v; scalar(@b) == 1 or die "Can only differentiate by one variable";
  3         10  
830 3 50       13 my $p = $t->vp($b[0]); $p == 1 or die "Can only differentiate by variable to power 1";
  3         21  
831 3         12 $b = $b[0];
832             }
833             else
834 0         0 {die "Cannot differentiate by $b";
835             }
836             }
837             else
838 56         74 {my %b;
839 56         124 for my $t($c->t)
840 108         134 {my %b; $b{$_}++ for ($t->v);
  108         299  
841             }
842 56         107 my $i = 0; my $n = scalar(keys(%b));
  56         87  
843 56 50       164 ++$i, $b = 'x' if $n == 0; # Constant expression anyway
844 56 50       127 ++$i, $b = (%b)[0] if $n == 1;
845 56         156 for my $v(qw(t x y z))
846 224 0 33     496 {++$i, $b = 't' if $n > 1 and exists($b{$v});
847             }
848 56 50       167 $i == 1 or croak "Please specify a single variable to differentiate by";
849             }
850            
851             #_______________________________________________________________________
852             # Each term
853             #_______________________________________________________________________
854            
855 195         315 my @t = ();
856 195         406 for my $t($c->t)
857 258         732 {my %V = $t->split;
858 258         899 my $T = $V{t}->z->clone->z;
859 258         1039 my ($S, $D, $E, $L) = @V{qw(s d e l)};
860 258 100       539 my $s = $S->d($b) if $S;
861 258 100       493 my $d = $D->d($b) if $D;
862 258 100       483 my $e = $E->d($b) if $E;
863 258 50       563 my $l = $L->d($b) if $L;
864            
865             #_______________________________________________________________________
866             # Differentiate Variables: A*v**n->d == A*n*v**(n-1)
867             #_______________________________________________________________________
868            
869 258         319 {my $v = $T->clone;
  258         672  
870 258         732 my $p = $v->vp($b);
871 258 100       842 if ($p != 0)
872 128         351 {$v->timesInt($p)->vp($b, $p-1);
873 128 100       298 $v->Sqrt ($S) if $S;
874 128 100       231 $v->Divide($D) if $D;
875 128 50       268 $v->Exp ($E) if $E;
876 128 50       239 $v->Log ($L) if $L;
877 128         347 push @t, $v->z;
878             }
879             }
880            
881             #_______________________________________________________________________
882             # Differentiate Sqrt: A*sqrt(F(x))->d == 1/2*A*f(x)/sqrt(F(x))
883             #_______________________________________________________________________
884            
885 258 100       551 if ($S)
886 5         17 {my $v = $T->clone->divideInt(2);
887 5 50       12 $v->Divide($D) if $D;
888 5 50       11 $v->Exp ($E) if $E;
889 5 50       10 $v->Log ($L) if $L;
890 5         16 push @t, sigma($v->z)->multiply($s)->divide($S->Sqrt)->t;
891             }
892            
893             #_______________________________________________________________________
894             # Differentiate Divide: A/F(x)->d == -A*f(x)/F(x)**2
895             #_______________________________________________________________________
896            
897 258 100       501 if ($D)
898 10         30 {my $v = $T->clone->negate;
899 10 50       33 $v->Sqrt($S) if $S;
900 10 100       27 $v->Exp ($E) if $E;
901 10 50       26 $v->Log ($L) if $L;
902 10         32 push @t, sigma($v->z)->multiply($d)->divide($D->multiply($D))->t;
903             }
904            
905             #_______________________________________________________________________
906             # Differentiate Exp: A*exp(F(x))->d == A*f(x)*exp(F(x))
907             #_______________________________________________________________________
908            
909 258 100       544 if ($E)
910 120         332 {my $v = $T->clone;
911 120 50       264 $v->Sqrt ($S) if $S;
912 120 100       242 $v->Divide($D) if $D;
913 120         318 $v->Exp ($E);
914 120 50       242 $v->Log ($L) if $L;
915 120         426 push @t, sigma($v->z)->multiply($e)->t;
916             }
917            
918             #_______________________________________________________________________
919             # Differentiate Log: A*log(F(x))->d == A*f(x)/F(x)
920             #_______________________________________________________________________
921            
922 258 50       1477 if ($L)
923 0         0 {my $v = $T->clone;
924 0 0       0 $v->Sqrt ($S) if $S;
925 0 0       0 $v->Divide($D) if $D;
926 0 0       0 $v->Exp ($E) if $E;
927 0         0 push @t, sigma($v->z)->multiply($l)->divide($L)->t;
928             }
929             }
930            
931             #_______________________________________________________________________
932             # Result
933             #_______________________________________________________________________
934            
935 195         480 sigma(@t);
936             }
937            
938            
939             =head3 simplify
940            
941             Simplify just before assignment.
942            
943             There is no general simplification algorithm. So try various methods and
944             see if any simplifications occur. This is cheating really, because the
945             examples will represent these specific transformations as general
946             features which they are not. On the other hand, Mathematics is full of
947             specifics so I suppose its not entirely unacceptable.
948            
949             Simplification cannot be done after every operation as it is
950             inefficient, doing it as part of += ameliorates this inefficiency.
951            
952             Note: += only works as a synonym for simplify() if the left hand side is
953             currently undefined. This can be enforced by using my() as in: my $z +=
954             ($x**2+5x+6)/($x+2);
955            
956             =cut
957            
958            
959             sub simplify($)
960 16     16 1 31 {my ($x) = @_;
961 16         49 $x = polynomialDivision($x);
962 16         55 $x = eigenValue($x);
963             }
964            
965             #_______________________________________________________________________
966             # Common factor: find the largest factor in one or more expressions
967             #_______________________________________________________________________
968            
969             sub commonFactor(@)
970 32 50   32 0 104 {return undef unless scalar(@_);
971 32 50       43 return undef unless scalar(keys(%{$_[0]->{t}}));
  32         124  
972 32         50 my $p = (values(%{$_[0]->{t}}))[0];
  32         85  
973            
974 32         47 my %v = %{$p->{v}}; # Variables
  32         106  
975 32         119 my %s = $p->split;
976 32         102 my ($s, $d, $e, $l) = @s{qw(s d e l)}; # Sub expressions
977 32         105 my ($C, $D, $I) = ($p->c, $p->d, $p->i);
978            
979 32         49 my @t;
980 32         79 for my $a(@_)
981 32         79 {for my $b($a->t)
982 88         186 {push @t, $b;
983             }
984             }
985            
986 32         63 for my $t(@t)
987 88         200 {my %V = %v;
988 88         146 %v = ();
989 88         251 for my $v($t->v)
990 59 100       157 {next unless $V{$v};
991 44         147 my $p = $t->vp($v);
992 44 100       162 $v{$v} = ($V{$v} < $p ? $V{$v} : $p);
993             }
994 88         262 my %S = $t->split;
995 88         226 my ($S, $D, $E, $L) = @S{qw(s d e l)}; # Sub expressions
996 88 0 33     266 $s = undef unless defined($s) and defined($S) and $S->id eq $s->id;
      33        
997 88 0 33     220 $d = undef unless defined($d) and defined($D) and $D->id eq $d->id;
      33        
998 88 100 66     226 $e = undef unless defined($e) and defined($E) and $E->id eq $e->id;
      100        
999 88 0 33     218 $l = undef unless defined($l) and defined($L) and $L->id eq $l->id;
      33        
1000 88 100 100     351 $C = undef unless defined($C) and $C == $t->c;
1001 88 50 33     288 $D = undef unless defined($D) and $D == $t->d;
1002 88 100 100     366 $I = undef unless defined($I) and $I == $t->i;
1003             }
1004 32         1249 my $r = term()->one->clone;
1005 32 100       190 $r->c($C) if defined($C);
1006 32 50       139 $r->d($D) if defined($D);
1007 32 100       128 $r->i($I) if defined($I);
1008 32         84 $r->vp($_, $v{$_}) for(keys(%v));
1009 32 50       83 $r->Sqrt ($s) if defined($s);
1010 32 50       68 $r->Divide($d) if defined($d);
1011 32 50       66 $r->Exp ($e) if defined($e);
1012 32 50       73 $r->Log ($l) if defined($l);
1013 32         99 sigma($r->z);
1014             }
1015            
1016             #_______________________________________________________________________
1017             # Find term of polynomial of highest degree.
1018             #_______________________________________________________________________
1019            
1020             sub polynomialTermOfHighestDegree($$)
1021 160     160 0 246 {my ($p, $v) = @_; # Polynomial, variable
1022 160         209 my $n = 0; # Current highest degree
1023 160         203 my $t; # Term with this degree
1024 160         327 for my $T($p->t)
1025 308         868 {my $N = $T->vp($v);
1026 308 100       807 if ($N > $n)
1027 148         202 {$n = $N;
1028 148         257 $t = $T;
1029             }
1030             }
1031 160         473 ($n, $t);
1032             }
1033            
1034            
1035             =head3 polynomialDivide
1036            
1037             Polynomial divide - divide one polynomial (a) by another (b) in variable v
1038            
1039             =cut
1040            
1041            
1042             sub polynomialDivide($$$)
1043 16     16 1 36 {my ($p, $q, $v) = @_;
1044            
1045 16         42 my $r = zero()->clone()->z;
1046 16         47 for(;;)
1047 80         205 {my ($np, $mp) = $p->polynomialTermOfHighestDegree($v);
1048 80         200 my ($nq, $mq) = $q->polynomialTermOfHighestDegree($v);
1049 80 100       210 last unless $np >= $nq;
1050 64         199 my $pq = sigma($mp->divide2($mq));
1051 64         162 $r = $r->add($pq);
1052 64         285 $p = $p->subtract($q->multiply($pq));
1053             }
1054 16 100       57 return $r if $p->isZero()->{id} == $zero->{id};
1055 2         7 undef;
1056             }
1057            
1058            
1059             =head3 eigenValue
1060            
1061             Eigenvalue check
1062            
1063             =cut
1064            
1065            
1066             sub eigenValue($)
1067 16     16 1 26 {my ($p) = @_;
1068            
1069             # Find divisors
1070 16         32 my %d;
1071 16         40 for my $t($p->t)
1072 67         188 {my $d = $t->Divide;
1073 67 100       180 next unless defined($d);
1074 6         16 $d{$d->id} = $d;
1075             }
1076            
1077             # Consolidate numerator and denominator
1078 16         60 my $P = $p ->clone()->z; $P = $P->multiply($d{$_}) for(keys(%d));
  16         85  
1079 16         46 my $Q = one()->clone()->z; $Q = $Q->multiply($d{$_}) for(keys(%d));
  16         70  
1080            
1081             # Check for P=nQ i.e. for eigenvalue
1082 16         56 my $cP = $P->commonFactor; my $dP = $P->divide($cP);
  16         53  
1083 16         59 my $cQ = $Q->commonFactor; my $dQ = $Q->divide($cQ);
  16         54  
1084            
1085 16 100       46 return $cP->divide($cQ) if $dP->id == $dQ->id;
1086 14         75 $p;
1087             }
1088            
1089            
1090             =head3 polynomialDivision
1091            
1092             Polynomial division.
1093            
1094             =cut
1095            
1096            
1097             sub polynomialDivision($)
1098 16     16 1 31 {my ($p) = @_;
1099            
1100             # Find a plausible indeterminate
1101 16         27 my %v; # Possible indeterminates
1102             my $v; # Polynomial indeterminate
1103 0         0 my %D; # Divisors for each term
1104            
1105             # Each term
1106 16         52 for my $t($p->t)
1107 32         109 {my @v = $t->v;
1108 32         134 $v{$_}{$t->vp($_)} = 1 for(@v);
1109 32         111 my %V = $t->split;
1110 32         100 my ($S, $D, $E, $L) = @V{qw(s d e l)};
1111 32 100 66     269 return $p if defined($S) or defined($E) or defined($L);
      66        
1112            
1113             # Each divisor term
1114 31 100       83 if (defined($D))
1115 30         75 {for my $T($D->t)
1116 60         224 {my @v = $T->v;
1117 60         243 $v{$_}{$T->vp($_)} = 1 for(@v);
1118 60         181 my %V = $T->split;
1119 60         168 my ($S, $D, $E, $L) = @V{qw(s d e l)};
1120 60 50 33     811 return $p if defined($S) or defined($D) or defined($E) or defined($L);
      33        
      33        
1121             }
1122 30         108 $D{$D->id} = $D;
1123             }
1124             }
1125            
1126             # Consolidate numerator and denominator
1127 15         60 my $P = $p ->clone()->z; $P = $P->multiply($D{$_}) for(keys(%D));
  15         103  
1128 15         47 my $Q = one()->clone()->z; $Q = $Q->multiply($D{$_}) for(keys(%D));
  15         81  
1129            
1130             # Pick a possible indeterminate
1131 15         44 for(keys(%v))
1132 19 50       28 {delete $v{$_} if scalar(keys(%{$v{$_}})) == 1;
  19         104  
1133             }
1134 15 100       55 return $p unless scalar(keys(%v));
1135 14         38 $v = (keys(%v))[0];
1136            
1137             # Divide P by Q
1138 14         24 my $r;
1139 14 100       57 $r = $P->polynomialDivide($Q, $v); return $r if defined($r);
  14         77  
1140 2 50       6 $r = $Q->polynomialDivide($P, $v); return one()->divide($r) if defined($r);
  2         13  
1141 0         0 $p;
1142             }
1143            
1144            
1145             =head3 Sqrt
1146            
1147             Square root of a sum
1148            
1149             =cut
1150            
1151            
1152             sub Sqrt($)
1153 140     140 1 203 {my ($x) = @_;
1154 140         339 my $s = $x->st;
1155 140 100       370 if (defined($s))
1156 62         195 {my $r = $s->sqrt2;
1157 62 100       208 return sigma($r) if defined($r);
1158             }
1159            
1160 114         3731 sigma(term()->c(1)->Sqrt($x)->z);
1161             }
1162            
1163            
1164             =head3 Exp
1165            
1166             Exponential (B raised to the power) of a sum
1167            
1168             =cut
1169            
1170            
1171             sub Exp($)
1172 484     484 1 675 {my ($x) = @_;
1173 484         14914 my $p = term()->one;
1174 484         1337 my @r;
1175 484         1157 for my $t($x->t)
1176 548         1538 {my $r = $t->exp2;
1177 548 100       1211 $p = $p->multiply($r) if $r;
1178 548 100       1613 push @r, $t unless $r;
1179             }
1180 484 100       1241 return sigma($p) if scalar(@r) == 0;
1181 473         1304 return sigma($p->clone->Exp(sigma(@r))->z);
1182             }
1183            
1184            
1185             =head3 Log
1186            
1187             Log to base B of a sum
1188            
1189             =cut
1190            
1191            
1192             sub Log($)
1193 1     1 1 2 {my ($x) = @_;
1194 1         4 my $s = $x->st;
1195 1 50       4 if (defined($s))
1196 1         6 {my $r = $s->log2;
1197 1 50       4 return sigma($r) if defined($r);
1198             }
1199            
1200 1         32 sigma(term()->c(1)->Log($x)->z);
1201             }
1202            
1203            
1204             =head3 Sin
1205            
1206             Sine of a sum
1207            
1208             =cut
1209            
1210            
1211             sub Sin($)
1212 136     136 1 189 {my ($x) = @_;
1213 136         315 my $s = $x->st;
1214 136 100       345 if (defined($s))
1215 120         378 {my $r = $s->sin2;
1216 120 100       331 return sigma($r) if defined($r);
1217             }
1218            
1219 113         284 my $a = $i->multiply($x);
1220 113         286 $i->multiply($half)->multiply($a->negate->Exp->subtract($a->Exp));
1221             }
1222            
1223            
1224             =head3 Cos
1225            
1226             Cosine of a sum
1227            
1228             =cut
1229            
1230            
1231             sub Cos($)
1232 141     141 1 192 {my ($x) = @_;
1233 141         294 my $s = $x->st;
1234 141 100       366 if (defined($s))
1235 125         398 {my $r = $s->cos2;
1236 125 100       336 return sigma($r) if defined($r);
1237             }
1238            
1239 118         274 my $a = $i->multiply($x);
1240 118         287 $half->multiply($a->negate->Exp->add($a->Exp));
1241             }
1242            
1243            
1244             =head3 tan, Ssc, csc, cot
1245            
1246             Tan, sec, csc, cot of a sum
1247            
1248             =cut
1249            
1250            
1251 27     27 1 39 sub tan($) {my ($x) = @_; $x->Sin()->divide($x->Cos())}
  27         104  
1252 11     11 0 18 sub sec($) {my ($x) = @_; $one ->divide($x->Cos())}
  11         31  
1253 11     11 1 21 sub csc($) {my ($x) = @_; $one ->divide($x->Sin())}
  11         29  
1254 11     11 1 21 sub cot($) {my ($x) = @_; $x->Cos()->divide($x->Sin())}
  11         27  
1255            
1256            
1257             =head3 sinh
1258            
1259             Hyperbolic sine of a sum
1260            
1261             =cut
1262            
1263            
1264             sub sinh($)
1265 34     34 1 54 {my ($x) = @_;
1266            
1267 34 50       103 return $zero if $x->{id} == $zero->{id};
1268            
1269 34         78 my $n = $x->negate;
1270 34         1085 sigma
1271             (term()->c( 1)->divideInt(2)->Exp($x)->z,
1272             term()->c(-1)->divideInt(2)->Exp($n)->z
1273             )
1274             }
1275            
1276            
1277             =head3 cosh
1278            
1279             Hyperbolic cosine of a sum
1280            
1281             =cut
1282            
1283            
1284             sub cosh($)
1285 34     34 1 70 {my ($x) = @_;
1286            
1287 34 50       100 return $one if $x->{id} == $zero->{id};
1288            
1289 34         76 my $n = $x->negate;
1290 34         1078 sigma
1291             (term()->c(1)->divideInt(2)->Exp($x)->z,
1292             term()->c(1)->divideInt(2)->Exp($n)->z
1293             )
1294             }
1295            
1296            
1297             =head3 Tanh, Sech, Csch, Coth
1298            
1299             Tanh, Sech, Csch, Coth of a sum
1300            
1301             =cut
1302            
1303            
1304 14     14 0 22 sub tanh($) {my ($x) = @_; $x->sinh()->divide($x->cosh())}
  14         34  
1305 4     4 0 7 sub sech($) {my ($x) = @_; $one ->divide($x->cosh())}
  4         11  
1306 4     4 0 8 sub csch($) {my ($x) = @_; $one ->divide($x->sinh())}
  4         11  
1307 4     4 0 7 sub coth($) {my ($x) = @_; $x->cosh()->divide($x->sinh())}
  4         11  
1308            
1309            
1310             =head3 dot
1311            
1312             Dot - complex dot product of two complex sums
1313            
1314             =cut
1315            
1316            
1317             sub dot($$)
1318 51     51 1 82 {my ($a, $b) = @_;
1319 51 50       140 $b = newFromString("$b") unless ref($b) eq __PACKAGE__;
1320 51         124 $a->re->multiply($b->re)->add($a->im->multiply($b->im));
1321             }
1322            
1323            
1324             =head3 cross
1325            
1326             The area of the parallelogram formed by two complex sums
1327            
1328             =cut
1329            
1330            
1331             sub cross($$)
1332 12     12 1 22 {my ($a, $b) = @_;
1333 12         39 $a->dot($a)->multiply($b->dot($b))->subtract($a->dot($b)->power($two))->Sqrt;
1334             }
1335            
1336            
1337             =head3 unit
1338            
1339             Intersection of a complex sum with the unit circle.
1340            
1341             =cut
1342            
1343            
1344             sub unit($)
1345 10     10 1 19 {my ($a) = @_;
1346 10         27 my $b = $a->modulus;
1347 10         40 my $c = $a->divide($b);
1348 10         29 $a->divide($a->modulus);
1349             }
1350            
1351            
1352             =head3 re
1353            
1354             Real part of a complex sum
1355            
1356             =cut
1357            
1358            
1359             sub re($)
1360 163     163 1 232 {my ($A) = @_;
1361 163 50       414 $A = newFromString("$A") unless ref($A) eq __PACKAGE__;
1362 163         200 my @r;
1363 163         456 for my $a($A->t)
1364 328 100       911 {next if $a->i == 1;
1365 174         318 push @r, $a;
1366             }
1367 163         400 sigma(@r);
1368             }
1369            
1370            
1371             =head3 im
1372            
1373             Imaginary part of a complex sum
1374            
1375             =cut
1376            
1377            
1378             sub im($)
1379 164     164 1 246 {my ($A) = @_;
1380 164 50       385 $A = newFromString("$A") unless ref($A) eq __PACKAGE__;
1381 164         187 my @r;
1382 164         349 for my $a($A->t)
1383 333 100       867 {next if $a->i == 0;
1384 156         304 push @r, $a;
1385             }
1386 164         401 $mI->multiply(sigma(@r));
1387             }
1388            
1389            
1390             =head3 modulus
1391            
1392             Modulus of a complex sum
1393            
1394             =cut
1395            
1396            
1397             sub modulus($)
1398 47     47 1 70 {my ($a) = @_;
1399 47         122 $a->re->power($two)->add($a->im->power($two))->Sqrt;
1400             }
1401            
1402            
1403             =head3 conjugate
1404            
1405             Conjugate of a complexs sum
1406            
1407             =cut
1408            
1409            
1410             sub conjugate($)
1411 11     11 1 18 {my ($a) = @_;
1412 11         23 $a->re->subtract($a->im->multiply($i));
1413             }
1414            
1415            
1416             =head3 clone
1417            
1418             Clone
1419            
1420             =cut
1421            
1422            
1423             sub clone($)
1424 78     78 1 122 {my ($t) = @_;
1425 78 50       217 $t->{z} or die "Attempt to clone unfinalized sum";
1426 78         359 my $c = bless {%$t};
1427 78         200 $c->{t} = {%{$t->{t}}};
  78         281  
1428 78         167 delete $c->{z};
1429 78         133 delete $c->{s};
1430 78         146 delete $c->{id};
1431 78         229 $c;
1432             }
1433            
1434            
1435             =head3 signature
1436            
1437             Signature of a sum: used to optimize add().
1438             # Fix the problem of adding different logs
1439            
1440             =cut
1441            
1442            
1443             sub signature($)
1444 4208     4208 1 5426 {my ($t) = @_;
1445 4208         5482 my $s = '';
1446 4208         8392 for my $a($t->t)
1447 25366         64747 {$s .= '+'. $a->print;
1448             }
1449 4208         14582 $s;
1450             }
1451            
1452            
1453             =head3 getSignature
1454            
1455             Get the signature (see L) of a sum
1456            
1457             =cut
1458            
1459            
1460             sub getSignature($)
1461 958     958 1 1164 {my ($t) = @_;
1462 958 50       2473 exists $t->{z} ? $t->{z} : die "Attempt to get signature of unfinalized sum";
1463             }
1464            
1465            
1466             =head3 id
1467            
1468             Get Id of sum: each sum has a unique identifying number.
1469            
1470             =cut
1471            
1472            
1473             sub id($)
1474 84     84 1 132 {my ($t) = @_;
1475 84 50       207 $t->{id} or die "Sum $t not yet finalized";
1476 84         495 $t->{id};
1477             }
1478            
1479            
1480             =head3 zz
1481            
1482             Check sum finalized. See: L.
1483            
1484             =cut
1485            
1486            
1487             sub zz($)
1488 0     0 1 0 {my ($t) = @_;
1489 0 0       0 $t->{z} or die "Sum $t not yet finalized";
1490 0         0 print $t->{z}, "\n";
1491 0         0 $t;
1492             }
1493            
1494            
1495             =head3 z
1496            
1497             Finalize creation of the sum: Once a sum has been finalized it becomes
1498             read only.
1499            
1500             =cut
1501            
1502            
1503             my $lock = 0; # Hash locking
1504             my $z = 0; # Term counter
1505             my %z; # Terms finalized
1506            
1507             sub z($)
1508 10089     10089 1 12884 {my ($t) = @_;
1509 10089 50       21856 !exists($t->{z}) or die "Already finalized this term";
1510            
1511 10089         19518 my $p = $t->print;
1512 10089 100       72289 return $z{$p} if defined($z{$p});
1513 4161         12432 $z{$p} = $t;
1514 4161         14144 weaken($z{$p}); # Greatly reduces memory usage.
1515            
1516 4161         8071 $t->{s} = $p;
1517 4161         8803 $t->{z} = $t->signature;
1518 4161         7651 $t->{id} = ++$z;
1519            
1520             #HashUtil lock_hash(%{$t->{v}}) if $lock;
1521             #HashUtil lock_hash %$t if $lock;
1522 4161         15408 $t;
1523             }
1524            
1525             #sub DESTROY($)
1526             # {my ($t) = @_;
1527             # delete $z{$t->{s}} if defined($t) and exists $t->{s};
1528             # }
1529            
1530             sub lockHashes()
1531 0     0 0 0 {my ($l) = @_;
1532             #HashUtil for my $t(values %z)
1533             #HashUtil {lock_hash(%{$t->{v}});
1534             #HashUtil lock_hash %$t;
1535             #HashUtil }
1536 0         0 $lock = 1;
1537             }
1538            
1539            
1540             =head3 print
1541            
1542             Print sum
1543            
1544             =cut
1545            
1546            
1547             sub print($)
1548 76236     76236 1 94032 {my ($t) = @_;
1549 76236 100       309773 return $t->{s} if defined($t->{s});
1550 10089         13177 my $s = '';
1551 10089         19663 for my $a($t->t)
1552 34510         86694 {$s .= $a->print .'+';
1553             }
1554 10089 100       28600 chop($s) if $s;
1555            
1556 10089         15907 $s =~ s/^\+//;
1557 10089         22381 $s =~ s/\+\-/\-/g;
1558 10089         19065 $s =~ s/\+1\*/\+/g; # change: +1* to +
1559 10089         15285 $s =~ s/\*1\*/\*/g; # remove: *1* to *
1560 10089         18286 $s =~ s/^1\*//g; # remove: 1* at start of expression
1561 10089         16638 $s =~ s/^\-1\*/\-/g; # change: -1* at start of expression to -
1562 10089         13742 $s =~ s/^0\+//g; # change: 0+ at start of expression to
1563 10089         13192 $s =~ s/\+0$//; # remove: +0 at end of expression
1564 10089         13666 $s =~ s#\(\+0\+#\(#g; # change: (+0+ to (
1565 10089         14018 $s =~ s/\(\+/\(/g; # change: (+ to (
1566 10089         14631 $s =~ s/\(1\*/\(/g; # change: (1* to (
1567 10089         14486 $s =~ s/\(\-1\*/\(\-/g; # change: (-1* to (-
1568 10089         18489 $s =~ s/([a-zA-Z0-9)])\-1\*/$1\-/g; # change: term-1* to term-
1569 10089         13807 $s =~ s/\*(\$[a-zA-Z]+)\*\*\-1(?!\d)/\/$1/g; # change: *$y**-1 to /$y
1570 10089         13377 $s =~ s/\*(\$[a-zA-Z]+)\*\*\-(\d+)/\/$1**$2/g; # change: *$y**-n to /$y**n
1571 10089         13339 $s =~ s/([\+\-])(\$[a-zA-Z]+)\*\*\-1(?!\d)/1\/$1/g; # change: +-$y**-1 to +-1/$y
1572 10089         13389 $s =~ s/([\+\-])(\$[a-zA-Z]+)\*\*\-(\d+)/${1}1\/$2**$3/g; # change: +-$y**-n to +-1/$y**n
1573 10089 100       22018 $s = 0 if $s eq '';
1574 10089         22990 $s;
1575             }
1576            
1577            
1578             =head3 constants
1579            
1580             Useful constants
1581            
1582             =cut
1583            
1584            
1585 16     16 0 48 $zero = sigma(term('0')); sub zero() {$zero}
1586 33     33 0 98 $one = sigma(term('1')); sub one() {$one}
1587 0     0 0 0 $two = sigma(term('2')); sub two() {$two}
1588 0     0 0 0 $four = sigma(term('4')); sub four() {$four}
1589 0     0 0 0 $mOne = sigma(term('-1')); sub mOne() {$mOne}
1590 0     0 0 0 $i = sigma(term('i')); sub i() {$i}
1591 0     0 0 0 $mI = sigma(term('-i')); sub mI() {$mI}
1592 0     0 0 0 $half = sigma(term('1/2')); sub half() {$half}
1593 0     0 0 0 $mHalf = sigma(term('-1/2')); sub mHalf() {$mHalf}
1594 0     0 0 0 $pi = sigma(term('pi')); sub pi() {$pi}
1595            
1596            
1597             =head3 factorize
1598            
1599             Factorize a number.
1600            
1601             =cut
1602            
1603            
1604             @primes = qw(
1605             2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61
1606             67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151
1607             157 163 167 173 179 181 191 193 197 199 211 223 227 229 233 239 241 251
1608             257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349 353 359
1609             367 373 379 383 389 397 401 409 419 421 431 433 439 443 449 457 461 463
1610             467 479 487 491 499 503 509 521 523 541 547 557 563 569 571 577 587 593
1611             599 601 607 613 617 619 631 641 643 647 653 659 661 673 677 683 691 701
1612             709 719 727 733 739 743 751 757 761 769 773 787 797 809 811 821 823 827
1613             829 839 853 857 859 863 877 881 883 887 907 911 919 929 937 941 947 953
1614             967 971 977 983 991 997);
1615            
1616             sub factorize($)
1617 0     0 1 0 {my ($n) = @_;
1618 0         0 my $f;
1619            
1620 0         0 for my $p(@primes)
1621 0         0 {for(;$n % $p == 0;)
1622 0         0 {$f->{$p}++;
1623 0         0 $n /= $p;
1624             }
1625 0 0       0 last unless $n > $p;
1626             }
1627 0         0 $f;
1628             };
1629            
1630            
1631             =head2 import
1632            
1633             Export L with either the default name B, or a name supplied by
1634             the caller of this package.
1635            
1636             =cut
1637            
1638            
1639             sub import
1640 45     45   174 {my %P = (program=>@_);
1641 45         76 my %p; $p{lc()} = $P{$_} for(keys(%P));
  45         236  
1642            
1643             #_______________________________________________________________________
1644             # New sum constructor - export to calling package.
1645             #_______________________________________________________________________
1646            
1647 45         107 my $s = "package XXXX;\n". <<'END';
1648             no warnings 'redefine';
1649             sub NNNN
1650             {return SSSSn(@_);
1651             }
1652             use warnings 'redefine';
1653             END
1654            
1655             #_______________________________________________________________________
1656             # Export to calling package.
1657             #_______________________________________________________________________
1658            
1659 45         82 my $name = 'sum';
1660 45 50       161 $name = $p{sum} if exists($p{sum});
1661 45         149 my ($main) = caller();
1662 45         99 my $pack = __PACKAGE__ . '::';
1663            
1664 45         181 $s=~ s/XXXX/$main/g;
1665 45         162 $s=~ s/NNNN/$name/g;
1666 45         160 $s=~ s/SSSS/$pack/g;
1667 45     45 0 278 eval($s);
  45     45   72  
  45     66   3774  
  45         233  
  45         75  
  45         1268  
  45         3471  
  66         403  
1668            
1669             #_______________________________________________________________________
1670             # Check options supplied by user
1671             #_______________________________________________________________________
1672            
1673 45         214 delete @p{qw(program sum)};
1674            
1675 45 50       1221 croak "Unknown option(s): ". join(' ', keys(%p))."\n\n". <<'END' if keys(%p);
1676            
1677             Valid options are:
1678            
1679             sum =>'name' Create a routine with this name in the callers
1680             namespace to create new symbols. The default is
1681             'sum'.
1682             END
1683             }
1684            
1685            
1686             =head2 Operators
1687            
1688            
1689             =head3 Operator Overloads
1690            
1691             Overload Perl operators. Beware the low priority of B<^>.
1692            
1693             =cut
1694            
1695            
1696             use overload
1697 45         884 '+' =>\&add3,
1698             '-' =>\&negate3,
1699             '*' =>\&multiply3,
1700             '/' =>\÷3,
1701             '**' =>\&power3,
1702             '==' =>\&equals3,
1703             '!=' =>\&nequal3,
1704             'eq' =>\&negate3,
1705             '>' =>\&solve3,
1706             '<=>' =>\&tequals3,
1707             'sqrt' =>\&sqrt3,
1708             'exp' =>\&exp3,
1709             'log' =>\&log3,
1710             #'tan' =>\&tan3, # 2016/01/20 15:23:53 No longer available
1711             'sin' =>\&sin3,
1712             'cos' =>\&cos3,
1713             '""' =>\&print3,
1714             '^' =>\&dot3, # Beware the low priority of this operator
1715             '~' =>\&conjugate3,
1716             'x' =>\&cross3,
1717             'abs' =>\&modulus3,
1718             '!' =>\&unit3,
1719 45     45   400 fallback=>1;
  45         94  
1720            
1721            
1722             =head3 add3
1723            
1724             Add operator.
1725            
1726             =cut
1727            
1728            
1729             sub add3
1730 282     282 1 649 {my ($a, $b) = @_;
1731 282 100       811 return simplify($a) unless defined($b); # += : simplify()
1732 269 100       877 $b = newFromString("$b") unless ref($b) eq __PACKAGE__;
1733 269 50 33     1420 $a->{z} and $b->{z} or die "Add using unfinalized sums";
1734 269         738 $a->add($b);
1735             }
1736            
1737            
1738             =head3 negate3
1739            
1740             Negate operator. Used in combination with the L operator to
1741             perform subtraction.
1742            
1743             =cut
1744            
1745            
1746             sub negate3
1747 244     244 1 653 {my ($a, $b, $c) = @_;
1748            
1749 244 50       605 if (defined($b))
1750 244 100       1032 {$b = newFromString("$b") unless ref($b) eq __PACKAGE__;
1751 244 50 33     1624 $a->{z} and $b->{z} or die "Negate using unfinalized sums";
1752 244 100       676 return $b->subtract($a) if $c;
1753 161 50       693 return $a->subtract($b) unless $c;
1754             }
1755             else
1756 0 0       0 {$a->{z} or die "Negate single unfinalized terms";
1757 0         0 return $a->negate;
1758             }
1759             }
1760            
1761            
1762             =head3 multiply3
1763            
1764             Multiply operator.
1765            
1766             =cut
1767            
1768            
1769             sub multiply3
1770 416     416 1 981 {my ($a, $b) = @_;
1771 416 100       1478 $b = newFromString("$b") unless ref($b) eq __PACKAGE__;
1772 416 50 33     2636 $a->{z} and $b->{z} or die "Multiply using unfinalized sums";
1773 416         1092 $a->multiply($b);
1774             }
1775            
1776            
1777             =head3 divide3
1778            
1779             Divide operator.
1780            
1781             =cut
1782            
1783            
1784             sub divide3
1785 167     167 1 360 {my ($a, $b, $c) = @_;
1786 167 100       728 $b = newFromString("$b") unless ref($b) eq __PACKAGE__;
1787 167 50 33     892 $a->{z} and $b->{z} or die "Divide using unfinalized sums";
1788 167 100       448 return $b->divide($a) if $c;
1789 143 50       559 return $a->divide($b) unless $c;
1790             }
1791            
1792            
1793             =head3 power3
1794            
1795             Power operator.
1796            
1797             =cut
1798            
1799            
1800             sub power3
1801 173     173 1 572 {my ($a, $b) = @_;
1802 173 50       871 $b = newFromString("$b") unless ref($b) eq __PACKAGE__;
1803 173 50 33     952 $a->{z} and $b->{z} or die "Power using unfinalized sums";
1804 173         483 $a->power($b);
1805             }
1806            
1807            
1808             =head3 equals3
1809            
1810             Equals operator.
1811            
1812             =cut
1813            
1814            
1815             sub equals3
1816 259     259 1 632 {my ($a, $b) = @_;
1817 259 100       943 $b = newFromString("$b") unless ref($b) eq __PACKAGE__;
1818 259 50 33     1185 $a->{z} and $b->{z} or die "Equals using unfinalized sums";
1819            
1820 259 100       1427 return 1 if $a->{id} == $b->{id}; # Fast equals
1821            
1822 58         163 my $c = $a->subtract($b);
1823            
1824 58 100       317 return 1 if $c->isZero()->{id} == $zero->{id};
1825 9         86 return 0;
1826             }
1827            
1828            
1829             =head3 nequal3
1830            
1831             Not equal operator.
1832            
1833             =cut
1834            
1835            
1836             sub nequal3
1837 9     9 1 20 {my ($a, $b) = @_;
1838 9         27 !equals3($a, $b);
1839             }
1840            
1841            
1842             =head3 tequals
1843            
1844             Evaluate the expression on the left hand side, stringify it, then
1845             compare it for string equality with the string on the right hand side.
1846             This operator is useful for making examples written with Test::Simple
1847             more readable.
1848            
1849             =cut
1850            
1851            
1852             sub tequals3
1853 27     27 0 65 {my ($a, $b) = @_;
1854            
1855 27 100       147 return 1 if "$a" eq $b;
1856            
1857 2         9 my $z = simplify($a);
1858            
1859 2         7 "$z" eq "$b";
1860             }
1861            
1862            
1863             =head3 solve3
1864            
1865             Solve operator.
1866            
1867             =cut
1868            
1869            
1870             sub solve3
1871 7     7 1 20 {my ($a, $b) = @_;
1872 7 50       26 $a->{z} or die "Solve using unfinalized sum";
1873             # $b =~ /^[a-z]+$/i or croak "Bad variable $b to solve for";
1874 7         26 solve($a, $b);
1875             }
1876            
1877            
1878             =head3 print3
1879            
1880             Print operator.
1881            
1882             =cut
1883            
1884            
1885             sub print3
1886 66147     66147 1 91269 {my ($a) = @_;
1887 66147 50       135254 $a->{z} or die "Print of unfinalized sum";
1888 66147         126371 $a->print();
1889             }
1890            
1891            
1892             =head3 sqrt3
1893            
1894             Sqrt operator.
1895            
1896             =cut
1897            
1898            
1899             sub sqrt3
1900 55     55 1 128 {my ($a) = @_;
1901 55 50       152 $a->{z} or die "Sqrt of unfinalized sum";
1902 55         143 $a->Sqrt();
1903             }
1904            
1905            
1906             =head3 exp3
1907            
1908             Exp operator.
1909            
1910             =cut
1911            
1912            
1913             sub exp3
1914 22     22 1 48 {my ($a) = @_;
1915 22 50       64 $a->{z} or die "Exp of unfinalized sum";
1916 22         53 $a->Exp();
1917             }
1918            
1919            
1920             =head3 sin3
1921            
1922             Sine operator.
1923            
1924             =cut
1925            
1926            
1927             sub sin3
1928 87     87 1 199 {my ($a) = @_;
1929 87 50       224 $a->{z} or die "Sin of unfinalized sum";
1930 87         228 $a->Sin();
1931             }
1932            
1933            
1934             =head3 cos3
1935            
1936             Cosine operator.
1937            
1938             =cut
1939            
1940            
1941             sub cos3
1942 92     92 1 152 {my ($a) = @_;
1943 92 50       264 $a->{z} or die "Cos of unfinalized sum";
1944 92         277 $a->Cos();
1945             }
1946            
1947            
1948             =head3 tan3
1949            
1950             Tan operator.
1951            
1952             =cut
1953            
1954            
1955             sub tan3
1956 0     0 1 0 {my ($a) = @_;
1957 0 0       0 $a->{z} or die "Tan of unfinalized sum";
1958 0         0 $a->tan();
1959             }
1960            
1961            
1962             =head3 log3
1963            
1964             Log operator.
1965            
1966             =cut
1967            
1968            
1969             sub log3
1970 1     1 1 6 {my ($a) = @_;
1971 1 50       5 $a->{z} or die "Log of unfinalized sum";
1972 1         4 $a->Log();
1973             }
1974            
1975            
1976             =head3 dot3
1977            
1978             Dot Product operator.
1979            
1980             =cut
1981            
1982            
1983             sub dot3
1984 14     14 1 71 {my ($a, $b, $c) = @_;
1985 14 100       83 $b = newFromString("$b") unless ref($b) eq __PACKAGE__;
1986 14 50 33     93 $a->{z} and $b->{z} or die "Dot of unfinalized sum";
1987 14         38 dot($a, $b);
1988             }
1989            
1990            
1991             =head3 cross3
1992            
1993             Cross operator.
1994            
1995             =cut
1996            
1997            
1998             sub cross3
1999 11     11 1 178 {my ($a, $b, $c) = @_;
2000 11 100       49 $b = newFromString("$b") unless ref($b) eq __PACKAGE__;
2001 11 50 33     72 $a->{z} and $b->{z} or die "Cross of unfinalized sum";
2002 11         33 cross($a, $b);
2003             }
2004            
2005            
2006             =head3 unit3
2007            
2008             Unit operator.
2009            
2010             =cut
2011            
2012            
2013             sub unit3
2014 9     9 1 24 {my ($a, $b, $c) = @_;
2015 9 50       35 $a->{z} or die "Unit of unfinalized sum";
2016 9         25 unit($a);
2017             }
2018            
2019            
2020             =head3 modulus3
2021            
2022             Modulus operator.
2023            
2024             =cut
2025            
2026            
2027             sub modulus3
2028 26     26 1 202 {my ($a, $b, $c) = @_;
2029 26 50       89 $a->{z} or die "Modulus of unfinalized sum";
2030 26         62 modulus($a);
2031             }
2032            
2033            
2034             =head3 conjugate3
2035            
2036             Conjugate.
2037            
2038             =cut
2039            
2040            
2041             sub conjugate3
2042 10     10 1 20 {my ($a, $b, $c) = @_;
2043 10 50       25 $a->{z} or die "Conjugate of unfinalized sum";
2044 10         27 conjugate($a);
2045             }
2046            
2047             #________________________________________________________________________
2048             # Package installed successfully
2049             #________________________________________________________________________
2050            
2051             1;