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)
Ejemplo n.º 3
0
 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)
Ejemplo n.º 4
0
 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)
Ejemplo n.º 6
0
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)
Ejemplo n.º 7
0
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
Ejemplo n.º 8
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)
Ejemplo n.º 9
0
    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)