Skip to content
This repository was archived by the owner on Mar 19, 2021. It is now read-only.

Commit 5f730bf

Browse files
authored
Update CVODES compilation procedure (#637)
* Compile CVODES functions with each Stan model In 2017, model compilation takes ~40-50 seconds. Compiling CVODES functions with each model adds about 7 seconds. Closes #209. * update Sundials in model.py * Update Sundials building procedure. * add test for Sundials * update platform.platform to platform.system and use tmp folder for Windows * minimal change * add travis tests
1 parent f772a0f commit 5f730bf

File tree

3 files changed

+142
-37
lines changed

3 files changed

+142
-37
lines changed

.appveyor.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ test_script:
9696
- ECHO "Starting the tests"
9797
- "python -c \"import nose; nose.main()\" --nocapture pystan.tests.test_basic \
9898
pystan.tests.test_basic_array pystan.tests.test_basic_matrix \
99+
pystan.tests.test_cvodes \
99100
pystan.tests.test_basic_pars pystan.tests.test_ess \
100101
pystan.tests.test_extract pystan.tests.test_fixed_param \
101102
pystan.tests.test_linear_regression pystan.tests.test_misc \

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ branches:
55
only:
66
- master
77
- develop
8+
- cvodes
89
os:
910
- linux
1011
git:

pystan/model.py

Lines changed: 140 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
import time
2626

2727
import distutils
28-
from distutils.core import Extension
28+
from distutils.core import Extension, Distribution
29+
from distutils.command.build_clib import build_clib
2930

3031
import Cython
3132
from Cython.Build.Inline import _get_build_extension
@@ -39,6 +40,66 @@
3940

4041
logger = logging.getLogger('pystan')
4142

43+
def _build_libraries(self, libraries):
44+
"""Fork distutils.build_clib.build_libraries to enable compiler flags."""
45+
for (lib_name, build_info) in libraries:
46+
sources = build_info.get('sources')
47+
if sources is None or not isinstance(sources, (list, tuple)):
48+
raise DistutilsSetupError(
49+
"in 'libraries' option (library '%s'), "
50+
"'sources' must be present and must be "
51+
"a list of source filenames" % lib_name)
52+
sources = list(sources)
53+
54+
distutils.command.build_clib.log.info("building '%s' library", lib_name)
55+
56+
macros = build_info.get('macros')
57+
include_dirs = build_info.get('include_dirs')
58+
extra_postargs = build_info.get('extra_postargs')
59+
objects = self.compiler.compile(sources,
60+
output_dir=self.build_temp,
61+
macros=macros,
62+
include_dirs=include_dirs,
63+
extra_postargs=extra_postargs,
64+
debug=self.debug)
65+
66+
self.compiler.create_static_lib(objects, lib_name,
67+
output_dir=self.build_clib,
68+
debug=self.debug)
69+
70+
# overwrite the default class method
71+
build_clib.build_libraries = _build_libraries
72+
73+
def _get_build_clib():
74+
"""Convenience function to create build_clib class instance."""
75+
dist = Distribution()
76+
config_files = dist.find_config_files()
77+
dist.parse_config_files(config_files)
78+
build_clibrary = build_clib(dist)
79+
build_clibrary.finalize_options()
80+
return build_clibrary
81+
82+
def _build_clib(sources, include_dirs_c, lib_dir, extra_args):
83+
"""Build C-library."""
84+
libraries = [("libsundials", {
85+
"sources" : sources,
86+
"include_dirs" : include_dirs_c,
87+
"extra_postargs" : extra_args,
88+
})]
89+
90+
build_clibrary = _get_build_clib()
91+
build_clibrary.libraries = libraries
92+
build_clibrary.include_dirs = include_dirs_c
93+
build_clibrary.build_temp = lib_dir
94+
build_clibrary.build_clib = lib_dir
95+
build_clibrary.run()
96+
97+
objects = []
98+
for _, build_info in libraries:
99+
obj = build_clibrary.compiler.object_filenames(build_info["sources"], output_dir=lib_dir)
100+
objects.extend(obj)
101+
102+
return objects
42103

43104
def load_module(module_name, module_path):
44105
"""Load the module named `module_name` from `module_path`
@@ -309,58 +370,95 @@ def __init__(self, file=None, charset='utf-8', model_name="anon_model",
309370
s = template.safe_substitute(model_cppname=self.model_cppname)
310371
outfile.write(s)
311372

312-
## cvodes sources
373+
if extra_compile_args is None:
374+
extra_compile_args = []
375+
376+
distutils.log.set_verbosity(verbose)
377+
build_extension = _get_build_extension()
378+
## sundials sources
313379

314-
# cvodes sources are complied and linked together with the Stan model
380+
# sundials sources are compiled and linked together with the Stan model
315381
# extension module. This is not ideal. In theory, build_clib could be
316382
# used to build a library once and models would be complied and then
317383
# linked with this library. This would save 7 or more seconds from every build.
318384
# But such a strategy is frustrated by the
319385
# lack of ``install_clib`` functionality in Python's distutils.
320386
#
321387
# TODO: numpy provides install_clib functionality, use that.
322-
cvodes_src_path = os.path.join(pystan_dir, 'stan', 'lib', 'stan_math', 'lib', 'cvodes_2.9.0', 'src')
323-
cvodes_sources = [
324-
os.path.join(cvodes_src_path, 'cvodes', 'cvodea.c'),
325-
os.path.join(cvodes_src_path, 'cvodes', 'cvodea_io.c'),
326-
os.path.join(cvodes_src_path, 'cvodes', 'cvodes.c'),
327-
os.path.join(cvodes_src_path, 'cvodes', 'cvodes_band.c'),
328-
os.path.join(cvodes_src_path, 'cvodes', 'cvodes_bandpre.c'),
329-
os.path.join(cvodes_src_path, 'cvodes', 'cvodes_bbdpre.c'),
330-
os.path.join(cvodes_src_path, 'cvodes', 'cvodes_dense.c'),
331-
os.path.join(cvodes_src_path, 'cvodes', 'cvodes_diag.c'),
332-
os.path.join(cvodes_src_path, 'cvodes', 'cvodes_direct.c'),
333-
os.path.join(cvodes_src_path, 'cvodes', 'cvodes_io.c'),
334-
os.path.join(cvodes_src_path, 'cvodes', 'cvodes_sparse.c'),
335-
os.path.join(cvodes_src_path, 'cvodes', 'cvodes_spbcgs.c'),
336-
os.path.join(cvodes_src_path, 'cvodes', 'cvodes_spgmr.c'),
337-
os.path.join(cvodes_src_path, 'cvodes', 'cvodes_spils.c'),
338-
os.path.join(cvodes_src_path, 'cvodes', 'cvodes_sptfqmr.c'),
339-
os.path.join(cvodes_src_path, 'nvec_ser', 'nvector_serial.c'),
340-
os.path.join(cvodes_src_path, 'sundials', 'sundials_band.c'),
341-
os.path.join(cvodes_src_path, 'sundials', 'sundials_dense.c'),
342-
os.path.join(cvodes_src_path, 'sundials', 'sundials_direct.c'),
343-
os.path.join(cvodes_src_path, 'sundials', 'sundials_iterative.c'),
344-
os.path.join(cvodes_src_path, 'sundials', 'sundials_math.c'),
345-
os.path.join(cvodes_src_path, 'sundials', 'sundials_nvector.c'),
346-
os.path.join(cvodes_src_path, 'sundials', 'sundials_spbcgs.c'),
347-
os.path.join(cvodes_src_path, 'sundials', 'sundials_spgmr.c'),
348-
os.path.join(cvodes_src_path, 'sundials', 'sundials_sptfqmr.c'),
388+
389+
sundials_excluded = {
390+
"nvector/openmp",
391+
"nvector/openmpdev",
392+
"nvector/parallel",
393+
"nvector/parhyp",
394+
"nvector/petsc",
395+
"nvector/pthreads",
396+
"sundials_mpi",
397+
"sunlinsol/klu",
398+
"sunlinsol/lapack",
399+
"sunlinsol/super",
400+
}
401+
402+
sundials_src_path = os.path.join(pystan_dir, 'stan', 'lib', 'stan_math', 'lib', 'sundials_4.1.0', 'src')
403+
sundials_sources = []
404+
for sun_root, _, sunfiles in os.walk(sundials_src_path):
405+
for sunfile in sunfiles:
406+
if os.path.splitext(sunfile)[1] == ".c":
407+
path = os.path.join(sun_root, sunfile).replace("\\", "/")
408+
if not any(item in path for item in sundials_excluded):
409+
sundials_sources.append(path)
410+
411+
include_dirs_c = [
412+
lib_dir,
413+
os.path.join(pystan_dir, "stan", "lib", "stan_math", "lib", "sundials_4.1.0", "include"),
349414
]
350415

416+
extra_compile_args_c = []
417+
if platform.system() == 'Windows':
418+
if build_extension.compiler in (None, 'msvc'):
419+
logger.warning("MSVC compiler is not supported")
420+
extra_compile_args_c.extend([
421+
'/EHsc',
422+
'-DBOOST_DATE_TIME_NO_LIB'
423+
])
424+
else:
425+
# Windows, but not msvc, likely mingw
426+
# fix bug in MingW-W64
427+
# use posix threads
428+
extra_compile_args_c.extend([
429+
'-O2',
430+
'-Wno-unused-function',
431+
'-Wno-uninitialized',
432+
'-std=c11',
433+
"-D_hypot=hypot",
434+
"-pthread",
435+
"-fexceptions",
436+
"-include",
437+
"stan_sundials_printf_override.hpp",
438+
] + [item for item in extra_compile_args if "std=c++" not in extra_compile_args])
439+
else:
440+
# linux or macOS
441+
extra_compile_args_c.extend([
442+
'-O2',
443+
'-Wno-unused-function',
444+
'-Wno-uninitialized',
445+
'-std=c11',
446+
"-include",
447+
"stan_sundials_printf_override.hpp",
448+
] + [item for item in extra_compile_args if "std=c++" not in extra_compile_args])
449+
450+
sundials_objects = _build_clib(sundials_sources, include_dirs_c, lib_dir, extra_compile_args_c)
451+
351452
stan_macros = [
352453
('BOOST_RESULT_OF_USE_TR1', None),
353454
('BOOST_NO_DECLTYPE', None),
354455
('BOOST_DISABLE_ASSERTS', None),
355456
]
356457

357-
build_extension = _get_build_extension()
358458
# compile stan models with optimization (-O2)
359459
# (stanc is compiled without optimization (-O0) currently, see #33)
360-
if extra_compile_args is None:
361-
extra_compile_args = []
362460

363-
if platform.platform().startswith('Win'):
461+
if platform.system() == 'Windows':
364462
if build_extension.compiler in (None, 'msvc'):
365463
logger.warning("MSVC compiler is not supported")
366464
extra_compile_args = [
@@ -381,6 +479,8 @@ def __init__(self, file=None, charset='utf-8', model_name="anon_model",
381479
"-D_hypot=hypot",
382480
"-pthread",
383481
"-fexceptions",
482+
"-include",
483+
"stan_sundials_printf_override.hpp",
384484
] + extra_compile_args
385485
else:
386486
# linux or macOS
@@ -390,15 +490,18 @@ def __init__(self, file=None, charset='utf-8', model_name="anon_model",
390490
'-Wno-unused-function',
391491
'-Wno-uninitialized',
392492
'-std=c++1y',
493+
'-include',
494+
'stan_sundials_printf_override.hpp',
393495
] + extra_compile_args
394496

395-
distutils.log.set_verbosity(verbose)
396497
extension = Extension(name=self.module_name,
397498
language="c++",
398-
sources=[pyx_file] + cvodes_sources,
499+
sources=[pyx_file],
399500
define_macros=stan_macros,
400501
include_dirs=include_dirs,
401-
extra_compile_args=extra_compile_args)
502+
extra_objects=sundials_objects,
503+
extra_compile_args=extra_compile_args
504+
)
402505

403506
cython_include_dirs = ['.', pystan_dir]
404507

0 commit comments

Comments
 (0)