This is SNAKES' main module, it holds the various Petri net elements: arcs, places, transitions, markings, nets themselves and marking graphs.

Auxiliary definitions

Class Evaluator

class Evaluator (object) :

Evaluate expression or execute statements in shareable namespace. Instances of this class can be found in particular as attribute globals of many objects among which PetriNet.

Method Evaluator.__init__

def __init__ (self, *larg, **karg) :

Initialize like a dict

Method Evaluator.__call__

def __call__ (self, expr, locals={}) :

Evaluate an expression, this is somehow equivalent to eval(expr, self, locals)

Call API

Method Evaluator.declare

def declare (self, stmt, locals=None) :

Execute a statement, this is somehow equivalent to exec(stmt, self, locals)

Call API

Method Evaluator.update

def update (self, other) :

Update namespace from another evaluator or a dict

Call API

Method Evaluator.copy

def copy (self) :

Copy the evaluator and its namespace

Call API

Method Evaluator.__contains__

def __contains__ (self, name) :

Test if a name is declared, like dict.__contains__

Method Evaluator.__getitem__

def __getitem__ (self, key) :

Return an item from namespace, like dict.__getitem__ but keys must be strings (because they are names)

Method Evaluator.__setitem__

def __setitem__ (self, key, val) :

Set an item in namespace, like dict.__setitem__ but keys must be strings (because they are names)

Method Evaluator.__iter__

def __iter__ (self) :

Iterate over namespace items (key/value pairs), like dict.items

Method Evaluator.__eq__

def __eq__ (self, other) :

Test for equality of namespaces

Method Evaluator.__ne__

def __ne__ (self, other) :

Test for inequality of namespaces

Class NetElement

class NetElement (object) :

The base class for Petri net elements. This class is abstract and should not be instanciated.

Class Token

class Token (NetElement) :

A container for one token value. This class is intended for internal use only in order to avoid some confusion when inserting arbitrary values in a place. End users will probably never need to use it directly.

Method Token.__init__

def __init__ (self, value) :

Initialise the token

>>> Token(42)
Token(42)
Call API

Class BlackToken

class BlackToken (Token) :

The usual black token, an instance is available as member dot of module snakes.nets, which is also its string representation. Another object tBlackToken is available to be used as a place type.

>>> BlackToken()
dot
>>> tBlackToken
Instance(BlackToken)

Arcs annotations

Class ArcAnnotation

class ArcAnnotation (NetElement) :

An annotation on an arc.

On input arcs (from a place to a transition), annotations may be values or variables, potentially nested in tuples. On output arcs (from a transition to a place), expressions are allowed, which corresponds to a computation.

A class attribute input_allowed is set to True or False according to whether an annotation is allowed on input arcs.

This class is abstract and should not be instantiated.

Method ArcAnnotation.copy

def copy (self) :

Return a copy of the annotation.

Method ArcAnnotation.substitute

def substitute (self, binding) :

Substitutes the variables in the annotation.

Call API

Method ArcAnnotation.replace

def replace (self, old, new) :

Returns a copy of the annotation in which the old annotation has been replaced by the new one.

With non composite annotation, this will return a copy of the annotation if it is not the same as old otherwise it returns new. Composite annotations will replace there components.

>>> Value(2).replace(Variable('x'), Value(5))
Value(2)
>>> Variable('x').replace(Variable('x'), Value(5))
Value(5)
>>> MultiArc([Value(2), Variable('x')]).replace(Variable('x'), Value(5))
MultiArc((Value(2), Value(5)))
>>> Test(Value(2)).replace(Variable('x'), Value(5))
Test(Value(2))
>>> Test(Variable('x')).replace(Variable('x'), Value(5))
Test(Value(5))
Call API

Method ArcAnnotation.vars

def vars (self) :

Return the list of variables involved in the annotation.

Method ArcAnnotation.bind

def bind (self, binding) :

Return the value of the annotation evaluated through binding.

>>> Expression('x+1').bind(Substitution(x=2))
Token(3)
Call API

Method ArcAnnotation.flow

def flow (self, binding) :

Return the flow of tokens implied by the annotation evaluated through binding.

>>> Value(1).flow(Substitution(x=2))
MultiSet([1])
Call API

Method ArcAnnotation.modes

def modes (self, values) :

Return the list of modes under which an arc with the annotation may flow the tokens in values. Each mode is a substitution indicating how to bind the annotation.

>>> Variable('x').modes([1, 2, 3])
[Substitution(x=1), Substitution(x=2), Substitution(x=3)]
Call API

Class Value

class Value (ArcAnnotation) :

A single token value.

Method Value.__init__

def __init__ (self, value) :

Initialise with the encapsulated value.

Call API

Method Value.copy

def copy (self) :

Return a copy of the value.

>>> Value(5).copy()
Value(5)
Call API

Method Value.vars

def vars (self) :

Return the list of variables involved in the value (empty).

>>> Value(42).vars()
[]
Call API

Method Value.bind

def bind (self, binding) :

Return the value evaluated through binding (which is the value itself).

>>> Value(1).bind(Substitution(x=2))
Token(1)
Call API

Method Value.modes

def modes (self, values) :

Return an empty binding (no binding needed for values) iff the value is in values, raise ModeError otherwise.

>>> Value(1).modes([1, 2, 3])
[Substitution()]
>>> try : Value(1).modes([2, 3, 4])
... except ModeError : print(sys.exc_info()[1])
no match for value
Call API
Exceptions

Method Value.substitute

def substitute (self, binding) :

Bind the value (nothing to do).

>>> v = Value(5)
>>> v.substitute(Substitution(x=5))
>>> v
Value(5)
Call API

Class Variable

class Variable (ArcAnnotation) :

A variable which may be bound to a token.

Method Variable.__init__

def __init__ (self, name) :

Variable names must start with a letter and may continue with any alphanumeric character.

>>> Variable('x')
Variable('x')
>>> try : Variable('_test')
... except ValueError: print(sys.exc_info()[1])
not a variable name '_test'
Call API

Method Variable.copy

def copy (self) :

Return a copy of the variable.

>>> Variable('x').copy()
Variable('x')
Call API

Method Variable.rename

def rename (self, name) :

Change the name of the variable.

>>> v = Variable('x')
>>> v.rename('y')
>>> v
Variable('y')
Call API

Method Variable.modes

def modes (self, values) :

Return the the list of substitutions mapping the name of the variable to each value in values.

>>> Variable('x').modes(range(3))
[Substitution(x=0), Substitution(x=1), Substitution(x=2)]
Call API
Exceptions

Method Variable.bind

def bind (self, binding) :

Return the value of the variable evaluated through binding.

>>> Variable('x').bind(Substitution(x=3))
Token(3)
>>> try : Variable('x').bind(Substitution(z=3))
... except DomainError : print(sys.exc_info()[1])
unbound variable 'x'
Call API

Method Variable.substitute

def substitute (self, binding) :

Change the name according to binding.

>>> v = Variable('x')
>>> v.substitute(Substitution(x='y'))
>>> v
Variable('y')
>>> v.substitute(Substitution(x='z'))
>>> v
Variable('y')
>>> v.rename('z')
>>> v
Variable('z')
Call API

Method Variable.vars

def vars (self) :

Return variable name in a list.

>>> Variable('x').vars()
['x']
Call API

Class Expression

class Expression (ArcAnnotation) :

An arbitrary Python expression which may be evaluated.

Each expression has its private namespace which can be extended or changed.

