pm4py / pm4py-core

Public repository for the PM4Py (Process Mining for Python) project.
https://pm4py.fit.fraunhofer.de
GNU General Public License v3.0
722 stars 286 forks source link

Conversion from Petri Net to Process Tree #434

Closed denzoned closed 1 year ago

denzoned commented 1 year ago

While using the conversion method, I stumbled upon the following case, that does not work for a conversion from a Petri Net to Process Tree:

import pm4py 
examplelog = pm4py.parse_event_log_string(['test'])
net = pm4py.discover_petri_net_inductive(examplelog)
pt = pm4py.convert_to_process_tree(net[0], net[1], net[2])

The error code:

AssertionError                            Traceback (most recent call last)
Cell In[49], line 4
      2 examplelog = pm4py.parse_event_log_string(['test'])
      3 net = pm4py.discover_petri_net_inductive(examplelog)
----> 4 pt = pm4py.convert_to_process_tree(net[0], net[1], net[2])
      6 from pm4py.objects.conversion.wf_net.variants.to_process_tree import group_blocks_in_net
      7 from pm4py.objects.process_tree.utils import generic as pt_util

File ~\AppData\Local\Programs\Python\Python310\lib\site-packages\pm4py\convert.py:232, in convert_to_process_tree(*args)
    229     net, im, fm = convert_to_petri_net(*args)
    231 from pm4py.objects.conversion.wf_net.variants import to_process_tree
--> 232 tree = to_process_tree.apply(net, im, fm)
    233 if tree is not None:
    234     return tree

File ~\AppData\Local\Programs\Python\Python310\lib\site-packages\pm4py\objects\conversion\wf_net\variants\to_process_tree.py:304, in apply(net, im, fm, parameters)
    302 if len(grouped_net.transitions) == 1:
    303     pt_str = list(grouped_net.transitions)[0].label
--> 304     pt = pt_util.parse(pt_str)
    305     ret = pt_util.fold(pt) if fold else pt
    306     tree_sort(ret)

File ~\AppData\Local\Programs\Python\Python310\lib\site-packages\pm4py\objects\process_tree\utils\generic.py:202, in parse(string_rep)
    200 depth_cache = dict()
    201 depth = 0
--> 202 return parse_recursive(string_rep, depth_cache, depth)

File ~\AppData\Local\Programs\Python\Python310\lib\site-packages\pm4py\objects\process_tree\utils\generic.py:263, in parse_recursive(string_rep, depth_cache, depth)
    261     string_rep = string_rep[escape_ext + 1:]
    262 else:
--> 263     assert (string_rep.startswith('tau') or string_rep.startswith('τ') or string_rep.startswith(u'\u03c4'))
    264     if string_rep.startswith('tau'):
    265         string_rep = string_rep[len('tau'):]

For reference, the code from pm4py.object.wf_net.variants.to_process_tree, which is called by pm4py.convert_to_process_tree:

def apply(net, im, fm, parameters=None):
    """
    Transforms a WF-net to a process tree

    Parameters
    -------------
    net
        Petri net
    im
        Initial marking
    fm
        Final marking

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

    debug = exec_utils.get_param_value(Parameters.DEBUG, parameters, False)
    fold = exec_utils.get_param_value(Parameters.FOLD, parameters, True)

    grouped_net = group_blocks_in_net(net, parameters=parameters)

    if len(grouped_net.transitions) == 1:
        pt_str = list(grouped_net.transitions)[0].label
        pt = pt_util.parse(pt_str)
        ret = pt_util.fold(pt) if fold else pt
        tree_sort(ret)
        return ret
    else:
        if debug:
            from pm4py.visualization.petri_net import visualizer as pn_viz
            pn_viz.view(pn_viz.apply(grouped_net, parameters={"format": "svg"}))
        raise ValueError('Parsing of WF-net Failed')

The problem:

From https://github.com/pm4py/pm4py-core/blob/release/pm4py/objects/process_tree/utils/generic.py

# ...
    if operator is not None:
        parent = None if depth == 0 else depth_cache[depth - 1]
        node = pt.ProcessTree(operator=operator, parent=parent)
        depth_cache[depth] = node
        if parent is not None:
            parent.children.append(node)
        depth += 1
        string_rep = string_rep.strip()
        assert (string_rep[0] == '(')
        parse_recursive(string_rep[1:], depth_cache, depth)
    else:
        label = None
        if string_rep.startswith('\''):
            string_rep = string_rep[1:]
            escape_ext = string_rep.find('\'')
            label = string_rep[0:escape_ext]
            string_rep = string_rep[escape_ext + 1:]
        else:
            assert (string_rep.startswith('tau') or string_rep.startswith('τ') or string_rep.startswith(u'\u03c4'))
            if string_rep.startswith('tau'):
                string_rep = string_rep[len('tau'):]
# ...

I know this is a trivial case, where we only have one transition between source and sink for the Petri Net and the Process Tree is only one normal activity, however for automation it would be nice to add a solution to this problem.

Possible solution: check if the net consists of only one transition between source and sink. Then create a ProcessTree object, with the corresponding label.

fit-alessandro-berti commented 1 year ago

Dear @denzoned

Thanks for signaling. We identified the problem and will release a fix in the next release

Cheers