diff --git a/CHANGELOG.md b/CHANGELOG.md index 800ff1795..0760c435f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog of pm4py +## pm4py 2.7.11 (2024.02.23) + +### Added + +### Changed + +### Deprecated + +### Fixed +* c2278110acbbbf36e8fe5055e0b76f867e833da4 + * bug fix POWL package +* d36d0d2ff8133bc979638b7f3405f96225e06ced + * bug fix discover_dfg_clean +* 64ce3341d40c073f274bdda9842fd92a0798a33f + * fixed typing of get_variants method +* ffd0761e8f5490382b33de4fa8187f1ed80ce802 + * fixed stochastic Petri net PNML importing + +### Removed + +### Other +* c4d24d54013e2a18856af840ffbad7c76e86c371 + 81fa8017fbd25352fd87961bc4dcc1406c4e0dda + * Process tree to POWL utility + +--- + ## pm4py 2.7.10 (2024.01.29) @@ -14,7 +41,7 @@ * refactoring Scikit-Learn usage throughout the project * bd1ac4fd04019b3022bfff55553e590aefbd21cb * refactoring NetworkX usage throughout the project -* + ### Deprecated ### Fixed diff --git a/Dockerfile b/Dockerfile index 6e3824103..0906aa1dc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.12.1 +FROM python:3.12.2 RUN apt-get update RUN apt-get -y upgrade diff --git a/pm4py/meta.py b/pm4py/meta.py index 9824679e0..d2fb59773 100644 --- a/pm4py/meta.py +++ b/pm4py/meta.py @@ -16,7 +16,7 @@ ''' __name__ = 'pm4py' -VERSION = '2.7.10' +VERSION = '2.7.10.1' __version__ = VERSION __doc__ = 'Process mining for Python' __author__ = 'Fraunhofer Institute for Applied Information Technology FIT' diff --git a/pm4py/objects/petri_net/importer/variants/pnml.py b/pm4py/objects/petri_net/importer/variants/pnml.py index bea93f8cb..67d2e9e70 100644 --- a/pm4py/objects/petri_net/importer/variants/pnml.py +++ b/pm4py/objects/petri_net/importer/variants/pnml.py @@ -240,6 +240,9 @@ def import_net_from_xml_object(root, parameters=None): priority = int(value) elif key == "weight": weight = float(value) + elif key == "invisible": + if value.lower() == "true": + trans_visible = False from pm4py.objects.random_variables.random_variable import RandomVariable diff --git a/pm4py/objects/powl/obj.py b/pm4py/objects/powl/obj.py index 26d1e8f6e..889b4a7d1 100644 --- a/pm4py/objects/powl/obj.py +++ b/pm4py/objects/powl/obj.py @@ -19,9 +19,10 @@ from pm4py.objects.powl.constants import STRICT_PARTIAL_ORDER_LABEL from pm4py.objects.process_tree.obj import ProcessTree, Operator from typing import List as TList, Optional, Union +from abc import ABC, abstractmethod -class POWL(ProcessTree): +class POWL(ProcessTree, ABC): def __str__(self) -> str: return self.__repr__() @@ -44,65 +45,25 @@ def validate_partial_orders(self): for child in self.children: child.validate_partial_orders() - def validate_partial_orders_with_missing_transitive_edges(self): - if isinstance(self, StrictPartialOrder): - if not self.order.is_irreflexive(): - raise Exception("The irreflexivity of the partial order is violated!") - if not self.order.is_transitive(): - self.order.add_transitive_edges() - if not self.order.is_irreflexive(): - raise Exception("The transitive closure of the provided relation violates irreflexivity!") - if hasattr(self, 'children'): - for child in self.children: - child.validate_partial_orders_with_missing_transitive_edges() - - def validate_unique_transitions(self) -> TList[Union["Transition", "SilentTransition"]]: - def _find_duplicates(lst): - counts = {} - duplicates = [] - for item in lst: - if item in counts: - counts[item] += 1 - if counts[item] == 2: - duplicates.append(item) - else: - counts[item] = 1 - return duplicates - - def _collect_leaves(node: "POWL"): - if isinstance(node, Transition) or isinstance(node, SilentTransition): - return [node] - - elif hasattr(node, 'children'): - leaves = [] - for child in node.children: - leaves = leaves + _collect_leaves(child) - return leaves - else: - raise Exception( - "Unknown model type! The following model is not a transition and has no children: " + str(node)) - - transitions = _collect_leaves(self) - duplicate_transitions = _find_duplicates(transitions) - if len(duplicate_transitions) > 0: - raise Exception("Each of the following transitions occurs in multiple submodels: " - + str([t.label if t.label else "silent transition" for t in duplicate_transitions])) - return transitions - @staticmethod def model_description() -> str: descr = """A partially ordered workflow language (POWL) is a partially ordered graph representation of a process, extended with control-flow operators for modeling choice and loop structures. There are four types of POWL models: -- an activity (identified by its label, i.e., 'M' identifies the activity M). Silent activities (i.e., with empty labels) are also supported. +- an activity (identified by its label, i.e., 'M' identifies the activity M). Silent activities with empty labels (tau labels) are also supported. - a choice of other POWL models (an exclusive choice between the sub-models A and B is identified by X ( A, B ) ) - a loop node between two POWL models (a loop between the sub-models A and B is identified by * ( A, B ) and tells that you execute A, then you either exit the loop or execute B and then A again, this is repeated until you exit the loop). -- a partial order over a set of POWL models. A partial order is a binary relation that is irreflexive, transitive, and asymmetric. A partial order sets an execution order between the sub-models (i.e., the target node cannot be executed before the source node is completed). +- a partial order over a set of POWL models. A partial order is a binary relation that is irreflexive, transitive, and asymmetric. A partial order sets an execution order between the sub-models (i.e., the target node cannot be executed before the source node is completed). Unconnected nodes in a partial order are considered to be concurrent. An example is PO=(nodes={ NODE1, NODE2 }, order={ }) +where NODE1 and NODE2 are independent and can be executed in parallel. Another example is PO=(nodes={ NODE1, NODE2 }, order={ NODE1-->NODE2 }) where NODE2 can only be executed after NODE1 is completed. + +A more advanced example: PO=(nodes={ NODE1, NODE2, NODE3, X ( NODE4, NODE5 ) }, order={ NODE1-->NODE2, NODE1-->X ( NODE4, NODE5 ), NODE2-->X ( NODE4, NODE5 ) }), in this case, NODE2 can be executed only after NODE1 is completed, while the choice between NODE4 and NODE5 needs to wait until both NODE1 and NODE2 are finalized. + -You can specify a POWL model as follows: -PO=(nodes={ NODE1, NODE2, NODE3, X ( NODE4, NODE5 ) }, order={ NODE1-->NODE2, NODE1-->X ( NODE4, NODE5 ), NODE2-->X ( NODE4, NODE5 ) }) -in this case, NODE2 can be executed only after NODE1 is completed, while the choice between NODE4 and NODE5 needs to wait until both NODE1 and NODE2 are finalized. """ return descr + @abstractmethod + def copy(self): + pass + class Transition(POWL): transition_id: int = 0 @@ -113,6 +74,9 @@ def __init__(self, label: Optional[str] = None) -> None: self._identifier = Transition.transition_id Transition.transition_id = Transition.transition_id + 1 + def copy(self): + return Transition(self._label) + def __eq__(self, other: object) -> bool: if isinstance(other, Transition): return self._label == other._label and self._identifier == other._identifier @@ -142,6 +106,9 @@ class SilentTransition(Transition): def __init__(self) -> None: super().__init__(label=None) + def copy(self): + return SilentTransition() + class FrequentTransition(Transition): def __init__(self, label, min_freq: Union[str, int], max_freq: Union[str, int]) -> None: @@ -167,6 +134,16 @@ def __init__(self, nodes: TList[POWL]) -> None: self._set_order(nodes) self.additional_information = None + def copy(self): + copied_nodes = {n:n.copy() for n in self.order.nodes} + res = StrictPartialOrder(list(copied_nodes.values())) + for n1 in self.order.nodes: + for n2 in self.order.nodes: + if self.order.is_edge(n1, n2): + res.add_edge(copied_nodes[n1], copied_nodes[n2]) + return res + + def _set_order(self, nodes: TList[POWL]) -> None: self.order = BinaryRelation(nodes) @@ -327,6 +304,10 @@ def __init__(self, operator: Operator, children: TList[POWL]) -> None: self.operator = operator self.children = children + def copy(self): + copied_nodes = [n.copy() for n in self.children] + return OperatorPOWL(self.operator, copied_nodes) + def __lt__(self, other: object) -> bool: if isinstance(other, OperatorPOWL): return self.__repr__() < other.__repr__() diff --git a/pm4py/stats.py b/pm4py/stats.py index 0e6c673f6..2644415fd 100644 --- a/pm4py/stats.py +++ b/pm4py/stats.py @@ -209,7 +209,7 @@ def get_trace_attribute_values(log: Union[EventLog, pd.DataFrame], attribute: st return ret -def get_variants(log: Union[EventLog, pd.DataFrame], activity_key: str = "concept:name", timestamp_key: str = "time:timestamp", case_id_key: str = "case:concept:name") -> Dict[Tuple[str], List[Trace]]: +def get_variants(log: Union[EventLog, pd.DataFrame], activity_key: str = "concept:name", timestamp_key: str = "time:timestamp", case_id_key: str = "case:concept:name") -> Union[Dict[Tuple[str], List[Trace]], Dict[Tuple[str], int]]: """ Gets the variants from the log @@ -228,7 +228,7 @@ def get_variants(log: Union[EventLog, pd.DataFrame], activity_key: str = "concep return get_variants_as_tuples(log, activity_key=activity_key, timestamp_key=timestamp_key, case_id_key=case_id_key) -def get_variants_as_tuples(log: Union[EventLog, pd.DataFrame], activity_key: str = "concept:name", timestamp_key: str = "time:timestamp", case_id_key: str = "case:concept:name") -> Dict[Tuple[str], List[Trace]]: +def get_variants_as_tuples(log: Union[EventLog, pd.DataFrame], activity_key: str = "concept:name", timestamp_key: str = "time:timestamp", case_id_key: str = "case:concept:name") -> Union[Dict[Tuple[str], List[Trace]], Dict[Tuple[str], int]]: """ Gets the variants from the log (where the keys are tuples and not strings) diff --git a/requirements_stable.txt b/requirements_stable.txt index f43e40fe7..1440c49a2 100644 --- a/requirements_stable.txt +++ b/requirements_stable.txt @@ -2,12 +2,12 @@ colorama==0.4.6 contourpy==1.2.0 cycler==0.12.1 deprecation==2.1.0 -fonttools==4.48.1 +fonttools==4.49.0 graphviz==0.20.1 intervaltree==3.1.0 kiwisolver==1.4.5 lxml==5.1.0 -matplotlib==3.8.2 +matplotlib==3.8.3 networkx==3.2.1 numpy==1.26.4 packaging==23.2 @@ -21,5 +21,5 @@ scipy==1.12.0 six==1.16.0 sortedcontainers==2.4.0 StringDist==1.0.9 -tqdm==4.66.1 -tzdata==2023.4 +tqdm==4.66.2 +tzdata==2024.1 diff --git a/safety_checks/20240222 b/safety_checks/20240222 new file mode 100644 index 000000000..59d7f4af8 --- /dev/null +++ b/safety_checks/20240222 @@ -0,0 +1,47 @@ ++==============================================================================+ + + /$$$$$$ /$$ + /$$__ $$ | $$ + /$$$$$$$ /$$$$$$ | $$ \__//$$$$$$ /$$$$$$ /$$ /$$ + /$$_____/ |____ $$| $$$$ /$$__ $$|_ $$_/ | $$ | $$ + | $$$$$$ /$$$$$$$| $$_/ | $$$$$$$$ | $$ | $$ | $$ + \____ $$ /$$__ $$| $$ | $$_____/ | $$ /$$| $$ | $$ + /$$$$$$$/| $$$$$$$| $$ | $$$$$$$ | $$$$/| $$$$$$$ + |_______/ \_______/|__/ \_______/ \___/ \____ $$ + /$$ | $$ + | $$$$$$/ + by pyup.io \______/ + ++==============================================================================+ + + REPORT + + Safety is using PyUp's free open-source vulnerability database. This +data is 30 days old and limited. + For real-time enhanced vulnerability data, fix recommendations, severity +reporting, cybersecurity support, team and project policy management and more +sign up at https://pyup.io or email sales@pyup.io + + Safety v2.3.5 is scanning for Vulnerabilities... + Scanning dependencies in your files: + + -> requirements_stable.txt + + Using non-commercial database + Found and scanned 25 packages + Timestamp 2024-02-22 07:09:34 + 0 vulnerabilities found + 0 vulnerabilities ignored ++==============================================================================+ + + No known security vulnerabilities found. + ++==============================================================================+ + + Safety is using PyUp's free open-source vulnerability database. This +data is 30 days old and limited. + For real-time enhanced vulnerability data, fix recommendations, severity +reporting, cybersecurity support, team and project policy management and more +sign up at https://pyup.io or email sales@pyup.io + ++==============================================================================+