def check_stability_wfnet(net):
    """
    Check if a workflow net is stable by using the incidence matrix

    Parameters
    -------------
    net
        Petri net

    Returns
    -------------
    boolean
        Boolean value (True if the WFNet is stable; False if it is not stable)
    """
    matrix = np.asmatrix(incidence_matrix.construct(net).a_matrix)
    matrix = np.transpose(matrix)
    id_matrix = np.identity(matrix.shape[1]) * -1
    vstack_matrix = np.vstack((matrix, id_matrix))
    c = np.ones(matrix.shape[1])
    bub = np.zeros(matrix.shape[0] + matrix.shape[1])
    i = matrix.shape[0]
    while i < matrix.shape[0] + matrix.shape[1]:
        bub[i] = -0.01
        i = i + 1

    try:
        sol = lp_solver_factory.apply(c, vstack_matrix, bub, None, None, variant=DEFAULT_LP_SOLVER_VARIANT)
        if sol:
            return True
    except:
        return False

    return False
def check_soundness_wfnet(net):
    """
    Check if a workflow net is sound by using the incidence matrix

    Parameters
    -------------
    net
        Petri net

    Returns
    -------------
    boolean
        Boolean value (True if the WFNet is sound; False if it is not sound)
    """
    matrix = np.asmatrix(incidence_matrix.construct(net).a_matrix)
    matrix = np.transpose(matrix)
    id_matrix = np.identity(matrix.shape[1]) * -1
    vstack_matrix = np.vstack((matrix, id_matrix))
    c = np.ones(matrix.shape[1])
    bub = np.zeros(matrix.shape[0] + matrix.shape[1])
    i = matrix.shape[0]
    while i < matrix.shape[0] + matrix.shape[1]:
        bub[i] = -0.01
        i = i + 1
    try:
        solution = linprog(c, A_ub=vstack_matrix, b_ub=bub)
        if solution.success:
            return True
    except:
        return False
    return False
示例#3
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:
            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)
示例#4
0
def __search(net, ini, fin):
    cost_function = {t: 1 for t in net.transitions}

    decorate_transitions_prepostset(net)
    decorate_places_preset_trans(net)

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

    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.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.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)
示例#5
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