Queries and backward-chaining

Queries were introduced in Chapter 5, Understanding KIE Sessions, as a way to retrieve information from a KIE Session. But queries are much more powerful than that in Drools. As a matter of fact, queries are the way Drools implements what is called backward-chaining reasoning. But before entering this new topic, and given that we were already talking about PHREAK, let's see how a regular query looks in the PHREAK network.

For this section of the book, we are going to introduce a new Java class that will be used to establish a whole-part relationship between Item objects. What this means it that an Item can now be composed of other Items:

Queries and backward-chaining

The whole-part relationship between Items is modeled as a generic class, called IsPartOf. This generic class allows us to define non-intrusive relationships, not just between Items, but also between any other types of object in our model. As an example, if we want to specify the relation between a car, an engine, and a distributor Item, we can do it with the following code snippet:

//The constructor arguments are: name, cost and sale price.
Item car = new Item("car", 15000D, 20000D);
Item engine = new Item("engine", 5000D, 7000D);
Item distributor = new Item("distributor", 200D, 280D);
//The constructor arguments are: whole and part
IsPartOf<Item> r1 = new IsPartOf<>(car, engine);
IsPartOf<Item> r2 = new IsPartOf<>(engine, distributor);

If all the Item and IsPartOf objects are facts in a knowledge base, we can write the following query to know whether an Item is part of another:

query isItemContainedIn(Item p, Item w)
    IsPartOf(whole == w,  part == p)
end

The preceding query has one major limitation: it doesn't expose the transitivity of the IsPartOf relationship. In other words, if we use this query to ask whether a distributor is part of a car, the answer will be no. We will take care of this limitation later; for now, let's see how the PHREAK representation of the preceding query looks:

Queries and backward-chaining

The first interesting thing to notice in the preceding PHREAK network is the presence of a DroolsQuery OTN. This class is used to represent every query in Drools, and it contains information such as the name and arguments of the query it represents. When a query is invoked in Drools, a new instance of this fact will be created with the corresponding name and arguments and inserted into the KIE Session as a fact.

The first node after the DroolsQuery OTN is the Alpha node that discriminates the name of the query. The Beta Node that follows joins the DroolsQuery and the IsPartOf patterns. This is indeed a Beta Node because the w and t variables used in the IsPartOf pattern are bound to the arguments of the DroolsQuery fact.

The last node in the network is a new type of node: a Query Terminal Node. This node will be in charge of the generation of the query result.

Unification

Drools supports argument unification in its patterns via the := symbol. This means that the same variable can be used in multiple places: the first occurrence of the variable will bind it to a value and any other occurrence will constrain to that same value.

Let's take the following rule from Chapter 6, Complex Event Processing and rewrite it using unification:

rule "More than 10 transactions in an hour from one client"
    when
        $t1: TransactionEvent($cId: customerId)
        Number(intValue >= 10) from accumulate(
            $t2: TransactionEvent(
                this != $t1,
                customerId == $cId,
                this meets[1h] $t1
            ),
            count($t2)
        )
        not (SuspiciousCustomerEvent(customerId == $cId, reason == "Many transactions"))
    then
        insert(new SuspiciousCustomerEvent($cId, "Many transactions"));
end

The variable $cId in the preceding rule is bound (defined) in the first pattern and then used in the following two. Using unification, this same rule could have been written as follows:

rule "More than 10 transactions in an hour from one client"
    when
        $t1: TransactionEvent($cId := customerId)
        Number(intValue >= 10) from accumulate(
            $t2: TransactionEvent(
                this != $t1,
                $cId := customerId,
                this meets[1h] $t1
            ),
            count($t2)
        )
        not (SuspiciousCustomerEvent($cId := customerId, reason == "Many transactions"))
    then
        insert(new SuspiciousCustomerEvent($cId, "Many transactions"));
end

When the variable $cId is first used in the first pattern, it is bound to the value of the customerId property of the TransactionEvent fact. Any other occurrence of this variable is then converted by Drools into an equals constraint.

For rules, the unification feature in Drools is mostly syntactic sugar. But when unification is used inside a query, things get interesting.

Going back to our isItemContainedIn query, let's assume that now we are also interested in knowing all the Items a specific Item is part of or what all the parts of a specified Item are. With our current knowledge, the new requirements introduce two new queries:

//Query to know if an Item is part of another
query isItemContainedIn(Item p, Item w)
    IsPartOf(whole == w,  part == p)
end

//Query to know all the parts of an Item
query getItemParts(Item w)
    IsPartOf(whole == w, $p: part)
end

//Query to know all the Items a specific Item is part of
query getItemsFromAPart(Item p)
    IsPartOf($w: whole, part == p)
end

The good news is that unification in queries gives us the possibility of having optional arguments. Using unification, the three previous queries can be rewritten as a single one:

query isItemContainedIn(Item p, Item w)
    IsPartOf(w := whole,  p := part)
end

When the query is executed, if both arguments are provided, the unification symbols in the IsPartOf pattern will be treated as constraints. For any argument that is not provided, the unification symbol will act as a binding. The results of this query according to its inputs are explained in the next table:

p

w

Resulting Pattern

bound

bound

IsPartOf ( whole == w,  part == p)

bound

not bound

IsPartOf ( w: whole,  part == p)

not bound

bound

IsPartOf ( whole == w,  p: part)

not bound

not bound

IsPartOf ( w: whole,  p: part)

Bound arguments in a query are referred to input argument and unbound ones as output arguments.

In Java, the way we have to use unbound arguments when executing a query is by using the special object org.kie.api.runtime.rule.Variable.v for the unbound arguments:

//engine and car are Item instances inserted as facts.
//Both arguments are bound
QueryResults qr1 = ksession.getQueryResults("isItemContainedIn", engine, car);

//Argument 'p' is bound. Argument 'w' will be bound in the result of the query to
//the corresponding values.
QueryResults qr2 = ksession.getQueryResults("isItemContainedIn", engine, Variable.v);

The sources associated with this chapter contain different tests, showing how unification can be used in a query to allow the use of optional arguments. The PhreakInspectorQueryTest class is a good starting point.

Positional arguments

Positional arguments in Drools are a way to add equality constraints to fields of a fact without having to explicitly name them. The order of a positional argument in a pattern determines which field of the pattern's class it refers to. So, for example, the pattern IsPartOf(w == whole, p == part) can be rewritten simply as IsPartOf(w, p;). Given that conditional arguments can be used along with regular constraints, a semicolon is used to indicate the end of the positional arguments section.

The map between the position of an argument in a pattern and the field it represents is explicitly stated using the org.kie.api.definition.type.Position annotation. This annotation, which can only be used at the field level of a class, will take an integer value that specifies its order. In order to be able to use positional arguments with our IsPartOf class, we then have to annotate its fields in the following way:

public class IsPartOf<T> {
    @Position(0)
    private final T whole;
    @Position(1)
    private final T part;
    ...
}

Fields in declared types can also be annotated with the @Position annotation but this is not required: by default the order in which the fields of a declared type are declared is used as its positional argument order.

Because the @Position annotation can be inherited by subclasses, possible conflicting values may appear. In these situations, the field in the superclasses will have precedence over the ones in the subclasses.

Another important feature about positional arguments is that they are always resolved using unification; if the variable used as an argument is not already bound, a new bind is created.

Backward reasoning in Drools

Now that we know some new tricks about queries in Drools, we are ready to introduce a new topic that will rely on them: backward reasoning (also known as backwards chaining).

Ever since its early development, Drools has always been a reactive forward-chaining engine: the rules react to the state of the session, and their action partintroduces or modify the available knowledge that can lead to the activation and execution of new rules. In this type of system, the available data is processed until a goal is reached.

The other side of the spectrum belongs to backward-chaining systems. Here, the starting point is the desired goal and the system works backward, checking whether the data in the session satisfies it or not. Both reasoning methods may involve the generation (inference) of new data in the process.

The way Drools implements a certain degree of backward reasoning is by using queries. In a backward-chaining world, queries can be seen as goals or sub-goals that need to be satisfied by the engine. But in an expert rule system, such as Drools, the individual conditions of a rule can also be seen as sub-goals. The way Drools came up with to bring both forward and backward reasoning together was by allowing queries to be used as conditions in a rule.

As an example, let's assume we have the following Items in our system and we know the Is Part Of relationship between them:

Backward reasoning in Drools

In code, the preceding diagram can be written as:

Item car = new Item("car", 15000D, 20000D);
Item engine = new Item("engine", 5000D, 7000D);
Item wheel = new Item("wheel", 50D, 75D);
Item battery = new Item("battery", 100D, 150D);
Item distributor = new Item("distributor", 200D, 280D);
IsPartOf<Item> r1 = new IsPartOf<>(car, engine));
IsPartOf<Item> r2 = ksession.insert(new IsPartOf<>(car, wheel));
IsPartOf<Item> r3 = ksession.insert(new IsPartOf<>(engine, battery));
IsPartOf<Item> r4 = ksession.insert(new IsPartOf<>(engine, distributor));

Let's also assume that we want to apply a 5% discount to Orders containing related (via the IsPartOf relation) Items. So, an order containing, for example, an Engine and a Battery will get a discount, but an order containing a Wheel and a Distributor will not. In this case, one of the "sub-goals" to apply a discount is then whether an IsPartOf relation exists between two items in an order. In the previous section of this chapter, we have already worked on a query that will allow us to determine this relation between items. What we can do then is use the query we had already created in a new rule that will apply the corresponding discount:

rule "Apply discount to orders with related items"
no-loop true
when
    $o: Order()
    exists (
        OrderLine($item1 := item) from $o.orderLines and
        OrderLine($item2 := item) from $o.orderLines and
        isItemContainedIn($item1, $item2;)
    )
then
    modify ($o){ increaseDiscount(0.05) };
end

The preceding rule can be read as: When there is an Order and it contains at least two items (that could be the same) where one is part of the other, then apply a 5% discount. The highlighted pattern in the rule is the invocation to the isItemContainedIn query. In this particular scenario, the query will be evaluated as soon as the $item1 and $item2 variables have a value. Drool will then try to see if both items satisfy the IsPartOf goal or not.

But remember that we know there is a major limitation in our query: an Order containing a Distributor and a Car will not get any discount, even if they are transitively related via the IsPartOf relationship. Now that we know that queries can be used as a pattern in Drools, there is an easy way to fix this:

query isItemContainedIn(Item p, Item w)
    IsPartOf(w, p;)
    or (IsPartOf(x, p;) and isItemContainedIn(x, w;))
end

The new version of our query now contains a recursive invocation that will deal with the transitive aspect of our relation.

When this query is invoked as isItemContainedIn(engine, car), its first pattern will match because we have an explicit relation between those two items. When it is invoked as isItemContainedIn(distributor, car) though, there is no explicit IsPartOf for those two items, so the first pattern of the query will not match. But we have now introduced a new path in our query; when the IsPartOf(x, p;) pattern is evaluated, x is an unbound variable that Drools will replace with the engine item (because we do have an IsPartOf fact for engine and distributor). Now that the x is bound, the query is recursively invoked now as isItemContainedIn(engine, car). The recursive call will indeed result in a match (we do have an IsPartOf fact for car and engine), meaning that the original query will also result in one.

The Query Element Node

The last remaining question regarding queries is, "How is a query invocation resolved in PHREAK?" The answer relies on a new type of node we haven't yet introduced: the Query Element Node.

In Chapter 5, Understanding KIE Sessions we learnt about live queries and how we can attach a ViewChangedEventListener to them in order to be notified in real-time when new information is available. This is pretty much how a reactive Query Element Node works. It registers itself as a ViewChangedEventListener to the corresponding query to react to new results or modifications in previously generated results.

The PHREAK network for a Knowledge Base containing the recursive version of the isItemContainedIn query and the Apply discount to orders with related items rule will then look like the following:

The Query Element Node

The network was visually split into two sections: one corresponding to the query and the other corresponding to the rule. Some interesting aspects of this network are:

  • It contains two Query Terminal Nodes because of the or method we used in the query.
  • It contains two Query Element Nodes: one for the recursive invocation inside the query itself and the other for the invocation of the query in the rule.
  • There is no explicit relationship (no arrow) connecting any of the nodes from the query with a node of the rule. This relationship is not required because the communication between a query and any related Query Element Node is done using a ViewChangedEventListener.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.12.3