From d3a216bda21b32631cdae9b29a5fbce1f7a9aa16 Mon Sep 17 00:00:00 2001 From: "Wanda B.K. Boyer" Date: Wed, 16 Sep 2026 12:49:31 -0700 Subject: [PATCH 1/5] Initial commit changing naming of `classic` to `express` --- .gitignore | 2 +- MANIFEST.in | 6 +- README.md | 2 +- examples/{classic => express}/ascii.py | 0 examples/{classic => express}/draw.py | 0 .../{classic => express}/graph_formats.py | 0 examples/{classic => express}/kuratowski.py | 0 .../{classic => express}/networkx_draw.py | 0 .../networkx_interface.py | 0 .../output/K5-minus-edge.png | Bin examples/{classic => express}/pgraph_class.py | 0 examples/{classic => express}/write_tests.py | 0 planarity/__init__.py | 6 +- planarity/{classic => express}/__init__.pxd | 0 planarity/{classic => express}/__init__.py | 0 planarity/{classic => express}/cplanarity.pxd | 0 planarity/{classic => express}/planarity.pyx | 22 +++--- .../planarity_functions.py | 66 +++++++++--------- .../planarity_networkx.py | 28 ++++---- pyproject.toml | 8 +-- setup.py | 10 +-- 21 files changed, 75 insertions(+), 75 deletions(-) rename examples/{classic => express}/ascii.py (100%) rename examples/{classic => express}/draw.py (100%) rename examples/{classic => express}/graph_formats.py (100%) rename examples/{classic => express}/kuratowski.py (100%) rename examples/{classic => express}/networkx_draw.py (100%) rename examples/{classic => express}/networkx_interface.py (100%) rename examples/{classic => express}/output/K5-minus-edge.png (100%) rename examples/{classic => express}/pgraph_class.py (100%) rename examples/{classic => express}/write_tests.py (100%) rename planarity/{classic => express}/__init__.pxd (100%) rename planarity/{classic => express}/__init__.py (100%) rename planarity/{classic => express}/cplanarity.pxd (100%) rename planarity/{classic => express}/planarity.pyx (97%) rename planarity/{classic => express}/planarity_functions.py (74%) rename planarity/{classic => express}/planarity_networkx.py (71%) diff --git a/.gitignore b/.gitignore index 3d9f6b4..62a9bb8 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,4 @@ doc/source/api/** # Cythonized extension .c files planarity/full/*.c -planarity/classic/*.c +planarity/express/*.c diff --git a/MANIFEST.in b/MANIFEST.in index 441a926..40d3257 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -9,9 +9,9 @@ include planarity/c/README recursive-include planarity/c/ *.h -include planarity/classic/cplanarity.pxd -include planarity/classic/planarity.c -include planarity/classic/planarity.pyx +include planarity/express/cplanarity.pxd +include planarity/express/planarity.c +include planarity/express/planarity.pyx include planarity/full/cgraphLib.pxd include planarity/full/graphLib.pxd diff --git a/README.md b/README.md index 7378192..1c8c67d 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ In [11]: print(planarity.ascii(edgelist)) ```python In [12]: planarity.draw(edgelist, outfileName='K5-minus-edge.png') ``` -![K_5-minus-edge](/examples/classic/output/K5-minus-edge.png +![K_5-minus-edge](/examples/express/output/K5-minus-edge.png "Matplotlib render of $K_5$-minus-edge") Note that edge `(a, b)` would correspond to an edge between vertex indexes diff --git a/examples/classic/ascii.py b/examples/express/ascii.py similarity index 100% rename from examples/classic/ascii.py rename to examples/express/ascii.py diff --git a/examples/classic/draw.py b/examples/express/draw.py similarity index 100% rename from examples/classic/draw.py rename to examples/express/draw.py diff --git a/examples/classic/graph_formats.py b/examples/express/graph_formats.py similarity index 100% rename from examples/classic/graph_formats.py rename to examples/express/graph_formats.py diff --git a/examples/classic/kuratowski.py b/examples/express/kuratowski.py similarity index 100% rename from examples/classic/kuratowski.py rename to examples/express/kuratowski.py diff --git a/examples/classic/networkx_draw.py b/examples/express/networkx_draw.py similarity index 100% rename from examples/classic/networkx_draw.py rename to examples/express/networkx_draw.py diff --git a/examples/classic/networkx_interface.py b/examples/express/networkx_interface.py similarity index 100% rename from examples/classic/networkx_interface.py rename to examples/express/networkx_interface.py diff --git a/examples/classic/output/K5-minus-edge.png b/examples/express/output/K5-minus-edge.png similarity index 100% rename from examples/classic/output/K5-minus-edge.png rename to examples/express/output/K5-minus-edge.png diff --git a/examples/classic/pgraph_class.py b/examples/express/pgraph_class.py similarity index 100% rename from examples/classic/pgraph_class.py rename to examples/express/pgraph_class.py diff --git a/examples/classic/write_tests.py b/examples/express/write_tests.py similarity index 100% rename from examples/classic/write_tests.py rename to examples/express/write_tests.py diff --git a/planarity/__init__.py b/planarity/__init__.py index 64db20a..74c587d 100644 --- a/planarity/__init__.py +++ b/planarity/__init__.py @@ -1,6 +1,6 @@ -from .classic.planarity import PGraph -from .classic.planarity_functions import * -from .classic.planarity_networkx import * +from .express.planarity import PGraph +from .express.planarity_functions import * +from .express.planarity_networkx import * # Surfaced from planarityc/graphLib/lowLevelUtils/apiutils.h from .full.graphLib import ( diff --git a/planarity/classic/__init__.pxd b/planarity/express/__init__.pxd similarity index 100% rename from planarity/classic/__init__.pxd rename to planarity/express/__init__.pxd diff --git a/planarity/classic/__init__.py b/planarity/express/__init__.py similarity index 100% rename from planarity/classic/__init__.py rename to planarity/express/__init__.py diff --git a/planarity/classic/cplanarity.pxd b/planarity/express/cplanarity.pxd similarity index 100% rename from planarity/classic/cplanarity.pxd rename to planarity/express/cplanarity.pxd diff --git a/planarity/classic/planarity.pyx b/planarity/express/planarity.pyx similarity index 97% rename from planarity/classic/planarity.pyx rename to planarity/express/planarity.pyx index bc6b5cd..6c08833 100644 --- a/planarity/classic/planarity.pyx +++ b/planarity/express/planarity.pyx @@ -7,7 +7,7 @@ from libc.stdlib cimport free import typing import warnings -from planarity.classic cimport cplanarity +from planarity.express cimport cplanarity cdef class PGraph: @@ -15,7 +15,7 @@ cdef class PGraph: Attributes: theGraph: The C-layer graph data structure instance wrapped by the - :py:class:`~planarity.classic.planarity.PGraph`. + :py:class:`~planarity.express.planarity.PGraph`. nodemap (dict[typing.Any, int]): the mapping of original labels to the internal vertex indexes. reverse_nodemap (dict[int, typing.Any]): The mapping of internal vertex @@ -23,9 +23,9 @@ cdef class PGraph: _embedding_workflow_status (int): Indicates the status of the embedding workflow; the value is not meaningful until after an embedding workflow method such as one of the following has been called: - * :py:meth:`~planarity.classic.planarity.PGraph.is_planar` - * :py:meth:`~planarity.classic.planarity.PGraph.embed_planar` - * :py:meth:`~planarity.classic.planarity.PGraph.embed_drawplanar` + * :py:meth:`~planarity.express.planarity.PGraph.is_planar` + * :py:meth:`~planarity.express.planarity.PGraph.embed_planar` + * :py:meth:`~planarity.express.planarity.PGraph.embed_drawplanar` Note that these are called by several of the other member methods. @@ -39,7 +39,7 @@ cdef class PGraph: cdef int _embedding_workflow_status def __init__(self, graph): - """Initialize :py:class:`~planarity.classic.planarity.PGraph` from an input graph. + """Initialize :py:class:`~planarity.express.planarity.PGraph` from an input graph. Args: graph (networkx.Graph | dict[typing.Any, collections.abc.Iterable[typing.Any]] | list[list[typing.Any] | tuple[typing.Any, typing.Any]]): @@ -48,9 +48,9 @@ cdef class PGraph: Raises: ValueError: if the given graph is already a - :py:class:`~planarity.classic.planarity.PGraph`. + :py:class:`~planarity.express.planarity.PGraph`. RuntimeError: if the graph couldn't be converted to a - :py:class:`~planarity.classic.planarity.PGraph`. + :py:class:`~planarity.express.planarity.PGraph`. RuntimeError: if an error was encountered by C-layer methods such as ``gp_New()``, ``gp_EnsureVertexCapacity()``, or ``gp_DynamicAddEdge()``. @@ -98,7 +98,7 @@ cdef class PGraph: n = len(nodes) # NOTE: This presumes 1-based arrays; however, this can only be changed - # if you add -DUSE_0BASEDARRAYS to the extra_compile_args of the classic + # if you add -DUSE_0BASEDARRAYS to the extra_compile_args of the express # planarity extension in setup.py. self.nodemap = dict(zip(nodes, range(1, n+1))) self.reverse_nodemap = dict(zip(range(1, n+1), nodes)) @@ -669,9 +669,9 @@ cdef class PGraph: Returns: A mapping between the integers assigned to the vertices, by - :py:class:`~planarity.classic.planarity.PGraph` initialization, + :py:class:`~planarity.express.planarity.PGraph` initialization, and their original labels provided to - :py:class:`~planarity.classic.planarity.PGraph` initialization. + :py:class:`~planarity.express.planarity.PGraph` initialization. """ return self.reverse_nodemap diff --git a/planarity/classic/planarity_functions.py b/planarity/express/planarity_functions.py similarity index 74% rename from planarity/classic/planarity_functions.py rename to planarity/express/planarity_functions.py index 47312de..6389200 100644 --- a/planarity/classic/planarity_functions.py +++ b/planarity/express/planarity_functions.py @@ -18,22 +18,22 @@ def is_planar(graph): """Tests whether or not the graph is planar. Constructs a - :py:class:`~planarity.classic.planarity.PGraph` - and calls its :py:meth:`~planarity.classic.planarity.PGraph.is_planar` + :py:class:`~planarity.express.planarity.PGraph` + and calls its :py:meth:`~planarity.express.planarity.PGraph.is_planar` method. Args: graph: A graph specified in a format that may be converted to a - :py:class:`~planarity.classic.planarity.PGraph`. + :py:class:`~planarity.express.planarity.PGraph`. Returns: ``True`` if the graph is planar, or ``False`` if not. Raises: ValueError: if the given graph is already a - :py:class:`~planarity.classic.planarity.PGraph`. + :py:class:`~planarity.express.planarity.PGraph`. RuntimeError: if the graph couldn't be converted to a - :py:class:`~planarity.classic.planarity.PGraph`. + :py:class:`~planarity.express.planarity.PGraph`. RuntimeError: if an error was encountered by C-layer methods such as ``gp_Embed()``. RuntimeError: if a prior invocation of this method already failed. @@ -47,13 +47,13 @@ def kuratowski_edges(graph): """Returns a list of the edges in a minimal non-planar subgraph of the graph. Constructs a - :py:class:`~planarity.classic.planarity.PGraph` + :py:class:`~planarity.express.planarity.PGraph` and calls its - :py:meth:`~planarity.classic.planarity.PGraph.kuratowski_edges` method. + :py:meth:`~planarity.express.planarity.PGraph.kuratowski_edges` method. Args: graph: A graph specified in a format that may be converted to a - :py:class:`~planarity.classic.planarity.PGraph`. + :py:class:`~planarity.express.planarity.PGraph`. Returns: A list of the edges in a minimal non-planar subgraph of the graph, @@ -61,9 +61,9 @@ def kuratowski_edges(graph): Raises: ValueError: if the given graph is already a - :py:class:`~planarity.classic.planarity.PGraph`. + :py:class:`~planarity.express.planarity.PGraph`. RuntimeError: if the graph couldn't be converted to a - :py:class:`~planarity.classic.planarity.PGraph`. + :py:class:`~planarity.express.planarity.PGraph`. RuntimeError: if an error was encountered by C-layer methods such as ``gp_Embed()``. RuntimeError: if a prior invocation of this method already failed. @@ -77,22 +77,22 @@ def ascii(graph) -> str: """Produces an ASCII string rendition of the graph, if it is planar. Constructs a - :py:class:`~planarity.classic.planarity.PGraph` + :py:class:`~planarity.express.planarity.PGraph` and calls its - :py:meth:`~planarity.classic.planarity.PGraph.ascii` method. + :py:meth:`~planarity.express.planarity.PGraph.ascii` method. Args: graph: A graph specified in a format that may be converted to a - :py:class:`~planarity.classic.planarity.PGraph`. + :py:class:`~planarity.express.planarity.PGraph`. Returns: An ASCII string rendition of a planar graph. Raises: ValueError: if the given graph is already a - :py:class:`~planarity.classic.planarity.PGraph`. + :py:class:`~planarity.express.planarity.PGraph`. RuntimeError: if the graph couldn't be converted to a - :py:class:`~planarity.classic.planarity.PGraph`. + :py:class:`~planarity.express.planarity.PGraph`. RuntimeError: if an error was encountered by C-layer methods such as ``gp_Embed()`` or ``gp_DrawPlanar_RenderToString()``. RuntimeError: if a prior invocation of this method already failed. @@ -107,29 +107,29 @@ def draw(graph, labels=True, outfileName=None, **kwargs): """Draws the graph with Matplotlib, if it is planar. Constructs a - :py:class:`~planarity.classic.planarity.PGraph` + :py:class:`~planarity.express.planarity.PGraph` and calls its - :py:meth:`~planarity.classic.planarity.PGraph.draw` method with the + :py:meth:`~planarity.express.planarity.PGraph.draw` method with the given ``labels`` and ``outfileName``. Args: graph: A graph specified in a format that may be converted to a - :py:class:`~planarity.classic.planarity.PGraph`. + :py:class:`~planarity.express.planarity.PGraph`. labels (bool): If ``True``, vertex labels are rendered in the drawing. Otherwise, vertices are rendered unlabelled in the drawing. outfileName (:obj:`str`): File to which to output a Matplotlib rendering of the planar graph. If not given, then the caller can call :external+matplotlib:py:func:`matplotlib.pyplot.savefig`. **kwargs: Optional figure-level parameters forwarded to - :py:meth:`~planarity.classic.planarity.PGraph.draw`. Supported + :py:meth:`~planarity.express.planarity.PGraph.draw`. Supported keys are ``figsize`` and ``dpi``. Raises: ValueError: if the given graph is already a - :py:class:`~planarity.classic.planarity.PGraph`. + :py:class:`~planarity.express.planarity.PGraph`. ValueError: if an unsupported keyword argument is given. RuntimeError: if the graph couldn't be converted to a - :py:class:`~planarity.classic.planarity.PGraph`. + :py:class:`~planarity.express.planarity.PGraph`. ImportError: if dependencies from Matplotlib fail to be imported. RuntimeError: if an error was encountered by C-layer methods such as ``gp_Embed()``. @@ -153,14 +153,14 @@ def write(graph, path: str = 'stdout', writeMode: int = WRITE_ADJLIST) -> None: """Writes the graph to ``path``. Constructs a - :py:class:`~planarity.classic.planarity.PGraph` + :py:class:`~planarity.express.planarity.PGraph` and calls its - :py:meth:`~planarity.classic.planarity.PGraph.write` method with the + :py:meth:`~planarity.express.planarity.PGraph.write` method with the specified ``path`` and ``writeMode``. Args: graph: A graph specified in a format that may be converted to a - :py:class:`~planarity.classic.planarity.PGraph`. + :py:class:`~planarity.express.planarity.PGraph`. path (str): Path to which to write graph. Defaults to ``stdout`` stream. writeMode (int): Format to write the graph. Defaults to @@ -168,9 +168,9 @@ def write(graph, path: str = 'stdout', writeMode: int = WRITE_ADJLIST) -> None: Raises: ValueError: if the given graph is already a - :py:class:`~planarity.classic.planarity.PGraph`. + :py:class:`~planarity.express.planarity.PGraph`. RuntimeError: if the graph couldn't be converted to a - :py:class:`~planarity.classic.planarity.PGraph`. + :py:class:`~planarity.express.planarity.PGraph`. RuntimeError: if the C-layer ``gp_Write()`` failed. """ planarity.PGraph(graph).write(path, writeMode) @@ -181,24 +181,24 @@ def mapping(graph) -> dict[int, typing.Any]: """Returns the map of integer vertex labels to their original labels. Constructs a - :py:class:`~planarity.classic.planarity.PGraph` + :py:class:`~planarity.express.planarity.PGraph` and calls its - :py:meth:`~planarity.classic.planarity.PGraph.mapping` method. + :py:meth:`~planarity.express.planarity.PGraph.mapping` method. Args: graph: A graph specified in a format that may be converted to a - :py:class:`~planarity.classic.planarity.PGraph`. + :py:class:`~planarity.express.planarity.PGraph`. Returns: A mapping between the integers assigned to the vertices, by - :py:class:`~planarity.classic.planarity.PGraph` initialization, + :py:class:`~planarity.express.planarity.PGraph` initialization, and their original labels provided to - :py:class:`~planarity.classic.planarity.PGraph` initialization. + :py:class:`~planarity.express.planarity.PGraph` initialization. Raises: ValueError: if the given graph is already a - :py:class:`~planarity.classic.planarity.PGraph`. + :py:class:`~planarity.express.planarity.PGraph`. RuntimeError: if the graph couldn't be converted to a - :py:class:`~planarity.classic.planarity.PGraph`. + :py:class:`~planarity.express.planarity.PGraph`. """ return planarity.PGraph(graph).mapping() diff --git a/planarity/classic/planarity_networkx.py b/planarity/express/planarity_networkx.py similarity index 71% rename from planarity/classic/planarity_networkx.py rename to planarity/express/planarity_networkx.py index 06abd4b..88d16c2 100644 --- a/planarity/classic/planarity_networkx.py +++ b/planarity/express/planarity_networkx.py @@ -12,26 +12,26 @@ def kuratowski_subgraph(graph): """Returns a :external+networkx:py:class:`networkx.Graph` containing a minimal non-planar subgraph of the graph. Constructs a - :py:class:`~planarity.classic.planarity.PGraph` + :py:class:`~planarity.express.planarity.PGraph` and calls its - :py:meth:`~planarity.classic.planarity.PGraph.kuratowski_edges` method, then + :py:meth:`~planarity.express.planarity.PGraph.kuratowski_edges` method, then converts the edge-list to a :external+networkx:py:class:`networkx.Graph`. Args: graph: A graph specified in a format that may be converted to a - :py:class:`~planarity.classic.planarity.PGraph`. + :py:class:`~planarity.express.planarity.PGraph`. Returns: A :external+networkx:py:class:`networkx.Graph` containing a minimal non-planar subgraph of a non-planar graph. Raises: - ValueError: if the given graph is already a - :py:class:`~planarity.classic.planarity.PGraph`. - RuntimeError: if the graph couldn't be converted to a - :py:class:`~planarity.classic.planarity.PGraph`. + ValueError: if the given graph is already a + :py:class:`~planarity.express.planarity.PGraph`. + RuntimeError: if the graph couldn't be converted to a + :py:class:`~planarity.express.planarity.PGraph`. RuntimeError: if an error was encountered by C-layer methods such - as ``gp_Embed()``. + as ``gp_Embed()``. ImportError: On failure to import ``NetworkX``. """ try: @@ -45,15 +45,15 @@ def kuratowski_subgraph(graph): def networkx_graph(pgraph): - """Builds a :external+networkx:py:class:`networkx.Graph` from a :py:class:`~planarity.classic.planarity.PGraph`. + """Builds a :external+networkx:py:class:`networkx.Graph` from a :py:class:`~planarity.express.planarity.PGraph`. Args: - pgraph: A :py:class:`~planarity.classic.planarity.PGraph` to convert to + pgraph: A :py:class:`~planarity.express.planarity.PGraph` to convert to a A :external+networkx:py:class:`networkx.Graph`. Returns: A :external+networkx:py:class:`networkx.Graph` built from a - :py:class:`~planarity.classic.planarity.PGraph`. + :py:class:`~planarity.express.planarity.PGraph`. Raises: ImportError: On failure to import ``NetworkX``. @@ -69,15 +69,15 @@ def networkx_graph(pgraph): def pgraph_graph(graph): - """Builds a :py:class:`~planarity.classic.planarity.PGraph` from the graph. + """Builds a :py:class:`~planarity.express.planarity.PGraph` from the graph. Args: graph: A graph specified in a format that may be converted to a - :py:class:`~planarity.classic.planarity.PGraph`, including a + :py:class:`~planarity.express.planarity.PGraph`, including a :external+networkx:py:class:`networkx.Graph`. Returns: - A :py:class:`~planarity.classic.planarity.PGraph` representing the same + A :py:class:`~planarity.express.planarity.PGraph` representing the same graph as the input ``graph``. """ return planarity.PGraph(graph) diff --git a/pyproject.toml b/pyproject.toml index aa1b136..1bf23e0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,4 +1,4 @@ -# Based on documentation from +# Based on documentation from # https://packaging.python.org/en/latest/guides/writing-pyproject-toml/ # and https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html @@ -18,7 +18,7 @@ authors = [ # NOTE: Only one of authors and maintainers is required, per # https://packaging.python.org/en/latest/specifications/pyproject-toml # However, also having maintainers allows a different first maintainer -# to appear, since only the first entry shows on the package page +# to appear, since only the first entry shows on the package page maintainers = [ {name = "Wanda B. K. Boyer", email = "wbkboyer@gmail.com"}, @@ -72,11 +72,11 @@ dynamic = ["version"] include-package-data = false packages = [ "planarity", - "planarity.classic", + "planarity.express", "planarity.full", ] -# NOTE: This enables dynamic metadata, e.g. versioning, to be computed during +# NOTE: This enables dynamic metadata, e.g. versioning, to be computed during # the build process; see: # https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html#dynamic-metadata # NOTE: In the future, we could automatically generate the version number by diff --git a/setup.py b/setup.py index f5515d7..29626f6 100644 --- a/setup.py +++ b/setup.py @@ -4,10 +4,10 @@ # NOTE: As soon as pyproject.toml supports glob notation, we should be able to # remove setup.py and allow setuptools to leverage that configuration. See: # https://github.com/pypa/setuptools/discussions/4154 -classic_sourcefiles = [ - "planarity/classic/planarity.pyx" +express_sourcefiles = [ + "planarity/express/planarity.pyx" ] -classic_sourcefiles.extend(glob("planarity/c/graphLib/**/*.c", recursive=True)) +express_sourcefiles.extend(glob("planarity/c/graphLib/**/*.c", recursive=True)) graphLib_sourcefiles = [ "planarity/full/graphLib.pyx" @@ -18,8 +18,8 @@ extensions = [ Extension( - name="planarity.classic.planarity", - sources=classic_sourcefiles, + name="planarity.express.planarity", + sources=express_sourcefiles, include_dirs=['planarity/c/graphLib'], # extra_compile_args=["-DDEBUG"], # Uncomment if you want to see debugNOTOK() statements ), From f9a94f313c4853952356dc44db16b5733286f919 Mon Sep 17 00:00:00 2001 From: "Wanda B.K. Boyer" Date: Wed, 16 Sep 2026 13:26:13 -0700 Subject: [PATCH 2/5] Fixed `PGraph` class docstring formatting for unordered list (needs to have a blank line before it) --- planarity/express/planarity.pyx | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/planarity/express/planarity.pyx b/planarity/express/planarity.pyx index 6c08833..44ad50d 100644 --- a/planarity/express/planarity.pyx +++ b/planarity/express/planarity.pyx @@ -23,9 +23,10 @@ cdef class PGraph: _embedding_workflow_status (int): Indicates the status of the embedding workflow; the value is not meaningful until after an embedding workflow method such as one of the following has been called: - * :py:meth:`~planarity.express.planarity.PGraph.is_planar` - * :py:meth:`~planarity.express.planarity.PGraph.embed_planar` - * :py:meth:`~planarity.express.planarity.PGraph.embed_drawplanar` + + - :py:meth:`~planarity.express.planarity.PGraph.is_planar` + - :py:meth:`~planarity.express.planarity.PGraph.embed_planar` + - :py:meth:`~planarity.express.planarity.PGraph.embed_drawplanar` Note that these are called by several of the other member methods. @@ -420,10 +421,11 @@ cdef class PGraph: ) ) - # NOTE: The DrawPlanar context data gives geometric positioning; - # a value of -1 indicates an error state, and therefore the - # drawplanar_edge_info should not be included (i.e., the - # final tuple member will be an empty dict) + # NOTE: The DrawPlanar context data gives geometric + # positioning; a value of -1 indicates an error state, + # and therefore the drawplanar_edge_info should not be + # included (i.e., the final tuple member will be an + # empty dict) if ( edge_position > -1 and edge_start > -1 and @@ -490,12 +492,13 @@ cdef class PGraph: 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 label. + 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 + label. outfileName (:obj:`str`): File to which to output a Matplotlib rendering of the planar graph. If not given, then the caller can call :external+matplotlib:py:func:`matplotlib.pyplot.savefig`. @@ -674,4 +677,3 @@ cdef class PGraph: :py:class:`~planarity.express.planarity.PGraph` initialization. """ return self.reverse_nodemap - From 68d20d8059317124e3d7ec6bd14ca344ac30eea6 Mon Sep 17 00:00:00 2001 From: "Wanda B.K. Boyer" Date: Wed, 16 Sep 2026 18:16:18 -0700 Subject: [PATCH 3/5] Removed `.c` files from `MANIFEST.in` - these should no longer be needed within the source distribution due to the isolated build environment having setuptools and cython installed, even if the user's environment doesn't have these dependencies. Bumped the `__version__` in `__init__.py` so I can push the source distribution to TestPyPI and test installing. --- MANIFEST.in | 4 ---- planarity/__init__.py | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 40d3257..d56c15b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -10,15 +10,11 @@ include planarity/c/README recursive-include planarity/c/ *.h include planarity/express/cplanarity.pxd -include planarity/express/planarity.c include planarity/express/planarity.pyx include planarity/full/cgraphLib.pxd include planarity/full/graphLib.pxd include planarity/full/graph.pxd -include planarity/full/graphLib.c -include planarity/full/graph.c -include planarity/full/g6IterationUtils.c include planarity/full/graphLib.pyx include planarity/full/graph.pyx include planarity/full/g6IterationUtils.pyx diff --git a/planarity/__init__.py b/planarity/__init__.py index 74c587d..11c35d2 100644 --- a/planarity/__init__.py +++ b/planarity/__init__.py @@ -108,4 +108,4 @@ # NOTE: In the future, we could automatically generate the version number by # configuring setuptools-scm, but presently this seems simpler. -__version__ = "1.0.0" +__version__ = "1.0.1" From 31f365402a587c042b49c0159970c971e8ee8b54 Mon Sep 17 00:00:00 2001 From: "Wanda B.K. Boyer" Date: Thu, 17 Sep 2026 09:52:21 -0700 Subject: [PATCH 4/5] Updated to use absolute path for K5-minus-edge.png, since (Test)PyPI page has a broken link with relative URL (even though GitHub repo page was happy) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1c8c67d..4ab31ee 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,8 @@ In [11]: print(planarity.ascii(edgelist)) ```python In [12]: planarity.draw(edgelist, outfileName='K5-minus-edge.png') ``` -![K_5-minus-edge](/examples/express/output/K5-minus-edge.png + +![K_5-minus-edge](https://github.com/graph-algorithms/planarity/blob/master/examples/express/output/K5-minus-edge.png "Matplotlib render of $K_5$-minus-edge") Note that edge `(a, b)` would correspond to an edge between vertex indexes From e8e415d827fe301fb6b1c4549fa9c483187a600e Mon Sep 17 00:00:00 2001 From: "Wanda B.K. Boyer" Date: Fri, 18 Sep 2026 16:39:00 -0800 Subject: [PATCH 5/5] `ax.margins(0)` should remove any additional margins added, which seems to satisfy `test_draw_pad_inches_controls_output_padding()`. Regenerated `K5-minus-edge.png` just in case. --- examples/express/output/K5-minus-edge.png | Bin 6588 -> 6588 bytes planarity/express/planarity.pyx | 8 +++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/express/output/K5-minus-edge.png b/examples/express/output/K5-minus-edge.png index 84459e29690dddcf1dd3713bd4d4b319a412345d..f4d68e821b7c8583dce7c77ab683b1b69ac5ab9c 100644 GIT binary patch literal 6588 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 literal 6588 zcmbVwXIN9~w)IK~MVg2p0s^5Zy+~0BEpDW31(e>4^p5l@K?S5oEC^DiNev|w>4HcT zk={F@g`#u<|%n(93*0D!^( z03u952EOrC^V7rcC5bJj z1ntNqCH0>CI7qR2pX&e~-5M*DM+I*22R9w~3xxmx{68A3>R|wYLW18Yz{~l&(X6b^ z`OmKBgG>8#DE`@XUOmimv@y6&ugD;?wic0;yXqwk06Zy{R|WX_`>+lJI(m9bL2|jR zTg_Vy2WiUYzcdkkpVWvao8sjwo-m*$eQY?_z156y5$8ByZuXPeF6Gtd&}Kqk!||?e z@F3>ns&B{Z*IPRL;H0KSpYI+M@0d^3U)mTqbn0cf)7BTi5V*hIw^?i2#p=%wngIPo zR{98RC!LzvH}E1l`;hJ{KQp4=A#KHgEsp2~_NLdG=OY0k840_J zYyo&?CExm-jybX<#8W()Q?XIxXnSAgzQ0|p{_Nnu*gaJDB?=(X>1feyylI>E?VH_x z-;%U^`CvlF!tZOoNd`i>sWQH6kqRXbrxwl%TKK8nm@*YDh z@xY(72w$qOKWY^>3~M$#-?9C0k>Nh|m-jcV2J3v?ijAu^@G6+?f6aa& z%s#(}wQMB|tg_gg{On^j_fFV2MT7>WNC88JuMHI@meg-|S+TQs5re_R2ePRo_H4I>wA};pp(LBqodKTQo$&EjRK#>G6?wOkd6njPF1{xvAwS78w zgUbM1J1PH(JtiwO!>%`NKiHZ>-+%o5%#>hfNxZq0G$ff*DDf*!p9A?a1K|T-`@&Q0> zRpP$%Y%g9}dwwjUn~2iXxF6|j)+)BRVPriM9)Cwgn)rr~>Y?YIg63qhK%T=182aVN z6}+4nV`(cp0DwUO0HNUXcVpCE05l~G3IIqr`2O$4tz=3t8vdi}`507e?jQf`dPTGq z2x?Fv{t65x)CDp)jbj>{es`h2xI@(5n4>Xnrvs!$_4coxeSur0BVo{4rxU_R)3DTt zN!B`~_3hM|vl2~!d#LrqME|FmlYQTU;^Lu@Vxt@+yjfY54hs3j$o1-FNfWWXi%nK< zig<)zCX*mRX_p^g93Uu6e^fX->P<#?3$SwCJ?tO}3Ih>ViOUkF6T?Lf&~}Tka+&*- zRJlwndGVJh!+}QI@aD&Ur!g1N(DHhsXnLY5wGT7k9%kVAyd~Y`HS>kavmSO#(2*QY zc)f?3?iVQ(g2G;ek`GT~mXdGVRJCb1j#)f0_dXqVwn(c1z;+0uWfr_~mGv zdWEMWw4dSMR;?MJ60OhZ>iYBTyLW4op1nhur+b1)-=TncbQU#G^W$4AtCY*QUL>{>pRsyBF!lE=XWQBl#63Ad(V!wOqzQ(E*bFbm8g6i!z) zD}#aB|!?^njJ+>hzCugO*3xk(}zD~ zw!F^2gW0Mk!TWutx0Aj7!$c9&#l3A0@@p4+>3|YjQ(9)^F}TKGQQ`1ij3M?Lc#nmv zzgB8d557@@j)Cj!98sC5^0sH2sCG*fmC+B6ko(l(d~RC7x-<9SlR5H$jmnXEqsE*Z zrFi-MniHJezidWNqG~*5V|R{QEVqeVQ~Pe!j_adkK9@85e(W}Rvn;HwIi9XBD~#~U zIO?9H7QWV#Ko@{_)ca)%C+YAjmoyh74kcG$K<+nynYu`?y|fuWeNSu zC5L6l`~{L_&C7;wD9IN-GS04?vWXFjuP2qm;q?;UfW44#V3sn?qD1zTOvaQ9T?HaR zWB{{LSP5`nEF)24)=dg@VEo zEwQVEAFp4uABk(K^~i00lYgqN;uFoVM6l^FwZnTJZ!O!ZRGaE$MBDyI$-f@GS%EQ% z{0jPd`;#MF;>7K0M_Lu{Z^HNu%uG{*mh@BK2A-PJ1J8=H&l{WI3rLk|%T02S-~9(` z`-{FgOEQ9Jcb@D2&rC1+He>h~Np&SwdF|bwPa94~L?{VbABCFtjJO|FZU$%?hKe}4 zxah_yjI1UAcfS7iHNz{}uf54HFg}hs-MSf%FujCMB9rsXGxOUtq+yZlSk(foAH&~2 z?exVySx{y)^Ll+EN9gXDZzhN5Kj0tk&j`0mTs`SyT5)r0$WKV1zXe5GUZa7<#>N_* z?uO(~jB*GZa5Unq`H{*y`SC#mn?Fm)Mf7!b-!>jJY)PBaqj)@b7Y@_p0@nl0Ejf6* z0eI&?4w5(t4JNGS#gL;zwXB{_i=~tRsIX-&blJ5nP9(t|ByornqPg|vsj4l{AQ8%r ziK*YDOnX{Vn{w00u+c)i3kG}*MWQbE*ZAkq%AT>l-Q&o3qTMd2pkS^nlYT=n6Kxif zWhAO6tcxF37f2~bJp=&$C?v`!Oku_{A~gMEk+=4Evu=V0dNz@({z1~#Ifv!8Le8^S z5?7x9s(17I6j5)6Prlq=OGwNEY2Ze7D++hA(>v0pohNY8XMS0W1ISpQfZ6L?n-?on zbCe+AbO-jSzSDe_>rz*M9Be53#%Ap!}8I|XdzYGGe#;K8PVfIxq?+G}j!fonJe&`&1;dpXg1 zQK9H~tI0g%u$2SrGI1-GSwi{#J0Z(^5ID^x3c70ZlX<3E5CC-y4IR=yt*9svUWwHI z`a}7Ad?@fFvh6Y9SL;RZ{V_X^ox@Vg)~k;IRYe%oi$dihfRdr3pks<}&l=EFFvTlT6l*_==aVo<>%HKMr|0`#-iQG;jvR}OIJiV+HHouiG_<)N3} zR=V<93qVKtc`Mh+ehv!=!IS$eSkza!P6>o;@{5Aey>*F^^s2f!HDze zmEg@6I=4xOT)s<*i>4H>e3299F6J#o>ra;`by0d*zJb8oE>XB^73Ct$tLc?#0F!Th zxh?hevd4|Zm7Sg9fU5Y!=_7cJJvgGO?RH;?bb7!~FXKVo)N7^M?)F1d z!S(EP^2eKxJ>-n8jz70?-BLk^?s%6bS(fdp!Z$oxHmG`XiaIXR-@ai`1I} zk1ikM1$%|x_AZqCT-cuxz0APCuyXF?q)aBWUi^T#6rBNK51;qJwN}k_=vtiPp0J9> z+73sxzCe3Aee1NtB=H!6yfK?~?Vosdj&T2t9R1&7ppaQOgzXY#*lv2$sV19WCRHmK=0Fii7Jx$`Nks&o}O+@eL=IsG94KZVHi3YAY#<3 z1~-F2zZPefJxa4+=cdR8@SpAFcticlx`7UO-?xS+sr1ng;k>a^*4b0P$XcD zQgqYL{~YkvNC6|4k9*b@{8qv%VVn5Gxy6wL%|Zb~bxY}ChC^e!C2hKNOrIW|u1!>9 zWZ8S4B!c05PFT6)WeeU4GEJnen?`6nFzNvk&V|o==IDY|T)UL#(gV4}+2`Wa3ojDc z5MG~ds3&KGBxdzMQ#P|+qP_FiuLMX9qk0%BI&5uPKl}5e5fjfIwJ?zC*%aAuN3b#! zOeCOLAlYQ+f~hQ*WItF*sqJR`7APk*Wq-dIACF7HF8dW#%(5Bk2R|!-p((*FM<*@4 zx*dc$7|kbwiqG*|3^Qd!>qbfI@(x=CD6HxB$75mKo$HPq?_zJTjk~A8>3~d zWdXn)5Ol3eNyuqWrkbLoqOKK^qdd>&uON`c5f)!-?t)~_ak1moxtwJNcg*ZO*AljG zNCj3k^4p@BoOW0G3W|!n&gCp9LpzXdZEdxS3HNuq1TlP9sS9drjVkPVhd~jWdM;?+ za{}UM?_en2i0s|b&a#XtEsCKTq^Rd?;Ew!&Br_MBL`)I1-JG;XPRr8LQq9N{udysp zV{4q3C{C14*N>UZgKlnmUo#yx53)7|CL_;HjrkN`UYF=dd}gd;ZXVL#ZQLzXgVPIS zYiyCfkbep3ILk-UjH47GG>sG5kJbjNF(ND>ufWI_jBodxw0T_i-e>*OP_o@H9~UMn z_bjk~jdYe8G`9zF(`M~yo9N)~rl-+P*9Y)h{oXAEC^btwehpFUyl z6kz&Gt&^2b|D;m>fi7YNf=B}>esW$*Aj>98i(cUaLuK~laUEn z<&U+yUq*Q1HHQw&a?PfquLgQigaT}O4#^D2@l(q9+wd zlTD@r*I?5pr(1Sa{PoLLr?Uk~j?pi?_lERkj4n#RF6@G?yfz;#s&}wjEm-HQ$`!%@ zz-f|l^9kPf8OFDt(Q>#Z^e%fAU7?kEkl`{>)tQ)j|0)0kU2he8qx~d}>s2XOK)KF) ziGrSwK9a8+uelfx0NK*5_~UK4R<&JF1-|bfrbe+(a#slZOm0ckLyiLX6M5?djY-_W zn?~k2js31-NjEMTZjjONW9IJ8O4-gt*i6QQD#or5Q2QoA0;CFHk4ZMn{p)_PkLqhG zoMPA3)^?AU(?d2~wx%0Eg%!WsoX|#j?NeVPBbBvst1~0K6AY-I`LwzU#pO>=v6%&b zezExV;vK(;$S?{*BI!ITM#CX*0^+yM4~OMa8x+suaBwS>ZkS=4#2J4U2mTv1ldX$+YVK=VA45-q8B`{S=e2efo{R4}0WO93h z+APu@+2<-%nM3u`H6IH2oILT+N^bY3qEVesv4Ei9Mmvay*1atM9$uqw9z5hKS^ukr zDn1y}HGiE#l~I-z!eb)IQb}@GB~Bhd8-Sj8!S@@p9q93^APcp`hJt(rK)VmGzEp{2 z$>BJeB_IFnnRhx0*4D80eO%{DgJaan9ESw`BMuC_C zs68?;_{1ov_X$+WjfR;3at#D3RRR1*mER_Z6v)S5Ui-`Sy|`LSw;q=nOcuUWwo3S7 z#|R(u8-}b%hPMlzS3ulm*bfl$(|X*Rw5O!Va!~hr34|Pt1+4$(9qjM-=9O^V>rVup?oH87Z*;;R)B>6s+@64&Sha?(b3h-F?Kz@ zO&Ykb?=xn2?hK&ZLg;3UAQ{ygblv2tO$eTQ zY{_M)2B&@`Kj?6dWNhnV2mS!Nzep)fd}yP-n=UNJFgU= z0D9@W{bKh4^?5LKAnXFObJT>Th9XaH0w_tF