Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better cycle detection #167

Draft
wants to merge 18 commits into
base: main
Choose a base branch
from

Conversation

matthiasdiener
Copy link
Contributor

@matthiasdiener matthiasdiener commented Dec 8, 2022

Fixes #165

(This is super a bit clumsy, but I couldn't think of a better way to do this apart from rewriting the function to use DFS (for which I don't know if that would work with a keyfunc) ...)

@matthiasdiener
Copy link
Contributor Author

This is ready for a first look.

@matthiasdiener matthiasdiener marked this pull request as ready for review December 8, 2022 16:46
pytools/graph.py Outdated Show resolved Hide resolved
pytools/graph.py Show resolved Hide resolved
@matthiasdiener matthiasdiener force-pushed the better-cycle branch 3 times, most recently from cd96dc2 to 8a32515 Compare December 12, 2022 14:52
pytools/graph.py Outdated
"""
def dfs(node: NodeT, path: List[NodeT]) -> List[NodeT]:
# Cycle detected
if visited[node] == 1:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make 0, 1, 2 an enum?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think of a95c4c0?

:returns: A :class:`list` in which each element represents another :class:`list`
of nodes that form a cycle.
"""
def dfs(node: NodeT, path: List[NodeT]) -> List[NodeT]:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Constructing path just in case is wasteful IMO: The path could be collected as you return, if a cycle is found.

Copy link
Contributor Author

@matthiasdiener matthiasdiener Dec 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think of 96bd3a6 + 3640c88?

pytools/graph.py Outdated
Comment on lines 269 to 275
res = []

for node in graph:
if not visited[node]:
cycle = dfs(node, [])
if cycle:
res.append(cycle)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finding all cycles is probably too much... all the use cases I can think of will only want one. I could see this as an option, maybe.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made it an optional parameter in 2ff7def. My thinking was that for the use case I had in mind, checking the validity of our DAG in pytato, having all cycles instead of just one may be beneficial.

pytools/graph.py Outdated
@@ -240,6 +241,42 @@ def __init__(self, node: NodeT) -> None:
self.node = node


def find_cycles(graph: GraphT) -> List[List[NodeT]]:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could use this in contains_cycle.

Copy link
Contributor Author

@matthiasdiener matthiasdiener Dec 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 29775e6

pytools/graph.py Outdated
"""Compute a topological order of nodes in a directed graph.

:arg key: A custom key function may be supplied to determine the order in
break-even cases. Expects a function of one argument that is used to
extract a comparison key from each node of the *graph*.

:arg verbose_cycle: Verbose reporting in case *graph* contains a cycle.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Say what this means.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 18d241b

Comment on lines +366 to +368
raise CycleError(None)
else:
raise CycleError(cycles[0][0])
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add to the documentation of CycleError what the value might mean.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, the current doc of CycleError has :attr node: Node in a directed graph that is part of a cycle. - I'm not sure what else to add there.

Comment on lines +247 to +249
WHITE = 0 # Not visited yet
GREY = 1 # Currently visiting
BLACK = 2 # Done visiting
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use descriptive names for the node state?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you prefer, I can rename these, but I thought white/grey/black were standard labels in DFS (see e.g. http://www.cs.cmu.edu/afs/cs/academic/class/15750-s17/ScribeNotes/lecture9.pdf)

pytools/graph.py Outdated
@@ -240,6 +243,52 @@ def __init__(self, node: NodeT) -> None:
self.node = node


class NodeState(Enum):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
class NodeState(Enum):
class _NodeState(Enum):

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, done in 85a4a29.

@inducer
Copy link
Owner

inducer commented Dec 24, 2022

It seems that only one of #167 (this) and #159 should get merged. Which one would you like this to be?

@matthiasdiener
Copy link
Contributor Author

It seems that only one of #167 (this) and #159 should get merged. Which one would you like this to be?

I think these PRs are orthogonal to each other. This PR adds a new cycle detection function and uses it to correct the cycle reporting in compute_topological_order (it still only reports one node in a cycle there). #159 modifies the CycleError to contain all nodes part of cycles. Imho, #167 should be merged before #159.

@matthiasdiener matthiasdiener marked this pull request as draft December 25, 2022 10:00
@matthiasdiener
Copy link
Contributor Author

This does not work correctly in all cases, so I've set it back to draft now-

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

graph: Misleading reporting of node in cycle for CycleError
2 participants