2.3 PlanningVariable的特性

1,064 阅读6分钟

这是我参与更文挑战的第6天,活动详情查看: 更文挑战

内容概要

今天我们主要来学PlanningVariable的一些特性,这些特性决定着我们的业务模型如何建立,所以十分重要。所以跟上一篇章区分开,单独在这里学习。

Planning Value

PlanningValue规划值是一个规划变量的可能值。通常情况下,规划值是一个ProblemFact问题事实,但它也可以是任何对象,例如一个double,它甚至可以是另一个规划实体,甚至是一个由规划实体和问题事实共同实现的接口。 规划值范围是一个规划变量的可能规划值的集合。这个集合可以是固定(例如1、2、3或4行)或随机的(例如0.0和1.0之间的任何双数)。

PlanningValue的范围

一个规划变量的value范围是用@ValueRangeProvider注解来定义的。一个@ValueRangeProvider注解总是有一个属性ID,它被@PlanningVariable的属性valueRangeProviderRefs所引用。

@PlanningEntity
public class Lesson {

    private Long id;
    ......
    @PlanningVariable(valueRangeProviderRefs = "timeslotRange")
    private Timeslot timeslot;
    ......
}

@ValueRangeProvider这个注解可以位于两种类型的方法上。

  • 在注解@PlanningSolution的类上:所有规划实体共享相同的数据集合。
  • PlanningEntity规划实体上:每个规划实体的值范围不同,这种情况不太常见。

@ValueRangeProvider注解需要在具有@PlanningSolution或@PlanningEntity注解的类中的一个方法或者属性上。它在没有这些注解的父类或子类上会被忽略。

该方法的返回类型可以是三种类型:

  • Collection:值范围由其可能值的集合(通常是一个List)来定义。
  • Array:值范围是由一个可能的值的数组定义的。
  • ValueRange:值范围是由其边界定义的。这种情况不太常见。

ValueRangeProvider在Solution类

同一规划实体类的所有实例共享该规划变量的同一组可能规划值。这是最常见的配置值范围的方法。 @PlanningSolution的实现有方法返回一个集合(或一个ValueRange)。该集合中的任何值都是该规划变量的可能规划值。

  @PlanningVariable(valueRangeProviderRefs = {"rowRange"})
    public Row getRow() {
        return row;
    }
@PlanningSolution
public class NQueens {
    ...

    @ValueRangeProvider(id = "rowRange")
    public List<Row> getRowList() {
        return rowList;
    }

}

注意该集合(或ValueRange)不能包含null值或者直接为null
也直接注解在属性上:

@PlanningSolution
public class NQueens {
    ...

    @ValueRangeProvider(id = "rowRange")
    private List<Row> rowList;

}

ValueRangeProvider在规划实体

每个PlanningEntity规划实体都有自己的规划变量的值范围(一组可能的规划值)。例如:如果一个教师永远不能在不属于他的部门的房间里授课,那么该教师的讲座可以将他们的房间范围限制在他的部门的房间范围里。

 @PlanningVariable(valueRangeProviderRefs = {"departmentRoomRange"})
    public Room getRoom() {
        return room;
    }

    @ValueRangeProvider(id = "departmentRoomRange")
    public List<Room> getPossibleRoomList() {
        return getCourse().getTeacher().getDepartment().getRoomList();
    }

千万不要用它来强制执行软约束(甚至当问题可能没有可行的解决方案时,也不要用硬约束)。比如说:除非没有其他办法,否则一个教师不能在一个不属于他的部门的教室里上课,在这种情况下,教师不应该被限制他的教室范围(因为有时没有其他办法就只剩其它部门的教室,他必须要给学生上课)。

限制一个规划实体的数值范围,实际上是在创造一个内在的硬约束。这样做的好处是严重降低了可能的解决方案的数量;然而,它也会剥夺优化算法的自由,使其暂时打破该约束,以摆脱局部最优的情况。

ValueRangeFactory工程类

支持返回一个由ValueRangeFactory构建的ValueRangeCountableValueRange,而不是一个集合。

@ValueRangeProvider(id = "delayRange")
    public CountableValueRange<Integer> getDelayRange() {
        return ValueRangeFactory.createIntValueRange(0, 5000);
    }

ValueRange使用的内存要少得多,因为它只保存边界。在上面的例子中,一个集合需要保存所有5000个整数,而不仅仅是两个边界。

此外,还可以指定一个增量单位(incrementUnit),例如:如果你必须以200块为单位购买股票。 ValueRangeFactory有几种值类类型的创建方法:

  • boolean: 一个布尔值范围。
  • int:一个32位的整数范围。
  • long:一个64位的整数范围。
  • double:一个64位的浮点范围。一个64位的浮点范围,只支持随机选择(因为它没有实现CountableValueRange)。
  • BigInteger:一个任意精度的整数范围。
  • BigDecimal:一个小数点范围。默认情况下,增量单位是界限规模中的最低非零值。
  • Temporal:(如LocalDateLocalDateTime,...)。一个时间范围。

组合ValueRangeProviders

值范围可以合并,例如:

@PlanningVariable(valueRangeProviderRefs = {"companyCarRange", "personalCarRange"})
    public Car getCar() {
        return car;
    }
 @ValueRangeProvider(id = "companyCarRange")
    public List<CompanyCar> getCompanyCarList() {
        return companyCarList;
    }

    @ValueRangeProvider(id = "personalCarRange")
    public List<PersonalCar> getPersonalCarList() {
        return personalCarList;
    }

PlanningValue权重

一些优化算法如果能估算出哪些规划值更强,可以优先提供,也就是更有可能满足一个规划实体,那么工作起来就会性能会提高。

例如:在垃圾箱包装中,更大的容器更有可能装下一个物品,在课程安排中,更大的房间更不可能打破学生的容量约束。通常情况下,规划值强度的效率增益远远小于规划实体的难度。

不要尝试使用规划值强度来实现业务约束。它不会影响得分函数:如果我们有无限的求解时间,返回的解决方案将是相同的。
要影响得分函数,请添加一个得分约束。只有在可以使求解器提高性能的情况下,才考虑也添加规划值强度。

为了让启发式方法能够利用该对象的特定信息,需要@PlanningVariable注解设置一个强度比较器类。

 @PlanningVariable(..., strengthComparatorClass = CloudComputerStrengthComparator.class)
    public CloudComputer getComputer() {
        return computer;
    }
public class CloudComputerStrengthComparator implements Comparator<CloudComputer> {

    public int compare(CloudComputer a, CloudComputer b) {
        return new CompareToBuilder()
                .append(a.getMultiplicand(), b.getMultiplicand())
                .append(b.getCost(), a.getCost()) // Descending (but this is debatable)
                .append(a.getId(), b.getId())
                .toComparison();
    }

}

如上例子,在提供Computer值时,优先提供资源比较大的Computer

如果你在同一数值范围内有多个规划值类,那么strengthComparatorClass需要实现一个共同的父类(例如Comparator<Object>)的比较器,并能够处理这些不同类的实例的比较。

链式规划变量

这一内容,我们暂时不在这里学习,后面的篇章,我们结合车辆路线优化的案例学习时,来学习这块内容。

总结

这一篇章主要学习了OptaPlanner的PlanningVariable一些特性,这些特性在建模时都会使用到。

作业

大家可以将TimeTable的例子中的规划变量,区分成多个提供者,以及在不同的类上,来体会一下实际的效果。

结束语

下一篇章,我们来学习Solution类,这个类是与OptaPlanner交互的核心类。

创作不易,禁止未授权的转载。如果我的文章对您有帮助,就请点赞/收藏/关注鼓励支持一下吧❤❤❤❤❤❤