Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

PEtab export ignores the global noise_location = mean key, so a mean-centered lnnormal fit is exported as PEtab's median-centered log-normal with no refusal

Chiusa Adatta ai principianti
#898 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
2/5
Tempo stimato
1-3 ore
Idoneità per principianti
85/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
python
Ambito
backend

Direzione di ricerca

Inizia in pybnf/petab/export.py, in _resolve_noise, e confronta la sua gestione con il rifiuto esistente in _reduce_noise_spec. Leggi i casi correlati in tests/test_petab_export.py e aggiungi la copertura per un noise_location = mean globale con objective o whole-fit lnnormal. Il lavoro è completo quando l’export rifiuta questa configurazione in modo coerente, mentre gli export esistenti centrati sulla mediana rimangono invariati.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

bug silent-incorrectness

_resolve_noise (pybnf/petab/export.py:877-893) takes the whole-fit location only from the parsed noise spec. For objective = <token> that spec comes from _OBJECTIVE_DESUGAR (pybnf/objective.py:2808-2820), which always returns None for the location (lnnormal is at line 2813). A whole-fit noise_model = ... line without a location field also carries None. The exporter's only mean-centering refusal is in _reduce_noise_spec (export.py:1022), and it fires only when location == 'mean' appears in the spec itself. Nothing under pybnf/petab/ reads conf['noise_location']. The fitter does read it: Configuration._load_obj_func (pybnf/config.py:2986-2996) calls obj.set_default_location(location) (pybnf/objective.py:1198-1206). On a log scale this subtracts the moment correction, so PyBNF fits a mean-centered likelihood while the exported problem declares PEtab's median-centered log-normal.

The two spellings of the same thing are handled differently. A location = mean field on a whole-fit noise_model line is refused for every family: tests/test_petab_export.py:2274 checks this on a gaussian line, and an lnnormal line is refused the same way. The documented whole-fit equivalent, noise_location = mean (docs/config_keys.rst:435), is dropped without a warning.

Failure scenario

An edition-2 job with objective = lnnormal and noise_location = mean, exported with pybnf.petab.export.export_job. The export completes, and every file it writes is byte-identical to the export of the same job without noise_location. For the same simulation, PyBNF scores the two jobs differently (0.5752 vs 0.1348 below), but a PEtab tool reading either export scores the median version. At fixed sigma, a mean-centered lnnormal shifts the fitted prediction by a factor of exp(sigma^2/2). A PEtab tool fitting the export therefore estimates different parameters from the ones PyBNF estimates, and nothing reports the change of likelihood.

Reproduction

Copy examples/demo/parabola_v2.bngl into an empty directory and add these files.

positive.exp:

# time y y_SD
11 5.0 0.5
12 8.0 0.5
13 12.0 0.5
14 17.0 0.5
15 22.0 0.5

median.conf:

edition = 2
job_type = de
population_size = 8
max_iterations = 5
model: parabola_v2.bngl
experiment: positive, data: positive.exp
objective = lnnormal
uniform_var = v1 0.1 10
uniform_var = v2 0.1 10
uniform_var = v3 0.1 10

mean.conf is median.conf plus one line:

noise_location = mean

repro.py:

import filecmp, os
import numpy as np
from pybnf.parse import load_config
from pybnf.data import Data
from pybnf.petab.export import export_job

# A fixed simulation of y at the data times (x = t - 10, y = 0.5 x^2 + x + 3).
t = np.arange(11.0, 16.0); x = t - 10; f = 0.5 * x**2 + x + 3
sim = Data(); sim.cols = {'time': 0, 'x': 1, 'y': 2}; sim.headers = {0: 'time', 1: 'x', 2: 'y'}
sim.data = np.column_stack([t, x, f])
exp = Data(file_name='positive.exp')
yobs, s = exp.data[:, 1], 0.5

for tag, shift in (('median', 0.0), ('mean', s**2 / 2)):
    conf = load_config(f'{tag}.conf')
    ref = np.sum(0.5 * ((np.log(yobs) - (np.log(f) - shift)) / s) ** 2)
    print(f'[{tag}] PyBNF objective = {conf.obj.evaluate(sim, exp):.6f}   by hand = {ref:.6f}')
    export_job(f'{tag}.conf', f'out_{tag}')

names = sorted(os.listdir('out_median'))
print('exports byte-identical:',
      all(filecmp.cmp(f'out_median/{n}', f'out_mean/{n}', shallow=False) for n in names))
print(open('out_mean/observables.tsv').read())

Run it with BNGPATH pointing at a BioNetGen install (load_config requires one): python repro.py. Output:

[median] PyBNF objective = 0.134830   by hand = 0.134830
[mean] PyBNF objective = 0.575182   by hand = 0.575182
exports byte-identical: True
observableId	observableFormula	noiseFormula	noiseDistribution	noisePlaceholders
func_y	y	noiseParameter1_func_y	log-normal	noiseParameter1_func_y

The fitter builds the mean-centered objective, and it matches the hand formula with mu = ln f - sigma^2/2. The export of the mean job is identical to the export of the median job. The correct behaviour is the refusal the spec-level form already gets: "the whole-fit noise model is mean-centered (location = mean); PEtab v2 takes the prediction as the distribution median ...".

ADR-0031 calls mean "the explicit, native-only opt-in" and says the exporter always emits median. That sentence says what PEtab can represent. It is not a reason to write a mean-centered fit as a median one. The exporter's own refusal of the spec-level form cites ADR-0031, and a median-centered export of a mean-centered fit has a different optimum.

I also checked a whole-fit noise_model = lnnormal, sigma = fix_at 0.5 line with noise_location = mean. The fitter's noise model is mean-centered, and the export again writes func_y y 0.5 log-normal without a refusal. The same line with , location = mean added is refused.

Reachability

Any edition-2 job that sets noise_location = mean and uses a log-scale family the exporter accepts, i.e. lnnormal, by objective = lnnormal or by a whole-fit noise_model = lnnormal, ... line. The linear gaussian and laplace families give the same numbers, because mean equals median there. lognormal (log10) and neg_bin are refused for other reasons before the location matters. Per-observable noise_model <obs> = ... overrides are not affected: set_default_location changes only the class-default noise model, and the exporter already refuses a location = mean field on an override. The earlier export silent-drop fixes (#719, #733, #736, #738) covered the structural config entries. noise_location is a plain global key (accepted at pybnf/parse.py:192), so it was not covered by that work.

The fix is to have _resolve_noise raise the same NotImplementedError when conf.get('noise_location') == 'mean'.

Where

  • pybnf/petab/export.py:877-893 (_resolve_noise: the location comes only from the spec)
  • pybnf/petab/export.py:1022 (the only mean-centering refusal, spec-level)
  • pybnf/objective.py:2808-2820 (_OBJECTIVE_DESUGAR: location always None)
  • pybnf/config.py:2986-2996 (the fitter applies noise_location)
  • pybnf/objective.py:1198-1206 (set_default_location)
  • docs/config_keys.rst:435 (noise_location documented)
  • docs/adr/0031-median-is-the-universal-centering-default-objective-surface-is-edition-gated.md (mean is the native-only opt-in)

Related: #899, #901, #896, #894, #738.

Found in a whole-codebase audit for silently wrong results (2026-09-23); the reproduction above was re-run independently of the original finding.

Lingua principale
Python
Stelle
25
Fork
25
Merge medio
5h 40m
PR unite (30g)
66

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di lanl/PyBNF

Tutte le issue di lanl/PyBNF

Issue simili

Altre issue su Python

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.