You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Updated 2026-06-19. This umbrella started life as an importer-only tracker. Two things have since reshaped it: (1) the work pivoted to exporter-first (PyBNF/BNGL → PEtab v2; ADR-0025), because exporting reads already-correct constructs that petablint can grade, whereas importing means generating BNGL with no oracle; and (2) the resulting package is a single two-way interop, not two subsystems — so the importer is the reverse reader of each asset mapper, not a parallel build. Checklist at the bottom is the source of truth.
The package is one import/export interop (the key architectural fact).pybnf/petab/ pairs each PEtab table with a neutral row dataclass (PetabParameterRow, PetabObservableRow, PetabMeasurementRow, …) and reversible asset mappers on both sides of it; export.py calls itself "disposable glue." So the hard, semantic part — prior families, noise distributions, scale conversions — is written once and run both directions. Consequences:
Parameters and observables are already mapped both ways and tested: petab_parameter_row ↔ free_parameter_from_row (ADR-0019), petab_observable_row ↔ noise_model_from_row (ADR-0023).
Measurements and conditions are export-only today, so their reverse readers are the main remaining importer code — but they slot onto the same neutral seam, not a new design.
Scope is BNGL-native PEtab problems: the model passes through _bngl.parse_model unchanged (no BNGL generation — the original "importer is hard" fear is dissolved). An SBML model is a separate optional adapter: export_job raises on SBML, and the inverse cannot synthesize a PyBNF model from SBML.
Export is lossy by design (raises on PyBNF features PEtab v2 can't express), so import↔export is identity only on the PEtab-representable subset.
⚠️Spec correction: this issue was originally drafted against a v1-flavoured picture of PEtab v2. The current v2 spec differs materially (parameterScale removed; prior columns renamed; bounds truncate; richer catalog). The sections below are corrected; the original mapping table is superseded.
Motivation
The M2 modularization gave PyBNF first-class, registry-backed Prior (ADR-0010, pybnf/priors/) and NoiseModel (ADR-0011, pybnf/noise/) abstractions, deliberately PEtab-defaulted but not PEtab-bound (ADR-0004). The payoff that justifies that shape is PEtab v2 interop: a thin adapter where a problem.yaml + its TSV tables + model and a native .conf produce the same internal objects, in either direction.
That makes it the "two-adapter" proof the refactor plan calls out — native .conf and a PEtab problem feeding one set of FreeParameter/Prior/NoiseModel/exp-data objects. If both adapters land on the same objects, the abstractions are right; if PEtab forces a special case, we learn where they're wrong. In practice this became a single reversible package (see Status): the asset mappers are the proof, run forward (export) and backward (import).
This is an umbrella/tracking issue. It scopes the whole PEtab v2 interop (import + export); each chunk splits into its own issue when work begins.
Spec correction — what PEtab v2 actually specifies
Verified against the live v2 data-format spec. Three premises in the original draft are now wrong:
parameterScale was removed entirely. v2 parameters are all in linear space; a scale change is expected to be done in the model file. PyBNF derives a parameter's Scale (Linear/Log10) from its prior family instead — so the original "natural-log scale gap" is moot.
Prior columns renamed to priorDistribution / priorParameters (from objectivePriorType / objectivePriorParameters). There is a single prior, used for the objective only (initializationPrior* was also removed).
Bounds truncate the prior, and the catalog is richer than the draft assumed: uniform, normal, laplace, log-normal, log-laplace, log-uniform, cauchy, gamma, exponential, chisquare, rayleigh. log-normal / log-laplace use the natural log.
What a PEtab v2 problem is
problem.yaml — references the model file(s) + the TSV tables
model — BNGL (first-class via ADR-0026; the supported interop target) or SBML (a separate adapter; PyBNF already imports SBML/Antimony: SbmlModel, BngsimAntimony)
pybnf/petab/parameters.py reads parameters.tsv and maps each estimated row to a FreeParameter carrying a Prior, driven by the prior registry (synthesizes the *_var keyword, validates against PRIOR_KEYWORD_MAP, builds through the FreeParameter constructor → bit-identical to the native .conf path — not a parallel mapping table). Dependency-free (stdlib csv; runs in the bngsim-less CI tier) behind a neutral PetabParameterRow seam shared with the export direction. PEtab/PyBNF boundaries are explicit NotImplementedErrors (the 5 unsupported families; unbounded-family truncation; estimate=false). Commit f151914, ADR-0019; 30 tests.
Chunks (rough order, each its own issue when reached)
Importer = the reverse readers on the shared asset seam (not a separate subsystem):
measurements — write the reverse reader (long rows → wide Data + .exp writeback; reconstruct the _SD column from noiseParameters); measurement_rows_from_data is export-only today
conditions/experiments — write the reverse reader (PEtab Condition/Experiment → new-era condition:/experiment:, undoing the __REF surrogate rename); build_experiment_conditions is export-only today
import_job orchestrator + problem.yaml reader — assemble a new-era .conf (the BNGL model passes through _bngl.parse_model unchanged)
observableFormula / noiseFormula — only arbitrary expressions need the sympy layer (where petab is adopted as an optional extra); the bare-name common case is handled (ADR-0025)
SBML model import — a separate optional adapter (the exporter raises on SBML; not obtainable by inversion)
New runtime deps (petab, python-libsbml, sympy) must be hand-mirrored into .github/actions/setup-pybnf or the tests/integration CI tiers go red (the recurring single-sync-point gotcha). Decision (ADR-0019): petab is adopted as an optional extra (pybnf[petab]) at the formula/SBML chunk, not in core. (As of the exporter-first track, petab is wired in as a test-only oracle in pybnf[tests]; core remains dependency-free — ADR-0025/0026.)
Keep the interop simulator-free where possible so it runs in the bngsim-less CI tier.
Out-of-scope framing comes from dev/refactor-plan.md. Relevant ADRs: 0003 (no Jacobian), 0004 (PEtab-defaulted not -bound), 0010 (Prior), 0011 (NoiseModel), 0019 (parameters), 0020 (truncation), 0021 (per-observable noise), 0023 (observables noise), 0025 (exporter-first), 0026 (BNGL PEtab model), 0027 (conditions/experiments), 0028 (config redesign).
Status
The package is one import/export interop (the key architectural fact).
pybnf/petab/pairs each PEtab table with a neutral row dataclass (PetabParameterRow,PetabObservableRow,PetabMeasurementRow, …) and reversible asset mappers on both sides of it;export.pycalls itself "disposable glue." So the hard, semantic part — prior families, noise distributions, scale conversions — is written once and run both directions. Consequences:petab_parameter_row↔free_parameter_from_row(ADR-0019),petab_observable_row↔noise_model_from_row(ADR-0023)._bngl.parse_modelunchanged (no BNGL generation — the original "importer is hard" fear is dissolved). An SBML model is a separate optional adapter:export_jobraises on SBML, and the inverse cannot synthesize a PyBNF model from SBML.Importer (read) status:
parameters.tsv→FreeParameter/Prior: ✅ DONE (commitf151914, ADR-0019). Dependency-free, registry-driven.observables.tsvnoise →(NoiseModel, SigmaSource): ✅ DONE (ADR-0023), on the decoupled(family × σ-source)engine Per-observable noise models: lift the single globalobjfuncto per-observable selection (PEtab v2 prerequisite) #410 built (ADR-0021).objfuncto per-observable selection (PEtab v2 prerequisite) #410, closed) and two-sided truncation of unbounded-support priors (Truncated/bounded priors: allow reflecting bounds on unbounded-support families (Normal/Laplace/log-*) (PEtab v2 prerequisite) #411, closed, ADR-0020). Only one-sided truncation remains (Native .conf grammar for bounded/truncated normal_var, laplace_var (and log forms) #417).import_joborchestrator +problem.yamlreader, and end-to-end import-and-fit. The importer can emit a new-era.conf, but fitting an imported problem is gated on the ADR-0028 config loader in the fitter (PEtab-aligned config redesign: explicit experiments/conditions/data (retire filename→suffix linkage) #423).Exporter-first track (ADR-0025; landed since this issue was filed):
PEtab v2 exporter (
export_job) — demo, conditions/experiments (PEtab v2 exporter: conditions/experiments chunk (mutants→conditions, suffix→experiments, dose-response) #422, closed, ADR-0027), full prior catalog, objective-family export, and the ADR-0028 new-era surface reader.BNGL as a first-class PEtab model language — a
petabModeladapter for BNGL with the fullpetablintoracle (Native BNGL PEtab linter: a petabModeladapter for BNGL (full petablint oracle), toward upstream libpetab-python (PEtab#436) #420, closed, ADR-0026); an upstream libpetab-python PR is drafted.ADR-0028 config redesign (PEtab-aligned config redesign: explicit experiments/conditions/data (retire filename→suffix linkage) #423, open) — explicit
experiment:/condition:/data:. The exporter reads it; the fitter's config-loader implementation is the keystone that unblocks the importer round-trip.parameterScaleremoved; prior columns renamed; bounds truncate; richer catalog). The sections below are corrected; the original mapping table is superseded.Motivation
The M2 modularization gave PyBNF first-class, registry-backed Prior (ADR-0010,
pybnf/priors/) and NoiseModel (ADR-0011,pybnf/noise/) abstractions, deliberately PEtab-defaulted but not PEtab-bound (ADR-0004). The payoff that justifies that shape is PEtab v2 interop: a thin adapter where aproblem.yaml+ its TSV tables + model and a native.confproduce the same internal objects, in either direction.That makes it the "two-adapter" proof the refactor plan calls out — native
.confand a PEtab problem feeding one set ofFreeParameter/Prior/NoiseModel/exp-data objects. If both adapters land on the same objects, the abstractions are right; if PEtab forces a special case, we learn where they're wrong. In practice this became a single reversible package (see Status): the asset mappers are the proof, run forward (export) and backward (import).This is an umbrella/tracking issue. It scopes the whole PEtab v2 interop (import + export); each chunk splits into its own issue when work begins.
Spec correction — what PEtab v2 actually specifies
Verified against the live v2 data-format spec. Three premises in the original draft are now wrong:
parameterScalewas removed entirely. v2 parameters are all in linear space; a scale change is expected to be done in the model file. PyBNF derives a parameter's Scale (Linear/Log10) from its prior family instead — so the original "natural-log scale gap" is moot.priorDistribution/priorParameters(fromobjectivePriorType/objectivePriorParameters). There is a single prior, used for the objective only (initializationPrior*was also removed).uniform, normal, laplace, log-normal, log-laplace, log-uniform, cauchy, gamma, exponential, chisquare, rayleigh.log-normal/log-laplaceuse the natural log.What a PEtab v2 problem is
problem.yaml— references the model file(s) + the TSV tablesSbmlModel,BngsimAntimony)parameters.tsv—parameterId, lowerBound, upperBound, nominalValue, estimate (true|false), priorDistribution, priorParameters(noparameterScale)observables.tsv—observableId, observableFormula, noiseFormula, observableTransformation, noiseDistributionmeasurements.tsv—observableId, simulationConditionId, measurement, time, …conditions.tsv— per-condition parameter/species overridesMapping to PyBNF's existing abstractions (corrected for v2)
priorDistributionuniform/normal/laplace(linear)log-uniformloguniform_var(Uniform × Log10); params are linear boundslog-normal/log-laplace(natural log)lognormal_var/loglaplace_var; convertμ/ln10, σ/ln10[lowerBound, upperBound]estimate = false(fixed)FreeParameterlowerBound/upperBoundtruncate the priorcauchy,gamma,exponential,chisquare,rayleighnoiseDistributionnormal / laplaceobservableTransformationlin / log / log10observableFormula/noiseFormula(sympy over model entities)BnglModel/_bngl.parse_modelSbmlModel/ AntimonyStep 1 —
parameterstable →Prior/FreeParameter(DONE)pybnf/petab/parameters.pyreadsparameters.tsvand maps each estimated row to aFreeParametercarrying aPrior, driven by the prior registry (synthesizes the*_varkeyword, validates againstPRIOR_KEYWORD_MAP, builds through theFreeParameterconstructor → bit-identical to the native.confpath — not a parallel mapping table). Dependency-free (stdlibcsv; runs in the bngsim-less CI tier) behind a neutralPetabParameterRowseam shared with the export direction. PEtab/PyBNF boundaries are explicitNotImplementedErrors (the 5 unsupported families; unbounded-family truncation;estimate=false). Commitf151914, ADR-0019; 30 tests.Chunks (rough order, each its own issue when reached)
Importer = the reverse readers on the shared asset seam (not a separate subsystem):
petab_parameter_row↔free_parameter_from_row(both ways, tested;f151914, ADR-0019)petab_observable_row↔noise_model_from_row(both ways, tested; ADR-0023, on the Per-observable noise models: lift the single globalobjfuncto per-observable selection (PEtab v2 prerequisite) #410/ADR-0021 engine)Data+.expwriteback; reconstruct the_SDcolumn fromnoiseParameters);measurement_rows_from_datais export-only todaycondition:/experiment:, undoing the__REFsurrogate rename);build_experiment_conditionsis export-only todayimport_joborchestrator +problem.yamlreader — assemble a new-era.conf(the BNGL model passes through_bngl.parse_modelunchanged)observableFormula/noiseFormula— only arbitrary expressions need the sympy layer (wherepetabis adopted as an optional extra); the bare-name common case is handled (ADR-0025)cauchy/gamma/exponential/chisquare/rayleigh); one-sided truncation (Native .conf grammar for bounded/truncated normal_var, laplace_var (and log forms) #417)truncation-of-unbounded feature→ done two-sided (Truncated/bounded priors: allow reflecting bounds on unbounded-support families (Normal/Laplace/log-*) (PEtab v2 prerequisite) #411, ADR-0020);Laplace noise family→ done (Per-observable noise models: lift the single globalobjfuncto per-observable selection (PEtab v2 prerequisite) #410, ADR-0021)Exporter-first track (ADR-0025; not in the original scope, landed since):
petabModeladapter + fullpetablintoracle (Native BNGL PEtab linter: a petabModeladapter for BNGL (full petablint oracle), toward upstream libpetab-python (PEtab#436) #420, ADR-0026)Notes / constraints
petab,python-libsbml,sympy) must be hand-mirrored into.github/actions/setup-pybnfor thetests/integrationCI tiers go red (the recurring single-sync-point gotcha). Decision (ADR-0019):petabis adopted as an optional extra (pybnf[petab]) at the formula/SBML chunk, not in core. (As of the exporter-first track,petabis wired in as a test-only oracle inpybnf[tests]; core remains dependency-free — ADR-0025/0026.)dev/refactor-plan.md. Relevant ADRs: 0003 (no Jacobian), 0004 (PEtab-defaulted not -bound), 0010 (Prior), 0011 (NoiseModel), 0019 (parameters), 0020 (truncation), 0021 (per-observable noise), 0023 (observables noise), 0025 (exporter-first), 0026 (BNGL PEtab model), 0027 (conditions/experiments), 0028 (config redesign).