def test_opts_cpp_opencl(self): cpp_opts = {'OPENCL_DEVICE_ID': 1} opts = CompilerOptions(cpp_options=cpp_opts) opts.validate() opts_list = opts.compose() self.assertTrue('STAN_OPENCL=TRUE' in opts_list) self.assertTrue('OPENCL_DEVICE_ID=1' in opts_list) cpp_opts = {'OPENCL_DEVICE_ID': 'BAD'} opts = CompilerOptions(cpp_options=cpp_opts) with self.assertRaises(ValueError): opts.validate() cpp_opts = {'OPENCL_DEVICE_ID': -1} opts = CompilerOptions(cpp_options=cpp_opts) with self.assertRaises(ValueError): opts.validate() cpp_opts = {'OPENCL_PLATFORM_ID': 'BAD'} opts = CompilerOptions(cpp_options=cpp_opts) with self.assertRaises(ValueError): opts.validate() cpp_opts = {'OPENCL_PLATFORM_ID': -1} opts = CompilerOptions(cpp_options=cpp_opts) with self.assertRaises(ValueError): opts.validate()
def test_opts_add(self): stanc_opts = {'warn-uninitialized': True} cpp_opts = {'STAN_OPENCL': 'TRUE', 'OPENCL_DEVICE_ID': 1} opts = CompilerOptions(stanc_options=stanc_opts, cpp_options=cpp_opts) opts.validate() opts_list = opts.compose() self.assertTrue('STAN_OPENCL=TRUE' in opts_list) self.assertTrue('OPENCL_DEVICE_ID=1' in opts_list) new_opts = CompilerOptions(cpp_options={ 'STAN_OPENCL': 'FALSE', 'OPENCL_DEVICE_ID': 2 }) opts.add(new_opts) opts_list = opts.compose() self.assertTrue('STAN_OPENCL=FALSE' in opts_list) self.assertTrue('OPENCL_DEVICE_ID=2' in opts_list) expect = 'STANCFLAGS+=--include_paths=' + DATAFILES_PATH.replace( '\\', '/') stanc_opts2 = {'include_paths': DATAFILES_PATH} new_opts2 = CompilerOptions(stanc_options=stanc_opts2) opts.add(new_opts2) opts_list = opts.compose() self.assertTrue(expect in opts_list) path2 = os.path.join(HERE, 'data', 'optimize') expect = 'STANCFLAGS+=--include_paths=' + ','.join( [DATAFILES_PATH, path2]).replace('\\', '/') stanc_opts3 = {'include_paths': path2} new_opts3 = CompilerOptions(stanc_options=stanc_opts3) opts.add(new_opts3) opts_list = opts.compose() self.assertTrue(expect in opts_list)
def test_user_header(self): header_file = os.path.join(DATAFILES_PATH, 'return_one.hpp') opts = CompilerOptions(user_header=header_file) opts.validate() self.assertTrue(opts.stanc_options['allow_undefined']) bad = os.path.join(DATAFILES_PATH, 'nonexistant.hpp') opts = CompilerOptions(user_header=bad) with self.assertRaisesRegex(ValueError, "cannot be found"): opts.validate() bad_dir = os.path.join(DATAFILES_PATH, 'optimize') opts = CompilerOptions(user_header=bad_dir) with self.assertRaisesRegex(ValueError, "cannot be found"): opts.validate() non_header = os.path.join(DATAFILES_PATH, 'bernoulli.stan') opts = CompilerOptions(user_header=non_header) with self.assertRaisesRegex(ValueError, "must end in .hpp"): opts.validate() header_file = os.path.join(DATAFILES_PATH, 'return_one.hpp') opts = CompilerOptions(user_header=header_file, cpp_options={'USER_HEADER': 'foo'}) with self.assertRaisesRegex(ValueError, "Disagreement"): opts.validate()
def test_opts_cpp(self): cpp_opts = {} opts = CompilerOptions(cpp_options=cpp_opts) opts.validate() self.assertEqual(opts.compose(), []) cpp_opts['STAN_MPI'] = 'TRUE' opts = CompilerOptions(cpp_options=cpp_opts) opts.validate() self.assertEqual(opts.compose(), ['STAN_MPI=TRUE'])
def test_opts_cpp(self): cpp_opts = {} opts = CompilerOptions(cpp_options=cpp_opts) opts.validate() self.assertEqual(opts.compose(), []) cpp_opts['STAN_MPI'] = 'TRUE' opts = CompilerOptions(cpp_options=cpp_opts) opts.validate() self.assertEqual(opts.compose(), ['STAN_MPI=TRUE']) cpp_opts['STAN_BAD'] = 'TRUE' opts = CompilerOptions(cpp_options=cpp_opts) with self.assertRaises(ValueError): opts.validate()
def test_opts_stanc_includes(self): path2 = os.path.join(HERE, 'data', 'optimize') paths_str = ','.join([DATAFILES_PATH, path2]).replace('\\', '/') expect = 'STANCFLAGS+=--include_paths=' + paths_str stanc_opts = {'include_paths': paths_str} opts = CompilerOptions(stanc_options=stanc_opts) opts.validate() opts_list = opts.compose() self.assertTrue(expect in opts_list) stanc_opts = {'include_paths': [DATAFILES_PATH, path2]} opts = CompilerOptions(stanc_options=stanc_opts) opts.validate() opts_list = opts.compose() self.assertTrue(expect in opts_list)
def test_opts_stanc_opencl(self): stanc_opts = {} stanc_opts['use-opencl'] = 'foo' opts = CompilerOptions(stanc_options=stanc_opts) opts.validate() self.assertEqual(opts.compose(), ['STANCFLAGS+=--use-opencl', 'STAN_OPENCL=TRUE'])
def test_opts_empty_eq(self): opts_a = CompilerOptions() self.assertTrue(opts_a.is_empty()) opts_b = None self.assertTrue(opts_a == opts_b) opts_c = CompilerOptions(stanc_options={'--O'}) self.assertTrue(opts_a != opts_c != opts_b) stanc_opts = {} cpp_opts = {'STAN_THREADS': 'T'} opts_c = CompilerOptions(stanc_options=stanc_opts, cpp_options=cpp_opts) self.assertFalse(opts_c.is_empty()) self.assertFalse(opts_a == opts_c)
def test_opts_empty(self): opts = CompilerOptions() opts.validate() self.assertEqual(opts.compose(), []) self.assertEqual(opts.__repr__(), 'stanc_options={}, cpp_options={}') stanc_opts = {} opts = CompilerOptions(stanc_options=stanc_opts) opts.validate() self.assertEqual(opts.compose(), []) cpp_opts = {} opts = CompilerOptions(cpp_options=cpp_opts) opts.validate() self.assertEqual(opts.compose(), []) opts = CompilerOptions(stanc_options=stanc_opts, cpp_options=cpp_opts) opts.validate() self.assertEqual(opts.compose(), []) self.assertEqual(opts.__repr__(), 'stanc_options={}, cpp_options={}')
def test_opts_stanc(self): stanc_opts = {} opts = CompilerOptions() opts.validate() self.assertEqual(opts.compose(), []) opts = CompilerOptions(stanc_options=stanc_opts) opts.validate() self.assertEqual(opts.compose(), []) stanc_opts['warn-uninitialized'] = True opts = CompilerOptions(stanc_options=stanc_opts) opts.validate() self.assertEqual(opts.compose(), ['STANCFLAGS+=--warn-uninitialized']) stanc_opts['name'] = 'foo' opts = CompilerOptions(stanc_options=stanc_opts) opts.validate() self.assertEqual( opts.compose(), ['STANCFLAGS+=--warn-uninitialized', 'STANCFLAGS+=--name=foo'], )
def test_opts_add_include_paths(self): expect = 'STANCFLAGS+=--include_paths=' + DATAFILES_PATH.replace( '\\', '/') stanc_opts = {'warn-uninitialized': True} opts = CompilerOptions(stanc_options=stanc_opts) opts.validate() opts_list = opts.compose() self.assertTrue(expect not in opts_list) opts.add_include_path(DATAFILES_PATH) opts.validate() opts_list = opts.compose() self.assertTrue(expect in opts_list) path2 = os.path.join(HERE, 'data', 'optimize') paths_str = ','.join([DATAFILES_PATH, path2]).replace('\\', '/') expect = 'STANCFLAGS+=--include_paths=' + paths_str opts.add_include_path(path2) opts.validate() opts_list = opts.compose() self.assertTrue(expect in opts_list)
def __init__( self, model_name: str = None, stan_file: str = None, exe_file: str = None, compile: bool = True, stanc_options: Dict = None, cpp_options: Dict = None, logger: logging.Logger = None, ) -> None: """ Initialize object given constructor args. :param model_name: Model name, used for output file names. :param stan_file: Path to Stan program file. :param exe_file: Path to compiled executable file. :param compile: Whether or not to compile the model. :param stanc_options: Options for stanc compiler. :param cpp_options: Options for C++ compiler. :param logger: Python logger object. """ self._name = None self._stan_file = None self._exe_file = None self._compiler_options = CompilerOptions(stanc_options=stanc_options, cpp_options=cpp_options) self._logger = logger or get_logger() if model_name is not None: if not model_name.strip(): raise ValueError( 'Invalid value for argument model name, found "{}"'.format( model_name)) self._name = model_name.strip() if stan_file is None: if exe_file is None: raise ValueError( 'Missing model file arguments, you must specify ' 'either Stan source or executable program file or both.') else: self._stan_file = os.path.realpath(os.path.expanduser(stan_file)) if not os.path.exists(self._stan_file): raise ValueError('no such file {}'.format(self._stan_file)) _, filename = os.path.split(stan_file) if len(filename) < 6 or not filename.endswith('.stan'): raise ValueError('invalid stan filename {}'.format( self._stan_file)) if self._name is None: self._name, _ = os.path.splitext(filename) # if program has include directives, record path with open(self._stan_file, 'r') as fd: program = fd.read() if '#include' in program: path, _ = os.path.split(self._stan_file) if self._compiler_options is None: self._compiler_options = CompilerOptions( stanc_options={'include_paths': [path]}) elif self._compiler_options._stanc_options is None: self._compiler_options._stanc_options = { 'include_paths': [path] } else: self._compiler_options.add_include_path(path) if exe_file is not None: self._exe_file = os.path.realpath(os.path.expanduser(exe_file)) if not os.path.exists(self._exe_file): raise ValueError('no such file {}'.format(self._exe_file)) _, exename = os.path.split(self._exe_file) if self._name is None: self._name, _ = os.path.splitext(exename) else: if self._name != os.path.splitext(exename)[0]: raise ValueError( 'Name mismatch between Stan file and compiled' ' executable, expecting basename: {}' ' found: {}.'.format(self._name, exename)) if self._compiler_options is not None: self._compiler_options.validate() if platform.system() == 'Windows': # Add tbb to the $PATH on Windows libtbb = os.environ.get('STAN_TBB') if libtbb is None: libtbb = os.path.join(cmdstan_path(), 'stan', 'lib', 'stan_math', 'lib', 'tbb') os.environ['PATH'] = ';'.join( list( OrderedDict.fromkeys( [libtbb] + os.environ.get('PATH', '').split(';')))) if compile and self._exe_file is None: self.compile() if self._exe_file is None: raise ValueError( 'Unable to compile Stan model file: {}.'.format( self._stan_file))
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 test_opts_stanc_ignore(self): stanc_opts = {} stanc_opts['auto-format'] = True opts = CompilerOptions(stanc_options=stanc_opts) opts.validate() self.assertEqual(opts.compose(), [])