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.
Follow-up to #481 / #487 (surfaced by a
/code-review highpass).Problem
On the
gntr(Fisher/Gauss-Newton trust-region) path,GradientOptimizer.gradient_atcalls both:assemble_gaussian_gradient— the scalar gradient (_accumulate_experimentwalks the scored points), andassemble_fisher_hessian— the EFIM Hessian (_accumulate_experiment_fisherwalks the same points again)._accumulate_experiment_fisherduplicates the entire point-iteration scaffold of_accumulate_experiment:min(exp_data.cols, ...)),comparable/compare_colsintersection,_sim_row_forrow-matching loop + NaN skip,_raw_sensitivity_accessor, andprediction_sensitivitycall fordpred_dtheta.So every scored point's sensitivity row is assembled twice, and
_require_gradient_supportedis 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 — sogntrbuildsgandHin 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/lbfgsare unaffected (they never assembleH). Filing so it isn't lost.