''' 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 . ''' 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