cmdstanpy 1.2.5__tar.gz → 1.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. {cmdstanpy-1.2.5/cmdstanpy.egg-info → cmdstanpy-1.3.1}/PKG-INFO +9 -6
  2. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy/__init__.py +4 -0
  3. cmdstanpy-1.3.1/cmdstanpy/_version.py +3 -0
  4. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy/cmdstan_args.py +41 -159
  5. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy/compilation.py +37 -99
  6. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy/install_cmdstan.py +12 -13
  7. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy/install_cxx_toolchain.py +10 -11
  8. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy/model.py +339 -328
  9. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy/progress.py +3 -2
  10. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy/stanfit/__init__.py +67 -58
  11. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy/stanfit/gq.py +59 -63
  12. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy/stanfit/laplace.py +52 -30
  13. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy/stanfit/mcmc.py +115 -101
  14. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy/stanfit/metadata.py +34 -6
  15. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy/stanfit/mle.py +52 -15
  16. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy/stanfit/pathfinder.py +18 -22
  17. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy/stanfit/runset.py +29 -23
  18. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy/stanfit/vb.py +66 -17
  19. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy/utils/__init__.py +9 -30
  20. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy/utils/cmdstan.py +28 -19
  21. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy/utils/command.py +5 -19
  22. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy/utils/data_munging.py +5 -3
  23. cmdstanpy-1.3.1/cmdstanpy/utils/deprecation.py +58 -0
  24. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy/utils/filesystem.py +39 -6
  25. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy/utils/json.py +1 -0
  26. cmdstanpy-1.3.1/cmdstanpy/utils/logging.py +62 -0
  27. cmdstanpy-1.3.1/cmdstanpy/utils/stancsv.py +670 -0
  28. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1/cmdstanpy.egg-info}/PKG-INFO +9 -6
  29. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy.egg-info/SOURCES.txt +4 -0
  30. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy.egg-info/requires.txt +5 -3
  31. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/pyproject.toml +9 -5
  32. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/test/test_cmdstan_args.py +7 -16
  33. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/test/test_compiler_opts.py +0 -106
  34. cmdstanpy-1.3.1/test/test_deprecations.py +140 -0
  35. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/test/test_generate_quantities.py +4 -4
  36. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/test/test_laplace.py +44 -0
  37. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/test/test_log_prob.py +1 -1
  38. cmdstanpy-1.3.1/test/test_logging.py +68 -0
  39. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/test/test_metadata.py +0 -1
  40. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/test/test_model.py +102 -144
  41. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/test/test_optimize.py +40 -13
  42. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/test/test_pathfinder.py +4 -4
  43. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/test/test_sample.py +161 -57
  44. cmdstanpy-1.3.1/test/test_stancsv.py +675 -0
  45. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/test/test_utils.py +38 -25
  46. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/test/test_variational.py +44 -0
  47. cmdstanpy-1.2.5/cmdstanpy/_version.py +0 -3
  48. cmdstanpy-1.2.5/cmdstanpy/utils/logging.py +0 -25
  49. cmdstanpy-1.2.5/cmdstanpy/utils/stancsv.py +0 -486
  50. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/LICENSE.md +0 -0
  51. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/MANIFEST.in +0 -0
  52. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/README.md +0 -0
  53. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy/py.typed +0 -0
  54. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy.egg-info/dependency_links.txt +0 -0
  55. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy.egg-info/entry_points.txt +0 -0
  56. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/cmdstanpy.egg-info/top_level.txt +0 -0
  57. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/setup.cfg +0 -0
  58. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/test/test_compliance.py +0 -0
  59. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/test/test_cxx_installation.py +0 -0
  60. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/test/test_install_cmdstan.py +0 -0
  61. {cmdstanpy-1.2.5 → cmdstanpy-1.3.1}/test/test_runset.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: cmdstanpy
3
- Version: 1.2.5
3
+ Version: 1.3.1
4
4
  Summary: Python interface to CmdStan
5
5
  Author: Stan Dev Team
6
6
  License: BSD-3-Clause
@@ -14,7 +14,7 @@ Classifier: Intended Audience :: Science/Research
14
14
  Classifier: Natural Language :: English
15
15
  Classifier: Programming Language :: Python
16
16
  Classifier: Topic :: Scientific/Engineering :: Information Analysis
17
- Requires-Python: >=3.8
17
+ Requires-Python: >=3.9
18
18
  Description-Content-Type: text/markdown
19
19
  License-File: LICENSE.md
20
20
  Requires-Dist: pandas
@@ -23,6 +23,7 @@ Requires-Dist: tqdm
23
23
  Requires-Dist: stanio<2.0.0,>=0.4.0
24
24
  Provides-Extra: all
25
25
  Requires-Dist: xarray; extra == "all"
26
+ Requires-Dist: polars>=1.8.2; extra == "all"
26
27
  Provides-Extra: test
27
28
  Requires-Dist: flake8; extra == "test"
28
29
  Requires-Dist: pylint; extra == "test"
@@ -31,16 +32,18 @@ Requires-Dist: pytest-cov; extra == "test"
31
32
  Requires-Dist: pytest-order; extra == "test"
32
33
  Requires-Dist: mypy; extra == "test"
33
34
  Requires-Dist: xarray; extra == "test"
35
+ Requires-Dist: polars>=1.8.2; extra == "test"
34
36
  Provides-Extra: docs
35
- Requires-Dist: sphinx<6,>5; extra == "docs"
36
- Requires-Dist: pydata-sphinx-theme<0.9; extra == "docs"
37
+ Requires-Dist: sphinx<9; extra == "docs"
38
+ Requires-Dist: pydata-sphinx-theme<0.17; extra == "docs"
37
39
  Requires-Dist: nbsphinx; extra == "docs"
38
40
  Requires-Dist: ipython; extra == "docs"
39
- Requires-Dist: ipykernel; extra == "docs"
41
+ Requires-Dist: ipykernel<7; extra == "docs"
40
42
  Requires-Dist: ipywidgets; extra == "docs"
41
43
  Requires-Dist: sphinx-copybutton; extra == "docs"
42
44
  Requires-Dist: xarray; extra == "docs"
43
45
  Requires-Dist: matplotlib; extra == "docs"
46
+ Dynamic: license-file
44
47
 
45
48
  # CmdStanPy
46
49
 
@@ -37,6 +37,8 @@ from .stanfit import (
37
37
  from .utils import (
38
38
  cmdstan_path,
39
39
  cmdstan_version,
40
+ disable_logging,
41
+ enable_logging,
40
42
  install_cmdstan,
41
43
  set_cmdstan_path,
42
44
  set_make_env,
@@ -63,4 +65,6 @@ __all__ = [
63
65
  'show_versions',
64
66
  'rebuild_cmdstan',
65
67
  'cmdstan_version',
68
+ "enable_logging",
69
+ "disable_logging",
66
70
  ]
@@ -0,0 +1,3 @@
1
+ """PyPi Version"""
2
+
3
+ __version__ = '1.3.1'
@@ -1,23 +1,16 @@
1
1
  """
2
2
  CmdStan arguments
3
3
  """
4
+
4
5
  import os
5
6
  from enum import Enum, auto
6
7
  from time import time
7
- from typing import Any, Dict, List, Mapping, Optional, Union
8
+ from typing import Any, Mapping, Optional, Union
8
9
 
9
10
  import numpy as np
10
11
  from numpy.random import default_rng
11
12
 
12
- from cmdstanpy import _TMPDIR
13
- from cmdstanpy.utils import (
14
- cmdstan_path,
15
- cmdstan_version_before,
16
- create_named_text_file,
17
- get_logger,
18
- read_metric,
19
- write_stan_json,
20
- )
13
+ from cmdstanpy.utils import cmdstan_path, cmdstan_version_before, get_logger
21
14
 
22
15
  OptionalPath = Union[str, os.PathLike, None]
23
16
 
@@ -64,10 +57,9 @@ class SamplerArgs:
64
57
  save_warmup: bool = False,
65
58
  thin: Optional[int] = None,
66
59
  max_treedepth: Optional[int] = None,
67
- metric: Union[
68
- str, Dict[str, Any], List[str], List[Dict[str, Any]], None
69
- ] = None,
70
- step_size: Union[float, List[float], None] = None,
60
+ metric_type: Optional[str] = None,
61
+ metric_file: Union[str, list[str], None] = None,
62
+ step_size: Union[float, list[float], None] = None,
71
63
  adapt_engaged: bool = True,
72
64
  adapt_delta: Optional[float] = None,
73
65
  adapt_init_phase: Optional[int] = None,
@@ -82,9 +74,8 @@ class SamplerArgs:
82
74
  self.save_warmup = save_warmup
83
75
  self.thin = thin
84
76
  self.max_treedepth = max_treedepth
85
- self.metric = metric
86
- self.metric_type: Optional[str] = None
87
- self.metric_file: Union[str, List[str], None] = None
77
+ self.metric_type: Optional[str] = metric_type
78
+ self.metric_file: Union[str, list[str], None] = metric_file
88
79
  self.step_size = step_size
89
80
  self.adapt_engaged = adapt_engaged
90
81
  self.adapt_delta = adapt_delta
@@ -161,8 +152,9 @@ class SamplerArgs:
161
152
  ):
162
153
  if self.step_size <= 0:
163
154
  raise ValueError(
164
- 'Argument "step_size" must be > 0, '
165
- 'found {}.'.format(self.step_size)
155
+ 'Argument "step_size" must be > 0, found {}.'.format(
156
+ self.step_size
157
+ )
166
158
  )
167
159
  else:
168
160
  if len(self.step_size) != chains:
@@ -176,124 +168,15 @@ class SamplerArgs:
176
168
  'Argument "step_size" must be > 0, '
177
169
  'chain {}, found {}.'.format(i + 1, step_size)
178
170
  )
179
- if self.metric is not None:
180
- if isinstance(self.metric, str):
181
- if self.metric in ['diag', 'diag_e']:
182
- self.metric_type = 'diag_e'
183
- elif self.metric in ['dense', 'dense_e']:
184
- self.metric_type = 'dense_e'
185
- elif self.metric in ['unit', 'unit_e']:
186
- self.metric_type = 'unit_e'
187
- else:
188
- if not os.path.exists(self.metric):
189
- raise ValueError('no such file {}'.format(self.metric))
190
- dims = read_metric(self.metric)
191
- if len(dims) == 1:
192
- self.metric_type = 'diag_e'
193
- else:
194
- self.metric_type = 'dense_e'
195
- self.metric_file = self.metric
196
- elif isinstance(self.metric, dict):
197
- if 'inv_metric' not in self.metric:
198
- raise ValueError(
199
- 'Entry "inv_metric" not found in metric dict.'
200
- )
201
- dims = list(np.asarray(self.metric['inv_metric']).shape)
202
- if len(dims) == 1:
203
- self.metric_type = 'diag_e'
204
- else:
205
- self.metric_type = 'dense_e'
206
- dict_file = create_named_text_file(
207
- dir=_TMPDIR, prefix="metric", suffix=".json"
208
- )
209
- write_stan_json(dict_file, self.metric)
210
- self.metric_file = dict_file
211
- elif isinstance(self.metric, (list, tuple)):
212
- if len(self.metric) != chains:
213
- raise ValueError(
214
- 'Number of metric files must match number of chains,'
215
- ' found {} metric files for {} chains.'.format(
216
- len(self.metric), chains
217
- )
218
- )
219
- if all(isinstance(elem, dict) for elem in self.metric):
220
- metric_files: List[str] = []
221
- for i, metric in enumerate(self.metric):
222
- metric_dict: Dict[str, Any] = metric # type: ignore
223
- if 'inv_metric' not in metric_dict:
224
- raise ValueError(
225
- 'Entry "inv_metric" not found in metric dict '
226
- 'for chain {}.'.format(i + 1)
227
- )
228
- if i == 0:
229
- dims = list(
230
- np.asarray(metric_dict['inv_metric']).shape
231
- )
232
- else:
233
- dims2 = list(
234
- np.asarray(metric_dict['inv_metric']).shape
235
- )
236
- if dims != dims2:
237
- raise ValueError(
238
- 'Found inconsistent "inv_metric" entry '
239
- 'for chain {}: entry has dims '
240
- '{}, expected {}.'.format(
241
- i + 1, dims, dims2
242
- )
243
- )
244
- dict_file = create_named_text_file(
245
- dir=_TMPDIR, prefix="metric", suffix=".json"
246
- )
247
- write_stan_json(dict_file, metric_dict)
248
- metric_files.append(dict_file)
249
- if len(dims) == 1:
250
- self.metric_type = 'diag_e'
251
- else:
252
- self.metric_type = 'dense_e'
253
- self.metric_file = metric_files
254
- elif all(isinstance(elem, str) for elem in self.metric):
255
- metric_files = []
256
- for i, metric in enumerate(self.metric):
257
- assert isinstance(metric, str) # typecheck
258
- if not os.path.exists(metric):
259
- raise ValueError('no such file {}'.format(metric))
260
- if i == 0:
261
- dims = read_metric(metric)
262
- else:
263
- dims2 = read_metric(metric)
264
- if len(dims) != len(dims2):
265
- raise ValueError(
266
- 'Metrics files {}, {},'
267
- ' inconsistent metrics'.format(
268
- self.metric[0], metric
269
- )
270
- )
271
- if dims != dims2:
272
- raise ValueError(
273
- 'Metrics files {}, {},'
274
- ' inconsistent metrics'.format(
275
- self.metric[0], metric
276
- )
277
- )
278
- metric_files.append(metric)
279
- if len(dims) == 1:
280
- self.metric_type = 'diag_e'
281
- else:
282
- self.metric_type = 'dense_e'
283
- self.metric_file = metric_files
284
- else:
285
- raise ValueError(
286
- 'Argument "metric" must be a list of pathnames or '
287
- 'Python dicts, found list of {}.'.format(
288
- type(self.metric[0])
289
- )
290
- )
291
- else:
171
+ if self.metric_type is not None:
172
+ if self.metric_type in ['diag', 'dense', 'unit']:
173
+ self.metric_type += '_e'
174
+ if self.metric_type not in ['diag_e', 'dense_e', 'unit_e']:
292
175
  raise ValueError(
293
- 'Invalid metric specified, not a recognized metric type, '
294
- 'must be either a metric type name, a filepath, dict, '
295
- 'or list of per-chain filepaths or dicts. Found '
296
- 'an object of type {}.'.format(type(self.metric))
176
+ 'Argument "metric" must be one of [diag, dense, unit,'
177
+ ' diag_e, dense_e, unit_e], found {}.'.format(
178
+ self.metric_type
179
+ )
297
180
  )
298
181
 
299
182
  if self.adapt_delta is not None:
@@ -330,7 +213,8 @@ class SamplerArgs:
330
213
 
331
214
  if self.fixed_param and (
332
215
  self.max_treedepth is not None
333
- or self.metric is not None
216
+ or self.metric_type is not None
217
+ or self.metric_file is not None
334
218
  or self.step_size is not None
335
219
  or not (
336
220
  self.adapt_delta is None
@@ -343,7 +227,7 @@ class SamplerArgs:
343
227
  'When fixed_param=True, cannot specify adaptation parameters.'
344
228
  )
345
229
 
346
- def compose(self, idx: int, cmd: List[str]) -> List[str]:
230
+ def compose(self, idx: int, cmd: list[str]) -> list[str]:
347
231
  """
348
232
  Compose CmdStan command for method-specific non-default arguments.
349
233
  """
@@ -369,7 +253,7 @@ class SamplerArgs:
369
253
  cmd.append(f'stepsize={self.step_size}')
370
254
  else:
371
255
  cmd.append(f'stepsize={self.step_size[idx]}')
372
- if self.metric is not None:
256
+ if self.metric_type is not None:
373
257
  cmd.append(f'metric={self.metric_type}')
374
258
  if self.metric_file is not None:
375
259
  if not isinstance(self.metric_file, list):
@@ -467,7 +351,7 @@ class OptimizeArgs:
467
351
  positive_float(self.tol_param, 'tol_param')
468
352
  positive_int(self.history_size, 'history_size')
469
353
 
470
- def compose(self, _idx: int, cmd: List[str]) -> List[str]:
354
+ def compose(self, _idx: int, cmd: list[str]) -> list[str]:
471
355
  """compose command string for CmdStan for non-default arg values."""
472
356
  cmd.append('method=optimize')
473
357
  if self.algorithm:
@@ -511,7 +395,7 @@ class LaplaceArgs:
511
395
  raise ValueError(f'Invalid path for mode file: {self.mode}')
512
396
  positive_int(self.draws, 'draws')
513
397
 
514
- def compose(self, _idx: int, cmd: List[str]) -> List[str]:
398
+ def compose(self, _idx: int, cmd: list[str]) -> list[str]:
515
399
  """compose command string for CmdStan for non-default arg values."""
516
400
  cmd.append('method=laplace')
517
401
  cmd.append(f'mode={self.mode}')
@@ -579,7 +463,7 @@ class PathfinderArgs:
579
463
  positive_int(self.num_draws, 'num_draws')
580
464
  positive_int(self.num_elbo_draws, 'num_elbo_draws')
581
465
 
582
- def compose(self, _idx: int, cmd: List[str]) -> List[str]:
466
+ def compose(self, _idx: int, cmd: list[str]) -> list[str]:
583
467
  """compose command string for CmdStan for non-default arg values."""
584
468
  cmd.append('method=pathfinder')
585
469
 
@@ -624,12 +508,13 @@ class PathfinderArgs:
624
508
  class GenerateQuantitiesArgs:
625
509
  """Arguments needed for generate_quantities method."""
626
510
 
627
- def __init__(self, csv_files: List[str]) -> None:
511
+ def __init__(self, csv_files: list[str]) -> None:
628
512
  """Initialize object."""
629
513
  self.sample_csv_files = csv_files
630
514
 
631
515
  def validate(
632
- self, chains: Optional[int] = None # pylint: disable=unused-argument
516
+ self,
517
+ chains: Optional[int] = None, # pylint: disable=unused-argument
633
518
  ) -> None:
634
519
  """
635
520
  Check arguments correctness and consistency.
@@ -642,7 +527,7 @@ class GenerateQuantitiesArgs:
642
527
  'Invalid path for sample csv file: {}'.format(csv)
643
528
  )
644
529
 
645
- def compose(self, idx: int, cmd: List[str]) -> List[str]:
530
+ def compose(self, idx: int, cmd: list[str]) -> list[str]:
646
531
  """
647
532
  Compose CmdStan command for method-specific non-default arguments.
648
533
  """
@@ -681,7 +566,8 @@ class VariationalArgs:
681
566
  self.output_samples = output_samples
682
567
 
683
568
  def validate(
684
- self, chains: Optional[int] = None # pylint: disable=unused-argument
569
+ self,
570
+ chains: Optional[int] = None, # pylint: disable=unused-argument
685
571
  ) -> None:
686
572
  """
687
573
  Check arguments correctness and consistency.
@@ -705,7 +591,7 @@ class VariationalArgs:
705
591
  positive_int(self.output_samples, 'output_samples')
706
592
 
707
593
  # pylint: disable=unused-argument
708
- def compose(self, idx: int, cmd: List[str]) -> List[str]:
594
+ def compose(self, idx: int, cmd: list[str]) -> list[str]:
709
595
  """
710
596
  Compose CmdStan command for method-specific non-default arguments.
711
597
  """
@@ -746,8 +632,8 @@ class CmdStanArgs:
746
632
  def __init__(
747
633
  self,
748
634
  model_name: str,
749
- model_exe: OptionalPath,
750
- chain_ids: Optional[List[int]],
635
+ model_exe: str,
636
+ chain_ids: Optional[list[int]],
751
637
  method_args: Union[
752
638
  SamplerArgs,
753
639
  OptimizeArgs,
@@ -757,8 +643,8 @@ class CmdStanArgs:
757
643
  PathfinderArgs,
758
644
  ],
759
645
  data: Union[Mapping[str, Any], str, None] = None,
760
- seed: Union[int, List[int], None] = None,
761
- inits: Union[int, float, str, List[str], None] = None,
646
+ seed: Union[int, list[int], None] = None,
647
+ inits: Union[int, float, str, list[str], None] = None,
762
648
  output_dir: OptionalPath = None,
763
649
  sig_figs: Optional[int] = None,
764
650
  save_latent_dynamics: bool = False,
@@ -806,10 +692,6 @@ class CmdStanArgs:
806
692
  * if no seed specified, set random seed.
807
693
  * length of per-chain lists equals specified # of chains
808
694
  """
809
- if self.model_name is None:
810
- raise ValueError('no stan model specified')
811
- if self.model_exe is None:
812
- raise ValueError('model not compiled')
813
695
 
814
696
  if self.chain_ids is not None:
815
697
  for chain_id in self.chain_ids:
@@ -959,11 +841,11 @@ class CmdStanArgs:
959
841
  *,
960
842
  diagnostic_file: Optional[str] = None,
961
843
  profile_file: Optional[str] = None,
962
- ) -> List[str]:
844
+ ) -> list[str]:
963
845
  """
964
846
  Compose CmdStan command for non-default arguments.
965
847
  """
966
- cmd: List[str] = []
848
+ cmd: list[str] = []
967
849
  if idx is not None and self.chain_ids is not None:
968
850
  if idx < 0 or idx > len(self.chain_ids) - 1:
969
851
  raise ValueError(
@@ -971,10 +853,10 @@ class CmdStanArgs:
971
853
  idx, len(self.chain_ids)
972
854
  )
973
855
  )
974
- cmd.append(self.model_exe) # type: ignore # guaranteed by validate
856
+ cmd.append(self.model_exe)
975
857
  cmd.append(f'id={self.chain_ids[idx]}')
976
858
  else:
977
- cmd.append(self.model_exe) # type: ignore # guaranteed by validate
859
+ cmd.append(self.model_exe)
978
860
 
979
861
  if self.seed is not None:
980
862
  if not isinstance(self.seed, list):