def check_non_blocking(net0): """ Checks if a workflow net is non-blocking Parameters ------------- net Petri net Returns ------------- boolean Boolean value """ net = deepcopy(net0) petri_utils.decorate_transitions_prepostset(net) graph, inv_dictionary = petri_utils.create_networkx_directed_graph(net) dictionary = {y:x for x,y in inv_dictionary.items()} source_place = [place for place in net.places if len(place.in_arcs) == 0][0] for trans in net.transitions: # transitions with 1 input arcs does not block if len(trans.in_arcs) > 1: places = [arc.source for arc in trans.in_arcs] # search the top-right intersection between a path connecting the initial marking and the input places # of such transition # # this exploration is based on heuristics; another option is to use the exploration provided in explore_path (that is based on alignments) spaths = [[inv_dictionary[y] for y in nx.algorithms.shortest_paths.generic.shortest_path(graph, dictionary[source_place], dictionary[x])] for x in places] spaths = [[y for y in x if type(y) is petrinet.PetriNet.Transition] for x in spaths] trans_dict = {} i = 0 while i < len(places): p1 = places[i] spath1 = spaths[i] j = i + 1 while j < len(places): p2 = places[j] spath2 = spaths[j] s1, s2 = [x for x in spath1 if x in spath2], [x for x in spath2 if x in spath1] if len(s1) > 0 and len(s2) > 0 and s1[-1] == s2[-1]: # there is an intersection t = s1[-1] if len(t.out_arcs) <= 1: return False else: if t not in trans_dict: trans_dict[t] = set() trans_dict[t].add(p1) trans_dict[t].add(p2) else: return False j = j + 1 i = i + 1 # after checking if the intersecting transition has at least one exit, # we check also that the number of outputs of the transition is at least the one expected for t in trans_dict: if len(t.out_arcs) < len(trans_dict[t]): return False return True
def __search(sync_net, ini, fin, cost_function, skip, ret_tuple_as_trans_desc=False, max_align_time_trace=DEFAULT_MAX_ALIGN_TIME_TRACE): start_time = time.time() decorate_transitions_prepostset(sync_net) decorate_places_preset_trans(sync_net) closed = set() ini_state = utils.DijkstraSearchTuple(0, ini, None, None, 0) open_set = [ini_state] heapq.heapify(open_set) visited = 0 queued = 0 traversed = 0 trans_empty_preset = set(t for t in sync_net.transitions if len(t.in_arcs) == 0) while not len(open_set) == 0: if (time.time() - start_time) > max_align_time_trace: return None curr = heapq.heappop(open_set) current_marking = curr.m already_closed = current_marking in closed if already_closed: continue if current_marking == fin: return utils.__reconstruct_alignment(curr, visited, queued, traversed, ret_tuple_as_trans_desc=ret_tuple_as_trans_desc) closed.add(current_marking) visited += 1 possible_enabling_transitions = copy(trans_empty_preset) for p in current_marking: for t in p.ass_trans: possible_enabling_transitions.add(t) enabled_trans = [t for t in possible_enabling_transitions if t.sub_marking <= current_marking] trans_to_visit_with_cost = [(t, cost_function[t]) for t in enabled_trans if not ( t is not None and utils.__is_log_move(t, skip) and utils.__is_model_move(t, skip))] for t, cost in trans_to_visit_with_cost: traversed += 1 new_marking = utils.add_markings(current_marking, t.add_marking) if new_marking in closed: continue queued += 1 tp = utils.DijkstraSearchTuple(curr.g + cost, new_marking, curr, t, curr.l + 1) heapq.heappush(open_set, tp)
def __build_entities(self): """ Builds entities useful to define the marking equation """ self.Aeq = None self.__build_encodings() if self.full_bootstrap_required: petri_utils.decorate_transitions_prepostset(self.sync_net) petri_utils.decorate_places_preset_trans(self.sync_net)
def __build_entities(self): """ Builds entities useful to define the marking equation """ transitions = self.incidence_matrix.transitions self.inv_indices = {y: x for x, y in transitions.items()} self.inv_indices = [self.inv_indices[i] for i in range(len(self.inv_indices))] self.ini_vec = np.matrix(self.incidence_matrix.encode_marking(self.ini)).transpose() self.fin_vec = np.matrix(self.incidence_matrix.encode_marking(self.fin)).transpose() if self.full_bootstrap_required: petri_utils.decorate_transitions_prepostset(self.net) petri_utils.decorate_places_preset_trans(self.net)
def __search(sync_net, ini, fin, stop, cost_function, skip, max_trace_length): from pm4py.objects.petri.utils import decorate_places_preset_trans, decorate_transitions_prepostset decorate_transitions_prepostset(sync_net) decorate_places_preset_trans(sync_net) incidence_matrix = petri.incidence_matrix.construct(sync_net) ini_vec, fin_vec, cost_vec = utils.__vectorize_initial_final_cost( incidence_matrix, ini, fin, cost_function) closed = set() a_matrix = np.asmatrix(incidence_matrix.a_matrix).astype(np.float64) g_matrix = -np.eye(len(sync_net.transitions)) h_cvx = np.matrix(np.zeros(len(sync_net.transitions))).transpose() cost_vec = [x * 1.0 for x in cost_vec] use_cvxopt = False if lp_solver_factory.DEFAULT_LP_SOLVER_VARIANT == lp_solver_factory.CVXOPT_SOLVER_CUSTOM_ALIGN or lp_solver_factory.DEFAULT_LP_SOLVER_VARIANT == lp_solver_factory.CVXOPT_SOLVER_CUSTOM_ALIGN_ILP: use_cvxopt = True if use_cvxopt: # not available in the latest version of PM4Py from cvxopt import matrix a_matrix = matrix(a_matrix) g_matrix = matrix(g_matrix) h_cvx = matrix(h_cvx) cost_vec = matrix(cost_vec) h, x = utils.__compute_exact_heuristic_new_version( sync_net, a_matrix, h_cvx, g_matrix, cost_vec, incidence_matrix, ini, fin_vec, lp_solver_factory.DEFAULT_LP_SOLVER_VARIANT, use_cvxopt=use_cvxopt) # heuristics need to be adapted for prefix alignments # here we make the heuristics way less powerful h = h / (max_trace_length + 1.0) ini_state = SearchTuple(0 + h, 0, h, ini, None, None, x, True) open_set = [ini_state] heapq.heapify(open_set) visited = 0 queued = 0 traversed = 0 while not len(open_set) == 0: curr = heapq.heappop(open_set) current_marking = curr.m # 11/10/2019 (optimization Y, that was optimization X, # but with the good reasons this way): avoid checking markings in the cycle using # the __get_alt function, but check them 'on the road' already_closed = current_marking in closed if already_closed: continue while not curr.trust: h, x = utils.__compute_exact_heuristic_new_version( sync_net, a_matrix, h_cvx, g_matrix, cost_vec, incidence_matrix, curr.m, fin_vec, lp_solver_factory.DEFAULT_LP_SOLVER_VARIANT, use_cvxopt=use_cvxopt) # heuristics need to be adapted for prefix alignments # here we make the heuristics way less powerful h = h / (max_trace_length + 1.0) # 11/10/19: shall not a state for which we compute the exact heuristics be # by nature a trusted solution? tp = SearchTuple(curr.g + h, curr.g, h, curr.m, curr.p, curr.t, x, True) # 11/10/2019 (optimization ZA) heappushpop is slightly more efficient than pushing # and popping separately curr = heapq.heappushpop(open_set, tp) current_marking = curr.m # max allowed heuristics value (27/10/2019, due to the numerical instability of some of our solvers) if curr.h > lp_solver_factory.MAX_ALLOWED_HEURISTICS: continue # 12/10/2019: do it again, since the marking could be changed already_closed = current_marking in closed if already_closed: continue # 12/10/2019: the current marking can be equal to the final marking only if the heuristics # (underestimation of the remaining cost) is 0. Low-hanging fruits enab_trans = [ x for x in petri.semantics.enabled_transitions( sync_net, current_marking) ] if stop <= current_marking: #print(utils.__reconstruct_alignment(curr, visited, queued, traversed)) enab_trans = [ x for x in sync_net.transitions if x.sub_marking <= current_marking ] return current_marking closed.add(current_marking) visited += 1 possible_enabling_transitions = set() for p in current_marking: for t in p.ass_trans: possible_enabling_transitions.add(t) enabled_trans = [ t for t in possible_enabling_transitions if t.sub_marking <= current_marking ] trans_to_visit_with_cost = [ (t, cost_function[t]) for t in enabled_trans if not (curr.t is not None and utils.__is_log_move(curr.t, skip) and utils.__is_model_move(t, skip)) ] for t, cost in trans_to_visit_with_cost: traversed += 1 new_marking = utils.add_markings(current_marking, t.add_marking) if new_marking in closed: continue g = curr.g + cost queued += 1 h, x = utils.__derive_heuristic(incidence_matrix, cost_vec, curr.x, t, curr.h) # heuristics need to be adapted for prefix alignments # here we make the heuristics way less powerful h = h / (max_trace_length + 1.0) trustable = utils.__trust_solution(x) new_f = g + h tp = SearchTuple(new_f, g, h, new_marking, curr, t, x, trustable) heapq.heappush(open_set, tp)
def __search(sync_net, ini, fin, cost_function, skip): """ Search function for the decomposed/recomposed alignments Parameters ------------ sync_net Synchronous Petri net ini Initial marking fin Final marking cost_function Cost function skip Skip symbol Returns ------------- ali Alignment (if not None) """ decorate_transitions_prepostset(sync_net) decorate_places_preset_trans(sync_net) closed = set() ini_state = utils.DijkstraSearchTuple(0, ini, None, None, 0) open_set = [ini_state] heapq.heapify(open_set) visited = 0 queued = 0 traversed = 0 trans_empty_preset = set(t for t in sync_net.transitions if len(t.in_arcs) == 0) while not len(open_set) == 0: curr = heapq.heappop(open_set) current_marking = curr.m already_closed = current_marking in closed if already_closed: continue if current_marking == fin: return utils.__reconstruct_alignment(curr, visited, queued, traversed, ret_tuple_as_trans_desc=True) closed.add(current_marking) visited += 1 possible_enabling_transitions = copy(trans_empty_preset) for p in current_marking: for t in p.ass_trans: possible_enabling_transitions.add(t) enabled_trans = [ t for t in possible_enabling_transitions if t.sub_marking <= current_marking ] trans_to_visit_with_cost = [ (t, cost_function[t]) for t in enabled_trans if not (t is not None and utils.__is_log_move(t, skip) and utils.__is_model_move(t, skip)) ] for t, cost in trans_to_visit_with_cost: traversed += 1 new_marking = utils.add_markings(current_marking, t.add_marking) if new_marking in closed: continue queued += 1 tp = utils.DijkstraSearchTuple(curr.g + cost, new_marking, curr, t, curr.l + 1) heapq.heappush(open_set, tp)
def __search(sync_net, ini, fin, stop, cost_function, skip): decorate_transitions_prepostset(sync_net) decorate_places_preset_trans(sync_net) incidence_matrix = petri.incidence_matrix.construct(sync_net) ini_vec, fin_vec, cost_vec = utils.__vectorize_initial_final_cost( incidence_matrix, ini, fin, cost_function) closed = set() ini_state = utils.SearchTuple(0, 0, 0, ini, None, None, None, True) open_set = [ini_state] heapq.heapify(open_set) visited = 0 queued = 0 traversed = 0 # return all the prefix markings of the optimal alignments as set ret_markings = None # keep track of the optimal cost of an alignment (to trim search when needed) optimal_cost = None while not len(open_set) == 0: curr = heapq.heappop(open_set) current_marking = curr.m # trim alignments when we already reached an optimal alignment and the # current cost is greater than the optimal cost if optimal_cost is not None and curr.f > optimal_cost: break already_closed = current_marking in closed if already_closed: continue if stop <= current_marking: # add the current marking to the set # of returned markings if ret_markings is None: ret_markings = set() ret_markings.add(current_marking) # close the marking closed.add(current_marking) # set the optimal cost optimal_cost = curr.f continue closed.add(current_marking) visited += 1 possible_enabling_transitions = set() for p in current_marking: for t in p.ass_trans: possible_enabling_transitions.add(t) enabled_trans = [ t for t in possible_enabling_transitions if t.sub_marking <= current_marking ] trans_to_visit_with_cost = [ (t, cost_function[t]) for t in enabled_trans if not (t is None or utils.__is_log_move(t, skip) or ( utils.__is_model_move(t, skip) and not t.label[1] is None)) ] for t, cost in trans_to_visit_with_cost: traversed += 1 new_marking = utils.add_markings(current_marking, t.add_marking) if new_marking in closed: continue g = curr.g + cost queued += 1 new_f = g tp = utils.SearchTuple(new_f, g, 0, new_marking, curr, t, None, True) heapq.heappush(open_set, tp) return ret_markings
def __search(sync_net, ini, fin, cost_function, skip, ret_tuple_as_trans_desc=False, max_align_time_trace=sys.maxsize): start_time = time.time() decorate_transitions_prepostset(sync_net) decorate_places_preset_trans(sync_net) incidence_matrix = inc_mat_construct(sync_net) ini_vec, fin_vec, cost_vec = utils.__vectorize_initial_final_cost( incidence_matrix, ini, fin, cost_function) closed = set() a_matrix = np.asmatrix(incidence_matrix.a_matrix).astype(np.float64) g_matrix = -np.eye(len(sync_net.transitions)) h_cvx = np.matrix(np.zeros(len(sync_net.transitions))).transpose() cost_vec = [x * 1.0 for x in cost_vec] use_cvxopt = False if lp_solver.DEFAULT_LP_SOLVER_VARIANT == lp_solver.CVXOPT_SOLVER_CUSTOM_ALIGN or lp_solver.DEFAULT_LP_SOLVER_VARIANT == lp_solver.CVXOPT_SOLVER_CUSTOM_ALIGN_ILP: use_cvxopt = True if use_cvxopt: # not available in the latest version of PM4Py from cvxopt import matrix a_matrix = matrix(a_matrix) g_matrix = matrix(g_matrix) h_cvx = matrix(h_cvx) cost_vec = matrix(cost_vec) h, x = utils.__compute_exact_heuristic_new_version( sync_net, a_matrix, h_cvx, g_matrix, cost_vec, incidence_matrix, ini, fin_vec, lp_solver.DEFAULT_LP_SOLVER_VARIANT, use_cvxopt=use_cvxopt) ini_state = utils.SearchTuple(0 + h, 0, h, ini, None, None, x, True) open_set = [ini_state] heapq.heapify(open_set) visited = 0 queued = 0 traversed = 0 lp_solved = 1 trans_empty_preset = set(t for t in sync_net.transitions if len(t.in_arcs) == 0) while not len(open_set) == 0: if (time.time() - start_time) > max_align_time_trace: return None curr = heapq.heappop(open_set) current_marking = curr.m while not curr.trust: if (time.time() - start_time) > max_align_time_trace: return None already_closed = current_marking in closed if already_closed: curr = heapq.heappop(open_set) current_marking = curr.m continue h, x = utils.__compute_exact_heuristic_new_version( sync_net, a_matrix, h_cvx, g_matrix, cost_vec, incidence_matrix, curr.m, fin_vec, lp_solver.DEFAULT_LP_SOLVER_VARIANT, use_cvxopt=use_cvxopt) lp_solved += 1 # 11/10/19: shall not a state for which we compute the exact heuristics be # by nature a trusted solution? tp = utils.SearchTuple(curr.g + h, curr.g, h, curr.m, curr.p, curr.t, x, True) # 11/10/2019 (optimization ZA) heappushpop is slightly more efficient than pushing # and popping separately curr = heapq.heappushpop(open_set, tp) current_marking = curr.m # max allowed heuristics value (27/10/2019, due to the numerical instability of some of our solvers) if curr.h > lp_solver.MAX_ALLOWED_HEURISTICS: continue # 12/10/2019: do it again, since the marking could be changed already_closed = current_marking in closed if already_closed: continue # 12/10/2019: the current marking can be equal to the final marking only if the heuristics # (underestimation of the remaining cost) is 0. Low-hanging fruits if curr.h < 0.01: if current_marking == fin: return utils.__reconstruct_alignment( curr, visited, queued, traversed, ret_tuple_as_trans_desc=ret_tuple_as_trans_desc, lp_solved=lp_solved) closed.add(current_marking) visited += 1 possible_enabling_transitions = copy(trans_empty_preset) for p in current_marking: for t in p.ass_trans: possible_enabling_transitions.add(t) enabled_trans = [ t for t in possible_enabling_transitions if t.sub_marking <= current_marking ] trans_to_visit_with_cost = [ (t, cost_function[t]) for t in enabled_trans if not (t is not None and utils.__is_log_move(t, skip) and utils.__is_model_move(t, skip)) ] for t, cost in trans_to_visit_with_cost: traversed += 1 new_marking = utils.add_markings(current_marking, t.add_marking) if new_marking in closed: continue g = curr.g + cost queued += 1 h, x = utils.__derive_heuristic(incidence_matrix, cost_vec, curr.x, t, curr.h) trustable = utils.__trust_solution(x) new_f = g + h tp = utils.SearchTuple(new_f, g, h, new_marking, curr, t, x, trustable) heapq.heappush(open_set, tp)
def construct_sync_product(self, trace, petri_net, initial_marking, final_marking, parameters=None): """ Performs the basic alignment search, given a trace and a net. Parameters ---------- trace: :class:`list` input trace, assumed to be a list of events (i.e. the code will use the activity key to get the attributes) petri_net: :class:`pm4py.objects.petri.net.PetriNet` the Petri net to use in the alignment initial_marking: :class:`pm4py.objects.petri.net.Marking` initial marking in the Petri net final_marking: :class:`pm4py.objects.petri.net.Marking` final marking in the Petri net parameters: :class:`dict` (optional) dictionary containing one of the following: Parameters.PARAM_TRACE_COST_FUNCTION: :class:`list` (parameter) mapping of each index of the trace to a positive cost value Parameters.PARAM_MODEL_COST_FUNCTION: :class:`dict` (parameter) mapping of each transition in the model to corresponding model cost Parameters.PARAM_SYNC_COST_FUNCTION: :class:`dict` (parameter) mapping of each transition in the model to corresponding synchronous costs Parameters.ACTIVITY_KEY: :class:`str` (parameter) key to use to identify the activity described by the events Returns ------- dictionary: `dict` with keys **alignment**, **cost**, **visited_states**, **queued_states** and **traversed_arcs** """ if parameters is None: parameters = {} parameters = copy(parameters) activity_key = exec_utils.get_param_value(Parameters.ACTIVITY_KEY, parameters, DEFAULT_NAME_KEY) trace_cost_function = exec_utils.get_param_value( Parameters.PARAM_TRACE_COST_FUNCTION, parameters, None) model_cost_function = exec_utils.get_param_value( Parameters.PARAM_MODEL_COST_FUNCTION, parameters, None) trace_net_constr_function = exec_utils.get_param_value( Parameters.TRACE_NET_CONSTR_FUNCTION, parameters, None) trace_net_cost_aware_constr_function = exec_utils.get_param_value( Parameters.TRACE_NET_COST_AWARE_CONSTR_FUNCTION, parameters, construct_trace_net_cost_aware) if trace_cost_function is None: trace_cost_function = list(map(lambda e: 1, trace)) parameters[ Parameters.PARAM_TRACE_COST_FUNCTION] = trace_cost_function if model_cost_function is None: # reset variables value model_cost_function = dict() sync_cost_function = dict() # apply unit cost function to assign cost for each move for t in petri_net.transitions: if t.label is not None: model_cost_function[t] = 1 sync_cost_function[t] = 0 else: model_cost_function[t] = 0 parameters[ Parameters.PARAM_MODEL_COST_FUNCTION] = model_cost_function parameters[ Parameters.PARAM_SYNC_COST_FUNCTION] = sync_cost_function trace_net, trace_im, trace_fm, parameters[ Parameters. PARAM_TRACE_NET_COSTS] = trace_net_cost_aware_constr_function( trace, trace_cost_function, activity_key=activity_key) if parameters is None: parameters = {} ret_tuple_as_trans_desc = exec_utils.get_param_value( Parameters.PARAM_ALIGNMENT_RESULT_IS_SYNC_PROD_AWARE, parameters, False) trace_cost_function = exec_utils.get_param_value( Parameters.PARAM_TRACE_COST_FUNCTION, parameters, None) model_cost_function = exec_utils.get_param_value( Parameters.PARAM_MODEL_COST_FUNCTION, parameters, None) sync_cost_function = exec_utils.get_param_value( Parameters.PARAM_SYNC_COST_FUNCTION, parameters, None) trace_net_costs = exec_utils.get_param_value( Parameters.PARAM_TRACE_NET_COSTS, parameters, None) revised_sync = dict() for t_trace in trace_net.transitions: for t_model in petri_net.transitions: if t_trace.label == t_model.label: revised_sync[(t_trace, t_model)] = sync_cost_function[t_model] sync_prod, sync_initial_marking, sync_final_marking, cost_function = self.construct_cost_aware( trace_net, trace_im, trace_fm, petri_net, initial_marking, final_marking, utils.SKIP, trace_net_costs, model_cost_function, revised_sync) self.final_marking = sync_final_marking max_align_time_trace = exec_utils.get_param_value( Parameters.PARAM_MAX_ALIGN_TIME_TRACE, parameters, sys.maxsize) decorate_transitions_prepostset(trace_net) decorate_places_preset_trans(trace_net) trans_empty_preset = set(t for t in trace_net.transitions if len(t.in_arcs) == 0) current_marking = trace_im trace_lst = [] while current_marking != trace_fm: enabled_trans = copy(trans_empty_preset) for p in current_marking: for t in p.ass_trans: if t.sub_marking <= current_marking: enabled_trans.add(t) trace_lst.append(t) for t in enabled_trans: new_marking = utils.add_markings(current_marking, t.add_marking) current_marking = new_marking decorate_transitions_prepostset(sync_prod) decorate_places_preset_trans(sync_prod) incidence_matrix = construct_incidence_matrix(sync_prod) trace_sync = [[] for i in range(0, len(trace_lst))] trace_log = [None for i in range(0, len(trace_lst))] t_index = incidence_matrix.transitions for t in sync_prod.transitions: for i in range(len(trace_lst)): if trace_lst[i].name == t.name[0] and t.label[1] == ">>": trace_log[i] = t_index[t] if trace_lst[i].name == t.name[0] and t.label[1] != ">>": trace_sync[i].append(t_index[t]) return sync_initial_marking, sync_final_marking, cost_function, incidence_matrix, trace_sync, trace_log
def check_loops_generating_tokens(net0): """ Check if the Petri net contains loops generating tokens Parameters ------------ net0 Petri net Returns ------------ boolean Boolean value (True if the net has loops generating tokens) """ net = deepcopy(net0) petri_utils.decorate_transitions_prepostset(net) graph, inv_dictionary = petri_utils.create_networkx_directed_graph(net) dictionary = {y:x for x,y in inv_dictionary.items()} loops_trans = petri_utils.get_cycles_petri_net_transitions(net) for loop in loops_trans: m = petrinet.Marking() for index, trans in enumerate(loop): m = m + trans.add_marking visited_couples = set() while True: changed_something = False # if there are no places with positive marking replaying the loop, then we are fine neg_places = [p for p in m if m[p] < 0] pos_places = [p for p in m if m[p] > 0] for p1 in pos_places: # if there are no places with negative marking replaying the loop, then we are doomed for p2 in neg_places: if not ((p1, p2)) in visited_couples: visited_couples.add((p1, p2)) # otherwise, do a check if there is a path in the workflow net between the two places # this exploration is based on heuristics; indeed, since we can enter the cycle multiple times # it does not really matter if we reach suddenly the optimal path # # another option is to use the exploration provided in explore_path (that is based on alignments) spath = None try: spath = nx.algorithms.shortest_paths.generic.shortest_path(graph, dictionary[p1], dictionary[p2]) except: pass if spath is not None: trans = [inv_dictionary[x] for x in spath if type(inv_dictionary[x]) is petrinet.PetriNet.Transition] sec_m0 = petrinet.Marking() for t in trans: sec_m0 = sec_m0 + t.add_marking sec_m = petrinet.Marking() for place in m: if place in sec_m0: sec_m[place] = sec_m0[place] m1 = m + sec_m # the exploration is (in part) successful if m1[p1] == 0: m = m1 changed_something = True break if not changed_something: break if not changed_something: break # at this point, we have definitely created one token if sum(m[place] for place in m) > 0: return True return False
def __search(sync_net, ini, fin, cost_function, skip, ret_tuple_as_trans_desc=False, max_align_time_trace=sys.maxsize): start_time = time.time() decorate_transitions_prepostset(sync_net) decorate_places_preset_trans(sync_net) incidence_matrix = inc_mat_construct(sync_net) ini_vec, fin_vec, cost_vec = utils.__vectorize_initial_final_cost( incidence_matrix, ini, fin, cost_function) closed = set() heu_dict = {} heu_max_ind_dict = {} mtcgt_dict = {} parameters = {} parameters[marking_equation.Parameters.FULL_BOOTSTRAP_REQUIRED] = False parameters[marking_equation.Parameters.INCIDENCE_MATRIX] = incidence_matrix parameters[marking_equation.Parameters.COSTS] = cost_function visited = 0 queued = 0 traversed = 0 me = marking_equation.build(sync_net, ini, fin, parameters=parameters) h, x = me.solve() lp_solved = 1 # try to see if the firing sequence is already fine firing_sequence, reach_fm, explained_events = me.get_firing_sequence(x) if reach_fm: return __reconstruct_alignment( firing_sequence, h, visited, queued, traversed, ret_tuple_as_trans_desc=ret_tuple_as_trans_desc, lp_solved=lp_solved) mm, index = __get_model_marking_and_index(ini) __update_heu_dict(heu_dict, heu_max_ind_dict, mm, index, h, x, firing_sequence, incidence_matrix, cost_vec) ini_state = utils.TweakedSearchTuple(0 + h, 0, h, ini, None, None, x, True, False) open_set = [ini_state] heapq.heapify(open_set) trans_empty_preset = set(t for t in sync_net.transitions if len(t.in_arcs) == 0) while not len(open_set) == 0: if (time.time() - start_time) > max_align_time_trace: return None curr = heapq.heappop(open_set) current_marking = curr.m while not curr.trust: if (time.time() - start_time) > max_align_time_trace: return None already_closed = current_marking in closed if already_closed: curr = heapq.heappop(open_set) current_marking = curr.m continue if curr.t not in mtcgt_dict: lp_solved += 1 mtcgt = __min_total_cost_given_trans(me, ini, incidence_matrix, curr.t) mtcgt_dict[curr.t] = mtcgt else: mtcgt = mtcgt_dict[curr.t] h1 = max(mtcgt - curr.g, 0) if h1 > curr.h: tp = utils.TweakedSearchTuple(curr.g + h1, curr.g, h1, curr.m, curr.p, curr.t, curr.x, False, False) curr = heapq.heappushpop(open_set, tp) current_marking = curr.m continue mm, index = __get_model_marking_and_index(curr.m) h2, x2, trust2 = __get_heu_from_dict(heu_dict, heu_max_ind_dict, mm, index) if h2 is not None and h2 > curr.h: tp = utils.TweakedSearchTuple(curr.g + h2, curr.g, h2, curr.m, curr.p, curr.t, x2, trust2, False) curr = heapq.heappushpop(open_set, tp) current_marking = curr.m continue me.change_ini_vec(curr.m) h, x = me.solve() __update_heu_dict_specific_point(heu_dict, heu_max_ind_dict, mm, index, h, x) lp_solved += 1 tp = utils.TweakedSearchTuple(curr.g + h, curr.g, h, curr.m, curr.p, curr.t, x, True, True) curr = heapq.heappushpop(open_set, tp) current_marking = curr.m already_closed = current_marking in closed if already_closed: continue if curr.h < 0.01: if current_marking == fin: trans_list = __transitions_list_from_state(curr) return __reconstruct_alignment( trans_list, curr.f, visited, queued, traversed, ret_tuple_as_trans_desc=ret_tuple_as_trans_desc, lp_solved=lp_solved) if curr.virgin: # try to see if the firing sequence is already fine firing_sequence, reach_fm, explained_events = me.get_firing_sequence( curr.x) if reach_fm: trans_list = __transitions_list_from_state(curr) + list( firing_sequence) return __reconstruct_alignment( trans_list, curr.f, visited, queued, traversed, ret_tuple_as_trans_desc=ret_tuple_as_trans_desc, lp_solved=lp_solved) mm, index = __get_model_marking_and_index(curr.m) __update_heu_dict(heu_dict, heu_max_ind_dict, mm, index, h, x, firing_sequence, incidence_matrix, cost_vec) closed.add(current_marking) visited += 1 possible_enabling_transitions = copy(trans_empty_preset) for p in current_marking: for t in p.ass_trans: possible_enabling_transitions.add(t) enabled_trans = [ t for t in possible_enabling_transitions if t.sub_marking <= current_marking ] trans_to_visit_with_cost = [ (t, cost_function[t]) for t in enabled_trans if not (t is not None and utils.__is_log_move(t, skip) and utils.__is_model_move(t, skip)) ] for t, cost in trans_to_visit_with_cost: traversed += 1 new_marking = utils.add_markings(current_marking, t.add_marking) if new_marking in closed: continue g = curr.g + cost queued += 1 h, x = utils.__derive_heuristic(incidence_matrix, cost_vec, curr.x, t, curr.h) trust = utils.__trust_solution(x) mm, index = __get_model_marking_and_index(new_marking) if not trust: h2, x2, trust2 = __get_heu_from_dict(heu_dict, heu_max_ind_dict, mm, index) if h2 is not None and (h2 > h or trust2): h = h2 x = x2 trust = trust2 else: __update_heu_dict_specific_point(heu_dict, heu_max_ind_dict, mm, index, h, x) new_f = g + h tp = utils.TweakedSearchTuple(new_f, g, h, new_marking, curr, t, x, trust, False) heapq.heappush(open_set, tp)