contractor 使用#
概述#
在本教程中,我们将演示如何利用不同类型的张量网络 contractor 进行电路仿真,以实现更好的时空消耗平衡。contractor 的定制是 TensorCircuit 库的主要亮点之一,因为更好的 contractor 可以更好地利用 TensorNetwok 仿真引擎的强大功能。
设置#
[1]:
import tensorcircuit as tc
import numpy as np
import cotengra as ctg
import opt_einsum as oem
K = tc.set_backend("tensorflow")
试验体系#
我们为两个电路提供张量网络,并测试这两个系统的收缩效率,第一个系统小,第二个系统大。
[2]:
# 获取小系统的量子态
def small_tn():
n = 10
d = 4
param = K.ones([2 * d, n])
c = tc.Circuit(n)
c = tc.templates.blocks.example_block(c, param, nlayers=d)
return c.state()
[3]:
# 获得对超大型系统的期望
def large_tn():
n = 60
d = 8
param = K.ones([2 * d, n])
c = tc.Circuit(n)
c = tc.templates.blocks.example_block(c, param, nlayers=d, is_split=True)
#
return c.expectation([tc.gates.z(), [n // 2]], reuse=False)
opt-einsum#
opt-einsum 提供了几个 contractor 优化器,并与 TensorNetwork 包一起提供。 由于 TensorCircuit 建立在 TensorNetwork 之上,我们可以使用这些简单的 contractor 优化器。 尽管对于任何中等系统,只有贪婪优化器有效,但其他优化器具有指数缩放并且在电路仿真场景中失效。我们总是为 contractor 系统设置contraction_info=True
(默认为False
),它将打印包括 contraction size、flops 和 writes 在内的收缩信息摘要。 有关这些指标的定义,另请参阅 cotengra 文档。
[4]:
# 如果我们什么都不设置,默认优化器是贪婪的,即:
tc.set_contractor("greedy", debug_level=2, contraction_info=True)
# 我们设置 debug_level=2 以不真正运行收缩计算
# 即通过设置debug_level>0,只有收缩信息和返回形状正确,结果错误
[4]:
functools.partial(<function custom at 0x7fd5a0a3d430>, optimizer=<function contraction_info_decorator.<locals>.new_algorithm at 0x7fd588e281f0>, memory_limit=None, debug_level=2)
[5]:
small_tn()
------ contraction cost summary ------
log10[FLOPs]: 5.132 log2[SIZE]: 11 log2[WRITE]: 13.083
[5]:
<tf.Tensor: shape=(1024,), dtype=complex64, numpy=
array([0.+0.j, 0.+0.j, 0.+0.j, ..., 0.+0.j, 0.+0.j, 0.+0.j],
dtype=complex64)>
[6]:
large_tn()
------ contraction cost summary ------
log10[FLOPs]: 17.766 log2[SIZE]: 44 log2[WRITE]: 49.636
[6]:
<tf.Tensor: shape=(), dtype=complex64, numpy=0j>
[7]:
# 我们可以在 opt-einsum 中使用更多花哨的 contractor,他们不在 TensorNetwork 中定义
# custom_stateful 用于具有一次性生命周期的路径求解器的收缩路径查找器
tc.set_contractor(
"custom_stateful",
optimizer=oem.RandomGreedy,
max_time=60,
max_repeats=128,
minimize="size",
debug_level=2,
contraction_info=True,
)
[7]:
functools.partial(<function custom_stateful at 0x7fd5a0a3d4c0>, optimizer=<class 'opt_einsum.path_random.RandomGreedy'>, opt_conf=None, contraction_info=True, debug_level=2, max_time=60, max_repeats=128, minimize='size')
[8]:
small_tn()
------ contraction cost summary ------
log10[FLOPs]: 4.925 log2[SIZE]: 10 log2[WRITE]: 12.531
[8]:
<tf.Tensor: shape=(1024,), dtype=complex64, numpy=
array([0.+0.j, 0.+0.j, 0.+0.j, ..., 0.+0.j, 0.+0.j, 0.+0.j],
dtype=complex64)>
[9]:
large_tn()
------ contraction cost summary ------
log10[FLOPs]: 11.199 log2[SIZE]: 26 log2[WRITE]: 28.183
[9]:
<tf.Tensor: shape=(), dtype=complex64, numpy=0j>
cotengra#
对于更高级的 contractor,我们向市场上的 sota contractor 优化器寻求帮助:cotengra
[10]:
opt = ctg.ReusableHyperOptimizer(
methods=["greedy", "kahypar"],
parallel=True,
minimize="write",
max_time=120,
max_repeats=1024,
progbar=True,
)
tc.set_contractor(
"custom", optimizer=opt, preprocessing=True, contraction_info=True, debug_level=2
)
## 有关 cotengra 优化器的更多设置,请参阅参考资料
## https://cotengra.readthedocs.io/en/latest/advanced.html
## preprocessing=True 将所有单量子比特门合并到相邻的双量子比特门
[10]:
functools.partial(<function custom at 0x7fd5a0a3d430>, optimizer=<function contraction_info_decorator.<locals>.new_algorithm at 0x7fd588e28ee0>, memory_limit=None, debug_level=2, preprocessing=True)
[11]:
small_tn()
log2[SIZE]: 10.00 log10[FLOPs]: 4.90: 100%|█████████████████████████████████████████| 1024/1024 [00:28<00:00, 35.45it/s]
------ contraction cost summary ------
log10[FLOPs]: 4.900 log2[SIZE]: 10 log2[WRITE]: 12.255
[11]:
<tf.Tensor: shape=(1024,), dtype=complex64, numpy=
array([0.+0.j, 0.+0.j, 0.+0.j, ..., 0.+0.j, 0.+0.j, 0.+0.j],
dtype=complex64)>
[12]:
large_tn()
log2[SIZE]: 20.00 log10[FLOPs]: 9.50: 4%|█▊ | 43/1024 [02:09<49:22, 3.02s/it]
------ contraction cost summary ------
log10[FLOPs]: 9.501 log2[SIZE]: 20 log2[WRITE]: 24.090
[12]:
<tf.Tensor: shape=(), dtype=complex64, numpy=0j>
我们也可以在 cotengra 找到路径之后应用 subtree reconf,这通常会进一步(并且通常会大大)为 contraction 改善 flops 和 writes。实际上, subtree reconf 后期处理通常比增加优化器的搜索时间或重复次数更重要。
[13]:
opt = ctg.ReusableHyperOptimizer(
minimize="combo",
max_repeats=1024,
max_time=240,
progbar=True,
)
def opt_reconf(inputs, output, size, **kws):
tree = opt.search(inputs, output, size)
tree_r = tree.subtree_reconfigure_forest(
progbar=True, num_trees=10, num_restarts=20, subtree_weight_what=("size",)
)
return tree_r.get_path()
tc.set_contractor(
"custom",
optimizer=opt_reconf,
contraction_info=True,
preprocessing=True,
debug_level=2,
)
[13]:
functools.partial(<function custom at 0x7fd5a0a3d430>, optimizer=<function contraction_info_decorator.<locals>.new_algorithm at 0x7fd58c832700>, memory_limit=None, debug_level=2, preprocessing=True)
[14]:
small_tn()
log2[SIZE]: 10.00 log10[FLOPs]: 4.87: 100%|█████████████████████████████████████████| 1024/1024 [02:29<00:00, 6.86it/s]
log2[SIZE]: 10.00 log10[FLOPs]: 4.86: 100%|█████████████████████████████████████████████| 20/20 [00:31<00:00, 1.57s/it]
------ contraction cost summary ------
log10[FLOPs]: 4.859 log2[SIZE]: 10 log2[WRITE]: 12.583
[14]:
<tf.Tensor: shape=(1024,), dtype=complex64, numpy=
array([0.+0.j, 0.+0.j, 0.+0.j, ..., 0.+0.j, 0.+0.j, 0.+0.j],
dtype=complex64)>
[15]:
large_tn()
log2[SIZE]: 21.00 log10[FLOPs]: 9.62: 9%|███▉ | 93/1024 [04:04<40:49, 2.63s/it]
log2[SIZE]: 17.00 log10[FLOPs]: 8.66: 100%|█████████████████████████████████████████████| 20/20 [03:08<00:00, 9.44s/it]
------ contraction cost summary ------
log10[FLOPs]: 8.657 log2[SIZE]: 17 log2[WRITE]: 25.035
[15]:
<tf.Tensor: shape=(), dtype=complex64, numpy=0j>
我们也可以直接提取张量网络用于电路或可观测计算,我们可以使用我们喜欢的任何方法进行收缩。此外,所有这些 contractor 或我们定制的外部收缩仍然可以与 jit,自动微分等兼容。具体来说,收缩路径求解器虽然需要一些时间成本,但由于 jit 基础设施只测量一次(注意,为了演示使用,我们在这里不使用K.jit
装饰我们的收缩函数)。