From 8ad2d1993018493aff07c781bc3373473275cd25 Mon Sep 17 00:00:00 2001 From: Joss Wright Date: Sun, 21 Dec 2025 22:55:25 +0000 Subject: [PATCH 01/12] 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 bb1427ed9..5cff615de 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 9202f0d4d..1240f8a84 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/12] 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 1240f8a84..5a69e8be6 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/12] 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 b157025d1..9e12b9082 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 8abc327ff..051ae0498 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 2558e6301..050b5fe7d 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/12] 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 5a69e8be6..15853292f 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/12] 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 15853292f..3afd8d757 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/12] 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 5cff615de..1c1e4c2de 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 3afd8d757..14f0d4609 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/12] 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 1c1e4c2de..f7a4caa3b 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 14f0d4609..0b137c50a 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/12] 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 f7a4caa3b..b3b7a6321 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 0b137c50a..2bbb04821 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 051ae0498..e268ca438 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 000000000..8138f04cd --- /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/12] 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 b3b7a6321..22448538c 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 e268ca438..7db2c1c85 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 050b5fe7d..a25ebaf9d 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 8138f04cd..28709ade2 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/12] 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 3280baa04..e4daa3247 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 9e12b9082..7e68c647a 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 804276c39..f955b6458 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 22448538c..5a91214b7 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 7db2c1c85..8c4d89c20 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 a25ebaf9d..7bf069d68 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/12] Moved progressr to Suggests [ci skip] --- DESCRIPTION | 1 + 1 file changed, 1 insertion(+) diff --git a/DESCRIPTION b/DESCRIPTION index e4daa3247..d883547ab 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/12] 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 d883547ab..2b422df53 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 7e68c647a..9e12b9082 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 f955b6458..804276c39 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"))