O3PRM’s documentation

The O3PRM language’s purpose is to model Probabilistic Relational Models (PRMs) using a strong object oriented syntax.

Introduction

Probabilistic Relational Models (PRMs) are a fully object-oriented extension of Bayesian Networks (BNs). Both PRMs and BNs are one of the many Probabilistic Graphical Models. In this introduction, we will offer a very short introduction to both models. For further reading, we suggest the reader to lookup the references in the Bibliography section.

Bayesian Networks

Bayesian Networks represent discrete probability distributions using a directed graph and parameters called Conditional Probability Tables. The nodes of the graph represent discrete random variables and the arcs represent probabilistic dependencies between those variables (to be precise, the lack of an arc between two nodes represents a conditional independence between the corresponding random variables). To each node is assigned the Conditional Probability Tables (CPTs) of the node given its parents in the graph, and the joint probability distribution over all the nodes/variables in the graph is equal to the product of all the CPTs.

_images/water_sprinkler.png

The Water Sprinkler is a classic example of a Bayesian Networks. Its purpose is to infer whether the grass in a garden is wet because of the rain or because the house’s owner forgot to turn off the water sprinkler.

The probability distribution is defined over four Boolean random variables: Cloudy, Sprinkler, Rain and Wet Grass. There are also four CPTs representing the following conditional distributions: P(Cloudy), P(Sprinkler|Cloudy), P(Rain|Cloudy) and P(Wet Grass | Sprinkler, Rain).

We won’t go further in our introduction of Bayesian Network and if you never heard of Bayesian Networks, you might need to first familiarize with them before using O3PRM. A good place to start is the pyAgrum tutorials that you can find at http://agrum.gitlab.io.

Probabilistic Relational Models

Bayesian Networks suffer from the same issue that had early programming languages: the larger the network, the harder it is to create it and to maintain it. Naturally, the BN community looked at the solutions provided by the language programming community and found several paradigms to extend BNs: first order logics, entity-relation models, object oriented paradigms, etc. There is no current consensus on which BN extension is the best as each one offers specific features lacking in the other paradigms.

Probabilistic Relational Models are one of several Object Oriented extensions of Bayesian Networks. They offer a good implementation of the Object Oriented paradigm while not diverging too much from the Bayesian Networks framework. Their precise definition is provided in subsequent sections of this documentation. But, for the moment, just keep in mind the fact that they are an object-oriented extension of BNs.

Implementation

The aGrUM framework offers an implementation of the O3PRM language. You can either directly use aGrUM, which is written in C++, its Python wrapper pyAgrum or the prm_run application shipped with aGrUM’s source code. All resources and installation instructions can be found at http://agrum.gitlab.io.

Tutorial

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

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.

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.

O3PRM project structure

The O3PRM language is made of compilation units that are placed into modules. It is possible to encode in a single file an entire project but it is not recommended. A package matches a specific file in which we we can find at least one compilation unit. The following is a sample O3PRM project:

fr \
  | lip6 \
  |   | printers \
  |   |   | types.o3prm // types definition
  |   |   | powersupply.o3prm // class PowerSupply definition
  |   |   | equipment.o3prm // class Equipment definition
  |   |   | room.o3prm // class Room definition
  |   |   | computer.o3prm // class Computer definition
  |   |   | printer.o3prm // class Printer definition
  |   |   | example.o3prm // system Example definition
  |   |   | query.o3prm // request query definition

File extensions must be used as an indicator of the file’s compilation unit nature. The following extensions are allowed: .o3prmr for queries, o3prm for everything else (types, classes, interfaces and systems). It is good practice to name a file with the compilation unit it holds, for example in computer.o3prm should contain the definition for the Computer class. If the file contains several compilation units, you should name the file according to the unit with the highest order. For example, if you define types, classes and a system in one file, you should use the system’s name.

Compilation units

There exist four different compilation units in the O3PRM language. A compilation unit declares a specific element in the modeling process and can either be: an attribute’s type, a class, an interface, a system or even a query. Each compilation unit can start with a header. Headers are the locations where you declare import statements.

<o3prm> ::= [<header>] <compilation_unit> [(<compilation_unit>)]
<header> ::= <import>
<compilation_unit> ::= <type_unit>      |
                       <class_unit>     |
                       <interface_unit> |
                       <system_unit>

Header syntax

Each compilation unit is declared in a module defined by the path from the project’s root to the file’s name in which it is declared. Directory separators are represented using dots. For example the file fr/lip6/printers/types.o3prm defines the namespace fr.lip6.printers.types.

Namespaces can be used to import all of the compilation units defined in them, since many compilation units will need units defined in other files. In such cases, we say that a given compilation unit has dependencies which are declared using the import keyword. The syntax is:

<import>  ::= import <path> ";"
<path>    ::= <word> [ ( "." <word> ) ]

A <word> is an alphanumerical identifier. You can find its proper definition in the full BNF.

An example:

import fr.lip6.printers.computer;

The O3PRM interpreter should use an environment variable to know which directory to lookup for resolving compilations units. You should check your O3PRM interpreter to know which environment variable is used.

A compilation unit can be accessed through two different names:

  • Its simple name: the name given to it in the file where it is declared (for example Computer).
  • Its full name: defined by its namespace and its simple name (for example fr.lip6.printers.Computer).

In most cases, referring to a compilation unit using its simple name will work, you will need full names only to prevent name collisions/ambiguities. Name collisions happen when two compilation units have the same name but are declared in different namespaces. In such situations, the O3PRM interpreter cannot resolve the name and will raise an interpretation error.

Note that no matter how you refer to a compilation unit (either by its simple name or full name) you must always import it using the package complete name.

Finally, note that compilation units are case sensitive regardless of the operating system.

Type Declaration

The O3PRM language offers three kinds of discrete random variables: categorical (labelized), integer ranged variables and real-valued discretized variables. Since domains can be shared among attributes in a PRM, the random variables’ domains should be declared in a separate compilation unit called a type.

All types declarations start with the keyword type followed by the type’s name. The variable’s domain is enclosed inside parentheses.

Here is the full entry for types in the O3PRM BNF:

<type_unit>      ::= type <word> <type_body>
<type_body>      ::= <basic_type> | <subtype>
<basic_type>     ::= <labelized_type> | <integer_type> | <real_type>
<labelized_type> ::= labels "(" <word> ( "," <word> )+ )"
<integer_type>   ::= int "(" <integer> "," <integer> ")"
<real_type>      ::= real "(" <float> "," <float> ( "," <float> )+ ")"
<subtype>        ::= extends <path> "(" <word> ":" <word> ( "," <word>)+ ")"

Categorical Types

Categorical types are used to model categorical random variables, such as Booleans or colors (red, green and blue for example). The syntax is straightforward:

type t_state labels (OK, NOK);
type t_colors labels (red, green, blue);

The boolean type

The O3PRM comes with a single built-in type for Boolean random variables. The type is defined as follows:

type boolean labels (false, true);

Integer Types

Integer types are used to model ranges between two integer values. The domain includes all integers between the lower bound and the upper bound specified.

type power int (0,9);

Real Types

Real types are used to model discretized continuous variables. There must be at least three values and each interval is defined as ]x, y]. For example, the following declaration:

type angle real (0, 90, 180);

defines the 2-valued discrete random variable defined over ]0;90] and ]90;180].

Class Declaration

Classes are the placeholder of attributes and references in the O3PRM language. You can see them as fragments of Bayesian Networks.

<class>         ::= class <word> [ extends <word> ] "{" <class_elt>* "}"
<class_elt>     ::= <reference_slot> | <attribute> | <parameter>

Classes contain three different elements: attributes, reference slots and parameters.

Attributes

Attributes are a generic definition of random variables. They are not random variables: only their instances after instantiating the class are random variables. Attributes are defined by a type, a name, a set of parents and a CPT.

<attribute>      ::= <attribute_type> <attribute_name> <attribute_cpt> ";"
<attribute_type> ::= <anonymous_type> | <word>
<anonymous_type> ::= <labelized_type> | <integer_type> | <real_type>
<attribute_name> ::= <word> [ <dependency> ]
<attribute_cpt>  ::= ( <CPT> | <aggregator> )
<dependency>     ::= dependson <parent> ( "," <parent> )*

<CPT>      ::= "{" ( <raw_CPT> | <rule_CPT> ) "}"
<raw_CPT>  ::= "[" <cpt_cell> ( "," <cpt_cell> )+ "]"
<rule_CPT> ::= ( <word> ("," <word>)* ":" <cpt_cell> ";" )+
<cpt_cell> ::= <float> | """ <formula> """
<formula>  ::= <real> | <function> | <formula> <operator> <formula>
<function> ::= <function_name> "(" <formula> ")"
<function_name> ::= exp | log | ln | pow | sqrt

Tabular Declaration

When declaring a CPT in tabular form, the probability values for all the possible values of the attribute and its parents must be specified. In addition, the order in which the values are specified is important. The O3PRM language uses a declaration by column, i.e., in each column of the CPT, the values must sum to one because the rows of the CPT correspond the domain of the attribute for which the CPT is specified (please, note that the terms column and row are used loosely since the table is only one-dimensional). The following example illustrates the reason why we say that the columns sum to one:

class Example {
  boolean Y {[0.2, 0.8]};
  boolean Z {[0.5, 0.5]};
  boolean X dependson Y, Z {[
  //     Y==false      |        Y==true
  // Z==false | Z=true | Z==false | Z==true
       0.2,     0.3,       0.7,     0.9,    // X == false
       0.8,     0.7,       0.3,     0.1     // X == true
  ]};
}

In this example, we can see that the first value is the probability P(X=false|Y=false,Z=false), the second value P(X=false|Y=false,Z=true), the third P(X=false|Y=true,Z=false) and so on.

Rule-based CPT declaration

Rule-based declarations exploit the * wildcard symbol to reduce the number of parameters needed to specify the CPT.

class Example {
  boolean Y {[0.2, 0.8]};
  boolean Z {[0.5, 0.5]};
  boolean X dependson Y, Z {
    // Y,    Z:   X=false, X=true
       *,  false:  1.0,     0.0;
      true, true:  0.01,    0.99;
     false, true:  0.3,     0.7;
};

The first line uses the wildcard * for Y’s outcomes. This defines in one line the set of probabilities P(X|Y=y,Z=false) for y in {false,true}. There is no limit in the number of rules and, when two rules overlap, the last one takes precedence.

Reference Slots

Reference slots can either be simple (defining a one to one relation, or a unary relation) or complex (defining a one to N relation, or n-ary relation).

<reference_slot> ::= [internal] <word> [ "[" "]" ] <word> ";"

Simple Reference Slots

Simple reference slots are used to define a one to one relation between two classes. They are used in slot chains to add parents from other classes to an attribute.

class SomeClass {
  boolean Y {[0.2, 0.8]};
  boolean Z {[0.5, 0.5]};
}

class AnotherClass {
  SomeClass myClass;
  boolean X dependson myClass.Y, myClass.Z {
    // Y,    Z:   X=false, X=true
       *,  false:  1.0,     0.0;
      true, true:  0.01,    0.99;
     false, true:  0.3,     0.7;
};

Class AnotherClass defines the reference slot myClass of type SomeClass and its attribute X uses two slot chains, myClass.Y and myClass.Z, to reference its parents.

Note that if reference cycles are allowed, you must be careful to not create cycles between attributes. Indeed, if there exists a cycle between two attributes, this implies that the CPT of the first one is conditional given the second attribute and the CPT of the second attribute is conditional given the first attribute. As a consequence, it is not possible to define a joint probability distribution using these two CPTs. The problem is exactly the same for regular Bayesian Networks and it explains why directed cycles are forbidden in BNs.

Complex Reference Slots

Complex reference slots are used to define n-ary relations between classes. They can be used in slot chains when declaring aggregators, special attributes described in section 5.

class SomeClass {
  boolean Y {[0.2, 0.8]};
  boolean Z {[0.5, 0.5]};
}

class AnotherClass {
  SomeClass[] myClass;
  boolean X = or([myClass.Y, myClass.Z]);
}

To declare a complex reference slots we use [] as a suffix to the reference slot type. In the above example, we declared an or aggregator referencing attributes Y and Z accessed though the complex reference myClass. Since myClass is a complex reference slot, we will be able to reference more than instance of SomeClass. Since we do not know how many parents there is for attribute X, we need to use an aggregator to generate the attribute’s CPT when instantiating the class containing the attribute.

Parameters

Parameters are used to define constants used in the CPT generation. For example, if we define two parameters such as lambda and t, we will be able to write the following formula in a CPT: 1-exp(-lambda*t).

class ClassWithParams {
  param real lambda default 0.003;
  param int t default 8760;
  boolean state {
    ["exp(-lambda*t)", "1-exp(-lambda*t)" ]
  };
}

The default keyword is mandatory to provide a default value to parameters, since they can be changed when declaring an instance of a class with parameters.

Interface Declaration

Interfaces are abstract classes used to impose constraints on classes. Just like classes, interfaces have attributes and reference slots (but no CPT). Classes that implement interfaces must necessarily contain the attributes and references slots specified in the interfaces. This mechanism is particularly effective to easily define relations between classes as well as multiple inheritance. For instance, interfaces are the key to define dynamic Bayesian networks (2TBN) using PRMs. Actually, a 2TBN contains one Bayesian network fragment for time slice t=0 and another fragment for transitions between time slice t and t+1, for all t’s. Using the 2TBN means copy/pasting the first fragment once, followed by (T-1) copy/pastes of the second fragment, hence resulting in the creation of a Bayesian network over time slices 0 to T. In the O3PRM language, we would naturally consider a class B0 for the first fragment and a class Bt for the second one. As Bt models the transition between time slices t0 and t1, some attributes of Bt should have parents in B0 (otherwise the transitions never depend on the past, which makes the temporal nature of the dynamic Bayesian network quite useless). But Bt also models the transition between time slices t1 and t2. As a result, the parents that were located in B0 should now be in Bt. So, at first sight, this prevents specifying dynamic Bayesian networks using the O3PRM language. Fortunately, interfaces enable this specification. Actually, in Bt, for the transition between t0 and t1, it is useless to know the value of the CPT of the parents belonging to B0, what is important is just to know which attributes of B0 are needed as parents in Bt. Similarly, for the transition between time slices t1 and t2, the only information needed in Bt is to know which attributes of t1 are used as parents in attributes of t2. As a consequence, if these parent attributes are specified in an interface, and if B0 and Bt implement this interface, both B0 and Bt are constrained to include these parent attributes and we just need to specify that, in Bt, the parents of the attributes at time t+1 are those contained in the interface. Since the interface is the same for B0 and Bt, when instantiating these classes, the O3PRM interpreter will select appropriately the parents. Here is the syntax to specify an interface:

<interface>      ::= interface <word> [ extends <path> ] "{" <interface_elt> "}"
<interface_elt>  ::= <reference_slot> | <abstract_attr>
<reference_slot> ::= [internal] <word> [ "[" "]" ] <word> ";"
<abstract_attr>  ::= <word> <word> ";"

Interface attributes are called abstract attributes because they do not have any CPT.

interface MyInterface {
  boolean state;
}

class MyClass {
  MyInterface iface;
  boolean X dependson iface.state {
    //iface.state==false | iface.state==true
    [                0.2,                0.7, // X==false
                     0.8,                0.3] // X==true
  };
}

Interfaces can be used as reference slots types and are useful for defining recursive relations (see the dynamic Bayesian network example described above). Note the keyword implements in the example below used to indicate that a class implements an interface, i.e., that it declares all the latter’s attributes and reference slots. Here, Base and Step correspond to classes B0 and Bt mentioned in the dynamic Bayesian network example respectively. Note that attribute state of Step depends on the attribute state of Interface Iface. As a consequence, when instantiating, previous.state can be either the state attribute of Class Base or that of Class Step.

interface Iface {
  boolean state;
}

class Base implements Iface {
  boolean state {[ 0.2, 0.8 ]};
}

class Step implements Iface {
  Iface previous;
  boolean state dependson previous.state {
    false: 0.9, 0.1; // P(state|previous.state==false)
    true:  0.2, 0.8; // P(state|previous.state==true)
  };
}

System DynamicO3PRM {
  Base base;
  Step step_1;
  step_1.previous = base;
  Step step_2;
  step_2.previous = step_1;
  Step step_3;
  step_3.previous = step_2;
  // ...
}

Functions

Functions are used as placeholders for specific CPTs of classes attributes. They replace the CPT declaration by a specific syntax depending on the type of function used. The first type is the set of functions called aggregators. These functions are used to quantify the information stored in multiple reference slots. The second kind contains deterministic functions and the third probabilistic functions. The last two kinds of functions are not part of the O3PRM specification and are implementation specific. All functions share the same syntax:

<aggregator> ::= ( "=" | "~" ) <word> "(" <parents>, <args> ")"
<parents>    ::= ( <parent> | "[" <parent> (, <parent> )* "]" )
<args>       ::= <word> ( "," <word> )*

The use of = is reserved for deterministic functions and ~ for probabilistic functions. There are only four built-in functions in the O3PRM language that are deterministic functions called aggregators. There are five built-in aggregators in the O3PRM language: min, max, exists, forall and count. Other deterministic functions such as median and amplitude are implemented in aGrUM but they can be implemented in different ways, preventing us from adding them to the O3PRM specification.

Deterministic Functions

The min and max functions require a single parameter: a list of slot chains pointing to attributes. The attributes must all be of the same type or share some common supertype. If the common type is not an int, then the type’s declaration order is used to compute the min and max values.

class Die {
  type int (1, 6) result {["1/6", "1/6", "1/6", "1/6", "1/6", "1/6"]};
}

class GameOfDice {
  Die[] dice;
  type int (1, 6) snake_eyes = min( dice.result );  
  type int (1, 6) bingo = max( [ dice.result ] );  
}

If there is only one element in the list of slot chains the [] are optional.

The exists and forall require two parameters: a list of slot chains and a value. As for min and max, all attributes referenced in the slot chains list must share a common type or supertype. The value must be a valid value of that common supertype. exists and forall attribute type must always be a boolean.

class BWPrinter {
  boolean black   { [ 0.2, 0.8 ] };
}

class ColorPrinter {
  boolean magenta { [ 0.8, 0.2 ] };
  boolean cyan    { [ 0.8, 0.2 ] };
  boolean yellow  { [ 0.8, 0.2 ] };
  boolean black   { [ 0.8, 0.2 ] };
}

class PrinterMonitor {
  BWPrinter[] bw;
  ColorPrinter[] color;

