Skip to content

get_embedding(): remove vertices that do not exist #40057

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

Merged
merged 4 commits into from
May 18, 2025

Conversation

Ordoviz
Copy link
Contributor

@Ordoviz Ordoviz commented May 6, 2025

A graph embedding is a Python dictionary d where d[v] is the list of neighbors of vertex v. When vertices get deleted, the embedding is not updated so get_embedding() needs to remove all vertices in d[v] that no longer exist. This commit fixes #40054 where not enough vertices in d[v] were removed.

This resulted in LookupError being thrown by get_embedding() if the graph is immutable.

📝 Checklist

  • I have linked a relevant issue or discussion.
  • I have created tests covering the changes.
  • I have updated the documentation and checked the documentation preview.

@Ordoviz Ordoviz changed the title get_embedding(): remove vertices that do no exist get_embedding(): remove vertices that do not exist May 6, 2025
Copy link

github-actions bot commented May 6, 2025

Documentation preview for this PR (built with commit 2bc85db; changes) is ready! 🎉
This preview will update shortly after each push to this PR.

Copy link
Contributor

@dcoudert dcoudert left a comment

Choose a reason for hiding this comment

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

this looks ok, but why not fixing method subgraph which tries to update the embedding ?

@Ordoviz Ordoviz marked this pull request as draft May 6, 2025 17:35
@Ordoviz
Copy link
Contributor Author

Ordoviz commented May 6, 2025

why not fixing method subgraph which tries to update the embedding ?

Good point. There appears to be a typo in generic_graph.py:14482. It currently reads:

G._embedding = {u: [v for v in embedding[u] if u in G] for u in G}
                                            ^^^^^^^^^

but it should be

G._embedding = {u: [v for v in embedding[u] if v in G] for u in G}
                                            ^^^^^^^^^

Fixing this typo would be another way to fix #40054 and it would make this PR superfluous. But following the spirit of #33760, wouldn't it be better in terms of performance and code maintainability to remove this code from _subgraph_by_adding() and keep the embedding untouched until get_embedding() is called? Then the embedding would only need up to be updated in a single place (in get_embedding). After all, the only way to get a certified legal embedding is via get_embedding() (the underscore in front of _embedding indicates this attribute is private) so all modifications to the embedding can be lazily postponed.

@dcoudert
Copy link
Contributor

dcoudert commented May 7, 2025

I agree with you that the code could be removed from subgraph (and may be other methods) and that method get_embedding should be responsible for returning the correct embedding.
One should ensure that the embedding is always accessed via method get_embedding and not directly via the private variable, even for internal use. This should be easy to check / fix.

@Ordoviz Ordoviz marked this pull request as ready for review May 11, 2025 14:19
@dcoudert
Copy link
Contributor

the CIs report several failing doctests. Please check

Ordoviz added 4 commits May 11, 2025 21:26
An embedding is a Python dictionary d where d[v] is the list of neighbors
of vertex v. When vertices get deleted, the embedding is not updated
immediately so get_embedding() needs to remove all vertices in d[v] that
no longer exist. This commit fixes sagemath#40054 where not enough vertices in
d[v] were removed.
subgraph() can be lazy because get_embedding() will remove
the old vertices of the supergraph from the embedding.
This generates a nicer error if the caller provided an invalid embedding.
genus() call this function with `embedding=self._embedding` but
`self._embedding` might no longer be valid after subgraph operations.
@Ordoviz
Copy link
Contributor Author

Ordoviz commented May 11, 2025

The CI was failing because I made a silly mistake. I force-pushed a new version of 44c58a3 where the embedding is preserved by subgraph and relabel (instead of being deleted like before).

@Ordoviz
Copy link
Contributor Author

Ordoviz commented May 11, 2025

The failing check Conda (ubuntu, Python 3.12, all, editable) is unrelated to this PR, see #40087.

Copy link
Contributor

@dcoudert dcoudert left a comment

Choose a reason for hiding this comment

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

Have you also check that the behavior is ok when adding a vertex to the graph ?

@Ordoviz
Copy link
Contributor Author

Ordoviz commented May 12, 2025

Have you also check that the behavior is ok when adding a vertex to the graph ?

The behavior did not change. When a vertex is added, the old embedding becomes invalid, so get_embedding() returns None. The following program still exits successfully:

emb = {0: [1], 1: [0]}
for ds in ("dense", "sparse"):
    G = Graph("A_", data_structure=ds)
    G.set_embedding(emb)
    G.add_vertex()
    assert G._embedding == emb
    assert G._check_embedding_validity() == False
    assert G.get_embedding() is None

Copy link
Contributor

@dcoudert dcoudert left a comment

Choose a reason for hiding this comment

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

LGTM.

@vbraun vbraun merged commit 9bc29f1 into sagemath:develop May 18, 2025
12 of 20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Calling get_embedding() on subgraph of immutable graph results in LookupError
3 participants