https://github.com/cran/ReacTran
Raw File
Tip revision: 6a2a464424c6eaca2ee6747a2ff1fc6e15105936 authored by Karline Soetaert on 24 December 2014, 00:00:00 UTC
version 1.4.2
Tip revision: 6a2a464
tran.volume.3D.R

##==============================================================================
## Take differences of an array
##==============================================================================

diff3D <- function(Array, along) {
  dimens <- dim(Array)
  if (length(dimens) != 3)
    stop("'Array' should be an array with dimension = 3")

  if (along == 1)
    return(Array[2:dimens[1], , ] - Array[1:(dimens[1]-1), , ])
  else if (along == 2)
    return(Array[, 2:dimens[2], ] - Array[, 1:(dimens[2]-1), ])
  else if (along == 3)
    return(Array[, , 2:dimens[3]] - Array[, , 1:(dimens[3]-1)])
  else  
    stop("'along' should be 1, 2, or 3")
}

# function to bind two matrices to an array, to left and right
Mbind <- function (Matleft = NULL, Array, Matright = NULL, along = 1)  {
  dimens <- dim(Array)
  if (length(dimens) != 3)
    stop("'Array' should be an array with dimension = 3")
  
  if (along < 1 | along > 3) 
    stop("'along' should be 1, 2, or 3")

  if (! is.null(Matright)) {
    if (sum(abs(dimens[-along] - dim(Matright))) != 0) 
    stop("'Matright' not compatible with Array")
    dimens[along] <- dimens[along] + 1
    Array <- mbindr(Array, Matright, along)
  }
  if (! is.null(Matleft)) {
   if (sum(abs(dimens[-along] - dim(Matleft))) != 0) 
    stop("'Matleft' not compatible with Array")
    dimens[along] <- dimens[along] + 1
    Array <- mbindl(Matleft, Array, along)
  }
  Array  
}

# function to bind two matrices to an array, to left and right
# NO error checking
mbind <- function (Mat1, Array, Mat2, along = 1)  {
  dimens <- dim(Array)
  
  dimens[along] <- dimens[along] + 2
     if (along == 3)
       array(dim = dimens, data = c(Mat1, Array, Mat2))
     else if (along == 1)
       aperm(array(dim = dimens[c(3, 2, 1)],
         data = c(t(Mat1), aperm(Array, c(3, 2, 1)), t(Mat2))),
         c(3, 2, 1))
     else if (along == 2)
       aperm(array(dim = dimens[c(1, 3, 2)],
         data = c(Mat1, aperm(Array, c(1, 3, 2)), Mat2)),
         c(1, 3, 2))
}

# function to bind a matrix to an array on the left
mbindl <- function (Mat1, Array, along = 1)  {
  dimens <- dim(Array)

  dimens[along] <- dimens[along]+1
  if (along == 3)
       array(dim = dimens, data = c(Mat1, Array))
  else if (along == 1)
       aperm(array(dim = dimens[c(3, 2, 1)],
         data = c(t(Mat1), aperm(Array, c(3, 2, 1)))),
         c(3, 2, 1))
  else if (along == 2)
       aperm(array(dim = dimens[c(1, 3, 2)],
         data = c(Mat1, aperm(Array, c(1, 3, 2)))),
         c(1, 3, 2))
   }
# function to bind a matrix to an array on the right
mbindr <- function (Array, Mat2, along = 1)  {
  dimens <- dim(Array)

  dimens[along] <- dimens[along]+1
  if (along == 3)
       array(dim = dimens, data = c(Array, Mat2))
  else if (along == 1)
       aperm(array(dim = dimens[c(3, 2, 1)],
         data = c(aperm(Array, c(3, 2, 1)), t(Mat2))),
         c(3, 2, 1))
  else if (along == 2)
       aperm(array(dim = dimens[c(1, 3, 2)],
         data = c(aperm(Array, c(1, 3, 2)), Mat2)),
         c(1, 3, 2))
}

##==============================================================================
## Transport in a three-dimensional finite difference grid
##==============================================================================

tran.volume.3D <- function(C, 
  C.x.up = C[1, , ], C.x.down = C[dim(C)[1], ,],
  C.y.up = C[, 1, ], C.y.down = C[,dim(C)[2],],
  C.z.up = C[, , 1], C.z.down = C[,,dim(C)[3]],
  F.x.up = NULL, F.x.down = NULL,
  F.y.up = NULL, F.y.down = NULL,
  F.z.up = NULL, F.z.down = NULL,
  Disp.grid = NULL, Disp.x = NULL, Disp.y = Disp.x, Disp.z = Disp.x,
  flow.grid = NULL, flow.x = 0, flow.y = 0, flow.z = 0,
  AFDW.grid = NULL, AFDW.x = 1, AFDW.y = AFDW.x, AFDW.z = AFDW.x,
  V = NULL, full.check = FALSE, full.output = FALSE)
											
{
  Nx <- dim(C)[1]
  Ny <- dim(C)[2]
  Nz <- dim(C)[3]

  if(length (C.x.up)   == 1) C.x.up   <- matrix(nrow=Ny,ncol=Nz,C.x.up)
  if(length (C.x.down) == 1) C.x.down <- matrix(nrow=Ny,ncol=Nz,C.x.down)
  if(length (C.y.up)   == 1) C.y.up   <- matrix(nrow=Nx,ncol=Nz,C.y.up)
  if(length (C.y.down) == 1) C.y.down <- matrix(nrow=Nx,ncol=Nz,C.y.down)
  if(length (C.z.up)   == 1) C.z.up   <- matrix(nrow=Nx,ncol=Ny,C.z.up)
  if(length (C.z.down) == 1) C.z.down <- matrix(nrow=Nx,ncol=Ny,C.z.down)

  if (is.null(V))
      stop("volume of each grid cell, 'V' should be specified  ")

#==============================================================================
# infilling of grids with only  x.int and y.int needed
#==============================================================================

  gridInt <- function(G.x,G.y,G.z,Name)   {    # define a function first

    # check if G.x and G.y and G.z is not NULL
    if (is.null(G.x) | is.null(G.y) | is.null(G.z))
      stop( (paste("error: ",Name,"and (",Name,".x and", Name,".y and",
             Name,".z) cannot be NULL at the same time", del="")))
    G.grid <- list()
    # infilling of x-array
    if (is.array(G.x)) {
      if (sum(abs(dim(G.x) - c(Nx+1,Ny,Nz)))!=0)
        stop (paste("error: ",Name,".x array not of correct (Nx+1) Ny ,Nz dimensions", del=""))
      G.grid$x.int <- G.x
    } else if (length(G.x) == 1) {
      G.grid$x.int <- array(data=G.x,dim=c(Nx+1,Ny,Nz))
    } else if (length(G.x) == Nx+1) {
      G.grid$x.int <- array(data=G.x,dim=c(Nx+1,Ny,Nz))
    } else
      stop (paste("error: ",Name,".x should be one element, a vector, or an array", del=""))

    # infilling of y-array
    if (is.array(G.y)) {
      if (sum(abs(dim(G.y) - c(Nx,Ny+1,Nz)))!=0)
        stop (paste("error: ",Name,".y array not of correct Nx, Ny+1 ,Nz dimensions", del=""))
      G.grid$y.int <- G.y
    } else if (length(G.y) == 1) {
      G.grid$y.int <- array(data=G.y,dim=c(Nx,Ny+1,Nz))
    } else if (length(G.y) == Ny+1) {
      G.grid$y.int <- aperm(array(data=G.y,dim=c(Ny+1,Nx,Nz)),c(2,1,3))
    } else
      stop (paste("error: ",Name,".y should be one element, a vector or an array", del=""))

    # infilling of z-array
    if (is.array(G.z)) {
      if (sum(abs(dim(G.z) - c(Nx,Ny,Nz+1)))!=0)
        stop (paste("error: ",Name,".z array not of correct Nx, Ny ,Nz+1 dimensions", del=""))
      G.grid$z.int <- G.z
    } else if (length(G.z) == 1) {
      G.grid$z.int <- array(data=G.z,dim=c(Nx,Ny,Nz+1))
    } else if (length(G.z) == Nz+1) {
      G.grid$z.int <- aperm(array(data=G.z,dim=c(Nz+1,Ny,Nx)),c(3,2,1))
    } else
      stop (paste("error: ",Name,".z should be one element, a vector or an array", del=""))

    G.grid
  }

# Need this for AFDW , D and v

  if (is.null(AFDW.grid)) AFDW.grid <- gridInt(AFDW.x,AFDW.y,AFDW.z,"AFDW")
  if (is.null(Disp.grid)) Disp.grid <- gridInt(Disp.x,Disp.y,Disp.z,"Disp")
  if (is.null(flow.grid)) flow.grid <- gridInt(flow.x,flow.y,flow.z,"flow")


#==============================================================================
# INPUT CHECKS  
#==============================================================================


  if (full.check) {

## check dimensions of input concentrations

    if (!is.null(C.x.up)) {
      if (!((length(C.x.up)==1) || (sum(abs(dim(C.x.up) - c(Ny,Nz)))==0)))
        stop("error: C.x.up should be of length 1 or a matrix with dim Ny, Nz")
    }
    if (!is.null(C.x.down)) {
      if (!((length(C.x.down)==1) || (sum(abs(dim(C.x.down) - c(Ny,Nz)))==0)))
        stop("error: C.x.down should be of length 1 or a matrix with dim Ny, Nz")
    }
    if (!is.null(C.y.up)) {
      if (!((length(C.y.up)==1) || (sum(abs(dim(C.y.up) - c(Nx,Nz)))==0)))
        stop("error: C.y.up should be of length 1 or a matrix with dim Nx, Nz")
    }
    if (!is.null(C.y.down)) {
      if (!((length(C.y.down)==1) || (sum(abs(dim(C.y.down) - c(Nx,Nz)))==0)))
        stop("error: C.y.down should be of length 1 or a matrix with dim Nx, Nz")
    }
    if (!is.null(C.z.up)) {
      if (!((length(C.z.up)==1) || (sum(abs(dim(C.z.up) - c(Nx,Ny)))==0)))
        stop("error: C.z.up should be of length 1 or a matrix with dim Nx, Ny")
    }
    if (!is.null(C.z.down)) {
      if (!((length(C.z.down)==1) || (sum(abs(dim(C.z.down) - c(Nx,Ny)))==0)))
        stop("error: C.z.down should be of length 1 or a matrix with dim Nx, Ny")
    }

# check dimensions of input fluxes
    if (!is.null(F.x.up)) {
      if (!((length(F.x.up)==1) || (sum(abs(dim(F.x.up) - c(Ny,Nz)))!=0)))
        stop("error: F.x.up should be of length 1 or a matrix with dim Ny, Nz")
    }
    if (!is.null(F.x.down)) {
      if (!((length(F.x.down)==1) || (sum(abs(dim(F.x.down) - c(Ny,Nz)))!=0)))
        stop("error: F.x.down should be of length 1 or a matrix with dim Ny, Nz")
    }
    if (!is.null(F.y.up)) {
      if (!((length(F.y.up)==1) || (sum(abs(dim(F.y.up) - c(Nx,Nz)))!=0)))
        stop("error: F.y.up should be of length 1 or a matrix with dim Nx, Nz")
    }
    if (!is.null(F.y.down)) {
      if (!((length(F.y.down)==1) || (sum(abs(dim(F.y.down) - c(Nx,Nz)))!=0)))
        stop("error: F.y.down should be of length 1 or a matrix with dim Nx, Nz")
    }
    if (!is.null(F.z.up)) {
      if (!((length(F.z.up)==1) || (sum(abs(dim(F.z.up) - c(Nx,Ny)))!=0)))
        stop("error: F.z.up should be of length 1 or a matrix with dim Nx, Ny")
    }
    if (!is.null(F.z.down)) {
      if (!((length(F.z.down)==1) || (sum(abs(dim(F.z.down) - c(Nx,Ny)))!=0)))
        stop("error: F.z.down should be of length 1 or a matrix with dim Nx, Ny")
    }

## check input of volumes
    if (any(V <= 0))
    	stop("error: the volumes should always be positive")
    if (dim(V)[1] != Nx || dim(V)[2] != Ny || dim(V)[3] != Nz)
    	stop("error: the dimension of 'V' should be = dimension of 'C'")

## check input of AFDW.grid

    if (is.null(AFDW.x) && is.null(AFDW.y) && is.null(AFDW.z) && is.null(AFDW.grid))
      stop("error: AFDW.x, AFDW.y, AFDW.z and AFDW.grid cannot be NULL at the same time")

    gn <- names(AFDW.grid)
    if (! "x.int" %in% gn)
      stop("error: AFDW.grid should be a list that contains 'x.int', the values at the interfaces of the grid cells in x-direction")
    if (! "y.int" %in% gn)
      stop("error: AFDW.grid should be a list that contains 'y.int', the values at the interfaces of the grid cells in y-direction")
    if (! "z.int" %in% gn)
      stop("error: AFDW.grid should be a list that contains 'z.int', the values at the interfaces of the grid cells in z-direction")
    if (is.null(AFDW.grid$x.int))
      stop("error: AFDW.grid$x.int should be a list with (numeric) values")
    if (is.null(AFDW.grid$y.int))
      stop("error: AFDW.grid$y.int should be a list with (numeric) values")
    if (is.null(AFDW.grid$z.int))
      stop("error: AFDW.grid$z.int should be a list with (numeric) values")
    if (any (AFDW.grid$x.int < 0)||any (AFDW.grid$x.int > 1))
    	stop("error: the AFDW should range between 0 and 1")
    if (any (AFDW.grid$y.int < 0)||any (AFDW.grid$y.int > 1))
	    stop("error: the AFDW should range between 0 and 1")
    if (any (AFDW.grid$z.int < 0)||any (AFDW.grid$z.int > 1))
	    stop("error: the AFDW should range between 0 and 1")

## check input of Disp.grid

    if (is.null(Disp.x) && is.null(Disp.y) && is.null(Disp.grid))
      stop("error: Disp.x, Disp.y, and Disp.grid cannot be NULL at the same time")

    gn <- names(Disp.grid)
    if (! "x.int" %in% gn)
      stop("error: DDisp.grid should be a list that contains 'x.int', the Disp values at the interfaces of the grid cells in x-direction")
    if (! "y.int" %in% gn)
      stop("error: Disp.grid should be a list that contains 'y.int', the Disp values at the interfaces of the grid cells in y-direction")
    if (! "z.int" %in% gn)
      stop("error: Disp.grid should be a list that contains 'z.int', the Disp values at the interfaces of the grid cells in z-direction")
    if (is.null(Disp.grid$x.int))
      stop("error: Disp.grid$x.int should be a list with (numeric) values")
    if (is.null(Disp.grid$y.int))
      stop("error: Disp.grid$y.int should be a list with (numeric) values")
    if (is.null(Disp.grid$z.int))
      stop("error: Disp.grid$z.int should be a list with (numeric) values")
    if (any (Disp.grid$x.int < 0)||any (Disp.grid$y.int < 0)||any (Disp.grid$z.int < 0))
    	stop("error: the diffusion coefficient should always be positive")

## check input of flow.grid

    if (is.null(flow.x) && is.null(flow.y) && is.null(flow.grid))
      stop("error: flow.x, flow.y, and flow.grid cannot be NULL at the same time")

    gn <- names(flow.grid)
    if (! "x.int" %in% gn)
      stop("error: flow.grid should be a list that contains 'x.int', the flow values at the interfaces of the grid cells in x-direction")
    if (! "y.int" %in% gn)
      stop("error: flow.grid should be a list that contains 'y.int', the flow values at the interfaces of the grid cells in y-direction")
    if (! "z.int" %in% gn)
      stop("error: flow.grid should be a list that contains 'z.int', the flow values at the interfaces of the grid cells in z-direction")
    if (is.null(flow.grid$x.int))
      stop("error: flow.grid$x.int should be a list with (numeric) values")
    if (is.null(flow.grid$y.int))
      stop("error: flow.grid$y.int should be a list with (numeric) values")
    if (is.null(flow.grid$z.int))
      stop("error: flow.grid$z.int should be a list with (numeric) values")


  }
## FUNCTION BODY: CALCULATIONS

## Calculate diffusive part of the flow
  x.Dif.flow <- -Disp.grid$x.int *
                diff3D(mbind(C.x.up, C, C.x.down, along=1), along=1)
  y.Dif.flow <-  -Disp.grid$y.int *
                diff3D(mbind(C.y.up, C, C.y.down, along=2), along=2)
  z.Dif.flow <-  -Disp.grid$z.int *
                diff3D(mbind(C.z.up, C, C.z.down, along=3), along=3)

## Calculate advective part of the flow
  x.Adv.flow <- 0
  
  if (any(flow.grid$x.int > 0) ) {
    vv <- flow.grid$x.int
    vv[vv < 0] <- 0
    x.Adv.flow <-  x.Adv.flow + vv * (
                      AFDW.grid$x.int  * mbindl (C.x.up,   C, along = 1)
                 + (1-AFDW.grid$x.int) * mbindr (C, C.x.down, along = 1))
  }
  if (any (flow.grid$x.int < 0))  {
    vv <- flow.grid$x.int
    vv [vv > 0] <- 0
    x.Adv.flow <-  x.Adv.flow + vv * (
                (1-AFDW.grid$x.int) * mbindl(C.x.up,   C, along = 1)
                 + AFDW.grid$x.int  * mbindr(C, C.x.down, along = 1))
  }
  y.Adv.flow <- 0
  if (any(flow.grid$y.int >0) ) {
    vv <- flow.grid$y.int
    vv[vv<0]<-0
    y.Adv.flow <-  y.Adv.flow + vv * (
                      AFDW.grid$y.int  * mbindl(C.y.up  , C, along = 2)
                 + (1-AFDW.grid$y.int) * mbindr(C, C.y.down, along = 2))
  }
  if (any (flow.grid$y.int < 0))  {
    vv <- flow.grid$y.int
    vv[vv>0]<-0
    y.Adv.flow <-  y.Adv.flow + vv * (
                (1-AFDW.grid$y.int) * mbindl(C.y.up  , C, along = 2)
                  + AFDW.grid$y.int * mbindr(C, C.y.down, along = 2))
  }
  z.Adv.flow <- 0
  if (any(flow.grid$z.int > 0 ) ) {
    vv <- flow.grid$z.int
    vv[vv<0]<-0
    z.Adv.flow <-  z.Adv.flow + vv * (
                       AFDW.grid$z.int * mbindl(C.z.up,   C, along = 3)
                 + (1-AFDW.grid$z.int) * mbindr(C, C.z.down, along = 3))
  }
  if (any (flow.grid$z.int < 0))  {
    vv <- flow.grid$z.int
    vv[vv>0]<-0
    z.Adv.flow <-  z.Adv.flow + vv * (
                  (1-AFDW.grid$z.int) * mbindl(C.z.up,   C, along = 3)
                 +    AFDW.grid$z.int * mbindr(C, C.z.down, along = 3))

  }

  x.flow <- x.Dif.flow + x.Adv.flow
  y.flow <- y.Dif.flow + y.Adv.flow
  z.flow <- z.Dif.flow + z.Adv.flow

## Impose boundary fluxes when needed
## Default boundary condition is no gradient
  if (! is.null (F.x.up[1]))
    x.flow[1,,]   <- F.x.up
  if (! is.null (F.x.down[1]))
    x.flow[dim(x.flow)[1],,] <- F.x.down
    
  if (! is.null (F.y.up[1]))
    y.flow[,1,]   <- F.y.up
  if (! is.null (F.y.down[1]))
    y.flow[,dim(y.flow)[2],] <- F.y.down

  if (! is.null (F.z.up[1]))
    z.flow[,,1]   <- F.z.up
  if (! is.null (F.z.down[1]))
    z.flow[,,dim(z.flow)[3]] <- F.z.down

## Calculate rate of change = flow gradient     NOG DOEN
  dFdx <- - (diff3D(x.flow,along=1))/ V
  dFdy <- - (diff3D(y.flow,along=2))/ V
  dFdz <- - (diff3D(z.flow,along=3))/ V

  if (!full.output) {
    return (list (dC = dFdx + dFdy + dFdz,               # Rate of change due to advective-diffuisve transport in each grid cell
                  F.x.up = x.flow[1,,],                  # flow across lower boundary interface; positive = IN
                  F.x.down = x.flow[dim(x.flow)[1],,],   # flow across lower boundary interface; positive = OUT
                  F.y.up = y.flow[,1,],                  # flow across lower boundary interface; positive = IN
                  F.y.down = y.flow[,dim(y.flow)[2],],   # flow across lower boundary interface; positive = OUT
                  F.z.up = z.flow[,,1],                  # flow across lower boundary interface; positive = IN
                  F.z.down = z.flow[,,dim(z.flow)[3]]))  # flow across lower boundary interface; positive = OUT

  } else {
    return (list (dC = dFdx + dFdy + dFdz,               # Rate of change due to transport in each grid cell
                  x.flow = x.flow,                       # flow across at the interface of each grid cell
                  y.flow = y.flow,                       # flow across at the interface of each grid cell
                  z.flow = z.flow,                       # flow across at the interface of each grid cell
                  F.x.up = x.flow[1,,],                  # flow across lower boundary interface; positive = IN
                  F.x.down = x.flow[dim(x.flow)[1],,],   # flow across lower boundary interface; positive = OUT
                  F.y.up = y.flow[,1,],                  # flow across lower boundary interface; positive = IN
                  F.y.down = y.flow[,dim(y.flow)[2],],   # flow across lower boundary interface; positive = OUT
                  F.z.up = z.flow[,,1],                  # flow across lower boundary interface; positive = IN
                  F.z.down = z.flow[,,dim(z.flow)[3]]))  # flow across lower boundary interface; positive = OUT
  }
} # end tran.3D

back to top