Skip to content

Commit d8eab9e

Browse files
committed
Merge branch 'master' into laplace-sample
2 parents 4d172ae + 8e51a84 commit d8eab9e

25 files changed

Lines changed: 432 additions & 194 deletions

DESCRIPTION

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Package: cmdstanr
22
Title: R Interface to 'CmdStan'
3-
Version: 0.6.0.9000
4-
Date: 2023-07-25
3+
Version: 0.6.1.9000
4+
Date: 2023-08-25
55
Authors@R:
66
c(person(given = "Jonah", family = "Gabry", role = c("aut", "cre"),
77
email = "jsg2201@columbia.edu"),

NEWS.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
1-
# cmdstanr 0.6.0.9000
1+
# cmdstanr 0.6.1.9000
2+
3+
# cmdstanr 0.6.1
4+
5+
* Store return codes instead of always querying exit status by @jgabry in #798
6+
* enable jacobian argument for optimization by @jgabry in #799
7+
* Fix init_model_methods for models with no data by @andrjohns in #801
8+
* Document a CmdStan-focused way to pre-compile Stan models in R packages by @wlandau in #809
9+
* Describe how to efficiently save model fit objects by @wlandau in #816
10+
* fix errors in doc for new methods by @jgabry in #823
11+
* Give informative error when exposing stan functions with precompiled model by @andrjohns in #831
12+
* Bugfixes in .stanfunctions, hessian model method, and exposing RNG functions by @andrjohns in #811
13+
* Fix variable_skeleton() with containers by @andrjohns in #832
14+
* Improve handling of user header by @martinmodrak in #818
15+
* change duplicate stdout_file to stderr_file by @jgabry in #834
216

3-
Items for next release
417

518
# cmdstanr 0.6.0
619

R/model.R

Lines changed: 71 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,12 @@ cmdstan_model <- function(stan_file = NULL, exe_file = NULL, compile = TRUE, ...
191191
#' [`$save_hpp_file()`][model-method-compile] | Save the `.hpp` file containing the generated C++ code. |
192192
#' [`$expose_functions()`][model-method-expose_functions] | Expose Stan functions for use in R. |
193193
#'
194+
#' ## Diagnostics
195+
#'
196+
#' |**Method**|**Description**|
197+
#' |:----------|:---------------|
198+
#' [`$diagnose()`][model-method-diagnose] | Run CmdStan's `"diagnose"` method to test gradients, return [`CmdStanDiagnose`] object. |
199+
#'
194200
#' ## Model fitting
195201
#'
196202
#' |**Method**|**Description**|
@@ -231,7 +237,7 @@ CmdStanModel <- R6::R6Class(
231237
self$functions <- new.env()
232238
self$functions$compiled <- FALSE
233239
if (!is.null(stan_file)) {
234-
assert_file_exists(stan_file, access = "r", extension = "stan")
240+
assert_file_exists(stan_file, access = "r", extension = c("stan", "stanfunctions"))
235241
checkmate::assert_flag(compile)
236242
private$stan_file_ <- absolute_path(stan_file)
237243
private$stan_code_ <- readLines(stan_file)
@@ -398,6 +404,8 @@ CmdStanModel <- R6::R6Class(
398404
#' `functions` field in the compiled model object. This can also be done after
399405
#' compilation using the
400406
#' [`$expose_functions()`][model-method-expose_functions] method.
407+
#' @param dry_run (logical) If `TRUE`, the code will do all checks before compilation,
408+
#' but skip the actual C++ compilation. Used to speedup tests.
401409
#'
402410
#' @param threads Deprecated and will be removed in a future release. Please
403411
#' turn on threading via `cpp_options = list(stan_threads = TRUE)` instead.
@@ -451,8 +459,10 @@ compile <- function(quiet = TRUE,
451459
compile_model_methods = FALSE,
452460
compile_hessian_method = FALSE,
453461
compile_standalone = FALSE,
462+
dry_run = FALSE,
454463
#deprecated
455464
threads = FALSE) {
465+
456466
if (length(self$stan_file()) == 0) {
457467
stop("'$compile()' cannot be used because the 'CmdStanModel' was not created with a Stan file.", call. = FALSE)
458468
}
@@ -501,15 +511,63 @@ compile <- function(quiet = TRUE,
501511
exe <- self$exe_file()
502512
}
503513

514+
# Resolve stanc and cpp options
515+
if (pedantic) {
516+
stanc_options[["warn-pedantic"]] <- TRUE
517+
}
518+
519+
if (isTRUE(cpp_options$stan_opencl)) {
520+
stanc_options[["use-opencl"]] <- TRUE
521+
}
522+
523+
# Note that unlike cpp_options["USER_HEADER"], the user_header variable is deliberately
524+
# not transformed with wsl_safe_path() as that breaks the check below on WSLv1
525+
if (!is.null(user_header)) {
526+
if (!is.null(cpp_options[["USER_HEADER"]]) || !is.null(cpp_options[["user_header"]])) {
527+
warning("User header specified both via user_header argument and via cpp_options arguments")
528+
}
529+
530+
cpp_options[["USER_HEADER"]] <- wsl_safe_path(absolute_path(user_header))
531+
stanc_options[["allow-undefined"]] <- TRUE
532+
private$using_user_header_ <- TRUE
533+
}
534+
else if (!is.null(cpp_options[["USER_HEADER"]])) {
535+
if(!is.null(cpp_options[["user_header"]])) {
536+
warning('User header specified both via cpp_options[["USER_HEADER"]] and cpp_options[["user_header"]].', call. = FALSE)
537+
}
538+
539+
user_header <- cpp_options[["USER_HEADER"]]
540+
cpp_options[["USER_HEADER"]] <- wsl_safe_path(absolute_path(cpp_options[["USER_HEADER"]]))
541+
private$using_user_header_ <- TRUE
542+
}
543+
else if (!is.null(cpp_options[["user_header"]])) {
544+
user_header <- cpp_options[["user_header"]]
545+
cpp_options[["user_header"]] <- wsl_safe_path(absolute_path(cpp_options[["user_header"]]))
546+
private$using_user_header_ <- TRUE
547+
}
548+
549+
550+
if(!is.null(user_header)) {
551+
user_header <- absolute_path(user_header) # As mentioned above, just absolute, not wsl_safe_path()
552+
if(!file.exists(user_header)) {
553+
stop(paste0("User header file '", user_header, "' does not exist."), call. = FALSE)
554+
}
555+
}
556+
504557
# compile if:
505558
# - the user forced compilation,
506559
# - the executable does not exist
507560
# - the stan model was changed since last compilation
561+
# - a user header is used and the user header changed since last compilation (#813)
508562
if (!file.exists(exe)) {
509563
force_recompile <- TRUE
510564
} else if (file.exists(self$stan_file())
511565
&& file.mtime(exe) < file.mtime(self$stan_file())) {
512566
force_recompile <- TRUE
567+
} else if (!is.null(user_header)
568+
&& file.exists(user_header)
569+
&& file.mtime(exe) < file.mtime(user_header)) {
570+
force_recompile <- TRUE
513571
}
514572

515573
if (!force_recompile) {
@@ -520,6 +578,7 @@ compile <- function(quiet = TRUE,
520578
private$precompile_cpp_options_ <- NULL
521579
private$precompile_stanc_options_ <- NULL
522580
private$precompile_include_paths_ <- NULL
581+
self$functions$existing_exe <- TRUE
523582
self$exe_file(exe)
524583
return(invisible(self))
525584
} else {
@@ -530,14 +589,14 @@ compile <- function(quiet = TRUE,
530589

531590
if (os_is_wsl() && (compile_model_methods || compile_standalone)) {
532591
warning("Additional model methods and standalone functions are not ",
533-
"currently available with WSL CmdStan and will not be compiled",
592+
"currently available with WSLv1 CmdStan and will not be compiled.",
534593
call. = FALSE)
535594
compile_model_methods <- FALSE
536595
compile_standalone <- FALSE
537596
compile_hessian_method <- FALSE
538597
}
539598

540-
temp_stan_file <- tempfile(pattern = "model-", fileext = ".stan")
599+
temp_stan_file <- tempfile(pattern = "model-", fileext = paste0(".", tools::file_ext(self$stan_file())))
541600
file.copy(self$stan_file(), temp_stan_file, overwrite = TRUE)
542601
temp_file_no_ext <- strip_ext(temp_stan_file)
543602
tmp_exe <- cmdstan_ext(temp_file_no_ext) # adds .exe on Windows
@@ -548,23 +607,6 @@ compile <- function(quiet = TRUE,
548607

549608
stancflags_val <- include_paths_stanc3_args(include_paths)
550609

551-
if (pedantic) {
552-
stanc_options[["warn-pedantic"]] <- TRUE
553-
}
554-
555-
if (isTRUE(cpp_options$stan_opencl)) {
556-
stanc_options[["use-opencl"]] <- TRUE
557-
}
558-
if (!is.null(user_header)) {
559-
cpp_options[["USER_HEADER"]] <- wsl_safe_path(user_header)
560-
stanc_options[["allow-undefined"]] <- TRUE
561-
}
562-
if (!is.null(cpp_options[["USER_HEADER"]])) {
563-
cpp_options[["USER_HEADER"]] <- wsl_safe_path(absolute_path(cpp_options[["USER_HEADER"]]))
564-
}
565-
if (!is.null(cpp_options[["user_header"]])) {
566-
cpp_options[["user_header"]] <- wsl_safe_path(absolute_path(cpp_options[["user_header"]]))
567-
}
568610
if (is.null(stanc_options[["name"]])) {
569611
stanc_options[["name"]] <- paste0(self$model_name(), "_model")
570612
}
@@ -587,10 +629,18 @@ compile <- function(quiet = TRUE,
587629
stancflags_standalone <- c("--standalone-functions", stancflags_val, stancflags_combined)
588630
self$functions$hpp_code <- get_standalone_hpp(temp_stan_file, stancflags_standalone)
589631
self$functions$external <- !is.null(user_header)
632+
self$functions$existing_exe <- FALSE
633+
634+
stancflags_val <- paste0("STANCFLAGS += ", stancflags_val, paste0(" ", stancflags_combined, collapse = " "))
635+
636+
if (dry_run) {
637+
return(invisible(self))
638+
}
639+
590640
if (compile_standalone) {
591641
expose_stan_functions(self$functions, !quiet)
592642
}
593-
stancflags_val <- paste0("STANCFLAGS += ", stancflags_val, paste0(" ", stancflags_combined, collapse = " "))
643+
594644
withr::with_path(
595645
c(
596646
toolchain_PATH_env_var(),

R/run.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -564,8 +564,8 @@ CmdStanRun$set("private", name = "run_variational_", value = .run_other)
564564
if (file.exists(stdout_file)) {
565565
cat(readLines(stdout_file), sep = "\n")
566566
}
567-
if (file.exists(stdout_file)) {
568-
cat(readLines(stdout_file), sep = "\n")
567+
if (file.exists(stderr_file)) {
568+
cat(readLines(stderr_file), sep = "\n")
569569
}
570570
stop(
571571
"Diagnose failed with the status code ", ret$status, "!\n",

R/utils.R

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -748,7 +748,8 @@ expose_model_methods <- function(env, verbose = FALSE, hessian = FALSE) {
748748
package = "cmdstanr", mustWork = TRUE)))
749749

750750
if (hessian) {
751-
code <- c(code,
751+
code <- c("#include <stan/math/mix.hpp>",
752+
code,
752753
readLines(system.file("include", "hessian.cpp",
753754
package = "cmdstanr", mustWork = TRUE)))
754755
}
@@ -758,9 +759,8 @@ expose_model_methods <- function(env, verbose = FALSE, hessian = FALSE) {
758759
invisible(NULL)
759760
}
760761

761-
initialize_model_pointer <- function(env, data, seed = 0) {
762-
datafile_path <- ifelse(is.null(data), "", data)
763-
ptr_and_rng <- env$model_ptr(datafile_path, seed)
762+
initialize_model_pointer <- function(env, datafile_path, seed = 0) {
763+
ptr_and_rng <- env$model_ptr(ifelse(is.null(datafile_path), "", datafile_path), seed)
764764
env$model_ptr_ <- ptr_and_rng$model_ptr
765765
env$model_rng_ <- ptr_and_rng$base_rng
766766
env$num_upars_ <- env$get_num_upars(env$model_ptr_)
@@ -780,7 +780,11 @@ create_skeleton <- function(param_metadata, model_variables,
780780
names(model_variables$generated_quantities))
781781
}
782782
lapply(param_metadata[target_params], function(par_dims) {
783-
array(0, dim = ifelse(length(par_dims) == 0, 1, par_dims))
783+
if ((length(par_dims) == 0)) {
784+
array(0, dim = 1)
785+
} else {
786+
array(0, dim = par_dims)
787+
}
784788
})
785789
}
786790

@@ -863,8 +867,8 @@ prep_fun_cpp <- function(fun_start, fun_end, model_lines) {
863867
fun_body <- gsub("auto", get_plain_rtn(fun_start, fun_end, model_lines), fun_body)
864868
fun_body <- gsub("// [[stan::function]]", "// [[Rcpp::export]]\n", fun_body, fixed = TRUE)
865869
fun_body <- gsub("std::ostream\\*\\s*pstream__\\s*=\\s*nullptr", "", fun_body)
866-
fun_body <- gsub("boost::ecuyer1988& base_rng__", "size_t seed = 0", fun_body, fixed = TRUE)
867-
fun_body <- gsub("base_rng__,", "*(new boost::ecuyer1988(seed)),", fun_body, fixed = TRUE)
870+
fun_body <- gsub("boost::ecuyer1988&\\s*base_rng__", "SEXP base_rng_ptr", fun_body)
871+
fun_body <- gsub("base_rng__,", "*(Rcpp::XPtr<boost::ecuyer1988>(base_rng_ptr).get()),", fun_body, fixed = TRUE)
868872
fun_body <- gsub("pstream__", "&Rcpp::Rcout", fun_body, fixed = TRUE)
869873
fun_body <- paste(fun_body, collapse = "\n")
870874
gsub(pattern = ",\\s*)", replacement = ")", fun_body)
@@ -904,6 +908,30 @@ compile_functions <- function(env, verbose = FALSE, global = FALSE) {
904908
} else {
905909
rcpp_source_stan(mod_stan_funs, env, verbose)
906910
}
911+
912+
# If an RNG function is exposed, initialise a Boost RNG object stored in the
913+
# environment
914+
rng_funs <- grep("rng\\b", env$fun_names, value = TRUE)
915+
if (length(rng_funs) > 0) {
916+
rng_cpp <- system.file("include", "base_rng.cpp", package = "cmdstanr", mustWork = TRUE)
917+
rcpp_source_stan(paste0(readLines(rng_cpp), collapse="\n"), env, verbose)
918+
env$rng_ptr <- env$base_rng(seed=0)
919+
}
920+
921+
# For all RNG functions, pass the initialised Boost RNG by default
922+
for (fun in rng_funs) {
923+
if (global) {
924+
fun_env <- globalenv()
925+
} else {
926+
fun_env <- env
927+
}
928+
fundef <- get(fun, envir = fun_env)
929+
funargs <- formals(fundef)
930+
funargs$base_rng_ptr <- env$rng_ptr
931+
formals(fundef) <- funargs
932+
assign(fun, fundef, envir = fun_env)
933+
}
934+
907935
env$compiled <- TRUE
908936
invisible(NULL)
909937
}
@@ -914,6 +942,10 @@ expose_stan_functions <- function(function_env, global = FALSE, verbose = FALSE)
914942
"WSL CmdStan and will not be compiled",
915943
call. = FALSE)
916944
}
945+
if (function_env$existing_exe) {
946+
stop("Exporting standalone functions is not possible with a pre-compiled Stan model!",
947+
call. = FALSE)
948+
}
917949
if (function_env$external && cmdstan_version() < "2.32") {
918950
stop("Exporting standalone functions with external C++ is not available before CmdStan 2.32",
919951
call. = FALSE)

docs/404.html

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/LICENSE-text.html

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/LICENSE.html

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)