Observables and diagnostics

Observables

EvolutionObserver collects measurements along the evolution and is itself the step_observer! callback:

obs = EvolutionObserver(
    :chi      => maxlinkdim,
    :entropy  => ψ -> entanglement_entropy(ψ),
    :energy   => (ψ, t) -> instantaneous_energy(channels, t, ψ),
)

observe!(obs, ψ0, 0.0)                     # optional: record the initial state
ψ = time_evolve(channels, ψ0, 0.0, 10.0; nsteps = 100, step_observer! = obs)

r = results(obs)                            # (; step, time, chi, entropy, energy)

Each measurement is called as f(state) or, if it accepts two arguments, f(state, t). Element types are narrowed, so r.entropy is a Vector{Float64}. Pass every = k to record only every k-th step — use this for diagnostics that cost more than the evolution itself.

Adiabaticity diagnostics

Everything here is opt-in: no driver computes a diagnostic unless you put it in an observer.

functioncostwhat it tells you
instantaneous_energy(ch, t, ψ)one MPO application⟨H(t)⟩
energy_variance(ch, t, ψ)one MPO application⟨H²⟩ − ⟨H⟩², zero on an eigenstate
entanglement_entropy(ψ, b)one SVDvon Neumann entropy across bond b
instantaneous_gap(ch, t, ψ)DMRG solveE₁(t) − E₀(t)
adiabatic_report(ch, t, ψ)cheap by defaultbundle; gap = true adds DMRG

Energy variance is the cheap adiabaticity check. It vanishes exactly when the state is an instantaneous eigenstate, needs no ground-state solve, and never forms (it is computed from H|ψ⟩). Use it every step; reserve the DMRG-based gap for a sparse subset via every = k.

adiabatic_report(ch, t, ψ)              # energy + variance, no DMRG
adiabatic_report(ch, t, ψ; gap = true)  # adds gap, excess energy, GS fidelity

Reference

ITensorTDMPO.EvolutionObserverType
EvolutionObserver(:name => f, ...)

Accumulates measurements along a time evolution. Each f is called on the state after every step — as f(state) or, if it accepts two arguments, as f(state, t) — and the results are collected under :name.

An EvolutionObserver is itself the callback the drivers expect, so pass it as step_observer!. Read the accumulated data back with results.

Pass every = k to record only every k-th step. Use this for diagnostics that cost more than the evolution itself — a DMRG-based gap, say — so they can be sampled sparsely without slowing the run. An explicit call to observe! always records, regardless of every.

Examples

obs = EvolutionObserver(
    :Sz      => ψ -> expect(ψ, "Sz"),
    :entropy => ψ -> entanglement_entropy(ψ),
    :chi     => maxlinkdim,
    :energy  => (ψ, t) -> instantaneous_energy(channels, t, ψ),
)

observe!(obs, ψ0, 0.0)          # record the initial state (optional)
ψ = time_evolve(channels, ψ0, 0.0, 10.0; nsteps = 100, step_observer! = obs)

r = results(obs)                # (; step, time, Sz, entropy, chi, energy)
r.time, r.entropy
source
ITensorTDMPO.observe!Function
observe!(obs::EvolutionObserver, state, t; step = <next>)

Record one measurement row. The drivers call this for you; call it directly to record the initial state before evolving.

source
ITensorTDMPO.resultsFunction
results(obs::EvolutionObserver)

The accumulated measurements as a named tuple of vectors, with step and time alongside one entry per measurement. Element types are narrowed, so results(obs).entropy comes back as a Vector{Float64} rather than Vector{Any}.

source
ITensorTDMPO.entanglement_entropyFunction
entanglement_entropy(ψ::MPS, b::Integer = length(ψ) ÷ 2; base = ℯ)

Von Neumann entanglement entropy of the bipartition across bond b (between sites b and b+1). The Schmidt values are renormalized internally, so an unnormalized ψ is handled correctly.

Pass base = 2 for entropy in bits.

source
ITensorTDMPO.instantaneous_energyFunction
instantaneous_energy(channels::DrivingChannels, t, ψ::MPS; cutoff, maxdim)

The expectation value ⟨ψ|H(t)|ψ⟩ / ⟨ψ|ψ⟩ of the instantaneous Hamiltonian.

source
ITensorTDMPO.energy_varianceFunction
energy_variance(channels::DrivingChannels, t, ψ::MPS; cutoff, maxdim)

⟨H(t)²⟩ - ⟨H(t)⟩², which vanishes exactly when ψ is an eigenstate of H(t).

This is the cheap adiabaticity diagnostic: it needs one MPO application and no ground-state solve, and it tells you directly how far the state has drifted from an instantaneous eigenstate during a ramp. is never formed — the variance is computed from H|ψ⟩.

source
ITensorTDMPO.instantaneous_spectrumFunction
instantaneous_spectrum(channels, t, ψ_guess; nlevels = 2, weight = 10.0, dmrg_kwargs...)

The lowest nlevels eigenstates of H(t), via DMRG, returned as (energies, states). Excited states are found by penalizing overlap with the already-converged ones (weight).

This runs a full DMRG solve per call and is by far the most expensive diagnostic here — use it on a coarse subset of times, not every step. dmrg_kwargs are forwarded to ITensorMPS.dmrg (nsweeps, maxdim, cutoff, outputlevel, …).

source
ITensorTDMPO.instantaneous_gapFunction
instantaneous_gap(channels, t, ψ_guess; kwargs...)

The gap E₁(t) - E₀(t) of the instantaneous Hamiltonian. Built on instantaneous_spectrum, and just as expensive.

The gap is what sets the adiabatic time scale: the sweep must be slow compared with 1/Δ² near a gap minimum, so this is the quantity that tells you where a ramp needs to slow down.

source
ITensorTDMPO.adiabatic_reportFunction
adiabatic_report(channels, t, ψ; ψ_guess = ψ, gap = false, kwargs...)

A bundle of adiabaticity diagnostics at time t, as a named tuple:

  • energy⟨H(t)⟩
  • variance⟨H²⟩ - ⟨H⟩², zero for an exact eigenstate
  • excess⟨H(t)⟩ - E₀(t), the energy above the instantaneous ground state (missing unless gap = true)
  • gapE₁(t) - E₀(t) (missing unless gap = true)
  • fidelity|⟨ψ₀(t)|ψ⟩| against the instantaneous ground state (missing unless gap = true)

The gap = false default keeps this cheap: only energy and variance are computed, both without a ground-state solve. Setting gap = true adds a two-level DMRG solve.

source