Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
H
HYH.APSJ
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
佟礼
HYH.APSJ
Commits
2ec4b057
Commit
2ec4b057
authored
Sep 14, 2026
by
Tong Li
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
MP
parent
7d6de509
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
580 additions
and
275 deletions
+580
-275
pom.xml
pom.xml
+65
-0
FlatParquetUtil.java
src/main/java/com/aps/common/util/FlatParquetUtil.java
+126
-0
MacroPlannerOptimizer.java
...main/java/com/aps/macroplanner/MacroPlannerOptimizer.java
+3
-3
MultiLevelBomTestRunner.java
...in/java/com/aps/macroplanner/MultiLevelBomTestRunner.java
+2
-1
BalanceConstraint.java
...va/com/aps/macroplanner/constraint/BalanceConstraint.java
+5
-8
BomConstraint.java
...n/java/com/aps/macroplanner/constraint/BomConstraint.java
+1
-3
CapacityConstraint.java
...a/com/aps/macroplanner/constraint/CapacityConstraint.java
+2
-2
DemandFulfillmentConstraint.java
.../macroplanner/constraint/DemandFulfillmentConstraint.java
+7
-14
DemandSlackLinkageConstraint.java
...macroplanner/constraint/DemandSlackLinkageConstraint.java
+7
-9
BenchmarkDataBuilder.java
.../java/com/aps/macroplanner/data/BenchmarkDataBuilder.java
+2
-2
DataValidator.java
src/main/java/com/aps/macroplanner/data/DataValidator.java
+5
-5
TestDataBuilder.java
src/main/java/com/aps/macroplanner/data/TestDataBuilder.java
+124
-23
UnitOperation.java
src/main/java/com/aps/macroplanner/data/UnitOperation.java
+1
-1
MacroPlannerModel.java
...in/java/com/aps/macroplanner/model/MacroPlannerModel.java
+1
-1
ResultWriter.java
src/main/java/com/aps/macroplanner/output/ResultWriter.java
+201
-150
SolutionPrinter.java
...ain/java/com/aps/macroplanner/output/SolutionPrinter.java
+5
-9
PeriodTaskResult.java
...ava/com/aps/macroplanner/output/dto/PeriodTaskResult.java
+13
-44
PispipResult.java
...in/java/com/aps/macroplanner/output/dto/PispipResult.java
+3
-0
logback-spring.xml
src/main/resources/logback-spring.xml
+7
-0
No files found.
pom.xml
View file @
2ec4b057
...
...
@@ -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>
...
...
src/main/java/com/aps/common/util/FlatParquetUtil.java
0 → 100644
View file @
2ec4b057
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
();
}
}
src/main/java/com/aps/macroplanner/MacroPlannerOptimizer.java
View file @
2ec4b057
...
...
@@ -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
;
}
...
...
src/main/java/com/aps/macroplanner/MultiLevelBomTestRunner.java
View file @
2ec4b057
...
...
@@ -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");
...
...
src/main/java/com/aps/macroplanner/constraint/BalanceConstraint.java
View file @
2ec4b057
...
...
@@ -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
;
...
...
@@ -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
();
}
}
...
...
src/main/java/com/aps/macroplanner/constraint/BomConstraint.java
View file @
2ec4b057
...
...
@@ -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
);
...
...
src/main/java/com/aps/macroplanner/constraint/CapacityConstraint.java
View file @
2ec4b057
...
...
@@ -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
.
getOperations
ByUnitId
(
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
.
getOperations
ByUnitId
(
up
.
getUnitId
()
))
{
for
(
UnitOperation
uo
:
op
.
getUnitOperations
())
{
if
(!
uo
.
getUnitId
().
equals
(
up
.
getUnitId
()))
continue
;
MPVariable
ptVar
=
ptQtyVars
.
get
(
op
.
ptQtyKey
(
uo
,
up
.
getPeriod
().
getIndex
()));
...
...
src/main/java/com/aps/macroplanner/constraint/DemandFulfillmentConstraint.java
View file @
2ec4b057
...
...
@@ -49,19 +49,13 @@ 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
())
{
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
()))
{
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
);
...
...
@@ -70,5 +64,4 @@ public class DemandFulfillmentConstraint {
}
}
}
}
}
\ No newline at end of file
src/main/java/com/aps/macroplanner/constraint/DemandSlackLinkageConstraint.java
View file @
2ec4b057
...
...
@@ -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
:
d
ata
.
getSalesDemandsFor
(
prod
,
sp
,
p
)
)
{
for
(
SalesDemand
sd
:
d
emands
)
{
MPVariable
sdVar
=
sdVars
.
get
(
sd
.
getKey
());
if
(
sdVar
!=
null
)
c
.
setCoefficient
(
sdVar
,
1.0
);
}
...
...
src/main/java/com/aps/macroplanner/data/BenchmarkDataBuilder.java
View file @
2ec4b057
...
...
@@ -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
;
...
...
src/main/java/com/aps/macroplanner/data/DataValidator.java
View file @
2ec4b057
...
...
@@ -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
);
...
...
src/main/java/com/aps/macroplanner/data/TestDataBuilder.java
View file @
2ec4b057
...
...
@@ -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
<>();
if
(
stockingPointsByProductIndex
==
null
)
{
stockingPointsByProductIndex
=
new
HashMap
<>();
for
(
ProductSpMapping
m
:
productSpMappings
)
{
if
(
m
.
getProduct
().
getId
().
equals
(
productId
))
{
result
.
add
(
m
.
getStockingPoint
());
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
result
;
}
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
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
)
{
if
(
initialInventoryIndex
==
null
)
{
initialInventoryIndex
=
new
HashMap
<>();
for
(
InitialInventory
inv
:
initialInventories
)
{
if
(
inv
.
getProduct
().
getId
().
equals
(
productId
)
&&
inv
.
getStockingPoint
().
getId
().
equals
(
spId
))
{
return
inv
.
getQuantity
(
);
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
<>();
if
(
salesDemandsIndex
==
null
)
{
salesDemandsIndex
=
new
HashMap
<>();
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
);
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
)
{
if
(
inventorySpecIndex
==
null
)
{
inventorySpecIndex
=
new
HashMap
<>();
for
(
InventorySpec
spec
:
inventorySpecs
)
{
if
(
spec
.
getProduct
().
getId
().
equals
(
product
.
getId
())
&&
spec
.
getStockingPoint
().
getId
().
equals
(
sp
.
getId
())
&&
spec
.
getPeriod
().
getIndex
()
==
period
.
getIndex
())
{
return
spec
;
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
());
}
/**
...
...
src/main/java/com/aps/macroplanner/data/UnitOperation.java
View file @
2ec4b057
...
...
@@ -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
...
...
src/main/java/com/aps/macroplanner/model/MacroPlannerModel.java
View file @
2ec4b057
...
...
@@ -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
<>();
// ==================== 库存变量 ====================
...
...
src/main/java/com/aps/macroplanner/output/ResultWriter.java
View file @
2ec4b057
This diff is collapsed.
Click to expand it.
src/main/java/com/aps/macroplanner/output/SolutionPrinter.java
View file @
2ec4b057
...
...
@@ -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
();
}
}
...
...
src/main/java/com/aps/macroplanner/output/dto/PeriodTaskResult.java
View file @
2ec4b057
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
src/main/java/com/aps/macroplanner/output/dto/PispipResult.java
View file @
2ec4b057
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
;
...
...
src/main/resources/logback-spring.xml
View file @
2ec4b057
...
...
@@ -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"
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment