R/SoilMoistureTemperatureRegimes.R

Defines functions calc_RRs_Maestas2016 Maestas2016_Table1 calc_RRs_Chambers2014 Chambers2014_Table1 calc_SMTRs SMR_logic STR_logic SMRq_names SMR_names STR_names

Documented in calc_RRs_Chambers2014 calc_RRs_Maestas2016 calc_SMTRs Chambers2014_Table1 Maestas2016_Table1 SMR_logic SMR_names SMRq_names STR_logic STR_names

########################
#------ SMTR functions

# Based on references provided by Chambers, J. C., D. A. Pyke, J. D. Maestas, M.
# Pellant, C. S. Boyd, S. B. Campbell, S. Espinosa, D. W. Havlina, K. E. Mayer,
# and A. Wuenschel. 2014. Using Resistance and Resilience Concepts to Reduce
# Impacts of Invasive Annual Grasses and Altered Fire Regimes on the Sagebrush
# Ecosystem and Greater Sage-Grouse: A Strategic Multi-Scale Approach. Gen.
# Tech. Rep. RMRS-GTR-326. U.S. Department of Agriculture, Forest Service, Rocky
# Mountain Research Station, Fort Collins, CO.
#

#' Categories of soil temperature regimes and soil moisture regimes
#'
#' @section Definitions: Soil temperature and moisture regimes are defined in
#'   SSS 2014. Our operationalization is explained in the vignette
#'   \var{SoilMoistureRegimes_SoilTemperatureRegimes}.
#'
#' @references Soil Survey Staff. 2014. Keys to soil taxonomy, 12th ed., USDA
#'   Natural Resources Conservation Service, Washington, DC.
#'
#' @examples
#' vignette(
#'   "SoilMoistureRegimes_SoilTemperatureRegimes",
#'   package = "rSOILWAT2"
#' )
#'
#' @name STMR
NULL

#' Soil temperature regime categories
#' @rdname STMR
#' @export
STR_names <- function() {
  c("Hyperthermic", "Thermic", "Mesic", "Frigid", "Cryic", "Gelic")
}

#' Soil moisture regime categories
#' @rdname STMR
#' @export
SMR_names <- function() {
  c("Anhydrous", "Aridic", "Xeric", "Ustic", "Udic", "Perudic", "Aquic")
}

#' Soil moisture regime categories including qualifiers
#' @rdname STMR
#' @export
SMRq_names <- function() {
  c(
    "Extreme-Aridic", "Typic-Aridic", "Weak-Aridic", #Aridic
    "Dry-Xeric", "Typic-Xeric", # Xeric
    "Typic-Tempustic", "Xeric-Tempustic", "Wet-Tempustic", "Aridic-Tropustic",
    "Typic-Tropustic", "Udic-Ustic", # Ustic
    "Typic-Udic", "Dry-Tropudic", "Dry-Tempudic" # Udic
  )
}

#' \var{NRCS} soil temperature regimes
#'
#' Soil temperature regimes are determined based on
#' Soil Survey Staff 2014 (page 31, Key to Soil Taxonomy).
#'
#' @references Soil Survey Staff (2014). Keys to soil taxonomy,
#'   12th ed. USDA Natural Resources Conservation Service, Washington, DC.
#'
#' @section Notes: The implementation currently ignores the distinction
#'   between \var{iso-} and not \var{iso-} (p.31 of Soil Survey Staff 2014)
#'
#' @keywords internal
STR_logic <- function(MAST, MSST, SatSoilSummer_days, has_permafrost,
  has_Ohorizon
) {

  tmp <- STR_names()
  Tregime <- rep(0L, length(tmp))
  names(Tregime) <- tmp

  if (anyNA(MAST) || anyNA(has_permafrost)) {
    Tregime[] <- NA # nolint: extraction_operator_linter.
    return(Tregime)
  }

  if (MAST >= 22) {
      Tregime[["Hyperthermic"]] <- 1L
  } else if (MAST >= 15) {
      Tregime[["Thermic"]] <- 1L
  } else if (MAST >= 8) {
      Tregime[["Mesic"]] <- 1L

  } else if (MAST > 0 && !has_permafrost) {
    if (any(anyNA(SatSoilSummer_days), anyNA(has_Ohorizon), anyNA(MSST))) {
      Tregime[c("Cryic", "Frigid")] <- NA
      return(Tregime)
    }

    # mineral soils
    if (SatSoilSummer_days > 0) {
      # "soil is saturated with water during some part of summer"
      if (has_Ohorizon) {
        # TODO: should be: 'O-horizon' OR 'histic epipedon'
        if (MSST < 6) {
          Tregime[["Cryic"]] <- 1L
        } else {
          Tregime[["Frigid"]] <- 1L
        }
      } else {
        if (MSST < 13) {
          Tregime[["Cryic"]] <- 1L
        } else {
          Tregime[["Frigid"]] <- 1L
        }
      }

    } else {
      # "not saturated with water during some part of the summer"
      if (has_Ohorizon) {
        if (MSST < 8) {
          Tregime[["Cryic"]] <- 1L
        } else {
          Tregime[["Frigid"]] <- 1L
        }
      } else {
        if (MSST < 15) {
          Tregime[["Cryic"]] <- 1L
        } else {
          Tregime[["Frigid"]] <- 1L
        }
      }
    }
    # TODO: else organic soils: cryic if mean(T50jja) > 0 C and < 6 C

  } else if (MAST <= 0 || has_permafrost) {
    # limit should be 1 C for Gelisols
    Tregime[["Gelic"]] <- 1L
  }

  Tregime
}


#' \var{NRCS} soil moisture regimes
#'
#' Soil moisture regimes are determined based on
#' Soil Survey Staff 2014 (pages 28-31, Key to Soil Taxonomy).
#'
#' @references Soil Survey Staff (2014). Keys to soil taxonomy,
#'   12th ed. USDA Natural Resources Conservation Service, Washington, DC.
#'
#' @keywords internal
SMR_logic <- function(ACS_COND1, ACS_COND2, ACS_COND3, MCS_COND0,
  MCS_COND1, MCS_COND2, MCS_COND2_1, MCS_COND2_2, MCS_COND2_3, MCS_COND3,
  MCS_COND3_1, MCS_COND4, MCS_COND5, MCS_COND6, MCS_COND6_1, MCS_COND7,
  MCS_COND8, MCS_COND9, MCS_COND10, has_permafrost
) {

  tmp <- c(SMR_names(), SMRq_names())
  Sregime <- rep(0L, length(tmp))
  names(Sregime) <- tmp

  # Anhydrous condition: Soil Survey Staff 2010: p.16
  # == Soil Survey Staff 2014: p.18
  # we ignore test for 'ice-cemented permafrost' and 'rupture-resistance class'
  if (any(anyNA(ACS_COND1), anyNA(ACS_COND2), anyNA(ACS_COND3))) {
    Sregime[["Anhydrous"]] <- NA

  } else if (ACS_COND1 && ACS_COND2 && ACS_COND3) {
    Sregime[["Anhydrous"]] <- 1L
  }

  # We ignore 'Aquic' because we have no information on soil oxygen content
  if (
    any(
      anyNA(MCS_COND0), anyNA(MCS_COND1), anyNA(MCS_COND2),
      anyNA(MCS_COND2_1), anyNA(MCS_COND2_2), anyNA(MCS_COND2_3),
      anyNA(MCS_COND3), anyNA(MCS_COND3_1), anyNA(MCS_COND4), anyNA(MCS_COND5),
      anyNA(MCS_COND6), anyNA(MCS_COND6_1), anyNA(MCS_COND7), anyNA(MCS_COND8),
      anyNA(MCS_COND9), anyNA(MCS_COND10), anyNA(has_permafrost)
    )
  ) {

    Sregime[-which("Anhydrous" == names(Sregime))] <- NA
    return(Sregime)
  }

  if (MCS_COND0) {
    # Perudic soil moisture regime
    Sregime[["Perudic"]] <- 1L

  } else if (MCS_COND1 && MCS_COND2) {
    # Aridic soil moisture regime; The limits set for soil temperature
    # exclude from these soil moisture regimes soils in the very cold and dry
    # polar regions and in areas at high elevations. Such soils are considered
    # to have anhydrous condition
    Sregime[["Aridic"]] <- 1L

    # Qualifier for aridic SMR
    if (MCS_COND10) {
      Sregime[["Extreme-Aridic"]] <- 1L
    } else if (MCS_COND2_3) {
      # NOTE: COND2_3: assumes that 'MaxContDaysAnyMoistCumAbove8' is
      # equivalent to jNSM variable 'ncpm[[2]]'
      Sregime[["Typic-Aridic"]] <- 1L
    } else {
      Sregime[["Weak-Aridic"]] <- 1L
    }

  } else if (!MCS_COND6 && MCS_COND9 && !MCS_COND4 && MCS_COND5) {
    # Xeric soil moisture regime
    Sregime[["Xeric"]] <- 1L

    # Qualifier for xeric SMR
    if (MCS_COND6_1) {
      # NOTE: this conditional assumes that 'DryDaysConsecSummer' is equivalent
      # to jNSM variable 'nccd'
      Sregime[["Dry-Xeric"]] <- 1L
    } else {
      Sregime[["Typic-Xeric"]] <- 1L
    }

  } else if (
      MCS_COND3 &&
      (!MCS_COND4 && MCS_COND5 && MCS_COND6 || (MCS_COND4 || !MCS_COND5))
  ) {

    # Udic soil moisture regime - #we ignore test for 'three- phase system'
    # during T50 > 5
    Sregime[["Udic"]] <- 1L

    # Qualifier for udic SMR
    if (MCS_COND3_1) {
      Sregime[["Typic-Udic"]] <- 1L
    } else if (!MCS_COND5) {
      Sregime[["Dry-Tropudic"]] <- 1L
    } else {
      Sregime[["Dry-Tempudic"]] <- 1L
    }

  } else if (
    !has_permafrost &&
    !MCS_COND3 &&
    (
      (MCS_COND4 || !MCS_COND5) &&
      (MCS_COND7 || MCS_COND8) ||
      !MCS_COND4 &&
      MCS_COND5 &&
      !MCS_COND1 &&
      (MCS_COND9 && MCS_COND6 || !MCS_COND9)
    )
  ) {

    # Ustic soil moisture regime
    Sregime[["Ustic"]] <- 1L

    # Qualifier for ustic SMR
    if (MCS_COND5) {
      if (!MCS_COND9) {
        # NOTE: this conditional assumes that 'MoistDaysConsecWinter' is
        # equivalent to jNSM variable 'nccm'
        Sregime[["Typic-Tempustic"]] <- 1L
      } else if (!MCS_COND6) {
        # NOTE: this conditional assumes that 'DryDaysConsecSummer' is
        # equivalent to jNSM variable 'nccd'
        Sregime[["Xeric-Tempustic"]] <- 1L
      } else {
        Sregime[["Wet-Tempustic"]] <- 1L
      }
    } else {
      # NOTE: COND2_1 and COND2_2: assume that 'MaxContDaysAnyMoistCumAbove8'
      # is equivalent to jNSM variable 'ncpm[[2]]'
      if (MCS_COND2_1) {
        Sregime[["Aridic-Tropustic"]] <- 1L
      } else if (MCS_COND2_2) {
        Sregime[["Typic-Tropustic"]] <- 1L
      } else {
        Sregime[["Udic-Ustic"]] <- 1L
      }
    }
  }

  Sregime
}



