2. Tutorial

2.1. The Water Sprinkler Example

Our first example demonstrates how to represent a Bayesian Network using the O3PRM language. The closest notion of a BN in O3PRM is a class. A class is composed of attributes and relations, we will skip relations for the moment and focus on attributes. Attributes are defined by a set of parents, a CPT and a type.

The attribute’s set of parents and CPT are similar to a node’s parents and CPT in a BN. However, there is no equivalent of an attribute’s type in a BN. Types are used to group random variables with identical domains. The O3PRM language comes with a predefined type for Boolean variables. Boolean Types are declared with the boolean keyword and have the domain false, true. Note that order of the values in the type’s domain is important as it determines the meaning of the values in the CPTs.

The following example implements the Water Sprinkler Bayesian Network in the O3PRM language. Each node is represented as an attribute of the WaterSprinkler class and all the attributes have the boolean type.

class WaterSprinkler {
  boolean cloudy {
    [ 0.5, // false
      0.5] // true
  };

  boolean sprinkler dependson cloudy {
  // false, true => cloudy  
    [  0.5,  0.9,  // sprinkler == false
       0.5,  0.1] // sprinkler == true
  };

  boolean rain dependson cloudy {
  // false, true => cloudy  
    [  0.8,  0.2,  // rain == false
       0.2,  0.8] // rain == true
  };

  boolean wet_grass dependson rain, sprinkler {
  //                   wet_grass
  // rain, sprinkler| false, true
        *,         *:  0.1, 0.9;
    false,     false: 1.00, 0.00;
     true,      true: 0.01, 0.99;
  };
}

This example shows how to define the set of parents of an attribute using the keyword dependson. It also provides two different ways to define an attribute’s CPT: either using a tabular declaration (inside square brackets) or a rule-based declaration (see the wet_grass CPT).

We strongly recommend formating your CPTs definitions as above to help writing and reading them. See the formatings we used in this tutorial’s examples. As you can see, in CPT’s tabular declarations, the O3PRM language expects that each column sums to one. In other words, this means that each row of the CPT represents one value of the attribute at the left of the dependson keyword. The size of the CPT is the product of the number of rows (i.e., the domain size of the attribute) by the number of columns (i.e., the domain size of the Cartesian product of the attribute’s parents).

  boolean sprinkler dependson cloudy {
  // false, true => cloudy  
    [  0.5,  0.9,  // sprinkler == false
       0.5,  0.1] // sprinkler == true
  };

Here, the first value is the probability of P(sprinkler==false|cloudy==false), the second value is P(sprinkler==false|cloudy==true), the third P(sprinkler==true|cloudy==false) and the fourth P(sprinkler==true|cloudy==true). You can easily see that each column sums to one.

You can also use rules to declare an attribute’s CPT. We recommend to use this syntax when dealing with large CPTs. Each line defines the attribute’s probability for a given value of its parents’ set. You can also use the wildcard * to define the probability for all values of the corresponding parent.

  boolean wet_grass dependson rain, sprinkler {
  //                   wet_grass
  // rain, sprinkler| false, true
        *,         *:  0.1, 0.9;
    false,     false: 1.00, 0.00;
     true,      true: 0.01, 0.99;
  };

Here, the first line defines the distribution for all possible value of wet_grass parents, the following lines overwrite this default distribution defining the probabilities P(wet_grass|rain==false,sprinkler==false) and P(wet_grass|rain==true,sprinkler==true). In rule-based declarations, each line must therefore sum to one.

Similarly to any object-oriented progamming language, to use a class, you need to instantiate it, i.e., to create instances of this class. In O3PRM, this is realized in a so-called system. The following shows how to do it.

system MyFirstSystem {
  WaterSprinkler water_sprinkler;
}

Since we have a single class that defines on its own a probability distribution, we simply need to instantiate it once. But it is possible to create several instances (see the next section) in order to create a world with several gardens and sprinklers.

Finally, we need to define a query using the O3PRM language, to do so you will need to use a different file with the .o3prmr extension. With the query language, you can import systems, set observations and query marginal probabilities of instances attributes.

import myFirstPRM;

request MyFirstRequest {
  ? myFirstPRM.MyFirstSystem.water_sprinkler.sprinkler;

  myFirstPRM.MyFirstSystem.water_sprinkler.cloudy = true;
  
  ? myFirstPRM.MyFirstSystem.water_sprinkler.sprinkler;
}

The import instruction is mandatory in a request file in order to access the system to query. You simply need to type the name of the file containing the system you want to import, excluding the .o3prm extension. You will need to have your .o3prm and .o3prmr files in the same folder for this to work. You can check Section 2.2 for a better understanding of how import works and how to structure your O3PRM project. For this example, we created the following structure:

MyProject /
  | myFirstPRM.o3prm // The water sprinkler class and system
  | myFirstQuery.o3prmr // The queries for the water sprinkler system

Instructions starting with a ? are queries. They tell the interpreter to compute the marginal probability of the following attribute. To define which attribute to query, you need to write the file’s name, system’s name, the instance’s and finally the attribute’s name. Remember that the O3PRM language is case sensitive, event if your operating system is not.

The second instruction assigns an observation (also called evidence) to an attribute. Observations change the beliefs of each node (at least, in most cases). The order among the instructions is important in a request: unlike the first ? query, the second ? will take into account the observation and will provide a different marginal distribution for the sprinkler attribute. Using the O3PRM interpreter shipped with aGrUM, prm_run, you would get the following output:

myFirstPRM.MyFirstSystem.water_sprinkler.sprinkler:
        false: 0.7
        true: 0.3
myFirstPRM.MyFirstSystem.water_sprinkler.sprinkler:
        false: 0.9

2.2. The Printer Example

In the previous example, we looked at how to model classic Bayesian Networks using the O3PRM language. In this example, we will look into the main features of Probabilistic Relational Models: typing, reference slots and slots chains. These three features, with inheritance, are what differentiate PRMs from BNs and will help modeling large scale probabilistic graphical models. In this example we will not be using inheritance and focus on attribute’s type, reference slots, slot chains and aggregators.

A good way to visualize a PRM is to show its class dependency graph.

../_images/printer.svg

In this graph, dashed nodes, normal nodes and square nodes represent reference slots, attributes and classes respectively. Arcs either represent probabilistic dependency relations (when solid) or relations (when dashed). We can interpret a class dependency graph as a system where each class is instantiated only once. As a consequence, it can be interpreted as a representation of a Bayesian Network (beware that not all dependency classes represent valid systems because some may involve directed cycles, which is forbidden in Bayes nets since they define incorrect joint probability distributions).

The above example aims to model a simple printer diagnosis problem: we have computers and printers in rooms, each room includes one power supply used to power the equipments it stores. We will start by defining the types used in this example and the PowerSupply class.

type t_state labels (OK, NOK);

class PowerSupply {
  t_state powState {
    [0.99, // OK
     0.01] // NOK
  };
}

The first line declares a categorical type with two possible outcomes: OK and NOK. The PowerSupply class is rather simple: it defines a single attribute powState representing the power supply state. The next class introduces reference slots.

class Room {
    PowerSupply power;
}

Reference slots are class members whose type is another class (it can also be an interface). The key idea behind reference slots is that some attributes may have parents belonging to other classes. Reference slots enable to reach these parents by establishing a link between these other classes and the class of the attribute. The Room class only defines a reference slot, pointing towards the PowerSupply class. As a result, the attributes in the PowerSupply class are accessible in the Room class. The next class defines a printer.

class Printer {
    Room room;

    boolean hasPaper {
      [ 0.1,  // false
        0.9 ] // true
    };

    boolean hasInk {
      [ 0.3,  // false
        0.7 ] // true
    };

    t_state equipState dependson
      room.power.powState, hasPaper, hasInk { // OK,  NOK
                        *,        *,     *:    0.00, 1.00;
                       OK,     true,  true:    0.80, 0.20;
    };
}

The Printer class defines a reference slot toward the Room class and three attributes: hasPaper, hasInk and equipState. The first two attributes are self explanatory, the third represents the printer’s state. The equipState attribute has three parents, one of which is the attribute powState of the PowerSupply class. Since powState is not defined in the Printer class, we must use a slot chain to tell the O3PRM interpreter where to find it. In this case, the slot chain is composed of two reference slots, room of the Printer class and power of the Room class. It ends with attribute powState of the PowerSupply class.

Finally, the last class defines a computer.

class Computer {
    Room room;

    Printer[] printers;

    boolean exists_printer = exists ( [printers.equipState], OK );

    boolean can_print = and([printers.equipState, exists_printer]);
}

