Bläddra i källkod

APS规划平台代码初始化

fangpy 1 år sedan
incheckning
134bd6d13c

+ 38 - 0
.gitignore

@@ -0,0 +1,38 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store

+ 55 - 0
pom.xml

@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.example</groupId>
+    <artifactId>rw-aps</artifactId>
+    <version>1.0-SNAPSHOT</version>
+
+    <properties>
+        <maven.compiler.source>11</maven.compiler.source>
+        <maven.compiler.target>11</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+
+        <version.org.optaplanner>9.44.0.Final</version.org.optaplanner>
+    </properties>
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.7.12</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.optaplanner</groupId>
+            <artifactId>optaplanner-spring-boot-starter</artifactId>
+            <version>${version.org.optaplanner}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>4.0.1</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <!-- spring boot提供的核心maven插件 -->
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 98 - 0
src/main/java/com/rongwei/aps/ApsBalancingApplication.java

@@ -0,0 +1,98 @@
+package com.rongwei.aps;
+
+import cn.hutool.core.date.DateUtil;
+import com.rongwei.aps.domain.ApsSolution;
+import com.rongwei.aps.domain.Equipment;
+import com.rongwei.aps.domain.ProduceOrder;
+import com.rongwei.aps.domain.ProductionProcesses;
+import com.rongwei.aps.score.ApsConstraintProvider;
+import lombok.extern.slf4j.Slf4j;
+import org.optaplanner.core.api.solver.Solver;
+import org.optaplanner.core.api.solver.SolverFactory;
+import org.optaplanner.core.config.solver.SolverConfig;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@Slf4j
+public class ApsBalancingApplication {
+
+    public static void main(String[] args) {
+        testAps();
+
+        /*ProductionProcesses p1 = new ProductionProcesses();
+        ProductionProcesses p2 = new ProductionProcesses();
+        p1.setId(1111);
+        p2.setId(2222);
+        p1.setNextProcesses(p2);
+        p2.setPreviousProcesses(p1);
+        System.out.println(p2.getPreviousProcesses().getId());*/
+
+        /*Date date1 = DateUtil.parseDateTime("2024-02-22 20:01:23");
+        LocalDateTime time2 = LocalDateTime.now();
+        long a = DateUtil.between(Date.from(time2.atZone(ZoneId.systemDefault()).toInstant()),date1, DateUnit.DAY);
+        System.out.println("a:"+a);*/
+    }
+
+    private static void testAps(){
+        log.info("app start...");
+        SolverFactory<ApsSolution> solverFactory = SolverFactory.create(new SolverConfig()
+                .withSolutionClass(ApsSolution.class)
+                .withEntityClasses(ProductionProcesses.class)
+                .withConstraintProviderClass(ApsConstraintProvider.class)
+                .withTerminationSpentLimit(Duration.ofSeconds(5)));
+        Solver<ApsSolution> solver = solverFactory.buildSolver();
+
+//        System.out.println("\nProcesses:\n" + unsolvedCloudBalance.toStringProcessList());
+
+        // Display the computer list
+//        System.out.println("\nComputers:\n" + unsolvedCloudBalance.toStringComputerList());
+
+        log.info("start to solve...");
+        ApsSolution solvedBalance = solver.solve(getTestApsSolution());
+        System.out.println(solvedBalance);
+        // Display the result
+//        System.out.println("\nSolved cloudBalance with 3 computers and 12 processes:\n" + toDisplayString(solvedCloudBalance));
+    }
+
+    private static ApsSolution getTestApsSolution(){
+        ApsSolution unsolvedCloudBalance = new ApsSolution();
+        // 设备列表初始化
+        List<Equipment> equipmentList = new ArrayList<>();
+        Equipment eq1 = new Equipment(1,"均热炉");
+        Equipment eq2 = new Equipment(2,"均热炉");
+        Equipment eq3 = new Equipment(3,"卧式分切机");
+        Equipment eq4 = new Equipment(4,"卧式分切机");
+        Equipment eq5 = new Equipment(5,"卧式分切机");
+        Equipment eq6 = new Equipment(6,"厚纵剪");
+        Equipment eq7 = new Equipment(7,"厚纵剪");
+        Equipment eq8 = new Equipment(8,"氮气保护退火炉");
+        equipmentList.add(eq1);
+        equipmentList.add(eq2);
+        equipmentList.add(eq3);
+        equipmentList.add(eq4);
+        equipmentList.add(eq5);
+        equipmentList.add(eq6);
+        equipmentList.add(eq7);
+        equipmentList.add(eq8);
+        unsolvedCloudBalance.setEquipmentList(equipmentList);
+        // 工步初始化
+        List<ProductionProcesses> processes = new ArrayList<>();
+        ProduceOrder produceOrder1 = new ProduceOrder("order1","订单1", DateUtil.parseDateTime("2024-02-27 21:52:12"));
+        ProductionProcesses p1 = new ProductionProcesses(1,"均热炉",produceOrder1,100);
+        ProductionProcesses p2 = new ProductionProcesses(2,"卧式分切机",produceOrder1,121);
+        p1.setNextProcesses(Arrays.asList(new ProductionProcesses[]{p2}));p2.setPreviousProcesses(Arrays.asList(new ProductionProcesses[]{p1}));
+        processes.add(p1);processes.add(p2);
+        ProduceOrder produceOrder2 = new ProduceOrder("order2","订单2", DateUtil.parseDateTime("2024-02-27 23:52:12"));
+        ProductionProcesses p3 = new ProductionProcesses(3,"均热炉",produceOrder2,100);
+        ProductionProcesses p4 = new ProductionProcesses(4,"卧式分切机",produceOrder2,121);
+        ProductionProcesses p5 = new ProductionProcesses(5,"厚纵剪",produceOrder2,131);
+        p3.setNextProcesses(Arrays.asList(new ProductionProcesses[]{p4}));p4.setPreviousProcesses(Arrays.asList(new ProductionProcesses[]{p3}));
+        p4.setNextProcesses(Arrays.asList(new ProductionProcesses[]{p5}));p5.setPreviousProcesses(Arrays.asList(new ProductionProcesses[]{p4}));
+        processes.add(p3);processes.add(p4);processes.add(p5);
+        unsolvedCloudBalance.setProcessesList(processes);
+        return unsolvedCloudBalance;
+    }
+
+}

+ 30 - 0
src/main/java/com/rongwei/aps/domain/ApsAbstractPersistable.java

