File Coverage

blib/lib/Math/Yapp.pm
Criterion Covered Total %
statement 699 814 85.8
branch 224 320 70.0
condition 20 30 66.6
subroutine 59 66 89.3
pod 12 56 21.4
total 1014 1286 78.8


line stmt bran cond sub pod time code
1             # Package: Math::Yapp.pm: Yet another Polynomial Package
2             #
3             package Math::Yapp;
4              
5             use overload
6 7         34 '+' => Yapp_add,
7             '+=' => Yapp_plus,
8             '!' => Yapp_negate, # Negate
9             '-' => Yapp_sub,
10             '-=' => Yapp_minus,
11             '*' => Yapp_times,
12             '*=' => Yapp_mult,
13             '/' => Yapp_dividedby,
14             '/=' => Yapp_divide,
15             '~' => Yapp_conj, # Conjugate complex coefficients
16             '**' => Yapp_power, # Raise a polynomial to an integer power
17             '.' => Yapp_innerProd # Inner product, for vector operations.
18 7     7   104591 ;
  7         5215  
19              
20 7     7   884 use 5.014002;
  7         17  
21 7     7   29 use strict;
  7         26  
  7         133  
22 7     7   22 use warnings;
  7         8  
  7         156  
23 7     7   21 use Carp;
  7         7  
  7         466  
24 7     7   3815 use Math::Complex;
  7         48325  
  7         959  
25             #use Math::BigFloat; # Needed for accuracy(), though I don't do
26             # # anything with that yet.
27 7     7   4017 use Storable 'dclone';
  7         15756  
  7         361  
28 7     7   3614 use Data::Dumper; # For debugging
  7         37292  
  7         365  
29 7     7   35 use Scalar::Util qw(refaddr); # Gets pointer values. Also for debugging.
  7         7  
  7         2293  
30              
31             require Exporter;
32              
33             our @ISA = ('Exporter');
34              
35             # Items to export into callers namespace by default. Note: do not export
36             # names by default without a very good reason. Use EXPORT_OK instead.
37             # Do not simply export all your public functions/methods/constants.
38              
39             # This allows declaration use Math::Yapp ':all';
40             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
41             # will save memory.
42             #
43             our %EXPORT_TAGS = ( 'all' => [ qw(Yapp Yapp_decimals Yapp_print0
44             Yapp_start_high) ] );
45              
46             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
47              
48             #our @EXPORT = qw(all);
49             our @EXPORT = qw(Yapp Yapp_interpolate Yapp_by_roots
50             Yapp_testmode
51             Yapp_decimals Yapp_print0 Yapp_start_high Yapp_margin
52             Yapp_Orthogonal
53             Csprint); # Unexport Csprint after debug
54              
55             our $VERSION = '1.07';
56              
57             my $class_name = "Math::Yapp"; # So I don't have to use the literal in tests
58             my $class_cplx = "Math::Complex"; # Same idea - avoid literal use
59              
60             # Here are the patterns I need to validate and parse the terms of a polynomial:
61             # They will be used by str2yapp() and str2cplx()
62             #
63             my $sign_pat = qr/[+-]/; # Allows me to isolate the optional sign
64              
65             # Combine integer_pat and decimal_pat for real_pat
66             #
67             my $integer_pat = qr/\d+/; # Integer is one or more digits
68             my $decimal_pat = qr/\.\d+/; # Decimal: A period followed by digits
69             my $decnum_pat = qr/($integer_pat)($decimal_pat)/; # Complete decimal number
70              
71             # A real number may be:
72             # - An integer /\d+/
73             # - A pure decimal number: /\.\d+/
74             # - An integer followd by a pure decimal i.e. a complete decimal number
75             # I am checking these in reverse of above order, testing for the more
76             # complicated pattern first.
77             # Of course, all the above may be preceeded by an optinal sign
78             #
79             my $real_pat = qr/(($decnum_pat)|($decimal_pat)|($integer_pat))/;
80              
81             my $cplx_pat = qr/\([+-]?$real_pat[+-]($real_pat)i\)/; # Cplx: (+-real+-real-i)
82              
83             # Now a coefficient is a real or complex number at the beginning of a term,
84             # optionally preceeded by a sign. If sign is omitted we will assume positive.
85             # On the other hand, it may be an implicit 1 with only a sign. Hence, both
86             # parts are basically optional; if omitted e.g. x^31, then coefficient is +1
87             #
88             # As to a regex pattern: A coefficient is at the start of the term and
89             # may be:
90             # - A sign alone => an implicit coefficient of 1.0 ($sign_only_pat)
91             # - A complex or real number alone => implicit sign of +1 ($usigned_coef_pat)
92             # = A real or complex number preceded by a sign. ($signed_coef_pat)
93             # The proper pattern tests for the most complicated possibility first
94             #
95             my $signed_real_pat = qr/^(($sign_pat)?($real_pat))$/;
96             my $signed_coef_pat = qr/((^$sign_pat)(($cplx_pat)|($real_pat)))/;
97             my $usigned_coef_pat = qr/^(($cplx_pat)|($real_pat))/;
98             my $sign_only_pat = qr/(^$sign_pat)/;
99             my $coef_pat = qr/($signed_coef_pat)|($usigned_coef_pat)|($sign_only_pat)/;
100              
101             my $varia_pat = qr/[A-Za-z]+/; # The "variable" part of the term
102             my $power_pat = qr/\^\d+$/; # Power: caret (^) followed by am integer.
103             # Must be at the end of the term
104             # With apologies: This is a patch to fix a bug in Ysprint(): It was printing
105             # the 0-term as X^0 when formatting starting at low-degree terms.
106             # This will help get rid of that.
107             #
108             my $zero_power_pat = qr/($varia_pat)(\^0)$/;
109              
110             my $const_pat = qr/^$coef_pat$/; # Coefficient only; no variable or exponent
111              
112             # Now that I have defined the patterns for the parts of a term, I can define
113             # the pattern for a complete term. Each part is optional.
114             #
115             my $term_pat = qr/($coef_pat)?($varia_pat)?($power_pat)?/;
116              
117             # However, one pattern not allowed is a $power_pat (exponent) without a
118             # variable. Since, at the momemtn, I cannot think of a regex to test for
119             # this condition, I'll have to handle it in the code. (Sigh)
120              
121             # Format strings for printf-ing the terms of the polynomial.
122             # First: For a real coefficient: Assume the data to printf includes:
123             # - Width of the coefficient, including decimal point and decimal places
124             # - Number of decimal places alone
125             # - The coeficient, a floating point number
126             # - The variable string (like X, Z, GIZORP etc.) This is omitted for the
127             # constant term
128             # - The exponent (Omitted for the constant term of the polynomial)
129             #
130             my $zero_term_fmt = "%+*.*f"; # No X term in here
131             my $one_term_fmt = "%+*.*f%s"; # For 1st degree term, omit the exponent
132             my $term_fmt = "%+*.*f%s^%d"; # For other terms, always use sign
133             my $first_term_fmt = "%*.*f%s^%s"; # For first term, skip + for first if
134             # it is positive anyway
135             # Now for a complex coefficient: Here format gets a bit more, er, complex;
136             # Each term includes the following in the data portion of printf:
137             # - Width of the real part of the coeficient
138             # - Number of decimal places in the real part
139             # - The real part itself
140             # - Width of the imaginary part of the coeficient
141             # - Number of decimal places in the imaginary part
142             # - The imaginary part itself
143             # - The variable string (Omitted in the constant term)
144             # - The exponent (Omitted in the constant term)
145             #
146             my $zero_term_fmt_i = "+(%*.*f%+*.*fi)"; # No variable or exponent
147             my $one_term_fmt_i = "+(%*.*f%+*.*fi)%s"; # +(Re+/-Im_i)X Variable, no
148             # exponent
149             my $term_fmt_i = "+(%*.*f%+*.*fi)%s^%d";# Sign, Variable, exponent
150             my $first_term_fmt_i = "(%*.*f%+*.*fi)%s^%d";# No sign but variable and
151             # exponent
152             # Some other constants I need for this module, before starting the work in
153             # earnest:
154             #
155             our $czero = Math::Complex->make(0,0); # Complex zero
156             our $inner_prod = "(generic)"; # Default inner product type; set with
157             # inner_prod()
158             # These globals control behavior of Ysprint. Changed by indicated functions
159             our $dec_places = 3; # Can be changed with call to Yapp_decimals()
160             our $dec_width = 4; # Minimum width: 4, with 3 decimal paces
161             our $print_zero = 0; # Default: FALSE: Skip zero-coefficient terms
162             # Changed by Yapp_print0()
163             our $start_high = 1; # Default: True: Start printing from high-degree terms.
164             # If FALSE, start from low-degree terms. Set by
165             # Yapp_start_high()
166             our $margin = 1/(10**10); # What is close enough to ignore rounding error?
167             #our $margin = 1/(10**8); # What is close enough to ignore rounding error?
168             our $testmode = 0; # Turned on by class method Yapp_testmode()
169              
170             BEGIN
171       7     {
172             #Math::BigFloat->accuracy(32); # In case I decide on a way to use complex
173             # numbers with BigFloat
174             }
175             #
176             #------------------------------------------------------------------------------
177             # Class setting functions: These functions set [semi] global variables used
178             # only by other function this package.
179             #------------------------------------------------------------------------------
180             # Yapp_testmode() Turns on or off the semi-global variable $testmode.
181             # This is not a class method - it is a straightforward function call.
182             #
183             sub Yapp_testmode
184             {
185 6 50   6 0 60 $testmode = $_[0] if (defined($_[0])); # Set the variable, if user sent
186             # a value to stick in there
187 6         11 return $testmode;
188             }
189             #------------------------------------------------------------------------------
190             # Yapp_margin(): Set the threshold for how close to require something be to
191             # a solution in order to be considered close enough. This sets the variable
192             # $margin, whcih is set to 1.0/(10.0 ** 10). It is intended to compensate
193             # for inevitable rounding errors that, for example, make a real number look
194             # like a complex number with a really tiny imaginary part (like 1.23456e-15i).
195             # Parameter:
196             # - (Optional) The floating point value to be the closeness factor.
197             # Returns:
198             # - The current (or new) setting for this margin.
199             #
200             sub Yapp_margin
201             { # If caller passed a parameter, use it. If not, just get the current setting
202             #
203 1 50   1 0 301 my $new_margin = (defined($_[0])) ? $_[0] : $margin;
204 1         2 $margin = $new_margin; # May be wasted but checking if different
205             # also costs.
206 1         32 return $margin;
207             }
208             #------------------------------------------------------------------------------
209             # dec_places(): Set or retrieve the precision to be used when
210             # printing coefficients of the polynomial. Default is 3.
211             # - To set precision:
212             # yapp_ref->dec_places(17); # Set output precision of 17 decimal
213             # # places to the right of the point,
214             # Math::Yapp->dec_places(17); # Will have the same effect.
215             #
216             # - To retrieve it:
217             # $int_var = jpoly_ref->dec_places();
218             # In both cases, it returns the precision to the caller.
219             #
220             sub dec_places
221             {
222 0     0 0 0 my $self = shift(@_);
223 0 0       0 if (@_ != 0) # Passed a parameter
224             {
225 0         0 $dec_places = $_[0]; # Set the number of places to that
226 0         0 $dec_width = $dec_places + 1; # Add 1 place for decimal point. (Probably
227             # not needed but cleaner logic)
228             }
229 0         0 return $dec_places; # Either way, that's the return value
230             }
231             #
232             #------------------------------------------------------------------------------
233             sub Yapp_decimals
234             { # (Replaces dec_places method)
235 14 100   14 1 503 if (defined($_[0])) # If user passed a parameter
236             {
237 13         17 $dec_places = $_[0]; # Set global decimal places for printf()
238 13         21 $dec_width = $dec_places + 1; # Add 1 place for decimal point. (Probably
239             } # not needed but cleaner logic)
240             # else # No parameter; just want to know it
241             # { } # (Nothing really; just a placeholder
242 14         16 return $dec_places; # Either way, send that back to caller
243             }
244             #------------------------------------------------------------------------------
245             # Yapp_print0(): Function to dictate if Ysprint will print terms with 0
246             # coefficient.
247             # Parameters:
248             # - (Optional) 1 (TRUE) or 0 (FALSE)
249             # If called w/no parameter, just returns the current value
250             # Returns:
251             # The current or newley set value.
252             sub Yapp_print0
253             {
254 5 50   5 1 29 if (defined($_[0])) {$print_zero = $_[0]; }
  5         9  
255 5         6 return $print_zero;
256             }
257             #------------------------------------------------------------------------------
258             # Yapp_start_high() - Sets $start_high to 0 or 1, or just returns that value.
259             # This decides if Ysprint will generate output starting from high-degree
260             # terms (default) or start low and work its way up in degree.
261             #
262             sub Yapp_start_high
263             {
264 5 50   5 1 21 if (defined($_[0])) {$start_high = $_[0]; }
  5         9  
265 5         6 return $start_high;
266             }
267             #
268             #------------------------------------------------------------------------------
269             # Constructors:
270             #------------------------------------------------------------------------------
271             # Math::Yapp::new(): Create a new polynomial#
272             # This method is heavily overloaded but it alwys returns the same type: a
273             # reference to a Yapp polynomial object. It will accept the following
274             # parameters:
275             # - Nothing; It returns a degenerate polynomial with no coefficients of
276             # degree, the equivalent of a NULL polynomial.
277             # - A list of numbers, real or complex. The first number is the 0-degree
278             # coefficient and the last, [n], is the coefficient of the highest-degree
279             # term.
280             # - A reference to a list of numbers i.e. \@list. THis list is identical
281             # to the explicit list described above.
282             # - A string in the form nnnX^m1 + nnnX^m2 .. etc.
283             #
284             # A note about complex coefficients:
285             # - In the string-form parameter, it must be in the form +-(REAL+-IMAGi)
286             # - In the list (or list-reference) form, a complex may be of the above
287             # string form or a true complex object, as returned by Math::Complex
288             #
289             sub new
290             {
291 148     148 1 137 my $class = shift(@_); # Get class name out of the way
292 148         128 my $self = {}; # Create new entity
293 148         106 bless $self; # Makes it an object
294 148         97 my $lc; # Loop counter (general purpose here)
295              
296 148         438 $self->{degree} = 0; # Initially IS a 0-degree polynomial
297 148         140 $self->{variable} = "X"; # For printing, use this variable. Can be set
298 148         173 $self->{coeff}[0] = 0; # Even a degenerate polynomial needs a coefficient
299             # to any string using the method variable()
300 148         111 my $aref = 0; # Will step through one array or another
301 148         105 my $coef_count = @_; # How many coefficients were passed to me?
302              
303 148 100       268 if ($coef_count > 1) # If caller passed me a list of numbers
    100          
304 41         39 { $aref = \@_; } # Point to the parameter list, save for soon
305             elsif ($coef_count == 1) # Passed only one parameter: Our possibilities:
306             {
307 95 100       128 if (ref($_[0]) eq "ARRAY") # I got a reference to a list
308 56         51 { $aref = shift(@_); } # Reference that list with same $aref
309             else # Next possibility: The parameter is a string
310 39         67 { $self->str2yapp($_[0]); } # Turn the string into a polynomial
311             }
312             # Remaining possibility: That $coef_count == 0 and user wants that degenerate
313             # polynommial. Do nothing for now; Will return a zero polynomial
314             #
315 148 100       229 if ($aref != 0) # If we have an array pointer
316             { # step through the array to build the polynomial
317 97         80 $self->{degree} = -1; # Degree shall end up 1 less than count of terms
318 97         105 for ($lc = 0; $lc <= $#{$aref}; $lc++) # Build array of coefficients
  489         653  
319             {
320             # Note: I am adding the 0.0 to force Perl to store the value as a
321             # number (real or ref(complex))
322             #
323 392         437 $self->{coeff}[$lc] = $aref->[$lc] + 0.0; # Use next number on list
324 392         4137 $self->{degree}++; # Don't lose track of degree
325             }
326             }
327              
328 148         200 $self->refresh(); # Last minute bookkeeping on our new object
329 148         164 return $self; # See? Even the empty object gets passed back
330             }
331             #
332             #------------------------------------------------------------------------------
333             # A neater version of calling new() - Just call Yapp(parameter[s]).
334             # Accepts all types that new() accepts.
335             #
336             sub Yapp
337             {
338 265     265 1 4705 my $class = $_[0]; # Most likely the class name, may be string, array
339 265         169 my $ryapp; # Alternative return object to caller
340              
341 265 100       376 if (ref($class) eq $class_name) # If this is copy-call, then the param
342             { # is not really a class name but a Yapp
343 117         132 $ryapp = $class->Yapp_copy(); # instance; construct clone of original
344             }
345             else # Not a reference
346 148         296 { $ryapp = __PACKAGE__->new(@_); } # Just pass all parameter(s) to new()
347 265         331 return $ryapp; # However we got it, return it to caller
348             }
349              
350             #------------------------------------------------------------------------------
351             # Yapp_copy() - Internal utility function, should be called by new() in case
352             # a copy-constructor is called for.
353             # Parameters:
354             # - (Implict) A reference to the source Yapp-polynomial for the copy
355             # Returns:
356             # - A referecne to a new Yapp object identical to the source
357             #
358             sub Yapp_copy
359             {
360 117     117 0 92 my $self = shift(@_); # Capture the object
361 117         3756 my $new_yapp = dclone($self); # Duplicate object, get back reference
362 117         174 return $new_yapp;
363             }
364             #
365             #------------------------------------------------------------------------------
366             # refresh() - (Internal?) Method to perform two smoothing operations on
367             # the given Yapp polynomial:
368             # - In case it got omitted in a contructor, if a coefficient between 0 and
369             # the degree is still undefined, stick a 0.0 in there.
370             # - If any complex coefficients have a 0 imaginary part, replace it with the
371             # real part only, as a real coefficient. e.g. cplx(-2.13,9) => -2.13
372             # - It looks for any 0 coefficient in the highest-degree places so that a 5th
373             # degree polynomila with 0 coefficients in the6th and 7th dregee places
374             # does not appear to be a 7th degree polynomial
375             # Parameter:
376             # - (Implicit) The Yapp to thus fixed up
377             # Returns:
378             # - The same reference but it has been mutated as described.
379             #
380             sub refresh
381             {
382 148     148 0 110 my $self = shift(@_);
383 148         93 my $slc; # Loop counter for counting up my own array
384              
385             # This first loop can handle the undefined coefficient problem as well as the
386             # disguised real-number propmlem
387             #
388 148         254 for ($slc = 0; $slc <= $self->{degree}; $slc++)
389             {
390             # Make sure there are no undefined coefficients in there.
391             # (Zero is better than nothing.)
392             #
393 464 100       608 $self->{coeff}[$slc] = 0.0 if (! defined($self->{coeff}[$slc]));
394              
395 464         471 realify($self->{coeff}[$slc]); # In case of complex coeff w Im == 0
396             }
397              
398             # Next: Make sure the highest-degree term has a non-zero coefficient;
399             # adjust the degree to match this. (Except if degree == 0. That's legit)
400             #
401 148         250 for ($slc = $self->{degree}; $slc > 0; $slc--)
402             {
403 113 100       171 last if ($self->{coeff}[$slc] != 0); # Found my non-0 highest term; done
404              
405             # Still here: The current high-order term is a degenerate term with a
406             # zero-coefficient. Lose it!
407             #
408 4         5 undef($self->{coeff}[$slc]); # This loses the term
409 4         5 $self->{degree}-- # This insures (?) it won't be referenced
410             }
411              
412             # Finally: Since the coefficients may have been diddled, the coefficients of
413             # the derivative and integral, if they have been generated, are unreliable.
414             # Rather than regenerate them now, just lose them. If they are needed again
415             # they will be automatically and transparently regenreated.
416             #
417 148 50       258 undef $self->{derivative} if (defined($self->{derivative}[0]));
418 148 50       220 undef $self->{integral} if (defined($self->{integral}[0]));
419              
420             # After all that, it is *still* possible for {variable} to be undefined.
421             # This will be the case for a constant term alone, a 0-degree polynomial
422             # string. Put the default in there now.
423             #
424 148 50       190 $self->{variable} = "X" if (!defined($self->{variable}));
425 148         112 return ($self);
426             }
427             #
428             #------------------------------------------------------------------------------
429             # Ysprint(): Returns a string with the polynomial in some standard form,
430             # suitable for printing.
431             # Parameters:
432             # - (Implicit) The Yapp opject
433             # - (Optional) The number of decimal places to use for the coefficients. If
434             # omitted, we will use themodule global $dec_places. See Yapp_decimals for
435             # more information on that.
436             # Default options:
437             # - Use X as the variable
438             # - Skip terms with 0-coefficients
439             # - Output highest powers first, as one would write in algebra class
440             # Funtions have been provided to change these behaviors. These are:
441             # - Yapp_decimals(), so that you may call method Ysprint() without a parameter.
442             # - Yapp_print0()
443             # - Yapp_start_high()
444             #
445             sub Ysprint
446             {
447 93     93 1 1003 my $self = shift(@_);
448 93 50       142 my $places = (defined($_[0])) ? $_[0] : $dec_places;
449 93 50       107 my $dwidth = (defined($_[0])) ? $places + 1 : $dec_width;
450              
451 93         123 my $var_name = $self->variable();
452 93         83 my $out_string = "";
453 93         76 my $use_fmt = \$first_term_fmt; # Make sure I use this first
454              
455 93         65 my $lc_start; # Start loop at high or low degree terms?
456 93         64 my $lc_finish = 0; # And where do I finish, based on above?
457 93         63 my $lc_diff = -1; # and in which direction doed my loop counter go?
458              
459 93 100       113 if ($start_high) # Default: Start from high-degree terms
460             {
461 51         50 $lc_start = $self->{degree}; # Start loop at high-power term
462 51         34 $lc_finish = 0; # End loop at 0-power term
463 51         36 $lc_diff = -1; # Normal behavior: Start high, step low
464             }
465             else
466             {
467 42         25 $lc_start = 0; # Start loop at 0-degree term
468 42         33 $lc_finish = $self->{degree}; # End loop at high-degree term
469 42         28 $lc_diff = 1; # work my way UP the degrees
470             }
471              
472 93         86 for (my $lc = $lc_start; ; $lc += $lc_diff) #(Check value of $lc inside
473             { # loop, not in loop header)
474 711 100 100     2240 last if ( ($lc > $self->{degree}) || ($lc < 0) );
475              
476             # How to format the coefficient depends on whether it is real or complex
477             #
478 618         458 my $term = ""; # Start the term as a null string and build from there
479 618 100       810 if (ref($self->{coeff}[$lc]) eq $class_cplx)
480             {
481             # First term should not have a + sign if coefficient is positive
482             # Constant term should not dislay X to the 0th degree
483             # First degree term should display as X, not X^1
484             # All other terms should include both aspects.
485             #
486 82 100       140 if ($lc == $lc_start) # If I'm generating the first term
    100          
    100          
487 14         14 { $use_fmt = \$first_term_fmt_i;} # No + sign
488             elsif ($lc == 1)
489 12         14 { $use_fmt = \$one_term_fmt_i; } # No exponent
490             elsif ($lc == 0 )
491 8         9 { $use_fmt = \$zero_term_fmt_i; } # No variable
492             else
493 48         44 { $use_fmt = \$term_fmt_i; } # +/-,var and exponent
494              
495 82 50 33     219 if ( ($self->{coeff}[$lc] != $czero) || ($print_zero) )
496             {
497 82         813 $term =sprintf(${$use_fmt},
  82         112  
498             $dwidth, $places, ($self->coefficient($lc))->Re(),
499             $dwidth, $places, ($self->coefficient($lc))->Im(),
500             $var_name, $lc);
501              
502             }
503             }
504             else # The ref() function did not say "Complex". So it
505             { # must be a real coefficient.
506             # Note: Same sign, variable, exponent conventions apply as above
507             #
508 536 100       835 if ($lc == $lc_start) # If I'm generating the first term
    100          
    100          
509 79         67 { $use_fmt = \$first_term_fmt;} # No +
510             elsif ($lc == 1)
511 81         71 { $use_fmt = \$one_term_fmt; } # No exponent
512             elsif ($lc == 0)
513 43         47 { $use_fmt = \$zero_term_fmt; } # No variable
514             else
515 333         326 { $use_fmt = \$term_fmt; } # +/-,variable and exponent
516              
517              
518 536 100 66     983 if ( ($self->{coeff}[$lc] != 0.0) || ($print_zero) )
519             {
520 511         315 $term = sprintf(${$use_fmt}, $dwidth, $places,
  511         736  
521             $self->coefficient($lc), $var_name, $lc);
522             }
523             }
524             # Second part of the patch I described when I defined $zero_power_pat:
525             # Lose the X^0 part of the term if I am on the 0-degree term.
526             #
527 618 100       3251 $term =~ s/($zero_power_pat)$// if ($lc == 0);
528              
529             # If the output target string is still empty, this is the first occupant
530             #
531 618 100       840 if ("$out_string" eq "") { $out_string = $term; }
  93         122  
532             else # Something alrady in there;
533             { # Append blank and current term
534 525 100       1453 $out_string = sprintf("%s %s", $out_string, $term)
535             if ($term ne ""); #(Provided our term has substance also)
536             }
537             } #(End of loop; at exit, $out_string is built)
538 93         5137 return $out_string;
539             }
540             #
541             #------------------------------------------------------------------------------
542             # print(): Method to print a formatted Yapp polynomial. Basically, just a
543             # wrapper for Ysprint. I expected this to be used mainly in debugging
544             # sessions so that I can say "p $something" but that doesn't work.
545             # (And I'm not ready to start with Tie::Handle at this juncture,
546             # although that seems to be the way to override the built-in print
547             # function.)
548             # Parameters:
549             # - (Implicit) Ref to a Yapp object
550             # - (Optional) Number of decimal places for the coefficients
551             # Returns: (Whatever)
552             #
553             sub print
554             {
555 0     0 1 0 my $yobj = shift(@_);
556 0         0 my $ystring = $yobj->Ysprint($_[0]); # There may be a places paremeter
557 0         0 print $ystring; # Output it - no automatic \n
558             }
559             #------------------------------------------------------------------------------
560             # Csprint(): Utility function to format a complex number in my display format
561             # e.g. (-3.24+6.03i), including the parenteses. The number of
562             # decimal places is based on the same Yapp_decimals() settings of
563             # $dec_places and $dec_width used by Ysprint.
564             # Parameters:
565             # - [Reference to] a complex number of type Math::Complex
566             # - (Optional) Number of decimal places to display. If omitted, use the
567             # number in $dec_places
568             # Returns:
569             # - A parenthesized string in the form shown in the above blurb.
570             # Note: If a scalar is passed instead, it will be returned as received
571             #
572             sub Csprint
573             {
574 40     40 0 200 my $cnum = shift(@_);
575 40 50       55 my $places = (defined($_[0])) ? $_[0] : $dec_places;
576 40 50       48 my $dwidth = (defined($_[0])) ? $places + 1 : $dec_width;
577 40 100       607 return $cnum if (ref($cnum) ne $class_cplx); # Don't bother with non-complex
578              
579 32         51 my $rstring = sprintf("(%*.*f%+*.*fi)",
580             $dwidth, $places, $cnum->Re(),
581             $dwidth, $places, $cnum->Im());
582 32         933 return $rstring;
583             }
584             #
585             #------------------------------------------------------------------------------
586             #
587             # realify(): Internal utility function. Loses extremely small real or imaginary
588             # component of a complex number, since we can assume it came from
589             # a rounding error.
590             # Parameter:
591             # - A real number or [Ref to] a Math::Complex number
592             # OR
593             # - An array of numbers that each may be real or complex. In this case we
594             # assume it was called in a list context. Violate that assumption and die!
595             # Returns:
596             # - If real:
597             # o If non-trivial, just return that number.
598             # o If trivial, return 0.0
599             # - If complex:
600             # o If the imaginary part is very small, less than $margin in absolute value,
601             # return only the real part as a real.
602             # o If real part is trivial, set it to 0.0 outright
603             # o If both components are non-trivial, return the reference unscathed.
604             # - If an array;
605             # o And array of numbers thus transformed
606             # If called with no return (void context like the chomp function) operate
607             # directly on the parameter or the array of numbers.
608             #
609             sub realify
610             {
611 2855     2855 0 1841 my $thing = $_[0]; # Don't shift, we might need to modify in place
612 2855         1670 my $rval = 0.0;
613              
614 2855 50       3268 die "You may not pass a list to realify(); use array reference"
615             if ($#_ > 0);
616              
617             # Real or complex, if absolute value is really tiny, "correct" to 0
618             #
619 2855 100       3526 if (wantarray) # Array context: Assume $thing is an
    100          
620             { # array reference.
621             #my @rlist = map {realify($_)} @$thing; # Return list
622             # Don't use map for this recursive call. Apparently because there is an
623             # array as the lvalue to the map{}, realify thinks it was called in the
624             # list context. Rather resort to brute force loop.
625             #
626 222         139 my @rlist; # List to be returned
627 222         354 for (my $rlc = 0; $rlc <= $#$thing; $rlc++)
628             {
629 1218         1087 $rlist[$rlc] = realify($thing->[$rlc]);
630             }
631 222         323 return @rlist;
632             }
633             elsif (defined(wantarray)) # Scalar context: assume $thing is a scalar
634             { # and caller wants a new value returned
635 1817 100       2488 return (0.0) if (abs($thing) < $margin); # Just tiny abs value: Quickie
636              
637             # If either component of a complex number is really tiny, set it to 0.
638             # And if it's the imaginary component, lose it altogether
639             #
640 1565 100       4077 if (ref($thing) eq $class_cplx) # If I have not zeroed it above then
641             { # I am permitted to check its components
642 240         182 $rval = $thing; # Preserve original complex number
643 240 100       334 if (abs($thing->Re) < $margin) # Need to keep real part, even if tiny
644             {
645 5         34 $rval = cplx(0.0, $thing->Im) # So just zero it out
646             }
647 240 100       1673 if (abs($thing->Im) < $margin) # If imaginary component is tiny
648             {
649 37         208 $rval = $thing->Re; # lose it completely and return a real
650             }
651             }
652             else # So it is real and its abs val is >= $margin
653             { # ==> I already know in can't zero it.
654 1325         806 $rval = $thing; # So the return value is the original number
655             }
656 1565         3852 return $rval;
657             }
658             else # Void context: Operate on the passed object.
659             {
660 816 100       901 if (ref($thing) eq "ARRAY") # Realifying every element in an array?
661             {
662 222         227 @{$thing} = realify($thing); # Tickle the wantarray code above
  222         440  
663             }
664             else # Realify only one object
665             { # Just work on that one
666 594         602 $_[0] = realify($_[0]); # Tickle the defined(wantarray) code above
667             }
668             # Note: No return statement.
669             }
670             }
671             #
672             #------------------------------------------------------------------------------
673             # all_real() - Method to just know if a Yapp polynomial has strictly real
674             # coefficients.
675             # Parameter:
676             # - [Ref to] a Yapp polynomial object
677             # Returns:
678             # - 1 (true) if all coeffienents are real
679             # - 0 (false) if even one os complex
680             #
681             sub all_real
682             {
683 6     6 0 6 my $self = shift(@_);
684 6         4 my $allreal = 1; # OK to start optimistic
685 6         4 foreach my $coef (@{$self->{coeff}})
  6         10  
686             {
687 29 50       40 if (ref($coef) eq $class_cplx)
688             {
689 0         0 $allreal = 0; # Not all real
690 0         0 last; # No point in continuing
691             }
692             }
693 6         12 return $allreal;
694             }
695             #
696             #------------------------------------------------------------------------------
697             # Yapp_equiv(): Method to compare a pair of Yapp polynomials, but only by the
698             # aspects that count: the degree and the coefficients. I don't
699             # care about the variable name nor about {deviative} or {integral}
700             # for this comparison
701             # Parameters:
702             # - (Implicit) One Yapp to compare against
703             # - The Yapp to compare to the first.
704             # (At this time, I am not overloading the == operator)
705             #
706             sub Yapp_equiv
707             {
708 1     1 0 8 my ($self, $compr) = @_;
709 1         3 my $comp_ok = 1; # Start by assuming true
710              
711 1 50       5 if ($self->{degree} == $compr->{degree})
712             {
713 1         4 for (my $dlc = 0; $dlc <= $self->{degree}; $dlc++)
714             {
715 6 50       9 $comp_ok = 0 if ($self->{coeff}[$dlc] != $compr->{coeff}[$dlc]);
716 6 50       13 last unless $comp_ok; # If found unequal coefficients, quit
717             }
718             }
719 0         0 else {$comp_ok = 0;} # If even degrees don't agree, soooo not equivalent!
720              
721 1         4 return $comp_ok;
722             }
723             #
724             #------------------------------------------------------------------------------
725             # str2cplx() - Internal utility function, not an object method.
726             # This function accepts a string and converts it to a complex number.
727             #
728             # Parameter:
729             # - A string already determined to match our syntax for a complex number
730             # Returns:
731             # - A [reference to] a proper Math::Complex::cplx complex number
732             #
733             sub str2cplx
734             {
735 4     4 0 5 my $c_string = shift(@_); # Grab the string
736              
737             # Plan: I will need to take apart the string, first stripping the parentheses
738             # and the letter i, then separating the components by splitting it by the
739             # sign between the components. But take care to not be fooled by a possible
740             # sign before the real component
741             #
742 4         4 my $rsign = 1; # Initially assume real component is positive
743 4         3 my $rval = $czero; # Return value: Establish it as a complex number
744 4         11 $c_string =~ s/[\(\)i]//g; # First, lose the parentheses and "i"
745 4 100       24 if ($c_string =~ /^($sign_pat)/) # Now, if there *is* a sign before the
746             { # real component
747 2 50       4 $rsign = ($1 eq "-") ? -1 : 1; # then [re]set the multiplier accordingly
748 2         5 $c_string =~ s/$sign_pat//; # and lose sign preceding the real
749             }
750              
751             # And now the complex number (still a string) looks like A[+-]Bi
752             #
753 4         12 my @c_pair = split /([+-])/, $c_string; # Split by that sign, but enclose
754             # the pattern in ()
755             # The effect of ([+-]), as opposed to [+-] without the parentheses:
756             # I capture the actual splitting string; the array produced by the split
757             # includes the splitting string itself. Thus, my array contains [A, -, B]
758             # (or +plus) as element[1] and the imaginary component is actually
759             # element[2]. We'll change that after we use it.
760             #
761 4 100       7 my $isign = ($c_pair[1] eq "-") ? -1 : 1; # But what was that sign?
762 4         5 $c_pair[2] *= $isign; # Recover correct sign of
763             # imaginary component
764 4         12 @c_pair = @c_pair[0,2]; # Now lose that im sign
765 4         4 $c_pair[0] *= $rsign; # Good time to recall the sign
766             # on the real component
767 4         6 $rval = Math::Complex::cplx(@c_pair); # Turn pair into complex ref
768 4         115 return $rval; # QED
769             }
770             #
771             #------------------------------------------------------------------------------
772             # Method: degree(): Just return the degree of the Yapp polynomial
773             # Usage:
774             # ivar = jpoly_ref->degree();
775             sub degree
776             {
777 1     1 1 300 my $self = shift(@_);
778 1         4 return ($self->{degree});
779             }
780              
781             #------------------------------------------------------------------------------
782             # Method: coefficient(): Return the coefficient of the inticated term.
783             # - If the coefficientis a real number, returns the number
784             # - If the coefficient is complex, returns the reference
785             # Usage:
786             # float_var = jpoly_ref->coefficient(n); # Retrieve nth-degree coefficient
787             # complex_var = jpoly_ref->coefficient(n); # Retrieve nth-degree coefficient
788             #
789             # Error condition: If the n is higher than the degree or negative,
790             #
791             sub coefficient
792             {
793 675     675 1 902 my $self = shift(@_);
794 675         458 my $term_id = shift(@_);
795 675 50 33     1807 if ( ($term_id > $self->{degree}) || ($term_id < 0) )
796             {
797 0         0 my $msg = "Indicated degree is out of range [0, $self->{degree}]";
798 0         0 croak $msg;
799             }
800 675         7595 return ($self->{coeff}[$term_id]); # Degree desired is valid
801             }
802              
803             #------------------------------------------------------------------------------
804             # Method: variable(): Retrieve or set the character [string] that will be used
805             # to display the polynomial in Ysprint.
806             # Usage:
807             # - To set the string:
808             # $jpoly_ref->variable("Y"); #(Default is "X")
809             # $jpoly_ref->variable("squeal");
810             # - To retrieve the string:
811             # $var_str = $jpoly_ref->variable();
812             # In both cases, the method returns the string.
813             #
814             sub variable
815             {
816 100     100 1 82 my $self = shift(@_);
817 100         94 my $rval = ""; # Value to return
818 100 100       123 if (@_ == 0) # No parameter?
819 93         97 { $rval = $self->{variable}; } # User wants to know it
820             else
821 7         8 { $rval = shift(@_); # Lop it off parameter list and
822 7         7 $self->{variable} = $rval; # set it as the variable symbol
823             }
824 100         130 return ($rval);
825             }
826             #
827             #------------------------------------------------------------------------------
828             # Method: str2yapp(): Accepts a string and converts it to a polynomial object.
829             # The implicit "self" parameter is asssumed to be referenceing a degenerate
830             # polynomial and that's where the member items will go. If you already have an
831             # initialized polynomial object in there, it is *so* gone.
832             #
833             # Parameter:
834             # - A string of terms in the form ^.
835             # separated by white-spaces
836             # Returns:
837             # - The Yapp reference, if successful
838             # - undef if failed anywhere
839             #
840             # Usage: This is not intended to be called from outside. Rather, new() will
841             # call it in the following sequence:
842             # 1. new() creates and blesses the empty Yapp object referenced by $self
843             # 2. $self->str2yapp(The string>
844             #
845             # Note that the exponents do not need to be in any order; the exponent will
846             # decide the position within the array.
847             #
848             # Plan: Parsing presents a problem: I wish to impose no requirement that the
849             # terms and operators be blank-separated. And while the terms are surely
850             # separated by + and -, the split function will remove the separators so I
851             # won't know what was the sign of each term in the polynomial string. However,
852             # for my initial phase, I will require a white-space separator between terms.
853             # Also, each term mus match one of the patterns below, $real_term_pat or
854             # cplx_term_pat.
855             # For real coefficient it must look like:
856             # +/-^
857             # The sign will be optional for all terms. (But don't tell that to the users.)
858             #
859             # For complex coefficient, it must look
860             # +/-(+i)^
861             #
862             sub str2yapp
863             {
864 39     39 0 35 my $self = shift(@_); # Get myself out of the parameter list
865 39         26 my $poly_string = shift(@_); # and get the string into my locale
866              
867 39         31 my $tlc = 0; # Term loop counter
868              
869 39         32 my $rval = undef; # Off to a pessimistic start
870 39         20 my $const_term = 0;
871              
872 39         29 my $cur_term; # Current term from the input polynomial
873 39         24 my $cur_sign = 1; # Assume positive if sign is omitted
874 39         24 my $cur_coeff = 0.0; # Current coefficient
875 39         29 my $cur_varia = ""; # Current variable name; should change only
876             # once - the first time I see it.
877 39         26 my $cur_degree = -1; # For stepping into coeficient array
878 39         27 my $hi_deg = 0; # For determining the highest exponent used
879              
880 39         36 undef($self->{variable}); # Leave it for user to name the variable
881 39         31 $self->{degree} = 0; # and for input to dictate the degree
882              
883 39 50       60 printf("String polynomial is:\n<%s>\n", $poly_string)
884             if ($testmode);
885 39         126 my @pterms = split /\s+/, $poly_string; # Separate the terms
886             #
887 39         70 for ($tlc = 0; $tlc <= $#pterms; $tlc++)
888             {
889             # Plan: For each term I parse out, determine the coefficient (including the
890             # sign, of course), the variable name and the exponent. This determines the
891             # position and value in the @coeff array
892             #
893             # Afterthought: During debugging I discovererd that a + or - not
894             # connected to the following term would also pass all the pattern
895             # checks. Rather than complain or reject the string, I'd rather be nice
896             # about it.
897             # So here's the scheme:
898             # - If the +/- is the last term of the string, ignore it.
899             # - Not the last:
900             # o If the following term *is* properly signed, threat this solitary
901             # sign like a multiplier for the following term
902             # o If the term following term is unsigned, prepend this solitary sign
903             # to that term.
904             # Either way, skip reat of this round and handle that following term
905             #
906 57 100       136 if ($pterms[$tlc] =~ m/^[-\+]$/) # If it is a + or - alone
907             {
908 3 50       7 last if ($tlc == $#pterms); # It is already last term: Ignore it
909              
910             # Still here: It is not the last term. Peek ahead at next term
911             #
912 3 50       14 if ($pterms[$tlc+1] =~ m/^[-\+]/) # If next term is signed
913             { # Use this signs as multiplier
914 0 0       0 if ($pterms[$tlc] eq substr($pterms[$tlc+1], 0, 1)) # Signs alike?
915 0         0 { substr($pterms[$tlc+1], 0, 1) = '+'; } # Next is positive
916             else # Signs not alike: Whichever way,
917 0         0 { substr($pterms[$tlc+1], 0, 1) = '-'; } # Next is negative
918 0         0 next; # Now handle that next term
919             }
920             else # But if next term is unsigned;
921             { # use this to make it a signed term
922 3         7 $pterms[$tlc+1] = $pterms[$tlc] . $pterms[$tlc+1]; # Prepend this sign
923 3         5 next; # handle next term
924             }
925             }
926              
927             # following that afterthought...
928             # Before anything else, check the sign; this makes it possible to specify
929             # the negation of a complex coefficient eg. -(-6+4i)
930             #
931 54         49 $cur_term = $pterms[$tlc]; # Leave array enty intact; disect copy
932             #
933             # Next: Fetch the coefficient, of there is one.
934             #
935 54 100       558 if ($cur_term =~ /($coef_pat)/) # Is there a coefficient?
936             {
937 49         72 $cur_coeff = $1; # Isolate coefficient for more checking
938              
939             # First, see if there is a sign preceeding the coefficient.
940             #
941 49 100       158 if ($cur_coeff =~ /^($sign_pat)/) # If there is a sign
942             {
943 15 100       30 $cur_sign = ($1 eq "-") ? -1 : 1; # Apply sign to coefficient later
944 15         41 $cur_coeff =~ s/$sign_pat//; # but for now, lose the sign
945             }
946              
947             # Check if the coefficient is a complex number in ofr (a[+-]bi) where
948             # a and b are reals
949             #
950 49 100       133 if ($cur_coeff =~ /$cplx_pat/) # If it looks like a complex number
951             {
952 4         8 $cur_coeff = str2cplx($cur_coeff); # Convert it to complex object
953             }
954             else # Not complex: Must match a real or a sign
955             { # What if it was just a sign? (Which is gone)
956 45 50       71 $cur_coeff = 1.0 if ($cur_coeff eq ""); # Ths is implicitly 1
957             }
958 49         59 $cur_coeff *= $cur_sign; # Finally, apply the original sign that
959             # preceded the real or complex coefficient
960              
961             # Now that we have the coefficient for the current term, it's just in
962             # the way. Lose it.
963             #
964 49         326 $cur_term =~ s/$coef_pat//; # Leave the term w/o sign or coefficient.
965             }
966             else # No coefficient?
967 5         7 { $cur_coeff = 1.0; } # Implicitly, that is 1. (If it had
968             # been -1, it would be -X)
969             # Now that the term has no coefficient (either by input or because it
970             # has been stripped off), let's have a look at the variable component
971             #
972 54 100       167 if ($cur_term =~ /^($varia_pat)/) # If there is a variable to speak of
973             {
974 15         19 $cur_varia = $1; # then captue that as variable name
975 15         53 $cur_term =~ s/^($varia_pat)//; # and remove it from the term.
976             }
977             else
978             {
979 39         28 undef($cur_varia); # No variable => constant. Make sure no expo-
980 39         33 $cur_degree = 0; # nent. But we're getting ahead of ourselves..
981             }
982             #
983             # Next, see about the exponent part: ^integer, which is optional
984             # but if provided, there had better be a variable as well.
985             #
986 54 100       124 if ($cur_term =~ /($power_pat)/) # Is there an exponent
987             { # Some error checking, then work
988 5 50       8 if (defined($cur_varia)) # If there is a variable
989             {
990 5         11 $cur_term =~ s/^\^//; # Lose the exponent operator
991 5         13 ($cur_degree) = $cur_term =~ m/(\d+)/; # Capture the integer part
992             }
993             else
994 0         0 { die "Term[$tlc] <$pterms[$tlc]> has an exponent w/o a variable"; }
995             }
996             else # No exponent. Certainly OK.. But..
997             { # Is the degree of the term 1 or 0? Well, is there
998 49 100       62 $cur_degree = (defined($cur_varia)) ? 1 : 0; # a variable? Yes => 1
999             }
1000              
1001             # Finished parsing the term. Our state at this point: We have:
1002             # - The coefficient (be it real or # complex), inclduing its sign
1003             # - The variable name, which should not have changed.
1004             # - The degree of the curent term, which may match a term already there.
1005             #
1006              
1007             # Degree of polynomial is that of highest degree term, so:
1008             #
1009 54 100       106 $self->{degree} = $cur_degree if ($cur_degree > $self->{degree});
1010              
1011              
1012             # If variable name has changed in the string, complain but do it anyway.
1013             #
1014 54 50 100     132 if ( (defined($cur_varia))
      66        
1015             && (defined($self->{variable}))
1016             && ($self->{variable} ne $cur_varia))
1017             {
1018 0         0 my $mg;
1019             $mg = sprintf("Warning: Changing variable name from <%s> to <%s>\n",
1020 0         0 $self->{variable}, $cur_varia);
1021 0         0 warn($mg); # Complain but let it go
1022             }
1023             # Set the variable name for whole polynomial (if not already set in a
1024             # previous round of the loop)... provided that
1025             #
1026 54 100       75 $self->{variable} = $cur_varia
1027             if (defined($cur_varia)); # [Re]set the variable, even with grumble
1028              
1029             # If we already have a term at this degree, just add the new coefficient
1030             # to the existing one. Otherwise, we are setting the coefficient now.
1031             #
1032 54 100       67 if (defined($self->{coeff}[$cur_degree])) # Already have a term at
1033             { # degree, then
1034 39         93 $self->{coeff}[$cur_degree] += $cur_coeff; # just add this one
1035             }
1036             else # No term of this degree yet
1037             { # This is new here
1038 15         33 $self->{coeff}[$cur_degree] = $cur_coeff; # Set the coefficient now
1039             }
1040             } # End of for-loop to parse individual terms
1041              
1042             # Bug fix (Too lazy to locate screw-up): Somehow I can get this far
1043             # without a variable component. So just in case..
1044             #
1045 39 100       323 $self->{variable} = "X" if (!defined($self->{variable}));
1046              
1047 39         61 return $self;
1048             }
1049             #
1050             #------------------------------------------------------------------------------
1051             # Yapp_plus - Function to implement the += operator
1052             # Parameters:
1053             # - (Implicit) [Reference to] my target object
1054             # - The object to added to my target object. This may be:
1055             # o Another Yapp polynomial object [reference]
1056             # o A constant, either real or complex (a different, albeit smaller can
1057             # of worms)
1058             #
1059             sub Yapp_plus
1060             {
1061 23     23 0 21 my ($self, $added) = @_; # Get relevant parameters. Swap is irrelevant
1062              
1063             # Now, what is $added?
1064             #
1065 23 100 66     194 if (ref($added) eq $class_name) # If it is another polynomial
    50          
1066             {
1067 21         16 my $alc = 0; # Loop counter to step up added terms
1068 21         24 my $new_degree = $self->{degree}; # Track degree in case the added
1069             # polynomial has a higher degree
1070 21         13 my @builder = @{$self->{coeff}}; # Copy coefficients into temporary list
  21         33  
1071 21         43 for ($alc = 0; $alc <= $added->{degree}; $alc++) # For all terms of the
1072             { # added polynomial
1073 201 100       168 if (defined($builder[$alc])) # If target has term of this degree
1074 184         139 { $builder[$alc] += $added->{coeff}[$alc]; } # Just add these terms
1075 17         14 else { $builder[$alc] = $added->{coeff}[$alc]; } # Or copy this term
1076 201 100       333 $new_degree = $alc if ($alc > $self->{degree}); # Maintain degree
1077             }
1078             # Temporary areas have been evaluated. Now plug these into target Yapp
1079             #
1080 21         15 $self->{degree} = $new_degree; # Carry over the augmented (?) degree
1081 21         18 @{$self->{coeff}} = @builder; # and the augmented polynomial
  21         51  
1082             } # And I have my return value
1083             elsif ( ($added =~ m/^($coef_pat)$/)
1084             || (ref($added) eq $class_cplx) ) # Adding real or complex const
1085             {
1086             # As above: If the target term is defined, add the new term.
1087             # Otherwise, set the target term to the given value
1088             #
1089 2 50       101 if (defined($self->{coeff}[0])) {$self->{coeff}[0] += $added;}
  2         7  
1090 0         0 else {$self->{coeff}[0] = $added;}
1091             } # (Just needed to augment the constant term)
1092             else
1093 0         0 { die "Operator += requires a constant or a Yapp polynomial reference";}
1094              
1095             # Didn't die - I have a good value to return
1096             #
1097 23         174 return ($self); # (Ought to return something)
1098             }
1099              
1100             #------------------------------------------------------------------------------
1101             # Yapp_add() - Function (and overloaded + operator) to add two polynomials.
1102             # Parameters:
1103             # - (Implicit) The polynomial on the left side of the + (self)
1104             # - The polynomial to be added. This may be:
1105             # o (Implicit) [A reference to] another Yapp polynomial object
1106             # o A constant, either real or complex (See Yapp_plus)
1107             # - (Implicit) The "swapped" flag, largely irrelevant for the + operator
1108             # Returns:
1109             # A reference to a new Yapp polynomial
1110             #
1111             sub Yapp_add
1112             {
1113 9     9 0 112 my ($self, $adder, $swapped) = @_; #(swap is irrelelvant for add)
1114              
1115 9         13 my $ryapp = Yapp($self); # Clone the original
1116 9         18 $ryapp += $adder; # Let _plus() function handle details
1117 9         16 return $ryapp; # Assume $ryapp will be auto-destroyed?
1118             }
1119             #
1120             #------------------------------------------------------------------------------
1121             # Yapp_minus(): Function (and overloaded -= operator) to subtract the passed
1122             # polybnomial from the object polynomial in place. That is,
1123             # it modifies the $self object
1124             # - (Implicit) [Reference to] my target object
1125             # - The object to added to my target object. This may be:
1126             # o Another Yapp polynomial object [reference]
1127             # o A constant, either real or complex
1128             # Returns:
1129             # - The same reference. But the target Yapp ahs been "deminished"
1130             #
1131             sub Yapp_minus
1132             {
1133 9     9 0 17 my ($self, $subtractor) = @_[0 .. 1];
1134              
1135 9 100       14 if (ref($subtractor) eq $class_name) # Subtracting another polynimal
1136             { # just use the add method
1137 1         3 my $temp_subt = $subtractor->Yapp_negate(); # Quickie way out: Negate and
1138             # add, However I cant
1139 1         2 my $temp_self = Yapp($self); # use -= on $sef. Dunno why
1140 1         2 $temp_self += $temp_subt; # Add the negated Yapp
1141 1         2 @{$self->{coeff}} = @{$temp_self->{coeff}}; # Restore subtracted
  1         4  
  1         2  
1142             # coeffiecient array
1143             }
1144             else # Otherwise, just assume a constant
1145             { # be it real or complex.
1146             # If our polynomial has no constant term, this is it, after negation.
1147             #
1148             $self->{coeff}[0] = (defined($self->{coeff}[0]))
1149 8 50       22 ? $self->{coeff}[0] - $subtractor
1150             : - $subtractor ;
1151             }
1152 9         11 return $self;
1153             }
1154              
1155             #------------------------------------------------------------------------------
1156             # Yapp_sub(): Function (and overloaded - operator) to subtract one polynomia
1157             # from another bot not in place.
1158             # Parameters:
1159             # - (Implicit) The polynomial on the left side of the - (self)
1160             # - The polynomial to be added. This may be:
1161             # o (Implicit) [A reference to] another Yapp polynomial object
1162             # o A constant, either real or complex (See Yapp_plus)
1163             # - (Implicit) The "swapped" flag
1164             # Returns:
1165             # - A reference to a new Yapp polynomial
1166             #
1167             sub Yapp_sub
1168             {
1169 9     9 0 13 my ($self, $subtractor, $swapped) = @_;
1170              
1171 9         13 my $ryapp = Yapp($self); # Clone the original
1172 9         15 $ryapp -= $subtractor; # Let _plus() function handle details
1173 9 100       20 $ryapp = $ryapp->Yapp_negate() if ($swapped); # Negate of $self was on
1174             # right of the - sign
1175 9         19 return $ryapp; # Assume $ryapp will be auto-destroyed?
1176             }
1177             #
1178             #------------------------------------------------------------------------------
1179             # Yapp_mult(): Function to implement the overloaded *= operator: Perform in-
1180             # place multiplication of the target Yapp my a constant or another Yapp
1181             # Parameters:
1182             # - (Implicit) The target object ($self)
1183             # - The multiplier. This may be:
1184             # o Another Yapp object (Happiest with this parameter type)
1185             # o A real or complex constant
1186             # Returns:
1187             # The $self-same reference, but it has been "mutated" by the method.
1188             #
1189             sub Yapp_mult
1190             {
1191 193     193 0 156 my ($self, $multiplier) = @_;
1192 193         133 my $aux_yapp; # I may have to create another Yapp
1193             my $ylc; # Loop counter for stepping up coefficients
1194              
1195             # Now what data type is the multiplier?
1196             #
1197             #Xif ( (ref($multiplier) eq "$class_cplx") # Complex number constant
1198             #X || ( (ref(\$multiplier) eq "SCALAR") # or a scalar that happens
1199             #X && ($multiplier =~ /^($coef_pat)$/))) # to be a real constant
1200             #X Note: The above logic is good but for very low floating point numbers it
1201             #X was failing to match the corefficient pattern. Hence, I relaxed the
1202             #X matching requirement. -- Jacob
1203              
1204 193 100 100     722 if ( (ref($multiplier) eq "$class_cplx") # Complex number constant
    50          
1205             || (ref(\$multiplier) eq "SCALAR" ) ) # or a scalar (Assume numeric)
1206             { # Just distribute multiplier
1207 66         128 for ($ylc = 0; $ylc <= $self->{degree}; $ylc++)
1208             {
1209             $self->{coeff}[$ylc] *= $multiplier
1210 415 100       1017 if ($self->{coeff}[$ylc] != 0) ; # Don't bother multiplying 0
1211             }
1212             }
1213             elsif (ref($multiplier) eq $class_name) # Multiplying by a polynomial
1214             {
1215 127         113 my @builder = (); # Build result area from scratch
1216             my $new_degree = $self->{degree}
1217 127         124 + $multiplier->{degree}; # (Remember your 9th grade algebra?)
1218 127         186 for ($ylc = 0; $ylc <= $self->{degree}; $ylc++)
1219             { # Outer loop multiplies one target term
1220 415         2771 for (my $mlc = 0; $mlc <= $multiplier->{degree}; $mlc++)
1221             { # Inner loop multiplies by one
1222             # multiplier term
1223 1345         7876 my $term_degree = $ylc + $mlc; # Degree of this target term
1224 1345 100       1541 $builder[$term_degree] = 0.0 # Make sure there is a
1225             if (! defined($builder[$term_degree])); # value in here to start
1226              
1227             # Accumulate product term of that degree: Product of the terms whose
1228             # exponents add up to the [eventual] exponent of this term
1229             #
1230             $builder[$term_degree] += $self->{coeff}[$ylc]
1231 1345         2244 * $multiplier->{coeff}[$mlc] ;
1232              
1233             } # End loop multiplying one target term by term from multiplier
1234             } # End loop multiplying whole polynomials
1235             # Done multiplication: Now put it back in place
1236             #
1237 127         277 $self->{degree} = $new_degree; # Copy back degree of multiplied target Yapp
1238 127         102 @{$self->{coeff}} = @builder; # and copy back array where we carried
  127         313  
1239             # out the multiplication.
1240             } # All done multiplying Yapps; product is in place
1241             else # No more permitted possibilities
1242 0         0 { die "Operator *= requires a constant or a Yapp polynomial reference";}
1243              
1244             # Afterthought: I have found that when I multiply two poly's with conjugate
1245             # complex constant terms, the result will include coefficients like like
1246             # (30.00+0.00i); which is a real, of course, but doesn't look that way.
1247             # Here's the fix:
1248             #
1249 193         185 realify(\@{$self->{coeff}});
  193         247  
1250 193         390 return $self;
1251             }
1252             #
1253             #------------------------------------------------------------------------------
1254             # Yapp_times(): Method to implement the overloaded '*' operator
1255             # Parameters:
1256             # - (Implicit) The operand (usually the left) to the multiplicaton
1257             # - [Reference] to the multiplier, which may be:
1258             # o Another Yapp object (Happiest with this parameter type)
1259             # o A real or complex constant
1260             # - Swapped-operands flag, largely irrelevant for multiplication
1261             # Returns:
1262             # - [Reference to] a new Yapp polynomial that is the product of the first
1263             # two parameters
1264             #
1265             sub Yapp_times
1266             {
1267 84     84 0 91 my ($self, $multiplier, $swapped) = @_;
1268 84         44 my $ryapp; # The object to be returned
1269              
1270 84         94 $ryapp = Yapp($self); # Make a copy
1271 84         110 $ryapp *= $multiplier; # Carry out operation
1272 84         134 return $ryapp; #(Wasnt't that simple?)
1273             }
1274             #
1275             #------------------------------------------------------------------------------
1276             # Yapp_power(): Method to implement exponentiation of polynomial by an
1277             # integer power.
1278             # Parameters:
1279             # - (Implicit) The operand, a ref to the polynomial being multiplied by itself
1280             # - The power, which must be an integer
1281             # - Swapped-operands flag: grounds for immediate death
1282             # Returns:
1283             # - A [ref to a] Yapp polynomial that is the original raised to the
1284             # indicated powert
1285             #
1286             sub Yapp_power
1287             {
1288 9     9 0 996 my ($self, $power, $swapped) = @_;
1289              
1290             # Some validations:
1291             #
1292 9 100       26 die "You cannot raise a number to a polynomial power"
1293             if ($swapped);
1294 8 100       20 die "Power must be an integer"
1295             if ($power != int($power));
1296              
1297             # And now that that's been squared away: Get to work
1298             #
1299 7         9 my $ryapp = Yapp(1); # Start with a unit polynomial
1300 7         14 while ($power-- > 0) # (Decrement after testing)
1301             {
1302 15         19 $ryapp *= $self; # Carry out the self-multiplication
1303             }
1304 7         9 return $ryapp;
1305             }
1306             #------------------------------------------------------------------------------
1307             # Yapp_divide(): Method to implement the overloaded '/=' operator. Unlike
1308             # the multiply operator, I am allowing only for deviding by a
1309             # constant value. Scheme is simple: Take reciprocal of divisor
1310             # and multiply the Yapp by that.
1311             # Parameters:
1312             # - (Implicit) the Yapp object itself
1313             # - The divisor, which may be real or complex
1314             # Returns:
1315             # - The original Yapp reference
1316             #
1317             sub Yapp_divide
1318             {
1319 22     22 0 22 my ($self, $divisor) = @_;
1320 22         24 my $recipro = (1.0 / $divisor); # Take reciprocal
1321              
1322             #$self *= $recipro; # (This causes "No Method" error, so..)
1323 22         79 $self = $self * $recipro; # Carry out that multiplication
1324 22         54 return $self;
1325             }
1326             #
1327             #------------------------------------------------------------------------------
1328             # Yapp_dividedby(): Method to implement the overloaded '/' operator.
1329             # Scheme: Identical to the Yapp_times() method.
1330             #
1331             # Parameters:
1332             # - (Implicit) the Yapp object itself
1333             # - The divisor, which may be real or complex
1334             # Returns:
1335             # - A reference to a new Yapp object wherin the division has been carried # out
1336             #
1337             sub Yapp_dividedby
1338             {
1339 9     9 0 69 my ($self, $multiplier, $swapped) = @_; # $swapped is irrelevant
1340 9         9 my $ryapp; # The object to be returned
1341              
1342 9         11 $ryapp = Yapp($self); # Make a copy
1343 9         15 $ryapp /= $multiplier; # Carry out operation
1344 9         25 return $ryapp; #(Wasnt't that simple?)
1345             }
1346             #
1347             #------------------------------------------------------------------------------
1348             # Negation and conjagation of the coefficients of the polynomial:
1349             #
1350             # Yapp_negate(): Method to handle the overloaded ! operator, to give a Yapp
1351             # whose coefficientes are the negatives of the given Yapp.
1352             # Parameter: (Implicit)
1353             # - A Yapp reference
1354             # Returns:
1355             # - If called in scalar context: A new Yapp with the negated coefficcients
1356             # - If called in void context, returns nothing but the coefficients of the
1357             # given Yapp have been negated.
1358             # Well, that *was* the idea; unfortunately, I cannot seem to get in-place
1359             # negation to work.
1360             sub Yapp_negate
1361             {
1362 9     9 0 15 my $self = shift(@_);
1363 9         6 my $ryapp; # Return reference, if non-void context
1364             # my $ryapp = Yapp($self); # Make a copy
1365              
1366 9 50       17 if ($testmode)
1367             {
1368 0 0       0 if (defined(wantarray))
1369             {
1370 0 0       0 if (wantarray) {print "List context\n";}
  0         0  
1371 0         0 else {print "Scalar context\n";}
1372             }
1373 0         0 else {print "Void context\n";}
1374             }
1375             # Depending on calling context, $ryapp is either a new Yapp [reference] or
1376             # a new reference to the same parameter
1377             #
1378             #Xmy $ryapp = (defined(wantarray)) ? Yapp($self) : $self;
1379             #$ryapp *= -1; # Multiply all coefficients by -1
1380             #X$ryapp = -1 * $ryapp; # Multiply all coefficients by -1
1381 9 50       13 if (defined(wantarray)) # List context would be silly so ..
1382             { # treat list context like scalar context
1383 9         13 $ryapp = -1 * $self; # Create a new Yapp, negative of $self
1384 9         13 return $ryapp; # Send that back to caller
1385             }
1386             else
1387             {
1388 0         0 $self = -1 * $self; # Negate the given Yapp
1389 0         0 return $self; # Return (a wasted value)
1390             }
1391             }
1392             ##------------------------------------------------------------------------------
1393             # Yapp_conj(): Method to handle the ~ operator; Conjugate the complex
1394             # coefficents of the given yapp
1395             # Parameter: (Implicit)
1396             # - A Yapp reference
1397             # Returns:
1398             # - In scalar context: A new one with the conjugate coefficcients
1399             # - In void context: Nothing but the original Yapp has been conjugated
1400             # That is, if I could get it to work. In-place
1401             # conjugation is not working, however.
1402             sub Yapp_conj
1403             {
1404 3     3 0 7 my $self = shift(@_);
1405             #my $ryapp = Yapp($self); # Make a copy
1406              
1407             # Depending on calling context, $ryapp is either a new Yapp [reference] or
1408             # a new reference to the same parameter
1409             #
1410 3 50       12 my $ryapp = (defined(wantarray)) ? Yapp($self) : $self;
1411 3         6 my $clc; # Loop counter
1412              
1413 3         15 for ($clc = 0; $clc <= $ryapp->{degree}; $clc++)
1414             {
1415 20 100       381 next unless (ref($ryapp->{coeff}[$clc]) eq $class_cplx);# Skip real coeffs
1416 11         27 $ryapp->{coeff}[$clc] = ~$ryapp->{coeff}[$clc];
1417             }
1418 3 50       71 return $ryapp if (defined(wantarray)); # Return a value if scalar context
1419             }
1420              
1421             #
1422             #-------------------------------------------------------------------------------
1423             # Evaluation: Plug a value into the polynimal and calculate the result
1424             # This is a simple application of Horner's "Synthetic Division"
1425             #
1426             # Parameters:
1427             # - (Implicit) Reference to the Yapp polynomial
1428             # - A real number or reference to complex number
1429             # Returns:
1430             # - The result of the plugging it in.
1431             # - Optionally, if caller specified ($value, $quotient) = $some_yapp(number)
1432             # also returns [a refernce to] the quotient polynomial
1433             #
1434             sub Yapp_eval
1435             {
1436 129     129 0 3469 my ($self, $in_val) = @_; # (Assuming correct parameters or Phzzzt!)
1437 129         86 my $elc; # Exponent loop counter for countdown
1438 129         88 my $addval = 0.0; # Intermediate value in synthetic division step
1439 129         89 my $sumval = 0.0; # A term in the "quotient" line
1440 129         138 my @quotient = ();# Array representing the quotient sans remainder
1441              
1442 129         271 for ($elc = $self->{degree}; $elc >= 0; $elc--)
1443             {
1444 871         12556 $quotient[$elc] = $self->{coeff}[$elc] + $addval; # A term in quotient
1445 871         21577 $addval = $quotient[$elc] * $in_val; # Add this in next round
1446             }
1447             # Last term of the quotient is the evaluation of the polynomial at that
1448             # input value. The rest is the quotient polynomial, albeit looking like a
1449             # degree too high. Use shift to solve both issues:
1450             #
1451 129         2227 my $result = shift(@quotient); # Get value and reduce degree
1452 129         171 realify($result); # In case of rounding error, if result
1453             # is near zero, make it zero
1454 129 100       361 if (wantarray()) # Did caller specify two return values?
1455             {
1456 10         15 my $y_quotient = Yapp(\@quotient); # Turn the quotient array into poly
1457 10         31 return($result, $y_quotient); # and return both to caller
1458             }
1459 119         338 else {return $result;} # Otherwise, we happy with result alone
1460              
1461             # for ($elc = $self->{degree}; $elc >= 0; $elc--)
1462             # {
1463             # $sumval = $self->{coeff}[$elc] + $addval;
1464             # $addval = $in_val * $sumval; # Add this to the next coefficient
1465             # }
1466             # return $sumval; #(OK last addition was wasted)
1467             }
1468             #
1469             #-------------------------------------------------------------------------------
1470             # Yapp_reduce: Reduce the roots of a polynomial by the indicated amount.
1471             # This is part of Horner's method for approximating the roots but will not
1472             # be used here for that purpose.
1473             #
1474             # Parameters:
1475             # - (Implicit) Reference to the Yapp polynomial
1476             # - Value by which to reduce the roots. May be real or [reference to]
1477             # complex
1478             # Reurns:
1479             # - A [ref to a] new Yapp object, whose roots are the same as those of the
1480             # original but reduced by the indicated amount,
1481             #
1482             # Scheme: This will use an array of successive arrays representing
1483             # intermdiate quitient polynomials, as well as an array or remainders to
1484             # build the final result
1485             #
1486             sub Yapp_reduce
1487             {
1488 8     8 0 14 my ($self, $reduce_by) = @_; # Get my parameters
1489 8         8 my @remainders;
1490             my @degrees; # Make it easier to track the degree of each quotient
1491 0         0 my ($qlc, $tlc); # Loop counters for outer and inner loops
1492 8         13 my $q_count = $self->{degree}; # How many quotients I will calculate
1493 8         8 my $lead_coeff = $self->{coeff}[$q_count]; # Will divide by this and
1494             # multiply it back later
1495 8         7 my @rlist = (); # Result list, the raw polynomial we build up
1496 8         8 my $rcount = 0; # Counter to make it easier to keep track to that build
1497 8         6 my @coeffs = @{$self->{coeff}}; # Will start with synthetic
  8         16  
1498              
1499             # Divide whole new "poly" by leading coefficient that that new leading
1500             # coefficient is 1.
1501             #
1502 8         18 for (my $clc=0; $clc <= $q_count; $clc++) {$coeffs[$clc] /= $lead_coeff;}
  36         50  
1503              
1504 8         18 for ($qlc = $self->{degree}; $qlc > 0; $qlc--)
1505             {
1506 28         15 my $addval = 0.0; # Set up for synthetic division
1507 28         22 my $q_deg = $qlc - 1; # Degree of new quotient
1508 28         18 my @quotient; # Quotient of a synthetic division operation
1509              
1510             # Note: This inner loop uses the same synthetic division used in function
1511             # Yapp_eval(). But the moving expression $quotient[$tlc] stands in place
1512             # of $sumval
1513             #
1514 28         37 for ($tlc = $qlc; $tlc >= 0; $tlc--)
1515             {
1516             # First add $addval to a term to determine next coefficient.
1517             # Then determine next $addval
1518             #
1519 92         81 $quotient[$tlc] = $coeffs[$tlc] + $addval;
1520 92         113 $addval = $reduce_by * $quotient[$tlc];
1521             }
1522             # When I come out of above inner loop, two items of interest:
1523             # - The last value in the quotient array, which is the remainder of the
1524             # synthetic division operation, is the next coefficient in the final
1525             # polynomial.
1526             # - The quotient array represents the next polynomial to be "divided" by
1527             # the divisor, once I get rid of the remainder term.
1528             #
1529 28         30 $rlist[$rcount++] = shift(@quotient); # Pull remainder into the polynomial
1530             # I am building up, an drop it out
1531             # of the next polynomial.
1532 28         35 @coeffs = @quotient; # Will next divide *this* polynomial
1533 28         47 @quotient = (); # Neatness: Clear for next round
1534             }
1535 8         8 $rlist[$q_count] = 1; # The inevitable final quotient
1536              
1537             # (Whew) I now have our list or remainders from the above sequence of
1538             # synthetic divisions. Create a polynomial out of that list
1539             #
1540 8         12 my $ryapp = Yapp(\@rlist); # This creates the polynomial
1541 8         12 $ryapp *= $lead_coeff; # Remember that? Multiply it back out
1542 8         14 return $ryapp; # This has the roots reduced.
1543             }
1544             #
1545             #-------------------------------------------------------------------------------
1546             # Yapp_negate_roots(): Returns a polynomial whose roots are the nagatives of the
1547             # roots of the given polynomial. Easy trick: Negate the
1548             # coefficient of every odd-exponent term.
1549             #
1550             # Parameter:
1551             # - (Implicit) The Yapp reference
1552             # Returns:
1553             # - A new Yapp with negated roots
1554             #-------------------------------------------------------------------------------
1555             #
1556             sub Yapp_negate_roots
1557             {
1558 1     1 0 4 my $self = shift(@_); # The only parameter I care about
1559 1         2 my $ryapp = Yapp($self); # Copy to another polynomial
1560 1         2 my $coefref = $ryapp->{coeff};
1561 1         3 for (my $clc = 1; $clc <=$#{$coefref}; $clc+=2) # Odd indexed terms only
  4         6  
1562             {
1563 3         6 $coefref->[$clc] = - ($coefref->[$clc]) #(If it was 0, no harm done)
1564             }
1565 1         3 return $ryapp
1566             }
1567             #
1568             #-------------------------------------------------------------------------------
1569             # Derivative and antiderivative i.e. indefinite integral.
1570             # In both cases, we will store the transformed polybnomial back into the
1571             # Yapp structure because they will very likely be resused for other stuff.
1572             # For example, Newton's method for finding roots uses the first derivative,
1573             # and laGuerre's method uses the second derivative.
1574             #
1575             # Yapp_derivative()
1576             # Parameters:
1577             # - (Implicit) Reference to the Yapp to differentiated
1578             # - Order of derivative. If omitted, assume caller meant first derivative
1579             # Returns:
1580             # - A [reference to a] Yapp polynomial that is the indicated derivative of
1581             # the given Yapp.
1582             # - Side effect: A new array of Yapp references to all the derivatives is
1583             # now hanging off the given Yapp structure. @{derivatives}
1584             #-------------------------------------------------------------------------------
1585             #
1586             sub Yapp_derivative
1587             {
1588 22     22 0 41 my $self = shift(@_);
1589 22 100       35 my $order = defined($_[0]) ? shift(@_) : 1; # Get nth derivative or assume 1
1590 22         17 my $start_n; # Highest order derivative we already have
1591             my $coefref; # Use as array reference as I step into each polynomial
1592 0         0 my $dlc; # Loop counter for derivatives
1593              
1594 22 100       45 if (defined($self->{derivative}[$order])) # If I have already derived this
1595 8         9 { return($self->{derivative}[$order]); } # just give it again, no work
1596              
1597             # Still here? Two possibilities:
1598             # 1. Previous derivatives have been derived, just not this order
1599             # 2. This is the first time a derivative it being requested, so I have to
1600             # create the derivatives array from scratch
1601             #
1602 14 100       23 if (defined($self->{derivative}[1]))
1603             { # If we already have derivatives saved
1604 2         2 $start_n = $#{$self->{derivative}}; # Start past highest we already have
  2         4  
1605             }
1606             else
1607             { # Starting derivatives from scratch
1608 12         14 $self->{derivative}[0] = Yapp(); # Empty placeholder for 0th deriv
1609 12         13 $start_n = 0; # 'Cause we got none yet
1610             }
1611              
1612 14         26 for ($dlc = $start_n; $dlc < $order; $dlc++)
1613             {
1614 16 50       24 last if ($dlc >= $self->{degree}); # No more derivatives than degree
1615 16         14 my @new_list;
1616 16 100       21 if ($dlc == 0) {$coefref = $self->{coeff}; } # -> my coeffiecients
  12         14  
1617             else
1618             {
1619 4         5 my $cur_deriv = $self->{derivative}[$dlc]; # -> (lc)th derivative
1620 4         5 $coefref = \@{$cur_deriv->{coeff}}; # and to *its* coefficients
  4         5  
1621             }
1622 16         17 for (my $tlc = 1; $tlc <= $#{$coefref}; $tlc++) # Skip 0th term
  108         130  
1623             {
1624 92         113 $new_list[$tlc-1] = $tlc * $coefref->[$tlc]; # Coeff * its exponent
1625             }
1626 16         22 $self->{derivative}[$dlc+1] = Yapp(\@new_list); # New Yapp in deriv slot
1627             }
1628 14         25 return ($self->{derivative}[$dlc]);
1629             }
1630             #
1631             #-------------------------------------------------------------------------------
1632             # Yapp_integral(): Method to calculate the first integral of the given Yapp
1633             # polynomial.
1634             # Let's clarify: This serves as two functions:
1635             # - The indefinite integral AKA the antiderivative, returning a Yapp polynomial
1636             # for the given polynomial. Will not include the arbitrary constant you were
1637             # taught to always include with your antiderivative.
1638             # - A definite integral: That is, supplying two numbers that represent the
1639             # endpoints of an interval i.e. the limits of the integral.
1640             # Returns: Well, that depends:
1641             # - For indefinte integral, returns a reference to the antiderivative polynomial
1642             # (which as been stored in $self anyway.)
1643             # - For definite integral: Returns the value of that integral between the
1644             # limits.
1645             # In both cases, the antiderivative polynomial is cached along with the given
1646             # Yapp so it may be re-used for various limits.
1647             #
1648             # Note: Unlike with derivatives, I see no need to code for additional order
1649             # of integral. (I'm not anticipating a Laurent series)
1650             #
1651             sub Yapp_integral
1652             {
1653 4 50 66 4 0 22 die ("Must supply either no integral limits or exactly two points")
1654             unless ( ($#_ == 2) || ($#_ == 0)); # Bail if wrong parameters given
1655 4         5 my $self = shift(@_); # If $#_ was 2, it is now 1
1656 4         7 my ($l_limit, $u_limit) = (undef, undef); # Don't know yet if user called
1657             # me for a definite integral.
1658 4         3 my $ryapp; # My return reference for indefinite
1659 4         3 my $rval= 0.0; # My return value for definite
1660              
1661              
1662 4 100       10 ($l_limit, $u_limit) = @_ if ($#_ == 1); # Now is when I find out about
1663             # those endpoint parameters
1664              
1665             # Note: If I never calculated the integral for this polynomial (or if it
1666             # has "refreshed"), the {integral} component may be an array or undefined.
1667             # But it *won't" be Yapp.
1668             #
1669 4 100       14 if (ref($self->{integral}) ne $class_name) # Ever calculated the indefinite
1670             # integral for this polynomial?
1671             { # No: Do it now.
1672 3         5 my @new_list = (0); # 0 in place of arbitrary constant
1673 3         10 for (my $ilc = 0; $ilc <= $self->{degree}; $ilc++)
1674             {
1675 30         846 push (@new_list, ($self->{coeff}[$ilc] / ($ilc + 1)));
1676             }
1677 3         71 $ryapp = Yapp(\@new_list); # Create the polynomial
1678 3         17 $self->{integral} = $ryapp; # and cache that for re-use
1679             }
1680 1         2 else {$ryapp = $self->{integral}; } # Already computed: Just re-use it
1681              
1682             # Part 2: What does the user want? Definite or indefinite integral?
1683             #
1684 4 100       10 return ($ryapp) unless (defined($l_limit)); # No limits? Wants indefinite
1685              
1686             # Still here: Wants the value of that integral
1687             #
1688 3         8 my $u_val = $ryapp->Yapp_eval($u_limit); # Value of antideriv at upper limit
1689 3         6 my $l_val = $ryapp->Yapp_eval($l_limit); # Value of antideriv at lower limit
1690 3         7 $rval = $u_val - $l_val; # And that's the integral value
1691 3         83 return $rval;
1692             }
1693             #
1694             #-------------------------------------------------------------------------------
1695             # Solving for the zeros of a polynomial:
1696             # Method Yapp_solve() is the only method that should be called byt he users.
1697             # This, in turn calls appropriate other methods. For example, for a quadratic
1698             # polynomial, Yapp_dolve() will call Yapp_solve_quad(), which will simply use
1699             # the quadratic formula. For a cubic, it uses the algorithm of Gerolamo Cardano
1700             # and for the quartic (4th degree) that of Lodovico Ferrari. After that, we
1701             # resort to bisection to locate a solution, then go to Newton's method. (A
1702             # future release may switch to laGuerre's method.)
1703             #
1704             # For degree >= 5 we are guranteed at least one real root only for odd-degree
1705             # polynomials so bisection is a sure thing. However, for an even-degree poly-
1706             # nomial, there is no such guarantee. I have tried to discuss and analyze
1707             # methods that might be analogous to bisection for complex numbers but to no
1708             # avail. I have bitten the bullet and coded Laguerre's method as a starting
1709             # point for even-degree polynomials of degree >= 6
1710             #
1711             # Short cuts:
1712             # - When a complex root is found, we will also evaluate the quotient for the
1713             # conjugate of that root.
1714             # - In all cases, when any root is found, we evaluate quotient for the same root
1715             # in case it's a multiple root.
1716             # - Whenever a root (or set of roots, as bove) is found, we make a recursive
1717             # call to Yapp_solve() to get the roots of the quotient polynomial. (I have
1718             # read that the technique is called deflation.) However, after getting the
1719             # roots of the deflated polynomial, I still apply Newton's method on the roots
1720             # with respect to the polynmomial at hand. This is to compensate for rounding
1721             # errors that have crept in while solving the deflated polynomial
1722             #
1723             # Parameter:
1724             # - (Implicit) The Yapp polynomial to solved
1725             # Returns:
1726             # - An array of solutions, which may include repeated values owing to repeated
1727             # roots. (Not a reference but the array otself.)
1728             #-------------------------------------------------------------------------------
1729             #
1730             sub Yapp_solve
1731             {
1732 25     25 0 44 my $self = shift(@_);
1733             my $solu_end = $self->{degree} # The degree the polynomial came it with
1734 25         26 - 1; # Top index of solutions array
1735 25         26 my @solutions = (); # Array to be returned starts empty
1736 25         18 my $solu_begin = 0; # Where to start pushing solutions in
1737             # the @soultions array
1738 25 50       34 if ($solu_end == 0)
1739             {
1740 0         0 carp("0-degree polynomial <$self->Ysprint()> has no solution");
1741 0         0 return @solutions; # Don't bother with it anymore
1742             }
1743              
1744             # Quick check: If the constant term of the polynomial is 0, then we know
1745             # that 0 is a solution. In that case, shift the polynomial down a degree
1746             # and test again. This is the point of the following loop.. Except that I
1747             # am assuming that constant term is zero if it is sufficiently small; that
1748             # it is the result of a rounding error in a previous computation is this
1749             # highly recursive function.
1750             #
1751             #
1752 25         45 while (abs($self->{coeff}[0]) <= $margin)
1753             {
1754 0         0 $solutions[$solu_begin++] = 0.0; # Zero *is* a solution to *this* polynomial
1755 0         0 shift(@{$self->{coeff}}); # Knock it down a degree for next round
  0         0  
1756 0         0 --($self->{degree}); # Register the lowered degree
1757             }
1758 25         17 my $degree = $self->{degree}; #(Just neater for successive if/elsif)
1759              
1760             # After this, we go structured; no returning from within "if" blocks
1761             #
1762 25 50       54 if ($degree == 1) # b + ax = 0
    100          
    100          
    100          
    50          
    100          
1763             {
1764 0         0 $solutions[$solu_begin] = - $self->{coeff}[0]/$self->{coeff}[1] ; # -b/a
1765             }
1766             elsif ($degree == 2)
1767             {
1768 16         21 @solutions[$solu_begin .. $solu_end] = $self->Yapp_solve_quad();
1769             }
1770             elsif ($degree == 3)
1771             {
1772 4         8 @solutions[$solu_begin .. $solu_end] = $self->Yapp_solve_cubic();
1773             }
1774             elsif ($degree == 4)
1775             {
1776 3         6 @solutions[$solu_begin .. $solu_end] = $self->Yapp_solve_quartic();
1777             }
1778              
1779             # We've just run out of fomulaic methods and we must resort to pure numerical
1780             # methods. Oh Joy!
1781             #
1782             elsif (! $self->all_real)
1783             { # Complex Coefficients: Some theorems go out the window
1784 0         0 @solutions[$solu_begin .. $solu_end] = $self->Yapp_solve_complex();
1785             }
1786             elsif (($degree % 2) == 1)
1787             {
1788 1         2 @solutions[$solu_begin .. $solu_end] = $self->Yapp_solve_odd();
1789             }
1790             else
1791             {
1792 1         3 @solutions[$solu_begin .. $solu_end] = $self->Yapp_solve_even();
1793             }
1794             # Apply this filter to the @solutions array: If we have reason to suspect
1795             # that a complex number looks complex only due to rounding errors i.e. the
1796             # imaginary part is so small that it looks ignorable, then do ignore it.
1797             # Similarly, we can nullify (but not ignore) a trivial real component of a
1798             # complex number.
1799             #
1800 25         44 realify(\@solutions);
1801              
1802 25         72 return @solutions;
1803             }
1804             #
1805             #-------------------------------------------------------------------------------
1806             # Yapp_newton(): Internal method. If we already have a crude (in the eye of
1807             # the beholder) appoximation of a root, let's get much closer
1808             # by using Newton's method. This is especially a problem
1809             # with the Ferrari and Cardano's algorithms when applied as
1810             # algorithms and rounding error are inevitable.
1811             # Parameters:
1812             # - (Implicit) The Yapp object
1813             # - The initial value
1814             # Returns:
1815             # - A refined value, with $margin of being correct.
1816             #
1817             sub Yapp_newton
1818             {
1819 9     9 0 10 my ($self, $xn) = @_; # Get object an our starting X-value
1820              
1821 9         42 my $func_name = (caller(0))[3]; #(For debugging)
1822 9         17 my $yprime = $self->Yapp_derivative(1);
1823 9         12 my $valxn = $self->Yapp_eval($xn); # Evaluation at starting point
1824 9         15 while (abs($valxn) >= $margin) # Continue this loop until I'm REALLY close
1825             { # to a solution.
1826 10         65 my $xc = $xn; # Hold a copy of current tested value
1827 10         16 my $yp = $yprime->Yapp_eval($xn); # Get derivative at current $xn
1828 10 50       11 last if (abs($yp) == 0); # Beware of zero-divide!
1829 10         72 my $correction = $valxn / $yp; # Current value / current derivative
1830 10         448 $xn -= $correction; # X[n+1] = X[n] - Y(X[n])/Y'(X[n])
1831 10 100       231 last if (abs($xn - $xc) < $margin); # Another exit condition: Almost no diff
1832             # between this x and previous x
1833 1         45 $valxn = $self->Yapp_eval($xn); # Ready for test or use atop next round
1834 1 50       6 printf("%s: xn = <%s>; valxn = <%s>\n",
1835             $func_name, Csprint($xn), Csprint($valxn))
1836             if ($testmode);
1837             }
1838 9         439 return($xn); # Close enough to send back to caller
1839             }
1840             #
1841             #-------------------------------------------------------------------------------
1842             # Yapp_laguerre(): Internal method. If we start with just about any starting
1843             # point - it doesen't even have to be all that close we can
1844             # get much closer by using Laguerre's method.
1845             # Source of the mathetics I am using:
1846             # Numerical Recipes in C; The Art of Scientific Computing
1847             # Second Edition (C) 1988, 1992`
1848             # Authors: William H. Press
1849             # Saul A. Teukolsky
1850             # William T. Vetterling
1851             # Brian P. Flannery
1852             # Pages: 372-374
1853             # Note: I made the chagrined discovery that rounding errors cause the test
1854             # values to fluctuate a few decimal places out of my desired margin.
1855             # Experiment: Use Laguerre's method to within .001, then switch to Newton's
1856             # method.
1857             # ...
1858             # Parameters:
1859             # - (Implicit) The Yapp object
1860             # - The initial value
1861             # Returns:
1862             # - A refined value, with $margin of being correct.
1863             #
1864             sub Yapp_laguerre
1865             {
1866 1     1 0 1 my ($self, $xn) = @_; # Get object an our starting X-value
1867 1         6 my $func_name = (caller(0))[3]; #(For debugging)
1868              
1869 1         1 my $degree = $self->{degree}; # This will figure in recalculations
1870 1         3 my $yprime = $self->Yapp_derivative(1); # First derivative of self
1871 1         2 my $yprime2 = $self->Yapp_derivative(2); # And second derivative
1872 1         2 my $valxn = $self->Yapp_eval($xn); # Evaluation at starting point
1873 1         2 my $crude_margin = .001; # Quite close - might as well take
1874             # advantage of Laguerre's faster
1875             # convergence.
1876 1         2 while (abs($valxn) >= $crude_margin) # Bet this looks familiar so far :)
1877             { # Well, hang on to your seats!
1878             # Correction factor is:
1879             # Degree / (G +/- sqrt((Degree - 1) * (Degree * H - G**2)))
1880             # See evaluation of $gxn for G and $hxn for H below
1881             # Ugly, ain't it? ;-)
1882             #
1883 4         27 my $prev_xn = $xn; # Keep current estimate for comparison
1884 4         7 my $gxn = $yprime->Yapp_eval($xn) / $valxn;
1885 4         167 my $hxn = $gxn**2 - ($yprime2->Yapp_eval($xn) / $valxn);
1886 4         313 my $under_rad = ($degree -1)
1887             * ($degree * $hxn - $gxn ** 2); # May be negative or complex
1888 4         906 my @rad_roots = root($under_rad, 2); # Take square roots of that
1889 4         558 my @denoms = ($gxn - $rad_roots[0], # Choose a denominator for fraction
1890             $gxn + $rad_roots[0]); # to be used in a correction.
1891 4         256 my @norms = (abs($denoms[0]), abs($denoms[1])); # Which denominator has the
1892             # bigger absolute value?
1893 4 50       77 my $denom = ($norms[0] > $norms[1]) ? $denoms[0] : $denoms[1]; # Pick that
1894 4         7 my $correction = $degree / $denom; # Correct previous estimate by this
1895 4         140 $xn -= $correction; # "amount" by subtracting it
1896             #? last if (abs($prev_xn - $xn) < $margin); # Quit if correction was teeny
1897 4         65 $valxn = $self->Yapp_eval($xn); # Otherwise, re-evaluate at new
1898             # estimate and plug in next round
1899 4 50       33 printf("%s: xn = <%s>; valxn = <%s>\n",
1900             $func_name, Csprint($xn), Csprint($valxn))
1901             if ($testmode);
1902             }
1903             # Came out of loop: I guess we're close enough to start with Newton?
1904             #
1905 1         8 $xn = realify($xn); # NUllify tiny Im component
1906 1         2 $xn = $self->Yapp_newton($xn); # before feeding it to Newton
1907 1         2 realify($xn); # and correct again afterward
1908 1         2 return $xn;
1909             }
1910             #
1911             #-------------------------------------------------------------------------------
1912             # Yapp_solve_quad(): Solve a quadratic equation
1913             #
1914             sub Yapp_solve_quad
1915             {
1916 16     16 0 14 my $self = shift(@_);
1917 16         9 my ($real_part, $discr, $rad_part);
1918 16         13 my @solutions = (); # Look familiar? :-)
1919 16         30 my $i = cplx(0, 1); # The familiar imaginary unit, "i"
1920 16         534 my ($c, $b, $a) = @{$self->{coeff}}; # Copy the array, for neatness
  16         26  
1921              
1922 16         20 $real_part = -$b / (2 * $a); # This part is always guaranteed real
1923             # (assuming real coefficients, of course)
1924 16         22 $discr = $b ** 2 - (4 * $a * $c); # Discriminant: b^2 - 4ac
1925 16 50       31 if ($discr == 0)
    100          
1926             {
1927 0         0 @solutions = ($real_part, $real_part); # Repeated roots. We're done!
1928             }
1929             elsif ($discr > 0)
1930             {
1931 5         12 $rad_part = sqrt($discr)/ (2 * $a); # The part under the radical
1932 5         33 @solutions = ($real_part - $rad_part, $real_part + $rad_part);
1933             }
1934             else # Negative discriminant => complex roots
1935             {
1936 11         23 my @i_roots = root($discr, 2); # Get both imaginary roots
1937              
1938             # Because the root() function is too *&&^% comfortable in polar form, we
1939             # get a miniscule (but still there) real part. Get rid of it!
1940             #
1941 11         909 @i_roots = (cplx(0.0, $i_roots[0]->Im), cplx(0.0, $i_roots[1]->Im));
1942 11 50       804 printf("Roots of discriminant <%f> are: <@i_roots>\n", $discr)
1943             if ($testmode);
1944 11         27 @i_roots = (($i_roots[0]/(2 * $a)),
1945             ($i_roots[1]/(2 * $a)) ); # Recall: All parts divided by 2a
1946 11         698 @solutions = ($real_part + $i_roots[0], $real_part + $i_roots[1]);
1947             }
1948 16         1657 return @solutions;
1949             }
1950             #
1951             #-------------------------------------------------------------------------------
1952             # Yapp_solve_cubic()
1953             #
1954             sub Yapp_solve_cubic
1955             {
1956 4     4 0 4 my $self = shift(@_);
1957 4         2 my @solutions; # I will return this array
1958             my $monic; # Original Yapp with 1 as leading coefficient
1959 0         0 my $reduced; # $monic with roots reduced, if needed
1960 4         4 my $reduced_by = 0; # By how much to reduce the roots in order
1961             # to eliminate the degree[2] term
1962 4         2 my ($zero, $quotient_2, $quotient_1); # zero is mainly a placeholder.
1963             # the two quotients are for after an
1964             # evaluation to get the next lower
1965             # degree polynomials.
1966 4         5 my $allreal = $self->all_real; # Optimistic start
1967              
1968             # 2 transformations:
1969             # - Make sure leading coefficient is 1 (monic)
1970             # - Reduce roots by 1/3 of the x^2 coefficient. This sets the x^2 coefficient
1971             # (Which is always the negative of the sum of the roots) to 0
1972             #
1973 4 50       7 if ($self->{coeff}[3] == 1) # If this is alrady a monic polynomial
1974             {
1975 0         0 $monic = Yapp($self); # Use a copy before reducing roots
1976             }
1977             else
1978             { # Divide by leading coefficent
1979 4         7 $monic = $self / ($self->{coeff}[3]); # to *make* it monic
1980 4 50       7 printf("Self: <%s>\nMonic: <%s>\n", $self->Ysprint(), $monic->Ysprint())
1981             if ($testmode); # TESTMODE
1982             }
1983 4 50       8 if ($monic->{coeff}[2] != 0) # If we have a degree[2] term
1984             {
1985 4         5 $reduced_by = -($monic->{coeff}[2])/3; # Reduce by 1/3 of x^2 term
1986 4         6 $reduced = $monic->Yapp_reduce($reduced_by);
1987 4 50       7 printf("Original <%s>, with roots reduced by <%.5f>\nYields: <%s>\n",
1988             $self->Ysprint(), $reduced_by, $reduced->Ysprint())
1989             if($testmode) # TESTMODE
1990             }
1991             else
1992             {
1993 0         0 $reduced = Yapp($monic); # Had no X^2 term.
1994             }
1995 4         5 my $p = $reduced->{coeff}[1];
1996 4         4 my $q = $reduced->{coeff}[0];
1997             #
1998             # In Cardano's algorithm, I set X = U + V, which lets me play games with
1999             # these new variables. After I pluck in (U+V) for X in the reduced monic
2000             # polynomial, I collect terms and get an expression like this:
2001             # U^3 + (U + V)(3*U*V + p) + V^3 + q = 0
2002             # If I set (3*U*V + p) to 0 I get a much simpler expression:
2003             # U^3 + V^3 + q = 0. But, in light of the 0 setting above, I need to
2004             # substitute V = -p/(3*U). This results in the 6th degre equation:
2005             # 27U^6 + 27q*U^3 - p^3 = 0.
2006             # Hey! This is quadratic in U^3. I can solve this!
2007             #
2008 4         10 my $u_six = Yapp((-$p ** 3), (27 * $q), 27);
2009 4         8 $u_six->variable("U-Cubed"); # Set this variable for clarity
2010 4 50       6 printf("U-Cubed quadratic is <%s>\n", $u_six->Ysprint())
2011             if ($testmode); # TESTMODE
2012 4         13 my @u_solution = $u_six->Yapp_solve(); # I only need one of these
2013 4         6 my $u_cubed = $u_solution[0]; # Pick first one - Easy
2014 4         3 my ($u, $v); # Components for X = U + V
2015              
2016             # $u_cubed may be a complex number; I need to be circumspect about taking
2017             # its cube root.
2018             #
2019 4         6 my @u_roots = root($u_cubed, 3); # Get all three cube roots
2020              
2021             # Note: Even the real cube root of a real number may come back as a complex
2022             # with an extremely small imaginary part, on the order of 10^-15 or so. This
2023             # is a legitimate rounding error, which I can lose with relative impunity.
2024             #
2025 4         700 my $use_this_u = $u_roots[0]; # Ready to use first root, even complex
2026 4         6 realify(\@u_roots); # Lose insignificant imaginary com-
2027             # ponents from rounding errors.
2028 4         9 for (my $rlc = 0; $rlc < 3; $rlc++) # Search for a real cube root
2029             {
2030 11 100       23 if (ref($u_roots[$rlc]) ne $class_cplx) # Fair to assume: If it ain't
2031             { # complex, it's the real we prefer
2032 1         2 $use_this_u = $u_roots[$rlc]; # If we found it, grab it and run
2033 1         1 last;
2034             }
2035             }
2036              
2037             # Now I can set $u and $v properly. $u may be real or complex as per above
2038             # loop. But if it is complex, $v seems to inevitable end up as the
2039             # conjugate of $u. Prefer to use a real root but if none is available,
2040             # one root is as good as another for this purpose; in that case, use the
2041             # first cube root.
2042             #
2043 4         3 $u = $use_this_u; # This is the cube root to work with.
2044              
2045             # Now recall that V = -p/(3*U) from the 3*U*V +p = 0 setting
2046             #
2047 4         8 $v = -$p / (3 * $u); # The other component ox X = U + V
2048 4         215 $solutions[0] = $u + $v # Almost there; but remember this is the root
2049             + $reduced_by; # reduced in the second transformation
2050             # Now THAT's a root! .. er, almost.
2051 4         308 $solutions[0] = realify($solutions[0]); # Lose trivial imaginary component
2052             #
2053             # OK, I have my first solution. If it is complex *and* the original
2054             # coefficients are all real, math guarantees me that the comjugate is also
2055             # a solution. In that case, [synthetic] divide by this complex solution
2056             # as well so we are left with a simple linear equation to solve.
2057             # Otherwise (Either this one is real or we had complex coefficients) just
2058             # [synthetic] divide by this solution and solve the rusulting quadratic
2059             # quotient.
2060             #
2061 4 50 33     12 if ( (ref($solutions[0]) eq $class_cplx) # First solution is complex
2062             &&($allreal) ) # and no complex coefficients
2063             { # Conjugate is also a solution
2064 0         0 $solutions[1] = ~ $solutions[0]; # from the original monic
2065 0         0 ($zero, $quotient_2) = $monic->Yapp_eval($solutions[0]);
2066             # Had *better* evaluate to 0!
2067 0         0 ($zero, $quotient_1) = $quotient_2->Yapp_eval($solutions[1]);
2068              
2069             # $quotient_1 is a [ref to a] 1st degree polynomial.
2070             #
2071 0         0 ($solutions[3]) = $quotient_1->Yapp_solve(); # This is kinda trivial
2072             }
2073             else # If I can't depend on conjugate, for what-
2074             { # ever reason, just divide this one out
2075 4         8 ($zero, $quotient_2) = $monic->Yapp_eval($solutions[0]); # -> quadratic
2076              
2077             # $quotient_2 is a quadratic expression. Just solve that.
2078             #
2079 4         6 @solutions[1,2] = $quotient_2->Yapp_solve();
2080             }
2081 4         33 return @solutions;
2082             }
2083             #
2084             #-------------------------------------------------------------------------------
2085             sub Yapp_solve_quartic
2086             { # Much of the setup here is similar to that of Yapp_solve_cubic, but with
2087             # tiny differences, enough to not try to make subroutins of this code.
2088 3     3 0 4 my $self = shift(@_);
2089 3         3 my @solutions; # I will return this array
2090             my $monic; # Original Yapp with 1 as leading coefficient
2091 0         0 my $reduced; # $monic with roots reduced, if needed
2092 3         2 my $reduced_by = 0; # By how much to reduce the roots in order
2093             # to eliminate the degree[2] term
2094 3         3 my ($zero, $quotient_2, $quotient_1); # zero is mainly a placeholder.
2095             # the two quotients are for after an
2096             # evaluation to get the next lower
2097             # degree polynomials.
2098              
2099             # 2 transformations:
2100             # - Make sure leading coefficient is 1 (monic)
2101             # - Reduce roots by 1/4 of the x^3 coefficient. This sets the x^3 coefficient
2102             # (Which is always the negative of the sum of the roots) to 0
2103             #
2104 3 50       5 if ($self->{coeff}[4] == 1) # If this is already a monic polynomial
2105             {
2106 0         0 $monic = Yapp($self); # Use a copy before reducing roots
2107             }
2108             else
2109             { # Divide by leading coefficent
2110 3         6 $monic = $self / ($self->{coeff}[4]); # to *make* it monic
2111 3 50       6 printf("Self: <%s>\nMonic: <%s>\n", $self->Ysprint(), $monic->Ysprint())
2112             if ($testmode); #TESTMODE
2113             }
2114 3 50       8 if ($monic->{coeff}[3] != 0) # If we have a degree[3] term
2115             {
2116 3         5 $reduced_by = -($monic->{coeff}[3])/4; # Reduce by -1/4 of x^3 term
2117 3         6 $reduced = $monic->Yapp_reduce($reduced_by);
2118 3 50       7 printf("Original <%s>, with roots reduced by <%.5f>\nYields: <%s>\n",
2119             $self->Ysprint(), $reduced_by, $reduced->Ysprint()) #TESTMODE
2120             if($testmode);
2121             }
2122             else
2123             {
2124 0         0 $reduced = Yapp($monic); # Had no X^3 term.
2125             }
2126             #
2127             # In Ferrari's algorithm, we transpose the X^2, X^1 and constant term to the
2128             # right side. Of course, it is not expected to be perfect square. However,
2129             # we can add a new variable, u, to the left side so that it becomes
2130             # (1) (X^2 + u)^2.
2131             # That is, add 2*u*X^2 + u^2 to the left side, keeping it a perfect square.
2132             # The right side, with the transposed 3 terms, had been:
2133             # (2) -cX^2 -d*X -e
2134             # now becomes (after collecting terms):
2135             # (3) (2u -c)X^2 -d*X + (u^2 -e)
2136             # (3a) A B C
2137             # Is this a perfect square? Well, that depends: Is *is* a quadradic
2138             # expression but it *can* be a perfect square if is discriminant is 0;
2139             # that is: B^2 - 4*A*C, which is an expression containing that unknown u
2140             # term, can be set to 0. That is:
2141             # (4) (-d)^2 - 4*(2u -c)*(u^2 -e) == 0
2142             # Multiplying out and collecting terms, this becomes:
2143             # (4a) 8*u^3 -4*c*u^2 -8*e*u +(d^2 -4c*e)
2144             # A cubic equation in the unknown u. This is called the resolvent cubic
2145             # of the original polynomial. (The last two terms in parentheses comprise
2146             # the constant of that cubic equation.)
2147             # The point is that: Any solution to (4a) can be plugged back into (3) to
2148             # make it a perfect square to match against the perfect square left hand side
2149             # (LHS) in (1).
2150             # Here goes!
2151             #
2152 3         4 my $c = $reduced->{coeff}[2]; # X^2 coefficient
2153 3         3 my $d = $reduced->{coeff}[1]; # X^1 coefficient
2154 3         4 my $e = $reduced->{coeff}[0]; # Constant term
2155              
2156 3         4 my @rca = (); # Array that will generate the resolvent cubic equation
2157 3         9 push @rca, 4*$c*$e - $d**2; # Constant term
2158 3         3 push @rca, -8*$e; # u^1 term
2159 3         3 push @rca, -4*$c; # u^2 term
2160 3         3 push @rca, 8; # u^3 term
2161 3         4 my $rc = Yapp(\@rca); # Turn that into a polynomial
2162 3         7 $rc->variable("u"); # Just for clarity: With variable letter u
2163 3         5 my @rc_solutions = $rc->Yapp_solve(); # (Obvious purpose)
2164 3         3 my $rc_plug; # The one we wil plug in to (3)
2165              
2166             # Now life is easier (with fewer rounding errors) if I choose a real solution.
2167             # I can't be sure it won't return a complex with a *very* low Im part (on the
2168             # order of 10^-15 ir so) due to rounding errors. So I merely look for a root
2169             # with # sufficiently low Im part that I would ignore it.
2170             #
2171 3         5 foreach my $rc_val (@rc_solutions) #(Had used $rc_plug here but it went
2172             { # undefined after loop exit. Hence
2173 3         3 $rc_plug = $rc_val; # using throwaway variable $rv_val
2174 3 50       6 last if (ref($rc_plug) ne $class_cplx); # It came back as a real! Use it
2175             }
2176             #
2177             # Truth be known, *any* one of the roots is usable but I would prefer a real
2178             # Now plug the found value, $rc_plug, back into (3);
2179             # Reminder: (3) (2u -c)X^2 -d*X + (u^2 -e)
2180             #
2181 3         4 my @rhsa = (); # Array that will become the above quadratic
2182 3         5 push @rhsa, ($rc_plug**2 -$e); # Constant term of above quadratic
2183 3         5 push @rhsa, -$d; # X^1 term of the quadratic is original
2184             # X^1 term of the root-reduced monic
2185 3         4 push @rhsa, (2*$rc_plug -$c); # The X^2 term of above quadratic
2186 3 50       5 if ($testmode)
2187             {
2188 0         0 my $rhs = Yapp(\@rhsa); #(Actually, it is not necessary to generate
2189             # this right-had-side polynomial.)
2190 0         0 printf("RHS Yapp is <%s>\n", $rhs->Ysprint);
2191             }
2192              
2193             # Testing has confirmed that $rhs is indeed a quadratric perfect square.
2194             # and that it is the square of yapp(sqrt(c), sqrt(a))
2195             #
2196 3         6 my $c_sqrt = sqrt($rhsa[0]); # Since the $rhs polynomial is a square
2197 3         16 my $a_sqrt = sqrt($rhsa[2]); # I'm guaranteed these are positive.
2198              
2199             # The above quadratic polynomial is the square of either:
2200             # * +$c_sqrt*X+$a_sqrt or
2201             # * -$c_sqrt*X-$a_sqrt
2202             #
2203             # Now the perfect square on left-had side, is the square of (X^2 + U) and
2204             # so the binomia itself may be set equal to either of these:
2205             # X^2 + U = AX + C or: X^2 -AX + (U - C) = 0
2206             # X^2 + U = -Ax - C or: X^2 +AX + (U + C) = 0
2207             # Where:
2208             # * U is $rc_plug
2209             # * A is $a_sqrt
2210             # * C is $c_sqrt
2211             # Solve these two quadratics and I have *almost* solved the original quartic.
2212             #
2213 3         13 my @quad1_a = ( ($rc_plug - $c_sqrt), -$a_sqrt, 1); # Well, in order to
2214 3         5 my @quad2_a = ( ($rc_plug + $c_sqrt), $a_sqrt, 1); # them, it helps to
2215 3         4 my $quad1 = Yapp(\@quad1_a); # generate them
2216 3         4 my $quad2 = Yapp(\@quad2_a);
2217 3 50       6 printf("quad1 = <%s>;\nquad2 = <%s>\n", $quad1->Ysprint, $quad2->Ysprint)
2218             if ($testmode);
2219 3         5 @solutions[0..1] = $quad1->Yapp_solve();
2220 3         7 @solutions[2..3] = $quad2->Yapp_solve();
2221              
2222             # Not there yet: Remember, I reduced the roots to produce the monic. Now
2223             # undo that:
2224             #
2225 3         6 @solutions = map {$_ + $reduced_by} @solutions; # Restore what has been
  12         641  
2226             # taken from them
2227 3         223 return @solutions;
2228             }
2229             #
2230             #-------------------------------------------------------------------------------
2231             # Yapp_solve_odd(): Method to solve polynomial equations of odd degree >= 5
2232             # with all real coeficcients.
2233             #
2234             # Parameter:
2235             # - [Ref to] a Yapp polynomial
2236             # Returns:
2237             # - Array of solution set
2238             # Note: Initially, this function supports only polynomials with real-only
2239             # coefficients, although I am leaving a stub block for handling complex
2240             # coefficients as well.
2241             #
2242             sub Yapp_solve_odd
2243             {
2244 1     1 0 2 my $self = shift(@_);
2245 1         2 my $crude_margin = 0.1; # Just get close enough to start using Newton
2246 1         1 my @solutions = (); # Array that I return to the caller
2247              
2248             # The following pair of betw variables will be set during a bisection
2249             # algorithm and used for initial values once we start wuth Newton's method.
2250             # Hence, we need to define them waaaay before they will be used, in order to
2251             # find them in scope anyplace in this function.
2252             #
2253 1         2 my $betw_try; # X-value at midpoint of an interval
2254             my $betw_eval; # Y-value at that X midpoint
2255              
2256             # Find one real root and check if it is duplicated.
2257             # Set up for a bisection algorithm: Starting with some initial guess at
2258             # 0, 1, and maybe -1, start doubling the interval until I get a + on one
2259             # side and - on the other. Then start bisecting until I get pretty
2260             # close, like within 1/10 or so. Then start using Newton.
2261             #
2262 1         1 my $left_try = 0.0; # First & easiest point to test is X = 0
2263 1         1 my $left_eval = $self->{coeff}[0]; # 'cause I don't need to evaluate
2264 1         1 my $right_try = 1.0;
2265 1         2 my $right_eval = $self->Yapp_eval($right_try);
2266              
2267             # Search for an interval such that the Yapp evaluates to opposite signs
2268             # at the two endpoints of the interval
2269             #
2270 1         4 until (($left_eval * $right_eval) < 0)
2271             { # Entering loop body [again] means both tries evauated to the same
2272             # sign. Sweep wider intervals..
2273             #
2274 3 100       5 if ($left_try == 0.0) # At first round of this loop, don't double
2275             { # both enpoints of the interval;
2276 1         2 $left_try = -1.0; # just expand in negative direction
2277             }
2278             else # But if we are well into the round
2279             { # we will double both enpoints of the interval
2280 2         1 $left_try *= 2;
2281 2         2 $right_try *= 2;
2282             }
2283             # Either way, re-evaluate at both endpoints. (OK, one wasted,
2284             # repeated evaluateion at $right_try == 1). Minor, IMO
2285             #
2286 3         4 $left_eval = $self->Yapp_eval($left_try);
2287 3         4 $right_eval = $self->Yapp_eval($right_try);
2288             }
2289              
2290             # OK, we have found X-values that evaluate to opposite signs. Now start
2291             # bisecting scheme.
2292             #
2293 1         4 until (abs($right_try - $left_try) <= $crude_margin)
2294             {
2295 4         5 $betw_try = ($left_try + $right_try)/2 ; # Bisect the interval
2296 4         6 $betw_eval = $self->Yapp_eval($betw_try); # Evaluate @ midpoint
2297 4 100       20 last if ($betw_eval == 0); # Hey! We hit an exact root!
2298 3 50       5 my $left_sign = $left_eval >= 0 ? 1 : -1; # and determine the signs
2299 3 50       4 my $right_sign = $right_eval >= 0 ? 1 : -1; # at the endpoints and
2300 3 100       5 my $betw_sign = $betw_eval >= 0 ? 1 : -1; # midpoint
2301              
2302             # Now mathematically, the sign at the midpoint evaluation must match
2303             # exactly one (never both) of the endpoint evaluation signs. Ergo, it
2304             # must be be different from exactly one of the endpoint evaluations
2305             #
2306 3 100       4 if ($left_sign != $betw_sign) # Changes sign to left
2307             { # of the midpoint
2308 1         1 $right_try = $betw_try; # Then move right end to
2309 1         3 $right_eval = $betw_eval; # middle, with its evaluate
2310             }
2311             else # Changed sign to right
2312             { # of the midpoint
2313 2         1 $left_try = $betw_try; # Then move left end to
2314 2         4 $left_eval = $betw_eval; # middle, with its evaluate
2315             }
2316             }
2317              
2318             # OK, however we got here, we are now close enough to a solution to start
2319             # using the Newton method. That is, unless we actually hit an exact root!
2320             #
2321 1 50       3 $solutions[0] = ($betw_eval == 0.0) ? # Start at latest midpoint
2322             $betw_try : $self->Yapp_newton($betw_try);
2323 1         2 my ($zero, $quotient) = $self->Yapp_eval($solutions[0]);
2324             #(A good debugging step would be to check
2325             # that $zero is indeed 0.0)
2326 1         1 my $xn = $solutions[0]; #(Shorter var name for neater code)
2327              
2328             # So $xn is a root. Question: Is it a multiple root?
2329             #
2330 1         4 while($quotient->{degree} > 0) # Keep reducing it as we loop
2331             {
2332 1         2 my ($zzero, $quotient2) = $quotient->Yapp_eval($xn); # Solves quotient?
2333 1 50       4 last if (abs($zzero) >= $margin); # Clearly not. Done checking; Outahere!
2334              
2335             # Still here: It is a solution again!
2336             #
2337 0         0 $quotient = $quotient2; # This is next quotient to use
2338 0         0 push (@solutions, $xn); # and add same root into solutions set
2339             }
2340             # OK, I've exhausted my repeated copies of first root.
2341             # If anything's left, solve it separately. But first: How many times was
2342             # that first root repeated?
2343             #
2344             #?my $rest_roots = @solutions; # This will be the starting index
2345             # for a newton-refinement for the
2346             # remaining roots.
2347 1 50       3 if ($quotient->{degree} > 0)
2348             { # as repeated and conjugate roots
2349 1         3 my @q_solutions = $quotient->Yapp_solve(); # Solve the deflated polynomial
2350              
2351             # Now, to compensate for additional rounding errors that creep in when
2352             # solving the lower-degree quotient polynomials, let's up the accuracy;
2353             # apply Newtons's method to the roots returned by the recursive calls.
2354             # Laguerre has already been applied to this first root (plus duplicates
2355             # and conjugates).
2356             #
2357 1         3 @q_solutions = map {$self->Yapp_newton($_)} @q_solutions;
  4         7  
2358 1         3 push (@solutions, @q_solutions); # THOSE solutions go into my set
2359             }
2360 1         6 return @solutions;
2361             }
2362             #
2363             #-------------------------------------------------------------------------------
2364             # Yapp_solve_even(): Method to solve polynomial equations of even degree >= 6
2365             # with real-only coefficients
2366             # Also good for solving polynomials with complex coefficients; the only
2367             # difference is that for real-ony, the conjugate of of a complex root is
2368             # guaranteed by theorem to be a root as well.
2369             #
2370             # Parameter:
2371             # - [Ref to] a Yapp polynomial
2372             # Returns:
2373             # - Array of solution set
2374             #
2375             sub Yapp_solve_even
2376             {
2377 1     1 0 2 my $self = shift(@_);
2378 1         2 my @solutions = (); # Array that I return to the caller
2379              
2380 1         3 my $start = cplx(.1, .1); # Dumb starting point but we gotta start someplace!
2381 1         335 $solutions[0] = $self->Yapp_laguerre($start);
2382 1         4 my ($zero, $quotient) = $self->Yapp_eval($solutions[0]); # Deflate a degree
2383 1         1 my ($zzero, $quotient2); # To test for repeated roots
2384 1         2 my $xn = $solutions[0]; #(Shorter var name for neater code)
2385              
2386             # So $xn is a root. 3 questions:
2387             # 1. Is it a complex root? If yes, we can just use its conjugate & # deflate
2388             # 2. Is it a multiple complex root?
2389             # 3. If real, is it a multiple real root?
2390             #
2391 1 50       3 if (ref($xn) eq $class_cplx) # Yes to question[1]: Is complex
2392             { # We have a work-free next solution
2393 1         3 push(@solutions, ~ $xn); # The conjugate is also a root: Stash it
2394 1         36 ($zero, $quotient) = $quotient->Yapp_eval(~ $xn); # and deflate a degree
2395             # $zero == 0; toss it
2396              
2397             # Now start checking for repeats of this complex root. (Question[2])
2398             #
2399 1         9 while($quotient->{degree} > 0) # Keep reducing it as we loop
2400             {
2401             # Question 2 rephrased: Does the same complex number solve the quotient?
2402 1         4 ($zzero, $quotient2) = $quotient->Yapp_eval($xn); # Evaluate to find out
2403 1 50       2 last if (abs($zzero) >= $margin); # Clearly not. Done checking; Outahere!
2404              
2405             # Still here: Same complex number *is* a solution again!
2406             #
2407 0         0 push (@solutions, $xn); # First, push same root into solutions set
2408 0         0 $quotient = $quotient2; # Point $quotient to above-deflated version
2409             # Of course, its conjugate is also a root
2410 0         0 push (@solutions, ~ $xn); # Push its conjugate again into @solutions
2411 0         0 ($zero, $quotient) # and deflate by the conjugate root
2412             = $quotient->Yapp_eval(~ $xn);
2413             }
2414             }
2415             else # Solution is real. No free conjugate ride
2416             {
2417 0         0 while($quotient->{degree} > 0) # Keep reducing it as we loop
2418             {
2419 0         0 ($zzero, $quotient2) = $quotient->Yapp_eval($xn); # Also solves quotient?
2420 0 0       0 last if (abs($zzero) >= $margin); # Clearly not. Done checking; Outahere!
2421              
2422             # Still here: Same real is a solution again!
2423             #
2424 0         0 $quotient = $quotient2; # This is next deflated quotient
2425 0         0 push (@solutions, $xn); # and add same root into solutions set
2426             }
2427             }
2428             # OK, I've exhausted my repeated and/or conjugate copies of first root.
2429             # If anything's left, solve it separately.
2430             #
2431 1 50       11 if ($quotient->{degree} > 0) # If I have not solved for all roots
2432             { # as repeated and conjugate roots
2433 1         3 my @q_solutions = $quotient->Yapp_solve(); # Solve the deflated polynomial
2434              
2435             # Now, to compensate for additional rounding errors that creep in when
2436             # solving the lower-degree quotient polynomials, let's up the accuracy;
2437             # apply Newtons's method to the roots returned by the recursive calls.
2438             # Laguerre has already been applied to this first root (plus duplicates
2439             # and conjugates).
2440             #
2441 1         2 @q_solutions = map {$self->Yapp_newton($_)} @q_solutions;
  4         6  
2442 1         4 push (@solutions, @q_solutions); # THOSE solutions go into my set
2443             }
2444              
2445 1         11 return @solutions;
2446             }
2447             #
2448             #-------------------------------------------------------------------------------
2449             # Yapp_solve_complex(): Method to solve for the zeroes of a polynomial with
2450             # complex coeffiecients. The theorem about roots in conjugate pairs flies
2451             # out the window.
2452             # Same parameter/returns as all others.
2453             #
2454             sub Yapp_solve_complex
2455             {
2456 0     0 0 0 my $self = shift(@_);
2457 0         0 my @solutions = (); # Array that I return to the caller
2458              
2459 0         0 my $start = cplx(.1, .1); # Dumb starting point but we gotta start someplace!
2460 0         0 $solutions[0] = $self->Yapp_laguerre($start);
2461 0         0 my ($zero, $quotient) = $self->Yapp_eval($solutions[0]); # Deflate a degree
2462 0         0 my ($zzero, $quotient2); # To test for repeated roots
2463 0         0 my ($conj_zero, $conj_quotient); # To test if conjugate is also a root
2464 0         0 my $xn = $solutions[0]; #(Shorter var name for neater code)
2465              
2466             # So $xn is a root. 3 questions:
2467             # 1. Is it a complex root? If yes, we can just use its conjugate & # deflate
2468             # 2. Is it a multiple complex root?
2469             # 3. If real, is it a multiple real root?
2470             #
2471 0 0       0 if (ref($xn) eq $class_cplx) # Yes to question[1]: Is complex
2472             { # We don't have a work-free next solution
2473 0         0 ($conj_zero, $conj_quotient) # Check: Is conjugate is also a root?
2474             = $quotient->Yapp_eval(~$xn); # Create a copy deflated by conjugate
2475 0 0       0 if (abs($conj_zero) < $margin) # If conjugate is also a root (Not
2476             { # likely with complex coefficients)
2477 0         0 push(@solutions, ~$xn); # Aha! Conjugate is also a solution
2478 0         0 $quotient = $conj_quotient; # Now $quotient is the deflated Yapp
2479             }
2480              
2481             # Now start checking for repeats of this complex root. (Question[2])
2482             #
2483 0         0 while($quotient->{degree} > 0) # Keep reducing it as we loop
2484             {
2485 0         0 ($zzero, $quotient2) = $quotient->Yapp_eval($xn); # Solves quotient?
2486 0 0       0 last if (abs($zzero) >= $margin); # Clearly not. Done checking; Outahere!
2487              
2488             # Still here: Same complex number *is* a solution again!
2489             #
2490 0         0 push (@solutions, $xn); # First, push same root into solutions set
2491 0         0 $quotient = $quotient2; # Point $quotient to above-deflated version
2492             # But is its conjugate also a root?
2493 0         0 ($conj_zero, $conj_quotient) # Is conjugate is also a root? Create
2494             = $quotient->Yapp_eval(~$xn); # a copy deflated by conjugate
2495 0 0       0 if (abs($conj_zero) < $margin) # If conjugate is also a root (Not
2496             { # likely with complex coefficients)
2497 0         0 push(@solutions, ~$xn); # Aha! Conjugate is also a solution
2498 0         0 $quotient = $conj_quotient; # Now $quotient is the deflated Yapp
2499             }
2500             }
2501             }
2502             else # Solution is real; just check for duplicate
2503             {
2504 0         0 while($quotient->{degree} > 0) # Keep reducing it as we loop
2505             {
2506 0         0 ($zzero, $quotient2) = $quotient->Yapp_eval($xn); # Also solves quotient?
2507 0 0       0 last if (abs($zzero) >= $margin); # Clearly not. Done checking; Outahere!
2508              
2509             # Still here: Same real is a solution again!
2510             #
2511 0         0 $quotient = $quotient2; # This is next deflated quotient
2512 0         0 push (@solutions, $xn); # and add same root into solutions set
2513             }
2514             }
2515             # OK, I've exhausted my repeated and/or conjugate copies of first root.
2516             # If anything's left, solve it separately.
2517             #
2518 0 0       0 if ($quotient->{degree} > 0) # If I have not solved for all roots
2519             { # as repeated and conjugate roots
2520 0         0 my @q_solutions = $quotient->Yapp_solve(); # Solve the deflated polynomial
2521              
2522             # Now, to compensate for additional rounding errors that creep in when
2523             # solving the lower-degree quotient polynomials, let's up the accuracy;
2524             # apply Newtons's method to the roots returned by the recursive calls.
2525             # Laguerre has already been applied to this first root (plus duplicates
2526             # and conjugates).
2527             #
2528 0         0 @q_solutions = map {$self->Yapp_newton($_)} @q_solutions;
  0         0  
2529 0         0 push (@solutions, @q_solutions); # THOSE solutions go into my set
2530             }
2531              
2532 0         0 return @solutions;
2533             }
2534             #
2535             #-------------------------------------------------------------------------------
2536             # Interpolation: Effectively a new kind of constuctor, though not pacakged
2537             # as such. The parameters will always be references to arrays of numbers.
2538             # The 0th array is a set of X values, the 1st array is a set of Y values.
2539             # This is sufficient for laGrange interpolation - the basic colocation
2540             # polynomial, Additional arrays are for first and successive derivatives.
2541             # The goal is to generate a polynomial of minimum degree whose Y values and
2542             # derivatives at the indicated X-values match the arrays. All will be
2543             # called via the function Yapp_interpolate(). This will call Yapp_lagrange()
2544             # or Yapp_hermite() as the case requires.
2545             # Calling sequence:
2546             # $yap_interp = Yapp_interpolate(\@x_vals, \@y_vals [,\@d1_vals, # \@d2_vals]);
2547             # Returns:
2548             # - A polynomial whose y values and derivatives match the input.
2549             #
2550             sub Yapp_interpolate
2551             {
2552 2     2 1 1230 my $self = Yapp("1"); # Create a 0-degree polynomial - just 1
2553 2         3 my $ryapp; # Polynomial to be returned to caller
2554              
2555             # One validation: All arrays must have the name number of elements
2556              
2557 2         2 my $pcount = @_; # Number of arrays passed to me
2558 2 50       6 die "Must supply at least two arrays (X and Y values) for interpolation"
2559             unless $pcount >= 2;
2560              
2561 2         2 my $x_vals = $_[0]; # Get -> X-values array
2562 2         2 my $xcount = @{$x_vals}; # How many X-values were given?
  2         2  
2563              
2564             # Validate that all arrays have the same number of elements
2565             #
2566 2         5 for (my $plc = 1; $plc < $pcount; $plc++)
2567             {
2568 3         3 my $y_list = $_[$plc]; # -> next array in parameter list
2569 3         3 my $ycount = @{$y_list}; # How many elements does this Y array have?
  3         4  
2570 3 50       8 die "Y-list[$plc] has $ycount elements; should have $xcount"
2571             unless ($ycount == $xcount);
2572             }
2573              
2574             # Still here: OK, we may proceed
2575             #
2576 2         1 my $y_vals = $_[1]; # Get first set of Y values
2577 2         3 my $derivs = @_; # How many levels of derivative are wanted?
2578              
2579 2 100       10 $ryapp = ($derivs == 2) ? $self->Yapp_lagrange($x_vals, $y_vals)
2580             : $self->Yapp_hermite(@_);
2581 2         11 return ($ryapp);
2582             }
2583             #
2584             #-------------------------------------------------------------------------------
2585             # Yapp_lagrange() - Generate a polynomial using laGrange interpolattion
2586             # Not intended for public use
2587             #
2588             # Parameters:
2589             # - (implicit) A token polynomial,
2590             # - Reference to an array of X-Values
2591             # - Reference to array of Y-values
2592             # Both arrays must be the same size or a fatal error will be generated
2593             # Returns:
2594             # - A polynomial that satisfies all those points.
2595             #
2596             sub Yapp_lagrange
2597             {
2598 1     1 0 1 my $self = shift(@_); # Get my token polynomial
2599 1         2 my ($x_vals, $y_vals) = @_; # Get my X & Y array references
2600 1         2 my $ryapp = Yapp(0); # 0-degree poly, with constant: 0
2601              
2602              
2603 1         5 my $lag_list = Yapp_lag($x_vals, $y_vals); # Generate laGrange multipliers
2604              
2605             # In case you have not read the long comment in Yapp_lag:
2606             # Each laGrange multiplier polynomial evaluates to 1 at its designated
2607             # point and 0 at all the other points. I will multiply it by the Y-value
2608             # at that point before adding it to the final polynomial. That way, each
2609             # laGrange multiplier has a value of Y[i] at X[i]
2610             #
2611 1         2 my $lagp;
2612 1         2 for (my $xlc = 0; $xlc <= $#{$x_vals}; $xlc++)
  8         15  
2613 7         8 { $ryapp += $lag_list->[$xlc] * $y_vals->[$xlc]; } # Add multilpied multiplier
2614             # to the result
2615 1         6 return $ryapp; # Show what I have built up
2616             }
2617             #
2618             #-------------------------------------------------------------------------------
2619             # Yapp_hermite() - Generate a polynomial using Hermite interpolattion
2620             # Not intended for public use
2621             #
2622             # Parameters:
2623             # - (implicit) A token polynomial,
2624             # - Reference to an array of X-Values
2625             # - Reference to array of Y-values
2626             # - As many references to derivative values.
2627             # Note that as of this release, only a first derivative is supported.
2628             # Others will be ignored.
2629             #
2630             # Returns:
2631             # - A polynomial that satisfies all those points.
2632             #
2633             sub Yapp_hermite
2634             {
2635 1     1 0 2 my $self = shift(@_); # Get my token polynomial
2636 1         2 my ($x_vals, $y_vals, $yp_vals) = @_; # Get my X, Y, Y' array references
2637              
2638 1         1 my $xlc; # Loop counter for a few loops below
2639 1         2 my (@U_herm, @V_herm); # -> arrays of Hermite components
2640              
2641 1         2 my $lag_list = Yapp_lag($x_vals, $y_vals); # Generate laGrange multipliers
2642 1         2 my $lag_prime = (); # Array: Derivatives of above
2643 1         2 for ($xlc = 0; $xlc <= $#{$x_vals}; $xlc++)
  7         12  
2644             {
2645 6         12 $lag_prime->[$xlc] = ($lag_list->[$xlc])->Yapp_derivative(1);
2646             }
2647              
2648             # Each additive to the Hermite polynomial is the sum of U and V polynomials,
2649             # as denerated below
2650             #
2651 1         2 for ($xlc = 0; $xlc <= $#{$x_vals}; $xlc++)
  7         11  
2652             {
2653             # First component: V[i](X) = (X - x[i])(Lagrange[i]^2)
2654             #
2655 6         10 my $lag_squared = ($lag_list->[$xlc])**2;
2656 6         11 my $v_herm1 = Yapp(-$x_vals->[$xlc], 1); # X - x_vals[i];
2657 6         9 $V_herm[$xlc] = $v_herm1 * $lag_squared;
2658              
2659             # Second component: U[i](X) = [1 - 2L'(x[i])(X-X[i])](Lagrange[i]^2)
2660             # (Ugly, but it gets you there, like the old Volkswagon slogan :-)
2661             #
2662             # Evaluate derivative of current Lagrange multiplier polynomial at x[i]
2663             #
2664 6         15 my $l_prime = $lag_prime->[$xlc]->Yapp_eval($x_vals->[$xlc]);
2665 6         11 my $u_herm1 = (1 - 2*$l_prime * $v_herm1); # Still a degree 1 polynomial
2666 6         13 $U_herm[$xlc] = $u_herm1 * $lag_squared;
2667             }
2668             # I now have the components for the Hermite polynomial. Now I need to start
2669             # using the Y and Y' values given> (Yes, I could have done this addition
2670             # within the above loop. Clarity over elegance here.)
2671             #
2672 1         3 my $ryapp = Yapp(0); # Start with a constant 0 and add to it
2673 1         2 for ($xlc = 0; $xlc <= $#{$x_vals}; $xlc++)
  7         13  
2674             {
2675 6         8 $ryapp += $y_vals->[$xlc] * $U_herm[$xlc]
2676             + $yp_vals->[$xlc] * $V_herm[$xlc] ;
2677             }
2678 1         20 return $ryapp;
2679             }
2680             #
2681             #-------------------------------------------------------------------------------
2682             # Yapp_lag() - Function to generate the laGrange polynomials that add up to the
2683             # laGrange interpolating polynimal. This code was originally in
2684             # Yapp_lagrange() but was separated out because it will be useful
2685             # for the Hermite inerpolation scheme as well.
2686             # This function is not a method and will not be exported, since its use is
2687             # strictly internal.
2688             # Parameters:
2689             # - Reference to an array of X values
2690             # - Reference to array of corresponding Y values
2691             # Returns:
2692             # - Reference to an array of component laGrange polynomials
2693             #
2694             sub Yapp_lag
2695             {
2696 2     2 0 3 my ($x_vals, $y_vals) = @_;
2697 2         3 my $xcount = @{$x_vals}; # Number of points in my lists
  2         3  
2698 2         3 my @x_minus; # Array of x-x[i] polynomials
2699             my @grange; # Array of largrange 1-point polynomials
2700             # I will be returning a reference to this array
2701              
2702 2         11 for (my $mlc = 0; $mlc < $xcount; $mlc++)
2703             {
2704 13         18 $x_minus[$mlc] = Yapp(-($x_vals->[$mlc]), 1); # X - x_val[mlc]
2705             }
2706              
2707             # Now build the laGrange set for this set of X points
2708             #
2709 2         5 for (my $xlc = 0; $xlc < $xcount; $xlc++)
2710             {
2711 13         14 $grange[$xlc] = Yapp(1); # Starting point for each laGrange poly
2712 13         19 for (my $mlc = 0; $mlc < $xcount; $mlc++)
2713             {
2714 85 100       108 next if ($mlc == $xlc); # Product of all but current point
2715 72         80 $grange[$xlc] *= $x_minus[$mlc];
2716             }
2717             # Coming out of above inner loop, $grange[$xlc] is the product of all
2718             # the (X - x_val[i]) except the i for the current point ($mlc)
2719             # The correct laGrange multiplier polynomial is 0 at all but the current
2720             # point but 1 at the current point. To force this one into that mold:
2721             # Divide the polynomial by its evaluation at x_val[xlc] (so it is 1 at
2722             # this point)
2723             #
2724 13         22 $grange[$xlc]
2725             /= ($grange[$xlc])->Yapp_eval($x_vals->[$xlc]);
2726             }
2727             # Coming out above outer loop, array @grange has all the lagrange polynomials
2728             # pertaining to this set of points. That's what the calling function wants.
2729             #
2730 2         11 return \@grange; # Return reference to that array
2731             }
2732             #
2733             # Yapp_by_roots(): Constructor to build a polynomial whose roots are in the
2734             # passed array or referenced array.
2735             # Parameter[s]: Either:
2736             # - A complete array of the roots of the desired polynomial
2737             # - A reference to such an array.
2738             # It is the caller's responsibility to include conjugate pairs of complex roots
2739             # if only real coefficients are desired.
2740             #
2741             sub Yapp_by_roots
2742             {
2743             # Question: Did user pass me an array or array reference? Either way I will
2744             # be stepping through the array using an array reference. That is:
2745             # - If user passed me a nice array reference, just use it.
2746             # - Passed me a naked array? Create reference the parameter array.
2747             #
2748 2 50   2 1 1555 my $roots = (ref($_[0]) eq "ARRAY") ? shift(@_) : \@_ ;
2749              
2750 2         5 my $ryapp = Yapp(1); # Start with nice unit Yapp - Just "1"
2751              
2752 2         4 for (my $rlc = 0; $rlc <= $#{$roots}; $rlc++)
  13         23  
2753             {
2754 11         21 $ryapp *= Yapp(- ($roots->[$rlc]), 1) # Multiply by (X - $roots[i])
2755             }
2756 2         6 return $ryapp; # Bet that looked easy! :-)
2757             }
2758             #
2759             # New section: Orthogonal Polynomials.
2760             # Although the plan is to have sub-classes for all classical kinds of orthognal
2761             # polynomials, I am including all the operations with Legendre orthogonality.
2762             # That is where the inner product is defined as the intergal of (P[1] * P[2])
2763             # taken over the interval [-1,1]. More accurately, since we are allowing for
2764             # complex coefficients, the intergral of (P[1] * conj(P[2]). Hence, the
2765             # complex inner product is not a commutative operation. (Finkbeiner)
2766             #
2767             # Based on this inner product as a starting point, we will define the following
2768             # methods:
2769             # - Yapp_norm: The square root of the inner product of the polynomial with
2770             # itself
2771             # - Yapp_normalize: Divide a polynomial by its norm to produce a polynomial
2772             # whose norm == 1
2773             # - Yapp_distance: The norm of the difference of two polynomials
2774             # - Yapp_orthoganal: True-false: Are these two Yapp-polynomials orthoganal, that
2775             # is, is their inner product 0?
2776             # - Yapp_gramSchmidt: Apply the Gram-Schmidt algorithm to an arbitrary set of
2777             # polynomials to produce an array of mutually orthonormal
2778             # polynomials.
2779             # - Yapp_genOrthogs: Generate orthogonals based on (1, X, X^2, .. X^n)
2780             #------------------------------------------------------------------------------
2781              
2782             # Yapp_innerProd() - Inner product of two Yapps, using the Legendre polynomials.
2783             # Intended for use as an operator: The dot . operator
2784             # Parameters:
2785             # - (Implicit) - The first Yapp as a vector
2786             # - The other Yapp
2787             # Returns:
2788             # - The inner product, which may be complex if any of the coefficients on
2789             # either side of the operator are complex
2790             #
2791             sub Yapp_innerProd
2792             {
2793 2     2 0 320 my ($self, $yapp2) = @_; # Get my parameters
2794 2 50       8 printf("\nCalculate inner product of polynomials:\n<%s>\n<%s>\n",
2795             $self->Ysprint(), $yapp2->Ysprint())
2796             if ($testmode);
2797              
2798 2         11 my $conj2 = ~ $yapp2; # Conjugate the second parameter
2799 2 50       6 printf("\nStart with product of:\n<%s>\n\tand\n<%s>\n",
2800             $self->Ysprint(), $conj2->Ysprint())
2801             if ($testmode);
2802              
2803 2         11 my $yprod = $self * $conj2; # Get product of yapp1, ~yapp2
2804 2 50       7 printf("That is:\n<%s>\n", $yprod->Ysprint())
2805             if ($testmode);
2806              
2807 2         11 my $rval = $yprod->Yapp_integral(-1, 1); # Intergal of the product over
2808             # the classic interval [-1, 1]
2809 2 50       7 printf("Returning value <%s> as inner product\n", Csprint($rval))
2810             if ($testmode);
2811              
2812 2         48 return $rval; # That's the inner product!
2813             }
2814             #
2815             # Yapp_Orthogonal() - True or false: Is one Yapp-vector orthogonal to the
2816             # other? i.e. Is the inner product of the two vectors 0 (or close enough)?
2817             # Parameters:
2818             # - (Implicit) A first Yapp to compare
2819             # - A second Yapp.
2820             # Returns:
2821             # - 0 if not orthogonal
2822             # - 1 if they are
2823             #
2824             sub Yapp_Orthogonal
2825             {
2826 0     0 0   my ($Y1, $Y2) = @_; # Get the references
2827 0           my $rval = 0; # Off to a negative start
2828              
2829 0           my $innerprod = $Y1 . $Y2; # Get the inner product of two vectors
2830 0 0         $rval = 1 if (abs($innerprod) <= $margin); # Changing mind about that NO?
2831 0           return $rval;
2832             }
2833             #-----------------------------------------------------------------------------
2834             # Yapp_norm() - The next step after defining an inner product function for a
2835             # vector space is to then make it a normed vector space by defining a "norm"
2836             # function. The most cooperative norm function seems to be the square root
2837             # of the inner product of a vector with itself.
2838             # Parameter:
2839             # - (Implicit): The Yapp reference
2840             # Returns:
2841             # - The norm value of that vector.
2842             #
2843             sub Yapp_norm
2844             {
2845 0     0 0   my $self = shift(@_);
2846 0           my $norm_squared = $self . $self; # Inner product of vector with itself
2847 0           my $norm = sqrt($norm_squared); # Relying on math to keep it real
2848 0           return $norm; # That's my return value
2849             }
2850             #-----------------------------------------------------------------------------
2851             # Yapp_normalize(): Divide a Yapp vector by its norm so that the norm of the
2852             # new vector is 1.
2853             # Parameter:
2854             # - (Implicit) The Yapp polynomial
2855             # Returns:
2856             # - A new Yapp, proportional to the original but whose norm == 1
2857             #
2858             sub Yapp_normalize
2859             {
2860 0     0 0   my $self = shift;
2861 0           my $norm = $self->Yapp_norm(); # Get the norm of the Yapp as vector
2862 0           my $normed = $self / $norm; # Create that new Yapp according to specs
2863 0           return $normed; # Send that back to the caller
2864             }
2865             #
2866             # Yapp_distance() - How "far" is the passed Yapp from the object Yapp?
2867             # Answer: That is the norm (Yapp_norm) of the difference between the two Yapps
2868             #
2869             # Parameters:
2870             # - (Implicit) A Yapp [reference], arbitrarily chosen as the fixed vector
2871             # - The [reference to the] other Yapp
2872             # Returns:
2873             # - The real value that is the norm of the difference
2874             #
2875             sub Yapp_distance
2876             {
2877 0     0 0   my ($self, $vector) = @_; # Get the parameter references
2878 0           my $diff = $self - $vector; # Get the difference
2879 0           my $rval = $diff->Yapp_norm(); # Get "size" of that difference
2880 0           return $rval;
2881             }
2882             #
2883             1;
2884              
2885             __END__