Skip to content

job_type = ms reports no fit at all when its first augmented model is non-finite, although the start point certifies fine (gntr reports it) #581

Description

@wshlavacek

What happens

A job_type = ms run whose first augmented model is non-finite reports no fit at all,
even when the start point itself certifies perfectly well. The run ends with:

Start 1 of 1: homotopy over 1 stage(s), inner_failed: best certified objective none, 2 evaluations
  stage trace: m=4: inf
No simulation completed, so there is no best fit to report.

job_type = gntr from the identical start point, same config, reports the fit normally:

Stop criterion satisfied with objective function value of -150.70078270720597

The two disagree about whether there is a result, and ms is the one that is wrong: the
point does score, and ms can prove it — see below.

Why

Reproduced on Borghans_BiophysChem1997 at a start drawn from the prior box. Rebuilding the
m=4 stage at that exact point:

1. unsegmented simulation: OK, 112 rows
2. seeded knots (log10):
   experiment1@1/4        [ 0.766 -2.836  4.133]
   experiment1@1/2        [ 0.766 -2.836  4.133]     <- all three identical
   experiment1@3/4        [ 0.766 -2.836  4.133]
3. objective at the seeded start: value=inf  finite=False
   equality finite=False  defect_norm=inf
4. per-segment simulation at the seeded start:
   segment 0: OK   end_state=[5.8307e+00 1.4589e-03 1.3584e+04] finite=False
   segment 1: OK   end_state=[5.8322e+00 1.4595e-03 1.3586e+04] finite=False
   segment 2: OK   end_state=[5.8322e+00 1.4595e-03 1.3586e+04] finite=False
   segment 3: OK   end_state=[5.8322e+00 1.4595e-03 1.3586e+04] finite=False
5. certificate: accepted=True objective=-150.70078278843627 detail='single-shoot reconstruction'

Three things are going on, and only the third is a bug:

  1. The trajectories integrate; the forward sensitivities overflow. Every segment returns
    OK (so backend.simulate's own finiteness check passed on the trajectory) but
    SegmentTrace.is_finite() is False, which tests d_end_param / d_end_ic. Z_state
    reaches 1.4e4 here. Refusing the point is correct — an assembly over non-finite
    sensitivities would produce a garbage local model.

  2. Seeding fell back to nominals, which is why all three knots carry the identical vector:
    _seed_knots returns [nominal.copy() for _ in knots] when the seeding trajectory is not
    finite. That is the documented fallback and is also fine.

  3. The run then reports nothing, although it has a certified score available. In
    AugmentedLagrangian.run:

    model = subproblem.at(u)
    outer_evals += 1
    if not model.is_finite():
        stop_reason = 'inner_failed'
        break                       # <-- returns before _certify is ever called
    ...
    record = self._certify(iteration, u, model, multipliers.penalty, optimality)

    The break precedes _certify, so no CertifiedIterate is produced, nothing is offered
    to CertifiedBest, on_iterate never fires, and nothing reaches
    MultipleShootingAlgorithm._record_iterate — hence an empty trajectory and
    "No simulation completed". Yet step 5 above shows problem.certify() at that very point
    returns accepted=True, objective=-150.70078, agreeing with gntr to 7 significant
    digits. Certification does not need the augmented model at all: it discards the auxiliary
    states and re-simulates unsegmented.

Why it matters beyond one start

  • On a single-start run this is the whole result. refine_method = ms (Add sensitivity-aware multiple shooting for ODE parameter estimation #563 arm 4) is
    single-start by construction — _resolve_n_starts returns 1 for an injected start — so a
    refine that lands here silently contributes nothing, and the run reports the search's
    fit with no indication the refine produced nothing usable.
  • On a multi-start run other starts paper over it, so it is easy to miss.
  • It is not rare on a stiff oscillator: 1 of 8 oscillating box draws hit it in a small paired
    experiment.

Suggested fix

Certify before bailing. The reported parameters at u are certifiable whether or not the
augmented model at u is finite, and the certificate is the run's honest answer for that
point. Roughly: on the non-finite branch, build the CertifiedIterate from
self.problem.certify(layout.reported_of(u)) with defect_norm = inf, offer it to best /
shared_best / on_iterate as usual, and then break with stop_reason = 'inner_failed'.

That would have this run report -150.70078 — the same number gntr reports — instead of
nothing.

A test at the layer level can pin it without a simulator: an offline TranscriptionProblem
whose objective_at returns a non-finite model while certify succeeds should still produce
OuterResult.best is not None.

Reproducing

Found while running the #563 acceptance work. The start is a prior-box draw on
Borghans_BiophysChem1997; the distinguishing property is a point whose whole-horizon
trajectory integrates but whose forward sensitivities are non-finite. Happy to attach the
exact parameter vector and the two confs if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions