首页-沐鸣娱乐-官方注册站

全国加盟咨询热线:

400-123-4567

当前位置: 首页 > 沐鸣资讯 > 公司新闻

五. 量子近似优化算法 (QAOA)

文章作者:佚名 浏览次数:发表时间:2024-05-20 19:43:07

量子近似优化算法 (Quantum Approximate Optimization Algorithm) 可以处理的问题中最著名的一个是名为 MaxCut 的图论问题

给定一个图,求一个图的最大分割。

此处分割的大小定义为将图划分为两部分,割过边的数目的多少。它的判定版本被证明是 NP-Complete 问题

给定一个图和整数 k,问是否有存在一个分割大于等于 k。

它可以被翻译为求解经典Ising模型基态的问题,其Hamiltonian如下

H_c=\\sum_{\\langle i,j\\rangle\\in E}\\frac12(-\\sigma_i^z\\sigma_j^z+1)

E 是一个图中边的集合,可以把定义在顶角 V 上的自旋 \\sigma_z=\\pm 1 理解为一种将图的二分割,+1 和 -1 分别代表了分割后的两部分,分割的大小为所有端点自旋符号相反的边的数目。根据上面的Hamiltonian形式,实际程序的 loss函数可以被定义为

\\mathcal{L}_{\	heta}=\\sum_{\\langle i,j\\rangle\\in E}\\frac{w_{ij}}{2}\\langle\\psi(\	heta)|\\sigma_i^z\\sigma_j^z|\\psi(\	heta)\\rangle

这里去掉了常数项,并且取反号使目标变成求极小。这里引入了权重 w_{ij} ,将MaxCut问题拓展到了带权重版本。特别的地方在于,它通过参数化量子波函数 |\\psi(\	heta)\\rangle=U(\	heta) |0\\rangle 来变分求解经典Hamilton的基态(是个直积态),避免了直接操作离散数据。

求解这个问题,QAOA 采用了经典量子混合算法,量子部分负责构造量子线路 ansatz U(\	heta) ,计算loss。作为一个可以在D-wave 这种量子模拟器上跑的算法,它的 ansatz 的设计是受量子退火算法影响的,其参数化形式为

|\\psi(\	heta)\\rangle=U_B(\\beta_N)\\ldots U_B(\\beta_2)U_C(\\gamma_2)U_B(\\beta_1)U_C(\\gamma_1)|s\\rangle

其中 \	heta\\beta_1\\ldots\\beta_N\\gamma_1\\ldots\\gamma_N 的集合,是需要优化的参数。 U_C(\\gamma)=e^{-iH_c\\gamma} , U_B(\\beta)=e^{-iH_b\\beta}H_b=\\sum_i\\sigma_i^x 为横场Hamiltonian。 |s\\rangle=\\frac{1}{\\sqrt{2^n}}\\sum_z|z\\rangle 为各种构型均匀叠加的初始态。简而言之,就是依次作用系统Hamiltonian和横场项到量子态上,以作用时间为优化参数,希望最后能演化到目标经典Hamiltonian基态。经典部分负责优化,可以基于梯度也可以不基于梯度。简单起见,我们采用不基于梯度的方式去模拟,文中采用的是COBYLA算法。

参考文献:

A Quantum Approximate Optimization Algorithm

首先导入一些包,其中 NLopt 是非线性优化器的包,在这个例子面我们将会使用基于单纯形法的 COBYLA 优化算法作为我们的经典优化部分。同时我们引入maxcut_gw.jl 这个文件,它包含了 MaxCut 问题的经典解法 - Goemans-Williamson 算法,具体见目录。

首先,我们构造两个Hamiltonian构造方法 HB 和 HC。

using Yao, BitBasis
using NLopt

include("maxcut_gw.jl")

"""
Transverse field term
"""
HB(nbit::Int) = sum([put(nbit, i=>X) for i=1:nbit])

"""
Target Hamiltonian.
"""
function HC(W::AbstractMatrix)
    nbit = size(W, 1)
    ab = Any[]
    for i=1:nbit,j=i+1:nbit
        if W[i,j] != 0
            push!(ab, 0.5*W[i,j]*repeat(nbit, Z, [i,j]))
        end
    end
    sum(ab)
end

对于三个角的图,HB 定义的含时演化如下

julia> TimeEvolution(HB(3), 0.1)
Time Evolution Total: 3, DataType: Complex{Float64}
+
├─ put on (1)
  └─ X gate
├─ put on (2)
  └─ X gate
└─ put on (3)
   └─ X gate
