Red Hat Training

A Red Hat training course is available for Red Hat Decision Manager

デシジョンサービスのスタートガイド

Red Hat Decision Manager 7.2

ガイド

概要

本書は、Red Hat Decision Manager 7.2 で交通違反のデシジョンサービスの例を作成およびテストする方法を説明します。

はじめに

ビジネスルールの開発者は、Red Hat Decision Manager の Decision Central を使用して、さまざまなデシジョンサービスを設計できます。Red Hat Decision Manager では、参考として、Decision Central に直接、デシジョンの例を含むプロジェクト例と、Java クラスとして配布されるデシジョン例 (統合開発環境 (IDE) にインポートして外部テストが可能) が提供されます。

本書は、Decision Cnetral で、交通違反のデシジョンサービスの例に関する新規プロジェクトを作成してテストする方法を主に見ていきます。

前提条件

第1章 交通違反プロジェクトの作成

プロジェクトとは、データオブジェクト、ガイド付きデシジョンテーブル、ガイド付きルールなどのアセットのコンテナーのことです。

手順

  1. Decision Central にログインします。
  2. MenuDesignProjects を選択します。

    Red Hat Decision Manager は以下のイメージのように MySpace と呼ばれるデフォルトスペースを提供します。このデフォルトスペースを使用してサンプルプロジェクトを作成およびテストできます。

    図1.1 デフォルトのスペース

    spaces teams
  3. Add Project をクリックします。
  4. Name フィールドに Driver_department_traffic_violations と入力します。
  5. Add をクリックします。

    図1.2 Add Project ウインドウ

    addproject

プロジェクトの Assets ビューを開きます。

第2章 データオブジェクト

データオブジェクトは、作成するルールアセットの設定要素です。データオブジェクトは、プロジェクトで指定したパッケージに Java オブジェクトとして実装されているカスタムのデータタイプです。たとえば、データフィールド NameAddress、および DateOfBirth を使用して Person オブジェクトを作成し、ローン申し込みルールに詳細な個人情報を指定できます。このカスタムのデータ型は、アセットとデシジョンサービスがどのデータに基づいているかを指定します。

以下の表では、このプロジェクト用に作成する Violation および Driver データオブジェクトを一覧表示しています。

表2.1 Violation データオブジェクト

IDLabelType

code

Code

String

points

Points

Integer

violationDate

Violation Date

Date

type

Type

String

fineAmount

Fine Amount

double

speedLimit

Speed Limit

Integer

actualSpeed

Actual Speed

Integer

表2.2 Driver データオブジェクト

IDLabelType

name

Name

String

age

Age

Integer

state

State

String

city

City

String

violations

Violations

Violation (org.jboss.example.traffic_violations.Violation)

注: Violations フィールドは List に設定すると、指定のタイプに、複数のアイテムを格納できます。

fineAmount

Fine Amount

double

totalPoints

Total Points

Integer

reason

Reason

String

2.1. Violation データオブジェクトの作成

Violation データオブジェクトには、Violation DateFine Amount および Speed Limit など、違反詳細をもとにしたデータフィールドが含まれます。

前提条件

Driver_department_traffic_violations プロジェクトを作成した。

手順

  1. Add AssetData Object をクリックします。
  2. Create new Data Object ウィザードで以下の値を入力します。

    • Data Object: Violation
    • Package: com.myspace.driver_department_traffic_violations を選択します。
  3. Ok をクリックします。

    図2.1 新規データオブジェクトの作成ウィンドウ

    データオブジェクト

2.1.1. Violation データオブジェクトの制約の追加

Violation データオブジェクトのフィールドに、ルールを定義するときに選択する制約を入力します。

前提条件

Violation データオブジェクトを作成した。

手順

  1. 'Violation'-general properties セクションで、Label フィールドに Violation と入力します。

    図2.2 一般的なプロパティー

    一般的なプロパティー
  2. + add field をクリックします。
  3. 以下の値を設定します。

    • Id: code
    • Label: Code
    • Type: String
  4. Create and continue をクリックしてから、以下の値を入力します。

    • Id: points
    • Label: Points
    • Type: Integer
  5. Create and continue をクリックしてから、以下の値を入力します。

    • Id: violationDate
    • Label: Violation Date
    • Type: Date
  6. Create and continue をクリックしてから、以下の値を入力します。

    • Id: type
    • Label: Type
    • Type: String
  7. Create and continue をクリックしてから、以下の値を入力します。

    • Id: fineAmount
    • Label: Fine Amount
    • Type: Double
  8. Create and continue をクリックしてから、以下の値を入力します。

    • Id: speedLimit
    • Label: Speed Limit
    • Type: Integer
  9. Create and continue をクリックしてから、以下の値を入力します。

    • Id: actualSpeed
    • Label: Actual Speed
    • Type: Integer
  10. Create をクリックします。
  11. Save をクリックした後、Save をクリックして変更を確認します。
  12. Driver_department_traffic_violations ラベルをクリックして、プロジェクトの Assets ビューに戻ります。

    図2.3 Violation データオブジェクトのフィールド

    違反データのフィールド

2.2. Driver データオブジェクトの作成

Driver データオブジェクトには、ドライバーの NameAgeTotal Points など、ドライバーの詳細に基づくデータフィールドを含めます。

前提条件

Driver_department_traffic_violations プロジェクトを作成した。

手順

  1. MenuDesignProjectsDriver_department_traffic_violations に移動します。
  2. Add AssetData Object をクリックします。
  3. Create new Data Object ウィザードで以下の値を入力します。

    • Data Object: Driver
    • Package: com.myspace.driver_department_traffic_violations を選択します。
  4. Ok をクリックします。

    図2.4 新規データオブジェクトの作成ウィンドウ

    data object2

2.2.1. Driver データオブジェクトの制約の追加

Driver データオブジェクトのフィールドに、ルールを定義するときに選択する制約を入力します。

前提条件

Driver データオブジェクトを作成した。

手順

  1. 'Driver'-general properties セクションで、Label フィールドに Driver と入力します。
  2. + add field をクリックします。
  3. 以下の値を設定します。

    • Id: name
    • Label: Full Name
    • Type: String
  4. Create and continue をクリックしてから、以下の値を入力します。

    • Id: age
    • Label: Age
    • Type: Integer
  5. Create and continue をクリックしてから、以下の値を入力します。

    • Id: state
    • Label: State
    • Type: String
  6. Create and continue をクリックしてから、以下の値を入力します。

    • Id: city
    • Label: City
    • Type: String
  7. Create and continue をクリックしてから、以下の値を入力します。

    • Id: violations
    • Label: Violations
    • Type: Violation(com.myspace.driver_department_traffic_violations.Violation)
    • List: このチェックボックスを選択すると、このフィールドで、指定したタイプのアイテムを複数保持できるようになります。
  8. Create and continue をクリックしてから、以下の値を入力します。

    • Id: fineAmount
    • Label: Fine Amount
    • Type: Double
  9. Create and continue をクリックしてから、以下の値を入力します。

    • Id: totalPoints
    • Label: Total Points
    • Type: Integer
  10. Create and continue をクリックしてから、以下の値を入力します。

    • Id: reason
    • Label: Reason
    • Type: String
  11. Create をクリックします。
  12. Save をクリックした後、Save をクリックして変更を確認します。
  13. Driver_department_traffic_violations ラベルをクリックして、プロジェクトの Assets ビューに戻ります。

    図2.5 Driver データオブジェクトのフィールド

    ドライバーデータフィールド

第3章 ガイド付きルール

ガイド付きルールは、ルール作成のプロセスを提供する、UI ベースのガイド付きルールデザイナーで作成できるビジネスルールです。ルールデザイナーは、編集中のルールのオブジェクトモデルに基づいて、許容される入力のフィールドおよびオプションを提供します。ルールに関連するすべてのデータオブジェクトは、ルールと同じプロジェクトパッケージに置く必要があります。同じパッケージに含まれるアセットはデフォルトでインポートされます。ルールデザイナーの Data Objects タブを使用すると、必要なデータオブジェクトがすべてリストされていることを確認したり、その他の必要なデータオブジェクトをインポートしたりできます。

図3.1 ガイド付きルールデザイナー

ガイド付きルールデザイナー

3.1. 運転免許停止ルールの作成

ガイド付きルールデザイナーを使用して、運転免許停止ルールを作成します。

前提条件

Violation データオブジェクトと Driver データオブジェクトの両方を作成した。

手順

  1. MenuDesignProjects をクリックしてから Driver_department_traffic_violations をクリックします。
  2. Add AssetGuided Rule をクリックし、以下の値を入力します。

    • Guided Rule: DriverLicenseSuspensionRule
    • Package: com.myspace.driver_department_traffic_violations
  3. Ok をクリックして ガイド付きルールデザイナー を開きます。

    図3.2 新しいガイド付きルールの作成ウィンドウ

    ガイド付きルール

3.2. 停止ルールの条件の設定

ドライバーの違反を判断するために使用する 停止 ルールの条件を設定します。

前提条件

運転免許停止ルールを作成した。

手順

  1. WHEN ラベルの横にある ( 6176 ) をクリックして、Add a condition to the rule ウィンドウを開きます。
  2. Driver を選択して Ok をクリックします。

    図3.3 新しいガイド付きルールの作成ウィンドウ

    条件の追加
  3. There is a Driver ラベルをクリックして Modify constraints for Driver ウィンドウを開きます。
  4. Variable name フィールドに driver と入力し、Set をクリックします。
  5. There is a Driver[driver] をクリックし、Expression editor をクリックします。
  6. [not bound] をクリックし、Expression editor を開きます。
  7. Bind the Expression to a new variable フィールドに previousPts と入力し、Set をクリックします。
  8. Choose メニューから totalPoints を選択します。
  9. 1 行目 (previousPts ラベル) の横にある ( 6176 ) をクリックして、Add a condition to the rule ウィンドウを開きます。
  10. From Accumulate を選択し、Ok をクリックします。
  11. From Accumulate ラベルの上にある click to add pattern をクリックし、 choose fact type メニューから Number を選択します。
  12. There is a number ラベルをクリックして Modify contraints for Number ウィンドウを開きます。
  13. Expression editor をクリックし、[not bound]: Choose メニューから intValue() を選択します。
  14. [not bound] をクリックし、Expression editor を開きます。
  15. Bind the Expression to a new variable フィールドに totalNewPoints と入力し、Set をクリックします。
  16. click to add pattern をクリックし、choose fact type メニューから Violation を選択します。
  17. All Violation with: をクリックして Modify contraints for Violation ウィンドウを開き、Add a restriction on a field メニューから point を選択します。
  18. points ラベルの横にある please choose をクリックして、greater than を選択します。
  19. ( 6191 ) をクリックした後、Literal value をクリックします。
  20. points ラベルをクリックして、Add a field ウィンドウを開きます。
  21. vPoints と入力し、Set をクリックします。
  22. Function フィールドに sum(vPoints) と入力します。
  23. totalNewPointsplease choose メニューから greater than or equal to を選択します。
  24. ( 6191 ) をクリックし、New formula をクリックして、新しいフィールドに 20-previousPts と入力します。
  25. Save をクリックした後、Save をクリックして変更を確認します。

図3.4 停止ルールの条件

ルールの条件

3.3. 停止ルールのアクションの設定

点数や罰金の金額など、ドライバーのペナルティを決定するために使用する 停止 ルールのアクションを設定します。これらの値は、停止ルールの条件に基づいています。

前提条件

停止ルールの条件を設定した。

手順

  1. THEN ラベルの横にある (show options…) をクリックします。

    図3.5 オプションの表示の選択

    オプションの表示
  2. THEN ラベルの横にある ( 6176 ) をクリックし、Change field values of driver を選択して、Ok をクリックします。
  3. Set value of Driver [driver] フィールドをクリックし、Add field メニューから state を選択します。
  4. Set value of Driver [driver] セクションの state の横にある ( 6191 ) をクリックして、Field value ウィンドウを開きます。
  5. Literal value をクリックし、新しいフィールドに suspend と入力します。

    図3.6 新しいフィールド

    suspend の入力
  6. Set value of Driver [driver] セクションの下にある (options) ラベルの横にある ( 6176 ) をクリックします。
  7. Add an option to the rule ウィンドウで、Attribute メニューから ruleflow-group オプションを選択します。
  8. ruleflow-group フィールドに trafficViolation と入力します。
  9. Save をクリックした後、Save をクリックして変更を確認します。

    図3.7 停止ルールのアクション

    ルールパネル

第4章 ガイド付きデシジョンテーブル

ガイド付きデシジョンテーブルは、アップロードされるデシジョンテーブルのスプレッドシートに代わる方法で、ウィザードを用いて表形式でビジネスルールを定義します。ガイド付きデシジョンテーブルでは、プロジェクトで指定したデータオブジェクトをもとに、Decision Central の UI ベースのウィザードに従ってルール属性、メタデータ、条件、およびアクションを定義します。ガイド付きデシジョンテーブルを作成すると、定義したルールは、その他のすべてのルールアセットとともに Drools Rule Language (DRL) ルールにコンパイルされます。

ガイド付きデシジョンテーブルに関連するすべてのデータオブジェクトは、ガイド付きデシジョンテーブルと同じプロジェクトパッケージに存在する必要があります。同じパッケージに含まれるアセットはデフォルトでインポートされます。必要なデータオブジェクトとガイド付きデシジョンテーブルの作成後、ガイド付きデシジョンテーブルデザイナーの Data Objects タブを使用して、必要なデータオブジェクトがすべてリストされていることを検証したり、新規アイテム を追加してその他の既存データオブジェクトをインポートしたりできます。

4.1. 交通違反のガイド付きデシジョンテーブルの作成

ガイド付きデシジョンテーブルデザイナーを使用して、交通違反のガイド付きデシジョンテーブルを作成します。このテーブルは、ドライバーの特定の違反とその結果として発生する罰金および点数を指定するためのものです。

前提条件

Violation データオブジェクトと Driver データオブジェクトの両方を作成した。

手順

  1. MenuDesignProjects をクリックしてから Driver_department_traffic_violations をクリックします。
  2. Add AssetGuided Decision Table をクリックし、以下の値を入力します。

    • Guided Decision Table: SpeedViolationRule
    • Package: com.myspace.driver_department_traffic_violations
  3. Hit Policy メニューから Unique Hit を選択します。
  4. Table format セクションで、Extended entry, values defined in table body を選択します。
  5. Ok をクリックして、Create new Guided Decision Table ウィンドウを開きます。

    図4.1 ガイド付きデシジョンテーブルのウィンドウ

    デシジョンテーブル

4.1.1. Violation Type 列の挿入

Violation Type 列には、ドライバーの速度や、ドライバーが薬物やアルコールの影響下にあったかどうかなどの違反の詳細を含めます。

前提条件

交通違反のガイド付きデシジョンテーブルを作成した。

手順

  1. ColumnsInsert Column をクリックしてから Include advanced options を選択します。

    図4.2 列のタブ

    列のタブ
  2. Add an Attribute column を選択し、Next をクリックします。

    図4.3 新しい列の追加ウィンドウ

    新しい列
  3. Ruleflow-group を選択し、Finish をクリックします。
  4. Attribute 列を展開し、Default value フィールドに trafficViolation と入力します。

    図4.4 属性列ウィンドウ

    属性列
  5. Insert Column をクリックし、Add a ConditionPattern+Create a new Fact Pattern を選択します。
  6. Fact type メニューから Violation を選択し、Binding フィールドに v と入力して、OK をクリックします。

    図4.5 新しいファクトパターンの作成ウィンドウ

    ファクトパターン
  7. Calculation typeLiteral value を選択します。

    図4.6 計算タイプのオプション

    リテラル値
  8. Field を選択し、Field メニューから type を選択します。
  9. Operator を選択し、Operator メニューから equal to を選択します。
  10. Value options を選択し、Value list (optional) フィールドに Speed,Driving while intoxicated,DWI=Driving while under the influence of drugs と入力します。
  11. Additional info を選択し、Header (description) フィールドに Violation Type と入力し、Finish をクリックします。

    図4.7 Violation Type ヘッダー

    違反列
  12. Insert Column をクリックし、Add a ConditionPattern を選択して、Pattern メニューから Violation[v] を選択します。
  13. Calculation typePredicateField を選択し、actualSpeed-speedLimit > $param と入力します。
  14. Value options を選択してから Additional info を選択し、Header (description) フィールドに Speed Limit (MPH) > と入力します。
  15. Finish をクリックします。

    図4.8 Speed Limit (MPH) > ヘッダー

    速度のヘッダー
  16. Insert Column をクリックし、Add a ConditionPattern を選択して、Pattern メニューから Violation[v] を選択します。
  17. Calculation typePredicate を選択します。
  18. Field を選択し、Field フィールドに actualSpeed-speedLimit < $param と入力します。
  19. Operator を選択し、Value options を選択し、Additional info を選択します。
  20. Header (description) フィールドに Speed Limit (MPH) < と入力し、Finish をクリックします。

    図4.9 条件列

    条件列

4.1.2. Fine Amount および Points 列の挿入

Fine Amount および Points 列には、対応する Violation Type フィールドの値に基づく罰金と点数を含めます。

前提条件

交通違反のガイド付きデシジョンテーブルに Violation Type 列を挿入した。

手順

  1. Insert Column をクリックし、Set the value of a fieldPattern を選択して、Pattern メニューから Violation[v] を選択します。
  2. Field を選択し、Field メニューから fineAmount を選択します。
  3. Value options を選択してから Additional info を選択し、Header (description) フィールドに Fine Amount と入力します。
  4. Update engine with changes オプションを選択し、Finish をクリックします。

    図4.10 Fine Amount ヘッダー

    罰金額
  5. Insert Column をクリックし、Set the value of a fieldPattern を選択して、Pattern メニューから Violation[v] を選択します。
  6. Field を選択し、Field メニューから points を選択します。
  7. Value options を選択してから Additional info を選択し、Header (description) フィールドに Points と入力します。
  8. Update engine with changes オプションを選択し、Finish をクリックします。

    図4.11 アクション列

    点数

4.1.3. ガイド付きデシジョンテーブルの行の挿入

ガイド付きデシジョンテーブルに列を作成したら、デシジョンテーブルデザイナーを使用して行を追加し、ルールを定義します。

前提条件

交通違反のガイド付きデシジョンテーブルに、Violation TypeFine Amount、および Points 列を作成した。

手順

  1. Model タブをクリックして、SpeedViolationRule テーブルを表示します。
  2. InsertAppend row をクリックします。この手順を繰り返して、合計 5 つのテーブル行を追加します。

    図4.12 Append row メニューの場所

    row1 の追加
  3. 以下の例のようにテーブルに入力します。

    図4.13 入力されたデータフィールド

    ガイド付き dt ds
  4. Save をクリックした後、Save をクリックして変更を確認します。

第5章 テストシナリオ

Red Hat Decision Manager のテストシナリオでは、ルール、モデル、およびイベントの機能を実稼働環境にデプロイする前に検証できます。テストシナリオでは、ファクトまたはプロジェクトモデルのインスタンスと類似する条件のデータを使用します。このデータは特定のルールセットと照合され、想定された結果が実際の結果と一致するとテストに成功します。想定された結果が実際の結果と一致しないと、テストは失敗します。

すべてのテストシナリオを実行すると、シナリオのステータスが Reporting パネルにレポートされます。

テストシナリオは、一度に 1 つずつ、またはグループ単位で実行できます。グループで実行する場合は、1 つのパッケージに含まれるすべてのシナリオが対象になります。テストシナリオは独立しており、他のシナリオに影響を与えたり、他のシナリオを変更したりできません。

5.1. 速度制限シナリオのテスト

交通違反のガイド付きデシジョンテーブルを作成したときに指定したデータを使用して、速度制限シナリオをテストします。

前提条件

  • Driver_department_traffic_violations プロジェクトを作成した。
  • Violation および Driver データオブジェクトを作成した。
  • SpeedViolationRule ガイド付きデシジョンテーブルを作成した。

