项目作者: sgreben

项目描述 :
A rule checker library for Java. Checks sets of rules for completeness, overlap, and constraint satisfaction.
高级语言: Java
项目地址: git://github.com/sgreben/rc.git
创建时间: 2017-02-05T16:48:13Z
项目社区:https://github.com/sgreben/rc

开源协议:

下载


rc - a rule checker

rc is a completeness, overlap, and constraint satisfaction checker library for sets of rules. For a command-line interface to this library see rc-cli.

rc is implemented using Z3, the automatic theorem prover built by Microsoft Research.

Table of Contents

Installation

Maven dependency

  1. <dependency>
  2. <groupId>io.github.sgreben</groupId>
  3. <artifactId>rule-checker</artifactId>
  4. <version>1.0-SNAPSHOT</version>
  5. </dependency>

Usage

rc is used as follows:

  • Construct a set of rules as an input to rc (either via a builder API or via YAML files)
  • Use rc to check the rules for completeness, consistency and constraint satisfaction
  • Use the counterexamples generated by rc to improve the rules

The input to rc has the following structure:

  • Module
    • Type definitions
    • Rule sets
      • Variable definitions
      • Rules
        • Precondition (when)
        • Postcondition (then)

There are two ways of constructing these objects:

  • Using the builders in io.github.sgreben.rc.Context and io.github.sgreben.rc.buildersto construct the objects directly.
  • Building (or loading from YAML) the declaration classes in io.github.sgreben.rc.declartions and then compiling them.

As an example, we will express the following rules for a fictional IoT device:

  • Given an enumeration type STATE with the values ON, OFF, STANDBY,
  • Given the sensor inputs temperature (integer), temperatureGoal (integer), brightness (real), motion (real),
  • Given the state input variable state and output variable stateOut of type STATE,
  • We apply the rules
    • When temperature > 23 && (motion < 0.3 || brightness < 0.1)
      • Then stateOut = OFF
    • When temperature < temperatureGoal && motion >= 0.3
      • Then stateOut = ON
    • When temperature >= temperatureGoal && motion < 0.1
      • Then stateOut = state
    • When temperature >= temperatureGoal && motion > 0.1
      • Then stateOut = STANDBY

Using the builder API

To start, we grab an instance of the Context class, which we will use to access the builders:

  1. import io.github.sgreben.rc.Context;
  2. Context context = new Context();

Next, we construct the enumeration type:

  1. EnumType stateType = context.buildType().enumeration()
  2. .withName("STATE")
  3. .withValue("ON")
  4. .withValue("OFF")
  5. .withValue("STANDBY")
  6. .build();

Using the type definition we can construct our variables:

  1. Variable temperature = context.buildExpression()
  2. .variable("temperature")
  3. .ofType(context.buildType().integer());
  4. Variable temperatureGoal = context.buildExpression()
  5. .variable("temperatureGoal")
  6. .ofType(context.buildType().integer());
  7. Variable brightness = context.buildExpression()
  8. .variable("brightness")
  9. .ofType(context.buildType().real());
  10. Variable motion = context.buildExpression()
  11. .variable("motion")
  12. .ofType(context.buildType().real());
  13. Variable state = context.buildExpression()
  14. .variable("state")
  15. .ofType(stateType);
  16. Variable stateOut = context.buildExpression()
  17. .variable("stateOut")
  18. .ofType(stateType);

We are now ready to construct our rules:

  1. ExpressionBuilder eb = context.buildExpression();
  2. // When `temperature > 23 && (motion < 0.3 || brightness < 0.1)`
  3. // Then `stateOut = OFF`
  4. Rule rule1 = context.buildRule()
  5. .withName("rule 1")
  6. .withPrecondition(eb.And(
  7. eb.Greater(temperature, context.buildValue().integer(23)),
  8. eb.Or(
  9. eb.Less(motion, context.buildValue().real(0.3)),
  10. eb.Less(brightness, context.buildValue().real(0.1))
  11. )
  12. ))
  13. .withPostcondition(eb.Equal(
  14. stateOut, context.buildValue().enumeration("OFF"))
  15. )
  16. .build();
  17. // When `temperature < temperatureGoal && motion >= 0.3`
  18. // Then `stateOut = ON`
  19. Rule rule2 = context.buildRule()
  20. .withName("rule 2")
  21. .withPrecondition(eb.And(
  22. eb.Less(temperature, temperatureGoal),
  23. eb.GreaterOrEqual(motion, context.buildValue().real(0.3))
  24. ))
  25. .withPostcondition(eb.Equal(
  26. stateOut, context.buildValue().enumeration("ON"))
  27. )
  28. .build();
  29. // When `temperature >= temperatureGoal && motion < 0.1`
  30. // Then `stateOut = state`
  31. Rule rule3 = context.buildRule()
  32. .withName("rule 3")
  33. .withPrecondition(eb.And(
  34. eb.GreaterOrEqual(temperature, temperatureGoal),
  35. eb.Less(motion, context.buildValue().real(0.1))
  36. ))
  37. .withPostcondition(eb.Equal(stateOut, state))
  38. .build();
  39. // When `temperature >= temperatureGoal && motion > 0.1`
  40. // Then `stateOut = STANDBY`
  41. Rule rule4 = context.buildRule()
  42. .withName("rule 4")
  43. .withPrecondition(eb.And(
  44. eb.GreaterOrEqual(temperature, temperatureGoal),
  45. eb.Greater(motion, context.buildValue().real(0.1))
  46. ))
  47. .withPostcondition(eb.Equal(
  48. stateOut, context.buildValue().enumeration("STANDBY"))
  49. )
  50. .build();

To check properties of a set of rules, we construct an instance of the RuleSet class:

  1. RuleSet myRuleSet = context.buildRuleSet()
  2. .withName("my rule set")
  3. .withRule(rule1)
  4. .withRule(rule2)
  5. .withRule(rule3)
  6. .withRule(rule4)
  7. .build();

Done! You can now skip ahead to Performing analysis to learn how to check the rule set for completeness, overlap, and constraint satisfaction.

Using the declaration API

Save the following as a file myModule.yaml.

  1. name: myModule
  2. types:
  3. STATE:
  4. values:
  5. - ON
  6. - OFF
  7. - STANDBY
  8. ruleSets:
  9. - name: my rule set
  10. variables:
  11. temperature: int
  12. temperatureGoal: int
  13. motion: real
  14. brightness: real
  15. state: STATE
  16. stateOut: STATE
  17. rules:
  18. - name: rule 1
  19. when: temperature > 23 && (motion < 0.3 || brightness < 0.1)
  20. then:
  21. stateOut: OFF
  22. - name: rule 2
  23. when: temperature < temperatureGoal && motion >= 0.3
  24. then:
  25. stateOut: ON
  26. - name: rule 3
  27. when: temperature >= temperatureGoal && motion < 0.1
  28. then:
  29. stateOut: state
  30. - name: rule 4
  31. when: temperature >= temperatureGoal && motion > 0.1
  32. then:
  33. stateOut: STANDBY

We can now load this module using the ModuleDeclaration class:

  1. import io.github.sgreben.rc.declarations.ModuleDeclaration;
  2. ModuleDeclaration moduleDeclaration = ModuleDeclaration.load("myModule.yaml")

To perform analysis on the module, we have to compile it using a Context:

  1. Context context = new Context();
  2. Module module = moduleDeclaration.compile(context);

We can now obtain the rule set we defined above:

  1. RuleSet myRuleSet = module.ruleSet("my rule set");

Done! You can now continue to Performing analysis to learn how to check the rule set for completeness, overlap, and constraint satisfaction.

Performing analysis

We are now ready to check our rules for completeness, overlap, and constraint satisfaction:

  1. // Checks completeness, prints example unmatched values if incomplete
  2. if (!myRuleSet.isComplete()) {
  3. System.out.println(myRuleSet.completenessCounterexample())
  4. }
  1. // Checks for overlaps, prints example values matched by multiple rules
  2. if (myRuleSet.isOverlapping()) {
  3. System.out.println(myRuleSet.overlaps())
  4. }
  1. // Checks if the given constraint is satisfied in all cases
  2. // Constraint: motion > 0.1 => state != OFF
  3. Expression constraint = eb.Implies(
  4. eb.Greater(motion, context.buildValue.real(0.1)),
  5. eb.NotEqual(state, context.buildValue.enumeration("OFF"))
  6. );
  7. if (!myRuleSet.satisfiesConstraint(constraint)) {
  8. System.out.println(myRuleSet.constraintCounterExamples(constraint))
  9. }