Skip to content

C — Gradient plumbing: Gaussian gradient + residual-Jacobian assembly, convention pin, native→sampling transform, FD-check #449

Description

@wshlavacek

Part of #385 (cut 1, step 3 of A→B→C). Depends on #447 and #448. Closes the common case and unblocks #386.

What

Assemble, from #447's per-Result sensitivity tensor and #448's per-experiment routing, both forms of
the gradient for the default Gaussian, LINEAR scale, fixed-σ objective, summed across experiments:

  1. scalar ∂F/∂θ (for quasi-Newton / L-BFGS-B), and
  2. residual vector + residual-Jacobian (for trust-region least-squares, Gradient-based local optimizers (D2D-style: TRF/Levenberg-Marquardt + L-BFGS-B) in the async Algorithm loop #386's primary path).

Convention pin

PyBNF Gaussian loss (noise/gaussian.py:52-54): data_fit = r²/(2σ²),
r = μ(pred) − additive_on.forward(obs); for default (LINEAR, MEDIAN) Gaussian, μ = pred exactly
(gaussian.py:15-17, symmetric ⇒ smooth, no trajectory centering). Therefore:

scipy.least_squares minimizes ½‖ρ‖² with the same ρ/J, so the residual form and the scalar
form agree by construction — the optimizer walks the surface PyBNF reports. Assembly threads through
the existing per-point objective evaluation (LikelihoodObjective.eval_point, objective.py:723) so
the gradient is the exact derivative of the loss PyBNF reports — same σ-weighting, same column selection.

Native → sampling space (once)

Apply the θ↔u Jacobian exactly once (ADR-0029): priors/scale.py already exposes a JAX-differentiable
inverse_jax per scale (scale.py:38/61/75/91), so ∂θ/∂u is one autodiff away — no hand-written
per-scale derivative, no double transform. ρ is scale-invariant; J → J·diag(∂θ/∂u).

Reaching the optimizer

The gradient must arrive through the async propose/score loop (Algorithm, algorithms/base.py:69;
make_job/got_result), not a blocking call — consumed by #386, but the assembly API here must fit
that loop (return the residual/Jacobian alongside the scored result).

Deliverables

  • Objective (and Constraint stub) methods producing scalar ∂F/∂θ and residual +
    residual-Jacobian, summed across experiments, for Gaussian/fixed-σ/LINEAR.
  • Native→sampling transform applied once via priors/scale.py (ADR-0029).
  • FD test (acceptance gate): central differences of PyBNF's own loss(θ) vs assembled
    grad_sampling on a 2–3 parameter net model — wildtype + one conditioned experiment
    to a tight tolerance.
  • User-guide section: enabling sensitivities, the ~(1+P) cost factor, the capability gate.

Out of scope (deferred layers, each its own issue under #385)

Estimated σ (layer D); LOG10/lognormal scale objective (layer E); normalization & cumulative→incident
(layer F); asymmetric families (layer G); SBML/Antimony (layer H); constraint/comparison gradients
(layer I); pre-equilibration/steady-state (layer J); the optimizer itself (#386).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    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