如何绘制CPLEX代码的效率及最优解收敛曲线?
嘿,这个需求太常见啦,很多做优化的伙伴都会需要用曲线直观展示CPLEX求解的收敛情况,我给你拆解成两步来做:先从CPLEX里捞到关键的收敛/效率数据,再用绘图工具把它可视化出来,下面给你具体说细节~
第一步:从CPLEX中获取收敛/效率数据
CPLEX提供了两种主要方式来拿这些数据,各有优劣,你可以按需选择:
方式1:用API自定义回调(精度高、灵活)
如果你用的是Python的docplex库(最方便的CPLEX Python接口),可以写一个自定义回调函数,在求解过程中实时抓取每一步的关键数据——比如当前时间、最优解上界(已经找到的最好可行解目标值)、松弛解下界(理论上的最优解下限)、求解间隙,甚至节点数、迭代次数这些效率指标。
给你贴个现成的示例代码,你可以直接套到自己的模型里:
from docplex.mp.callbacks import CplexCallback import pandas as pd import matplotlib.pyplot as plt # 自定义回调类,专门记录收敛数据 class ConvergenceTracker(CplexCallback): def __init__(self): CplexCallback.__init__(self) self.convergence_data = [] self.start_timestamp = self.get_time() # 记录求解开始时间 def __call__(self): # 计算当前耗时(秒) elapsed_time = self.get_time() - self.start_timestamp # 获取松弛解的最好下界 lower_bound = self.get_best_objective_bound() # 获取当前找到的最好可行解(上界),如果还没找到可行解就设为None upper_bound = self.get_objective_value() if self.has_incumbent() else None # 只在有有效上界的时候记录(避免无效数据) if upper_bound is not None: # 计算求解间隙(百分比) optimality_gap = (upper_bound - lower_bound) / abs(upper_bound) * 100 if upper_bound != 0 else 0 self.convergence_data.append({ "耗时(秒)": elapsed_time, "下界": lower_bound, "上界": upper_bound, "求解间隙(%)": optimality_gap, "已探索节点数": self.get_num_nodes() }) # --- 把下面的部分换成你自己的模型代码 --- # 假设你已经定义好了你的CPLEX模型model # model = Model(name="your_model") # ... 你的变量、约束、目标函数定义 ... # 注册回调函数 tracker = ConvergenceTracker() model.register_callback(tracker) # 开始求解 model.solve() # 把记录的数据转成DataFrame,方便后续处理 convergence_df = pd.DataFrame(tracker.convergence_data)
如果用的是C++/Java的CPLEX API,思路也是一样的:继承对应的回调类(比如C++里的IloCplex::MIPInfoCallback),在回调函数里调用CPLEX的接口获取数据,然后写到数组或者文件里就行。
方式2:解析CPLEX求解日志(快速上手,无需改代码)
如果你不想改模型代码,可以让CPLEX把求解日志输出到文件,然后手动解析日志里的关键数据。
比如在Python里可以这样让CPLEX输出日志到文件:
model.parameters.logfilename.set("cplex_solve_log.txt") model.solve()
日志里会有类似这样的行:
Nodes | Current Node | Objective Bounds | Work
Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
0 0 | - 0 0 | 1000.0000 500.0000 50.0% - 0s
5 12 | - 3 2 | 900.0000 600.0000 33.3% 2 1s
你可以用正则表达式或者字符串匹配,把每一行里的Incumbent(上界)、BestBd(下界)、Time(耗时)、Expl(已探索节点数)这些数据提取出来,整理成表格格式。
第二步:绘制收敛/效率曲线
拿到数据之后,用matplotlib或者seaborn就能轻松画图了,给你几个常用的可视化方向:
1. 最优解上下界收敛曲线(核心!直观展示逼近最优解的过程)
这个曲线能直接看到上界(可行解)和下界(理论下限)怎么逐渐靠拢,直到收敛到最优解:
plt.figure(figsize=(10, 6)) plt.plot(convergence_df["耗时(秒)"], convergence_df["下界"], label="松弛解下界", color="#1f77b4", linewidth=2) plt.plot(convergence_df["耗时(秒)"], convergence_df["上界"], label="最优可行解上界", color="#ff7f0e", linewidth=2) # 填充上下界之间的区域,更直观 plt.fill_between(convergence_df["耗时(秒)"], convergence_df["下界"], convergence_df["上界"], alpha=0.2, color="gray") plt.xlabel("求解耗时(秒)") plt.ylabel("目标函数值") plt.title("最优解收敛曲线") plt.legend() plt.grid(alpha=0.3) plt.show()
2. 求解间隙随时间变化曲线
求解间隙是判断收敛速度的关键指标,能看到间隙怎么从大到小降到0(或者你设置的停止阈值):
plt.figure(figsize=(10, 6)) plt.plot(convergence_df["耗时(秒)"], convergence_df["求解间隙(%)"], label="最优性间隙", color="#2ca02c", linewidth=2) plt.xlabel("求解耗时(秒)") plt.ylabel("最优性间隙(%)") plt.title("求解间隙收敛曲线") plt.legend() plt.grid(alpha=0.3) plt.yscale("log") # 用对数刻度更适合看间隙快速下降的过程 plt.show()
3. 效率相关曲线(展示求解速度)
如果想展示代码的求解效率,可以画已探索节点数随时间的变化,或者迭代次数随时间的变化:
plt.figure(figsize=(10, 6)) plt.plot(convergence_df["耗时(秒)"], convergence_df["已探索节点数"], label="已探索节点数", color="#d62728", linewidth=2) plt.xlabel("求解耗时(秒)") plt.ylabel("已探索节点数") plt.title("节点探索效率曲线") plt.legend() plt.grid(alpha=0.3) plt.show()
一些小Tips
- 如果求解时间很长,可以设置回调函数每隔N秒或者N次迭代才记录一次数据,避免数据量太大,也不会影响求解速度;
- 对于连续线性规划(LP),收敛曲线看的是对偶间隙的变化,和MIP的上下界逻辑类似;
- 如果需要更美观的图,可以用
seaborn替换matplotlib,或者导出数据到Excel/Tableau做交互式可视化。
备注:内容来源于stack exchange,提问作者MO MO MA

