From 8ad2d1993018493aff07c781bc3373473275cd25 Mon Sep 17 00:00:00 2001 From: Joss Wright Date: Sun, 21 Dec 2025 22:55:25 +0000 Subject: [PATCH 01/17] Implemented progress bar for sampling messages. This uses the [`progressr`](https://progressr.futureverse.org/index.html) framework to enable a progress bar for sampling operations. By default, this replaces standard iteration messages, but not other informative messages produced during sampling. From a user point of view, this adds two arguments to the `$sample()` method: - `show_progress_bar`: Default = FALSE If TRUE, registers a progress bar via `progressr` and signals an update for every output line that matches "Iteration:". The user is responsible for registering those progress updates with an appropriate handler. - `suppress_iteration_messages`: Defaults to the value of show_progress_bar, but can also be set directly. If TRUE, disables display of output lines matching "Iteration:", while still causing the progress bar to update. This keeps all of Stan's other informative output, but just removes the superfluous iteration messages when using a progress bar. I've tried to keep all additions to the code as non-intrusive as possible. One minor decision I made was to pass the value of `refresh` from the `$sample()` method through to the `CmdStanProcs` class so that the progress bar can report and update the number of steps in the bar to the same 'scale' as the number of iterations. (Calling `$sample()` with `iter_sampling=8000` and `refresh=100` will result in 80 'ticks' of the progress bar, each increasing the progress by 100.) By default, `progressr` doesn't register a handler to display the progress bar, as this is the responsibility of the user. Multiple packages can be used to display the progress bar. A default progress bar can be registered using the [`cli`](https://cli.r-lib.org/) library in this way: ```r library(progressr) library(cli) handlers( global=TRUE ) handlers("cli") options( cli.spinner = "moon", cli.progress_show_after = 0, cli.progress_clear = FALSE ) handlers( handler_cli( format = "{cli::pb_spin} Progress: |{cli::pb_bar}| {cli::pb_current}/{cli::pb_total} | {cli::pb_percent} | ETA: {cli::pb_eta}", clear = FALSE )) ``` A variety of alternative progress bar handlers are available, including audible and notification-based handlers, and ones that interact with RStudio's jobs pane: --- R/model.R | 36 ++++++++++++++++++++++++++++++++++-- R/run.R | 49 +++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/R/model.R b/R/model.R index bb1427ed..5cff615d 100644 --- a/R/model.R +++ b/R/model.R @@ -1157,6 +1157,8 @@ sample <- function(data = NULL, diagnostics = c("divergences", "treedepth", "ebfmi"), save_metric = NULL, save_cmdstan_config = NULL, + show_progress_bar = FALSE, + suppress_iteration_messages = NULL, # deprecated cores = NULL, num_cores = NULL, @@ -1221,12 +1223,42 @@ sample <- function(data = NULL, if (fixed_param) { save_warmup <- FALSE } + # Check for and create progressr::progressor object for progress reporting, if required. + # Pass default value for refresh + progress_bar <- NULL + if (show_progress_bar) { + if(require(progressr)) { + + # progressr only supports single-line progress bars at time of writing, + # so all chains must be combined into a single process bar. + + # Calculate a total number of steps for progress as + # (chains*(iter_warmup+iter_sampling)). We will update the progress bar + # by 'refresh' steps each time. + + # As all the arguments to CmdStan can be NULL, we need to reproduce the + # defaults here manually. + + n_samples <- ifelse(is.null(iter_sampling), 1000, iter_sampling) + n_warmup <- ifelse(is.null(iter_warmup), 1000, iter_warmup) + n_chains <- ifelse(is.null(chains), 1, chains) + n_steps <- (n_chains*(n_samples+n_warmup)) + + progress_bar <- progressr::progressor(steps=n_steps, auto_finish=FALSE) + } + else { + warning("'show_progress_bar=TRUE' requires the 'progressr' package. Please install 'progressr'.") + } + } procs <- CmdStanMCMCProcs$new( num_procs = checkmate::assert_integerish(chains, lower = 1, len = 1), parallel_procs = checkmate::assert_integerish(parallel_chains, lower = 1, null.ok = TRUE), threads_per_proc = assert_valid_threads(threads_per_chain, self$cpp_options(), multiple_chains = TRUE), show_stderr_messages = show_exceptions, - show_stdout_messages = show_messages + show_stdout_messages = show_messages, + progress_bar = progress_bar, + suppress_iteration_messages = suppress_iteration_messages, + refresh = ifelse(is.null(refresh), 100, refresh) ) model_variables <- NULL if (is_variables_method_supported(self)) { @@ -2375,4 +2407,4 @@ resolve_exe_path <- function( exe <- self_exe_file } exe -} \ No newline at end of file +} diff --git a/R/run.R b/R/run.R index 9202f0d4..1240f8a8 100644 --- a/R/run.R +++ b/R/run.R @@ -705,7 +705,10 @@ CmdStanProcs <- R6::R6Class( parallel_procs = NULL, threads_per_proc = NULL, show_stderr_messages = TRUE, - show_stdout_messages = TRUE) { + show_stdout_messages = TRUE, + progress_bar = NULL, + suppress_iteration_messages = NULL, + refresh = 100 ) { checkmate::assert_integerish(num_procs, lower = 1, len = 1, any.missing = FALSE) checkmate::assert_integerish(parallel_procs, lower = 1, len = 1, any.missing = FALSE, null.ok = TRUE) checkmate::assert_integerish(threads_per_proc, lower = 1, len = 1, null.ok = TRUE) @@ -726,6 +729,19 @@ CmdStanProcs <- R6::R6Class( private$proc_total_time_ <- zeros private$show_stderr_messages_ <- show_stderr_messages private$show_stdout_messages_ <- show_stdout_messages + private$progress_bar_ <- progress_bar + # If 'progress_bar' is set, suppress iteration messages by default. + # If 'suppress_iteration_messages' is explicitly set, honour that setting. + # Do not suppress iteration messages by default. + if(!is.null(progress_bar)) { + private$suppress_iteration_messages_ <- TRUE + } else { + private$suppress_iteration_messages_ <- FALSE + } + if(!is.null(suppress_iteration_messages)) { + private$suppress_iteration_messages_ <- suppress_iteration_messages + } + private$refresh_ <- refresh invisible(self) }, show_stdout_messages = function () { @@ -734,6 +750,15 @@ CmdStanProcs <- R6::R6Class( show_stderr_messages = function () { private$show_stderr_messages_ }, + progress_bar = function() { + private$progress_bar_ + }, + suppress_iteration_messages = function () { + private$suppress_iteration_messages_ + }, + refresh = function () { + private$refresh_ + }, num_procs = function() { private$num_procs_ }, @@ -750,6 +775,10 @@ CmdStanProcs <- R6::R6Class( lapply(private$processes_, function(p) { try(p$kill_tree(), silent = TRUE) }) + # Ensure that the progress bar is closed, if created. + if(!is.null(private$progress_bar_)){ + private$progress_bar_(type="finish") + } invisible(self) }, poll = function(ms) { # time in milliseconds @@ -973,7 +1002,10 @@ CmdStanProcs <- R6::R6Class( proc_error_ouput_ = list(), total_time_ = numeric(), show_stderr_messages_ = TRUE, - show_stdout_messages_ = TRUE + show_stdout_messages_ = TRUE, + progress_bar_ = NULL, + suppress_iteration_messages_ = NULL, + refresh_ = 100 ) ) @@ -1050,6 +1082,19 @@ CmdStanMCMCProcs <- R6::R6Class( || grepl("stancflags", line, fixed = TRUE)) { ignore_line <- TRUE } + # Update progress bar for all lines reporting an iteration. + if (!ignore_line && !is.null(private$progress_bar_)) { + # Update progress bar on any sampling iteration lines + if(grepl("Iteration:", line, perl = TRUE)) { + private$progress_bar_( amount=private$refresh_ ) + } + } + # Allow suppression of iteration messages + if (private$suppress_iteration_messages_) { + if(grepl("Iteration:", line, perl = TRUE)) { + ignore_line <- TRUE + } + } if ((state > 1.5 && state < 5 && !ignore_line && private$show_stdout_messages_) || is_verbose_mode()) { if (state == 2) { message("Chain ", id, " ", line) From 96b79e986a5e79052a95711150baa67b90412a86 Mon Sep 17 00:00:00 2001 From: Joss Wright Date: Sun, 21 Dec 2025 23:30:22 +0000 Subject: [PATCH 02/17] Pass all stdout lines to progress bar as a message In addition to updating the progress bar on stdout lines that match "Iteration:", the CmdStanProcs object(s) now pass the current stdout line to the progress bar as a message for _every_ line, even if the number of completed iterations hasn't increased. --- R/run.R | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/R/run.R b/R/run.R index 1240f8a8..5a69e8be 100644 --- a/R/run.R +++ b/R/run.R @@ -1082,12 +1082,16 @@ CmdStanMCMCProcs <- R6::R6Class( || grepl("stancflags", line, fixed = TRUE)) { ignore_line <- TRUE } - # Update progress bar for all lines reporting an iteration. + # Update progress bar if (!ignore_line && !is.null(private$progress_bar_)) { - # Update progress bar on any sampling iteration lines + # Pass the current output line to the progress bar as a message, + # but only update the actual progress if the current line is an + # iteration message. + progress_amount <- 0 if(grepl("Iteration:", line, perl = TRUE)) { - private$progress_bar_( amount=private$refresh_ ) + progress_amount <- private$refresh_ } + private$progress_bar_(amount=private$refresh_, message=line) } # Allow suppression of iteration messages if (private$suppress_iteration_messages_) { From 34d001daac7e6c350b14ec3c9395ae0907730f66 Mon Sep 17 00:00:00 2001 From: Joss Wright Date: Mon, 22 Dec 2025 00:13:36 +0000 Subject: [PATCH 03/17] Added convenience handler for progress bar Added a `register_default_progress_handler()` function to `utils.R` that creates a default progress bar for sampling operations and registers it as global handler for progressr. Requires `cli` and `progressr`. --- NAMESPACE | 1 + R/utils.R | 36 ++++++++++++++++++++++++++++++++++++ man/model-method-sample.Rd | 2 ++ 3 files changed, 39 insertions(+) diff --git a/NAMESPACE b/NAMESPACE index b157025d..9e12b908 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -41,6 +41,7 @@ export(print_example_program) export(read_cmdstan_csv) export(read_sample_csv) export(rebuild_cmdstan) +export(register_default_progress_handler) export(register_knitr_engine) export(set_cmdstan_path) export(set_num_threads) diff --git a/R/utils.R b/R/utils.R index 8abc327f..051ae049 100644 --- a/R/utils.R +++ b/R/utils.R @@ -1072,3 +1072,39 @@ expose_stan_functions <- function(function_env, global = FALSE, verbose = FALSE) } invisible(NULL) } + +#' Register a default progress bar handler +#' +#' Create a default progress bar for CmdStan sampling operations, and +#' register it as the default global handler for progressr updates. +#' +#' @export +#' +#' @param verbose (logical) Report creation of progress bar to stdout? +#' The default is `TRUE`. +#' +register_default_progress_handler <- function(verbose=TRUE) { + # Require both the progressr and cli packages. + if(require(progressr) & require(cli)) { + + handlers( global=TRUE ) + handlers("cli") + + # Progress bar options + options( cli.spinner = "moon", + cli.progress_show_after = 0, + cli.progress_clear = FALSE ) + + # Default informative progress output for sampling + handlers( handler_cli( + format = "{cli::pb_spin} Progress: |{cli::pb_bar}| {cli::pb_current}/{cli::pb_total} | {cli::pb_percent} | ETA: {cli::pb_eta}", + clear = FALSE + )) + if(verbose) { + message("Default progress bar registered.") + } + } else { + warning("The 'progressr' library is required to enable a progress bar. The default progress bar uses the 'cli' library.") + } + invisible(NULL) +} diff --git a/man/model-method-sample.Rd b/man/model-method-sample.Rd index 2558e630..050b5fe7 100644 --- a/man/model-method-sample.Rd +++ b/man/model-method-sample.Rd @@ -39,6 +39,8 @@ sample( diagnostics = c("divergences", "treedepth", "ebfmi"), save_metric = NULL, save_cmdstan_config = NULL, + show_progress_bar = FALSE, + suppress_iteration_messages = NULL, cores = NULL, num_cores = NULL, num_chains = NULL, From a083a7e95bc8ceb00dc065f38f8bbe1ada0ea40c Mon Sep 17 00:00:00 2001 From: Joss Wright Date: Thu, 25 Dec 2025 23:12:29 +0000 Subject: [PATCH 04/17] Fixed progress update bug Was accidentally passing `refresh` to each informational progress bar update, rather than the calculated `progress_amount`. This caused each message line to update the progress bar, even if it didn't match an iteration message. --- R/run.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/run.R b/R/run.R index 5a69e8be..15853292 100644 --- a/R/run.R +++ b/R/run.R @@ -1091,7 +1091,7 @@ CmdStanMCMCProcs <- R6::R6Class( if(grepl("Iteration:", line, perl = TRUE)) { progress_amount <- private$refresh_ } - private$progress_bar_(amount=private$refresh_, message=line) + private$progress_bar_(amount=progress_amount, message=line) } # Allow suppression of iteration messages if (private$suppress_iteration_messages_) { From ce20c36bbce39b77d10a5c2ddbbf270ea569ff6e Mon Sep 17 00:00:00 2001 From: Joss Wright Date: Wed, 11 Feb 2026 21:48:04 +0000 Subject: [PATCH 05/17] Potential `length(milestones) >0L` error fix Moved code to close the progress bar to after the `check_finished()` function. --- R/run.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/R/run.R b/R/run.R index 15853292..3afd8d75 100644 --- a/R/run.R +++ b/R/run.R @@ -511,6 +511,10 @@ check_target_exe <- function(exe) { } procs$check_finished() } + # Ensure that the progress bar is closed, if created. + if(!is.null(private$progress_bar_)){ + private$progress_bar_(type="finish") + } procs$set_total_time(as.double((Sys.time() - start_time), units = "secs")) procs$report_time() } @@ -775,10 +779,6 @@ CmdStanProcs <- R6::R6Class( lapply(private$processes_, function(p) { try(p$kill_tree(), silent = TRUE) }) - # Ensure that the progress bar is closed, if created. - if(!is.null(private$progress_bar_)){ - private$progress_bar_(type="finish") - } invisible(self) }, poll = function(ms) { # time in milliseconds From b648daadad393109127d17be2ed564788a905265 Mon Sep 17 00:00:00 2001 From: Joss Wright Date: Fri, 13 Feb 2026 20:41:07 +0000 Subject: [PATCH 06/17] Rewritten refresh and iteration calculations. The nature of CmdStan's output makes pulling appropriate refresh and update values for the progress bar slightly tricky. In the original verison of the code, this led to the bar occasionally updating past the point where it was full, or terminating early due to an incorrectly calculated number of steps. This commit reworks the calculations of updates and refresh values for the progress bar, as well as adding some extra conditions to avoid the bar potentially crashing in odd scenarios. --- R/model.R | 11 ++++++--- R/run.R | 74 ++++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 70 insertions(+), 15 deletions(-) diff --git a/R/model.R b/R/model.R index 5cff615d..1c1e4c2d 100644 --- a/R/model.R +++ b/R/model.R @@ -1233,8 +1233,8 @@ sample <- function(data = NULL, # so all chains must be combined into a single process bar. # Calculate a total number of steps for progress as - # (chains*(iter_warmup+iter_sampling)). We will update the progress bar - # by 'refresh' steps each time. + # (chains*(iter_warmup+iter_sampling)). + # We will update the progress bar by 'refresh' steps each time. # As all the arguments to CmdStan can be NULL, we need to reproduce the # defaults here manually. @@ -1244,7 +1244,8 @@ sample <- function(data = NULL, n_chains <- ifelse(is.null(chains), 1, chains) n_steps <- (n_chains*(n_samples+n_warmup)) - progress_bar <- progressr::progressor(steps=n_steps, auto_finish=FALSE) + progress_bar <- progressr::progressor(steps=n_steps, auto_finish=TRUE) + } else { warning("'show_progress_bar=TRUE' requires the 'progressr' package. Please install 'progressr'.") @@ -1252,13 +1253,15 @@ sample <- function(data = NULL, } procs <- CmdStanMCMCProcs$new( num_procs = checkmate::assert_integerish(chains, lower = 1, len = 1), + iter_warmup = checkmate::assert_integerish(iter_warmup, lower = 1, len = 1), + iter_sampling = checkmate::assert_integerish(iter_sampling, lower = 1, len = 1), parallel_procs = checkmate::assert_integerish(parallel_chains, lower = 1, null.ok = TRUE), threads_per_proc = assert_valid_threads(threads_per_chain, self$cpp_options(), multiple_chains = TRUE), show_stderr_messages = show_exceptions, show_stdout_messages = show_messages, progress_bar = progress_bar, suppress_iteration_messages = suppress_iteration_messages, - refresh = ifelse(is.null(refresh), 100, refresh) + refresh = refresh ) model_variables <- NULL if (is_variables_method_supported(self)) { diff --git a/R/run.R b/R/run.R index 3afd8d75..14f0d460 100644 --- a/R/run.R +++ b/R/run.R @@ -511,7 +511,7 @@ check_target_exe <- function(exe) { } procs$check_finished() } - # Ensure that the progress bar is closed, if created. + # Ensure at this point that any created progress bar is closed. if(!is.null(private$progress_bar_)){ private$progress_bar_(type="finish") } @@ -706,17 +706,23 @@ CmdStanProcs <- R6::R6Class( classname = "CmdStanProcs", public = list( initialize = function(num_procs, + iter_warmup, + iter_sampling, parallel_procs = NULL, threads_per_proc = NULL, show_stderr_messages = TRUE, show_stdout_messages = TRUE, progress_bar = NULL, suppress_iteration_messages = NULL, - refresh = 100 ) { + refresh = NULL ) { checkmate::assert_integerish(num_procs, lower = 1, len = 1, any.missing = FALSE) + checkmate::assert_integerish(iter_warmup, lower = 1, len = 1, any.missing = FALSE) + checkmate::assert_integerish(iter_sampling, lower = 1, len = 1, any.missing = FALSE) checkmate::assert_integerish(parallel_procs, lower = 1, len = 1, any.missing = FALSE, null.ok = TRUE) checkmate::assert_integerish(threads_per_proc, lower = 1, len = 1, null.ok = TRUE) private$num_procs_ <- as.integer(num_procs) + private$iter_warmup_ <- as.integer(iter_warmup) + private$iter_sampling_ <- as.integer(iter_sampling) if (is.null(parallel_procs)) { private$parallel_procs_ <- private$num_procs_ } else { @@ -734,18 +740,26 @@ CmdStanProcs <- R6::R6Class( private$show_stderr_messages_ <- show_stderr_messages private$show_stdout_messages_ <- show_stdout_messages private$progress_bar_ <- progress_bar - # If 'progress_bar' is set, suppress iteration messages by default. - # If 'suppress_iteration_messages' is explicitly set, honour that setting. - # Do not suppress iteration messages by default. - if(!is.null(progress_bar)) { - private$suppress_iteration_messages_ <- TRUE - } else { + + # Defaults when enabling the progress bar: + # - If 'progress_bar' is set, suppress iteration messages; + # - if `progress_bar` is unset, do not suppress iteration messages; + # - if 'suppress_iteration_messages' is set explicitly, honour that setting. + if(is.null(progress_bar)) { private$suppress_iteration_messages_ <- FALSE + } else { + private$suppress_iteration_messages_ <- TRUE } if(!is.null(suppress_iteration_messages)) { private$suppress_iteration_messages_ <- suppress_iteration_messages } - private$refresh_ <- refresh + + if(is.null(refresh)) { + # Default to Stan default of 100 if refresh not set explicitly. + private$refresh_ <- 100 + } else { + private$refresh_ <- refresh + } invisible(self) }, show_stdout_messages = function () { @@ -766,6 +780,12 @@ CmdStanProcs <- R6::R6Class( num_procs = function() { private$num_procs_ }, + iter_warmup = function() { + privatea$iter_warmup_ + }, + iter_sampling = function() { + private$iter_sampling_ + }, parallel_procs = function() { private$parallel_procs_ }, @@ -991,6 +1011,8 @@ CmdStanProcs <- R6::R6Class( processes_ = NULL, # will be list of processx::process objects proc_ids_ = integer(), num_procs_ = integer(), + iter_warmup_ = integer(), + iter_sampling_ = integer(), parallel_procs_ = integer(), active_procs_ = integer(), threads_per_proc_ = integer(), @@ -1085,11 +1107,41 @@ CmdStanMCMCProcs <- R6::R6Class( # Update progress bar if (!ignore_line && !is.null(private$progress_bar_)) { # Pass the current output line to the progress bar as a message, - # but only update the actual progress if the current line is an + # but only update the progress bar if the current line is an # iteration message. progress_amount <- 0 if(grepl("Iteration:", line, perl = TRUE)) { - progress_amount <- private$refresh_ + # Calculating the amount by which to increment the progress bar + # is more complicated than it initially seems, due to occasional + # extra or awkward iteration reporting messages when starting + # sampling, moving from warmup to sampling, reaching the end of + # sampling where the number of samples is not a multiple of the + # refresh_rate. + + # Strategy: + # If the line's iteration value is divisible by refresh_rate, or + # is the final sampling step, update the progress bar by + # refresh_rate. + + # Additionally, when moving from warmup to sampling, iterations + # are reported starting from a baseline of the number of warmup + # iterations. (For example, if refresh_rate is 12 and iter_warmup + # is 100, the first reported iteration for sampling will be 112, + # not 108.) + + # Get the current iteration count. + # Subtract iter_warmup if greater than that. + iter_current <- as.numeric(gsub( ".*Iteration:\\s*([0-9]+) \\/.*", "\\1", line, perl=TRUE )) + if( iter_current > private$iter_warmup_ ) { + iter_current <- iter_current - private$iter_warmup_ + } + + # Update progress bar if the iteration is a multiple of the + # refresh rate, or is the final sampling iteration. + if(((iter_current %% private$refresh_) == 0) | + iter_current == private$iter_warmup_ + private$iter_sampling_) { + progress_amount <- private$refresh_ + } } private$progress_bar_(amount=progress_amount, message=line) } From 37931e9771693d274d11d1a0028a51bda11ab944 Mon Sep 17 00:00:00 2001 From: Joss Wright Date: Fri, 13 Feb 2026 23:23:58 +0000 Subject: [PATCH 07/17] Fixed defaults for `iter_{warmup,sampling}` New arguments for `iter_warmup` and `iter_sampling` were not being assigned a default value if not specified in sampling statement. --- R/model.R | 4 ++-- R/run.R | 21 +++++++++++++++------ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/R/model.R b/R/model.R index 1c1e4c2d..f7a4caa3 100644 --- a/R/model.R +++ b/R/model.R @@ -1253,8 +1253,8 @@ sample <- function(data = NULL, } procs <- CmdStanMCMCProcs$new( num_procs = checkmate::assert_integerish(chains, lower = 1, len = 1), - iter_warmup = checkmate::assert_integerish(iter_warmup, lower = 1, len = 1), - iter_sampling = checkmate::assert_integerish(iter_sampling, lower = 1, len = 1), + iter_warmup = checkmate::assert_integerish(iter_warmup, lower = 1, len = 1, null.ok = TRUE), + iter_sampling = checkmate::assert_integerish(iter_sampling, lower = 1, len = 1, null.ok = TRUE), parallel_procs = checkmate::assert_integerish(parallel_chains, lower = 1, null.ok = TRUE), threads_per_proc = assert_valid_threads(threads_per_chain, self$cpp_options(), multiple_chains = TRUE), show_stderr_messages = show_exceptions, diff --git a/R/run.R b/R/run.R index 14f0d460..0b137c50 100644 --- a/R/run.R +++ b/R/run.R @@ -706,8 +706,8 @@ CmdStanProcs <- R6::R6Class( classname = "CmdStanProcs", public = list( initialize = function(num_procs, - iter_warmup, - iter_sampling, + iter_warmup = NULL, + iter_sampling = NULL, parallel_procs = NULL, threads_per_proc = NULL, show_stderr_messages = TRUE, @@ -716,13 +716,22 @@ CmdStanProcs <- R6::R6Class( suppress_iteration_messages = NULL, refresh = NULL ) { checkmate::assert_integerish(num_procs, lower = 1, len = 1, any.missing = FALSE) - checkmate::assert_integerish(iter_warmup, lower = 1, len = 1, any.missing = FALSE) - checkmate::assert_integerish(iter_sampling, lower = 1, len = 1, any.missing = FALSE) + checkmate::assert_integerish(iter_warmup, lower = 1, len = 1, any.missing = FALSE, null.ok = TRUE ) + checkmate::assert_integerish(iter_sampling, lower = 1, len = 1, any.missing = FALSE, null.ok = TRUE ) checkmate::assert_integerish(parallel_procs, lower = 1, len = 1, any.missing = FALSE, null.ok = TRUE) checkmate::assert_integerish(threads_per_proc, lower = 1, len = 1, null.ok = TRUE) private$num_procs_ <- as.integer(num_procs) - private$iter_warmup_ <- as.integer(iter_warmup) - private$iter_sampling_ <- as.integer(iter_sampling) + if (is.null(iter_warmup)) { + private$iter_warmup_ <- 1000 + } else { + private$iter_warmup_ <- as.integer(iter_warmup) + } + if (is.null(iter_sampling)) { + private$iter_sampling_ <- 1000 + } else { + private$iter_sampling_ <- as.integer(iter_sampling) + } + if (is.null(parallel_procs)) { private$parallel_procs_ <- private$num_procs_ } else { From 6c21b5da0b06539140112780ece3bed2533be820 Mon Sep 17 00:00:00 2001 From: Joss Wright Date: Sun, 15 Feb 2026 16:33:05 +0000 Subject: [PATCH 08/17] Fixes for failing unit tests - `iter_{warmup,sampling}` arguments to MCMCProcs are allowed to be 0. - Documentation for `register_default_progress_handler` added to repository. - Ensured that calls to `progressr` functions are appropriately prefixed. --- R/model.R | 4 ++-- R/run.R | 4 ++-- R/utils.R | 8 ++++---- man/register_default_progress_handler.Rd | 16 ++++++++++++++++ 4 files changed, 24 insertions(+), 8 deletions(-) create mode 100644 man/register_default_progress_handler.Rd diff --git a/R/model.R b/R/model.R index f7a4caa3..b3b7a632 100644 --- a/R/model.R +++ b/R/model.R @@ -1253,8 +1253,8 @@ sample <- function(data = NULL, } procs <- CmdStanMCMCProcs$new( num_procs = checkmate::assert_integerish(chains, lower = 1, len = 1), - iter_warmup = checkmate::assert_integerish(iter_warmup, lower = 1, len = 1, null.ok = TRUE), - iter_sampling = checkmate::assert_integerish(iter_sampling, lower = 1, len = 1, null.ok = TRUE), + iter_warmup = checkmate::assert_integerish(iter_warmup, lower = 0, len = 1, null.ok = TRUE), + iter_sampling = checkmate::assert_integerish(iter_sampling, lower = 0, len = 1, null.ok = TRUE), parallel_procs = checkmate::assert_integerish(parallel_chains, lower = 1, null.ok = TRUE), threads_per_proc = assert_valid_threads(threads_per_chain, self$cpp_options(), multiple_chains = TRUE), show_stderr_messages = show_exceptions, diff --git a/R/run.R b/R/run.R index 0b137c50..2bbb0482 100644 --- a/R/run.R +++ b/R/run.R @@ -716,8 +716,8 @@ CmdStanProcs <- R6::R6Class( suppress_iteration_messages = NULL, refresh = NULL ) { checkmate::assert_integerish(num_procs, lower = 1, len = 1, any.missing = FALSE) - checkmate::assert_integerish(iter_warmup, lower = 1, len = 1, any.missing = FALSE, null.ok = TRUE ) - checkmate::assert_integerish(iter_sampling, lower = 1, len = 1, any.missing = FALSE, null.ok = TRUE ) + checkmate::assert_integerish(iter_warmup, lower = 0, len = 1, any.missing = FALSE, null.ok = TRUE ) + checkmate::assert_integerish(iter_sampling, lower = 0, len = 1, any.missing = FALSE, null.ok = TRUE ) checkmate::assert_integerish(parallel_procs, lower = 1, len = 1, any.missing = FALSE, null.ok = TRUE) checkmate::assert_integerish(threads_per_proc, lower = 1, len = 1, null.ok = TRUE) private$num_procs_ <- as.integer(num_procs) diff --git a/R/utils.R b/R/utils.R index 051ae049..e268ca43 100644 --- a/R/utils.R +++ b/R/utils.R @@ -1073,7 +1073,7 @@ expose_stan_functions <- function(function_env, global = FALSE, verbose = FALSE) invisible(NULL) } -#' Register a default progress bar handler +#' Register a default progress bar handler for sampling #' #' Create a default progress bar for CmdStan sampling operations, and #' register it as the default global handler for progressr updates. @@ -1087,8 +1087,8 @@ register_default_progress_handler <- function(verbose=TRUE) { # Require both the progressr and cli packages. if(require(progressr) & require(cli)) { - handlers( global=TRUE ) - handlers("cli") + progressr::handlers(global=TRUE) + progressr::handlers("cli") # Progress bar options options( cli.spinner = "moon", @@ -1096,7 +1096,7 @@ register_default_progress_handler <- function(verbose=TRUE) { cli.progress_clear = FALSE ) # Default informative progress output for sampling - handlers( handler_cli( + progressr::handlers( progressr::handler_cli( format = "{cli::pb_spin} Progress: |{cli::pb_bar}| {cli::pb_current}/{cli::pb_total} | {cli::pb_percent} | ETA: {cli::pb_eta}", clear = FALSE )) diff --git a/man/register_default_progress_handler.Rd b/man/register_default_progress_handler.Rd new file mode 100644 index 00000000..8138f04c --- /dev/null +++ b/man/register_default_progress_handler.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{register_default_progress_handler} +\alias{register_default_progress_handler} +\title{Register a default progress bar handler for sampling} +\usage{ +register_default_progress_handler(verbose = TRUE) +} +\arguments{ +\item{verbose}{(logical) Report creation of progress bar to stdout? +The default is \code{TRUE}.} +} +\description{ +Create a default progress bar for CmdStan sampling operations, and +register it as the default global handler for progressr updates. +} From 84b3ed6a7b41f7712cf37a5817a1f5289651cfd8 Mon Sep 17 00:00:00 2001 From: Joss Wright Date: Sun, 15 Feb 2026 19:13:41 +0000 Subject: [PATCH 09/17] Documented progress bar arguments. Fixed requires. Added documentation for `show_progress_bar` and `suppress_iteration_messages` in `sample()` function. Changed `require()` calls for `progressr` and `cli` to suggested `requireNamespace()` alternatives. --- R/model.R | 10 +++++++++- R/utils.R | 7 ++++--- man/model-method-sample.Rd | 10 ++++++++++ man/register_default_progress_handler.Rd | 5 +++-- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/R/model.R b/R/model.R index b3b7a632..22448538 100644 --- a/R/model.R +++ b/R/model.R @@ -1116,6 +1116,14 @@ CmdStanModel$set("public", name = "format", value = format) #' #' @template model-common-args #' @template model-sample-args +#' @param show_progress_bar (logical). If TRUE, registers a progress bar to +#' display sampling progress via the `progressr` framework. The user is +#' responsible for registering a handler to display the progress bar. A +#' default handler, using the `cli` package, can be registered via the +#' `cmdstanr::register_default_progress_handler()`. Default: FALSE. +#' @param suppress_iteration_messages: Suppress CmdStan output lines reporting +#' iterations, intended for use with the `show_progress_bar` argument. Defaults +#' to the value of `show_progress_bar`. #' @param cores,num_cores,num_chains,num_warmup,num_samples,save_extra_diagnostics,max_depth,stepsize,validate_csv #' Deprecated and will be removed in a future release. #' @@ -1227,7 +1235,7 @@ sample <- function(data = NULL, # Pass default value for refresh progress_bar <- NULL if (show_progress_bar) { - if(require(progressr)) { + if(requireNamespace("progressr", quietly = TRUE)) { # progressr only supports single-line progress bars at time of writing, # so all chains must be combined into a single process bar. diff --git a/R/utils.R b/R/utils.R index e268ca43..7db2c1c8 100644 --- a/R/utils.R +++ b/R/utils.R @@ -1075,8 +1075,9 @@ expose_stan_functions <- function(function_env, global = FALSE, verbose = FALSE) #' Register a default progress bar handler for sampling #' -#' Create a default progress bar for CmdStan sampling operations, and -#' register it as the default global handler for progressr updates. +#' Create a default progress bar for CmdStan sampling operations, and register +#' it as the default global handler for progressr updates. Requires `progressr` +#' for the progress framework, and `cli` for the default progress bar handler. #' #' @export #' @@ -1085,7 +1086,7 @@ expose_stan_functions <- function(function_env, global = FALSE, verbose = FALSE) #' register_default_progress_handler <- function(verbose=TRUE) { # Require both the progressr and cli packages. - if(require(progressr) & require(cli)) { + if(requireNamespace("progressr", quietly = TRUE) & requireNamespace("cli", quietly = TRUE)) { progressr::handlers(global=TRUE) progressr::handlers("cli") diff --git a/man/model-method-sample.Rd b/man/model-method-sample.Rd index 050b5fe7..a25ebaf9 100644 --- a/man/model-method-sample.Rd +++ b/man/model-method-sample.Rd @@ -305,7 +305,17 @@ with argument \code{"output save_config=1"} to save a json file which contains the argument tree and extra information (equivalent to the output CSV file header). This option is only available in CmdStan 2.34.0 and later.} +\item{show_progress_bar}{(logical). If TRUE, registers a progress bar to +display sampling progress via the \code{progressr} framework. The user is +responsible for registering a handler to display the progress bar. A +default handler, using the \code{cli} package, can be registered via the +\code{cmdstanr::register_default_progress_handler()}. Default: FALSE.} + \item{cores, num_cores, num_chains, num_warmup, num_samples, save_extra_diagnostics, max_depth, stepsize, validate_csv}{Deprecated and will be removed in a future release.} + +\item{suppress_iteration_messages:}{Suppress CmdStan output lines reporting +iterations, intended for use with the \code{show_progress_bar} argument. Defaults +to the value of \code{show_progress_bar}.} } \value{ A \code{\link{CmdStanMCMC}} object. diff --git a/man/register_default_progress_handler.Rd b/man/register_default_progress_handler.Rd index 8138f04c..28709ade 100644 --- a/man/register_default_progress_handler.Rd +++ b/man/register_default_progress_handler.Rd @@ -11,6 +11,7 @@ register_default_progress_handler(verbose = TRUE) The default is \code{TRUE}.} } \description{ -Create a default progress bar for CmdStan sampling operations, and -register it as the default global handler for progressr updates. +Create a default progress bar for CmdStan sampling operations, and register +it as the default global handler for progressr updates. Requires \code{progressr} +for the progress framework, and \code{cli} for the default progress bar handler. } From 551e8d8a7bd0b00ca5ee53da292d48314f416951 Mon Sep 17 00:00:00 2001 From: VisruthSK <67435125+VisruthSK@users.noreply.github.com> Date: Sun, 15 Feb 2026 11:29:08 -0800 Subject: [PATCH 10/17] Imported dependencies and fixed a typo in documentation --- DESCRIPTION | 4 +++- NAMESPACE | 2 ++ R/cmdstanr-package.R | 6 ++++++ R/model.R | 2 +- R/utils.R | 6 +++--- man/model-method-sample.Rd | 6 +++--- 6 files changed, 18 insertions(+), 8 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 3280baa0..e4daa324 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -45,9 +45,11 @@ Imports: processx (>= 3.5.0), R6 (>= 2.4.0), withr (>= 2.5.0), - rlang (>= 0.4.7) + rlang (>= 0.4.7), + progressr Suggests: bayesplot, + cli, fs, ggplot2, knitr (>= 1.37), diff --git a/NAMESPACE b/NAMESPACE index 9e12b908..7e68c647 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -50,4 +50,6 @@ export(write_stan_json) export(write_stan_tempfile) import(R6) importFrom(posterior,as_draws) +importFrom(progressr,handler_cli) +importFrom(progressr,handlers) importFrom(stats,aggregate) diff --git a/R/cmdstanr-package.R b/R/cmdstanr-package.R index 804276c3..f955b645 100644 --- a/R/cmdstanr-package.R +++ b/R/cmdstanr-package.R @@ -35,4 +35,10 @@ #' "_PACKAGE" +## usethis namespace: start +#' @importFrom progressr handlers +#' @importFrom progressr handler_cli +## usethis namespace: end +NULL + if (getRversion() >= "2.15.1") utils::globalVariables(c("self", "private", "super")) diff --git a/R/model.R b/R/model.R index 22448538..5a91214b 100644 --- a/R/model.R +++ b/R/model.R @@ -1121,7 +1121,7 @@ CmdStanModel$set("public", name = "format", value = format) #' responsible for registering a handler to display the progress bar. A #' default handler, using the `cli` package, can be registered via the #' `cmdstanr::register_default_progress_handler()`. Default: FALSE. -#' @param suppress_iteration_messages: Suppress CmdStan output lines reporting +#' @param suppress_iteration_messages Suppress CmdStan output lines reporting #' iterations, intended for use with the `show_progress_bar` argument. Defaults #' to the value of `show_progress_bar`. #' @param cores,num_cores,num_chains,num_warmup,num_samples,save_extra_diagnostics,max_depth,stepsize,validate_csv diff --git a/R/utils.R b/R/utils.R index 7db2c1c8..8c4d89c2 100644 --- a/R/utils.R +++ b/R/utils.R @@ -1086,18 +1086,18 @@ expose_stan_functions <- function(function_env, global = FALSE, verbose = FALSE) #' register_default_progress_handler <- function(verbose=TRUE) { # Require both the progressr and cli packages. - if(requireNamespace("progressr", quietly = TRUE) & requireNamespace("cli", quietly = TRUE)) { + if(requireNamespace("progressr", quietly = TRUE) && requireNamespace("cli", quietly = TRUE)) { progressr::handlers(global=TRUE) progressr::handlers("cli") # Progress bar options - options( cli.spinner = "moon", + options(cli.spinner = "moon", cli.progress_show_after = 0, cli.progress_clear = FALSE ) # Default informative progress output for sampling - progressr::handlers( progressr::handler_cli( + progressr::handlers(progressr::handler_cli( format = "{cli::pb_spin} Progress: |{cli::pb_bar}| {cli::pb_current}/{cli::pb_total} | {cli::pb_percent} | ETA: {cli::pb_eta}", clear = FALSE )) diff --git a/man/model-method-sample.Rd b/man/model-method-sample.Rd index a25ebaf9..7bf069d6 100644 --- a/man/model-method-sample.Rd +++ b/man/model-method-sample.Rd @@ -311,11 +311,11 @@ responsible for registering a handler to display the progress bar. A default handler, using the \code{cli} package, can be registered via the \code{cmdstanr::register_default_progress_handler()}. Default: FALSE.} -\item{cores, num_cores, num_chains, num_warmup, num_samples, save_extra_diagnostics, max_depth, stepsize, validate_csv}{Deprecated and will be removed in a future release.} - -\item{suppress_iteration_messages:}{Suppress CmdStan output lines reporting +\item{suppress_iteration_messages}{Suppress CmdStan output lines reporting iterations, intended for use with the \code{show_progress_bar} argument. Defaults to the value of \code{show_progress_bar}.} + +\item{cores, num_cores, num_chains, num_warmup, num_samples, save_extra_diagnostics, max_depth, stepsize, validate_csv}{Deprecated and will be removed in a future release.} } \value{ A \code{\link{CmdStanMCMC}} object. From 06e2c188e01f8e8ba33400944d047598b681d7f5 Mon Sep 17 00:00:00 2001 From: VisruthSK <67435125+VisruthSK@users.noreply.github.com> Date: Sun, 15 Feb 2026 13:07:13 -0800 Subject: [PATCH 11/17] Moved progressr to Suggests [ci skip] --- DESCRIPTION | 1 + 1 file changed, 1 insertion(+) diff --git a/DESCRIPTION b/DESCRIPTION index e4daa324..d883547a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -54,6 +54,7 @@ Suggests: ggplot2, knitr (>= 1.37), loo (>= 2.0.0), + progressr, qs2, rmarkdown, testthat (>= 2.1.0), From 6ea01d9f03fd0890f00b86d60967d1cd9a8afad9 Mon Sep 17 00:00:00 2001 From: VisruthSK <67435125+VisruthSK@users.noreply.github.com> Date: Sun, 15 Feb 2026 13:09:12 -0800 Subject: [PATCH 12/17] Proper move progressr to Suggests --- DESCRIPTION | 3 +-- NAMESPACE | 2 -- R/cmdstanr-package.R | 6 ------ 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index d883547a..2b422df5 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -45,8 +45,7 @@ Imports: processx (>= 3.5.0), R6 (>= 2.4.0), withr (>= 2.5.0), - rlang (>= 0.4.7), - progressr + rlang (>= 0.4.7) Suggests: bayesplot, cli, diff --git a/NAMESPACE b/NAMESPACE index 7e68c647..9e12b908 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -50,6 +50,4 @@ export(write_stan_json) export(write_stan_tempfile) import(R6) importFrom(posterior,as_draws) -importFrom(progressr,handler_cli) -importFrom(progressr,handlers) importFrom(stats,aggregate) diff --git a/R/cmdstanr-package.R b/R/cmdstanr-package.R index f955b645..804276c3 100644 --- a/R/cmdstanr-package.R +++ b/R/cmdstanr-package.R @@ -35,10 +35,4 @@ #' "_PACKAGE" -## usethis namespace: start -#' @importFrom progressr handlers -#' @importFrom progressr handler_cli -## usethis namespace: end -NULL - if (getRversion() >= "2.15.1") utils::globalVariables(c("self", "private", "super")) From d0d0eb1156e48f7768853f15f4a4635baffd92ee Mon Sep 17 00:00:00 2001 From: jgabry Date: Thu, 26 Feb 2026 18:42:53 -0700 Subject: [PATCH 13/17] minor doc edits --- R/model.R | 12 ++++++------ R/utils.R | 11 ++++++++--- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/R/model.R b/R/model.R index 24ad305d..a29559d1 100644 --- a/R/model.R +++ b/R/model.R @@ -1094,14 +1094,14 @@ CmdStanModel$set("public", name = "format", value = format) #' #' @template model-common-args #' @template model-sample-args -#' @param show_progress_bar (logical). If TRUE, registers a progress bar to +#' @param show_progress_bar (logical). When `TRUE`, registers a progress bar to #' display sampling progress via the `progressr` framework. The user is #' responsible for registering a handler to display the progress bar. A -#' default handler, using the `cli` package, can be registered via the -#' `cmdstanr::register_default_progress_handler()`. Default: FALSE. -#' @param suppress_iteration_messages Suppress CmdStan output lines reporting -#' iterations, intended for use with the `show_progress_bar` argument. Defaults -#' to the value of `show_progress_bar`. +#' default handler, using the `cli` package, can be registered via +#' [register_default_progress_handler()]. The default is `FALSE`. +#' @param suppress_iteration_messages (logical) When `TRUE`, suppresses CmdStan +#' output lines reporting iterations, intended for use with the +#' `show_progress_bar` argument. Defaults to the value of `show_progress_bar`. #' #' @return A [`CmdStanMCMC`] object. #' diff --git a/R/utils.R b/R/utils.R index 49f1c109..331fe875 100644 --- a/R/utils.R +++ b/R/utils.R @@ -1047,16 +1047,21 @@ expose_stan_functions <- function(function_env, global = FALSE, verbose = FALSE) invisible(NULL) } + + +# Progress bar helpers --------------------------------------------------- + #' Register a default progress bar handler for sampling #' #' Create a default progress bar for CmdStan sampling operations, and register -#' it as the default global handler for progressr updates. Requires `progressr` -#' for the progress framework, and `cli` for the default progress bar handler. +#' it as the default global handler for `progressr` updates. Requires +#' `progressr` for the progress framework, and `cli` for the default progress +#' bar handler. #' #' @export #' #' @param verbose (logical) Report creation of progress bar to stdout? -#' The default is `TRUE`. +#' The default is `TRUE`. #' register_default_progress_handler <- function(verbose=TRUE) { # Require both the progressr and cli packages. From 27b301b25be9d00f02ce5cefab0c811911b54928 Mon Sep 17 00:00:00 2001 From: jgabry Date: Thu, 26 Feb 2026 18:50:40 -0700 Subject: [PATCH 14/17] Error instead of warning if no progressr or cli --- R/model.R | 32 +++++++++++++++----------------- R/utils.R | 47 +++++++++++++++++++++++++---------------------- 2 files changed, 40 insertions(+), 39 deletions(-) diff --git a/R/model.R b/R/model.R index a29559d1..073fac9b 100644 --- a/R/model.R +++ b/R/model.R @@ -1158,29 +1158,27 @@ sample <- function(data = NULL, # Pass default value for refresh progress_bar <- NULL if (show_progress_bar) { - if(requireNamespace("progressr", quietly = TRUE)) { + if (!requireNamespace("progressr", quietly = TRUE)) { + stop("Please install the 'progressr' package to enable a progress bar. ", call. = FALSE) + } - # progressr only supports single-line progress bars at time of writing, - # so all chains must be combined into a single process bar. + # progressr only supports single-line progress bars at time of writing, + # so all chains must be combined into a single process bar. - # Calculate a total number of steps for progress as - # (chains*(iter_warmup+iter_sampling)). - # We will update the progress bar by 'refresh' steps each time. + # Calculate a total number of steps for progress as + # (chains*(iter_warmup+iter_sampling)). + # We will update the progress bar by 'refresh' steps each time. - # As all the arguments to CmdStan can be NULL, we need to reproduce the - # defaults here manually. + # As all the arguments to CmdStan can be NULL, we need to reproduce the + # defaults here manually. - n_samples <- ifelse(is.null(iter_sampling), 1000, iter_sampling) - n_warmup <- ifelse(is.null(iter_warmup), 1000, iter_warmup) - n_chains <- ifelse(is.null(chains), 1, chains) - n_steps <- (n_chains*(n_samples+n_warmup)) + n_samples <- ifelse(is.null(iter_sampling), 1000, iter_sampling) + n_warmup <- ifelse(is.null(iter_warmup), 1000, iter_warmup) + n_chains <- ifelse(is.null(chains), 1, chains) + n_steps <- (n_chains*(n_samples+n_warmup)) - progress_bar <- progressr::progressor(steps=n_steps, auto_finish=TRUE) + progress_bar <- progressr::progressor(steps=n_steps, auto_finish=TRUE) - } - else { - warning("'show_progress_bar=TRUE' requires the 'progressr' package. Please install 'progressr'.") - } } procs <- CmdStanMCMCProcs$new( num_procs = checkmate::assert_integerish(chains, lower = 1, len = 1), diff --git a/R/utils.R b/R/utils.R index 331fe875..6b8ce94b 100644 --- a/R/utils.R +++ b/R/utils.R @@ -1063,28 +1063,31 @@ expose_stan_functions <- function(function_env, global = FALSE, verbose = FALSE) #' @param verbose (logical) Report creation of progress bar to stdout? #' The default is `TRUE`. #' -register_default_progress_handler <- function(verbose=TRUE) { - # Require both the progressr and cli packages. - if(requireNamespace("progressr", quietly = TRUE) && requireNamespace("cli", quietly = TRUE)) { - - progressr::handlers(global=TRUE) - progressr::handlers("cli") - - # Progress bar options - options(cli.spinner = "moon", - cli.progress_show_after = 0, - cli.progress_clear = FALSE ) - - # Default informative progress output for sampling - progressr::handlers(progressr::handler_cli( - format = "{cli::pb_spin} Progress: |{cli::pb_bar}| {cli::pb_current}/{cli::pb_total} | {cli::pb_percent} | ETA: {cli::pb_eta}", - clear = FALSE - )) - if(verbose) { - message("Default progress bar registered.") - } - } else { - warning("The 'progressr' library is required to enable a progress bar. The default progress bar uses the 'cli' library.") +register_default_progress_handler <- function(verbose = TRUE) { + if (!requireNamespace("progressr", quietly = TRUE)) { + stop("Please install the 'progressr' package to enable a progress bar. ", call. = FALSE) + } + if (!requireNamespace("cli", quietly = TRUE)) { + stop("Please install the 'cli' package to use the default progress bar.", call. = FALSE) + } + progressr::handlers(global = TRUE) + progressr::handlers("cli") + + options( + cli.spinner = "moon", + cli.progress_show_after = 0, + cli.progress_clear = FALSE + ) + + # Default informative progress output for sampling + progressr::handlers( + progressr::handler_cli( + format = "{cli::pb_spin} Progress: |{cli::pb_bar}| {cli::pb_current}/{cli::pb_total} | {cli::pb_percent} | ETA: {cli::pb_eta}", + clear = FALSE + ) + ) + if (verbose) { + message("Default progress bar registered.") } invisible(NULL) } From 320fa9e8b5a937db91e0ac6291cc53e6c2c1a364 Mon Sep 17 00:00:00 2001 From: jgabry Date: Thu, 26 Feb 2026 19:00:48 -0700 Subject: [PATCH 15/17] Use internal %||% function --- R/model.R | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/R/model.R b/R/model.R index 073fac9b..bb895d25 100644 --- a/R/model.R +++ b/R/model.R @@ -1154,31 +1154,23 @@ sample <- function(data = NULL, if (fixed_param) { save_warmup <- FALSE } - # Check for and create progressr::progressor object for progress reporting, if required. - # Pass default value for refresh + progress_bar <- NULL if (show_progress_bar) { if (!requireNamespace("progressr", quietly = TRUE)) { - stop("Please install the 'progressr' package to enable a progress bar. ", call. = FALSE) + stop("Please install the 'progressr' package to enable a progress bar. ", + call. = FALSE) } - - # progressr only supports single-line progress bars at time of writing, - # so all chains must be combined into a single process bar. - - # Calculate a total number of steps for progress as - # (chains*(iter_warmup+iter_sampling)). - # We will update the progress bar by 'refresh' steps each time. - - # As all the arguments to CmdStan can be NULL, we need to reproduce the - # defaults here manually. - - n_samples <- ifelse(is.null(iter_sampling), 1000, iter_sampling) - n_warmup <- ifelse(is.null(iter_warmup), 1000, iter_warmup) - n_chains <- ifelse(is.null(chains), 1, chains) - n_steps <- (n_chains*(n_samples+n_warmup)) - + # - progressr only supports single-line progress bars at time of writing, + # so all chains must be combined into a single process bar. + # - The total number of steps for progress is the total number of iterations + # (including warmup) across all chains. + # - We will update the progress bar by 'refresh' steps each time. + # - As 'iter_sampling' and 'iter_warmup' can be NULL, we need to reproduce the + # defaults here manually. + chains <- checkmate::assert_integerish(chains, lower = 1, len = 1) + n_steps <- chains * (iter_sampling %||% 1000 + iter_warmup %||% 1000) progress_bar <- progressr::progressor(steps=n_steps, auto_finish=TRUE) - } procs <- CmdStanMCMCProcs$new( num_procs = checkmate::assert_integerish(chains, lower = 1, len = 1), From 640961efb0d5d8aeed171ed9623e7cfe4ca0702d Mon Sep 17 00:00:00 2001 From: jgabry Date: Thu, 26 Feb 2026 19:02:17 -0700 Subject: [PATCH 16/17] regenerate Rd --- man/model-method-sample.Rd | 12 ++++++------ man/register_default_progress_handler.Rd | 5 +++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/man/model-method-sample.Rd b/man/model-method-sample.Rd index f5892516..17cbcca2 100644 --- a/man/model-method-sample.Rd +++ b/man/model-method-sample.Rd @@ -296,15 +296,15 @@ with argument \code{"output save_config=1"} to save a json file which contains the argument tree and extra information (equivalent to the output CSV file header). This option is only available in CmdStan 2.34.0 and later.} -\item{show_progress_bar}{(logical). If TRUE, registers a progress bar to +\item{show_progress_bar}{(logical). When \code{TRUE}, registers a progress bar to display sampling progress via the \code{progressr} framework. The user is responsible for registering a handler to display the progress bar. A -default handler, using the \code{cli} package, can be registered via the -\code{cmdstanr::register_default_progress_handler()}. Default: FALSE.} +default handler, using the \code{cli} package, can be registered via +\code{\link[=register_default_progress_handler]{register_default_progress_handler()}}. The default is \code{FALSE}.} -\item{suppress_iteration_messages}{Suppress CmdStan output lines reporting -iterations, intended for use with the \code{show_progress_bar} argument. Defaults -to the value of \code{show_progress_bar}.} +\item{suppress_iteration_messages}{(logical) When \code{TRUE}, suppresses CmdStan +output lines reporting iterations, intended for use with the +\code{show_progress_bar} argument. Defaults to the value of \code{show_progress_bar}.} } \value{ A \code{\link{CmdStanMCMC}} object. diff --git a/man/register_default_progress_handler.Rd b/man/register_default_progress_handler.Rd index 28709ade..45867454 100644 --- a/man/register_default_progress_handler.Rd +++ b/man/register_default_progress_handler.Rd @@ -12,6 +12,7 @@ The default is \code{TRUE}.} } \description{ Create a default progress bar for CmdStan sampling operations, and register -it as the default global handler for progressr updates. Requires \code{progressr} -for the progress framework, and \code{cli} for the default progress bar handler. +it as the default global handler for \code{progressr} updates. Requires +\code{progressr} for the progress framework, and \code{cli} for the default progress +bar handler. } From fa4ad1066029f9b4e2b4efa29c294774c6a5f811 Mon Sep 17 00:00:00 2001 From: jgabry Date: Fri, 27 Feb 2026 13:54:38 -0700 Subject: [PATCH 17/17] minor linting to match cmdstanr style --- R/run.R | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/R/run.R b/R/run.R index 2bbb0482..c1f226ed 100644 --- a/R/run.R +++ b/R/run.R @@ -512,8 +512,8 @@ check_target_exe <- function(exe) { procs$check_finished() } # Ensure at this point that any created progress bar is closed. - if(!is.null(private$progress_bar_)){ - private$progress_bar_(type="finish") + if (!is.null(private$progress_bar_)) { + private$progress_bar_(type = "finish") } procs$set_total_time(as.double((Sys.time() - start_time), units = "secs")) procs$report_time() @@ -731,7 +731,6 @@ CmdStanProcs <- R6::R6Class( } else { private$iter_sampling_ <- as.integer(iter_sampling) } - if (is.null(parallel_procs)) { private$parallel_procs_ <- private$num_procs_ } else { @@ -749,22 +748,21 @@ CmdStanProcs <- R6::R6Class( private$show_stderr_messages_ <- show_stderr_messages private$show_stdout_messages_ <- show_stdout_messages private$progress_bar_ <- progress_bar - + # Defaults when enabling the progress bar: # - If 'progress_bar' is set, suppress iteration messages; # - if `progress_bar` is unset, do not suppress iteration messages; # - if 'suppress_iteration_messages' is set explicitly, honour that setting. - if(is.null(progress_bar)) { + if (is.null(progress_bar)) { private$suppress_iteration_messages_ <- FALSE } else { private$suppress_iteration_messages_ <- TRUE } - if(!is.null(suppress_iteration_messages)) { + if (!is.null(suppress_iteration_messages)) { private$suppress_iteration_messages_ <- suppress_iteration_messages } - if(is.null(refresh)) { - # Default to Stan default of 100 if refresh not set explicitly. + if (is.null(refresh)) { private$refresh_ <- 100 } else { private$refresh_ <- refresh @@ -1113,13 +1111,13 @@ CmdStanMCMCProcs <- R6::R6Class( || grepl("stancflags", line, fixed = TRUE)) { ignore_line <- TRUE } - # Update progress bar + # Update progress bar if (!ignore_line && !is.null(private$progress_bar_)) { # Pass the current output line to the progress bar as a message, # but only update the progress bar if the current line is an # iteration message. progress_amount <- 0 - if(grepl("Iteration:", line, perl = TRUE)) { + if (grepl("Iteration:", line, perl = TRUE)) { # Calculating the amount by which to increment the progress bar # is more complicated than it initially seems, due to occasional # extra or awkward iteration reporting messages when starting @@ -1127,36 +1125,36 @@ CmdStanMCMCProcs <- R6::R6Class( # sampling where the number of samples is not a multiple of the # refresh_rate. - # Strategy: + # Strategy: # If the line's iteration value is divisible by refresh_rate, or # is the final sampling step, update the progress bar by # refresh_rate. - + # Additionally, when moving from warmup to sampling, iterations # are reported starting from a baseline of the number of warmup # iterations. (For example, if refresh_rate is 12 and iter_warmup # is 100, the first reported iteration for sampling will be 112, # not 108.) - + # Get the current iteration count. # Subtract iter_warmup if greater than that. - iter_current <- as.numeric(gsub( ".*Iteration:\\s*([0-9]+) \\/.*", "\\1", line, perl=TRUE )) - if( iter_current > private$iter_warmup_ ) { + iter_current <- as.numeric(gsub(".*Iteration:\\s*([0-9]+) \\/.*", "\\1", line, perl = TRUE)) + if (iter_current > private$iter_warmup_) { iter_current <- iter_current - private$iter_warmup_ } # Update progress bar if the iteration is a multiple of the # refresh rate, or is the final sampling iteration. - if(((iter_current %% private$refresh_) == 0) | - iter_current == private$iter_warmup_ + private$iter_sampling_) { + if (((iter_current %% private$refresh_) == 0) || + iter_current == private$iter_warmup_ + private$iter_sampling_) { progress_amount <- private$refresh_ - } + } } - private$progress_bar_(amount=progress_amount, message=line) + private$progress_bar_(amount = progress_amount, message = line) } # Allow suppression of iteration messages if (private$suppress_iteration_messages_) { - if(grepl("Iteration:", line, perl = TRUE)) { + if (grepl("Iteration:", line, perl = TRUE)) { ignore_line <- TRUE } }