refactor JIT backward changeover scheduling

parent b1b2a85e
......@@ -18,6 +18,8 @@ public class ScheduleResultDetail {
private double Quantity; // 时间段
private double efficiency=1;//效率
private List<TimeSegment> usedSegment;
// true 表示换型段,false 表示加工段。
private boolean setup;
// Key 的 getter/setter
......
package com.aps.service.Algorithm;
import com.aps.common.util.FileHelper;
import com.aps.common.util.SpringContextUtil;
import com.aps.entity.Algorithm.GAScheduleResult;
import com.aps.entity.Algorithm.ScheduleResultDetail;
import com.aps.entity.basic.Entry;
import com.aps.entity.basic.GlobalParam;
import com.aps.entity.basic.Machine;
import com.aps.entity.basic.SegmentType;
import com.aps.entity.basic.TimeSegment;
import com.aps.service.DiscreteParameterDurationService;
import com.aps.service.DiscreteParameterMatrixService;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
/**
* 倒排换型辅助类:集中处理 JIT 倒排时绿色/蓝色/黄色之间的换型计算、窗口校验和黄色旧换型替换。
*/
class BackwardChangeoverHelper {
private static final String DEBUG_BACKWARD_SETUP_TIME_SECONDS_PROPERTY = "aps.debug.backwardSetupTimeSeconds";
private static final String DEBUG_BACKWARD_CHANGEOVER_VERBOSE_PROPERTY = "aps.debug.backwardChangeoverVerbose";
private final GlobalParam globalParam;
private final LocalDateTime baseTime;
private final MachineCalculator machineCalculator;
private final boolean verboseLogging;
BackwardChangeoverHelper(GlobalParam globalParam, LocalDateTime baseTime, MachineCalculator machineCalculator) {
this.globalParam = globalParam;
this.baseTime = baseTime;
this.machineCalculator = machineCalculator;
this.verboseLogging = Boolean.parseBoolean(System.getProperty(DEBUG_BACKWARD_CHANGEOVER_VERBOSE_PROPERTY, "false"));
}
boolean isVerboseLoggingEnabled() {
return verboseLogging;
}
void writeVerboseLog(String message) {
if (verboseLogging) {
FileHelper.writeLogFile(message);
}
}
private String convertTime(int second) {
return baseTime.plusSeconds(second).format(DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm"));
}
// ==================== 倒排换型辅助方法开始 ====================
GAScheduleResult getNextMachineTaskForBackward(CopyOnWriteArrayList<GAScheduleResult> machineTasks, int processEndTime) {
if (machineTasks == null || machineTasks.isEmpty()) {
return null;
}
return machineTasks.stream()
.filter(t -> getProcessStartTime(t) >= processEndTime)
.min(Comparator.comparingInt(this::getProcessStartTime))
.orElse(null);
}
GAScheduleResult getPrevMachineTaskForBackward(CopyOnWriteArrayList<GAScheduleResult> machineTasks, int processStartTime) {
if (machineTasks == null || machineTasks.isEmpty()) {
return null;
}
return machineTasks.stream()
.filter(t -> getProcessEndTime(t) <= processStartTime)
.max(Comparator.comparingInt(this::getProcessEndTime))
.orElse(null);
}
int getProcessStartTime(GAScheduleResult result) {
if (result == null) {
return 0;
}
return getProcessStartTime(result.getGeneDetails(), result.getStartTime());
}
int getProcessEndTime(GAScheduleResult result) {
if (result == null) {
return 0;
}
return getProcessEndTime(result.getGeneDetails(), result.getEndTime());
}
int getProcessStartTime(List<ScheduleResultDetail> details, int fallback) {
if (details == null || details.isEmpty()) {
return fallback;
}
return details.stream()
.filter(detail -> !detail.isSetup())
.mapToInt(ScheduleResultDetail::getStartTime)
.min()
.orElse(fallback);
}
int getProcessEndTime(List<ScheduleResultDetail> details, int fallback) {
if (details == null || details.isEmpty()) {
return fallback;
}
return details.stream()
.filter(detail -> !detail.isSetup())
.mapToInt(ScheduleResultDetail::getEndTime)
.max()
.orElse(fallback);
}
JitBackwardScheduleResult scheduleJitBackwardWithChangeover(Machine machine,
Entry operation,
int latestEndTime,
int processingTimeTotal,
double processingTime,
CopyOnWriteArrayList<GAScheduleResult> machineTasks,
List<Entry> allOperations) {
boolean backwardScheduled = false;
int backwardLatestEndTime = latestEndTime;
int backwardAttemptLimit = Math.max(1, (machineTasks == null ? 0 : machineTasks.size()) + 1);
int setupTime = 0;
CopyOnWriteArrayList<ScheduleResultDetail> geneDetails = new CopyOnWriteArrayList<>();
for (int backwardAttempt = 0; backwardAttempt < backwardAttemptLimit; backwardAttempt++) {
CopyOnWriteArrayList<TimeSegment> emptySetupSegments = new CopyOnWriteArrayList<>();
Map<Integer,Object> previewResult = machineCalculator.CreateScheduleResultBackward(machine, operation,
processingTimeTotal, backwardLatestEndTime, processingTime, operation.getQuantity(),
0, backwardLatestEndTime, backwardLatestEndTime, emptySetupSegments, false);
CopyOnWriteArrayList<ScheduleResultDetail> previewGeneDetails =
(CopyOnWriteArrayList<ScheduleResultDetail>) previewResult.get(2);
int previewProcessStartTime = previewGeneDetails.stream()
.mapToInt(ScheduleResultDetail::getStartTime)
.min()
.orElse(backwardLatestEndTime);
int previewProcessEndTime = previewGeneDetails.stream()
.mapToInt(ScheduleResultDetail::getEndTime)
.max()
.orElse(backwardLatestEndTime);
machineCalculator.AddMachineAvailable(machine, previewGeneDetails);
GAScheduleResult nextGeneOnMachine = getNextMachineTaskForBackward(machineTasks, previewProcessEndTime);
writeVerboseLog("[倒排换型定位] 试排后准备计算换型 operationId=" + operation.getId()
+ ", groupId=" + operation.getGroupId()
+ ", execId=" + operation.getExecId()
+ ", machineId=" + machine.getId()
+ ", attempt=" + backwardAttempt
+ ", latestEndTime=" + backwardLatestEndTime
+ ", latestEnd=" + convertTime(backwardLatestEndTime)
+ ", previewProcessStart=" + previewProcessStartTime
+ ", previewProcessStartTime=" + convertTime(previewProcessStartTime)
+ ", previewProcessEnd=" + previewProcessEndTime
+ ", previewProcessEndTime=" + convertTime(previewProcessEndTime)
+ ", processingSeconds=" + processingTimeTotal
+ ", machineTaskCount=" + (machineTasks == null ? 0 : machineTasks.size())
+ ", nextTask=" + describeScheduleResult(nextGeneOnMachine)
+ ", params=" + describeDiscreteParams(operation));
CopyOnWriteArrayList<ScheduleResultDetail> releasedNextSetupDetails =
releaseNextTaskSetupForBackward(nextGeneOnMachine, machine);
int nextSetupLatestEndTime = nextGeneOnMachine == null
? previewProcessEndTime
: Math.min(backwardLatestEndTime, getProcessStartTime(nextGeneOnMachine));
Map<Integer,Object> nextSetupResult = calculateSetupTimeBackward(nextGeneOnMachine, operation, machine,
nextSetupLatestEndTime, processingTimeTotal, globalParam.is_smoothChangeOverInWeek(),
allOperations, machineTasks);
int nextSetupTime = (int) nextSetupResult.get(1);
int latestProcessEndTime = (int) nextSetupResult.get(4);
int attemptProcessingTimeTotal = (int) nextSetupResult.get(5);
boolean backwardChangeoverFeasible = true;
if (Boolean.TRUE.equals(nextSetupResult.get(8))) {
backwardChangeoverFeasible = false;
writeVerboseLog("[BackwardInsertWindow] 后置换型可用时间不足,当前插入位置不可行"
+ ", operationId=" + operation.getId()
+ ", groupId=" + operation.getGroupId()
+ ", machineId=" + machine.getId()
+ ", setupTime=" + nextSetupTime
+ ", availableSeconds=" + nextSetupResult.get(7));
}
CopyOnWriteArrayList<TimeSegment> noSetupSegments = new CopyOnWriteArrayList<>();
Map<Integer,Object> candidateResult = machineCalculator.CreateScheduleResultBackward(machine, operation,
attemptProcessingTimeTotal, latestProcessEndTime, processingTime, operation.getQuantity(),
0, latestProcessEndTime, latestProcessEndTime, noSetupSegments, false);
CopyOnWriteArrayList<ScheduleResultDetail> candidateGeneDetails =
(CopyOnWriteArrayList<ScheduleResultDetail>) candidateResult.get(2);
int finalProcessStartTime = getProcessStartTime(candidateGeneDetails, previewProcessStartTime);
int finalProcessEndTime = getProcessEndTime(candidateGeneDetails, previewProcessEndTime);
GAScheduleResult prevGeneOnMachine = getPrevMachineTaskForBackward(machineTasks, finalProcessStartTime);
writeVerboseLog("[BackwardChangeover] check previous adjacent task, operationId=" + operation.getId()
+ ", groupId=" + operation.getGroupId()
+ ", execId=" + operation.getExecId()
+ ", machineId=" + machine.getId()
+ ", attempt=" + backwardAttempt
+ ", finalProcessStart=" + finalProcessStartTime
+ ", finalProcessStartTime=" + convertTime(finalProcessStartTime)
+ ", prevTask=" + describeScheduleResult(prevGeneOnMachine));
Map<Integer,Object> previousSetupResult = calculateSetupTimeBeforeBackward(prevGeneOnMachine, operation, machine,
finalProcessStartTime, attemptProcessingTimeTotal, globalParam.is_smoothChangeOverInWeek(),
allOperations, machineTasks);
int previousSetupTime = (int) previousSetupResult.get(1);
if (Boolean.TRUE.equals(previousSetupResult.get(8))) {
backwardChangeoverFeasible = false;
writeVerboseLog("[BackwardInsertWindow] 前置换型可用时间不足,当前插入位置不可行"
+ ", operationId=" + operation.getId()
+ ", groupId=" + operation.getGroupId()
+ ", machineId=" + machine.getId()
+ ", setupTime=" + previousSetupTime
+ ", availableSeconds=" + previousSetupResult.get(7));
}
boolean canInsertBackwardWindow = backwardChangeoverFeasible
&& validateBackwardInsertWindow(prevGeneOnMachine, nextGeneOnMachine,
operation, machine, finalProcessStartTime, finalProcessEndTime,
previousSetupResult, nextSetupResult);
if (canInsertBackwardWindow) {
setupTime = previousSetupTime;
operation.setChangeLineTime(setupTime);
geneDetails = candidateGeneDetails;
if (previousSetupTime > 0) {
CopyOnWriteArrayList<TimeSegment> previousSetupSegments =
(CopyOnWriteArrayList<TimeSegment>) previousSetupResult.get(6);
CopyOnWriteArrayList<ScheduleResultDetail> previousSetupDetails =
machineCalculator.CreateSetupDetailsBackward(machine, previousSetupTime,
(int) previousSetupResult.get(2),
(int) previousSetupResult.get(3),
previousSetupSegments,
globalParam.is_smoothChangeOverInWeek());
if (previousSetupDetails != null && !previousSetupDetails.isEmpty()) {
geneDetails.addAll(previousSetupDetails);
geneDetails.sort(Comparator.comparingInt(ScheduleResultDetail::getStartTime));
}
}
replaceNextTaskSetupForBackward(nextGeneOnMachine, machine, nextSetupTime, nextSetupResult);
backwardScheduled = true;
break;
}
machineCalculator.AddMachineAvailable(machine, candidateGeneDetails);
restoreNextTaskSetupForBackward(nextGeneOnMachine, machine, releasedNextSetupDetails);
writeVerboseLog("[BackwardInsertWindow] 当前间隔放不下,继续往前找"
+ ", operationId=" + operation.getId()
+ ", groupId=" + operation.getGroupId()
+ ", machineId=" + machine.getId()
+ ", attempt=" + backwardAttempt
+ ", prevTask=" + describeScheduleResult(prevGeneOnMachine));
int nextLatestEndTime;
if (prevGeneOnMachine != null) {
nextLatestEndTime = getProcessStartTime(prevGeneOnMachine);
} else {
nextLatestEndTime = Math.min(finalProcessStartTime, previewProcessStartTime) - 1;
}
if (nextLatestEndTime <= 0 || nextLatestEndTime >= backwardLatestEndTime) {
break;
}
backwardLatestEndTime = nextLatestEndTime;
}
if (!backwardScheduled) {
writeVerboseLog("[BackwardInsertWindow] 所有倒排插入位置都放不下,回退为不带换型倒排"
+ ", operationId=" + operation.getId()
+ ", groupId=" + operation.getGroupId()
+ ", machineId=" + machine.getId()
+ ", latestEndTime=" + latestEndTime
+ ", latestEnd=" + convertTime(latestEndTime));
geneDetails = machineCalculator.getNextAvailableTime(machine, operation, latestEndTime, -1,
processingTimeTotal, machineTasks, operation.getIsInterrupt() != 1, true,
processingTime, operation.getQuantity(), true, true);
setupTime = 0;
operation.setChangeLineTime(setupTime);
}
return new JitBackwardScheduleResult(geneDetails, setupTime);
}
static class JitBackwardScheduleResult {
private final CopyOnWriteArrayList<ScheduleResultDetail> geneDetails;
private final int setupTime;
JitBackwardScheduleResult(CopyOnWriteArrayList<ScheduleResultDetail> geneDetails, int setupTime) {
this.geneDetails = geneDetails;
this.setupTime = setupTime;
}
CopyOnWriteArrayList<ScheduleResultDetail> getGeneDetails() {
return geneDetails;
}
int getSetupTime() {
return setupTime;
}
}
boolean validateBackwardInsertWindow(GAScheduleResult prevGeneOnMachine,
GAScheduleResult nextGeneOnMachine,
Entry operation,
Machine machine,
int processStartTime,
int processEndTime,
Map<Integer,Object> previousSetupResult,
Map<Integer,Object> nextSetupResult) {
int prevBoundaryTime = prevGeneOnMachine == null
? Integer.MIN_VALUE
: getProcessEndTime(prevGeneOnMachine);
int nextBoundaryTime = nextGeneOnMachine == null
? Integer.MAX_VALUE
: getProcessStartTime(nextGeneOnMachine);
int blockStartTime = processStartTime;
int previousSetupTime = getSetupTime(previousSetupResult);
if (previousSetupTime > 0) {
blockStartTime = Math.min(blockStartTime, (int) previousSetupResult.get(2));
}
int blockEndTime = processEndTime;
int nextSetupTime = getSetupTime(nextSetupResult);
if (nextSetupTime > 0) {
blockEndTime = Math.max(blockEndTime, (int) nextSetupResult.get(3));
}
boolean previousSetupInsufficient = Boolean.TRUE.equals(previousSetupResult == null ? false : previousSetupResult.get(8));
boolean nextSetupInsufficient = Boolean.TRUE.equals(nextSetupResult == null ? false : nextSetupResult.get(8));
boolean canInsert = !previousSetupInsufficient
&& !nextSetupInsufficient
&& blockStartTime >= prevBoundaryTime
&& blockEndTime <= nextBoundaryTime;
writeVerboseLog("[BackwardInsertWindow] operationId=" + operation.getId()
+ ", groupId=" + operation.getGroupId()
+ ", machineId=" + machine.getId()
+ ", prevTask=" + describeScheduleResult(prevGeneOnMachine)
+ ", nextTask=" + describeScheduleResult(nextGeneOnMachine)
+ ", prevBoundary=" + (prevGeneOnMachine == null ? "null" : convertTime(prevBoundaryTime))
+ ", nextBoundary=" + (nextGeneOnMachine == null ? "null" : convertTime(nextBoundaryTime))
+ ", blockStart=" + convertTime(blockStartTime)
+ ", blockEnd=" + convertTime(blockEndTime)
+ ", processStart=" + convertTime(processStartTime)
+ ", processEnd=" + convertTime(processEndTime)
+ ", previousSetupTime=" + previousSetupTime
+ ", nextSetupTime=" + nextSetupTime
+ ", canInsert=" + canInsert);
return canInsert;
}
/**
* 试插蓝色前,先把黄色任务身上的旧后置换型释放出来。
* 这里不永久删除业务含义,只是让设备日历先空出来,方便判断真实的绿色/黄色间隔。
*/
CopyOnWriteArrayList<ScheduleResultDetail> releaseNextTaskSetupForBackward(GAScheduleResult nextGeneOnMachine,
Machine machine) {
CopyOnWriteArrayList<ScheduleResultDetail> releasedSetupDetails = new CopyOnWriteArrayList<>();
if (nextGeneOnMachine == null || nextGeneOnMachine.getGeneDetails() == null
|| nextGeneOnMachine.getGeneDetails().isEmpty()) {
return releasedSetupDetails;
}
CopyOnWriteArrayList<ScheduleResultDetail> nextDetails =
new CopyOnWriteArrayList<>(nextGeneOnMachine.getGeneDetails());
nextDetails.stream()
.filter(ScheduleResultDetail::isSetup)
.forEach(releasedSetupDetails::add);
if (releasedSetupDetails.isEmpty()) {
return releasedSetupDetails;
}
machineCalculator.AddMachineAvailable(machine, releasedSetupDetails);
nextDetails.removeIf(ScheduleResultDetail::isSetup);
nextGeneOnMachine.setGeneDetails(nextDetails);
nextGeneOnMachine.setChangeOverTime(0);
refreshScheduleResultWindow(nextGeneOnMachine);
writeVerboseLog("[BackwardInsertWindow] 临时释放黄色旧换型"
+ ", nextTask=" + describeScheduleResult(nextGeneOnMachine)
+ ", releasedSetupCount=" + releasedSetupDetails.size()
+ ", releasedSetupSeconds=" + sumSetupSeconds(releasedSetupDetails));
return releasedSetupDetails;
}
/**
* 当前绿色/黄色间隔放不下蓝色时,把刚才释放的黄色旧换型恢复回去。
*/
void restoreNextTaskSetupForBackward(GAScheduleResult nextGeneOnMachine,
Machine machine,
List<ScheduleResultDetail> releasedSetupDetails) {
if (releasedSetupDetails == null || releasedSetupDetails.isEmpty()) {
return;
}
setMachineAvailabilityUsed(machine, releasedSetupDetails, true);
if (nextGeneOnMachine != null) {
CopyOnWriteArrayList<ScheduleResultDetail> nextDetails = nextGeneOnMachine.getGeneDetails() == null
? new CopyOnWriteArrayList<>()
: new CopyOnWriteArrayList<>(nextGeneOnMachine.getGeneDetails());
nextDetails.removeIf(ScheduleResultDetail::isSetup);
nextDetails.addAll(releasedSetupDetails);
nextDetails.sort(Comparator.comparingInt(ScheduleResultDetail::getStartTime));
nextGeneOnMachine.setGeneDetails(nextDetails);
nextGeneOnMachine.setChangeOverTime(sumSetupSeconds(releasedSetupDetails));
refreshScheduleResultWindow(nextGeneOnMachine);
}
writeVerboseLog("[BackwardInsertWindow] 恢复黄色旧换型"
+ ", nextTask=" + describeScheduleResult(nextGeneOnMachine)
+ ", restoredSetupCount=" + releasedSetupDetails.size()
+ ", restoredSetupSeconds=" + sumSetupSeconds(releasedSetupDetails));
}
/**
* 按排程明细里的 key/usedSegment,把设备日历设置为占用或可用。
* 恢复旧换型时用 true,释放时仍沿用 machineCalculator.AddMachineAvailable。
*/
private void setMachineAvailabilityUsed(Machine machine,
List<ScheduleResultDetail> geneDetails,
boolean used) {
if (machine == null || machine.getAvailability() == null
|| geneDetails == null || geneDetails.isEmpty()) {
return;
}
Set<String> keys = new HashSet<>();
for (ScheduleResultDetail detail : geneDetails) {
if (detail == null) {
continue;
}
if (detail.getUsedSegment() != null && !detail.getUsedSegment().isEmpty()) {
detail.getUsedSegment().stream()
.map(TimeSegment::getKey)
.filter(Objects::nonNull)
.forEach(keys::add);
} else if (detail.getKey() != null) {
keys.add(detail.getKey());
}
}
if (keys.isEmpty()) {
return;
}
machine.getAvailability().stream()
.filter(t -> keys.contains(t.getKey()))
.forEach(t -> t.setUsed(used));
}
private int sumSetupSeconds(List<ScheduleResultDetail> setupDetails) {
if (setupDetails == null || setupDetails.isEmpty()) {
return 0;
}
return setupDetails.stream()
.mapToInt(ScheduleResultDetail::getProcessingTime)
.sum();
}
/**
* 后置换型归属黄色任务。
* 插入蓝色后,黄色原来的“绿色 -> 黄色”换型要释放掉,再替换成“蓝色 -> 黄色”的新换型。
*/
void replaceNextTaskSetupForBackward(GAScheduleResult nextGeneOnMachine,
Machine machine,
int setupTime,
Map<Integer,Object> setupResult) {
if (nextGeneOnMachine == null || setupResult == null) {
return;
}
CopyOnWriteArrayList<ScheduleResultDetail> nextDetails = nextGeneOnMachine.getGeneDetails() == null
? new CopyOnWriteArrayList<>()
: new CopyOnWriteArrayList<>(nextGeneOnMachine.getGeneDetails());
List<ScheduleResultDetail> oldSetupDetails = nextDetails.stream()
.filter(ScheduleResultDetail::isSetup)
.collect(Collectors.toList());
if (!oldSetupDetails.isEmpty()) {
machineCalculator.AddMachineAvailable(machine, oldSetupDetails);
nextDetails.removeIf(ScheduleResultDetail::isSetup);
}
nextGeneOnMachine.setChangeOverTime(0);
if (setupTime > 0) {
CopyOnWriteArrayList<TimeSegment> setupSegments =
(CopyOnWriteArrayList<TimeSegment>) setupResult.get(6);
CopyOnWriteArrayList<ScheduleResultDetail> newSetupDetails =
machineCalculator.CreateSetupDetailsBackward(machine, setupTime,
(int) setupResult.get(2),
(int) setupResult.get(3),
setupSegments,
globalParam.is_smoothChangeOverInWeek());
if (newSetupDetails != null && !newSetupDetails.isEmpty()) {
nextDetails.addAll(newSetupDetails);
nextDetails.sort(Comparator.comparingInt(ScheduleResultDetail::getStartTime));
}
nextGeneOnMachine.setChangeOverTime(setupTime);
}
nextGeneOnMachine.setGeneDetails(nextDetails);
refreshScheduleResultWindow(nextGeneOnMachine);
writeVerboseLog("[BackwardInsertWindow] replaced next task setup"
+ ", nextTask=" + describeScheduleResult(nextGeneOnMachine)
+ ", setupTime=" + setupTime
+ ", removedOldSetupCount=" + oldSetupDetails.size());
}
/**
* 根据明细刷新任务整体起止时间;加工开始/结束仍由 getProcessStartTime/getProcessEndTime 过滤 setup 得到。
*/
private void refreshScheduleResultWindow(GAScheduleResult result) {
if (result == null || result.getGeneDetails() == null || result.getGeneDetails().isEmpty()) {
return;
}
result.setStartTime(result.getGeneDetails().stream()
.mapToInt(ScheduleResultDetail::getStartTime)
.min()
.orElse(result.getStartTime()));
result.setEndTime(result.getGeneDetails().stream()
.mapToInt(ScheduleResultDetail::getEndTime)
.max()
.orElse(result.getEndTime()));
}
private int getSetupTime(Map<Integer,Object> setupResult) {
if (setupResult == null || setupResult.get(1) == null) {
return 0;
}
return (int) setupResult.get(1);
}
String describeScheduleResult(GAScheduleResult result) {
if (result == null) {
return "null";
}
return "{operationId=" + result.getOperationId()
+ ", groupId=" + result.getGroupId()
+ ", execId=" + result.getExecId()
+ ", start=" + result.getStartTime() + "(" + convertTime(result.getStartTime()) + ")"
+ ", end=" + result.getEndTime() + "(" + convertTime(result.getEndTime()) + ")"
+ ", processStart=" + getProcessStartTime(result) + "(" + convertTime(getProcessStartTime(result)) + ")"
+ ", processEnd=" + getProcessEndTime(result) + "(" + convertTime(getProcessEndTime(result)) + ")"
+ ", changeOverTime=" + result.getChangeOverTime()
+ "}";
}
String describeDiscreteParams(Entry operation) {
if (operation == null || operation.getDiscreteParameter() == null || operation.getDiscreteParameter().isEmpty()) {
return "[]";
}
return operation.getDiscreteParameter().stream()
.map(p -> "{groupId=" + p.getGroupId()
+ ", parameterId=" + p.getParameterId()
+ ", parameterName=" + p.getParameterName()
+ "}")
.collect(Collectors.joining(",", "[", "]"));
}
/**
* 临时测试开关:启动参数传 -Daps.debug.backwardSetupTimeSeconds=秒数 时,
* 只覆盖倒排换型时间;不传或传负数时走原来的矩阵/时长计算。
*/
private int overrideSetupTimeForDebug(int setupTime, String direction,
Entry fromOperation, Entry toOperation, Machine machine) {
String overrideValue = System.getProperty(DEBUG_BACKWARD_SETUP_TIME_SECONDS_PROPERTY);
if (overrideValue == null || overrideValue.trim().isEmpty()) {
return setupTime;
}
int debugSetupTime;
try {
debugSetupTime = Integer.parseInt(overrideValue.trim());
} catch (NumberFormatException ex) {
writeVerboseLog("[BackwardChangeoverDebug] 手动换型时间参数无效,继续使用原计算值"
+ ", property=" + DEBUG_BACKWARD_SETUP_TIME_SECONDS_PROPERTY
+ ", value=" + overrideValue
+ ", originalSetupTime=" + setupTime);
return setupTime;
}
if (debugSetupTime < 0) {
return setupTime;
}
writeVerboseLog("[BackwardChangeoverDebug] 使用手动换型时间"
+ ", direction=" + direction
+ ", machineId=" + (machine == null ? null : machine.getId())
+ ", fromOp=" + (fromOperation == null ? null : fromOperation.getId())
+ ", toOp=" + (toOperation == null ? null : toOperation.getId())
+ ", originalSetupTime=" + setupTime
+ ", debugSetupTime=" + debugSetupTime);
return debugSetupTime;
}
Map<Integer,Object> calculateSetupTimeBeforeBackward(GAScheduleResult prevGeneOnMachine, Entry operation,
Machine machine, int processStartTime,
int processingTimeTotal, boolean changeOverInWeek,
List<Entry> allOperations,
CopyOnWriteArrayList<GAScheduleResult> machineTasks) {
Map<Integer,Object> result = new HashMap<>();
CopyOnWriteArrayList<TimeSegment> setupSegments = new CopyOnWriteArrayList<>();
result.put(1, 0);
result.put(2, processStartTime);
result.put(3, processStartTime);
result.put(4, processStartTime);
result.put(5, processingTimeTotal);
result.put(6, setupSegments);
result.put(7, 0);
result.put(8, false);
if (prevGeneOnMachine == null || operation == null || machine == null) {
writeVerboseLog("[BackwardChangeover] skip previous changeover, prev/operation/machine is null"
+ ", operationId=" + (operation == null ? null : operation.getId())
+ ", machineId=" + (machine == null ? null : machine.getId())
+ ", processStartTime=" + processStartTime
+ ", processStart=" + convertTime(processStartTime)
+ ", machineTaskCount=" + (machineTasks == null ? 0 : machineTasks.size()));
return result;
}
Entry prevOperation = allOperations == null ? null : allOperations.stream()
.filter(t -> Objects.equals(t.getExecId(), prevGeneOnMachine.getExecId()))
.findFirst()
.orElse(null);
if (prevOperation == null) {
writeVerboseLog("[BackwardChangeover] skip previous changeover, previous Entry not found"
+ ", operationId=" + operation.getId()
+ ", prevTask=" + describeScheduleResult(prevGeneOnMachine)
+ ", allOperationCount=" + (allOperations == null ? 0 : allOperations.size()));
return result;
}
DiscreteParameterMatrixService discreteParameterMatrixService = SpringContextUtil.getBean(DiscreteParameterMatrixService.class);
DiscreteParameterDurationService discreteParameterDurationService = SpringContextUtil.getBean(DiscreteParameterDurationService.class);
double durationValue = discreteParameterDurationService.calculateChangeoverTime(
prevOperation, operation, machine, allOperations, processStartTime, machineTasks);
double matrixValue = discreteParameterMatrixService.getDiscreteParameterMatrixValue(operation, prevOperation);
int setupTime = (int) Math.max(durationValue, matrixValue);
setupTime = overrideSetupTimeForDebug(setupTime, "prev->current", prevOperation, operation, machine);
writeVerboseLog("[BackwardChangeover] previous changeover result prevOp=" + prevOperation.getId()
+ ", currentOp=" + operation.getId()
+ ", machineId=" + machine.getId()
+ ", prevParams=" + describeDiscreteParams(prevOperation)
+ ", currentParams=" + describeDiscreteParams(operation)
+ ", durationValue=" + durationValue
+ ", matrixValue=" + matrixValue
+ ", setupTime=" + setupTime
+ ", processStart=" + convertTime(processStartTime)
+ ", prevProcessEnd=" + convertTime(getProcessEndTime(prevGeneOnMachine)));
result.put(1, setupTime);
if (setupTime <= 0) {
return result;
}
int prevBoundaryTime = getProcessEndTime(prevGeneOnMachine);
LocalDateTime setupEnd = baseTime.plusSeconds(processStartTime);
if (changeOverInWeek) {
LocalDateTime setupStart = setupEnd.minusSeconds(setupTime);
int setupStartTime = secondsFromBase(setupStart);
setupSegments = collectAvailableSegmentsBetween(machine, setupStart, setupEnd);
result.put(2, setupStartTime);
result.put(3, processStartTime);
result.put(4, processStartTime);
result.put(6, setupSegments);
result.put(7, Math.max(0, processStartTime - prevBoundaryTime));
result.put(8, setupStartTime < prevBoundaryTime);
writeVerboseLog("[BackwardChangeover] previous changeover window currentOp=" + operation.getId()
+ ", setupStart=" + convertTime(setupStartTime)
+ ", setupEnd=" + convertTime(processStartTime)
+ ", prevBoundary=" + convertTime(prevBoundaryTime)
+ ", requiredSeconds=" + setupTime
+ ", availableCalendarSeconds=" + result.get(7)
+ ", insufficient=" + result.get(8)
+ ", segments=" + describeTimeSegments(setupSegments));
return result;
}
BackwardSetupWindow setupWindow = collectBackwardSetupWindow(machine, setupEnd, setupTime,
baseTime.plusSeconds(prevBoundaryTime));
result.put(2, setupWindow.startTime);
result.put(3, setupWindow.endTime);
result.put(4, processStartTime);
result.put(6, setupWindow.segments);
result.put(7, setupWindow.availableSeconds);
result.put(8, setupWindow.insufficient);
writeVerboseLog("[BackwardChangeover] previous changeover window currentOp=" + operation.getId()
+ ", setupStart=" + convertTime(setupWindow.startTime)
+ ", setupEnd=" + convertTime(setupWindow.endTime)
+ ", prevBoundary=" + convertTime(prevBoundaryTime)
+ ", requiredSeconds=" + setupTime
+ ", availableSeconds=" + setupWindow.availableSeconds
+ ", insufficient=" + setupWindow.insufficient
+ ", segments=" + describeTimeSegments(setupWindow.segments));
return result;
}
Map<Integer,Object> calculateSetupTimeBackward(GAScheduleResult nextGeneOnMachine, Entry operation,
Machine machine, int latestEndTime,
int processingTimeTotal, boolean changeOverInWeek,
List<Entry> allOperations,
CopyOnWriteArrayList<GAScheduleResult> machineTasks) {
Map<Integer,Object> result = new HashMap<>();
CopyOnWriteArrayList<TimeSegment> setupSegments = new CopyOnWriteArrayList<>();
result.put(1, 0);
result.put(2, latestEndTime);
result.put(3, latestEndTime);
result.put(4, latestEndTime);
result.put(5, processingTimeTotal);
result.put(6, setupSegments);
result.put(7, 0);
result.put(8, false);
if (nextGeneOnMachine == null || operation == null || machine == null) {
writeVerboseLog("[倒排换型定位] 未计算换型:nextGeneOnMachine/operation/machine为空"
+ ", operationId=" + (operation == null ? null : operation.getId())
+ ", machineId=" + (machine == null ? null : machine.getId())
+ ", latestEndTime=" + latestEndTime
+ ", latestEnd=" + convertTime(latestEndTime)
+ ", machineTaskCount=" + (machineTasks == null ? 0 : machineTasks.size()));
return result;
}
Entry nextOperation = allOperations == null ? null : allOperations.stream()
.filter(t -> Objects.equals(t.getExecId(), nextGeneOnMachine.getExecId()))
.findFirst()
.orElse(null);
if (nextOperation == null) {
writeVerboseLog("[倒排换型定位] 未计算换型:找不到后继工序Entry"
+ ", operationId=" + operation.getId()
+ ", nextTask=" + describeScheduleResult(nextGeneOnMachine)
+ ", allOperationCount=" + (allOperations == null ? 0 : allOperations.size()));
return result;
}
DiscreteParameterMatrixService discreteParameterMatrixService = SpringContextUtil.getBean(DiscreteParameterMatrixService.class);
DiscreteParameterDurationService discreteParameterDurationService = SpringContextUtil.getBean(DiscreteParameterDurationService.class);
double durationValue = discreteParameterDurationService.calculateChangeoverTime(
operation, nextOperation, machine, allOperations, latestEndTime, machineTasks);
double matrixValue = discreteParameterMatrixService.getDiscreteParameterMatrixValue(nextOperation, operation);
int setupTime = (int) Math.max(durationValue, matrixValue);
setupTime = overrideSetupTimeForDebug(setupTime, "current->next", operation, nextOperation, machine);
nextOperation.setChangeLineTime(setupTime);
int nextProcessStartTime = getProcessStartTime(nextGeneOnMachine);
writeVerboseLog("[倒排换型定位] 换型查询结果 currentOp=" + operation.getId()
+ ", nextOp=" + nextOperation.getId()
+ ", machineId=" + machine.getId()
+ ", currentParams=" + describeDiscreteParams(operation)
+ ", nextParams=" + describeDiscreteParams(nextOperation)
+ ", durationValue=" + durationValue
+ ", matrixValue=" + matrixValue
+ ", setupTime=" + setupTime
+ ", latestEnd=" + convertTime(latestEndTime)
+ ", nextStart=" + convertTime(nextProcessStartTime));
result.put(1, setupTime);
if (setupTime <= 0) {
return result;
}
int setupEndTime = Math.min(latestEndTime, nextProcessStartTime);
LocalDateTime setupEnd = baseTime.plusSeconds(setupEndTime);
if (changeOverInWeek) {
LocalDateTime setupStart = setupEnd.minusSeconds(setupTime);
setupSegments = collectAvailableSegmentsBetween(machine, setupStart, setupEnd);
result.put(2, secondsFromBase(setupStart));
result.put(3, setupEndTime);
result.put(4, secondsFromBase(setupStart));
result.put(6, setupSegments);
result.put(7, sumCalendarSeconds(setupSegments));
writeVerboseLog("[倒排换型定位] 换型窗口 currentOp=" + operation.getId()
+ ", setupStart=" + convertTime(secondsFromBase(setupStart))
+ ", setupEnd=" + convertTime(setupEndTime)
+ ", latestProcessEnd=" + convertTime(secondsFromBase(setupStart))
+ ", requiredSeconds=" + setupTime
+ ", availableSeconds=" + result.get(7)
+ ", segments=" + describeTimeSegments(setupSegments));
return result;
}
BackwardSetupWindow setupWindow = collectBackwardSetupWindow(machine, setupEnd, setupTime);
result.put(2, setupWindow.startTime);
result.put(3, setupWindow.endTime);
result.put(4, setupWindow.startTime);
result.put(6, setupWindow.segments);
result.put(7, setupWindow.availableSeconds);
result.put(8, setupWindow.insufficient);
writeVerboseLog("[倒排换型定位] 换型窗口 currentOp=" + operation.getId()
+ ", setupStart=" + convertTime(setupWindow.startTime)
+ ", setupEnd=" + convertTime(setupWindow.endTime)
+ ", latestProcessEnd=" + convertTime(setupWindow.startTime)
+ ", requiredSeconds=" + setupTime
+ ", availableSeconds=" + setupWindow.availableSeconds
+ ", insufficient=" + setupWindow.insufficient
+ ", segments=" + describeTimeSegments(setupWindow.segments));
return result;
}
private BackwardSetupWindow collectBackwardSetupWindow(Machine machine, LocalDateTime setupEnd, int setupTime) {
return collectBackwardSetupWindow(machine, setupEnd, setupTime, null);
}
private BackwardSetupWindow collectBackwardSetupWindow(Machine machine, LocalDateTime setupEnd, int setupTime,
LocalDateTime earliestStart) {
BackwardSetupWindow window = new BackwardSetupWindow();
window.endTime = secondsFromBase(setupEnd);
window.startTime = secondsFromBase(setupEnd);
if (machine == null || machine.getAvailability() == null || setupTime <= 0) {
window.insufficient = setupTime > 0;
return window;
}
int remaining = setupTime;
LocalDateTime cursor = setupEnd;
List<TimeSegment> slots = machine.getAvailability().stream()
.filter(slot -> !slot.isUsed())
.filter(slot -> slot.getType() != SegmentType.MAINTENANCE)
.filter(slot -> slot.getStart().isBefore(setupEnd))
.filter(slot -> earliestStart == null || slot.getEnd().isAfter(earliestStart))
.sorted(Comparator.comparing(TimeSegment::getEnd).reversed())
.collect(Collectors.toList());
for (TimeSegment slot : slots) {
LocalDateTime effectiveEnd = slot.getEnd().isAfter(cursor) ? cursor : slot.getEnd();
LocalDateTime effectiveSlotStart = earliestStart != null && slot.getStart().isBefore(earliestStart)
? earliestStart
: slot.getStart();
if (!effectiveSlotStart.isBefore(effectiveEnd)) {
continue;
}
int availableSeconds = (int) ChronoUnit.SECONDS.between(effectiveSlotStart, effectiveEnd);
int usedSeconds = Math.min(remaining, availableSeconds);
LocalDateTime effectiveStart = effectiveEnd.minusSeconds(usedSeconds);
window.segments.add(copySegment(slot, effectiveStart, effectiveEnd, true));
window.availableSeconds += usedSeconds;
remaining -= usedSeconds;
cursor = effectiveStart;
if (remaining <= 0) {
break;
}
}
window.segments.sort(Comparator.comparing(TimeSegment::getStart));
window.startTime = secondsFromBase(cursor);
window.insufficient = remaining > 0;
return window;
}
private CopyOnWriteArrayList<TimeSegment> collectAvailableSegmentsBetween(Machine machine,
LocalDateTime startTime,
LocalDateTime endTime) {
if (machine == null || machine.getAvailability() == null || !startTime.isBefore(endTime)) {
return new CopyOnWriteArrayList<>();
}
return machine.getAvailability().stream()
.filter(slot -> !slot.isUsed())
.filter(slot -> slot.getType() != SegmentType.MAINTENANCE)
.filter(slot -> slot.getStart().isBefore(endTime) && slot.getEnd().isAfter(startTime))
.map(slot -> {
LocalDateTime effectiveStart = slot.getStart().isBefore(startTime) ? startTime : slot.getStart();
LocalDateTime effectiveEnd = slot.getEnd().isAfter(endTime) ? endTime : slot.getEnd();
return copySegment(slot, effectiveStart, effectiveEnd, true);
})
.filter(slot -> slot.getStart().isBefore(slot.getEnd()))
.sorted(Comparator.comparing(TimeSegment::getStart))
.collect(Collectors.toCollection(CopyOnWriteArrayList::new));
}
private TimeSegment copySegment(TimeSegment source, LocalDateTime startTime,
LocalDateTime endTime, boolean used) {
TimeSegment segment = new TimeSegment();
segment.setStart(startTime);
segment.setEnd(endTime);
segment.setType(source.getType());
segment.setHoliday(source.isHoliday());
segment.setUsed(used);
segment.setEfficiency(source.getEfficiency());
return segment;
}
private int sumCalendarSeconds(List<TimeSegment> segments) {
if (segments == null || segments.isEmpty()) {
return 0;
}
return segments.stream()
.mapToInt(segment -> (int) ChronoUnit.SECONDS.between(segment.getStart(), segment.getEnd()))
.sum();
}
private String describeTimeSegments(List<TimeSegment> segments) {
if (segments == null || segments.isEmpty()) {
return "[]";
}
return segments.stream()
.map(segment -> "[" + secondsFromBase(segment.getStart())
+ "-" + secondsFromBase(segment.getEnd())
+ "](" + segment.getStart()
+ "-" + segment.getEnd()
+ "," + ChronoUnit.SECONDS.between(segment.getStart(), segment.getEnd())
+ ")")
.collect(Collectors.joining(",", "[", "]"));
}
private int secondsFromBase(LocalDateTime time) {
return (int) ChronoUnit.SECONDS.between(baseTime, time);
}
private static class BackwardSetupWindow {
private int startTime;
private int endTime;
private int availableSeconds;
private boolean insufficient;
private CopyOnWriteArrayList<TimeSegment> segments = new CopyOnWriteArrayList<>();
}
// ==================== 倒排换型辅助方法结束 ====================
}
......@@ -1297,40 +1297,48 @@ public class GeneticDecoder {
lastGeneOnMachine=machineTasks.get(machineTasks.size()-1);
}
BackwardChangeoverHelper backwardChangeoverHelper =
new BackwardChangeoverHelper(_globalParam, baseTime, machineCalculator);
// 下面开始生成正式的 geneDetails。
// 与 buildMachinePreview 的区别是:这里得到的结果会继续向下写入 GAScheduleResult,成为真正排程结果。
//if (_globalParam.is_smoothChangeOver()) {
if (false) {
//是否考虑换型时间
Map<Integer,Object> reslte = calculateSetupTime(lastGeneOnMachine, operation, machine,earliestStartTime,processingTimeTotal, _globalParam.is_smoothChangeOverInWeek(),chromosome.getAllOperations());
setupTime=(int)reslte.get(1);//换型时间
int setupStartTime=(int)reslte.get(2);//换型开始时间
//earliestStartTime=(int)reslte.get(3);//上个任务的结束时间
earliestStartTime=(int)reslte.get(4);//最早开工时间
processingTimeTotal=(int)reslte.get(5);//processingTimeTotal
if(setupTime==0)
{
if (true) {
if (isJit) {
BackwardChangeoverHelper.JitBackwardScheduleResult backwardResult =
backwardChangeoverHelper.scheduleJitBackwardWithChangeover(machine, operation, earliestStartTime,
processingTimeTotal, processingTime, machineTasks, chromosome.getAllOperations());
geneDetails = backwardResult.getGeneDetails();
setupTime = backwardResult.getSetupTime();
} else {
// 非 JIT 正排 + 考虑换型:沿用原来的正排换型逻辑。
Map<Integer,Object> reslte = calculateSetupTime(lastGeneOnMachine, operation, machine, earliestStartTime,
processingTimeTotal, _globalParam.is_smoothChangeOverInWeek(), chromosome.getAllOperations());
setupTime = (int) reslte.get(1);//换型时间
int setupStartTime = (int) reslte.get(2);//换型开始时间
earliestStartTime = (int) reslte.get(4);//最早开工时间
processingTimeTotal = (int) reslte.get(5);//processingTimeTotal
if (setupTime == 0) {
geneDetails = machineCalculator.getNextAvailableTime(machine, operation, earliestStartTime, -1,
processingTimeTotal, machineTasks, operation.getIsInterrupt()!=1, true,processingTime, operation.getQuantity(), true,isJit);
}else {
CopyOnWriteArrayList<TimeSegment> AvailableTimeSegment = (CopyOnWriteArrayList<TimeSegment>) reslte.get(6);
Map<Integer,Object> result = machineCalculator.CreateScheduleResult(machine, operation, processingTimeTotal, earliestStartTime,
AvailableTimeSegment, processingTime, operation.getQuantity(), operation.getIsInterrupt() != 1, setupTime, _globalParam.is_smoothChangeOverInWeek(), setupStartTime,isJit);
setupTime=(int)result.get(1);
processingTimeTotal, machineTasks, operation.getIsInterrupt() != 1, true,
processingTime, operation.getQuantity(), true, isJit);
} else {
CopyOnWriteArrayList<TimeSegment> AvailableTimeSegment =
(CopyOnWriteArrayList<TimeSegment>) reslte.get(6);
Map<Integer,Object> result = machineCalculator.CreateScheduleResult(machine, operation,
processingTimeTotal, earliestStartTime, AvailableTimeSegment, processingTime,
operation.getQuantity(), operation.getIsInterrupt() != 1, setupTime,
_globalParam.is_smoothChangeOverInWeek(), setupStartTime, isJit);
setupTime = (int) result.get(1);
operation.setChangeLineTime(setupTime);
geneDetails=(CopyOnWriteArrayList<ScheduleResultDetail>) result.get(2);
geneDetails = (CopyOnWriteArrayList<ScheduleResultDetail>) result.get(2);
}
}else {
}
} else {
// 不考虑换型:是否倒排由 isJit 参数交给 MachineCalculator 控制。
geneDetails = machineCalculator.getNextAvailableTime(machine, operation, earliestStartTime, -1,
processingTimeTotal, machineTasks, operation.getIsInterrupt()!=1, true,processingTime, operation.getQuantity(), true,isJit);
processingTimeTotal, machineTasks, operation.getIsInterrupt() != 1, true,
processingTime, operation.getQuantity(), true, isJit);
}
// FileHelper.writeLogFile(" 开始 "+operation.getGroupId()+" : "+operation.getId()+",处理时间: " + processingTime + ", 后处理: " + teardownTime +
// ", 前处理: " + preTime + ", 换型: " + setupTime+ ", 数量: " + operation.getQuantity()+ ", 设备: "+machine.getId()+ ", 是否可中断: "+operation.getIsInterrupt());
......@@ -2593,6 +2601,8 @@ if(geneDetails!=null&&geneDetails.size()>0)
private String ConvertTime(int minute) {
return baseTime.plusSeconds(minute).format(java.time.format.DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm"));
}
private Map<Integer,Object> calculateSetupTime(GAScheduleResult lastGeneOnMachine, Entry operation, Machine machine, int earliestStartTime,int processingTimeTotal,boolean changeOverInWeek,List<Entry> allOperations) {
Map<Integer,Object> reslte=new HashMap<>();
......
......@@ -8,7 +8,6 @@ import com.aps.entity.basic.*;
import com.aps.service.plan.MachineSchedulerService;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.sun.org.apache.xpath.internal.objects.XBoolean;
import java.math.BigDecimal;
import java.math.RoundingMode;
......@@ -73,6 +72,196 @@ public class MachineCalculator {
}
// 基于已选可用时段组装排程结果,并把换型段/加工段对应的 availability 临时占用。
// ==================== 倒排换型排程辅助方法开始 ====================
public Map<Integer,Object> CreateScheduleResultBackward(
Machine machine, Entry operation, int processingTime, int latestEndTime,
double oneTime, double quantity, int changeOverTime, int setupStartTime,
int setupEndTime, CopyOnWriteArrayList<TimeSegment> setupSegments,
boolean setupInCalendarTime) {
Map<Integer,Object> result = new HashMap<>(2);
CopyOnWriteArrayList<ScheduleResultDetail> times = new CopyOnWriteArrayList<>();
CopyOnWriteArrayList<TimeSegment> usedSegments = new CopyOnWriteArrayList<>();
times.addAll(CreateSetupDetailsBackward(machine, changeOverTime, setupStartTime, setupEndTime,
setupSegments, setupInCalendarTime));
LocalDateTime cursor = baseTime.plusSeconds(latestEndTime);
int remainingTime = processingTime;
final LocalDateTime processSearchEnd = cursor;
List<TimeSegment> processSegments = machine.getAvailability().stream()
.filter(slot -> !slot.isUsed())
.filter(slot -> slot.getType() != SegmentType.MAINTENANCE)
.filter(slot -> slot.getStart().isBefore(processSearchEnd))
.sorted(Comparator.comparing(TimeSegment::getEnd).reversed())
.collect(Collectors.toList());
double totalAvailableSeconds = calculateTotalAvailableSecond(
new CopyOnWriteArrayList<>(processSegments), processSearchEnd, true);
if (totalAvailableSeconds + 0.0001 < processingTime) {
throw new IllegalStateException(buildInsufficientScheduleMessage(machine, operation,
processingTime, totalAvailableSeconds, processSegments.size()));
}
for (TimeSegment slot : processSegments) {
if (remainingTime <= 0) {
break;
}
LocalDateTime effectiveEnd = slot.getEnd().isAfter(cursor) ? cursor : slot.getEnd();
if (!slot.getStart().isBefore(effectiveEnd)) {
continue;
}
long availableSeconds = ChronoUnit.SECONDS.between(slot.getStart(), effectiveEnd);
long effectiveSeconds = Math.round(availableSeconds * slot.getEfficiency());
if (effectiveSeconds <= 0) {
continue;
}
int processable = Math.min(remainingTime, (int) effectiveSeconds);
int calendarSeconds = (int) Math.ceil(processable / slot.getEfficiency());
LocalDateTime effectiveStart = effectiveEnd.minusSeconds(calendarSeconds);
ScheduleResultDetail time = createScheduleDetail(slot, effectiveStart, effectiveEnd,
processable, oneTime, quantity, false);
times.add(time);
CopyOnWriteArrayList<TimeSegment> usedSegments1 = RemoveMachineAvailable(machine, time, slot);
if (usedSegments1 != null && usedSegments1.size() > 0) {
usedSegments.addAll(usedSegments1);
}
remainingTime -= processable;
cursor = effectiveStart;
}
if (remainingTime > 0) {
throw new IllegalStateException(buildInsufficientScheduleMessage(machine, operation,
processingTime, totalAvailableSeconds, processSegments.size()));
}
if (usedSegments != null && usedSegments.size() > 0) {
machine.getAvailability().addAll(usedSegments);
}
machine.getAvailability().sort(Comparator.comparing(TimeSegment::getStart));
times.sort(Comparator.comparingInt(ScheduleResultDetail::getStartTime));
result.put(1, changeOverTime);
result.put(2, times);
return result;
}
public CopyOnWriteArrayList<ScheduleResultDetail> CreateSetupDetailsBackward(
Machine machine, int changeOverTime, int setupStartTime, int setupEndTime,
CopyOnWriteArrayList<TimeSegment> setupSegments, boolean setupInCalendarTime) {
CopyOnWriteArrayList<ScheduleResultDetail> times = new CopyOnWriteArrayList<>();
if (setupInCalendarTime && changeOverTime > 0 && setupEndTime > setupStartTime) {
ScheduleResultDetail setupDetail = new ScheduleResultDetail();
setupDetail.setKey(UUID.randomUUID().toString());
setupDetail.setStartTime(setupStartTime);
setupDetail.setEndTime(setupEndTime);
setupDetail.setSetup(true);
if (setupSegments != null && setupSegments.size() > 0) {
List<TimeSegment> occupiedSetupSegments = new ArrayList<>();
for (TimeSegment setupSegment : setupSegments) {
ScheduleResultDetail occupiedSetupDetail = occupySetupSegment(machine, setupSegment);
if (occupiedSetupDetail != null && occupiedSetupDetail.getKey() != null) {
TimeSegment keyRef = new TimeSegment();
keyRef.setKey(occupiedSetupDetail.getKey());
keyRef.setStart(baseTime.plusSeconds(occupiedSetupDetail.getStartTime()));
keyRef.setEnd(baseTime.plusSeconds(occupiedSetupDetail.getEndTime()));
occupiedSetupSegments.add(keyRef);
}
}
setupDetail.setUsedSegment(occupiedSetupSegments);
}
times.add(setupDetail);
} else if (setupSegments != null && setupSegments.size() > 0) {
for (TimeSegment setupSegment : setupSegments) {
ScheduleResultDetail setupDetail = occupySetupSegment(machine, setupSegment);
if (setupDetail != null) {
times.add(setupDetail);
}
}
} else if (changeOverTime > 0 && setupEndTime > setupStartTime) {
ScheduleResultDetail setupDetail = new ScheduleResultDetail();
setupDetail.setStartTime(setupStartTime);
setupDetail.setEndTime(setupEndTime);
setupDetail.setSetup(true);
times.add(setupDetail);
}
if (machine != null && machine.getAvailability() != null) {
machine.getAvailability().sort(Comparator.comparing(TimeSegment::getStart));
}
return times;
}
private ScheduleResultDetail occupySetupSegment(Machine machine, TimeSegment setupSegment) {
if (machine == null || setupSegment == null || !setupSegment.getStart().isBefore(setupSegment.getEnd())) {
return null;
}
TimeSegment targetSegment = machine.getAvailability().stream()
.filter(slot -> !slot.isUsed())
.filter(slot -> slot.getType() != SegmentType.MAINTENANCE)
.filter(slot -> slot.getStart().compareTo(setupSegment.getStart()) <= 0)
.filter(slot -> slot.getEnd().compareTo(setupSegment.getEnd()) >= 0)
.findFirst()
.orElse(null);
if (targetSegment == null) {
return null;
}
ScheduleResultDetail setupDetail = new ScheduleResultDetail();
setupDetail.setStartTime((int) ChronoUnit.SECONDS.between(baseTime, setupSegment.getStart()));
setupDetail.setEndTime((int) ChronoUnit.SECONDS.between(baseTime, setupSegment.getEnd()));
setupDetail.setSetup(true);
setupDetail.setKey(targetSegment.getKey());
CopyOnWriteArrayList<TimeSegment> usedSegments = RemoveMachineAvailable(machine, setupDetail, targetSegment);
if (usedSegments != null && usedSegments.size() > 0) {
machine.getAvailability().addAll(usedSegments);
}
return setupDetail;
}
private ScheduleResultDetail createScheduleDetail(TimeSegment slot, LocalDateTime startTime,
LocalDateTime endTime, int processable,
double oneTime, double quantity,
boolean useWholeQuantity) {
ScheduleResultDetail time = new ScheduleResultDetail();
time.setKey(slot.getKey());
time.setStartTime((int) ChronoUnit.SECONDS.between(baseTime, startTime));
time.setEndTime((int) ChronoUnit.SECONDS.between(baseTime, endTime));
time.setEfficiency(slot.getEfficiency());
time.setOneTime(oneTime);
if (useWholeQuantity) {
time.setQuantity(quantity);
} else if (oneTime > 0) {
BigDecimal bd = new BigDecimal(String.valueOf(processable / oneTime));
time.setQuantity(bd.setScale(4, RoundingMode.HALF_UP).doubleValue());
}
return time;
}
private String buildInsufficientScheduleMessage(Machine machine, Entry operation,
int requiredSeconds, double availableSeconds, int segmentCount) {
String machineId = machine == null ? "null" : String.valueOf(machine.getId());
String machineName = machine == null ? "" : String.valueOf(machine.getName());
String operationId = operation == null ? "null" : String.valueOf(operation.getId());
String groupId = operation == null ? "null" : String.valueOf(operation.getGroupId());
return String.format("设备可用时间不足,machineId=%s, machineName=%s, operationId=%s, groupId=%s, requiredSeconds=%d, availableSeconds=%.0f, segmentCount=%d",
machineId, machineName, operationId, groupId, requiredSeconds, availableSeconds, segmentCount);
}
// ==================== 倒排换型排程辅助方法结束 ====================
public Map<Integer,Object> CreateScheduleResult(
Machine machine,Entry operation, int processingTime, int proposedStartTime,CopyOnWriteArrayList<TimeSegment> timeSegments,
double oneTime,double quantity
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment