File Coverage

blib/lib/Math/Algebra/Symbols/Sum.pm
Criterion Covered Total %
statement 737 806 91.4
branch 340 482 70.5
condition 73 146 50.0
subroutine 89 102 87.2
pod 71 93 76.3
total 1310 1629 80.4


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