CPLEX杂记(一) 生成解池(solution pool)

这个系列是记录笔者在使用CPLEX过程中遇到的一些小问题和相应的解决方案。对于不同的求解器未必有相同的功能,仅供参考。

在使用求解器求解混合整数规划问题时,我们有时候想要的并不是最优的解,而是目标函数值在一定gap内的一组解。

在这种情景下,我们可以使用CPLEX的solution pool功能。

CPLEX中有两种产生solution pool的方式:

  • 在默认模式下,进行branch-and-cut的过程中,求解器如果遇到符合要求的解,会把它加入solution pool中,当然在过程中由于并没有找到最优解,这里加入solution pool的解有一些可能不符合要求,需要在后续进行删除;
  • populate来生成solution pool:在这种模式下,求解器首先找到最优解,然后在已知最优解的基础上,通过一些启发式算法来寻找符合要求的解来加入solution pool中。

通过设置下面几个参数,我们就可以得到一个solution pool:

  1. model.parameters.mip.pool.intensity,这个参数可以取1-4的值,代表筛选solution pool时的枚举强度(其实也就是在搜索过程中搜索的深度)。设置为1时,不会影响求解器的速度也不用存储额外信息,求解器在求解过程中“顺便”将遇到的一些符合要求的解加入解池;设置为2时,在branch-and-cut中生成树结构时额外存储一些信息,以便在求解之后快速找回一些符合要求的解,这个模式会额外使用一定内存,但是不会对明显影响优化求解速度;设置为3时,搜索深度会更深,也会额外存储一些信息,因此优化求解速度会变慢,同时会额外利用空间;设置为4时,全枚举所有符合要求的解,这种模式下求解速度会变得很慢。
  2. model.parameters.mip.pool.relgap,这个参数控制solution pool中的解相对于最优解的gap。例如说我们的最优解目标函数值为10,设置relgap为0.2,那么在枚举解时,如果遇到目标函数值大于等于8的解,这个解就会被放入solution pool中。
  3. model.parameters.mip.pool.capacity,这个参数控制solution pool的大小(也就是解池能容纳的最大解个数)。当搜索到的符合要求的解超过解池大小时,会执行replacement strategy去替换解池中的解。
  4. model.parameters.mip.pool.replace,这个参数可以取1-3的整数值。1是默认的替换策略,在这个模式下会用新搜索到的符合要求的解替换解池中最早搜索到的解;在模式2下,会替换掉解池中目标函数值最差的解;在模式3下,会根据解池的diversity,替换掉解池中对diversity贡献最小的解。
  5. parameters.mip.limits.populate,这个参数控制使用populate进行解池构建时,每次调用populate能获得的最大解池规模。

我们可以以一个简单的背包问题为例,来看一下如何使用solution pool功能。

给定问题如下:

物品编号 1 2 3 4 5 6 7
价值 12 3 10 3 6 2 3
重量 5 4 7 2 6 1 2

我们现在有一个最大负载为10的背包,求能够装入背包的物品的最大价值。

在求最值的情况下,无论使用求解器建模求解还是用动态规划,都比较容易能获得最大价值。但是如果我们想要知道所有价值落在区间 [80%*maxValue, maxValue]的解呢?

我们可以使用如下代码让CPLEX帮我们生成一个solution pool:

from docplex.mp.model import Model

# --------------------------------
# 参数
# --------------------------------
values: list = [12, 3, 10, 3, 6, 2, 3]  # 物品的价值
weights: list = [5, 4, 7, 2, 6, 1, 2]  # 物品的重量
capacity: int = 10  # 背包的最大重量
lower_bound: float = 0.8  # 需要所有 0.8*maxObj ~ Obj的解

# --------------------------------
# 建立模型
# --------------------------------
# 实例化模型
mdl = Model("Knapsack Prob")
# 添加决策变量: x[i] - 是否选择第i个物品
idx: list = list(range(1, len(values) + 1))
x = mdl.binary_var_dict(idx, name='x')
# 添加约束 -- 选择的物品重量不能超过背包能承受的最大重量
mdl.add_constraint(mdl.sum(x[i] * weights[i - 1] for i in idx) <= capacity)
# 目标函数 -- 最大化背包内物品的价值
mdl.maximize(mdl.sum(x[i] * values[i - 1] for i in idx))

# --------------------------------
# 设置solution pool参数
# --------------------------------
mdl.parameters.mip.pool.intensity = 4
mdl.parameters.mip.pool.relgap = 1 - lower_bound
mdl.parameters.mip.limits.populate = len(weights) ** 3
mdl.parameters.timelimit = 30  # 设置一下最大求解时间,防止跑的时间太久
solution_pool = mdl.populate(log_output=True)

# --------------------------------
# 输出结果
# --------------------------------
if not solution_pool:
   raise Exception("No solution found for the problem")
else:
   n_feasible_sols: int = solution_pool.size
   print("{} solutions in solution pool".format(n_feasible_sols))
   for i in range(n_feasible_sols):
       print("----Solution {:d}".format(i))
       active_var_index = [idx + 1 for idx, var in enumerate(solution_pool._solutions[i].get_all_values()) if
                           var > 0.9]
       print("Selected Assignment Units: {}".format(active_var_index))
       print("Objective value of the solution: {:.2f}".format(solution_pool._solutions[i].objective_value))

在运行之后,得到结果:

5 solutions in solution pool
----Solution 0
Selected Assignment Units: [1, 4, 6, 7]
Objective value of the solution: 20.00
----Solution 1
Selected Assignment Units: [1, 6, 7]
Objective value of the solution: 17.00
----Solution 2
Selected Assignment Units: [1, 4, 7]
Objective value of the solution: 18.00
----Solution 3
Selected Assignment Units: [1, 4, 6]
Objective value of the solution: 17.00
----Solution 4
Selected Assignment Units: [1, 2, 6]
Objective value of the solution: 17.00

可以看到除了最优解[1, 4, 6, 7]之外,解池中还包含所有目标函数值 > 20 * 0.8的解。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容