Commit fd3b9e49 authored by Tong Li's avatar Tong Li

MP-将kpi设置接入,优化器

parent 2f9e7b36
......@@ -532,7 +532,8 @@ public class MacroPlannerResultController {
description = "执行数据库转换→数据验证→MIP建模→分层求解→JSON结果保存的完整流程。"
+ "耗时通常数秒到数分钟, 取决于数据规模。")
public R<Map<String, Object>> runOptimization(
@RequestParam("sceneId") @Parameter(description = "场景ID", required = true) String sceneId) {
@RequestParam("sceneId") @Parameter(description = "场景ID", required = true) String sceneId,
@RequestParam("kpiSetting") @Parameter(description = "kpi设置", required = true) Integer kpiSetting) {
if (sceneId == null || sceneId.trim().isEmpty()) {
return R.failed("sceneId不能为空");
}
......@@ -543,7 +544,7 @@ public class MacroPlannerResultController {
try {
// 1. 数据转换: 数据库工艺物料类 → macroplanner 实体
TestDataBuilder data = macroPlannerDataConverter.convert(sid);
TestDataBuilder data = macroPlannerDataConverter.convert(sid,kpiSetting);
result.put("dataSummary", buildDataSummary(data));
// 2. 数据验证
......@@ -560,7 +561,7 @@ public class MacroPlannerResultController {
long solveStart = System.currentTimeMillis();
MacroPlannerOptimizer optimizer = new MacroPlannerOptimizer(data);
optimizer.buildModel();
optimizer.solve();
optimizer.solve(sid);
long solveEnd = System.currentTimeMillis();
// 4. 保存结果到 JSON 文件
......
......@@ -30,7 +30,7 @@ import org.springframework.context.ApplicationContext;
public class MacroPlannerDataConverterRunner {
public static void main(String[] args) {
String sceneId = (args.length > 0) ? args[0] : "ff8a2f2b-e134-4ec2-8e2e-b77e830c9e64";
String sceneId = (args.length > 0) ? args[0] : "15b4cf5b-1224-418e-aa6f-1c643ea78d3c";
System.out.println("===== MACROPLANNER DATA CONVERTER RUNNER START =====");
System.out.println("SceneId: " + sceneId);
......@@ -40,7 +40,7 @@ public class MacroPlannerDataConverterRunner {
MacroPlannerDataConverter converter = ctx.getBean(MacroPlannerDataConverter.class);
// 1. 转换数据: 数据库工艺物料类 → macroplanner 实体
TestDataBuilder data = converter.convert(sceneId);
TestDataBuilder data = converter.convert(sceneId,8);
// 2. 打印转换结果规模
System.out.println("Data converted:");
......
package com.aps.macroplanner;
import com.aps.common.util.FileHelper;
import com.aps.macroplanner.objective.KpiLib;
import com.aps.macroplanner.objective.KpiSetting;
import com.aps.macroplanner.output.dto.OptimizationResult;
import com.aps.service.MpPispipResultPersistenceService;
import com.google.ortools.Loader;
......@@ -25,9 +27,12 @@ import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
/**
* MacroPlanner 核心优化器 — 供应链产能规划混合整数规划(MIP)模型
......@@ -197,11 +202,11 @@ public class MacroPlannerOptimizer {
writeLog(" [OK] 约束与KPI汇总创建完成");
// 3. 目标函数 (加权求和)
ObjectiveBuilder.build(model, data);
// ObjectiveBuilder.build(model, data);
writeLog(" [OK] 目标函数创建完成");
// 4. 导出 LP 模型文件
exportLpModel();
//exportLpModel();
writeLog("\n模型统计: 变量=" + model.getSolver().numVariables()
+ ", 约束=" + model.getSolver().numConstraints() + "\n");
......@@ -279,12 +284,13 @@ public class MacroPlannerOptimizer {
final long startTimeMs = System.currentTimeMillis();
// ========== 1. 前置参数校验 ==========
KPIWeights w = data.getKpiWeights();
List<KpiSetting> w = data.getKpiSettings();
List<StrategyLevel> levels=new ArrayList<>();
if (w == null) {
writeLog("[ERROR] KPI权重配置为空,终止求解");
return;
levels = defineLevels(data.getKpiWeights());
}else {
levels = getKpiLevels(w);
}
List<StrategyLevel> levels = defineLevels(w);
if (levels == null || levels.isEmpty()) {
writeLog("[ERROR] 分层策略为空,终止求解");
return;
......@@ -427,31 +433,64 @@ public class MacroPlannerOptimizer {
// === Level 1: 需求满足 (最高优先级, 严格分层, slack=0%) ===
StrategyLevel l1 = new StrategyLevel(1, "需求满足", 0.0);
l1.addKPI("需求缺口", model.getTotalFulfillment(), w.getFulfillmentWeight());
l1.addKPI(KpiLib.Fulfillment.getEn(),KpiLib.Fulfillment.getCn(), model.getTotalFulfillment(), w.getFulfillmentWeight());
levels.add(l1);
// === Level 2: 产能约束 (物理硬约束, 严格分层, slack=0%) ===
StrategyLevel l2 = new StrategyLevel(2, "产能约束", 0.0);
l2.addKPI("产能超载", model.getTotalUnitCapacity(), w.getUnitCapacityWeight());
l2.addKPI(KpiLib.UnitCapacity.getEn(),KpiLib.Fulfillment.getCn(), model.getTotalUnitCapacity(), w.getUnitCapacityWeight());
levels.add(l2);
// === Level 3: 业务KPI (允许 5% 退化, slack=5%) ===
StrategyLevel l3 = new StrategyLevel(3, "业务KPI", 0.05);
l3.addKPI("批次偏差", model.getTotalLotSize(), w.getLotSizeWeight());
l3.addKPI("目标库存偏差", model.getTotalTargetInvLevel(), w.getTargetInventoryLevelWeight());
l3.addKPI("供应目标偏差", model.getTotalSupplyTarget(), w.getSupplyTargetWeight());
l3.addKPI("销售优先级", model.getTotalSalesDemandPriority(),
l3.addKPI(KpiLib.LotSize.getEn(),KpiLib.LotSize.getCn(), model.getTotalLotSize(), w.getLotSizeWeight());
l3.addKPI(KpiLib.TargetInvLevel.getEn(),KpiLib.TargetInvLevel.getCn(), model.getTotalTargetInvLevel(), w.getTargetInventoryLevelWeight());
l3.addKPI(KpiLib.SupplyTarget.getEn(),KpiLib.SupplyTarget.getCn(), model.getTotalSupplyTarget(), w.getSupplyTargetWeight());
l3.addKPI(KpiLib.SalesDemandPriority.getEn(),KpiLib.SalesDemandPriority.getCn(), model.getTotalSalesDemandPriority(),
w.getSalesDemandPriorityWeight(), true); // 负系数 = 最大化
levels.add(l3);
// === Level 4: 软约束 (允许 10% 退化, slack=10%) ===
StrategyLevel l4 = new StrategyLevel(4, "软约束", 0.10);
l4.addKPI("超库存", model.getTotalMaxInventoryLevel(), w.getMaxInventoryLevelWeight());
l4.addKPI("欠库存", model.getTotalMinInventoryLevel(), w.getMinInventoryLevelWeight());
l4.addKPI("最小供应不足", model.getTotalMinSupply(), w.getMinSupplyWeight());
l4.addKPI("最大供应超出", model.getTotalMaxSupply(), w.getMaxSupplyWeight());
l4.addKPI(KpiLib.MaxInventoryLevel.getEn(),KpiLib.MaxInventoryLevel.getCn(), model.getTotalMaxInventoryLevel(), w.getMaxInventoryLevelWeight());
l4.addKPI(KpiLib.MinInventoryLevel.getEn(),KpiLib.MinInventoryLevel.getCn(), model.getTotalMinInventoryLevel(), w.getMinInventoryLevelWeight());
l4.addKPI(KpiLib.MinSupply.getEn(),KpiLib.MinSupply.getCn(), model.getTotalMinSupply(), w.getMinSupplyWeight());
l4.addKPI(KpiLib.MaxSupply.getEn(),KpiLib.MaxSupply.getCn(), model.getTotalMaxSupply(), w.getMaxSupplyWeight());
levels.add(l4);
return levels;
}
private List<StrategyLevel> getKpiLevels(List<KpiSetting> kpis) {
List<StrategyLevel> levels = new ArrayList<>();
Map<Integer, List<KpiSetting>> groupByLevelMap = kpis.stream()
.collect(Collectors.groupingBy(
KpiSetting::getLevel, //如果改成getter
TreeMap::new,
Collectors.toList()
));
for (Map.Entry<Integer, List<KpiSetting>> entry : groupByLevelMap.entrySet()) {
Integer currentLevel = entry.getKey();
List<KpiSetting> kpiGroup = entry.getValue();
StrategyLevel level = new StrategyLevel(currentLevel, "", 0.05);
// 遍历当前分组里面每一个KPI
for (KpiSetting kpi : kpiGroup) {
level.addKPI(kpi.code, kpi.name, model.getKpi(kpi.code), kpi.weight);
}
levels.add(level);
}
return levels;
}
......
......@@ -2,10 +2,12 @@ package com.aps.macroplanner.data;
import com.aps.common.util.ParamValidator;
import com.aps.entity.*;
import com.aps.macroplanner.objective.KpiSetting;
import com.aps.macroplanner.scene.MacroSceneContext;
import com.aps.entity.basic.Material;
import com.aps.entity.basic.MaterialSupply;
import com.aps.mapper.EquipShiftCapacityMapper;
import com.aps.service.KpiCategoryItemService;
import com.aps.service.PlanPeriodService;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.core.type.TypeReference;
......@@ -131,6 +133,9 @@ public class MacroPlannerDataConverter {
@Autowired
private PlanPeriodService planPeriodService;
@Autowired
private KpiCategoryItemService kpiCategoryItemService;
// ==================== 公开入口 ====================
/**
......@@ -139,16 +144,16 @@ public class MacroPlannerDataConverter {
* @param sceneId 场景ID
* @return 填充好的 TestDataBuilder, 可直接传给 MacroPlannerOptimizer
*/
public TestDataBuilder convert(String sceneId) {
return MacroSceneContext.execute(sceneId, () -> convertScoped(sceneId));
public TestDataBuilder convert(String sceneId,Integer kpiSetting) {
return MacroSceneContext.execute(sceneId, () -> convertScoped(sceneId, kpiSetting));
}
private TestDataBuilder convertScoped(String sceneId) {
private TestDataBuilder convertScoped(String sceneId,Integer kpiSetting) {
log.info("开始转换场景数据到 macroplanner: sceneId={}", sceneId);
ConvertContext ctx = loadRawData(sceneId);
ConvertContext ctx = loadRawData(sceneId,kpiSetting);
buildMaterials(ctx);
buildDailyCapacityByUnitId(ctx);
......@@ -167,7 +172,7 @@ public class MacroPlannerDataConverter {
// ==================== 步骤1: 数据加载 ====================
private ConvertContext loadRawData(String sceneId) {
private ConvertContext loadRawData(String sceneId,Integer kpiSetting) {
ConvertContext ctx = new ConvertContext();
// 0. 读取时间配置, 计算 horizonEnd
......@@ -395,6 +400,14 @@ public class MacroPlannerDataConverter {
}
log.info("加载设备产能日历(EquipShiftCapacity): {} 条 (时间范围过滤)", ctx.equipShiftCapacities.size());
ctx.kpiCategoryItems = kpiCategoryItemService.list(
new LambdaQueryWrapper<KpiCategoryItem>()
.eq(KpiCategoryItem::getIsDeleted, 0)
.eq(KpiCategoryItem::getCategoryId, kpiSetting));
return ctx;
}
......@@ -720,7 +733,7 @@ public class MacroPlannerDataConverter {
fillPeriodsAndUnitPeriods(ctx);
fillSalesDemands(ctx);
fillInventorySpecsAndSupplySpecs(ctx);
fillKpiWeights();
fillKpiWeights(ctx);
}
// ---------- 步骤3: Product + StockingPoint ----------
......@@ -1499,21 +1512,15 @@ public class MacroPlannerDataConverter {
// ---------- 步骤10: KPIWeights (默认值, 复用 RoutingTestDataBuilder) ----------
private void fillKpiWeights() {
kpiWeights = new KPIWeights(
100.0, // fulfillmentWeight
10.0, // lotSizeWeight
5.0, // maxInventoryLevelWeight
5.0, // minInventoryLevelWeight
8.0, // targetInventoryLevelWeight
3.0, // unitCapacityWeight
8.0, // supplyTargetWeight
5.0, // minSupplyWeight
5.0, // maxSupplyWeight
1.0, // salesDemandPriorityWeight
20.0, // postponementPenaltyWeight
5.0 // processMaxQuantityWeight
);
private void fillKpiWeights(ConvertContext ctx) {
List<KpiSetting> kpis=new ArrayList<>();
for (KpiCategoryItem item:ctx.kpiCategoryItems) {
KpiSetting kpi=new KpiSetting(item.getKpiName(), item.getKpiCode(),item.getWeight().doubleValue(),item.getSort().intValue());
kpis.add(kpi);
}
kpiSettings = kpis;
}
// ---------- 工具方法 ----------
......@@ -1549,6 +1556,11 @@ public class MacroPlannerDataConverter {
List<Equipinfo> equipinfos = new ArrayList<>();
List<PlanResource> planResources = new ArrayList<>();
List<EquipShiftCapacity> equipShiftCapacities = new ArrayList<>();
List<KpiCategoryItem> kpiCategoryItems = new ArrayList<>();
LocalDateTime baseTime;
/** 计划截止日期 (不包含), 由 baseTime + periodDimension + periodCount 计算 */
LocalDate horizonEnd;
......
package com.aps.macroplanner.data;
import com.aps.macroplanner.objective.KpiSetting;
import java.util.*;
import java.time.LocalDate;
......@@ -57,7 +59,12 @@ public class TestDataBuilder {
/** 在途供应列表 (供应商已发货, 将在未来周期到货的固定量) */
protected final List<InTransitSupply> inTransitSupplies = new ArrayList<>();
/** KPI 权重 (子类可访问) */
protected KPIWeights kpiWeights;
protected KPIWeights kpiWeights;
protected List<KpiSetting> kpiSettings = new ArrayList<>();
/** 初始库存: key = productId_spId, value = 初始库存量 (子类可访问) */
/** 初始库存列表 (子类可访问) */
......@@ -278,21 +285,20 @@ public class TestDataBuilder {
supplySpecs.add(new SupplySpec("Supply-R2-Alt", 40.0, 20.0, 80.0,
false, Arrays.asList( opR2Alt)));
// === KPI 权重 ===
kpiWeights = new KPIWeights(
100.0, // fulfillmentWeight
10.0, // lotSizeWeight
5.0, // maxInventoryLevelWeight
5.0, // minInventoryLevelWeight
8.0, // targetInventoryLevelWeight
20.0, // unitCapacityWeight
8.0, // supplyTargetWeight
5.0, // minSupplyWeight
5.0, // maxSupplyWeight
1.0, // salesDemandPriorityWeight
20.0, // postponementPenaltyWeight
5.0 // processMaxQuantityWeight
);
List<KpiSetting> kpis=new ArrayList<>();
kpis.add(new KpiSetting("需求满足","Fulfillment",100,1));
kpis.add(new KpiSetting("产能","UnitCapacity",20,1));
kpis.add(new KpiSetting("批次","LotSize",10,1));
kpis.add(new KpiSetting("目标库存","TargetInvLevel",8,1));
kpis.add(new KpiSetting("供应目标","SupplyTarget",8,1));
kpis.add(new KpiSetting("销售优先级","SalesDemandPriority",1,1));
kpis.add(new KpiSetting("最大库存","MaxInventoryLevel",5,1));
kpis.add(new KpiSetting("最小库存","MinInventoryLevel",5,1));
kpis.add(new KpiSetting("最小供应","MinSupply",5,1));
kpis.add(new KpiSetting("最大供应","MaxSupply",5,1));
kpiSettings=kpis;
}
// ==================== Getters ====================
......@@ -395,8 +401,11 @@ public class TestDataBuilder {
public List<InitialInventory> getInitialInventories() { return initialInventories; }
public List<KpiSetting> getKpiSettings() { return kpiSettings; }
public KPIWeights getKpiWeights() { return kpiWeights; }
/**
* 获取初始库存量
* @param productId 产品ID
......
package com.aps.macroplanner.model;
import com.aps.macroplanner.objective.KpiLib;
import com.google.ortools.linearsolver.MPSolver;
import com.google.ortools.linearsolver.MPVariable;
......@@ -170,4 +171,46 @@ public class MacroPlannerModel {
public MPVariable getTotalSalesDemandPriority() { return totalSalesDemandPriority; }
public MPVariable getTotalPostponementPenalty() { return totalPostponementPenalty; }
public MPVariable getTotalProcessMaxQuantity() { return totalProcessMaxQuantity; }
public MPVariable getKpi(String code) {
MPVariable kpivar = null;
KpiLib kpi = KpiLib.ofEn(code);
switch (kpi) {
case Fulfillment:
kpivar = totalFulfillment;
break;
case UnitCapacity:
kpivar = totalUnitCapacity;
break;
case LotSize:
kpivar = totalLotSize;
break;
case TargetInvLevel:
kpivar = totalTargetInvLevel;
break;
case SupplyTarget:
kpivar = totalSupplyTarget;
break;
case SalesDemandPriority:
kpivar = totalSalesDemandPriority;
break;
case MaxInventoryLevel:
kpivar = totalMaxInventoryLevel;
break;
case MinInventoryLevel:
kpivar = totalMinInventoryLevel;
break;
case MinSupply:
kpivar = totalMinSupply;
break;
case MaxSupply:
kpivar = totalMaxSupply;
break;
}
return kpivar;
}
}
\ No newline at end of file
package com.aps.macroplanner.objective;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 作者:佟礼
* 时间:2026-09-22
*/
public enum KpiLib {
Fulfillment("需求满足"),
UnitCapacity("产能"),
LotSize("批次"),
TargetInvLevel("目标库存"),
SupplyTarget("供应目标"),
SalesDemandPriority("销售优先级"),
MaxInventoryLevel("最大库存"),
MinInventoryLevel("最小库存"),
MinSupply("最小供应"),
MaxSupply("最大供应");
private final String en;
private final String cn;
KpiLib(String cn) {
this.en = this.name();
this.cn = cn;
}
public String getEn() {
return en;
}
public String getCn() {
return cn;
}
/**
* 转为 Map<英文编码,中文名称>,用于批量翻译、表头转换
*/
public static Map<String, String> toEnCnMap() {
return Arrays.stream(KpiLib.values())
.collect(Collectors.toMap(KpiLib::getEn, KpiLib::getCn));
}
/**
* 根据英文编码获取枚举,返回null则未匹配
*/
public static KpiLib ofEn(String enName) {
for (KpiLib dict : KpiLib.values()) {
if (dict.getEn().equals(enName)) {
return dict;
}
}
return null;
}
}
package com.aps.macroplanner.objective;
import com.google.ortools.linearsolver.MPVariable;
import lombok.Data;
/**
* 作者:佟礼
* 时间:2026-09-21
*/
@Data
public class KpiSetting {
public final String code;
/**
* 该 KPI 在当前层级内的权重
*/
public final double weight;
public final Integer level;
/**
* KPI 名称
*/
public final String name;
public KpiSetting(String name, String code, double weight, Integer level) {
this.name = name;
this.code = code;
this.weight = weight;
this.level = level;
}
}
......@@ -54,63 +54,63 @@ public class ObjectiveBuilder {
*/
public static void build(MacroPlannerModel model, TestDataBuilder data) {
MPObjective objective = model.getSolver().objective();
KPIWeights w = data.getKpiWeights();
// KPIWeights w = data.getKpiWeights();
// ===== 惩罚项 (最小化: 正系数) =====
// Fulfillment: 需求松弛惩罚, 权重 100.0 (最高优先级)
// 含义: 所有周期所有产品的需求松弛总和。值越小表示需求满足越好。
objective.setCoefficient(model.getTotalFulfillment(), w.getFulfillmentWeight());
// LotSize: 批次偏差惩罚, 权重 10.0
// 含义: 所有操作所有周期的批次大小偏差总和。惩罚不按批次生产的量。
objective.setCoefficient(model.getTotalLotSize(), w.getLotSizeWeight());
// MaxInventoryLevel: 超库存惩罚, 权重 5.0
// 含义: 所有超标库存的总和。惩罚库存超过最大水平。
objective.setCoefficient(model.getTotalMaxInventoryLevel(), w.getMaxInventoryLevelWeight());
// MinInventoryLevel: 欠库存(安全库存)惩罚, 权重 5.0
// 含义: 所有低于最小库存的总和。惩罚库存低于安全水平。
objective.setCoefficient(model.getTotalMinInventoryLevel(), w.getMinInventoryLevelWeight());
// TargetInventoryLevel: 目标库存偏差惩罚, 权重 8.0
// 含义: 所有低于目标库存的总和。惩罚库存偏离目标水平。
objective.setCoefficient(model.getTotalTargetInvLevel(), w.getTargetInventoryLevelWeight());
// UnitCapacity: 产能超载惩罚, 权重 3.0
// 含义: 所有设备所有周期的产能超载总和。惩罚超负荷生产。
objective.setCoefficient(model.getTotalUnitCapacity(), w.getUnitCapacityWeight());
// SupplyTarget: 供应目标偏差惩罚, 权重 8.0
// 含义: 供应低于目标的总缺口。惩罚供应不足。
objective.setCoefficient(model.getTotalSupplyTarget(), w.getSupplyTargetWeight());
// MinSupply: 最小供应不足惩罚, 权重 5.0
// 含义: 供应低于最小值的总缺口。惩罚供应严重不足。
objective.setCoefficient(model.getTotalMinSupply(), w.getMinSupplyWeight());
// MaxSupply: 最大供应超出惩罚, 权重 5.0
// 含义: 供应超出最大值的总量。惩罚过度供应。
objective.setCoefficient(model.getTotalMaxSupply(), w.getMaxSupplyWeight());
// ===== 收益项 (最大化: 负系数, 因为 OR-Tools 默认最小化) =====
// SalesDemandPriority: 销售需求优先级, 权重 1.0, 负系数 = 最大化
// 含义: 按优先级加权的销售需求满足量。值越大表示满足的需求越多越好。
// 因目标函数是最小化, 此项使用负系数 (最大化 = 加负号后最小化)。
objective.setCoefficient(model.getTotalSalesDemandPriority(), -w.getSalesDemandPriorityWeight());
// PostponementPenalty: 推迟惩罚, 权重 20.0 (简化版固定为 0)
// 含义: 对应原模型中的延迟销售需求惩罚。当前版本未实现推迟逻辑。
objective.setCoefficient(model.getTotalPostponementPenalty(), w.getPostponementPenaltyWeight());
// ProcessMaxQuantity: 过程最大量惩罚, 权重 5.0 (简化版固定为 0)
// 含义: 对应原模型中超出最大过程量的惩罚。当前版本未实现。
objective.setCoefficient(model.getTotalProcessMaxQuantity(), w.getProcessMaxQuantityWeight());
// 设置为最小化目标
objective.setMinimization();
// objective.setCoefficient(model.getTotalFulfillment(), w.getFulfillmentWeight());
//
// // LotSize: 批次偏差惩罚, 权重 10.0
// // 含义: 所有操作所有周期的批次大小偏差总和。惩罚不按批次生产的量。
// objective.setCoefficient(model.getTotalLotSize(), w.getLotSizeWeight());
//
// // MaxInventoryLevel: 超库存惩罚, 权重 5.0
// // 含义: 所有超标库存的总和。惩罚库存超过最大水平。
// objective.setCoefficient(model.getTotalMaxInventoryLevel(), w.getMaxInventoryLevelWeight());
//
// // MinInventoryLevel: 欠库存(安全库存)惩罚, 权重 5.0
// // 含义: 所有低于最小库存的总和。惩罚库存低于安全水平。
// objective.setCoefficient(model.getTotalMinInventoryLevel(), w.getMinInventoryLevelWeight());
//
// // TargetInventoryLevel: 目标库存偏差惩罚, 权重 8.0
// // 含义: 所有低于目标库存的总和。惩罚库存偏离目标水平。
// objective.setCoefficient(model.getTotalTargetInvLevel(), w.getTargetInventoryLevelWeight());
//
// // UnitCapacity: 产能超载惩罚, 权重 3.0
// // 含义: 所有设备所有周期的产能超载总和。惩罚超负荷生产。
// objective.setCoefficient(model.getTotalUnitCapacity(), w.getUnitCapacityWeight());
//
// // SupplyTarget: 供应目标偏差惩罚, 权重 8.0
// // 含义: 供应低于目标的总缺口。惩罚供应不足。
// objective.setCoefficient(model.getTotalSupplyTarget(), w.getSupplyTargetWeight());
//
// // MinSupply: 最小供应不足惩罚, 权重 5.0
// // 含义: 供应低于最小值的总缺口。惩罚供应严重不足。
// objective.setCoefficient(model.getTotalMinSupply(), w.getMinSupplyWeight());
//
// // MaxSupply: 最大供应超出惩罚, 权重 5.0
// // 含义: 供应超出最大值的总量。惩罚过度供应。
// objective.setCoefficient(model.getTotalMaxSupply(), w.getMaxSupplyWeight());
//
// // ===== 收益项 (最大化: 负系数, 因为 OR-Tools 默认最小化) =====
//
// // SalesDemandPriority: 销售需求优先级, 权重 1.0, 负系数 = 最大化
// // 含义: 按优先级加权的销售需求满足量。值越大表示满足的需求越多越好。
// // 因目标函数是最小化, 此项使用负系数 (最大化 = 加负号后最小化)。
// objective.setCoefficient(model.getTotalSalesDemandPriority(), -w.getSalesDemandPriorityWeight());
//
// // PostponementPenalty: 推迟惩罚, 权重 20.0 (简化版固定为 0)
// // 含义: 对应原模型中的延迟销售需求惩罚。当前版本未实现推迟逻辑。
// objective.setCoefficient(model.getTotalPostponementPenalty(), w.getPostponementPenaltyWeight());
//
// // ProcessMaxQuantity: 过程最大量惩罚, 权重 5.0 (简化版固定为 0)
// // 含义: 对应原模型中超出最大过程量的惩罚。当前版本未实现。
// objective.setCoefficient(model.getTotalProcessMaxQuantity(), w.getProcessMaxQuantityWeight());
//
// // 设置为最小化目标
// objective.setMinimization();
}
/**
* 计算层级边界约束的上界。
......
......@@ -63,10 +63,14 @@ public class StrategyLevel {
/** 是否为负向 KPI (越小越好 = 正常惩罚项; false = 正常惩罚项) */
public final boolean isNegative;
/** 编号 */
public final String code;
/** KPI 名称 (用于日志) */
public final String name;
public KPIEntry(String name, MPVariable variable, double weight, boolean isNegative) {
public KPIEntry(String code,String name, MPVariable variable, double weight, boolean isNegative) {
this.code = code;
this.name = name;
this.variable = variable;
this.weight = weight;
......@@ -102,8 +106,8 @@ public class StrategyLevel {
* @param variable KPI 汇总变量
* @param weight 权重 (0 = 跳过)
*/
public void addKPI(String name, MPVariable variable, double weight) {
addKPI(name, variable, weight, false);
public void addKPI(String code,String name, MPVariable variable, double weight) {
addKPI(code,name, variable, weight, false);
}
/**
......@@ -114,9 +118,9 @@ public class StrategyLevel {
* @param weight 权重 (0 = 跳过)
* @param isNegative 是否为负向 KPI (true = 最大化, 使用负系数)
*/
public void addKPI(String name, MPVariable variable, double weight, boolean isNegative) {
public void addKPI(String code, String name, MPVariable variable, double weight, boolean isNegative) {
if (weight > 0.0 && variable != null) {
kpis.add(new KPIEntry(name, variable, weight, isNegative));
kpis.add(new KPIEntry(code,name, variable, weight, isNegative));
}
}
......
......@@ -535,7 +535,7 @@ public class ResultWriter {
// KPI + 统计
writeLog("Kpi");
result.setKpis(buildKpis());
// result.setKpis(buildKpis());
// result.setStatistics(buildStatistics());
return result;
......
......@@ -3,6 +3,7 @@ package com.aps.macroplanner.output;
import com.aps.common.util.FileHelper;
import com.aps.macroplanner.data.*;
import com.aps.macroplanner.model.MacroPlannerModel;
import com.aps.macroplanner.objective.KpiLib;
import com.aps.macroplanner.objective.StrategyLevel;
import com.google.ortools.linearsolver.MPSolver;
......@@ -670,7 +671,7 @@ public class SolutionPrinter {
double totalWeightedPenalty = 0.0;
KPIWeights w = data.getKpiWeights();
Map<String,Double> weights=new HashMap<>();
for (int i = 0; i < levels.size(); i++) {
StrategyLevel level = levels.get(i);
double objValue = (i < levelObjValues.size()) ? levelObjValues.get(i) : 0.0;
......@@ -679,6 +680,7 @@ public class SolutionPrinter {
List<String> kpiNames = new ArrayList<>();
for (StrategyLevel.KPIEntry kpi : level.getKpis()) {
kpiNames.add(kpi.name);
weights.put(kpi.code,kpi.weight);
}
writeLog("%-6d %-14s %-12s %-12.2f %s",
......@@ -703,39 +705,48 @@ public class SolutionPrinter {
double maxSupply = model.getTotalMaxSupply().solutionValue();
double salesPriority = model.getTotalSalesDemandPriority().solutionValue();
double totalPenalty = fulfillment * w.getFulfillmentWeight()
+ lotSize * w.getLotSizeWeight()
+ maxInv * w.getMaxInventoryLevelWeight()
+ minInv * w.getMinInventoryLevelWeight()
+ targetInv * w.getTargetInventoryLevelWeight()
+ capacity * w.getUnitCapacityWeight()
+ supplyTarget * w.getSupplyTargetWeight()
+ minSupply * w.getMinSupplyWeight()
+ maxSupply * w.getMaxSupplyWeight()
- salesPriority * w.getSalesDemandPriorityWeight();
double totalPenalty = fulfillment * weights.getOrDefault(KpiLib.Fulfillment.getEn(),0d)
+ lotSize * weights.getOrDefault(KpiLib.LotSize.getEn(),0d)
+ maxInv * weights.getOrDefault(KpiLib.MinInventoryLevel.getEn(),0d)
+ minInv * weights.getOrDefault(KpiLib.MinInventoryLevel.getEn(),0d)
+ targetInv * weights.getOrDefault(KpiLib.TargetInvLevel.getEn(),0d)
+ capacity * weights.getOrDefault(KpiLib.UnitCapacity.getEn(),0d)
+ supplyTarget * weights.getOrDefault(KpiLib.SupplyTarget.getEn(),0d)
+ minSupply * weights.getOrDefault(KpiLib.MinSupply.getEn(),0d)
+ maxSupply * weights.getOrDefault(KpiLib.MaxSupply.getEn(),0d)
- salesPriority * weights.getOrDefault(KpiLib.SalesDemandPriority.getEn(),0d);
writeLog(" 加权总惩罚: %.2f", totalPenalty);
writeLog(" 需求缺口: %.2f (权重%.0f × %.2f)",
fulfillment * w.getFulfillmentWeight(), w.getFulfillmentWeight(), fulfillment);
writeLog(" 产能超载: %.2f (权重%.0f × %.2f)",
capacity * w.getUnitCapacityWeight(), w.getUnitCapacityWeight(), capacity);
writeLog(" 批次偏差: %.2f (权重%.0f × %.2f)",
lotSize * w.getLotSizeWeight(), w.getLotSizeWeight(), lotSize);
writeLog(" 目标库存偏差: %.2f (权重%.0f × %.2f)",
targetInv * w.getTargetInventoryLevelWeight(), w.getTargetInventoryLevelWeight(), targetInv);
writeLog(" 供应目标偏差: %.2f (权重%.0f × %.2f)",
supplyTarget * w.getSupplyTargetWeight(), w.getSupplyTargetWeight(), supplyTarget);
writeLog(" 超库存: %.2f (权重%.0f × %.2f)",
maxInv * w.getMaxInventoryLevelWeight(), w.getMaxInventoryLevelWeight(), maxInv);
writeLog(" 欠库存: %.2f (权重%.0f × %.2f)",
minInv * w.getMinInventoryLevelWeight(), w.getMinInventoryLevelWeight(), minInv);
writeLog(" 最小供应不足: %.2f (权重%.0f × %.2f)",
minSupply * w.getMinSupplyWeight(), w.getMinSupplyWeight(), minSupply);
writeLog(" 最大供应超出: %.2f (权重%.0f × %.2f)",
maxSupply * w.getMaxSupplyWeight(), w.getMaxSupplyWeight(), maxSupply);
writeLog(" 销售优先级(负): %.2f (权重%.0f × %.2f)",
salesPriority * w.getSalesDemandPriorityWeight(),
w.getSalesDemandPriorityWeight(), salesPriority);
for (Map.Entry<String, Double> entry : weights.entrySet()) {
String key = entry.getKey();
Double weight = entry.getValue();
KpiLib kpi = KpiLib.ofEn(key);
double val= model.getKpi(key).solutionValue();
writeLog(" %s: %.2f (权重%.0f × %.2f)",
kpi.getCn(),val * weight, val, val);
}
// writeLog(" 产能超载: %.2f (权重%.0f × %.2f)",
// capacity * weights.get(KpiLib.Fulfillment.getEn()), w.getUnitCapacityWeight(), capacity);
// writeLog(" 批次偏差: %.2f (权重%.0f × %.2f)",
// lotSize * weights.get(KpiLib.Fulfillment.getEn()), w.getLotSizeWeight(), lotSize);
// writeLog(" 目标库存偏差: %.2f (权重%.0f × %.2f)",
// targetInv * w.getTargetInventoryLevelWeight(), w.getTargetInventoryLevelWeight(), targetInv);
// writeLog(" 供应目标偏差: %.2f (权重%.0f × %.2f)",
// supplyTarget * w.getSupplyTargetWeight(), w.getSupplyTargetWeight(), supplyTarget);
// writeLog(" 超库存: %.2f (权重%.0f × %.2f)",
// maxInv * w.getMaxInventoryLevelWeight(), w.getMaxInventoryLevelWeight(), maxInv);
// writeLog(" 欠库存: %.2f (权重%.0f × %.2f)",
// minInv * w.getMinInventoryLevelWeight(), w.getMinInventoryLevelWeight(), minInv);
// writeLog(" 最小供应不足: %.2f (权重%.0f × %.2f)",
// minSupply * w.getMinSupplyWeight(), w.getMinSupplyWeight(), minSupply);
// writeLog(" 最大供应超出: %.2f (权重%.0f × %.2f)",
// maxSupply * w.getMaxSupplyWeight(), w.getMaxSupplyWeight(), maxSupply);
// writeLog(" 销售优先级(负): %.2f (权重%.0f × %.2f)",
// salesPriority * w.getSalesDemandPriorityWeight(),
// w.getSalesDemandPriorityWeight(), salesPriority);
}
// ==================== 辅助方法 ====================
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment