密度矩阵和混态演化#

概述#

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.DMCircuittc.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_krausunitary\_kraus 示例都从各自的方法内部处理随机性生成。 也就是说,当将 Kraus 运算符的列表 \([K_0, K_1, \ldots, K_{m-1}]\) 提供给 general_krausunitary_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_krausunitary_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)>