Exemplo n.º 1
0
def add_new_node(tree, level):
    """
    Randomly select an inner node, and add a new child, and ensure that it satisfies the tree rules.

    Parameters
    -----------
    tree
        Original Process Tree
    level
        The maximal depth of the chosen node
    """
    node = randomly_choose_node(tree, level)
    tmp_node = copy.deepcopy(node)
    add_node = ProcessTree(
        None, node, None,
        pt_gene_utils.get_cur_label(
            pt_mani_utils.non_none_leaves_number(tree) + 1))
    if node.operator == Operator.LOOP:
        child = node.children[1]
        new_child = ProcessTree(Operator.XOR, node, [child, add_node], None)
        child.parent = new_child
        add_node.parent = new_child
        node.children[1] = new_child
    else:
        node.children.append(add_node)
    return tmp_node, node
Exemplo n.º 2
0
 def __init__(self, tree):
     i = 0
     while i < len(tree.children):
         tree.children[i] = GenerationTree(tree.children[i])
         tree.children[i].parent = self
         i = i + 1
     ProcessTree.__init__(self, operator=tree.operator, parent=tree.parent, children=tree.children, label=tree.label)
Exemplo n.º 3
0
def create_pt_of_three_node(root, op):
    child1, child2 = ProcessTree(), ProcessTree()
    root.children = [child1, child2]
    root.operator = op
    child1.parent = root
    child2.parent = root
    if op == Operator.LOOP:
        root.children.append(ProcessTree(None, root, None, None))
    return [child1, child2]
Exemplo n.º 4
0
def create_new_binary_process_tree(no_number):
    """
    Random create a new tree with fixed node number

    Parameters
    -----------
    no_number
        Node number of the created process tree

    Returns
    ------------
    tree
        Process Tree (None, if there no normal process tree with such node)
    """
    root = ProcessTree()
    if no_number == 1:
        return ProcessTree(None, None, None, 'a')
    if no_number == 2:
        return None
    operators, enable, cur_num = [_ for _ in Operator], [root], 1
    while cur_num + 3 <= no_number:
        node = random.choice(enable)
        enable.remove(node)
        children = create_pt_of_three_node(node, random.choice(operators))
        enable += children
        if node.parent is not None and node.operator == node.parent.operator and node.operator != Operator.LOOP:
            cur_num += 1
        elif node.operator == Operator.LOOP:
            cur_num += 3
        else:
            cur_num += 2
    if no_number - cur_num == 1:  # must be agree with parent and not equal LOOP
        flag = False
        for i in range(len(enable)):
            if enable[i].parent.operator != Operator.LOOP:
                node = enable.pop(i)
                children = create_pt_of_three_node(node, node.parent.operator)
                enable += children
                flag = True
                break
        while not flag:
            root = create_new_binary_process_tree(no_number)
            flag = False if root is None else True
            print('could not create a tree, try again!!! Maybe infinite!!!')
    elif no_number - cur_num == 2:  # must differ with parent and not equal LOOP
        node = random.choice(enable)
        enable.remove(node)
        op = random.choice(operators)
        while op == Operator.LOOP or (node.parent is not None and op == node.parent.operator)\
                or node.parent is None:
            op = random.choice(operators)
        children = create_pt_of_three_node(node, op)
        enable += children

    add_label_to_leaf(enable)
    return root
def process_tree_to_binary_process_tree(pt: ProcessTree) -> ProcessTree:
    if len(pt.children) > 2:
        new_subtree = ProcessTree()
        new_subtree.operator = pt.operator
        new_subtree.children = pt.children[1:]
        pt.children = pt.children[:1]
        pt.children.append(new_subtree)
    for c in pt.children:
        process_tree_to_binary_process_tree(c)
    return pt
Exemplo n.º 6
0
def execute_enabled(enabled, open, closed, execution_sequence=None):
    """
    Execute an enabled node of the process tree

    Parameters
    -----------
    enabled
        Enabled nodes
    open
        Open nodes
    closed
        Closed nodes
    execution_sequence
        Execution sequence

    Returns
    -----------
    execution_sequence
        Execution sequence
    """
    execution_sequence = list(
    ) if execution_sequence is None else execution_sequence
    vertex = random.sample(list(enabled), 1)[0]
    enabled.remove(vertex)
    open.add(vertex)
    execution_sequence.append((vertex, pt_st.State.OPEN))
    if len(vertex.children) > 0:
        if vertex.operator is pt_opt.Operator.LOOP:
            while len(vertex.children) < 3:
                vertex.children.append(ProcessTree(parent=vertex))
        if vertex.operator is pt_opt.Operator.SEQUENCE or vertex.operator is pt_opt.Operator.LOOP:
            c = vertex.children[0]
            enabled.add(c)
            execution_sequence.append((c, pt_st.State.ENABLED))
        elif vertex.operator is pt_opt.Operator.PARALLEL:
            enabled |= set(vertex.children)
            for x in vertex.children:
                if x in closed:
                    closed.remove(x)
            map(lambda c: execution_sequence.append((c, pt_st.State.ENABLED)),
                vertex.children)
        elif vertex.operator is pt_opt.Operator.XOR:
            vc = vertex.children
            c = vc[random.randint(0, len(vc) - 1)]
            enabled.add(c)
            execution_sequence.append((c, pt_st.State.ENABLED))
        elif vertex.operator is pt_opt.Operator.OR:
            some_children = [
                c for c in vertex.children if random.random() < 0.5
            ]
            enabled |= set(some_children)
            for x in some_children:
                if x in closed:
                    closed.remove(x)
            map(lambda c: execution_sequence.append((c, pt_st.State.ENABLED)),
                some_children)
    else:
        close(vertex, enabled, open, closed, execution_sequence)
    return execution_sequence
Exemplo n.º 7
0
def calculate_optimal_alignment(pt: ProcessTree,
                                trace: Trace,
                                parameters=None):
    if parameters is None:
        parameters = {}
    align_variant = exec_utils.get_param_value(
        Parameters.CLASSIC_ALIGNMENTS_VARIANT, parameters,
        Variants.VERSION_STATE_EQUATION_A_STAR)
    conversion_version = exec_utils.get_param_value(
        Parameters.CONVERSION_VERSION, parameters,
        pt_converter.Variants.TO_PETRI_NET_TRANSITION_BORDERED)

    parent = pt.parent
    pt.parent = None
    net, im, fm = pt_converter.apply(pt, variant=conversion_version)

    # in this way, also the other parameters are passed to alignments
    alignment_parameters = copy(parameters)
    alignment_parameters[
        AlignParameters.PARAM_ALIGNMENT_RESULT_IS_SYNC_PROD_AWARE] = True

    alignment = get_alignment(trace,
                              net,
                              im,
                              fm,
                              variant=align_variant,
                              parameters=alignment_parameters)

    pt.parent = parent
    res = []

    # if the alignment has terminated prematurely due to time constraints, raise an Exception
    if alignment is None:
        raise AlignmentNoneException("alignment terminated prematurely")

    if conversion_version == pt_converter.Variants.TO_PETRI_NET_TRANSITION_BORDERED or conversion_version == pt_converter.Variants.TO_PETRI_NET_TRANSITION_BORDERED.value:
        # remove invisible model moves from alignment steps that do not belong to a silent model move in the process tree
        # this is possible only if the TO_PETRI_NET_TRANSITION_BORDERED variant is used
        for a in alignment["alignment"]:
            if not (a[0][0] == SKIP and not a[0][1].isdigit()):
                res.append(a[1])
    else:
        for a in alignment["alignment"]:
            res.append(a[1])

    return res
Exemplo n.º 8
0
def reduce(bottomup_nodes: List[ProcessTree], fps: Dict[str, Any],
           activities: Set[str]) -> ProcessTree:
    """
    Reduce a process tree replacing the skippable elements that have empty intersection with the
    trace.

    Parameters
    -----------------
    bottomup_nodes
        List of nodes of the process tree (that are process trees by themselves) in a bottomup order
    fps
        Footprints of the process tree
    activities
        Set of activities in the trace

    Returns
    ------------------
    tree
        Reduced process tree
    """
    from pm4py.algo.discovery.footprints.outputs import Outputs

    i = 0
    while i < len(bottomup_nodes) - 1:
        node = bottomup_nodes[i]
        parent = node.parent

        is_skippable = fps[id(node)][Outputs.SKIPPABLE.value]
        node_activities = fps[id(node)][Outputs.ACTIVITIES.value]

        if is_skippable and not node_activities.intersection(activities):
            pt = ProcessTree()
            pt.parent = parent
            parent.children[parent.children.index(node)] = pt
        i = i + 1

    return fold(bottomup_nodes[-1])
Exemplo n.º 9
0
def remove_node(tree, level):
    """
    Randomly select one node, and remove one of the child

    Parameters
    -----------
    tree
        Original Process Tree
    level
        The maximal depth of the chosen node
    """

    node = randomly_choose_node(tree, level)
    tmp_node = copy.deepcopy(node)
    if node.operator == Operator.LOOP:
        node.children[random.randint(0,
                                     1)] = ProcessTree(None, node, None, None)
    else:
        node.children.remove(random.choice(node.children))
    pt_normalize.apply(tree)
    return tmp_node, node
Exemplo n.º 10
0
def change_node_operator(tree, level):
    """
    Randomly select an inner node, and replace the operator using others,
    and ensure that it satisfies the tree rules.

    Parameters
    -----------
    tree
        Original Process Tree
    level
        The maximal depth of the chosen node
    """
    node = randomly_choose_node(tree, level)
    tmp_node = copy.deepcopy(node)
    operators = [_ for _ in Operator]
    op = random.choice(operators)
    while op == node.operator:
        op = random.choice(operators)
    if op == Operator.LOOP:
        while len(node.children) > 2:
            node.children.pop()
        node.children.append(ProcessTree(None, node, None, None))
    node.operator = op
    return tmp_node, node
def get_repr(spec_tree_struct, rec_depth, contains_empty_traces=False):
    """
    Get the representation of a process tree

    Parameters
    -----------
    spec_tree_struct
        Internal tree structure (after application of Inductive Miner)
    rec_depth
        Current recursion depth
    contains_empty_traces
        Boolean value that is True if the event log from which the DFG has been extracted contains empty traces

    Returns
    -----------
    final_tree_repr
        Representation of the tree (could be printed, transformed, viewed)
    """

    need_loop_on_subtree = check_loop_need(spec_tree_struct)

    if contains_empty_traces and rec_depth == 0:
        rec_depth = rec_depth + 1

    child_tree = None
    if spec_tree_struct.detected_cut == "flower" or (
            spec_tree_struct.detected_cut == "base_concurrent" and need_loop_on_subtree):
        final_tree_repr = ProcessTree(operator=Operator.LOOP)
        child_tree = ProcessTree(operator=Operator.XOR)
        child_tree_redo = ProcessTree(label=None)
        child_tree_exit = ProcessTree(label=None)
        final_tree_repr.children.append(child_tree)
        final_tree_repr.children.append(child_tree_redo)
        final_tree_repr.children.append(child_tree_exit)
        child_tree.parent = final_tree_repr
        child_tree_redo.parent = final_tree_repr
        child_tree_exit.parent = final_tree_repr
    elif spec_tree_struct.detected_cut == "base_concurrent":
        if len(spec_tree_struct.activities) > 1 or spec_tree_struct.must_insert_skip:
            final_tree_repr = ProcessTree(operator=Operator.XOR)
            child_tree = final_tree_repr
        else:
            final_tree_repr = ProcessTree(operator=None, label=None)
    elif spec_tree_struct.detected_cut == "sequential":
        final_tree_repr = ProcessTree(operator=Operator.SEQUENCE)
        child_tree = final_tree_repr
    elif spec_tree_struct.detected_cut == "loopCut":
        final_tree_repr = ProcessTree(operator=Operator.LOOP)
        child_tree = final_tree_repr
    elif spec_tree_struct.detected_cut == "concurrent":
        final_tree_repr = ProcessTree(operator=Operator.XOR)
        child_tree = final_tree_repr
    elif spec_tree_struct.detected_cut == "parallel":
        final_tree_repr = ProcessTree(operator=Operator.PARALLEL)
        child_tree = final_tree_repr

    if spec_tree_struct.detected_cut == "base_concurrent" or spec_tree_struct.detected_cut == "flower":
        for act in spec_tree_struct.activities:
            if child_tree is None:
                new_vis_trans = get_transition(act)
                child_tree = new_vis_trans
                final_tree_repr = child_tree
            else:
                new_vis_trans = get_transition(act)
                child_tree.children.append(new_vis_trans)
                new_vis_trans.parent = child_tree
    if spec_tree_struct.detected_cut == "sequential" or spec_tree_struct.detected_cut == "loopCut":
        #if spec_tree_struct.detected_cut == "loopCut":
        #    spec_tree_struct.children[0].must_insert_skip = True
        for ch in spec_tree_struct.children:
            child = get_repr(ch, rec_depth + 1)
            child_tree.children.append(child)
            child.parent = child_tree
        if spec_tree_struct.detected_cut == "loopCut" and len(spec_tree_struct.children) < 2:
            while len(spec_tree_struct.children) < 2:
                child = ProcessTree()
                child_tree.children.append(child)
                child.parent = child_tree
                spec_tree_struct.children.append(None)

    if spec_tree_struct.detected_cut == "parallel":
        for child in spec_tree_struct.children:
            child_final = get_repr(child, rec_depth + 1)
            child_tree.children.append(child_final)
            child_final.parent = child_tree

    if spec_tree_struct.detected_cut == "concurrent":
        for child in spec_tree_struct.children:
            child_final = get_repr(child, rec_depth + 1)
            child_tree.children.append(child_final)
            child_final.parent = child_tree

    if spec_tree_struct.must_insert_skip:
        skip = get_new_hidden_trans()
        if spec_tree_struct.detected_cut == "base_concurrent":
            child_tree.children.append(skip)
            skip.parent = child_tree
        else:
            master_tree_repr = ProcessTree(operator=Operator.XOR)
            master_tree_repr.children.append(final_tree_repr)
            final_tree_repr.parent = master_tree_repr

            master_tree_repr.children.append(skip)
            skip.parent = master_tree_repr

            return master_tree_repr

    if contains_empty_traces and rec_depth == 1:
        master_tree_repr = ProcessTree(operator=Operator.XOR)
        master_tree_repr.children.append(final_tree_repr)
        final_tree_repr.parent = master_tree_repr

        skip_transition = ProcessTree()

        master_tree_repr.children.append(skip_transition)
        skip_transition.parent = master_tree_repr

        return master_tree_repr

    return final_tree_repr