#' Calculate soil moisture and soil temperature regimes and underlying
#' conditions
#'
#' Calculations are based on SSS (2014, 2015) and explained in detail in the
#' \code{vignette(
#'   topic = "SoilMoistureRegimes_SoilTemperatureRegimes",
#'   package = "rSOILWAT2"
#' )}.
#'
#' @param sim_in An object of class \code{\linkS4class{swInputData}}. The
#'   \pkg{rSOILWAT2} simulation input.
#' @param sim_out An object of class \code{\linkS4class{swOutput}}. The
#'   \pkg{rSOILWAT2} simulation output. If \code{NULL} then \code{sim_agg}
#'   must be provided instead.
#' @param sim_agg A named list. The prepared \pkg{rSOILWAT2} simulation output.
#'   If \code{NULL} then \code{sim_out} must be provided so that the elements
#'   of \code{sim_agg} can be determined internally. The list contains:
#'   \var{"soiltemp.dy.all"}, \var{"soiltemp.yr.all"}, \var{"soiltemp.mo.all"},
#'   \var{"vwcmatric.dy.all"}, \var{"swpmatric.dy.all"}, \var{"prcp.yr"},
#'   \var{"prcp.mo"}, \var{"pet.mo"}, and \var{"temp.mo"}. Note:
#'   if \code{sim_agg} has already been calculated for other reasons, then
#'   passing \code{sim_agg} may be faster than passing \code{sim_out},
#'   which (re-)calculates \code{sim_agg}.
#' @param soil_TOC A numeric vector. Total soil organic matter in g C / kg soil
#'   for each soil layer. If \code{NULL}, then internally set to 0.
#' @param has_soil_temperature A logical value. Set to \code{TRUE}, if soil
#'   temperature values have been simulated.
#' @param opt_SMTR A named list. Parameters for the calculation of the regimes:
#'   \itemize{
#'     \item \code{aggregate_at}: A character string. Determines the approach
#'       for regime determination; we recommend the value of "conditions".
#'       See details.
#'     \item \code{crit_agree_frac}: A numeric value. The aggregation agreement
#'       level (e.g., 0.5 = majority; 1 = all); we recommend a value of 0.9.
#'     \item \code{use_normal}: A logical value. If \code{TRUE}, then
#'       "normal years" as defined by (Soil Survey Staff 2014: p.29) are
#'       determined (note: should be at least a time period of 30 years);
#'       if \code{FALSE}, then all years are used for the calculation of
#'       regimes. We recommend to determine "normal years".
#'     \item \code{SWP_dry}: A numeric value. Soil water potential (MPa)
#'       below which soils are considered dry; we recommend a value of -1.5 MPa.
#'     \item \code{SWP_sat}: A numeric value. Soil water potential (MPa)
#'       above which soils are considered saturated; we recommend a value
#'       of -0.033 MPa.
#'     \item \code{impermeability}: A numeric value. The value above which
#'       the code considers a soil layer to be impermeable. We recommend a
#'       value of 0.9.
#'   }
#' @param simTime1 A list with named elements. Calculated internally
#'   if \code{NULL}; alternatively, it can be generated by a call to the
#'   function \code{\link[rSW2data]{setup_time_simulation_run}}.
#' @param simTime2 A list with named elements. Calculated internally
#'   if \code{NULL}; alternatively, it can be generated by a call to the
#'   function \code{\link[rSW2data]{simTiming_ForEachUsedTimeUnit}}.
#' @param verbose A logical value. If \code{TRUE} more messages are printed.
#' @param msg_tag A character string. Tag that is pre-appended to each verbose
#'   message.
#'
#' @section Details: The argument \code{aggregate_at} determines at which level
#'   aggregations to regimes are carried out: \itemize{
#'   \item \code{data}: Determine conditions and regimes based on aggregated
#'     mean soil moisture/temperature values.
#'   \item \code{conditions} Determine conditions based on time-series values
#'     of soil moisture/temperature values; determine regimes based on
#'     aggregated mean conditions.
#'   \item \code{regime}: Determine conditions and regimes based on
#'     time-series values of soil moisture/temperature values.
#' }
#'
#' @return A list with the following elements: \itemize{
#'   \item \code{regimes_done}: if successful calculation of soil moisture and
#'     soil temperature regimes then \code{TRUE} otherwise \code{FALSE}
#'   \item \code{has_simulated_SoilTemp}: if soil temperature was simulated
#'     then \code{1} otherwise \code{0}
#'   \item \code{has_realistic_SoilTemp}: if simulated soil temperature values
#'     are realistic then \code{1} otherwise \code{0}
#'   \item \code{has_Ohorizon}: if soil profile may have an O-horizon, then
#'     \code{TRUE} otherwise \code{FALSE}
#'   \item \code{Lanh_depth}: a numeric vector of length two
#'   \item \code{MCS_depth}: a numeric vector of length two
#'   \item \code{Fifty_depth}: a numeric value
#'   \item \code{permafrost_yrs}: number of years with permafrost conditions
#'   \item \code{SMR_normalyears}: years considered "normal"
#'   \item \code{SMR_normalyears_N}: number of years considered "normal"
#'   \item \code{cond_annual}: a numeric matrix with annual values of
#'     underlying conditions used to determine soil moisture and soil
#'     temperature regimes
#'   \item \code{STR}: soil temperature regimes
#'   \item \code{SMR}: soil moisture regimes
#' }
#'
#' @references Soil Survey Staff (2014). Keys to soil taxonomy,
#'   12th ed. USDA Natural Resources Conservation Service, Washington, DC.
#' @references Soil Survey Staff (2015). Illustrated guide to soil
#'   taxonomy. USDA Natural Resources Conservation Service, National Soil Survey
#'   Center, Lincoln, Nebraska.
#'
#' @examples
#' sw_in <- rSOILWAT2::sw_exampleData
#' sw_out <- rSOILWAT2::sw_exec(inputData = sw_in)
#' SMTR <- calc_SMTRs(sim_in = sw_in, sim_out = sw_out)
#'
#' @export
calc_SMTRs <- function(
  sim_in,
  sim_out = NULL,
  sim_agg = NULL,
  soil_TOC = NULL,
  has_soil_temperature = TRUE,
  opt_SMTR = list(
    aggregate_at = "conditions",
    crit_agree_frac = 0.9,
    use_normal = TRUE,
    SWP_dry = -1.5,
    SWP_sat = -0.033,
    impermeability = 0.9
  ),
  simTime1 = NULL,
  simTime2 = NULL,
  verbose = FALSE,
  msg_tag = NULL
) {


  #--- Check arguments
  opt_SMTR[["aggregate_at"]] <- match.arg(
    opt_SMTR[["aggregate_at"]],
    choices = c("conditions", "data", "regime")
  )

  has_soil_temperature <-
    has_soil_temperature &&
    rSOILWAT2::swSite_SoilTemperatureFlag(sim_in)


  #------ Prepare soil data

  soildat <- rSOILWAT2::swSoils_Layers(sim_in)
  req_soilvars <- c(
    "depth_cm", "sand_frac", "clay_frac", "impermeability_frac",
    "gravel_content", "bulkDensity_g/cm^3"
  )
  stopifnot(req_soilvars %in% colnames(soildat))

  n_soillayers <- NROW(soildat)


  # Pull all soil data together
  soildat <- cbind(
    soildat[, req_soilvars, drop = FALSE],
    soil_TOC = if (is.null(soil_TOC)) {
      rep(0, n_soillayers)
    }
  )

  if (verbose) {
    layers_depth_old <- soildat[, "depth_cm"]
  }


  if (is.null(sim_agg)) {
    # we need simulation output object instead
    sim_agg <- new.env(parent = baseenv())

    if (is.null(sim_out)) {
      stop(
        "We need simulation output either `sim_out` or already ",
        "aggregated `sim_agg`."
      )
    }
  }


  #--- Deal with soil water retention curves (if available)
  use_sw2_v6 <- getNamespaceVersion("rSOILWAT2") >= as.numeric_version("6.0.0")
  has_swrc <- isTRUE(
    try(
      rSOILWAT2::get_version(sim_in) >= as.numeric_version("6.0.0"),
      silent = TRUE
    )
  )

  if (!use_sw2_v6 && has_swrc) {
    stop(
      "Available 'rSOILWAT2' is older than v6.0.0",
      " and cannot handle 'sim_in' which is v6.0.0 or later."
    )
  }

  if (use_sw2_v6 && !has_swrc) {
    sim_in <- rSOILWAT2::sw_upgrade(sim_in, verbose = verbose)
    has_swrc <- TRUE
  }

  use_swrc_v6 <- use_sw2_v6 && has_swrc

  if (use_swrc_v6) {
    swrc_flags <- rSOILWAT2::swSite_SWRCflags(sim_in)
    has_active_ptf <- isTRUE(rSOILWAT2::check_ptf_availability(
      swrc_flags[["ptf_name"]]
    ))

    swrcp <- if (rSOILWAT2::swSite_hasSWRCp(sim_in)) {
      rSOILWAT2::swSoils_SWRCp(sim_in)
    } else {
      NA
    }

    if (anyNA(swrcp)) {
      if (has_active_ptf) {
        swrcp <- rSOILWAT2::ptf_estimate(
          sand = soildat[, "sand_frac"],
          clay = soildat[, "clay_frac"],
          fcoarse = soildat[, "gravel_content"],
          bdensity = soildat[, "bulkDensity_g/cm^3"],
          swrc_name = swrc_flags[["swrc_name"]],
          ptf_name = swrc_flags[["ptf_name"]],
          fail = TRUE
        )

      } else {
        stop(
          "Missing SWRC parameters and ",
          "requested PTF ", shQuote(swrc_flags[["ptf_name"]]),
          " is not available."
        )
      }
    }

  } else {
    has_active_ptf <- FALSE
  }



  #------ Get time sequence information
  years <- seq(
    rSOILWAT2::swYears_StartYear(sim_in),
    rSOILWAT2::swYears_EndYear(sim_in)
  )

  st1_elem_names <- c(
    "useyrs", "no.useyr", "no.usemo",
    "index.useyr", "index.usemo", "index.usedy"
  )

  tmp <- vapply(
    st1_elem_names,
    function(en) is.null(simTime1[[en]]),
    FUN.VALUE = NA
  )
  is_simTime1_good <- !is.null(simTime1) && !any(tmp)

  if (is_simTime1_good) {
    st1 <- simTime1
  } else {
    st1 <- rSW2data::setup_time_simulation_run(
      sim_time = list(
        spinup_N = 0,
        startyr = years[[1]],
        endyr = years[length(years)]
      )
    )
  }

  st2_elem_names <- c(
    "month_ForEachUsedDay", "doy_ForEachUsedDay",
    "doy_ForEachUsedDay_NSadj", "month_ForEachUsedDay_NSadj",
    "year_ForEachUsedDay_NSadj", "month_ForEachUsedMonth",
    "month_ForEachUsedMonth_NSadj", "yearno_ForEachUsedMonth",
    "yearno_ForEachUsedMonth_NSadj"
  )

  tmp <- vapply(
    st2_elem_names,
    function(en) is.null(simTime2[[en]]),
    FUN.VALUE = NA
  )

  is_simTime2_good <- !is.null(simTime2) && !any(tmp)

  if (is_simTime2_good) {
    st2 <- simTime2
  } else {
    st2 <- rSW2data::simTiming_ForEachUsedTimeUnit(
      useyrs = years,
      sim_tscales = c("daily", "monthly", "yearly"),
      latitude = rSOILWAT2::swSite_IntrinsicSiteParams(sim_in)[["Latitude"]],
      account_NorthSouth = TRUE
    )
  }


  #--- Prepare result containers
  SMTR <- list()
  SMTR[["regimes_done"]] <- FALSE
  SMTR[["has_simulated_SoilTemp"]] <- NA
  SMTR[["has_realistic_SoilTemp"]] <- NA
  SMTR[["has_Ohorizon"]] <- NA

  SMTR[["MCS_depth"]] <- SMTR[["Lanh_depth"]] <- rep(NA, 2)
  SMTR[["Fifty_depth"]] <- NA
  SMTR[["permafrost_yrs"]] <- NA
  SMTR[["SMR_normalyears"]] <- NA
  SMTR[["SMR_normalyears_N"]] <- 0

  icols0 <- c(
    "MATLanh", "MAT50", "T50jja", "T50djf",
    "CSPartSummer", "meanTair_Tsoil50_offset_C"
  )
  icols1a <- c("ACS_COND1", "ACS_COND2", "ACS_COND3")
  icols1 <- c(icols1a, "ACS_HalfDryDaysCumAbove0C", "ACS_SoilAbove0C")
  icols2 <- c("T50_at0C", "Lanh_Dry_Half", "ACS_COND3_Test")
  icols3 <- c(
    "COND0",
    "DryDaysCumAbove5C", "SoilAbove5C", "COND1",
    "MaxContDaysAnyMoistCumAbove8", "COND2", "COND2_1", "COND2_2",
    "COND2_3",
    "DryDaysCumAny", "COND3", "COND3_1",
    "COND4",
    "AbsDiffSoilTemp_DJFvsJJA", "COND5",
    "DryDaysConsecSummer", "COND6", "COND6_1",
    "MoistDaysCumAny", "COND7",
    "MoistDaysConsecAny", "COND8",
    "MoistDaysConsecWinter", "COND9",
    "AllDryDaysCumAny", "COND10"
  )
  icols4 <- c(
    "T50_at5C", "T50_at8C", "MCS_Moist_All", "COND1_Test", "COND2_Test"
  )
  tmp <- c(icols0, icols1, icols2, icols3, icols4)
  stopifnot(length(tmp) == 45)

  SMTR[["cond_annual"]] <- matrix(
    data = NA,
    nrow = st1[["no.useyr"]],
    ncol = length(tmp),
    dimnames = list(NULL, tmp)
  )

  tmp <- STR_names()
  SMTR[["STR"]] <- matrix(
    data = 0,
    nrow = 1,
    ncol = length(tmp),
    dimnames = list(NULL, tmp)
  )

  tmp <- c(SMR_names(), SMRq_names())
  SMTR[["SMR"]] <- matrix(
    data = 0,
    nrow = 1,
    ncol = length(tmp),
    dimnames = list(NULL, tmp)
  )


  # Check that soil temperature are realistic: use 100 C as upper
  # limit from Garratt, J.R. (1992). Extreme maximum land surface
  # temperatures. Journal of Applied Meteorology, 31, 1096-1105.
  if (has_soil_temperature) {
    SMTR[["has_simulated_SoilTemp"]] <- 1

    if (!exists("soiltemp.dy.all", where = sim_agg)) {
      if (!inherits(sim_out, "swOutput")) {
        stop(
          "`sim_out` is not 'rSOILWAT2' output ",
          "and 'soiltemp.dy.all' does not exist in `sim_agg`."
        )
      }

      sim_agg[["soiltemp.dy.all"]] <- list(
        val = slot(
          slot(sim_out, rSW2_glovars[["swof"]][["sw_soiltemp"]]),
          "Day"
        )
      )
    }

    ihead <- 1:2

    if (
      !anyNA(sim_agg[["soiltemp.dy.all"]][["val"]]) &&
      all(sim_agg[["soiltemp.dy.all"]][["val"]][, -ihead] < 100)
    ) {

      SMTR[["has_realistic_SoilTemp"]] <- 1

      if (!exists("soiltemp.yr.all", where = sim_agg)) {
        if (!inherits(sim_out, "swOutput")) {
          stop(
            "`sim_out` is not 'rSOILWAT2' output ",
            "and 'soiltemp.yr.all' does not exist in `sim_agg`."
          )
        }

        sim_agg[["soiltemp.yr.all"]] <- list(
          val = slot(
            slot(sim_out, rSW2_glovars[["swof"]][["sw_soiltemp"]]),
            "Year"
          )
        )
      }

      if (!exists("soiltemp.mo.all", where = sim_agg)) {
        if (!inherits(sim_out, "swOutput")) {
          stop(
            "`sim_out` is not 'rSOILWAT2' output ",
            "and 'soiltemp.mo.all' does not exist in `sim_agg`."
          )
        }

        sim_agg[["soiltemp.mo.all"]] <- list(
          val = slot(
            slot(sim_out, rSW2_glovars[["swof"]][["sw_soiltemp"]]),
            "Month"
          )
        )
      }

      if (!exists("vwcmatric.dy.all", where = sim_agg)) {
        if (!inherits(sim_out, "swOutput")) {
          stop(
            "`sim_out` is not 'rSOILWAT2' output ",
            "and 'vwcmatric.dy.all' does not exist in `sim_agg`."
          )
        }

        sim_agg[["vwcmatric.dy.all"]] <- list(
          val = if (use_sw2_v6) {
            rSOILWAT2::get_soilmoisture(
              sim_out,
              timestep = "Day",
              type = "vwc_matric",
              swInput = sim_in,
              keep_time = TRUE
            )
          } else {
            slot(
              slot(sim_out, rSW2_glovars[["swof"]][["sw_vwcmatric"]]),
              "Day"
            )
          }
        )
      }

      if (!exists("swpmatric.dy.all", where = sim_agg)) {
        sim_agg[["swpmatric.dy.all"]] <- list(
          val = cbind(
            sim_agg[["vwcmatric.dy.all"]][["val"]][, ihead, drop = FALSE],
            if (use_swrc_v6) {
              # `rSOILWAT2::swrc_vwc_to_swp()` expects bulk VWC`;
              # however, `vwc` values here represent the matric component, i.e.,
              # set `fcoarse` to zero
              rSOILWAT2::swrc_vwc_to_swp(
                sim_agg[["vwcmatric.dy.all"]][["val"]][, -ihead, drop = FALSE],
                fcoarse = rep(0., nrow(soildat)),
                swrc = list(
                  swrc_name = swrc_flags[["swrc_name"]],
                  swrcp = swrcp
                )
              )
            } else {
              rSOILWAT2::VWCtoSWP(
                sim_agg[["vwcmatric.dy.all"]][["val"]][, -ihead, drop = FALSE],
                sand = soildat[, "sand_frac"],
                clay = soildat[, "clay_frac"]
              )
            }
          )
        )
      }

      if (!exists("prcp.yr", where = sim_agg)) {
        if (!inherits(sim_out, "swOutput")) {
          stop(
            "`sim_out` is not 'rSOILWAT2' output ",
            "and 'prcp.yr' does not exist in `sim_agg`."
          )
        }

        tmp <- 10 * slot(
          slot(sim_out, rSW2_glovars[["swof"]][["sw_precip"]]),
          "Year"
        )
        sim_agg[["prcp.yr"]] <- list(
          ppt = tmp[st1[["index.useyr"]], 2, drop = FALSE]
        )
      }

      if (!exists("prcp.mo", where = sim_agg)) {
        if (!inherits(sim_out, "swOutput")) {
          stop(
            "`sim_out` is not 'rSOILWAT2' output ",
            "and 'prcp.mo' does not exist in `sim_agg`."
          )
        }

        tmp <- 10 * slot(
          slot(sim_out, rSW2_glovars[["swof"]][["sw_precip"]]),
          "Month"
        )
        sim_agg[["prcp.mo"]] <- list(
          ppt = tmp[st1[["index.usemo"]], 3, drop = FALSE]
        )
      }

      if (!exists("pet.mo", where = sim_agg)) {
        if (!inherits(sim_out, "swOutput")) {
          stop(
            "`sim_out` is not 'rSOILWAT2' output ",
            "and 'pet.mo' does not exist in `sim_agg`."
          )
        }

        tmp <- 10 * slot(
          slot(sim_out, rSW2_glovars[["swof"]][["sw_pet"]]),
          "Month"
        )
        sim_agg[["pet.mo"]] <- list(val = tmp[st1[["index.usemo"]], 3])
      }

      if (!exists("temp.mo", where = sim_agg)) {
        if (!inherits(sim_out, "swOutput")) {
          stop(
            "`sim_out` is not 'rSOILWAT2' output ",
            "and 'temp.mo' does not exist in `sim_agg`."
          )
        }

        tmp <- 10 * slot(
          slot(sim_out, rSW2_glovars[["swof"]][["sw_temp"]]),
          "Month"
        )
        sim_agg[["temp.mo"]] <- list(mean = tmp[st1[["index.usemo"]], 5])
      }

      # Prepare data
      # Water year:
      #  for each day of the simulation, i.e., October 1st is day 1 of the
      #  water year in the northern hemisphere and April 1st is day 1 of the
      #  water year in the southern hemisphere.
      wateryear_ForEachUsedDay_NSadj <-
        st2[["year_ForEachUsedDay_NSadj"]] +
        as.integer(st2[["doy_ForEachUsedDay_NSadj"]] > 273)

      # eliminate last (potentially) incomplete) year
      tmp <- unique(wateryear_ForEachUsedDay_NSadj)
      wyears <- tmp[-length(tmp)]

      if (opt_SMTR[["use_normal"]]) {
        #--- Normal years for soil moisture regimes
        # (Soil Survey Staff 2014: p.29)
        # Should have a time period of 30 years to determine normal years
        #   - Annual precipitation that is plus or minus one standard
        #     precipitation
        #   - and Mean monthly precipitation that is plus or minus one
        #     standard deviation of the long-term monthly precipitation for
        #     8 of the 12 months
        if (st1[["no.useyr"]] < 30) {
          print(paste0(
            msg_tag, ": has only ", st1[["no.useyr"]], " years ",
            "of data; determination of normal years for NRCS soil moisture ",
            "regimes should be based on >= 30 years."
          ))
        }

        MAP <- c(
          mean(sim_agg[["prcp.yr"]][["ppt"]]),
          stats::sd(sim_agg[["prcp.yr"]][["ppt"]])
        )

        normal1 <- as.vector(
          (sim_agg[["prcp.yr"]][["ppt"]] >= MAP[[1]] - MAP[[2]]) &
          (sim_agg[["prcp.yr"]][["ppt"]] <= MAP[[1]] + MAP[[2]])
        )

        MMP <- tapply(
          X = sim_agg[["prcp.mo"]][["ppt"]],
          INDEX = st2[["month_ForEachUsedMonth_NSadj"]],
          FUN = function(x) c(mean(x), stats::sd(x))
        )

        MMP <- matrix(unlist(MMP), nrow = 2, ncol = 12)

        normal2 <- tapply(
          X = sim_agg[["prcp.mo"]][["ppt"]],
          INDEX = st2[["yearno_ForEachUsedMonth_NSadj"]],
          FUN = function(x) {
            sum((x >= MMP[1, ] - MMP[2, ]) & (x <= MMP[1, ] + MMP[2, ])) >= 8
          }
        )

        st_NRCS <- list(
          yr_used = yr_used <- wyears[normal1 & normal2],
          i_yr_used = findInterval(yr_used, wyears)
        )

      } else {
        st_NRCS <- list(
          yr_used = st1[["useyrs"]],
          i_yr_used = findInterval(st1[["useyrs"]], wyears)
        )
      }

      i_dy_used <- wateryear_ForEachUsedDay_NSadj %in% st_NRCS[["yr_used"]]
      tmp <- seq_len(st1[["no.usemo"]])
      i_mo_used <- tmp[rep(wyears, each = 12) %in% st_NRCS[["yr_used"]]]
      dtmp <- table(wateryear_ForEachUsedDay_NSadj[i_dy_used], dnn = NULL)

      st_NRCS <- c(
        st_NRCS,
        list(
          N_yr_used = length(st_NRCS[["yr_used"]]),
          i_dy_used = i_dy_used,
          N_dy_used = sum(i_dy_used),
          i_mo_used = i_mo_used,
          days_per_yr_used = as.integer(dtmp)
        )
      )

      SMTR[["SMR_normalyears"]] <- st_NRCS[["yr_used"]]
      SMTR[["SMR_normalyears_N"]] <- st_NRCS[["N_yr_used"]]

      # Subset to used time steps and extract average soil temperature values
      soiltemp_nrsc <- list(
        yr = list(
          data = {
            tmp <- sim_agg[["soiltemp.yr.all"]][["val"]]
            id_rows <- st1[["index.useyr"]][st_NRCS[["i_yr_used"]]]
            id_cols <- if (ncol(tmp) == 1 + n_soillayers) {
              seq_len(1 + n_soillayers)
            } else {
              # rSOILWAT2 since v5.3.0: returns max/min/avg soil temperature
              c(1, grep("Lyr_[[:digit:]]+_avg_C", colnames(tmp)))
            }
            tmp[id_rows, id_cols, drop = FALSE]
          },
          nheader = 1
        ),
        mo = list(
          data = {
            tmp <- sim_agg[["soiltemp.mo.all"]][["val"]]
            id_rows <- st1[["index.usemo"]][st_NRCS[["i_mo_used"]]]
            id_cols <- if (ncol(tmp) == 2 + n_soillayers) {
              seq_len(2 + n_soillayers)
            } else {
              # rSOILWAT2 since v5.3.0: returns max/min/avg soil temperature
              c(1:2, grep("Lyr_[[:digit:]]+_avg_C", colnames(tmp)))
            }
            tmp[id_rows, id_cols, drop = FALSE]
          },
          nheader = 2
        ),
        dy = list(
          data = {
            tmp <- sim_agg[["soiltemp.dy.all"]][["val"]]
            id_rows <- st1[["index.usedy"]][st_NRCS[["i_dy_used"]]]
            id_cols <- if (ncol(tmp) == 2 + n_soillayers) {
              seq_len(2 + n_soillayers)
            } else {
              # rSOILWAT2 since v5.3.0: returns max/min/avg soil temperature
              c(1:2, grep("Lyr_[[:digit:]]+_avg_C", colnames(tmp)))
            }
            tmp[id_rows, id_cols, drop = FALSE]
          },
          nheader = 2
        )
      )
      vwc_dy_nrsc <- sim_agg[["vwcmatric.dy.all"]]

      if (opt_SMTR[["aggregate_at"]] == "data") {
        # Aggregate SOILWAT2 output to mean conditions before determining
        # conditions and regimes
        soiltemp_nrsc <- list(
          yr = list(
            data = matrix(colMeans(soiltemp_nrsc[["yr"]][["data"]]), nrow = 1),
            nheader = soiltemp_nrsc[["yr"]][["nheader"]]
          ),
          mo = list(
            data = {
              tmp <- st2[["month_ForEachUsedMonth"]][st_NRCS[["i_mo_used"]]]
              stats::aggregate(
                soiltemp_nrsc[["mo"]][["data"]],
                by = list(tmp),
                mean
              )[, -1]
            },
            nheader = soiltemp_nrsc[["mo"]][["nheader"]]
          ),
          dy = list(
            data = {
              tmp <- st2[["doy_ForEachUsedDay"]][st_NRCS[["i_dy_used"]]]
              stats::aggregate(
                soiltemp_nrsc[["dy"]][["data"]],
                by = list(tmp),
                mean
              )[, -1]},
            nheader = soiltemp_nrsc[["dy"]][["nheader"]]
          )
        )

        vwc_dy_nrsc <- lapply(
          sim_agg[["vwcmatric.dy.all"]],
          function(x) {
            tmp1 <- st1[["index.usedy"]][st_NRCS[["i_dy_used"]]]
            tmp2 <- st2[["doy_ForEachUsedDay"]][st_NRCS[["i_dy_used"]]]
            stats::aggregate(as.matrix(x)[tmp1, ], list(tmp2), mean)[, -1]
          }
        )

        tmp <- dim(vwc_dy_nrsc[["val"]])[[1]]
        st_NRCS <- c(
          st_NRCS,
          list(
            index_usedy = seq_len(tmp),
            month_ForMonth = rSW2_glovars[["st_mo"]],
            yearno_ForMonth = rep(1, 12),
            doy_ForDay = seq_len(tmp)
          )
        )

        # adjust st_NRCS for the aggregation
        st_NRCS <- utils::modifyList(
          st_NRCS,
          list(
            yr_used = 1,
            N_yr_used = 1,
            i_yr_used = 1,
            i_mo_used = rSW2_glovars[["st_mo"]],
            i_dy_used = rep(TRUE, tmp),
            N_dy_used = tmp,
            days_per_yr_used = tmp
          )
        )

        wateryear_ForEachUsedDay_NSadj <- rep(1, tmp)
        wyears <- 1

      } else {
        # Determine regimes based on time-series output and then determine
        # conditions and regime
        st_NRCS <- c(
          st_NRCS,
          list(
            index_usedy = st1[["index.usedy"]][st_NRCS[["i_dy_used"]]],
            month_ForMonth =
              st2[["month_ForEachUsedMonth_NSadj"]][st_NRCS[["i_mo_used"]]],
            yearno_ForMonth =
              st2[["yearno_ForEachUsedMonth_NSadj"]][st_NRCS[["i_mo_used"]]],
            doy_ForDay =
              st2[["doy_ForEachUsedDay_NSadj"]][st_NRCS[["i_dy_used"]]]
          )
        )
      }

      #--- Required soil layers
      # 50 cm soil depth or impermeable layer (whichever is shallower;
      # Soil Survey Staff 2014: p.31)
      imp_depth <- max(soildat[, "depth_cm"])
      tmp <- which(
        soildat[, "impermeability_frac"] >= opt_SMTR[["impermeability"]]
      )

      # Interpret maximum soil depth as possible impermeable layer
      if (length(tmp) > 0) {
        imp_depth <- min(min(soildat[tmp, "depth_cm"]), imp_depth)
      }

      SMTR[["Fifty_depth"]] <- min(50, imp_depth)

      # Definition of MCS (Soil Survey Staff 2014: p.29):
      # "The moisture control section (MCS) of a soil: the depth to which a
      # dry (tension of more than 1500 kPa, but not air-dry) soil will be
      # moistened by 2.5 cm of water within 24 hours. The lower boundary is
      # the depth to which a dry soil will be moistened by 7.5 cm of water
      # within 48 hours."
      layers_width <- rSW2data::getLayersWidth(soildat[, "depth_cm"])
      sand_tmp <- stats::weighted.mean(soildat[, "sand_frac"], layers_width)
      clay_tmp <- stats::weighted.mean(soildat[, "clay_frac"], layers_width)
      # Practical depth definition of MCS
      #  - 10 to 30 cm below the soil surface if the particle-size class of
      #    the soil is fine-loamy, coarse-silty, fine-silty, or clayey
      #  - 20 to 60 cm if the particle-size class is coarse-loamy
      #  - 30 to 90 cm if the particle-size class is sandy.
      SMTR[["MCS_depth"]] <- if (clay_tmp >= 0.18) {
        c(10, 30)
      } else if (sand_tmp < 0.15) {
        c(10, 30)
      } else if (sand_tmp >= 0.50) {
        c(30, 90)
      } else {
        c(20, 60)
      }
      # "If 7.5 cm of water moistens the soil to a densic, lithic, paralithic,
      # or petroferric contact or to a petrocalcic or petrogypsic horizon or a
      # duripan, the contact or the upper boundary of the cemented horizon
      # constitutes the lower boundary of the soil moisture control section.
      # If a soil is moistened to one of these contacts or horizons by 2.5 cm
      # of water, the soil moisture control section is the boundary of the
      # contact itself. The control section of such a soil is considered moist
      # if the contact or upper boundary of the cemented horizon has a thin
      # film of water. If that upper boundary is dry, the control section is
      # considered dry."

      SMTR[["MCS_depth"]] <- rSW2data::adjustLayer_byImp(
        depths = SMTR[["MCS_depth"]],
        imp_depth = imp_depth,
        sdepths = soildat[, "depth_cm"]
      )

      # Soil layer 10-70 cm used for anhydrous layer definition;
      # adjusted for impermeable layer
      SMTR[["Lanh_depth"]] <- rSW2data::adjustLayer_byImp(
        depths = c(10, 70),
        imp_depth = imp_depth,
        sdepths = soildat[, "depth_cm"]
      )

      # Permafrost (Soil Survey Staff 2014: p.28) is defined as a "thermal
      # condition in which a material (including soil material) remains
      # below 0 C for 2 or more years in succession"
      tmp <-
        sim_agg[["soiltemp.yr.all"]][["val"]][
          st1[["index.useyr"]], -1, drop = FALSE
        ]

      SMTR[["permafrost_yrs"]] <- max(
        apply(
          X = tmp,
          MARGIN = 2,
          FUN = function(x) {
            tmp <- rle(x < 0)
            if (any(tmp[["values"]])) {
              max(tmp[["lengths"]][tmp[["values"]]])
            } else {
              0L
            }
          }
        )
      )

      has_notenough_normalyears <- FALSE
      if (SMTR[["SMR_normalyears_N"]] > 0) {

        SMTR[["cond_annual"]] <- SMTR[["cond_annual"]][
          st_NRCS[["i_yr_used"]], , drop = FALSE]

        # Set soil depths and intervals accounting for shallow soil profiles:
        # Soil Survey Staff 2014: p.31)
        depths_required <- sort(unique(c(
          SMTR[["Fifty_depth"]],
          SMTR[["MCS_depth"]],
          SMTR[["Lanh_depth"]]
        )))

        ## Calculate soil values at necessary depths using a weighted mean
        tmp <- depths_required %in% soildat[, "depth_cm"]
        depths_toadd <- depths_required[!tmp]

        for (dadd in depths_toadd) {
          prev_depths <- soildat[, "depth_cm"]

          soildat <- t(rSW2data::add_soil_layer(
            x = t(soildat),
            target_cm = dadd,
            depths_cm = prev_depths,
            method = "interpolate"
          ))

          soiltemp_nrsc <- lapply(
            soiltemp_nrsc,
            function(st) {
              itmp <- seq_len(st[["nheader"]])

              list(
                data = cbind(
                  st[["data"]][, itmp, drop = FALSE],
                  rSW2data::add_soil_layer(
                    x = st[["data"]][, - itmp, drop = FALSE],
                    target_cm = dadd,
                    depths_cm = prev_depths,
                    method = "interpolate"
                  )
                ),
                nheader = st[["nheader"]]
              )
            }
          )

          vwc_dy_nrsc[["val"]] <- cbind(
            vwc_dy_nrsc[["val"]][, ihead, drop = FALSE],
            rSW2data::add_soil_layer(
              x = vwc_dy_nrsc[["val"]][, - ihead, drop = FALSE],
              target_cm = dadd,
              depths_cm = prev_depths,
              method = "interpolate"
            )
          )

          if (use_swrc_v6 && !has_active_ptf) {
            # Interpolate SWRCp because we don't have an active PTF
            swrcp <- t(rSW2data::add_soil_layer(
              x = t(swrcp),
              target_cm = dadd,
              depths_cm = prev_depths,
              method = "interpolate"
            ))
          }
        }


        if (length(depths_toadd) > 0L && use_swrc_v6 && has_active_ptf) {
          # Re-estimate SWRCp from updated soil data
          swrcp <- rSOILWAT2::ptf_estimate(
            sand = soildat[, "sand_frac"],
            clay = soildat[, "clay_frac"],
            fcoarse = soildat[, "gravel_content"],
            bdensity = soildat[, "bulkDensity_g/cm^3"],
            swrc_name = swrc_flags[["swrc_name"]],
            ptf_name = swrc_flags[["ptf_name"]]
          )
        }

        soilLayers_N_NRCS <- dim(soildat)[[1]]
        soiltemp_nrsc <- lapply(soiltemp_nrsc, function(st) st[["data"]])

        swp_recalculate <- length(depths_toadd) > 0
        if (swp_recalculate && verbose) {
          print(paste0(
            msg_tag, ": interpolated soil layers for NRCS soil ",
            "regimes because of insufficient soil layers: ",
            "required would be {",
            toString(
              sort(unique(c(
                SMTR[["Fifty_depth"]],
                SMTR[["MCS_depth"]],
                SMTR[["Lanh_depth"]]
              )))
            ),
            "} and available are {",
            toString(layers_depth_old),
            "}"
          ))
        }

        # Note: `soildat` and `swrcp` may contain additional layers
        swp_dy_nrsc <- if (
          swp_recalculate ||
          opt_SMTR[["aggregate_at"]] == "data"
        ) {
          tmp <- if (use_swrc_v6) {
            # `rSOILWAT2::swrc_vwc_to_swp()` expects bulk VWC`;
            # however, `vwc` values here represent the matric component, i.e.,
            # set `fcoarse` to zero
            rSOILWAT2::swrc_vwc_to_swp(
              vwc_dy_nrsc[["val"]][, -ihead, drop = FALSE],
              fcoarse = rep(0., nrow(soildat)),
              swrc = list(
                swrc_name = swrc_flags[["swrc_name"]],
                swrcp = swrcp
              )
            )
          } else {
            rSOILWAT2::VWCtoSWP(
              vwc_dy_nrsc[["val"]][, -ihead, drop = FALSE],
              sand = soildat[, "sand_frac"],
              clay = soildat[, "clay_frac"]
            )
          }

          tmp[st_NRCS[["index_usedy"]], , drop = FALSE]

        } else {
          sim_agg[["swpmatric.dy.all"]][["val"]][
            st_NRCS[["index_usedy"]], -ihead, drop = FALSE]
        }

        #MCS (Soil Survey Staff 2014: p.29)
        i_depth50 <- rSW2data::identify_soillayers(
          depths = SMTR[["Fifty_depth"]],
          sdepth = soildat[, "depth_cm"]
        )

        #What soil layer info used for MCS
        i_MCS <- rSW2data::identify_soillayers(
          depths = SMTR[["MCS_depth"]],
          sdepth = soildat[, "depth_cm"]
        )

        #Repeat for Anhydrous soil layer moisture delineation
        i_Lanh <- rSW2data::identify_soillayers(
          depths = SMTR[["Lanh_depth"]],
          sdepth = soildat[, "depth_cm"]
        )

        #mean soil temperature in Lahn depths (10 - 70 cm)
        SMTR[["cond_annual"]][, "MATLanh"] <- apply(
          soiltemp_nrsc[["yr"]][, 1 + i_Lanh, drop = FALSE],
          MARGIN = 1,
          FUN = stats::weighted.mean,
          w = soildat[i_Lanh, "depth_cm"]
        )

        #---Calculate variables
        crit_agree <- opt_SMTR[["crit_agree_frac"]] * st_NRCS[["N_yr_used"]]

        #mean soil temperatures at 50cm depth
        SMTR[["cond_annual"]][, "MAT50"] <-
          soiltemp_nrsc[["yr"]][, 1 + i_depth50]

        tmp <- soiltemp_nrsc[["mo"]][, 2 + i_depth50][
          st_NRCS[["month_ForMonth"]] %in% 6:8]

        SMTR[["cond_annual"]][, "T50jja"] <- apply(
          matrix(tmp, ncol = st_NRCS[["N_yr_used"]]),
          MARGIN = 2,
          FUN = mean
        )

        tmp <- soiltemp_nrsc[["mo"]][, 2 + i_depth50][
          st_NRCS[["month_ForMonth"]] %in% c(12, 1:2)]

        SMTR[["cond_annual"]][, "T50djf"] <- apply(
          matrix(tmp, ncol = st_NRCS[["N_yr_used"]]),
          MARGIN = 2,
          FUN = mean
        )

        T50 <- soiltemp_nrsc[["dy"]][, 2 + i_depth50]
        # offset between soil and air temperature
        fc <- sim_agg[["temp.mo"]][["mean"]][st_NRCS[["i_mo_used"]]] -
          soiltemp_nrsc[["mo"]][, 2 + i_depth50]

        SMTR[["cond_annual"]][, "meanTair_Tsoil50_offset_C"] <- tapply(
          fc,
          INDEX = st_NRCS[["yearno_ForMonth"]],
          FUN = mean
        )

        # CSPartSummer: "Is the soil saturated with water during some part of
        # the summer June1 ( = regular doy 244) - Aug31 ( = regular doy 335)"
        isummer <-
          st_NRCS[["doy_ForDay"]] >= 244 &
          st_NRCS[["doy_ForDay"]] <= 335

        SMTR[["cond_annual"]][, "CSPartSummer"] <- vapply(
          st_NRCS[["yr_used"]],
          FUN = function(yr) {
            tmp <- swp_dy_nrsc[wateryear_ForEachUsedDay_NSadj[
              st_NRCS[["i_dy_used"]]] == yr & isummer, , drop = FALSE]

            tmp <- apply(tmp, 1, function(x) all(x >= opt_SMTR[["SWP_sat"]]))
            rtmp <- rle(tmp)

            if (any(rtmp[["values"]])) {
              max(rtmp[["lengths"]][rtmp[["values"]]])
            } else {
              0
            }
          },
          FUN.VALUE = NA_real_
        )

        # "saturated with water for X cumulative days in normal years"
        days_saturated_layers <- vapply(
          st_NRCS[["yr_used"]],
          FUN = function(yr) {
            tmp <- swp_dy_nrsc[wateryear_ForEachUsedDay_NSadj[
              st_NRCS[["i_dy_used"]]] == yr, , drop = FALSE]

            apply(tmp, 2, function(x) sum(x >= opt_SMTR[["SWP_sat"]]))
          },
          FUN.VALUE = rep(NA_real_, soilLayers_N_NRCS)
        )

        if (!is.matrix(days_saturated_layers)) {
          days_saturated_layers <- matrix(
            data = days_saturated_layers,
            nrow = soilLayers_N_NRCS,
            ncol = st_NRCS[["N_yr_used"]]
          )
        }

        somCOND0 <- t(days_saturated_layers) >= 30
        #if (opt_SMTR[["aggregate_at"]] == "conditions") {
        tmp <- matrix(colSums(somCOND0), nrow = 1, ncol = soilLayers_N_NRCS)
        somCOND0 <- tmp >= crit_agree
        #}

        # Organic versus mineral soil material per layer
        # units(TOC) = g C / kg soil
        organic_carbon_wfraction <- soildat[, "soil_TOC"] / 1000

        is_mineral_layer <-
          (!somCOND0 & organic_carbon_wfraction < 0.2) |
          (
            somCOND0 &
            (soildat[, "clay_frac"] >= 0.6 & organic_carbon_wfraction < 0.18) |
            (organic_carbon_wfraction < 0.12 + 0.1 * soildat[, "clay_frac"])
          )

        # determine presence of O horizon
        # TODO: guess (critical levels 'crit_Oh' are made up/not based on data):
        #       O-horizon if 50% trees or 75% shrubs or lots of litter
        crit_Oh <- c(0.5, 0.75, 0.8)
        veg_comp <- rSOILWAT2::swProd_Composition(sim_in)[1:4]

        tmp <- cbind(
          rSOILWAT2::swProd_MonProd_grass(sim_in)[, "Litter"],
          rSOILWAT2::swProd_MonProd_shrub(sim_in)[, "Litter"],
          rSOILWAT2::swProd_MonProd_tree(sim_in)[, "Litter"],
          rSOILWAT2::swProd_MonProd_forb(sim_in)[, "Litter"]
        )

        veg_litter <- mean(apply(sweep(tmp, 2, veg_comp, "*"), 1, sum))

        tmp <- sum(rSOILWAT2::swProd_Es_param_limit(sim_in) * veg_comp)
        crit_litter <- crit_Oh[[3]] * tmp

        SMTR[["has_Ohorizon"]] <-
          (veg_litter >= crit_litter) &&
          if (!is.finite(is_mineral_layer[[1]])) {
            veg_comp[["Trees"]] > crit_Oh[[1]] ||
              veg_comp[["Shrubs"]] > crit_Oh[[2]]
          } else {
            !is_mineral_layer[[1]]
          }

        #--- Soil temperature regime: based on Soil Survey Staff 2014
        # (Key to Soil Taxonomy): p.31
        # here, we ignore distinction between iso- and not iso-
        icol <- c("MAT50", "T50jja", "CSPartSummer")
        stCONDs <- SMTR[["cond_annual"]][, icol, drop = FALSE]

        if (opt_SMTR[["aggregate_at"]] == "conditions") {
          tmp <- colMeans(stCONDs)
          tmp[["CSPartSummer"]] <-
            tmp[["CSPartSummer"]] > opt_SMTR[["crit_agree_frac"]]

          stCONDs <- matrix(
            data = tmp,
            nrow = 1,
            ncol = length(icol),
            dimnames = list(NULL, icol)
          )
        }
        has_permafrost <- SMTR[["permafrost_yrs"]] >= 2

        SMTR[["STR"]] <- t(apply(
          stCONDs,
          MARGIN = 1,
          FUN = function(x) {
            STR_logic(
              MAST = x[["MAT50"]],
              MSST = x[["T50jja"]],
              SatSoilSummer_days = x[["CSPartSummer"]],
              has_permafrost = has_permafrost,
              has_Ohorizon = SMTR[["has_Ohorizon"]]
            )
          }
        ))


        if (SMTR[["SMR_normalyears_N"]] > 2) {
          # Structures used Lanh delineation
          # Days are moists in half of the Lanh soil depth (not soil layers!)
          n_Lanh <- length(i_Lanh)
          width_Lanh <- diff(c(0, soildat[, "depth_cm"]))[i_Lanh]
          if (FALSE) {
            stopifnot(
              sum(width_Lanh) ==
              SMTR[["Lanh_depth"]][[2]] - SMTR[["Lanh_depth"]][[1]])
          }

          tmp <- swp_dy_nrsc[, i_Lanh, drop = FALSE] > opt_SMTR[["SWP_dry"]]
          tmp <- tmp * matrix(
            data = width_Lanh,
            nrow = st_NRCS[["N_dy_used"]],
            ncol = length(i_Lanh),
            byrow = TRUE
          )
          tmp <- .rowSums(tmp, m = st_NRCS[["N_dy_used"]], n = n_Lanh)
          Lanh_Dry_Half <- tmp <= sum(width_Lanh) / 2

          #Conditions for Anhydrous soil delineation
          ACS_CondsDF_day <- data.frame(
            Years = rep(st_NRCS[["yr_used"]], st_NRCS[["days_per_yr_used"]]),
            T50_at0C = T50 > 0, # days where T @ 50 is > 0 C
            Lanh_Dry_Half = Lanh_Dry_Half
          )
          ACS_CondsDF_yrs <- data.frame(
            Years = st_NRCS[["yr_used"]],
            MAT50 = SMTR[["cond_annual"]][, "MAT50"],
            MATLanh = SMTR[["cond_annual"]][, "MATLanh"]
          )

          # Mean Annual soil temperature is less than or equal to 0C
          ACS_CondsDF_yrs[["ACS_COND1"]] <- ACS_CondsDF_yrs[["MAT50"]] <= 0
          # Soil temperature in the Lahn Depth is never greater than 5
          ACS_CondsDF_day[["ACS_COND2_Test"]] <- apply(
            soiltemp_nrsc[["dy"]][, 1 + i_Lanh, drop = FALSE],
            MARGIN = 1,
            FUN = function(st) all(st < 5)
          )

          ACS_CondsDF_yrs[["ACS_COND2"]] <- tapply(
            ACS_CondsDF_day[["ACS_COND2_Test"]],
            ACS_CondsDF_day[["Years"]],
            all
          )

          # In the Lahn Depth, 1/2 of soil dry > 1/2 CUMULATIVE days when
          # Mean Annual ST > 0C
          ACS_CondsDF_day[["ACS_COND3_Test"]] <-
            ACS_CondsDF_day[["Lanh_Dry_Half"]] == ACS_CondsDF_day[["T50_at0C"]]

          ACS_CondsDF_yrs[["ACS_HalfDryDaysCumAbove0C"]] <- tapply(
            ACS_CondsDF_day[["ACS_COND3_Test"]],
            ACS_CondsDF_day[["Years"]],
            sum
          )

          ACS_CondsDF_yrs[["ACS_SoilAbove0C"]] <- tapply(
            ACS_CondsDF_day[["T50_at0C"]],
            ACS_CondsDF_day[["Years"]],
            sum
          )

          # TRUE = Half of soil layers are dry greater than half the days
          #   where MAST > 0 C
          ACS_CondsDF_yrs[["ACS_COND3"]] <-
            ACS_CondsDF_yrs[["ACS_HalfDryDaysCumAbove0C"]] >
              0.5 * ACS_CondsDF_yrs[["ACS_SoilAbove0C"]]


          ACS_CondsDF3 <- as.matrix(ACS_CondsDF_yrs[, icols1a, drop = FALSE])

          if (opt_SMTR[["aggregate_at"]] == "conditions") {
            tmp <- matrix(
              data = colSums(ACS_CondsDF3, na.rm = TRUE),
              nrow = 1,
              ncol = length(icols1a),
              dimnames = list(NULL, icols1a)
            )
            ACS_CondsDF3 <- tmp >= crit_agree

          } else {
            dimnames(ACS_CondsDF3)[[2]] <- icols1a
          }

          #--- Structures used for MCS delineation
          MCS_CondsDF_day <- data.frame(
            Years = rep(st_NRCS[["yr_used"]], st_NRCS[["days_per_yr_used"]]),
            DOY = st_NRCS[["doy_ForDay"]],
            T50_at5C = T50 > 5, # days where T @ 50cm exceeds 5C
            T50_at8C = T50 > 8, # days where T @ 50cm exceeds 8C
            MCS_Moist_All = apply(
              X = swp_dy_nrsc[, i_MCS, drop = FALSE] > opt_SMTR[["SWP_dry"]],
              MARGIN = 1,
              FUN = all
            ),
            MCS_Dry_All = apply(
              X = swp_dy_nrsc[, i_MCS, drop = FALSE] < opt_SMTR[["SWP_dry"]],
              MARGIN = 1,
              FUN = all
            )
          )

          MCS_CondsDF_yrs <- data.frame(
            Years = st_NRCS[["yr_used"]],
            MAT50 = SMTR[["cond_annual"]][, "MAT50"],
            T50jja = SMTR[["cond_annual"]][, "T50jja"],
            T50djf = SMTR[["cond_annual"]][, "T50djf"]
          )

          #COND0 - monthly PET < PPT
          tmp <- sim_agg[["prcp.mo"]][["ppt"]] - sim_agg[["pet.mo"]][["val"]]

          MCS_CondsDF_yrs[["COND0"]] <- if (
            opt_SMTR[["aggregate_at"]] == "data"
          ) {
            all(tapply(tmp, st2[["month_ForEachUsedMonth"]], mean) > 0)

          } else {
            tmp <- tapply(tmp > 0, st2[["yearno_ForEachUsedMonth"]], all)
            tmp[st_NRCS[["i_yr_used"]]]
          }

          # COND1 - Dry in ALL parts for more than half of the CUMULATIVE days
          # per year when the soil temperature at a depth of 50cm is above 5C
          MCS_CondsDF_day[["COND1_Test"]] <-
            MCS_CondsDF_day[["MCS_Dry_All"]] & MCS_CondsDF_day[["T50_at5C"]]

          MCS_CondsDF_yrs[["DryDaysCumAbove5C"]] <- tapply(
            MCS_CondsDF_day[["COND1_Test"]],
            MCS_CondsDF_day[["Years"]],
            sum
          )

          MCS_CondsDF_yrs[["SoilAbove5C"]] <- tapply(
            MCS_CondsDF_day[["T50_at5C"]],
            MCS_CondsDF_day[["Years"]],
            sum
          )

          #TRUE =Soils are dry greater than 1/2 cumulative days/year
          MCS_CondsDF_yrs[["COND1"]] <-
            MCS_CondsDF_yrs[["DryDaysCumAbove5C"]] >
              0.5 * MCS_CondsDF_yrs[["SoilAbove5C"]]

          # Cond2 - Moist in SOME or all parts for less than 90 CONSECUTIVE
          # days when the the soil temperature at a depth of 50cm is above 8C
          MCS_CondsDF_day[["COND2_Test"]] <-
            !MCS_CondsDF_day[["MCS_Dry_All"]] & MCS_CondsDF_day[["T50_at8C"]]

          # Maximum consecutive days
          # TRUE = moist less than 90 consecutive days during >8 C soils,
          # FALSE = moist more than 90 consecutive days
          MCS_CondsDF_yrs[["MaxContDaysAnyMoistCumAbove8"]] <- tapply(
            MCS_CondsDF_day[["COND2_Test"]],
            MCS_CondsDF_day[["Years"]],
            rSW2utils::max_duration
          )

          MCS_CondsDF_yrs[["COND2"]] <-
            MCS_CondsDF_yrs[["MaxContDaysAnyMoistCumAbove8"]] < 90

          MCS_CondsDF_yrs[["COND2_1"]] <-
            MCS_CondsDF_yrs[["MaxContDaysAnyMoistCumAbove8"]] < 180

          MCS_CondsDF_yrs[["COND2_2"]] <-
            MCS_CondsDF_yrs[["MaxContDaysAnyMoistCumAbove8"]] < 270

          MCS_CondsDF_yrs[["COND2_3"]] <-
            MCS_CondsDF_yrs[["MaxContDaysAnyMoistCumAbove8"]] <= 45

          # COND3 - MCS is Not dry in ANY part as long as 90 CUMULATIVE days -
          # Can't be dry longer than 90 cum days
          # Number of days where any soils are dry:
          MCS_CondsDF_yrs[["DryDaysCumAny"]] <- tapply(
            !MCS_CondsDF_day[["MCS_Moist_All"]],
            MCS_CondsDF_day[["Years"]],
            sum
          )
          # TRUE = Not Dry for as long 90 cumlative days,
          # FALSE = Dry as long as as 90 Cumlative days
          MCS_CondsDF_yrs[["COND3"]] <- MCS_CondsDF_yrs[["DryDaysCumAny"]] < 90
          MCS_CondsDF_yrs[["COND3_1"]] <-
            MCS_CondsDF_yrs[["DryDaysCumAny"]] < 30

          # COND4 - The means annual soil temperature at 50cm is < or > 22C
          # TRUE - Greater than 22, False - Less than 22
          MCS_CondsDF_yrs[["COND4"]] <- MCS_CondsDF_yrs[["MAT50"]] >= 22

          # COND5 - The absolute difference between the temperature in winter
          # @ 50cm and the temperature in summer @ 50cm is > or < 6
          MCS_CondsDF_yrs[["AbsDiffSoilTemp_DJFvsJJA"]] <- abs(
            MCS_CondsDF_yrs[["T50djf"]] - MCS_CondsDF_yrs[["T50jja"]]
          )

          # TRUE - Greater than 6, FALSE - Less than 6
          MCS_CondsDF_yrs[["COND5"]] <-
            MCS_CondsDF_yrs[["AbsDiffSoilTemp_DJFvsJJA"]] >= 6

          # COND6 - Dry in ALL parts LESS than 45 CONSECUTIVE days in the 4
          # months following the summer solstice
          # Consecutive days of dry soil after summer solstice
          ids <- MCS_CondsDF_day[["DOY"]] %in% 172:293
          tmp <- tapply(
            MCS_CondsDF_day[["MCS_Dry_All"]][ids],
            MCS_CondsDF_day[["Years"]][ids],
            rSW2utils::max_duration
          )

          ids <- match(
            MCS_CondsDF_yrs[, "Years"],
            as.integer(names(tmp)),
            nomatch = 0
          )
          MCS_CondsDF_yrs[ids > 0, "DryDaysConsecSummer"] <- tmp[ids]

          # TRUE = dry less than 45 consecutive days
          MCS_CondsDF_yrs[["COND6"]] <-
            MCS_CondsDF_yrs[["DryDaysConsecSummer"]] < 45

          MCS_CondsDF_yrs[["COND6_1"]] <-
            MCS_CondsDF_yrs[["DryDaysConsecSummer"]] > 90

          # COND7 - MCS is MOIST in SOME parts for more than 180 CUMULATIVE days
          # Number of days where any soils are moist:
          MCS_CondsDF_yrs[["MoistDaysCumAny"]] <- tapply(
            !MCS_CondsDF_day[["MCS_Dry_All"]],
            MCS_CondsDF_day[["Years"]],
            sum
          )

          # TRUE = Not Dry or Moist for as long 180 cumlative days
          MCS_CondsDF_yrs[["COND7"]] <-
            MCS_CondsDF_yrs[["MoistDaysCumAny"]] > 180

          # COND8 - MCS is MOIST in SOME parts for more than 90 CONSECUTIVE days
          # Consecutive days of Moist soil:
          MCS_CondsDF_yrs[["MoistDaysConsecAny"]] <- tapply(
            !MCS_CondsDF_day[["MCS_Dry_All"]],
            MCS_CondsDF_day[["Years"]],
            rSW2utils::max_duration
          )
          # TRUE = Moist more than 90 Consecutive Days
          MCS_CondsDF_yrs[["COND8"]] <-
            MCS_CondsDF_yrs[["MoistDaysConsecAny"]] > 90

          # COND9 - Moist in ALL parts MORE than 45 CONSECUTIVE days in the 4
          # months following the winter solstice
          # Consecutive days of moist soil after winter solsitice:
          ids <- MCS_CondsDF_day[["DOY"]] %in% c(355:365, 1:111)
          tmp <- tapply(
            MCS_CondsDF_day[["MCS_Moist_All"]][ids],
            MCS_CondsDF_day[["Years"]][ids],
            rSW2utils::max_duration
          )
          ids <- match(
            MCS_CondsDF_yrs[, "Years"],
            as.integer(names(tmp)),
            nomatch = 0
          )

          MCS_CondsDF_yrs[ids > 0, "MoistDaysConsecWinter"] <- tmp[ids]
          # TRUE = moist more than 45 consecutive days
          MCS_CondsDF_yrs[["COND9"]] <-
            MCS_CondsDF_yrs[["MoistDaysConsecWinter"]] > 45

          # COND10 - MCS is Dry in ALL layers for more or equal to 360 days
          # Number of days where all soils are dry:
          MCS_CondsDF_yrs[["AllDryDaysCumAny"]] <- tapply(
            MCS_CondsDF_day[["MCS_Dry_All"]],
            MCS_CondsDF_day[["Years"]],
            sum
          )
          MCS_CondsDF_yrs[["COND10"]] <-
            MCS_CondsDF_yrs[["AllDryDaysCumAny"]] >= 360

          icol <- c(
            "COND0", "COND1", "COND2", "COND2_1", "COND2_2", "COND2_3",
            "COND3", "COND3_1", "COND4", "COND5", "COND6", "COND6_1", "COND7",
            "COND8", "COND9", "COND10"
          )
          icol_new <- paste0("MCS_", icol)
          MCS_CondsDF3 <- as.matrix(MCS_CondsDF_yrs[, icol, drop = FALSE])

          if (opt_SMTR[["aggregate_at"]] == "conditions") {
            tmp <- matrix(
              data = colSums(MCS_CondsDF3, na.rm = TRUE),
              nrow = 1,
              ncol = length(icol),
              dimnames = list(NULL, icol_new)
            )
            MCS_CondsDF3 <- tmp >= crit_agree

          } else {
            dimnames(MCS_CondsDF3)[[2]] <- icol_new
          }


          #---Soil moisture regime: Soil Survey Staff 2014
          # (Key to Soil Taxonomy): p.28-31
          SMTR[["SMR"]] <- t(apply(
            X = cbind(ACS_CondsDF3, MCS_CondsDF3),
            MARGIN = 1,
            FUN = function(x) {
              do.call(
                SMR_logic,
                args = c(as.list(x), list(has_permafrost = has_permafrost))
              )
            }
          ))

          SMTR[["cond_annual"]][, icols1] <- as.matrix(
            ACS_CondsDF_yrs[, icols1]
          )

          SMTR[["cond_annual"]][, icols2] <- as.matrix(
            stats::aggregate(
              ACS_CondsDF_day[, icols2],
              by = list(ACS_CondsDF_day[["Years"]]),
              mean
            )[, -1]
          )

          SMTR[["cond_annual"]][, icols3] <- as.matrix(
            MCS_CondsDF_yrs[, icols3]
          )

          SMTR[["cond_annual"]][, icols4] <- as.matrix(
            stats::aggregate(
              MCS_CondsDF_day[, icols4],
              by = list(MCS_CondsDF_day[["Years"]]),
              mean
            )[, -1]
          )

          SMTR[["regimes_done"]] <- TRUE

        } else {
          has_notenough_normalyears <- TRUE
          SMTR[["SMR"]][] <- NA # nolint: extraction_operator_linter.
        }

      } else {
        SMTR[["STR"]][] <- NA # nolint: extraction_operator_linter.
        SMTR[["SMR"]][] <- NA # nolint: extraction_operator_linter.
        has_notenough_normalyears <- TRUE
      }

      if (has_notenough_normalyears) {
        if (verbose) {
          print(paste0(
            msg_tag, ": number of normal years is ",
            SMTR[["SMR_normalyears_N"]], " which is insufficient to calculate ",
            "NRCS soil moisture",
            if (SMTR[["SMR_normalyears_N"]] <= 0) "/temperature",
            " regimes."
          ))
        }
      }

    } else {
      if (verbose) {
        print(paste0(
          msg_tag, ": has unrealistic soil temperature values: ",
          "NRCS soil moisture/temperature regimes not calculated."
        ))
      }

      SMTR[["STR"]][] <- NA # nolint: extraction_operator_linter.
      SMTR[["SMR"]][] <- NA # nolint: extraction_operator_linter.
      SMTR[["has_realistic_SoilTemp"]] <- 0
    }

  } else {
    if (verbose) {
      print(paste0(
        msg_tag, ": soil temperature module turned off but ",
        "required for NRCS Soil Moisture/Temperature Regimes."
      ))
    }

    SMTR[["STR"]][] <- NA # nolint: extraction_operator_linter.
    SMTR[["SMR"]][] <- NA # nolint: extraction_operator_linter.
    SMTR[["has_simulated_SoilTemp"]] <- 0
  }

  SMTR
}


