Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
d3fcc52
Add initial basic ConfigInfo model
amas0 Jan 6, 2026
81d08ab
Add flattening and parsing logic for config JSON
amas0 Jan 30, 2026
b3b20eb
Update flatten_value_dict to clarify variable naming
amas0 Jan 31, 2026
a5f8094
Add PathfinderConfig
amas0 Jan 31, 2026
d49c62c
Add LaplaceConfig
amas0 Jan 31, 2026
503a970
Add VariationalConfig
amas0 Jan 31, 2026
08c6d0e
Add GeneratedQuantitiesConfig
amas0 Jan 31, 2026
63ca9a5
Bump actions/cache from 4 to 5
dependabot[bot] Dec 15, 2025
8f62f2e
Add first-pass CmdStanPathfinder rewrite
amas0 Apr 12, 2026
ca13a2e
Fixup Path conversion to string
amas0 Apr 16, 2026
c5a42db
Convert StanConfig to Generic for richer typing
amas0 Apr 16, 2026
610ad2f
Minor comment on config_file optionality
amas0 Apr 16, 2026
1e2f272
Refactor CmdStanLaplace
amas0 Apr 16, 2026
da04db4
Add default method_type for parse_config
amas0 Apr 16, 2026
8b1a39b
Refactor CmdStanMLE
amas0 Apr 18, 2026
3466024
Allow passing 'mode' directly to CmdStanLaplace.from_files
amas0 Apr 18, 2026
16eff3d
Refactor CmdStanVB to build from files
amas0 May 3, 2026
ef8cc74
Regenerate test output files for config.json compatibility
amas0 Jul 20, 2026
80f10d6
Remove config and metric files in sample test cleanup
amas0 Jul 21, 2026
f4b5c57
Update CmdStanMCMC to build from files
amas0 Jul 22, 2026
aa8ca89
Merge branch 'develop' into use-save-cmdstan-config
amas0 Jul 22, 2026
4f82501
Update CmdStanGQ to build from files
amas0 Jul 27, 2026
16a4e73
Source run configuration from config JSON, not CSV comments
amas0 Jul 27, 2026
78fc69f
Require config JSON in from_csv, drop CSV-comment fallback
amas0 Jul 27, 2026
8e529cc
Remove dead Stan CSV comment-parsing functions
amas0 Jul 27, 2026
3d7032d
Rename save_csvfiles and from_csv to save_output_files and from_outpu…
amas0 Jul 28, 2026
f1ec616
Bump minimum CmdStan version to 2.37
amas0 Aug 14, 2026
9c3b764
Name every output file explicitly when chains share a process
amas0 Aug 14, 2026
0833d8f
Simplify path handling and dispatch in from_output_files
amas0 Aug 14, 2026
fd60ea0
Order chains by id when reading output files
amas0 Aug 14, 2026
b411602
Read the shared run config when chains share a process
amas0 Aug 14, 2026
e62425e
Skip diagnostic and profile CSVs when reading a directory
amas0 Aug 14, 2026
8437240
Recover chain ids from the config when reading output files
amas0 Aug 14, 2026
0212413
Fix stanfit pickling with named run-config classes
amas0 Aug 24, 2026
ac9ddd2
Add shared StanFit base classes, port CmdStanPathfinder
amas0 Aug 24, 2026
bbcb0cf
Port CmdStanLaplace to the SingleFileFit base
amas0 Aug 24, 2026
8573f57
Port CmdStanVB to the SingleFileFit base
amas0 Aug 24, 2026
505623d
Port CmdStanMLE to the SingleFileFit base
amas0 Aug 24, 2026
eff860d
Keep draws() off fits whose rows are not all posterior draws
amas0 Aug 24, 2026
add7671
Add MultiChainFit base, port CmdStanGQ
amas0 Aug 25, 2026
28b1b3f
Port CmdStanMCMC to the MultiChainFit base
amas0 Aug 25, 2026
39aece0
Document inherited members of the stanfit classes
amas0 Aug 25, 2026
6d63994
Minor comment update for clarity
amas0 Aug 25, 2026
1f0e3d3
Take chain ids from filenames when reading output files
amas0 Aug 25, 2026
ced0bab
Simplify from_output_files edges and header-only CSV reads
amas0 Aug 25, 2026
98e52fa
Minor docs cleanup
amas0 Aug 25, 2026
e643667
Update CmdStanLaplace.from_file and tests
amas0 Aug 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmdstanpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def _cleanup_tmpdir() -> None:
CmdStanMLE,
CmdStanPathfinder,
CmdStanVB,
from_csv,
from_output_files,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unfortunate we don't have a release in the 1.x cycle that formally deprecated from_csv, we might want to keep it around as an alias as a result. Thoughts?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My approach with this PR was to specifically target a 2.0 release, which is why I opted for a clean compatibility break here. That being said an alias that replicates the previous experience of using from_csv probably wouldn't be an issue?

)
from .utils import (
cmdstan_path,
Expand Down Expand Up @@ -60,7 +60,7 @@ def _cleanup_tmpdir() -> None:
'CmdStanLaplace',
'CmdStanPathfinder',
'CmdStanModel',
'from_csv',
'from_output_files',
'write_stan_json',
'show_versions',
'rebuild_cmdstan',
Expand Down
109 changes: 68 additions & 41 deletions cmdstanpy/model.py

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General comment: I really like how the changes look in this file, to me it really motivates the rest of the changes to be able to have this all be nicer!

Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
CmdStanArgs,
GenerateQuantitiesArgs,
LaplaceArgs,
Method,
OptimizeArgs,
PathfinderArgs,
SamplerArgs,
Expand All @@ -37,8 +36,9 @@
CmdStanVB,
PrevFit,
RunSet,
from_csv,
from_output_files,
)
from cmdstanpy.stanfit.base import SingleFileFit
from cmdstanpy.utils import do_command, get_logger
from cmdstanpy.utils.cmdstan import cmdstan_version_before, windows_tbb_path
from cmdstanpy.utils.filesystem import (
Expand Down Expand Up @@ -175,12 +175,6 @@ def __init__(
user_header=user_header,
)

# try to detect models w/out parameters, needed for sampler
if cmdstan_version_before(2, 36):
model_info = self.src_info()
if 'parameters' in model_info:
self._fixed_param |= len(model_info['parameters']) == 0

# check CmdStan version compatibility
exe_info = None
try:
Expand All @@ -192,9 +186,9 @@ def __init__(
self._name,
str(e),
)
if cmdstan_version_before(2, 35, exe_info):
if cmdstan_version_before(2, 37, exe_info):
raise RuntimeError(
"This version of CmdStanPy requires CmdStan 2.35 or higher."
"This version of CmdStanPy requires CmdStan 2.37 or higher."
)

def __repr__(self) -> str:
Expand Down Expand Up @@ -434,16 +428,21 @@ def optimize(
)
runset.raise_for_timeouts()

if not runset._check_retcodes():
converged = runset._check_retcodes()
if not converged:
msg = "Error during optimization! Command '{}' failed: {}".format(
' '.join(runset.cmd(0)), runset.get_err_msgs()
)
if 'Line search failed' in msg and not require_converged:
get_logger().warning(msg)
else:
raise RuntimeError(msg)
mle = CmdStanMLE(runset)
return mle
return CmdStanMLE.from_files(
csv_file=runset.csv_files[0],
config_file=runset.config_files[0],
stdout_file=runset.stdout_files[0],
converged=converged,
)

# pylint: disable=too-many-arguments
def sample(
Expand Down Expand Up @@ -530,16 +529,16 @@ def sample(
:param parallel_chains: Number of processes to run in parallel. Must be
a positive integer. Defaults to :func:`multiprocessing.cpu_count`,
i.e., it will only run as many chains in parallel as there are
cores on the machine. Note that CmdStan 2.28 and higher can run
all chains in parallel providing that the model was compiled with
threading support.
cores on the machine. Note that CmdStan can run all chains in
parallel providing that the model was compiled with threading
support.

:param threads_per_chain: The number of threads to use in parallelized
sections within an MCMC chain (e.g., when using the Stan functions
``reduce_sum()`` or ``map_rect()``). This will only have an effect
if the model was compiled with threading support. For such models,
CmdStan version 2.28 and higher will run all chains in parallel
from within a single process. The total number of threads used
CmdStan will run all chains in parallel from within a single
process. The total number of threads used
will be ``parallel_chains * threads_per_chain``, where the default
value for parallel_chains is the number of cpus, not chains.

Expand Down Expand Up @@ -683,12 +682,12 @@ def sample(

:param force_one_process_per_chain: If ``True``, run multiple chains in
distinct processes regardless of model ability to run parallel
chains (CmdStan 2.28+ feature). If ``False``, always run multiple
chains in one process (does not check that this is valid).
chains. If ``False``, always run multiple chains in one process
(does not check that this is valid).

If None (Default): Check that CmdStan version is >=2.28, and that
model was compiled with STAN_THREADS=True, and utilize the
parallel chain functionality if those conditions are met.
If None (Default): Check that the model was compiled with
STAN_THREADS=True, and utilize the parallel chain functionality
if so.

:param timeout: Duration at which sampling times out in seconds.

Expand Down Expand Up @@ -945,7 +944,16 @@ def sample(
)
get_logger().warning(msg)

mcmc = CmdStanMCMC(runset)
mcmc = CmdStanMCMC.from_files(
csv_files=runset.csv_files,
config_files=runset.config_files,
metric_files=runset.metric_files or None,
stdout_files=runset.stdout_files,
diagnostic_files=runset.diagnostic_files or None,
profile_files=runset.profile_files or None,
chain_ids=runset.chain_ids,
sig_figs=runset._args.sig_figs,
)
return mcmc

def generate_quantities(
Expand Down Expand Up @@ -1040,15 +1048,20 @@ def generate_quantities(
),
):
fit_object = previous_fit
fit_csv_files = previous_fit.runset.csv_files
if isinstance(previous_fit, SingleFileFit):
fit_csv_files = [previous_fit.csv_file]
else:
fit_csv_files = previous_fit.csv_files
elif isinstance(previous_fit, list):
if len(previous_fit) < 1:
raise ValueError(
'Expecting list of Stan CSV files, found empty list'
)
try:
fit_csv_files = previous_fit
fit_object: PrevFit = from_csv(fit_csv_files) # type: ignore
fit_object: PrevFit = from_output_files( # type: ignore
fit_csv_files
)
except ValueError as e:
raise ValueError(
'Invalid sample from Stan CSV files, error:\n\t{}\n\t'
Expand All @@ -1072,7 +1085,7 @@ def generate_quantities(
elif isinstance(fit_object, CmdStanMLE):
chains = 1
chain_ids = [1]
if fit_object._save_iterations:
if fit_object.config.method_config.save_iterations:
get_logger().warning(
'MLE contains saved iterations which will be used '
'to generate additional quantities of interest.'
Expand Down Expand Up @@ -1126,7 +1139,13 @@ def generate_quantities(
' above output is unclear!'
)
raise RuntimeError(msg)
quantities = CmdStanGQ(runset=runset, previous_fit=fit_object)
quantities = CmdStanGQ.from_files(
csv_files=runset.csv_files,
config_files=runset.config_files,
previous_fit=fit_object,
stdout_files=runset.stdout_files,
chain_ids=runset.chain_ids,
)
return quantities

def variational(
Expand Down Expand Up @@ -1329,9 +1348,11 @@ def variational(
runset.get_err_msgs()
)
raise RuntimeError(msg)
# pylint: disable=invalid-name
vb = CmdStanVB(runset)
return vb
return CmdStanVB.from_files(
csv_file=runset.csv_files[0],
config_file=runset.config_files[0],
stdout_file=runset.stdout_files[0],
)

def pathfinder(
self,
Expand Down Expand Up @@ -1553,7 +1574,11 @@ def pathfinder(
' '.join(runset.cmd(0)), runset.get_err_msgs()
)
raise RuntimeError(msg)
return CmdStanPathfinder(runset)
return CmdStanPathfinder.from_files(
csv_file=runset.csv_files[0],
config_file=runset.config_files[0],
stdout_file=runset.stdout_files[0],
)

def log_prob(
self,
Expand Down Expand Up @@ -1735,28 +1760,25 @@ def laplace_sample(
"Consider supplying a mode or additional optimizer args"
) from e
elif not isinstance(mode, CmdStanMLE):
cmdstan_mode = from_csv(mode) # type: ignore # we check below
# we check the type below
cmdstan_mode = from_output_files(mode) # type: ignore
else:
cmdstan_mode = mode

if cmdstan_mode.runset.method != Method.OPTIMIZE:
if not isinstance(cmdstan_mode, CmdStanMLE):
raise ValueError(
"Mode must be a CmdStanMLE or a path to an optimize CSV"
)

mode_jacobian = (
cmdstan_mode.runset._args.method_args.jacobian # type: ignore
)
mode_jacobian = cmdstan_mode.config.method_config.jacobian
if mode_jacobian != jacobian:
raise ValueError(
"Jacobian argument to optimize and laplace must match!\n"
f"Laplace was run with jacobian={jacobian},\n"
f"but optimize was run with jacobian={mode_jacobian}"
)

laplace_args = LaplaceArgs(
cmdstan_mode.runset.csv_files[0], draws, jacobian
)
laplace_args = LaplaceArgs(cmdstan_mode.csv_file, draws, jacobian)

with temp_single_json(data) as _data:
args = CmdStanArgs(
Expand All @@ -1780,7 +1802,12 @@ def laplace_sample(
timeout=timeout,
)
runset.raise_for_timeouts()
return CmdStanLaplace(runset, cmdstan_mode)
return CmdStanLaplace.from_files(
csv_file=runset.csv_files[0],
config_file=runset.config_files[0],
stdout_file=runset.stdout_files[0],
mode=cmdstan_mode,
)

def _run_cmdstan(
self,
Expand Down
Loading
Loading