diff --git a/.github/workflows/run_tox.yml b/.github/workflows/run_tox.yml index 173ea765..25b08dff 100644 --- a/.github/workflows/run_tox.yml +++ b/.github/workflows/run_tox.yml @@ -23,15 +23,15 @@ jobs: - uses: actions/checkout@v2 - - name: Set up Python 3.9 + - name: Set up Python 3.10 uses: actions/setup-python@v2 with: - python-version: '3.9' + python-version: '3.10' - - name: Set up Python 3.10 + - name: Set up Python 3.11 uses: actions/setup-python@v2 with: - python-version: '3.10' + python-version: '3.11' - name: Install build dependencies run: | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1e1f63c8..75eb069e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,7 @@ repos: - id: check-merge-conflict - id: detect-private-key - repo: https://github.com/psf/black - rev: 22.10.0 + rev: 24.3.0 hooks: - id: black - repo: https://github.com/pycqa/isort @@ -19,6 +19,6 @@ repos: name: isort (python) - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.0.278 + rev: v0.3.4 hooks: - id: ruff diff --git a/docs/getting_started.md b/docs/getting_started.md index 68dca0aa..7979bc28 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -4,7 +4,7 @@ This page explains how to install Maud and use its command line interface. ## Installing Maud -Maud is compatible with Python versions 3.9 and above +Maud is compatible with Python versions 3.10 and above. We recommend using a fresh virtual environment to install Maud. To make one and then activate it, run the following commands: diff --git a/maud/data/example_inputs/__init__.py b/maud/data/example_inputs/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/maud/data/example_inputs/example_ode/__init__.py b/maud/data/example_inputs/example_ode/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/maud/data/example_inputs/linear/__init__.py b/maud/data/example_inputs/linear/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/maud/data/example_inputs/linear_multidgf/__init__.py b/maud/data/example_inputs/linear_multidgf/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/maud/data/example_inputs/linear_multidgf/config.toml b/maud/data/example_inputs/linear_multidgf/config.toml new file mode 100644 index 00000000..5e3aaabb --- /dev/null +++ b/maud/data/example_inputs/linear_multidgf/config.toml @@ -0,0 +1,24 @@ +name = "linear_multidgf" +kinetic_model_file = "kinetic_model.toml" +priors_file = "priors.toml" +experiments_file = "experiments.toml" +likelihood = true +steady_state_threshold_abs = 1e-6 + +[cmdstanpy_config] +refresh = 1 +iter_warmup = 300 +iter_sampling = 300 +chains = 4 +save_warmup = true +seed = 1234 + +[ode_solver_config] +abs_tol = 1e-4 +rel_tol = 1e-6 +max_num_steps = 1e6 + +[algebra_solver_config] +abs_tol = 1e-4 +rel_tol = 1e-6 +max_num_steps = 1e6 diff --git a/maud/data/example_inputs/linear_multidgf/experiments.toml b/maud/data/example_inputs/linear_multidgf/experiments.toml new file mode 100644 index 00000000..d0e8c9ea --- /dev/null +++ b/maud/data/example_inputs/linear_multidgf/experiments.toml @@ -0,0 +1,32 @@ +[[experiment]] +id = "condition1" +is_train = true +is_test = true +temperature = 299.0 +measurements = [ + {target_type = "mic", metabolite = "M1", compartment = "c", value = 0.59, error_scale = 0.1}, + {target_type = "mic", metabolite = "M1", compartment = "e", value = 1.09, error_scale = 0.05}, + {target_type = "mic", metabolite = "M2", compartment = "e", value = 1.05, error_scale = 0.05}, + {target_type = "flux", reaction = "r3", value = 0.19, error_scale = 0.1}, + {target_type = "enzyme", enzyme = "r1", value = 1.5, error_scale = 0.1}, + {target_type = "enzyme", enzyme = "r2", value = 1.5, error_scale = 0.1}, + {target_type = "enzyme", enzyme = "r3", value = 1.5, error_scale = 0.1}, +] +initial_state = [ + {metabolite = "M2", compartment = "c", value = 0.38}, +] + +[[experiment]] +id = "condition2" +is_train = true +is_test = true +measurements = [ + {target_type = "mic", metabolite = "M1", compartment = "c", value = 0.54, error_scale = 0.1}, + {target_type = "mic", metabolite = "M2", compartment = "c", value = 0.38, error_scale = 0.1}, + {target_type = "mic", metabolite = "M1", compartment = "e", value = 1.12, error_scale = 0.05}, + {target_type = "mic", metabolite = "M2", compartment = "e", value = 1.14, error_scale = 0.05}, + {target_type = "flux", reaction = "r3", value = 0.39, error_scale = 0.1}, + {target_type = "enzyme", enzyme = "r1", value = 1.5, error_scale = 0.1}, + {target_type = "enzyme", enzyme = "r2", value = 1.5, error_scale = 0.1}, + {target_type = "enzyme", enzyme = "r3", value = 1.5, error_scale = 0.1}, +] diff --git a/maud/data/example_inputs/linear_multidgf/kinetic_model.toml b/maud/data/example_inputs/linear_multidgf/kinetic_model.toml new file mode 100644 index 00000000..f24bd0e1 --- /dev/null +++ b/maud/data/example_inputs/linear_multidgf/kinetic_model.toml @@ -0,0 +1,66 @@ +name = "Example kinetic model" + +compartment = [ + {id = 'c', name = 'cytosol', volume = 1}, + {id = 'e', name = 'external', volume = 1}, +] + +metabolite = [ + {id = "M1", name = "Metabolite number 1"}, + {id = "M2", name = "Metabolite number 2"}, +] + +enzyme = [ + {id = "r1", name = "r1ase", subunits = 1}, + {id = "r2", name = "r2ase", subunits = 1}, + {id = "r3", name = "r3ase", subunits = 1}, +] + +metabolite_in_compartment = [ + {metabolite_id = "M1", compartment_id = "e", balanced = false}, + {metabolite_id = "M1", compartment_id = "c", balanced = true}, + {metabolite_id = "M2", compartment_id = "c", balanced = true}, + {metabolite_id = "M2", compartment_id = "e", balanced = false}, +] +enzyme_reaction = [ + {enzyme_id = "r1", reaction_id = "r1"}, + {enzyme_id = "r2", reaction_id = "r2"}, + {enzyme_id = "r3", reaction_id = "r3"}, +] + +[[reaction]] +id = "r1" +name = "Reaction number 1" +mechanism = "reversible_michaelis_menten" +stoichiometry = { M1_e = -1, M1_c = 1} + +[[reaction]] +id = "r2" +name = "Reaction number 2" +mechanism = "irreversible_michaelis_menten" +stoichiometry = { M1_c = -1, M2_c = 1} + +[[reaction]] +id = "r3" +name = "Reaction number 3" +mechanism = "reversible_michaelis_menten" +stoichiometry = { M2_c = -1, M2_e = 1} +transported_charge = 1 + +[[allostery]] +enzyme_id = "r1" +metabolite_id = "M2" +compartment_id = "c" +modification_type = "activation" + +[[allostery]] +enzyme_id = "r2" +metabolite_id = "M1" +compartment_id = "c" +modification_type = "inhibition" + +[[competitive_inhibition]] +enzyme_id = "r2" +reaction_id = "r2" +metabolite_id = "M1" +compartment_id = "c" diff --git a/maud/data/example_inputs/linear_multidgf/parameters.toml b/maud/data/example_inputs/linear_multidgf/parameters.toml new file mode 100644 index 00000000..85c7ae73 --- /dev/null +++ b/maud/data/example_inputs/linear_multidgf/parameters.toml @@ -0,0 +1,37 @@ +kcat = [ + {exploc = 1, scale = 0.6, reaction = "r1", enzyme = "r1"}, + {exploc = 1, scale = 0.6, reaction = "r2", enzyme = "r2"}, + {exploc = 1, scale = 0.6, reaction = "r3", enzyme = "r3"}, +] + +km = [ + {exploc = 1, scale = 0.6, metabolite = "M1", compartment = "e", enzyme = "r1"}, + {exploc = 1, scale = 0.6, metabolite = "M1", compartment = "c", enzyme = "r1"}, + {exploc = 1, scale = 0.6, metabolite = "M1", compartment = "c", enzyme = "r2"}, + {exploc = 1, scale = 0.6, metabolite = "M2", compartment = "c", enzyme = "r3"}, + {exploc = 1, scale = 0.6, metabolite = "M2", compartment = "e", enzyme = "r3"}, +] + +transfer_constant = [ + {exploc = 1, scale = 0.6, enzyme = "r1"}, + {exploc = 1, scale = 0.6, enzyme = "r2"}, +] + +dissociation_constant = [ + {exploc = 1, scale = 0.6, enzyme = "r1", metabolite = "M2", compartment = "c", modification_type = "activation"}, + {exploc = 1, scale = 0.6, enzyme = "r2", metabolite = "M1", compartment = "c", modification_type = "inhibition"}, +] + +psi = [ + {location = -0.95, scale = 0.2, experiment = "condition1"}, + {location = -0.95, scale = 0.2, experiment = "condition2"}, +] + +ki = [ + {exploc = 1, scale = 0.6, enzyme = "r2", reaction = "r2", metabolite = "M1", compartment = "c"}, +] + +[dgf] +ids = [ "M1", "M2" ] +mean_vector = [-1, -2] +covariance_matrix = [ [0.5, 0.5], [0.5, 0.5] ] diff --git a/maud/data/example_inputs/linear_multidgf/priors.toml b/maud/data/example_inputs/linear_multidgf/priors.toml new file mode 100644 index 00000000..b820eaa2 --- /dev/null +++ b/maud/data/example_inputs/linear_multidgf/priors.toml @@ -0,0 +1,37 @@ +kcat = [ + {exploc = 1, scale = 0.6, reaction = "r1", enzyme = "r1"}, + {exploc = 1, scale = 0.6, reaction = "r2", enzyme = "r2"}, + {exploc = 1, scale = 0.6, reaction = "r3", enzyme = "r3"}, +] + +km = [ + {exploc = 1, scale = 0.6, metabolite = "M1", compartment = "e", enzyme = "r1"}, + {exploc = 1, scale = 0.6, metabolite = "M1", compartment = "c", enzyme = "r1"}, + {exploc = 1, scale = 0.6, metabolite = "M1", compartment = "c", enzyme = "r2"}, + {exploc = 1, scale = 0.6, metabolite = "M2", compartment = "c", enzyme = "r3"}, + {exploc = 1, scale = 0.6, metabolite = "M2", compartment = "e", enzyme = "r3"}, +] + +transfer_constant = [ + {exploc = 1, scale = 0.6, enzyme = "r1"}, + {exploc = 1, scale = 0.6, enzyme = "r2"}, +] + +dissociation_constant = [ + {exploc = 1, scale = 0.6, enzyme = "r1", metabolite = "M2", compartment = "c", modification_type = "activation"}, + {exploc = 1, scale = 0.6, enzyme = "r2", metabolite = "M1", compartment = "c", modification_type = "inhibition"}, +] + +psi = [ + {location = -0.95, scale = 0.2, experiment = "condition1"}, + {location = -0.95, scale = 0.2, experiment = "condition2"}, +] + +ki = [ + {exploc = 1, scale = 0.6, enzyme = "r2", reaction = "r2", metabolite = "M1", compartment = "c"}, +] + +[dgf] +ids = [ "M1", "M2" ] +mean_vector = [ -10, -32] +covariance_matrix = [ [5,2], [2,4] ] diff --git a/maud/data/example_inputs/methionine/__init__.py b/maud/data/example_inputs/methionine/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/maud/data_model/maud_input.py b/maud/data_model/maud_input.py index c7757413..b5104414 100644 --- a/maud/data_model/maud_input.py +++ b/maud/data_model/maud_input.py @@ -82,4 +82,6 @@ def inits_dict(self) -> Dict: for met, init in met_to_init.items() if met not in param.fixed_ids[0] ] + elif param.fixable: + inits_dict[param.name + "_free"] = param.inits.inits_unscaled return inits_dict diff --git a/maud/data_model/maud_parameter.py b/maud/data_model/maud_parameter.py index a22ea6f3..2f2ad0fb 100644 --- a/maud/data_model/maud_parameter.py +++ b/maud/data_model/maud_parameter.py @@ -31,12 +31,13 @@ class MaudParameter(BaseModel): init_input: Optional[List[InitAtomInput]] ids: List[List[str]] split_ids: List[List[List[str]]] + fixable: bool = False measurements: Optional[List[Measurement]] = None @computed_field def fixed_ids(self) -> Optional[List[List[str]]]: """Set the fixed_ids field.""" - if self.name != "dgf": + if not self.fixable: return None elif self.user_input is None: return None @@ -60,7 +61,7 @@ def fixed_ids(self) -> Optional[List[List[str]]]: @computed_field def fixed_values(self) -> Optional[List[List[float]]]: """Set the fixed_values field.""" - if self.name != "dgf": + if not self.fixable: return None elif self.user_input is None: return None @@ -244,6 +245,7 @@ class Dgf(MaudParameter): default_scale: float = 10 prior_in_test_model: bool = False prior_in_train_model: bool = True + fixable: bool = True class DissociationConstant(MaudParameter): diff --git a/pyproject.toml b/pyproject.toml index 7377a273..2496fa95 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,10 +21,10 @@ classifiers = [ license = {text = "GNU General Public License version 3"} description = "Bayesian statistical models of metabolic networks" readme = "README.rst" -requires-python = ">=3.9" +requires-python = ">=3.10" dependencies = [ "pip >= 20", - "arviz >= 0.12.1", + "arviz >= 0.18.0", "importlib_resources >= 3.2", "numpy", "scipy", @@ -76,6 +76,7 @@ packages = [ "maud.data.example_inputs.example_ode", "maud.data.example_inputs.linear", "maud.data.example_inputs.methionine", + "maud.data.example_inputs.linear_multidgf", "maud.data.example_outputs", "maud.data.example_outputs.linear", "maud.data.example_outputs.linear.user_input", @@ -85,7 +86,6 @@ packages = [ [tool.black] line-length = 80 -python-version = ['py38', 'py39'] exclude = ''' ( __init__.py @@ -117,23 +117,23 @@ markers = ["raises"] [tool.ruff] # Enable the following rules: # pycodestyle (`E`), Pyflakes (`F`), pycodestyle (`W`), flake8-bugbear (`B`) -select = ["E", "F", "B", "W", "D"] -ignore = [ +lint.select = ["E", "F", "B", "W", "D"] +lint.ignore = [ "B905", # Use zip() without a `strict` parameter. "D107", # Init methods can be undocumented. "D203", # Class docstrints don't need blank lines before them. "D213", # https://beta.ruff.rs/docs/rules/multi-line-summary-second-line/ "D104", # __init__.py can be empty. ] -fixable = ["ALL"] -unfixable = [] +lint.fixable = ["ALL"] +lint.unfixable = [] line-length = 80 target-version = "py310" [tool.tox] legacy_tox_ini = """ [tox] - envlist = isort, black, ruff, safety, py3{9,10} + envlist = isort, black, ruff, safety, py3{10,11} [testenv] deps = @@ -143,11 +143,11 @@ legacy_tox_ini = """ commands = pytest --cov=maud --cov-report=term --ignore=tests/test_integration {posargs} - [testenv:py38] + [testenv:py310] passenv = CMDSTAN - [testenv:py39] + [testenv:py311] passenv = CMDSTAN @@ -171,7 +171,7 @@ legacy_tox_ini = """ deps= ruff commands= - ruff . + ruff check . [testenv:safety] deps= diff --git a/tests/test_unit/test_load_maud_input.py b/tests/test_unit/test_load_maud_input.py index 2199bc25..f15212fc 100644 --- a/tests/test_unit/test_load_maud_input.py +++ b/tests/test_unit/test_load_maud_input.py @@ -6,7 +6,7 @@ import numpy as np from numpy.testing import assert_equal -from maud.data.example_inputs import linear +from maud.data.example_inputs import linear, linear_multidgf from maud.loading_maud_inputs import load_maud_input @@ -22,7 +22,7 @@ def test_load_maud_input(): "dissociation_constant": [["r1_M2_c_activation", "r2_M1_c_inhibition"]], } linear_files = importlib_resources.files(linear) - mi = load_maud_input(data_path=linear_files._paths[0]) # path 0 is package + mi = load_maud_input(data_path=str(linear_files)) # path 0 is package r1 = next(r for r in mi.kinetic_model.reactions if r.id == "r1") assert r1.stoichiometry == {"M1_e": -1, "M1_c": 1} assert "r1_r1" in mi.parameters.kcat.ids[0] @@ -36,3 +36,10 @@ def test_load_maud_input(): actual = v.tolist() if isinstance(v, np.ndarray) else v expected = expected_stan_input[k] assert_equal(actual, expected, err_msg=f"{k} different from expected.") + + +def test_load_multidgf(): + """Test that the multidgf input loads correctly.""" + files = importlib_resources.files(linear_multidgf) + mi = load_maud_input(data_path=str(files)) # path 0 is package + assert mi.inits_dict["dgf_free"] == [-10.0, -32.0]