def test_non_spaces_location(self): good_path = os.path.join(TMPDIR, 'good_dir') with TemporaryCopiedFile(good_path) as (p, is_changed): self.assertEqual(p, good_path) self.assertFalse(is_changed) # prepare files for test bad_path = os.path.join(TMPDIR, 'bad dir') os.makedirs(bad_path, exist_ok=True) stan = os.path.join(datafiles_path, 'bernoulli.stan') stan_bad = os.path.join(bad_path, 'bad name.stan') shutil.copy(stan, stan_bad) stan_copied = None try: with TemporaryCopiedFile(stan_bad) as (p, is_changed): stan_copied = p self.assertTrue(os.path.exists(stan_copied)) self.assertTrue(' ' not in stan_copied) self.assertTrue(is_changed) raise RuntimeError except RuntimeError: pass if platform.system() != 'Windows': self.assertFalse(os.path.exists(stan_copied)) # cleanup after test shutil.rmtree(bad_path, ignore_errors=True)
def compile( self, force: bool = False, stanc_options: Dict = None, cpp_options: Dict = None, override_options: bool = False, ) -> None: """ Compile the given Stan program file. Translates the Stan code to C++, then calls the C++ compiler. By default, this function compares the timestamps on the source and executable files; if the executable is newer than the source file, it will not recompile the file, unless argument ``force`` is ``True``. :param force: When ``True``, always compile, even if the executable file is newer than the source file. Used for Stan models which have ``#include`` directives in order to force recompilation when changes are made to the included files. :param stanc_options: Options for stanc compiler. :param cpp_options: Options for C++ compiler. :param override_options: When ``True``, override existing option. When ``False``, add/replace existing options. Default is ``False``. """ if not self._stan_file: raise RuntimeError('Please specify source file') compiler_options = None if not (stanc_options is None and cpp_options is None): compiler_options = CompilerOptions(stanc_options=stanc_options, cpp_options=cpp_options) compiler_options.validate() if self._compiler_options is None: self._compiler_options = compiler_options elif override_options: self._compiler_options = compiler_options else: self._compiler_options.add(compiler_options) compilation_failed = False with TemporaryCopiedFile(self._stan_file) as (stan_file, is_copied): exe_file, _ = os.path.splitext(os.path.abspath(stan_file)) exe_file = Path(exe_file).as_posix() + EXTENSION do_compile = True if os.path.exists(exe_file): src_time = os.path.getmtime(self._stan_file) exe_time = os.path.getmtime(exe_file) if exe_time > src_time and not force: do_compile = False self._logger.info('found newer exe file, not recompiling') if do_compile: self._logger.info('compiling stan program, exe file: %s', exe_file) if self._compiler_options is not None: self._compiler_options.validate() self._logger.info('compiler options: %s', self._compiler_options) make = os.getenv( 'MAKE', 'make' if platform.system() != 'Windows' else 'mingw32-make', ) cmd = [make] if self._compiler_options is not None: cmd.extend(self._compiler_options.compose()) cmd.append(Path(exe_file).as_posix()) try: do_command(cmd, cmdstan_path(), logger=self._logger) except RuntimeError as e: self._logger.error('file %s, exception %s', stan_file, str(e)) compilation_failed = True if not compilation_failed: if is_copied: original_target_dir = os.path.dirname( os.path.abspath(self._stan_file)) new_exec_name = (os.path.basename( os.path.splitext(self._stan_file)[0]) + EXTENSION) self._exe_file = os.path.join(original_target_dir, new_exec_name) shutil.copy(exe_file, self._exe_file) else: self._exe_file = exe_file self._logger.info('compiled model file: %s', self._exe_file) else: self._logger.error('model compilation failed')
def compile( self, opt_lvl: int = 2, overwrite: bool = False, include_paths: List[str] = None, ) -> None: """ Compile the given Stan program file. Translates the Stan code to C++, then calls the C++ compiler. :param opt_lvl: Optimization level used by the C++ compiler, one of {0, 1, 2, 3}. Defaults to level 2. Level 0 optimization results in the shortest compilation time with code that may run slowly. Higher optimization levels increase runtime performance but will take longer to compile. :param overwrite: When True, existing executable will be overwritten. Defaults to False. :param include_paths: List of paths to directories where Stan should look for files to include in compilation of the C++ executable. """ if not self._stan_file: raise RuntimeError('Please specify source file') if self._exe_file is not None and not overwrite: self._logger.warning('model is already compiled') return with TemporaryCopiedFile(self._stan_file) as (stan_file, is_copied): hpp_file = os.path.splitext(stan_file)[0] + '.hpp' hpp_file = Path(hpp_file).as_posix() if overwrite or not os.path.exists(hpp_file): self._logger.info('stan to c++ (%s)', hpp_file) stanc_path = os.path.join(cmdstan_path(), 'bin', 'stanc' + EXTENSION) stanc_path = Path(stanc_path).as_posix() cmd = [ stanc_path, '--o={}'.format(hpp_file), Path(stan_file).as_posix(), ] if include_paths is not None: bad_paths = [ d for d in include_paths if not os.path.exists(d) ] if any(bad_paths): raise Exception('invalid include paths: {}'.format( ', '.join(bad_paths))) cmd.append('--include_paths=' + ','.join((Path(p).as_posix() for p in include_paths))) do_command(cmd, logger=self._logger) if not os.path.exists(hpp_file): raise Exception('syntax error'.format(stan_file)) exe_file, _ = os.path.splitext(os.path.abspath(stan_file)) exe_file = Path(exe_file).as_posix() exe_file += EXTENSION make = os.getenv('MAKE', 'make') cmd = [make, 'O={}'.format(opt_lvl), exe_file] self._logger.info('compiling c++') try: do_command(cmd, cmdstan_path(), self._logger) except Exception as e: self._logger.error('make cmd failed %s', e) if is_copied: original_target_dir = os.path.dirname(self._stan_file) # reconstruct the output file name new_exec_name = ( os.path.basename(os.path.splitext(self._stan_file)[0]) + EXTENSION) self._exe_file = os.path.join(original_target_dir, new_exec_name) # copy the generated file back to the original directory shutil.copy(exe_file, self._exe_file) else: self._exe_file = exe_file self._logger.info('compiled model file: %s', self._exe_file)
def compile(self, opt_lvl: int = 3, force: bool = False) -> None: """ Compile the given Stan program file. Translates the Stan code to C++, then calls the C++ compiler. By default, this function compares the timestamps on the source and executable files; if the executable is newer than the source file, it will not recompile the file, unless argument ``force`` is True. :param opt_lvl: Optimization level used by the C++ compiler, one of {0, 1, 2, 3}. Defaults to level 2. Level 0 optimization results in the shortest compilation time with code that may run slowly. Higher optimization levels increase runtime performance but will take longer to compile. :param force: When ``True``, always compile, even if the executable file is newer than the source file. Used for Stan models which have ``#include`` directives in order to force recompilation when changes are made to the included files. """ if not self._stan_file: raise RuntimeError('Please specify source file') compilation_failed = False with TemporaryCopiedFile(self._stan_file) as (stan_file, is_copied): exe_file, _ = os.path.splitext(os.path.abspath(stan_file)) exe_file = Path(exe_file).as_posix() exe_file += EXTENSION do_compile = True if os.path.exists(exe_file): src_time = os.path.getmtime(self._stan_file) exe_time = os.path.getmtime(exe_file) if exe_time > src_time and not force: do_compile = False self._logger.info('found newer exe file, not recompiling') if do_compile: make = os.getenv( 'MAKE', 'make' if platform.system() != 'Windows' else 'mingw32-make', ) hpp_file = os.path.splitext(stan_file)[0] + '.hpp' hpp_file = Path(hpp_file).as_posix() if not os.path.exists(hpp_file): self._logger.info('stan to c++ (%s)', hpp_file) cmd = [ make, Path(exe_file).as_posix(), 'STANCFLAGS+=--o={}'.format(hpp_file), ] if self._include_paths is not None: bad_paths = [ d for d in self._include_paths if not os.path.exists(d) ] if any(bad_paths): raise ValueError( 'invalid include paths: {}'.format( ', '.join(bad_paths) ) ) cmd.append( 'STANCFLAGS+=--include_paths=' + ','.join( ( Path(p).as_posix() for p in self._include_paths ) ) ) try: do_command(cmd, cmdstan_path(), logger=self._logger) except RuntimeError as e: self._logger.error( 'file %s, exception %s', stan_file, str(e) ) compilation_failed = True if not compilation_failed: cmd = [make, 'O={}'.format(opt_lvl), exe_file] self._logger.info('compiling c++') try: do_command(cmd, cmdstan_path(), logger=self._logger) except RuntimeError as e: self._logger.error('make cmd failed %s', repr(e)) compilation_failed = True if not compilation_failed: if is_copied: original_target_dir = os.path.dirname( os.path.abspath(self._stan_file) ) new_exec_name = ( os.path.basename(os.path.splitext(self._stan_file)[0]) + EXTENSION ) self._exe_file = os.path.join( original_target_dir, new_exec_name ) shutil.copy(exe_file, self._exe_file) else: self._exe_file = exe_file self._logger.info('compiled model file: %s', self._exe_file) else: self._logger.error('model compilation failed')