File Coverage

blib/lib/Math/Yapp.pm
Criterion Covered Total %
statement 697 808 86.2
branch 225 320 70.3
condition 17 24 70.8
subroutine 58 65 89.2
pod 12 56 21.4
total 1009 1273 79.2


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