fix(petab): reset a re-injected log observable's noiseDistribution to its v1 base family (#679) - #680
Merged
Conversation
… its v1 base family (#679) Every v1 observable with observableTransformation = log10 converted into a v2 problem the importer then refused, under petab >= 0.9.0: PybnfError: Observable 'obs_V': observableTransformation 'log10' contradicts the scale of noiseDistribution 'log-normal'. Give the residual scale in one place -- a log observableTransformation over a linear noiseDistribution. The scale-preserving converter was leaning on a petab bug. petab1to2 is designed to fold the v1 transformation into the v2 noiseDistribution; a missing return in that merge left the column blank in every petab < 0.9.0, the importer defaulted the blank to normal, and log10 over normal imported as lognormal as ADR-0073 describes. petab 0.9.0 (2026-09-07, PEtab-dev/libpetab-python#502) fixed the merge. PEtab v2 has no log10-normal, so the converter now substitutes the natural-log family (log10 + normal -> log-normal, with a warning), a silent ln 10 rescaling of sigma. Stacked under our re-injected log10 column, that is exactly the contradiction the importer exists to refuse. petab1to2_preserve_scale now reads each log observable's v1 noiseDistribution (blank -> normal) alongside its transformation, and inject_observable_transformations takes an optional {observableId: base} map and resets the row's noiseDistribution to that linear base while writing the transformation. The scale is then stated once, in the preserved column, whichever petab produced the table: a blank cell (0.8.2) and a folded log-normal / log-laplace (0.9.0) both come back to the v1 family. Rows without a transformation keep whatever petab1to2 wrote, so a linear problem is still byte-identical. petab's substitution warning lands inside the catch_warnings block that already silences the parameterScale warning, for the same reason. Tests: an end-to-end pin that a converted log10 observable carries noiseDistribution = normal, and three unit tests on the injector (log-normal reset to normal with the linear row untouched, log-laplace reset to laplace, and the column left alone when no base map is passed). Three of the four fail on main. test_petab_convert.py and test_petab_import.py pass under both petab 0.8.2 and 0.9.0; the full petab test set passes under 0.8.2. Not an upstream bug: PEtab v2 removed log10-normal deliberately (PEtab-dev/PEtab#644, libpetab-python#455) and the converter warns about the substitution. ADR-0073 gains an addendum recording this.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #679.
Every v1 observable with
observableTransformation = log10converted into a v2 problem the importer then refused, under petab >= 0.9.0:This is the failure behind the three red pytest legs on #678 and on every PR since 2026-09-07. The py3.11 leg stays green only because petab 0.9.0 requires Python >= 3.12.
What was wrong
The scale-preserving converter was leaning on a petab bug.
petab1to2is designed to fold the v1 transformation into the v2noiseDistribution; a missingreturnin that merge left the column blank in every petab < 0.9.0, the importer defaulted the blank tonormal, andlog10overnormalimported aslognormalas ADR-0073 describes. petab 0.9.0 (PEtab-dev/libpetab-python#502) fixed the merge. PEtab v2 has nolog10-normal, so the converter now substitutes the natural-log family (log10+normal->log-normal, with a warning), a silentln 10rescaling of sigma. Stacked under our re-injectedlog10column, that is exactly the contradiction the importer exists to refuse.What changed
petab1to2_preserve_scalenow reads each log observable's v1noiseDistribution(blank ->normal) alongside its transformation, andinject_observable_transformationstakes an optional{observableId: 'normal' | 'laplace'}map and resets the row'snoiseDistributionto that linear base while writing the transformation. The scale is then stated once, in the preserved column, whichever petab produced the table: a blank cell (0.8.2) and a foldedlog-normal/log-laplace(0.9.0) both come back to the v1 family. Rows without a transformation keep whatever petab1to2 wrote, so a linear problem is still byte-identical. petab's substitution warning lands inside thecatch_warningsblock that already silences theparameterScalewarning, for the same reason.ADR-0073 gains an addendum recording this.
Tests
noiseDistribution = normal.log-normalreset tonormalwith the linear row untouched;log-laplacereset tolaplace; the column left alone when no base map is passed.Three of the four fail against main's
convert.py.tests/test_petab_convert.pyandtests/test_petab_import.py(175 tests) pass under both petab 0.8.2 and 0.9.0; alltests/test_petab*.py(629 tests) pass under 0.8.2. ruff and the-Wdocs build are clean.Not an upstream bug
PEtab v2 removed
log10-normaldeliberately (PEtab-dev/PEtab#644, libpetab-python#455, editors' decision on PEtab-dev/PEtab#623), and the converter warns about the substitution. The preserved column is the workaround the specification leaves us.