@@ -0,0 +1,30 @@
+package com.rongwei.aps.domain;
+
+import org.optaplanner.core.api.domain.lookup.PlanningId;
+
+public abstract class ApsAbstractPersistable {
+
+    protected Long id;
+
+    protected ApsAbstractPersistable() {
+    }
+
+    protected ApsAbstractPersistable(long id) {
+        this.id = id;
+    }
+
+    @PlanningId
+    public long getId() {
+        return id;
+    }
+
+    public void setId(long id) {
+        this.id = id;
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getName().replaceAll(".*\\.", "") + "-" + id;
+    }
+
+}

+ 64 - 0
src/main/java/com/rongwei/aps/domain/ApsSolution.java

@@ -0,0 +1,64 @@
+package com.rongwei.aps.domain;
+
+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.CountableValueRange;
+import org.optaplanner.core.api.domain.valuerange.ValueRangeFactory;
+import org.optaplanner.core.api.domain.valuerange.ValueRangeProvider;
+import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
+
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.List;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+@NoArgsConstructor
+@PlanningSolution
+public class ApsSolution extends ApsAbstractPersistable{
+
+    @Getter(value = AccessLevel.NONE)
+    private List<ProductionProcesses> processesList;
+
+    @Getter(value = AccessLevel.NONE)
+    private List<Equipment> equipmentList;
+
+    @Getter(value = AccessLevel.NONE)
+    private HardSoftScore score;
+
+    public ApsSolution(long id, List<ProductionProcesses> processesList, List<Equipment> equipmentList) {
+        super(id);
+        this.processesList = processesList;
+        this.equipmentList = equipmentList;
+    }
+
+    @ValueRangeProvider
+    @PlanningEntityCollectionProperty
+    public List<ProductionProcesses> getProcessesList() {
+        return processesList;
+    }
+
+    @ValueRangeProvider(id="equipmentRange")
+    @ProblemFactCollectionProperty
+    public List<Equipment> getEquipmentList() {
+        return equipmentList;
+    }
+
+    @ValueRangeProvider(id="timeRange")
+    public CountableValueRange<LocalDateTime> getxRange(){
+        Integer produceTimeTotal = 0;
+        for (ProductionProcesses productionProcesses : processesList) {
+            produceTimeTotal = produceTimeTotal + productionProcesses.getProduceTime();
+        }
+        LocalDateTime now = LocalDateTime.now();
+        return ValueRangeFactory.createLocalDateTimeValueRange(now,now.plusMinutes(produceTimeTotal),1, ChronoUnit.MINUTES);
+    }
+
+    @PlanningScore
+    public HardSoftScore getScore() {
+        return score;
+    }
+}

+ 52 - 0
src/main/java/com/rongwei/aps/domain/Equipment.java

@@ -0,0 +1,52 @@
+package com.rongwei.aps.domain;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+
+/**
+ * 设备
+ */
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@Data
+public class Equipment extends ApsAbstractPersistable{
+
+    /**
+     * 设备类型
+     */
+    private String equipmentType;
+
+    /**
+     * 设备优先级
+     */
+    private Integer eqOrder;
+
+    /**
+     * 设备所属工厂
+     */
+    private String factory;
+
+    /**
+     * 设备所属车间
+     */
+    private String workshop;
+
+    /**
+     * 设备满负荷物料
+     */
+    private BigDecimal fullMaterial;
+
+    /**
+     * 单位时间生产产品的时间
+     */
+    private Integer unitProductTime;
+
+    public Equipment(long id, String equipmentType) {
+        super(id);
+        this.equipmentType = equipmentType;
+    }
+
+}

+ 45 - 0
src/main/java/com/rongwei/aps/domain/ProduceOrder.java

@@ -0,0 +1,45 @@
+package com.rongwei.aps.domain;
+
+import lombok.Data;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 生产订单,即输出一种产品
+ */
+@Data
+public class ProduceOrder {
+
+    public ProduceOrder(String id,String orderName,Date deliveryDate){
+        this.id = id;
+        this.orderName = orderName;
+        this.deliveryDate = deliveryDate;
+    }
+
+    /**
+     * 通用标识属性
+     */
+    private String id;
+
+    /**
+     * 订单名称
+     */
+    private String orderName;
+
+    /**
+     * 交货日期
+     */
+    private Date deliveryDate;
+
+    /**
+     * 生产订单的输出产品
+     */
+    private List<Product> products;
+
+    /**
+     * 生产订单的工步集合,有序的
+     */
+    private List<ProductionProcesses> processes;
+
+
+}

+ 25 - 0
src/main/java/com/rongwei/aps/domain/Product.java

@@ -0,0 +1,25 @@
+package com.rongwei.aps.domain;
+
+import java.math.BigDecimal;
+
+/**
+ * 产品
+ */
+public class Product {
+
+    /**
+     * 产品类型
+     */
+    private String productType;
+
+    /**
+     * 产品单位
+     */
+    private String productDw;
+
+    /**
+     * 产品数量
+     */
+    private BigDecimal productNum;
+
+}

+ 199 - 0
src/main/java/com/rongwei/aps/domain/ProductionProcesses.java

