def reach_fm_with_invisibles(self, marking): """ Reaches the final marking using invisible transitions Parameters -------------- marking Marking Returns -------------- new_marking New marking (hopely equal to the final marking) """ spath = None spath_length = sys.maxsize for pl in marking: if pl in self.dictio_spaths: for pl2 in self.fm: if pl2 in self.dictio_spaths[pl]: new_path = self.dictio_spaths[pl][pl2] if len(new_path) < spath_length: spath = new_path spath_length = len(spath) if spath is not None: # try to fire the transitions for tr in spath: if tr in semantics.enabled_transitions(self.net, marking): marking = semantics.weak_execute(tr, marking) else: return None return marking return None
def get_visible_transitions_eventually_enabled_by_marking(net, marking): """ Get visible transitions eventually enabled by marking (passing possibly through hidden transitions) Parameters ---------- net Petri net marking Current marking """ all_enabled_transitions = sorted(list(semantics.enabled_transitions(net, marking)), key=lambda x: (str(x.name), id(x))) initial_all_enabled_transitions_marking_dictio = {} all_enabled_transitions_marking_dictio = {} for trans in all_enabled_transitions: all_enabled_transitions_marking_dictio[trans] = marking initial_all_enabled_transitions_marking_dictio[trans] = marking visible_transitions = set() visited_transitions = set() i = 0 while i < len(all_enabled_transitions): t = all_enabled_transitions[i] marking_copy = copy(all_enabled_transitions_marking_dictio[t]) if repr([t, marking_copy]) not in visited_transitions: if t.label is not None: visible_transitions.add(t) else: if semantics.is_enabled(t, net, marking_copy): new_marking = semantics.execute(t, net, marking_copy) new_enabled_transitions = sorted(list(semantics.enabled_transitions(net, new_marking)), key=lambda x: (str(x.name), id(x))) for t2 in new_enabled_transitions: all_enabled_transitions.append(t2) all_enabled_transitions_marking_dictio[t2] = new_marking visited_transitions.add(repr([t, marking_copy])) i = i + 1 return visible_transitions
def acyclic_net_variants(net, initial_marking, final_marking, activity_key=xes_util.DEFAULT_NAME_KEY): """ Given an acyclic accepting Petri net, initial and final marking extracts a set of variants (in form of traces) replayable on the net. Warning: this function is based on a marking exploration. If the accepting Petri net contains loops, the method will not work properly as it stops the search if a specific marking has already been encountered. Parameters ---------- :param net: An acyclic workflow net :param initial_marking: The initial marking of the net. :param final_marking: The final marking of the net. :param activity_key: activity key to use Returns ------- :return: variants: :class:`list` Set of variants - in the form of Trace objects - obtainable executing the net """ active = {(initial_marking, ())} visited = set() variants = set() while active: curr_marking, curr_partial_trace = active.pop() curr_pair = (curr_marking, curr_partial_trace) enabled_transitions = semantics.enabled_transitions(net, curr_marking) for transition in enabled_transitions: if transition.label is not None: next_partial_trace = curr_partial_trace + (transition.label, ) else: next_partial_trace = curr_partial_trace next_marking = semantics.execute(transition, net, curr_marking) next_pair = (next_marking, next_partial_trace) if next_marking == final_marking: variants.add(next_partial_trace) else: # If the next marking is not in visited, if the next marking+partial trace is different from the current one+partial trace if next_pair not in visited and curr_pair != next_pair: active.add(next_pair) visited.add(curr_pair) trace_variants = [] for variant in variants: trace = Trace() for activity_label in variant: trace.append(Event({activity_key: activity_label})) trace_variants.append(trace) return trace_variants
def marking_flow_petri(net, im, return_eventually_enabled=False, parameters=None): """ Construct the marking flow of a Petri net Parameters ----------------- net Petri net im Initial marking return_eventually_enabled Return the eventually enabled (visible) transitions """ if parameters is None: parameters = {} # set a maximum execution time of 1 day (it can be changed by providing the parameter) max_exec_time = exec_utils.get_param_value(Parameters.MAX_ELAB_TIME, parameters, 86400) start_time = time.time() incoming_transitions = {im: set()} outgoing_transitions = {} eventually_enabled = {} active = [im] while active: if (time.time() - start_time) >= max_exec_time: # interrupt the execution return incoming_transitions, outgoing_transitions, eventually_enabled m = active.pop() enabled_transitions = semantics.enabled_transitions(net, m) if return_eventually_enabled: eventually_enabled[m] = align_utils.get_visible_transitions_eventually_enabled_by_marking(net, m) outgoing_transitions[m] = {} for t in enabled_transitions: nm = semantics.weak_execute(t, m) outgoing_transitions[m][t] = nm if nm not in incoming_transitions: incoming_transitions[nm] = set() if nm not in active: active.append(nm) incoming_transitions[nm].add(t) return incoming_transitions, outgoing_transitions, eventually_enabled
def enable_trans_with_invisibles(self, marking, activity): """ Enables a visible transition (that is not enabled) through invisible transitions Parameters ---------------- marking Marking activity Activity to enable Returns --------------- new_marking New marking (where the transition CAN be enabled) """ corr_trans_to_act = [x for x in self.net.transitions if x.label == activity] spath = None spath_length = sys.maxsize for pl in marking: for tr in corr_trans_to_act: if pl in self.dictio_spaths: if tr in self.dictio_spaths[pl]: new_path = self.dictio_spaths[pl][tr] if len(new_path) < spath_length: spath = new_path spath_length = len(spath) if spath is not None: # try to fire the transitions for tr in spath: if tr in semantics.enabled_transitions(self.net, marking): marking = semantics.weak_execute(tr, marking) else: return None return marking return None
""" if activity in self.activities: if case not in self.case_dict: self.case_dict[case] = self.encode_marking(copy(self.im)) self.missing[case] = 0 self.remaining[case] = 0 marking = self.decode_marking(self.case_dict[case]) new_marking = marking prev_marking = None correct_exec = False numb_it = 0 while new_marking is not None and prev_marking != new_marking: numb_it = numb_it + 1 if numb_it > self.maximum_iterations_invisibles: break enabled_transitions = semantics.enabled_transitions(self.net, new_marking) matching_transitions = [x for x in enabled_transitions if x.label == activity] if matching_transitions: new_marking = semantics.weak_execute(matching_transitions[0], new_marking) self.case_dict[case] = self.encode_marking(new_marking) correct_exec = True break prev_marking = new_marking new_marking = self.enable_trans_with_invisibles(new_marking, activity) correct_exec = False if correct_exec is False: self.message_missing_tokens(activity, case) # enables one of the matching transitions matching_transitions = [x for x in self.net.transitions if x.label == activity] t = matching_transitions[0] for a in t.in_arcs:
def apply_playout(net, initial_marking, no_traces=100, max_trace_length=100, case_id_key=xes_constants.DEFAULT_TRACEID_KEY, activity_key=xes_constants.DEFAULT_NAME_KEY, timestamp_key=xes_constants.DEFAULT_TIMESTAMP_KEY, final_marking=None, smap=None, log=None, return_visited_elements=False): """ Do the playout of a Petrinet generating a log Parameters ---------- net Petri net to play-out initial_marking Initial marking of the Petri net no_traces Number of traces to generate max_trace_length Maximum number of events per trace (do break) case_id_key Trace attribute that is the case ID activity_key Event attribute that corresponds to the activity timestamp_key Event attribute that corresponds to the timestamp final_marking If provided, the final marking of the Petri net smap Stochastic map log Log """ if final_marking is None: # infer the final marking from the net final_marking = final_marking_discovery.discover_final_marking(net) if smap is None: if log is None: raise Exception( "please provide at least one between stochastic map and log") smap = replay.get_map_from_log_and_net(log, net, initial_marking, final_marking, parameters={ Parameters.ACTIVITY_KEY: activity_key, Parameters.TIMESTAMP_KEY: timestamp_key }) # assigns to each event an increased timestamp from 1970 curr_timestamp = 10000000 all_visited_elements = [] for i in range(no_traces): visited_elements = [] visible_transitions_visited = [] marking = copy(initial_marking) while len(visible_transitions_visited) < max_trace_length: visited_elements.append(marking) if not semantics.enabled_transitions( net, marking): # supports nets with possible deadlocks break all_enabled_trans = semantics.enabled_transitions(net, marking) if final_marking is not None and marking == final_marking: en_t_list = list(all_enabled_trans.union({None})) else: en_t_list = list(all_enabled_trans) trans = stochastic_utils.pick_transition(en_t_list, smap) if trans is None: break visited_elements.append(trans) if trans.label is not None: visible_transitions_visited.append(trans) marking = semantics.execute(trans, net, marking) all_visited_elements.append(tuple(visited_elements)) if return_visited_elements: return all_visited_elements log = log_instance.EventLog() for index, visited_elements in enumerate(all_visited_elements): trace = log_instance.Trace() trace.attributes[case_id_key] = str(index) for element in visited_elements: if type(element ) is PetriNet.Transition and element.label is not None: event = log_instance.Event() event[activity_key] = element.label event[timestamp_key] = datetime.datetime.fromtimestamp( curr_timestamp) trace.append(event) # increases by 1 second curr_timestamp += 1 log.append(trace) return log
def calculate_annotation_for_trace(trace, net, initial_marking, act_trans, activity_key, ht_perf_method="last"): """ Calculate annotation for a trace in the variant, in order to retrieve information useful for calculate frequency/performance for all the traces belonging to the variant Parameters ----------- trace Trace net Petri net initial_marking Initial marking act_trans Activated transitions during token replay of the given trace activity_key Attribute that identifies the activity (must be specified if different from concept:name) ht_perf_method Method to use in order to annotate hidden transitions (performance value could be put on the last possible point (last) or in the first possible point (first) Returns ---------- annotation Statistics annotation for the given trace """ annotations_places_trans = {} annotations_arcs = {} trace_place_stats = {} current_trace_index = 0 j = 0 marking = copy(initial_marking) for place in marking: if place not in annotations_places_trans: annotations_places_trans[place] = {"count": 0} annotations_places_trans[place][ "count"] = annotations_places_trans[place]["count"] + marking[ place] trace_place_stats[place] = [current_trace_index] * marking[place] for z in range(len(act_trans)): enabled_trans_in_marking = semantics.enabled_transitions(net, marking) # print("enabled_trans_in_marking", enabled_trans_in_marking) for trans in enabled_trans_in_marking: if trans not in annotations_places_trans: annotations_places_trans[trans] = { "count": 0, "performance": [], "no_of_times_enabled": 0, "no_of_times_activated": 0 } annotations_places_trans[trans][ "no_of_times_enabled"] = annotations_places_trans[trans][ "no_of_times_enabled"] + 1 trans = act_trans[z] if trans not in annotations_places_trans: annotations_places_trans[trans] = { "count": 0, "performance": [], "no_of_times_enabled": 0, "no_of_times_activated": 0 } annotations_places_trans[trans][ "count"] = annotations_places_trans[trans]["count"] + 1 if trans not in enabled_trans_in_marking: annotations_places_trans[trans][ "no_of_times_enabled"] = annotations_places_trans[trans][ "no_of_times_enabled"] + 1 annotations_places_trans[trans][ "no_of_times_activated"] = annotations_places_trans[trans][ "no_of_times_activated"] + 1 new_marking = semantics.weak_execute(trans, marking) if not new_marking: break marking_diff = set(new_marking).difference(set(marking)) for place in marking_diff: if place not in annotations_places_trans: annotations_places_trans[place] = {"count": 0} annotations_places_trans[place][ "count"] = annotations_places_trans[place]["count"] + max( new_marking[place] - marking[place], 1) marking = new_marking if j < len(trace): current_trace_index = j if trans.label == trace[j][activity_key]: j = j + 1 in_arc_indexes = [ trace_place_stats[arc.source][0] for arc in trans.in_arcs if arc.source in trace_place_stats and trace_place_stats[arc.source] ] if in_arc_indexes: min_in_arc_indexes = min(in_arc_indexes) max_in_arc_indexes = max(in_arc_indexes) else: min_in_arc_indexes = None max_in_arc_indexes = None performance_for_this_trans_execution = [] for arc in trans.in_arcs: source_place = arc.source if arc not in annotations_arcs: annotations_arcs[arc] = {"performance": [], "count": 0} annotations_arcs[arc][ "count"] = annotations_arcs[arc]["count"] + 1 if source_place in trace_place_stats and trace_place_stats[ source_place]: if trans.label or ht_perf_method == "first": annotations_arcs[arc]["performance"].append([ current_trace_index, trace_place_stats[source_place][0] ]) performance_for_this_trans_execution.append([[ current_trace_index, trace_place_stats[source_place][0] ], current_trace_index - trace_place_stats[source_place][0] ]) elif min_in_arc_indexes: annotations_arcs[arc]["performance"].append( [current_trace_index, current_trace_index]) performance_for_this_trans_execution.append( [[current_trace_index, current_trace_index], 0]) del trace_place_stats[source_place][0] for arc in trans.out_arcs: target_place = arc.target if arc not in annotations_arcs: annotations_arcs[arc] = {"performance": [], "count": 0} annotations_arcs[arc][ "count"] = annotations_arcs[arc]["count"] + 1 if target_place not in trace_place_stats: trace_place_stats[target_place] = [] if trans.label or ht_perf_method == "first": trace_place_stats[target_place].append(current_trace_index) elif max_in_arc_indexes: trace_place_stats[target_place].append(max_in_arc_indexes) if performance_for_this_trans_execution: performance_for_this_trans_execution = sorted( performance_for_this_trans_execution, key=lambda x: x[1]) annotations_places_trans[trans]["performance"].append( performance_for_this_trans_execution[0][0]) return annotations_places_trans, annotations_arcs
def apply(net, initial_marking, final_marking=None, parameters=None): """ Do the playout of a Petrinet generating a log (extensive search; stop at the maximum trace length specified Parameters ----------- net Petri net to play-out initial_marking Initial marking of the Petri net final_marking If provided, the final marking of the Petri net parameters Parameters of the algorithm: Parameters.MAX_TRACE_LENGTH -> Maximum trace length """ if parameters is None: parameters = {} case_id_key = exec_utils.get_param_value(Parameters.CASE_ID_KEY, parameters, xes_constants.DEFAULT_TRACEID_KEY) activity_key = exec_utils.get_param_value(Parameters.ACTIVITY_KEY, parameters, xes_constants.DEFAULT_NAME_KEY) timestamp_key = exec_utils.get_param_value( Parameters.TIMESTAMP_KEY, parameters, xes_constants.DEFAULT_TIMESTAMP_KEY) max_trace_length = exec_utils.get_param_value(Parameters.MAX_TRACE_LENGTH, parameters, 10) return_elements = exec_utils.get_param_value(Parameters.RETURN_ELEMENTS, parameters, False) max_marking_occ = exec_utils.get_param_value(Parameters.MAX_MARKING_OCC, parameters, sys.maxsize) # assigns to each event an increased timestamp from 1970 curr_timestamp = 10000000 feasible_elements = [] to_visit = [(initial_marking, (), ())] visited = set() while len(to_visit) > 0: state = to_visit.pop(0) m = state[POSITION_MARKING] trace = state[POSITION_TRACE] elements = state[POSITION_ELEMENTS] if (m, trace) in visited: continue visited.add((m, trace)) en_t = semantics.enabled_transitions(net, m) if (final_marking is not None and m == final_marking) or (final_marking is None and len(en_t) == 0): if len(trace) <= max_trace_length: feasible_elements.append(elements) for t in en_t: new_elements = elements + (m, ) new_elements = new_elements + (t, ) counter_elements = Counter(new_elements) if counter_elements[m] > max_marking_occ: continue new_m = semantics.weak_execute(t, m) if t.label is not None: new_trace = trace + (t.label, ) else: new_trace = trace new_state = (new_m, new_trace, new_elements) if new_state in visited or len(new_trace) > max_trace_length: continue to_visit.append(new_state) if return_elements: return feasible_elements log = log_instance.EventLog() for elements in feasible_elements: log_trace = log_instance.Trace() log_trace.attributes[case_id_key] = str(len(log)) activities = [ x.label for x in elements if type(x) is PetriNet.Transition and x.label is not None ] for act in activities: curr_timestamp = curr_timestamp + 1 log_trace.append( log_instance.Event({ activity_key: act, timestamp_key: datetime.datetime.fromtimestamp(curr_timestamp) })) log.append(log_trace) return log
def __transform_model_to_mem_efficient_structure(net, im, fm, trace, parameters=None): """ Transform the Petri net model to a memory efficient structure Parameters -------------- net Petri net im Initial marking fm Final marking trace Trace parameters Parameters Returns -------------- model_struct Model data structure, including: PLACES_DICT: associates each place to a number INV_TRANS_DICT: associates a number to each transition LABELS_DICT: labels dictionary (a label to a number) TRANS_LABELS_DICT: associates each transition to the number corresponding to its label TRANS_PRE_DICT: preset of a transition, expressed as in this data structure TRANS_POST_DICT: postset of a transition, expressed as in this data structure TRANSF_IM: transformed initial marking TRANSF_FM: transformed final marking TRANSF_MODEL_COST_FUNCTION: transformed model cost function """ if parameters is None: parameters = {} activity_key = exec_utils.get_param_value(Parameters.ACTIVITY_KEY, parameters, DEFAULT_NAME_KEY) labels = sorted(list(set(x[activity_key] for x in trace))) model_cost_function = exec_utils.get_param_value( Parameters.PARAM_MODEL_COST_FUNCTION, parameters, None) if model_cost_function is None: model_cost_function = {} for t in net.transitions: if t.label is not None: model_cost_function[t] = align_utils.STD_MODEL_LOG_MOVE_COST else: preset_t = Marking() for a in t.in_arcs: preset_t[a.source] = a.weight # optimization 12/08/2020 # # instead of giving undiscriminately weight 1 to # invisible transitions, assign weight 0 to the ones # for which no 'sync' transition is enabled in their # activation markings. # # this requires to modify the state of the alignment, keeping track # of the length of the alignment, to avoid loops. en_t = enabled_transitions(net, preset_t) vis_t_trace = [t for t in en_t if t.label in labels] if len(vis_t_trace) == 0: model_cost_function[t] = 0 else: model_cost_function[t] = align_utils.STD_TAU_COST places_dict = {place: index for index, place in enumerate(net.places)} trans_dict = {trans: index for index, trans in enumerate(net.transitions)} labels = sorted( list(set(t.label for t in net.transitions if t.label is not None))) labels_dict = {labels[i]: i for i in range(len(labels))} trans_labels_dict = {} for t in net.transitions: trans_labels_dict[trans_dict[t]] = labels_dict[ t.label] if t.label is not None else None trans_pre_dict = { trans_dict[t]: {places_dict[x.source]: x.weight for x in t.in_arcs} for t in net.transitions } trans_post_dict = { trans_dict[t]: {places_dict[x.target]: x.weight for x in t.out_arcs} for t in net.transitions } transf_im = {places_dict[p]: im[p] for p in im} transf_fm = {places_dict[p]: fm[p] for p in fm} transf_model_cost_function = { trans_dict[t]: model_cost_function[t] for t in net.transitions } inv_trans_dict = {y: x for x, y in trans_dict.items()} return { PLACES_DICT: places_dict, INV_TRANS_DICT: inv_trans_dict, LABELS_DICT: labels_dict, TRANS_LABELS_DICT: trans_labels_dict, TRANS_PRE_DICT: trans_pre_dict, TRANS_POST_DICT: trans_post_dict, TRANSF_IM: transf_im, TRANSF_FM: transf_fm, TRANSF_MODEL_COST_FUNCTION: transf_model_cost_function }
def run(self): """ Runs the thread """ if self.enable_diagnostics: diagnostics = SimulationDiagnostics(self) diagnostics.start() from intervaltree import IntervalTree, Interval logging.basicConfig() logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) net, im, fm, smap, source, sink, start_time = self.net, self.im, self.fm, self.map, self.source, self.sink, self.start_time places_interval_trees = self.places_interval_trees transitions_interval_trees = self.transitions_interval_trees cases_ex_time = self.cases_ex_time current_time = start_time self.internal_thread_start_time = time() rem_time = self.get_rem_time() acquired_places = set() acquired = source.semaphore.acquire(timeout=rem_time) if acquired: acquired_places.add(source) source.assigned_time.append(current_time) current_marking = im et = enabled_transitions(net, current_marking) first_event = None last_event = None while not fm <= current_marking or len(et) == 0: et = list(enabled_transitions(net, current_marking)) ct = stochastic_utils.pick_transition(et, smap) simulated_execution_plus_waiting_time = -1 while simulated_execution_plus_waiting_time < 0: simulated_execution_plus_waiting_time = smap[ct].get_value( ) if ct in smap else 0.0 # establish how much time we need to wait before firing the transition # (it depends on the input places tokens) waiting_time = 0 for arc in ct.out_arcs: place = arc.target sem_value = int(place.semaphore._value) rem_time = self.get_rem_time() acquired = place.semaphore.acquire(timeout=rem_time) if acquired: acquired_places.add(place) rem_time = self.get_rem_time() if rem_time == 0: break if sem_value == 0: waiting_time = max( waiting_time, place.assigned_time.pop(0) - current_time) if place.assigned_time else waiting_time if rem_time == 0: for place in acquired_places: place.semaphore.release() break # if the waiting time is greater than 0, add an interval to the interval tree denoting # the waiting times for the given transition if waiting_time > 0: transitions_interval_trees[ct].add( Interval(current_time, current_time + waiting_time)) # get the actual execution time of the transition as a difference between simulated_execution_plus_waiting_time # and the waiting time execution_time = max( simulated_execution_plus_waiting_time - waiting_time, 0) # increase the timing based on the waiting time and the execution time of the transition current_time = current_time + waiting_time + execution_time for arc in ct.out_arcs: place = arc.target place.assigned_time.append(current_time) place.assigned_time = sorted(place.assigned_time) current_marking = weak_execute(ct, current_marking) if ct.label is not None: eve = Event({ xes_constants.DEFAULT_NAME_KEY: ct.label, xes_constants.DEFAULT_TIMESTAMP_KEY: datetime.datetime.fromtimestamp(current_time) }) last_event = eve if first_event is None: first_event = last_event self.list_cases[self.id].append(eve) for arc in ct.in_arcs: place = arc.source p_ex_time = place.assigned_time.pop(0) if current_time - p_ex_time > 0: places_interval_trees[place].add( Interval(p_ex_time, current_time)) place.assigned_time.append(current_time) place.assigned_time = sorted(place.assigned_time) place.semaphore.release() # sleep before starting next iteration sleep((waiting_time + execution_time) / self.small_scale_factor) if first_event is not None and last_event is not None: cases_ex_time.append( last_event[xes_constants.DEFAULT_TIMESTAMP_KEY].timestamp() - first_event[xes_constants.DEFAULT_TIMESTAMP_KEY].timestamp()) else: cases_ex_time.append(0) places_to_free = set(current_marking).union(acquired_places) for place in places_to_free: place.semaphore.release() rem_time = self.get_rem_time() if rem_time > 0: self.terminated_correctly = True if self.enable_diagnostics: logger.info( str(time()) + " terminated successfully thread ID " + str(self.id)) if self.enable_diagnostics: if rem_time == 0: if self.enable_diagnostics: logger.info( str(time()) + " terminated for timeout thread ID " + str(self.id)) if self.enable_diagnostics: diagnostics.diagn_open = False