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.