package com.aps.service.mp;
import com.aps.common.util.FileHelper;
import com.google.ortools.Loader;
import com.google.ortools.linearsolver.*;

/**
 * 作者：佟礼
 * 时间：2026-07-23
 * MIP 主生产排程 Demo
 * 场景：1条产线，2种产品，2天排产
 * 目标：最小化 生产成本 + 库存持有成本 + 换型成本
 */
public class MpsSchedulingDemo {

    public static void main(String[] args) {
        // 1. 加载 OR-Tools 本地库（必须）
        Loader.loadNativeLibraries();

        // ========== 2. 定义参数 ==========
        // 产品
        String[] products = {"P1", "P2"};
        int numProducts = products.length;

        // 时间段（天）
        int numDays = 2;


















        // ========== 3. 创建求解器 ==========
        // 使用 CBC 求解器（开源MIP求解器）
        MPSolver solver = MPSolver.createSolver("CBC");
        if (solver == null) {
            System.err.println("无法创建 CBC 求解器，请检查 OR-Tools 依赖");
            return;
        }

        // ========== 4. 定义决策变量 ==========

        // x[i][t]: 产品i,第0-t天生产的数量（连续变量，≥0）
        MPVariable[][] x = new MPVariable[numProducts][numDays];
        for (int i = 0; i < numProducts; i++) {
            for (int t = 0; t < numDays; t++) {
                x[i][t] = solver.makeNumVar(0, Double.POSITIVE_INFINITY, "x_" + products[i] + "_d" + (t + 1));
            }
        }

        // I[i][t]: 产品i第0-t天末的库存量（连续变量，≥0）
        MPVariable[][] inventory = new MPVariable[numProducts][numDays];
        for (int i = 0; i < numProducts; i++) {
            for (int t = 0; t < numDays; t++) {
                inventory[i][t] = solver.makeNumVar(0, Double.POSITIVE_INFINITY, "I_" + products[i] + "_d" + (t + 1));
            }
        }

        // y[i][t]: 产品i,0-t天是否生产（0-1布尔变量）
        MPVariable[][] y = new MPVariable[numProducts][numDays];
        for (int i = 0; i < numProducts; i++) {
            for (int t = 0; t < numDays; t++) {
                y[i][t] = solver.makeBoolVar("y_" + products[i] + "_d" + (t + 1));
            }
        }

        // ========== 5. 目标函数：最小化总成本 ==========
        MPObjective objective = solver.objective();

        // 单位生产成本 c[i]（元/件）
        double[] unitCost = {5, 8};
        // 生产成本
        for (int i = 0; i < numProducts; i++) {
            for (int t = 0; t < numDays; t++) {
                objective.setCoefficient(x[i][t], unitCost[i]);
            }
        }
        // 单位库存持有成本 h[i]（元/件/天）
        double[] holdingCost = {0.5, 1.0};
        // 库存持有成本
        for (int i = 0; i < numProducts; i++) {
            for (int t = 0; t < numDays; t++) {
                objective.setCoefficient(inventory[i][t], holdingCost[i]);
            }
        }
        // 换型成本 s[i]（元/次）
        double[] setupCost = {100, 150};
        // 换型成本
        for (int i = 0; i < numProducts; i++) {
            for (int t = 0; t < numDays; t++) {
                objective.setCoefficient(y[i][t], setupCost[i]);
            }
        }

        objective.setMinimization();

        // ========== 6. 添加约束 ==========
        // 需求量 D[i][t]：产品i在第t天的需求
        double[][] demand = {
                {80, 60},   // P1: 第1天80, 第2天60
                {50, 70}    // P2: 第1天50, 第2天70
        };
        // 初始库存
        double[] initialInventory = {20, 10};

        // ---- 约束1：库存平衡 ----
        // 第1天：初始库存 + 当天产量 = 当天需求 + 期末库存
        for (int i = 0; i < numProducts; i++) {
            MPConstraint invDay1 = solver.makeConstraint(demand[i][0], demand[i][0], "inv_balance_" + products[i] + "_d1");
            invDay1.setCoefficient(x[i][0], 1);//当天产量*1
            invDay1.setCoefficient(inventory[i][0], -1);//-期末库存*1
            // x[i][0]−inventory[i][0]
            // 移项后: x - I = D - I0   sum(当天产量-期末库存)
            // 当天需求-初始库存 上下限
            //上下界相等 → 等式约束
            //当天产量-期末库存=当天需求-初始库存
            //x[i][0]-inventory[i][0]=demand[i][0] - initialInventory[i]
            //期末库存=当天产量-（当天需求-初始库存）
            //当天产量=当天需求-初始库存+期末库存
            //第 0 天期末库存 0,0 = 当天产量-当天需求(80,50)+初始库存(20,10)
            //当天产量=60,40
            //第 0 天期末库存 10,10 = 当天产量-当天需求(80,50)+初始库存(20,10)
            //当天产量=70,50
            invDay1.setBounds(0, demand[i][0] - initialInventory[i]);
        }

        // 第2天及以后：上期库存 + 当天产量 = 当天需求 + 期末库存
        for (int i = 0; i < numProducts; i++) {
            for (int t = 1; t < numDays; t++) {
                MPConstraint inv = solver.makeConstraint(0, demand[i][t], "inv_balance_" + products[i] + "_d" + (t + 1));
                inv.setCoefficient(inventory[i][t - 1], 1);  // 上期库存
                inv.setCoefficient(x[i][t], 1);              // 当天产量
                inv.setCoefficient(inventory[i][t], -1);     // 期末库存
                //上期库存+当天产量-期末库存=当天需求
                //上期库存(0,0)+当天产量-当天需求(60,70)=期末库存
                //当天产量=当天需求(60,70)-上期库存(0,0)+期末库存(0,0)
                //上期库存(10,10)+当天产量-当天需求(60,70)=期末库存(10,10)
                //当天产量=当天需求(60,70)-上期库存(10,10)+期末库存(10,10)
            }
        }

        // ---- 约束2：安全库存约束 ----
        // 安全库存
        double safetyStock = 10;
        for (int i = 0; i < numProducts; i++) {
            for (int t = 0; t < numDays; t++) {
                MPConstraint ss = solver.makeConstraint(safetyStock, Double.POSITIVE_INFINITY, "safety_stock_" + products[i] + "_d" + (t + 1));
                ss.setCoefficient(inventory[i][t], 1);
               // 期末库存=安全库存
            }
        }

        // ---- 约束3：产能约束 ----

        // 生产效率 p[i]（件/小时）
        double[] productivity = {20, 10};
        // 每日产能（小时）
        double dailyCapacity = 10;

        // 各产品产量/效率之和 ≤ 日产能
        for (int t = 0; t < numDays; t++) {
            MPConstraint cap = solver.makeConstraint(0, dailyCapacity, "capacity_d" + (t + 1));
            for (int i = 0; i < numProducts; i++) {
                //当天产量*1件需要时间<=每日产能
                cap.setCoefficient(x[i][t], 1.0 / productivity[i]);
                //一天最多干多少个
                //当天产量<=生产效率*每日产能
                //当天产量<=20*10,10*10
                // 产品1 最多生产 200个
                //产品2 最多生产 100个
            }
        }
        // 大M常数（足够大的数，用于0-1开关约束）
        double bigM = 10000;
        // ---- 约束4：生产开关约束（大M法）----
        // x[i][t] <= M * y[i][t]  →  x - M*y <= 0
        //M*y=0,时x必须=0
        for (int i = 0; i < numProducts; i++) {
            for (int t = 0; t < numDays; t++) {
                MPConstraint switchCon = solver.makeConstraint(-Double.POSITIVE_INFINITY, 0, "switch_" + products[i] + "_d" + (t + 1));
                switchCon.setCoefficient(x[i][t], 1);
                switchCon.setCoefficient(y[i][t], -bigM);

            }
        }

        // ---- 约束5：产线互斥（一天只能生产一种产品，可选）----
        // 注：如果允许一天内换型多次，可移除此约束
        for (int t = 0; t < numDays; t++) {
            MPConstraint mutex = solver.makeConstraint(0, 1, "mutex_d" + (t + 1));
            for (int i = 0; i < numProducts; i++) {
                mutex.setCoefficient(y[i][t], 1);
                //y[1][1]+y[2][1]<=1; 每天只能生产一个商品，y[1][1]和y[2][1]不能同时为1
                //y[1][2]+y[2][2]<=1;
            }
            //约束5--影响y>约束4---影响y--影响x> x=0的话约束1不成立 初始库存(20) + 当天产量(0) = 当天需求(80) + 期末库存(10) 不成立无解
        }

        // ========== 7. 求解 ==========
        System.out.println("========== 开始求解 ==========");
        MPSolver.ResultStatus status = solver.solve();

        // ========== 8. 输出结果 ==========
        if (status == MPSolver.ResultStatus.OPTIMAL) {
            System.out.println("✅ 找到最优解！");
            System.out.printf("最小总成本：%.2f 元%n", objective.value());
            System.out.println();

            // 按天输出排产结果
            for (int t = 0; t < numDays; t++) {
                System.out.println("━━━━━━━━ 第 " + (t + 1) + " 天 ━━━━━━━━");
                double totalHours = 0;
                for (int i = 0; i < numProducts; i++) {
                    double d=  demand[i][t];
                    double qty = x[i][t].solutionValue();
                    double hours = qty / productivity[i];
                    totalHours += hours;
                    if (qty > 0) {
                        System.out.printf("  生产 %s：%.0f 件，需求%.0f 件 耗时 %.1f 小时，换型成本 %.0f 元%n",
                                products[i], qty,d, hours, y[i][t].solutionValue() * setupCost[i]);
                    } else {
                        System.out.printf("  生产 %s：休息（0件）%n", products[i]);
                    }
                }
                System.out.printf("  当日总工时：%.1f / %.1f 小时%n", totalHours, dailyCapacity);

                System.out.println("  --- 期末库存 ---");
                for (int i = 0; i < numProducts; i++) {
                    System.out.printf("  %s 库存：%.0f 件%n", products[i], inventory[i][t].solutionValue());
                }
                System.out.println();
            }

            // 成本明细
            double totalProdCost = 0, totalHoldCost = 0, totalSetupCost = 0;
            for (int i = 0; i < numProducts; i++) {
                for (int t = 0; t < numDays; t++) {
                    totalProdCost += x[i][t].solutionValue() * unitCost[i];
                    totalHoldCost += inventory[i][t].solutionValue() * holdingCost[i];
                    totalSetupCost += y[i][t].solutionValue() * setupCost[i];
                }
            }
            System.out.println("========== 成本明细 ==========");
            System.out.printf("生产成本：  %.2f 元%n", totalProdCost);
            System.out.printf("库存成本：  %.2f 元%n", totalHoldCost);
            System.out.printf("换型成本：  %.2f 元%n", totalSetupCost);
            System.out.printf("合计：      %.2f 元%n", objective.value());

        } else if (status == MPSolver.ResultStatus.FEASIBLE) {
            System.out.println("⚠️ 找到可行解，但非最优");
            System.out.printf("当前成本：%.2f 元%n", objective.value());
        } else {
            System.out.println("❌ 无解或求解失败，状态：" + status);
        }
        MPModelExportOptions options=new MPModelExportOptions();

        String lpText = solver.exportModelAsLpFormat(false);
        FileHelper.writeFile(lpText,"model.lp");

        for(MPVariable var : solver.variables()){
            String name = var.name();

            double val = var.solutionValue();
            double lb = var.lb();
            double ub = var.ub();

            System.out.printf("变量[%s] 下界=%.2f 上界=%.2f 最优解=%.4f%n",
                    name, lb, ub, val);
        }

        // 求解统计
        System.out.println();
        System.out.println("========== 求解统计 ==========");
        System.out.println("变量数量：" + solver.numVariables());
        System.out.println("约束数量：" + solver.numConstraints());
        System.out.printf("求解时间：%.2f 秒%n", solver.wallTime() / 1000.0);
    }
}
