The constraints (3) exclude subtours by imposing that for any proper subset S of the vertex set V such that |S| ≥ 2 a solution cannot encompass a cycle within S. However, as there is an exponential number of subsets of V , it is impractical to specify all of these constraints. A possible approach is to iteratively solve the problem, starting without these constraints and after each solving round add constraints (3) violated by the current solution.
一般商业求解器Gurobi或者CPLEX中直接提供的callback(回调函数)的方法,SCIP采用的是constraint handler的方式,使用起来没那么方便,需要自定义一个继承了约束处理的类Conshdlr,覆写conscheck、consenfolp、conslock方法。看源码其实就是callback的一种实现形式而已,其运行日志显示也是迭代运行求解的。
import numpy as np
import networkx as nx
import itertools
from pyscipopt import Model, Conshdlr, quicksum, SCIP_RESULT
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] # 替换sans-serif字体
plt.rcParams['axes.unicode_minus'] = False # 正常显示负号
# 继承约束处理的类
class TSPconshdlr(Conshdlr):
def __init__(self, variables):
self.variables = variables
def find_subtours(self, solution = None):
# find subtours in the graph induced by the edges {i,j} for which x[i,j] is positive
# at the given solution; when solution is None, then the LP solution is used
# 获取所有连接边
edges = []
x = self.variables
for (i, j) in x:
if self.model.getSolVal(solution, x[i, j]) > 1.e-6:
edges.append((i, j))
# 判断图是否有连接,如果返回的长度=1,则说明没有子连接
G = nx.Graph()
G.add_edges_from(edges)
components = list(nx.connected_components(G))
if len(components) == 1:
return []
else:
return components
# checks whether solution is feasible, ie, if there are no subtours;
# since the checkpriority is < 0, we are only called if the integrality
# constraint handler didn't find infeasibility, so solution is integral
def conscheck(self, constraints, solution, check_integrality,
check_lp_rows, print_reason, completely, **results):
""" calls feasibility check method of constraint handler """
if self.find_subtours(solution):
return {
"result": SCIP_RESULT.INFEASIBLE}
else:
return {
"result": SCIP_RESULT.FEASIBLE}
def consenfolp(self, constraints, n_useful_conss, sol_infeasible):
""" calls enforcing method of constraint handler for LP solution for all constraints added """
subtours = self.find_subtours()
if subtours:
x = self.variables
# 添加子环消除路约束
for subset in subtours:
self.model.addCons(quicksum(x[i, j] for(i, j) in pairs(sorted(subset))) <= len(subset) - 1)
print("cut: len(%s) <= %s" % (subset, len(subset) - 1))
return {
"result": SCIP_RESULT.CONSADDED}
else:
return {
"result": SCIP_RESULT.FEASIBLE}
def conslock(self, constraint, locktype, nlockspos, nlocksneg):
# 可以直接跳过
pass
# def conslock(self, constraint, locktype, nlockspos, nlocksneg):
# x = self.variables
# for (i,j) in x:
# self.model.addVarLocks(x[i,j], nlocksneg, nlockspos)
def pairs(nodes):
return itertools.combinations(nodes, 2)
def get_route_data(edges):
# 解析获取路径,构成一个环
routes = []
for i in range(len(edges)):
if i == 0:
routes.append(edges[0])
edges.pop(0)
else:
pre = routes[-1]
connected_node = pre[1]
for j in range(len(edges)):
aft = edges[j]
if connected_node in aft:
if aft[0] == connected_node:
chosen_node = (aft[0], aft[1])
elif aft[1] == connected_node:
chosen_node = (aft[1], aft[0])
routes.append(chosen_node)
edges.pop(j)
break
i = i+1
return routes
def plot_pic(route, city_location):
plt.figure()
# 绘制散点
x = np.array(city_location)[:, 0] # 横坐标
y = np.array(city_location)[:, 1] # 纵坐标
plt.scatter(x, y, color='r')
# 绘制城市编号
for i, txt in enumerate(range(1, len(city_location) + 1)):
plt.annotate(txt, (x[i], y[i]))
# 绘制方向
x0 = x[route]
y0 = y[route]
for i in range(len(city_location) - 1):
plt.quiver(x0[i], y0[i], x0[i + 1] - x0[i], y0[i + 1] - y0[i], color='b', width=0.005, angles='xy', scale=1,
scale_units='xy')
plt.quiver(x0[-1], y0[-1], x0[0] - x0[-1], y0[0] - y0[-1], color='b', width=0.005, angles='xy', scale=1,
scale_units='xy')
plt.title('TSP')
plt.xlabel('x')
plt.ylabel('y')
plt.savefig('./TSP.png')
plt.show()
def pre_test_data():
# 城市节点的位置信息,一行代表一个城市的横坐标及纵坐标
city_location = [[ 94, 99],
[ 66, 67],
[ 14, 78],
[ 95, 56],
[ 68, 9],
[ 26, 20],
[ 51, 67],
[ 39, 39],
[ 5, 55],
[ 12, 33],
[ 55, 85],
[ 98, 46],
[ 36, 39],
[ 65, 100],
[ 57, 89],
[ 88, 24],
[ 53, 96],
[ 91, 41],
[ 32, 69],
[ 38, 38],
[ 38, 39],
[ 85, 100],
[ 7, 37],
[ 85, 96],
[ 89, 48],
[ 85, 35],
[ 32, 29],
[ 31, 25],
[ 20, 17],
[ 75, 21],
[ 74, 29],
[ 6, 32],
[ 20, 81],
[ 62, 1],
[ 11, 48],
[ 1, 69],
[ 99, 70],
[ 20, 27],
[ 25, 42],
[ 6, 31],
[ 78, 24],
[ 42, 39],
[ 83, 30],
[ 94, 10],
[ 90, 37],
[ 76, 73],
[ 9, 56],
[ 39, 33],
[ 74, 15],
[ 77, 14]]
nodes = list(range(len(city_location))) # 节点集合
# 计算距离成本矩阵 distance, 直接使用欧式距离
distance = {
}
for (i, j) in pairs(nodes):
distance[(i, j)] = ((city_location[i][0]-city_location[j][0])**2+(city_location[i][1]-city_location[j][1])**2)**0.5
return nodes, city_location, distance
def solve_tsp(nodes, distance):
# 构建模型
model = Model("TSP")
# 定义变量: 为了减少决策变量,节点i和j是否连接(无方向),即(1,2)代表1和2连接,不代表1->2
x = {
}
for (i, j) in pairs(nodes):
x[i, j] = model.addVar(vtype="B", name="x(%s,%s)" % (i, j))
# 添加流约束
for i in nodes:
model.addCons(quicksum(x[j, i] for j in nodes if j < i) +
quicksum(x[i, j] for j in nodes if j > i) == 2, "Degree(%s)" % i)
# 去除子环路
conshdlr = TSPconshdlr(x)
model.includeConshdlr(conshdlr, "TSP", "TSP subtour eliminator", chckpriority=-10, needscons=False)
model.setBoolParam("misc/allowstrongdualreds", False)
# 设置目标
model.setObjective(quicksum(distance[i, j] * x[i, j] for (i, j) in pairs(nodes)), "minimize")
# 求解
model.hideOutput()
model.optimize()
# 获取结果
if model.getStatus() != 'infeasible':
edges = []
for (i, j) in x:
if model.getVal(x[i, j]) > 1.e-6:
edges.append((i, j))
routes = get_route_data(edges)
print("Optimal routes:", routes)
print("Optimal cost:", model.getObjVal())
else:
print('model is infeasible')
return routes
if __name__ == "__main__":
############## 准备测试数据 ##############
nodes, city_location, distance = pre_test_data()
############## 建模 & 求解 ##############
route = solve_tsp(nodes, distance)
############## 绘图结果 ##############
plot_pic(route, city_location)
cut: len({
0, 21, 23}) <= 2
cut: len({
1, 2, 3, 4, 6, 7, 8, 11, 12, 15, 17, 18, 19, 20, 24, 25, 26, 27, 32, 33, 34, 35, 36, 38, 41, 42, 43, 44, 45, 46, 47, 48, 49}) <= 32
cut: len({
37, 28, 5}) <= 2
cut: len({
9, 31, 22, 39}) <= 3
cut: len({
16, 10, 13, 14}) <= 3
cut: len({
40, 29, 30}) <= 2
cut: len({
0, 1, 36, 6, 10, 45, 13, 14, 16, 21, 23}) <= 10
cut: len({
32, 2, 18}) <= 2
cut: len({
24, 11, 3}) <= 2
cut: len({
48, 33, 4}) <= 2
cut: len({
34, 37, 5, 39, 7, 9, 41, 38, 12, 47, 19, 20, 22, 26, 27, 28, 31}) <= 16
cut: len({
8, 35, 46}) <= 2
cut: len({
40, 42, 43, 15, 49, 29, 30}) <= 6
cut: len({
17, 44, 25}) <= 2
cut: len({
0, 13, 21, 23}) <= 3
cut: len({
1, 2, 3, 4, 5, 6, 8, 9, 11, 15, 17, 18, 24, 25, 26, 27, 28, 29, 30, 32, 33, 34, 35, 36, 37, 38, 40, 42, 43, 44, 45, 46, 47, 48, 49}) <= 34
cut: len({
7, 41, 12, 19, 20}) <= 4
cut: len({
16, 10, 14}) <= 2
cut: len({
31, 22, 39}) <= 2
cut: len({
0, 1, 36, 6, 10, 45, 13, 14, 16, 18, 21, 23}) <= 11
cut: len({
32, 2, 35}) <= 2
cut: len({
24, 11, 3, 17}) <= 3
cut: len({
33, 4, 40, 42, 43, 44, 15, 48, 49, 25, 29, 30}) <= 11
cut: len({
37, 5, 39, 7, 9, 41, 38, 12, 47, 19, 20, 22, 26, 27, 28, 31}) <= 15
cut: len({
8, 34, 46}) <= 2
cut: len({
0, 10, 13, 14, 16, 21, 23}) <= 6
cut: len({
1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 15, 17, 18, 19, 20, 22, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49}) <= 42
cut: len({
0, 1, 36, 10, 45, 13, 14, 16, 21, 23}) <= 9
cut: len({
2, 5, 6, 7, 8, 9, 12, 18, 19, 20, 22, 26, 27, 28, 31, 32, 34, 35, 37, 38, 39, 41, 46, 47}) <= 23
cut: len({
3, 11, 44, 17, 24}) <= 4
cut: len({
33, 4, 40, 42, 43, 15, 48, 49, 25, 29, 30}) <= 10
cut: len({
0, 1, 6, 10, 13, 45, 14, 16, 21, 23}) <= 9
cut: len({
2, 5, 7, 8, 9, 12, 18, 19, 20, 22, 26, 27, 28, 31, 32, 34, 35, 37, 38, 39, 41, 46, 47}) <= 22
cut: len({
11, 3, 36}) <= 2
cut: len({
33, 4, 40, 42, 43, 15, 48, 49, 29, 30}) <= 9
cut: len({
24, 17, 44, 25}) <= 3
Optimal routes: [(0, 21), (21, 23), (23, 13), (13, 16), (16, 14), (14, 10), (10, 45), (45, 1), (1, 6), (6, 18), (18, 32), (32, 2), (2, 35), (35, 8), (8, 46), (46, 34), (34, 22), (22, 31), (31, 39), (39, 9), (9, 37), (37, 28), (28, 5), (5, 27), (27, 26), (26, 38), (38, 12), (12, 19), (19, 20), (20, 7), (7, 41), (41, 47), (47, 33), (33, 4), (4, 48), (48, 49), (49, 43), (43, 15), (15, 40), (40, 29), (29, 30), (30, 42), (42, 25), (25, 44), (44, 17), (17, 24), (24, 11), (11, 3), (3, 36), (36, 0)]
Optimal cost: 508.0830635384923
文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib
文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang
文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些
文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器
文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距
文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器
文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn
文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios
文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql
文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...
文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120
文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数