ArcTN Rust-native tensor-network contraction planning
ArcTN repository ArcTN is a Rust tensor-network library for contraction-path optimization, slicing, and numerical execution, with Python integration for Quimb, opt_einsum, and other tools. ArcTN accepts tensor networks; it does not directly convert quantum-circuit tasks into networks. Build those networks with a frontend such as Quimb or supply them yourself.
On this page
Licensing and permitted use
Source code that the company is entitled to license is governed by the Arclight Non-Commercial Source-Available License 1.0, which prohibits commercial use and closed-source integration. This is not an OSI-approved open-source license. Commercial use requires separate authorization.
- Third-party dependencies retain their own licenses. Versions previously licensed under MIT / Apache retain the rights already granted; see third-party and historical licensing notices.
- Light / Heavy dynamic libraries are licensed separately. The source license for company code does not authorize their use or distribution. Their external distribution terms have not yet been determined, and neither the initial source release nor wheels built from that source include them.
See the ArcTN repository license section for full details. Commercial licensing and license inquiries: quill@arclightquantum.com.
Tensor networks and binary contraction trees
A tensor network describes how tensors connect through indices. With pairwise contraction, the computation can be represented by a binary contraction tree: leaves are input tensors, internal nodes are contractions, and the root is the final result.
One network can have different contraction trees. Consider the four-matrix product below: both orders produce the same result, but require different amounts of computation.
Connections between tensors
A: 2×8 · B: 8×2
C: 2×8 · D: 8×2
Contraction tree 1
(AB)(CD)
32 + 32 + 8 = 72 multiplications
Contraction tree 2
((AB)C)D
32 + 32 + 32 = 96 multiplications
A contraction tree describes how tensors combine; a contraction path lists the actual computation steps. For example, the first tree permits either or to be computed first, followed by their combination. Contraction-path optimization searches different trees and selects a lower-cost order according to objectives such as computation and memory traffic. Light / Heavy use heuristic search and do not guarantee global optimality. See tensor networks and contraction trees.
Planning and execution
Describe a tensor network with inputs, output, and size_dict, and specify an objective to optimize its contraction path.
When intermediate tensors are too large, set target_size to split a large contraction into smaller sliced contractions. target_size bounds the element count of any single contraction result within each slice: for example, one million float64 elements occupy about 8 MB. It does not bound total program memory; input arrays and temporary computation space must be counted separately.
ArcTN supports both planning alone and numerical computation. Save a path and slicing scheme as an ArcTNExecutionPlan, an intermediate representation (IR) between planning and execution. With arrays supplied, arctn_contract can plan and execute directly. Existing Quimb networks can also use ArcTN plans through ArcTNOptimizer.
For quantum circuits, use the Quimb frontend: build the circuit in Quimb and pass optimize=ArcTNOptimizer(...) when computing amplitudes or expectation values. ArcTN then optimizes the path without requiring you to assemble inputs, output, and size_dict manually. Quimb converts the task into a network and may simplify it before search; Quimb / Cotengra perform numerical contraction.
VQE, QAOA, and quantum machine learning
In these algorithms, a circuit frontend builds the tensor network for an amplitude, probability, or expectation value from a parameterized circuit. ArcTN optimizes its contraction path, and an outer classical optimizer updates circuit parameters from the results. With Quimb, integrate through optimize=ArcTNOptimizer(...) and execute through Quimb / Cotengra; alternatively, pass the network and arrays directly to arctn_contract.
After parameter updates, reuse an existing path if tensor order, index correspondence, dimensions, and output order remain unchanged. ArcTN is not a complete quantum machine-learning training framework: the application or framework handles losses, parameter gradients, and training loops. See the VQE and QAOA examples and path reuse when parameters change.
Circuit definition: Qiskit or Quimb
Other tensor-network methods: MPS state updates with optional truncation; PEPS update and contraction algorithms.
Other methods: dense state vectors or density matrices, stabilizer methods, and Pauli propagation.
inputs · output · size_dict
arrays (for execution)
Qiskit circuits require conversion; compatible OpenQASM 2 circuits can be imported by Quimb.
Contraction path + sliced indices
arctn_contract(…)
Or save the contraction order and slicing information: ArcTNExecutionPlan (IR)
Native CPU executor (Rust)
Or opt_einsum + NumPy / CuPy / Torch / JAX
ArcTNOptimizer.search()
↓ cotengra.ContractionTree
Quimb / Cotengra execution → autoray
CPU or GPU array backend
Contraction result: scalar or tensor
ArcTN handles tensor contractions, including contraction subproblems in MPS and PEPS algorithms. The caller manages state updates, truncation, sampling, and noise models.
From quantum circuit to contraction result
Two qubits start in and undergo H, , CNOT, and in sequence. To compute the final probability of measuring 11, first find the amplitude , then evaluate . The basis order here is .
Inspecting contractions step by step
Express the initial state, quantum gates, and terminal as tensors, then ask ArcTN to search for a path. The diagram matches circuit gates to tensors; click the next-step button to inspect the generated path and intermediate results.
The NumPy run control computes the amplitude and probability along this path and compares them with ArcTN, a direct contraction, and an independent state-vector calculation.
Quantum circuit
Time runs from left to right. The CNOT control is q0 and the target is q1; the terminal ⟨1| bras specify the output to compute.
Corresponding tensor network
Each edge is a shared index taking values 0 or 1. All indices are summed over, producing an amplitude.
How do quantum gates become tensors?
| Circuit object | Tensor | Indices and shape |
|---|---|---|
| Two initial states |0⟩ | T0 and T1, vectors [1, 0] | [a] and [b], 2 elements each |
| H, Ry(π/3) | T2 and T3, single-qubit gate matrices | [c,a] and [d,b], each 2 × 2 |
| CNOT | T4, with two output axes and two input axes | [e,f,c,d], 2 × 2 × 2 × 2 |
| Ry(π/4) | T5, a single-qubit gate matrix | [g,e], 2 × 2 |
| Two terminal ⟨1| bras specifying output 11 | T6 and T7, dual vectors [0, 1] | [g] and [f], 2 elements each |
Gate tensors place output axes before input axes. The 4 × 4 CNOT matrix is reshaped with reshape(2, 2, 2, 2) into four axes [e,f,c,d] without changing element order. With the terminal vectors connected, the network has no open indices, so the output argument passed to ArcTN is output=[].
This example constructs the tensors with NumPy. ArcTN plans from inputs, output and size_dict; it does not parse circuit diagrams or quantum gates.
SSA path [(4, 7), (0, 2), (8, 9), (1, 3), (5, 6), (10, 11), (12, 13)]
(4, 7) → 8Pending(0, 2) → 9Pending(8, 9) → 10Pending(1, 3) → 11Pending(5, 6) → 12Pending(10, 11) → 13Pending(12, 13) → 14Pending
T0…T7 are the circuit's input tensors; T8 onward are the results produced at each step. Left and right placement in the tree is for layout only; IDs match the actual path.
Binary contraction tree
Leaves are initial states, quantum gates, or terminal bras; each internal node represents a pairwise contraction.
Currently retained: T0 [a]T1 [b]T2 [c,a]T3 [d,b]T4 [e,f,c,d]T5 [g,e]T6 [g]T7 [f]
T0[a]
2 · float64| 1 |
| 0 |
T1[b]
2 · float64| 1 |
| 0 |
T2[c,a]
2 × 2 · float64| 0.707107 | 0.707107 |
| 0.707107 | -0.707107 |
T3[d,b]
2 × 2 · float64| 0.866025 | -0.5 |
| 0.5 | 0.866025 |
T4[e,f,c,d]
2 × 2 × 2 × 2 · float64| 1 | 0 | 0 | 0 |
| 0 | 1 | 0 | 0 |
| 0 | 0 | 0 | 1 |
| 0 | 0 | 1 | 0 |
T5[g,e]
2 × 2 · float64| 0.92388 | -0.382683 |
| 0.382683 | 0.92388 |
T6[g]
2 · float64| 0 |
| 1 |
T7[f]
2 · float64| 0 |
| 1 |
Elements are shown in input-axis order. The CNOT 4 × 4 table groups [e,f] as rows and [c,d] as columns. After selecting Next, matrices are reordered and grouped by that step's summed index.
ArcTN final amplitude A₁₁
| 0.701057 |
Probability of measuring 11: P(11) = |A₁₁|²
| 0.491481 |
Complete path: 42 FLOPs · Largest contraction result: 8 elements
Path and metrics
{
"ssa_path": [[4, 7], [0, 2], [8, 9], [1, 3], [5, 6], [10, 11], [12, 13]],
"linear_path": [[4, 7], [0, 2], [4, 5], [0, 1], [0, 1], [0, 1], [0, 1]],
"metrics": {
"log10_flops": 1.6232492903979006,
"log2_max_size": 3,
"log2_max_contraction_size": 4.700439718141093,
"log2_total_size": 4.392317422778761,
"log2_read_write": 6.266786540694901,
"log2_peak_size": 5.459431618637297,
"log2_n_slices": null,
"sliced_log10_flops_total": null,
"sliced_log2_max_size": null,
"sliced_log2_max_contraction_size": null,
"sliced_log2_peak_size": null,
"max_intermediate_log2_elements_per_slice": null
}
}Tables show up to 6 decimal places. Each step displays matrices in the native executor's axis order: columns of the left matrix and rows of the right matrix correspond to the summed index. After multiplication, the remaining indices are restored as axes of the result tensor.
Execute the path returned by ArcTN without searching again. The first run loads Pyodide and NumPy from jsDelivr; computation runs in the browser.
Not run yet. Select Run NumPy to compute in your browser.
Execution steps (pseudocode)
tensors = store input arrays by SSA ID(arrays)
for left, right, out, axis_orders, matrix_shapes, output_shape in contractions:
A = reorder axes and reshape as matrix(tensors[left], axis_orders.left, matrix_shapes.left)
B = reorder axes and reshape as matrix(tensors[right], axis_orders.right, matrix_shapes.right)
tensors[out] = reshape(A @ B, output_shape)
del tensors[left], tensors[right]
amplitude = take final tensor(tensors)
probability = abs(amplitude) ** 2
compare amplitude(amplitude, ArcTN result, direct contraction result, state-vector result)
return amplitude, probabilityThis example uses the default objective FLOPs + 64 × read/write. The step buttons display saved results without repeating the search; Run NumPy executes this contraction path. FLOPs count scalar multiplications in this example.
- Quick start — Begin with a smaller Python example.
Numerical execution and integrations
Direct contraction execution
With numerical arrays supplied, arctn_contract performs planning, optional slicing, and execution in one call. It defaults to the native ArcTN CPU executor, with explicit external-array backends also available. return_info=True returns planning and execution reports alongside the result.
- Numerical execution — Array types, contraction steps, and returned information.
- Slicing example — Set
target_sizeand choose Fixed or Dynamic slicing.
Using Quimb
Pass an ArcTNOptimizer to Quimb through optimize= to use ArcTN path search. ArcTNOptimizer.search() returns a ContractionTree with the path and slicing indices; Quimb / Cotengra perform subsequent numerical contraction.
Saving and reusing paths
With the same structure and dimensions, changing array values does not require a new path search. Use arctn_plan to obtain the path and slicing information, and save it for later contractions.
Core capabilities
| Capability | Implementation | Further reading |
|---|---|---|
| Path generation | Greedy, randomized greedy, dynamic programming, and recursive hypergraph bisection | Path-search algorithms |
| Path improvement | Subtree reconfiguration, simulated annealing, replica exchange, and fixed-leaf-order dynamic programming | Annealing and replica exchange, subtree reconfiguration |
| Automatic path optimization | Light / Heavy presets schedule generation and improvement | Planning pipeline |
| Slicing | Fixed preserves the path; Dynamic permits local path changes | Slicing |
| Numerical execution | Native CPU executor or an explicit opt_einsum array backend | Execution backends |
| Parallel and distributed execution | Rayon on one host and tnmpi for distributed slices |
Parallel settings |
Scope and boundaries
The native executor uses CPUs; CuPy, PyTorch, and JAX arrays run through their explicitly selected external backends.
Slicing does not perform low-rank truncation. Currently only internal indices are sliced: all values are enumerated and their results summed. Output indices remain intact and are not sliced.
Light, Heavy, and most local-search methods are heuristic and do not guarantee global optimality. Dynamic programming is suitable for small subproblems; see its search scope.
max_timeis checked at algorithm checkpoints, not enforced as a process timeout. Use an external process or job scheduler for a strict limit.