Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions doc/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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",
}
222 changes: 222 additions & 0 deletions examples/full/demo_embedding_facelister.py
Original file line number Diff line number Diff line change
@@ -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 ''}."
)
24 changes: 17 additions & 7 deletions planarity/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
from .full.graphLib import (
gp_GetQuietMode,
gp_SetQuietMode,
)
from .full.graphLib import (
gp_GetRandomNumber,
)

Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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.
Expand Down
38 changes: 18 additions & 20 deletions planarity/express/planarity.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -484,61 +484,59 @@ 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
call :external+matplotlib:py:func:`matplotlib.pyplot.savefig`.
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.

Expand Down Expand Up @@ -576,7 +574,7 @@ cdef class PGraph:
"planarity: Unable to draw() graph due to error encountered in "
"embedding workflow."
)

###############################
# Prepare for graphic rendering
###############################
Expand Down
Loading
Loading