OptaPlanner的新約束表達方式 Constraint Streams

  有好些時間沒有寫過關於OptaPlanner的東西了,其實近半年來,OptaPlanner仍是推出了很多有用、好用的新特性。包括本文講到的以Stream接口實現評分編程。關於OptraPlanner的約束詳細用法,能夠參考官方資料.html

  最近幾個版本推出的新功能、特性中,有很多功能還處於初始探索階段,甚至有些功能還未成體系,包括我在上一篇文件中推出的SolverManger實現批量異步規劃。此功能還沒有支持ProblemChanged接口,從而沒法實現Realtime Planning. 所以,若須要將這些功能應用於項目實踐,還請自行做詳細調查分析,以避免在項目中處於進退兩難境地。java

PS. 任何技術都同樣,功能、版本越新,帶來的收益越高,固然須要面對的風險也越高。node

  對OptaPlanner有初步認識都清楚,咱們使用OptaPlanner規劃建模時,須要在模型中表達一系列約束,以描述各個業務實體的約束和規劃的優化目標。以往一般有兩種方式實現評分邏輯(詳細可分爲3種)。分別是:web

1. 經過Drools腳本中的Rule來描述約束並進行評分;編程

2. 經過Java編寫評分邏輯,經過Java編輯評分邏輯又分爲:數組

   2.1. Java簡易評分 - Easy Java score calculation微信

   2.2. Java增量評分 - Incremental Java score calculation網絡

  從7.31版本開始提供的constraint streams屬於Java增量評分的一種。在普通的Java增量評分中,咱們須要針對各個約束邏輯,編輯相應的判斷,並在知足必定條件後,經過ScoreHolder對象進行記分。引擎會將各個層次的分數進行累加,成爲當前方案的總分。Constraint Streams的原理也同樣,只是經過強大的Stream特性,令評分邏輯更爲簡潔,使用更短的代碼便可實現更豐富的邏輯描述。app

  關於Java的Stream特性(Java1.8及之後的版本纔出現)的使用方法,可自行經過其它網絡資源學習,本文假設讀者熟悉Java Stream的各類用法。異步

咱們先以一個簡單的示例說明Constraint streams接口的使用方法:

private int doNotAssignAnn() {
        int softScore = 0;
        schedule.getShiftList().stream()
                .filter(Shift::isEmployeeAnn)
                .forEach(shift -> {
                    softScore -= 1;
                });
        return softScore;
    }

  經過上述代碼塊是一Java簡易評分的示例,從方法名doNotAssignAnn就很容易理解到,該約束的做用是「使得任務不要分配給Ann」。咱們知道在OptaPlanner裏,評分一般都是負數,表示懲罰一個行爲,令引擎找出儘量規避這種行爲的方案。示例中使用了Java的Stream功能進行判斷和過濾。其邏輯是:從班次列表中找出全部分配給了Ann的班次,對每個知足這個條件的班次進行扣分,並把分數加總做爲方法的返回值。

  那麼一樣的約束要求,使用Constraint Stream應該如何實現呢?見如下代碼:

private Constraint doNotAssignAnn(ConstraintFactory factory) {
        return factory.from(Shift.class)
                .filter(Shift::isEmployeeAnn)
                .penalize("Don't assign Ann", HardSoftScore.ONE_SOFT);
    }

  先要提醒一下,與Java簡單評分法相似(評分類須要實現EasyScoreCalculator接口),OptaPlanner的Constraint Stream提供一個名爲ConstraintProvider的接口,實現評分的類須要實現這個接口,這個接口只有一個須要實現的方法 - defineConstraints,它傳入ConstrantFactory類,返回一個Constraint數組,數組的元素就是已進行了評分和懲罰的各個約束對象。上面的代碼中能夠看到,doNotAssignAnn方法返回一個Constraint對象,這個對象表示了對Ann被分配到的班次數的懲罰分數。上述代碼能夠看到,咱們只須要對ConstraintFactory的對象factory進行Stream操做,一步便可完成判斷、過濾和懲罰三個操做,完成這些操做後會獲得一個操做過的Contraint對象,返回該對象便可。上述代碼中,對於factory的三步操做也至關明瞭,你們能夠本身理解。

  可是對於一些更復雜的判斷,其實現步驟與模式也同樣,只不過須要編寫一些更復雜的Lambda表達式來進行判斷、過濾和各類運算。以下代碼:

private Constraint requiredCpuPowerTotal(ConstraintFactory factory) {
        return factory.from(CloudProcess.class)
                .groupBy(CloudProcess::getComputer, sum(CloudProcess::getRequiredCpuPower))
                .filter((computer, requiredCpuPower) -> requiredCpuPower > computer.getCpuPower())
                .penalize("requiredCpuPowerTotal",
                        HardSoftScore.ONE_HARD,
                        (computer, requiredCpuPower) -> requiredCpuPower - computer.getCpuPower());
    }

  該代碼是CloudBalance中用於,計算限制一臺計劃機被分配超出其CPU運算能力的約束。你們能夠回想,或從官方示例中看一下CloudBalance的其中一個最基本約束 - 每臺計算機所分得的CPU需求,不可超過該計算機的可用CPU能力。

  所以,能夠看到,factory除了過from操做得到全部Process對象,經過filter對Process進行過濾,經過penalize進行計分外。factory對象還有一個groupBy方法,用於對全部Process中的Computer進行分組並加總每一組(即每一個Computer)的全部CPU計算能力需求量。所以,在filter方法中,就找出那些超出CPU能力的Computer(即分組),在penalize方法中,對整全部超出CPU需求中的計算進行扣分,扣分值是超出部分。

  由此可能,OptaPlanner提供的Constraint Stream能夠進行更復雜的條件判斷,至於這種方法是否更好用,就取決於你們對Stream(相似C#中的Linq)的熟悉程度。

  至於整個Constraint Stream代碼的結果方式,即上面提到的實現ConstraintProvider接口的代碼以下(摘自官方示例CloudBalance):

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.from(CloudProcess.class)
                .groupBy(CloudProcess::getComputer, sum(CloudProcess::getRequiredCpuPower))
                .filter((computer, requiredCpuPower) -> requiredCpuPower > computer.getCpuPower())
                .penalize("requiredCpuPowerTotal",
                        HardSoftScore.ONE_HARD,
                        (computer, requiredCpuPower) -> requiredCpuPower - computer.getCpuPower());
    }
.
.
.

  重複提示一下,Constraint Stream功能是7.31版纔開始提供的功能,從功能接口上應該是未夠成功的,若是須要在項目中實現一些更爲複雜的約束描述,建議暫時仍是不要直接使用。在OptaPlanner的用戶手冊中,也有相關的提示;你們看狀況而用。

  最近一段時間OptaPlanner更新算比較頻繁,但從網站上的更新內容看到,不少版本儘管隔了很長時間,但接口上的更新內容卻很少。我向Geoffrey查詢過,他表示這些版本更多的狀況是在實現一些引擎內部的優化和一些新的內部運算功能,但這些功能不必定反映到API上,所以對於咱們使用者來講,並無太大的變化。但是若是你們也跟進將OptaPlanner的程序包也更新到最新版本,就會發現,不少一些經常使用的接口、方法,都已經被標準爲將爲放棄,從Javadocs上能夠看到一些當前版本被標識爲@Deprecated的方法、成員,已明確說明將在8.x中中止使用。

  上述功能但願能夠幫你們理解並應用OptaPlanner的第四種評分方式。

有好些時間沒有寫過關於OptaPlanner的東西了,其實近半年來,OptaPlanner仍是推出了很多有用、好用的新特性。包括本文講到的以Stream接口實現評分編程。關於OptraPlanner的約束詳細用法,能夠參考官方資料:

Constraint streams score calculationdocs.optaplanner.org

最近幾個版本推出的新功能、特性中,有很多功能還處於初始探索階段,甚至有些功能還未成體系,包括我在上一篇文件中推出的SolverManger實現批量異步規劃。此功能還沒有支持ProblemChanged接口,從而沒法實現Realtime Planning. 所以,若須要將這些功能應用於項目實踐,還請自行做詳細調查分析,以避免在項目中處於進退兩難境地。

PS. 任何技術都同樣,功能、版本越新,帶來的收益越高,固然須要面對的風險也越高。

對OptaPlanner有初步認識都清楚,咱們使用OptaPlanner規劃建模時,須要在模型中表達一系列約束,以描述各個業務實體的約束和規劃的優化目標。以往一般有兩種方式實現評分邏輯(詳細可分爲3種)。分別是:

  1. 經過Drools腳本中的Rule來描述約束並進行評分;
  2. 經過Java編寫評分邏輯,經過Java編輯評分邏輯又分爲:
    1. Java簡易評分 - Easy Java score calculation
    2. Java增量評分 - Incremental Java score calculation

從7.31版本開始提供的constraint streams屬於Java增量評分的一種。在普通的Java增量評分中,咱們須要針對各個約束邏輯,編輯相應的判斷,並在知足必定條件後,經過ScoreHolder對象進行記分。引擎會將各個層次的分數進行累加,成爲當前方案的總分。Constraint Streams的原理也同樣,只是經過強大的Stream特性,令評分邏輯更爲簡潔,使用更短的代碼便可實現更豐富的邏輯描述。

關於Java的Stream特性(Java1.8及之後的版本纔出現)的使用方法,可自行經過其它網絡資源學習,本文假設讀者熟悉Java Stream的各類用法。

咱們先以一個簡單的示例說明Constraint streams接口的使用方法:

private int doNotAssignAnn() {
        int softScore = 0;
        schedule.getShiftList().stream()
                .filter(Shift::isEmployeeAnn)
                .forEach(shift -> {
                    softScore -= 1;
                });
        return softScore;
    }

經過上述代碼塊是一Java簡易評分的示例,從方法名doNotAssignAnn就很容易理解到,該約束的做用是「使得任務不要分配給Ann」。咱們知道在OptaPlanner裏,評分一般都是負數,表示懲罰一個行爲,令引擎找出儘量規避這種行爲的方案。示例中使用了Java的Stream功能進行判斷和過濾。其邏輯是:從班次列表中找出全部分配給了Ann的班次,對每個知足這個條件的班次進行扣分,並把分數加總做爲方法的返回值。

那麼一樣的約束要求,使用Constraint Stream應該如何實現呢?見如下代碼:

private Constraint doNotAssignAnn(ConstraintFactory factory) {
        return factory.from(Shift.class)
                .filter(Shift::isEmployeeAnn)
                .penalize("Don't assign Ann", HardSoftScore.ONE_SOFT);
    }

先要提醒一下,與Java簡單評分法相似(評分類須要實現EasyScoreCalculator接口),OptaPlanner的Constraint Stream提供一個名爲ConstraintProvider的接口,實現評分的類須要實現這個接口,這個接口只有一個須要實現的方法 - defineConstraints,它傳入ConstrantFactory類,返回一個Constraint數組,數組的元素就是已進行了評分和懲罰的各個約束對象。上面的代碼中能夠看到,doNotAssignAnn方法返回一個Constraint對象,這個對象表示了對Ann被分配到的班次數的懲罰分數。上述代碼能夠看到,咱們只須要對ConstraintFactory的對象factory進行Stream操做,一步便可完成判斷、過濾和懲罰三個操做,完成這些操做後會獲得一個操做過的Contraint對象,返回該對象便可。上述代碼中,對於factory的三步操做也至關明瞭,你們能夠本身理解。

