Repository navigation
Choosing a Simulation Method
Choose between statevector, matrix product state (MPS), and general tensor-network simulation based on the circuit structure, requested outputs, accuracy requirements, and available resources.
Version: FlagQuantum 0.3.1. All three complete scripts below were verified on macOS Apple Silicon with Python 3.12.14 and PyTorch 2.14.1 using local CPU execution. They require the core installation from Installation and Compatibility; the demonstrated native PyTorch paths do not require the optional JAX or cotengra packages.
Formula/code review — October 20, 2026: The equations are paired with code excerpts, and the local CPU examples were re-run with Python 3.12.14 and PyTorch 2.13.0. See the review record for executed commands, distributed checks, and untested hardware/provider paths. Earlier environment and output records are retained.
| Method | Representation | Good starting scenarios | Main limitation |
|---|---|---|---|
statevector |
One complex amplitude for every computational-basis state | Small reference calculations, debugging, arbitrary circuit connectivity, and full-state inspection | State storage grows exponentially with qubit count. |
mps |
A chain of small tensors connected by bond indices | Circuits that maintain modest entanglement across cuts in the chosen qubit ordering, especially local chain-like interactions | Bond dimensions can grow exponentially; long-range interactions and decompositions add cost. |
tensor_network |
A circuit-derived network of tensors evaluated by contraction | Structured circuits with manageable contraction width, particularly when only a few outputs are needed | Contraction order and intermediate tensor sizes can make execution expensive or infeasible. |
MPS is itself a tensor-network representation. The distinction here is between FlagQuantum's chain-state mps mode and its more general circuit-contraction tensor_network mode.
Start with statevector when the circuit fits comfortably in memory. Use it as a correctness reference on a reduced problem before moving to a compressed or contraction-based method. There is no universal qubit-count threshold or fastest method: depth, connectivity, entanglement, outputs, batching, and gradient requirements all matter.
An n-qubit pure state has 2**n complex amplitudes. Statevector simulation stores that dense vector and updates it as gates execute. It represents general pure states without imposing a low-entanglement structure.
- The full state fits in memory, including working space and any training graph.
- You want a small, straightforward reference for probabilities, expectations, or gradients.
- You need to inspect many amplitudes or the full probability distribution.
The amplitude array alone requires approximately 2**n * bytes_per_complex_value bytes per batch entry. For 30 qubits, that is 8 GiB with complex64 or 16 GiB with complex128, before buffers, output arrays, optimizer state, or backward storage. A single-device allocation estimate is not a guarantee that execution fits.
Requesting only one expectation can reduce output size, but it does not remove the dense statevector maintained by this method. These noiseless pure-state examples also do not establish mixed-state or noise support; density-matrix and trajectory workflows are separate capabilities.
An MPS factors amplitudes into a chain of site tensors. Neighboring tensors share indices whose sizes are called bond dimensions. A bond must be large enough to represent the state's correlations across the corresponding cut in the chain.
For fixed physical dimension and a maximum bond dimension chi, state storage scales roughly as O(n * chi**2) per batch entry. This is useful only while chi stays manageable. General states can require exponentially large bonds, and gate application or differentiation needs additional working memory.
- The evolving state has modest entanglement across the relevant chain cuts.
- Interactions are mostly local in a useful qubit ordering.
- You need observables or samples without constructing every amplitude.
MPS is not inherently approximate. With no bond cap and no truncation, it can represent an exact state up to numerical precision, while allowing bonds to grow. A low bond cap or nonzero truncation threshold can reduce cost by discarding information, introducing approximation error.
Low entanglement in a final state does not guarantee a cheap run: intermediate states may need larger bonds. Non-neighbor gates can require extra work, and changing the qubit ordering can change which cuts are difficult. Converting an arbitrary dense input state to MPS also starts from an exponentially large input description.
Use explicit accuracy settings and inspect truncation information when approximating. A bond cap alone is not a certified bound on an observable's error. Changes in rank and tensor factorizations can also affect numerical gradient behavior; compare gradients on a tractable reference problem.
A circuit can be represented as gate and state tensors joined by shared indices. Computing an amplitude or expectation involves summing over those internal indices, called tensor contraction.
The contraction path determines which tensors are combined first. Different paths can have very different intermediate sizes, memory requirements, and arithmetic cost, even for the same circuit.
- The circuit's structure permits small contraction intermediates.
- You need selected expectations, amplitudes, or marginals rather than the full state.
- You can inspect or control contraction planning for your workload.
Tensor networks do not make arbitrary circuits efficient. Highly connected or deep circuits can have expensive contractions. Path search itself can cost time. Slicing a contraction into pieces can reduce peak intermediate storage while increasing work; slicing alone does not imply multi-GPU or multi-node execution.
General tensor-network contraction can be exact up to floating-point precision. Approximation, when used by a particular workflow, is a separate choice. In FlagQuantum 0.3.1, the general tensor-network execution/training capability is labeled Experimental; noise-channel instructions in this mode are unsupported. Consult the release capability catalog for the exact scope rather than extrapolating from a successful small example.
| Goal | Suggested output strategy |
|---|---|
| Check a local observable | Request fq.expectation(...). |
| Inspect a few qubits | Request a marginal such as fq.probabilities(qubits=(0,)). |
| Compare methods on a tiny circuit | Compare outputs against an analytical result and a statevector reference. |
| Inspect every amplitude | Use statevector if practical; a dense conversion from another representation can erase its memory advantage. |
| Train a model | Validate both predictions and gradients for the actual execution path. |
A full statevector or full probability array has 2**n entries regardless of how efficiently the internal state was represented. Computing a large dense output can therefore dominate a compressed calculation. Marginals also grow exponentially with the number of selected qubits.
The scripts below request small outputs and explicitly select each mode. They disable backend fallback and disallow approximate execution. These settings do not make an unsupported workload supported, impose a memory budget, or guarantee a particular numerical tolerance.
| Formula / quantity | Corresponding code excerpt | What the scripts verify |
|---|---|---|
statevector_elements = batch_size * (2 ** n_qubits) |
This is an allocation estimate, not a measured peak-memory value. | |
statevector_elements * torch.tensor([], dtype=torch.complex64).element_size() |
s is bytes per complex value; exclude work buffers and autograd storage. | |
fq.Circuit(2).ry(0, 0.6).cx(0, 1) |
The same circuit is evaluated in all three modes. | |
expected_z = torch.cos(torch.tensor(0.6)).item() |
compare_simulation_methods.py checks the named expectations. | |
expected = -torch.sin(torch.tensor(0.6)) |
compare_simulation_gradients.py checks all three gradient paths, with atol=1e-5. | |
state.bond_dims, state.max_bond
|
inspect_mps_bonds.py checks normalization and the GHZ maximum bond. |
MPS storage roughly follows
Save as compare_simulation_methods.py:
import torch
import flagquantum as fq
def build_circuit():
return fq.Circuit(2).ry(0, 0.6).cx(0, 1)
expected_z = torch.cos(torch.tensor(0.6)).item()
for mode in ("statevector", "mps", "tensor_network"):
result = fq.run(
build_circuit(),
outputs=[
fq.expectation(fq.Z(0), name="z0"),
fq.expectation(fq.Z(0) @ fq.Z(1), name="zz"),
fq.probabilities(qubits=(0,)),
],
options=fq.ExecutionOptions(
mode=mode,
backend="pytorch",
device="cpu",
allow_backend_fallback=False,
allow_approximate=False,
),
)
z0 = result.expectation("z0").item()
zz = result.expectation("zz").item()
probabilities = result.probabilities[0]
print(f"{mode}: <Z0>={z0:.6f}, <Z0 Z1>={zz:.6f}")
print(" Qubit 0 probabilities:", [round(p.item(), 6) for p in probabilities])
assert abs(z0 - expected_z) < 1e-6
assert abs(zz - 1.0) < 1e-6
assert abs(probabilities.sum().item() - 1.0) < 1e-6
print("Three-method output checks passed.")Run python compare_simulation_methods.py. Expected output:
statevector: <Z0>=0.825336, <Z0 Z1>=1.000000
Qubit 0 probabilities: [0.912668, 0.087332]
mps: <Z0>=0.825336, <Z0 Z1>=1.000000
Qubit 0 probabilities: [0.912668, 0.087332]
tensor_network: <Z0>=0.825336, <Z0 Z1>=1.000000
Qubit 0 probabilities: [0.912668, 0.087332]
Three-method output checks passed.
The circuit prepares cos(0.3)|00> + sin(0.3)|11>. Consequently, <Z0> = cos(0.6) and <Z0 Z1> = 1. The displayed probabilities concern only qubit 0; no full-register probability vector is requested.
This is a correctness comparison, not a performance benchmark. For a two-qubit circuit, execution overhead can dominate, so its runtime would not establish which method is best for a larger workload.
A product state needs bond dimension 1. A GHZ state is entangled, but needs only bond dimension 2 across each nontrivial chain cut. Thus, the presence of entanglement alone does not imply that an MPS will be large; the required ranks across cuts matter.
The following script uses the documented native MPS entry point to inspect the representation. Its max_bond and cutoff arguments belong to run_mps; they are not fields of the generic fq.ExecutionOptions object in 0.3.1.
Save as inspect_mps_bonds.py:
import torch
import flagquantum as fq
from flagquantum.simulation.mps import run_mps
product = fq.Circuit(6)
ghz = fq.Circuit(6).h(0)
for qubit in range(1, 6):
ghz.cx(qubit - 1, qubit)
for label, circuit in (("Product", product), ("GHZ", ghz)):
state = run_mps(circuit, max_bond=None, cutoff=0.0)
print(f"{label} bond dimensions:", state.bond_dims)
print(f"{label} maximum bond:", state.max_bond)
assert torch.allclose(state.state_norm(), torch.ones(1), atol=1e-6)
assert run_mps(ghz, max_bond=None, cutoff=0.0).max_bond == 2
print("MPS bond checks passed.")Run python inspect_mps_bonds.py. Expected output:
Product bond dimensions: (1, 1, 1, 1, 1)
Product maximum bond: 1
GHZ bond dimensions: (2, 2, 2, 2, 2)
GHZ maximum bond: 2
MPS bond checks passed.
Here max_bond=None and cutoff=0.0 request untruncated execution. The native result is an MPSState, not the ExecutionResult returned by fq.run. The norm checks verify normalization for these examples; they do not establish an error guarantee for an approximate run.
Matching forward values is necessary but not sufficient for training. The gradient path, numerical stability, and supported observable must also match your requirements. Test the derivative on a small problem before training a larger model.
Save as compare_simulation_gradients.py:
import torch
import flagquantum as fq
def build_circuit(parameters):
return fq.Circuit(2).ry(0, parameters[0]).cx(0, 1)
for mode in ("statevector", "mps", "tensor_network"):
model = fq.Module(
build_circuit,
n_parameters=1,
init=torch.tensor([0.6]),
device="cpu",
policy=fq.RuntimePolicy(
execution_options=fq.ExecutionOptions(
mode=mode,
backend="pytorch",
device="cpu",
allow_backend_fallback=False,
allow_approximate=False,
),
observable="z",
observable_qubits=(0,),
),
)
loss = model().mean()
loss.backward()
gradient = next(model.parameters()).grad[0]
expected = -torch.sin(torch.tensor(0.6))
assert torch.isfinite(gradient)
assert torch.allclose(gradient, expected, atol=1e-5)
print(f"{mode}: <Z0>={loss.item():.6f}, gradient={gradient.item():.6f}")
print("Three-method gradient checks passed.")Run python compare_simulation_gradients.py. Expected output:
statevector: <Z0>=0.825336, gradient=-0.564642
mps: <Z0>=0.825336, gradient=-0.564642
tensor_network: <Z0>=0.825336, gradient=-0.564642
Three-method gradient checks passed.
Each iteration creates a fresh module with the same initial angle. The analytical derivative is -sin(0.6). The script checks that each gradient is finite and agrees with this reference within 1e-5.
Agreement for this circuit does not guarantee gradients for every gate, rank configuration, output type, noise model, or backend. In particular, the release catalog describes the general tensor-network execution/training surface as experimental even though selected exact gradient workflows have their own scoped support statements.
- Specify the result. Decide whether you need a few observables, samples, marginals, or the entire state, and whether backward computation is required.
- Estimate the dense baseline. Include batch size, precision, working space, and gradients rather than just the amplitude array.
- Inspect structure. Consider MPS bond growth and interaction locality, or tensor-network contraction intermediates and output size.
- Validate a reduced case. Compare values and, when needed, gradients with analytical expectations or a statevector reference.
- Measure the real workload. Record peak memory and forward/backward time on the intended hardware. Increase circuit size gradually and keep accuracy settings explicit.
If statevector fits and meets your needs, it is a useful initial choice. If it does not fit, examine MPS and tensor-network structure before assuming either method will solve the capacity problem. Qubit count alone is insufficient to predict performance.
The 0.3.1 capability catalog distinguishes Production supported local statevector simulation/training, Development evidence for the combined differentiable/sharded MPS training entry, and Experimental general tensor-network execution/training. Other specifically qualified gradient or multi-node workflows have separate entries; their maturity does not automatically extend to every use of a representation.
| Pitfall | Better approach |
|---|---|
| Treating MPS as always approximate | Check whether truncation is enabled and whether bonds can grow. |
| Assuming a small bond cap has negligible error | Inspect truncation evidence and compare observables against a reference. |
| Assuming tensor networks are always faster | Evaluate planning cost, contraction intermediates, and the requested outputs. |
| Asking a compressed method for an enormous dense state | Request the quantities you actually need. |
Using device="mps" to select matrix product states |
Use mode="mps". Apple's MPS device naming is a separate concept and does not establish device support here. |
| Treating local tensor slicing as distributed execution | Verify the separately supported distribution semantics and transport path. |
| Assuming a CPU demonstration certifies GPU behavior | Validate the intended device and workload separately. |
| Silently allowing a different backend during a comparison | Disable fallback and report any explicit representation conversions. |
- Distributed Simulation and Training: sharding, communication preflight, and a verified two-worker training workflow.
- Accelerator Setup: runtime detection, explicit device execution, and gradient/optimizer checks.
- Building and Simulating Circuits: gates and result requests.
- Training Parameterized Circuits: objectives and optimizer updates.
- Hybrid Classical-Quantum Models: differentiable classical-quantum composition.
- Qubit Ordering and Result Conventions: probability-vector ordering and marginal selections.
- Wiki Directory: published guides.
- Release MPS entry points, MPS state, tensor-network entry points, and execution options.
- Installation and Compatibility
- Quick Start
- Install and Run
- Qubit Ordering and Result Conventions
- Runnable Examples
- Building and Simulating Circuits
- Training Parameterized Circuits
- Hybrid Classical-Quantum Models
- Core Concepts
- From Papers and Equations to FlagQuantum Code
- Reproducing Molecular Hydrogen VQE
- Fundamentals
- Tutorials
- Choosing a Simulation Method
- Accelerator Setup
- Distributed Simulation and Training
- Execution
- Circuit Compilation and Interoperability
- Noise and Open-System Simulation
- Quantum Error Correction Experiments
- Cloud and Quantum Hardware Execution
- QPU Digital Twins
- Advanced Topics
- Support and Contribution
All 21 guides are linked in the navigation sections. The Wiki Directory lists published guides and current repository resources.
- Troubleshooting and FAQ
- Reproducibility and Benchmarks
- Development and Contribution Guide
- Documentation Guide
Documentation, capability, and contribution links in this section follow current main; the guides above target 0.3.1.