A tensor is a multi-dimensional array, drawn as a shape with one leg per index. A k-qubit gate is a unitary 2k×2k matrix. Reshaped, it becomes a tensor with k input and k output legs, each of dimension 2. Connecting two legs means summing over that shared index. A whole circuit acting on |0…0〉 is therefore a network of such tensors joined along the qubit wires:
Contracting the network means evaluating this sum. It is done one pair of tensors at a time: two tensors A and B are replaced by C = Σshared A·B, whose legs are the union of the non-shared legs of A and B. The final result does not depend on the order of the pairs, but the cost depends on it dramatically. That dependence is what the applet lets you explore.
With Penrose split of controlled gates enabled (the default), a controlled-U gate is drawn through its operator-Schmidt decomposition CU = Σk∈{0,1} |k〉〈k| ⊗ Uk. The control becomes a COPY tensor δo,i,k, joined by a purple bond index k to a rank-3 target To,i,k = (Uk)o,i. For CX the target is the XOR tensor ⊕. A SWAP is simply a crossing of wires and has no tensor. With the option off, every gate is a single box of rank 2k.
| Mode | Network | Result |
|---|---|---|
| Output state | |0〉 on each input, output legs left open | rank-n tensor = state vector |ψ〉 (2n amplitudes) |
| Amplitude | outputs capped by 〈bq| (bitstring b, q[0] first) | scalar 〈b|U|0…0〉 |
| Expectation | U, then a layer of Paulis P, then U† (reversed, adjoint gates), capped by 〈0| | scalar 〈ψ|P|ψ〉 |
Pauli strings can be dense (ZZIX) or sparse (Z0 Z1 X3). Closing the network turns the result into a scalar, and the cheapest order is then
often much cheaper than producing the full state vector.
Contracting A (legs a∪s) with B (legs s∪b) over the shared legs s is a matrix product of size (|a|×|s|)·(|s|×|b|):
The statistics panel accumulates the total cost and tracks the largest intermediate, which is the quantity that limits simulations in practice. Its log2 (the rank of the largest tensor) is the contraction width of the chosen path. In the cost-profile chart the bars show log2 of each step's result size and the line shows the cumulative cost on a log scale. Contracting two tensors with no shared index (an outer product) is allowed, but usually expensive.
| Strategy | Rule | Typical behaviour |
|---|---|---|
| Greedy (min. size growth) | among all connected pairs pick the one minimising size(C) − size(A) − size(B), the same heuristic as opt_einsum's greedy | good general default |
| Greedy (min. step cost) | pick the pair with the cheapest contraction right now | very cheap early steps, can paint itself into a corner |
| Time sweep (Schrödinger) | absorb tensors left to right into one growing tensor | the growing tensor is the state vector, so memory is about 2n |
| Qubit-by-qubit sweep | absorb tensors row by row (qubit 0 first) | bad for deep circuits, where width grows with depth (Feynman-like) |
| Random | random connected pair | a baseline for comparison |
Enable suggest next pair to see (green dashed rings) which pair the selected strategy would contract next, then decide yourself.
| Action | How |
|---|---|
| Contract two tensors | click one (it gets a blue ring), then click the other. Hover the second one first to preview the cost |
| Contract along an index | click the edge |
| Inspect | hover any tensor or edge |
| Step / play / all | Space / P / A |
| Undo / reset | Z or Backspace / R, or click a step in the path list to rewind |
| Zoom / pan / fit | mouse wheel or +- / drag the background / F |
| Rebuild from editor | Ctrl+Enter in the QASM editor |
| Load a file | “Load .qasm” or drag & drop onto the canvas |
| Export | SVG (the diagram as currently shown) and Path JSON (tensors, contraction sequence, costs) |
URL parameters preload a configuration, for example
app.html?example=pauli&n=6&terms=10&mode=expectation&pauli=Z0 Z1.
Other keys are bits, decompose=0, strategy, plus any example parameter (steps, J, h, dt, seed, …).
OPENQASM 2.0; with include "qelib1.inc";, qreg/creg, several registers, and broadcasting (h q;, cx q, r;).qubit[n] q;, bit[n] c;, c = measure q;, stdgates.inc.id x y z h s sdg t tdg sx sxdg rx ry rz p u1 u2 u3 u U,
cx cy cz ch csx crx cry crz cp cu1 cu3 cu swap iswap rxx ryy rzz, ccx ccz cswap.gate name(params) a,b { … } definitions are inlined, and the tooltip says which definition a tensor came from.+ - * / ^, pi, sin cos tan exp ln sqrt.measure and barrier are accepted. Measurements are dropped, since the network represents the unitary part, and barriers are drawn as dashed lines.
reset is ignored with a warning. Classical control (if, loops) and gate modifiers are rejected with an error message pointing to the line.file:// or any static host (GitHub Pages and similar).js/tensor.js).
Data is carried while each intermediate stays below 222 elements (64 MB) and each step below 6·108 MACs.
Beyond that the engine continues symbolically: shapes, costs and memory remain exact, and dashed outlines mark tensors without values.js/tensor.js, simulate).node tests/test_core.js) that compares every example, mode and strategy against the state-vector reference.