def get_new_hidden_trans():
    """
    Create a hidden node (transition) in the process tree
    """
    return ProcessTree(operator=None, label=None)
Exemplo n.º 13
0
def get_repr(spec_tree_struct,
             rec_depth,
             counts,
             must_add_skip=False,
             contains_empty_traces=False):
    """
    Get the representation of a process tree

    Parameters
    -----------
    spec_tree_struct
        Internal tree structure (after application of Inductive Miner)
    rec_depth
        Current recursion depth
    counts
        Count object (keep track of the number of nodes (transitions) added to the tree
    must_add_skip
        Boolean value that indicate if we are forced to add the skip
    contains_empty_traces
        Boolean value that is True if the event log from which the DFG has been extracted contains empty traces

    Returns
    -----------
    final_tree_repr
        Representation of the tree (could be printed, transformed, viewed)
    """
    need_loop_on_subtree = check_loop_need(spec_tree_struct)

    final_tree_repr = ProcessTree()
    final_tree_repr.rec_depth = rec_depth

    if contains_empty_traces and rec_depth == 0:
        rec_depth = rec_depth + 1

    child_tree = ProcessTree()
    if spec_tree_struct.detected_cut == "flower" or (
            spec_tree_struct.detected_cut == "base_concurrent"
            and need_loop_on_subtree):
        final_tree_repr.operator = tree_constants.LOOP_OPERATOR
        child_tree = ProcessTree()
        child_tree.operator = tree_constants.EXCLUSIVE_OPERATOR
        rec_depth = rec_depth + 1
        child_tree.rec_depth = rec_depth
        final_tree_repr.add_subtree(child_tree)
    elif spec_tree_struct.detected_cut == "base_concurrent":
        final_tree_repr.operator = tree_constants.EXCLUSIVE_OPERATOR
        child_tree = final_tree_repr
    elif spec_tree_struct.detected_cut == "sequential":
        final_tree_repr.operator = tree_constants.SEQUENTIAL_OPERATOR
        child_tree = final_tree_repr
    elif spec_tree_struct.detected_cut == "loopCut":
        final_tree_repr.operator = tree_constants.LOOP_OPERATOR
        child_tree = final_tree_repr
    elif spec_tree_struct.detected_cut == "concurrent":
        final_tree_repr.operator = tree_constants.EXCLUSIVE_OPERATOR
        child_tree = final_tree_repr
    elif spec_tree_struct.detected_cut == "parallel":
        final_tree_repr.operator = tree_constants.PARALLEL_OPERATOR
        child_tree = final_tree_repr

    if spec_tree_struct.detected_cut == "base_concurrent" or spec_tree_struct.detected_cut == "flower":
        for act in spec_tree_struct.activities:
            child_tree.add_transition(get_transition(counts, act))
        if verify_skip_transition_necessity(
                must_add_skip, spec_tree_struct.initial_dfg,
                spec_tree_struct.activities) and counts.num_visible_trans > 0:
            # add skip transition
            child_tree.add_transition(
                get_new_hidden_trans(counts, type_trans="skip"))
    if spec_tree_struct.detected_cut == "sequential" or spec_tree_struct.detected_cut == "loopCut":
        for ch in spec_tree_struct.children:
            child, counts = get_repr(
                ch,
                rec_depth + 1,
                counts,
                must_add_skip=verify_skip_transition_necessity(
                    False, ch.initial_dfg, ch.activities))
            child_tree.add_subtree(child)
    if spec_tree_struct.detected_cut == "parallel":
        m_add_skip = verify_skip_for_parallel_cut(spec_tree_struct.dfg,
                                                  spec_tree_struct.children)
        for child in spec_tree_struct.children:
            m_add_skip_final = verify_skip_transition_necessity(
                m_add_skip, spec_tree_struct.dfg, spec_tree_struct.activities)
            child_final, counts = get_repr(child,
                                           rec_depth + 1,
                                           counts,
                                           must_add_skip=m_add_skip_final)
            child_tree.add_subtree(child_final)
    if spec_tree_struct.detected_cut == "concurrent":
        for child in spec_tree_struct.children:
            m_add_skip_final = verify_skip_transition_necessity(
                False, spec_tree_struct.dfg, spec_tree_struct.activities)
            child_final, counts = get_repr(child,
                                           rec_depth + 1,
                                           counts,
                                           must_add_skip=m_add_skip_final)
            child_tree.add_subtree(child_final)

    if contains_empty_traces and rec_depth == 1:
        master_tree_repr = ProcessTree()
        master_tree_repr.rec_depth = 0
        master_tree_repr.operator = tree_constants.EXCLUSIVE_OPERATOR
        master_tree_repr.add_transition(
            get_new_hidden_trans(counts, type_trans="skip"))
        master_tree_repr.add_subtree(final_tree_repr)

        return master_tree_repr, counts

    return final_tree_repr, counts
Exemplo n.º 14
0
def recursively_add_tree(parent_tree,
                         tree,
                         net,
                         initial_entity_subtree,
                         final_entity_subtree,
                         counts,
                         rec_depth,
                         force_add_skip=False):
    """
    Recursively add the subtrees to the Petri net

    Parameters
    -----------
    parent_tree
        Parent tree
    tree
        Current subtree
    net
        Petri net
    initial_entity_subtree
        Initial entity (place/transition) that should be attached from the subtree
    final_entity_subtree
        Final entity (place/transition) that should be attached from the subtree
    counts
        Counts object (keeps the number of places, transitions and hidden transitions)
    rec_depth
        Recursion depth of the current iteration
    force_add_skip
        Boolean value that tells if the addition of a skip is mandatory

    Returns
    ----------
    net
        Updated Petri net
    counts
        Updated counts object (keeps the number of places, transitions and hidden transitions)
    final_place
        Last place added in this recursion
    """
    if type(initial_entity_subtree) is PetriNet.Transition:
        initial_place = get_new_place(counts)
        net.places.add(initial_place)
        add_arc_from_to(initial_entity_subtree, initial_place, net)
    else:
        initial_place = initial_entity_subtree
    if final_entity_subtree is not None and type(
            final_entity_subtree) is PetriNet.Place:
        final_place = final_entity_subtree
    else:
        final_place = get_new_place(counts)
        net.places.add(final_place)
        if final_entity_subtree is not None and type(
                final_entity_subtree) is PetriNet.Transition:
            add_arc_from_to(final_place, final_entity_subtree, net)
    tree_childs = [child for child in tree.children]

    if tree.operator is None:
        trans = tree
        if trans.label is None:
            petri_trans = get_new_hidden_trans(counts, type_trans="skip")
        else:
            petri_trans = get_transition(counts, trans.label)
        net.transitions.add(petri_trans)
        add_arc_from_to(initial_place, petri_trans, net)
        add_arc_from_to(petri_trans, final_place, net)

    if tree.operator == Operator.XOR:
        for subtree in tree_childs:
            net, counts, intermediate_place = recursively_add_tree(
                tree,
                subtree,
                net,
                initial_place,
                final_place,
                counts,
                rec_depth + 1,
                force_add_skip=force_add_skip)
    elif tree.operator == Operator.PARALLEL:
        new_initial_trans = get_new_hidden_trans(counts, type_trans="tauSplit")
        net.transitions.add(new_initial_trans)
        add_arc_from_to(initial_place, new_initial_trans, net)
        new_final_trans = get_new_hidden_trans(counts, type_trans="tauJoin")
        net.transitions.add(new_final_trans)
        add_arc_from_to(new_final_trans, final_place, net)

        for subtree in tree_childs:
            net, counts, intermediate_place = recursively_add_tree(
                tree,
                subtree,
                net,
                new_initial_trans,
                new_final_trans,
                counts,
                rec_depth + 1,
                force_add_skip=force_add_skip)
    elif tree.operator == Operator.SEQUENCE:
        intermediate_place = initial_place
        for i in range(len(tree_childs)):
            final_connection_place = None
            if i == len(tree_childs) - 1:
                final_connection_place = final_place
            net, counts, intermediate_place = recursively_add_tree(
                tree,
                tree_childs[i],
                net,
                intermediate_place,
                final_connection_place,
                counts,
                rec_depth + 1,
                force_add_skip=force_add_skip)
    elif tree.operator == Operator.LOOP:
        # if not parent_tree.operator == Operator.SEQUENCE:
        new_initial_place = get_new_place(counts)
        net.places.add(new_initial_place)
        init_loop_trans = get_new_hidden_trans(counts, type_trans="init_loop")
        net.transitions.add(init_loop_trans)
        add_arc_from_to(initial_place, init_loop_trans, net)
        add_arc_from_to(init_loop_trans, new_initial_place, net)
        initial_place = new_initial_place
        loop_trans = get_new_hidden_trans(counts, type_trans="loop")
        net.transitions.add(loop_trans)
        if len(tree_childs) == 1:
            net, counts, intermediate_place = recursively_add_tree(
                tree,
                tree_childs[0],
                net,
                initial_place,
                final_place,
                counts,
                rec_depth + 1,
                force_add_skip=force_add_skip)
            add_arc_from_to(final_place, loop_trans, net)
            add_arc_from_to(loop_trans, initial_place, net)
        else:
            dummy = ProcessTree()
            do = tree_childs[0]
            redo = tree_childs[1]
            exit = tree_childs[2] if len(tree_childs) > 2 and (
                tree_childs[2].label is not None
                or tree_childs[2].children) else dummy

            net, counts, int1 = recursively_add_tree(
                tree,
                do,
                net,
                initial_place,
                None,
                counts,
                rec_depth + 1,
                force_add_skip=force_add_skip)
            net, counts, int2 = recursively_add_tree(
                tree,
                redo,
                net,
                int1,
                None,
                counts,
                rec_depth + 1,
                force_add_skip=force_add_skip)
            net, counts, int3 = recursively_add_tree(
                tree,
                exit,
                net,
                int1,
                final_place,
                counts,
                rec_depth + 1,
                force_add_skip=force_add_skip)

            looping_place = int2

            add_arc_from_to(looping_place, loop_trans, net)
            add_arc_from_to(loop_trans, initial_place, net)
    if force_add_skip:
        skip_trans = get_new_hidden_trans(counts, type_trans="skip")
        net.transitions.add(skip_trans)
        add_arc_from_to(initial_place, skip_trans, net)
        add_arc_from_to(skip_trans, final_place, net)

    return net, counts, final_place
def get_repr(spec_tree_struct, rec_depth, contains_empty_traces=False):
    """
    Get the representation of a process tree

    Parameters
    -----------
    spec_tree_struct
        Internal tree structure (after application of Inductive Miner)
    rec_depth
        Current recursion depth
    contains_empty_traces
        Boolean value that is True if the event log from which the DFG has been extracted contains empty traces

    Returns
    -----------
    final_tree_repr
        Representation of the tree (could be printed, transformed, viewed)
    """

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

    base_cases = ('empty_log', 'single_activity')
    cut = ('concurrent', 'sequential', 'parallel', 'loopCut')
    # note that the activity_once_per_trace is not included here, as it is can be dealt with as a parallel cut
    fall_throughs = ('empty_trace', 'strict_tau_loop', 'tau_loop', 'flower')

    # if a cut was detected in the current subtree:
    if spec_tree_struct.detected_cut in cut:
        if spec_tree_struct.detected_cut == "sequential":
            final_tree_repr = ProcessTree(operator=Operator.SEQUENCE)
        elif spec_tree_struct.detected_cut == "loopCut":
            final_tree_repr = ProcessTree(operator=Operator.LOOP)
        elif spec_tree_struct.detected_cut == "concurrent":
            final_tree_repr = ProcessTree(operator=Operator.XOR)
        elif spec_tree_struct.detected_cut == "parallel":
            final_tree_repr = ProcessTree(operator=Operator.PARALLEL)

        if not (spec_tree_struct.detected_cut == "loopCut"
                and len(spec_tree_struct.children) >= 3):
            for ch in spec_tree_struct.children:
                # get the representation of the current child (from children in the subtree-structure):
                child = get_repr(ch, rec_depth + 1)
                # add connection from child_tree to child_final and the other way around:
                final_tree_repr.children.append(child)
                child.parent = final_tree_repr

        else:
            child = get_repr(spec_tree_struct.children[0], rec_depth + 1)
            final_tree_repr.children.append(child)
            child.parent = final_tree_repr

            redo_child = ProcessTree(operator=Operator.XOR)
            for ch in spec_tree_struct.children[1:]:
                child = get_repr(ch, rec_depth + 1)
                redo_child.children.append(child)
                child.parent = redo_child

            final_tree_repr.children.append(redo_child)
            redo_child.parent = final_tree_repr

        if spec_tree_struct.detected_cut == "loopCut" and len(
                spec_tree_struct.children) < 3:
            while len(spec_tree_struct.children) < 2:
                child = ProcessTree()
                final_tree_repr.children.append(child)
                child.parent = final_tree_repr
                spec_tree_struct.children.append(None)

    if spec_tree_struct.detected_cut in base_cases:
        # in the base case of an empty log, we only return a silent transition
        if spec_tree_struct.detected_cut == "empty_log":
            return ProcessTree(operator=None, label=None)
        # in the base case of a single activity, we return a tree consisting of the single activity
        elif spec_tree_struct.detected_cut == "single_activity":
            act_a = spec_tree_struct.log[0][0][activity_key]
            return ProcessTree(operator=None, label=act_a)

    if spec_tree_struct.detected_cut in fall_throughs:
        if spec_tree_struct.detected_cut == "empty_trace":
            # should return XOR(tau, IM(L') )
            final_tree_repr = ProcessTree(operator=Operator.XOR)
            final_tree_repr.children.append(
                ProcessTree(operator=None, label=None))
            # iterate through all children of the current node
            for ch in spec_tree_struct.children:
                child = get_repr(ch, rec_depth + 1)
                final_tree_repr.children.append(child)
                child.parent = final_tree_repr

        elif spec_tree_struct.detected_cut == "strict_tau_loop" or spec_tree_struct.detected_cut == "tau_loop":
            # should return LOOP( IM(L'), tau)
            final_tree_repr = ProcessTree(operator=Operator.LOOP)
            # iterate through all children of the current node
            if spec_tree_struct.children:
                for ch in spec_tree_struct.children:
                    child = get_repr(ch, rec_depth + 1)
                    final_tree_repr.children.append(child)
                    child.parent = final_tree_repr
            else:
                for ch in spec_tree_struct.activities:
                    child = get_transition(ch)
                    final_tree_repr.append(child)
                    child.parent = final_tree_repr

            # add a silent tau transition as last child of the current node
            final_tree_repr.children.append(
                ProcessTree(operator=None, label=None))

        elif spec_tree_struct.detected_cut == "flower":
            # should return something like LOOP(XOR(a,b,c,d,...), tau)
            final_tree_repr = ProcessTree(operator=Operator.LOOP)
            xor_child = ProcessTree(operator=Operator.XOR,
                                    parent=final_tree_repr)
            # append all the activities in the current subtree to the XOR part to allow for any behaviour
            for ch in spec_tree_struct.activities:
                child = get_transition(ch)
                xor_child.children.append(child)
                child.parent = xor_child
            final_tree_repr.children.append(xor_child)
            # now add the tau to the children to get the wanted output
            final_tree_repr.children.append(
                ProcessTree(operator=None, label=None))

    return final_tree_repr
Exemplo n.º 16
0
def export_ptree_tree(tree, parameters=None):
    """
    Exports the XML tree from a process tree

    Parameters
    -----------------
    tree
        Process tree
    parameters
        Parameters of the algorithm

    Returns
    -----------------
    xml_tree
        XML tree object
    """
    tree = copy.deepcopy(tree)
    if parameters is None:
        parameters = {}

    nodes = get_list_nodes_from_tree(tree, parameters=parameters)
    nodes_dict = {(id(x), x): str(uuid.uuid4()) for x in nodes}

    # make sure that in the exporting, loops have 3 children
    # (for ProM compatibility)
    # just add a skip as third child
    for node in nodes:
        if node.operator == Operator.LOOP and len(node.children) < 3:
            third_children = ProcessTree(operator=None, label=None)
            third_children.parent = node
            node.children.append(third_children)
            nodes_dict[(id(third_children),
                        third_children)] = str(uuid.uuid4())

    # repeat twice (structure has changed)
    nodes = get_list_nodes_from_tree(tree, parameters=parameters)
    nodes_dict = {(id(x), x): str(uuid.uuid4()) for x in nodes}

    root = etree.Element("ptml")
    processtree = etree.SubElement(root, "processTree")
    processtree.set("name", str(uuid.uuid4()))
    processtree.set("root", nodes_dict[(id(tree), tree)])
    processtree.set("id", str(uuid.uuid4()))

    for node in nodes:
        nk = nodes_dict[(id(node), node)]
        child = None
        if node.operator is None:
            if node.label is None:
                child = etree.SubElement(processtree, "automaticTask")
                child.set("name", "")
            else:
                child = etree.SubElement(processtree, "manualTask")
                child.set("name", node.label)
        else:
            if node.operator is Operator.SEQUENCE:
                child = etree.SubElement(processtree, "sequence")
            elif node.operator is Operator.XOR:
                child = etree.SubElement(processtree, "xor")
            elif node.operator is Operator.PARALLEL:
                child = etree.SubElement(processtree, "and")
            elif node.operator is Operator.OR:
                child = etree.SubElement(processtree, "or")
            elif node.operator is Operator.LOOP:
                child = etree.SubElement(processtree, "xorLoop")
            child.set("name", "")
        child.set("id", nk)

    for node in nodes:
        if not node == tree:
            child = etree.SubElement(processtree, "parentsNode")
            child.set("id", str(uuid.uuid4()))
            child.set("sourceId", nodes_dict[(id(node.parent), node.parent)])
            child.set("targetId", nodes_dict[(id(node), node)])

    tree = etree.ElementTree(root)
    return tree
