From 2bceb5250cf527d17db88fc2d18acae048937e84 Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Thu, 17 Nov 2022 15:11:42 -0600 Subject: [PATCH 1/6] CycleError: Report all nodes --- pytools/graph.py | 10 +++++----- test/test_graph_tools.py | 6 ++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/pytools/graph.py b/pytools/graph.py index fa2a12f8..057be737 100644 --- a/pytools/graph.py +++ b/pytools/graph.py @@ -212,10 +212,10 @@ class CycleError(Exception): """ Raised when a topological ordering cannot be computed due to a cycle. - :attr node: Node in a directed graph that is part of a cycle. + :attr nodes: List of nodes in a directed graph that are part of a cycle. """ - def __init__(self, node: T) -> None: - self.node = node + def __init__(self, nodes: List[T]) -> None: + self.nodes = nodes class HeapEntry: @@ -299,8 +299,8 @@ def compute_topological_order(graph: Mapping[T, Collection[T]], if len(order) != total_num_nodes: # any node which has a predecessor left is a part of a cycle - raise CycleError(next(iter(n for n, num_preds in - nodes_to_num_predecessors.items() if num_preds != 0))) + raise CycleError(list(n for n, num_preds in + nodes_to_num_predecessors.items() if num_preds != 0)) return order diff --git a/test/test_graph_tools.py b/test/test_graph_tools.py index 701cd577..221bf254 100644 --- a/test/test_graph_tools.py +++ b/test/test_graph_tools.py @@ -76,6 +76,12 @@ def test_compute_topological_order(): with pytest.raises(CycleError): compute_topological_order(cycle) + cycle2 = {0: [2], 1: [2], 2: [3], 3: [1]} + with pytest.raises(CycleError) as exc: + compute_topological_order(cycle2) + + assert str(exc.value) == "[1, 2, 3]" + def test_transitive_closure(): from pytools.graph import compute_transitive_closure From bf707434f3750db98f3cb77afac9146818a9ce52 Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Thu, 17 Nov 2022 15:29:02 -0600 Subject: [PATCH 2/6] simplify test --- test/test_graph_tools.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/test_graph_tools.py b/test/test_graph_tools.py index 221bf254..7a662bd9 100644 --- a/test/test_graph_tools.py +++ b/test/test_graph_tools.py @@ -77,11 +77,9 @@ def test_compute_topological_order(): compute_topological_order(cycle) cycle2 = {0: [2], 1: [2], 2: [3], 3: [1]} - with pytest.raises(CycleError) as exc: + with pytest.raises(CycleError, match=r"\[1, 2, 3\]"): compute_topological_order(cycle2) - assert str(exc.value) == "[1, 2, 3]" - def test_transitive_closure(): from pytools.graph import compute_transitive_closure From c39afcaa55faed838ffc82ec205de1eaef91f4b3 Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Tue, 6 Dec 2022 11:22:51 -0600 Subject: [PATCH 3/6] maintain node attr --- pytools/graph.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pytools/graph.py b/pytools/graph.py index 057be737..ca34c69b 100644 --- a/pytools/graph.py +++ b/pytools/graph.py @@ -212,9 +212,12 @@ class CycleError(Exception): """ Raised when a topological ordering cannot be computed due to a cycle. + :attr node: Node in a directed graph that is part of a cycle. + :attr nodes: List of nodes in a directed graph that are part of a cycle. """ def __init__(self, nodes: List[T]) -> None: + self.node = nodes[0] self.nodes = nodes From 1462f7226bde362beb9509c474af0a4d2c539472 Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Tue, 6 Dec 2022 11:49:10 -0600 Subject: [PATCH 4/6] limit maximum print len --- pytools/graph.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pytools/graph.py b/pytools/graph.py index ca34c69b..5ddd880f 100644 --- a/pytools/graph.py +++ b/pytools/graph.py @@ -220,6 +220,11 @@ def __init__(self, nodes: List[T]) -> None: self.node = nodes[0] self.nodes = nodes + def __str__(self) -> str: + le = len(self.nodes) + mlen = 10 + return f"{[n for n in self.nodes[:mlen]] + (['...'] if le > mlen else [])}" + class HeapEntry: """ From 8823bcad13949b3a14f88c0fb495c9bd494e1101 Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Fri, 16 Dec 2022 18:52:16 +0100 Subject: [PATCH 5/6] add node property --- pytools/graph.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pytools/graph.py b/pytools/graph.py index 6225242c..52e72746 100644 --- a/pytools/graph.py +++ b/pytools/graph.py @@ -238,8 +238,7 @@ class CycleError(Exception): :attr nodes: List of nodes in a directed graph that are part of a cycle. """ - def __init__(self, nodes: List[T]) -> None: - self.node = nodes[0] + def __init__(self, nodes: List[NodeT]) -> None: self.nodes = nodes def __str__(self) -> str: @@ -247,6 +246,13 @@ def __str__(self) -> str: mlen = 10 return f"{[n for n in self.nodes[:mlen]] + (['...'] if le > mlen else [])}" + @property + def node(self) -> Hashable: + from warnings import warn + warn("CycleError.node is deprecated and will go away in 06/2023. " + "Use CycleError.nodes instead.", DeprecationWarning) + return self.nodes[0] + class HeapEntry: """ From 47aa3164f3a5e04c9f16afe3bf23fd9cde226275 Mon Sep 17 00:00:00 2001 From: Matthias Diener Date: Fri, 16 Dec 2022 19:01:51 +0100 Subject: [PATCH 6/6] prepare for multiple cycles --- pytools/graph.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pytools/graph.py b/pytools/graph.py index 52e72746..d0363f3f 100644 --- a/pytools/graph.py +++ b/pytools/graph.py @@ -234,24 +234,24 @@ class CycleError(Exception): """ Raised when a topological ordering cannot be computed due to a cycle. - :attr node: Node in a directed graph that is part of a cycle. - - :attr nodes: List of nodes in a directed graph that are part of a cycle. + :attr paths: A :class:`list` in which each element represents another + :class:`list` of nodes that form a cycle. In each cycle, + ``node[i+1]`` is a successor of ``node[i]``. """ - def __init__(self, nodes: List[NodeT]) -> None: - self.nodes = nodes + def __init__(self, paths: List[List[NodeT]]) -> None: + self.paths = paths def __str__(self) -> str: - le = len(self.nodes) + le = len(self.paths) mlen = 10 - return f"{[n for n in self.nodes[:mlen]] + (['...'] if le > mlen else [])}" + return f"{[n for n in self.paths[:mlen]] + (['...'] if le > mlen else [])}" @property def node(self) -> Hashable: from warnings import warn warn("CycleError.node is deprecated and will go away in 06/2023. " "Use CycleError.nodes instead.", DeprecationWarning) - return self.nodes[0] + return self.paths[0][0] class HeapEntry: @@ -331,8 +331,8 @@ def compute_topological_order(graph: GraphT[NodeT], if len(order) != total_num_nodes: # any node which has a predecessor left is a part of a cycle - raise CycleError(list(n for n, num_preds in - nodes_to_num_predecessors.items() if num_preds != 0)) + raise CycleError([list(n for n, num_preds in + nodes_to_num_predecessors.items() if num_preds != 0)]) return order