Skip to content

Commit 09f39de

Browse files
authored
Merge pull request #834 from stan-dev/2.0-min-cmdstan
Remove remaining deprecations, very old cmdstan version checks
2 parents 58ce09e + 6a44ec4 commit 09f39de

20 files changed

+303
-374
lines changed

cmdstanpy/cmdstan_args.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import numpy as np
1111
from numpy.random import default_rng
1212

13-
from cmdstanpy.utils import cmdstan_path, cmdstan_version_before, get_logger
13+
from cmdstanpy.utils import get_logger
1414

1515
OptionalPath = str | os.PathLike | None
1616

@@ -748,15 +748,6 @@ def validate(self) -> None:
748748
'Argument "sig_figs" must be an integer between 1 and 18,'
749749
' found {}'.format(self.sig_figs)
750750
)
751-
# TODO: remove at some future release
752-
if cmdstan_version_before(2, 25):
753-
self.sig_figs = None
754-
get_logger().warning(
755-
'Argument "sig_figs" invalid for CmdStan versions < 2.25, '
756-
'using version %s in directory %s',
757-
os.path.basename(cmdstan_path()),
758-
os.path.dirname(cmdstan_path()),
759-
)
760751

761752
if self.seed is None:
762753
rng = default_rng()

cmdstanpy/compilation.py

Lines changed: 8 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,7 @@
1313
from typing import Any, Iterable
1414

1515
from cmdstanpy.utils import get_logger
16-
from cmdstanpy.utils.cmdstan import (
17-
EXTENSION,
18-
cmdstan_path,
19-
cmdstan_version,
20-
cmdstan_version_before,
21-
stanc_path,
22-
)
16+
from cmdstanpy.utils.cmdstan import EXTENSION, cmdstan_path, stanc_path
2317
from cmdstanpy.utils.command import do_command
2418
from cmdstanpy.utils.filesystem import SanitizedOrTmpFilePath
2519

@@ -463,38 +457,15 @@ def format_stan_file(
463457
)
464458

465459
if canonicalize:
466-
if cmdstan_version_before(2, 29):
467-
if isinstance(canonicalize, bool):
468-
cmd.append('--print-canonical')
469-
else:
470-
raise ValueError(
471-
"Invalid arguments passed for current CmdStan"
472-
+ " version({})\n".format(
473-
cmdstan_version() or "Unknown"
474-
)
475-
+ "--canonicalize requires 2.29 or higher"
476-
)
460+
if isinstance(canonicalize, str):
461+
cmd.append('--canonicalize=' + canonicalize)
462+
elif isinstance(canonicalize, Iterable):
463+
cmd.append('--canonicalize=' + ','.join(canonicalize))
477464
else:
478-
if isinstance(canonicalize, str):
479-
cmd.append('--canonicalize=' + canonicalize)
480-
elif isinstance(canonicalize, Iterable):
481-
cmd.append('--canonicalize=' + ','.join(canonicalize))
482-
else:
483-
cmd.append('--print-canonical')
484-
485-
# before 2.29, having both --print-canonical
486-
# and --auto-format printed twice
487-
if not (cmdstan_version_before(2, 29) and canonicalize):
488-
cmd.append('--auto-format')
465+
cmd.append('--print-canonical')
489466

490-
if not cmdstan_version_before(2, 29):
491-
cmd.append(f'--max-line-length={max_line_length}')
492-
elif max_line_length != 78:
493-
raise ValueError(
494-
"Invalid arguments passed for current CmdStan version"
495-
+ " ({})\n".format(cmdstan_version() or "Unknown")
496-
+ "--max-line-length requires 2.29 or higher"
497-
)
467+
cmd.append('--auto-format')
468+
cmd.append(f'--max-line-length={max_line_length}')
498469

499470
out = subprocess.run(cmd, capture_output=True, text=True, check=True)
500471
if out.stderr:

cmdstanpy/install_cxx_toolchain.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ def get_toolchain_name() -> str:
236236
return ''
237237

238238

239-
# TODO(2.0): drop 3.5 support
239+
# TODO(2.0): consider something other than RTools
240240
def get_url(version: str) -> str:
241241
"""Return URL for toolchain."""
242242
url = ''

cmdstanpy/model.py

Lines changed: 48 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,16 @@
22

33
import io
44
import os
5-
import platform
65
import re
76
import shutil
87
import subprocess
98
import sys
109
import tempfile
1110
import threading
12-
from collections import OrderedDict
1311
from concurrent.futures import ThreadPoolExecutor
1412
from io import StringIO
1513
from multiprocessing import cpu_count
16-
from typing import Any, Callable, Mapping, Sequence, TypeVar
14+
from typing import Any, Callable, Mapping, Sequence
1715

1816
import numpy as np
1917
import pandas as pd
@@ -37,15 +35,12 @@
3735
CmdStanMLE,
3836
CmdStanPathfinder,
3937
CmdStanVB,
38+
PrevFit,
4039
RunSet,
4140
from_csv,
4241
)
43-
from cmdstanpy.utils import (
44-
cmdstan_path,
45-
cmdstan_version_before,
46-
do_command,
47-
get_logger,
48-
)
42+
from cmdstanpy.utils import do_command, get_logger
43+
from cmdstanpy.utils.cmdstan import cmdstan_version_before, windows_tbb_path
4944
from cmdstanpy.utils.filesystem import (
5045
temp_inits,
5146
temp_metrics,
@@ -56,7 +51,6 @@
5651
from . import progress as progbar
5752

5853
OptionalPath = str | os.PathLike | None
59-
Fit = TypeVar('Fit', CmdStanMCMC, CmdStanMLE, CmdStanVB)
6054

6155

6256
class CmdStanModel:
@@ -118,6 +112,8 @@ def __init__(
118112

119113
self._fixed_param = False
120114

115+
windows_tbb_path()
116+
121117
if exe_file is not None:
122118
self._exe_file = os.path.realpath(os.path.expanduser(exe_file))
123119
if not os.path.exists(self._exe_file):
@@ -164,33 +160,26 @@ def __init__(
164160
)
165161

166162
# try to detect models w/out parameters, needed for sampler
167-
if (not cmdstan_version_before(2, 27)) and cmdstan_version_before(
168-
2, 36
169-
):
163+
if cmdstan_version_before(2, 36):
170164
model_info = self.src_info()
171165
if 'parameters' in model_info:
172166
self._fixed_param |= len(model_info['parameters']) == 0
173167

174-
if platform.system() == 'Windows':
175-
try:
176-
do_command(['where.exe', 'tbb.dll'], fd_out=None)
177-
except RuntimeError:
178-
# Add tbb to the $PATH on Windows
179-
libtbb = os.environ.get('STAN_TBB')
180-
if libtbb is None:
181-
libtbb = os.path.join(
182-
cmdstan_path(), 'stan', 'lib', 'stan_math', 'lib', 'tbb'
183-
)
184-
get_logger().debug("Adding TBB (%s) to PATH", libtbb)
185-
os.environ['PATH'] = ';'.join(
186-
list(
187-
OrderedDict.fromkeys(
188-
[libtbb] + os.environ.get('PATH', '').split(';')
189-
)
190-
)
191-
)
192-
else:
193-
get_logger().debug("TBB already found in load path")
168+
# check CmdStan version compatibility
169+
exe_info = None
170+
try:
171+
exe_info = self.exe_info()
172+
# pylint: disable=broad-except
173+
except Exception as e:
174+
get_logger().warning(
175+
'Could not get exe info for model %s, error: %s',
176+
self._name,
177+
str(e),
178+
)
179+
if cmdstan_version_before(2, 35, exe_info):
180+
raise RuntimeError(
181+
"This version of CmdStanPy requires CmdStan 2.35 or higher."
182+
)
194183

195184
def __repr__(self) -> str:
196185
return (
@@ -238,7 +227,7 @@ def src_info(self) -> dict[str, Any]:
238227
If stanc is older than 2.27 or if the stan
239228
file cannot be found, returns an empty dictionary.
240229
"""
241-
if self.stan_file is None or cmdstan_version_before(2, 27):
230+
if self.stan_file is None:
242231
return {}
243232
return compilation.src_info(str(self.stan_file), self._stanc_options)
244233

@@ -404,12 +393,6 @@ def optimize(
404393
jacobian=jacobian,
405394
)
406395

407-
if jacobian and cmdstan_version_before(2, 32, self.exe_info()):
408-
raise ValueError(
409-
"Jacobian adjustment for optimization is only supported "
410-
"in CmdStan 2.32 and above."
411-
)
412-
413396
with (
414397
temp_single_json(data) as _data,
415398
temp_inits(inits, allow_multiple=False) as _inits,
@@ -734,34 +717,23 @@ def sample(
734717
if chains == 1:
735718
force_one_process_per_chain = True
736719

737-
if (
738-
force_one_process_per_chain is None
739-
and not cmdstan_version_before(2, 28, info_dict)
740-
and stan_threads == 'true'
741-
):
720+
if force_one_process_per_chain is None and stan_threads == 'true':
742721
one_process_per_chain = False
743722
num_threads = parallel_chains * num_threads
744723
parallel_procs = 1
745724
if force_one_process_per_chain is False:
746-
if not cmdstan_version_before(2, 28, info_dict):
747-
one_process_per_chain = False
748-
num_threads = parallel_chains * num_threads
749-
parallel_procs = 1
750-
if stan_threads == 'false':
751-
get_logger().warning(
752-
'Stan program not compiled for threading, '
753-
'process will run chains sequentially. '
754-
'For multi-chain parallelization, recompile '
755-
'the model with argument '
756-
'"cpp_options={\'STAN_THREADS\':\'TRUE\'}.'
757-
)
758-
else:
725+
one_process_per_chain = False
726+
num_threads = parallel_chains * num_threads
727+
parallel_procs = 1
728+
if stan_threads == 'false':
759729
get_logger().warning(
760-
'Installed version of CmdStan cannot multi-process '
761-
'chains, will run %d processes. '
762-
'Run "install_cmdstan" to upgrade to latest version.',
763-
chains,
730+
'Stan program not compiled for threading, '
731+
'process will run chains sequentially. '
732+
'For multi-chain parallelization, recompile '
733+
'the model with argument '
734+
'"cpp_options={\'STAN_THREADS\':\'TRUE\'}.'
764735
)
736+
765737
os.environ['STAN_NUM_THREADS'] = str(num_threads)
766738

767739
if chain_ids is None:
@@ -958,15 +930,15 @@ def sample(
958930
def generate_quantities(
959931
self,
960932
data: Mapping[str, Any] | str | os.PathLike | None = None,
961-
previous_fit: Fit | list[str] | None = None,
933+
previous_fit: PrevFit | list[str] | None = None,
962934
seed: int | None = None,
963935
gq_output_dir: OptionalPath = None,
964936
sig_figs: int | None = None,
965937
show_console: bool = False,
966938
refresh: int | None = None,
967939
time_fmt: str = "%Y%m%d%H%M%S",
968940
timeout: float | None = None,
969-
) -> CmdStanGQ[Fit]:
941+
) -> CmdStanGQ[PrevFit]:
970942
"""
971943
Run CmdStan's generate_quantities method which runs the generated
972944
quantities block of a model given an existing sample.
@@ -1032,7 +1004,16 @@ def generate_quantities(
10321004
:return: CmdStanGQ object
10331005
"""
10341006

1035-
if isinstance(previous_fit, (CmdStanMCMC, CmdStanMLE, CmdStanVB)):
1007+
if isinstance(
1008+
previous_fit,
1009+
(
1010+
CmdStanMCMC,
1011+
CmdStanMLE,
1012+
CmdStanVB,
1013+
CmdStanLaplace,
1014+
CmdStanPathfinder,
1015+
),
1016+
):
10361017
fit_object = previous_fit
10371018
fit_csv_files = previous_fit.runset.csv_files
10381019
elif isinstance(previous_fit, list):
@@ -1042,7 +1023,7 @@ def generate_quantities(
10421023
)
10431024
try:
10441025
fit_csv_files = previous_fit
1045-
fit_object: Fit = from_csv(fit_csv_files) # type: ignore
1026+
fit_object: PrevFit = from_csv(fit_csv_files) # type: ignore
10461027
except ValueError as e:
10471028
raise ValueError(
10481029
'Invalid sample from Stan CSV files, error:\n\t{}\n\t'
@@ -1064,11 +1045,6 @@ def generate_quantities(
10641045
'to generate additional quantities of interest.'
10651046
)
10661047
elif isinstance(fit_object, CmdStanMLE):
1067-
if cmdstan_version_before(2, 31):
1068-
raise RuntimeError(
1069-
"Method generate_quantities was not "
1070-
"available for non-HMC until CmdStan 2.31"
1071-
)
10721048
chains = 1
10731049
chain_ids = [1]
10741050
if fit_object._save_iterations:
@@ -1077,11 +1053,6 @@ def generate_quantities(
10771053
'to generate additional quantities of interest.'
10781054
)
10791055
else: # isinstance(fit_object, CmdStanVB)
1080-
if cmdstan_version_before(2, 31):
1081-
raise RuntimeError(
1082-
"Method generate_quantities was not "
1083-
"available for non-HMC until CmdStan 2.31"
1084-
)
10851056
chains = 1
10861057
chain_ids = [1]
10871058

@@ -1492,19 +1463,6 @@ def pathfinder(
14921463
"""
14931464

14941465
exe_info = self.exe_info()
1495-
if cmdstan_version_before(2, 33, exe_info):
1496-
raise ValueError(
1497-
"Method 'pathfinder' not available for CmdStan versions "
1498-
"before 2.33"
1499-
)
1500-
1501-
if (not psis_resample or not calculate_lp) and cmdstan_version_before(
1502-
2, 34, exe_info
1503-
):
1504-
raise ValueError(
1505-
"Arguments 'psis_resample' and 'calculate_lp' are only "
1506-
"available for CmdStan versions 2.34 and later"
1507-
)
15081466

15091467
if num_threads is not None:
15101468
if (
@@ -1613,11 +1571,6 @@ def log_prob(
16131571
unconstrained parameters of the model.
16141572
"""
16151573

1616-
if cmdstan_version_before(2, 31, self.exe_info()):
1617-
raise ValueError(
1618-
"Method 'log_prob' not available for CmdStan versions "
1619-
"before 2.31"
1620-
)
16211574
with (
16221575
temp_single_json(data) as _data,
16231576
temp_single_json(params) as _params,
@@ -1729,11 +1682,7 @@ def laplace_sample(
17291682
17301683
:return: A :class:`CmdStanLaplace` object.
17311684
"""
1732-
if cmdstan_version_before(2, 32, self.exe_info()):
1733-
raise ValueError(
1734-
"Method 'laplace_sample' not available for CmdStan versions "
1735-
"before 2.32"
1736-
)
1685+
17371686
if opt_args is not None and mode is not None:
17381687
raise ValueError(
17391688
"Cannot specify both 'opt_args' and 'mode' arguments"

cmdstanpy/stanfit/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
)
1414
from cmdstanpy.utils import check_sampler_csv, get_logger, stancsv
1515

16-
from .gq import CmdStanGQ
16+
from .gq import CmdStanGQ, PrevFit
1717
from .laplace import CmdStanLaplace
1818
from .mcmc import CmdStanMCMC
1919
from .metadata import InferenceMetadata
@@ -31,6 +31,7 @@
3131
"CmdStanGQ",
3232
"CmdStanLaplace",
3333
"CmdStanPathfinder",
34+
"PrevFit",
3435
]
3536

3637

0 commit comments

Comments
 (0)