Exemplo n.º 1
0
def __approximate_alignment_on_parallel(pt: ProcessTree, trace: Trace, a_sets: Dict[ProcessTree, Set[str]],
                                        sa_sets: Dict[ProcessTree, Set[str]], ea_sets: Dict[ProcessTree, Set[str]],
                                        tau_flags: Dict[ProcessTree, bool], tl: int, th: int,
                                        parameters=None):
    if parameters is None:
        parameters = {}

    from pulp import lpSum, LpVariable, LpProblem, LpMinimize

    activity_key = exec_utils.get_param_value(Parameters.ACTIVITY_KEY, parameters, DEFAULT_NAME_KEY)

    assert pt.operator == Operator.PARALLEL
    assert len(pt.children) > 0
    assert len(trace) > 0

    ilp = LpProblem(sense=LpMinimize)

    # x_i_j = 1 <=> assigns activity i to subtree j
    x_variables: Dict[int, Dict[int, LpVariable]] = {}

    # s_i_j = 1 <=> activity i is a start activity in the current sub-trace assigned to subtree j
    s_variables: Dict[int, Dict[int, LpVariable]] = {}

    # e_i_j = 1 <=> activity i is an end activity in the current sub-trace assigned to subtree j
    e_variables: Dict[int, Dict[int, LpVariable]] = {}

    # auxiliary u_j <=> u_j=1 if an activity is assigned to subtree j
    u_variables: Dict[int, LpVariable] = {}

    # v_i_j = 1 <=> activity i is neither a start nor end-activity in the current sub-trace assigned to subtree j
    v_variables: Dict[int, Dict[int, LpVariable]] = {}

    s_costs = {}
    e_costs = {}
    u_costs = {}
    v_costs = {}

    for i, a in enumerate(trace):
        x_variables[i] = {}
        s_variables[i] = {}
        s_costs[i] = {}
        e_variables[i] = {}
        e_costs[i] = {}
        v_variables[i] = {}
        v_costs[i] = {}

        for j, subtree in enumerate(pt.children):
            x_variables[i][j] = LpVariable('x_' + str(i) + '_' + str(j), cat='Binary')

            s_variables[i][j] = LpVariable('s_' + str(i) + '_' + str(j), cat='Binary')
            s_costs[i][j] = 0 if a[activity_key] in sa_sets[subtree] else 1

            e_variables[i][j] = LpVariable('e_' + str(i) + '_' + str(j), cat='Binary')
            e_costs[i][j] = 0 if a[activity_key] in ea_sets[subtree] else 1

            v_variables[i][j] = LpVariable('v_' + str(i) + '_' + str(j), cat='Binary')
            v_costs[i][j] = 0 if a[activity_key] in a_sets[subtree] else 1

    for j in range(len(pt.children)):
        u_variables[j] = LpVariable('u_' + str(j), cat='Binary')
        # define costs to not assign anything to subtree j
        if tau_flags[pt.children[j]]:
            u_costs[j] = 0
        elif sa_sets[pt.children[j]] & ea_sets[pt.children[j]]:
            # intersection of start-activities and end-activities is not empty
            u_costs[j] = 1
        else:
            # intersection of start-activities and end-activities is empty
            u_costs[j] = 2

    # objective function
    ilp += lpSum(
        [v_variables[i][j] * v_costs[i][j] for i in range(len(trace)) for j in range(len(pt.children))] +
        [s_variables[i][j] * s_costs[i][j] for i in range(len(trace)) for j in range(len(pt.children))] +
        [e_variables[i][j] * e_costs[i][j] for i in range(len(trace)) for j in range(len(pt.children))] +
        [(1 - u_variables[j]) * u_costs[j] for j in range(len(pt.children))]), "objective_function"

    # constraints
    for i in range(len(trace)):
        # every activity is assigned to one subtree
        ilp += lpSum([x_variables[i][j] * 1 for j in range(len(pt.children))]) == 1

    for j in range(len(pt.children)):
        # first activity is a start activity
        ilp += x_variables[0][j] <= s_variables[0][j]
        # last activity is an end-activity
        ilp += x_variables[len(trace) - 1][j] <= e_variables[len(trace) - 1][j]

    # define s_i_j variables
    for i in range(len(trace)):
        for j in range(len(pt.children)):
            ilp += s_variables[i][j] <= x_variables[i][j]
            for k in range(i):
                ilp += s_variables[i][j] <= 1 - x_variables[k][j]
        # activity can be only a start-activity for one subtree
        ilp += lpSum(s_variables[i][j] for j in range(len(pt.children))) <= 1

    # define e_i_j variables
    for i in range(len(trace)):
        for j in range(len(pt.children)):
            ilp += e_variables[i][j] <= x_variables[i][j]
            for k in range(i + 1, len(trace)):
                ilp += e_variables[i][j] <= 1 - x_variables[k][j]
        # activity can be only an end-activity for one subtree
        ilp += lpSum(e_variables[i][j] for j in range(len(pt.children))) <= 1

    for j in range(len(pt.children)):
        for i in range(len(trace)):
            # define u_j variables
            ilp += u_variables[j] >= x_variables[i][j]
        # if u_j variable = 1 ==> a start activity must exist
        ilp += u_variables[j] <= lpSum(s_variables[i][j] for i in range(len(trace)))
        # if u_j variable = 1 ==> an end activity must exist
        ilp += u_variables[j] <= lpSum(e_variables[i][j] for i in range(len(trace)))

    # define v_i_j variables
    for i in range(len(trace)):
        for j in range(2):
            ilp += v_variables[i][j] >= 1 - s_variables[i][j] + 1 - e_variables[i][j] + x_variables[i][j] - 2
            ilp += v_variables[i][j] <= x_variables[i][j]
            ilp += v_variables[i][j] <= 1 - e_variables[i][j]
            ilp += v_variables[i][j] <= 1 - s_variables[i][j]

    status = ilp.solve()
    assert status == 1

    # trace_parts list contains trace parts mapped onto the determined subtree
    trace_parts: List[Tuple[ProcessTree, Trace]] = []
    last_subtree: ProcessTree = None
    for i in range(len(trace)):
        for j in range(len(pt.children)):
            subtree = pt.children[j]
            if x_variables[i][j].varValue == 1:
                if last_subtree and subtree == last_subtree:
                    trace_parts[-1][1].append(trace[i])
                else:
                    assert last_subtree is None or subtree != last_subtree
                    t = Trace()
                    t.append(trace[i])
                    trace_parts.append((subtree, t))
                    last_subtree = subtree
                continue

    # calculate an alignment for each subtree
    alignments_per_subtree: Dict[ProcessTree] = {}
    for j in range(len(pt.children)):
        subtree: ProcessTree = pt.children[j]
        sub_trace = Trace()
        for trace_part in trace_parts:
            if subtree == trace_part[0]:
                sub_trace = concatenate_traces(sub_trace, trace_part[1])
        align_result = __approximate_alignment_for_trace(subtree, a_sets, sa_sets, ea_sets,
                                                                            tau_flags, sub_trace, tl, th,
                                                                            parameters=parameters)
        if align_result is None:
            # the alignment did not terminate correctly.
            return None
        alignments_per_subtree[subtree] = align_result
    # compose alignments from subtree alignments
    res = []
    for trace_part in trace_parts:
        activities_to_cover = trace_to_list_of_str(trace_part[1])
        activities_covered_so_far = []
        alignment = alignments_per_subtree[trace_part[0]]
        while activities_to_cover != activities_covered_so_far:
            move = alignment.pop(0)
            res.append(move)
            # if the alignment move is NOT a model move add activity to activities_covered_so_far
            if move[0] != SKIP:
                activities_covered_so_far.append(move[0])
    # add possible remaining alignment moves to resulting alignment, the order does not matter (parallel operator)
    for subtree in alignments_per_subtree:
        if len(alignments_per_subtree[subtree]) > 0:
            res.extend(alignments_per_subtree[subtree])
    return res
Exemplo n.º 2
0
def __approximate_alignment_on_parallel(pt: ProcessTree, trace: Trace, a_sets: Dict[ProcessTree, Set[str]],
                                        sa_sets: Dict[ProcessTree, Set[str]], ea_sets: Dict[ProcessTree, Set[str]],
                                        tau_flags: Dict[ProcessTree, bool], tl: int, th: int,
                                        parameters=None):
    if parameters is None:
        parameters = {}
    activity_key = exec_utils.get_param_value(Parameters.ACTIVITY_KEY, parameters, DEFAULT_NAME_KEY)

    assert pt.operator == Operator.PARALLEL
    assert len(pt.children) > 0
    assert len(trace) > 0

    x__variables = {}
    s__variables = {}
    e__variables = {}
    u__variables = {}
    v__variables = {}

    s__costs = {}
    e__costs = {}
    u__costs = {}
    v__costs = {}
    all_variables = []

    for i, a in enumerate(trace):
        x__variables[i] = {}
        s__variables[i] = {}
        s__costs[i] = {}
        e__variables[i] = {}
        e__costs[i] = {}
        v__variables[i] = {}
        v__costs[i] = {}

        for j, subtree in enumerate(pt.children):
            all_variables.append('x_' + str(i) + '_' + str(j))
            x__variables[i][j] = len(all_variables) - 1
            all_variables.append('s_' + str(i) + '_' + str(j))
            s__variables[i][j] = len(all_variables) - 1
            all_variables.append('e_' + str(i) + '_' + str(j))
            e__variables[i][j] = len(all_variables) - 1
            all_variables.append('v_' + str(i) + '_' + str(j))
            v__variables[i][j] = len(all_variables) - 1
            s__costs[i][j] = 0 if a[activity_key] in sa_sets[subtree] else 1
            e__costs[i][j] = 0 if a[activity_key] in ea_sets[subtree] else 1
            v__costs[i][j] = 0 if a[activity_key] in a_sets[subtree] else 1

    for j in range(len(pt.children)):
        all_variables.append('u_' + str(j))
        u__variables[j] = len(all_variables) - 1
        # define costs to not assign anything to subtree j
        if tau_flags[pt.children[j]]:
            u__costs[j] = 0
        elif sa_sets[pt.children[j]] & ea_sets[pt.children[j]]:
            # intersection of start-activities and end-activities is not empty
            u__costs[j] = 1
        else:
            # intersection of start-activities and end-activities is empty
            u__costs[j] = 2

    c = [0] * len(all_variables)
    for i in range(len(trace)):
        for j in range(len(pt.children)):
            c[v__variables[i][j]] = v__costs[i][j]
            c[s__variables[i][j]] = s__costs[i][j]
            c[e__variables[i][j]] = e__costs[i][j]
    for j in range(len(pt.children)):
        c[u__variables[j]] = -u__costs[j]
    Aub = []
    bub = []
    Aeq = []
    beq = []

    for i in range(len(trace)):
        r = [0] * len(all_variables)
        for j in range(len(pt.children)):
            r[x__variables[i][j]] = 1
        Aeq.append(r)
        beq.append(1)

    for j in range(len(pt.children)):
        r1 = [0] * len(all_variables)
        r2 = [0] * len(all_variables)

        # first activity is a start activity
        r1[x__variables[0][j]] = 1
        r1[s__variables[0][j]] = -1
        # last activity is an end-activity
        r2[x__variables[len(trace) - 1][j]] = 1
        r2[e__variables[len(trace) - 1][j]] = -1

        Aub.append(r1)
        Aub.append(r2)
        bub.append(0)
        bub.append(0)

    # define s_i_j variables
    for i in range(len(trace)):
        for j in range(len(pt.children)):
            r = [0] * len(all_variables)
            r[s__variables[i][j]] = 1
            r[x__variables[i][j]] = -1
            Aub.append(r)
            bub.append(0)
            for k in range(i):
                r = [0] * len(all_variables)
                r[s__variables[i][j]] = 1
                r[x__variables[k][j]] = 1
                Aub.append(r)
                bub.append(1)
        r = [0] * len(all_variables)
        for j in range(len(pt.children)):
            r[s__variables[i][j]] = 1
        Aub.append(r)
        bub.append(1)

    # define e_i_j variables
    for i in range(len(trace)):
        for j in range(len(pt.children)):
            r = [0] * len(all_variables)
            r[e__variables[i][j]] = 1
            r[x__variables[i][j]] = -1
            Aub.append(r)
            bub.append(0)
            for k in range(i + 1, len(trace)):
                r = [0] * len(all_variables)
                r[e__variables[i][j]] = 1
                r[x__variables[k][j]] = 1
                Aub.append(r)
                bub.append(1)
        # activity can be only an end-activity for one subtree
        r = [0] * len(all_variables)
        for j in range(len(pt.children)):
            r[e__variables[i][j]] = 1
        Aub.append(r)
        bub.append(1)

    for j in range(len(pt.children)):
        r2 = [0] * len(all_variables)
        r3 = [0] * len(all_variables)

        r2[u__variables[j]] = 1
        r3[u__variables[j]] = 1
        for i in range(len(trace)):
            r1 = [0] * len(all_variables)

            # define u_j variables
            r1[u__variables[j]] = -1
            r1[x__variables[i][j]] = 1
            Aub.append(r1)
            bub.append(0)

            # if u_j variable = 1 ==> a start activity must exist
            r2[s__variables[i][j]] = -1
            # if u_j variable = 1 ==> an end activity must exist
            r3[e__variables[i][j]] = -1

        Aub.append(r2)
        bub.append(0)
        Aub.append(r3)
        bub.append(0)

    # define v_i_j variables
    for i in range(len(trace)):
        for j in range(2):
            r1 = [0] * len(all_variables)
            r2 = [0] * len(all_variables)
            r3 = [0] * len(all_variables)
            r4 = [0] * len(all_variables)

            r1[v__variables[i][j]] = -1
            r1[s__variables[i][j]] = -1
            r1[e__variables[i][j]] = -1
            r1[x__variables[i][j]] = 1

            r2[v__variables[i][j]] = 1
            r2[x__variables[i][j]] = -1

            r3[v__variables[i][j]] = 1
            r3[e__variables[i][j]] = 1

            r4[v__variables[i][j]] = 1
            r4[s__variables[i][j]] = 1

            Aub.append(r1)
            Aub.append(r2)
            Aub.append(r3)
            Aub.append(r4)
            bub.append(0)
            bub.append(0)
            bub.append(1)
            bub.append(1)

    for idx, v in enumerate(all_variables):
        r = [0] * len(all_variables)
        r[idx] = -1
        Aub.append(r)
        bub.append(0)
        r = [0] * len(all_variables)
        r[idx] = 1
        Aub.append(r)
        bub.append(1)

    points = __ilp_solve(c, Aub, bub, Aeq, beq)

    for i in x__variables:
        for j in x__variables[i]:
            x__variables[i][j] = True if points[x__variables[i][j]] == 1 else False

    x_variables = x__variables

    # trace_parts list contains trace parts mapped onto the determined subtree
    trace_parts: List[Tuple[ProcessTree, Trace]] = []
    last_subtree: ProcessTree = None
    for i in range(len(trace)):
        for j in range(len(pt.children)):
            subtree = pt.children[j]
            if x_variables[i][j]:
                if last_subtree and subtree == last_subtree:
                    trace_parts[-1][1].append(trace[i])
                else:
                    assert last_subtree is None or subtree != last_subtree
                    t = Trace()
                    t.append(trace[i])
                    trace_parts.append((subtree, t))
                    last_subtree = subtree
                continue

    # calculate an alignment for each subtree
    alignments_per_subtree: Dict[ProcessTree] = {}
    for j in range(len(pt.children)):
        subtree: ProcessTree = pt.children[j]
        sub_trace = Trace()
        for trace_part in trace_parts:
            if subtree == trace_part[0]:
                sub_trace = concatenate_traces(sub_trace, trace_part[1])
        align_result = __approximate_alignment_for_trace(subtree, a_sets, sa_sets, ea_sets,
                                                         tau_flags, sub_trace, tl, th,
                                                         parameters=parameters)
        if align_result is None:
            # the alignment did not terminate correctly.
            return None
        alignments_per_subtree[subtree] = align_result


    # compose alignments from subtree alignments
    res = []
    for trace_part in trace_parts:
        activities_to_cover = trace_to_list_of_str(trace_part[1])
        activities_covered_so_far = []
        alignment = alignments_per_subtree[trace_part[0]]
        while activities_to_cover != activities_covered_so_far:
            move = alignment.pop(0)
            res.append(move)
            # if the alignment move is NOT a model move add activity to activities_covered_so_far
            if move[0] != SKIP:
                activities_covered_so_far.append(move[0])
    # add possible remaining alignment moves to resulting alignment, the order does not matter (parallel operator)
    for subtree in alignments_per_subtree:
        if len(alignments_per_subtree[subtree]) > 0:
            res.extend(alignments_per_subtree[subtree])
    return res