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

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

8.2. 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.
}

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

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

8.3. Class Inheritance

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

8.3.1. 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 ] };
}

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

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