Back home 3.2.4. Modules down Contents

The Module Type
A Module Instance
Module Parameter
Predicate Overloading
Module Overloading
Module Properties


Modelling based on petri nets can result in large and confusing complex structures. The fear of generating confusing and hardly understandable models is often used as an argument against net-based describing methods. Other describing methods which are not based on the net theory, also create complex structures without imaging them in a single representation. Often model structures are used which mainly have physical references to the real system but their informative references are often represented by other means such as decision tables.

The experience in modelling with nets proves that models remain clear and understandable when working with 6 to 10 predicates and 6 to 10 transitions. This results in approximately 30 to 40 arcs. If this number is exceeded greatly, the above mentioned effect is inevitable. But this is not only true for the generation of simulation models but also for other software development. Therefore modularization is an essential programming concept for large applications. With this concept a total complex system is divided into several modules which can be implemented independently of each other and remain understandable. Their interaction, their functionalities and their interfaces are described accordingly.

The concept of modularization provides two essential advantages regarding the development of simulation models:
The behaviour of partial processes can be described by means of clear modules.
A partial behaviour, when described once, can be reused everywhere according to its functionality.


3.2.4.1. The Module Type top of page down Contents

Poses++ provides a concept of module types similar to a C++ class data types with declarations of parameter, behaviour and interfaces. A module is applicable as a type and can be reused in other modules. The resulting language properties are very similar to those of the type concept of C++, so that Poses++-modules support typical object-oriented methods such as data packaging, connection of data and rules in a type.

Only inside a module type the declaration of predicates and transitions with their arcs is allowed.

The declaration of a module starts with the keyword module followed by the name of that module type. Within a module type a distinction is made between externally visible (public) and invisible (private) areas. Instances of other module types can only use declarations or access parameters public area. If no decision for a public or a private section was made for all declarations in a module type public is assumed automatically.

Start example

module ModuleTypeName {

private:  int PrivateParameter; // only visible inside

public:   int PublicParameter;  // visible for all

private:

  place PrivatePlace;

  trans PrivateTransition {
    PrivatePlace >> $ << $;
  }

};

End example

A Poses++ source file contains typicaly a module type with the same name (e.g. clock.pos contains the module type clock). But declaring more than one module types in one source file is  as well allowed as you can assign the module types the names you want following the C-like conventions for type names (leaded by a letter and following letter, digit and underscore characters).

To make the module types from one source file visible and accessible for module types in other source files you should use the import keyword before any other declarations. When you import a source file all other source files imported into that source file are implicitly visible to you. To make e.g. C/C++ functions from a header file visible in a Poses++ source file you can use the C-like #include filename directive. Type declarations are possible on global scope (outside any module declaration) and in module scopes.

Start example

import clock;            // imports all declarations from clock.pos

#include "posanim.h"     // includes the text from posanim.h here

struct GlobalStruct {    // GlobalStruct declared on global scope
  int a,b,c;
};

module ModuleType {      // ModuleType declared on global scope

  struct LocalStruct {   // LocalStruct on module scope
    int d,e,f;
  };
};

End example

To access embedded types like LocalStruct from outside the module ModuleType the scope resolution operator :: has to be used (e.g. ModuleType::LocalStruct).


3.2.4.2. A Module Instance up down Contents

All modelling specifications in Poses++ have to be developed on the base of module types. You can use module types as instances in other module types and you can select a module type to be the model for an experiment (see also: Miscellaneous Configurations).

An instance is a member of a structured type (struct, module) by itself based on a data type. So you can construct structured types with members based on oridinal types (e.g. char, int, float) or also based on structured types. If inside a module declaration a member based on a module type is specified we call this a module instance.

Start example

module A {
};

module B {

  A a1; // a1 and a2 are in B module instances of type A
  A a2;

};

module C {

  A a;  // a is in C a module instance of type A
  B b;  // b is in C a module instance of type B

};

End example

If you would build such a Poses++ source code to a model you can select to get A, B or C as the model. If you select A you will see nothing in your model but selecting B or C you will have the module instances inside B or C as sub modules. For your simulation experiment at least one module must contain all sub modules necessary for your model but in your source code you can also have some different variants of module types also suitable for experiments.


3.2.4.3. Module Parameter up down Contents

Defining a module type you are able to specify constant variables with initial values public for all other module types. Such constants you can treat as the parameter of a module type. At first you can specify the values of these parameters in the module type itself but you are also able to change these parameters when you instanciate this module type inside another module. Doing so the values of the parameters will be changed when the whole model structure will be created by the simulation server.

Start example

module Range {

  const int Max = 5;
  const int Min = 1;

private:

  const int RangeWide = Max - Min;

};
End example

In case of Range would be your model you would have Max=5, Min=1 and from Max and Min dependent RangeWide=4. Using the module type Range inside another module type you can change the public accessible parameters which means the values will be changed.

Start example

import Range; // if the module Range comes from range.pos

module Area {

  Range LeftRight(Min = 0);
  Range TopDown  (Min = 0, Max = 10);

};

End example

In module type Area two sub modules of module type Range are instanciated. The instance LeftRight changes the parameter Min from its original value 1 to the new value 0. The parameters can be changed in the parameter list of an instance with an expression where on left side of the assignment the identifier from inside the module has to be used and on the right side of the assignment the new value has to be specified. Parameters are changed by value what means the value of the right part of the assignment will affect the parameter at the time when the module instance is created.


3.2.4.4. Predicate Overloading up down Contents

A simple tree hierarchie of modules like constructable with module types and module instances is often not flexible enough to describe the structure, and thus, the behaviour of a model. To connect module instances together Poses++ supports on one hand the predicate overloading and on the other hand the module instance overloading which is sometimes more powerful.

clockLets discuss a simple clock example:

Your task is to model a clock - a simple clock with seconds, minutes and hours. What do we need ? We should be able to count seconds, minutes and hours and every time we reach a zero value for a counter the following and dependent counter should be informed.
At first we need a counter able to count e.g. seconds, minutes or hours like shown in the figure below:

the simplest counter

Start example

module Counter { // (1)

  /* an unsigned integer because values less than 0 are not expected
     and the capacity of 1 can represent our actual counter value */

  buffer<unsigned int,1> Digit << 0;

private:

  match unsigned int value;

  trans Increment {
    Digit >> value << value + 1;
  }

};
End example

But this simple counter has no limit. It would count beginning from 0 continuously. To count seconds of a minute or minutes of a hour we need a limit.

Start example

module Counter { // (2)

  const unsigned int     Limit = 60; // defaults to 60 (seconds, minutes)
  buffer<unsigned int,1> Digit << 0;

private:

  match unsigned int value;

  trans Increment {
    Digit   >> value,cond(value < Limit)
            << value + 1;
  }

  trans Zero {
    Digit   >> Limit
            << 0;
  }
};
End example

The counter by itself work but how we can use this module as part of a clock model ? For a clock model we need three counter. One for the seconds, one for the minutes and one for the hours. But these counters must work in relation to each other. Only if the conter for the seconds reaches the value zero the counter for the minute should change its value one time. This can be reached by a trigger all changes will depend on a trigger.

Start example

module Counter { // (3)

  const unsigned int     Limit = 60; // defaults to 60 (seconds, minutes)
  buffer<unsigned int,1> Digit << 0;
  place <             1> Trigger;

private:

  match unsigned int value;

  trans Increment {
    Trigger >> $;
    Digit   >> value, cond(value < Limit)
            << value + 1;
  }

  trans Zero {
    Trigger >> $;
    Digit   >> Limit
            << 0;
  }
};
End example

If we would add an additional trigger not for the counter module itself but for a neighbour counter we would have all to build our clock.

Start example

module Counter { // (4)

  const unsigned int     Limit = 59; // defaults to 0..59 (seconds, minutes)
  buffer<unsigned int,1> Digit << 0;
  place                  Trigger,
                         Successor;
private:

  match unsigned int value;

  trans Increment {
    Trigger   >> $;
    Digit     >> value, cond(value < Limit)
              << value + 1;
  }

  trans Zero {
    Trigger   >> $;
    Digit     >> Limit
              << 0;
    Successor << $;
  }
};

module Clock {

  place   ClockTrigger;

  Counter Seconds(Trigger = ClockTrigger);
  Counter Minutes(Trigger = Seconds.Successor);
  Counter Hours  (Trigger = Minutes.Successer, Limit = 23); // 0..23

  trans Trigger (parallel = 1, firetime = 1sec) {
    ClockTrigger << $;
  }
};

End example

By the assignment of Seconds(Trigger = ClockTrigger) the place Trigger in the module instance Seconds will be replaced  or better overloaded by ClockTrigger. Such a replacement of a predicate is done by reference what means not only the actual situation of ClockTrigger will be copied to Seconds.Trigger. Every time when the module Seconds acts with its predicate Trigger it will really act with the predicate ClockTrigger from its parent module.

And so the following model structure we have now:

module structure


3.2.4.5. Module Overloading up down Contents

Even more powerful than predicate overloading is the concept of module overloading. But the mechanism is very similar. Assigning a module instance in the parameter list of another module instance will replace the original one by the overloaded module. With this mechanism you are able to use central module instances for tasks (transitions) and you can use them also as predicate or parameter containers.

Start example

module A {

  ram<string> aString << "string in A";

};

module B {

  ram<string> aString << "string in B";

  A           a1_in_B(aString = aString);
  A           a2_in_B;

  // aString in a1_in_B contains: "string in B"
};

module C {

  ram<string> aString << "string in C";

  A           a(aString = aString); // aString in a contains: "string in C"
  B           b(a2_in_B = a);
  /*
    "C.b.a2_in_B.aString" is identical to "C.aString" and
    "C.a.aString". The module a2_in_B in module instance b
    is overloaded with module a.
  */
};

End example

Module overloading is useful to have near global access to parameter container or to central functionalities.


3.2.4.6. Module Properties up end of page Contents

Every module instance has the predefined properties name and path. The property name specifies the real instance name of this module instance and path the real full instance name including all parent names of that module instance. Both non writable properties are based on the data type string. Every predicate has the same predefined properties.

Start example

module Global { // a global container for all names and paths

  fifo<string> Names;
  fifo<string> Paths;

};

module M {

  Global global; // public visible

private:

  place<1> Initialized;

  trans Init {
    Initialized  << $;
    global.Names << name; // inform about the instance name
    global.Paths << path; // inform about the instance path
  }
};

module X { M mX; };

module All {

  Global global; // public visible
  M      m1(global = global);
  M      m2(global = global);
  X      x (mX     = m1    );

};

End example
Model tree When you start this simple example the both Init transitions of the module instances m1 and m2 will fire before the simulation server reach the deadlock because nothing more is to do. The predicate All.global.Names will contain: "m1" and "m2". The predicate All.global.Paths will contain: "All.m1", and "All.m2".
Model tree For the module mX in All.X you will not find a name or a path string generated because mX was overloaded by All.m1. All overloaded module instances are marked by an @ character. The same indication also via the character @ is used to display the names of overloaded predicates.


Back home mail copyright © Andrae Behrens 05/24/2023
up Contents