Skip to content

Commit c98a625

Browse files
committed
Merge branch 'develop' into model-formatting
2 parents 27a219f + d839f02 commit c98a625

74 files changed

Lines changed: 5628 additions & 4076 deletions

Some content is hidden

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

.github/workflows/main.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ on:
1313
cmdstan-version:
1414
description: 'Version to test'
1515
required: false
16-
default: 'latest'
16+
default: ''
1717

1818
jobs:
1919
get-cmdstan-version:
@@ -24,7 +24,7 @@ jobs:
2424
- name: Get CmdStan version
2525
id: check-cmdstan
2626
run: |
27-
if [[ "${{ github.event.inputs.cmdstan-version }}" == "latest" ]]; then
27+
if [[ "${{ github.event.inputs.cmdstan-version }}" != "" ]]; then
2828
echo "::set-output name=version::${{ github.event.inputs.cmdstan-version }}"
2929
else
3030
echo "::set-output name=version::$(python -c 'import requests;print(requests.get("https://api.github.com/repos/stan-dev/cmdstan/releases/latest").json()["tag_name"][1:])')"
@@ -96,14 +96,14 @@ jobs:
9696
run: |
9797
install_cmdstan -h
9898
install_cxx_toolchain -h
99-
python -m cmdstanpy.install_cmdstan
99+
python -m cmdstanpy.install_cmdstan --version ${{ needs.get-cmdstan-version.outputs.version }}
100100
101101
- name: Install CmdStan (Windows)
102102
if: matrix.os == 'windows-latest'
103103
run: |
104104
install_cmdstan -h
105105
install_cxx_toolchain -h
106-
python -m cmdstanpy.install_cmdstan --compiler
106+
python -m cmdstanpy.install_cmdstan --compiler --version ${{ needs.get-cmdstan-version.outputs.version }}
107107
108108
- name: Run tests
109109
run: |

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__ = '1.0.0'
3+
__version__ = '1.0.1'

cmdstanpy/compiler_opts.py

Lines changed: 66 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,30 @@
33
"""
44

55
import os
6+
from copy import copy
67
from pathlib import Path
78
from typing import Any, Dict, List, Optional, Union
89

910
from cmdstanpy.utils import get_logger
1011

1112
STANC_OPTS = [
1213
'O',
13-
'allow_undefined',
14+
'O0',
15+
'O1',
16+
'Oexperimental',
17+
'allow-undefined',
1418
'use-opencl',
1519
'warn-uninitialized',
16-
'include_paths',
20+
'include-paths',
1721
'name',
1822
'warn-pedantic',
1923
]
2024

25+
STANC_DEPRECATED_OPTS = {
26+
'allow_undefined': 'allow-undefined',
27+
'include_paths': 'include-paths',
28+
}
29+
2130
STANC_IGNORE_OPTS = [
2231
'debug-lex',
2332
'debug-parse',
@@ -121,41 +130,69 @@ def validate_stanc_opts(self) -> None:
121130
return
122131
ignore = []
123132
paths = None
133+
has_o_flag = False
134+
135+
for deprecated, replacement in STANC_DEPRECATED_OPTS.items():
136+
if deprecated in self._stanc_options:
137+
if replacement:
138+
get_logger().warning(
139+
'compiler option "%s" is deprecated, use "%s" instead',
140+
deprecated,
141+
replacement,
142+
)
143+
self._stanc_options[replacement] = copy(
144+
self._stanc_options[deprecated]
145+
)
146+
del self._stanc_options[deprecated]
147+
else:
148+
get_logger().warning(
149+
'compiler option "%s" is deprecated and '
150+
'should not be used',
151+
deprecated,
152+
)
124153
for key, val in self._stanc_options.items():
125154
if key in STANC_IGNORE_OPTS:
126155
get_logger().info('ignoring compiler option: %s', key)
127156
ignore.append(key)
128157
elif key not in STANC_OPTS:
129158
raise ValueError(f'unknown stanc compiler option: {key}')
130-
elif key == 'include_paths':
159+
elif key == 'include-paths':
131160
paths = val
132161
if isinstance(val, str):
133162
paths = val.split(',')
134163
elif not isinstance(val, list):
135164
raise ValueError(
136-
'Invalid include_paths, expecting list or '
165+
'Invalid include-paths, expecting list or '
137166
f'string, found type: {type(val)}.'
138167
)
139168
elif key == 'use-opencl':
140169
if self._cpp_options is None:
141170
self._cpp_options = {'STAN_OPENCL': 'TRUE'}
142171
else:
143172
self._cpp_options['STAN_OPENCL'] = 'TRUE'
173+
elif key.startswith('O'):
174+
if has_o_flag:
175+
get_logger().warning(
176+
'More than one of (O, O1, O2, Oexperimental)'
177+
'optimizations passed. Only the last one will'
178+
'be used'
179+
)
180+
else:
181+
has_o_flag = True
144182

145183
for opt in ignore:
146184
del self._stanc_options[opt]
147185
if paths is not None:
148-
self._stanc_options['include_paths'] = paths
149-
bad_paths = [
150-
dir
151-
for dir in self._stanc_options['include_paths']
152-
if not os.path.exists(dir)
153-
]
186+
bad_paths = [dir for dir in paths if not os.path.exists(dir)]
154187
if any(bad_paths):
155188
raise ValueError(
156189
'invalid include paths: {}'.format(', '.join(bad_paths))
157190
)
158191

192+
self._stanc_options['include-paths'] = [
193+
os.path.abspath(os.path.expanduser(path)) for path in paths
194+
]
195+
159196
def validate_cpp_opts(self) -> None:
160197
"""
161198
Check cpp compiler args.
@@ -190,8 +227,8 @@ def validate_user_header(self) -> None:
190227
raise ValueError(
191228
f"Header file must end in .hpp, got {self._user_header}"
192229
)
193-
if "allow_undefined" not in self._stanc_options:
194-
self._stanc_options["allow_undefined"] = True
230+
if "allow-undefined" not in self._stanc_options:
231+
self._stanc_options["allow-undefined"] = True
195232
# set full path
196233
self._user_header = os.path.abspath(self._user_header)
197234

@@ -218,7 +255,7 @@ def add(self, new_opts: "CompilerOptions") -> None: # noqa: disable=Q000
218255
self._stanc_options = new_opts.stanc_options
219256
else:
220257
for key, val in new_opts.stanc_options.items():
221-
if key == 'include_paths':
258+
if key == 'include-paths':
222259
self.add_include_path(str(val))
223260
else:
224261
self._stanc_options[key] = val
@@ -230,30 +267,35 @@ def add(self, new_opts: "CompilerOptions") -> None: # noqa: disable=Q000
230267

231268
def add_include_path(self, path: str) -> None:
232269
"""Adds include path to existing set of compiler options."""
233-
if 'include_paths' not in self._stanc_options:
234-
self._stanc_options['include_paths'] = [path]
235-
elif path not in self._stanc_options['include_paths']:
236-
self._stanc_options['include_paths'].append(path)
270+
path = os.path.abspath(os.path.expanduser(path))
271+
if 'include-paths' not in self._stanc_options:
272+
self._stanc_options['include-paths'] = [path]
273+
elif path not in self._stanc_options['include-paths']:
274+
self._stanc_options['include-paths'].append(path)
237275

238-
def compose(self) -> List[str]:
239-
"""Format makefile options as list of strings."""
276+
def compose_stanc(self) -> List[str]:
240277
opts = []
241278
if self._stanc_options is not None and len(self._stanc_options) > 0:
242279
for key, val in self._stanc_options.items():
243-
if key == 'include_paths':
280+
if key == 'include-paths':
244281
opts.append(
245-
'STANCFLAGS+=--include_paths='
282+
'--include-paths='
246283
+ ','.join(
247284
(
248285
Path(p).as_posix()
249-
for p in self._stanc_options['include_paths']
286+
for p in self._stanc_options['include-paths']
250287
)
251288
)
252289
)
253290
elif key == 'name':
254-
opts.append(f'STANCFLAGS+=--name={val}')
291+
opts.append(f'--name={val}')
255292
else:
256-
opts.append(f'STANCFLAGS+=--{key}')
293+
opts.append(f'--{key}')
294+
return opts
295+
296+
def compose(self) -> List[str]:
297+
"""Format makefile options as list of strings."""
298+
opts = ['STANCFLAGS+=' + flag for flag in self.compose_stanc()]
257299
if self._cpp_options is not None and len(self._cpp_options) > 0:
258300
for key, val in self._cpp_options.items():
259301
opts.append(f'{key}={val}')

cmdstanpy/install_cmdstan.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,9 @@ def main(args: Dict[str, Any]) -> None:
460460
print('Installing CmdStan version: {}'.format(version))
461461
else:
462462
raise ValueError(
463-
'Invalid version requested: {}, cannot install.'.format(version)
463+
f'Version {version} cannot be downloaded. '
464+
'Connection to GitHub failed. '
465+
'Check firewall settings or ensure this version exists.'
464466
)
465467

466468
cmdstan_dir = os.path.expanduser(os.path.join('~', _DOT_CMDSTAN))

cmdstanpy/model.py

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ def __init__(
128128
)
129129
self._name = model_name.strip()
130130

131+
self._compiler_options.validate()
132+
131133
if stan_file is None:
132134
if exe_file is None:
133135
raise ValueError(
@@ -146,30 +148,25 @@ def __init__(
146148
if not self._name:
147149
self._name, _ = os.path.splitext(filename)
148150

149-
# TODO: When minimum version is 2.27, use --info instead
150151
# if program has include directives, record path
151152
with open(self._stan_file, 'r') as fd:
152153
program = fd.read()
153154
if '#include' in program:
154155
path, _ = os.path.split(self._stan_file)
155-
if self._compiler_options is None:
156-
self._compiler_options = CompilerOptions(
157-
stanc_options={'include_paths': [path]}
158-
)
159-
elif self._compiler_options._stanc_options is None:
156+
if self._compiler_options._stanc_options is None:
160157
self._compiler_options._stanc_options = {
161-
'include_paths': [path]
158+
'include-paths': [path]
162159
}
163160
else:
164161
self._compiler_options.add_include_path(path)
165162

166163
# try to detect models w/out parameters, needed for sampler
167-
if not cmdstan_version_before(2, 27) and cmdstan_version_before(
168-
2, 29
169-
):
164+
if not cmdstan_version_before(
165+
2, 27
166+
): # unknown end of version range
170167
model_info = self.src_info()
171168
if 'parameters' in model_info:
172-
self._fixed_param = len(model_info['parameters']) == 0
169+
self._fixed_param |= len(model_info['parameters']) == 0
173170

174171
if exe_file is not None:
175172
self._exe_file = os.path.realpath(os.path.expanduser(exe_file))
@@ -186,8 +183,6 @@ def __init__(
186183
' found: {}.'.format(self._name, exename)
187184
)
188185

189-
self._compiler_options.validate()
190-
191186
if platform.system() == 'Windows':
192187
try:
193188
do_command(['where.exe', 'tbb.dll'], fd_out=None)
@@ -279,17 +274,26 @@ def src_info(self) -> Dict[str, Any]:
279274
if self.stan_file is None:
280275
return result
281276
try:
282-
283-
cmd = [
284-
os.path.join('.', 'bin', 'stanc' + EXTENSION),
285-
'--info',
286-
self.stan_file,
287-
]
288-
sout = io.StringIO()
289-
do_command(cmd=cmd, cwd=cmdstan_path(), fd_out=sout)
290-
result = json.loads(sout.getvalue())
277+
cmd = (
278+
[os.path.join(cmdstan_path(), 'bin', 'stanc' + EXTENSION)]
279+
# handle include-paths, allow-undefined etc
280+
+ self._compiler_options.compose_stanc()
281+
+ [
282+
'--info',
283+
self.stan_file,
284+
]
285+
)
286+
proc = subprocess.run(
287+
cmd, capture_output=True, text=True, check=True
288+
)
289+
result = json.loads(proc.stdout)
291290
return result
292-
except (ValueError, RuntimeError) as e:
291+
except (
292+
ValueError,
293+
RuntimeError,
294+
OSError,
295+
subprocess.CalledProcessError,
296+
) as e:
293297
get_logger().debug(e)
294298
return result
295299

0 commit comments

Comments
 (0)