<?xml version="1.0" encoding="utf-8"?>
<html xmlns="
http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<style type="text/css">
/*
:Author: David Goodger (
goodger@python.org)
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.
See
https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic, pre.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
Introduction
What is an SPQR-Tree?
An SPQR-tree is a tree data structure that represents the decomposition
of a biconnected graph into its triconnected components. Each node of
the tree corresponds to one of four types:
- S-node (series): a simple cycle.
- P-node (parallel): a bundle of parallel edges between two poles.
- Q-node: a single real edge (degenerate case).
- R-node (rigid): a 3-connected subgraph that cannot be further
decomposed.
SPQR-trees are widely used in graph drawing, planarity testing,
and network reliability analysis.
Features
- Pure Python --- no compiled extensions or external dependencies.
- Handles multigraphs (parallel edges between the same vertex pair).
- Implements the Gutwenger & Mutzel (2001) algorithm with corrections
to Hopcroft & Tarjan (1973).
- Simple dict-based input for quick prototyping.
- Typed package with PEP 561 support.
Installation
Install from PyPI with pip:
pip install spqrtree
Or install the latest development version from GitHub:
pip install git+https://github.com/imacat/spqrtree.git
Requires Python 3.10 or later.
Quick Start
Build an SPQR-tree from an adjacency-list dictionary:
from spqrtree import SPQRTree
# A simple diamond graph (K4 minus one edge)
graph = {
1: [2, 3, 4],
2: [1, 3, 4],
3: [1, 2, 4],
4: [1, 2, 3],
}
tree = SPQRTree(graph)
print(tree.root.type) # NodeType.R
print(len(tree.nodes())) # number of SPQR-tree nodes
The input dictionary maps each vertex to its list of neighbors. For
each pair (u, v) where u < v, one edge is added.
Usage Guide
Inspecting the Tree
The :class:`~spqrtree.SPQRTree` object exposes two main attributes:
System Message: ERROR/3 (<stdin>, line 80); backlink
Unknown interpreted text role "class".
from spqrtree import SPQRTree, NodeType
graph = {
0: [1, 2],
1: [0, 2, 3],
2: [0, 1, 3],
3: [1, 2],
}
tree = SPQRTree(graph)
for node in tree.nodes():
print(node.type, node.poles)
Each :class:`~spqrtree.SPQRNode` has the following attributes:
System Message: ERROR/3 (<stdin>, line 101); backlink
Unknown interpreted text role "class".
type --- a :class:`~spqrtree.NodeType` enum (S, P,
Q, or R).
System Message: ERROR/3 (<stdin>, line 103); backlink
Unknown interpreted text role "class".
skeleton --- the skeleton graph containing the real and virtual
edges of the component.
poles --- the two vertices shared with the parent component.
parent --- the parent node (None for the root).
children --- the list of child nodes.
Understanding Node Types
S-node (series):
Represents a cycle. The skeleton is a simple polygon whose edges
alternate between real edges and virtual edges leading to children.
P-node (parallel):
Represents parallel edges between two pole vertices. The skeleton
contains three or more edges (real and/or virtual) between the same
pair of poles.
R-node (rigid):
Represents a 3-connected component that cannot be further decomposed
by any separation pair. The skeleton is a 3-connected graph.
Q-node:
Represents a single real edge. Q-nodes appear as leaves of the tree.
Using a MultiGraph
For more control, build a
:class:`~spqrtree._graph.MultiGraph` directly:
System Message: ERROR/3 (<stdin>, line 135); backlink
Unknown interpreted text role "class".
from spqrtree._graph import MultiGraph
from spqrtree import SPQRTree
g = MultiGraph()
g.add_edge(0, 1)
g.add_edge(1, 2)
g.add_edge(2, 0)
g.add_edge(1, 3)
g.add_edge(3, 2)
tree = SPQRTree(g)
References
The implementation is based on the following papers:
- J. Hopcroft and R. Tarjan, "Dividing a graph into triconnected
components," SIAM Journal on Computing, vol. 2, no. 3,
pp. 135--158, 1973.
doi:10.1137/0202012
- G. Di Battista and R. Tamassia, "On-line planarity testing,"
SIAM Journal on Computing, vol. 25, no. 5, pp. 956--997, 1996.
doi:10.1137/S0097539794280736
- C. Gutwenger and P. Mutzel, "A linear time implementation of
SPQR-trees," Proc. 8th International Symposium on Graph Drawing
(GD 2000), LNCS 1984, pp. 77--90, Springer, 2001.
doi:10.1007/3-540-44541-2_8
Acknowledgments
The test suite was validated against the SPQR-tree implementation in
SageMath, which served as the reference
for verifying correctness of the decomposition results.
</html>