cmdstanpy 1.3.1__tar.gz → 1.3.2__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 (59) hide show
  1. {cmdstanpy-1.3.1/cmdstanpy.egg-info → cmdstanpy-1.3.2}/PKG-INFO +1 -1
  2. cmdstanpy-1.3.2/cmdstanpy/_version.py +3 -0
  3. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy/cmdstan_args.py +7 -3
  4. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy/compilation.py +82 -19
  5. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy/model.py +211 -47
  6. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2/cmdstanpy.egg-info}/PKG-INFO +1 -1
  7. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/test/test_compiler_opts.py +106 -0
  8. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/test/test_model.py +144 -69
  9. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/test/test_sample.py +5 -3
  10. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/test/test_utils.py +1 -0
  11. cmdstanpy-1.3.1/cmdstanpy/_version.py +0 -3
  12. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/LICENSE.md +0 -0
  13. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/MANIFEST.in +0 -0
  14. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/README.md +0 -0
  15. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy/__init__.py +0 -0
  16. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy/install_cmdstan.py +0 -0
  17. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy/install_cxx_toolchain.py +0 -0
  18. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy/progress.py +0 -0
  19. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy/py.typed +0 -0
  20. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy/stanfit/__init__.py +0 -0
  21. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy/stanfit/gq.py +0 -0
  22. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy/stanfit/laplace.py +0 -0
  23. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy/stanfit/mcmc.py +0 -0
  24. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy/stanfit/metadata.py +0 -0
  25. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy/stanfit/mle.py +0 -0
  26. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy/stanfit/pathfinder.py +0 -0
  27. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy/stanfit/runset.py +0 -0
  28. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy/stanfit/vb.py +0 -0
  29. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy/utils/__init__.py +0 -0
  30. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy/utils/cmdstan.py +0 -0
  31. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy/utils/command.py +0 -0
  32. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy/utils/data_munging.py +0 -0
  33. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy/utils/deprecation.py +0 -0
  34. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy/utils/filesystem.py +0 -0
  35. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy/utils/json.py +0 -0
  36. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy/utils/logging.py +0 -0
  37. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy/utils/stancsv.py +0 -0
  38. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy.egg-info/SOURCES.txt +0 -0
  39. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy.egg-info/dependency_links.txt +0 -0
  40. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy.egg-info/entry_points.txt +0 -0
  41. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy.egg-info/requires.txt +0 -0
  42. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/cmdstanpy.egg-info/top_level.txt +0 -0
  43. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/pyproject.toml +0 -0
  44. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/setup.cfg +0 -0
  45. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/test/test_cmdstan_args.py +0 -0
  46. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/test/test_compliance.py +0 -0
  47. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/test/test_cxx_installation.py +0 -0
  48. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/test/test_deprecations.py +0 -0
  49. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/test/test_generate_quantities.py +0 -0
  50. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/test/test_install_cmdstan.py +0 -0
  51. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/test/test_laplace.py +0 -0
  52. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/test/test_log_prob.py +0 -0
  53. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/test/test_logging.py +0 -0
  54. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/test/test_metadata.py +0 -0
  55. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/test/test_optimize.py +0 -0
  56. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/test/test_pathfinder.py +0 -0
  57. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/test/test_runset.py +0 -0
  58. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/test/test_stancsv.py +0 -0
  59. {cmdstanpy-1.3.1 → cmdstanpy-1.3.2}/test/test_variational.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cmdstanpy
3
- Version: 1.3.1
3
+ Version: 1.3.2
4
4
  Summary: Python interface to CmdStan
5
5
  Author: Stan Dev Team
6
6
  License: BSD-3-Clause
