% Piecewise polynomial package
% programer: t. shores		date: 10/7/00
% description: this package contains a discussion of structures and routines
% for implementing a piecewise polynomial package suitable for Octave and
% Matlab. Because of incompatibilities, the data structures are kept simple
% and do not use all the features of either language.  One could add some of
% these features to the package.
% Matlab users will have to unpackage functions of this file into 
% separate function files for each of the routines below, while
% Octave users can simply read in this single file. 
% The basic structure, whether formally declared or not, is a piecewise
% polynomial (p.p.), represented by a PP structure. An instance of this
% structure named pp has the following properties (components): 
%	pp.order: the order of the p.p. approximation
%	pp.knum: the number of knots for this p.p.
%	pp.knots: a row vector of size pp.order, consisting of knots in
%		  strictly ascending order.
%	pp.coefs: a matrix of pp.knum - 1 rows,  pp.order columns, each row of
%		 which is an array of coefficients of a degree m-1 polynomial
%		 in the form [a_1, a_2, ... , a_m], where
%		 si(x) = a_1(x-xi)^(m-1) + ... + a_{m-1}(x-xi) + a_m.
%		 (This is the standard form for a "polynomial" vector in
%		 Matlab and Octave.  Thus, their builtin routines like
%		 polyeval are available for use.)
% 
% Matlab and Octave handle objects rather differently.  Matlab has a full
% featured OOP environment with classes, objects with properties and methods,
% inheritance, operator overloading, etc. Octave has only C-style structures.
% Since our packages are on the simple side, we won't worry about these 
% details and simply use structures with parts, with our self-enforced 
% convention that PP structures have the properties given above. 
% Furthermore, we only do a little bit of bulletproofing.  It is advisable
% to use the PP constructor to ensure a correct PP structure.  Outside the 
% constructor one could, for example, define a PP structure with non-strictly 
% increasing knots vector. Results would then be unpredictable.
%
% not a function file
1;
%

function pp = PP(order,knum,knots,coefs)
% usage: pp = PP(order,knum,knots,coefs)
% description: given input of at least positive integers order and
% knum, this function constructs a PP object with this order and
% knum knots. Last two arguments are optional, but if entered,
% user must define knots before coefs. If these are not supplied,
% the  the coef matrix is initialized to zeros and knots to 0:knum-1.

% local variables:
% j: loop variable

if (nargin < 2 | nargin >4)
  disp('Incorrect paramater list')
  return;
end;
pp.order = order;
pp.knum = knum;
pp.knots = 0:(knum-1);
pp.coefs = zeros(knum-1,order);
if (nargin >= 3)
  if (sum([1,knum] ~=size(knots)))
    disp('Incorrect knots vector')
    clear pp;
    return;
  end;
  for j = 2:knum
    if (knots(j) <= knots(j-1))
      disp('Knots vector is not strictly increasing')
      clear pp;
      return;
    end;
  end;
  pp.knots = knots;
end;
if (nargin == 4)
  if (sum([knum-1,order] ~=size(coefs)))
    disp('Incorrect coefs matrix')
    clear pp;
    return;
  end;
  pp.coefs = coefs;
end;

endfunction % Matlab users must cut above this line for a function file

%

function y = PPeval(pp,x)
% usage: y = PPeval(pp,x)
% description:  given input pp a PP structure and x a vector of
% absicssas, this routine returns the vector of values of the PP
% structure pp at the coordinates of x.
 
% local variables: 
% j,k,m:  index variables
% n: size of node array
% klim: number of knots
% ord: order of pp
% xtmp: temporary variable

n = length(x);

y = zeros(size(x));
klim = pp.knum;
ord = pp.order;
for j = 1:n % loop over entries of x 
  xtmp = x(j); 
  % find index of interval containing entry
  for k = 1:klim-1
    if (xtmp <  pp.knots(k+1))
      break;
    end;
  end;
  dx = xtmp - pp.knots(k);
  % use Horner's method to evaluate the polynomial
  y(j) = pp.coefs(k,1);
  for m = 2:ord
    y(j) = dx*y(j)+pp.coefs(k,m);
  end;
end;

endfunction % Matlab users must cut above this line for a function file

%

function dd = PPder(pp)
% usage: dd = PPder(pp)
% description: given input pp a PP structure, this function
% returns the PP structure representing the derivative of the p.p.
% function represented by pp.

% local variables: 
% j,k:  index variables
% klim: number of knots
% ord: order of pp

dd.knum = pp.knum;
dd.knots = pp.knots;
dd.order =pp.order -1;
dd.coefs = zeros(dd.knum -1, dd.order);
deg = pp.order - 1;
for j = 1:dd.knum-1
  for k = 1:dd.order
    dd.coefs(j,k) = (deg-k+1)*pp.coefs(j,k);
  end;
end;

endfunction % Matlab users must cut above this line for a function file

%

function pp = LCpp(x,f) 
% usage: pp = LCpp(x,f) 
% description:  this function returns a PP structure representing
% the Lagrange cubic p.p. with knot row vector consisting of 
% the first entry and every subsequent third entry of x, counting
% from the second entry of x.  It is understood that the ordered
% node row vector x includes the intermediate nodes needed for
% Lagrange interpolation and that these are not counted as knots.
% Therefore, x and f must have length 3*k+1 where  
%     k = number of knots -1.

% local variables: 
% n,m: size of node and value arrays
% j,knotindex:  index variables

n = length(x);
m= size(f);
m = m(1)+m(2)-1;
if (m ~= n) % error check for size and order
  disp('Size mismatch of abscissa and ordinate');
  return;
end;
if (rem(n,3)~=1)
  disp('Abscissa size is not 3*k+1');
  return;
end;
for j = 2:n
  if (x(j) <= x(j-1))
    disp('Nodes vector is not strictly increasing')
    return;
  end;
end;
% size ok, construct PP structure
pp = PP(4, round((n-1)/3)+1, x(1:3:n));
% determine the poly coefs in each knot subinterval
knotindex = 1;
for j = 1:3:(n-1)
  % set up and solve the system on this subinterval
  pp.coefs(knotindex,:) = (vander(x(j:j+3) - x(j))\f(j:j+3)')'; 
  knotindex = knotindex+1;
end;

endfunction % Matlab users must cut above this line for a function file.

%

function pp = HCpp(x,f,fp) 
% usage: pp = HCpp(x,f,fp) 
% description: this function returns a PP structure representing
% the Hermite cubic p.p. with node row vector x, corresponding
% function values in the row vector f and derivative values in the
% row vector fp.


% local variables: 
% n,m: size of node and value arrays
% j: index variable
% h: stepsize

n = length(x);
m= size(f);
m = m(1)+m(2)-1;
if (m ~= n) % error check for size and order
  disp('Size mismatch of abscissa and function ordinate');
  return;
end;
m = length(fp);
if (m ~= n) % error check for size and order
  disp('Size mismatch of abscissa and derivative ordinate');
  return;
end;
pp = PP(4, n, x);
% determine the poly coefs in each knot subinterval
for j = 1:(n-1)
  % set up and solve the system on this subinterval
  h = x(j+1) - x(j);
  pp.coefs(j,:) = ([0, 0, 0, 1; h^3, h^2, h, 1; 0 0 1 0;
3*h^2, 2*h, 1, 0] \ [f(j); f(j+1); fp(j); fp(j+1)] )';  
end;

endfunction % Matlab users must cut above this line for a function file.

%

function pp = CCSpp(x,f,fpl,fpr)
% usage: pp = CCSpp(x,f,fpl,fpr)
% description:  this function returns the clamped cubic spline
% structure with knots at the entries of the row vector of abscissas
% x and corresponding ordinates in the row vector f. The scalars
% fpl and fpr are values of f'(x) at left and right endpoints of x.

% local variables:
% n,m: size of node and value arrays
% j: index variable
% h: stepsize vector
% d: divided difference vector
% Coef: coefficient matrix for the system
% Rhs: right hand side vector
% M: vector of second derivative values
% coefs: coefficient matrix of PP structure to be returned

n = length(x);
m= length(f);
if (m ~= n) % error check for size and order
  disp('Size mismatch of abscissa and function ordinate');
  return;
end;
% construct the stepsize and divided difference vectors
h = x(2:n) - x(1:n-1);
d = (f(2:n) - f(1:n-1))./h;
% now set up the system as in Atkinson, p. 169, row by row
Coef = zeros(n,n); 
Rhs = zeros(n,1);
% first row
Coef(1, 1:2) = [h(1)/3, h(1)/6];
Rhs(1) = d(1) - fpl;
for j = 2:(n-1) % interior rows
  Coef(j, (j-1):(j+1)) = [h(j-1)/6, (h(j-1)+h(j))/3, h(j)/6];
  Rhs(j) = d(j) - d(j-1);
end;
% last row
Coef(n, (n-1):n) = [h(n-1)/6, h(n-1)/3]; 
Rhs(n) = fpr - d(n-1);
% solve for M
M = Coef\Rhs;
% finally construct the PP structure to be returned
coefs = zeros(n-1,4);
% given si(x)=ai*(x-xi)^3+bi*(x-xi)^2+ci*(x-xi)+di 
% we deduce from si''(x)=6*ai*(x-xi)+2*bi that
% bi = Mi/2 and ai = (M(i+1)-Mi)/(6*hi)
coefs(:,2) = M(1:n-1)/2;
coefs(:,1) = (M(2:n) - M(1:n-1))./(6*h');
% evaulation gives di = yi and ci = (y(i+1) - ai*hi^3-bi*hi^2-di)/hi
coefs(:,4) = f(1:n-1)';
coefs(:,3) = (f(2:n)' - coefs(:,1).*h'.^3 - coefs(:,2).*h'.^2 - coefs(:,4)) ./ h';
pp = PP(4,n,x,coefs);

endfunction % Matlab users must cut above this line for a function file.

%

function pp = NKCSpp(xx,ff)
% usage: pp = NKCSpp(x,f)
% description: this function returns the not-a-knot cubic spline
% with nodes at the entries of the row vector of abscissas x and
% corresponding ordinates in the row vector f, excluding the 
% second and penultimate entries of each.

% local variables:
% x,f: vectors with not-a-knot entries removed
% nn,m,n: size of node and value arrays
% j: index variable
% h: stepsize vector
% d: divided difference vector
% Coef: coefficient matrix for the system
% Rhs: right hand side vector
% h0, h1, hnm2, hnm1: extra step sizes
% M: vector of second derivative values
% coefs: coefficient matrix of PP structure to be returned

nn = length(xx);
m= length(ff);
if (m ~= nn | nn<4)  % error check for size and order
  disp('Size mismatch of abscissa and function ordinate or size problem');
  return;
end;
x = [xx(1), xx(3:nn-2), xx(nn)];
f = [ff(1), ff(3:nn-2), ff(nn)];
n = nn - 2; % adjust size to reflect size of x
% construct the stepsize and divided difference vectors
h = x(2:n) - x(1:n-1);
d = (f(2:n) - f(1:n-1))./h;
hh = xx(2:nn) - xx(1:nn-1);
% now set up the system as in Atkinson, p. 169, row by row
Coef = zeros(n,n); 
Rhs = zeros(n,1);
% first row (use equation of exercise 3.34)
Coef(1, 1:2) = [hh(1)+2*hh(2), 2*hh(1)+hh(2)];
Rhs(1) = 6*(ff(1)/hh(1) - h(1)*ff(2)/(hh(1)*hh(2)) + ff(3)/hh(2));
for j = 2:(n-1) % interior rows
  Coef(j, (j-1):(j+1)) = [h(j-1)/6, (h(j-1)+h(j))/3, h(j)/6];
  Rhs(j) = d(j) - d(j-1);
end;
% last row (use equation of exercise 3.34)
Coef(n, (n-1):n) = [hh(nn-2)+2*hh(nn-1), 2*hh(nn-2)+hh(nn-1)]; 
Rhs(n) = 6*(ff(nn-2)/hh(nn-2) - h(n-1)*ff(nn-1)/(hh(nn-2)*hh(nn-1)) + ff(nn)/hh(nn-1));
% solve for M
M = Coef\Rhs;
% finally construct the PP structure to be returned
coefs = zeros(n-1,4);
% given si(x)=ai*(x-xi)^3+bi*(x-xi)^2+ci*(x-xi)+di 
% we deduce from si''(x)=6*ai*(x-xi)+2*bi that
% bi = Mi/2 and ai = (M(i+1)-Mi)/(6*hi)
coefs(:,2) = M(1:n-1)/2;
coefs(:,1) = (M(2:n) - M(1:n-1))./(6*h');
% evaulation gives di = yi and ci = (y(i+1) - ai*hi^3-bi*hi^2-di)/hi
coefs(:,4) = f(1:n-1)';
coefs(:,3) = (f(2:n)' - coefs(:,1).*h'.^3 - coefs(:,2).*h'.^2 - coefs(:,4)) ./ h';
pp = PP(4,n,x,coefs);


endfunction % Matlab users must cut above this line for a function file.

%

function pp = NCSpp(x,f)
% usage: pp = NCSpp(x,f)
% description: this function returns the natural cubic spline
% with nodes at the entries of the row vector of abscissas x and
% corresponding ordinates in the row vector f. 

% local variables:
% n,m: size of node and value arrays
% j: index variable
% h: stepsize vector
% d: divided difference vector
% Coef: coefficient matrix for the system
% Rhs: right hand side vector
% M: vector of second derivative values
% coefs: coefficient matrix of PP structure to be returned

n = length(x);
m= length(f);
if (m ~= n) % error check for size and order
  disp('Size mismatch of abscissa and function ordinate');
  return;
end;
% construct the stepsize and divided difference vectors
h = x(2:n) - x(1:n-1);
d = (f(2:n) - f(1:n-1))./h;
% now set up the system as in Atkinson, p. 169, row by row
Coef = zeros(n,n); 
Rhs = zeros(n,1);
% first row
Coef(1, 1) = 1;
Rhs(1) = 0;
for j = 2:(n-1) % interior rows
  Coef(j, (j-1):(j+1)) = [h(j-1)/6, (h(j-1)+h(j))/3, h(j)/6];
  Rhs(j) = d(j) - d(j-1);
end;
% last row
Coef(n, n) = 1; 
Rhs(n) = 0;
% solve for M
M = Coef\Rhs;
% finally construct the PP structure to be returned
coefs = zeros(n-1,4);
% given si(x)=ai*(x-xi)^3+bi*(x-xi)^2+ci*(x-xi)+di 
% we deduce from si''(x)=6*ai*(x-xi)+2*bi that
% bi = Mi/2 and ai = (M(i+1)-Mi)/(6*hi)
coefs(:,2) = M(1:n-1)/2;
coefs(:,1) = (M(2:n) - M(1:n-1))./(6*h');
% evaulation gives di = yi and ci = (y(i+1) - ai*hi^3-bi*hi^2-di)/hi
coefs(:,4) = f(1:n-1)';
coefs(:,3) = (f(2:n)' - coefs(:,1).*h'.^3 - coefs(:,2).*h'.^2 - coefs(:,4)) ./ h';
pp = PP(4,n,x,coefs);

endfunction % Matlab users must cut above this line for a function file.

function pp = RNCSpp(x,a,alp)
% usage: pp = RNCSpp(x,a,alp)
% description: this function returns the regularized natural 
% cubic spline with nodes at the entries of the vector
% of abscissas x and corresponding ordinates in the 
% vector a. Here alp is  the regularization parameter.
% Given that the unregularized system is R*c=Q*a, where c is
% the column of moments (2nd deriv values), with 
% both R and Q tridiagonal and diagonally dominant, 
% the regularized system is 
%             (R + alp*Q*Q^{T}*R^{-1})*c = Q*a
% which is equivalent to the interpolating natural cubic 
% spline when alp=0. 

% local variables:
% n,m: size of node and value arrays
% j: index variable
% h: stepsize vector
% R: coefficient matrix for the system
% Q: coefficient matrix for right hand side vector
% c: vector of second derivative values
% coefs: coefficient matrix of PP structure to be returned

% columnize the input data
[m,n]=size(x);
if (n > 1)
    x = x';
end;
[m,n] = size(a);
if (n > 1)
    a = a';
end;
n = length(x);
m= length(a);
if (m ~= n) % error check for size and order
  disp('Size mismatch of abscissa and function ordinate');
  return;
end;
% construct the stepsize and divided difference vectors
h = x(2:n) - x(1:n-1);
% now set up the system as in my notes along lines of de Boor
R = zeros(n,n);
Q = R;
% first row
R(1,1) = 1;
Q(1,1) = 1;
for j = 2:(n-1) % interior rows
  R(j,(j-1):(j+1)) = [h(j-1), (h(j-1)+h(j))*2, h(j)];
  Q(j,(j-1):(j+1)) = [3/h(j-1), -3/h(j-1)-3/h(j), 3/h(j)];
end;
% last row
R(n,n) = 1;
Q(n,n) = 1;
% fix R to be symmetric
R(2,1) = 0;
R(n-1,n) = 0;
% solve for c
c = (R+alp*Q*Q'*inv(R))\(Q*a);
% find new nodes for spline
a = Q\(R*c);
% fix first and last entries, currently spline values at end nodes
c(1) = 0;
c(n) = 0;
% finally construct the PP structure to be returned
coefs = zeros(n-1,4);
% given si(x)=ai+bi*(x-xi)+ci*(x-xi)^2+di*(x-xi)^3 
% we deduce from si''(x)=6*di*(x-xi)+2*ci that
% ai = (ci(+1)-ci)/(3*hi)
coefs(:,2) = c(1:n-1);
coefs(:,1) = (c(2:n) - c(1:n-1))./(3*h);
% evaulation gives ai = yi and bi = (y(i+1) - di*hi^3-ci*hi^2-ai)/hi
coefs(:,4) = a(1:n-1);
coefs(:,3) = (a(2:n) - coefs(:,1).*h .^3 - coefs(:,2).*h .^2 - ...
    coefs(:,4)) ./ h;
pp = PP(4,n,x',coefs);

endfunction % Matlab users must cut above this line for a function file.

%
% the following functions are not really specific to p.p. fcns, but are useful.
% 

function [y,x] = MaxNorm(f_f,a,b,n)
% usage: [y,x] = MaxNorm(fcn,a,b,n) or y=MaxNorm(fcn,a,b,n)
% description: returns the max norm y of the function f(x) on the
% interval a<=x<=b based on a uniform sample of n points and
% returns the first point x where the max occurs. Observe that the
% function name fcn is a string, so must be quoted.

% local variables:
% h: stepsize for sampling
% xc: current sample point
% fxc: abs of function value at current sample point
% j: index variable

xc = a;
x = a;
y = 0;
h = (b-a)/(n-1);
for j=1:n
  fxc = abs(feval(f_f,xc));
  if (y < fxc)
    y = fxc;
    x = xc;
  end;
  xc = xc + h;
end;

endfunction % Matlab users must cut above this line for a function file.

%

function retval = GaussInt(fn_,nodes,n)
% usage retval = GaussInt('fcn',nodes)
% description:  inputs a function of one variable fcn and ordered
% vector nodes and returns an approximate value for the
% definite integral of fcn(x) on the interval enclosing the
% coordinates of nodes, calculated by using a n-point  Gaussian
% quadrature method between adjacent nodes. The function fcn
% must handle vector arguments correctly: evaluate each coordinate.
% Only 2<=n<=8 are implemented.  This routine is useful for integrating 
% pp functions exactly. E.g., for cubic splines use spline nodes and n=3.

% local variables:
% xabsc: Gaussian abscissas 
% t: translates of Gaussian abscissas and vals
% wts: Gaussian weights
% h: width of subinterval of Gauss integration
% k: index variables

switch n %decide on size of interpolation
  case 2
    xabsc = [ -0.577350269189626, 0.577350269189626];
    wts = [ 1.0, 1.0 ];
  case 3
    xabsc = [ -0.774596669241483 0 0.774596669241483 ];
    wts = [ 0.555555555555556 0.888888888888889 0.555555555555556 ];
  case 4
    xabsc = [  -0.861136311594052 -0.339981043584856 0.339981043584856 \
0.861136311594052]; 
wts = [  0.347854845137454 0.652145154862546 \ 
0.652145154862546 0.347854845137454];
  case 5
    xabsc = [ -0.9061798459386639928, -0.5384693101056830910, \ 
0.,0.5384693101056830910, 0.9061798459386639928];      
    wts = [0.236926885056189, 0.4786286704993667, \
          0.5688888888888887, 0.4786286704993664, \
          0.2369268850561892];
  case 6
    xabsc = [-0.932469514203151 -0.661209386466264 -0.238619186083197 \
0.238619186083197 0.661209386466264 0.932469514203151];
    wts = [  0.17132449237917 0.360761573048138 0.467913934572691 \	
0.467913934572691 0.360761573048138 0.17132449237917];
  case 7
    xabsc = [-0.949107912342758 -0.741531185599394 -0.405845151377397 0 \
0.405845151377397 0.741531185599394 0.949107912342758];
    wts = [ 0.129484966168879 0.279705391489274 0.381830050505119 \
0.417959183673469 0.381830050505119 0.279705391489274 0.129484966168879];
  case 8
    xabsc = [  -0.960289856497535 -0.796666477413627 -0.52553240991633 \
-0.183434642495649 0.183434642495649 \
0.52553240991633 0.796666477413627 0.960289856497535];
    wts = [ 0.101228536290382 0.22238103445337 0.313706645877889 \
0.362683783378362 0.362683783378362 \
0.313706645877889 0.22238103445337 0.101228536290382];
  otherwise
    disp('This node number is not supported');
    return;
end
retval = 0;
nn = length(nodes);
for k = 1:nn-1
  t = nodes(k) + (nodes(k+1)-nodes(k))/2*(xabsc+1);
  t = feval(fn_,t);
  retval = retval + (nodes(k+1)-nodes(k))/2*wts*t';
end;

endfunction % Matlab users must cut above this line for a function file.

%