Exemplo n.º 17
0
def execute_enabled(enabled, open, closed, actdict, execution_sequence=None):
    """
    Execute an enabled node of the process tree

    Parameters
    -----------
    enabled
        Enabled nodes
    open
        Open nodes
    closed
        Closed nodes
    execution_sequence
        Execution sequence

    Returns
    -----------
    execution_sequence
        Execution sequence
    """
    execution_sequence = list(
    ) if execution_sequence is None else execution_sequence
    vertex = random.sample(enabled, 1)[0]
    enabled.remove(vertex)
    open.add(vertex)
    #print(vertex,'vertex')
    execution_sequence.append((vertex, pt_st.State.OPEN))
    if len(vertex.children) > 0:
        #print(vertex.children,'vertex.children')
        if vertex.operator is pt_opt.Operator.LOOP:
            while len(vertex.children) < 3:
                vertex.children.append(ProcessTree(parent=vertex))
        if vertex.operator is pt_opt.Operator.SEQUENCE or vertex.operator is pt_opt.Operator.LOOP:
            c = vertex.children[0]
            enabled.add(c)
            execution_sequence.append((c, pt_st.State.ENABLED))
        elif vertex.operator is pt_opt.Operator.PARALLEL:
            enabled |= set(vertex.children)
            #print(set(vertex.children),'set(vertex.children)')
            for x in vertex.children:
                if x in closed:
                    closed.remove(x)
            map(lambda c: execution_sequence.append((c, pt_st.State.ENABLED)),
                vertex.children)
        elif vertex.operator is pt_opt.Operator.XOR:
            #print(vertex.parent,'vertex.parent')
            #print(vertex.operator,'vertex.operator')
            #pre = execution_sequence[-1]
            vc = vertex.children
            #print(vc,'vc')

            vcl = [ele.label for ele in vc]
            #print('line164',[ele.label for ele in vc])
            #compute the number of none, and then probability.
            nonec = 0
            probdominator = 0
            allnone = 1
            allnotnone = 1
            for ele in vcl:

                if ele == None:
                    nonec += 1
                    allnotnone = 0
                else:
                    probdominator += actdict[ele]
                    allnone = 0
            if allnone == 1:
                nonec = nonec / 2
            if allnotnone == 1:
                factor = 1
            else:
                factor = 0.5
            vclprob = []
            for ele in vcl:
                if ele == None and vclprob == []:
                    #vclprob.append(1/(nonec+1))

                    vclprob.append(1 / (2 * nonec))

                    #vclprob.append(0.1)

                elif ele == None and vclprob != []:

                    vclprob.append(vclprob[-1] + 1 / (2 * nonec))

                    #vclprob.append(0.1)

                else:
                    for key in actdict:
                        if key == ele and vclprob == []:
                            vclprob.append(factor * actdict[key] /
                                           probdominator)
                            break
                        elif key == ele and vclprob != []:
                            vclprob.append(vclprob[-1] + factor *
                                           actdict[key] / probdominator)
                            break
            #print(vcl,vclprob)
            r = random.random()
            for i, ele in enumerate(vclprob):
                if r <= ele:
                    index = i
                    break
            c = vc[index]

            #c = vc[random.randint(0,len(vc)-1)]
            #print(c,'c')
            enabled.add(c)
            #print(execution_sequence[-1],execution_sequence[-1][0].label,'execution_sequence[-1]')
            execution_sequence.append((c, pt_st.State.ENABLED))
            #print(execution_sequence,'execution_sequence in XOR')
        elif vertex.operator is pt_opt.Operator.OR:

            vcl = [ele.label for ele in vertex.children]
            vclprob = []
            for ele in vcl:
                if ele == None:
                    vclprob.append(0.5)
                else:
                    for key in actdict:
                        if ele == key:
                            vclprob.append(actdict[key])
                            #vclprob.append(0.5)
            some_children = []
            for i, c in enumerate(vertex.children):
                if random.random() <= vclprob[i]:
                    some_children.append(c)

            #some_children = [c for c in vertex.children if random.random() < 0.5]
            enabled |= set(some_children)
            for x in some_children:
                if x in closed:
                    closed.remove(x)
            map(lambda c: execution_sequence.append((c, pt_st.State.ENABLED)),
                some_children)
            #print(execution_sequence,'execution_sequence in OR')
    else:
        close(vertex, enabled, open, closed, execution_sequence)
    #print(execution_sequence,'line169')
    return execution_sequence
def get_transition(label):
    """
    Create a node (transition) with the specified label in the process tree
    """
    return ProcessTree(operator=None, label=label)