Δt = 0.1, tol = 1.0e-7

HC通过权重矩阵生成,比如如下的 3 个角的的全连接图

julia> W = [0 1 3; 1 0 1; 3 1 0]   # 3x3 的全连接图
3×3 Array{Int64,2}:
 0  1  3
 1  0  1
 3  1  0

julia> hc = HC(W)
Total: 3, DataType: Complex{Float64}
+
├─ [0.5] repeat on (1, 2)
  └─ Z gate
├─ [1.5] repeat on (1, 3)
  └─ Z gate
└─ [0.5] repeat on (2, 3)
   └─ Z gate

julia> ishermitian(hc)
true


下来可以构造 QAOA 整体的线路图,此处depth定义为 U_C , U_C 的重复次数。可选参数use_cache如果为true,则可以把Hamiltonian的矩阵形式缓存下来加速含时演化操作。

function qaoa_circuit(W::AbstractMatrix, depth::Int; use_cache::Bool=false)
    nbit = size(W, 1)
    hb = HB(nbit)
    hc = HC(W)
    use_cache && (hb = hb |> cache; hc = hc |> cache)
    c = chain(nbit, [repeat(nbit, H, 1:nbit)])
    append!(c, [chain(nbit, [time_evolve(hc, 0.0, tol=1e-5),
                        time_evolve(hb, 0.0, tol=1e-5)]) for i=1:depth])
end

可以看到对于3个node的全连接图,两层的结构最后得到的线路图如下

julia> qaoa_circuit(W, 2)
Total: 3, DataType: Complex{Float64}
chain
├─ repeat on (1, 2, 3)
  └─ H gate
├─ chain
  ├─ Time Evolution Total: 3, DataType: Complex{Float64}
    +
    ├─ [0.5] repeat on (1, 2)
      └─ Z gate
    ├─ [1.5] repeat on (1, 3)
      └─ Z gate
    └─ [0.5] repeat on (2, 3)
       └─ Z gate
    Δt = 0.0, tol = 1.0e-5
  └─ Time Evolution Total: 3, DataType: Complex{Float64}
     +
     ├─ put on (1)
       └─ X gate
     ├─ put on (2)
       └─ X gate
     └─ put on (3)
        └─ X gate
     Δt = 0.0, tol = 1.0e-5
└─ chain
   ├─ Time Evolution Total: 3, DataType: Complex{Float64}
     +
     ├─ [0.5] repeat on (1, 2)
       └─ Z gate
     ├─ [1.5] repeat on (1, 3)
       └─ Z gate
     └─ [0.5] repeat on (2, 3)
        └─ Z gate
     Δt = 0.0, tol = 1.0e-5
   └─ Time Evolution Total: 3, DataType: Complex{Float64}
      +
      ├─ put on (1)
        └─ X gate
      ├─ put on (2)
        └─ X gate
      └─ put on (3)
         └─ X gate
      Δt = 0.0, tol = 1.0e-5


训练采用COBYLA算法,是基于单纯形方法的优化算法中比较好用的。用法请参见NLopt的文档。

function cobyla_optimize(circuit::MatrixBlock{N}, hc::MatrixBlock; niter::Int) where N
    function f(params, grad)
        reg = zero_state(N) |> dispatch!(circuit, params)
        loss = expect(hc, reg) |> real
        #println(loss)
        loss
    end
    opt = Opt(:LN_COBYLA, nparameters(circuit))
    min_objective!(opt, f)
    maxeval!(opt, niter)
    cost, params, info = optimize(opt, parameters(circuit))
    pl = zero_state(N) |> circuit |> probs
    cost, params, pl
end


作为测试,我们构造一个5个节点的图

图1. 测试用的拥有5个节点的图,图中红色虚线为最大分割。
nbit = 5
W = [0 5 2 1 0;
	 5 0 3 2 0;
	 2 3 0 0 0;
	 1 2 0 0 4;
	 0 0 0 4 0]

# the exact solution
exact_cost, sets = goemansWilliamson(W)

hc = HC(W)
# the cut is -<Hc> + sum(wij)/2
@test -expect(hc, product_state(5, bmask(sets[1]...)))+sum(W)/4  exact_cost


这里我们会得到严格解[1, 3, 4],[2, 5], 此分割大小为14. 这里我们测试了目标 Hamiltonian的正确性,注意这里符号和常数的差别。

# build a QAOA circuit and start training.
qc = qaoa_circuit(W, 20; use_cache=false)
opt_pl = nothing
opt_cost = Inf
for i = 1:10
	dispatch!(qc, :random)
	cost, params, pl = cobyla_optimize(qc, hc; niter=2000)
	@show cost
	cost < opt_cost && (opt_pl = pl)
