R/cql.R

Defines functions assert_cql_name assert_cql_dots cql_in cql_sum cql_span cql_order cql_last cql_first cql_combine cql_before cql_after cql_start cql_shift cql_sapwood_model cql_sapwood cql_sample cql_reservoir cql_r_simulate cql_r_combine cql_probability cql_prior cql_pois cql_p cql_outlier_model cql_outlier cql_offset cql_number cql_mix_curves cql_mcmc_sample cql_line cql_label cql_kde_plot cql_kde_model cql_interval cql_gap cql_exp cql_end cql_difference cql_delta_r cql_curve cql_covar_matrix cql_correlation cql_correl_matrix cql_c_simulate cql_c_combine cql_axis cql_age cql_u cql_top_hat cql_t cql_lnn cql_n cql_transition cql_zero_boundary cql_tau_boundary cql_sigma_boundary cql_boundary cql_v_sequence cql_u_sequence cql_p_sequence cql_d_sequence cql_sequence cql_phase cql_date cql_r_f14c cql_c_date cql_r_date cql_options print.cql as_cql.default as_cql.list as_cql cql

Documented in as_cql as_cql.default as_cql.list cql cql_after cql_age cql_axis cql_before cql_boundary cql_c_combine cql_c_date cql_combine cql_correlation cql_correl_matrix cql_covar_matrix cql_c_simulate cql_curve cql_date cql_delta_r cql_difference cql_d_sequence cql_end cql_exp cql_first cql_gap cql_interval cql_kde_model cql_kde_plot cql_label cql_last cql_line cql_lnn cql_mcmc_sample cql_mix_curves cql_n cql_number cql_offset cql_options cql_order cql_outlier cql_outlier_model cql_p cql_phase cql_pois cql_prior cql_probability cql_p_sequence cql_r_combine cql_r_date cql_reservoir cql_r_f14c cql_r_simulate cql_sample cql_sapwood cql_sapwood_model cql_sequence cql_shift cql_sigma_boundary cql_span cql_start cql_sum cql_t cql_tau_boundary cql_top_hat cql_transition cql_u cql_u_sequence cql_v_sequence cql_zero_boundary

# API for OxCal's Chronological Query Language (CQL2)
# See https://c14.arch.ox.ac.uk/oxcalhelp/hlp_commands.html

# TODO:
# * Indenting
# * More sophisticated type checking:
#   * Expressions in e.g. Boundary() can only take a limited number of other
#     commands

#' Chronological Query Language (CQL)
#'
#' Provides an R interface for the Chronological Query Language (CQL), primarily
#' used to input commands and describe models to OxCal.
#'
#' @param ... `cql` objects to be assembled into a script.
#' @param x   Object to be coerced to a `cql` object.
#'
#' @details
#' CQL scripts are represented by the S3 class `cql`. `cql()` takes the output
#' of individual `cql_` functions and assembles them into a single script. See
#' `vignette("cql")` for a tutorial.
#'
#' List arguments to `cql()` are collapsed to produce a single script. If you
#' want to coerce an object to `cql` and preserve its structure, use `as_cql()`
#' instead.
#'
#' @return A CQL script.
#'
#' @family CQL functions
#'
#' @references
#' <https://c14.arch.ox.ac.uk/oxcalhelp/hlp_commands.html>
#'
#' @export
cql <- function(...) {
  cql <- paste(..., sep = "\n", collapse = "\n")
  cql <- as_cql(cql)
  return(cql)
}


# cql class methods -----------------------------------------------------------

#' @rdname cql
#' @export
as_cql <- function(x) UseMethod("as_cql", x)

#' @rdname cql
#' @export
as_cql.list <- function(x) {
  x <- purrr::map(x, as_cql)
  class(x) <- c("cql", "list")
  return(x)
}

#' @rdname cql
#' @export
as_cql.default <- function(x) {
  x <- as.character(x)
  class(x) <- c("cql", "character")
  return(x)
}

#' @export
print.cql <- function(x, ...) {
  cql <- paste0("// CQL2 generated by stratigraphr v",
                utils::packageVersion("stratigraphr"),
                "\n",
                x)
  cql <- cql_in(cql)
  cql <- as.character(cql)
  writeLines(cql)
  invisible(x)
}


# CQL options -------------------------------------------------------------
# https://c14.arch.ox.ac.uk/oxcalhelp/hlp_analysis_detail.html#opt

#' Set OxCal options in CQL
#'
#' This function generates a CQL command that sets OxCal's global options.
#'
#' @param bcad               Logical. Whether BC/AD are used in the log file output.
#' @param convergence_data   Logical. Whether sample convergence data is included in the output data file.
#' @param curve              Character. The default calibration curve.
#' @param cubic              Logical. Whether cubic (as opposed to linear) interpolation is used for calibration curves.
#' @param ensembles          Integer. The number of age-depth ensembles stored during the analysis.
#' @param floruit            Logical. Whether quantile ranges are calculated instead of the default highest posterior density (hpd).
#' @param intercept          Logical. Whether the intercept method is used for radiocarbon calibration ranges.
#' @param k_iterations       Integer. The default number of MCMC passes.
#' @param plus_minus         Logical. Whether + and - are used in place of BC and AD in log files.
#' @param raw_data           Logical. Whether raw calibration curve data is included in the output data file.
#' @param resolution         Integer. The default bin size for probability distributions of Date and Interval type.
#' @param round              Logical. Whether ranges are rounded off.
#' @param round_by           Integer. Resolution of rounding (0 for automatic).
#' @param sd1                Logical. Whether 68.2% (1 σ) ranges are given in the log and tab delimited files.
#' @param sd2                Logical. Whether 95.4% (2 σ) ranges are given in the log and tab delimited files.
#' @param sd3                Logical. Whether 99.7% (3 σ) ranges are given in the log and tab delimited files.
#' @param uniform_span_prior Logical. Whether the two extra prior factors suggested by Nicholls and Jones 2001 are used.
#' @param use_f14c           Logical. Whether all calibrations take place in F14C space (rather than BP space).
#' @param year               Numeric. The datum point for ages - the default is mid AD 1950.
#' @param ...                Additional named arguments converted to OxCal options. See details.
#'
#' @details
#' Parameter descriptions and defaults are taken from the OxCal v4.4
#' documentation (see references).
#'
#' Options not explicitly included here can be specified as additional named
#' arguments to this function. The name must exactly match the name of the
#' option expected by OxCal; unknown or misspelled options are silently ignored.
#'
#' @return
#' A `cql` object.
#'
#' @references
#' <https://c14.arch.ox.ac.uk/oxcalhelp/hlp_analysis_detail.html#opt>
#'
#' @family CQL functions
#'
#' @export
cql_options <- function(bcad = TRUE,
                        convergence_data = FALSE,
                        curve = "intcal20.14c",
                        cubic = TRUE,
                        ensembles = 30,
                        floruit = FALSE,
                        intercept = FALSE,
                        k_iterations = 30,
                        plus_minus = FALSE,
                        raw_data = FALSE,
                        resolution = 5,
                        round = FALSE,
                        round_by = 0,
                        sd1 = TRUE,
                        sd2 = TRUE,
                        sd3 = FALSE,
                        uniform_span_prior = TRUE,
                        use_f14c = TRUE,
                        year = 1950.5,
                        ...) {
  options <- as.list(match.call())

  if (length(options) > 1) {
    options <- options[2:length(options)]

    strings <- sapply(options, is.character)
    options[strings] <- paste0("\"", options[strings], "\"")

    names(options) <- dplyr::recode(
      names(options),
      bcad = "BCAD",
      convergence_data = "ConvergenceData",
      curve = "Curve",
      cubic = "Cubic",
      ensembles = "Ensembles",
      floruit = "Floruit",
      intercept = "Intercept",
      k_iterations = "kInterations",
      plus_minus = "PlusMinus",
      raw_data = "RawData",
      resolution = "Resolution",
      round = "Round",
      round_by = "RoundBy",
      sd1 = "SD1",
      sd2 = "SD2",
      sd3 = "SD3",
      uniform_span_prior = "UniformSpanPrior",
      use_f14c = "UseF14C",
      year = "Year"
    )

    cql <- paste(names(options), options, sep = " = ")
    cql <- paste(cql, collapse = ";\n")
    cql <- paste0("Options()\n",
                  "{\n",
                  cql, ";",
                  "\n};")
  }
  else {
    cql <- "Options();"
  }

  cql <- as_cql(cql)
  return(cql)
}

# CQL date functions ------------------------------------------------------
# (Vectorised)

