Commit 2ec4b057 authored by Tong Li's avatar Tong Li

MP

parent 7d6de509
......@@ -128,6 +128,71 @@
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
</dependency>
<!-- parquet (用于存储运算结果) -->
<dependency>
<groupId>org.apache.parquet</groupId>
<artifactId>parquet-avro</artifactId>
<version>1.14.4</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>3.3.6</version>
<exclusions>
<!-- 排除 Hadoop 自带的 slf4j-reload4j 绑定,避免与 Spring Boot 的 logback 冲突(SLF4J multiple bindings) -->
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-reload4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-core</artifactId>
<version>3.3.6</version>
<exclusions>
<!-- 排除日志绑定,避免再次出现 SLF4J multiple bindings -->
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-reload4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
<!-- 排除YARN相关,完全用不到 -->
<exclusion>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-yarn-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-yarn-common</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-yarn-client</artifactId>
</exclusion>
<!-- 排除其他不需要的模块 -->
<exclusion>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs-client</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.xerial.snappy</groupId>
<artifactId>snappy-java</artifactId>
<version>1.1.10.5</version>
</dependency>
</dependencies>
<build>
......
package com.aps.common.util;
/**
* 作者:佟礼
* 时间:2026-09-08
*/
import org.apache.avro.Schema;
import org.apache.avro.reflect.ReflectData;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.parquet.avro.AvroParquetReader;
import org.apache.parquet.avro.AvroParquetWriter;
import org.apache.parquet.hadoop.ParquetReader;
import org.apache.parquet.hadoop.ParquetWriter;
import org.apache.parquet.hadoop.metadata.CompressionCodecName;
import org.apache.parquet.io.LocalInputFile;
import org.apache.parquet.io.LocalOutputFile;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
/**
* 范式化扁平Row POJO读写Parquet
* 要求:POJO不要包含嵌套List集合,子List拆成独立子parquet
*/
public class FlatParquetUtil {
static {
// Windows 下 Hadoop 需要 winutils.exe/hadoop.dll。
// 优先复用 HADOOP_HOME,避免把 hadoop.home.dir 指到当前目录(无 bin/winutils.exe)导致写入失败。
if (System.getProperty("hadoop.home.dir") == null) {
String hadoopHome = System.getenv("HADOOP_HOME");
if (hadoopHome != null && !hadoopHome.trim().isEmpty()) {
System.setProperty("hadoop.home.dir", hadoopHome);
}
}
}
private final Configuration conf;
public FlatParquetUtil() {
conf = new Configuration();
// 使用本地文件系统,不依赖hadoop集群
conf.set("fs.file.impl", org.apache.hadoop.fs.LocalFileSystem.class.getName());
// 关闭hadoop Shell的部分本地命令调用(Windows不需要winutils.exe)
conf.setBoolean("hadoop.native.lib", false);
}
/**
* 写入扁平对象列表到parquet(覆盖)
*/
public <T> void write(List<T> rows, String filePath, Class<T> clazz) throws IOException {
if (rows == null || rows.isEmpty()) {
return;
}
File file = resolvePath(filePath);
File parent = file.getParentFile();
if (parent != null && !parent.exists()) {
parent.mkdirs();
}
if (file.exists()) {
file.delete();
}
Schema schema = ReflectData.AllowNull.get().getSchema(clazz);
LocalOutputFile outputFile = new LocalOutputFile(file.toPath());
try(ParquetWriter<T> writer = AvroParquetWriter.<T>builder(outputFile)
.withSchema(schema)
.withDataModel(ReflectData.AllowNull.get())
.withConf(conf)
.withCompressionCodec(CompressionCodecName.SNAPPY)
.withRowGroupSize(128 * 1024 * 1024)
.build()){
for(T row : rows){
writer.write(row);
}
}
}
private File resolvePath(String filePath) throws IOException {
try {
if (filePath.startsWith("file:")) {
// 是URI格式,用标准URI解析,自动处理Windows盘符、空格、中文
return Paths.get(new URI(filePath)).toFile();
} else {
// 纯本地路径
return new File(filePath);
}
} catch (Exception e) {
throw new IOException("路径解析失败: " + filePath, e);
}
}
/**
* 读取全部行
*/
public <T> List<T> readAll(String filePath, Class<T> clazz) throws IOException {
List<T> res = new ArrayList<>();
File file = resolvePath(filePath);
LocalInputFile inputFile = new LocalInputFile(file.toPath());
try(ParquetReader<T> reader = AvroParquetReader.<T>builder(inputFile)
.withDataModel(ReflectData.AllowNull.get())
.withConf(conf)
.build()){
T t;
while ((t = reader.read()) != null) {
res.add(t);
}
}
return res;
}
/**
* 获取迭代器流式读取,不一次性全加载内存
*/
public <T> ParquetReader<T> getReader(String filePath, Class<T> clazz) throws IOException {
Path path = new Path(filePath);
return AvroParquetReader.<T>builder(path)
.withDataModel(ReflectData.AllowNull.get())
.withConf(conf)
.build();
}
}
......@@ -196,7 +196,7 @@ public class MacroPlannerOptimizer {
writeLog(" [OK] 目标函数创建完成");
// 4. 导出 LP 模型文件
exportLpModel();
//exportLpModel();
writeLog("\n模型统计: 变量=" + model.getSolver().numVariables()
+ ", 约束=" + model.getSolver().numConstraints() + "\n");
......@@ -432,7 +432,7 @@ public class MacroPlannerOptimizer {
l3.addKPI("供应目标偏差", model.getTotalSupplyTarget(), w.getSupplyTargetWeight());
l3.addKPI("销售优先级", model.getTotalSalesDemandPriority(),
w.getSalesDemandPriorityWeight(), true); // 负系数 = 最大化
levels.add(l3);
// levels.add(l3);
// === Level 4: 软约束 (允许 10% 退化, slack=10%) ===
StrategyLevel l4 = new StrategyLevel(4, "软约束", 0.10);
......@@ -440,7 +440,7 @@ public class MacroPlannerOptimizer {
l4.addKPI("欠库存", model.getTotalMinInventoryLevel(), w.getMinInventoryLevelWeight());
l4.addKPI("最小供应不足", model.getTotalMinSupply(), w.getMinSupplyWeight());
l4.addKPI("最大供应超出", model.getTotalMaxSupply(), w.getMaxSupplyWeight());
levels.add(l4);
// levels.add(l4);
return levels;
}
......
......@@ -55,10 +55,11 @@ public class MultiLevelBomTestRunner {
// for (int scale : BenchmarkDataBuilder.SUPPORTED_SCALES) {
//
// int scale=10000;
// BenchmarkDataBuilder data =
// BenchmarkDataBuilder.forScale(scale);
// writeLog("===== TEST RUNNER START "+scale+"=====");
//
////
// data.init();
// writeLog("Data loaded: " + data.getProducts().size() + " products, "
// + data.getOperations().size() + " operations");
......
......@@ -42,8 +42,7 @@ public class BalanceConstraint {
// === 流入 ===
// PTQty 产出 (考虑提前期: productionDate 生产, productionDate+leadTimeDays 到货)
// 关键: 只统计产出到当前库存点(sp)的操作, 避免多工序路由中产出到错误的库存点
for (Operation op : data.getOperations()) {
if (!op.producesProductAtSp(prod.getId(), sp.getId())) continue;
for (Operation op : data.getOperationsProducing(prod.getId(), sp.getId())) {
int leadTimeDays = op.getLeadTimeDays();
Period productionPeriod = data.getPeriodOffsetByDays(p, leadTimeDays);
if (productionPeriod == null) continue;
......@@ -53,8 +52,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);
......@@ -84,10 +83,8 @@ public class BalanceConstraint {
}
// 在途供应 (供应商已发货, 固定到货量, 按日期匹配周期)
for (InTransitSupply its : data.getInTransitSupplies()) {
if (its.getProduct().getId().equals(prod.getId())
&& its.getStockingPoint().getId().equals(sp.getId())
&& p.equals(data.getPeriodByDate(its.getArrivalDate()))) {
for (InTransitSupply its : data.getInTransitSuppliesFor(prod.getId(), sp.getId())) {
if (p.equals(data.getPeriodByDate(its.getArrivalDate()))) {
rhs -= its.getQuantity();
}
}
......
......@@ -57,9 +57,7 @@ public class BomConstraint {
"DepDemandDef_" + ddKey);
ddCon.setCoefficient(ddVar, -1.0);
for (OperationInput input : data.getOperationInputs()) {
if (!input.getInputProduct().getId().equals(prod.getId())) continue;
if (!input.getInputSp().getId().equals(sp.getId())) continue;
for (OperationInput input : data.getOperationInputsFor(prod.getId(), sp.getId())) {
MPVariable odVar = operationDemandQtyVars.get(
input.getKey() + "_" + p.getIndex());
if (odVar != null) ddCon.setCoefficient(odVar, 1.0);
......
......@@ -38,7 +38,7 @@ public class CapacityConstraint {
// --- 最大产能 ---
MPConstraint maxCap = model.getSolver().makeConstraint(
-MPSolver.infinity(), up.getMaxCapacity(), "MaxCap_" + capKey);
for (Operation op : data.getOperations()) {
for (Operation op : data.getOperationsByUnitId(up.getUnitId())) {
for (UnitOperation uo : op.getUnitOperations()) {
if (!uo.getUnitId().equals(up.getUnitId())) continue;
MPVariable ptVar = ptQtyVars.get(op.ptQtyKey(uo, up.getPeriod().getIndex()));
......@@ -52,7 +52,7 @@ public class CapacityConstraint {
if (up.hasMinCapacity()) {
MPConstraint minCap = model.getSolver().makeConstraint(
up.getMinCapacity(), MPSolver.infinity(), "MinCap_" + capKey);
for (Operation op : data.getOperations()) {
for (Operation op : data.getOperationsByUnitId(up.getUnitId())) {
for (UnitOperation uo : op.getUnitOperations()) {
if (!uo.getUnitId().equals(up.getUnitId())) continue;
MPVariable ptVar = ptQtyVars.get(op.ptQtyKey(uo, up.getPeriod().getIndex()));
......
......@@ -49,23 +49,16 @@ public class DemandFulfillmentConstraint {
if (dfVar != null) c.setCoefficient(dfVar, -1.0);
// + SalesDemandQty
for (SalesDemand sd : data.getSalesDemands()) {
if (sd.getProduct().getId().equals(prod.getId())
&& sd.getStockingPoint().getId().equals(sp.getId())
&& sd.getPeriod().getIndex() == p.getIndex()) {
MPVariable sdVar = sdVars.get(sd.getKey());
if (sdVar != null) c.setCoefficient(sdVar, 1.0);
}
for (SalesDemand sd : data.getSalesDemandsFor(prod, sp, p)) {
MPVariable sdVar = sdVars.get(sd.getKey());
if (sdVar != null) c.setCoefficient(sdVar, 1.0);
}
// + OperationDemandQty (BOM 消耗)
for (OperationInput input : data.getOperationInputs()) {
if (input.getInputProduct().getId().equals(prod.getId())
&& input.getInputSp().getId().equals(sp.getId())) {
String opKey = input.getKey() + "_" + p.getIndex();
MPVariable odVar = opDemandVars.get(opKey);
if (odVar != null) c.setCoefficient(odVar, 1.0);
}
for (OperationInput input : data.getOperationInputsFor(prod.getId(), sp.getId())) {
String opKey = input.getKey() + "_" + p.getIndex();
MPVariable odVar = opDemandVars.get(opKey);
if (odVar != null) c.setCoefficient(odVar, 1.0);
}
}
}
......
......@@ -6,6 +6,7 @@ import com.google.ortools.linearsolver.MPVariable;
import com.aps.macroplanner.data.*;
import com.aps.macroplanner.model.MacroPlannerModel;
import java.util.List;
import java.util.Map;
/**
......@@ -58,25 +59,22 @@ public class DemandSlackLinkageConstraint {
for (Period p : data.getPeriods()) {
String pispipKey = prod.getId() + "_" + sp.getId() + "_" + p.getIndex();
double totalDemandQty = 0.0;
boolean hasDemand = false;
// 汇总该 PISPIP 的所有销售需求
for (SalesDemand sd : data.getSalesDemandsFor(prod, sp, p)) {
List<SalesDemand> demands = data.getSalesDemandsFor(prod, sp, p);
if (demands.isEmpty()) continue;
double totalDemandQty = 0.0;
for (SalesDemand sd : demands) {
totalDemandQty += sd.getQuantity();
hasDemand = true;
}
// 只对存在销售需求的 PISPIP 创建约束
if (!hasDemand) continue;
// 约束: Σ SalesDemandQty + DemandSlack >= totalDemandQty
MPConstraint c = model.getSolver().makeConstraint(
totalDemandQty, MPSolver.infinity(),
"DSLink_" + pispipKey);
// + Σ SalesDemandQty
for (SalesDemand sd : data.getSalesDemandsFor(prod, sp, p)) {
for (SalesDemand sd : demands) {
MPVariable sdVar = sdVars.get(sd.getKey());
if (sdVar != null) c.setCoefficient(sdVar, 1.0);
}
......
......@@ -19,9 +19,9 @@ import java.util.Random;
*/
public class BenchmarkDataBuilder extends TestDataBuilder {
public static final int[] SUPPORTED_SCALES = {100, 200, 500, 1000, 2000, 5000};
public static final int[] SUPPORTED_SCALES = {100, 200, 500, 1000, 2000, 5000, 10000};
public static final long RANDOM_SEED = 20260826L;
public static final int PERIOD_COUNT = 15;
public static final int PERIOD_COUNT = 30;
private static final int SEMI_DIVISOR = 5;
private static final int RAW_MULTIPLIER = 2;
......
......@@ -179,6 +179,7 @@ public class DataValidator {
// 循环依赖检测 (含库存点: 仅当产品+库存点都匹配时才构成循环)
// 多工序路由中同一产品经不同库存点流转不构成循环
// 优化: 通过 getOperationInputsFor 索引查找反向边, 避免 O(Inputs²) 全表扫描
for (OperationInput input : data.getOperationInputs()) {
String consumedProd = input.getInputProduct().getId();
String consumedSp = input.getInputSp().getId();
......@@ -188,11 +189,10 @@ public class DataValidator {
String consumerProd = consumerOutput.getProductId();
String consumerSp = consumerOutput.getSpId();
for (OperationInput other : data.getOperationInputs()) {
// 检查 other 是否产出 consumedProd@consumedSp 且消耗 consumerProd@consumerSp
if (other.getOperation().producesProductAtSp(consumedProd, consumedSp)
&& other.getInputProduct().getId().equals(consumerProd)
&& other.getInputSp().getId().equals(consumerSp)) {
// 反向边: 消耗 consumerProd@consumerSp 的输入
for (OperationInput other : data.getOperationInputsFor(consumerProd, consumerSp)) {
// 检查 other 是否产出 consumedProd@consumedSp (形成 A→B→A)
if (other.getOperation().producesProductAtSp(consumedProd, consumedSp)) {
errors.add("BOM 循环依赖: " + consumerProd + "@" + consumerSp
+ " → " + consumedProd + "@" + consumedSp
+ " → " + consumerProd + "@" + consumerSp);
......
......@@ -63,6 +63,17 @@ public class TestDataBuilder {
/** 初始库存列表 (子类可访问) */
protected final List<InitialInventory> initialInventories = new ArrayList<>();
// ==================== 查询索引 (懒加载, 避免约束构建中的 O(N²) 线性扫描) ====================
private Map<String, List<StockingPoint>> stockingPointsByProductIndex;
private Map<String, List<SalesDemand>> salesDemandsIndex;
private Map<String, List<OperationInput>> operationInputsIndex;
private Map<String, List<Operation>> operationsByOutputIndex;
private Map<String, List<Operation>> operationsByProductIndex;
private Map<String, List<Operation>> operationsByUnitIndex;
private Map<String, Double> initialInventoryIndex;
private Map<String, InventorySpec> inventorySpecIndex;
private Map<String, List<InTransitSupply>> inTransitSuppliesByProductSpIndex;
/** 公开构造器 — 自动调用 build() 初始化默认测试数据 */
public TestDataBuilder() {
this(false);
......@@ -295,13 +306,83 @@ public class TestDataBuilder {
* @return 库存点列表, 不存在则返回空列表
*/
public List<StockingPoint> getStockingPointsForProduct(String productId) {
List<StockingPoint> result = new ArrayList<>();
for (ProductSpMapping m : productSpMappings) {
if (m.getProduct().getId().equals(productId)) {
result.add(m.getStockingPoint());
if (stockingPointsByProductIndex == null) {
stockingPointsByProductIndex = new HashMap<>();
for (ProductSpMapping m : productSpMappings) {
stockingPointsByProductIndex
.computeIfAbsent(m.getProduct().getId(), k -> new ArrayList<>())
.add(m.getStockingPoint());
}
}
return stockingPointsByProductIndex.getOrDefault(productId, Collections.emptyList());
}
/**
* 获取产出到指定产品/库存点的所有操作 (索引查询, O(1))。
*/
public List<Operation> getOperationsProducing(String productId, String spId) {
if (operationsByOutputIndex == null) {
operationsByOutputIndex = new HashMap<>();
for (Operation op : operations) {
for (OperationOutput oo : op.getOutputs()) {
operationsByOutputIndex
.computeIfAbsent(oo.getProductId() + "_" + oo.getSpId(),
k -> new ArrayList<>())
.add(op);
}
}
}
return operationsByOutputIndex.getOrDefault(productId + "_" + spId, Collections.emptyList());
}
/**
* 获取产出指定产品 (任意库存点) 的所有操作 (索引查询, O(1))。
*/
public List<Operation> getOperationsProducingProduct(String productId) {
if (operationsByProductIndex == null) {
operationsByProductIndex = new HashMap<>();
for (Operation op : operations) {
for (OperationOutput oo : op.getOutputs()) {
operationsByProductIndex
.computeIfAbsent(oo.getProductId(), k -> new ArrayList<>())
.add(op);
}
}
}
return operationsByProductIndex.getOrDefault(productId, Collections.emptyList());
}
/**
* 获取在指定单元上执行的所有操作 (索引查询, O(1))。
*/
public List<Operation> getOperationsByUnitId(String unitId) {
if (operationsByUnitIndex == null) {
operationsByUnitIndex = new HashMap<>();
for (Operation op : operations) {
for (UnitOperation uo : op.getUnitOperations()) {
operationsByUnitIndex
.computeIfAbsent(uo.getUnitId(), k -> new ArrayList<>())
.add(op);
}
}
}
return operationsByUnitIndex.getOrDefault(unitId, Collections.emptyList());
}
/**
* 获取消耗指定产品/库存点的所有 BOM 输入 (索引查询, O(1))。
*/
public List<OperationInput> getOperationInputsFor(String productId, String spId) {
if (operationInputsIndex == null) {
operationInputsIndex = new HashMap<>();
for (OperationInput input : operationInputs) {
operationInputsIndex
.computeIfAbsent(input.getInputProduct().getId() + "_" + input.getInputSp().getId(),
k -> new ArrayList<>())
.add(input);
}
}
return result;
return operationInputsIndex.getOrDefault(productId + "_" + spId, Collections.emptyList());
}
public List<Operation> getOperations() { return operations; }
public List<OperationInput> getOperationInputs() { return operationInputs; }
......@@ -323,42 +404,62 @@ public class TestDataBuilder {
* @return 初始库存量,不存在则返回 0
*/
public double getInitialInventory(String productId, String spId) {
for (InitialInventory inv : initialInventories) {
if (inv.getProduct().getId().equals(productId)
&& inv.getStockingPoint().getId().equals(spId)) {
return inv.getQuantity();
if (initialInventoryIndex == null) {
initialInventoryIndex = new HashMap<>();
for (InitialInventory inv : initialInventories) {
initialInventoryIndex.put(
inv.getProduct().getId() + "_" + inv.getStockingPoint().getId(),
inv.getQuantity());
}
}
return 0.0;
return initialInventoryIndex.getOrDefault(productId + "_" + spId, 0.0);
}
/**
* 获取指定产品/库存点/周期的销售需求
*/
public List<SalesDemand> getSalesDemandsFor(Product product, StockingPoint sp, Period period) {
List<SalesDemand> result = new ArrayList<>();
for (SalesDemand sd : salesDemands) {
if (sd.getProduct().getId().equals(product.getId())
&& sd.getStockingPoint().getId().equals(sp.getId())
&& sd.getPeriod().getIndex() == period.getIndex()) {
result.add(sd);
if (salesDemandsIndex == null) {
salesDemandsIndex = new HashMap<>();
for (SalesDemand sd : salesDemands) {
salesDemandsIndex
.computeIfAbsent(sd.getKey(), k -> new ArrayList<>())
.add(sd);
}
}
return result;
return salesDemandsIndex.getOrDefault(
product.getId() + "_" + sp.getId() + "_" + period.getIndex(),
Collections.emptyList());
}
/**
* 获取指定产品/库存点/周期的库存规格
*/
public InventorySpec getInventorySpecFor(Product product, StockingPoint sp, Period period) {
for (InventorySpec spec : inventorySpecs) {
if (spec.getProduct().getId().equals(product.getId())
&& spec.getStockingPoint().getId().equals(sp.getId())
&& spec.getPeriod().getIndex() == period.getIndex()) {
return spec;
if (inventorySpecIndex == null) {
inventorySpecIndex = new HashMap<>();
for (InventorySpec spec : inventorySpecs) {
inventorySpecIndex.put(spec.getKey(), spec);
}
}
return null;
return inventorySpecIndex.get(
product.getId() + "_" + sp.getId() + "_" + period.getIndex());
}
/**
* 获取指定产品/库存点的所有在途供应 (索引查询, O(1))。
*/
public List<InTransitSupply> getInTransitSuppliesFor(String productId, String spId) {
if (inTransitSuppliesByProductSpIndex == null) {
inTransitSuppliesByProductSpIndex = new HashMap<>();
for (InTransitSupply its : inTransitSupplies) {
inTransitSuppliesByProductSpIndex
.computeIfAbsent(its.getProduct().getId() + "_" + its.getStockingPoint().getId(),
k -> new ArrayList<>())
.add(its);
}
}
return inTransitSuppliesByProductSpIndex.getOrDefault(productId + "_" + spId, Collections.emptyList());
}
/**
......
......@@ -12,7 +12,7 @@ public class UnitOperation {
private final String unitId; // 所属单元ID
private final String unitName; // 所属单元Name
private final double capacityCoeff; // 产能消耗系数 (单件耗时)
private final double capacityCoeff; // 产能消耗系数 (单件工时)
private final boolean hasLotSize; // 是否有批次大小
private final double lotSize; // 批次大小
private final double qtpfactor; // QuantityToProcessFactor
......
......@@ -29,7 +29,7 @@ public class MacroPlannerModel {
private final MPSolver solver;
// ==================== 生产变量 ====================
/** PTQty[operationId_periodIndex] — 生产量, 范围 [0, +∞) */
/** PTQty[operationId_Uintid_periodIndex] — 生产量, 范围 [0, +∞) */
private final Map<String, MPVariable> ptQtyVars = new HashMap<>();
// ==================== 库存变量 ====================
......
......@@ -58,7 +58,7 @@ public class SolutionPrinter {
// printBomStructure();
// printHeader();
// printDailyViews();
// printKpiSummary();
// printKpiSummary();
printStatistics();
}
......@@ -318,10 +318,8 @@ public class SolutionPrinter {
// 在途到货
double inTransitQty = 0;
for (InTransitSupply its : data.getInTransitSupplies()) {
if (its.getProduct().getId().equals(prod.getId())
&& its.getStockingPoint().getId().equals(sp.getId())
&& p.equals(data.getPeriodByDate(its.getArrivalDate()))) {
for (InTransitSupply its : data.getInTransitSuppliesFor(prod.getId(), sp.getId())) {
if (p.equals(data.getPeriodByDate(its.getArrivalDate()))) {
inTransitQty += its.getQuantity();
}
}
......@@ -540,10 +538,8 @@ public class SolutionPrinter {
// 在途到货
double inTransit = 0;
for (InTransitSupply its : data.getInTransitSupplies()) {
if (its.getProduct().getId().equals(inputProd.getId())
&& its.getStockingPoint().getId().equals(inputSp.getId())
&& p.equals(data.getPeriodByDate(its.getArrivalDate()))) {
for (InTransitSupply its : data.getInTransitSuppliesFor(inputProd.getId(), inputSp.getId())) {
if (p.equals(data.getPeriodByDate(its.getArrivalDate()))) {
inTransit += its.getQuantity();
}
}
......
package com.aps.macroplanner.output.dto;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
......@@ -8,8 +10,10 @@ import java.util.List;
*
* <p>每个操作在每个周期的生产量、产能消耗、批次偏差等。</p>
*/
@Data
public class PeriodTaskResult {
private String snapshotId;
private String dataKey;
private String operationId;
private String operationName;
private String unitId;
......@@ -18,9 +22,9 @@ public class PeriodTaskResult {
/** PTQty — 生产量 */
private double productionQty;
/** 产能消耗 = productionQty × coefficient */
/** 产能消耗 总工时= productionQty × coefficient */
private double capacityUsed;
/** 产能消耗系数 */
/** 单件工时 */
private double capacityCoeff;
/** 批次大小 (如有) */
......@@ -31,51 +35,16 @@ public class PeriodTaskResult {
private double lotSizeUnder;
/** 产出: 产品 → 库存点 */
private final List<OutputInfo> outputs = new ArrayList<>();
// private final List<OutputInfo> outputs = new ArrayList<>();
// ==================== 内嵌类 ====================
/** 产出信息 */
public static class OutputInfo {
public String productId;
public String spId;
public double factor;
}
// ==================== Getters / Setters ====================
public String getOperationId() { return operationId; }
public void setOperationId(String v) { this.operationId = v; }
public String getOperationName() { return operationName; }
public void setOperationName(String v) { this.operationName = v; }
public String getUnitId() { return unitId; }
public void setUnitId(String v) { this.unitId = v; }
public int getPeriodIndex() { return periodIndex; }
public void setPeriodIndex(int v) { this.periodIndex = v; }
public String getPeriodStartDate() { return periodStartDate; }
public void setPeriodStartDate(String v) { this.periodStartDate = v; }
public double getProductionQty() { return productionQty; }
public void setProductionQty(double v) { this.productionQty = v; }
public double getCapacityUsed() { return capacityUsed; }
public void setCapacityUsed(double v) { this.capacityUsed = v; }
public double getCapacityCoeff() { return capacityCoeff; }
public void setCapacityCoeff(double v) { this.capacityCoeff = v; }
public Double getLotSize() { return lotSize; }
public void setLotSize(Double v) { this.lotSize = v; }
public double getLotSizeOver() { return lotSizeOver; }
public void setLotSizeOver(double v) { this.lotSizeOver = v; }
// public static class OutputInfo {
// public String productId;
// public String spId;
// public double factor;
// }
public double getLotSizeUnder() { return lotSizeUnder; }
public void setLotSizeUnder(double v) { this.lotSizeUnder = v; }
public List<OutputInfo> getOutputs() { return outputs; }
}
\ No newline at end of file
package com.aps.macroplanner.output.dto;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
......@@ -8,6 +10,7 @@ import java.util.List;
*
* <p>每个产品在每个库存点每个周期的库存状态、流入流出、规格偏差。</p>
*/
@Data
public class PispipResult {
private String productId;
......
......@@ -95,6 +95,13 @@
<logger name="org.mybatis" level="INFO"/>
<logger name="com.zaxxer.hikari" level="INFO"/>
<!-- 关闭 Parquet 内部 DEBUG 刷屏日志 -->
<logger name="org.apache.parquet" level="ERROR" additivity="false">
<appender-ref ref="CONSOLE"/> <!-- 替换为你项目实际的控制台/文件 appender 名称 -->
</logger>
<!-- 屏蔽 Hadoop Shell 的冗余警告 -->
<logger name="org.apache.hadoop.util.Shell" level="ERROR" additivity="false"/>
<!-- 根据不同环境调整 -->
<springProfile name="dev">
<root level="INFO">
......
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