From 638e80b6c7452bd0a22bb8ed5ebd297da81ca900 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 17 Jun 2024 11:05:23 +0200 Subject: [PATCH 01/11] AmiciObjective: check that sensitivities wrt all relevant parameters can be computed Closes #1414 --- pypesto/objective/amici/amici.py | 43 ++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/pypesto/objective/amici/amici.py b/pypesto/objective/amici/amici.py index 9c7035329..07e43f656 100644 --- a/pypesto/objective/amici/amici.py +++ b/pypesto/objective/amici/amici.py @@ -4,6 +4,7 @@ import tempfile from collections import OrderedDict from collections.abc import Sequence +from itertools import chain from pathlib import Path from typing import TYPE_CHECKING, Optional, Union @@ -174,6 +175,21 @@ def __init__( ) self.parameter_mapping = parameter_mapping + # Check that we can compute the max_sensi_order sensitivities + # based on the supplied model. If not, raise an error. + # If max_sensi_order is not set, set it if necessary + if (self.max_sensi_order is None or self.max_sensi_order > 0) and ( + missing_sensitivities := self._get_missing_sensitivities() + ): + if self.max_sensi_order is not None: + raise ValueError( + f"`max_sensi_order` is {self.max_sensi_order}`, but " + "the provided model does not support computing " + "sensitivities w.r.t. the following parameters: " + f"{missing_sensitivities}." + ) + self.max_sensi_order = 0 + # If supported, enable `guess_steadystate` by default. If not # supported, disable by default. If requested but unsupported, raise. if ( @@ -233,6 +249,33 @@ def __init__( # `set_custom_timepoints` method for more information. self.custom_timepoints = None + def _get_missing_sensitivities(self) -> set[str]: + """Get the model parameters for which sensitivities are missing. + + Get the model parameters w.r.t. which sensitivities are required, + but aren't available missing. + """ + required_sensitivities = set() + objective_parameters = set(self.x_ids) + # resolve parameter mapping: collect all model parameters that depend + # on the optimization parameters + for condition_mapping in self.parameter_mapping: + required_sensitivities.update( + model_par + for model_par, opt_par in chain( + condition_mapping.map_preeq_fix.items(), + condition_mapping.map_sim_fix.items(), + condition_mapping.map_sim_var.items(), + ) + # we assume that the values in the parameter mapping are + # either numeric values or parameter ID strings + if opt_par in objective_parameters + ) + + # All parameters w.r.t. which we can compute sensitivities are the + # non-fixed parameters + return required_sensitivities - set(self.amici_model.getParameterIds()) + def get_config(self) -> dict: """Return basic information of the objective configuration.""" info = super().get_config() From 6397f3eff8911362ef9bba6e92b9d3cc73b9249d Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 17 Jun 2024 12:51:00 +0200 Subject: [PATCH 02/11] .. --- pypesto/objective/amici/amici.py | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/pypesto/objective/amici/amici.py b/pypesto/objective/amici/amici.py index 07e43f656..0bb17c2f3 100644 --- a/pypesto/objective/amici/amici.py +++ b/pypesto/objective/amici/amici.py @@ -175,21 +175,6 @@ def __init__( ) self.parameter_mapping = parameter_mapping - # Check that we can compute the max_sensi_order sensitivities - # based on the supplied model. If not, raise an error. - # If max_sensi_order is not set, set it if necessary - if (self.max_sensi_order is None or self.max_sensi_order > 0) and ( - missing_sensitivities := self._get_missing_sensitivities() - ): - if self.max_sensi_order is not None: - raise ValueError( - f"`max_sensi_order` is {self.max_sensi_order}`, but " - "the provided model does not support computing " - "sensitivities w.r.t. the following parameters: " - f"{missing_sensitivities}." - ) - self.max_sensi_order = 0 - # If supported, enable `guess_steadystate` by default. If not # supported, disable by default. If requested but unsupported, raise. if ( @@ -256,7 +241,7 @@ def _get_missing_sensitivities(self) -> set[str]: but aren't available missing. """ required_sensitivities = set() - objective_parameters = set(self.x_ids) + objective_parameters = set(self.x_names) # resolve parameter mapping: collect all model parameters that depend # on the optimization parameters for condition_mapping in self.parameter_mapping: @@ -499,6 +484,18 @@ def call_unprocessed( """ import amici + # Check that we can compute the requested sensitivities + # based on the supplied model. If not, raise an error. + # This has to be done on every call, since the preprocessor may + # change the parameters w.r.t. which sensitivities are required. + if max(sensi_orders) > 0 and ( + missing_sensitivities := self._get_missing_sensitivities() + ): + raise ValueError( + f"Requested sensitivities w.r.t. parameters that can't " + f"be computed by the current model: {missing_sensitivities}." + ) + x_dct = self.par_arr_to_dct(x) # only ask amici to compute required quantities From 74ba61f6b4c4b845916be5d2eafda1d1d28c37ed Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 17 Jun 2024 14:33:18 +0200 Subject: [PATCH 03/11] .. --- pypesto/objective/amici/amici_calculator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pypesto/objective/amici/amici_calculator.py b/pypesto/objective/amici/amici_calculator.py index de1d99030..7c0e5a7d1 100644 --- a/pypesto/objective/amici/amici_calculator.py +++ b/pypesto/objective/amici/amici_calculator.py @@ -92,7 +92,7 @@ def __call__( # set order in solver sensi_order = 0 if sensi_orders: - sensi_order = max(sensi_orders) + sensi_order = max(0, *sensi_orders) if sensi_order == 2 and fim_for_hess: # we use the FIM From ecf00dbdc62ce76e3a4afdf581668cafa9c47304 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 17 Jun 2024 14:47:38 +0200 Subject: [PATCH 04/11] test_max_sensi_order --- test/petab/test_petab_import.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/test/petab/test_petab_import.py b/test/petab/test_petab_import.py index 4c9cd2243..ad7e15681 100644 --- a/test/petab/test_petab_import.py +++ b/test/petab/test_petab_import.py @@ -167,16 +167,19 @@ def test_max_sensi_order(): """Test that the AMICI objective created via PEtab exposes derivatives correctly.""" model_name = "Boehm_JProteomeRes2014" - problem = pypesto.petab.PetabImporter.from_yaml( + importer = pypesto.petab.PetabImporter.from_yaml( os.path.join(models.MODELS_DIR, model_name, model_name + ".yaml") ) + problem = importer.create_problem() # define test parameter - par = problem.petab_problem.x_nominal_scaled - npar = len(par) + par = np.asarray(importer.petab_problem.x_nominal_scaled)[ + problem.x_free_indices + ] + npar = problem.dim # auto-computed max_sensi_order and fim_for_hess - objective = problem.create_objective() + objective = problem.objective hess = objective(par, sensi_orders=(2,)) assert hess.shape == (npar, npar) assert (hess != 0).any() @@ -190,18 +193,24 @@ def test_max_sensi_order(): ) # fix max_sensi_order to 1 - objective = problem.create_objective(max_sensi_order=1) + objective = importer.create_problem( + objective=importer.create_objective(max_sensi_order=1) + ).objective objective(par, sensi_orders=(1,)) with pytest.raises(ValueError): objective(par, sensi_orders=(2,)) # do not use FIM - objective = problem.create_objective(fim_for_hess=False) + objective = importer.create_problem( + objective=importer.create_objective(fim_for_hess=False) + ).objective with pytest.raises(ValueError): objective(par, sensi_orders=(2,)) # only allow computing function values - objective = problem.create_objective(max_sensi_order=0) + objective = importer.create_problem( + objective=importer.create_objective(max_sensi_order=0) + ).objective objective(par) with pytest.raises(ValueError): objective(par, sensi_orders=(1,)) From 838a73f84f4a428b5d576d03433c63c1d0a564ed Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 17 Jun 2024 15:00:49 +0200 Subject: [PATCH 05/11] Revert ".." This reverts commit 74ba61f6b4c4b845916be5d2eafda1d1d28c37ed. --- pypesto/objective/amici/amici_calculator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pypesto/objective/amici/amici_calculator.py b/pypesto/objective/amici/amici_calculator.py index 7c0e5a7d1..de1d99030 100644 --- a/pypesto/objective/amici/amici_calculator.py +++ b/pypesto/objective/amici/amici_calculator.py @@ -92,7 +92,7 @@ def __call__( # set order in solver sensi_order = 0 if sensi_orders: - sensi_order = max(0, *sensi_orders) + sensi_order = max(sensi_orders) if sensi_order == 2 and fim_for_hess: # we use the FIM From 19e99bfd754856949826414ef6846cfa957a3bd5 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 17 Jun 2024 15:01:16 +0200 Subject: [PATCH 06/11] .. --- pypesto/objective/amici/amici.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pypesto/objective/amici/amici.py b/pypesto/objective/amici/amici.py index 0bb17c2f3..733f7f09d 100644 --- a/pypesto/objective/amici/amici.py +++ b/pypesto/objective/amici/amici.py @@ -488,7 +488,7 @@ def call_unprocessed( # based on the supplied model. If not, raise an error. # This has to be done on every call, since the preprocessor may # change the parameters w.r.t. which sensitivities are required. - if max(sensi_orders) > 0 and ( + if max(0, *sensi_orders) > 0 and ( missing_sensitivities := self._get_missing_sensitivities() ): raise ValueError( From 5a69d70a8674a43731719e9ce2fef06df9ab3de0 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 17 Jun 2024 15:03:57 +0200 Subject: [PATCH 07/11] doc --- pypesto/petab/importer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pypesto/petab/importer.py b/pypesto/petab/importer.py index 23103d673..284d7a8ae 100644 --- a/pypesto/petab/importer.py +++ b/pypesto/petab/importer.py @@ -447,6 +447,9 @@ def create_objective( Returns ------- A :class:`pypesto.objective.AmiciObjective` for the model and the data. + This object is expected to be passed to + :class:`PetabImporter.create_problem` to correctly handle fixed + parameters. """ # get simulation conditions simulation_conditions = petab.get_simulation_conditions( From 5dd6c00192689a572148658ae8756a82b90e38bb Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 17 Jun 2024 15:26:57 +0200 Subject: [PATCH 08/11] .. --- pypesto/objective/amici/amici.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pypesto/objective/amici/amici.py b/pypesto/objective/amici/amici.py index 733f7f09d..039f20761 100644 --- a/pypesto/objective/amici/amici.py +++ b/pypesto/objective/amici/amici.py @@ -488,7 +488,8 @@ def call_unprocessed( # based on the supplied model. If not, raise an error. # This has to be done on every call, since the preprocessor may # change the parameters w.r.t. which sensitivities are required. - if max(0, *sensi_orders) > 0 and ( + # max(0, 0) to work with empty sensi_orders + if max(0, 0, *sensi_orders) > 0 and ( missing_sensitivities := self._get_missing_sensitivities() ): raise ValueError( From 31db0160b5a0b6ffffb9cb1702bf787cb608ff37 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 17 Jun 2024 15:36:50 +0200 Subject: [PATCH 09/11] fix notebook --- doc/example/petab_import.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/example/petab_import.ipynb b/doc/example/petab_import.ipynb index 4b3b5bbdc..2de6b7522 100644 --- a/doc/example/petab_import.ipynb +++ b/doc/example/petab_import.ipynb @@ -178,7 +178,7 @@ "converter_config = libsbml.SBMLLocalParameterConverter().getDefaultProperties()\n", "petab_problem.sbml_document.convert(converter_config)\n", "\n", - "obj = importer.create_objective()\n", + "obj = problem.objective\n", "\n", "# for some models, hyperparameters need to be adjusted\n", "# obj.amici_solver.setMaxSteps(10000)\n", From e865f8c1931b3d227d81a85703d233f863079b05 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 17 Jun 2024 16:13:35 +0200 Subject: [PATCH 10/11] .. --- doc/example/petab_import.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/example/petab_import.ipynb b/doc/example/petab_import.ipynb index 2de6b7522..a1f6781d9 100644 --- a/doc/example/petab_import.ipynb +++ b/doc/example/petab_import.ipynb @@ -200,7 +200,7 @@ "outputs": [], "source": [ "ret = obj(\n", - " petab_problem.x_nominal_scaled,\n", + " petab_problem.x_nominal_free_scaled,\n", " mode=\"mode_fun\",\n", " sensi_orders=(0, 1),\n", " return_dict=True,\n", From f107c48d5044369629e0f9d59cac636ef6f42ac6 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 17 Jun 2024 16:18:50 +0200 Subject: [PATCH 11/11] .. --- doc/example/petab_import.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/example/petab_import.ipynb b/doc/example/petab_import.ipynb index a1f6781d9..5f4fe05a4 100644 --- a/doc/example/petab_import.ipynb +++ b/doc/example/petab_import.ipynb @@ -383,7 +383,7 @@ "outputs": [], "source": [ "ref = visualize.create_references(\n", - " x=petab_problem.x_nominal_scaled, fval=obj(petab_problem.x_nominal_scaled)\n", + " x=petab_problem.x_nominal_scaled, fval=obj(petab_problem.x_nominal_free_scaled)\n", ")\n", "\n", "visualize.waterfall(result, reference=ref, scale_y=\"lin\")\n",