Rule Engine

Rule Engine #

Another adaptation approach, which is strongly intertwined with the OperatorAdaptation is the adaptation via the RuleEngine. ProCAKE differentiates between the usage of RuleAdaptation for the adaptation for generic DataObjects and RuleOperatorAdaptation, which applies the conventional learning process of Adaptation Operators (as described here) in conjunction with a rule-based representation of adaptation knowledge for workflows. In the following, the basic concepts and usage in combination with the AdaptationManager as well as standalone are shown.

Basic Concept #

Rules are store in so-called rulebases persisted as *.drl files. In alignment with classic adaptation rules, every rule follows the same pattern: A precondition containing all the conditions that must hold in order for the rule to fire, and a conclusion which specifies the actions taken as a consequence. In the context of ProCAKE this means that the precondition of a rule can contain an arbitrary amount of domain-specific predicates defined by the user. These predicates can directly address attributes of the case object as well as the query object. Consequently, the execution of a rule leads to a manipulation of the object (or a copy of it) that was previously obtained by retrieval. In its current state, the rule engine makes use of a local search algorithm, i.e., Hillclimbing, in order to identify the rule whose application would lead to the greatest similarity improvement towards the query. For this purpose, there are two separate rule bases for a domain: AsimulationBase.drl and an appplicationBase.drl. The rules in those two rule bases only differ in terms of meta-predicates that are used to control the search process. The skeleton of both a simulation rule and an application rule are shown below.

rule "Exchange PAINT_COLOR yellow by silver (Simulation)"
    activation-group "Exchange COLOR yellow by silver (Simulation)"

    when

        $queryCar:  AggregateObject(id == "query")                                                **
        $caseCar:   AggregateObject(id == "case")                                                 **
        $ri:        DroolsCarInterface(                                                           **   ** = Meta-predicates
        	
            bindObjectState($caseCar, true),                                                      **
            hasColor("silver", $queryCar),                                                        ##
            hasColor("yellow", $caseCar)                                                          ##   ## = Domain-predicates

        )

    then

        $ri.exchangeColorBy("silver", $caseCar);                                                  ##
        $ri.evaluateSimulation(drools.getRule().getName());                                       **
end

Application-version of the same rule:

rule "Exchange PAINT_COLOR yellow by silver (Application)"
   activation-group "application"

   when
   	HillClimbingObject(getBestRuleId() == "Exchange COLOR yellow by silver (Application)")   **
       $queryCar:  AggregateObject(id == "query")                                                **
       $caseCar:   AggregateObject(id == "case")                                                 **
       $ri:        DroolsCarInterface(                                                           **   ** = Meta-predicates
       	
           bindObjectState($caseCar, false),                                                     **
           hasColor("silver", $queryCar),                                                        ##
           hasColor("yellow", $caseCar)                                                          ##   ## = Domain-predicates

       )

   then

       $ri.exchangeColorBy("silver", $caseCar);                                                  ##
       $ri.concludeIteration();                                                                  **
end

In analogy to the principles of the Operator Adaptation, the applicable rules of the simulation base are applied to a copy of the retrieved object in order to determine the rule that improves the similarity the most. Once the ID of this rule has been determined, the corresponding rule of the application base is applied to the real case object. This process repeats until no further rule is applicable and/or no further improvement in similarity can be achieved.

For simpler application purposes that do not require the usage of a local search, RuleAdaptation also provides the possibility to execute a single rule base by calling its method applyRuleBase(String pathRuleBase)(more on this to follow). Note that this behavior is currently only supported in standalone mode.

RuleAdaptation #

RuleAdaptation is used for the adaptation of generic DataObjects, e.g., AggregateObjects and can be executed via the AdaptationManager or in standalone mode. Regardless of the selected mode, you need to provide an own implementation of a rule interface extending de.uni_trier.wi2.procake.adaptation.rules.impl.DroolsManualInterfaceImpl. This rule interface represents the mediator between the RuleEngine and ProCAKE by defining domain-specific predicates used within the rules themselves. A simple predicate in the car domain which was also used by the rules above could look like this:

RuleEngineDemoCars.java

    public boolean hasColor(String color, AggregateObject car) {
      StringObject colorObject = (StringObject) car.getAttributeValue("paint_color");
      return colorObject.getNativeString().equals(color);
    }

Note that predicates used within the precondition of the rule need to return a primitive boolean in order for the RuleEngine to evaluate them correctly. A corresponding action used within the conclusion of the rule could look like this:

RuleEngineDemoCars.java

    public void exchangeColorBy(String newColor, AggregateObject car) {
      StringObject colorObject = ModelFactory.getDefaultModel().createObject("String");
      colorObject.setNativeString(newColor);
      ((AggregateObject) getObject()).setAttributeValue("paint_color", colorObject);
    }

Note: When manipulating the case objects in the conclusion of the rule (e.g., exchangeColor) please make sure to use the helper method getObject() from DroolsManualInterfaceImpl as it needs to be distinguished if those changes are applied in the simulation phase or application phase. After defining the required set of predicates in the custom rule interface and importing this interface in your rule base, the method signatures can be used within the precondition (when) and conclusion (then) of the rule. A full implementation of a rule interface for the car domain is located at de.uni_trier.wi2.procake_demos.cars.adaptation.rules.DroolsCarInterface.

Usage with the AdaptationManager #

When using the AdaptationManager to perform RuleAdaptation, simply create a custom AlgorithmConfiguration in the XML configuration file.RuleAdaptation expects the following three parameters to be set:

Parameter (Code, XML)Type/RangeDefault ValueDescription
PATH_SIMULATION_BASE, pathSimulationBaseStringnullPath to the rule base used during the simulation phase.
PATH_APPLICATION_BASE, pathApplicationBaseStringnullPath to the rule base used during the application phase.
MANUAL_INTERFACE_IMPLEMENTATION, manualInterfaceImplementationStringnullCustom implementation class of the domain-specific implementation of DroolsManualInferfaceImpl.

A sample configuration can be found at de.uni_trier.wi2.procake_demos.domains.cars.adaptation.CarsRuleAdaptationTestConfig.xml. Once configured, the adaptation process can be initiated as follows:

RuleEngineDemoCars.java

    AdaptationManager<AggregateObject, AggregateObject> adaptationManager = new AdaptationManagerImpl<>(
        pool);

    AdaptationConfiguration adaptationConfiguration = IOUtil.readFile(
        "/de/uni_trier/wi2/procake_demos/wiki/adaptation/cars/CarsRuleAdaptationTestConfig.xml",
        AdaptationConfigReader.READER_NAME);

    adaptationManager.init(adaptationConfiguration, null);

    AdaptationSession<AggregateObject, AggregateObject> adaptationSession =
        adaptationManager.execute(pool.getObject("C07"), pool.getObject("C20"));

    System.out.println("Query:\t " + adaptationSession.getAdaptationQuery().toDetailedString());
    System.out.println("Case:\t " + adaptationSession.getOriginCase().toDetailedString());
    System.out.println("Adapted: " + adaptationSession.getAdaptedCase().toDetailedString());

In this case no retrieval is performed and adaptation is directly initiated with the case object C07 and the query object C20. The demo class containing this code is located at de.uni_trier.wi2.procake_demos.cars.adaptation.rules.CarsRuleDemoAdaptationManager.

Usage in Standalone mode #

It is also possible to use the RuleEngine without its integration into the AdaptationManager. To choose this form of operation, RuleAdaptation has to be instantiated directly. Due to the lack of XML configuration, the required parameters listed in the table above have to be set programmatically. Additionally, the method executeObjectAdaptation(orderedCategories) accepts a sorted list of object attributes. If provided, the RuleEngine executes rules adapting the given attribute in the listed order, meaning that in this case rules adapting the attribute year will be considered before rules adaptingfuel. Attributes that are not contained in the list will not be adapted at all. If no such list is passed to the method, rules will be chosen according to their most significant similarity improvement towards the query (Hillclimbing).