@@ -0,0 +1,199 @@
+package com.rongwei.aps.domain;
+
+import org.optaplanner.core.api.domain.entity.PlanningEntity;
+import org.optaplanner.core.api.domain.variable.PlanningVariable;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 工步,工艺路线里的工步
+ */
+@PlanningEntity
+public class ProductionProcesses extends ApsAbstractPersistable{
+
+    public ProductionProcesses(){
+
+    }
+
+    public ProductionProcesses(long id, String equipmentType, ProduceOrder produceOrder, Integer produceTime) {
+        super(id);
+        this.equipmentType = equipmentType;
+        this.produceOrder = produceOrder;
+        this.produceTime = produceTime;
+    }
+
+    /**
+     * 设备类型
+     */
+    private String equipmentType;
+
+    /**
+     * 工步生产时的设备
+     */
+    private Long equipmentId;
+
+    /**
+     * 工步生产时的设备
+     */
+    private Equipment equipment;
+
+    /**
+     * 所属的生产订单
+     */
+    private ProduceOrder produceOrder;
+
+    /**
+     * 是否瓶颈工序
+     */
+    private Boolean bottleneck;
+
+    /**
+     * 合并生产标识,同样的合并生产标识,尽量一起生产
+     */
+    private String mergeProcessMark;
+
+    /**
+     * 工序流转时间
+     */
+    private Integer circulationTime;
+
+    /**
+     * 下一步工序最小等待生产时间
+     */
+    private Integer minWaitTime;
+
+    /**
+     * 下一步工序最大等待生产时间
+     */
+    private Integer maxWaitTime;
+
+    /**
+     * 当前工步的生产时间(单位是分钟)
+     */
+    private Integer produceTime;
+
+    /**
+     * 开始时间
+     */
+    private LocalDateTime startTime;
+
+    /**
+     * 结束时间
+     */
+    private LocalDateTime endTime;
+
+    /**
+     * 上一个工步
+     */
+    private List<ProductionProcesses> previousProcesses;
+
+    /**
+     * 下一个工步
+     */
+    private List<ProductionProcesses> nextProcesses;
+
+    public String getEquipmentType() {
+        return equipmentType;
+    }
+
+    public void setEquipmentType(String equipmentType) {
+        this.equipmentType = equipmentType;
+    }
+
+    @PlanningVariable(valueRangeProviderRefs={"equipmentRange"})
+    public Equipment getEquipment() {
+        return equipment;
+    }
+
+    public void setEquipment(Equipment equipment) {
+        this.equipment = equipment;
+        if(equipment != null){
+            this.equipmentId = equipment.getId();
+        }
+    }
+
+    public Long getEquipmentId() {
+        return equipmentId;
+    }
+
+    public void setEquipmentId(Long equipmentId) {
+        this.equipmentId = equipmentId;
+    }
+
+    public Integer getProduceTime() {
+        return produceTime;
+    }
+
+    public void setProduceTime(Integer produceTime) {
+        this.produceTime = produceTime;
+    }
+
+    @PlanningVariable(valueRangeProviderRefs={"timeRange"})
+    public LocalDateTime getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(LocalDateTime startTime) {
+        this.startTime = startTime;
+        if(startTime != null){
+            this.endTime = startTime.plusMinutes(produceTime);
+        }
+    }
+
+    public LocalDateTime getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(LocalDateTime endTime) {
+        this.endTime = endTime;
+    }
+
+    public List<ProductionProcesses> getPreviousProcesses() {
+        return previousProcesses;
+    }
+
+    public void setPreviousProcesses(List<ProductionProcesses> previousProcesses) {
+        this.previousProcesses = previousProcesses;
+    }
+
+    public List<ProductionProcesses> getNextProcesses() {
+        return nextProcesses;
+    }
+
+    public void setNextProcesses(List<ProductionProcesses> nextProcesses) {
+        this.nextProcesses = nextProcesses;
+    }
+
+    public ProduceOrder getProduceOrder() {
+        return produceOrder;
+    }
+
+    public void setProduceOrder(ProduceOrder produceOrder) {
+        this.produceOrder = produceOrder;
+    }
+
+    public Integer getCirculationTime() {
+        return circulationTime;
+    }
+
+    public void setCirculationTime(Integer circulationTime) {
+        this.circulationTime = circulationTime;
+    }
+
+    public Integer getMinWaitTime() {
+        return minWaitTime;
+    }
+
+    public void setMinWaitTime(Integer minWaitTime) {
+        this.minWaitTime = minWaitTime;
+    }
+
+    public Integer getMaxWaitTime() {
+        return maxWaitTime;
+    }
+
+    public void setMaxWaitTime(Integer maxWaitTime) {
+        this.maxWaitTime = maxWaitTime;
+    }
+}

+ 195 - 0
src/main/java/com/rongwei/aps/score/ApsConstraintProvider.java

@@ -0,0 +1,195 @@
+package com.rongwei.aps.score;
+
+import cn.hutool.core.date.DateUnit;
+import cn.hutool.core.date.DateUtil;
+import com.rongwei.aps.domain.ProductionProcesses;
+import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
+import org.optaplanner.core.api.score.stream.Constraint;
+import org.optaplanner.core.api.score.stream.ConstraintFactory;
+import org.optaplanner.core.api.score.stream.ConstraintProvider;
+import org.optaplanner.core.api.score.stream.Joiners;
+
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.Date;
+
+import static org.optaplanner.core.api.score.stream.ConstraintCollectors.count;
+import static org.optaplanner.core.api.score.stream.ConstraintCollectors.sum;
+
+public class ApsConstraintProvider implements ConstraintProvider {
+
+    private ZoneId zoneId = ZoneId.systemDefault();
+
+    @Override
+    public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
+        return new Constraint[]{
+                eqTypeSame(constraintFactory),
+                noPreGbAfterNow(constraintFactory),
+                hasPreGbAfterNow(constraintFactory),
+                eqTimeCross(constraintFactory),
+                deliveryDate(constraintFactory),
+                balancedEqUse(constraintFactory)
+        };
+    }
+
+    /**
+     * 硬约束:工步所需的设备类型和工步所用的设备类型必须一致
+     * @param constraintFactory
+     * @return
+     */
+    private Constraint eqTypeSame(ConstraintFactory constraintFactory) {
+        return constraintFactory.forEach(ProductionProcesses.class)
+                .filter(productionProcesses -> {
+                    return !productionProcesses.getEquipmentType().equals(productionProcesses.getEquipment().getEquipmentType());
+                })
+                .penalize(HardSoftScore.ONE_HARD,(productionProcesses) -> 100)
+                .asConstraint("eqTypeSame");
+    }
+
+    /**
+     * 硬约束:没有上一步工步的开始时间小于当前时间
+     * @param constraintFactory
+     * @return
+     */
+    private Constraint noPreGbAfterNow(ConstraintFactory constraintFactory) {
+        return constraintFactory.forEach(ProductionProcesses.class)
+                .filter(productionProcesses -> {
+                    return productionProcesses.getPreviousProcesses() == null && productionProcesses.getStartTime().atZone(zoneId).toInstant().toEpochMilli()< DateUtil.date().getTime();
+                })
+                .penalize(HardSoftScore.ONE_HARD,(productionProcesses) -> 80)
+                .asConstraint("noPreGbAfterNow");
+    }
+
+    /**
+     * 硬约束:有上一步工步的开始时间要大于上一步工步最早结束时间,小于最晚结束时间
+     * 根据上一步的最小等待时间和最大等待时间来判断
+     * @param constraintFactory
+     * @return
+     */
+    private Constraint hasPreGbAfterNow(ConstraintFactory constraintFactory) {
+        return constraintFactory.forEach(ProductionProcesses.class)
+                .filter(productionProcesses -> {
+                    // 获取上一步最晚结束时间
+                    LocalDateTime preLastMinTime = null;
+                    LocalDateTime preLastMaxTime = null;
+                    if(productionProcesses.getPreviousProcesses() != null){
+                        for (ProductionProcesses previousProcess : productionProcesses.getPreviousProcesses()) {
+                            LocalDateTime thisMinEndTime = previousProcess.getEndTime();
+                            LocalDateTime thisMaxEndTime = null;
+                            if(previousProcess.getMinWaitTime() != null){
+                                thisMinEndTime = thisMinEndTime.plusMinutes(previousProcess.getMinWaitTime());
+                            }
+                            if(previousProcess.getMaxWaitTime() != null){
+                                thisMaxEndTime = thisMaxEndTime.plusMinutes(previousProcess.getMaxWaitTime());
+                            }
+                            // 最小结束时间
+                            if(preLastMinTime == null){
+                                preLastMinTime = thisMinEndTime;
+                            }else{
+                                if(previousProcess.getEndTime().compareTo(thisMinEndTime)>0){
+                                    preLastMinTime = thisMinEndTime;
+                                }
+                            }
+                            // 最大结束时间
+                            if(thisMaxEndTime != null){
+                                if(preLastMaxTime == null){
+                                    preLastMaxTime = thisMaxEndTime;
+                                }else{
+                                    if(previousProcess.getEndTime().compareTo(thisMinEndTime)>0){
+                                        preLastMinTime = thisMaxEndTime;
+                                    }
+                                }
+                            }
+                        }
+                        boolean bln = productionProcesses.getStartTime().atZone(zoneId).toInstant().toEpochMilli() < preLastMinTime.atZone(zoneId).toInstant().toEpochMilli();
+                        if(preLastMaxTime != null){
+                            bln = bln || productionProcesses.getStartTime().atZone(zoneId).toInstant().toEpochMilli() > preLastMaxTime.atZone(zoneId).toInstant().toEpochMilli();
+                        }
+                        return bln;
+                    }else{
+                        return false;
+                    }
+                })
+                .penalize(HardSoftScore.ONE_HARD,(productionProcesses) -> 80)
+                .asConstraint("hasPreGbAfterNow");
+    }
+
+    /**
+     * 硬约束:同一个设备的不同的工步运行时间不能有交叉
+     * @param constraintFactory
+     * @return
+     */
+    private Constraint eqTimeCross(ConstraintFactory constraintFactory) {
+        return constraintFactory.forEach(ProductionProcesses.class)
+                .join(ProductionProcesses.class,Joiners.equal(ProductionProcesses::getEquipmentId))
+                .filter((proc1,proc2)->{
+                    if(proc1.getId() == proc2.getId()){
+                        return false;
+                    }
+                    Boolean b1 = (proc1.getStartTime().compareTo(proc2.getStartTime())<=0 && proc2.getStartTime().compareTo(proc1.getEndTime())<=0);
+                    Boolean b2 = (proc2.getStartTime().compareTo(proc1.getStartTime())<=0 && proc1.getStartTime().compareTo(proc2.getEndTime())<=0);
+                    return b1 || b2;
+                })
+                .penalize(HardSoftScore.ONE_HARD,(proc1,proc2)->100)
+                .asConstraint("eqTimeCross");
+    }
+
+    /**
+     * 软约束:交货日期,根据延迟交货日期的天数来做惩罚分数计算
+     * @param constraintFactory
+     * @return
+     */
+    private Constraint deliveryDate(ConstraintFactory constraintFactory) {
+        return constraintFactory.forEach(ProductionProcesses.class)
+                .filter(productionProcesses -> {
+                    /*
+                        获取最后一步工步的结束时间(最后一步工步的结束时间即此产品生产的实际结束时间)
+                        并且获取结束时间大于生产订单的交货日期
+                     */
+                    Boolean bol = productionProcesses.getNextProcesses() == null &&
+                            productionProcesses.getEndTime().atZone(zoneId).toInstant().toEpochMilli()>productionProcesses.getProduceOrder().getDeliveryDate().getTime();
+                    if(bol){
+                        System.out.println("endTime:"+productionProcesses.getEndTime());
+                        System.out.println("deliveryDate:"+productionProcesses.getProduceOrder().getDeliveryDate());
+                    }
+                    return bol;
+                })
+                .penalize(HardSoftScore.ONE_SOFT,
+                        (productionProcesses) ->
+//                                100
+                                ((int)(DateUtil.between(productionProcesses.getProduceOrder().getDeliveryDate(), Date.from(productionProcesses.getEndTime().atZone(zoneId).toInstant()), DateUnit.MINUTE)+1)*50)
+                )
+                .asConstraint("deliveryDate");
+    }
+
+    /**
+     * 均衡排产的软约束,任务尽量分配到不同设备上
+     * @param constraintFactory
+     * @return
+     */
+    private Constraint balancedEqUse(ConstraintFactory constraintFactory){
+        return constraintFactory.forEach(ProductionProcesses.class)
+                .filter((tc) -> {
+                    return true;
+                })
+                .groupBy(ProductionProcesses::getEquipmentId,count())
+                .filter((productionProcesses, num) -> num > 1)
+                .penalize(HardSoftScore.ONE_SOFT,
+                        (productionProcesses, num) -> num * 50)
+                .asConstraint("requiredMemory");
+    }
+
+    /**
+     * 合并生产工序,订单合并、尽量保证单次生产设备最大量
+     * @param constraintFactory
+     * @return
+     */
+    private Constraint mergeProcess(ConstraintFactory constraintFactory){
+        return constraintFactory.forEach(ProductionProcesses.class)
+                .filter((tc) -> {
+                    return true;
+                })
+                .penalize(HardSoftScore.ONE_SOFT,(productionProcesses) ->50)
+                .asConstraint("mergeProcess");
+    }
+}

+ 52 - 0
src/main/java/com/rongwei/cloudbalancing/CloudBalancingApplication.java

@@ -0,0 +1,52 @@
+package com.rongwei.cloudbalancing;
+
+import com.rongwei.cloudbalancing.domain.CloudBalance;
+import com.rongwei.cloudbalancing.domain.CloudComputer;
+import com.rongwei.cloudbalancing.domain.CloudProcess;
+import com.rongwei.cloudbalancing.persistence.CloudBalancingGenerator;
+import com.rongwei.cloudbalancing.score.CloudBalancingConstraintProvider;
+import lombok.extern.slf4j.Slf4j;
+import org.optaplanner.core.api.solver.Solver;
+import org.optaplanner.core.api.solver.SolverFactory;
+import org.optaplanner.core.config.solver.SolverConfig;
+
+import java.time.Duration;
+
+@Slf4j
+public class CloudBalancingApplication {
+    public static void main(String[] args) {
+        log.info("app start...");
+        SolverFactory<CloudBalance> solverFactory = SolverFactory.create(new SolverConfig()
+                .withSolutionClass(CloudBalance.class)
+                .withEntityClasses(CloudProcess.class)
+                .withConstraintProviderClass(CloudBalancingConstraintProvider.class)
+                .withTerminationSpentLimit(Duration.ofSeconds(5)));
+        Solver<CloudBalance> solver = solverFactory.buildSolver();
+
+        CloudBalance unsolvedCloudBalance = new CloudBalancingGenerator().createCloudBalance(3, 20);
+
+        System.out.println("\nProcesses:\n"
+                + unsolvedCloudBalance.toStringProcessList());
+
+        // Display the computer list
+        System.out.println("\nComputers:\n"
+                + unsolvedCloudBalance.toStringComputerList());
+
+        log.info("start to solve...");
+        CloudBalance solvedCloudBalance = solver.solve(unsolvedCloudBalance);
+
+        // Display the result
+        System.out.println("\nSolved cloudBalance with 3 computers and 12 processes:\n"
+                + toDisplayString(solvedCloudBalance));
+    }
+
+    public static String toDisplayString(CloudBalance cloudBalance) {
+        StringBuilder displayString = new StringBuilder();
+        for (CloudProcess process : cloudBalance.getProcessList()) {
+            CloudComputer computer = process.getComputer();
+            displayString.append("  ").append(process.getLabel()).append(" -> ")
+                    .append(computer == null ? null : computer.getLabel()).append("\n");
+        }
+        return displayString.toString();
+    }
+}

+ 30 - 0
src/main/java/com/rongwei/cloudbalancing/domain/AbstractPersistable.java

@@ -0,0 +1,30 @@
+package com.rongwei.cloudbalancing.domain;
+
+import org.optaplanner.core.api.domain.lookup.PlanningId;
+
+public abstract class AbstractPersistable {
+
+    protected Long id;
+
+    protected AbstractPersistable() {
+    }
+
+    protected AbstractPersistable(long id) {
+        this.id = id;
+    }
+
+    @PlanningId
+    public long getId() {
+        return id;
+    }
+
+    protected void setId(long id) {
+        this.id = id;
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getName().replaceAll(".*\\.", "") + "-" + id;
+    }
+
+}

+ 76 - 0
src/main/java/com/rongwei/cloudbalancing/domain/CloudBalance.java

@@ -0,0 +1,76 @@
+package com.rongwei.cloudbalancing.domain;
+
+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;
+
+import java.util.List;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+@NoArgsConstructor
+@PlanningSolution
+public class CloudBalance extends AbstractPersistable {
+    @Getter(value = AccessLevel.NONE)
+    private List<CloudComputer> computerList;
+
+    @Getter(value = AccessLevel.NONE)
+    private List<CloudProcess> processList;
+
+    @Getter(value = AccessLevel.NONE)
+    private HardSoftScore score;
+
+    public CloudBalance(long id, List<CloudComputer> computerList, List<CloudProcess> processList) {
+        super(id);
+        this.computerList = computerList;
+        this.processList = processList;
+    }
+
+    @ValueRangeProvider
+    @ProblemFactCollectionProperty
+    public List<CloudComputer> getComputerList() {
+        return computerList;
+    }
+
+    @ValueRangeProvider
+    @PlanningEntityCollectionProperty
+    public List<CloudProcess> getProcessList() {
+        return processList;
+    }
+
+    @PlanningScore
+    public HardSoftScore getScore() {
+        return score;
+    }
+
+    public String toStringProcessList() {
+        StringBuilder displayString = new StringBuilder();
+        List<CloudProcess> processes = this.getProcessList();
+        for (CloudProcess process : processes) {
+            displayString.append(" ").append(process.getLabel()).append(" -> ")
+                    .append("requiredCpuPower: ").append(process.getRequiredCpuPower()).append("; ")
+                    .append("requiredMemory: ").append(process.getRequiredMemory()).append("; ")
+                    .append("requiredNetworkBandwidth: ").append(process.getRequiredMemory()).append("; ")
+                    .append("\n");
+        }
+        return displayString.toString();
+    }
+
+    public String toStringComputerList() {
+        StringBuilder displayString = new StringBuilder();
+        List<CloudComputer> computers = this.getComputerList();
+        for (CloudComputer computer : computers) {
+            displayString.append(" ").append(computer.getLabel()).append(" -> ")
+                    .append("cpuPower: ").append(computer.getCpuPower()).append("; ")
+                    .append("memory: ").append(computer.getMemory()).append("; ")
+                    .append("networkBandwidth: ").append(computer.getNetworkBandwidth()).append("; ")
+                    .append("cost: ").append(computer.getCost()).append("; ")
+                    .append("\n");
+        }
+        return displayString.toString();
+    }
+}

+ 31 - 0
src/main/java/com/rongwei/cloudbalancing/domain/CloudComputer.java

@@ -0,0 +1,31 @@
+package com.rongwei.cloudbalancing.domain;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+@NoArgsConstructor
+public class CloudComputer extends AbstractPersistable {
+    private int cpuPower;
+    private int memory;
+    private int networkBandwidth;
+    private int cost;
+
+    public CloudComputer(long id, int cpuPower, int memory, int networkBandwidth, int cost) {
+        super(id);
+        this.cpuPower = cpuPower;
+        this.memory = memory;
+        this.networkBandwidth = networkBandwidth;
+        this.cost = cost;
+    }
+
+    public int getMultiplicand() {
+        return this.cpuPower * this.memory * this.networkBandwidth;
+    }
+
+    public String getLabel() {
+        return "Computer " + id;
+    }
+}

+ 40 - 0
src/main/java/com/rongwei/cloudbalancing/domain/CloudProcess.java

