You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在GLPK.js中设置‘工作时长为0或≥3小时’的约束?

员工排班优化的线性规划约束问题

问题背景

我用Node.js开发一款考虑多约束(周工作时长、人员可用性等)的周排班优化应用,采用glpk.js实现线性规划。目前已能生成排班,但在添加新约束时遇到问题:需实现员工若当班则工作时长至少3小时,否则为0小时。现有代码已添加「当班至少2小时」的约束,尝试使用glpk.GLP_DP未成功,求可行的约束设置方案。

相关类型定义

export interface LPEmployeeInput {
  id: number;
  name: string;
  availability: number[];
  hoursPerWeek: number;
  formations: string[];
}
export interface LPDailyNeed {
  hour: number;
  kitchen: number;
  service: number;
  manager: number;
}

日需求示例

[
        { hour: 11, kitchen: 1, service: 1, manager: 1 },
        { hour: 12, kitchen: 1, service: 2, manager: 1 },
        { hour: 13, kitchen: 1, service: 2, manager: 1 },
        { hour: 14, kitchen: 1, service: 2, manager: 1 },

        { hour: 18, kitchen: 2, service: 1, manager: 1 },
        { hour: 19, kitchen: 2, service: 2, manager: 1 },
        { hour: 20, kitchen: 2, service: 2, manager: 1 },
        { hour: 21, kitchen: 2, service: 1, manager: 1 }
]

现有LP求解代码片段

const lp: LP = {
    name: 'Employee Scheduling',
    objective: {
        direction: glpk.GLP_MAX,
        name: 'total_hours',
        vars: []
    },
    subjectTo: []
};

// 添加每日时长约束
for (let i = 0; i < numEmployees; i++) {
    for (let j = 0; j < numDays; j++) {
        const noonVars: any[] = [];
        const eveningVars: any[] = [];

        for (let k = 0; k < dailyNeeds[j].length; k++) {
            if(dailyNeeds[j][k].hour < shiftBreakpoint && employees[i].availability[j * 2] == 1){
                // 午班变量
                noonVars.push({ name: `x_${i}_${j}_${k}`, coef: 1 });

            } else if(dailyNeeds[j][k].hour >= shiftBreakpoint && employees[i].availability[j * 2 + 1] == 1){
                // 晚班变量
                eveningVars.push({ name: `x_${i}_${j}_${k}`, coef: 1 });
            }
        }
        if(employees[i].availability[j * 2] == 1){
            lp.subjectTo.push({
                name: `daily_hours_${i}_${j}_noon`,
                vars: noonVars,
                bnds: { type: glpk.GLP_LO, ub: Infinity, lb: 2 }
            });

        }
        if(employees[i].availability[j * 2 + 1] == 1){
            lp.subjectTo.push({
                name: `daily_hours_${i}_${j}_evening`,
                vars: eveningVars,
                bnds: { type: glpk.GLP_LO, ub: Infinity, lb: 2 }
            });

        }

    }
}

// 其他约束...

// 求解问题
const result = glpk.solve(lp, glpk.GLP_MSG_ALL);

解决方案

单纯的线性规划无法直接表达「要么0小时要么至少3小时」的离散逻辑,必须引入二进制决策变量将问题转化为整数规划。具体实现步骤如下:

1. 添加二进制变量

为每个员工的每个时段(午班/晚班)创建二进制变量,标记是否当班:

  • y_i_j_noon = 1:员工i在第j天午班当班;0:不当班
  • y_i_j_evening = 1:员工i在第j天晚班当班;0:不当班

在LP模型中添加这些变量:

// 为员工的午班/晚班添加二进制变量
for (let i = 0; i < numEmployees; i++) {
    for (let j = 0; j < numDays; j++) {
        // 午班二进制变量
        if (employees[i].availability[j * 2] == 1) {
            lp.objective.vars.push({
                name: `y_${i}_${j}_noon`,
                coef: 0 // 目标函数不涉及该变量,系数设为0
            });
        }
        // 晚班二进制变量
        if (employees[i].availability[j * 2 + 1] == 1) {
            lp.objective.vars.push({
                name: `y_${i}_${j}_evening`,
                coef: 0
            });
        }
    }
}

