Summary
A PEtab v1 observable with observableTransformation = log10 imports as a linear gaussian
noise model, so the fit optimizes the wrong objective (linear residual instead of log10 residual, and
no change-of-variables Jacobian). The log10 transformation is dropped during the v1→v2 conversion —
directly parallel to the parameterScale drop that petab1to2_preserve_scale (#491) was written to fix.
Root cause (verified on main)
petab.v2.petab1to2 drops the v1 observableTransformation column. Converting Perelson_Science1996
(v1 observable: observableTransformation = log10, noiseDistribution = normal) via
pybnf.petab.petab1to2_preserve_scale yields a v2 observables table with a blank noiseDistribution
and no transformation column:
observableId observableFormula noiseDistribution noiseFormula ...
task0_model0_perelson1_V V (blank) noiseParameter1_...
import_job then reads noiseDistribution only (import_.py:_resolve_noise → row.noise_distribution;
_PETAB_DISTRIBUTION_TO_NOISE_MODEL @587 has no scale axis), so a blank/normal distribution imports as
Gaussian(LINEAR) — the emitted conf line is noise_model = gaussian, sigma = fit <p>.
Complication (v2 spec gap). PyBNF's v2 observables adapter (pybnf/petab/observables.py) documents
that PEtab v2 removed observableTransformation and folded it into noiseDistribution as natural-log
prefixes (log-normal = Gaussian(LN)), with no log10 form. But PyBNF's native lognormal conf
token is log10 (objective.py:731 → Gaussian(additive_on=LOG10)). So even if the converter emitted
log-normal, it would give natural-log, not the base-10 the v1 problem (and the paper) use. v1 log10
therefore has no faithful PEtab-v2 noiseDistribution representation.
Impact
Three of the six gradient-fittable Grein et al. 2026 benchmark problems have observableTransformation = log10 and are affected: Perelson_Science1996, Borghans_BiophysChem1997, Elowitz_Nature2000. The other
three (Boehm, Sneyd, Okuonghae) are lin and import correctly.
Worked example (Perelson): the linear import's global minimum is J_paper = 232.3 (OG ≈ 10 — the model,
a monotone decay, cannot beat σ̂ ≈ 4.9e5 on the linear scale). Correcting gaussian → lognormal by
hand (Gaussian(additive_on=LOG10)) gives -log_likelihood = 222.2807694, matching the reference
J* = 222.2807689 to OG = 5e-7. An independent roadrunner reconstruction confirms only the log10
scale plus the data Jacobian Σ log(y_obs·ln10) reproduces J* (linear → 232.3, log10-without-Jacobian
→ −9.6, log10+Jacobian → 222.3). PyBNF's lognormal lnL already includes that Jacobian, so once the
family is right the score is exact.
Proposed fix
Extend the scale-preserving conversion (as petab1to2_preserve_scale did for parameterScale, #491) to
re-inject observableTransformation, and have the importer route it to the native families:
v1 observableTransformation + noiseDistribution |
PyBNF family |
lin + normal |
gaussian = Gaussian(LINEAR) (current) |
log10 + normal |
native lognormal = Gaussian(LOG10) |
log + normal |
Gaussian(LN) (the v2 log-normal mapping) |
(analogously for laplace) |
laplace / log10-laplace / Laplace(LN) |
i.e. select the noise family's additive scale from observableTransformation, not just the family
from noiseDistribution. This also closes the v2-spec gap that log-normal currently means only
natural-log while the native lognormal token means log10.
Context
Part of the subset-I benchmark build (sibling of #492–#496; tracked in wshlavacek/BNGL-Models#3).
Affects Perelson/Borghans/Elowitz; hand-corrected in those job confs pending this fix.
Summary
A PEtab v1 observable with
observableTransformation = log10imports as a lineargaussiannoise model, so the fit optimizes the wrong objective (linear residual instead of
log10residual, andno change-of-variables Jacobian). The
log10transformation is dropped during the v1→v2 conversion —directly parallel to the
parameterScaledrop thatpetab1to2_preserve_scale(#491) was written to fix.Root cause (verified on
main)petab.v2.petab1to2drops the v1observableTransformationcolumn. Converting Perelson_Science1996(v1 observable:
observableTransformation = log10,noiseDistribution = normal) viapybnf.petab.petab1to2_preserve_scaleyields a v2 observables table with a blanknoiseDistributionand no transformation column:
import_jobthen readsnoiseDistributiononly (import_.py:_resolve_noise→row.noise_distribution;_PETAB_DISTRIBUTION_TO_NOISE_MODEL@587 has no scale axis), so a blank/normaldistribution imports asGaussian(LINEAR)— the emitted conf line isnoise_model = gaussian, sigma = fit <p>.Complication (v2 spec gap). PyBNF's v2 observables adapter (
pybnf/petab/observables.py) documentsthat PEtab v2 removed
observableTransformationand folded it intonoiseDistributionas natural-logprefixes (
log-normal=Gaussian(LN)), with nolog10form. But PyBNF's nativelognormalconftoken is log10 (
objective.py:731→Gaussian(additive_on=LOG10)). So even if the converter emittedlog-normal, it would give natural-log, not the base-10 the v1 problem (and the paper) use. v1log10therefore has no faithful PEtab-v2
noiseDistributionrepresentation.Impact
Three of the six gradient-fittable Grein et al. 2026 benchmark problems have
observableTransformation = log10and are affected: Perelson_Science1996, Borghans_BiophysChem1997, Elowitz_Nature2000. The otherthree (Boehm, Sneyd, Okuonghae) are
linand import correctly.Worked example (Perelson): the linear import's global minimum is
J_paper = 232.3(OG ≈ 10 — the model,a monotone decay, cannot beat
σ̂ ≈ 4.9e5on the linear scale). Correctinggaussian → lognormalbyhand (
Gaussian(additive_on=LOG10)) gives-log_likelihood = 222.2807694, matching the referenceJ* = 222.2807689toOG = 5e-7. An independent roadrunner reconstruction confirms only the log10scale plus the data Jacobian
Σ log(y_obs·ln10)reproducesJ*(linear → 232.3, log10-without-Jacobian→ −9.6, log10+Jacobian → 222.3). PyBNF's
lognormallnLalready includes that Jacobian, so once thefamily is right the score is exact.
Proposed fix
Extend the scale-preserving conversion (as
petab1to2_preserve_scaledid forparameterScale, #491) tore-inject
observableTransformation, and have the importer route it to the native families:observableTransformation+noiseDistributionlin+normalgaussian=Gaussian(LINEAR)(current)log10+normallognormal=Gaussian(LOG10)log+normalGaussian(LN)(the v2log-normalmapping)laplace)laplace/ log10-laplace /Laplace(LN)i.e. select the noise family's additive scale from
observableTransformation, not just the familyfrom
noiseDistribution. This also closes the v2-spec gap thatlog-normalcurrently means onlynatural-log while the native
lognormaltoken means log10.Context
Part of the subset-I benchmark build (sibling of #492–#496; tracked in wshlavacek/BNGL-Models#3).
Affects Perelson/Borghans/Elowitz; hand-corrected in those job confs pending this fix.