1. 数学模型
m
i
n
∑
(
i
,
j
)
∈
A
c
i
j
x
i
j
∑
j
∈
V
x
i
j
−
∑
j
∈
V
x
j
i
=
{
−
1
,
i
=
s
0
,
i
≠
s
a
n
d
i
≠
t
1
,
i
=
t
min\sum_{(i,j)\in A}{c_{ij} x_{ij}}\\ \sum_{j \in V}x_{ij} - \sum_{j\in V}x_{ji} = \begin{cases} -1, & {i = s } \\ 0, & {i \neq s \quad and \quad i \neq t}\\ 1, & {i=t}\\ \end{cases}
min(i,j)∈A∑cijxijj∈V∑xij−j∈V∑xji=⎩
⎨
⎧−1,0,1,i=si=sandi=ti=t
x
i
j
x_{ij}
xij: 0-1决策变量,
x
i
j
=
1
x_{ij}=1
xij=1表示经过弧
(
i
,
j
)
(i, j)
(i,j),
x
i
j
=
0
x_{ij}=0
xij=0表示不经过弧
(
i
,
j
)
(i, j)
(i,j)
c i j c_{ij} cij: 弧 ( i , j ) (i, j) (i,j)上的权重
A A A: 所有弧的集合
V V V: 所有点的集合
s , t s, t s,t :分别表示源节点和结束节点
2. python调用SCIP求解最短路径问题
- 根据上面的数学模型建立规划模型,完整代码如下:
import numpy as np
import matplotlib.pyplot as plt
import networkx as nx
import time
import pyscipopt as opt
def print_fun_run_time(func):
def wrapper(*args, **kwargs):
local_time = time.time()
func(*args, **kwargs)
print('current function [%s] run time is %.4f'%(func.__name__, time.time()-local_time))
return wrapper
def prepare_data():
# 测试数据: 邻接矩阵标识图结构,其中极大值1000表示不连通, 测试数据包含11个节点
matrix = np.array([[0, 2, 8, 1, 1000, 1000, 1000, 1000, 1000, 1000, 1000],
[2, 0, 6, 1000, 1, 1000, 1000, 1000, 1000, 1000, 1000],
[8, 6, 0, 7, 5, 1, 2, 1000, 1000, 1000, 1000],
[1, 1000, 7, 0, 1000, 1000, 9, 1000, 1000, 1000, 1000],
[1000, 1, 5, 1000, 0, 3, 1000, 2, 1000, 1000, 1000],
[1000, 1000, 1, 1000, 3, 0, 4, 1000, 6, 1000, 1000],
[1000, 1000, 2, 9, 1000, 4, 0, 1000, 3, 1, 1000],
[1000, 1000, 1000, 1000, 2, 1000, 1000, 0, 7, 1000, 9],
[1000, 1000, 1000, 1000, 1000, 6, 3, 7, 0, 1, 2],
[1000, 1000, 1000, 1000, 1000, 1000, 1, 1000, 1, 0, 4],
[1000, 1000, 1000, 1000, 1000, 1000, 1000, 9, 2, 4, 0]])
# 转换数据(只是为了方便理解,不为效率), 获取不包含本节点且连通的点及弧
row, col = np.where((matrix != 1000) & (matrix != 0))
Nodes = list(np.unique(np.unique(np.append(row, col)) + 1).astype('str'))
Arcs = {}
for i in range(len(row)):
Arcs[str(row[i] + 1), str(col[i] + 1)] = matrix[row[i], col[i]]
print('Nodes:\n', Nodes)
print('Arcs:\n', Arcs)
return Nodes, Arcs
@print_fun_run_time
def SCIP_solver(Nodes, Arcs, source, target):
model = opt.Model('SPP')
# ==========定义变量==========
arc = {}
for idx in Arcs.keys():
arc[idx] = model.addVar(vtype='B', name=str(idx))
# ==========定义约束==========
# 其实类似VRP的流量约束,源节点一定是流出,结束节点一定是流入,其他节点有流入一定有流出
# 约束1: 源点一定流出
model.addCons(opt.quicksum(arc[idx] for idx in Arcs.keys() if idx[0] == source) == 1, name='source_flow')
# 约束2: 终点一定流入
model.addCons(opt.quicksum(arc[idx] for idx in Arcs.keys() if idx[1] == target) == 1, name='target_flow')
# 约束3: 其他点流平衡
for node in Nodes:
if node != source and node != target:
model.addCons(opt.quicksum(arc[idx] for idx in Arcs.keys() if idx[0] == node and idx[1] != source) -
opt.quicksum(arc[idx] for idx in Arcs.keys() if idx[1] == node and idx[0] != target) == 0)
# ==========定义目标==========
model.setObjective(opt.quicksum(arc[arc_idx] * arc_weight for arc_idx, arc_weight in Arcs.items()))
model.setMinimize()
model.optimize()
# ==========输出结果==========
distance = model.getObjVal()
# 转换结果
visit_arcs = []
for idx in Arcs.keys():
if model.getVal(arc[idx]) > 0:
visit_arcs.append(idx)
visit_arcs_dict = dict([list(i) for i in visit_arcs])
idx = source
path = []
for i in range(len(Nodes)):
next_node = visit_arcs_dict.get(idx, None)
if next_node is not None:
path.append(next_node)
idx = next_node
print(f'节点 {source} 到 {target} 的路径={path}, 最短距离={distance}')
return True
if __name__ == '__main__':
source = '1' # starting node
target = '11' # ending node
# 测试数据
Nodes, Arcs = prepare_data()
# 方式3: python调用开源求解器SCIP求解最短路径问题
SCIP_solver(Nodes, Arcs, source, target)