Skip to content

Module and Graph method for Projective planarity via forbidden minors #39802

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

Open
wants to merge 40 commits into
base: develop
Choose a base branch
from

Conversation

juan-mlr
Copy link

Addresses #37937 (to enable checking if a graph in projective planar) by

  1. creating a new module, src/sage/graphs/projective_planarity.py, that holds the relevant forbidden minors and a function to search for them in another graph.
  2. adding is_projective_planar method to Graph.

📝 Checklist

  • The title is concise and informative.
  • The description explains in detail what this PR is about.
  • 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.

⌛ Dependencies

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.

you will have to declare the new file in src/sage/graphs/meson.build and in src/doc/en/reference/graphs/index.rst

@@ -9235,6 +9235,60 @@ def bipartite_double(self, extended=False):
G.name("%sBipartite Double of %s" % (prefix, self.name()))
return G

@doc_index("Graph properties")
def is_projective_planar(self, minor_map=False, **minor_kwargs):
Copy link
Contributor

Choose a reason for hiding this comment

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

this method could be defined in file projective_planarity.py and then imported in Graph.

Copy link

@steveschluchter steveschluchter Apr 9, 2025

Choose a reason for hiding this comment

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

@dcoudert Do you mean that we should define a wrapper of some sort in the Graphs class that uses the method is_projective_planar?

Copy link
Contributor

@dcoudert dcoudert Apr 9, 2025

Choose a reason for hiding this comment

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

To see how methods are imported in Graph and declared in the documentation, check file graph.py from line 9240 to 9354

p2_forbidden_minor = get_p2_forbidden_minor(self, **minor_kwargs)
if minor_map:
return p2_forbidden_minor
else:
Copy link
Contributor

Choose a reason for hiding this comment

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

the else is not needed here.

@@ -0,0 +1,416 @@
r"""
Minimal forbidden minors for projective plane and function for finding them in G
Copy link
Contributor

Choose a reason for hiding this comment

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

This module should be names Projective planarity. Then you have text to explain what this is about.

Copy link

@steveschluchter steveschluchter Apr 9, 2025

Choose a reason for hiding this comment

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

@dcoudert Can you elaborate on this comment? We don't understand what you're trying to say.

Copy link
Contributor

Choose a reason for hiding this comment

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

The first line will be displayed in the documentation as the title of the module. Mu suggèstion is to change to

r"""
Projective planarity

This module contains the 35 minimal forbidden minors for projective plane
and a function for checking if a graph `G` has one of them as a minor.

# https://www.gnu.org/licenses/
# ****************************************************************************

from copy import deepcopy
Copy link
Contributor

Choose a reason for hiding this comment

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

I don"t think you need it.

def get_p2_forbidden_minor(G, **minor_kwargs):
"""
Check if one of the minimal forbidden minors of the projective plane is an
induced minor of G.
Copy link
Contributor

Choose a reason for hiding this comment

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

G -> ``G``


OUTPUT:

Return :meth:`~Graph.minor` output if an element of P2_FORBIDDEN_MINORS is
Copy link
Contributor

Choose a reason for hiding this comment

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

P2_FORBIDDEN_MINORS -> P2_FORBIDDEN_MINORS


EXAMPLES:

#. The Peterson graph is a known projective planar graph so it doesn't have
Copy link
Contributor

Choose a reason for hiding this comment

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

why adding #. ?


for forbidden_minor in P2_FORBIDDEN_MINORS:
# Can't be a minor if it has more vertices or edges than G
if (
Copy link
Contributor

Choose a reason for hiding this comment

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

        if (forbidden_minor.num_verts() > num_verts_G
                or forbidden_minor.num_edges() > num_edges_G):

):
continue

try:
Copy link
Contributor

Choose a reason for hiding this comment

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

why using a try...except statement ?

Choose a reason for hiding this comment

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

G.minor throws a ValueError if a minor is not found

Copy link

github-actions bot commented Mar 29, 2025

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

@steveschluchter
Copy link

@dcoudert can you comment on how we should declare this file in meson.build? It's just a python file with no cython or c to go with it.

you will have to declare the new file in src/sage/graphs/meson.build and in src/doc/en/reference/graphs/index.rst

@dcoudert
Copy link
Contributor

dcoudert commented Apr 9, 2025

@dcoudert can you comment on how we should declare this file in meson.build? It's just a python file with no cython or c to go with it.

you will have to declare the new file in src/sage/graphs/meson.build and in src/doc/en/reference/graphs/index.rst

open file src/sage/graphs/meson.build and add the name of the file in the block py.install_sources(...)

@steveschluchter
Copy link

@dcoudert

Can you help us avoid the circular import problem?

These lines appear in projective_planarity.py, and we're trying to keep the bulk of the code for the projective planarity check in a separate file.

from sage.graphs.graph import Graph
from sage.graphs.generators.basic import (
CompleteGraph,
CompleteBipartiteGraph,
CompleteMultipartiteGraph,
CycleGraph
)

We could also presumably put the code in the graph.py file, but that's presumably not the way you want it done.

@@ -9236,13 +9238,10 @@ def bipartite_double(self, extended=False):
return G

@doc_index("Graph properties")
def is_projective_planar(self, minor_map=False, **minor_kwargs):
def is_projective_planar(self, minor_map=False):
Copy link
Contributor

Choose a reason for hiding this comment

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

parameter minor_map should be renamed return_map. Currently, you mix the input parameter with the internal variable minor_map storing the result of the call to self.minor(..).

r"""
Check whether this graph is projective planar.

