1. 编程框架简介

在编程领域,软件框架是指一种抽象形式,它提供了一个具有通用功能的软件,这些功能可以由使用者编写代码来有选择的进行更改,从而提供服务于特定应用的软件。可以说,软件框架提供了一种标准的方式来构建并部署应用。

软件框架是一种通用的、可复用的软件环境,它提供特定的功能,作为一个更大的软件平台的一部分,用以促进软件应用、产品和解决方案的开发工作。软件框架可能会包含支撑程序、编译器、代码、库、工具集以及API,它把所有这些部件汇集在一起,以支持项目或系统的开发。

由于提取了特定领域软件的共性部分,因此在这个领域内再次开发新项目的过程中代码不需要从头编写,只需要在框架的基础上进行一些再开发和调整便可满足要求;对于开发过程而言,这样做会提高软件的质量,降低成本,缩短开发时间,使开发越做越轻松,效益越做越好,形成一种良性循环。但我们也要认识到,框架不是现成可用的的应用系统,而是一个半成品,需要后来的开发人员进行二次开发,才能够实现相应的具体功能。

2. 各公司或组织的编程框架

目前,国内外很多量子技术相关的公司或组织都打造了自己的量子编程框架,这些框架或开源或闭源。量子框架的优势在于并不需要拥有一台真实的量子计算机,就能够通过它来进行量子电路等方面的模拟,让更多的人可以参与到量子技术的研发上来,更快速的推动量子技术的发展。

接下来我们看看目前量子领域内都有哪些影响力较大的量子框架:

2.1 QPanda

开源量子计算编程框架软件介绍_编程框架


QPanda2是由本源量子开发的开源量子计算框架,作为本源量子计算系列软件的基础库,为OriginIR、Qurator、量子计算服务提供核心部件,可用于构建,运行和优化量子算法。目前提供C++Python两种版本。

2.1.1 Python示例代码

以下示例可用于在量子计算机中构造量子纠缠$(\left | 0000 \right \rangle + \left | 1111 \right \rangle)$,测量所有量子比特并运行1000次:

from pyqpanda import *

qvm = CPUQVM()
qvm.init_qvm()
prog = QProg()
q = qvm.qAlloc_many(4)
c = qvm.cAlloc_many(4)
prog << H(q[0])\
<< CNOT(q[0:-1],q[1:])\
<< measure_all(q,c)
result = qvm.run_with_configuration(prog, c, 1000)
print(result)
qvm.finalize()

结果:

{'0000': 518, '1111': 482}

如果想要建立量子电路,我们可以按照如下步骤来完成:首先调试运行环境

cmake_minimum_required(VERSION 3.1)
project(QCircui)
add_executable(${PROJECT_NAME} QCircuit.cpp)
target_link_libraries(${PROJECT_NAME} ${QPANDA_LIBRARIES})


接着开始建立量子电路

#include "QPanda.h"
USING_QPANDA

int main(void)
{
init();
auto qvec = qAllocMany(4);
auto cbits = cAllocMany(4);
auto circuit = createEmptyCircuit();

circuit << H(qvec[0]) << CNOT(qvec[0], qvec[1])
<< CNOT(qvec[1], qvec[2]) << CNOT(qvec[2], qvec[3]);
circuit.setDagger(true);
auto prog = createEmptyQProg();
prog << H(qvec[3]) << circuit << Measure(qvec[0], cbits[0]);

auto result = runWithConfiguration(prog, cbits, 1000);
for (auto &val : result)
{
std::cout << val.first << ", " << val.second << std::endl;
}

finalize();
return 0;
}

2.2 ProjectQ

ProjectQ是苏黎世联邦理工学院启动的量子计算开源软件框架。它有能够针对各种类型硬件的编译框架,与IBM 5bit云上量子计算兼容,还有具有仿真能力的高性能量子计算机模拟器以及各种编译器插件。它能够帮助使用者完成以下功能:

  1. 在IBM Quantum Experience芯片、AQT设备、AWS Braket或IonQ服务提供的设备上运行量子程序
  2. 在经典计算机上模拟量子程序
  3. 在更高的抽象层次上模拟量子程序(例如,模仿大型预言机的行为,而不是将它们编译为低级门)
  4. 将量子程序导出为电路(使用TikZ)
  5. 获取资源估算等等

2.2.1 运行实例

首个量子方案:

from projectq import MainEngine # import the main compiler engine
from projectq.ops import H, Measure # import the operations we want to perform (Hadamard and measurement)

eng = MainEngine() # create a default compiler (the back-end is a simulator)
qubit = eng.allocate_qubit() # allocate a quantum register with 1 qubit

H | qubit # apply a Hadamard gate
Measure | qubit # measure the qubit

eng.flush() # flush all gates (and execute measurements)
print("Measured {}".format(int(qubit))) # converting a qubit to int or bool gives access to the measurement result

projectQ的特点是采用了接近于量子物理学中所使用的数学符号的精简语法。例如,一个量子比特围X轴的旋转通常被指定为:$R_x(\theta)\left | qubit \right \rangle$

同样的语句在projectQ中的语法是:

Rx(theta) | qubit

只需使用|运算符将门的操作规范(左侧)与应用操作的量子比特(右侧)分开。

如果要在IBM Quantum Experience芯片上运行程序,只需要择IBMBackend和相应的设置:

import projectq.setups.ibm
from projectq.backends import IBMBackend

token='MY_TOKEN'
device='ibmq_16_melbourne'
compiler_engines = projectq.setups.ibm.get_engine_list(token=token,device=device)
eng = MainEngine(IBMBackend(token=token, use_hardware=True, num_runs=1024,
verbose=False, device=device),
engine_list=compiler_engines)

如果要IonQ设备上运行量子程序,可以通过使用IonQBackend及其相应的设置。 目前可用的设备有:

  1. ionq_simulator:一个29量子比特的模拟器。
  2. ionq_qpu:一个11量子比特的俘获离子系统。


具体使用到的代码如下:

import projectq.setups.ionq
from projectq import MainEngine
from projectq.backends import IonQBackend

token = 'MY_TOKEN'
device = 'ionq_qpu'
backend = IonQBackend(
token=token,
use_hardware=True,
num_runs=1024,
verbose=False,
device=device,
)
compiler_engines = projectq.setups.ionq.get_engine_list(
token=token,
device=device,
)
eng = MainEngine(backend, engine_list=compiler_engines)

在经典模拟量子程序方面ProjectQ还拥有高性能的量子模拟器,可以在一台普通笔记本电脑上模拟多达30个量子比特。我们可以很容易的通过它来使用Shor算法模拟超过50个量子比特的问题。

它的量子模拟器对于研究量子系统的算法也特别有用。例如,模拟器可以及时演化量子系统,并且可以直接访问Hamiltonian的期望值,从而对VQE类型算法进行极快模拟:

from projectq import MainEngine
from projectq.ops import All, Measure, QubitOperator, TimeEvolution

eng = MainEngine()
wavefunction = eng.allocate_qureg(2)
# Specify a Hamiltonian in terms of Pauli operators:
hamiltonian = QubitOperator("X0 X1") + 0.5 * QubitOperator("Y0 Y1")
# Apply exp(-i * Hamiltonian * time) (without Trotter error)
TimeEvolution(time=1, hamiltonian=hamiltonian) | wavefunction
# Measure the expection value using the simulator shortcut:
eng.flush()
value = eng.backend.get_expectation_value(hamiltonian, wavefunction)

# Last operation in any program should be measuring all qubits
All(Measure) | qureg
eng.flush()

2.3 Qiskit Terra

开源量子计算编程框架软件介绍_量子计算_02


Qiskit是一个开源框架,用于在电路,脉冲和算法级别使用量子计算机。它由好几个子库组成,名字都是拉丁语的元素,Terra代表的是土元素,有奠基之意,因为Terra是Qiskit架构中的基础。功能上,Terra提供了在电路和脉冲级别编写上的基础,为特定的设备的约束进行了优化,并管理远程访问设备上批量实验的执行。Terra定义了理想的最终用户体验接口,以及优化层、脉冲调度和后端通信层的高效处理。