RuleEngineDemoCars.java

    AggregateObject caseCar = pool.getObject("C07");
    AggregateObject queryCar = pool.getObject("C20");

    DroolsCarInterface manualInterface = new DroolsCarInterface();

    RuleAdaptation adaptation = new RuleAdaptation(caseCar, queryCar, manualInterface);
    adaptation.init(
        "de/uni_trier/wi2/procake_demos/wiki/adaptation/cars/ruleBaseSimulation.drl",
        "de/uni_trier/wi2/procake_demos/wiki/adaptation/cars/ruleBaseApplication.drl");

    List<String> orderedCategories = Arrays.asList(
        "year", "fuel", "price"
    );

    DroolsAdaptationResult adaptationResult = adaptation.executeObjectAdaptation(orderedCategories);
    System.out.println(adaptationResult);

    System.out.println("Query:\t " + queryCar.toDetailedString());
    System.out.println("Case:\t " + caseCar.toDetailedString());
    System.out.println("Adapted: " + adaptationResult.getAdaptedObject().toDetailedString());

The demo class containing this code is located at de.uni_trier.wi2.procake_demos.cars.adaptation.rules.CarsRuleDemoStandalone.

As mentioned previously running the RuleEngine in standalone mode also allows for the execution of a single rule base without applying a local search algorithm. To do this, simply call the method applyRuleBase() instead of initiating the rule bases and executing executeObjectAdaptation() as shown in the snippet below:

RuleEngineDemoCars.java

    AggregateObject caseCar = pool.getObject("C07");
    AggregateObject queryCar = pool.getObject("C20");

    DroolsCarInterface manualInterface = new DroolsCarInterface();
    RuleAdaptation adaptation = new RuleAdaptation(caseCar, queryCar, manualInterface);

    DataObject adaptedCar = adaptation.applyRuleBase(
        "de/uni_trier/wi2/procake_demos/wiki/adaptation/cars/singleRuleBase.drl");

The adapted object will be returned directly. Note that since there is no control mechanism over the rule execution process, all applicable rules present in the rule base will be applied to the provided case object. Hence, rules no longer need any meta predicates directing the search. As a consequence, rules can be as simple as:

rule "Paint case car grey"
    when

        $queryCar:  AggregateObject(id == "query")
        $caseCar:   AggregateObject(id == "case")
        $ri:        DroolsCarInterface(

            bindObjectState($queryCar, $caseCar),
            hasColor("grey", $queryCar)
        )

    then
        $ri.exchangeColorBy("grey", $caseCar);
        System.out.println("Painted case car grey!");
end

RuleOperatorAdaptation #

RuleOperatorAdaptation is used for the adaptation of NESTWorkflowObjects and can be executed via the AdaptationManager or in standalone mode. As the name suggests, it applies the principles of OperatorAdaptation at heart, generating rules based on the learned operators. Consequently, rules are a different form of representing those operators in order to make them accessible for the RuleEngine. Just like with RuleAdaptation you need to provide an own implementation of a rule interface extending de.uni_trier.wi2.procake.adaptation.rules.operator.impl.DroolsOperatorInterface. This extension needs to provide methods that differ depending on the application domain, namely insertDataNode, insertTaskNode, insertDataflowEdge, insertControlflowEdge, exchange, and cleanUp. If you are unfamiliar with these concepts stemming from the operator adaptation, please refer to the corresponding paper available here. A sample implementation for the cooking domain can be found at de.uni_trier.wi2.procake_demos.recipes.workflows.adaptation.rules.DroolsCookingOperatorInterface. If you wish to add custom domain preconditions that go beyond those defined in DroolsOperatorInterface, they would need to be added to your custom implementation as well.

In addition to the rule interface, a custom utility class defining certain aspects about NEST graph handling in your domain needs to be provided. The generic utility class de.uni_trier.wi2.procake.adaptation.rules.impl.DomainUtils contains methods for handing graph fragments that are independent of the application domain. Beyond these existing implementations, however, you need to implement the following methods, as they depend on the semantic description used within your domain: getNodeName, getNodeDescription, compareNodes. A sample implementation for the cooking domain can be found at de.uni_trier.wi2.procake_demos.recipes.workflows.adaptation.rules.CookingDomainUtils.

