charles lakos loop n
DESCRIPTION
Book about loopn++TRANSCRIPT
Table of Contents
1 Introduction 1
2 Petri Nets 1
2.1 Black and white nets 2
2.2 Condition-event net systems 4
2.3 Place-transition nets 5
2.4 Coloured nets 5
2.5 Predicate-Transition Nets 7
2.6 Traditional language constructs as petri nets 8
3 The Definition of the Language LOOPN++ 9
3.1 LOOPN++ grammar 9
3.2 Elementary syntactic issues 10
3.2 Programs, classes and instances 11
3.3 Types 12
3.4 Values and expressions 12
3.5 Fields 13
3.6 Functions 14
3.7 Actions 15
3.8 Transitions 16
3.9 Places 16
4 The LOOPN++ Run-time Support Environment 17
4.1 Input-output 17
4.2 Statistics gathering 17
4.3 Other support functions 18
4.4 Tracing 18
4.5 Interactive debugging 18
5 Examples 20
5.1 Black and white dining philosophers 20
5.2 Black and white dining philosophers – solution 2 20
5.3 Coloured dining philosophers 21
5.4 Critical regions 22
5.5 Traffic lights - solution 1 23
5.6 Traffic lights - solution 2 24
5.7 Street of traffic lights 25
6 Running LOOPN++ at the University of Tasmania 26
6.1 The commands l++, l++go 26
6.2 The LOOPN++ simulator 27
7 The Object Orientation of LOOPN++ 29
7.1 The motivation for Object Orientation 29
7.2 Review of Object-Oriented Petri Net Proposals 30
7.3 Example of the Russian philosophers 32
7.4 Super places 35
7.5 Example of the active philosophers 37
7.6 Example of Electronic Data Interchange 39
8 Conclusions 41
References 42
1 Introduction
The language and simulator referred to in this technical report as LOOPN++ has evolved over a
period of about eight years. Originally, it was motivated by the need to provide practical
experience with network protocols for the third year Computer Science Networks course at the
University of Tasmania. Initially, the Protocol Workshop of J. Colville [2] was used, but while
that tool was appropriate for the demonstration of a number of fixed protocols, it left little room
for experimentation with others. At the other extreme, real-life protocols were considered to be
so complex as to be beyond the scope of a first course in networking. As a step towards an
alternative solution, Simon Milton implemented the inital version of a petri net simulator called
PETSI (PETri net SImulator) as an Honours project in 1987. Since then, it has been rewritten a
couple of times by Charles Lakos, with collaboration from Chris Keen, and with the programming
assistance of Stephen Quan and Carl Lewis, resulting in the language and simulator called
LOOPN (Language for Object-Oriented Petri Nets). LOOPN has been relatively stable since
1992. In 1993, while on sabbatical leave, Charles Lakos proposed a formalism for Object Petri
Nets and a revised language called LOOPN++. These built on the experience with LOOPN, and
represented a more thorough integration of object-oriented technology, while still retaining the
important theoretical property of being able to translate Object Petri Nets into behaviourally
equivalent Coloured Petri Nets. Michael Cahill began implementation of LOOPN++ in late-1994,
and Charles Lakos extended it to a full implementation in 1996, with the assistance of Wei
Shang, Nicole Harvey, and Richard Cockerill.
This document represents the status of the current version of LOOPN++ as it is available on the
Unix™ workstations and the IBM RISC 6000 at the University of Tasmania. The document is
written with the modelling of network protocols in mind, though LOOPN++ is also used for
general simulation. Petri nets and their general use in modelling concurrent processes are
introduced in §2 while the grammar of LOOPN++ together with the semantic implications of
each construct are presented in §3. The run-time support environment for LOOPN++ is described
in §4. Sample LOOPN++ programs are presented in §5, while §6 describes how LOOPN++ is
used on the University of Tasmania Unix™ workstations. The object-oriented features of
LOOPN++ are discussed in §7, together with their motivation and examples of use. Finally, §8
contains some concluding comments.
2 Petri Nets
Petri Nets have been popular as a formalism and practical tool for modelling, simulating and
analysing concurrent systems [6, 9, 20, 22]. There is an attractive simplicity about the basic
constructs of places (which hold state information in the form of tokens), transitions (which
indicate possible changes of state), and arcs (which link the two together). In spite of this
essential simplicity of so-called Place-Transition Nets (or PT-nets), enhancements involving the
use of coloured tokens and the use of functional languages for net inscriptions can provide
powerful tools for specifying concurrent systems [4, 11]. Kurt Jensen [9] cites examples of the
application of these Coloured Petri Nets (or CP-nets) to the design and validation of VLSI chips,
the modelling of a radar surveillance command post, and the design and implementation of
1
software to control the electronic transfer of money between banks.
Petri nets were introduced by C.A.Petri in the early 1960s as a mathematical tool for modelling
distributed systems and, in particular, notions of concurrency, non-determinism, communication
and synchronisation. Thiagarajan [27] points out that petri nets model the twin notions of state
and change-of-state (or transitions), within the guiding principles:
• states and transitions are two intertwined but distinct notions that deserve an even-handed
treatment
• both states and transitions are distributed entities
• the extent of change caused by a transition is fixed and does not depend on the state at
which it occurs
• a transition is enabled to occur at a state if and only if the fixed extent of change associated
with the transition is possible at that state.
There are many variations of petri nets [20] from black and white or colourless nets, which are
conceptually simple and straightforward to analyse, to more complex nets such as coloured nets
which allow the modelling of complex systems.
The form of net used in LOOPN++ is an extension of coloured nets to include object-oriented
features. In this section, we first consider colourless nets and their interpretation as condition-event
systems, before covering the coloured net extensions and the form incorporated into LOOPN++.
2.1 Black and white nets
A simple (black and white) petri net is a bipartite digraph. A digraph is a directed graph with
nodes and directed edges, while the fact that it is bipartite means that it has two different kinds of
nodes, called places and transitions. Edges can connect places to transitions (known as input
arcs , and the corresponding places known as input places) or transitions to places (known as
output arcs, and the corresponding places known as output places). A petri net can be marked
by indicating the tokens which are contained in each place at a point in time (drawn as dots). If
the input places of a transition all contain (at least) one token, then the transition is enabled for
firing. If it does fire, then one token is removed from each input place and one token is added to
each output place. A petri net is executed by establishing an initial marking and then, at each
subsequent point in time, one or more enabled transitions are chosen for firing.
In the example of fig 2.1, and in the rest of this document, we follow the convention of drawing
the places as circles and the transitions as lines or rectangles. In fig 2.1(a) the places to the left
are the input places while those on the right are the output places. Since there are tokens in each
of the input places, the transition is enabled to fire. If it does fire, a token will be removed from
each input place and a token will be added to each output place, giving the marking of fig 2.1(b).
(a) Before (b) After
2
Fig 2.1 Firing a transition
It is worth noting at this stage that the ability of a transition to fire is determined solely by local
conditions, namely the presence of tokens in the adjacent input places. This is a desirable
feature in modelling concurrent, distributed systems where locality of reference is desirable. The
only global consideration in the description above concerns the choice of an enabled transition
for firing. However, this description is merely conceptual (and reflects the current uniprocessor
implementation). It is possible to implement petri nets as sets of parallel processes. Care must
be taken in conflict situations as in fig 2.2, where both transitions are enabled but the firing of
one disables the other.
Fig 2.2 Conflict between transitions
Even with the simplicity of black and white nets, it is possible to model interesting concurrent
systems. Consider the example of modelling two concurrent processes with critical regions,
such that only one of the two processes is allowed to be in its critical region at any time. A petri
net for this is given in fig 2.3.
Proc1 Crit1 Sem Crit2 Proc2
Enter2Enter1
Leave1 Leave2
Legend:
CritProcSemEnterLeave
Critical regionProcessingSemaphoreEnter critical regionLeave critical region
Fig 2.3 Modelling critical regions
The initial marking is indicated, with one token in each of the places labelled Proc1 and Proc2
(indicating that the two processes proceed independently), and with one token in the place
labelled Sem (for the semaphore). Subsequently, either the left-hand or the right-hand process
can enter its critical region, but not both. This restriction is guaranteed by the presence of only
one token in the place Sem . The result of firing transition Enter1 would be to remove the tokens
from places Proc1 and Sem and place a token in Crit1 . At this point, only the transition Leave1
is eligible for firing, and when it does fire, the original marking is restored. Analysis of this net
would show that at all times only one of the processes can enter its critical region, as required.
It should be noted that the state of the system is given by the marking of the places. The
3
transitions (normally) do not hold any state information, but represent changes-of-state.
2.2 Condition-event net systems
Black and white nets are often interpreted as condition-event systems. Here, each place is
interpreted as a condition, which holds if and only if a token is present at the place. Each
transition is interpreted as an event, the occurrence of which leads to a change in the associated
conditions (indicated by the adjacent places). This interpretation of a black and white net puts
additional constraints on the net: a place can only hold one token at a time (since a condition can
only be true or false); and a transition cannot fire if a token is already present in an output place
(a consequence of the first constraint). Thus the net of fig 2.1(a) would not be eligible for firing
as a condition-event system.
The net of fig 2.3 can be interpreted as a condition event system with the appropriate conditions
associated with the places:
Proc1: process 1 is performing its general processing
Crit1: process 1 is in its critical region
Sem: one process may enter its critical region
Such interpretations, together with the changing of conditions when events occur, can be used to
prove properties about the activity of the system being modelled using propositional logic.
Further properties or invariants can be derived using linear algebra techniques [20].
Forks
Eating Thinking
Starteating
Startthinking
Fig 2.4 The Dining Philosophers
Another example of a black and white net, which can be interpreted as a condition event system,
is that of the dining philosophers. This scenario was proposed by E.W. Dijkstra to illustrate the
problems of deadlock and starvation in operating systems. Five philosophers are seated at a
round table, with one fork or chopstick between each pair of philosophers and one bowl of
spaghetti in the centre of the table. Initially, the philosophers are all thinking. At random
intervals, each philosopher becomes hungry and decides to eat. This is possible if the philosopher
can get hold of the fork on each side. This will not be possible if an adjacent philosopher is
already eating. In this case, the hungry philosopher must wait. The dining philosophers problem
is interesting since it reflects the common computer resource allocation primitives which allow
4
access to only one resource at a time. As a result, deadlock is possible (if each philosopher lifts
their left fork simultaneously). The petri net modelling this problem (but without the possibility
of deadlock) is shown in fig 2.4 with one place for each fork and the complete subnet for only
one philosopher shown.
Note that a philosopher can start eating if there is a token in the associated fork places and one in
the philosopher's thinking place. When the philosopher starts thinking again, the forks are
replaced, as well as a token added to the thinking place. With petri nets, as in the above case, it
is possible for a transition to be dependent on more than one input place. Hence a philosopher
can only pick up a fork if both forks are available. Therefore, deadlock cannot occur unless the
petri net is redrawn to model the raising of one fork at a time, an exercise which is left to the
reader.
2.3 Place-transition nets
Black and white nets can be generalised to place-transition systems which allow the presence of
multiple tokens in a place, the removal and addition of multiple tokens from input and output
places (respectively) when a transition fires, and the limitation of the number of tokens in each
place. This is still essentially a black and white net, and can be mapped into the basic form
already introduced.
2.4 Coloured nets
Coloured nets extend the concept of a net by differentiating between tokens (i.e. giving them
colours), and making the firing of the net dependent on the availability of the appropriately
coloured tokens. Coloured nets, like place-transition systems, can be mapped to black and white
nets, thus indicating that they (theoretically) do not have any greater modelling power . However,
the differentiation of tokens gives, in practice, much greater descriptive power , and helps to
avoid a lot of the duplication that would be required in black and white nets.
Forks
Eatingphilosophers
Thinkingphilosophers
j = i+1
i
i
i
i
i
i
123
4
0
123
4
0
ji+1
thinkeat
Fig 2.5 Coloured Petri Net for the dining philosophers
Consider, for example, the dining philosophers problem above. In our presentation, we only
indicated the subnet for one philosopher, noting that the others were just duplicates. Such
duplication becomes extremely tedious and may be enough to make the modelling of a significant
5
concurrent system infeasible. In the dining philosophers net, the forks are all the same, apart
from their position at the table. It would therefore be possible to model them as one place with
five differently coloured tokens, the colour indicating the position. Similarly, the subnet for the
philosophers will occur only once with the firing of the transitions dependent on the availability
of the appropriately coloured tokens. The resultant net is shown in fig 2.5.
In this net, the colours of both forks and philosophers are indicated by numerals taken from the
set {0,1,2,3,4}. Addition is assumed to be modulo 5. The tokens required for a transition to fire
are indicated by the inscriptions on each arc and the condition inscribed on each transition. The
net indicates that philosopher i can start eating if forks i and i+1 are available. Similarly,
philosopher i can stop eating and start thinking, in which case forks i and i+1 are returned to the
table. Once again, it is left as an exercise to model the dining philosophers where only one fork
is raised at a time and hence with the possibility of deadlock.
Another coloured net example is adapted from K. Jensen's paper on coloured nets [7]. It models
the behaviour of traffic lights at a cross intersection. The traffic lights have the traditional red,
amber and green lights but, as in Denmark and Melbourne (Australia), the red and the amber are
displayed together just before turning green. In other words, the cycle followed by the lights is
green → amber → red → red,amber → green … The petri net given by Jensen is presented
below as fig 2.6.
toRed
fromGreen
fromRed
toGreen
Green
Amber
Red
NS NS
EW
x
x≠y
x x
x x
x x
x y
x x
x x
xx
Fig 2.6 Coloured traffic lights
All places can hold either NS tokens or EW tokens. The presence of (at least) one NS token in
the place Green indicates that the green light is illuminated in the north-south direction. Similarly,
the presence of an EW token indicates the illumination of the light in the east-west direction.
The initial marking indicates that the green light is illuminated for the north-south traffic and the
red light is showing to the east-west traffic. The annotation of the arcs indicates the requirements
of tokens for the associated transition to fire, with two arcs labelled x indicating that two tokens
with the same colour are required. The only transition with an additional restriction is the
transition toRed which requires that two differently coloured tokens x,y are added to the place
Red .
6
With two NS tokens in the place Green , the only transition that can fire is fromGreen, which
removes the two tokens from Green and adds them to Amber , thus illuminating that light for the
north-south traffic. Then, the only transition that can fire is toRed , with the result that both
tokens are removed from place Amber and one NS and one EW token are added to place Red,
which now has two EW tokens and one NS. Traffic in both directions now faces red lights. The
only transition that can now fire is fromRed which has the overall effect of removing one EW
token from Red and adding one EW token to Amber . The east-west traffic sees a red and an
amber light. Then transition toGreen is the only one that can fire, removing the EW tokens from
Red and Amber and adding two EW tokens to Green . We return to the initial marking except
with the role of NS and EW tokens reversed. The sequence of lights is thus given by:
NS: green → amber → red → red,amber → green → …
EW: red → red,amber → green → amber → red → …
2.5 Predicate-Transition Nets
We have already seen that black and white nets can be interpreted as condition-event systems
based on propositional logic, i.e. a condition or proposition is associated with each place and the
presence of the token indicates that the condition is true. In a similar way, coloured nets can be
interpreted as systems based on predicate logic, leading to the notion of Predicate-Transition
nets [3]. Here, a predicate is associated with each place and the presence of a token in the place
indicates that the predicate holds for that token value. Compare, for example, the two black and
white petri net segments in fig 2.7(a),(b) with the corresponding coloured net segment in fig
2.7(c).
Pa
QbRa,b
Pb
QaRb,a
(a) (b)
P
Q R
<x>
<y>
x≠y
<x,y>
a
a
[b,a]
b
(c)
Fig 2.7 Using predicates to collapse nets
The condition-event interpretation of fig 2.7(a) is that the event represented by the transition is
enabled if the conditions represented by Pa and Qb hold, which they do. If the event occurs,
then Pa and Qb will cease to hold, and Ra,b will then hold. Similarly, the event of fig 2.7(b) is
7
enabled if the conditions represented by Pb and Qa hold, which they don’t. If the event occurs,
then Rb,a will hold, which it already does. Fig 2.7(c) represents the above two situations as a
coloured net. Now, the conditions and events are modelled by places annotated by predicates.
The fact that the place labelled Q has tokens a and b present is interpreted to mean that the
predicates Q(a) and Q(b) hold. The transition can fire if different tokens are present in the
places P and Q and, when it does fire, the token with a tuple value is added to place R, to
augment the current marking.
Again, the interpretation of coloured nets as predicate-transition systems introduces certain
restrictions. For example, output conditions should not hold prior to the firing of a transition.
Despite these restrictions, the association of a predicate with a place can be a helpful abstraction
for understanding or documenting a coloured net.
2.6 Traditional language constructs as petri nets
By now, it should be clear that petri nets are ideally suited to the description and modelling of
concurrent systems. Jensen [9] shows that it is also possible to combine petri net components to
model traditional control constructs of imperative programming languages (figs 2.8 and 2.9).
A1 A2 A3
Start
End
b not b
S1 S2
(a) Alternatives A1, A2, A3
(b) IF b THEN S1 ELSE S2
Fig 2.8 Modelling conditionals in petri nets
S
WHILE b DO S
End
Start
b not b
8
Fig 2.9 Modelling loops in petri nets
3 The Definition of the Language LOOPN++
This section specifies the syntax and informal semantics of LOOPN++. It first presents the basic
grammar and then the following subsections consider the various components of the language in
turn, including classes, fields , functions , actions , transitions and places . We illustrate the various
language features with the dining philosophers nets from figs 2.4 and 2.5.
3.1 LOOPN++ grammar
class → CLASS id [ : [ parent ] {, parent } ] -- parents
EXPORT ident {, ident } -- exported identifiers
{ field } -- data fields
{ func } -- functions
{ trans } -- transitions
{ action } -- token and anonymous actions
END id
field → type ident [ = value ] [ | guard ] -- data field with initial value + guard
{ , ident [ = value ] [ | guard ] } ;
func → type ident ( parms ) [ = fvalue ] -- function definition
fvalue → expr ; --possible function values
→ FORALL type ident -- place [ | guard ] ; end FORALL
→ EXISTS type ident -- place [ | guard ] ; end EXISTS
→ COUNT type ident -- place [ | guard ] ; end COUNT
→ REDUCE expr ; type ident -- place [ | guard ] ;
[ type ident2 = expr2 ; ] end REDUCE
trans → TRANS id [ : [ parent ] {, parent } ] -- transition definition
{ field } -- data fields
{ action } -- token and anonymous actions
END id
action → type ident <- place [ | guard ] -- input action + selection condition
→ type ident -> place [ | guard ] -- output action + output value
→ type ident -- place [ | guard ] -- test action +selection condition
→ procedure-call -- interact with environment
type → basic-type-ident -- int, bool, real, string, etc.
→ class-ident -- predefined or user-defined
→ type '*' -- multiset type for places
place → ident -- a place is an identifier
value → ident -- copy of specified object
→ '['ident:value {, ident:value }']' -- new object with specified fields
→ ident'['ident:value {,ident:value }']' -- modify object by specified fields
→ '['value {, value} [ | value ]']' -- multiset value constructor
→ expr -- value given by expression
guard → expr -- boolean expression
expr → const -- constant literal
→ ident -- object identifier
→ ident ( [ expr {, expr } ] ) -- function call
→ exprs-combined-with-operators -- operators not specified
→ IF expr THEN expr ELSE expr END -- conditional expression
9
Fig 3.1 The basic grammar of LOOPN++
The design of LOOPN++ attempts to provide a minimal set of contructs with orthogonal
combinations. It may well be appropriate to provide syntactic sugar which resembles traditional
constructs, but it is intended that the underlying constructs should be more general and should be
able to be combined in arbitrary ways. As a result LOOPN++ supports concepts not previously
available, including place types, substitution places, and synchronous interaction between subnets.
In the grammar above we have adopted the following conventions:
• the symbol → is used to separate the non-terminal from its definition(s)
• the order of symbols in a definition is significant, i.e. a definition A → B C D is to be
read as an A is a B followed by a C, followed by a D
• brackets, ie '['…']', indicate that the enclosing construct(s) is optional
• braces, ie '{'…'}', indicate that the enclosing construct(s) can occur zero or more times
• terminal symbols, ie symbols that will occur in a LOOPN++ specification, are given in
upper case or as special characters (colon, semicolon, etc) or enclosed in quotes (e.g.
brackets)
• terminal symbols (which are written in upper case letters) are case insensitive
• non-terminal symbols, i.e. symbols for which grammar rules are explicitly or implicitly
provided, are given in lower case
• a non-terminal with suffix ident is an identifier of the specified kind, eg class-ident is a
type identifier
• identifiers are case sensitive
3.2 Elementary syntactic issues
In a LOOPN++ specification comments are enclosed in braces, i.e. {…}, or appear between a
double slash, i.e. //, and the end of line. Numeric literals take the usual format, while character
and string literals are enclosed in single and double quotes respectively, e.g. "Here is a string".
Some identifiers are reserved words . These include the reserved words of C++ which may never
appear in a LOOPN++ program. They also include the following identifiers specific to LOOPN++,
which should never be redefined (except for deliberate modification of the system behaviour):
null the basic class for passive data (tokens)
generic the basic class for active data
trans the basic transition class
null* the basic place class
self the identifier for the current instance
token the identifier for the current token (in a token action)
char, integer, real, boolean, string the predefined basic types
true, false the boolean constants
first, last, delay, empty, tail, focus the predefined token functions
Some identifiers are predefined in LOOPN++ and may be redefined for specific behaviour.
These include:
report report the status of the current token/object
Functions which can take parameters can specify empty parameter lists with either the Pascal or
10
the C convention, i.e. by including the parentheses or by omitting them altogether. However, for
functions and procedures defined externally (in supporting C files), the C convention (of including
the parentheses) must be adopted.
3.2 Programs, classes and instances
class → CLASS id [ : [ parent ] {, parent } ] -- parents
EXPORT ident {, ident } -- exported identifiers
{ field } -- data fields
{ func } -- functions
{ trans } -- transitions
{ action } -- token and anonymous actions
END id
A LOOPN++ specification or program consists of one or more class definitions. One class is
designated the root class for the purposes of the compilation, and a single instantiation of this
class constitutes the main program.
A class defines a set of objects , the instances of the class. The term type is used interchangeably
with class . Types may be classes defined by the user or basic, predefined types such as
boolean, integer, real, char, string.
In general, a class consists of fields (or data), functions , transitions and actions . Each component
or feature of a class is of one of these four kinds. Each component is allocated and initialised on
instantiation of the enclosing object.
A class may be declared to inherit the features of one or more parents , in which case all the
features of the parents, together with the additional features declared within the class constitute
the features of the new class. It is not possible to override one feature by another of a different
kind – it is therefore not possible to override a field by a function or an action.
A class which inherits (directly or indirectly) from the pseudo class trans is called a transition
class . A transition is a shorthand notation for a class definition with a singleton instance, and
therefore, by default, a transition inherits from the pseudo class trans. A transition class may
contain arbitrary actions, but may not contain transitions. A class which does not inherit
(directly or indirectly) from the pseudo class trans is called a state class. A state class may
contain transitions but may not contain actions (other than procedure calls).
The export clause of a class specifies which features are externally accessible. It does so simply
by listing the relevant identifiers. Any field or function, whether declared locally or inherited,
may be exported. E.g. a simple class with a single integer field which is exported could be:
class int_class
export i;
integer i;
end class
Such a class is useful since the current implementation does not support the declaration of
11
multisets of predefined types, e.g. you can have a multiset of int_class but not of integer.
3.3 Types
type → basic-type-ident -- int, bool, real, string, etc.
→ class-ident -- predefined or user-defined
→ type '*' -- multiset type for places
Various net components may have a type which may be any built-in, predefined type or a
user-defined class. The type may also be of the form type* which indicates a multiset (or bag)
of objects each of type type, and hence is referred to as a multiset type. (The form type* is
used to declare petri net places. By implication, type** is also possible, but not necessarily
useful!)
3.4 Values and expressions
value → ident -- copy of specified object
→ '['ident:value {, ident:value }']' -- new object with specified fields
→ ident'['ident:value {,ident:value }']' -- modify object by specified fields
→ '['value {, value} [ | value ]']' -- multiset value constructor
→ expr -- value given by expression
expr → const -- constant literal
→ ident -- object identifier
→ ident ( [ expr {, expr } ] ) -- function call
→ exprs-combined-with-operators -- operators not specified
→ IF expr THEN expr ELSE expr END -- conditional expression
In certain contexts – in field and function definitions and in guards – it is possible to specify
values. These values may indicate a copy of an existing object, the generation of a new object
(by specifying values for some of the exported fields), a copy of an existing object with certain
specified fields modified, a multiset of values, or a value computed by an expression. For a
multiset of values, the values preceding the vertical bar are elements of the multiset, while the
value following the vertical bar is the tail or remainder of the multiset. The use of the tail is
helpful in defining a recursive function to generate a multiset.
Expression may occur in various contexts in LOOPN++ classes. The grammar does not fully
specify the format of expressions since they are determined by the target language, in this case
C++. Thus const, values-combined-with-operators, procedure-call follow the format of
C++. Note, however, that the unary operators recognised by LOOPN++ are arithmetic and
boolean negation ('–', 'NOT') and the C++ address-of operator ('&'); the binary operators
supported by LOOPN++ are the comparison operators ('=', '<>', '<', '<=', '>', '>='), the arithmetic
operators ('+', '–', '*', '/', 'mod'), and the boolean operators ('and', 'or'). The relative priority of
these operators is determined by the target language, in this case C++, i.e. the boolean operators
have lesser priority than the comparison operators which, in turn, have lesser priority than the
arithmetic operators. This means that the expression below has the implied bracketting shown
but, of course, the programmer is free to use parentheses to enforce any priority of operators.i > 0 and buf[i] <> 0 (i > 0) and (buf[i] <> 0)
LOOPN++ also includes operators to support conditional expressions in additon to the operators
listed above. For example, the expression below returns the maximum of two integers i and j.if i > j then i else j end
12
Note: It is important to note that expressions are grammatically components of values and not
vice versa. This means that once you are nested within an expression (with operators or a
conditional expression), then the arbitrary object or multiset notation cannot be used. This
significant restriction arises because the LOOPN++ parser performs only simple syntax analysis
and translation on expressions before passing them to the C++ compiler. Since C++ does not
support such flexible object and multiset creation within expressions, neither does LOOPN++.
This occasionally means that expressions will need to be rearranged. E.g. given below is the
definition of functions to generate a multiset of n tokens:
int_class* empty_list() = [];
int_class* form_list(integer n) =
[[i:n] | if n=0 then empty_list() else form_list(n-1)];
The above minimal processing of C++ expressions means that the LOOPN++ compiler may
accept an expression as valid which may later be rejected by the C++ compiler. Worse still, the
C++ compiler may not detect the error, in which case a run-time error may result. However, it is
anticipated that this will be a rare occurrence.
3.5 Fields
field → type ident [ = value ] [ | guard ] -- data field with initial value + guard
{ , ident [ = value ] [ | guard ] } ;
A field is a class component of some type which normally holds data (and hence determines part
of the state of the object). A default initial value may be specified for each field using the
notation:
type ident = value;
For every instance of the containing class, this default value will be associated with the field on
initialisation, unless the field is exported. In this case, the instantiation of the containing class
may specify an alternative initial value for the field, using the notation:
type object = [ident:value, ...];
in which case, each instance of the containing class may have a different value for this field.
For example, given below is the declaration of the fork and eating places for the colourless
dining philosophers of fig 2.4, with the forks each holding a single token.
null* fork0=[[]], fork1=[[]], fork2=[[]], fork3=[[]], fork4=[[]];
null* eat0, eat1, eat2, eat3, eat4;
As another example, we present the declaration of the fork and eating places for the coloured
solution to the dining philosophers problem of fig 2.5. Note that the fork place now holds
int_class tokens, and the five tokens in the initial marking have their exported i-fields initialised.
int_class* fork = [[i:0], [i:1], [i:2], [i:3], [i:4]];
int_class* eat;
The value of a field is fixed when its containing class is instantiated, and is in effect for the
lifetime of the instance. Note that if the field is of some class type, then the identity of the object
to which it is bound is fixed, but the contents may not be fixed. For example, if a field acts as a
petri net place, then the particular place is fixed, but its contents or marking is not.
13
A field specification may also specify a guard which is a boolean expression. The interpretation
of the guard varies depending on whether the field occurs in a state class or a transition class. In
a state class, the guard specifies an integrity constraint . If the integrity constraint is ever
violated, then the program will abort with an appropriate error message. In a transition class, the
guard specifies an enabling condition. If the guard is not satisfied, then the transition is not
enabled and will not fire.
3.6 Functions
func → type ident ( parms ) [ = fvalue ] -- function definition
fvalue → expr ; --possible function values
→ FORALL type ident -- place [ | guard ] ; end FORALL
→ EXISTS type ident -- place [ | guard ] ; end EXISTS
→ COUNT type ident -- place [ | guard ] ; end COUNT
→ REDUCE expr ; type ident -- place [ | guard ] ;
[ type ident2 = expr2 ; ] end REDUCE
A function defines a parameterised expression, which returns a value based on the other features
(and hence state) of an object. Functions take parameters and return values of some type (which
may be a predefined type, a class type, or a multiset class).
For example, the following functions allow the fork place from the dining philosophers problem
of fig 2.5 to be initialised to hold 100 forks.
int_class* null_list() = [];
int_class* make_list(integer n) =
[[i:0] | if n = 0 then null_list() else make_list(n-1)];
int_class* fork = make_list(100);
Functions can use quantifiers: forall, exists, count, and reduce to determine a value from the
multiset (or bag) of tokens resident in a place. Reduce is the most general form and requires
further explanation. It supplies an expression (here expr) which is the default initial value and is
assigned to the pseudo variable reduce. For each token (ident) from place satisfying guard,
the following field definition (for ident2) is evaluated, and the result becomes the value of the
pseudo variable reduce. (If no such field definition is provided, the value of the token is used.)
Note that the pseudo variable reduce can be referenced in the expression expr2. The final value
of the pseudo variable reduce becomes the function result. This form of quantified function can
be used for list comprehensions as well as the extraction of summary values from the tokens in a
place. For example, the following function forms a list of fork tokens with odd value fields:
int_class* odd_forks() = reduce [];
int_class f -- fork | odd(f.i);
int_class* result = [f | reduce];
end reduce
LOOPN++ allows expressions to include references to externally defined C/C++ functions. In
order to perform some type checking of these functions, LOOPN++ requires the user to provide
a prototype for each such function. The prototype is in the format of a LOOPN++ function
declaration, except that the result expression is not specified. The same notation can be used for
14
forward declaration of functions.
3.7 Actions
action → type ident <- place [ | guard ] -- input action + selection condition
→ type ident -> place [ | guard ] -- output action + output value
→ type ident -- place [ | guard ] -- test action + selection condition
→ procedure-call -- interact with environment
An action is the only construct for changing the state of an object. Actions may be input
actions, output actions, test actions, or anonymous actions. Apart from anonymous actions (or
procedure calls), actions may only occur within transition classes. The procedure calls of a state
class are executed on instantiation of the class. The actions of a transition class are executed
each time the (transition) instance fires. The actions which are immediate components of an
object are always synchronised with each other, but not necessarily with the actions contained
within nested component objects.
An input action extracts a value from an object and is written:type x <- p | guard;
where the constraint "| guard" is optional. For such an input action to occur, the value of x
obtained from p must be of an appropriate type and satisfy the condition, if any. The value x is
called a token (or tokens), while the object p is called an input place. The guard is a boolean
expression which may contain terms of the form:x = value
which is interpreted as testing that the components of x match those specified by the value.
An test action examines a value in an object and is written:type x -- p | guard;
where the constraint "| guard" is optional. For such a test action to occur, the value of x in p
must be of an appropriate type and satisfy the condition, if any. The guard is a boolean
expression which may contain terms of the form:x = value
which is interpreted as testing that the components of x match those specified by the value.
An output action deposits a value into an object and is written:type x -> p | guard;
where "| guard" is optional. For such an output action to occur, the value of x must be
acceptable to p. Again, the value x is called a token (or tokens), while the object p is called an
output place . The guard is a boolean expression which will be of the form:x = value
which is interpreted as generating an object with components matching that of the value.
It is important to note that output tokens are always newly-generated objects or newly-generated
copies of existing objects. In this way, it is not possible to carry a reference to a remote object
(such as a place) around a petri net.
An anonymous action interacts with the environment of the petri net and is of the form:procedure-call
In order to support formal analysis it is necessary for anonymous actions to have no effect on the
firing of transitions in the petri net.
15
For example, the following is a set of actions for the dining philosophers problem of fig 2.5 to
pick up the forks for a philosopher and report that this has been done.
int_class p1 <- think; -- choose the thinking philosopher
int_class f1 <- fork | f1.i = p1.i; -- get the left fork
int_class f2 <- fork | f2.i = (p1.i+1) mod 5; -- get the right fork
int_class p2 -> eat | p2 = p1; -- generate the eating philosopher
printf("philosopher %d starts eating\n",p1.i); --print the message
3.8 Transitions
trans → TRANS id [ : [ parent ] {, parent } ] -- transition definition
{ field } -- data fields
{ action } -- token and anonymous actions
END id
Traditional petri net formalisms include the fundamental concepts of a transition. In LOOPN++,
these are built from more basic components. A transition class is one which inherits (directly or
indirectly) from the pseudo class trans. A transition is syntactic sugar for declaring a transition
class with a singleton instance, as in:
Trans ident
type x <- p | …; -- zero or more input actions
type y -> q | …; -- zero or more output actions
procedure-calls -- zero or more anonymous actions
End ident
Note that it is possible to include field declarations in transitions. These are used for temporary
variables, which are instantiated for the duration of firing the transition. The guard on the field
(if any), will contribute to determining whether the transition is enabled.
3.9 Places
LOOPN++ does not supply specific syntax for places. A place is simply an object which
supports input and output actions, by supplying or accepting tokens. In the simplest case, a place
is a field of multiset type and is declared in the form:type* ident
In this case, the support for input and output actions is predefined.
In general, however, any class which inherits from a multiset class can be instantiated to form a
place. The inherited multiset class determines the kind of tokens which the place can accept or
offer to its environment. The new class may use the built-in operations for accepting and
offering tokens or may redefine them. This is covered in more detail in §7.4.
16
4 The LOOPN++ Run-time Support Environment
4.1 Input-output
The primary means by which LOOPN++ programs interact with the machine environment is
through the procedure calls included as the anonymous actions of a transition. These actions
may specify any externally defined C/C++ function. Such functions can also be used in expressions
including those restricting the visibility of tokens, but this should be done only with extreme
caution. If such a function depends on an externally determined event, then the LOOPN++
simulator may fail to recognise that the event has occurred (and the corresponding function
result has changed), leading to erroneous operation of the petri net.
Familiarity with the C/C++ library functions is therefore highly desirable for the LOOPN++
programmer. However, for those unfamiliar with the standard C/C++ input output functions, a
set of functions is provided based on those available in Modula 2:
Read () Return the next character from input
ReadInt () Return the next integer from input
ReadReal () Return the next real number from input
ReadString (var s:string; w:integer) Return the next string from input.1 The
string will be terminated at w characters, when a newline
character or end-of-file is read, whichever occurs first.
Write (c:char) Write the character to output
WriteLn() Terminate the current line on output
WriteInt (n,w:integer) Write the integer n to output in w columns or use as many
columns as required if w is zero
WriteReal (r:real; w,d:integer) Write the real number r to output in w columns
and d decimal places, or use as many columns as required
if w is zero
WriteString (c:string; w:integer)Write the string to output, right- justified in w
columns, or use as many columns as required if w is zero
4.2 Statistics gathering
LOOPN++ provides built-in mechanisms for statistics gathering. The statistics associated with
places and transitions can be accessed using the following functions:
totaltokens() Defined on places to return the total number of tokens which
have visited the place
totaltime() Defined on places to return the total accumulated time that
tokens have been resident at the place. (The above functions
can be combined to determined the average waiting time
17
1Note that the first argument to call of ReadString must be a string variable, possibly a token component, the
value of which will be replaced by the string read from input.
for each token at a place.)
totalfired() Defined on transitions to return the number of times that
the transition has fired. (Combined with globaltime(), it
can be used to determine the firing frequency.)
4.3 Other support functions
The following external functions are provided for controlling the simulator and for the generation
of random numbers:
globaltime() Return the current simulated time
setTimeLimit(real time) Set the maximum simulation time for this run (default =
50)
setRetryLimit(real r) Set the probability of extending a step with further
transitions
randomint(integer lo,hi) Return a uniformly distributed integer random number in
the range [lo..hi]
randomreal(integer seed) Return a uniformly distributed real random number in the
range [0..1]
globalseed() Return the current random number seed
negexp(real lambda) Return an exponentially distributed random number with
mean 1/lambda
meminuse() Return the amount of heap space currently used
timeused() Return the number of seconds of run-time used so far
4.4 Tracing
It can be difficult to track down bugs in concurrent systems. Accordingly, the LOOPN++
run-time environment includes a function to control tracing of net activity for classes derived
from generic (which includes places and transitions) for providing trace information. This
function is normally called as an anonymous action in a transition, and thereby can selectively
turn tracing on and off.
The tracing of a place indicates when tokens have been added to or removed from the place. The
tracing of transitions indicates when the transition has been armed and whether firing was
successful or not. The function is:
settrace(boolean on) Turn tracing on for the specified place
4.5 Interactive debugging
Tracing a simulation often produces too much information, making it hard to track down problems.
A more selective approach is interactive debugging, which is also supported by LOOPN++.
Interactive debugging is enabled by using the option -d on the compiled output file (a.out) or
on the l++go command (see §6). At each cycle of the simulator, or as directed by the user, the
user will be prompted with the name of the current net component being examined, to which it is
possible to reply with a number of commands. The list of commands can be obtained with the
18
command help, which responds as follows:
help - produce this message
quit - terminate simulation
step - simulate the next event
focus obj - change focus to specified object
status obj - print status of specified object
gofor t - simulate for t ticks
gowhile t - simulate while trans t enabled
gountil t - simulate until trans t enabled
Some of these commands are explained further below. It is important to note that an object can
be specified in the notation feature1.feature2. … where feature1 is a feature of the current
net component, which in turn has a named component feature2, etc. The main module is
identified by a leading dot.
quit
An alias for this command is an end-of-file character. This command terminates the simulation.
step
An alias for this command is simply a carriage return character. The command will get the
simulator to execute one pending event. This may be the examination of a transition to see if it
is enabled, the firing of a transition, or the advancement of the simulated time.
gofor t
This command will simulate the net for a simulated time of t.
gowhile t, gountil t
These commands allow you to instruct the simulator to keep executing while a specified transition
is enabled or while it is disabled. The transition can be specified as an object (as above).
focus obj
The component object is specified as above. The interactive debugger now makes the specified
component object the focus for subsequent debugging commands.
status obj
This command asks the interactive debugger to report on the status of the specified object – for a
place, the list of tokens is given; for a transition, its enabled/disabled status is reported; for a
class instance, its components are given. In the case of the component tokens in places, and the
components of classes, the status is reported by calling the built-in function report. By default,
this function prints a numeric value for each component. If the component is a composite object,
then the number printed is the address of the component. It is possible to provide more descriptive
output by overriding the report function in the relevant token type or module declaration.
19
5 Examples
5.1 Black and white dining philosophers
The petri net diagram for this example is found in fig 2.4. This initial solution is given as a
single class. We therefore avoid unnecessary duplication by the judicial use of the ellipsis ('…').
class philos1
null* fork0 = [[]], ... fork4 = [[]];
null* think0 = [[]], ... think4 = [[]];
null* eat0, ... eat4;
trans starteating0
null p1 <- think0;
null f1 <- fork0;
null f2 <- fork1;
null p2 -> eat0;
printf("*** philosopher 0 picks up forks\n");
end starteating0
...
trans starteating4
null p1 <- think4;
null f1 <- fork4;
null f2 <- fork0;
null p2 -> eat4;
printf("*** philosopher 4 picks up forks\n");
end starteating4
trans stopeating0
null p1 <- eat0;
null f1 -> fork0;
null f2 -> fork1;
null p2 -> think0;
printf("*** philosopher 0 puts down forks\n");
end stopeating0
...
trans stopeating4
null p1 <- eat4;
null f1 -> fork4;
null f2 -> fork0;
null p2 -> think4;
printf("*** philosopher 4 puts down forks\n");
end stopeating4
end philos1
5.2 Black and white dining philosophers – solution 2
In §5.1 we modelled the dining philosophers problem as a black and white net. There we
avoided tedious repetition by judicious use of an ellipsis ('…'). Here, we present a modular
20
solution, where repetition is minimised by the use of supporting classes.
This solution is a natural reflection of the presentation of fig 2.4, since we define one module for
a philosopher, and then the main module instantiates five philosophers. Note that each philosopher
instance initialises its own thinking and eating place, while the main module initialises the forks
which are shared between the philosophers.
class philos0
export id, left, right;
integer id;
null* left, right;
null* think = [[]];
null* eat;
trans starteating0
null p1 <- think;
null f1 <- left;
null f2 <- right;
null p2 -> eat;
printf("*** philosopher %d picks up forks\n", id);
end starteating0
trans stopeating0
null p1 <- eat;
null f1 -> left;
null f2 -> right;
null p2 -> think;
printf("*** philosopher %d puts down forks\n", id);
end stopeating0
end philos0
class philos2
null* fork0 = [[]], ... fork4 = [[]];
philos0 phil0 = [id:0, left:fork0, right:fork1];
philos0 phil1 = [id:1, left:fork1, right:fork2];
philos0 phil2 = [id:2, left:fork2, right:fork3];
philos0 phil3 = [id:3, left:fork3, right:fork4];
philos0 phil4 = [id:4, left:fork4, right:fork0];
end philos2
5.3 Coloured dining philosophers
The petri net diagram for this example is found in fig 2.5. The reader will note that unlike the
example in §5.1, the coloured net solution is complete. This demonstrates the additional descriptive
power of coloured nets, which can obviate the need for tedious duplication.
class int_class
export i;
integer i;
21
end int_class
class philos3
int_class* fork = [[i:0], [i:1], [i:2], [i:3], [i:4]];
int_class* think = [[i:0], [i:1], [i:2], [i:3], [i:4]];
int_class* eat;
trans starteating
int_class p1 <- think;
int_class f1 <- fork | f1.i = p1.i;
int_class f2 <- fork | f2.i = (p1.i+1) mod 5;
int_class p2 -> eat | p2 = p1;
printf("*** philosopher %d picks up forks\n", p1.i);
end starteating
trans stopeating
int_class p1 <- eat;
int_class f1 -> fork | f1.i = p1.i;
int_class f2 -> fork | f2.i = (p1.i+1) mod 5;
int_class p2 -> think | p2 = p1;
printf("*** philosopher %d puts down forks\n", p1.i);
end stopeating
end philos3
5.4 Critical regions
The critical regions problem (as introduced in §2) can be modelled in LOOPN++ with the
following specification. As in the previous example, we avoid duplication by defining a module
for a process and instantiating it twice. Clearly, it would be possible to instantiate it more times
if required.
class crit_proc
export which, sem;
integer which;
null* sem;
null* processing = [[]];
null* critical;
trans enter
null s <- sem;
null p <- processing;
null c -> critical | c=p;
printf("Process %d enters its critical region\n", which);
end enter
trans leave
null c <- critical;
null p -> processing | p=c;
null s -> sem;
printf("Process %d leaves its critical region\n", which);
end leave
end crit_proc
22
class crit_test
null* sem = [[]];
crit_proc proc1 = [which:1, sem:sem];
crit_proc proc2 = [which:2, sem:sem];
crit_proc proc3 = [which:3, sem:sem];
end crit_test
5.5 Traffic lights - solution 1
The Danish traffic lights of fig 2.6 can be modelled closely by the LOOPN++ description below.
Note how we can extract multiple tokens from each place in a single transition, as shown in the
figure.
class light
export dir, other;
integer dir;
integer other() = 1-dir;
end light
class lights
export isgreen;
integer EW = 0, NS = 1;
light* red = [[dir:EW]],
amber,
green = [[dir:NS], [dir:NS]];
boolean isgreen(integer d) = exists x -- green | x.dir = d; end exists
trans fromgreen
light gn1 <- green | gn1.delay(5);
light gn2 <- green | gn2.dir = gn1.dir;
light am1 -> amber | am1 = gn1;
light am2 -> amber | am2 = gn1;
printf("Direction %d changes from green\n", gn1.dir);
end fromgreen
trans togreen
light am <- amber | am.delay(1);
light rd <- red | rd.dir = am.dir;
light gn1 -> green | gn1 = rd;
light gn2 -> green | gn2 = rd;
printf("Direction %d changes to green\n", rd.dir);
end togreen
trans fromred
light rd1 <- red | rd1.delay(5);
23
light rd2 <- red | rd2.dir = rd1.dir;
light am -> amber | am = rd1;
light rd3 -> red | rd3 = rd1;
printf("Direction %d changes from red\n", rd1.dir);
end fromred
trans tored
light am1 <- amber | am1.delay(1);
light am2 <- amber | am2.dir = am1.dir;
light rd1 -> red | rd1 = am1;
light rd2 -> red | rd2 = [dir:am1.other];
printf("Direction %d changes to red\n", am1.dir);
end tored
end lights
5.6 Traffic lights - solution 2
An alternative solution to the Danish traffic lights of fig 2.6 is given below. Here only one token
of any direction is present in a place at any time, and the associated conditions are satisfied by
careful choice of functions and firing conditions. (Note that this example has not yet been run
by the loopn++ system pending complete implementation of quantifiers.)
class light2 : light*
export ison, noton, alloff;
boolean ison(integer d) = EXISTS x -- self | x.dir = d; END EXISTS
boolean noton(integer d) = FORALL x -- self | x.dir <> d; END FORALL
boolean alloff() = FORALL x -- self | false; END FORALL
end light2
class lights2
export isgreen;
integer EW = 0, NS = 1;
light2 red = [[dir:EW]],
amber,
green = [[dir:NS]];
boolean isgreen(integer d) = exists x -- green | x.dir = d; end exists
trans fromgreen
light gn1 <- green | gn1.delay(5);
light am2 -> amber | am2 = gn1;
printf("Direction %d changes from green\n", gn1.dir);
end fromgreen
trans togreen
light am <- amber | am.delay(1);
light rd <- red | rd.dir = am.dir;
light gn1 -> green | gn1 = rd;
printf("Direction %d changes to green\n", rd.dir);
24
end togreen
trans fromred
light rd1 <- red | rd1.delay(5) and rd1.first() and
red.ison(rd1.other()) and amber.alloff();
light am -> amber | am = rd1;
light rd2 -> red | rd2 = rd1;
printf("Direction %d changes from red\n", rd1.dir);
end fromred
trans tored
light am1 <- amber | am1.delay(1) and red.noton(am1.dir);
light rd1 -> red | rd1 = am1;
printf("Direction %d changes to red\n", am1.dir);
end tored
end lights2
5.7 Street of traffic lights
The example below demonstrates how three instances of the traffic lights modules (either from
§5.5 or §5.6) can be combined in another module, which can then refer to the status of the
component instances without affecting their performance. The street of lights has one transition
to report when there is a clear run in the north-south direction through all the traffic lights. Note
that a dummy place needs to be declared to provide an input place for the transition.
class street
integer EW = 0, NS = 1;
null* dummy = [[]];
lights first_set;
lights second_set;
lights third_set;
trans isclear
null d <- dummy | d.delay(1) and first_set.isgreen(NS) and
second_set.isgreen(NS) and third_set.isgreen(NS);
null d2 -> dummy;
WriteString("Clear run through lights at time ");
WriteReal(globaltime(),6,2);
WriteLn();
end isclear
end street
25
6 Running LOOPN++ at the University of Tasmania
6.1 The commands l++, l++go
Each LOOPN++ class is prepared in a separate file which is given the same name as the class,
with a suffix '.l'. The modules are compiled using the command l++, which can specify one or
more files, the last one being assumed to be the root class for the petri net. In addition, the l++
command may specify a number of options, and a number of auxiliary files, including C and
C++ source files (with a '.c' or '.C' suffix respectively) and object files (with a '.o' suffix).
A petri net class, cls say, found in the file cls.l is first translated by the l++ command into its
intermediate clausal form, in a file with name cls.cl (i.e. the original file name but with a '.cl'
suffix). From there, the clausal form is translated into C++ code, in files named cls.C and
cls.h. The C++ files are then compiled by the C++ compiler to form an object file, called
cls.o. The object file is linked with the appropriate support libraries to form the executable file
a.out. Finally, the C++ and object files are removed, unless the relevant options are specified
to override this default.
Where a complete petri net consists of several classes, the relevant files can all be specified in
the l++ command provided they are in dependency order (i.e. dependent modules follow the
modules on which they depend). Alternatively it is sufficient to specify the main driver module,
and the l++ command will locate the others provided they are in the same directory, or in
directories specified in the include path (see option 'I' below). With these provisos, the following
commands will achieve identical results (assuming that filen is the main driver module):
l++ file1 file2 file3 ... filen
and
l++ filen
The arguments which can be specified with the l++ command are:
cls.l compile the specified LOOPN++ source file containing class cls
cls.cl compile the specified LOOPN++ clause file containing class cls
fyl.c include the specified C source file in the executable program
fyl.C include the specified C++ source file in the executable program
fyl.o include the specified object file in the executable program
-d set interactive debugging (relevant to l++go command or a.out)
-r do not remove any of the generated files on completion of compilation
-c do not remove the C++ files on completion of compilation
-o do not remove the object files on completion of compilation
-Idir the directory dir should be added to the include path for locating supporting
classes or C header files
-Ldir the directory dir should be added to the include path for locating supporting
libraries
-lx the library libx.a should be bound with the executable program
A variant of the l++ command, called l++go, is also available. This has identical arguments to
that of l++, but it assembles the executable file in the temporary file area /tmp. This avoids
26
excessive file space demands for students with small file space allocations.
6.2 The LOOPN++ simulator
The firing of petri net transitions results in the addition and removal of tokens at places. Each
place is implemented as a queue of tokens of the relevant type. When a token is added to a place
it is added at the end of the queue (so that first and last functions can be easily evaluated),
and is time-stamped with the simulation time at which it was added to the place. In the course of
testing and firing eligible transitions we may determine (through a call to the delay function)
that some tokens should only be visible at some future point of simulated time. In this case, an
event notice is placed on an event queue so that the token will be a reconsidered later.
At each cycle of the simulator, transitions from the event queue are examined. Once an enabled
transition is identified, it is added to the step and a random real number is generated in the range
0..1. If this random number is less than the retry limit, the simulator attempts to extend the step
with another enabled transition from the event queue. If successful, another random number is
generated, etc. The default retry limit is 0.5, and it can be modified by calling the function
setRetryLimit(limit). If the retry limit is set to 0.0, each step will contain only one transition.
If the retry limit is set to 1.0, each step will be maximal.
When all eligible transitions at the current simulated time have been considered, the simulation
time is advanced to the earliest time for which an event notice appears in the event queue.
When the event queue has been emptied or the simulation time exceeds the value maxtime,
then the simulation stops. The value of maxtime may be changed by a call to the function
setTimeLimit(newmaxtime).
Sample output
Given below is the sample output from running the traffic lights specification. Note that the
compilation passes through three stages – the translation of the specification into an intermediate
clausal form, the translation of the clausal form to C, the compilation of the C, and finally, its
execution.
% l++ lights.l
Loopn translator: version 3.115 (27 Dec 95)
Generating clausal form for loopn++ files...
lights.l:
light.l:
8 lines compiled
42 lines compiled
Generating C code for loopn++ clause files...
light.cl:
9 lines read, 187 lines generated
lights.cl:
85 lines read, 1507 lines generated
light.C:
lights.C:
Loading to form a.out
Cleaning up
% a.out
Direction 1 changes from green
Direction 1 changes to red
27
Direction 0 changes from red
Direction 0 changes to green
Direction 0 changes from green
...
Sample output with interactive debugging
Given below is the sample output from running the traffic lights specification with the loopngo
command and with interactive debugging enabled.
% l++ lights.l
Loopn translator: version 3.115 (27 Dec 95)
Generating clausal form for loopn++ files...
lights.l:
light.l:
8 lines compiled
42 lines compiled
Generating C code for loopn++ clause files...
light.cl:
9 lines read, 187 lines generated
lights.cl:
85 lines read, 1507 lines generated
light.C:
lights.C:
Loading to form a.out
Cleaning up
% a.out -d
command> status
EW is integer 0
NS is integer 1
red 122824
amber 123520
green 124120
fromgreen 124912
togreen 125072
fromred 125232
tored 125392
command> status red
Token 1:
dir is integer 0
command> focus amber
command> status
command> focus .green
command> status
Token 1:
dir is integer 1
Token 2:
dir is integer 1
command> step
Direction 1 changes from green
command> status
command> quit
%
28
7 The Object Orientation of LOOPN++
In this section we consider the more advanced object-oriented features of LOOPN++. This
section is adapted from a paper published on LOOPN++. It therefore considers the motivations
for object-orientation, a review of other approaches, plus some examples to demonstrate the
features of LOOPN++.
7.1 The motivation for Object Orientation
In §2.4 we drew the distinction between the modelling power and descriptive power of a petri net
formalism. The former describes what can theoretically be modelled, while the latter describes
what can be conveniently modelled in practice. It is a common property of high-level net
formalisms that they can be transformed into behaviourally equivalent PT-nets (e.g. see [9]),
thus indicating that they have no greater modelling power. Such transformations can form the
basis for adapting known analysis techniques to new petri net formalisms [10].
The importance of increased descriptive power should not be underestimated. A practitioner
may well face a situation of being able to model a concurrent system as a high level net (such as
a CP-net) but not as a simple net (such as a PT-net). This may be due to unfamiliarity with the
formal results quoted above, or it may be because the amount of duplication required for a
PT-net is simply impractical. For example, we might consider the simple case of the dining
philosophers problem where philosophers pick up one fork at a time. A CP-net solution for this
problem (as presented by Valmari [28]) is given in fig 7.1. The initial marking indicates that the
places freeForks and thinking hold the numeric tokens 1..n, thus indicating that these forks
are available and that these philosophers are initially thinking. The arc inscriptions constrain the
enabling of the transitions – transition takeLeft can fire if a token numbered i can be removed
both from the place thinking and the place freeForks and if a similarly numbered token can
be added to the place hasLeft. The firing of this transition corresponds to philosopher i picking
up their left fork.
thinking takeLeft hasLeft
freeForks takeRightretRight
hasRight retLeft eating
1:n
1:n
i
i+1 i+1
i
iii
iii
i i
Fig 7.1 Dining philosophers Petri Net
Even though the inscriptions on this CP-net are relatively simple, it is still a significant advance
over modelling the system as a PT-net. The behaviourally equivalent PT-net would require 5n
places and 4n transitions. This may be quite manageable for n equal to 5, but impractical for n
29
equal to 50.
CP-nets therefore constitute a significant advance over PT-nets, but the absence of powerful
structuring primitives has still been identified as a weakness. As Jensen says: the absence of
compositionality has been one of the main critiques raised against Petri net models [8]. This has
led to the development of Hierarchical Coloured Petri Nets (or HCP-nets) which introduce a
facility for building a petri net out of subnets or modules. More recently, researchers have been
attempting to harness the structuring techniques of object-oriented programming to the petri net
formalism. Some of these proposals are reviewed in §7.2, where we observe that they can, at
best, be described as partial solutions. One common criticism is that the petri net remains a
static, global control structure. This makes certain applications difficult if not impractical (as
considered in §7.2).
The language LOOPN++ (and its underlying formalism Object Petri Nets) embodies a more
extensive adoption of object-oriented structuring into petri nets. The intention is to increase
further the expressive comfort of petri nets and thus make feasible the modelling of even more
complex systems, and at the same time, reaping the other practical benefits of object-orientation
including clean interfaces, reusable software components, and extensible component libraries
[19]. Furthermore, OP-nets retain the important property of being transformable into behaviourally
equivalent CP-nets [15] so that the analysis techniques developed for CP-nets can be adapted.
7.2 Review of Object-Oriented Petri Net Proposals
In traditional petri net formalisms, the token types determine the kind of data that can be handled
by the net. In elementary nets and PT-nets, there is only one token type with only one value.
The only state information available is the number of tokens present in each place. In CP-nets,
tokens can assume arbitrary data types, such as integers, tuples, unions, records, arrays. These
token colours can be used to collapse repetitive net structures which are then differentiated by
the different token values (as was the case in the dining philosophers example of fig 7.1). Along
with the flexible token types comes flexible net inscriptions.
However, despite the range of token types provided in high-level petri net models, tokens are
still passive data items. Their life cycle or even acceptable operations is not encapsulated but is
external to the data and given by the petri net – by its connectivity and its inscriptions. In other
words, the net forms a global control structure in the same way that procedures form the global
control structure in traditional imperative programming languages.
This is contrary to the pattern displayed by object-oriented languages where classes encapsulate
both data and control, so that objects can constrain their response to messages. Furthermore,
there is a pleasing symmetry or balance between data and control. The data (or more specifically
the type of the data) can affect the choice of method for a particular message and hence can
affect the response to an operation. On the other hand, a method can modify the data stored in
an object. This symmetry is not present in traditional petri net formalisms and therefore complicates
or makes impractical the modelling of certain kinds of systems.
It would be desirable, for example, to have a simple way of modelling multi-level simulations,
where the components that flow through the system have their own internal life cycles. Real-life
situations generally have a number of layers of data and activity. For example, in modelling a
30
traffic intersection, the cars which move through the intersection can be considered as data
objects. But cars also have internal activities such as consumption of petrol, mechanical failure,
etc. which may be of interest in the simulation. One could continue by considering the people in
the car as systems requiring modelling at a more detailed level.
As an illustrative example of such multi-level systems, we propose an extension of the dining
philosophers problem which we call the Russian philosophers problem. (The title is intended to
be reminiscent of the Russian matrioshka dolls which can be pulled apart to reveal other dolls
inside.) The Russian philosophers are like the dining philosophers, except that each Russian
philosopher is thinking about the dining philosophers problem. Only when such an imagined
dining philosophers problem deadlocks, will the corresponding philosopher stop thinking and try
to start eating. When such a Russian philosopher stops eating, he or she starts thinking about a
fresh dining philosophers problem. This fanciful problem is an example of a multi-level system
and typical of the situation where one part of a sytem is performing some task which is being
monitored by another.
Another desirable area of application is that of Object-Oriented Operating Systems, where the
entities handled by the system encapsulate their own protocols. For example, we might consider
a situation where an item of electronic mail encapsulates the logic to control interaction with its
contents. Thus, different parts of the document may be visible depending on the security
clearance of the viewer, or the display of the message may vary depending on the screen
technology available. Similar features are exhibited by Electronic Data Interchange, where a
message encapsulates not only the original message but also related documents. Thus, a bill of
lading might incorporate a request for a letter of credit, a request for insurance cover, etc.
A third desirable area of application is that of producing prototypes from Object-Oriented Design
methodologies. The designs produced by such methodologies include a set of objects, each of
which encapsulates its own data and interactions. For example, the Shlaer-Mellor methodology
[23], which can be integrated in a simulation design methodology [12], produces objects which
encapsulate their own life cycle, and which can be dynamically created and discarded. Without
the flexibility of intermixing data and control, the production of prototypes from such designs is
rather awkward [18].
With these sorts of application areas in mind, it is instructive to review some of the proposals
which have been made to incorporate object-oriented structuring into the petri net formalism.
One system, which was the precursor to this work, was the textual language for object-oriented
petri nets called LOOPN. LOOPN supported two class hierarchies – one for tokens and one for
subnets or modules. Inheritance, overriding and polymorphism for token types led to some
interesting results in the modelling of layered network protocols, so that one protocol layer could
be designed to pass any kind of message token [16]. Inheritance, overriding and polymorphism
of subnet or module types allowed the derivation of more complex modules from simpler ones,
thereby encouraging software reuse [17]. An unusual feature (in the context of petri net work)
was the provision of module access functions (which allowed access to some aspect of the state
of a subnet without modifying that state). These facilities proved to be extremely beneficial in
the development of clean module interfaces [17]. Nevertheless, LOOPN still retained a rigid
separation of token types and subnet types, with the petri net being a global control structure and
31
tokens being essentially passive data items. In other words, it is not a simple matter to apply
LOOPN to the application areas identified above.
Researchers at the University of Aarhus, who have been intimately involved with the development
of the CP-net formalism and the associated Design/CPN tool [9, 11] have also been experimenting
with the integration of an object-oriented language BETA [13] into Design/CPN [1]. The BETA
language is used to declare the token types and to annotate the transitions with BETA code
segments. However, there are other contexts where BETA cannot be used and there is no
current intention of departing from the traditional approach where the petri net is a global control
structure.
Van Hee and Verkoulen [5, 29] have considered the modelling of (information) systems in petri
net models. In specifying such systems they identified the need for formalisms which integrated
three aspects of a system – the state space (described by data structures), the interaction structure
(between system components) and the operations (or local state transformations of the components).
In focussing on the petri net model, they observed: Petri Net formalisms do not support a real
data-oriented view of systems, such as is provided by modern object-oriented data models . In
response to this, they present a two-level data model consisting of simplexes or simple objects,
and complexes or container objects. This data model is integrated into a petri net formalism
where places hold particular complexes and where transitions transfer, modify, delete and generate
complexes, as specified in an object algebra. There are many attractive features to this system
including a greater facility in information modelling, the ability to specify class-dependent
methods and cardinality constraints, and the ability to verify some constraints automatically.
However, it seems to fall short of a complete integration of object-oriented ideas with data and
functions separated rather than encapsulated together, and with the life cycle of a complex being
external to the complex and not integrated within the object.
In summary, all the above proposals have, to a greater or lesser extent, focussed on the incorporation
of object-oriented structuring into the definition of token types. They have not addressed the
encapsulation of data and functions, but have retained the traditional petri net style with the
tokens as passive data items and their life cycles specified by the global control structure of the
net.
7.3 Example of the Russian philosophers
Having presented the definition of LOOPN++, we now consider solutions to two problems
illustrating the distinctive features of the language. First, we consider the Russian philosophers
problem posed in §7.2. We start with a solution to the traditional dining philsophers problem.
The class DPhil (fig 7.2) is assumed to be self-evident – it simply defines the data which
identifies each philosopher and provides functions to determine the related fork numbers.
The classes DgetRight and DretRight (fig 7.3) show how transition classes can be defined. The
fact that they are classes means that they can be instantiated many times. They are identified as
transition classes because they inherit from the pseudo class trans . Note that all the places which
the actions refer to are exported (and are bound when the classes are instantiated).
32
Class DPhil -- dining philosophers token type
Export id, left, right, hungry;
integer n = 5; -- number of philosophers
integer id; -- identity of this philosopher
integer left() = (id - 1) mod n + 1; -- left fork number
integer right() = id mod n + 1; -- right fork number
boolean hungry() = true; -- function determines if hungry
End DPhil
Fig 7.2 Dining philosophers token type
class DgetRight : trans -- transition class to pick up right fork
export hasLeft, eating, fork;
DPhil* hasLeft, eating;
int_class* fork;
DPhil x <- hasLeft; -- get a philosopher who has their left fork
int_class f <- fork | f.i = x.right(); -- get their right fork
DPhil y -> eating | y = x; -- the philosopher can start eating
printf("*** %d picks up right \t(%d %7.2f)\n", x.id,
meminuse(), timeused());
end DgetRight
class DretRight : trans -- transition class to put down right fork
export hasRight, thinking, fork;
DPhil* hasRight = [], thinking = [];
int_class* fork = [];
DPhil x <- hasRight; -- get a philosopher who has their right fork
DPhil y -> thinking | y = x; -- the philosopher can start thinking
int_class f -> fork | f = [i:x.right()]; -- return the right fork
printf("*** %d puts down right \t(%d %7.2f)\n", x.id,
meminuse(), timeused());
end DretRight
Fig 7.3 Transition classes for handling the right fork
The class DTable (fig 7.4) defines transitions getLeft and retLeft , while instantiating the classes
DgetRight and DretRight . This serves to compare these two possible approaches of defining
transitions and emphasises that the notation for transitions is simply syntactic sugar.
The place thinking is initialised to hold the philosopher tokens, while the places hasLeft, hasRight,
eating are initially empty. The function deadlock uses the quantifier count to determine the
number of tokens in place hasLeft (satisfying the condition true). If this number is 5, then the
table of dining philosophers is deadlocked.
Note that the function setRetryLimit is called to ensure that only one transition is included in
each step. Further, the functions meminuse and timeused are called to report on the use of
machine resources.
33
Class DTable -- table of dining philosophers
Export k, deadlock;
integer n = 5 ;
integer k = 0;
int_class* fork = [[i:1],[i:2],[i:3],[i:4],[i:5]];
DPhil* thinking = [[id:k+1],[id:k+2],[id:k+3],[id:k+4],[id:k+5]];
DPhil* hasLeft, hasRight, eating;
integer numwaiting() = count x -- hasLeft | true; end count
boolean deadlock() = numwaiting() = 5;
setRetryLimit(0.0); -- ensure one transition per step
trans getLeft
DPhil x <- thinking | x.hungry();
int_class f <- fork | f.i = x.left();
DPhil y -> hasLeft | y = x;
printf("*** %d picks up left \t(%d %7.2f)\n", x.id,
meminuse(), timeused());
end getLeft
DgetRight gr = [hasLeft:hasLeft, eating:eating, fork:fork];
trans retLeft
DPhil x <- eating;
DPhil y -> hasRight | y = x;
int_class f -> fork | f = [i: x.left()];
printf("*** %d puts down left \t(%d %7.2f)\n", x.id,
meminuse(), timeused());
end retLeft
DretRight rr = [hasRight:hasRight, thinking:thinking, fork:fork];
End DTable
Fig 7.4 Table of dining philosophers
It is now possible to extend the above classes to produce a solution to the Russian philosophers
problem. It is important to bear in mind how much of the above classes can be inherited without
change from the classes already defined. Firstly, we can define the class RPhil for Russian
philosophers (fig 7.5), as inheriting the features of the class DPhil, augmenting it with an image
of a table of dining philosophers, and overriding the definition of the function hungry .
Class RPhil : DPhil -- Russian philosophers token type
export k;
integer k = 10;
DTable image = [k:k]; -- image of the dining philosophers
boolean hungry() = image.deadlock(); -- hungry only if the image is deadlocked
End RPhil
Fig 7.5 Class for Russian philosophers
Next, we can define a modified form of the transition class for returning the right fork number,
called DretRight (fig 7.6). This transition needs to be modified since a Russian philosopher
returning to the thinking state, needs to start thinking about a fresh dining philosophers problem,
34
and not the old deadlocked version.
class RretRight : DretRight -- transition class to return the right fork
RPhil* hasRight = [], thinking = [];
int_class* fork = [];
RPhil x <- hasRight;
RPhil y -> thinking | y = [id:x.id, k:x.k]; -- new Russian philosopher
int_class f -> fork | f = [i:x.right()];
printf("*** %d puts down Rright\n", x.id);
end RretRight
Fig 7.6 Transition class to return the right fork for a Russian philosopher
Finally, we can define the class RTable for a class of Russian philosophers (fig 7.7). Note that
this class inherits most of its fields, functions and transitions from its parent class (DTable). It
only needs to specify the place thinking with the new philosopher tokens. Since RPhil is a
subtype of DPhil , the inherited places of type DPhil will also be able to hold the RPhil tokens.
Most of the transitions can therefore be inherited without change, thus emphasising the power of
polymorphism in conjunction with object-orientation.
Class RTable : DTable –– table of Russian philosophers
RPhil* thinking = [[id:1, k:10],[id:2, k:20],[id:3, k:30],
[id:4, k:40],[id:5, k:50]];
RretRight rr = [hasRight:hasRight, thinking:thinking, fork:fork];
End RTable
Fig 7.7 LOOPN++ program for the Russian philosophers problem
7.4 Super places
As already noted in §3.9 LOOPN++ does not supply specific syntax for places. In the simplest
case, a place is simply declared using a multiset type, of the form type*. In order to define more
complex objects with place properties – here called super places – it is necessary to declare a
class which inherits from a multiset type. The multiset type serves to define the type of tokens
which can be passed to and from the place.
Using inheritance, it is possible in this way to define an enhanced place type which has the
features of a place type augmented by additional fields and functions. For example, fig 7.8
shows the definition of a super place called super1 which can hold integer tokens and which
supplies a function to return the number of tokens in the place.
class super1 : int_class*
export size;
boolean size() = count x -- self; end count
end super1
Fig 7.8 A super place class with a function to count the number of tokens
For more sophisticated use of super places, it is necessary to be able to modify the way the
35
object accepts and offers tokens to its environment. In this case, it is necessary to understand
that multiset types are implemented as classes with pseudo transitions get, see, put, as shown in
fig 7.9. It is important to observe that this implies that a place accepts or offers one token at a
time, since the above pseudo transitions accept and offer one token at a time. Of course, a step
involving multiple activations of the above pseudo transitions can result in multiple tokens being
accepted or offered simultaneously by a place, but then these tokens are handled independently.
class T*
trans get
T token <- ... -- access the token to be returned
end get
trans see
T token -- ... -- access the token to be observed
end see
trans put
T token -> ... -- store the token
end put
end T*
Fig 7.9 Pseudo class definition for multiset types
In order to modify the behaviour of a place, it is necessary to override the above pseudo
transitions get, see and put. It is preferable to override them all in order to avoid inconsistent
behaviour. For example, fig 7.10 shows the definition of a capacity place, which will accept at
most limit tokens holding integer values.
class capacityplace : int_class*
export limit;
integer limit = 5; -- maximum number of tokens
int_class* buffer; -- place to hold the tokens
integer size() = count x -- buffer; end count
trans put : put
int_class y -> buffer | y = token; -- store a copy of the token in the buffer
integer d = 0 | size() < limit; -- check that the limit is not exceeded
end put
trans get : get
int_class token <- buffer; -- fetch the token from the buffer
end get
end capacityplace
Fig 7.10 Capacity place
It is important to observe that the transitions get and put (of fig 7.10) inherit from the pseudo
transitions of the same name of fig 7.9. Each of these require that the action which sets the token
token is the first action in the transition. (This may be more flexible when LOOPN++ has full
support for multiple inheritance.)
Another example of a super place is that of a place where tokens are delayed for a specified
period of time, as shown in fig 7.11.
36
class delay_int_place : int_class*
export dly;
real dly = 0;
int_class* buffer;
trans put : put
int_class y -> buffer | y = token;
end put
trans get : get
int_class token <- buffer | token.delay(token.i);
end get
end delay_int_place
Fig 7.11 Delayed place
7.5 Example of the active philosophers
The solution to the dining philosophers (and the Russian philosophers) problem in §7.3 followed
the style of traditional petri net solutions. Thus, the actions for picking up forks are embedded in
the table (class DTable) rather than the more natural, object-oriented solution of embedding
them in the philosophers. Furthermore, it seems unnecessary for each philosopher to know the
number of philosophers at the table. In response to these criticisms, an alternative solution to the
dining philosophers problem is presented below.
To begin with, an extra class (OSetting) is introduced with the table setting (fig 7.12). This class
determines how many forks are available, as well as the identities of the left and right forks for
each philosopher. The table setting is defined as a super place which is prepared to offer and
accept the integer-valued forks. (Note that the table setting is, as we would expect, the same
whether we are dealing with the simpler dining philosophers, or the more complex Russian
philosophers.) Each dining philosopher is initialised with access to the (one) table setting.
Class OSetting : int_class*
export left, right, forks;
integer n = 5;
integer left(integer id) = id;
integer right(integer id) = id mod n + 1;
int_class* forks = [[i:1], [i:2], [i:3], [i:4], [i:5]];
trans get : get
int_class token <- forks;
int_class q -> self | q = token;
end get
trans put : put
int_class q -> forks | q = token;
end put
End OSetting
37
Fig 7.12 Table setting for the dining philosophers
Now, each dining philosopher (class OPhil in fig 7.13) has places for the various possible states
and distinguishes between the states of the philosopher by the presence of a single colourless
token in the relevant place. Otherwise, the transitions of class OPhil are like those of class
DTable (in fig 7.4).
Class OPhil
export id, setting, hungry;
integer id;
OSetting setting;
null* thinking = [[]];
null* hasLeft, hasRight, eating;
boolean hungry() = true;
Trans getLeft
null x <- thinking | hungry();
int_class f <- setting | f.i = setting.left(id);
null y -> hasLeft;
printf("*** %d picks up left \t(%d %7.2f)\n", id,
meminuse(), timeused());
End getLeft
Trans getRight
null x <- hasLeft;
int_class f <- setting | f.i = setting.right(id);
null y -> eating;
printf("*** %d picks up right \t(%d %7.2f)\n", id,
meminuse(), timeused());
End getRight
...
End OPhil
Fig 7.13 Active philosopher, with encapsulated activity
Finally, the class OTable (in fig 7.14) allocates the table setting and makes it available to each
philosopher instance. All actions are now embedded within the philosopher instances.
Class OTable
OSetting setting;
OPhil* phils = [[id:1,setting:setting],
[id:2,setting:setting],
[id:3,setting:setting],
[id:4,setting:setting],
[id:5,setting:setting]];
End OTable
Fig 7.14 Table of active philosophers
It is possible to extend the above object-oriented solution to a solution to the Russian philosophers
problem. The above example demonstrates an important aspect of LOOPN++ – it gives the
modeller the choice as to the level at which to include the actions of the net.
38
7.6 Example of Electronic Data Interchange
Our second example is taken from the field of Electronic Data Interchange and is motivated by
the presentation in [25]. We model a system where there are a number of mail subscribers and a
number of documents. Each subscriber generates, receives, reads and replies to mail documents.
Each document contains a number of information fields which we assume is supplied by a class
definition called Field . A document may also be associated with a number of related documents,
which are determined by the information fields of the original. A document may expect a
standard reply, or in a more complex example, a bill of lading might have a paper trail including
a request for a letter of credit, a request for insurance cover, etc.
Note that this example has not yet been run with the current loopn++ implementation, though it
should indicate how a reasonably complex problem can be addressed.
We start by defining a basic document. As well as data fields indicating the kind of document,
the specification of sender and receiver and the information fields, a document encapsulates
functions to generate the standard replies, to determine which information fields (from an incoming
reply) are not already included in the document, and to determine the subdocuments of the
current document. The appropriate class definition is given in fig 7.15.
Class Doc -- Class for basic documents
Export code, sender, receiver, info, newinfo, hasreply, reply, subdocs;
DocCode code; -- code for kind of document
Address sender, receiver; -- specification of sender and receiver
Field* info; -- current information in document
Doc* reply() = …; -- generate standard reply to this document
Field* newinfo(Field* incoming) = …; -- information fields in incoming not
-- already present in the document
Doc* subdocs() = …; -- subdocuments of this document
-- from available information fields
End Doc
Fig 7.15 LOOPN++ class definitions for a basic document
Class Umbrella : Doc*, Doc -- Class for umbrella documents
Export ref, receive, post;
UmbrID ref; -- unique identification for the umbrella
Doc* forposting, posted, expected; -- status of related subdocuments
Null* regenerate = [[]]; -- non-empty to regenerate subdocuments
Trans gensubdocs -- regenerate the subdocs from the info fields
Null x <- regenerate;
Doc* y -> forposting | y = subdocs()-forposting-posted;
-- only add new subdocuments
End gensubdocs
Trans put : put -- receive a response with its information
Doc y <- expected | y.ref=token.ref; -- reply must be expected
Field* f -> info | f=newinfo(token.info);-- determine new information from reply
Null g -> regenerate; -- need to regenerate subdocuments
End receive
39
Trans get : get -- post a subdocument
Doc token <- forposting; -- a subdocument to be posted
Doc z -> posted | z=token; -- record the subdocument as posted
Doc* r -> expected | r=x.reply(); -- determine the replies expected
End;
End Umbrella
Fig 7.16 Class definition for umbrella documents
Since an EDI document is associated with a number of other subdocuments, it is important to
maintain the status of the paper trail associated with a particular document. This is modelled in
the class of umbrella documents, given by the class Umbrella . This class is a subclass of Doc
(because it gives the status information associated with a particular document). It is also a
subclass of Doc* since it can accept and offer subdocuments to its environment. It includes
places holding the subdocuments which are still to be posted, those which have already been
posted, and the expected replies. The class includes transitions to generate the subdocuments
associated with the current document, to receive an expected reply, and to post subdocuments.
The class definition is given in fig 7.16.
Note that the Umbrella class uses a predefined class Null . As in LOOPN, this is the class used
for colourless tokens, and is indirectly inherited by all other classes. While containing no data
fields, it includes functions for determining the status of a token in a place.
Next, we consider the class Subscriber for a user of the EDI system (fig 7.17). A subscriber is
also a super place which can accept and offer documents to its environment.
Class Subscriber : Doc* -- Class for an EDI subscriber
Export receivemail, sendmail, readmail, replymail;
Address own; -- address of the subscriber
Doc* mbox; -- mail box of messages for this subscriber
Doc* tosend; -- messages to be sent by this subscriber
Trans put : put -- receive mail and add it to the mail box
Address a = token.receiver | a = own; -- the document must be addressed to me
Doc y -> mbox | y=token; -- save the document in the mail box
End put
Trans get : get -- generate a new document and send it
Doc token <- tosend; -- new document generated
End get
Trans readmail -- examine a document without replying
Doc x <- mbox | x.reply()=[]; -- no reply expected
End readmail
Trans replymail; -- examine a document and reply
Doc x <- mbox | x.reply()≠[]; -- the document has an associated reply
Doc y -> tosend | y=x.reply(); -- send the reply
End replymail
End Subscriber
Fig 7.17 Class definition for an EDI subscriber
40
Finally, the EDI system consists of subscribers and documents and the communication between
the two. The management of the EDI system can be modelled in many different ways. In fig
7.18, we model the system as some global mail-clearing house.
The ability to encapsulate data and actions in the one class and to dynamically generate instances
of such a class is fundamental to the above solution. For example, the transition receive in class
System accesses an umbrella u and then extracts the document from u. This multilevel access
makes the above solution very natural but it is not supported by traditional petri net formalisms.
Class System -- Root class for the EDI system
Subscriber* subs; -- the EDI subscribers
Umbrella* umb; -- the umbrella documents
Trans send -- subscriber-generated EDI document
Subscriber s <- subs;
Doc d <- s.sendmail; -- document from the subscriber
Umbrella u -> umb | u=d; -- form the umbrella from the document
Subscriber t -> subs | t=s; -- don't discard the subscriber
End send
Trans receive -- subscriber accepts an EDI document
Subscriber s <- subs;
Umbrella u <- umb;
Doc x <- u.post; -- the umbrella supplies the subdocument
Doc y -> s.receivemail | y=x; -- document must be acceptable
Subscriber t -> subs | t=s; -- retain the subscriber
Umbrella v -> umb | v=u; -- retain the umbrella
End receive
Trans reply -- a subscriber replies to an EDI document
Subscriber s <- subs;
Umbrella u <- umb;
Doc x <- s.replymail; -- reply sent by the subscriber
Doc y -> u.receive | y=x; -- the umbrella must accept the reply
Subscriber t -> subs | t=s; -- retain the subscriber
Umbrella v -> umb | v=u; -- retain the umbrella
End reply
End System
Fig 7.18 Class definitions for subscribers and the EDI system
There is a pleasing simplicity about both of the above solutions. Solutions in a traditional petri
net formalism are far from obvious. It is therefore strongly suggested that, as with CP-nets, the
mapping from the more descriptive formalism into a simpler one should be provided by an
implementation and not forced on the user of the formalism. As already observed, the added
expressive comfort could be the difference between a practitioner being able to propose a
solution to a problem or considering it impractical.
8 Conclusions
In this paper, we have presented a textual language which supports a complete integration of
object-oriented structuring into the petri net formalism. Classes encapsulate data, functions and
41
actions, and as a result, we are no longer constrained to have the actions in the form of the net as
a global control structure. The language makes it convenient to model and simulate complex
systems with multiple levels of activity such as found in multi-level simulations, object-oriented
operating systems, and prototypes of object-oriented designs. It is thus possible to model
systems which would have been difficult or impractical before, while retaining the benefits of
object-orientation such as clean interfaces, reusable software components, and extensible
component libraries.
The language has a formal semantics which makes it possible to transform OP-nets into the
simpler formalism of CP-nets [15]. As a result, the automated analysis techniques available for
CP-nets can be adapted for use with OP-nets.
There are a number of projects arising out of this work. An implementation of LOOPN++ is
currently available and is being refined. A second project is considering the appropriate graphical
conventions for drawing OP-nets and the corresponding modifications to the LOOPN screen
editor. A third project requiring a significant ongoing effort is the adapting of standard net
analysis techniques to suit OP-nets.
References[1] S. Christensen and J. Toksvig DesignBeta V2.0.1 – BETA Code Segments in CP-nets
Lecture Notes OO&CPN - nr 5, Computer Science Department, Aarhus University(1993).
[2] J. Colville Demonstration of Data Communication Protocols Technical Report85.8, School of Computer Sciences, New South Wales Institute of Technology (1985).
[3] H.J. Genrich Predicate/Transition Nets Advances in Petri Nets 1986 – Part 1, W.Brauer, W. Reisig, and G. Rozenberg (eds.), Lecture Notes in Computer Science 254,Springer-Verlag (1987).
[4] K.M.v. Hee, P.M.P. Rambags, and P.A.C. Verkoulen Specification and Simulationwith ExSpect Functional Programming, Concurrency, Simulation and AutomatedReasoning, Lecture Notes in Computer Science 693, Springer-Verlag (1993).
[5] K.M.v. Hee and P.A.C. Verkoulen Integration of a Data Model and High-LevelPetri Nets Proceedings of 12th International Conference on the Application andTheory of Petri Nets, Lecture Notes in Computer Science , Gjern, Denmark, Springer(1991).
[6] H.P. Hillion Timed Petri Nets and Application to Multi-Stage Production SystemsAdvances in Petri Nets 1989, G. Rozenberg (ed.), Lecture Notes in Computer Science424, pp 281–305, Springer-Verlag (1990).
[7] K. Jensen Coloured Petri Nets Advances in Petri Nets 1986 – Part 1, W. Brauer, W.Reisig, and G. Rozenberg (eds.), Lecture Notes in Computer Science 254, pp 248–299,Springer-Verlag (1987).
[8] K. Jensen Coloured Petri Nets: A High Level Language for System Design andAnalysis Advances in Petri Nets 1990, G. Rozenberg (ed.), Lecture Notes in ComputerScience 483, Springer-Verlag (1990).
[9] K. Jensen Coloured Petri Nets: Basic Concepts, Analysis Methods and PracticalUse – Volume 1: Basic Concepts EATCS Monographs in Computer Science, Vol.26, Springer-Verlag (1992).
[10] K. Jensen Coloured Petri Nets: Basic Concepts, Analysis Methods and PracticalUse – Volume 2: Analysis Methods EATCS Monographs on Theoretical ComputerScience, Springer-Verlag (1994).
42
[11] K. Jensen, S. Christensen, P. Huber, and M. Holla Design/CPN™: A Reference
Manual MetaSoftware Corporation (1992).
[12] C.D. Keen and C.A. Lakos A Methodology for the Construction of SimulationModels Using Object-oriented Petri Nets Proceedings of SCS Multiconference onSimulation Methodology and Practice – Modelling and Simulation, pp 267-271,Lyon, France (1993).
[13] B.B. Kristensen, O.L. Madsen, B. Møller-Pedersen, and K. Nygaard Object OrientedProgramming in the BETA Programming Language (1991).
[14] C. Lakos and C. Keen LOOPN++: A New Language for Object-Oriented Petri NetsProceedings of Modelling and Simulation (European Simulation Multiconference),pp 369-374, Barcelona, Society for Computer Simulation (1994).
[15] C.A. Lakos Object Petri Nets – Definition and Relationship to Coloured NetsTechnical Report TR94-3, Computer Science Department, University of Tasmania(1994).
[16] C.A. Lakos and C.D. Keen Modelling Layered Protocols in LOOPN Proceedings ofFourth International Workshop on Petri Nets and Performance Models, Melbourne,Australia (1991).
[17] C.A. Lakos and C.D. Keen Modelling a Door Controller Protocol in LOOPNProceedings of 10th European Conference on the Technology of Object-orientedLanguages and Systems, Versailles, Prentice-Hall (1993).
[18] A. Martin and B. Santanach Introducing Object-Oriented Concepts into the Frameworkof Coloured Petri Nets Thesis , Institut D'Informatique D'Entreprise, Evry (1993).
[19] B. Meyer Object-Oriented Software Construction Prentice Hall (1988).
[20] W. Reisig Petri nets : An Introduction EATCS Monographs on Theoretical ComputerScience, Vol. 4, Springer-Verlag (1985).
[21] W. Reisig Petri Nets in Software Engineering Advances in Petri Nets 1986 – Part 2,W. Brauer, W. Reisig, and G. Rozenberg (eds.)255, pp 63–96, Springer-Verlag (1987).
[22] W. Reisig A Primer in Petri Net Design Springer-Verlag (1992).
[23] S. Shlaer and S.J. Mellor Object Lifecycles – Modeling the World in States YourdonPress, Prentice Hall (1992).
[24] B. Stroustrup The C++ Programming Language (Second Edition) Addison-Wesley(1991).
[25] P.A. Swatman, P.M.C. Swatman, and R. Duke Electronic Data Interchange: AHigh-level Formal Specification in Object-Z Proceedings of 6th Australian SoftwareEngineering Conference, pp 341-354, Sydney, Australia, Springer-Verlag (1991).
[26] A.S. Tannenbaum (ed.) Computer Networks Second ed. Prentice-Hall, EnglewoodCliffs (1989).
[27] P.S. Thiagarajan Elementary Net Systems Advances in Petri Nets 1986, Part I, W.Brauer, W. Reisig, and G. Rozenberg (eds.), Lecture Notes in Computer Science 254,pp 26–59, Springer-Verlag (1987).
[28] A. Valmari Stubborn Sets for Coloured Petri Nets Proceedings of 12th InternationalConference on the Application and Theory of Petri Nets, Aarhus (1991).
[29] P.A.C. Verkoulen Integrated Information Systems Design: An Approach Based onObject-Oriented Concepts and Petri Nets PhD Thesis, Technical University of
43
Eindhoven, the Netherlands (1993).
44
R96-1
LOOPN++ User Manual
CA LakosNetworking Research Group
January 1996
Department of Computer ScienceUniversity of TasmaniaGPO Box 252CHobart Tasmania 7001
TR96-1
LOOPN++ User Manual
CA Lakos
Networking Research Group
January 1996
Abstract
This technical report describes a language and simulator for specifying systems in terms of
Object Petri Nets. These petri nets are a variant of Coloured Petri Nets with the inclusion of
simulated time and a complete integration of object-oriented features. These features serve to
break away from the traditional approach of viewing the data as essentially passive and the petri
net as a global control structure. These features also encourage the convenient modularisation of
complex specifications and allow for the possibility of model reuse.
Kewords and phrases
Object-oriented languages, Object Petri Nets, Petri Net models, Concurrent systems,
Simulation
CR categories
B.4.4, F.1.1