#' Describe dates in CQL
#'
#' The CQL commands `R_Date`, `F14C_Date`, `C_Date` and `Date` are used to
#' describe individual dated events in a model. `R_Date` and `F14C_Date`
#' represent radiocarbon dates expressed in radiocarbon years (the conventional
#' radiocarbon age, CRA) or *fraction modern* (F14C) respectively.
#' `C_Date` represents a calendar date. `Date` directly specifies a date in
#' OxCal's internal format.
#'
#' @param name  Character. Date label(s), usually a lab code.
#' @param date  Numeric. Date or dates expressed in radiocarbon years (`cql_r_date()`),
#'              F14C (`cql_f14c_date()`), calendar years (`cql_c_date()`), or
#'              OxCal's internal format (`cql_date()`) See details.
#' @param error Integer. Uncertainty associated with the date(s).
#'
#' @details
#' The era expected for calendar dates (BP or BC/AD) depends on a global option
#' in OxCal, which defaults to BC/AD. BC dates are specified as negative values.
#'
#' F14C measurements are recommended for modern, "post-bomb" radiocarbon dates
#' \insertCite{Reimer2004-yl}{stratigraphr}.
#'
#' OxCal's internal date format, used with `Date()`, is a decimal Gregorian
#' year, for details see: <https://c14.arch.ox.ac.uk/oxcalhelp/hlp_analysis_calend.html>
#'
#' @return
#' A `cql` object, or a list of `cql` objects if the arguments are vectors.
#'
#' @references
#' <https://c14.arch.ox.ac.uk/oxcalhelp/hlp_analysis_inform.html#date>
#'
#' <https://c14.arch.ox.ac.uk/oxcalhelp/hlp_commands.html>
#'
#' \insertAllCited{}
#'
#' @family CQL functions
#'
#' @export
cql_r_date <- function(name, date, error) {
  name <- as.character(name)
  checkmate::assert_integerish(date)
  checkmate::assert_integerish(error)

  if(length(name) != length(date) |
     length(date) != length(error) |
     length(error) != length(name)) {
    stop("Vector arguments to name, date, error must all be the same length.")
  }

  cql <- glue::glue('R_Date("{name}", {date}, {error});')

  cql <- as_cql(cql)
  return(cql)
}

#' @rdname cql_r_date
#' @export
cql_c_date <- function(name, date, error) {
  name <- as.character(name)
  checkmate::assert_integerish(date)
  checkmate::assert_integerish(error)

  if(length(name) != length(date) |
     length(date) != length(error) |
     length(error) != length(name)) {
    stop("Vector arguments to name, date, error must all be the same length.")
  }

  cql <- glue::glue('C_date("{name}", {date}, {error});')

  cql <- as_cql(cql)
  return(cql)
}

#' @rdname cql_r_date
#' @export
cql_r_f14c <- function(name, date, error) {
  name <- as.character(name)
  checkmate::assert_integerish(date)
  checkmate::assert_integerish(error)

  if(length(name) != length(date) |
     length(date) != length(error) |
     length(error) != length(name)) {
    stop("Vector arguments to name, date, error must all be the same length.")
  }

  cql <- glue::glue('F14C_date("{name}", {date}, {error});')

  cql <- as_cql(cql)
  return(cql)
}

#' @rdname cql_r_date
#' @export
cql_date <- function(name, date) {
  name <- as.character(name)

  cql <- glue::glue('Date("{name}", {date});')

  cql <- as_cql(cql)
  return(cql)
}


# CQL phase functions -----------------------------------------------------
# (Summary)

#' Describe an unordered group in CQL
#'
#' The CQL command `Phase` is used to describe an unordered group of events in
#' a model.
#'
#' @param name  Character. Label for the phase.
#' @param cql   Vector of `cql` objects contained in the phase.
#'
#' @return
#' A `cql` object.
#'
#' @references
#' <https://c14.arch.ox.ac.uk/oxcalhelp/hlp_commands.html>
#'
#' @family CQL functions
#'
#' @export
#'
#' @examples
#' cql(
#'   cql_phase("Phase A", c(
#'     cql_r_date("ABC-001", 10100, 50),
#'     cql_r_date("ABC-002", 10200, 50),
#'     cql_r_date("ABC-003", 10300, 50)
#'   ))
#' )
cql_phase <- function(name, cql) {
  name <- assert_cql_name(name, "cql_phase")

  if(length(name) > 1) {
    if(length(unique(name)) != 1) {
      warning("Vector arguments to name after the first are ignored.")
    }
    name <- name[[1]]
  }

  cql <- paste0("Phase(\"", name, "\")\n",
                "{\n",
                paste(cql, collapse = "\n"),
                "\n};")

  cql <- as_cql(cql)
  return(cql)
}