@@ -0,0 +1,40 @@
+package com.rongwei.cloudbalancing.domain;
+
+import com.rongwei.cloudbalancing.domain.solver.CloudComputerStrengthComparator;
+import com.rongwei.cloudbalancing.domain.solver.CloudProcessDifficultyComparator;
+import lombok.*;
+import org.optaplanner.core.api.domain.entity.PlanningEntity;
+import org.optaplanner.core.api.domain.variable.PlanningVariable;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+@NoArgsConstructor
+@PlanningEntity(difficultyComparatorClass = CloudProcessDifficultyComparator.class)
+public class CloudProcess extends AbstractPersistable {
+    private int requiredCpuPower;
+    private int requiredMemory;
+    private int requiredNetworkBandwidth;
+
+    @Getter(value = AccessLevel.NONE)
+    private CloudComputer computer;
+
+    public CloudProcess(long id, int requiredCpuPower, int requiredMemory, int requiredNetworkBandwidth) {
+        super(id);
+        this.requiredCpuPower = requiredCpuPower;
+        this.requiredMemory = requiredMemory;
+        this.requiredNetworkBandwidth = requiredNetworkBandwidth;
+    }
+
+    @PlanningVariable(strengthComparatorClass = CloudComputerStrengthComparator.class)
+    public CloudComputer getComputer() {
+        return computer;
+    }
+
+    public int getRequiredMultiplicand() {
+        return this.requiredCpuPower * this.requiredMemory * this.requiredNetworkBandwidth;
+    }
+
+    public String getLabel() {
+        return "Process " + id;
+    }
+}

+ 18 - 0
src/main/java/com/rongwei/cloudbalancing/domain/solver/CloudComputerStrengthComparator.java

@@ -0,0 +1,18 @@
+package com.rongwei.cloudbalancing.domain.solver;
+
+import com.rongwei.cloudbalancing.domain.CloudComputer;
+import static java.util.Comparator.comparingInt;
+import static java.util.Comparator.comparing;
+import java.util.Collections;
+import java.util.Comparator;
+
+public class CloudComputerStrengthComparator implements Comparator<CloudComputer> {
+    private static final Comparator<CloudComputer> COMPARATOR = comparingInt(CloudComputer::getMultiplicand)
+            .thenComparing(Collections.reverseOrder(comparing(CloudComputer::getCost)))
+            .thenComparingLong(CloudComputer::getId);
+
+    @Override
+    public int compare(CloudComputer a, CloudComputer b) {
+        return COMPARATOR.compare(a, b);
+    }
+}

+ 14 - 0
src/main/java/com/rongwei/cloudbalancing/domain/solver/CloudProcessDifficultyComparator.java

@@ -0,0 +1,14 @@
+package com.rongwei.cloudbalancing.domain.solver;
+
+import com.rongwei.cloudbalancing.domain.CloudProcess;
+import static java.util.Comparator.comparingInt;
+import java.util.Comparator;
+
+public class CloudProcessDifficultyComparator implements Comparator<CloudProcess> {
+    private static final Comparator<CloudProcess> COMPARATOR = comparingInt(CloudProcess::getRequiredMultiplicand)
+            .thenComparingLong(CloudProcess::getId);
+    @Override
+    public int compare(CloudProcess a, CloudProcess b) {
+        return COMPARATOR.compare(a, b);
+    }
+}

+ 235 - 0
src/main/java/com/rongwei/cloudbalancing/persistence/CloudBalancingGenerator.java

