diff --git a/CHANGELOG.md b/CHANGELOG.md index 3305b344df..b5955204a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,6 +79,9 @@ See also our [versioning policy](https://amici.readthedocs.io/en/latest/versioni * The import function `sbml2amici`, `pysb2amici`, and `antimony2amici` now return an instance of the generated model class if called with `compile=True` (default). +* The default directory for model import changed, and a base directory + can now be specified via the `AMICI_MODELS_ROOT` environment variable. + See `amici.get_model_dir` for details. ## v0.X Series diff --git a/python/sdist/amici/__init__.py b/python/sdist/amici/__init__.py index 7bdf4ea5ed..84f907230a 100644 --- a/python/sdist/amici/__init__.py +++ b/python/sdist/amici/__init__.py @@ -76,6 +76,53 @@ def _imported_from_setup() -> bool: return False +def get_model_root_dir() -> Path: + """Get the default root directory for AMICI models. + + :return: + The model root directory. + This defaults to `{base_dir}/{amici_version}`. + If the environment variable `AMICI_MODELS_ROOT` is set, + it is used as `base_dir`, otherwise `amici_models` in the current + working directory. + """ + try: + base_dir = Path(os.environ["AMICI_MODELS_ROOT"]) + except KeyError: + base_dir = Path("amici_models") + + return base_dir / __version__ + + +def get_model_dir(model_id: str | None = None, jax: bool = False) -> Path: + """Get the default directory for the model with the given ID. + + :param model_id: + The model ID. + :param jax: + Whether to get the model directory for a JAX model. + If `True`, a suffix `_jax` is appended to the `model_id`. + :return: + The model directory. + This defaults to `{root_dir}/{model_id}`, where `root_dir` is + determined via :func:`get_model_root_dir`. + If `model_id` is `None`, a temporary directory is created in + `{base_dir}/{amici_version}` and returned. + """ + base_dir = get_model_root_dir() + + suffix = "_jax" if jax else "" + + if model_id is None: + import tempfile + + return Path( + tempfile.mkdtemp(dir=base_dir / __version__), suffix=suffix + ) + + return base_dir / __version__ / (model_id + suffix) + + # Initialize AMICI paths #: absolute root path of the amici repository or Python package amici_path = _get_amici_path() diff --git a/python/sdist/amici/de_export.py b/python/sdist/amici/de_export.py index d87638423d..05498c30fc 100644 --- a/python/sdist/amici/de_export.py +++ b/python/sdist/amici/de_export.py @@ -30,6 +30,7 @@ amiciModulePath, amiciSrcPath, amiciSwigPath, + get_model_dir, splines, ) from ._codegen.cxx_functions import ( @@ -1311,7 +1312,7 @@ def set_paths(self, output_dir: str | Path | None = None) -> None: """ if output_dir is None: - output_dir = os.path.join(os.getcwd(), f"amici-{self.model_name}") + output_dir = get_model_dir(self.model_name) self.model_path = os.path.abspath(output_dir) self.model_swig_path = os.path.join(self.model_path, "swig") diff --git a/python/sdist/amici/petab/petab_import.py b/python/sdist/amici/petab/petab_import.py index ac49894685..28d1b02033 100644 --- a/python/sdist/amici/petab/petab_import.py +++ b/python/sdist/amici/petab/petab_import.py @@ -55,7 +55,7 @@ def import_petab_problem( :param model_output_dir: Directory to write the model code to. It will be created if it doesn't - exist. Defaults to current directory. + exist. Defaults to :func:`amici.get_model_dir`. :param model_name: Name of the generated model module. Defaults to the ID of the model @@ -99,20 +99,11 @@ def import_petab_problem( # generate folder and model name if necessary if model_output_dir is None: - if petab_problem.model.type_id == MODEL_TYPE_PYSB: - raise ValueError("Parameter `model_output_dir` is required.") - - from .sbml_import import _create_model_output_dir_name - - model_output_dir = _create_model_output_dir_name( - petab_problem.sbml_model, model_name, jax=jax - ) + model_output_dir = amici.get_model_dir(model_name, jax=jax).absolute() else: - model_output_dir = os.path.abspath(model_output_dir) + model_output_dir = Path(model_output_dir).absolute() - # create folder - if not os.path.exists(model_output_dir): - os.makedirs(model_output_dir) + model_output_dir.mkdir(parents=True, exist_ok=True) # check if compilation necessary if compile_ or ( diff --git a/python/sdist/amici/petab/sbml_import.py b/python/sdist/amici/petab/sbml_import.py index 801cb6227f..26936de986 100644 --- a/python/sdist/amici/petab/sbml_import.py +++ b/python/sdist/amici/petab/sbml_import.py @@ -2,7 +2,6 @@ import math import os import re -import tempfile from _collections import OrderedDict from itertools import chain from pathlib import Path @@ -604,29 +603,3 @@ def _get_fixed_parameters_sbml( continue return list(sorted(fixed_parameters)) - - -def _create_model_output_dir_name( - sbml_model: "libsbml.Model", - model_name: str | None = None, - jax: bool = False, -) -> Path: - """ - Find a folder for storing the compiled amici model. - If possible, use the sbml model id, otherwise create a random folder. - The folder will be located in the `amici_models` subfolder of the current - folder. - """ - BASE_DIR = Path("amici_models").absolute() - BASE_DIR.mkdir(exist_ok=True) - # try model_name - suffix = "_jax" if jax else "" - if model_name: - return BASE_DIR / (model_name + suffix) - - # try sbml model id - if sbml_model_id := sbml_model.getId(): - return BASE_DIR / (sbml_model_id + suffix) - - # create random folder name - return Path(tempfile.mkdtemp(dir=BASE_DIR)) diff --git a/python/sdist/amici/pysb_import.py b/python/sdist/amici/pysb_import.py index 727e5c59c6..4587bbb572 100644 --- a/python/sdist/amici/pysb_import.py +++ b/python/sdist/amici/pysb_import.py @@ -251,7 +251,7 @@ def pysb2amici( constant_parameters = [] model_name = model_name or model.name - + output_dir = output_dir or amici.get_model_dir(model_name) set_log_level(logger, verbose) ode_model = ode_model_from_pysb_importer( model, diff --git a/python/sdist/amici/sbml_import.py b/python/sdist/amici/sbml_import.py index 4824f38de7..f3717aa21f 100644 --- a/python/sdist/amici/sbml_import.py +++ b/python/sdist/amici/sbml_import.py @@ -28,7 +28,7 @@ import amici -from . import has_clibs +from . import get_model_dir, has_clibs from .constants import SymbolId from .de_export import ( DEExporter, @@ -317,6 +317,7 @@ def sbml2amici( :param output_dir: Directory where the generated model package will be stored. + Defaults to :func:`amici.get_model_dir`. :param constant_parameters: list of SBML Ids identifying constant parameters @@ -398,6 +399,8 @@ def sbml2amici( hardcode_symbols=hardcode_symbols, ) + output_dir = output_dir or get_model_dir(model_name) + exporter = DEExporter( ode_model, model_name=model_name, diff --git a/python/tests/conftest.py b/python/tests/conftest.py index 07654edc9c..3e32bd42ee 100644 --- a/python/tests/conftest.py +++ b/python/tests/conftest.py @@ -2,6 +2,7 @@ import copy import importlib +import os import sys from pathlib import Path @@ -12,7 +13,8 @@ pytest_plugins = ["amici.testing.fixtures"] -EXAMPLES_DIR = Path(__file__).parents[2] / "doc" / "examples" +REPO_ROOT = Path(__file__).parents[2] +EXAMPLES_DIR = REPO_ROOT / "doc" / "examples" TEST_DIR = Path(__file__).parent MODEL_STEADYSTATE_SCALED_XML = ( EXAMPLES_DIR / "getting_started" / "model_steadystate_scaled.xml" @@ -21,6 +23,8 @@ EXAMPLES_DIR / "example_presimulation" / "model_presimulation.xml" ) +os.environ.setdefault("AMICI_MODELS_ROOT", str(REPO_ROOT.absolute())) + @pytest.fixture(scope="session") def sbml_example_presimulation_module(): diff --git a/tests/benchmark_models/conftest.py b/tests/benchmark_models/conftest.py index 09ba3eec3b..7918275179 100644 --- a/tests/benchmark_models/conftest.py +++ b/tests/benchmark_models/conftest.py @@ -5,6 +5,7 @@ import benchmark_models_petab import petab.v1 as petab import pytest +from amici import get_model_root_dir from amici.petab.petab_import import import_petab_problem from petab.v1.lint import measurement_table_has_timepoint_specific_mappings @@ -14,8 +15,7 @@ from test_petab_benchmark import problems -repo_root = script_dir.parent.parent -benchmark_outdir = repo_root / "test_bmc" +benchmark_outdir = get_model_root_dir() / "test_bmc" @pytest.fixture(scope="session", params=problems, ids=problems) diff --git a/tests/benchmark_models/test_petab_benchmark.py b/tests/benchmark_models/test_petab_benchmark.py index 242b45a5ec..0a5ddfb9bc 100644 --- a/tests/benchmark_models/test_petab_benchmark.py +++ b/tests/benchmark_models/test_petab_benchmark.py @@ -20,7 +20,7 @@ import petab.v1 as petab import pytest import yaml -from amici import SensitivityMethod +from amici import SensitivityMethod, get_model_root_dir from amici.adapters.fiddy import simulate_petab_to_cached_functions from amici.logging import get_logger from amici.petab.petab_import import import_petab_problem @@ -44,8 +44,7 @@ ) script_dir = Path(__file__).parent.absolute() -repo_root = script_dir.parent.parent -benchmark_outdir = repo_root / "test_bmc" +benchmark_outdir = get_model_root_dir() / "test_bmc" debug_path = script_dir / "debug" if debug: debug_path.mkdir(exist_ok=True, parents=True) diff --git a/tests/petab_test_suite/test_petab_suite.py b/tests/petab_test_suite/test_petab_suite.py index fae48528f8..d74e870664 100755 --- a/tests/petab_test_suite/test_petab_suite.py +++ b/tests/petab_test_suite/test_petab_suite.py @@ -63,10 +63,8 @@ def _test_case(case, model_type, version, jax): model_name = ( f"petab_{model_type}_test_case_{case}_{version.replace('.', '_')}" ) - model_output_dir = f"amici_models/{model_name}" + ("_jax" if jax else "") imported = import_petab_problem( petab_problem=problem, - model_output_dir=model_output_dir, model_name=model_name, compile_=True, jax=jax,