Skip to content

Remove deferred compilation and $compile(); add standalone file operations #1256

Description

@jgabry

Design note: #1254.

cmdstan_model(compile = FALSE) and the $compile() method both go. Options become one-shot: cmdstan_model() is the only place a build is configured, and every call states what it wants.

Current usage to migrate: 96 compile = FALSE in tests, 10 in R/, 9 in man/, 5 in vignettes.

Why removing $compile() rather than narrowing it

Once deferred compilation is gone it has no unique purpose — cmdstan_model(file, force_recompile = TRUE, ...) covers every remaining use. A $compile() that instead rebuilt "as recorded" would have to replay build arguments from a file, which needs a strictly validated record schema before it could be safe.

Nothing structural blocks removal: fits do not hold model references (R/fit.R:20-26 copies the model-methods environment rather than pointing at the object).

This must ship together with #1255. If $compile() is removed before configuration mismatches trigger rebuilds, an existing unthreaded executable is still reused under today's logic while the only escape route is gone — which breaks the central promise of the new API in the window between them.

Standalone replacements

The introspection use cases compile = FALSE served are better handled by functions that never needed a model object. Names follow cmdstanpy where a counterpart exists, and cmdstanr's existing write_stan_file():

compile_stan_file(file, include_paths = NULL, cpp_options = NULL, stanc_options = NULL, ...)  # -> exe path
format_stan_file(file, include_paths = NULL, ...)
check_syntax_stan_file(file, include_paths = NULL, ...)
stan_variables(file, include_paths = NULL, ...)
stan_build_info(exe_file)

Every one that hands a source file to stanc takes include_paths (#1254 §8, "Every function that hands a source file to stanc takes include_paths"). For format_stan_file() that is correctness rather than symmetry: $format() reads self$include_paths() internally today (R/model.R:1252), so a standalone with no object to read from cannot format any program containing #include at all.

compile_stan_file() and format_stan_file() match cmdstanpy exactly. model_variables() at R/model.R:2657 is already this shape internally.

compile_stan_file() is exported, sharing one implementation

An earlier version of this issue said to export it only if a consumer committed to it. That was the wrong bar — the argument is parity, not demand: cmdstanpy already has compile_stan_file, and having format_stan_file() and check_syntax_stan_file() public while the compile step is not is arbitrary. With compile = FALSE gone there would otherwise be no way to build without constructing an R6 object.

One implementation, two entry points, so nothing is duplicated:

compile_impl(stan_file, cpp_options, stanc_options, include_paths,
             user_header, pedantic, dir, force_recompile, quiet, dry_run)
    -> list(path =, record =, src_info =, hpp_code =)

compile_stan_file(...)   # exported: compile_impl(...)$path
cmdstan_model(...)       # exported: R6 object built from all four

This is a lift of today's $compile() rather than a rewrite — the stanc and make invocation moves unchanged, and 12 of its 31 private$ touches are the precompile_* fields this issue deletes anyway.

force_recompile keeps cmdstanr's spelling rather than cmdstanpy's force: matching the function name is what buys cross-implementation teachability, matching every argument at the cost of internal consistency is not.

Where cmdstanpy already has a name we copy it; where it does not, we pick one and they can copy it if they add a counterpart (#1254 §8). The two APIs are taught together, so parity matters, but nothing here waits on a joint naming decision.

stan_variables() is the one name to revisit before it ships. metadata()$stan_variables already exists on fit objects (R/csv.R:362) and means the variable names in the output rather than the declarations in the program. That is not a collision in R and not a reason to hold the design; the name is provisional and the decision belongs with the implementation.

Consequences

  • Closes $check_syntax() and $format() inherit stanc options meant for compilation #1252. Its premise — stanc options meant for compilation leaking into $check_syntax() and $format() — disappears with the precompile store.
  • Removes precompile_stanc_options_, precompile_include_paths_, precompile_cpp_options_, the %||% include-path baseline and include_paths_dirty_ (R/model.R:642-656).
  • dry_run demotes to internal. Its documentation says "Used to speedup tests" (R/model.R:558-559); 22 test uses, zero vignette uses. It is what makes migrating the 96 compile = FALSE sites affordable.
  • $format() gets a standalone plus a method wrapper. It must stop refreshing stan_code_/variables_ (R/model.R:1309-1311) — formatting makes the object stale rather than updating it.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions