---
title: "使用 Quimb 构建并执行张量网络"
description: "让 Quimb 负责量子电路任务或自定义 TensorNetwork，让 ArcTN 负责路径规划与可选切片。"
eyebrow: "教程"
---

## 组件职责 {#responsibilities}

本教程展开的是“接入现有生态”路线：Quimb 负责把任务变成待收缩张量网络，ArcTN 负责路径规划和可选切片，Quimb/Cotengra 负责按返回的收缩树执行。若已经直接持有结构和数组，也可以改用 `arctn_contract(..., backend="native")`，不经过 Quimb/Cotengra。

| 组件 | 负责 | 不负责 |
| --- | --- | --- |
| Quimb | 电路和张量网络构造、任务化简、数值 contraction、数组后端 | 替 ArcTN 决定其内部搜索策略 |
| ArcTN | 收缩路径、完整路径指标，以及通过 ArcTNOptimizer.search 或 arctn\_tree 请求的切片腿 | 量子门语义、振幅定义或采样逻辑 |
| Cotengra | 承载 ContractionTree，并执行 tree-aware contraction | ArcTN 的 Light / Heavy 搜索 |

这条接口不仅适用于量子电路。只要 Quimb `TensorNetwork` 满足 ArcTN 当前的输入约束，就可以把 `ArcTNOptimizer` 传给 `optimize=`：同名 index 的维度必须一致，`output` 不能重复，而且同一个 index 在单个输入张量内至多出现两次。

## 安装 {#install}

```bash
git clone https://github.com/2hengy1/arctn.git
cd arctn/pybind
python -m pip install -e '.[quimb]'
```

从源码安装完整 Quimb 集成时，`quimb` extra 会安装 Quimb、Cotengra 和 opt\_einsum。若使用 CuPy、PyTorch 或 JAX，还需要另行安装与本机硬件和驱动匹配的数组库。

## 从 Quimb Circuit 计算一个振幅 {#circuit}

```python
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,
    simplify_sequence="ADCRS",
)
print(amplitude)
```

调用 `amplitude` 时，Quimb 先把振幅任务变成张量网络并执行局部化简，然后把网络的 `inputs`、`output` 和 `size_dict` 交给 ArcTN。对于非平凡网络，Cotengra/Quimb 会调用 `ArcTNOptimizer.search()`；ArcTN 返回 `ContractionTree`，随后由 Quimb/Cotengra 执行。平凡收缩可能绕过优化器，详见下文。

| Quimb Circuit 任务 | ArcTN 的作用 | 结果语义 |
| --- | --- | --- |
| amplitude | 规划振幅网络 | 给定 bitstring 的复振幅 |
| local\_expectation | 规划局域期望值网络 | 局域算符期望值 |
| compute\_marginal / partial\_trace | 规划边缘概率或约化态网络 | 数组或密度矩阵 |
| sample | 为条件采样过程中产生的网络规划路径 | 指定数量的 bitstrings |
| to\_dense | 规划完整态矢量网络 | 稠密态矢量 |

> **路径按网络几何复用**
>
> 不同任务生成的网络不同，同一个 Circuit 不等于只需要一条可供所有任务复用的路径。是否能够复用，取决于待收缩网络的 inputs、output 和尺寸是否相同。

## 理解 Quimb 的任务化简 {#simplification}

Quimb 的 `amplitude`、`local_expectation`、`partial_trace`、`compute_marginal` 和常用 sampling 入口当前默认使用 `simplify_sequence="ADCRS"`；`to_dense` 当前默认使用 `R`。这些都是 Quimb 在 ArcTN 寻路之前执行的局部张量网络化简，因此 ArcTN 看到的是化简后的 residual tensor network，而不是原始门列表。下表解释本教程振幅示例使用的 `ADCRS`。

| 字母 | Quimb 方法 | 作用 |
| --- | --- | --- |
| A | antidiag\_gauge | 翻转与反对角结构相连的 bond gauge，为后续 diagonal reduction 创造条件 |
| D | diagonal\_reduce | 折叠对角张量的冗余轴；可能产生由多个张量共享的 hyperedge |
| C | column\_reduce | 识别某个指标只有一个取值使张量非零的情况，并将该指标固定为这个取值 |
| R | rank\_simplify | 收缩不会增大张量阶数（指标数）的相邻张量；该选择规则只需要网络结构 |
| S | split\_simplify | 在给定数值容差内寻找低秩分解并拆分张量 |

Quimb 按给定字母顺序循环这些步骤，直到张量数和 index 数不再减少。它与 ArcTN 内部可能执行的结构处理不是同一个 pass；ArcTN 从 Quimb 交来的 residual tensor network 开始规划。

```python
rehearsal = circ.amplitude_rehearse(
    b="0" * n,
    simplify_sequence="ADCRS",
    optimize=optimizer,
)
tn = rehearsal["tn"]
tree = rehearsal["tree"]
print(len(tn), tree.total_flops())
```

- 振幅、局域期望值、约化态和 sampling 等任务通常可以保留 `ADCRS`；`to_dense` 应先保留其 `R` 默认值，除非实验协议明确要求统一化简序列。
- 若要测量不经过 Quimb 可选局部化简的网络寻路，或输入已经按实验协议完成这些化简，可以显式传 `simplify_sequence=""`。电路建网与任务特定处理仍会执行。
- 浅电路可能被化简为单个张量甚至标量。这是任务已经被直接化简的合法结果，不表示寻路失败。

> **Normalization exponent**
>
> 在 Quimb 内完成建网和收缩时，normalization exponent 已由 Quimb 管理；再次手工乘缩放因子会造成重复缩放。

## 通过 ArcTNOptimizer 直接切片 {#slicing}

```python
from arctn import ArcTNOptimizer

tn = circ.amplitude_tn(
    "0" * n, simplify_sequence="ADCRS"
)
output = tuple(tn.outer_inds())

optimizer = ArcTNOptimizer(
    preset="heavy", seed=0,
    target_size=2**28,
    slicing_mode="fixed",
)
value = tn.contract(
    all, output_inds=output, optimize=optimizer,
)
```

对于这条调用链中的非平凡收缩，Quimb/Cotengra 会调用 `ArcTNOptimizer.search()`。返回的 `ContractionTree` 同时携带 contraction path 和 sliced indices；Quimb/Cotengra 随后执行各个 slice 并求和，Quimb 则应用网络的 normalization exponent 所记录的缩放因子。`fixed` 保持当前 objective 下选出的最佳未切片候选路径不变；`dynamic` 允许切片后做局部路径重构。平凡网络可能绕过优化器，详见下文。

> **target\_size 的单位**
>
> `target_size` 是每个 slice 中二元收缩结果元素数的硬约束；单张量网络则检查最终输出。它不限制全部临时张量，也不是字节数、RSS、VRAM 或整个进程的内存上限。

同一个 `ArcTNOptimizer` 同时支持两种上层协议：opt\_einsum 直接调用 `__call__` 时只能返回 linear path；Cotengra/Quimb 调用 `search()` 时返回 `ContractionTree`，因此可以保留 sliced indices。若调用方需要先取得树再决定何时执行，也可以显式使用 `arctn_tree`。

> **平凡网络的调用边界**
>
> Cotengra 对只有一到两个输入张量的平凡收缩可能直接构造树，不调用外部 optimizer。必须对这类网络严格施加 `target_size` 时，应直接使用 `arctn_tree` 或 `arctn_contract`。

## 手工组装任意 TensorNetwork {#manual-network}

```python
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"), tags={"A"})
tb = qtn.Tensor(rng.normal(size=(3, 4)), inds=("x", "y"), tags={"B"})
tc = qtn.Tensor(rng.normal(size=(4, 5)), inds=("y", "z"), tags={"C"})
td = qtn.Tensor(rng.normal(size=(5, 2)), inds=("z", "b"), tags={"D"})
tn = qtn.TensorNetwork([ta, tb, tc, td])

result = tn.contract(
    all,
    output_inds=("a", "b"),
    optimize=ArcTNOptimizer(preset="light", seed=0),
)
array = result.data
print(array.shape)  # (2, 2)
```

相同的 index 名表示连接：`x`、`y`、`z` 在收缩时求和；`output_inds=("a", "b")` 保留两个开放指标，并规定结果轴的顺序。每个同名 index 在所有张量上的维度必须一致。

- 标量收缩使用 `output_inds=()`；有开放指标时，显式的 `output_inds` 用于固定结果指标顺序。
- 标准张量网络通常让内部 index 跨两个张量出现。若一个 index 跨三个或更多张量出现，它是 hyperedge，必须显式指定 `output_inds`，并确认执行后端支持相应收缩。
- ArcTN 允许同一个 index 在单个输入张量中出现两次，以表示 trace 或 diagonal；出现三次或更多目前会被拒绝。Quimb 能表达更一般的重复指标，因此交给 ArcTN 之前要先把这类结构显式化简为受支持形式。
- `tags` 用于选择、绘图和组织张量，不参与 ArcTN 的路径代价计算。

## 高级用法：拆出网络并交给 arctn\_contract {#explicit-execution}

```python
from arctn import arctn_contract

tensors = list(tn)
inputs = [tuple(tensor.inds) for tensor in tensors]
output = ("a", "b")
size_dict = dict(tn.ind_sizes())
arrays = [tensor.data for tensor in tensors]

value = arctn_contract(
    inputs, output, size_dict, arrays,
    preset="heavy", backend="numpy",
)
exponent = float(getattr(tn, "exponent", 0.0) or 0.0)
value = value * (10.0 ** exponent)
```

这条路线适合需要 ArcTN `native` executor、显式外部后端，或完整 `return_info` 报告的场景。若网络曾由 Quimb 在 equalize-norms 模式下化简，`tn.exponent` 保存的是独立缩放因子的十进制指数。提取数组并在 Quimb 之外完成收缩后，将结果乘以一次 `10.0 ** tn.exponent`。

## 实验和性能比较边界 {#comparison}

| 比较对象 | 固定条件 | 独立记录项 |
| --- | --- | --- |
| 路径质量 | 同一个化简后 TensorNetwork、同一 objective | 寻路时间、FLOPs、read/write、最大中间张量 |
| 端到端任务 | 同一 Circuit 任务、化简序列、dtype 和 backend | 建网、化简、寻路、执行和同步后的总时间 |
| 切片执行 | 同一 target\_size 与结果精度 | slice 数、每片峰值、总 FLOPs、设备完成时间 |

GPU 后端可能异步执行；只测 Python 调用返回时间会漏掉设备工作。比较 CuPy 或 JAX 时，应在计时终点执行对应后端的同步操作。

- [Quimb Quantum Circuits](https://quimb.readthedocs.io/en/latest/circuit/circuit.html) — Circuit 任务、化简与 optimize 接口
- [Quimb Tensor Contraction](https://quimb.readthedocs.io/en/latest/tensor/tensor-contraction.html) — TensorNetwork contraction 与输出指标
- [切片执行](/docs/slicing-execution)
- [执行后端](/docs/execution-backends)
