| | |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | |
| | | import com.zy.asrs.domain.path.StationPathProfileConfig; |
| | | import com.zy.asrs.domain.path.StationPathResolvedPolicy; |
| | | import com.zy.asrs.domain.path.StationPathRuleConfig; |
| | | import com.zy.asrs.domain.vo.StationCycleCapacityVo; |
| | | import com.zy.asrs.domain.vo.StationCycleLoopVo; |
| | | import com.zy.asrs.entity.BasDevp; |
| | | import com.zy.asrs.entity.BasStation; |
| | | import com.zy.asrs.service.BasDevpService; |
| | | import com.zy.asrs.service.BasStationService; |
| | | import com.zy.asrs.service.StationCycleCapacityService; |
| | | import com.zy.asrs.service.StationPathPolicyService; |
| | | import com.zy.core.News; |
| | | import com.zy.core.model.StationObjModel; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | |
| | | |
| | | @Autowired |
| | | private BasStationService basStationService; |
| | | @Autowired |
| | | private StationPathPolicyService stationPathPolicyService; |
| | | @Autowired |
| | | private StationCycleCapacityService stationCycleCapacityService; |
| | | |
| | | public synchronized List<NavigateNode> calcByStationId(Integer startStationId, Integer endStationId) { |
| | | BasStation startStation = basStationService.getById(startStationId); |
| | |
| | | throw new CoolException("未找到该 终点 对应的节点"); |
| | | } |
| | | |
| | | StationPathResolvedPolicy resolvedPolicy = resolveStationPathPolicy(startStationId, endStationId); |
| | | StationPathProfileConfig profileConfig = resolvedPolicy.getProfileConfig() == null |
| | | ? StationPathProfileConfig.defaultConfig() |
| | | : resolvedPolicy.getProfileConfig(); |
| | | |
| | | long startTime = System.currentTimeMillis(); |
| | | News.info("[WCS Debug] 站点路径开始计算,startStationId={},endStationId={}", startStationId, endStationId); |
| | | List<List<NavigateNode>> allList = navigateSolution.allSimplePaths(stationMap, startNode, endNode, 120, 500, 300); |
| | | int calcMaxDepth = resolvedPolicy.useTwoStage() ? safeInt(profileConfig.getCalcMaxDepth(), 120) : 120; |
| | | int calcMaxPaths = resolvedPolicy.useTwoStage() ? safeInt(profileConfig.getCalcMaxPaths(), 500) : 500; |
| | | int calcMaxCost = resolvedPolicy.useTwoStage() ? safeInt(profileConfig.getCalcMaxCost(), 300) : 300; |
| | | List<List<NavigateNode>> allList = navigateSolution.allSimplePaths(stationMap, startNode, endNode, calcMaxDepth, calcMaxPaths, calcMaxCost); |
| | | if (allList.isEmpty()) { |
| | | // throw new CoolException("未找到该路径"); |
| | | return new ArrayList<>(); |
| | |
| | | |
| | | startTime = System.currentTimeMillis(); |
| | | News.info("[WCS Debug] 站点路径权重开始分析,startStationId={},endStationId={}", startStationId, endStationId); |
| | | List<NavigateNode> list = findStationBestPath(allList); |
| | | List<NavigateNode> list = resolvedPolicy.useTwoStage() |
| | | ? findStationBestPathTwoStage(allList, resolvedPolicy) |
| | | : findStationBestPath(allList); |
| | | News.info("[WCS Debug] 站点路径权重分析完成,耗时:{}ms", System.currentTimeMillis() - startTime); |
| | | |
| | | //去重 |
| | |
| | | return liftStationList; |
| | | } |
| | | |
| | | private StationPathResolvedPolicy resolveStationPathPolicy(Integer startStationId, Integer endStationId) { |
| | | try { |
| | | if (stationPathPolicyService != null) { |
| | | StationPathResolvedPolicy resolved = stationPathPolicyService.resolvePolicy(startStationId, endStationId); |
| | | if (resolved != null) { |
| | | return resolved; |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | News.warn("站点路径策略加载失败,回退 legacy: {}", e.getMessage()); |
| | | } |
| | | return new StationPathResolvedPolicy(); |
| | | } |
| | | |
| | | private List<NavigateNode> findStationBestPathTwoStage(List<List<NavigateNode>> allList, StationPathResolvedPolicy resolvedPolicy) { |
| | | if (allList == null || allList.isEmpty()) { |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | StationPathRuleConfig ruleConfig = resolvedPolicy.getRuleConfig() == null |
| | | ? new StationPathRuleConfig() |
| | | : resolvedPolicy.getRuleConfig(); |
| | | StationPathProfileConfig profileConfig = resolvedPolicy.getProfileConfig() == null |
| | | ? StationPathProfileConfig.defaultConfig() |
| | | : resolvedPolicy.getProfileConfig(); |
| | | |
| | | List<List<NavigateNode>> filteredCandidates = applyRuleFilters(allList, ruleConfig, true); |
| | | if (filteredCandidates.isEmpty() && hasWaypoint(ruleConfig) && !strictWaypoint(ruleConfig)) { |
| | | filteredCandidates = applyRuleFilters(allList, ruleConfig, false); |
| | | News.info("[WCS Debug] 站点路径规则已降级,忽略关键途经点约束后重试"); |
| | | } |
| | | if (filteredCandidates.isEmpty()) { |
| | | if (resolvedPolicy.matchedRule()) { |
| | | News.warn("站点路径规则命中但无可行路径,ruleCode={}", |
| | | resolvedPolicy.getRuleEntity() == null ? "" : resolvedPolicy.getRuleEntity().getRuleCode()); |
| | | return new ArrayList<>(); |
| | | } |
| | | filteredCandidates = allList; |
| | | } |
| | | |
| | | Map<Integer, StationProtocol> statusMap = loadStationStatusMap(); |
| | | Map<Integer, Double> stationLoopLoadMap = loadStationLoopLoadMap(); |
| | | List<PathCandidateMetrics> metricsList = new ArrayList<>(); |
| | | for (List<NavigateNode> path : filteredCandidates) { |
| | | if (path == null || path.isEmpty()) { |
| | | continue; |
| | | } |
| | | metricsList.add(buildCandidateMetrics(path, statusMap, stationLoopLoadMap, profileConfig, ruleConfig)); |
| | | } |
| | | if (metricsList.isEmpty()) { |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | metricsList.sort((a, b) -> compareDouble(a.staticCost, b.staticCost, a.turnCount, b.turnCount, a.pathLen, b.pathLen)); |
| | | PathCandidateMetrics preferred = metricsList.get(0); |
| | | int maxLen = (int) Math.ceil(preferred.pathLen * safeDouble(profileConfig.getS1MaxLenRatio(), 1.15d)); |
| | | int maxTurns = preferred.turnCount + safeInt(profileConfig.getS1MaxTurnDiff(), 1); |
| | | |
| | | List<PathCandidateMetrics> stage1Selected = new ArrayList<>(); |
| | | for (PathCandidateMetrics metrics : metricsList) { |
| | | if (metrics.pathLen <= maxLen && metrics.turnCount <= maxTurns) { |
| | | stage1Selected.add(metrics); |
| | | } |
| | | } |
| | | if (stage1Selected.isEmpty()) { |
| | | stage1Selected.addAll(metricsList); |
| | | } |
| | | |
| | | int topK = safeInt(profileConfig.getS1TopK(), 5); |
| | | if (topK > 0 && stage1Selected.size() > topK) { |
| | | stage1Selected = new ArrayList<>(stage1Selected.subList(0, topK)); |
| | | } |
| | | |
| | | stage1Selected.sort((a, b) -> compareDouble(a.dynamicCost, b.dynamicCost, a.pathLen, b.pathLen, a.turnCount, b.turnCount)); |
| | | return stage1Selected.get(0).path; |
| | | } |
| | | |
| | | private List<List<NavigateNode>> applyRuleFilters(List<List<NavigateNode>> allList, |
| | | StationPathRuleConfig ruleConfig, |
| | | boolean includeWaypoint) { |
| | | if (allList == null || allList.isEmpty()) { |
| | | return new ArrayList<>(); |
| | | } |
| | | if (ruleConfig == null) { |
| | | return allList; |
| | | } |
| | | |
| | | List<List<NavigateNode>> result = new ArrayList<>(); |
| | | for (List<NavigateNode> path : allList) { |
| | | if (path == null || path.isEmpty()) { |
| | | continue; |
| | | } |
| | | List<Integer> stationIdList = extractStationIdList(path); |
| | | if (!matchHardConstraint(stationIdList, ruleConfig.getHard())) { |
| | | continue; |
| | | } |
| | | if (includeWaypoint && !matchWaypointConstraint(stationIdList, ruleConfig.getWaypoint())) { |
| | | continue; |
| | | } |
| | | result.add(path); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private boolean matchHardConstraint(List<Integer> stationIdList, StationPathRuleConfig.HardConstraint hard) { |
| | | if (stationIdList == null) { |
| | | return false; |
| | | } |
| | | if (hard == null) { |
| | | return true; |
| | | } |
| | | Set<Integer> stationIdSet = new HashSet<>(stationIdList); |
| | | for (Integer stationId : safeList(hard.getMustPassStations())) { |
| | | if (stationId != null && !stationIdSet.contains(stationId)) { |
| | | return false; |
| | | } |
| | | } |
| | | for (Integer stationId : safeList(hard.getForbidStations())) { |
| | | if (stationId != null && stationIdSet.contains(stationId)) { |
| | | return false; |
| | | } |
| | | } |
| | | for (String edge : safeList(hard.getMustPassEdges())) { |
| | | if (notBlank(edge) && !containsEdge(stationIdList, edge)) { |
| | | return false; |
| | | } |
| | | } |
| | | for (String edge : safeList(hard.getForbidEdges())) { |
| | | if (notBlank(edge) && containsEdge(stationIdList, edge)) { |
| | | return false; |
| | | } |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | private boolean matchWaypointConstraint(List<Integer> stationIdList, StationPathRuleConfig.WaypointConstraint waypoint) { |
| | | if (waypoint == null || waypoint.getStations() == null || waypoint.getStations().isEmpty()) { |
| | | return true; |
| | | } |
| | | int cursor = 0; |
| | | for (Integer stationId : stationIdList) { |
| | | Integer expected = waypoint.getStations().get(cursor); |
| | | if (expected != null && expected.equals(stationId)) { |
| | | cursor++; |
| | | if (cursor >= waypoint.getStations().size()) { |
| | | return true; |
| | | } |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private boolean hasWaypoint(StationPathRuleConfig ruleConfig) { |
| | | return ruleConfig != null |
| | | && ruleConfig.getWaypoint() != null |
| | | && ruleConfig.getWaypoint().getStations() != null |
| | | && !ruleConfig.getWaypoint().getStations().isEmpty(); |
| | | } |
| | | |
| | | private boolean strictWaypoint(StationPathRuleConfig ruleConfig) { |
| | | return ruleConfig != null |
| | | && ruleConfig.getFallback() != null |
| | | && Boolean.TRUE.equals(ruleConfig.getFallback().getStrictWaypoint()); |
| | | } |
| | | |
| | | private boolean containsEdge(List<Integer> stationIdList, String edgeText) { |
| | | int[] edge = parseEdge(edgeText); |
| | | if (edge == null) { |
| | | return false; |
| | | } |
| | | for (int i = 0; i < stationIdList.size() - 1; i++) { |
| | | Integer current = stationIdList.get(i); |
| | | Integer next = stationIdList.get(i + 1); |
| | | if (current != null && next != null && current == edge[0] && next == edge[1]) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private int[] parseEdge(String edgeText) { |
| | | if (!notBlank(edgeText)) { |
| | | return null; |
| | | } |
| | | String normalized = edgeText.replace(" ", ""); |
| | | String[] parts = normalized.split("->"); |
| | | if (parts.length != 2) { |
| | | return null; |
| | | } |
| | | try { |
| | | return new int[]{Integer.parseInt(parts[0]), Integer.parseInt(parts[1])}; |
| | | } catch (Exception ignore) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private PathCandidateMetrics buildCandidateMetrics(List<NavigateNode> path, |
| | | Map<Integer, StationProtocol> statusMap, |
| | | Map<Integer, Double> stationLoopLoadMap, |
| | | StationPathProfileConfig profileConfig, |
| | | StationPathRuleConfig ruleConfig) { |
| | | PathCandidateMetrics metrics = new PathCandidateMetrics(); |
| | | metrics.path = path; |
| | | metrics.pathLen = path.size(); |
| | | metrics.turnCount = countTurnCount(path); |
| | | metrics.liftTransferCount = countLiftTransferCount(path); |
| | | |
| | | List<Integer> stationIdList = extractStationIdList(path); |
| | | metrics.busyStationCount = countBusyStationCount(stationIdList, statusMap); |
| | | metrics.runBlockCount = countRunBlockCount(stationIdList, statusMap); |
| | | metrics.loopPenalty = calcLoopPenalty(stationIdList, stationLoopLoadMap); |
| | | metrics.softDeviationCount = calcSoftDeviationCount(stationIdList, |
| | | ruleConfig == null || ruleConfig.getSoft() == null ? null : ruleConfig.getSoft().getPreferredPath()); |
| | | |
| | | double softDeviationWeight = safeDouble(profileConfig.getS1SoftDeviationWeight(), 4.0d); |
| | | if (ruleConfig != null && ruleConfig.getSoft() != null && ruleConfig.getSoft().getDeviationWeight() != null) { |
| | | softDeviationWeight = ruleConfig.getSoft().getDeviationWeight(); |
| | | } |
| | | |
| | | metrics.staticCost = |
| | | safeDouble(profileConfig.getS1LenWeight(), 1.0d) * metrics.pathLen |
| | | + safeDouble(profileConfig.getS1TurnWeight(), 3.0d) * metrics.turnCount |
| | | + safeDouble(profileConfig.getS1LiftWeight(), 8.0d) * metrics.liftTransferCount |
| | | + softDeviationWeight * metrics.softDeviationCount; |
| | | |
| | | metrics.dynamicCost = |
| | | safeDouble(profileConfig.getS2BusyWeight(), 2.0d) * metrics.busyStationCount |
| | | + safeDouble(profileConfig.getS2RunBlockWeight(), 10.0d) * metrics.runBlockCount |
| | | + safeDouble(profileConfig.getS2LoopLoadWeight(), 12.0d) * metrics.loopPenalty; |
| | | return metrics; |
| | | } |
| | | |
| | | private int countTurnCount(List<NavigateNode> path) { |
| | | if (path == null || path.size() < 3) { |
| | | return 0; |
| | | } |
| | | int count = 0; |
| | | for (int i = 1; i < path.size() - 1; i++) { |
| | | NavigateNode prev = path.get(i - 1); |
| | | NavigateNode next = path.get(i + 1); |
| | | if (prev == null || next == null) { |
| | | continue; |
| | | } |
| | | if (prev.getX() != next.getX() && prev.getY() != next.getY()) { |
| | | count++; |
| | | } |
| | | } |
| | | return count; |
| | | } |
| | | |
| | | private int countLiftTransferCount(List<NavigateNode> path) { |
| | | int count = 0; |
| | | for (NavigateNode node : safeList(path)) { |
| | | try { |
| | | JSONObject valueObject = JSON.parseObject(node.getNodeValue()); |
| | | if (valueObject == null) { |
| | | continue; |
| | | } |
| | | Object isLiftTransfer = valueObject.get("isLiftTransfer"); |
| | | if (isLiftTransfer != null) { |
| | | String text = String.valueOf(isLiftTransfer); |
| | | if ("1".equals(text) || "true".equalsIgnoreCase(text)) { |
| | | count++; |
| | | } |
| | | } |
| | | } catch (Exception ignore) { |
| | | } |
| | | } |
| | | return count; |
| | | } |
| | | |
| | | private int countBusyStationCount(List<Integer> stationIdList, Map<Integer, StationProtocol> statusMap) { |
| | | int count = 0; |
| | | for (Integer stationId : stationIdList) { |
| | | StationProtocol protocol = statusMap.get(stationId); |
| | | if (protocol != null && protocol.getTaskNo() != null && protocol.getTaskNo() > 0) { |
| | | count++; |
| | | } |
| | | } |
| | | return count; |
| | | } |
| | | |
| | | private int countRunBlockCount(List<Integer> stationIdList, Map<Integer, StationProtocol> statusMap) { |
| | | int count = 0; |
| | | for (Integer stationId : stationIdList) { |
| | | StationProtocol protocol = statusMap.get(stationId); |
| | | if (protocol != null && protocol.isRunBlock()) { |
| | | count++; |
| | | } |
| | | } |
| | | return count; |
| | | } |
| | | |
| | | private double calcLoopPenalty(List<Integer> stationIdList, Map<Integer, Double> stationLoopLoadMap) { |
| | | double maxLoad = 0.0d; |
| | | for (Integer stationId : stationIdList) { |
| | | Double load = stationLoopLoadMap.get(stationId); |
| | | if (load != null && load > maxLoad) { |
| | | maxLoad = load; |
| | | } |
| | | } |
| | | return maxLoad; |
| | | } |
| | | |
| | | private int calcSoftDeviationCount(List<Integer> stationIdList, List<Integer> preferredPath) { |
| | | if (preferredPath == null || preferredPath.isEmpty() || stationIdList == null || stationIdList.isEmpty()) { |
| | | return 0; |
| | | } |
| | | Set<Integer> preferredSet = new HashSet<>(preferredPath); |
| | | int count = 0; |
| | | for (int i = 1; i < stationIdList.size() - 1; i++) { |
| | | Integer stationId = stationIdList.get(i); |
| | | if (stationId != null && !preferredSet.contains(stationId)) { |
| | | count++; |
| | | } |
| | | } |
| | | return count; |
| | | } |
| | | |
| | | private List<Integer> extractStationIdList(List<NavigateNode> path) { |
| | | List<Integer> stationIdList = new ArrayList<>(); |
| | | Set<Integer> seen = new HashSet<>(); |
| | | for (NavigateNode node : safeList(path)) { |
| | | Integer stationId = extractStationId(node); |
| | | if (stationId == null) { |
| | | continue; |
| | | } |
| | | if (seen.add(stationId)) { |
| | | stationIdList.add(stationId); |
| | | } |
| | | } |
| | | return stationIdList; |
| | | } |
| | | |
| | | private Map<Integer, StationProtocol> loadStationStatusMap() { |
| | | Map<Integer, StationProtocol> statusMap = new HashMap<>(); |
| | | try { |
| | | DeviceConfigService deviceConfigService = SpringUtils.getBean(DeviceConfigService.class); |
| | | if (deviceConfigService == null) { |
| | | return statusMap; |
| | | } |
| | | List<DeviceConfig> devpList = deviceConfigService.list(new QueryWrapper<DeviceConfig>() |
| | | .eq("device_type", String.valueOf(SlaveType.Devp))); |
| | | for (DeviceConfig deviceConfig : devpList) { |
| | | StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, deviceConfig.getDeviceNo()); |
| | | if (stationThread == null) { |
| | | continue; |
| | | } |
| | | Map<Integer, StationProtocol> map = stationThread.getStatusMap(); |
| | | if (map != null && !map.isEmpty()) { |
| | | statusMap.putAll(map); |
| | | } |
| | | } |
| | | } catch (Exception ignore) { |
| | | } |
| | | return statusMap; |
| | | } |
| | | |
| | | private Map<Integer, Double> loadStationLoopLoadMap() { |
| | | Map<Integer, Double> stationLoopLoadMap = new HashMap<>(); |
| | | try { |
| | | if (stationCycleCapacityService == null) { |
| | | return stationLoopLoadMap; |
| | | } |
| | | StationCycleCapacityVo capacityVo = stationCycleCapacityService.getLatestSnapshot(); |
| | | if (capacityVo == null || capacityVo.getLoopList() == null) { |
| | | return stationLoopLoadMap; |
| | | } |
| | | for (StationCycleLoopVo loopVo : capacityVo.getLoopList()) { |
| | | if (loopVo == null || loopVo.getStationIdList() == null) { |
| | | continue; |
| | | } |
| | | double currentLoad = loopVo.getCurrentLoad() == null ? 0.0d : loopVo.getCurrentLoad(); |
| | | for (Integer stationId : loopVo.getStationIdList()) { |
| | | if (stationId != null) { |
| | | stationLoopLoadMap.put(stationId, currentLoad); |
| | | } |
| | | } |
| | | } |
| | | } catch (Exception ignore) { |
| | | } |
| | | return stationLoopLoadMap; |
| | | } |
| | | |
| | | private int compareDouble(double left, double right, int thenLeft1, int thenRight1, int thenLeft2, int thenRight2) { |
| | | int result = Double.compare(left, right); |
| | | if (result != 0) { |
| | | return result; |
| | | } |
| | | result = Integer.compare(thenLeft1, thenRight1); |
| | | if (result != 0) { |
| | | return result; |
| | | } |
| | | return Integer.compare(thenLeft2, thenRight2); |
| | | } |
| | | |
| | | private int safeInt(Integer value, int defaultValue) { |
| | | return value == null ? defaultValue : value; |
| | | } |
| | | |
| | | private double safeDouble(Double value, double defaultValue) { |
| | | return value == null ? defaultValue : value; |
| | | } |
| | | |
| | | private boolean notBlank(String text) { |
| | | return text != null && !text.trim().isEmpty(); |
| | | } |
| | | |
| | | private <T> List<T> safeList(List<T> list) { |
| | | return list == null ? Collections.emptyList() : list; |
| | | } |
| | | |
| | | private static class PathCandidateMetrics { |
| | | private List<NavigateNode> path; |
| | | private int pathLen; |
| | | private int turnCount; |
| | | private int liftTransferCount; |
| | | private int busyStationCount; |
| | | private int runBlockCount; |
| | | private int softDeviationCount; |
| | | private double loopPenalty; |
| | | private double staticCost; |
| | | private double dynamicCost; |
| | | } |
| | | |
| | | public synchronized List<NavigateNode> findStationBestPath(List<List<NavigateNode>> allList) { |
| | | if (allList == null || allList.isEmpty()) { |
| | | return new ArrayList<>(); |