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

import java.io.*;
import java.nio.charset.StandardCharsets;

/**
 * 作者：佟礼
 * 时间：2026-07-23
 * MIP 主生产排程 Demo
 * 场景：1条产线，2种产品，2天排产
 * 目标：最小化 生产成本 + 库存持有成本 + 换型成本
 * 在MpsSchedulingDemo中发现两个问题，
 * 1 约束5造成一天只能生产一个产品，约束1 产品必须生产，造成无解，2 需求数超过生产能力无解
 */
public class MpsSchedulingDemo2 {


    // 关闭日志文件和恢复流

    public static void main(String[] args) throws FileNotFoundException, UnsupportedEncodingException {
        // 初始化日志文件

        // 1. 加载 OR-Tools 本地库（必须）
        Loader.loadNativeLibraries();

        // ========== 2. 定义参数 ==========
        // 产品
        String[] products = {"P1", "P2"};
        int numProducts = products.length;
// 保存原始控制台输出流
        PrintStream originalOut = System.out;
        PrintStream originalErr = System.err;
        String logPath = "scip_full.log";
        // 时间段（天）
        int numDays = 2;
        PrintStream logWriter = new PrintStream(
                new FileOutputStream(logPath, false),
                true,
                StandardCharsets.UTF_8.name()
        );
        System.setOut(logWriter);
        System.setErr(logWriter);
        // ========== 3. 创建求解器 ==========
        // 使用 CBC 求解器（开源MIP求解器）
       // MPSolver solver = MPSolver.createSolver("CBC");
        MPSolver solver = new MPSolver("demo",
                MPSolver.OptimizationProblemType.SCIP_MIXED_INTEGER_PROGRAMMING);
        if (solver == null) {
            System.err.println("无法创建 CBC 求解器，请检查 OR-Tools 依赖");
            return;
        }
        String params = String.join(";",
                "display/verblevel = 5",          // 最高详细日志，输出每轮Gap
                "display/logfile = solver.log",// 日志写入文件，控制台干净
                "separating/maxrounds = 10",      // 割平面迭代轮数
                "limits/gap = 0.001"            // 最优间隙阈值0.1%
        );

        solver.setSolverSpecificParametersAsString("display/verblevel = 5");
        solver.setSolverSpecificParametersAsString("separating/maxrounds = 10");
        solver.setSolverSpecificParametersAsString("limits/gap = 0.001");
        solver.setSolverSpecificParametersAsString("limits/time = 300");
        solver.enableOutput();
        // ========== 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));
            }
        }
        // shortfall[i][t]: 产品i第t天未满足的需求量（≥0），解决互斥约束下某产品产量=0时等式不成立的问题
        MPVariable[][] shortfall = new MPVariable[numProducts][numDays];
        for (int i = 0; i < numProducts; i++) {
            for (int t = 0; t < numDays; t++) {
                shortfall[i][t] = solver.makeNumVar(0, Double.POSITIVE_INFINITY, "short_" + 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]);
            }
        }

        // 缺口惩罚（元/件），权重最大，优先满足需求
        double shortfallPenalty = 1000;
        for (int i = 0; i < numProducts; i++) {
            for (int t = 0; t < numDays; t++) {
                objective.setCoefficient(shortfall[i][t], shortfallPenalty);
            }
        }
        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天：初始库存 + 当天产量 = 当天需求 + 期末库存,当天产量-期末库存=当天需求-初始库存
        // ---- 约束1：库存平衡（加入shortfall吸收缺口）----
        // 公式：x + shortfall − inventory = demand − startInventory
        // 第1天：startInventory = initialInventory

        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]);

         //   x + shortfall − inventory = demand − startInventory
            //P1 130+0-70=80-20
            //P2 0+50-10 =50-10
            MPConstraint invDay1 = solver.makeConstraint(
                    demand[i][0] - initialInventory[i],
                    demand[i][0] - initialInventory[i],
                    "inv_balance_" + products[i] + "_d1");
            invDay1.setCoefficient(x[i][0], 1);
            invDay1.setCoefficient(shortfall[i][0], 1);   // 缺口补等式
            invDay1.setCoefficient(inventory[i][0], -1);

        }

        // 第2天及以后：上期库存 + 当天产量 = 当天需求 + 期末库存
        // 第2天及以后：startInventory = 上期期末库存
        // 公式：inventory[i][t-1] + x[i][t] + shortfall[i][t] = demand[i][t] + inventory[i][t]
        // 移项后：inventory[i][t-1] + x[i][t] + shortfall[i][t] - inventory[i][t] = demand[i][t]

        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)
                // 公式：inventory[i][t-1] + x[i][t] + shortfall[i][t] = demand[i][t] + inventory[i][t]

                //P1 0+0-10=60-70
                //P2 70+0-10 =70-10
                MPConstraint inv = solver.makeConstraint(
                        demand[i][t], 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(shortfall[i][t], 1);      // 缺口补等式
                inv.setCoefficient(inventory[i][t], -1);     // 期末库存

            }
        }

        // ---- 约束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 sht = shortfall[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]);
                    }
                    if (sht > 0.001) {
                        System.out.printf("    ⚠ 未满足需求（shortfall）：%.0f 件%n", sht);
                    }
                }
                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, totalShortCost = 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];
                    totalShortCost += shortfall[i][t].solutionValue() * shortfallPenalty;
                }
            }
            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", totalShortCost);
            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");
        String mpsText = solver.exportModelAsMpsFormat(true,true);
        FileHelper.writeFile(mpsText,"model.mps");
        // 输出LP模型到控制台
        System.out.println("========== LP模型输出 ==========");
       // System.out.println(lpText);

        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);
    }
}
