From cf56080831129f155514d10573bd848e1abf7c4f Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Tue, 19 Nov 2024 09:32:47 +0100 Subject: [PATCH 1/6] Fix ESSOptimizer min of empty sequence (#1510) Fixes `ValueError: min() arg is an empty sequence)` that may cause deadlocks. --- pypesto/optimize/ess/ess.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/pypesto/optimize/ess/ess.py b/pypesto/optimize/ess/ess.py index 1f31df0f5..b38476830 100644 --- a/pypesto/optimize/ess/ess.py +++ b/pypesto/optimize/ess/ess.py @@ -487,19 +487,25 @@ def _do_local_search( quality_order = np.argsort(fx_best_children) # compute minimal distance between the best children and all local # optima found so far - min_distances = np.fromiter( - ( - min( - np.linalg.norm( - y_i - - optimizer_result.x[optimizer_result.free_indices] + min_distances = ( + np.fromiter( + ( + min( + np.linalg.norm( + y_i + - optimizer_result.x[ + optimizer_result.free_indices + ] + ) + for optimizer_result in self.local_solutions ) - for optimizer_result in self.local_solutions - ) - for y_i in x_best_children - ), - dtype=np.float64, - count=len(x_best_children), + for y_i in x_best_children + ), + dtype=np.float64, + count=len(x_best_children), + ) + if len(self.local_solutions) + else np.zeros(len(x_best_children)) ) # sort by furthest distance to existing local optima diversity_order = np.argsort(min_distances)[::-1] From e62c2cb94090309a564fda01d546e2ffb2902f12 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Tue, 19 Nov 2024 13:47:52 +0100 Subject: [PATCH 2/6] PEtab: Fix warning from `fill_in_parameters` with fixed parameters (#1509) Fixes a warning `RuntimeWarning: The following problem parameters were not used: {[...]} amici.petab.conditions.fill_in_parameters(` in case of fixed parameters. The warning was issued since https://github.com/ICB-DCM/pyPESTO/pull/1493. This also fills in the fixed parameter values in the parameter mapping for the fixed parameters that was missing in https://github.com/ICB-DCM/pyPESTO/pull/1493. --- pypesto/objective/amici/amici.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/pypesto/objective/amici/amici.py b/pypesto/objective/amici/amici.py index 8edcf13fa..6edbe1dde 100644 --- a/pypesto/objective/amici/amici.py +++ b/pypesto/objective/amici/amici.py @@ -183,7 +183,12 @@ def __init__( # (relevant for setting ``plist`` in ExpData later on) self.parameter_mapping = parameter_mapping # parameter mapping independent of fixed parameters + # (i.e., all objective parameters are included as parameter IDs, + # not as their values) self._parameter_mapping_full = copy.deepcopy(parameter_mapping) + # IDs of fixed `Problem` parameters + self._fixed_parameter_ids = [] + # If supported, enable `guess_steadystate` by default. If not # supported, disable by default. If requested but unsupported, raise. if ( @@ -478,8 +483,16 @@ def call_unprocessed( edatas = self.edatas if parameter_mapping is None: parameter_mapping = self.parameter_mapping + # Some parameters may appear estimated in the original compiled model, + # but then are fixed during parameter estimation. These are removed + # from the parameter vector to avoid warnings about unused parameters. + x_dct_free = { + par_id: val + for par_id, val in x_dct.items() + if par_id not in self._fixed_parameter_ids + } ret = self.calculator( - x_dct=x_dct, + x_dct=x_dct_free, sensi_orders=sensi_orders, mode=mode, amici_model=self.amici_model, @@ -679,6 +692,7 @@ def update_from_problem( # and replace the IDs of all fixed parameters by their respective # values. self.parameter_mapping = copy.deepcopy(self._parameter_mapping_full) + self._fixed_parameter_ids = [self.x_ids[i] for i in x_fixed_indices] if not len(x_fixed_indices): return @@ -693,3 +707,9 @@ def update_from_problem( ) in condition_mapping.map_sim_var.items(): if (val := id_to_val.get(mapped_to_par)) is not None: condition_mapping.map_sim_var[model_par] = val + for ( + model_par, + mapped_to_par, + ) in condition_mapping.map_sim_fix.items(): + if (val := id_to_val.get(mapped_to_par)) is not None: + condition_mapping.map_sim_fix[model_par] = val From c5bfd72c22592ca53b1ab7c240067b0ae1bb1f5a Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Tue, 19 Nov 2024 15:23:58 +0100 Subject: [PATCH 3/6] Doc: extend scatter search documentation (#1506) Add example, background, cross-references, ... --------- Co-authored-by: Dilan Pathirana <59329744+dilpath@users.noreply.github.com> --- pypesto/optimize/ess/ess.py | 120 ++++++++++++++++++++++++++------- pypesto/optimize/ess/refset.py | 14 ++-- pypesto/optimize/ess/sacess.py | 90 ++++++++++++++++++++----- pytest.ini | 1 + 4 files changed, 179 insertions(+), 46 deletions(-) diff --git a/pypesto/optimize/ess/ess.py b/pypesto/optimize/ess/ess.py index b38476830..2a86e18d9 100644 --- a/pypesto/optimize/ess/ess.py +++ b/pypesto/optimize/ess/ess.py @@ -3,11 +3,12 @@ See papers on ESS :footcite:p:`EgeaBal2009,EgeaMar2010`, CESS :footcite:p:`VillaverdeEge2012`, and saCeSS :footcite:p:`PenasGon2017`. """ +from __future__ import annotations import enum import logging import time -from typing import Callable, Optional, Union +from typing import Protocol from warnings import warn import numpy as np @@ -38,15 +39,80 @@ class ESSExitFlag(int, enum.Enum): MAX_TIME = -3 +class OptimizerFactory(Protocol): + def __call__( + self, max_eval: float, max_walltime_s: float + ) -> pypesto.optimize.Optimizer: + """Create a new optimizer instance. + + Parameters + ---------- + max_eval: + Maximum number of objective functions allowed. + max_walltime_s: + Maximum walltime in seconds. + """ + ... + + class ESSOptimizer: """Enhanced Scatter Search (ESS) global optimization. - See papers on ESS :footcite:p:`EgeaBal2009,EgeaMar2010`, - CESS :footcite:p:`VillaverdeEge2012`, and saCeSS :footcite:p:`PenasGon2017`. + Scatter search is a meta-heuristic for global optimization. A set of points + (the reference set, RefSet) is iteratively adapted to explore the parameter + space and to follow promising directions. - .. footbibliography:: + This implementation is based on :footcite:p:`EgeaBal2009,EgeaMar2010`, + but does not implement any constraint handling beyond box constraints. + + The basic steps of ESS are: + + * Initialization: Generate a diverse set of points (RefSet) in the + parameter space. + * Recombination: Generate new points by recombining the RefSet points. + * Improvement: Improve the RefSet by replacing points with better ones. + + The steps are repeated until a stopping criterion is met. + + ESS is gradient-free, unless a gradient-based local optimizer is used + (``local_optimizer``). - .. note: Does not implement any constraint handling beyond box constraints + Hyperparameters + --------------- + + Various hyperparameters control the behavior of ESS. + Initialization is controlled by ``dim_refset`` and ``n_diverse``. + Local optimizations are controlled by ``local_optimizer``, ``local_n1``, + ``local_n2``, and ``balance``. + + Exit criteria + ------------- + + The optimization stops if any of the following criteria are met: + + * The maximum number of iterations is reached (``max_iter``). + * The maximum number of objective function evaluations is reached + (``max_eval``). + * The maximum wall-time is reached (``max_walltime_s``). + + One of these criteria needs to be provided. + Note that the wall-time and function evaluation criteria are not checked + after every single function evaluation, and thus, the actual number of + function evaluations may slightly exceed the given value. + + Parallelization + --------------- + + Objective function evaluations inside :class:`ESSOptimizer` can be + parallelized using multiprocessing or multithreading by passing a value + >1 for ``n_procs`` or ``n_threads``, respectively. + + + .. seealso:: + + :class:`pypesto.optimize.ess.sacess.SacessOptimizer` + + .. footbibliography:: """ def __init__( @@ -57,10 +123,9 @@ def __init__( local_n1: int = 1, local_n2: int = 10, balance: float = 0.5, - local_optimizer: Union[ - "pypesto.optimize.Optimizer", - Callable[..., "pypesto.optimize.Optimizer"], - ] = None, + local_optimizer: pypesto.optimize.Optimizer + | OptimizerFactory + | None = None, max_eval=None, n_diverse: int = None, n_procs=None, @@ -68,7 +133,7 @@ def __init__( max_walltime_s=None, result_includes_refset: bool = False, ): - """Construct new ESS instance. + r"""Construct new ESS instance. For plausible values of hyperparameters, see :footcite:t:`VillaverdeEge2012`. @@ -81,10 +146,11 @@ def __init__( Maximum number of ESS iterations. local_n1: Minimum number of iterations before first local search. + Ignored if ``local_optimizer=None``. local_n2: Minimum number of iterations between consecutive local searches. Maximally one local search per performed in each - iteration. + iteration. Ignored if ``local_optimizer=None``. local_optimizer: Local optimizer for refinement, or a callable that creates an :class:`pypesto.optimize.Optimizer` or ``None`` to skip local searches. @@ -104,8 +170,14 @@ def __init__( optimizations and other simulations, and thus, may be exceeded by the duration of a local search. balance: - Quality vs diversity balancing factor [0, 1]; - 0 = only quality; 1 = only diversity + Quality vs. diversity balancing factor with + :math:`0 \leq balance \leq 1`; ``0`` = only quality, + ``1`` = only diversity. + Affects the choice of starting points for local searches. I.e., + whether local optimization should focus on improving the best + solutions found so far (quality), or on exploring new regions of + the parameter space (diversity). + Ignored if ``local_optimizer=None``. n_procs: Number of parallel processes to use for parallel function evaluation. Mutually exclusive with `n_threads`. @@ -144,8 +216,8 @@ def __init__( raise ValueError( "`n_procs` and `n_threads` are mutually exclusive." ) - self.n_procs: Optional[int] = n_procs - self.n_threads: Optional[int] = n_threads + self.n_procs: int | None = n_procs + self.n_threads: int | None = n_threads self.balance: float = balance # After how many iterations a stagnated solution is to be replaced by # a random one. Default value taken from [EgeaMar2010]_ @@ -162,9 +234,9 @@ def __init__( def _initialize(self): """(Re-)Initialize.""" # RefSet - self.refset: Optional[RefSet] = None + self.refset: RefSet | None = None # Overall best parameters found so far - self.x_best: Optional[np.array] = None + self.x_best: np.ndarray | None = None # Overall best function value found so far self.fx_best: float = np.inf # Results from local searches (only those with finite fval) @@ -177,15 +249,15 @@ def _initialize(self): # Whether self.x_best has changed in the current iteration self.x_best_has_changed: bool = False self.exit_flag: ESSExitFlag = ESSExitFlag.DID_NOT_RUN - self.evaluator: Optional[FunctionEvaluator] = None - self.starttime: Optional[float] = None + self.evaluator: FunctionEvaluator | None = None + self.starttime: float | None = None self.history: MemoryHistory = MemoryHistory() def _initialize_minimize( self, problem: Problem = None, startpoint_method: StartpointMethod = None, - refset: Optional[RefSet] = None, + refset: RefSet | None = None, ): """Initialize for optimizations. @@ -242,7 +314,7 @@ def minimize( self, problem: Problem = None, startpoint_method: StartpointMethod = None, - refset: Optional[RefSet] = None, + refset: RefSet | None = None, ) -> pypesto.Result: """Minimize the given objective. @@ -384,7 +456,7 @@ def _get_remaining_eval(self): return np.inf return self.max_eval - self.evaluator.n_eval - def _combine_solutions(self) -> tuple[np.array, np.array]: + def _combine_solutions(self) -> tuple[np.ndarray, np.ndarray]: """Combine solutions and evaluate. Creates the next generation from the RefSet by pair-wise combination @@ -418,7 +490,7 @@ def _combine_solutions(self) -> tuple[np.array, np.array]: break return y, fy - def _combine(self, i, j) -> np.array: + def _combine(self, i, j) -> np.ndarray: """Combine RefSet members ``i`` and ``j``. Samples a new point from a biased hyper-rectangle derived from the @@ -463,7 +535,7 @@ def _combine(self, i, j) -> np.array: ) def _do_local_search( - self, x_best_children: np.array, fx_best_children: np.array + self, x_best_children: np.ndarray, fx_best_children: np.ndarray ) -> None: """ Perform a local search to refine the next generation. diff --git a/pypesto/optimize/ess/refset.py b/pypesto/optimize/ess/refset.py index 0e3cff403..dfd8a4d42 100644 --- a/pypesto/optimize/ess/refset.py +++ b/pypesto/optimize/ess/refset.py @@ -29,8 +29,8 @@ def __init__( self, dim: int, evaluator: FunctionEvaluator, - x: Optional[np.array] = None, - fx: Optional[np.array] = None, + x: Optional[np.ndarray] = None, + fx: Optional[np.ndarray] = None, ): """Construct. @@ -65,7 +65,7 @@ def __init__( self.fx = fx self.n_stuck = np.zeros(shape=[dim]) - self.attributes: dict[Any, np.array] = {} + self.attributes: dict[Any, np.ndarray] = {} def __repr__(self): fx = ( @@ -97,7 +97,9 @@ def initialize_random( x_diverse, fx_diverse = self.evaluator.multiple_random(n_diverse) self.initialize_from_array(x_diverse=x_diverse, fx_diverse=fx_diverse) - def initialize_from_array(self, x_diverse: np.array, fx_diverse: np.array): + def initialize_from_array( + self, x_diverse: np.ndarray, fx_diverse: np.ndarray + ): """Create an initial reference set using the provided points. Populate half of the RefSet using the best given solutions and fill the @@ -168,7 +170,7 @@ def normalize(x): x[j], self.fx[j] = self.evaluator.single_random() self.sort() - def update(self, i: int, x: np.array, fx: float): + def update(self, i: int, x: np.ndarray, fx: float): """Update a RefSet entry.""" self.x[i] = x self.fx[i] = fx @@ -179,7 +181,7 @@ def replace_by_random(self, i: int): self.x[i], self.fx[i] = self.evaluator.single_random() self.n_stuck[i] = 0 - def add_attribute(self, name: str, values: np.array): + def add_attribute(self, name: str, values: np.ndarray): """ Add an attribute array to the refset members. diff --git a/pypesto/optimize/ess/sacess.py b/pypesto/optimize/ess/sacess.py index c54ef0ad6..113310f25 100644 --- a/pypesto/optimize/ess/sacess.py +++ b/pypesto/optimize/ess/sacess.py @@ -41,8 +41,10 @@ class SacessOptimizer: """SACESS optimizer. - A shared-memory-based implementation of the SaCeSS algorithm presented in - :footcite:t:`PenasGon2017`. Multiple processes (`workers`) run + A shared-memory-based implementation of the + `Self-Adaptive Cooperative Enhanced Scatter Search` (SaCeSS) algorithm + presented in :footcite:t:`PenasGon2017`. This is a meta-heuristic for + global optimization. Multiple processes (`workers`) run :class:`enhanced scatter searches (ESSs) ` in parallel. After each ESS iteration, depending on the outcome, there is a chance of exchanging good parameters, and changing ESS hyperparameters to those of @@ -51,6 +53,41 @@ class SacessOptimizer: :class:`SacessOptimizer` can be used with or without a local optimizer, but it is highly recommended to use one. + A basic example using :class:`SacessOptimizer` to minimize the Rosenbrock + function: + + >>> from pypesto.optimize import SacessOptimizer + >>> from pypesto.problem import Problem + >>> from pypesto.objective import Objective + >>> import scipy as sp + >>> import numpy as np + >>> import logging + >>> # Define some test Problem + >>> objective = Objective( + ... fun=sp.optimize.rosen, + ... grad=sp.optimize.rosen_der, + ... hess=sp.optimize.rosen_hess, + ... ) + >>> dim = 6 + >>> problem = Problem( + ... objective=objective, + ... lb=-5 * np.ones((dim, 1)), + ... ub=5 * np.ones((dim, 1)), + ... ) + >>> # Create and run the optimizer + >>> sacess = SacessOptimizer( + ... num_workers=2, + ... max_walltime_s=5, + ... sacess_loglevel=logging.WARNING + ... ) + >>> result = sacess.minimize(problem) + + .. seealso:: + + :class:`pypesto.optimize.ess.ess.ESSOptimizer` + + References + ---------- .. footbibliography:: Attributes @@ -83,11 +120,25 @@ def __init__( process. I.e., the length of this list is the number of ESSs. Ideally, this list contains some more conservative and some more aggressive configurations. - Resource limits such as ``max_eval`` apply to a single CESS + Resource limits such as ``max_eval`` apply to a single ESS iteration, not to the full search. Mutually exclusive with ``num_workers``. + Recommended default settings can be obtained from - :func:`get_default_ess_options`. + :func:`get_default_ess_options`. For example, to run + :class:`SacessOptimizer` without a local optimizer, use: + + >>> from pypesto.optimize.ess import get_default_ess_options + >>> ess_init_args = get_default_ess_options( + ... num_workers=12, + ... dim=10, # usually problem.dim + ... local_optimizer=False, + ... ) + >>> ess_init_args # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE + [{'dim_refset': 5, 'balance': 0.0, 'local_n1': 1, 'local_n2': 1}, + ... + {'dim_refset': 7, 'balance': 1.0, 'local_n1': 4, 'local_n2': 4}] + num_workers: Number of workers to be used. If this argument is given, (different) default ESS settings will be used for each worker. @@ -112,9 +163,10 @@ def __init__( parallel have a unique `tmpdir`. mp_start_method: The start method for the multiprocessing context. - See :mod:`multiprocessing` for details. + See :mod:`multiprocessing` for details. Running `SacessOptimizer` + under Jupyter may require ``mp_start_method="fork"``. options: - Further optimizer hyperparameters. + Further optimizer hyperparameters, see :class:`SacessOptions`. """ if (num_workers is None and ess_init_args is None) or ( num_workers is not None and ess_init_args is not None @@ -178,11 +230,12 @@ def minimize( Returns ------- - Result object with optimized parameters in - :attr:`pypesto.Result.optimize_result`. - Results are sorted by objective. At least the best parameters are - included. Additional results may be included - this is subject to - change. + _: + Result object with optimized parameters in + :attr:`pypesto.Result.optimize_result`. + Results are sorted by objective. At least the best parameters are + included. Additional results may be included - this is subject to + change. """ if startpoint_method is not None: warn( @@ -392,7 +445,7 @@ def __init__( self._logger = logging.getLogger() self._result_queue = shmem_manager.Queue() - def get_best_solution(self) -> tuple[np.array, float]: + def get_best_solution(self) -> tuple[np.ndarray, float]: """Get the best objective value and corresponding parameters.""" with self._lock: return np.array(self._best_known_x), self._best_known_fx.value @@ -416,7 +469,7 @@ def reconfigure_worker(self, worker_idx: int) -> dict: def submit_solution( self, - x: np.array, + x: np.ndarray, fx: float, sender_idx: int, elapsed_time_s: float, @@ -703,7 +756,7 @@ def _maybe_adapt(self, problem: Problem): f"neval: {self._neval} <= {problem.dim * self._options.adaptation_min_evals}." ) - def maybe_update_best(self, x: np.array, fx: float): + def maybe_update_best(self, x: np.ndarray, fx: float): """Maybe update the best known solution and send it to the manager.""" rel_change = ( abs((fx - self._best_known_fx) / fx) if fx != 0 else np.nan @@ -743,7 +796,7 @@ def maybe_update_best(self, x: np.array, fx: float): ) @staticmethod - def replace_solution(refset: RefSet, x: np.array, fx: float): + def replace_solution(refset: RefSet, x: np.ndarray, fx: float): """Replace the global refset member by the given solution.""" # [PenasGon2017]_ page 8, top if "cooperative_solution" not in refset.attributes: @@ -841,6 +894,11 @@ def get_default_ess_options( or a :obj:`Callable` returning an optimizer instance. The latter can be used to propagate walltime limits to the local optimizers. See :meth:`SacessFidesFactory.__call__` for an example. + The current default optimizer assumes that the optimized objective + function can provide its gradient. If this is not the case, the user + should provide a different local optimizer or consider using + :class:`pypesto.objective.finite_difference.FD` to approximate the + gradient using finite differences. """ min_dimrefset = 5 @@ -1086,7 +1144,7 @@ class SacessWorkerResult: Exit flag of the optimization process. """ - x: np.array + x: np.ndarray fx: float n_eval: int n_iter: int diff --git a/pytest.ini b/pytest.ini index 7571948eb..c852c729e 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,4 @@ [pytest] +addopts = "--doctest-modules" filterwarnings = ignore:.*inspect.getargspec\(\) is deprecated.*:DeprecationWarning From 46115860f4ec4bcf88273f7d64d6950b0b1e2f1a Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Wed, 20 Nov 2024 10:50:05 +0100 Subject: [PATCH 4/6] Fix handling of PEtab fixed parameters (#1514) Follow-up to #1493 and #1509, which only filled in PEtab-fixed parameters for simulation but not preequilibration in the parameter mapping. Adding Weber_BMC2015 to the PEtab tests, where this issue popped up. --- pypesto/objective/amici/amici.py | 6 ++++++ test/petab/test_petab_import.py | 11 ++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/pypesto/objective/amici/amici.py b/pypesto/objective/amici/amici.py index 6edbe1dde..d9fd371a3 100644 --- a/pypesto/objective/amici/amici.py +++ b/pypesto/objective/amici/amici.py @@ -713,3 +713,9 @@ def update_from_problem( ) in condition_mapping.map_sim_fix.items(): if (val := id_to_val.get(mapped_to_par)) is not None: condition_mapping.map_sim_fix[model_par] = val + for ( + model_par, + mapped_to_par, + ) in condition_mapping.map_preeq_fix.items(): + if (val := id_to_val.get(mapped_to_par)) is not None: + condition_mapping.map_preeq_fix[model_par] = val diff --git a/test/petab/test_petab_import.py b/test/petab/test_petab_import.py index 1ce935880..d4a39f6d5 100644 --- a/test/petab/test_petab_import.py +++ b/test/petab/test_petab_import.py @@ -36,12 +36,13 @@ def setUpClass(cls): cls.obj_edatas = [] def test_0_import(self): - for model_name in ["Zheng_PNAS2012", "Boehm_JProteomeRes2014"]: + for model_name in [ + "Zheng_PNAS2012", + "Boehm_JProteomeRes2014", + "Weber_BMC2015", + ]: # test yaml import for one model: - yaml_config = os.path.join( - models.MODELS_DIR, model_name, model_name + ".yaml" - ) - petab_problem = petab.Problem.from_yaml(yaml_config) + petab_problem = models.get_problem(model_name) self.petab_problems.append(petab_problem) def test_1_compile(self): From fae775515e0b2f3f1d811d52b5732023482d0a02 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Tue, 26 Nov 2024 09:54:01 +0100 Subject: [PATCH 5/6] Exclude nlopt==2.9.0 (#1519) We want to avoid nlopt-python 2.9.0 because it does not match nlopt v2.9.0 and is affected by https://github.com/stevengj/nlopt/issues/575. Closes #1511. --- setup.cfg | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index bc28ae610..d9eb70919 100644 --- a/setup.cfg +++ b/setup.cfg @@ -105,7 +105,8 @@ ipopt = dlib = dlib >= 19.19.0 nlopt = - nlopt >= 2.6.2 + # != 2.9.0: https://github.com/stevengj/nlopt/issues/575 + nlopt >= 2.6.2, != 2.9.0 pyswarm = pyswarm >= 0.6 cma = From 6c049f599bbda2026ae1edfef76fb1df87d84358 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Tue, 26 Nov 2024 16:25:58 +0100 Subject: [PATCH 6/6] Fix test_deepcopy_objective, test_preeq_guesses (#1521) * Fix test_deepcopy_objective (Fixes #1520) * Fix test_preeq_guesses Failures were related to changes in amici's default steady-state settings. --- test/base/test_engine.py | 9 ++++++++- test/petab/test_amici_objective.py | 15 ++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/test/base/test_engine.py b/test/base/test_engine.py index 6db8e79c3..1481c4ca1 100644 --- a/test/base/test_engine.py +++ b/test/base/test_engine.py @@ -86,7 +86,14 @@ def test_deepcopy_objective(): ) ) factory = petab_importer.create_objective_creator() - objective = factory.create_objective() + amici_model = factory.create_model() + amici_model.setSteadyStateSensitivityMode( + amici.SteadyStateSensitivityMode.integrateIfNewtonFails + ) + amici_model.setSteadyStateComputationMode( + amici.SteadyStateComputationMode.integrateIfNewtonFails + ) + objective = factory.create_objective(model=amici_model) objective.amici_solver.setSensitivityMethod( amici.SensitivityMethod_adjoint diff --git a/test/petab/test_amici_objective.py b/test/petab/test_amici_objective.py index 274962fc1..5ff4512b4 100644 --- a/test/petab/test_amici_objective.py +++ b/test/petab/test_amici_objective.py @@ -86,12 +86,21 @@ def test_preeq_guesses(): importer = pypesto.petab.PetabImporter.from_yaml( os.path.join(models.MODELS_DIR, model_name, model_name + ".yaml") ) - problem = importer.create_problem() - obj = problem.objective - obj.amici_solver.setNewtonMaxSteps(0) + obj_creator = importer.create_objective_creator() + amici_model = obj_creator.create_model() + amici_model.setSteadyStateComputationMode( + amici.SteadyStateComputationMode.integrateIfNewtonFails + ) + amici_model.setSteadyStateSensitivityMode( + amici.SteadyStateSensitivityMode.integrateIfNewtonFails + ) + obj = obj_creator.create_objective(model=amici_model) + problem = importer.create_problem(objective=obj) obj.amici_model.setSteadyStateSensitivityMode( amici.SteadyStateSensitivityMode.integrationOnly ) + obj = problem.objective + obj.amici_solver.setNewtonMaxSteps(0) obj.amici_solver.setAbsoluteTolerance(1e-12) obj.amici_solver.setRelativeTolerance(1e-12)