forked from Nikolaj-K/petri-net
-
Notifications
You must be signed in to change notification settings - Fork 0
/
petri_net.py
144 lines (116 loc) · 4.22 KB
/
petri_net.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
"""
Modeling approach:
* define Petri nets in terms of their transactions
* define transactions in terms of the actions of their arcs
* define arcs in terms with their action on their in- or outgoing place
* define places as basic containers
Run with python 2 or 3, for the example coded up in in __main__, via
python petri_net.py --firings 10 --marking 1 2 3 2
References:
* https://en.wikipedia.org/wiki/Petri_net
* https://www.amazon.com/Understanding-Petri-Nets-Modeling-Techniques/dp/3642332773
"""
class Place:
def __init__(self, holding):
"""
Place vertex in the petri net.
:holding: Numer of token the place is initialized with.
"""
self.holding = holding
class ArcBase:
def __init__(self, place, amount=1):
"""
Arc in the petri net.
:place: The one place acting as source/target of the arc as arc in the net
:amount: The amount of token removed/added from/to the place.
"""
self.place = place
self.amount = amount
class Out(ArcBase):
def trigger(self):
"""
Remove token.
"""
self.place.holding -= self.amount
def non_blocking(self):
"""
Validate action of outgoing arc is possible.
"""
return self.place.holding >= self.amount
class In(ArcBase):
def trigger(self):
"""
Add tokens.
"""
self.place.holding += self.amount
class Transition:
def __init__(self, out_arcs, in_arcs):
"""
Transition vertex in the petri net.
:out_arcs: Collection of ingoing arcs, to the transition vertex.
:in_arcs: Collection of outgoing arcs, to the transition vertex.
"""
self.out_arcs = set(out_arcs)
self.arcs = self.out_arcs.union(in_arcs)
def fire(self):
"""
Fire!
"""
not_blocked = all(arc.non_blocking() for arc in self.out_arcs)
# Note: This would have to be checked differently for variants of
# petri nets that take more than once from a place, per transition.
if not_blocked:
for arc in self.arcs:
arc.trigger()
return not_blocked # return if fired, just for the sake of debuging
class PetriNet:
def __init__(self, transitions):
"""
The petri net runner.
:transitions: The transitions encoding the net.
"""
self.transitions = transitions
def run(self, firing_sequence, ps):
"""
Run the petri net.
Details: This is a loop over the transactions firing and then some printing.
:firing_sequence: Sequence of transition names use for run.
:ps: Place holdings to print during the run (debugging).
"""
print("Using firing sequence:\n" + " => ".join(firing_sequence))
print("start {}\n".format([p.holding for p in ps]))
for name in firing_sequence:
t = self.transitions[name]
if t.fire():
print("{} fired!".format(name))
print(" => {}".format([p.holding for p in ps]))
else:
print("{} ...fizzled.".format(name))
print("\nfinal {}".format([p.holding for p in ps]))
def make_parser():
"""
:return: A parser reading in some of our simulation paramaters.
"""
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument('--firings', type=int)
parser.add_argument('--marking', type=int, nargs='+')
return parser
if __name__ == "__main__":
args = make_parser().parse_args()
ps = [Place(m) for m in args.marking]
ts = dict(
t1=Transition(
[Out(ps[0])],
[In(ps[1]), In(ps[2])]
),
t2=Transition(
[Out(ps[1]), Out(ps[2])],
[In(ps[3]), In(ps[0])]
),
)
from random import choice
firing_sequence = [choice(list(ts.keys())) for _ in range(args.firings)] # stochastic execution
#firing_sequence = ["t1", "t1", "t2", "t1"] # alternative deterministic example
petri_net = PetriNet(ts)
petri_net.run(firing_sequence, ps)