Skip to content

Make cpp_options and stanc_options persistent across compile() calls for 1.0? #1248

Description

@jgabry

Written with the assistance of both Claude and Codex based on my prompting

Problem

cpp_options and stanc_options supplied to cmdstan_model() or
$compile() are retained only until the first successful compilation consumes
them. A later $compile() call on the same object uses empty option lists unless
the options are supplied again.

This differs from include_paths and user_header, which are persistent model
configuration: when omitted from a later $compile() call, their most recently
supplied values are reused. The distinction is encoded as an explicit
specification in tests/testthat/test-model-compile.R (the tests beginning
"successful compilation consumes one-shot cpp and stanc options" and
"$compile() doesn't reuse cpp and stanc options supplied to
cmdstan_model()"), but no user-facing or architectural reason for the opposite
lifecycle is recorded.

For example, this currently compiles the second time without threading:

mod <- cmdstan_model(stan_file, compile = FALSE)
mod$compile(
  cpp_options = list(stan_threads = TRUE),
  force_recompile = TRUE
)
mod$compile(force_recompile = TRUE)

This is especially relevant to #1238. Once CmdStanR records build provenance
and automatically rebuilds when desired configuration differs from an artifact,
an omitted option must have an intentional meaning. Treating omission as an
empty configuration would make a bare $compile() remove options from the
previous build.

Proposed 1.0 behavior

Treat C++ and stanc options as persistent desired build configuration, like
include paths and the user header:

  • Omitting cpp_options or stanc_options reuses the most recently configured
    value.
  • Supplying a nonempty list replaces the corresponding configuration; it does
    not patch or merge with the previous list.
  • Explicit cpp_options = list() or stanc_options = list() clears the
    corresponding configuration.
  • A NULL element inside cpp_options, such as
    list(stan_threads = NULL), retains its current meaning: it emits an empty
    STAN_THREADS= assignment and overrides make/local.
  • The desired configuration is updated before compilation and survives a failed
    build, while the installed artifact's recorded options change only after a
    successful executable installation.

R can distinguish omission from an explicitly supplied empty list with
missing(), so this does not require a new user-facing argument.

Internally, this would also remove the need for precompile_cpp_options_ and
precompile_stanc_options_: the fields currently exist to hold constructor
options until the one compilation that consumes them. It supports the state
model proposed in #1238: persistent desired configuration plus a committed
artifact snapshot.

Compatibility and scope

This is a breaking change to repeated $compile() calls on the same
CmdStanModel, and the existing tests intentionally specify the current
one-shot behavior. It should therefore be decided deliberately for 1.0 rather
than folded quietly into #1238 or described as a bug fix.

The ordinary cmdstan_model() path creates a fresh object and is unaffected.
The changed behavior requires multiple compilations through the same object,
which narrows the compatibility impact.

Related: #1234 established persistence for include_paths and user_header but
explicitly left the one-shot build-option behavior unchanged.

Questions to settle

  1. Should both cpp_options and stanc_options persist, or is there a reason
    for different lifecycles?
  2. Should an explicitly supplied list replace the previous configuration, as
    proposed, or merge with it?
  3. How should the pedantic convenience argument interact with persistent
    stanc_options?
  4. Should desired options be updated by dry_run = TRUE, consistently with
    other desired source configuration?

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    featureNew feature or request

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions