예제 #1
0
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)
예제 #2
0
def __align(model_struct,
            trace_struct,
            product_net,
            corresp,
            sync_cost=align_utils.STD_SYNC_COST,
            max_align_time_trace=sys.maxsize,
            ret_tuple_as_trans_desc=False):
    """
    Alignments using Dijkstra

    Parameters
    ---------------
    model_struct
        Efficient model structure
    trace_struct
        Efficient trace structure
    product_net
        Synchronous product net
    sync_cost
        Cost of a sync move (limitation: all sync moves shall have the same cost in this setting)
    corresp
        Correspondence between indexed places and places of the sync product net
    max_align_time_trace
        Maximum alignment time for a trace (in seconds)
    ret_tuple_as_trans_desc
        Says if the alignments shall be constructed including also
        the name of the transition, or only the label (default=False includes only the label)

    Returns
    --------------
    alignment
        Alignment of the trace, including:
            alignment: the sequence of moves
            queued: the number of states that have been queued
            visited: the number of states that have been visited
            cost: the cost of the alignment
    """
    start_time = time.time()

    trans_pre_dict = model_struct[TRANS_PRE_DICT]
    trans_post_dict = model_struct[TRANS_POST_DICT]
    trans_labels_dict = model_struct[TRANS_LABELS_DICT]
    transf_model_cost_function = model_struct[TRANSF_MODEL_COST_FUNCTION]
    transf_trace = trace_struct[TRANSF_TRACE]

    trace_cost_function = trace_struct[TRACE_COST_FUNCTION]

    sync_net, ini, fin, cost_function = product_net
    incidence_matrix = construct(sync_net)
    ini_vec, fin_vec, cost_vec = utils.__vectorize_initial_final_cost(
        incidence_matrix, ini, fin, cost_function)

    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)

    marking_dict = {}
    im = __encode_marking(marking_dict, model_struct[TRANSF_IM])
    fm = __encode_marking(marking_dict, model_struct[TRANSF_FM])

    h, x, trustable = __calculate_heuristics(
        None,
        None,
        im,
        0,
        corresp,
        None,
        sync_net,
        incidence_matrix,
        fin_vec,
        cost_vec,
        a_matrix,
        g_matrix,
        h_cvx,
        lp_solver.DEFAULT_LP_SOLVER_VARIANT,
        use_cvxopt=use_cvxopt)
    exact_heu_calculations = 1

    initial_state = (0, h, 0, 0, 0, 0, None, im, None, 0, x, trustable)
    open_set = [initial_state]
    heapq.heapify(open_set)

    closed = {}
    dummy_count = 0
    visited = 0

    while not len(open_set) == 0:
        if (time.time() - start_time) > max_align_time_trace:
            return None
        curr = heapq.heappop(open_set)
        curr_m0 = curr[POSITION_MARKING]

        curr_m = __decode_marking(curr_m0)
        index = curr[POSITION_INDEX]

        # if a situation equivalent to the one of the current state has been
        # visited previously, then discard this
        if __check_closed(closed, (curr_m0, curr[POSITION_INDEX])):
            continue
        visited = visited + 1

        h = curr[POSITION_HEURISTICS]
        x = curr[POSITION_X]
        trustable = curr[POSITION_TRUSTABLE]

        if not trustable:
            if (time.time() - start_time) > max_align_time_trace:
                return None

            m, t = get_corresp_marking_and_trans(curr_m, index, corresp, None)
            h, x = utils.__compute_exact_heuristic_new_version(
                sync_net,
                a_matrix,
                h_cvx,
                g_matrix,
                cost_vec,
                incidence_matrix,
                m,
                fin_vec,
                lp_solver.DEFAULT_LP_SOLVER_VARIANT,
                use_cvxopt=use_cvxopt)
            exact_heu_calculations = exact_heu_calculations + 1

            curr = list(curr)
            curr[POSITION_HEURISTICS] = h
            curr[POSITION_TOTAL_COST] = curr[POSITION_COST] + h
            curr[POSITION_X] = x
            curr[POSITION_TRUSTABLE] = True

            curr = tuple(curr)

            heapq.heappush(open_set, curr)
            continue

        __add_closed(closed, (curr_m0, curr[POSITION_INDEX]))
        if curr_m0 == fm:
            if -curr[POSITION_INDEX] == len(transf_trace):
                # returns the alignment only if the final marking has been reached AND
                # the trace is over
                return __reconstruct_alignment(
                    curr,
                    model_struct,
                    trace_struct,
                    visited,
                    len(open_set),
                    len(closed),
                    len(marking_dict),
                    exact_heu_calculations,
                    ret_tuple_as_trans_desc=ret_tuple_as_trans_desc)
        # retrieves the transitions that are enabled in the current marking
        en_t = [[
            t,
            __encode_marking(
                marking_dict,
                __fire_trans(curr_m, trans_pre_dict[t], trans_post_dict[t])),
            0, None, False
        ] for t in trans_pre_dict if __dict_leq(trans_pre_dict[t], curr_m)]

        this_closed = set()
        j = 0
        while j < len(en_t):
            t = en_t[j][0]
            # checks if a given transition can be executed in sync with the trace
            is_sync = trans_labels_dict[t] == transf_trace[
                -curr[POSITION_INDEX]] if -curr[POSITION_INDEX] < len(
                    transf_trace) else False
            if is_sync:
                new_m = en_t[j][1]
                new_h, new_x, new_trustable = __calculate_heuristics(
                    h,
                    x,
                    new_m,
                    curr[POSITION_INDEX] - 1,
                    corresp, (curr[POSITION_INDEX], t),
                    sync_net,
                    incidence_matrix,
                    fin_vec,
                    cost_vec,
                    a_matrix,
                    g_matrix,
                    h_cvx,
                    lp_solver.DEFAULT_LP_SOLVER_VARIANT,
                    use_cvxopt=use_cvxopt)

                dummy_count = dummy_count + 1
                new_f = curr[POSITION_COST] + sync_cost

                new_g = new_f + new_h
                new_state = (new_g, new_h, curr[POSITION_INDEX] - 1,
                             IS_SYNC_MOVE, curr[POSITION_ALIGN_LENGTH] + 1,
                             dummy_count, curr, new_m, t, new_f, new_x,
                             new_trustable)
                if not __check_closed(
                        closed,
                    (new_state[POSITION_MARKING], new_state[POSITION_INDEX])):
                    # if it can be executed in a sync way, add a new state corresponding
                    # to the sync execution only if it has not already been closed
                    open_set = __add_to_open_set(open_set, new_state)
                # if a sync move reached new_m, do not schedule any model move that reaches new_m
                this_closed.add(new_m)
                del en_t[j]

                continue
            j = j + 1

        # calculate the heuristics for the remaining states
        j = 0
        while j < len(en_t):
            t = en_t[j][0]
            new_m = en_t[j][1]
            en_t[j][2], en_t[j][3], en_t[j][4] = __calculate_heuristics(
                h,
                x,
                new_m,
                curr[POSITION_INDEX],
                corresp, (">>", t),
                sync_net,
                incidence_matrix,
                fin_vec,
                cost_vec,
                a_matrix,
                g_matrix,
                h_cvx,
                lp_solver.DEFAULT_LP_SOLVER_VARIANT,
                use_cvxopt=use_cvxopt)
            j = j + 1

        en_t.sort(key=lambda t: transf_model_cost_function[t[0]] + t[2])
        j = 0
        while j < len(en_t):
            t = en_t[j][0]
            new_m = en_t[j][1]
            new_h = en_t[j][2]
            new_x = en_t[j][3]
            new_trustable = en_t[j][4]

            dummy_count = dummy_count + 1
            new_f = curr[POSITION_COST] + transf_model_cost_function[t]

            new_g = new_f + new_h
            new_state = (new_g, new_h, curr[POSITION_INDEX], IS_MODEL_MOVE,
                         curr[POSITION_ALIGN_LENGTH] + 1, dummy_count, curr,
                         new_m, t, new_f, new_x, new_trustable)
            if new_m not in this_closed and not curr_m0 == new_m:
                if not __check_closed(
                        closed,
                    (new_state[POSITION_MARKING], new_state[POSITION_INDEX])):
                    open_set = __add_to_open_set(open_set, new_state)
                this_closed.add(new_m)
            j = j + 1

        # IMPORTANT: to reduce the complexity, assume that you can schedule a log move
        # only if the previous move has not been a move-on-model.
        # since this setting is equivalent to scheduling all the log moves before and then
        # the model moves
        if -curr[POSITION_INDEX] < len(
                transf_trace) and curr[POSITION_TYPE_MOVE] != IS_MODEL_MOVE:
            dummy_count = dummy_count + 1
            new_f = curr[POSITION_COST] + trace_cost_function[
                -curr[POSITION_INDEX]]
            new_h, new_x, new_trustable = __calculate_heuristics(
                h,
                x,
                curr_m0,
                curr[POSITION_INDEX] - 1,
                corresp, (curr[POSITION_INDEX], ">>"),
                sync_net,
                incidence_matrix,
                fin_vec,
                cost_vec,
                a_matrix,
                g_matrix,
                h_cvx,
                lp_solver.DEFAULT_LP_SOLVER_VARIANT,
                use_cvxopt=use_cvxopt)

            new_g = new_f + new_h
            new_state = (new_g, new_h, curr[POSITION_INDEX] - 1, IS_LOG_MOVE,
                         curr[POSITION_ALIGN_LENGTH] + 1, dummy_count, curr,
                         curr_m0, None, new_f, new_x, new_trustable)
            if not __check_closed(
                    closed,
                (new_state[POSITION_MARKING], new_state[POSITION_INDEX])):
                # adds the log move only if it has not been already closed before
                open_set = __add_to_open_set(open_set, new_state)
예제 #3
0
def __search(net, ini, fin):
    from pm4py.objects.petri.utils import decorate_places_preset_trans, decorate_transitions_prepostset

    cost_function = {t: 1 for t in net.transitions}

    decorate_transitions_prepostset(net)
    decorate_places_preset_trans(net)

    incidence_matrix = petri.incidence_matrix.construct(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(net.transitions))
    h_cvx = np.matrix(np.zeros(len(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(
        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)
    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

    trans_empty_preset = set(t for t in net.transitions if len(t.in_arcs) == 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(
                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)

            # 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_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
        if curr.h < 0.01:
            if current_marking == fin:
                return utils.__reconstruct_alignment(curr, visited, queued,
                                                     traversed)

        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]

        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)
예제 #4
0
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

        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()
    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)