kota
initial commit
e60e568
raw
history blame
8.61 kB
'''
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 random
from pm4py.objects.log.obj import EventLog, Trace, Event
from pm4py.util import xes_constants as xes
from pm4py.objects.process_tree import obj as pt_opt
from pm4py.objects.process_tree import state as pt_st
from pm4py.objects.process_tree.utils import generic as pt_util
from pm4py.objects.process_tree.obj import ProcessTree
import datetime
from copy import deepcopy
class GenerationTree(ProcessTree):
# extend the parent class to replace the __eq__ and __hash__ method
def __init__(self, tree):
i = 0
while i < len(tree.children):
tree.children[i] = GenerationTree(tree.children[i])
tree.children[i].parent = self
i = i + 1
ProcessTree.__init__(self, operator=tree.operator, parent=tree.parent, children=tree.children, label=tree.label)
def __eq__(self, other):
# method that is different from default one (different taus must give different ID in log generation!!!!)
return id(self) == id(other)
def __hash__(self):
return id(self)
def generate_log(pt0, no_traces=100):
"""
Generate a log out of a process tree
Parameters
------------
pt
Process tree
no_traces
Number of traces contained in the process tree
Returns
------------
log
Trace log object
"""
pt = deepcopy(pt0)
# different taus must give different ID in log generation!!!!
# so we cannot use the default process tree class
# we use this different one!
pt = GenerationTree(pt)
log = EventLog()
# assigns to each event an increased timestamp from 1970
curr_timestamp = 10000000
for i in range(no_traces):
ex_seq = execute(pt)
ex_seq_labels = pt_util.project_execution_sequence_to_labels(ex_seq)
trace = Trace()
trace.attributes[xes.DEFAULT_NAME_KEY] = str(i)
for label in ex_seq_labels:
event = Event()
event[xes.DEFAULT_NAME_KEY] = label
event[xes.DEFAULT_TIMESTAMP_KEY] = datetime.datetime.fromtimestamp(curr_timestamp)
trace.append(event)
curr_timestamp = curr_timestamp + 1
log.append(trace)
return log
def execute(pt):
"""
Execute the process tree, returning an execution sequence
Parameters
-----------
pt
Process tree
Returns
-----------
exec_sequence
Execution sequence on the process tree
"""
enabled, open, closed = set(), set(), set()
enabled.add(pt)
# populate_closed(pt.children, closed)
execution_sequence = list()
while len(enabled) > 0:
execute_enabled(enabled, open, closed, execution_sequence)
return execution_sequence
def populate_closed(nodes, closed):
"""
Populate all closed nodes of a process tree
Parameters
------------
nodes
Considered nodes of the process tree
closed
Closed nodes
"""
closed |= set(nodes)
for node in nodes:
populate_closed(node.children, closed)
def execute_enabled(enabled, open, closed, execution_sequence=None):
"""
Execute an enabled node of the process tree
Parameters
-----------
enabled
Enabled nodes
open
Open nodes
closed
Closed nodes
execution_sequence
Execution sequence
Returns
-----------
execution_sequence
Execution sequence
"""
execution_sequence = list() if execution_sequence is None else execution_sequence
vertex = random.sample(list(enabled), 1)[0]
enabled.remove(vertex)
open.add(vertex)
execution_sequence.append((vertex, pt_st.State.OPEN))
if len(vertex.children) > 0:
if vertex.operator is pt_opt.Operator.LOOP:
while len(vertex.children) < 3:
vertex.children.append(ProcessTree(parent=vertex))
if vertex.operator is pt_opt.Operator.SEQUENCE or vertex.operator is pt_opt.Operator.LOOP:
c = vertex.children[0]
enabled.add(c)
execution_sequence.append((c, pt_st.State.ENABLED))
elif vertex.operator is pt_opt.Operator.PARALLEL:
enabled |= set(vertex.children)
for x in vertex.children:
if x in closed:
closed.remove(x)
map(lambda c: execution_sequence.append((c, pt_st.State.ENABLED)), vertex.children)
elif vertex.operator is pt_opt.Operator.XOR:
vc = vertex.children
c = vc[random.randint(0, len(vc) - 1)]
enabled.add(c)
execution_sequence.append((c, pt_st.State.ENABLED))
elif vertex.operator is pt_opt.Operator.OR:
some_children = [c for c in vertex.children if random.random() < 0.5]
enabled |= set(some_children)
for x in some_children:
if x in closed:
closed.remove(x)
map(lambda c: execution_sequence.append((c, pt_st.State.ENABLED)), some_children)
elif vertex.operator is pt_opt.Operator.INTERLEAVING:
random.shuffle(vertex.children)
c = vertex.children[0]
enabled.add(c)
execution_sequence.append((c, pt_st.State.ENABLED))
else:
close(vertex, enabled, open, closed, execution_sequence)
return execution_sequence
def close(vertex, enabled, open, closed, execution_sequence):
"""
Close a given vertex of the process tree
Parameters
------------
vertex
Vertex to be closed
enabled
Set of enabled nodes
open
Set of open nodes
closed
Set of closed nodes
execution_sequence
Execution sequence on the process tree
"""
open.remove(vertex)
closed.add(vertex)
execution_sequence.append((vertex, pt_st.State.CLOSED))
process_closed(vertex, enabled, open, closed, execution_sequence)
def process_closed(closed_node, enabled, open, closed, execution_sequence):
"""
Process a closed node, deciding further operations
Parameters
-------------
closed_node
Node that shall be closed
enabled
Set of enabled nodes
open
Set of open nodes
closed
Set of closed nodes
execution_sequence
Execution sequence on the process tree
"""
vertex = closed_node.parent
if vertex is not None and vertex in open:
if should_close(vertex, closed, closed_node):
close(vertex, enabled, open, closed, execution_sequence)
else:
enable = None
if vertex.operator is pt_opt.Operator.SEQUENCE or vertex.operator is pt_opt.Operator.INTERLEAVING:
enable = vertex.children[vertex.children.index(closed_node) + 1]
elif vertex.operator is pt_opt.Operator.LOOP:
enable = vertex.children[random.randint(1, 2)] if vertex.children.index(closed_node) == 0 else \
vertex.children[0]
if enable is not None:
enabled.add(enable)
execution_sequence.append((enable, pt_st.State.ENABLED))
def should_close(vertex, closed, child):
"""
Decides if a parent vertex shall be closed based on
the processed child
Parameters
------------
vertex
Vertex of the process tree
closed
Set of closed nodes
child
Processed child
Returns
------------
boolean
Boolean value (the vertex shall be closed)
"""
if vertex.children is None:
return True
elif vertex.operator is pt_opt.Operator.LOOP or vertex.operator is pt_opt.Operator.SEQUENCE or vertex.operator is pt_opt.Operator.INTERLEAVING:
return vertex.children.index(child) == len(vertex.children) - 1
elif vertex.operator is pt_opt.Operator.XOR:
return True
else:
return set(vertex.children) <= closed