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
PlanarEmbedding.remove_edge() now updates removed edge's neighbors #6798
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks very good! And fast too.. :)
Could you add a simple test to the tests/test_planarity.py
module that removes an edge or two and checks planarity -- similar to however you tracked down this issue.
Thanks!
Is that test what you had in mind? |
Thanks for that. It should go in the tests folder next to the I also now realize that this creates a difference in the treatment of Another question: what happens if removing that edge makes it no longer possible to have a planar graph? Yet another question: what about removing nodes? Is that not needed? As an aside, I think these features were not included because they led to headaches -- and a user could recreate the graph with the removed edges if needed. But that was obviously not documented nor enforced. |
Perhaps the |
I would rather not have I added a That way, only the addition of edges can lead to invalidating the embedding. If there is a need to make |
Is there any chance this PR will be merged? |
I'm afraid there is a lot more checking to do before this PR will be merged. You can tell from comments in the code that it was written in a fragile way. As long as you do "normal" things, it will be correct. But it doesn't protect you from yourself. Another example if that it subclasses Another example is that adding an edge to the graph could easily make it no longer planar. But we have no mechanism for tracking that. This PR will provide It'd be great to expand on this class to make it editable -- with checks for each edit to ensure that the attribute structure is maintained. Or make the class "frozen" as suggested above. Then people are expected to change the graph in its form as a standard networkx graph. Once changed they can go back to a planar embedding to check traits there. Both would help this code become less "fragile". But the Can you convince us that this change does not break the PlanarEmbedding data structure? Are you willing to take on making the class fully editable? Can you describe a use case where making these changes to the code and maintaining them is better for the users than having them use the networkx graph to make graph changes and then convert the new graph to a PlanarEmbedding to check planarity. How slow is that anyway? I think those are three things I would want to see for this. |
The representation The only structure beyond a DiGraph that a PlanarEmbedding contains is encoded in edge attributes
There is no need to worry about the splitting of a component by an edge removal: the planarity code is prepared to deal with multiple components.
I can include the blocking of
I have the impression that implementing PlanarEmbedding as a nx.Graph would be less of a hassle, but I haven't looked deep enough into all the details. Any valid embedding will have both edges anyway. It would just be a matter of keying
I'm proposing making it editable only for removals. I believe the PR enables removals in a non-breaking way, which is an improvement over the current status. For additions, it would require implementing a local planarity test (I can take a shot at it, but I am not sure how complicated that is). For the time being, additions would throw an exception.
I hope the above was convincing enough.
I'd have to investigate a bit further for that (and whether the use of nx.Graph would be an improvement).
I cannot say what is better for the users, but I'm using my PR in a project and find it useful. I even add edges (which I know that will maintain the planarity). I'm working with Euclidean Delaunay triangulations, but I need to make small adjustments to them as a spanning tree is built over the nodes. It would impact negatively my algorithm if I were to recreate a PlanarEmbedding after each change. The current state is buggy, so I think this PR is an improvement, but I agree those changes add to the maintenance burden.
Not sure, but it's not a trivial procedure. |
We talked about this in a community meeting recently and we are OK with having remove_edge/node added to the class, but we'd really like it if the other morphing methods would raise exceptions. We are worried that people will see that they can remove and edge and then try to add an edge. Could you add methods for the following that simply raise an exception stating that this is not allowed for a PlanarEmbedding? The methods are (obtained from the function frozen which turns off all of these).
If that's too much for now let us know and we'll find a way to get it in. [Edited: if we then in the future want to make one of these work, we still can. But it completes this improvement in PlanarEmbedding] |
I had to override those above by overriding the base class' As for these:
Addition of nodes do not pose a risk of invalidating the PlanarEmbedding and are required by Let me know if there is anything missing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
This looks ready to me -- but one last pass through lead me to two requests.
- use
nx.NetworkXError
rather thanNetworkXError
in the remove methods. - add simple tests of the forbidden functions.
(see below)
…e _cw and _ccw varieties, plus update the planar_drawing and tests accordingly (planar_drawing.py, planarity.py, test_planarity.py)
…ed comments (planarity.py)
Co-authored-by: Dan Schult <dschult@colgate.edu>
…test for `add_half_edge()` (planarity.py, test_planarity.py)
…ising method call (test_planarity.py)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm unfortunately this is very difficult to review due to something going very wrong with your fork - when I try to check out your branch it looks like a ton of stuff has been deleted, including the pyproject.toml which makes it impossible to install.
I'm not sure exactly what happened, but if there's a commit pre-rebase attempts that still has all of the proposed changes it might be worth trying to roll back to that!
networkx/algorithms/planarity.py
Outdated
self.add_edge = self.__forbidden | ||
self.add_edges_from = self.__forbidden | ||
self.add_weighted_edges_from = self.__forbidden | ||
|
||
def __forbidden(self, *args, **kwargs): | ||
"""Forbidden operation | ||
|
||
Any edge additions to a PlanarEmbedding should be done using | ||
method `add_half_edge`. | ||
""" | ||
raise nx.NetworkXError( | ||
"Use `add_half_edge` method to add edges to a PlanarEmbedding." | ||
) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I may be missing something here, but IMO it'd be more idiomatic to override these methods directly instead of assigning them to a hidden/forbidden method, e.g.
def add_edge(self, *args, **kwargs):
raise NotImplemented("Use `add_half_edge` to add edges to a PlanarEmbedding")
# same for the other methods
I'd also vote for using NotImplemented
instead of NetworkXError
, which I think fits slightly better and will automatically resolve the "tests passing for the wrong reason" issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for reviewing, @rossbar
I rebased again this branch and now I think I stacked on top of PR #7202 correctly.
I may be missing something here, but IMO it'd be more idiomatic to override these methods directly instead of assigning them to a hidden/forbidden method, e.g.
The constructor of DiGraph (PlanarEmbedding's parent class) uses self.add_edge*()
. This happens via
delegation of incoming_graph_data to functions in convert.py
. If I override add_edge*()
in the class definition, those functions will fail. I avoided that by extending DiGraph's constructor and overriding add_edge*()
after incoming_graph_data is processed.
I think that in order to use the idiomatic approach you suggested, the PlanarEmbedding's constructor would have to do something like that if it receives graph data:
# not exactly what we need, since it returns a new object
LRPlanarity(nx.Graph(incoming_graph_data)).lr_planarity()
Though this would make it very hard to create invalid embeddings for the test instances.
I'd also vote for using
NotImplemented
instead ofNetworkXError
, which I think fits slightly better and will automatically resolve the "tests passing for the wrong reason" issue.
The "tests passing for the wrong reason" issue was already solved by only using the context pytest.raises()
for the actual functionality under test.
Let me know if the difficulties with checking this PR out remains.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, good point - thanks for the explanation, that makes sense. It sounds like there's opportunity for a deeper refactor here, but I'm inclined not to let that be a blocker for this PR!
The "tests passing for the wrong reason" issue was already solved by only using the context pytest.raises() for the actual functionality under test.
True, though I still feel like NotImplemented
is a better-fitting exception, as this has to do with methods on the Python object we're forbidding, rather than anything dealing with graphs (in the abstract sense), where a NetworkX-flavored exception would be expected. This is very subjective though and also not a blocker!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've changed the exception raised by the forbidden methods to NotImplementedError
(tests updated accordingly).
…od from super() (planarity.py)
…led (planarity.py)
…to NotImplementedError (planarity.py, test_planarity.py)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for all of this! I think separating into two PRs really helped get it across the line.
I approve this PR (and have one suggestion below that doesn't matter except for cleanliness)
Yay!
with pytest.raises(nx.NetworkXException): | ||
embedding.traverse_face(1, 2) | ||
|
||
def test_forbidden_methods(self): | ||
embedding = nx.PlanarEmbedding() | ||
# embedding = nx.Graph([(0, 1), (1, 2), (3, 4), (4, 5)]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we remove this comment that doesn't seem to be relevant anymore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This LGTM, thanks again @mdealencar for all the continued work on the PlanarEmbedding!
Implements edge and node removal methods for the PlanarEmbedding class, with all of the additional checking required to ensure edge attributes are updated properly. Also explicitly disallows the methods for adding edges, which may invalidate the planarity Co-authored-by: mdealencar <mdealencar@users.noreply.github.com> Co-authored-by: Dan Schult <dschult@colgate.edu> Co-authored-by: mdealencar <DTUnote+mdealencar@users.noreply.github.com>
Removing an edge in a
PlanarEmbedding
requires updating the data dict ('cw'
and'ccw'
values) of neighboring edges on both ends. This PR implements this updating.closes #6796