# CQL sequence functions --------------------------------------------------
# (Summary)

#' Describe an ordered group in CQL
#'
#' The CQL command `Sequence` is used to describe an *ordered* group of events
#' in a model. Specific types of sequence, i.e. `D_Sequence`, `P_Sequence`,
#' `U_Sequence`, and `V_Sequence`, are not yet implemented in stratigraphr.
#'
#' @param name           Character. Label for the sequence.
#' @param cql            Vector of `cql` objects contained in the sequence.
#' @param boundaries     Logical. If `TRUE`, adds `Boundary` constraints between
#'                       each item in the sequence.
#'
#' @details
#' OxCal expects events within sequences to be in chronological order (oldest to
#' youngest) not stratigraphic order (youngest to oldest).
#'
#' @return
#' A `cql` object.
#'
#' @references
#' <https://c14.arch.ox.ac.uk/oxcalhelp/hlp_commands.html>
#'
#' @family CQL functions
#'
#' @export
#'
#' @examples
#' cql(
#'   cql_sequence("Sequence A", c(
#'     cql_r_date("ABC-001", 10100, 50),
#'     cql_r_date("ABC-002", 10200, 50),
#'     cql_r_date("ABC-003", 10300, 50)
#'   ))
#' )
cql_sequence <- function(name, cql, boundaries = FALSE) {
  name <- assert_cql_name(name, "cql_sequence")
  checkmate::assert_logical(boundaries)

  if(length(name) > 1) {
    if(length(unique(name)) != 1) {
      warning("Vector arguments to name after the first are ignored.")
    }
    name <- name[[1]]
  }


  # TODO: Different types of boundaries, parameters, etc.
  if(boundaries) {
    cql <- as.vector(rbind(cql, rep(cql_boundary(""), length(cql))))
    cql <- c(cql_boundary(""), cql)
  }

  cql <- paste0(cql, collapse = "\n")

  cql <- paste(paste0("Sequence(\"", name, "\")"),
               "{",
               cql,
               "};",
               sep = "\n")

  cql <- as_cql(cql)
  return(cql)
}

#' @rdname cql_sequence
#' @export
cql_d_sequence <- function() { warning("CQL command D_Sequence is not yet implemented in stratigraphr") }

#' @rdname cql_sequence
#' @export
cql_p_sequence <- function() { warning("CQL command P_Sequence is not yet implemented in stratigraphr") }

#' @rdname cql_sequence
#' @export
cql_u_sequence <- function() { warning("CQL command U_Sequence is not yet implemented in stratigraphr") }

#' @rdname cql_sequence
#' @export
cql_v_sequence <- function() { warning("CQL command V_Sequence is not yet implemented in stratigraphr") }

# CQL boundary functions --------------------------------------------------

