学习日志38-cpelx求解器使用
cplex中在目标函数中使用分段线性函数
基于cplex的考虑质量系数的最大化收益代码
/********************************************** OPL 22.1.0.0 模型* 作者: 示例* 创建日期: 2025年5月15日* 功能: 拆卸线平衡优化模型* 目标: 最大化拆卸净收益 = 组件回收价值 - 拆卸成本*********************************************//*** 基本参数定义 ***/
int I = 15; // 产品组件数的最大值(索引1-15)
int J = 13; // 产品任务数的最大值(索引1-13)
int W = 3; // 可用的工作站数量
int R = 3; // 每个任务可选的拆卸方式数量/*** 实际产品参数 ***/
int I_p = 15; // 当前产品实际组件数
int J_p = 13; // 当前产品实际任务数/*** 组件参数 ***/
int v_mpi[1..I] = [0,115,125,111,104,96,115,100,106,114,107,100,111,89,1030]; // 组件i的市场价格(第0位占位)
float H_pi[1..I] = [0.99,0.98,0.95,0.94,0.93,0.98,0.95,0.96,0.89,0.9,0.91,0.93,0.79,0.99,0.9]; // 组件i的初始质量系数/*** 任务参数 ***/
int t_pj[1..R][1..J] = [ // 任务j采用方式r的拆卸时间[[9,7,9,5,8,8,8,8,7,8,7,8,9],[8,9,8,6,6,5,5,5,10,6,8,9,7],[7,8,6,8,9,8,8,8,7,9,10,6,5]];
int c_pj[1..R][1..J] = [ // 任务j采用方式r的单位时间成本[[9,7,9,5,8,8,8,8,7,8,7,8,9],[8,9,8,6,6,5,5,5,10,6,8,9,7],[7,8,6,8,9,8,8,8,7,9,10,6,5]];/*** 质量影响参数 ***/
float gamma_pjri[1..R][1..I][1..J] = [// 拆卸方式r对组件i在任务j执行后的质量保留系数[[1,1,1,...], ...], // 具体数据已简略[[1,1,1,...], ...],[[1,1,1,...], ...]
];/*** 任务-组件关系矩阵 ***/
int q_pij[1..I][1..J] = [ // 任务j是否需要组件i存在 (1=需要)[0,0,0,...], [1,0,0,...], ...
];
int d_pij[1..I][1..J] = [ // 任务j对组件i的影响:-1=拆卸,1=获得,0=无关[-1,0,0,...],[1,0,0,...],...
];/*** 任务间关系矩阵 ***/
int r_pjq[1..J][1..J] = [ // 任务冲突矩阵 (1=冲突任务不能分配到同一工作站)[0,0,0,...],[0,0,1,...],...
];
int s_pjq[1..J][1..J] = [ // 任务优先关系矩阵 (1=任务j是q的紧前任务)[0,1,1,...],[0,0,0,...],...
];/*** 收益分段函数参数 ***/
int n = 2; // 分段点数量
float objectiveForXEqualsStart = 0; // 起始点(0,0)
float breakpoint[1..n] = [0.5, 1]; // 分段点位置
float slope[1..n+1] = [0.5, 1.5, 0]; // 各段斜率/*** 决策变量声明 ***/
dvar boolean x_pjkw[1..J][1..W][1..R]; // 任务j分配到工作站w以方式r执行 (二进制)
dvar boolean u_w[1..W]; // 工作站w是否启用 (二进制)
dvar float H_pij[1..I][1..J]; // 执行任务j后组件i的质量 (0-1)
dvar float H_pe[1..I]; // 组件i的最终质量 (取所有相关任务后的最小值)/*** 求解器配置 ***/
execute {cplex.tilim = 10800; // 设置求解时间限制为3小时cplex.optimalitytarget = 3; // 追求全局最优解
}/*** 目标函数:最大化净收益 ***/
maximize
// 第一部分:组件回收收益(考虑质量分段函数)
sum(i in 1..I, j in 1..J, r in 1..R, w in 1..W) (v_mpi[i] * x_pjkw[j][w][r] * d_pij[i][j] * piecewise(i in 1..n) {slope[i] -> breakpoint[i]; slope[n+1]} (0, objectiveForXEqualsStart) H_pe[i]
)
// 第二部分:减去拆卸成本
- sum(j in 1..J, w in 1..W, r in 1..R) (c_pj[r][j] * t_pj[r][j] * x_pjkw[j][w][r]
);/*** 约束条件 ***/
subject to {// 约束1:工作站启用状态必须为二进制 (式4)forall(w in 1..W)u_w[w] <= 1; // 二进制变量自然满足<=1,此处显式声明约束// 约束2:任务只能分配到已启用的工作站 (式7)forall(j in 1..J, w in 1..W, r in 1..R)x_pjkw[j][w][r] <= u_w[w]; // 如果工作站w未启用(u_w=0),则不能分配任务// 约束3:每个任务最多执行一次 (式9)forall(j in 1..J)sum(w in 1..W, r in 1..R) x_pjkw[j][w][r] <= 1; // 所有工作站和方式的选择总和<=1// 约束4:优先关系约束——紧前任务必须执行 (式10)forall(j1 in 1..J, j2 in 1..J)if (s_pjq[j1][j2] == 1) // 如果j1是j2的紧前任务sum(w in 1..W, r in 1..R) x_pjkw[j1][w][r] >= sum(w in 1..W, r in 1..R) x_pjkw[j2][w][r];// 约束5:优先关系约束——工作站顺序 (式11)forall(j in 1..J, q in 1..J)if (s_pjq[j][q] == 1) // j是q的紧前任务sum(w in 1..W, r in 1..R) (w * (x_pjkw[j][w][r] - x_pjkw[q][w][r])) + W * (sum(w in 1..W, r in 1..R) (x_pjkw[q][w][r]) - 1) <= 0; // 确保j分配到的工作站号<=q的工作站号// 约束6:冲突任务不能分配到同一工作站 (式12)forall(j in 1..J, q in 1..J)if (r_pjq[j][q] == 1) // 任务j与q冲突sum(w in 1..W, r in 1..R) (x_pjkw[j][w][r] + x_pjkw[q][w][r]) <= 1; // 两个任务不能同时被分配// 约束7:组件质量更新——初始质量传递 (式13)forall(j in 1..J, w in 1..W, i in 1..I, r in 1..R)H_pij[i][j] <= (1 - x_pjkw[j][w][r]) + H_pi[i] * gamma_pjri[r][i][j]; // 如果任务j未执行(x=0),质量保持原值;否则按gamma系数衰减// 约束8:组件质量更新——前驱任务质量传递 (式14)forall(j in 1..J, v in 1..J, i in 1..I, w in 1..W, r in 1..R)if (s_pjq[v][j] == 1 && q_pij[i][j] == 1) // v是j的前驱任务且j需要组件iH_pij[i][j] <= (1 - x_pjkw[j][w][r]) + H_pij[i][v] * gamma_pjri[r][i][j]; // j的质量继承自前驱任务v的质量// 约束9:质量上限约束 (式15)forall(j in 1..J, i in 1..I)H_pij[i][j] <= H_pi[i]; // 质量不能超过初始质量// 约束10:最终质量取最小值 (式16)forall(j in 1..J, i in 1..I)H_pe[i] <= H_pij[i][j]; // 最终质量是所有相关任务后的最小质量// 非负约束forall(j in 1..J, i in 1..I) {H_pe[i] >= 0;H_pij[i][j] >= 0;}
}/*** 结果输出模块 ***/
execute DISPLAY {// 输出组件价值计算详情writeln("\n=== 组件价值贡献详情 ===");for (var i = 1; i <= I; i++) {var total = 0;for (var j = 1; j <= J; j++) {for (var w = 1; w <= W; w++) {for (var r = 1; r <= R; r++) {if (x_pjkw[j][w][r] == 1) {total += v_mpi[i] * H_pe[i] * d_pij[i][j];}}}}writeln("组件", i, "总贡献值: ", total);}// 输出被拆除组件信息writeln("\n=== 被拆除组件列表 ===");for (var i = 1; i <= I; i++) {var removedValue = 0;for (var j = 1; j <= J; j++) {if (d_pij[i][j] == -1) { // 仅处理拆卸操作for (var w = 1; w <= W; w++) {for (var r = 1; r <= R; r++) {if (x_pjkw[j][w][r] == 1) {removedValue += v_mpi[i] * H_pe[i] * (-d_pij[i][j]); }}}}}if (removedValue > 0) {writeln("组件", i, " 被拆除,价值: ", removedValue);}}// 输出工作站启用状态writeln("\n=== 工作站启用状态 ===");for (var w = 1; w <= W; w++) {writeln("工作站", w, ": ", u_w[w] == 1 ? "启用" : "未启用");}// 输出组件最终质量writeln("\n=== 组件最终质量 ===");for (var i = 1; i <= I; i++) {writeln("组件", i, ": ", H_pe[i]);}// 输出任务分配详情writeln("\n=== 任务分配详情 ===");for (var j = 1; j <= J; j++) {for (var w = 1; w <= W; w++) {for (var r = 1; r <= R; r++) {if (x_pjkw[j][w][r] == 1) {writeln("任务", j, " -> 工作站", w, " 方式", r);}}}}
}
主要注释说明:
-
参数部分
使用多维数组描述拆卸时间、成本、质量影响等复杂关系gamma_pjri三维数组体现不同拆卸方式对质量的差异化影响
-
变量部分
x_pjkw三维二进制变量精确描述"任务-工作站-方式"的三重分配H_pe最终质量变量通过最小值约束确保质量评估的保守性
-
目标函数
使用piecewise函数实现分段线性收益计算第一部分计算组件回收价值,第二部分计算拆卸成本
-
约束体系
约束4-5处理任务优先级,既保证执行顺序又控制工作站分配约束7-8构建质量传递网络,体现任务执行对组件状态的累积影响
-
输出模块
分模块展示组件价值贡献、工作站状态、质量变化等关键信息使用条件判断精准识别被拆卸组件及其价值
分段函数cpelx代码实现
int n=2;
float objectiveForXEqualsStart=0; // 起始点为(0,0)
float breakpoint[1..n]=[0.5,1]; // 分段点为0.5和1
float slope[1..n+1]=[0.5,1.5,0]; // 每段的斜率,第一段斜率为0.5,第二段斜率为1.5,第三段斜率为0
dvar int x;// 分段函数:在x<0.5时,斜率为0.5;0.5≤x<1时,斜率为1.5;x≥1时,斜率为0
maximize
piecewise(i in 1..n)
{slope[i] -> breakpoint[i]; slope[n+1]}(0,objectiveForXEqualsStart) x;
以下面代码为例
piecewise{1 -> 100; 2->200;-3}(0,300) x;
其中, 1, 2, -3 分别是3个线段的斜率, 100, 200 是3个线段的分割点(3个线段有两个分割点), 而 (0, 300) 表示分段线性函数其中一点, x 是自变量。