Commit 1a09ae86 by 张晓

Merge branch 'develop' of https://gitlab.dituhui.com/bsh/project/project into develop

2 parents 5f0bc92b 9037490e
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 {
@Override
public Constraint[] defineConstraints(ConstraintFactory factory) {
return new Constraint[] { greaterThanZero(factory), customerTimeWindowsMatch1(factory),
customerTimeWindowsMatch2(factory), skillMatch(factory), technicianBalance(factory),
technicianBalance2(factory), technicianBalanceSoft(factory), totalDistance(factory),
preferredTotalDistance(factory) };
return new Constraint[] {
// 硬约束
// 运行时长提升效果
// 5s/60s: 技能权重4-技能匹配问题0个,时间窗问题14个
// 10s/300s: 技能权重4-技能匹配问题0个,时间窗问题9个
customerTimeWindowsMatch(factory),
skillMatch(factory),
// 软约束
technicianBalanceSoft(factory),
totalDistance(factory),
preferredTotalDistance(factory)
};
}
// ************************************************************************
// Hard constraints
// ************************************************************************
// 1,每个技术员至少分配一个greaterThanZero
// 2,技术员到达时间跟时间窗吻合customerTimeWindowsMatch
// 3,技术员时间窗吻合technicianTimeWindowsMatch
// 4,技术员技能跟订单相匹配skillMatch
// 5,订单均分technicianBalance
// 1,技术员到达时间跟时间窗吻合customerTimeWindowsMatch
// 2,技术员时间窗吻合technicianTimeWindowsMatch
// 3,技术员技能跟订单相匹配skillMatch
public Constraint greaterThanZero(ConstraintFactory factory) {
return factory.forEach(Technician.class).filter(technician -> technician.getCustomerList().size() == 0)
.penalizeLong(HardSoftLongScore.ONE_HARD, technician -> 1).asConstraint("每个技术员至少分配一个单子");
}
protected Constraint customerTimeWindowsMatch1(ConstraintFactory factory) {
protected Constraint customerTimeWindowsMatch(ConstraintFactory factory) {
return factory.forEach(Customer.class).filter(
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) {
// 迟到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 customerTimeWindowsMatch1(ConstraintFactory factory) {
// return factory.forEach(Customer.class).filter(
// customer -> customer.getTechnician() != null && customer.getArrivalTime() > customer.getEndTime())
// .penalizeLong(HardSoftLongScore.ONE_HARD, customer -> 1).asConstraint("技术员到达时间跟时间窗吻合1");
// }
//
// 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) {
return factory.forEach(Customer.class)
......@@ -54,38 +69,21 @@ public class DispatchConstraintProvider implements ConstraintProvider {
// 3-技能匹配问题1个,时间窗问题8个
// 4-技能匹配问题0个,时间窗问题14个
customer -> 4)
.asConstraint("技术员技能跟订单相匹配skillMatch");
}
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");
.asConstraint(ConstraintNameEnum.skillMatch.name());
}
// protected Constraint technicianBalance(ConstraintFactory factory) {
// return factory.forEachUniquePair(Technician.class, Joiners.filtering(
// // 一个人的工作时长超过另外一个人的2倍 FIXME 无效
// (t1, t2) -> (t1.getWorkTime() > t2.getWorkTime() * 2) || (t2.getWorkTime() > t1.getWorkTime() * 2)))
// .penalizeLong(HardSoftLongScore.ONE_HARD, (c1, c2) -> 1).asConstraint("订单均分");
// // 会导致剩余单子集中?
// return factory.forEach(Technician.class).filter(technician -> technician.getOffWorkTime() > 1140)
// .penalizeLong(HardSoftLongScore.ONE_HARD,
// technician -> (long) Math.ceil(technician.getOffWorkTime() / 1440f))
// .asConstraint("订单均分");
// }
// protected Constraint technicianBalance(ConstraintFactory factory) {
// return factory.forEachUniquePair(Technician.class, Joiners.filtering(
// // 一个人的数量是另外一个人的3倍 FIXME 无效
// (t1, t2) -> {
// int size1 = t1.getCustomerSize();
// int size2 = t2.getCustomerSize();
// return (size1 > size2 * 3) || (size2 > size1 * 3);
// })).penalizeLong(HardSoftLongScore.ONE_HARD, (c1, c2) -> 1).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");
// }
// ************************************************************************
......@@ -97,19 +95,21 @@ public class DispatchConstraintProvider implements ConstraintProvider {
protected Constraint totalDistance(ConstraintFactory factory) {
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) {
return factory.forEach(Technician.class)
.penalizeLong(HardSoftLongScore.ONE_SOFT, Technician::getPreferredTotalDistanceMeters)
.asConstraint("技术员中心点偏好");
.asConstraint(ConstraintNameEnum.preferredTotalDistance.name());
}
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");
(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;
public class Location {
private final long id;
// 坐标点
private float x;
private float y;
private String code;
// 类型 engineer order
private String type;
......@@ -28,6 +33,12 @@ public class Location {
this.id = id;
}
public Location(long id, float x, float y) {
this.id = id;
this.x = x;
this.y = y;
}
public Location(long id, String code, String type, double longitude, double latitude) {
this.id = id;
this.code = code;
......
......@@ -31,6 +31,11 @@ public class Technician {
// 技能
private Set<String> skills;
// 偏好坐标
private Location preferredlocation;
// technician code : customer code , distance
@JsonIgnore
private Map<String, Long> preferredlocationDistanceMap = new HashMap<String, Long>();
// 每日最大单量
private int maxCount;
......@@ -40,13 +45,6 @@ public class Technician {
// 单位是米,这里要注意
private int maxDistanceMeter;
// 偏好坐标
// private Location preferredlocation;
// teck code : customer code , distance
@JsonIgnore
private Map<String, Long> preferredlocationDistanceMap = new HashMap<String, Long>();
@PlanningListVariable
private List<Customer> customerList = new ArrayList<>();
......@@ -54,7 +52,7 @@ public class Technician {
}
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.code = code;
this.depot = depot;
......@@ -62,11 +60,11 @@ public class Technician {
this.endTime = endTime;
this.skills = skills;
this.preferredlocationDistanceMap = preferredlocationDistanceMap;
this.preferredlocation = preferredlocation;
}
public Technician(long id, String code, int maxCount, int maxMinute, int maxDistanceMeter,
Depot depot, int startTime, int endTime, Set<String> skills,
Map<String, Long> preferredlocationDistanceMap) {
public Technician(long id, String code, int maxCount, int maxMinute, int maxDistanceMeter, Depot depot,
int startTime, int endTime, Set<String> skills, Map<String, Long> preferredlocationDistanceMap) {
this.id = id;
this.code = code;
this.depot = depot;
......@@ -124,7 +122,6 @@ public class Technician {
return totalDistance;
}
/**
* 获取偏好总距离 所有customer与该技术员的PreferredLocation距离之和
*
......@@ -161,7 +158,6 @@ public class Technician {
}
}
/**
* 获取下班时间,最后一个订单完成时间
*
......@@ -177,8 +173,6 @@ public class Technician {
}
}
@Override
public int hashCode() {
return Long.valueOf(this.id).hashCode();
......@@ -197,16 +191,8 @@ public class Technician {
@Override
public String toString() {
return "Technician{" +
"id=" + id +
", code='" + code + '\'' +
", depot=" + depot +
", startTime=" + startTime +
", endTime=" + endTime +
", skills=" + skills +
", maxCount=" + maxCount +
", maxMinute=" + maxMinute +
", maxDistanceMeter=" + maxDistanceMeter +
'}';
return "Technician{" + "id=" + id + ", code='" + code + '\'' + ", depot=" + depot + ", startTime=" + startTime
+ ", endTime=" + endTime + ", skills=" + skills + ", maxCount=" + maxCount + ", maxMinute=" + maxMinute
+ ", maxDistanceMeter=" + maxDistanceMeter + '}';
}
}
......@@ -26,7 +26,7 @@ spring:
enabled: false
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://10.10.0.116:3306/saas_aftersale_test?serverTimezone=Asia/Shanghai
url: jdbc:mysql://10.10.0.54:3306/saas_aftersale_test?serverTimezone=Asia/Shanghai
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
......
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"
package com.dituhui.pea.dispatch.test;
import java.io.FileNotFoundException;
import java.io.UncheckedIOException;
import org.optaplanner.core.api.solver.Solver;
import org.optaplanner.core.api.solver.SolverFactory;
import org.optaplanner.core.impl.solver.DefaultSolverFactory;
import com.dituhui.pea.dispatch.eventListener.DispatchSolverEventListener;
import com.dituhui.pea.dispatch.pojo.DispatchSolution;
import com.dituhui.pea.dispatch.utils.DataUtils;
import com.dituhui.pea.dispatch.utils.DispatchSolutionUtils;
public class Test {
public static void main(String[] args) throws UncheckedIOException, FileNotFoundException {
// 创建解决方案对象
DispatchSolution problem = DataUtils.getInitialProblem();
// 创建求解器
DefaultSolverFactory<DispatchSolution> solverFactory = DispatchSolutionUtils.getSolverFactory(3, 30);
Solver<DispatchSolution> solver = solverFactory.buildSolver();
// 得分监听器
solver.addEventListener(new DispatchSolverEventListener());
// 求解
DispatchSolution solution = solver.solve(problem);
// 打印和输出solution
DispatchSolutionUtils.printSolution(solution);
// DispatchSolutionUtils.explainSolutionConstraintDetail(solution);
// DispatchSolutionUtils.exportSolutionJson(solution, "dispatchSolution.json");
// DispatchSolutionUtils.exportMapHtml(solution, "dispatchMap" +
// System.currentTimeMillis());
// FIXME
DispatchSolution movedSolution = DispatchSolutionUtils.moveCustomer(solverFactory, solution,
solution.getTechnicianList().get(0), 0, 1);
DispatchSolutionUtils.printSolution(movedSolution);
}
}
......@@ -26,7 +26,7 @@ spring:
# - optional:nacos:datasource-config.yaml
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://10.10.0.116:3306/saas_aftersale_test?serverTimezone=Asia/Shanghai
url: jdbc:mysql://10.10.0.54:3306/saas_aftersale_test?serverTimezone=Asia/Shanghai
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!