#' Describe a boundary constraint in CQL
#'
#' @description
#' The CQL command `Boundary` describes constraints within an ordered sequence
#' (see [cql_sequence()]). Groups of events between two boundaries are assumed
#' to be sampled from the same prior distribution. `Boundary` alone models a
#' uniform prior. Other types of boundary, i.e. `Sigma_Boundary`, `Tau_Boundary`,
#' and `Zero_Boundary` are not yet implemented in stratigraphr.
#'
#' Boundaries can contain a `Transition` command, in which case they describe
#' a non-instantaneous transition or 'trapezium' prior.
#'
#' @param name        Character. Label for a boundary or transition.
#' @param ...         `cql` objects contained within a boundary.
#' @param prior       `cql` object. Expression describing the prior likelihood of
#'                    a boundary or transition.
#'
#' @return
#' A `cql` object.
#'
#' @references
#' <https://c14.arch.ox.ac.uk/oxcalhelp/hlp_analysis_oper.html#group>
#'
#' <https://c14.arch.ox.ac.uk/oxcalhelp/hlp_commands.html>
#'
#' @family CQL functions
#'
#' @export
#'
#' @examples
#' # Uniform model with unknown boundaries
#' cql(
#'   cql_boundary("P1 start"),
#'   cql_phase("P1", list(
#'     cql_r_date("A", 5050, 30),
#'     cql_r_date("B", 5000, 30),
#'     cql_r_date("C", 4950, 30)
#'   )),
#'   cql_boundary("P1 end")
#' )
#'
#' # Uniform model with boundaries with a prior likelihood
#' cql(
#'   cql_boundary("P1 start",
#'                prior = cql_date("P1S-Prior", cql_u("", 5200, 5100))),
#'   cql_phase("P1", list(
#'     cql_r_date("A", 5050, 30),
#'     cql_r_date("B", 5000, 30),
#'     cql_r_date("C", 4950, 30)
#'   )),
#'   cql_boundary("P1 end",
#'                prior = cql_date("P1E-Prior", cql_u("", 4800, 4900)))
#' )
#'
#' # 'Trapezium' model
#' cql(
#'   cql_boundary("P1 start",
#'     cql_transition("")
#'   ),
#'   cql_phase("P1", list(
#'     cql_r_date("A", 5050, 30),
#'     cql_r_date("B", 5000, 30),
#'     cql_r_date("C", 4950, 30)
#'   )),
#'   cql_boundary("P1 end",
#'     cql_transition("")
#'   )
#' )
cql_boundary <- function(name, ..., prior = NULL) {
  name <- assert_cql_name(name, "cql_boundary")
  checkmate::assert_class(prior, "cql", null.ok = TRUE)
  assert_cql_dots(...)

  if(is.null(prior)) {
    cql <- glue::glue('Boundary("{name}")')
  }
  else {
    prior <- stringr::str_remove(prior, stringr::coll(";"))
    cql <- glue::glue('Boundary("{name}", {prior})')
  }

  if(!missing(...)) {
    innercql <- paste(..., sep = "\n")
    cql <- paste0(
      cql, "\n",
      "{\n",
      innercql, "\n",
      "};"
    )
  }
  else {
    cql <- paste0(cql, ";")
  }

  cql <- as_cql(cql)
  return(cql)
}

#' @rdname cql_boundary
#' @export
cql_sigma_boundary <- function() { warning("CQL command Sigma_Boundary is not yet implemented in stratigraphr") }

#' @rdname cql_boundary
#' @export
cql_tau_boundary <- function() { warning("CQL command Tau_Boundary is not yet implemented in stratigraphr") }

#' @rdname cql_boundary
#' @export
cql_zero_boundary <- function() { warning("CQL command Zero_Boundary is not yet implemented in stratigraphr") }

#' @rdname cql_boundary
#' @export
cql_transition <- function(name, prior = NULL) {
  name <- as.character(name)
  checkmate::assert_class(prior, "cql", null.ok = TRUE)

  if(is.null(prior)) {
    cql <- glue::glue('Transition("{name}");')
  }
  else {
    prior <- stringr::str_remove(prior, stringr::coll(";"))
    cql <- glue::glue('Transition("{name}", {prior});')
  }

  cql <- as_cql(cql)
  return(cql)
}


# CQL distribution functions ----------------------------------------------

#' Describe distributions in CQL
#'
#' The CQL commands `N`, `LnN`, `T`, `U` and `Top_Hat` describe various types
#' of probability distribution functions. `cql_n()` defines a normal distribution
#' by its mean and standard deviation; `cql_lnn()` a log–normal distribution by
#' its mean and standard deviation; `cql_t()` a Student's t distribution by
#' degrees of freedom; `cql_u()` a uniform distribution by its start and end;
#' `cql_top_hat()` a uniform distribution by its central point and width.
#'
#' @param name        Character. Label for the distribution.
#' @param mu          Integer. Mean of a normal or log–normal distribution.
#' @param sigma       Integer. Standard deviation of a normal or log–normal distribution.
#' @param freedom     Integer. Degrees of freedom of a Student's t distribution.
#' @param scale       Numeric. Optional scaling parameter for a Student's t distribution.
#' @param mid         Integer. Centre point of a uniform distribution.
#' @param half_width  Integer. Half-width of a uniform distribution.
#' @param from        Integer. Start point of a uniform distribution.
#' @param to          Integer. End point of a uniform distribution.
#' @param resolution  Integer. Resolution of the PDF. Leave `NULL` (the default) to use the OxCal default.
#'
#' @return
#' A `cql` object.
#'
#' @references
#' <https://c14.arch.ox.ac.uk/oxcalhelp/hlp_analysis_inform.html#param>
#'
#' <https://c14.arch.ox.ac.uk/oxcalhelp/hlp_commands.html>
#'
#' @family CQL functions
#'
#' @export
#'
#' @examples
#' # Describe a date as a uniform distribution
#' cql_date("U-Date", cql_u("U", -5000, -4000))
cql_n <- function(name, mu, sigma, resolution = NULL) {
  name <- as.character(name)
  checkmate::assert_integerish(mu)
  checkmate::assert_integerish(sigma)
  checkmate::assert_integerish(resolution, null.ok = TRUE)

  if(is.null(resolution)) {
    cql <- glue::glue('N("{name}", {mu}, {sigma});')
  }
  else {
    cql <- glue::glue('N("{name}", {mu}, {sigma}, {resolution});')
  }

  cql <- as_cql(cql)
  return(cql)
}

