Dyson series

U(t, t₀) = 1 + Σₐ [fₐ] H⁽ᵃ⁾ + Σₐᵦ [fₐfᵦ] H⁽ᵃ⁾H⁽ᵇ⁾ + …
ψ = time_evolve([(1.0, Hzz), (ramp, Hx)], ψ0, 0.0, 10.0;
                alg = "dyson", order = 2, nsteps = 100)

# ...or the driver directly, for independent control of each truncation
U = dyson_mpo_fsm(channels, t0, t1; order = 2, cutoff = 1e-14)
ψ = dyson_evolve(
    channels, ψ0, 0.0, 10.0;
    nsteps = 100, order = 2,
    mpo_kwargs = (; cutoff = 1e-14),
    apply_kwargs = (; cutoff = 1e-10, maxdim = 128),
)

A truncated Dyson MPO is not exactly unitary, so dyson_evolve renormalizes after each step by default (normalize = true).

For a single channel with constant driving the construction reduces to the truncated Taylor series of exp(-i Δ H), which the tests verify against dense matrices.

Two constructions

dyson_evolve builds each step with dyson_mpo_fsm, the size-extensive finite-state-machine encoding of Vanthilt et al. — its accuracy does not degrade with chain length.

A second, direct construction is also implemented and exported — dyson_mpo and dyson_terms — which builds operator strings by explicit MPO multiplication rather than by manipulating the paper's {L, R, A, D} block structure. It is simpler and useful for inspecting individual terms of the series directly, and it is what the FSM construction is verified against, but it is not size-extensive: its error grows sharply with chain length, and by around 4 sites it is already less accurate than freezing the Hamiltonian. Prefer dyson_evolve/dyson_mpo_fsm unless you specifically need dyson_mpo's direct term-by-term construction. See Scope and limitations for the measured comparison and why the paper's row compression is not implemented on top of the FSM construction.

Why Dyson improves order-by-order but Magnus doesn't

The paper's own benchmark (Sec. VIII A) shows the Dyson MPO's error scaling cleanly as O(dtᴺ) at every order N. Both constructions encode the same series to the same order, so this applies equally to dyson_mpo_fsm and dyson_mpo; the table below was measured on the latter, against an exact dense reference:

order123
Dyson: global order~1.0~2.0~2.9
Magnus: global order~2.0~3.9~3.9

Dyson is a plain truncated polynomial in the time step — U ≈ 1 + Σₐ[fₐ]H⁽ᵃ⁾ + Σₐᵦ[fₐfᵦ]H⁽ᵃ⁾H⁽ᵇ⁾ + … — so order N means "correct through (dt)ᴺ" and each additional order buys exactly one more power of dt, with no further subtlety.

Magnus is different in kind: it builds a generator from nested commutators and then exponentiates it, U ≈ exp(Ω). That nonlinear map is where the pairing comes from — a standard feature of Magnus/geometric integrators, not a defect: Ω₁ alone already reaches global order 2 (a "free" jump from the exponential), and Ω₁+Ω₂ jumps again to ~4, but Ω₃ alone doesn't buy a further jump to 6 — that requires Ω₃ and Ω₄ together, which is a substantially harder nested-commutator basis to get right. See Magnus expansion for why Ω₄ is not offered here and where higher order comes from instead.

When Dyson wins on cost

The previous section shows Dyson needs a higher nominal order than Magnus/CFET to reach the same global accuracy — but "higher order" does not mean "more expensive" until construction cost catches up with it, and at low order it hasn't. Measured on the driven TFIM chain (Sz·Sz + oscillating transverse field) at N = 12 with exact bond dimension, against a dense RK4 reference, finding the cheapest configuration of each method that reaches a given target error:

target errorcheapest methodcost
~1e-6dyson order 3, 8 steps (1.22e-6)1.05 s
~1e-8dyson order 3, 16 steps (1.60e-8)1.38 s
~1e-10cfet, 16 steps (6.44e-12)12.95 s

At moderate accuracy — plausibly where most workloads sit — Dyson at order 2–3 is 3–6× cheaper than cfet/magnus for comparable or better accuracy, because a low-order Dyson MPO is cheap to build and dyson_evolve applies it in a single apply per step, with no internal TDVP sweep at all (see Large-system benchmarks for the same effect at production scale). But this reverses at tight targets: reaching ~1e-10 needs order 4, and order 4 (16 steps, 5.62e-11, 22.46 s) is already slower than cfet/magnus reaching better accuracy. Past order 4 it gets much worse fast — order 5 alone (8 steps) measured 12.31 s → 610.75 s going from order 4, a ~50× jump for one more order, the combinatorial construction cost described in Scope and limitations arriving in full force. Don't reach for order ≥ 5 expecting it to behave like the cheap end of this table.

So: cfet remains the right default — it is uniformly good across the whole accuracy range, with no such cliff — but if you know your accuracy target is moderate (order 2–3 territory) and want the fastest option, dyson_evolve at that order is a genuine, measured win, not a compromise. This is a distinct comparison from the previous section: that one holds order fixed and asks which method is more accurate; this one holds the target accuracy fixed and asks which method is cheaper to reach it — and the answer depends on where in the accuracy range you're asking.

Reference

ITensorTDMPO.dyson_evolveFunction
dyson_evolve(channels, ψ0, times; order = 2, kwargs...)
dyson_evolve(channels, ψ0, t_start, t_stop; dt = nothing, nsteps = nothing, kwargs...)

Time-evolve ψ0 under the time-dependent Hamiltonian carried by channels by building the order-th order Dyson MPO on each interval of times and applying it to the state.

Unlike piecewise_constant_tdvp, the Hamiltonian is not frozen within a step: the time dependence within each interval is captured exactly, to the given order, through the time-ordered integrals of the driving functions. This allows substantially larger time steps for rapidly varying drives.

The step MPO is built by dyson_mpo_fsm, the size-extensive finite-state-machine encoding of Vanthilt et al.: its accuracy does not degrade with chain length, unlike the direct construction of dyson_mpo, which is available for direct use and is the independent reference the FSM construction is tested against.

Keywords

  • order = 2: order of the Dyson expansion on each step.
  • mpo_kwargs = (;): forwarded to dyson_mpo_fsm (cutoff, maxdim, npoints, compress).
  • apply_kwargs = (; cutoff = 1e-10): forwarded to apply(U, ψ).
  • normalize = true: renormalize the state after each step. A truncated Dyson MPO is not exactly unitary, so for real-time evolution this removes the spurious norm drift.
  • (step_observer!) = nothing: callback step_observer!(; step, t_start, t_stop, state) after each step.
  • outputlevel = 0: set >= 1 to print progress.

Returns the final evolved MPS.

source
dyson_evolve([(f1, H1), (f2, H2), ...], ψ0, args...; 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.dyson_mpo_fsmFunction
dyson_mpo_fsm(channels, t0, t; order = 2, cutoff, maxdim, npoints, prefactor, compress)

The order-th order Dyson MPO of the time-evolution operator U(t, t₀), built by the size-extensive finite-state-machine encoding of Vanthilt et al. rather than by direct MPO multiplication. This is the construction dyson_evolve uses for each step; call it directly for independent control of the per-step truncation, or to build a single step's operator without driving a full evolution.

Unlike dyson_mpo, the accuracy of the operator this returns does not degrade with chain length — see Scope and limitations for the measured comparison. dyson_mpo is available for direct use, and is the construction this one is verified against.

cutoff and maxdim truncate the resulting MPO after construction. This truncation is doing real work: on an XXZ chain it takes the order-3 bond dimension from 64 to 14 while leaving the error unchanged to three significant figures, which is at or below the bond dimensions the paper's Sec. VI B row compression is designed to reach — see Scope and limitations for why that compression is not implemented separately.

compress = true applies the exact equivalent-column compression of the paper's Sec. VI A before truncation, which only ever shrinks the bond dimension the truncation step then has to work with.

Do not loosen `cutoff` much

The construction is exact, so truncation error lands directly on the result: at cutoff = 1e-12, an order-3 MPO stops converging as O(dt⁴) once dt is small enough for the step error to fall below the truncation floor — the measured rate turns negative. The default of 1e-14 avoids this; anything looser silently caps the accuracy that raising order can buy.

source
ITensorTDMPO.dyson_mpoFunction
dyson_mpo(channels::DrivingChannels, t0, t; order = 2, kwargs...)

The order-th order Dyson series approximation to the time-evolution operator U(t, t0) of the time-dependent Hamiltonian carried by channels, as a single MPO.

Following Vanthilt et al., writing H(t) = Σₐ fₐ(t) H^{(a)} the Dyson series reads

\[U(t, t_0) = 1 + \sum_a [f_a] H^{(a)} + \sum_{ab} [f_a f_b] H^{(a)} H^{(b)} + …\]

where [f_{a₁} ⋯ f_{aₙ}] are the time-ordered integrals of time_ordered_integral, which carry the factors of -i. The operator strings are built by direct MPO multiplication and the series is summed with truncation.

Scope and accuracy

This is the direct construction: operator strings are formed as explicit MPO products. It reproduces the Dyson series of the paper term by term, but not the paper's size-extensive finite-state-machine encoding, which keeps the bond dimension low and makes the accuracy independent of chain length — on finite systems as well as in the thermodynamic limit.

As a result the error here grows steeply with system size — already less accurate than freezing the Hamiltonian by a chain length of about 4 sites. dyson_evolve does not have this problem: it builds each step with dyson_mpo_fsm instead. Building steps from this function directly is appropriate for small systems and for inspecting individual Dyson terms; for anything larger, prefer dyson_evolve. See Scope and limitations for the measured comparison.

Keywords

  • order = 2: highest order in the time step retained.
  • cutoff = 1e-12, maxdim: truncation used for the intermediate MPO products and for the final sum.
  • npoints = 1025: grid resolution for the time-ordered integrals.

A truncated Dyson MPO is not exactly unitary, so applying it to a normalized state does not preserve the norm exactly; the deviation is of the order of the neglected terms.

source
ITensorTDMPO.dyson_termsFunction
dyson_terms(channels::DrivingChannels, t0, t; order, cutoff, maxdim, npoints)

The individual terms of the Dyson series for U(t, t0), as a vector of MPOs including the zeroth-order identity. Summing them gives dyson_mpo; they are exposed separately so that the truncation of the sum can be controlled by the caller.

source