From 1fb6b3993a392ecefdf52db0dd714cbd9e0a820c Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 28 Apr 2025 15:18:05 +0200 Subject: [PATCH 1/3] Handle `observableTransformation` in `petab.v1.simulate.sample_noise` Previously, `petab.v1.simulate.sample_noise` silently ignored `observableTransformation`. For example, in case of observableTransformation=log and noiseDistribution=normal, it incorrectly sampled from a normal instead of a log-normal distribution. Fixes https://github.com/PEtab-dev/libpetab-python/issues/382. --- petab/v1/simulate.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/petab/v1/simulate.py b/petab/v1/simulate.py index 334929ad..d46919e3 100644 --- a/petab/v1/simulate.py +++ b/petab/v1/simulate.py @@ -241,20 +241,38 @@ def sample_noise( simulated_value, ) - # default noise distribution is petab.C.NORMAL - noise_distribution = petab_problem.observable_df.loc[ + observable_row = petab_problem.observable_df.loc[ measurement_row[petab.C.OBSERVABLE_ID] - ].get(petab.C.NOISE_DISTRIBUTION, petab.C.NORMAL) + ] + # default noise distribution is petab.C.NORMAL + noise_distribution = observable_row.get( + petab.C.NOISE_DISTRIBUTION, petab.C.NORMAL + ) # an empty noise distribution column in an observables table can result in # `noise_distribution == float('nan')` if pd.isna(noise_distribution): noise_distribution = petab.C.NORMAL + observable_transformation = observable_row.get( + petab.C.OBSERVABLE_TRANSFORMATION, petab.C.LIN + ) + # observableTransformation=log -> the log of the simulated value is + # distributed according to `noise_distribution` + if observable_transformation == petab.C.LOG: + simulated_value = np.log(simulated_value) + elif observable_transformation == petab.C.LOG10: + simulated_value = np.log10(simulated_value) + # below is e.g.: `np.random.normal(loc=simulation, scale=noise_value)` simulated_value_with_noise = getattr(rng, noise_distribution)( loc=simulated_value, scale=noise_value * noise_scaling_factor ) + if observable_transformation == petab.C.LOG: + simulated_value_with_noise = np.exp(simulated_value_with_noise) + elif observable_transformation == petab.C.LOG10: + simulated_value_with_noise = np.power(10, simulated_value_with_noise) + if zero_bounded and np.sign(simulated_value) != np.sign( simulated_value_with_noise ): From 75f5f7ff4eccfc7540aa1b639e938fd9aaf0a164 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 28 Apr 2025 15:42:27 +0200 Subject: [PATCH 2/3] Update petab/v1/simulate.py Co-authored-by: Dilan Pathirana <59329744+dilpath@users.noreply.github.com> --- petab/v1/simulate.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/petab/v1/simulate.py b/petab/v1/simulate.py index d46919e3..12ebd123 100644 --- a/petab/v1/simulate.py +++ b/petab/v1/simulate.py @@ -256,22 +256,23 @@ def sample_noise( observable_transformation = observable_row.get( petab.C.OBSERVABLE_TRANSFORMATION, petab.C.LIN ) + transform = lambda x: x # observableTransformation=log -> the log of the simulated value is # distributed according to `noise_distribution` if observable_transformation == petab.C.LOG: simulated_value = np.log(simulated_value) + transform = np.exp elif observable_transformation == petab.C.LOG10: simulated_value = np.log10(simulated_value) + transform = lambda x: np.power(10, x) # below is e.g.: `np.random.normal(loc=simulation, scale=noise_value)` simulated_value_with_noise = getattr(rng, noise_distribution)( loc=simulated_value, scale=noise_value * noise_scaling_factor ) - if observable_transformation == petab.C.LOG: - simulated_value_with_noise = np.exp(simulated_value_with_noise) - elif observable_transformation == petab.C.LOG10: - simulated_value_with_noise = np.power(10, simulated_value_with_noise) + # apply observable transformation, ensure `float` type + simulated_value_with_noise = float(transform(simulated_value_with_noise)) if zero_bounded and np.sign(simulated_value) != np.sign( simulated_value_with_noise From 3cc79bd1003c280ac40570f43ad54f3f7d14b7a2 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 28 Apr 2025 20:01:02 +0200 Subject: [PATCH 3/3] noqa --- petab/v1/simulate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/petab/v1/simulate.py b/petab/v1/simulate.py index 12ebd123..46001a72 100644 --- a/petab/v1/simulate.py +++ b/petab/v1/simulate.py @@ -256,7 +256,7 @@ def sample_noise( observable_transformation = observable_row.get( petab.C.OBSERVABLE_TRANSFORMATION, petab.C.LIN ) - transform = lambda x: x + transform = lambda x: x # noqa: E731 # observableTransformation=log -> the log of the simulated value is # distributed according to `noise_distribution` if observable_transformation == petab.C.LOG: @@ -264,7 +264,7 @@ def sample_noise( transform = np.exp elif observable_transformation == petab.C.LOG10: simulated_value = np.log10(simulated_value) - transform = lambda x: np.power(10, x) + transform = lambda x: np.power(10, x) # noqa: E731 # below is e.g.: `np.random.normal(loc=simulation, scale=noise_value)` simulated_value_with_noise = getattr(rng, noise_distribution)(