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:
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.
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.