# The Azimuth Project Blog - Petri net programming (part 1) (Rev #22, changes)

Showing changes from revision #21 to #22: Added | Removed | Changed

# Contents

## Introduction

Petri nets are a simple model of computation with a range of modelling applications that include chemical reaction networks, manufacturing processes, and population biology dynamics. They are graphs through which entities flow and have their types transformed. They also have far-reaching mathematical properties which are the subject of an extensive literature. See the Network Theory [Reference #1] series on the Azimuth Blog for a panoramic and well-diagrammed introduction to the theory and applications of Petri nets.

In this type of article, we approach the concepts with the aim of writing programs that make them work. The readership would include coding enthusiasts, professional programmers, scientists interested in programming models, software engineers and computer scientists. The articles will start with a tutorial recap of the topic, and then give programs that exercise the concepts, accompanied by a concept-based dissection of the code. The sample programs should give the reader some clay to work with.

This article is the first of two on Petri net programming. Here we introduce Petri nets and give an expository presentation of a simulator program. This will be applied to a “toy” model of a chemical reaction network for the synthesis and dissociation of H2O molecules. The following article will extend the discussion and the code to cover stochastic Petri nets, where the sequencing of the Petri net is probabilistically controlled.

## Definition of Petri nets

A Petri net is a diagram with two kinds of nodes: container nodes, called species (or “places”, “states”), which can hold zero or more tokens, and transition nodes, which are “wired” to some containers called its inputs and some called its outputs. A transition can have multiple input or output connections to a single container.

When a transition fires, it removes one token from each input container, and adds one token to each output container. When there are multiple inputs or outputs to the same container, then that many tokens get removed or added.

The total state of the Petri net is described by a labelling function which maps each container to the number of tokens it holds. A transition is enabled to fire in a labelling if there are enough tokens at its input containers. If no transitions are enabled, the it is halted. The sequencing is non-deterministic, because in a given labelling there may be several transitions that are enabled. Dataflow arises whenever one transition sends tokens to a container that is read by another transition.

## Using Petri nets to model Entity Conversion Networks

Petri nets can describe networks consisting of entities of different species, with conversion reactions from the input species to the output species. Each entity is symbolized by a token in the net, and all the tokens for a species are grouped into a container. The conversion of entities is symbolized by a transition that consumes input tokens and generates output tokens.

One application is to population dynamics. The tokens represent organisms, and the species are biological species. A simplified example, involving two species, Wolf and Rabbit, is given on the Network Theory page. There are three transitions: Birth, which takes one Rabbit in and outputs two Rabbits (like asexual reproduction), Predation, which converts Rabbit plus Wolf to Wolf plus Wolf, and Death, which inputs one Wolf and outputs nothing.

A major application is to chemical reaction networks. The entities are chemical units: molecules, isolated atoms, or ions. Each chemical unit is represented by a token in the net, and a container holds all of the tokens for a chemical species. Chemical reactions are then modeled as transitions that consume input tokens and generate output tokens.

We will be using the following simplified model for water formation and dissociation:

• The species are H, O, and H2O.

• Transition combine inputs two H atoms and one O atom, and outputs an H2O molecule.

• Transition split is the reverse process, which inputs one H2 and outputs two H and one O.

Note that the model is intended to show the idea of chemical reaction Petri nets, so it is not physically realistic. The actual reactions involve H2 and O2, and there are intermediate transitions as well. For more details, see Part 3 of the Network Theory series.

## A Petri net simulator

The following Python script will simulate a Petri net, using parameters that describe the species, the transitions, and the initial labelling. It will run the net for a specified number of steps. At each step it chooses randomly among the enabled transitions, fires it, and prints the new labelling on the console.

Here is the first Petri net simulator.

### Running the script

The model parameters are already coded into the script. So let’s give it whirl:

python petri_simulator1.py petri1.py

This produced the output:

H, O, H2O, Transition
5, 3, 4, split
7, 4, 3, split
9, 5, 2, combine
7, 4, 3, combine
5, 3, 4, split
7, 4, 3, split
9, 5, 2, split
11, 6, 1, combine
9, 5, 2, split
11, 6, 1, split
13, 7, 0, ...

We started out in a state with 5 H’s, 3 O’s, and 2 H2O’s, then a split took place, which increased H by 2, increased O by 1, and decreased H2O by one, then…

Running it again gives a different execution sequence.

### Top-level structure of the script

First construct the Petri net:

 petriNet # combine: 2H + 1O -> 1H2O combineSpec = PetriNet( ("combine", [["H",2],["O",1]], [["H2O",1]])     ["H", "O", "H2O"],    # species split: 1H2O -> 2H + 1O  splitSpec = ("split", [["H2O",1]], [["H",2],["O",1]])  petriNet = PetriNet(   ["combine", "split"], # transitions   ["H","O","H2O"],   [("combine",2,"H"), ("combine",1,"O"), ("split",1,"H2O")], # inputs     [("combine",1,"H2O"), ("split",2,"H"), ("split",1,"O")],   # outputs species     # [combineSpec,splitSpec]     # combine transitions has 2 H inputs, 1 O input, and 1 H2O output      # split has 1 H20 input, 2 H outputs, and 1 O output  )

Then establish the initial labelling:

initialLabelling = {"H":5, "O":3, "H2O":4}

Then run it for twenty steps:

steps = 20
petriNet.RunSimulation(steps, initialLabelling)

Exercise: Improve the script to accept the model parameters from the command-line.

### Program code

Species will be represented simply by their names, i.e., as strings. PetriNet and Transition will be defined as object classes. Each PetriNet instance holds a list of species names, a list of transition names, a list of Transition objects, and the current labelling. The labelling is a dictionary from species name to token count. Each Transition object contains an input dictionary and an input dictionary. The input dictionary map the name of a species to the number of times that the transition takes it as input, and similarly for the output dictionary.

#### Class PetriNet

The PetriNet class has a top-level method called RunSimulation, which makes repeated calls to FireOneRule. FireOneRule obtains the list of enabled transitions, chooses one randomly, and fires it.

class PetriNet(PetriNetBase):
def RunSimulation(this, iterations, initialLabelling):
this.PrintHeader()  # prints e.g. "H, O, H2O"
this.labelling = initialLabelling
this.PrintLabelling() # prints e.g. "3, 5, 2"

for i in range(iterations):
if this.IsHalted():
print "halted"
return
else:
this.FireOneRule()
this.PrintLabelling();
print "iterations completed"

def EnabledTransitions(this):
return filter(lambda transition: transition.IsEnabled(this.labelling), this.transitions)

def IsHalted(this):
return len(this.EnabledTransitions()) == 0

def FireOneRule(this):
this.SelectRandom(this.EnabledTransitions()).Fire(this.labelling)

#### Class Transition

The Transition class exposes two key methods. IsEnabled takes a labeling as parameter, and returns a boolean saying whether the transition can fire. This is determined by comparing the input map for the transition with the token counts in the labeling, to see if there is sufficient tokens for it to fire. The Fire method takes a labelling in, and updates the counts in it to reflect the action of removing input tokens and creating output tokens.

class Transition:
# Fields:
# transitionName
# inputMap: speciesName -> inputCount
# outputMap: speciesName -> outputCount

# constructor
def __init__(this, transitionName):
this.transitionName = transitionName
this.inputMap = {}
this.outputMap = {}

def IsEnabled(this, labelling):
for inputSpecies in this.inputMap.keys():
if labelling[inputSpecies] < this.inputMap[inputSpecies]:
return False  # not enough tokens
return True # good to go

def Fire(this, labelling):
print this.transitionName
for inputName in this.inputMap.keys():
labelling[inputName] = labelling[inputName] - this.inputMap[inputName]
for outputName in this.outputMap.keys():
labelling[outputName] = labelling[outputName] + this.outputMap[outputName] 

#### Class PetriNetBase

Notice that the class line for PetriNet declares that it inherits from a base class PetriNetBase. The base class contains utility methods that support PetriNet: PrintHeader, PrintLabelling, SelectRandom, and the constructor, which converts the transition specifications into Transition objects.

 class PetriNetBase:     # Fields:     # speciesNames     # transitionNames      # Transition list     # labelling: speciesName -> token count       # constructor     def __init__(this, speciesNames, transitionNames, transitionSpecs): inputMap, outputMap):         this.speciesNames = speciesNames         this.transitionNames this.transitions = transitionNames this.BuildTransitions(transitionSpecs)      def BuildTransitions(this, transitionSpecs):     this.transitions = this.BuildTransitions(inputMap, outputMap)     transitions = []  def BuildTransitions(this, inputSpecs, outputSpecs):        for (transitionName, inputSpecs, outputSpecs) in transitionSpecs:  # map: transitionName -> Transition object          transitionMap   transition = {} Transition(transitionName)             for (transitionName, degreeSpec degree, speciesName) in inputSpecs:            if not(transitionMap.has_key(transitionName)):      this.SetDegree(transition.inputMap, degreeSpec)           transitionMap[transitionName] = Transition(transitionName)   for degreeSpec in outputSpecs:          transitionMap[transitionName].inputMap[speciesName] = degree        this.SetDegree(transition.outputMap, degreeSpec)   for (transitionName, degree, speciesName) in outputSpecs:           transitions.append(transition)  if not(transitionMap.has_key(transitionName)):        return transitions       def SetDegree(this, dictionary, degreeSpec):   transitionMap[transitionName]       speciesName = Transition(transitionName) degreeSpec[0]         if len(degreeSpec) == 2:    transitionMap[transitionName].outputMap[speciesName]  =         degree = degreeSpec[1]         else:   return  transitionMap.values()           degree = 1         dictionary[speciesName] = degree      def PrintHeader(this):         print string.join(this.speciesNames, ", ") + ", Transition"                 def PrintLabelling(this):         for speciesName in this.speciesNames:             print str(this.labelling[speciesName]) + ",",       def SelectRandom(this, list):         return list[randrange(len(list))]

## Summary

We’ve learned about an important model for computation, called Petri nets, and seen how it can be used to model a general class of entity conversion networks, which include chemical reaction networks as a major case. Then we wrote a program to simulate them, and applied it to a simple model for formation and dissociation of water molecules.

This is a good beginning, but observe the following limitation of our current program: it just randomly picks a rule. When we run the program, the simulation makes a kind of random walk, back and forth, between the states of full synthesis and full dissociation. But in a real chemical system, the rates at which the transitions fires are probabilistically determined, and depend, among other things, on the temperature. With a high probability for formation, and a low probability for dissociation, we would expect the system to reach an equilibrium state in which H2O is the predominant “token” in the system. The relative concentration of the various chemical species would be determined by the relative firing rates of the various transitions.

This gives motivation for our next article, which will cover stochastic Petri nets.

## Programming exercises

1. Extend the constructor for PetriNet to accept a dictionary from transitionName to a number, which will give the relative probability of that transition firing. Modify the firing rule logic to use these values. This is a step in the direction of the stochastic Petri nets covered in the next article.

2. Make a prediction about the overall evolution of the system, given fixed probabilities for synthesis and dissociation, and then run the program to see if your prediction is confirmed.

3. If you like, improve the usability of the script, by passing the model parameters from the command line. Use sys.argv and eval. You can use single quotes, and pass a string like “{‘H’:5, ‘O’:3, ‘H2O’:4}” from the command line.

## References

• Network Theory, Azimuth blog

1. John Baez, Network Theory, Parts 2 and 3, Azimuth blog
• Petri Nets – Wikipedia

1. Petri Nets – Wikipedia
• python.org

1. python.org

## Acknowledgements

Thanks to David Tweed and Ken Webb for reviewing the code and coming up with improvements to the specification syntax for the Petri nets.

## Appendix: Notes on the language interpreter

The sample program is written in Python, which is a low-fuss scripting language with abstraction capabilities. The language is well-suited for proof-of-concept programming, and it has a medium-sized user base and support community.

You have a few distributions to choose from:

• If you are on a Linux or Mac type of system, it is likely to already be installed. Just open a shell and type “python.” Otherwise, use the package manager to install it.

• In Windows, you can use the version from the python.org web site. Alternatively, install cygwin, and in the installer, select Python, along with any of your other favorite Linux-style packages. Then you can partially pretend that you are working on a Linux type of system.

• The Enthought Python distribution comes packaged with a suite of Python-based open-source scientific and numeric computing packages. This includes ipython, scipy, numpy and matplotlib.

The program is distributed as a self-contained script, so in Linux and cygwin, you can just execute ./petri_simulator1.py. ./petri1.py. When running this way under cygwin, you can either refer to the cygwin version of the Python executable, or to any of the native Windows versions of interpreter. You just need to adjust the first line of the script to point to the python executable.

category: blog