Adaptive stepping
Set an accuracy instead of a step count:
ψ = time_evolve(channels, ψ0, 0.0, 10.0; alg = "cfet", adaptive = true, tol = 1e-7)
# adaptive_time_evolve also returns the step history
ψ, hist = adaptive_time_evolve(channels, ψ0, 0.0, 10.0; alg = "cfet", tol = 1e-7)
hist.dts, hist.errors # where the stepper had to slow downEach candidate step is taken once at dt and again as two steps of dt/2; the trace_distance between the results estimates the local error, the step is accepted when that is below tol, and the next step size is scaled by (tol/err)^(1/p). This costs three sub-steps per accepted step, so it pays off when ‖Ḣ‖ varies strongly across the evolution — a ramp that must crawl through a gap minimum and can sprint elsewhere — and not for a uniform drive.
Step-doubling assumes the per-step integrator is otherwise exact, so that shrinking the step always shrinks the error. TDVP is not exact: push tol far enough below its own per-application roundoff and the coarse/fine comparison stops measuring the integration error at all — it measures roundoff noise instead. Measured on the benchmark model on The time_evolve interface page, the true error (against an independent reference) is minimized around tol ≈ 1e-7 and increases for tol = 1e-8, 1e-9, 1e-10, even though the stepper dutifully takes more steps each time:
tol | true error | steps |
|---|---|---|
| 1e-5 | 7.4e-7 | 6 |
| 1e-6 | 3.8e-7 | 8 |
| 1e-7 | 3.4e-7 (best) | 12 |
| 1e-8 | 8.7e-7 | 21 |
| 1e-10 | 1.3e-6 | 34 |
A telltale sign you've crossed this floor: the recorded step errors in hist.errors start reading exactly 0.0. Keep tol at or above roughly the state/operator cutoff in use, not many orders tighter.
Reference
ITensorTDMPO.adaptive_time_evolve — Function
adaptive_time_evolve(channels, ψ0, t_start, t_stop; tol = 1e-6, kwargs...)Evolve from t_start to t_stop choosing each step size automatically, so that you set an accuracy rather than a step count.
Each candidate step is taken twice — once at size dt, and once as two steps of dt/2 — and the trace_distance between the two results estimates the local error. The step is accepted when that estimate falls below tol, and the next step size is scaled by (tol/err)^(1/p) with p the local error order of the chosen algorithm. The finer of the two solutions is the one kept.
This costs three sub-steps per accepted step, so it pays off when ‖Ḣ‖ varies strongly over the evolution — as it does for a ramp that is fast near a gap minimum and slow elsewhere — and not for a uniform drive.
Step-doubling assumes shrinking the step always shrinks the error, which requires the per-step integrator itself to be exact. TDVP is not: push tol far enough below its own per-application roundoff and the coarse/fine comparison starts measuring roundoff noise instead of integration error, at which point more steps make the true result worse. A telltale sign is history.errors reading exactly 0.0. On the benchmark model used throughout this page, the true error is minimized around tol ≈ 1e-7 and increases for tol ≤ 1e-8; keep tol at or above roughly the cutoff in use, not many orders tighter.
Keywords
tol = 1e-6: target local error per step.dt_init: first step size (default: a twentieth of the interval).dt_min,dt_max: bounds on the step size. Falling belowdt_minraises an error rather than stalling.safety = 0.9,grow_max = 5.0,shrink_min = 0.2: step-size controller limits.maxsteps = 100_000: guard against runaway loops.(step_observer!) = nothing: called after each accepted step, asstep_observer!(; step, t_start, t_stop, state).outputlevel = 0:>= 1prints each accepted step and its error,>= 2also prints rejections.
All remaining keywords (alg, order, cutoff, operator_cutoff, …) are passed to time_evolve for the individual sub-steps.
Returns (state, history) where history is a named tuple of vectors (; times, dts, errors) recording the accepted steps.
adaptive_time_evolve([(f1, H1), (f2, H2), ...], ψ0, t_start, t_stop; kwargs...)Convenience form taking the channels as a plain list of (driving, MPO) tuples or pairs (see DrivingChannels), for calling this driver directly without building the DrivingChannels object yourself.
ITensorTDMPO.trace_distance — Function
trace_distance(a::MPS, b::MPS)sqrt(1 - |⟨a|b⟩|²) between normalized states, the error measure used by the adaptive stepper and by the paper's finite-size benchmark. Both states are normalized internally, so a norm drift from truncation does not register as an error.