Commit 2521e779 authored by Tong Li's avatar Tong Li

MP

parent d653ce10
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
......@@ -20,4 +20,6 @@ public class ApsTimeConfig {
/** 周期维度: DAY(天) / WEEK(周) / MONTH(月), 默认 DAY */
private String periodDimension;
private String MpSceneId;
}
\ No newline at end of file
package com.aps.macroplanner;
import com.aps.common.util.FileHelper;
import com.aps.macroplanner.data.BenchmarkDataBuilder;
import com.aps.macroplanner.data.LargeScaleBomTestDataBuilder;
import com.google.ortools.Loader;
import com.aps.macroplanner.data.MultiLevelBomTestDataBuilder;
import com.aps.macroplanner.data.TestDataBuilder;
......@@ -23,6 +26,18 @@ import com.aps.macroplanner.data.TestDataBuilder;
* </ol>
*/
public class MultiLevelBomTestRunner {
private static final String LOG_DIR = "mp";
/** LP 模型文件路径 */
/** 运行日志文件路径 */
private static final String LOG_FILE_PATH = LOG_DIR + "/log/";
private static void writeLog(String msg)
{
FileHelper.writeFile(msg,LOG_FILE_PATH,"log.txt");
}
public static void main(String[] args) {
Loader.loadNativeLibraries();
......@@ -31,12 +46,21 @@ public class MultiLevelBomTestRunner {
System.out.println("Demand: P1=40/day, P2=30/day");
System.out.println();
TestDataBuilder data = new MultiLevelBomTestDataBuilder();
System.out.println("Data loaded: " + data.getProducts().size() + " products, "
+ data.getOperations().size() + " operations");
MacroPlannerOptimizer optimizer = new MacroPlannerOptimizer(data);
optimizer.buildModel();
optimizer.solve();
System.out.println("===== MULTI-LEVEL BOM TEST RUNNER END =====");
for (int scale : BenchmarkDataBuilder.SUPPORTED_SCALES) {
BenchmarkDataBuilder data =
BenchmarkDataBuilder.forScale(scale);
writeLog("===== TEST RUNNER START "+scale+"=====");
data.init();
writeLog("Data loaded: " + data.getProducts().size() + " products, "
+ data.getOperations().size() + " operations");
MacroPlannerOptimizer optimizer = new MacroPlannerOptimizer(data);
optimizer.buildModel();
optimizer.solve(String.valueOf(scale));
writeLog("===== TEST RUNNER END =====");
}
}
}
\ No newline at end of file
......@@ -53,8 +53,8 @@ public class BalanceConstraint {
}
}
// DemandSlack
MPVariable slackVar = demandSlackVars.get(invKey);
if (slackVar != null) balance.setCoefficient(slackVar, 1.0);
// MPVariable slackVar = demandSlackVars.get(invKey);
// if (slackVar != null) balance.setCoefficient(slackVar, 1.0);
// 上一周期库存 (t>0)
if (p.getIndex() > 0) {
String prevKey = prod.getId() + "_" + sp.getId() + "_" + (p.getIndex() - 1);
......
package com.aps.macroplanner.constraint;
import com.aps.common.util.FileHelper;
import com.aps.macroplanner.data.TestDataBuilder;
import com.aps.macroplanner.model.MacroPlannerModel;
......@@ -117,7 +118,21 @@ public class ConstraintFactory {
logSummary(model);
}
private static final String LOG_DIR = "mp";
/** LP 模型文件路径 */
private static final String LP_FILE_PATH = LOG_DIR + "/lp/";
/** 运行日志文件路径 */
private static final String LOG_FILE_PATH = LOG_DIR + "/log/";
private static void writeLog(String msg)
{
FileHelper.writeFile(msg,LOG_FILE_PATH,"log.txt");
}
private static void writeLog(String msg, Object... args)
{
FileHelper.writeFile(String.format(msg,args),LOG_FILE_PATH,"log.txt");
}
/**
* 记录单个约束构建步骤的结果。
* 输出新增约束数和新增变量数, 如果为 0 则输出 WARNING。
......@@ -129,7 +144,7 @@ public class ConstraintFactory {
int addedVariables = model.getSolver().numVariables() - beforeVariables;
if (addedConstraints == 0 && addedVariables == 0) {
LOG.warning(String.format("[%s] %s → 未创建任何约束或变量! (可能数据为空)",
writeLog(String.format("[%s] %s → 未创建任何约束或变量! (可能数据为空)",
stepName, description));
} else {
StringBuilder sb = new StringBuilder();
......@@ -140,7 +155,7 @@ public class ConstraintFactory {
if (addedVariables > 0) {
sb.append(String.format(" | +%d变量", addedVariables));
}
LOG.info(sb.toString());
writeLog(sb.toString());
}
}
......
package com.aps.macroplanner.data;
import java.util.Arrays;
/** 六档 MacroPlanner Benchmark 规模。 */
public final class BenchmarkScales {
private BenchmarkScales() {}
public static int[] all() {
return Arrays.copyOf(
BenchmarkDataBuilder.SUPPORTED_SCALES,
BenchmarkDataBuilder.SUPPORTED_SCALES.length);
}
/** 顺序创建六档数据,方便现有 Benchmark/性能测试接入。 */
public static void forEach(java.util.function.IntFunction<BenchmarkDataBuilder> consumer) {
for (int scale : all()) consumer.apply(scale);
}
}
......@@ -160,7 +160,7 @@ public class MacroPlannerDataConverter {
// 0. 读取时间配置, 计算 horizonEnd
// endCount = 期数 (与 periodDimension 结合决定实际天数)
ApsTimeConfig timeConfig = apsTimeConfigService.getOne(new LambdaQueryWrapper<>());
ApsTimeConfig timeConfig = apsTimeConfigService.getOne(new LambdaQueryWrapper<ApsTimeConfig>().eq(ApsTimeConfig::getMpSceneId,"172f545f-f94c-4143-86af-bd65e787e8e6"));
ctx.baseTime = LocalDateTime.of(2026,9,28,0,0,0);
// ctx.baseTime = (timeConfig != null && timeConfig.getBaseTime() != null)
// ? timeConfig.getBaseTime() : LocalDateTime.now();
......@@ -188,7 +188,7 @@ public class MacroPlannerDataConverter {
// 1. 需求订单 (ApsDemandOrder, 按 deliverytime 时间范围过滤)
// ctx.apsDemandOrders = apsDemandOrderMapper.selectList(
// new LambdaQueryWrapper<ApsDemandOrder>()
// new LambdaQueryWrapper<ApsDemandOrder>().eq(ApsDemandOrder::getIsdeleted, 0)
// .ge(ApsDemandOrder::getDeliverytime, ctx.baseTime)
// .lt(ApsDemandOrder::getDeliverytime, horizonEndDateTime));
......@@ -1348,7 +1348,7 @@ public class MacroPlannerDataConverter {
String key = m.getProduct().getId() + "_" + m.getStockingPoint().getId() + "_" + p.getIndex();
if (invSpecKeys.add(key)) {
inventorySpecs.add(new InventorySpec(m.getProduct(), m.getStockingPoint(), p,
0.0, 0.0, LOOSE_MAX, false, true, true));
0.0, 0.0, 0, false, true, false));
}
}
}
......@@ -1361,12 +1361,12 @@ public class MacroPlannerDataConverter {
}
Operation lastOp = ops.get(ops.size() - 1);
supplySpecs.add(new SupplySpec("Supply-" + r.getId(),
0.0, 0.0, LOOSE_MAX, false, Collections.singletonList(lastOp)));
0.0, 0.0, 0, false, Collections.singletonList(lastOp)));
}
for (Operation op : operations) {
if (op.getId().startsWith("OP_PROCURE_")) {
supplySpecs.add(new SupplySpec("Supply-" + op.getId(),
0.0, 0.0, LOOSE_MAX, true, Collections.singletonList(op)));
0.0, 0.0, 0, false, Collections.singletonList(op)));
}
}
log.info("构建 InventorySpec: {}, SupplySpec: {}", inventorySpecs.size(), supplySpecs.size());
......
......@@ -73,9 +73,11 @@ public class TestDataBuilder {
* @param skipAutoBuild 如果为 true, 不自动调用 build(), 由子类负责初始化
*/
protected TestDataBuilder(boolean skipAutoBuild) {
if (!skipAutoBuild) {
build();
}
}
public void init() {
build();
}
/** 初始化默认测试数据 (3级BOM, 5产品, 5操作, 4周期) */
......
......@@ -99,14 +99,26 @@ public class MacroPlannerModel {
* 变量创建由 {@link VariableFactory} 负责。
*/
public MacroPlannerModel() {
this.solver = MPSolver.createSolver("SCIP");
this.solver = MPSolver.createSolver("CBC");
if (solver == null) {
throw new RuntimeException("无法加载 SCIP 求解器,请检查 OR-Tools 依赖");
}
solver.enableOutput();
// solver.setSolverSpecificParametersAsString("numerics/feastol = 1e-5");
// solver.setSolverSpecificParametersAsString("numerics/epsilon = 1e-8");
// solver.setSolverSpecificParametersAsString("misc/checkfeastolfac = 1.0");
// solver.setSolverSpecificParametersAsString("presolving/maxrounds = 1");
// solver.setSolverSpecificParametersAsString("crossover = true");
// solver.setSolverSpecificParametersAsString("algorithm = barrier");
solver.setSolverSpecificParametersAsString("log level 2");
// solver.setTimeLimit(5*60*1000);
solver.setNumThreads(4);
solver.enableOutput();
}
// ==================== KPI 变量 setter (由 KpiAggregator 调用) ====================
......
......@@ -422,7 +422,7 @@ public class ResultWriter {
sr.setPeriodStartDate(sd.getPeriod().getStartDate().toString());
sr.setDemandQty(sd.getQuantity());
sr.setPriority(sd.getPriority());
sr.setDemandOrderDate(sd.getDemandOrderDate().toString());
sr.setDemandOrderDate(sd.getDemandOrderDate()==null?"":sd.getDemandOrderDate().toString());
double fulfilled = solutionValue(model.getSalesDemandQtyVars(), sd.getKey()).setScale(3, RoundingMode.HALF_UP).doubleValue();;
double unmet = Math.max(0, sd.getQuantity() - fulfilled);
......@@ -686,7 +686,7 @@ public class ResultWriter {
DemandSummaryResult.PeriodEntry pe = new DemandSummaryResult.PeriodEntry();
pe.periodIndex = sd.getPeriod().getIndex();
pe.periodStartDate = sd.getPeriod().getStartDate().toString();
pe.demandOrderDate= sd.getDemandOrderDate().toString();
pe.demandOrderDate=sd.getDemandOrderDate()==null?"": sd.getDemandOrderDate().toString();
pe.demandQty = demand;
pe.fulfilledQty = fulfilled;
pe.unmetQty = unmet;
......
......@@ -4,6 +4,7 @@ import com.aps.common.util.FileHelper;
import com.aps.macroplanner.data.*;
import com.aps.macroplanner.model.MacroPlannerModel;
import com.aps.macroplanner.objective.StrategyLevel;
import com.google.ortools.linearsolver.MPSolver;
import java.util.*;
import java.time.LocalDate;
......@@ -54,10 +55,10 @@ public class SolutionPrinter {
/** 输出完整求解结果 */
public void printAll() {
printBomStructure();
printHeader();
printDailyViews();
printKpiSummary();
// printBomStructure();
// printHeader();
// printDailyViews();
// printKpiSummary();
printStatistics();
}
......@@ -224,8 +225,8 @@ public class SolutionPrinter {
hasProduction = true;
double capacityUsed = ptQty * uo.getCapacityCoeff();
if (up.isUnlimited()) {
writeLog(" %s(%s): %.0f件 (耗时%.1fh)",
op.getName(), formatOutputProducts(op), ptQty, capacityUsed);
writeLog(" %s(%s): %.0f件",
op.getName(), formatOutputProducts(op), ptQty);
} else {
double utilPercent = up.getMaxCapacity() > 0
? capacityUsed / up.getMaxCapacity() * 100 : 0;
......@@ -635,10 +636,17 @@ public class SolutionPrinter {
private void printStatistics() {
long elapsedMs = System.currentTimeMillis() - startTimeMs;
MPSolver solver= model.getSolver();
writeLog("");
writeLog("═══════════════════ 求解统计 ═══════════════════");
writeLog("变量数:%d", model.getSolver().numVariables());
writeLog("约束数:%d", model.getSolver().numConstraints());
writeLog("变量数:%d", solver.numVariables());
writeLog("约束数:%d", solver.numConstraints());
double primalBound = solver.objective().value(); // 当前最好可行解目标值(原始界)
double dualBound = solver.objective().bestBound(); // 全局理论最优边界(对偶界)
writeLog("当前最好可行解目标值:%f", primalBound);
writeLog("全局理论最优边界:%f", dualBound);
double gap = Math.abs(primalBound - dualBound) / Math.abs(primalBound) * 100;
writeLog("gap:%.2f", gap);
writeLog("耗时:%.3f 秒", elapsedMs / 1000.0);
}
......
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