'''
This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
PM4Py is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
PM4Py is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with PM4Py. If not, see <https://www.gnu.org/licenses/>.
'''
import tempfile
from graphviz import Digraph
from pm4py.objects.petri_net.obj import Marking
from pm4py.objects.petri_net import properties as petri_properties
from pm4py.util import exec_utils, constants
from enum import Enum
from pm4py.util.constants import PARAMETER_CONSTANT_ACTIVITY_KEY, PARAMETER_CONSTANT_TIMESTAMP_KEY, DEFAULT_ARTIFICIAL_START_ACTIVITY, DEFAULT_ARTIFICIAL_END_ACTIVITY
[docs]class Parameters(Enum):
FORMAT = "format"
DEBUG = "debug"
RANKDIR = "set_rankdir"
ACTIVITY_KEY = PARAMETER_CONSTANT_ACTIVITY_KEY
TIMESTAMP_KEY = PARAMETER_CONSTANT_TIMESTAMP_KEY
AGGREGATION_MEASURE = "aggregationMeasure"
FONT_SIZE = "font_size"
BGCOLOR = "bgcolor"
[docs]def apply(net, initial_marking, final_marking, decorations=None, parameters=None):
"""
Apply method for Petri net visualization (it calls the
graphviz_visualization method)
Parameters
-----------
net
Petri net
initial_marking
Initial marking
final_marking
Final marking
decorations
Decorations for elements in the Petri net
parameters
Algorithm parameters
Returns
-----------
viz
Graph object
"""
if parameters is None:
parameters = {}
image_format = exec_utils.get_param_value(Parameters.FORMAT, parameters, "png")
debug = exec_utils.get_param_value(Parameters.DEBUG, parameters, False)
set_rankdir = exec_utils.get_param_value(Parameters.RANKDIR, parameters, None)
font_size = exec_utils.get_param_value(Parameters.FONT_SIZE, parameters, "12")
bgcolor = exec_utils.get_param_value(Parameters.BGCOLOR, parameters, constants.DEFAULT_BGCOLOR)
return graphviz_visualization(net, image_format=image_format, initial_marking=initial_marking,
final_marking=final_marking, decorations=decorations, debug=debug,
set_rankdir=set_rankdir, font_size=font_size, bgcolor=bgcolor)
[docs]def graphviz_visualization(net, image_format="png", initial_marking=None, final_marking=None, decorations=None,
debug=False, set_rankdir=None, font_size="12", bgcolor=constants.DEFAULT_BGCOLOR):
"""
Provides visualization for the petrinet
Parameters
----------
net: :class:`pm4py.entities.petri.petrinet.PetriNet`
Petri net
image_format
Format that should be associated to the image
initial_marking
Initial marking of the Petri net
final_marking
Final marking of the Petri net
decorations
Decorations of the Petri net (says how element must be presented)
debug
Enables debug mode
set_rankdir
Sets the rankdir to LR (horizontal layout)
Returns
-------
viz :
Returns a graph object
"""
if initial_marking is None:
initial_marking = Marking()
if final_marking is None:
final_marking = Marking()
if decorations is None:
decorations = {}
font_size = str(font_size)
filename = tempfile.NamedTemporaryFile(suffix='.gv')
viz = Digraph(net.name, filename=filename.name, engine='dot', graph_attr={'bgcolor': bgcolor})
if set_rankdir:
viz.graph_attr['rankdir'] = set_rankdir
else:
viz.graph_attr['rankdir'] = 'LR'
# transitions
viz.attr('node', shape='box')
for t in net.transitions:
if t.label is not None:
if t in decorations and "label" in decorations[t] and "color" in decorations[t]:
viz.node(str(id(t)), decorations[t]["label"], style='filled', fillcolor=decorations[t]["color"],
border='1', fontsize=font_size)
else:
viz.node(str(id(t)), str(t.label), fontsize=font_size)
else:
if debug:
viz.node(str(id(t)), str(t.name), fontsize=font_size)
elif t in decorations and "color" in decorations[t] and "label" in decorations[t]:
viz.node(str(id(t)), decorations[t]["label"], style='filled', fillcolor=decorations[t]["color"],
fontsize=font_size)
else:
viz.node(str(id(t)), "", style='filled', fillcolor="black", fontsize=font_size)
if petri_properties.TRANS_GUARD in t.properties:
guard = t.properties[petri_properties.TRANS_GUARD]
viz.node(str(id(t))+"guard", style="dotted", label=guard)
viz.edge(str(id(t))+"guard", str(id(t)), arrowhead="none", style="dotted")
# places
# add places, in order by their (unique) name, to avoid undeterminism in the visualization
places_sort_list_im = sorted([x for x in list(net.places) if x in initial_marking], key=lambda x: x.name)
places_sort_list_fm = sorted([x for x in list(net.places) if x in final_marking and not x in initial_marking],
key=lambda x: x.name)
places_sort_list_not_im_fm = sorted(
[x for x in list(net.places) if x not in initial_marking and x not in final_marking], key=lambda x: x.name)
# making the addition happen in this order:
# - first, the places belonging to the initial marking
# - after, the places not belonging neither to the initial marking and the final marking
# - at last, the places belonging to the final marking (but not to the initial marking)
# in this way, is more probable that the initial marking is on the left and the final on the right
places_sort_list = places_sort_list_im + places_sort_list_not_im_fm + places_sort_list_fm
for p in places_sort_list:
if p in initial_marking:
if initial_marking[p] == 1:
viz.node(str(id(p)), "<●>", fontsize="34", fixedsize='true', shape="circle", width='0.75')
else:
viz.node(str(id(p)), str(initial_marking[p]), fontsize="34", fixedsize='true', shape="circle", width='0.75')
elif p in final_marking:
# <■>
viz.node(str(id(p)), "<■>", fontsize="32", shape='doublecircle', fixedsize='true', width='0.75')
else:
if debug:
viz.node(str(id(p)), str(p.name), fontsize=font_size, shape="ellipse")
else:
if p in decorations and "color" in decorations[p] and "label" in decorations[p]:
viz.node(str(id(p)), decorations[p]["label"], style='filled', fillcolor=decorations[p]["color"],
fontsize=font_size, shape="ellipse")
else:
viz.node(str(id(p)), "", shape='circle', fixedsize='true', width='0.75')
# add arcs, in order by their source and target objects names, to avoid undeterminism in the visualization
arcs_sort_list = sorted(list(net.arcs), key=lambda x: (x.source.name, x.target.name))
# check if there is an arc with weight different than 1.
# in that case, all the arcs in the visualization should have the arc weight visible
arc_weight_visible = False
for arc in arcs_sort_list:
if arc.weight != 1:
arc_weight_visible = True
break
for a in arcs_sort_list:
arrowhead = "normal"
if petri_properties.ARCTYPE in a.properties:
if a.properties[petri_properties.ARCTYPE] == petri_properties.RESET_ARC:
arrowhead = "vee"
elif a.properties[petri_properties.ARCTYPE] == petri_properties.INHIBITOR_ARC:
arrowhead = "dot"
if a in decorations and "label" in decorations[a] and "penwidth" in decorations[a]:
viz.edge(str(id(a.source)), str(id(a.target)), label=decorations[a]["label"],
penwidth=decorations[a]["penwidth"], fontsize=font_size, arrowhead=arrowhead)
elif a in decorations and "color" in decorations[a]:
viz.edge(str(id(a.source)), str(id(a.target)), color=decorations[a]["color"], fontsize=font_size, arrowhead=arrowhead)
else:
if arc_weight_visible:
viz.edge(str(id(a.source)), str(id(a.target)), fontsize=font_size, arrowhead=arrowhead, label=str(a.weight))
else:
viz.edge(str(id(a.source)), str(id(a.target)), fontsize=font_size, arrowhead=arrowhead)
viz.attr(overlap='false')
viz.format = image_format
return viz