@@ -0,0 +1,235 @@
+package com.rongwei.cloudbalancing.persistence;
+
+import com.rongwei.cloudbalancing.domain.CloudBalance;
+import com.rongwei.cloudbalancing.domain.CloudComputer;
+import com.rongwei.cloudbalancing.domain.CloudProcess;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+@Slf4j
+public class CloudBalancingGenerator {
+    private static final int MAXIMUM_REQUIRED_CPU_POWER = 12; // in gigahertz
+    private static final int MAXIMUM_REQUIRED_MEMORY = 32; // in gigabyte RAM
+    private static final int MAXIMUM_REQUIRED_NETWORK_BANDWIDTH = 12; // in gigabyte per hour
+    protected Random random;
+
+    public CloudBalance createCloudBalance(int computerListSize, int processListSize) {
+        return createCloudBalance(determineFileName(computerListSize, processListSize),
+                computerListSize, processListSize);
+    }
+
+    private String determineFileName(int computerListSize, int processListSize) {
+        return computerListSize + "computers-" + processListSize + "processes";
+    }
+
+    public CloudBalance createCloudBalance(String inputId, int computerListSize, int processListSize) {
+        random = new Random(47);
+        List<CloudComputer> computerList = createComputerList(computerListSize);
+        List<CloudProcess> processList = createProcessList(processListSize);
+        CloudBalance cloudBalance = new CloudBalance(0, computerList, processList);
+        assureComputerCapacityTotalAtLeastProcessRequiredTotal(cloudBalance);
+        BigInteger possibleSolutionSize = BigInteger.valueOf(cloudBalance.getComputerList().size()).pow(
+                cloudBalance.getProcessList().size());
+        log.error("CloudBalance {} has {} computers and {} processes with a search space of {}.",
+                inputId, computerListSize, processListSize,
+                getFlooredPossibleSolutionSize(possibleSolutionSize));
+
+        return cloudBalance;
+    }
+
+    private List<CloudProcess> createProcessList(int processListSize) {
+        List<CloudProcess> processList = new ArrayList<>(processListSize);
+        for (int i = 0; i < processListSize; i++) {
+            CloudProcess process = generateProcess(i);
+            processList.add(process);
+        }
+        return processList;
+    }
+
+    private CloudProcess generateProcess(int id) {
+        int requiredCpuPower = generateRandom(MAXIMUM_REQUIRED_CPU_POWER);
+        int requiredMemory = generateRandom(MAXIMUM_REQUIRED_MEMORY);
+        int requiredNetworkBandwidth = generateRandom(MAXIMUM_REQUIRED_NETWORK_BANDWIDTH);
+        CloudProcess process = new CloudProcess(id, requiredCpuPower, requiredMemory, requiredNetworkBandwidth);
+        log.error("Created CloudProcess with requiredCpuPower ({}), requiredMemory ({}),"
+                        + " requiredNetworkBandwidth ({}).",
+                requiredCpuPower, requiredMemory, requiredNetworkBandwidth);
+        // Notice that we leave the PlanningVariable properties on null
+        return process;
+    }
+
+    private int generateRandom(int maximumValue) {
+        double randomDouble = random.nextDouble();
+        double parabolaBase = 2000.0;
+        double parabolaRandomDouble = (Math.pow(parabolaBase, randomDouble) - 1.0) / (parabolaBase - 1.0);
+        if (parabolaRandomDouble < 0.0 || parabolaRandomDouble >= 1.0) {
+            throw new IllegalArgumentException("Invalid generated parabolaRandomDouble (" + parabolaRandomDouble + ")");
+        }
+        int value = ((int) Math.floor(parabolaRandomDouble * maximumValue)) + 1;
+        if (value < 1 || value > maximumValue) {
+            throw new IllegalArgumentException("Invalid generated value (" + value + ")");
+        }
+        return value;
+    }
+
+    private List<CloudComputer> createComputerList(int computerListSize) {
+        List<CloudComputer> computerList = new ArrayList<>(computerListSize);
+        for (int i = 0; i < computerListSize; i++) {
+            CloudComputer computer = generateComputer(i);
+            computerList.add(computer);
+        }
+        return computerList;
+    }
+
+    private CloudComputer generateComputer(int id) {
+        int cpuPowerPricesIndex = random.nextInt(CPU_POWER_PRICES.length);
+        int memoryPricesIndex = distortIndex(cpuPowerPricesIndex, MEMORY_PRICES.length);
+        int networkBandwidthPricesIndex = distortIndex(cpuPowerPricesIndex, NETWORK_BANDWIDTH_PRICES.length);
+//        log.error("cpuIndex: {}, memoryIndex: {}, networkIndex: {}", cpuPowerPricesIndex, memoryPricesIndex, networkBandwidthPricesIndex);
+
+        int cost = CPU_POWER_PRICES[cpuPowerPricesIndex].getCost()
+                + MEMORY_PRICES[memoryPricesIndex].getCost()
+                + NETWORK_BANDWIDTH_PRICES[networkBandwidthPricesIndex].getCost();
+
+        CloudComputer computer = new CloudComputer(id, CPU_POWER_PRICES[cpuPowerPricesIndex].getHardwareValue(),
+                MEMORY_PRICES[memoryPricesIndex].getHardwareValue(),
+                NETWORK_BANDWIDTH_PRICES[networkBandwidthPricesIndex].getHardwareValue(),
+                cost);
+
+        log.error("Created computer with cpuPowerPricesIndex ({}), memoryPricesIndex ({}),"
+                        + " networkBandwidthPricesIndex ({}).",
+                cpuPowerPricesIndex, memoryPricesIndex, networkBandwidthPricesIndex);
+
+        return computer;
+    }
+
+    private int distortIndex(int referenceIndex, int length) {
+        int index = referenceIndex;
+        double randomDouble = random.nextDouble();
+        double loweringThreshold = 0.25;
+        while (randomDouble < loweringThreshold && index >= 1) {
+            index--;
+            loweringThreshold *= 0.10;
+        }
+        double heighteningThreshold = 0.75;
+        while (randomDouble >= heighteningThreshold && index <= (length - 2)) {
+            index++;
+            heighteningThreshold = (1.0 - ((1.0 - heighteningThreshold) * 0.10));
+        }
+        return index;
+    }
+
+    private static final Price[] CPU_POWER_PRICES = { // in gigahertz
+            new Price(3, "single core 3ghz", 110),
+            new Price(4, "dual core 2ghz", 140),
+            new Price(6, "dual core 3ghz", 180),
+            new Price(8, "quad core 2ghz", 270),
+            new Price(12, "quad core 3ghz", 400),
+            new Price(16, "quad core 4ghz", 1000),
+            new Price(24, "eight core 3ghz", 3000),
+    };
+
+    private static final Price[] MEMORY_PRICES = { // in gigabyte RAM
+            new Price(2, "2 gigabyte", 140),
+            new Price(4, "4 gigabyte", 180),
+            new Price(8, "8 gigabyte", 220),
+            new Price(16, "16 gigabyte", 300),
+            new Price(32, "32 gigabyte", 400),
+            new Price(64, "64 gigabyte", 600),
+            new Price(96, "96 gigabyte", 1000),
+    };
+
+    private static final Price[] NETWORK_BANDWIDTH_PRICES = { // in gigabyte per hour
+            new Price(2, "2 gigabyte", 100),
+            new Price(4, "4 gigabyte", 200),
+            new Price(6, "6 gigabyte", 300),
+            new Price(8, "8 gigabyte", 400),
+            new Price(12, "12 gigabyte", 600),
+            new Price(16, "16 gigabyte", 800),
+            new Price(20, "20 gigabyte", 1000),
+    };
+
+    @Getter
+    private static class Price {
+
+        private final int hardwareValue;
+        private final String description;
+        private final int cost;
+
+        private Price(int hardwareValue, String description, int cost) {
+            this.hardwareValue = hardwareValue;
+            this.description = description;
+            this.cost = cost;
+        }
+    }
+
+    private void assureComputerCapacityTotalAtLeastProcessRequiredTotal(CloudBalance cloudBalance) {
+        List<CloudComputer> computerList = cloudBalance.getComputerList();
+        int cpuPowerTotal = 0;
+        int memoryTotal = 0;
+        int networkBandwidthTotal = 0;
+        for (CloudComputer computer : computerList) {
+            cpuPowerTotal += computer.getCpuPower();
+            memoryTotal += computer.getMemory();
+            networkBandwidthTotal += computer.getNetworkBandwidth();
+        }
+        int requiredCpuPowerTotal = 0;
+        int requiredMemoryTotal = 0;
+        int requiredNetworkBandwidthTotal = 0;
+        for (CloudProcess process : cloudBalance.getProcessList()) {
+            requiredCpuPowerTotal += process.getRequiredCpuPower();
+            requiredMemoryTotal += process.getRequiredMemory();
+            requiredNetworkBandwidthTotal += process.getRequiredNetworkBandwidth();
+        }
+        int cpuPowerLacking = requiredCpuPowerTotal - cpuPowerTotal;
+        while (cpuPowerLacking > 0) {
+            CloudComputer computer = computerList.get(random.nextInt(computerList.size()));
+            int upgrade = determineUpgrade(cpuPowerLacking);
+            computer.setCpuPower(computer.getCpuPower() + upgrade);
+            cpuPowerLacking -= upgrade;
+        }
+        int memoryLacking = requiredMemoryTotal - memoryTotal;
+        while (memoryLacking > 0) {
+            CloudComputer computer = computerList.get(random.nextInt(computerList.size()));
+            int upgrade = determineUpgrade(memoryLacking);
+            computer.setMemory(computer.getMemory() + upgrade);
+            memoryLacking -= upgrade;
+        }
+        int networkBandwidthLacking = requiredNetworkBandwidthTotal - networkBandwidthTotal;
+        while (networkBandwidthLacking > 0) {
+            CloudComputer computer = computerList.get(random.nextInt(computerList.size()));
+            int upgrade = determineUpgrade(networkBandwidthLacking);
+            computer.setNetworkBandwidth(computer.getNetworkBandwidth() + upgrade);
+            networkBandwidthLacking -= upgrade;
+        }
+    }
+
+    private int determineUpgrade(int lacking) {
+        for (int upgrade : new int[]{8, 4, 2, 1}) {
+            if (lacking >= upgrade) {
+                return upgrade;
+            }
+        }
+        throw new IllegalStateException("Lacking (" + lacking + ") should be at least 1.");
+    }
+
+    public static String getFlooredPossibleSolutionSize(BigInteger possibleSolutionSize) {
+        if (possibleSolutionSize == null) {
+            return null;
+        }
+        if (possibleSolutionSize.compareTo(BigInteger.valueOf(1000L)) < 0) {
+            return possibleSolutionSize.toString();
+        }
+        BigDecimal possibleSolutionSizeBigDecimal = new BigDecimal(possibleSolutionSize);
+        int decimalDigits = possibleSolutionSizeBigDecimal.scale() < 0
+                ? possibleSolutionSizeBigDecimal.precision() - possibleSolutionSizeBigDecimal.scale()
+                : possibleSolutionSizeBigDecimal.precision();
+        return "10^" + decimalDigits;
+    }
+}