Method Expression.__init__

def __init__ (self, expr) :

The expression is compiled so its syntax is checked.

Call API

Method Expression.copy

def copy (self) :

Return a copy of the expression.

Method Expression.bind

def bind (self, binding) :

Evaluate the expression through binding.

>>> e = Expression('x*(y-1)')
>>> e.bind(Substitution(x=2, y=3))
Token(4)
>>> e.bind(Substitution(x=2))
Traceback (most recent call last):
  ...
NameError: name 'y' is not defined
Call API
Exceptions

Method Expression.__call__

def __call__ (self, binding) :

Returns the value from bind (but not encapsulated in a Token).

Method Expression.substitute

def substitute (self, binding) :

Substitute the variables according to 'binding'.

>>> e = Expression('x+1*xy')
>>> e.substitute(Substitution(x='y'))
>>> e
Expression('(y + (1 * xy))')

As we can see, the substitution adds a lot of parentheses and spaces to the expression. But is really parses the expression and makes no confusion is a substritued name appears as a substrung of another name (like y that appears in xy above).

Call API

Method Expression.vars

def vars (self) :

Return the list of variable names involved in the expression.

>>> list(sorted(Expression('x+y').vars()))
['x', 'y']
Call API

Method Expression.__and__

def __and__ (self, other) :

Implement & to perform and between two expressions.

>>> Expression('x==1') & Expression('y==2')
Expression('(x==1) and (y==2)')

Minor optimisation are implemented:

>>> Expression('True') & Expression('x==y')
Expression('x==y')
>>> Expression('x==y') & Expression('True')
Expression('x==y')
Note: unfortunately, Python does not let us override and
Warning: it is not checked whether we combine boolean expressions or not (this is probably not implementable in a reasonable way)
Call API

Method Expression.__or__

def __or__ (self, other) :

Implement | to perform or between two expressions.

>>> Expression('x==1') | Expression('y==2')
Expression('(x==1) or (y==2)')

Minor optimisation are implemented:

>>> Expression('True') | Expression('x==y')
Expression('True')
>>> Expression('x==y') | Expression('True')
Expression('True')
Note: unfortunately, Python does not let us override or
Warning: it is not checked whether we combine boolean expressions or not (this is probably not implementable in a reasonable way)
Call API

Method Expression.__invert__

def __invert__ (self) :

Implement ~ to perform not.

>>> ~Expression('x==1')
Expression('not (x==1)')

Minor optimisation are implemented:

>>> ~Expression('True')
Expression('False')
>>> ~Expression('False')
Expression('True')
Note: unfortunately, Python does not let us override not
Warning: it is not checked whether we negate a boolean expression or not (this is probably not implementable in a reasonable way)
Call API

Method Expression.__eq__

def __eq__ (self, other) :

Method Expression.__hash__

def __hash__ (self) :

Class MultiArc

class MultiArc (ArcAnnotation) :

A collection of other annotations, allowing to consume or produce several tokens at once.

Such a collection is allowed on input arcs only if so are all its components.

Method MultiArc.__init__

def __init__ (self, components) :

Initialise with the components of the tuple.

>>> MultiArc((Value(1), Expression('x+1')))
MultiArc((Value(1), Expression('x+1')))
>>> MultiArc((Value(1), Expression('x+1'))).input_allowed
False
>>> MultiArc((Value(1), Variable('x'))).input_allowed
True
Call API

Method MultiArc.copy

def copy (self) :

Return a copy of the multi-arc.

Method MultiArc.__iter__

def __iter__ (self) :

Iterate over the components.

Method MultiArc.__len__

def __len__ (self) :

Return the number of components

Method MultiArc.__contains__

def __contains__ (self, item) :

Test if an item is part of the multi-arc

Method MultiArc.flow

def flow (self, binding) :

Method MultiArc.bind

def bind (self, binding) :

Return the value of the annotation evaluated through binding.

>>> t = MultiArc((Value(1), Variable('x'), Variable('y')))
>>> t.bind(Substitution(x=2, y=3))
(Token(1), Token(2), Token(3))
Call API

Method MultiArc.modes

def modes (self, values) :

Return the list of modes under which an arc with the annotation may flow the tokens in values. Each mode is a substitution indicating how to bind the annotation.

>>> t = MultiArc((Value(1), Variable('x'), Variable('y')))
>>> m = t.modes([1, 2, 3])
>>> len(m)
2
>>> Substitution(y=3, x=2) in m
True
>>> Substitution(y=2, x=3) in m
True
Call API

Method MultiArc.substitute

def substitute (self, binding) :

Substitute each component according to 'binding'.

>>> t = MultiArc((Value(1), Variable('x'), Variable('y')))
>>> t.substitute(Substitution(x='z'))
>>> t
MultiArc((Value(1), Variable('z'), Variable('y')))
Call API

Method MultiArc.vars

def vars (self) :

Return the list of variables involved in the components.

>>> t = MultiArc((Value(1), Variable('x'), Variable('y')))
>>> list(sorted(t.vars()))
['x', 'y']
Call API

Method MultiArc.replace

def replace (self, old, new) :

Returns a copy of the annotation in which the old annotation has been replaced by the new one.

With MultiArc, replaces each occurrence of old by new

Call API

Class Tuple

class Tuple (MultiArc) :

An annotation that that is a tuple of other annotations. This is a subclass of MultiArc only for programming convenience, actually, a Tuple instance carry a single token that is a tuple.

>>> t = Tuple((Value(1), Variable('x'), Variable('y')))
>>> m = t.modes([1, 2, (1, 2, 3), (3, 4, 5), (1, 2), (1, 2, 3, 4)])
>>> m == [Substitution(x=2, y=3)]
True

As we can see, this binds the variables inside the tuple, but only token (1, 2, 3) is correct to match the annotation. Note that tuples may be nested arbitrarily.

Class Test

class Test (ArcAnnotation) :

This is a test arc, that behaves like another arc annotation but never transport tokens. It is obtained by encapsulating the other annotation and behaves exactly like the encapsulated annotation except for method flow that always returns an empty multiset.

Method Test.__init__

def __init__ (self, annotation) :

Make a test arc from annotation.

Method Test.copy

def copy (self) :

Return a copy of the test arc.

Method Test.flow

def flow (self, binding) :

Return the flow of tokens implied by the annotation evaluated through binding. For test arcs, this is alwas empty.

>>> Test(Value(1)).flow(Substitution())
MultiSet([])
Call API

Class Inhibitor

class Inhibitor (Test) :

This is an inhibitoir arc that forbids the presence of some tokens in a place.

Method Inhibitor.__init__

def __init__ (self, annotation, condition=None) :

Like Test, it works by encapsulating another annotation. Additionally, a condition may be given which allows to select the forbidden tokens more precisely. This is generally better to make this selection at this level rather that at the level of a transition guard: indeed, in the latter case, we may build many modes and reject most of them because of the guard; while in the former case, we will not build these modes at all.

For instance:

Method Inhibitor.bind

def bind (self, binding) :

Return no tokens since arc corresponds to an absence of tokens. Raise ValueError if this binding does not validate the condition.

>>> Inhibitor(Expression('x+1'), Expression('x>0')).bind(Substitution(x=2))
()
>>> try : Inhibitor(Expression('x+1'), Expression('x<0')).bind(Substitution(x=2))
... except ValueError : print(sys.exc_info()[1])
condition not True for {x -> 2}
Call API

Method Inhibitor.modes

def modes (self, values) :

