4.2 函数最值求解

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

内容概要

上一篇章我们学习了如何使用OptaPlanner来求解N皇后问题,今天呢,我们来学习使用OptaPlanner来解决,高等数学的一些个例题,简单的求极值问题。通过这个例子,我们可以回顾我们所学习的一些知识点,还是很有必要的。

问题描述

在市场经营、工程技术及日常生活中,常常会遇到这样一类问题:在一定的条件下,怎样使“成本最低”、“用料最省”、“效率最高”等问题,而这类问题在数学上有时可以归结求某一函数的最大值或最小值问题。我们来看这样一个例题:

一房地产公司有50套公寓要出租,当月租金定为1000元时,公寓会全部租出去,当月租金每增加 50元,就会多一套租不出去,而租出去的公寓每月需花费 100元维修费,则房租定为多少元时可获得最大收入。
解答∶设x套为没有租出去的公寓数,则收入函数f(x)=(1000+50x)(50-x)-100(50-x),。.f'(x)=1600-100x,∶当x=16时,f(x)取最大值,把租金定为1800元时,收入最大。
答案∶ 1800

问题建模

从这个例题当中,我们可以得出几个信息,PlanningEntity规划实体和PlanningVariable规划变量是什么,哪些是ProblemFact问题事实。

roomret.png

PlanningVariable

从例题当中可以很容易的找到一个PlanningVariable变量roomRent租金,而且这个例题的PlanningEntity只有一个,之前我们学习的案例中都是多个PlanningEntity,都是以Collection的形式,作为Solution类的属性。这次我们只需要PlanningEntityProperty注解这单个对象作为属性即可。

@PlanningEntity(difficultyWeightFactoryClass = RoomRetWeightFactory.class)
public class RoomRet extends AbstractPersistable implements Comparable<RoomRet> {

    @CustomShadowVariable(variableListenerClass = RoomRetUpdateVariableListener.class,
            sources = {@PlanningVariableReference(variableName = "roomRet")})
    private Integer leasedOut;

    @PlanningVariable(valueRangeProviderRefs = "roomRetRange", strengthComparatorClass = RoomStrengthComparator.class)
    private Integer roomRet;


    public Integer getLeasedOut() {
        return leasedOut;
    }

    public void setLeasedOut(Integer leasedOut) {
        this.leasedOut = leasedOut;
    }

    public Integer getRoomRet() {
        return roomRet;
    }

    public void setRoomRet(Integer roomRet) {
        this.roomRet = roomRet;
    }

    @Override
    public int compareTo(RoomRet o) {
        return 0;
    }
}
复制代码

VariableRange变量值范围

例题当中我们看到的变量范围是1000租金起,每次递增50,没有上限,但是我们为了求解计算方法,给它设定一个上线,租金最高只能5000,所以我们需要提供一个Collection作为roomRent变量值的范围。

// Range: 1000, 1050, 1100, 1150, ..., 4950, 5000
ValueRangeFactory.createIntValueRange(1000, 5000, 50);
复制代码

Solution类

Solution的类相对而言就很简单了,如下:

@PlanningSolution
public class RoomRetSolution extends AbstractPersistable {


    private Integer roomCount = 50;

    @PlanningEntityProperty
    private RoomRet roomRet;

    @PlanningScore
    private SimpleScore score;

    @ValueRangeProvider(id = "roomRetRange")
    public CountableValueRange<Integer> getRoomRetRange() {
        // Range: 1000, 1050, 1100, 1150, ..., 4950, 5000
        return ValueRangeFactory.createIntValueRange(1000, 5000, 50);
    }

    public RoomRet getRoomRet() {
        return roomRet;
    }

    public void setRoomRet(RoomRet roomRet) {
        this.roomRet = roomRet;
    }

    @ProblemFactProperty
    public Integer getRoomCount() {
        return roomCount;
    }

    public void setRoomCount(Integer roomCount) {
        this.roomCount = roomCount;
    }

    public SimpleScore getScore() {
        return score;
    }

    public void setScore(SimpleScore score) {
        this.score = score;
    }
}
复制代码

ShadowVariable影子变量监听器

