Commit e9575d03 by chamberone

feat: 提交过程打印逻辑,优化实体类,添加时间控制参数

1 parent 5022be8c
......@@ -13,11 +13,8 @@ public class DispatchConstraintProvider implements ConstraintProvider {
@Override
public Constraint[] defineConstraints(ConstraintFactory factory) {
return new Constraint[] { greaterThanZero(factory), customerTimeWindowsMatch1(factory),
customerTimeWindowsMatch2(factory),
skillMatch(factory),
technicianBalance(factory),
technicianBalance2(factory),
totalDistance(factory),
customerTimeWindowsMatch2(factory), skillMatch(factory), technicianBalance(factory),
technicianBalance2(factory), technicianBalanceSoft(factory), totalDistance(factory),
preferredTotalDistance(factory) };
}
......@@ -30,7 +27,7 @@ public class DispatchConstraintProvider implements ConstraintProvider {
// 4,技术员技能跟订单相匹配skillMatch
// 5,订单均分technicianBalance
protected Constraint greaterThanZero(ConstraintFactory factory) {
public Constraint greaterThanZero(ConstraintFactory factory) {
return factory.forEach(Technician.class).filter(technician -> technician.getCustomerList().size() == 0)
.penalizeLong(HardSoftLongScore.ONE_HARD, technician -> 1).asConstraint("每个技术员至少分配一个单子");
}
......@@ -42,6 +39,7 @@ public class DispatchConstraintProvider implements ConstraintProvider {
}
protected Constraint customerTimeWindowsMatch2(ConstraintFactory factory) {
// 迟到2小时惩罚
return factory.forEach(Customer.class).filter(
customer -> customer.getTechnician() != null && customer.getArrivalTime() > customer.getEndTime() + 120)
.penalizeLong(HardSoftLongScore.ONE_HARD, customer -> 1).asConstraint("技术员到达时间跟时间窗吻合2");
......@@ -51,7 +49,12 @@ public class DispatchConstraintProvider implements ConstraintProvider {
return factory.forEach(Customer.class)
.filter(customer -> customer.getTechnician() != null
&& !customer.getTechnician().getSkills().contains(customer.getRequiredSkill()))
.penalizeLong(HardSoftLongScore.ONE_HARD, customer -> 1).asConstraint("技术员技能跟订单相匹配skillMatch");
.penalizeLong(HardSoftLongScore.ONE_HARD,
// 技能匹配跟时间窗匹配存在很明显的跷跷板效应,权重小于3就会存在技能匹配问题
// 3-技能匹配问题1个,时间窗问题8个
// 4-技能匹配问题0个,时间窗问题14个
customer -> 4)
.asConstraint("技术员技能跟订单相匹配skillMatch");
}
protected Constraint technicianBalance(ConstraintFactory factory) {
......@@ -65,9 +68,7 @@ public class DispatchConstraintProvider implements ConstraintProvider {
protected Constraint technicianBalance2(ConstraintFactory factory) {
// 单量不能过少 FIXME
return factory.forEach(Technician.class).filter(technician -> technician.getOffWorkTime() <= 960)
.penalizeLong(HardSoftLongScore.ONE_HARD,
technician -> 1)
.asConstraint("订单均分2");
.penalizeLong(HardSoftLongScore.ONE_HARD, technician -> 1).asConstraint("订单均分2");
}
// protected Constraint technicianBalance(ConstraintFactory factory) {
......@@ -92,6 +93,7 @@ public class DispatchConstraintProvider implements ConstraintProvider {
// ************************************************************************
// 1, 总路程最小 totalDistance
// 2, 技术员中心点偏好 preferredTotalDistance
// 3, 订单数量均衡 technicianBalanceSoft
protected Constraint totalDistance(ConstraintFactory factory) {
return factory.forEach(Technician.class)
......@@ -104,4 +106,10 @@ public class DispatchConstraintProvider implements ConstraintProvider {
.asConstraint("技术员中心点偏好");
}
protected Constraint technicianBalanceSoft(ConstraintFactory factory) {
return factory.forEachUniquePair(Technician.class).penalizeLong(HardSoftLongScore.ONE_SOFT,
// 权重需要调节,差距一个相当于多一公里 FIXME 这里应该是时长均衡,不是订单量均衡
(a, b) -> Math.abs(a.getCustomerSize() - b.getCustomerSize()) * 1000).asConstraint("订单均分soft");
}
}
......@@ -19,6 +19,7 @@ package com.dituhui.pea.dispatch.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.dituhui.pea.common.Result;
......@@ -35,9 +36,11 @@ public class DispatchController {
private DispatchService dispatchService;
@GetMapping("/manual")
public Result<?> manualDispatch() {
public Result<?> manualDispatch(
@RequestParam(value = "unimprovedSecondsSpentLimit", required = false) long unimprovedSecondsSpentLimit,
@RequestParam(value = "secondsSpentLimit", required = false) long secondsSpentLimit) {
try {
return dispatchService.manualDispatch();
return dispatchService.manualDispatch(unimprovedSecondsSpentLimit, secondsSpentLimit);
} catch (Exception e) {
return Result.failed(e.getMessage());
}
......
package com.dituhui.pea.dispatch.pojo;
import org.optaplanner.core.api.domain.entity.PlanningEntity;
import org.optaplanner.core.api.domain.variable.InverseRelationShadowVariable;
import org.optaplanner.core.api.domain.variable.ShadowVariable;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.optaplanner.core.api.domain.variable.InverseRelationShadowVariable;
import lombok.Getter;
import lombok.Setter;
/**
* 订单
......@@ -14,7 +15,8 @@ import org.optaplanner.core.api.domain.variable.InverseRelationShadowVariable;
* @author gpzhang
*
*/
@Data
@Setter
@Getter
@PlanningEntity
public class Customer {
......@@ -44,7 +46,8 @@ public class Customer {
public Customer() {
}
public Customer(long id, String code, Location location, int startTime, int endTime, String requiredSkill, int serviceDuration) {
public Customer(long id, String code, Location location, int startTime, int endTime, String requiredSkill,
int serviceDuration) {
this.id = id;
this.code = code;
this.location = location;
......@@ -145,4 +148,25 @@ public class Customer {
return previousCustomer.getLocation().getPathTimeTo(location);
}
@Override
public int hashCode() {
return Long.valueOf(this.id).hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (!(obj instanceof Customer))
return false;
if (obj == this)
return true;
return this.id == ((Customer) obj).getId();
}
@Override
public String toString() {
return "Customer{" + "id=" + id + '}';
}
}
......@@ -55,6 +55,21 @@ public class Location {
// Complex methods
// ************************************************************************
@Override
public int hashCode() {
return Long.valueOf(this.id).hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (!(obj instanceof Location))
return false;
if (obj == this)
return true;
return this.id == ((Location) obj).getId();
}
@Override
public String toString() {
......
......@@ -13,9 +13,11 @@ import org.optaplanner.core.api.domain.variable.PlanningListVariable;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
@Data
@Setter
@Getter
@PlanningEntity
public class Technician {
......@@ -150,4 +152,27 @@ public class Technician {
}
}
@Override
public int hashCode() {
return Long.valueOf(this.id).hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (!(obj instanceof Technician))
return false;
if (obj == this)
return true;
return this.id == ((Technician) obj).getId();
}
@Override
public String toString() {
return "Technician{" + "id=" + id + '}';
}
}
......@@ -26,6 +26,7 @@ import com.dituhui.pea.common.Result;
*/
public interface DispatchService {
Result<?> manualDispatch() throws UncheckedIOException, IOException;
Result<?> manualDispatch(long unimprovedSecondsSpentLimit, long secondsSpentLimit)
throws UncheckedIOException, IOException;
}
......@@ -16,10 +16,10 @@
package com.dituhui.pea.dispatch.service.impl;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.UncheckedIOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
......@@ -33,21 +33,28 @@ import java.util.stream.Collectors;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.StringUtils;
import org.optaplanner.core.api.score.ScoreExplanation;
import org.optaplanner.core.api.score.buildin.hardsoftlong.HardSoftLongScore;
import org.optaplanner.core.api.solver.SolutionManager;
import org.optaplanner.core.api.solver.Solver;
import org.optaplanner.core.api.solver.SolverFactory;
import org.optaplanner.core.api.solver.event.BestSolutionChangedEvent;
import org.optaplanner.core.api.solver.event.SolverEventListener;
import org.optaplanner.core.config.solver.SolverConfig;
import org.optaplanner.core.config.solver.termination.TerminationConfig;
import org.optaplanner.persistence.jackson.impl.domain.solution.JacksonSolutionFileIO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import com.dituhui.pea.dispatch.constraint.DispatchConstraintProvider;
import com.dituhui.pea.dispatch.pojo.DispatchSolution;
import com.dituhui.pea.common.Result;
import com.dituhui.pea.dispatch.service.DispatchService;
import com.dituhui.pea.dispatch.constraint.DispatchConstraintProvider;
import com.dituhui.pea.dispatch.pojo.Customer;
import com.dituhui.pea.dispatch.pojo.Depot;
import com.dituhui.pea.dispatch.pojo.DispatchSolution;
import com.dituhui.pea.dispatch.pojo.Location;
import com.dituhui.pea.dispatch.pojo.Technician;
import com.dituhui.pea.dispatch.service.DispatchService;
/**
* @author gpzhang
......@@ -58,7 +65,7 @@ public class DispatchServiceImpl implements DispatchService {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public Result<?> manualDispatch() throws UncheckedIOException, FileNotFoundException {
public Result<?> manualDispatch(long unimprovedSecondsSpentLimit, long secondsSpentLimit) throws UncheckedIOException, FileNotFoundException {
logger.info("{}", "invoke manualDispatch");
// 创建解决方案对象
......@@ -66,17 +73,25 @@ public class DispatchServiceImpl implements DispatchService {
Map<Integer, String> technicianIndexMap = loadTechnicianIndex();
Map<String, Set<String>> technicianCodeSkillsMap = loadTechnicianCodeSkillsMap();
Map<String, String> customerCodeSkillMap = loadCustomerCodeSkillMap();
Map<String, Map<String, Long>> preferredlocationDistanceMap =loadPreferredlocationDistanceMap();
Map<String, Map<String, Long>> preferredlocationDistanceMap = loadPreferredlocationDistanceMap();
Map<String, Integer> customerCodeServiceTimeMap = loadCustomerCodeServiceTimeMap();
DispatchSolution problem = createVehicleRoutingSolution(customerIndexMap, technicianIndexMap,
technicianCodeSkillsMap, customerCodeSkillMap, preferredlocationDistanceMap,customerCodeServiceTimeMap);
technicianCodeSkillsMap, customerCodeSkillMap, preferredlocationDistanceMap,
customerCodeServiceTimeMap);
// 创建求解器配置
// 创建 SolverConfig 对象,并设置求解器配置
SolverConfig solverConfig = new SolverConfig();
solverConfig.setSolutionClass(DispatchSolution.class);
solverConfig.withEntityClassList(Arrays.asList(Technician.class, Customer.class));// 这里不能漏掉,否则约束不生效
solverConfig.withTerminationSpentLimit(Duration.ofSeconds(60));
TerminationConfig terminationConfig = new TerminationConfig();
// 运行时长提升效果
// 5s/60s: 技能权重4-技能匹配问题0个,时间窗问题14个
// 10s/300s: 技能权重4-技能匹配问题0个,时间窗问题9个
terminationConfig
.setUnimprovedSecondsSpentLimit(unimprovedSecondsSpentLimit == 0 ? 5 : unimprovedSecondsSpentLimit);// XX秒没有找到更好方案
terminationConfig.setSecondsSpentLimit(secondsSpentLimit == 0 ? 60 : secondsSpentLimit);// 总时间不能超过XXs
solverConfig.withTerminationConfig(terminationConfig);
// 约束条件
solverConfig.withConstraintProviderClass(DispatchConstraintProvider.class);
......@@ -84,21 +99,44 @@ public class DispatchServiceImpl implements DispatchService {
// 创建求解器
SolverFactory<DispatchSolution> solverFactory = SolverFactory.create(solverConfig);
Solver<DispatchSolution> solver = solverFactory.buildSolver();
DispatchSolution solution = solver.solve(problem);
SolutionManager<DispatchSolution, HardSoftLongScore> scoreManager = SolutionManager.create(solverFactory);
solver.addEventListener(new SolverEventListener<DispatchSolution>() {
public void bestSolutionChanged(BestSolutionChangedEvent<DispatchSolution> event) {
// System.out.printf("found better score:%s at time:%ss %n", event.getNewBestScore().toShortString(),
// event.getTimeMillisSpent() / 1000);
System.out.printf("%s,%s%n", event.getNewBestScore().toLevelDoubles()[0],
event.getTimeMillisSpent() / 1000F);
}
});
DispatchSolution solution = solver.solve(problem);
printSolution(solution, customerIndexMap, technicianIndexMap);
System.out.println("hardScore: " + solution.getScore().hardScore());
System.out.println("softScore: " + solution.getScore().softScore());
System.out.println("final Score: " + solution.getScore().toShortString());
// Create a JacksonSolutionFileIO instance.
JacksonSolutionFileIO<DispatchSolution> exporter = new JacksonSolutionFileIO<DispatchSolution>(
DispatchSolution.class);
// Set the output file.
exporter.write(solution, new File("dispatchSolution.json"));
// Obtain a ScoreExplanation object for the best solution
// Using score calculation outside the Solver
// https://www.optaplanner.org/docs/optaplanner/latest/score-calculation/score-calculation.html
ScoreExplanation<DispatchSolution, HardSoftLongScore> scoreExplanation = scoreManager.explain(solution);
System.out.println(scoreExplanation.getSummary());
// Map<String, ConstraintMatchTotal<HardSoftLongScore>> constraintMatchTotalMap = scoreExplanation
// .getConstraintMatchTotalMap();
// constraintMatchTotalMap.forEach((key, value) -> {
// System.out.println(key + ":" + value.getConstraintName() + ":" + value.getScore().toShortString());
// });
return Result.success(solution.getTechnicianList());
}
private static Map<String, Integer> loadCustomerCodeServiceTimeMap()
throws UncheckedIOException, FileNotFoundException {
List<String> customerServiceTime = IOUtils.readLines(new FileInputStream("data/customerServiceTime.csv"), "utf-8");
List<String> customerServiceTime = IOUtils.readLines(new FileInputStream("data/customerServiceTime.csv"),
"utf-8");
Map<String, Integer> customerCodeServiceTimeMap = new HashMap<String, Integer>();// code-time
for (int i = 0; i < customerServiceTime.size(); i++) {
String line = customerServiceTime.get(i);
......@@ -110,8 +148,8 @@ public class DispatchServiceImpl implements DispatchService {
private static Map<String, Map<String, Long>> loadPreferredlocationDistanceMap()
throws UncheckedIOException, FileNotFoundException {
List<String> technicianCodeLocation = IOUtils
.readLines(new FileInputStream("data/technicianLocation.csv"), "utf-8");
List<String> technicianCodeLocation = IOUtils.readLines(new FileInputStream("data/technicianLocation.csv"),
"utf-8");
Map<String, String> technicianCodeLocationMap = new HashMap<String, String>();// 序号-code
for (int i = 0; i < technicianCodeLocation.size(); i++) {
String line = technicianCodeLocation.get(i);
......@@ -119,8 +157,8 @@ public class DispatchServiceImpl implements DispatchService {
technicianCodeLocationMap.put(temps[0], temps[1] + "," + temps[2]);
}
List<String> customerCodeLocation = IOUtils
.readLines(new FileInputStream("data/customerLocation.csv"), "utf-8");
List<String> customerCodeLocation = IOUtils.readLines(new FileInputStream("data/customerLocation.csv"),
"utf-8");
Map<String, String> customerCodeLocationMap = new HashMap<String, String>();// 序号-code
for (int i = 0; i < customerCodeLocation.size(); i++) {
String line = customerCodeLocation.get(i);
......@@ -137,7 +175,7 @@ public class DispatchServiceImpl implements DispatchService {
long distance = (long) getDistance(Double.parseDouble(temps[1]), Double.parseDouble(temps[0]),
Double.parseDouble(temps2[1]), Double.parseDouble(temps2[0]));
Map<String, Long> customerMaps = customerTecnicianDistanceMap.get(technicianCode);
if(null == customerMaps) {
if (null == customerMaps) {
customerMaps = new HashMap<String, Long>();
customerTecnicianDistanceMap.put(technicianCode, customerMaps);
}
......@@ -218,7 +256,8 @@ public class DispatchServiceImpl implements DispatchService {
private static DispatchSolution createVehicleRoutingSolution(Map<Integer, String> customerIndexMap,
Map<Integer, String> technicianIndexMap, Map<String, Set<String>> technicianCodeSkillsMap,
Map<String, String> customerCodeSkillMap, Map<String, Map<String, Long>> preferredlocationDistanceMap, Map<String, Integer> customerCodeServiceTimeMap) throws UncheckedIOException, FileNotFoundException {
Map<String, String> customerCodeSkillMap, Map<String, Map<String, Long>> preferredlocationDistanceMap,
Map<String, Integer> customerCodeServiceTimeMap) throws UncheckedIOException, FileNotFoundException {
DispatchSolution vehicleRoutingSolution = new DispatchSolution();
// 翻转map
......@@ -351,14 +390,16 @@ public class DispatchServiceImpl implements DispatchService {
totalNum.addAndGet(technician.getCustomerList().size());
for (Customer customer : technician.getCustomerList()) {
Customer previousCustomer = customer.getPreviousCustomer();
int startPath, endPath;//路上时间
int startPath, endPath;// 路上时间
if (null == previousCustomer) {
startPath = technician.getDepot().getStartTime();
// endPath = startPath + customer.getLocation().getPathTimeTo(technician.getDepot().getLocation());
// endPath = startPath +
// customer.getLocation().getPathTimeTo(technician.getDepot().getLocation());
endPath = startPath + technician.getDepot().getLocation().getPathTimeTo(customer.getLocation());
} else {
startPath = previousCustomer.getDepartureTime();
// endPath = startPath + customer.getLocation().getPathTimeTo(previousCustomer.getLocation());
// endPath = startPath +
// customer.getLocation().getPathTimeTo(previousCustomer.getLocation());
endPath = startPath + previousCustomer.getLocation().getPathTimeTo(customer.getLocation());
}
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!