gikoha’s blog

個人的メモがわり

Optaplannerでシフト作成 - 7

IDEでjar作成を自動化するとき;

Configurationから JAR Applicationを追加

下のBefore Launchから

+を押して Build 'xxx' artifact

+を押してRun Gradle Task

 

f:id:gikoha:20180920232440j:plain

Gradle Taskでは、 replaceKieConfをターゲットとする

 

build.gradleに以下を記載

task replaceKieConf doLast {
'jar uvf ShiftSolver.jar META-INF/kie.conf'.execute([], new File("$projectDir/out/artifacts/ShiftSolver_jar/"))
}

RunAsJARと名前をつけてConfigulationを保存

そうすると RunAsJARを起動するだけでkie.confのアップデートされた JARができて 実行もされる

Optaplannerでシフト作成 - 6

残るは jarにすると動作しなくなる問題だけだったが、Stack overflowに記事があった

stackoverflow.com

同じようにしてやればできたので報告

  1. IDEから Build Artifactで普通にjarを作る
  2. そのまま起動すると読み込み時にNull Pointer Exceptionが出て解析されない
  3. jar ディレクトリにcd
  4. mkdir META-INF
  5. META-INF/kie.confを作る (User Arturo W - Stack Overflow さんによる)
    org.kie.api.internal.assembler.KieAssemblers = +org.optaplanner.core.impl.solver.kie.KieSolverAssemblerService
    org.kie.api.internal.assembler.KieAssemblers = org.kie.internal.services.KieAssemblersImpl
    org.kie.api.internal.runtime.KieRuntimes = org.kie.internal.services.KieRuntimesImpl
    org.kie.api.internal.weaver.KieWeavers = org.kie.internal.services.KieWeaversImpl
    org.kie.api.internal.runtime.beliefs.KieBeliefs = org.kie.internal.services.KieBeliefsImpl
    org.kie.api.io.KieResources = org.drools.core.io.impl.ResourceFactoryServiceImpl
    org.kie.api.marshalling.KieMarshallers = org.drools.core.marshalling.impl.MarshallerProviderImpl
    org.kie.api.concurrent.KieExecutors = org.drools.core.concurrent.ExecutorProviderImpl
    org.kie.api.KieServices = org.drools.compiler.kie.builder.impl.KieServicesImpl
    org.kie.internal.builder.KnowledgeBuilderFactoryService = org.drools.compiler.builder.impl.KnowledgeBuilderFactoryServiceImpl
  6. jar uvf ShiftSolver.jar META-INF/kie.conf としてMETA-INF/kie.confを置き換える

 xcode10をインストールしようとしたら70GBも開けないといけなくてげんなり。

Optaplannerでシフト作成 - 5

結局DRLで「あるdayのemployeeがworkDayであった場合、他のemployeeのassign scoreを-1する」と表現するのがわからなかったので、employee workDayを読み込んだときに他のemployeeにそのdayを使わないようにフラグをつけることとした

 

うまく動くようになってご満悦

GitHub - vascarpenter/ShiftSolver

あとIntelliJ IDECSV pluginを使うと余計なスペースを入れられて、openCSVで読み込むときゴミが入ってしまうのでプラグインを削除した。

 

Optaplannerでシフト作成 - 4

Employeeに HashMapを持たせ、Assetsを解析させることで DRLから参照できるようにして、ついに拘束日と休日を実装できた

GitHub - vascarpenter/ShiftSolver

f:id:gikoha:20180915211528j:plain

だが、DRLではどうやったら拘束日の実装をすればいいのかわからん

一番の理想:拘束日の医師は決定しており optaplannerを使うまでもない

理想:ある日がだれかの拘束日に当たっていて、その担当以外の医師だったらスコアを下げる

→ある日が誰かの拘束日であるか逆算できる必要がある

現実:その医師の拘束日だったらスコアをあげる。本当は上げたくないんだが。

DRL:

when WorkDayAssignment(employee != null, employee.isWorkDay(day) == true) then scoreHolder.addHardConstraintMatch(kcontext, new BigDecimal(1));

うーんむずかしい

でもプログラムとしてはこれでほとんど完成なんだな..

 

Optaplannerでシフト作成 - 3

GitHub - vascarpenter/ShiftSolver

GUIを拡張し、テーブルにして、拘束日と休日を","で区切って入力できるようにした

そのリストはCSVでの読み込みと保存できる

出力もテーブルにした

出力はCSVで結果を保存できるようにした

Optaplanner 7.0に対応し、deprecatedなものは削除

こんなふうになった

 

f:id:gikoha:20180914230416j:plain

まだ拘束日と休日の検索に対応していないけれどな!

 

jarで書き出した場合、DRLはリソースにしないと読めず、リソースにするとJARファイル内にDRLが入ってしまってこれはgetResourceで読めないから解析がなされなくなってしまう

DRLをlistにしてbuilderに読ませる方法がわからん。

Optaplannerでシフト作成 - 2

GUIを作成しました

GUIの問題点は「ボタンを押して計算を始めるとGUIに処理がいかずに計算中はハングしてしまう」こと

SwingWorkerを使うとよさそうで、実際optaplannerのサンプルもSwingWorkerを使っているので、とりあえず入れてみた

GitHub - vascarpenter/ShiftSolver

 

f:id:gikoha:20180913221806j:plain

Optaplannerでシフト作成 - 1

www.ogis-ri.co.jp

を参考に、以前に作ったシフト割り振りをoptaplannerを使ってヒューリスティック法で探索するプログラムを作る。

GitHub - vascarpenter/ShiftSolver: shift solver sample application using optaplanner

とりあえず doctorA,B,Cの3人を Employeeに

日付を Dayに

拘束割り付けモデルを WorkDayAssignmentにする

solutionParameterを使ったあまり美しくないやりかたも継承

とりあえずdrlは

rule "countLeveling"
  when
    $solutionParameter : SolutionParameter()
    $employee : Employee()
    $workCountPerPeople : Number() from accumulate( $workDayAssignment : WorkDayAssignment( employee == $employee ), count( $workDayAssignment ) )
  then
    scoreHolder.addSoftConstraintMatch(kcontext, new BigDecimal((int)(-10000D * Math.pow($solutionParameter.getCountAverage() - $workCountPerPeople.doubleValue(), 2) )));
end

のみとした (均等割り付けモデルのみ)

実際走らせてみると

    Day	People
1	Doctor A
2	Doctor C
3	Doctor B
4	Doctor A
5	Doctor C
...
<secondsSpentLimit>10</secondsSpentLimit>

で10秒間だけ探索するよう指定したけれど上記のようにうまくいってそうな感じ 

あとは休み希望や専門の縛り、土日拘束回数の均一化、

非同期探索、swingやkotlin/TornadeFXのガワもかぶせないとな..