Source code for bridgestan.compile

import os
import platform
import subprocess
import warnings
from pathlib import Path
from typing import List, Union

from .__version import __version__
from .download import CURRENT_BRIDGESTAN, HOME_BRIDGESTAN, get_bridgestan_src
from .util import validate_readable


def verify_bridgestan_path(path: Union[str, os.PathLike]) -> None:
    folder = Path(path).resolve()
    if not folder.exists():
        raise ValueError(
            f"BridgeStan folder '{folder}' does not exist!\n"
            "If you need to set a different location, call 'set_bridgestan_path()'"
        )
    makefile = folder / "Makefile"
    if not makefile.exists():
        raise ValueError(
            f"BridgeStan folder '{folder}' does not "
            "contain file 'Makefile', please ensure it is built properly!\n"
            "If you need to set a different location, call 'set_bridgestan_path()'"
        )


IS_WINDOWS = platform.system() == "Windows"
WINDOWS_PATH_SET = False

MAKE = os.getenv(
    "MAKE",
    "make" if not IS_WINDOWS else "mingw32-make",
)


[docs] def set_bridgestan_path(path: Union[str, os.PathLike]) -> None: """ Set the path to BridgeStan. This should point to the top-level folder of the repository. """ path = os.path.abspath(path) verify_bridgestan_path(path) os.environ["BRIDGESTAN"] = path
def get_bridgestan_path() -> str: """ Get the path to BridgeStan. By default this is set to the value of the environment variable ``BRIDGESTAN``. If there is no path set, this function will download a matching version of BridgeStan to a folder called ``.bridgestan`` in the user's home directory. See also :func:`set_bridgestan_path` """ path = os.getenv("BRIDGESTAN", "") if path == "": try: path = os.fspath(CURRENT_BRIDGESTAN) verify_bridgestan_path(path) except ValueError: print( "BridgeStan not found at location specified by $BRIDGESTAN " f"environment variable, downloading version {__version__} to {path}" ) get_bridgestan_src() num_files = len(list(HOME_BRIDGESTAN.iterdir())) if num_files >= 5: warnings.warn( f"Found {num_files} different versions of BridgeStan in {HOME_BRIDGESTAN}. " "Consider deleting old versions to save space." ) print("Done!") return path def generate_so_name(model: Path) -> Path: name = model.stem return model.with_stem(f"{name}_model").with_suffix(".so")
[docs] def compile_model( stan_file: Union[str, os.PathLike], *, stanc_args: List[str] = [], make_args: List[str] = [], ) -> Path: """ Run BridgeStan's Makefile on a ``.stan`` file, creating the ``.so`` used by the StanModel class. This function checks that the path to BridgeStan is valid and will error if not. This can be set with :func:`set_bridgestan_path`. :param stan_file: A path to a Stan model file. :param stanc_args: A list of arguments to pass to stanc3. For example, ``["--O1"]`` will enable compiler optimization level 1. :param make_args: A list of additional arguments to pass to Make. For example, ``["STAN_THREADS=True"]`` will enable threading for the compiled model. If the same flags are defined in ``make/local``, the versions passed here will take precedent. :raises FileNotFoundError or PermissionError: If `stan_file` does not exist or is not readable. :raises ValueError: If BridgeStan cannot be located. :raises RuntimeError: If compilation fails. """ verify_bridgestan_path(get_bridgestan_path()) file_path = Path(stan_file).resolve() validate_readable(file_path) if file_path.suffix != ".stan": raise ValueError(f"File '{stan_file}' does not end in .stan") output = generate_so_name(file_path) cmd = ( [MAKE] + make_args + ["STANCFLAGS=" + " ".join(["--include-paths=."] + stanc_args)] + [os.fspath(output)] ) proc = subprocess.run( cmd, cwd=get_bridgestan_path(), capture_output=True, text=True, check=False ) if proc.returncode: error = ( f"Command {' '.join(cmd)} failed with code {proc.returncode}.\n" f"stdout:\n{proc.stdout}\nstderr:\n{proc.stderr}" ) raise RuntimeError(error) return output
def windows_dll_path_setup() -> None: """Add tbb.dll to %PATH% on Windows.""" global WINDOWS_PATH_SET if IS_WINDOWS and not WINDOWS_PATH_SET: try: out = subprocess.run( ["where.exe", "tbb.dll"], check=True, capture_output=True ) tbb_path = os.path.dirname(out.stdout.decode().splitlines()[0]) os.add_dll_directory(tbb_path) except: try: tbb_path = os.path.abspath( os.path.join( get_bridgestan_path(), "stan", "lib", "stan_math", "lib", "tbb" ) ) os.environ["PATH"] = tbb_path + ";" + os.environ["PATH"] os.add_dll_directory(tbb_path) WINDOWS_PATH_SET = True except: warnings.warn( "Unable to set path to TBB's DLL. Loading BridgeStan models may fail. " f"Tried path '{tbb_path}'", RuntimeWarning, ) WINDOWS_PATH_SET = False try: out = subprocess.run( [ "where.exe", "libwinpthread-1.dll", "libgcc_s_seh-1.dll", "libstdc++-6.dll", ], check=True, capture_output=True, ) mingw_dir = os.path.abspath( os.path.dirname(out.stdout.decode().splitlines()[0]) ) os.add_dll_directory(mingw_dir) except: # no default location warnings.warn( "Unable to find MinGW's DLL location. Loading BridgeStan models may fail.", RuntimeWarning, ) WINDOWS_PATH_SET = False