#' Table 1 in Chambers et al. 2014
#'
#' @references Chambers, J. C., D. A. Pyke, J. D. Maestas, M. Pellant,
#'   C. S. Boyd, S. B. Campbell, S. Espinosa, D. W. Havlina, K. E. Mayer, and
#'   A. Wuenschel. 2014. Using Resistance and Resilience Concepts to Reduce
#'   Impacts of Invasive Annual Grasses and Altered Fire Regimes on the
#'   Sagebrush Ecosystem and Greater Sage-Grouse: A Strategic Multi-Scale
#'   Approach. Gen. Tech. Rep. RMRS-GTR-326. U.S. Department of Agriculture,
#'   Forest Service, Rocky Mountain Research Station, Fort Collins, CO.
Chambers2014_Table1 <- function() {

  tmp1 <- 2.54 * 10 * matrix(
    data = c(14, Inf, 12, 22, 12, 16, 6, 12, 8, 12),
    ncol = 2,
    byrow = TRUE,
    dimnames = list(NULL, c("MAP_low", "MAP_high"))
  )

  tmp2 <- matrix(
    data = c(
      "Cryic", "Xeric", "ModeratelyHigh", "High",
      "Frigid", "Xeric", "ModeratelyHigh", "Moderate",
      "Mesic", "Xeric", "Moderate", "ModeratelyLow",
      "Frigid", "Aridic", "Low", "Moderate",
      "Mesic", "Aridic", "Low", "Low"
    ),
    ncol = 4,
    byrow = TRUE,
    dimnames = list(NULL, c("STR", "SMR", "Resilience", "Resistance"))
  )

  data.frame(tmp1, tmp2, stringsAsFactors = FALSE)
}


#' Determine resilience & resistance classes (sensu Chambers et al. 2014) based
#' on soil moisture and soil temperature regimes
#'
#' @param Tregime A named numeric vector. The soil temperature regime
#'   \var{\dQuote{STR}} element of the return object of
#'   \code{\link{calc_SMTRs}}.
#' @param Sregime A named numeric vector. The soil moisture regime
#'   \var{\dQuote{SMR}} element of the return object of
#'   \code{\link{calc_SMTRs}}.
#' @param MAP_mm A numeric value. The mean annual precipitation in millimeters.
#'
#' @return A named numeric vector of \code{0s} and \code{1s} indicating
#'   the matching resilience and resistance class. Note: All elements may be
#'   0 if the input soil moisture/temperature regimes are not covered by
#'   Chambers et al. 2014.
#'
#' @references Chambers, J. C., D. A. Pyke, J. D. Maestas, M. Pellant,
#'   C. S. Boyd, S. B. Campbell, S. Espinosa, D. W. Havlina, K. E. Mayer, and
#'   A. Wuenschel. 2014. Using Resistance and Resilience Concepts to Reduce
#'   Impacts of Invasive Annual Grasses and Altered Fire Regimes on the
#'   Sagebrush Ecosystem and Greater Sage-Grouse: A Strategic Multi-Scale
#'   Approach. Gen. Tech. Rep. RMRS-GTR-326. U.S. Department of Agriculture,
#'   Forest Service, Rocky Mountain Research Station, Fort Collins, CO.
#'
#' @examples
#' # Calculate soil moisture and soil temperature regimes
#' sw_in <- rSOILWAT2::sw_exampleData
#' sw_out <- rSOILWAT2::sw_exec(inputData = sw_in)
#' SMTR <- calc_SMTRs(sim_in = sw_in, sim_out = sw_out)
#'
#' # Determine average across years and set aggregation agreement level
#' Tregime <- colMeans(SMTR[["STR"]]) >= 0.9
#' Sregime <- colMeans(SMTR[["SMR"]]) >= 0.9
#'
#' # Calculate resilience and resistance categories
#' clim <- rSOILWAT2::calc_SiteClimate(
#'   weatherList = rSOILWAT2::get_WeatherHistory(sw_in)
#' )
#' calc_RRs_Chambers2014(Tregime, Sregime, MAP_mm = 10 * clim[["MAP_cm"]])
#'
#' @export
calc_RRs_Chambers2014 <- function(Tregime, Sregime, MAP_mm) {

  # Result containers
  cats <- c("Low", "ModeratelyLow", "Moderate", "ModeratelyHigh", "High")
  resilience <- resistance <- rep(0, times = length(cats))
  names(resilience) <- names(resistance) <- cats

  if (!all(is.na(Tregime)) && !all(is.na(Sregime))) {
    Table1 <- Chambers2014_Table1()
    Type <- as.logical(
      Tregime[Table1[, "STR"]]) &
      as.logical(Sregime[Table1[, "SMR"]]
    )
    Characteristics <-
      MAP_mm >= Table1[, "MAP_low"] & MAP_mm <=  Table1[, "MAP_high"]

    # Resilience and Resistance
    is_notRR <- which(!is.na(Type) & !Type & Characteristics)

    if (any(is_notRR)) {
      resilience[Table1[is_notRR, "Resilience"]] <- 0
      resistance[Table1[is_notRR, "Resistance"]] <- 0
    }

    is_RR <- which(!is.na(Type) & Type & Characteristics)
    if (any(is_RR)) {
      resilience[Table1[is_RR, "Resilience"]] <- 1
      resistance[Table1[is_RR, "Resistance"]] <- 1
    }

  } else {
    resilience[] <- resistance[] <- NA # nolint: extraction_operator_linter.
  }

  c(resilience = resilience, resistance = resistance)
}


#' Table 1 in Maestas et al. 2016
#'
#' @section Details: We need to make the following additional assumptions:
#'   \itemize{
#'     \item "Dry-Xeric" == "Xeric bordering on Aridic"
#'     \item "Weak-Aridic" == "Aridic bordering on Xeric"
#'   }
#'
#' @references Maestas, J.D., Campbell, S.B., Chambers, J.C., Pellant, M. &
#'   Miller, R.F. (2016). Tapping Soil Survey Information for Rapid Assessment
#'   of Sagebrush Ecosystem Resilience and Resistance. Rangelands, 38, 120-128.
Maestas2016_Table1 <- function() {
  matrix(
    data = c(
      "Cryic", "Typic-Xeric", "High",
      "Cryic", "Dry-Xeric", "High",
      "Frigid", "Typic-Xeric", "High",
      "Cryic", "Weak-Aridic", "High",

      "Cryic", "Typic-Aridic", "Moderate",
      "Frigid", "Dry-Xeric", "Moderate",
      "Frigid", "Typic-Aridic", "Moderate",
      "Frigid", "Weak-Aridic", "Moderate",
      "Mesic", "Typic-Xeric", "Moderate",

      "Mesic", "Dry-Xeric", "Low",
      "Mesic", "Weak-Aridic", "Low",
      "Mesic", "Typic-Aridic", "Low"
    ),
    ncol = 3,
    byrow = TRUE,
    dimnames = list(NULL, c("STR", "SMR", "RR"))
  )
}

#' Determine resilience & resistance classes (sensu Maestas et al. 2016) based
#' on soil moisture and soil temperature regimes
#'
#' @param Tregime A named numeric vector. The soil temperature regime
#'   \var{\dQuote{STR}} element of the return object of
#'   \code{\link{calc_SMTRs}}.
#' @param Sregime A named numeric vector. The soil moisture regime
#'   \var{\dQuote{SMR}} element of the return object of
#'   \code{\link{calc_SMTRs}}.
#'
#' @return A named numeric vector of \code{0s} and \code{1s} indicating
#'   the matching resilience and resistance class. Note: All elements may be
#'   0 if the input soil moisture/temperature regimes are not covered by
#'   Maestas et al. 2016.
#'
#' @references Maestas, J.D., Campbell, S.B., Chambers, J.C., Pellant, M. &
#'   Miller, R.F. (2016). Tapping Soil Survey Information for Rapid Assessment
#'   of Sagebrush Ecosystem Resilience and Resistance. Rangelands, 38, 120-128.
#'
#' @examples
#' # Calculate soil moisture and soil temperature regimes
#' sw_in <- rSOILWAT2::sw_exampleData
#' sw_out <- rSOILWAT2::sw_exec(inputData = sw_in)
#' SMTR <- calc_SMTRs(sim_in = sw_in, sim_out = sw_out)
#'
#' # Determine average across years and set aggregation agreement level
#' Tregime <- colMeans(SMTR[["STR"]]) >= 0.9
#' Sregime <- colMeans(SMTR[["SMR"]]) >= 0.9
#'
#' # Calculate resilience and resistance categories
#' calc_RRs_Maestas2016(Tregime, Sregime)
#'
#' @export
calc_RRs_Maestas2016 <- function(Tregime, Sregime) {

  RR <- c(Low = NA, Moderate = NA, High = NA)

  if (!all(is.na(Tregime)) && !all(is.na(Sregime))) {
    Table1 <- Maestas2016_Table1()

    tmp <- as.logical(
      Tregime[Table1[, "STR"]]) &
      as.logical(Sregime[Table1[, "SMR"]]
    )

    is_notRR <- !is.na(tmp) & !tmp
    if (any(is_notRR)) {
      RR[Table1[is_notRR, "RR"]] <- 0
    }

    is_RR <- !is.na(tmp) & tmp
    if (any(is_RR)) {
      RR[Table1[is_RR, "RR"]] <- 1
    }
  }

  RR
}


#------ End of SMTR functions
########################
DrylandEcology/rSW2funs documentation built on June 28, 2023, 2:51 a.m.