Reviewed 2026-08-02 against SciPy 1.18.0, the current stable release, using the
official API reference,
the tagged public
ivp.py and
base.py sources,
and the 1.18.0 release.
PhaseProbe uses only the public scipy.integrate.solve_ivp function and public result fields.
The public signature is solve_ivp(fun, t_span, y0, method='RK45', t_eval=None, dense_output=False, events=None, vectorized=False, args=None, **options).
SciPy recommends explicit RK23, RK45, and DOP853 for non-stiff problems, with DOP853
for high precision. Radau and BDF target stiff problems; LSODA wraps ODEPACK and switches
between Adams and BDF formulas. Method selection is scientific configuration, not an
implementation detail, so PhaseProbe stores and replays it.
SciPy supports complex states only for some methods, and stiff complex problems require a
complex-differentiable RHS. PhaseProbe's common state protocol is deliberately a tuple of real
finite floats. SolveIVPAdapter therefore rejects complex state/output and tells users to split
real and imaginary components. This is narrower than SciPy, not a SciPy limitation.
SciPy validates finite y0, but intermediate non-finite RHS or result behavior can vary by
method and failure path. PhaseProbe independently validates RHS outputs, every retained time and
state, observables, events, and the final state. It treats solver exceptions, unsuccessful status,
and invalid values as different diagnostic evidence where possible.
Adaptive step acceptance can change after small floating-point, compiler, architecture, NumPy,
or SciPy differences. A controlled t_eval makes comparison meaningful but does not make the
internal step sequence or interpolated values byte-identical. Therefore:
- built-in fixed-step adapters retain
exactclass/identity/trace-hash replay; - SciPy trajectories use
tolerancereplay with declared state, observable, invariant, grid, endpoint, and event tolerances; - every stored fixture and run artifact still has a SHA-256 integrity digest;
- environment evidence is preserved for interpretation but a version string is not substituted for numerical comparison.
This is tolerance-based numerical replay, never "exact deterministic replay."
The optional extra is numpy>=1.23.5,<2.5 and scipy>=1.15,<1.19. The lower bound preserves
PhaseProbe's Python 3.10 support through SciPy 1.15; the upper bound prevents silently claiming
support for an unreviewed future SciPy minor. The lock resolves and tests these representative
combinations:
SciPy 1.18.0 itself requires Python 3.12–3.14 and NumPy 2.0 or newer. Resolver metadata chooses an older compatible SciPy within the declared PhaseProbe range on Python 3.10 and 3.11.
- SciPy issue #18039 discusses default tolerance
limitations, piecewise dynamics,
max_step, and manual convergence checks. - SciPy issue #9686 discusses bounded internal work and minimum-step behavior.
- SciPy issue #10319 records the distinction between
a controlled
t_evalgrid and terminal event states returned throughy_events. - The Scientific Python discussion "Design of callback for scipy.integrate.solve_ivp" concerns progress/control callbacks, not external qualitative regression evidence.
None duplicates PhaseProbe's external adapter, tolerance fixture, qualitative search, and pytest materialization workflow, and this project requests no SciPy API change.
