Skip to content

gntr assembles forward sensitivities twice per evaluation — DRY the gradient + Fisher-Hessian assemblers into one pass #488

Description

@wshlavacek

Follow-up to #481 / #487 (surfaced by a /code-review high pass).

Problem

On the gntr (Fisher/Gauss-Newton trust-region) path, GradientOptimizer.gradient_at calls both:

  • assemble_gaussian_gradient — the scalar gradient (_accumulate_experiment walks the scored points), and
  • assemble_fisher_hessian — the EFIM Hessian (_accumulate_experiment_fisher walks the same points again).

_accumulate_experiment_fisher duplicates the entire point-iteration scaffold of _accumulate_experiment:

  • independent-variable resolution (min(exp_data.cols, ...)),
  • the comparable / compare_cols intersection,
  • the _sim_row_for row-matching loop + NaN skip,
  • the _raw_sensitivity_accessor, and
  • the per-point prediction_sensitivity call for dpred_dtheta.

So every scored point's sensitivity row is assembled twice, and _require_gradient_supported is re-run per point on both passes.

Suggested fix

Factor a shared point-iterator (e.g. _iter_scored_points(objective, sim_data, exp_data, routing, ...)) that yields (sim_row, rownum, col_name, weight, dpred_dtheta) once, and have both the gradient and Hessian assemblers consume it — so gntr builds g and H in a single pass and the scaffold lives in one place.

Priority

Low. Correctness-neutral (a pure cleanup/efficiency refactor) and dominated by the bngsim ODE solve, not the PyBNF-side assembly. trf / lbfgs are unaffected (they never assemble H). Filing so it isn't lost.

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