Skip to content

Commit

Permalink
Recursive up-to-date checks (ref #614). (#627)
Browse files Browse the repository at this point in the history
undefined
  • Loading branch information
tillahoffmann authored Oct 20, 2022
1 parent fb3dfe2 commit 4d99ecc
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 2 deletions.
9 changes: 7 additions & 2 deletions cmdstanpy/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,9 +474,14 @@ def compile(
self._compiler_options.add(compiler_options)
exe_target = os.path.splitext(self._stan_file)[0] + EXTENSION
if os.path.exists(exe_target):
src_time = os.path.getmtime(self._stan_file)
exe_time = os.path.getmtime(exe_target)
if exe_time > src_time and not force:
included_files = [self._stan_file]
included_files.extend(self.src_info().get('included_files', []))
out_of_date = any(
os.path.getmtime(included_file) > exe_time
for included_file in included_files
)
if not out_of_date and not force:
get_logger().debug('found newer exe file, not recompiling')
if self._exe_file is None: # called from constructor
self._exe_file = exe_target
Expand Down
7 changes: 7 additions & 0 deletions test/data/add_one_model.stan
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
functions {
#include add_one_function.stan
}

generated quantities {
real x = add_one(3);
}
3 changes: 3 additions & 0 deletions test/data/include-path/add_one_function.stan
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
real add_one(real x) {
return x + 1;
}
46 changes: 46 additions & 0 deletions test/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,52 @@ def test_model_info(self):
self.assertIn('theta', model_info_include['parameters'])
self.assertIn('included_files', model_info_include)

def test_compile_with_includes(self):
getmtime = os.path.getmtime
configs = [
('add_one_model.stan', ['include-path']),
('bernoulli_include.stan', []),
]
for stan_file, include_paths in configs:
stan_file = os.path.join(DATAFILES_PATH, stan_file)
include_paths = [
os.path.join(DATAFILES_PATH, path) for path in include_paths
]

# Compile for the first time.
model = CmdStanModel(
stan_file=stan_file,
compile=False,
stanc_options={"include-paths": include_paths},
)
with LogCapture(level=logging.INFO) as log:
model.compile()
log.check_present(
('cmdstanpy', 'INFO', StringComparison('compiling stan file'))
)

# Compile for the second time, ensuring cache is used.
with LogCapture(level=logging.DEBUG) as log:
model.compile()
log.check_present(
('cmdstanpy', 'DEBUG', StringComparison('found newer exe file'))
)

# Compile after modifying included file, ensuring cache is not used.
def _patched_getmtime(filename: str) -> float:
includes = ['divide_real_by_two.stan', 'add_one_function.stan']
if any(filename.endswith(include) for include in includes):
return float('inf')
return getmtime(filename)

with LogCapture(level=logging.INFO) as log, patch(
'os.path.getmtime', side_effect=_patched_getmtime
):
model.compile()
log.check_present(
('cmdstanpy', 'INFO', StringComparison('compiling stan file'))
)

def test_compile_force(self):
if os.path.exists(BERN_EXE):
os.remove(BERN_EXE)
Expand Down

0 comments on commit 4d99ecc

Please sign in to comment.