手順

  1. MenuDesignProjects をクリックしてから Driver_department_traffic_violations をクリックします。
  2. Add AssetTest Scenario の順にクリックします。
  3. Create new Test Scenario ウィンドウ で、以下の値を入力します。

    1. Test Scenario: Speed limit 10-20
    2. Package: com.myspace.driver_department_traffic_violations を選択します。
  4. Ok をクリックします。
  5. +GIVEN をクリックして、New input ウィンドウを開きます。
  6. Insert a new fact メニューから Violation を選択します。
  7. Fact name フィールドに violation と入力し、Add をクリックします。
  8. Insert 'Violation'[violation] の下にある Add a field をクリックして、Choose a field to add ウィンドウを開きます。
  9. Choose a field to add メニューから speedLimit を選択し、OK をクリックします。
  10. ( 6191 ) をクリックした後、Literal value をクリックします。
  11. speedLimit フィールドに 40 と入力します。
  12. Insert 'Violation'[violation] をクリックします。
  13. Choose a field to add ウィンドウの Choose a field to add メニューから type を選択し、OK をクリックします。
  14. ( 6191 ) をクリックした後、Literal value をクリックします。
  15. type フィールドに Speed と入力します。
  16. Insert 'Violation'[violation] をクリックします。
  17. Choose a field to add メニューから actualSpeed を選択し、OK をクリックします。
  18. ( 6191 ) をクリックした後、Literal value をクリックします。
  19. actualSpeed フィールドに 55 と入力します。
  20. +Expect をクリックして、New expectation ウィンドウを開きます。
  21. Rule メニューを展開し、Row 1 SpeedViolationRule を選択して、OK をクリックします。
  22. +GIVEN をクリックして New input ウィンドウを開き、Activate rule flow group フィールドに trafficViolation と入力し、Add をクリックします。
  23. +Expect をクリックして New expectation ウィンドウを開き、Fact value: violation の横にある Add をクリックします。
  24. Violation 'violation' has values: をクリックして Choose a field to add ウィンドウを開きます。
  25. Choose a field to add メニューから fineAmount を選択し、OK をクリックします。
  26. fineAmount: equals フィールドに 100.0 と入力します。
  27. Violation 'violation' has values: をクリックして Choose a field to add ウィンドウを開きます。
  28. Choose a field to add メニューから points を選択し、OK をクリックします。
  29. points: equals フィールドに 1 と入力します。
  30. Save をクリックした後、Save をクリックして変更を確認します。
  31. Run scenario をクリックします。

    図5.1 速度のテストの結果画面

    速度のテストの結果画面

テストシナリオに設定した値と条件が、速度違反のガイド付きデシジョンテーブルで指定されている要件を満たしている場合、ウィンドウの下部にある Reporting セクションに Success というメッセージが表示されます。

5.2. 運転免許停止シナリオのテスト

運転免許停止ルールおよびアクションを設定したときに指定したデータを使用して、運転免許停止シナリオをテストします。

前提条件

  • Driver_department_traffic_violations プロジェクトを作成した。
  • Violation および Driver データオブジェクトを作成した。
  • 運転免許停止ルールおよびアクションを設定した。

手順

  1. MenuDesignProjects をクリックしてから Driver_department_traffic_violations をクリックします。
  2. Add AssetTest Scenario の順にクリックします。
  3. Create new Test Scenario ウィザードで、以下の値を入力します。

    1. Test Scenario: Suspend due to total points
    2. Package: com.myspace.driver_department_traffic_violations を選択します。
  4. Ok をクリックします。
  5. +GIVEN をクリックして、New input ウィンドウを開きます。
  6. Insert a new fact メニューから Driver を選択します。
  7. Fact name フィールドに driver と入力し、Add をクリックします。
  8. 'Driver'[driver] の下にある Add a field をクリックして、Choose a field to add ウィンドウを開きます。
  9. Choose a field to add メニューから totalPoints を選択し、OK をクリックします。
  10. totalPoints の横にある ( 6191 ) をクリックし、Literal value をクリックして totalPoints フィールドに 10 と入力します。
  11. +GIVEN をクリックして、New input ウィンドウを開きます。
  12. Insert a new fact メニューから Violation を選択します。
  13. Fact name フィールドに violation と入力し、Add をクリックします。
  14. Insert 'Violation'[violation] の下にある Add a field をクリックして、Choose a field to add ウィンドウを開きます。
  15. Choose a field to add メニューから points を選択し、OK をクリックします。
  16. ( 6191 ) をクリックした後、Literal value の横にある Literal value をクリックします。
  17. points フィールドに 10 と入力します。
  18. +Expect をクリックして、New expectation ウィンドウを開きます。
  19. Rule メニューを展開し、DriverLicenseSuspensionRule を選択して、OK をクリックします。
  20. +GIVEN をクリックして New input ウィンドウを開き、Activate rule flow group フィールドに trafficViolation と入力し、Add をクリックします。
  21. +Expect をクリックして New expectation ウィンドウを開き、Fact value: driver の横にある Add をクリックします。
  22. Driver 'driver' has values: をクリックして Choose a field to add ウィンドウを開きます。
  23. Choose a field to add メニューから state を選択し、OK をクリックします。
  24. state: equals フィールドに suspend と入力します。
  25. Save をクリックした後、Save をクリックして変更を確認します。
  26. Run scenario をクリックします。

    結果

    合計点数が 20 以上であるため、ルールが実行され、運転免許が停止されます。

    図5.2 停止のテストの結果画面

    停止のテストの結果画面

テストシナリオに設定した値と条件が、運転免許停止ルールおよびアクションを設定したときに指定した要件を満たしている場合、ウィンドウの下部にある Reporting セクションに Success というメッセージが表示されます。

5.3. 複数の違反シナリオのテスト

Suspend due to total points をコピーして変更し、運転免許停止ルールおよびアクションを設定したときに指定したデータを使用して、複数の違反があるドライバーの運転免許停止シナリオを作成します。

前提条件

  • Driver_department_traffic_violations プロジェクトを作成した。
  • Violation および Driver データオブジェクトを作成した。
  • 運転免許停止ルールおよびアクションを設定した。

手順

  1. MenuDesignProjects をクリックしてから Driver_department_traffic_violations をクリックします。
  2. Suspend due to total pointsCopy をクリックし、New Asset Name フィールドに Suspend due to multiple violations と入力して、Make a Copy をクリックします。
  3. SpaceMySpaceDriver_department_traffic_violations をクリックし、Suspend due to multiple violations アセットを選択します。
  4. +GIVEN をクリックして、New input ウィンドウを開きます。
  5. Insert a new fact メニューから Violation を選択します。
  6. Fact name フィールドに violation2 と入力し、Add をクリックします。
  7. points の横にある ( 6191 ) をクリックし、Literal value をクリックして pointsviolation2 フィールドに 5 と入力します。
  8. totalPoints フィールドで、値を 10 から 5 に変更します。
  9. Save をクリックした後、Save をクリックして変更を確認します。
  10. Run scenario をクリックします。

    図5.3 停止のテストの結果画面

    停止のテストの結果画面

テストシナリオに設定した値と条件が、運転免許停止ルールおよびアクションを設定したときに指定した要件を満たしている場合、ウィンドウの下部にある Reporting セクションに Success というメッセージが表示されます。

第6章 Decision Central のプロジェクトおよびビジネスアセットの例

Decision Central には、プロジェクト例がビジネスアセットと合わせて同梱されており、ルールや他のアセットを自分の Red Hat Decision Manager プロジェクトに作成するときに参考として使用できます。各プロジェクトは、Red Hat Decision Manager の意思決定管理やビジネス最適化アセットおよび論理を異なる方法で説明するように設計されています。

以下のプロジェクト例が、Decision Central で利用できます。

  • Mortgages (住宅ローン): (意思決定管理) デシジョンアセットを使用した住宅ローン審査プロセスの例。申し込み者のデータと資格を基にローンの申し込み資格を判定します。
  • Employee_Rostering (従業員勤務表) : (ビジネス最適化) デシジョンおよびソルバーアセットを使用した従業員勤務表の最適化の例。スキルに基づいて従業員をシフトに割り当てます。
  • OptaCloud: (ビジネス最適化) デシジョンおよびソルバーアセットを使用したリソース割り当ての最適化の例。リソースが制限されるなかでプロセスをコンピューターに割り当てます。

6.1. Decision Central のプロジェクトおよびビジネスアセット例へのアクセス

Decision Central のプロジェクト例を使用すると、自分の Red Hat Decision Manager プロジェクトにルールや他のアセットを作成するときに、参考としてビジネスアセット例を確認できます。

前提条件

手順

  1. Decision Central で、MenuDesignProjects に移動して、Try Samples をクリックします。

    プロジェクトが既にある場合は、Projects ページの画面右上の縦に並んだ 3 つの点をクリックし、Try Samples をクリックします。

  2. 各サンプルプロジェクトの説明を読んで、どのプロジェクトが最適か確認します。各プロジェクトは、Red Hat Decision Manager の意思決定管理やビジネス最適化アセットおよび論理を異なる方法で説明するように設計されています。
  3. サンプルプロジェクトを選択し、Ok をクリックして自分のスペースにプロジェクトを追加します。
  4. 自分のスペースの Projects ページで、サンプルプロジェクトを選択してアセットを確認します。
  5. 各サンプルアセットを選択して、指定された目標またはワークフローを達成するためにプロジェクトがどのように設計されているかを調べます。
  6. プロジェクトの Assets ページの右上にある Build をクリックしてサンプルプロジェクトをビルドするか、Deploy をクリックして、プロジェクトをビルドして Decision Server にデプロイします。

    (該当する場合は) プロジェクトのデプロイメント詳細を確認するには、MenuDeployExecution servers に移動します。

6.2. ルールの実行

ルールの例を特定するか、Decision Central でルールを作成したら、関連のプロジェクトをビルドしてデプロイし、ローカルまたは Decision Server でルールを実行してテストできます。

前提条件

手順

  1. Decision Central で、MenuDesignProjects に移動して、プロジェクト名をクリックします。
  2. プロジェクトの Assets ページの右上にある Deploy をクリックして、プロジェクトをビルドして Decision Server にデプロイします。ビルドに失敗したら、画面下部の Alerts パネルに記載されている問題に対処します。

    プロジェクトのデプロイに関する詳細は、Red Hat Decision Manager プロジェクトのパッケージ化およびデプロイ を参照してください。

  3. ローカルでのルール実行に使用するか、Decision Server でルールを実行するクライアントアプリケーションとして使用できるように、まだ作成されていない場合には、Decision Central 外に Maven または Java プロジェクトを作成します。プロジェクトには、pom.xml ファイルと、プロジェクトリソースの実行に必要なその他のコンポーネントを含める必要があります。

    テストプロジェクトの例は、その他の DRL ルールの作成および実行方法 を参照してください。

  4. テストプロジェクトまたはクライアントアプリケーションの pom.xml ファイルを開き、以下の依存関係が追加されていない場合は追加します。

    • kie-ci: クライアントアプリケーションで、ReleaseId を使用して、Decision Central プロジェクトデータをローカルに読み込みます。
    • kie-server-client: クライアントアプリケーションで、Decision Server のアセットを使用してリモートに接続します。
    • slf4j: (オプション) クライアントアプリケーションで、Decision Server に接続した後に、SLF4J (Simple Logging Facade for Java) を使用して、デバッグのログ情報を返します。

    クライアントアプリケーションの pom.xml ファイルにおける Red Hat Decision Manager 7.2 の依存関係の例

    <!-- For local execution -->
    <dependency>
      <groupId>org.kie</groupId>
      <artifactId>kie-ci</artifactId>
      <version>7.14.0.Final-redhat-00002</version>
    </dependency>
    
    <!-- For remote execution on Decision Server -->
    <dependency>
      <groupId>org.kie.server</groupId>
      <artifactId>kie-server-client</artifactId>
      <version>7.14.0.Final-redhat-00002</version>
    </dependency>
    
    <!-- For debug logging (optional) -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-simple</artifactId>
      <version>1.7.25</version>
    </dependency>

    このアーティファクトで利用可能なバージョンについては、オンラインの Nexus Repository Manager でグループ ID とアーティファクト ID を検索してください。

    注記

    個別の依存関係に対して Red Hat Decision Manager <version> を指定するのではなく、Red Hat Business Automation 部品表 (BOM) の依存関係をプロジェクトの pom.xml ファイルに追加することを検討してください。Red Hat Business Automation BOM は、Red Hat Decision Manager と Red Hat Process Automation Manager の両方に適用されます。BOM ファイルを追加すると、提供される Maven リポジトリーから、推移的依存関係の適切なバージョンがプロジェクトに含められます。

    BOM 依存関係の例:

    <dependency>
      <groupId>com.redhat.ba</groupId>
      <artifactId>ba-platform-bom</artifactId>
      <version>7.2.0.GA-redhat-00002</version>
      <scope>import</scope>
      <type>pom</type>
    </dependency>

    Red Hat Business Automation BOM (Bill of Materials) の詳細情報は、What is the mapping between Red Hat Decision Manager and the Maven library version? を参照してください。

  5. モデルクラスを含むアーティファクトの依存関係が、クライアントアプリケーションの pom.xml ファイルに定義されていて、デプロイしたプロジェクトの pom.xml ファイルに記載されているのと同じであることを確認します。モデルクラスの依存関係が、クライアントアプリケーションとプロジェクトで異なると、実行エラーが発生します。

    Decision Central でプロジェクトの pom.xml ファイルを利用するには、プロジェクトで既存のアセットを選択し、画面左側の Project Explorer メニューで Customize View ギアアイコンをクリックし、Repository Viewpom.xml を選択します。

    たとえば、以下の Person クラスの依存関係は、クライアントと、デプロイしたプロジェクトの pom.xml ファイルの両方に表示されます。

    <dependency>
      <groupId>com.sample</groupId>
      <artifactId>Person</artifactId>
      <version>1.0.0</version>
    </dependency>
  6. デバッグ向けロギングを行うために、slf4j 依存関係を、クライアントアプリケーションの pom.xml ファイルに追加した場合は、関連するクラスパス (Maven の src/main/resources/META-INF 内など) に simplelogger.properties ファイルを作成し、以下の内容を記載します。

    org.slf4j.simpleLogger.defaultLogLevel=debug
  7. クライアントアプリケーションに、必要なインポートを含む .java メインクラスと、KIE ベースを読み込む main() メソッドを作成し、ファクトを挿入し、ルールを実行します。

    たとえば、プロジェクトの Person オブジェクトには、名前、苗字、時給、および賃金を設定および取得するゲッターメソッドおよびセッターメソッドが含まれます。プロジェクトにある以下の Wage ルールでは、賃金と時給を計算し、その結果に基づいてメッセージを表示します。

    package com.sample;
    
    import com.sample.Person;
    
    dialect "java"
    
    rule "Wage"
      when
        Person(hourlyRate * wage > 100)
        Person(name : firstName, surname : lastName)
      then
        System.out.println("Hello" + " " + name + " " + surname + "!");
        System.out.println("You are rich!");
    end

    (必要に応じて) Decision Server の外でローカルにこのルールをテストするには、.java クラスで、KIE サービス、KIE コンテナー、および KIE セッションをインポートするように設定し、その後、main() メソッドを使用して、定義したファクトモデルに対してすべてのルールを実行するようにします。

    ローカルでルールの実行

    import org.kie.api.KieServices;
    import org.kie.api.runtime.KieContainer;
    import org.kie.api.runtime.KieSession;
    
    public class RulesTest {
    
      public static final void main(String[] args) {
        try {
          // Identify the project in the local repository:
          ReleaseId rid = new ReleaseId();
          rid.setGroupId("com.myspace");
          rid.setArtifactId("MyProject");
          rid.setVersion("1.0.0");
    
          // Load the KIE base:
          KieServices ks = KieServices.Factory.get();
          KieContainer kContainer = ks.newKieContainer(rid);
          KieSession kSession = kContainer.newKieSession();
    
          // Set up the fact model:
          Person p = new Person();
          p.setWage(12);
          p.setFirstName("Tom");
          p.setLastName("Summers");
          p.setHourlyRate(10);
    
          // Insert the person into the session:
          kSession.insert(p);
    
          // Fire all rules:
          kSession.fireAllRules();
          kSession.dispose();
        }
    
        catch (Throwable t) {
          t.printStackTrace();
        }
      }
    }

    Decision Server でこのルールをテストするには、ローカル例と同じように、インポートとルール実行情報で .java クラスを設定し、KIE サービス設定および KIE サービスクライアントの詳細を指定します。

    Decision Server でのルールの実行

    package com.sample;
    
    import java.util.ArrayList;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Set;
    
    import org.kie.api.command.BatchExecutionCommand;
    import org.kie.api.command.Command;
    import org.kie.api.KieServices;
    import org.kie.api.runtime.ExecutionResults;
    import org.kie.api.runtime.KieContainer;
    import org.kie.api.runtime.KieSession;
    import org.kie.server.api.marshalling.MarshallingFormat;
    import org.kie.server.api.model.ServiceResponse;
    import org.kie.server.client.KieServicesClient;
    import org.kie.server.client.KieServicesConfiguration;
    import org.kie.server.client.KieServicesFactory;
    import org.kie.server.client.RuleServicesClient;
    
    import com.sample.Person;
    
    public class RulesTest {
    
      private static final String containerName = "testProject";
      private static final String sessionName = "myStatelessSession";
    
      public static final void main(String[] args) {
        try {
          // Define KIE services configuration and client:
          Set<Class<?>> allClasses = new HashSet<Class<?>>();
          allClasses.add(Person.class);
          String serverUrl = "http://$HOST:$PORT/kie-server/services/rest/server";
          String username = "$USERNAME";
          String password = "$PASSWORD";
          KieServicesConfiguration config =
            KieServicesFactory.newRestConfiguration(serverUrl,
                                                    username,
                                                    password);
          config.setMarshallingFormat(MarshallingFormat.JAXB);
          config.addExtraClasses(allClasses);
          KieServicesClient kieServicesClient =
            KieServicesFactory.newKieServicesClient(config);
    
          // Set up the fact model:
          Person p = new Person();
          p.setWage(12);
          p.setFirstName("Tom");
          p.setLastName("Summers");
          p.setHourlyRate(10);
    
          // Insert Person into the session:
          KieCommands kieCommands = KieServices.Factory.get().getCommands();
          List<Command> commandList = new ArrayList<Command>();
          commandList.add(kieCommands.newInsert(p, "personReturnId"));
    
          // Fire all rules:
          commandList.add(kieCommands.newFireAllRules("numberOfFiredRules"));
          BatchExecutionCommand batch = kieCommands.newBatchExecution(commandList, sessionName);
    
          // Use rule services client to send request:
          RuleServicesClient ruleClient = kieServicesClient.getServicesClient(RuleServicesClient.class);
          ServiceResponse<ExecutionResults> executeResponse = ruleClient.executeCommandsWithResults(containerName, batch);
          System.out.println("number of fired rules:" + executeResponse.getResult().getValue("numberOfFiredRules"));
        }
    
        catch (Throwable t) {
          t.printStackTrace();
        }
      }
    }

  8. 設定した .java クラスをプロジェクトディレクトリーから実行します。(Red Hat JBoss Developer Studio などの) 開発プラットフォーム、またはコマンドラインでファイルを実行できます。

    (プロジェクトディレクトリーにおける) Maven の実行例

    mvn clean install exec:java -Dexec.mainClass="com.sample.app.RulesTest"

    (プロジェクトディレクトリーにおける) Java の実行例

    javac -classpath "./$DEPENDENCIES/*:." RulesTest.java
    java -classpath "./$DEPENDENCIES/*:." RulesTest
  9. コマンドラインおよびサーバーログで、ルール実行のステータスを確認します。ルールが期待通りに実行しない場合は、プロジェクトに設定したルールと、メインのクラス設定を確認して、提供されるデータの妥当性を確認します。

第7章 Red Hat Decision Manager の IDE 向けのデシジョン例

Red Hat Decision Manager は、統合開発環境 (IDE: integrated development environment) にインポートできるように Java クラスとして配信されるデシジョン例を提供します。これらの例は、Red Hat Decision Manager のデシジョンエンジン機能をさらに理解するために使用するか、Red Hat Decision Manager プロジェクトに定義するデシジョンの参考として使用してください。

以下のデシジョンセットの例は、Red Hat Decision Manager で利用可能な例の一部です。

  • Hello World の例: 基本的なルール実行や、デバッグ出力の使用方法を例示します。
  • 状態の例: ルールの顕著性やアジェンダグループを使用した前向き連鎖や競合解決を例示します。
  • フィボナッチの例: ルールの顕著性を使用した再帰や競合解決を例示します。
  • 銀行の例: パターン一致、基本的なソート、計算を例示します。
  • ペットショップの例: ルールアジェンダグループ、グローバル変数、コールバック、GUI 統合を例示します。
  • 数独の例: 複雑なパターン一致、問題解決、コールバック、GUI 統合を例示します。
  • House of Doom の例: 後向き連鎖と再帰を例示します。
注記

Red Hat Business Optimizer で提供される最適化の例は、Red Hat Business Optimizer のスタートガイド を参照してください。

7.1. IDE での Red Hat Decision Manager のデシジョン例のインポートおよび実行

Red Hat Decision Manager のデシジョン例を統合開発環境 (IDE) にインポートして実行し、ルールとコードがどのように機能するかチェックできます。これらの例は、Red Hat Decision Manager のデシジョンエンジン機能をさらに理解するために使用するか、Red Hat Decision Manager プロジェクトに定義するデシジョンの参考として使用してください。

前提条件

  • Java 8 以降がインストールされている。
  • Maven 3.5.x 以降をインストールしていること
  • Red Hat JBoss Developer Studio など IDE がインストールされていること

手順

  1. Red Hat カスタマーポータル から Red Hat Decision Manager 7.2.0 Source Distribution をダウンロードし、/rhdm-7.2.0-sources などの一時的なディレクトリーに展開します。
  2. IDE を開き、FileImportMavenExisting Maven Projects を選択するか、同等のオプションを選択して、Maven プロジェクトをインポートします。
  3. Browse をクリックして、~/rhdm-7.2.0-sources/src/drools-$VERSION/drools-examples (または、Conway の Game of Life の例の場合は ~/rhdm-7.2.0-sources/src/droolsjbpm-integration-$VERSION/droolsjbpm-integration-examples) に移動して、プロジェクトをインポートします。
  4. 実行するパッケージ例に移動して、main メソッドが含まれる Java クラスを検索します。
  5. Java クラスを右クリックし、Run AsJava Application を選択して例を実行します。

    基本的なユーザーインターフェイスですべての例を実行するには、Main クラス org.drools.examplesDroolsExamplesApp.java クラス (または Conway の Game of Life の場合は DroolsJbpmIntegrationExamplesApp.java クラス) を実行します。

    図7.1 drools-examples (DroolsExamplesApp.java) 内のすべての例のインターフェイス

    drools examples run all

    図7.2 droolsjbpm-integration-examples (DroolsJbpmIntegrationExamplesApp.java) のすべての例のインターフェイス

    droolsjbpm examples run all

7.2. Hello World の例のデシジョン (基本ルールおよびデバッグ)

Hello World のデシジョンセットの例では、Red Hat Decision Manager デシジョンエンジンのワーキングメモリーにオブジェクトを挿入する方法、ルールを使用してオブジェクトを一致させる方法、エンジンの内部アクティビティーを追跡するロギングの設定方法を説明します。

以下は、Hello World の例の概要です。

  • 名前: helloworld
  • Main クラス: (src/main/java 内の) org.drools.examples.helloworld.HelloWorldExample
  • モジュール: drools-examples
  • タイプ: Java アプリケーション
  • ルールファイル: (src/main/resources 内の) org.drools.examples.helloworld.HelloWorld.drl
  • 目的: 基本的なルール実行とデバッグ出力の使用方法を例示します。

Hello World の例では、KIE セッションが生成されて、ルールの実行が可能になります。すべてのルールは、実行するのに KIE セッションが必要です。

ルール実行の KIE セッション

KieServices ks = KieServices.Factory.get(); 1
KieContainer kc = ks.getKieClasspathContainer(); 2
KieSession ksession = kc.newKieSession("HelloWorldKS"); 3

1
KieServices ファクトリーを取得します。これは、アプリケーションがエンジンとの対話に使用する主なインターフェイスです。
2
プロジェクトクラスパスから KieContainer を作成します。これは、KieModuleKieContainer を設定してインスタンス化し、そこから /META-INF/kmodule.xml ファイルを検出します。
3
/META-INF/kmodule.xml ファイルに定義された KIE セッション設定 "HelloWorldKS" をもとに KieSession を作成します。
注記

Red Hat Decision Manager プロジェクトのパッケージ化に関する詳細は、Red Hat Decision Manager プロジェクトのパッケージ化およびデプロイ を参照してください。

Red Hat Decision Manager には、内部エンジンアクティビティーを公開するイベントモデルがあります。DebugAgendaEventListenerDebugWorkingMemoryEventListener のデフォルトのデバッグリスナー 2 つにより、デバッグイベント情報が System.err の出力に表示されます。KieRuntimeLogger では、実行監査と、グラフィックビューワーで確認可能な結果が提供されます。

リスナーと監査ロガーのデバッグ

// Set up listeners.
ksession.addEventListener( new DebugAgendaEventListener() );
ksession.addEventListener( new DebugRuleRuntimeEventListener() );

// Set up a file-based audit logger.
KieRuntimeLogger logger = KieServices.get().getLoggers().newFileLogger( ksession, "./target/helloworld" );

// Set up a ThreadedFileLogger so that the audit view reflects events while debugging.
KieRuntimeLogger logger = ks.getLoggers().newThreadedFileLogger( ksession, "./target/helloworld", 1000 );

ロガーは、AgendaRuleRuntime リスナーにビルドされる特別な実装です。エンジンが実行を終えると、logger.close() が呼び出されます。

この例では、"Hello World" というメッセージを含む Message オブジェクトを作成し、ステータス HELLOKieSession に挿入して、fireAllRules() でルールを実行します。

データの挿入および実行

// Insert facts into the KIE session.
final Message message = new Message();
message.setMessage( "Hello World" );
message.setStatus( Message.HELLO );
ksession.insert( message );

// Fire the rules.
ksession.fireAllRules();

ルール実行は、データモデルを使用して、KieSession への出入力としてデータを渡します。この例のデータモデルには message (String) と status (HELLO または GOODBYE) の 2 つのフィールドが含まれます。

データモデルクラス

public static class Message {
    public static final int HELLO   = 0;
    public static final int GOODBYE = 1;

    private String          message;
    private int             status;
    ...
}

この 2 つのルールは、src/main/resources/org/drools/examples/helloworld/HelloWorld.drl ファイルに配置されます。

"Hello World" ルールの when 条件では、ステータスが Message.HELLO の KIE セッションに、Message オブジェクトが挿入されるたびに、このルールをアクティベートすると記述しています。さらに、変数のバインドが 2 つ作成されます (message 変数を message 属性に、m 変数を一致する Message オブジェクト自体にバインド)。

ルールの then アクションは、ルールの dialect 属性に宣言されているように、MVEL 式言語を使用して記述されます。message の束縛変数のコンテンツを System.out に出力した後に、ルールは m にバインドされている Message オブジェクトの messagestatus 属性値を変更します。このルールは MVEL の modify ステートメントを使用して、1 つのステートメントに割り当てブロックを適用し、この変更についてブロックの最後でエンジンに通知します。

"Hello World" のルール

rule "Hello World"
      dialect "mvel"
  when
    m : Message( status == Message.HELLO, message : message )
  then
    System.out.println( message );
    modify ( m ) { message = "Goodbye cruel world",
                   status = Message.GOODBYE };
end

java 方言を指定する "Good Bye" ルールは、ステータスが Message.GOODBYEMessage オブジェクトと一致する点を除き、"Hello World" ルールによく似ています。

"Good Bye" ルール

rule "Good Bye"
      dialect "java"
  when
    Message( status == Message.GOODBYE, message : message )
  then
    System.out.println( message );
end

この例を実行するには、org.drools.examples.helloworld.HelloWorldExample クラスを IDE で Java アプリケーションとして実行します。このルールは System.out に、デバッグリスナーは System.err に書き込み、監査ロガーは target/helloworld.log のログファイルを作成します。

IDE コンソールの System.out 出力

Hello World
Goodbye cruel world

IDE コンソールでの System.err の出力

==>[ActivationCreated(0): rule=Hello World;
                   tuple=[fid:1:1:org.drools.examples.helloworld.HelloWorldExample$Message@17cec96]]
[ObjectInserted: handle=[fid:1:1:org.drools.examples.helloworld.HelloWorldExample$Message@17cec96];
                 object=org.drools.examples.helloworld.HelloWorldExample$Message@17cec96]
[BeforeActivationFired: rule=Hello World;
                   tuple=[fid:1:1:org.drools.examples.helloworld.HelloWorldExample$Message@17cec96]]
==>[ActivationCreated(4): rule=Good Bye;
                   tuple=[fid:1:2:org.drools.examples.helloworld.HelloWorldExample$Message@17cec96]]
[ObjectUpdated: handle=[fid:1:2:org.drools.examples.helloworld.HelloWorldExample$Message@17cec96];
                old_object=org.drools.examples.helloworld.HelloWorldExample$Message@17cec96;
                new_object=org.drools.examples.helloworld.HelloWorldExample$Message@17cec96]
[AfterActivationFired(0): rule=Hello World]
[BeforeActivationFired: rule=Good Bye;
                   tuple=[fid:1:2:org.drools.examples.helloworld.HelloWorldExample$Message@17cec96]]
[AfterActivationFired(4): rule=Good Bye]

この例の実行フローをさらに理解するには、target/helloworld.log の監査ログファイルを IDE デバッグビュー (または Audit View が利用できる場合は Audit View (例: IDE の WindowShow View)) に読み込みます。

この例では、Audit view で、オブジェクトが挿入され、"Hello World" ルールのアクティベーションが作成されます。次に、このアクティベーションが実行され、Message オブジェクトを更新して、"Good Bye" ルールのアクティベーションをトリガーします。最後に、"Good Bye" ルールが実行します。Audit View でイベントが選択されると、この例の "Activation created" イベントである元のイベントが緑色にハイライトされます。

図7.3 Hello World の例の監査ビュー

helloworld auditview1

7.3. 状態の例のデシジョン (前向き連鎖および競合解決)

状態の例のディジョンセットでは、デシジョンエンジンが前向き連鎖と、ワーキングメモリー内のファクトへの変更をどのように使用してルールの実行競合を順番に解決していくのかを例示します。この例では、ルールで定義可能な顕著性の値またはアジェンダグループを使用して競合を解決することにフォーカスします。

以下は、状態の例の概要です。

  • 名前: state
  • Main クラス: (src/main/java 内の) org.drools.examples.state.StateExampleUsingSalienceorg.drools.examples.state.StateExampleUsingAgendaGroup
  • モジュール: drools-examples
  • タイプ: Java アプリケーション
  • ルールファイル: (src/main/resources 内の) org.drools.examples.state.*.drl
  • 目的: ルールの顕著性やアジェンダグループを使用した前向き連鎖や競合解決を例示します。

前向き連鎖のルールシステムは、ディジョンエンジンのワーキングメモリーにあるファクトで開始して、そのファクトへの変更に反応するデータ駆動型のシステムです。オブジェクトがワーキングメモリーに挿入されると、その変更の結果として True となるルールの条件はすべて、アジェンダによって実行されるようにスケジュールされます。

反対に、後向き連鎖のルールシステムは、しばしば再帰を使用して、デシジョンエンジンが満たそうとする結論から開始する目的駆動型のシステムです。システムが結論または目的に到達できない場合は、サブとなる目的、つまり、現在の目的の一部を完了する結論を検索します。システムは、最初の結論が満たされるか、すべてのサブとなる目的が満たされるまでこのプロセスを続行します。

Red Hat Decision Manager のデシジョンエンジンは、前向き連鎖と後向き連鎖の両方を使用してルールを評価します。

以下の図は、デシジョンエンジンが、ロジックフローで後向き連鎖のセグメントと、前向き連鎖全体を使用してルールを評価する方法を例示します。

図7.4 前向き連鎖と後向き連鎖を使用したルール評価のロジック

RuleEvaluation Enterprise

状態の例では、State クラスごとに、名前や現在の状態のフィールドが含まれます (org.drools.examples.state.State のクラス参照)。以下の状態は、各プロジェクトで考えられる 2 つの状態です。

  • NOTRUN
  • FINISHED

State クラス

public class State {
    public static final int NOTRUN   = 0;
    public static final int FINISHED = 1;

    private final PropertyChangeSupport changes =
        new PropertyChangeSupport( this );

    private String name;
    private int    state;

    ... setters and getters go here...
}

状態の例には、同じ例が 2 つのバージョンとして提供されており、それぞれルール実行の競合を解決します。

  • ルールの顕著性を使用して競合を解決する StateExampleUsingSalience バージョン
  • ルールアジェンダグループを使用して競合を解決する StateExampleUsingAgendaGroups バージョン

状態の例のバージョンはいずれも、ABC、および D の 4 つの State オブジェクトを使用します。最初に、それぞれの状態は、NOTRUN に設定されます。NOTRUN は、例が使用するコンストラクターのデフォルト値です。

顕著性を使用した状態の例

状態の例の StateExampleUsingSalience バージョンでは、ルールで顕著性の値を使用し、ルール実行の競合を解決します。顕著性の値が高いルールは、アクティベーションキューの順番で、優先度が高くなります。

この例では、各 State インスタンスを KIE セッションに挿入して、fireAllRules() を呼び出します。

顕著性の状態例の実行

final State a = new State( "A" );
final State b = new State( "B" );
final State c = new State( "C" );
final State d = new State( "D" );

ksession.insert( a );
ksession.insert( b );
ksession.insert( c );
ksession.insert( d );

ksession.fireAllRules();

// Dispose KIE session if stateful (not required if stateless).
ksession.dispose();

この例を実行するには、IDE で Java アプリケーションとして org.drools.examples.state.StateExampleUsingSalience クラスを実行します。

実行後に、以下の出力が IDE コンソールウィンドウに表示されます。

IDE コンソールでの顕著性の状態例の出力

A finished
B finished
C finished
D finished

4 つのルールが存在します。

まず、"Bootstrap" ルールが実行され、A の状態が FINISHED に設定されます。次に、B の状態が FINISHED に変更されます。オブジェクト CD はいずれも B に依存するため競合が発生しますが、顕著性の値で解決されます。

この例の実行フローをさらに理解するには、target/state.log の監査ログファイルを IDE デバッグビュー (または Audit View が利用できる場合は Audit View (例: IDE の WindowShow View)) に読み込みます。

この例では、Audit View は、状態が NOTRUN のオブジェクト A のアサーションが "Bootstrap" ルールをアクティベートしますが、他のオブジェクトのアサーションはすぐに有効になりません。

図7.5 顕著性の状態例の監査ビュー

state example audit1

顕著性の状態例の "Bootstrap" ルール

rule "Bootstrap"
  when
    a : State(name == "A", state == State.NOTRUN )
  then
    System.out.println(a.getName() + " finished" );
    a.setState( State.FINISHED );
end

"Bootstrap" ルールを実行すると、A の状態が FINISHED に変わり、ルール "A to B" をアクティベートします。

顕著性の状態例の "A to B" ルール

rule "A to B"
  when
    State(name == "A", state == State.FINISHED )
    b : State(name == "B", state == State.NOTRUN )
  then
    System.out.println(b.getName() + " finished" );
    b.setState( State.FINISHED );
end

"A to B" ルールを実行すると、B の状態を FINISHED に変更し、"B to C""B to D" の両方のルールをアクティベートして、これらのアクティベーションをエンジンアジェンダに配置します。

顕著性の状態例の "B to C" および "B to D" ルール

rule "B to C"
    salience 10
  when
    State(name == "B", state == State.FINISHED )
    c : State(name == "C", state == State.NOTRUN )
  then
    System.out.println(c.getName() + " finished" );
    c.setState( State.FINISHED );
end

rule "B to D"
  when
    State(name == "B", state == State.FINISHED )
    d : State(name == "D", state == State.NOTRUN )
  then
    System.out.println(d.getName() + " finished" );
    d.setState( State.FINISHED );
end

この時点から、両方のルールが実行される可能性があるため、これらのルールは競合しています。競合解決ストラテジーを使用すると、エンジンアジェンダがどのルールを実行するかを決定できます。"B to C" は、顕著性の値が高いため (デフォルトの顕著性の値 0 に対して 10) 先に実行し、オブジェクト C の状態が FINISHED に変更されます。

IDE の Audit View では、ルール "A to B"State オブジェクトが変更され、2 つのアクティベーションが競合する結果になることが分かります。

IDE で Agenda View を使用して、エンジンアジェンダの状態を調査できます。この例では Agenda View で、ルール "A to B" のブレークポイントと、2 つの競合するルールを持つアジェンダの状態が分かります。最後にルール "B to D" が実行され、オブジェクト D の状態が FINISHED に変更されます。

図7.6 顕著性の状態例のアジェンダビュー

state example agenda1

アジェンダグループを使用した状態の例

状態の例の StateExampleUsingAgendaGroups バージョンでは、ルールでアジェンダグループを使用し、ルール実行における競合を解決します。アジェンダグループを使用すると、エンジンアジェンダを分割し、ルールのグループの実行を、詳細にわたり制御できるようになります。デフォルトでは、ルールはすべてアジェンダグループ MAIN に含まれています。agenda-group 属性を使用して、ルールに異なるアジェンダグループを指定できます。

最初は、ワーキングメモリーは、アジェンダグループ MAIN にフォーカスを当てます。アジェンダグループのルールは、グループがこのフォーカスを受けた場合のみ実行されます。setFocus() メソッドか、auto-focus ルール属性を使用してフォーカスを設定できます。auto-focus 属性を使用すると、ルールが一致してアクティベートされた場合のみ、ルールにアジェンダグループのフォーカスが自動的に当てられます。

この例では、auto-focus 属性を使用すると "B to D" の前に "B to C" ルールを実行できます。

アジェンダグループの状態例のルール "B to C"

rule "B to C"
    agenda-group "B to C"
    auto-focus true
  when
    State(name == "B", state == State.FINISHED )
    c : State(name == "C", state == State.NOTRUN )
  then
    System.out.println(c.getName() + " finished" );
    c.setState( State.FINISHED );
    kcontext.getKnowledgeRuntime().getAgenda().getAgendaGroup( "B to D" ).setFocus();
end

ルール "B to C" は、アジェンダグループ "B to D"setFocus() を呼び出し、アクティブなルールを実行できるようにします。その後にルール "B to D" が実行できるようになります。

アジェンダグループの状態例のルール "B to D"

rule "B to D"
    agenda-group "B to D"
  when
    State(name == "B", state == State.FINISHED )
    d : State(name == "D", state == State.NOTRUN )
  then
    System.out.println(d.getName() + " finished" );
    d.setState( State.FINISHED );
end

この例を実行するには、IDE で Java アプリケーションとして org.drools.examples.state.StateExampleUsingAgendaGroups クラスを実行します。

実行後に、以下の出力が IDE コンソールウィンドウに表示されます (状態の例の顕著性バージョンと同じ)。

IDE コンソールでのアジェンダグループの状態例の出力

A finished
B finished
C finished
D finished

状態の例の含まれる動的なファクト

状態の例に含まれる主なコンセプトとしては、他にも PropertyChangeListener オブジェクトを実装するオブジェクトに基づいて 動的ファクト を使用するというものがあります。エンジンがファクトプロパティーへの変更を確認し、対応するためには、アプリケーションがエンジンに対して、変更があったことを通知する必要があります。modify ステートメントを使用して、このコミュニケーションをルールで明示的に設定するか、JavaBeans 仕様で定義されているようにファクトが PropertyChangeSupport インターフェイスを実装するように指定することで暗黙的に設定できます。

この例は、ルールで modify ステートメントを明示的に指定しなくても良いように PropertyChangeSupport インターフェイスを使用する方法が示されています。このインターフェイスを使用するには、org.drools.example.State クラスと同じ方法で、ファクトに PropertyChangeSupport が実装されていることを確認し、DRL ルールファイルで以下のコードを使用して、これらのファクトでプロパティー変更がないかをリッスンするようにエンジンを設定してください。

動的ファクトの宣言

declare type State
  @propertyChangeSupport
end

PropertyChangeListener オブジェクトを使用する場合に、各セッターは通知用に追加のコードを実装する必要があります。たとえば、state の以下のセッターは org.drools.examples のクラスに含まれます。

PropertyChangeSupport のセッター例

public void setState(final int newState) {
    int oldState = this.state;
    this.state = newState;
    this.changes.firePropertyChange( "state",
                                     oldState,
                                     newState );
}

7.4. フィボナッチの例のデシジョン (再帰および競合解決)

フィボナッチの例のデシジョンセットでは、デシジョンエンジンが再帰をどのように使用してルールの実行競合を順番に解決していくのかを例示します。この例では、ルールで定義可能な顕著性の値を使用して競合を解決することにフォーカスします。

以下は、フィボナッチの例の概要です。

  • 名前: フィボナッチ
  • Main クラス: (src/main/java 内の) org.drools.examples.fibonacci.FibonacciExample
  • モジュール: drools-examples
  • タイプ: Java アプリケーション
  • ルールファイル: (src/main/resources 内の) org.drools.examples.fibonacci.Fibonacci.drl
  • 目的: ルールの顕著性を使用した再帰や競合解決を例示します。

フィボナッチ数は、0 または 1 で開始する数列です。0、1、1、2、3、5、8、13、21、34、55、89、144、233、377、610、987、1597、2584、4181、6765、10946 などのように、2 つの先行する数を足すことにより、次にくるフィボナッチ数が求められます。

フィボナッチの例では、Fibonacci のファクトクラスを 1 つ使用し、このクラスに以下の 2 つのフィールドが含まれています。

  • sequence
  • value

sequence フィールドは、フィボナッチ数列のオブジェクトの位置を示します。value フィールドは、その数列の位置のフィボナッチオブジェクトの値を示します。-1 は、計算する必要がある値という意味です。

フィボナッチクラス

public static class Fibonacci {
    private int  sequence;
    private long value;

    public Fibonacci( final int sequence ) {
        this.sequence = sequence;
        this.value = -1;
    }

    ... setters and getters go here...
}

この例を実行するには、IDE で Java アプリケーションとして org.drools.examples.fibonacci.FibonacciExample クラスを実行します。

実行後に、以下の出力が IDE コンソールウィンドウに表示されます。

IDE コンソールでのフィボナッチの例の出力

recurse for 50
recurse for 49
recurse for 48
recurse for 47
...
recurse for 5
recurse for 4
recurse for 3
recurse for 2
1 == 1
2 == 1
3 == 2
4 == 3
5 == 5
6 == 8
...
47 == 2971215073
48 == 4807526976
49 == 7778742049
50 == 12586269025

Java でこの動作を実現するには、sequence フィールドに 50 を指定して、Fibonacci オブジェクトを挿入します。この例では、次に再帰ルールを使用して、他の 49 個の Fibonacci オブジェクトを挿入します。

PropertyChangeSupport インターフェイスを実装して動的ファクトを使用する代わりに、この例では MVEL 方言の modify キーワードを使用して、ブロックセッターアクションを有効にしてエンジンに変更を通知しています。

フィボナッチの例の実行

ksession.insert( new Fibonacci( 50 ) );
ksession.fireAllRules();

この例では、以下の 3 つのルールを使用します。

  • "Recurse"
  • "Bootstrap"
  • "Calculate"

"Recurse" ルールは、値が -1 の、アサートされた各 Fibonacci オブジェクトを照合して、現在の値よりも数列が 1 つ小さい Fibonacci オブジェクトを新たに作成し、アサートします。数列フィールドが 1 に相当するオブジェクトが存在しない場合に、フィボナッチオブジェクトが追加されると毎回、このルールは再度照合され、実行されます。メモリーにフィボナッチオブジェクト 50 個がすべて存在する場合は、not 条件要素を使用して、ルールの合致を停止します。また、"Bootstrap" ルールを実行する前に Fibonacci オブジェクト 50 個をすべてアサートする必要があるため、このルールには salience の値も含まれます。

ルール "Recurse"

rule "Recurse"
    salience 10
  when
    f : Fibonacci ( value == -1 )
    not ( Fibonacci ( sequence == 1 ) )
  then
    insert( new Fibonacci( f.sequence - 1 ) );
    System.out.println( "recurse for " + f.sequence );
end

この例の実行フローをさらに理解するには、target/fibonacci.log の監査ログファイルを IDE デバッグビュー (または Audit View が利用できる場合は Audit View (例: IDE の WindowShow View)) に読み込みます。

この例では、監査ビュー に、sequence フィールドが 50 に指定された、Fibonacci の元のアサーションが表示されます。これは Java コードで実行されています。これ以降、監査ビュー で、ルールの再帰が継続して行われ、アサートされた Fibonacci オブジェクトにより、"Recurse" ルールがアクティベートされて、再度実行されます。

図7.7 監査ビューでのルール "Recurse"

fibonacci1

sequence フィールドが 2Fibonacci オブジェクトがアサートされると、"Bootstrap" ルールが一致し、"Recurse" ルールとともにアクティベートされます。フィールド sequence には複数の制約があり、1 または 2 と同等かをテストしている点に注目してください。

ルール "Bootstrap"

rule "Bootstrap"
  when
    f : Fibonacci( sequence == 1 || == 2, value == -1 ) // multi-restriction
  then
    modify ( f ){ value = 1 };
    System.out.println( f.sequence + " == " + f.value );
end

IDE で Agenda View を使用して、エンジンアジェンダの状態を調査できます。"Recurse" の顕著性の値のほうが高いため、"Bootstrap" ルールは実行していません。

図7.8 アジェンダビュー 1 でのルール "Recurse" および "Bootstrap"

fibonacci agenda1

sequence1Fibonacci オブジェクトがアサートされると、"Bootstrap" ルールが再度一致し、このルールに含まれる 2 つのルールがアクティベートされます。sequence1Fibonacci オブジェクトが存在すると、すぐに not 条件要素でルールが一致しなくなるため、"Recurse" ルールの照合やアクティベーションはされません。

図7.9 アジェンダビュー 2 でのルール "Recurse" および "Bootstrap"

fibonacci agenda2

"Bootstrap" ルールは、sequence12 のオブジェクトの値を 1 に設定します。値が -1 でない Fibonacci オブジェクトが 2 つあるため、"Calculate" ルールの照合が可能になります。

この例のある時点で、ワーキングメモリーに 50 近くの Fibonacci オブジェクトが存在します。3 つ選択してそれぞれを乗算し、順番に各値を計算する必要があります。フィールドの制約なしに、ルールで 3 つの Fibonacci パターンを使用してクラス積候補を絞り込む場合に、考えられる組み合わせとして 50x49x48 通りあり、約 12 万 5000 のルールを実行できるにもかかわらず、その大半が誤っていることになります。

"Calculate" ルールは、フィールドの制約を使用して正しい順番にフィボナッチパターンを 3 つ評価します。この手法は cross-product matching と呼ばれます。

最初のパターンでは、値が != -1Fibonacci オブジェクトを検索して、このパターンとフィールド両方をバインドします。2 番目の Fibonacci オブジェクトが実行する内容は同じですが、別のフィールド制約を追加して、シーケンスが f1 にバインドされている Fibonacci オブジェクトより 1 つ大きくなるようにします。このルールが初めて実行されると、シーケンスが 12 にだけ、値 1 が割り当てられていることが分かります。また、この 2 つの制約で、f1 がシーケンス 1 を参照し、f2 がシーケンス 2 を参照するようにします。

最後のパターンでは、値が -1 と等しく、シーケンスが f2 よりも大きい Fibonacci オブジェクトを検索します。

フィボナッチの例のこの時点で、3 つの Fibonacci オブジェクトが利用可能なクロス積から正しく選択され、f3 にバインドされている 3 番目の Fibonacci オブジェクトの値を計算できます。

ルール "Calculate"

rule "Calculate"
  when
    // Bind f1 and s1.
    f1 : Fibonacci( s1 : sequence, value != -1 )
    // Bind f2 and v2, refer to bound variable s1.
    f2 : Fibonacci( sequence == (s1 + 1), v2 : value != -1 )
    // Bind f3 and s3, alternative reference of f2.sequence.
    f3 : Fibonacci( s3 : sequence == (f2.sequence + 1 ), value == -1 )
  then
    // Note the various referencing techniques.
    modify ( f3 ) { value = f1.value + v2 };
    System.out.println( s3 + " == " + f3.value );
end

modify ステートメントにより、f3 にバインドされた Fibonacci オブジェクトの値が更新されます。つまり、値が -1 以外の Fibonacci オブジェクトが新たに存在するということで、"Calculate" ルールにより、再度合致があるか検索して次のフィボナッチ番号を算出することができます。

IDE のデバッグビューまたは 監査ビュー では、最後の "Bootstrap" ルールが実行されることで Fibonacci オブジェクトが変更され、"Calculate" ルールに合致し、次に、別の Fibonacci オブジェクトが変更され、この "Calculate" ルールに再度合致できていることが分かります。このプロセスは、すべての Fibonacci オブジェクトに値が設定されるまで継続されます。

図7.10 監査ビューのルール

fibonacci4

7.5. 価格設定のデシジョン例 (デシジョンテーブル)

価格設定のデシジョンセットの例では、スプレッドシートのデシジョンテーブルを使用して、DRL ファイルに直接ではなく、表形式で保険料金の価格を計算する方法を説明します。

以下は価格設定の例の概要です。

  • 名前: decisiontable
  • Main クラス: (src/main/java の場合) org.drools.examples.decisiontable.PricingRuleDTExample
  • モジュール: drools-examples
  • タイプ: Java アプリケーション
  • ルールファイル: org.drools.examples.decisiontable.ExamplePolicyPricing.xls (src/main/resources 内)
  • 目的: スプレッドシートのデシジョンテーブルを使用してルールを定義する方法を示します。

デシジョンテーブルは、テーブル形式でビジネスルールを定義するのに使用できる XLS または XLSX スプレッドシートで、Red Hat Decision Manager プロジェクトに含めたり、Decision Central にアップロードしたりできます。スプレッドシートの各行がルールになり、各列が条件、アクション、または別のルール属性になります。最初に Red Hat Decision Manager でデシジョンテーブルを作成してアップロードします。次に、その他のすべてのルールアセットと同じように、定義したルールを Drools Rule Language (DRL) ルールにコンパイルします。

この価格設定の例では、特定タイプの保険を申請するドライバーに対して基本価格と割引を計算するビジネスルールセットを提供します。ドライバーの年齢と履歴、およびポリシータイプはすべて、基本保険料の計算に役立ち、追加のルールは、ドライバーが適格となる可能性のある潜在的な割引を計算します。

この例を実行するには、IDE で Java アプリケーションとして org.drools.examples.decisiontable.PricingRuleDTExample クラスを実行します。

実行後に、以下の出力が IDE コンソールウィンドウに表示されます。

Cheapest possible
BASE PRICE IS: 120
DISCOUNT IS: 20

この例を実行するコードは、標準の実行パターンに準拠しています。ルールが読み込まれ、ファクトが挿入されて、ステートレス KIE セッションが作成されます。この例における違いは、DRL ファイルや他のソースではなく、ExamplePolicyPricing.xls ファイルでルールが定義されるという点です。このスプレッドシートファイルは、テンプレートと DRL ルールを使用してデシジョンエンジンに読み込まれます。

スプレッドシートのデシジョンテーブルの設定

ExamplePolicyPricing.xls スプレッドシートには、以下の 2 つのデシジョンテーブルが含まれています。

  • Base pricing rules
  • Promotional discount rules

この例のスプレッドシートで分かるように、デシジョンテーブルの作成にはスプレッドシートの最初のタブしか使用できませんが、単一のタブ内に複数のテーブルが作成できます。デシジョンテーブルは必ずしもトップダウンの論理に従うものではなく、ルールとなるデータを補足する手段です。ルールの評価は、ルールエンジンのすべての通常のメカニックが適用されるため、必ずしも特定の順序で行われるわけではありません。このために、スプレッドシートの同一タブ内に複数のデシジョンテーブルが作成可能となります。

デシジョンテーブルは、対応するルールテンプレートファイルである BasePricing.drtPromotionalPricing.drt で実行されます。これらのテンプレートファイルはテンプレートパラメーターによってデシジョンテーブルを参照し、デシジョンテーブルの条件およびアクションの各種ヘッダーを直接参照します。

BasePricing.drt ルールテンプレートファイル

template header
age[]
profile
priorClaims
policyType
base
reason

package org.drools.examples.decisiontable;

template "Pricing bracket"
age
policyType
base

rule "Pricing bracket_@{row.rowNumber}"
  when
    Driver(age >= @{age0}, age <= @{age1}
        , priorClaims == "@{priorClaims}"
        , locationRiskProfile == "@{profile}"
    )
    policy: Policy(type == "@{policyType}")
  then
    policy.setBasePrice(@{base});
    System.out.println("@{reason}");
end
end template

PromotionalPricing.drt ルールテンプレートファイル

template header
age[]
priorClaims
policyType
discount

package org.drools.examples.decisiontable;

template "discounts"
age
priorClaims
policyType
discount

rule "Discounts_@{row.rowNumber}"
  when
    Driver(age >= @{age0}, age <= @{age1}, priorClaims == "@{priorClaims}")
    policy: Policy(type == "@{policyType}")
  then
    policy.applyDiscount(@{discount});
end
end template

ルールは、KIE セッション DTableWithTemplateKBkmodule.xml 参照によって実行されます。これは ExamplePolicyPricing.xls スプレッドシートを特定して参照するもので、ルールの実行の成功には必要なものです。この実行方法により、ルールをスタンドアロンユニットとして実行したり (ここでの例) パッケージ化されたナレッジ JAR (KJAR) ファイルにルールを含めたりすることができるため、スプレッドシートはルール実行とともにパッケージ化されます。

kmodule.xml ファイルの以下のセクションは、ルールが正常に実行され、スプレッドシートが機能するために必要になります。

    <kbase name="DecisionTableKB" packages="org.drools.examples.decisiontable">
        <ksession name="DecisionTableKS" type="stateless"/>
    </kbase>

    <kbase name="DTableWithTemplateKB" packages="org.drools.examples.decisiontable-template">
        <ruleTemplate dtable="org/drools/examples/decisiontable-template/ExamplePolicyPricingTemplateData.xls"
                      template="org/drools/examples/decisiontable-template/BasePricing.drt"
                      row="3" col="3"/>
        <ruleTemplate dtable="org/drools/examples/decisiontable-template/ExamplePolicyPricingTemplateData.xls"
                      template="org/drools/examples/decisiontable-template/PromotionalPricing.drt"
                      row="18" col="3"/>
        <ksession name="DTableWithTemplateKS"/>
    </kbase>

ルールテンプレートファイルを使用したデシジョンテーブルの実行方法とは別に、DecisionTableConfiguration オブジェクトを使用して、DecisionTableInputType.xls のような入力スプレッドシートを入力タイプとして指定することもできます。

DecisionTableConfiguration dtableconfiguration =
    KnowledgeBuilderFactory.newDecisionTableConfiguration();
        dtableconfiguration.setInputType( DecisionTableInputType.XLS );

        KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();

        Resource xlsRes = ResourceFactory.newClassPathResource( "ExamplePolicyPricing.xls",
                                                                getClass() );
        kbuilder.add( xlsRes,
                      ResourceType.DTABLE,
                      dtableconfiguration );

価格設定の例では以下の 2 つのファクトタイプを使用します。

  • Driver
  • Policy

この例では、これらのファクトのデフォルト値をそれぞれの Java クラス Driver.javaPolicy.java に設定します。この Driver は 30 歳で、これまでに保険の請求をしたことがなく、現在のリスクプロファイルが LOW となっています。申請している PolicyCOMPREHENSIVE です。

デシジョンテーブルでは、各行は異なるルールと見なされ、各列は条件またはアクションとみなされます。実行時にアジェンダがクリアされなければ、デシジョンテーブルの各行が評価されます。

デシジョンテーブルのスプレッドシートには、ルールデータを定義する以下の 2 つの主要なエリアが必要です。

  • RuleSet エリア
  • RuleTable エリア

RuleSet 領域では、ルールセット名、ユニバーサルルール属性など、(このスプレッドシートだけでなく) すべてのルールをパッケージ全体に、グローバルに適用する要素を定義します。RuleTable 領域では、実際のルール (行) と、指定したルールセットのルールテーブルを設定する条件、アクション、その他のルール属性 (列) を定義します。デシジョンテーブルのスプレッドシートには複数の RuleTable エリアを追加できますが、RuleSet エリアは 1 つのみとなります。

図7.11 デシジョンテーブルの設定

DT Config

RuleTable エリアでは、ルール属性を適用するオブジェクトも定義します。この例では、DriverPolicy、さらにオブジェクトの制限です。たとえば、Driver オブジェクトの制限では、Age Bracket 列が age >= $1, age <= $2 と定義されています。ここでのコンマ区切りの範囲は、18,24 などのテーブルの列値で定義されます。

Base pricing rules

価格設定例での Base pricing rules デシジョンテーブルでは、ドライバーの年齢、リスクプロファイル、請求数、ポリシータイプを評価し、これらの条件をベースにしたポリシーの基本価格を算定します。

図7.12 基本価格の計算

DT Table1

Driver 属性は、以下のテーブル列で定義されます。

  • Age Bracket: この年齢層には、ドライバー年齢の条件範囲を定義する条件 age >=$1, age <=$2 の定義があります。この条件列では $1 and $2 を使用しており、スプレッドシートではコンマ区切りになります。ここでの値入力は 18,2418, 24 の形式となり、両方ともビジネスルールの実行で機能する形式です。
  • Location risk profile: リスクプロファイルは、この例のプロブラムでは常に LOW として渡す文字列です。ただし、MED または HIGH に変更することが可能です。
  • Number of prior claims: これまでの請求数は整数で定義し、アクションをトリガーするには条件列のものと同一である必要があります。この値は範囲ではなく、完全一致のみになります。

デシジョンテーブルの Policy は、ルールの条件とアクションの両方で使用され、属性は以下のテーブル列で定義されます。

  • Policy type applying for: ポリシータイプは文字列として渡される条件で、適用する以下のいずれかのポリシータイプ (COMPREHENSIVEFIRE_THEFT、または THIRD_PARTY) を定義します。
  • Base $ AUD: basePriceACTION として定義され、これはこの値に対応するスプレッドシートのセルをベースに制限 policy.setBasePrice($param); で価格を設定します。このデシジョンテーブルの対応する DRL ルールを実行する際には、ファクトに合致する true 条件でルールの then 部分がこのアクションステートメントを実行し、基本価格を対応する値に設定します。
  • Record Reason: ルールが正常に実行されると、アクションは出力メッセージを System.out コンソールに生成し、どのルールが適用されたかが反映されます。これは後でアプリケーションにキャプチャーされ、出力されます。

この例では、左側の最初の列でルールをカテゴリー分けしています。この列は注釈目的で、ルール実行には影響がありません。

Promotional discount rules

価格設定例での Promotional discount rules デシジョンテーブルでは、ドライバーの年齢、請求数、ポリシータイプを評価し、ポリシー価格の割引を算定します。

図7.13 割引計算

DT Table2

このデシジョンテーブルには、ドライバーに適用可能な割引条件が含まれています。基本価格の算定と同様に、このテーブルではドライバーの AgeNumber of prior claims、および Policy type applying for を評価して、適用する Discount % 率を判定します。たとえば、ドライバーは 30 歳であり、請求履歴がなく、COMPREHENSIVE ポリシーを申請している場合は、20 パーセントの割引率が導き出されます。

7.6. ペットショップの例のデシジョン (アジェンダグループ、グローバル変数、コールバック、および GUI 統合)

ペットショップの例のデシジョンセットでは、ルールでのアジェンダグループとグローバル変数の使用方法と、Red Hat Decision Manager ルールとグラフィカルユーザーインターフェイス (GUI) の統合方法が分かります。今回は Swing ベースのデスクトップアプリケーションを使用します。また、この例では、コールバックを使用して実行中のデシジョンエンジンと通信し、ランタイム時に加えられたワーキングメモリー内の変更をもとに GUI を更新する方法を例示しています。

以下は、ペットショップの例の概要です。

  • 名前: petstore
  • Main クラス: (src/main/java 内の) org.drools.examples.petstore.PetStoreExample
  • モジュール: drools-examples
  • タイプ: Java アプリケーション
  • ルールファイル: (src/main/resources 内の) org.drools.examples.petstore.PetStore.drl
  • 目的: ルールアジェンダグループ、グローバル変数、コールバック、および GUI 統合を例示します。

ペットショップの例では、PetStoreExample.java クラス例を使用して (Swing イベントを処理する複数のクラスに加え)、以下のクラスを主に定義しています。

  • Petstore には main() メソッドが含まれます。
  • PetStoreUI は Swing ベースの GUI を作成して表示します。このクラスには複数の小さいクラスが含まれており、マウスボタンのクリックなど、さまざまな GUI イベントに主に対応します。
  • TableModel には表データが含まれています。このクラスは基本的に、Swing クラス AbstractTableModel を拡張する JavaBean です。
  • CheckoutCallback により、GUI がルールと対話できるようになります。
  • Ordershow は購入するアイテムを保持します。
  • Purchase には、注文の詳細と、購入する製品が保存されます。
  • Product は、販売可能な商品と価格の詳細を含む JavaBean です。

この例の Java コードはほぼ、プレーンな JavaBean か Swing ベースとなっています。Swing コンポーネントの詳細は、Creating a GUI with JFC/Swing の Java チュートリアルを参照してください。

ペットショップの例でのルール実行動作

他の例のデシジョンセットではファクトがすぐにアサートされて実行されるのに対し、ペットショップの例では、ユーザーの対話をもとに他のファクトが収集されるまでルールが実行します。このルールでは、コンストラクターで作成される PetStoreUI オブジェクトを使用してルールを実行し、Vector オブジェクトの stock を受けれ入れて商品を収集します。次に、この例では、以前の読み込まれたルールベースを含む CheckoutCallback クラスのインスタンスを使用します。

ペットショップの KIE コンテナーおよびファクト実行の設定

// KieServices is the factory for all KIE services.
KieServices ks = KieServices.Factory.get();

// Create a KIE container on the class path.
KieContainer kc = ks.getKieClasspathContainer();

// Create the stock.
Vector<Product> stock = new Vector<Product>();
stock.add( new Product( "Gold Fish", 5 ) );
stock.add( new Product( "Fish Tank", 25 ) );
stock.add( new Product( "Fish Food", 2 ) );

// A callback is responsible for populating the working memory and for firing all rules.
PetStoreUI ui = new PetStoreUI( stock,
                                new CheckoutCallback( kc ) );
ui.createAndShowGUI();

ルールを実行する Java コードは CheckoutCallBack.checkout() メソッドに含まれます。このメソッドは、ユーザーが UI で チェックアウト をクリックするとトリガーされます。

CheckoutCallBack.checkout() からのルール実行

public String checkout(JFrame frame, List<Product> items) {
    Order order = new Order();

    // Iterate through list and add to cart.
    for ( Product p: items ) {
        order.addItem( new Purchase( order, p ) );
    }

    // Add the JFrame to the ApplicationData to allow for user interaction.

    // From the KIE container, a KIE session is created based on
    // its definition and configuration in the META-INF/kmodule.xml file.
    KieSession ksession = kcontainer.newKieSession("PetStoreKS");

    ksession.setGlobal( "frame", frame );
    ksession.setGlobal( "textArea", this.output );

    ksession.insert( new Product( "Gold Fish", 5 ) );
    ksession.insert( new Product( "Fish Tank", 25 ) );
    ksession.insert( new Product( "Fish Food", 2 ) );

    ksession.insert( new Product( "Fish Food Sample", 0 ) );

    ksession.insert( order );

    // Execute rules.
    ksession.fireAllRules();

    // Return the state of the cart
    return order.toString();
}

このコード例では、2 つの要素を CheckoutCallBack.checkout() メソッドに渡します。1 つ目の要素は、GUI の一番下にある出力テキストのフレームを囲む Swing コンポーネント JFrame のハンドルです。2 つ目の要素は注文アイテムのリストで、GUI の右上のセクションにある Table エリアからの情報を保存する TableModel から取得します。

for ループは GUI からの注文アイテム一覧を JavaBean Order に変換します。これは、PetStoreExample.java ファイルにも含まれています。

今回の例では、データはすべて Swing コンポーネントに含まれており、ユーザーが UI の チェックアウト をクリックしない限り実行しないため、ルールはステートレスの KIE セッションで実行します。ユーザーが チェックアウト をクリックするたびに、リストの内容を Swing TableModel から KIE セッションのワーキングメモリーに移動し、ksession.fireAllRules() メソッドで実行します。

このコード内には、KieSession への呼び出しが 9 個あります。1 つ目は、KieContainer から新しい KieSession を作成します (この例では、main() メソッドの CheckoutCallBack クラスから KieContainer に渡されます)。次の 2 つの呼び出しは、ルールでグローバル変数として保持されるオブジェクト を 2 つ渡します (メッセージの書き込みに使用する Swing テキストエリアと Swing フレーム)。より多くの商品の情報を KieSession と注文リストに入力します。最後の呼び出しは、標準の fireAllRules() です。

ペットショップのルールファイルのインポート、グローバル変数、Java 関数

PetStore.drl ファイルには、さまざまな Java クラスをルールで利用できるように、標準のパッケージとインポートステートメントが含まれています。このルールファイルには、frametextArea などのように、ルール内で使用する グローバル変数 が含まれています。グローバル変数では、Swing コンポーネント JFrame と、setGlobal() メソッドを呼び出した Java コードにより以前に渡された JTextArea コンポーネントへの参照を保持します。ルールが実行するとすぐに失効するルールの標準変数とは異なり、グローバル変数は KIE セッションの有効期間中この値を保持します。これは、このグローバル変数の内容が、後続のすべてのルールで評価できることを意味します。

PetStore.drl パッケージ、インポート、およびグローバル変数

package org.drools.examples;

import org.kie.api.runtime.KieRuntime;
import org.drools.examples.petstore.PetStoreExample.Order;
import org.drools.examples.petstore.PetStoreExample.Purchase;
import org.drools.examples.petstore.PetStoreExample.Product;
import java.util.ArrayList;
import javax.swing.JOptionPane;

import javax.swing.JFrame;

global JFrame frame
global javax.swing.JTextArea textArea

PetStore.drl ファイルには、このファイル内のルールが使用する関数が 2 つも含まれています。

PetStore.drl Java 関数

function void doCheckout(JFrame frame, KieRuntime krt) {
        Object[] options = {"Yes",
                            "No"};

        int n = JOptionPane.showOptionDialog(frame,
                                             "Would you like to checkout?",
                                             "",
                                             JOptionPane.YES_NO_OPTION,
                                             JOptionPane.QUESTION_MESSAGE,
                                             null,
                                             options,
                                             options[0]);

       if (n == 0) {
            krt.getAgenda().getAgendaGroup( "checkout" ).setFocus();
       }
}

function boolean requireTank(JFrame frame, KieRuntime krt, Order order, Product fishTank, int total) {
        Object[] options = {"Yes",
                            "No"};

        int n = JOptionPane.showOptionDialog(frame,
                                             "Would you like to buy a tank for your " + total + " fish?",
                                             "Purchase Suggestion",
                                             JOptionPane.YES_NO_OPTION,
                                             JOptionPane.QUESTION_MESSAGE,
                                             null,
                                             options,
                                             options[0]);

       System.out.print( "SUGGESTION: Would you like to buy a tank for your "
                           + total + " fish? - " );

       if (n == 0) {
             Purchase purchase = new Purchase( order, fishTank );
             krt.insert( purchase );
             order.addItem( purchase );
             System.out.println( "Yes" );
       } else {
            System.out.println( "No" );
       }
       return true;
}

この 2 つの関数は以下のアクションを実行します。

  • doCheckout() は、チェックアウトするかどうかユーザーに尋ねるダイアログボックスを表示します。チェックアウトする場合は、フォーカスが checkout アジェンダグループに設定され、そのグループのルールを (今後) 実行できるようにします。
  • requireTank() は、水槽を購入するかどうかを確認するダイアログを表示します。購入する場合は、新しい水槽の Product がワーキングメモリーの注文リストに追加されます。
注記

この例では、効率化を図るため、すべてのルールと関数が同じルールファイルで実行しています。実稼働環境では、通常、ルールと関数を別のファイルに分けるか、静的な Java メソッドを構築して、import function my.package.name.hello などのインポート関数を使用し、ファイルをインポートします。

アジェンダグループを使用したペットショップルール

ペットショップの例のルールはほぼ、アジェンダグループを使用してルールの実行を制御しています。アジェンダグループを使用すると、エンジンアジェンダを分割し、ルールのグループの実行を、詳細にわたり制御できるようになります。デフォルトでは、ルールはすべてアジェンダグループ MAIN に含まれています。agenda-group 属性を使用して、ルールに異なるアジェンダグループを指定できます。

最初は、ワーキングメモリーは、アジェンダグループ MAIN にフォーカスを当てます。アジェンダグループのルールは、グループがこのフォーカスを受けた場合のみ実行されます。setFocus() メソッドか、auto-focus ルール属性を使用してフォーカスを設定できます。auto-focus 属性を使用すると、ルールが一致してアクティベートされた場合のみ、ルールにアジェンダグループのフォーカスが自動的に当てられます。

ペットショップの例では、ルールに以下のアジェンダグループを使用します。

  • "init"
  • "evaluate"
  • "show items"
  • "checkout"

たとえば、同じルール "Explode Cart""init" のアジェンダグループを使用して、ショッピングカードのアイテムを起動して KIE セッションのワーキングメモリーに挿入するオプションが提供されるようにします。

ルール "Explode Cart"

// Insert each item in the shopping cart into the working memory.
rule "Explode Cart"
    agenda-group "init"
    auto-focus true
    salience 10
    dialect "java"
  when
    $order : Order( grossTotal == -1 )
    $item : Purchase() from $order.items
  then
    insert( $item );
    kcontext.getKnowledgeRuntime().getAgenda().getAgendaGroup( "show items" ).setFocus();
    kcontext.getKnowledgeRuntime().getAgenda().getAgendaGroup( "evaluate" ).setFocus();
end

このルールは、grossTotal がまだ計算されていない全注文に対して照合が行われます。購入アイテムごとに、順番に実行がループされます。

ルールは、アジェンダグループに関連する以下の機能を使用します。

  • agenda-group "init" はアジェンダグループの名前を定義します。この例では、グループにはルールが 1 つしかありません。ただし、Java コードもルール結果もこのグループにフォーカスされていないため、auto-focus の属性により、ルールが実行されるかが決まります。
  • このルールはアジェンダグループで唯一のルールですが、auto-focus true を使用して、fireAllRules() が Java コードから呼び出されると、必ず実行されるようにします。
  • kcontext…​.setFocus() は、"show items" および "evaluate" アジェンダグループにフォーカスを設定し、ルールを実行できるようにします。実際には、すべての項目をその順序でループしてメモリーに挿入し、各挿入の後に他のルールを実行します。

"show items" アジェンダグループには "Show Items" というルールだけが含まれます。KIE セッションのワーキングメモリーに現在含まれる注文で購入があるたびに、このルールを使用して、ルールファイルに定義した textArea 変数をもとに、GUI の下の部分にあるテキストエリアに詳細がロギングされます。

ルール "Show Items"

rule "Show Items"
    agenda-group "show items"
    dialect "mvel"
  when
    $order : Order( )
    $p : Purchase( order == $order )
  then
   textArea.append( $p.product + "\n");
end

また、"evaluate" アジェンダグループにより、"Explode Cart" ルールからフォーカスを取得します。このアジェンダグループには、2 つのルール ("Free Fish Food Sample""Suggest Tank") が含まれます。この順番で実行されます。

ルール "Free Fish Food Sample"

// Free fish food sample when users buy a goldfish if they did not already buy
// fish food and do not already have a fish food sample.
rule "Free Fish Food Sample"
    agenda-group "evaluate" 1
    dialect "mvel"
  when
    $order : Order()
    not ( $p : Product( name == "Fish Food") && Purchase( product == $p ) ) 2
    not ( $p : Product( name == "Fish Food Sample") && Purchase( product == $p ) ) 3
    exists ( $p : Product( name == "Gold Fish") && Purchase( product == $p ) ) 4
    $fishFoodSample : Product( name == "Fish Food Sample" );
  then
    System.out.println( "Adding free Fish Food Sample to cart" );
    purchase = new Purchase($order, $fishFoodSample);
    insert( purchase );
    $order.addItem( purchase );
end

ルール "Free Fish Food Sample" は、以下の条件がすべて該当する場合のみ実行されます。

1
アジェンダグループ "evaluate" がルール実行で評価されている
2
ユーザーが魚の餌をまだ持っていない
3
ユーザーが無料の魚の餌サンプルをまだ持っていない
4
ユーザーが金魚を注文している

この注文ファクトが上記の要件すべてを満たす場合は、新しい商品 (Fish Food Sample) が作成され、ワーキングメモリーの注文に追加されます。

ルール "Suggest Tank"

// Suggest a fish tank if users buy more than five goldfish and
// do not already have a tank.
rule "Suggest Tank"
    agenda-group "evaluate"
    dialect "java"
  when
    $order : Order()
    not ( $p : Product( name == "Fish Tank") && Purchase( product == $p ) ) 1
    ArrayList( $total : size > 5 ) from collect( Purchase( product.name == "Gold Fish" ) ) 2
    $fishTank : Product( name == "Fish Tank" )
  then
    requireTank(frame, kcontext.getKieRuntime(), $order, $fishTank, $total);
end

ルール "Suggest Tank" は以下の条件がすべて該当する場合のみ実行されます。

1
ユーザーが水槽を注文していない
2
ユーザーが 6 匹以上注文した

このルールが実行すると、ルールファイルに定義されている requireTank() 関数が呼び出されます。この関数により、水槽を購入するかどうかを尋ねるダイアログが表示されます。購入する場合は、新しい水槽の Product がワーキングメモリーの注文リストに追加されます。ルールが requireTank() 関数を呼び出した場合は、このルールを使用して、関数に Swing GUI のハンドルが含まれるように、frame のグローバル変数を渡します。

ペットショップの例の "do checkout" ルールにはアジェンダグループや when 条件がないため、ルールは常に実行され、デフォルトの MAIN のアジェンダグループの一部とみなされます。

ルール "do checkout"

rule "do checkout"
    dialect "java"
  when
  then
    doCheckout(frame, kcontext.getKieRuntime());
end

このルールが実行されると、ルールファイルで定義されている doCheckout() 関数を呼び出します。この関数により、チェックアウトするかどうかをユーザーに尋ねるダイアログボックスが表示されます。チェックアウトする場合は、フォーカスが checkout アジェンダグループに設定され、そのグループのルールを (今後) 実行できるようにします。このルールで doCheckout() 関数を呼び出し、この変数に Swing GUI のハンドルが含まれるように frame グローバル変数を渡します。

注記

この例では、結果が想定どおりに実行されない場合のトラブルシューティングの方法を例示します。ルールの when ステートメントから条件を削除して、then ステートメントのアクションをテストし、アクションが正しく実行されることを検証します。

"checkout" アジェンダグループには、注文のチェックアウト処理、および割引の適用の 3 つのルール ("Gross Total""Apply 5% Discount"、および "Apply 10% Discount") が含まれています。

ルール "Gross Total"、"Apply 5% Discount"、および "Apply 10% Discount"

rule "Gross Total"
    agenda-group "checkout"
    dialect "mvel"
  when
    $order : Order( grossTotal == -1)
    Number( total : doubleValue ) from accumulate( Purchase( $price : product.price ),
                                                              sum( $price ) )
  then
    modify( $order ) { grossTotal = total }
    textArea.append( "\ngross total=" + total + "\n" );
end

rule "Apply 5% Discount"
    agenda-group "checkout"
    dialect "mvel"
  when
    $order : Order( grossTotal >= 10 && < 20 )
  then
    $order.discountedTotal = $order.grossTotal * 0.95;
    textArea.append( "discountedTotal total=" + $order.discountedTotal + "\n" );
end

rule "Apply 10% Discount"
    agenda-group "checkout"
    dialect "mvel"
  when
    $order : Order( grossTotal >= 20 )
  then
    $order.discountedTotal = $order.grossTotal * 0.90;
    textArea.append( "discountedTotal total=" + $order.discountedTotal + "\n" );
end

ユーザーがまだ総計を算出していない場合には、Gross Total で、商品の価格を累積して合計を出し、この合計を KIE セッションに渡して、textArea のグローバル変数を使用し、Swing JTextArea で合計を表示します。

総計が 10 から 20 (通貨単位) の場合は、"Apply 5% Discount" ルールで割引合計を計算し、KIE セッションに追加して、テキストエリアに表示します。

総計が 20 未満の場合は、"Apply 10% Discount" ルールで割引合計を計算し、KIE セッションに追加して、テキストエリアに表示します。

ペットショップ例の実行

他の Red Hat Decision Manager のデシジョン例と同じように、お使いの IDE で org.drools.examples.petstore.PetStoreExample クラスを Java アプリケーションとして実行し、ペットショップの例を実行します。

ペットショップの例を実行すると、Pet Store Demo GUI ウィンドウが表示されます。このウィンドウでは、購入可能な商品 (左上)、選択済み商品の空白のリスト (右上)、チェックアウト および リセット ボタン (真ん中)、空白のシステムメッセージエリア (下) が表示されます。

図7.14 起動後のペットショップ例の GUI

1 PetStore Start Screen

この例では、以下のイベントが発生して、この実行動作を確立します。

  1. main() メソッドがルールベースの実行と読み込みを終えていますが、ルールは実行していない。今のところ、これが、実行されたルールに関連する唯一のコードになります。
  2. 新しい PetStoreUI オブジェクトが作成され、後で使用できるようにルールベースにハンドルを渡している。
  3. さまざまな Swing コンポーネントが関数を実行し、最初の UI 画面が表示され、ユーザーの入力を待っている。

リストからさまざまな商品をクリックして、UI 設定をチェックできます:

図7.15 ペットショップ例の GUI のチェック

2 stock added to order list

ルールコードはまだ実行されていません。UI は Swing コードを使用してユーザーによるマウスクリックを検出し、選択済みの商品を TableModel オブジェクトに追加して、UI の右上隅に表示します。この例では、Model-View-Controller 設計パターンを紹介しています。

チェックアウト をクリックすると、ルールが以下の方法で実行されます。

  1. Swing クラスは Checkout がクリックされるまで待機して、(最終的に) CheckOutCallBack.checkout() メソッドを呼び出します。これにより、TableModel オブジェクト (UI の右上隅) から KIE セッションのワーキングメモリーにデータを挿入します。その後に、メソッドによりルールが実行されます。
  2. "Explode Cart" ルールは、auto-focus 属性を true に設定して最初に実行します。このルールは、カートの商品をすべて順にループしていき、商品がワーキングメモリーに含まれていることを確認し、アジェンダグループ "show Items""evaluate" に実行するオプションを提供します。このグループのルールは、カートのコンテンツをテキストエリア (UI の下) に追加して、魚の餌を無料で受け取る資格があるかどうかを評価し、また水槽購入の有無を尋ねるかどうかを決定します。

    図7.16 水槽の資格

    3 purchase suggestion
  3. 現在、他のアジェンダグループにフォーカスが当たっておらず、"do checkout" ルールは、デフォルトの MAIN アジェンダグループに含まれているため、次に実行されます。このルールは常に doCheckout() 関数を呼び出し、この関数によりチェックアウトをするかどうかが尋ねられます。
  4. doCheckout() 関数は、フォーカスを "checkout" アジェンダグループに設定し、そのグループ内のルールに、実行するオプションを提供します。
  5. "checkout" アジェンダグループ内のルールは、カート内の内容を表示し、適切な割引を適用します。
  6. Swing は、別の商品の選択 (およびもう一度ルールを実行) または GUI の終了のいずれかのユーザー入力を待ちます。

    図7.17 全ルールが実行された後のペットショップ例の GUI

    4 Petstore final screen

IDE コンソールでイベントのこのフローを例示するには、他の System.out 呼び出しを追加します。

IDE コンソールの System.out 出力

Adding free Fish Food Sample to cart
SUGGESTION: Would you like to buy a tank for your 6 fish? - Yes

7.7. 誠実な政治家の例のデシジョン (真理維持および顕著性)

誠実な政治家のデシジョンセットの例では、論理挿入を使用した真理維持の概念およびルールでの顕著性の使用方法を説明します。

以下は、誠実な政治家の例の概要です。

  • 名前: honestpolitician
  • Main クラス: (src/main/java 内の) org.drools.examples.honestpolitician.HonestPoliticianExample
  • モジュール: drools-examples
  • タイプ: Java アプリケーション
  • ルールファイル: (src/main/resources 内の) org.drools.examples.honestpolitician.HonestPolitician.drl
  • 目的: ファクトの論理挿入をもとにした真理維持の概念およびルールでの顕著性の使用方法を紹介します。

誠実な政治家例の前提として基本的に、ステートメントが True の場合にのみ、オブジェクトが存在できます。insertLogical() メソッドを使用して、ルールの結果により、オブジェクトを論理的に挿入します。つまり、論理的に挿入されたルールが True の状態であれば、オブジェクトは KIE セッションのワーキングメモリー内に留まります。ルールが True でなくなると、オブジェクトは自動的に取り消されます。

この例では、ルールを実行することで、企業による政治家の買収が原因で、政治家グループが誠実から不誠実に変わります。各政治家が評価されるにつれ、最初は honesty 属性を true に設定して開始しますが、ルールが実行すると政治家は誠実ではなくなります。状態が誠実から不誠実に切り替わると、ワーキングメモリーから削除されます。ルールの顕著性により、顕著性が定義されているルールをどのように優先付けするかを、エンジンに通知します。通知しないと、デフォルトの顕著性である 0 が使用されます。顕著性の値が高いルールは、アクティベーションキューの順番で、優先度が高くなります。

Politician クラスおよび Hope クラス

この例の Politician クラス例は、誠実な政治家として設定されています。Politician クラスは、文字列アイテム name とブール値アイテム honest で設定されています。

Politician クラス

public class Politician {
    private String name;
    private boolean honest;
    ...
}

Hope クラスは、Hope オブジェクトが存在するかどうかを判断します。このクラスには意味を持つメンバーは存在しませんが、社会に希望がある限り、ワーキングメモリーに存在します。

Hope クラス

public class Hope {

    public Hope() {

    }
  }

政治家の誠実性に関するルール定義

誠実な政治家の例では、ワーキングメモリーに最低でも 1 名誠実な政治家が存在する場合は、"We have an honest Politician" ルールで論理的に新しい Hope オブジェクトを挿入します。すべての政治家が不誠実になると、Hope オブジェクトは自動的に取り除かれます。このルールでは、salience 属性の値が 10 となっており、他のルールより先に実行されます。理由は、この時点では "Hope is Dead" ルールが True となっているためです。

ルール "We have an honest politician"

rule "We have an honest Politician"
    salience 10
  when
    exists( Politician( honest == true ) )
  then
    insertLogical( new Hope() );
end

Hope オブジェクトが存在すると、すぐに "Hope Lives" ルールが一致して実行されます。"Corrupt the Honest" ルールよりも優先されるように、このルールにも salience 値を 10 に指定しています。

ルール "Hope Lives"

rule "Hope Lives"
    salience 10
  when
    exists( Hope() )
  then
    System.out.println("Hurrah!!! Democracy Lives");
end

最初は、誠実な政治家が 4 人いるため、このルールには 4 つのアクティベーションが存在し、すべてが競合しています。各ルールが順番に実行し、政治家が誠実でなくなるように、企業により各政治家を買収させていきます。政治家 4 人が全員買収されたら、プロパティーが honest == true の政治家はいなくなります。"We have an honest Politician" のルールは True でなくなり、論理的に挿入されるオブジェクト (最後に実行された new Hope() による) は自動的に取り除かれます。

ルール "Corrupt the Honest"

rule "Corrupt the Honest"
  when
    politician : Politician( honest == true )
    exists( Hope() )
  then
    System.out.println( "I'm an evil corporation and I have corrupted " + politician.getName() );
    modify ( politician ) { honest = false };
end

真理維持システムにより Hope オブジェクトが自動的に取り除かれると、Hope に適用された条件付き要素 not は True でなくなり、"Hope is Dead" ルールが一致して実行されます。

ルール "Hope is Dead"

rule "Hope is Dead"
  when
    not( Hope() )
  then
    System.out.println( "We are all Doomed!!! Democracy is Dead" );
end

実行と監査証跡

HonestPoliticianExample.java クラスでは、honest の状態が true に設定されている政治家 4 人が挿入され、定義したビジネスルールに対して評価されます。

HonestPoliticianExample.java クラスの実行

public static void execute( KieContainer kc ) {
        KieSession ksession = kc.newKieSession("HonestPoliticianKS");

        final Politician p1 = new Politician( "President of Umpa Lumpa", true );
        final Politician p2 = new Politician( "Prime Minster of Cheeseland", true );
        final Politician p3 = new Politician( "Tsar of Pringapopaloo", true );
        final Politician p4 = new Politician( "Omnipotence Om", true );

        ksession.insert( p1 );
        ksession.insert( p2 );
        ksession.insert( p3 );
        ksession.insert( p4 );

        ksession.fireAllRules();

        ksession.dispose();
    }

この例を実行するには、IDE で Java アプリケーションとして org.drools.examples.honestpolitician.HonestPoliticianExample クラスを実行します。

実行後に、以下の出力が IDE コンソールウィンドウに表示されます。

IDE コンソールでの実行出力

Hurrah!!! Democracy Lives
I'm an evil corporation and I have corrupted President of Umpa Lumpa
I'm an evil corporation and I have corrupted Prime Minster of Cheeseland
I'm an evil corporation and I have corrupted Tsar of Pringapopaloo
I'm an evil corporation and I have corrupted Omnipotence Om
We are all Doomed!!! Democracy is Dead

この出力では、democracy lives に誠実な政治家が最低でも 1 人いることが分かります。ただし、各政治家は企業に買収されているため、全政治家が不誠実になり、民主性がなくなります。

この例の実行フローをさらに理解するには、HonestPoliticianExample.java クラスを変更して、RuleRuntime リスナーと監査ロガーを追加して、実行の詳細を表示できます。

監査ロガーを含む HonestPoliticianExample.java クラス

package org.drools.examples.honestpolitician;

import org.kie.api.KieServices;
import org.kie.api.event.rule.DebugAgendaEventListener; 1
import org.kie.api.event.rule.DebugRuleRuntimeEventListener;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;

public class HonestPoliticianExample {

    /**
     * @param args
     */
    public static void main(final String[] args) {
    	KieServices ks = KieServices.Factory.get(); 2
    	//ks = KieServices.Factory.get();
        KieContainer kc = KieServices.Factory.get().getKieClasspathContainer();
        System.out.println(kc.verify().getMessages().toString());
        //execute( kc );
        execute( ks, kc); 3
    }

    public static void execute( KieServices ks, KieContainer kc ) { 4
        KieSession ksession = kc.newKieSession("HonestPoliticianKS");

        final Politician p1 = new Politician( "President of Umpa Lumpa", true );
        final Politician p2 = new Politician( "Prime Minster of Cheeseland", true );
        final Politician p3 = new Politician( "Tsar of Pringapopaloo", true );
        final Politician p4 = new Politician( "Omnipotence Om", true );

        ksession.insert( p1 );
        ksession.insert( p2 );
        ksession.insert( p3 );
        ksession.insert( p4 );

        // The application can also setup listeners 5
        ksession.addEventListener( new DebugAgendaEventListener() );
        ksession.addEventListener( new DebugRuleRuntimeEventListener() );

        // Set up a file-based audit logger.
        ks.getLoggers().newFileLogger( ksession, "./target/honestpolitician" ); 6

        ksession.fireAllRules();

        ksession.dispose();
    }

}

1
DebugAgendaEventListenerDebugRuleRuntimeEventListener を処理するインポートにパッケージを追加します。
2
この監査ログは KieContainer レベルでは利用できないため、KieServices Factory 要素および ks 要素を作成してログを生成します。
3
execute メソッドを変更して KieServicesKieContainer の両方を使用します。
4
execute メソッドを変更して KieContainer に加えて KieServices で渡します。
5
リスナーを作成します。
6
ルールの実行後にデバッグビュー、監査ビュー、または IDE に渡すことが可能なログを構築します。

ログ機能を変更して誠実な政治家のサンプルを実行すると、target/honestpolitician.log から IDE デバッグビュー、または利用可能な場合には 監査ビュー (IDE の一部では WindowShow View) に、監査ログファイルを読み込むことができます。

この例では、監査ビュー では、クラスやルールのサンプルで定義されているように、実行、挿入、取り消しのフローが示されています。

図7.18 誠実な政治家の例での監査ビュー

honest politician audit

最初の政治家が挿入されると、2 つのアクティベーションが発生します。"We have an honest Politician" のルールは、exists の条件付き要素を使用するため、最初に挿入された政治家に対してのみ一度だけアクティベートされます。この条件付き要素は、政治家が最低でも 1 人挿入されると一致します。Hope オブジェクトがまだ挿入されていないため、ルール "Hope is Dead" もこの時点でアクティベートになります。"We have an honest Politician" ルールは、"Hope is Dead" ルールより、salience の値が高いため先に実行され、Hope オブジェクト (緑にハイライト) を挿入します。Hope オブジェクトを挿入すると、ルール "Hope Lives" が有効になり、ルール "Hope is Dead" が無効になります。この挿入により、挿入された誠実な各政治家に対して "Corrupt the Honest" ルールがアクティベートになります。"Hope Lives" のルールが実行して、"Hurrah!!!Democracy Lives" が出力されます。

次に、政治家ごとに "Corrupt the Honest" ルールを実行して "I'm an evil corporation and I have corrupted X" と出力します。X は政治家の名前で、その政治家の誠実値が false に変更になります。最後の誠実な政治家が買収されると、真理維持システム (青でハイライト) により Hope が自動的に取り消されます。緑でハイライトされたエリアは、現在選択されている青のハイライトエリアの出元です。Hope ファクトが取り消されると、"Hope is dead" ルールが実行して "We are all Doomed!!!Democracy is Dead" が出力されます。

7.8. 数独例のデシジョン (複雑なパターン一致、コールバック、および GUI 統合)

数独例のデシジョンセットは、人気の数字パズルゲーム数独をもとにしています。このセットでは、Red Hat Decision Manager のルールを使用してさまざまな制約をもとに、多数の考えられる回答スペースの中で回答を導き出す方法を例示します。またこの例では、Red Hat Decision Manager ルールとグラフィカルユーザーインターフェイス (GUI) の統合方法が分かります。今回は Swing ベースのデスクトップアプリケーションを使用します。また、この例では、コールバックを使用して実行中のデシジョンエンジンと通信し、ランタイム時に加えられたワーキングメモリー内の変更をもとに GUI を更新する方法を例示しています。

以下は数独の例の概要です。

  • 名前: sudoku
  • Main クラス: (src/main/java 内の) org.drools.examples.sudoku.SudokuExample
  • モジュール: drools-examples
  • タイプ: Java アプリケーション
  • ルールファイル: (src/main/resources 内の) org.drools.examples.sudoku.*.drl
  • 目的: 複雑なパターン一致、問題解決、コールバック、および GUI 統合を例示します。

数独は、ロジックベースの数字配置パズルです。目的は、各列、各行、および各 3x3 ゾーンに 1 から 9 の数字が一度だけ含まれるように 9x9 のグリッドを埋めることです。パズルセッターでは、グリッド内の一部だけ記入されており、上記の制約ですべての空白を埋めるのがパズルの回答者のタスクです。

問題解決の一般的なストラテジーとして、新しい番号の挿入時に、特定の 3x3 ゾーン、行、および列で同じ番号がないことを確認します。この数独例のデシジョンセットでは、Red Hat Decision Manager ルールを使用して、さまざまな難易度の数独パズルを解き、無効なエントリーが含まれ、不備のあるパズルの解決を試みます。

数独例の実行および対話

他の Red Hat Decision Manager のデシジョン例と同じように、お使いの IDE で org.drools.examples.sudoku.SudokuExample クラスを Java アプリケーションとして実行し、数独の例を実行します。

数独の例を実行すると、GUI ウィンドウ Drools Sudoku Example が表示されます。このウィンドウには空のグリッドが含まれていますが、プログラムには内部に保存されたさまざまなグリッドが含まれ、読み込んで解決できます。

FileSamplesSimple をクリックして、例の 1 つを読み込みます。グリッドが読み込まれるまで、すべてのボタンが無効になっている点に注目してください。

図7.19 起動後の数独例の GUI

sudoku1

Simple サンプルを読み込むと、パズルの最初の状態に合わせて、グリッドが埋められます。

図7.20 Simple サンプルを読み込んだ後の数独例の GUI

sudoku2

以下のオプションから選択します。

  • Solve をクリックして、数独の例に定義されているルールを実行し、残りの値を埋めていき、このボタンを再度無効にします。

    図7.21 Simple サンプルの解決

    sudoku3
  • Step をクリックして、ルールセットに含まれる次の数字を表示します。IDE のコンソールウィンドウでは、解決手順を実行するルールに関する情報が詳細に表示されます。

    IDE コンソールでの手順実行の出力

    single 8 at [0,1]
    column elimination due to [1,2]: remove 9 from [4,2]
    hidden single 9 at [1,2]
    row elimination due to [2,8]: remove 7 from [2,4]
    remove 6 from [3,8] due to naked pair at [3,2] and [3,7]
    hidden pair in row at [4,6] and [4,4]

  • Dump をクリックしてグリッドの状態を表示します。セルには、解決済みの値か、残りの候補値が表示されます。

    IDE コンソールでのダンプ実行の出力

            Col: 0     Col: 1     Col: 2     Col: 3     Col: 4     Col: 5     Col: 6     Col: 7     Col: 8
    Row 0:  123456789  --- 5 ---  --- 6 ---  --- 8 ---  123456789  --- 1 ---  --- 9 ---  --- 4 ---  123456789
    Row 1:  --- 9 ---  123456789  123456789  --- 6 ---  123456789  --- 5 ---  123456789  123456789  --- 3 ---
    Row 2:  --- 7 ---  123456789  123456789  --- 4 ---  --- 9 ---  --- 3 ---  123456789  123456789  --- 8 ---
    Row 3:  --- 8 ---  --- 9 ---  --- 7 ---  123456789  --- 4 ---  123456789  --- 6 ---  --- 3 ---  --- 5 ---
    Row 4:  123456789  123456789  --- 3 ---  --- 9 ---  123456789  --- 6 ---  --- 8 ---  123456789  123456789
    Row 5:  --- 4 ---  --- 6 ---  --- 5 ---  123456789  --- 8 ---  123456789  --- 2 ---  --- 9 ---  --- 1 ---
    Row 6:  --- 5 ---  123456789  123456789  --- 2 ---  --- 6 ---  --- 9 ---  123456789  123456789  --- 7 ---
    Row 7:  --- 6 ---  123456789  123456789  --- 5 ---  123456789  --- 4 ---  123456789  123456789  --- 9 ---
    Row 8:  123456789  --- 4 ---  --- 9 ---  --- 7 ---  123456789  --- 8 ---  --- 3 ---  --- 5 ---  123456789

数独の例には、不備のあるサンプルファイルが意図的に含められています。このファイルは、例で定義したルールを使用して解決できます。

FileSamples!DELIBERATELY BROKEN! をクリックして、不備のあるサンプルを読み込みます。グリッドは、最初の行に 5 の値を 2 回表示できないにもかかわらず表示されるなど、問題が含まれた状態で表示されます。

図7.22 不備のある数独例の最初の状態

sudoku4

Solve をクリックしてこの無効なグリッドに解決ルールを適用します。数独の例に含まれる関連の解決ルールにより、サンプルの問題が検出され、できる限りパズルを解決します。このプロセスでは、すべてを完了させず、空白のセルをいくつか残します。

解決ルールのアクティビティーが IDE コンソールウィンドウに表示されます。

不備のあるサンプルでの問題検出

cell [0,8]: 5 has a duplicate in row 0
cell [0,0]: 5 has a duplicate in row 0
cell [6,0]: 8 has a duplicate in col 0
cell [4,0]: 8 has a duplicate in col 0
Validation complete.

図7.23 不備のあるサンプルの解決試行

sudoku5

Hard のラベルの付いた数独サンプルファイルはより複雑で、解決ルールを使用しても解決できない可能性があります。解決をしようとして失敗した場合は、IDE コンソールウィンドウに表示されます。

解決不可の Hard サンプル

Validation complete.
...
Sorry - can't solve this grid.

不備のあるサンプルを解決するためのルールでは、セルの候補となりえる値をもとにした標準の解決手法を実装します。たとえば、セットに値が 1 つ含まれる場合は、これが値になります。セルが 9 個あるグループの 1 つに値が 1 度挿入された場合に、ルールを使用して、特定のセルに対する値を持ち、タイプが Setting のファクトを挿入します。このファクトにより、そのセルが含まれるグループにある他のすべてのセルからこの値が削除され、この値が取り消されます。

この例の他のルールで、セルに入力可能な値を減らしていきます。"naked pair""hidden pair in row""hidden pair in column"、および "hidden pair in square" のルールでは、候補の絞り込みはできますが、回答を得ることはできません。"X-wings in rows"、"`X-wings in columns"`、"intersection removal row"、および "intersection removal column" のルールは、より高度な絞り込みを実行します。

数独例のクラス

org.drools.examples.sudoku.swing パッケージには、以下のように、数独パズルのフレームワークを実装する主なクラスセットが含まれます。

  • SudokuGridModel は、9x9 グリッドの Cell オブジェクトとして数独パズルを格納するために実装可能なインターフェイスを定義しています。
  • SudokuGridView クラスは Swing コンポーネントで、SudokuGridModel クラス実装の視覚化が可能です。
  • SudokuGridEvent クラスおよび SudokuGridListener クラスは、モデルとビューの間のステータスの変化をやり取りするために使用します。セルの値が解決または変更すると、イベントが実行します。
  • SudokuGridSamples クラスは、デモ目的に一部入力されている数独パズルを複数提供します。
注記

このパッケージには、Red Hat Decision Manager ライブラリーの依存関係は含まれません。

org.drools.examples.sudoku パッケージには、以下のように、基本的な Cell オブジェクトと各種アグリゲーションを実装する主なクラスセットが含まれます。

  • CellRowCellCol、および CellSqr のサブタイプを含む CellFile クラス。これはすべて、CellGroup クラスのサブタイプになります。
  • CellCellGroupSetOfNine のサブクラスで、Set<Integer> 型の free プロパティーを提供します。Cell クラスは、個別の候補セットを表します。CellGroup は、セルの全候補セットの統合 (割り当ての必要がある数値セット) です。

    数独の例には、81 個の Cell と 27 個の CellGroup オブジェクト、Cell プロパティーの cellRowcellCol、および cellSqr が提供するリンク、CellGroup プロパティー cells (Cell オブジェクトリスト) が提供するリストが含まれます。これらのコンポーネントを使用して、セルに値を割り当てたり、候補セットから値を取り除いたりできるように、特定の状態を検出するルールを記述できます。

  • Setting クラスを使用して、値の割り当てに伴うオペレーションをトリガーします。Setting ファクトは、整合性の取れない中間の状態に対して反応しないように、新しい状況を検出する全ルールに配置して使用します。
  • Stepping クラスは、優先順位が低いルールに使用して、"Step" が予期なく中断された場合に緊急停止を行います。この動作は、プログラムでパズルを解決できないということです。
  • Main クラス org.drools.examples.sudoku.SudokuExample は、全コンポーネントを統合する Java アプリケーションを実装します。

数独の検証ルール (validate.drl)

数独の例の validate.drl ファイルには、セルグループで数が重複している状況を検出する検証ルールが含まれます。このグループは、"validate" アジェンダグループに統合され、ユーザーがパズルを読み込むと、明示的にルールをアクティベートできます。

"duplicate in cell …​" の 3 つのルールの when 条件はすべて以下の方法で機能します。

  • このルールの最初の条件で、割り当てられた値でセルを特定します。
  • このルールの 2 番目の条件では、3 つのセルグループのどれかを所属先にプルします。
  • 最終条件は、ルールに従い、最初のセル、同じ行、列、または四角に入る値と同じセル (上記のセル以外) を検索します。

ルール "duplicate in cell …​"

rule "duplicate in cell row"
  when
    $c: Cell( $v: value != null )
    $cr: CellRow( cells contains $c )
    exists Cell( this != $c, value == $v, cellRow == $cr )
  then
    System.out.println( "cell " + $c.toString() + " has a duplicate in row " + $cr.getNumber() );
end

rule "duplicate in cell col"
  when
    $c: Cell( $v: value != null )
    $cc: CellCol( cells contains $c )
    exists Cell( this != $c, value == $v, cellCol == $cc )
  then
    System.out.println( "cell " + $c.toString() + " has a duplicate in col " + $cc.getNumber() );
end

rule "duplicate in cell sqr"
  when
    $c: Cell( $v: value != null )
    $cs: CellSqr( cells contains $c )
    exists Cell( this != $c, value == $v, cellSqr == $cs )
  then
    System.out.println( "cell " + $c.toString() + " has duplicate in its square of nine." );
end

ルール "terminate group" は最後に実行されます。このルールは、メッセージを出力して、シーケンスを停止します。

ルール "terminate group"

rule "terminate group"
    salience -100
  when
  then
    System.out.println( "Validation complete." );
    drools.halt();
end

数独の解決ルール (sudoku.drl)

数独の例の sudoku.drl ファイルには、3 種類のルールタイプが含まれます。1 つ目のグループは、セルへの数値の割り当てを処理して、2 つ目は実行可能な割り当てを検出して、3 つ目は候補セットからの値を削除します。

"set a value""eliminate a value from Cell"、および "retract setting" のルールは、Setting オブジェクトの有無により左右されます。最初のルールは、セルへの割り当てと、3 つのセルグループの free セットから値を削除する操作を処理します。また、ゼロの場合は、このグループでカウンターが 1 つ減り、fireUntilHalt() を呼び出した Java アプリケーションに制御を戻します。

"eliminate a value from Cell" ルールの目的は、新たに割り当てられたセルに関連する全セルの候補リストを絞り込むことです。最後に、すべての除外が完了したら、"retract setting" ルールにより、トリガーされている Setting ファクトを取り消します。

ルール "set a value"、"eliminate a value from a Cell"、および "retract setting"

// A Setting object is inserted to define the value of a Cell.
// Rule for updating the cell and all cell groups that contain it
rule "set a value"
  when
    // A Setting with row and column number, and a value
    $s: Setting( $rn: rowNo, $cn: colNo, $v: value )

    // A matching Cell, with no value set
    $c: Cell( rowNo == $rn, colNo == $cn, value == null,
              $cr: cellRow, $cc: cellCol, $cs: cellSqr )

    // Count down
    $ctr: Counter( $count: count )
  then
    // Modify the Cell by setting its value.
    modify( $c ){ setValue( $v ) }
    // System.out.println( "set cell " + $c.toString() );
    modify( $cr ){ blockValue( $v ) }
    modify( $cc ){ blockValue( $v ) }
    modify( $cs ){ blockValue( $v ) }
    modify( $ctr ){ setCount( $count - 1 ) }
end

// Rule for removing a value from all cells that are siblings
// in one of the three cell groups
rule "eliminate a value from Cell"
  when
    // A Setting with row and column number, and a value
    $s: Setting( $rn: rowNo, $cn: colNo, $v: value )

    // The matching Cell, with the value already set
    Cell( rowNo == $rn, colNo == $cn, value == $v, $exCells: exCells )

    // For all Cells that are associated with the updated cell
    $c: Cell( free contains $v ) from $exCells
  then
    // System.out.println( "clear " + $v + " from cell " + $c.posAsString()  );
    // Modify a related Cell by blocking the assigned value.
    modify( $c ){ blockValue( $v ) }
end

// Rule for eliminating the Setting fact
rule "retract setting"
  when
    // A Setting with row and column number, and a value
    $s: Setting( $rn: rowNo, $cn: colNo, $v: value )

    // The matching Cell, with the value already set
    $c: Cell( rowNo == $rn, colNo == $cn, value == $v )

    // This is the negation of the last pattern in the previous rule.
    // Now the Setting fact can be safely retracted.
    not( $x: Cell( free contains $v )
         and
         Cell( this == $c, exCells contains $x ) )
  then
    // System.out.println( "done setting cell " + $c.toString() );
    // Discard the Setter fact.
    delete( $s );
    // Sudoku.sudoku.consistencyCheck();
end

解決ルールを 2 つ使用して、セルに数字を割り当てることができる状況を検出します。"single" のルールは、Cell に、数字が 1 つだけの候補セットが含まれる場合に実行します。"hidden single" ルールは、候補が 1 つだけのセルが存在しない場合に実行しますが、セルに候補が含まれる場合は、セルが所属する 3 つのグループの 1 つに含まれるその他のすべてのセルに、この候補が存在しないということです。いずれのルールも Setting ファクトを作成して、挿入します。

ルール "single" および "hidden single"

// Detect a set of candidate values with cardinality 1 for some Cell.
// This is the value to be set.
rule "single"
  when
    // Currently no setting underway
    not Setting()

    // One element in the "free" set
    $c: Cell( $rn: rowNo, $cn: colNo, freeCount == 1 )
  then
    Integer i = $c.getFreeValue();
    if (explain) System.out.println( "single " + i + " at " + $c.posAsString() );
    // Insert another Setter fact.
    insert( new Setting( $rn, $cn, i ) );
end

// Detect a set of candidate values with a value that is the only one
// in one of its groups. This is the value to be set.
rule "hidden single"
  when
    // Currently no setting underway
    not Setting()
    not Cell( freeCount == 1 )

    // Some integer
    $i: Integer()

    // The "free" set contains this number
    $c: Cell( $rn: rowNo, $cn: colNo, freeCount > 1, free contains $i )

    // A cell group contains this cell $c.
    $cg: CellGroup( cells contains $c )
    // No other cell from that group contains $i.
    not ( Cell( this != $c, free contains $i ) from $cg.getCells() )
  then
    if (explain) System.out.println( "hidden single " + $i + " at " + $c.posAsString() );
    // Insert another Setter fact.
    insert( new Setting( $rn, $cn, $i ) );
end

最大グループからのルール (個別または 2 ~ 3 のグループ単位) は、数独パズルを手作業で解決するのに使用する、さまざまな解決手法を実装します。

"naked pair" ルールは、グループの 2 つのセルで、全く同じ候補セットでサイズ 2 のものを検出します。これらの 2 つの値は、対象グループにあるその他のすべての候補セットから削除できます。

ルール "naked pair"

// A "naked pair" is two cells in some cell group with their sets of
// permissible values being equal with cardinality 2. These two values
// can be removed from all other candidate lists in the group.
rule "naked pair"
  when
    // Currently no setting underway
    not Setting()
    not Cell( freeCount == 1 )

    // One cell with two candidates
    $c1: Cell( freeCount == 2, $f1: free, $r1: cellRow, $rn1: rowNo, $cn1: colNo, $b1: cellSqr )

    // The containing cell group
    $cg: CellGroup( freeCount > 2, cells contains $c1 )

    // Another cell with two candidates, not the one we already have
    $c2: Cell( this != $c1, free == $f1 /*** , rowNo >= $rn1, colNo >= $cn1 ***/ ) from $cg.cells

    // Get one of the "naked pair".
    Integer( $v: intValue ) from $c1.getFree()

    // Get some other cell with a candidate equal to one from the pair.
    $c3: Cell( this != $c1 && != $c2, freeCount > 1, free contains $v ) from $cg.cells
  then
    if (explain) System.out.println( "remove " + $v + " from " + $c3.posAsString() + " due to naked pair at " + $c1.posAsString() + " and " + $c2.posAsString() );
    // Remove the value.
    modify( $c3 ){ blockValue( $v ) }
end

3 つのルールの "hidden pair in …​" 関数は、ルール "naked pair" と同じように機能します。ルールはグループの 2 つのセルで 2 つの数字を検出します。どの値もこのグループの他のセルには入りません。つまり、他の候補はすべて、隠れたペアを持つ 2 つのセルから削除します。

ルール "hidden pair in …​"

// If two cells within the same cell group contain candidate sets with more than
// two values, with two values being in both of them but in none of the other
// cells, then we have a "hidden pair". We can remove all other candidates from
// these two cells.
rule "hidden pair in row"
  when
    // Currently no setting underway
    not Setting()
    not Cell( freeCount == 1 )

    // Establish a pair of Integer facts.
    $i1: Integer()
    $i2: Integer( this > $i1 )

    // Look for a Cell with these two among its candidates. (The upper bound on
    // the number of candidates avoids a lot of useless work during startup.)
    $c1: Cell( $rn1: rowNo, $cn1: colNo, freeCount > 2 && < 9, free contains $i1 && contains $i2, $cellRow: cellRow )

    // Get another one from the same row, with the same pair among its candidates.
    $c2: Cell( this != $c1, cellRow == $cellRow, freeCount > 2, free contains $i1 && contains $i2 )

    // Ascertain that no other cell in the group has one of these two values.
    not( Cell( this != $c1 && != $c2, free contains $i1 || contains $i2 ) from $cellRow.getCells() )
  then
    if( explain) System.out.println( "hidden pair in row at " + $c1.posAsString() + " and " + $c2.posAsString() );
    // Set the candidate lists of these two Cells to the "hidden pair".
    modify( $c1 ){ blockExcept( $i1, $i2 ) }
    modify( $c2 ){ blockExcept( $i1, $i2 ) }
end

rule "hidden pair in column"
  when
    not Setting()
    not Cell( freeCount == 1 )

    $i1: Integer()
    $i2: Integer( this > $i1 )
    $c1: Cell( $rn1: rowNo, $cn1: colNo, freeCount > 2 && < 9, free contains $i1 && contains $i2, $cellCol: cellCol )
    $c2: Cell( this != $c1, cellCol == $cellCol, freeCount > 2, free contains $i1 && contains $i2 )
    not( Cell( this != $c1 && != $c2, free contains $i1 || contains $i2 ) from $cellCol.getCells() )
  then
    if (explain) System.out.println( "hidden pair in column at " + $c1.posAsString() + " and " + $c2.posAsString() );
    modify( $c1 ){ blockExcept( $i1, $i2 ) }
    modify( $c2 ){ blockExcept( $i1, $i2 ) }
end

rule "hidden pair in square"
  when
    not Setting()
    not Cell( freeCount == 1 )

    $i1: Integer()
    $i2: Integer( this > $i1 )
    $c1: Cell( $rn1: rowNo, $cn1: colNo, freeCount > 2 && < 9, free contains $i1 && contains $i2,
               $cellSqr: cellSqr )
    $c2: Cell( this != $c1, cellSqr == $cellSqr, freeCount > 2, free contains $i1 && contains $i2 )
    not( Cell( this != $c1 && != $c2, free contains $i1 || contains $i2 ) from $cellSqr.getCells() )
  then
    if (explain) System.out.println( "hidden pair in square " + $c1.posAsString() + " and " + $c2.posAsString() );
    modify( $c1 ){ blockExcept( $i1, $i2 ) }
    modify( $c2 ){ blockExcept( $i1, $i2 ) }
end

2 つのルールは行と列で "X-wings" を処理します。2 つの異なる行 (または列) で、ある値を入力できるセルが 2 つしかなく、これらの候補が同じ列 (または行) に入る場合に、この列 (または行) のこの値に対する他の候補は除外できます。これらのルールの 1 つに含まれるパターンシーケンスに従うと、sameonly などの用語で都合よく表現されている条件は、適切な制約が付けられたパターンになるか、not のプリフィックスが付きます。

ルール "X-wings in …​"

rule "X-wings in rows"
  when
    not Setting()
    not Cell( freeCount == 1 )

    $i: Integer()
    $ca1: Cell( freeCount > 1, free contains $i,
                $ra: cellRow, $rano: rowNo,         $c1: cellCol,        $c1no: colNo )
    $cb1: Cell( freeCount > 1, free contains $i,
                $rb: cellRow, $rbno: rowNo > $rano,      cellCol == $c1 )
    not( Cell( this != $ca1 && != $cb1, free contains $i ) from $c1.getCells() )

    $ca2: Cell( freeCount > 1, free contains $i,
                cellRow == $ra, $c2: cellCol,       $c2no: colNo > $c1no )
    $cb2: Cell( freeCount > 1, free contains $i,
                cellRow == $rb,      cellCol == $c2 )
    not( Cell( this != $ca2 && != $cb2, free contains $i ) from $c2.getCells() )

    $cx: Cell( rowNo == $rano || == $rbno, colNo != $c1no && != $c2no,
               freeCount > 1, free contains $i )
  then
    if (explain) {
        System.out.println( "X-wing with " + $i + " in rows " +
            $ca1.posAsString() + " - " + $cb1.posAsString() +
            $ca2.posAsString() + " - " + $cb2.posAsString() + ", remove from " + $cx.posAsString() );
    }
    modify( $cx ){ blockValue( $i ) }
end

rule "X-wings in columns"
  when
    not Setting()
    not Cell( freeCount == 1 )

    $i: Integer()
    $ca1: Cell( freeCount > 1, free contains $i,
                $c1: cellCol, $c1no: colNo,         $ra: cellRow,        $rano: rowNo )
    $ca2: Cell( freeCount > 1, free contains $i,
                $c2: cellCol, $c2no: colNo > $c1no,      cellRow == $ra )
    not( Cell( this != $ca1 && != $ca2, free contains $i ) from $ra.getCells() )

    $cb1: Cell( freeCount > 1, free contains $i,
                cellCol == $c1, $rb: cellRow,  $rbno: rowNo > $rano )
    $cb2: Cell( freeCount > 1, free contains $i,
                cellCol == $c2,      cellRow == $rb )
    not( Cell( this != $cb1 && != $cb2, free contains $i ) from $rb.getCells() )

    $cx: Cell( colNo == $c1no || == $c2no, rowNo != $rano && != $rbno,
               freeCount > 1, free contains $i )
  then
    if (explain) {
        System.out.println( "X-wing with " + $i + " in columns " +
            $ca1.posAsString() + " - " + $ca2.posAsString() +
            $cb1.posAsString() + " - " + $cb2.posAsString() + ", remove from " + $cx.posAsString()  );
    }
    modify( $cx ){ blockValue( $i ) }
end

この 2 つのルール intersection removal …​​は、1 つの行または 1 つの列のいずれかで、1 つの四角の中で一部の数字が制限されたことに基づきます。これは、この番号が行または列の 2 つまたは 3 つのセルのいずれかにある必要があり、グループの他のすべてのセルの候補セットから削除できることを意味します。このパターンは、発生制限を確立して、同じセルファイルの中、かつ四角の外のセルそれぞれに対して実行されます。

ルール "intersection removal …​"

rule "intersection removal column"
  when
    not Setting()
    not Cell( freeCount == 1 )

    $i: Integer()
    // Occurs in a Cell
    $c: Cell( free contains $i, $cs: cellSqr, $cc: cellCol )
    // Does not occur in another cell of the same square and a different column
    not Cell( this != $c, free contains $i, cellSqr == $cs, cellCol != $cc )

    // A cell exists in the same column and another square containing this value.
    $cx: Cell( freeCount > 1, free contains $i, cellCol == $cc, cellSqr != $cs )
  then
    // Remove the value from that other cell.
    if (explain) {
        System.out.println( "column elimination due to " + $c.posAsString() +
                            ": remove " + $i + " from " + $cx.posAsString() );
    }
    modify( $cx ){ blockValue( $i ) }
end

rule "intersection removal row"
  when
    not Setting()
    not Cell( freeCount == 1 )

    $i: Integer()
    // Occurs in a Cell
    $c: Cell( free contains $i, $cs: cellSqr, $cr: cellRow )
    // Does not occur in another cell of the same square and a different row.
    not Cell( this != $c, free contains $i, cellSqr == $cs, cellRow != $cr )

    // A cell exists in the same row and another square containing this value.
    $cx: Cell( freeCount > 1, free contains $i, cellRow == $cr, cellSqr != $cs )
  then
    // Remove the value from that other cell.
    if (explain) {
        System.out.println( "row elimination due to " + $c.posAsString() +
                            ": remove " + $i + " from " + $cx.posAsString() );
    }
    modify( $cx ){ blockValue( $i ) }
end

これらのルールは、すべてではありませんが、多くの数独パズルでは十分です。非常に難度の高いグリッドを解決するには、ルールセットにさらに複雑なルールが必要です。(最終的には、パズルは試行錯誤でしか解決できません)。

7.9. Conway の Game of Life 例のデシジョン (ルールフローグループおよび GUI 統合)

John Conway による有名なセルオートマトン (CA: Cellular automation) をベースにした Conway の Game of Life 例のデシジョンセットは、ルールでルールフローグループを使用してルール実行を制御する方法を例示します。またこの例は、Red Hat Decision Manager ルールをグラフィカルユーザーインターフェイス (GUI) と統合する方法も例示しています。今回は、Conway の Game of Life を Swing ベースで実装しています。

以下は、Conway の Game of Life の例の概要です。

  • 名前: conway
  • Main クラス: (src/main/java 内の) org.drools.examples.conway.ConwayRuleFlowGroupRunorg.drools.examples.conway.ConwayAgendaGroupRun
  • モジュール: droolsjbpm-integration-examples
  • タイプ: Java アプリケーション
  • ルールファイル: (src/main/resources 内の) org.drools.examples.conway.*.drl
  • 目的: ルールフローグループと GUI 統合を例示します。
注記

Conway の Game of Life の例は、Red Hat Decision Manager に含まれる他のデシジョンセットの例の多くとは異なり、Red Hat カスタマーポータル から取得する Red Hat Decision Manager 7.2.0 Source Distribution~/rhdm-7.2.0-sources/src/droolsjbpm-integration-$VERSION/droolsjbpm-integration-examples に保存されています。

Conway の Game of Life では、初期設定または定義済みのプロパティーで高度なパターンを作成して、初期状態からどのように進化していくかを観察することで、ユーザーはゲームと対話します。ゲームの目的は、世代ごとに人口の成長を表示します。各世代は、すべてのセル (細胞) が同時に進化していき、前の世代をもとにして生み出されます。

以下の基本的なルールで、次の世代がどのようになるかを制御していきます。

  • 生きているセルの近傍に、生きているセルが 2 個未満の場合は、孤独で死んでしまう。
  • 生きているセルの近傍に、生きているセルが 4 個以上ある場合は、過密で死んでしまう。
  • 死亡したセルの近傍に、生きているセルがちょうど 3 つある場合には、このセルは生き返る。

この基準のいずれも満たさないセルは、そのまま次の世代に残ります。

Conway の Game of Life の例は、ruleflow-group 属性が含まれる Red Hat Decision Manager ルールで、ゲームに実装されているパターンを定義します。この例には、アジェンダグループを使用して同じ動作を行うデシジョンセットのバージョンも含まれています。アジェンダグループを使用すると、エンジンアジェンダを分割し、ルールのグループの実行を制御できるようになります。デフォルトでは、ルールはすべてアジェンダグループ MAIN に含まれています。agenda-group 属性を使用して、ルールに異なるアジェンダグループを指定できます。

この概要では、Conway の例でアジェンダグループを使用したバージョンは触れません。アジェンダグループの詳細情報は、特にアジェンダグループについて対応している Red Hat Decision Manager 例のデシジョンセットを参照してください。

Conway 例の実行および対話

他の Red Hat Decision Manager のデシジョン例と同じように、お使いの IDE で org.drools.examples.conway.ConwayRuleFlowGroupRun クラスを Java アプリケーションとして実行し、Conway の例を実行します。

Conway の例を実行すると、Conway's Game of Life GUI ウィンドウが表示されます。このウィンドウには、空のグリッドまたはアリーナが含まれており、ここで生命のシミュレーションが行われます。システムにまだ生きているセルが含まれていないため、グリッドは最初は空白です。

図7.24 起動後の Conway 例の GUI

conway1

パターン のドロップダウンメニューから事前定義済みのパターンを選択して、次の世代 をクリックし、各人口の世代をクリックしていきます。セルは生きているか、死んでいるかのどちらかで、生きているセルには緑のボールが含まれます。最初のパターンから人口が進化するにつれ、ゲームのルールをもとに、セルが近傍のセルに合わせて、生存するか、死亡していきます。

図7.25 Conway の例の世代進化

conway2

近傍には、上下左右のセルだけでなく対角線上につながっているセルも含まれるため、各セルには合計 8 つの近傍があります。例外は、角のセルと 4 辺上にあるセルで、それぞれ順に近傍が 3 つだけと、5 つだけになります。

セルをクリックすることで手動で介入して、セルを作成することも、死亡させることもできます。

最初のパターンから自動的に進化を実行するには、スタート をクリックします。

ルールグループを使用する Conway 例のルール

ConwayRuleFlowGroupRun の例のルールは、ルールフローグループを使用して、ルール実行を制御します。ルールフローグループは、ruleflow-group ルール属性に関連付けられたルールのグループです。これらのルールは、このグループがアクティベートされたときにしか実行されません。グループ自体は、ルールフローの図の詳細がグループを表すノードに到達してからでないと、アクティブになりません。

Conway の例では、ルールに以下のルールフローグループを使用します。

  • "register neighbor"
  • "evaluate"
  • "calculate"
  • "reset calculate"
  • "birth"
  • "kill"
  • "kill all"

Cell オブジェクトはすべて KIE セッションに挿入され、"register neighbor" ルールフローグループの "register …​" ルールがルールフロー処理により実行できるようになります。4 つのルールが含まれるこのグループは、セル同士の Neighbor の関係と、北東、北、北西、西の近傍との Neighbor の関係を作り出します。

この関係は双方向で、他の 4 方向を処理します。各辺上のセルは、特別な対応は必要ありません。これらのセルは、近傍のセルがなければペアは作成されません。

これらのルールに対して、すべてのアクティベーションが実行されるまで、全セルは、近傍の全セルと関係があります。

ルール "register …​"

rule "register north east"
    ruleflow-group "register neighbor"
  when
    $cell: Cell( $row : row, $col : col )
    $northEast : Cell( row  == ($row - 1), col == ( $col + 1 ) )
  then
    insert( new Neighbor( $cell, $northEast ) );
    insert( new Neighbor( $northEast, $cell ) );
end

rule "register north"
    ruleflow-group "register neighbor"
  when
    $cell: Cell( $row : row, $col : col )
    $north : Cell( row  == ($row - 1), col == $col )
  then
    insert( new Neighbor( $cell, $north ) );
    insert( new Neighbor( $north, $cell ) );
end

rule "register north west"
    ruleflow-group "register neighbor"
  when
    $cell: Cell( $row : row, $col : col )
    $northWest : Cell( row  == ($row - 1), col == ( $col - 1 ) )
  then
    insert( new Neighbor( $cell, $northWest ) );
    insert( new Neighbor( $northWest, $cell ) );
end

rule "register west"
    ruleflow-group "register neighbor"
  when
    $cell: Cell( $row : row, $col : col )
    $west : Cell( row  == $row, col == ( $col - 1 ) )
  then
    insert( new Neighbor( $cell, $west ) );
    insert( new Neighbor( $west, $cell ) );
end

全セルが挿入されたら、Java コードはグリッドにパターンを適用し、特定のセルを Live に設定します。次に、ユーザーが スタート または 次の世代 をクリックすると、Generation のルールフローが実行されます。このルールフローは、世代のサイクルごとにセルの変更をすべて管理します。

図7.26 世代のルールフロー

conway ruleflow generation

ルールフロープロセスは、実行可能なグループに "evaluate" ルールフローグループおよびアクティブなルールを追加します。このグループの "Kill the …​""Give Birth" ルールを使用して、細胞の誕生または死亡セルにゲームのルールを適用します。この例では、phase 属性を使用して、特定のルールグループで Cell オブジェクトの推論をトリガーします。通常は、フェーズはルールフロープロセス定義に含まれるルールフローグループに紐づけされています。

この例では、変更の適用前に評価を完全に完了しておく必要があるため、この時点では Cell オブジェクトの状態は変更されません。細胞の phasePhase.KILL または Phase.BIRTH に適用し、後ほど Cell オブジェクトに適用されたアクションを制御するのに使用します。

ルール "Kill the …​" および "Give Birth"

rule "Kill The Lonely"
    ruleflow-group "evaluate"
    no-loop
  when
    // A live cell has fewer than 2 live neighbors.
    theCell: Cell( liveNeighbors < 2, cellState == CellState.LIVE,
                   phase == Phase.EVALUATE )
  then
    modify( theCell ){
        setPhase( Phase.KILL );
    }
end

rule "Kill The Overcrowded"
    ruleflow-group "evaluate"
    no-loop
  when
    // A live cell has more than 3 live neighbors.
    theCell: Cell( liveNeighbors > 3, cellState == CellState.LIVE,
                   phase == Phase.EVALUATE )
  then
    modify( theCell ){
        setPhase( Phase.KILL );
    }
end

rule "Give Birth"
    ruleflow-group "evaluate"
    no-loop
  when
    // A dead cell has 3 live neighbors.
    theCell: Cell( liveNeighbors == 3, cellState == CellState.DEAD,
                   phase == Phase.EVALUATE )
  then
    modify( theCell ){
        theCell.setPhase( Phase.BIRTH );
    }
end

グリッド内の全 Cell オブジェクトが評価されると、この例では "reset calculate" ルールを使用して "calculate" ルールフローグループのアクティベーションを消去します。次に、ルールフローグループがアクティベートされると、"kill""birth" のルールを有効にするルールフローに分岐を挿入します。これらのルールにより状態の変更が適用されます。

ルール "reset calculate"、"kill"、および "birth"

rule "reset calculate"
    ruleflow-group "reset calculate"
  when
  then
    WorkingMemory wm = drools.getWorkingMemory();
    wm.clearRuleFlowGroup( "calculate" );
end

rule "kill"
    ruleflow-group "kill"
    no-loop
  when
    theCell: Cell( phase == Phase.KILL )
  then
    modify( theCell ){
        setCellState( CellState.DEAD ),
        setPhase( Phase.DONE );
    }
end

rule "birth"
    ruleflow-group "birth"
    no-loop
  when
    theCell: Cell( phase == Phase.BIRTH )
  then
    modify( theCell ){
        setCellState( CellState.LIVE ),
        setPhase( Phase.DONE );
    }
end

この段階では、複数の Cell オブジェクトの状態が LIVE または DEAD のいずれかに変更されています。この例では、細胞が生存または死亡すると、"Calculate …​" ルールの Neighbor 関係を使用して、周辺のすべての細胞に繰り返し実行することで、liveNeighbor の数が増減します。数が変更した細胞は、EVALUATE フェーズに設定され、ルールフロー処理の評価段階の推論に含められるようにします。

生存数が判断され、全細胞に設定されると、ルールフロープロセスが終了します。ユーザーが最初に Start をクリックした場合は、その時点でエンジンによりルールフローが再起動します。ユーザーが最初に Next Generation をクリックした場合は、ユーザーが別の世代を要求することができます。

ルール "Calculate …​"

rule "Calculate Live"
    ruleflow-group "calculate"
    lock-on-active
  when
    theCell: Cell( cellState == CellState.LIVE )
    Neighbor( cell == theCell, $neighbor : neighbor )
  then
    modify( $neighbor ){
        setLiveNeighbors( $neighbor.getLiveNeighbors() + 1 ),
        setPhase( Phase.EVALUATE );
    }
end

rule "Calculate Dead"
    ruleflow-group "calculate"
    lock-on-active
  when
    theCell: Cell( cellState == CellState.DEAD )
    Neighbor( cell == theCell, $neighbor : neighbor )
  then
    modify( $neighbor ){
        setLiveNeighbors( $neighbor.getLiveNeighbors() - 1 ),
        setPhase( Phase.EVALUATE );
    }
end

7.10. House of Doom 例のデシジョン (後向き連鎖および再帰)

House of Doom のデシジョンセットの例では、デシジョンエンジンが後向き連鎖と再帰を使用して、階層システムで定義した目的やサブゴールに到達する方法を説明します。

以下は House of Doom の例の概要です。

  • 名前: backwardchaining
  • Main クラス: (src/main/java 内の) org.drools.examples.backwardchaining.HouseOfDoomMain
  • モジュール: drools-examples
  • タイプ: Java アプリケーション
  • ルールファイル: (src/main/resources 内の) org.drools.examples.backwardchaining.BC-Example.drl
  • 目的: 後向き連鎖と再帰を例示します。

後向き連鎖のルールシステムは、通常再帰を使用して、デシジョンエンジンが満たそうとする結論から開始する目的駆動型のシステムです。システムが結論または目的に到達できない場合は、サブとなる目的、つまり、現在の目的の一部を完了する結論を検索します。システムは、最初の結論が満たされるか、すべてのサブとなる目的が満たされるまでこのプロセスを続行します。

反対に、前向き連鎖のルールシステムは、デシジョンエンジンのワーキングメモリーにあるファクトで開始して、そのファクトへの変更に反応するデータ駆動型のシステムです。オブジェクトがワーキングメモリーに挿入されると、その変更の結果として True となるルールの条件はすべて、アジェンダによって実行されるようにスケジュールされます。

Red Hat Decision Manager のデシジョンエンジンは、前向き連鎖と後向き連鎖の両方を使用してルールを評価します。

以下の図は、デシジョンエンジンが、ロジックフローで後向き連鎖のセグメントと、前向き連鎖全体を使用してルールを評価する方法を例示します。

図7.27 前向き連鎖と後向き連鎖を使用したルール評価のロジック

RuleEvaluation Enterprise

House of Doom の例は、さまざまなクエリータイプが含まれるルールを使用し、部屋の場所と家の中のアイテムを探し出します。Location.java のサンプルクラスには、この例で使用する itemlocation 要素が含まれます。HouseOfDoomMain.java のサンプルクラスで、家の該当の場所にアイテムまたは部屋を挿入して、ルールを実行します。

HouseOfDoomMain.java クラスでのアイテムと場所

ksession.insert( new Location("Office", "House") );
ksession.insert( new Location("Kitchen", "House") );
ksession.insert( new Location("Knife", "Kitchen") );
ksession.insert( new Location("Cheese", "Kitchen") );
ksession.insert( new Location("Desk", "Office") );
ksession.insert( new Location("Chair", "Office") );
ksession.insert( new Location("Computer", "Desk") );
ksession.insert( new Location("Drawer", "Desk") );

ルールの例では、家の構造の中で全アイテムおよび部屋の場所を判断するのに、後向き連鎖と再帰を使用します。

以下の図は、House of Doom の構造と、その構造内のアイテムと部屋を示しています。

図7.28 House of Doom の構造

TransitiveReasoning Enterprise

この例を実行するには、IDE で Java アプリケーションとして org.drools.examples.backwardchaining.HouseOfDoomMain クラスを実行します。

実行後に、以下の出力が IDE コンソールウィンドウに表示されます。

IDE コンソールでの実行出力

go1
Office is in the House
---
go2
Drawer is in the House
---
go3
---
Key is in the Office
---
go4
Chair is in the Office
Desk is in the Office
Key is in the Office
Computer is in the Office
Drawer is in the Office
---
go5
Chair is in Office
Desk is in Office
Drawer is in Desk
Key is in Drawer
Kitchen is in House
Cheese is in Kitchen
Knife is in Kitchen
Computer is in Desk
Office is in House
Key is in Office
Drawer is in House
Computer is in House
Key is in House
Desk is in House
Chair is in House
Knife is in House
Cheese is in House
Computer is in Office
Drawer is in Office
Key is in Desk

この例のルールはすべて実行し、家の中の全アイテムの場所を検出して、出力でそれぞれの場所を出力します。

再帰クエリーは、要素間の関係におけるデータ構造階層を使用して繰り返し検索を行います。

House of Doom の例では、BC-Example.drl ファイルに、この例のルールの大半が使用する isContainedIn クエリーが含まれており、家のデータ構造を再帰的に評価して、デシジョンエンジンに挿入するデータがないかを確認します。

BC-Example.drl の再帰クエリー

query isContainedIn( String x, String y )
  Location( x, y; )
  or
  ( Location( z, y; ) and isContainedIn( x, z; ) )
end

"go" のルールは、システムに挿入する文字列をすべて出力し、アイテムをどのように導入し、"go1" ルールが isContainedIn クエリーを呼び出すかを判断します。

ルール "go" および "go1"

rule "go" salience 10
  when
    $s : String( )
  then
    System.out.println( $s );
end

rule "go1"
  when
    String( this == "go1" )
    isContainedIn("Office", "House"; )
  then
    System.out.println( "Office is in the House" );
end

この例は、"go1" 文字列をエンジンに挿入して、"go1" ルールを有効化し、House の場所にある Office アイテムを検出します。

文字列の挿入とルールの実行

ksession.insert( "go1" );
ksession.fireAllRules();

IDE コンソールでの ルール "go1" の出力

go1
Office is in the House

推移閉包ルール

推移閉包は、階層構造の複数レベルであり、上層にある親要素に含まれる要素間の関係です。

"go2" ルールは、DrawerHouse の推移閉包の関係を特定します。Drawer は、House の中の、Office の中の、Desk の中にあります。

rule "go2"
  when
    String( this == "go2" )
    isContainedIn("Drawer", "House"; )
  then
    System.out.println( "Drawer is in the House" );
end

この例は、"go2" 文字列をエンジンに挿入して、"go2" ルールを有効化し、最終的に House の場所に含まれる Drawer アイテムを検出します。

文字列の挿入とルールの実行

ksession.insert( "go2" );
ksession.fireAllRules();

IDE コンソールのルール "go2" の出力

go2
Drawer is in the House

エンジンは、以下のロジックをもとにこの結果を判断します。

  1. クエリーは再帰的に、家の中の複数レベルを検索して、DrawerHouse の間の推移閉包を検出します。
  2. DrawerHouse に直接含まれないため、Location( x, y; ) を使用する代わりに、このクエリーは (z, y; ) の値を使用します。
  3. z の引数は現在バインドされておらず、値が指定されていないため、引数に含まれるものはすべて返されます。
  4. y の引数は現在、House にバインドされているため、zOfficeKitchen を返します。
  5. クエリーは、Office からの情報を収集して、DrawerOffice に含まれているかを再帰的にチェックします。これらのパラメーターに対して、クエリーの行 isContainedIn( x, z; ) が呼び出されます。
  6. Office に直接含まれる Drawer が存在しないため、一致するものはありません。
  7. z のバインドがない場合、このクエリーでは Office 内のデータが返され、z == Desk と判断されます。

    isContainedIn(x==drawer, z==desk)
  8. isContainedIn クエリーは再帰的に 3 回検索し、3 回目に、このクエリーにより Desk の中に Drawer があることが検出されます。

    Location(x==drawer, y==desk)
  9. 最初の場所で上記が一致した後に、このクエリーにより再帰的に構造を上方向に検索し、DrawerDesk の中に、DeskOffice の中に、OfficeHouse の中にあることを判断します。このように、DrawerHouse の中にあるため、このルールは満たされます。

リアクティブクエリールール

リアクティブクエリーでは、データ構造の階層を検索して、要素間に関係があるかを確認し、構造内の要素が変更されると動的に更新されます。

"go3" ルールは、リアクティブクエリーとして機能し、推移閉包により、新しいアイテム KeyOffice に含まれるかどうかを検出します (Office の中の Drawer の中の Key など)。

ルール "go3"

rule "go3"
  when
    String( this == "go3" )
    isContainedIn("Key", "Office"; )
  then
    System.out.println( "Key is in the Office" );
end

この例は、"go3" 文字列をエンジンに挿入して、"go3" ルールを有効にします。最初は、Key が家の構造に存在するため、このルールは満たされず、出力が生成されません。

文字列の挿入とルールの実行

ksession.insert( "go3" );
ksession.fireAllRules();

IDE コンソールのルール "go3" の出力 (条件を満たさない)

go3

この例では、Office の中にある Drawer の場所に、新しいアイテム Key を挿入します。この変更で、"go3" ルールの推移閉包が満たされ、それに合わせて出力が生成されます。

新規アイテムの場所の挿入とルールの実行

ksession.insert( new Location("Key", "Drawer") );
ksession.fireAllRules();

IDE コンソールのルール "go3" の出力 (条件を満たす)

Key is in the Office

またこの変更で、クエリーにより、後に続く再帰検索に含まれるよう、この構造に別のレベルが追加されます。

ルールにバインドなしの引数が含まれたクエリー

バインドなしの引数が 1 つ以上あるクエリーでは、クエリーの定義済み (バインドされている) 引数に含まれる未定義 (バインドされていない) のアイテムをすべて返します。クエリー内の引数でバインドされているものがない場合、クエリーはクエリーの範囲内のアイテムをすべて返します。

"go4" ルールは、バインドされている引数を使用して、Office 内の特定のアイテムを検索するのではなく、バインドされていない引数 thing を使用して、バインドされている引数 Office 内の全アイテムを検索します。

ルール "go4"

rule "go4"
  when
    String( this == "go4" )
    isContainedIn(thing, "Office"; )
  then
    System.out.println( thing + "is in the Office" );
end

この例では "go4" 文字列をエンジンに挿入して、"go4" ルールをアクティベートし、Office の全アイテムを返します。

文字列の挿入とルールの実行

ksession.insert( "go4" );
ksession.fireAllRules();

IDE コンソールのルール "go4" の出力

go4
Chair is in the Office
Desk is in the Office
Key is in the Office
Computer is in the Office
Drawer is in the Office

"go5" ルールは、バインドされていない引数 thinglocation を使用して、House の全データ構造の中に含まれる全アイテムとその場所を検索します。

ルール "go5"

rule "go5"
  when
    String( this == "go5" )
    isContainedIn(thing, location; )
  then
    System.out.println(thing + " is in " + location );
end

この例は "go5" 文字列をエンジンに挿入して、"go5" ルールをアクティベートし、House データ構造に含まれる全アイテムとその場所を返します。

文字列の挿入とルールの実行

ksession.insert( "go5" );
ksession.fireAllRules();

IDE コンソールのルール "go5" の出力

go5
Chair is in Office
Desk is in Office
Drawer is in Desk
Key is in Drawer
Kitchen is in House
Cheese is in Kitchen
Knife is in Kitchen
Computer is in Desk
Office is in House
Key is in Office
Drawer is in House
Computer is in House
Key is in House
Desk is in House
Chair is in House
Knife is in House
Cheese is in House
Computer is in Office
Drawer is in Office
Key is in Desk

付録A バージョン情報

本書の最終更新日: 2021 年 11 月 15 日 (月)