https://github.com/cran/fields
Raw File
Tip revision: 6c8b30169bba182a68765ee3cb9b4e2ef7d38332 authored by Doug Nychka on 16 November 2011, 00:00:00 UTC
version 6.6.3
Tip revision: 6c8b301
LatticeKrig.R.BACK
# fields, Tools for spatial data
# Copyright 2004-2011, Institute for Mathematics Applied Geosciences
# University Corporation for Atmospheric Research
# Licensed under the GPL -- www.gpl.org/licenses/gpl.html
`Wendland.father` <-
function (x, theta = 1, dimension = 1, k=3) 
{
  Wendland(abs(x), theta=theta, dimension=dimension, k=k)}

`Wendland.mother` <-
function (x, theta = 1, dimension = 1, k=3) 
{ 
  sign(x)*Wendland(abs(x), theta=theta, dimension=dimension, k=k, derivative=1)}

"wendland.basis" <- function(x1, x2, theta = 1, V=NULL,
    k = 3, C = NA, Dist.args = list(method = "euclidean"), 
    spam.format = TRUE, verbose = FALSE, flavor=0) {
    
#  the rest of the possiblities require some computing
# setup the two matrices of locations
#
  d <- ncol(x1)
  if( d!=2) stop("only 2-d basis is supported")
  n1 <- nrow(x1)
  n2 <- nrow(x2)
# catch bad theta format
  if (length(theta) > 1) {
       stop("theta as a matrix or vector has been depreciated") }
  if (!is.null(V)) {
        if (theta != 1) {
            stop("can't specify both theta and V!")
        }
        x1 <- x1 %*% t(solve(V))
        x2 <- x2 %*% t(solve(V))
    }
  delta <- 1.5*theta 
# once scaling is done taper is applied with default range of 1.0
#  d dimension and  k is the order
#  first  find sparse matrix of Euclidean distances
#  ||x1-x2||**2 (or any other distance that may be specified by
#   the method component in Dist.args
#  any distance beyond delta is set to zero -- anticipating the
# tapering to zero by the Wendland.
#
  sM <- do.call("nearest.dist", c(list(x1, x2, delta = delta, 
        upper = NULL), Dist.args))
# scale distances by theta
# note: if V is passed then theta==1 and all the scaling should be done with the V matrix.
# there are two possible actions listed below:
# find  Wendland cross covariance matrix
# return either in sparse or matrix format
# Create rowindices vector
  sMrowindices <- rep(1:n1, diff(sM@rowpointers))
  dx <- (x1[sMrowindices, 1] - x2[sM@colindices, 1])
  dy <- (x1[sMrowindices, 2] - x2[sM@colindices, 2])
# switches for different flavors    
  if(flavor==0){
        sM@entries <- Wendland.father(dx,theta,k=k)*
                          Wendland.father(dy,theta,k=k)}
  if(flavor==1){
        sM@entries <- Wendland.father(dx,theta,k=k)*
                          Wendland.mother(dy,theta,k=k)}
  if(flavor==2){
        sM@entries <- Wendland.mother(dx,theta,k=k)*
                          Wendland.father(dy,theta,k=k)}
  if(flavor==3){
        sM@entries <- Wendland.mother(dx,theta,k=k)*
                          Wendland.mother(dy,theta,k=k)}
# convert to full matrix format     
  if (!spam.format) {
          sM<- as.matrix(sM)}
# 
  if (is.na(C[1])) {
      return(sM)}
    else{
      return( sM%*%C)} 
       
    # should not get here!
}



LatticeKrig<- function( x,y=NULL, weights = rep(1, nrow(x)), NC=16, center.range=NULL, beta=-.2, lambda=1.0,
                       iseed=123,NtrA=20,
                          overlap=2.5, setup=FALSE){
  N<- nrow(x)
# if center range is missing use the locations
  if( is.null(center.range)){
  x1<- range( x[,1])
  x2<- range( x[,2])}
  else{
  x1<- center.range$x
  x2<- center.range$y}
# spacing for grid
  d1<- x1[2] - x1[1]
  d2<- x2[2] - x2[1]
# actual number of grid points is set so that centers are equally
# spaced in both axes. NC is the maximum number of grid points
# delta used to determine support of wendland basis functions
# default support is (-1,1)  -- i.e. if delta is 1.0 
  delta<- max(c(d1,d2))/NC
  center.grid.list<-  list( x= seq( x1[1], x1[2],delta ), y= seq( x2[1], x2[2],delta))
  N1<- length( center.grid.list$x)
  N2<- length( center.grid.list$y)
  np<- N1*N2
# create centers there should be N1*N2 of these
  centers<- make.surface.grid( center.grid.list)
  basis.delta<- overlap*delta
# Matrix of N1*N2 basis function (columns) evaluated at the N locations (rows)
# this can be a large matrix if not encoded in sparse format.   
  PHI<-  wendland.basis( x, centers, theta=basis.delta, spam.format=TRUE, flavor=0)
# square root of precision matrix of the lattice process
#   solve(t(H)%*%H) is proportional to the covariance matrix of the Markov Random Field
  H<-  lattice.precision(N1,N2, spam.format=TRUE, beta=beta)
  
# variational matrix used to find coefficients of fitted surface and evaluate
  
############################################################################################
# this is the regularized regression matrix that is the key to the entire algorithm:
########################################################################################  
  temp<- t(PHI)%*%PHI + lambda*(t(H)%*%H)
  nzero <- length(temp@entries)
#  
# S-M-W identity can be used to evaluate the data covariance matrix:
#   M =  PHI%*% solve(t(H)%*%H) %*% t( PHI) + diag( lambda, N)
# i.e. because temp is sparse, sparse computations can be used to indirectly solve
# linear systems based on M
############################################################################################  
# find Cholesky square root of this matrix
############################################################################################  
#  This is where the heavy lifting happens! Note that temp is sparse format so
#  implied by overloading is a sparse cholesky decomposition. 
#  if this has been coded efficiently this step should dominate all other computations. 
  chol( temp)-> Mc
  if( setup){
     return(
            list(centers=centers,  center.grid.list= center.grid.list, N1=N1, N2=N2,
              overlap=overlap, delta=delta,basis.delta=basis.delta,            
              Mc=Mc, np=np, lambda.fixed=lambda,nonzero.entries=nzero,
                 PHI=PHI, XtX=t(PHI)%*%PHI)
            )
   }
#
# find log determinant of reg matrix temp for use in the log likeihood
  lnDetReg <- 2 * sum(log(diag(Mc)))
  lnDetQ<-  2* sum( log( diag( chol(t(H)%*%H))))
# now apply a miraculous determinant identity (Sylvester''s theorem)
#  det( I + UV) = det( I + VU)    providing UV is square
# or more generally   
#  det( A + UV) = det(A) det( I + V A^{-1}U)
#  or as we use it
#  ln(det( I + V A^{-1}U)) = ln( det(  A + UV)) - ln( det(A))
#  
#  with A = lambda* t(H)%*%H , U= t(PHI) V= PHI   
#  M =  lambda*(I + V A^{-1}U)
#
#   lnDet (M) = N* ln(lambda) + ln(Det(I + V A^{-1}U))
#             = N* ln(lambda) + ln(Det( temp)) - ln Det( lambda*Q)
#             = N* ln(lambda) + ln(Det( temp))  - ln Det( Q) - N1*N2* log( lambda)
# Here we  canceled out lambda terms 
# 
  lnDetCov<- lnDetReg - lnDetQ + (N - N1*N2)* log(lambda)
# Amazing!
# 
# Spatial drift matrix -- assumed to be linear in coordinates. (m=2)
  m=2
  nt= 3
  T.matrix<- cbind( rep(1, N), x)
#
# The goal is   d.coef = (T^t M^{-1} T)^{-1} T^t %*% M^{-1} y
#  M  is  as defined in comments above
#  apply the inverse of M to T and y  using S-M-W formula and cholesky of
#  regularization matrix (Mc in code above)
#  
  A <- forwardsolve(Mc, transpose = TRUE, t(PHI)%*%T.matrix, upper.tri = TRUE)
  A <- backsolve(Mc, A)  
  A<- t(T.matrix)%*%(T.matrix - PHI%*%A)/lambda
#   A is  (T^t M^{-1} T) 
  b <- forwardsolve(Mc, transpose = TRUE, t(PHI)%*%y, upper.tri = TRUE)
  b <- backsolve(Mc, b)  
  b<- t(T.matrix)%*%(y- PHI%*%b)/lambda
# b is   (T^t M^{-1} y)
# Save the intermediate matrix   (T^t M^{-1} T) ^{-1}
# this the GLS covariance matrix of estimated coefficients
# should be small -- the default is 3X3  
  Omega<- solve( A)
# GLS estimates   
  d.coef<- Omega%*% b

# coefficients of basis functions.   
  c.coef <- forwardsolve(Mc, transpose = TRUE, t(PHI)%*%(y-T.matrix%*%d.coef),
                         upper.tri = TRUE)
  c.coef <- backsolve(Mc, c.coef)
  fitted.values<- T.matrix%*%d.coef + PHI%*%c.coef
  residuals<- y- fitted.values
  c.mKrig<- (y- fitted.values)/lambda

# finding quadratic form
# this uses a shortcut formula for the quadratic form in terms of
# the residuals 
    quad.form<-   sum(y* c.mKrig )
# MLE estimate of rho and sigma
# these are derived by assuming Y is  MN(  Td, rho*M )  
  rho.MLE <- quad.form/N
  shat.MLE <- sigma.MLE <- sqrt(lambda * rho.MLE)
# the  log profile likehood with  rhohat  and  dhat substituted
# leaving a profile for just lambda.
# note that this is _not_  -2*loglike just the log and
# includes the constants
  lnProfileLike <- (-N/2 - log(2*pi)*(N/2)
                      - (N/2)*log(rho.MLE) - (1/2) * lnDetCov)
# Monte Carlo estimate of effective degrees of freedom
# uses the fact that the expected value of a quadratic form is the
# trace of the matrix.
#  NtrA samples are taken and  typically NtrA= 20 is adequate
#  to use the sample mean as an estimate of the expectation.
#  A(lambda,beta)  here is of the course the smoothing matrix and predicted
#  values found the same way as the estimate. Note use of vectorization
#  to avoid a loop over the Monte Carlo samples. 
#
  set.seed(iseed)
 
  Ey<- matrix( rnorm( NtrA*N), N,NtrA)
  b <- forwardsolve(Mc, transpose = TRUE, t(PHI)%*%Ey, upper.tri = TRUE)
  b <- backsolve(Mc, b)
  b<- t(T.matrix)%*%(Ey- PHI%*%b)/lambda
  d.temp<-  solve( A, b)
  
  c.temp <- forwardsolve(Mc, transpose = TRUE, t(PHI)%*%(Ey- T.matrix%*%d.temp),
                                         upper.tri = TRUE)
  c.temp <- backsolve(Mc, c.temp)
  
  Eyhat<-T.matrix%*%d.temp + PHI%*%c.temp 
  trA.info <- t(Ey *(Eyhat))%*%rep(1,N)
  trA.est <- mean(trA.info)

# find the GCV function
  GCV=  (sum((residuals)^2)/N )  /( 1- trA.est/np)^2
  
  obj<-list(x=x,y=y,d.coef=d.coef, c.coef=c.coef,
              fitted.values=fitted.values, residuals= residuals,
              centers=centers, beta=beta, center.grid.list= center.grid.list, N1=N1, N2=N2,
              overlap=overlap, delta=delta,basis.delta=basis.delta,
              GCV=GCV, lnProfileLike= lnProfileLike,
              rho.MLE=rho.MLE, shat.MLE= shat.MLE,lambda=lambda,
              lnDetCov= lnDetCov, quad.form= quad.form,
              Mc=Mc,
              trA.info=trA.info, trA.est=trA.est,
              eff.df= trA.est, np=np, lambda.fixed=lambda,nonzero.entries=nzero,m=m,nt=nt,
              call=match.call()
            )
  class(obj)<- "LatticeKrig"
  return(obj)
}

surface.LatticeKrig<- function(obj,...){
  surface.Krig( obj,...)}

predict.LatticeKrig<- function( object, xnew=NULL,...){
  if( is.null(xnew)){
    xnew<- object$x}
  PHIg<-  wendland.basis( xnew, object$centers, theta=object$basis.delta, spam.format=TRUE, flavor=0)
  NG<- nrow( xnew)
  T.matrix.g<- cbind( rep(1,NG), xnew)
return( T.matrix.g%*% object$d.coef + PHIg%*%object$c.coef)
}

print.LatticeKrig <- function(x, digits=4, ...){
   if (is.matrix(x$residuals)) {
        n <- nrow(x$residuals)
        NData <- ncol(x$residuals)
    }
    else {
        n <- length(x$residuals)
        NData <- 1
    }
    c1 <- "Number of Observations:"
    c2 <- n
    if (NData > 1) {
        c1 <- c(c1, "Number of data sets fit:")
        c2 <- c(c2, NData)
    }
    c1 <- c(c1, "Degree of polynomial null space ( base model):")
    c2 <- c(c2, x$m - 1)
    c1 <- c(c1, "Number of parameters in the null space")
    c2 <- c(c2, x$nt)
    if (!is.na(x$eff.df)) {
        c1 <- c(c1, " Eff. degrees of freedom")
        c2 <- c(c2, signif(x$eff.df, digits))
        if (length(x$trA.info) < x$np) {
            c1 <- c(c1, "   Standard Error of estimate: ")
            c2 <- c(c2, signif(sd(x$trA.info)/sqrt(length(x$trA.info)), 
                digits))
        }
    }
    c1 <- c(c1, "Smoothing parameter")
    c2 <- c(c2, signif(x$lambda.fixed, digits))
    if (NData == 1) {
        c1 <- c(c1, "MLE sigma ")
        c2 <- c(c2, signif(x$shat.MLE, digits))
        c1 <- c(c1, "MLE rho")
        c2 <- c(c2, signif(x$rho.MLE, digits))
    }
    c1 <- c(c1, "Nonzero entries in covariance")
    c2 <- c(c2, x$nonzero.entries)
    sum <- cbind(c1, c2)
    dimnames(sum) <- list(rep("", dim(sum)[1]), rep("", dim(sum)[2]))
    cat("Call:\n")
    dput(x$call)
    print(sum, quote = FALSE)
    cat(" ", fill = TRUE)
    cat("Covariance Model: Wendland/Lattice", fill = TRUE)
    cat("Lattice is ", x$N1,"X",x$N2, fill=TRUE)
    cat( "total number of points: ", x$N1*x$N2, " with spacing", x$delta,  fill=TRUE)
    cat("Value for lattice dependence (beta): ", x$beta,fill = TRUE)
    invisible(x)
}                      

"lattice.precision" <- function(N1,N2, spam.format = TRUE, beta) {
  NN<- N1*N2
  da<- as.integer( c(NN,NN))
  I<- as.integer(rep(1:N1,N2))
  J<- as.integer(rep((1:N2), rep( N1,N2)))
# contents of sparse matrix
  ra<- rep( 1, N1*N2)
  ra<- c( ra, rep( beta, 4*NN))
#  Note: if beta depends on lattice position
# pass beta as a matrix and use  ra<- as.numeric(c( ra,rep( c(beta), 4)))
  Hi<- rep(1:NN,5) 
  Hj<- c( I + (J-1)*N1,
         (I-1) + (J-1)*N1, (I+1) + (J-1)*N1,
         I + (J-2)*N1, I + (J)*N1 )   
  good<- c( rep( TRUE,NN),
          (I-1) > 0, (I<N1),
          (J-2)>=0, (J<N2))
# remove cases that are beyond the lattice (edges, corners)        
  Hi<- as.integer(Hi[good])
  Hj<- as.integer(Hj[good])
  ra<- ra[good]
# Convert matrix to spam format  
  sM <- spind2spam( list( ind=cbind(Hi,Hj), ra=ra, da=da) )
# convert to an ordinary matrix (usually for debugging)     
  if (!spam.format) {
          sM<- as.matrix(sM)}
  return(sM)
}

LatticeKrig.cov<- function( x1,x2=NULL,center.grid.list, beta=-.2, overlap=2.5, C=NA, marginal=FALSE){
  if( is.null(x2)){ x2<-x1}
  N1<- length( center.grid.list$x)
  N2<- length( center.grid.list$y)
  centers<- make.surface.grid( center.grid.list)
  delta<- max(c( center.grid.list$x[2] - center.grid.list$x[1],
              center.grid.list$y[2] - center.grid.list$y[1] ))
  basis.delta<- overlap*delta
  PHI1<-  wendland.basis( x1, centers, theta=basis.delta, spam.format=FALSE, flavor=0)
  PHI2<-  wendland.basis( x2, centers, theta=basis.delta, spam.format=FALSE, flavor=0)
  H<-  lattice.precision(N1,N2, spam.format=TRUE, beta=beta)
#  Note:  Q  <- t(H)%*%(H)
  chol(t(H)%*%(H))-> Mc 
  A <- forwardsolve(Mc, transpose = TRUE, t(PHI2), upper.tri = TRUE)
  A <- backsolve(Mc, A)
  
   if (is.na(C[1]) & !marginal) {
        return( PHI1%*%A)
    }
    if (!is.na(C[1])) {
        return(PHI1%*%A %*% C)
    }
    if (marginal) {
        return(rep(1, nrow(x1)))
    }

 
}
back to top