旅行商问题的QUBO建模「下篇之Python实现」

graphite
2024-11-07 16:44:01
 

一,旅行商问题QUBO的两种实现

提示:上篇已经讲过了旅行商问题的QUBO建模,这里直接讲两种编程实现:

 

看过上篇的读者应该已经注意到,因为旅行商问题需要最终返回到初始点的。所以,下面的目标函数里,循环进行到N 时,最后一个x j , t + 1 应该确定回到初始点的。

 

 

针对这个特殊设定,我们可以有两种实现方式:

 

方式一:使用取余操作符%,在t = N 时,这样的话(t+1)%N=1,也就相当于最后一个时间步回到了初始点。

 

 

方式二:把x i , t 和x j , t + 1 对应的数值矩阵,分为独立的两个矩阵。具体来说,x i , t 从矩阵X中取值和x j , t + 1 从矩阵Y中取值。矩阵Y的数值就是,y j , t = x j , t + 1 ,从式子上看,有点难以理解,大家可以看下面的图示。其实就是把矩阵X的第一列,转移到最后一列。

 

 

最后我们的目标函数转换如下:

 

 

二,方式一:取余操作

这里代码复制于下面的链接,这里只讲解QUBO部分的代码:

https://github.com/recruit-communications/pyqubo/blob/master/notebooks/TSP.ipynb
 

旅行问题的QUBO的定义:

%matplotlib inline
from pyqubo import Array, Placeholder, Constraint
import matplotlib.pyplot as plt
import networkx as nx
import numpy as np
import neal

# 地点名和坐标 list[("name", (x, y))]
cities = [
    ("a", (0, 0)),
    ("b", (1, 3)),
    ("c", (3, 2)),
    ("d", (2, 1)),
    ("e", (0, 1))
]

n_city = len(cities)
 

下面实现约束部分:

 

# ‘BINARY’代表目标变量时0或1 
x = Array.create('c', (n_city, n_city), 'BINARY')

# 约束① : 每个时间步只能访问1个地点
time_const = 0.0
for i in range(n_city):
    # Constraint(...)函数用来定义约束项
    time_const += Constraint((sum(x[i, j] for j in range(n_city)) - 1)**2, label="time{}".format(i))

# 约束② : 每个地点只能经过1次
city_const = 0.0
for j in range(n_city):
    city_const += Constraint((sum(x[i, j] for i in range(n_city)) - 1)**2, label="city{}".format(j))
 

接下来是目标函数:

 

# ‘BINARY’代表目标变量时0或1 
x = Array.create('c', (n_city, n_city), 'BINARY')

# 约束① : 每个时间步只能访问1个地点
time_const = 0.0
for i in range(n_city):
    # Constraint(...)函数用来定义约束项
    time_const += Constraint((sum(x[i, j] for j in range(n_city)) - 1)**2, label="time{}".format(i))

# 约束② : 每个地点只能经过1次
city_const = 0.0
for j in range(n_city):
    city_const += Constraint((sum(x[i, j] for i in range(n_city)) - 1)**2, label="city{}".format(j))
 

最后就是整体的Hamiltonian量定义和输出采样结果。

# 总体的Hamiltonian,这里的A代表约束项的惩罚系数,数值自由指定
A = Placeholder("A")
H = distance + A * (time_const + city_const)

# 求BQM
model = H.compile()
feed_dict = {'A': 4.0}
bqm = model.to_bqm(feed_dict=feed_dict)


sa = neal.SimulatedAnnealingSampler()
# 这里要注意,退火算法是要采样足够的次数,从中取占比最高的结果作为最优解
sampleset = sa.sample(bqm, num_reads=100, num_sweeps=100)

# Decode solution
decoded_samples = model.decode_sampleset(sampleset, feed_dict=feed_dict)
best_sample = min(decoded_samples, key=lambda x: x.energy)


# 如果指定参数only_broken=True,则只会返回损坏的约束。
# 如果best_sample长度为0,就代表没有损坏的约束项,也就满足最优解了。
num_broken = len(best_sample.constraints(only_broken=True))
if num_broken == 0:
	print(best_sample.sample)
 

三、方式二:独立矩阵

这里的实现复制于以下文章:

https://qiita.com/yufuji25/items/0425567b800443a679f7
import neal
import numpy as np
import networkx as nx
import matplotlib.pyplot as plt
from pyqubo import Array, Placeholder
from scipy.spatial.distance import cdist


def construct_graph(n_pos:int):
    """ construct complete graph """
    pos = nx.spring_layout(nx.complete_graph(n_pos))
    coordinates = np.array(list(pos.values()))
    G = nx.Graph(cdist(coordinates, coordinates))
    nx.set_node_attributes(G, pos, "pos")
    return G

 

整体Hamiltonian量:

 

def create_hamiltonian(G:nx.Graph):
    """ create QUBO model from graph structure """

    # generate QUBO variable
    n_pos = G.number_of_nodes()
    X = np.array(Array.create("X", shape = (n_pos, n_pos), vartype = "BINARY"))

    # construct `Y` matrix
    Xtmp = np.concatenate([X, X[:, 0].reshape(-1, 1)], axis = 1)
    Y = np.delete(Xtmp, 0, 1)

    # Distance matrix
    L = np.array(nx.adjacency_matrix(G).todense())

    # return hamiltonian
    Hbind1 = np.sum((1 - X.sum(axis = 0)) ** 2)
    Hbind2 = np.sum((1 - X.sum(axis = 1)) ** 2)
    Hobj = np.sum(X.dot(Y.T) * L)
    H = Hobj + Placeholder("a1") * Hbind1 + Placeholder("a2") * Hbind2

    return H.compile()
 

求解并输出结果:

def decode(response, Gorigin:nx.Graph):
    """ return output graph generated from response """

    # derive circuit
    solution = min(response.record, key = lambda x: x[1])[0]
    X = solution.reshape((num_pos, num_pos))
    circuit = np.argmax(X, axis = 0).tolist()
    circuit.append(circuit[0])

    # generate output graph structure
    Gout = nx.Graph()
    Gout.add_edges_from(list(zip(circuit, circuit[1:])))
    positions = nx.get_node_attributes(Gorigin, "pos")
    nx.set_node_attributes(Gout, positions, "pos")

    return Gout


def draw_graph(G:nx.Graph, **config):
    """ drawing graph """
    plt.figure(figsize = (5, 4.5))
    nx.draw(G, nx.get_node_attributes(G, "pos"), **config)
    plt.show()



# configuration for drawing graph
config = {"with_labels":True, "node_size":500, "edge_color":"r", "width":1.5,
          "node_color":"k", "font_color":"w", "font_weight":"bold"}

# construct complete graph
num_pos = 8
G = construct_graph(num_pos)
draw_graph(G, **config)

# sampling
model = create_hamiltonian(G)
qubo, offset = model.to_qubo(feed_dict = {"a1":500, "a2":500})
response = neal.SimulatedAnnealingSampler().sample_qubo(qubo, num_reads = 1000)


# output graph
Gout = decode(response, G)
draw_graph(Gout, **config)
 

总结

以上就是两种实现方式,大家可以体会一下,怎么实现稍微复杂的Hamiltonian量,接下来,讲解一下量子退火的物理原理。

 

参考文献:

[*1] : https://www.nttdata.com/jp/ja/-/media/nttdatajapan/files/news/services_info/2021/012800/012800-01.pdf

[*2] : https://qiita.com/yufuji25/items/0425567b800443a679f7

 

————————————————

本文转自CSDN平台博主:gang_unerry

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/gangshen1993/article/details/127638370

71
0
0
0
关于作者
相关文章
  • 旅行商问题的QUBO建模「上篇」
     一、旅行商问题(Traveling Saleman Problem,TSP)1.旅行商问题的定义旅行商问题,是一个 ...
    了解详情 
  • 总投资1600万美元!美国首个州级资助的量子计算中心落户麻省 ...
     近日,Healey-Driscoll 政府拨款 4,994,520 美元,用于在霍利奥克的马萨诸塞州(麻省)绿 ...
    了解详情 
  • 量子通信为什么安全?|十万个量子为什么
    随着电子设备的普及和网络技术的发展,互联网世界逐渐成为生活中不可分割的一部分。大众对于虚拟 ...
    了解详情 
  • 能量为什么是不连续的?能量为什么是一份一份的? ...
    在日常生活中,我们常常认为能量是一种可以随意变化、逐渐增加或减少的量,无论是加热水壶中的水 ...
    了解详情 
在本版发帖返回顶部
快速回复 返回顶部 返回列表