这里我们使用到了一个ShadowVariable影子变量,其属性为leasedOut已租出,因为它不是一个PlanningVariable,而是根据roomRent的变化而发生变化,因为每增加50元,则少租1个房屋。

public class RoomRetUpdateVariableListener implements VariableListener<RoomRetSolution, RoomRet> {
    @Override
    public void beforeEntityAdded(ScoreDirector<RoomRetSolution> scoreDirector, RoomRet roomRet) {
        this.setRet(scoreDirector, roomRet);
    }

 	......
    @Override
    public void afterVariableChanged(ScoreDirector<RoomRetSolution> scoreDirector, RoomRet roomRet) {
        this.setRet(scoreDirector, roomRet);
    }
	......

    private void setRet(ScoreDirector<RoomRetSolution> scoreDirector, RoomRet roomRet) {
        Integer ret = roomRet.getRoomRet();
        if(ret == null) {
            return;
        }

        if (ret <= 1000) {
            scoreDirector.beforeVariableChanged(roomRet, "leasedOut");
            roomRet.setLeasedOut(50);
            scoreDirector.afterVariableChanged(roomRet, "leasedOut");
            return;
        }
        Integer leaseOut = 50 - (roomRet.getRoomRet() - 1000) / 50;
        scoreDirector.beforeVariableChanged(roomRet, "leasedOut");
        roomRet.setLeasedOut(leaseOut);
        scoreDirector.afterVariableChanged(roomRet, "leasedOut");
        return;
    }
}

复制代码

先用后学习,后面我们会讲到影子变量,在这里我们先简单使用一下,使大家对ShadowVariable有一个大概的理解。

约束评分规则

约束评分的规则,我们发现没有约束条件,只有一个结果,就是总的收益越高越好

其实这一条就可以转换成一个规则,我们将收益作为一个分值即可,因为OptaPlanner就是求最高分的解决方案的。

package org.optaplanner.examples.house.solver;
    dialect "java"

import org.optaplanner.core.api.score.buildin.simple.SimpleScoreHolder;
import org.optaplanner.examples.house.domain.RoomRetSolution;
import org.optaplanner.examples.house.domain.RoomRet;
global SimpleScoreHolder scoreHolder;

rule "Maximum Profit"
    when
        RoomRet(roomRet != null, leasedOut != null, $leasedOut : leasedOut, $roomRet : roomRet)
    then
        Integer profit = ($leasedOut * $roomRet) - ($leasedOut * 100);
        scoreHolder.addConstraintMatch(kcontext, profit);
end
复制代码

运行求解

大家在执行时,请开启OptaPlanner日志的debug模式,可以观察下求解的结果。

logging.level.org.optaplanner=debug
复制代码

执行结果:

14:03:35.946 [main        ] DEBUG Service org.kie.api.internal.weaver.KieWeavers is implemented by org.kie.internal.services.KieWeaversImpl@b62d79
14:03:40.536 [main        ] INFO  Solving started: time spent (80), best score (-1init/0), environment mode (REPRODUCIBLE), move thread count (NONE), random (JDK with seed 0).
14:03:40.596 [main        ] DEBUG     CH step (0), time spent (140), score (57800), selected move count (80), picked move (RoomRet-1 {null -> 1800}).
14:03:40.596 [main        ] INFO  Construction Heuristic phase (0) ended: time spent (140), best score (57800), score calculation speed (1350/sec), step total (1).
14:03:40.596 [main        ] INFO  Solving ended: time spent (140), best score (57800), score calculation speed (585/sec), phase total (1), environment mode (REPRODUCIBLE), move thread count (NONE).
ret = 1800
复制代码

结果rent = 1800元,这与答案是一样的。

其中:

DEBUG     CH step (0), time spent (140), score (57800), selected move count (80), picked move (RoomRet-1 {null -> 1800}).
复制代码

这一条debug日志发现,只通过了一步step,就得到了结果。当然在之前做了很多的操作,后续可以通过trace来查看。

总结

通过这个例子,我们学习了ShadowVariable的使用方式,其实也可以不使用,只是为了引入影子变量的使用方法,所以在此例子当中加入了这样的写法。(代码中的ret拼错了,是rent)。

结束语

下一篇章我们来学习一其它的例子。

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

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享