可是對於一些更復雜的判斷,其實現步驟與模式也同樣,只不過須要編寫一些更復雜的Lambda表達式來進行判斷、過濾和各類運算。以下代碼:

private Constraint requiredCpuPowerTotal(ConstraintFactory factory) {
        return factory.from(CloudProcess.class)
                .groupBy(CloudProcess::getComputer, sum(CloudProcess::getRequiredCpuPower))
                .filter((computer, requiredCpuPower) -> requiredCpuPower > computer.getCpuPower())
                .penalize("requiredCpuPowerTotal",
                        HardSoftScore.ONE_HARD,
                        (computer, requiredCpuPower) -> requiredCpuPower - computer.getCpuPower());
    }

該代碼是CloudBalance中用於,計算限制一臺計劃機被分配超出其CPU運算能力的約束。你們能夠回想,或從官方示例中看一下CloudBalance的其中一個最基本約束 - 每臺計算機所分得的CPU需求,不可超過該計算機的可用CPU能力。

所以,能夠看到,factory除了過from操做得到全部Process對象,經過filter對Process進行過濾,經過penalize進行計分外。factory對象還有一個groupBy方法,用於對全部Process中的Computer進行分組並加總每一組(即每一個Computer)的全部CPU計算能力需求量。所以,在filter方法中,就找出那些超出CPU能力的Computer(即分組),在penalize方法中,對整全部超出CPU需求中的計算進行扣分,扣分值是超出部分。

由此可能,OptaPlanner提供的Constraint Stream能夠進行更復雜的條件判斷,至於這種方法是否更好用,就取決於你們對Stream(相似C#中的Linq)的熟悉程度。

至於整個Constraint Stream代碼的結果方式,即上面提到的實現ConstraintProvider接口的代碼以下(摘自官方示例CloudBalance):

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.from(CloudProcess.class)
                .groupBy(CloudProcess::getComputer, sum(CloudProcess::getRequiredCpuPower))
                .filter((computer, requiredCpuPower) -> requiredCpuPower > computer.getCpuPower())
                .penalize("requiredCpuPowerTotal",
                        HardSoftScore.ONE_HARD,
                        (computer, requiredCpuPower) -> requiredCpuPower - computer.getCpuPower());
    }
.
.
.

重複提示一下,Constraint Stream功能是7.31版纔開始提供的功能,從功能接口上應該是未夠成功的,若是須要在項目中實現一些更爲複雜的約束描述,建議暫時仍是不要直接使用。在OptaPlanner的用戶手冊中,也有相關的提示;你們看狀況而用。

最近一段時間OptaPlanner更新算比較頻繁,但從網站上的更新內容看到,不少版本儘管隔了很長時間,但接口上的更新內容卻很少。我向Geoffrey查詢過,他表示這些版本更多的狀況是在實現一些引擎內部的優化和一些新的內部運算功能,但這些功能不必定反映到API上,所以對於咱們使用者來講,並無太大的變化。但是若是你們也跟進將OptaPlanner的程序包也更新到最新版本,就會發現,不少一些經常使用的接口、方法,都已經被標準爲將爲放棄,從Javadocs上能夠看到一些當前版本被標識爲@Deprecated的方法、成員,已明確說明將在8.x中中止使用。

 

本系列文章在公衆號不定時連載,請關注公衆號(讓APS成爲可能)及時接收,二維碼:

 


如需瞭解更多關於Optaplanner的應用,請發電郵致:kentbill@gmail.com
或到討論組發表你的意見:
如有須要可添加本人微信(13631823503)或QQ(12977379)實時溝通,但因本人平常工做繁忙,經過微信,QQ等工具可能沒法深刻溝通,較複雜的問題,建議以郵件或討論組方式提出。(討論組屬於google郵件列表,國內網絡可能較難訪問,需自行解決)

 

上述功能但願能夠幫你們理解並應用OptaPlanner的第四種評分方式。

相關文章
相關標籤/搜索