Commit 8c25ca0b by chamberone

feat: 代码重构,添加评分等工具

1 parent 430eddf7
package com.dituhui.pea.dispatch.constraint;
/**
* 约束枚举
*
* @author zhangguoping
*
*/
public enum ConstraintNameEnum {
// 硬约束
/**
* 技术员技能跟订单相匹配
*/
skillMatch,
/**
* 技术员到达时间跟时间窗吻合
*/
customerTimeWindowsMatch,
// 软约束
/**
* 订单数量均衡
*/
technicianBalanceSoft,
/**
* 总路程最小
*/
totalDistance,
/**
* 技术员中心点偏好
*/
preferredTotalDistance
}
...@@ -12,38 +12,53 @@ public class DispatchConstraintProvider implements ConstraintProvider { ...@@ -12,38 +12,53 @@ public class DispatchConstraintProvider implements ConstraintProvider {
@Override @Override
public Constraint[] defineConstraints(ConstraintFactory factory) { public Constraint[] defineConstraints(ConstraintFactory factory) {
return new Constraint[] { greaterThanZero(factory), customerTimeWindowsMatch1(factory), return new Constraint[] {
customerTimeWindowsMatch2(factory), skillMatch(factory), technicianBalance(factory), // 硬约束
technicianBalance2(factory), technicianBalanceSoft(factory), totalDistance(factory), // 运行时长提升效果
preferredTotalDistance(factory) }; // 5s/60s: 技能权重4-技能匹配问题0个,时间窗问题14个
// 10s/300s: 技能权重4-技能匹配问题0个,时间窗问题9个
customerTimeWindowsMatch(factory),
skillMatch(factory),
// 软约束
technicianBalanceSoft(factory),
totalDistance(factory),
preferredTotalDistance(factory)
};
} }
// ************************************************************************ // ************************************************************************
// Hard constraints // Hard constraints
// ************************************************************************ // ************************************************************************
// 1,每个技术员至少分配一个greaterThanZero // 1,技术员到达时间跟时间窗吻合customerTimeWindowsMatch
// 2,技术员到达时间跟时间窗吻合customerTimeWindowsMatch // 2,技术员时间窗吻合technicianTimeWindowsMatch
// 3,技术员时间窗吻合technicianTimeWindowsMatch // 3,技术员技能跟订单相匹配skillMatch
// 4,技术员技能跟订单相匹配skillMatch
// 5,订单均分technicianBalance
public Constraint greaterThanZero(ConstraintFactory factory) { public Constraint greaterThanZero(ConstraintFactory factory) {
return factory.forEach(Technician.class).filter(technician -> technician.getCustomerList().size() == 0) return factory.forEach(Technician.class).filter(technician -> technician.getCustomerList().size() == 0)
.penalizeLong(HardSoftLongScore.ONE_HARD, technician -> 1).asConstraint("每个技术员至少分配一个单子"); .penalizeLong(HardSoftLongScore.ONE_HARD, technician -> 1).asConstraint("每个技术员至少分配一个单子");
} }
protected Constraint customerTimeWindowsMatch1(ConstraintFactory factory) { protected Constraint customerTimeWindowsMatch(ConstraintFactory factory) {
return factory.forEach(Customer.class).filter( return factory.forEach(Customer.class).filter(
customer -> customer.getTechnician() != null && customer.getArrivalTime() > customer.getEndTime()) customer -> customer.getTechnician() != null && customer.getArrivalTime() > customer.getEndTime())
.penalizeLong(HardSoftLongScore.ONE_HARD, customer -> 1).asConstraint("技术员到达时间跟时间窗吻合1"); .penalizeLong(HardSoftLongScore.ONE_HARD,
// 迟到每2小时扣一分
customer -> (long) Math.ceil((customer.getArrivalTime() - customer.getEndTime()) / 120F))
.asConstraint(ConstraintNameEnum.customerTimeWindowsMatch.name());
} }
protected Constraint customerTimeWindowsMatch2(ConstraintFactory factory) { // protected Constraint customerTimeWindowsMatch1(ConstraintFactory factory) {
// 迟到2小时惩罚 // return factory.forEach(Customer.class).filter(
return factory.forEach(Customer.class).filter( // customer -> customer.getTechnician() != null && customer.getArrivalTime() > customer.getEndTime())
customer -> customer.getTechnician() != null && customer.getArrivalTime() > customer.getEndTime() + 120) // .penalizeLong(HardSoftLongScore.ONE_HARD, customer -> 1).asConstraint("技术员到达时间跟时间窗吻合1");
.penalizeLong(HardSoftLongScore.ONE_HARD, customer -> 1).asConstraint("技术员到达时间跟时间窗吻合2"); // }
} //
// 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");
// }
protected Constraint skillMatch(ConstraintFactory factory) { protected Constraint skillMatch(ConstraintFactory factory) {
return factory.forEach(Customer.class) return factory.forEach(Customer.class)
...@@ -54,38 +69,21 @@ public class DispatchConstraintProvider implements ConstraintProvider { ...@@ -54,38 +69,21 @@ public class DispatchConstraintProvider implements ConstraintProvider {
// 3-技能匹配问题1个,时间窗问题8个 // 3-技能匹配问题1个,时间窗问题8个
// 4-技能匹配问题0个,时间窗问题14个 // 4-技能匹配问题0个,时间窗问题14个
customer -> 4) customer -> 4)
.asConstraint("技术员技能跟订单相匹配skillMatch"); .asConstraint(ConstraintNameEnum.skillMatch.name());
}
protected Constraint technicianBalance(ConstraintFactory factory) {
// 会导致剩余单子集中?
return factory.forEach(Technician.class).filter(technician -> technician.getOffWorkTime() > 1140)
.penalizeLong(HardSoftLongScore.ONE_HARD,
technician -> (long) Math.ceil(technician.getOffWorkTime() / 1440f))
.asConstraint("订单均分");
}
protected Constraint technicianBalance2(ConstraintFactory factory) {
// 单量不能过少 FIXME
return factory.forEach(Technician.class).filter(technician -> technician.getOffWorkTime() <= 960)
.penalizeLong(HardSoftLongScore.ONE_HARD, technician -> 1).asConstraint("订单均分2");
} }
// protected Constraint technicianBalance(ConstraintFactory factory) { // protected Constraint technicianBalance(ConstraintFactory factory) {
// return factory.forEachUniquePair(Technician.class, Joiners.filtering( // // 会导致剩余单子集中?
// // 一个人的工作时长超过另外一个人的2倍 FIXME 无效 // return factory.forEach(Technician.class).filter(technician -> technician.getOffWorkTime() > 1140)
// (t1, t2) -> (t1.getWorkTime() > t2.getWorkTime() * 2) || (t2.getWorkTime() > t1.getWorkTime() * 2))) // .penalizeLong(HardSoftLongScore.ONE_HARD,
// .penalizeLong(HardSoftLongScore.ONE_HARD, (c1, c2) -> 1).asConstraint("订单均分"); // technician -> (long) Math.ceil(technician.getOffWorkTime() / 1440f))
// .asConstraint("订单均分");
// } // }
//
// protected Constraint technicianBalance(ConstraintFactory factory) { // protected Constraint technicianBalance2(ConstraintFactory factory) {
// return factory.forEachUniquePair(Technician.class, Joiners.filtering( // // 单量不能过少 FIXME
// // 一个人的数量是另外一个人的3倍 FIXME 无效 // return factory.forEach(Technician.class).filter(technician -> technician.getOffWorkTime() <= 960)
// (t1, t2) -> { // .penalizeLong(HardSoftLongScore.ONE_HARD, technician -> 1).asConstraint("订单均分2");
// int size1 = t1.getCustomerSize();
// int size2 = t2.getCustomerSize();
// return (size1 > size2 * 3) || (size2 > size1 * 3);
// })).penalizeLong(HardSoftLongScore.ONE_HARD, (c1, c2) -> 1).asConstraint("订单均分");
// } // }
// ************************************************************************ // ************************************************************************
...@@ -97,19 +95,21 @@ public class DispatchConstraintProvider implements ConstraintProvider { ...@@ -97,19 +95,21 @@ public class DispatchConstraintProvider implements ConstraintProvider {
protected Constraint totalDistance(ConstraintFactory factory) { protected Constraint totalDistance(ConstraintFactory factory) {
return factory.forEach(Technician.class) return factory.forEach(Technician.class)
.penalizeLong(HardSoftLongScore.ONE_SOFT, Technician::getTotalDistanceMeters).asConstraint("总路程最小"); .penalizeLong(HardSoftLongScore.ONE_SOFT, Technician::getTotalDistanceMeters)
.asConstraint(ConstraintNameEnum.totalDistance.name());
} }
protected Constraint preferredTotalDistance(ConstraintFactory factory) { protected Constraint preferredTotalDistance(ConstraintFactory factory) {
return factory.forEach(Technician.class) return factory.forEach(Technician.class)
.penalizeLong(HardSoftLongScore.ONE_SOFT, Technician::getPreferredTotalDistanceMeters) .penalizeLong(HardSoftLongScore.ONE_SOFT, Technician::getPreferredTotalDistanceMeters)
.asConstraint("技术员中心点偏好"); .asConstraint(ConstraintNameEnum.preferredTotalDistance.name());
} }
protected Constraint technicianBalanceSoft(ConstraintFactory factory) { protected Constraint technicianBalanceSoft(ConstraintFactory factory) {
return factory.forEachUniquePair(Technician.class).penalizeLong(HardSoftLongScore.ONE_SOFT, return factory.forEachUniquePair(Technician.class).penalizeLong(HardSoftLongScore.ONE_SOFT,
// 权重需要调节,差距一个相当于多一公里 FIXME 这里应该是时长均衡,不是订单量均衡 // 权重需要调节,差距一个相当于多一公里 FIXME 这里应该是时长均衡,不是订单量均衡
(a, b) -> Math.abs(a.getCustomerSize() - b.getCustomerSize()) * 1000).asConstraint("订单均分soft"); (a, b) -> Math.abs(a.getCustomerSize() - b.getCustomerSize()) * 1000)
.asConstraint(ConstraintNameEnum.technicianBalanceSoft.name());
} }
} }
package com.dituhui.pea.dispatch.eventListener;
import org.optaplanner.core.api.solver.event.BestSolutionChangedEvent;
import com.dituhui.pea.dispatch.pojo.DispatchSolution;
public class DispatchSolverEventListener implements org.optaplanner.core.api.solver.event.SolverEventListener<DispatchSolution> {
@Override
public void bestSolutionChanged(BestSolutionChangedEvent<DispatchSolution> event) {
// TODO Auto-generated method stub
}
}
...@@ -13,6 +13,11 @@ import lombok.Setter; ...@@ -13,6 +13,11 @@ import lombok.Setter;
public class Location { public class Location {
private final long id; private final long id;
// 坐标点
private float x;
private float y;
@JsonIgnore @JsonIgnore
private Map<Location, Long> distanceMap= new HashMap<Location, Long>();// 路网距离矩阵 private Map<Location, Long> distanceMap= new HashMap<Location, Long>();// 路网距离矩阵
@JsonIgnore @JsonIgnore
...@@ -21,6 +26,12 @@ public class Location { ...@@ -21,6 +26,12 @@ public class Location {
public Location(long id) { public Location(long id) {
this.id = id; this.id = id;
} }
public Location(long id, float x, float y) {
this.id = id;
this.x = x;
this.y = y;
}
/** /**
* Set the distance map. Distances are in meters. * Set the distance map. Distances are in meters.
......
...@@ -22,31 +22,31 @@ import lombok.Setter; ...@@ -22,31 +22,31 @@ import lombok.Setter;
public class Technician { public class Technician {
@PlanningId @PlanningId
private long id; private long id;
private String code; private String code;
@JsonIgnore @JsonIgnore
private Depot depot; private Depot depot;
// 上班时间窗 分钟480-1080 8-18点 // 上班时间窗 分钟480-1080 8-18点
private int startTime; private int startTime;
private int endTime; private int endTime;
// 技能 // 技能
private Set<String> skills; private Set<String> skills;
// 偏好坐标 // 偏好坐标
// private Location preferredlocation; private Location preferredlocation;
// teck code : customer code , distance // technician code : customer code , distance
@JsonIgnore @JsonIgnore
private Map<String, Long> preferredlocationDistanceMap = new HashMap<String, Long>(); private Map<String, Long> preferredlocationDistanceMap = new HashMap<String, Long>();
@PlanningListVariable @PlanningListVariable
private List<Customer> customerList = new ArrayList<>(); private List<Customer> customerList = new ArrayList<>();
public Technician() { public Technician() {
} }
public Technician(long id, String code, Depot depot, int startTime, int endTime, Set<String> skills, public Technician(long id, String code, Depot depot, int startTime, int endTime, Set<String> skills,
Map<String, Long> preferredlocationDistanceMap) { Map<String, Long> preferredlocationDistanceMap, Location preferredlocation) {
this.id = id; this.id = id;
this.code = code; this.code = code;
this.depot = depot; this.depot = depot;
...@@ -54,53 +54,54 @@ public class Technician { ...@@ -54,53 +54,54 @@ public class Technician {
this.endTime = endTime; this.endTime = endTime;
this.skills = skills; this.skills = skills;
this.preferredlocationDistanceMap = preferredlocationDistanceMap; this.preferredlocationDistanceMap = preferredlocationDistanceMap;
this.preferredlocation = preferredlocation;
} }
// ************************************************************************ // ************************************************************************
// Complex methods // Complex methods
// ************************************************************************ // ************************************************************************
/** /**
* @return route of the vehicle * @return route of the vehicle
*/ */
@JsonIgnore @JsonIgnore
public List<Location> getRoute() { public List<Location> getRoute() {
if (customerList.isEmpty()) { if (customerList.isEmpty()) {
return Collections.emptyList(); return Collections.emptyList();
} }
List<Location> route = new ArrayList<Location>(); List<Location> route = new ArrayList<Location>();
route.add(depot.getLocation()); route.add(depot.getLocation());
for (Customer customer : customerList) { for (Customer customer : customerList) {
route.add(customer.getLocation()); route.add(customer.getLocation());
} }
return route; return route;
} }
/** /**
* 总路线距离 * 总路线距离
* *
* @return * @return
*/ */
public long getTotalDistanceMeters() { public long getTotalDistanceMeters() {
if (customerList.isEmpty()) { if (customerList.isEmpty()) {
return 0; return 0;
} }
long totalDistance = 0; long totalDistance = 0;
Location previousLocation = depot.getLocation(); Location previousLocation = depot.getLocation();
for (Customer customer : customerList) { for (Customer customer : customerList) {
totalDistance += previousLocation.getDistanceTo(customer.getLocation()); totalDistance += previousLocation.getDistanceTo(customer.getLocation());
previousLocation = customer.getLocation(); previousLocation = customer.getLocation();
} }
totalDistance += previousLocation.getDistanceTo(depot.getLocation()); totalDistance += previousLocation.getDistanceTo(depot.getLocation());
return totalDistance; return totalDistance;
} }
/** /**
* 获取偏好总距离 所有customer与该技术员的PreferredLocation距离之和 * 获取偏好总距离 所有customer与该技术员的PreferredLocation距离之和
* *
...@@ -117,11 +118,11 @@ public class Technician { ...@@ -117,11 +118,11 @@ public class Technician {
} }
return totalDistance; return totalDistance;
} }
public int getCustomerSize() { public int getCustomerSize() {
return customerList.size(); return customerList.size();
} }
/** /**
* 获取总上班时间,第一个订单到最后一个订单时间跨度 * 获取总上班时间,第一个订单到最后一个订单时间跨度
* *
...@@ -136,7 +137,7 @@ public class Technician { ...@@ -136,7 +137,7 @@ public class Technician {
- customerList.get(0).getArrivalTime(); - customerList.get(0).getArrivalTime();
} }
} }
/** /**
* 获取下班时间,最后一个订单完成时间 * 获取下班时间,最后一个订单完成时间
* *
...@@ -151,8 +152,6 @@ public class Technician { ...@@ -151,8 +152,6 @@ public class Technician {
return lastCustomer.getDepartureTime(); return lastCustomer.getDepartureTime();
} }
} }
@Override @Override
public int hashCode() { public int hashCode() {
...@@ -169,7 +168,7 @@ public class Technician { ...@@ -169,7 +168,7 @@ public class Technician {
return true; return true;
return this.id == ((Technician) obj).getId(); return this.id == ((Technician) obj).getId();
} }
@Override @Override
public String toString() { public String toString() {
return "Technician{" + "id=" + id + '}'; return "Technician{" + "id=" + id + '}';
......
depot,"120.614853,31.338807"
106330400,"120.982906,31.440902"
106632743,"120.726916,31.592422"
106632993,"120.868246,31.041383"
106704506,"120.625144,31.257837"
106729637,"120.959821,31.402083"
106732179,"121.077572,31.563578"
106737923,"120.622214,31.333417"
106758769,"120.731802,31.300809"
106765790,"120.847507,31.42479"
106790276,"120.606255,31.268432"
106798348,"120.959821,31.402083"
106845767,"120.687214,31.253326"
106848936,"120.679366,31.287809"
106854117,"120.664649,31.316367"
106855369,"120.616312,31.385716"
106865528,"120.623197,31.470323"
106867266,"120.597917,31.348755"
106879505,"120.765083,31.697774"
106882180,"120.664487,31.282887"
106893582,"121.107535,31.479541"
106228451,"120.721813,31.303003"
106660472,"120.611127,31.250817"
106684680,"120.626474,31.224566"
106703025,"120.530752,31.281024"
106762173,"120.606634,31.292016"
106802404,"120.654614,31.098804"
106820041,"120.719909,31.382979"
106820735,"120.58446,31.322863"
106824266,"120.697424,31.344814"
106828070,"120.596177,31.283586"
106833813,"120.523634,31.858481"
106835282,"121.039728,31.400563"
106853363,"121.104428,31.453363"
106856986,"120.745434,31.304664"
106861402,"120.785598,31.097726"
106863823,"120.966152,31.389332"
106870737,"120.612394,31.194301"
106870765,"120.451477,31.938889"
106871040,"120.686309,31.327041"
106872591,"120.591992,31.292224"
106872658,"120.768159,31.337805"
106875426,"120.64272,31.277918"
106890613,"120.526677,31.863775"
106893208,"120.748525,31.324368"
106279591,"120.982906,31.440902"
106704564,"120.625144,31.257837"
106729640,"120.959821,31.402083"
106732181,"121.077572,31.563578"
106738036,"120.622214,31.333417"
106758988,"120.731802,31.300809"
106765761,"120.847507,31.42479"
106818668,"120.493618,31.24607"
106823211,"120.651719,31.278747"
106824512,"120.754292,31.337413"
106836405,"120.665415,31.32013"
106841840,"120.615365,31.186788"
106851428,"120.98225,31.417492"
106852112,"120.654228,31.337012"
106853178,"120.632811,31.173964"
106854359,"120.84248,31.284855"
106855674,"120.743073,31.677162"
106856390,"120.655769,31.393115"
106859149,"120.724624,31.487646"
106859862,"120.549426,31.376686"
106860870,"120.748602,31.317585"
106862449,"120.777722,31.636159"
106865892,"120.664487,31.282887"
106866730,"120.606376,31.391923"
106868422,"120.702768,31.333509"
106870053,"120.732045,31.30597"
106873767,"120.964531,31.349257"
106873992,"120.574297,31.321478"
106876557,"120.721418,31.487407"
106876682,"121.088316,31.5656"
106881724,"120.601868,31.148427"
106883231,"121.120276,31.465135"
106883391,"120.861145,31.080919"
106888947,"120.628533,31.368943"
106889389,"120.680358,31.325299"
106893581,"121.107535,31.479541"
106776742,"120.70851,31.248646"
106820178,"120.678376,31.149701"
106826035,"120.49468,31.996066"
106835053,"120.600039,31.295101"
106839639,"120.883187,31.287842"
106840295,"120.526884,31.373801"
106848186,"121.135103,31.441649"
106869743,"120.938132,31.396139"
106871570,"120.634245,31.188141"
106871918,"120.64205,31.296388"
106872059,"120.589301,31.3104"
106874727,"120.748439,31.684466"
106879292,"120.715751,31.29945"
106881156,"120.566187,31.855965"
106884799,"120.605229,31.394392"
106886425,"120.548647,31.842596"
106890832,"120.604399,31.349966"
106576847,"121.106111,31.480717"
106704602,"120.625144,31.257837"
106729639,"120.959821,31.402083"
106732180,"121.077572,31.563578"
106738035,"120.622214,31.333417"
106853161,"120.632811,31.173964"
106855710,"120.743073,31.677162"
106856389,"120.655769,31.393115"
106859148,"120.724624,31.487646"
106860869,"120.748602,31.317585"
106862447,"120.777722,31.636159"
106866729,"120.606376,31.391923"
106868053,"120.606634,31.292016"
106888946,"120.628533,31.368943"
106854169,"120.634382,31.374593"
106870713,"120.633518,31.153463"
106891293,"120.636053,31.289328"
106549827,"121.144142,31.422735"
106765447,"120.616316,31.402105"
106781641,"120.606019,31.398212"
106793056,"120.747643,31.310646"
106803268,"120.607062,31.398126"
106818600,"121.144926,31.461553"
106822716,"120.705113,31.257879"
106829323,"120.673489,31.297058"
106840366,"120.649177,31.303238"
106847687,"120.530373,31.853617"
106866881,"120.629969,31.270552"
106870492,"120.650458,31.225728"
106871200,"120.59747,31.285217"
106872822,"120.616482,31.402948"
106878500,"120.606019,31.398212"
106880124,"120.615837,31.316617"
106880969,"120.677186,31.149534"
106892125,"120.587065,31.283529"
106763043,"120.687676,31.443469"
106801344,"120.69381,31.304455"
106806874,"120.650618,31.176803"
106824742,"120.77035,31.312694"
106858813,"120.769821,31.342065"
106870383,"120.624333,31.361689"
106632622,"120.726916,31.592422"
106744960,"121.077777,31.560303"
106809648,"120.956653,31.459789"
106862554,"120.685176,31.471252"
106649991,"120.632112,31.286827"
106753590,"120.597212,31.294007"
106845946,"120.747488,31.340815"
106859379,"120.756575,31.336275"
106632543,"120.726916,31.592422"
106632849,"120.726916,31.592422"
106665842,"120.64402,31.77468"
106730931,"120.639926,31.306425"
106744959,"121.077777,31.560303"
106809662,"120.956653,31.459789"
106813589,"120.844551,31.013903"
106862553,"120.685176,31.471252"
106871868,"120.686193,31.327165"
106879520,"120.614752,31.309742"
106694001,"120.632112,31.286827"
106828628,"120.77099,31.682885"
106834398,"120.761397,31.311754"
106886055,"120.598933,31.297181"
106665727,"120.64402,31.77468"
106665740,"120.64402,31.77468"
106704534,"120.625144,31.257837"
106809681,"120.956653,31.459789"
106862552,"120.685176,31.471252"
106884214,"120.620985,31.129614"
106884485,"120.485939,31.334295"
106885700,"120.727943,31.254621"
106887147,"120.790707,31.683097"
106754377,"120.639734,31.32923"
106775190,"120.612111,31.189908"
106849556,"120.605103,31.296943"
106867319,"120.696831,31.275636"
106888973,"120.898629,31.749332"
106889146,"120.60652,31.404533"
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!