Exemplo n.º 19
0
def get_repr(spec_tree_struct,
             rec_depth,
             must_add_skip=False,
             contains_empty_traces=False):
    """
    Get the representation of a process tree

    Parameters
    -----------
    spec_tree_struct
        Internal tree structure (after application of Inductive Miner)
    rec_depth
        Current recursion depth
    must_add_skip
        Boolean value that indicate if we are forced to add the skip
    contains_empty_traces
        Boolean value that is True if the event log from which the DFG has been extracted contains empty traces

    Returns
    -----------
    final_tree_repr
        Representation of the tree (could be printed, transformed, viewed)
    """
    need_loop_on_subtree = check_loop_need(spec_tree_struct)

    if contains_empty_traces and rec_depth == 0:
        rec_depth = rec_depth + 1

    # TODO
    child_tree = ProcessTree()
    if spec_tree_struct.detected_cut == "flower" or (
            spec_tree_struct.detected_cut == "base_concurrent"
            and need_loop_on_subtree):
        final_tree_repr = ProcessTree(operator=Operator.LOOP)
        child_tree = ProcessTree(operator=Operator.XOR)
        child_tree_redo = ProcessTree(label=None)
        child_tree_exit = ProcessTree(label=None)
        final_tree_repr.children.append(child_tree)
        final_tree_repr.children.append(child_tree_redo)
        final_tree_repr.children.append(child_tree_exit)
        child_tree.parent = final_tree_repr
        child_tree_redo.parent = final_tree_repr
        child_tree_exit.parent = final_tree_repr
    elif spec_tree_struct.detected_cut == "base_concurrent":
        final_tree_repr = ProcessTree(operator=Operator.XOR)
        child_tree = final_tree_repr
    elif spec_tree_struct.detected_cut == "sequential":
        final_tree_repr = ProcessTree(operator=Operator.SEQUENCE)
        child_tree = final_tree_repr
    elif spec_tree_struct.detected_cut == "loopCut":
        final_tree_repr = ProcessTree(operator=Operator.LOOP)
        child_tree = final_tree_repr
    elif spec_tree_struct.detected_cut == "concurrent":
        final_tree_repr = ProcessTree(operator=Operator.XOR)
        child_tree = final_tree_repr
    elif spec_tree_struct.detected_cut == "parallel":
        final_tree_repr = ProcessTree(operator=Operator.PARALLEL)
        child_tree = final_tree_repr

    if spec_tree_struct.detected_cut == "base_concurrent" or spec_tree_struct.detected_cut == "flower":
        for act in spec_tree_struct.activities:
            new_vis_trans = get_transition(act)
            child_tree.children.append(new_vis_trans)
            new_vis_trans.parent = child_tree
        if verify_skip_transition_necessity(must_add_skip,
                                            spec_tree_struct.initial_dfg,
                                            spec_tree_struct.dfg,
                                            spec_tree_struct.activities):
            # add skip transition
            new_hidden_trans = get_new_hidden_trans()
            child_tree.children.append(new_hidden_trans)
            new_hidden_trans.parent = child_tree
    if spec_tree_struct.detected_cut == "sequential" or spec_tree_struct.detected_cut == "loopCut":
        for ch in spec_tree_struct.children:
            child = get_repr(ch,
                             rec_depth + 1,
                             must_add_skip=(verify_skip_transition_necessity(
                                 False, ch.initial_dfg, ch.dfg, ch.activities))
                             or ch.force_loop_hidden)
            child_tree.children.append(child)
            child.parent = child_tree
        if spec_tree_struct.detected_cut == "loopCut" and len(
                spec_tree_struct.children) < 3:
            while len(spec_tree_struct.children) < 3:
                child = ProcessTree()
                child_tree.children.append(child)
                child.parent = child_tree
                spec_tree_struct.children.append(None)
    if spec_tree_struct.detected_cut == "parallel":
        m_add_skip = verify_skip_for_parallel_cut(spec_tree_struct.dfg,
                                                  spec_tree_struct.children)

        for child in spec_tree_struct.children:
            m_add_skip_final = verify_skip_transition_necessity(
                m_add_skip, spec_tree_struct.initial_dfg, spec_tree_struct.dfg,
                spec_tree_struct.activities)
            child_final = get_repr(child,
                                   rec_depth + 1,
                                   must_add_skip=m_add_skip_final)
            child_tree.children.append(child_final)
            child_final.parent = child_tree
    if spec_tree_struct.detected_cut == "concurrent":
        for child in spec_tree_struct.children:
            m_add_skip_final = verify_skip_transition_necessity(
                False, spec_tree_struct.dfg, spec_tree_struct.dfg,
                spec_tree_struct.activities)
            child_final = get_repr(child,
                                   rec_depth + 1,
                                   must_add_skip=m_add_skip_final)
            child_tree.children.append(child_final)
            child_final.parent = child_tree
    if contains_empty_traces and rec_depth == 1:
        master_tree_repr = ProcessTree(operator=Operator.XOR)
        master_tree_repr.children.append(final_tree_repr)
        final_tree_repr.parent = master_tree_repr

        skip_transition = ProcessTree()

        master_tree_repr.children.append(skip_transition)
        skip_transition.parent = master_tree_repr

        return master_tree_repr

    return final_tree_repr
Exemplo n.º 20
0
def apply(path, parameters=None):
    """
    Imports a PTML file from the specified path

    Parameters
    ---------------
    path
        Path
    parameters
        Possible parameters

    Returns
    ---------------
    tree
        Process tree
    """
    if parameters is None:
        parameters = {}

    parser = etree.XMLParser(remove_comments=True)
    xml_tree = objectify.parse(path, parser=parser)
    root = xml_tree.getroot()

    nodes = {}

    for c0 in root:
        root = c0.get("root")
        for child in c0:
            tag = child.tag
            id = child.get("id")
            name = child.get("name")
            sourceId = child.get("sourceId")
            targetId = child.get("targetId")
            if name is not None:
                # node
                if tag == "and":
                    operator = Operator.PARALLEL
                    label = None
                elif tag == "sequence":
                    operator = Operator.SEQUENCE
                    label = None
                elif tag == "xor":
                    operator = Operator.XOR
                    label = None
                elif tag == "xorLoop":
                    operator = Operator.LOOP
                    label = None
                elif tag == "or":
                    operator = Operator.OR
                    label = None
                elif tag == "manualTask":
                    operator = None
                    label = name
                elif tag == "automaticTask":
                    operator = None
                    label = None
                else:
                    raise Exception("unknown tag: " + tag)
                tree = ProcessTree(operator=operator, label=label)
                nodes[id] = tree
            else:
                nodes[sourceId].children.append(nodes[targetId])
                nodes[targetId].parent = nodes[sourceId]

    # make sure that .PTML files having loops with 3 children are imported
    # into the PM4Py process tree structure
    # we want loops to have two children
    for node in nodes.values():
        if node.operator == Operator.LOOP and len(node.children) == 3:
            if not (node.children[2].operator is None
                    and node.children[2].label is None):
                parent_node = node.parent
                new_parent_node = ProcessTree(operator=Operator.SEQUENCE,
                                              label=None)
                node.parent = new_parent_node
                new_parent_node.children.append(node)
                node.children[2].parent = new_parent_node
                new_parent_node.children.append(node.children[2])
                if parent_node is not None:
                    new_parent_node.parent = parent_node
                    del parent_node.children[parent_node.children.index(node)]
                    parent_node.children.append(new_parent_node)
            del node.children[2]

    return nodes[root]