Return the list of modes under which an arc with the annotation may flow the tokens in values. Each mode is a substitution indicating how to bind the annotation.

>>> try : Inhibitor(Value(1)).modes([1, 2, 3])
... except ModeError : print(sys.exc_info()[1])
inhibited by {}
>>> Inhibitor(Value(0)).modes([1, 2, 3])
[Substitution()]
>>> try : Inhibitor(Variable('x')).modes([1, 2, 3])
... except ModeError : print(sys.exc_info()[1])
inhibited by {x -> 1}
>>> Inhibitor(Variable('x'), Expression('x>3')).modes([1, 2, 3])
[Substitution()]
>>> try : Inhibitor(MultiArc([Variable('x'), Variable('y')]),
...                 Expression('x>y')).modes([1, 2, 3])
... except ModeError : print(sys.exc_info()[1])
inhibited by {...}
>>> Inhibitor(MultiArc([Variable('x'), Variable('y')]),
...           Expression('x==y')).modes([1, 2, 3])
[Substitution()]
Call API

Class Flush

class Flush (ArcAnnotation) :

A flush arc used as on input arc will consume all the tokens in a place, binding this multiset to a variable. When used as an output arc, it will produce several tokens in the output place.

Method Flush.__init__

def __init__ (self, expr) :

Build a flush arc from either a variable name or an expression. In the latter case, the annotation is only allowed to be used on output arcs.

Method Flush.modes

def modes (self, values) :

Return the list of modes under which an arc with the annotation may flow the tokens in values. Each mode is a substitution indicating how to bind the annotation. In the case of flush arcs, there will be only one mode that binds all the tokens in a multiset.

>>> m = Flush('x').modes([1, 2, 3])
>>> m
[Substitution(x=MultiSet([...]))]
>>> m[0]['x'] == MultiSet([1, 2, 3])
True

Take care that a flush arc allows to fire a transition by consuming no token in a place. This is different from usual arc annotation that require tokens to be present in input places.

>>> Flush('x').modes([])
[Substitution(x=MultiSet([]))]
Call API

Method Flush.flow

def flow (self, binding) :

Return the flow of tokens implied by the annotation evaluated through binding.

>>> Flush('x').flow(Substitution(x=MultiSet([1, 2, 3]))) == MultiSet([1, 2, 3])
True
Call API

Method Flush.bind

def bind (self, binding) :

Return the value of the annotation evaluated through binding.

>>> set(Flush('x').bind(Substitution(x=MultiSet([1, 2, 3])))) == set([Token(1), Token(2), Token(3)])
True
Call API

Petri net nodes

Class Node

class Node (NetElement) :

A node in a Petri net.

This class is abstract and should not be instanciated, use Place or Transition as concrete nodes.

Method Node.rename

def rename (self, name) :

Change the name of the node.

>>> p = Place('p', range(3))
>>> p.rename('egg')
>>> p
Place('egg', MultiSet([...]), tAll)
>>> t = Transition('t', Expression('x==1'))
>>> t.rename('spam')
>>> t
Transition('spam', Expression('x==1'))
Call API

Class Place

class Place (Node) :

A place of a Petri net.

Method Place.__init__

def __init__ (self, name, tokens=[], check=None) :

