Skip to content

Commit ec08eb8

Browse files
committed
Merge branch 'develop' of https://github.com/stan-dev/cmdstanpy into fix/354
2 parents 09abac9 + 1e1c8a2 commit ec08eb8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1050
-654
lines changed

.github/workflows/release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ jobs:
6969
git push -f origin master
7070
7171
- name: Build wheel
72-
run: python setup.py bdist_wheel
72+
run: python setup.py sdist bdist_wheel
7373

7474
- name: Install bdist_wheel
7575
run: pip install dist/*.whl
@@ -86,4 +86,4 @@ jobs:
8686
env:
8787
RTD_USERNAME: ${{ secrets.RTD_USERNAME }}
8888
RTD_PASSWORD: ${{ secrets.RTD_PASSWORD }}
89-
run: python rtd_change_default_version.py cmdstanpy ${RTD_USERNAME} ${RTD_PASSWORD} v${{ github.event.inputs.new_version }}
89+
run: python rtd_change_default_version.py cmdstanpy ${RTD_USERNAME} ${RTD_PASSWORD} v${{ github.event.inputs.new_version }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ docsrc/_build
1717
*.hpp
1818
*.exe
1919
.mypy_cache
20+
*.dll

.pylintrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ disable=bad-continuation,
7272
redundant-unittest-assert,
7373
too-many-branches,
7474
too-many-locals,
75-
unsubscriptable-object
75+
unsubscriptable-object,
76+
consider-using-with
7677

7778

7879

MANIFEST.in

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# This file is used to include extra files in source distributions.
2+
# (Source distributions are generated by running `python setup.py sdist`.)
3+
4+
include requirements*.txt
5+
include LICENSE.md

cmdstanpy/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""PyPi Version"""
22

3-
__version__ = '0.9.75'
3+
__version__ = '0.9.76'

cmdstanpy/cmdstan_args.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ def compose(self, idx: int, cmd: List) -> str:
486486
Compose CmdStan command for method-specific non-default arguments.
487487
"""
488488
cmd.append('method=generate_quantities')
489-
cmd.append('fitted_params={}'.format(self.sample_csv_files[idx - 1]))
489+
cmd.append('fitted_params={}'.format(self.sample_csv_files[idx]))
490490
return cmd
491491

492492

cmdstanpy/model.py

Lines changed: 127 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -277,63 +277,80 @@ def compile(
277277
else:
278278
self._compiler_options.add(compiler_options)
279279

280-
compilation_failed = False
281-
with TemporaryCopiedFile(self._stan_file) as (stan_file, is_copied):
282-
exe_file, _ = os.path.splitext(os.path.abspath(stan_file))
283-
exe_file = Path(exe_file).as_posix() + EXTENSION
284-
do_compile = True
285-
if os.path.exists(exe_file):
286-
src_time = os.path.getmtime(self._stan_file)
287-
exe_time = os.path.getmtime(exe_file)
288-
if exe_time > src_time and not force:
289-
do_compile = False
290-
self._logger.info('found newer exe file, not recompiling')
291-
292-
if do_compile:
293-
self._logger.info(
294-
'compiling stan program, exe file: %s', exe_file
295-
)
296-
if self._compiler_options is not None:
297-
self._compiler_options.validate()
280+
# check if exe file exists in original location
281+
exe_file, _ = os.path.splitext(os.path.abspath(self._stan_file))
282+
exe_file = Path(exe_file).as_posix() + EXTENSION
283+
do_compile = True
284+
if os.path.exists(exe_file):
285+
src_time = os.path.getmtime(self._stan_file)
286+
exe_time = os.path.getmtime(exe_file)
287+
if exe_time > src_time and not force:
288+
do_compile = False
289+
self._logger.info('found newer exe file, not recompiling')
290+
self._exe_file = exe_file
291+
self._logger.info('compiled model file: %s', self._exe_file)
292+
if do_compile:
293+
compilation_failed = False
294+
with TemporaryCopiedFile(self._stan_file) as (stan_file, is_copied):
295+
exe_file, _ = os.path.splitext(os.path.abspath(stan_file))
296+
exe_file = Path(exe_file).as_posix() + EXTENSION
297+
do_compile = True
298+
if os.path.exists(exe_file):
299+
src_time = os.path.getmtime(self._stan_file)
300+
exe_time = os.path.getmtime(exe_file)
301+
if exe_time > src_time and not force:
302+
do_compile = False
303+
self._logger.info(
304+
'found newer exe file, not recompiling'
305+
)
306+
307+
if do_compile:
298308
self._logger.info(
299-
'compiler options: %s', self._compiler_options
309+
'compiling stan program, exe file: %s', exe_file
300310
)
301-
make = os.getenv(
302-
'MAKE',
303-
'make'
304-
if platform.system() != 'Windows'
305-
else 'mingw32-make',
306-
)
307-
cmd = [make]
308-
if self._compiler_options is not None:
309-
cmd.extend(self._compiler_options.compose())
310-
cmd.append(Path(exe_file).as_posix())
311-
try:
312-
do_command(cmd, cmdstan_path(), logger=self._logger)
313-
except RuntimeError as e:
314-
self._logger.error(
315-
'file %s, exception %s', stan_file, str(e)
311+
if self._compiler_options is not None:
312+
self._compiler_options.validate()
313+
self._logger.info(
314+
'compiler options: %s', self._compiler_options
315+
)
316+
make = os.getenv(
317+
'MAKE',
318+
'make'
319+
if platform.system() != 'Windows'
320+
else 'mingw32-make',
316321
)
317-
compilation_failed = True
322+
cmd = [make]
323+
if self._compiler_options is not None:
324+
cmd.extend(self._compiler_options.compose())
325+
cmd.append(Path(exe_file).as_posix())
326+
try:
327+
do_command(cmd, cmdstan_path(), logger=self._logger)
328+
except RuntimeError as e:
329+
self._logger.error(
330+
'file %s, exception %s', stan_file, str(e)
331+
)
332+
compilation_failed = True
318333

319-
if not compilation_failed:
320-
if is_copied:
321-
original_target_dir = os.path.dirname(
322-
os.path.abspath(self._stan_file)
323-
)
324-
new_exec_name = (
325-
os.path.basename(os.path.splitext(self._stan_file)[0])
326-
+ EXTENSION
327-
)
328-
self._exe_file = os.path.join(
329-
original_target_dir, new_exec_name
330-
)
331-
shutil.copy(exe_file, self._exe_file)
334+
if not compilation_failed:
335+
if is_copied:
336+
original_target_dir = os.path.dirname(
337+
os.path.abspath(self._stan_file)
338+
)
339+
new_exec_name = (
340+
os.path.basename(
341+
os.path.splitext(self._stan_file)[0]
342+
)
343+
+ EXTENSION
344+
)
345+
self._exe_file = os.path.join(
346+
original_target_dir, new_exec_name
347+
)
348+
shutil.copy(exe_file, self._exe_file)
349+
else:
350+
self._exe_file = exe_file
351+
self._logger.info('compiled model file: %s', self._exe_file)
332352
else:
333-
self._exe_file = exe_file
334-
self._logger.info('compiled model file: %s', self._exe_file)
335-
else:
336-
self._logger.error('model compilation failed')
353+
self._logger.error('model compilation failed')
337354

338355
def optimize(
339356
self,
@@ -473,7 +490,10 @@ def optimize(
473490
self._run_cmdstan(runset, dummy_chain_id)
474491

475492
if not runset._check_retcodes():
476-
msg = 'Error during optimization.\n{}'.format(runset.get_err_msgs())
493+
msg = 'Error during optimization:\n{}'.format(runset.get_err_msgs())
494+
msg = '{}Command and output files:\n{}'.format(
495+
msg, runset.__repr__()
496+
)
477497
raise RuntimeError(msg)
478498
mle = CmdStanMLE(runset)
479499
return mle
@@ -820,7 +840,6 @@ def sample(
820840
dynamic_ncols = False
821841
else:
822842
dynamic_ncols = True
823-
824843
pbar = tqdm_pbar(
825844
desc='Chain {} - warmup'.format(i + 1),
826845
position=i,
@@ -838,7 +857,10 @@ def sample(
838857
self._logger.propagate = True
839858

840859
if not runset._check_retcodes():
841-
msg = 'Error during sampling.\n{}'.format(runset.get_err_msgs())
860+
msg = 'Error during sampling:\n{}'.format(runset.get_err_msgs())
861+
msg = '{}Command and output files:\n{}'.format(
862+
msg, runset.__repr__()
863+
)
842864
raise RuntimeError(msg)
843865

844866
mcmc = CmdStanMCMC(runset, validate_csv, logger=self._logger)
@@ -990,9 +1012,12 @@ def generate_quantities(
9901012
executor.submit(self._run_cmdstan, runset, i)
9911013

9921014
if not runset._check_retcodes():
993-
msg = 'Error during generate_quantities.\n{}'.format(
1015+
msg = 'Error during generate_quantities:\n{}'.format(
9941016
runset.get_err_msgs()
9951017
)
1018+
msg = '{}Command and output files:\n{}'.format(
1019+
msg, runset.__repr__()
1020+
)
9961021
raise RuntimeError(msg)
9971022
quantities = CmdStanGQ(runset=runset, mcmc_sample=sample_drawset)
9981023
return quantities
@@ -1149,9 +1174,12 @@ def variational(
11491174
if require_converged and not valid:
11501175
raise RuntimeError('The algorithm may not have converged.')
11511176
if not runset._check_retcodes():
1152-
msg = 'Error during variational inference.\n{}'.format(
1177+
msg = 'Error during variational inference:\n{}'.format(
11531178
runset.get_err_msgs()
11541179
)
1180+
msg = '{}Command and output files:\n{}'.format(
1181+
msg, runset.__repr__()
1182+
)
11551183
raise RuntimeError(msg)
11561184
# pylint: disable=invalid-name
11571185
vb = CmdStanVB(runset)
@@ -1170,26 +1198,48 @@ def _run_cmdstan(
11701198
'threads: %s', str(os.environ.get('STAN_NUM_THREADS'))
11711199
)
11721200
self._logger.debug('sampling: %s', cmd)
1173-
proc = subprocess.Popen(
1174-
cmd,
1175-
stdin=subprocess.DEVNULL,
1176-
stdout=subprocess.PIPE,
1177-
stderr=subprocess.PIPE,
1178-
env=os.environ,
1179-
)
1180-
if pbar:
1181-
stdout_pbar = self._read_progress(proc, pbar, idx)
1182-
stdout, stderr = proc.communicate()
1183-
if pbar:
1184-
stdout = stdout_pbar + stdout
1185-
self._logger.info('finish chain %u', idx + 1)
1186-
if stdout:
1187-
with open(runset.stdout_files[idx], 'w+') as fd:
1188-
fd.write(stdout.decode('utf-8'))
1189-
if stderr:
1190-
with open(runset.stderr_files[idx], 'w+') as fd:
1191-
fd.write(stderr.decode('utf-8'))
1192-
runset._set_retcode(idx, proc.returncode)
1201+
try:
1202+
proc = subprocess.Popen(
1203+
cmd,
1204+
stdin=subprocess.DEVNULL,
1205+
stdout=subprocess.PIPE,
1206+
stderr=subprocess.PIPE,
1207+
env=os.environ,
1208+
)
1209+
if pbar:
1210+
stdout_pbar = self._read_progress(proc, pbar, idx)
1211+
stdout, stderr = proc.communicate()
1212+
if pbar:
1213+
stdout = stdout_pbar + stdout
1214+
1215+
self._logger.info('finish chain %u', idx + 1)
1216+
runset._set_retcode(idx, proc.returncode)
1217+
if stdout:
1218+
with open(runset.stdout_files[idx], 'w+') as fd:
1219+
fd.write(stdout.decode('utf-8'))
1220+
console_error = ''
1221+
if stderr:
1222+
console_error = stderr.decode('utf-8')
1223+
with open(runset.stderr_files[idx], 'w+') as fd:
1224+
fd.write(console_error)
1225+
1226+
if proc.returncode != 0:
1227+
if proc.returncode < 0:
1228+
msg = 'Chain {} terminated by signal {}'.format(
1229+
idx + 1, proc.returncode
1230+
)
1231+
else:
1232+
msg = 'Chain {} processing error'.format(idx + 1)
1233+
msg = '{}, non-zero return code {}'.format(
1234+
msg, proc.returncode
1235+
)
1236+
if len(console_error) > 0:
1237+
msg = '{}\n error message:\n\t{}'.format(msg, console_error)
1238+
self._logger.error(msg)
1239+
1240+
except OSError as e:
1241+
msg = 'Chain {} encounted error: {}\n'.format(idx + 1, str(e))
1242+
raise RuntimeError(msg) from e
11931243

11941244
def _read_progress(
11951245
self, proc: subprocess.Popen, pbar: Any, idx: int

cmdstanpy/stanfit.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,6 @@ def _set_retcode(self, idx: int, val: int) -> None:
263263
def get_err_msgs(self) -> List[str]:
264264
"""Checks console messages for each chain."""
265265
msgs = []
266-
msgs.append(self.__repr__())
267266
for i in range(self._chains):
268267
if (
269268
os.path.exists(self._stderr_files[i])
@@ -275,8 +274,10 @@ def get_err_msgs(self) -> List[str]:
275274
self._chain_ids[i], fd.read()
276275
)
277276
)
277+
# pre 2.27, all msgs sent to stdout, including errors
278278
if (
279-
os.path.exists(self._stdout_files[i])
279+
not cmdstan_version_at(2, 27)
280+
and os.path.exists(self._stdout_files[i])
280281
and os.stat(self._stdout_files[i]).st_size > 0
281282
):
282283
with open(self._stdout_files[i], 'r') as fd:
@@ -718,6 +719,7 @@ def validate_csv_files(self) -> None:
718719
'stepsize',
719720
'init',
720721
'seed',
722+
'start_datetime',
721723
]
722724
and dzero[key] != drest[key]
723725
):
@@ -1227,6 +1229,7 @@ def _assemble_generated_quantities(self) -> None:
12271229
self.runset.csv_files[chain],
12281230
comment='#',
12291231
float_precision='high',
1232+
dtype=float,
12301233
)
12311234
)
12321235
self._generated_quantities = pd.concat(drawset_list).values

0 commit comments

Comments
 (0)