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) 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: # from pympler.asizeof import asizeof # from pm4py.util import measurements # measurements.Measurements.ALIGN_TIME.append(asizeof(open_set)) return utils.__reconstruct_alignment(curr, visited, queued, traversed, ret_tuple_as_trans_desc=ret_tuple_as_trans_desc) closed.add(current_marking) visited += 1 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) 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 _expand_from_current_marking(self, curr, cost_vec, closed): """ Expand all subsequent markings from current marking. """ # get subsequent firing transitions enabled_trans1 = {} for p in curr.m: for t in p.ass_trans: if t.sub_marking <= curr.m: enabled_trans1[t] = self.incidence_matrix.transitions[t] enabled_trans1 = dict(sorted(enabled_trans1.items(), key=lambda item: item[1])) enabled_trans = enabled_trans1.keys() trans_to_visit_with_cost = [(t, self.cost_function[t]) for t in enabled_trans if not ( t is not None and is_log_move(t, '>>') and is_model_move(t, '>>'))] for t, cost in trans_to_visit_with_cost: # compute the new g score of the subsequent marking reached if t would be fired new_g = curr.g + cost new_m = add_markings(curr.m, t.add_marking) self.traversed_arc += 1 # subsequent marking is fresh, compute the f score of this path and add it to open set if new_m in closed: # the heuristic is not consistent, th us smaller g than closed could happen if closed[new_m] > new_g: del closed[new_m] self.order += 1 new_h, new_x, trustable = derive_heuristic(cost_vec, curr.x, self.incidence_matrix.transitions[t], curr.h) marking_to_reopen = NormalMarking(new_g + new_h, new_g, new_h, new_m, curr, t, new_x, trustable, self.order) if trustable and t.label[0] != ">>": curr_max = get_max_events(marking_to_reopen) if curr_max > self.max_rank: self.max_rank = curr_max start_time = timeit.default_timer() self.open_set.heap_insert(marking_to_reopen) self.queue_time += timeit.default_timer() - start_time self.num_insert += 1 else: start_time = timeit.default_timer() m_in_open = self.open_set.heap_find(new_m) self.queue_time += timeit.default_timer() - start_time # subsequent marking is already in open set if m_in_open: start_time = timeit.default_timer() marking_to_explore = self.open_set.heap_get(new_m) self.queue_time += timeit.default_timer() - start_time if new_g < marking_to_explore.g: marking_to_explore.p, marking_to_explore.t, marking_to_explore.g = curr, t, new_g marking_to_explore.h, marking_to_explore.x, marking_to_explore.trust = \ derive_heuristic(cost_vec, curr.x, self.incidence_matrix.transitions[t], curr.h) marking_to_explore.f = marking_to_explore.g + marking_to_explore.h start_time = timeit.default_timer() self.open_set.heap_update(marking_to_explore) self.queue_time += timeit.default_timer() - start_time self.num_update += 1 if t.label[0] != ">>": curr_max = get_max_events(marking_to_explore) if curr_max > self.max_rank: self.max_rank = curr_max # subsequent marking has equal path, but the heuristic change from infeasible to feasible elif not marking_to_explore.trust: new_h, new_x, trustable = derive_heuristic(cost_vec, curr.x, self.incidence_matrix.transitions[t], curr.h) if trustable: marking_to_explore.h = new_h marking_to_explore.f = new_h + marking_to_explore.g marking_to_explore.trustable = True marking_to_explore.x = new_x start_time = timeit.default_timer() self.open_set.heap_update(marking_to_explore) self.queue_time += timeit.default_timer() - start_time self.num_update += 1 if t.label[0] != ">>": curr_max = get_max_events(marking_to_explore) if curr_max > self.max_rank: self.max_rank = curr_max else: self.order += 1 new_h, new_x, trustable = derive_heuristic(cost_vec, curr.x, self.incidence_matrix.transitions[t], curr.h) new_marking_to_explore = NormalMarking(new_g + new_h, new_g, new_h, new_m, curr, t, new_x, trustable, self.order) if trustable and t.label[0] != ">>": curr_max = get_max_events(new_marking_to_explore) if curr_max > self.max_rank: self.max_rank = curr_max start_time = timeit.default_timer() self.open_set.heap_insert(new_marking_to_explore) self.queue_time += timeit.default_timer() - start_time self.num_insert += 1
def __search(net, ini, fin, variants, exponent=2, marking_limit=1000): ''' This function is the A* algorithm for multi-alignments. ''' decorate_transitions_prepostset(net) decorate_places_preset_trans(net) mymemory = {} closed = {} ini_state = utils.DijkstraSearchTupleForAntiAndMulti(0, ini, []) open_set = [ini_state] heapq.heapify(open_set) visited = 0 queued = 0 traversed = 0 best, sizeAA = None, None distanceTime = 0 trans_empty_preset = set(t for t in net.transitions if len(t.in_arcs) == 0) def costfunction(t, curr_ma, withFrac=True): ''' Compute the maximal distance of the current multi-alignment + t and the variants. :param t: transition that we want to add in the current multi-alignment :param curr_ma: current prefix of multi-alignment :param withFrac: the fraction that prevents the best suffice, this parameter is false at the end of the algorithm. ''' if t is None: str_aa = [a.label for a in curr_ma] elif len(curr_ma) == 0: str_aa = [t.label] else: str_aa = [a.label for a in curr_ma] + [t.label] all = [] for v in variants: if str(v + str_aa) not in mymemory.keys() or not withFrac: size_of_alignment, distance = discountedEditDistance( str_aa, v, exponent) if withFrac: mymemory[str(v + str_aa)] = distance - ( exponent**(-len(str_aa) + 1) - exponent** (-(len(str_aa) + len(v)))) / (exponent - 1) else: mymemory[str(v + str_aa)] = distance all.append(mymemory[str(v + str_aa)]) return max(all) # ---------------------------- # a* algorithm starts here while not len(open_set) == 0: curr = heapq.heappop(open_set) if best and best.g < curr.g: continue if curr.m == fin: curr.g = costfunction(None, curr.r, withFrac=False) # in case there only one run if not best and (len(heapq.nsmallest(1, open_set)) == 0 or heapq.nsmallest(1, open_set)[0].g > curr.g): return { 'multi-alignment': curr.r, 'cost': curr.g, 'visited_states': visited, 'queued_states': queued, 'traversed_arcs': traversed, "max_distance_to_log": getMaxDist(curr.r, variants) } # other runs might be interesting elif not best or (best and best.g > curr.g): best = curr continue closed[curr.m] = 1 if ( curr.m) not in closed.keys() else closed[curr.m] + 1 visited += 1 possible_enabling_transitions = copy(trans_empty_preset) for p in curr.m: for t in p.ass_trans: possible_enabling_transitions.add(t) enabled_trans = [ t for t in possible_enabling_transitions if t.sub_marking <= curr.m ] trans_to_visit_with_cost = [(t, costfunction(t, curr.r, withFrac=True)) for t in enabled_trans if t is not None] for t, cost in trans_to_visit_with_cost: traversed += 1 new_marking = utils.add_markings(curr.m, t.add_marking) if ((new_marking) in closed.keys() and closed[new_marking] > marking_limit) or cost == curr.g: continue queued += 1 tp = utils.DijkstraSearchTupleForAntiAndMulti( cost, new_marking, curr.r + [t]) heapq.heappush(open_set, tp) return { 'multi-alignment': best.r, 'cost': best.g, 'visited_states': visited, 'queued_states': queued, 'traversed_arcs': traversed, "max_distance_to_log": getMaxDist(best.r, variants) }
def __search(sync_net, ini, fin, stop, cost_function, skip): decorate_transitions_prepostset(sync_net) decorate_places_preset_trans(sync_net) 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 enabled_trans = set() for p in current_marking: for t in p.ass_trans: if t.sub_marking <= current_marking: enabled_trans.add(t) 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 _expand_from_current_marking(self, curr, cost_vec, closed): """ Expand all subsequent markings from current marking. """ enabled_trans1 = {} for p in curr.m: for t in p.ass_trans: if t.sub_marking <= curr.m: enabled_trans1[t] = self.incidence_matrix.transitions[t] enabled_trans1 = dict( sorted(enabled_trans1.items(), key=lambda item: item[1])) enabled_trans = enabled_trans1.keys() trans_to_visit_with_cost = [ (t, self.cost_function[t]) for t in enabled_trans if not (t is not None and is_log_move(t, '>>') and is_model_move(t, '>>')) ] for t, cost in trans_to_visit_with_cost: t_idx = self.incidence_matrix.transitions[t] # compute the new g score of the subsequent marking reached if t would be fired new_g = curr.g + cost new_m = add_markings(curr.m, t.add_marking) self.traversed_arc += 1 # subsequent marking is fresh, compute the f score of this path and add it to open set if new_m in closed: marking_to_explore = closed[new_m] # reach this marking with shorter path, abandon all longer paths if marking_to_explore.g > new_g: t_idx = self.incidence_matrix.transitions[t] del closed[new_m] marking_to_explore.p, marking_to_explore.t, marking_to_explore.g = curr, t, new_g # update previous transition list and abandon path before new_parikh_vec_lst = get_parikh_vec_lst( curr.parikh_vec_lst, t_idx) marking_to_explore.parikh_vec_lst = new_parikh_vec_lst new_h, new_x, trustable = derive_multi_heuristic( cost_vec, curr.x, t_idx, curr.h) marking_to_explore.h = new_h marking_to_explore.x = new_x marking_to_explore.trust = trustable marking_to_explore.f = marking_to_explore.g + new_h start_time = timeit.default_timer() self.queue += timeit.default_timer() - start_time self.update += 1 if trustable or not self.split_open_set_flag: if trustable: marking_to_explore.heuristic_priority = curr.heuristic_priority self.max_rank = check_max_event( marking_to_explore, self.max_rank, t) start_time = timeit.default_timer() self.open_set.heap_insert(marking_to_explore) self.queue += timeit.default_timer() - start_time self.insertion += 1 else: self.infeasible_set[new_m] = marking_to_explore elif marking_to_explore.g == new_g: # update previous paths temp_parikh_vec_lst = get_parikh_vec_lst( curr.parikh_vec_lst, t_idx) new_parikh_vec_lst, update_flag = update_parikh_vec_lst( temp_parikh_vec_lst, marking_to_explore.parikh_vec_lst) # if new paths found if update_flag: del closed[new_m] marking_to_explore.parikh_vec_lst = new_parikh_vec_lst # compute all possible solution vector new_h, new_x, trustable = derive_multi_heuristic( cost_vec, curr.x, t_idx, curr.h) # if the h is still feasible, keep it in open set, otherwise put into infeasible set if trustable or not self.split_open_set_flag: if trustable: if curr.heuristic_priority > marking_to_explore.heuristic_priority: marking_to_explore.heuristic_priority = curr.heuristic_priority marking_to_explore.x = new_x else: marking_to_explore.x = concatenate_two_sol( marking_to_explore.x, new_x) marking_to_explore.h = min(marking_to_explore.h, new_h) marking_to_explore.f = marking_to_explore.g + marking_to_explore.h start_time = timeit.default_timer() self.open_set.heap_insert(marking_to_explore) self.queue += timeit.default_timer() - start_time self.update += 1 else: self.infeasible_set[new_m] = marking_to_explore else: # check if m is in feasible set m_in_open_flag = self.open_set.heap_find(new_m) # check if m is in infeasible set m_in_infeasible_set = self.split_open_set_flag and new_m in self.infeasible_set # split open set into two: set containing feasible markings and set containing infeasible markings if self.split_open_set_flag: # if this marking is already in open set (feasible set) if m_in_open_flag: start_time = timeit.default_timer() marking_to_explore = self.open_set.heap_get(new_m) self.queue += timeit.default_timer() - start_time # reach this marking with shorter path, abandon all longer paths if new_g < marking_to_explore.g: marking_to_explore.p, marking_to_explore.t, marking_to_explore.g = curr, t, new_g # update previous transition list marking_to_explore.parikh_vec_lst = get_parikh_vec_lst( curr.parikh_vec_lst, t_idx) # get heuristic and solution vector new_h, new_x, trustable = derive_multi_heuristic( cost_vec, curr.x, t_idx, curr.h) marking_to_explore.h = new_h marking_to_explore.x = new_x marking_to_explore.trust = trustable marking_to_explore.f = marking_to_explore.g + marking_to_explore.h # if the h is still feasible, keep it in open set, otherwise put into infeasible set if trustable: start_time = timeit.default_timer() self.open_set.heap_update(marking_to_explore) self.queue += timeit.default_timer( ) - start_time self.update += 1 else: self.open_set.heap_remove(marking_to_explore) self.infeasible_set[new_m] = marking_to_explore # if found a path with equal length, need to propagate the new path elif new_g == marking_to_explore.g: # update previous transition list temp_parikh_vec_lst = get_parikh_vec_lst( curr.parikh_vec_lst, t_idx) new_parikh_vec_lst, update_flag = update_parikh_vec_lst( temp_parikh_vec_lst, marking_to_explore.parikh_vec_lst) # if new paths found if update_flag: marking_to_explore.parikh_vec_lst = new_parikh_vec_lst # compute all possible solution vector new_h, new_x, trustable = derive_multi_heuristic( cost_vec, curr.x, t_idx, curr.h) # if the h is still feasible, keep it in open set, otherwise put into infeasible set if trustable: marking_to_explore.h = min( marking_to_explore.h, new_h) marking_to_explore.x = concatenate_two_sol( marking_to_explore.x, new_x) marking_to_explore.f = marking_to_explore.g + marking_to_explore.h start_time = timeit.default_timer() self.open_set.heap_update( marking_to_explore) self.queue += timeit.default_timer( ) - start_time self.update += 1 # subsequent marking is not in open set but in infeasible set elif m_in_infeasible_set: marking_to_explore = self.infeasible_set[new_m] # if paths are updated if new_g < marking_to_explore.g: marking_to_explore.p, marking_to_explore.t, marking_to_explore.g = curr, t, new_g # update previous transition list marking_to_explore.parikh_vec_lst = get_parikh_vec_lst( curr.parikh_vec_lst, t_idx) new_h, new_x, trustable = derive_multi_heuristic( cost_vec, curr.x, t_idx, curr.h) marking_to_explore.h = new_h marking_to_explore.x = new_x marking_to_explore.trust = trustable marking_to_explore.f = marking_to_explore.g + marking_to_explore.h if trustable: # remove from infeasible set del self.infeasible_set[new_m] start_time = timeit.default_timer() self.open_set.heap_insert(marking_to_explore) self.queue += timeit.default_timer( ) - start_time self.update += 1 self.insertion += 1 else: self.infeasible_set[new_m] = marking_to_explore elif new_g == marking_to_explore.g: # update previous transition list temp_parikh_vec_lst = get_parikh_vec_lst( curr.parikh_vec_lst, t_idx) new_parikh_vec_lst, update_flag = update_parikh_vec_lst( temp_parikh_vec_lst, marking_to_explore.parikh_vec_lst) # reach the marking with a different paths but has same g-value if update_flag: # compute all possible solution vector marking_to_explore.parikh_vec_lst = new_parikh_vec_lst new_h, new_x, trustable = derive_multi_heuristic( cost_vec, curr.x, t_idx, curr.h) # if the h is still feasible, keep it in open set, otherwise put into infeasible set if trustable: marking_to_explore.h = new_h marking_to_explore.x = new_x marking_to_explore.trust = True marking_to_explore.f = marking_to_explore.g + marking_to_explore.h # remove from infeasible set del self.infeasible_set[new_m] start_time = timeit.default_timer() self.open_set.heap_insert( marking_to_explore) self.queue += timeit.default_timer( ) - start_time self.insertion += 1 else: marking_to_explore.h = min( marking_to_explore.h, new_h) marking_to_explore.f = marking_to_explore.g + marking_to_explore.h self.infeasible_set[ new_m] = marking_to_explore # explore marking for the first time, namely, neither in open set or infeasible set else: # update previous transition list new_parikh_vec_lst = get_parikh_vec_lst( curr.parikh_vec_lst, t_idx) t_idx = self.incidence_matrix.transitions[t] new_h, new_x, trustable = derive_multi_heuristic( cost_vec, curr.x, t_idx, curr.h) # if the h is still feasible, keep it in open set, otherwise put into infeasible set if trustable: tp = CacheReopenMarking(new_g + new_h, new_g, new_h, new_m, curr, t, new_x, trustable, self.order, new_parikh_vec_lst, curr.heuristic_priority) start_time = timeit.default_timer() self.open_set.heap_insert(tp) self.queue += timeit.default_timer() - start_time self.insertion += 1 self.max_rank = check_max_event( tp, self.max_rank, t) else: tp = CacheReopenMarking(new_g + new_h, new_g, new_h, new_m, curr, t, [], trustable, self.order, new_parikh_vec_lst, curr.heuristic_priority) self.infeasible_set[new_m] = tp # put all markings into open set, regardless of feasibleity of markings else: if m_in_open_flag: start_time = timeit.default_timer() marking_to_explore = self.open_set.heap_get(new_m) self.queue += timeit.default_timer() - start_time # if shorter path found, update if new_g < marking_to_explore.g: marking_to_explore.p, marking_to_explore.t, marking_to_explore.g = curr, t, new_g # update previous transition list and abandon path before new_parikh_vec_lst = get_parikh_vec_lst( curr.parikh_vec_lst, t_idx) marking_to_explore.parikh_vec_lst = new_parikh_vec_lst new_h, new_x, trustable = derive_multi_heuristic( cost_vec, curr.x, t_idx, curr.h) marking_to_explore.h = new_h marking_to_explore.x = new_x marking_to_explore.trust = trustable marking_to_explore.f = marking_to_explore.g + new_h start_time = timeit.default_timer() self.open_set.heap_update(marking_to_explore) self.queue += timeit.default_timer() - start_time self.update += 1 # subsequent marking has equal path, but the heuristic change from infeasible to feasible elif new_g == marking_to_explore.g: temp_parikh_vec_lst = get_parikh_vec_lst( curr.parikh_vec_lst, t_idx) new_parikh_vec_lst, update_flag = update_parikh_vec_lst( temp_parikh_vec_lst, marking_to_explore.parikh_vec_lst) # if new paths are found if update_flag: marking_to_explore.parikh_vec_lst = new_parikh_vec_lst new_h, new_x, trust_flag = derive_multi_heuristic( cost_vec, curr.x, t_idx, curr.h) # if the path is equally long, but the heuristic change from infeasible to feasible if not marking_to_explore.trust and trust_flag: marking_to_explore.h = new_h marking_to_explore.f = marking_to_explore.h + marking_to_explore.g marking_to_explore.x = new_x marking_to_explore.trust_flag = True start_time = timeit.default_timer() self.open_set.heap_update( marking_to_explore) self.queue += timeit.default_timer( ) - start_time self.update += 1 elif marking_to_explore.trust and trust_flag: marking_to_explore.h = min( marking_to_explore.h, new_h) marking_to_explore.f = marking_to_explore.g + marking_to_explore.h marking_to_explore.x = concatenate_two_sol( marking_to_explore.x, new_x) start_time = timeit.default_timer() self.open_set.heap_update( marking_to_explore) self.queue += timeit.default_timer( ) - start_time self.update += 1 # the marking explored is not in open set else: self.order += 1 new_h, new_x, trustable = derive_multi_heuristic( cost_vec, curr.x, t_idx, curr.h) new_parikh_vec_lst = get_parikh_vec_lst( curr.parikh_vec_lst, t_idx) tp = CacheReopenMarking(new_g + new_h, new_g, new_h, new_m, curr, t, new_x, trustable, self.order, new_parikh_vec_lst, curr.heuristic_priority) if trustable: self.max_rank = check_max_event( tp, self.max_rank, t) start_time = timeit.default_timer() self.open_set.heap_insert(tp) self.queue += timeit.default_timer() - start_time self.insertion += 1
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 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) 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 search(self): incidence_matrix = self.incidence_matrix ini_vec, fin_vec, cost_vec = self.__vectorize_initial_final_cost( self.incidence_matrix, self.ini, self.fin, self.cost_function) closed = set() cost_vec = [x * 1.0 for x in cost_vec] start_time = timeit.default_timer() h, x = compute_init_heuristic_without_split( np.array(np.array(fin_vec) - np.array(ini_vec)), np.array(incidence_matrix.a_matrix), np.array(cost_vec)) self.heuristic_time += timeit.default_timer() - start_time ini_state = utils.SearchTuple(0 + h, 0, h, self.ini, None, None, x, True) open_set = [ini_state] self.num_insert += 1 heapq.heapify(open_set) self.queue_time += timeit.default_timer() - start_time self.simple_lp = 1 trans_empty_preset = set(t for t in incidence_matrix.transitions if len(t.in_arcs) == 0) while not len(open_set) == 0: start_time = timeit.default_timer() curr = heapq.heappop(open_set) self.queue_time += timeit.default_timer() - start_time self.num_removal += 1 current_marking = curr.m while not curr.trust: already_closed = current_marking in closed if already_closed: start_time = timeit.default_timer() curr = heapq.heappop(open_set) self.queue_time += timeit.default_timer() - start_time self.num_removal += 1 current_marking = curr.m continue start_time = timeit.default_timer() h, x = compute_init_heuristic_without_split( np.array(fin_vec) - np.array(incidence_matrix.encode_marking(curr.m)), np.array(incidence_matrix.a_matrix), np.array(cost_vec)) self.heuristic_time += timeit.default_timer() - start_time self.simple_lp += 1 tp = utils.SearchTuple(curr.g + h, curr.g, h, curr.m, curr.p, curr.t, x, True) start_time = timeit.default_timer() curr = heapq.heappushpop(open_set, tp) self.queue_time += timeit.default_timer() - start_time self.num_insert += 1 self.num_removal += 1 current_marking = curr.m already_closed = current_marking in closed if already_closed: continue if curr.h < 0.01: if current_marking == self.fin: return self._reconstruct_alignment(curr) closed.add(current_marking) self.visited += 1 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) trans_to_visit_with_cost = [ (t, self.cost_function[t]) for t in enabled_trans if not (t is not None and is_log_move(t, '>>') and is_model_move(t, '>>')) ] for t, cost in trans_to_visit_with_cost: self.traversed += 1 new_marking = utils.add_markings(current_marking, t.add_marking) if new_marking in closed: continue g = curr.g + cost h, x, trustable = derive_heuristic( cost_vec, curr.x, incidence_matrix.transitions[t], curr.h) new_f = g + h tp = utils.SearchTuple(new_f, g, h, new_marking, curr, t, x, trustable) start_time = timeit.default_timer() heapq.heappush(open_set, tp) self.queue_time += timeit.default_timer() - start_time self.num_insert += 1
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 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) 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)
def __search_without_synchr(net, ini, fin, log_trace, skip=utils.SKIP, ret_tuple_as_trans_desc=True, max_align_time_trace=sys.maxsize, expo=2): ''' In this function that can be called with the following way: alignment.algorithm.apply(trace, net, marking, fmarking,variant=ali.VERSION_DIJKSTRA_EXPONENTIAL_HEURISTIC, parameters={ali.Parameters.SYNCHRONOUS:False}) we compute the distance at each marking. However there is a question: Should we keep the entire trace or cut it? Notice that the heuristic on marking reachability is ON ''' trace = [log_trace[i]["concept:name"] for i in range(len(log_trace))] start_time = time.time() decorate_transitions_prepostset(net) decorate_places_preset_trans(net) closed = {} # a node in this version contains the marking state.m[Ø] and the position in the trace state.m[1] ini_state = utils.DijkstraSearchTuple(0, (ini, 0), None, None, 0) open_set = [ini_state] heapq.heapify(open_set) visited = 0 queued = 0 traversed = 0 def cost_function(t, l, expo): if t == utils.SKIP: return expo**(-l) else: return 0 trans_empty_preset = set(t for t in 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.keys() if already_closed: # if marking has already been visited, we don't visit it again # it's a heuristic continue if current_marking[0] == fin and current_marking[1] == len(trace): return utils.__reconstruct_alignment( curr, visited, queued, traversed, ret_tuple_as_trans_desc=ret_tuple_as_trans_desc) closed[current_marking] = curr.l visited += 1 # ----- either we try to move in model possible_enabling_transitions = copy(trans_empty_preset) for p in current_marking[0]: 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[0] ] trans_to_visit_with_cost = [(t, cost_function(utils.SKIP, curr.l, expo)) for t in enabled_trans if t is not None] for t, cost in trans_to_visit_with_cost: traversed += 1 new_marking = (utils.add_markings(current_marking[0], t.add_marking), current_marking[1]) if new_marking in closed.keys(): # if marking has already been visited, we don't visit it again # it's a heuristic continue queued += 1 tp = utils.DijkstraSearchTuple(curr.g + cost, new_marking, curr, (">>", t), curr.l + 1) heapq.heappush(open_set, tp) # ------ either we try to move in log if current_marking[1] < len(trace): traversed += 1 new_marking = (current_marking[0], current_marking[1] + 1) if new_marking in closed.keys(): # if marking has already been visited, we don't visit it again # it's a heuristic continue queued += 1 tp = utils.DijkstraSearchTuple( curr.g + cost_function(utils.SKIP, curr.l, expo), new_marking, curr, (trace[current_marking[1]], ">>"), curr.l + 1) heapq.heappush(open_set, tp) # ------ either we try to move in both (synchronous moves) trans_to_visit_with_cost = [ (t, cost_function(t, curr.l, expo)) for t in enabled_trans if t is not None and str(t) == trace[current_marking[1]] ] for t, cost in trans_to_visit_with_cost: traversed += 1 new_marking = (utils.add_markings(current_marking[0], t.add_marking), current_marking[1] + 1) if new_marking in closed.keys(): # if marking has already been visited, we don't visit it again # it's a heuristic continue queued += 1 tp = utils.DijkstraSearchTuple(curr.g + cost, new_marking, curr, (trace[current_marking[1]], t), curr.l + 1) heapq.heappush(open_set, tp)
def __search_with_synchr(sync_net, ini, fin, skip, ret_tuple_as_trans_desc=False, max_align_time_trace=sys.maxsize, expo=2): ''' In this function that can be called with the following way: alignment.algorithm.apply(trace, net, marking, fmarking,variant=ali.VERSION_DIJKSTRA_EXPONENTIAL_HEURISTIC, parameters={ali.Parameters.SYNCHRONOUS:True}) Cost of transition depends on the run of the synchronous product. Other parameters: ali.Parameters.EXPONENT:2 (change the base of the log) ''' start_time = time.time() decorate_transitions_prepostset(sync_net) decorate_places_preset_trans(sync_net) closed = {} ini_state = utils.DijkstraSearchTuple(0, ini, None, None, 0) open_set = [ini_state] heapq.heapify(open_set) visited = 0 queued = 0 traversed = 0 def cost_function(t, l, expo): if t.label is None: return expo**(-l) if t.label[1] == utils.SKIP or t.label[0] == utils.SKIP: return expo**(-l) else: return 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.keys() 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[current_marking] = curr.l 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, curr.l, expo)) for t in enabled_trans if t is not None] for t, cost in trans_to_visit_with_cost: traversed += 1 new_marking = utils.add_markings(current_marking, t.add_marking) already_closed = new_marking in closed.keys() if already_closed: continue queued += 1 tp = utils.DijkstraSearchTuple(curr.g + cost, new_marking, curr, t, curr.l + 1) heapq.heappush(open_set, tp)