/*
* Roby Joehanes
*
* Copyright 2007 Roby Joehanes
* This file is distributed under the GNU General Public License version 3.0.
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package jdistlib.math.spline;
import static java.lang.Math.*;
import static java.util.Arrays.fill;
import static jdistlib.math.LinPack.dpbfa;
import static jdistlib.math.LinPack.dpbsl;
import static jdistlib.math.Constants.kInvGoldRatio;
import static jdistlib.math.Constants.M_LN2;
import static jdistlib.math.spline.SmoothSplineCriterion.*;
/**
* This class deals with smoothing of cubic B-Splines. The algorithm is explained
* in the following paper:
*
*
Silverman, B. W. (1985) Some aspects of the spline smoothing approach to
* non-parametric regression curve-fitting. Journal of the Royal Statistical Society.
* Series B (Methodological), 47(1): 1--52.
* JSTOR link: http://www.jstor.org/stable/2345542
*
*
And the comments of Finbarr O'Sullivan at page 39--40 of the paper above.
*
*
The code is in FORTRAN 77 and it is readily available in Netlib:
* sbart.f in GCV
* package.
*
*
This code uses the idea explained in the following paper:
*
*
de Boor, C. (1977) Package for calculating with B-Splines, SIAM Journal on
* Numerical Analysis, 14(3): 441--472.
* JSTOR link: http://www.jstor.org/stable/2156696
*
*
De Boor's code is also in FORTRAN 77 and it is also readily available in Netlib as
* PPPack package.
*
*
De Boor uses LINPACK for Cholesky decomposition and solver for band matrices (dpbfa and dpbsl).
* LINPACK is also available in Netlib here:
* http://www.netlib.org/linpack/
*
*
All routines above are available as public domain. They are also included in
* R, a popular statistical framework. In R, some
* routines are translated into C and hand-polished. The codes included in R is in GPL.
* My translation is also in GPL.
*
*
Relevant methods:
*
* - fit = Essentially a call akin to R's smooth.spline. Returns a
* SmoothSplineResult object that has all outputs you need for prediction.
* - predict = Essentially like R's predict.smooth.spline. Returns
* the predicted value of the B-Spline
*
*
* Here's what's different here from the original code and/or R:
*
* - In the original codes, some of the routines are in single precision (real). In R,
* they are already converted to double precision. The ones in here are all in double-precision.
* - The original codes and R's code contains a questionable constant value, namely 0.3330
* (in sgram). The original paper states that it should be 1/3. So, I used that instead. (See kAThird)
* Inevitably, the output of the original code and R loses some precision. Note that
* the output starts to differ only at the fourth or fifth decimal place.
* - Removed superfluous parameters. FORTRAN 77 doesn't allow dynamic arrays. So, all
* intermediary / buffer arrays have to be passed from the caller. This leads to code
* ugliness. I created arrays just where needed without seriously affecting the
* performance. Also, array lengths / dimensions are no longer passed here since Java allows
* .length for all arrays.
* - Some matrices are better put as double-dimension arrays (most notably sg0, sg1, sg2, sg3,
* which presents Sigma matrix, and hs0, hs1, hs2, hs3, which represents X'WX matrix)
* - Some loop unrolling is reversed (most notably in sgram). Some of the loops are unrolled
* to avoid costly if() statements (e.g. in sinerp). Some are left intact (e.g. in stxwx)
* - I changed the derivative array to ROW major. FORTRAN uses column major array convention
* and it's a pain to pass the array around.
* - Variable / parameter / method renaming. FORTRAN 77's harsh 6-character names no longer
* applies in Java. So, I put better names around. But I keep the original comments intact,
* unless it's either superfluous or no longer applies.
* - I uses R's default for epsilon, tolerance, the lower- and upper-bounds for smoothing
* parameter search, and the maximum number of iterations.
*
*
* Here's the gotchas:
*
* - I have tested this routine quite a lot, but far from extensively. I think it should
* be relatively free of bugs / errors. But, let me know if you do find some discrepancies.
* - Also, despite the testing, some branches of the code have never gotten tested. I put some
* markers at the code. I've never encountered errors so far.
* - Note that the original comment is still intact. The call parameters are different
* now since some have been deleted.
*
*
* @author Roby Joehanes
*
*/
public class SmoothSpline
{
public static final double
kDefaultTolerance = 1e-4, // Taken from R
kDefaultEpsilon = 2e-8, // Taken from R
kDefaultSmoothingParamLowerBound = -1.5, // Taken from R
kDefaultSmoothingParamUpperBound = 1.5; // Taken from R
public static final int kDefaultMaxNumIterations = 500; // Taken from R
private static final double
kLog2Of1_4 = log(1.4) / M_LN2,
kLog2Of1_42 = log(10.0/7) / M_LN2,
kstxwx_eps = 1e-10, // I don't know why O'Sullivan used different epsilon values?
ksslvrg_eps = 1e-11,
kBigValue = 1e100,
kAThird = 1.0/3.0; // The original code and R use 0.3330f instead
private static final int kInsertionTreshold = 4;
private static final int calcNumInnerKnots(int n)
{
if (n < 50)
return n;
if (n < 200)
return (int) floor(50 * pow(2, (n-50)/150.0));
if (n < 800)
return (int) floor(100 * pow(2, kLog2Of1_4 * (n-200)/600.0) );
if (n < 3200)
return (int) floor(140 * pow(2, kLog2Of1_42 * (n-800)/2400.0) );
return (int) floor(200 + pow(n - 3200, 0.2));
}
/**
* Form a knot. Original name: sknotl
* @param x
* @return
*/
private static final double[] formKnots(double[] x)
{
int
n = x.length,
nk = calcNumInnerKnots(n),
nMin1 = n - 1,
nkMin1 = nk - 1;
double[] result = new double[nk + 6];
result[0] = result[1] = result[2] = x[0];
result[nk+3] = result[nk+4] = result[nk+5] = x[nMin1];
for (int i = 0; i < nk; i++)
result[i + 3] = x[(i * nMin1) / nkMin1];
return result;
}
/**
* A Cubic B-spline Smoothing routine.
*
* @author Finbarr O'Sullivan
*
* Ported from Fortran by Roby Joehanes. Original routine name: sbart
*
*
The algorithm minimises:
*
* (1/n) * sum ws(i)^2 * (ys(i)-sz(i))^2 + lambda* int ( s"(x) )^2 dx
*
* lambda is a function of the spar which is assumed to be between 0 and 1
*
* INPUT
* -----
* penalt A penalty > 1 to be used in the gcv criterion
* dofoff either `df.offset' for GCV or `df' (to be matched).
* n number of data points
* ys(n) vector of length n containing the observations
* ws(n) vector containing the weights given to each data point
* NB: the code alters the values here.
* xs(n) vector containing the ordinates of the observations
* ssw `centered weighted sum of y^2'
* nk number of b-spline coefficients to be estimated nk <= n+2
* knot(nk+4) vector of knot points defining the cubic b-spline basis.
* To obtain full cubic smoothing splines one might
* have (provided the xs-values are strictly increasing)
* spar penalised likelihood smoothing parameter
* ispar indicating if spar is supplied (ispar=1) or to be estimated
* lspar, uspar lower and upper values for spar search; 0.,1. are good values
* tol, eps used in Golden Search routine
* isetup setup indicator [initially 0
* icrit indicator saying which cross validation score is to be computed
* 0: none ; 1: GCV ; 2: CV ; 3: 'df matching'
* ld4 the leading dimension of abd (ie ld4=4)
* ldnk the leading dimension of p2ip (not referenced)
*
*
* OUTPUT
* ------
* coef(nk) vector of spline coefficients
* sz(n) vector of smoothed z-values
* lev(n) vector of leverages
* crit either ordinary or generalized CV score
* spar if ispar != 1
* lspar == lambda (a function of spar and the design)
* iter number of iterations needed for spar search (if ispar != 1)
* ier error indicator
* ier = 0 ___ everything fine
* ier = 1 ___ spar too small or too big problem in cholesky decomposition
*
* Working arrays/matrix
* xwy X'Wy
* hs0,hs1,hs2,hs3 the diagonals of the X'WX matrix
* sg0,sg1,sg2,sg3 the diagonals of the Gram matrix SIGMA
* abd (ld4,nk) [ X'WX + lambda*SIGMA ] in diagonal form
* p1ip(ld4,nk) inner products between columns of L inverse
* p2ip(ldnk,nk) all inner products between columns of L inverse where L'L = [X'WX + lambda*SIGMA] NOT REFERENCED
*/
private static final SmoothSplineResult fitCubicSpline (double penalty, double dfOffset,
double[] xs, double[] ys, double[] ws, double ssw, double[] knots,
SmoothSplineCriterion criterion, double spar, double lspar,
double uspar, double tol, double eps, int maxIteration)
{
int
n = xs.length,
nk = knots.length - 4;
boolean hasError = false;
assert (n == ys.length && n == ws.length); // sanity check
SmoothSplineResult result = new SmoothSplineResult();
result.mCoefficients = new double[nk];
result.mSmoothedValues = new double[n];
result.mLeverage = new double[n];
result.mEstimatedDF = dfOffset;
result.mCriterion = criterion;
result.mKnots = knots;
double
ratio = 1,
sigma[][] = null,
xwy[] = null,
XtWX[][] = null;
// Compute SIGMA, X' W X, X' W z, trace ratio, s0, s1.
// SIGMA -> sg0,sg1,sg2,sg3
// X' W X -> hs0,hs1,hs2,hs3
// X' W Z -> xwy
// trevor fixed this 4/19/88
// Note: sbart, i.e. stxwx() and sslvrg() {mostly, not always!}, use
// the square of the weights; the following rectifies that
double[] newWs = new double[n];
for (int i = 0; i < n; ++i)
if (ws[i] > 0.)
newWs[i] = sqrt(ws[i]);
// SIGMA[i,j] := Int B''(i,t) B''(j,t) dt {B(k,.) = k-th B-spline}
sigma = calculateSigma(knots);
XtWX = calculateXtWX(xs, ys, newWs, knots);
xwy = XtWX[4];
double[][] newHs = new double[4][];
for (int i = 0; i < 4; i++)
newHs[i] = XtWX[i];
XtWX = newHs;
{
// Compute ratio := tr(X' W X) / tr(SIGMA)
double
t1 = 0,
t2 = 0;
for (int i = 3 - 1; i < (nk - 3); ++i)
{
t1 += XtWX[0][i];
t2 += sigma[0][i];
}
ratio = t1 / t2;
}
boolean isSparSupplied = !(Double.isNaN(spar) || Double.isInfinite(spar));
if (isSparSupplied) { // Value of spar supplied
result.mSmoothingParameter = spar;
result.mLambda = ratio * pow(16, result.mSmoothingParameter * 6 - 2);
result.mHasFactorizationProblems = calculateCVScore(result, penalty, xs, ys, newWs, ssw, knots, criterion, xwy, XtWX, sigma);
return result;
}
// ELSE ---- spar not supplied --> compute it !
result.mIterNo = 0;
double
a = lspar,
b = uspar,
v = a + kInvGoldRatio * (b - a),
w = v,
x = v,
e = 0;
result.mSmoothingParameter = x;
result.mLambda = ratio * pow(16, result.mSmoothingParameter * 6 - 2);
result.mHasFactorizationProblems = calculateCVScore(result, penalty, xs, ys, newWs, ssw, knots, criterion, xwy, XtWX, sigma);
double
fx = result.mFitCVScore,
fv = fx,
fw = fx,
d = 0;
//boolean
// Fparabol = false,
// tracing = ispar < 0;
while (!hasError)
{
double
xm = (a + b) * .5,
tol1 = eps * abs(x) + tol / 3.0,
tol2 = tol1 * 2.0;
++result.mIterNo;
if (abs(x - xm) <= tol2 - (b - a) * .5 || result.mIterNo > maxIteration)
break;
// is golden-section necessary?
boolean isGoldenSect = abs(e) <= tol1 ||
// if had Inf then go to golden-section
fx >= kBigValue || fv >= kBigValue || fw >= kBigValue;
// Fit Parabola
if (!isGoldenSect)
{
double
r = (x - w) * (fx - fv),
q = (x - v) * (fx - fw),
p = (x - v) * q - (x - w) * r;
q = (q - r) * 2.;
if (q > 0.)
p = -p;
q = abs(q);
r = e;
e = d;
// is parabola acceptable? Otherwise do golden-section
isGoldenSect = abs(p) >= abs(.5 * q * r) || q == 0.
|| p <= q * (a - x) || p >= q * (b - x);
// Parabolic Interpolation step
if (!isGoldenSect)
{
d = p / q;
double u = x + d;
// f must not be evaluated too close to ax or bx
if (u - a < tol2 || b - u < tol2)
d = xm >= x ? abs(tol1) : - abs(tol1);
}
}
// a golden-section step
if (isGoldenSect)
{
e = x >= xm ? a - x : b - x;
d = kInvGoldRatio * e;
}
// f must not be evaluated too close to x
double u = x + ((abs(d) >= tol1) ? d : (d >= 0 ? abs(tol1) : -abs(tol1)));
result.mSmoothingParameter = u;
result.mLambda = ratio * pow(16, result.mSmoothingParameter * 6 - 2);
hasError = calculateCVScore(result, penalty, xs, ys, newWs, ssw, knots, criterion, xwy, XtWX, sigma);
double fu = result.mFitCVScore;
// update a, b, v, w, and x
if (fu <= fx) {
if (u >= x) a = x; else b = x;
v = w; fv = fw;
w = x; fw = fx;
x = u; fx = fu;
}
else {
if (u < x) a = u; else b = u;
if (fu <= fw || w == x) {
v = w; fv = fw;
w = u; fw = fu;
} else if (fu <= fv || v == x || v == w) {
v = u; fv = fu;
}
}
}
result.mSmoothingParameter = x;
result.mFitCVScore = fx;
result.mHasFactorizationProblems = hasError;
double sum = 0;
for (int i = 0; i < n; i++)
sum += result.mLeverage[i];
result.mEstimatedDF = sum;
sum = 0;
for (int i = 0; i < n; i++)
{
double temp = ys[i] - result.mSmoothedValues[i];
sum += ws[i] * temp * temp;
}
result.mPenalizedCriterion = sum;
return result;
}
/**
* Calculation of the cubic B-spline smoothness prior for "usual" interior knot setup.
*
sgm[0-3](nb) Symmetric matrix whose (i,j)'th element contains the integral of
* B"(i,.) B"(j,.) , i=1,2 ... nb and j=i,...nb.
* Only the upper four diagonals are computed.
*
*
Original subroutine name: sgram
*
* @param tb
*/
private static final double[][] calculateSigma(double[] tb)
{
int
nb = tb.length - 4,
ileft = 0;
double
sg[][] = new double[4][nb],
yw1[] = new double[4],
yw2[] = new double[4],
wpt,
vnikx[][] = new double[3][4];
for (int i = 0; i < nb; ++i)
{
// Calculate a linear approximation to the
// second derivative of the non-zero B-splines
// over the interval [tb(i),tb(i+1)].
ileft = determineInterval(tb, tb[i], ileft, null);
// Left end second derivatives
calculateBSplineDerivatives(tb, tb[i], ileft, vnikx);
// Put values into yw1
for (int ii = 0; ii < 4; ++ii)
yw1[ii] = vnikx[2][ii];
// Right end second derivatives
calculateBSplineDerivatives(tb, tb[i + 1], ileft, vnikx);
ileft++;
// Slope*(length of interval) in Linear Approximation to B"
for (int ii = 0; ii < 4; ++ii)
yw2[ii] = vnikx[2][ii] - yw1[ii];
wpt = tb[i + 1] - tb[i];
// Calculate Contributions to the sigma vectors
int maxIdx = min(ileft, 4);
for (int ii = 0; ii < maxIdx; ++ii)
for (int jj = ii; jj < maxIdx; ++jj)
sg[jj-ii][ileft + ii - maxIdx] += wpt * (yw1[ii] * yw1[jj] +
(yw2[ii] * yw1[jj] + yw2[jj] * yw1[ii]) * 0.5 + yw2[ii] * yw2[jj] * kAThird);
}
return sg;
}
/**
* Calculate X'WX and X'WY. Original routine name: stxwx
* @param x
* @param y
* @param w
* @param knots
* @return
*/
private static final double[][] calculateXtWX(double[] x, double[] y, double[] w, double[] knots)
{
int
k = x.length,
n = knots.length - 4,
ileft;
// Local variables
int mflag[] = new int[1];
double
vnikx[] = new double[4],
dvnikx[][] = new double[][] { vnikx },
hs0[] = new double[n],
hs1[] = new double[n],
hs2[] = new double[n],
hs3[] = new double[n],
xwy[] = new double[n],
retval[][] = new double[][] { hs0, hs1, hs2, hs3, xwy };
// Compute X' W^2 X -> hs0,hs1,hs2,hs3 and X' W^2 Z -> y
// Note that here the weights w(i) == sqrt(wt[i]) where wt[] where original weights
ileft = 0;
for (int i = 0; i < k; ++i) {
ileft = determineInterval(knots, x[i], ileft, mflag);
if (mflag[0] == 1) {
if (x[i] <= knots[ileft] + kstxwx_eps)
ileft = n - 1;
else
return retval;
}
calculateBSplineDerivatives(knots, x[i], ileft, dvnikx);
int j = ileft - 3;
double
w_i = w[i],
z_i = y[i],
vnikx_i = vnikx[0],
temp = w_i * w_i * vnikx_i;
xwy[j] += temp * z_i;
hs0[j] += temp * vnikx_i;
hs1[j] += temp * vnikx[1];
hs2[j] +=temp * vnikx[2];
hs3[j] += temp * vnikx[3];
j++;
vnikx_i = vnikx[1];
temp = w_i * w_i * vnikx_i;
xwy[j] += temp * z_i;
hs0[j] += temp * vnikx_i;
hs1[j] += temp * vnikx[2];
hs2[j] += temp * vnikx[3];
j++;
vnikx_i = vnikx[2];
temp = w_i * w_i * vnikx_i;
xwy[j] += temp * z_i;
hs0[j] += temp * vnikx_i;
hs1[j] += temp * vnikx[3];
j++;
vnikx_i = vnikx[3];
temp = w_i * w_i * vnikx_i;
xwy[j] += temp * z_i;
hs0[j] += temp * vnikx_i;
}
return retval;
}
/**
* Purpose : Computes Inner Products between columns of L^{-1}
* where L = abd is a Banded Matrix with 3 subdiagonals
*
*
The algorithm works in two passes:
* Pass 1 computes (cj,ck) k=j,j-1,j-2,j-3 ; j=nk, .. 1
* Pass 2 computes (cj,ck) k <= j-4 (If flag == 1 ).
* A refinement of Elden's trick is used.
*
*
Original routine name: sinerp. Pass 2 is unused. So, I took it out
*
* @param abd Band matrix
* @return the inner product matrix
*/
private static final double[][] calculateInnerProduct(double[][] abd)
{
int
nk = abd[0].length,
i = nk - 1;
double
p1ip[][] = new double[abd.length][nk],
c0 = 1.0 / abd[3][i],
c1,c2,c3,
wjm1 = c0 * c0,
wjm21, wjm22, wjm31, wjm32, wjm33;
p1ip[3][i] = wjm1;
i--;
c0 = 1.0 / abd[3][i];
c3 = -abd[2][i+1] * c0;
wjm21 = wjm1;
wjm22 = p1ip[2][i] = c3 * wjm1;
wjm1 = p1ip[3][i] = c0*c0 + c3*c3 * wjm1;
i--;
c0 = 1.0 / abd[3][i];
c2 = -abd[1][i+2] * c0;
c3 = -abd[2][i+1] * c0;
wjm31 = wjm21;
wjm32 = wjm22;
wjm33 = p1ip[1][i] = c2 * wjm21 + c3 * wjm22;
p1ip[2][i] = c2 * wjm22 + c3 * wjm1;
p1ip[3][i] = c0*c0 + c2*c2 * wjm21 + 2*c2*c3 * wjm22 + c3*c3 * wjm1;
wjm21 = wjm1;
wjm22 = p1ip[2][i];
wjm1 = p1ip[3][i];
for (--i; i >= 0; i--)
{
c0 = 1.0 / abd[3][i];
c1 = -abd[0][i+3] * c0;
c2 = -abd[1][i+2] * c0;
c3 = -abd[2][i+1] * c0;
p1ip[0][i] = c1 * wjm31 + c2 * wjm32 + c3 * wjm33;
p1ip[1][i] = c1 * wjm32 + c2 * wjm21 + c3 * wjm22;
p1ip[2][i] = c1 * wjm33 + c2 * wjm22 + c3 * wjm1;
p1ip[3][i] = c0*c0 + c1*c1 * wjm31 + 2*c1*c2 * wjm32
+ 2*c1*c3 * wjm33 + c2*c2 * wjm21 + 2*c2*c3 * wjm22 + c3*c3 * wjm1;
wjm31 = wjm21;
wjm32 = wjm22;
wjm33 = p1ip[1][i];
wjm21 = wjm1;
wjm22 = p1ip[2][i];
wjm1 = p1ip[3][i];
}
return p1ip;
}
/**
* Estimate cross validation score
*
*
Original routine name: sslvrg
* @param result
* @param penalty
* @param x
* @param y
* @param w
* @param ssw
* @param knot
* @param criterion
* @param xwy
* @param hs
* @param sg
* @return
*/
private static final boolean calculateCVScore(SmoothSplineResult result, double penalty, double[] x,
double[] y, double[] w, double ssw, double[] knot, SmoothSplineCriterion criterion,
double[] xwy, double[][] hs, double[][] sg)
{
// Local variables
double
temp,
coef[] = result.mCoefficients,
sz[] = result.mSmoothedValues,
lev[] = result.mLeverage;
int
n = x.length,
nk = knot.length - 4,
mflag[] = new int[1];
double
dvnikx[][] = new double[1][4],
vnikx[] = dvnikx[0],
abd[][] = new double[4][nk];
// Purpose :
// Compute smoothing spline for smoothing parameter lambda
// and compute one of three `criteria' (OCV , GCV , "df match").
// See comments in ./sbart.f from which this is called
int ileft = 0;
if (hs != null && sg != null)
{
// compute the coefficients coef() of estimated smooth
int
nkMin1 = nk - 1,
nkMin2 = nk - 2,
nkMin3 = nk - 3;
double lambda = result.mLambda;
System.arraycopy(xwy, 0, coef, 0, nk);
abd[3][nkMin1] = hs[0][nkMin1] + lambda * sg[0][nkMin1];
abd[3][nkMin2] = hs[0][nkMin2] + lambda * sg[0][nkMin2];
abd[3][nkMin3] = hs[0][nkMin3] + lambda * sg[0][nkMin3];
abd[2][nkMin1] = hs[1][nkMin2] + lambda * sg[1][nkMin2];
abd[2][nkMin2] = hs[1][nkMin3] + lambda * sg[1][nkMin3];
abd[1][nkMin1] = hs[2][nkMin3] + lambda * sg[2][nkMin3];
for (int i = 0; i < nkMin3; ++i)
for (int j = 0; j < 4; ++j)
abd[3-j][i+j] = hs[j][i] + lambda * sg[j][i];
// factorize banded matrix abd:
int retVal = dpbfa(abd, 3);
if (retVal != 0)
return true;
// solve linear system (from factorize abd):
dpbsl(abd, 3, coef);
// Value of smooth at the data points
}
for (int i = 0; i < n; ++i)
// bvalue_(double[] t, double[] bcoef, double x, int jderiv)
// bvalue(t,lent,bcoef,n,k,x,jderiv)
// bvalue(knot,lenkno,coef, nk,4,xv,0)
sz[i] = evaluateBSplineDerivatives(knot, coef, x[i], 0);
// Compute the criterion function if requested
if (criterion == NO_CRITERION)
return false;
// --- Ordinary or Generalized CV or "df match" ---
// Get Leverages First
double[][] p1ip = calculateInnerProduct(abd);
for (int i = 0; i < n; ++i) {
double xv = x[i];
ileft = determineInterval(knot, xv, ileft, mflag);
if (mflag[0] == -1) {
ileft = 3;
xv = knot[3] + ksslvrg_eps;
} else if (mflag[0] == 1) {
ileft = nk - 1;
xv = knot[nk] - ksslvrg_eps;
}
calculateBSplineDerivatives(knot, xv, ileft, dvnikx);
int
j = ileft - 3,
jp1 = j+1,
jp2 = j+2;
double
b0 = vnikx[0],
b1 = vnikx[1],
b2 = vnikx[2],
b3 = vnikx[3],
wi = w[i];
lev[i] = (p1ip[3][j] * b0 * b0 + 2 * p1ip[2][j] * b0 * b1
+ 2 * p1ip[1][j] * b0 * b2 + 2 * p1ip[0][j] * b0 * b3
+ p1ip[3][jp1] * b1 * b1 + 2 * p1ip[2][jp1] * b1 * b2
+ 2 * p1ip[1][jp1] * b1 * b3 + p1ip[3][jp2] * b2 * b2
+ 2 * p1ip[2][jp2] * b2 * b3 + p1ip[3][ileft] * b3 * b3) * wi * wi;
}
// Evaluate Criterion
switch (criterion)
{
case GCV: // Generalized CV
{
double
rss = ssw,
df = 0,
sumw = 0.;
// w(i) are sqrt( wt[i] ) weights scaled such
// that sumw = number of observations with w(i) > 0
for (int i = 0; i < n; ++i) {
temp = (y[i] - sz[i]) * w[i];
rss += temp * temp;
df += lev[i];
temp = w[i];
sumw += temp * temp;
//System.out.println((i+1) + " ==> " + y[i] + sCm + rss + sCm + df + sCm + sumw);
}
temp = 1 - (result.mEstimatedDF + penalty * df) / sumw;
result.mFitCVScore = (rss / sumw) / (temp * temp);
}
break;
case CV: // Ordinary CV
{
double sum = 0;
for (int i = 0; i < n; ++i) {
temp = (y[i] - sz[i]) * w[i] / (1 - lev[i]);
sum += temp * temp;
}
result.mFitCVScore = sum / n;
}
break;
case DF_MATCH: // df matching
{
double sum = 0;
for (int i = 0; i < n; ++i)
sum += lev[i];
temp = result.mEstimatedDF - sum;
result.mFitCVScore = temp * temp + 3;
}
break;
default:
}
return false;
}
/**
* Calculates value and deriv.s of all b-splines which do not vanish at x
* calls bsplvb
*
*
Ripped from PPPACK. Original routine name = bsplvd
*
*
* ****** i n p u t ******
* t the knot array, of length left+k (at least)
* k the order of the b-splines to be evaluated
* x the point at which these values are sought
* left an int indicating the left endpoint of the interval of
* interest. the k b-splines whose support contains the interval
* (t(left), t(left+1))
* are to be considered.
* a s s u m p t i o n - - - it is assumed that
* t(left) < t(left+1)
* division by zero will result otherwise (in b s p l v b ).
* also, the output is as advertised only if
* t(left) <= x <= t(left+1) .
* nderiv an int indicating that values of b-splines and their
* derivatives up to but not including the nderiv-th are asked
* for. ( nderiv is replaced internally by the int in (1,k)
* closest to it.)
*
* ****** w o r k a r e a ******
* a an array of order (k,k), to contain b-coeff.s of the derivat-
* ives of a certain order of the k b-splines of interest.
*
* ****** o u t p u t ******
* dbiatx an array of order (k,nderiv). its entry (i,m) contains
* value of (m-1)st derivative of (left-k+i)-th b-spline of
* order k for knot sequence t , i=m,...,k; m=1,...,nderiv.
*
* ****** m e t h o d ******
* values at x of all the relevant b-splines of order k,k-1,...,
* k+1-nderiv are generated via bsplvb and stored temporarily
* in dbiatx . then, the b-coeffs of the required derivatives of the
* b-splines of interest are generated by differencing, each from the
* preceding one of lower order, and combined with the values of b-
* splines of corresponding order in dbiatx to produce the desired
* values.
*
*/
private static final void calculateBSplineDerivatives(double[] t, double x, int left, double[][] dbiatx)
{
// dbiatx is nderiv x k instead of k x nderiv
int
k = dbiatx[0].length,
nderiv = dbiatx.length;
int mhigh = max(min(nderiv,k),1); // mhigh is usually equal to nderiv.
calculateBSplineInitialDerivatives(t, k - mhigh, true, x, left, dbiatx[0]);
if (mhigh == 1)
return;
// the first column of dbiatx always contains the b-spline values
// for the current order. these are stored in column k+1-current
// order before bsplvb is called to put values for the next
// higher order on top of it.
int ideriv = mhigh;
for (int m = 2; m <= mhigh; ++m) {
for (int j = ideriv, jp1mid = 0; j <= k; ++j, ++jp1mid)
dbiatx[ideriv-1][j-1] = dbiatx[0][jp1mid];
--ideriv;
calculateBSplineInitialDerivatives(t, k - ideriv, false, x, left, dbiatx[0]);
}
// at this point, b(left-k+i, k+1-j)(x) is in dbiatx(i,j) for
// i=j,...,k and j=1,...,mhigh ('=' nderiv). in particular, the
// first column of dbiatx is already in final form. to obtain cor-
// responding derivatives of b-splines in subsequent columns, gene-
// rate their b-repr. by differencing, then evaluate at x.
double a[][] = new double[k][k];
for (int i = 0; i < k; ++i)
a[i][i] = 1;
// at this point, a(.,j) contains the b-coeffs for the j-th of the
// k b-splines of interest here.
int kp1 = k + 1;
for (int m = 2; m <= mhigh; ++m) {
int kp1mm = kp1 - m;
// for j=1,...,k, construct b-coeffs of (m-1)st derivative of
// b-splines from those for preceding derivative by differencing
// and store again in a(.,j) . the fact that a(i,j) = 0 for
// i < j is used.sed.
for (int ldummy = 1, il = left+1, j = k; ldummy <= kp1mm; ++ldummy, --il, --j) {
double factor = kp1mm / (t[il + kp1mm - 1] - t[il - 1]);
// the assumption that t(left) < t(left+1) makes denominator in factor nonzero.
for (int l = 0; l < j; ++l)
a[l][j-1] = (a[l][j-1] - a[l][j-2]) * factor;
}
// for i=1,...,k, combine b-coeffs a(.,i) with b-spline values
// stored in dbiatx(.,m) to get value of (m-1)st derivative of
// i-th b-spline (of interest here) at x , and store in
// dbiatx(i,m). storage of this value over the value of a b-spline
// of order m there is safe since the remaining b-spline derivative
// of the same order do not use this value due to the fact that a(j,i) = 0 for j < i.
for (int i = 1; i <= k; ++i) {
double sum = 0;
for (int j = max(i,m); j <= k; ++j)
sum += a[i-1][j-1] * dbiatx[m-1][j-1];
dbiatx[m-1][i-1] = sum;
}
}
}
/**
* Calculates the value of all possibly nonzero b-splines at x of order
* jout = dmax( jhigh , (j+1)*(index-1) ) with knot sequence t .
*
* Ripped from PPPACK. Original name: bsplvb
*
*
* ****** i n p u t ******
* t.....knot sequence, of length left + jout , assumed to be nondecreasing.
* a s s u m p t i o n : t(left) < t(left + 1)
* d i v i s i o n b y z e r o will result if t(left) = t(left+1)
*
* jhigh, index.....ints which determine the order jout = max(jhigh, (j+1)*(index-1))
* of the b-splines whose values at x are to be returned.
* index is used to avoid recalculations when several columns of
* the triangular array of b-spline values are needed
* (e.g., in bvalue or in bsplvd ). precisely,
*
* if index = 1 ,
* the calculation starts from scratch and the entire triangular
* array of b-spline values of orders 1,2,...,jhigh is generated
* order by order , i.e., column by column .
*
* if index = 2 ,
* only the b-spline values of order j+1, j+2, ..., jout are ge-
* nerated, the assumption being that biatx , j , deltal , deltar
* are, on entry, as they were on exit at the previous call.
* in particular, if jhigh = 0, then jout = j+1, i.e., just
* the next column of b-spline values is generated.
*
* w a r n i n g . . . the restriction jout <= jmax (= 20) is
* imposed arbitrarily by the dimension statement for deltal and
* deltar below, but is n o w h e r e c h e c k e d for.
*
* x.....the point at which the b-splines are to be evaluated.
* left.....an int chosen (usually) so that t(left) <= x <= t(left+1).
*
* ****** o u t p u t ******
* biatx.....array of length jout , with biatx(i) containing the value
* at x of the polynomial of order jout which agrees with
* the b-spline b(left-jout+i,jout,t) on the interval (t(left),
* t(left+1)) .
*
* ****** m e t h o d ******
* the recurrence relation
* x - t(i) t(i+j+1) - x
* b(i,j+1)(x) = ----------- b(i,j)(x) + --------------- b(i+1,j)(x)
* t(i+j)-t(i) t(i+j+1)-t(i+1)
* is used (repeatedly) to generate the
* (j+1)-vector b(left-j,j+1)(x),...,b(left,j+1)(x)
* from the j-vector b(left-j+1,j)(x),...,b(left,j)(x),
* storing the new values in biatx over the old. the facts that
* b(i,1) = 1 if t(i) <= x < t(i+1)
* and that
* b(i,j)(x) = 0 unless t(i) <= x < t(i+j)
* are used. the particular organization of the calculations follows
* algorithm (8) in chapter x of the text.
* Arguments
* dimension t(left+jout), biatx(jout)
* -----------------------------------
* current fortran standard makes it impossible to specify the length of
* t and of biatx precisely without the introduction of otherwise
* superfluous additional arguments.
*
*/
private static final void calculateBSplineInitialDerivatives(double[] t, int jhigh, boolean isInit, double x, int left, double[] biatx)
{
if (isInit)
{
biatx[0] = 1;
if (jhigh < 1)
return;
}
double
deltal[] = new double[jhigh + 1],
deltar[] = new double[jhigh + 1];
for (int j = 0; j < jhigh; j++)
{
deltar[j] = t[left + j + 1] - x;
deltal[j] = x - t[left - j];
double saved = 0;
for (int i = 0; i <= j; ++i) {
double term = biatx[i] / (deltar[i] + deltal[j - i]);
biatx[i] = saved + deltar[i] * term;
saved = deltal[j - i] * term;
}
biatx[j+1] = saved;
}
}
/**
* computes `left' := max( i ; 1 <= i <= n && xt[i] <= x ) .
*
*
RJ's note: I ripped this from CMLIB. Original name: interv
*
*
*
****** i n p u t ******
*
* xt numeric vector of length n , assumed to be nondecreasing
* x the point whose location with respect to the sequence xt is
* to be determined.
* mflag =: all_inside {logical} indicating if result should be coerced
* to lie inside {1, n-1}
* ilo typically the result of the last call to findInterval(.)
* `ilo' used to be a static variable (in Fortran) which is not
* desirable in R anymore (threads!).
* Instead, you *should* use a reasonable value, in the first call.
*
****** o u t p u t ******
*
* left, mflag both integers, whose value is
* 0 -1 if x < xt[1]
* i 0 if xt[i] <= x < xt[i+1]
* n 1 if xt[n] <= x
*
* in particular, mflag = 0 is the 'usual' case. mflag != 0
* indicates that x lies outside the halfopen interval
* xt[1] <= y < xt[n] . the asymmetric treatment of the
* interval is due to the decision to make all pp functions cont-
* inuous from the right.
*
* Note that if all_inside, left is 1 instead of 0 and n-1 instead of n;
* and if rightmost_closed and x == xt[n], left is n-1 instead of n.
*
*
****** m e t h o d ******
*
* the program is designed to be efficient in the common situation that
* it is called repeatedly, with x taken from an increasing or decreasing
* sequence. this will happen, e.g., when a pp function is to be graphed.
* The first guess for left is therefore taken to be the value returned at
* the previous call and stored in the l o c a l variable ilo .
* a first check ascertains that ilo < n (this is necessary since the
* present call may have nothing to do with the previous call).
*
* then, if xt[ilo] <= x < xt[ilo+1], we set left = ilo
* and are done after just three comparisons.
* otherwise, we repeatedly double the difference istep = ihi - ilo
* while also moving ilo and ihi in the direction of x , until
* xt[ilo] <= x < xt[ihi] ,
* after which we use bisection to get, in addition, ilo+1 = ihi .
* left = ilo is then returned.
*
*/
private static final int determineInterval(double[] xt, double x, int ilo, int[] mflag)
{
int ihi, n = xt.length;
if (mflag == null)
mflag = new int[1];
if(ilo < 0)
{
if (x < xt[0])
{
mflag[0] = -1;
return 0; // left_boundary
}
ilo = 0;
}
ihi = ilo + 1;
if (ihi >= n) {
if (x >= xt[n - 1]) // right_boundary
{
mflag[0] = 1;
return n - 1;
}
if (n <= 1) // x < xt[1]
{
mflag[0] = -1;
return 0; // left_boundary
}
ilo = n - 2;
ihi = n - 1;
}
if (x < xt[ihi])
{
if (x >= xt[ilo]) // `lucky': same interval as last time
return ilo;
// **** now x < xt[ilo] . decrease ilo to capture x
for(int istep = 1; ; istep *= 2) {
ihi = ilo;
ilo = ihi - istep;
if (ilo <= 0)
{
if (x < xt[0])
{
mflag[0] = -1;
return 0;
}
ilo = 0;
break;
}
if (x >= xt[ilo - 1])
break;
}
}
else {
// **** now x >= xt[ihi] . increase ihi to capture x
for(int istep = 1; ; istep *= 2) {
ilo = ihi;
ihi = ilo + istep;
if (ihi >= n)
{
if (x >= xt[n - 1]) //right_boundary;
{
mflag[0] = 1;
return n - 1;
}
ihi = n- 1;
break;
}
if (x < xt[ihi])
break;
}
}
//* **** now xt[ilo] <= x < xt[ihi] . narrow the interval.
for(;;) {
int middle = (ilo + ihi) / 2;
if (middle == ilo)
return ilo;
// note. it is assumed that middle = ilo in case ihi = ilo+1 .
if (x >= xt[middle])
ilo = middle;
else
ihi = middle;
}
}
/**
* Calculates value at x of jderiv-th derivative of spline from B-repr.
* The spline is taken to be continuous from the right.
*
*
calls interv
*
*
Ripped from PPPACK. Original name: bvalue
*
*
* ****** i n p u t ******
* t, bcoef, n, k......forms the b-representation of the spline f to
* be evaluated. specifically,
* t.....knot sequence, of length n+k, assumed nondecreasing.
* bcoef.....b-coefficient sequence, of length n .
* n.....length of bcoef and dimension of s(k,t),
* a s s u m e d positive .
* k.....order of the spline .
*
* w a r n i n g . . . the restriction k <= kmax (=20) is imposed
* arbitrarily by the dimension statement for aj, dm, dm below,
* but is n o w h e r e c h e c k e d for.
*
* x.....the point at which to evaluate .
* jderiv.....integer giving the order of the derivative to be evaluated
* a s s u m e d to be zero or positive.
*
* ****** o u t p u t ******
* bvalue.....the value of the (jderiv)-th derivative of f at x .
*
* ****** m e t h o d ******
* the nontrivial knot interval (t(i),t(i+1)) containing x is lo-
* cated with the aid of interv(). the k b-coeffs of f relevant for
* this interval are then obtained from bcoef (or taken to be zero if
* not explicitly available) and are then differenced jderiv times to
* obtain the b-coeffs of (d^jderiv)f relevant for that interval.
* precisely, with j = jderiv, we have from x.(12) of the text that
*
* (d^j)f = sum ( bcoef(.,j)*b(.,k-j,t) )
*
* where
* / bcoef(.), , j .eq. 0
* /
* bcoef(.,j) = / bcoef(.,j-1) - bcoef(.-1,j-1)
* / ----------------------------- , j > 0
* / (t(.+k-j) - t(.))/(k-j)
*
* then, we use repeatedly the fact that
*
* sum ( a(.)*b(.,m,t)(x) ) = sum ( a(.,x)*b(.,m-1,t)(x) )
* with
* (x - t(.))*a(.) + (t(.+m-1) - x)*a(.-1)
* a(.,x) = ---------------------------------------
* (x - t(.)) + (t(.+m-1) - x)
*
* to write (d^j)f(x) eventually as a linear combination of b-splines
* of order 1 , and the coefficient for b(i,1,t)(x) must then
* be the desired number (d^j)f(x). (see x.(17)-(19) of text).
*
* Arguments
* dimension t(n+k)
* current fortran standard makes it impossible to specify the length of
* t precisely without the introduction of otherwise superfluous
* additional arguments.
*
*/
private static final double evaluateBSplineDerivatives(double[] t, double[] bcoef, double x, int jderiv)
{
// Local variables
int
i = 1,
n = bcoef.length,
k = t.length - n,
mflag[] = new int[1];
double
aj[] = new double[k],
dm[] = new double[k-1],
dp[] = new double[k-1];
if (jderiv >= k)
return 0;
// *** find i s.t. 1 <= i < n+k and t(i) < t(i+1) and
// t(i) <= x < t(i+1) . if no such i can be found, x lies
// outside the support of the spline f and bvalue = 0.
// {this case is handled in the calling R code}
// (the asymmetry in this choice of i makes f rightcontinuous)
if (x != t[n] || t[n] != t[n + k - 1])
{
i = determineInterval(t, x, 0, mflag) + 1;
if (mflag[0] != 0)
return 0; // Should never happen
} else
i = n;
// *** if k = 1 (and jderiv = 0), bvalue = bcoef(i).
int km1 = k - 1;
if (km1 <= 0)
return bcoef[i - 1];
// store the k b-spline coefficients relevant for the knot interval
// (t(i),t(i+1)) in aj(1),...,aj(k) and compute dm(j) = x - t(i+1-j),
// dp(j) = t(i+j) - x, j=1,...,k-1 . set any of the aj not obtainable
// from input to zero. set any t.s not obtainable equal to t(1) or
// to t(n+k) appropriately.
int
jcmin = 1,
imk = i - k;
if (imk >= 0) {
for (int j = 0; j < km1; ++j)
dm[j] = x - t[i - j - 1];
} else {
// TODO This branch is not tested
jcmin = 1 - imk;
for (int j = 0; j < i; ++j)
dm[j] = x - t[i - j + 1];
for (int j = i - 1; j < km1; ++j)
dm[j] = dm[i - 1];
}
int
jcmax = k - 1,
nmi = n - i;
if (nmi >= 0) {
for (int j = 0; j < km1; ++j)
dp[j] = t[i + j] - x;
} else {
// TODO This branch is not tested
jcmax = k + nmi - 1;
for (int j = 0; j <= jcmax; ++j)
dp[j] = t[i + j] - x;
for (int j = jcmax; j < k; ++j)
dp[j] = dp[jcmax];
}
for (int jc = jcmin - 1; jc <= jcmax; ++jc)
aj[jc] = bcoef[imk + jc];
// *** difference the coefficients jderiv times.
for (int j = 0; j < jderiv; ++j) {
int kmj = k - j - 1;
for (int jj = 0, ilo = kmj - 1; jj < kmj; ++jj, --ilo)
aj[jj] = (aj[jj + 1] - aj[jj]) / (dm[ilo] + dp[jj]) * kmj;
}
// *** compute value at x in (t(i),t(i+1)) of jderiv-th derivative,
// given its relevant b-spline coeffs in aj(1),...,aj(k-jderiv).
for (int j = jderiv; j < km1; ++j) {
int kmj = k - j - 2;
for (int jj = 0, ilo = kmj; jj <= kmj; ++jj, --ilo)
aj[jj] = (aj[jj + 1] * dm[ilo] + aj[jj] * dp[jj]) / (dm[ilo] + dp[jj]);
}
return aj[0];
}
public static final double predict(SmoothSplineResult result, double val, int deriv)
{ return predict(result.mKnots, result.mCoefficients, result.mXMin, result.mXMax, val, deriv); }
public static final double predict(double[] knots, double[] coefs, double xmin, double xmax, double val, int deriv)
{
double
range = xmax - xmin,
normalizedValue = (val - xmin) / range;
if (normalizedValue >= 0 && normalizedValue <= 1)
return evaluateBSplineDerivatives(knots, coefs, normalizedValue, deriv) / (deriv > 0 ? pow(range, deriv) : 1);
// Either xs < 0 or xs > 1
double neg = normalizedValue < 0 ? 0 : 1;
if (deriv == 0)
{
double
intercept = evaluateBSplineDerivatives(knots, coefs, neg, 0),
slope = evaluateBSplineDerivatives(knots, coefs, neg, 1);
return intercept + slope * (normalizedValue - neg);
}
else if (deriv == 1)
return evaluateBSplineDerivatives(knots, coefs, neg, 1);
return 0;
}
private static final void normalizeWeights(double[] weights)
{
// Normalize weights
double sumW = 0;
int
n = weights.length,
numNonZero = 0;
for (int i = 0; i < n; i++)
{
double wt = weights[i];
if (wt < 0)
throw new RuntimeException("Negative weight is not allowed!");
else
if (wt > 0)
{
sumW += wt;
numNonZero++;
}
}
if (numNonZero == 0)
throw new RuntimeException("All zero weight is not allowed!");
sumW = numNonZero / sumW;
for (int i = 0; i < n; i++)
weights[i] *= sumW;
}
public static final SmoothSplineResult fitDFMatch(double[] x, double[] y, double df)
{
return fit(x,y,null,DF_MATCH, 1.0, df, Double.NaN,
kDefaultSmoothingParamLowerBound, kDefaultSmoothingParamUpperBound,
kDefaultTolerance, kDefaultMaxNumIterations);
}
public static final SmoothSplineResult fitDFMatch(double[] x, double[] y, double[] weights, double df)
{
return fit(x,y,weights,DF_MATCH, 1.0, df, Double.NaN,
kDefaultSmoothingParamLowerBound, kDefaultSmoothingParamUpperBound,
kDefaultTolerance, kDefaultMaxNumIterations);
}
public static final SmoothSplineResult fit(double[] x, double[] y)
{
return fit(x,y, null, SmoothSplineCriterion.NO_CRITERION, 1, 0, Double.NaN,
kDefaultSmoothingParamLowerBound, kDefaultSmoothingParamUpperBound,
kDefaultTolerance, kDefaultMaxNumIterations);
}
public static final SmoothSplineResult fit(double[] x, double[] y, double[] weights)
{
return fit(x,y,weights, SmoothSplineCriterion.NO_CRITERION, 1, 0, Double.NaN,
kDefaultSmoothingParamLowerBound, kDefaultSmoothingParamUpperBound,
kDefaultTolerance, kDefaultMaxNumIterations);
}
public static final SmoothSplineResult fit(double[] x, double[] y, double[] weights,
SmoothSplineCriterion criterion, double penalty, double df)
{
return fit(x,y,weights,criterion, penalty, df, Double.NaN,
kDefaultSmoothingParamLowerBound, kDefaultSmoothingParamUpperBound,
kDefaultTolerance, kDefaultMaxNumIterations);
}
/**
* Fit a smooth spline
* @param x array of n observations
* @param y array of n observations
* @param weights array of n observations. Null for default weights.
* @param criterion one of NO_CRITERION, GCV, CV, and DF_MATCHING
* @param penalty Must be 0 < penalty <= 1. Default to 1
* @param df Supply if you want DF matching. Else, it will be used for DF offset.
* @param smoothingParam Put Double.NaN to estimate it.
*/
public static final SmoothSplineResult fit(double[] x, double[] y, double[] weights,
SmoothSplineCriterion criterion, double penalty, double df, double smoothingParam)
{
return fit(x,y,weights,criterion, penalty, df, smoothingParam,
kDefaultSmoothingParamLowerBound, kDefaultSmoothingParamUpperBound,
kDefaultTolerance, kDefaultMaxNumIterations);
}
/**
* Fit a smooth spline
* @param x array of n observations
* @param y array of n observations
* @param weights array of n observations. Null for default weights.
* @param criterion one of NO_CRITERION, GCV, CV, and DF_MATCHING
* @param penalty Must be 0 < penalty <= 1. Default to 1
* @param df Supply if you want DF matching. Else, it will be used for DF offset.
* @param smoothingParam Put Double.NaN to estimate it.
* @param smoothingParamLBound Will be ignored if smoothingParam is not NaN. Default = -1.5
* @param smoothingParamUBound Will be ignored if smoothingParam is not NaN. Default = +1.5
* @param tolerance Tolerance threshold. Default is 0.0001.
* @param maxNumIterations maximum number of iterations. Default is 500.
* @return
*/
public static final SmoothSplineResult fit(double[] x, double[] y, double[] weights,
SmoothSplineCriterion criterion, double penalty, double df, double smoothingParam,
double smoothingParamLBound, double smoothingParamUBound, double tolerance, int maxNumIterations)
{
int n = x.length;
if (y == null)
{
y = x;
x = new double[n];
for (int i = 0; i < n; i++)
x[i] = i+1;
}
if (weights == null)
{
weights = new double[n];
fill(weights, 1.0);
}
else
normalizeWeights(weights);
assert(n == y.length && n == weights.length);
double
sortedArray[][] = new double[3][n],
sumOfSquare = 0; // ssw
// Copy the array first. We don't want to clobber the data
System.arraycopy(x, 0, sortedArray[0], 0, n);
System.arraycopy(y, 0, sortedArray[1], 0, n);
System.arraycopy(weights, 0, sortedArray[2], 0, n);
sort2DByRow(sortedArray, 0); // Sort!
int
dupeIdx[] = findDuplicateIndices(sortedArray[0]),
numUniqueElements = dupeIdx[n-1] + 1;
double[][] sortedUniqueArray;
if (numUniqueElements < n)
{
sortedUniqueArray = new double[3][numUniqueElements];
double[]
oldXBar = sortedArray[0],
oldYBar = sortedArray[1],
oldWBar = sortedArray[2],
newXBar = sortedUniqueArray[0],
newYBar = sortedUniqueArray[1],
newWBar = sortedUniqueArray[2],
tempBar = new double[numUniqueElements];
for (int i = 0; i < n; i++)
{
int idx = dupeIdx[i];
double
wt = oldWBar[i],
yi = oldYBar[i],
wtyi = wt*yi;
newXBar[idx] = oldXBar[i];
newWBar[idx] += wt;
newYBar[idx] += wtyi;
tempBar[idx] += wtyi * yi;
}
for (int i = 0; i < numUniqueElements; i++)
{
double
wi = newWBar[i],
yi = newYBar[i];
if (wi > 0)
newYBar[i] = yi = yi / wi;
sumOfSquare += tempBar[i] - wi * yi * yi;
}
}
else
sortedUniqueArray = sortedArray;
double
xbar[] = sortedUniqueArray[0],
ybar[] = sortedUniqueArray[1],
wbar[] = sortedUniqueArray[2],
xmin = xbar[0],
xmax = xbar[numUniqueElements-1],
range = xmax - xmin;
for (int i = 0; i < numUniqueElements; i++)
xbar[i] = (xbar[i] - xmin) / range;
double[] knots = formKnots(xbar);
SmoothSplineResult result = fitCubicSpline(penalty, df, xbar, ybar, wbar, sumOfSquare, knots,
criterion, smoothingParam, smoothingParamLBound, smoothingParamUBound,
tolerance, kDefaultEpsilon, maxNumIterations);
result.mXMax = xmax;
result.mXMin = xmin;
if (criterion == CV)
{
double
sum = 0,
sumW = 0;
for (int i = 0; i < n; i++)
{
int idx = dupeIdx[i];
double
wi = weights[i],
wbi = wbar[idx];
if (wbi <= 0)
wbi = 1;
double temp = (y[i] - result.mSmoothedValues[idx]) / (1 - (result.mLeverage[idx] * wi) / wbi);
sum += wi * temp * temp;
sumW += wi;
}
result.mCVScore = sum / sumW;
} else
{
double
dfOffset = criterion == DF_MATCH ? 0 : df,
sum = 0,
sumW = 0,
denom = 1 - (dfOffset + penalty * result.mEstimatedDF) / n;
denom *= denom; // square it;
for (int i = 0; i < n; i++)
{
double
wi = weights[i],
temp = y[i] - result.mSmoothedValues[dupeIdx[i]];
sum += wi * temp * temp;
sumW += wi;
}
result.mCVScore = (sum / sumW) / denom;
}
return result;
}
/**
* Find duplicate indices, assuming arr is sorted.
* It's similar to R's match function, but it
* further assumes that arr is sorted.
* Example:
*
Input: {1, 2, 2, 2, 3, 3, 4, 5, 6, 6}
*
Output: {0, 1, 1, 1, 2, 2, 3, 4, 5, 5}
*
*
So, the number of unique elements can be obtained
* by (result[arr.length-1] + 1).
* @param arr
* @return
*/
static final int[] findDuplicateIndices(double[] arr)
{
int
n = arr.length,
idx = 0,
result[] = new int[n];
double lastX = arr[0];
for (int i = 1; i < n; i++)
{
double curX = arr[i];
if (curX != lastX)
{
idx++;
lastX = curX;
}
result[i] = idx;
}
return result;
}
private static final void sort2DByRow(double[][] data, int rowNo)
{
int maxData = data[0].length - 1;
quickSortByRow(data, rowNo, 0, maxData);
insertionSortByRow(data, rowNo, 0, maxData);
}
/**
* Three median quick sort
*
* @param data the data
* @param rowNo the column index of which the table is compared against
* @param lo left boundary of array partition
* @param hi right boundary of array partition
*/
private static final void quickSortByRow(double[][] data, int rowNo, int lo, int hi)
{
int left, right, midpoint;
if ((hi - lo) <= kInsertionTreshold)
return;
midpoint = (hi + lo)/2;
if (data[rowNo][lo] > data[rowNo][midpoint])
swapRow(data, lo, midpoint);
if (data[rowNo][lo] > data[rowNo][hi])
swapRow(data, lo, hi);
if (data[rowNo][midpoint] > data[rowNo][hi])
swapRow(data, midpoint, hi);
right = hi - 1;
swapRow(data, midpoint, right);
left = lo;
double
dataRow[] = data[rowNo],
pivotElement = dataRow[right];
for(;;)
{
while(dataRow[++left] < pivotElement){}
while(dataRow[--right] > pivotElement){}
if (right < left)
break;
swapRow(data, left, right);
}
swapRow(data, left, hi-1);
quickSortByRow(data, rowNo, lo, right);
quickSortByRow(data, rowNo, left+1, hi);
}
/**
* Insertion Sort
*
* @param data the data
* @param rowNo the row index of which the table is compared against
* @param lo left boundary of array partition
* @param hi right boundary of array partition
*/
private static final void insertionSortByRow(double[][] data, int rowNo, int lo, int hi)
{
double element_i, element_j;
int j, jp1, numRows = data.length;
double[] col_i = new double[numRows];
for (int i = 1; i <= hi; i++)
{
for (int r = 0; r < numRows; r++)
col_i[r] = data[r][i];
element_i = data[rowNo][i];
for (j = i - 1; j >= lo; j--)
{
element_j = data[rowNo][j];
if (element_j <= element_i)
break;
jp1 = j+1;
for (int r = 0; r < numRows; r++)
data[r][jp1] = data[r][j]; // a[j+1] = a[j]
}
jp1 = j+1;
for (int r = 0; r < numRows; r++)
data[r][jp1] = col_i[r];
}
}
private static final void swapRow(double[][] data, int i, int j)
{
int numRows = data.length;
for (int rowNo = 0; rowNo < numRows; rowNo++)
{
double
curRow[] = data[rowNo],
temp = curRow[i];
curRow[i] = curRow[j];
curRow[j] = temp;
}
}
static void testBSPLVD()
{
// Correct output:
// 0.00 1.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000
// 0.25 0.5625000 0.3750000 0.0625000 0.0000000 0.0000000 0.0000000 0.0000000
// 0.50 0.2500000 0.5000000 0.2500000 0.0000000 0.0000000 0.0000000 0.0000000
// 0.75 0.0625000 0.3750000 0.5625000 0.0000000 0.0000000 0.0000000 0.0000000
// 1.00 0.0000000 0.0000000 1.0000000 0.0000000 0.0000000 0.0000000 0.0000000
// 1.25 0.0000000 0.0000000 0.7656250 0.2239583 0.0104167 0.0000000 0.0000000
// 1.50 0.0000000 0.0000000 0.5625000 0.3958333 0.0416667 0.0000000 0.0000000
// 1.75 0.0000000 0.0000000 0.3906250 0.5156250 0.0937500 0.0000000 0.0000000
// 2.00 0.0000000 0.0000000 0.2500000 0.5833333 0.1666667 0.0000000 0.0000000
// 2.25 0.0000000 0.0000000 0.1406250 0.5989583 0.2604167 0.0000000 0.0000000
// 2.50 0.0000000 0.0000000 0.0625000 0.5625000 0.3750000 0.0000000 0.0000000
// 2.75 0.0000000 0.0000000 0.0156250 0.4739583 0.5104167 0.0000000 0.0000000
// 3.00 0.0000000 0.0000000 0.0000000 0.3333333 0.6666667 0.0000000 0.0000000
// 3.25 0.0000000 0.0000000 0.0000000 0.1875000 0.7916667 0.0208333 0.0000000
// 3.50 0.0000000 0.0000000 0.0000000 0.0833333 0.8333333 0.0833333 0.0000000
// 3.75 0.0000000 0.0000000 0.0000000 0.0208333 0.7916667 0.1875000 0.0000000
// 4.00 0.0000000 0.0000000 0.0000000 0.0000000 0.6666667 0.3333333 0.0000000
// 4.25 0.0000000 0.0000000 0.0000000 0.0000000 0.5104167 0.4739583 0.0156250
// 4.50 0.0000000 0.0000000 0.0000000 0.0000000 0.3750000 0.5625000 0.0625000
// 4.75 0.0000000 0.0000000 0.0000000 0.0000000 0.2604167 0.5989583 0.1406250
// 5.00 0.0000000 0.0000000 0.0000000 0.0000000 0.1666667 0.5833333 0.2500000
// 5.25 0.0000000 0.0000000 0.0000000 0.0000000 0.0937500 0.5156250 0.3906250
// 5.50 0.0000000 0.0000000 0.0000000 0.0000000 0.0416667 0.3958333 0.5625000
// 5.75 0.0000000 0.0000000 0.0000000 0.0000000 0.0104167 0.2239583 0.7656250
// 6.00 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 0.0000000 1.0000000
// Taken from Example 1 of:
// de Boor (1977) Package for Calculating with B-Splines, SIAM J. Numerical Analysis 14(3), June 1977
double
t[] = new double[] {0,0,0,1,1,3,4,6,6,6},
values[][] = new double[1][3];
int
n = 7,
k = 3,
nPoint = 25,
iLeft = 0;
double
xl = t[k - 1],
dx = (t[n] - xl) / (nPoint - 1);
for (int i = 0; i < nPoint; i++)
{
double
x = xl + i * dx,
allValues[] = new double[n];
iLeft = determineInterval(t, x, iLeft, null);
if (iLeft >= n)
iLeft = n - 1;
calculateBSplineDerivatives(t, x, iLeft, values);
int imkp1 = iLeft - k + 1;
for (int j = 0; j < k; j++)
{
allValues[imkp1+j] = values[0][j];
values[0][j] = 0;
}
System.out.print(String.format("%5.2f %5.7f", x, allValues[0])); //$NON-NLS-1$
for (int j = 1; j < n; j++)
System.out.print(String.format(" %5.7f",allValues[j])); //$NON-NLS-1$
System.out.println();
}
}
static void testSmoothSplines()
{
/*
* xi and fi are taken from Pang, Tao (2006) "An Introduction to Computational Physics", 2nd ed.
* speed, dist, and y18 are taken from R's car example
*/
double
xi[] = new double[] { 0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4,
0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1},
fi[] = new double[] { 0.420650268, 0.869057123, 0.243074468, 0.352583416,
0.869472216, 0.219531331, 0.663085387, 0.476098157, 0.781728422, 0.509583544,
0.570627762, 0.540800479, 0.233658831, 0.103976142, 0.527013717, 0.519541602,
0.935703005, 0.360399664, 0.237154223, 0.851028235, 0.231539401},
speed[] = new double[] {4, 4, 7, 7, 8, 9, 10, 10, 10, 11, 11, 12, 12, 12, 12, 13,
13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 16, 16, 17, 17, 17, 18, 18, 18, 18,
19, 19, 19, 20, 20, 20, 20, 20, 22, 23, 24, 24, 24, 24, 25},
dist[] = new double[] {2, 10, 4, 22, 16, 10, 18, 26, 34, 17, 28, 14, 20, 24, 28, 26, 34, 34, 46,
26, 36, 60, 80, 20, 26, 54, 32, 40, 32, 40, 50, 42, 56, 76, 84, 36, 46, 68,
32, 48, 52, 56, 64, 66, 54, 70, 92, 93, 120, 85},
y18[] = new double[] {1, 2, 3, 5, 4, 7, 6, 5, 4, 3, 4, 6, 8, 10, 10, 10, 10, 10};
assert (xi.length == fi.length);
assert (speed.length == dist.length);
SmoothSplineResult result;
//*
result = fit(fi, xi, null, DF_MATCH, 1, 3);
//Minimizing criterion: DF_MATCH
//Smoothing parameter: 1.018851345458852
//Lambda: 0.020315773036788925
//Num. iterations: 11
//Equivalent degrees of freedom (DF): 3.0003649975970452
//Penalized criterion: 1.8626611778254198
//GCV: 0.1517015406647021
System.out.println(result);
System.out.println(predict(result, 0.05, 0)); //0.6374198108495056
System.out.println(predict(result, 0.90, 0)); //0.4909193517530635
System.out.println(predict(result, 0.95, 0)); //0.5055859812416792
System.out.println();
result = fit(fi, xi, null, GCV, 1, 0);
//Minimizing criterion: GCV
//Smoothing parameter: 1.4999418748090076
//Lambda: 60.7881102827463
//Num. iterations: 26
//Equivalent degrees of freedom (DF): 2.0009149997606093
//Penalized criterion: 1.9085673029717933
//GCV: 0.13324690261258523
System.out.println(result);
System.out.println(predict(result, 0.05, 0)); //0.5514480705995526 vs 0.5514477
System.out.println(predict(result, 0.90, 0)); //0.45455352986272307 vs 0.4545536
System.out.println(predict(result, 0.95, 0)); //0.4488745767544037 vs 0.4488747
System.out.println();
result = fit(fi, xi, null, CV, 1, 0);
//Minimizing criterion: CV
//Smoothing parameter: 1.4999579810285835
//Lambda: 60.80439975985657
//Num. iterations: 30
//Equivalent degrees of freedom (DF): 2.0009147864018386
//Penalized criterion: 1.9085673187134673
//PRESS: 0.14641910796023755
System.out.println(result);
System.out.println(predict(result, 0.05, 0)); //0.551448070184632
System.out.println(predict(result, 0.90, 0)); //0.45455351361617224
System.out.println(predict(result, 0.95, 0)); //0.4488745540299733
System.out.println();
//*/
result = fit(speed, dist, null, GCV, 1, 0);
//Minimizing criterion: GCV
//Smoothing parameter: 0.7802768994560999
//Lambda: 0.11121547691795662
//Num. iterations: 11
//Equivalent degrees of freedom (DF): 2.6352697498668305
//Penalized criterion: 4187.779014409647
//GCV: 244.1043967439251
System.out.println(result);
result = fit(speed, dist, null, DF_MATCH, 1, 10);
//Minimizing criterion: DF_MATCH
//Smoothing parameter: 0.3541654525300812
//Lambda: 9.281738283403245E-5
//Num. iterations: 9
//Equivalent degrees of freedom (DF): 9.99857714398653
//Penalized criterion: 1680.6894921712935
//GCV: 263.9022507178775
System.out.println(result);
result = fit(y18, null, null, GCV, 1, 0);
System.out.println(result);
}
// public static void main(String argv[])
// {
// //testBSPLVD();
// testSmoothSplines();
// }
}
Ads help maintain this website.