首先简单介绍一下,前面自己学了drools规则引擎,但是好像学习了解drools后,发现的optaplanner这个基于drools规则引擎开发的一个约束求解器,用于一些业务排程,机台排程,计划排程等问题以及类似的应用场景。说的更通俗易懂点,就是有几个变量,会根据实际情况进行变化,而这几个变量变化就会造成结果产生变化的情况。
例如:一个生产任务,可能受员工人数,原材料,生产设备的影响导致生产任务可能会有变化(例如都充足情况下,然后可能提前完成生产任务,如果原材料数量不足,生成到一半然后没材料了,导致生产任务停止)
这些都是optaplanner可以来帮我实现的一些业务场景。官网有详细介绍,需要的可以自行企业官网细细了解。
简单介绍完背景后,直接上实际操作。
第1步:创建一个springboot项目
第2步:导入optaplanner的相关依赖,(此处避坑,如果是通过drl文件实现的规则,则使用9.x之前的版本,如果是通过约束流来实现规则判断的则使用9.X以后得版本),因为9.0版本后不使用drl规则文件来实现了。
<!--OptaPlanner的核心库,提供了求解器、规划变量、评分计算等核心功能的实现.-->
<dependency>
<groupId>org.optaplanner</groupId>
<artifactId>optaplanner-core</artifactId>
<version>8.20.0.Final</version>
</dependency>
<!-- OptaPlanner 在 Spring Boot 中集成的依赖.-->
<dependency>
<groupId>org.optaplanner</groupId>
<artifactId>optaplanner-spring-boot-starter</artifactId>
<version>8.20.0.Final</version>
</dependency>
第3步:创建对应的配置文件src/main/resources/application.yaml
server:
address: 127.0.0.1
port: 8080
servlet:
context-path: /api
spring:
config:
name: optaplanner_springboot
第四步:创建drl规则文件src/main/resources/rules/taskAssignmentDrools.drl(drl规则文件就是大概意思就是当什么什么的时候,我们就进行软,硬规则的操作。比如这里面就是进行扣分)。
package rules;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScoreHolder;
import com.qiyan.smartrule.entity.Task;
import com.qiyan.smartrule.entity.Machine;
import com.qiyan.smartrule.entity.TaskAssignment;
global HardSoftScoreHolder scoreHolder;
rule "yarnTypeMatch"
when
Task(machine != null, machine.yarnType != requiredYarnType)
then
scoreHolder.addHardConstraintMatch(kcontext, -10000);
end
rule "machineCapacity"
when
$machine : Machine($capacity : capacity)
accumulate(
Task(
machine == $machine,
$amount : amount);
$amountTotal : sum($amount);
$amountTotal > $capacity
)
then
scoreHolder.addHardConstraintMatch(kcontext, $capacity - $amountTotal);
end
rule "machineCost_used"
when
$machine : Machine($cost : cost)
exists Task(machine == $machine)
then
scoreHolder.addSoftConstraintMatch(kcontext, -$cost);
end
第4步:创建optaplanner对应的xml文件src/main/resources/taskassignmentConfiguration.xml
其实这个xml文件也可以不要,也可以写一个对应的optaplanner_config配置类,实现动态设置对应的Solver对象。
<?xml version="1.0" encoding="UTF-8"?>
<solver>
<!-- 解决方案对象 -->
<solutionClass>com.qiyan.smartrule.entity.TaskAssignment</solutionClass>
<!-- 因变量对象 -->
<entityClass>com.qiyan.smartrule.entity.Task</entityClass>
<!-- drl规则文件地址 -->
<scoreDirectorFactory>
<scoreDrl>rules/taskAssignmentDrools.drl</scoreDrl>
</scoreDirectorFactory>
<!-- 规则执行时间限制,超过时间会返回类似兜底数据 -->
<termination>
<secondsSpentLimit>10</secondsSpentLimit>
</termination>
</solver>
第5步:创建对应的entity对象,可以理解为这些就是一个任务的变量。
5.1 AbstractPersistable 这个类就是用来对任务id进行排序等操作的,你可以通过compareTo方法自己实现自己的一些排序等操作。
package com.qiyan.smartrule.entity;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.commons.lang3.builder.CompareToBuilder;
import org.optaplanner.core.api.domain.lookup.PlanningId;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AbstractPersistable implements Serializable, Comparable<AbstractPersistable> {
@PlanningId
protected Long id;
@Override
public int compareTo(AbstractPersistable other) {
return new CompareToBuilder().append(getClass().getName(), other.getClass().getName()).append(id, other.id)
.toComparison();
}
@Override
public String toString() {
return getClass().getName().replaceAll(".*\\.", "") + "-" + id;
}
}
5.2Machine (机台类)
package com.qiyan.smartrule.entity;
import lombok.Getter;
@Getter
public class Machine extends AbstractPersistable {
private String yarnType;
private int capacity;
private int cost;
public void setYarnType(String yarnType) {
this.yarnType = yarnType;
}
public void setCapacity(int capacity) {
this.capacity = capacity;
}
public void setCost(int cost) {
this.cost = cost;
}
public Machine(long id, String yarnType, int capacity, int cost) {
super(id);
this.yarnType = yarnType;
this.capacity = capacity;
this.cost = cost;
}
}
5.3Task (任务类)
package com.qiyan.smartrule.entity;
import lombok.Setter;
import org.optaplanner.core.api.domain.entity.PlanningEntity;
import org.optaplanner.core.api.domain.variable.PlanningVariable;
@Setter
@PlanningEntity
public class Task extends AbstractPersistable {
//当前任务的类型
private String requiredYarnType;
private int amount;
private Machine machine;
public String getRequiredYarnType() {
return requiredYarnType;
}
public int getAmount() {
return amount;
}
//规划变量是指可以被调整和优化的属性 (可以理解为变量)
@PlanningVariable(valueRangeProviderRefs={"machineRange"})
public Machine getMachine() {
return machine;
}
public Task(){}
public Task(long id, String requiredYarnType, int amount) {
super(id);
this.requiredYarnType = requiredYarnType;
this.amount = amount;
}
}
5.4 TaskAssignment (任务结果类)
package com.qiyan.smartrule.entity;
import java.util.List;
import lombok.*;
import org.optaplanner.core.api.domain.solution.PlanningEntityCollectionProperty;
import org.optaplanner.core.api.domain.solution.PlanningScore;
import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.api.domain.solution.ProblemFactCollectionProperty;
import org.optaplanner.core.api.domain.valuerange.ValueRangeProvider;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
@PlanningSolution
public class TaskAssignment extends AbstractPersistable {
//这个就是最终的软硬分对象
@PlanningScore
private HardSoftScore score;
//问题的事实集合 (可以理解为因变量)
@ProblemFactCollectionProperty
@ValueRangeProvider(id = "machineRange")
private List<Machine> machineList;
//问题的规划实体集合(可以理解为变量)
@PlanningEntityCollectionProperty
@ValueRangeProvider(id = "taskRange")
private List<Task> taskList;
public TaskAssignment(List<Machine> machineList, List<Task> taskList) {
//super(0);
this.machineList = machineList;
this.taskList = taskList;
}
}
第6步:创建对应的接口测试
@Test
void optaplannerTest() {
startPlan();
}
private static void startPlan(){
List<Machine> machines = getMachines();
List<Task> tasks = getTasks();
// InputStream ins = App.class.getResourceAsStream("");
SolverFactory<TaskAssignment> solverFactory = SolverFactory.createFromXmlResource("taskassignmentConfiguration.xml");
Solver<TaskAssignment> solver = solverFactory.buildSolver();
TaskAssignment unassignment = new TaskAssignment(machines, tasks);
TaskAssignment assigned = solver.solve(unassignment);//启动引擎
List<Machine> machinesAssigned = assigned.getTaskList().stream().map(Task::getMachine).distinct().collect(Collectors.toList());
for(Machine machine : machinesAssigned) {
System.out.print("\n" + machine + ":");
List<Task> tasksInMachine = assigned.getTaskList().stream().filter(x -> x.getMachine().equals(machine)).collect(Collectors.toList());
for(Task task : tasksInMachine) {
System.out.print("->" + task);
}
}
}
private static List<Machine> getMachines() {
// 六个机台
Machine m1 = new Machine(1, "Type_A", 300, 100);
Machine m2 = new Machine(2, "Type_A", 1000, 100);
Machine m3 = new Machine(3, "TYPE_B", 1000, 300);
Machine m4 = new Machine(4, "TYPE_B", 1000, 100);
Machine m5 = new Machine(5, "Type_C", 1100, 100);
Machine m6 = new Machine(6, "Type_D", 900, 100);
List<Machine> machines = new ArrayList<Machine>();
machines.add(m1);
machines.add(m2);
machines.add(m3);
machines.add(m4);
machines.add(m5);
machines.add(m6);
return machines;
}
第7步;代码运行结果:
在本地搭建过程中遇到了几个问题:
问题一:大概意思就是找不到
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScoreHolder;这个类,排错后发现这问题产生的原因是没有读取到drl文件,也就是xml配置中的XXXXX.drl文件不对,我之前没加rules造成的。
java.lang.IllegalArgumentException: The kieBase with kiePackages ([]) has no global field called scoreHolder.
Check if the rule files are found and if the global field is spelled correctly.
at org.optaplanner.constraint.drl.DrlScoreDirectorFactory.assertGlobalScoreHolderExists(DrlScoreDirectorFactory.java:82)
at org.optaplanner.constraint.drl.DrlScoreDirectorFactory.<init>(DrlScoreDirectorFactory.java:60)
at org.optaplanner.constraint.drl.DrlScoreDirectorFactoryService.createScoreDirectorFactory(DrlScoreDirectorFactoryService.java:53)
at org.optaplanner.constraint.drl.AbstractDrlScoreDirectorFactoryService.buildScoreDirectorFactory(AbstractDrlScoreDirectorFactoryService.java:68)
at org.optaplanner.constraint.drl.DrlScoreDirectorFactoryService.lambda$buildScoreDirectorFactory$0(DrlScoreDirectorFactoryService.java:47)
at org.optaplanner.core.impl.score.director.ScoreDirectorFactoryFactory.decideMultipleScoreDirectorFactories(ScoreDirectorFactoryFactory.java:137)
at org.optaplanner.core.impl.score.director.ScoreDirectorFactoryFactory.buildScoreDirectorFactory(ScoreDirectorFactoryFactory.java:55)
at org.optaplanner.core.impl.solver.DefaultSolverFactory.buildScoreDirectorFactory(DefaultSolverFactory.java:177)
at org.optaplanner.core.impl.solver.DefaultSolverFactory.<init>(DefaultSolverFactory.java:87)
at org.optaplanner.core.api.solver.SolverFactory.createFromXmlResource(SolverFactory.java:56)
at com.qiyan.smartrule.SmartruleApplicationTests.startPlan(SmartruleApplicationTests.java:56)
at com.qiyan.smartrule.SmartruleApplicationTests.droolsTest(SmartruleApplicationTests.java:46)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
问题二:就是我创建测试类的时候没传对应的问题id过去,导致找不到对应的问题id。
java.lang.IllegalArgumentException: The planningId (null) of the member (field protected java.lang.Long com.qiyan.smartrule.entity.AbstractPersistable.id) of the class (class com.qiyan.smartrule.entity.Task) on externalObject (Task-null) must not be null.
Maybe initialize the planningId of the class (Task) instance (Task-null) before solving.
Maybe remove the @PlanningId annotation or change the @PlanningSolution annotation's LookUpStrategyType.
at org.optaplanner.core.impl.domain.lookup.PlanningIdLookUpStrategy.extractPlanningId(PlanningIdLookUpStrategy.java:98)
at org.optaplanner.core.impl.domain.lookup.PlanningIdLookUpStrategy.addWorkingObject(PlanningIdLookUpStrategy.java:38)
at org.optaplanner.core.impl.domain.lookup.LookUpManager.addWorkingObject(LookUpManager.java:46)
at org.optaplanner.core.impl.score.director.AbstractScoreDirector.lambda$setWorkingSolution$0(AbstractScoreDirector.java:182)
at org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor.visitAllFacts(SolutionDescriptor.java:912)
at org.optaplanner.core.impl.score.director.AbstractScoreDirector.setWorkingSolution(AbstractScoreDirector.java:181)
at org.optaplanner.constraint.drl.DrlScoreDirector.setWorkingSolution(DrlScoreDirector.java:68)
at org.optaplanner.core.impl.solver.scope.SolverScope.setWorkingSolutionFromBestSolution(SolverScope.java:258)
at org.optaplanner.core.impl.solver.AbstractSolver.solvingStarted(AbstractSolver.java:80)
at org.optaplanner.core.impl.solver.DefaultSolver.solvingStarted(DefaultSolver.java:240)
at org.optaplanner.core.impl.solver.DefaultSolver.solve(DefaultSolver.java:208)
at com.qiyan.smartrule.SmartruleApplicationTests.startPlan(SmartruleApplicationTests.java:59)
at com.qiyan.smartrule.SmartruleApplicationTests.droolsTest(SmartruleApplicationTests.java:46)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)