#' @rdname cql_n
#' @export
cql_lnn <- function(name, mu, sigma, resolution = NULL) {
  name <- as.character(name)
  checkmate::assert_integerish(mu)
  checkmate::assert_integerish(sigma)
  checkmate::assert_integerish(resolution, null.ok = TRUE)

  if(is.null(resolution)) {
    cql <- glue::glue('LnN("{name}", {mu}, {sigma});')
  }
  else {
    cql <- glue::glue('LnN("{name}", {mu}, {sigma}, {resolution});')
  }

  cql <- as_cql(cql)
  return(cql)
}

#' @rdname cql_n
#' @export
cql_t <- function(name, freedom, scale = 1, resolution = NULL) {
  name <- as.character(name)
  checkmate::assert_integerish(freedom)
  checkmate::assert_integerish(scale)
  checkmate::assert_integerish(resolution, null.ok = TRUE)

  if(is.null(resolution)) {
    cql <- glue::glue('T("{name}", {freedom}, {scale});')
  }
  else {
    cql <- glue::glue('T("{name}", {freedom}, {scale}, {resolution});')
  }

  cql <- as_cql(cql)
  return(cql)
}

#' @rdname cql_n
#' @export
cql_top_hat <- function(name, mid, half_width) {
  name <- as.character(name)
  checkmate::assert_integerish(mid)
  checkmate::assert_integerish(half_width)

  cql <- glue::glue('Top_Hat("{name}", {mid}, {half_width});')

  cql <- as_cql(cql)
  return(cql)
}

#' @rdname cql_n
#' @export
cql_u <- function(name, from, to, resolution = NULL) {
  name <- as.character(name)
  checkmate::assert_integerish(from)
  checkmate::assert_integerish(to)
  checkmate::assert_integerish(resolution, null.ok = TRUE)

  if(is.null(resolution)) {
    cql <- glue::glue('U("{name}", {from}, {to});')
  }
  else {
    cql <- glue::glue('U("{name}", {from}, {to}, {resolution});')
  }

  cql <- as_cql(cql)
  return(cql)
}

# Other CQL functions ------------------------------------------------------------

#' Other CQL functions (unimplemented)
#'
#' These CQL commands are not yet implemented in stratigraphr and only included
#' here for reference. Unimplemented commands can be included manually using
#' [cql()] (see examples), but if you need one for an analysis please file a bug
#' report!
#'
#' @name cql_other
#'
#' @param ... Ignored.
#'
#' @return
#' A warning not to use these functions!
#'
#' @family CQL functions
#'
#' @examples
#' # Use cql() to manually write commands that are not yet implemented, e.g.:
#' cql("After(\"A\");")
NULL