一般我们通过pip工具安装Qiskit。以下命令可以安装核心Qiskit组件,包括Terra。

pip install qiskit

安装成功后,我们可以利用它来创建一个量子电路示例,该示例使用Qiskit Basic Aer元素在本地进行模拟。这是一个简单的例子,可以看到它会产生纠缠状态:

>>> from qiskit import QuantumCircuit, transpile
>>> from qiskit.providers.basicaer import QasmSimulatorPy
>>> qc = QuantumCircuit(2, 2)
>>> qc.h(0)
>>> qc.cx(0, 1)
>>> qc.measure([0,1], [0,1])
>>> backend_sim = QasmSimulatorPy()
>>> transpiled_qc = transpile(qc, backend_sim)
>>> result = backend_sim.run(transpiled_qc).result()
>>> print(result.get_counts(qc))

输出结果为:

{'00': 498, '11': 502}

如果要绘制电路图,我们使用以下代码:

circuit.draw()

开源量子计算编程框架软件介绍_编程框架_03


绘制直方图:

plot_histogram(counts)

开源量子计算编程框架软件介绍_量子计算_04

2.4 QuEST编程框架

开源量子计算编程框架软件介绍_量子计算_05


QuESTQuantum Exact Simulation Toolkit 是量子电路、状态向量和密度矩阵的高性能模拟器。QuEST使用多线程GPU加速分发,首先在笔记本电脑,台式机和联网超级计算机上率先运行。QuEST只是编程框架,它是独立的,不需要安装,编译和运行起来很简单。

QuEST是由牛津大学的QTechTheory小组联合一些作者共同开发的。它支持:

  1. 用于精确模拟噪声量子计算机的密度矩阵
  2. 具有控制任意数量和目标量子位的一般酉阵
  3. 很多分析量子态的工具。包括泡利小工具,分析相位函数和Trotter电路等等
  4. QASM输出,用于模拟仿真电路
  5. 在MacOS、Linux和Windows上通过Clang、GNU、Intel和MSVC编译器进行本地编译等等。

2.4.1 框架构建情况

QuEST在CPU、GPU和网络之间有一个简单的接口,其在运行时与环境无关

hadamard(qubits, 0);

controlledRotateX(qubits, 0, 1, angle);

double prob = calcProbOfOutcome(qubits, 0, outcome);


但是它也很灵活

Vector v;
v.x = 1; v.y = .5; v.z = 0;
rotateAroundAxis(qubits, 0, angle, v);

ComplexMatrix2 u = {
.real = {{.5, .5}, { .5,.5}},
.imag = {{.5,-.5}, {-.5,.5}}};
unitary(qubits, 0, u);

mixDepolarising(qubits, 0, prob);


并且功能强大

ComplexMatrixN u = createComplexMatrixN(5);
int ctrls[] = {0, 1, 2};
int targs[] = {5, 20, 15, 10, 25};
multiControlledMultiQubitUnitary(qubits, ctrls, 3, targs, 5, u);

ComplexMatrixN k1, k2, k3 = ...
mixMultiQubitKrausMap(qubits, targs, 5, {k1, k2, k3}, 3);

double val = calcExpecPauliHamil(qubits, hamiltonian, workspace);

applyTrotterCircuit(qubits, hamiltonian, time, order, repetitions);

2.4.2 运行示例

我们来看看如何用QuEST来搭建一个复杂的量子电路:

首先使createQuESTEnv()构建一个QuEST环境,在该环境抽象出多线程,完成分发或GPU加速策略的任何准备工作。

QuESTEnv env = createQuESTEnv();


然后,我们通过createQureg()创建一个量子寄存器,在本例中包含3个量子比特。

Qureg qubits = createQureg(3, env);


并初始化寄存器。

initZeroState(qubits);


我们可以创建多个实例,QuEST会为状态向量分配内存,甚至也能够通过网络完成;如果我们想在电路中模拟噪声,我们可以用createDensityQureg来代替,以创建一个更强大的密度矩阵,能够表示混合状态并模拟去相干。

接下来将一些酉阵应用于我们的量子比特,在本例中,量子比特3种状态。在应用运算符时,我们确定在哪个量子寄存器上操作:0 1 2。

