Skip to content

Commit b78b3bf

Browse files
authored
Centralize default model path generation (#3002)
Currently, there are several places where default model directory paths are generated. Centralize that. Running the test suite or building the documentation creates model directories all over the place. Reduce that. Allow overriding the model base dir via environment variable. Closes #2994.
1 parent 2ab8cf2 commit b78b3bf

File tree

11 files changed

+70
-51
lines changed

11 files changed

+70
-51
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ See also our [versioning policy](https://amici.readthedocs.io/en/latest/versioni
7979
* The import function `sbml2amici`, `pysb2amici`, and `antimony2amici` now
8080
return an instance of the generated model class if called with `compile=True`
8181
(default).
82+
* The default directory for model import changed, and a base directory
83+
can now be specified via the `AMICI_MODELS_ROOT` environment variable.
84+
See `amici.get_model_dir` for details.
8285

8386
## v0.X Series
8487

python/sdist/amici/__init__.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,53 @@ def _imported_from_setup() -> bool:
7676
return False
7777

7878

79+
def get_model_root_dir() -> Path:
80+
"""Get the default root directory for AMICI models.
81+
82+
:return:
83+
The model root directory.
84+
This defaults to `{base_dir}/{amici_version}`.
85+
If the environment variable `AMICI_MODELS_ROOT` is set,
86+
it is used as `base_dir`, otherwise `amici_models` in the current
87+
working directory.
88+
"""
89+
try:
90+
base_dir = Path(os.environ["AMICI_MODELS_ROOT"])
91+
except KeyError:
92+
base_dir = Path("amici_models")
93+
94+
return base_dir / __version__
95+
96+
97+
def get_model_dir(model_id: str | None = None, jax: bool = False) -> Path:
98+
"""Get the default directory for the model with the given ID.
99+
100+
:param model_id:
101+
The model ID.
102+
:param jax:
103+
Whether to get the model directory for a JAX model.
104+
If `True`, a suffix `_jax` is appended to the `model_id`.
105+
:return:
106+
The model directory.
107+
This defaults to `{root_dir}/{model_id}`, where `root_dir` is
108+
determined via :func:`get_model_root_dir`.
109+
If `model_id` is `None`, a temporary directory is created in
110+
`{base_dir}/{amici_version}` and returned.
111+
"""
112+
base_dir = get_model_root_dir()
113+
114+
suffix = "_jax" if jax else ""
115+
116+
if model_id is None:
117+
import tempfile
118+
119+
return Path(
120+
tempfile.mkdtemp(dir=base_dir / __version__), suffix=suffix
121+
)
122+
123+
return base_dir / __version__ / (model_id + suffix)
124+
125+
79126
# Initialize AMICI paths
80127
#: absolute root path of the amici repository or Python package
81128
amici_path = _get_amici_path()

python/sdist/amici/de_export.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
amiciModulePath,
3131
amiciSrcPath,
3232
amiciSwigPath,
33+
get_model_dir,
3334
splines,
3435
)
3536
from ._codegen.cxx_functions import (
@@ -1311,7 +1312,7 @@ def set_paths(self, output_dir: str | Path | None = None) -> None:
13111312
13121313
"""
13131314
if output_dir is None:
1314-
output_dir = os.path.join(os.getcwd(), f"amici-{self.model_name}")
1315+
output_dir = get_model_dir(self.model_name)
13151316

13161317
self.model_path = os.path.abspath(output_dir)
13171318
self.model_swig_path = os.path.join(self.model_path, "swig")

python/sdist/amici/petab/petab_import.py

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def import_petab_problem(
5555
5656
:param model_output_dir:
5757
Directory to write the model code to. It will be created if it doesn't
58-
exist. Defaults to current directory.
58+
exist. Defaults to :func:`amici.get_model_dir`.
5959
6060
:param model_name:
6161
Name of the generated model module. Defaults to the ID of the model
@@ -99,20 +99,11 @@ def import_petab_problem(
9999

100100
# generate folder and model name if necessary
101101
if model_output_dir is None:
102-
if petab_problem.model.type_id == MODEL_TYPE_PYSB:
103-
raise ValueError("Parameter `model_output_dir` is required.")
104-
105-
from .sbml_import import _create_model_output_dir_name
106-
107-
model_output_dir = _create_model_output_dir_name(
108-
petab_problem.sbml_model, model_name, jax=jax
109-
)
102+
model_output_dir = amici.get_model_dir(model_name, jax=jax).absolute()
110103
else:
111-
model_output_dir = os.path.abspath(model_output_dir)
104+
model_output_dir = Path(model_output_dir).absolute()
112105

113-
# create folder
114-
if not os.path.exists(model_output_dir):
115-
os.makedirs(model_output_dir)
106+
model_output_dir.mkdir(parents=True, exist_ok=True)
116107

117108
# check if compilation necessary
118109
if compile_ or (

python/sdist/amici/petab/sbml_import.py

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import math
33
import os
44
import re
5-
import tempfile
65
from _collections import OrderedDict
76
from itertools import chain
87
from pathlib import Path
@@ -604,29 +603,3 @@ def _get_fixed_parameters_sbml(
604603
continue
605604

606605
return list(sorted(fixed_parameters))
607-
608-
609-
def _create_model_output_dir_name(
610-
sbml_model: "libsbml.Model",
611-
model_name: str | None = None,
612-
jax: bool = False,
613-
) -> Path:
614-
"""
615-
Find a folder for storing the compiled amici model.
616-
If possible, use the sbml model id, otherwise create a random folder.
617-
The folder will be located in the `amici_models` subfolder of the current
618-
folder.
619-
"""
620-
BASE_DIR = Path("amici_models").absolute()
621-
BASE_DIR.mkdir(exist_ok=True)
622-
# try model_name
623-
suffix = "_jax" if jax else ""
624-
if model_name:
625-
return BASE_DIR / (model_name + suffix)
626-
627-
# try sbml model id
628-
if sbml_model_id := sbml_model.getId():
629-
return BASE_DIR / (sbml_model_id + suffix)
630-
631-
# create random folder name
632-
return Path(tempfile.mkdtemp(dir=BASE_DIR))

python/sdist/amici/pysb_import.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ def pysb2amici(
252252
constant_parameters = []
253253

254254
model_name = model_name or model.name
255-
255+
output_dir = output_dir or amici.get_model_dir(model_name)
256256
set_log_level(logger, verbose)
257257
ode_model = ode_model_from_pysb_importer(
258258
model,

python/sdist/amici/sbml_import.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
import amici
3030

31-
from . import has_clibs
31+
from . import get_model_dir, has_clibs
3232
from .constants import SymbolId
3333
from .de_export import (
3434
DEExporter,
@@ -317,6 +317,7 @@ def sbml2amici(
317317
318318
:param output_dir:
319319
Directory where the generated model package will be stored.
320+
Defaults to :func:`amici.get_model_dir`.
320321
321322
:param constant_parameters:
322323
list of SBML Ids identifying constant parameters
@@ -398,6 +399,8 @@ def sbml2amici(
398399
hardcode_symbols=hardcode_symbols,
399400
)
400401

402+
output_dir = output_dir or get_model_dir(model_name)
403+
401404
exporter = DEExporter(
402405
ode_model,
403406
model_name=model_name,

python/tests/conftest.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import copy
44
import importlib
5+
import os
56
import sys
67
from pathlib import Path
78

@@ -12,7 +13,8 @@
1213

1314
pytest_plugins = ["amici.testing.fixtures"]
1415

15-
EXAMPLES_DIR = Path(__file__).parents[2] / "doc" / "examples"
16+
REPO_ROOT = Path(__file__).parents[2]
17+
EXAMPLES_DIR = REPO_ROOT / "doc" / "examples"
1618
TEST_DIR = Path(__file__).parent
1719
MODEL_STEADYSTATE_SCALED_XML = (
1820
EXAMPLES_DIR / "getting_started" / "model_steadystate_scaled.xml"
@@ -21,6 +23,8 @@
2123
EXAMPLES_DIR / "example_presimulation" / "model_presimulation.xml"
2224
)
2325

26+
os.environ.setdefault("AMICI_MODELS_ROOT", str(REPO_ROOT.absolute()))
27+
2428

2529
@pytest.fixture(scope="session")
2630
def sbml_example_presimulation_module():

tests/benchmark_models/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import benchmark_models_petab
66
import petab.v1 as petab
77
import pytest
8+
from amici import get_model_root_dir
89
from amici.petab.petab_import import import_petab_problem
910
from petab.v1.lint import measurement_table_has_timepoint_specific_mappings
1011

@@ -14,8 +15,7 @@
1415

1516
from test_petab_benchmark import problems
1617

17-
repo_root = script_dir.parent.parent
18-
benchmark_outdir = repo_root / "test_bmc"
18+
benchmark_outdir = get_model_root_dir() / "test_bmc"
1919

2020

2121
@pytest.fixture(scope="session", params=problems, ids=problems)

tests/benchmark_models/test_petab_benchmark.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import petab.v1 as petab
2121
import pytest
2222
import yaml
23-
from amici import SensitivityMethod
23+
from amici import SensitivityMethod, get_model_root_dir
2424
from amici.adapters.fiddy import simulate_petab_to_cached_functions
2525
from amici.logging import get_logger
2626
from amici.petab.petab_import import import_petab_problem
@@ -44,8 +44,7 @@
4444
)
4545

4646
script_dir = Path(__file__).parent.absolute()
47-
repo_root = script_dir.parent.parent
48-
benchmark_outdir = repo_root / "test_bmc"
47+
benchmark_outdir = get_model_root_dir() / "test_bmc"
4948
debug_path = script_dir / "debug"
5049
if debug:
5150
debug_path.mkdir(exist_ok=True, parents=True)

0 commit comments

Comments
 (0)