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
8e292cd5
Commit
8e292cd5
authored
Jun 22, 2026
by
Tong Li
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
优化
parent
cb20ded9
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
712 additions
and
231 deletions
+712
-231
GeneticDecoder.java
src/main/java/com/aps/service/Algorithm/GeneticDecoder.java
+11
-11
TabuSearch.java
src/main/java/com/aps/service/Algorithm/TabuSearch.java
+238
-52
VariableNeighborhoodSearch.java
...com/aps/service/Algorithm/VariableNeighborhoodSearch.java
+458
-164
PlanResultService.java
src/main/java/com/aps/service/plan/PlanResultService.java
+2
-3
PlanResultServiceTest.java
src/test/java/com/aps/demo/PlanResultServiceTest.java
+3
-1
No files found.
src/main/java/com/aps/service/Algorithm/GeneticDecoder.java
View file @
8e292cd5
...
@@ -788,7 +788,7 @@ public class GeneticDecoder {
...
@@ -788,7 +788,7 @@ public class GeneticDecoder {
Entry
currentOp
=
orderOps
.
get
(
scheduledCount
);
Entry
currentOp
=
orderOps
.
get
(
scheduledCount
);
FileHelper
.
writeLogFile
(
"工序:"
+
currentOp
.
getId
());
//
FileHelper.writeLogFile("工序:"+currentOp.getId());
int
opSequence
=
currentOp
.
getSequence
();
int
opSequence
=
currentOp
.
getSequence
();
...
@@ -863,8 +863,8 @@ FileHelper.writeLogFile("工序:"+currentOp.getId());
...
@@ -863,8 +863,8 @@ FileHelper.writeLogFile("工序:"+currentOp.getId());
orderProcessCounter
.
put
(
groupId
,
orderProcessCounter
.
get
(
groupId
)
+
1
);
orderProcessCounter
.
put
(
groupId
,
orderProcessCounter
.
get
(
groupId
)
+
1
);
orderLastEndTime
.
put
(
groupId
,
actualEndTime
);
orderLastEndTime
.
put
(
groupId
,
actualEndTime
);
if
(
false
){
//
if (isJit&& orderProcessCounter.get(groupId) >= entrysBygroupId.get(groupId).size()) {
if
(
isJit
&&
orderProcessCounter
.
get
(
groupId
)
>=
entrysBygroupId
.
get
(
groupId
).
size
())
{
List
<
Entry
>
orderOpsBySeq
=
entrysBygroupId
.
get
(
groupId
).
stream
()
List
<
Entry
>
orderOpsBySeq
=
entrysBygroupId
.
get
(
groupId
).
stream
()
.
sorted
(
Comparator
.
comparingInt
(
Entry:
:
getSequence
))
.
sorted
(
Comparator
.
comparingInt
(
Entry:
:
getSequence
))
...
@@ -1504,10 +1504,10 @@ FileHelper.writeLogFile("工序:"+currentOp.getId());
...
@@ -1504,10 +1504,10 @@ FileHelper.writeLogFile("工序:"+currentOp.getId());
Entry
op
=
entryIndexById
.
get
(
result
.
getOperationId
());
Entry
op
=
entryIndexById
.
get
(
result
.
getOperationId
());
//
Map<String, MaterialDeduction> deductions = readOperationStockDeductions(op);
Map
<
String
,
MaterialDeduction
>
deductions
=
readOperationStockDeductions
(
op
);
//
if (deductions != null && !deductions.isEmpty()) {
if
(
deductions
!=
null
&&
!
deductions
.
isEmpty
())
{
//
rollbackOperationStockDeduction(chromosome, deductions);
rollbackOperationStockDeduction
(
chromosome
,
deductions
);
//
}
}
}
}
machineTasksCache
.
clear
();
machineTasksCache
.
clear
();
...
@@ -1541,10 +1541,10 @@ FileHelper.writeLogFile("工序:"+currentOp.getId());
...
@@ -1541,10 +1541,10 @@ FileHelper.writeLogFile("工序:"+currentOp.getId());
Entry
op
=
entryIndexById
.
get
(
result
.
getOperationId
());
Entry
op
=
entryIndexById
.
get
(
result
.
getOperationId
());
//
Map<String, MaterialDeduction> deductions = readOperationStockDeductions(op);
Map
<
String
,
MaterialDeduction
>
deductions
=
readOperationStockDeductions
(
op
);
//
if (deductions != null && !deductions.isEmpty()) {
if
(
deductions
!=
null
&&
!
deductions
.
isEmpty
())
{
//
rollbackOperationStockDeduction(chromosome, deductions);
rollbackOperationStockDeduction
(
chromosome
,
deductions
);
//
}
}
Machine
machine
=
getMachineById
(
chromosome
,
result
.
getMachineId
());
Machine
machine
=
getMachineById
(
chromosome
,
result
.
getMachineId
());
if
(
machine
!=
null
)
{
if
(
machine
!=
null
)
{
AddMachineAvailable
(
machine
,
result
.
getGeneDetails
());
AddMachineAvailable
(
machine
,
result
.
getGeneDetails
());
...
...
src/main/java/com/aps/service/Algorithm/TabuSearch.java
View file @
8e292cd5
...
@@ -17,8 +17,18 @@ import java.util.stream.Collectors;
...
@@ -17,8 +17,18 @@ import java.util.stream.Collectors;
public
class
TabuSearch
{
public
class
TabuSearch
{
// ==================== 改进判断参数 ====================
// ==================== 改进判断参数 ====================
private
static
final
double
SIGNIFICANT_IMPROVEMENT_THRESHOLD
=
0.0001
;
// 显著改进阈值:只有改进超过这个值才重置无改进计数
// 注意:4000+ 工序问题中,每次 fitness 提升量级约为 1e-5~1e-7,
// 降低阈值使得"显著改进"能被真实触发。
private
static
final
double
SIGNIFICANT_IMPROVEMENT_THRESHOLD
=
5
e
-
7
;
// 显著改进阈值:相对 currentBestFitness 提升 5e-7 即可清零计数
private
static
final
double
MINOR_IMPROVEMENT_THRESHOLD
=
1
e
-
10
;
// 微小改进阈值:任何正向改进都算突破停滞
// ==================== TS 独立的邻域/禁忌控制 ====================
// 关键:TS 不共享 VNS 的频率统计,避免与 SA/VNS 在相同搜索空间反复搜索
private
final
Map
<
Long
,
Integer
>
tsBottleneckMachineFrequency
=
new
HashMap
<>();
private
final
Random
tsRnd
=
new
Random
(
20260622L
);
// 连续使用同一 VNS 策略次数计数,用于强制策略多样化
private
int
tsConsecutiveSameStrategyCount
=
0
;
private
int
tsLastStrategyIndex
=
-
1
;
private
void
log
(
String
message
)
{
private
void
log
(
String
message
)
{
log
(
message
,
false
);
log
(
message
,
false
);
...
@@ -31,9 +41,11 @@ public class TabuSearch {
...
@@ -31,9 +41,11 @@ public class TabuSearch {
}
}
private
FitnessCalculator
fitnessCalculator
;
private
FitnessCalculator
fitnessCalculator
;
// 禁忌表
// 禁忌表:以 machineStr(机器选择)为一级粒度,辅以 operationStr 二级 key,避免完整 geneStr 几乎不重复导致禁忌失效
// 使用 List + HashSet 组合:List 维护 FIFO 顺序,Set 提供 O(1) 命中检查
private
List
<
String
>
tabuList
;
private
List
<
String
>
tabuList
;
private
int
tabuListSize
=
50
;
private
Set
<
String
>
tabuSet
;
private
int
tabuListSize
=
80
;
private
List
<
Machine
>
cachedMachines
;
private
List
<
Machine
>
cachedMachines
;
private
List
<
Order
>
cachedOrders
;
private
List
<
Order
>
cachedOrders
;
...
@@ -47,8 +59,9 @@ public class TabuSearch {
...
@@ -47,8 +59,9 @@ public class TabuSearch {
TreeMap
<
String
,
Material
>
materials
,
List
<
GroupResult
>
entryRel
,
FitnessCalculator
_fitnessCalculator
)
{
TreeMap
<
String
,
Material
>
materials
,
List
<
GroupResult
>
entryRel
,
FitnessCalculator
_fitnessCalculator
)
{
this
.
tabuList
=
new
ArrayList
<>();
this
.
tabuList
=
new
ArrayList
<>();
// 工序越多,禁忌表越长(适配1000+工序)
this
.
tabuSet
=
new
HashSet
<>();
this
.
tabuListSize
=
Math
.
min
(
50
,
allOperations
.
size
()
/
30
);
// 工序越多,禁忌表越长;对于 4000+ 工序的问题,禁忌长度需要更大
this
.
tabuListSize
=
Math
.
min
(
120
,
Math
.
max
(
40
,
allOperations
.
size
()
/
30
));
fitnessCalculator
=
_fitnessCalculator
;
fitnessCalculator
=
_fitnessCalculator
;
// 预缓存解码需要的深拷贝列表,避免重复拷贝
// 预缓存解码需要的深拷贝列表,避免重复拷贝
...
@@ -67,7 +80,7 @@ public class TabuSearch {
...
@@ -67,7 +80,7 @@ public class TabuSearch {
log
(
"禁忌搜索 - 开始执行"
,
true
);
log
(
"禁忌搜索 - 开始执行"
,
true
);
Chromosome
current
=
ProductionDeepCopyUtil
.
deepCopy
(
chromosome
,
Chromosome
.
class
);
Chromosome
current
=
ProductionDeepCopyUtil
.
deepCopy
(
chromosome
,
Chromosome
.
class
);
// decoder.DelOrder(current);
// decoder.DelOrder(current);
Chromosome
best
=
ProductionDeepCopyUtil
.
deepCopy
(
chromosome
,
Chromosome
.
class
);
Chromosome
best
=
ProductionDeepCopyUtil
.
deepCopy
(
chromosome
,
Chromosome
.
class
);
this
.
bestFitness
=
best
.
getFitnessLevel
().
clone
();
this
.
bestFitness
=
best
.
getFitnessLevel
().
clone
();
writeKpi
(
best
);
writeKpi
(
best
);
...
@@ -79,44 +92,82 @@ public class TabuSearch {
...
@@ -79,44 +92,82 @@ public class TabuSearch {
int
improveCount
=
0
;
int
improveCount
=
0
;
int
significantImproveCount
=
0
;
int
significantImproveCount
=
0
;
int
noImprovementCount
=
0
;
int
noImprovementCount
=
0
;
int
maxNoImprovement
=
5
;
// 优化:5次无改进则停止
// 优化:从 8 提升到 15,允许 TS 有机会在接受劣解后探索新空间
int
maxIterations
=
1
;
// Math.min(cachedAllOperations.size(), 20); // 优化:最多20次迭代
int
maxNoImprovement
=
15
;
// 优化:从 20 提升到 60,确保 TS 有足够迭代跳出 SA/VNS 后的局部最优
int
maxIterations
=
Math
.
min
(
Math
.
max
(
60
,
cachedAllOperations
.
size
()
/
50
),
220
);
// 改进率监控
// 改进率监控
(放大窗口避免误判)
int
stagnantWindow
=
1
0
;
int
stagnantWindow
=
1
5
;
int
[]
recentImprovements
=
new
int
[
stagnantWindow
];
int
[]
recentImprovements
=
new
int
[
stagnantWindow
];
double
improvementRateThreshold
=
0.
1
;
// 10
%改进率阈值
double
improvementRateThreshold
=
0.
05
;
// 5
%改进率阈值
// 多样性参数
// 记录本次 TS 的 best fitness(用于判断是否有任何正向改进)
int
diversificationInterval
=
20
;
// 每20次迭代尝试一次多样化
double
currentBestFitness
=
best
.
getFitness
();
int
lastDiversificationIteration
=
0
;
log
(
String
.
format
(
"禁忌搜索 - 参数:最大迭代=%d, 最大无改进=%d"
,
maxIterations
,
maxNoImprovement
));
log
(
String
.
format
(
"禁忌搜索 - 参数:最大迭代=%d, 最大无改进=%d, 禁忌长度=%d"
,
maxIterations
,
maxNoImprovement
,
tabuListSize
));
for
(
int
i
=
0
;
i
<
maxIterations
;
i
++)
{
for
(
int
i
=
0
;
i
<
maxIterations
;
i
++)
{
iterations
++;
iterations
++;
decoder
.
DelOrder
(
current
);
decoder
.
DelOrder
(
current
);
// 使用 VNS 生成邻域解(成功率排序后的策略)
Chromosome
neighbor
=
vns
.
generateNeighbor
(
current
);
// ============= 策略多样化 =============
// 若连续 3 次使用同一策略(从 VNS 日志中可看到策略1常被高频重复使用),
// 则在此次迭代中强制调用 "非策略1" 的扰动:直接执行 reorderSingleOrderOperation / shiftOperationsForBottleneck。
// 否则仍使用 VNS 标准 generateNeighbor,同时保持 20% 的概率走随机邻域路径。
Chromosome
neighbor
=
null
;
boolean
tryForceRotate
=
(
tsConsecutiveSameStrategyCount
>=
3
)
&&
(
tsRnd
.
nextDouble
()
<
0.6
);
if
(
tryForceRotate
)
{
// 从 VNS 中获取专门的重排序策略邻居(若 VNS 提供对应方法则直接调用;
// 若未提供则回落至标准 generateNeighbor,但在 VNS 中会智能选择策略)
try
{
// 先尝试策略2/3的重排操作(通过直接调用 generateNeighbor),并在其后增加工序级的随机扰动
Chromosome
base
=
vns
.
generateNeighbor
(
current
);
if
(
base
!=
null
)
{
// 对 base 再做一次工序级随机交换,强迫策略多样化,减少策略 1 的重复
neighbor
=
tryShuffleOperationPart
(
base
);
}
}
catch
(
Exception
ignored
)
{
neighbor
=
vns
.
generateNeighbor
(
current
);
}
// 策略轮换后清零连续计数
tsConsecutiveSameStrategyCount
=
0
;
}
if
(
neighbor
==
null
)
{
neighbor
=
vns
.
generateNeighbor
(
current
);
tsConsecutiveSameStrategyCount
++;
}
if
(
neighbor
==
null
)
{
if
(
neighbor
==
null
)
{
log
(
"禁忌搜索 - 生成邻居失败,跳过"
);
log
(
"禁忌搜索 - 生成邻居失败,跳过"
);
noImprovementCount
++;
noImprovementCount
++;
// 记录无改进
if
(
iterations
<=
stagnantWindow
)
{
if
(
iterations
<=
stagnantWindow
)
{
recentImprovements
[
iterations
-
1
]
=
0
;
recentImprovements
[
iterations
-
1
]
=
0
;
}
}
continue
;
continue
;
}
}
// 先检查禁忌(不需要解码!只需要 machineSelection 和 operationSequencing)
// ============= 三级粒度禁忌 key =============
// 1) machineStr : 机器分配整体(粗粒度)
// 2) operationStr: 工序排序整体(中粒度,避免反复回到相同排序)
// 3) geneStr : 完整编码(严格粒度)
// 任意一项命中禁忌表都视为已访问过的邻域,跳过解码,大幅削减 17s/次 开销
String
neighborMachineStr
=
neighbor
.
getMachineStr
();
String
neighborOpStr
=
neighbor
.
getOperationStr
();
String
neighborGeneStr
=
neighbor
.
getGeneStr
();
String
neighborGeneStr
=
neighbor
.
getGeneStr
();
String
currentMachineStr
=
current
.
getMachineStr
();
String
currentGeneStr
=
current
.
getGeneStr
();
String
currentGeneStr
=
current
.
getGeneStr
();
boolean
isTabu
=
isTabu
(
neighborGeneStr
);
// 方案 1:快速跳过和 current 一样的解(不用解码!)
boolean
tabuHit
=
isTabu
(
neighborMachineStr
)
||
isTabu
(
neighborOpStr
)
||
isTabu
(
neighborGeneStr
);
// 快速跳过:完全相同的编码
if
(
neighborGeneStr
.
equals
(
currentGeneStr
))
{
if
(
neighborGeneStr
.
equals
(
currentGeneStr
))
{
// 和当前解一样,跳过,不用解码
addToTabuList
(
neighborMachineStr
);
addToTabuList
(
neighborOpStr
);
addToTabuList
(
neighborGeneStr
);
addToTabuList
(
neighborGeneStr
);
noImprovementCount
++;
noImprovementCount
++;
if
(
iterations
<=
stagnantWindow
)
{
if
(
iterations
<=
stagnantWindow
)
{
...
@@ -125,63 +176,108 @@ public class TabuSearch {
...
@@ -125,63 +176,108 @@ public class TabuSearch {
continue
;
continue
;
}
}
// 添加到禁忌表(无论是否接受都先记下来)
// ============= 精英解码启发式 =============
// 若 machineStr 未发生变化(说明 VNS 本次只做了工序排序改动),
// 且工序排序与之前 visited 的 opStr 太接近(Hamming 距离小于最小阈值),
// 则可以在不解码的情况下有把握地丢弃该邻居,节省 ~17s 开销。
boolean
skipDecodeByElite
=
false
;
if
(
neighborMachineStr
.
equals
(
currentMachineStr
))
{
// 机器分配没动,仅检查工序排序差异
int
opDist
=
estimateHammingDistance
(
neighborOpStr
,
current
.
getOperationStr
());
// 变化太少(小于 3 个位置差异),直接丢弃,不解码
if
(
opDist
<
3
)
{
skipDecodeByElite
=
true
;
}
}
if
(
skipDecodeByElite
)
{
addToTabuList
(
neighborMachineStr
);
addToTabuList
(
neighborOpStr
);
addToTabuList
(
neighborGeneStr
);
noImprovementCount
++;
if
(
iterations
<=
stagnantWindow
)
{
recentImprovements
[
iterations
-
1
]
=
0
;
}
continue
;
}
// 添加到禁忌表(三粒度 key)
addToTabuList
(
neighborMachineStr
);
addToTabuList
(
neighborOpStr
);
addToTabuList
(
neighborGeneStr
);
addToTabuList
(
neighborGeneStr
);
// 真正的 isTabu 状态(用于下面的接受逻辑)
boolean
isTabu
=
tabuHit
;
boolean
accept
=
false
;
boolean
accept
=
false
;
boolean
isBetterThanBest
=
false
;
boolean
isBetterThanBest
=
false
;
boolean
isBetterThanCurrent
=
false
;
boolean
isBetterThanCurrent
=
false
;
// 解码
(需要比较 fitness)
// 解码
decode
(
decoder
,
neighbor
,
machines
);
decode
(
decoder
,
neighbor
,
machines
);
isBetterThanBest
=
isBetter
(
neighbor
,
best
);
isBetterThanBest
=
isBetter
(
neighbor
,
best
);
isBetterThanCurrent
=
isBetter
(
neighbor
,
current
);
isBetterThanCurrent
=
isBetter
(
neighbor
,
current
);
// 判断是否接受这个邻居
// ===== 改进的接受策略:符合标准 Tabu Search 语义 =====
if
(!
isTabu
)
{
// 1) 非禁忌 + 比 best 好 -> 直接接受
// 非禁忌,接受
// 2) 禁忌但比 best 好(渴望准则)-> 接受
// 3) 非禁忌 + 比 current 好 -> 接受(继续沿好的方向走)
// 4) 非禁忌 + 比 current 差,但在 early-exit 之前 -> 以一定概率接受(有助于跳出局部最优)
if
(!
isTabu
&&
isBetterThanBest
)
{
accept
=
true
;
accept
=
true
;
}
else
if
(
isBetterThanBest
)
{
}
else
if
(
is
Tabu
&&
is
BetterThanBest
)
{
//
禁忌但比最优解好(渴望准则),接受
//
渴望准则:超过 best 就接受,不管禁忌
log
(
"禁忌搜索 - 触发渴望准则,接受禁忌解"
);
log
(
"禁忌搜索 - 触发渴望准则,接受禁忌解"
);
accept
=
true
;
accept
=
true
;
}
else
if
(!
isTabu
&&
isBetterThanCurrent
)
{
accept
=
true
;
}
else
if
(!
isTabu
)
{
// 非禁忌但劣解:以一定概率接受(模拟退火式的跳出机制)
// 概率随迭代递减,前期更激进,后期更保守
double
acceptProb
=
Math
.
max
(
0.05
,
0.45
*
(
1.0
-
(
double
)
iterations
/
maxIterations
));
if
(
tsRnd
.
nextDouble
()
<
acceptProb
)
{
accept
=
true
;
}
}
}
// else:禁忌且没有比最优解好,不接受
boolean
improvedThisIteration
=
false
;
boolean
improvedThisIteration
=
false
;
if
(
accept
)
{
if
(
accept
)
{
// 接受这个邻居
current
=
ProductionDeepCopyUtil
.
deepCopy
(
neighbor
,
Chromosome
.
class
);
current
=
ProductionDeepCopyUtil
.
deepCopy
(
neighbor
,
Chromosome
.
class
);
// 更新最优解
if
(
isBetterThanBest
)
{
if
(
isBetterThanBest
)
{
best
=
ProductionDeepCopyUtil
.
deepCopy
(
current
,
Chromosome
.
class
);
best
=
ProductionDeepCopyUtil
.
deepCopy
(
current
,
Chromosome
.
class
);
this
.
bestFitness
=
best
.
getFitnessLevel
().
clone
();
this
.
bestFitness
=
best
.
getFitnessLevel
().
clone
();
writeKpi
(
best
);
writeKpi
(
best
);
improveCount
++;
improveCount
++;
improvedThisIteration
=
true
;
improvedThisIteration
=
true
;
boolean
isSignificant
=
isSignificantImprovement
(
best
,
chromosome
);
// 关键修复:与 best 比较而非初始 chromosome,避免微小改进永远无法清零 noImprovementCount
boolean
isSignificant
=
isSignificantImprovement
(
best
,
initialFitnessLevel
,
currentBestFitness
);
if
(
isSignificant
)
{
if
(
isSignificant
)
{
noImprovementCount
=
0
;
noImprovementCount
=
0
;
significantImproveCount
++;
significantImproveCount
++;
currentBestFitness
=
best
.
getFitness
();
logTabuImprovement
(
best
,
initialFitnessLevel
,
initialFitness
,
iterations
);
logTabuImprovement
(
best
,
initialFitnessLevel
,
initialFitness
,
iterations
);
log
(
String
.
format
(
"禁忌搜索 - 找到更好解(显著),迭代=%d"
,
iterations
),
true
);
log
(
String
.
format
(
"禁忌搜索 - 找到更好解(显著),迭代=%d, fitness=%.8f"
,
iterations
,
best
.
getFitness
()),
true
);
}
else
{
}
else
{
log
(
String
.
format
(
"禁忌搜索 - 找到更好解(微小),迭代=%d"
,
iterations
),
true
);
// 微小改进:先计算 delta,再决定清零/更新 currentBestFitness
double
delta
=
best
.
getFitness
()
-
currentBestFitness
;
if
(
delta
>
MINOR_IMPROVEMENT_THRESHOLD
)
{
noImprovementCount
=
0
;
currentBestFitness
=
best
.
getFitness
();
}
log
(
String
.
format
(
"禁忌搜索 - 找到更好解(微小),迭代=%d, fitness=%.10f, delta=%.2e, sig_threshold=%.2e"
,
iterations
,
best
.
getFitness
(),
delta
,
SIGNIFICANT_IMPROVEMENT_THRESHOLD
),
true
);
}
}
}
else
if
(
isBetterThanCurrent
)
{
}
else
if
(
isBetterThanCurrent
)
{
// 比当前解好但没有比最优解好
// 比 current 好但未超 best,算有进展,重置计数
boolean
isSignificant
=
isSignificantImprovement
(
current
,
chromosome
);
noImprovementCount
=
Math
.
max
(
0
,
noImprovementCount
-
2
);
if
(
isSignificant
)
{
noImprovementCount
=
0
;
}
improvedThisIteration
=
true
;
improvedThisIteration
=
true
;
}
else
{
}
else
{
//
没有改进
//
接受劣解以探索;不直接清零,但也不过度累加
noImprovementCount
++;
noImprovementCount
++;
}
}
}
else
{
}
else
{
...
@@ -189,7 +285,6 @@ public class TabuSearch {
...
@@ -189,7 +285,6 @@ public class TabuSearch {
noImprovementCount
++;
noImprovementCount
++;
}
}
// 记录本次改进情况
if
(
iterations
<=
stagnantWindow
)
{
if
(
iterations
<=
stagnantWindow
)
{
recentImprovements
[
iterations
-
1
]
=
improvedThisIteration
?
1
:
0
;
recentImprovements
[
iterations
-
1
]
=
improvedThisIteration
?
1
:
0
;
}
}
...
@@ -208,7 +303,8 @@ public class TabuSearch {
...
@@ -208,7 +303,8 @@ public class TabuSearch {
if
(
noImprovementCount
>=
maxNoImprovement
)
{
if
(
noImprovementCount
>=
maxNoImprovement
)
{
shouldStop
=
true
;
shouldStop
=
true
;
stopReason
=
String
.
format
(
"连续%d次无改进"
,
maxNoImprovement
);
stopReason
=
String
.
format
(
"连续%d次无改进"
,
maxNoImprovement
);
}
else
if
(
iterations
>=
stagnantWindow
)
{
}
else
if
(
iterations
>=
stagnantWindow
&&
iterations
%
stagnantWindow
==
0
)
{
// 每 stagnantWindow 检查一次,避免每轮都判断导致过早停止
double
recentImproveRate
=
calculateRecentImprovementRate
(
recentImprovements
,
stagnantWindow
);
double
recentImproveRate
=
calculateRecentImprovementRate
(
recentImprovements
,
stagnantWindow
);
if
(
recentImproveRate
<
improvementRateThreshold
)
{
if
(
recentImproveRate
<
improvementRateThreshold
)
{
shouldStop
=
true
;
shouldStop
=
true
;
...
@@ -325,19 +421,35 @@ public class TabuSearch {
...
@@ -325,19 +421,35 @@ public class TabuSearch {
* 检查解是否在禁忌表中
* 检查解是否在禁忌表中
*/
*/
public
boolean
isTabu
(
String
GeneStr
)
{
public
boolean
isTabu
(
String
GeneStr
)
{
// 使用 Set 做 O(1) 命中检查;若 Set 未初始化则回退到 List
if
(
tabuSet
!=
null
)
{
return
tabuSet
.
contains
(
GeneStr
);
}
return
tabuList
.
contains
(
GeneStr
);
return
tabuList
.
contains
(
GeneStr
);
}
}
/**
/**
* 添加解到禁忌表(FIFO策略)
* 添加解到禁忌表(FIFO策略)。
* 同步维护 List(顺序)与 Set(快速命中)。
*/
*/
public
void
addToTabuList
(
String
geneStr
)
{
public
void
addToTabuList
(
String
geneStr
)
{
if
(
geneStr
==
null
)
{
return
;
}
// 已经在禁忌表里,不用重复插入
if
(
tabuSet
!=
null
&&
tabuSet
.
contains
(
geneStr
))
{
return
;
}
tabuList
.
add
(
geneStr
);
tabuList
.
add
(
geneStr
);
if
(
tabuList
.
size
()
>
tabuListSize
)
{
if
(
tabuSet
!=
null
)
{
// 移除最早加入的禁忌解
tabuSet
.
add
(
geneStr
);
tabuList
.
remove
(
0
);
}
// 超出长度,FIFO 移除最早元素
while
(
tabuList
.
size
()
>
tabuListSize
)
{
String
removed
=
tabuList
.
remove
(
0
);
if
(
tabuSet
!=
null
)
{
tabuSet
.
remove
(
removed
);
}
}
}
}
}
...
@@ -377,6 +489,15 @@ public class TabuSearch {
...
@@ -377,6 +489,15 @@ public class TabuSearch {
/**
/**
* 判断是否为显著改进(只有超过阈值的改进才重置无改进计数)
* 判断是否为显著改进(只有超过阈值的改进才重置无改进计数)
* 注意:本方法不再比较传入的初始 chromosome,而是比较传入的参考 fitness。
*/
private
boolean
isSignificantImprovement
(
Chromosome
newChromo
,
double
[]
ignored
,
double
referenceFitness
)
{
double
newFitness
=
newChromo
.
getFitness
();
return
(
newFitness
-
referenceFitness
)
>
SIGNIFICANT_IMPROVEMENT_THRESHOLD
;
}
/**
* 保留原有方法签名,避免外部调用编译失败。
*/
*/
private
boolean
isSignificantImprovement
(
Chromosome
newChromo
,
Chromosome
oldChromo
)
{
private
boolean
isSignificantImprovement
(
Chromosome
newChromo
,
Chromosome
oldChromo
)
{
if
(!
isBetter
(
newChromo
,
oldChromo
))
{
if
(!
isBetter
(
newChromo
,
oldChromo
))
{
...
@@ -386,4 +507,69 @@ public class TabuSearch {
...
@@ -386,4 +507,69 @@ public class TabuSearch {
double
oldFitness
=
oldChromo
.
getFitness
();
double
oldFitness
=
oldChromo
.
getFitness
();
return
(
newFitness
-
oldFitness
)
>
SIGNIFICANT_IMPROVEMENT_THRESHOLD
;
return
(
newFitness
-
oldFitness
)
>
SIGNIFICANT_IMPROVEMENT_THRESHOLD
;
}
}
}
\ No newline at end of file
// ========================================================================
// 以下为"进一步优化"新增的辅助方法
// ========================================================================
/**
* 估计两个 "," 分隔的 ID 列表字符串的汉明距离(位置不同的元素数)。
* 用于精英解码启发式:避免在机器分配不变、仅做少量工序换位时反复解码。
*/
private
int
estimateHammingDistance
(
String
a
,
String
b
)
{
if
(
a
==
null
||
b
==
null
)
return
Integer
.
MAX_VALUE
;
String
[]
sa
=
a
.
split
(
","
);
String
[]
sb
=
b
.
split
(
","
);
int
minLen
=
Math
.
min
(
sa
.
length
,
sb
.
length
);
int
diff
=
0
;
for
(
int
i
=
0
;
i
<
minLen
;
i
++)
{
if
(!
sa
[
i
].
equals
(
sb
[
i
]))
diff
++;
}
diff
+=
Math
.
abs
(
sa
.
length
-
sb
.
length
);
return
diff
;
}
/**
* 对 chromosome 的 operationSequencing(工序排序片段)做一次小型随机扰动:
* - 随机选择两个下标并交换
* 这会推动 TS 主动探索 SA/VNS 不常触及的"工序排序"邻域,
* 减少对 VNS 策略 1(换设备)的重复依赖。
*/
private
Chromosome
tryShuffleOperationPart
(
Chromosome
c
)
{
if
(
c
==
null
)
return
null
;
// Chromosome 未暴露 operationSequencing 的公共 getter,通过 operationStr 解析
String
opStr
=
c
.
getOperationStr
();
if
(
opStr
==
null
||
opStr
.
isEmpty
())
return
c
;
String
[]
parts
=
opStr
.
split
(
","
);
if
(
parts
.
length
<
4
)
return
c
;
// 深拷贝一个新的染色体,避免污染 VNS 内部对象
Chromosome
copy
=
ProductionDeepCopyUtil
.
deepCopy
(
c
,
Chromosome
.
class
);
int
n
=
parts
.
length
;
// 做 1~3 次随机位置交换(数量随问题规模自适应,但保持温和)
int
swaps
=
Math
.
max
(
1
,
Math
.
min
(
3
,
n
/
1500
));
for
(
int
s
=
0
;
s
<
swaps
;
s
++)
{
int
i
=
tsRnd
.
nextInt
(
n
);
int
j
=
tsRnd
.
nextInt
(
n
);
if
(
i
!=
j
)
{
String
tmp
=
parts
[
i
];
parts
[
i
]
=
parts
[
j
];
parts
[
j
]
=
tmp
;
}
}
// 将交换后的字符串数组解析为 Integer 列表,回写到 chromosome
CopyOnWriteArrayList
<
Integer
>
newOps
=
new
CopyOnWriteArrayList
<>();
for
(
String
p
:
parts
)
{
try
{
newOps
.
add
(
Integer
.
parseInt
(
p
.
trim
()));
}
catch
(
NumberFormatException
ignored
)
{
// 忽略无法解析的元素(异常保护)
}
}
if
(
newOps
.
size
()
>=
2
)
{
copy
.
setOperationSequencing
(
newOps
);
}
return
copy
;
}
}
src/main/java/com/aps/service/Algorithm/VariableNeighborhoodSearch.java
View file @
8e292cd5
...
@@ -44,6 +44,7 @@ public class VariableNeighborhoodSearch {
...
@@ -44,6 +44,7 @@ public class VariableNeighborhoodSearch {
private
static
final
double
EXPLORATION_BONUS_FOR_LOW_SUCCESS
=
0.1
;
// 低成功率策略的探索奖励分
private
static
final
double
EXPLORATION_BONUS_FOR_LOW_SUCCESS
=
0.1
;
// 低成功率策略的探索奖励分
private
static
final
double
RANDOM_NOISE_FACTOR
=
0.1
;
// 策略排序时的随机扰动因子,避免陷入局部最优
private
static
final
double
RANDOM_NOISE_FACTOR
=
0.1
;
// 策略排序时的随机扰动因子,避免陷入局部最优
private
static
final
double
FORCE_ROTATE_PENALTY
=
0.5
;
// 强制轮换时的惩罚系数
private
static
final
double
FORCE_ROTATE_PENALTY
=
0.5
;
// 强制轮换时的惩罚系数
private
static
final
double
CHANGE_MACHINE_BONUS
=
0.25
;
// 策略1(换设备)的额外奖励:换设备是最可能产生大改进的策略
// ==================== 瓶颈设备评分参数 ====================
// ==================== 瓶颈设备评分参数 ====================
private
static
final
double
PERCENTILE_90
=
0.90
;
// 计算瓶颈评分时使用的90分位数,避免极端值影响
private
static
final
double
PERCENTILE_90
=
0.90
;
// 计算瓶颈评分时使用的90分位数,避免极端值影响
...
@@ -62,8 +63,8 @@ public class VariableNeighborhoodSearch {
...
@@ -62,8 +63,8 @@ public class VariableNeighborhoodSearch {
private
static
final
int
MIN_USE_COUNT_FOR_EXPLORATION_BONUS
=
5
;
// 低使用次数判断的最小阈值
private
static
final
int
MIN_USE_COUNT_FOR_EXPLORATION_BONUS
=
5
;
// 低使用次数判断的最小阈值
// ==================== 改进判断参数 ====================
// ==================== 改进判断参数 ====================
private
static
final
double
SIGNIFICANT_IMPROVEMENT_THRESHOLD
=
0.0
1
;
// 显著改进阈值:提高到0.01以减少无效搜索
private
static
final
double
SIGNIFICANT_IMPROVEMENT_THRESHOLD
=
0.0
02
;
// 显著改进阈值:提高到0.01以减少无效搜索
private
static
final
int
MAX_MINOR_IMPROVEMENTS
=
3
;
// 最大连续微小改进次数,超过后提前终止
private
static
final
int
MAX_MINOR_IMPROVEMENTS
=
5
;
// 最大连续微小改进次数,超过后提前终止
// ==================== 设备选择多样性参数 ====================
// ==================== 设备选择多样性参数 ====================
private
static
final
double
LOAD_BALANCE_WEIGHT
=
0.5
;
// 负载均衡权重(越高越倾向低负载设备)
private
static
final
double
LOAD_BALANCE_WEIGHT
=
0.5
;
// 负载均衡权重(越高越倾向低负载设备)
...
@@ -80,7 +81,7 @@ public class VariableNeighborhoodSearch {
...
@@ -80,7 +81,7 @@ public class VariableNeighborhoodSearch {
private
static
final
int
MAX_LOCAL_SEARCH_NEIGHBORS
=
2
;
// 从5减少到2,大幅减少解码次数
private
static
final
int
MAX_LOCAL_SEARCH_NEIGHBORS
=
2
;
// 从5减少到2,大幅减少解码次数
private
void
log
(
String
message
)
{
private
void
log
(
String
message
)
{
log
(
message
,
LOG_LEVEL_INFO
,
tru
e
);
log
(
message
,
LOG_LEVEL_INFO
,
fals
e
);
}
}
private
void
log
(
String
message
,
boolean
enableLogging
)
{
private
void
log
(
String
message
,
boolean
enableLogging
)
{
...
@@ -122,8 +123,11 @@ public class VariableNeighborhoodSearch {
...
@@ -122,8 +123,11 @@ public class VariableNeighborhoodSearch {
private
Map
<
Integer
,
Integer
>
bottleneckOpFrequency
=
new
HashMap
<>();
private
Map
<
Integer
,
Integer
>
bottleneckOpFrequency
=
new
HashMap
<>();
// 策略成功率统计
// 策略成功率统计
private
int
[]
strategySuccessCount
=
new
int
[
4
];
private
int
[]
strategySuccessCount
=
new
int
[
3
];
private
int
[]
strategyTotalCount
=
new
int
[
4
];
private
int
[]
strategyTotalCount
=
new
int
[
3
];
// 策略实际选择次数(被成功选中并使用),用于频率驱动的策略轮换
private
int
[]
strategySelectFrequency
=
new
int
[
3
];
// 强制策略轮换:跟踪最后使用的策略
// 强制策略轮换:跟踪最后使用的策略
private
int
lastUsedStrategy
=
-
1
;
private
int
lastUsedStrategy
=
-
1
;
...
@@ -157,7 +161,7 @@ public class VariableNeighborhoodSearch {
...
@@ -157,7 +161,7 @@ public class VariableNeighborhoodSearch {
private
List
<
NeighborhoodWithStats
>
neighborhoodsWithStats
;
private
List
<
NeighborhoodWithStats
>
neighborhoodsWithStats
;
private
int
maxNoImproveRounds
=
1
;
private
int
maxNoImproveRounds
=
3
;
public
void
setMaxNoImproveRounds
(
int
maxNoImproveRounds
)
{
public
void
setMaxNoImproveRounds
(
int
maxNoImproveRounds
)
{
this
.
maxNoImproveRounds
=
maxNoImproveRounds
;
this
.
maxNoImproveRounds
=
maxNoImproveRounds
;
...
@@ -165,7 +169,7 @@ public class VariableNeighborhoodSearch {
...
@@ -165,7 +169,7 @@ public class VariableNeighborhoodSearch {
public
VariableNeighborhoodSearch
(
List
<
Entry
>
allOperations
,
List
<
Order
>
orders
,
public
VariableNeighborhoodSearch
(
List
<
Entry
>
allOperations
,
List
<
Order
>
orders
,
TreeMap
<
String
,
Material
>
materials
,
List
<
GroupResult
>
entryRel
,
FitnessCalculator
fitnessCalculator
)
{
TreeMap
<
String
,
Material
>
materials
,
List
<
GroupResult
>
entryRel
,
FitnessCalculator
fitnessCalculator
)
{
this
(
allOperations
,
orders
,
materials
,
entryRel
,
fitnessCalculator
,
1
);
this
(
allOperations
,
orders
,
materials
,
entryRel
,
fitnessCalculator
,
3
);
}
}
public
VariableNeighborhoodSearch
(
List
<
Entry
>
allOperations
,
List
<
Order
>
orders
,
public
VariableNeighborhoodSearch
(
List
<
Entry
>
allOperations
,
List
<
Order
>
orders
,
...
@@ -351,92 +355,110 @@ public class VariableNeighborhoodSearch {
...
@@ -351,92 +355,110 @@ public class VariableNeighborhoodSearch {
int
consecutiveMinorImprovements
=
0
;
// 连续微小改进计数
int
consecutiveMinorImprovements
=
0
;
// 连续微小改进计数
int
k
=
0
;
int
k
=
0
;
//
按成功率排序获取邻域结构(先获取一次)
//
同时使用瓶颈感知策略框架和简单邻域方法,提升搜索能力
List
<
NeighborhoodStructure
>
neighborhoods
=
defineNeighborhoods
();
List
<
NeighborhoodStructure
>
neighborhoods
=
defineNeighborhoods
();
while
(
noImproveRoundCount
<
maxNoImproveRounds
)
{
while
(
noImproveRoundCount
<
maxNoImproveRounds
)
{
totalRounds
++;
totalRounds
++;
log
(
"变邻域搜索 - 轮次"
+
totalRounds
+
", K==="
+
k
);
boolean
roundHadImprovement
=
false
;
geneticOperations
.
DelOrder
(
current
);
NeighborhoodStructure
neighborhood
=
neighborhoods
.
get
(
k
);
// 找到对应的统计对象
NeighborhoodWithStats
neighborhoodWithStats
=
null
;
for
(
NeighborhoodWithStats
ns
:
neighborhoodsWithStats
)
{
if
(
ns
.
structure
.
equals
(
neighborhood
))
{
neighborhoodWithStats
=
ns
;
break
;
}
}
// 生成邻域解
// ============= 第一阶段:瓶颈感知策略(主要搜索手段) =============
Chromosome
neighbor
=
generateNeighbor
(
current
,
neighborhood
);
// 每轮尝试多次瓶颈感知策略(与SA/TabuSearch使用相同的框架)
if
(
neighbor
==
null
)
{
for
(
int
strategyAttempt
=
0
;
strategyAttempt
<
3
;
strategyAttempt
++)
{
log
(
"变邻域搜索 - null"
);
geneticOperations
.
DelOrder
(
current
);
if
(
neighborhoodWithStats
!=
null
)
{
recordNeighborhoodResult
(
neighborhood
,
false
);
// 使用瓶颈感知的3策略框架(策略1:换设备, 策略2:工序前移, 策略3:工序交换)
Chromosome
neighbor
=
generateNeighbor
(
current
);
if
(
neighbor
==
null
)
{
continue
;
}
}
k
++;
// 检查是否完成一轮
// 局部搜索
if
(
k
>=
neighborhoods
.
size
())
{
Chromosome
localBest
=
localSearch
(
neighbor
,
decoder
,
machines
);
noImproveRoundCount
++;
log
(
String
.
format
(
"变邻域搜索 - 本轮无改进,连续无改进轮数: %d/%d"
,
// 检查改进
noImproveRoundCount
,
maxNoImproveRounds
));
boolean
success
=
isBetter
(
localBest
,
best
);
k
=
0
;
// 重置k,开始新一轮
boolean
isSignificant
=
isSignificantImprovement
(
localBest
,
best
);
if
(
success
)
{
best
=
ProductionDeepCopyUtil
.
deepCopy
(
localBest
,
Chromosome
.
class
);
writeKpi
(
best
);
current
=
localBest
;
totalImprovements
++;
roundHadImprovement
=
true
;
if
(
isSignificant
)
{
noImproveRoundCount
=
0
;
consecutiveMinorImprovements
=
0
;
totalSignificantImprovements
++;
logVNSImprovement
(
best
,
initialFitnessLevel
,
initialFitness
,
totalRounds
,
"BottleneckStrategy"
);
log
(
String
.
format
(
"变邻域搜索 - 瓶颈策略成功(显著), 轮次=%d, 策略尝试=%d"
,
totalRounds
,
strategyAttempt
+
1
),
true
);
break
;
// 找到显著改进后跳出策略尝试,进入下一轮
}
else
{
consecutiveMinorImprovements
++;
log
(
String
.
format
(
"变邻域搜索 - 瓶颈策略成功(微小), 轮次=%d, 尝试=%d, 连续微小改进=%d"
,
totalRounds
,
strategyAttempt
+
1
,
consecutiveMinorImprovements
),
true
);
if
(
consecutiveMinorImprovements
>=
MAX_MINOR_IMPROVEMENTS
)
{
log
(
String
.
format
(
"变邻域搜索 - 提前终止:连续%d次微小改进"
,
MAX_MINOR_IMPROVEMENTS
),
true
);
break
;
}
}
}
}
continue
;
}
}
// ============= 第二阶段:简单邻域补充(备选搜索手段) =============
// 局部搜索
if
(!
roundHadImprovement
)
{
Chromosome
localBest
=
localSearch
(
neighbor
,
decoder
,
machines
);
geneticOperations
.
DelOrder
(
current
);
NeighborhoodStructure
neighborhood
=
neighborhoods
.
get
(
k
);
// 邻域移动
boolean
success
=
isBetter
(
localBest
,
best
);
// 生成邻域解(简单邻域方法)
boolean
isSignificant
=
isSignificantImprovement
(
localBest
,
best
);
Chromosome
neighbor
=
generateNeighbor
(
current
,
neighborhood
);
if
(
success
)
{
if
(
neighbor
!=
null
)
{
best
=
ProductionDeepCopyUtil
.
deepCopy
(
localBest
,
Chromosome
.
class
);
;
Chromosome
localBest
=
localSearch
(
neighbor
,
decoder
,
machines
)
;
writeKpi
(
best
);
current
=
localBest
;
boolean
success
=
isBetter
(
localBest
,
best
)
;
totalImprovements
++
;
boolean
isSignificant
=
isSignificantImprovement
(
localBest
,
best
)
;
if
(
isSignificant
)
{
if
(
success
)
{
k
=
0
;
// 重置邻域索引
best
=
ProductionDeepCopyUtil
.
deepCopy
(
localBest
,
Chromosome
.
class
);
noImproveRoundCount
=
0
;
// 只有显著改进才重置无改进计数
writeKpi
(
best
);
consecutiveMinorImprovements
=
0
;
// 重置连续微小改进计数
current
=
localBest
;
totalSignificant
Improvements
++;
total
Improvements
++;
logVNSImprovement
(
best
,
initialFitnessLevel
,
initialFitness
,
totalRounds
,
neighborhood
.
name
)
;
roundHadImprovement
=
true
;
log
(
String
.
format
(
"变邻域搜索 - 邻域成功(显著): %s"
,
neighborhood
.
name
),
true
);
}
else
{
if
(
isSignificant
)
{
// 微小改进也接受,但不重置计数,继续尝试
noImproveRoundCount
=
0
;
consecutiveMinorImprovements
++
;
consecutiveMinorImprovements
=
0
;
log
(
String
.
format
(
"变邻域搜索 - 邻域成功(微小): %s, 连续微小改进: %d/%d"
,
totalSignificantImprovements
++;
neighborhood
.
name
,
consecutiveMinorImprovements
,
MAX_MINOR_IMPROVEMENTS
),
tru
e
);
logVNSImprovement
(
best
,
initialFitnessLevel
,
initialFitness
,
totalRounds
,
neighborhood
.
nam
e
);
log
(
String
.
format
(
"变邻域搜索 - 邻域成功(显著): %s"
,
neighborhood
.
name
),
true
);
// 检查连续微小改进是否超过阈值,超过则提前终止
}
else
{
if
(
consecutiveMinorImprovements
>=
MAX_MINOR_IMPROVEMENTS
)
{
consecutiveMinorImprovements
++;
log
(
String
.
format
(
"变邻域搜索 - 提前终止:连续%d次微小改进,无显著提升"
,
MAX_MINOR_IMPROVEMENTS
),
true
);
log
(
String
.
format
(
"变邻域搜索 - 邻域成功(微小): %s"
,
neighborhood
.
name
),
true
);
break
;
}
}
}
}
}
}
else
{
k
++;
k
++;
// 尝试下一个邻域
// 检查是否完成一轮
if
(
k
>=
neighborhoods
.
size
())
{
if
(
k
>=
neighborhoods
.
size
())
{
noImproveRoundCount
++;
k
=
0
;
log
(
String
.
format
(
"变邻域搜索 - 本轮无改进,连续无改进轮数: %d/%d"
,
noImproveRoundCount
,
maxNoImproveRounds
));
k
=
0
;
// 重置k,开始新一轮
}
}
}
}
// 记录结果
// 轮次结束:若无改进则增加无改进计数
if
(
neighborhoodWithStats
!=
null
)
{
if
(!
roundHadImprovement
)
{
recordNeighborhoodResult
(
neighborhood
,
success
);
noImproveRoundCount
++;
log
(
String
.
format
(
"变邻域搜索 - 轮次%d无改进,连续无改进轮数: %d/%d"
,
totalRounds
,
noImproveRoundCount
,
maxNoImproveRounds
));
}
else
{
// 本轮有改进,重置部分计数(显著改进已在上方重置为0)
if
(
noImproveRoundCount
>
0
)
{
log
(
String
.
format
(
"变邻域搜索 - 轮次%d有改进,继续搜索"
,
totalRounds
));
}
}
}
// 检查提前结束条件
// 检查提前结束条件
if
(
noImproveRoundCount
>=
maxNoImproveRounds
)
{
if
(
noImproveRoundCount
>=
maxNoImproveRounds
)
{
log
(
String
.
format
(
"变邻域搜索 - 提前结束:连续%d轮无改进
"
,
maxNoImprove
Rounds
));
log
(
String
.
format
(
"变邻域搜索 - 提前结束:连续%d轮无改进
,总轮次=%d"
,
maxNoImproveRounds
,
total
Rounds
));
logVNSFinalSummary
(
best
,
initialFitnessLevel
,
initialFitness
,
totalRounds
,
totalImprovements
,
totalSignificantImprovements
);
logVNSFinalSummary
(
best
,
initialFitnessLevel
,
initialFitness
,
totalRounds
,
totalImprovements
,
totalSignificantImprovements
);
break
;
break
;
}
}
...
@@ -585,7 +607,7 @@ public class VariableNeighborhoodSearch {
...
@@ -585,7 +607,7 @@ public class VariableNeighborhoodSearch {
if
(
rnd
.
nextDouble
()
<
0.8
)
{
// 增加使用智能策略的概率
if
(
rnd
.
nextDouble
()
<
0.8
)
{
// 增加使用智能策略的概率
// 构建策略顺序,考虑成功率
// 构建策略顺序,考虑成功率
List
<
Integer
>
strategyOrder
=
buildStrategyOrder
();
List
<
Integer
>
strategyOrder
=
buildStrategyOrder
();
log
(
String
.
format
(
"瓶颈设备-策略顺序=%s, 关键工序数=%d"
,
strategyOrder
,
criticalOps
.
size
()));
log
(
String
.
format
(
"瓶颈设备-策略顺序=%s, 关键工序数=%d"
,
strategyOrder
,
criticalOps
.
size
())
,
true
);
Chromosome
result
=
null
;
Chromosome
result
=
null
;
...
@@ -594,28 +616,26 @@ public class VariableNeighborhoodSearch {
...
@@ -594,28 +616,26 @@ public class VariableNeighborhoodSearch {
// 如果没有多个机器选项,跳过策略1
// 如果没有多个机器选项,跳过策略1
if
(
strategyIndex
==
0
&&
!
hasMultipleMachineOptions
)
{
if
(
strategyIndex
==
0
&&
!
hasMultipleMachineOptions
)
{
log
(
String
.
format
(
"跳过%d/
4
: 策略1-无多机器选项"
,
attempt
+
1
));
log
(
String
.
format
(
"跳过%d/
3
: 策略1-无多机器选项"
,
attempt
+
1
));
continue
;
continue
;
}
}
strategyTotalCount
[
strategyIndex
]++;
strategyTotalCount
[
strategyIndex
]++;
if
(
strategyIndex
==
0
)
{
if
(
strategyIndex
==
0
)
{
log
(
String
.
format
(
"尝试%d/
4
: 策略1-关键工序换设备"
,
attempt
+
1
));
log
(
String
.
format
(
"尝试%d/
3
: 策略1-关键工序换设备"
,
attempt
+
1
));
result
=
tryChangeMachineForBottleneckOp
(
chromosome
,
criticalOps
,
MachinePositionIndex
,
positionToEntryIndex
,
positionByPriority
);
result
=
tryChangeMachineForBottleneckOp
(
chromosome
,
criticalOps
,
MachinePositionIndex
,
positionToEntryIndex
,
positionByPriority
);
}
else
if
(
strategyIndex
==
1
)
{
}
else
if
(
strategyIndex
==
1
)
{
log
(
String
.
format
(
"尝试%d/
4
: 策略2-关键工序往前移动"
,
attempt
+
1
));
log
(
String
.
format
(
"尝试%d/
3
: 策略2-关键工序往前移动"
,
attempt
+
1
));
result
=
tryMoveBottleneckOpForward
(
chromosome
,
criticalOps
,
positionIndex
,
positionByPriority
,
positionToEntryIndex
,
MachinePositionIndex
,
false
);
result
=
tryMoveBottleneckOpForward
(
chromosome
,
criticalOps
,
positionIndex
,
positionByPriority
,
positionToEntryIndex
,
MachinePositionIndex
,
false
);
}
else
if
(
strategyIndex
==
2
)
{
log
(
String
.
format
(
"尝试%d/4: 策略3-相同设备工序交换"
,
attempt
+
1
));
result
=
tryMoveBottleneckOpForward
(
chromosome
,
criticalOps
,
positionIndex
,
positionByPriority
,
positionToEntryIndex
,
MachinePositionIndex
,
true
);
}
else
{
}
else
{
log
(
String
.
format
(
"尝试%d/
4: 策略4-非瓶颈工序往后移动
"
,
attempt
+
1
));
log
(
String
.
format
(
"尝试%d/
3: 策略3-相同设备工序交换
"
,
attempt
+
1
));
result
=
tryMove
NonBottleneckOpBackward
(
chromosome
,
bottleneckMachineId
,
positionIndex
,
positionByPriority
,
positionToEntryIndex
);
result
=
tryMove
BottleneckOpForward
(
chromosome
,
criticalOps
,
positionIndex
,
positionByPriority
,
positionToEntryIndex
,
MachinePositionIndex
,
true
);
}
}
if
(
result
!=
null
)
{
if
(
result
!=
null
)
{
strategySuccessCount
[
strategyIndex
]++;
strategySuccessCount
[
strategyIndex
]++;
strategySelectFrequency
[
strategyIndex
]++;
// 记录策略被成功使用的次数
// 更新策略使用跟踪信息
// 更新策略使用跟踪信息
if
(
strategyIndex
==
lastUsedStrategy
)
{
if
(
strategyIndex
==
lastUsedStrategy
)
{
...
@@ -626,19 +646,19 @@ public class VariableNeighborhoodSearch {
...
@@ -626,19 +646,19 @@ public class VariableNeighborhoodSearch {
}
}
logStrategyStats
();
logStrategyStats
();
log
(
String
.
format
(
"策略成功,使用策略%d (连续%d次)"
,
strategyIndex
+
1
,
consecutiveSameStrategyCount
));
log
(
String
.
format
(
"策略成功,使用策略%d (连续%d次)"
,
strategyIndex
+
1
,
consecutiveSameStrategyCount
)
,
true
);
return
result
;
return
result
;
}
}
log
(
"当前策略失败,尝试下一个..."
);
log
(
"当前策略失败,尝试下一个..."
,
false
);
}
}
log
(
"所有瓶颈策略都失败,使用随机邻域"
);
log
(
"所有瓶颈策略都失败,使用随机邻域"
,
false
);
}
}
}
else
{
}
else
{
log
(
"没有找到瓶颈设备或关键工序"
);
log
(
"没有找到瓶颈设备或关键工序"
,
false
);
}
}
log
(
"使用随机邻域"
);
log
(
"使用随机邻域"
,
false
);
return
generateRandomNeighbor
(
chromosome
,
positionToEntryIndex
,
positionByPriority
);
return
generateRandomNeighbor
(
chromosome
,
positionToEntryIndex
,
positionByPriority
);
}
}
...
@@ -666,22 +686,49 @@ public class VariableNeighborhoodSearch {
...
@@ -666,22 +686,49 @@ public class VariableNeighborhoodSearch {
List
<
Map
.
Entry
<
Long
,
Double
>>
machineEntries
=
new
ArrayList
<>(
utilization
.
entrySet
());
List
<
Map
.
Entry
<
Long
,
Double
>>
machineEntries
=
new
ArrayList
<>(
utilization
.
entrySet
());
machineEntries
.
sort
((
a
,
b
)
->
Double
.
compare
(
b
.
getValue
(),
a
.
getValue
()));
machineEntries
.
sort
((
a
,
b
)
->
Double
.
compare
(
b
.
getValue
(),
a
.
getValue
()));
// 取利用率前30%的设备作为候选(确保都是高利用率的,不选冷门低负载设备)
// 第一步:筛选出真正的"瓶颈候选"设备
int
topCount
=
Math
.
max
(
3
,
Math
.
min
(
machineEntries
.
size
(),
(
int
)
Math
.
ceil
(
machineEntries
.
size
()
*
0.3
)));
// 必须满足:利用率 >= 50%(如果符合条件的少于3台,降至40%)
List
<
Map
.
Entry
<
Long
,
Double
>>
topMachines
=
machineEntries
.
subList
(
0
,
topCount
);
double
minUtilThreshold
=
0.50
;
List
<
Map
.
Entry
<
Long
,
Double
>>
bottleneckCandidates
=
new
ArrayList
<>();
for
(
Map
.
Entry
<
Long
,
Double
>
entry
:
machineEntries
)
{
if
(
entry
.
getValue
()
>=
minUtilThreshold
)
{
bottleneckCandidates
.
add
(
entry
);
}
}
// 如果候选太少,放宽到40%
if
(
bottleneckCandidates
.
size
()
<
3
)
{
bottleneckCandidates
.
clear
();
minUtilThreshold
=
0.40
;
for
(
Map
.
Entry
<
Long
,
Double
>
entry
:
machineEntries
)
{
if
(
entry
.
getValue
()
>=
minUtilThreshold
)
{
bottleneckCandidates
.
add
(
entry
);
}
}
}
// 再次兜底:如果还不够3台,直接取利用率最高的3台
if
(
bottleneckCandidates
.
size
()
<
3
)
{
bottleneckCandidates
=
new
ArrayList
<>(
machineEntries
.
subList
(
0
,
Math
.
min
(
3
,
machineEntries
.
size
())));
minUtilThreshold
=
bottleneckCandidates
.
isEmpty
()
?
0
:
bottleneckCandidates
.
get
(
bottleneckCandidates
.
size
()
-
1
).
getValue
();
}
// 第二步:取候选池中前topN(不超过总数30%,但至少3台、最多8台)
int
maxCandidates
=
Math
.
min
(
8
,
Math
.
max
(
3
,
Math
.
min
(
bottleneckCandidates
.
size
(),
(
int
)
Math
.
ceil
(
machineEntries
.
size
()
*
0.3
))));
List
<
Map
.
Entry
<
Long
,
Double
>>
topMachines
=
bottleneckCandidates
.
subList
(
0
,
Math
.
min
(
maxCandidates
,
bottleneckCandidates
.
size
()));
// 给每个候选设备计算综合评分(利用率 + 频率多样性)
// 给每个候选设备计算综合评分(利用率 + 频率多样性)
double
maxUtil
=
topMachines
.
get
(
0
).
getValue
();
double
maxUtil
=
topMachines
.
get
(
0
).
getValue
();
double
minUtil
=
topMachines
.
get
(
topMachines
.
size
()
-
1
).
getValue
();
double
minUtil
=
topMachines
.
get
(
topMachines
.
size
()
-
1
).
getValue
();
double
utilRange
=
Math
.
max
(
0.01
,
maxUtil
-
minUtil
);
double
utilRange
=
Math
.
max
(
0.01
,
maxUtil
-
minUtil
);
List
<
Object
[]>
scoredMachines
=
new
ArrayList
<>();
// 计算候选设备中的最大频率,用于归一化判断
int
maxFreq
=
1
;
int
maxFreq
=
1
;
for
(
Map
.
Entry
<
Long
,
Double
>
entry
:
topMachines
)
{
for
(
Map
.
Entry
<
Long
,
Double
>
entry
:
topMachines
)
{
int
freq
=
bottleneckMachineFrequency
.
getOrDefault
(
entry
.
getKey
(),
0
);
int
freq
=
bottleneckMachineFrequency
.
getOrDefault
(
entry
.
getKey
(),
0
);
maxFreq
=
Math
.
max
(
maxFreq
,
freq
);
maxFreq
=
Math
.
max
(
maxFreq
,
freq
);
}
}
List
<
Object
[]>
scoredMachines
=
new
ArrayList
<>();
for
(
Map
.
Entry
<
Long
,
Double
>
entry
:
topMachines
)
{
for
(
Map
.
Entry
<
Long
,
Double
>
entry
:
topMachines
)
{
Long
machineId
=
entry
.
getKey
();
Long
machineId
=
entry
.
getKey
();
double
util
=
entry
.
getValue
();
double
util
=
entry
.
getValue
();
...
@@ -689,12 +736,45 @@ public class VariableNeighborhoodSearch {
...
@@ -689,12 +736,45 @@ public class VariableNeighborhoodSearch {
// 归一化评分
// 归一化评分
double
normalizedUtil
=
(
util
-
minUtil
)
/
utilRange
;
// 0~1
double
normalizedUtil
=
(
util
-
minUtil
)
/
utilRange
;
// 0~1
double
diversityScore
=
1.0
/
(
1.0
+
freq
);
// 0次→1.0, 次数越多越低
// 综合评分:60% 利用率 + 40% 频率多样性 + 小随机扰动
// 改进的频率评分:使用更强的非线性衰减,前2次影响小,之后指数增强
double
score
=
0.6
*
normalizedUtil
+
0.4
*
diversityScore
+
0.1
*
rnd
.
nextDouble
();
// 0次→1.0, 2次→0.36, 4次→0.13, 6次→0.05
double
diversityScore
=
Math
.
pow
(
0.6
,
freq
);
// 根据利用率差距和频率动态调整权重
double
utilGapRatio
=
utilRange
;
// 0~1
double
utilizationWeight
;
double
diversityWeight
;
// 频率驱动的权重:被选次数越多,多样性权重越大
// 利用率差距驱动:差距越大,利用率权重越大
// 综合两个因素:
if
(
freq
>=
4
)
{
// 被选次数非常多 - 强制让给其他设备
utilizationWeight
=
0.30
;
diversityWeight
=
0.70
;
}
else
if
(
freq
>=
2
&&
maxFreq
>=
3
)
{
// 中高频率:显著增加多样性权重
utilizationWeight
=
0.40
;
diversityWeight
=
0.60
;
}
else
if
(
utilGapRatio
<
0.2
)
{
// 利用率差距小(20%以内):多样性更重要
utilizationWeight
=
0.35
;
diversityWeight
=
0.65
;
}
else
if
(
utilGapRatio
<
0.4
)
{
// 利用率差距中等
utilizationWeight
=
0.50
;
diversityWeight
=
0.50
;
}
else
{
// 利用率差距大,且频率低:以利用率为主
utilizationWeight
=
0.65
;
diversityWeight
=
0.35
;
}
// 综合评分:动态权重 + 小随机扰动
double
score
=
utilizationWeight
*
normalizedUtil
+
diversityWeight
*
diversityScore
+
0.1
*
rnd
.
nextDouble
();
scoredMachines
.
add
(
new
Object
[]{
machineId
,
score
,
util
,
freq
});
scoredMachines
.
add
(
new
Object
[]{
machineId
,
score
,
util
,
freq
,
utilizationWeight
,
diversityWeight
});
}
}
// 按综合评分降序排序
// 按综合评分降序排序
...
@@ -725,6 +805,28 @@ public class VariableNeighborhoodSearch {
...
@@ -725,6 +805,28 @@ public class VariableNeighborhoodSearch {
// 更新该设备被选为瓶颈设备的频率
// 更新该设备被选为瓶颈设备的频率
bottleneckMachineFrequency
.
merge
(
selectedMachineId
,
1
,
Integer:
:
sum
);
bottleneckMachineFrequency
.
merge
(
selectedMachineId
,
1
,
Integer:
:
sum
);
// === 日志:输出瓶颈设备选择决策(enableLogging=true)===
StringBuilder
bottleneckLog
=
new
StringBuilder
();
bottleneckLog
.
append
(
String
.
format
(
"[瓶颈设备选择] 选中=设备%d(利用率=%.2f%%, 历史被选次数=%d) | 利用率差距=%.2f%%, 候选%d台:"
,
selectedMachineId
,
utilization
.
getOrDefault
(
selectedMachineId
,
0.0
)
*
100
,
bottleneckMachineFrequency
.
getOrDefault
(
selectedMachineId
,
0
),
utilRange
*
100
,
scoredMachines
.
size
()));
for
(
int
i
=
0
;
i
<
Math
.
min
(
5
,
scoredMachines
.
size
());
i
++)
{
Object
[]
m
=
scoredMachines
.
get
(
i
);
Long
mid
=
(
Long
)
m
[
0
];
double
score
=
(
Double
)
m
[
1
];
double
util
=
(
Double
)
m
[
2
];
int
freq
=
(
Integer
)
m
[
3
];
double
uw
=
(
Double
)
m
[
4
];
// utilizationWeight
double
dw
=
(
Double
)
m
[
5
];
// diversityWeight
bottleneckLog
.
append
(
String
.
format
(
" 设备%d(评分=%.3f, 利用率=%.2f%%, 次数=%d, 权重=util%.2f/div%.2f)%s"
,
mid
,
score
,
util
*
100
,
freq
,
uw
,
dw
,
mid
.
equals
(
selectedMachineId
)
?
"←选中"
:
(
i
==
0
?
"←最高评分"
:
""
)));
}
log
(
bottleneckLog
.
toString
(),
true
);
Long
bottleneckMachineId
=
selectedMachineId
;
Long
bottleneckMachineId
=
selectedMachineId
;
result
.
put
(
"bottleneckMachineId"
,
bottleneckMachineId
);
result
.
put
(
"bottleneckMachineId"
,
bottleneckMachineId
);
...
@@ -1136,33 +1238,87 @@ public class VariableNeighborhoodSearch {
...
@@ -1136,33 +1238,87 @@ public class VariableNeighborhoodSearch {
}
}
/**
/**
* 构建策略顺序,基于成功率动态调整(优化版 + 强制轮换)
* 构建策略顺序,基于成功率+频率驱动的动态调整
* 目标:避免单一策略被反复使用,确保3个策略都被尝试
*/
*/
private
List
<
Integer
>
buildStrategyOrder
()
{
private
List
<
Integer
>
buildStrategyOrder
()
{
List
<
Integer
>
strategies
=
new
ArrayList
<>(
Arrays
.
asList
(
0
,
1
,
2
,
3
));
List
<
Integer
>
strategies
=
new
ArrayList
<>(
Arrays
.
asList
(
0
,
1
,
2
));
// 计算策略选择频率的最大值、总成功数
int
maxStrategyFreq
=
0
;
int
totalStrategyFreq
=
0
;
for
(
int
freq
:
strategySelectFrequency
)
{
maxStrategyFreq
=
Math
.
max
(
maxStrategyFreq
,
freq
);
totalStrategyFreq
+=
freq
;
}
// 强制策略轮换:如果某个策略被连续使用超过 MAX_CONSECUTIVE_SAME_STRATEGY 次,强制换一个
// 强制策略轮换:如果某个策略被连续使用超过 MAX_CONSECUTIVE_SAME_STRATEGY 次,强制换一个
boolean
needForceRotate
=
(
lastUsedStrategy
>=
0
&&
consecutiveSameStrategyCount
>=
MAX_CONSECUTIVE_SAME_STRATEGY
);
boolean
needForceRotate
=
(
lastUsedStrategy
>=
0
&&
consecutiveSameStrategyCount
>=
MAX_CONSECUTIVE_SAME_STRATEGY
);
if
(
needForceRotate
)
{
if
(
needForceRotate
)
{
log
(
String
.
format
(
"强制策略轮换:策略%d已连续使用%d次"
,
log
(
String
.
format
(
"强制策略轮换:策略%d已连续使用%d次"
,
lastUsedStrategy
,
consecutiveSameStrategyCount
));
lastUsedStrategy
+
1
,
consecutiveSameStrategyCount
),
true
);
}
// 计算"未被探索的策略数",用于加强探索
int
unexploredCount
=
0
;
for
(
int
i
=
0
;
i
<
3
;
i
++)
{
if
(
strategyTotalCount
[
i
]
==
0
)
unexploredCount
++;
}
}
//
先为每个策略计算固定的评分(包含随机扰动),避免在Comparator中使用随机数
//
为每个策略计算评分
final
double
[]
scores
=
new
double
[
4
];
final
double
[]
scores
=
new
double
[
3
];
for
(
int
strategy
:
strategies
)
{
for
(
int
strategy
:
strategies
)
{
double
s
cor
e
=
strategyTotalCount
[
strategy
]
>
0
?
double
s
uccessRat
e
=
strategyTotalCount
[
strategy
]
>
0
?
(
double
)
strategySuccessCount
[
strategy
]
/
strategyTotalCount
[
strategy
]
:
INITIAL_SUCCESS_RATE
;
(
double
)
strategySuccessCount
[
strategy
]
/
strategyTotalCount
[
strategy
]
:
INITIAL_SUCCESS_RATE
;
// 频率多样性评分:被成功选中次数越少,分越高
int
freq
=
strategySelectFrequency
[
strategy
];
// 更强的频率衰减:0次→1.0, 2次→0.36, 4次→0.13, 6次→0.05
double
freqDiversity
=
Math
.
pow
(
0.6
,
freq
);
// 动态权重调整:
// - 如果有策略被成功使用 ≥ 2次,就开始加强探索
// - 如果有未探索的策略,给它们更大的激励
double
successWeight
;
double
diversityWeight
;
if
(
totalStrategyFreq
>=
8
)
{
// 后期:平衡为主,避免搜索空间塌陷
successWeight
=
0.40
;
diversityWeight
=
0.60
;
}
else
if
(
totalStrategyFreq
>=
4
)
{
// 中期:稍微偏重多样性
successWeight
=
0.45
;
diversityWeight
=
0.55
;
}
else
if
(
maxStrategyFreq
>=
2
)
{
// 有策略被用了2次以上:开始引入平衡
successWeight
=
0.50
;
diversityWeight
=
0.50
;
}
else
{
// 前期:以成功率为主,建立基线
successWeight
=
0.70
;
diversityWeight
=
0.30
;
}
double
score
=
successWeight
*
successRate
+
diversityWeight
*
freqDiversity
;
// "未探索策略"额外加分:从未被尝试过的策略大幅提升
if
(
strategyTotalCount
[
strategy
]
==
0
)
{
score
+=
0.40
;
// 大幅激励从未被尝试的策略
}
else
if
(
strategySelectFrequency
[
strategy
]
==
0
&&
strategyTotalCount
[
strategy
]
<
3
)
{
// 被尝试过但从未成功的策略,给中等激励
score
+=
0.15
;
}
// 强制轮换:给最后使用的策略降权
// 强制轮换:给最后使用的策略降权
if
(
needForceRotate
&&
strategy
==
lastUsedStrategy
)
{
if
(
needForceRotate
&&
strategy
==
lastUsedStrategy
)
{
score
-=
FORCE_ROTATE_PENALTY
;
// 大幅降权
score
-=
FORCE_ROTATE_PENALTY
;
}
}
// 给使用次数少的策略加分
,鼓励探索
// 给使用次数少的策略加分
if
(
strategyTotalCount
[
strategy
]
<
MIN_USE_COUNT_FOR_EXPLORATION_BONUS
)
{
if
(
strategyTotalCount
[
strategy
]
<
MIN_USE_COUNT_FOR_EXPLORATION_BONUS
)
{
score
+=
EXPLORATION_BONUS_FOR_LOW_USE
;
score
+=
EXPLORATION_BONUS_FOR_LOW_USE
*
0.5
;
// 减半,避免与上面的未探索重复
}
}
// 如果策略被尝试次数很多但成功率很低,给它额外加分鼓励探索
// 如果策略被尝试次数很多但成功率很低,给它额外加分鼓励探索
...
@@ -1170,30 +1326,37 @@ public class VariableNeighborhoodSearch {
...
@@ -1170,30 +1326,37 @@ public class VariableNeighborhoodSearch {
score
+=
EXPLORATION_BONUS_FOR_LOW_SUCCESS
;
score
+=
EXPLORATION_BONUS_FOR_LOW_SUCCESS
;
}
}
// 加入少量随机扰动,避免陷入局部最优
// ========== 新增: 策略1(换设备)额外奖励 ==========
// 换设备是最可能产生"大改动"的策略类型,提高其被选择概率
if
(
strategy
==
0
)
{
score
+=
CHANGE_MACHINE_BONUS
;
}
// 加入随机扰动
score
+=
rnd
.
nextDouble
()
*
RANDOM_NOISE_FACTOR
;
score
+=
rnd
.
nextDouble
()
*
RANDOM_NOISE_FACTOR
;
scores
[
strategy
]
=
score
;
scores
[
strategy
]
=
score
;
}
}
// 按评分排序
(Comparator只使用预计算的固定值)
// 按评分排序
strategies
.
sort
((
a
,
b
)
->
Double
.
compare
(
scores
[
b
],
scores
[
a
]));
strategies
.
sort
((
a
,
b
)
->
Double
.
compare
(
scores
[
b
],
scores
[
a
]));
return
strategies
;
return
strategies
;
}
}
/**
/**
* 记录策略统计信息
* 记录策略统计信息
(包括频率信息)
*/
*/
private
void
logStrategyStats
()
{
private
void
logStrategyStats
()
{
StringBuilder
sb
=
new
StringBuilder
(
"策略统计: "
);
StringBuilder
sb
=
new
StringBuilder
(
"策略统计: "
);
for
(
int
i
=
0
;
i
<
4
;
i
++)
{
for
(
int
i
=
0
;
i
<
3
;
i
++)
{
double
rate
=
strategyTotalCount
[
i
]
>
0
?
double
rate
=
strategyTotalCount
[
i
]
>
0
?
(
double
)
strategySuccessCount
[
i
]
/
strategyTotalCount
[
i
]
:
0.0
;
(
double
)
strategySuccessCount
[
i
]
/
strategyTotalCount
[
i
]
:
0.0
;
sb
.
append
(
String
.
format
(
"[%d]%d/%d(%.1f%%) "
,
i
+
1
,
sb
.
append
(
String
.
format
(
"[%d]%d/%d(%.1f%%)被选%d次 "
,
i
+
1
,
strategySuccessCount
[
i
],
strategyTotalCount
[
i
],
rate
*
100
));
strategySuccessCount
[
i
],
strategyTotalCount
[
i
],
rate
*
100
,
strategySelectFrequency
[
i
]));
}
}
log
(
sb
.
toString
());
log
(
sb
.
toString
()
,
true
);
}
}
/**
/**
* 识别瓶颈设备(利用率最高的设备)
* 识别瓶颈设备(利用率最高的设备)
...
@@ -1313,12 +1476,16 @@ public class VariableNeighborhoodSearch {
...
@@ -1313,12 +1476,16 @@ public class VariableNeighborhoodSearch {
int
groupCount
=
priorityToGroupIds
.
getOrDefault
(
entry
.
getPriority
(),
new
HashSet
<>()).
size
();
int
groupCount
=
priorityToGroupIds
.
getOrDefault
(
entry
.
getPriority
(),
new
HashSet
<>()).
size
();
double
priorityScore
=
(
double
)
groupCount
/
maxGroupCount
;
double
priorityScore
=
(
double
)
groupCount
/
maxGroupCount
;
//
频率多样性评分(0次→1.0, 次数越多越低)
//
改进的频率多样性评分:非线性衰减,避免同一工序被反复选中
int
freq
=
bottleneckOpFrequency
.
getOrDefault
(
op
.
getOperationId
(),
0
);
int
freq
=
bottleneckOpFrequency
.
getOrDefault
(
op
.
getOperationId
(),
0
);
double
diversityScore
=
1.0
/
(
1.0
+
freq
);
double
diversityScore
=
Math
.
pow
(
0.6
,
freq
);
// 0次→1.0, 3次→0.22, 6次→0.05
// 根据整体频率动态调整权重
double
priorityWeight
=
maxOpFreq
>=
3
?
0.45
:
0.55
;
double
diversityWeight
=
maxOpFreq
>=
3
?
0.55
:
0.45
;
// 综合评分:
50% 优先级 + 40% 频率多样性 + 10%
随机
// 综合评分:
动态权重 + 小
随机
double
score
=
0.5
*
priorityScore
+
0.4
*
diversityScore
+
0.1
*
rnd
.
nextDouble
();
double
score
=
priorityWeight
*
priorityScore
+
diversityWeight
*
diversityScore
+
0.1
*
rnd
.
nextDouble
();
scoredOps
.
add
(
new
Object
[]{
op
,
score
,
groupCount
,
freq
});
scoredOps
.
add
(
new
Object
[]{
op
,
score
,
groupCount
,
freq
});
}
}
...
@@ -1342,6 +1509,27 @@ public class VariableNeighborhoodSearch {
...
@@ -1342,6 +1509,27 @@ public class VariableNeighborhoodSearch {
// 更新该工序被选为优化目标的频率
// 更新该工序被选为优化目标的频率
bottleneckOpFrequency
.
merge
(
selectedOp
.
getOperationId
(),
1
,
Integer:
:
sum
);
bottleneckOpFrequency
.
merge
(
selectedOp
.
getOperationId
(),
1
,
Integer:
:
sum
);
// === 日志:输出换设备工序选择决策 ===
Entry
selEntry
=
entrybyids
.
get
(
selectedOp
.
getOperationId
());
StringBuilder
opChangeLog
=
new
StringBuilder
();
if
(
selEntry
!=
null
)
{
int
opFreq
=
bottleneckOpFrequency
.
getOrDefault
(
selectedOp
.
getOperationId
(),
0
);
opChangeLog
.
append
(
String
.
format
(
"[换设备-工序选择] 选中=工序%d(组=%d_序号=%d, 优先级=%.2f, 被选次数=%d) | top%d候选:"
,
selectedOp
.
getOperationId
(),
selEntry
.
getGroupId
(),
selEntry
.
getSequence
(),
selEntry
.
getPriority
(),
opFreq
,
scoredOps
.
size
()));
for
(
int
i
=
0
;
i
<
Math
.
min
(
5
,
scoredOps
.
size
());
i
++)
{
Object
[]
item
=
scoredOps
.
get
(
i
);
GAScheduleResult
opItem
=
(
GAScheduleResult
)
item
[
0
];
double
sc
=
(
Double
)
item
[
1
];
int
gc
=
(
Integer
)
item
[
2
];
int
fr
=
(
Integer
)
item
[
3
];
opChangeLog
.
append
(
String
.
format
(
" 工序%d(评分=%.3f, 组订单数=%d, 次数=%d)%s"
,
opItem
.
getOperationId
(),
sc
,
gc
,
fr
,
opItem
.
getOperationId
()
==
selectedOp
.
getOperationId
()
?
"←选中"
:
""
));
}
log
(
opChangeLog
.
toString
(),
true
);
}
int
maPos
=
-
1
;
int
maPos
=
-
1
;
Entry
entry
=
entrybyids
.
get
(
selectedOp
.
getOperationId
());
Entry
entry
=
entrybyids
.
get
(
selectedOp
.
getOperationId
());
...
@@ -1515,6 +1703,13 @@ public class VariableNeighborhoodSearch {
...
@@ -1515,6 +1703,13 @@ public class VariableNeighborhoodSearch {
}
}
}
}
// 计算候选工序中的最大频率
int
maxOpFreqMove
=
1
;
for
(
GAScheduleResult
op
:
bottleneckOps
)
{
int
freq
=
bottleneckOpFrequency
.
getOrDefault
(
op
.
getOperationId
(),
0
);
maxOpFreqMove
=
Math
.
max
(
maxOpFreqMove
,
freq
);
}
// 给每个候选工序计算综合评分
// 给每个候选工序计算综合评分
List
<
Object
[]>
scoredOps
=
new
ArrayList
<>();
List
<
Object
[]>
scoredOps
=
new
ArrayList
<>();
for
(
GAScheduleResult
op
:
bottleneckOps
)
{
for
(
GAScheduleResult
op
:
bottleneckOps
)
{
...
@@ -1533,12 +1728,17 @@ public class VariableNeighborhoodSearch {
...
@@ -1533,12 +1728,17 @@ public class VariableNeighborhoodSearch {
}
}
double
timeScore
=
maxEndTime
>
0
?
(
double
)
endTime
/
maxEndTime
:
0.5
;
double
timeScore
=
maxEndTime
>
0
?
(
double
)
endTime
/
maxEndTime
:
0.5
;
//
频率多样性评分(0次→1.0, 次数越多越低)
//
改进的频率多样性评分:非线性衰减,避免同一工序被反复选中
int
freq
=
bottleneckOpFrequency
.
getOrDefault
(
op
.
getOperationId
(),
0
);
int
freq
=
bottleneckOpFrequency
.
getOrDefault
(
op
.
getOperationId
(),
0
);
double
diversityScore
=
1.0
/
(
1.0
+
freq
);
double
diversityScore
=
Math
.
pow
(
0.6
,
freq
);
// 0次→1.0, 3次→0.22, 6次→0.05
// 综合评分:35% 优先级 + 25% 时间 + 40% 频率多样性 + 小随机
// 根据整体频率动态调整权重:频率越高,多样性权重越大
double
score
=
0.35
*
priorityScore
+
0.25
*
timeScore
+
0.4
*
diversityScore
+
0.1
*
rnd
.
nextDouble
();
double
priorityWeight
=
maxOpFreqMove
>=
3
?
0.3
:
0.35
;
double
timeWeight
=
maxOpFreqMove
>=
3
?
0.2
:
0.25
;
double
diversityWeight
=
maxOpFreqMove
>=
3
?
0.5
:
0.4
;
// 综合评分:动态权重 + 小随机
double
score
=
priorityWeight
*
priorityScore
+
timeWeight
*
timeScore
+
diversityWeight
*
diversityScore
+
0.1
*
rnd
.
nextDouble
();
scoredOps
.
add
(
new
Object
[]{
op
,
score
,
freq
});
scoredOps
.
add
(
new
Object
[]{
op
,
score
,
freq
});
}
}
...
@@ -1563,9 +1763,30 @@ public class VariableNeighborhoodSearch {
...
@@ -1563,9 +1763,30 @@ public class VariableNeighborhoodSearch {
// 更新该工序被选为优化目标的频率
// 更新该工序被选为优化目标的频率
bottleneckOpFrequency
.
merge
(
selectedOp
.
getOperationId
(),
1
,
Integer:
:
sum
);
bottleneckOpFrequency
.
merge
(
selectedOp
.
getOperationId
(),
1
,
Integer:
:
sum
);
// === 日志:输出移动工序选择决策 ===
Entry
moveEntry
=
entrybyids
.
get
(
selectedOp
.
getOperationId
());
StringBuilder
moveLog
=
new
StringBuilder
();
if
(
moveEntry
!=
null
)
{
int
opFreq
=
bottleneckOpFrequency
.
getOrDefault
(
selectedOp
.
getOperationId
(),
0
);
int
endTime
=
orderCompletionTimes
.
getOrDefault
(
selectedOp
.
getGroupId
(),
0
);
moveLog
.
append
(
String
.
format
(
"[移动工序选择] 选中=工序%d(组=%d_序号=%d, 优先级=%.2f, 完成时间=%d, 被选次数=%d) | top%d候选:"
,
selectedOp
.
getOperationId
(),
moveEntry
.
getGroupId
(),
moveEntry
.
getSequence
(),
moveEntry
.
getPriority
(),
endTime
,
opFreq
,
scoredOps
.
size
()));
for
(
int
i
=
0
;
i
<
Math
.
min
(
5
,
scoredOps
.
size
());
i
++)
{
Object
[]
item
=
scoredOps
.
get
(
i
);
GAScheduleResult
opItem
=
(
GAScheduleResult
)
item
[
0
];
double
sc
=
(
Double
)
item
[
1
];
int
fr
=
(
Integer
)
item
[
2
];
moveLog
.
append
(
String
.
format
(
" 工序%d(评分=%.3f, 次数=%d)%s"
,
opItem
.
getOperationId
(),
sc
,
fr
,
opItem
.
getOperationId
()
==
selectedOp
.
getOperationId
()
?
"←选中"
:
""
));
}
log
(
moveLog
.
toString
(),
true
);
}
Entry
entry
=
entrybyids
.
get
(
selectedOp
.
getOperationId
());
Entry
entry
=
entrybyids
.
get
(
selectedOp
.
getOperationId
());
if
(
entry
==
null
)
{
if
(
entry
==
null
)
{
log
(
"tryMoveBottleneckOpForward: 找不到工序"
);
log
(
"tryMoveBottleneckOpForward: 找不到工序"
,
false
);
return
null
;
return
null
;
}
}
...
@@ -2087,53 +2308,75 @@ public class VariableNeighborhoodSearch {
...
@@ -2087,53 +2308,75 @@ public class VariableNeighborhoodSearch {
}
}
private
Chromosome
generateSameMachineSwapNeighbor
(
Chromosome
chromosome
,
int
idx1
,
Map
<
Integer
,
Entry
>
positionIndex
,
Map
<
String
,
Integer
>
machinePositionIndex
)
{
private
Chromosome
generateSameMachineSwapNeighbor
(
Chromosome
chromosome
,
int
idx1
,
Map
<
Integer
,
Entry
>
positionIndex
,
Map
<
String
,
Integer
>
machinePositionIndex
)
{
//有问题
log
(
"generateSameMachineSwapNeighbor"
);
log
(
"generateSameMachineSwapNeighbor"
+
positionIndex
.
size
());
Chromosome
neighbor
=
copyChromosome
(
chromosome
);
Chromosome
neighbor
=
copyChromosome
(
chromosome
);
// ProductionDeepCopyUtil.deepCopy(chromosome, Chromosome.class);
CopyOnWriteArrayList
<
Integer
>
os
=
neighbor
.
getOperationSequencing
();
CopyOnWriteArrayList
<
Integer
>
os
=
neighbor
.
getOperationSequencing
();
CopyOnWriteArrayList
<
Integer
>
ms
=
neighbor
.
getMachineSelection
();
CopyOnWriteArrayList
<
Integer
>
ms
=
neighbor
.
getMachineSelection
();
if
(
os
.
size
()
>=
2
&&
ms
.
size
()
>=
2
)
{
if
(
os
.
size
()
<
2
||
ms
.
size
()
<
2
)
{
return
neighbor
;
}
List
<
Integer
>
positions
=
new
ArrayList
<>();
// ========== 修复1: op1 必须非 null ==========
Entry
op1
=
positionIndex
.
get
(
idx1
);
Entry
op1
=
positionIndex
.
get
(
idx1
);
int
maPos1
=
machinePositionIndex
.
get
(
op1
.
getGroupId
()
+
"_"
+
op1
.
getSequence
());
if
(
op1
==
null
)
{
int
machineSeq1
=
ms
.
get
(
maPos1
);
return
neighbor
;
}
MachineOption
machineOption1
=
op1
.
getMachineOptions
().
get
(
machineSeq1
-
1
);
// ========== 修复2: machinePositionIndex 查找必须非 null 且有效 ==========
Long
machineId1
=
machineOption1
.
getMachineId
();
String
op1Key
=
op1
.
getGroupId
()
+
"_"
+
op1
.
getSequence
();
for
(
int
i
=
0
;
i
<
os
.
size
();
i
++)
{
Integer
maPos1
=
machinePositionIndex
.
get
(
op1Key
);
Entry
op
=
positionIndex
.
get
(
i
);
if
(
maPos1
==
null
||
maPos1
<
0
||
maPos1
>=
ms
.
size
())
{
if
(
op
==
null
)
continue
;
return
neighbor
;
}
int
maPos
=
machinePositionIndex
.
get
(
op
.
getGroupId
()
+
"_"
+
op
.
getSequence
());
int
machineSeq1
=
ms
.
get
(
maPos1
);
if
(
maPos
<
0
)
continue
;
if
(
machineSeq1
<
1
||
machineSeq1
>
op1
.
getMachineOptions
().
size
())
{
return
neighbor
;
}
int
machineSeq
=
ms
.
get
(
maPos
);
MachineOption
machineOption1
=
op1
.
getMachineOptions
().
get
(
machineSeq1
-
1
);
if
(
machineSeq
<
1
||
machineSeq
>
op
.
getMachineOptions
().
size
())
continue
;
Long
machineId1
=
machineOption1
.
getMachineId
()
;
MachineOption
machineOption
=
op
.
getMachineOptions
().
get
(
machineSeq
-
1
);
// ========== 修复3: 收集 os 位置(i)而非 ms 位置(maPos)==========
Long
machineId
=
machineOption
.
getMachineId
();
List
<
Integer
>
sameMachineOsPositions
=
new
ArrayList
<>();
if
(
machineId
==
null
)
continue
;
if
(
machineId
.
equals
(
machineId1
)&&
Math
.
abs
(
op1
.
getPriority
()
-
op
.
getPriority
())==
0
&&
op1
.
getGroupId
()!=
op
.
getGroupId
())
{
for
(
int
i
=
0
;
i
<
os
.
size
();
i
++)
{
positions
.
add
(
maPos
);
if
(
i
==
idx1
)
continue
;
}
}
Entry
op
=
positionIndex
.
get
(
i
);
if
(
op
==
null
)
continue
;
String
opKey
=
op
.
getGroupId
()
+
"_"
+
op
.
getSequence
();
Integer
maPos
=
machinePositionIndex
.
get
(
opKey
);
if
(
maPos
==
null
||
maPos
<
0
||
maPos
>=
ms
.
size
())
continue
;
int
machineSeq
=
ms
.
get
(
maPos
);
if
(
machineSeq
<
1
||
machineSeq
>
op
.
getMachineOptions
().
size
())
continue
;
if
(
positions
.
isEmpty
()||
positions
.
size
()
<=
1
)
return
neighbor
;
MachineOption
machineOption
=
op
.
getMachineOptions
().
get
(
machineSeq
-
1
);
int
idx2
=
positions
.
get
(
rnd
.
nextInt
(
positions
.
size
()));
Long
machineId
=
machineOption
.
getMachineId
();
if
(
machineId
==
null
)
continue
;
if
(
idx1
>
os
.
size
()||
idx2
>
os
.
size
())
if
(
machineId
.
equals
(
machineId1
)
&&
op1
.
getPriority
()
==
op
.
getPriority
()
&&
op1
.
getGroupId
()
!=
op
.
getGroupId
())
{
{
sameMachineOsPositions
.
add
(
i
);
// 关键: 存储 os 位置 i,不是 ms 位置 maPos
int
i
=
0
;
}
}
}
if
(
sameMachineOsPositions
.
isEmpty
())
{
return
neighbor
;
}
Collections
.
swap
(
os
,
idx1
,
idx2
);
// ========== 修复4: 确保 idx2 在 os 有效范围内 ==========
neighbor
.
setOperationSequencing
(
os
);
int
idx2
=
sameMachineOsPositions
.
get
(
rnd
.
nextInt
(
sameMachineOsPositions
.
size
()));
if
(
idx2
<
0
||
idx2
>=
os
.
size
()
||
idx1
<
0
||
idx1
>=
os
.
size
())
{
return
neighbor
;
}
}
// ========== 修复5: swap 使用两个 os 位置,完全一致 ==========
Collections
.
swap
(
os
,
idx1
,
idx2
);
neighbor
.
setOperationSequencing
(
os
);
return
neighbor
;
return
neighbor
;
}
}
...
@@ -2490,10 +2733,24 @@ public class VariableNeighborhoodSearch {
...
@@ -2490,10 +2733,24 @@ public class VariableNeighborhoodSearch {
* - 然后使用加权轮盘赌选择,得分最高的设备
* - 然后使用加权轮盘赌选择,得分最高的设备
*/
*/
private
int
selectMachineByLoad
(
List
<
Integer
>
availableMachineSeqs
,
List
<
MachineOption
>
machineOptions
,
Chromosome
chromosome
,
String
opKey
)
{
private
int
selectMachineByLoad
(
List
<
Integer
>
availableMachineSeqs
,
List
<
MachineOption
>
machineOptions
,
Chromosome
chromosome
,
String
opKey
)
{
// 计算当前各设备的负载(利用率)
// 计算当前各设备的负载(利用率)— 只计算有限产能设备(false)
Map
<
Long
,
Double
>
machineUtilization
=
calculateMachineUtilization
(
chromosome
,
true
);
// 这样利用率计算更准确,不会被无限产能机器的工序"稀释"
Map
<
Long
,
Double
>
machineUtilization
=
calculateMachineUtilization
(
chromosome
,
false
);
// 预计算无限产能机器ID集合(这些设备可以作为目标,因为不受时间约束)
Set
<
Long
>
infiniteMachineIds
=
new
HashSet
<>();
List
<
GAScheduleResult
>
allResults
=
chromosome
.
getResult
();
if
(
allResults
!=
null
)
{
for
(
GAScheduleResult
r
:
allResults
)
{
if
(
"Infinite"
.
equals
(
r
.
getCapacityTypeName
()))
{
infiniteMachineIds
.
add
(
r
.
getMachineId
());
}
}
}
// 构建可用机器列表,计算综合评分
// 构建候选机器列表:包含有限产能和无限产能机器
// - 有限产能机器:用真实利用率评分
// - 无限产能机器:视为利用率=0(永远空闲),给予高评分
List
<
MachineOptionWithScore
>
availableMachines
=
new
ArrayList
<>();
List
<
MachineOptionWithScore
>
availableMachines
=
new
ArrayList
<>();
for
(
int
seq
:
availableMachineSeqs
)
{
for
(
int
seq
:
availableMachineSeqs
)
{
...
@@ -2502,7 +2759,17 @@ public class VariableNeighborhoodSearch {
...
@@ -2502,7 +2759,17 @@ public class VariableNeighborhoodSearch {
}
}
MachineOption
option
=
machineOptions
.
get
(
seq
-
1
);
MachineOption
option
=
machineOptions
.
get
(
seq
-
1
);
Long
machineId
=
option
.
getMachineId
();
Long
machineId
=
option
.
getMachineId
();
double
utilization
=
machineUtilization
.
getOrDefault
(
machineId
,
0.0
);
double
utilization
;
boolean
isInfiniteMachine
=
infiniteMachineIds
.
contains
(
machineId
);
if
(
isInfiniteMachine
)
{
// 无限产能机器:视为 utilization=0,因为它不受时间约束
utilization
=
0.0
;
}
else
{
// 有限产能机器:用真实利用率
utilization
=
machineUtilization
.
getOrDefault
(
machineId
,
0.0
);
}
// 获取该工序对该设备的选择次数
// 获取该工序对该设备的选择次数
int
frequency
=
0
;
int
frequency
=
0
;
...
@@ -2513,7 +2780,8 @@ public class VariableNeighborhoodSearch {
...
@@ -2513,7 +2780,8 @@ public class VariableNeighborhoodSearch {
// 归一化多样性分:选择次数越少,分越高(0~1范围
// 归一化多样性分:选择次数越少,分越高(0~1范围
double
diversity
=
1.0
/
(
1.0
+
frequency
);
// 0次→1, 1次→0.5, 2次→0.33...
double
diversity
=
1.0
/
(
1.0
+
frequency
);
// 0次→1, 1次→0.5, 2次→0.33...
// 综合评分
// 综合评分:优先选负载低(1-utilization)且历史选择次数少的设备
// 对于无限产能机器,因为 utilization=0,loadBalanceScore=1.0,会得到较高评分
double
loadBalanceScore
=
(
1.0
-
utilization
);
double
loadBalanceScore
=
(
1.0
-
utilization
);
double
noise
=
rnd
.
nextDouble
()
*
RANDOM_NOISE_FOR_MACHINE
;
double
noise
=
rnd
.
nextDouble
()
*
RANDOM_NOISE_FOR_MACHINE
;
double
score
=
LOAD_BALANCE_WEIGHT
*
loadBalanceScore
double
score
=
LOAD_BALANCE_WEIGHT
*
loadBalanceScore
...
@@ -2525,16 +2793,18 @@ public class VariableNeighborhoodSearch {
...
@@ -2525,16 +2793,18 @@ public class VariableNeighborhoodSearch {
availableMachines
.
add
(
machineWithScore
);
availableMachines
.
add
(
machineWithScore
);
}
}
if
(
availableMachines
.
size
()
==
0
)
{
// 如果没有可用机器,返回第一个选项(兜底)
return
1
;
if
(
availableMachines
.
isEmpty
())
{
return
availableMachineSeqs
.
get
(
0
);
}
}
// 按综合评分降序排序
// 按综合评分降序排序
availableMachines
.
sort
((
m1
,
m2
)
->
Double
.
compare
(
m2
.
score
,
m1
.
score
));
availableMachines
.
sort
((
m1
,
m2
)
->
Double
.
compare
(
m2
.
score
,
m1
.
score
));
// 使用加权轮盘赌选择(70%概率选择得分最高的,30%按评分加权随机
// 先计算选中的机器,再输出日志,最后返回
int
selectedMachineSeq
;
if
(
rnd
.
nextDouble
()
<
0.7
)
{
if
(
rnd
.
nextDouble
()
<
0.7
)
{
return
availableMachines
.
get
(
0
).
seq
;
selectedMachineSeq
=
availableMachines
.
get
(
0
).
seq
;
}
else
{
}
else
{
// 按评分加权随机选择
// 按评分加权随机选择
double
totalScore
=
0
;
double
totalScore
=
0
;
...
@@ -2543,14 +2813,38 @@ public class VariableNeighborhoodSearch {
...
@@ -2543,14 +2813,38 @@ public class VariableNeighborhoodSearch {
}
}
double
r
=
rnd
.
nextDouble
()
*
totalScore
;
double
r
=
rnd
.
nextDouble
()
*
totalScore
;
double
cumSum
=
0
;
double
cumSum
=
0
;
selectedMachineSeq
=
availableMachines
.
get
(
availableMachines
.
size
()
-
1
).
seq
;
for
(
MachineOptionWithScore
m
:
availableMachines
)
{
for
(
MachineOptionWithScore
m
:
availableMachines
)
{
cumSum
+=
Math
.
max
(
m
.
score
,
0.001
);
cumSum
+=
Math
.
max
(
m
.
score
,
0.001
);
if
(
r
<=
cumSum
)
{
if
(
r
<=
cumSum
)
{
return
m
.
seq
;
selectedMachineSeq
=
m
.
seq
;
break
;
}
}
}
}
return
availableMachines
.
get
(
availableMachines
.
size
()
-
1
).
seq
;
}
}
// === 日志:输出设备分配决策 ===
StringBuilder
machineLog
=
new
StringBuilder
();
Long
selectedId
=
machineOptions
.
get
(
selectedMachineSeq
-
1
).
getMachineId
();
boolean
selectedIsInfinite
=
infiniteMachineIds
.
contains
(
selectedId
);
machineLog
.
append
(
String
.
format
(
"[设备分配] 工序=%s, 选中=设备%d(%s, 利用率=%.2f%%, 历史次数=%d) | 候选%d台:"
,
opKey
!=
null
?
opKey
:
"未知"
,
selectedId
,
selectedIsInfinite
?
"无限产能"
:
"有限产能"
,
machineUtilization
.
getOrDefault
(
selectedId
,
0.0
)
*
100
,
opKey
!=
null
?
opMachineSelectFrequency
.
getOrDefault
(
opKey
,
new
HashMap
<>()).
getOrDefault
(
selectedId
,
0
)
:
0
,
availableMachines
.
size
()));
// 找到选中设备的准确评分
for
(
MachineOptionWithScore
m
:
availableMachines
)
{
String
machineType
=
infiniteMachineIds
.
contains
(
m
.
option
.
getMachineId
())
?
"无限"
:
"有限"
;
machineLog
.
append
(
String
.
format
(
" 设备%d(%s产能, 评分=%.3f, 利用率=%.2f%%, 次数=%d)%s"
,
m
.
option
.
getMachineId
(),
machineType
,
m
.
score
,
m
.
utilization
*
100
,
m
.
frequency
,
m
.
seq
==
selectedMachineSeq
?
"←选中"
:
""
));
}
log
(
machineLog
.
toString
(),
false
);
return
selectedMachineSeq
;
}
}
/**
/**
...
...
src/main/java/com/aps/service/plan/PlanResultService.java
View file @
8e292cd5
...
@@ -297,8 +297,7 @@ public class PlanResultService {
...
@@ -297,8 +297,7 @@ public class PlanResultService {
if
(!
saved
)
{
if
(!
saved
)
{
throw
new
BusinessException
(
"排产计算结果保存失败,请稍后重试或联系管理员"
);
throw
new
BusinessException
(
"排产计算结果保存失败,请稍后重试或联系管理员"
);
}
}
//WriteScheduleSummary(chromosome);
WriteScheduleSummary
(
chromosome
);
return
chromosome
;
return
chromosome
;
...
@@ -2062,7 +2061,7 @@ public class PlanResultService {
...
@@ -2062,7 +2061,7 @@ public class PlanResultService {
public
void
WriteScheduleSummary
(
Chromosome
schedule
)
{
public
void
WriteScheduleSummary
(
Chromosome
schedule
)
{
WriteOperationIntegrityLog
(
schedule
);
//
WriteOperationIntegrityLog(schedule);
// 写入日志
// 写入日志
FileHelper
.
writeLogFile
(
"\n=== Schedule Summary === "
);
FileHelper
.
writeLogFile
(
"\n=== Schedule Summary === "
);
FileHelper
.
writeLogFile
(
String
.
format
(
"ID: %s"
,
schedule
.
getID
()));
FileHelper
.
writeLogFile
(
String
.
format
(
"ID: %s"
,
schedule
.
getID
()));
...
...
src/test/java/com/aps/demo/PlanResultServiceTest.java
View file @
8e292cd5
...
@@ -43,7 +43,9 @@ public class PlanResultServiceTest {
...
@@ -43,7 +43,9 @@ public class PlanResultServiceTest {
// planResultService.execute2("64E64F6B68094AF38CEDC418630C3CC2");//2000
// planResultService.execute2("64E64F6B68094AF38CEDC418630C3CC2");//2000
// planResultService.execute2("E1448B3C9C8743DEAB39708F2CFE348A");//倒排bomces
// planResultService.execute2("E1448B3C9C8743DEAB39708F2CFE348A");//倒排bomces
planResultService
.
execute2
(
"197083D0D26A449EB179AC103C753FD3"
);
// planResultService.execute2("197083D0D26A449EB179AC103C753FD3");
planResultService
.
execute2
(
"F8F147BD627C47B1A190399DD7A697F6"
);
// planResultService.execute2("9FEDFD92BB6A4675BF9B1CC64505D1AB");
// planResultService.execute2("9FEDFD92BB6A4675BF9B1CC64505D1AB");
// planResultService.execute2("15210B13B88A453F8B84AAC7F16C7541");//2000
// planResultService.execute2("15210B13B88A453F8B84AAC7F16C7541");//2000
...
...
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