hadamard(qubits, 0);
controlledNot(qubits, 0, 1);
rotateY(qubits, 2, .1);


一些门允许我们指定一般数量的控制量子比特

int controls[] = {0, 1, 2};
multiControlledPhaseGate(qubits, controls, 3);


我们可以将一般的单量子位酉阵运算指定为2x2矩阵

// sqrt(X) with a pi/4 global phase
ComplexMatrix2 u = {
.real = {{.5, .5}, { .5,.5}},
.imag = {{.5,-.5}, {-.5,.5}}};
unitary(qubits, 0, u);


或者更确切地说,放弃全局相位系数,

Complex a = {.real = .5, .imag = .5};
Complex b = {.real = .5, .imag =-.5};
compactUnitary(qubits, 1, a, b);


或者,围绕布洛赫球面上任意轴的旋转

Vector v = {.x=1, .y=0, .z=0};
rotateAroundAxis(qubits, 2, 3.14/2, v);


我们可以控制应用一般酉阵

controlledCompactUnitary(qubits, 0, 1, a, b);


也能够同时控制多个量子比特

multiControlledUnitary(qubits, (int[]) {0, 1}, 2, 2, u);


还可以使用量子寄存器完成更多问题和计算。

qreal prob = getProbAmp(qubits, 7);
printf("Probability amplitude of |111>: %lf\n", prob);


为了保持代码精度不受影响,所以在编译时可以改变数值精度,而不对代码做任何修改。改变精度在验证数字收敛性或研究四舍五入之类的错误时是很有用的。

在结果中测量最终量子比特的可能性有多大呢,2,1:

prob = calcProbOfOutcome(qubits, 2, 1);
printf("Probability of qubit 2 being in state 1: %f\n", prob);


我们还可以对量子状态执行非单一门。破坏性地测量第一个量子位,让结果随机坍缩成0或1。

int outcome = measure(qubits, 0);
printf("Qubit 0 was measured in state %d\n", outcome);


现在测量我们的最终量子比特,同时也能得到最终结果的概率数据。

outcome = measureWithStats(qubits, 2, &prob);
printf("Qubit 2 collapsed to %d with probability %f\n", outcome, prob);

我们甚至可以将非物理算子应用于我们的寄存器,以打破它的规范化,这往往可以让我们采取类似这样的计算捷径。


在结束使用电路后,不要忘记释放量子寄存器使用的内存。

destroyQureg(qubits, env);
destroyQuESTEnv(env);


按照上述代码操作,模拟出的是下面的电路:

开源量子计算编程框架软件介绍_编程框架_06



3. 启科量子框架——QuTrunk

3.1 编程框架介绍

QuTrunk是启科量子自行研发的量子编程框架,基于Python提供量子编程API,对量子编程涉及到的基本概念做了代码层面的抽象封装和实现,这些实现对应到QuTrunk框架内相应的Python模块,比如QCircuit实现了量子线路,Qubit实现量子比特,Qureg实现量子寄存器,Command对应每个量子门操作的指令,Backend代表运行量子线路的后端模块,gate模块里面实现了各类基础量子门操作,下面对这些主要模块做进一步说明:

  1. QCircuit:量子线路,维护对所有量子比特的各种门操作及操作时序,代表了整个量子算法的实现。
  2. Qubit:代表单个量子比特,每个量子比特默认持有一个经典比特,方便存放量子比特对测量结果。
  3. Qureg:维护若干个量子比特,用于实现一个具体的量子算法。
  4. Command:每个量子门操作其背后都会转换成一个基础指令,这些指令按照时间顺序存放在QCircuit中,当整个算法结束或者需要计算当前量子线路的某种状态取值时,这些指令会被发送到指定的后端去执行。
  5. Backend:后端模块,用于执行量子线路,支持本地后端,QuSprout后端等。
  6. Gate:量子算法基本组成单元,提供各类量子门操作,包括H, Measure, CNOT, Toffoli, P, R, Rx, Ry, Rz, S, Sdg, T, Tdg, X, Y, Z, NOT, Swap, SqrtSwap, SqrtX, All, C, Rxx, Ryy, Rzz

QuTrunk目前已完成第一版开发工作,通过接入QuSprout(量子计算后端设备)实现量子算法的运行,未来计划接入本地模拟计算、量子云计算、真实量子计算设备。

3.2 运用实例

如果要使用QuTrunk建立量子电路,我们首先需要调用它:

from QuTrunk.core.circuit import QCircuit
from QuTrunk.core.printer import Printer
from QuTrunk.core.gates import H, CNOT, Measure


接着,执行运行建立量子电路:

def run_bell_pair():
# allocate
qc = QCircuit()
qr = qc.allocate(2)

# apply gate
H | qr[0]
CNOT | (qr[0], qr[1])
Measure | qr[0]
Measure | qr[1]

# print circuit
Printer.print_circuit(qc)

# run circuit
qc.flush()

# print result
print([int(q) for q in qr])


当然,我们也可以使用Grover算法来建立量子电路,首先进行环境调试:

import math
import random

from numpy import pi

from QuTrunk.core.circuit import QCircuit, InitState
from QuTrunk.core.gates import H, X, C, Z
from QuTrunk.core.calculator import Calculator
from QuTrunk.core.counter import Counter


输入随机数后创建

def apply_oracle(qr, num_qubits, sol_elem):
for q in range(num_qubits):
if ((sol_elem >> q) & 1) == 0:
X | qr[q]

ctrls = []
for q in range(num_qubits):
ctrls.append(qr[q])
C(Z, len(ctrls) - 1) | (tuple(ctrls))

for q in range(num_qubits):
if ((sol_elem >> q) & 1) == 0:
X | qr[q]

接下来,执行Grover算法,确定搜索值并创建量子电路:

def run_grover():
num_qubits = 15
num_elems = 2 ** num_qubits
num_reps = math.ceil(pi / 4 * math.sqrt(num_elems))
print("num_qubits:", num_qubits, "num_elems:", num_elems, "num_reps:", num_reps)

# 待搜索的值
sol_elem = random.randint(0, num_elems)

circuit = QCircuit()
qureg = circuit.allocate(num_qubits, InitState.Plus)
calc = Calculator(circuit)
# 将统计信息归集到Counter类
ct = Counter(circuit)

for _ in range(num_reps):
apply_oracle(qureg, num_qubits, sol_elem)
apply_diffuser(qureg, num_qubits)
prob_amp = calc.get_prob_amp(sol_elem)
print(f"prob of solution |{sol_elem}> = {prob_amp}")

# 冲刷线路,确保所有指令都执行了
circuit.flush()
ct.show_verbose()

3.3 QuTrunk编程框架优势

QuTrunk作为启科自研的量子编程框架,可以更高效的与QuSprout进行联动,它的具体优势总结如下:

  1. 灵活扩展:当选择与QuSprout连接进行模拟计算时,量子线路当所有状态都由后端QuSprout维护,极少占用本地资源,QuTrunk只提供量子编程API当选择本地模拟时(计划第二版实现),QuTrunk成为量子编程+量子模拟的全栈框架;
  2. 各种量子门操作,这些门操作的主要特点是通过重载运算符,提供一种近似物理表达式的运算操作,给从事量子编程/计算的工作者一种更为直观的表现形式;
  3. 提供经典与量子混合编程,当经典算法与量子算法产生依赖交互时QuTrunk通过量子线路分段执行机制,立刻将当前线路发送给后端,让后端运行然后获取运行结果;
  4. 提供calulator模块,运行在不改变整个状态矢量当情况下获取线路的状态取值,比如计算某种状态的出现概率,计算所有状态的概率振幅等;
  5. 整个量子算法结束时,需要调用量子线路QCircuit的flush方法,将 QuTrunk生成的所有操作全部推送后端,并通知后端释放计算资源;
  6. 量子线路初始化时,可以指定线路的初始化状态,目前支持设置零态和加态;
  7. 量子线路支持噪声模型,可以在量子线路初始化时设置;
  8. 支持量子线路打印,在终端以字符串方式打印;
  9. 支持量子线路运行统计功能,统计:量子比特,量子门,运行耗时等;
  10. 支持全振幅量子模拟。