	       A Univariate Polynomial Class for S
	       ===================================

Introduction and summary:

S-Plus is not Maple and will never emulate it very well, however
it does have some interesting data and programming features that
allow it to handle some simple symbol manipulation problems, and
this can turn out to be very useful on occasions.

The following started as a straightforward programming exercise
in operator overloading, but it seemed to me the result might be
more generally useful.  The goal is to write a polynomial class,
that is a suite of S facilities that allow operations on
polynomials: addition, subtraction, multiplication, "division",
remaindering, printing, plotting, &c to be conducted using the
same operators and functions, and hence with the same ease, as
ordinary arithmetic, plotting, printing, and so on.

The class is limited to univariate polynomials, and so they may
therefore be uniquely defined by their numeric coefficient
vector.  Coercing a polynomial to numeric yields this coefficient
vector as a numeric vector.

For reasons of simplicity I have limited it to real polynomials;
handling polynomials with complex coefficients would be a simple
extension, but I see no compelling reason to do so as yet.
Handling polynomials with polynomial coefficients, and hence
multivariate polynomials, would be feasible, though a major
undertaking and the result would be very slow and of rather
limited usefulness and efficiency.  That seems to be the time
when you do need to go to Maple and do things properly.

General orientation:

The function polynomial() creates an object of class polynomial
from a numeric coefficient vector.  [as.polynomial() does exactly
the same job.]  Coefficient vectors are assumed to apply to the
powers of the carrier variable in increasing order, that is, in
the `truncated power series' form, and in the same form as
required by polyroot(), the system function for computing zeros
of polynomials.  (As a matter or terminology, the "zeros" of the
polynomial P(x) are the same as the "roots" of equation P(x) =
0.)

Polynomials may also be created by specifying a set of (x,y)
pairs and constructing the Lagrange interpolation polynomial that
passes through them (poly.from.values(x, y)).  If y is a matrix
an interpolation polynomial is calculated for each column and the
result is a list of polynomials.

The third way polynomials are commonly generated is to use the
function poly.from.zeros(z), which creates the monic polynomial
of lowest degree with the values in z as its zeros.

The core facility provided is the group method function
Ops.polynomial, which allows arithmetic operations to be
performed on polynomial arguments using ordinary arithmetic
operators.  

Notes:

 1. "+", "-" and "*" have their obvious meanings for polynomials.
 
 2. "^" is limited to non-negative integer powers.
 
 3. "/" returns the polynomial quotient and "%/%" does the same.
    If division is not exact the remainder is discarded, (but see
    4.)
 
 4. "%%" returns the polynomial remainder, so that if all
    arguments are polynomials, p1 * (p2 / p1) + p2 %% p1 is the
    same polynomial as p2, provided p1 is not the zero polynomial.
 
 5. If numeric vectors are used in polynomial arithmetic they are
    coerced to polynomial, which could be a source of surprise.
    In the case of scalars, though, the result is natural,
 
 6. Some logical operations are allowed, but not always very
    satisfactorily.  "==" and "!=" mean exact equality or not,
    respectively, however "<", "<=", ">", ">=" really only refer
    to the leading (highest power) coefficients.  Thus p <= p-1
    is T, unless p is a constant.  The operators "!", "|" and "&"
    are not allowed at all and cause stops in the calculation.
 
 7. Most Math functions are deprecated on polynomial arguments,
    however round(p, digits) and signif(p, digits) may be used to
    modify the coeffieicnt vectors accordingly.  For other
    functions the polynomial behaves as a vector of coefficients
    a warning is issued, and the result is not a polynomial.
 
 8. Summary functions are allowed, but with a warning that the
    request to do so is really a bit silly.  In such cases the
    polynomials revert to simple numeric vectors of coefficients.
 
 9. Polynomials may be evaluated at specific x values either
    directly using poly.value(p, x), or indirectly using
    as.function(p), which creates an S function to evaluate the
    polynomial, and then using the result.  

    The latter is the preferred way if, for example, the
    polynomials have to be used in a model formula, since the
    prediction method for lm(), say, will then work properly with
    it.
 
10. The print method for polynomials can be slow and is a bit
    pretentious.  The plotting methods (plot(p), lines(p),
    points(p)) are fairly nominal, but may prove useful.

Examples:

1.  Find the Hermite polynomials up to degree 5 and plot them.
    Also plot their derivatives and integrals on separate plots. 

    The polynomials in question satisfy He(0) = 1, He(1) = x,
    He(n) = x * He(n) - (n - 1) * He(n-1), n = 2, 3, ...

> He <- list(polynomial(1), polynomial(0:1))
> for (n in 3:6) He[[n]] <- 0:1 * He[[n-1]] - (n-2) * He[[n-2]]
> He
[[1]]:
1 

[[2]]:
x 

[[3]]:
-1 + x^2 

[[4]]:
-3*x + x^3 

[[5]]:
3 - 6*x^2 + x^4 

[[6]]:
15*x - 10*x^3 + x^5 

> motif() 		# or win.graph() or ...

> plot(He[[6]])
> for(n in 1:5) lines(He[[n]], lty=n)

> plot(deriv(He[[6]]))
> for(n in 1:5) lines(deriv(He[[n]]), lty=n)

> plot(integral(He[[6]]))
> for(n in 1:5) lines(integral(He[[n]]), lty=n)

_________________________________________________________________

2.  Find the orthogonal polynomials on x = (0, 1, 2, 4) and
    construct S functions to evaluate them at arbitrary x values.

> x <- c(0,1,2,4)
> op <- poly.from.values(x, poly(x, 3))  # OR op <- poly.orth(x)
> op
$P1:
-0.59161 + 0.33806*x 

$P2:
0.56408 - 1.1684*x + 0.28204*x^2 

$P3:
-0.28604 + 3.1146*x - 2.5028*x^2 + 0.437*x^3 

> p1 <- as.function(op[[1]])
> p2 <- as.function(op[[2]])
> p3 <- as.function(op[[3]])
> p3
function(x, nam = as.character(x))
{
	val <- -0.286038776773678 + x * (3.11464445820227 + x * (
		-2.50283929676968 + x * (0.437003686737563)))
	if(!inherits(val, "polynomial"))
		names(val) <- nam
	val
}
> # as a check:
> cbind(P1 = p1(x), P2 = p2(x), P3 = p3(x))
           P1         P2          P3
0 -0.59160798  0.5640761 -0.28603878
1 -0.25354628 -0.3223292  0.76277007
2  0.08451543 -0.6446584 -0.57207755
4  0.76063883  0.4029115  0.09534626
> poly(x, 3)
               1          2           3 
[1,] -0.59160798  0.5640761 -0.28603878
[2,] -0.25354628 -0.3223292  0.76277007
[3,]  0.08451543 -0.6446584 -0.57207755
[4,]  0.76063883  0.4029115  0.09534626

[excess output deleted for simplicity]

_________________________________________________________________

3. Miscelaneous computations using polynomial arithmetic.

> p1 <- poly.from.zeros(1:6)
> p1
720 - 1764*x + 1624*x^2 - 735*x^3 + 175*x^4 - 21*x^5 + x^6 

> p2 <- change.origin(p1, 3)
> p2
-12x + 4x^2 + 15x^3 - 5x^4 - 3x^5 + x^6 

> fp1 <- as.function(p1)            # An alternative way of 
> p2 <- fp1(polynomial(c(3,1)))     # changing origin but a
> p2				    # bit slower.
-12*x + 4*x^2 + 15*x^3 - 5*x^4 - 3*x^5 + x^6 

> poly.value(p1, 0:7)
   0 1 2 3 4 5 6   7 
 720 0 0 0 0 0 0 720

> poly.value(p2, 0:7)
 0 1 2 3   4    5     6     7 
 0 0 0 0 720 5040 20160 60480

> poly.value(p2, 0:7 - 3)
  -3 -2 -1 0 1 2 3   4 
 720  0  0 0 0 0 0 720

> p3 <- (p1 - 2 * p2)^2		# moderate arithmetic expression.
> p3
518400 - 2505600*x + 5354640*x^2 - 6725280*x^3 + 5540056*x^4 - 
3137880*x^5 + 1233905*x^6 - 328050*x^7 + 53943*x^8 - 4020*x^9 - 
145*x^10 + 30*x^11 + x^12 

> fp3 <- as.function(p3)           # should have 1, 2, 3 as zeros
> fp3(0:4)
      0 1 2 3       4 
 518400 0 0 0 2073600
> 
_________________________________________________________________
William Venables, Department of Statistics,  Tel.: +61 8 303 3026
The University of Adelaide,                  Fax.: +61 8 303 3696
South AUSTRALIA.     5005.   Email: Bill.Venables@adelaide.edu.au