end

输入如下:

cost=-5.056439885146659
cost=-5.3148313277163455
cost=-5.314515402351343
cost=-5.27061216619153
cost=-5.218577446603341
cost=-5.140030583281352
cost=-4.859469398853606
cost=-5.445797991390686
cost=-5.2074338799039
cost=-4.971171116896116

对于严格解,cost应该是-5.5,可见经过充分优化,很多情况QAOA都可以得到足够好的解。

检查输出是否正确

# check the correctness of result
config = argmax(opt_pl)-1
@test config in [bmask(set...) for set in sets]


大家有没有发现其实QAOA优化有时候不稳定,经常得到的结果是错的?一个可以优化的地方是用基于梯度的优化器,但是这需要很特别的构造。大家可以去尝试哦~

Goemans-Williamson 算法

该算法用于求解Max-Cut问题,原算法在 github.com/ericproffitt, 但是由于很久没更新已经不再兼容Julia 1.0以上版本。如下是适用于Julia1.x的代码,需要提前安装CSC和Convex两个包来使用。

using Convex
using SCS
using LinearAlgebra

"
The Goemans-Williamson algorithm for the MAXCUT problem.

Adapted From: https://github.com/ericproffitt/MaxCut
"

function goemansWilliamson(W::Matrix{T}; tol::Real=1e-1, iter::Int=100) where T<:Real
	"Partition a graph into two disjoint sets such that the sum of the edge weights
	which cross the partition is as large as possible (known to be NP-hard)."

	"A cut of a graph can be produced by assigning either 1 or -1 to each vertex.  The Goemans-Williamson
	algorithm relaxes this binary condition to allow for vector assignments drawn from the (n-1)-sphere
	(choosing an n-1 dimensional space will ensure seperability).  This relaxation can then be written as
	an SDP.  Once the optimal vector assignments are found, origin centered hyperplanes are generated and
	their corresponding cuts evaluated.  After 'iter' trials, or when the desired tolerance is reached,
	the hyperplane with the highest corresponding binary cut is used to partition the vertices."

	"W:		Adjacency matrix."
	"tol:	Maximum acceptable distance between a cut and the MAXCUT upper bound."
	"iter:	Maximum number of hyperplane iterations before a cut is chosen."

	LinearAlgebra.checksquare(W)
	@assert LinearAlgebra.issymmetric(W)	"Adjacency matrix must be symmetric."
	@assert all(W .>= 0)			"Entries of the adjacency matrix must be nonnegative."
	@assert all(diag(W) .== 0)		"Diagonal entries of adjacency matrix must be zero."
	@assert tol > 0					"The tolerance 'tol' must be positive."
	@assert iter > 0				"The number of iterations 'iter' must be a positive integer."

	"This is the standard SDP Relaxation of the MAXCUT problem, a reference can be found at
	http://www.sfu.ca/~mdevos/notes/semidef/GW.pdf."
	k = size(W, 1)
	S = Semidefinite(k)

	expr = dot(W, S)
	constr = [S[i,i] == 1.0 for i in 1:k]
	problem = minimize(expr, constr...)
	solve!(problem, SCSSolver(verbose=0))

	### Ensure symmetric positive-definite.
	A = 0.5 * (S.value + S.value')
	A += (max(0, -eigmin(A)) + eps(1e3))*I

	X = Matrix(cholesky(A))

	### A non-trivial upper bound on MAXCUT.
	upperbound = (sum(W) - dot(W, S.value)) / 4

	"Random origin-centered hyperplanes, generated to produce partitions of the graph."
	maxcut = 0
	maxpartition = nothing

	for i in 1:iter
		gweval = X' * randn(k)
		partition = (findall(x->x>0, gweval), findall(x->x<0, gweval))
		cut = sum(W[partition...])

		if cut > maxcut
			maxpartition = partition
			maxcut = cut
		end

		upperbound - maxcut < tol && break
		i == iter && println("Max iterations reached.")
	end
	return round(maxcut; digits=3), maxpartition
end

using Test
@testset "max cut" begin
	W = [0 5 2 1 0;
		 5 0 3 2 0;
		 2 3 0 0 0;
		 1 2 0 0 4;
		 0 0 0 4 0]

	maxcut, maxpartition = goemansWilliamson(W)
	@test maxcut == 14
	@test [2,5] in maxpartition
end

回顶部

平台注册入口