2. 替换原约束,添加逻辑约束

将原「至少2小时」约束替换为两组约束,实现「0或≥3小时」的逻辑:

  • 约束1:若当班(y=1),工作时长≥3小时
  • 约束2:若不当班(y=0),工作时长=0小时

代码实现:

for (let i = 0; i < numEmployees; i++) {
    for (let j = 0; j < numDays; j++) {
        const noonVars: any[] = [];
        const eveningVars: any[] = [];

        for (let k = 0; k < dailyNeeds[j].length; k++) {
            if(dailyNeeds[j][k].hour < shiftBreakpoint && employees[i].availability[j * 2] == 1){
                noonVars.push({ name: `x_${i}_${j}_${k}`, coef: 1 });
            } else if(dailyNeeds[j][k].hour >= shiftBreakpoint && employees[i].availability[j * 2 + 1] == 1){
                eveningVars.push({ name: `x_${i}_${j}_${k}`, coef: 1 });
            }
        }

        // 处理午班约束
        if(employees[i].availability[j * 2] == 1){
            const maxNoonHours = noonVars.length; // 午班最大可工作时长

            // 约束1:工作时长 ≥ 3*y_i_j_noon
            lp.subjectTo.push({
                name: `noon_min_hours_${i}_${j}`,
                vars: [
                    ...noonVars,
                    { name: `y_${i}_${j}_noon`, coef: -3 }
                ],
                bnds: { type: glpk.GLP_LO, lb: 0, ub: Infinity }
            });

            // 约束2:工作时长 ≤ maxNoonHours * y_i_j_noon
            lp.subjectTo.push({
                name: `noon_max_hours_${i}_${j}`,
                vars: [
                    ...noonVars,
                    { name: `y_${i}_${j}_noon`, coef: -maxNoonHours }
                ],
                bnds: { type: glpk.GLP_UP, lb: -Infinity, ub: 0 }
            });
        }

        // 处理晚班约束
        if(employees[i].availability[j * 2 + 1] == 1){
            const maxEveningHours = eveningVars.length;

            // 约束1:工作时长 ≥ 3*y_i_j_evening
            lp.subjectTo.push({
                name: `evening_min_hours_${i}_${j}`,
                vars: [
                    ...eveningVars,
                    { name: `y_${i}_${j}_evening`, coef: -3 }
                ],
                bnds: { type: glpk.GLP_LO, lb: 0, ub: Infinity }
            });

            // 约束2:工作时长 ≤ maxEveningHours * y_i_j_evening
            lp.subjectTo.push({
                name: `evening_max_hours_${i}_${j}`,
                vars: [
                    ...eveningVars,
                    { name: `y_${i}_${j}_evening`, coef: -maxEveningHours }
                ],
                bnds: { type: glpk.GLP_UP, lb: -Infinity, ub: 0 }
            });
        }
    }
}

3. 声明二进制变量类型

glpk.js需要明确变量为二进制类型,需在求解前通过API设置:

// 收集所有二进制变量名称
const binaryVars = [];
for (let i = 0; i < numEmployees; i++) {
    for (let j = 0; j < numDays; j++) {
        if (employees[i].availability[j * 2] == 1) {
            binaryVars.push(`y_${i}_${j}_noon`);
        }
        if (employees[i].availability[j * 2 + 1] == 1) {
            binaryVars.push(`y_${i}_${j}_evening`);
        }
    }
}

// 读取LP模型并设置变量类型
const prob = glpk.readLP(lp);
binaryVars.forEach(varName => {
    const idx = glpk.get_col_index(prob, varName);
    glpk.set_col_kind(prob, idx, glpk.GLP_BV); // GLP_BV表示二进制变量
});

// 求解整数规划问题
const result = glpk.solveLP(prob, glpk.GLP_MSG_ALL);

关键说明

  • 原线性约束无法表达离散逻辑,必须引入二进制变量转化为整数规划
  • maxNoonHours/maxEveningHours是对应时段的总小时数,用来确保不当班时工作时长强制为0
  • glpk.js原生支持整数/二进制规划,只需正确声明变量类型并添加对应约束即可

内容的提问来源于stack exchange,提问作者Mirecos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 07:05:01