Exemplo n.º 21
0
def apply(parameters=None):
    """
    Generate a process tree

    Parameters
    ------------
    parameters
        Paramters of the algorithm, including:
            Parameters.REC_DEPTH -> current recursion depth
            Parameters.MIN_REC_DEPTH -> minimum recursion depth
            Parameters.MAX_REC_DEPTH -> maximum recursion depth
            Parameters.PROB_LEAF -> Probability to get a leaf

    Returns
    ------------
    tree
        Process tree
    """
    if parameters is None:
        parameters = {}

    rec_depth = exec_utils.get_param_value(Parameters.REC_DEPTH, parameters, 0)
    min_rec_depth = exec_utils.get_param_value(Parameters.MIN_REC_DEPTH, parameters, 1)
    max_rec_depth = exec_utils.get_param_value(Parameters.MAX_REC_DEPTH, parameters, 3)
    prob_leaf = exec_utils.get_param_value(Parameters.PROB_LEAF, parameters, 0.25)

    next_parameters = {Parameters.REC_DEPTH: rec_depth + 1, Parameters.MIN_REC_DEPTH: min_rec_depth,
                       Parameters.MAX_REC_DEPTH: max_rec_depth,
                       Parameters.PROB_LEAF: prob_leaf}

    is_leaf = False

    if min_rec_depth <= rec_depth <= max_rec_depth:
        r = random.random()
        if r < prob_leaf:
            is_leaf = True
    elif rec_depth > max_rec_depth:
        is_leaf = True

    if is_leaf:
        current_tree = ProcessTree(label=generate_random_string(6))
    elif rec_depth == 0:
        current_tree = ProcessTree(operator=Operator.SEQUENCE)
        start = ProcessTree(label=generate_random_string(6), parent=current_tree)
        current_tree.children.append(start)
        node = apply(parameters=next_parameters)
        node.parent = current_tree
        current_tree.children.append(node)
        end = ProcessTree(label=generate_random_string(6))
        end.parent = current_tree
        current_tree.children.append(end)
    else:
        o = get_random_operator()

        current_tree = ProcessTree(operator=o)
        if o == Operator.SEQUENCE:
            n_min = 2
            n_max = 6
            selected_n = random.randrange(n_min, n_max)
            for i in range(selected_n):
                child = apply(parameters=next_parameters)
                child.parent = current_tree
                current_tree.children.append(child)
        elif o == Operator.LOOP:
            do = apply(parameters=next_parameters)
            do.parent = current_tree
            current_tree.children.append(do)
            redo = apply(parameters=next_parameters)
            redo.parent = current_tree
            current_tree.children.append(redo)
            exit = ProcessTree(parent=current_tree)
            current_tree.children.append(exit)
        elif o == Operator.XOR:
            n_min = 2
            n_max = 5
            selected_n = random.randrange(n_min, n_max)
            for i in range(selected_n):
                child = apply(parameters=next_parameters)
                child.parent = current_tree
                current_tree.children.append(child)
        elif o == Operator.PARALLEL:
            n_min = 2
            n_max = 4
            selected_n = random.randrange(n_min, n_max)
            for i in range(selected_n):
                child = apply(parameters=next_parameters)
                child.parent = current_tree
                current_tree.children.append(child)
    return current_tree
Exemplo n.º 22
0
def apply(parameters=None):
    """
    Generate a process tree

    Parameters
    ------------
    parameters
        Paramters of the algorithm, including:
            rec_depth -> current recursion depth
            min_rec_depth -> minimum recursion depth
            max_rec_depth -> maximum recursion depth
            prob_leaf -> Probability to get a leaf

    Returns
    ------------
    tree
        Process tree
    """
    if parameters is None:
        parameters = {}

    rec_depth = parameters["rec_depth"] if "rec_depth" in parameters else 0
    min_rec_depth = parameters[
        "min_rec_depth"] if "min_rec_depth" in parameters else 1
    max_rec_depth = parameters[
        "max_rec_depth"] if "max_rec_depth" in parameters else 3
    prob_leaf = parameters["prob_leaf"] if "prob_leaf" in parameters else 0.25

    next_parameters = {
        "rec_depth": rec_depth + 1,
        "min_rec_depth": min_rec_depth,
        "max_rec_depth": max_rec_depth,
        "prob_leaf": prob_leaf
    }

    is_leaf = False

    if min_rec_depth <= rec_depth <= max_rec_depth:
        r = random.random()
        if r < prob_leaf:
            is_leaf = True
    elif rec_depth > max_rec_depth:
        is_leaf = True

    if is_leaf:
        current_tree = ProcessTree(label=generate_random_string(6))
    elif rec_depth == 0:
        current_tree = ProcessTree(operator=Operator.SEQUENCE)
        start = ProcessTree(label=generate_random_string(6),
                            parent=current_tree)
        current_tree.children.append(start)
        node = apply(parameters=next_parameters)
        node.parent = current_tree
        current_tree.children.append(node)
        end = ProcessTree(label=generate_random_string(6))
        end.parent = current_tree
        current_tree.children.append(end)
    else:
        o = get_random_operator()

        current_tree = ProcessTree(operator=o)
        if o == Operator.SEQUENCE:
            n_min = 2
            n_max = 6
            selected_n = random.randrange(n_min, n_max)
            for i in range(selected_n):
                child = apply(parameters=next_parameters)
                child.parent = current_tree
                current_tree.children.append(child)
        elif o == Operator.LOOP:
            do = apply(parameters=next_parameters)
            do.parent = current_tree
            current_tree.children.append(do)
            redo = apply(parameters=next_parameters)
            redo.parent = current_tree
            current_tree.children.append(redo)
            exit = ProcessTree(parent=current_tree)
            current_tree.children.append(exit)
        elif o == Operator.XOR:
            n_min = 2
            n_max = 5
            selected_n = random.randrange(n_min, n_max)
            for i in range(selected_n):
                child = apply(parameters=next_parameters)
                child.parent = current_tree
                current_tree.children.append(child)
        elif o == Operator.PARALLEL:
            n_min = 2
            n_max = 4
            selected_n = random.randrange(n_min, n_max)
            for i in range(selected_n):
                child = apply(parameters=next_parameters)
                child.parent = current_tree
                current_tree.children.append(child)
    return current_tree