diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5957b3d55..d5160abf1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,36 @@
# PM4Py Changelog
+## PM4Py 2.2.13 (2021.09.03)
+
+### Fixed
+
+### Removed
+
+### Deprecated
+
+### Changed
+* 5723df7b
+ * xes exporter now reports on xes features and xmlns
+* 3b632548
+ * graphviz based visualizations now expose background color as a parameter
+
+### Added
+* 0592157b
+ * new dfg playout including performance specification
+* 85739ba0
+ * allow pandas df to be used as an iterable for streaming simulation
+* 2fa9993f
+ * path filter that filters the cases of an event log where there is at least one occurrence of the provided path
+ occurring in a given time range.
+* a7ee73a8
+ * added filter based on rework detection
+* c03b6188
+ * add petri net, reset/inhibitor net and data petri net semantics
+### Other
+
+
+---
+
## PM4Py 2.2.12 (2021.08.19)
### Fixed
diff --git a/docs/source/conf.py b/docs/source/conf.py
index a1026952b..54c2f796a 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -26,7 +26,7 @@
# The short X.Y version
version = '2.2'
# The full version, including alpha/beta/rc tags
-release = '2.2.12'
+release = '2.2.13'
# -- General configuration ---------------------------------------------------
diff --git a/examples/pandas_iterable.py b/examples/pandas_iterable.py
new file mode 100644
index 000000000..f176cdfc7
--- /dev/null
+++ b/examples/pandas_iterable.py
@@ -0,0 +1,18 @@
+import pandas as pd
+import pm4py
+import os
+from pm4py.streaming.conversion import from_pandas
+
+
+def execute_script():
+ df = pd.read_csv(os.path.join("..", "tests", "input_data", "receipt.csv"))
+ df = pm4py.format_dataframe(df)
+ it = from_pandas.apply(df)
+ count = 0
+ for trace in it:
+ print(count, trace)
+ count = count + 1
+
+
+if __name__ == "__main__":
+ execute_script()
diff --git a/examples/pandas_iterable_to_trace_stream.py b/examples/pandas_iterable_to_trace_stream.py
new file mode 100644
index 000000000..d5c619bb7
--- /dev/null
+++ b/examples/pandas_iterable_to_trace_stream.py
@@ -0,0 +1,22 @@
+import pandas as pd
+import pm4py
+import os
+from pm4py.streaming.conversion import from_pandas
+from pm4py.streaming.stream.live_trace_stream import LiveTraceStream
+from pm4py.streaming.util import trace_stream_printer
+
+
+def execute_script():
+ df = pd.read_csv(os.path.join("..", "tests", "input_data", "receipt.csv"))
+ df = pm4py.format_dataframe(df)
+ it = from_pandas.apply(df)
+ printer = trace_stream_printer.TraceStreamPrinter()
+ trace_stream = LiveTraceStream()
+ trace_stream.register(printer)
+ trace_stream.start()
+ it.to_trace_stream(trace_stream)
+ trace_stream.stop()
+
+
+if __name__ == "__main__":
+ execute_script()
diff --git a/examples/performance_dfg_simulation.py b/examples/performance_dfg_simulation.py
new file mode 100644
index 000000000..957a73886
--- /dev/null
+++ b/examples/performance_dfg_simulation.py
@@ -0,0 +1,17 @@
+import os
+
+import pm4py
+from pm4py.algo.simulation.playout.dfg import algorithm as dfg_simulator
+
+
+def execute_script():
+ log = pm4py.read_xes(os.path.join("..", "tests", "input_data", "receipt.xes"))
+ frequency_dfg, sa, ea = pm4py.discover_dfg(log)
+ performance_dfg, sa, ea = pm4py.discover_performance_dfg(log)
+ simulated_log = dfg_simulator.apply(frequency_dfg, sa, ea, variant=dfg_simulator.Variants.PERFORMANCE,
+ parameters={"performance_dfg": performance_dfg})
+ print(simulated_log)
+
+
+if __name__ == "__main__":
+ execute_script()
diff --git a/pm4py/algo/filtering/log/paths/paths_filter.py b/pm4py/algo/filtering/log/paths/paths_filter.py
index ea71821c8..091757bcb 100644
--- a/pm4py/algo/filtering/log/paths/paths_filter.py
+++ b/pm4py/algo/filtering/log/paths/paths_filter.py
@@ -21,8 +21,9 @@
from pm4py.objects.log.obj import EventLog, Trace
from pm4py.util import exec_utils
from pm4py.util import xes_constants as xes
-from pm4py.util.constants import PARAMETER_CONSTANT_ATTRIBUTE_KEY
+from pm4py.util.constants import PARAMETER_CONSTANT_ATTRIBUTE_KEY, PARAMETER_CONSTANT_TIMESTAMP_KEY
import deprecation
+import sys
from typing import Optional, Dict, Any, Union, Tuple, List
from pm4py.objects.log.obj import EventLog, EventStream, Trace
@@ -32,6 +33,9 @@ class Parameters(Enum):
ATTRIBUTE_KEY = PARAMETER_CONSTANT_ATTRIBUTE_KEY
DECREASING_FACTOR = "decreasingFactor"
POSITIVE = "positive"
+ TIMESTAMP_KEY = PARAMETER_CONSTANT_TIMESTAMP_KEY
+ MIN_PERFORMANCE = "min_performance"
+ MAX_PERFORMANCE = "max_performance"
def apply(log: EventLog, paths: List[Tuple[str, str]], parameters: Optional[Dict[Union[str, Parameters], Any]] = None) -> EventLog:
@@ -72,6 +76,53 @@ def apply(log: EventLog, paths: List[Tuple[str, str]], parameters: Optional[Dict
return filtered_log
+def apply_performance(log: EventLog, provided_path: Tuple[str, str], parameters: Optional[Dict[Union[str, Parameters], Any]] = None) -> EventLog:
+ """
+ Filters the cases of an event log where there is at least one occurrence of the provided path
+ occurring in the defined timedelta range.
+
+ Parameters
+ ----------------
+ log
+ Event log
+ provided_path
+ Path between two activities (expressed as tuple)
+ parameters
+ Parameters of the filter, including:
+ Parameters.ATTRIBUTE_KEY -> Attribute identifying the activity in the log
+ Parameters.TIMESTAMP_KEY -> Attribute identifying the timestamp in the log
+ Parameters.POSITIVE -> Indicate if events should be kept/removed
+ Parameters.MIN_PERFORMANCE -> Minimal allowed performance of the provided path
+ Parameters.MAX_PERFORMANCE -> Maximal allowed performance of the provided path
+
+ Returns
+ ----------------
+ filtered_log
+ Filtered event log
+ """
+ if parameters is None:
+ parameters = {}
+ attribute_key = exec_utils.get_param_value(Parameters.ATTRIBUTE_KEY, parameters, xes.DEFAULT_NAME_KEY)
+ timestamp_key = exec_utils.get_param_value(Parameters.TIMESTAMP_KEY, parameters, xes.DEFAULT_TIMESTAMP_KEY)
+ min_performance = exec_utils.get_param_value(Parameters.MIN_PERFORMANCE, parameters, 0)
+ max_performance = exec_utils.get_param_value(Parameters.MAX_PERFORMANCE, parameters, sys.maxsize)
+ positive = exec_utils.get_param_value(Parameters.POSITIVE, parameters, True)
+ filtered_log = EventLog(list(), attributes=log.attributes, extensions=log.extensions, classifiers=log.classifiers,
+ omni_present=log.omni_present, properties=log.properties)
+ for trace in log:
+ found = False
+ for i in range(len(trace) - 1):
+ path = (trace[i][attribute_key], trace[i + 1][attribute_key])
+ if path == provided_path:
+ timediff = trace[i + 1][timestamp_key].timestamp() - trace[i][timestamp_key].timestamp()
+ if min_performance <= timediff <= max_performance:
+ found = True
+ break
+ if (found and positive) or (not found and not positive):
+ filtered_log.append(trace)
+ return filtered_log
+
+
def get_paths_from_log(log, attribute_key="concept:name"):
"""
Get the paths of the log along with their count
diff --git a/pm4py/algo/filtering/log/rework/__init__.py b/pm4py/algo/filtering/log/rework/__init__.py
new file mode 100644
index 000000000..6ece5258d
--- /dev/null
+++ b/pm4py/algo/filtering/log/rework/__init__.py
@@ -0,0 +1,17 @@
+'''
+ This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+ PM4Py is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ PM4Py is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with PM4Py. If not, see .
+'''
+from pm4py.algo.filtering.log.rework import rework_filter
diff --git a/pm4py/algo/filtering/log/rework/rework_filter.py b/pm4py/algo/filtering/log/rework/rework_filter.py
new file mode 100644
index 000000000..b25781ec4
--- /dev/null
+++ b/pm4py/algo/filtering/log/rework/rework_filter.py
@@ -0,0 +1,74 @@
+'''
+ This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+ PM4Py is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ PM4Py is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with PM4Py. If not, see .
+'''
+from enum import Enum
+from pm4py.util import constants, xes_constants, exec_utils
+from pm4py.objects.log.obj import EventLog
+from collections import Counter
+from typing import Optional, Dict, Any
+
+
+class Parameters(Enum):
+ ACTIVITY_KEY = constants.PARAMETER_CONSTANT_ACTIVITY_KEY
+ MIN_OCCURRENCES = "min_occurrences"
+ POSITIVE = "positive"
+
+
+def apply(log: EventLog, activity: str, parameters: Optional[Dict[Any, Any]] = None) -> EventLog:
+ """
+ Applies the rework filter on the provided event log and activity.
+ This filter the cases of the log having at least Parameters.MIN_OCCURRENCES (default: 2) occurrences
+ of the given activity.
+
+ It is also possible (setting Parameters.POSITIVE to False) to retrieve the cases of the log not having the
+ given activity or having the activity occurred less than Parameters.MIN_OCCURRENCES times.
+
+ Parameters
+ -------------------
+ log
+ Event log
+ activity
+ Activity of which the rework shall be filtered
+ parameters
+ Parameters of the filter, including:
+ - Parameters.ACTIVITY_KEY => the attribute to use as activity
+ - Parameters.MIN_OCCURRENCES => the minimum number of occurrences for the activity
+ - Parameters.POSITIVE => if True, filters the cases of the log having at least MIN_OCCURRENCES occurrences.
+ if False, filters the cases of the log where such behavior does not occur.
+
+ Returns
+ -----------------
+ filtered_log
+ Filtered event log
+ """
+ if parameters is None:
+ parameters = {}
+
+ activity_key = exec_utils.get_param_value(Parameters.ACTIVITY_KEY, parameters, xes_constants.DEFAULT_NAME_KEY)
+ min_occurrences = exec_utils.get_param_value(Parameters.MIN_OCCURRENCES, parameters, 2)
+ positive = exec_utils.get_param_value(Parameters.POSITIVE, parameters, True)
+
+ filtered_log = EventLog(list(), attributes=log.attributes, extensions=log.extensions, classifiers=log.classifiers,
+ omni_present=log.omni_present, properties=log.properties)
+
+ for trace in log:
+ act_counter = Counter([x[activity_key] for x in trace])
+ if positive and activity in act_counter and act_counter[activity] >= min_occurrences:
+ filtered_log.append(trace)
+ elif not positive and (activity not in act_counter or act_counter[activity] < min_occurrences):
+ filtered_log.append(trace)
+
+ return filtered_log
diff --git a/pm4py/algo/filtering/pandas/paths/paths_filter.py b/pm4py/algo/filtering/pandas/paths/paths_filter.py
index 6c4060b78..b34d47dd6 100644
--- a/pm4py/algo/filtering/pandas/paths/paths_filter.py
+++ b/pm4py/algo/filtering/pandas/paths/paths_filter.py
@@ -28,6 +28,7 @@
import deprecation
from typing import Optional, Dict, Any, Union, Tuple, List
import pandas as pd
+import sys
class Parameters(Enum):
@@ -36,6 +37,8 @@ class Parameters(Enum):
TIMESTAMP_KEY = PARAMETER_CONSTANT_TIMESTAMP_KEY
DECREASING_FACTOR = "decreasingFactor"
POSITIVE = "positive"
+ MIN_PERFORMANCE = "min_performance"
+ MAX_PERFORMANCE = "max_performance"
def apply(df: pd.DataFrame, paths: List[Tuple[str, str]], parameters: Optional[Dict[Union[str, Parameters], Any]] = None) -> pd.DataFrame:
@@ -61,11 +64,11 @@ def apply(df: pd.DataFrame, paths: List[Tuple[str, str]], parameters: Optional[D
"""
if parameters is None:
parameters = {}
- paths = [path[0] + "," + path[1] for path in paths]
case_id_glue = exec_utils.get_param_value(Parameters.CASE_ID_KEY, parameters, CASE_CONCEPT_NAME)
attribute_key = exec_utils.get_param_value(Parameters.ATTRIBUTE_KEY, parameters, DEFAULT_NAME_KEY)
timestamp_key = exec_utils.get_param_value(Parameters.TIMESTAMP_KEY, parameters, DEFAULT_TIMESTAMP_KEY)
positive = exec_utils.get_param_value(Parameters.POSITIVE, parameters, True)
+ paths = [path[0] + "," + path[1] for path in paths]
df = df.sort_values([case_id_glue, timestamp_key])
filt_df = df[[case_id_glue, attribute_key]]
filt_dif_shifted = filt_df.shift(-1)
@@ -84,6 +87,62 @@ def apply(df: pd.DataFrame, paths: List[Tuple[str, str]], parameters: Optional[D
return ret
+def apply_performance(df: pd.DataFrame, provided_path: Tuple[str, str], parameters: Optional[Dict[Union[str, Parameters], Any]] = None) -> pd.DataFrame:
+ """
+ Filters the cases of a dataframe where there is at least one occurrence of the provided path
+ occurring in the defined timedelta range.
+
+ Parameters
+ ----------
+ df
+ Dataframe
+ paths
+ Paths to filter on
+ parameters
+ Possible parameters of the algorithm, including:
+ Parameters.CASE_ID_KEY -> Case ID column in the dataframe
+ Parameters.ATTRIBUTE_KEY -> Attribute we want to filter
+ Parameters.TIMESTAMP_KEY -> Attribute identifying the timestamp in the log
+ Parameters.POSITIVE -> Specifies if the filter should be applied including traces (positive=True)
+ or excluding traces (positive=False)
+ Parameters.MIN_PERFORMANCE -> Minimal allowed performance of the provided path
+ Parameters.MAX_PERFORMANCE -> Maximal allowed performance of the provided path
+
+ Returns
+ ----------
+ df
+ Filtered dataframe
+ """
+ if parameters is None:
+ parameters = {}
+ case_id_glue = exec_utils.get_param_value(Parameters.CASE_ID_KEY, parameters, CASE_CONCEPT_NAME)
+ attribute_key = exec_utils.get_param_value(Parameters.ATTRIBUTE_KEY, parameters, DEFAULT_NAME_KEY)
+ timestamp_key = exec_utils.get_param_value(Parameters.TIMESTAMP_KEY, parameters, DEFAULT_TIMESTAMP_KEY)
+ positive = exec_utils.get_param_value(Parameters.POSITIVE, parameters, True)
+ provided_path = provided_path[0] + "," + provided_path[1]
+ min_performance = exec_utils.get_param_value(Parameters.MIN_PERFORMANCE, parameters, 0)
+ max_performance = exec_utils.get_param_value(Parameters.MAX_PERFORMANCE, parameters, sys.maxsize)
+ df = df.sort_values([case_id_glue, timestamp_key])
+ filt_df = df[[case_id_glue, attribute_key, timestamp_key]]
+ filt_dif_shifted = filt_df.shift(-1)
+ filt_dif_shifted.columns = [str(col) + '_2' for col in filt_dif_shifted.columns]
+ stacked_df = pd.concat([filt_df, filt_dif_shifted], axis=1)
+ stacked_df["@@path"] = stacked_df[attribute_key] + "," + stacked_df[attribute_key + "_2"]
+ stacked_df = stacked_df[stacked_df["@@path"] == provided_path]
+ stacked_df["@@timedelta"] = (stacked_df[timestamp_key + "_2"] - stacked_df[timestamp_key]).astype('timedelta64[s]')
+ stacked_df = stacked_df[stacked_df["@@timedelta"] >= min_performance]
+ stacked_df = stacked_df[stacked_df["@@timedelta"] <= max_performance]
+ i1 = df.set_index(case_id_glue).index
+ i2 = stacked_df.set_index(case_id_glue).index
+ if positive:
+ ret = df[i1.isin(i2)]
+ else:
+ ret = df[~i1.isin(i2)]
+
+ ret.attrs = copy(df.attrs) if hasattr(df, 'attrs') else {}
+ return ret
+
+
@deprecation.deprecated("2.2.11", "3.0.0", details="Removed")
def apply_auto_filter(df, parameters=None):
del df
diff --git a/pm4py/algo/filtering/pandas/rework/__init__.py b/pm4py/algo/filtering/pandas/rework/__init__.py
new file mode 100644
index 000000000..3f0559c31
--- /dev/null
+++ b/pm4py/algo/filtering/pandas/rework/__init__.py
@@ -0,0 +1,17 @@
+'''
+ This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+ PM4Py is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ PM4Py is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with PM4Py. If not, see .
+'''
+from pm4py.algo.filtering.pandas.rework import rework_filter
diff --git a/pm4py/algo/filtering/pandas/rework/rework_filter.py b/pm4py/algo/filtering/pandas/rework/rework_filter.py
new file mode 100644
index 000000000..b136d66da
--- /dev/null
+++ b/pm4py/algo/filtering/pandas/rework/rework_filter.py
@@ -0,0 +1,82 @@
+'''
+ This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+ PM4Py is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ PM4Py is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with PM4Py. If not, see .
+'''
+from enum import Enum
+from pm4py.util import constants, xes_constants, exec_utils
+from typing import Optional, Dict, Any
+import pandas as pd
+from copy import copy
+
+
+class Parameters(Enum):
+ ACTIVITY_KEY = constants.PARAMETER_CONSTANT_ACTIVITY_KEY
+ CASE_ID_KEY = constants.PARAMETER_CONSTANT_CASEID_KEY
+ MIN_OCCURRENCES = "min_occurrences"
+ POSITIVE = "positive"
+
+
+INT_CASE_ACT_SIZE = "@@int_case_act_size"
+
+
+def apply(df0: pd.DataFrame, activity: str, parameters: Optional[Dict[Any, Any]] = None) -> pd.DataFrame:
+ """
+ Applies the rework filter on the provided dataframe and activity.
+ This filter the cases of the log having at least Parameters.MIN_OCCURRENCES (default: 2) occurrences
+ of the given activity.
+
+ It is also possible (setting Parameters.POSITIVE to False) to retrieve the cases of the log not having the
+ given activity or having the activity occurred less than Parameters.MIN_OCCURRENCES times.
+
+ Parameters
+ -------------------
+ df0
+ Dataframe
+ activity
+ Activity of which the rework shall be filtered
+ parameters
+ Parameters of the filter, including:
+ - Parameters.ACTIVITY_KEY => the attribute to use as activity
+ - Parameters.CASE_ID_KEY => the attribute to use as case ID
+ - Parameters.MIN_OCCURRENCES => the minimum number of occurrences for the activity
+ - Parameters.POSITIVE => if True, filters the cases of the log having at least MIN_OCCURRENCES occurrences.
+ if False, filters the cases of the log where such behavior does not occur.
+
+ Returns
+ -----------------
+ filtered_df
+ Filtered dataframe
+ """
+ if parameters is None:
+ parameters = {}
+
+ activity_key = exec_utils.get_param_value(Parameters.ACTIVITY_KEY, parameters, xes_constants.DEFAULT_NAME_KEY)
+ case_id_key = exec_utils.get_param_value(Parameters.CASE_ID_KEY, parameters, constants.CASE_CONCEPT_NAME)
+ min_occurrences = exec_utils.get_param_value(Parameters.MIN_OCCURRENCES, parameters, 2)
+ positive = exec_utils.get_param_value(Parameters.POSITIVE, parameters, True)
+
+ df = df0.copy()
+ df = df[{activity_key, case_id_key}]
+ df = df[df[activity_key] == activity]
+ df[INT_CASE_ACT_SIZE] = df.groupby([activity_key, case_id_key]).cumcount()
+ cases = df[df[INT_CASE_ACT_SIZE] >= (min_occurrences-1)][case_id_key].unique()
+
+ if positive:
+ ret = df0[df0[case_id_key].isin(cases)]
+ else:
+ ret = df0[~df0[case_id_key].isin(cases)]
+
+ ret.attrs = copy(df0.attrs) if hasattr(df0, 'attrs') else {}
+ return ret
diff --git a/pm4py/algo/simulation/playout/dfg/algorithm.py b/pm4py/algo/simulation/playout/dfg/algorithm.py
index 6146e813d..e04212d52 100644
--- a/pm4py/algo/simulation/playout/dfg/algorithm.py
+++ b/pm4py/algo/simulation/playout/dfg/algorithm.py
@@ -14,7 +14,7 @@
You should have received a copy of the GNU General Public License
along with PM4Py. If not, see .
'''
-from pm4py.algo.simulation.playout.dfg.variants import classic
+from pm4py.algo.simulation.playout.dfg.variants import classic, performance
from enum import Enum
from pm4py.util import exec_utils
from typing import Optional, Dict, Any, Union, Tuple
@@ -23,6 +23,7 @@
class Variants(Enum):
CLASSIC = classic
+ PERFORMANCE = performance
def apply(dfg: Dict[Tuple[str, str], int], start_activities: Dict[str, int], end_activities: Dict[str, int], variant=Variants.CLASSIC, parameters: Optional[Dict[Any, Any]] = None) -> Union[EventLog, Dict[Tuple[str, str], int]]:
@@ -40,6 +41,7 @@ def apply(dfg: Dict[Tuple[str, str], int], start_activities: Dict[str, int], end
variant
Variant of the playout to be used, possible values:
- Variants.CLASSIC
+ - Variants.PERFORMANCE
parameters
Parameters of the algorithm
diff --git a/pm4py/algo/simulation/playout/dfg/variants/__init__.py b/pm4py/algo/simulation/playout/dfg/variants/__init__.py
index 9d30f84c5..9c6a37105 100644
--- a/pm4py/algo/simulation/playout/dfg/variants/__init__.py
+++ b/pm4py/algo/simulation/playout/dfg/variants/__init__.py
@@ -14,4 +14,4 @@
You should have received a copy of the GNU General Public License
along with PM4Py. If not, see .
'''
-from pm4py.algo.simulation.playout.dfg.variants import classic
+from pm4py.algo.simulation.playout.dfg.variants import classic, performance
diff --git a/pm4py/algo/simulation/playout/dfg/variants/performance.py b/pm4py/algo/simulation/playout/dfg/variants/performance.py
new file mode 100644
index 000000000..a9d732b1c
--- /dev/null
+++ b/pm4py/algo/simulation/playout/dfg/variants/performance.py
@@ -0,0 +1,145 @@
+'''
+ This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+ PM4Py is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ PM4Py is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with PM4Py. If not, see .
+'''
+from copy import copy
+from datetime import datetime
+from enum import Enum
+from typing import Optional, Dict, Any, Tuple
+
+from numpy.random import choice, exponential
+
+from pm4py.objects.log.obj import EventLog, Trace, Event
+from pm4py.util import exec_utils, constants, xes_constants
+
+
+class Parameters(Enum):
+ NUM_TRACES = "num_traces"
+ ACTIVITY_KEY = constants.PARAMETER_CONSTANT_ACTIVITY_KEY
+ TIMESTAMP_KEY = constants.PARAMETER_CONSTANT_TIMESTAMP_KEY
+ CASE_ID_KEY = constants.PARAMETER_CONSTANT_CASEID_KEY
+ CASE_ARRIVAL_RATE = "case_arrival_rate"
+ PERFORMANCE_DFG = "performance_dfg"
+
+
+def dict_based_choice(dct: Dict[str, float]) -> str:
+ """
+ Performs a weighted choice, given a dictionary associating
+ a weight to each possible choice
+
+ Parameters
+ -----------------
+ dct
+ Dictionary associating a weight to each choice
+
+ Returns
+ -----------------
+ choice
+ Choice
+ """
+ X = []
+ Y = []
+ summ = 0
+ for x, y in dct.items():
+ X.append(x)
+ Y.append(y)
+ summ += y
+ if summ > 0:
+ for i in range(len(Y)):
+ Y[i] = Y[i] / summ
+ return list(choice(X, 1, p=Y))[0]
+
+
+def apply(frequency_dfg: Dict[Tuple[str, str], int], start_activities: Dict[str, int], end_activities: Dict[str, int],
+ parameters: Optional[Dict[Any, Any]] = None) -> EventLog:
+ """
+ Simulates a log out with the transition probabilities provided by the frequency DFG,
+ and the time deltas provided by the performance DFG
+
+ Parameters
+ ---------------
+ frequency_dfg
+ Frequency DFG
+ start_activities
+ Start activities
+ end_activities
+ End activities
+ parameters
+ Parameters of the algorithm, including:
+ - Parameters.NUM_TRACES: the number of traces of the simulated log
+ - Parameters.ACTIVITY_KEY: the activity key to be used in the simulated log
+ - Parameters.TIMESTAMP_KEY: the timestamp key to be used in the simulated log
+ - Parameters.CASE_ID_KEY: the case identifier key to be used in the simulated log
+ - Parameters.CASE_ARRIVAL_RATE: the average distance (in seconds) between the start of two cases (default: 1)
+ - Parameters.PERFORMANCE_DFG: (mandatory) the performance DFG that is used for the time deltas.
+
+ Returns
+ ---------------
+ simulated_log
+ Simulated log
+ """
+ if parameters is None:
+ parameters = {}
+
+ num_traces = exec_utils.get_param_value(Parameters.NUM_TRACES, parameters, 1000)
+ activity_key = exec_utils.get_param_value(Parameters.ACTIVITY_KEY, parameters, xes_constants.DEFAULT_NAME_KEY)
+ timestamp_key = exec_utils.get_param_value(Parameters.TIMESTAMP_KEY, parameters,
+ xes_constants.DEFAULT_TIMESTAMP_KEY)
+ case_id_key = exec_utils.get_param_value(Parameters.CASE_ID_KEY, parameters, xes_constants.DEFAULT_TRACEID_KEY)
+ case_arrival_rate = exec_utils.get_param_value(Parameters.CASE_ARRIVAL_RATE, parameters, 1)
+ performance_dfg = copy(exec_utils.get_param_value(Parameters.PERFORMANCE_DFG, parameters, None))
+ frequency_dfg = copy(frequency_dfg)
+
+ for sa in start_activities:
+ frequency_dfg[("▶", sa)] = start_activities[sa]
+ performance_dfg[("▶", sa)] = 0
+
+ for ea in end_activities:
+ frequency_dfg[(ea, "■")] = end_activities[ea]
+ performance_dfg[(ea, "■")] = 0
+
+ choices = {}
+ for el in frequency_dfg:
+ if not el[0] in choices:
+ choices[el[0]] = {}
+ choices[el[0]][el[1]] = frequency_dfg[el]
+
+ if performance_dfg is None:
+ raise Exception(
+ "performance DFG simulation requires the Parameters.PERFORMANCE_DFG ('performance_dfg') parameter specification.")
+
+ log = EventLog()
+ curr_st = 10000000
+
+ for i in range(num_traces):
+ curr_st += case_arrival_rate
+ curr_t = curr_st
+ trace = Trace(attributes={case_id_key: str(i)})
+ log.append(trace)
+ curr_act = "▶"
+ while True:
+ next_act = dict_based_choice(choices[curr_act])
+ if next_act == "■" or next_act is None:
+ break
+ perf = performance_dfg[(curr_act, next_act)]
+ if type(perf) is dict:
+ perf = perf["mean"]
+ perf = 0 if perf == 0 else exponential(perf)
+ curr_t += perf
+ curr_act = next_act
+ eve = Event({activity_key: curr_act, timestamp_key: datetime.fromtimestamp(curr_t)})
+ trace.append(eve)
+
+ return log
diff --git a/pm4py/algo/simulation/playout/petri_net/variants/basic_playout.py b/pm4py/algo/simulation/playout/petri_net/variants/basic_playout.py
index 1a6e6c228..e4da65f78 100644
--- a/pm4py/algo/simulation/playout/petri_net/variants/basic_playout.py
+++ b/pm4py/algo/simulation/playout/petri_net/variants/basic_playout.py
@@ -18,18 +18,16 @@
from copy import copy
from enum import Enum
from random import choice
+from typing import Optional, Dict, Any, Union
+from pm4py.objects import petri_net
from pm4py.objects.log import obj as log_instance
-from pm4py.objects.petri_net import semantics
-from pm4py.objects.petri_net.obj import PetriNet
+from pm4py.objects.log.obj import EventLog
+from pm4py.objects.petri_net.obj import PetriNet, Marking
from pm4py.util import constants
from pm4py.util import exec_utils
from pm4py.util import xes_constants
-from pm4py.objects.petri_net.obj import PetriNet, Marking
-from typing import Optional, Dict, Any, Union, Tuple
-from pm4py.objects.log.obj import EventLog, EventStream
-
class Parameters(Enum):
ACTIVITY_KEY = constants.PARAMETER_CONSTANT_ACTIVITY_KEY
@@ -38,12 +36,13 @@ class Parameters(Enum):
RETURN_VISITED_ELEMENTS = "return_visited_elements"
NO_TRACES = "noTraces"
MAX_TRACE_LENGTH = "maxTraceLength"
+ PETRI_SEMANTICS = "petri_semantics"
def apply_playout(net, initial_marking, no_traces=100, max_trace_length=100,
case_id_key=xes_constants.DEFAULT_TRACEID_KEY,
activity_key=xes_constants.DEFAULT_NAME_KEY, timestamp_key=xes_constants.DEFAULT_TIMESTAMP_KEY,
- final_marking=None, return_visited_elements=False):
+ final_marking=None, return_visited_elements=False, semantics=petri_net.semantics.ClassicSemantics()):
"""
Do the playout of a Petrinet generating a log
@@ -65,6 +64,8 @@ def apply_playout(net, initial_marking, no_traces=100, max_trace_length=100,
Event attribute that corresponds to the timestamp
final_marking
If provided, the final marking of the Petri net
+ semantics
+ Semantics of the Petri net to be used (default: petri_net.semantics.ClassicSemantics())
"""
# assigns to each event an increased timestamp from 1970
curr_timestamp = 10000000
@@ -117,7 +118,8 @@ def apply_playout(net, initial_marking, no_traces=100, max_trace_length=100,
return log
-def apply(net: PetriNet, initial_marking: Marking, final_marking: Marking = None, parameters: Optional[Dict[Union[str, Parameters], Any]] = None) -> EventLog:
+def apply(net: PetriNet, initial_marking: Marking, final_marking: Marking = None,
+ parameters: Optional[Dict[Union[str, Parameters], Any]] = None) -> EventLog:
"""
Do the playout of a Petrinet generating a log
@@ -133,6 +135,7 @@ def apply(net: PetriNet, initial_marking: Marking, final_marking: Marking = None
Parameters of the algorithm:
Parameters.NO_TRACES -> Number of traces of the log to generate
Parameters.MAX_TRACE_LENGTH -> Maximum trace length
+ Parameters.PETRI_SEMANTICS -> Petri net semantics to be used (default: petri_nets.semantics.ClassicSemantics())
"""
if parameters is None:
parameters = {}
@@ -143,7 +146,9 @@ def apply(net: PetriNet, initial_marking: Marking, final_marking: Marking = None
no_traces = exec_utils.get_param_value(Parameters.NO_TRACES, parameters, 1000)
max_trace_length = exec_utils.get_param_value(Parameters.MAX_TRACE_LENGTH, parameters, 1000)
return_visited_elements = exec_utils.get_param_value(Parameters.RETURN_VISITED_ELEMENTS, parameters, False)
+ semantics = exec_utils.get_param_value(Parameters.PETRI_SEMANTICS, parameters, petri_net.semantics.ClassicSemantics())
return apply_playout(net, initial_marking, max_trace_length=max_trace_length, no_traces=no_traces,
case_id_key=case_id_key, activity_key=activity_key, timestamp_key=timestamp_key,
- final_marking=final_marking, return_visited_elements=return_visited_elements)
+ final_marking=final_marking, return_visited_elements=return_visited_elements,
+ semantics=semantics)
diff --git a/pm4py/algo/simulation/playout/petri_net/variants/extensive.py b/pm4py/algo/simulation/playout/petri_net/variants/extensive.py
index b1c67c468..b47494b20 100644
--- a/pm4py/algo/simulation/playout/petri_net/variants/extensive.py
+++ b/pm4py/algo/simulation/playout/petri_net/variants/extensive.py
@@ -15,20 +15,18 @@
along with PM4Py. If not, see .
'''
import datetime
+import sys
from collections import Counter
from enum import Enum
+from typing import Optional, Dict, Any, Union
+from pm4py.objects import petri_net
from pm4py.objects.log import obj as log_instance
-from pm4py.objects.petri_net import semantics
-from pm4py.objects.petri_net.obj import PetriNet
+from pm4py.objects.log.obj import EventLog
+from pm4py.objects.petri_net.obj import PetriNet, Marking
from pm4py.util import constants
from pm4py.util import exec_utils
from pm4py.util import xes_constants
-import sys
-
-from pm4py.objects.petri_net.obj import PetriNet, Marking
-from typing import Optional, Dict, Any, Union, Tuple
-from pm4py.objects.log.obj import EventLog, EventStream
class Parameters(Enum):
@@ -38,6 +36,7 @@ class Parameters(Enum):
MAX_TRACE_LENGTH = "maxTraceLength"
RETURN_ELEMENTS = "return_elements"
MAX_MARKING_OCC = "max_marking_occ"
+ PETRI_SEMANTICS = "petri_semantics"
POSITION_MARKING = 0
@@ -45,7 +44,8 @@ class Parameters(Enum):
POSITION_ELEMENTS = 2
-def apply(net: PetriNet, initial_marking: Marking, final_marking: Marking = None, parameters: Optional[Dict[Union[str, Parameters], Any]] = None) -> EventLog:
+def apply(net: PetriNet, initial_marking: Marking, final_marking: Marking = None,
+ parameters: Optional[Dict[Union[str, Parameters], Any]] = None) -> EventLog:
"""
Do the playout of a Petrinet generating a log (extensive search; stop at the maximum
trace length specified
@@ -61,6 +61,7 @@ def apply(net: PetriNet, initial_marking: Marking, final_marking: Marking = None
parameters
Parameters of the algorithm:
Parameters.MAX_TRACE_LENGTH -> Maximum trace length
+ Parameters.PETRI_SEMANTICS -> Petri net semantics
"""
if parameters is None:
parameters = {}
@@ -72,6 +73,7 @@ def apply(net: PetriNet, initial_marking: Marking, final_marking: Marking = None
max_trace_length = exec_utils.get_param_value(Parameters.MAX_TRACE_LENGTH, parameters, 10)
return_elements = exec_utils.get_param_value(Parameters.RETURN_ELEMENTS, parameters, False)
max_marking_occ = exec_utils.get_param_value(Parameters.MAX_MARKING_OCC, parameters, sys.maxsize)
+ semantics = exec_utils.get_param_value(Parameters.PETRI_SEMANTICS, parameters, petri_net.semantics.ClassicSemantics())
# assigns to each event an increased timestamp from 1970
curr_timestamp = 10000000
@@ -107,7 +109,7 @@ def apply(net: PetriNet, initial_marking: Marking, final_marking: Marking = None
if counter_elements[m] > max_marking_occ:
continue
- new_m = semantics.weak_execute(t, m)
+ new_m = semantics.weak_execute(t, net, m)
if t.label is not None:
new_trace = trace + (t.label,)
else:
diff --git a/pm4py/algo/simulation/playout/petri_net/variants/stochastic_playout.py b/pm4py/algo/simulation/playout/petri_net/variants/stochastic_playout.py
index 7ffd49a9d..9e7f84858 100644
--- a/pm4py/algo/simulation/playout/petri_net/variants/stochastic_playout.py
+++ b/pm4py/algo/simulation/playout/petri_net/variants/stochastic_playout.py
@@ -17,21 +17,19 @@
import datetime
from copy import copy
from enum import Enum
+from typing import Optional, Dict, Any, Union
+from pm4py.algo.simulation.montecarlo.utils import replay
+from pm4py.objects import petri_net
from pm4py.objects.log import obj as log_instance
-from pm4py.objects.petri_net import semantics
+from pm4py.objects.log.obj import EventLog
+from pm4py.objects.petri_net.obj import PetriNet, Marking
from pm4py.objects.petri_net.utils import final_marking as final_marking_discovery
-from pm4py.objects.petri_net.obj import PetriNet
from pm4py.objects.stochastic_petri import utils as stochastic_utils
-from pm4py.algo.simulation.montecarlo.utils import replay
from pm4py.util import constants
from pm4py.util import exec_utils
from pm4py.util import xes_constants
-from pm4py.objects.petri_net.obj import PetriNet, Marking
-from typing import Optional, Dict, Any, Union, Tuple
-from pm4py.objects.log.obj import EventLog, EventStream
-
class Parameters(Enum):
ACTIVITY_KEY = constants.PARAMETER_CONSTANT_ACTIVITY_KEY
@@ -42,12 +40,14 @@ class Parameters(Enum):
MAX_TRACE_LENGTH = "maxTraceLength"
LOG = "log"
STOCHASTIC_MAP = "stochastic_map"
+ PETRI_SEMANTICS = "petri_semantics"
def apply_playout(net, initial_marking, no_traces=100, max_trace_length=100,
case_id_key=xes_constants.DEFAULT_TRACEID_KEY,
activity_key=xes_constants.DEFAULT_NAME_KEY, timestamp_key=xes_constants.DEFAULT_TIMESTAMP_KEY,
- final_marking=None, smap=None, log=None, return_visited_elements=False):
+ final_marking=None, smap=None, log=None, return_visited_elements=False,
+ semantics=petri_net.semantics.ClassicSemantics()):
"""
Do the playout of a Petrinet generating a log
@@ -73,6 +73,8 @@ def apply_playout(net, initial_marking, no_traces=100, max_trace_length=100,
Stochastic map
log
Log
+ semantics
+ Semantics of the Petri net to be used (default: petri_net.semantics.ClassicSemantics())
"""
if final_marking is None:
# infer the final marking from the net
@@ -137,7 +139,8 @@ def apply_playout(net, initial_marking, no_traces=100, max_trace_length=100,
return log
-def apply(net: PetriNet, initial_marking: Marking, final_marking: Marking = None, parameters: Optional[Dict[Union[str, Parameters], Any]] = None) -> EventLog:
+def apply(net: PetriNet, initial_marking: Marking, final_marking: Marking = None,
+ parameters: Optional[Dict[Union[str, Parameters], Any]] = None) -> EventLog:
"""
Do the playout of a Petrinet generating a log
@@ -153,6 +156,7 @@ def apply(net: PetriNet, initial_marking: Marking, final_marking: Marking = None
Parameters of the algorithm:
Parameters.NO_TRACES -> Number of traces of the log to generate
Parameters.MAX_TRACE_LENGTH -> Maximum trace length
+ Parameters.PETRI_SEMANTICS -> Petri net semantics to be used (default: petri_nets.semantics.ClassicSemantics())
"""
if parameters is None:
parameters = {}
@@ -165,7 +169,10 @@ def apply(net: PetriNet, initial_marking: Marking, final_marking: Marking = None
smap = exec_utils.get_param_value(Parameters.STOCHASTIC_MAP, parameters, None)
log = exec_utils.get_param_value(Parameters.LOG, parameters, None)
return_visited_elements = exec_utils.get_param_value(Parameters.RETURN_VISITED_ELEMENTS, parameters, False)
+ semantics = exec_utils.get_param_value(Parameters.PETRI_SEMANTICS, parameters, petri_net.semantics.ClassicSemantics())
return apply_playout(net, initial_marking, max_trace_length=max_trace_length, no_traces=no_traces,
case_id_key=case_id_key, activity_key=activity_key, timestamp_key=timestamp_key,
- final_marking=final_marking, smap=smap, log=log, return_visited_elements=return_visited_elements)
+ final_marking=final_marking, smap=smap, log=log,
+ return_visited_elements=return_visited_elements,
+ semantics=semantics)
diff --git a/pm4py/meta.py b/pm4py/meta.py
index cdb893198..d1e70bce7 100644
--- a/pm4py/meta.py
+++ b/pm4py/meta.py
@@ -14,7 +14,7 @@
You should have received a copy of the GNU General Public License
along with PM4Py. If not, see .
'''
-VERSION = '2.2.12'
+VERSION = '2.2.13'
__name__ = 'pm4py'
__version__ = VERSION
diff --git a/pm4py/objects/log/exporter/xes/variants/etree_xes_exp.py b/pm4py/objects/log/exporter/xes/variants/etree_xes_exp.py
index 029f9bb59..04fdf32a9 100644
--- a/pm4py/objects/log/exporter/xes/variants/etree_xes_exp.py
+++ b/pm4py/objects/log/exporter/xes/variants/etree_xes_exp.py
@@ -290,6 +290,8 @@ def export_log_tree(log, parameters=None):
log = log_converter.apply(log)
root = etree.Element(xes_util.TAG_LOG)
root.set(xes_util.TAG_VERSION, xes_util.VALUE_XES_VERSION)
+ root.set(xes_util.TAG_FEATURES, xes_util.VALUE_XES_FEATURES)
+ root.set(xes_util.TAG_XMLNS, xes_util.VALUE_XMLNS)
# add attributes at the log level
__export_attributes(log, root)
diff --git a/pm4py/objects/log/exporter/xes/variants/line_by_line.py b/pm4py/objects/log/exporter/xes/variants/line_by_line.py
index 873d5234a..3d820b906 100644
--- a/pm4py/objects/log/exporter/xes/variants/line_by_line.py
+++ b/pm4py/objects/log/exporter/xes/variants/line_by_line.py
@@ -192,7 +192,7 @@ def export_log_line_by_line(log, fp_obj, encoding, parameters=None):
fp_obj.write(("\n").encode(
encoding))
- fp_obj.write(("\n").encode(encoding))
+ fp_obj.write(("\n").encode(encoding))
for ext_name, ext_value in log.extensions.items():
fp_obj.write((get_tab_indent(1) + "\n" % (
ext_name, ext_value[xes_util.KEY_PREFIX], ext_value[xes_util.KEY_URI])).encode(encoding))
diff --git a/pm4py/objects/log/util/xes.py b/pm4py/objects/log/util/xes.py
index 913bd8672..536ea4f28 100644
--- a/pm4py/objects/log/util/xes.py
+++ b/pm4py/objects/log/util/xes.py
@@ -30,6 +30,8 @@
TAG_TRACE = 'trace'
TAG_VALUES = 'values'
TAG_VERSION = 'xes.version'
+TAG_FEATURES = 'xes.features'
+TAG_XMLNS = 'xmlns'
# XES/INTERNAL KEYS
KEY_CHILDREN = 'children'
@@ -49,4 +51,5 @@
DEFAULT_TRANSITION_KEY = "lifecycle:transition"
VALUE_XES_VERSION = '1849-2016'
-
+VALUE_XES_FEATURES = "nested-attributes"
+VALUE_XMLNS = 'http://www.xes-standard.org/'
diff --git a/pm4py/objects/petri_net/data_petri_nets/semantics.py b/pm4py/objects/petri_net/data_petri_nets/semantics.py
index ca21ee855..d1c752ed7 100644
--- a/pm4py/objects/petri_net/data_petri_nets/semantics.py
+++ b/pm4py/objects/petri_net/data_petri_nets/semantics.py
@@ -16,6 +16,81 @@
'''
import copy
from pm4py.objects.petri_net import properties as petri_properties
+from pm4py.objects.petri_net.sem_interface import Semantics
+
+
+class DataPetriNetSemantics(Semantics):
+ def is_enabled(self, t, pn, m, **kwargs):
+ """
+ Verifies whether a given transition is enabled in a given Petri net and marking
+
+ Parameters
+ ----------
+ :param t: transition to check
+ :param pn: Petri net
+ :param m: marking to check
+ :param e: associated event (optional, as keyword argument)
+
+ Returns
+ -------
+ :return: true if enabled, false otherwise
+ """
+ e = kwargs["e"] if "e" in kwargs else {}
+ return is_enabled(t, pn, m, e)
+
+ def execute(self, t, pn, m, **kwargs):
+ """
+ Executes a given transition in a given Petri net, the given data marking and the associated event
+
+ Parameters
+ ----------
+ :param t: transition to execute
+ :param pn: Petri net
+ :param m: marking to use
+ :param e: associated event (optional, as keyword argument)
+
+ Returns
+ -------
+ :return: newly reached marking if :param t: is enabled, None otherwise
+ """
+ e = kwargs["e"] if "e" in kwargs else {}
+ return execute(t, pn, m, e)
+
+ def weak_execute(self, t, pn, m, **kwargs):
+ """
+ Executes a given transition in a given Petri net, the given data marking and the associated event,
+ even if not fully enabled
+
+ Parameters
+ ----------
+ :param t: transition to execute
+ :param pn: Petri net
+ :param m: marking to use
+ :param e: associated event (optional, as keyword argument)
+
+ Returns
+ -------
+ :return: newly reached marking
+ """
+ e = kwargs["e"] if "e" in kwargs else {}
+ return weak_execute(t, m, e)
+
+ def enabled_transitions(self, pn, m, **kwargs):
+ """
+ Returns a set of enabled transitions in a Petri net, the given data marking and the associated event
+
+ Parameters
+ ----------
+ :param pn: Petri net
+ :param m: marking of the pn
+ :param e: associated event (optional, as keyword argument)
+
+ Returns
+ -------
+ :return: set of enabled transitions
+ """
+ e = kwargs["e"] if "e" in kwargs else {}
+ return enabled_transitions(pn, m, e)
def evaluate_guard(guard, read_variables, data):
@@ -46,22 +121,12 @@ def evaluate_guard(guard, read_variables, data):
return False
-def is_enabled(t, pn, m, e):
- """
- Verifies whether a given transition is enabled in a given Petri net and marking
-
- Parameters
- ----------
- :param t: transition to check
- :param pn: Petri net
- :param m: marking to check
- :param e: associated event
-
- Returns
- -------
- :return: true if enabled, false otherwise
- """
+# 29/08/2021: the following methods have been incapsulated in the DataPetriNetSemantics class.
+# the long term idea is to remove them. However, first we need to adapt the existing code to the new
+# structure. Moreover, for performance reason, it is better to leave the code here, without having
+# to instantiate a DataPetriNetSemantics object.
+def is_enabled(t, pn, m, e):
if t not in pn.transitions:
return False
else:
@@ -80,21 +145,6 @@ def is_enabled(t, pn, m, e):
def execute(t, pn, m, e):
- """
- Executes a given transition in a given Petri net, the given data marking and the associated event
-
- Parameters
- ----------
- :param t: transition to execute
- :param pn: Petri net
- :param m: marking to use
- :param e: associated event
-
- Returns
- -------
- :return: newly reached marking if :param t: is enabled, None otherwise
- """
-
if not is_enabled(t, pn, m, e):
return None
@@ -112,20 +162,21 @@ def execute(t, pn, m, e):
return m_out
-def enabled_transitions(pn, m, e):
- """
- Returns a set of enabled transitions in a Petri net, the given data marking and the associated event
+def weak_execute(t, m, e):
+ m_out = copy.copy(m)
+ for a in t.in_arcs:
+ m_out[a.source] -= a.weight
+ if m_out[a.source] <= 0:
+ del m_out[a.source]
+ for a in t.out_arcs:
+ m_out[a.target] += a.weight
- Parameters
- ----------
- :param pn: Petri net
- :param m: marking of the pn
- :param e: associated event
+ m_out.data_dict.update(e)
- Returns
- -------
- :return: set of enabled transitions
- """
+ return m_out
+
+
+def enabled_transitions(pn, m, e):
enabled = set()
for t in pn.transitions:
if is_enabled(t, pn, m, e):
diff --git a/pm4py/objects/petri_net/inhibitor_reset/semantics.py b/pm4py/objects/petri_net/inhibitor_reset/semantics.py
index c82e4f40d..3485a093c 100644
--- a/pm4py/objects/petri_net/inhibitor_reset/semantics.py
+++ b/pm4py/objects/petri_net/inhibitor_reset/semantics.py
@@ -16,23 +16,79 @@
'''
import copy
from pm4py.objects.petri_net import properties
+from pm4py.objects.petri_net.sem_interface import Semantics
+
+
+class InhibitorResetSemantics(Semantics):
+ def is_enabled(self, t, pn, m, **kwargs):
+ """
+ Verifies whether a given transition is enabled in a given Petri net and marking
+
+ Parameters
+ ----------
+ :param t: transition to check
+ :param pn: Petri net
+ :param m: marking to check
+
+ Returns
+ -------
+ :return: true if enabled, false otherwise
+ """
+ return is_enabled(t, pn, m)
+
+ def execute(self, t, pn, m, **kwargs):
+ """
+ Executes a given transition in a given Petri net and Marking
+
+ Parameters
+ ----------
+ :param t: transition to execute
+ :param pn: Petri net
+ :param m: marking to use
+
+ Returns
+ -------
+ :return: newly reached marking if :param t: is enabled, None otherwise
+ """
+ return execute(t, pn, m)
-
+ def weak_execute(self, t, pn, m, **kwargs):
+ """
+ Execute a transition even if it is not fully enabled
+
+ Parameters
+ ----------
+ :param t: transition to execute
+ :param pn: Petri net
+ :param m: marking to use
+
+ Returns
+ -------
+ :return: newly reached marking if :param t: is enabled, None otherwise
+ """
+ return weak_execute(t, m)
+
+ def enabled_transitions(self, pn, m, **kwargs):
+ """
+ Returns a set of enabled transitions in a Petri net and given marking
+
+ Parameters
+ ----------
+ :param pn: Petri net
+ :param m: marking of the pn
+
+ Returns
+ -------
+ :return: set of enabled transitions
+ """
+ return enabled_transitions(pn, m)
+
+
+# 29/08/2021: the following methods have been incapsulated in the InhibitorResetSemantics class.
+# the long term idea is to remove them. However, first we need to adapt the existing code to the new
+# structure. Moreover, for performance reason, it is better to leave the code here, without having
+# to instantiate a InhibitorResetSemantics object.
def is_enabled(t, pn, m):
- """
- Verifies whether a given transition is enabled in a given Petri net and marking
-
- Parameters
- ----------
- :param t: transition to check
- :param pn: Petri net
- :param m: marking to check
-
- Returns
- -------
- :return: true if enabled, false otherwise
- """
-
if t not in pn.transitions:
return False
else:
@@ -48,20 +104,6 @@ def is_enabled(t, pn, m):
def execute(t, pn, m):
- """
- Executes a given transition in a given Petri net and Marking
-
- Parameters
- ----------
- :param t: transition to execute
- :param pn: Petri net
- :param m: marking to use
-
- Returns
- -------
- :return: newly reached marking if :param t: is enabled, None otherwise
- """
-
if not is_enabled(t, pn, m):
return None
@@ -83,27 +125,7 @@ def execute(t, pn, m):
return m_out
-def try_to_execute(t, pn, m):
- if not is_enabled(t, pn, m):
- return None
- else:
- return execute(t, pn, m)
-
-
def weak_execute(t, m):
- """
- Execute a transition even if it is not fully enabled
-
- Parameters
- ----------
- :param t: transition to execute
- :param m: marking to use
-
- Returns
- -------
- :return: newly reached marking if :param t: is enabled, None otherwise
- """
-
m_out = copy.copy(m)
for a in t.in_arcs:
if properties.ARCTYPE in a.properties and a.properties[properties.ARCTYPE] == properties.RESET_ARC:
@@ -121,18 +143,6 @@ def weak_execute(t, m):
def enabled_transitions(pn, m):
- """
- Returns a set of enabled transitions in a Petri net and given marking
-
- Parameters
- ----------
- :param pn: Petri net
- :param m: marking of the pn
-
- Returns
- -------
- :return: set of enabled transitions
- """
enabled = set()
for t in pn.transitions:
if is_enabled(t, pn, m):
diff --git a/pm4py/objects/petri_net/sem_interface.py b/pm4py/objects/petri_net/sem_interface.py
new file mode 100644
index 000000000..ceef621e4
--- /dev/null
+++ b/pm4py/objects/petri_net/sem_interface.py
@@ -0,0 +1,28 @@
+'''
+ This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+ PM4Py is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ PM4Py is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with PM4Py. If not, see .
+'''
+class Semantics(object):
+ def is_enabled(self, t, pn, m, **kwargs):
+ pass
+
+ def execute(self, t, pn, m, **kwargs):
+ pass
+
+ def weak_execute(self, t, pn, m, **kwargs):
+ pass
+
+ def enabled_transitions(self, pn, m, **kwargs):
+ pass
diff --git a/pm4py/objects/petri_net/semantics.py b/pm4py/objects/petri_net/semantics.py
index ea9416ab7..cee125cb6 100644
--- a/pm4py/objects/petri_net/semantics.py
+++ b/pm4py/objects/petri_net/semantics.py
@@ -15,23 +15,80 @@
along with PM4Py. If not, see .
'''
import copy
+import deprecation
+from pm4py.objects.petri_net.sem_interface import Semantics
+
+
+class ClassicSemantics(Semantics):
+ def is_enabled(self, t, pn, m, **kwargs):
+ """
+ Verifies whether a given transition is enabled in a given Petri net and marking
+
+ Parameters
+ ----------
+ :param t: transition to check
+ :param pn: Petri net
+ :param m: marking to check
+
+ Returns
+ -------
+ :return: true if enabled, false otherwise
+ """
+ return is_enabled(t, pn, m)
+
+ def execute(self, t, pn, m, **kwargs):
+ """
+ Executes a given transition in a given Petri net and Marking
+
+ Parameters
+ ----------
+ :param t: transition to execute
+ :param pn: Petri net
+ :param m: marking to use
+
+ Returns
+ -------
+ :return: newly reached marking if :param t: is enabled, None otherwise
+ """
+ return execute(t, pn, m)
-
+ def weak_execute(self, t, pn, m, **kwargs):
+ """
+ Execute a transition even if it is not fully enabled
+
+ Parameters
+ ----------
+ :param t: transition to execute
+ :param pn: Petri net
+ :param m: marking to use
+
+ Returns
+ -------
+ :return: newly reached marking if :param t: is enabled, None otherwise
+ """
+ return weak_execute(t, m)
+
+ def enabled_transitions(self, pn, m, **kwargs):
+ """
+ Returns a set of enabled transitions in a Petri net and given marking
+
+ Parameters
+ ----------
+ :param pn: Petri net
+ :param m: marking of the pn
+
+ Returns
+ -------
+ :return: set of enabled transitions
+ """
+ return enabled_transitions(pn, m)
+
+
+# 29/08/2021: the following methods have been incapsulated in the ClassicSemantics class.
+# the long term idea is to remove them. However, first we need to adapt the existing code to the new
+# structure. Moreover, for performance reason, it is better to leave the code here, without having
+# to instantiate a ClassicSemantics object.
def is_enabled(t, pn, m):
- """
- Verifies whether a given transition is enabled in a given Petri net and marking
-
- Parameters
- ----------
- :param t: transition to check
- :param pn: Petri net
- :param m: marking to check
-
- Returns
- -------
- :return: true if enabled, false otherwise
- """
-
if t not in pn.transitions:
return False
else:
@@ -42,20 +99,6 @@ def is_enabled(t, pn, m):
def execute(t, pn, m):
- """
- Executes a given transition in a given Petri net and Marking
-
- Parameters
- ----------
- :param t: transition to execute
- :param pn: Petri net
- :param m: marking to use
-
- Returns
- -------
- :return: newly reached marking if :param t: is enabled, None otherwise
- """
-
if not is_enabled(t, pn, m):
return None
@@ -71,6 +114,7 @@ def execute(t, pn, m):
return m_out
+@deprecation.deprecated("2.2.13", "3.0.0", details="please use the execute method.")
def try_to_execute(t, pn, m):
if not is_enabled(t, pn, m):
return None
@@ -79,19 +123,6 @@ def try_to_execute(t, pn, m):
def weak_execute(t, m):
- """
- Execute a transition even if it is not fully enabled
-
- Parameters
- ----------
- :param t: transition to execute
- :param m: marking to use
-
- Returns
- -------
- :return: newly reached marking if :param t: is enabled, None otherwise
- """
-
m_out = copy.copy(m)
for a in t.in_arcs:
m_out[a.source] -= a.weight
@@ -103,18 +134,6 @@ def weak_execute(t, m):
def enabled_transitions(pn, m):
- """
- Returns a set of enabled transitions in a Petri net and given marking
-
- Parameters
- ----------
- :param pn: Petri net
- :param m: marking of the pn
-
- Returns
- -------
- :return: set of enabled transitions
- """
enabled = set()
for t in pn.transitions:
if is_enabled(t, pn, m):
diff --git a/pm4py/streaming/__init__.py b/pm4py/streaming/__init__.py
index 36af06ddc..7e4ef3208 100644
--- a/pm4py/streaming/__init__.py
+++ b/pm4py/streaming/__init__.py
@@ -14,4 +14,4 @@
You should have received a copy of the GNU General Public License
along with PM4Py. If not, see .
'''
-from pm4py.streaming import algo, stream, importer, util
+from pm4py.streaming import algo, stream, importer, util, conversion
diff --git a/pm4py/streaming/conversion/__init__.py b/pm4py/streaming/conversion/__init__.py
new file mode 100644
index 000000000..4d998a1df
--- /dev/null
+++ b/pm4py/streaming/conversion/__init__.py
@@ -0,0 +1,17 @@
+'''
+ This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+ PM4Py is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ PM4Py is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with PM4Py. If not, see .
+'''
+from pm4py.streaming.conversion import from_pandas
diff --git a/pm4py/streaming/conversion/from_pandas.py b/pm4py/streaming/conversion/from_pandas.py
new file mode 100644
index 000000000..ecb911ec2
--- /dev/null
+++ b/pm4py/streaming/conversion/from_pandas.py
@@ -0,0 +1,125 @@
+'''
+ This file is part of PM4Py (More Info: https://pm4py.fit.fraunhofer.de).
+
+ PM4Py is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ PM4Py is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with PM4Py. If not, see .
+'''
+from enum import Enum
+from typing import Optional, Dict, Any
+
+import numpy as np
+import pandas as pd
+
+from pm4py.objects.log.obj import Trace, Event
+from pm4py.streaming.stream.live_trace_stream import LiveTraceStream
+from pm4py.util import constants, xes_constants, exec_utils, pandas_utils
+
+
+class Parameters(Enum):
+ CASE_ID_KEY = constants.PARAMETER_CONSTANT_CASEID_KEY
+ ACTIVITY_KEY = constants.PARAMETER_CONSTANT_ACTIVITY_KEY
+ TIMESTAMP_KEY = constants.PARAMETER_CONSTANT_TIMESTAMP_KEY
+ INDEX_KEY = "index_key"
+
+
+class PandasDataframeAsIterable(object):
+ def __init__(self, dataframe: pd.DataFrame, parameters: Optional[Dict[Any, Any]] = None):
+ if parameters is None:
+ parameters = {}
+
+ case_id_key = exec_utils.get_param_value(Parameters.CASE_ID_KEY, parameters, constants.CASE_CONCEPT_NAME)
+ activity_key = exec_utils.get_param_value(Parameters.ACTIVITY_KEY, parameters, xes_constants.DEFAULT_NAME_KEY)
+ timestamp_key = exec_utils.get_param_value(Parameters.TIMESTAMP_KEY, parameters,
+ xes_constants.DEFAULT_TIMESTAMP_KEY)
+ index_key = exec_utils.get_param_value(Parameters.INDEX_KEY, parameters, constants.DEFAULT_INDEX_KEY)
+
+ if not (hasattr(dataframe, "attrs") and dataframe.attrs):
+ # dataframe has not been initialized through format_dataframe
+ dataframe = pandas_utils.insert_index(dataframe, index_key)
+ dataframe.sort_values([case_id_key, timestamp_key, index_key])
+
+ cases = dataframe[case_id_key].to_numpy()
+
+ self.activities = dataframe[activity_key].to_numpy()
+ self.timestamps = dataframe[timestamp_key].to_numpy()
+ self.c_unq, self.c_ind, self.c_counts = np.unique(cases, return_index=True, return_counts=True)
+ self.no_traces = len(self.c_ind)
+ self.i = 0
+
+ def read_trace(self) -> Trace:
+ if self.i < self.no_traces:
+ case_id = self.c_unq[self.i]
+ si = self.c_ind[self.i]
+ ei = si + self.c_counts[self.i]
+ trace = Trace(attributes={xes_constants.DEFAULT_TRACEID_KEY: case_id})
+ for j in range(si, ei):
+ event = Event({xes_constants.DEFAULT_NAME_KEY: self.activities[j],
+ xes_constants.DEFAULT_TIMESTAMP_KEY: self.timestamps[j]})
+ trace.append(event)
+ self.i = self.i + 1
+ return trace
+
+ def reset(self):
+ self.i = 0
+
+ def __iter__(self):
+ """
+ Starts the iteration
+ """
+ return self
+
+ def __next__(self):
+ """
+ Gets the next trace
+ """
+ trace = self.read_trace()
+ if trace is None:
+ raise StopIteration
+ return trace
+
+ def to_trace_stream(self, trace_stream: LiveTraceStream):
+ """
+ Sends the content of the dataframe to a trace stream
+
+ Parameters
+ --------------
+ trace_stream
+ Trace stream
+ """
+ trace = self.read_trace()
+ while trace is not None:
+ trace_stream.append(trace)
+ trace = self.read_trace()
+
+
+def apply(dataframe, parameters=None) -> PandasDataframeAsIterable:
+ """
+ Transforms the Pandas dataframe object to an iterable
+
+ Parameters
+ ----------------
+ dataframe
+ Pandas dataframe
+ parameters
+ Parameters of the algorithm, including:
+ - Parameters.CASE_ID_KEY => the attribute to be used as case identifier (default: constants.CASE_CONCEPT_NAME)
+ - Parameters.ACTIVITY_KEY => the attribute to be used as activity (default: xes_constants.DEFAULT_NAME_KEY)
+ - Parameters.TIMESTAMP_KEY => the attribute to be used as timestamp (default: xes_constants.DEFAULT_TIMESTAMP_KEY)
+
+ Returns
+ ----------------
+ log_iterable
+ Iterable log object, which can be iterated directly or added to a live trace stream
+ (using the method to_trace_stream).
+ """
+ return PandasDataframeAsIterable(dataframe, parameters=parameters)
diff --git a/pm4py/visualization/bpmn/variants/classic.py b/pm4py/visualization/bpmn/variants/classic.py
index 9378a9c64..61a319bdd 100644
--- a/pm4py/visualization/bpmn/variants/classic.py
+++ b/pm4py/visualization/bpmn/variants/classic.py
@@ -28,6 +28,7 @@ class Parameters(Enum):
FORMAT = "format"
RANKDIR = "rankdir"
FONT_SIZE = "font_size"
+ BGCOLOR = "bgcolor"
def apply(bpmn_graph: BPMN, parameters: Optional[Dict[Any, Any]] = None) -> graphviz.Digraph:
@@ -58,9 +59,10 @@ def apply(bpmn_graph: BPMN, parameters: Optional[Dict[Any, Any]] = None) -> grap
rankdir = exec_utils.get_param_value(Parameters.RANKDIR, parameters, "LR")
font_size = exec_utils.get_param_value(Parameters.FONT_SIZE, parameters, 12)
font_size = str(font_size)
+ bgcolor = exec_utils.get_param_value(Parameters.BGCOLOR, parameters, "transparent")
filename = tempfile.NamedTemporaryFile(suffix='.gv')
- viz = Digraph("", filename=filename.name, engine='dot', graph_attr={'bgcolor': 'transparent'})
+ viz = Digraph("", filename=filename.name, engine='dot', graph_attr={'bgcolor': bgcolor})
viz.graph_attr['rankdir'] = rankdir
nodes, edges = get_sorted_nodes_edges(bpmn_graph)
diff --git a/pm4py/visualization/dfg/variants/frequency.py b/pm4py/visualization/dfg/variants/frequency.py
index bc098b1f8..5e5f27315 100644
--- a/pm4py/visualization/dfg/variants/frequency.py
+++ b/pm4py/visualization/dfg/variants/frequency.py
@@ -43,6 +43,7 @@ class Parameters(Enum):
TIMESTAMP_KEY = constants.PARAMETER_CONSTANT_TIMESTAMP_KEY
START_TIMESTAMP_KEY = constants.PARAMETER_CONSTANT_START_TIMESTAMP_KEY
FONT_SIZE = "font_size"
+ BGCOLOR = "bgcolor"
def get_min_max_value(dfg):
@@ -132,7 +133,7 @@ def get_activities_color(activities_count):
def graphviz_visualization(activities_count, dfg, image_format="png", measure="frequency",
max_no_of_edges_in_diagram=100000, start_activities=None, end_activities=None, soj_time=None,
- font_size="12"):
+ font_size="12", bgcolor="transparent"):
"""
Do GraphViz visualization of a DFG graph
@@ -166,7 +167,7 @@ def graphviz_visualization(activities_count, dfg, image_format="png", measure="f
end_activities = {}
filename = tempfile.NamedTemporaryFile(suffix='.gv')
- viz = Digraph("", filename=filename.name, engine='dot', graph_attr={'bgcolor': 'transparent'})
+ viz = Digraph("", filename=filename.name, engine='dot', graph_attr={'bgcolor': bgcolor})
# first, remove edges in diagram that exceeds the maximum number of edges in the diagram
dfg_key_value_list = []
@@ -280,6 +281,7 @@ def apply(dfg: Dict[Tuple[str, str], int], log: EventLog = None, parameters: Opt
font_size = exec_utils.get_param_value(Parameters.FONT_SIZE, parameters, 12)
font_size = str(font_size)
activities = dfg_utils.get_activities_from_dfg(dfg)
+ bgcolor = exec_utils.get_param_value(Parameters.BGCOLOR, parameters, "transparent")
if activities_count is None:
if log is not None:
@@ -296,4 +298,4 @@ def apply(dfg: Dict[Tuple[str, str], int], log: EventLog = None, parameters: Opt
return graphviz_visualization(activities_count, dfg, image_format=image_format, measure="frequency",
max_no_of_edges_in_diagram=max_no_of_edges_in_diagram,
start_activities=start_activities, end_activities=end_activities, soj_time=soj_time,
- font_size=font_size)
+ font_size=font_size, bgcolor=bgcolor)
diff --git a/pm4py/visualization/dfg/variants/performance.py b/pm4py/visualization/dfg/variants/performance.py
index 6e56ae18a..02ffbf380 100644
--- a/pm4py/visualization/dfg/variants/performance.py
+++ b/pm4py/visualization/dfg/variants/performance.py
@@ -45,6 +45,7 @@ class Parameters(Enum):
START_TIMESTAMP_KEY = constants.PARAMETER_CONSTANT_START_TIMESTAMP_KEY
FONT_SIZE = "font_size"
AGGREGATION_MEASURE = "aggregation_measure"
+ BGCOLOR = "bgcolor"
def get_min_max_value(dfg):
@@ -132,7 +133,7 @@ def get_activities_color_soj_time(soj_time):
def graphviz_visualization(activities_count, dfg, image_format="png", measure="frequency",
max_no_of_edges_in_diagram=100000, start_activities=None, end_activities=None, soj_time=None,
- font_size="12"):
+ font_size="12", bgcolor="transparent"):
"""
Do GraphViz visualization of a DFG graph
@@ -166,7 +167,7 @@ def graphviz_visualization(activities_count, dfg, image_format="png", measure="f
end_activities = []
filename = tempfile.NamedTemporaryFile(suffix='.gv')
- viz = Digraph("", filename=filename.name, engine='dot', graph_attr={'bgcolor': 'transparent'})
+ viz = Digraph("", filename=filename.name, engine='dot', graph_attr={'bgcolor': bgcolor})
# first, remove edges in diagram that exceeds the maximum number of edges in the diagram
dfg_key_value_list = []
@@ -281,6 +282,7 @@ def apply(dfg: Dict[Tuple[str, str], int], log: EventLog = None, parameters: Opt
font_size = str(font_size)
activities = dfg_utils.get_activities_from_dfg(dfg)
aggregation_measure = exec_utils.get_param_value(Parameters.AGGREGATION_MEASURE, parameters, "mean")
+ bgcolor = exec_utils.get_param_value(Parameters.BGCOLOR, parameters, "transparent")
if activities_count is None:
if log is not None:
@@ -310,4 +312,4 @@ def apply(dfg: Dict[Tuple[str, str], int], log: EventLog = None, parameters: Opt
return graphviz_visualization(activities_count, dfg, image_format=image_format, measure="performance",
max_no_of_edges_in_diagram=max_no_of_edges_in_diagram,
start_activities=start_activities, end_activities=end_activities, soj_time=soj_time,
- font_size=font_size)
+ font_size=font_size, bgcolor=bgcolor)
diff --git a/pm4py/visualization/petri_net/common/visualize.py b/pm4py/visualization/petri_net/common/visualize.py
index 03630a772..fec4b892d 100644
--- a/pm4py/visualization/petri_net/common/visualize.py
+++ b/pm4py/visualization/petri_net/common/visualize.py
@@ -33,6 +33,7 @@ class Parameters(Enum):
TIMESTAMP_KEY = PARAMETER_CONSTANT_TIMESTAMP_KEY
AGGREGATION_MEASURE = "aggregationMeasure"
FONT_SIZE = "font_size"
+ BGCOLOR = "bgcolor"
def apply(net, initial_marking, final_marking, decorations=None, parameters=None):
@@ -65,14 +66,15 @@ def apply(net, initial_marking, final_marking, decorations=None, parameters=None
debug = exec_utils.get_param_value(Parameters.DEBUG, parameters, False)
set_rankdir = exec_utils.get_param_value(Parameters.RANKDIR, parameters, None)
font_size = exec_utils.get_param_value(Parameters.FONT_SIZE, parameters, "12")
+ bgcolor = exec_utils.get_param_value(Parameters.BGCOLOR, parameters, "transparent")
return graphviz_visualization(net, image_format=image_format, initial_marking=initial_marking,
final_marking=final_marking, decorations=decorations, debug=debug,
- set_rankdir=set_rankdir, font_size=font_size)
+ set_rankdir=set_rankdir, font_size=font_size, bgcolor=bgcolor)
def graphviz_visualization(net, image_format="png", initial_marking=None, final_marking=None, decorations=None,
- debug=False, set_rankdir=None, font_size="12"):
+ debug=False, set_rankdir=None, font_size="12", bgcolor="transparent"):
"""
Provides visualization for the petrinet
@@ -108,7 +110,7 @@ def graphviz_visualization(net, image_format="png", initial_marking=None, final_
font_size = str(font_size)
filename = tempfile.NamedTemporaryFile(suffix='.gv')
- viz = Digraph(net.name, filename=filename.name, engine='dot', graph_attr={'bgcolor': 'transparent'})
+ viz = Digraph(net.name, filename=filename.name, engine='dot', graph_attr={'bgcolor': bgcolor})
if set_rankdir:
viz.graph_attr['rankdir'] = set_rankdir
else:
diff --git a/pm4py/visualization/petrinet/common/visualize.py b/pm4py/visualization/petrinet/common/visualize.py
index e4d65577c..c2a1f77b9 100644
--- a/pm4py/visualization/petrinet/common/visualize.py
+++ b/pm4py/visualization/petrinet/common/visualize.py
@@ -64,7 +64,7 @@ def apply(net, initial_marking, final_marking, decorations=None, parameters=None
def graphviz_visualization(net, image_format="png", initial_marking=None, final_marking=None, decorations=None,
- debug=False, set_rankdir=None, font_size="12"):
+ debug=False, set_rankdir=None, font_size="12", bgcolor="transparent"):
"""
Provides visualization for the petrinet
@@ -100,7 +100,7 @@ def graphviz_visualization(net, image_format="png", initial_marking=None, final_
font_size = str(font_size)
filename = tempfile.NamedTemporaryFile(suffix='.gv')
- viz = Digraph(net.name, filename=filename.name, engine='dot', graph_attr={'bgcolor': 'transparent'})
+ viz = Digraph(net.name, filename=filename.name, engine='dot', graph_attr={'bgcolor': bgcolor})
if set_rankdir:
viz.graph_attr['rankdir'] = set_rankdir
else:
diff --git a/pm4py/visualization/process_tree/variants/symbolic.py b/pm4py/visualization/process_tree/variants/symbolic.py
index 99b37f1cc..35caca19d 100644
--- a/pm4py/visualization/process_tree/variants/symbolic.py
+++ b/pm4py/visualization/process_tree/variants/symbolic.py
@@ -33,6 +33,7 @@ class Parameters(Enum):
COLOR_MAP = "color_map"
ENABLE_DEEPCOPY = "enable_deepcopy"
FONT_SIZE = "font_size"
+ BGCOLOR = "bgcolor"
def get_color(node, color_map):
@@ -95,7 +96,10 @@ def apply(tree: ProcessTree, parameters: Optional[Dict[Union[str, Parameters], A
parameters = {}
filename = tempfile.NamedTemporaryFile(suffix='.gv')
- viz = Graph("pt", filename=filename.name, engine='dot', graph_attr={'bgcolor': 'transparent'})
+
+ bgcolor = exec_utils.get_param_value(Parameters.BGCOLOR, parameters, "transparent")
+
+ viz = Graph("pt", filename=filename.name, engine='dot', graph_attr={'bgcolor': bgcolor})
viz.attr('node', shape='ellipse', fixedsize='false')
image_format = exec_utils.get_param_value(Parameters.FORMAT, parameters, "png")
diff --git a/pm4py/visualization/process_tree/variants/wo_decoration.py b/pm4py/visualization/process_tree/variants/wo_decoration.py
index 3440a6398..854f975fd 100644
--- a/pm4py/visualization/process_tree/variants/wo_decoration.py
+++ b/pm4py/visualization/process_tree/variants/wo_decoration.py
@@ -33,6 +33,7 @@ class Parameters(Enum):
COLOR_MAP = "color_map"
ENABLE_DEEPCOPY = "enable_deepcopy"
FONT_SIZE = "font_size"
+ BGCOLOR = "bgcolor"
# maps the operators to the ProM strings
@@ -140,7 +141,10 @@ def apply(tree: ProcessTree, parameters: Optional[Dict[Union[str, Parameters], A
parameters = {}
filename = tempfile.NamedTemporaryFile(suffix='.gv')
- viz = Graph("pt", filename=filename.name, engine='dot', graph_attr={'bgcolor': 'transparent'})
+
+ bgcolor = exec_utils.get_param_value(Parameters.BGCOLOR, parameters, "transparent")
+
+ viz = Graph("pt", filename=filename.name, engine='dot', graph_attr={'bgcolor': bgcolor})
viz.attr('node', shape='ellipse', fixedsize='false')
image_format = exec_utils.get_param_value(Parameters.FORMAT, parameters, "png")
diff --git a/pm4py/visualization/transition_system/util/visualize_graphviz.py b/pm4py/visualization/transition_system/util/visualize_graphviz.py
index 74d96ce65..a0a002182 100644
--- a/pm4py/visualization/transition_system/util/visualize_graphviz.py
+++ b/pm4py/visualization/transition_system/util/visualize_graphviz.py
@@ -29,6 +29,7 @@ class Parameters(Enum):
FORCE_NAMES = "force_names"
FILLCOLORS = "fillcolors"
FONT_SIZE = "font_size"
+ BGCOLOR = "bgcolor"
def visualize(ts, parameters=None):
@@ -42,6 +43,7 @@ def visualize(ts, parameters=None):
fillcolors = exec_utils.get_param_value(Parameters.FILLCOLORS, parameters, {})
font_size = exec_utils.get_param_value(Parameters.FONT_SIZE, parameters, 11)
font_size = str(font_size)
+ bgcolor = exec_utils.get_param_value(Parameters.BGCOLOR, parameters, "transparent")
for state in ts.states:
state.label = state.name
@@ -57,7 +59,7 @@ def visualize(ts, parameters=None):
ts = nts
filename = tempfile.NamedTemporaryFile(suffix='.gv')
- viz = Digraph(ts.name, filename=filename.name, engine='dot', graph_attr={'bgcolor': 'transparent'})
+ viz = Digraph(ts.name, filename=filename.name, engine='dot', graph_attr={'bgcolor': bgcolor})
# states
viz.attr('node')
diff --git a/pm4py/visualization/trie/variants/classic.py b/pm4py/visualization/trie/variants/classic.py
index 0ee933901..cff7a851b 100644
--- a/pm4py/visualization/trie/variants/classic.py
+++ b/pm4py/visualization/trie/variants/classic.py
@@ -26,6 +26,7 @@
class Parameters(Enum):
FORMAT = "format"
+ BGCOLOR = "bgcolor"
def draw_recursive(trie_node: Trie, parent: Union[str, None], gviz: Graph):
@@ -71,9 +72,10 @@ def apply(trie: Trie, parameters: Optional[Dict[Union[str, Parameters], Any]] =
parameters = {}
image_format = exec_utils.get_param_value(Parameters.FORMAT, parameters, "png")
+ bgcolor = exec_utils.get_param_value(Parameters.BGCOLOR, parameters, "transparent")
filename = tempfile.NamedTemporaryFile(suffix='.gv')
- viz = Graph("pt", filename=filename.name, engine='dot', graph_attr={'bgcolor': 'transparent'})
+ viz = Graph("pt", filename=filename.name, engine='dot', graph_attr={'bgcolor': bgcolor})
viz.attr('node', shape='ellipse', fixedsize='false')
draw_recursive(trie, None, viz)
diff --git a/setup.py b/setup.py
index d49624f29..26cf35eab 100644
--- a/setup.py
+++ b/setup.py
@@ -62,20 +62,20 @@ def read_file(filename):
'pm4py.algo.discovery.minimum_self_distance', 'pm4py.algo.discovery.minimum_self_distance.variants',
'pm4py.algo.filtering', 'pm4py.algo.filtering.dfg', 'pm4py.algo.filtering.log',
'pm4py.algo.filtering.log.ltl', 'pm4py.algo.filtering.log.cases', 'pm4py.algo.filtering.log.paths',
- 'pm4py.algo.filtering.log.between', 'pm4py.algo.filtering.log.variants',
- 'pm4py.algo.filtering.log.timestamp', 'pm4py.algo.filtering.log.attributes',
- 'pm4py.algo.filtering.log.auto_filter', 'pm4py.algo.filtering.log.end_activities',
- 'pm4py.algo.filtering.log.start_activities', 'pm4py.algo.filtering.log.attr_value_repetition',
- 'pm4py.algo.filtering.common', 'pm4py.algo.filtering.common.timestamp',
- 'pm4py.algo.filtering.common.attributes', 'pm4py.algo.filtering.common.end_activities',
- 'pm4py.algo.filtering.common.start_activities', 'pm4py.algo.filtering.pandas',
- 'pm4py.algo.filtering.pandas.ltl', 'pm4py.algo.filtering.pandas.cases',
- 'pm4py.algo.filtering.pandas.paths', 'pm4py.algo.filtering.pandas.between',
- 'pm4py.algo.filtering.pandas.variants', 'pm4py.algo.filtering.pandas.timestamp',
- 'pm4py.algo.filtering.pandas.attributes', 'pm4py.algo.filtering.pandas.auto_filter',
- 'pm4py.algo.filtering.pandas.end_activities', 'pm4py.algo.filtering.pandas.start_activities',
- 'pm4py.algo.filtering.pandas.attr_value_repetition', 'pm4py.algo.reduction',
- 'pm4py.algo.reduction.process_tree', 'pm4py.algo.reduction.process_tree.variants',
+ 'pm4py.algo.filtering.log.rework', 'pm4py.algo.filtering.log.between',
+ 'pm4py.algo.filtering.log.variants', 'pm4py.algo.filtering.log.timestamp',
+ 'pm4py.algo.filtering.log.attributes', 'pm4py.algo.filtering.log.auto_filter',
+ 'pm4py.algo.filtering.log.end_activities', 'pm4py.algo.filtering.log.start_activities',
+ 'pm4py.algo.filtering.log.attr_value_repetition', 'pm4py.algo.filtering.common',
+ 'pm4py.algo.filtering.common.timestamp', 'pm4py.algo.filtering.common.attributes',
+ 'pm4py.algo.filtering.common.end_activities', 'pm4py.algo.filtering.common.start_activities',
+ 'pm4py.algo.filtering.pandas', 'pm4py.algo.filtering.pandas.ltl', 'pm4py.algo.filtering.pandas.cases',
+ 'pm4py.algo.filtering.pandas.paths', 'pm4py.algo.filtering.pandas.rework',
+ 'pm4py.algo.filtering.pandas.between', 'pm4py.algo.filtering.pandas.variants',
+ 'pm4py.algo.filtering.pandas.timestamp', 'pm4py.algo.filtering.pandas.attributes',
+ 'pm4py.algo.filtering.pandas.auto_filter', 'pm4py.algo.filtering.pandas.end_activities',
+ 'pm4py.algo.filtering.pandas.start_activities', 'pm4py.algo.filtering.pandas.attr_value_repetition',
+ 'pm4py.algo.reduction', 'pm4py.algo.reduction.process_tree', 'pm4py.algo.reduction.process_tree.variants',
'pm4py.algo.clustering', 'pm4py.algo.clustering.trace_attribute_driven',
'pm4py.algo.clustering.trace_attribute_driven.dfg', 'pm4py.algo.clustering.trace_attribute_driven.util',
'pm4py.algo.clustering.trace_attribute_driven.variants',
@@ -166,9 +166,9 @@ def read_file(filename):
'pm4py.streaming.util', 'pm4py.streaming.util.dictio', 'pm4py.streaming.util.dictio.versions',
'pm4py.streaming.stream', 'pm4py.streaming.importer', 'pm4py.streaming.importer.csv',
'pm4py.streaming.importer.csv.variants', 'pm4py.streaming.importer.xes',
- 'pm4py.streaming.importer.xes.variants', 'pm4py.evaluation', 'pm4py.evaluation.wf_net',
- 'pm4py.evaluation.wf_net.variants', 'pm4py.evaluation.precision', 'pm4py.evaluation.precision.variants',
- 'pm4py.evaluation.soundness', 'pm4py.evaluation.soundness.woflan',
+ 'pm4py.streaming.importer.xes.variants', 'pm4py.streaming.conversion', 'pm4py.evaluation',
+ 'pm4py.evaluation.wf_net', 'pm4py.evaluation.wf_net.variants', 'pm4py.evaluation.precision',
+ 'pm4py.evaluation.precision.variants', 'pm4py.evaluation.soundness', 'pm4py.evaluation.soundness.woflan',
'pm4py.evaluation.soundness.woflan.graphs', 'pm4py.evaluation.soundness.woflan.graphs.reachability_graph',
'pm4py.evaluation.soundness.woflan.graphs.minimal_coverability_graph',
'pm4py.evaluation.soundness.woflan.graphs.restricted_coverability_graph',
diff --git a/tests/input_data/receipt.parquet b/tests/input_data/receipt.parquet
index d5ed3b462..b661021c9 100644
Binary files a/tests/input_data/receipt.parquet and b/tests/input_data/receipt.parquet differ
diff --git a/tests/input_data/running-example.parquet b/tests/input_data/running-example.parquet
index 933a72537..f9bc15aec 100644
Binary files a/tests/input_data/running-example.parquet and b/tests/input_data/running-example.parquet differ