  boolean has_magenta = exists ([color.magenta], true);
  boolean has_cyan = exists ([color.cyan], true);
  boolean has_yellow = exists ([color.yellow], true);
  boolean color = forall([color.black, color.magenta, color.cyan, color.yellow], true);
  boolean black = exists( [ bw.black, color.black ] };
}

The count aggregator counts how many times a given outcome occurred. Its type must be of the form type int (0, N), where N is a positive integer. The outcome N must be interpreted as “the outcome occurred at least N times”.

class Die {
  type int (1, 6) result {["1/6", "1/6", "1/6", "1/6", "1/6", "1/6"]};
}

class GameOfDice {
  Die[] dice;
  type int (0, 4) four_six = count( dice.result, 6 );  
}

Probabilistic Functions

Instead of generating CPTs filled with 0 and 1, like deterministic functions, probabilistic functions return conditional distributions following a specific rule. A classic probabilistic function is the noisy-or, which is implemented in aGrUM as shown below:

class NoisyOr {
  SomeIface iface;
  SomeIface jface;
  boolean state ~ noisy_or([iface.state, jface.state], [0.2, 0.1], 0.4);
}

As for deterministic functions, the first parameter must be a list of parents. For the noisy-or, the next parameter is a list of weights and the third the noise. These functions are not part of the O3PRM specification and you should check your interpreter documentation for their proper syntax.

Inheritance

Inheritance is a key aspect of the O3PRM language. O3PRM offers four different inheritance mechanisms, all with a specific task. Type inheritance allows to create specialization among random variables’ domains. Coupled with type casting, it can be used to model complex problems. Class and interface inheritances offer a more traditional inheritance feature. However its implementation in the O3PRM language adds a lot of expressiveness to Probabilistic Relational Models. Finally, interface implementation is how we implemented multiple inheritance.

Type Inheritance

Subtypes are used to model a is a relation between types. They are declared using the extends keyword. You can only subtype categorical types.

type t_state labels (OK, NOK);
type t_degraded extends t_state ( OK: OK, DYSFONCTION: NOK, DEGRADED: NOK);

Here we declared the type t_degraded as a subtype of t_state. The mapping notation used inside parentheses indicates how to interpret each of t_degraded outcomes as a random variable of type t_state.

Interface Inheritance

An interface can extend another one, using the keyword extends. By doing so, the sub interface inherits all of its super interface attributes and references.

interface SomeIface {}

interface SuperIface {
  SomeIface myRef;
  t_state state;
}

interface SubIface extends SuperIface {
  // No need to declare myRef and state:
  // They are inherited from SuperIface.
}

Reference Overloading

When you declare a sub interface, you can overload inherited reference slots. To do so, the new reference slot type must be a sub class or sub interface of the reference slot type in the super interface.

interface SomeIface {}

interface SomeOtherIface extends SomeIface {
  boolean state;
}

interface SuperIface {
  SomeIface myRef;
  t_state state;
}

interface SubIface extends SuperIface {
  // myRef is overloaded with the sub type SomeOtherIface
  SomeOtherIface myRef;
}

Attribute Overloading

As for reference overloading, you can overload inherited attributes with a subtype of the attribute types in the super interface.

interface SuperIface {
  SomeIface myRef;
  t_state state;
}

interface SubIface extends SuperIface {
  // state is overloaded with t_state subtype t_degraded
  t_degraded state;
}

Class Inheritance

Class inheritance works the same way as inheritance for interfaces with the additional possibility to overload an inherited attribute’s CPT.

Attribute CPT Overloading

To overload an inherited attribute’s CPT, you simply need to declare an attribute with a compatible type.

class SuperClass {
  boolean state { [ 0.5, 0.5 ] };
}

class SubClass {
  boolean state { [ 0.2, 0.8 ] };
}

Multiple Inheritance

Classes can implement interfaces using the keyword implements. When a class implements an interface, it must declare all of the interface’s attributes and reference slots. If the class implements several interfaces, then it must declare all the attributes and reference slots of all its interfaces.

interface MyIface {
  boolean state;
}

interface MyOhterIface {
  MyIface aIface;
  boolean working;
}

class MyClass implements MyIface, MyOtherIface {
  MyIface aIface;
  boolean state {[0.2, 0.8]};
  boolean working dependson state {
    [0.3, 0.6,
     0.7, 0.4]
  };
}

Note that, if a class implements a set of interfaces, then all of its subclasses also implement the same set of interfaces.

Casting and cast descendants

Casting and cast descendants are how the O3PRM language handles attribute type overloading and probabilistic dependencies. Attributes types and CPTs are tightly coupled: the size of a CPT is the product of the domain sizes of its attribute’s type and its parents types. The following example will help us illustrate why we need casting and casting descendants:

type t_state labels(OK, NOK);
type t_degraded extends t_state(OK: OK, degraded: NOK, NOK: NOK);

interface Pump {
  t_state state;
}

class WaterTank {
  Pump myPump;

  boolean overflow dependson myPump.state {
  // OK | NOK => myPump.state
  [ 0.99, 0.25, // overflow == false
    0.01, 0.75] // overflow == true
  };
}

// Centrifugal Water Pump
class CWPump implements Pump {
  t_degraded state {
    [ 0.80, // OK
      0.19, // degraded
      0.01] // NOK
  };
}

system MyPumpSystem {
  WaterTank tank;
  CWPump pump;
  tank.myPump = pump;
}

In this example, we model a water tank overflow problem. We have an interface describing pumps, a class representing a water tank and an implementation of interface Pump for a centrifugal water pump.

If you look at class WaterTank you will notice that its attribute overflow depends on Pump attribute state, which is of type t_state.

However, in system MyPumpSystem, the reference myPump of the instance tank of Class WaterTank is assigned to an instance of class CWPump. Since we overloaded the Pump.state type by t_state subtype t_degraded, the CPT definition of attribute WaterTank.overflow should be incompatible.

This is not the case here because a cast descendant of attribute CWPump.state is automatically added to the class CWPump:

t_state state dependons (t_degraded)state {
  // OK, degraded, NOK => (t_degraded)state
  [ 1.0,      0.0, 0.0, // OK
    0.0,      1.0, 1.0] // NOK
};

This cast descendant is of the expected type and preserves WaterTank.overflow CPT’s compatibility.

Attributes added automatically are called cast descendants and can be accessed using the casting notion:

<parent>  ::= [ "(" <path> ")"] <path>

System Declaration

A system is declared as follows:

<system>      ::= system <word> "{" <system_elt>* "}"
<system_elt>  ::= <instance> | <assignment>

The first word is the system’s name. A system is composed of instance declarations and assignments. Assignments are used to assign an instance to an instance’s reference slot. The following illustrates a system declaration:

system name {
  // body
}

Instance declaration

The syntax to declare an instance in a system is:

<instance>  ::= <path> [ "[" digit* "]" ] <word> ";"

The first word is the instance’s class name and the second is the instance’s name. For example, if we have a class A we could declare the following instance:

A an_instance;

We may want to declare arrays of instances. To do so we need to add [n] as a suffix to the instance’s type, where n is the number of instances that the array should contain. if n = 0 then we can simply write [].

// An empty array of instances
A_class[] a_name;
// A array of 5 instances
A_class[5] another_name;

You can also specify values for parameters when instantiating a class (see Section 5.3 on how to parameterize CPTs). The syntax to do so is:

<instance>          ::= <path> <word> "(" <parameters> ")" ";"
<parameters>        ::= instanceParameter ("," instanceParameter)*
<instanceParameter> ::= <word>"="(<integer>|<float>)

An example:

// We declare an instance of A_class where a_param equals 0.001
A_class a_name(a_param=0.001);

Assignment

<assignment> ::= <path> += <word> ";" |
                 <path> = <word> ";"

It is possible to add instances into an array, using the += operator:

// Declaring some instances
A_class x;
A_class y;
A_class z;
// An empty array of instances
A_class[] array;
// Adding instances to array
array += x;
array += y;
array += z;

Reference assignment is done using the = operator:

class A {
  boolean X {[0.5, 0.5]};
}

class B {
  A myRef;
}

system S {
  // declaring two instances
  A a;
  B b;
  // Assigning b's reference to a
  b.myRef = a;
}

In the case of multiple references, we can either use the = to assign a whole array or the += operator to add instances one by one:

class A {
  boolean X {[0.5, 0.5]};
}

class B {
  A myRef[];
}

system S1 {
  // declaring an array of five instances of A.
  A[5] a;
  // declaring an instance of B
  B b;
  // Assigning b's reference to a
  b.myRef = a;
}
// An alternative declaration
system S2 {
  // declaring three instances of A
  A a1;
  A a2;
  A a3;
  // declaring an instance of B
  B b;
  // Assigning b's reference to a
  b.myRef += a1;
  b.myRef += a2;
  b.myRef += a3;
}

Query unit declaration

A query unit is defined using the keyword request. Its syntax is the following:

<query_unit>  ::= request <word> "{" <query_elt>* "}"
<query_elt>   ::= <observation> | <query>
<observation> ::= ( <path> = <word> ) |
                  ( unobserved <path> )
                  ";"
<query> ::= "?" <path> ";"

The first word is the query’s name. In a query unit we can alternate between observations and queries. An observation, also called an evidence, allows to specify the value that we observe for a given random variable (e.g., we observe on our thermometer that variable temperature is equal to 20 degrees Celsius). Evidence are assigned to their corresponding random variables using the = operator. A query over random variable X asks to infer the probability P(X|e) where e represents the set of all the evidence specified so far in the request unit. This is done using the ? operator. The unobserve keyword can be used to remove evidence inside the request unit.

request myQuery {
	// adding evidence
	mySystem.anObject.aVariable = true;
	mySystem.anotherObject.aVariable = 3;
	mySystem.anotherObject.anotherVariable = false;
	// asking to infer some probability value given evidence
	? mySystem.anObject.anotherVariable;
	// remove evidence over an attribute
	unobserve mySystem.anObject.aVariable;
        ? mySystem.anObject.anotherVariable;
}

For instance, in the above example, the first query over random variable mySystem.anObject.anotherVariable returns the posterior of the variable given the three evidence entered into the system. The second query returns the posterior of the same variable given only the last two evidence entered, the first one being invalidated by the unobserve instruction.

O3PRM BNF

O3PRM Language Specification

<o3prm> ::= [<header>] <compilation_unit> [(<compilation_unit>)]

<header> ::= <import>
<import> ::= import <path> ";"

<compilation_unit> ::= <type_unit>   |
                       <class_unit>  |
                       <system_unit>

<type_unit>      ::= type <word> <type_body>
<type_body>      ::= <basic_type> | <subtype>
<basic_type>     ::= <labelized_type> | <integer_type> | <real_type>
<labelized_type> ::= labels "(" <word> ( "," <word> )+ )"
<integer_type>   ::= int "(" <integer> "," <integer> ")"
<real_type>      ::= real "(" <float> "," <float> ( "," <float> )+ ")"
<subtype>        ::= extends <path> "(" <word> ":" <word> ( "," <word>)+ ")"

<class_unit> ::= <class> | <interface>
<class>      ::= class <word> [ extends <path> ] "{" <class_elt>* "}"
<class_elt>  ::= <reference_slot> | <attribute> | <parameter>

<interface>      ::= interface <word> [ extends <path> ] "{" <interface_elt> "}"
<interface_elt>  ::= <reference_slot> | <abstract_attr>

<reference_slot> ::= [internal] <word> [ "[" "]" ] <word> ";"

<attribute>      ::= <attribute_type> <attribute_name> <attribute_cpt> ";"
<attribute_type> ::= <anonymous_type> | <word>
<anonymous_type> ::= <labelized_type> | <integer_type> | <real_type>
<attribute_name> ::= <word> [ <dependency> ]
<attribute_cpt>  ::= ( <CPT> | <aggregator> )
<dependency>     ::= dependson <path> ( "," <path> )*
<abstract_attr>  ::= <word> <word> ";"

<CPT>      ::= "{" ( <raw_CPT> | <rule_CPT> ) "}"
<raw_CPT>  ::= "[" <cpt_cell> ( "," <cpt_cell> )+ "]"
<rule_CPT> ::= ( <word> ("," <word>)* ":" <cpt_cell> ";" )+
<cpt_cell> ::= <float> | """ <formula> """
<formula>  ::= <real> | <function> | <formula> <operator> <formula>
<function> ::= <function_name> "(" <formula> ")"
<function_name> ::= exp | log | ln | pow | sqrt

<parameter>      ::= param ( <int_parameter> | <real_parameter> )
<int_parameter>  ::= "int" <word> default <integer> ";"
<real_parameter> ::= "real" <word> default <float> ";"

<aggregator> ::= ( "=" | "~" ) <word> "(" <parents>, <args> ")"
<parents>    ::= ( <parent> | "[" <parent> (, <parent> )* "]" )
<args>       ::= <word> ( "," <word> )*

<parent>  ::= [ "(" <path> ")"] <path>

<system>     ::= system <word> "{" <system_elt>* "}"
<system_elt> ::= <instance> | <assignment>

<instance>          ::= <path> [ "[" digit* "]" ] <word> ";"
<instance>          ::= <path> <word> "(" <parameters> ")" ";"
<parameters>        ::= instanceParameter ("," instanceParameter)*
<instanceParameter> ::= <word>"="(<integer>|<float>)

<assignment> ::= <path> += <word> ";" |
                 <path> = <word> ";"

<word>    ::= <letter> (<letter> | <digit>)
<letter>  ::= 'A'..'Z' + 'a'..'z'+ '_'
<integer> ::= <digit> <digit>*
<float>   ::= <integer> "." <integer>
<digit>   ::= '0'..'9'
<path>    ::= ["(" ")"] <word> [ ( "." <word> ) ]

O3PRM Query Language Specification

<O3PRM> ::= [<header>] <compilation_unit> [(<compilation_unit>)]

<header> ::= <import>
<import> ::= import <path> ";"

<compilation_unit> ::= <query_unit>

<query_unit>  ::= request <word> "{" <query_elt>* "}"
<query_elt>   ::= <observation> | <query>
<observation> ::= ( <path> = <word> ) |
                  ( unobserved <path> )
                  ";"
<query> ::= "?" <path> ";"

<word>    ::= <letter> (<letter> | <digit>)
<letter>  ::= 'A'..'Z' + 'a'..'z'+ '_'
<integer> ::= <digit> <digit>*
<float>   ::= <integer> "." <integer>
<digit>   ::= '0'..'9'
<path>    ::= <word> [ ( "." <word> ) ]

Examples

The Water Sprinkler

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;
  };
}

system MyFirstSystem {
  WaterSprinkler water_sprinkler;
}

The Printer Example

type t_state labels (OK, NOK);

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

class Room {
    PowerSupply power;
}

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;
    };
}

class Computer {
    Room room;

    Printer[] printers;

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

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

Printers with inheritance

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);

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

class Room {
  PowerSupply power;
}

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

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;
    };
}

class Computer {
    Room room;
    Printer[] printers;
    boolean functional_printer = exists ( printers.equipState, OK );
    boolean degraded_printer = exists ( printers.equipState, Degraded );
    boolean working_printer = exists ( [functional_printer, degraded_printer], true );
    t_state equipState dependson room.power.state {
      //     OK,  NOK
      OK:  0.90, 0.10;
      NOK: 0.00, 1.00;
    };
    boolean can_print = and([working_printer, (boolean)equipState]);
}

Bibliography

J. Pearl. Probabilistic Reasoning in Intelligent Systems: Networks of Plausible Infer ence. Morgan Kaufman, 1988.

Daphne Koller and Avi Pfeffer. Object-oriented bayesian networks. In Proceedings of the 13th Annual Conference on Uncertainty in AI, pages 302–313, 1997.

Daphne Koller and Avi Pfeffer. Probabilistic frame-based systems. In Proceedings of the Fifteenth National Conference on Artificial Intelligence (AAAI-98), pages 580–587, 1998.

Avi Pfeffer. Probabilistic Reasoning for Complex Systems. PhD thesis, Stanford Uni versity, 1999.

Olav Bangsø and Pierre-Henri Wuillemin. Object oriented bayesian networks: A frame work for topdown specification of large bayesian networks and repetitive structures. Technical report, Department of Computer Science, Aalborg University, 2000.

Olav Bangsø and Pierre-Henri Wuillemin. Top-down construction and repetitive struc tures representation in bayesian networks. In Proceedings of the 13th Florida Artifi cial Intelligence Research Society Conference, 2000.

Olav Bangsø. Object Oriented Bayesian Networks. PhD thesis, Aalborg University, March 2004.

Lise Getoor, Nir Friedman, Daphne Koller, Avi Pfeffer, and Ben Taskar. Probabilistic relational models. In L. Getoor and B. Taskar, editors, An Introduction to Statistical Relational Learning. MIT Press, 2007.

D. Koller and N. Friedman. Probabilistic Graphical Models: Principles and Techniques. MIT Press, 2009.

Judea Pearl. Causality. Cambridge University Press, 2009.

Lionel Torti, Pierre-Henri Wuillemin, and Christophe Gonzales. Reinforcing the object oriented aspect of probabilistic relational models. In Teemu Roos Petri Myllymäki and Tommi Jaakkola, editors, Proceedings of the The Fifth European Workshop on Probabilistic Graphical Models. HIIT Publications, 2010.

Lionel Torti. Structured probabilistic inference in object-oriented probabilistic graphical models. PhD Thesis, Université Pierre et Marie Curie, 2012.