Initialise with name, tokens and typecheck. tokens may be a single value or an iterable object. check may be None (any token allowed) or a type from module `snakes.typing.

>>> Place('p', range(3), tInteger)
Place('p', MultiSet([...]), Instance(int))
Call API

Method Place.copy

def copy (self, name=None) :

Return a copy of the place, with no arc attached.

>>> p = Place('p', range(3), tInteger)
>>> n = p.copy()
>>> n.name == 'p'
True
>>> n.tokens == MultiSet([0, 1, 2])
True
>>> n.checker()
Instance(int)
>>> n = p.copy('x')
>>> n.name == 'x'
True
>>> n.tokens == MultiSet([0, 1, 2])
True
>>> n.checker()
Instance(int)
Call API

Method Place.checker

def checker (self, check=None) :

Change or return the type of the place: if check is None, current type is returned, otherwise, it is assigned with the given value.

>>> p = Place('p', range(3), tInteger)
>>> p.checker()
Instance(int)
>>> p.checker(tAll)
>>> p.checker()
tAll
Call API

Method Place.__contains__

def __contains__ (self, token) :

Check if a token is in the place.

>>> p = Place('p', range(3))
>>> 1 in p
True
>>> 5 in p
False
Call API

Method Place.__iter__

def __iter__ (self) :

Iterate over the tokens in the place, including repetitions.

>>> p = Place('p', list(range(3))*2)
>>> list(sorted([tok for tok in p]))
[0, 0, 1, 1, 2, 2]
Call API

Method Place.is_empty

def is_empty (self) :

Check is the place is empty.

>>> p = Place('p', range(3))
>>> p.tokens == MultiSet([0, 1, 2])
True
>>> p.is_empty()
False
>>> p.tokens = MultiSet()
>>> p.is_empty()
True
Call API

Method Place.check

def check (self, tokens) :

Check if the tokens are allowed in the place. Exception ValueError is raised whenever a forbidden token is encountered.

>>> p = Place('p', [], tInteger)
>>> p.check([1, 2, 3])
>>> try : p.check(['forbidden!'])
... except ValueError : print(sys.exc_info()[1])
forbidden token 'forbidden!'
Call API
Exceptions

Method Place.add

def add (self, tokens) :

Add tokens to the place.

>>> p = Place('p')
>>> p.tokens == MultiSet([])
True
>>> p.add(range(3))
>>> p.tokens == MultiSet([0, 1, 2])
True
Call API

Method Place.remove

def remove (self, tokens) :

Remove tokens from the place.

>>> p = Place('p', list(range(3)) * 2)
>>> p.tokens == MultiSet([0, 0, 1, 1, 2, 2])
True
>>> p.remove(range(3))
>>> p.tokens == MultiSet([0, 1, 2])
True
Call API

Method Place.empty

def empty (self) :

Remove all the tokens.

>>> p = Place('p', list(range(3)) * 2)
>>> p.tokens == MultiSet([0, 0, 1, 1, 2, 2])
True
>>> p.empty()
>>> p.tokens == MultiSet([])
True

Method Place.reset

def reset (self, tokens) :

Replace the marking with tokens.

>>> p = Place('p', list(range(3)) * 2)
>>> p.tokens == MultiSet([0, 0, 1, 1, 2, 2])
True
>>> p.reset(['a', 'b'])
>>> p.tokens == MultiSet(['a', 'b'])
True

Class Transition

class Transition (Node) :

A transition in a Petri net.

Method Transition.__init__

def __init__ (self, name, guard=None) :

Initialise with the name and the guard. If guard is None, Expression('True') is assumed instead:

>>> Transition('t').guard
Expression('True')
Call API

Method Transition.copy

def copy (self, name=None) :

Return a copy of the transition, with no arc attached.

>>> t = Transition('t', Expression('x==1'))
>>> t.copy()
Transition('t', Expression('x==1'))
>>> t.copy('x')
Transition('x', Expression('x==1'))
Call API

Method Transition.add_input

def add_input (self, place, label) :

Add an input arc from place labelled by label.

>>> t = Transition('t')
>>> t.add_input(Place('p'), Variable('x'))
>>> t.input()
[(Place('p', MultiSet([]), tAll), Variable('x'))]
>>> try : t.add_input(Place('x'), Expression('x+y'))
... except ConstraintError : print(sys.exc_info()[1])
'Expression' objects not allowed on input arcs
Call API
Exceptions

Method Transition.remove_input

def remove_input (self, place) :

Remove the input arc from place.

>>> t = Transition('t')
>>> p = Place('p')
>>> t.add_input(p, Variable('x'))
>>> t.input()
[(Place('p', MultiSet([]), tAll), Variable('x'))]
>>> t.remove_input(p)
>>> t.input()
[]
Call API
Exceptions

Method Transition.input

def input (self) :

Return the list of input arcs.

>>> t = Transition('t')
>>> t.add_input(Place('p'), Variable('x'))
>>> t.input()
[(Place('p', MultiSet([]), tAll), Variable('x'))]
Call API

Method Transition.add_output

def add_output (self, place, label) :

Add an output arc from place labelled by label.

>>> t = Transition('t')
>>> t.add_output(Place('p'), Expression('x+1'))
>>> t.output()
[(Place('p', MultiSet([]), tAll), Expression('x+1'))]
Call API
Exceptions

Method Transition.remove_output

def remove_output (self, place) :

Remove the output arc to place.

>>> t = Transition('t')
>>> p = Place('p')
>>> t.add_output(p, Variable('x'))
>>> t.output()
[(Place('p', MultiSet([]), tAll), Variable('x'))]
>>> t.remove_output(p)
>>> t.output()
[]
Call API
Exceptions

Method Transition.output

def output (self) :

Return the list of output arcs.

>>> t = Transition('t')
>>> t.add_output(Place('p'), Expression('x+1'))
>>> t.output()
[(Place('p', MultiSet([]), tAll), Expression('x+1'))]
Call API

Method Transition.activated

def activated (self, binding) :

Check if binding activates the transition. This is the case if the guard evaluates to True and if the types of the places are respected. Note that the presence of enough tokens is not required.

>>> t = Transition('t', Expression('x>0'))
>>> p = Place('p', [], tInteger)
>>> t.add_input(p, Variable('x'))
>>> t.activated(Substitution(x=1))
True
>>> t.activated(Substitution(x=-1))
False
>>> t.activated(Substitution(x=3.14))
False
Call API

Method Transition.enabled

def enabled (self, binding) :

Check if binding enables the transition. This is the case if the transition is activated and if the input places hold enough tokens to allow the firing.

>>> t = Transition('t', Expression('x>0'))
>>> p = Place('p', [0], tInteger)
>>> t.add_input(p, Variable('x'))
>>> t.enabled(Substitution(x=1))
False
>>> p.add(1)
>>> t.enabled(Substitution(x=1))
True
Call API

Method Transition.vars

def vars (self) :

Return the set of variables involved in the guard, input and output arcs of the transition.

>>> t = Transition('t', Expression('z is not None'))
>>> px = Place('px')
>>> t.add_input(px, Variable('x'))
>>> py = Place('py')
>>> t.add_output(py, Variable('y'))
>>> list(sorted(t.vars()))
['x', 'y', 'z']
Call API

Method Transition.substitute

def substitute (self, binding) :

Substitute all the annotations arround the transition. binding is used to substitute the guard and all the labels on the arcs attached to the transition.

>>> t = Transition('t', Expression('z is not None'))
>>> px = Place('px')
>>> t.add_input(px, Variable('x'))
>>> py = Place('py')
>>> t.add_output(py, Variable('y'))
>>> t.substitute(Substitution(x='a', y='b', z='c'))
>>> t
Transition('t', Expression('(c is not None)'))
>>> t.input()
[(Place('px', MultiSet([]), tAll), Variable('a'))]
>>> t.output()
[(Place('py', MultiSet([]), tAll), Variable('b'))]
Call API

Method Transition.modes

def modes (self) :

Return the list of bindings which enable the transition. Note that the modes are usually considered to be the list of bindings that activate a transitions. However, this list may be infinite so we retricted ourselves to actual modes, taking into account only the tokens actually present in the input places.

>>> t = Transition('t', Expression('x!=y'))
>>> px = Place('px', range(2))
>>> t.add_input(px, Variable('x'))
>>> py = Place('py', range(2))
>>> t.add_input(py, Variable('y'))
>>> m = t.modes()
>>> len(m)
2
>>> Substitution(y=0, x=1) in m
True
>>> Substitution(y=1, x=0) in m
True

Note also that modes cannot be computed with respect to output arcs: indeed, only input arcs allow for concretely associate values to variables; on the other hand, binding an output arc would require to solve the equation provided by the guard.

>>> t = Transition('t', Expression('x!=y'))
>>> px = Place('px', range(2))
>>> t.add_input(px, Variable('x'))
>>> py = Place('py')
>>> t.add_output(py, Variable('y'))
>>> t.modes()
Traceback (most recent call last):
  ...
NameError: name 'y' is not defined
Call API

Method Transition.flow

def flow (self, binding) :

Return the token flow for a firing with binding. The flow is represented by a pair (in, out), both being instances of the class Marking.

>>> t = Transition('t', Expression('x!=1'))
>>> px = Place('px', range(3))
>>> t.add_input(px, Variable('x'))
>>> py = Place('py')
>>> t.add_output(py, Expression('x+1'))
>>> t.flow(Substitution(x=0))
(Marking({'px': MultiSet([0])}), Marking({'py': MultiSet([1])}))
>>> try : t.flow(Substitution(x=1))
... except ValueError : print(sys.exc_info()[1])
transition not enabled for {x -> 1}
>>> t.flow(Substitution(x=2))
(Marking({'px': MultiSet([2])}), Marking({'py': MultiSet([3])}))
Call API
Exceptions

Method Transition.fire

def fire (self, binding) :

Fire the transition with binding.

>>> t = Transition('t', Expression('x!=1'))
>>> px = Place('px', range(3))
>>> t.add_input(px, Variable('x'))
>>> py = Place('py')
>>> t.add_output(py, Expression('x+1'))
>>> t.fire(Substitution(x=0))
>>> px.tokens == MultiSet([1, 2])
True
>>> py.tokens == MultiSet([1])
True
>>> try : t.fire(Substitution(x=1))
... except ValueError : print(sys.exc_info()[1])
transition not enabled for {x -> 1}
>>> t.fire(Substitution(x=2))
>>> px.tokens == MultiSet([1])
True
>>> py.tokens == MultiSet([1, 3])
True
Call API
Exceptions

Marking, Petri nets and state graphs

Class Marking

class Marking (hdict) :

A marking of a Petri net. This is basically a snakes.hashables.hdict mapping place names to multisets of tokens.

The parameters for the constructor must be given in a form suitable for initialising a hdict with place names as keys and multisets as values. Places not given in the marking are assumed empty. A Marking object is independent of any Petri net and so its list of places is not related to the places actually present in a given net. This allows in particular to extract a Marking from one Petri net and to assign it to another.

Method Marking.__str__

def __str__ (self) :

Method Marking.__call__

def __call__ (self, place) :

Return the marking of place. The empty multiset is returned if place is not explicitely given in the marking.

>>> m = Marking(p1=MultiSet([1]), p2=MultiSet([2]))
>>> m('p1')
MultiSet([1])
>>> m('p')
MultiSet([])
Call API

Method Marking.copy

def copy (self) :

Copy a marking

>>> m = Marking(p1=MultiSet([1]), p2=MultiSet([2]))
>>> m.copy() == Marking({'p2': MultiSet([2]), 'p1': MultiSet([1])})
True

Method Marking.__add__

def __add__ (self, other) :

Addition of markings.

>>> Marking(p1=MultiSet([1]), p2=MultiSet([2])) + Marking(p2=MultiSet([2]), p3=MultiSet([3])) == Marking({'p2': MultiSet([2, 2]), 'p3': MultiSet([3]), 'p1': MultiSet([1])})
True
Call API

Method Marking.__sub__

def __sub__ (self, other) :

Substraction of markings.

>>> Marking(p1=MultiSet([1]), p2=MultiSet([2, 2])) - Marking(p2=MultiSet([2]))
Marking({'p2': MultiSet([2]), 'p1': MultiSet([1])})
>>> try : Marking(p1=MultiSet([1]), p2=MultiSet([2])) - Marking(p2=MultiSet([2, 2]))
... except ValueError : print(sys.exc_info()[1])
not enough occurrences
>>> Marking(p1=MultiSet([1]), p2=MultiSet([2])) - Marking(p3=MultiSet([3]))
Traceback (most recent call last):
  ...
DomainError: 'p3' absent from the marking
Call API

Method Marking.__ge__

def __ge__ (self, other) :

Test if the marking self is greater than or equal to other. This is the case when any place in other is also in self and is marked with a smaller or equal multiset of tokens.

>>> Marking(p=MultiSet([1])) >= Marking(p=MultiSet([1]))
True
>>> Marking(p=MultiSet([1, 1])) >= Marking(p=MultiSet([1]))
True
>>> Marking(p=MultiSet([1]), r=MultiSet([2])) >= Marking(p=MultiSet([1]))
True
>>> Marking(p=MultiSet([1])) >= Marking(p=MultiSet([1, 2]))
False
>>> Marking(p=MultiSet([1]), r=MultiSet([2])) >= Marking(p=MultiSet([1, 1]))
False
Call API

Method Marking.__gt__

def __gt__ (self, other) :

Test if the marking self is strictly greater than other. This is the case when any place in other is also in self and either one place in other is marked with a smaller multiset of tokens or slef has more places than other.

>>> Marking(p=MultiSet([1])) > Marking(p=MultiSet([1]))
False
>>> Marking(p=MultiSet([1, 1])) > Marking(p=MultiSet([1]))
True
>>> Marking(p=MultiSet([1]), r=MultiSet([2])) > Marking(p=MultiSet([1]))
True
>>> Marking(p=MultiSet([1])) > Marking(p=MultiSet([1, 2]))
False
>>> Marking(p=MultiSet([1]), r=MultiSet([2])) > Marking(p=MultiSet([1, 1]))
False
Call API

Method Marking.__le__

def __le__ (self, other) :

Test if the marking self is smaller than or equal to other. This is the case when any place in self is also in other and is marked with a smaller or equal multiset of tokens.

>>> Marking(p=MultiSet([1])) <= Marking(p=MultiSet([1]))
True
>>> Marking(p=MultiSet([1])) <= Marking(p=MultiSet([1, 1]))
True
>>> Marking(p=MultiSet([1])) <= Marking(p=MultiSet([1]), r=MultiSet([2]))
True
>>> Marking(p=MultiSet([1, 2])) <= Marking(p=MultiSet([1]))
False
>>> Marking(p=MultiSet([1, 1])) <= Marking(p=MultiSet([1]), r=MultiSet([2]))
False
Call API

Method Marking.__lt__

def __lt__ (self, other) :

Test if the marking self is strictly smaller than other. This is the case when any place in self is also in other and either one place in self marked in self with a strictly smaller multiset of tokens or other has more places than self.

>>> Marking(p=MultiSet([1])) < Marking(p=MultiSet([1]))
False
>>> Marking(p=MultiSet([1])) < Marking(p=MultiSet([1, 1]))
True
>>> Marking(p=MultiSet([1])) < Marking(p=MultiSet([1]), r=MultiSet([2]))
True
>>> Marking(p=MultiSet([1, 2])) < Marking(p=MultiSet([1]))
False
>>> Marking(p=MultiSet([1, 1])) < Marking(p=MultiSet([1]), r=MultiSet([2]))
False
Call API

Class PetriNet

class PetriNet (object) :

A Petri net. As soon as nodes are added to a PetriNet, they should be handled by name instead of by the Place or Transition instance. For instance:

>>> n = PetriNet('N')
>>> t = Transition('t')
>>> n.add_transition(t)
>>> n.has_transition('t') # use 't' and not t
True
>>> n.transition('t') is t
True

Method PetriNet.__init__

def __init__ (self, name) :

Initialise with a name that may be an arbitrary string.

>>> PetriNet('N')
PetriNet('N')
Call API

Method PetriNet.copy

def copy (self, name=None) :

Return a complete copy of the net, including places, transitions, arcs and declarations.

>>> PetriNet('N').copy()
PetriNet('N')
>>> PetriNet('N').copy('x')
PetriNet('x')
Call API

Method PetriNet.rename

def rename (self, name) :

Change the name of the net.

>>> n = PetriNet('N')
>>> n.rename('Long name!')
>>> n
PetriNet('Long name!')
Call API

Method PetriNet.has_place

def has_place (self, name) :

Check if there is a place called name in the net.

>>> n = PetriNet('N')
>>> n.has_place('p')
False
>>> n.add_place(Place('p'))
>>> n.has_place('p')
True
Call API

Method PetriNet.has_transition

def has_transition (self, name) :

Check if there is a transition called name in the net.

>>> n = PetriNet('N')
>>> n.has_transition('t')
False
>>> n.add_transition(Transition('t'))
>>> n.has_transition('t')
True
Call API

Method PetriNet.has_node

def has_node (self, name) :

Check if there is a transition called name in the net.

>>> n = PetriNet('N')
>>> n.has_node('t')
False
>>> n.has_node('p')
False
>>> n.add_transition(Transition('t'))
>>> n.add_place(Place('p'))
>>> n.has_node('t')
True
>>> n.has_node('p')
True
Call API

Method PetriNet.__contains__

def __contains__ (self, name) :

name in net is a shortcut for net.has_node(name)

>>> n = PetriNet('N')
>>> 't' in n
False
>>> 'p' in n
False
>>> n.add_transition(Transition('t'))
>>> n.add_place(Place('p'))
>>> 't' in n
True
>>> 'p' in n
True
Call API

Method PetriNet.declare

def declare (self, statements, locals=None) :

Execute statements in the global dictionnary of the net. This has also on the dictionnarie of the instances of Expression in the net (guards of the transitions and labels on the arcs) so the declarations have an influence over the elements embedded in the net. If locals is given, most of the declared objects will be placed in it instead of the global dictionnary, see the documentation of Python for more details about local and global environments.

>>> n = PetriNet('N')
>>> t = Transition('t', Expression('x==0'))
>>> n.add_transition(t)
>>> t.guard(Substitution())
Traceback (most recent call last):
  ...
NameError: name 'x' is not defined
>>> n.declare('x=0')
>>> t.guard(Substitution())
True
>>> n.add_place(Place('p'))
>>> n.add_output('p', 't', Expression('math.pi'))
>>> t.fire(Substitution())
Traceback (most recent call last):
  ...
NameError: name 'math' is not defined
>>> n.declare('import math')
>>> t.fire(Substitution())
>>> n.place('p')
Place('p', MultiSet([3.14...]), tAll)
Call API

Method PetriNet.add_place

def add_place (self, place) :

Add a place to the net. Each node in a net must have a name unique to this net, which is checked when it is added.

>>> n = PetriNet('N')
>>> n.place('p')
Traceback (most recent call last):
  ...
ConstraintError: place 'p' not found
>>> n.add_place(Place('p', range(3)))
>>> n.place('p')
Place('p', MultiSet([...]), tAll)
>>> n.place('p').tokens == MultiSet([0, 1, 2])
True
>>> n.add_place(Place('p'))
Traceback (most recent call last):
  ...
ConstraintError: place 'p' exists
Call API
Exceptions

Method PetriNet.remove_place

def remove_place (self, name) :

Remove a place (given by its name) from the net.

>>> n = PetriNet('N')
>>> n.remove_place('p')
Traceback (most recent call last):
  ...
ConstraintError: place 'p' not found
>>> n.add_place(Place('p', range(3)))
>>> n.place('p')
Place('p', MultiSet([...]), tAll)
>>> n.remove_place('p')
>>> n.place('p')
Traceback (most recent call last):
  ...
ConstraintError: place 'p' not found
Call API
Exceptions

Method PetriNet.add_transition

def add_transition (self, trans) :

Add a transition to the net. Each node in a net must have a name unique to this net, which is checked when it is added.

>>> n = PetriNet('N')
>>> n.transition('t')
Traceback (most recent call last):
  ...
ConstraintError: transition 't' not found
>>> n.add_transition(Transition('t', Expression('x==1')))
>>> n.transition('t')
Transition('t', Expression('x==1'))
>>> n.add_transition(Transition('t'))
Traceback (most recent call last):
  ...
ConstraintError: transition 't' exists
Call API
Exceptions

Method PetriNet.remove_transition

def remove_transition (self, name) :

Remove a transition (given by its name) from the net.

>>> n = PetriNet('N')
>>> n.remove_transition('t')
Traceback (most recent call last):
  ...
ConstraintError: transition 't' not found
>>> n.add_transition(Transition('t', Expression('x==1')))
>>> n.transition('t')
Transition('t', Expression('x==1'))
>>> n.remove_transition('t')
>>> n.transition('t')
Traceback (most recent call last):
  ...
ConstraintError: transition 't' not found
Call API
Exceptions

Method PetriNet.place

def place (self, name=None) :

Return one (if name is not None) or all the places.

>>> n = PetriNet('N')
>>> n.add_place(Place('p1'))
>>> n.add_place(Place('p2'))
>>> n.place('p1')
Place('p1', MultiSet([]), tAll)
>>> n.place('p')
Traceback (most recent call last):
  ...
ConstraintError: place 'p' not found
>>> n.place()
[Place('p2', MultiSet([]), tAll), Place('p1', MultiSet([]), tAll)]
Call API
Exceptions

Method PetriNet.transition

def transition (self, name=None) :

Return one (if name is not None) or all the transitions.

>>> n = PetriNet('N')
>>> n.add_transition(Transition('t1'))
>>> n.add_transition(Transition('t2'))
>>> n.transition('t1')
Transition('t1', Expression('True'))
>>> n.transition('t')
Traceback (most recent call last):
  ...
ConstraintError: transition 't' not found
>>> n.transition()
[Transition('t2', Expression('True')),
 Transition('t1', Expression('True'))]
Call API
Exceptions

Method PetriNet.node

def node (self, name=None) :

Return one (if name is not None) or all the nodes.

>>> n = PetriNet('N')
>>> n.add_transition(Transition('t'))
>>> n.add_place(Place('p'))
>>> n.node('t')
Transition('t', Expression('True'))
>>> n.node('x')
Traceback (most recent call last):
  ...
ConstraintError: node 'x' not found
>>> list(sorted(n.node(), key=str))
[Place('p', MultiSet([]), tAll), Transition('t', Expression('True'))]
Call API
Exceptions

Method PetriNet.add_input

def add_input (self, place, trans, label) :

Add an input arc between place and trans (nodes names). An input arc is directed from a place toward a transition.

>>> n = PetriNet('N')
>>> n.add_place(Place('p', range(3)))
>>> n.add_transition(Transition('t', Expression('x!=1')))
>>> try : n.add_input('p', 't', Expression('2*x'))
... except ConstraintError : print(sys.exc_info()[1])
'Expression' not allowed on input arcs
>>> n.add_input('p', 't', Variable('x'))
>>> (n.post('p'), n.pre('t')) == (set(['t']), set(['p']))
True
>>> n.transition('t').modes()
[Substitution(x=0), Substitution(x=2)]
>>> n.place('p').tokens == MultiSet([0, 1, 2])
True
>>> n.transition('t').fire(Substitution(x=0))
>>> n.place('p').tokens == MultiSet([1, 2])
True
>>> try : n.add_input('p', 't', Value(42))
... except ConstraintError: print(sys.exc_info()[1])
already connected to 'p'
Call API
Exceptions

Method PetriNet.remove_input

def remove_input (self, place, trans) :

Remove an input arc between place and trans (nodes names).

>>> n = PetriNet('N')
>>> n.add_place(Place('p', range(3)))
>>> n.add_transition(Transition('t', Expression('x!=1')))
>>> n.add_input('p', 't', Variable('x'))
>>> (n.post('p'), n.pre('t')) == (set(['t']), set(['p']))
True
>>> n.remove_input('p', 't')
>>> (n.post('p'), n.pre('t')) == (set([]), set([]))
True
>>> try : n.remove_input('p', 't')
... except ConstraintError : print(sys.exc_info()[1])
not connected to 'p'
Call API
Exceptions

Method PetriNet.add_output

def add_output (self, place, trans, label) :

Add an output arc between place and trans (nodes names).

An output arc is directed from a transition toward a place.

>>> n = PetriNet('N')
>>> n.add_place(Place('p'))
>>> n.add_transition(Transition('t'))
>>> n.add_output('p', 't', Value(42))
>>> (n.post('t'), n.pre('p')) == (set(['p']), set(['t']))
True
>>> n.place('p').tokens == MultiSet([])
True
>>> n.transition('t').fire(Substitution())
>>> n.place('p').tokens == MultiSet([42])
True
>>> try : n.add_output('p', 't', Value(42))
... except ConstraintError : print(sys.exc_info()[1])
already connected to 'p'
Call API
Exceptions

Method PetriNet.remove_output

def remove_output (self, place, trans) :

Remove an output arc between place and trans (nodes names).

>>> n = PetriNet('N')
>>> n.add_place(Place('p'))
>>> n.add_transition(Transition('t'))
>>> n.add_output('p', 't', Value(42))
>>> (n.post('t'), n.pre('p')) == (set(['p']), set(['t']))
True
>>> n.remove_output('p', 't')
>>> (n.post('t'), n.pre('p')) == (set([]), set([]))
True
>>> try : n.remove_output('p', 't')
... except ConstraintError : print(sys.exc_info()[1])
not connected to 'p'
Call API
Exceptions

Method PetriNet.pre

def pre (self, nodes) :

Return the set of nodes names preceeding nodes. nodes can be a single node name ot a list of nodes names.

>>> n = PetriNet('N')
>>> n.add_place(Place('p1'))
>>> n.add_place(Place('p2'))
>>> n.add_transition(Transition('t1'))
>>> n.add_transition(Transition('t2'))
>>> n.add_output('p1', 't1', Value(1))
>>> n.add_output('p2', 't2', Value(2))
>>> n.pre('p1') == set(['t1'])
True
>>> n.pre(['p1', 'p2']) == set(['t2', 't1'])
True
Call API

Method PetriNet.post

def post (self, nodes) :

Return the set of nodes names succeeding nodes. nodes can be a single node name ot a list of nodes names.

>>> n = PetriNet('N')
>>> n.add_place(Place('p1'))
>>> n.add_place(Place('p2'))
>>> n.add_transition(Transition('t1'))
>>> n.add_transition(Transition('t2'))
>>> n.add_output('p1', 't1', Value(1))
>>> n.add_output('p2', 't2', Value(2))
>>> n.post('t1') == set(['p1'])
True
>>> n.post(['t1', 't2']) == set(['p2', 'p1'])
True
Call API

Method PetriNet.get_marking

def get_marking (self) :

Return the current marking of the net, omitting empty places.

>>> n = PetriNet('N')
>>> n.add_place(Place('p0', range(0)))
>>> n.add_place(Place('p1', range(1)))
>>> n.add_place(Place('p2', range(2)))
>>> n.add_place(Place('p3', range(3)))
>>> n.get_marking() == Marking({'p2': MultiSet([0, 1]), 'p3': MultiSet([0, 1, 2]), 'p1': MultiSet([0])})
True
Call API

Method PetriNet.set_marking

def set_marking (self, marking) :

Assign a marking to the net. Places not listed in the marking are considered empty, the corresponding place in the net is thus emptied. If the marking has places that do not belong to the net, these are ignored (as in the last instruction below). If an error occurs during the assignment, the marking is left unchanged.

>>> n = PetriNet('N')
>>> n.add_place(Place('p0', range(5), tInteger))
>>> n.add_place(Place('p1'))
>>> n.add_place(Place('p2'))
>>> n.add_place(Place('p3'))
>>> n.get_marking() == Marking({'p0': MultiSet([0, 1, 2, 3, 4])})
True
>>> n.set_marking(Marking(p1=MultiSet([0]), p2=MultiSet([0, 1]), p3=MultiSet([0, 1, 2])))
>>> n.get_marking() == Marking({'p2': MultiSet([0, 1]), 'p3': MultiSet([0, 1, 2]), 'p1': MultiSet([0])})
True
>>> n.set_marking(Marking(p=MultiSet([0])))
>>> n.get_marking()
Marking({})
>>> try : n.set_marking(Marking(p2=MultiSet([1]), p0=MultiSet([3.14])))
... except ValueError : print(sys.exc_info()[1])
forbidden token '3.14'
>>> n.get_marking() # unchanged
Marking({})
Call API

Method PetriNet.add_marking

def add_marking (self, marking) :

Add a marking to the current one. If an error occurs during the process, the marking is left unchanged. Places in the marking that do not belong to the net are ignored.

>>> n = PetriNet('N')
>>> n.add_place(Place('p1'))
>>> n.add_place(Place('p2', range(3)))
>>> n.get_marking() == Marking({'p2': MultiSet([0, 1, 2])})
True
>>> n.add_marking(Marking(p1=MultiSet(range(2)), p2=MultiSet([1])))
>>> n.get_marking() == Marking({'p2': MultiSet([0, 1, 1, 2]), 'p1': MultiSet([0, 1])})
True
Call API

Method PetriNet.remove_marking

def remove_marking (self, marking) :

Substract a marking from the current one. If an error occurs during the process, the marking is left unchanged. Places in the marking that do not belong to the net are ignored.

>>> n = PetriNet('N')
>>> n.add_place(Place('p1'))
>>> n.add_place(Place('p2', range(3)))
>>> n.get_marking() == Marking({'p2': MultiSet([0, 1, 2])})
True
>>> try : n.remove_marking(Marking(p1=MultiSet(range(2)), p2=MultiSet([1])))
... except ValueError : print(sys.exc_info()[1])
not enough occurrences
>>> n.get_marking() == Marking({'p2': MultiSet([0, 1, 2])})
True
>>> n.remove_marking(Marking(p2=MultiSet([1])))
>>> n.get_marking() == Marking({'p2': MultiSet([0, 2])})
True
Call API

Method PetriNet.rename_node

def rename_node (self, old, new) :

Change the name of a node.

>>> n = PetriNet('N')
>>> n.add_place(Place('p'))
>>> n.add_transition(Transition('t'))
>>> n.add_output('p', 't', Value(0))
>>> list(sorted(n.node(), key=str))
[Place('p', MultiSet([]), tAll), Transition('t', Expression('True'))]
>>> n.post('t') == set(['p'])
True
>>> n.rename_node('p', 'new_p')
>>> list(sorted(n.node(), key=str))
[Place('new_p', MultiSet([]), tAll), Transition('t', Expression('True'))]
>>> n.post('t') == set(['new_p'])
True
>>> try : n.rename_node('new_p', 't')
... except ConstraintError : print(sys.exc_info()[1])
node 't' exists
>>> try : n.rename_node('old_t', 'new_t')
... except ConstraintError : print(sys.exc_info()[1])
node 'old_t' not found
Call API

Method PetriNet.copy_place

def copy_place (self, source, targets) :

Make copies of the source place (use place names).

>>> n = PetriNet('N')
>>> n.add_place(Place('p', range(3)))
>>> n.add_transition(Transition('t'))
>>> n.add_input('p', 't', Value(0))
>>> n.copy_place('p', ['bis', 'ter'])
>>> n.copy_place('bis', 'more')
>>> list(sorted(n.place(), key=str))
[Place('bis', MultiSet([...]), tAll),
 Place('more', MultiSet([...]), tAll),
 Place('p', MultiSet([...]), tAll),
 Place('ter', MultiSet([...]), tAll)]
>>> list(sorted(n.pre('t'), key=str))
['bis', 'more', 'p', 'ter']
Call API

Method PetriNet.copy_transition

def copy_transition (self, source, targets) :

Make copies of the source transition (use transition names).

>>> n = PetriNet('N')
>>> n.add_transition(Transition('t', Expression('x==1')))
>>> n.add_place(Place('p'))
>>> n.add_input('p', 't', Value(0))
>>> n.copy_transition('t', ['bis', 'ter'])
>>> n.copy_transition('bis', 'more')
>>> list(sorted(n.transition(), key=str))
[Transition('bis', Expression('x==1')),
 Transition('more', Expression('x==1')),
 Transition('t', Expression('x==1')),
 Transition('ter', Expression('x==1'))]
>>> list(sorted(n.post('p')))
['bis', 'more', 't', 'ter']
Call API

Method PetriNet.merge_places

def merge_places (self, target, sources) :

Create a new place by merging those in sources. Markings are added, place types are 'or'ed and arcs labels are joinded into multi-arcs, the sources places are not removed. Use places names.

>>> n = PetriNet('n')
>>> n.add_place(Place('p1', [1], tInteger))
>>> n.add_place(Place('p2', [2.0], tFloat))
>>> n.add_transition(Transition('t1'))
>>> n.add_transition(Transition('t2'))
>>> n.add_output('p1', 't1', Value(1))
>>> n.add_output('p2', 't2', Value(2.0))
>>> n.add_output('p2', 't1', Value(2.0))
>>> n.merge_places('p', ['p1', 'p2'])
>>> (n.pre('p'), n.post('t1')) == (set(['t2', 't1']), set(['p2', 'p', 'p1']))
True
>>> list(sorted(n.node('p').pre.items()))
[('t1', MultiArc((Value(1), Value(2.0)))),
 ('t2', Value(2.0))]
>>> n.node('p').tokens == MultiSet([1, 2.0])
True
>>> n.node('p').checker()
(Instance(int) | Instance(float))
Call API

Method PetriNet.merge_transitions

def merge_transitions (self, target, sources) :

Create a new transition by merging those in sources. Guards are 'and'ed and arcs labels are joinded into multi- arcs, the sources transitions are not removed. Use transitions names.

>>> n = PetriNet('n')
>>> n.add_place(Place('p1'))
>>> n.add_place(Place('p2'))
>>> n.add_transition(Transition('t1', Expression('x==1')))
>>> n.add_transition(Transition('t2', Expression('y==2')))
>>> n.add_output('p1', 't1', Value(1))
>>> n.add_output('p2', 't2', Value(2.0))
>>> n.add_output('p2', 't1', Value(2.0))
>>> n.merge_transitions('t', ['t1', 't2'])
>>> list(sorted(n.post('t'), key=str))
['p1', 'p2']
>>> list(sorted(n.pre('p2'), key=str))
['t', 't1', 't2']
>>> n.transition('t')
Transition('t', Expression('(x==1) and (y==2)'))
>>> n.node('t').post
{'p2': MultiArc((Value(2.0), Value(2.0))), 'p1': Value(1)}
Call API

Class StateGraph

class StateGraph (object) :

The graph of reachable markings of a net.

Method StateGraph.__init__

def __init__ (self, net) :

Initialise with the net.

>>> StateGraph(PetriNet('N')).net
PetriNet('N')
Call API

Method StateGraph.goto

def goto (self, state) :

Change the current state to another (given by its number). This also changes the marking of the net consistently. Notice that the state may not exist yet.

>>> n = PetriNet('N')
>>> n.add_place(Place('p', [0]))
>>> n.add_transition(Transition('t', Expression('x<5')))
>>> n.add_input('p', 't', Variable('x'))
>>> n.add_output('p', 't', Expression('x+1'))
>>> g = StateGraph(n)
>>> try : g.goto(2)
... except ValueError : print(sys.exc_info()[1])
unknown state
>>> g.build()
>>> g.goto(2)
>>> g.net.get_marking()
Marking({'p': MultiSet([2])})
Call API

Method StateGraph.current

def current (self) :

Return the number of the current state.

>>> n = PetriNet('N')
>>> n.add_place(Place('p', [0]))
>>> n.add_transition(Transition('t', Expression('x<5')))
>>> n.add_input('p', 't', Variable('x'))
>>> n.add_output('p', 't', Expression('x+1'))
>>> g = StateGraph(n)
>>> g.current()
0
Call API

Method StateGraph.__getitem__

def __getitem__ (self, state) :

Return a marking by its state number.

Method StateGraph.__contains__

def __contains__ (self, marking) :

Method StateGraph.successors

def successors (self, state=None) :

Return the successors of the current state. The value returned is a iterator over triples (succ, trans, mode) representing the number of the successor, the name of the transition and the binding needed to reach the new state.

>>> n = PetriNet('N')
>>> n.add_place(Place('p', [0]))
>>> n.add_transition(Transition('t', Expression('x<5')))
>>> n.add_input('p', 't', Variable('x'))
>>> n.add_output('p', 't', Expression('x+1'))
>>> g = StateGraph(n)
>>> g.build()
>>> g.goto(2)
>>> list(g.successors())
[(3, Transition('t', Expression('x<5')), Substitution(x=2))]
Call API

Method StateGraph.predecessors

def predecessors (self, state=None) :

Return the predecessors states. The returned value is as in successors. Notice that if the graph is not complete, this value may be wrong: states computed in the future may lead to the current one thus becoming one of its predecessors.

>>> n = PetriNet('N')
>>> n.add_place(Place('p', [0]))
>>> n.add_transition(Transition('t', Expression('x<5')))
>>> n.add_input('p', 't', Variable('x'))
>>> n.add_output('p', 't', Expression('x+1'))
>>> g = StateGraph(n)
>>> g.build()
>>> g.goto(2)
>>> list(g.predecessors())
[(1, Transition('t', Expression('x<5')), Substitution(x=1))]
Call API

Method StateGraph.__len__

def __len__ (self) :

Return the number of states currently reached.

>>> n = PetriNet('N')
>>> n.add_place(Place('p', [0]))
>>> n.add_transition(Transition('t', Expression('x<5')))
>>> n.add_input('p', 't', Variable('x'))
>>> n.add_output('p', 't', Expression('x+1'))
>>> g = StateGraph(n)
>>> len(g)
1
>>> for state in g :
...     print('%s states known' % len(g))
2 states known
3 states known
4 states known
5 states known
6 states known
6 states known
Call API

Method StateGraph.__iter__

def __iter__ (self) :

Iterate over the reachable states (numbers). If needed, the successors of each state are computed just before it is yield. So, if the graph is not complete, getting the predecessors may be wrong during the iteration.

Warning: the net may have an infinite state graph, which is not checked. So you may enter an infinite iteration.

>>> n = PetriNet('N')
>>> n.add_place(Place('p', [0]))
>>> n.add_transition(Transition('t', Expression('x<5')))
>>> n.add_input('p', 't', Variable('x'))
>>> n.add_output('p', 't', Expression('x+1'))
>>> g = StateGraph(n)
>>> for state in g :
...     print('state %s is %r' % (state, g.net.get_marking()))
state 0 is Marking({'p': MultiSet([0])})
state 1 is Marking({'p': MultiSet([1])})
state 2 is Marking({'p': MultiSet([2])})
state 3 is Marking({'p': MultiSet([3])})
state 4 is Marking({'p': MultiSet([4])})
state 5 is Marking({'p': MultiSet([5])})
>>> n = PetriNet('N')
>>> n.add_place(Place('p', [0]))
>>> n.add_transition(Transition('t'))
>>> n.add_input('p', 't', Variable('x'))
>>> n.add_output('p', 't', Expression('(x+1) % 5'))
>>> g = StateGraph(n)
>>> for state in g :
...     print('state %s is %r' % (state, g.net.get_marking()))
state 0 is Marking({'p': MultiSet([0])})
state 1 is Marking({'p': MultiSet([1])})
state 2 is Marking({'p': MultiSet([2])})
state 3 is Marking({'p': MultiSet([3])})
state 4 is Marking({'p': MultiSet([4])})

Method StateGraph.build

def build (self) :

Method StateGraph.completed

def completed (self) :

Check if all the reachable markings have been explored.

>>> n = PetriNet('N')
>>> n.add_place(Place('p', [0]))
>>> n.add_transition(Transition('t', Expression('x<5')))
>>> n.add_input('p', 't', Variable('x'))
>>> n.add_output('p', 't', Expression('x+1'))
>>> g = StateGraph(n)
>>> for state in g :
...     print('%s %s' % (state, g.completed()))
0 False
1 False
2 False
3 False
4 False
5 True
Call API

Method StateGraph.todo

def todo (self) :

Return the number of states whose successors are not yet computed.

>>> n = PetriNet('N')
>>> n.add_place(Place('p', [0]))
>>> n.add_transition(Transition('t', Expression('x<5')))
>>> n.add_input('p', 't', Variable('x'))
>>> n.add_output('p', 't', Expression('x+1'))
>>> g = StateGraph(n)
>>> for state in g :
...     print('%s %s' % (state, g.todo()))
0 1
1 1
2 1
3 1
4 1
5 0
Call API