@@ -0,0 +1,3 @@
1
+ """PyPi Version"""
2
+
3
+ __version__ = '1.3.2'
@@ -632,7 +632,7 @@ class CmdStanArgs:
632
632
  def __init__(
633
633
  self,
634
634
  model_name: str,
635
- model_exe: str,
635
+ model_exe: OptionalPath,
636
636
  chain_ids: Optional[list[int]],
637
637
  method_args: Union[
638
638
  SamplerArgs,
@@ -692,6 +692,10 @@ class CmdStanArgs:
692
692
  * if no seed specified, set random seed.
693
693
  * length of per-chain lists equals specified # of chains
694
694
  """
695
+ if self.model_name is None:
696
+ raise ValueError('no stan model specified')
697
+ if self.model_exe is None:
698
+ raise ValueError('model not compiled')
695
699
 
696
700
  if self.chain_ids is not None:
697
701
  for chain_id in self.chain_ids:
@@ -853,10 +857,10 @@ class CmdStanArgs:
853
857
  idx, len(self.chain_ids)
854
858
  )
855
859
  )
856
- cmd.append(self.model_exe)
860
+ cmd.append(self.model_exe) # type: ignore # guaranteed by validate
857
861
  cmd.append(f'id={self.chain_ids[idx]}')
858
862
  else:
859
- cmd.append(self.model_exe)
863
+ cmd.append(self.model_exe) # type: ignore # guaranteed by validate
860
864
 
861
865
  if self.seed is not None:
862
866
  if not isinstance(self.seed, list):
@@ -8,6 +8,7 @@ import os
8
8
  import platform
9
9
  import shutil
10
10
  import subprocess
11
+ from copy import copy
11
12
  from datetime import datetime
12
13
  from pathlib import Path
13
14
  from typing import Any, Iterable, Optional, Union
@@ -36,6 +37,12 @@ STANC_OPTS = [
36
37
  'warn-pedantic',
37
38
  ]
38
39
 
40
+ # TODO(2.0): remove
41
+ STANC_DEPRECATED_OPTS = {
42
+ 'allow_undefined': 'allow-undefined',
43
+ 'include_paths': 'include-paths',
44
+ }
45
+
39
46
  STANC_IGNORE_OPTS = [
40
47
  'debug-lex',
41
48
  'debug-parse',
@@ -60,6 +67,7 @@ STANC_IGNORE_OPTS = [
60
67
  OptionalPath = Union[str, os.PathLike, None]
61
68
 
62
69
 
70
+ # TODO(2.0): can remove add function and other logic
63
71
  class CompilerOptions:
64
72
  """
65
73
  User-specified flags for stanc and C++ compiler.
@@ -87,6 +95,26 @@ class CompilerOptions:
87
95
  self._stanc_options, self._cpp_options
88
96
  )
89
97
 
98
+ def __eq__(self, other: Any) -> bool:
99
+ """Overrides the default implementation"""
100
+ if self.is_empty() and other is None: # equiv w/r/t compiler
101
+ return True
102
+ if not isinstance(other, CompilerOptions):
103
+ return False
104
+ return (
105
+ self._stanc_options == other.stanc_options
106
+ and self._cpp_options == other.cpp_options
107
+ and self._user_header == other.user_header
108
+ )
109
+
110
+ def is_empty(self) -> bool:
111
+ """True if no options specified."""
112
+ return (
113
+ self._stanc_options == {}
114
+ and self._cpp_options == {}
115
+ and self._user_header == ''
116
+ )
117
+
90
118
  @property
91
119
  def stanc_options(self) -> dict[str, Union[bool, int, str, Iterable[str]]]:
92
120
  """Stanc compiler options."""
@@ -116,12 +144,31 @@ class CompilerOptions:
116
144
  Check stanc compiler args and consistency between stanc and C++ options.
117
145
  Raise ValueError if bad config is found.
118
146
  """
147
+ # pylint: disable=no-member
119
148
  if self._stanc_options is None:
120
149
  return
121
150
  ignore = []
122
151
  paths = None
123
152
  has_o_flag = False
124
153
 
154
+ for deprecated, replacement in STANC_DEPRECATED_OPTS.items():
155
+ if deprecated in self._stanc_options:
156
+ if replacement:
157
+ get_logger().warning(
158
+ 'compiler option "%s" is deprecated, use "%s" instead',
159
+ deprecated,
160
+ replacement,
161
+ )
162
+ self._stanc_options[replacement] = copy(
163
+ self._stanc_options[deprecated]
164
+ )
165
+ del self._stanc_options[deprecated]
166
+ else:
167
+ get_logger().warning(
168
+ 'compiler option "%s" is deprecated and should '
169
+ 'not be used',
170
+ deprecated,
171
+ )
125
172
  for key, val in self._stanc_options.items():
126
173
  if key in STANC_IGNORE_OPTS:
127
174
  get_logger().info('ignoring compiler option: %s', key)
@@ -220,6 +267,37 @@ class CompilerOptions:
220
267
 
221
268
  self._cpp_options['USER_HEADER'] = self._user_header
222
269
 
270
+ def add(self, new_opts: "CompilerOptions") -> None: # noqa: disable=Q000
271
+ """Adds options to existing set of compiler options."""
272
+ if new_opts.stanc_options is not None:
273
+ if self._stanc_options is None:
274
+ self._stanc_options = new_opts.stanc_options
275
+ else:
276
+ for key, val in new_opts.stanc_options.items():
277
+ if key == 'include-paths':
278
+ if isinstance(val, Iterable) and not isinstance(
279
+ val, str
280
+ ):
281
+ for path in val:
282
+ self.add_include_path(str(path))
283
+ else:
284
+ self.add_include_path(str(val))
285
+ else:
286
+ self._stanc_options[key] = val
287
+ if new_opts.cpp_options is not None:
288
+ for key, val in new_opts.cpp_options.items():
289
+ self._cpp_options[key] = val
290
+ if new_opts._user_header != '' and self._user_header == '':
291
+ self._user_header = new_opts._user_header
292
+
293
+ def add_include_path(self, path: str) -> None:
294
+ """Adds include path to existing set of compiler options."""
295
+ path = os.path.abspath(os.path.expanduser(path))
296
+ if 'include-paths' not in self._stanc_options:
297
+ self._stanc_options['include-paths'] = [path]
298
+ elif path not in self._stanc_options['include-paths']:
299
+ self._stanc_options['include-paths'].append(path)
300
+
223
301
  def compose_stanc(self, filename_in_msg: Optional[str]) -> list[str]:
224
302
  opts = []
225
303
 
@@ -265,8 +343,7 @@ class CompilerOptions:
265
343
 
266
344
 
267
345
  def src_info(
268
- stan_file: str,
269
- stanc_options: Optional[dict[str, Any]] = None,
346
+ stan_file: str, compiler_options: CompilerOptions
270
347
  ) -> dict[str, Any]:
271
348
  """
272
349
  Get source info for Stan program file.
@@ -277,7 +354,7 @@ def src_info(
277
354
  cmd = (
278
355
  [stanc_path()]
279
356
  # handle include-paths, allow-undefined etc
280
- + CompilerOptions(stanc_options=stanc_options).compose_stanc(None)
357
+ + compiler_options.compose_stanc(None)
281
358
  + ['--info', str(stan_file)]
282
359
  )
283
360
  proc = subprocess.run(cmd, capture_output=True, text=True, check=False)
@@ -330,26 +407,12 @@ def compile_stan_file(
330
407
  )
331
408
  compiler_options.validate()
332
409
 
333
- # if program has include directives, record path
334
- if '#include' in src.read_text():
335
- path = os.fspath(src.parent.resolve())
336
- if 'include-paths' not in compiler_options.stanc_options:
337
- compiler_options.stanc_options['include-paths'] = [path]
338
- else:
339
- paths: list[str] = compiler_options.stanc_options[
340
- 'include-paths'
341
- ] # type: ignore
342
- if path not in paths:
343
- paths.append(path)
344
-
345
410
  exe_target = src.with_suffix(EXTENSION)
346
411
  if exe_target.exists():
347
412
  exe_time = os.path.getmtime(exe_target)
348
413
  included_files = [src]
349
414
  included_files.extend(
350
- src_info(str(src), compiler_options.stanc_options).get(
351
- 'included_files', []
352
- )
415
+ src_info(str(src), compiler_options).get('included_files', [])
353
416
  )
354
417
  out_of_date = any(
355
418
  os.path.getmtime(included_file) > exe_time
@@ -419,7 +482,7 @@ def compile_stan_file(
419
482
  raise ValueError(
420
483
  f"Failed to compile Stan model '{src}'. Console:\n{console}"
421
484
  )
422
- return os.fspath(exe_target)
485
+ return str(exe_target)
423
486
 
424
487
 
425
488
  def format_stan_file(
@@ -13,7 +13,16 @@ from collections import OrderedDict
13
13
  from concurrent.futures import ThreadPoolExecutor
14
14
  from io import StringIO
15
15
  from multiprocessing import cpu_count
16
- from typing import Any, Callable, Iterable, Mapping, Optional, TypeVar, Union
16
+ from typing import (
17
+ Any,
18
+ Callable,
19
+ Iterable,
20
+ Literal,
21
+ Mapping,
22
+ Optional,
23
+ TypeVar,
24
+ Union,
25
+ )
17
26
 
18
27
  import numpy as np
19
28
  import pandas as pd
@@ -69,6 +78,11 @@ class CmdStanModel:
69
78
  Stan program source file or the compiled executable, or both.
70
79
  This will compile the model if provided a Stan file and no executable,
71
80
 
81
+ :param model_name: Model name, used for output file names.
82
+ Optional, default is the base filename of the Stan program file.
83
+ Deprecated: In version 2.0.0, model name cannot be
84
+ specified and will always be taken from executable.
85
+
72
86
  :param stan_file: Path to Stan program file.
73
87
 
74
88
  :param exe_file: Path to compiled executable file. Optional, unless
@@ -90,20 +104,31 @@ class CmdStanModel:
90
104
  :param user_header: A path to a header file to include during C++
91
105
  compilation.
92
106
  Optional.
107
+
108
+ :param compile: Whether or not to compile the model. Default is ``True``.
109
+ If set to the string ``"force"``, it will always compile even if
110
+ an existing executable is found.
111
+ Deprecated: Use ``force_compile`` instead. The ability to instantiate
112
+ a CmdStanModel without an executable will be removed in version 2.0.0.
113
+
93
114
  """
94
115
 
95
116
  def __init__(
96
117
  self,
118
+ model_name: Optional[str] = None,
97
119
  stan_file: OptionalPath = None,
98
120
  exe_file: OptionalPath = None,
99
121
  force_compile: bool = False,
100
122
  stanc_options: Optional[dict[str, Any]] = None,
101
123
  cpp_options: Optional[dict[str, Any]] = None,
102
124
  user_header: OptionalPath = None,
125
+ *,
126
+ compile: Union[bool, Literal['force'], None] = None,
103
127
  ) -> None:
104
128
  """
105
129
  Initialize object given constructor args.
106
130
 
131
+ :param model_name: Deprecated. Model name, used for output file names.
107
132
  :param stan_file: Path to Stan program file.
108
133
  :param exe_file: Path to compiled executable file.
109
134
  :param force_compile: Whether or not to force recompilation if
@@ -112,19 +137,46 @@ class CmdStanModel:
112
137
  :param cpp_options: Options for C++ compiler.
113
138
  :param user_header: A path to a header file to include during C++
114
139
  compilation.
140
+ :param compile: Deprecated. Whether or not to compile the model.
115
141
  """
116
142
  self._name = ''
117
143
  self._stan_file = None
118
- self._stanc_options: dict[str, Any] = stanc_options or {}
144
+ self._exe_file = None
145
+ self._compiler_options = compilation.CompilerOptions(
146
+ stanc_options=stanc_options,
147
+ cpp_options=cpp_options,
148
+ user_header=user_header,
149
+ )
150
+ self._compiler_options.validate()
119
151
 
120
152
  self._fixed_param = False
121
153
 
122
- if exe_file is not None:
123
- self._exe_file = os.path.realpath(os.path.expanduser(exe_file))
124
- if not os.path.exists(self._exe_file):
125
- raise ValueError('no such file {}'.format(self._exe_file))
126
- _, exename = os.path.split(self._exe_file)
127
- self._name, _ = os.path.splitext(exename)
154
+ if compile is None:
155
+ compile = True
156
+ else:
157
+ get_logger().warning(
158
+ "CmdStanModel(compile=...) is deprecated and will be "
159
+ "removed in the next major version. The constructor will "
160
+ "always ensure a model has a compiled executable.\n"
161
+ "If you wish to force recompilation, use force_compile=True "
162
+ "instead."
163
+ )
164
+
165
+ if force_compile:
166
+ compile = 'force'
167
+
168
+ if model_name is not None:
169
+ get_logger().warning(
170
+ "CmdStanModel(model_name=...) is deprecated and will be "
171
+ "removed in the next major version."
172
+ )
173
+ if not model_name.strip():
174
+ raise ValueError(
175
+ 'Invalid value for argument model name, found "{}"'.format(
176
+ model_name
177
+ )
178
+ )
179
+ self._name = model_name.strip()
128
180
 
129
181
  if stan_file is None:
130
182
  if exe_file is None:
@@ -132,11 +184,6 @@ class CmdStanModel:
132
184
  'Missing model file arguments, you must specify '
133
185
  'either Stan source or executable program file or both.'
134
186
  )
135
- if force_compile:
136
- raise ValueError(
137
- 'Cannot force compilation when no Stan source file '
138
- 'is specified.'
139
- )
140
187
  else:
141
188
  self._stan_file = os.path.realpath(os.path.expanduser(stan_file))
142
189
  if not os.path.exists(self._stan_file):
@@ -148,29 +195,41 @@ class CmdStanModel:
148
195
  )
149
196
  if not self._name:
150
197
  self._name, _ = os.path.splitext(filename)
151
- else:
152
- if self._name != os.path.splitext(filename)[0]:
153
- raise ValueError(
154
- 'Name mismatch between Stan file and compiled'
155
- ' executable, expecting basename: {}'
156
- ' found: {}.'.format(self._name, filename)
157
- )
158
198
 
159
- self._exe_file = compilation.compile_stan_file(
160
- str(self.stan_file),
161
- force=force_compile,
162
- stanc_options=self._stanc_options,
163
- cpp_options=cpp_options,
164
- user_header=user_header,
165
- )
199
+ # if program has include directives, record path
200
+ with open(self._stan_file, 'r') as fd:
201
+ program = fd.read()
202
+ if '#include' in program:
203
+ path, _ = os.path.split(self._stan_file)
204
+ self._compiler_options.add_include_path(path)
166
205
 
167
206
  # try to detect models w/out parameters, needed for sampler
168
207
  if (not cmdstan_version_before(2, 27)) and cmdstan_version_before(
169
208
  2, 36
170
209
  ):
171
- model_info = self.src_info()
172
- if 'parameters' in model_info:
173
- self._fixed_param |= len(model_info['parameters']) == 0
210
+ try:
211
+ model_info = self.src_info()
212
+ if 'parameters' in model_info:
213
+ self._fixed_param |= len(model_info['parameters']) == 0
214
+ except ValueError as e:
215
+ if compile:
216
+ raise
217
+ get_logger().debug(e)
218
+
219
+ if exe_file is not None:
220
+ self._exe_file = os.path.realpath(os.path.expanduser(exe_file))
221
+ if not os.path.exists(self._exe_file):
222
+ raise ValueError('no such file {}'.format(self._exe_file))
223
+ _, exename = os.path.split(self._exe_file)
224
+ if not self._name:
225
+ self._name, _ = os.path.splitext(exename)
226
+ else:
227
+ if self._name != os.path.splitext(exename)[0]:
228
+ raise ValueError(
229
+ 'Name mismatch between Stan file and compiled'
230
+ ' executable, expecting basename: {}'
231
+ ' found: {}.'.format(self._name, exename)
232
+ )
174
233
 
175
234
  if platform.system() == 'Windows':
176
235
  try:
@@ -193,16 +252,22 @@ class CmdStanModel:
193
252
  else:
194
253
  get_logger().debug("TBB already found in load path")
195
254
 
255
+ if compile and self._exe_file is None:
256
+ self.compile(force=str(compile).lower() == 'force', _internal=True)
257
+
196
258
  def __repr__(self) -> str:
197
- return (
198
- f"CmdStanModel(stan_file={self.stan_file!r}, "
199
- f"exe_file={self.exe_file!r})"
200
- )
259
+ repr = 'CmdStanModel: name={}'.format(self._name)
260
+ repr = '{}\n\t stan_file={}'.format(repr, self._stan_file)
261
+ repr = '{}\n\t exe_file={}'.format(repr, self._exe_file)
262
+ repr = '{}\n\t compiler_options={}'.format(repr, self._compiler_options)
263
+ return repr
201
264
 
202
265
  @property
203
266
  def name(self) -> str:
204
267
  """
205
- Model name used in output filename templates.
268
+ Model name used in output filename templates. Default is basename
269
+ of Stan program or exe file, unless specified in call to constructor
270
+ via argument ``model_name``.
206
271
  """
207
272
  return self._name
208
273
 
@@ -212,7 +277,7 @@ class CmdStanModel:
212
277
  return self._stan_file
213
278
 
214
279
  @property
215
- def exe_file(self) -> str:
280
+ def exe_file(self) -> OptionalPath:
216
281
  """Full path to Stan exe file."""
217
282
  return self._exe_file
218
283
 
@@ -220,17 +285,25 @@ class CmdStanModel:
220
285
  """
221
286
  Run model with option 'info'. Parse output statements, which all
222
287
  have form 'key = value' into a Dict.
288
+ If exe file compiled with CmdStan < 2.27, option 'info' isn't
289
+ available and the method returns an empty dictionary.
223
290
  """
224
291
  result: dict[str, str] = {}
225
- info = StringIO()
226
- do_command(cmd=[self.exe_file, 'info'], fd_out=info)
227
- lines = info.getvalue().split('\n')
228
- for line in lines:
229
- kv_pair = [x.strip() for x in line.split('=')]
230
- if len(kv_pair) != 2:
231
- continue
232
- result[kv_pair[0]] = kv_pair[1]
233
- return result
292
+ if self.exe_file is None:
293
+ return result
294
+ try:
295
+ info = StringIO()
296
+ do_command(cmd=[str(self.exe_file), 'info'], fd_out=info)
297
+ lines = info.getvalue().split('\n')
298
+ for line in lines:
299
+ kv_pair = [x.strip() for x in line.split('=')]
300
+ if len(kv_pair) != 2:
301
+ continue
302
+ result[kv_pair[0]] = kv_pair[1]
303
+ return result
304
+ except RuntimeError as e:
305
+ get_logger().debug(e)
306
+ return result
234
307
 
235
308
  def src_info(self) -> dict[str, Any]:
236
309
  """
@@ -241,7 +314,7 @@ class CmdStanModel:
241
314
  """
242
315
  if self.stan_file is None or cmdstan_version_before(2, 27):
243
316
  return {}
244
- return compilation.src_info(str(self.stan_file), self._stanc_options)
317
+ return compilation.src_info(str(self.stan_file), self._compiler_options)
245
318
 
246
319
  # TODO(2.0) remove
247
320
  def format(
@@ -287,9 +360,24 @@ class CmdStanModel:
287
360
  max_line_length=max_line_length,
288
361
  canonicalize=canonicalize,
289
362
  backup=backup,
290
- stanc_options=self._stanc_options,
363
+ stanc_options=self.stanc_options,
291
364
  )
292
365
 
366
+ @property
367
+ def stanc_options(self) -> dict[str, Union[bool, int, str]]:
368
+ """Options to stanc compilers."""
369
+ return self._compiler_options._stanc_options
370
+
371
+ @property
372
+ def cpp_options(self) -> dict[str, Union[bool, int]]:
373
+ """Options to C++ compilers."""
374
+ return self._compiler_options._cpp_options
375
+
376
+ @property
377
+ def user_header(self) -> str:
378
+ """The user header file if it exists, otherwise empty"""
379
+ return self._compiler_options._user_header
380
+
293
381
  def code(self) -> Optional[str]:
294
382
  """Return Stan program as a string."""
295
383
  if not self._stan_file:
@@ -305,6 +393,82 @@ class CmdStanModel:
305
393
  )
306
394
  return code
307
395
 
396
+ # TODO(2.0): remove
397
+ def compile(
398
+ self,
399
+ force: bool = False,
400
+ stanc_options: Optional[dict[str, Any]] = None,
401
+ cpp_options: Optional[dict[str, Any]] = None,
402
+ user_header: OptionalPath = None,
403
+ override_options: bool = False,
404
+ *,
405
+ _internal: bool = False,
406
+ ) -> None:
407
+ """
408
+ Deprecated: To compile a model, use the :class:`~cmdstanpy.CmdStanModel`
409
+ constructor or :func:`cmdstanpy.compile_stan_file()`.
410
+
411
+ Compile the given Stan program file. Translates the Stan code to
412
+ C++, then calls the C++ compiler.
413
+
414
+ By default, this function compares the timestamps on the source and
415
+ executable files; if the executable is newer than the source file, it
416
+ will not recompile the file, unless argument ``force`` is ``True``
417
+ or unless the compiler options have been changed.
418
+
419
+ :param force: When ``True``, always compile, even if the executable file
420
+ is newer than the source file. Used for Stan models which have
421
+ ``#include`` directives in order to force recompilation when changes
422
+ are made to the included files.
423
+
424
+ :param stanc_options: Options for stanc compiler.
425
+ :param cpp_options: Options for C++ compiler.
426
+ :param user_header: A path to a header file to include during C++
427
+ compilation.
428
+
429
+ :param override_options: When ``True``, override existing option.
430
+ When ``False``, add/replace existing options. Default is ``False``.
431
+ """
432
+ if not _internal:
433
+ get_logger().warning(
434
+ "CmdStanModel.compile() is deprecated and will be removed in "
435
+ "the next major version. To compile a model, use the "
436
+ "CmdStanModel() constructor or cmdstanpy.compile_stan_file()."
437
+ )
438
+
439
+ if not self._stan_file:
440
+ raise RuntimeError('Please specify source file')
441
+
442
+ compiler_options = None
443
+ if (
444
+ stanc_options is not None
445
+ or cpp_options is not None
446
+ or user_header is not None
447
+ ):
448
+ compiler_options = compilation.CompilerOptions(
449
+ stanc_options=stanc_options,
450
+ cpp_options=cpp_options,
451
+ user_header=user_header,
452
+ )
453
+ compiler_options.validate()
454
+
455
+ if compiler_options != self._compiler_options:
456
+ force = True
457
+ if self._compiler_options is None:
458
+ self._compiler_options = compiler_options
459
+ elif override_options:
460
+ self._compiler_options = compiler_options
461
+ else:
462
+ self._compiler_options.add(compiler_options)
463
+
464
+ self._exe_file = compilation.compile_stan_file(
465
+ str(self.stan_file),
466
+ force=force,
467
+ stanc_options=self._compiler_options.stanc_options,
468
+ cpp_options=self._compiler_options.cpp_options,
469
+ user_header=self._compiler_options.user_header,
470
+ )
471
+
308
472
  @deprecate_positional_args(last_deprecated="jacobian")
309
473
  def optimize(
310
474
  self,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cmdstanpy
3
- Version: 1.3.1
3
+ Version: 1.3.2
4
4
  Summary: Python interface to CmdStan
5
5
  Author: Stan Dev Team
6
6
  License: BSD-3-Clause