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.2
# 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


LatticeKrig<- function( x,y=NULL, weights = rep(1, nrow(x)),Z=NULL, NC=16, center.range=NULL, beta=-.2, lambda=1.0,
                       iseed=123,NtrA=20,
                          overlap=2.5, setup=FALSE){
# make sure locations are a matrix and get the rows  
  x<- as.matrix(x)
  N<- nrow(x)
# make sure covariate is a matrix  
   if(!is.null(Z)){
      Z<- as.matrix(Z)}
# 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<- max(c(d1,d2))/(NC-1)
  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)
  
# delta used to determine support of wendland basis functions
  
  basis.delta<- overlap*delta
# weighted observation vector  
  wy<- sqrt(weights)*y
# Spatial drift matrix -- assumed to be linear in coordinates. (m=2)
# and includes Z covariate if not NULL  
  wT.matrix<- sqrt(weights)*cbind( rep(1, N), x,Z)
  nt<- ncol(wT.matrix)
  print(nt)
  nZ<- ifelse (is.null(Z), 0, ncol(Z))   
  ind.drift <- c(rep(TRUE, (nt-nZ)), rep( FALSE, nZ))
# Matrix of N1*N2 basis function (columns) evaluated at the N locations (rows)
# and multiplied by square root of diagonal weight matrix  
# this can be a large matrix if not encoded in sparse format.
  wS<-  diag.spam(sqrt( weights))
  wPHI<-  wS%*%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:
########################################################################################
  Q<- t(H)%*%H
  temp<- t(wPHI)%*%wPHI + lambda*(Q)
  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,
                 wPHI=wPHI, wT.matrix=wT.matrix,
                 weights=weights, nZ=nZ,ind.drift=ind.drift))
   }
  
  out1<- LatticeKrig.coef( Mc, wPHI, wT.matrix, wy, lambda)
  
  fitted.values<- (wT.matrix%*%out1$d.coef + wPHI%*%out1$c.coef)/sqrt(weights)
  residuals<- y- fitted.values

  out2<-LatticeKrig.lnPlike(Mc, Q, y, N1, N2,lambda,residuals, weights)
# save seed if random number generation happening outside LatticeKrig  
  save.seed<- .Random.seed
  if( !is.na(iseed)){
    set.seed(iseed)}
# generate N(0,1)  
  wEy<- matrix( rnorm( NtrA*N), N,NtrA)*sqrt(weights)
# restore seed   
   if( !is.na(iseed)){
      assign(".Random.seed",save.seed, pos=1)}
#  
  out3<- LatticeKrig.coef( Mc, wPHI, wT.matrix, wEy, lambda)
  wEyhat<-(wT.matrix%*%out3$d.coef + wPHI%*%out3$c.coef) 
  trA.info <- t((wEy *wEyhat)/weights)%*%rep(1,N)
  trA.est <- mean(trA.info)

# find the GCV function
  GCV=  (sum(weights*(residuals)^2)/N )  /( 1- trA.est/np)^2

  
  obj<-list(x=x,y=y,weights=weights, Z=Z,
              d.coef=out1$d.coef, c.coef=out1$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= out2$lnProfileLike,
              rho.MLE=out2$rho.MLE, shat.MLE= out2$shat.MLE,lambda=lambda,
              lnDetCov= out2$lnDetCov, quad.form= out2$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=2,nt=nt, nZ=nZ,ind.drift=ind.drift,
              call=match.call())
  class(obj)<- "LatticeKrig"
  return(obj)
}

LatticeKrig.coef<- function(Mc,wPHI,wT.matrix,wy,lambda){
  A <- forwardsolve(Mc, transpose = TRUE, t(wPHI)%*%wT.matrix, upper.tri = TRUE)
  A <- backsolve(Mc, A)  
  A<- t(wT.matrix)%*%(wT.matrix - wPHI%*%A)/lambda
#   A is  (T^t M^{-1} T) 
  b <- forwardsolve(Mc, transpose = TRUE, t(wPHI)%*%wy, upper.tri = TRUE)
  b <- backsolve(Mc, b)  
  b<- t(wT.matrix)%*%(wy- wPHI%*%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(wPHI)%*%(wy-wT.matrix%*%d.coef),
                         upper.tri = TRUE)
  c.coef <- backsolve(Mc, c.coef)

  return(list( c.coef=c.coef, d.coef= d.coef))  
  }

LatticeKrig.lnPlike<- function(Mc,Q,y,N1,N2,lambda,residuals,weights){
  N<- length(y)
  c.mKrig<- weights*residuals/lambda
# 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(Q))))
# 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)
# 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)
  return( list(lnProfileLike=lnProfileLike,rho.MLE=rho.MLE, shat.MLE=shat.MLE,
               quad.form=quad.form, lnDetCov=lnDetCov) )
 
}


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

predict.LatticeKrig<- function( object, xnew=NULL,Z=NULL,drop.Z=FALSE,...){
  if( is.null(xnew)){
    xnew<- object$x}
  if( is.null(Z)& object$nZ>0){
    Z<- object$Z} 
  PHIg<-  wendland.basis( xnew, object$centers, theta=object$basis.delta,
                          spam.format=TRUE, flavor=0)
  NG<- nrow( xnew)
   if( drop.Z|object$nZ==0){
     temp1<-cbind( rep(1,NG), xnew)%*% object$d.coef[object$ind.drift,]}
   else{
     temp1<- cbind( rep(1,NG), xnew,Z)%*% object$d.coef}
  temp2<- PHIg%*%object$c.coef
return( temp1 + temp2)
}

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( x$nZ>0){
     c1 <- c(c1, "Number of covariates")
     c2 <- c(c2, x$nZ)}
    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$np, " with spacing", x$delta,
        "overlap of ", x$overlap, 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]
  if( spam.format){
# Convert matrix to spam format  
  sM <- spind2spam( list( ind=cbind(Hi,Hj), ra=ra, da=da) )
# convert to an ordinary matrix (usually for debugging)     
  return(sM)}
  else{
    return( list( ind=cbind(Hi,Hj), ra=ra, da=da))}
}

`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.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)))
    }

 
}

LatticeKrig.sim<- function( x1,center.grid.list, beta=-.2,k=3,
                                          overlap=2.5,M=1){
  N1<- length( center.grid.list$x)
  N2<- length( center.grid.list$y)
# total number of basis functions/coefficients
  N<- N1*N2 
  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
#basis fucntions evaluated at points for simulation  
  PHI1<-  wendland.basis( x1, centers, theta=basis.delta, spam.format=FALSE,k=k,
                         flavor=0)
  H<-  lattice.precision(N1,N2, spam.format=TRUE, beta=beta)
  Q<- t(H)%*%(H) 
#  Note:  Q  <- t(H)%*%(H) is the precision matrix of coefficients
#  if Sigma = solve(Q) then to simulate from Sigma need
#     sqrt(Sigma) * N(0,1)   or  solve( sqrt(Q)) *N(0,1)
#
   E<- matrix(rnorm(N*M),N,M)
   chol(Q)-> Mc
# solve linear system   
   A <- forwardsolve(Mc, transpose = TRUE, E, upper.tri = TRUE)
        return( PHI1%*%A)
 
}


back to top