Skip to content

Gradient path: add a discrete-events pre-flight gate (refuse cleanly, don't fail mid-run) #461

Description

@wshlavacek

Summary

The gradient optimizers (#386) refuse an unsupported fit with a clean pre-flight PybnfError that points the user at a metaheuristic fit_type. Today that happens in three places (pybnf/algorithms/optimizers/gradient_base.py), plus a per-evaluation objective gate:

  • edition_require_edition_2 (before any model is built);
  • backend_require_sensitivity_backend (after model build: the model must expose bngsim's forward-sensitivity hooks);
  • capability — deferred to _setup_gradient_path's apply_routing (the bngsim build must have the output_sensitivities feature);
  • objective — a per-evaluation GradientNotSupportedPybnfError for an objective the assembly cannot differentiate.

#386's deliverable list called for one more — "Capability / gradient-availability / events gate … Refuse to start … when the model has discrete events (BNGsim raises on sensitivity requests there)" — and that one was never implemented. This issue tracks it (it is the discrete-events half of #386 deliverable 5, the remaining unchecked gate).

The gap

A model with discrete events (a state-dependent discrete change in the dynamics) has no smooth forward sensitivity, so bngsim raises when sensitivities are requested for it. Because there is no pre-flight gate, such a model does not get the clean "use a metaheuristic fit_type" refusal the other gates give — instead it fails mid-run, at _setup_gradient_path / the first sensitivity-bearing simulate(), with a backend-level error. The requirement is documented (docs/gradient_fitting.rst: "requires a deterministic ODE simulation of a reaction network … run with method=>\"ode\"") but it is only documented, not enforced up front.

What to build

A new gate in GradientOptimizer (gradient_base.py) — e.g. _require_differentiable_dynamics — fired as early as it can be (after model build, next to _require_sensitivity_backend), that detects a discrete-event model and raises a PybnfError carrying the shared _FALLBACK_HINT. Mirror the existing gate pattern exactly:

def _require_sensitivity_backend(self):
    for model in self.model_list:
        if not hasattr(model, 'enable_output_sensitivities'):
            raise PybnfError("Gradient-based fitting (fit_type = %s) requires the bngsim "
                             "backend's forward sensitivities, but model '%s' ..." % (...),
                             _FALLBACK_HINT)

Detection mechanism — TBD / needs confirmation. I did not pin down how a discrete event is represented at the PyBNF/bngsim model layer (an events block? a model attribute exposed on the BngsimModel? something only the bngsim engine knows?). The implementer should confirm where the signal lives; the gate may need a small bngsim-side hook (a model.has_discrete_events / capability query) so the refusal can be made before the run rather than caught from a backend exception. If a pre-flight signal genuinely isn't available, the fallback is to catch the bngsim sensitivity-request exception at _setup_gradient_path and re-raise it as the same clean PybnfError + fallback hint (a late but still actionable gate).

Adjacent (decide in scope)

Non-ODE simulation methods — SSA (stochastic) and NFsim (network-free) — are likewise non-differentiable, and there is no runtime gate for them on the gradient path either (only the documented method=>\"ode\" requirement). Decide whether this gate should be the unified "smooth deterministic ODE" check covering both the events case and the non-ODE-method case, or events-only with the method case tracked separately.

Done criteria

  • A discrete-event model selected with fit_type = trf / lbfgs is refused before the run (or at worst at path activation) with an actionable PybnfError pointing at a metaheuristic fit_type — never a raw backend traceback.
  • A test mirroring the existing gate tests (test_trf_refuses_when_bngsim_lacks_output_sensitivities / test_trf_refuses_non_least_squares_objective_pointing_at_lbfgs) covering the events refusal.
  • gradient_base.py's gate comment block (currently lists three gates) updated to four; docs/gradient_fitting.rst notes the ODE/no-events requirement is enforced, not just documented.
  • Decide and record the scope re: SSA / NFsim (unified non-differentiable-dynamics gate vs events-only).

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    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