Wraps :func:`get_p2_forbidden_minor
Copy link
Contributor

Choose a reason for hiding this comment

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

add a few lines to explain what it means to be projective planar.

num_edges_G = self.num_edges()
minor_map = None

for forbidden_minor_string in P2_FORBIDDEN_MINORS:
Copy link
Contributor

Choose a reason for hiding this comment

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

a better form:

        for forbidden_minor_string in P2_FORBIDDEN_MINORS:
            # Can't be a minor if it has more vertices or edges than G
            forbidden_minor = Graph(forbidden_minor_string)
            if (forbidden_minor.num_verts() > num_verts_G
                    or forbidden_minor.num_edges() > num_edges_G):
                continue

            try:
                minor_map = self.minor(forbidden_minor)
                if minor_map is not None:
                    break

                # If self has no H minor, then self.minor(H) throws a ValueError
            except ValueError:
                continue

        if return_map:
            return minor_map
        return minor_map is None

@@ -0,0 +1,62 @@
r"""
p2_forbidden_minors
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the need of this file ? why not simply adding the list to method is_projective_planar ?

Do you have any reference with the proof that this list is the correct list of minors ?

Copy link

@steveschluchter steveschluchter Apr 28, 2025

Choose a reason for hiding this comment

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

@dcoudert

Thank you for your helpful comments!

The short answer is yes, we do have a reference.
The book we used is this one.
B. Mohar and C. Thomassen, Graphs on Surfaces, The Johns Hopkins University Press, 2001.
Theorem 6.5.1, pages 197-198.

The longer answer is that we used the existing sage codebase to hard coded the graphs in that theorem using and then put them in graph6 format.

How would you like us to say this in our code?

We thought it might be helpful to leave the list of excluded minors in a separate file in import them. In the original hard coded form (not using graph6), it's a lot more code. Please tell us what you'd like us to do: shall we leave them in graph6 format, or leave them in a longer hardcoded format that uses the existing sage codebase.

Copy link
Contributor

Choose a reason for hiding this comment

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

We have for instance method graphs.petersen_family that returns a collection of 7 graphs which are the forbidden minors of the linklessly embeddable graphs.
See: https://doc.sagemath.org/html/en/reference/graphs/sage/graphs/generators/families.html#sage.graphs.generators.families.petersen_family
and file src/sage/graphs/generators/families.py

A similar method could be added for the p2 forbidden minors. This would allow the graphs to be accessed for different purposes. The method could simply contain the list of graph6 strings and produce them as graphs. If the graphs have names, these can be specified. There is no need to specify the embedding of each graph.

Copy link
Contributor

Choose a reason for hiding this comment

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

The reference can be added to file src/doc/en/reference/references/index.rst. Then you can refer to it using [MT2001]_ (assuming that the key if MT2001.

True

`K_{4,4}` has a projective plane crossing number of 2. One of the
minimal forbidden minors is `K_{4,4} - e`, so we get a one-to-one
Copy link
Contributor

Choose a reason for hiding this comment

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

the alignment is incorrect.

-        `K_{4,4}` has a projective plane crossing number of 2. One of the
-           minimal forbidden minors is `K_{4,4} - e`, so we get a one-to-one
-           dictionary from :meth:`~Graph.minor`::
+        `K_{4,4}` has a projective plane crossing number of 2. One of the
+        minimal forbidden minors is `K_{4,4} - e`, so we get a one-to-one
+        dictionary from :meth:`~Graph.minor`::


TESTS::

sage: len(graphs. p2_forbidden_minors())
Copy link
Contributor

Choose a reason for hiding this comment

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

no space between . and p2_forbidden_minors

@dcoudert
Copy link
Contributor

@dimpase, can you try again to trigger CI runs ?

@dimpase
Copy link
Member

dimpase commented May 31, 2025

I cannot - @roed or @saraedum can, but they are busy, and on the other hand reluctant to give such rights to e.g. me or you.

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.

Please be more careful. Currently doctests are failing, we have lint errors, etc.
Here are all the issues that remains to be fixed in the code:

diff --git a/src/sage/graphs/generators/families.py b/src/sage/graphs/generators/families.py
index 69a17f182dc..2cd519d6762 100644
--- a/src/sage/graphs/generators/families.py
+++ b/src/sage/graphs/generators/families.py
@@ -3132,6 +3132,10 @@ def p2_forbidden_minors():
 
     We return the graphs using the Sage Graph constructor.
 
+    TESTS::
+
+        sage: len(graphs. p2_forbidden_minors())
+        35
     """
 
     p2_forbidden_minors_graph6 = [
diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py
index 14b253e1e31..602d5a1fe91 100644
--- a/src/sage/graphs/graph.py
+++ b/src/sage/graphs/graph.py
@@ -9522,7 +9522,7 @@ class Graph(GenericGraph):
         plane.  The approach is to check that the graph does not contain any
         of the known forbidden minors.
 
-        INPUT
+        INPUT:
 
         - ``return_map`` -- boolean (default: ``False``); whether to return
           a map indicating one of the forbidden graph minors if in fact the
@@ -9540,32 +9540,26 @@ class Graph(GenericGraph):
         The Peterson graph is a known projective planar graph::
 
             sage: P = graphs.PetersenGraph()
-            sage: P.is_projective_planar()
+            sage: P.is_projective_planar()  # long time
             True
 
         `K_{4,4}` has a projective plane crossing number of 2. One of the
-         minimal forbidden minors is `K_{4,4} - e`, so we get a one-to-one
-         dictionary from :meth:`~Graph.minor`::
+        minimal forbidden minors is `K_{4,4} - e`, so we get a one-to-one
+        mapping from :meth:`~Graph.minor`::
 
             sage: K44 = graphs.CompleteBipartiteGraph(4, 4)
-            sage: minor_map = K44.is_projective_planar()
-            sage: minor_map
-            {0: [0], 1: [1], 2: [2], 3: [3], 4: [4], 5: [5], 6: [6], 7: [7]}
+            sage: K44.is_projective_planar(return_map=True)
+            (False,
+             {0: [0], 1: [1], 2: [2], 3: [3], 4: [4], 5: [5], 6: [6], 7: [7]})
 
         .. SEEALSO::
 
             - :meth:`~Graph.minor`
 
-        TESTS::
-
-        sage: len(graphs.p2_forbidden_minors())
-        35
         """
-
         from sage.graphs.generators.families import p2_forbidden_minors
         num_verts_G = self.num_verts()
         num_edges_G = self.num_edges()
-        minor_map = None
 
         for forbidden_minor in p2_forbidden_minors():
             # Can't be a minor if it has more vertices or edges than G
@@ -9579,8 +9573,7 @@ class Graph(GenericGraph):
                 if minor_map is not None:
                     if return_map:
                         return False, minor_map
-                    else:
-                        return False
+                    return False
 
             # If G has no H minor, then G.minor(H) throws a ValueError

plane. The approach is to check that the graph does not contain any
of the known forbidden minors.

INPUT
Copy link
Contributor

Choose a reason for hiding this comment

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

you are missing : after INPUT. It should be INPUT:

@dcoudert
Copy link
Contributor

dcoudert commented Jun 1, 2025

My main concern is that method is_projective_planar is extremely slow (that's why we must add a # long time tag to a doctest). Searching for minors is a difficult problem. Do you know any (faster) combinatorial algorithm ?

sage: P = graphs.PetersenGraph()
sage: %time P.is_projective_planar()
CPU times: user 2min 51s, sys: 642 ms, total: 2min 52s
Wall time: 2min 52s
True
sage: K44 = graphs.CompleteBipartiteGraph(4, 4)
sage: %time K44.is_projective_planar(return_map=True)
CPU times: user 2.12 s, sys: 11.5 ms, total: 2.13 s
Wall time: 2.13 s
(False, {0: [0], 1: [1], 2: [2], 3: [3], 4: [4], 5: [5], 6: [6], 7: [7]})

@dimpase
Copy link
Member

dimpase commented Jun 1, 2025

can this be computed via combinatorial embeddings?

@steveschluchter
Copy link

@dcoudert @dimpase

I don't know of any other implemented projective planarity algorithm.

This work is related to a research project that @juan-mlr and I are doing, which is a continuation of https://combinatorialpress.com/article/jcmcc/Some-Excluded-Minors-for-the-Spindle-Surface.pdf for the projective plane.

Thank you very much for your thoughtfulness.

@dimpase
Copy link
Member

dimpase commented Jun 1, 2025

https://doc.sagemath.org/html/en/reference/graphs/sage/graphs/genus.html lets you compute the (oriented) genus of a graph, and it's reasonably fast on small graphs.
If I understand this well, it's helpful in deciding the non-oriented genus, as it gives you Euler characteristic.

@dcoudert
Copy link
Contributor

dcoudert commented Jun 2, 2025

There is also this paper by Mohar https://doi.org/10.1006/jagm.1993.1050 https://www.sfu.ca/~mohar/Reprints/1993/BM93_JA15_Mohar_ProjectivePlanarity.pdf
However, it might not be easy to implement.

Let us finish this PR. We can let the implementation of a faster method in the TODO list.

@steveschluchter
Copy link

Is there anything else we need to do? @dcoudert

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.

minor details. Then it should be ready.


- :meth:`~Graph.minor`

TESTS::
Copy link
Contributor

Choose a reason for hiding this comment

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

Add a similar test block in method graphs.p2_forbidden_minors


OUTPUT:

Return True if the graph is projective planar and False if not. If the
Copy link
Contributor

Choose a reason for hiding this comment

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

correct form so that the documentation can be build properly and display well

        Return ``True`` if the graph is projective planar and ``False`` if not.  If the
        parameter ``map_flag`` is ``True`` and the graph is not projective planar, then
        the method returns ``False`` and a map from :meth:`~Graph.minor`
        indicating one of the forbidden graph minors.

Choose a reason for hiding this comment

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

Thank you @dcoudert. We'll get it done.

Choose a reason for hiding this comment

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

Thank you again @dcoudert . We have acted on your comments.

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.

I hope one will add a faster method in the future. This one is extremely slow.

@steveschluchter
Copy link

@dcoudert thank you for staying with me on this PR through each commit. I'd like to see the Mojar paper get implemented. Perhaps I'll have time in the future. That sounds like a nice challenge.

@vbraun
Copy link
Member

vbraun commented Jun 20, 2025

sage -t --warn-long 30.0 --random-seed=123 src/sage/graphs/generators/families.py  # 2 doctests failed

@dcoudert
Copy link
Contributor

For some reason, the code of method p2_forbidden_minors is inside de code of method petersen_family. Indeed, method DeltaYTrans and YDeltaTrans are sub-methods of petersen_family and are followed by some code of that method.
Please move the code of method p2_forbidden_minors at a correct place, i.e., outside.

Sorry Volker for not seing that before. Not easy to work without CIs.

@steveschluchter
Copy link

I'm sorry about the mistake. I should have caught that one. I've made another push. Thank you for your comments and support.

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.

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.

5 participants