From 2d05621c8879f1e4cef12fbc1da267df362e5be2 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Wed, 17 Apr 2024 10:03:02 +0200 Subject: [PATCH 01/28] Fix lagging version number -> 0.5.1 --- pypesto/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pypesto/version.py b/pypesto/version.py index 3d187266f..dd9b22ccc 100644 --- a/pypesto/version.py +++ b/pypesto/version.py @@ -1 +1 @@ -__version__ = "0.5.0" +__version__ = "0.5.1" From d17807090e1904ea385786b4372dae500aa4d5dc Mon Sep 17 00:00:00 2001 From: Maren Philipps <55318391+m-philipps@users.noreply.github.com> Date: Wed, 17 Apr 2024 13:33:36 +0200 Subject: [PATCH 02/28] move petab imports outside of try/except (#1355) Co-authored-by: Daniel Weindl --- pypesto/petab/importer.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pypesto/petab/importer.py b/pypesto/petab/importer.py index ea00937bf..8a34b4597 100644 --- a/pypesto/petab/importer.py +++ b/pypesto/petab/importer.py @@ -19,6 +19,15 @@ import numpy as np import pandas as pd +import petab +from petab.C import ( + ESTIMATE, + NOISE_PARAMETERS, + OBSERVABLE_ID, + PREEQUILIBRATION_CONDITION_ID, + SIMULATION_CONDITION_ID, +) +from petab.models import MODEL_TYPE_SBML from ..C import ( CENSORED, @@ -50,16 +59,7 @@ import amici.petab.conditions import amici.petab.parameter_mapping import amici.petab.simulations - import petab from amici.petab.import_helpers import check_model - from petab.C import ( - ESTIMATE, - NOISE_PARAMETERS, - OBSERVABLE_ID, - PREEQUILIBRATION_CONDITION_ID, - SIMULATION_CONDITION_ID, - ) - from petab.models import MODEL_TYPE_SBML except ImportError: amici = None From ac03f4798d67786bc1088cb8694203bc83b258a4 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Thu, 18 Apr 2024 14:29:57 +0200 Subject: [PATCH 03/28] Require scipy<1.13.0 for pypesto[pymc] only for python3.9 (#1360) (#1376) The most recent arviz should now work with the most recent scipy (but requires python>=3.10): https://github.com/arviz-devs/arviz/blob/main/CHANGELOG.md#maintenance-and-fixes-1 Closes #1354 --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 129cedb13..e5d201411 100644 --- a/setup.cfg +++ b/setup.cfg @@ -117,7 +117,7 @@ mpi = mpi4py >= 3.0.3 pymc = arviz >= 0.12.1 - scipy < 1.13.0 # https://github.com/ICB-DCM/pyPESTO/issues/1354 + scipy < 1.13.0; python_version=='3.9' # https://github.com/ICB-DCM/pyPESTO/issues/1354 aesara >= 2.8.6 pymc >= 4.2.1 aesara = From 3b1a12f97ae645391974f2cf52dfb28d38ccf439 Mon Sep 17 00:00:00 2001 From: Paul Jonas Jost <70631928+PaulJonasJost@users.noreply.github.com> Date: Thu, 18 Apr 2024 15:27:28 +0200 Subject: [PATCH 04/28] Added option to sample startpoints of a problem, from the problem directly. (#1364) Co-authored-by: Maren Philipps <55318391+m-philipps@users.noreply.github.com> --- pypesto/problem/base.py | 16 ++++++++++++++++ test/base/test_startpoint.py | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/pypesto/problem/base.py b/pypesto/problem/base.py index 8b11c5dd4..d8d29847b 100644 --- a/pypesto/problem/base.py +++ b/pypesto/problem/base.py @@ -479,6 +479,22 @@ def print_parameter_summary(self) -> None: ) ) + def get_startpoints(self, n_starts: int) -> np.ndarray: + """ + Sample startpoints from method. + + Parameters + ---------- + n_starts: + Number of start points. + + Returns + ------- + xs: + Start points, shape (n_starts, dim). + """ + return self.startpoint_method(n_starts, self) + _convtypes = { "float": {"attr": "__float__", "conv": float}, diff --git a/test/base/test_startpoint.py b/test/base/test_startpoint.py index 94a100022..cb3165eee 100644 --- a/test/base/test_startpoint.py +++ b/test/base/test_startpoint.py @@ -5,6 +5,8 @@ import pypesto +from ..visualize import create_problem + # default setting n_starts = 5 dim = 2 @@ -121,3 +123,17 @@ def grad(x: np.ndarray): assert not np.allclose(x_guesses, xs[:n_guesses, :]) else: assert np.allclose(x_guesses, xs[:n_guesses, :]) + + +def test_startpoints_from_problem(): + """Test that startpoints can be generated from a problem.""" + # create problem + problem = create_problem() + + # generate startpoints + xs = problem.get_startpoints(n_starts=n_starts) + + # check shape and bounds + assert xs.shape == (n_starts, problem.dim) + assert np.all(xs >= problem.lb) + assert np.all(xs <= problem.ub) From 3edf785eea44cce1eff59fa1e1080175ca727483 Mon Sep 17 00:00:00 2001 From: Doresic <85789271+Doresic@users.noreply.github.com> Date: Fri, 19 Apr 2024 10:57:04 +0200 Subject: [PATCH 05/28] Relative: fix log of zero for default 0 sigma values (#1377) * Fix log of zero in hierarchical calculate_nllh by masking * Fix in `compute_bounded_optimal_scaling_offset_coupled` * Update numerical solver with data mask --------- Co-authored-by: Jonas Arruda <69197639+arrjon@users.noreply.github.com> --- pypesto/hierarchical/relative/solver.py | 4 ++-- pypesto/hierarchical/relative/util.py | 31 ++++++++++++++++++++----- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/pypesto/hierarchical/relative/solver.py b/pypesto/hierarchical/relative/solver.py index a0a6ea3d7..e1c91cd76 100644 --- a/pypesto/hierarchical/relative/solver.py +++ b/pypesto/hierarchical/relative/solver.py @@ -82,7 +82,7 @@ def calculate_obj_function( mask=x.ixs, ) - return compute_nllh(relevant_data, sim, sigma) + return compute_nllh(relevant_data, sim, sigma, problem.data_mask) def calculate_gradients( self, @@ -502,7 +502,7 @@ def fun(x): f"`{par.inner_parameter_type}`." ) - return compute_nllh(_data, _sim, _sigma) + return compute_nllh(_data, _sim, _sigma, problem.data_mask) # TODO gradient objective = Objective(fun) diff --git a/pypesto/hierarchical/relative/util.py b/pypesto/hierarchical/relative/util.py index 4465b3b12..0639d7556 100644 --- a/pypesto/hierarchical/relative/util.py +++ b/pypesto/hierarchical/relative/util.py @@ -362,6 +362,11 @@ def compute_bounded_optimal_scaling_offset_coupled( relevant_data[i][~s.ixs[i]] = np.nan relevant_sim[i][~s.ixs[i]] = np.nan + # Get relevant data mask + relevant_data_mask = [] + for i in range(len(data)): + relevant_data_mask.append(~np.isnan(relevant_data[i])) + # Get bounds s_bounds = s.get_bounds() b_bounds = b.get_bounds() @@ -407,6 +412,7 @@ def compute_bounded_optimal_scaling_offset_coupled( for sim_i in relevant_sim ], sigma=sigma, + data_mask=relevant_data_mask, ) for candidate_point in candidate_points ] @@ -447,18 +453,31 @@ def compute_bounded_optimal_scaling_offset_coupled( def compute_nllh( - data: list[np.ndarray], sim: list[np.ndarray], sigma: list[np.ndarray] + data: list[np.ndarray], + sim: list[np.ndarray], + sigma: list[np.ndarray], + data_mask: list[np.ndarray], ) -> float: """Compute negative log-likelihood. Compute negative log-likelihood of the data, given the model outputs and sigmas. """ - return sum( - 0.5 * np.nansum(np.log(2 * np.pi * sigma_i**2)) - + 0.5 * np.nansum((data_i - sim_i) ** 2 / sigma_i**2) - for data_i, sim_i, sigma_i in zip(data, sim, sigma) - ) + nllh = 0.0 + for data_i, sim_i, sigma_i, data_mask_i in zip( + data, sim, sigma, data_mask + ): + # Mask the data, sim and sigma + data_i = data_i[data_mask_i] + sim_i = sim_i[data_mask_i] + sigma_i = sigma_i[data_mask_i] + + # Compute the negative log-likelihood + nllh += 0.5 * np.nansum( + np.log(2 * np.pi * sigma_i**2) + ) + 0.5 * np.nansum((data_i - sim_i) ** 2 / sigma_i**2) + + return nllh def compute_nllh_gradient_for_condition( From b95f422407bffd1255aa8caa89e93c4ca0b08886 Mon Sep 17 00:00:00 2001 From: Paul Jonas Jost <70631928+PaulJonasJost@users.noreply.github.com> Date: Thu, 2 May 2024 10:52:09 +0200 Subject: [PATCH 06/28] Correlation plot with nans (#1365) * Added option to sample startpoints of a problem, from the problem directly. Also safety checks for startindices like "all" or "clustered" * Fixed error in correlation matrix, if there were nn values. Also safety checks in general for start indices. Added a test for correlation matrix * log filtering in process_start_indices * update docstring --------- Co-authored-by: Maren Philipps --- pypesto/visualize/misc.py | 25 ++++++++++++++++++++----- pypesto/visualize/parameters.py | 15 +-------------- test/visualize/test_visualize.py | 12 ++++++++++++ 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/pypesto/visualize/misc.py b/pypesto/visualize/misc.py index d23f710b5..5d7c1b491 100644 --- a/pypesto/visualize/misc.py +++ b/pypesto/visualize/misc.py @@ -1,3 +1,4 @@ +import logging import warnings from collections.abc import Iterable from numbers import Number @@ -23,6 +24,8 @@ from ..util import assign_clusters, delete_nan_inf from .clust_color import assign_colors_for_list +logger = logging.getLogger(__name__) + def process_result_list( results: Union[Result, list[Result]], colors=None, legends=None @@ -303,8 +306,8 @@ def process_start_indices( """ Process the start_indices. - Create an array of indices if a number was provided and checks that the - indices do not exceed the max_index. + Create an array of indices if a number was provided, checks that the indices + do not exceed the max_index and removes starts with non-finite fval. Parameters ---------- @@ -323,7 +326,7 @@ def process_start_indices( start_indices = ALL if isinstance(start_indices, str): if start_indices == ALL: - return np.asarray(range(len(result.optimize_result))) + start_indices = np.asarray(range(len(result.optimize_result))) elif start_indices == ALL_CLUSTERED: clust_ind, clust_size = assign_clusters( delete_nan_inf(result.optimize_result.fval)[1] @@ -336,12 +339,12 @@ def process_start_indices( start_indices = np.concatenate( [np.where(clust_ind == i_clust)[0] for i_clust in clust_gr2] ) - return start_indices + start_indices = start_indices elif start_indices == FIRST_CLUSTER: clust_ind = assign_clusters( delete_nan_inf(result.optimize_result.fval)[1] )[0] - return np.where(clust_ind == 0)[0] + start_indices = np.where(clust_ind == 0)[0] else: raise ValueError( f"Permissible values for start_indices are {ALL}, " @@ -359,6 +362,18 @@ def process_start_indices( if start_index < len(result.optimize_result) ] + # filter out the indices that are not finite + start_indices_unfiltered = len(start_indices) + start_indices = [ + start_index + for start_index in start_indices + if np.isfinite(result.optimize_result[start_index].fval) + ] + if len(start_indices) != start_indices_unfiltered: + logger.warning( + "Some start indices were removed due to inf or nan function values." + ) + return np.asarray(start_indices) diff --git a/pypesto/visualize/parameters.py b/pypesto/visualize/parameters.py index a482a94ae..81b10e1e2 100644 --- a/pypesto/visualize/parameters.py +++ b/pypesto/visualize/parameters.py @@ -601,23 +601,10 @@ def optimization_scatter( parameter_indices = process_parameter_indices( parameter_indices=parameter_indices, result=result ) - # remove all start indices that encounter an inf value at the start - # resulting in optimize_result[start]["x"] being None - start_indices_finite = start_indices[ - [ - result.optimize_result[i_start]["x"] is not None - for i_start in start_indices - ] - ] - # compare start_indices with start_indices_finite and log a warning - if len(start_indices) != len(start_indices_finite): - logger.warning( - "Some start indices were removed due to inf values at the start." - ) # put all parameters into a dataframe, where columns are parameters parameters = [ result.optimize_result[i_start]["x"][parameter_indices] - for i_start in start_indices_finite + for i_start in start_indices ] x_labels = [ result.problem.x_names[parameter_index] diff --git a/test/visualize/test_visualize.py b/test/visualize/test_visualize.py index cff700497..0b568e12d 100644 --- a/test/visualize/test_visualize.py +++ b/test/visualize/test_visualize.py @@ -1179,3 +1179,15 @@ def test_sacess_history(): ) sacess.minimize(problem) sacess_history(sacess.histories) + + +@pytest.mark.parametrize( + "result_creation", + [create_optimization_result, create_optimization_result_nan_inf], +) +@close_fig +def test_parameters_correlation_matrix(result_creation): + """Test pypesto.visualize.parameters_correlation_matrix""" + result = result_creation() + + visualize.parameters_correlation_matrix(result) From e197a8b9cafb9ef9c88ecc11c92e95e16e15f0e9 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Thu, 2 May 2024 19:00:07 +0200 Subject: [PATCH 07/28] ESS optimizers: suppress divide-by-zero warnings; report n_eval (#1380) For ESSOptimizer and SacessOptimizer: * supporess divide-by-zero warnings * report number of function evaluations --- pypesto/optimize/ess/ess.py | 4 ++-- pypesto/optimize/ess/sacess.py | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/pypesto/optimize/ess/ess.py b/pypesto/optimize/ess/ess.py index b0d62f718..479c67c61 100644 --- a/pypesto/optimize/ess/ess.py +++ b/pypesto/optimize/ess/ess.py @@ -647,8 +647,8 @@ def _report_final(self): formatter={"float": lambda x: "%.3g" % x}, ): self.logger.info( - f"-- Final ESS fval after {self.n_iter} " - f"iterations: {self.fx_best}. " + f"-- Final ESS fval after {self.n_iter} iterations, " + f"{self.evaluator.n_eval} function evaluations: {self.fx_best}. " f"Exit flag: {self.exit_flag.name}. " f"Num local solutions: {len(self.local_solutions)}." ) diff --git a/pypesto/optimize/ess/sacess.py b/pypesto/optimize/ess/sacess.py index c750f79c3..ae8b16883 100644 --- a/pypesto/optimize/ess/sacess.py +++ b/pypesto/optimize/ess/sacess.py @@ -445,10 +445,15 @@ def submit_solution( # reject solution self._rejections.value += 1 + rel_change = ( + abs(abs_change / self._best_known_fx.value) + if self._best_known_fx.value != 0 + else np.nan + ) self._logger.debug( f"Rejected solution from worker {sender_idx} " f"abs change: {abs_change} " - f"rel change: {abs(abs_change / self._best_known_fx.value):.4g} " + f"rel change: {rel_change:.4g} " f"(threshold: {self._rejection_threshold.value}) " f"(total rejections: {self._rejections.value})." ) @@ -577,7 +582,8 @@ def run( self._logger.info( f"sacess worker {self._worker_idx} iteration {ess.n_iter} " - f"(best: {self._best_known_fx})." + f"(best: {self._best_known_fx}, " + f"n_eval: {ess.evaluator.n_eval})." ) ess.history.finalize(exitflag=ess.exit_flag.name) @@ -650,10 +656,13 @@ def _maybe_adapt(self, problem: Problem): def maybe_update_best(self, x: np.array, 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 + ) self._logger.debug( f"Worker {self._worker_idx} maybe sending solution {fx}. " f"best known: {self._best_known_fx}, " - f"rel change: {(fx - self._best_known_fx) / fx:.4g}, " + f"rel change: {rel_change:.4g}, " f"threshold: {self._acceptance_threshold}" ) From fc7b615b726476a898918d99c292610f10a9e9c0 Mon Sep 17 00:00:00 2001 From: Vincent Wieland Date: Fri, 3 May 2024 10:03:54 +0200 Subject: [PATCH 08/28] Require and test python >=3.10 according to NEP 29 (#1379) * Update setup.cfg Remove python 3.9 * Update ci.yml Remove python 3.9 and update to 3.11 * Update deploy.yml Change python 3.9 by 3.11 * Update ci.yml Add python 3.10 * Update setup.cfg Add python 3.12 * Update ci.yml Update from python 3.11 to 3.12 * Update deploy.yml Update from python 3.11 to 3.12 * Skip aesara tests on case of python >= 3.12 and use 3.11 for building the documentation. * Update test/base/test_objective.py Co-authored-by: Daniel Weindl * Update .github/workflows/ci.yml --------- Co-authored-by: PaulJonasJost Co-authored-by: Daniel Weindl --- .github/workflows/ci.yml | 23 +++++++++++------------ .github/workflows/deploy.yml | 2 +- setup.cfg | 4 ++-- test/base/test_objective.py | 7 +++++++ 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 75a91fccb..29869655c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.9', '3.11'] + python-version: ['3.10', '3.12'] steps: - name: Check out repository @@ -63,7 +63,7 @@ jobs: runs-on: macos-latest strategy: matrix: - python-version: ['3.11'] + python-version: ['3.12'] steps: - name: Check out repository @@ -99,7 +99,7 @@ jobs: runs-on: windows-latest strategy: matrix: - python-version: ['3.11'] + python-version: ['3.12'] steps: - name: Check out repository @@ -132,7 +132,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.9', '3.11'] + python-version: ['3.10', '3.12'] steps: - name: Check out repository @@ -171,7 +171,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.9', '3.11'] + python-version: ['3.10', '3.12'] # needed to allow julia-actions/cache to delete old caches that it has created permissions: @@ -225,8 +225,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - # ipopt does not work on 3.9 (https://github.com/mechmotum/cyipopt/issues/225) - python-version: ['3.11'] + python-version: ['3.12'] steps: - name: Check out repository @@ -262,7 +261,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.9', '3.11'] + python-version: ['3.10', '3.12'] steps: - name: Check out repository @@ -298,7 +297,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.9', '3.11'] + python-version: ['3.10', '3.12'] steps: - name: Check out repository @@ -334,7 +333,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.11'] + python-version: ['3.12'] steps: - name: Check out repository @@ -399,7 +398,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.9'] + python-version: ['3.10'] steps: - name: Check out repository @@ -429,7 +428,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.9'] + python-version: ['3.10'] steps: - name: Check out repository diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 4cb46a6e3..e1ed397bb 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.9'] + python-version: ['3.12'] steps: - name: Check out repository diff --git a/setup.cfg b/setup.cfg index e5d201411..4d94bb3b7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -33,9 +33,9 @@ classifiers = License :: OSI Approved :: BSD License Operating System :: OS Independent Programming Language :: Python + Programming Language :: Python :: 3.12 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.10 - Programming Language :: Python :: 3.9 keywords = parameter inference optimization @@ -58,7 +58,7 @@ install_requires = tqdm >= 4.46.0 tabulate >= 0.8.10 -python_requires = >=3.9 +python_requires = >=3.10 include_package_data = True # Where is my code diff --git a/test/base/test_objective.py b/test/base/test_objective.py index d2c556927..998a1f0af 100644 --- a/test/base/test_objective.py +++ b/test/base/test_objective.py @@ -2,6 +2,7 @@ import copy import numbers +import sys from functools import partial import numpy as np @@ -12,6 +13,11 @@ from ..util import CRProblem, poly_for_sensi, rosen_for_sensi +pytest_skip_aesara = pytest.mark.skipif( + sys.version_info >= (3, 12), + reason="Skipped Aesara tests on Python 3.12 or higher", +) + @pytest.fixture(params=[True, False]) def integrated(request): @@ -178,6 +184,7 @@ def rel_err(eps_): ) +@pytest_skip_aesara def test_aesara(max_sensi_order, integrated): """Test function composition and gradient computation via aesara""" import aesara.tensor as aet From 4d814282734077e4d3c9443bcfd4ddc5164d78df Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Fri, 3 May 2024 10:43:42 +0200 Subject: [PATCH 09/28] Update deprecated amici imports (#1384) --- .../inner_calculator_collector.py | 6 +++--- pypesto/hierarchical/ordinal/calculator.py | 5 +++-- pypesto/hierarchical/ordinal/solver.py | 2 +- pypesto/hierarchical/relative/calculator.py | 6 +++--- pypesto/hierarchical/relative/solver.py | 2 +- .../semiquantitative/calculator.py | 5 +++-- .../hierarchical/semiquantitative/solver.py | 2 +- pypesto/objective/amici/amici_util.py | 20 ++++++++++--------- pypesto/visualize/model_fit.py | 3 ++- pypesto/visualize/ordinal_categories.py | 3 ++- pypesto/visualize/spline_approximation.py | 7 ++++--- 11 files changed, 34 insertions(+), 27 deletions(-) diff --git a/pypesto/hierarchical/inner_calculator_collector.py b/pypesto/hierarchical/inner_calculator_collector.py index 80e3151e5..a3f1a7dd8 100644 --- a/pypesto/hierarchical/inner_calculator_collector.py +++ b/pypesto/hierarchical/inner_calculator_collector.py @@ -48,7 +48,7 @@ try: import amici import petab - from amici.parameter_mapping import ParameterMapping + from amici.petab.parameter_mapping import ParameterMapping except ImportError: petab = None ParameterMapping = None @@ -325,7 +325,7 @@ def __call__( Whether to use the FIM (if available) instead of the Hessian (if requested). """ - import amici.parameter_mapping + from amici.petab.conditions import fill_in_parameters if mode == MODE_RES and any( data_type in self.data_types @@ -403,7 +403,7 @@ def __call__( x_dct = copy.deepcopy(x_dct) x_dct.update(self.necessary_par_dummy_values) # fill in parameters - amici.parameter_mapping.fill_in_parameters( + fill_in_parameters( edatas=edatas, problem_parameters=x_dct, scaled_parameters=True, diff --git a/pypesto/hierarchical/ordinal/calculator.py b/pypesto/hierarchical/ordinal/calculator.py index 8044d5d7d..5fb9e60c6 100644 --- a/pypesto/hierarchical/ordinal/calculator.py +++ b/pypesto/hierarchical/ordinal/calculator.py @@ -32,7 +32,8 @@ try: import amici - from amici.parameter_mapping import ParameterMapping + from amici.petab.conditions import fill_in_parameters + from amici.petab.parameter_mapping import ParameterMapping except ImportError: pass @@ -155,7 +156,7 @@ def __call__( x_dct = copy.deepcopy(x_dct) # fill in parameters - amici.parameter_mapping.fill_in_parameters( + fill_in_parameters( edatas=edatas, problem_parameters=x_dct, scaled_parameters=True, diff --git a/pypesto/hierarchical/ordinal/solver.py b/pypesto/hierarchical/ordinal/solver.py index 5c65b1722..66930faec 100644 --- a/pypesto/hierarchical/ordinal/solver.py +++ b/pypesto/hierarchical/ordinal/solver.py @@ -37,7 +37,7 @@ from .problem import OrdinalProblem try: - from amici.parameter_mapping import ParameterMapping + from amici.petab.parameter_mapping import ParameterMapping except ImportError: pass diff --git a/pypesto/hierarchical/relative/calculator.py b/pypesto/hierarchical/relative/calculator.py index 3ba59452f..cfd74a42f 100644 --- a/pypesto/hierarchical/relative/calculator.py +++ b/pypesto/hierarchical/relative/calculator.py @@ -7,8 +7,8 @@ try: import amici - import amici.parameter_mapping - from amici.parameter_mapping import ParameterMapping + from amici.petab.conditions import fill_in_parameters + from amici.petab.parameter_mapping import ParameterMapping except ImportError: pass @@ -296,7 +296,7 @@ def calculate_directly( amici_solver.setSensitivityOrder(sensi_order) x_dct.update(self.inner_problem.get_dummy_values(scaled=True)) # fill in parameters - amici.parameter_mapping.fill_in_parameters( + fill_in_parameters( edatas=edatas, problem_parameters=x_dct, scaled_parameters=True, diff --git a/pypesto/hierarchical/relative/solver.py b/pypesto/hierarchical/relative/solver.py index e1c91cd76..b3000eaaa 100644 --- a/pypesto/hierarchical/relative/solver.py +++ b/pypesto/hierarchical/relative/solver.py @@ -27,7 +27,7 @@ try: import amici - from amici.parameter_mapping import ParameterMapping + from amici.petab.parameter_mapping import ParameterMapping except ImportError: pass diff --git a/pypesto/hierarchical/semiquantitative/calculator.py b/pypesto/hierarchical/semiquantitative/calculator.py index 3b2cd44a4..4991f6766 100644 --- a/pypesto/hierarchical/semiquantitative/calculator.py +++ b/pypesto/hierarchical/semiquantitative/calculator.py @@ -32,7 +32,8 @@ try: import amici - from amici.parameter_mapping import ParameterMapping + from amici.petab.conditions import fill_in_parameters + from amici.petab.parameter_mapping import ParameterMapping except ImportError: pass @@ -154,7 +155,7 @@ def __call__( ) # fill in parameters - amici.parameter_mapping.fill_in_parameters( + fill_in_parameters( edatas=edatas, problem_parameters=x_dct, scaled_parameters=True, diff --git a/pypesto/hierarchical/semiquantitative/solver.py b/pypesto/hierarchical/semiquantitative/solver.py index 07254a913..49dfeec4c 100644 --- a/pypesto/hierarchical/semiquantitative/solver.py +++ b/pypesto/hierarchical/semiquantitative/solver.py @@ -27,7 +27,7 @@ from .problem import SemiquantProblem try: - from amici.parameter_mapping import ParameterMapping + from amici.petab.parameter_mapping import ParameterMapping except ImportError: pass diff --git a/pypesto/objective/amici/amici_util.py b/pypesto/objective/amici/amici_util.py index ff5ca29fd..c5533af03 100644 --- a/pypesto/objective/amici/amici_util.py +++ b/pypesto/objective/amici/amici_util.py @@ -24,7 +24,7 @@ if TYPE_CHECKING: try: import amici - from amici.parameter_mapping import ( + from amici.petab.parameter_mapping import ( ParameterMapping, ParameterMappingForCondition, ) @@ -124,23 +124,25 @@ def create_identity_parameter_mapping( both in preequilibration and simulation, are assumed to be provided correctly in model or edatas already. """ - import amici.parameter_mapping + from amici.petab.parameter_mapping import ( + ParameterMapping, + ParameterMappingForCondition, + amici_to_petab_scale, + ) x_ids = list(amici_model.getParameterIds()) x_scales = list(amici_model.getParameterScale()) - parameter_mapping = amici.parameter_mapping.ParameterMapping() + parameter_mapping = ParameterMapping() for _ in range(n_conditions): condition_map_sim_var = {x_id: x_id for x_id in x_ids} condition_scale_map_sim_var = { - x_id: amici.parameter_mapping.amici_to_petab_scale(x_scale) + x_id: amici_to_petab_scale(x_scale) for x_id, x_scale in zip(x_ids, x_scales) } # assumes fixed parameters are filled in already - mapping_for_condition = ( - amici.parameter_mapping.ParameterMappingForCondition( - map_sim_var=condition_map_sim_var, - scale_map_sim_var=condition_scale_map_sim_var, - ) + mapping_for_condition = ParameterMappingForCondition( + map_sim_var=condition_map_sim_var, + scale_map_sim_var=condition_scale_map_sim_var, ) parameter_mapping.append(mapping_for_condition) diff --git a/pypesto/visualize/model_fit.py b/pypesto/visualize/model_fit.py index dd2fd0d4a..ef21d0686 100644 --- a/pypesto/visualize/model_fit.py +++ b/pypesto/visualize/model_fit.py @@ -14,6 +14,7 @@ import matplotlib.pyplot as plt import numpy as np import petab +from amici.petab.conditions import fill_in_parameters from amici.petab.simulations import rdatas_to_simulation_df from petab.visualize import plot_problem @@ -269,7 +270,7 @@ def _get_simulation_rdatas( for j in range(len(edatas)): edatas[j].setTimepoints(simulation_timepoints) - amici.parameter_mapping.fill_in_parameters( + fill_in_parameters( edatas=edatas, problem_parameters=x_dct, scaled_parameters=True, diff --git a/pypesto/visualize/ordinal_categories.py b/pypesto/visualize/ordinal_categories.py index 1672465a9..c229d92f6 100644 --- a/pypesto/visualize/ordinal_categories.py +++ b/pypesto/visualize/ordinal_categories.py @@ -9,6 +9,7 @@ try: import amici + from amici.petab.conditions import fill_in_parameters from petab.C import OBSERVABLE_ID from ..hierarchical.ordinal.calculator import OrdinalCalculator @@ -86,7 +87,7 @@ def plot_categories_from_pypesto_result( n_threads = pypesto_result.problem.objective.n_threads # Fill in the parameters. - amici.parameter_mapping.fill_in_parameters( + fill_in_parameters( edatas=edatas, problem_parameters=x_dct, scaled_parameters=True, diff --git a/pypesto/visualize/spline_approximation.py b/pypesto/visualize/spline_approximation.py index 5f617db30..217f445f1 100644 --- a/pypesto/visualize/spline_approximation.py +++ b/pypesto/visualize/spline_approximation.py @@ -23,6 +23,7 @@ try: import amici + from amici.petab.conditions import fill_in_parameters from ..hierarchical import InnerCalculatorCollector from ..hierarchical.semiquantitative.calculator import SemiquantCalculator @@ -105,7 +106,7 @@ def plot_splines_from_pypesto_result( observable_ids = amici_model.getObservableIds() # Fill in the parameters. - amici.parameter_mapping.fill_in_parameters( + fill_in_parameters( edatas=edatas, problem_parameters=x_dct, scaled_parameters=True, @@ -379,7 +380,7 @@ def _add_spline_mapped_simulations_to_model_fit( n_threads = pypesto_problem.objective.n_threads # Fill in the parameters. - amici.parameter_mapping.fill_in_parameters( + fill_in_parameters( edatas=edatas, problem_parameters=x_dct, scaled_parameters=True, @@ -528,7 +529,7 @@ def _obtain_regularization_for_start( n_threads = pypesto_result.problem.objective.n_threads # Fill in the parameters. - amici.parameter_mapping.fill_in_parameters( + fill_in_parameters( edatas=edatas, problem_parameters=x_dct, scaled_parameters=True, From fbfae2a4b623922a3583eda26d9e1d9f6554921f Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Fri, 3 May 2024 14:44:07 +0200 Subject: [PATCH 10/28] Remove obsolete pymc scipy requirement --- setup.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 4d94bb3b7..425cbc8d5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -117,7 +117,6 @@ mpi = mpi4py >= 3.0.3 pymc = arviz >= 0.12.1 - scipy < 1.13.0; python_version=='3.9' # https://github.com/ICB-DCM/pyPESTO/issues/1354 aesara >= 2.8.6 pymc >= 4.2.1 aesara = From 4f778cc4da88c670207df50b2da36f0295f8f90d Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Fri, 3 May 2024 16:54:49 +0200 Subject: [PATCH 11/28] GHA: use macos-12 runner (#1386) Prevents unclear timeouts that occur with `macos-14-arm64` (see #1383). --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29869655c..121e9f75f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,7 +60,7 @@ jobs: file: ./coverage.xml mac: - runs-on: macos-latest + runs-on: macos-12 strategy: matrix: python-version: ['3.12'] From e4b15be48bb15324c6a5aeb8deb6eb39da9b9e97 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Fri, 3 May 2024 18:14:12 +0200 Subject: [PATCH 12/28] SacessOptimizer: collect worker stats (#1381) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So far, most stats from different SacessOptimizer workers have only been available from the logs. Now they are also available via `SacessOptimizer.worker_results`. Additionally, the total number of objective evaluations across all workers ís logged. --- pypesto/optimize/ess/sacess.py | 60 +++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/pypesto/optimize/ess/sacess.py b/pypesto/optimize/ess/sacess.py index ae8b16883..0dee7482a 100644 --- a/pypesto/optimize/ess/sacess.py +++ b/pypesto/optimize/ess/sacess.py @@ -6,6 +6,7 @@ import multiprocessing import os import time +from dataclasses import dataclass from math import ceil, sqrt from multiprocessing import get_context from multiprocessing.managers import SyncManager @@ -128,6 +129,7 @@ def __init__( self.exit_flag = ESSExitFlag.DID_NOT_RUN self.ess_loglevel = ess_loglevel self.sacess_loglevel = sacess_loglevel + self.worker_results: list[SacessWorkerResult] = [] logger.setLevel(self.sacess_loglevel) self._tmpdir = tmpdir @@ -249,21 +251,29 @@ def minimize( # wait for finish # collect results - histories = [ + self.worker_results = [ sacess_manager._result_queue.get() for _ in range(self.num_workers) ] - self.histories = histories for p in worker_processes: p.join() logging_thread.stop() + + self.histories = [ + worker_result.history for worker_result in self.worker_results + ] + result = self._create_result(problem) walltime = time.time() - start_time + n_eval_total = sum( + worker_result.n_eval for worker_result in self.worker_results + ) logger.info( - f"{self.__class__.__name__} stopped after {walltime:3g}s with global best " - f"{result.optimize_result[0].fval}." + f"{self.__class__.__name__} stopped after {walltime:3g}s " + f"and {n_eval_total} objective evaluations " + f"with global best {result.optimize_result[0].fval}." ) return result @@ -587,7 +597,15 @@ def run( ) ess.history.finalize(exitflag=ess.exit_flag.name) - self._manager._result_queue.put(ess.history) + worker_result = SacessWorkerResult( + x=ess.x_best, + fx=ess.fx_best, + history=ess.history, + n_eval=ess.evaluator.n_eval, + n_iter=ess.n_iter, + exit_flag=ess.exit_flag, + ) + self._manager._result_queue.put(worker_result) ess._report_final() def _setup_ess(self, startpoint_method: StartpointMethod) -> ESSOptimizer: @@ -1025,3 +1043,35 @@ def __call__( def __repr__(self): return f"{self.__class__.__name__}(fides_options={self._fides_options}, fides_kwargs={self._fides_kwargs})" + + +@dataclass +class SacessWorkerResult: + """Container for :class:`SacessWorker` results. + + Contains various information about the optimization process of a single + :class:`SacessWorker` instance that is to be sent to + :class:`SacessOptimizer`. + + Attributes + ---------- + x: + Best parameters found. + fx: + Objective value corresponding to ``x``. + n_eval: + Number of objective evaluations performed. + n_iter: + Number of scatter search iterations performed. + history: + History object containing information about the optimization process. + exit_flag: + Exit flag of the optimization process. + """ + + x: np.array + fx: float + n_eval: int + n_iter: int + history: "pypesto.history.memory.MemoryHistory" + exit_flag: ESSExitFlag From cacd525ce897a5b6c228b3b5424cbcb3ce1d3307 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Sat, 4 May 2024 13:59:04 +0200 Subject: [PATCH 13/28] Fix `pypesto.sample.geweke_test.spectrum` for nfft<=3 (#1388) For `nfft<=3`, this function computed `k` by converting infinity to int64. For the last [4 years](https://github.com/ICB-DCM/pyPESTO/commit/fdbacb49df944051cfccf5116a1de30da9365747), this seemed to not cause any trouble, because `np.float64("inf").astype(int)` yielded -9223372036854775808 and the loop did not run. However, with the current `macos-14` GitHub runner, this yields 9223372036854775807, and accordingly, rather long computation times (this is what caused #1383). I am not really sure what caused this change. macos-12 vs macos-14, arm64 vs x86, ...? all should be IEEE 754-compliant. Maybe some different integer type? :man_shrugging: With this change `macos-14` runners yield the same results as the other runners. However, I am not familiar with computing power spectral density and I don't know if this is indeed what should happen. It would be great if somebody more familiar with that topic could double-check. (Also, can't we use [scipy.signal.welch](https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.welch.html#scipy.signal.welch) here?) --- pypesto/sample/geweke_test.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pypesto/sample/geweke_test.py b/pypesto/sample/geweke_test.py index 186c9890d..f9c810c6e 100644 --- a/pypesto/sample/geweke_test.py +++ b/pypesto/sample/geweke_test.py @@ -46,7 +46,11 @@ def spectrum(x: np.ndarray, nfft: int = None, nw: int = None) -> np.ndarray: n = nw # Number of windows - k = np.floor((n - n_overlap) / (nw - n_overlap)).astype(int) + k = ( + np.floor((n - n_overlap) / (nw - n_overlap)).astype(int) + if nw != n_overlap + else 0 + ) index = np.arange(nw) # Normalizing scale factor kmu = k * np.linalg.norm(w) ** 2 From ccc7f72ecb70adcb3fa7b063064efbf5c924a327 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Sun, 5 May 2024 18:24:03 +0200 Subject: [PATCH 14/28] GHA: Test macos-14 (#1387) * switch to `macos-14` previous issues with macos-14 were a mix of #1388 and caching with seemingly random switching between macos-12 and macos-14 runners when using macos-latest * include arch in cache keys; use uniform cache keys * change some imports in `test/sample/test_sample.py` to be able to run subsets of tests despite missing optional dependencies * adjust resource limits to avoid [random](https://github.com/ICB-DCM/pyPESTO/actions/runs/8945258779/job/24573887731) [failures](https://github.com/ICB-DCM/pyPESTO/actions/runs/8945870387/job/24575587898?pr=1387) such as: ``` /Users/runner/work/pyPESTO/pyPESTO/.tox/base/lib/python3.12/site-packages/_pytest/main.py:339: PluggyTeardownRaisedWarning: A plugin raised an exception during an old-style hookwrapper teardown. Plugin: _cov, Hook: pytest_runtestloop DataError: Couldn't use data file '/Users/runner/work/pyPESTO/pyPESTO/.coverage.Mac-1714771409828.local.9672.XvRVVmRx': unable to open database file For more information see https://pluggy.readthedocs.io/en/stable/api_reference.html#pluggy.PluggyTeardownRaisedWarning config.hook.pytest_runtestloop(session=session) ..FFFF [100%] INTERNALERROR> Traceback (most recent call last): INTERNALERROR> File "/Users/runner/work/pyPESTO/pyPESTO/.tox/base/lib/python3.12/site-packages/coverage/sqlitedb.py", line 52, in _connect INTERNALERROR> sqlite3.OperationalError: unable to open database file INTERNALERROR> INTERNALERROR> The above exception was the direct cause of the following exception: INTERNALERROR> INTERNALERROR> Traceback (most recent call last): INTERNALERROR> File "/Users/runner/work/pyPESTO/pyPESTO/.tox/base/lib/python3.12/site-packages/_pytest/main.py", line 285, in wrap_session INTERNALERROR> session.exitstatus = doit(config, session) or 0 INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^ INTERNALERROR> File "/Users/runner/work/pyPESTO/pyPESTO/.tox/base/lib/python3.12/site-packages/_pytest/main.py", line 339, in _main INTERNALERROR> config.hook.pytest_runtestloop(session=session) INTERNALERROR> File "/Users/runner/work/pyPESTO/pyPESTO/.tox/base/lib/python3.12/site-packages/pluggy/_hooks.py", line 513, in __call__ INTERNALERROR> return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult) INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ INTERNALERROR> File "/Users/runner/work/pyPESTO/pyPESTO/.tox/base/lib/python3.12/site-packages/pluggy/_manager.py", line 120, in _hookexec INTERNALERROR> return self._inner_hookexec(hook_name, methods, kwargs, firstresult) INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ INTERNALERROR> File "/Users/runner/work/pyPESTO/pyPESTO/.tox/base/lib/python3.12/site-packages/pluggy/_callers.py", line 156, in _multicall INTERNALERROR> teardown[0].send(outcome) INTERNALERROR> File "/Users/runner/work/pyPESTO/pyPESTO/.tox/base/lib/python3.12/site-packages/pytest_cov/plugin.py", line 339, in pytest_runtestloop INTERNALERROR> self.cov_controller.finish() INTERNALERROR> File "/Users/runner/work/pyPESTO/pyPESTO/.tox/base/lib/python3.12/site-packages/pytest_cov/engine.py", line 46, in ensure_topdir_wrapper INTERNALERROR> return meth(self, *args, **kwargs) INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^^^^ INTERNALERROR> File "/Users/runner/work/pyPESTO/pyPESTO/.tox/base/lib/python3.12/site-packages/pytest_cov/engine.py", line 256, in finish INTERNALERROR> self.cov.save() INTERNALERROR> File "/Users/runner/work/pyPESTO/pyPESTO/.tox/base/lib/python3.12/site-packages/coverage/control.py", line 785, in save INTERNALERROR> data = self.get_data() INTERNALERROR> ^^^^^^^^^^^^^^^ INTERNALERROR> File "/Users/runner/work/pyPESTO/pyPESTO/.tox/base/lib/python3.12/site-packages/coverage/control.py", line 865, in get_data INTERNALERROR> if self._collector.flush_data(): INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ INTERNALERROR> File "/Users/runner/work/pyPESTO/pyPESTO/.tox/base/lib/python3.12/site-packages/coverage/collector.py", line 535, in flush_data INTERNALERROR> self.covdata.add_lines(self.mapped_file_dict(line_data)) INTERNALERROR> File "/Users/runner/work/pyPESTO/pyPESTO/.tox/base/lib/python3.12/site-packages/coverage/sqldata.py", line 124, in _wrapped INTERNALERROR> File "/Users/runner/work/pyPESTO/pyPESTO/.tox/base/lib/python3.12/site-packages/coverage/sqldata.py", line 495, in add_lines INTERNALERROR> File "/Users/runner/work/pyPESTO/pyPESTO/.tox/base/lib/python3.12/site-packages/coverage/sqldata.py", line 564, in _choose_lines_or_arcs INTERNALERROR> File "/Users/runner/work/pyPESTO/pyPESTO/.tox/base/lib/python3.12/site-packages/coverage/sqldata.py", line 344, in _connect INTERNALERROR> File "/Users/runner/work/pyPESTO/pyPESTO/.tox/base/lib/python3.12/site-packages/coverage/sqldata.py", line 287, in _open_db INTERNALERROR> File "/Users/runner/work/pyPESTO/pyPESTO/.tox/base/lib/python3.12/site-packages/coverage/sqldata.py", line 291, in _read_db INTERNALERROR> File "/Users/runner/work/pyPESTO/pyPESTO/.tox/base/lib/python3.12/site-packages/coverage/sqlitedb.py", line 88, in __enter__ INTERNALERROR> File "/Users/runner/work/pyPESTO/pyPESTO/.tox/base/lib/python3.12/site-packages/coverage/sqlitedb.py", line 54, in _connect INTERNALERROR> coverage.exceptions.DataError: Couldn't use data file '/Users/runner/work/pyPESTO/pyPESTO/.coverage.Mac-1714771409828.local.9672.XvRVVmRx': unable to open database file ``` --- .github/workflows/ci.yml | 28 ++++++++++++++-------------- test/sample/test_sample.py | 9 ++++++--- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 121e9f75f..ab9c76f57 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: path: | ~/.cache/pip .tox/ - key: ${{ runner.os }}-${{ matrix.python-version }}-ci-${{ github.job }} + key: "${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}-ci-${{ github.job }}" - name: Install dependencies run: .github/workflows/install_deps.sh amici @@ -60,7 +60,7 @@ jobs: file: ./coverage.xml mac: - runs-on: macos-12 + runs-on: macos-latest strategy: matrix: python-version: ['3.12'] @@ -80,14 +80,14 @@ jobs: path: | ~/.cache/pip .tox/ - key: ${{ runner.os }}-${{ matrix.python-version }}-ci + key: "${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}-ci-${{ github.job }}" - name: Install dependencies run: .github/workflows/install_deps.sh amici - name: Run tests timeout-minutes: 30 - run: tox -e base + run: ulimit -n 65536 65536 && tox -e base - name: Coverage uses: codecov/codecov-action@v3 @@ -116,7 +116,7 @@ jobs: path: | ~\AppData\Local\pip\Cache .tox - key: ${{ runner.os }}-${{ matrix.python-version }}-ci + key: "${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}-ci-${{ github.job }}" - name: Install dependencies run: | @@ -149,7 +149,7 @@ jobs: path: | ~/.cache/pip .tox/ - key: ${{ runner.os }}-${{ matrix.python-version }}-ci-${{ github.job }} + key: "${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}-ci-${{ github.job }}" - name: Install dependencies run: .github/workflows/install_deps.sh amici pysb @@ -193,7 +193,7 @@ jobs: path: | ~/.cache/pip .tox/ - key: ${{ runner.os }}-${{ matrix.python-version }}-ci-${{ github.job }} + key: "${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}-ci-${{ github.job }}" - name: Install julia uses: julia-actions/setup-julia@v1 @@ -242,7 +242,7 @@ jobs: path: | ~/.cache/pip .tox/ - key: ${{ runner.os }}-${{ matrix.python-version }}-ci-${{ github.job }} + key: "${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}-ci-${{ github.job }}" - name: Install dependencies run: .github/workflows/install_deps.sh ipopt @@ -278,7 +278,7 @@ jobs: path: | ~/.cache/pip .tox/ - key: ${{ runner.os }}-${{ matrix.python-version }}-ci-${{ github.job }} + key: "${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}-ci-${{ github.job }}" - name: Install dependencies run: .github/workflows/install_deps.sh amici @@ -314,7 +314,7 @@ jobs: path: | ~/.cache/pip .tox/ - key: ${{ runner.os }}-${{ matrix.python-version }}-ci-${{ github.job }} + key: "${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}-ci-${{ github.job }}" - name: Install dependencies run: .github/workflows/install_deps.sh amici @@ -350,7 +350,7 @@ jobs: path: | ~/.cache/pip .tox/ - key: ${{ runner.os }}-${{ matrix.python-version }}-ci-${{ github.job }} + key: "${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}-ci-${{ github.job }}" - name: Install dependencies run: pip install tox pre-commit @@ -382,7 +382,7 @@ jobs: path: | ~/.cache/pip .tox/ - key: ${{ runner.os }}-${{ matrix.python-version }}-ci-${{ github.job }} + key: "${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}-ci-${{ github.job }}" - name: Install dependencies run: .github/workflows/install_deps.sh doc amici @@ -415,7 +415,7 @@ jobs: path: | ~/.cache/pip .tox/ - key: ${{ runner.os }}-${{ matrix.python-version }}-ci-${{ github.job }} + key: "${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}-ci-${{ github.job }}" - name: Install dependencies run: .github/workflows/install_deps.sh amici ipopt @@ -445,7 +445,7 @@ jobs: path: | ~/.cache/pip .tox/ - key: ${{ runner.os }}-${{ matrix.python-version }}-ci-${{ github.job }} + key: "${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}-ci-${{ github.job }}" - name: Install dependencies run: .github/workflows/install_deps.sh amici diff --git a/test/sample/test_sample.py b/test/sample/test_sample.py index a33adde18..4f1d6fb86 100644 --- a/test/sample/test_sample.py +++ b/test/sample/test_sample.py @@ -3,7 +3,6 @@ import os import numpy as np -import petab import pytest import scipy.optimize as so from scipy.integrate import quad @@ -11,10 +10,8 @@ import pypesto import pypesto.optimize as optimize -import pypesto.petab import pypesto.sample as sample from pypesto.C import OBJECTIVE_NEGLOGLIKE, OBJECTIVE_NEGLOGPOST -from pypesto.sample.pymc import PymcSampler def gaussian_llh(x): @@ -96,6 +93,10 @@ def rosenbrock_problem(): def create_petab_problem(): + import petab + + import pypesto.petab + current_path = os.path.dirname(os.path.realpath(__file__)) dir_path = os.path.abspath( os.path.join(current_path, "..", "..", "doc", "example") @@ -187,6 +188,8 @@ def sampler(request): n_chains=5, ) elif request.param == "Pymc": + from pypesto.sample.pymc import PymcSampler + return PymcSampler(tune=5, progressbar=False) elif request.param == "Emcee": return sample.EmceeSampler(nwalkers=10) From d9aa0424bed72fd57dff1164668f660c00f190c4 Mon Sep 17 00:00:00 2001 From: Polina Lakrisenko Date: Mon, 6 May 2024 09:12:36 +0200 Subject: [PATCH 15/28] add load method to Hdf5AmiciHistory (#1370) * add load method to Hdf5AmiciHistory --- pypesto/history/amici.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pypesto/history/amici.py b/pypesto/history/amici.py index 6d0c8fb9b..0e2fcb44b 100644 --- a/pypesto/history/amici.py +++ b/pypesto/history/amici.py @@ -43,6 +43,18 @@ def __init__( ): super().__init__(id, file, options=options) + @staticmethod + def load( + id: str, + file: Union[str, Path], + options: Union[HistoryOptions, dict] = None, + ) -> "Hdf5AmiciHistory": + """Load the History object from memory.""" + history = Hdf5AmiciHistory(id=id, file=file, options=options) + if options is None: + history.recover_options(file) + return history + @staticmethod def _simulation_to_values(x, result, used_time): values = Hdf5History._simulation_to_values(x, result, used_time) From 80ae7fa5268475f9f6bc80fd6e4b4509aacbf580 Mon Sep 17 00:00:00 2001 From: Paul Jonas Jost <70631928+PaulJonasJost@users.noreply.github.com> Date: Mon, 6 May 2024 11:08:06 +0200 Subject: [PATCH 16/28] Correct header for roadrunner example (#1394) Removed a spacebar preventing header to be seen as such (#1394) --- doc/example/roadrunner.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/example/roadrunner.ipynb b/doc/example/roadrunner.ipynb index 06b540937..527276085 100644 --- a/doc/example/roadrunner.ipynb +++ b/doc/example/roadrunner.ipynb @@ -3,7 +3,7 @@ { "cell_type": "markdown", "source": [ - " # RoadRunner in pyPESTO\n", + "# RoadRunner in pyPESTO\n", "\n", "**After going through this notebook, you will be able to...**\n", "\n", From cf3cbc534690147b03f49517243bc6cd22711a7b Mon Sep 17 00:00:00 2001 From: Paul Jonas Jost <70631928+PaulJonasJost@users.noreply.github.com> Date: Tue, 7 May 2024 09:49:43 +0200 Subject: [PATCH 17/28] remove random.seed in favor of np.random.seed (#1391) * remove random.see in favor of np.random.seed * added import numpy as np import --- doc/example/amici.ipynb | 6 +- doc/example/roadrunner.ipynb | 245 ++++++++++++++++++++++------------- doc/example/store.ipynb | 3 +- 3 files changed, 157 insertions(+), 97 deletions(-) diff --git a/doc/example/amici.ipynb b/doc/example/amici.ipynb index 477c63e52..3b0bcb96e 100644 --- a/doc/example/amici.ipynb +++ b/doc/example/amici.ipynb @@ -36,7 +36,6 @@ "source": [ "# import\n", "import logging\n", - "import random\n", "import tempfile\n", "from pprint import pprint\n", "\n", @@ -57,7 +56,8 @@ "mpl.rcParams[\"figure.dpi\"] = 100\n", "mpl.rcParams[\"font.size\"] = 18\n", "\n", - "random.seed(1912)\n", + "# Set seed for reproducibility\n", + "np.random.seed(1912)\n", "\n", "\n", "# name of the model that will also be the name of the python module\n", @@ -929,8 +929,6 @@ "outputs": [], "source": [ "%%time\n", - "# Set seed for reproducibility\n", - "np.random.seed(1)\n", "result = optimize.minimize(\n", " problem=problem,\n", " optimizer=optimizer,\n", diff --git a/doc/example/roadrunner.ipynb b/doc/example/roadrunner.ipynb index 527276085..f666efb6e 100644 --- a/doc/example/roadrunner.ipynb +++ b/doc/example/roadrunner.ipynb @@ -2,6 +2,12 @@ "cells": [ { "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "source": [ "# RoadRunner in pyPESTO\n", "\n", @@ -9,31 +15,37 @@ "\n", "* ... create a pyPESTO problem using [RoadRunner](https://www.libroadrunner.org) as a simulator directly from a PEtab problem.\n", "* ... perform a parameter estimation using pyPESTO with RoadRunner as a simulator, setting advanced simulator features." - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "code", "execution_count": null, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "outputs": [], "source": [ "# install pyPESTO with roadrunner support\n", "# %pip install pypesto[roadrunner,petab] --quiet" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "code", "execution_count": null, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "outputs": [], "source": [ "# import\n", - "import random\n", "import matplotlib as mpl\n", + "import numpy as np\n", "import petab\n", "import pypesto.objective\n", "import pypesto.optimize as optimize\n", @@ -47,7 +59,7 @@ "mpl.rcParams[\"figure.dpi\"] = 100\n", "mpl.rcParams[\"font.size\"] = 18\n", "\n", - "random.seed(1912)\n", + "np.random.seed(1912)\n", "\n", "\n", "# name of the model that will also be the name of the python module\n", @@ -55,25 +67,31 @@ "\n", "# output directory\n", "model_output_dir = \"tmp/\" + model_name" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "source": [ "## Creating pyPESTO problem from PEtab\n", "\n", "The [PEtab file format](https://petab.readthedocs.io/en/latest/documentation_data_format.html) stores all the necessary information to define a parameter estimation problem. This includes the model, the experimental data, the parameters to estimate, and the experimental conditions. Using the `pypesto_rr.PetabImporterRR` class, we can create a pyPESTO problem directly from a PEtab problem." - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "code", "execution_count": null, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "outputs": [], "source": [ "petab_yaml = f\"./{model_name}/{model_name}.yaml\"\n", @@ -81,34 +99,43 @@ "petab_problem = petab.Problem.from_yaml(petab_yaml)\n", "importer = pypesto_rr.PetabImporterRR(petab_problem)\n", "problem = importer.create_problem()" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "source": [ "We now have a pyPESTO problem that we can use to perform parameter estimation. We can get some information on the RoadRunnerObjective and access the RoadRunner model." - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "code", "execution_count": null, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "outputs": [], "source": [ "pprint(problem.objective.get_config())" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "code", "execution_count": null, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "outputs": [], "source": [ "# direct simulation of the model using roadrunner\n", @@ -117,23 +144,29 @@ ")\n", "pprint(sim_res)\n", "problem.objective.roadrunner_instance.plot();" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "source": [ "For more details on interacting with the roadrunner instance, we refer to the [documentation of libroadrunner](https://libroadrunner.readthedocs.io/en/latest/). However, we point out that while theoretical possible, we **strongly advice against** changing the model in that manner." - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "code", "execution_count": null, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "outputs": [], "source": [ "ret = problem.objective(\n", @@ -142,25 +175,31 @@ " return_dict=True,\n", ")\n", "pprint(ret)" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "source": [ "## Optimization\n", "\n", "To optimize a problem using a RoadRunner objective, we can set additional solver options for the ODE solver." - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "code", "execution_count": null, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "outputs": [], "source": [ "optimizer = optimize.ScipyOptimizer()\n", @@ -172,14 +211,17 @@ ")\n", "engine = pypesto.engine.SingleCoreEngine()\n", "problem.objective.solver_options = solver_options" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "code", "execution_count": null, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "outputs": [], "source": [ "result = optimize.minimize(\n", @@ -189,78 +231,99 @@ " engine=engine\n", ")\n", "display(Markdown(result.summary()))" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "source": [ "Disclaimer: Currently there are two main things not yet fully supported with roadrunner objectives. One is parallelization of the optimization using MultiProcessEngine. The other is explicit gradients of the objective function. While the former will be added in a near future version, we will show a workaround for the latter, until it is implemented." - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "source": [ "### Visualization Methods\n", "\n", "In order to visualize the optimization, there are a few things possible. For a more extensive explanation we refer to the \"getting started\" notebook." - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "code", "execution_count": null, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "outputs": [], "source": [ "visualize.waterfall(result);" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "code", "execution_count": null, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "outputs": [], "source": [ "visualize.parameters(result);" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "code", "execution_count": null, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "outputs": [], "source": [ "visualize.parameters_correlation_matrix(result);" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "source": [ "### Sensitivities via finite differences\n", "\n", "Some solvers need a way to calculate the sensitivities, which currently RoadRunner Objectives do not suport. For this scenario, we can use the FiniteDifferences objective in pypesto, which wraps a given objective into one, that computes sensitivities via finite differences." - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "code", "execution_count": null, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "outputs": [], "source": [ "# no support for sensitivities\n", @@ -274,14 +337,17 @@ " pprint(ret)\n", "except ValueError as e:\n", " pprint(e)" - ], - "metadata": { - "collapsed": false - } + ] }, { "cell_type": "code", "execution_count": null, + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, "outputs": [], "source": [ "objective_fd = pypesto.objective.FD(problem.objective)\n", @@ -296,31 +362,28 @@ " pprint(ret)\n", "except ValueError as e:\n", " pprint(e)" - ], - "metadata": { - "collapsed": false - } + ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", - "version": 2 + "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.6" + "pygments_lexer": "ipython3", + "version": "3.10.2" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 4 } diff --git a/doc/example/store.ipynb b/doc/example/store.ipynb index f96520f6c..58f9902af 100644 --- a/doc/example/store.ipynb +++ b/doc/example/store.ipynb @@ -59,7 +59,6 @@ "outputs": [], "source": [ "import logging\n", - "import random\n", "import tempfile\n", "\n", "import matplotlib as mpl\n", @@ -76,7 +75,7 @@ "mpl.rcParams[\"figure.dpi\"] = 100\n", "mpl.rcParams[\"font.size\"] = 18\n", "# set a random seed to get reproducible results\n", - "random.seed(3142)\n", + "np.random.seed(3142)\n", "\n", "%matplotlib inline" ] From 18be6e38f02793b3b4982f550e3a83764062dfca Mon Sep 17 00:00:00 2001 From: Maren Philipps <55318391+m-philipps@users.noreply.github.com> Date: Wed, 8 May 2024 13:14:03 +0200 Subject: [PATCH 18/28] update readme links (#1399) * update readme links * Add relative to READ_ME.md --------- Co-authored-by: Doresic --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 036437888..c75c53c08 100644 --- a/README.md +++ b/README.md @@ -25,12 +25,15 @@ pyPESTO features include: * Parameter estimation pipeline for systems biology problems specified in [SBML](http://sbml.org/) and [PEtab](https://github.com/PEtab-dev/PEtab) ([example](https://github.com/ICB-DCM/pyPESTO/blob/master/doc/example/petab_import.ipynb)) +* Parameter estimation with relative (scaled and offset) data as described in + [Schmiester et al. (2020)](https://doi.org/10.1093/bioinformatics/btz581). + ([example](https://github.com/ICB-DCM/pyPESTO/blob/master/doc/example/relative_data.ipynb)) * Parameter estimation with ordinal data as described in [Schmiester et al. (2020)](https://doi.org/10.1007/s00285-020-01522-w) and [Schmiester et al. (2021)](https://doi.org/10.1093/bioinformatics/btab512). - ([example](https://github.com/ICB-DCM/pyPESTO/blob/master/doc/example/ordinal.ipynb)) -* Parameter estimation with censored data. ([example](https://github.com/ICB-DCM/pyPESTO/blob/master/doc/example/censored.ipynb)) -* Parameter estimation with nonlinear-monotone data. ([example](https://github.com/ICB-DCM/pyPESTO/blob/master/doc/example/nonlinear_monotone.ipynb)) + ([example](https://github.com/ICB-DCM/pyPESTO/blob/master/doc/example/ordinal_data.ipynb)) +* Parameter estimation with censored data. ([example](https://github.com/ICB-DCM/pyPESTO/blob/master/doc/example/censored_data.ipynb)) +* Parameter estimation with nonlinear-monotone data. ([example](https://github.com/ICB-DCM/pyPESTO/blob/master/doc/example/semiquantitative_data.ipynb)) ## Quick install From 9d5c13b1c3752c9c19ca6bf96fedebaf9a64e61b Mon Sep 17 00:00:00 2001 From: Paul Jonas Jost <70631928+PaulJonasJost@users.noreply.github.com> Date: Tue, 14 May 2024 09:53:03 +0200 Subject: [PATCH 19/28] More detailed defaults for `problem.get_full_vector` (#1393) * Made get_full_vector more intuitive * Corrected he usage of get_full vector. Removed it for lb and ub in favor of lb_full and ub_full. * Apply suggestions from code review Co-authored-by: Maren Philipps <55318391+m-philipps@users.noreply.github.com> --------- Co-authored-by: Maren Philipps <55318391+m-philipps@users.noreply.github.com> --- pypesto/problem/base.py | 14 ++++++++++---- pypesto/result/optimize.py | 6 +++--- pypesto/visualize/parameters.py | 4 ++-- test/base/test_history.py | 4 +--- test/base/test_x_fixed.py | 3 +-- 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/pypesto/problem/base.py b/pypesto/problem/base.py index d8d29847b..ad4b87142 100644 --- a/pypesto/problem/base.py +++ b/pypesto/problem/base.py @@ -332,7 +332,10 @@ def unfix_parameters( self.normalize() def get_full_vector( - self, x: Union[np.ndarray, None], x_fixed_vals: Iterable[float] = None + self, + x: Union[np.ndarray, None], + x_fixed_vals: Iterable[float] = None, + x_is_grad: bool = False, ) -> Union[np.ndarray, None]: """ Map vector from dim to dim_full. Usually used for x, grad. @@ -342,9 +345,9 @@ def get_full_vector( x: array_like, shape=(dim,) The vector in dimension dim. x_fixed_vals: array_like, ndim=1, optional - The values to be used for the fixed indices. If None, then nans are - inserted. Usually, None will be used for grad and - problem.x_fixed_vals for x. + The values to be used for the fixed indices. If None and x_is_grad=False, problem.x_fixed_vals is used; for x_is_grad=True, nans are inserted. + x_is_grad: bool + If true, x is treated as gradients. """ if x is None: return None @@ -362,6 +365,9 @@ def get_full_vector( x_full[..., self.x_free_indices] = x if x_fixed_vals is not None: x_full[..., self.x_fixed_indices] = x_fixed_vals + return x_full + if not x_is_grad: + x_full[..., self.x_fixed_indices] = self.x_fixed_vals return x_full def get_full_matrix( diff --git a/pypesto/result/optimize.py b/pypesto/result/optimize.py index 4fb2883fa..6f1d69964 100644 --- a/pypesto/result/optimize.py +++ b/pypesto/result/optimize.py @@ -191,10 +191,10 @@ def update_to_full(self, problem: Problem) -> None: problem which contains info about how to convert to full vectors or matrices """ - self.x = problem.get_full_vector(self.x, problem.x_fixed_vals) - self.grad = problem.get_full_vector(self.grad) + self.x = problem.get_full_vector(self.x) + self.grad = problem.get_full_vector(self.grad, x_is_grad=True) self.hess = problem.get_full_matrix(self.hess) - self.x0 = problem.get_full_vector(self.x0, problem.x_fixed_vals) + self.x0 = problem.get_full_vector(self.x0) self.free_indices = np.array(problem.x_free_indices) diff --git a/pypesto/visualize/parameters.py b/pypesto/visualize/parameters.py index 81b10e1e2..c50f4fdeb 100644 --- a/pypesto/visualize/parameters.py +++ b/pypesto/visualize/parameters.py @@ -417,8 +417,8 @@ def handle_inputs( ub = result.problem.get_reduced_vector(ub, parameter_indices) x_labels = [x_labels[int(i)] for i in parameter_indices] else: - lb = result.problem.get_full_vector(lb) - ub = result.problem.get_full_vector(ub) + lb = result.problem.lb_full + ub = result.problem.ub_full if inner_xs is not None and plot_inner_parameters: lb = np.concatenate([lb, inner_lb]) diff --git a/test/base/test_history.py b/test/base/test_history.py index 57d01a3f2..9056fd7a2 100644 --- a/test/base/test_history.py +++ b/test/base/test_history.py @@ -236,9 +236,7 @@ def check_reconstruct_history( def check_history_consistency(self, start: pypesto.OptimizerResult): def xfull(x_trace): - return self.problem.get_full_vector( - x_trace, self.problem.x_fixed_vals - ) + return self.problem.get_full_vector(x_trace) if isinstance(start.history, (CsvHistory, Hdf5History)): # get index of optimal parameter diff --git a/test/base/test_x_fixed.py b/test/base/test_x_fixed.py index fc7ea2e00..320cd8d08 100644 --- a/test/base/test_x_fixed.py +++ b/test/base/test_x_fixed.py @@ -41,8 +41,7 @@ def test_optimize(): # fixed values written into parameter vector assert optimizer_result.x[1] == 1 - lb_full = problem.get_full_vector(problem.lb) - assert len(lb_full) == 5 + assert len(problem.lb_full) == 5 def create_problem(): From 1206dc0826a1d2feb7aa15de72d2ecd696189a7c Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Tue, 14 May 2024 11:11:57 +0200 Subject: [PATCH 20/28] Doc: Update references (#1292) Papers using pypesto --- doc/using_pypesto.bib | 98 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 87 insertions(+), 11 deletions(-) diff --git a/doc/using_pypesto.bib b/doc/using_pypesto.bib index bb19094d9..ebaa06055 100644 --- a/doc/using_pypesto.bib +++ b/doc/using_pypesto.bib @@ -79,17 +79,19 @@ @Article{FroehlichSor2022 } @Article{FroehlichGer2022, - author = {Fr{\"o}hlich, Fabian and Gerosa, Luca and Muhlich, Jeremy and Sorger, Peter K.}, - journal = {bioRxiv}, - title = {Mechanistic model of MAPK signaling reveals how allostery and rewiring contribute to drug resistance}, - year = {2022}, - abstract = {BRAFV600E is prototypical of oncogenic mutations that can be targeted therapeutically and treatment of BRAF-mutant melanomas with RAF and MEK inhibitors results in rapid tumor regression. However, drug-induced rewiring causes BRAFV600E melanoma cells to rapidly acquire a drug-adapted state. In patients this is thought to promote acquisition or selection for resistance mutations and disease recurrence. In this paper we use an energy-based implementation of ordinary differential equations in combination with proteomic, transcriptomic and imaging data from melanoma cells, to model the precise mechanisms responsible for adaptive rewiring. We demonstrate the presence of two parallel MAPK (RAF-MEK-ERK kinase) reaction channels in BRAFV600E melanoma cells that are differentially sensitive to RAF and MEK inhibitors. This arises from differences in protein oligomerization and allosteric regulation induced by oncogenic mutations and drug binding. As a result, the RAS-regulated MAPK channel can be active under conditions in which the BRAFV600E-driven channel is fully inhibited. Causal tracing demonstrates that this provides a sufficient quantitative explanation for initial and acquired responses to multiple different RAF and MEK inhibitors individually and in combination.HighlightsA thermodynamic framework enables structure-based description of allosteric interactions in the EGFR and MAPK pathwaysCausal decomposition of efficacy of targeted drugs elucidates rewiring of MAPK channelsModel-based extrapolation from type I{\textonehalf} RAF inhibitors to type II RAF inhibitorsA unified mechanistic explanation for adaptive and genetic resistance across BRAF-cancersCompeting Interest StatementPKS is a member of the SAB or Board of Directors of Glencoe Software, Applied Biomath, and RareCyte Inc. and has equity in these companies; PKS is also a member of the SAB of NanoString and a consultant for Montai Health and Merck. LG is currently an employee of Genentech. PKS and LG declare that none of these relationships are directly or indirectly related to the content of this manuscript.}, - creationdate = {2023-01-26T11:32:12}, - doi = {10.1101/2022.02.17.480899}, - elocation-id = {2022.02.17.480899}, - eprint = {https://www.biorxiv.org/content/early/2022/02/18/2022.02.17.480899.full.pdf}, - publisher = {Cold Spring Harbor Laboratory}, - url = {https://www.biorxiv.org/content/early/2022/02/18/2022.02.17.480899}, + author = {Fr{\"o}hlich, Fabian and Gerosa, Luca and Muhlich, Jeremy and Sorger, Peter K.}, + journal = {bioRxiv}, + title = {Mechanistic model of MAPK signaling reveals how allostery and rewiring contribute to drug resistance}, + year = {2022}, + abstract = {BRAFV600E is prototypical of oncogenic mutations that can be targeted therapeutically and treatment of BRAF-mutant melanomas with RAF and MEK inhibitors results in rapid tumor regression. However, drug-induced rewiring causes BRAFV600E melanoma cells to rapidly acquire a drug-adapted state. In patients this is thought to promote acquisition or selection for resistance mutations and disease recurrence. In this paper we use an energy-based implementation of ordinary differential equations in combination with proteomic, transcriptomic and imaging data from melanoma cells, to model the precise mechanisms responsible for adaptive rewiring. We demonstrate the presence of two parallel MAPK (RAF-MEK-ERK kinase) reaction channels in BRAFV600E melanoma cells that are differentially sensitive to RAF and MEK inhibitors. This arises from differences in protein oligomerization and allosteric regulation induced by oncogenic mutations and drug binding. As a result, the RAS-regulated MAPK channel can be active under conditions in which the BRAFV600E-driven channel is fully inhibited. Causal tracing demonstrates that this provides a sufficient quantitative explanation for initial and acquired responses to multiple different RAF and MEK inhibitors individually and in combination.HighlightsA thermodynamic framework enables structure-based description of allosteric interactions in the EGFR and MAPK pathwaysCausal decomposition of efficacy of targeted drugs elucidates rewiring of MAPK channelsModel-based extrapolation from type I{\textonehalf} RAF inhibitors to type II RAF inhibitorsA unified mechanistic explanation for adaptive and genetic resistance across BRAF-cancersCompeting Interest StatementPKS is a member of the SAB or Board of Directors of Glencoe Software, Applied Biomath, and RareCyte Inc. and has equity in these companies; PKS is also a member of the SAB of NanoString and a consultant for Montai Health and Merck. LG is currently an employee of Genentech. PKS and LG declare that none of these relationships are directly or indirectly related to the content of this manuscript.}, + creationdate = {2023-01-26T11:32:12}, + doi = {10.1101/2022.02.17.480899}, + elocation-id = {2022.02.17.480899}, + eprint = {https://www.biorxiv.org/content/early/2022/02/18/2022.02.17.480899.full.pdf}, + modificationdate = {2024-05-13T09:29:21}, + publisher = {Cold Spring Harbor Laboratory}, + ranking = {rank1}, + url = {https://www.biorxiv.org/content/early/2022/02/18/2022.02.17.480899}, } @Article{GerosaChi2020, @@ -207,4 +209,78 @@ @Article{FischerHolzhausenRoe2023 url = {https://www.biorxiv.org/content/early/2023/01/19/2023.01.17.523407}, } +@Article{KissVen2024, + author = {Kiss, Anna E and Venkatasubramani, Anuroop V and Pathirana, Dilan and Krause, Silke and Sparr, Aline Campos and Hasenauer, Jan and Imhof, Axel and Müller, Marisa and Becker, Peter B}, + journal = {Nucleic Acids Research}, + title = {{Processivity and specificity of histone acetylation by the male-specific lethal complex}}, + year = {2024}, + issn = {0305-1048}, + month = {02}, + pages = {gkae123}, + abstract = {{Acetylation of lysine 16 of histone H4 (H4K16ac) stands out among the histone modifications, because it decompacts the chromatin fiber. The metazoan acetyltransferase MOF (KAT8) regulates transcription through H4K16 acetylation. Antibody-based studies had yielded inconclusive results about the selectivity of MOF to acetylate the H4 N-terminus. We used targeted mass spectrometry to examine the activity of MOF in the male-specific lethal core (4-MSL) complex on nucleosome array substrates. This complex is part of the Dosage Compensation Complex (DCC) that activates X-chromosomal genes in male Drosophila. During short reaction times, MOF acetylated H4K16 efficiently and with excellent selectivity. Upon longer incubation, the enzyme progressively acetylated lysines 12, 8 and 5, leading to a mixture of oligo-acetylated H4. Mathematical modeling suggests that MOF recognizes and acetylates H4K16 with high selectivity, but remains substrate-bound and continues to acetylate more N-terminal H4 lysines in a processive manner. The 4-MSL complex lacks non-coding roX RNA, a critical component of the DCC. Remarkably, addition of RNA to the reaction non-specifically suppressed H4 oligo-acetylation in favor of specific H4K16 acetylation. Because RNA destabilizes the MSL-nucleosome interaction in vitro we speculate that RNA accelerates enzyme-substrate turn-over in vivo, thus limiting the processivity of MOF, thereby increasing specific H4K16 acetylation.}}, + creationdate = {2024-02-28T18:27:01}, + doi = {10.1093/nar/gkae123}, + eprint = {https://academic.oup.com/nar/advance-article-pdf/doi/10.1093/nar/gkae123/56756494/gkae123.pdf}, + modificationdate = {2024-02-28T18:27:01}, + url = {https://doi.org/10.1093/nar/gkae123}, +} + +@Article{DoresicGre2024, + author = {Domagoj Dore{\v s}i{\'c} and Stephan Grein and Jan Hasenauer}, + journal = {bioRxiv}, + title = {Efficient parameter estimation for ODE models of cellular processes using semi-quantitative data}, + year = {2024}, + abstract = {Quantitative dynamical models facilitate the understanding of biological processes and the prediction of their dynamics. The parameters of these models are commonly estimated from experimental data. Yet, experimental data generated from different techniques do not provide direct information about the state of the system but a non-linear (monotonic) transformation of it. For such semi-quantitative data, when this transformation is unknown, it is not apparent how the model simulations and the experimental data can be compared. Here, we propose a versatile spline-based approach for the integration of a broad spectrum of semi-quantitative data into parameter estimation. We derive analytical formulas for the gradients of the hierarchical objective function and show that this substantially increases the estimation efficiency. Subsequently, we demonstrate that the method allows for the reliable discovery of unknown measurement transformations. Furthermore, we show that this approach can significantly improve the parameter inference based on semi-quantitative data in comparison to available methods. Modelers can easily apply our method by using our implementation in the open-source Python Parameter EStimation TOolbox (pyPESTO).Competing Interest StatementThe authors have declared no competing interest.}, + creationdate = {2024-04-20T13:06:42}, + doi = {10.1101/2024.01.26.577371}, + elocation-id = {2024.01.26.577371}, + eprint = {https://www.biorxiv.org/content/early/2024/01/30/2024.01.26.577371.full.pdf}, + modificationdate = {2024-04-20T13:06:42}, + publisher = {Cold Spring Harbor Laboratory}, + url = {https://www.biorxiv.org/content/early/2024/01/30/2024.01.26.577371}, +} + +@Article{ArrudaSch2023, + author = {Jonas Arruda and Yannik Sch{\"a}lte and Clemens Peiter and Olga Teplytska and Ulrich Jaehde and Jan Hasenauer}, + journal = {bioRxiv}, + title = {An amortized approach to non-linear mixed-effects modeling based on neural posterior estimation}, + year = {2023}, + abstract = {Non-linear mixed-effects models are a powerful tool for studying heterogeneous populations in various fields, including biology, medicine, economics, and engineering. However, fitting these models to data is computationally challenging if the description of individuals is complex and the population is large. To address this issue, we propose a novel machine learning-based approach: We exploit neural density estimation based on normalizing flows to approximate individual-specific posterior distributions in an amortized fashion, thereby allowing for an efficient inference of population parameters. Applying this approach to problems from cell biology and pharmacology, we demonstrate its scalability to large data sets in an unprecedented manner. Moreover, we show that it enables accurate uncertainty quantification and extends to stochastic models, which established methods, such as SAEM and FOCEI are unable to handle. Thus, our approach outperforms state-of-the-art methods and improves the analysis capabilities for heterogeneous populations.Competing Interest StatementThe authors have declared no competing interest.}, + creationdate = {2024-04-22T12:56:00}, + doi = {10.1101/2023.08.22.554273}, + elocation-id = {2023.08.22.554273}, + eprint = {https://www.biorxiv.org/content/early/2023/08/23/2023.08.22.554273.full.pdf}, + modificationdate = {2024-04-22T12:56:00}, + publisher = {Cold Spring Harbor Laboratory}, + url = {https://www.biorxiv.org/content/early/2023/08/23/2023.08.22.554273}, +} + +@Article{MerktAli2024, + author = {Merkt, Simon and Ali, Solomon and Gudina, Esayas Kebede and Adissu, Wondimagegn and Gize, Addisu and Muenchhoff, Maximilian and Graf, Alexander and Krebs, Stefan and Elsbernd, Kira and Kisch, Rebecca and Betizazu, Sisay Sirgu and Fantahun, Bereket and Bekele, Delayehu and Rubio-Acero, Raquel and Gashaw, Mulatu and Girma, Eyob and Yilma, Daniel and Zeynudin, Ahmed and Paunovic, Ivana and Hoelscher, Michael and Blum, Helmut and Hasenauer, Jan and Kroidl, Arne and Wieser, Andreas}, + journal = {Nature Communications}, + title = {Long-term monitoring of SARS-CoV-2 seroprevalence and variants in Ethiopia provides prediction for immunity and cross-immunity}, + year = {2024}, + issn = {2041-1723}, + month = apr, + number = {1}, + volume = {15}, + creationdate = {2024-04-29T08:32:16}, + doi = {10.1038/s41467-024-47556-2}, + modificationdate = {2024-04-29T08:32:16}, + publisher = {Springer Science and Business Media LLC}, +} + +@Article{FalcoCoh2024a, + author = {Falcó, Carles and Cohen, Daniel J. and Carrillo, José A. and Baker, Ruth E.}, + journal = {Biophysical Journal}, + title = {Quantifying cell cycle regulation by tissue crowding}, + year = {2024}, + issn = {0006-3495}, + month = may, + creationdate = {2024-05-13T09:29:26}, + doi = {10.1016/j.bpj.2024.05.003}, + modificationdate = {2024-05-13T09:29:26}, + publisher = {Elsevier BV}, +} + @Comment{jabref-meta: databaseType:bibtex;} From 63c56626ef0ef6248c7ccb2bbb6203441142dfa2 Mon Sep 17 00:00:00 2001 From: Paul Jonas Jost <70631928+PaulJonasJost@users.noreply.github.com> Date: Wed, 15 May 2024 10:52:44 +0200 Subject: [PATCH 21/28] fix #1401 (#1402) * As a first test change the mac version * Add a remark to revert the runs-on * Set Runs-on to macos-13 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ab9c76f57..e9847bc68 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,7 +60,7 @@ jobs: file: ./coverage.xml mac: - runs-on: macos-latest + runs-on: macos-13 # TODO: change to macos-latest after the next release strategy: matrix: python-version: ['3.12'] From 049493cac5a1374130da66d675afd66fda21da82 Mon Sep 17 00:00:00 2001 From: Paul Jonas Jost <70631928+PaulJonasJost@users.noreply.github.com> Date: Wed, 15 May 2024 12:09:25 +0200 Subject: [PATCH 22/28] Fix in doc of visualise.optimization_stats (#1390) * Fixed doc example from generic call (that did not exists) to apropriate call * Marked code as codeblocks --- pypesto/visualize/optimization_stats.py | 64 ++++++++++++++----------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/pypesto/visualize/optimization_stats.py b/pypesto/visualize/optimization_stats.py index 74a5323e7..65e864033 100644 --- a/pypesto/visualize/optimization_stats.py +++ b/pypesto/visualize/optimization_stats.py @@ -53,15 +53,19 @@ def optimization_run_properties_one_plot( Examples -------- - optimization_properties_per_multistart( - result1, - properties_to_plot=['time'], - colors=[.5, .9, .9, .3]) - - optimization_properties_per_multistart( - result1, - properties_to_plot=['time', 'n_grad'], - colors=[[.5, .9, .9, .3], [.2, .1, .9, .5]]) + .. code-block:: python + + optimization_run_properties_one_plot( + result1, + properties_to_plot=['time'], + colors=[.5, .9, .9, .3] + ) + + optimization_run_properties_one_plot( + result1, + properties_to_plot=['time', 'n_grad'], + colors=[[.5, .9, .9, .3], [.2, .1, .9, .5]] + ) """ if properties_to_plot is None: properties_to_plot = [ @@ -156,24 +160,30 @@ def optimization_run_properties_per_multistart( Examples -------- - optimization_properties_per_multistart( - result1, - properties_to_plot=['time'], - colors=[.5, .9, .9, .3]) - - optimization_properties_per_multistart( - [result1, result2], - properties_to_plot=['time'], - colors=[[.5, .9, .9, .3], [.2, .1, .9, .5]]) - - optimization_properties_per_multistart( - result1, - properties_to_plot=['time', 'n_grad'], - colors=[.5, .9, .9, .3]) - - optimization_properties_per_multistart( - [result1, result2], properties_to_plot=['time', 'n_fval'], - colors=[[.5, .9, .9, .3], [.2, .1, .9, .5]]) + .. code-block:: python + + optimization_run_properties_per_multistart( + result1, + properties_to_plot=['time'], + colors=[.5, .9, .9, .3] + ) + + optimization_run_properties_per_multistart( + [result1, result2], + properties_to_plot=['time'], + colors=[[.5, .9, .9, .3], [.2, .1, .9, .5]] + ) + + optimization_run_properties_per_multistart( + result1, + properties_to_plot=['time', 'n_grad'], + colors=[.5, .9, .9, .3] + ) + + optimization_run_properties_per_multistart( + [result1, result2], properties_to_plot=['time', 'n_fval'], + colors=[[.5, .9, .9, .3], [.2, .1, .9, .5]] + ) """ if properties_to_plot is None: properties_to_plot = [ From d5fde03fd3bdc8855c3641d0c9f92d0ca3795294 Mon Sep 17 00:00:00 2001 From: Jonas Arruda <69197639+arrjon@users.noreply.github.com> Date: Thu, 16 May 2024 09:36:31 +0200 Subject: [PATCH 23/28] Calling prior in sampling with fixed parameters does not work (#1378) * fixing call of prior in sampling * check if priors not None * remove deepcopy --- pypesto/objective/priors.py | 6 ------ pypesto/problem/base.py | 9 +++++++++ pypesto/sample/emcee.py | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/pypesto/objective/priors.py b/pypesto/objective/priors.py index 493cf9b41..3b8f177fe 100644 --- a/pypesto/objective/priors.py +++ b/pypesto/objective/priors.py @@ -1,7 +1,6 @@ import logging import math from collections.abc import Sequence -from copy import deepcopy from typing import Callable, Union import numpy as np @@ -68,11 +67,6 @@ def __init__( self.prior_list = prior_list super().__init__(x_names) - def __deepcopy__(self, memodict=None): - """Create deepcopy of object.""" - other = NegLogParameterPriors(deepcopy(self.prior_list)) - return other - def call_unprocessed( self, x: np.ndarray, diff --git a/pypesto/problem/base.py b/pypesto/problem/base.py index ad4b87142..a97a3a5fb 100644 --- a/pypesto/problem/base.py +++ b/pypesto/problem/base.py @@ -237,6 +237,15 @@ def normalize(self) -> None: x_fixed_vals=self.x_fixed_vals, ) + # make prior aware of fixed parameters (for sampling etc.) + if self.x_priors is not None: + self.x_priors.update_from_problem( + dim_full=self.dim_full, + x_free_indices=self.x_free_indices, + x_fixed_indices=self.x_fixed_indices, + x_fixed_vals=self.x_fixed_vals, + ) + # sanity checks if len(self.x_scales) != self.dim_full: raise AssertionError("x_scales dimension invalid.") diff --git a/pypesto/sample/emcee.py b/pypesto/sample/emcee.py index 1afe8930f..f0e30c569 100644 --- a/pypesto/sample/emcee.py +++ b/pypesto/sample/emcee.py @@ -149,7 +149,7 @@ def initialize( lb = self.problem.lb ub = self.problem.ub - # parameter dimenstion + # parameter dimension ndim = len(self.problem.x_free_indices) def log_prob(x): From 7fe40bace62a41bf4391be018aadc17370d08316 Mon Sep 17 00:00:00 2001 From: Paul Jonas Jost <70631928+PaulJonasJost@users.noreply.github.com> Date: Thu, 16 May 2024 14:51:06 +0200 Subject: [PATCH 24/28] Update `test_thermodynamic_integration` (#1385) * Lowered samples, removed comparison of two different amount of chains * Removed even argument, due to scipy wanting to go for "simpson" and depracating "even" * Removed warning for `test_thermodynamic_integration` --------- Co-authored-by: Jonas Arruda <69197639+arrjon@users.noreply.github.com> Co-authored-by: Dilan Pathirana <59329744+dilpath@users.noreply.github.com> --- pypesto/sample/metropolis.py | 8 ++++ pypesto/sample/parallel_tempering.py | 1 - test/sample/test_sample.py | 69 ++++++++++++++-------------- 3 files changed, 43 insertions(+), 35 deletions(-) diff --git a/pypesto/sample/metropolis.py b/pypesto/sample/metropolis.py index 34c5d8d85..7213dfcc3 100644 --- a/pypesto/sample/metropolis.py +++ b/pypesto/sample/metropolis.py @@ -129,6 +129,14 @@ def _perform_step( # compute log prior lprior_new = -self.neglogprior(x_new) + # if lpost_new is -inf, x_new will not be accepted + if lpost_new == -np.inf: + # update proposal + self._update_proposal( + x, lpost, -np.inf, len(self.trace_neglogpost) + 1 + ) + return x, lpost, lprior + if not self.temper_lpost: # extract current log likelihood value llh = lpost - lprior diff --git a/pypesto/sample/parallel_tempering.py b/pypesto/sample/parallel_tempering.py index 2d54857f6..306774c46 100644 --- a/pypesto/sample/parallel_tempering.py +++ b/pypesto/sample/parallel_tempering.py @@ -260,7 +260,6 @@ def compute_log_evidence( # integrate from low to high temperature y=mean_loglike_per_beta, x=temps, - even="last", ) else: raise ValueError( diff --git a/test/sample/test_sample.py b/test/sample/test_sample.py index 4f1d6fb86..40d246aa4 100644 --- a/test/sample/test_sample.py +++ b/test/sample/test_sample.py @@ -796,42 +796,43 @@ def test_thermodynamic_integration(): problem = gaussian_problem() # approximation should be better for more chains - for n_chains, tol in zip([10, 20], [1, 1e-1]): - sampler = sample.ParallelTemperingSampler( - internal_sampler=sample.AdaptiveMetropolisSampler(), - options={"show_progress": False, "beta_init": "beta_decay"}, - n_chains=n_chains, - ) + n_chains = 10 + tol = 1 + sampler = sample.ParallelTemperingSampler( + internal_sampler=sample.AdaptiveMetropolisSampler(), + options={"show_progress": False, "beta_init": "beta_decay"}, + n_chains=n_chains, + ) - result = optimize.minimize( - problem, - progress_bar=False, - ) + result = optimize.minimize( + problem, + progress_bar=False, + ) - result = sample.sample( - problem, - n_samples=10000, - result=result, - sampler=sampler, - ) + result = sample.sample( + problem, + n_samples=2000, + result=result, + sampler=sampler, + ) - # compute the log evidence using trapezoid and simpson rule - log_evidence = sampler.compute_log_evidence(result, method="trapezoid") - log_evidence_not_all = sampler.compute_log_evidence( - result, method="trapezoid", use_all_chains=False - ) - log_evidence_simps = sampler.compute_log_evidence( - result, method="simpson" - ) + # compute the log evidence using trapezoid and simpson rule + log_evidence = sampler.compute_log_evidence(result, method="trapezoid") + log_evidence_not_all = sampler.compute_log_evidence( + result, method="trapezoid", use_all_chains=False + ) + log_evidence_simps = sampler.compute_log_evidence(result, method="simpson") - # compute evidence - evidence = quad( - lambda x: 1 / (problem.ub - problem.lb) * np.exp(gaussian_llh(x)), - a=problem.lb, - b=problem.ub, - ) + # compute evidence + evidence = quad( + lambda x: 1 + / (problem.ub[0] - problem.lb[0]) + * np.exp(gaussian_llh(x)), + a=problem.lb[0], + b=problem.ub[0], + ) - # compare to known value - assert np.isclose(log_evidence, np.log(evidence[0]), atol=tol) - assert np.isclose(log_evidence_not_all, np.log(evidence[0]), atol=tol) - assert np.isclose(log_evidence_simps, np.log(evidence[0]), atol=tol) + # compare to known value + assert np.isclose(log_evidence, np.log(evidence[0]), atol=tol) + assert np.isclose(log_evidence_not_all, np.log(evidence[0]), atol=tol) + assert np.isclose(log_evidence_simps, np.log(evidence[0]), atol=tol) From fefefd53aa85bcaab8da182dfd79ce7d1e3707a0 Mon Sep 17 00:00:00 2001 From: Jonas Arruda <69197639+arrjon@users.noreply.github.com> Date: Tue, 21 May 2024 14:52:37 +0200 Subject: [PATCH 25/28] Variational inference with PyMC (#1306) * variational inference fit * remove variational from sample * make pymc object accessible * save as McmcPtResult * tests added * add warning in write_result() --- pypesto/store/save_to_hdf5.py | 6 + pypesto/variational/__init__.py | 9 + pypesto/variational/pymc.py | 196 +++++++++++++++++++ pypesto/variational/variational_inference.py | 136 +++++++++++++ test/variational/__init__.py | 1 + test/variational/test_variational.py | 72 +++++++ 6 files changed, 420 insertions(+) create mode 100644 pypesto/variational/__init__.py create mode 100644 pypesto/variational/pymc.py create mode 100644 pypesto/variational/variational_inference.py create mode 100644 test/variational/__init__.py create mode 100644 test/variational/test_variational.py diff --git a/pypesto/store/save_to_hdf5.py b/pypesto/store/save_to_hdf5.py index a38f34a06..71d58d950 100644 --- a/pypesto/store/save_to_hdf5.py +++ b/pypesto/store/save_to_hdf5.py @@ -316,3 +316,9 @@ def write_result( if sample: pypesto_sample_writer = SamplingResultHDF5Writer(filename) pypesto_sample_writer.write(result, overwrite=overwrite) + + if hasattr(result, "variational_result"): + logger.warning( + "Results from variational inference are not saved in the hdf5 file. " + "You have to save them manually." + ) diff --git a/pypesto/variational/__init__.py b/pypesto/variational/__init__.py new file mode 100644 index 000000000..1d3065277 --- /dev/null +++ b/pypesto/variational/__init__.py @@ -0,0 +1,9 @@ +""" +Variational inference +====== + +Find the best variational approximation in a given family to a distribution from which we can sample. +""" + +from .pymc import PymcVariational +from .variational_inference import variational_fit diff --git a/pypesto/variational/pymc.py b/pypesto/variational/pymc.py new file mode 100644 index 000000000..cfb7348f4 --- /dev/null +++ b/pypesto/variational/pymc.py @@ -0,0 +1,196 @@ +"""Pymc v4 Sampler for Variational Inference.""" + +import logging +from typing import Optional + +import numpy as np +import pytensor.tensor as pt +from scipy import stats + +from ..objective import FD +from ..result import McmcPtResult +from ..sample.pymc import PymcObjectiveOp, PymcSampler +from ..sample.sampler import SamplerImportError + +logger = logging.getLogger(__name__) + + +# implementation based on the pymc sampler code in pypesto and: +# https://www.pymc.io/projects/examples/en/latest/variational_inference/variational_api_quickstart.html + + +class PymcVariational(PymcSampler): + """Wrapper around Pymc v4 variational inference. + + Parameters + ---------- + step_function: + A pymc step function, e.g. NUTS, Slice. If not specified, pymc + determines one automatically (preferable). + **kwargs: + Options are directly passed on to `pymc.fit`. + """ + + def fit( + self, + n_iterations: int, + method: str = "advi", + random_seed: Optional[int] = None, + start_sigma: Optional = None, + inf_kwargs: Optional = None, + beta: float = 1.0, + **kwargs, + ): + """ + Sample the problem. + + Parameters + ---------- + n_iterations: + Number of iterations. + method: str or :class:`Inference` of pymc + string name is case-insensitive in: + - 'advi' for ADVI + - 'fullrank_advi' for FullRankADVI + - 'svgd' for Stein Variational Gradient Descent + - 'asvgd' for Amortized Stein Variational Gradient Descent + random_seed: int + random seed for reproducibility + start_sigma: `dict[str, np.ndarray]` + starting standard deviation for inference, only available for method 'advi' + inf_kwargs: dict + additional kwargs passed to pymc.Inference + beta: + Inverse temperature (e.g. in parallel tempering). + """ + try: + import pymc + except ImportError: + raise SamplerImportError("pymc") from None + + problem = self.problem + if not problem.objective.has_grad: + logger.info( + "The objective function does not provide gradients. " + "Finite differences will be used." + ) + problem.objective = FD(obj=problem.objective) + log_post = PymcObjectiveOp.create_instance(problem.objective, beta) + + x0 = None + x_names_free = problem.get_reduced_vector(problem.x_names) + if self.x0 is not None: + x0 = { + x_name: val + for x_name, val in zip(problem.x_names, self.x0) + if x_name in x_names_free + } + + # create model context + with pymc.Model(): + # parameter bounds as uniform prior + _k = [ + pymc.Uniform(x_name, lower=lb, upper=ub) + for x_name, lb, ub in zip( + x_names_free, + problem.lb, + problem.ub, + ) + ] + + # convert parameters to PyTensor tensor variable + theta = pt.as_tensor_variable(_k) + + # define distribution with log-posterior as density + pymc.Potential("potential", log_post(theta)) + + # record function values + pymc.Deterministic("loggyposty", log_post(theta)) + + # perform the actual sampling + data = pymc.fit( + n=int(n_iterations), + method=method, + random_seed=random_seed, + start=x0, + start_sigma=start_sigma, + inf_kwargs=inf_kwargs, + **kwargs, + ) + + self.data = data + + def sample(self, n_samples: int, beta: float = 1.0) -> McmcPtResult: + """ + Sample from the variational approximation and return McmcPtResult object. + + Parameters + ---------- + n_samples: + Number of samples to be computed. + """ + # get InferenceData object + pymc_data = self.data.sample(n_samples) + x_names_free = self.problem.get_reduced_vector(self.problem.x_names) + post_samples = np.concatenate( + [pymc_data.posterior[name].values for name in x_names_free] + ).T + return McmcPtResult( + trace_x=post_samples[np.newaxis, :], + trace_neglogpost=pymc_data.posterior.loggyposty.values, + trace_neglogprior=np.full( + pymc_data.posterior.loggyposty.values.shape, np.nan + ), + betas=np.array([1.0] * post_samples.shape[0]), + burn_in=0, + auto_correlation=0, + effective_sample_size=n_samples, + message="variational inference results", + ) + + def get_variational_parameters(self) -> (list, list): + """Get the internal pymc variational parameters.""" + return ( + [param.name for param in self.data.params], + [param.eval() for param in self.data.params], + ) + + def set_variational_parameters(self, param_list: list): + """ + Set the internal pymc variational parameters. + + Parameters + ---------- + param_list: + List of tuples of the form (param_name, param_value). + """ + if len(param_list) != len(self.data.params): + raise ValueError( + "The number of parameters does not match the number of variational parameters." + ) + for i, param in enumerate(param_list): + self.data.params[i].set_value(param) + + def eval_variational_log_density(self, x: np.ndarray) -> np.ndarray: + """ + Evaluate the log density of the variational approximation at x_points. + + Parameters + ---------- + x: + The points at which to evaluate the log density. + """ + # TODO: add support for other methods + logger.warning( + "currently only supports the methods `advi` and `fullrank_advi`" + ) + + if x.ndim == 1: + x = x.reshape(1, -1) + log_density_at_points = np.zeros_like(x) + for i, point in enumerate(x): + log_density_at_points[i] = stats.multivariate_normal.logpdf( + point, mean=self.data.mean.eval(), cov=self.data.cov.eval() + ) + vi_log_density = np.sum(log_density_at_points, axis=-1) + return vi_log_density diff --git a/pypesto/variational/variational_inference.py b/pypesto/variational/variational_inference.py new file mode 100644 index 000000000..23b0c8266 --- /dev/null +++ b/pypesto/variational/variational_inference.py @@ -0,0 +1,136 @@ +"""Functions for variational inference accessible to the user. Currently only pymc is supported.""" + +import logging +from time import process_time +from typing import Callable, List, Optional, Union + +import numpy as np + +from ..problem import Problem +from ..result import Result +from ..sample.util import bound_n_samples_from_env +from ..store import autosave +from .pymc import PymcVariational + +logger = logging.getLogger(__name__) + + +def variational_fit( + problem: Problem, + n_iterations: int, + method: str = "advi", + n_samples: Optional[int] = None, + random_seed: Optional[int] = None, + start_sigma: Optional[dict[str, np.ndarray]] = None, + x0: Union[np.ndarray, List[np.ndarray]] = None, + result: Result = None, + filename: Union[str, Callable, None] = None, + overwrite: bool = False, + **kwargs, +) -> Result: + """ + Call to do parameter sampling. + + Parameters + ---------- + problem: + The problem to be solved. If None is provided, a + :class:`pypesto.AdaptiveMetropolisSampler` is used. + n_iterations: + Number of iterations for the optimization. + method: str or :class:`Inference` of pymc (only interface currently supported) + string name is case-insensitive in: + - 'advi' for ADVI + - 'fullrank_advi' for FullRankADVI + - 'svgd' for Stein Variational Gradient Descent + - 'asvgd' for Amortized Stein Variational Gradient Descent + n_samples: + Number of samples to generate after optimization. + random_seed: int + random seed for reproducibility + start_sigma: `dict[str, np.ndarray]` + starting standard deviation for inference, only available for method 'advi' + x0: + Initial parameter for the variational optimization. If None, the best parameter + found in optimization is used. + result: + A result to write to. If None provided, one is created from the + problem. + filename: + Name of the hdf5 file, where the result will be saved. Default is + None, which deactivates automatic saving. If set to + "Auto" it will automatically generate a file named + `year_month_day_profiling_result.hdf5`. + Optionally a method, see docs for `pypesto.store.auto.autosave`. + overwrite: + Whether to overwrite `result/sampling` in the autosave file + if it already exists. + + Returns + ------- + result: + A result with filled in sample_options part. + """ + # prepare result object + if result is None: + result = Result(problem) + + # number of samples + if n_iterations is not None: + n_iterations = bound_n_samples_from_env(n_iterations) + + # try to find initial parameters + if x0 is None: + result.optimize_result.sort() + if len(result.optimize_result.list) > 0: + x0 = problem.get_reduced_vector( + result.optimize_result.list[0]["x"] + ) + + # set variational inference + # currently we only support pymc + variational = PymcVariational() + + # initialize sampler to problem + variational.initialize(problem=problem, x0=x0) + + # perform the sampling and track time + t_start = process_time() + variational.fit( + n_iterations=n_iterations, + method=method, + random_seed=random_seed, + start_sigma=start_sigma, + **kwargs, + ) + t_elapsed = process_time() - t_start + logger.info("Elapsed time: " + str(t_elapsed)) + + # extract results and save samples to pypesto result + if n_samples is None or n_samples == 0: + # constructing a McmcPtResult object with nearly empty trace_x + n_samples = 1 + + result.sample_result = variational.sample(n_samples) + result.sample_result.time = t_elapsed + + autosave( + filename=filename, + result=result, + store_type="sample", + overwrite=overwrite, + ) + + # make pymc object available in result + # TODO: if needed, we can add a result object for variational inference methods + result.variational_result = variational + ( + result.sample_result.variational_parameters_names, + result.sample_result.variational_parameters, + ) = variational.get_variational_parameters() + if filename is not None: + logger.warning( + "Variational parameters are not saved in the hdf5 file. You have to save them manually." + ) + + return result diff --git a/test/variational/__init__.py b/test/variational/__init__.py new file mode 100644 index 000000000..4ce01abf5 --- /dev/null +++ b/test/variational/__init__.py @@ -0,0 +1 @@ +"""Variational inference tests.""" diff --git a/test/variational/test_variational.py b/test/variational/test_variational.py new file mode 100644 index 000000000..c3b829bf3 --- /dev/null +++ b/test/variational/test_variational.py @@ -0,0 +1,72 @@ +"""Tests for `pypesto.sample` methods.""" + +import pytest +from scipy.stats import kstest + +import pypesto.optimize as optimize +from pypesto.variational import variational_fit + +from ..sample.test_sample import ( + gaussian_mixture_problem, + gaussian_problem, + rosenbrock_problem, +) + + +@pytest.fixture(params=["gaussian", "gaussian_mixture", "rosenbrock"]) +def problem(request): + if request.param == "gaussian": + return gaussian_problem() + if request.param == "gaussian_mixture": + return gaussian_mixture_problem() + elif request.param == "rosenbrock": + return rosenbrock_problem() + + +def test_pipeline(problem): + """Check that a typical pipeline runs through.""" + # optimization + optimizer = optimize.ScipyOptimizer(options={"maxiter": 10}) + result = optimize.minimize( + problem=problem, + n_starts=3, + optimizer=optimizer, + progress_bar=False, + ) + + # sample + result = variational_fit( + problem=problem, + n_iterations=100, + n_samples=10, + result=result, + ) + + +def test_ground_truth(): + """Test whether we actually retrieve correct distributions.""" + problem = gaussian_problem() + + result = optimize.minimize( + problem, + progress_bar=False, + ) + + result = variational_fit( + problem, + n_iterations=10000, + n_samples=5000, + result=result, + ) + + # get samples of first chain + samples = result.sample_result.trace_x[0].flatten() + + # test against different distributions + statistic, pval = kstest(samples, "norm") + print(statistic, pval) + assert statistic < 0.1 + + statistic, pval = kstest(samples, "uniform") + print(statistic, pval) + assert statistic > 0.1 From 4222672d3d68bbb70c6f72d935e24eac3c3e346c Mon Sep 17 00:00:00 2001 From: Paul Jonas Jost <70631928+PaulJonasJost@users.noreply.github.com> Date: Sun, 26 May 2024 10:49:51 +0200 Subject: [PATCH 26/28] Saves pypesto and python version to the problem. (#1382) * Saves pypesto and python version to the problem. * Update pypesto/problem/base.py Co-authored-by: Daniel Weindl --------- Co-authored-by: Daniel Weindl --- pypesto/problem/base.py | 5 +++++ pypesto/store/save_to_hdf5.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pypesto/problem/base.py b/pypesto/problem/base.py index a97a3a5fb..15548b023 100644 --- a/pypesto/problem/base.py +++ b/pypesto/problem/base.py @@ -1,5 +1,6 @@ import copy import logging +import sys from collections.abc import Iterable from typing import ( Callable, @@ -15,6 +16,7 @@ from ..objective import ObjectiveBase from ..objective.priors import NegLogParameterPriors from ..startpoint import StartpointMethod, to_startpoint_method, uniform +from ..version import __version__ SupportsFloatIterableOrValue = Union[Iterable[SupportsFloat], SupportsFloat] SupportsIntIterableOrValue = Union[Iterable[SupportsInt], SupportsInt] @@ -164,6 +166,9 @@ def __init__( startpoint_method = uniform # convert startpoint method to class instance self.startpoint_method = to_startpoint_method(startpoint_method) + # save python and pypesto version + self.python_version = ".".join(map(str, sys.version_info[:3])) + self.pypesto_version = __version__ @property def lb(self) -> np.ndarray: diff --git a/pypesto/store/save_to_hdf5.py b/pypesto/store/save_to_hdf5.py index 71d58d950..3804bfbbb 100644 --- a/pypesto/store/save_to_hdf5.py +++ b/pypesto/store/save_to_hdf5.py @@ -92,7 +92,7 @@ def write(self, problem, overwrite: bool = False): value = np.asarray(value) if value.size: write_array(problem_grp, problem_attr, value) - elif isinstance(value, Integral): + elif isinstance(value, (Integral, str)): problem_grp.attrs[problem_attr] = value From 0b8417fcbb783e9248190dacbfa430eacce17840 Mon Sep 17 00:00:00 2001 From: Giacomo Fabrini <65025875+GiacomoFabrini@users.noreply.github.com> Date: Wed, 29 May 2024 17:18:10 +0100 Subject: [PATCH 27/28] Fix JaxObjective (#1400) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add pre_post_processor.setter and base_objective wrapper functions * fix docstring & simplify implementation * extend tests * fix tests * add returns to docstring * fix max sensi orders, add test --------- Co-authored-by: Paul Jonas Jost <70631928+PaulJonasJost@users.noreply.github.com> Co-authored-by: Fabian Fröhlich Co-authored-by: Fabian Fröhlich --- pypesto/objective/jax/base.py | 65 +++++++++++++++++++++++++++-------- test/base/test_objective.py | 28 +++++++++++++-- 2 files changed, 75 insertions(+), 18 deletions(-) diff --git a/pypesto/objective/jax/base.py b/pypesto/objective/jax/base.py index 49327bb37..ea86b07a0 100644 --- a/pypesto/objective/jax/base.py +++ b/pypesto/objective/jax/base.py @@ -8,7 +8,7 @@ import copy from functools import partial -from typing import Union +from typing import Callable, Union import numpy as np @@ -26,12 +26,24 @@ "`pip install jax jaxlib`." ) from None + +def _base_objective_as_jax_array_tuple(func: Callable): + def decorator(*args, **kwargs): + # make sure return is a tuple of jax arrays + results = func(*args, **kwargs) + if isinstance(results, tuple): + return tuple(jnp.array(r) for r in results) + return jnp.array(results) + + return decorator + + # jax compatible (jit-able) objective function using external callback, see # https://jax.readthedocs.io/en/latest/notebooks/external_callbacks.html @partial(custom_jvp, nondiff_argnums=(0,)) -def _device_fun(base_objective: ObjectiveBase, x: jnp.array): +def _device_fun(base_objective: ObjectiveBase, x: jnp.array) -> jnp.array: """Jax compatible objective function execution using external callback. Parameters @@ -40,15 +52,24 @@ def _device_fun(base_objective: ObjectiveBase, x: jnp.array): The wrapped jax objective. x: jax computed input array. + + Returns + ------- + fval : jnp.array + The function value as 0-dimensional jax array. """ return jax.pure_callback( - partial(base_objective, sensi_orders=(0,)), + _base_objective_as_jax_array_tuple( + partial(base_objective, sensi_orders=(0,)) + ), jax.ShapeDtypeStruct((), x.dtype), x, ) -def _device_fun_value_and_grad(base_objective: ObjectiveBase, x: jnp.array): +def _device_fun_value_and_grad( + base_objective: ObjectiveBase, x: jnp.array +) -> tuple[jnp.array, jnp.array]: """Jax compatible objective gradient execution using external callback. This function will be called when computing the gradient of the @@ -63,14 +84,23 @@ def _device_fun_value_and_grad(base_objective: ObjectiveBase, x: jnp.array): The wrapped jax objective. x: jax computed input array. + + Returns + ------- + fval : jnp.array + The function value as 0-dimensional jax array. + grad : jnp.array + The gradient as jax array. """ return jax.pure_callback( - partial( - base_objective, - sensi_orders=( - 0, - 1, - ), + _base_objective_as_jax_array_tuple( + partial( + base_objective, + sensi_orders=( + 0, + 1, + ), + ) ), ( jax.ShapeDtypeStruct((), x.dtype), @@ -112,7 +142,7 @@ class JaxObjective(ObjectiveBase): Note ---- - Currently only implements MODE_FUN and sensi_orders=(0,). Support for + Currently only implements MODE_FUN and sensi_orders<=1. Support for MODE_RES should be straightforward to add. """ @@ -143,7 +173,7 @@ def check_sensi_orders(self, sensi_orders, mode: ModeType) -> bool: else: return ( self.base_objective.check_sensi_orders(sensi_orders, mode) - and max(sensi_orders) == 0 + and max(sensi_orders) <= 1 ) def __call__( @@ -204,15 +234,20 @@ def __deepcopy__(self, memodict=None): @property def history(self): - """Exposes the history of the inner objective.""" + """Expose the history of the inner objective.""" return self.base_objective.history @property def pre_post_processor(self): - """Exposes the pre_post_processor of inner objective.""" + """Expose the pre_post_processor of inner objective.""" return self.base_objective.pre_post_processor + @pre_post_processor.setter + def pre_post_processor(self, new_pre_post_processor): + """Set the pre_post_processor of inner objective.""" + self.base_objective.pre_post_processor = new_pre_post_processor + @property def x_names(self): - """Exposes the x_names of inner objective.""" + """Expose the x_names of inner objective.""" return self.base_objective.x_names diff --git a/test/base/test_objective.py b/test/base/test_objective.py index 998a1f0af..a9d2e3151 100644 --- a/test/base/test_objective.py +++ b/test/base/test_objective.py @@ -223,7 +223,8 @@ def test_aesara(max_sensi_order, integrated): @pytest.mark.parametrize("enable_x64", [True, False]) -def test_jax(max_sensi_order, integrated, enable_x64): +@pytest.mark.parametrize("fix_parameters", [True, False]) +def test_jax(max_sensi_order, integrated, enable_x64, fix_parameters): """Test function composition and gradient computation via jax""" import jax import jax.numpy as jnp @@ -234,6 +235,7 @@ def test_jax(max_sensi_order, integrated, enable_x64): jax.config.update("jax_enable_x64", enable_x64) from pypesto.objective.jax import JaxObjective + from pypesto.objective.pre_post_process import FixedParametersProcessor prob = rosen_for_sensi(max_sensi_order, integrated, [0, 1]) @@ -250,9 +252,20 @@ def jax_op_out(x: jnp.array) -> jnp.array: # compose rosenbrock function with sinh transformation obj = JaxObjective(prob["obj"]) + if fix_parameters: + obj.pre_post_processor = FixedParametersProcessor( + dim_full=2, + x_free_indices=[0], + x_fixed_indices=[1], + x_fixed_vals=[0.0], + ) + # evaluate for a couple of random points such that we can assess # compatibility with vmap xx = x_ref + np.random.randn(10, x_ref.shape[0]) + if fix_parameters: + xx = xx[:, obj.pre_post_processor.x_free_indices] + rvals_ref = [ jax_op_out( prob["obj"](jax_op_in(xxi), sensi_orders=(max_sensi_order,)) @@ -263,6 +276,9 @@ def jax_op_out(x: jnp.array) -> jnp.array: def _fun(y, pypesto_fun, jax_fun_in, jax_fun_out): return jax_fun_out(pypesto_fun(jax_fun_in(y))) + assert obj.check_sensi_orders((max_sensi_order,), pypesto.C.MODE_FUN) + assert not obj.check_sensi_orders((max_sensi_order,), pypesto.C.MODE_RES) + for _obj in (obj, copy.deepcopy(obj)): fun = partial( _fun, @@ -273,6 +289,7 @@ def _fun(y, pypesto_fun, jax_fun_in, jax_fun_out): if max_sensi_order == 1: fun = jax.grad(fun) + # check compatibility with vmap and jit vmapped_fun = jax.vmap(fun) rvals_jax = vmapped_fun(xx) @@ -281,8 +298,11 @@ def _fun(y, pypesto_fun, jax_fun_in, jax_fun_out): # can't use rtol = 1e-8 for 32bit rtol = 1e-16 if enable_x64 else 1e-4 for x, rref, rj in zip(xx, rvals_ref, rvals_jax): + assert isinstance(rj, jnp.ndarray) if max_sensi_order == 0: - np.testing.assert_allclose(rref, rj, atol=atol, rtol=rtol) + np.testing.assert_allclose( + rref, float(rj), atol=atol, rtol=rtol + ) if max_sensi_order == 1: # g(x) = b(c(x)) => g'(x) = b'(c(x))) * c'(x) # f(x) = a(g(x)) => f'(x) = a'(g(x)) * g'(x) @@ -295,7 +315,9 @@ def _fun(y, pypesto_fun, jax_fun_in, jax_fun_out): ) @ jax.jacfwd(jax_op_in)(x) # f'(x) = a'(g(x)) * g'(x) f_prime = jax.jacfwd(jax_op_out)(g) * g_prime - np.testing.assert_allclose(f_prime, rj, atol=atol, rtol=rtol) + np.testing.assert_allclose( + f_prime, np.asarray(rj), atol=atol, rtol=rtol + ) @pytest.fixture( From e9c9949866ba61a1110d82718b3925913fbc7dfa Mon Sep 17 00:00:00 2001 From: Paul Jonas Jost <70631928+PaulJonasJost@users.noreply.github.com> Date: Fri, 31 May 2024 13:28:57 +0200 Subject: [PATCH 28/28] prepare release v0.5.2 (#1407) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update Changelog and Version --------- Co-authored-by: Fabian Fröhlich Co-authored-by: Maren Philipps <55318391+m-philipps@users.noreply.github.com> --- CHANGELOG.rst | 31 +++++++++++++++++++++++++++++++ pypesto/version.py | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index da8a08f33..d7fc65da3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,37 @@ Release notes .......... +0.5.2 (2024-05-27) +------------------- + +* **New Feature**: Variational inference with PyMC (#1306) +* PEtab + * Import of petab independent of amici (#1355) +* Problem + * Added option to sample startpoints for a problem, from the problem directly. (#1364) + * More detailed defaults for problem.get_full_vector (#1393) + * Save pypesto and python version to the problem. (#1382) +* Objective + * Fix calling priors in sampling with fixed parameters (#1378) + * Fix JaxObjective (#1400) +* Optimize + * ESS optimizers: suppress divide-by-zero warnings; report n_eval (#1380) + * SacessOptimizer: collect worker stats (#1381) + * Add load method to Hdf5AmiciHistory (#1370) +* Hierarchical + * Relative: fix log of zero for default 0 sigma values (#1377) +* Sample + * Fix pypesto.sample.geweke_test.spectrum for nfft<=3 (#1388) +* Visualize + * Handle correlation plot with nans (#1365) +* General + * Remove scipy requirement from pypesto[pymc] (#1376) + * Require and test python >=3.10 according to NEP 29 (#1379) + * Fix various warnings (#1384) + * Small changes to GHA actions and tests (#1386, #1387, #1402, #1385) + * Improve Documentation (#1394, #1391, #1399, #1292, #1390) + + 0.5.0 (2024-04-10) ------------------- diff --git a/pypesto/version.py b/pypesto/version.py index dd9b22ccc..722515271 100644 --- a/pypesto/version.py +++ b/pypesto/version.py @@ -1 +1 @@ -__version__ = "0.5.1" +__version__ = "0.5.2"