Cirq教程

半世苍凉 提交于 2020-01-22 07:24:49

教程

在该教程中,我们将会从对Cirq的一无所知到创建创建一个量子变分算法(quantum variational algorithm)
,请注意,本教程不是量子计算101教程,我们假设在Nielsen和Chuang的教科书《量子计算和量子信息》的水平上熟悉量子计算. 有关概念性概述,请参阅概念文档

首先,请遵循安装的说明。

背景: 变分量子算法

量子理论中的变分法是用于发现一个量子系统低能态的经典方法。该方法的大致思想是是将一个试波函数(有时称为ansatz)定义为某些参数的函数,然后找到这些参数的值,以最小化能量相对于这些参数的期望值。这个最小化的anstaz是最低能量特征态的近似值,并且其期望值用作基态能量的上界。

在最近几年(以 arXiv:1304.3061arXiv:1507.08969作为示例),人们已经意识到量子计算机可以模拟经典的技术,并且量子计算机具有特点优点。特别地,当我们把经典的量子变分法应用到一个nn位量子位的系统中,需要指数(nn个)的复数来一般地表示系统的波函​​数。然而,随着量子计算机可以用参数化的量子电路直接产生这种状态,然后通过重复测量估计能量的期望值。

这种想法导致了一类称为变分量子算法的算法。实际上,这种方法不仅限于寻找低能量本征态,而是能最小化任何可以表示为量子可观测量的目标函数。确定这些量子变分算法在什么条件下能够成功是一个有待解决的问题,探索这类算法是研究有噪声的中等规模量子计算机的一个关键部分。

我们将关注的经典问题是具有横向场的2D +/- Ising模型(ISING). 这个问题是NP完全的,因此量子计算机极不可能在所有实例中有效地解决它。然而,这类问题说明了Cirq旨在解决的一般问题。

考虑能量函数
E(s1,...,sn)=<i,j>Ji,jsisj+ihisiE(s_1, ..., s_n) = \sum_{<i, j>}J_{i,j}s_is_j+\sum_{i}h_is_i
这里的每一个si,Ji,js_i, J_{i,j}hih_i要么是+1+1, 要么是1-1。这里的每一个ii都与方格上的一位相联系,并且<i,j><i, j>记号表示该晶格上相邻位的总和。我们想要解决的问题是:给定Ji,jJ_{i,j}hih_i, 找出最小化EEsis_i取值。

一个变分量子算法是如何工作的?一种方法是考虑n个量子位,并将它们与经典问题中的每个量子位联系起来。这将经典问题映射到最小化可观测值的量子问题上。

H=<i,j>Ji,jZiZj+ihiZiH=\sum_{<i, j>}J_{i, j}Z_iZ_j+\sum_{i}h_iZ_i

然后定义一组参数化量子电路,即一个量子电路,其中的门(或更一般的量子操作)由一些值参数化。这会产生一个ansatz状态.

ψ(p1,p2,...,Pr)\psi(p_1, p_2, ..., P_r)
这里的pip_i是产生该状态的参数(在这里我们假设一个纯粹的状态,但混合状态当然是可能的)。

然后,变分算法可以获得给定ansatz状态的目标函数的值来工作。

  1. 准备ansatz态。
  2. 对H中的一些项进行抽样测量。
  3. 转到1

注意,不能总是直接测量H(不使用量子相位估计),因此通常依赖于期望值的线性来测量步骤2中的H的部分。总是需要重复测量以获得对期望值的估计。 实现给定精度所需的测量数量超出了本教程的范围,但Cirq可以帮助调查此问题。

由此可见,我们可以利用量子计算机来估计ansatz的目标函数。然后,可以在外部循环中使用它来尝试获取目标函数的最低值的参数。对于这些值,我们可以使用最好的ansatz来生成问题的解的样本,这些样本可以得到目标函数的最低可能值的一个很好的近似。

在一个网格上创建电路

为了使用Criq创建上述的变分量子算法,首先要建立适当的电路。在Cirq中,电路由一个Circuit对象或一个Schedule对象表征。Schedule提供更多的时间级上的量子门和电路的控制,但这里我们并不需要这些,所以我们使用Circuit
概念上:一个Circuit是一个Moment集合。一个Moment是一个在同一个抽象时间片中发生作用的Collection集合。一个Operation作用于一个特定的Qubits子集上的一种效应。最常用的Operation是应用于几个量子位(GateOperation)的一个门。下面的图表应该有助于说明这些概念。

有关这些类的详细信息,请参阅概念文档。由于我们已定义的问题在网格上具有自然结构,我们将会使用Cirq内建的GridQubit作为我们的量子位。我们将会证明这些工作在交互式python环境中怎样工作,如下的代码可以在Cirq安装的python环境中序列式地运行。
以我们谈论的量子位作为开始。在一个交互式的Python环境中运行

import cirq

# define the length of the grid.
length = 3
# define qubits on the grid.
qubits = [cirq.GridQubit(i, j) for i in range(length) for j in range(length)]
print(qubits)
# prints 
# [GridQubit(0, 0), GridQubit(0, 1), GridQubit(0, 2), GridQubit(1, 0), GridQubit(1, 1), GridQubit(1, 2), GridQubit(2, 0), GridQubit(2, 1), GridQubit(2, 2)]

在这里,我们看到我们创建了GridQubit的一个分支。GridQubit实现了QubitId类,这意味着它们是等同的和可清除的。
GridQubit有一行和一列,指示其在网格中的位置。
既然我们有了一些量子位,那我们在这些量子位上构建Circuit。举个例子,假设我们想要将Hadamard门H应用于行索引加列索引为偶数的每个量子位,并且将行索引加列索引为奇数的每个量子位的X门。我们用如下代码实现这一点

circuit = cirq.Circuit()
circuit.append(cirq.H.on(q) for q in qubits if (q.row + q.col) % 2 == 0)
circuit.append(cirq.X(q) for q in qubits if (q.row + q.col) % 2 == 1)
print(circuit)
# prints
# (0, 0): ───H───────
#
# (0, 1): ───────X───
#
# (0, 2): ───H───────
#
# (1, 0): ───────X───
#
# (1, 1): ───H───────
# 
# (1, 2): ───────X───
# 
# (2, 0): ───H───────
#
# (2, 1): ───────X───
#
# (2, 2): ───H───────

这里需要注意一点。首先cirq.X是一个门对象。Cirq中支持多种不同的门。在common_gates.py有一个很好的地方可以看到门的定义。要避免的一个常见困惑是门类和门对象(它是类的实例化)之间的差异。第二个式通过方法on(qubit)或者通过将正如我们看到的X门应用到量子上将门对象转换成Operation(技术上叫GateOperation)。我们在这里只使用了单个量子位门,但一个相似的模式应用于多个量子位,不过现在量子位序列应该作为参数提供。
另外值得注意的式上述circuit是交错的circuit门。则是因为我们应用门的这种方式已经创建了Moment

for i, m in enumerate(circuit):
    print('Moment {}: {}'.format(i, m))
# prints 
# Moment 0: H((0, 0)) and H((0, 2)) and H((1, 1)) and H((2, 0)) and H((2, 2))
# Moment 1: X((0, 1)) and X((1, 0)) and X((1, 2)) and X((2, 1))

这里我们看到,我们可以在CircuitMoment上迭代。创建两个Moment的原因是append方法使用了一个NEW_THEN_INLINEInsertStrategyInsertStrategy描述了新插入到Circuit怎样排放他们的门。这些策略的细节可以在Circuit文档中找到。如果我们想要插入门以便让让门形成一个Moment,我们可以使用EARLIEST策略代替。

circuit = cirq.Circuit()
circuit.append([cirq.H.on(q) for q in qubits if (q.row + q.col) % 2 == 0],
               strategy=cirq.InsertStrategy.EARLIEST)
circuit.append([cirq.X(q) for q in qubits if (q.row + q.col) % 2 == 1],
               strategy=cirq.InsertStrategy.EARLIEST)
print(circuit)
# (0, 0): ───H───
#
# (0, 1): ───X───
#
# (0, 2): ───H───
#
# (1, 0): ───X───
#
# (1, 1): ───H───
#
# (1, 2): ───X───
#
# (2, 0): ───H───
#
# (2, 1): ───X───
#
# (2, 2): ───H───

我们现在只能看到一个moment,因为X门已经滑过来尽可能在最早的时刻采取Moment

创建Ansatz

如果你仔细地看上述创建的电路代码,你会发现我们将append方法应用到了generator
list(回想一下,在python中,你可以在方法调用中使用生成器的理解)。审查append的代码就可以看到,append方法通常需要一个OP_TREE(或者一个Moment)。什么是OP_TREE?这不是一门课,而是一份合同。大致上,OP_TREE是可以被扁平化(可能是递归地)到操作列表或单个操作中的任何东西。OP_TREE的例子为:

\bullet 单个Operation
\bullet Operation列表
\bullet Operation元组
\bulletOperation列表的列表
\bulletOperation生成器

最后一种情况产生了一个很好的模式,用于定义子电路/层,定义一个接收相关参数的函数,然后产生子电路的操作,然后将其附加到电路:

def rot_x_layer(length, half_turns):
    """Yields X rotations by half_turns on a square grid of given length."""
    rot = cirq.RotXGate(half_turns=half_turns)
    for i in range(length):
        for j in range(length):
            yield rot(cirq.GridQubit(i, j))
        
circuit = cirq.Circuit()
circuit.append(rot_x_layer(2, 0.1))
print(circuit)
# prints
# (0, 0): ───X^0.1───
#
# (0, 1): ───X^0.1───
#
# (1, 0): ───X^0.1───
#
# (1, 1): ───X^0.1───

这里另一个重要概念是由"half turns"指定的旋转门。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!