+ 63 - 0
src/main/java/com/rongwei/cloudbalancing/score/CloudBalancingConstraintProvider.java

@@ -0,0 +1,63 @@
+package com.rongwei.cloudbalancing.score;
+
+import com.rongwei.cloudbalancing.domain.CloudComputer;
+import com.rongwei.cloudbalancing.domain.CloudProcess;
+import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
+import org.optaplanner.core.api.score.stream.Constraint;
+import org.optaplanner.core.api.score.stream.ConstraintFactory;
+import org.optaplanner.core.api.score.stream.ConstraintProvider;
+
+import java.util.function.Function;
+
+import static org.optaplanner.core.api.score.stream.ConstraintCollectors.sum;
+import static org.optaplanner.core.api.score.stream.Joiners.equal;
+
+public class CloudBalancingConstraintProvider implements ConstraintProvider {
+    @Override
+    public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
+        return new Constraint[]{
+                requiredCpuPowerTotal(constraintFactory),
+                requiredMemoryTotal(constraintFactory),
+                requiredNetWorkBandWidthTotal(constraintFactory),
+                computerCost(constraintFactory)
+        };
+    }
+
+    // ************************************************************************
+    // Hard constraints
+    // ************************************************************************
+    private Constraint requiredCpuPowerTotal(ConstraintFactory constraintFactory) {
+        return constraintFactory.forEach(CloudProcess.class)
+                .groupBy(CloudProcess::getComputer, sum(CloudProcess::getRequiredCpuPower))
+                .filter((computer, requiredCpuPower) -> requiredCpuPower > computer.getCpuPower())
+                .penalize(HardSoftScore.ONE_HARD,
+                        (computer, requiredCpuPower) -> requiredCpuPower - computer.getCpuPower())
+                .asConstraint("requiredCpuPower");
+    }
+
+    private Constraint requiredMemoryTotal(ConstraintFactory constraintFactory) {
+        return constraintFactory.forEach(CloudProcess.class)
+                .groupBy(CloudProcess::getComputer, sum(CloudProcess::getRequiredMemory))
+                .filter((computer, requiredMemory) -> requiredMemory > computer.getMemory())
+                .penalize(HardSoftScore.ONE_HARD,
+                        (computer, requiredMemory) -> requiredMemory - computer.getMemory())
+                .asConstraint("requiredMemory");
+    }
+
+    private Constraint requiredNetWorkBandWidthTotal(ConstraintFactory constraintFactory) {
+        return constraintFactory.forEach(CloudProcess.class)
+                .groupBy(CloudProcess::getComputer, sum(CloudProcess::getRequiredNetworkBandwidth))
+                .filter((computer, requiredNetWorkBandWidth) -> requiredNetWorkBandWidth > computer.getNetworkBandwidth())
+                .penalize(HardSoftScore.ONE_HARD,
+                        (computer, requiredNetWorkBandWidth) -> requiredNetWorkBandWidth - computer.getNetworkBandwidth())
+                .asConstraint("requiredNetWorkBandWidth");
+    }
+
+    private Constraint computerCost(ConstraintFactory constraintFactory) {
+        return constraintFactory.forEach(CloudComputer.class)
+                .ifExists(CloudProcess.class, equal(Function.identity(), CloudProcess::getComputer))
+                .penalize(HardSoftScore.ONE_SOFT, CloudComputer::getCost)
+                .asConstraint("computerCost");
+    }
+
+}

+ 33 - 0
src/main/resources/logback.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+
+    <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <!-- %L lowers performance, %C and %c break indentation and therefore reduce readability, normal %t is verbose -->
+            <pattern>%d{HH:mm:ss.SSS} [%-12.12t] %-5p %m%n</pattern>
+        </encoder>
+    </appender>
+    <!--<appender name="fileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">-->
+    <!--<file>local/log/optaplanner.log</file>-->
+    <!--<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">-->
+    <!--<fileNamePattern>local/log/optaplanner.%i.log.zip</fileNamePattern>-->
+    <!--<minIndex>1</minIndex>-->
+    <!--<maxIndex>3</maxIndex>-->
+    <!--</rollingPolicy>-->
+    <!--<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">-->
+    <!--<maxFileSize>5MB</maxFileSize>-->
+    <!--</triggeringPolicy>-->
+    <!--<encoder>-->
+    <!--<pattern>%d{HH:mm:ss.SSS} [%t] %-5p %m%n</pattern>-->
+    <!--</encoder>-->
+    <!--</appender>-->
+
+    <!-- To override the debug log level from the command line, use the VM option "-Dlogback.level.org.optaplanner=trace" -->
+    <!--    <logger name="org.optaplanner" level="${logback.level.org.optaplanner:-debug}"/>-->
+
+    <root level="error">
+        <appender-ref ref="consoleAppender"/>
+        <!--<appender-ref ref="fileAppender" />-->
+    </root>
+
+</configuration>