Skip to content

Per-observable noise models: lift the single global objfunc to per-observable selection (PEtab v2 prerequisite) #410

@wshlavacek

Description

@wshlavacek

Motivation

PyBNF today applies one objective function to an entire fit: objfunc is a single config key (default chi_sq), resolved to a single ObjectiveFunction whose NoiseModel (ADR-0011) is a class attribute applied uniformly to every observable / data column.

PEtab v2 specifies noise per observable: noiseDistribution (normal / laplace) and observableTransformation (lin / log / log10) are columns of observables.tsv, so different observables in one problem can carry different noise models. Faithful PEtab v2 import — the observables chunk of the importer (#407) — therefore requires per-observable noise selection. This is a hard prerequisite, not a nicety: most PEtab problems have several observables, and heterogeneous noise across them is routine.

It also clears the bar on its own merits, independent of PEtab: a single fit against heterogeneous data — e.g. molecule counts (NegBinomial) alongside concentrations (Gaussian), or some observables on a log scale and others linear — is impossible today and is a natural modeling need.

Current state

  • config['objfunc'] → one code → OBJFUNC_REGISTRY → one ObjectiveFunction built via from_config, held as Configuration.obj.
  • The NoiseModel is a class attribute (noise = Gaussian(...), noise = NegBinomial()) on that single objective; SummationObjective.eval_point applies it to every compared column uniformly.
  • The σ-source is likewise global per objfunc: the per-point _SD data column (chi_sq/lognormal), a magic-named free parameter sigma__FREE / r__FREE (the _dynamic objfuncs, via the global free_noise_params map), or the neg_bin_r config constant.

What's needed

  • Per-observable selection of the noise model — each observable (matched to its exp-data column by name; see the Observable glossary entry) gets its own distribution family × additive-noise-scale × location, plus its own σ-source.
  • The ADR-0011 NoiseModel kernel is already a pure per-point object and is well-suited as-is — the harness (SummationObjective) just needs to choose the kernel per column instead of reading one class-level self.noise. So this is largely a selection/dispatch extension, not a rewrite of the noise math.
  • A config surface to express per-observable noise, with the existing single global objfunc preserved as the default/shorthand (backward compatible).
  • A per-observable σ-source, retiring the hardcoded sigma__FREE / r__FREE magic names in favour of per-observable noise parameters (PEtab gives each its own id).

Design questions (for an ADR before building)

  • Config surface. How does a .conf declare per-observable noise — an objfunc keyable by suffix/observable, a per-suffix override block, a small table? The global form must keep working unchanged.
  • Granularity. Per observable name, per column, per suffix, or per (suffix, column)? (PEtab conditions add an orthogonal per-condition axis, handled separately in PEtab v2 problem importer — the 'two-adapter' proof (first step: parameters table → FreeParameter/Prior) #407's measurements/conditions chunk.)
  • σ-source generalization. How do per-observable noise parameters reach the kernel once free_noise_params is no longer global? This is where the sigma__FREE / r__FREE magic-string coupling that ADR-0011 already flagged would finally be dissolved.
  • Laplace noise family. PyBNF has Laplace only as a prior, not a noise family — close that gap here (a one-file kernel like the prior Laplace seam, plus σ-source/normalizer wiring) or in a separate follow-up?
  • Blast radius. Does evaluate_multiple / the σ-injection need to change, or only objfunc construction + eval_point dispatch?

Scope & relationships

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions