Skip to content

Simple Genealogy Rules – part 5

August 28, 2013

This tutorial is an excerpt from the new Skills421 (www.skills421.com) training course that I am writing for JBOSS Drools.

In part1 we covered the Problem Domain, in part2 we set up our project to work with Drools 5.4.0.Final and JUnit, and in part3 we will create the Classes for our Facts that we are going to pass to the Rule Engine.

In part4 we created the Person class which is the type of Fact that we insert into the Rule Engine, we created the Rule Runner that will build the Knowledge Base for our Rule Engine and insert our Facts into the Rule Engine, and we created and ran our first rule.

Now in part5 we will modify our rules to identify possible Father – Son relationships in the People from whom we have derived our facts.

Refining Our Rules

The first thing we will do is add the additional test cases to our TestRuleRunner class.

Change class as follows:

package com.skills421.training.geneology.rules;

import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;

import com.skills421.training.extend.util.ExtendedDate;
import com.skills421.training.geneology.model.Person;
import com.skills421.training.geneology.rules.RuleRunner;

public class TestDroolsRunner
{
	private static RuleRunner ruleRunner;

	private static Person p1;
	private static Person p2;
	private static Person p3;
	private static Person p4;

	@BeforeClass
	public static void setupFacts()
	{
		try
		{
			ruleRunner = new RuleRunner();

			p1 = new Person();
			p1.setFirstname("John");
			p1.setLastname("Walker");
			p1.setBirthdate(new ExtendedDate("18/02/1803"));

			p2 = new Person();
			p2.setFirstname("James");
			p2.setLastname("Walker");
			p2.setBirthdate(new ExtendedDate("14/11/1825"));

			p3 = new Person();
			p3.setFirstname("Sam");
			p3.setLastname("Walker");
			p3.setBirthdate(new ExtendedDate("28/12/1810"));

			p4 = new Person();
			p4.setFirstname("Jack");
			p4.setLastname("Walker");
			p4.setBirthdate(new ExtendedDate("07/01/1800"));
			p4.setMarriagedate(new ExtendedDate("14/02/1824"));
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	@Test
	public void testFactsInserted()
	{
		System.out.println("*** testFactsInserted ***");
		ruleRunner.buildKnowledgeBaseWithRuleFiles("Ancestry1.drl");
		ruleRunner.runWithFacts(p1,p2,p3,p4);
		ruleRunner.dispose();
	}

	@Test
	@Ignore
	public void testFactsInsertedPrintInOrder()
	{
		System.out.println("*** testFactsInsertedPrintInOrder ***");
		ruleRunner.buildKnowledgeBaseWithRuleFiles("Ancestry1.1.drl");
		ruleRunner.runWithFacts(p1,p2,p3,p4);
		ruleRunner.dispose();
	}

	@Test
	@Ignore
	public void testFatherOlderThanSon()
	{
		System.out.println("*** testFatherOlderThanSon ***");
		ruleRunner.buildKnowledgeBaseWithRuleFiles("Ancestry2.drl");
		ruleRunner.runWithFacts(p1,p2,p3,p4);
		ruleRunner.dispose();
	}

	@Test
	@Ignore
	public void testFatherMarriedAndOlderThanSon()
	{
		System.out.println("*** testFatherMarriedAndOlderThanSon ***");
		ruleRunner.buildKnowledgeBaseWithRuleFiles("Ancestry3.drl");
		ruleRunner.runWithFacts(p1,p2,p3,p4);
		ruleRunner.dispose();
	}

}

TestRuleRunner.java

Reviewing the Changes

We have added 3 more test cases but we have annotated then with @Ignore for now because we don’t want these additional test cases to run.

Each test does exactly the same thing but uses a different Rule File so the expected output  will change.

To run each new test as we create each new Rule File, simply remove the @Ignore that prefixes that test

Sorting our Facts into Date-of-Birth Order

The first of our new rules will simply sort our Facts into order by Date of Birth.

Create the DRL file Ancestry1.1.drl as follows:

package com.skills421.training.geneology

import com.skills421.training.geneology.model.Person;

dialect "mvel"

rule "Order People by Date of Birth"
    when
        $person : Person(lastname=="Walker", $dob : birthdate);
        not Person(birthdate < $dob)
    then
        System.out.println( "Found Person: "+$person.firstname+" "+$person.lastname+" b:"+$person.birthdate);
        retract($person);
end

Ancestry1.1.drl

This rule is a simple modification to our first rule.

Looking at the first line of our Criteria:

  • First of all we identify every Person with a lastname of Walker.  We store a reference to that Person in the variable $person.
  • In addition, we also obtain the birthdate of that person and reference that value in the $dob variable.

Looking at the second line of our Criteria;

  • We tell the Rule Engine that there must not exist a Person whose birthdate is less than the birthdate of the first Person whose birthdate we reference with $dob.

Now if we think about this.  We have 4 Person objects in working memory, so only one of these Person objects can match that criteria.  This rule therefore will return the one person who was born before everybody else.

In order for the remaining Person objects to be sorted we now need to make a change to the Consequence section of the rule.

Looking at the second line of our Consequence:

We now retract the Person who matched our criteria.

This means that we will remove from Working Memory the Person who was born before everybody else.

As soon as a Fact is removed from the Working Memory, however, the Rule Engine will re-evaluate all the other remaining Facts to see if this change has affected them.  It does this by re-firing all the rules.

In this situation, by retracting the first born of all the Person objects and then implicitly re-firing all the rules, the rule will not identify, print and retract a new first born Person.

The rule will be re-fired until all the Person objects have become the first born and retracted from working memory.

This is not a particularly efficient way to use the Rule Engine as every rule would be re-evaluated and if we have a lot of rules this could result in a lot of unnecessary processing.

Remove the @Ignore from the testFactsInsertedPrintInOrder() test and run the tests.

Your console should now display the following:

testFactsInsertedPrintInOrder
Found Person: Jack Walker b:07/01/1800
Found Person: John Walker b:18/02/1803
Found Person: Sam Walker b:28/12/1810
Found Person: James Walker b:14/11/1825

Console Output

Looking for a Potential Father

In our next test case we will identify all potential fathers.

  • We do this by stipulating that  a father must be 18 years older than his son.

Create the DRL file Ancestry2.drl as follows:

package com.skills421.training.geneology

import com.skills421.training.geneology.model.Person;

dialect "mvel"

rule "Could be Father"
	when
        $father : Person(lastname=="Walker", $fborn : birthdate);
        $son : Person(lastname=="Walker", $sborn : birthdate > $fborn.add(0,0,18))
    then
        System.out.println( $father.firstname+" (b: "+$fborn+") could be father of "+$son.firstname+" (b: "+$sborn+")");
end

Ancestry2.drl

Let’s take a look a the first line of our Criteria:

  • here we identify a  father, and reference the Person object with the variable $father.
  • The Father object must have a lastname of Walker
  • We reference the birthdate of the Father with the variable $fborn so that we can use it later.

Let’s take a look at the second line of our Criteria:

  • here we identify a son for the father, and reference the Person object with the variable $son.
  • The Son object must have a lastname of Walker
  • We reference the birthdate of the Son with the variable $sborn so that we can use it later.
  • We stipulate that the Son’s birthdate must be more than the Father’s birthdate + 18 years.

If we think about this, all the work is done in the second line of the Criteria and shows how simple this rule is with our new ExtendedDate object in the Person class as we can access the add() method directly from within the Criteria.

As for the calculation itself.  If we assume that the father was born on 18/05/1800; then adding 18 years to that would give 18/05/1818 making the father 18 years old.  So long as the son is born after that date we have a potential match.

Remove the @Ignore from the testFatherOlderThanSon() test and run the tests.

Your console should now display the following:

testFatherOlderThanSon
Jack (b: 07/01/1800) could be father of James (b: 14/11/1825)
John (b: 18/02/1803) could be father of James (b: 14/11/1825)

Console Output

Looking for a Potential Married Father

In our next test case we will identify all potential fathers who were married.

  • We do this by stipulating that  a father must be 18 years older than his son.
  • We also stipulate that the father must have married 9 month before the son was born.

Create the DRL file Ancestry3.drl as follows:

package com.skills421.training.geneology

import com.skills421.training.geneology.model.Person;

dialect "mvel"

rule "Could be Married Father"
	when
        $father : Person(lastname=="Walker", $fborn : birthdate, $married : marriagedate !=null);
        $son : Person(lastname=="Walker", $sborn : birthdate > $fborn.add(0,0,18), birthdate > $married.add(0,9,0) );
    then
        System.out.println("MARRIED "+$father.firstname+" (b: "+$fborn+", m:"+$married+") could be father of "+$son.firstname+" ("+$sborn+")");
end

Ancestry3.drl

Let’s take a look a the first line of our Criteria:

  • here we identify a  father, and reference the Person object with the variable $father.
  • The Father object must have a lastname of Walker
  • We reference the birthdate of the Father with the variable $fborn so that we can use it later.
  • We reference the marriagedate  of the Father with the variable $married so that we can use it later.
  • We ensure that the marriagedate is not null

Let’s take a look at the second line of our Criteria:

  • here we identify a son for the father, and reference the Person object with the variable $son.
  • The Son object must have a lastname of Walker
  • We reference the birthdate of the Son with the variable $sborn so that we can use it later.
  • We stipulate that the Son’s birthdate must be more than the Father’s birthdate + 18 years.
  • We stipulate that the Son’s birthdate must be more than the Father’s marriagedate + 9 months.

Remove the @Ignore from the testFatherMarriedAndOlderThanSon() test and run the tests.

Your console should now display the following:

testFatherMarriedAndOlderThanSon
MARRIED Jack (b: 07/01/1800, m:14/02/1824) could be father of James (14/11/1825)

Console Output

 

Moving Forward

Moving forward we will modify our rules to make them more efficient and more resilient. We will also create new Fact types and new Rules to process the additional information we may discover as we research of Genealogy tree.

Advertisements

From → Tutorials

2 Comments
  1. Reblogged this on Skills421.

Trackbacks & Pingbacks

  1. Moving Geneology to Drools 6.0 | Drools Rules

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: