diff --git a/R/fit.R b/R/fit.R index 554a79b3..c35b6b81 100644 --- a/R/fit.R +++ b/R/fit.R @@ -322,6 +322,7 @@ CmdStanFit$set("public", name = "init", value = init) #' @param seed (integer) The random seed to use when initializing the model. #' @param verbose (logical) Whether to show verbose logging during compilation. #' @param hessian (logical) Whether to expose the (experimental) hessian method. +#' @param force_recompile (logical) Whether to recompile cached model methods. #' #' @examples #' \dontrun{ @@ -332,25 +333,26 @@ CmdStanFit$set("public", name = "init", value = init) #' [unconstrain_variables()], [unconstrain_draws()], [variable_skeleton()], #' [hessian()] #' -init_model_methods <- function(seed = 0, verbose = FALSE, hessian = FALSE) { +init_model_methods <- function(seed = 0, verbose = FALSE, hessian = FALSE, force_recompile = FALSE) { if (os_is_wsl()) { stop("Additional model methods are not currently available with ", "WSL CmdStan and will not be compiled", call. = FALSE) } require_suggested_package("Rcpp") - if (length(private$model_methods_env_$hpp_code_) == 0) { + if (length(private$model_methods_env_$hpp_code_) == 0 && ( + is.null(private$model_methods_env_$obj_file_) || + !file.exists(private$model_methods_env_$obj_file_))) { stop("Model methods cannot be used with a pre-compiled Stan executable, ", "the model must be compiled again", call. = FALSE) } if (hessian) { - message("The hessian method relies on higher-order autodiff ", - "which is still experimental. Please report any compilation ", - "errors that you encounter") + warning("The hessian argument is deprecated and will be removed in a future release.\n", + "The hessian method is now exposed by default.") } - message("Compiling additional model methods...") if (is.null(private$model_methods_env_$model_ptr)) { - expose_model_methods(private$model_methods_env_, verbose, hessian) + expose_model_methods(private$model_methods_env_, verbose = verbose, + force_recompile = force_recompile) } initialize_model_pointer(private$model_methods_env_, self$data_file(), seed) invisible(NULL) diff --git a/R/install.R b/R/install.R index 7d92f5e9..692eddbd 100644 --- a/R/install.R +++ b/R/install.R @@ -475,6 +475,8 @@ build_cmdstan <- function(dir, clean_cmdstan <- function(dir = cmdstan_path(), cores = getOption("mc.cores", 2), quiet = FALSE) { + unlink(file.path(dir, "model_methods.o")) + unlink(file.path(dir, "model_methods.cpp")) withr::with_path( c( toolchain_PATH_env_var(), diff --git a/R/model.R b/R/model.R index e11d5c41..d9acc20c 100644 --- a/R/model.R +++ b/R/model.R @@ -405,8 +405,8 @@ CmdStanModel <- R6::R6Class( #' @param compile_model_methods (logical) Compile additional model methods #' (`log_prob()`, `grad_log_prob()`, `constrain_variables()`, #' `unconstrain_variables()`). -#' @param compile_hessian_method (logical) Should the (experimental) `hessian()` method be -#' be compiled with the model methods? +#' @param compile_hessian_method (logical) Deprecated and will be removed in a future release. +#' The hessian method is now compiled by default. #' @param compile_standalone (logical) Should functions in the Stan model be #' compiled for use in R? If `TRUE` the functions will be available via the #' `functions` field in the compiled model object. This can also be done after @@ -504,6 +504,10 @@ compile <- function(quiet = TRUE, warning("'threads' is deprecated. Please use 'cpp_options = list(stan_threads = TRUE)' instead.") cpp_options[["stan_threads"]] <- TRUE } + if (isTRUE(compile_hessian_method)) { + warning("'compile_hessian_method' is deprecated. The hessian method is now compiled by default.") + compile_hessian_method <- FALSE + } if (length(self$exe_file()) == 0) { if (is.null(dir)) { @@ -655,9 +659,10 @@ compile <- function(quiet = TRUE, run_log <- wsl_compatible_run( command = make_cmd(), args = c(wsl_safe_path(tmp_exe), - cpp_options_to_compile_flags(cpp_options), + cpp_options_to_compile_flags(c(cpp_options, list("KEEP_OBJECT"="true"))), stancflags_val), wd = cmdstan_path(), + env = c("current", "CXXFLAGS" = "-fPIC"), echo = !quiet || is_verbose_mode(), echo_cmd = is_verbose_mode(), spinner = quiet && rlang::is_interactive() && !identical(Sys.getenv("IN_PKGDOWN"), "true"), @@ -708,6 +713,7 @@ compile <- function(quiet = TRUE, file.remove(exe) } file.copy(tmp_exe, exe, overwrite = TRUE) + private$model_methods_env_$obj_file_ <- paste0(temp_file_no_ext, ".o") if (os_is_wsl()) { res <- processx::run( command = "wsl", @@ -726,11 +732,9 @@ compile <- function(quiet = TRUE, private$precompile_stanc_options_ <- NULL private$precompile_include_paths_ <- NULL - if(!dry_run) { + if (!dry_run) { if (compile_model_methods) { - expose_model_methods(env = private$model_methods_env_, - verbose = !quiet, - hessian = compile_hessian_method) + expose_model_methods(private$model_methods_env_, verbose = !quiet) } } invisible(self) diff --git a/R/utils.R b/R/utils.R index 9764ec36..fcf82d52 100644 --- a/R/utils.R +++ b/R/utils.R @@ -728,45 +728,116 @@ get_cmdstan_flags <- function(flag_name) { paste(flags, collapse = " ") } -rcpp_source_stan <- function(code, env, verbose = FALSE) { +with_cmdstan_flags <- function(expr, model_methods = FALSE) { cxxflags <- get_cmdstan_flags("CXXFLAGS") cmdstanr_includes <- system.file("include", package = "cmdstanr", mustWork = TRUE) - cmdstanr_includes <- paste0(" -I\"", cmdstanr_includes,"\"") + cmdstanr_includes <- paste0("-I", shQuote(cmdstanr_includes)) + + r_includes <- paste( + paste0("-I", shQuote(system.file("include", package = "Rcpp", mustWork = TRUE))), + paste0("-I", shQuote(R.home(component = "include"))) + ) + libs <- c("LDLIBS", "LIBSUNDIALS", "TBB_TARGETS", "LDFLAGS_TBB") libs <- paste(sapply(libs, get_cmdstan_flags), collapse = " ") - if (.Platform$OS.type == "windows") { + if (os_is_windows()) { libs <- paste(libs, "-fopenmp") } lib_paths <- c("/stan/lib/stan_math/lib/tbb/", "/stan/lib/stan_math/lib/sundials_6.1.1/lib/") - withr::with_path(paste0(cmdstan_path(), lib_paths), - withr::with_makevars( - c( - USE_CXX14 = 1, - PKG_CPPFLAGS = ifelse(cmdstan_version() <= "2.30.1", "-DCMDSTAN_JSON", ""), - PKG_CXXFLAGS = paste0(cxxflags, cmdstanr_includes, collapse = " "), - PKG_LIBS = libs - ), - Rcpp::sourceCpp(code = code, env = env, verbose = verbose) + new_makevars <- c( + PKG_CPPFLAGS = ifelse(cmdstan_version() <= "2.30.1", "-DCMDSTAN_JSON", ""), + PKG_CXXFLAGS = paste(cxxflags, cmdstanr_includes, r_includes, collapse = " "), + PKG_LIBS = libs + ) + if (os_is_windows() && model_methods) { + new_makevars <- c( + new_makevars, + SHLIB_LD = paste0(rtools4x_toolchain_path(),"/gcc"), + LOCAL_CPPFLAGS = paste0("-I'",rtools4x_toolchain_path(),"/../include'"), + LOCAL_LIBS = paste0("-L'",rtools4x_toolchain_path(),"/../lib' -lstdc++"), + BINPREF = paste0(rtools4x_toolchain_path(), "/") ) + } + withr::with_path( + c( + paste0(cmdstan_path(), lib_paths), + toolchain_PATH_env_var() + ), + withr::with_makevars(new_makevars, expr) ) +} + +rcpp_source_stan <- function(code, env, verbose = FALSE) { + with_cmdstan_flags(Rcpp::sourceCpp(code = code, env = env, verbose = verbose)) invisible(NULL) } -expose_model_methods <- function(env, verbose = FALSE, hessian = FALSE) { - code <- c(env$hpp_code_, - readLines(system.file("include", "model_methods.cpp", - package = "cmdstanr", mustWork = TRUE))) +initialize_method_functions <- function(env, so_name) { + env$model_ptr <- + function(...) { .Call("model_ptr_", ..., PACKAGE = so_name) } + env$log_prob <- + function(...) { .Call("log_prob_", ..., PACKAGE = so_name) } + env$grad_log_prob <- + function(...) { .Call("grad_log_prob_", ..., PACKAGE = so_name) } + env$hessian <- + function(...) { .Call("hessian_", ..., PACKAGE = so_name) } + env$get_num_upars <- + function(...) { .Call("get_num_upars_", ..., PACKAGE = so_name) } + env$get_param_metadata <- + function(...) { .Call("get_param_metadata_", ..., PACKAGE = so_name) } + env$unconstrain_variables <- + function(...) { .Call("unconstrain_variables_", ..., PACKAGE = so_name) } + env$constrain_variables <- + function(...) { .Call("constrain_variables_", ..., PACKAGE = so_name) } + env$unconstrained_param_names <- + function(...) { .Call("unconstrained_param_names_", ..., PACKAGE = so_name) } + env$constrained_param_names <- + function(...) { .Call("constrained_param_names_", ..., PACKAGE = so_name) } +} - if (hessian) { - code <- c("#include ", - code, - readLines(system.file("include", "hessian.cpp", - package = "cmdstanr", mustWork = TRUE))) +expose_model_methods <- function(env, force_recompile = FALSE, verbose = FALSE) { + precomp_methods_file <- file.path(cmdstan_path(), "model_methods.o") + if (file.exists(precomp_methods_file) && force_recompile) { + unlink(precomp_methods_file) } + model_methods_cpp <- system.file("include", "model_methods.cpp", + package = "cmdstanr", mustWork = TRUE) + source_file <- paste0(strip_ext(precomp_methods_file), ".cpp") + file.copy(model_methods_cpp, source_file, overwrite = FALSE) + + model_obj_file <- env$obj_file_ + if (!file.exists(model_obj_file)) { + if (rlang::is_interactive()) { + message("Model object file not found, recompiling model...") + } + temp_hpp_file <- tempfile() + writeLines(env$hpp_code_, con = paste0(temp_hpp_file, ".cpp")) + model_obj_file <- paste0(temp_hpp_file, ".o") + } + + if (!file.exists(precomp_methods_file) && rlang::is_interactive()) { + message("Compiling and caching additional model methods...") + } + if (rlang::is_interactive()) { + message("Linking precompiled model methods to model object file...") + } + + methods_dll <- tempfile(fileext = .Platform$dynlib.ext) + with_cmdstan_flags( + processx::run( + command = file.path(R.home(component = "bin"), "R"), + args = c("CMD", "SHLIB", repair_path(model_obj_file), repair_path(precomp_methods_file), + "-o", repair_path(methods_dll)), + echo = verbose || is_verbose_mode(), + echo_cmd = is_verbose_mode(), + error_on_status = FALSE + ), + model_methods = TRUE + ) - code <- paste(code, collapse = "\n") - rcpp_source_stan(code, env, verbose) + env$methods_dll_info <- with_cmdstan_flags(dyn.load(methods_dll, local = TRUE, now = TRUE)) + initialize_method_functions(env, strip_ext(basename(methods_dll))) invisible(NULL) } diff --git a/inst/include/hessian.cpp b/inst/include/hessian.cpp deleted file mode 100644 index 3883c130..00000000 --- a/inst/include/hessian.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include -#include - -template -struct hessian_wrapper { - const M& model; - bool jac_adjust; - std::ostream* o; - - hessian_wrapper(const M& m, bool adj, std::ostream* out) : model(m), jac_adjust(adj), o(out) {} - - template - T operator()(const Eigen::Matrix& x) const { - if (jac_adjust) { - // log_prob() requires non-const but doesn't modify its argument - return model.template log_prob( - const_cast&>(x), o); - } else { - // log_prob() requires non-const but doesn't modify its argument - return model.template log_prob( - const_cast&>(x), o); - } - } -}; - -// [[Rcpp::export]] -Rcpp::List hessian(SEXP ext_model_ptr, Eigen::VectorXd& upars, bool jac_adjust) { - Rcpp::XPtr ptr(ext_model_ptr); - - double log_prob; - Eigen::VectorXd grad; - Eigen::MatrixXd hessian; - - stan::math::hessian(hessian_wrapper(*ptr.get(), jac_adjust, 0), - upars, log_prob, grad, hessian); - - return Rcpp::List::create( - Rcpp::Named("log_prob") = log_prob, - Rcpp::Named("grad_log_prob") = grad, - Rcpp::Named("hessian") = hessian); -} diff --git a/inst/include/model_methods.cpp b/inst/include/model_methods.cpp index 978eb3cf..0f4c9719 100644 --- a/inst/include/model_methods.cpp +++ b/inst/include/model_methods.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -9,6 +8,12 @@ #include #include #endif +#include +#include + +stan::model::model_base& +new_model(stan::io::var_context& data_context, unsigned int seed, + std::ostream* msg_stream); std::shared_ptr var_context(std::string file_path) { if (file_path == "") { @@ -26,58 +31,90 @@ using json_data_t = stan::json::json_data; return std::make_shared(data_context); } -// [[Rcpp::export]] -Rcpp::List model_ptr(std::string data_path, boost::uint32_t seed) { - Rcpp::XPtr ptr( - new stan_model(*var_context(data_path), seed, &Rcpp::Rcout) +RcppExport SEXP model_ptr_(SEXP data_path_, SEXP seed_) { + BEGIN_RCPP + std::string data_path = Rcpp::as(data_path_); + boost::uint32_t seed = Rcpp::as(seed_); + Rcpp::XPtr ptr( + &new_model(*var_context(data_path), seed, &Rcpp::Rcout) ); Rcpp::XPtr base_rng(new boost::ecuyer1988(seed)); return Rcpp::List::create( Rcpp::Named("model_ptr") = ptr, Rcpp::Named("base_rng") = base_rng ); + END_RCPP } -// [[Rcpp::export]] -double log_prob(SEXP ext_model_ptr, std::vector upars, - bool jac_adjust) { +RcppExport SEXP log_prob_(SEXP ext_model_ptr, SEXP upars_, SEXP jacobian_) { + BEGIN_RCPP Rcpp::XPtr ptr(ext_model_ptr); - std::vector params_i; - if (jac_adjust) { - return stan::model::log_prob_propto(*ptr.get(), upars, params_i, &Rcpp::Rcout); + double rtn; + Eigen::VectorXd upars = Rcpp::as(upars_); + if (Rcpp::as(jacobian_)) { + rtn = stan::model::log_prob_propto(*ptr.get(), upars, &Rcpp::Rcout); } else { - return stan::model::log_prob_propto(*ptr.get(), upars, params_i, &Rcpp::Rcout); + rtn = stan::model::log_prob_propto(*ptr.get(), upars, &Rcpp::Rcout); } + return Rcpp::wrap(rtn); + END_RCPP } -// [[Rcpp::export]] -Rcpp::NumericVector grad_log_prob(SEXP ext_model_ptr, std::vector upars, - bool jac_adjust) { +RcppExport SEXP grad_log_prob_(SEXP ext_model_ptr, SEXP upars_, SEXP jacobian_) { + BEGIN_RCPP Rcpp::XPtr ptr(ext_model_ptr); - std::vector gradients; - std::vector params_i; + Eigen::VectorXd gradients; + Eigen::VectorXd upars = Rcpp::as(upars_); double lp; - if (jac_adjust) { + if (Rcpp::as(jacobian_)) { lp = stan::model::log_prob_grad( - *ptr.get(), upars, params_i, gradients); + *ptr.get(), upars, gradients); } else { lp = stan::model::log_prob_grad( - *ptr.get(), upars, params_i, gradients); + *ptr.get(), upars, gradients); } - Rcpp::NumericVector grad_rtn = Rcpp::wrap(gradients); + Rcpp::NumericVector grad_rtn(Rcpp::wrap(gradients)); grad_rtn.attr("log_prob") = lp; return grad_rtn; + END_RCPP +} + +RcppExport SEXP hessian_(SEXP ext_model_ptr, SEXP upars_, SEXP jacobian_) { + BEGIN_RCPP + Rcpp::XPtr ptr(ext_model_ptr); + Eigen::VectorXd upars = Rcpp::as(upars_); + + auto hessian_functor = [&](auto&& x) { + if (Rcpp::as(jacobian_)) { + return ptr->log_prob(x, 0); + } else { + return ptr->log_prob(x, 0); + } + }; + + double log_prob; + Eigen::VectorXd grad; + Eigen::MatrixXd hessian; + + stan::math::internal::finite_diff_hessian_auto(hessian_functor, upars, log_prob, grad, hessian); + + return Rcpp::List::create( + Rcpp::Named("log_prob") = log_prob, + Rcpp::Named("grad_log_prob") = grad, + Rcpp::Named("hessian") = hessian); + END_RCPP } -// [[Rcpp::export]] -size_t get_num_upars(SEXP ext_model_ptr) { +RcppExport SEXP get_num_upars_(SEXP ext_model_ptr) { + BEGIN_RCPP Rcpp::XPtr ptr(ext_model_ptr); - return ptr->num_params_r(); + return Rcpp::wrap(ptr->num_params_r()); + END_RCPP } -// [[Rcpp::export]] -Rcpp::List get_param_metadata(SEXP ext_model_ptr) { +RcppExport SEXP get_param_metadata_(SEXP ext_model_ptr) { + BEGIN_RCPP Rcpp::XPtr ptr(ext_model_ptr); std::vector param_names; std::vector > param_dims; @@ -92,45 +129,60 @@ Rcpp::List get_param_metadata(SEXP ext_model_ptr) { } return param_metadata; + END_RCPP } -// [[Rcpp::export]] -std::vector unconstrain_variables(SEXP ext_model_ptr, std::string init_path) { +RcppExport SEXP unconstrain_variables_(SEXP ext_model_ptr, SEXP init_path_) { + BEGIN_RCPP + std::string init_path = Rcpp::as(init_path_); Rcpp::XPtr ptr(ext_model_ptr); std::vector params_i; std::vector vars; ptr->transform_inits(*var_context(init_path), params_i, vars, &Rcpp::Rcout); - return vars; + return Rcpp::wrap(vars); + END_RCPP } -// [[Rcpp::export]] -std::vector constrain_variables(SEXP ext_model_ptr, SEXP base_rng, - std::vector upars, - bool return_trans_pars, - bool return_gen_quants) { +RcppExport SEXP constrain_variables_(SEXP ext_model_ptr, SEXP base_rng, + SEXP upars_, + SEXP return_trans_pars_, + SEXP return_gen_quants_) { + BEGIN_RCPP Rcpp::XPtr ptr(ext_model_ptr); Rcpp::XPtr rng(base_rng); + std::vector upars = Rcpp::as>(upars_); + bool return_trans_pars = Rcpp::as(return_trans_pars_); + bool return_gen_quants = Rcpp::as(return_gen_quants_); std::vector params_i; std::vector vars; ptr->write_array(*rng.get(), upars, params_i, vars, return_trans_pars, return_gen_quants); - return vars; + return Rcpp::wrap(vars); + END_RCPP } -// [[Rcpp::export]] -std::vector unconstrained_param_names(SEXP ext_model_ptr, bool return_trans_pars, bool return_gen_quants) { +RcppExport SEXP unconstrained_param_names_(SEXP ext_model_ptr, + SEXP return_trans_pars_, + SEXP return_gen_quants_) { + BEGIN_RCPP Rcpp::XPtr ptr(ext_model_ptr); + bool return_trans_pars = Rcpp::as(return_trans_pars_); + bool return_gen_quants = Rcpp::as(return_gen_quants_); std::vector rtn_names; ptr->unconstrained_param_names(rtn_names, return_trans_pars, return_gen_quants); - return rtn_names; + return Rcpp::wrap(rtn_names); + END_RCPP } -// [[Rcpp::export]] -std::vector constrained_param_names(SEXP ext_model_ptr, - bool return_trans_pars, - bool return_gen_quants) { +RcppExport SEXP constrained_param_names_(SEXP ext_model_ptr, + SEXP return_trans_pars_, + SEXP return_gen_quants_) { + BEGIN_RCPP Rcpp::XPtr ptr(ext_model_ptr); + bool return_trans_pars = Rcpp::as(return_trans_pars_); + bool return_gen_quants = Rcpp::as(return_gen_quants_); std::vector rtn_names; ptr->constrained_param_names(rtn_names, return_trans_pars, return_gen_quants); - return rtn_names; + return Rcpp::wrap(rtn_names); + END_RCPP } diff --git a/man/fit-method-init_model_methods.Rd b/man/fit-method-init_model_methods.Rd index 1a96cd59..8de98ee8 100644 --- a/man/fit-method-init_model_methods.Rd +++ b/man/fit-method-init_model_methods.Rd @@ -6,7 +6,12 @@ \title{Compile additional methods for accessing the model log-probability function and parameter constraining and unconstraining.} \usage{ -init_model_methods(seed = 0, verbose = FALSE, hessian = FALSE) +init_model_methods( + seed = 0, + verbose = FALSE, + hessian = FALSE, + force_recompile = FALSE +) } \arguments{ \item{seed}{(integer) The random seed to use when initializing the model.} @@ -14,6 +19,8 @@ init_model_methods(seed = 0, verbose = FALSE, hessian = FALSE) \item{verbose}{(logical) Whether to show verbose logging during compilation.} \item{hessian}{(logical) Whether to expose the (experimental) hessian method.} + +\item{force_recompile}{(logical) Whether to recompile cached model methods.} } \description{ The \verb{$init_model_methods()} method compiles and initializes the diff --git a/man/model-method-compile.Rd b/man/model-method-compile.Rd index d295eedc..72443a3f 100644 --- a/man/model-method-compile.Rd +++ b/man/model-method-compile.Rd @@ -64,8 +64,8 @@ via a global \code{cmdstanr_force_recompile} option.} (\code{log_prob()}, \code{grad_log_prob()}, \code{constrain_variables()}, \code{unconstrain_variables()}).} -\item{compile_hessian_method}{(logical) Should the (experimental) \code{hessian()} method be -be compiled with the model methods?} +\item{compile_hessian_method}{(logical) Deprecated and will be removed in a future release. +The hessian method is now compiled by default.} \item{compile_standalone}{(logical) Should functions in the Stan model be compiled for use in R? If \code{TRUE} the functions will be available via the diff --git a/tests/testthat/test-model-methods.R b/tests/testthat/test-model-methods.R index 74419717..7a088fbc 100644 --- a/tests/testthat/test-model-methods.R +++ b/tests/testthat/test-model-methods.R @@ -34,13 +34,13 @@ test_that("Methods error if not compiled", { ) }) -test_that("User warned about higher-order autodiff with hessian", { +test_that("User warned hessian argument deprecated", { skip_if(os_is_wsl()) - expect_message( + expect_warning( fit$init_model_methods(hessian = TRUE, verbose = TRUE), - "The hessian method relies on higher-order autodiff which is still experimental. Please report any compilation errors that you encounter", + "The hessian argument is deprecated and will be removed in a future release.", fixed = TRUE - ) + ) }) test_that("Methods return correct values", { @@ -151,12 +151,23 @@ test_that("Methods error with already-compiled model", { ) }) +test_that("User warned compilation arguments deprecated", { + skip_if(os_is_wsl()) + expect_warning( + cmdstan_model(testing_stan_file("bernoulli"), + force_recompile = TRUE, + compile_model_methods = TRUE, + compile_hessian_method = TRUE), + "'compile_hessian_method' is deprecated. The hessian method is now compiled by default.", + fixed = TRUE + ) +}) + test_that("Methods can be compiled with model", { skip_if(os_is_wsl()) mod <- cmdstan_model(testing_stan_file("bernoulli"), force_recompile = TRUE, - compile_model_methods = TRUE, - compile_hessian_method = TRUE) + compile_model_methods = TRUE) fit <- mod$sample(data = data_list, chains = 1) lp <- fit$log_prob(unconstrained_variables=c(0.6)) @@ -196,7 +207,8 @@ test_that("unconstrain_variables correctly handles zero-length containers", { } " mod <- cmdstan_model(write_stan_file(model_code), - compile_model_methods = TRUE) + compile_model_methods = TRUE, + force_recompile = TRUE) fit <- mod$sample(data = list(N = 0), chains = 1) unconstrained <- fit$unconstrain_variables(variables = list(x = 5)) expect_equal(unconstrained, 5) @@ -296,7 +308,7 @@ test_that("Variable skeleton returns correct dimensions for matrices", { x_real ~ std_normal(); }") mod <- cmdstan_model(stan_file, compile_model_methods = TRUE, - force_recompile = TRUE) + force_recompile = TRUE) N <- 4 K <- 3 fit <- mod$sample(data = list(N = N, K = K), chains = 1, @@ -312,3 +324,15 @@ test_that("Variable skeleton returns correct dimensions for matrices", { expect_equal(fit$variable_skeleton(), target_skeleton) }) + +test_that("Model object file can be recompiled if not found", { + skip_if(os_is_wsl()) + mod <- cmdstan_model(testing_stan_file("bernoulli_log_lik"), + force_recompile = TRUE) + model_obj_file <- mod$.__enclos_env__$private$model_methods_env_$obj_file_ + unlink(model_obj_file) + expect_false(file.exists(model_obj_file)) + data_list <- testing_data("bernoulli") + fit <- mod$sample(data = data_list, chains = 1, refresh = 0) + expect_no_error(fit$init_model_methods()) +})