MPS Backend

The MPS (Matrix Product State) backend is the default backend and the package's namesake: it represents the quantum state as a matrix product state via ITensors.jl and ITensorMPS.jl, truncating the bond dimension at every gate application according to cutoff/maxdim. This is what lets it scale to L = 100+ qubits for the entangling-but-not- maximally-entangling circuits typical of MIPT/CIPT studies, at the cost of a controlled truncation error.

When to use it: the general-purpose default — production runs at L = 100+, or any circuit whose entanglement growth stays bounded by a reasonable maxdim (measurement-induced and control-induced phase transitions are exactly this regime, by construction: measurements and resets keep entanglement in check).

When not to use it: when you need an exact zero-truncation-error reference (see State Vector Backend), or when the circuit is built entirely from Clifford-group gates and you want to push L into the hundreds or thousands (see Clifford Backend).

Quick Example

using QuantumCircuitsMPS

state = SimulationState(L=12, bc=:periodic, backend=:mps, maxdim=64, cutoff=1e-10,
    rng=RNGRegistry(gates_spacetime=42, gates_realization=1, born_measurement=2))
initialize!(state, ProductState(binary_int=0))
track!(state, :entropy => EntanglementEntropy(; cut=6))

apply!(state, HaarRandom(), Bricklayer(:even))
apply!(state, HaarRandom(), Bricklayer(:odd))
record!(state)
println("Entropy: $(state.observables[:entropy][end])")

backend=:mps is shown explicitly above but is the default and can be omitted. cutoff (SVD truncation threshold, default 1e-10), maxdim (maximum bond dimension, default 100) and retruncate (what a projective gate does to the bonds it did not touch, default :never; see Normalization After Truncation) are MPS-only keywords — they are silently ignored on the other backends for cross-backend API consistency (see the Backend Interface Contract).

Internal Engine

Every apply! call resolves a gate to an ITensor via build_operator, then contracts and truncates it back into the MPS via apply_op_internal! (SVD-based contraction with re-orthogonalization). See Design Philosophy for the full layered picture and Backend Interface Contract for the developer-facing method table every backend (including this one) must implement.

Normalization After Truncation

Unitary gates are not renormalized after application (only gates with needs_normalization(gate) == true, such as projections and measurements, trigger normalize!). When a unitary layer is truncated by cutoff/maxdim, the discarded Schmidt weight is simply dropped, so the stored MPS has $\langle\psi|\psi\rangle < 1$. This is a valid (unnormalized) representation of the retained state — the norm carries the accumulated truncation weight and can be read off with norm(state.backend.mps).

Every gate, unitary or projective, SVD-truncates the bonds inside its range with cutoff/maxdim. A projection also changes the Schmidt spectrum at every other bond (it can only lower the ranks), and the retruncate keyword decides what to do about that:

  • retruncate = :never (default): nothing. Untouched bonds keep their current dimension until the next gate that touches them — in a brickwork circuit, within one layer. Nothing is discarded, so this is never less accurate; the cost is at most a layer of slightly larger bonds.
  • retruncate = :gate: a full-chain truncate!(; cutoff) after every projective gate (an orthogonalization sweep to one end and an SVD sweep back), which keeps every bond minimal at all times at a cost of $O(L)$ factorizations per measurement — for a layer of $pL$ measurements, that is $O(pL^2)$ against the $O(L)$ of the unitary layer. This was the only behavior before the keyword existed.

The two settings give the same physics up to truncation error: they differ in when bonds are compressed, so an individual trajectory is reproduced only to the level of its own truncation error (on a maxdim-limited run that can be well above machine precision), not digit for digit.

Every built-in observable divides the norm out, so reported values are expectation values of the normalized retained state: born_probability and Magnetization through ITensorMPS.expect, EntanglementEntropy and MutualInformation by normalizing the Schmidt/RDM spectrum, and PauliString, StringOrder, DomainWall (hence also Correlator and MagnetizationFluctuations) by dividing their inner contraction by $\langle\psi|\psi\rangle$. A custom observable that contracts the MPS directly (e.g. with inner) must do the same — see Custom Observables.

PBC Indexing Caveat

Under bc=:periodic, the MPS backend stores sites in a folded RAM ordering (src/Core/basis.jl) so that periodic boundary conditions can be represented by a 1-D chain topology. All public geometry/gate/observable APIs speak physical site indices and translate transparently — except EntanglementEntropy(cut=k), whose cut is a bond index of the folded chain: the subsystem is state.ram_phy[1:k], a contiguous ring arc growing outward from the fold seam (pbc_fold_start, default L÷4+1). The state-vector, Clifford, and Gaussian backends have no fold and bipartition the physical prefix $\{1,\ldots,k\}$. With the default fold the half cut cut = L÷2 is $\{1,\ldots,L/2\}$ on every backend (one neighbouring cut is aligned as well), and sort(state.ram_phy[1:k]) == 1:k tells whether a given k is. For L = 8 (ram_phy = [3, 2, 4, 1, 5, 8, 6, 7]), cut = 2 measures $\{2, 3\}$ on MPS and $\{1, 2\}$ on the other backends. See the PBC note in the EntanglementEntropy docstring for the full table, the Backend Interface Contract's PBC section, and EntropyProfile's docstring; for cross-backend comparison at other cuts use bc=:open.

Regions are not supported on MPS. EntanglementEntropy also accepts a region form of cut — a range (c1:c2) or a vector of sites ([s1, s2, ...]) — on the state-vector, Clifford, and Gaussian backends (see their respective pages). On MPS, any non-Int cut throws an ArgumentError: MPS entanglement entropy is read off the SVD of a single bond adjacent to the orthogonality center, which has no native representation for an arbitrary site subset. The single cut::Int bipartition remains the only MPS interface for entanglement entropy; switch to backend=:statevector, :clifford, or :gaussian for region-based entropy measurements.