From 5ad365f7797d383e3eee7fd9bdccceb94a9027c3 Mon Sep 17 00:00:00 2001 From: john-boyer-phd Date: Fri, 2 Oct 2026 13:23:37 -0700 Subject: [PATCH 01/15] Surface new constants from EAPS 5.1.0.0 --- planarity/full/cgraphLib.pxd | 7 ++++++- planarity/full/graphLib.pyx | 9 +++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/planarity/full/cgraphLib.pxd b/planarity/full/cgraphLib.pxd index 106ffe4..bd68f1b 100644 --- a/planarity/full/cgraphLib.pxd +++ b/planarity/full/cgraphLib.pxd @@ -72,6 +72,8 @@ cdef extern from "../c/graphLib/graph.h": int gp_RestoreVertices(graphP theGraph) int gp_GetGraphFlags(graphP theGraph) + + int GRAPHFLAGS_DIRECTEDEDGEDETECTED int gp_GetFirstEdge(graphP theGraph, int v) int gp_GetLastEdge(graphP theGraph, int v) @@ -226,7 +228,10 @@ cdef extern from "../c/graphLib/graphDFSUtils.h": int gp_ExtendWith_DFSUtils(graphP theGraph) int gp_Detach_DFSUtils(graphP theGraph) - int GRAPHFLAGS_EXTENDEDWITH_DFSUTILS, GRAPHFLAGS_DFSNUMBERED, GRAPHFLAGS_SORTEDBYDFI + int GRAPHFLAGS_EXTENDEDWITH_DFSUTILS + int GRAPHFLAGS_DFSNUMBERED, GRAPHFLAGS_SORTEDBYDFI, GRAPHFLAGS_LOWPOINTSCOMPUTED + int DFSMODE_UNDIRECTED, DFSMODE_DIRECTED + int GRAPHFLAGS_DFSNUMBERED_DIRECTED int gp_DepthFirstSearch(graphP theGraph) int gp_SortVertices(graphP theGraph) diff --git a/planarity/full/graphLib.pyx b/planarity/full/graphLib.pyx index dafdce3..5fd810c 100644 --- a/planarity/full/graphLib.pyx +++ b/planarity/full/graphLib.pyx @@ -28,6 +28,8 @@ FALSE = cgraphLib.FALSE NIL = cgraphLib.NIL # Surfaced from planarity/c/graphLib/graph.h +GRAPHFLAGS_DIRECTEDEDGEDETECTED = cgraphLib.GRAPHFLAGS_DIRECTEDEDGEDETECTED + VERTEX_VISITED_MASK = cgraphLib.VERTEX_VISITED_MASK VERTEX_MARKED_MASK = cgraphLib.VERTEX_MARKED_MASK @@ -63,8 +65,15 @@ GRAPHFLAGS_ZEROBASEDIO = cgraphLib.GRAPHFLAGS_ZEROBASEDIO DFSUTILS_NAME = cgraphLib.DFSUTILS_NAME GRAPHFLAGS_EXTENDEDWITH_DFSUTILS = cgraphLib.GRAPHFLAGS_EXTENDEDWITH_DFSUTILS + GRAPHFLAGS_DFSNUMBERED = cgraphLib.GRAPHFLAGS_DFSNUMBERED GRAPHFLAGS_SORTEDBYDFI = cgraphLib.GRAPHFLAGS_SORTEDBYDFI +GRAPHFLAGS_LOWPOINTSCOMPUTED = cgraphLib.GRAPHFLAGS_LOWPOINTSCOMPUTED + +DFSMODE_UNDIRECTED = cgraphLib.DFSMODE_UNDIRECTED +DFSMODE_DIRECTED = cgraphLib.DFSMODE_DIRECTED + +GRAPHFLAGS_DFSNUMBERED_DIRECTED = cgraphLib.GRAPHFLAGS_DFSNUMBERED_DIRECTED # Surfaced from planarity/c/graphLib/planarityRelated/graphPlanarity.h PLANARITY_NAME = cgraphLib.PLANARITY_NAME From c97d85ba54e7cf004256a2b114a729c6b14beb7d Mon Sep 17 00:00:00 2001 From: john-boyer-phd Date: Fri, 2 Oct 2026 15:40:42 -0700 Subject: [PATCH 02/15] Surface gp_DepthFirstSearchEx() and update docstrings of related methods --- planarity/full/cgraphLib.pxd | 1 + planarity/full/graph.pyx | 53 ++++++++++++++++++++++++++++++------ planarity/full/graphLib.pxd | 1 + planarity/full/graphLib.pyx | 4 +++ 4 files changed, 50 insertions(+), 9 deletions(-) diff --git a/planarity/full/cgraphLib.pxd b/planarity/full/cgraphLib.pxd index bd68f1b..2393da6 100644 --- a/planarity/full/cgraphLib.pxd +++ b/planarity/full/cgraphLib.pxd @@ -234,6 +234,7 @@ cdef extern from "../c/graphLib/graphDFSUtils.h": int GRAPHFLAGS_DFSNUMBERED_DIRECTED int gp_DepthFirstSearch(graphP theGraph) + int gp_DepthFirstSearchEx(graphP theGraph, unsigned mode) int gp_SortVertices(graphP theGraph) int gp_ComputeLowpoints(graphP theGraph) int gp_ComputeLeastAncestors(graphP theGraph) diff --git a/planarity/full/graph.pyx b/planarity/full/graph.pyx index 8d71767..2c2e9ac 100644 --- a/planarity/full/graph.pyx +++ b/planarity/full/graph.pyx @@ -1925,14 +1925,16 @@ cdef class Graph: ) def gp_DepthFirstSearch(self) -> None: - """Performs a depth-first search (DFS) on the graph. + """Performs an undirected depth-first search (DFS) on the graph. Gives vertices a value for their depth first indexes (DFIs) and DFS parents, and gives edges a value for their type. See ``gp_GetParent()``, ``gp_GetIndex()``, and ``gp_GetEdgeType()``. - This method also sets ``GRAPHFLAGS_DFSNUMBERED``. This method performs - ``gp_ExtendWith_DFSUtils()`` if not already done. + This method sets ``GRAPHFLAGS_DFSNUMBERED``. This method also begins by + performing ``gp_ExtendWith_DFSUtils()``, if not already done. + + This method clears ``GRAPHFLAGS_DFSNUMBERED_DIRECTED``. Raises: RuntimeError: if the C-layer ``graphLib`` version of this function @@ -1943,16 +1945,48 @@ cdef class Graph: "gp_DepthFirstSearch() failed: unable to perform DFS on graph." ) + def gp_DepthFirstSearchEx(self, unsigned mode) -> None: + """Performs a depth-first search (DFS) on the graph, based on the ``mode``. + + Gives vertices a value for their depth-first indexes (DFIs) and DFS + parents, and gives edges a value for their type. See ``gp_GetParent()``, + ``gp_GetIndex()``, and ``gp_GetEdgeType()``. The settings given depend + on whether an undirected or directed depth-first search was performed. + Upon completion this method sets either ``GRAPHFLAGS_DFSNUMBERED`` or + ``GRAPHFLAGS_DFSNUMBERED_DIRECTED``, depending on the ``mode``. The + undirected DFS is performed by ``gp_DepthFirstSearch()``. + + This method also begins by performing ``gp_ExtendWith_DFSUtils()``, + if not already done. + + Args: + mode: ``DFSMODE_DIRECTED`` or ``DFSMODE_UNDIRECTED``. + + Raises: + RuntimeError: if the C-layer ``graphLib`` version of this function + fails. + """ + if graphLib.gp_DepthFirstSearchEx(self._theGraph, mode) != OK: + raise RuntimeError( + "gp_DepthFirstSearchEx() failed." + ) + def gp_SortVertices(self) -> None: """Sorts the vertices in ascending order according to their DFIs. - This method invokes ``gp_DepthFirstSearch()``, if it has not already - been done. This method sets ``GRAPHFLAGS_SORTEDBYDFI``. A second - invocation of this method restores vertices to their original order and - clears ``GRAPHFLAGS_SORTEDBYDFI``. When ``GRAPHFLAGS_SORTEDBYDFI`` is - set, the index values of all vertices are changed from their DFIs to - their original index positions in vertex storage. + This method rearranges the vertices according to their depth-first + index values, in linear time. The DFIs are set based on a prior call + of ``gp_DepthFirstSearch()`` or ``gp_DepthFirstSearchEx()``, which + set ``GRAPHFLAGS_DFSNUMBERED`` or ``GRAPHFLAGS_DFSNUMBERED_DIRECTED``. + If neither graph flag is set, then this method will begin by invoking + ``gp_DepthFirstSearch()``. + This method sets ``GRAPHFLAGS_SORTEDBYDFI`` and replaces the DFIs + in the index fields of vertices with their original array index + positions prior to sorting. A second invocation of this method + restores vertices to their original order, restores the DFI values + into the vertex index fields, and clears ``GRAPHFLAGS_SORTEDBYDFI``. + Raises: RuntimeError: if the C-layer ``graphLib`` version of this function fails. @@ -1967,6 +2001,7 @@ cdef class Graph: This method first performs ``gp_DepthFirstSearch()`` and ``gp_SortVertices()`` if they have not already been done. + This method sets GRAPHFLAGS_LOWPOINTSCOMPUTED. Raises: RuntimeError: if the C-layer ``graphLib`` version of this function diff --git a/planarity/full/graphLib.pxd b/planarity/full/graphLib.pxd index 3f863bb..dfa09cf 100644 --- a/planarity/full/graphLib.pxd +++ b/planarity/full/graphLib.pxd @@ -177,6 +177,7 @@ cdef int gp_ExtendWith_DFSUtils(graphP theGraph) cdef int gp_Detach_DFSUtils(graphP theGraph) cdef int gp_DepthFirstSearch(graphP theGraph) +cdef int gp_DepthFirstSearchEx(graphP theGraph, unsigned mode) cdef int gp_SortVertices(graphP theGraph) cdef int gp_ComputeLowpoints(graphP theGraph) cdef int gp_ComputeLeastAncestors(graphP theGraph) diff --git a/planarity/full/graphLib.pyx b/planarity/full/graphLib.pyx index 5fd810c..a27adae 100644 --- a/planarity/full/graphLib.pyx +++ b/planarity/full/graphLib.pyx @@ -681,6 +681,10 @@ cdef int gp_DepthFirstSearch(graphP theGraph): return cgraphLib.gp_DepthFirstSearch(theGraph) +cdef int gp_DepthFirstSearchEx(graphP theGraph, unsigned mode): + return cgraphLib.gp_DepthFirstSearchEx(theGraph, mode) + + cdef int gp_SortVertices(graphP theGraph): return cgraphLib.gp_SortVertices(theGraph) From c1e473f867e04674436aaea6a926db515e19892d Mon Sep 17 00:00:00 2001 From: john-boyer-phd Date: Sat, 3 Oct 2026 10:24:28 -0700 Subject: [PATCH 03/15] Add the new cross-edge type --- planarity/__init__.py | 1 + planarity/full/cgraphLib.pxd | 2 ++ planarity/full/graph.pyx | 13 ++++++++----- planarity/full/graphLib.pyx | 3 ++- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/planarity/__init__.py b/planarity/__init__.py index 69e2d5a..5bd4037 100644 --- a/planarity/__init__.py +++ b/planarity/__init__.py @@ -39,6 +39,7 @@ EDGE_TYPE_FORWARD, EDGE_TYPE_PARENT, EDGE_TYPE_BACK, + EDGE_TYPE_CROSS, EDGE_TYPE_NOTDEFINED, EDGE_TYPE_TREE, EDGEFLAG_INVERTED_MASK, diff --git a/planarity/full/cgraphLib.pxd b/planarity/full/cgraphLib.pxd index 2393da6..2bcb3a2 100644 --- a/planarity/full/cgraphLib.pxd +++ b/planarity/full/cgraphLib.pxd @@ -148,6 +148,8 @@ cdef extern from "../c/graphLib/graph.h": int EDGE_TYPE_CHILD, EDGE_TYPE_FORWARD, EDGE_TYPE_PARENT, EDGE_TYPE_BACK + int EDGE_TYPE_CROSS + int EDGE_TYPE_NOTDEFINED, EDGE_TYPE_TREE int gp_GetEdgeType(graphP theGraph, int e) diff --git a/planarity/full/graph.pyx b/planarity/full/graph.pyx index 2c2e9ac..deda43a 100644 --- a/planarity/full/graph.pyx +++ b/planarity/full/graph.pyx @@ -25,6 +25,7 @@ EDGE_TYPE_CHILD = graphLib.EDGE_TYPE_CHILD EDGE_TYPE_FORWARD = graphLib.EDGE_TYPE_FORWARD EDGE_TYPE_PARENT = graphLib.EDGE_TYPE_PARENT EDGE_TYPE_BACK = graphLib.EDGE_TYPE_BACK +EDGE_TYPE_CROSS = graphLib.EDGE_TYPE_CROSS EDGE_TYPE_TREE = graphLib.EDGE_TYPE_TREE EDGE_TYPE_NOTDEFINED = graphLib.EDGE_TYPE_NOTDEFINED @@ -1487,7 +1488,7 @@ cdef class Graph: Returns: The edge type of ``e`` if set, i.e., ``EDGE_TYPE_NOTDEFINED``, ``EDGE_TYPE_CHILD``, ``EDGE_TYPE_FORWARD``, ``EDGE_TYPE_PARENT``, - ``EDGE_TYPE_BACK``, or ``EDGE_TYPE_TREE``. + ``EDGE_TYPE_BACK``, ``EDGE_TYPE_CROSS``, or ``EDGE_TYPE_TREE``. Raises: ValueError: if ``e`` is not a valid in-use edge. @@ -1528,7 +1529,8 @@ cdef class Graph: e: an in-use edge whose edge type you wish to set for the first time to the given ``type``. type: one of ``EDGE_TYPE_CHILD``, ``EDGE_TYPE_FORWARD``, - ``EDGE_TYPE_PARENT``, ``EDGE_TYPE_BACK``, or ``EDGE_TYPE_TREE``. + ``EDGE_TYPE_PARENT``, ``EDGE_TYPE_BACK``, + ``EDGE_TYPE_CROSS``, or ``EDGE_TYPE_TREE``. Raises: ValueError: if ``e`` is not a valid in-use edge or if ``type`` is @@ -1544,7 +1546,7 @@ cdef class Graph: type not in ( EDGE_TYPE_CHILD, EDGE_TYPE_FORWARD, EDGE_TYPE_PARENT, - EDGE_TYPE_BACK, EDGE_TYPE_TREE + EDGE_TYPE_BACK, EDGE_TYPE_CROSS, EDGE_TYPE_TREE ) ): raise ValueError( @@ -1559,7 +1561,8 @@ cdef class Graph: Args: e: an in-use edge whose edge type you wish to reset to ``type``. type: one of ``EDGE_TYPE_CHILD``, ``EDGE_TYPE_FORWARD``, - ``EDGE_TYPE_PARENT``, ``EDGE_TYPE_BACK``, or ``EDGE_TYPE_TREE``. + ``EDGE_TYPE_PARENT``, ``EDGE_TYPE_BACK``, + ``EDGE_TYPE_CROSS``, or ``EDGE_TYPE_TREE``. Raises: ValueError: if ``e`` is not a valid in-use edge or if ``type`` is @@ -1575,7 +1578,7 @@ cdef class Graph: type not in ( EDGE_TYPE_CHILD, EDGE_TYPE_FORWARD, EDGE_TYPE_PARENT, - EDGE_TYPE_BACK, EDGE_TYPE_TREE + EDGE_TYPE_BACK, EDGE_TYPE_CROSS, EDGE_TYPE_TREE ) ): raise ValueError( diff --git a/planarity/full/graphLib.pyx b/planarity/full/graphLib.pyx index a27adae..b7ce7d2 100644 --- a/planarity/full/graphLib.pyx +++ b/planarity/full/graphLib.pyx @@ -41,8 +41,9 @@ EDGE_TYPE_CHILD = cgraphLib.EDGE_TYPE_CHILD EDGE_TYPE_FORWARD = cgraphLib.EDGE_TYPE_FORWARD EDGE_TYPE_PARENT = cgraphLib.EDGE_TYPE_PARENT EDGE_TYPE_BACK = cgraphLib.EDGE_TYPE_BACK -EDGE_TYPE_NOTDEFINED = cgraphLib.EDGE_TYPE_NOTDEFINED +EDGE_TYPE_CROSS = cgraphLib.EDGE_TYPE_CROSS EDGE_TYPE_TREE = cgraphLib.EDGE_TYPE_TREE +EDGE_TYPE_NOTDEFINED = cgraphLib.EDGE_TYPE_NOTDEFINED EDGEFLAG_INVERTED_MASK = cgraphLib.EDGEFLAG_INVERTED_MASK From d59aaf4f3fd171bd9e0ced90b59b7596d8ac8839 Mon Sep 17 00:00:00 2001 From: john-boyer-phd Date: Sat, 3 Oct 2026 11:00:13 -0700 Subject: [PATCH 04/15] Finish adding flag for directed edge detection --- planarity/__init__.py | 1 + planarity/full/graph.pyx | 2 ++ 2 files changed, 3 insertions(+) diff --git a/planarity/__init__.py b/planarity/__init__.py index 5bd4037..3dc25dd 100644 --- a/planarity/__init__.py +++ b/planarity/__init__.py @@ -30,6 +30,7 @@ # Surfaced from planarity/c/graphLib/graph.h from .full.graphLib import ( + GRAPHFLAGS_DIRECTEDEDGEDETECTED, VERTEX_VISITED_MASK, VERTEX_MARKED_MASK, EDGE_VISITED_MASK, diff --git a/planarity/full/graph.pyx b/planarity/full/graph.pyx index deda43a..034b547 100644 --- a/planarity/full/graph.pyx +++ b/planarity/full/graph.pyx @@ -21,6 +21,8 @@ TRUE = graphLib.TRUE FALSE = graphLib.FALSE NIL = graphLib.NIL +GRAPHFLAGS_DIRECTEDEDGEDETECTED = graphLib.GRAPHFLAGS_DIRECTEDEDGEDETECTED + EDGE_TYPE_CHILD = graphLib.EDGE_TYPE_CHILD EDGE_TYPE_FORWARD = graphLib.EDGE_TYPE_FORWARD EDGE_TYPE_PARENT = graphLib.EDGE_TYPE_PARENT From 16f9c78af3dfe8f3713ce85f3d9b90f1186abcc1 Mon Sep 17 00:00:00 2001 From: "Wanda B.K. Boyer" Date: Wed, 7 Oct 2026 15:14:01 -0800 Subject: [PATCH 05/15] Fixed declaration of functions/methods that deal with for `unsigned` parameters and return types (e.g. graph flags, embed flags, edge direction, DFS mode). Created workaround so that `__seed_rng()` function can still be called when one imports `planarity`, but use name mangling to ensure that library users can't `import planarity` or `from planarity import *` and access this private function. Updated sphinx docs generation so that we no longer include `__reduce_cython__()`, `__setstate_cython__()`, nor `__seed_rng()`; it turns out this was likely a `napoleon` extension issue with `napoleon_include_special_with_doc` (but I also set `napoleon_include_private_with_doc` to `False` as well). Also fixed issue with `unsigned` type not being recognized by sphinx (i.e. `nitpicky` config option was highlighting issue), so I added `nitpicky_ignore`. Now exposes `G6WriteIterator`'s `g6_SetOutputErrorFlag()` (which should be used if an error occurs during processing so that we clean up the .g6 output file upon freeing the writer, even if the issue wasn't with the writer itself), as well as `Graph`'s `gp_DynamicInsertEdge()`, `gp_ClearEdgeDirectionFlags()`, `gp_TransposeDirectedGraph()`, `gp_CountConnectedComponents()`, `gp_GetVisitedIndex()`, `gp_GetEmbedFlags()`, and `gp_GetObstructionMinorType()`. Also addressed `FIXME`s in `gp_IsDFSTreeRoot()`, `gp_IsNotDFSTreeRoot()`, and `gp_GetVertexFromBicompRoot()` now that the underlying macros don't rely on a private macro, `gp_GetVertexParent()`. Confirmed `pytest` ran, and that `examples\full\demo_specific_graph.py` worked for `nauty_example.g6` and `Petersen.0-based.txt`. --- doc/source/conf.py | 11 +- planarity/__init__.py | 9 +- planarity/full/cgraphLib.pxd | 45 +++-- planarity/full/g6IterationUtils.pyx | 11 ++ planarity/full/graph.pyx | 289 ++++++++++++++++++++++------ planarity/full/graphLib.pxd | 33 ++-- planarity/full/graphLib.pyx | 91 ++++++--- 7 files changed, 351 insertions(+), 138 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 95c8d84..d0651f1 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -63,8 +63,8 @@ napoleon_google_docstring = True napoleon_numpy_docstring = False napoleon_include_init_with_doc = True -napoleon_include_private_with_doc = True -napoleon_include_special_with_doc = True +napoleon_include_private_with_doc = False +napoleon_include_special_with_doc = False napoleon_use_admonition_for_examples = False napoleon_use_admonition_for_notes = False napoleon_use_admonition_for_references = False @@ -79,7 +79,12 @@ # Warn about all missing references nitpicky=True - +nitpick_ignore = [ + ('py:class', 'unsigned'), + ('py:class', 'unsigned int'), + ('py:class', 'uint'), + ('py:class', 'cython.uint'), +] # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output diff --git a/planarity/__init__.py b/planarity/__init__.py index 3dc25dd..94c0afb 100644 --- a/planarity/__init__.py +++ b/planarity/__init__.py @@ -12,6 +12,8 @@ from .full.graphLib import ( gp_GetQuietMode, gp_SetQuietMode, +) +from .full.graphLib import ( gp_GetRandomNumber, ) @@ -110,13 +112,12 @@ from .full.graph import Graph from .full.g6IterationUtils import G6ReadIterator, G6WriteIterator + # Seed the random number generator in the C library so that the # random number and random graph generators will produce different # outputs on each run of a script. -from .full.graphLib import ( - seed_rng, -) -seed_rng() +from .full import graphLib +getattr(graphLib, "__seed_rng")() # NOTE: In the future, we could automatically generate the version number by # configuring setuptools-scm, but presently this seems simpler. diff --git a/planarity/full/cgraphLib.pxd b/planarity/full/cgraphLib.pxd index 2bcb3a2..4dd27b8 100644 --- a/planarity/full/cgraphLib.pxd +++ b/planarity/full/cgraphLib.pxd @@ -52,15 +52,18 @@ cdef extern from "../c/graphLib/graph.h": int gp_FindEdge(graphP theGraph, int u, int v) int gp_GetVertexDegree(graphP theGraph, int v) - int gp_IsNeighborDirected(graphP theGraph, int u, int v, unsigned direction) - int gp_FindDirectedEdge(graphP theGraph, int u, int v, unsigned direction) + int gp_IsNeighborDirected(graphP theGraph, int u, int v, unsigned int direction) + int gp_FindDirectedEdge(graphP theGraph, int u, int v, unsigned int direction) int gp_GetVertexInDegree(graphP theGraph, int v) int gp_GetVertexOutDegree(graphP theGraph, int v) int gp_AddEdge(graphP theGraph, int u, int ulink, int v, int vlink) int gp_DynamicAddEdge(graphP theGraph, int u, int ulink, int v, int vlink) int gp_InsertEdge(graphP theGraph, int u, int e_u, int e_ulink, int v, int e_v, int e_vlink) + int gp_DynamicInsertEdge(graphP theGraph, int u, int e_u, int e_ulink, int v, int e_v, int e_vlink) int gp_DeleteEdge(graphP theGraph, int e) + int gp_ClearEdgeDirectionFlags(graphP theGraph) + int gp_TransposeDirectedGraph(graphP theGraph) void gp_HideEdge(graphP theGraph, int e) void gp_RestoreEdge(graphP theGraph, int e) @@ -71,9 +74,8 @@ cdef extern from "../c/graphLib/graph.h": int gp_IdentifyVertices(graphP theGraph, int u, int v, int eBefore) int gp_RestoreVertices(graphP theGraph) - int gp_GetGraphFlags(graphP theGraph) - int GRAPHFLAGS_DIRECTEDEDGEDETECTED + unsigned int gp_GetGraphFlags(graphP theGraph) int gp_GetFirstEdge(graphP theGraph, int v) int gp_GetLastEdge(graphP theGraph, int v) @@ -145,12 +147,9 @@ cdef extern from "../c/graphLib/graph.h": void gp_SetEdgeMarked(graphP theGraph, int e) int EDGE_TYPE_MASK - - int EDGE_TYPE_CHILD, EDGE_TYPE_FORWARD, EDGE_TYPE_PARENT, EDGE_TYPE_BACK - - int EDGE_TYPE_CROSS - - int EDGE_TYPE_NOTDEFINED, EDGE_TYPE_TREE + int EDGE_TYPE_NOTDEFINED + int EDGE_TYPE_TREE, EDGE_TYPE_PARENT, EDGE_TYPE_CHILD + int EDGE_TYPE_BACK, EDGE_TYPE_FORWARD, EDGE_TYPE_CROSS int gp_GetEdgeType(graphP theGraph, int e) void gp_ClearEdgeType(graphP theGraph, int e) @@ -219,6 +218,8 @@ cdef extern from "../c/graphLib/io/g6-write-iterator.h": int g6_InitWriterWithString(G6WriteIteratorP theG6WriteIterator, char **pOutputString) int g6_InitWriterWithFileName(G6WriteIteratorP theG6WriteIterator, char *outputFileName) + void g6_SetOutputErrorFlag(G6WriteIteratorP theG6WriteIterator) + int g6_WriteGraph(G6WriteIteratorP theG6WriteIterator) void g6_FreeWriter(G6WriteIteratorP *pG6WriteIterator) @@ -232,31 +233,29 @@ cdef extern from "../c/graphLib/graphDFSUtils.h": int GRAPHFLAGS_EXTENDEDWITH_DFSUTILS int GRAPHFLAGS_DFSNUMBERED, GRAPHFLAGS_SORTEDBYDFI, GRAPHFLAGS_LOWPOINTSCOMPUTED - int DFSMODE_UNDIRECTED, DFSMODE_DIRECTED int GRAPHFLAGS_DFSNUMBERED_DIRECTED + int DFSMODE_UNDIRECTED, DFSMODE_DIRECTED + int gp_DepthFirstSearch(graphP theGraph) - int gp_DepthFirstSearchEx(graphP theGraph, unsigned mode) + int gp_DepthFirstSearchEx(graphP theGraph, unsigned int mode) + int gp_SortVertices(graphP theGraph) int gp_ComputeLowpoints(graphP theGraph) int gp_ComputeLeastAncestors(graphP theGraph) + int gp_CountConnectedComponents(graphP theGraph) int gp_GetParent(graphP theGraph, int v) + int gp_GetVisitedIndex(graphP theGraph, int v) int gp_GetLeastAncestor(graphP theGraph, int v) int gp_GetLowpoint(graphP theGraph, int v) - # FIXME: We fail to link due to issue where these macros call private - # gp_GetVertexParent() rather than public gp_GetParent() - # int gp_IsDFSTreeRoot(graphP theGraph, int v) - # int gp_IsNotDFSTreeRoot(graphP theGraph, int v) + int gp_IsDFSTreeRoot(graphP theGraph, int v) + int gp_IsNotDFSTreeRoot(graphP theGraph, int v) int gp_GetBicompRootFromDFSChild(graphP theGraph, int c) int gp_GetDFSChildFromBicompRoot(graphP theGraph, int R) - - # FIXME: We fail to link due to issue where this macro calls private - # gp_GetVertexParent() rather than public gp_GetParent() - # int gp_GetVertexFromBicompRoot(graphP theGraph, int R) - + int gp_GetVertexFromBicompRoot(graphP theGraph, int R) int gp_IsBicompRoot(graphP theGraph, int v) int gp_IsSeparatedDFSChild(graphP theGraph, int theChild) @@ -276,12 +275,12 @@ cdef extern from "../c/graphLib/planarityRelated/graphPlanarity.h": int NONEMBEDDABLE - int gp_GetEmbedFlags(graphP theGraph) + unsigned int gp_GetEmbedFlags(graphP theGraph) int EMBEDFLAGS_PLANAR, EMBEDFLAGS_DRAWPLANAR, EMBEDFLAGS_OUTERPLANAR int EMBEDFLAGS_SEARCHFORK23, EMBEDFLAGS_SEARCHFORK33, EMBEDFLAGS_SEARCHFORK4 - unsigned gp_GetObstructionMinorType(graphP theGraph) + unsigned int gp_GetObstructionMinorType(graphP theGraph) int MINORTYPE_NONE, MINORTYPE_A, MINORTYPE_B, MINORTYPE_C, MINORTYPE_D, MINORTYPE_E int MINORTYPE_E1, MINORTYPE_E2, MINORTYPE_E3, MINORTYPE_E4 diff --git a/planarity/full/g6IterationUtils.pyx b/planarity/full/g6IterationUtils.pyx index e79cdfd..629edda 100644 --- a/planarity/full/g6IterationUtils.pyx +++ b/planarity/full/g6IterationUtils.pyx @@ -274,6 +274,17 @@ cdef class G6WriteIterator: "g6_InitWriterWithFileName() in EAPS graphLib failed." ) + def g6_SetOutputErrorFlag(self) -> None: + """Sets the writer's output error flag to prevent further operation. + + .. Note:: + This method ensures that when an error is encountered (either by the + writer or during some process that outputs graphs using the writer), + we clean up any output. See + `EAPS PR #313 <(https://github.com/graph-algorithms/edge-addition-planarity-suite/pull/313>`__ + """ + graphLib.g6_SetOutputErrorFlag(self._g6WriteIterator) + def g6_WriteGraph(self) -> None: """Writes a ``G6``-encoded graph to the output source. diff --git a/planarity/full/graph.pyx b/planarity/full/graph.pyx index 034b547..00b0f5a 100644 --- a/planarity/full/graph.pyx +++ b/planarity/full/graph.pyx @@ -14,33 +14,63 @@ from planarity.full import graphLib OK = graphLib.OK -NONEMBEDDABLE = graphLib.NONEMBEDDABLE NOTOK = graphLib.NOTOK - TRUE = graphLib.TRUE FALSE = graphLib.FALSE NIL = graphLib.NIL GRAPHFLAGS_DIRECTEDEDGEDETECTED = graphLib.GRAPHFLAGS_DIRECTEDEDGEDETECTED -EDGE_TYPE_CHILD = graphLib.EDGE_TYPE_CHILD -EDGE_TYPE_FORWARD = graphLib.EDGE_TYPE_FORWARD +VERTEX_VISITED_MASK = graphLib.VERTEX_VISITED_MASK +VERTEX_MARKED_MASK = graphLib.VERTEX_MARKED_MASK + +EDGE_VISITED_MASK = graphLib.EDGE_VISITED_MASK +EDGE_MARKED_MASK = graphLib.EDGE_MARKED_MASK + +EDGE_TYPE_MASK = graphLib.EDGE_TYPE_MASK +EDGE_TYPE_NOTDEFINED = graphLib.EDGE_TYPE_NOTDEFINED +EDGE_TYPE_TREE = graphLib.EDGE_TYPE_TREE EDGE_TYPE_PARENT = graphLib.EDGE_TYPE_PARENT +EDGE_TYPE_CHILD = graphLib.EDGE_TYPE_CHILD EDGE_TYPE_BACK = graphLib.EDGE_TYPE_BACK +EDGE_TYPE_FORWARD = graphLib.EDGE_TYPE_FORWARD EDGE_TYPE_CROSS = graphLib.EDGE_TYPE_CROSS -EDGE_TYPE_TREE = graphLib.EDGE_TYPE_TREE -EDGE_TYPE_NOTDEFINED = graphLib.EDGE_TYPE_NOTDEFINED + +EDGEFLAG_INVERTED_MASK = graphLib.EDGEFLAG_INVERTED_MASK EDGEFLAG_DIRECTION_INONLY = graphLib.EDGEFLAG_DIRECTION_INONLY EDGEFLAG_DIRECTION_OUTONLY = graphLib.EDGEFLAG_DIRECTION_OUTONLY +EDGEFLAG_DIRECTION_MASK = graphLib.EDGEFLAG_DIRECTION_MASK +DEFAULT_EDGE_CAPACITY_FACTOR = graphLib.DEFAULT_EDGE_CAPACITY_FACTOR AT_EDGE_CAPACITY_LIMIT = graphLib.AT_EDGE_CAPACITY_LIMIT + WRITE_ADJLIST = graphLib.WRITE_ADJLIST WRITE_ADJMATRIX = graphLib.WRITE_ADJMATRIX WRITE_G6 = graphLib.WRITE_G6 WRITE_GRAPHML = graphLib.WRITE_GRAPHML +GRAPHFLAGS_ZEROBASEDIO = graphLib.GRAPHFLAGS_ZEROBASEDIO + + +DFSUTILS_NAME = graphLib.DFSUTILS_NAME +GRAPHFLAGS_EXTENDEDWITH_DFSUTILS = graphLib.GRAPHFLAGS_EXTENDEDWITH_DFSUTILS + +GRAPHFLAGS_DFSNUMBERED = graphLib.GRAPHFLAGS_DFSNUMBERED +GRAPHFLAGS_SORTEDBYDFI = graphLib.GRAPHFLAGS_SORTEDBYDFI +GRAPHFLAGS_LOWPOINTSCOMPUTED = graphLib.GRAPHFLAGS_LOWPOINTSCOMPUTED + +DFSMODE_UNDIRECTED = graphLib.DFSMODE_UNDIRECTED +DFSMODE_DIRECTED = graphLib.DFSMODE_DIRECTED + +GRAPHFLAGS_DFSNUMBERED_DIRECTED = graphLib.GRAPHFLAGS_DFSNUMBERED_DIRECTED + + +PLANARITY_NAME = graphLib.PLANARITY_NAME +GRAPHFLAGS_EXTENDEDWITH_PLANARITY = graphLib.GRAPHFLAGS_EXTENDEDWITH_PLANARITY + +NONEMBEDDABLE = graphLib.NONEMBEDDABLE EMBEDFLAGS_PLANAR = graphLib.EMBEDFLAGS_PLANAR EMBEDFLAGS_DRAWPLANAR = graphLib.EMBEDFLAGS_DRAWPLANAR @@ -49,6 +79,34 @@ EMBEDFLAGS_SEARCHFORK23 = graphLib.EMBEDFLAGS_SEARCHFORK23 EMBEDFLAGS_SEARCHFORK33 = graphLib.EMBEDFLAGS_SEARCHFORK33 EMBEDFLAGS_SEARCHFORK4 = graphLib.EMBEDFLAGS_SEARCHFORK4 +MINORTYPE_NONE = graphLib.MINORTYPE_NONE +MINORTYPE_A = graphLib.MINORTYPE_A +MINORTYPE_B = graphLib.MINORTYPE_B +MINORTYPE_C = graphLib.MINORTYPE_C +MINORTYPE_D = graphLib.MINORTYPE_D +MINORTYPE_E = graphLib.MINORTYPE_E +MINORTYPE_E1 = graphLib.MINORTYPE_E1 +MINORTYPE_E2 = graphLib.MINORTYPE_E2 +MINORTYPE_E3 = graphLib.MINORTYPE_E3 +MINORTYPE_E4 = graphLib.MINORTYPE_E4 +MINORTYPE_E5 = graphLib.MINORTYPE_E5 +MINORTYPE_E6 = graphLib.MINORTYPE_E6 +MINORTYPE_E7 = graphLib.MINORTYPE_E7 + + +OUTERPLANARITY_NAME = graphLib.OUTERPLANARITY_NAME +GRAPHFLAGS_EXTENDEDWITH_OUTERPLANARITY = ( + graphLib.GRAPHFLAGS_EXTENDEDWITH_OUTERPLANARITY +) + +DRAWPLANAR_NAME = graphLib.DRAWPLANAR_NAME + +K23SEARCH_NAME = graphLib.K23SEARCH_NAME + +K33SEARCH_NAME = graphLib.K33SEARCH_NAME + +K4SEARCH_NAME = graphLib.K4SEARCH_NAME + cdef class Graph: """Wraps the graph data structure from the Edge Addition Planarity Suite. @@ -290,7 +348,9 @@ cdef class Graph: """ return graphLib.gp_GetVertexDegree(self._theGraph, v) - def gp_IsNeighborDirected(self, int u, int v, unsigned direction) -> int: + def gp_IsNeighborDirected( + self, int u, int v, unsigned int direction + ) -> int: """Checks if an edge in a given direction exists between two vertices. Args: @@ -305,7 +365,7 @@ cdef class Graph: """ return graphLib.gp_IsNeighborDirected(self._theGraph, u, v, direction) - def gp_FindDirectedEdge(self, int u, int v, unsigned direction) -> int: + def gp_FindDirectedEdge(self, int u, int v, unsigned int direction) -> int: """Finds a directed edge between ``u`` and ``v``, if it exists. Args: @@ -463,6 +523,38 @@ cdef class Graph: return result + def gp_DynamicInsertEdge( + self, int u, int e_u, int e_ulink, int v, int e_v, int e_vlink + ) -> None: + """Inserts edge (``u``, ``v``) in specific adjacency lists positions, increasing edge capacity if needed. + + Args: + u: index of a vertex in the graph. + e_u: new edge is added next to this edge in ``u``'s adjacency list. + e_ulink: ``0`` or ``1``; to which side of ``e_u`` to add new edge + (or which side of the adjacency list of ``u`` if ``e_u`` is + ``NIL``). + v: index of a vertex in the graph. + e_v: new edge is added next to this edge in ``v``'s adjacency list. + e_vlink: ``0`` or ``1``; to which side of ``e_v`` to add new edge + (or which side of the adjacency list of ``v`` if ``e_v`` is + ``NIL``). + + Raises: + RuntimeError: if the C-layer ``graphLib`` version of this function + returns anything other than OK. + """ + result = graphLib.gp_DynamicInsertEdge( + self._theGraph, u, e_u, e_ulink, v, e_v, e_vlink + ) + if result != OK: + raise RuntimeError( + "gp_DynamicInsertEdge() failed: unable to insert edge (u, v) = " + f"({u}, {v}) adjacent to e_u = {e_u} by e_ulink = {e_ulink} in " + f"u's adjacency list and adjacent to e_v = {e_v} by e_vlink = " + f"{e_vlink} in ``v``'s adjacency list." + ) + def gp_DeleteEdge(self, int e) -> None: """Deletes the in-use edge with index ``e`` from the graph. @@ -479,6 +571,32 @@ cdef class Graph: f"gp_DeleteEdge() failed: unable to delete edge e = {e}" ) + def gp_ClearEdgeDirectionFlags(self) -> None: + """Clears edge direction flags on each edge in the graph. + + Raises: + RuntimeError: if the C-layer ``graphLib`` version of this function + fails. + """ + if graphLib.gp_ClearEdgeDirectionFlags(self._theGraph) != OK: + raise RuntimeError( + "gp_ClearEdgeDirectionFlags() failed: unable to clear edge " + "direction flags on the graph." + ) + + def gp_TransposeDirectedGraph(self) -> None: + """Reverses every in-use directed edge in the graph. + + Raises: + RuntimeError: if the C-layer ``graphLib`` version of this function + fails. + """ + if graphLib.gp_TransposeDirectedGraph(self._theGraph) != OK: + raise RuntimeError( + "gp_TransposeDirectedGraph() failed: unable to reverse every " + "in-use directed edge." + ) + def gp_HideEdge(self, int e) -> None: """Hides the edge with index ``e`` from the adjacency list of the graph. @@ -591,11 +709,15 @@ cdef class Graph: "hidden during an edge contraction or vertex identification." ) - def gp_GetGraphFlags(self) -> int: + def gp_GetGraphFlags(self) -> unsigned: """Returns the graph-level flags. Returns: - An integer representing the graph-level flags. + An unsigned integer representing the graph-level flags. Use + bitwise-and with the following constants to determine whether the + given flag is set on the graph: + + * **FIXME:** ENUMERATE ALL POSSIBLE VALUES """ return graphLib.gp_GetGraphFlags(self._theGraph) @@ -1531,7 +1653,7 @@ cdef class Graph: e: an in-use edge whose edge type you wish to set for the first time to the given ``type``. type: one of ``EDGE_TYPE_CHILD``, ``EDGE_TYPE_FORWARD``, - ``EDGE_TYPE_PARENT``, ``EDGE_TYPE_BACK``, + ``EDGE_TYPE_PARENT``, ``EDGE_TYPE_BACK``, ``EDGE_TYPE_CROSS``, or ``EDGE_TYPE_TREE``. Raises: @@ -1563,7 +1685,7 @@ cdef class Graph: Args: e: an in-use edge whose edge type you wish to reset to ``type``. type: one of ``EDGE_TYPE_CHILD``, ``EDGE_TYPE_FORWARD``, - ``EDGE_TYPE_PARENT``, ``EDGE_TYPE_BACK``, + ``EDGE_TYPE_PARENT``, ``EDGE_TYPE_BACK``, ``EDGE_TYPE_CROSS``, or ``EDGE_TYPE_TREE``. Raises: @@ -1950,14 +2072,14 @@ cdef class Graph: "gp_DepthFirstSearch() failed: unable to perform DFS on graph." ) - def gp_DepthFirstSearchEx(self, unsigned mode) -> None: + def gp_DepthFirstSearchEx(self, unsigned int mode) -> None: """Performs a depth-first search (DFS) on the graph, based on the ``mode``. Gives vertices a value for their depth-first indexes (DFIs) and DFS parents, and gives edges a value for their type. See ``gp_GetParent()``, ``gp_GetIndex()``, and ``gp_GetEdgeType()``. The settings given depend on whether an undirected or directed depth-first search was performed. - Upon completion this method sets either ``GRAPHFLAGS_DFSNUMBERED`` or + Upon completion this method sets either ``GRAPHFLAGS_DFSNUMBERED`` or ``GRAPHFLAGS_DFSNUMBERED_DIRECTED``, depending on the ``mode``. The undirected DFS is performed by ``gp_DepthFirstSearch()``. @@ -1979,19 +2101,19 @@ cdef class Graph: def gp_SortVertices(self) -> None: """Sorts the vertices in ascending order according to their DFIs. - This method rearranges the vertices according to their depth-first + This method rearranges the vertices according to their depth-first index values, in linear time. The DFIs are set based on a prior call of ``gp_DepthFirstSearch()`` or ``gp_DepthFirstSearchEx()``, which set ``GRAPHFLAGS_DFSNUMBERED`` or ``GRAPHFLAGS_DFSNUMBERED_DIRECTED``. - If neither graph flag is set, then this method will begin by invoking + If neither graph flag is set, then this method will begin by invoking ``gp_DepthFirstSearch()``. This method sets ``GRAPHFLAGS_SORTEDBYDFI`` and replaces the DFIs - in the index fields of vertices with their original array index - positions prior to sorting. A second invocation of this method + in the index fields of vertices with their original array index + positions prior to sorting. A second invocation of this method restores vertices to their original order, restores the DFI values - into the vertex index fields, and clears ``GRAPHFLAGS_SORTEDBYDFI``. - + into the vertex index fields, and clears ``GRAPHFLAGS_SORTEDBYDFI``. + Raises: RuntimeError: if the C-layer ``graphLib`` version of this function fails. @@ -2032,6 +2154,23 @@ cdef class Graph: "gp_ComputeLeastAncestors() failed." ) + def gp_CountConnectedComponents(self) -> int: + """Counts the number of connected components identified by DFS tree roots. + + Raises: + RuntimeError: if the C-layer ``graphLib`` version of this function + fails (i.e., returns -1) + """ + num_connected_components = graphLib.gp_CountConnectedComponents( + self._theGraph + ) + if num_connected_components == -1: + raise RuntimeError( + "gp_CountConnectedComponents() failed." + ) + + return num_connected_components + def gp_GetParent(self, int v) -> int: """Gets the DFS parent of vertex ``v``, after depth-first search. @@ -2059,6 +2198,26 @@ cdef class Graph: return graphLib.gp_GetParent(self._theGraph, v) + def gp_GetVisitedIndex(self, int v) -> int: + """Gets the visited index of the given vertex v. + + Args: + v: a vertex in the graph whose visited index you wish to obtain. + + Returns: + The depth-first index if the vertex has been visited, or ``NIL`` if + the data member has not been set, such as by directed DFS. + + Raises: + ValueError: if ``v`` is not a valid vertex. + """ + if not self.gp_IsVertex(v): + raise ValueError( + f"gp_GetVisitedIndex() failed: invalid vertex v = {v}" + ) + + return graphLib.gp_GetVisitedIndex(self._theGraph, v) + def gp_GetLeastAncestor(self, int v) -> int: """Gets the least ancestor of vertex ``v``, after least ancestors are computed. @@ -2132,15 +2291,7 @@ cdef class Graph: f"gp_IsDFSTreeRoot() failed: invalid vertex v = {v}" ) - # FIXME: This EAPS macro call private gp_GetVertexParent() rather than - # public gp_GetParent(), so we implement it at this level for now and - # should switch to the commented-out call-through once it is fixed. - # - # if graphLib.gp_IsDFSTreeRoot(self._theGraph, v): - # return TRUE - # - # return FALSE - if self.gp_IsNotVertex(self.gp_GetParent(v)): + if graphLib.gp_IsDFSTreeRoot(self._theGraph, v): return TRUE return FALSE @@ -2168,15 +2319,7 @@ cdef class Graph: f"gp_IsNotDFSTreeRoot() failed: invalid vertex v = {v}" ) - # FIXME: This EAPS macro call private gp_GetVertexParent() rather than - # public gp_GetParent(), so we implement it at this level for now and - # should switch to the commented-out call-through once it is fixed. - # - # if graphLib.gp_IsNotDFSTreeRoot(self._theGraph, v): - # return TRUE - # - # return FALSE - if not self.gp_IsDFSTreeRoot(v): + if graphLib.gp_IsNotDFSTreeRoot(self._theGraph, v): return TRUE return FALSE @@ -2245,26 +2388,7 @@ cdef class Graph: f"R = {R} for bicomp root." ) - # FIXME: This EAPS macro call private gp_GetVertexParent() rather than - # public gp_GetParent(), so we implement it at this level for now and - # should switch to the commented-out call-through once it is fixed. - # return graphLib.gp_GetVertexFromBicompRoot(self._theGraph, R) - - c = self.gp_GetDFSChildFromBicompRoot(R) - if not self.gp_IsVertex(c): - raise RuntimeError( - "gp_GetVertexFromBicompRoot() failed: invalid DFS child c = " - f"{c} for bicomp root R = {R}" - ) - - bicomp_root = self.gp_GetParent(c) - if bicomp_root == NIL: - raise RuntimeError( - f"gp_GetVertexFromBicompRoot() failed: c = {c} should not be " - "the root of the DFS tree." - ) - - return bicomp_root + return graphLib.gp_GetVertexFromBicompRoot(self._theGraph, R) def gp_IsBicompRoot(self, int v) -> int: """Determines whether a non-virtual or virtual vertex is a bicomp root. @@ -2358,7 +2482,7 @@ cdef class Graph: "Failed to extend graph with Planarity structures." ) - def gp_Embed(self, int embedFlags) -> int: + def gp_Embed(self, unsigned int embedFlags) -> unsigned: """Embeds the graph or provides a minimal subgraph obstructing embedding. Modifies the graph to be either an embedding of the original graph or a @@ -2416,6 +2540,53 @@ cdef class Graph: return check_result + def gp_GetEmbedFlags(self) -> unsigned: + """Gets the ``embedFlags`` of the graph. + + Returns: + The ``embedFlags`` of the graph, which are ``0`` if + :py:meth:`~planarity.full.graph.Graph.gp_Embed` has not yet been + called. After the embedding operation has been performed, use + bitwise-and with the following constants to determine which + embedding algorithm was requested: + + * ``EMBEDFLAGS_PLANAR`` + * ``EMBEDFLAGS_DRAWPLANAR`` + * ``EMBEDFLAGS_OUTERPLANAR`` + * ``EMBEDFLAGS_SEARCHFORK23`` + * ``EMBEDFLAGS_SEARCHFORK33`` + * ``EMBEDFLAGS_SEARCHFORK4`` + """ + return graphLib.gp_GetEmbedFlags(self._theGraph) + + def gp_GetObstructionMinorType(self) -> unsigned: + """Gets the obstruction type if :py:meth:`~planarity.full.graph.Graph.gp_Embed` result is ``NONEMBEDDABLE``. + + Returns: + If the graph has been found to be nonembeddable, use bitwise-and + with the following constants to determine the obstruction type(s): + + * ``MINORTYPE_A`` + * ``MINORTYPE_B`` + * ``MINORTYPE_C`` + * ``MINORTYPE_D`` + * ``MINORTYPE_E`` + * ``MINORTYPE_E1`` + * ``MINORTYPE_E2`` + * ``MINORTYPE_E3`` + * ``MINORTYPE_E4`` + * ``MINORTYPE_E5`` + * ``MINORTYPE_E6`` + * ``MINORTYPE_E7`` + + Otherwise, returns ``MINORTYPE_NONE`` if the graph is embeddable or + if :py:meth:`~planarity.full.graph.Graph.gp_Embed` has not yet been + run. + """ + # TODO: Do we want to check that gp_Embed() has been called (i.e. check + # that the embed flags have been set) + return graphLib.gp_GetObstructionMinorType(self._theGraph) + def gp_ExtendWith_Outerplanarity(self) -> None: """Dynamically subclasses the graph with the ``Outerplanarity`` extension. diff --git a/planarity/full/graphLib.pxd b/planarity/full/graphLib.pxd index dfa09cf..b5d2ec2 100644 --- a/planarity/full/graphLib.pxd +++ b/planarity/full/graphLib.pxd @@ -33,15 +33,18 @@ cdef int gp_IsNeighbor(graphP theGraph, int u, int v) cdef int gp_FindEdge(graphP theGraph, int u, int v) cdef int gp_GetVertexDegree(graphP theGraph, int v) -cdef int gp_IsNeighborDirected(graphP theGraph, int u, int v, unsigned direction) -cdef int gp_FindDirectedEdge(graphP theGraph, int u, int v, unsigned direction) +cdef int gp_IsNeighborDirected(graphP theGraph, int u, int v, unsigned int direction) +cdef int gp_FindDirectedEdge(graphP theGraph, int u, int v, unsigned int direction) cdef int gp_GetVertexInDegree(graphP theGraph, int v) cdef int gp_GetVertexOutDegree(graphP theGraph, int v) cdef int gp_AddEdge(graphP theGraph, int u, int ulink, int v, int vlink) cdef int gp_DynamicAddEdge(graphP theGraph, int u, int ulink, int v, int vlink) cdef int gp_InsertEdge(graphP theGraph, int u, int e_u, int e_ulink, int v, int e_v, int e_vlink) +cdef int gp_DynamicInsertEdge(graphP theGraph, int u, int e_u, int e_ulink, int v, int e_v, int e_vlink) cdef int gp_DeleteEdge(graphP theGraph, int e) +cdef int gp_ClearEdgeDirectionFlags(graphP theGraph) +cdef int gp_TransposeDirectedGraph(graphP theGraph) cdef void gp_HideEdge(graphP theGraph, int e) cdef void gp_RestoreEdge(graphP theGraph, int e) @@ -52,7 +55,7 @@ cdef int gp_ContractEdge(graphP theGraph, int e) cdef int gp_IdentifyVertices(graphP theGraph, int u, int v, int eBefore) cdef int gp_RestoreVertices(graphP theGraph) -cdef int gp_GetGraphFlags(graphP theGraph) +cdef unsigned int gp_GetGraphFlags(graphP theGraph) cdef int gp_GetFirstEdge(graphP theGraph, int v) cdef int gp_GetLastEdge(graphP theGraph, int v) @@ -167,6 +170,8 @@ cdef int g6_NewWriter(G6WriteIteratorP *pG6WriteIterator, graphP theGraph) cdef int g6_InitWriterWithString(G6WriteIteratorP theG6WriteIterator, char **pOutputString) cdef int g6_InitWriterWithFileName(G6WriteIteratorP theG6WriteIterator, char *outputFileName) +cdef void g6_SetOutputErrorFlag(G6WriteIteratorP theG6WriteIterator) + cdef int g6_WriteGraph(G6WriteIteratorP theG6WriteIterator) cdef void g6_FreeWriter(G6WriteIteratorP *pG6WriteIterator) @@ -177,29 +182,23 @@ cdef int gp_ExtendWith_DFSUtils(graphP theGraph) cdef int gp_Detach_DFSUtils(graphP theGraph) cdef int gp_DepthFirstSearch(graphP theGraph) -cdef int gp_DepthFirstSearchEx(graphP theGraph, unsigned mode) +cdef int gp_DepthFirstSearchEx(graphP theGraph, unsigned int mode) cdef int gp_SortVertices(graphP theGraph) cdef int gp_ComputeLowpoints(graphP theGraph) cdef int gp_ComputeLeastAncestors(graphP theGraph) +cdef int gp_CountConnectedComponents(graphP theGraph) cdef int gp_GetParent(graphP theGraph, int v) +cdef int gp_GetVisitedIndex(graphP theGraph, int v) cdef int gp_GetLeastAncestor(graphP theGraph, int v) cdef int gp_GetLowpoint(graphP theGraph, int v) -# FIXME: We fail to link due to issue where these macros call private -# gp_GetVertexParent() rather than public gp_GetParent(). These can -# be added in once EAPS is fixed. -# cdef int gp_IsDFSTreeRoot(graphP theGraph, int v) -# cdef int gp_IsNotDFSTreeRoot(graphP theGraph, int v) +cdef int gp_IsDFSTreeRoot(graphP theGraph, int v) +cdef int gp_IsNotDFSTreeRoot(graphP theGraph, int v) cdef int gp_GetBicompRootFromDFSChild(graphP theGraph, int c) cdef int gp_GetDFSChildFromBicompRoot(graphP theGraph, int R) - -# FIXME: We fail to link due to issue where this macro calls private -# gp_GetVertexParent() rather than public gp_GetParent(). This can -# added in once EAPS is fixed. -# cdef int gp_GetVertexFromBicompRoot(graphP theGraph, int R) - +cdef int gp_GetVertexFromBicompRoot(graphP theGraph, int R) cdef int gp_IsBicompRoot(graphP theGraph, int v) cdef int gp_IsSeparatedDFSChild(graphP theGraph, int theChild) @@ -213,9 +212,9 @@ cdef int gp_Detach_Planarity(graphP theGraph) cdef int gp_Embed(graphP theGraph, unsigned int embedFlags) cdef int gp_TestEmbedResultIntegrity(graphP theGraph, graphP origGraph, int embedResult) -cdef int gp_GetEmbedFlags(graphP theGraph) +cdef unsigned int gp_GetEmbedFlags(graphP theGraph) -cdef unsigned gp_GetObstructionMinorType(graphP theGraph) +cdef unsigned int gp_GetObstructionMinorType(graphP theGraph) # Surfaced from planarity/c/graphLib/planarityRelated/graphOuterplanarity.h diff --git a/planarity/full/graphLib.pyx b/planarity/full/graphLib.pyx index b7ce7d2..08469bc 100644 --- a/planarity/full/graphLib.pyx +++ b/planarity/full/graphLib.pyx @@ -37,13 +37,13 @@ EDGE_VISITED_MASK = cgraphLib.EDGE_VISITED_MASK EDGE_MARKED_MASK = cgraphLib.EDGE_MARKED_MASK EDGE_TYPE_MASK = cgraphLib.EDGE_TYPE_MASK -EDGE_TYPE_CHILD = cgraphLib.EDGE_TYPE_CHILD -EDGE_TYPE_FORWARD = cgraphLib.EDGE_TYPE_FORWARD +EDGE_TYPE_NOTDEFINED = cgraphLib.EDGE_TYPE_NOTDEFINED +EDGE_TYPE_TREE = cgraphLib.EDGE_TYPE_TREE EDGE_TYPE_PARENT = cgraphLib.EDGE_TYPE_PARENT +EDGE_TYPE_CHILD = cgraphLib.EDGE_TYPE_CHILD EDGE_TYPE_BACK = cgraphLib.EDGE_TYPE_BACK +EDGE_TYPE_FORWARD = cgraphLib.EDGE_TYPE_FORWARD EDGE_TYPE_CROSS = cgraphLib.EDGE_TYPE_CROSS -EDGE_TYPE_TREE = cgraphLib.EDGE_TYPE_TREE -EDGE_TYPE_NOTDEFINED = cgraphLib.EDGE_TYPE_NOTDEFINED EDGEFLAG_INVERTED_MASK = cgraphLib.EDGEFLAG_INVERTED_MASK @@ -64,21 +64,18 @@ GRAPHFLAGS_ZEROBASEDIO = cgraphLib.GRAPHFLAGS_ZEROBASEDIO # Surfaced from planarity/c/graphLib/graphDFSUtils.h DFSUTILS_NAME = cgraphLib.DFSUTILS_NAME - GRAPHFLAGS_EXTENDEDWITH_DFSUTILS = cgraphLib.GRAPHFLAGS_EXTENDEDWITH_DFSUTILS GRAPHFLAGS_DFSNUMBERED = cgraphLib.GRAPHFLAGS_DFSNUMBERED GRAPHFLAGS_SORTEDBYDFI = cgraphLib.GRAPHFLAGS_SORTEDBYDFI GRAPHFLAGS_LOWPOINTSCOMPUTED = cgraphLib.GRAPHFLAGS_LOWPOINTSCOMPUTED +GRAPHFLAGS_DFSNUMBERED_DIRECTED = cgraphLib.GRAPHFLAGS_DFSNUMBERED_DIRECTED DFSMODE_UNDIRECTED = cgraphLib.DFSMODE_UNDIRECTED DFSMODE_DIRECTED = cgraphLib.DFSMODE_DIRECTED -GRAPHFLAGS_DFSNUMBERED_DIRECTED = cgraphLib.GRAPHFLAGS_DFSNUMBERED_DIRECTED - # Surfaced from planarity/c/graphLib/planarityRelated/graphPlanarity.h PLANARITY_NAME = cgraphLib.PLANARITY_NAME - GRAPHFLAGS_EXTENDEDWITH_PLANARITY = cgraphLib.GRAPHFLAGS_EXTENDEDWITH_PLANARITY NONEMBEDDABLE = cgraphLib.NONEMBEDDABLE @@ -157,7 +154,7 @@ def gp_SetQuietMode(int newQuietMode) -> None: def gp_GetRandomNumber(int NMin, int NMax) -> int: """Generates a pseudo-random integer number in the range of the parameters. - + Like ``random.randint()`` except this method calls the same C-layer ``gp_GetRandomNumber()`` method that is called by the random graph generator methods such as ``gp_CreateRandomGraph()``. @@ -171,15 +168,22 @@ def gp_GetRandomNumber(int NMin, int NMax) -> int: """ return cgraphLib.gp_GetRandomNumber(NMin, NMax) -def seed_rng() -> None: - # Called by __init__.py to seed the pseudo-random number generator - # used by gp_GetRandomNumber() and the random graph generator methods. - # We seed with a random int generated by Python because Python seeds - # its random number generator with a true source of entropy when + +#NOTE: Using name mangling to hide this method from users who import planarity +# Confirmed that with `import planarity`, `dir(planarity)` in an IDLE session +# didn't include this method, and with `from planarity import *`, `dir()` didn't +# include `__seed_rng()`. +def __seed_rng() -> None: + # Called by ``__init__.py`` to seed the pseudo-random number generator + # used by :py:func:`~planarity.full.graphLib.gp_GetRandomNumber` and the + # random graph generator methods. + # We seed with a random ``int`` generated by Python because Python seeds + # its random number generator with a true source of entropy when # available and only falls back to system time otherwise. import random srand(random.randint(0, 4294967295)) + def gp_GetProjectVersionFull() -> str: """Returns the Edge Addition Planarity Suite's project release version. @@ -283,13 +287,13 @@ cdef int gp_GetVertexDegree(graphP theGraph, int v): cdef int gp_IsNeighborDirected( - graphP theGraph, int u, int v, unsigned direction + graphP theGraph, int u, int v, unsigned int direction ): return cgraphLib.gp_IsNeighborDirected(theGraph, u, v, direction) cdef int gp_FindDirectedEdge( - graphP theGraph, int u, int v, unsigned direction + graphP theGraph, int u, int v, unsigned int direction ): return cgraphLib.gp_FindDirectedEdge(theGraph, u, v, direction) @@ -316,10 +320,26 @@ cdef int gp_InsertEdge( return cgraphLib.gp_InsertEdge(theGraph, u, e_u, e_ulink, v, e_v, e_vlink) +cdef int gp_DynamicInsertEdge( + graphP theGraph, int u, int e_u, int e_ulink, int v, int e_v, int e_vlink +): + return cgraphLib.gp_DynamicInsertEdge( + theGraph, u, e_u, e_ulink, v, e_v, e_vlink + ) + + cdef int gp_DeleteEdge(graphP theGraph, int e): return cgraphLib.gp_DeleteEdge(theGraph, e) +cdef int gp_ClearEdgeDirectionFlags(graphP theGraph): + return cgraphLib.gp_ClearEdgeDirectionFlags(theGraph) + + +cdef int gp_TransposeDirectedGraph(graphP theGraph): + return cgraphLib.gp_TransposeDirectedGraph(theGraph) + + cdef void gp_HideEdge(graphP theGraph, int e): cgraphLib.gp_HideEdge(theGraph, e) @@ -348,7 +368,7 @@ cdef int gp_RestoreVertices(graphP theGraph): return cgraphLib.gp_RestoreVertices(theGraph) -cdef int gp_GetGraphFlags(graphP theGraph): +cdef unsigned int gp_GetGraphFlags(graphP theGraph): return cgraphLib.gp_GetGraphFlags(theGraph) @@ -661,6 +681,10 @@ cdef int g6_InitWriterWithFileName( ) +cdef void g6_SetOutputErrorFlag(G6WriteIteratorP theG6WriteIterator): + cgraphLib.g6_SetOutputErrorFlag(theG6WriteIterator) + + cdef int g6_WriteGraph(G6WriteIteratorP theG6WriteIterator): return cgraphLib.g6_WriteGraph(theG6WriteIterator) @@ -682,7 +706,7 @@ cdef int gp_DepthFirstSearch(graphP theGraph): return cgraphLib.gp_DepthFirstSearch(theGraph) -cdef int gp_DepthFirstSearchEx(graphP theGraph, unsigned mode): +cdef int gp_DepthFirstSearchEx(graphP theGraph, unsigned int mode): return cgraphLib.gp_DepthFirstSearchEx(theGraph, mode) @@ -698,10 +722,18 @@ cdef int gp_ComputeLeastAncestors(graphP theGraph): return cgraphLib.gp_ComputeLeastAncestors(theGraph) +cdef int gp_CountConnectedComponents(graphP theGraph): + return cgraphLib.gp_CountConnectedComponents(theGraph) + + cdef int gp_GetParent(graphP theGraph, int v): return cgraphLib.gp_GetParent(theGraph, v) +cdef int gp_GetVisitedIndex(graphP theGraph, int v): + return cgraphLib.gp_GetVisitedIndex(theGraph, v) + + cdef int gp_GetLeastAncestor(graphP theGraph, int v): return cgraphLib.gp_GetLeastAncestor(theGraph, v) @@ -710,15 +742,12 @@ cdef int gp_GetLowpoint(graphP theGraph, int v): return cgraphLib.gp_GetLowpoint(theGraph, v) -# FIXME: These EAPS macros call private gp_GetVertexParent() rather than -# public gp_GetParent(), so we have implemented them in graph.pyx for now -# and should switch to these commented-out versions once EAPS is fixed. -# cdef int gp_IsDFSTreeRoot(graphP theGraph, int v): -# return cgraphLib.gp_IsDFSTreeRoot(theGraph, v) +cdef int gp_IsDFSTreeRoot(graphP theGraph, int v): + return cgraphLib.gp_IsDFSTreeRoot(theGraph, v) -# cdef int gp_IsNotDFSTreeRoot(graphP theGraph, int v): -# return cgraphLib.gp_IsNotDFSTreeRoot(theGraph, v) +cdef int gp_IsNotDFSTreeRoot(graphP theGraph, int v): + return cgraphLib.gp_IsNotDFSTreeRoot(theGraph, v) cdef int gp_GetBicompRootFromDFSChild(graphP theGraph, int c): @@ -728,11 +757,9 @@ cdef int gp_GetBicompRootFromDFSChild(graphP theGraph, int c): cdef int gp_GetDFSChildFromBicompRoot(graphP theGraph, int R): return cgraphLib.gp_GetDFSChildFromBicompRoot(theGraph, R) -# FIXME: This EAPS macro calls private gp_GetVertexParent() rather than -# public gp_GetParent(), so we have implemented it in graph.pyx for now -# and should switch to this commented-out version once EAPS is fixed. -# cdef int gp_GetVertexFromBicompRoot(graphP theGraph, int R): -# return cgraphLib.gp_GetVertexFromBicompRoot(theGraph, R) + +cdef int gp_GetVertexFromBicompRoot(graphP theGraph, int R): + return cgraphLib.gp_GetVertexFromBicompRoot(theGraph, R) cdef int gp_IsBicompRoot(graphP theGraph, int v): @@ -768,11 +795,11 @@ cdef int gp_TestEmbedResultIntegrity( ) -cdef int gp_GetEmbedFlags(graphP theGraph): +cdef unsigned int gp_GetEmbedFlags(graphP theGraph): return cgraphLib.gp_GetEmbedFlags(theGraph) -cdef unsigned gp_GetObstructionMinorType(graphP theGraph): +cdef unsigned int gp_GetObstructionMinorType(graphP theGraph): return cgraphLib.gp_GetObstructionMinorType(theGraph) From 4c761cdfee5ad52f2f0b44a266a92272874b8e8f Mon Sep 17 00:00:00 2001 From: "Wanda B.K. Boyer" Date: Tue, 13 Oct 2026 11:23:55 -0700 Subject: [PATCH 06/15] Fixes sphinx RST syntax for external link to EAPS pr #313 --- planarity/full/g6IterationUtils.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/planarity/full/g6IterationUtils.pyx b/planarity/full/g6IterationUtils.pyx index 629edda..649efeb 100644 --- a/planarity/full/g6IterationUtils.pyx +++ b/planarity/full/g6IterationUtils.pyx @@ -281,7 +281,7 @@ cdef class G6WriteIterator: This method ensures that when an error is encountered (either by the writer or during some process that outputs graphs using the writer), we clean up any output. See - `EAPS PR #313 <(https://github.com/graph-algorithms/edge-addition-planarity-suite/pull/313>`__ + `EAPS PR #313 `__. """ graphLib.g6_SetOutputErrorFlag(self._g6WriteIterator) From 03a64f83ea7662c08b076030493b0961a9b126da Mon Sep 17 00:00:00 2001 From: "Wanda B.K. Boyer" Date: Tue, 13 Oct 2026 14:20:37 -0700 Subject: [PATCH 07/15] Multiple fixes to docstrings, ensuring successful build of html and pdf documentation. * Fully enumerate options returned by `gp_GetGraphFlags()`. * Fixed latexpdf build error due to unordered list depth * Fixed reference error (i.e. :py:meth:`mapping` should be :py:meth:`~planarity.express.planarity.PGraph.mapping`). * Updated unordered lists to use `-` rather than `*`, and use `:` instead of `-` within list items for further descriptions. * Changed some line lengths for comments. * Fixed some whitespace issues and issue with `#` comment not followed by a space highlighted by `cython-lint`. * Fixed some cross-references by searching for "See". * Added external link to planarity Issue #91 in docstring for `PGraph.draw()` I've noticed some issues rendering `make latexpdf`, namely that the first line of some list items is bolded, and noticed some issues with line breaks in `make html` output. --- doc/source/conf.py | 6 ++++ planarity/express/planarity.pyx | 39 +++++++++++---------- planarity/express/planarity_functions.py | 28 +++++++-------- planarity/full/graph.pyx | 43 +++++++++++++++++------- planarity/full/graphLib.pyx | 2 +- 5 files changed, 69 insertions(+), 49 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index d0651f1..da05577 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -93,3 +93,9 @@ html_theme = 'alabaster' html_static_path = ['_static'] + +# Resolves error "! LaTeX Error: Too deeply nested." pertaining to unorderd +# lists +latex_elements = { + "maxlistdepth": "7", +} diff --git a/planarity/express/planarity.pyx b/planarity/express/planarity.pyx index afe63c6..d24eafa 100644 --- a/planarity/express/planarity.pyx +++ b/planarity/express/planarity.pyx @@ -450,7 +450,7 @@ cdef class PGraph: Returns: The ASCII string rendition produced by the C-layer method - ``gp_DrawPlanar_RenderToString()`` + ``gp_DrawPlanar_RenderToString()``. Raises: RuntimeError: if an error was encountered by C-layer methods @@ -484,23 +484,23 @@ cdef class PGraph: return py_bytes.decode('ascii') - def draw( self, bool labels=True, str outfileName=None, str label_type='default', **kwargs ) -> None: """Draws the graph using Matplotlib, if it is planar. - If the graph is planar, then it is drawn as a figure within - Matplotlib and then saved to ``outfileName``, if given. + If the graph is planar, then it is drawn as a figure within Matplotlib + and then saved to ``outfileName``, if given. Args: labels (bool): If ``True``, vertex labels are rendered in the drawing. Otherwise, vertices are rendered unlabelled in the drawing. If a label's rendered width would exceed its vertex's own bounding rectangle, the label is truncated with a trailing - ``...`` so it stays within that rectangle (see Issue #91). The - vertex's geometry itself is never resized to accommodate a + ``...`` so it stays within that rectangle (see + `Planarity Issue #91 `__). + The vertex's geometry itself is never resized to accommodate a label. outfileName (:obj:`str`): File to which to output a Matplotlib rendering of the planar graph. If not given, then the caller can @@ -508,37 +508,36 @@ cdef class PGraph: label_type (:obj:`str`): Selects which text identifies each vertex when ``labels`` is ``True`` : - * ``'default'`` - The current behavior, which is to show the + - ``'default'``: The current behaviour, which is to show the original labels given when the graph was converted to a :py:class:`~planarity.express.planarity.PGraph`. - * ``'label'`` - Show the original labels. - * ``'index'`` - Show the numeric vertex indexes assigned by - the :py:class:`~planarity.express.planarity.PGraph` - (see :py:meth:`mapping`). + - ``'label'``: Show the original labels. + - ``'index'``: Show the numeric vertex indexes assigned by + the :py:class:`~planarity.express.planarity.PGraph` (see + :py:meth:`~planarity.express.planarity.PGraph.mapping`). Defaults to ``'default'``. **kwargs: Optional figure-level parameters to apply to the current figure after it is cleared. Supported keys are: - * ``figsize`` and ``dpi`` - Control figure size and - resolution. - * ``pad_inches`` - The image is saved with a tight bounding + - ``figsize`` and ``dpi``: Control figure size and resolution. + - ``pad_inches``: The image is saved with a tight bounding box (``bbox_inches="tight"``), where ``pad_inches`` gives the padding, in inches, around the drawing. If not given, ``pad_inches`` defaults to 0.1, while a value of 0.0 causes the drawing to extend to the borders of the image. - * ``facecolor`` - Sets the figure background (default + - ``facecolor``: Sets the figure background (default ``'#ffffff'``). - * ``vertex_facecolor`` and ``vertex_bordercolor`` set vertex + - ``vertex_facecolor`` and ``vertex_bordercolor`` set vertex rectangle colors; omitted values use Matplotlib's patch defaults. - * ``vertex_label_facecolor`` and ``vertex_label_bordercolor`` - + - ``vertex_label_facecolor`` and ``vertex_label_bordercolor``: Set label rectangle colors (defaults ``'white'`` and ``'black'``). - * ``edge_linecolor`` - Set edge colors (default Matplotlib line + - ``edge_linecolor``: Set edge colors (default Matplotlib line color). - * ``transparent`` (default ``False``) makes the figure and axes + - ``transparent`` (default ``False``) makes the figure and axes backgrounds transparent when saving to ``outfileName``; it does not remove matching colors from vertices or labels. @@ -576,7 +575,7 @@ cdef class PGraph: "planarity: Unable to draw() graph due to error encountered in " "embedding workflow." ) - + ############################### # Prepare for graphic rendering ############################### diff --git a/planarity/express/planarity_functions.py b/planarity/express/planarity_functions.py index 325c73f..c9e0ce5 100644 --- a/planarity/express/planarity_functions.py +++ b/planarity/express/planarity_functions.py @@ -107,9 +107,7 @@ def draw(graph, labels=True, outfileName=None, label_type='default', **kwargs): """Draws the graph with Matplotlib, if it is planar. - Constructs a - :py:class:`~planarity.express.planarity.PGraph` - and calls its + Constructs a :py:class:`~planarity.express.planarity.PGraph` and calls its :py:meth:`~planarity.express.planarity.PGraph.draw` method with the given ``labels``, ``outfileName``, and ``label_type``. @@ -122,28 +120,28 @@ def draw(graph, labels=True, outfileName=None, label_type='default', rendering of the planar graph. If not given, then the caller can call :external+matplotlib:py:func:`matplotlib.pyplot.savefig`. label_type (:obj:`str`): Selects which text identifies each vertex - when ``labels`` is ``True`` : + when ``labels`` is ``True``: - * ``'default'`` - The current behavior, which is to show the + - ``'default'``: The current behaviour, which is to show the original labels given when the graph was converted to a :py:class:`~planarity.express.planarity.PGraph`. - * ``'label'`` - Show the original labels. - * ``'index'`` - Show the numeric vertex indexes assigned by + - ``'label'``: Show the original labels. + - ``'index'``: Show the numeric vertex indexes assigned by the :py:class:`~planarity.express.planarity.PGraph` - (see :py:meth:`mapping`). + (see :py:meth:`~planarity.express.planarity.PGraph.mapping`). Defaults to ``'default'``. **kwargs: Optional figure-level parameters forwarded to :py:meth:`~planarity.express.planarity.PGraph.draw`. Supported keys are: - * ``figsize`` and ``dpi`` - * ``pad_inches`` - * ``facecolor`` - * ``vertex_facecolor`` and ``vertex_bordercolor`` - * ``vertex_label_facecolor`` and ``vertex_label_bordercolor`` - * ``edge_linecolor`` - * ``transparent`` + - ``figsize`` and ``dpi`` + - ``pad_inches`` + - ``facecolor`` + - ``vertex_facecolor`` and ``vertex_bordercolor`` + - ``vertex_label_facecolor`` and ``vertex_label_bordercolor`` + - ``edge_linecolor`` + - ``transparent`` Please see the :py:meth:`~planarity.express.planarity.PGraph.draw` documentation for more information and for default values. diff --git a/planarity/full/graph.pyx b/planarity/full/graph.pyx index 00b0f5a..38b3325 100644 --- a/planarity/full/graph.pyx +++ b/planarity/full/graph.pyx @@ -601,7 +601,8 @@ cdef class Graph: """Hides the edge with index ``e`` from the adjacency list of the graph. The edge still exists in edge storage but has been unhooked from the - adjacency lists of its endpoint vertices. See ``gp_RestoreEdge()``. + adjacency lists of its endpoint vertices. See + :py:meth:`~planarity.full.graph.Graph.gp_RestoreEdge`. Args: e: index of the edge in the graph to hide. @@ -717,7 +718,15 @@ cdef class Graph: bitwise-and with the following constants to determine whether the given flag is set on the graph: - * **FIXME:** ENUMERATE ALL POSSIBLE VALUES + * ``GRAPHFLAGS_DIRECTEDEDGEDETECTED`` + * ``GRAPHFLAGS_EXTENDEDWITH_DFSUTILS`` + * ``GRAPHFLAGS_DFSNUMBERED`` + * ``GRAPHFLAGS_SORTEDBYDFI`` + * ``GRAPHFLAGS_LOWPOINTSCOMPUTED`` + * ``GRAPHFLAGS_DFSNUMBERED_DIRECTED`` + * ``GRAPHFLAGS_ZEROBASEDIO`` + * ``GRAPHFLAGS_EXTENDEDWITH_PLANARITY`` + * ``GRAPHFLAGS_EXTENDEDWITH_OUTERPLANARITY`` """ return graphLib.gp_GetGraphFlags(self._theGraph) @@ -2055,11 +2064,14 @@ cdef class Graph: """Performs an undirected depth-first search (DFS) on the graph. Gives vertices a value for their depth first indexes (DFIs) and DFS - parents, and gives edges a value for their type. See ``gp_GetParent()``, - ``gp_GetIndex()``, and ``gp_GetEdgeType()``. + parents, and gives edges a value for their type. See + :py:meth:`~planarity.full.graph.Graph.gp_GetParent`, + :py:meth:`~planarity.full.graph.Graph.gp_GetIndex`, and + :py:meth:`~planarity.full.graph.Graph.gp_GetEdgeType`. This method sets ``GRAPHFLAGS_DFSNUMBERED``. This method also begins by - performing ``gp_ExtendWith_DFSUtils()``, if not already done. + performing C-layer ``gp_ExtendWith_DFSUtils()``, if not already done + by calling :py:meth:`~planarity.full.graph.Graph.gp_ExtendWith_DFSUtils`. This method clears ``GRAPHFLAGS_DFSNUMBERED_DIRECTED``. @@ -2076,15 +2088,19 @@ cdef class Graph: """Performs a depth-first search (DFS) on the graph, based on the ``mode``. Gives vertices a value for their depth-first indexes (DFIs) and DFS - parents, and gives edges a value for their type. See ``gp_GetParent()``, - ``gp_GetIndex()``, and ``gp_GetEdgeType()``. The settings given depend - on whether an undirected or directed depth-first search was performed. + parents, and gives edges a value for their type. See + :py:meth:`~planarity.full.graph.Graph.gp_GetParent`, + :py:meth:`~planarity.full.graph.Graph.gp_GetIndex`, and + :py:meth:`~planarity.full.graph.Graph.gp_GetEdgeType`. The settings + given depend on whether an undirected or directed depth-first search was + performed. + Upon completion this method sets either ``GRAPHFLAGS_DFSNUMBERED`` or ``GRAPHFLAGS_DFSNUMBERED_DIRECTED``, depending on the ``mode``. The - undirected DFS is performed by ``gp_DepthFirstSearch()``. + undirected DFS is performed by C-layer ``gp_DepthFirstSearch()``. - This method also begins by performing ``gp_ExtendWith_DFSUtils()``, - if not already done. + This method also begins by performing C- layer + ``gp_ExtendWith_DFSUtils()``, if not already done. Args: mode: ``DFSMODE_DIRECTED`` or ``DFSMODE_UNDIRECTED``. @@ -2128,7 +2144,7 @@ cdef class Graph: This method first performs ``gp_DepthFirstSearch()`` and ``gp_SortVertices()`` if they have not already been done. - This method sets GRAPHFLAGS_LOWPOINTSCOMPUTED. + This method sets ``GRAPHFLAGS_LOWPOINTSCOMPUTED``. Raises: RuntimeError: if the C-layer ``graphLib`` version of this function @@ -2395,7 +2411,8 @@ cdef class Graph: This method essentially just returns whether ``v`` is a virtual vertex; it may not be in use in a separate biconnected component. To test if a - bicomp root is in use, please see ``gp_VirtualVertexInUse()``. + bicomp root is in use, please see + :py:meth:`~planarity.full.graph.Graph.gp_VirtualVertexInUse`. Args: v: a vertex in the graph which you wish to determine is the root diff --git a/planarity/full/graphLib.pyx b/planarity/full/graphLib.pyx index 08469bc..423ab9a 100644 --- a/planarity/full/graphLib.pyx +++ b/planarity/full/graphLib.pyx @@ -169,7 +169,7 @@ def gp_GetRandomNumber(int NMin, int NMax) -> int: return cgraphLib.gp_GetRandomNumber(NMin, NMax) -#NOTE: Using name mangling to hide this method from users who import planarity +# NOTE: Using name mangling to hide this method from users who import planarity # Confirmed that with `import planarity`, `dir(planarity)` in an IDLE session # didn't include this method, and with `from planarity import *`, `dir()` didn't # include `__seed_rng()`. From ec2ba470a0614da9705248beb5e66db14f7d1baa Mon Sep 17 00:00:00 2001 From: "Wanda B.K. Boyer" Date: Tue, 13 Oct 2026 16:12:15 -0700 Subject: [PATCH 08/15] Removed `__seed_rng()` function from `planarity.full.graphLib` module, since we don't need to expose this function to users, and calling `srand()` from within `graphLib` module works to seed the random number generator at module initialization (only occurs once upon import). Confirmed by: - Running `examples/full/demo_random_int.py` - In separate IDLE sessions: - when I `import planarity` and then call `print(planarity.gp_GetRandomNumber(1, 10))` multiple times, the numbers are all different and don't follow a pattern - when I call `from planarity import *` and call `print(gp_GetRandomNumber(1, 10))` multiple times, the numbers are all different and don't follow a pattern --- planarity/__init__.py | 6 ------ planarity/full/graphLib.pyx | 26 +++++++++++--------------- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/planarity/__init__.py b/planarity/__init__.py index 94c0afb..1867cf5 100644 --- a/planarity/__init__.py +++ b/planarity/__init__.py @@ -113,12 +113,6 @@ from .full.g6IterationUtils import G6ReadIterator, G6WriteIterator -# Seed the random number generator in the C library so that the -# random number and random graph generators will produce different -# outputs on each run of a script. -from .full import graphLib -getattr(graphLib, "__seed_rng")() - # NOTE: In the future, we could automatically generate the version number by # configuring setuptools-scm, but presently this seems simpler. __version__ = "1.0.1" diff --git a/planarity/full/graphLib.pyx b/planarity/full/graphLib.pyx index 423ab9a..ffe5861 100644 --- a/planarity/full/graphLib.pyx +++ b/planarity/full/graphLib.pyx @@ -9,11 +9,22 @@ to expose the G6 read and write iterator machinery. NOTE: No bounds/error checking is done here, and is rather the responsibility of the caller to make sure these functions are being called legitimately. """ +import random + from planarity.full cimport cgraphLib from planarity.full.cgraphLib cimport graphP, G6ReadIteratorP, G6WriteIteratorP from libc.stdlib cimport srand + +# Seeds the pseudo-random number generator used by gp_GetRandomNumber() and the +# random graph generator methods upon module initialization. +# We seed with a random int generated by Python because Python seeds +# its random number generator with a true source of entropy when +# available and only falls back to system time otherwise. +srand(random.randint(0, 4294967295)) + + # Surfaced from planarityc/graphLib/lowLevelUtils/apiutils.h QUIETMODE_NONE = cgraphLib.QUIETMODE_NONE QUIETMODE_ERRORS = cgraphLib.QUIETMODE_ERRORS @@ -169,21 +180,6 @@ def gp_GetRandomNumber(int NMin, int NMax) -> int: return cgraphLib.gp_GetRandomNumber(NMin, NMax) -# NOTE: Using name mangling to hide this method from users who import planarity -# Confirmed that with `import planarity`, `dir(planarity)` in an IDLE session -# didn't include this method, and with `from planarity import *`, `dir()` didn't -# include `__seed_rng()`. -def __seed_rng() -> None: - # Called by ``__init__.py`` to seed the pseudo-random number generator - # used by :py:func:`~planarity.full.graphLib.gp_GetRandomNumber` and the - # random graph generator methods. - # We seed with a random ``int`` generated by Python because Python seeds - # its random number generator with a true source of entropy when - # available and only falls back to system time otherwise. - import random - srand(random.randint(0, 4294967295)) - - def gp_GetProjectVersionFull() -> str: """Returns the Edge Addition Planarity Suite's project release version. From 4a34793b098d9b902eb847dfdafbd9036dd8b6ba Mon Sep 17 00:00:00 2001 From: "Wanda B.K. Boyer" Date: Wed, 14 Oct 2026 15:59:44 -0700 Subject: [PATCH 09/15] Surfacing `gp_CreateEmbeddingFaceList()` and `gp_CountEmbeddingFaces()`. Relates to https://github.com/graph-algorithms/planarity/issues/9 --- planarity/full/cgraphLib.pxd | 3 ++ planarity/full/graph.pyx | 59 ++++++++++++++++++++++++++++++++++-- planarity/full/graphLib.pxd | 3 ++ planarity/full/graphLib.pyx | 8 +++++ 4 files changed, 71 insertions(+), 2 deletions(-) diff --git a/planarity/full/cgraphLib.pxd b/planarity/full/cgraphLib.pxd index 4dd27b8..eb24eb7 100644 --- a/planarity/full/cgraphLib.pxd +++ b/planarity/full/cgraphLib.pxd @@ -273,6 +273,9 @@ cdef extern from "../c/graphLib/planarityRelated/graphPlanarity.h": int gp_Embed(graphP theGraph, unsigned int embedFlags) int gp_TestEmbedResultIntegrity(graphP theGraph, graphP origGraph, int embedResult) + int gp_CountEmbeddingFaces(graphP theGraph) + int gp_CreateEmbeddingFaceList(graphP theGraph, char **pFaceList) + int NONEMBEDDABLE unsigned int gp_GetEmbedFlags(graphP theGraph) diff --git a/planarity/full/graph.pyx b/planarity/full/graph.pyx index 38b3325..f9d66e7 100644 --- a/planarity/full/graph.pyx +++ b/planarity/full/graph.pyx @@ -2007,8 +2007,10 @@ cdef class Graph: ValueError: if writeMode is not ``WRITE_ADJLIST``, ``WRITE_ADJMATRIX``, ``WRITE_G6``, or ``WRITE_GRAPHML``. RuntimeError: if the C-layer ``graphLib`` version of this function - fails, if the ``outputString`` is ``NULL``, or if decoding the - bytes to produce the Python string fails. + fails. + RuntimeError: if the ``outputString`` is ``NULL``. + RuntimeError: if decoding the bytes to produce the Python string + fails. """ if writeMode not in (WRITE_ADJLIST, WRITE_ADJMATRIX, WRITE_G6, WRITE_GRAPHML): raise ValueError( @@ -2557,6 +2559,57 @@ cdef class Graph: return check_result + def gp_CountEmbeddingFaces(self) -> int: + """Traverses planar embedding to count all faces. + + Returns: + The number of faces if the traversal succeeds and the face count + agrees with the extended Euler formula. + + Raises: + RuntimeError: if C-layer ``graphLib`` version of this function + failed. + """ + result = graphLib.gp_CountEmbeddingFaces(self._theGraph) + + if result == -1: + raise RuntimeError( + "Underlying gp_CountEmbeddingFaces() reported failure." + ) + + return result + + def gp_CreateEmbeddingFaceList(self) -> str: + """Returns a YAML string containing the face list of a planar embedding. + + Returns: + A YAML string containing one face list section per connected + component of a planar embedding. + + Raises: + RuntimeError: if the C-layer ``graphLib`` version of this function + failed. + RuntimeError: if decoding the bytes to produce the Python string + fails. + """ + cdef char* faceList = NULL + + result = graphLib.gp_CreateEmbeddingFaceList(self._theGraph, &faceList) + if result != OK: + raise RuntimeError( + "gp_CreateEmbeddingFaceList() failed: unable to create YAML " + "string containing embedding face list." + ) + + face_list_bytes = faceList[:] + free(faceList) + try: + return face_list_bytes.decode('ascii') + except Exception as string_conversion_error: + raise RuntimeError( + "Failed to convert C string to Python string." + ) from string_conversion_error + def gp_GetEmbedFlags(self) -> unsigned: """Gets the ``embedFlags`` of the graph. @@ -2677,6 +2730,8 @@ cdef class Graph: Raises: RuntimeError: if the C-layer ``graphLib`` version of this function fails. + RuntimeError: if decoding the bytes to produce the Python string + fails. """ cdef char* renditionString = NULL diff --git a/planarity/full/graphLib.pxd b/planarity/full/graphLib.pxd index b5d2ec2..0d58e56 100644 --- a/planarity/full/graphLib.pxd +++ b/planarity/full/graphLib.pxd @@ -212,6 +212,9 @@ cdef int gp_Detach_Planarity(graphP theGraph) cdef int gp_Embed(graphP theGraph, unsigned int embedFlags) cdef int gp_TestEmbedResultIntegrity(graphP theGraph, graphP origGraph, int embedResult) +cdef int gp_CountEmbeddingFaces(graphP theGraph) +cdef int gp_CreateEmbeddingFaceList(graphP theGraph, char **pFaceList) + cdef unsigned int gp_GetEmbedFlags(graphP theGraph) cdef unsigned int gp_GetObstructionMinorType(graphP theGraph) diff --git a/planarity/full/graphLib.pyx b/planarity/full/graphLib.pyx index ffe5861..22dfbdf 100644 --- a/planarity/full/graphLib.pyx +++ b/planarity/full/graphLib.pyx @@ -791,6 +791,14 @@ cdef int gp_TestEmbedResultIntegrity( ) +cdef int gp_CountEmbeddingFaces(graphP theGraph): + return cgraphLib.gp_CountEmbeddingFaces(theGraph) + + +cdef int gp_CreateEmbeddingFaceList(graphP theGraph, char **pFaceList): + return cgraphLib.gp_CreateEmbeddingFaceList(theGraph, pFaceList) + + cdef unsigned int gp_GetEmbedFlags(graphP theGraph): return cgraphLib.gp_GetEmbedFlags(theGraph) From 6f6897d5eaf0e5c8cf0dd758a866e3d1a1ffb5bc Mon Sep 17 00:00:00 2001 From: "Wanda B.K. Boyer" Date: Wed, 14 Oct 2026 16:49:43 -0700 Subject: [PATCH 10/15] First draft of embedding face lister demo script. --- examples/full/demo_embedding_facelister.py | 232 +++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 examples/full/demo_embedding_facelister.py diff --git a/examples/full/demo_embedding_facelister.py b/examples/full/demo_embedding_facelister.py new file mode 100644 index 0000000..d7dc945 --- /dev/null +++ b/examples/full/demo_embedding_facelister.py @@ -0,0 +1,232 @@ +""" +A simple script that imports the planarity Cython package, reads in a graph, +calls gp_Embed() and tests embed result integrity, then writes the result of the +embedding operation to file (either the obstruction to planarity or a planar +embedding). Then, if the graph is planar, creates the embedding face list and +writes the YAML string to file. + +Functions: + def write_embedding_facelist( + infile: Path, + outdir: Optional[Path], + graph_output_mode: str, + ) -> None +""" +#!/usr/bin/env python + +# STANDARD LIBRARY IMPORTS +import argparse +from pathlib import Path +import re +from typing import Optional + +# THIRD PARTY IMPORTS +from planarity import ( + QUIETMODE_NONE, + gp_SetQuietMode, + OK, + NONEMBEDDABLE, + NOTOK, + WRITE_ADJLIST, + WRITE_ADJMATRIX, + WRITE_G6, + WRITE_GRAPHML, + EMBEDFLAGS_PLANAR, + Graph, +) + +# LOCAL IMPORTS +from planarity_app_utils import ( + PLANARITY_PACKAGE_INFO, + PLANARITY_ALGORITHM_SPECIFIERS, + ALGORITHM_SPECIFIER_NAME_CORRESPONDENCE, + ALGORITHM_SPECIFIER_OUTPUT_CORRESPONDENCE, + EMBED_RESULT_NAME_CORRESPONDENCE, + extend_graph, +) + + +def write_embedding_facelist( + infile: Path, + outdir: Optional[Path], + graph_output_mode: str, +) -> int: + """Perform embedding operation and output face list for planar embedding. + + Writes the result of the embedding operation to file (either the obstruction + to planarity or a planar embedding). Then, if the graph is planar, creates + the embedding face list and writes the YAML string to file before returning + the number of faces in the embedding. + + Args: + infile: name of graph input file to read + outdir: parent directory under which to make output directory + graph_output_mode: the desired output format for the result of the embed + operation, which may be one of: + + - ``WRITE_ADJLIST`` + - ``WRITE_ADJMATRIX`` + - ``WRITE_G6`` + - ``WRITE_GRAPHML`` + + Returns: + The number of faces of the embedding, or -1 if the graph is + nonembeddable. + + Raises: + ValueError: If the ``output_mode`` is invalid. + ValueError: If the ``outdir`` is invalid. + RuntimeError: If unable to extend graph with structures necessary for + planarity extension. + RuntimeError: If embedding result does not match integrity check result. + RuntimeError: If creating the embedding face list failed. + RuntimeError: If string containing embedding face list is falsy. + IOError: If writing the YAML string representing the embedding + face list to file failed. + RuntimeError: If the ``gp_CountEmbeddingFaces()`` method failed. + """ + gp_SetQuietMode(QUIETMODE_NONE) + + if ( + graph_output_mode not in ( + WRITE_ADJLIST, WRITE_ADJMATRIX, WRITE_G6, WRITE_GRAPHML + ) + ): + raise ValueError( + f"Invalid output_mode = {graph_output_mode}" + ) + + if outdir is None: + outdir = infile.parent + if outdir.is_file(): + raise ValueError( + f"Output directory '{outdir}' corresponds to a file." + ) + + outdir = Path.joinpath(outdir, f"{infile.stem}") + + Path.mkdir(outdir, parents=True, exist_ok=True) + + outfile = Path.joinpath(outdir, f"{infile.stem}.p.out.txt") + + graph_for_embedding_check = Graph() + graph_for_embedding = Graph() + + graph_for_embedding_check.gp_Read(str(infile)) + order = graph_for_embedding_check.gp_GetN() + + graph_for_embedding.gp_EnsureVertexCapacity(order) + graph_for_embedding.gp_CopyGraph(graph_for_embedding_check) + + extend_graph(graph_for_embedding, "p") + + embed_result = graph_for_embedding.gp_Embed(EMBEDFLAGS_PLANAR) + if ( + graph_for_embedding.gp_TestEmbedResultIntegrity( + graph_for_embedding_check, embed_result + ) + != embed_result + ): + raise RuntimeError( + f"Failed embed integrity check on graph in '{infile}'." + ) + + if embed_result == OK or embed_result == NONEMBEDDABLE: + graph_for_embedding.gp_Write(str(outfile), graph_output_mode) + + if embed_result == NONEMBEDDABLE: + print( + f"Unable to produce embedding face list, as input graph '{infile}' " + "is nonplanar." + ) + return -1 + + face_list = graph_for_embedding.gp_CreateEmbeddingFaceList() + if not face_list: + raise RuntimeError( + "Received empty string when trying to create embedding face list." + ) + + face_list_outfile_path = Path.joinpath( + outdir, f"{infile.stem}.face-list.yaml" + ) + with open( + face_list_outfile_path, mode="w", encoding="utf8" + ) as face_list_outfile: + face_list_outfile.write(face_list) + + return graph_for_embedding.gp_CountEmbeddingFaces() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + formatter_class=argparse.RawTextHelpFormatter, + usage="python %(prog)s [options]", + description="Produces embedding face list for planar graph and counts " + "number of faces.\n", + ) + + parser.add_argument( + "-i", + "--infile", + required=True, + type=Path, + help="Path to graph input file containing a single input graph; if a " + ".g6 input file is specified, only the first graph in the file will be " + "processed.", + ) + parser.add_argument( + "-o", + "--outdir", + required=False, + type=Path, + default=None, + help="Path of directory in which to make the output directory which " + "will contain the output from performing the embedding operation, and " + "if the graph is planar, the YAML representation of the embedding " + "face list. Defaults to:\n" + "\t{infile_parent_dir}/{infile_stem}/\n" + "Where either the planar embedding or the obstruction to planarity will" + "be output to a file named:\n" + "\t{infile_stem}.p.out.txt\n" + "And the YAML file containing the face list will be output to a file " + "named:\n" + "\t{infile_stem}.face-list.yaml", + ) + + parser.add_argument( + "-m", + "--graphoutputmode", + type=str, + required=False, + default="a", + help="Desired graph output format: .g6 (g), .graphml (x), " + "Adjacency List (a), or Adjacency Matrix (m). Defaults to 'a'\n", + ) + + args = parser.parse_args() + + print(PLANARITY_PACKAGE_INFO()) + mode = args.graphoutputmode + graph_output_mode = (WRITE_ADJLIST if mode == "a" + else (WRITE_ADJMATRIX if mode == "m" + else (WRITE_G6 if mode == "g" + else (WRITE_GRAPHML if mode == "x" + else None)))) + + if not graph_output_mode: + raise ValueError( + f"Invalid argument for mode = {mode}; only accepted values are g " + "(.g6), x (.graphml), a (Adjacency List), or m (Adjacency Matrix)." + ) + + num_faces = write_embedding_facelist( + infile=args.infile, + outdir=args.outdir, + graph_output_mode=graph_output_mode, + ) + if num_faces > 0: + print( + f"Planar embedding has {num_faces} " + f"face{'s' if num_faces > 1 else ''}." + ) From cbe71bb4a15e31688f0030526199e1bb6909464e Mon Sep 17 00:00:00 2001 From: "Wanda B.K. Boyer" Date: Wed, 14 Oct 2026 17:28:02 -0700 Subject: [PATCH 11/15] Addressing PR comments --- planarity/express/planarity.pyx | 5 ++--- planarity/express/planarity_functions.py | 2 +- planarity/full/g6IterationUtils.pyx | 3 +-- planarity/full/graph.pyx | 10 +++++----- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/planarity/express/planarity.pyx b/planarity/express/planarity.pyx index d24eafa..015c843 100644 --- a/planarity/express/planarity.pyx +++ b/planarity/express/planarity.pyx @@ -498,8 +498,7 @@ cdef class PGraph: drawing. Otherwise, vertices are rendered unlabelled in the drawing. If a label's rendered width would exceed its vertex's own bounding rectangle, the label is truncated with a trailing - ``...`` so it stays within that rectangle (see - `Planarity Issue #91 `__). + ``...`` so it stays within that rectangle. The vertex's geometry itself is never resized to accommodate a label. outfileName (:obj:`str`): File to which to output a Matplotlib @@ -508,7 +507,7 @@ cdef class PGraph: label_type (:obj:`str`): Selects which text identifies each vertex when ``labels`` is ``True`` : - - ``'default'``: The current behaviour, which is to show the + - ``'default'``: The current behavior, which is to show the original labels given when the graph was converted to a :py:class:`~planarity.express.planarity.PGraph`. - ``'label'``: Show the original labels. diff --git a/planarity/express/planarity_functions.py b/planarity/express/planarity_functions.py index c9e0ce5..7263823 100644 --- a/planarity/express/planarity_functions.py +++ b/planarity/express/planarity_functions.py @@ -122,7 +122,7 @@ def draw(graph, labels=True, outfileName=None, label_type='default', label_type (:obj:`str`): Selects which text identifies each vertex when ``labels`` is ``True``: - - ``'default'``: The current behaviour, which is to show the + - ``'default'``: The current behavior, which is to show the original labels given when the graph was converted to a :py:class:`~planarity.express.planarity.PGraph`. - ``'label'``: Show the original labels. diff --git a/planarity/full/g6IterationUtils.pyx b/planarity/full/g6IterationUtils.pyx index 649efeb..45db130 100644 --- a/planarity/full/g6IterationUtils.pyx +++ b/planarity/full/g6IterationUtils.pyx @@ -280,8 +280,7 @@ cdef class G6WriteIterator: .. Note:: This method ensures that when an error is encountered (either by the writer or during some process that outputs graphs using the writer), - we clean up any output. See - `EAPS PR #313 `__. + we clean up any output. """ graphLib.g6_SetOutputErrorFlag(self._g6WriteIterator) diff --git a/planarity/full/graph.pyx b/planarity/full/graph.pyx index f9d66e7..7df6768 100644 --- a/planarity/full/graph.pyx +++ b/planarity/full/graph.pyx @@ -572,7 +572,7 @@ cdef class Graph: ) def gp_ClearEdgeDirectionFlags(self) -> None: - """Clears edge direction flags on each edge in the graph. + """Makes the graph undirected. Raises: RuntimeError: if the C-layer ``graphLib`` version of this function @@ -581,11 +581,11 @@ cdef class Graph: if graphLib.gp_ClearEdgeDirectionFlags(self._theGraph) != OK: raise RuntimeError( "gp_ClearEdgeDirectionFlags() failed: unable to clear edge " - "direction flags on the graph." + "direction flags on the graph (i.e., to make graph undirected)." ) def gp_TransposeDirectedGraph(self) -> None: - """Reverses every in-use directed edge in the graph. + """Reverses the direction of every in-use directed edge in the graph. Raises: RuntimeError: if the C-layer ``graphLib`` version of this function @@ -593,8 +593,8 @@ cdef class Graph: """ if graphLib.gp_TransposeDirectedGraph(self._theGraph) != OK: raise RuntimeError( - "gp_TransposeDirectedGraph() failed: unable to reverse every " - "in-use directed edge." + "gp_TransposeDirectedGraph() failed: unable to reverse the " + "direction of every in-use directed edge." ) def gp_HideEdge(self, int e) -> None: From 6bc3270c15d011033debde219a12479c084f1e06 Mon Sep 17 00:00:00 2001 From: "Wanda B.K. Boyer" Date: Wed, 14 Oct 2026 17:37:26 -0700 Subject: [PATCH 12/15] Per [this](https://github.com/graph-algorithms/planarity/pull/113#issuecomment-6178530063) comment, all flags will be considered as `int` rather than `unsigned`/`unsigned int`/`uint` --- doc/source/conf.py | 7 +------ planarity/full/cgraphLib.pxd | 14 +++++++------- planarity/full/graph.pyx | 16 ++++++++-------- planarity/full/graphLib.pxd | 14 +++++++------- planarity/full/graphLib.pyx | 14 +++++++------- 5 files changed, 30 insertions(+), 35 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index da05577..788a9c1 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -79,12 +79,7 @@ # Warn about all missing references nitpicky=True -nitpick_ignore = [ - ('py:class', 'unsigned'), - ('py:class', 'unsigned int'), - ('py:class', 'uint'), - ('py:class', 'cython.uint'), -] + # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output diff --git a/planarity/full/cgraphLib.pxd b/planarity/full/cgraphLib.pxd index eb24eb7..900fc8f 100644 --- a/planarity/full/cgraphLib.pxd +++ b/planarity/full/cgraphLib.pxd @@ -52,8 +52,8 @@ cdef extern from "../c/graphLib/graph.h": int gp_FindEdge(graphP theGraph, int u, int v) int gp_GetVertexDegree(graphP theGraph, int v) - int gp_IsNeighborDirected(graphP theGraph, int u, int v, unsigned int direction) - int gp_FindDirectedEdge(graphP theGraph, int u, int v, unsigned int direction) + int gp_IsNeighborDirected(graphP theGraph, int u, int v, int direction) + int gp_FindDirectedEdge(graphP theGraph, int u, int v, int direction) int gp_GetVertexInDegree(graphP theGraph, int v) int gp_GetVertexOutDegree(graphP theGraph, int v) @@ -75,7 +75,7 @@ cdef extern from "../c/graphLib/graph.h": int gp_RestoreVertices(graphP theGraph) int GRAPHFLAGS_DIRECTEDEDGEDETECTED - unsigned int gp_GetGraphFlags(graphP theGraph) + int gp_GetGraphFlags(graphP theGraph) int gp_GetFirstEdge(graphP theGraph, int v) int gp_GetLastEdge(graphP theGraph, int v) @@ -238,7 +238,7 @@ cdef extern from "../c/graphLib/graphDFSUtils.h": int DFSMODE_UNDIRECTED, DFSMODE_DIRECTED int gp_DepthFirstSearch(graphP theGraph) - int gp_DepthFirstSearchEx(graphP theGraph, unsigned int mode) + int gp_DepthFirstSearchEx(graphP theGraph, int mode) int gp_SortVertices(graphP theGraph) int gp_ComputeLowpoints(graphP theGraph) @@ -270,7 +270,7 @@ cdef extern from "../c/graphLib/planarityRelated/graphPlanarity.h": int GRAPHFLAGS_EXTENDEDWITH_PLANARITY - int gp_Embed(graphP theGraph, unsigned int embedFlags) + int gp_Embed(graphP theGraph, int embedFlags) int gp_TestEmbedResultIntegrity(graphP theGraph, graphP origGraph, int embedResult) int gp_CountEmbeddingFaces(graphP theGraph) @@ -278,12 +278,12 @@ cdef extern from "../c/graphLib/planarityRelated/graphPlanarity.h": int NONEMBEDDABLE - unsigned int gp_GetEmbedFlags(graphP theGraph) + int gp_GetEmbedFlags(graphP theGraph) int EMBEDFLAGS_PLANAR, EMBEDFLAGS_DRAWPLANAR, EMBEDFLAGS_OUTERPLANAR int EMBEDFLAGS_SEARCHFORK23, EMBEDFLAGS_SEARCHFORK33, EMBEDFLAGS_SEARCHFORK4 - unsigned int gp_GetObstructionMinorType(graphP theGraph) + int gp_GetObstructionMinorType(graphP theGraph) int MINORTYPE_NONE, MINORTYPE_A, MINORTYPE_B, MINORTYPE_C, MINORTYPE_D, MINORTYPE_E int MINORTYPE_E1, MINORTYPE_E2, MINORTYPE_E3, MINORTYPE_E4 diff --git a/planarity/full/graph.pyx b/planarity/full/graph.pyx index 7df6768..e7c1024 100644 --- a/planarity/full/graph.pyx +++ b/planarity/full/graph.pyx @@ -349,7 +349,7 @@ cdef class Graph: return graphLib.gp_GetVertexDegree(self._theGraph, v) def gp_IsNeighborDirected( - self, int u, int v, unsigned int direction + self, int u, int v, int direction ) -> int: """Checks if an edge in a given direction exists between two vertices. @@ -365,7 +365,7 @@ cdef class Graph: """ return graphLib.gp_IsNeighborDirected(self._theGraph, u, v, direction) - def gp_FindDirectedEdge(self, int u, int v, unsigned int direction) -> int: + def gp_FindDirectedEdge(self, int u, int v, int direction) -> int: """Finds a directed edge between ``u`` and ``v``, if it exists. Args: @@ -710,11 +710,11 @@ cdef class Graph: "hidden during an edge contraction or vertex identification." ) - def gp_GetGraphFlags(self) -> unsigned: + def gp_GetGraphFlags(self) -> int: """Returns the graph-level flags. Returns: - An unsigned integer representing the graph-level flags. Use + An integer representing the graph-level flags. Use bitwise-and with the following constants to determine whether the given flag is set on the graph: @@ -2086,7 +2086,7 @@ cdef class Graph: "gp_DepthFirstSearch() failed: unable to perform DFS on graph." ) - def gp_DepthFirstSearchEx(self, unsigned int mode) -> None: + def gp_DepthFirstSearchEx(self, int mode) -> None: """Performs a depth-first search (DFS) on the graph, based on the ``mode``. Gives vertices a value for their depth-first indexes (DFIs) and DFS @@ -2501,7 +2501,7 @@ cdef class Graph: "Failed to extend graph with Planarity structures." ) - def gp_Embed(self, unsigned int embedFlags) -> unsigned: + def gp_Embed(self, int embedFlags) -> int: """Embeds the graph or provides a minimal subgraph obstructing embedding. Modifies the graph to be either an embedding of the original graph or a @@ -2610,7 +2610,7 @@ cdef class Graph: "Failed to convert C string to Python string." ) from string_conversion_error - def gp_GetEmbedFlags(self) -> unsigned: + def gp_GetEmbedFlags(self) -> int: """Gets the ``embedFlags`` of the graph. Returns: @@ -2629,7 +2629,7 @@ cdef class Graph: """ return graphLib.gp_GetEmbedFlags(self._theGraph) - def gp_GetObstructionMinorType(self) -> unsigned: + def gp_GetObstructionMinorType(self) -> int: """Gets the obstruction type if :py:meth:`~planarity.full.graph.Graph.gp_Embed` result is ``NONEMBEDDABLE``. Returns: diff --git a/planarity/full/graphLib.pxd b/planarity/full/graphLib.pxd index 0d58e56..9ec05df 100644 --- a/planarity/full/graphLib.pxd +++ b/planarity/full/graphLib.pxd @@ -33,8 +33,8 @@ cdef int gp_IsNeighbor(graphP theGraph, int u, int v) cdef int gp_FindEdge(graphP theGraph, int u, int v) cdef int gp_GetVertexDegree(graphP theGraph, int v) -cdef int gp_IsNeighborDirected(graphP theGraph, int u, int v, unsigned int direction) -cdef int gp_FindDirectedEdge(graphP theGraph, int u, int v, unsigned int direction) +cdef int gp_IsNeighborDirected(graphP theGraph, int u, int v, int direction) +cdef int gp_FindDirectedEdge(graphP theGraph, int u, int v, int direction) cdef int gp_GetVertexInDegree(graphP theGraph, int v) cdef int gp_GetVertexOutDegree(graphP theGraph, int v) @@ -55,7 +55,7 @@ cdef int gp_ContractEdge(graphP theGraph, int e) cdef int gp_IdentifyVertices(graphP theGraph, int u, int v, int eBefore) cdef int gp_RestoreVertices(graphP theGraph) -cdef unsigned int gp_GetGraphFlags(graphP theGraph) +cdef int gp_GetGraphFlags(graphP theGraph) cdef int gp_GetFirstEdge(graphP theGraph, int v) cdef int gp_GetLastEdge(graphP theGraph, int v) @@ -182,7 +182,7 @@ cdef int gp_ExtendWith_DFSUtils(graphP theGraph) cdef int gp_Detach_DFSUtils(graphP theGraph) cdef int gp_DepthFirstSearch(graphP theGraph) -cdef int gp_DepthFirstSearchEx(graphP theGraph, unsigned int mode) +cdef int gp_DepthFirstSearchEx(graphP theGraph, int mode) cdef int gp_SortVertices(graphP theGraph) cdef int gp_ComputeLowpoints(graphP theGraph) cdef int gp_ComputeLeastAncestors(graphP theGraph) @@ -209,15 +209,15 @@ cdef int gp_IsNotSeparatedDFSChild(graphP theGraph, int theChild) cdef int gp_ExtendWith_Planarity(graphP theGraph) cdef int gp_Detach_Planarity(graphP theGraph) -cdef int gp_Embed(graphP theGraph, unsigned int embedFlags) +cdef int gp_Embed(graphP theGraph, int embedFlags) cdef int gp_TestEmbedResultIntegrity(graphP theGraph, graphP origGraph, int embedResult) cdef int gp_CountEmbeddingFaces(graphP theGraph) cdef int gp_CreateEmbeddingFaceList(graphP theGraph, char **pFaceList) -cdef unsigned int gp_GetEmbedFlags(graphP theGraph) +cdef int gp_GetEmbedFlags(graphP theGraph) -cdef unsigned int gp_GetObstructionMinorType(graphP theGraph) +cdef int gp_GetObstructionMinorType(graphP theGraph) # Surfaced from planarity/c/graphLib/planarityRelated/graphOuterplanarity.h diff --git a/planarity/full/graphLib.pyx b/planarity/full/graphLib.pyx index 22dfbdf..1447205 100644 --- a/planarity/full/graphLib.pyx +++ b/planarity/full/graphLib.pyx @@ -283,13 +283,13 @@ cdef int gp_GetVertexDegree(graphP theGraph, int v): cdef int gp_IsNeighborDirected( - graphP theGraph, int u, int v, unsigned int direction + graphP theGraph, int u, int v, int direction ): return cgraphLib.gp_IsNeighborDirected(theGraph, u, v, direction) cdef int gp_FindDirectedEdge( - graphP theGraph, int u, int v, unsigned int direction + graphP theGraph, int u, int v, int direction ): return cgraphLib.gp_FindDirectedEdge(theGraph, u, v, direction) @@ -364,7 +364,7 @@ cdef int gp_RestoreVertices(graphP theGraph): return cgraphLib.gp_RestoreVertices(theGraph) -cdef unsigned int gp_GetGraphFlags(graphP theGraph): +cdef int gp_GetGraphFlags(graphP theGraph): return cgraphLib.gp_GetGraphFlags(theGraph) @@ -702,7 +702,7 @@ cdef int gp_DepthFirstSearch(graphP theGraph): return cgraphLib.gp_DepthFirstSearch(theGraph) -cdef int gp_DepthFirstSearchEx(graphP theGraph, unsigned int mode): +cdef int gp_DepthFirstSearchEx(graphP theGraph, int mode): return cgraphLib.gp_DepthFirstSearchEx(theGraph, mode) @@ -779,7 +779,7 @@ cdef int gp_Detach_Planarity(graphP theGraph): return cgraphLib.gp_Detach_Planarity(theGraph) -cdef int gp_Embed(graphP theGraph, unsigned int embedFlags): +cdef int gp_Embed(graphP theGraph, int embedFlags): return cgraphLib.gp_Embed(theGraph, embedFlags) @@ -799,11 +799,11 @@ cdef int gp_CreateEmbeddingFaceList(graphP theGraph, char **pFaceList): return cgraphLib.gp_CreateEmbeddingFaceList(theGraph, pFaceList) -cdef unsigned int gp_GetEmbedFlags(graphP theGraph): +cdef int gp_GetEmbedFlags(graphP theGraph): return cgraphLib.gp_GetEmbedFlags(theGraph) -cdef unsigned int gp_GetObstructionMinorType(graphP theGraph): +cdef int gp_GetObstructionMinorType(graphP theGraph): return cgraphLib.gp_GetObstructionMinorType(theGraph) From 1dfd3649a43ce485fbbff58ec7a8752abc46e745 Mon Sep 17 00:00:00 2001 From: "Wanda B.K. Boyer" Date: Wed, 14 Oct 2026 17:53:32 -0700 Subject: [PATCH 13/15] Surfaced missing constants from `graphDFSUtils.h` and changed order of constants surfaced from `graphPlanarity.h` to match header order. --- planarity/__init__.py | 13 +++++++++++++ planarity/full/cgraphLib.pxd | 3 ++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/planarity/__init__.py b/planarity/__init__.py index 1867cf5..da630ef 100644 --- a/planarity/__init__.py +++ b/planarity/__init__.py @@ -61,6 +61,19 @@ WRITE_GRAPHML, GRAPHFLAGS_ZEROBASEDIO, ) + +# Surfaced from planarity/c/graphLib/graphDFSUtils.h +from .full.graphLib import ( + DFSUTILS_NAME, + GRAPHFLAGS_EXTENDEDWITH_DFSUTILS, + GRAPHFLAGS_DFSNUMBERED, + GRAPHFLAGS_SORTEDBYDFI, + GRAPHFLAGS_LOWPOINTSCOMPUTED, + GRAPHFLAGS_DFSNUMBERED_DIRECTED, + DFSMODE_UNDIRECTED, + DFSMODE_DIRECTED, +) + # Surfaced from planarity/c/graphLib/planarityRelated/graphPlanarity.h from .full.graphLib import ( PLANARITY_NAME, diff --git a/planarity/full/cgraphLib.pxd b/planarity/full/cgraphLib.pxd index 900fc8f..fb7e21f 100644 --- a/planarity/full/cgraphLib.pxd +++ b/planarity/full/cgraphLib.pxd @@ -280,7 +280,8 @@ cdef extern from "../c/graphLib/planarityRelated/graphPlanarity.h": int gp_GetEmbedFlags(graphP theGraph) - int EMBEDFLAGS_PLANAR, EMBEDFLAGS_DRAWPLANAR, EMBEDFLAGS_OUTERPLANAR + int EMBEDFLAGS_PLANAR, EMBEDFLAGS_OUTERPLANAR + int EMBEDFLAGS_DRAWPLANAR int EMBEDFLAGS_SEARCHFORK23, EMBEDFLAGS_SEARCHFORK33, EMBEDFLAGS_SEARCHFORK4 int gp_GetObstructionMinorType(graphP theGraph) From 91d7dff13395a5866796eb564c58dc7d32ed2716 Mon Sep 17 00:00:00 2001 From: "Wanda B.K. Boyer" Date: Wed, 14 Oct 2026 18:06:43 -0700 Subject: [PATCH 14/15] Fixed minor issues with `examples/full/demo_embedding_facelister.py`, including incorrect signature in module docstring, removing unused imports, moving output directory creation until after successful embedding operation, and `graph_output_mode` shadowed scope warning. --- examples/full/demo_embedding_facelister.py | 28 +++++++--------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/examples/full/demo_embedding_facelister.py b/examples/full/demo_embedding_facelister.py index d7dc945..433f203 100644 --- a/examples/full/demo_embedding_facelister.py +++ b/examples/full/demo_embedding_facelister.py @@ -10,23 +10,20 @@ def write_embedding_facelist( infile: Path, outdir: Optional[Path], graph_output_mode: str, - ) -> None + ) -> int """ #!/usr/bin/env python # STANDARD LIBRARY IMPORTS import argparse from pathlib import Path -import re from typing import Optional # THIRD PARTY IMPORTS from planarity import ( QUIETMODE_NONE, gp_SetQuietMode, - OK, NONEMBEDDABLE, - NOTOK, WRITE_ADJLIST, WRITE_ADJMATRIX, WRITE_G6, @@ -38,10 +35,6 @@ def write_embedding_facelist( # LOCAL IMPORTS from planarity_app_utils import ( PLANARITY_PACKAGE_INFO, - PLANARITY_ALGORITHM_SPECIFIERS, - ALGORITHM_SPECIFIER_NAME_CORRESPONDENCE, - ALGORITHM_SPECIFIER_OUTPUT_CORRESPONDENCE, - EMBED_RESULT_NAME_CORRESPONDENCE, extend_graph, ) @@ -103,12 +96,6 @@ def write_embedding_facelist( f"Output directory '{outdir}' corresponds to a file." ) - outdir = Path.joinpath(outdir, f"{infile.stem}") - - Path.mkdir(outdir, parents=True, exist_ok=True) - - outfile = Path.joinpath(outdir, f"{infile.stem}.p.out.txt") - graph_for_embedding_check = Graph() graph_for_embedding = Graph() @@ -131,8 +118,11 @@ def write_embedding_facelist( f"Failed embed integrity check on graph in '{infile}'." ) - if embed_result == OK or embed_result == NONEMBEDDABLE: - graph_for_embedding.gp_Write(str(outfile), graph_output_mode) + outdir = Path.joinpath(outdir, f"{infile.stem}") + Path.mkdir(outdir, parents=True, exist_ok=True) + + graph_outfile = Path.joinpath(outdir, f"{infile.stem}.p.out.txt") + graph_for_embedding.gp_Write(str(graph_outfile), graph_output_mode) if embed_result == NONEMBEDDABLE: print( @@ -208,13 +198,13 @@ def write_embedding_facelist( print(PLANARITY_PACKAGE_INFO()) mode = args.graphoutputmode - graph_output_mode = (WRITE_ADJLIST if mode == "a" + mode = (WRITE_ADJLIST if mode == "a" else (WRITE_ADJMATRIX if mode == "m" else (WRITE_G6 if mode == "g" else (WRITE_GRAPHML if mode == "x" else None)))) - if not graph_output_mode: + if not mode: raise ValueError( f"Invalid argument for mode = {mode}; only accepted values are g " "(.g6), x (.graphml), a (Adjacency List), or m (Adjacency Matrix)." @@ -223,7 +213,7 @@ def write_embedding_facelist( num_faces = write_embedding_facelist( infile=args.infile, outdir=args.outdir, - graph_output_mode=graph_output_mode, + graph_output_mode=mode, ) if num_faces > 0: print( From 7b8fc8ec67a3b8e53c92d6a6425b9eae7c4791a7 Mon Sep 17 00:00:00 2001 From: "Wanda B.K. Boyer" Date: Thu, 15 Oct 2026 16:10:01 -0700 Subject: [PATCH 15/15] `gp_GetObstructionMinorType()` will return `MINORTYPE_NONE` if `gp_Embed()` has not yet been called, so removing this TODO item. --- planarity/full/graph.pyx | 2 -- 1 file changed, 2 deletions(-) diff --git a/planarity/full/graph.pyx b/planarity/full/graph.pyx index e7c1024..886dd5c 100644 --- a/planarity/full/graph.pyx +++ b/planarity/full/graph.pyx @@ -2653,8 +2653,6 @@ cdef class Graph: if :py:meth:`~planarity.full.graph.Graph.gp_Embed` has not yet been run. """ - # TODO: Do we want to check that gp_Embed() has been called (i.e. check - # that the embed flags have been set) return graphLib.gp_GetObstructionMinorType(self._theGraph) def gp_ExtendWith_Outerplanarity(self) -> None: