R/wtOptiwells.R

Defines functions .stopOnTooBigWellNumber

# owConfigureWell --------------------------------------------------------------
owConfigureWell <- function # Configure well(s) for Optiwells configuration
### Configure well(s) for Optiwells configuration
(
  wellName,
  ### well identifier(s). Must be unique within a well field.
  x,
  ### x coordinate(s) of well centre(s)
  y,
  ### y coordinate(s) of well centre(s)
  r,
  ### well radius(es)
  z1,
  ### Depth(s) below top of aquifer or initial water table to the top of the 
  ### screened interval of the well(s), in units of length.  
  z2, 
  ### Depth(s) below top of aquifer or initial water table to the bottom of the
  ### screened interval of the well(s), in units of length.
  sw
  ### Well-bore skin parameter(s), dimensionless. 
)
{
  data.frame(wellName = wellName, x = x, y = y, r = r, z1 = z1, z2 = z2, sw = sw,
             stringsAsFactors = FALSE)
  
  ### data frame with one row (per well) and columns elements \emph{wellName},
  ### \emph{x}, \emph{y}, \emph{r}, \emph{z1}, \emph{z2}, \emph{sw} defining a
  ### well.
}

# owRandomConfiguration --------------------------------------------------------
owRandomConfiguration <- function # Random Optiwells well field configuration
### Returns a random Optiwells configuration on which e.g.
### \code{\link{owGetDrawdowns}} can be run.
(
  numberOfWells, 
  ### number of wells in the well field (must be a number between 1 and 25).
  bb = 80,
  ### Thickness or saturated thickness of aquifer at beginning of simulation, 
  ### in units of length.  
  ...
  ### additional arguments that are passed to owRandomWellfield, such as: 
  ### \emph{rmean}, \emph{z1mean}, \emph{z2mean}, \emph{swmean}, \emph{rsd},
  ### \emph{z1sd}, \emph{z2sd}, \emph{swsd}, \emph{digits}, see
  ### \code{\link{owRandomWellfield}}
) 
{
  ##seealso<< \code{\link{owRandomWellfield}, \link{owConfigure}}
  
  .stopOnTooBigWellNumber(numberOfWells)
  
  n <- numberOfWells
  
  owConfigure(
    wellfield = owRandomWellfield(numberOfWells = n, ...), 
    aquifer = wtConfigureAquifer(bb = bb), 
    drainage = wtConfigureDrainage())

  ### list with elements \emph{wellfield} (configuration of well
  ### characteristics), \emph{aquifer} (WTAQ aquifer configuration) and
  ### \emph{drainage} (WTAQ drainage configuration), just as created by
  ### \code{\link{owConfigure}}
}

# .stopOnTooBigWellNumber ------------------------------------------------------
.stopOnTooBigWellNumber <- function(numberOfWells)
{
  if (numberOfWells < 1 || numberOfWells > 25) {
    stop("The number of wells must be between 1 and 25.")
  }
}

# owRandomWellfield ------------------------------------------------------------
owRandomWellfield <- function # Generation of a random wellfield
### Generation of a random wellfield
(
  numberOfWells, 
  ### number of wells in the well field (must be a number between 1 and 25).
  rmean, 
  ### mean value of normal distribution used to generate random values for 'r'
  ### (well radius)
  z1mean, 
  ### mean value of normal distribution used to generate random values for 'z1'
  ### (depth of begin of screen)
  z2mean, 
  ### mean value of normal distribution used to generate random values for 'z2'
  ### (depth of end of screen)
  swmean, 
  ### mean value of normal distribution used to generate random values for 'sw'
  ### (well-bore skin parameter)
  rsd = 0.1 * rmean, 
  ### standard deviation of normal distribution used to generate random values
  ### for 'r' (well radius). Default: 0.1 * rmean
  z1sd = 0.1 * z1mean, 
  ### standard deviation of normal distribution used to generate random values
  ### for 'z1'-values' (depth of begin of screen). Default: 0.1 * z1mean
  z2sd = 0.1 * z2mean, 
  ### standard deviation of normal distribution used to generate random values
  ### for 'z2'-values' (depth of end of screen). Default: 0.1 * z2mean
  swsd = 0.1 * swmean, 
  ### standard deviation of normal distribution used to generate random values
  ### for 'sw' (well-bore skin parameter). Default: 0.1 * swmean
  digits = 1
  ### number of decimal digits for values of r, z1, z2. Default: 1
)
{
  .stopOnTooBigWellNumber(numberOfWells)
  
  wellName <- sprintf("W%02d", 1:numberOfWells)
  x <- sample(300, numberOfWells)
  y <- sample(300, numberOfWells)
  r <-  round(rnorm(numberOfWells, rmean, rsd), digits)
  z1 <- round(rnorm(numberOfWells, z1mean, z1sd), digits)
  sw <- round(rnorm(numberOfWells, swmean, swsd), 5)
  
  # guarantee that z2 is greater than z1
  z2 <- round(z1 + abs(rnorm(numberOfWells, z2mean, z2sd) - z1), digits)
  
  # guarantee that sw is positive
  sw <- pmax(0, sw)
  
  owConfigureWell(
    wellName = wellName, x = x, y = y, r = r, z1 = z1, z2 = z2, sw = sw)  
  
  ### data frame with each row representing a well of the well field and the 
  ### columns representing the well properties: \emph{wellName} (well name),
  ### \emph{x} (x-coordinate of well), \emph{y-coordinate of well}, \emph{r}
  ### (well radius), \emph{z1} (depth of begin of well screen), \emph{z2} (depth
  ### of end of well screen), \emph{sw} (well-bore skin parameter)
}

# owConfigure --------------------------------------------------------------
owConfigure <- function # Definition of an Optiwells well field configuration 
### Returns a user-defined Optiwells configuration on which e.g.
### \code{\link{owGetDrawdowns}} can be run.
(
  wellfield = NULL,
  ### Optiwells wellfield configuration. Data frame 
  aquifer = NULL,
  ### WTAQ aquifer configuration as returned by \code{\link{wtConfigureAquifer}}
  drainage = NULL
  ### WTAQ drainage configuration as returned by \code{\link{wtConfigureDrainage}}
) 
{
  ##seealso<< \code{\link{owRandomConfiguration}}
  
  if (is.null(wellfield)) {
    stop(paste("Parameter 'wellfield' is undefined. Create a wellfield by",
               "binding well configurations as created with owConfigureWell", 
               "to a data frame using rbind. See example in tutorial."))
  }
  
  numberOfWells <- nrow(wellfield)
  .stopOnTooBigWellNumber(numberOfWells)
  
  if (is.null(aquifer))
  {
    stop(paste("Parameter 'aquifer' is undefined. Create a WTAQ aquifer",
         "configuration by using wtConfigureAquifer."))
  }
  
  if (is.null(drainage)) {
    stop(paste("Parameter 'drainage' is undefined. Create a WTAQ drainage",
               "configuration by using wtConfigureDrainage."))
  }
  
  list(wellfield = wellfield,
       aquifer = aquifer,
       drainage = drainage)
  
  ### list with elements \emph{wellfield} (configuration of well
  ### characteristics), \emph{aquifer} (WTAQ aquifer configuration) and
  ### \emph{drainage} (WTAQ drainage configuration).
}

# wtConfigurationToOwConfiguration ---------------------------------------------
wtConfigurationToOwConfiguration <- function
### Generate Optiwells configuration from WTAQ configuration
(
  wtaqConfiguration
  ### WTAQ configuration as retrieved by \code{\link{wtConfigure}}
)
{
  ##seealso<< \code{\link{owConfigurationToWtConfiguration}}
    
  distances <- wtConfiguredDistances(wtaqConfiguration)
  
  n <- length(distances)
  y <- 0
  
  wellfield <- .pumpingWellToWellfieldWell(
    wtaqConfiguration$pumpwell, 
    x = distances[1], # = 0
    y = y)  
  
  for (i in seq(from = 1, by = 1, length.out = n - 1)) {
    
    well.new <- .observationWellToWellfieldWell(
      wtaqConfiguration$obswells[[i]], 
      x = distances[i + 1], 
      y = y, 
      sw = wtaqConfiguration$pumpwell$sw)
    
    wellfield <- rbind(wellfield, well.new)
  }

  owConfigure(
    wellfield = wellfield,
    aquifer = wtaqConfiguration$aquifer,
    drainage = wtaqConfiguration$drainage)
  
  ### list with elements \emph{wellfield} (configuration of well
  ### characteristics), \emph{aquifer} (WTAQ aquifer configuration) and
  ### \emph{drainage} (WTAQ drainage configuration), just as creaetd by
  ### \code{\link{owConfigure}}. The list elements \emph{aquifer} and 
  ### \emph{drainage} are copied from the given WTAQ configuration. The 
  ### wellfield configuration is generated from the information on the pumping
  ### well and on the observation wells given in that same configuration.
}

# owWellDistances ---------------------------------------------------------------
owWellDistances <- function # Distances between wells
### Calculates distances between wells based on coordinates given in Optiwells
### configuration of the wellfield
(
  wellfield, 
  ### data frame as e.g. retrieved by \code{\link{owRandomWellfield}} with each
  ### row representing a well of the well field.
  referenceWell = 1
  ### Number of reference well corresponding with row number in 
  ### \emph{wellfield}. For each well the distance to the reference well is
  ### calculated. Default: 1
)
{
  dx <- wellfield$x - wellfield$x[referenceWell]
  dy <- wellfield$y - wellfield$y[referenceWell]
  sqrt(dx*dx + dy*dy)
  
  ### vector of numeric representing the distances of each well to a reference
  ### well (determined by \emph{referenceWell})
}

# .checkAndGetUserTimes --------------------------------------------------------
.checkAndGetUserTimes <- function
(
  times, 
  owConfiguration, 
  ### list with elements \emph{wtaqConfiguration}, \emph{wellfield}, 
  ### \emph{observation} (= data frame containing times and drawdowns)
  activeWell  
) 
{
  return (times)
  
    # If times are given, return these times, otherwise check the WTAQ
    # times configuration if user times are to be used
    if (! is.null(times)) {
      userTimes <- data.frame(t = times)
    }
    else if (owConfiguration$wtaqConfiguration$times$its == 1) { # user-specified times?
      
      # Stop if there are no user-specified times
      userTimes <- owConfiguration$observation[[activeWell]]
      
      if (isNullOrEmpty(userTimes)) {
        stop("*** According to the WTAQ configuration user-specified times are to be ",
             "used but there were\nno times found in owConfiguration$observation for well ",
             owConfiguration$wellfield$wellName[activeWell], 
             "!\nYou may want to set the times argument of this function!")
      }
    }
    else { # log-cycle time steps
      userTimes <- NULL
    } 
    
    userTimes  
  ### data frame with column \emph{t}
}

# owConfigurationToWtConfiguration ---------------------------------------------
owConfigurationToWtConfiguration <- function # WTAQ configuration from Optiwells configuration
### convert Optiwells configuration to WTAQ configuration with one well
### being selected as pumping well
(
  owConfiguration, 
  ### Optiwells configuration as created with \code{\link{owConfigure}}
  activeWell, 
  ### number of active well = row number in \emph{owConfiguration$wellfield}
  Q,
  ### discharge of active well
  times = NULL,
  ### vector of times for which drawdowns are to be calculated    
  solution = wtConfigureSolution()
  ### List of solution parameters as retrieved by
  ### \code{\link{wtConfigureSolution}}.
) 
{
  ##seealso<< \code{\link{wtConfigurationToOwConfiguration}}  
  
  # Wellfield configuration
  wellfield <- owConfiguration$wellfield
  
  # distances between non-active wells and active well
  distances <- owWellDistances(wellfield, activeWell)

  # prepare data frame with "user" times
  userTimes <- data.frame(t = times)
  
  # Create list of observation wells
  observationWells <- list()
  
  # Loop through the wells in the wellfield
  for (i in 1:nrow(wellfield)) {

    if (i == activeWell) {
      
      # Create pumping well configuration
      pumpingWell <- .wellfieldWellToPumpingWell(
        wellfieldWell = wellfield[activeWell, ], 
        drawdownTimeseries = userTimes,
        discharge = Q)
    }        
    else { 
      
      # Create observation well configuration      
      observationWell <- .wellfieldWellToObservationWell(
        wellfieldWell = wellfield[i, ], 
        drawdownTimeseries = userTimes, 
        distanceToPumpingWell = distances[i])
      
      observationWells[[length(observationWells) + 1]] <- observationWell
    }
  }
  
  # Start with WTAQ basic configuration as given in Optiwells configuration
  wtConfigure(
    general = wtConfigureGeneral(
      title = "Configuration generated by R-package kwb.wtaq"),
    aquifer = owConfiguration$aquifer,
    drainage = owConfiguration$drainage,
    times = wtConfigureTimes(its = 1, tlast = 0, nlc = 0, nox = 0),
    solution = solution,
    pumpwell = pumpingWell, 
    obswells = observationWells)
  
  ### WTAQ configuration as e.g. returned by \code{\link{wtConfigure}}
}

# .wellfieldWellToPumpingWell --------------------------------------------------
.wellfieldWellToPumpingWell <- function
(
  wellfieldWell, drawdownTimeseries, discharge
)
{
  wtConfigurePumpwell(
    irun  = 1, 
    ipws  = 0, 
    ipwd  = 1, 
    ipump = 1, 
    qq    = discharge, 
    rw    = wellfieldWell$r, 
    rc    = wellfieldWell$r, 
    zpd   = wellfieldWell$z1, 
    zpl   = wellfieldWell$z2, 
    sw    = wellfieldWell$sw, 
    tspw  = drawdownTimeseries,
    pwname = wellfieldWell$wellName)
}

# .wellfieldWellToObservationWell ----------------------------------------------
.wellfieldWellToObservationWell <- function
(
  wellfieldWell, drawdownTimeseries, distanceToPumpingWell
)
{
  wtConfigureObservationWell(
    irun   = 1, 
    obname = wellfieldWell$wellName,
    iows   = 0, 
    idpr   = 1, 
    r      = distanceToPumpingWell, 
    z1     = wellfieldWell$z1, 
    z2     = wellfieldWell$z2, 
    zp     = 0, 
    rp     = wellfieldWell$r, 
    xll    = wellfieldWell$z2 - wellfieldWell$z1, 
    tsobs  = drawdownTimeseries)
}

# .pumpingWellToWellfieldWell --------------------------------------------------
.pumpingWellToWellfieldWell <- function
(
  pumpingWell, x, y
)
{
  owConfigureWell(
    wellName = pumpingWell$pwname, 
    x = x, 
    y = y, 
    r = pumpingWell$rw, 
    z1 = pumpingWell$zpd, 
    z2 = pumpingWell$zpl, 
    sw = pumpingWell$sw)
}

# .observationWellToWellfieldWell ----------------------------------------------
.observationWellToWellfieldWell <- function
(
  observationWell, x, y, sw
)
{
  owConfigureWell(
    wellName = observationWell$obname, 
    x = x, 
    y = y, 
    r = observationWell$rp, 
    z1 = observationWell$z1, 
    z2 = observationWell$z2, 
    sw = sw)
}

# # owAddDrawdownLines -----------------------------------------------------------
# owAddDrawdownLines <- function
# ### adds lines representing drawdowns to an existing plot
# (
#   wtaqResult
#   ### WTAQ result as e.g. retrieved by \code{\link{wtRunConfiguration}}.
# )
# {
#   if (! ("wtaqResult" %in% class(wtaqResult))) {
#     stop("wtaqResult must be of class wtaqResult!")
#   }  
#   
#   .owAddDrawdownLines()
# }

# .owAddDrawdownLines ----------------------------------------------------------
.owAddDrawdownLines <- function # add drawdown lines to plot
### Add drawdown lines to plot
(
  wres, 
  ### WTAQ result as e.g. retrieved by \code{\link{wtRunConfiguration}}.
  xpos, # wdist, 
  ### x positions of drawdowns 
  myTime, 
  ### vector of times
  refLevel = 0, 
  ### reference level (top of aquifer)
  col = grey((1:(length(myTime)))/(length(myTime)+1)), 
  ilabel = 1,
  ### index of well at which time labels shall appear
  ...
)
{
  # Recycle colour vector to desired length
  col = rep(col, length.out=length(myTime))
  
  i <- 1
  for (ti in myTime) {
    
    # get row out of wres (exclude time!)
    resr <- wres[wres$TIME == ti, -1]
    
    # order x positions
    #ord <- order(wdist)
    ord <- order(xpos)
    
    # Add lines
    lines(xpos[ord], refLevel - resr[ord], col = col[i], ...)

    # Add labels indicating the time
    offset <- 0 # 0.01*diff(par()$usr[3:4])
    text(xpos[ilabel], refLevel-resr[ilabel]-offset, sprintf("t=%s", ti))    

    i <- i+1
  }
}

# owGetDrawdowns ---------------------------------------------------------------
owGetDrawdowns <- function # Drawdowns for Optiwells configuration and pump discharges
### Calculate drawdowns for given Optiwells configuration and pump discharges
(
  owConfiguration, 
  ### Optiwells configuration as e.g. retrieved by \code{\link{owRandomConfiguration}}.
  Q,
  ### vector of discharges at the wells in the order of wells in 
  ### owConfiguration$wellfield
  times = NULL,
  ### vector of times for which drawdowns are to be calculated
  solution = NULL,
  ### List of solution parameters as retrieved by 
  ### \code{\link{wtConfigureSolution}}. If not specified, a default 
  ### configuration, as retrieved by \code{\link{wtDefaultConfigurationSolution}} 
  ### is used.
  to.matrix = TRUE,
  ### if TRUE, the results returned by \code{\link{wtRunConfiguration}} that 
  ### come in "long" form (the drawdowns for each well appear in blocks one
  ### below each other with a column "WELL" indicating the well name) are
  ### converted to "matrix" (= wide) form in which the calculated drawdowns
  ### appear in columns one beside the other.
  ... 
  ### additional arguments passed to \code{\link{wtRunConfiguration}}, such as
  ### \emph{show.results} or \emph{dbg}, see there.
)
{
  ##seealso<< \code{\link{owSuperposeDrawdowns}}    
  
  # If there are not exactly as many discharge values as there are wells, 
  # give a warning but nevertheless recycle the Q vector to as many values
  # as required
  n <- nrow(owConfiguration$wellfield)
  wellNames <- owConfiguration$wellfield$wellName
  
  if (length(Q) != n) {
    warning(sprintf("Length of Q (%d) does not equal number of wells (%d)!\n%s",
                    length(Q), n, "The Q values are recycled!"))
    Q <- recycle(Q, n)
  }  
  
  if (is.null(solution)) {
    solution <- wtDefaultConfigurationSolution(
      aqtype = owConfiguration$aquifer$aqtype, 
      idra = owConfiguration$drainage$idra)
  }
  
  # Prepare result list containing drawdowns
  drawdownList <- list()

  # Loop through wells  
  for (i in 1:n) {
    
    ### Only run WTAQ if the well is pumping
    if (Q[i] > 0) {
      
      # Create i-th WTAQ configuration with the i-th well active
      wtaqConfiguration <- owConfigurationToWtConfiguration(
        owConfiguration, 
        activeWell = i, 
        Q = Q[i], 
        times = times,
        solution = solution)
      
      # Run WTAQ with i-th well active and save result to list drawdownList
      wtaqResult <- wtRunConfiguration(
        wtaqConfiguration, 
        fileExtension = as.character(i), 
        ...)
    }
    else {
      wtaqResult <- .zeroDrawdowns(wellNames, times)
    }

    # Cast to "wide" view (package reshape) if required. Restore the 
    # attributes, such as "src", "wtaqConfiguration" (cast removes them!)
    if (to.matrix) {
      
      wtaqResult <- hsRestoreAttributes(
        cast(wtaqResult, TIME ~ WELL, value = "CALCDD"), 
        attributes(wtaqResult))        
    }    
    
    drawdownList[[wellNames[i]]] <- wtaqResult
  }
  
  # Set attribute oconf
  attr(drawdownList, "oconf") <- owConfiguration
  
  drawdownList

  ### list with as many elements as there are wells defined in the Optiwells 
  ### configuration (list element \emph{wellfield} of \emph{owConfiguration}). For each
  ### pumping well (well with Q > 0) the list element at the corresponding
  ### position contains a data frame holding the drawdowns calculated by WTAQ
  ### with that well pumping alone and the other wells being observation wells.
  ### For each non-pumping well (well with Q = 0) the list element will be NULL.
  ### This list can then be used to calculate the superposition of drawdowns.
}

# .zeroDrawdowns ---------------------------------------------------------------
.zeroDrawdowns <- function # wtaqResult-like structure, filled with zeroes
### wtaqResult-like structure, filled with zeroes
(
  wellNames, times
)
{
  numberOfWells <- length(wellNames)
  numberOfTimes <- length(times)
  
  data.frame(TIME = rep(times, numberOfWells), 
             WELL = sort(rep(wellNames, numberOfTimes)), 
             CALCDD = rep(0, numberOfWells * numberOfTimes),
             stringsAsFactors = FALSE)
}

