diff --git a/R/model.R b/R/model.R index 79882226..0359678a 100644 --- a/R/model.R +++ b/R/model.R @@ -196,6 +196,7 @@ cmdstan_model <- function(stan_file = NULL, exe_file = NULL, compile = TRUE, ... #' [`$hpp_file()`][model-method-compile] | Return the file path to the `.hpp` file containing the generated C++ code. | #' [`$save_hpp_file()`][model-method-compile] | Save the `.hpp` file containing the generated C++ code. | #' [`$expose_functions()`][model-method-expose_functions] | Expose Stan functions for use in R. | +#' [`$cmdstan_defaults()`][model-method-cmdstan_defaults] | Get CmdStan default argument values for a method. | #' #' ## Diagnostics #' @@ -2209,6 +2210,51 @@ expose_functions = function(global = FALSE, verbose = FALSE) { CmdStanModel$set("public", name = "expose_functions", value = expose_functions) +#' Get CmdStan default argument values +#' +#' @name model-method-cmdstan_defaults +#' @aliases cmdstan_defaults +#' @family CmdStanModel methods +#' +#' @description The `$cmdstan_defaults()` method of a [`CmdStanModel`] +#' object queries the compiled model binary for the default argument +#' values used by a given inference method. The returned list uses +#' cmdstanr-style argument names (e.g., `iter_sampling` instead of +#' CmdStan's `num_samples`). +#' +#' The model must be compiled before calling this method. +#' +#' @param method (string) The inference method whose defaults to +#' retrieve. One of `"sample"`, `"optimize"`, `"variational"`, +#' `"pathfinder"`, or `"laplace"`. +#' @return A named list of default argument values for the specified +#' method, with cmdstanr-style argument names. +#' +#' @template seealso-docs +#' +#' @examples +#' \dontrun{ +#' mod <- cmdstan_model(file.path(cmdstan_path(), +#' "examples/bernoulli/bernoulli.stan")) +#' mod$cmdstan_defaults("sample") +#' mod$cmdstan_defaults("optimize") +#' } +#' +cmdstan_defaults <- function(method = c("sample", "optimize", "variational", + "pathfinder", "laplace")) { + method <- match.arg(method) + if (length(self$exe_file()) == 0 || !file.exists(self$exe_file())) { + stop( + "'$cmdstan_defaults()' requires a compiled model. ", + "Please compile the model first with '$compile()'.", + call. = FALSE + ) + } + parse_cmdstan_args(self$exe_file(), method) +} +CmdStanModel$set("public", name = "cmdstan_defaults", value = cmdstan_defaults) + + # internal ---------------------------------------------------------------- assert_valid_stanc_options <- function(stanc_options) { @@ -2289,10 +2335,10 @@ model_variables <- function(stan_file, include_paths = NULL, allow_undefined = F variables } - is_variables_method_supported <- function(mod) { mod$has_stan_file() && file.exists(mod$stan_file()) } + resolve_exe_path <- function(dir = NULL, private_dir = NULL, self_exe_file = NULL, @@ -2329,3 +2375,199 @@ resolve_exe_path <- function(dir = NULL, } exe } + +# cmdstan_defaults() helpers + +#' Parse CmdStan default argument values from model binary +#' +#' Runs a CmdStan model binary with `help-all` to extract valid arguments +#' and their default values for a given inference method, returning them +#' with cmdstanr argument names. +#' +#' @noRd +#' @param model_binary Path to the CmdStan model binary. +#' @param method Inference method: `"sample"`, `"optimize"`, +#' `"variational"`, `"pathfinder"`, or `"laplace"`. +#' @return A named list with cmdstanr-style argument names and default +#' values. +parse_cmdstan_args <- function(model_binary, method) { + withr::with_path( + c( + toolchain_PATH_env_var(), + tbb_path() + ), + ret <- wsl_compatible_run( + command = wsl_safe_path(model_binary), + args = c(method, "help-all"), + error_on_status = FALSE + ) + ) + # CmdStan may write help text to stdout or stderr depending on the platform + raw <- paste0(ret$stdout, ret$stderr) + output <- strsplit(raw, "\r?\n")[[1]] + + argument_map <- map_cmdstan_to_cmdstanr(method) + cmdstan_keys <- unname(argument_map) + public_names <- names(argument_map) + + defaults <- list() + n <- length(output) + # Track the current hierarchical argument key using section indentation. + section_indents <- integer(0) + section_names <- character(0) + + for (i in seq_len(n)) { + line <- output[i] + content <- trimws(line) + + # Skip blank lines so they don't reset the section stack + if (!nzchar(content)) next + + indent <- nchar(sub("^(\\s*).*", "\\1", line)) + + # Drop sections at deeper or equal indentation + while (length(section_indents) > 0 && + section_indents[[length(section_indents)]] >= indent) { + section_indents <- section_indents[-length(section_indents)] + section_names <- section_names[-length(section_names)] + } + + section_name <- parse_cmdstan_section_name(content) + if (!is.null(section_name)) { + section_indents <- c(section_indents, indent) + section_names <- c(section_names, section_name) + next + } + + arg_name <- parse_cmdstan_arg_name(content) + if (!is.null(arg_name)) { + + # Build the full dotted argument key: method.section1.section2...arg_name + # The top-level method heading (e.g. "sample") is tracked as a section, + # so it becomes the first segment of the key. + full_key <- paste(c(section_names, arg_name), collapse = ".") + + # Check if this full argument key matches one of our target arguments + match_idx <- match(full_key, cmdstan_keys, nomatch = 0L) + + if (match_idx > 0L) { + default_value <- find_cmdstan_default_value(output, i, n) + defaults[[public_names[[match_idx]]]] <- default_value + } + } + } + + defaults +} + +#' Parse CmdStan section name from a help-all line +#' @noRd +parse_cmdstan_section_name <- function(line) { + match <- regmatches(line, regexec("^([a-z_][a-z0-9_]*)$", line))[[1]] + if (length(match) >= 2) match[2] else NULL +} + +#' Parse CmdStan argument name from a help-all line +#' @noRd +parse_cmdstan_arg_name <- function(line) { + match <- regmatches(line, regexec("^([a-z_][a-z0-9_]*)=", line))[[1]] + if (length(match) >= 2) match[2] else NULL +} + +#' Find CmdStan default value following a help-all argument line +#' @noRd +find_cmdstan_default_value <- function(output, line_idx, n_lines) { + default_value <- NULL + + for (j in (line_idx + 1):min(line_idx + 5, n_lines)) { + next_content <- trimws(output[j]) + if (grepl("^Defaults to", next_content)) { + default_value <- parse_default_value(next_content) + break + } + # Stop if we hit another argument + if (grepl("^[a-z_][a-z0-9_]*=", next_content)) break + } + + default_value +} + +#' Parse default value from "Defaults to ..." line +#' @noRd +parse_default_value <- function(line) { + val_str <- sub("^Defaults to\\s*", "", line) + if (val_str %in% c("true", "false")) return(val_str == "true") + if (grepl("^-?[0-9]+$", val_str)) return(as.integer(val_str)) + if (grepl("^-?[0-9]*\\.?[0-9]+([eE][+-]?[0-9]+)?$", val_str)) return(as.numeric(val_str)) + val_str +} + +#' Map CmdStan argument names to CmdStanR argument names +#' @noRd +map_cmdstan_to_cmdstanr <- function(method) { + switch(method, + sample = c( + iter_sampling = "sample.num_samples", + iter_warmup = "sample.num_warmup", + save_warmup = "sample.save_warmup", + thin = "sample.thin", + adapt_engaged = "sample.adapt.engaged", + adapt_delta = "sample.adapt.delta", + init_buffer = "sample.adapt.init_buffer", + term_buffer = "sample.adapt.term_buffer", + window = "sample.adapt.window", + save_metric = "sample.adapt.save_metric", + max_treedepth = "sample.hmc.nuts.max_depth", + metric = "sample.hmc.metric", + metric_file = "sample.hmc.metric_file", + step_size = "sample.hmc.stepsize" + ), + optimize = c( + algorithm = "optimize.algorithm", + jacobian = "optimize.jacobian", + iter = "optimize.iter", + init_alpha = "optimize.lbfgs.init_alpha", + tol_obj = "optimize.lbfgs.tol_obj", + tol_rel_obj = "optimize.lbfgs.tol_rel_obj", + tol_grad = "optimize.lbfgs.tol_grad", + tol_rel_grad = "optimize.lbfgs.tol_rel_grad", + tol_param = "optimize.lbfgs.tol_param", + history_size = "optimize.lbfgs.history_size" + ), + variational = c( + algorithm = "variational.algorithm", + iter = "variational.iter", + grad_samples = "variational.grad_samples", + elbo_samples = "variational.elbo_samples", + eta = "variational.eta", + adapt_engaged = "variational.adapt.engaged", + adapt_iter = "variational.adapt.iter", + tol_rel_obj = "variational.tol_rel_obj", + eval_elbo = "variational.eval_elbo", + draws = "variational.output_samples" + ), + pathfinder = c( + init_alpha = "pathfinder.init_alpha", + tol_obj = "pathfinder.tol_obj", + tol_rel_obj = "pathfinder.tol_rel_obj", + tol_grad = "pathfinder.tol_grad", + tol_rel_grad = "pathfinder.tol_rel_grad", + tol_param = "pathfinder.tol_param", + history_size = "pathfinder.history_size", + draws = "pathfinder.num_psis_draws", + num_paths = "pathfinder.num_paths", + save_single_paths = "pathfinder.save_single_paths", + psis_resample = "pathfinder.psis_resample", + calculate_lp = "pathfinder.calculate_lp", + max_lbfgs_iters = "pathfinder.max_lbfgs_iters", + single_path_draws = "pathfinder.num_draws", + num_elbo_draws = "pathfinder.num_elbo_draws" + ), + laplace = c( + jacobian = "laplace.jacobian", + draws = "laplace.draws" + ), + character(0) + ) +} + diff --git a/_pkgdown.yml b/_pkgdown.yml index 123d2191..af774887 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -95,6 +95,7 @@ reference: - read_cmdstan_csv - write_stan_json - write_stan_file + - print_stan_file - draws_to_csv - as_mcmc.list - as_draws.CmdStanMCMC diff --git a/man/CmdStanModel.Rd b/man/CmdStanModel.Rd index 0b21ac5c..7057014e 100644 --- a/man/CmdStanModel.Rd +++ b/man/CmdStanModel.Rd @@ -30,6 +30,7 @@ methods, many of which have their own (linked) documentation pages: \code{\link[=model-method-compile]{$hpp_file()}} \tab Return the file path to the \code{.hpp} file containing the generated C++ code. \cr \code{\link[=model-method-compile]{$save_hpp_file()}} \tab Save the \code{.hpp} file containing the generated C++ code. \cr \code{\link[=model-method-expose_functions]{$expose_functions()}} \tab Expose Stan functions for use in R. \cr + \code{\link[=model-method-cmdstan_defaults]{$cmdstan_defaults()}} \tab Get CmdStan default argument values for a method. \cr } } diff --git a/man/cmdstanr-package.Rd b/man/cmdstanr-package.Rd index c2ccbb1d..e6479f57 100644 --- a/man/cmdstanr-package.Rd +++ b/man/cmdstanr-package.Rd @@ -34,22 +34,22 @@ algorithms, and writing results to output files. \subsection{Advantages of RStan}{ \itemize{ \item Allows other developers to distribute R packages with \emph{pre-compiled} -Stan programs (like \strong{rstanarm}) on CRAN. (Note: As of 2023, this can -mostly be achieved with CmdStanR as well. See \href{https://mc-stan.org/cmdstanr/articles/cmdstanr-internals.html#developing-using-cmdstanr}{Developing using CmdStanR}.) -\item Avoids use of R6 classes, which may result in more familiar syntax for -many R users. +Stan programs (like \strong{rstanarm}) on CRAN. (Note: As of 2023, this +can mostly be achieved with CmdStanR as well. See \href{https://mc-stan.org/cmdstanr/articles/cmdstanr-internals.html#developing-using-cmdstanr}{Developing using CmdStanR}.) +\item Avoids use of R6 classes, which may result in more familiar syntax +for many R users. \item CRAN binaries available for Mac and Windows. } } \subsection{Advantages of CmdStanR}{ \itemize{ -\item Compatible with latest versions of Stan. Keeping up with Stan releases -is complicated for RStan, often requiring non-trivial changes to the -\strong{rstan} package and new CRAN releases of both \strong{rstan} and -\strong{StanHeaders}. With CmdStanR the latest improvements in Stan will be -available from R immediately after updating CmdStan using -\code{cmdstanr::install_cmdstan()}. +\item Compatible with latest versions of Stan. Keeping up with Stan +releases is complicated for RStan, often requiring non-trivial +changes to the \strong{rstan} package and new CRAN releases of both +\strong{rstan} and \strong{StanHeaders}. With CmdStanR the latest improvements +in Stan will be available from R immediately after updating CmdStan +using \code{cmdstanr::install_cmdstan()}. \item Running Stan via external processes results in fewer unexpected crashes, especially in RStudio. \item Less memory overhead. diff --git a/man/model-method-check_syntax.Rd b/man/model-method-check_syntax.Rd index 68366fb5..5725defd 100644 --- a/man/model-method-check_syntax.Rd +++ b/man/model-method-check_syntax.Rd @@ -78,6 +78,7 @@ The Stan and CmdStan documentation: } Other CmdStanModel methods: +\code{\link{model-method-cmdstan_defaults}}, \code{\link{model-method-compile}}, \code{\link{model-method-diagnose}}, \code{\link{model-method-expose_functions}}, diff --git a/man/model-method-cmdstan_defaults.Rd b/man/model-method-cmdstan_defaults.Rd new file mode 100644 index 00000000..fa45b200 --- /dev/null +++ b/man/model-method-cmdstan_defaults.Rd @@ -0,0 +1,65 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model.R +\name{model-method-cmdstan_defaults} +\alias{model-method-cmdstan_defaults} +\alias{cmdstan_defaults} +\title{Get CmdStan default argument values} +\usage{ +cmdstan_defaults( + method = c("sample", "optimize", "variational", "pathfinder", "laplace") +) +} +\arguments{ +\item{method}{(string) The inference method whose defaults to +retrieve. One of \code{"sample"}, \code{"optimize"}, \code{"variational"}, +\code{"pathfinder"}, or \code{"laplace"}.} +} +\value{ +A named list of default argument values for the specified +method, with cmdstanr-style argument names. +} +\description{ +The \verb{$cmdstan_defaults()} method of a \code{\link{CmdStanModel}} +object queries the compiled model binary for the default argument +values used by a given inference method. The returned list uses +cmdstanr-style argument names (e.g., \code{iter_sampling} instead of +CmdStan's \code{num_samples}). + +The model must be compiled before calling this method. +} +\examples{ +\dontrun{ +mod <- cmdstan_model(file.path(cmdstan_path(), + "examples/bernoulli/bernoulli.stan")) +mod$cmdstan_defaults("sample") +mod$cmdstan_defaults("optimize") +} + +} +\seealso{ +The CmdStanR website +(\href{https://mc-stan.org/cmdstanr/}{mc-stan.org/cmdstanr}) for online +documentation and tutorials. + +The Stan and CmdStan documentation: +\itemize{ +\item Stan documentation: \href{https://mc-stan.org/users/documentation/}{mc-stan.org/users/documentation} +\item CmdStan User’s Guide: \href{https://mc-stan.org/docs/cmdstan-guide/}{mc-stan.org/docs/cmdstan-guide} +} + +Other CmdStanModel methods: +\code{\link{model-method-check_syntax}}, +\code{\link{model-method-compile}}, +\code{\link{model-method-diagnose}}, +\code{\link{model-method-expose_functions}}, +\code{\link{model-method-format}}, +\code{\link{model-method-generate-quantities}}, +\code{\link{model-method-laplace}}, +\code{\link{model-method-optimize}}, +\code{\link{model-method-pathfinder}}, +\code{\link{model-method-sample}}, +\code{\link{model-method-sample_mpi}}, +\code{\link{model-method-variables}}, +\code{\link{model-method-variational}} +} +\concept{CmdStanModel methods} diff --git a/man/model-method-compile.Rd b/man/model-method-compile.Rd index 14871ef5..1dd12463 100644 --- a/man/model-method-compile.Rd +++ b/man/model-method-compile.Rd @@ -146,6 +146,7 @@ The Stan and CmdStan documentation: Other CmdStanModel methods: \code{\link{model-method-check_syntax}}, +\code{\link{model-method-cmdstan_defaults}}, \code{\link{model-method-diagnose}}, \code{\link{model-method-expose_functions}}, \code{\link{model-method-format}}, diff --git a/man/model-method-diagnose.Rd b/man/model-method-diagnose.Rd index c7117ef1..a9bb6a6a 100644 --- a/man/model-method-diagnose.Rd +++ b/man/model-method-diagnose.Rd @@ -146,6 +146,7 @@ The Stan and CmdStan documentation: Other CmdStanModel methods: \code{\link{model-method-check_syntax}}, +\code{\link{model-method-cmdstan_defaults}}, \code{\link{model-method-compile}}, \code{\link{model-method-expose_functions}}, \code{\link{model-method-format}}, diff --git a/man/model-method-expose_functions.Rd b/man/model-method-expose_functions.Rd index b7d42231..92c105bb 100644 --- a/man/model-method-expose_functions.Rd +++ b/man/model-method-expose_functions.Rd @@ -70,6 +70,7 @@ The Stan and CmdStan documentation: Other CmdStanModel methods: \code{\link{model-method-check_syntax}}, +\code{\link{model-method-cmdstan_defaults}}, \code{\link{model-method-compile}}, \code{\link{model-method-diagnose}}, \code{\link{model-method-format}}, diff --git a/man/model-method-format.Rd b/man/model-method-format.Rd index 4a8af83b..9d28973e 100644 --- a/man/model-method-format.Rd +++ b/man/model-method-format.Rd @@ -86,6 +86,7 @@ The Stan and CmdStan documentation: Other CmdStanModel methods: \code{\link{model-method-check_syntax}}, +\code{\link{model-method-cmdstan_defaults}}, \code{\link{model-method-compile}}, \code{\link{model-method-diagnose}}, \code{\link{model-method-expose_functions}}, diff --git a/man/model-method-generate-quantities.Rd b/man/model-method-generate-quantities.Rd index 4f7c87bf..e2131f63 100644 --- a/man/model-method-generate-quantities.Rd +++ b/man/model-method-generate-quantities.Rd @@ -184,6 +184,7 @@ The Stan and CmdStan documentation: Other CmdStanModel methods: \code{\link{model-method-check_syntax}}, +\code{\link{model-method-cmdstan_defaults}}, \code{\link{model-method-compile}}, \code{\link{model-method-diagnose}}, \code{\link{model-method-expose_functions}}, diff --git a/man/model-method-laplace.Rd b/man/model-method-laplace.Rd index e94c0f64..6977e301 100644 --- a/man/model-method-laplace.Rd +++ b/man/model-method-laplace.Rd @@ -238,6 +238,7 @@ https://mc-stan.org/docs/cmdstan-guide/ \seealso{ Other CmdStanModel methods: \code{\link{model-method-check_syntax}}, +\code{\link{model-method-cmdstan_defaults}}, \code{\link{model-method-compile}}, \code{\link{model-method-diagnose}}, \code{\link{model-method-expose_functions}}, diff --git a/man/model-method-optimize.Rd b/man/model-method-optimize.Rd index f9fbb5c5..07f3c841 100644 --- a/man/model-method-optimize.Rd +++ b/man/model-method-optimize.Rd @@ -363,6 +363,7 @@ https://mc-stan.org/docs/cmdstan-guide/ \seealso{ Other CmdStanModel methods: \code{\link{model-method-check_syntax}}, +\code{\link{model-method-cmdstan_defaults}}, \code{\link{model-method-compile}}, \code{\link{model-method-diagnose}}, \code{\link{model-method-expose_functions}}, diff --git a/man/model-method-pathfinder.Rd b/man/model-method-pathfinder.Rd index a7b3c15e..58036c17 100644 --- a/man/model-method-pathfinder.Rd +++ b/man/model-method-pathfinder.Rd @@ -382,6 +382,7 @@ https://mc-stan.org/docs/cmdstan-guide/ \seealso{ Other CmdStanModel methods: \code{\link{model-method-check_syntax}}, +\code{\link{model-method-cmdstan_defaults}}, \code{\link{model-method-compile}}, \code{\link{model-method-diagnose}}, \code{\link{model-method-expose_functions}}, diff --git a/man/model-method-sample.Rd b/man/model-method-sample.Rd index dd8a1b43..f5285024 100644 --- a/man/model-method-sample.Rd +++ b/man/model-method-sample.Rd @@ -448,6 +448,7 @@ https://mc-stan.org/docs/cmdstan-guide/ \seealso{ Other CmdStanModel methods: \code{\link{model-method-check_syntax}}, +\code{\link{model-method-cmdstan_defaults}}, \code{\link{model-method-compile}}, \code{\link{model-method-diagnose}}, \code{\link{model-method-expose_functions}}, diff --git a/man/model-method-sample_mpi.Rd b/man/model-method-sample_mpi.Rd index b8620ecb..b0cc6bc9 100644 --- a/man/model-method-sample_mpi.Rd +++ b/man/model-method-sample_mpi.Rd @@ -343,6 +343,7 @@ details on MPI support in Stan. Other CmdStanModel methods: \code{\link{model-method-check_syntax}}, +\code{\link{model-method-cmdstan_defaults}}, \code{\link{model-method-compile}}, \code{\link{model-method-diagnose}}, \code{\link{model-method-expose_functions}}, diff --git a/man/model-method-variables.Rd b/man/model-method-variables.Rd index 87e9d73e..1415fdc8 100644 --- a/man/model-method-variables.Rd +++ b/man/model-method-variables.Rd @@ -38,6 +38,7 @@ mod$variables() \seealso{ Other CmdStanModel methods: \code{\link{model-method-check_syntax}}, +\code{\link{model-method-cmdstan_defaults}}, \code{\link{model-method-compile}}, \code{\link{model-method-diagnose}}, \code{\link{model-method-expose_functions}}, diff --git a/man/model-method-variational.Rd b/man/model-method-variational.Rd index 41fb890c..684b152b 100644 --- a/man/model-method-variational.Rd +++ b/man/model-method-variational.Rd @@ -353,6 +353,7 @@ https://mc-stan.org/docs/cmdstan-guide/ \seealso{ Other CmdStanModel methods: \code{\link{model-method-check_syntax}}, +\code{\link{model-method-cmdstan_defaults}}, \code{\link{model-method-compile}}, \code{\link{model-method-diagnose}}, \code{\link{model-method-expose_functions}}, diff --git a/tests/testthat/test-model-cmdstan-defaults.R b/tests/testthat/test-model-cmdstan-defaults.R new file mode 100644 index 00000000..608bd3e3 --- /dev/null +++ b/tests/testthat/test-model-cmdstan-defaults.R @@ -0,0 +1,140 @@ +set_cmdstan_path() +mod <- testing_model("bernoulli") + +expected_cmdstan_defaults <- list( + sample = list( + iter_sampling = 1000L, + iter_warmup = 1000L, + save_warmup = FALSE, + thin = 1L, + adapt_engaged = TRUE, + adapt_delta = 0.8, + init_buffer = 75L, + term_buffer = 50L, + window = 25L, + save_metric = FALSE, + max_treedepth = 10L, + metric = "diag_e", + metric_file = "", + step_size = 1L + ), + optimize = list( + algorithm = "lbfgs", + init_alpha = 0.001, + tol_obj = 1e-12, + tol_rel_obj = 10000L, + tol_grad = 1e-08, + tol_rel_grad = 1e+07, + tol_param = 1e-08, + history_size = 5L, + jacobian = FALSE, + iter = 2000L + ), + variational = list( + algorithm = "meanfield", + iter = 10000L, + grad_samples = 1L, + elbo_samples = 100L, + eta = 1L, + adapt_engaged = TRUE, + adapt_iter = 50L, + tol_rel_obj = 0.01, + eval_elbo = 100L, + draws = 1000L + ), + pathfinder = list( + init_alpha = 0.001, + tol_obj = 1e-12, + tol_rel_obj = 10000L, + tol_grad = 1e-08, + tol_rel_grad = 1e+07, + tol_param = 1e-08, + history_size = 5L, + draws = 1000L, + num_paths = 4L, + save_single_paths = FALSE, + psis_resample = TRUE, + calculate_lp = TRUE, + max_lbfgs_iters = 1000L, + single_path_draws = 1000L, + num_elbo_draws = 25L + ), + laplace = list( + jacobian = TRUE, + draws = 1000L + ) +) + +expect_cmdstan_defaults <- function(method, expected) { + args <- mod$cmdstan_defaults(method) + expect_type(args, "list") + expect_named(args) + expect_setequal(names(args), names(expected)) + + for (name in names(expected)) { + expect_identical(args[[name]], expected[[name]], info = paste0(method, "$", name)) + } +} + +test_that("cmdstan_defaults() errors for uncompiled model", { + mod_uncompiled <- cmdstan_model( + stan_file = testing_stan_file("bernoulli"), + compile = FALSE + ) + expect_error( + mod_uncompiled$cmdstan_defaults("sample"), + "'$cmdstan_defaults()' requires a compiled model", + fixed = TRUE + ) +}) + +test_that("cmdstan_defaults() errors for invalid method", { + expect_error( + mod$cmdstan_defaults("bogus"), + "'arg' should be one of", + fixed = TRUE + ) +}) + +test_that("cmdstan_defaults() returns expected names and values", { + for (method in names(expected_cmdstan_defaults)) { + expect_cmdstan_defaults(method, expected_cmdstan_defaults[[method]]) + } +}) + +# internal helpers -------------------------------------------------------- + +test_that("parse_default_value() parses booleans", { + expect_identical(parse_default_value("Defaults to true"), TRUE) + expect_identical(parse_default_value("Defaults to false"), FALSE) +}) + +test_that("parse_default_value() parses integers", { + expect_identical(parse_default_value("Defaults to 1000"), 1000L) + expect_identical(parse_default_value("Defaults to -1"), -1L) + expect_identical(parse_default_value("Defaults to 0"), 0L) +}) + +test_that("parse_default_value() parses doubles", { + expect_identical(parse_default_value("Defaults to 0.8"), 0.8) + expect_identical(parse_default_value("Defaults to 1e-6"), 1e-6) + expect_identical(parse_default_value("Defaults to -0.5"), -0.5) +}) + +test_that("parse_default_value() returns strings for non-numeric values", { + expect_identical(parse_default_value("Defaults to lbfgs"), "lbfgs") + expect_identical(parse_default_value("Defaults to diagonal_e"), "diagonal_e") +}) + +test_that("map_cmdstan_to_cmdstanr() returns named character for valid methods", { + for (method in c("sample", "optimize", "variational", "pathfinder", "laplace")) { + mapping <- map_cmdstan_to_cmdstanr(method) + expect_type(mapping, "character") + expect_true(length(mapping) > 0, info = method) + expect_named(mapping) + } +}) + +test_that("map_cmdstan_to_cmdstanr() returns empty for unknown method", { + expect_length(map_cmdstan_to_cmdstanr("unknown"), 0) +})