密度矩阵和混态演化#
概述#
TensorCircuit 提供了两种含噪声、混态量子演化的方法。 \(n\) 量子比特的全密度矩阵模拟是通过使用 tc.DMCircuit(n)
提供的,然后将量子操作——包括幺正门以及由 Kraus 算子指定的一般量子操作——添加到电路中。 相对于通过 tc.Circuit
对 \(n\) 个量子比特进行纯态模拟,全密度矩阵模拟会占据两倍内存,因此可模拟的最大系统大小将是纯态情况下可以模拟的一半。 内存需求较小的选项是使用标准的 tc.Circuit(n)
对象并通过蒙特卡罗轨迹方法随机模拟开放系统演化。
设置#
[1]:
import numpy as np
import tensorcircuit as tc
K = tc.set_backend("tensorflow")
使用 tc.DMCircuit
进行密度矩阵模拟#
我们在下面通过考虑单个量子比特上的简单电路来说明这种方法,该电路将对应于 \(\vert{0}\rangle\) 状态和最大混合状态的概率混合的混合状态作为输入 \(\rho(\alpha) = \alpha\vert 0\rangle \langle 0\vert + (1-\alpha)I/2。\)
然后这个状态通过一个应用 \(X\) 门的电路,然后是对应于带有参数 \(\gamma\) 的振幅阻尼通道 \(\mathcal{E}_\gamma\) 的量子操作。 这有 Kraus 运算符\(K_0 = \begin{pmatrix} 1 & 0 \\ 0 & \sqrt{1-\gamma} \end{pmatrix}, \quad K_1 = \begin{pmatrix} 0 & \sqrt{\gamma} \\ 0 & 0 \end{pmatrix}\) 因此,该电路导致演化 \(\rho(\alpha) \xrightarrow[]{X} X\rho(\alpha)X\xrightarrow[]{\mathcal{E}_\gamma}\sum_{i=0}^1 K_i X\rho(\alpha)X K_i^\dagger\)
为了在 TensorCircuit 中模拟这一点,我们首先创建一个 tc.DMCircuit
(密度矩阵电路)对象并使用 dminputs
可选参数设置输入状态 (请注意,如果将纯状态输入提供给 tc.DMCircuit ,这应该通过
inputs`` 可选参数来完成)。
\(\rho(\alpha)\) 有矩阵形式 \(\rho(\alpha) = \begin{pmatrix} \frac{1+\alpha}{2} & \\ & \frac{1-\alpha}{2} \end{pmatrix},\) 因此(取 \(\alpha=0.6\))我们如下初始化密度矩阵电路。
为了实现诸如振幅阻尼通道之类的通用量子操作,我们使用了 general_kraus
,并提供了相应的 Kraus 运算符列表。
[2]:
def rho(alpha):
return np.array([[(1 + alpha) / 2, 0], [0, (1 - alpha) / 2]])
input_state = rho(0.6)
dmc = tc.DMCircuit(1, dminputs=input_state)
dmc.x(0)
def amp_damp_kraus(gamma):
K0 = np.array([[1, 0], [0, np.sqrt(1 - gamma)]])
K1 = np.array([[0, np.sqrt(gamma)], [0, 0]])
return K0, K1
K0, K1 = amp_damp_kraus(0.3)
dmc.general_kraus([K0, K1], 0) # 将具有 Kraus 算子 [K0,K1] 的通道应用于 qubit 0
[3]:
# 得到输出密度矩阵
dmc.state()
[3]:
<tf.Tensor: shape=(2, 2), dtype=complex64, numpy=
array([[0.44+0.j, 0. +0.j],
[0. +0.j, 0.56+0.j]], dtype=complex64)>
[4]:
# 将期望作为电路对象评估
print(dmc.expectation_ps(z=[0]), dmc.measure(0))
tf.Tensor((-0.11999999+0j), shape=(), dtype=complex64) (<tf.Tensor: shape=(1,), dtype=float32, numpy=array([1.], dtype=float32)>, -1.0)
在上面的例子中,我们手动输入振幅阻尼通道的 Kraus 算子,以说明实现常见量子通道的一般方法。 事实上,TensorCircuit 包含用于返回许多公共通道的 Kraus 算子的内置方法,包括振幅阻尼、去极化、相位阻尼和复位通道。
[5]:
# 一组内置量子通道
for k in dir(tc.channels):
if k.endswith("channel"):
print(k)
amplitudedampingchannel
depolarizingchannel
phasedampingchannel
resetchannel
[6]:
dmc = tc.DMCircuit(2)
dmc.h(0)
gamma = 0.2
K0, K1 = tc.channels.phasedampingchannel(gamma)
dmc.general_kraus([K0, K1], 0)
dmc.state()
[6]:
<tf.Tensor: shape=(4, 4), dtype=complex64, numpy=
array([[0.49999997+0.j, 0. +0.j, 0.4472136 +0.j, 0. +0.j],
[0. +0.j, 0. +0.j, 0. +0.j, 0. +0.j],
[0.4472136 +0.j, 0. +0.j, 0.49999994+0.j, 0. +0.j],
[0. +0.j, 0. +0.j, 0. +0.j, 0. +0.j]],
dtype=complex64)>
[7]:
# 或者我们可以直接使用下面的API进行速记
dmc = tc.DMCircuit(2)
dmc.h(0)
gamma = 0.2
dmc.phasedamping(0, gamma=0.2)
dmc.state()
[7]:
<tf.Tensor: shape=(4, 4), dtype=complex64, numpy=
array([[0.49999997+0.j, 0. +0.j, 0.4472136 +0.j, 0. +0.j],
[0. +0.j, 0. +0.j, 0. +0.j, 0. +0.j],
[0.4472136 +0.j, 0. +0.j, 0.49999994+0.j, 0. +0.j],
[0. +0.j, 0. +0.j, 0. +0.j, 0. +0.j]],
dtype=complex64)>
自动微分、即时编译兼容性#
tc.DMCircuit
和 tc.Circuit
一样也兼容 ML 范式,例如 自动微分、即时编译和 vmap。请参见下面的示例。
[8]:
n = 3
nbatch = 2
def loss(params, noisep):
c = tc.DMCircuit(n)
for i in range(n):
c.rx(i, theta=params[i])
for i in range(n):
c.depolarizing(i, px=noisep, py=noisep, pz=noisep)
return K.real(K.sum([c.expectation_ps(z=[i]) for i in range(n)]))
loss_vvg = K.jit(
K.vectorized_value_and_grad(loss, argnums=(0, 1), vectorized_argnums=(0))
)
[9]:
vs, (gparams, gnoisep) = loss_vvg(0.1 * K.ones([nbatch, n]), 0.1 * K.ones([]))
[10]:
vs.shape, gparams.shape, gnoisep.shape
[10]:
(TensorShape([2]), TensorShape([2, 3]), TensorShape([]))
注意噪声参数也可以被微分和即时编译!
使用 tc.Circuit
进行蒙特卡罗噪声模拟#
对于纯态输入,蒙特卡洛方法可用于使用 tc.Circuit
而不是 tc.DMCircuit
对嘈杂的量子演化进行采样,其中混合状态是用纯状态的集合有效模拟的。
至于密度矩阵模拟,可以通过提供相关的 Kraus 算子 \(\{K_i\}\) 的列表将量子通道 \(\mathcal{E}\) 添加到电路对象中。API 与全密度矩阵模拟相同。
[11]:
input_state = np.array([1, 1] / np.sqrt(2))
c = tc.Circuit(1, inputs=input_state)
c.general_kraus(tc.channels.phasedampingchannel(0.5), 0)
c.state()
[11]:
<tf.Tensor: shape=(2,), dtype=complex64, numpy=array([0.+0.j, 1.+0.j], dtype=complex64)>
不过,在这个框架中,作用于 \(\vert{\psi}\rangle\) 的通道的输出,即 \(\mathcal{E} ( \vert{\psi}\rangle\langle{\psi}\vert) = \sum_i K_i \vert{\psi}\rangle\langle{\psi}\vert K_i^ \dagger\) 被视为状态的集合 \(\frac{K_i\vert{\psi}\rangle}{\sqrt{\langle{\psi}\vert K_i^\dagger K_i \vert{\psi}\rangle}}\) 每个发生的概率为 \(p_i = \langle{\psi}\vert K_i^\dagger K_i \vert{\psi}\rangle\). 因此,上面的代码随机产生在状态
\(\vert{\psi}\rangle=\frac{\vert{0}\rangle+\vert{1}\rangle}{\sqrt{2} }\) 中初始化的单个量子比特的输出通过参数 \(\gamma=0.5\) 的相位阻尼通道。 通过使用 unitary_kraus
而不是 general_kraus
,可以更有效地处理 Kraus 算子都是幺正矩阵(可相差一个常数因子)的通道的蒙特卡罗模拟。
[12]:
px, py, pz = 0.1, 0.2, 0.3
c.unitary_kraus(tc.channels.depolarizingchannel(px, py, pz), 0)
[12]:
<tf.Tensor: shape=(), dtype=int32, numpy=3>
请注意,上面返回的 int 张量指示在此轨迹中,在电路上应用了哪个算子。
外化随机性#
上面的 general_kraus
和 unitary\_kraus
示例都从各自的方法内部处理随机性生成。 也就是说,当将 Kraus 运算符的列表 \([K_0, K_1, \ldots, K_{m-1}]\) 提供给 general_kraus
或unitary_kraus
时, 该方法将区间 \([0, 1]\) 到 \(m\) 连续区间 \([0,1] = I_0 \cup I_1 \cup \ldots I_{m-1}\) 其中\(I_i\)的长度等于获得结果\(i\)的相对概率。 然后从方法内部生成\([0,1]\)中的均匀随机变量\(x\),并根据\(x\)所在的区间选择结果\(i\)。
在 TensorCircuit 中,我们拥有用于随机数生成和管理的完整的后端不可知基础设施。 但是,如果我们依赖这些方法中的随机数生成,jit、随机数和后端切换之间的相互作用通常是微妙的。 有关详细信息,请参阅 advance.html#randoms-jit-backend-agnostic-and-their-interplay。
在某些情况下,最好先从方法外部生成随机变量,然后将生成的值传递给 general_kraus
或 unitary_kraus
这可以通过可选的 status
参数来完成:
[13]:
px, py, pz = 0.1, 0.2, 0.3
x = 0.5
print(c.unitary_kraus(tc.channels.depolarizingchannel(px, py, pz), 0, status=x))
x = 0.8
print(c.unitary_kraus(tc.channels.depolarizingchannel(px, py, pz), 0, status=x))
tf.Tensor(1, shape=(), dtype=int32)
tf.Tensor(3, shape=(), dtype=int32)
这很有用,例如,当希望使用 vmap
批量计算蒙特卡罗模拟的多次运行时。这在下面的示例中进行了说明,其中 vmap
用于并行计算 10 次模拟运行。
[14]:
def f(x):
c = tc.Circuit(1)
c.h(0)
c.unitary_kraus(tc.channels.depolarizingchannel(0.1, 0.2, 0.3), 0, status=x)
return c.expectation_ps(x=[0])
f_vmap = K.vmap(f, vectorized_argnums=0)
X = K.implicit_randn(10)
f_vmap(X)
[14]:
<tf.Tensor: shape=(10,), dtype=complex64, numpy=
array([ 0.99999994+0.j, 0.99999994+0.j, 0.99999994+0.j, -0.99999994+0.j,
0.99999994+0.j, 0.99999994+0.j, 0.99999994+0.j, 0.99999994+0.j,
-0.99999994+0.j, 0.99999994+0.j], dtype=complex64)>