# .owDrawdownListToDataFrame ---------------------------------------------------
.owDrawdownListToDataFrame <- function
### Convert drawdown list to data frame introducing column PW indicating the
### pumping well
(
  drawdownList
)
{
  # Init result data frame
  res <- NULL
  
  # Loop through list elements
  for (i in 1:length(drawdownList)) {    
    # Append to result data frame
    res <- rbind(
      res, data.frame(PW = attributes(drawdownList)$oconf$wellfield$wellName[i], drawdownList[[i]]))
  }
  
  hsRestoreAttributes(res, attributes(drawdownList))
}

# .owPlotDrawdowns.1 -----------------------------------------------------------
.owPlotDrawdowns.1 <- function
(
  drawdownList
)
{
  dd.df <- .owDrawdownListToDataFrame(drawdownList)
  dat <- hsMatrixToListForm(dd.df, 
                            keyFields = names(dd.df)[1:2], 
                            colNamePar = "WELL", 
                            colNameVal = "CALCDD")
  yrng <- range(dat$CALCDD)

  tro <- lattice::xyplot(CALCDD ~ TIME | PW,                   
                data = dat,
                groups = dat$WELL,
                ylim = rev(yrng + c(-1,1) * 0.04 * diff(yrng)),
                type = c("b", "g"), 
                strip = lattice::strip.custom(
                  factor.levels = 
                    sprintf("Pumping well: %s", 
                            attributes(drawdownList)$oconf$wellfield$wellName)),
                auto.key = TRUE, 
                as.table = TRUE
  )
  print(tro)
}

# .owPlotDrawdowns.2 -----------------------------------------------------------
.owPlotDrawdowns.2 <- function
(
  dd
)
{
  n <- length(dd)
  att <- attributes(dd)
  cols <- rainbow(n)
  
  # Plot into matrix of two rows and one column
  opar <- par(mfrow = c(2, 1))    
  
  wellfield <- att$oconf$wellfield
  
  # Loop through list elements
  for (i in 1:n) {
    
    # 1st plot: top view on well field with well i as reference well
    #.owPlotTopView(wellfield, col = cols, ref = i)
    owPlotConfiguration(att$oconf, referenceWell = i, view = "top")
    
    # 2nd plot: side view on well field    
    #wtaqConfiguration <- attributes(dd[[i]])$wtaqConfiguration    
    #owPlotConfiguration(att$oconf, referenceWell = i, view = "top")#, xpos=xpos, xlim=xlim) #xpos=wf$x
    wtPlotResult(dd[[i]], plottype = "d", pumpingWellName = wellfield$wellName[i])
  }
  
  # Reset graphical parameters
  par(opar)  
}

# owPlotDrawdowns --------------------------------------------------------------
owPlotDrawdowns <- function # plot drawdown (under construction!)
### plot drawdown (under construction!)
(
  drawdownList, 
  topview = TRUE, 
  overlay = FALSE,
  fixview = FALSE
)
{
  # 1st plot: drawdown over time for each well being the pumping well
  .owPlotDrawdowns.1(drawdownList)
  
  # 2nd plot: one page per well: drawdown around well being pumping well
  .owPlotDrawdowns.2(drawdownList)
}

# owSuperposeDrawdowns ---------------------------------------------------------
owSuperposeDrawdowns <- function # superpose drawdowns
### superpose drawdowns in list of drawdowns as returned by
### \code{\link{owGetDrawdowns}}.
(
  drawdownList,
  ### list of drawdowns as returned by \code{\link{owGetDrawdowns}}
  dbg = FALSE
  ### if TRUE, debug messages are shown. Default: FALSE  
) 
{
  ##seealso<< \code{\link{owGetDrawdowns}}      
  
  # Number of elements in the list of WTAQ results
  n <- length(drawdownList)

  # Init result data frame
  result <- NULL
  
  for (i in 1:n) {
    
    wtaqResult <- drawdownList[[i]]
    
    if (i == 1) {
      result <- wtaqResult      
    } 
    else {      
      result[, -1] <- result[, -1] + wtaqResult[, -1]
    }
  }
  
  result
  ### data frame
}
KWB-R/kwb.wtaq documentation built on June 17, 2022, 3:05 a.m.