Spaces:
Running
Running
''' | |
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/>. | |
''' | |
from enum import Enum | |
from pm4py.util import hie_utils | |
import sys | |
class Operator(Enum): | |
# sequence operator | |
SEQUENCE = '->' | |
# exclusive choice operator | |
XOR = 'X' | |
# parallel operator | |
PARALLEL = '+' | |
# loop operator | |
LOOP = '*' | |
# or operator | |
OR = 'O' | |
# interleaving operator | |
INTERLEAVING = "<>" | |
# partially-ordered operator | |
PARTIALORDER = "PO" | |
def __str__(self): | |
""" | |
Provides a string representation of the current operator | |
Returns | |
----------- | |
stri | |
String representation of the process tree | |
""" | |
return self.value | |
def __repr__(self): | |
""" | |
Provides a string representation of the current operator | |
Returns | |
----------- | |
stri | |
String representation of the process tree | |
""" | |
return self.value | |
class ProcessTree(object): | |
class OperatorState(Enum): | |
ENABLED = "enabled" | |
OPEN = "open" | |
CLOSED = "closed" | |
FUTURE = "future" | |
def __init__(self, operator=None, parent=None, children=None, label=None): | |
""" | |
Constructor | |
Parameters | |
------------ | |
operator | |
Operator (of the current node) of the process tree | |
parent | |
Parent node (of the current node) | |
children | |
List of children of the current node | |
label | |
Label (of the current node) | |
""" | |
self._operator = operator | |
self._parent = parent | |
self._children = list() if children is None else children | |
self._label = label | |
self._properties = {} | |
def __hash__(self): | |
if self.label is not None: | |
return hash(self.label) | |
elif len(self.children) == 0: | |
return 37 | |
else: | |
h = 1337 | |
for i in range(len(self.children)): | |
h += 41 * i * hash(self.children[i]) | |
if self.operator == Operator.SEQUENCE: | |
h = h * 13 | |
elif self.operator == Operator.XOR: | |
h = h * 17 | |
elif self.operator == Operator.OR: | |
h = h * 23 | |
elif self.operator == Operator.PARALLEL: | |
h = h * 29 | |
elif self.operator == Operator.LOOP: | |
h = h * 37 | |
elif self.operator == Operator.INTERLEAVING: | |
h = h * 41 | |
elif self.operator == Operator.PARTIALORDER: | |
h = h * 43 | |
return h % 268435456 | |
def _set_operator(self, operator): | |
self._operator = operator | |
def _set_parent(self, parent): | |
self._parent = parent | |
def _set_label(self, label): | |
self._label = label | |
def _set_children(self, children): | |
self._children = children | |
def _get_children(self): | |
return self._children | |
def _get_parent(self): | |
return self._parent | |
def _get_operator(self): | |
return self._operator | |
def _get_label(self): | |
return self._label | |
def __eq__(self, other): | |
if isinstance(other, ProcessTree): | |
if self.label is not None: | |
return True if other.label == self.label else False | |
elif len(self.children) == 0: | |
return other.label is None and len(other.children) == 0 | |
else: | |
if self.operator == other.operator: | |
if len(self.children) != len(other.children): | |
return False | |
else: | |
for i in range(len(self.children)): | |
if self.children[i] != other.children[i]: | |
return False | |
return True | |
else: | |
return False | |
return False | |
def to_string(self, level=0, indent=False, max_indent=sys.maxsize): | |
""" | |
Represents a process tree model as a string. | |
Parameters | |
----------------- | |
indent | |
Enable the indentation of the resulting string | |
max_indent | |
Maximum level of indentation | |
""" | |
if self.operator is not None: | |
rep = str(self._operator) + '( ' | |
for i in range(0, len(self._children)): | |
child = self._children[i] | |
if len(child.children) == 0: | |
if child.label is not None: | |
rep += '\'' + child.to_string(level=level+1) + '\'' + ', ' if i < len(self._children) - 1 else '\'' + child.to_string(level=level+1) + '\'' | |
else: | |
rep += child.to_string(level=level+1) + ', ' if i < len(self._children) - 1 else child.to_string(level=level+1) | |
else: | |
rep += child.to_string(level=level+1) + ', ' if i < len(self._children) - 1 else child.to_string(level=level+1) | |
stru = rep + ' )' | |
if level == 0 and indent: | |
stru = "\n".join(hie_utils.indent_representation(stru, max_indent=max_indent)) | |
return stru | |
elif self.label is not None: | |
return self.label | |
else: | |
return 'tau' | |
def __repr__(self): | |
""" | |
Returns a string representation of the process tree | |
Returns | |
------------ | |
stri | |
String representation of the process tree | |
""" | |
return self.to_string() | |
def __str__(self): | |
""" | |
Returns a string representation of the process tree | |
Returns | |
------------ | |
stri | |
String representation of the process tree | |
""" | |
return self.to_string() | |
def model_description() -> str: | |
descr = """A process tree is a hierarchical process model. | |
The following operators are defined for process trees: | |
-> ( A, B ) tells that the process tree A should be executed before the process tree B | |
X ( A, B ) tells that there is an exclusive choice between executing the process tree A or the process tree B | |
+ ( A, B ) tells that A and B are executed in true concurrency. | |
* ( A, B ) is a loop. So the process tree A is executed, then either you exit the loop, or you execute B and then A again (this can happen several times until the loop is exited). | |
the leafs of a process tree are either activities (denoted by 'X' where X is the name of the activity) or silent steps (indicated by tau). | |
An example process tree follows: | |
+ ( 'A', -> ( 'B', 'C' ) ) | |
tells that you should execute B before executing C. In true concurrency, you can execute A. So the possible traces are A->B->C, B->A->C, B->C->A. | |
""" | |
return descr | |
def _get_root(self): | |
root = self | |
while root._get_parent() is not None: | |
root = root._get_parent() | |
return root | |
def _get_leaves(self): | |
root = self._get_root() | |
leaves = [root] | |
if root._get_children() != list(): | |
leaves = root._get_children() | |
change_of_leaves = True | |
while change_of_leaves: | |
leaves_to_replace = list() | |
new_leaves = list() | |
for leaf in leaves: | |
if leaf._get_children() != list(): | |
leaves_to_replace.append(leaf) | |
else: | |
new_leaves.append(leaf) | |
if leaves_to_replace != list(): | |
for leaf in leaves_to_replace: | |
for el in leaf.children: | |
new_leaves.append(el) | |
leaves = new_leaves | |
else: | |
change_of_leaves = False | |
return leaves | |
def _print_tree(self): | |
root = self._get_root() | |
print(root) | |
parent = property(_get_parent, _set_parent) | |
children = property(_get_children, _set_children) | |
operator = property(_get_operator, _set_operator) | |
label = property(_get_label, _set_label) | |