Commit f8d2a066 authored by Tong Li's avatar Tong Li

MP

parent 97f10b74
......@@ -30,6 +30,9 @@ public class CapacityConstraint {
Map<String, MPVariable> notMetVars = model.getCapacityNotMetVars();
for (UnitPeriod up : data.getUnitPeriods()) {
// 无限产能单元: 无需产能限制
if (up.isUnlimited()) continue;
String capKey = up.getKey();
// --- 最大产能 ---
......
......@@ -913,6 +913,10 @@ public class MacroPlannerDataConverter {
}
// 为无产出工序的 MP 原材料补充"采购 Operation"
// 根据 MATERIAL_PURCHASE 创建供应商 unit:
// - 有 MATERIAL_PURCHASE → 每供应商一个 Operation, leadTimeDays = purchaseCycle
// - 无 MATERIAL_PURCHASE → 通用供应商 unit, relativeDuration=1, leadTimeDays=0, UnitPeriod 无限
for (Material m : ctx.materialByMaterialId.values()) {
if (!"MP".equals(m.getMaterialTypeName())) {
continue;
......@@ -932,15 +936,40 @@ public class MacroPlannerDataConverter {
productSpMappings.add(new ProductSpMapping(p, sp));
ctx.finalSpByMaterialId.put(m.getId(), sp);
}
int leadTimeDays = m.getPurchaseLeadTime() > 0 ? m.getPurchaseLeadTime() : 1;
List<MaterialPurchase> purchases = m.getMaterialPurchases();
if (purchases != null && !purchases.isEmpty()) {
// 有供应商: 按每个供应商创建独立的采购 Operation, leadTimeDays = purchaseCycle
for (MaterialPurchase mp : purchases) {
String unitId = "SUPPLIER_" + (mp.getSupplyId() != null ? mp.getSupplyId() : "UNKNOWN");
int leadTimeDays = mp.getPurchaseCycle() != null ? mp.getPurchaseCycle() : 0;
Operation procureOp = new Operation(
"OP_PROCURE_" + m.getId() + "_" + (mp.getSupplyId() != null ? mp.getSupplyId() : "X"),
"采购-" + pickName(m.getName(), m.getId())
+ (mp.getSupplyName() != null ? "-" + mp.getSupplyName() : ""),
Collections.singletonList(
new UnitOperation(unitId, 0.5, false, 0.0, 1.0)),
new OperationOutput(p, sp),
1.0, leadTimeDays);
operations.add(procureOp);
log.info("为 MP 原材料 {} 创建采购 Operation (供应商={}, leadTime={}天)",
m.getId(), mp.getSupplyName(), leadTimeDays);
}
} else {
// 无供应商: 使用通用供应商, 无限产能
String unitId = "UNIT_DEFAULT_SUPPLIER";
ctx.unlimitedUnitIds.add(unitId);
Operation procureOp = new Operation(
"OP_PROCURE_" + m.getId(),
"采购-" + pickName(m.getName(), m.getId()),
"UNIT_PROCURE_" + m.getId(),
Collections.singletonList(
new UnitOperation(unitId, 0.5, false, 0.0, 1.0)),
new OperationOutput(p, sp),
0.5, 1.0, false, 0.0, 1.0, leadTimeDays);
1.0, 0);
operations.add(procureOp);
log.info("为 MP 原材料 {} 补充采购 Operation (leadTime={}天)", m.getId(), leadTimeDays);
log.info("为 MP 原材料 {} 创建通用采购 Operation (无供应商, 无限产能)", m.getId());
}
}
}
log.info("构建 OperationInput: {}", operationInputs.size());
......@@ -1051,6 +1080,13 @@ public class MacroPlannerDataConverter {
LocalDate periodEnd = periodStart.plusDays((long) p.getDurationInDays());
for (String unitId : unitIds) {
// 无限产能设备 (通用供应商): 无需计算, 直接创建 unlimited UnitPeriod
if (ctx.unlimitedUnitIds.contains(unitId)) {
unitPeriods.add(UnitPeriod.unlimited(unitId, p));
continue;
}
double dailyCapacity = ctx.dailyCapacityByUnitId.getOrDefault(unitId, DEFAULT_DAILY_HOURS);
// 节假日扣减
......@@ -1222,6 +1258,10 @@ public class MacroPlannerDataConverter {
Map<String, Double> dailyCapacityByUnitId = new HashMap<>();
/** unitId → 节假日日期集合 */
Map<String, Set<LocalDate>> holidayDatesByUnitId = new HashMap<>();
/** unitId 集合: 产能无上限的设备 (如通用供应商, 无 MATERIAL_PURCHASE 时) */
Set<String> unlimitedUnitIds = new HashSet<>();
/** 排产周期数 (天), 从 ApsTimeConfig 计算 */
int periodCount = 7;
}
......
......@@ -161,33 +161,29 @@ public class TestDataBuilder {
operationInputs.add(new OperationInput(opS1, prodR2, spRM, 2.0));
// === 单元周期定义 (7 个生产单元, 不同可用工时) ===
// L1: 成品线, 最大可用工时 200h/周期
// L1: 成品线, 最大可用工时 10h/周期
for (Period p : periods) {
unitPeriods.add(new UnitPeriod("L1", p, 0.0, 10.0, false));
}
for (Period p : periods) {
unitPeriods.add(new UnitPeriod("L2", p, 0.0, 10.0, false));
}
// Unit_Semi: 半成品线, 最大产能 200/周期
// Unit_Semi: 半成品线, 最大产能 10h/周期
for (Period p : periods) {
unitPeriods.add(new UnitPeriod("L3", p, 0.0, 10.0, false));
}
// Unit_R1: 供应商 R1, 最大产能 150/周期
// 原材料供应商: 无限产能 (只要有需求就能供应)
for (Period p : periods) {
unitPeriods.add(new UnitPeriod("SuA", p, 0.0, 24, false));
unitPeriods.add(UnitPeriod.unlimited("SuA", p));
}
// Unit_R2: 供应商 R2, 最大产能 150/周期
for (Period p : periods) {
unitPeriods.add(new UnitPeriod("SuB", p, 0.0, 24, false));
unitPeriods.add(UnitPeriod.unlimited("SuB", p));
}
// Unit_R1_Alt: 备用供应商 R1, 最大产能 100/周期 (成本较高, 仅在主供应商不足时使用)
for (Period p : periods) {
unitPeriods.add(new UnitPeriod("Su1", p, 0.0, 24, false));
unitPeriods.add(UnitPeriod.unlimited("Su1", p));
}
// Unit_R2_Alt: 备用供应商 R2, 最大产能 100/周期 (成本较高, 仅在主供应商不足时使用)
for (Period p : periods) {
unitPeriods.add(new UnitPeriod("Su2", p, 0.0, 100.0, false));
unitPeriods.add(UnitPeriod.unlimited("Su2", p));
}
// === 初始库存 ===
......@@ -408,6 +404,5 @@ public class TestDataBuilder {
if (reference == null || reference.getStartDate() == null) return null;
LocalDate targetDate = reference.getStartDate().minusDays(daysBack);
return getPeriodByDate(targetDate);
}
}
\ No newline at end of file
......@@ -10,13 +10,27 @@ public class UnitPeriod {
private final double minCapacity; // 最小产能
private final double maxCapacity; // 最大可用产能
private final boolean hasMinCapacity; // 是否有最小产能约束
private final boolean unlimited; // 产能是否无上限 (如原材料供应商)
public UnitPeriod(String unitId, Period period, double minCapacity, double maxCapacity, boolean hasMinCapacity) {
/** 完整构造器 */
public UnitPeriod(String unitId, Period period, double minCapacity, double maxCapacity,
boolean hasMinCapacity, boolean unlimited) {
this.unitId = unitId;
this.period = period;
this.minCapacity = minCapacity;
this.maxCapacity = maxCapacity;
this.maxCapacity = unlimited ? Double.MAX_VALUE : maxCapacity;
this.hasMinCapacity = hasMinCapacity;
this.unlimited = unlimited;
}
/** 有限产能构造器 (向后兼容) */
public UnitPeriod(String unitId, Period period, double minCapacity, double maxCapacity, boolean hasMinCapacity) {
this(unitId, period, minCapacity, maxCapacity, hasMinCapacity, false);
}
/** 无限产能便捷构造器 */
public static UnitPeriod unlimited(String unitId, Period period) {
return new UnitPeriod(unitId, period, 0, 0, false, true);
}
public String getUnitId() { return unitId; }
......@@ -24,6 +38,7 @@ public class UnitPeriod {
public double getMinCapacity() { return minCapacity; }
public double getMaxCapacity() { return maxCapacity; }
public boolean hasMinCapacity() { return hasMinCapacity; }
public boolean isUnlimited() { return unlimited; }
public String getKey() {
return unitId + "_" + period.getIndex();
......
......@@ -366,23 +366,33 @@ public class ResultWriter {
}
}
if (up.isUnlimited()) {
pe.maxCapacity = -1; // -1 表示无限
pe.utilization = -1; // -1 表示不适用
pe.overloaded = 0;
pe.notMet = 0;
} else {
double maxCap = up.getMaxCapacity();
double utilization = maxCap > 0 ? capacityUsed / maxCap : 0;
double overloaded = solutionValue(model.getCapacityOverloadedVars(), up.getKey());
double notMet = solutionValue(model.getCapacityNotMetVars(), up.getKey());
pe.maxCapacity = maxCap;
pe.capacityUsed = capacityUsed;
pe.utilization = utilization;
pe.overloaded = overloaded;
pe.notMet = notMet;
}
pe.capacityUsed = capacityUsed;
ucr.getPeriodDetails().add(pe);
// 累加汇总
ucr.setTotalMaxCapacity(ucr.getTotalMaxCapacity() + maxCap);
if (!up.isUnlimited()) {
ucr.setTotalMaxCapacity(ucr.getTotalMaxCapacity() + pe.maxCapacity);
ucr.setTotalOverloaded(ucr.getTotalOverloaded() + pe.overloaded);
ucr.setTotalNotMet(ucr.getTotalNotMet() + pe.notMet);
}
ucr.setTotalCapacityUsed(ucr.getTotalCapacityUsed() + capacityUsed);
ucr.setTotalOverloaded(ucr.getTotalOverloaded() + overloaded);
ucr.setTotalNotMet(ucr.getTotalNotMet() + notMet);
}
// 计算整体利用率
......
......@@ -192,9 +192,13 @@ public class SolutionPrinter {
UnitPeriod up = data.getUnitPeriod(unitId, p);
if (up == null) continue;
if (up.isUnlimited()) {
System.out.printf(" 【%s】产能无限 (供应商)%n", unitId);
} else {
String minStr = up.hasMinCapacity() ? String.format("最小%.0fh/", up.getMinCapacity()) : "";
System.out.printf(" 【%s】产能%s%.0fh %s%n", unitId, minStr, up.getMaxCapacity(),
up.hasMinCapacity() ? "(强制最小)" : "");
}
boolean hasProduction = false;
for (Operation op : data.getOperations()) {
......@@ -205,6 +209,10 @@ public class SolutionPrinter {
if (ptQty > 0.001) {
hasProduction = true;
double capacityUsed = ptQty * uo.getCapacityCoeff();
if (up.isUnlimited()) {
System.out.printf(" %s(%s): %.0f件 (耗时%.1fh)%n",
op.getName(), formatOutputProducts(op), ptQty, capacityUsed);
} else {
double utilPercent = up.getMaxCapacity() > 0
? capacityUsed / up.getMaxCapacity() * 100 : 0;
System.out.printf(" %s(%s): %.0f件 (耗时%.1fh) %s%n",
......@@ -215,6 +223,7 @@ public class SolutionPrinter {
}
}
}
}
if (!hasProduction) {
System.out.println(" (休息)");
}
......
......@@ -24,6 +24,7 @@ public class CapacityVariableBuilder {
public static void create(MacroPlannerModel model, TestDataBuilder data) {
double inf = MPSolver.infinity();
for (UnitPeriod up : data.getUnitPeriods()) {
if (up.isUnlimited()) continue; // 无限产能无需松弛变量
String key = up.getKey();
model.getCapacityOverloadedVars().put(key,
model.getSolver().makeNumVar(0.0, inf, "CapOver_" + key));
......
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