The Computer class defines two reference slots, room and printers, and two attributes, exists_printer and can_print. The printers reference slot is different from room as its type is suffixed with []. This means that the reference is complex and that more than one printer can be referenced by the Computer class. If the number of printers reachable was the same for each computer, we could have created as many instances of class Printer as needed and we would not have used keyword []. But what if the number of printers reachable differs from one computer to the other? The keyword [] is made for that purpose. It just indicates that there may be zero, one or several printers reachable. When instantiating Class Computer, each instance will define its own set of printers. When attributes parents are a slot chain with at least one complex reference slot, the attribute’s CPT must be defined using an aggregator (that is designed generically to cope with arbitrary numbers of parents, here of printers). The exists_printer attribute illustrates how to declare such attributes using the exists aggregator. You can also use aggregators with non complex reference slots, as illustrated with attribute can_print.

Aggregators are functions used to generate deterministic CPTs when instantiating classes, i.e., when the exact number of instances referenced by a complex reference slot is known.

2.3. Printers with inheritance

In this example, we will extend the previous printer example with inheritance features of the O3PRM language. Our goal here is to show how you can extend an existing model by using three inheritance tools offered by the O3PRM language: type extensions, class inheritance and interface implementation.

We will first add new types to our model, in order to better represent the semantics of different states each equipment can have.

type t_state extends boolean (
  OK: true,
  NOK: false
);

type t_ink extends t_state (
  NotEmpty: OK,
  Empty: NOK
);

type t_paper extends t_state (
  Ready: OK,
  Jammed: NOK,
  Empty: NOK);

First we changed the t_state type to make it a subtype of the built-in boolean type. This will let us use logic functions such as the and and or aggregators. Type extension syntax is a mapping between the subtype outcomes and the super type ones. Here, we mapped outcome OK with true and outcome NOK with false.

We then declared two subtypes of t_state: t_ink and t_paper. Type t_ink renames the labels of t_state to better represent the semantics of ink cartridges in our example. On the other hand t_paper adds a new outcome Jammed, mapped to the outcome NOK. This helps us distinguish different printer’s failure states: the paper tray can be empty or paper can be jammed. Both states prevent from printing but the action to fix the printer’s state will differ.

class PowerSupply {
  t_state state { 
    ["0.99", // OK
     "0.01"] // NOK
  };
}

class Room {
  PowerSupply power;
}

The first two classes are identical with those of the previous example. We now define the Printer as an interface instead of a class.

interface Printer {
  Room room;
  t_state equipState;
  boolean hasPaper;
  boolean hasInk;
}

Interfaces can be viewed as the abstraction of a class: they are defined by a set of attributes and reference slots but they do not define any probabilistic distribution. Classes can implement interfaces, which constrain them to define all the implemented interface’s elements. We illustrate this with two new classes: BWPrinter and ColorPrinter which both implement the Printer interface.

class BWPrinter implements Printer {
  Room room;

  t_ink hasInk { 
    [0.8, // NotEmpty
     0.2] // Empty
  };
  t_paper hasPaper { 
    [0.7, // Ready
     0.2, // Jammed
     0.1] // Empty
  };
  t_state equipState dependson room.power.state, hasInk, hasPaper {
    //                    OK,  NOK
    *, *, *:             0.0,  1.0;
    OK, NotEmpty, Ready: 1.0,  0.0;
  };
}

class ColorPrinter implements Printer {
    Room room;
    t_ink black   { 
      [0.8, // NotEmpty
       0.2] // Empty
    };
    t_ink magenta { 
      [0.8, // NotEmpty
       0.2] // Empty 
    };
    t_ink yellow  {
      [0.8, // NotEmpty
       0.2] // Empty
    };
    t_ink cyan { 
      [0.8, // NotEmpty
       0.2] // Empty
    };
    boolean hasInk = forall ( [black, magenta, yellow, cyan], NotEmpty );
    t_paper hasPaper {
      [0.7, // Ready
       0.2, // Jammed
       0.1] // Empty
    };
    t_state equipState dependson room.power.state, hasPaper, hasInk, black {
      //                        OK, NOK
      *, *, *, *:             0.00, 1.00;
      *, *, false, NotEmpty:  0.00, 0.00;
      OK, Ready, true, *:     0.99, 0.01;
    };
}

Both BWPrinter and ColorPrinter define all elements in the Printer interface, but with different types. Indeed, in the Printer interface attributes hasPaper and hasInk are both Booleans. In classes BWPrinter and ColorPrinter they are of type t_ink and t_paper respectively. This is called type overloading and is legal because both types are subtypes of t_state, itself being a subtype of boolean. The O3PRM, through the use of cast descendants, ensure that attributes are casted into the proper subtype when used in a CPT.

Finally, class Computer has more attributes and illustrates different usages of the exist and and aggregators. Note that attribute can_print casts its parent equipState into the boolean type.