Using ArcTN with Quimb
Optimize amplitude and expectation-value calculations in Quimb with ArcTN, integrate variational algorithms, and optionally limit slice sizes.
On this page
Connecting ArcTN
Pass ArcTNOptimizer as the Quimb optimize argument to optimize contraction order with ArcTN. Quimb constructs the tensor network, ArcTN returns a ContractionTree with a path and optional slicing information, and Quimb/Cotengra performs the numerical computation.
Installation
After installing the ArcTN Python package containing Light / Heavy, install the Quimb integration dependencies in the same virtual environment:
python -m pip install quimb cotengra opt_einsum
See Installation for ArcTN setup and environment requirements.
Computing a quantum-circuit amplitude
Build the circuit, then specify ArcTNOptimizer for the amplitude calculation:
import numpy as np
import quimb.tensor as qtn
from arctn import ArcTNOptimizer
n = 8
rng = np.random.default_rng(0)
circ = qtn.Circuit(n)
for depth in range(6):
for q in range(n):
circ.apply_gate("RY", float(rng.uniform(0, 2 * np.pi)), q)
for q in range(depth % 2, n - 1, 2):
circ.apply_gate("CNOT", q, q + 1)
optimizer = ArcTNOptimizer(preset="light", seed=0)
amplitude = circ.amplitude("0" * n, optimize=optimizer)
print(amplitude)
The output is the amplitude for an all-zero final bitstring when starting from the all-zero initial state, . The probability of measuring this bitstring is its squared modulus. ArcTN optimizes the contraction order of the network used for this calculation.
Quimb preprocessing
Quimb may simplify the task network before passing it to ArcTN. Its defaults are usually sufficient. To disable optional simplification, set simplify_sequence="" in the circuit task call. This parameter controls simplification before Quimb passes on the network.
If simplification reduces the task to a simple contraction, Quimb/Cotengra may compute it directly without calling ArcTN. See the Quimb circuit documentation for its simplification rules.
Adding a slice-size limit
Using the circuit above, convert the amplitude task into a tensor network and set target_size:
tn = circ.amplitude_tn("0" * n)
optimizer = ArcTNOptimizer(
preset="heavy", seed=0,
target_size=2**28,
slicing_mode="fixed",
)
value = tn.contract(
all, output_inds=(), optimize=optimizer,
)
Quimb/Cotengra obtains a contraction tree containing the path and sliced indices through ArcTNOptimizer.search(), then computes and sums the slices. fixed preserves the searched path; dynamic permits local path changes. See Fixed and dynamic slicing.
The largest binary contraction result within each slice must not exceed target_size elements; this is not a total-memory cap. Slicing may be unnecessary if the original path already satisfies the limit.
Simple contractions with only one or two input tensors may bypass the external optimizer. If such networks must also enforce target_size, call arctn_tree or arctn_contract directly.
Expectation values for VQE and QAOA
A variational quantum eigensolver (VQE) prepares a state with a parameterized circuit and computes the Hamiltonian expectation:
Here is a product of Pauli operators and its coefficient. Quimb constructs an expectation network from the circuit and operator, containing the state , operator , and conjugate-transposed state , without first building the full state vector. Circuit.local_expectation accepts ArcTNOptimizer through its optimize argument.
The quantum approximate optimization algorithm (QAOA) also computes expectation values, but its objective depends on the combinatorial optimization problem. Both methods can delegate contraction-order search for each expectation calculation to ArcTN.
The pseudocode below illustrates the relationship between energy evaluation and parameter optimization without expanding library-specific calls.
Inputs: initial parameters theta_initial and Hamiltonian terms (c_j, P_j)
function energy(theta):
circuit = build a parameterized circuit using theta
value = 0
For each Hamiltonian term (c_j, P_j):
network = build an expectation-value tensor network from circuit and P_j
tree = use ArcTN to optimize the network path, adding slicing if needed
expectation = contract network along tree, summing slices if needed
value = value + c_j * expectation
return the real part of value
theta_star = minimize energy from theta_initial using a classical optimizer
With Quimb, call Circuit.local_expectation with its optimize argument set to ArcTNOptimizer to perform the network construction, path optimization, and numerical computation shown in the pseudocode. The application separately invokes the classical optimizer: ArcTN selects contraction order, not circuit parameters. Expectations here are computed directly, without finite-shot measurement sampling.
| Application | Circuit calculation returns | Outer algorithm does |
|---|---|---|
| VQE | Expectation of each Hamiltonian term | Weighted summation and energy minimization |
| QAOA | Expectation of the problem cost Hamiltonian | Maximize or minimize the problem objective and update , |
| Quantum machine learning | Selected observable expectations or output probabilities | Compute model loss and update trainable parameters |
To limit intermediates, add target_size=2**20, slicing_mode="fixed" to ArcTNOptimizer, with the same meaning as in the slicing example. Quimb/Cotengra executes the returned slices; this setting is not a process-wide memory cap.
An ArcTNOptimizer object does not cache paths. Reuse is controlled by the caller. For parameterized networks with fixed structure, explicitly save a path and execute it repeatedly; see Reusing paths as parameters change. Different observables may produce different networks and cannot automatically share a path.
Parameter gradients and other frontends
ArcTN path optimization does not compute circuit-parameter gradients. Gradient-free optimization can use the energy function above directly. When gradients are needed, eligible gates can use the parameter-shift rule: evaluate expectations at shifted parameters and combine them into gradients. Frontend-supported automatic differentiation is another option. Native Rust execution does not provide parameter gradients; with external array backends, verify that the operations actually used support differentiation. See Quimb optimization documentation.
Other circuit frontends must first convert the task to a tensor network. Callers using Quimb or opt_einsum contraction interfaces can reuse the existing integration. Framework-specific device or execution interfaces require an adapter. ArcTNOptimizer itself is neither a Qiskit Estimator or Sampler nor a PennyLane device.
Building a tensor network
Build a four-tensor network from arrays and indices:
import numpy as np
import quimb.tensor as qtn
from arctn import ArcTNOptimizer
rng = np.random.default_rng(0)
ta = qtn.Tensor(rng.normal(size=(2, 3)), inds=("a", "x"))
tb = qtn.Tensor(rng.normal(size=(3, 4)), inds=("x", "y"))
tc = qtn.Tensor(rng.normal(size=(4, 5)), inds=("y", "z"))
td = qtn.Tensor(rng.normal(size=(5, 2)), inds=("z", "b"))
tn = qtn.TensorNetwork([ta, tb, tc, td])
result = tn.contract(
all, output_inds=("a", "b"),
optimize=ArcTNOptimizer(preset="light", seed=0),
)
print(result.data.shape) # (2, 2)
Matching index names indicate connections and must have consistent dimensions. output_inds=("a", "b") retains two output indices in the specified axis order; use output_inds=() for a scalar result. See Tensor networks and contraction paths for input requirements.
Executing the contraction with ArcTN
You can also extract structure and arrays from a Quimb network and pass them to arctn_contract for path optimization and numerical execution:
from arctn import arctn_contract
tensors = list(tn)
value = arctn_contract(
[tuple(t.inds) for t in tensors],
("a", "b"),
dict(tn.ind_sizes()),
[t.data for t in tensors],
preset="heavy", backend="numpy",
)
value = value * (10.0 ** float(tn.exponent))
backend="numpy" uses the NumPy array backend; native is also available. See Execution backends. If Quimb stores a separate scale factor in tn.exponent, multiply the extracted-array result by 10**tn.exponent, as in the final line above. Without separate scaling, this exponent is zero. Quimb handles this when using tn.contract, so do not multiply it in again.
Related documentation
- Python API:
ArcTNOptimizerparameters and return values. - Third-party integrations: differences between path and contraction-tree interfaces.
- Quimb Tensor Contraction: Quimb tensor-network contraction interfaces.
- Quimb expectation calculations: operators and outputs of
Circuit.local_expectation. - Timing and CPU usage: distinguish path-optimization time from complete computation time.