diff --git a/doc/source/conf.py b/doc/source/conf.py index 95c8d84..788a9c1 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 @@ -88,3 +88,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/examples/full/demo_embedding_facelister.py b/examples/full/demo_embedding_facelister.py new file mode 100644 index 0000000..433f203 --- /dev/null +++ b/examples/full/demo_embedding_facelister.py @@ -0,0 +1,222 @@ +""" +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, + ) -> int +""" +#!/usr/bin/env python + +# STANDARD LIBRARY IMPORTS +import argparse +from pathlib import Path +from typing import Optional + +# THIRD PARTY IMPORTS +from planarity import ( + QUIETMODE_NONE, + gp_SetQuietMode, + NONEMBEDDABLE, + WRITE_ADJLIST, + WRITE_ADJMATRIX, + WRITE_G6, + WRITE_GRAPHML, + EMBEDFLAGS_PLANAR, + Graph, +) + +# LOCAL IMPORTS +from planarity_app_utils import ( + PLANARITY_PACKAGE_INFO, + 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." + ) + + 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}'." + ) + + 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( + 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 + 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 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=mode, + ) + if num_faces > 0: + print( + f"Planar embedding has {num_faces} " + f"face{'s' if num_faces > 1 else ''}." + ) diff --git a/planarity/__init__.py b/planarity/__init__.py index 69e2d5a..da630ef 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, ) @@ -30,6 +32,7 @@ # Surfaced from planarity/c/graphLib/graph.h from .full.graphLib import ( + GRAPHFLAGS_DIRECTEDEDGEDETECTED, VERTEX_VISITED_MASK, VERTEX_MARKED_MASK, EDGE_VISITED_MASK, @@ -39,6 +42,7 @@ EDGE_TYPE_FORWARD, EDGE_TYPE_PARENT, EDGE_TYPE_BACK, + EDGE_TYPE_CROSS, EDGE_TYPE_NOTDEFINED, EDGE_TYPE_TREE, EDGEFLAG_INVERTED_MASK, @@ -57,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, @@ -108,13 +125,6 @@ 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() # NOTE: In the future, we could automatically generate the version number by # configuring setuptools-scm, but presently this seems simpler. diff --git a/planarity/express/planarity.pyx b/planarity/express/planarity.pyx index afe63c6..015c843 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,22 @@ 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. + 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 +507,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 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. - * ``'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 +574,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..7263823 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 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. - * ``'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/cgraphLib.pxd b/planarity/full/cgraphLib.pxd index 106ffe4..fb7e21f 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, 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) 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,6 +74,7 @@ cdef extern from "../c/graphLib/graph.h": int gp_IdentifyVertices(graphP theGraph, int u, int v, int eBefore) int gp_RestoreVertices(graphP theGraph) + int GRAPHFLAGS_DIRECTEDEDGEDETECTED int gp_GetGraphFlags(graphP theGraph) int gp_GetFirstEdge(graphP theGraph, int v) @@ -143,10 +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_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) @@ -215,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) @@ -226,29 +231,31 @@ 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 GRAPHFLAGS_DFSNUMBERED_DIRECTED + + int DFSMODE_UNDIRECTED, DFSMODE_DIRECTED int gp_DepthFirstSearch(graphP theGraph) + int gp_DepthFirstSearchEx(graphP theGraph, 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) @@ -263,17 +270,21 @@ 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) + int gp_CreateEmbeddingFaceList(graphP theGraph, char **pFaceList) + int NONEMBEDDABLE 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 - unsigned 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/g6IterationUtils.pyx b/planarity/full/g6IterationUtils.pyx index e79cdfd..45db130 100644 --- a/planarity/full/g6IterationUtils.pyx +++ b/planarity/full/g6IterationUtils.pyx @@ -274,6 +274,16 @@ 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. + """ + 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 8d71767..886dd5c 100644 --- a/planarity/full/graph.pyx +++ b/planarity/full/graph.pyx @@ -14,30 +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 -EDGE_TYPE_CHILD = graphLib.EDGE_TYPE_CHILD -EDGE_TYPE_FORWARD = graphLib.EDGE_TYPE_FORWARD +GRAPHFLAGS_DIRECTEDEDGEDETECTED = graphLib.GRAPHFLAGS_DIRECTEDEDGEDETECTED + +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_TREE = graphLib.EDGE_TYPE_TREE -EDGE_TYPE_NOTDEFINED = graphLib.EDGE_TYPE_NOTDEFINED +EDGE_TYPE_FORWARD = graphLib.EDGE_TYPE_FORWARD +EDGE_TYPE_CROSS = graphLib.EDGE_TYPE_CROSS + +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 @@ -46,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. @@ -287,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, int direction + ) -> int: """Checks if an edge in a given direction exists between two vertices. Args: @@ -302,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, int direction) -> int: """Finds a directed edge between ``u`` and ``v``, if it exists. Args: @@ -460,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. @@ -476,11 +571,38 @@ cdef class Graph: f"gp_DeleteEdge() failed: unable to delete edge e = {e}" ) + def gp_ClearEdgeDirectionFlags(self) -> None: + """Makes the graph undirected. + + 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 (i.e., to make graph undirected)." + ) + + def gp_TransposeDirectedGraph(self) -> None: + """Reverses the direction of 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 the " + "direction of 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. 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. @@ -592,7 +714,19 @@ cdef class Graph: """Returns the graph-level flags. Returns: - An integer representing the graph-level flags. + 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: + + * ``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) @@ -1487,7 +1621,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 +1662,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 +1679,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 +1694,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 +1711,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( @@ -1871,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( @@ -1925,14 +2063,19 @@ 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()``. + 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 C-layer ``gp_ExtendWith_DFSUtils()``, if not already done + by calling :py:meth:`~planarity.full.graph.Graph.gp_ExtendWith_DFSUtils`. - This method also sets ``GRAPHFLAGS_DFSNUMBERED``. This method performs - ``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,15 +2086,51 @@ cdef class Graph: "gp_DepthFirstSearch() failed: unable to perform DFS on graph." ) + 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 + 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 C-layer ``gp_DepthFirstSearch()``. + + This method also begins by performing C- layer + ``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 @@ -1967,6 +2146,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 @@ -1992,6 +2172,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. @@ -2019,6 +2216,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. @@ -2092,15 +2309,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 @@ -2128,15 +2337,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 @@ -2205,33 +2406,15 @@ 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. 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 @@ -2376,6 +2559,102 @@ 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) -> int: + """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) -> int: + """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. + """ + return graphLib.gp_GetObstructionMinorType(self._theGraph) + def gp_ExtendWith_Outerplanarity(self) -> None: """Dynamically subclasses the graph with the ``Outerplanarity`` extension. @@ -2449,6 +2728,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 3f863bb..9ec05df 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, 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) 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) @@ -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,28 +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, 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) @@ -209,12 +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 int gp_GetEmbedFlags(graphP theGraph) -cdef unsigned 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 dafdce3..1447205 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 @@ -28,6 +39,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 @@ -35,12 +48,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_PARENT = cgraphLib.EDGE_TYPE_PARENT -EDGE_TYPE_BACK = cgraphLib.EDGE_TYPE_BACK 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 EDGEFLAG_INVERTED_MASK = cgraphLib.EDGEFLAG_INVERTED_MASK @@ -61,14 +75,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 # Surfaced from planarity/c/graphLib/planarityRelated/graphPlanarity.h PLANARITY_NAME = cgraphLib.PLANARITY_NAME - GRAPHFLAGS_EXTENDEDWITH_PLANARITY = cgraphLib.GRAPHFLAGS_EXTENDEDWITH_PLANARITY NONEMBEDDABLE = cgraphLib.NONEMBEDDABLE @@ -147,7 +165,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()``. @@ -161,14 +179,6 @@ 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 - # 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. @@ -273,13 +283,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, 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, int direction ): return cgraphLib.gp_FindDirectedEdge(theGraph, u, v, direction) @@ -306,10 +316,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) @@ -651,6 +677,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) @@ -672,6 +702,10 @@ cdef int gp_DepthFirstSearch(graphP theGraph): return cgraphLib.gp_DepthFirstSearch(theGraph) +cdef int gp_DepthFirstSearchEx(graphP theGraph, int mode): + return cgraphLib.gp_DepthFirstSearchEx(theGraph, mode) + + cdef int gp_SortVertices(graphP theGraph): return cgraphLib.gp_SortVertices(theGraph) @@ -684,10 +718,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) @@ -696,15 +738,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): @@ -714,11 +753,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): @@ -742,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) @@ -754,11 +791,19 @@ 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 int gp_GetEmbedFlags(graphP theGraph): return cgraphLib.gp_GetEmbedFlags(theGraph) -cdef unsigned gp_GetObstructionMinorType(graphP theGraph): +cdef int gp_GetObstructionMinorType(graphP theGraph): return cgraphLib.gp_GetObstructionMinorType(theGraph)