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 down

Each 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.

`tol` has a floor too

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:

toltrue errorsteps
1e-57.4e-76
1e-63.8e-78
1e-73.4e-7 (best)12
1e-88.7e-721
1e-101.3e-634

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_evolveFunction
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.

`tol` has a floor

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 below dt_min raises 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, as step_observer!(; step, t_start, t_stop, state).
  • outputlevel = 0: >= 1 prints each accepted step and its error, >= 2 also 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.

source
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.

source
ITensorTDMPO.trace_distanceFunction
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.

source