Usage with the AdaptationManager #

When using the AdaptationManager to perform RuleOperatorAdaptation, simply create a custom AlgorithmConfiguration in the XML configuration file. The parameters required by RuleOperatorAdaptation overlap with those required by OperatorAdaptatation:

The following parameters can be set:

Parameter (Code, XML)Type/RangeDefault ValueDescription
PATH_SIMULATION_BASE, pathSimulationBaseStringnullPath to the rule base used during the simulation phase.
PATH_APPLICATION_BASE, pathApplicationBaseStringnullPath to the rule base used during the application phase.
EXPERT_MODE, expertModeFlag (boolean)falseSets whether conditional actions such as deleteEdgeIfPresent should be exposed in the conclusion. (currently not supported)
TRANSFORMATION_MODE, transformationModeTransformationModesSIMILAR_STREAMLETSets the transformation mode used for identifying the deletion streamlets and generating the deletion/exchange rules for adaptation.SIMILAR_STREAMLET requires the presence of a sufficiently similar deletion streamlet in the graph while EXACT_STREAMLET requires the existence of an exact match.
SIM_THRESHOLD_STREAMLETS, simThresholdStreamletsDouble0.5Sets the minimum similarity between the deletion streamlet and the current streamlet in the workflow.
SIM_THRESHOLD_ANCHOR, simThresholdAnchorDouble0.5Sets the min similarity between the anchor task of the insertion streamlet in the workflow streamlet in order to be a valid anchor task.
OPERATOR_ADAPTATION_IMPLEMENTATION, operatorAdaptationImplementationStringnullDomain implementation of OperatorAdaptationImpl that is used for learning the operators from a casebase.
OPERATOR_INTERFACE_IMPLEMENTATION, operatorInterfaceImplementationStringnullDomain implementation of DroolsOperatorInterface that is used for providing custom predicates and other utility methods within rules.
DOMAIN_UTILS_IMPLEMENTATION, domainUtilsImplementationStringnullDomain implementation of DomainUtils providing custom utility methods for handling NEST graphs.
DATA_NODE_DATA_CLASS, dataNodeDataClassStringnullData node class of the data items.
TASK_NODE_DATA_CLASS, taskNodeDataClassStringnullTask node class of the tasks.
MIN_HEAD_DATA_SIM, minHeadDataSimDouble0.5Sets the minimum similarity between the data node in the workflow and the head data node in the streamlet.
MIN_HEAD_PRODUCER_TASK_SIM, minHeadProducerTaskSimDouble0.5Sets the minimum similarity between the data node in the workflow and the head data node in the streamlet.
MIN_WFPAIR_SIM, minWFPairSimDouble0.0Sets the min similarity between workflows that must be achieved in order to consider workflows as similar enough to learn adaptation operators based on the difference of these two workflows.

A sample configuration can be found at de.uni_trier.wi2.procake_demos.domains.recipes.workflows.adaptation.RecipeRuleAdaptationTestConfig.xml. Once configured, the adaptation process can be initiated as follows:

RuleEngineDemoRecipes.java

    SimilarityModelFactory.getDefaultSimilarityModel()
        .addSimilarityMeasure(new SMNESTQueryImpl(new CookingWorkflowSimilarityUtils()),
            SMNESTQuery.NAME);

    AdaptationManager<NESTWorkflowObject, NESTQuery> adaptationManager = new AdaptationManagerImpl<>(
        pool);

    AdaptationConfiguration adaptationConfiguration = IOUtil.readFile(
        "/de/uni_trier/wi2/procake_demos/wiki/adaptation/recipes/RecipeRuleAdaptationTestConfig.xml",
        AdaptationConfigReader.READER_NAME);

    adaptationManager.init(adaptationConfiguration, null);

    NESTWorkflowObject emptyWorkflow = new NESTWorkflowBuilderImpl<NESTWorkflowObject>().createNESTWorkflowGraphObject(
        "Empty_Workflow", "CustomWorkflow", null);

    NESTQuery query = new NESTQueryImpl();
    query.setQueryGraph(pool.getObject("W01"));
    query.setRestrictionGraph(emptyWorkflow);

    AdaptationSession<NESTWorkflowObject, NESTQuery> adaptationSession =
        adaptationManager.execute(pool.getObject("W02"), query);

The demo class containing this code is located at de.uni_trier.wi2.procake_demos.recipesworkflows.adaptation.RecipeRuleDemoAdaptationManager.

Usage in Standalone mode #

It is also possible to use the operator-based rule adaptation without its integration into the AdaptationManager. To use this mode of operation, a few more extra steps are required as the configuration process has to be done manually:

  1. Since RuleOperatorAdaptation uses regular AdaptationOperators as a base for rule extraction, you need to provide those either directly in-memory or as serialized adaptation knowledge. If provided directly in-memory you can skip this step. Otherwise, RuleEngine provides a parser (de.uni_trier.wi2.procake_demos.recipes.workflows.adaptation.operator.xml.AdaptationOperatorParser.xml) to read the operators. This can be done by calling:

RuleEngineDemoRecipes.java

    AdaptationOperatorParser operatorParser = new AdaptationOperatorParser();
    operatorParser.init(
        "/de/uni_trier/wi2/procake_demos/wiki/adaptation/recipes/operatorRepository.xml");
    List<AdaptationOperator> operators = operatorParser.parse();
  1. Additionally, your own implementations of DomainUtils and DroolsOperatorInterface need to be instantiated and set up with the corresponding DataClass of the data and task nodes in your domain. For the cooking domain this could look like the following:

RuleEngineDemoRecipes.java

    CookingDomainUtils cookingDomainUtils = new CookingDomainUtils();
    DroolsOperatorInterface cookingOperatorInterface = new DroolsCookingOperatorInterface(
        cookingDomainUtils);
    cookingOperatorInterface.setDataNodeDataClass("ingredientType");
    cookingOperatorInterface.setTaskNodeDataClass("activityType");
  1. As a next step, the RuleBaseGenerator has to be set up. It expects the list of operatos and your custom implementation of DomainUtils as arguments. Additionally, you need to configure it by providing paths where the simulationBase and applicationBase should be written to. Note: You can add as many output paths as you like, however, you should always also write them to your projects target in order for the RuleEngine being able to load them without any issues.

RuleEngineDemoRecipes.java

    RuleBaseGenerator repository = new RuleBaseGenerator(operators, cookingDomainUtils);
    repository.addCustomOutputPathSimulation(
        "target/classes/de/uni_trier/wi2/procake_demos/wiki/adaptation/recipes/ruleBaseSimulation.drl");
    repository.addCustomOutputPathApplication(
        "target/classes/de/uni_trier/wi2/procake_demos/wiki/adaptation/recipes/ruleBaseApplication.drl");
    repository.extractRuleBases();
  1. Finally, RuleOperatorAdaptation can be instantiated and executed. The result of the adaptation is wrapped in the class DroolsAdaptationOperatorResultImpl providing an own toString() implementation for displaying the generated chain of rules in the console.

RuleEngineDemoRecipes.java

    RuleOperatorAdaptation adaptation = new RuleOperatorAdaptation(retrievedGraph, queryGraph,
        repository, false, cookingOperatorInterface);
    adaptation.init(
        "de/uni_trier/wi2/procake_demos/wiki/adaptation/recipes/ruleBaseSimulation.drl",
        "de/uni_trier/wi2/procake_demos/wiki/adaptation/recipes/ruleBaseApplication.drl");

    DroolsOperatorAdaptationResultImpl adaptationResult = adaptation.executeWorkflowAdaptation();
    System.out.println(adaptationResult);

The entire demo implementation can be found at de.uni_trier.wi2.procake_demos.recipes.workflows.adaptation.RecipeRuleDemoStandalone.