File Coverage

blib/lib/Math/Yapp.pm
Criterion Covered Total %
statement 690 781 88.3
branch 216 298 72.4
condition 20 30 66.6
subroutine 58 61 95.0
pod 12 51 23.5
total 996 1221 81.5


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