#' @rdname cql_other
#' @export
cql_age <- function(...) { warning("CQL command Age is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_axis <- function(...) { warning("CQL command Axis is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_c_combine <- function(...) { warning("CQL command C_Combine is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_c_simulate <- function(...) { warning("CQL command C_Simulate is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_correl_matrix <- function(...) { warning("CQL command Correl_Matrix is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_correlation <- function(...) { warning("CQL command Correlation is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_covar_matrix <- function(...) { warning("CQL command Covar_Matrix is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_curve <- function(...) { warning("CQL command Curve is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_delta_r <- function(...) { warning("CQL command Delta_R is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_difference <- function(...) { warning("CQL command Difference is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_end <- function(...) { warning("CQL command End is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_exp <- function(...) { warning("CQL command Exp is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_gap <- function(...) { warning("CQL command Gap is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_interval <- function(...) { warning("CQL command Interval is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_kde_model <- function(...) { warning("CQL command KDE_Model is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_kde_plot <- function(...) { warning("CQL command KDE_Plot is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_label <- function(...) { warning("CQL command Label is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_line <- function(...) { warning("CQL command Line is not yet implemented in stratigraphr") }


#' @rdname cql_other
#' @export
cql_mcmc_sample <- function(...) { warning("CQL command MCMC_Sample is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_mix_curves <- function(...) { warning("CQL command Mix_Curves is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_number <- function(...) { warning("CQL command Number is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_offset <- function(...) { warning("CQL command Offset is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_outlier <- function(...) { warning("CQL command Outlier is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_outlier_model <- function(...) { warning("CQL command Outlier_Model is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_p <- function(...) { warning("CQL command P is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_pois <- function(...) { warning("CQL command Pois is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_prior <- function(...) { warning("CQL command Prior is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_probability <- function(...) { warning("CQL command Probability is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_r_combine <- function(...) { warning("CQL command R_Combine is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_r_simulate <- function(...) { warning("CQL command R_Simulate is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_reservoir <- function(...) { warning("CQL command Reservoir is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_sample <- function(...) { warning("CQL command Sample is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_sapwood <- function(...) { warning("CQL command Sapwood is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_sapwood_model <- function(...) { warning("CQL command Sapwood_Model is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_shift <- function(...) { warning("CQL command Shift is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_start <- function(...) { warning("CQL command Start is not yet implemented in stratigraphr") }


# CQL group functions -----------------------------------------------------

#' @rdname cql_other
#' @export
cql_after <- function() { warning("CQL command After is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_before <- function() { warning("CQL command Before is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_combine <- function() { warning("CQL command Combine is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_first <- function() { warning("CQL command First is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_last <- function() { warning("CQL command Last is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_order <- function() { warning("CQL command Order is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_span <- function() { warning("CQL command Span is not yet implemented in stratigraphr") }

#' @rdname cql_other
#' @export
cql_sum <- function() { warning("CQL command Sum is not yet implemented in stratigraphr") }


# CQL utility functions (not exported) ----------------------------------------

cql_in <- function(x) {
  x <- stringr::str_split(x, stringr::coll("\n"))
  x <- purrr::map(x, function(x) {
    idd <- 1
    for(i in 1:length(x)) {
      if(stringr::str_starts(stringr::str_trim(x[i]), stringr::coll("}"))) idd <- idd - 1
      indent <- paste0(rep(" ", idd), collapse = "")
      x[i] <- paste0(indent, x[i])
      if(stringr::str_starts(stringr::str_trim(x[i]), stringr::coll("{"))) idd <- idd + 1
    }

    x <- paste0(x, collapse = "\n")
    return(x)
  })

  if(length(x) == 1) {
    x <- unlist(x)
  }

  x <- as_cql(x)
  return(x)
}

assert_cql_dots <- function(...) {
  purrr::map(list(...),
             checkmate::assert_class,
             classes= "cql",
             .var.name = "...")
}

# The Name parameter of most OxCal commands is optional, but since we rely on
# R's dots syntax for nested commands, there's no robust way to determine
# whether the user intentionally omitted it. This function is used
# in CQL functions that expect Name as a first argument to do some soft checks.
# If it's passed a cql object, we assume the user omitted Name and throw an
# error. If it's passed something other than a character, we warn that the user
# might have omitted it but try to continue, e.g. if the name was specified as
# an integer representing just the numeric part of a lab code.
#
# Exceptions are the date functions (e.g. R_Date()), which currently insist on
# all three arguments.
assert_cql_name <- function(name, function_name = "CQL function") {
  # TODO: Could be simplified if not is added to checkmate:
  # https://github.com/mllg/checkmate/issues/193
  if(checkmate::test_class(name, "cql")) {
    stop("First argument to ", function_name, " is a cql object. ",
         "Did you forget to include a name?")
  }
  else if (!checkmate::test_character(name)) {
    warning("First argument to ", function_name, " is not a string. ",
            "Did you forget to include a name?")
  }

  return(as.character(name))
}
joeroe/stratigraphr documentation built on May 17, 2023, 9:52 p.m.