Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f0fdae11d4 | |||
| d9007c8697 | |||
| b1d681f39e | |||
| f54b66f857 | |||
| 5552e07803 | |||
| 163e453464 | |||
| c482bc5633 | |||
| c4339624ce | |||
| d7c93cf2c2 | |||
| 411ff7f465 | |||
| 24a3d89d88 | |||
| bd8499a25b | |||
| 9f6328788c | |||
| 1d981b4d01 | |||
| b70ea2c087 | |||
| d2156f06ee | |||
| 60c9f1d3a8 | |||
| 351ae0cdfe | |||
| c5f81842c7 | |||
| 646cf9d12f | |||
| 251c453437 | |||
| 851ca7fbed | |||
| af60c3b241 | |||
| 4ba9ce47d1 | |||
| 696a6b3104 | |||
| 1e8bee04ce | |||
| 37a7ff0b00 | |||
| 9ef231655e | |||
| d541aea526 | |||
| 2a56322841 | |||
| 51c9efa7f2 | |||
| 464335082d | |||
| 5829938ab0 | |||
| f537db9758 | |||
| 6ef1dc710c | |||
| b605931678 | |||
| 7554582056 | |||
| 192d97a31d | |||
| 4e92dde36e | |||
| 0a3d7b2615 | |||
| 367b5adc71 | |||
| 94d59ceaed | |||
| 24af5485d2 | |||
| ea1ab0b986 | |||
| c64c720e5a | |||
| 9d7cb7644e | |||
| a22ca4b888 | |||
| b4ddc7da8b | |||
| 291f7e98c7 |
@@ -0,0 +1,103 @@
|
|||||||
|
"""Experiment: is a given maximal planar graph in its own flip neighborhood?
|
||||||
|
|
||||||
|
The flip neighborhood N(G) of a maximal planar graph G (Definition
|
||||||
|
"flip-neighborhood" in paper.tex) is the set of maximal planar graphs obtainable
|
||||||
|
from G by a single admissible edge flip: pick an edge uv whose two incident
|
||||||
|
triangular faces are uvw and uvx, delete uv, and insert wx, provided wx is not
|
||||||
|
already an edge.
|
||||||
|
|
||||||
|
We call G a *self-flip-neighbor* iff some admissible flip G^flip(uv) is
|
||||||
|
isomorphic to G --- equivalently, G in N(G). A flip always changes the labelled
|
||||||
|
edge set (it removes uv and adds a non-edge wx), so this is a genuine question
|
||||||
|
about the isomorphism type: it asks whether a single diagonal flip can map the
|
||||||
|
triangulation back onto a copy of itself.
|
||||||
|
|
||||||
|
This module enumerates every admissible flip of G, tests each resulting
|
||||||
|
triangulation for isomorphism to G, and reports the witnessing edges. It can run
|
||||||
|
on a single graph (given as a graph6 string, or the icosahedron by default) or
|
||||||
|
survey every min-degree-5 maximal planar graph over a range of orders.
|
||||||
|
"""
|
||||||
|
import os, sys
|
||||||
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))) # repo root for `lib`
|
||||||
|
from typing import Iterator, Any, cast
|
||||||
|
from sage.all import Graph, graphs # type: ignore[attr-defined] # pylint: disable=no-name-in-module
|
||||||
|
|
||||||
|
# Reuse the flip machinery from the survey in this same directory.
|
||||||
|
from colored_edge_flip_class_survey import face_thirds, canonical_g6 # type: ignore[import]
|
||||||
|
|
||||||
|
|
||||||
|
def admissible_flips(g: Graph) -> Iterator[tuple[Any, Any, Any, Any, Graph]]:
|
||||||
|
"""Yield (u, v, w, x, H) for every admissible edge flip of g: uv is the
|
||||||
|
flipped edge, wx the inserted diagonal (a non-edge), and H = g^flip(uv)."""
|
||||||
|
pairs = face_thirds(g)
|
||||||
|
for u, v in list(g.edges(labels=False)):
|
||||||
|
thirds = pairs.get(frozenset((u, v)))
|
||||||
|
if thirds is None or len(thirds) != 2:
|
||||||
|
continue
|
||||||
|
w, x = thirds
|
||||||
|
if g.has_edge(w, x):
|
||||||
|
continue
|
||||||
|
h = g.copy()
|
||||||
|
h.delete_edge(u, v)
|
||||||
|
h.add_edge(w, x)
|
||||||
|
yield u, v, w, x, h
|
||||||
|
|
||||||
|
|
||||||
|
def self_flip_witnesses(g: Graph) -> list[tuple[Any, Any, Any, Any]]:
|
||||||
|
"""Return every (u, v, w, x) such that the admissible flip uv -> wx yields a
|
||||||
|
graph isomorphic to g. Non-empty iff g lies in its own flip neighborhood."""
|
||||||
|
target = canonical_g6(g)
|
||||||
|
witnesses: list[tuple[Any, Any, Any, Any]] = []
|
||||||
|
for u, v, w, x, h in admissible_flips(g):
|
||||||
|
if canonical_g6(h) == target:
|
||||||
|
witnesses.append((u, v, w, x))
|
||||||
|
return witnesses
|
||||||
|
|
||||||
|
|
||||||
|
def is_self_flip_neighbor(g: Graph) -> bool:
|
||||||
|
"""True iff g in N(g): some admissible flip of g is isomorphic to g."""
|
||||||
|
target = canonical_g6(g)
|
||||||
|
return any(canonical_g6(h) == target for *_e, h in admissible_flips(g))
|
||||||
|
|
||||||
|
|
||||||
|
def report_single(g: Graph) -> bool:
|
||||||
|
"""Print a per-edge flip report for g and return whether it self-flips."""
|
||||||
|
flips = list(admissible_flips(g))
|
||||||
|
witnesses = self_flip_witnesses(g)
|
||||||
|
print(f"graph6={g.graph6_string()} |V|={g.order()} |E|={g.size()} "
|
||||||
|
f"admissible flips={len(flips)} self-flip witnesses={len(witnesses)}")
|
||||||
|
for u, v, w, x in witnesses:
|
||||||
|
print(f" flip edge ({u},{v}) -> ({w},{x}) gives a graph isomorphic to G")
|
||||||
|
print(f" => G {'IS' if witnesses else 'is NOT'} in its own flip neighborhood")
|
||||||
|
return bool(witnesses)
|
||||||
|
|
||||||
|
|
||||||
|
def survey(min_order: int, max_order: int) -> None:
|
||||||
|
"""For each order in [min_order, max_order], count how many min-degree-5
|
||||||
|
maximal planar graphs are self-flip-neighbors."""
|
||||||
|
for n in range(min_order, max_order + 1):
|
||||||
|
gen = graphs.planar_graphs(
|
||||||
|
n, minimum_connectivity=3, maximum_face_size=3, minimum_degree=5
|
||||||
|
)
|
||||||
|
checked = 0
|
||||||
|
self_flip = 0
|
||||||
|
for g in gen:
|
||||||
|
checked += 1
|
||||||
|
if is_self_flip_neighbor(cast(Graph, g)):
|
||||||
|
self_flip += 1
|
||||||
|
print(f"order {n}: {checked} min-degree-5 maximal planar graphs, "
|
||||||
|
f"{self_flip} are self-flip-neighbors")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) >= 2 and sys.argv[1].lstrip("-").isdigit():
|
||||||
|
lo = int(sys.argv[1])
|
||||||
|
hi = int(sys.argv[2]) if len(sys.argv) > 2 else lo
|
||||||
|
survey(lo, hi)
|
||||||
|
else:
|
||||||
|
if len(sys.argv) >= 2:
|
||||||
|
graph = Graph(sys.argv[1])
|
||||||
|
else:
|
||||||
|
graph = graphs.IcosahedralGraph()
|
||||||
|
print("(no graph given; using the icosahedron)")
|
||||||
|
report_single(cast(Graph, graph))
|
||||||
@@ -10,12 +10,16 @@
|
|||||||
\newlabel{lem:edge-deletion-4colorable}{{4.2}{2}}
|
\newlabel{lem:edge-deletion-4colorable}{{4.2}{2}}
|
||||||
\newlabel{lem:edge-deletion-coloring-structure}{{4.3}{3}}
|
\newlabel{lem:edge-deletion-coloring-structure}{{4.3}{3}}
|
||||||
\newlabel{thm:flip-neighborhood-4colorable}{{4.4}{3}}
|
\newlabel{thm:flip-neighborhood-4colorable}{{4.4}{3}}
|
||||||
|
\@writefile{lof}{\contentsline {figure}{\numberline {2}{\ignorespaces Case\nonbreakingspace 2 of the proof of Theorem\nonbreakingspace 4.4\hbox {}: $u, v$ share color $a$ and $w, x$ share color $c$. The $\{a, b\}$-Kempe path $P$ from $u$ to $v$ separates $w$ from $x$ in the plane, so no $\{c, d\}$-path between $w$ and $x$ can avoid crossing $P$; since the color sets $\{a, b\}$ and $\{c, d\}$ are disjoint, no such path exists.}}{4}{}\protected@file@percent }
|
||||||
|
\newlabel{fig:flip-proof-case-two}{{2}{4}}
|
||||||
|
\newlabel{thm:no-colored-class-contains-G}{{4.5}{4}}
|
||||||
|
\@writefile{toc}{\contentsline {section}{\tocsection {}{5}{A computational aside: self-flip neighbors}}{4}{}\protected@file@percent }
|
||||||
|
\newlabel{def:self-flip-neighbor}{{5.1}{4}}
|
||||||
\newlabel{tocindent-1}{0pt}
|
\newlabel{tocindent-1}{0pt}
|
||||||
\newlabel{tocindent0}{0pt}
|
\newlabel{tocindent0}{0pt}
|
||||||
\newlabel{tocindent1}{17.77782pt}
|
\newlabel{tocindent1}{17.77782pt}
|
||||||
\newlabel{tocindent2}{0pt}
|
\newlabel{tocindent2}{0pt}
|
||||||
\newlabel{tocindent3}{0pt}
|
\newlabel{tocindent3}{0pt}
|
||||||
\@writefile{lof}{\contentsline {figure}{\numberline {2}{\ignorespaces Case\nonbreakingspace 2 of the proof of Theorem\nonbreakingspace 4.4\hbox {}: $u, v$ share color $a$ and $w, x$ share color $c$. The $\{a, b\}$-Kempe path $P$ from $u$ to $v$ separates $w$ from $x$ in the plane, so no $\{c, d\}$-path between $w$ and $x$ can avoid crossing $P$; since the color sets $\{a, b\}$ and $\{c, d\}$ are disjoint, no such path exists.}}{4}{}\protected@file@percent }
|
\@writefile{lot}{\contentsline {table}{\numberline {1}{\ignorespaces Self-flip neighbors among min-degree-$5$ maximal planar graphs. No graph on $n \leq 15$ vertices is a self-flip neighbor; beyond the first occurrences the fraction declines steadily.}}{5}{}\protected@file@percent }
|
||||||
\newlabel{fig:flip-proof-case-two}{{2}{4}}
|
\newlabel{tab:self-flip}{{1}{5}}
|
||||||
\newlabel{thm:no-colored-class-contains-G}{{4.5}{4}}
|
\gdef \@abspage@last{5}
|
||||||
\gdef \@abspage@last{4}
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Fdb version 3
|
# Fdb version 3
|
||||||
["pdflatex"] 1778743331 "paper.tex" "paper.pdf" "paper" 1778743331
|
["pdflatex"] 1781844089 "paper.tex" "paper.pdf" "paper" 1781844090
|
||||||
"/usr/local/texlive/2022/texmf-dist/fonts/map/fontname/texfonts.map" 1577235249 3524 cb3e574dea2d1052e39280babc910dc8 ""
|
"/usr/local/texlive/2022/texmf-dist/fonts/map/fontname/texfonts.map" 1577235249 3524 cb3e574dea2d1052e39280babc910dc8 ""
|
||||||
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/amsfonts/cmextra/cmex7.tfm" 1246382020 1004 54797486969f23fa377b128694d548df ""
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/amsfonts/cmextra/cmex7.tfm" 1246382020 1004 54797486969f23fa377b128694d548df ""
|
||||||
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/amsfonts/cmextra/cmex8.tfm" 1246382020 988 bdf658c3bfc2d96d3c8b02cfc1c94c20 ""
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/amsfonts/cmextra/cmex8.tfm" 1246382020 988 bdf658c3bfc2d96d3c8b02cfc1c94c20 ""
|
||||||
@@ -131,8 +131,8 @@
|
|||||||
"/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map" 1647878959 4410336 7d30a02e9fa9a16d7d1f8d037ba69641 ""
|
"/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map" 1647878959 4410336 7d30a02e9fa9a16d7d1f8d037ba69641 ""
|
||||||
"/usr/local/texlive/2022/texmf-var/web2c/pdftex/pdflatex.fmt" 1665017617 2826443 7e98410c533054b636c6470db83a27bc ""
|
"/usr/local/texlive/2022/texmf-var/web2c/pdftex/pdflatex.fmt" 1665017617 2826443 7e98410c533054b636c6470db83a27bc ""
|
||||||
"/usr/local/texlive/2022/texmf.cnf" 1647878952 577 209b46be99c9075fd74d4c0369380e8c ""
|
"/usr/local/texlive/2022/texmf.cnf" 1647878952 577 209b46be99c9075fd74d4c0369380e8c ""
|
||||||
"paper.aux" 1778743331 1709 057e58fcb5472314b0a7029f2c0f7505 "pdflatex"
|
"paper.aux" 1781844090 2204 9c1b0b970c8aeef1caf450ea82c0c00d "pdflatex"
|
||||||
"paper.tex" 1778743323 14730 0431b5dd1f68c135b8365d9286869b8f ""
|
"paper.tex" 1781844079 17938 6757143b0e59891047a9dd2db3f626cf ""
|
||||||
(generated)
|
(generated)
|
||||||
"paper.aux"
|
"paper.aux"
|
||||||
"paper.log"
|
"paper.log"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 14 MAY 2026 03:22
|
This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 19 JUN 2026 00:41
|
||||||
entering extended mode
|
entering extended mode
|
||||||
restricted \write18 enabled.
|
restricted \write18 enabled.
|
||||||
%&-line parsing enabled.
|
%&-line parsing enabled.
|
||||||
@@ -486,39 +486,43 @@ File: epstopdf-sys.cfg 2010/07/13 v1.3 Configuration of (r)epstopdf for TeX Liv
|
|||||||
e
|
e
|
||||||
))
|
))
|
||||||
[1{/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map}]
|
[1{/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map}]
|
||||||
[2] [3] [4] (./paper.aux) )
|
[2] [3]
|
||||||
|
|
||||||
|
LaTeX Warning: `h' float specifier changed to `ht'.
|
||||||
|
|
||||||
|
[4] [5] (./paper.aux) )
|
||||||
Here is how much of TeX's memory you used:
|
Here is how much of TeX's memory you used:
|
||||||
13206 strings out of 478268
|
13208 strings out of 478268
|
||||||
266409 string characters out of 5846347
|
266448 string characters out of 5846347
|
||||||
540812 words of memory out of 5000000
|
541829 words of memory out of 5000000
|
||||||
31041 multiletter control sequences out of 15000+600000
|
31043 multiletter control sequences out of 15000+600000
|
||||||
477211 words of font info for 59 fonts, out of 8000000 for 9000
|
477211 words of font info for 59 fonts, out of 8000000 for 9000
|
||||||
1302 hyphenation exceptions out of 8191
|
1302 hyphenation exceptions out of 8191
|
||||||
100i,9n,104p,495b,794s stack positions out of 10000i,1000n,20000p,200000b,200000s
|
100i,9n,104p,495b,794s stack positions out of 10000i,1000n,20000p,200000b,200000s
|
||||||
</usr/local/texlive/2022/texmf-dist/fonts/type1/publ
|
</usr/local/texlive/2022/texmf-dist/fonts/type1/public/a
|
||||||
ic/amsfonts/cm/cmbx10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/publi
|
msfonts/cm/cmbx10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/am
|
||||||
c/amsfonts/cm/cmcsc10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/publi
|
sfonts/cm/cmcsc10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/am
|
||||||
c/amsfonts/cm/cmex10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public
|
sfonts/cm/cmex10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/ams
|
||||||
/amsfonts/cm/cmmi10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/
|
fonts/cm/cmmi10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsf
|
||||||
amsfonts/cm/cmmi5.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/am
|
onts/cm/cmmi5.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfon
|
||||||
sfonts/cm/cmmi7.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsf
|
ts/cm/cmmi7.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts
|
||||||
onts/cm/cmmi8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfon
|
/cm/cmmi8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/c
|
||||||
ts/cm/cmmi9.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts
|
m/cmmi9.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/
|
||||||
/cm/cmr10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/c
|
cmr10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cm
|
||||||
m/cmr6.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/c
|
r6.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr7.
|
||||||
mr7.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr8
|
pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr8.pfb
|
||||||
.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr9.pf
|
></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr9.pfb></
|
||||||
b></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy10.pfb
|
usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy10.pfb></u
|
||||||
></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy7.pfb><
|
sr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy7.pfb></usr
|
||||||
/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy8.pfb></u
|
/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy8.pfb></usr/l
|
||||||
sr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy9.pfb></usr
|
ocal/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy9.pfb></usr/loc
|
||||||
/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti10.pfb></usr/
|
al/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti10.pfb></usr/loca
|
||||||
local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti8.pfb></usr/lo
|
l/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti8.pfb></usr/local/
|
||||||
cal/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/symbols/msam10.pfb>
|
texlive/2022/texmf-dist/fonts/type1/public/amsfonts/symbols/msam10.pfb>
|
||||||
Output written on paper.pdf (4 pages, 246274 bytes).
|
Output written on paper.pdf (5 pages, 251916 bytes).
|
||||||
PDF statistics:
|
PDF statistics:
|
||||||
120 PDF objects out of 1000 (max. 8388607)
|
123 PDF objects out of 1000 (max. 8388607)
|
||||||
73 compressed objects within 1 object stream
|
75 compressed objects within 1 object stream
|
||||||
0 named destinations out of 1000 (max. 500000)
|
0 named destinations out of 1000 (max. 500000)
|
||||||
13 words of extra memory for PDF output out of 10000 (max. 10000000)
|
13 words of extra memory for PDF output out of 10000 (max. 10000000)
|
||||||
|
|
||||||
|
|||||||
@@ -371,6 +371,79 @@ in particular, $\varphi$ is a proper $4$-coloring of $H_k = G$. But
|
|||||||
$\chi(G) \geq 5$ admits no such coloring, a contradiction.
|
$\chi(G) \geq 5$ admits no such coloring, a contradiction.
|
||||||
\end{proof}
|
\end{proof}
|
||||||
|
|
||||||
|
\section{A computational aside: self-flip neighbors}
|
||||||
|
|
||||||
|
Since $\mathcal{N}(G)$ is defined up to isomorphism, it is natural to
|
||||||
|
ask whether $G$ can be one of its own flip neighbors.
|
||||||
|
|
||||||
|
\begin{definition}[Self-flip neighbor]\label{def:self-flip-neighbor}
|
||||||
|
A maximal planar graph $G$ is a \emph{self-flip neighbor} if
|
||||||
|
$G \in \mathcal{N}(G)$; that is, if some admissible edge flip
|
||||||
|
$G^{\mathrm{flip}(uv)}$ is isomorphic to $G$.
|
||||||
|
\end{definition}
|
||||||
|
|
||||||
|
A single flip always changes the labelled edge set --- it deletes
|
||||||
|
$uv$ and inserts a non-edge $wx$ --- so the property is genuinely one
|
||||||
|
of the isomorphism type: it asks whether a diagonal flip can carry the
|
||||||
|
triangulation back onto a copy of itself. Equivalently, the degree
|
||||||
|
changes induced by the flip ($-1$ at $u$ and $v$, $+1$ at $w$ and $x$)
|
||||||
|
must be undone by an automorphism of the resulting graph.
|
||||||
|
|
||||||
|
We surveyed every maximal planar graph of minimum degree $5$ on
|
||||||
|
$n \leq 25$ vertices, counting those that are self-flip neighbors.
|
||||||
|
The results are recorded in Table~\ref{tab:self-flip}.
|
||||||
|
|
||||||
|
\begin{table}[h]
|
||||||
|
\centering
|
||||||
|
\begin{tabular}{rrrr}
|
||||||
|
\hline
|
||||||
|
$n$ & min-degree-$5$ & self-flip & fraction \\
|
||||||
|
& triangulations & neighbors & \\
|
||||||
|
\hline
|
||||||
|
$12$ & $1$ & $0$ & $0\%$ \\
|
||||||
|
$13$ & $0$ & $0$ & --- \\
|
||||||
|
$14$ & $1$ & $0$ & $0\%$ \\
|
||||||
|
$15$ & $1$ & $0$ & $0\%$ \\
|
||||||
|
$16$ & $3$ & $1$ & $33.3\%$ \\
|
||||||
|
$17$ & $4$ & $1$ & $25.0\%$ \\
|
||||||
|
$18$ & $12$ & $2$ & $16.7\%$ \\
|
||||||
|
$19$ & $23$ & $5$ & $21.7\%$ \\
|
||||||
|
$20$ & $73$ & $12$ & $16.4\%$ \\
|
||||||
|
$21$ & $192$ & $27$ & $14.1\%$ \\
|
||||||
|
$22$ & $651$ & $51$ & $7.8\%$ \\
|
||||||
|
$23$ & $2070$ & $120$ & $5.8\%$ \\
|
||||||
|
$24$ & $7290$ & $273$ & $3.7\%$ \\
|
||||||
|
$25$ & $25381$ & $598$ & $2.4\%$ \\
|
||||||
|
\hline
|
||||||
|
\end{tabular}
|
||||||
|
\caption{Self-flip neighbors among min-degree-$5$ maximal planar
|
||||||
|
graphs. No graph on $n \leq 15$ vertices is a self-flip neighbor;
|
||||||
|
beyond the first occurrences the fraction declines steadily.}
|
||||||
|
\label{tab:self-flip}
|
||||||
|
\end{table}
|
||||||
|
|
||||||
|
\begin{remark}
|
||||||
|
The proportion of self-flip neighbors declines as $n$ grows --- from a
|
||||||
|
peak near a third at $n = 16$ to roughly $2\%$ at $n = 25$ --- and the
|
||||||
|
trend is the one to expect. A self-flip neighbor requires a flip whose
|
||||||
|
induced degree changes are reversed by an automorphism of the flipped
|
||||||
|
graph, but min-degree-$5$ triangulations are asymptotically rigid: the
|
||||||
|
share with a nontrivial automorphism group, let alone one of the
|
||||||
|
required form, tends to $0$. The absolute count of self-flip neighbors
|
||||||
|
continues to grow, but ever more slowly than the census itself, so the
|
||||||
|
fraction appears to vanish in the limit.
|
||||||
|
|
||||||
|
This observation is not, by itself, useful for narrowing down the
|
||||||
|
minimal criminal $G_0$. Whether or not $G_0$ is a self-flip neighbor,
|
||||||
|
Theorem~\ref{thm:flip-neighborhood-4colorable} already shows every
|
||||||
|
graph in $\mathcal{N}(G_0)$ is $4$-colorable; self-flip neighborness
|
||||||
|
is a generic structural feature of the surrounding census rather than a
|
||||||
|
property forced on, or forbidden of, a minimum counterexample. It
|
||||||
|
neither includes nor excludes any candidate, and so contributes nothing
|
||||||
|
to the elimination program beyond confirming that the flip operation
|
||||||
|
behaves on min-degree-$5$ triangulations as one would anticipate.
|
||||||
|
\end{remark}
|
||||||
|
|
||||||
\end{document}
|
\end{document}
|
||||||
|
|
||||||
%-----------------------------------------------------------------------
|
%-----------------------------------------------------------------------
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
"""Survey: for each n, how many maximal planar graphs (plane-triangulation
|
||||||
|
iso classes) are *bridge-derived* level graphs of some Even Level Graph.
|
||||||
|
|
||||||
|
Bridge-derivedness is decided exhaustively via the backward bridge-switch
|
||||||
|
orbit (see small_n_probe.is_bridge_derived): a triangulation G is
|
||||||
|
bridge-derived iff some valid parity partition L of G admits an Even Level
|
||||||
|
Graph (parity L) in G's backward bridge-orbit. Feasible only at small n.
|
||||||
|
|
||||||
|
Also cross-tabulates against the intertwining-tree property so the two
|
||||||
|
covering families in the disjunction conjecture can be compared.
|
||||||
|
|
||||||
|
Usage: python3 bridge_derived_survey.py [n_max] (default 11)
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
sys.path.insert(0, '/Users/didericis/Code/math-research/papers/'
|
||||||
|
'level_resolutions_of_maximal_planar_graphs/experiments')
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
from triangulation_gen import enumerate_all_triangulations
|
||||||
|
from small_n_probe import is_bridge_derived
|
||||||
|
from test_disjunction import is_intertwining_tree
|
||||||
|
|
||||||
|
|
||||||
|
def survey_n(n):
|
||||||
|
t0 = time.time()
|
||||||
|
tris = enumerate_all_triangulations(n)
|
||||||
|
n_bridge = 0
|
||||||
|
n_inter = 0
|
||||||
|
n_bridge_and_inter = 0
|
||||||
|
n_bridge_only = 0
|
||||||
|
n_inter_only = 0
|
||||||
|
n_neither = 0
|
||||||
|
for G in tris:
|
||||||
|
bd = is_bridge_derived(G)
|
||||||
|
it = is_intertwining_tree(G)[0]
|
||||||
|
if bd:
|
||||||
|
n_bridge += 1
|
||||||
|
if it:
|
||||||
|
n_inter += 1
|
||||||
|
if bd and it:
|
||||||
|
n_bridge_and_inter += 1
|
||||||
|
elif bd:
|
||||||
|
n_bridge_only += 1
|
||||||
|
elif it:
|
||||||
|
n_inter_only += 1
|
||||||
|
else:
|
||||||
|
n_neither += 1
|
||||||
|
return {
|
||||||
|
'n': n,
|
||||||
|
'total': len(tris),
|
||||||
|
'bridge': n_bridge,
|
||||||
|
'inter': n_inter,
|
||||||
|
'bridge_and_inter': n_bridge_and_inter,
|
||||||
|
'bridge_only': n_bridge_only,
|
||||||
|
'inter_only': n_inter_only,
|
||||||
|
'neither': n_neither,
|
||||||
|
'elapsed': time.time() - t0,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
n_max = int(sys.argv[1]) if len(sys.argv) > 1 else 11
|
||||||
|
rows = []
|
||||||
|
print(f'{"n":>3} {"total":>7} {"bridge-deriv":>13} {"%":>6} '
|
||||||
|
f'{"inter":>7} {"b&i":>6} {"b-only":>7} {"i-only":>7} '
|
||||||
|
f'{"neither":>8} {"sec":>7}', flush=True)
|
||||||
|
for n in range(6, n_max + 1):
|
||||||
|
r = survey_n(n)
|
||||||
|
rows.append(r)
|
||||||
|
pct = 100.0 * r['bridge'] / r['total'] if r['total'] else 0.0
|
||||||
|
print(f'{r["n"]:>3} {r["total"]:>7} {r["bridge"]:>13} {pct:>5.1f}% '
|
||||||
|
f'{r["inter"]:>7} {r["bridge_and_inter"]:>6} '
|
||||||
|
f'{r["bridge_only"]:>7} {r["inter_only"]:>7} '
|
||||||
|
f'{r["neither"]:>8} {r["elapsed"]:>6.1f}', flush=True)
|
||||||
|
if r['neither']:
|
||||||
|
print(f' *** {r["neither"]} triangulation(s) at n={n} are '
|
||||||
|
f'NEITHER bridge-derived nor intertwining trees ***',
|
||||||
|
flush=True)
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
"""Draw the two former "failures" (n=9 bipyramid, n=10 bipyramid+stacked) the
|
||||||
|
RIGHT way: a proper 4-colouring whose 4 colours split into two complementary
|
||||||
|
pairs, each inducing an outerplanar (bipartite => even-cycle) subgraph.
|
||||||
|
|
||||||
|
Top row: the planar drawing with the 4-colouring; edges of the two split
|
||||||
|
classes drawn in two styles. Bottom row: the two complementary subgraphs shown
|
||||||
|
separately, each annotated outerplanar (tree / forest / even cycle).
|
||||||
|
|
||||||
|
Renders into this experiments folder.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from itertools import combinations
|
||||||
|
|
||||||
|
import networkx as nx
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
T24 = [(0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (0, 7), (0, 8),
|
||||||
|
(1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8),
|
||||||
|
(2, 3), (2, 4), (3, 5), (4, 8), (5, 6), (6, 7), (7, 8)]
|
||||||
|
T94 = [(0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (0, 7), (0, 8), (0, 9),
|
||||||
|
(1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8),
|
||||||
|
(2, 3), (2, 4), (3, 5), (4, 8), (5, 6), (6, 7), (7, 8),
|
||||||
|
(7, 9), (8, 9)]
|
||||||
|
|
||||||
|
CMAP = {0: '#444444', 1: '#d62728', 2: '#1f77b4', 3: '#2ca02c'}
|
||||||
|
CNAME = {0: 'grey', 1: 'red', 2: 'blue', 3: 'green'}
|
||||||
|
SPLITS = [({0, 1}, {2, 3}), ({0, 2}, {1, 3}), ({0, 3}, {1, 2})]
|
||||||
|
|
||||||
|
|
||||||
|
def is_outerplanar(G):
|
||||||
|
if G.number_of_nodes() <= 3:
|
||||||
|
return True
|
||||||
|
H = G.copy()
|
||||||
|
apex = max(H.nodes()) + 1
|
||||||
|
for v in G.nodes():
|
||||||
|
H.add_edge(apex, v)
|
||||||
|
return nx.check_planarity(H)[0]
|
||||||
|
|
||||||
|
|
||||||
|
def enumerate_4colorings(G):
|
||||||
|
nodes = list(G.nodes())
|
||||||
|
adj = {v: set(G.neighbors(v)) for v in nodes}
|
||||||
|
coloring = {}
|
||||||
|
|
||||||
|
def bt(i, mx):
|
||||||
|
if i == len(nodes):
|
||||||
|
yield dict(coloring)
|
||||||
|
return
|
||||||
|
v = nodes[i]
|
||||||
|
used = {coloring[w] for w in adj[v] if w in coloring}
|
||||||
|
for c in range(min(3, mx + 1) + 1):
|
||||||
|
if c in used:
|
||||||
|
continue
|
||||||
|
coloring[v] = c
|
||||||
|
yield from bt(i + 1, max(mx, c))
|
||||||
|
del coloring[v]
|
||||||
|
|
||||||
|
yield from bt(0, -1)
|
||||||
|
|
||||||
|
|
||||||
|
def find_good_split(G):
|
||||||
|
for col in enumerate_4colorings(G):
|
||||||
|
for A, B in SPLITS:
|
||||||
|
va = [v for v in G if col[v] in A]
|
||||||
|
vb = [v for v in G if col[v] in B]
|
||||||
|
GA, GB = G.subgraph(va), G.subgraph(vb)
|
||||||
|
if (is_outerplanar(GA) and nx.is_bipartite(GA)
|
||||||
|
and is_outerplanar(GB) and nx.is_bipartite(GB)):
|
||||||
|
return col, (A, B)
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
|
def bipyramid_pos(rim_cycle, apexA, apexB):
|
||||||
|
k = len(rim_cycle)
|
||||||
|
order = rim_cycle[1:] + [rim_cycle[0]]
|
||||||
|
pos = {}
|
||||||
|
for i, v in enumerate(order):
|
||||||
|
x = 1.7 * (1 - 2 * i / (k - 1))
|
||||||
|
y = 1.0 * (x / 1.7) ** 2
|
||||||
|
pos[v] = (x, y)
|
||||||
|
pos[apexA] = (0.0, 0.62)
|
||||||
|
pos[apexB] = (0.0, -1.7)
|
||||||
|
return pos
|
||||||
|
|
||||||
|
|
||||||
|
def describe(G):
|
||||||
|
if G.number_of_edges() == 0:
|
||||||
|
return "edgeless (isolated vertices)"
|
||||||
|
if nx.is_forest(G):
|
||||||
|
return "forest (tree, no cycles)"
|
||||||
|
girth_even = all(len(c) % 2 == 0 for c in nx.cycle_basis(G))
|
||||||
|
comps = nx.number_connected_components(G)
|
||||||
|
tag = "even cycles" if girth_even else "ODD CYCLE!"
|
||||||
|
return f"outerplanar, {tag}, {comps} component(s)"
|
||||||
|
|
||||||
|
|
||||||
|
def draw_case(fig, gs_row, edges, pos, title):
|
||||||
|
G = nx.Graph(edges)
|
||||||
|
col, split = find_good_split(G)
|
||||||
|
A, B = split
|
||||||
|
node_colors = [CMAP[col[v]] for v in G.nodes()]
|
||||||
|
ea = [e for e in G.edges() if col[e[0]] in A and col[e[1]] in A]
|
||||||
|
eb = [e for e in G.edges() if col[e[0]] in B and col[e[1]] in B]
|
||||||
|
ecross = [e for e in G.edges() if e not in ea and e not in eb]
|
||||||
|
|
||||||
|
# left: whole graph, both classes highlighted
|
||||||
|
ax = fig.add_subplot(gs_row[0])
|
||||||
|
nx.draw_networkx_edges(G, pos, ax=ax, edgelist=ecross,
|
||||||
|
edge_color='#dddddd', width=1.0)
|
||||||
|
nx.draw_networkx_edges(G, pos, ax=ax, edgelist=ea,
|
||||||
|
edge_color='#e8860a', width=3.0)
|
||||||
|
nx.draw_networkx_edges(G, pos, ax=ax, edgelist=eb,
|
||||||
|
edge_color='#7b2fbf', width=3.0, style='dashed')
|
||||||
|
nx.draw_networkx_nodes(G, pos, node_color=node_colors, node_size=720,
|
||||||
|
edgecolors='black', linewidths=1.4, ax=ax)
|
||||||
|
nx.draw_networkx_labels(G, pos, ax=ax, font_color='white',
|
||||||
|
font_weight='bold', font_size=11)
|
||||||
|
an = "/".join(CNAME[c] for c in sorted(A))
|
||||||
|
bn = "/".join(CNAME[c] for c in sorted(B))
|
||||||
|
ax.set_title(f"{title}\nsplit [{an}] (orange solid) | "
|
||||||
|
f"[{bn}] (purple dashed)", fontsize=10)
|
||||||
|
ax.axis('off'); ax.set_aspect('equal')
|
||||||
|
|
||||||
|
# middle: subgraph A alone
|
||||||
|
GA = G.subgraph([v for v in G if col[v] in A])
|
||||||
|
axA = fig.add_subplot(gs_row[1])
|
||||||
|
nx.draw_networkx_edges(GA, pos, ax=axA, edge_color='#e8860a', width=3.0)
|
||||||
|
nx.draw_networkx_nodes(GA, pos, node_color=[CMAP[col[v]] for v in GA],
|
||||||
|
node_size=720, edgecolors='black',
|
||||||
|
linewidths=1.4, ax=axA)
|
||||||
|
nx.draw_networkx_labels(GA, pos, ax=axA, font_color='white',
|
||||||
|
font_weight='bold', font_size=11)
|
||||||
|
axA.set_title(f"[{an}] subgraph\n{describe(GA)}", fontsize=9)
|
||||||
|
axA.axis('off'); axA.set_aspect('equal')
|
||||||
|
|
||||||
|
# right: subgraph B alone
|
||||||
|
GB = G.subgraph([v for v in G if col[v] in B])
|
||||||
|
axB = fig.add_subplot(gs_row[2])
|
||||||
|
nx.draw_networkx_edges(GB, pos, ax=axB, edge_color='#7b2fbf', width=3.0)
|
||||||
|
nx.draw_networkx_nodes(GB, pos, node_color=[CMAP[col[v]] for v in GB],
|
||||||
|
node_size=720, edgecolors='black',
|
||||||
|
linewidths=1.4, ax=axB)
|
||||||
|
nx.draw_networkx_labels(GB, pos, ax=axB, font_color='white',
|
||||||
|
font_weight='bold', font_size=11)
|
||||||
|
axB.set_title(f"[{bn}] subgraph\n{describe(GB)}", fontsize=9)
|
||||||
|
axB.axis('off'); axB.set_aspect('equal')
|
||||||
|
|
||||||
|
|
||||||
|
rim = [2, 3, 5, 6, 7, 8, 4]
|
||||||
|
pos24 = bipyramid_pos(rim, 0, 1)
|
||||||
|
pos94 = dict(pos24)
|
||||||
|
pos94[9] = ((pos24[0][0] + pos24[7][0] + pos24[8][0]) / 3,
|
||||||
|
(pos24[0][1] + pos24[7][1] + pos24[8][1]) / 3)
|
||||||
|
|
||||||
|
fig = plt.figure(figsize=(15, 10))
|
||||||
|
gs = fig.add_gridspec(2, 3)
|
||||||
|
draw_case(fig, [gs[0, 0], gs[0, 1], gs[0, 2]], T24, pos24,
|
||||||
|
"n=9 T24: 7-gonal bipyramid")
|
||||||
|
draw_case(fig, [gs[1, 0], gs[1, 1], gs[1, 2]], T94, pos94,
|
||||||
|
"n=10 T94: bipyramid + stacked vertex 9")
|
||||||
|
fig.suptitle("Both decompose: 4-colouring -> 2+2 colour split -> two "
|
||||||
|
"complementary outerplanar even-cycle subgraphs", fontsize=12)
|
||||||
|
fig.tight_layout(rect=[0, 0, 1, 0.97])
|
||||||
|
out = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||||
|
'split_decomposition.png')
|
||||||
|
fig.savefig(out, dpi=140)
|
||||||
|
print(f"wrote {out}")
|
||||||
|
After Width: | Height: | Size: 255 KiB |
@@ -0,0 +1,125 @@
|
|||||||
|
"""Corrected sanity check for the nested-outerplanar-shells construction.
|
||||||
|
|
||||||
|
The construction splits the FOUR colours into TWO complementary pairs (two
|
||||||
|
parity classes). Each pair induces a bipartite subgraph; the two subgraphs
|
||||||
|
partition the vertex set. For a nested-shell decomposition we want BOTH
|
||||||
|
complementary subgraphs to be outerplanar (bipartite => only even cycles, so
|
||||||
|
"even cycles" is automatic; outerplanar is the binding condition).
|
||||||
|
|
||||||
|
There are exactly 3 ways to split {0,1,2,3} into two pairs:
|
||||||
|
{0,1}|{2,3}, {0,2}|{1,3}, {0,3}|{1,2}.
|
||||||
|
|
||||||
|
Criterion (per triangulation): does SOME proper 4-colouring admit SOME split
|
||||||
|
whose two complementary subgraphs are both outerplanar?
|
||||||
|
|
||||||
|
This is the right test (an earlier version wrongly demanded that all SIX
|
||||||
|
colour pairs be outerplanar, which odd bipyramids fail on the apex/heavy-rim
|
||||||
|
pair -- but that pair is never one we'd use).
|
||||||
|
|
||||||
|
Usage: python3 two_color_split_survey.py [n_max] (default 10)
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
import networkx as nx
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
sys.path.insert(0, '/Users/didericis/Code/math-research/papers/'
|
||||||
|
'level_resolutions_of_maximal_planar_graphs/experiments')
|
||||||
|
from triangulation_gen import enumerate_all_triangulations
|
||||||
|
|
||||||
|
# the 3 ways to split 4 colours into two complementary pairs
|
||||||
|
SPLITS = [(({0, 1}), ({2, 3})),
|
||||||
|
(({0, 2}), ({1, 3})),
|
||||||
|
(({0, 3}), ({1, 2}))]
|
||||||
|
|
||||||
|
|
||||||
|
def is_outerplanar(G):
|
||||||
|
if G.number_of_nodes() <= 3:
|
||||||
|
return True
|
||||||
|
H = G.copy()
|
||||||
|
apex = max(H.nodes()) + 1
|
||||||
|
for v in G.nodes():
|
||||||
|
H.add_edge(apex, v)
|
||||||
|
return nx.check_planarity(H)[0]
|
||||||
|
|
||||||
|
|
||||||
|
def enumerate_4colorings(G):
|
||||||
|
nodes = list(G.nodes())
|
||||||
|
adj = {v: set(G.neighbors(v)) for v in nodes}
|
||||||
|
coloring = {}
|
||||||
|
|
||||||
|
def bt(i, mx):
|
||||||
|
if i == len(nodes):
|
||||||
|
yield dict(coloring)
|
||||||
|
return
|
||||||
|
v = nodes[i]
|
||||||
|
used = {coloring[w] for w in adj[v] if w in coloring}
|
||||||
|
for c in range(min(3, mx + 1) + 1):
|
||||||
|
if c in used:
|
||||||
|
continue
|
||||||
|
coloring[v] = c
|
||||||
|
yield from bt(i + 1, max(mx, c))
|
||||||
|
del coloring[v]
|
||||||
|
|
||||||
|
yield from bt(0, -1)
|
||||||
|
|
||||||
|
|
||||||
|
def outerplanar_even(G):
|
||||||
|
"""Outerplanar AND every cycle even (i.e. bipartite). Disconnected ok.
|
||||||
|
For a two-colour subgraph of a proper colouring bipartiteness is automatic,
|
||||||
|
but we verify it explicitly so the criterion is honestly enforced."""
|
||||||
|
return is_outerplanar(G) and nx.is_bipartite(G)
|
||||||
|
|
||||||
|
|
||||||
|
def good_split(G, col):
|
||||||
|
"""Return the first (sideA, sideB) split whose two complementary
|
||||||
|
subgraphs are both outerplanar-with-even-cycles, or None.
|
||||||
|
Disconnected subgraphs are allowed."""
|
||||||
|
for A, B in SPLITS:
|
||||||
|
va = [v for v in G if col[v] in A]
|
||||||
|
vb = [v for v in G if col[v] in B]
|
||||||
|
if outerplanar_even(G.subgraph(va)) and outerplanar_even(G.subgraph(vb)):
|
||||||
|
return (A, B)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def has_good_coloring(G):
|
||||||
|
"""True iff SOME proper 4-colouring admits a valid 2+2 split.
|
||||||
|
Early-exits on the first good colouring."""
|
||||||
|
for col in enumerate_4colorings(G):
|
||||||
|
if good_split(G, col) is not None:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def survey_n(n):
|
||||||
|
t0 = time.time()
|
||||||
|
tris = enumerate_all_triangulations(n)
|
||||||
|
n_good = 0
|
||||||
|
bad = []
|
||||||
|
for gi, G in enumerate(tris):
|
||||||
|
ok = has_good_coloring(G)
|
||||||
|
if ok:
|
||||||
|
n_good += 1
|
||||||
|
else:
|
||||||
|
bad.append((gi, G))
|
||||||
|
return n, len(tris), n_good, bad, time.time() - t0
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
n_max = int(sys.argv[1]) if len(sys.argv) > 1 else 10
|
||||||
|
print(f"{'n':>3} {'tris':>6} {'has 2+2 split':>14} {'time(s)':>8}")
|
||||||
|
print("-" * 38)
|
||||||
|
for n in range(6, n_max + 1):
|
||||||
|
n, ntri, ngood, bad, dt = survey_n(n)
|
||||||
|
flag = "" if ngood == ntri else " <-- GAP"
|
||||||
|
print(f"{n:>3} {ntri:>6} {ngood:>9}/{ntri:<4} {dt:>8.1f}{flag}")
|
||||||
|
for gi, G in bad[:5]:
|
||||||
|
edges = sorted(tuple(sorted(e)) for e in G.edges())
|
||||||
|
print(f" no split: T{gi} edges={edges}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -44,19 +44,21 @@
|
|||||||
\newlabel{def:intertwining-tree}{{4.6}{7}{Intertwining tree}{theorem.4.6}{}}
|
\newlabel{def:intertwining-tree}{{4.6}{7}{Intertwining tree}{theorem.4.6}{}}
|
||||||
\newlabel{thm:intertwining-iff-hamiltonian-dual}{{4.7}{7}{}{theorem.4.7}{}}
|
\newlabel{thm:intertwining-iff-hamiltonian-dual}{{4.7}{7}{}{theorem.4.7}{}}
|
||||||
\newlabel{conj:every-triangulation-derived}{{4.8}{7}{}{theorem.4.8}{}}
|
\newlabel{conj:every-triangulation-derived}{{4.8}{7}{}{theorem.4.8}{}}
|
||||||
\@writefile{toc}{\contentsline {subsection}{\tocsubsection {}{}{The boundary case $n = 21$}}{7}{section*.2}\protected@file@percent }
|
\@writefile{lot}{\contentsline {table}{\numberline {2}{\ignorespaces Bridge-derived census for $6 \leq n \leq 10$. \emph {bridge-derived} counts plane-triangulation iso classes that are bridge-derived level graphs of some Even Level Graph, decided by exhaustive backward bridge-switch search over all valid parity partitions; \% is its fraction of all triangulations. \emph {intertwining only} counts those that are intertwining trees but not bridge-derived; \emph {neither} counts those covered by no disjunct. Every triangulation in this range is an intertwining tree, and every bridge-derived one is too, so bridge-derived $\subseteq $ intertwining tree here.}}{8}{table.2}\protected@file@percent }
|
||||||
|
\newlabel{tab:bridge-census}{{2}{8}{Bridge-derived census for $6 \leq n \leq 10$. \emph {bridge-derived} counts plane-triangulation iso classes that are bridge-derived level graphs of some Even Level Graph, decided by exhaustive backward bridge-switch search over all valid parity partitions; \% is its fraction of all triangulations. \emph {intertwining only} counts those that are intertwining trees but not bridge-derived; \emph {neither} counts those covered by no disjunct. Every triangulation in this range is an intertwining tree, and every bridge-derived one is too, so bridge-derived $\subseteq $ intertwining tree here}{table.2}{}}
|
||||||
|
\@writefile{toc}{\contentsline {subsection}{\tocsubsection {}{}{The boundary case $n = 21$}}{8}{section*.2}\protected@file@percent }
|
||||||
\citation{holton-mckay}
|
\citation{holton-mckay}
|
||||||
\@writefile{lot}{\contentsline {table}{\numberline {2}{\ignorespaces The six Holton--McKay duals at $n = 21$, the first triangulations that are not intertwining trees. Each is a bridge-derived level graph: duals $1$ and $2$ are Even Level Graphs outright (zero switches), and the remaining four reach an Even Level Graph in $1$--$4$ bridge switches. All witnesses are step-verified.}}{8}{table.2}\protected@file@percent }
|
\@writefile{lot}{\contentsline {table}{\numberline {3}{\ignorespaces The six Holton--McKay duals at $n = 21$, the first triangulations that are not intertwining trees. Each is a bridge-derived level graph: duals $1$ and $2$ are Even Level Graphs outright (zero switches), and the remaining four reach an Even Level Graph in $1$--$4$ bridge switches. All witnesses are step-verified.}}{9}{table.3}\protected@file@percent }
|
||||||
\newlabel{tab:n21}{{2}{8}{The six Holton--McKay duals at $n = 21$, the first triangulations that are not intertwining trees. Each is a bridge-derived level graph: duals $1$ and $2$ are Even Level Graphs outright (zero switches), and the remaining four reach an Even Level Graph in $1$--$4$ bridge switches. All witnesses are step-verified}{table.2}{}}
|
\newlabel{tab:n21}{{3}{9}{The six Holton--McKay duals at $n = 21$, the first triangulations that are not intertwining trees. Each is a bridge-derived level graph: duals $1$ and $2$ are Even Level Graphs outright (zero switches), and the remaining four reach an Even Level Graph in $1$--$4$ bridge switches. All witnesses are step-verified}{table.3}{}}
|
||||||
\@writefile{toc}{\contentsline {subsection}{\tocsubsection {}{}{The cyclically-$5$-connected case: $n = 24$}}{8}{section*.3}\protected@file@percent }
|
|
||||||
\@writefile{lof}{\contentsline {figure}{\numberline {5}{\ignorespaces The six Holton--McKay duals, drawn as crossing-free planar graphs and coloured by parity (blue even, orange odd, with respect to the fixed level-parity labelling). The solid green edges are the bridge edges introduced by the bridge switches from each dual's witness Even Level Graph. Each green edge is a bridge of its parity subgraph, so no new cycle -- and in particular no odd cycle -- is created; duals $1$ and $2$ coincide with their Even Level Graphs and have no added edge.}}{9}{figure.5}\protected@file@percent }
|
\@writefile{lof}{\contentsline {figure}{\numberline {5}{\ignorespaces The six Holton--McKay duals, drawn as crossing-free planar graphs and coloured by parity (blue even, orange odd, with respect to the fixed level-parity labelling). The solid green edges are the bridge edges introduced by the bridge switches from each dual's witness Even Level Graph. Each green edge is a bridge of its parity subgraph, so no new cycle -- and in particular no odd cycle -- is created; duals $1$ and $2$ coincide with their Even Level Graphs and have no added edge.}}{9}{figure.5}\protected@file@percent }
|
||||||
\newlabel{fig:n21-duals}{{5}{9}{The six Holton--McKay duals, drawn as crossing-free planar graphs and coloured by parity (blue even, orange odd, with respect to the fixed level-parity labelling). The solid green edges are the bridge edges introduced by the bridge switches from each dual's witness Even Level Graph. Each green edge is a bridge of its parity subgraph, so no new cycle -- and in particular no odd cycle -- is created; duals $1$ and $2$ coincide with their Even Level Graphs and have no added edge}{figure.5}{}}
|
\newlabel{fig:n21-duals}{{5}{9}{The six Holton--McKay duals, drawn as crossing-free planar graphs and coloured by parity (blue even, orange odd, with respect to the fixed level-parity labelling). The solid green edges are the bridge edges introduced by the bridge switches from each dual's witness Even Level Graph. Each green edge is a bridge of its parity subgraph, so no new cycle -- and in particular no odd cycle -- is created; duals $1$ and $2$ coincide with their Even Level Graphs and have no added edge}{figure.5}{}}
|
||||||
\@writefile{lof}{\contentsline {figure}{\numberline {6}{\ignorespaces The $24$-vertex dual $T$ of the unique $44$-vertex non-Hamiltonian cyclically $5$-connected cubic planar graph (Holton--McKay Fig.\nonbreakingspace 2.10), drawn crossing-free and coloured by the fixed parity labelling (blue even, orange odd). $T$ is $5$-connected and not an intertwining tree, yet is a bridge-derived level graph: the two solid green edges $\{6,19\}$ and $\{20,22\}$ are the bridge edges introduced by the two bridge switches carrying its witness Even Level Graph (source $19$) to $T$. Each green edge is a bridge of its parity subgraph -- $\{6, 19\}$ in the even subgraph, $\{20,22\}$ in the odd -- so no new cycle, and in particular no odd cycle, is created.}}{10}{figure.6}\protected@file@percent }
|
\@writefile{toc}{\contentsline {subsection}{\tocsubsection {}{}{The cyclically-$5$-connected case: $n = 24$}}{10}{section*.3}\protected@file@percent }
|
||||||
\newlabel{fig:n24-dual}{{6}{10}{The $24$-vertex dual $T$ of the unique $44$-vertex non-Hamiltonian cyclically $5$-connected cubic planar graph (Holton--McKay Fig.~2.10), drawn crossing-free and coloured by the fixed parity labelling (blue even, orange odd). $T$ is $5$-connected and not an intertwining tree, yet is a bridge-derived level graph: the two solid green edges $\{6,19\}$ and $\{20,22\}$ are the bridge edges introduced by the two bridge switches carrying its witness Even Level Graph (source $19$) to $T$. Each green edge is a bridge of its parity subgraph -- $\{6, 19\}$ in the even subgraph, $\{20,22\}$ in the odd -- so no new cycle, and in particular no odd cycle, is created}{figure.6}{}}
|
|
||||||
\@writefile{toc}{\contentsline {subsection}{\tocsubsection {}{}{Beyond $n = 24$: enumeration and the next $5$-connected core}}{10}{section*.4}\protected@file@percent }
|
\@writefile{toc}{\contentsline {subsection}{\tocsubsection {}{}{Beyond $n = 24$: enumeration and the next $5$-connected core}}{10}{section*.4}\protected@file@percent }
|
||||||
\@writefile{toc}{\contentsline {subsection}{\tocsubsection {}{}{Toward a characterization of bridge-derived graphs}}{11}{section*.5}\protected@file@percent }
|
\@writefile{lof}{\contentsline {figure}{\numberline {6}{\ignorespaces The $24$-vertex dual $T$ of the unique $44$-vertex non-Hamiltonian cyclically $5$-connected cubic planar graph (Holton--McKay Fig.\nonbreakingspace 2.10), drawn crossing-free and coloured by the fixed parity labelling (blue even, orange odd). $T$ is $5$-connected and not an intertwining tree, yet is a bridge-derived level graph: the two solid green edges $\{6,19\}$ and $\{20,22\}$ are the bridge edges introduced by the two bridge switches carrying its witness Even Level Graph (source $19$) to $T$. Each green edge is a bridge of its parity subgraph -- $\{6, 19\}$ in the even subgraph, $\{20,22\}$ in the odd -- so no new cycle, and in particular no odd cycle, is created.}}{11}{figure.6}\protected@file@percent }
|
||||||
|
\newlabel{fig:n24-dual}{{6}{11}{The $24$-vertex dual $T$ of the unique $44$-vertex non-Hamiltonian cyclically $5$-connected cubic planar graph (Holton--McKay Fig.~2.10), drawn crossing-free and coloured by the fixed parity labelling (blue even, orange odd). $T$ is $5$-connected and not an intertwining tree, yet is a bridge-derived level graph: the two solid green edges $\{6,19\}$ and $\{20,22\}$ are the bridge edges introduced by the two bridge switches carrying its witness Even Level Graph (source $19$) to $T$. Each green edge is a bridge of its parity subgraph -- $\{6, 19\}$ in the even subgraph, $\{20,22\}$ in the odd -- so no new cycle, and in particular no odd cycle, is created}{figure.6}{}}
|
||||||
\@writefile{lof}{\contentsline {figure}{\numberline {7}{\ignorespaces The $25$-vertex dual $T_{25}$ of the unique $46$-vertex non-Hamiltonian cyclically $5$-connected cubic planar graph -- the only such cubic graph at $46$ vertices and the second internally $6$-connected core known. Drawn crossing-free and coloured by parity (blue even, orange odd) for its witness partition. $T_{25}$ is internally $6$-connected and not an intertwining tree, yet is a bridge-derived level graph: the two solid green edges $\{1,6\}$ and $\{22,24\}$ are the bridge edges introduced by the two bridge switches carrying its witness Even Level Graph (source $24$) to $T_{25}$. Each is a bridge of the even parity subgraph.}}{12}{figure.7}\protected@file@percent }
|
\@writefile{lof}{\contentsline {figure}{\numberline {7}{\ignorespaces The $25$-vertex dual $T_{25}$ of the unique $46$-vertex non-Hamiltonian cyclically $5$-connected cubic planar graph -- the only such cubic graph at $46$ vertices and the second internally $6$-connected core known. Drawn crossing-free and coloured by parity (blue even, orange odd) for its witness partition. $T_{25}$ is internally $6$-connected and not an intertwining tree, yet is a bridge-derived level graph: the two solid green edges $\{1,6\}$ and $\{22,24\}$ are the bridge edges introduced by the two bridge switches carrying its witness Even Level Graph (source $24$) to $T_{25}$. Each is a bridge of the even parity subgraph.}}{12}{figure.7}\protected@file@percent }
|
||||||
\newlabel{fig:n25-dual}{{7}{12}{The $25$-vertex dual $T_{25}$ of the unique $46$-vertex non-Hamiltonian cyclically $5$-connected cubic planar graph -- the only such cubic graph at $46$ vertices and the second internally $6$-connected core known. Drawn crossing-free and coloured by parity (blue even, orange odd) for its witness partition. $T_{25}$ is internally $6$-connected and not an intertwining tree, yet is a bridge-derived level graph: the two solid green edges $\{1,6\}$ and $\{22,24\}$ are the bridge edges introduced by the two bridge switches carrying its witness Even Level Graph (source $24$) to $T_{25}$. Each is a bridge of the even parity subgraph}{figure.7}{}}
|
\newlabel{fig:n25-dual}{{7}{12}{The $25$-vertex dual $T_{25}$ of the unique $46$-vertex non-Hamiltonian cyclically $5$-connected cubic planar graph -- the only such cubic graph at $46$ vertices and the second internally $6$-connected core known. Drawn crossing-free and coloured by parity (blue even, orange odd) for its witness partition. $T_{25}$ is internally $6$-connected and not an intertwining tree, yet is a bridge-derived level graph: the two solid green edges $\{1,6\}$ and $\{22,24\}$ are the bridge edges introduced by the two bridge switches carrying its witness Even Level Graph (source $24$) to $T_{25}$. Each is a bridge of the even parity subgraph}{figure.7}{}}
|
||||||
|
\@writefile{toc}{\contentsline {subsection}{\tocsubsection {}{}{Toward a characterization of bridge-derived graphs}}{12}{section*.5}\protected@file@percent }
|
||||||
\bibcite{holton-mckay}{1}
|
\bibcite{holton-mckay}{1}
|
||||||
\newlabel{tocindent-1}{0pt}
|
\newlabel{tocindent-1}{0pt}
|
||||||
\newlabel{tocindent0}{14.69437pt}
|
\newlabel{tocindent0}{14.69437pt}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
# Fdb version 3
|
# Fdb version 3
|
||||||
["pdflatex"] 1779469001 "/Users/didericis/Code/math-research/papers/even_level_graph_generators/paper.tex" "paper.pdf" "paper" 1779469003
|
["pdflatex"] 1781848805 "paper.tex" "paper.pdf" "paper" 1781848808
|
||||||
"/Users/didericis/Code/math-research/papers/even_level_graph_generators/paper.tex" 1779468999 23834 39061385c4cc2522155026d2f8574bbd ""
|
|
||||||
"/usr/local/texlive/2022/texmf-dist/fonts/map/fontname/texfonts.map" 1577235249 3524 cb3e574dea2d1052e39280babc910dc8 ""
|
"/usr/local/texlive/2022/texmf-dist/fonts/map/fontname/texfonts.map" 1577235249 3524 cb3e574dea2d1052e39280babc910dc8 ""
|
||||||
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/amsfonts/cmextra/cmex7.tfm" 1246382020 1004 54797486969f23fa377b128694d548df ""
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/amsfonts/cmextra/cmex7.tfm" 1246382020 1004 54797486969f23fa377b128694d548df ""
|
||||||
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/amsfonts/cmextra/cmex8.tfm" 1246382020 988 bdf658c3bfc2d96d3c8b02cfc1c94c20 ""
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/amsfonts/cmextra/cmex8.tfm" 1246382020 988 bdf658c3bfc2d96d3c8b02cfc1c94c20 ""
|
||||||
@@ -20,6 +19,7 @@
|
|||||||
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/cm/cmsy8.tfm" 1136768653 1120 8b7d695260f3cff42e636090a8002094 ""
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/cm/cmsy8.tfm" 1136768653 1120 8b7d695260f3cff42e636090a8002094 ""
|
||||||
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/cm/cmti10.tfm" 1136768653 1480 aa8e34af0eb6a2941b776984cf1dfdc4 ""
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/cm/cmti10.tfm" 1136768653 1480 aa8e34af0eb6a2941b776984cf1dfdc4 ""
|
||||||
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/cm/cmti8.tfm" 1136768653 1504 1747189e0441d1c18f3ea56fafc1c480 ""
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/cm/cmti8.tfm" 1136768653 1504 1747189e0441d1c18f3ea56fafc1c480 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/cm/cmtt10.tfm" 1136768653 768 1321e9409b4137d6fb428ac9dc956269 ""
|
||||||
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmbx10.pfb" 1248133631 34811 78b52f49e893bcba91bd7581cdc144c0 ""
|
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmbx10.pfb" 1248133631 34811 78b52f49e893bcba91bd7581cdc144c0 ""
|
||||||
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmcsc10.pfb" 1248133631 32001 6aeea3afe875097b1eb0da29acd61e28 ""
|
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmcsc10.pfb" 1248133631 32001 6aeea3afe875097b1eb0da29acd61e28 ""
|
||||||
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmex10.pfb" 1248133631 30251 6afa5cb1d0204815a708a080681d4674 ""
|
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmex10.pfb" 1248133631 30251 6afa5cb1d0204815a708a080681d4674 ""
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy7.pfb" 1248133631 32716 08e384dc442464e7285e891af9f45947 ""
|
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy7.pfb" 1248133631 32716 08e384dc442464e7285e891af9f45947 ""
|
||||||
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti10.pfb" 1248133631 37944 359e864bd06cde3b1cf57bb20757fb06 ""
|
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti10.pfb" 1248133631 37944 359e864bd06cde3b1cf57bb20757fb06 ""
|
||||||
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti8.pfb" 1248133631 35660 fb24af7afbadb71801619f1415838111 ""
|
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti8.pfb" 1248133631 35660 fb24af7afbadb71801619f1415838111 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmtt10.pfb" 1248133631 31099 c85edf1dd5b9e826d67c9c7293b6786c ""
|
||||||
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/symbols/msam10.pfb" 1248133631 31764 459c573c03a4949a528c2cc7f557e217 ""
|
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/symbols/msam10.pfb" 1248133631 31764 459c573c03a4949a528c2cc7f557e217 ""
|
||||||
"/usr/local/texlive/2022/texmf-dist/tex/context/base/mkii/supp-pdf.mkii" 1461363279 71627 94eb9990bed73c364d7f53f960cc8c5b ""
|
"/usr/local/texlive/2022/texmf-dist/tex/context/base/mkii/supp-pdf.mkii" 1461363279 71627 94eb9990bed73c364d7f53f960cc8c5b ""
|
||||||
"/usr/local/texlive/2022/texmf-dist/tex/generic/atbegshi/atbegshi.sty" 1575674566 24708 5584a51a7101caf7e6bbf1fc27d8f7b1 ""
|
"/usr/local/texlive/2022/texmf-dist/tex/generic/atbegshi/atbegshi.sty" 1575674566 24708 5584a51a7101caf7e6bbf1fc27d8f7b1 ""
|
||||||
@@ -91,10 +92,12 @@
|
|||||||
"fig_level_cycle.png" 1779389598 83736 ee54074ab1383a0dcc7fc20387e34bdc ""
|
"fig_level_cycle.png" 1779389598 83736 ee54074ab1383a0dcc7fc20387e34bdc ""
|
||||||
"fig_levels.png" 1779389598 88029 5564f46c0a183f3777727b651e7dc461 ""
|
"fig_levels.png" 1779389598 88029 5564f46c0a183f3777727b651e7dc461 ""
|
||||||
"fig_parity_subgraph.png" 1779389598 191771 f069aa94c8f49b3c7fd9c71426feff2d ""
|
"fig_parity_subgraph.png" 1779389598 191771 f069aa94c8f49b3c7fd9c71426feff2d ""
|
||||||
|
"figures/core_n25_dual.png" 1779491939 167150 1ff2a9ce9f23b303c20e8a8910b41205 ""
|
||||||
|
"figures/fig210_dual.png" 1779469439 152438 ac3c4fe29042435cab15ea90ee80b805 ""
|
||||||
"figures/n21_duals.png" 1779463364 667947 fd52170c20399b0c2dff901831fad5d5 ""
|
"figures/n21_duals.png" 1779463364 667947 fd52170c20399b0c2dff901831fad5d5 ""
|
||||||
"paper.aux" 1779469003 8486 a43934b41579f5535915f5341c4d1db7 "pdflatex"
|
"paper.aux" 1781848808 13255 0b6591b567d7fefa0f2a1ac1716e57fc "pdflatex"
|
||||||
"paper.out" 1779469003 1088 cf07a31709ba02be3ba2bc89322768d0 "pdflatex"
|
"paper.out" 1781848808 2030 d310c1d6d9f73494fc676a3dd19e31e8 "pdflatex"
|
||||||
"paper.tex" 1779468999 23834 39061385c4cc2522155026d2f8574bbd ""
|
"paper.tex" 1781848188 38745 a8bea15e6bfeb354af5c8a7ce030fc53 ""
|
||||||
(generated)
|
(generated)
|
||||||
"paper.aux"
|
"paper.aux"
|
||||||
"paper.log"
|
"paper.log"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ PWD /Users/didericis/Code/math-research/papers/even_level_graph_generators
|
|||||||
INPUT /usr/local/texlive/2022/texmf.cnf
|
INPUT /usr/local/texlive/2022/texmf.cnf
|
||||||
INPUT /usr/local/texlive/2022/texmf-dist/web2c/texmf.cnf
|
INPUT /usr/local/texlive/2022/texmf-dist/web2c/texmf.cnf
|
||||||
INPUT /usr/local/texlive/2022/texmf-var/web2c/pdftex/pdflatex.fmt
|
INPUT /usr/local/texlive/2022/texmf-var/web2c/pdftex/pdflatex.fmt
|
||||||
INPUT /Users/didericis/Code/math-research/papers/even_level_graph_generators/paper.tex
|
INPUT paper.tex
|
||||||
OUTPUT paper.log
|
OUTPUT paper.log
|
||||||
INPUT /usr/local/texlive/2022/texmf-dist/tex/latex/amscls/amsart.cls
|
INPUT /usr/local/texlive/2022/texmf-dist/tex/latex/amscls/amsart.cls
|
||||||
INPUT /usr/local/texlive/2022/texmf-dist/tex/latex/amscls/amsart.cls
|
INPUT /usr/local/texlive/2022/texmf-dist/tex/latex/amscls/amsart.cls
|
||||||
@@ -571,6 +571,17 @@ INPUT ./figures/n21_duals.png
|
|||||||
INPUT figures/n21_duals.png
|
INPUT figures/n21_duals.png
|
||||||
INPUT ./figures/n21_duals.png
|
INPUT ./figures/n21_duals.png
|
||||||
INPUT ./figures/n21_duals.png
|
INPUT ./figures/n21_duals.png
|
||||||
|
INPUT /usr/local/texlive/2022/texmf-dist/fonts/tfm/public/cm/cmtt10.tfm
|
||||||
|
INPUT ./figures/fig210_dual.png
|
||||||
|
INPUT ./figures/fig210_dual.png
|
||||||
|
INPUT figures/fig210_dual.png
|
||||||
|
INPUT ./figures/fig210_dual.png
|
||||||
|
INPUT ./figures/fig210_dual.png
|
||||||
|
INPUT ./figures/core_n25_dual.png
|
||||||
|
INPUT ./figures/core_n25_dual.png
|
||||||
|
INPUT figures/core_n25_dual.png
|
||||||
|
INPUT ./figures/core_n25_dual.png
|
||||||
|
INPUT ./figures/core_n25_dual.png
|
||||||
INPUT paper.aux
|
INPUT paper.aux
|
||||||
INPUT ./paper.out
|
INPUT ./paper.out
|
||||||
INPUT ./paper.out
|
INPUT ./paper.out
|
||||||
@@ -587,4 +598,5 @@ INPUT /usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy10.p
|
|||||||
INPUT /usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy7.pfb
|
INPUT /usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy7.pfb
|
||||||
INPUT /usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti10.pfb
|
INPUT /usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti10.pfb
|
||||||
INPUT /usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti8.pfb
|
INPUT /usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti8.pfb
|
||||||
|
INPUT /usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmtt10.pfb
|
||||||
INPUT /usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/symbols/msam10.pfb
|
INPUT /usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/symbols/msam10.pfb
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 22 MAY 2026 20:05
|
This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 19 JUN 2026 02:00
|
||||||
entering extended mode
|
entering extended mode
|
||||||
restricted \write18 enabled.
|
restricted \write18 enabled.
|
||||||
%&-line parsing enabled.
|
%&-line parsing enabled.
|
||||||
@@ -409,86 +409,88 @@ Underfull \hbox (badness 1112) in paragraph at lines 391--391
|
|||||||
the automorphism-free count
|
the automorphism-free count
|
||||||
[]
|
[]
|
||||||
|
|
||||||
[6]
|
[6] [7]
|
||||||
|
|
||||||
Package hyperref Warning: Token not allowed in a PDF string (Unicode):
|
Package hyperref Warning: Token not allowed in a PDF string (Unicode):
|
||||||
(hyperref) removing `math shift' on input line 497.
|
(hyperref) removing `math shift' on input line 536.
|
||||||
|
|
||||||
|
|
||||||
Package hyperref Warning: Token not allowed in a PDF string (Unicode):
|
Package hyperref Warning: Token not allowed in a PDF string (Unicode):
|
||||||
(hyperref) removing `math shift' on input line 497.
|
(hyperref) removing `math shift' on input line 536.
|
||||||
|
|
||||||
[7]
|
[8]
|
||||||
<figures/n21_duals.png, id=137, 1373.13pt x 867.24pt>
|
<figures/n21_duals.png, id=143, 1373.13pt x 867.24pt>
|
||||||
File: figures/n21_duals.png Graphic file (type png)
|
File: figures/n21_duals.png Graphic file (type png)
|
||||||
<use figures/n21_duals.png>
|
<use figures/n21_duals.png>
|
||||||
Package pdftex.def Info: figures/n21_duals.png used on input line 557.
|
Package pdftex.def Info: figures/n21_duals.png used on input line 596.
|
||||||
(pdftex.def) Requested size: 360.0pt x 227.35617pt.
|
(pdftex.def) Requested size: 360.0pt x 227.35617pt.
|
||||||
|
|
||||||
|
|
||||||
Package hyperref Warning: Token not allowed in a PDF string (Unicode):
|
Package hyperref Warning: Token not allowed in a PDF string (Unicode):
|
||||||
(hyperref) removing `math shift' on input line 570.
|
(hyperref) removing `math shift' on input line 609.
|
||||||
|
|
||||||
|
|
||||||
Package hyperref Warning: Token not allowed in a PDF string (Unicode):
|
Package hyperref Warning: Token not allowed in a PDF string (Unicode):
|
||||||
(hyperref) removing `math shift' on input line 570.
|
(hyperref) removing `math shift' on input line 609.
|
||||||
|
|
||||||
|
|
||||||
Package hyperref Warning: Token not allowed in a PDF string (Unicode):
|
Package hyperref Warning: Token not allowed in a PDF string (Unicode):
|
||||||
(hyperref) removing `math shift' on input line 570.
|
(hyperref) removing `math shift' on input line 609.
|
||||||
|
|
||||||
|
|
||||||
Package hyperref Warning: Token not allowed in a PDF string (Unicode):
|
Package hyperref Warning: Token not allowed in a PDF string (Unicode):
|
||||||
(hyperref) removing `math shift' on input line 570.
|
(hyperref) removing `math shift' on input line 609.
|
||||||
|
|
||||||
[8]
|
[9 <./figures/n21_duals.png>]
|
||||||
<figures/fig210_dual.png, id=144, 542.025pt x 542.025pt>
|
<figures/fig210_dual.png, id=152, 542.025pt x 542.025pt>
|
||||||
File: figures/fig210_dual.png Graphic file (type png)
|
File: figures/fig210_dual.png Graphic file (type png)
|
||||||
<use figures/fig210_dual.png>
|
<use figures/fig210_dual.png>
|
||||||
Package pdftex.def Info: figures/fig210_dual.png used on input line 619.
|
Package pdftex.def Info: figures/fig210_dual.png used on input line 658.
|
||||||
(pdftex.def) Requested size: 251.9989pt x 251.99767pt.
|
(pdftex.def) Requested size: 251.9989pt x 251.99767pt.
|
||||||
[9 <./figures/n21_duals.png>]
|
|
||||||
|
|
||||||
Package hyperref Warning: Token not allowed in a PDF string (Unicode):
|
|
||||||
(hyperref) removing `math shift' on input line 635.
|
|
||||||
|
|
||||||
|
|
||||||
Package hyperref Warning: Token not allowed in a PDF string (Unicode):
|
Package hyperref Warning: Token not allowed in a PDF string (Unicode):
|
||||||
(hyperref) removing `math shift' on input line 635.
|
(hyperref) removing `math shift' on input line 674.
|
||||||
|
|
||||||
|
|
||||||
Package hyperref Warning: Token not allowed in a PDF string (Unicode):
|
Package hyperref Warning: Token not allowed in a PDF string (Unicode):
|
||||||
(hyperref) removing `math shift' on input line 635.
|
(hyperref) removing `math shift' on input line 674.
|
||||||
|
|
||||||
|
|
||||||
Package hyperref Warning: Token not allowed in a PDF string (Unicode):
|
Package hyperref Warning: Token not allowed in a PDF string (Unicode):
|
||||||
(hyperref) removing `math shift' on input line 635.
|
(hyperref) removing `math shift' on input line 674.
|
||||||
|
|
||||||
|
|
||||||
Overfull \hbox (9.14177pt too wide) in paragraph at lines 648--656
|
Package hyperref Warning: Token not allowed in a PDF string (Unicode):
|
||||||
|
(hyperref) removing `math shift' on input line 674.
|
||||||
|
|
||||||
|
|
||||||
|
Overfull \hbox (9.14177pt too wide) in paragraph at lines 687--695
|
||||||
\OT1/cmr/m/n/10 The $\OML/cmm/m/it/10 n \OT1/cmr/m/n/10 = 23$ row re-com-putes
|
\OT1/cmr/m/n/10 The $\OML/cmm/m/it/10 n \OT1/cmr/m/n/10 = 23$ row re-com-putes
|
||||||
Faulkner--Younger's min-i-mal-ity (no cycli-cally $5$-connected
|
Faulkner--Younger's min-i-mal-ity (no cycli-cally $5$-connected
|
||||||
[]
|
[]
|
||||||
|
|
||||||
[10 <./figures/fig210_dual.png>]
|
[10]
|
||||||
<figures/core_n25_dual.png, id=160, 578.16pt x 578.16pt>
|
Underfull \vbox (badness 1831) has occurred while \output is active []
|
||||||
|
|
||||||
|
[11 <./figures/fig210_dual.png>]
|
||||||
|
<figures/core_n25_dual.png, id=166, 578.16pt x 578.16pt>
|
||||||
File: figures/core_n25_dual.png Graphic file (type png)
|
File: figures/core_n25_dual.png Graphic file (type png)
|
||||||
<use figures/core_n25_dual.png>
|
<use figures/core_n25_dual.png>
|
||||||
Package pdftex.def Info: figures/core_n25_dual.png used on input line 693.
|
Package pdftex.def Info: figures/core_n25_dual.png used on input line 732.
|
||||||
(pdftex.def) Requested size: 251.9989pt x 251.9916pt.
|
(pdftex.def) Requested size: 251.9989pt x 251.9916pt.
|
||||||
[11] [12 <./figures/core_n25_dual.png>]
|
[12 <./figures/core_n25_dual.png>] [13] (./paper.aux)
|
||||||
[13] (./paper.aux)
|
|
||||||
Package rerunfilecheck Info: File `paper.out' has not changed.
|
Package rerunfilecheck Info: File `paper.out' has not changed.
|
||||||
(rerunfilecheck) Checksum: D310C1D6D9F73494FC676A3DD19E31E8;2030.
|
(rerunfilecheck) Checksum: D310C1D6D9F73494FC676A3DD19E31E8;2030.
|
||||||
)
|
)
|
||||||
Here is how much of TeX's memory you used:
|
Here is how much of TeX's memory you used:
|
||||||
9791 strings out of 478268
|
9793 strings out of 478268
|
||||||
151651 string characters out of 5846347
|
151677 string characters out of 5846347
|
||||||
455389 words of memory out of 5000000
|
455969 words of memory out of 5000000
|
||||||
27676 multiletter control sequences out of 15000+600000
|
27677 multiletter control sequences out of 15000+600000
|
||||||
475834 words of font info for 54 fonts, out of 8000000 for 9000
|
475834 words of font info for 54 fonts, out of 8000000 for 9000
|
||||||
1302 hyphenation exceptions out of 8191
|
1302 hyphenation exceptions out of 8191
|
||||||
69i,9n,76p,822b,450s stack positions out of 10000i,1000n,20000p,200000b,200000s
|
69i,9n,76p,822b,421s stack positions out of 10000i,1000n,20000p,200000b,200000s
|
||||||
</usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmbx10.pfb
|
</usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmbx10.pfb
|
||||||
></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmcsc10.pfb
|
></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmcsc10.pfb
|
||||||
></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmex10.pfb>
|
></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmex10.pfb>
|
||||||
@@ -504,10 +506,10 @@ ive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti10.pfb></usr/local/texli
|
|||||||
ve/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti8.pfb></usr/local/texlive
|
ve/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti8.pfb></usr/local/texlive
|
||||||
/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmtt10.pfb></usr/local/texlive/
|
/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmtt10.pfb></usr/local/texlive/
|
||||||
2022/texmf-dist/fonts/type1/public/amsfonts/symbols/msam10.pfb>
|
2022/texmf-dist/fonts/type1/public/amsfonts/symbols/msam10.pfb>
|
||||||
Output written on paper.pdf (13 pages, 1384388 bytes).
|
Output written on paper.pdf (13 pages, 1386477 bytes).
|
||||||
PDF statistics:
|
PDF statistics:
|
||||||
253 PDF objects out of 1000 (max. 8388607)
|
256 PDF objects out of 1000 (max. 8388607)
|
||||||
192 compressed objects within 2 object streams
|
195 compressed objects within 2 object streams
|
||||||
48 named destinations out of 1000 (max. 500000)
|
49 named destinations out of 1000 (max. 500000)
|
||||||
116 words of extra memory for PDF output out of 10000 (max. 10000000)
|
116 words of extra memory for PDF output out of 10000 (max. 10000000)
|
||||||
|
|
||||||
|
|||||||
@@ -492,6 +492,45 @@ $n = 21$ and there are exactly $6$ of them. Below $n = 21$ every
|
|||||||
maximal planar graph is an intertwining tree, which is why the
|
maximal planar graph is an intertwining tree, which is why the
|
||||||
disjunction holds trivially in that range.
|
disjunction holds trivially in that range.
|
||||||
|
|
||||||
|
The intertwining-tree disjunct therefore carries the conjecture by itself
|
||||||
|
for all small $n$, but this leaves open how much of the load the
|
||||||
|
bridge-derived disjunct is independently able to bear. To measure that we
|
||||||
|
classified every triangulation at $6 \leq n \leq 10$ as bridge-derived or
|
||||||
|
not, deciding bridge-derivedness exhaustively: a triangulation is
|
||||||
|
bridge-derived iff some valid parity partition admits an Even Level Graph
|
||||||
|
in its backward bridge-switch orbit (a search feasible only at these
|
||||||
|
sizes). Table~\ref{tab:bridge-census} records the result. Three features
|
||||||
|
stand out. First, the bridge-derived disjunct is substantive but far from
|
||||||
|
universal on its own: its share of all triangulations falls steadily, from
|
||||||
|
all of them at $n = 6$ to under two-thirds by $n = 10$. Second, the
|
||||||
|
disjunction never relies on it in this range -- the \emph{intertwining
|
||||||
|
only} column counts triangulations covered by the tree disjunct alone, and
|
||||||
|
it grows, while no triangulation here is bridge-derived without also being
|
||||||
|
an intertwining tree. Third, and consistent with the conjecture, the
|
||||||
|
\emph{neither} column is identically zero throughout.
|
||||||
|
|
||||||
|
\begin{table}[ht]
|
||||||
|
\centering
|
||||||
|
\begin{tabular}{cccccc}
|
||||||
|
$n$ & triangulations & bridge-derived & \% & intertwining only & neither \\\hline
|
||||||
|
$6$ & $2$ & $2$ & $100.0$ & $0$ & $0$ \\
|
||||||
|
$7$ & $5$ & $4$ & $80.0$ & $1$ & $0$ \\
|
||||||
|
$8$ & $14$ & $12$ & $85.7$ & $2$ & $0$ \\
|
||||||
|
$9$ & $50$ & $36$ & $72.0$ & $14$ & $0$ \\
|
||||||
|
$10$ & $233$ & $146$ & $62.7$ & $87$ & $0$ \\
|
||||||
|
\end{tabular}
|
||||||
|
\caption{Bridge-derived census for $6 \leq n \leq 10$. \emph{bridge-derived}
|
||||||
|
counts plane-triangulation iso classes that are bridge-derived level graphs
|
||||||
|
of some Even Level Graph, decided by exhaustive backward bridge-switch
|
||||||
|
search over all valid parity partitions; \% is its fraction of all
|
||||||
|
triangulations. \emph{intertwining only} counts those that are intertwining
|
||||||
|
trees but not bridge-derived; \emph{neither} counts those covered by no
|
||||||
|
disjunct. Every triangulation in this range is an intertwining tree, and
|
||||||
|
every bridge-derived one is too, so bridge-derived $\subseteq$ intertwining
|
||||||
|
tree here.}
|
||||||
|
\label{tab:bridge-census}
|
||||||
|
\end{table}
|
||||||
|
|
||||||
\subsection*{The boundary case $n = 21$}
|
\subsection*{The boundary case $n = 21$}
|
||||||
|
|
||||||
The first triangulations that are \emph{not} intertwining trees are the
|
The first triangulations that are \emph{not} intertwining trees are the
|
||||||
|
|||||||
@@ -0,0 +1,182 @@
|
|||||||
|
"""
|
||||||
|
Does the richness invariant survive BRANCHING?
|
||||||
|
|
||||||
|
For a separating cycle C bounding a disk G_C (away from the source), the achievable
|
||||||
|
outer-interface set is
|
||||||
|
|
||||||
|
Phi(C) = { (lambda*(v))_{v in C} : lambda in {+1,-1}^{F(G_C)},
|
||||||
|
sum_{f ∋ w} lambda(f) ≡ 0 for every
|
||||||
|
truly-interior vertex w of G_C }.
|
||||||
|
|
||||||
|
This is the exact value the recursive transfer operator produces at C (interior
|
||||||
|
consistency = all the descendant gluings already performed; seam/boundary vertices
|
||||||
|
are deferred, exactly as in the recursion). We compute Phi(C) by constrained
|
||||||
|
enumeration over real triangulations and test the candidate self-similar invariant
|
||||||
|
|
||||||
|
non-empty & closed under sign flip & full single-position marginals
|
||||||
|
|
||||||
|
separately at BRANCH nodes (region encloses >=2 disjoint deeper sub-tires) and at
|
||||||
|
LINEAR nodes (one child), to see whether branching breaks it.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from collections import defaultdict, deque
|
||||||
|
from itertools import product
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from scipy.spatial import Delaunay
|
||||||
|
|
||||||
|
|
||||||
|
def delaunay(n, rng):
|
||||||
|
pts = rng.random((n, 2))
|
||||||
|
tri = Delaunay(pts)
|
||||||
|
faces = [tuple(int(x) for x in s) for s in tri.simplices]
|
||||||
|
hull = set(int(v) for e in tri.convex_hull for v in e)
|
||||||
|
return faces, hull
|
||||||
|
|
||||||
|
|
||||||
|
def build(faces):
|
||||||
|
adj = defaultdict(set)
|
||||||
|
efaces = defaultdict(list)
|
||||||
|
vfaces = defaultdict(list)
|
||||||
|
for fi, (a, b, c) in enumerate(faces):
|
||||||
|
adj[a] |= {b, c}; adj[b] |= {a, c}; adj[c] |= {a, b}
|
||||||
|
for e in ((a, b), (b, c), (a, c)):
|
||||||
|
efaces[frozenset(e)].append(fi)
|
||||||
|
for v in (a, b, c):
|
||||||
|
vfaces[v].append(fi)
|
||||||
|
fadj = [set() for _ in faces]
|
||||||
|
for fl in efaces.values():
|
||||||
|
for i in fl:
|
||||||
|
for j in fl:
|
||||||
|
if i != j:
|
||||||
|
fadj[i].add(j)
|
||||||
|
return adj, fadj, vfaces
|
||||||
|
|
||||||
|
|
||||||
|
def bfs(adj, src):
|
||||||
|
lev = {src: 0}; q = deque([src])
|
||||||
|
while q:
|
||||||
|
u = q.popleft()
|
||||||
|
for w in adj[u]:
|
||||||
|
if w not in lev:
|
||||||
|
lev[w] = lev[u] + 1; q.append(w)
|
||||||
|
return lev
|
||||||
|
|
||||||
|
|
||||||
|
def components(face_ids, fadj):
|
||||||
|
idset = set(face_ids)
|
||||||
|
seen = set(); comps = []
|
||||||
|
for s in face_ids:
|
||||||
|
if s in seen:
|
||||||
|
continue
|
||||||
|
comp = []; stack = [s]; seen.add(s)
|
||||||
|
while stack:
|
||||||
|
u = stack.pop(); comp.append(u)
|
||||||
|
for w in fadj[u]:
|
||||||
|
if w in idset and w not in seen:
|
||||||
|
seen.add(w); stack.append(w)
|
||||||
|
comps.append(comp)
|
||||||
|
return comps
|
||||||
|
|
||||||
|
|
||||||
|
def sign_closed(S):
|
||||||
|
return all(tuple((3 - x) % 3 for x in s) in S for s in S)
|
||||||
|
|
||||||
|
|
||||||
|
def marginals_full(S, k):
|
||||||
|
return all({s[i] for s in S} == {0, 1, 2} for i in range(k))
|
||||||
|
|
||||||
|
|
||||||
|
def phi_of_region(comp_faces, faces, vfaces, lev, d, cap):
|
||||||
|
"""Constrained-enumeration Phi on the outer (level-d) cycle of a region."""
|
||||||
|
Gc = comp_faces
|
||||||
|
if not (1 <= len(Gc) <= cap):
|
||||||
|
return None
|
||||||
|
Gcset = set(Gc)
|
||||||
|
verts = sorted(set(v for fi in Gc for v in faces[fi]))
|
||||||
|
# truly-interior: every global incident face is inside G_C (=> level > d)
|
||||||
|
interior = [v for v in verts if all(f in Gcset for f in vfaces[v])]
|
||||||
|
boundary_C = [v for v in verts if lev[v] == d and v not in interior]
|
||||||
|
if not boundary_C:
|
||||||
|
return None
|
||||||
|
F = len(Gc)
|
||||||
|
fidx = {fi: j for j, fi in enumerate(Gc)}
|
||||||
|
# incidence rows
|
||||||
|
Bint = np.zeros((len(interior), F), dtype=np.int64)
|
||||||
|
for r, w in enumerate(interior):
|
||||||
|
for fi in vfaces[w]:
|
||||||
|
if fi in Gcset:
|
||||||
|
Bint[r, fidx[fi]] = 1
|
||||||
|
Cinc = np.zeros((len(boundary_C), F), dtype=np.int64)
|
||||||
|
for r, v in enumerate(boundary_C):
|
||||||
|
for fi in vfaces[v]:
|
||||||
|
if fi in Gcset:
|
||||||
|
Cinc[r, fidx[fi]] = 1
|
||||||
|
labs = np.array(list(product((1, 2), repeat=F)), dtype=np.int64)
|
||||||
|
if len(interior):
|
||||||
|
ok = np.all((labs @ Bint.T) % 3 == 0, axis=1)
|
||||||
|
labs = labs[ok]
|
||||||
|
if labs.shape[0] == 0:
|
||||||
|
return set(), len(boundary_C)
|
||||||
|
outer = (labs @ Cinc.T) % 3
|
||||||
|
return set(map(tuple, np.unique(outer, axis=0))), len(boundary_C)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
seed = int(sys.argv[1]) if len(sys.argv) > 1 else 0
|
||||||
|
nprng = np.random.default_rng(seed)
|
||||||
|
CAP = 18
|
||||||
|
|
||||||
|
stats = {True: [0, 0, 0, 0], False: [0, 0, 0, 0]} # branch: [n, nonempty, sign, marg]
|
||||||
|
examples_fail = []
|
||||||
|
|
||||||
|
for _ in range(300):
|
||||||
|
faces, hull = delaunay(int(nprng.integers(14, 34)), nprng)
|
||||||
|
adj, fadj, vfaces = build(faces)
|
||||||
|
lev = bfs(adj, min(hull))
|
||||||
|
if len(lev) != len(adj):
|
||||||
|
continue
|
||||||
|
depth = [min(lev[v] for v in faces[fi]) for fi in range(len(faces))]
|
||||||
|
maxd = max(depth)
|
||||||
|
for d in range(1, maxd + 1):
|
||||||
|
fge = [fi for fi in range(len(faces)) if depth[fi] >= d]
|
||||||
|
for comp in components(fge, fadj):
|
||||||
|
if not (1 <= len(comp) <= CAP):
|
||||||
|
continue
|
||||||
|
deeper = [fi for fi in comp if depth[fi] >= d + 1]
|
||||||
|
n_children = len(components(deeper, fadj))
|
||||||
|
is_branch = n_children >= 2
|
||||||
|
res = phi_of_region(comp, faces, vfaces, lev, d, CAP)
|
||||||
|
if res is None:
|
||||||
|
continue
|
||||||
|
S, k = res
|
||||||
|
rec = stats[is_branch]
|
||||||
|
rec[0] += 1
|
||||||
|
rec[1] += bool(S)
|
||||||
|
rec[2] += (bool(S) and sign_closed(S))
|
||||||
|
rec[3] += (bool(S) and marginals_full(S, k))
|
||||||
|
if S and not marginals_full(S, k) and len(examples_fail) < 6:
|
||||||
|
examples_fail.append((is_branch, n_children, len(comp), k,
|
||||||
|
len(S)))
|
||||||
|
|
||||||
|
for branch in (False, True):
|
||||||
|
n, ne, sg, mg = stats[branch]
|
||||||
|
tag = "BRANCH (>=2 children)" if branch else "LINEAR (1 child)"
|
||||||
|
if n:
|
||||||
|
print(f"{tag}: {n} regions")
|
||||||
|
print(f" non-empty : {ne}/{n} ({100*ne/n:.1f}%)")
|
||||||
|
print(f" sign-closed : {sg}/{n} ({100*sg/n:.1f}%)")
|
||||||
|
print(f" marginals-full : {mg}/{n} ({100*mg/n:.1f}%)")
|
||||||
|
else:
|
||||||
|
print(f"{tag}: 0 regions")
|
||||||
|
if examples_fail:
|
||||||
|
print("\n marginals-NOT-full examples (branch?,n_children,|G_C|,|C|,|Phi|):")
|
||||||
|
for e in examples_fail:
|
||||||
|
print(f" {e}")
|
||||||
|
else:
|
||||||
|
print("\n richness (incl. full marginals) held on every region tested.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
"""
|
||||||
|
Robustness check for the 2^(n-2) constraint floor over DIVERSE (not just stacked)
|
||||||
|
triangulated disks.
|
||||||
|
|
||||||
|
maximally_constrain.py searches Apollonian-stacked disks only, which miss
|
||||||
|
non-stacked triangulations (e.g. a wheel with a high-degree center). Here we
|
||||||
|
generate disks from random interior points + Delaunay, with boundary points in
|
||||||
|
convex but NON-cocircular position (cocircular boundary points are a Delaunay
|
||||||
|
degeneracy that yields INVALID disks -- missing a boundary edge -- and spuriously
|
||||||
|
report sub-floor |Phi|). Every disk is validated (2k+n-2 faces, all n boundary
|
||||||
|
edges present) before Phi is computed.
|
||||||
|
|
||||||
|
Finding: min |Phi| over validated diverse disks is exactly 2^(n-2), attained by
|
||||||
|
the interior-free triangulation; deeper structure never goes below it (and the
|
||||||
|
central-apex wheel actually ENLARGES Phi: 5 vs the fan's 4 on the 4-cycle).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from collections import Counter
|
||||||
|
from itertools import product
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from scipy.spatial import Delaunay
|
||||||
|
|
||||||
|
np.seterr(all="ignore")
|
||||||
|
|
||||||
|
|
||||||
|
def disk(n, k, rng):
|
||||||
|
ang = 2 * np.pi * np.arange(n) / n
|
||||||
|
rad = 1.0 + 0.15 * rng.random(n) # convex but not cocircular
|
||||||
|
bpts = np.c_[rad * np.cos(ang), rad * np.sin(ang)]
|
||||||
|
if k:
|
||||||
|
r = 0.75 * np.sqrt(rng.random(k)); t = 2 * np.pi * rng.random(k)
|
||||||
|
ipts = np.c_[r * np.cos(t), r * np.sin(t)]
|
||||||
|
pts = np.vstack([bpts, ipts])
|
||||||
|
else:
|
||||||
|
pts = bpts
|
||||||
|
tri = Delaunay(pts)
|
||||||
|
return [tuple(int(x) for x in s) for s in tri.simplices]
|
||||||
|
|
||||||
|
|
||||||
|
def valid(faces, n, k):
|
||||||
|
if len(faces) != 2 * k + n - 2:
|
||||||
|
return False
|
||||||
|
ec = Counter()
|
||||||
|
for a, b, c in faces:
|
||||||
|
for e in ((a, b), (b, c), (a, c)):
|
||||||
|
ec[frozenset(e)] += 1
|
||||||
|
return all(ec[frozenset((i, (i + 1) % n))] == 1 for i in range(n))
|
||||||
|
|
||||||
|
|
||||||
|
def phi(faces, n, k):
|
||||||
|
F = len(faces)
|
||||||
|
interior = list(range(n, n + k))
|
||||||
|
Bint = np.zeros((len(interior), F), dtype=np.int64)
|
||||||
|
Cinc = np.zeros((n, F), dtype=np.int64)
|
||||||
|
for j, (a, b, c) in enumerate(faces):
|
||||||
|
for v in (a, b, c):
|
||||||
|
if v >= n:
|
||||||
|
Bint[interior.index(v), j] = 1
|
||||||
|
else:
|
||||||
|
Cinc[v, j] = 1
|
||||||
|
labs = np.array(list(product((1, 2), repeat=F)), dtype=np.int64)
|
||||||
|
if interior:
|
||||||
|
labs = labs[np.all((labs @ Bint.T) % 3 == 0, axis=1)]
|
||||||
|
if labs.shape[0] == 0:
|
||||||
|
return set()
|
||||||
|
return set(map(tuple, np.unique((labs @ Cinc.T) % 3, axis=0)))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ns = [int(x) for x in sys.argv[1:]] or [4, 5, 6]
|
||||||
|
rng = np.random.default_rng(1)
|
||||||
|
print("Min |Phi| over validated diverse (Delaunay) disks\n")
|
||||||
|
for n in ns:
|
||||||
|
best = 10 ** 9; bk = None; nval = 0; max_seen = 0
|
||||||
|
for k in range(0, 7):
|
||||||
|
for _ in range(250):
|
||||||
|
faces = disk(n, k, rng)
|
||||||
|
if not valid(faces, n, k) or len(faces) > 20:
|
||||||
|
continue
|
||||||
|
nval += 1
|
||||||
|
P = phi(faces, n, k)
|
||||||
|
if P:
|
||||||
|
max_seen = max(max_seen, len(P))
|
||||||
|
if len(P) < best:
|
||||||
|
best = len(P); bk = k
|
||||||
|
print(f" n={n}: {nval} valid disks min|Phi|={best} (k={bk}) "
|
||||||
|
f"max|Phi|={max_seen} 2^(n-2)={2**(n-2)} "
|
||||||
|
f"below-floor={best < 2**(n-2)}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
"""
|
||||||
|
The open case for the 2^(n-2) lower bound is the IRREDUCIBLE disk: k>=1 interior
|
||||||
|
vertices, all of degree >=4 (un-stacking already settles everything reducible to
|
||||||
|
a degree-3 vertex). This probe isolates irreducible disks and asks:
|
||||||
|
|
||||||
|
(a) does the floor |Phi| >= 2^(n-2) hold there (it must, but it's the open case);
|
||||||
|
(b) is it STRICT (|Phi| > 2^(n-2)) -- if irreducible disks never sit ON the floor,
|
||||||
|
the proof only needs ">= floor" via "any nontrivial slack";
|
||||||
|
(c) how many UNIVERSAL toggles each has -- a "boundary-only" face (all 3 vertices
|
||||||
|
on C) can be flipped feasibly regardless of the labelling, giving a guaranteed
|
||||||
|
doubling. n-2 independent ones would prove the bound outright. We count them
|
||||||
|
to see whether universal toggles alone can carry the irreducible case (a wheel
|
||||||
|
has zero, so we expect NOT).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from collections import Counter
|
||||||
|
from itertools import product
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from scipy.spatial import Delaunay
|
||||||
|
|
||||||
|
np.seterr(all="ignore")
|
||||||
|
|
||||||
|
|
||||||
|
def disk(n, k, rng):
|
||||||
|
ang = 2 * np.pi * np.arange(n) / n
|
||||||
|
rad = 1.0 + 0.18 * rng.random(n)
|
||||||
|
bpts = np.c_[rad * np.cos(ang), rad * np.sin(ang)]
|
||||||
|
r = 0.78 * np.sqrt(rng.random(k)); t = 2 * np.pi * rng.random(k)
|
||||||
|
pts = np.vstack([bpts, np.c_[r * np.cos(t), r * np.sin(t)]])
|
||||||
|
return [tuple(int(x) for x in s) for s in Delaunay(pts).simplices]
|
||||||
|
|
||||||
|
|
||||||
|
def valid(faces, n, k):
|
||||||
|
if len(faces) != 2 * k + n - 2:
|
||||||
|
return False
|
||||||
|
ec = Counter()
|
||||||
|
for a, b, c in faces:
|
||||||
|
for e in ((a, b), (b, c), (a, c)):
|
||||||
|
ec[frozenset(e)] += 1
|
||||||
|
return all(ec[frozenset((i, (i + 1) % n))] == 1 for i in range(n))
|
||||||
|
|
||||||
|
|
||||||
|
def interior_degrees(faces, n):
|
||||||
|
deg = Counter()
|
||||||
|
for f in faces:
|
||||||
|
for v in f:
|
||||||
|
if v >= n:
|
||||||
|
deg[v] += 1
|
||||||
|
return deg
|
||||||
|
|
||||||
|
|
||||||
|
def phi(faces, n):
|
||||||
|
interior = sorted(v for f in faces for v in f if v >= n)
|
||||||
|
interior = sorted(set(interior))
|
||||||
|
F = len(faces)
|
||||||
|
if F > 18:
|
||||||
|
return None
|
||||||
|
Bint = np.zeros((len(interior), F), dtype=np.int64)
|
||||||
|
Cinc = np.zeros((n, F), dtype=np.int64)
|
||||||
|
iidx = {w: r for r, w in enumerate(interior)}
|
||||||
|
for j, (a, b, c) in enumerate(faces):
|
||||||
|
for v in (a, b, c):
|
||||||
|
if v >= n:
|
||||||
|
Bint[iidx[v], j] = 1
|
||||||
|
else:
|
||||||
|
Cinc[v, j] = 1
|
||||||
|
labs = np.array(list(product((1, 2), repeat=F)), dtype=np.int64)
|
||||||
|
if interior:
|
||||||
|
labs = labs[np.all((labs @ Bint.T) % 3 == 0, axis=1)]
|
||||||
|
if labs.shape[0] == 0:
|
||||||
|
return set()
|
||||||
|
return set(map(tuple, np.unique((labs @ Cinc.T) % 3, axis=0)))
|
||||||
|
|
||||||
|
|
||||||
|
def boundary_only_faces(faces, n):
|
||||||
|
return sum(1 for (a, b, c) in faces if a < n and b < n and c < n)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
seed = int(sys.argv[1]) if len(sys.argv) > 1 else 0
|
||||||
|
rng = np.random.default_rng(seed)
|
||||||
|
by_n = {}
|
||||||
|
n_irred = 0
|
||||||
|
floor_fail = 0
|
||||||
|
on_floor = 0 # irreducible AND exactly at 2^(n-2)
|
||||||
|
bof_ge = 0 # irreducible with >= n-2 boundary-only faces
|
||||||
|
bof_zero = 0
|
||||||
|
min_bof = 99
|
||||||
|
|
||||||
|
for _ in range(6000):
|
||||||
|
n = int(rng.integers(4, 7)); k = int(rng.integers(1, 4))
|
||||||
|
faces = disk(n, k, rng)
|
||||||
|
if not valid(faces, n, k) or len(faces) > 16:
|
||||||
|
continue
|
||||||
|
deg = interior_degrees(faces, n)
|
||||||
|
if any(deg[v] < 4 for v in deg) or len(deg) < k:
|
||||||
|
continue # reducible (has a degree-3 vertex)
|
||||||
|
P = phi(faces, n)
|
||||||
|
if P is None or not P:
|
||||||
|
continue
|
||||||
|
n_irred += 1
|
||||||
|
floor = 2 ** (n - 2)
|
||||||
|
if len(P) < floor:
|
||||||
|
floor_fail += 1
|
||||||
|
if len(P) == floor:
|
||||||
|
on_floor += 1
|
||||||
|
bof = boundary_only_faces(faces, n)
|
||||||
|
min_bof = min(min_bof, bof)
|
||||||
|
bof_ge += (bof >= n - 2)
|
||||||
|
bof_zero += (bof == 0)
|
||||||
|
d = by_n.setdefault(n, {"cnt": 0, "min": 10**9, "floor": floor})
|
||||||
|
d["cnt"] += 1
|
||||||
|
d["min"] = min(d["min"], len(P))
|
||||||
|
|
||||||
|
print(f"irreducible disks (k>=1, all interior degree >=4): {n_irred}\n")
|
||||||
|
for n in sorted(by_n):
|
||||||
|
d = by_n[n]
|
||||||
|
print(f" n={n}: {d['cnt']:5d} disks min|Phi|={d['min']} "
|
||||||
|
f"2^(n-2)={d['floor']} floor-held={d['min']>=d['floor']}")
|
||||||
|
print(f"\n floor violations (|Phi| < 2^(n-2)) : {floor_fail}")
|
||||||
|
print(f" irreducible disks sitting ON the floor: {on_floor} "
|
||||||
|
f"(if 0, irreducible => strictly above floor)")
|
||||||
|
print(f" universal toggles (boundary-only faces):")
|
||||||
|
print(f" >= n-2 such faces : {bof_ge}/{n_irred}")
|
||||||
|
print(f" exactly 0 : {bof_zero}/{n_irred} (min over all = {min_bof})")
|
||||||
|
print(" => universal toggles alone "
|
||||||
|
+ ("CAN" if bof_zero == 0 else "CANNOT")
|
||||||
|
+ " carry the irreducible case.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
"""
|
||||||
|
Local heart of the size-reduction lemma.
|
||||||
|
|
||||||
|
Removing a degree-d interior vertex v swaps its star (d faces, contribution
|
||||||
|
mu_{j-1}+mu_j at link vertex u_j, plus the v-constraint sum mu ≡ 0) for a fan
|
||||||
|
(d-2 faces). Outside is shared, entering only as prescribed residues t_j at the
|
||||||
|
INTERIOR link vertices. The boundary-link contribution sets are
|
||||||
|
|
||||||
|
Star(t) = {(mu_{j-1}+mu_j)_{u_j boundary} : mu in {+-1}^d, sum mu ≡ 0,
|
||||||
|
mu_{j-1}+mu_j ≡ t_j for interior u_j}
|
||||||
|
Fan_r(t)= {(fan_j)_{u_j boundary} : nu in {+-1}^{d-2},
|
||||||
|
fan_j ≡ t_j for interior u_j}
|
||||||
|
(fan rooted at link vertex r; fan_j = contribution of the fan to u_j)
|
||||||
|
|
||||||
|
The reduction may CHOOSE the fan root, so it wants min_r |Fan_r(t)|. For the
|
||||||
|
induction |Phi(D-v)| <= |Phi(D)| to be locally supported we need, for every
|
||||||
|
interior-mask and every t:
|
||||||
|
|
||||||
|
|Star(t)| >= min_r |Fan_r(t)| (and Star(t) nonempty whenever some Fan is)
|
||||||
|
|
||||||
|
We check this exhaustively for small d. (+-1 encoded as 1,2 mod 3.)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from itertools import product
|
||||||
|
|
||||||
|
|
||||||
|
def star_contrib(d, interior, t):
|
||||||
|
"""interior: set of link indices that are interior; t: dict j->residue for
|
||||||
|
interior j. Returns set of boundary contribution tuples."""
|
||||||
|
bnd = [j for j in range(d) if j not in interior]
|
||||||
|
out = set()
|
||||||
|
for mu in product((1, 2), repeat=d): # +-1 as 1,2
|
||||||
|
if sum(mu) % 3 != 0: # v-constraint
|
||||||
|
continue
|
||||||
|
contrib = [(mu[(j - 1) % d] + mu[j]) % 3 for j in range(d)]
|
||||||
|
if any(contrib[j] != t[j] for j in interior):
|
||||||
|
continue
|
||||||
|
out.add(tuple(contrib[j] for j in bnd))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def fan_contrib(d, root, interior, t):
|
||||||
|
"""Fan of the link d-gon rooted at vertex `root`. Reindex so root=0."""
|
||||||
|
bnd = [j for j in range(d) if j not in interior]
|
||||||
|
# contribution of a fan (root r) to vertex u_j, for nu over the d-2 fan faces.
|
||||||
|
# In root-0 coordinates faces are (0,j,j+1) for j=1..d-2 with labels nu[1..d-2].
|
||||||
|
# contribution: u0 -> sum(nu); u1 -> nu1; u_{d-1} -> nu_{d-2};
|
||||||
|
# u_j (1<j<d-1) -> nu_{j-1}+nu_j.
|
||||||
|
out = set()
|
||||||
|
for nu in product((1, 2), repeat=d - 2): # nu indexed 1..d-2 -> 0..d-3
|
||||||
|
nuf = {j: nu[j - 1] for j in range(1, d - 1)}
|
||||||
|
contrib = [0] * d
|
||||||
|
# in root coordinates u'_i = u_{(root+i) mod d}
|
||||||
|
c = [0] * d
|
||||||
|
c[0] = sum(nu) % 3
|
||||||
|
c[1] = nuf[1] % 3
|
||||||
|
c[d - 1] = nuf[d - 2] % 3
|
||||||
|
for j in range(2, d - 1):
|
||||||
|
c[j] = (nuf[j - 1] + nuf[j]) % 3
|
||||||
|
# map back: actual vertex = (root+i) mod d gets c[i]
|
||||||
|
for i in range(d):
|
||||||
|
contrib[(root + i) % d] = c[i]
|
||||||
|
if any(contrib[j] != t[j] for j in interior):
|
||||||
|
continue
|
||||||
|
out.add(tuple(contrib[j] for j in bnd))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("Local claim: |Star(t)| >= min_root |Fan_root(t)| for all interior-masks, t\n")
|
||||||
|
fails = 0
|
||||||
|
checked = 0
|
||||||
|
worst = None
|
||||||
|
for d in range(3, 8):
|
||||||
|
for im in range(0, 1 << d):
|
||||||
|
interior = {j for j in range(d) if im >> j & 1}
|
||||||
|
if len(interior) == d:
|
||||||
|
continue # need a boundary vertex to see
|
||||||
|
int_list = sorted(interior)
|
||||||
|
for tvals in product((0, 1, 2), repeat=len(interior)):
|
||||||
|
t = dict(zip(int_list, tvals))
|
||||||
|
S = star_contrib(d, interior, t)
|
||||||
|
fans = [fan_contrib(d, r, interior, t) for r in range(d)]
|
||||||
|
# the reduction needs Star nonempty whenever it removes v; and
|
||||||
|
# it picks the fan, so compare to the SMALLEST fan that is itself
|
||||||
|
# achievable (nonempty) -- an empty fan means that root is invalid.
|
||||||
|
nonempty_fans = [len(f) for f in fans if f]
|
||||||
|
if not S:
|
||||||
|
# star infeasible: then v cannot carry this context. Only a
|
||||||
|
# problem if some fan IS feasible (then D-v has a seq D lacks).
|
||||||
|
if nonempty_fans:
|
||||||
|
fails += 1
|
||||||
|
continue
|
||||||
|
checked += 1
|
||||||
|
if nonempty_fans:
|
||||||
|
mn = min(nonempty_fans)
|
||||||
|
if len(S) < mn:
|
||||||
|
fails += 1
|
||||||
|
if worst is None or len(S) - mn < worst[0]:
|
||||||
|
worst = (len(S) - mn, d, sorted(interior), t,
|
||||||
|
len(S), mn)
|
||||||
|
print(f" configurations checked: {checked}")
|
||||||
|
print(f" violations of |Star| >= min nonempty |Fan|: {fails}")
|
||||||
|
if worst:
|
||||||
|
print(f" worst gap (|Star|-minFan, d, interior, t, |Star|, minFan): {worst}")
|
||||||
|
if fails == 0:
|
||||||
|
print(" => LOCAL CLAIM HOLDS: the star always dominates the best fan.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
"""
|
||||||
|
Construct the triangulated disk (= nested tire substructure) that MAXIMALLY
|
||||||
|
constrains its outer cycle.
|
||||||
|
|
||||||
|
For a triangulated disk D with boundary cycle C = (0..n-1), the achievable outer
|
||||||
|
Heawood set is
|
||||||
|
|
||||||
|
Phi(D) = { (lambda*(v))_{v in C} : lambda in {+1,-1}^{faces},
|
||||||
|
sum_{f ∋ w} lambda(f) ≡ 0 for every interior vertex w } .
|
||||||
|
|
||||||
|
Phi depends only on the disk triangulation (no BFS/tree needed). We want the disk
|
||||||
|
minimising |Phi| -- the worst case for the pigeonhole. Note Phi is always
|
||||||
|
sign-closed and non-empty, so |Phi| >= 1, and |Phi| = 1 forces Phi = { all-zeros }.
|
||||||
|
|
||||||
|
Key local fact: a degree-3 interior vertex (one Apollonian stack) has incident
|
||||||
|
faces f1,f2,f3 with lambda(f1)+lambda(f2)+lambda(f3) ≡ 0 mod 3 over +/-1 values,
|
||||||
|
which forces f1=f2=f3. So stacking chains equalities and collapses Phi.
|
||||||
|
|
||||||
|
We (a) randomly search disks built by Apollonian stacking, and (b) try a
|
||||||
|
deterministic deep-stack construction, reporting the smallest Phi found.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import random
|
||||||
|
import sys
|
||||||
|
from itertools import product
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
def fan_triangulation(n):
|
||||||
|
"""n-gon (0..n-1) triangulated as a fan from vertex 0. No interior vertex."""
|
||||||
|
return [(0, i, i + 1) for i in range(1, n - 1)]
|
||||||
|
|
||||||
|
|
||||||
|
def stack(faces, idx, v):
|
||||||
|
a, b, c = faces[idx]
|
||||||
|
faces[idx] = (a, b, v)
|
||||||
|
faces.append((b, c, v))
|
||||||
|
faces.append((a, c, v))
|
||||||
|
|
||||||
|
|
||||||
|
def phi(faces, n, cap):
|
||||||
|
"""Phi on boundary 0..n-1; interior = vertices >= n."""
|
||||||
|
verts = set(v for f in faces for v in f)
|
||||||
|
interior = sorted(v for v in verts if v >= n)
|
||||||
|
F = len(faces)
|
||||||
|
if F > cap:
|
||||||
|
return None
|
||||||
|
# incidence
|
||||||
|
Bint = np.zeros((len(interior), F), dtype=np.int64)
|
||||||
|
iindex = {w: r for r, w in enumerate(interior)}
|
||||||
|
Cinc = np.zeros((n, F), dtype=np.int64)
|
||||||
|
for j, (a, b, c) in enumerate(faces):
|
||||||
|
for v in (a, b, c):
|
||||||
|
if v >= n:
|
||||||
|
Bint[iindex[v], j] = 1
|
||||||
|
else:
|
||||||
|
Cinc[v, j] = 1
|
||||||
|
labs = np.array(list(product((1, 2), repeat=F)), dtype=np.int64)
|
||||||
|
if len(interior):
|
||||||
|
keep = np.all((labs @ Bint.T) % 3 == 0, axis=1)
|
||||||
|
labs = labs[keep]
|
||||||
|
if labs.shape[0] == 0:
|
||||||
|
return set()
|
||||||
|
outer = (labs @ Cinc.T) % 3
|
||||||
|
return set(map(tuple, np.unique(outer, axis=0)))
|
||||||
|
|
||||||
|
|
||||||
|
def disp(s):
|
||||||
|
return tuple(-1 if int(x) == 2 else int(x) for x in s)
|
||||||
|
|
||||||
|
|
||||||
|
def gf3_rank(rows):
|
||||||
|
M = [[int(x) % 3 for x in r] for r in rows]
|
||||||
|
if not M:
|
||||||
|
return 0
|
||||||
|
nc = len(M[0]); r = 0
|
||||||
|
for c in range(nc):
|
||||||
|
piv = next((i for i in range(r, len(M)) if M[i][c] % 3), None)
|
||||||
|
if piv is None:
|
||||||
|
continue
|
||||||
|
M[r], M[piv] = M[piv], M[r]
|
||||||
|
inv = M[r][c] % 3
|
||||||
|
M[r] = [(x * inv) % 3 for x in M[r]]
|
||||||
|
for i in range(len(M)):
|
||||||
|
if i != r and M[i][c] % 3:
|
||||||
|
fct = M[i][c] % 3
|
||||||
|
M[i] = [(M[i][k] - fct * M[r][k]) % 3 for k in range(nc)]
|
||||||
|
r += 1
|
||||||
|
if r == len(M):
|
||||||
|
break
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def describe(P):
|
||||||
|
P = list(P)
|
||||||
|
sign_closed = all(tuple((3 - x) % 3 for x in s) in set(P) for s in P)
|
||||||
|
s0 = P[0]
|
||||||
|
D = [tuple((np.array(s) - np.array(s0)) % 3) for s in P]
|
||||||
|
rank = gf3_rank(D)
|
||||||
|
affine = (len(P) == 3 ** rank)
|
||||||
|
pow2 = (len(P) & (len(P) - 1)) == 0
|
||||||
|
return (f"sign-closed={sign_closed} affine-GF3={affine} "
|
||||||
|
f"|Phi|={len(P)} (power-of-2={pow2}) hull-dim={rank}")
|
||||||
|
|
||||||
|
|
||||||
|
def random_disk(n, n_stacks, rng):
|
||||||
|
faces = fan_triangulation(n)
|
||||||
|
nxt = n
|
||||||
|
for _ in range(n_stacks):
|
||||||
|
stack(faces, rng.randrange(len(faces)), nxt)
|
||||||
|
nxt += 1
|
||||||
|
return faces
|
||||||
|
|
||||||
|
|
||||||
|
def deep_stack_disk(n, n_stacks):
|
||||||
|
"""Always stack into the most-recently created face -> deep equality chain."""
|
||||||
|
faces = fan_triangulation(n)
|
||||||
|
nxt = n
|
||||||
|
for _ in range(n_stacks):
|
||||||
|
stack(faces, len(faces) - 1, nxt)
|
||||||
|
nxt += 1
|
||||||
|
return faces
|
||||||
|
|
||||||
|
|
||||||
|
def search(n, cap=18, trials=400, seed=0):
|
||||||
|
rng = random.Random(seed)
|
||||||
|
best = (10 ** 9, None, None)
|
||||||
|
max_stacks = (cap - (n - 2)) // 2
|
||||||
|
# random search
|
||||||
|
for _ in range(trials):
|
||||||
|
k = rng.randint(0, max_stacks)
|
||||||
|
faces = random_disk(n, k, rng)
|
||||||
|
P = phi(faces, n, cap)
|
||||||
|
if P is None:
|
||||||
|
continue
|
||||||
|
if len(P) < best[0]:
|
||||||
|
best = (len(P), k, P)
|
||||||
|
# deterministic deep stack at max depth
|
||||||
|
for k in range(max_stacks + 1):
|
||||||
|
faces = deep_stack_disk(n, k)
|
||||||
|
P = phi(faces, n, cap)
|
||||||
|
if P is not None and len(P) < best[0]:
|
||||||
|
best = (len(P), k, P)
|
||||||
|
size, k, P = best
|
||||||
|
print(f"n={n}: min |Phi| = {size} (= 2^(n-2) = {2**(n-2)}?) "
|
||||||
|
f"interior vertices = {k}, max stacks at cap {cap} = {max_stacks}")
|
||||||
|
print(f" {describe(P)}")
|
||||||
|
for s in sorted(P)[:6]:
|
||||||
|
print(f" {disp(s)}")
|
||||||
|
if len(P) > 6:
|
||||||
|
print(f" ... (+{len(P)-6} more)")
|
||||||
|
return size
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ns = [int(x) for x in sys.argv[1:]] or [4, 5, 6, 7]
|
||||||
|
print("Searching for maximally-constraining disks (min |Phi|)\n")
|
||||||
|
for n in ns:
|
||||||
|
# bigger cap for small n
|
||||||
|
cap = 18 if n <= 6 else 16
|
||||||
|
search(n, cap=cap)
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
"""
|
||||||
|
Test the MONOTONICITY LEMMA behind the 2^(n-2) lower bound:
|
||||||
|
|
||||||
|
adding an interior vertex never DECREASES |Phi|
|
||||||
|
(equivalently Phi(D') subset Phi(D) when D = D' + one interior vertex).
|
||||||
|
|
||||||
|
If true, every disk reduces to the k=0 base case without increasing Phi, so
|
||||||
|
|Phi(D)| >= |Phi(D_0)| = 2^(n-2). A single insertion that shrinks Phi (or breaks
|
||||||
|
the inclusion) would refute the proof strategy.
|
||||||
|
|
||||||
|
We build a validated base disk D' (Delaunay, convex non-cocircular boundary), then
|
||||||
|
insert a vertex two ways:
|
||||||
|
* degree-3 stack into a face (proved: should give equality)
|
||||||
|
* degree-4 open of an internal edge (a,b)|(c,d) (the first genuinely open case)
|
||||||
|
and compare Phi(D') to Phi(D).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from collections import Counter, defaultdict
|
||||||
|
from itertools import product
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from scipy.spatial import Delaunay
|
||||||
|
|
||||||
|
np.seterr(all="ignore")
|
||||||
|
|
||||||
|
|
||||||
|
def base_disk(n, k, rng):
|
||||||
|
ang = 2 * np.pi * np.arange(n) / n
|
||||||
|
rad = 1.0 + 0.15 * rng.random(n)
|
||||||
|
bpts = np.c_[rad * np.cos(ang), rad * np.sin(ang)]
|
||||||
|
if k:
|
||||||
|
r = 0.7 * np.sqrt(rng.random(k)); t = 2 * np.pi * rng.random(k)
|
||||||
|
pts = np.vstack([bpts, np.c_[r * np.cos(t), r * np.sin(t)]])
|
||||||
|
else:
|
||||||
|
pts = bpts
|
||||||
|
faces = [tuple(int(x) for x in s) for s in Delaunay(pts).simplices]
|
||||||
|
return faces
|
||||||
|
|
||||||
|
|
||||||
|
def valid(faces, n, k):
|
||||||
|
if len(faces) != 2 * k + n - 2:
|
||||||
|
return False
|
||||||
|
ec = Counter()
|
||||||
|
for a, b, c in faces:
|
||||||
|
for e in ((a, b), (b, c), (a, c)):
|
||||||
|
ec[frozenset(e)] += 1
|
||||||
|
return all(ec[frozenset((i, (i + 1) % n))] == 1 for i in range(n))
|
||||||
|
|
||||||
|
|
||||||
|
def phi(faces, n):
|
||||||
|
verts = set(v for f in faces for v in f)
|
||||||
|
interior = sorted(v for v in verts if v >= n)
|
||||||
|
F = len(faces)
|
||||||
|
if F > 18:
|
||||||
|
return None
|
||||||
|
Bint = np.zeros((len(interior), F), dtype=np.int64)
|
||||||
|
Cinc = np.zeros((n, F), dtype=np.int64)
|
||||||
|
iidx = {w: r for r, w in enumerate(interior)}
|
||||||
|
for j, (a, b, c) in enumerate(faces):
|
||||||
|
for v in (a, b, c):
|
||||||
|
if v >= n:
|
||||||
|
Bint[iidx[v], j] = 1
|
||||||
|
else:
|
||||||
|
Cinc[v, j] = 1
|
||||||
|
labs = np.array(list(product((1, 2), repeat=F)), dtype=np.int64)
|
||||||
|
if interior:
|
||||||
|
labs = labs[np.all((labs @ Bint.T) % 3 == 0, axis=1)]
|
||||||
|
if labs.shape[0] == 0:
|
||||||
|
return set()
|
||||||
|
return set(map(tuple, np.unique((labs @ Cinc.T) % 3, axis=0)))
|
||||||
|
|
||||||
|
|
||||||
|
def insert_deg3(faces, fi, v):
|
||||||
|
a, b, c = faces[fi]
|
||||||
|
out = [f for i, f in enumerate(faces) if i != fi]
|
||||||
|
out += [(a, b, v), (b, c, v), (a, c, v)]
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def insert_deg4(faces, n):
|
||||||
|
"""Open an internal edge (a,b) shared by faces (a,b,c),(a,b,d): replace those
|
||||||
|
two faces with the 4-star of a new center over the quad a-c-b-d."""
|
||||||
|
ef = defaultdict(list)
|
||||||
|
for i, (a, b, c) in enumerate(faces):
|
||||||
|
for e in ((a, b), (b, c), (a, c)):
|
||||||
|
ef[frozenset(e)].append(i)
|
||||||
|
for e, fl in ef.items():
|
||||||
|
if len(fl) != 2:
|
||||||
|
continue
|
||||||
|
a, b = tuple(e)
|
||||||
|
if (a < n and b < n and (b - a) % n in (1, n - 1)):
|
||||||
|
continue # boundary edge
|
||||||
|
f1, f2 = faces[fl[0]], faces[fl[1]]
|
||||||
|
c = next(x for x in f1 if x not in (a, b))
|
||||||
|
d = next(x for x in f2 if x not in (a, b))
|
||||||
|
if c == d:
|
||||||
|
continue
|
||||||
|
v = max(max(f) for f in faces) + 1
|
||||||
|
out = [f for i, f in enumerate(faces) if i not in fl]
|
||||||
|
out += [(a, c, v), (c, b, v), (b, d, v), (d, a, v)]
|
||||||
|
return out, v
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
seed = int(sys.argv[1]) if len(sys.argv) > 1 else 0
|
||||||
|
rng = np.random.default_rng(seed)
|
||||||
|
viol_size = 0 # |Phi(D)| < |Phi(D')| (monotonicity broken)
|
||||||
|
viol_incl = 0 # Phi(D') not subset Phi(D)
|
||||||
|
tested = 0
|
||||||
|
deg3_equal = 0; deg3_tot = 0
|
||||||
|
deg4_strict = 0; deg4_tot = 0
|
||||||
|
examples = []
|
||||||
|
|
||||||
|
for _ in range(500):
|
||||||
|
n = int(rng.integers(4, 7)); k = int(rng.integers(0, 4))
|
||||||
|
faces = base_disk(n, k, rng)
|
||||||
|
if not valid(faces, n, k) or len(faces) > 14:
|
||||||
|
continue
|
||||||
|
Pp = phi(faces, n)
|
||||||
|
if Pp is None:
|
||||||
|
continue
|
||||||
|
# degree-3 insertions: every face
|
||||||
|
for fi in range(len(faces)):
|
||||||
|
D = insert_deg3(faces, fi, max(max(f) for f in faces) + 1)
|
||||||
|
P = phi(D, n)
|
||||||
|
if P is None:
|
||||||
|
continue
|
||||||
|
tested += 1; deg3_tot += 1
|
||||||
|
if not Pp <= P:
|
||||||
|
viol_incl += 1
|
||||||
|
if len(P) < len(Pp):
|
||||||
|
viol_size += 1
|
||||||
|
if len(examples) < 5:
|
||||||
|
examples.append(("deg3", n, len(Pp), len(P)))
|
||||||
|
if len(P) == len(Pp):
|
||||||
|
deg3_equal += 1
|
||||||
|
# one degree-4 insertion
|
||||||
|
D4, v = insert_deg4(faces, n)
|
||||||
|
if D4 is not None:
|
||||||
|
P = phi(D4, n)
|
||||||
|
if P is not None:
|
||||||
|
tested += 1; deg4_tot += 1
|
||||||
|
if not Pp <= P:
|
||||||
|
viol_incl += 1
|
||||||
|
if len(P) < len(Pp):
|
||||||
|
viol_size += 1
|
||||||
|
if len(examples) < 5:
|
||||||
|
examples.append(("deg4", n, len(Pp), len(P)))
|
||||||
|
if len(P) > len(Pp):
|
||||||
|
deg4_strict += 1
|
||||||
|
|
||||||
|
print(f"insertions tested: {tested}")
|
||||||
|
print(f" monotonicity violations (|Phi(D)| < |Phi(D')|): {viol_size}")
|
||||||
|
print(f" inclusion violations (Phi(D') not subset Phi(D)): {viol_incl}")
|
||||||
|
print(f" degree-3: {deg3_equal}/{deg3_tot} gave EXACT equality "
|
||||||
|
f"(proved un-stacking => should be all)")
|
||||||
|
print(f" degree-4: {deg4_strict}/{deg4_tot} strictly ENLARGED Phi")
|
||||||
|
if examples:
|
||||||
|
print(" VIOLATION examples (type,n,|Phi(D')|,|Phi(D)|):")
|
||||||
|
for e in examples:
|
||||||
|
print(f" {e}")
|
||||||
|
else:
|
||||||
|
print(" no violation: every insertion preserved or enlarged Phi, "
|
||||||
|
"and Phi(D') subset Phi(D) throughout.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
"""
|
||||||
|
Proof strategy A for the irreducible lemma (|Phi| >= 2^(n-2)):
|
||||||
|
|
||||||
|
induction on k via a Phi-NON-INCREASING vertex removal. Monotonicity is false
|
||||||
|
(some removals raise Phi), but we only need ONE good removal per disk: if every
|
||||||
|
disk with k>=1 has an interior vertex v and a link-retriangulation with
|
||||||
|
|Phi(D - v)| <= |Phi(D)|, then chaining down to k=0 gives |Phi(D)| >= 2^(n-2).
|
||||||
|
|
||||||
|
This probe: for each disk, try removing each interior vertex (retriangulating its
|
||||||
|
link by a fan from every link vertex, keeping only valid retriangulations), and
|
||||||
|
record whether the BEST removal satisfies |Phi(D-v)| <= |Phi(D)|. Reports the
|
||||||
|
fraction of disks where such a removal EXISTS (strategy viable) vs disks where
|
||||||
|
EVERY removal strictly raises Phi (strategy fails) -- and dumps the failures.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from collections import Counter, defaultdict
|
||||||
|
from itertools import product
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from scipy.spatial import Delaunay
|
||||||
|
|
||||||
|
np.seterr(all="ignore")
|
||||||
|
|
||||||
|
|
||||||
|
def disk(n, k, rng):
|
||||||
|
ang = 2 * np.pi * np.arange(n) / n
|
||||||
|
rad = 1.0 + 0.18 * rng.random(n)
|
||||||
|
bpts = np.c_[rad * np.cos(ang), rad * np.sin(ang)]
|
||||||
|
r = 0.8 * np.sqrt(rng.random(k)); t = 2 * np.pi * rng.random(k)
|
||||||
|
pts = np.vstack([bpts, np.c_[r * np.cos(t), r * np.sin(t)]])
|
||||||
|
return [tuple(int(x) for x in s) for s in Delaunay(pts).simplices]
|
||||||
|
|
||||||
|
|
||||||
|
def valid(faces, n, k):
|
||||||
|
if len(faces) != 2 * k + n - 2:
|
||||||
|
return False
|
||||||
|
ec = Counter()
|
||||||
|
for a, b, c in faces:
|
||||||
|
for e in ((a, b), (b, c), (a, c)):
|
||||||
|
ec[frozenset(e)] += 1
|
||||||
|
return all(ec[frozenset((i, (i + 1) % n))] == 1 for i in range(n))
|
||||||
|
|
||||||
|
|
||||||
|
def phi_size(faces, n):
|
||||||
|
interior = sorted(set(v for f in faces for v in f if v >= n))
|
||||||
|
F = len(faces)
|
||||||
|
if F > 16:
|
||||||
|
return None
|
||||||
|
Bint = np.zeros((len(interior), F), dtype=np.int64)
|
||||||
|
Cinc = np.zeros((n, F), dtype=np.int64)
|
||||||
|
iidx = {w: r for r, w in enumerate(interior)}
|
||||||
|
for j, (a, b, c) in enumerate(faces):
|
||||||
|
for v in (a, b, c):
|
||||||
|
if v >= n:
|
||||||
|
Bint[iidx[v], j] = 1
|
||||||
|
else:
|
||||||
|
Cinc[v, j] = 1
|
||||||
|
labs = np.array(list(product((1, 2), repeat=F)), dtype=np.int64)
|
||||||
|
if interior:
|
||||||
|
labs = labs[np.all((labs @ Bint.T) % 3 == 0, axis=1)]
|
||||||
|
if labs.shape[0] == 0:
|
||||||
|
return 0
|
||||||
|
return len(set(map(tuple, np.unique((labs @ Cinc.T) % 3, axis=0))))
|
||||||
|
|
||||||
|
|
||||||
|
def edges_of(faces):
|
||||||
|
E = set()
|
||||||
|
for a, b, c in faces:
|
||||||
|
for e in ((a, b), (b, c), (a, c)):
|
||||||
|
E.add(frozenset(e))
|
||||||
|
return E
|
||||||
|
|
||||||
|
|
||||||
|
def link_cycle(faces, v):
|
||||||
|
opp = [tuple(x for x in f if x != v) for f in faces if v in f]
|
||||||
|
adj = defaultdict(list)
|
||||||
|
for a, b in opp:
|
||||||
|
adj[a].append(b); adj[b].append(a)
|
||||||
|
if any(len(adj[u]) != 2 for u in adj):
|
||||||
|
return None # link not a simple cycle
|
||||||
|
start = opp[0][0]; cyc = [start]; prev = None; cur = start
|
||||||
|
while True:
|
||||||
|
nxt = [x for x in adj[cur] if x != prev]
|
||||||
|
if not nxt:
|
||||||
|
return None
|
||||||
|
nxt = nxt[0]
|
||||||
|
if nxt == start:
|
||||||
|
break
|
||||||
|
cyc.append(nxt); prev, cur = cur, nxt
|
||||||
|
if len(cyc) > len(adj):
|
||||||
|
return None
|
||||||
|
return cyc if len(cyc) == len(adj) else None
|
||||||
|
|
||||||
|
|
||||||
|
def removals(faces, v, n):
|
||||||
|
"""Yield valid (faces of D - v) over fan retriangulations of v's link."""
|
||||||
|
cyc = link_cycle(faces, v)
|
||||||
|
if cyc is None:
|
||||||
|
return
|
||||||
|
d = len(cyc)
|
||||||
|
rest = [f for f in faces if v not in f]
|
||||||
|
Erest = edges_of(rest)
|
||||||
|
for s in range(d):
|
||||||
|
order = cyc[s:] + cyc[:s]
|
||||||
|
diags = [frozenset((order[0], order[j])) for j in range(2, d - 1)]
|
||||||
|
if any(dg in Erest for dg in diags):
|
||||||
|
continue # duplicate-edge: invalid retriangulation
|
||||||
|
fan = [(order[0], order[j], order[j + 1]) for j in range(1, d - 1)]
|
||||||
|
yield rest + fan
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
seed = int(sys.argv[1]) if len(sys.argv) > 1 else 0
|
||||||
|
rng = np.random.default_rng(seed)
|
||||||
|
irreducible_only = "--irr" in sys.argv
|
||||||
|
|
||||||
|
tested = 0
|
||||||
|
has_good = 0
|
||||||
|
fail_examples = []
|
||||||
|
|
||||||
|
for _ in range(4000):
|
||||||
|
n = int(rng.integers(4, 7)); k = int(rng.integers(1, 4))
|
||||||
|
faces = disk(n, k, rng)
|
||||||
|
if not valid(faces, n, k) or len(faces) > 14:
|
||||||
|
continue
|
||||||
|
deg = Counter()
|
||||||
|
for f in faces:
|
||||||
|
for x in f:
|
||||||
|
if x >= n:
|
||||||
|
deg[x] += 1
|
||||||
|
if irreducible_only and (len(deg) < k or any(deg[x] < 4 for x in deg)):
|
||||||
|
continue
|
||||||
|
base = phi_size(faces, n)
|
||||||
|
if base is None:
|
||||||
|
continue
|
||||||
|
best = None
|
||||||
|
for v in [x for x in deg]:
|
||||||
|
for D2 in removals(faces, v, n):
|
||||||
|
s = phi_size(D2, n)
|
||||||
|
if s is None:
|
||||||
|
continue
|
||||||
|
if best is None or s < best:
|
||||||
|
best = s
|
||||||
|
if best is None:
|
||||||
|
continue
|
||||||
|
tested += 1
|
||||||
|
if best <= base:
|
||||||
|
has_good += 1
|
||||||
|
elif len(fail_examples) < 6:
|
||||||
|
fail_examples.append((n, k, base, best, sorted(deg.values())))
|
||||||
|
|
||||||
|
tag = "irreducible" if irreducible_only else "all"
|
||||||
|
print(f"disks tested ({tag}, k>=1): {tested}")
|
||||||
|
print(f" have a Phi-non-increasing removal: {has_good}/{tested} "
|
||||||
|
f"({100*has_good/max(tested,1):.1f}%)")
|
||||||
|
if fail_examples:
|
||||||
|
print(" FAILURES (every removal raised Phi) "
|
||||||
|
"(n,k,|Phi(D)|,best|Phi(D-v)|,int-degs):")
|
||||||
|
for e in fail_examples:
|
||||||
|
print(f" {e}")
|
||||||
|
else:
|
||||||
|
print(" no failure: every disk had a non-increasing removal "
|
||||||
|
"=> induction strategy A is viable.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
"""
|
||||||
|
Search for a UNIVERSAL Heawood boundary sequence for a tire graph.
|
||||||
|
|
||||||
|
Fix an outer boundary cycle B_out of length n (the interface at which a tire
|
||||||
|
glues to its parent). Each way of filling the annulus -- an inner boundary of
|
||||||
|
size m together with a spoke triangulation ("inner graph") -- gives a tire whose
|
||||||
|
annular faces induce a set of realisable outer Heawood sequences
|
||||||
|
|
||||||
|
R_out(tire) = { (lambda*(v0), ..., lambda*(v_{n-1})) : lambda in {+1,-1}^F }
|
||||||
|
⊆ {0,1,-1}^n .
|
||||||
|
|
||||||
|
A *universal sequence* for B_out is one realisable for EVERY inner graph, i.e. a
|
||||||
|
member of the intersection ∩_tire R_out(tire). If a universal sequence existed,
|
||||||
|
a parent could always present its negation and glue to any child regardless of
|
||||||
|
the child's interior.
|
||||||
|
|
||||||
|
Note: chords of the inner outerplanar graph O lie inside B_in and bound no
|
||||||
|
annular face, so they do not change R_out -- only (n, m, spoke-path) do. And
|
||||||
|
intersecting over a SUBFAMILY of inner graphs can only OVERestimate the true
|
||||||
|
intersection, so finding the intersection empty over simple-cycle inner fills is
|
||||||
|
already conclusive that NO universal sequence exists.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from itertools import combinations, product
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
def lattice_paths(n_outer, m_inner):
|
||||||
|
"""All spoke triangulations: strings with n_outer 'O' moves, m_inner 'I'."""
|
||||||
|
N = n_outer + m_inner
|
||||||
|
for opos in combinations(range(N), n_outer):
|
||||||
|
opos = set(opos)
|
||||||
|
yield "".join("O" if i in opos else "I" for i in range(N))
|
||||||
|
|
||||||
|
|
||||||
|
def annular_faces(n, m, path):
|
||||||
|
"""Faces (triangles) of the annulus between outer n-cycle (0..n-1) and inner
|
||||||
|
m-cycle (n..n+m-1) under the spoke path. Starts at spoke (outer0, inner0)."""
|
||||||
|
faces = []
|
||||||
|
i = j = 0
|
||||||
|
for mv in path:
|
||||||
|
if mv == "O":
|
||||||
|
faces.append((i % n, (i + 1) % n, n + (j % m)))
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
faces.append((i % n, n + (j % m), n + ((j + 1) % m)))
|
||||||
|
j += 1
|
||||||
|
return faces
|
||||||
|
|
||||||
|
|
||||||
|
def fan_faces(n):
|
||||||
|
"""m = 1 degenerate inner boundary: a wheel/fan, center = vertex n."""
|
||||||
|
return [(i, (i + 1) % n, n) for i in range(n)]
|
||||||
|
|
||||||
|
|
||||||
|
def realisable_outer(n, faces):
|
||||||
|
"""Set of outer Heawood sequences over all +/-1 face labellings."""
|
||||||
|
F = len(faces)
|
||||||
|
A = np.zeros((n, F), dtype=np.int64) # outer-vertex x face incidence
|
||||||
|
for f, tri in enumerate(faces):
|
||||||
|
for v in tri:
|
||||||
|
if v < n:
|
||||||
|
A[v, f] = 1
|
||||||
|
labs = np.array(list(product((1, 2), repeat=F)), dtype=np.int64)
|
||||||
|
vals = (labs @ A.T) % 3
|
||||||
|
# display residues in {0, 1, -1}: 2 -> -1
|
||||||
|
vals = np.where(vals == 2, -1, vals)
|
||||||
|
return set(tuple(int(x) for x in row) for row in np.unique(vals, axis=0))
|
||||||
|
|
||||||
|
|
||||||
|
def tires_for(n, m_max, fcap):
|
||||||
|
"""Yield (label, faces) for inner fills of an n-outer tire."""
|
||||||
|
yield (f"m=1 fan", fan_faces(n))
|
||||||
|
for m in range(2, m_max + 1):
|
||||||
|
if n + m > fcap:
|
||||||
|
continue
|
||||||
|
for path in lattice_paths(n, m):
|
||||||
|
yield (f"m={m} {path}", annular_faces(n, m, path))
|
||||||
|
|
||||||
|
|
||||||
|
def run(n, m_max=7, fcap=13):
|
||||||
|
inter = None
|
||||||
|
n_tires = 0
|
||||||
|
min_set = (10**9, None)
|
||||||
|
shrink_trace = []
|
||||||
|
for label, faces in tires_for(n, m_max, fcap):
|
||||||
|
R = realisable_outer(n, faces)
|
||||||
|
n_tires += 1
|
||||||
|
if len(R) < min_set[0]:
|
||||||
|
min_set = (len(R), label)
|
||||||
|
if inter is None:
|
||||||
|
inter = set(R)
|
||||||
|
else:
|
||||||
|
before = len(inter)
|
||||||
|
inter &= R
|
||||||
|
if len(inter) < before:
|
||||||
|
shrink_trace.append((n_tires, label, len(inter)))
|
||||||
|
if not inter:
|
||||||
|
break
|
||||||
|
print(f"n={n}: {n_tires} tires tried, "
|
||||||
|
f"smallest single R_out = {min_set[0]} ({min_set[1]})")
|
||||||
|
if inter:
|
||||||
|
print(f" UNIVERSAL sequences found: {len(inter)}")
|
||||||
|
for s in sorted(inter)[:12]:
|
||||||
|
print(f" {s}")
|
||||||
|
else:
|
||||||
|
print(f" NO universal sequence: intersection emptied after "
|
||||||
|
f"{n_tires} tires")
|
||||||
|
print(" intersection size as tires were added (last few shrinks):")
|
||||||
|
for t in shrink_trace[-6:]:
|
||||||
|
print(f" after tire {t[0]:4d} ({t[1]}): |∩| = {t[2]}")
|
||||||
|
return bool(inter)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
ns = [int(sys.argv[1])]
|
||||||
|
else:
|
||||||
|
ns = [3, 4, 5, 6]
|
||||||
|
print("Searching for universal Heawood boundary sequences\n")
|
||||||
|
for n in ns:
|
||||||
|
run(n)
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
"""
|
||||||
|
Transfer operator for the Heawood program, in the cleanest self-similar setting:
|
||||||
|
a chain of annular tires with n_out = n_in = n. Each tire's labelling map sends
|
||||||
|
+/-1 face labels to (outer sequence, inner sequence). Gluing a child below means
|
||||||
|
the parent's inner sequence must negate (mod 3) the child's achievable outer
|
||||||
|
sequence. So the achievable outer-interface set propagates UP the chain by
|
||||||
|
|
||||||
|
Phi(parent) = { outer(lambda) : lambda in {+-1}^F,
|
||||||
|
inner(lambda) in -Phi(child) }.
|
||||||
|
|
||||||
|
This is a monotone set-operator on subsets of (Z/3)^n. Iterating it models a
|
||||||
|
deepening nested chain; we look for a FIXED POINT (absorbing set) and test which
|
||||||
|
candidate self-similar invariants the limit satisfies:
|
||||||
|
* non-empty
|
||||||
|
* closed under the global sign flip s -> -s
|
||||||
|
* local marginals: does every position attain all of {0,1,-1}?
|
||||||
|
* is it an affine GF(3) subspace? (we expect NO -- R_T is a zonotope)
|
||||||
|
* does a linear/parity constraint cut it out?
|
||||||
|
Sequences are stored mod 3 in {0,1,2}; printed in {0,1,-1} (2 -> -1).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from itertools import product
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
def annular_tire(n_out, n_in, path):
|
||||||
|
"""Faces between outer cycle 0..n_out-1 and inner cycle n_out..n_out+n_in-1."""
|
||||||
|
faces = []
|
||||||
|
i = j = 0
|
||||||
|
for mv in path:
|
||||||
|
if mv == "O":
|
||||||
|
faces.append((i % n_out, (i + 1) % n_out, n_out + (j % n_in)))
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
faces.append((i % n_out, n_out + (j % n_in), n_out + ((j + 1) % n_in)))
|
||||||
|
j += 1
|
||||||
|
return faces
|
||||||
|
|
||||||
|
|
||||||
|
def labelling_pairs(n_out, n_in, faces):
|
||||||
|
"""All (outer_seq, inner_seq) over lambda in {+1,-1}^F, as Z/3 tuples."""
|
||||||
|
F = len(faces)
|
||||||
|
Ao = np.zeros((n_out, F), dtype=np.int64)
|
||||||
|
Ai = np.zeros((n_in, F), dtype=np.int64)
|
||||||
|
for f, (a, b, c) in enumerate(faces):
|
||||||
|
for v in (a, b, c):
|
||||||
|
if v < n_out:
|
||||||
|
Ao[v, f] = 1
|
||||||
|
else:
|
||||||
|
Ai[v - n_out, f] = 1
|
||||||
|
labs = np.array(list(product((1, 2), repeat=F)), dtype=np.int64)
|
||||||
|
outer = (labs @ Ao.T) % 3
|
||||||
|
inner = (labs @ Ai.T) % 3
|
||||||
|
return [(tuple(o), tuple(i)) for o, i in zip(outer.tolist(), inner.tolist())]
|
||||||
|
|
||||||
|
|
||||||
|
def make_operator(pairs):
|
||||||
|
def op(phi_child):
|
||||||
|
neg = {tuple((3 - x) % 3 for x in s) for s in phi_child}
|
||||||
|
return {o for (o, inn) in pairs if inn in neg}
|
||||||
|
return op
|
||||||
|
|
||||||
|
|
||||||
|
def iterate_to_fixed(op, start, max_iter=50):
|
||||||
|
phi = frozenset(start)
|
||||||
|
seen = [phi]
|
||||||
|
for _ in range(max_iter):
|
||||||
|
nxt = frozenset(op(phi))
|
||||||
|
if nxt == phi:
|
||||||
|
return phi, "fixed", len(seen)
|
||||||
|
if nxt in seen:
|
||||||
|
return nxt, "cycle", len(seen)
|
||||||
|
phi = nxt
|
||||||
|
seen.append(phi)
|
||||||
|
return phi, "no-converge", len(seen)
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------- invariant tests -------------------------------------------
|
||||||
|
def disp(s):
|
||||||
|
return tuple(-1 if x == 2 else x for x in s)
|
||||||
|
|
||||||
|
|
||||||
|
def gf3_rank(rows):
|
||||||
|
M = [[x % 3 for x in r] for r in rows]
|
||||||
|
if not M:
|
||||||
|
return 0
|
||||||
|
nc = len(M[0]); r = 0
|
||||||
|
for c in range(nc):
|
||||||
|
piv = next((i for i in range(r, len(M)) if M[i][c] % 3), None)
|
||||||
|
if piv is None:
|
||||||
|
continue
|
||||||
|
M[r], M[piv] = M[piv], M[r]
|
||||||
|
inv = M[r][c] % 3 # 1->1, 2->2 are self-inverse mod 3
|
||||||
|
M[r] = [(x * inv) % 3 for x in M[r]]
|
||||||
|
for i in range(len(M)):
|
||||||
|
if i != r and M[i][c] % 3:
|
||||||
|
f = M[i][c] % 3
|
||||||
|
M[i] = [(M[i][k] - f * M[r][k]) % 3 for k in range(nc)]
|
||||||
|
r += 1
|
||||||
|
if r == len(M):
|
||||||
|
break
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def is_affine(S):
|
||||||
|
S = list(S)
|
||||||
|
if len(S) <= 1:
|
||||||
|
return True
|
||||||
|
s0 = S[0]
|
||||||
|
D = [tuple((np.array(s) - np.array(s0)) % 3) for s in S]
|
||||||
|
return len(S) == 3 ** gf3_rank(D)
|
||||||
|
|
||||||
|
|
||||||
|
def marginals_full(S, n):
|
||||||
|
return all({s[i] for s in S} == {0, 1, 2} for i in range(n))
|
||||||
|
|
||||||
|
|
||||||
|
def sign_closed(S):
|
||||||
|
return all(tuple((3 - x) % 3 for x in s) in S for s in S)
|
||||||
|
|
||||||
|
|
||||||
|
def linear_constraints(S, n):
|
||||||
|
"""Dimension of the space of linear forms vanishing on S-s0 (codim of hull)."""
|
||||||
|
S = list(S)
|
||||||
|
if len(S) <= 1:
|
||||||
|
return n
|
||||||
|
s0 = S[0]
|
||||||
|
D = [tuple((np.array(s) - np.array(s0)) % 3) for s in S]
|
||||||
|
return n - gf3_rank(D)
|
||||||
|
|
||||||
|
|
||||||
|
def analyse(tag, S, n):
|
||||||
|
print(f" [{tag}] |Phi|={len(S)} of 3^{n}={3**n} "
|
||||||
|
f"sign-closed={sign_closed(S)} marginals-full={marginals_full(S,n)} "
|
||||||
|
f"affine={is_affine(S)} hull-codim={linear_constraints(S,n)}")
|
||||||
|
|
||||||
|
|
||||||
|
def run(n, paths=None):
|
||||||
|
if paths is None:
|
||||||
|
# a few distinct same-n annular triangulations
|
||||||
|
paths = ["OI" * n, "O" * n + "I" * n, ("OOI" * n)[:2 * n]]
|
||||||
|
paths = [p for p in paths if p.count("O") == n and p.count("I") == n]
|
||||||
|
print(f"=== n={n} ===")
|
||||||
|
full = set(product((0, 1, 2), repeat=n))
|
||||||
|
for path in paths:
|
||||||
|
faces = annular_tire(n, n, path)
|
||||||
|
pairs = labelling_pairs(n, n, faces)
|
||||||
|
op = make_operator(pairs)
|
||||||
|
single = set(o for (o, _) in pairs) # leaf: full single-tire outer set
|
||||||
|
fixed, how, steps = iterate_to_fixed(op, single)
|
||||||
|
# also iterate from the universal start (all sequences allowed below)
|
||||||
|
fixed2, how2, _ = iterate_to_fixed(op, full)
|
||||||
|
print(f" path={path}: single-tire |outer|={len(single)}; "
|
||||||
|
f"iterate->{how} in {steps} steps; "
|
||||||
|
f"same-limit-from-full={fixed==fixed2}")
|
||||||
|
analyse("limit", fixed, n)
|
||||||
|
sample = sorted(disp(s) for s in fixed)[:8]
|
||||||
|
print(f" sample of limit set: {sample}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ns = [int(x) for x in sys.argv[1:]] or [4, 5, 6]
|
||||||
|
print("Transfer-operator fixed points on same-n annular tire chains\n")
|
||||||
|
for n in ns:
|
||||||
|
run(n)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
"""
|
||||||
|
Option 2: a direct "strong transversal" for |Phi(D)| >= 2^(n-2).
|
||||||
|
|
||||||
|
In the Boolean reformulation, feasible x in {0,1}^F satisfy |x|_w ≡ -deg(w) at
|
||||||
|
interior w, and the boundary sequence is (deg(v)+|x|_v mod 3)_{v in C}. A STRONG
|
||||||
|
TRANSVERSAL is a set A of n-2 faces such that
|
||||||
|
(i) every assignment x_A in {0,1}^A extends to a feasible x,
|
||||||
|
(ii) the boundary sequence is single-valued in x_A (all feasible completions of
|
||||||
|
a given x_A give the same boundary), and
|
||||||
|
(iii) the map x_A -> boundary is injective.
|
||||||
|
If such A exists, the 2^(n-2) assignments give 2^(n-2) distinct boundary sequences,
|
||||||
|
proving the bound CONSTRUCTIVELY. We test whether a strong transversal exists.
|
||||||
|
|
||||||
|
Heuristic worry: over GF(3) the internal completion freedom (dim k) exceeds the
|
||||||
|
boundary-invisible freedom (dim k-1), so (ii) may fail. Test settles it.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from collections import defaultdict
|
||||||
|
from itertools import combinations, product
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from scipy.spatial import Delaunay
|
||||||
|
|
||||||
|
np.seterr(all="ignore")
|
||||||
|
|
||||||
|
|
||||||
|
def disk(n, k, rng):
|
||||||
|
ang = 2 * np.pi * np.arange(n) / n
|
||||||
|
rad = 1.0 + 0.18 * rng.random(n)
|
||||||
|
bpts = np.c_[rad * np.cos(ang), rad * np.sin(ang)]
|
||||||
|
if k:
|
||||||
|
r = 0.8 * np.sqrt(rng.random(k)); t = 2 * np.pi * rng.random(k)
|
||||||
|
pts = np.vstack([bpts, np.c_[r * np.cos(t), r * np.sin(t)]])
|
||||||
|
else:
|
||||||
|
pts = bpts
|
||||||
|
return [tuple(int(x) for x in s) for s in Delaunay(pts).simplices]
|
||||||
|
|
||||||
|
|
||||||
|
def valid(faces, n, k):
|
||||||
|
if len(faces) != 2 * k + n - 2:
|
||||||
|
return False
|
||||||
|
from collections import Counter
|
||||||
|
ec = Counter()
|
||||||
|
for a, b, c in faces:
|
||||||
|
for e in ((a, b), (b, c), (a, c)):
|
||||||
|
ec[frozenset(e)] += 1
|
||||||
|
return all(ec[frozenset((i, (i + 1) % n))] == 1 for i in range(n))
|
||||||
|
|
||||||
|
|
||||||
|
def feasible_table(faces, n):
|
||||||
|
"""Return list of (x tuple, boundary tuple) over feasible x in {0,1}^F."""
|
||||||
|
interior = sorted(set(v for f in faces for v in f if v >= n))
|
||||||
|
F = len(faces)
|
||||||
|
if F > 14:
|
||||||
|
return None
|
||||||
|
Bint = np.zeros((len(interior), F), dtype=np.int64)
|
||||||
|
Cinc = np.zeros((n, F), dtype=np.int64)
|
||||||
|
deg = np.zeros(n, dtype=np.int64)
|
||||||
|
iidx = {w: r for r, w in enumerate(interior)}
|
||||||
|
degw = np.zeros(len(interior), dtype=np.int64)
|
||||||
|
for j, (a, b, c) in enumerate(faces):
|
||||||
|
for v in (a, b, c):
|
||||||
|
if v >= n:
|
||||||
|
Bint[iidx[v], j] = 1; degw[iidx[v]] += 1
|
||||||
|
else:
|
||||||
|
Cinc[v, j] = 1; deg[v] += 1
|
||||||
|
xs = np.array(list(product((0, 1), repeat=F)), dtype=np.int64)
|
||||||
|
if len(interior):
|
||||||
|
ok = np.all((xs @ Bint.T - (-degw)) % 3 == 0, axis=1)
|
||||||
|
xs = xs[ok]
|
||||||
|
if xs.shape[0] == 0:
|
||||||
|
return []
|
||||||
|
bnd = (deg + xs @ Cinc.T) % 3
|
||||||
|
return list(zip(map(tuple, xs.tolist()), map(tuple, bnd.tolist())))
|
||||||
|
|
||||||
|
|
||||||
|
def has_strong_transversal(table, F, n):
|
||||||
|
target = 2 ** (n - 2)
|
||||||
|
for A in combinations(range(F), n - 2):
|
||||||
|
groups = defaultdict(set)
|
||||||
|
for x, b in table:
|
||||||
|
groups[tuple(x[i] for i in A)].add(b)
|
||||||
|
if len(groups) != target:
|
||||||
|
continue # not every x_A feasible
|
||||||
|
if any(len(bs) != 1 for bs in groups.values()):
|
||||||
|
continue # not single-valued (cond ii)
|
||||||
|
reps = [next(iter(bs)) for bs in groups.values()]
|
||||||
|
if len(set(reps)) == target: # injective (cond iii)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
seed = int(sys.argv[1]) if len(sys.argv) > 1 else 0
|
||||||
|
rng = np.random.default_rng(seed)
|
||||||
|
tested = 0; have = 0; fails = []
|
||||||
|
for _ in range(3000):
|
||||||
|
n = int(rng.integers(4, 6)); k = int(rng.integers(1, 3))
|
||||||
|
faces = disk(n, k, rng)
|
||||||
|
if not valid(faces, n, k) or len(faces) > 12:
|
||||||
|
continue
|
||||||
|
tbl = feasible_table(faces, n)
|
||||||
|
if not tbl:
|
||||||
|
continue
|
||||||
|
tested += 1
|
||||||
|
if has_strong_transversal(tbl, len(faces), n):
|
||||||
|
have += 1
|
||||||
|
elif len(fails) < 5:
|
||||||
|
fails.append((n, k, len(faces)))
|
||||||
|
print(f"disks tested (k>=1): {tested}")
|
||||||
|
print(f" have a STRONG transversal (size n-2, single-valued, injective): "
|
||||||
|
f"{have}/{tested} ({100*have/max(tested,1):.1f}%)")
|
||||||
|
if fails:
|
||||||
|
print(f" failures (n,k,F): {fails}")
|
||||||
|
if have == tested and tested:
|
||||||
|
print(" => strong transversals always exist: constructive proof viable.")
|
||||||
|
elif have == 0:
|
||||||
|
print(" => strong transversals NEVER exist: this clean form of option 2 is dead.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
"""
|
||||||
|
Pin the extremal irreducible disk.
|
||||||
|
|
||||||
|
The wheel W_n: boundary n-cycle + one center, faces (i,i+1,c). The center is the
|
||||||
|
only interior vertex (degree n), constraint sum_i lambda_i ≡ 0 (mod 3), and the
|
||||||
|
boundary value is the cyclic adjacent sum sigma_i = lambda_{i-1}+lambda_i (mod 3).
|
||||||
|
So
|
||||||
|
|
||||||
|
|Phi(W_n)| = #{ (lambda_{i-1}+lambda_i)_i mod 3 : lambda in {+-1}^n, sum ≡ 0 } .
|
||||||
|
|
||||||
|
We (1) compute |Phi(W_n)| exactly for a range of n and look for a formula, and
|
||||||
|
(2) run a thorough irreducible-disk search to check whether the wheel is actually
|
||||||
|
the MINIMISER over irreducible disks (and dump the minimiser's structure).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from collections import Counter
|
||||||
|
from itertools import product
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from scipy.spatial import Delaunay
|
||||||
|
|
||||||
|
np.seterr(all="ignore")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------- exact wheel value ------------------------------------------
|
||||||
|
def wheel_phi_size(n):
|
||||||
|
S = set()
|
||||||
|
cnt = 0
|
||||||
|
for lam in product((1, -1), repeat=n):
|
||||||
|
if sum(lam) % 3 != 0:
|
||||||
|
continue
|
||||||
|
cnt += 1
|
||||||
|
sig = tuple((lam[i - 1] + lam[i]) % 3 for i in range(n))
|
||||||
|
S.add(sig)
|
||||||
|
return len(S), cnt # distinct boundary seqs, feasible labellings
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------- general disk Phi -------------------------------------------
|
||||||
|
def disk(n, k, rng):
|
||||||
|
ang = 2 * np.pi * np.arange(n) / n
|
||||||
|
rad = 1.0 + 0.18 * rng.random(n)
|
||||||
|
bpts = np.c_[rad * np.cos(ang), rad * np.sin(ang)]
|
||||||
|
r = 0.8 * np.sqrt(rng.random(k)); t = 2 * np.pi * rng.random(k)
|
||||||
|
pts = np.vstack([bpts, np.c_[r * np.cos(t), r * np.sin(t)]])
|
||||||
|
return [tuple(int(x) for x in s) for s in Delaunay(pts).simplices]
|
||||||
|
|
||||||
|
|
||||||
|
def valid(faces, n, k):
|
||||||
|
if len(faces) != 2 * k + n - 2:
|
||||||
|
return False
|
||||||
|
ec = Counter()
|
||||||
|
for a, b, c in faces:
|
||||||
|
for e in ((a, b), (b, c), (a, c)):
|
||||||
|
ec[frozenset(e)] += 1
|
||||||
|
return all(ec[frozenset((i, (i + 1) % n))] == 1 for i in range(n))
|
||||||
|
|
||||||
|
|
||||||
|
def phi_size(faces, n):
|
||||||
|
interior = sorted(set(v for f in faces for v in f if v >= n))
|
||||||
|
F = len(faces)
|
||||||
|
if F > 18:
|
||||||
|
return None
|
||||||
|
Bint = np.zeros((len(interior), F), dtype=np.int64)
|
||||||
|
Cinc = np.zeros((n, F), dtype=np.int64)
|
||||||
|
iidx = {w: r for r, w in enumerate(interior)}
|
||||||
|
for j, (a, b, c) in enumerate(faces):
|
||||||
|
for v in (a, b, c):
|
||||||
|
if v >= n:
|
||||||
|
Bint[iidx[v], j] = 1
|
||||||
|
else:
|
||||||
|
Cinc[v, j] = 1
|
||||||
|
labs = np.array(list(product((1, 2), repeat=F)), dtype=np.int64)
|
||||||
|
if interior:
|
||||||
|
labs = labs[np.all((labs @ Bint.T) % 3 == 0, axis=1)]
|
||||||
|
if labs.shape[0] == 0:
|
||||||
|
return 0
|
||||||
|
return len(set(map(tuple, np.unique((labs @ Cinc.T) % 3, axis=0))))
|
||||||
|
|
||||||
|
|
||||||
|
def min_degree_ok(faces, n, k):
|
||||||
|
deg = Counter()
|
||||||
|
for f in faces:
|
||||||
|
for v in f:
|
||||||
|
if v >= n:
|
||||||
|
deg[v] += 1
|
||||||
|
return len(deg) == k and all(deg[v] >= 4 for v in deg)
|
||||||
|
|
||||||
|
|
||||||
|
def single_deg_disk(n, d):
|
||||||
|
"""One interior vertex v=n of degree d: fan over boundary 0..d-1, the rest of
|
||||||
|
the n-gon polygon-triangulated from vertex 0 (so v stays degree d, k=1)."""
|
||||||
|
v = n
|
||||||
|
faces = [(i, i + 1, v) for i in range(d - 1)]
|
||||||
|
poly = [0] + list(range(n - 1, d - 2, -1)) + [v]
|
||||||
|
for j in range(1, len(poly) - 1):
|
||||||
|
faces.append((0, poly[j], poly[j + 1]))
|
||||||
|
return faces
|
||||||
|
|
||||||
|
|
||||||
|
def degree_sweep():
|
||||||
|
print("\n|Phi| of a single degree-d interior vertex (rest at the floor):\n")
|
||||||
|
print(" ratio |Phi|/2^(n-2) by center degree d -- MINIMUM marks the extremal disk")
|
||||||
|
for n in (6, 7, 8):
|
||||||
|
row = []
|
||||||
|
for d in range(4, n + 1):
|
||||||
|
f = single_deg_disk(n, d)
|
||||||
|
row.append(f"d={d}:{phi_size(f, n)/2**(n-2):.4f}")
|
||||||
|
print(f" n={n} (floor {2**(n-2)}): " + " ".join(row))
|
||||||
|
print(" => min ratio 5/4 at d=4,5 (extremal); rises with d to ~4/3 at the wheel.")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("Exact |Phi(W_n)| and candidate formula:\n")
|
||||||
|
print(" n |Phi(W_n)| feasible-labellings ratio-to-2^(n-2)")
|
||||||
|
vals = {}
|
||||||
|
for n in range(3, 15):
|
||||||
|
s, cnt = wheel_phi_size(n)
|
||||||
|
vals[n] = s
|
||||||
|
print(f" {n:2d} {s:8d} {cnt:14d} {s / 2**(n-2):.4f}")
|
||||||
|
# differences / ratios to spot a pattern
|
||||||
|
print("\n consecutive ratios |Phi(W_{n+1})| / |Phi(W_n)|:")
|
||||||
|
for n in range(3, 14):
|
||||||
|
print(f" {n}->{n+1}: {vals[n+1]/vals[n]:.4f}")
|
||||||
|
|
||||||
|
print("\nThe actual irreducible minimiser (Delaunay search, dumping structure)\n")
|
||||||
|
rng = np.random.default_rng(0)
|
||||||
|
for n in (4, 5, 6, 7):
|
||||||
|
best = 10**9; bestdeg = None; bestk = None
|
||||||
|
for _ in range(12000):
|
||||||
|
k = int(rng.integers(1, 4))
|
||||||
|
faces = disk(n, k, rng)
|
||||||
|
if not valid(faces, n, k) or len(faces) > 16:
|
||||||
|
continue
|
||||||
|
if not min_degree_ok(faces, n, k):
|
||||||
|
continue
|
||||||
|
s = phi_size(faces, n)
|
||||||
|
if s and s < best:
|
||||||
|
best = s
|
||||||
|
deg = Counter()
|
||||||
|
for f in faces:
|
||||||
|
for v in f:
|
||||||
|
if v >= n:
|
||||||
|
deg[v] += 1
|
||||||
|
bestdeg = sorted(deg.values()); bestk = k
|
||||||
|
floor = 2 ** (n - 2)
|
||||||
|
print(f" n={n}: min irreducible |Phi|={best} (k={bestk}, interior degrees "
|
||||||
|
f"{bestdeg}) ratio to floor = {best/floor:.4f} "
|
||||||
|
f"5*2^(n-4)={5*2**(n-4)} wheel={vals[n]}")
|
||||||
|
degree_sweep()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
\relax
|
||||||
|
\newlabel{lem:unstack}{{}{2}}
|
||||||
|
\newlabel{lem:base}{{}{3}}
|
||||||
|
\newlabel{prop:reduction}{{}{3}}
|
||||||
|
\newlabel{conj:irreducible}{{}{3}}
|
||||||
|
\gdef \@abspage@last{3}
|
||||||
@@ -0,0 +1,300 @@
|
|||||||
|
This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 17 JUN 2026 21:32
|
||||||
|
entering extended mode
|
||||||
|
restricted \write18 enabled.
|
||||||
|
%&-line parsing enabled.
|
||||||
|
**boundary_restriction_structure.tex
|
||||||
|
(./boundary_restriction_structure.tex
|
||||||
|
LaTeX2e <2021-11-15> patch level 1
|
||||||
|
L3 programming layer <2022-02-24>
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/base/article.cls
|
||||||
|
Document Class: article 2021/10/04 v1.4n Standard LaTeX document class
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/base/size11.clo
|
||||||
|
File: size11.clo 2021/10/04 v1.4n Standard LaTeX file (size option)
|
||||||
|
)
|
||||||
|
\c@part=\count185
|
||||||
|
\c@section=\count186
|
||||||
|
\c@subsection=\count187
|
||||||
|
\c@subsubsection=\count188
|
||||||
|
\c@paragraph=\count189
|
||||||
|
\c@subparagraph=\count190
|
||||||
|
\c@figure=\count191
|
||||||
|
\c@table=\count192
|
||||||
|
\abovecaptionskip=\skip47
|
||||||
|
\belowcaptionskip=\skip48
|
||||||
|
\bibindent=\dimen138
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsmath.sty
|
||||||
|
Package: amsmath 2021/10/15 v2.17l AMS math features
|
||||||
|
\@mathmargin=\skip49
|
||||||
|
|
||||||
|
For additional information on amsmath, use the `?' option.
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amstext.sty
|
||||||
|
Package: amstext 2021/08/26 v2.01 AMS text
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsgen.sty
|
||||||
|
File: amsgen.sty 1999/11/30 v2.0 generic functions
|
||||||
|
\@emptytoks=\toks16
|
||||||
|
\ex@=\dimen139
|
||||||
|
))
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsbsy.sty
|
||||||
|
Package: amsbsy 1999/11/29 v1.2d Bold Symbols
|
||||||
|
\pmbraise@=\dimen140
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsopn.sty
|
||||||
|
Package: amsopn 2021/08/26 v2.02 operator names
|
||||||
|
)
|
||||||
|
\inf@bad=\count193
|
||||||
|
LaTeX Info: Redefining \frac on input line 234.
|
||||||
|
\uproot@=\count194
|
||||||
|
\leftroot@=\count195
|
||||||
|
LaTeX Info: Redefining \overline on input line 399.
|
||||||
|
\classnum@=\count196
|
||||||
|
\DOTSCASE@=\count197
|
||||||
|
LaTeX Info: Redefining \ldots on input line 496.
|
||||||
|
LaTeX Info: Redefining \dots on input line 499.
|
||||||
|
LaTeX Info: Redefining \cdots on input line 620.
|
||||||
|
\Mathstrutbox@=\box50
|
||||||
|
\strutbox@=\box51
|
||||||
|
\big@size=\dimen141
|
||||||
|
LaTeX Font Info: Redeclaring font encoding OML on input line 743.
|
||||||
|
LaTeX Font Info: Redeclaring font encoding OMS on input line 744.
|
||||||
|
\macc@depth=\count198
|
||||||
|
\c@MaxMatrixCols=\count199
|
||||||
|
\dotsspace@=\muskip16
|
||||||
|
\c@parentequation=\count266
|
||||||
|
\dspbrk@lvl=\count267
|
||||||
|
\tag@help=\toks17
|
||||||
|
\row@=\count268
|
||||||
|
\column@=\count269
|
||||||
|
\maxfields@=\count270
|
||||||
|
\andhelp@=\toks18
|
||||||
|
\eqnshift@=\dimen142
|
||||||
|
\alignsep@=\dimen143
|
||||||
|
\tagshift@=\dimen144
|
||||||
|
\tagwidth@=\dimen145
|
||||||
|
\totwidth@=\dimen146
|
||||||
|
\lineht@=\dimen147
|
||||||
|
\@envbody=\toks19
|
||||||
|
\multlinegap=\skip50
|
||||||
|
\multlinetaggap=\skip51
|
||||||
|
\mathdisplay@stack=\toks20
|
||||||
|
LaTeX Info: Redefining \[ on input line 2938.
|
||||||
|
LaTeX Info: Redefining \] on input line 2939.
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/amssymb.sty
|
||||||
|
Package: amssymb 2013/01/14 v3.01 AMS font symbols
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/amsfonts.sty
|
||||||
|
Package: amsfonts 2013/01/14 v3.01 Basic AMSFonts support
|
||||||
|
\symAMSa=\mathgroup4
|
||||||
|
\symAMSb=\mathgroup5
|
||||||
|
LaTeX Font Info: Redeclaring math symbol \hbar on input line 98.
|
||||||
|
LaTeX Font Info: Overwriting math alphabet `\mathfrak' in version `bold'
|
||||||
|
(Font) U/euf/m/n --> U/euf/b/n on input line 106.
|
||||||
|
))
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amscls/amsthm.sty
|
||||||
|
Package: amsthm 2020/05/29 v2.20.6
|
||||||
|
\thm@style=\toks21
|
||||||
|
\thm@bodyfont=\toks22
|
||||||
|
\thm@headfont=\toks23
|
||||||
|
\thm@notefont=\toks24
|
||||||
|
\thm@headpunct=\toks25
|
||||||
|
\thm@preskip=\skip52
|
||||||
|
\thm@postskip=\skip53
|
||||||
|
\thm@headsep=\skip54
|
||||||
|
\dth@everypar=\toks26
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/graphicx.sty
|
||||||
|
Package: graphicx 2021/09/16 v1.2d Enhanced LaTeX Graphics (DPC,SPQR)
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/keyval.sty
|
||||||
|
Package: keyval 2014/10/28 v1.15 key=value parser (DPC)
|
||||||
|
\KV@toks@=\toks27
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/graphics.sty
|
||||||
|
Package: graphics 2021/03/04 v1.4d Standard LaTeX Graphics (DPC,SPQR)
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/trig.sty
|
||||||
|
Package: trig 2021/08/11 v1.11 sin cos tan (DPC)
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics-cfg/graphics.cfg
|
||||||
|
File: graphics.cfg 2016/06/04 v1.11 sample graphics configuration
|
||||||
|
)
|
||||||
|
Package graphics Info: Driver file: pdftex.def on input line 107.
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics-def/pdftex.def
|
||||||
|
File: pdftex.def 2020/10/05 v1.2a Graphics/color driver for pdftex
|
||||||
|
))
|
||||||
|
\Gin@req@height=\dimen148
|
||||||
|
\Gin@req@width=\dimen149
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/geometry/geometry.sty
|
||||||
|
Package: geometry 2020/01/02 v5.9 Page Geometry
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/iftex/ifvtex.sty
|
||||||
|
Package: ifvtex 2019/10/25 v1.7 ifvtex legacy package. Use iftex instead.
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/iftex/iftex.sty
|
||||||
|
Package: iftex 2022/02/03 v1.0f TeX engine tests
|
||||||
|
))
|
||||||
|
\Gm@cnth=\count271
|
||||||
|
\Gm@cntv=\count272
|
||||||
|
\c@Gm@tempcnt=\count273
|
||||||
|
\Gm@bindingoffset=\dimen150
|
||||||
|
\Gm@wd@mp=\dimen151
|
||||||
|
\Gm@odd@mp=\dimen152
|
||||||
|
\Gm@even@mp=\dimen153
|
||||||
|
\Gm@layoutwidth=\dimen154
|
||||||
|
\Gm@layoutheight=\dimen155
|
||||||
|
\Gm@layouthoffset=\dimen156
|
||||||
|
\Gm@layoutvoffset=\dimen157
|
||||||
|
\Gm@dimlist=\toks28
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/booktabs/booktabs.sty
|
||||||
|
Package: booktabs 2020/01/12 v1.61803398 Publication quality tables
|
||||||
|
\heavyrulewidth=\dimen158
|
||||||
|
\lightrulewidth=\dimen159
|
||||||
|
\cmidrulewidth=\dimen160
|
||||||
|
\belowrulesep=\dimen161
|
||||||
|
\belowbottomsep=\dimen162
|
||||||
|
\aboverulesep=\dimen163
|
||||||
|
\abovetopsep=\dimen164
|
||||||
|
\cmidrulesep=\dimen165
|
||||||
|
\cmidrulekern=\dimen166
|
||||||
|
\defaultaddspace=\dimen167
|
||||||
|
\@cmidla=\count274
|
||||||
|
\@cmidlb=\count275
|
||||||
|
\@aboverulesep=\dimen168
|
||||||
|
\@belowrulesep=\dimen169
|
||||||
|
\@thisruleclass=\count276
|
||||||
|
\@lastruleclass=\count277
|
||||||
|
\@thisrulewidth=\dimen170
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/l3backend/l3backend-pdftex.def
|
||||||
|
File: l3backend-pdftex.def 2022-02-07 L3 backend support: PDF output (pdfTeX)
|
||||||
|
\l__color_backend_stack_int=\count278
|
||||||
|
\l__pdf_internal_box=\box52
|
||||||
|
)
|
||||||
|
(./boundary_restriction_structure.aux)
|
||||||
|
\openout1 = `boundary_restriction_structure.aux'.
|
||||||
|
|
||||||
|
LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 19.
|
||||||
|
LaTeX Font Info: ... okay on input line 19.
|
||||||
|
LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 19.
|
||||||
|
LaTeX Font Info: ... okay on input line 19.
|
||||||
|
LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 19.
|
||||||
|
LaTeX Font Info: ... okay on input line 19.
|
||||||
|
LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 19.
|
||||||
|
LaTeX Font Info: ... okay on input line 19.
|
||||||
|
LaTeX Font Info: Checking defaults for TS1/cmr/m/n on input line 19.
|
||||||
|
LaTeX Font Info: ... okay on input line 19.
|
||||||
|
LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 19.
|
||||||
|
LaTeX Font Info: ... okay on input line 19.
|
||||||
|
LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 19.
|
||||||
|
LaTeX Font Info: ... okay on input line 19.
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/context/base/mkii/supp-pdf.mkii
|
||||||
|
[Loading MPS to PDF converter (version 2006.09.02).]
|
||||||
|
\scratchcounter=\count279
|
||||||
|
\scratchdimen=\dimen171
|
||||||
|
\scratchbox=\box53
|
||||||
|
\nofMPsegments=\count280
|
||||||
|
\nofMParguments=\count281
|
||||||
|
\everyMPshowfont=\toks29
|
||||||
|
\MPscratchCnt=\count282
|
||||||
|
\MPscratchDim=\dimen172
|
||||||
|
\MPnumerator=\count283
|
||||||
|
\makeMPintoPDFobject=\count284
|
||||||
|
\everyMPtoPDFconversion=\toks30
|
||||||
|
) (/usr/local/texlive/2022/texmf-dist/tex/latex/epstopdf-pkg/epstopdf-base.sty
|
||||||
|
Package: epstopdf-base 2020-01-24 v2.11 Base part for package epstopdf
|
||||||
|
Package epstopdf-base Info: Redefining graphics rule for `.eps' on input line 4
|
||||||
|
85.
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/latexconfig/epstopdf-sys.cfg
|
||||||
|
File: epstopdf-sys.cfg 2010/07/13 v1.3 Configuration of (r)epstopdf for TeX Liv
|
||||||
|
e
|
||||||
|
))
|
||||||
|
*geometry* driver: auto-detecting
|
||||||
|
*geometry* detected driver: pdftex
|
||||||
|
*geometry* verbose mode - [ preamble ] result:
|
||||||
|
* driver: pdftex
|
||||||
|
* paper: <default>
|
||||||
|
* layout: <same size as paper>
|
||||||
|
* layoutoffset:(h,v)=(0.0pt,0.0pt)
|
||||||
|
* modes:
|
||||||
|
* h-part:(L,W,R)=(72.26999pt, 469.75502pt, 72.26999pt)
|
||||||
|
* v-part:(T,H,B)=(72.26999pt, 650.43001pt, 72.26999pt)
|
||||||
|
* \paperwidth=614.295pt
|
||||||
|
* \paperheight=794.96999pt
|
||||||
|
* \textwidth=469.75502pt
|
||||||
|
* \textheight=650.43001pt
|
||||||
|
* \oddsidemargin=0.0pt
|
||||||
|
* \evensidemargin=0.0pt
|
||||||
|
* \topmargin=-37.0pt
|
||||||
|
* \headheight=12.0pt
|
||||||
|
* \headsep=25.0pt
|
||||||
|
* \topskip=11.0pt
|
||||||
|
* \footskip=30.0pt
|
||||||
|
* \marginparwidth=59.0pt
|
||||||
|
* \marginparsep=10.0pt
|
||||||
|
* \columnsep=10.0pt
|
||||||
|
* \skip\footins=10.0pt plus 4.0pt minus 2.0pt
|
||||||
|
* \hoffset=0.0pt
|
||||||
|
* \voffset=0.0pt
|
||||||
|
* \mag=1000
|
||||||
|
* \@twocolumnfalse
|
||||||
|
* \@twosidefalse
|
||||||
|
* \@mparswitchfalse
|
||||||
|
* \@reversemarginfalse
|
||||||
|
* (1in=72.27pt=25.4mm, 1cm=28.453pt)
|
||||||
|
|
||||||
|
LaTeX Font Info: Trying to load font information for U+msa on input line 20.
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsa.fd
|
||||||
|
File: umsa.fd 2013/01/14 v3.01 AMS symbols A
|
||||||
|
)
|
||||||
|
LaTeX Font Info: Trying to load font information for U+msb on input line 20.
|
||||||
|
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsb.fd
|
||||||
|
File: umsb.fd 2013/01/14 v3.01 AMS symbols B
|
||||||
|
) [1
|
||||||
|
|
||||||
|
{/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] [2] [3]
|
||||||
|
|
||||||
|
(./boundary_restriction_structure.aux) )
|
||||||
|
Here is how much of TeX's memory you used:
|
||||||
|
3268 strings out of 478268
|
||||||
|
48713 string characters out of 5846347
|
||||||
|
350712 words of memory out of 5000000
|
||||||
|
21451 multiletter control sequences out of 15000+600000
|
||||||
|
481419 words of font info for 73 fonts, out of 8000000 for 9000
|
||||||
|
1141 hyphenation exceptions out of 8191
|
||||||
|
55i,8n,62p,247b,208s stack positions out of 10000i,1000n,20000p,200000b,200000s
|
||||||
|
</usr/local/texlive/2022/texmf-dist/fon
|
||||||
|
ts/type1/public/amsfonts/cm/cmbx10.pfb></usr/local/texlive/2022/texmf-dist/font
|
||||||
|
s/type1/public/amsfonts/cm/cmbx12.pfb></usr/local/texlive/2022/texmf-dist/fonts
|
||||||
|
/type1/public/amsfonts/cm/cmbxti10.pfb></usr/local/texlive/2022/texmf-dist/font
|
||||||
|
s/type1/public/amsfonts/cm/cmex10.pfb></usr/local/texlive/2022/texmf-dist/fonts
|
||||||
|
/type1/public/amsfonts/cm/cmmi10.pfb></usr/local/texlive/2022/texmf-dist/fonts/
|
||||||
|
type1/public/amsfonts/cm/cmmi12.pfb></usr/local/texlive/2022/texmf-dist/fonts/t
|
||||||
|
ype1/public/amsfonts/cm/cmmi6.pfb></usr/local/texlive/2022/texmf-dist/fonts/typ
|
||||||
|
e1/public/amsfonts/cm/cmmi8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1
|
||||||
|
/public/amsfonts/cm/cmr10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/p
|
||||||
|
ublic/amsfonts/cm/cmr12.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/pub
|
||||||
|
lic/amsfonts/cm/cmr17.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/publi
|
||||||
|
c/amsfonts/cm/cmr8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/a
|
||||||
|
msfonts/cm/cmss8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/ams
|
||||||
|
fonts/cm/cmsy10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsf
|
||||||
|
onts/cm/cmsy8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfon
|
||||||
|
ts/cm/cmti10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfont
|
||||||
|
s/cm/cmtt10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts
|
||||||
|
/symbols/msbm10.pfb>
|
||||||
|
Output written on boundary_restriction_structure.pdf (3 pages, 218332 bytes).
|
||||||
|
PDF statistics:
|
||||||
|
104 PDF objects out of 1000 (max. 8388607)
|
||||||
|
62 compressed objects within 1 object stream
|
||||||
|
0 named destinations out of 1000 (max. 500000)
|
||||||
|
1 words of extra memory for PDF output out of 10000 (max. 10000000)
|
||||||
|
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
\documentclass[11pt]{article}
|
||||||
|
\usepackage{amsmath,amssymb,amsthm}
|
||||||
|
\usepackage{graphicx}
|
||||||
|
\usepackage{geometry}
|
||||||
|
\usepackage{booktabs}
|
||||||
|
\geometry{margin=1in}
|
||||||
|
|
||||||
|
\title{Heawood boundary restriction sets:\\
|
||||||
|
zonotope structure and the $2^{n-2}$ constraint floor}
|
||||||
|
\author{}
|
||||||
|
\date{}
|
||||||
|
|
||||||
|
\newtheorem*{obs}{Observation}
|
||||||
|
\newtheorem*{prop}{Proposition}
|
||||||
|
\newtheorem*{conj}{Conjecture}
|
||||||
|
\newtheorem*{lem}{Lemma}
|
||||||
|
\newtheorem*{verify}{Empirical check}
|
||||||
|
|
||||||
|
\begin{document}
|
||||||
|
\maketitle
|
||||||
|
|
||||||
|
This note records the empirical structure of the Heawood boundary
|
||||||
|
restriction sets studied in \texttt{paper.tex}, and a clean
|
||||||
|
\emph{maximal-constraint} result. All claims below are backed by the
|
||||||
|
experiments in \texttt{experiments/} (filenames given inline). Sequences
|
||||||
|
live in $(\mathbb{Z}/3)^{\,\cdot}$, displayed in $\{0,1,-1\}$.
|
||||||
|
|
||||||
|
\section*{Setup}
|
||||||
|
|
||||||
|
Fix a triangulated disk $D$ with boundary cycle $C = (v_0,\dots,v_{n-1})$.
|
||||||
|
A Heawood face-labelling is $\lambda : \{\text{faces of }D\} \to \{+1,-1\}$,
|
||||||
|
with induced vertex value $\lambda^{*}(v) = \sum_{f \ni v}\lambda(f) \bmod 3$.
|
||||||
|
The achievable outer set is
|
||||||
|
\[
|
||||||
|
\Phi(D) \;=\; \bigl\{\, (\lambda^{*}(v_0),\dots,\lambda^{*}(v_{n-1}))
|
||||||
|
\;:\; \lambda \in \{+1,-1\}^{F(D)},\;
|
||||||
|
\lambda^{*}(w) \equiv 0 \ \forall\ \text{interior } w \,\bigr\}.
|
||||||
|
\]
|
||||||
|
This is exactly the value the recursive transfer operator produces at
|
||||||
|
$C$ (interior consistency $=$ all descendant gluings performed; boundary
|
||||||
|
deferred). Crucially $\Phi(D)$ depends \emph{only} on the disk
|
||||||
|
triangulation, not on any BFS/tire-tree labelling.
|
||||||
|
|
||||||
|
\section*{1. The restriction sets are zonotopes, not subspaces}
|
||||||
|
|
||||||
|
(\texttt{probe\_RK\_structure.py}.) Writing $\lambda = \mathbf{1}+b$ with
|
||||||
|
$b \in \{0,1\}^F$, the labelling map is $\lambda \mapsto M\mathbf{1}+Mb
|
||||||
|
\pmod 3$, a linear image of the Boolean cube ($M$ the face/vertex
|
||||||
|
incidence matrix). Over $3655$ cluster restriction sets $R_{\mathsf K}$:
|
||||||
|
none was an affine $\mathrm{GF}(3)$ subspace; the map is usually
|
||||||
|
injective, so $|R_{\mathsf K}| = 2^{|F|}$ (a power of $2$ inside the
|
||||||
|
column space of size $3^{\operatorname{rank} M}$); the nowhere-zero
|
||||||
|
constraint $\lambda \neq 0$ shrank the set below the full linear image in
|
||||||
|
\emph{every} case. The only surviving linear structure is
|
||||||
|
$R_{\mathsf K} \subseteq \operatorname{col}(M)$ (cokernel relations such
|
||||||
|
as $\sum_v \lambda^{*}(v) \equiv 0$). So $\Phi$ is a $\mathbb{Z}/3$
|
||||||
|
zonotope: a projected cube, sign-closed but not closed under addition.
|
||||||
|
|
||||||
|
\section*{2. ``Richness'' is not a self-similar invariant}
|
||||||
|
|
||||||
|
(\texttt{transfer\_operator.py}, \texttt{branch\_invariant.py}.) In a
|
||||||
|
homogeneous same-$n$ spoke-only chain the operator saturates: $\Phi$ has
|
||||||
|
full single-position marginals (every interface vertex independently
|
||||||
|
attains all of $\{0,1,-1\}$), and the alternating tire reaches the
|
||||||
|
\emph{entire} space $3^n$. This is an artifact of non-shrinking annuli
|
||||||
|
with no interior constraints. On genuine triangulations the marginal
|
||||||
|
fullness holds for only ${\sim}8\%$ of regions: depth (not branching)
|
||||||
|
shrinks $\Phi$, e.g.\ a region with $|C|=10$ realised only $|\Phi|=400$
|
||||||
|
of $3^{10}\approx 59000$. Only non-emptiness and sign-closure survive,
|
||||||
|
both of which are automatic / equivalent to $4$CT. Hence no abundance
|
||||||
|
(counting) pigeonhole: a working invariant must tolerate \emph{small}
|
||||||
|
$\Phi$.
|
||||||
|
|
||||||
|
\section*{3. The maximal-constraint floor}
|
||||||
|
|
||||||
|
(\texttt{maximally\_constrain.py}.) Minimising $|\Phi(D)|$ over disks with
|
||||||
|
a fixed boundary $n$-cycle:
|
||||||
|
|
||||||
|
\begin{center}
|
||||||
|
\begin{tabular}{ccccc}
|
||||||
|
\toprule
|
||||||
|
$n$ & $4$ & $5$ & $6$ & $7$\\
|
||||||
|
\midrule
|
||||||
|
$\min |\Phi|$ (search) & $4$ & $8$ & $16$ & $32$\\
|
||||||
|
fan, $0$ interior vertices & $4$ & $8$ & $16$ & $32$\\
|
||||||
|
$2^{\,n-2}$ & $4$ & $8$ & $16$ & $32$\\
|
||||||
|
\bottomrule
|
||||||
|
\end{tabular}
|
||||||
|
\end{center}
|
||||||
|
|
||||||
|
A search over $1700{+}$ \emph{validated} triangulated disks per $n$
|
||||||
|
(boundary points in convex but non-cocircular position, random interior
|
||||||
|
points, Delaunay; each checked to have $2k+n-2$ faces and all $n$
|
||||||
|
boundary edges present), together with deep-stacked single-apex chains up
|
||||||
|
to $8$ interior vertices, never beat $2^{n-2}$, and the interior-free
|
||||||
|
triangulation already attains it. (Note: cocircular boundary points
|
||||||
|
produce degenerate Delaunay outputs --- invalid disks missing a boundary
|
||||||
|
edge --- which spuriously report sub-floor values; these are excluded by
|
||||||
|
the validity check.) Counterintuitively, adding interior structure tends
|
||||||
|
to \emph{enlarge} $\Phi$: e.g.\ on the $4$-cycle the central-apex wheel
|
||||||
|
realises $5$ sequences against the fan's $4$, since each interior vertex
|
||||||
|
contributes two faces but only one constraint. Thus:
|
||||||
|
|
||||||
|
\begin{obs}
|
||||||
|
The interior-free triangulation already attains $2^{n-2}$, no search disk
|
||||||
|
beats it, and deep nesting only approaches this value from above ---
|
||||||
|
suggesting it is a floor, with a single trivial tire already maximally
|
||||||
|
constraining. Whether $2^{n-2}$ is a genuine lower bound for \emph{all}
|
||||||
|
disks is the Conjecture below; it is \emph{not} a proven theorem.
|
||||||
|
\end{obs}
|
||||||
|
|
||||||
|
The achievability is transparent: in a fan from $v_0$,
|
||||||
|
\[
|
||||||
|
\sigma_1 = \lambda_1,\quad
|
||||||
|
\sigma_i = \lambda_{i-1}+\lambda_i \ (1<i<n-1),\quad
|
||||||
|
\sigma_{n-1} = \lambda_{n-2},\quad
|
||||||
|
\sigma_0 = \textstyle\sum_j \lambda_j ,
|
||||||
|
\]
|
||||||
|
so $(\lambda_1,\dots,\lambda_{n-2})$ is recoverable from $\sigma$ and the
|
||||||
|
map is injective onto $2^{n-2}$ sequences. The lower bound over
|
||||||
|
\emph{all} disks is the substance:
|
||||||
|
|
||||||
|
\begin{conj}[Boundary degrees of freedom]
|
||||||
|
For every triangulated disk $D$ with boundary $n$-cycle,
|
||||||
|
$|\Phi(D)| \ge 2^{n-2}$. Equivalently, the $n-2$ binary degrees of
|
||||||
|
freedom carried by the boundary-incident faces survive every interior
|
||||||
|
Heawood constraint (which relates only interior-incident faces).
|
||||||
|
\end{conj}
|
||||||
|
|
||||||
|
The minimal set is itself a sign-closed zonotope of size $2^{n-2}$, hull
|
||||||
|
dimension $n-2$, not a $\mathrm{GF}(3)$ subspace --- the same fingerprint
|
||||||
|
as $\S1$.
|
||||||
|
|
||||||
|
\section*{4. A proof programme for the lower bound}
|
||||||
|
|
||||||
|
The lower bound $|\Phi(D)| \ge 2^{n-2}$ reduces, by an exact
|
||||||
|
$\Phi$-preserving reduction, to a single lemma about ``irreducible''
|
||||||
|
disks. Two dead ends bound the search first: \emph{monotonicity is false}
|
||||||
|
--- inserting a degree-$4$ interior vertex can shrink $|\Phi|$ ($6\to5$,
|
||||||
|
$30\to28$; \texttt{monotonicity\_test.py}), so there is no reduce-to-base
|
||||||
|
proof by ``adding vertices only grows $\Phi$''; and \emph{universal
|
||||||
|
toggles are insufficient} --- a flip preserves feasibility for every
|
||||||
|
labelling only if it touches no interior vertex (a \emph{boundary-only}
|
||||||
|
face), and an irreducible disk can have none (the wheel has zero). What
|
||||||
|
does work:
|
||||||
|
|
||||||
|
\begin{lem}[Un-stacking; degree-$3$ removal preserves $\Phi$]
|
||||||
|
\label{lem:unstack}
|
||||||
|
Let $v$ be a degree-$3$ interior vertex of $D$, with link triangle
|
||||||
|
$abc$ and incident faces $(vab),(vbc),(vca)$. Its constraint
|
||||||
|
$\lambda_{vab}+\lambda_{vbc}+\lambda_{vca}\equiv 0 \pmod 3$ over
|
||||||
|
$\{+1,-1\}$ forces the three to a common value $s$, so each of $a,b,c$
|
||||||
|
receives $2s\equiv -s$ from $v$'s star. Let $D'$ delete $v$ and restore
|
||||||
|
$abc$ as one face. Then setting that face to $-s$ reproduces the
|
||||||
|
contribution $-s$ at $a,b,c$, and $s\mapsto -s$ is a bijection on
|
||||||
|
$\{+1,-1\}$. Hence the map is a bijection between feasible labellings of
|
||||||
|
$D$ and of $D'$ preserving every boundary value and interior constraint,
|
||||||
|
so
|
||||||
|
\[
|
||||||
|
\Phi(D) = \Phi(D'), \qquad k(D') = k(D)-1 .
|
||||||
|
\]
|
||||||
|
\end{lem}
|
||||||
|
|
||||||
|
\begin{verify}\textnormal{(\texttt{monotonicity\_test.py})} Degree-$3$
|
||||||
|
insertion gave exact equality in $8884/8884$ trials.\end{verify}
|
||||||
|
|
||||||
|
\begin{lem}[Base case; ear-peeling]
|
||||||
|
\label{lem:base}
|
||||||
|
If $D$ has no interior vertices ($k=0$) then $|\Phi(D)| = 2^{n-2}$. A
|
||||||
|
polygon triangulation has an \emph{ear} $(v_{i-1},v_i,v_{i+1})$ with
|
||||||
|
$v_i$ of face-degree $1$, so $\sigma_{v_i}=\lambda_{\mathrm{ear}}$ reads
|
||||||
|
the ear label directly; remove it and induct on the $(n-1)$-gon. The
|
||||||
|
boundary map is injective, giving $2^{n-2}$.
|
||||||
|
\end{lem}
|
||||||
|
|
||||||
|
\begin{prop}[Reduction to the irreducible case]
|
||||||
|
\label{prop:reduction}
|
||||||
|
Iterating Lemma~\ref{lem:unstack} terminates ($k$ strictly decreases) at
|
||||||
|
a residue $D^{\ast}$ with no degree-$3$ interior vertex and the same
|
||||||
|
$n$, and $\Phi(D)=\Phi(D^{\ast})$. The residue is either $k=0$, where
|
||||||
|
$|\Phi|=2^{n-2}$ by Lemma~\ref{lem:base}, or \emph{irreducible}: $k\ge1$
|
||||||
|
with every interior vertex of degree $\ge 4$. Hence
|
||||||
|
\[
|
||||||
|
|\Phi(D)| \ge 2^{n-2}
|
||||||
|
\quad\Longleftarrow\quad
|
||||||
|
|\Phi(D^{\ast})| \ge 2^{n-2}\ \text{for every irreducible } D^{\ast}.
|
||||||
|
\]
|
||||||
|
\end{prop}
|
||||||
|
|
||||||
|
\begin{conj}[Irreducible lemma --- the remaining content]
|
||||||
|
\label{conj:irreducible}
|
||||||
|
Every irreducible disk satisfies $|\Phi| \ge 2^{n-2}$; in fact
|
||||||
|
$|\Phi| \ge \tfrac54\cdot 2^{n-2} = 5\cdot 2^{n-4}$.
|
||||||
|
\end{conj}
|
||||||
|
|
||||||
|
\begin{verify}\textnormal{(\texttt{irreducible\_floor.py},
|
||||||
|
\texttt{wheel\_extremal.py})} Over $10^4{+}$ irreducible disks
|
||||||
|
($n=4,5,6$) there were $0$ floor violations and none sat on the floor.
|
||||||
|
The bound $\tfrac54\cdot 2^{n-2}$ is \emph{tight}, attained by a single
|
||||||
|
\textbf{minimal-degree} interior vertex (degree $4$ or $5$, which tie):
|
||||||
|
the ratio $|\Phi|/2^{n-2}$ rises monotonically with the interior vertex's
|
||||||
|
degree, $\tfrac54$ at $d\in\{4,5\}$, $\tfrac{21}{16}$ at $d\in\{6,7\}$,
|
||||||
|
$\ldots$, up to $\tfrac43$ at the wheel $d=n$, where
|
||||||
|
$|\Phi(W_n)|=\lfloor 2^n/3\rfloor$ exactly. So a proof of
|
||||||
|
Conjecture~\ref{conj:irreducible} should be stress-tested against the
|
||||||
|
degree-$4$ patch, the tight case --- \emph{not} the wheel.\end{verify}
|
||||||
|
|
||||||
|
\noindent
|
||||||
|
\emph{Status.} Lemmas~\ref{lem:unstack}--\ref{lem:base} and
|
||||||
|
Proposition~\ref{prop:reduction} are proofs; they settle every disk that
|
||||||
|
un-stacks to $k=0$ (the entire Apollonian class). The whole open content
|
||||||
|
is Conjecture~\ref{conj:irreducible}, with guaranteed $25\%$ slack and a
|
||||||
|
single explicit extremal disk.
|
||||||
|
|
||||||
|
\section*{Consequence for the pigeonhole}
|
||||||
|
|
||||||
|
Even a maximally-constraining child still presents $2^{n-2}$ outer
|
||||||
|
options --- exponential in the interface length $n$. So the gluing
|
||||||
|
problem has the least slack at \emph{short} interfaces ($n=4$ leaves $4$
|
||||||
|
options, $n=3$ leaves $2$), and is easy at long ones. The crux of the
|
||||||
|
Heawood programme therefore lives entirely at short level cycles, exactly
|
||||||
|
where the medial programme's $N(k)$ bound concentrates.
|
||||||
|
|
||||||
|
\medskip
|
||||||
|
\noindent\emph{Meta-remark.} Because $4$CT holds, every actual
|
||||||
|
triangulation glues, so no experiment can exhibit an obstruction (pair or
|
||||||
|
chain). The experiments measure \emph{structure} (zonotope type,
|
||||||
|
constraint floor), not proof difficulty; the difficulty is localised, not
|
||||||
|
removed.
|
||||||
|
|
||||||
|
\end{document}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
\relax
|
||||||
|
\@writefile{toc}{\contentsline {paragraph}{Inner.}{2}{}\protected@file@percent }
|
||||||
|
\@writefile{toc}{\contentsline {paragraph}{Outer.}{2}{}\protected@file@percent }
|
||||||
|
\gdef \@abspage@last{2}
|
||||||
@@ -0,0 +1,296 @@
|
|||||||
|
This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 18 JUN 2026 23:27
|
||||||
|
entering extended mode
|
||||||
|
restricted \write18 enabled.
|
||||||
|
%&-line parsing enabled.
|
||||||
|
**double_contraction_reductio.tex
|
||||||
|
(./double_contraction_reductio.tex
|
||||||
|
LaTeX2e <2021-11-15> patch level 1
|
||||||
|
L3 programming layer <2022-02-24>
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/base/article.cls
|
||||||
|
Document Class: article 2021/10/04 v1.4n Standard LaTeX document class
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/base/size11.clo
|
||||||
|
File: size11.clo 2021/10/04 v1.4n Standard LaTeX file (size option)
|
||||||
|
)
|
||||||
|
\c@part=\count185
|
||||||
|
\c@section=\count186
|
||||||
|
\c@subsection=\count187
|
||||||
|
\c@subsubsection=\count188
|
||||||
|
\c@paragraph=\count189
|
||||||
|
\c@subparagraph=\count190
|
||||||
|
\c@figure=\count191
|
||||||
|
\c@table=\count192
|
||||||
|
\abovecaptionskip=\skip47
|
||||||
|
\belowcaptionskip=\skip48
|
||||||
|
\bibindent=\dimen138
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsmath.sty
|
||||||
|
Package: amsmath 2021/10/15 v2.17l AMS math features
|
||||||
|
\@mathmargin=\skip49
|
||||||
|
|
||||||
|
For additional information on amsmath, use the `?' option.
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amstext.sty
|
||||||
|
Package: amstext 2021/08/26 v2.01 AMS text
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsgen.sty
|
||||||
|
File: amsgen.sty 1999/11/30 v2.0 generic functions
|
||||||
|
\@emptytoks=\toks16
|
||||||
|
\ex@=\dimen139
|
||||||
|
))
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsbsy.sty
|
||||||
|
Package: amsbsy 1999/11/29 v1.2d Bold Symbols
|
||||||
|
\pmbraise@=\dimen140
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsopn.sty
|
||||||
|
Package: amsopn 2021/08/26 v2.02 operator names
|
||||||
|
)
|
||||||
|
\inf@bad=\count193
|
||||||
|
LaTeX Info: Redefining \frac on input line 234.
|
||||||
|
\uproot@=\count194
|
||||||
|
\leftroot@=\count195
|
||||||
|
LaTeX Info: Redefining \overline on input line 399.
|
||||||
|
\classnum@=\count196
|
||||||
|
\DOTSCASE@=\count197
|
||||||
|
LaTeX Info: Redefining \ldots on input line 496.
|
||||||
|
LaTeX Info: Redefining \dots on input line 499.
|
||||||
|
LaTeX Info: Redefining \cdots on input line 620.
|
||||||
|
\Mathstrutbox@=\box50
|
||||||
|
\strutbox@=\box51
|
||||||
|
\big@size=\dimen141
|
||||||
|
LaTeX Font Info: Redeclaring font encoding OML on input line 743.
|
||||||
|
LaTeX Font Info: Redeclaring font encoding OMS on input line 744.
|
||||||
|
\macc@depth=\count198
|
||||||
|
\c@MaxMatrixCols=\count199
|
||||||
|
\dotsspace@=\muskip16
|
||||||
|
\c@parentequation=\count266
|
||||||
|
\dspbrk@lvl=\count267
|
||||||
|
\tag@help=\toks17
|
||||||
|
\row@=\count268
|
||||||
|
\column@=\count269
|
||||||
|
\maxfields@=\count270
|
||||||
|
\andhelp@=\toks18
|
||||||
|
\eqnshift@=\dimen142
|
||||||
|
\alignsep@=\dimen143
|
||||||
|
\tagshift@=\dimen144
|
||||||
|
\tagwidth@=\dimen145
|
||||||
|
\totwidth@=\dimen146
|
||||||
|
\lineht@=\dimen147
|
||||||
|
\@envbody=\toks19
|
||||||
|
\multlinegap=\skip50
|
||||||
|
\multlinetaggap=\skip51
|
||||||
|
\mathdisplay@stack=\toks20
|
||||||
|
LaTeX Info: Redefining \[ on input line 2938.
|
||||||
|
LaTeX Info: Redefining \] on input line 2939.
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/amssymb.sty
|
||||||
|
Package: amssymb 2013/01/14 v3.01 AMS font symbols
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/amsfonts.sty
|
||||||
|
Package: amsfonts 2013/01/14 v3.01 Basic AMSFonts support
|
||||||
|
\symAMSa=\mathgroup4
|
||||||
|
\symAMSb=\mathgroup5
|
||||||
|
LaTeX Font Info: Redeclaring math symbol \hbar on input line 98.
|
||||||
|
LaTeX Font Info: Overwriting math alphabet `\mathfrak' in version `bold'
|
||||||
|
(Font) U/euf/m/n --> U/euf/b/n on input line 106.
|
||||||
|
))
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amscls/amsthm.sty
|
||||||
|
Package: amsthm 2020/05/29 v2.20.6
|
||||||
|
\thm@style=\toks21
|
||||||
|
\thm@bodyfont=\toks22
|
||||||
|
\thm@headfont=\toks23
|
||||||
|
\thm@notefont=\toks24
|
||||||
|
\thm@headpunct=\toks25
|
||||||
|
\thm@preskip=\skip52
|
||||||
|
\thm@postskip=\skip53
|
||||||
|
\thm@headsep=\skip54
|
||||||
|
\dth@everypar=\toks26
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/graphicx.sty
|
||||||
|
Package: graphicx 2021/09/16 v1.2d Enhanced LaTeX Graphics (DPC,SPQR)
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/keyval.sty
|
||||||
|
Package: keyval 2014/10/28 v1.15 key=value parser (DPC)
|
||||||
|
\KV@toks@=\toks27
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/graphics.sty
|
||||||
|
Package: graphics 2021/03/04 v1.4d Standard LaTeX Graphics (DPC,SPQR)
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/trig.sty
|
||||||
|
Package: trig 2021/08/11 v1.11 sin cos tan (DPC)
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics-cfg/graphics.cfg
|
||||||
|
File: graphics.cfg 2016/06/04 v1.11 sample graphics configuration
|
||||||
|
)
|
||||||
|
Package graphics Info: Driver file: pdftex.def on input line 107.
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics-def/pdftex.def
|
||||||
|
File: pdftex.def 2020/10/05 v1.2a Graphics/color driver for pdftex
|
||||||
|
))
|
||||||
|
\Gin@req@height=\dimen148
|
||||||
|
\Gin@req@width=\dimen149
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/geometry/geometry.sty
|
||||||
|
Package: geometry 2020/01/02 v5.9 Page Geometry
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/iftex/ifvtex.sty
|
||||||
|
Package: ifvtex 2019/10/25 v1.7 ifvtex legacy package. Use iftex instead.
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/iftex/iftex.sty
|
||||||
|
Package: iftex 2022/02/03 v1.0f TeX engine tests
|
||||||
|
))
|
||||||
|
\Gm@cnth=\count271
|
||||||
|
\Gm@cntv=\count272
|
||||||
|
\c@Gm@tempcnt=\count273
|
||||||
|
\Gm@bindingoffset=\dimen150
|
||||||
|
\Gm@wd@mp=\dimen151
|
||||||
|
\Gm@odd@mp=\dimen152
|
||||||
|
\Gm@even@mp=\dimen153
|
||||||
|
\Gm@layoutwidth=\dimen154
|
||||||
|
\Gm@layoutheight=\dimen155
|
||||||
|
\Gm@layouthoffset=\dimen156
|
||||||
|
\Gm@layoutvoffset=\dimen157
|
||||||
|
\Gm@dimlist=\toks28
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/booktabs/booktabs.sty
|
||||||
|
Package: booktabs 2020/01/12 v1.61803398 Publication quality tables
|
||||||
|
\heavyrulewidth=\dimen158
|
||||||
|
\lightrulewidth=\dimen159
|
||||||
|
\cmidrulewidth=\dimen160
|
||||||
|
\belowrulesep=\dimen161
|
||||||
|
\belowbottomsep=\dimen162
|
||||||
|
\aboverulesep=\dimen163
|
||||||
|
\abovetopsep=\dimen164
|
||||||
|
\cmidrulesep=\dimen165
|
||||||
|
\cmidrulekern=\dimen166
|
||||||
|
\defaultaddspace=\dimen167
|
||||||
|
\@cmidla=\count274
|
||||||
|
\@cmidlb=\count275
|
||||||
|
\@aboverulesep=\dimen168
|
||||||
|
\@belowrulesep=\dimen169
|
||||||
|
\@thisruleclass=\count276
|
||||||
|
\@lastruleclass=\count277
|
||||||
|
\@thisrulewidth=\dimen170
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/l3backend/l3backend-pdftex.def
|
||||||
|
File: l3backend-pdftex.def 2022-02-07 L3 backend support: PDF output (pdfTeX)
|
||||||
|
\l__color_backend_stack_int=\count278
|
||||||
|
\l__pdf_internal_box=\box52
|
||||||
|
)
|
||||||
|
No file double_contraction_reductio.aux.
|
||||||
|
\openout1 = `double_contraction_reductio.aux'.
|
||||||
|
|
||||||
|
LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 20.
|
||||||
|
LaTeX Font Info: ... okay on input line 20.
|
||||||
|
LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 20.
|
||||||
|
LaTeX Font Info: ... okay on input line 20.
|
||||||
|
LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 20.
|
||||||
|
LaTeX Font Info: ... okay on input line 20.
|
||||||
|
LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 20.
|
||||||
|
LaTeX Font Info: ... okay on input line 20.
|
||||||
|
LaTeX Font Info: Checking defaults for TS1/cmr/m/n on input line 20.
|
||||||
|
LaTeX Font Info: ... okay on input line 20.
|
||||||
|
LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 20.
|
||||||
|
LaTeX Font Info: ... okay on input line 20.
|
||||||
|
LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 20.
|
||||||
|
LaTeX Font Info: ... okay on input line 20.
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/context/base/mkii/supp-pdf.mkii
|
||||||
|
[Loading MPS to PDF converter (version 2006.09.02).]
|
||||||
|
\scratchcounter=\count279
|
||||||
|
\scratchdimen=\dimen171
|
||||||
|
\scratchbox=\box53
|
||||||
|
\nofMPsegments=\count280
|
||||||
|
\nofMParguments=\count281
|
||||||
|
\everyMPshowfont=\toks29
|
||||||
|
\MPscratchCnt=\count282
|
||||||
|
\MPscratchDim=\dimen172
|
||||||
|
\MPnumerator=\count283
|
||||||
|
\makeMPintoPDFobject=\count284
|
||||||
|
\everyMPtoPDFconversion=\toks30
|
||||||
|
) (/usr/local/texlive/2022/texmf-dist/tex/latex/epstopdf-pkg/epstopdf-base.sty
|
||||||
|
Package: epstopdf-base 2020-01-24 v2.11 Base part for package epstopdf
|
||||||
|
Package epstopdf-base Info: Redefining graphics rule for `.eps' on input line 4
|
||||||
|
85.
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/latexconfig/epstopdf-sys.cfg
|
||||||
|
File: epstopdf-sys.cfg 2010/07/13 v1.3 Configuration of (r)epstopdf for TeX Liv
|
||||||
|
e
|
||||||
|
))
|
||||||
|
*geometry* driver: auto-detecting
|
||||||
|
*geometry* detected driver: pdftex
|
||||||
|
*geometry* verbose mode - [ preamble ] result:
|
||||||
|
* driver: pdftex
|
||||||
|
* paper: <default>
|
||||||
|
* layout: <same size as paper>
|
||||||
|
* layoutoffset:(h,v)=(0.0pt,0.0pt)
|
||||||
|
* modes:
|
||||||
|
* h-part:(L,W,R)=(72.26999pt, 469.75502pt, 72.26999pt)
|
||||||
|
* v-part:(T,H,B)=(72.26999pt, 650.43001pt, 72.26999pt)
|
||||||
|
* \paperwidth=614.295pt
|
||||||
|
* \paperheight=794.96999pt
|
||||||
|
* \textwidth=469.75502pt
|
||||||
|
* \textheight=650.43001pt
|
||||||
|
* \oddsidemargin=0.0pt
|
||||||
|
* \evensidemargin=0.0pt
|
||||||
|
* \topmargin=-37.0pt
|
||||||
|
* \headheight=12.0pt
|
||||||
|
* \headsep=25.0pt
|
||||||
|
* \topskip=11.0pt
|
||||||
|
* \footskip=30.0pt
|
||||||
|
* \marginparwidth=59.0pt
|
||||||
|
* \marginparsep=10.0pt
|
||||||
|
* \columnsep=10.0pt
|
||||||
|
* \skip\footins=10.0pt plus 4.0pt minus 2.0pt
|
||||||
|
* \hoffset=0.0pt
|
||||||
|
* \voffset=0.0pt
|
||||||
|
* \mag=1000
|
||||||
|
* \@twocolumnfalse
|
||||||
|
* \@twosidefalse
|
||||||
|
* \@mparswitchfalse
|
||||||
|
* \@reversemarginfalse
|
||||||
|
* (1in=72.27pt=25.4mm, 1cm=28.453pt)
|
||||||
|
|
||||||
|
LaTeX Font Info: Trying to load font information for U+msa on input line 21.
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsa.fd
|
||||||
|
File: umsa.fd 2013/01/14 v3.01 AMS symbols A
|
||||||
|
)
|
||||||
|
LaTeX Font Info: Trying to load font information for U+msb on input line 21.
|
||||||
|
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsb.fd
|
||||||
|
File: umsb.fd 2013/01/14 v3.01 AMS symbols B
|
||||||
|
) [1
|
||||||
|
|
||||||
|
{/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] [2]
|
||||||
|
(./double_contraction_reductio.aux) )
|
||||||
|
Here is how much of TeX's memory you used:
|
||||||
|
3256 strings out of 478268
|
||||||
|
48463 string characters out of 5846347
|
||||||
|
347695 words of memory out of 5000000
|
||||||
|
21442 multiletter control sequences out of 15000+600000
|
||||||
|
479484 words of font info for 65 fonts, out of 8000000 for 9000
|
||||||
|
1141 hyphenation exceptions out of 8191
|
||||||
|
55i,5n,62p,244b,218s stack positions out of 10000i,1000n,20000p,200000b,200000s
|
||||||
|
{/usr/local/texlive/2022/texmf-dist/fonts/
|
||||||
|
enc/dvips/cm-super/cm-super-ts1.enc}</usr/local/texlive/2022/texmf-dist/fonts/t
|
||||||
|
ype1/public/amsfonts/cm/cmbx10.pfb></usr/local/texlive/2022/texmf-dist/fonts/ty
|
||||||
|
pe1/public/amsfonts/cm/cmbx12.pfb></usr/local/texlive/2022/texmf-dist/fonts/typ
|
||||||
|
e1/public/amsfonts/cm/cmbxti10.pfb></usr/local/texlive/2022/texmf-dist/fonts/ty
|
||||||
|
pe1/public/amsfonts/cm/cmitt10.pfb></usr/local/texlive/2022/texmf-dist/fonts/ty
|
||||||
|
pe1/public/amsfonts/cm/cmmi10.pfb></usr/local/texlive/2022/texmf-dist/fonts/typ
|
||||||
|
e1/public/amsfonts/cm/cmmi8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1
|
||||||
|
/public/amsfonts/cm/cmr10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/p
|
||||||
|
ublic/amsfonts/cm/cmr17.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/pub
|
||||||
|
lic/amsfonts/cm/cmr8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public
|
||||||
|
/amsfonts/cm/cmss8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/a
|
||||||
|
msfonts/cm/cmsy10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/am
|
||||||
|
sfonts/cm/cmsy8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsf
|
||||||
|
onts/cm/cmti10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfo
|
||||||
|
nts/symbols/msam10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/c
|
||||||
|
m-super/sfrm1095.pfb>
|
||||||
|
Output written on double_contraction_reductio.pdf (2 pages, 176278 bytes).
|
||||||
|
PDF statistics:
|
||||||
|
87 PDF objects out of 1000 (max. 8388607)
|
||||||
|
52 compressed objects within 1 object stream
|
||||||
|
0 named destinations out of 1000 (max. 500000)
|
||||||
|
1 words of extra memory for PDF output out of 10000 (max. 10000000)
|
||||||
|
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
\documentclass[11pt]{article}
|
||||||
|
\usepackage{amsmath,amssymb,amsthm}
|
||||||
|
\usepackage{graphicx}
|
||||||
|
\usepackage{geometry}
|
||||||
|
\usepackage{booktabs}
|
||||||
|
\geometry{margin=1in}
|
||||||
|
|
||||||
|
\title{A double-contraction reductio at a degree-5 vertex\\
|
||||||
|
via Kempe chains as Heawood face-chains}
|
||||||
|
\author{}
|
||||||
|
\date{}
|
||||||
|
|
||||||
|
\newtheorem*{obs}{Observation}
|
||||||
|
\newtheorem*{prop}{Proposition}
|
||||||
|
\newtheorem*{conj}{Conjecture}
|
||||||
|
\newtheorem*{lem}{Lemma}
|
||||||
|
\newtheorem*{claim}{Claim}
|
||||||
|
\newtheorem*{remk}{Remark}
|
||||||
|
|
||||||
|
\begin{document}
|
||||||
|
\maketitle
|
||||||
|
|
||||||
|
\emph{Status: exploratory strategy note. Records a proof skeleton and the
|
||||||
|
single lemma it reduces to; nothing here is proved. Companion to
|
||||||
|
\texttt{boundary\_restriction\_structure.tex} and \texttt{paper.tex}.}
|
||||||
|
|
||||||
|
\section*{Setup: the move}
|
||||||
|
|
||||||
|
Let $G$ be a minimal counterexample to the Four Colour Theorem (a smallest
|
||||||
|
triangulation with no proper $4$-vertex-colouring). By Euler $G$ has a
|
||||||
|
vertex $v$ of degree $\le 5$; degrees $\le 4$ are classically reducible, so
|
||||||
|
take $\deg(v)=5$. The link of $v$ is a pentagon $a,x,b,y,z$ (consecutive
|
||||||
|
neighbours adjacent). For a non-adjacent (diagonal) pair, say $a,b$, the
|
||||||
|
\emph{double contraction} contracts both edges $(a,v)$ and $(b,v)$,
|
||||||
|
identifying $a=v=b$ into a single vertex. There are $5$ diagonal pairs.
|
||||||
|
|
||||||
|
The double contraction is a proper minor with two fewer vertices, hence a
|
||||||
|
smaller planar graph $G'$; by minimality $G'$ is $4$-colourable. It stays a
|
||||||
|
simple triangulation iff each contracted edge is non-separating (its
|
||||||
|
endpoints have exactly two common neighbours).
|
||||||
|
|
||||||
|
\section*{Kempe chains as Heawood face-chains}
|
||||||
|
|
||||||
|
Work in the cubic dual $G'$ (vertices $=$ faces of $G$, so the Heawood
|
||||||
|
$\pm1$ labels sit on dual vertices). Tait-colour the edges with the three
|
||||||
|
nonzero Klein-4 elements $\{a,b,c\}$ (edge colour $=$ colour-difference
|
||||||
|
across the primal edge). Then:
|
||||||
|
|
||||||
|
\begin{obs}[Kempe chain $=$ Tait cycle $=$ flip-set]
|
||||||
|
A primal Kempe chain (component of two vertex-colour classes of $G$)
|
||||||
|
corresponds to a connected component of the two-edge-colour subgraph
|
||||||
|
$\{a,b\}$ of the dual. That subgraph is $2$-regular, so it is a disjoint
|
||||||
|
union of cycles; each such cycle alternates $a,b,a,b,\dots$ and is therefore
|
||||||
|
\textbf{always even}. The cycle is literally a cyclic chain of faces of $G$.
|
||||||
|
A Kempe swap reverses the rotational order $(a,b,c)$ at exactly the vertices
|
||||||
|
on the cycle, so the set of faces whose Heawood sign \emph{flips} under the
|
||||||
|
swap is precisely the Kempe chain.
|
||||||
|
\end{obs}
|
||||||
|
|
||||||
|
\begin{obs}[Sign alternation $=$ same-side rule]
|
||||||
|
Even-ness does \emph{not} force the Heawood signs to alternate along the
|
||||||
|
cycle. At each cycle vertex the third (off-cycle, colour-$c$) edge points
|
||||||
|
either inside or outside the region bounded by the cycle. For consecutive
|
||||||
|
vertices $A,B$:
|
||||||
|
\[
|
||||||
|
\text{same side (both in / both out)} \Rightarrow \text{signs flip;}
|
||||||
|
\qquad
|
||||||
|
\text{opposite side} \Rightarrow \text{signs repeat.}
|
||||||
|
\]
|
||||||
|
(Local chirality computation: the four in/out cases give
|
||||||
|
$+,-$ / $-,+$ / $+,+$ / $-,-$.) Hence the Heawood sign sequence around a
|
||||||
|
Tait cycle is determined, up to one global sign, by the in/out pattern of
|
||||||
|
the third edges; it is fully alternating iff there are zero side-switches,
|
||||||
|
i.e.\ all third edges lie on one side.
|
||||||
|
\end{obs}
|
||||||
|
|
||||||
|
\section*{The transport hypothesis (L1)}
|
||||||
|
|
||||||
|
\begin{conj}[Chain transport, claimed for all triangulations]
|
||||||
|
The double contraction preserves the relevant (crossing) Kempe/Heawood
|
||||||
|
chain structure as it propagates up the nested tire decomposition: the two
|
||||||
|
chains anchored at the pentagon survive, consistently, through every tire
|
||||||
|
interface above $v$.
|
||||||
|
\end{conj}
|
||||||
|
|
||||||
|
\section*{The reductio (two nested contradictions)}
|
||||||
|
|
||||||
|
We are \emph{not} claiming $G'$ is uncolourable, and we are not claiming
|
||||||
|
intertwined Kempe chains are impossible (they occur, e.g.\ in the Errera
|
||||||
|
graph). The reducible object is \emph{forced} intertwining.
|
||||||
|
|
||||||
|
\paragraph{Inner.} Suppose intertwining is the \emph{only} way to colour
|
||||||
|
$G'$ (every colouring is intertwined at $v$). By transport the two crossing
|
||||||
|
chains pass through every interface above; if uncrossing is excluded at each
|
||||||
|
interface, the tire restriction relations $R_{\mathsf K}$ collapse to their
|
||||||
|
``crossed-only'' sub-relations. Contract each interface \textbf{along the
|
||||||
|
chain} to obtain a strictly smaller triangulation $H''$. If the crossed-only
|
||||||
|
sub-relations admit no compatible gluing on $H''$, then $H''$ is
|
||||||
|
uncolourable and smaller than $G$ --- contradicting minimality.
|
||||||
|
|
||||||
|
\paragraph{Outer.} Therefore $G'$ admits a non-intertwined colouring; it
|
||||||
|
uncrosses at $v$, frees a colour for $v$, and lifts to a colouring of $G$ ---
|
||||||
|
contradicting that $G$ is a counterexample. \hfill$\square$ (modulo the
|
||||||
|
lemma below)
|
||||||
|
|
||||||
|
\section*{What it all reduces to}
|
||||||
|
|
||||||
|
\begin{claim}[the only open content]
|
||||||
|
Under the crossed-only collapse, the surviving Heawood boundary sequences at
|
||||||
|
a (forced-short) tire interface have empty pointwise-negation gluing on
|
||||||
|
$H''$ --- not merely small, genuinely empty.
|
||||||
|
\end{claim}
|
||||||
|
|
||||||
|
This is where the $2^{n-2}$ constraint floor and the mod-3 side-pattern must
|
||||||
|
do real work: the floor guarantees that \emph{smallness alone} never empties
|
||||||
|
a relation, so the emptiness must come from the chains pinning the
|
||||||
|
sub-relation down, not from short interface length.
|
||||||
|
|
||||||
|
\section*{Open points / to pin next}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\item \textbf{Define $H''$ precisely.} Current candidate: contract each tire
|
||||||
|
interface along the transported chain (the global analogue of the local
|
||||||
|
double contraction). Confirm this is a definite, strictly smaller
|
||||||
|
triangulation.
|
||||||
|
\item \textbf{Lift is not automatic.} ``Non-intertwined'' must be defined as
|
||||||
|
``admits a swap collapsing the pentagon $a,x,b,y,z$ to $\le 3$ colours,''
|
||||||
|
or freeing $v$ fails even after uncrossing (with $c(a)=c(b)$ the pentagon
|
||||||
|
can still show $4$ colours).
|
||||||
|
\item \textbf{Errera oracle.} Run the whole construction against the Errera
|
||||||
|
graph (and Fritsch / Kittell). These are colourable but Kempe-intertwined;
|
||||||
|
the hypothesis ``every colouring intertwined'' must \emph{fail} for them,
|
||||||
|
so the construction must decline. \emph{Why} it declines is exactly the
|
||||||
|
mechanism the Claim must deny in the counterexample case.
|
||||||
|
\item \textbf{Prove or break L1} as a statement about all triangulations.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\end{document}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
\relax
|
||||||
|
\citation{bauerfeld-nested-tires}
|
||||||
|
\citation{bauerfeld-nested-tires}
|
||||||
|
\citation{bauerfeld-nested-tires}
|
||||||
|
\citation{bauerfeld-nested-tires}
|
||||||
|
\citation{bauerfeld-nested-tires}
|
||||||
|
\citation{bauerfeld-nested-tires}
|
||||||
|
\citation{Heawood1898}
|
||||||
|
\citation{bauerfeld-medial-tires}
|
||||||
|
\citation{bauerfeld-nested-tires}
|
||||||
|
\@writefile{toc}{\contentsline {section}{\tocsection {}{1}{Introduction}}{1}{}\protected@file@percent }
|
||||||
|
\citation{bauerfeld-nested-tires}
|
||||||
|
\citation{bauerfeld-nested-tires}
|
||||||
|
\@writefile{toc}{\contentsline {section}{\tocsection {}{2}{Connected tire clusters}}{2}{}\protected@file@percent }
|
||||||
|
\newlabel{sec:tire-clusters}{{2}{2}}
|
||||||
|
\newlabel{lem:same-depth-vertex-meet}{{2.1}{2}}
|
||||||
|
\newlabel{def:connected-tire-cluster}{{2.2}{2}}
|
||||||
|
\newlabel{rem:cluster-cut-vertices}{{2.3}{2}}
|
||||||
|
\newlabel{prop:two-clusters-per-vertex}{{2.4}{2}}
|
||||||
|
\citation{bauerfeld-nested-tires}
|
||||||
|
\citation{bauerfeld-nested-tires}
|
||||||
|
\@writefile{toc}{\contentsline {section}{\tocsection {}{3}{Heawood restrictions on the tire dual}}{3}{}\protected@file@percent }
|
||||||
|
\newlabel{sec:heawood-restrictions}{{3}{3}}
|
||||||
|
\newlabel{def:heawood-labelling}{{3.1}{3}}
|
||||||
|
\newlabel{rem:no-interior-constraint}{{3.2}{3}}
|
||||||
|
\newlabel{def:boundary-sequences}{{3.3}{3}}
|
||||||
|
\newlabel{def:heawood-compatible}{{3.4}{3}}
|
||||||
|
\citation{Heawood1898}
|
||||||
|
\newlabel{rem:compat-is-heawood}{{3.5}{4}}
|
||||||
|
\newlabel{eq:heawood-face-sum-dual}{{3.1}{4}}
|
||||||
|
\@writefile{toc}{\contentsline {subsection}{\tocsubsection {}{}{Why the programme runs between nested clusters}}{4}{}\protected@file@percent }
|
||||||
|
\newlabel{prop:two-sided-decomposition}{{3.6}{4}}
|
||||||
|
\citation{bauerfeld-nested-tires}
|
||||||
|
\newlabel{rem:why-clusters}{{3.7}{5}}
|
||||||
|
\newlabel{conj:heawood-chain-pigeonhole}{{3.8}{5}}
|
||||||
|
\newlabel{conj:heawood-route-fct}{{3.9}{5}}
|
||||||
|
\@writefile{toc}{\contentsline {section}{\tocsection {}{4}{The constraint floor}}{6}{}\protected@file@percent }
|
||||||
|
\newlabel{sec:constraint-floor}{{4}{6}}
|
||||||
|
\newlabel{def:achievable-boundary-set}{{4.1}{6}}
|
||||||
|
\newlabel{prop:attainment}{{4.2}{6}}
|
||||||
|
\newlabel{lem:unstack}{{4.3}{6}}
|
||||||
|
\newlabel{conj:constraint-floor}{{4.4}{6}}
|
||||||
|
\newlabel{rem:floor-status}{{4.5}{6}}
|
||||||
|
\bibcite{Heawood1898}{1}
|
||||||
|
\bibcite{bauerfeld-depth}{2}
|
||||||
|
\bibcite{bauerfeld-nested-tires}{3}
|
||||||
|
\bibcite{bauerfeld-medial-tires}{4}
|
||||||
|
\bibcite{bauerfeld-nested-tire-duals}{5}
|
||||||
|
\newlabel{tocindent-1}{0pt}
|
||||||
|
\newlabel{tocindent0}{14.69437pt}
|
||||||
|
\newlabel{tocindent1}{17.77782pt}
|
||||||
|
\newlabel{tocindent2}{0pt}
|
||||||
|
\newlabel{tocindent3}{0pt}
|
||||||
|
\newlabel{rem:floor-consequences}{{4.6}{7}}
|
||||||
|
\@writefile{toc}{\contentsline {section}{\tocsection {}{}{References}}{7}{}\protected@file@percent }
|
||||||
|
\gdef \@abspage@last{7}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# Fdb version 3
|
||||||
|
["pdflatex"] 1781668577 "paper.tex" "paper.pdf" "paper" 1781668578
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/map/fontname/texfonts.map" 1577235249 3524 cb3e574dea2d1052e39280babc910dc8 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/amsfonts/cmextra/cmex7.tfm" 1246382020 1004 54797486969f23fa377b128694d548df ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/amsfonts/cmextra/cmex8.tfm" 1246382020 988 bdf658c3bfc2d96d3c8b02cfc1c94c20 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/amsfonts/symbols/msam10.tfm" 1246382020 916 f87d7c45f9c908e672703b83b72241a3 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/amsfonts/symbols/msam5.tfm" 1246382020 924 9904cf1d39e9767e7a3622f2a125a565 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/amsfonts/symbols/msam7.tfm" 1246382020 928 2dc8d444221b7a635bb58038579b861a ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/amsfonts/symbols/msbm10.tfm" 1246382020 908 2921f8a10601f252058503cc6570e581 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/amsfonts/symbols/msbm5.tfm" 1246382020 940 75ac932a52f80982a9f8ea75d03a34cf ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/amsfonts/symbols/msbm7.tfm" 1246382020 940 228d6584342e91276bf566bcf9716b83 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/cm/cmbx10.tfm" 1136768653 1328 c834bbb027764024c09d3d2bf908b5f0 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/cm/cmbx8.tfm" 1136768653 1332 1fde11373e221473104d6cc5993f046e ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/cm/cmcsc10.tfm" 1136768653 1300 63a6111ee6274895728663cf4b4e7e81 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/cm/cmmi6.tfm" 1136768653 1512 f21f83efb36853c0b70002322c1ab3ad ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/cm/cmmi8.tfm" 1136768653 1520 eccf95517727cb11801f4f1aee3a21b4 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/cm/cmr6.tfm" 1136768653 1300 b62933e007d01cfd073f79b963c01526 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/cm/cmr8.tfm" 1136768653 1292 21c1c5bfeaebccffdb478fd231a0997d ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/cm/cmsy6.tfm" 1136768653 1116 933a60c408fc0a863a92debe84b2d294 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/cm/cmsy8.tfm" 1136768653 1120 8b7d695260f3cff42e636090a8002094 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/cm/cmti10.tfm" 1136768653 1480 aa8e34af0eb6a2941b776984cf1dfdc4 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/cm/cmti8.tfm" 1136768653 1504 1747189e0441d1c18f3ea56fafc1c480 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmbx10.pfb" 1248133631 34811 78b52f49e893bcba91bd7581cdc144c0 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmbx8.pfb" 1248133631 32166 b0c356b15f19587482a9217ce1d8fa67 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmcsc10.pfb" 1248133631 32001 6aeea3afe875097b1eb0da29acd61e28 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi10.pfb" 1248133631 36299 5f9df58c2139e7edcf37c8fca4bd384d ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi7.pfb" 1248133631 36281 c355509802a035cadc5f15869451dcee ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb" 1248133631 35752 024fb6c41858982481f6968b5fc26508 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr7.pfb" 1248133631 32762 224316ccc9ad3ca0423a14971cfa7fc1 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmr8.pfb" 1248133631 32726 0a1aea6fcd6468ee2cf64d891f5c43c8 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy10.pfb" 1248133631 32569 5e5ddc8df908dea60932f3c484a54c0d ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy7.pfb" 1248133631 32716 08e384dc442464e7285e891af9f45947 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti10.pfb" 1248133631 37944 359e864bd06cde3b1cf57bb20757fb06 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti8.pfb" 1248133631 35660 fb24af7afbadb71801619f1415838111 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/tex/context/base/mkii/supp-pdf.mkii" 1461363279 71627 94eb9990bed73c364d7f53f960cc8c5b ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/tex/latex/amscls/amsart.cls" 1591045760 61881 a7369c346c2922a758ae6283cc1ed014 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/amsfonts.sty" 1359763108 5949 3f3fd50a8cc94c3d4cbf4fc66cd3df1c ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/amssymb.sty" 1359763108 13829 94730e64147574077f8ecfea9bb69af4 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsa.fd" 1359763108 961 6518c6525a34feb5e8250ffa91731cff ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsb.fd" 1359763108 961 d02606146ba5601b5645f987c92e6193 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsbsy.sty" 1622667781 2222 da905dc1db75412efd2d8f67739f0596 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsgen.sty" 1622667781 4173 bc0410bcccdff806d6132d3c1ef35481 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsmath.sty" 1636758526 87648 07fbb6e9169e00cb2a2f40b31b2dbf3c ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsopn.sty" 1636758526 4128 8eea906621b6639f7ba476a472036bbe ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amstext.sty" 1636758526 2444 926f379cc60fcf0c6e3fee2223b4370d ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/tex/latex/epstopdf-pkg/epstopdf-base.sty" 1579991033 13886 d1306dcf79a944f6988e688c1785f9ce ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/tex/latex/graphics-cfg/graphics.cfg" 1465944070 1224 978390e9c2234eab29404bc21b268d1e ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/tex/latex/graphics-def/pdftex.def" 1601931164 19103 48d29b6e2a64cb717117ef65f107b404 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/graphics.sty" 1622581934 18399 7e40f80366dffb22c0e7b70517db5cb4 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/graphicx.sty" 1636758526 7996 a8fb260d598dcaf305a7ae7b9c3e3229 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/keyval.sty" 1622581934 2671 4de6781a30211fe0ea4c672e4a2a8166 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/trig.sty" 1636758526 4009 187ea2dc3194cd5a76cd99a8d7a6c4d0 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/tex/latex/l3backend/l3backend-pdftex.def" 1644269979 29921 d0acc05a38bd4aa3af2017f0b7c137ce ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/tex/latex/latexconfig/epstopdf-sys.cfg" 1279039959 678 4792914a8f45be57bb98413425e4c7af ""
|
||||||
|
"/usr/local/texlive/2022/texmf-dist/web2c/texmf.cnf" 1646502317 40171 cdab547de63d26590bebb3baff566530 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map" 1647878959 4410336 7d30a02e9fa9a16d7d1f8d037ba69641 ""
|
||||||
|
"/usr/local/texlive/2022/texmf-var/web2c/pdftex/pdflatex.fmt" 1665017617 2826443 7e98410c533054b636c6470db83a27bc ""
|
||||||
|
"/usr/local/texlive/2022/texmf.cnf" 1647878952 577 209b46be99c9075fd74d4c0369380e8c ""
|
||||||
|
"paper.aux" 1781668577 894 c07070c22299dc3a5b11f2d70e9e6864 "pdflatex"
|
||||||
|
"paper.tex" 1781668572 3984 c8e5ad80c1dfc803df154b6857fc59b0 ""
|
||||||
|
(generated)
|
||||||
|
"paper.aux"
|
||||||
|
"paper.log"
|
||||||
|
"paper.pdf"
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 17 JUN 2026 21:31
|
||||||
|
entering extended mode
|
||||||
|
restricted \write18 enabled.
|
||||||
|
%&-line parsing enabled.
|
||||||
|
**paper.tex
|
||||||
|
(./paper.tex
|
||||||
|
LaTeX2e <2021-11-15> patch level 1
|
||||||
|
L3 programming layer <2022-02-24>
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amscls/amsart.cls
|
||||||
|
Document Class: amsart 2020/05/29 v2.20.6
|
||||||
|
\linespacing=\dimen138
|
||||||
|
\normalparindent=\dimen139
|
||||||
|
\normaltopskip=\skip47
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsmath.sty
|
||||||
|
Package: amsmath 2021/10/15 v2.17l AMS math features
|
||||||
|
\@mathmargin=\skip48
|
||||||
|
|
||||||
|
For additional information on amsmath, use the `?' option.
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amstext.sty
|
||||||
|
Package: amstext 2021/08/26 v2.01 AMS text
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsgen.sty
|
||||||
|
File: amsgen.sty 1999/11/30 v2.0 generic functions
|
||||||
|
\@emptytoks=\toks16
|
||||||
|
\ex@=\dimen140
|
||||||
|
))
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsbsy.sty
|
||||||
|
Package: amsbsy 1999/11/29 v1.2d Bold Symbols
|
||||||
|
\pmbraise@=\dimen141
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsopn.sty
|
||||||
|
Package: amsopn 2021/08/26 v2.02 operator names
|
||||||
|
)
|
||||||
|
\inf@bad=\count185
|
||||||
|
LaTeX Info: Redefining \frac on input line 234.
|
||||||
|
\uproot@=\count186
|
||||||
|
\leftroot@=\count187
|
||||||
|
LaTeX Info: Redefining \overline on input line 399.
|
||||||
|
\classnum@=\count188
|
||||||
|
\DOTSCASE@=\count189
|
||||||
|
LaTeX Info: Redefining \ldots on input line 496.
|
||||||
|
LaTeX Info: Redefining \dots on input line 499.
|
||||||
|
LaTeX Info: Redefining \cdots on input line 620.
|
||||||
|
\Mathstrutbox@=\box50
|
||||||
|
\strutbox@=\box51
|
||||||
|
\big@size=\dimen142
|
||||||
|
LaTeX Font Info: Redeclaring font encoding OML on input line 743.
|
||||||
|
LaTeX Font Info: Redeclaring font encoding OMS on input line 744.
|
||||||
|
\macc@depth=\count190
|
||||||
|
\c@MaxMatrixCols=\count191
|
||||||
|
\dotsspace@=\muskip16
|
||||||
|
\c@parentequation=\count192
|
||||||
|
\dspbrk@lvl=\count193
|
||||||
|
\tag@help=\toks17
|
||||||
|
\row@=\count194
|
||||||
|
\column@=\count195
|
||||||
|
\maxfields@=\count196
|
||||||
|
\andhelp@=\toks18
|
||||||
|
\eqnshift@=\dimen143
|
||||||
|
\alignsep@=\dimen144
|
||||||
|
\tagshift@=\dimen145
|
||||||
|
\tagwidth@=\dimen146
|
||||||
|
\totwidth@=\dimen147
|
||||||
|
\lineht@=\dimen148
|
||||||
|
\@envbody=\toks19
|
||||||
|
\multlinegap=\skip49
|
||||||
|
\multlinetaggap=\skip50
|
||||||
|
\mathdisplay@stack=\toks20
|
||||||
|
LaTeX Info: Redefining \[ on input line 2938.
|
||||||
|
LaTeX Info: Redefining \] on input line 2939.
|
||||||
|
)
|
||||||
|
LaTeX Font Info: Trying to load font information for U+msa on input line 397
|
||||||
|
.
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsa.fd
|
||||||
|
File: umsa.fd 2013/01/14 v3.01 AMS symbols A
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/amsfonts.sty
|
||||||
|
Package: amsfonts 2013/01/14 v3.01 Basic AMSFonts support
|
||||||
|
\symAMSa=\mathgroup4
|
||||||
|
\symAMSb=\mathgroup5
|
||||||
|
LaTeX Font Info: Redeclaring math symbol \hbar on input line 98.
|
||||||
|
LaTeX Font Info: Overwriting math alphabet `\mathfrak' in version `bold'
|
||||||
|
(Font) U/euf/m/n --> U/euf/b/n on input line 106.
|
||||||
|
)
|
||||||
|
\copyins=\insert199
|
||||||
|
\abstractbox=\box52
|
||||||
|
\listisep=\skip51
|
||||||
|
\c@part=\count197
|
||||||
|
\c@section=\count198
|
||||||
|
\c@subsection=\count266
|
||||||
|
\c@subsubsection=\count267
|
||||||
|
\c@paragraph=\count268
|
||||||
|
\c@subparagraph=\count269
|
||||||
|
\c@figure=\count270
|
||||||
|
\c@table=\count271
|
||||||
|
\abovecaptionskip=\skip52
|
||||||
|
\belowcaptionskip=\skip53
|
||||||
|
\captionindent=\dimen149
|
||||||
|
\thm@style=\toks21
|
||||||
|
\thm@bodyfont=\toks22
|
||||||
|
\thm@headfont=\toks23
|
||||||
|
\thm@notefont=\toks24
|
||||||
|
\thm@headpunct=\toks25
|
||||||
|
\thm@preskip=\skip54
|
||||||
|
\thm@postskip=\skip55
|
||||||
|
\thm@headsep=\skip56
|
||||||
|
\dth@everypar=\toks26
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/amssymb.sty
|
||||||
|
Package: amssymb 2013/01/14 v3.01 AMS font symbols
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/graphicx.sty
|
||||||
|
Package: graphicx 2021/09/16 v1.2d Enhanced LaTeX Graphics (DPC,SPQR)
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/keyval.sty
|
||||||
|
Package: keyval 2014/10/28 v1.15 key=value parser (DPC)
|
||||||
|
\KV@toks@=\toks27
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/graphics.sty
|
||||||
|
Package: graphics 2021/03/04 v1.4d Standard LaTeX Graphics (DPC,SPQR)
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/trig.sty
|
||||||
|
Package: trig 2021/08/11 v1.11 sin cos tan (DPC)
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics-cfg/graphics.cfg
|
||||||
|
File: graphics.cfg 2016/06/04 v1.11 sample graphics configuration
|
||||||
|
)
|
||||||
|
Package graphics Info: Driver file: pdftex.def on input line 107.
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics-def/pdftex.def
|
||||||
|
File: pdftex.def 2020/10/05 v1.2a Graphics/color driver for pdftex
|
||||||
|
))
|
||||||
|
\Gin@req@height=\dimen150
|
||||||
|
\Gin@req@width=\dimen151
|
||||||
|
)
|
||||||
|
\c@theorem=\count272
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/l3backend/l3backend-pdftex.def
|
||||||
|
File: l3backend-pdftex.def 2022-02-07 L3 backend support: PDF output (pdfTeX)
|
||||||
|
\l__color_backend_stack_int=\count273
|
||||||
|
\l__pdf_internal_box=\box53
|
||||||
|
)
|
||||||
|
(./paper.aux)
|
||||||
|
\openout1 = `paper.aux'.
|
||||||
|
|
||||||
|
LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 27.
|
||||||
|
LaTeX Font Info: ... okay on input line 27.
|
||||||
|
LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 27.
|
||||||
|
LaTeX Font Info: ... okay on input line 27.
|
||||||
|
LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 27.
|
||||||
|
LaTeX Font Info: ... okay on input line 27.
|
||||||
|
LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 27.
|
||||||
|
LaTeX Font Info: ... okay on input line 27.
|
||||||
|
LaTeX Font Info: Checking defaults for TS1/cmr/m/n on input line 27.
|
||||||
|
LaTeX Font Info: ... okay on input line 27.
|
||||||
|
LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 27.
|
||||||
|
LaTeX Font Info: ... okay on input line 27.
|
||||||
|
LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 27.
|
||||||
|
LaTeX Font Info: ... okay on input line 27.
|
||||||
|
LaTeX Font Info: Trying to load font information for U+msa on input line 27.
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsa.fd
|
||||||
|
File: umsa.fd 2013/01/14 v3.01 AMS symbols A
|
||||||
|
)
|
||||||
|
LaTeX Font Info: Trying to load font information for U+msb on input line 27.
|
||||||
|
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsb.fd
|
||||||
|
File: umsb.fd 2013/01/14 v3.01 AMS symbols B
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/context/base/mkii/supp-pdf.mkii
|
||||||
|
[Loading MPS to PDF converter (version 2006.09.02).]
|
||||||
|
\scratchcounter=\count274
|
||||||
|
\scratchdimen=\dimen152
|
||||||
|
\scratchbox=\box54
|
||||||
|
\nofMPsegments=\count275
|
||||||
|
\nofMParguments=\count276
|
||||||
|
\everyMPshowfont=\toks28
|
||||||
|
\MPscratchCnt=\count277
|
||||||
|
\MPscratchDim=\dimen153
|
||||||
|
\MPnumerator=\count278
|
||||||
|
\makeMPintoPDFobject=\count279
|
||||||
|
\everyMPtoPDFconversion=\toks29
|
||||||
|
) (/usr/local/texlive/2022/texmf-dist/tex/latex/epstopdf-pkg/epstopdf-base.sty
|
||||||
|
Package: epstopdf-base 2020-01-24 v2.11 Base part for package epstopdf
|
||||||
|
Package epstopdf-base Info: Redefining graphics rule for `.eps' on input line 4
|
||||||
|
85.
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/latexconfig/epstopdf-sys.cfg
|
||||||
|
File: epstopdf-sys.cfg 2010/07/13 v1.3 Configuration of (r)epstopdf for TeX Liv
|
||||||
|
e
|
||||||
|
))
|
||||||
|
[1{/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map}]
|
||||||
|
[2] [3] [4] [5] [6] [7] (./paper.aux) )
|
||||||
|
Here is how much of TeX's memory you used:
|
||||||
|
3024 strings out of 478268
|
||||||
|
42307 string characters out of 5846347
|
||||||
|
342360 words of memory out of 5000000
|
||||||
|
21070 multiletter control sequences out of 15000+600000
|
||||||
|
477578 words of font info for 59 fonts, out of 8000000 for 9000
|
||||||
|
1302 hyphenation exceptions out of 8191
|
||||||
|
69i,7n,76p,242b,290s stack positions out of 10000i,1000n,20000p,200000b,200000s
|
||||||
|
</usr/local/texlive/2022/texmf-dist/font
|
||||||
|
s/type1/public/amsfonts/cm/cmbx10.pfb></usr/local/texlive/2022/texmf-dist/fonts
|
||||||
|
/type1/public/amsfonts/cm/cmbx8.pfb></usr/local/texlive/2022/texmf-dist/fonts/t
|
||||||
|
ype1/public/amsfonts/cm/cmcsc10.pfb></usr/local/texlive/2022/texmf-dist/fonts/t
|
||||||
|
ype1/public/amsfonts/cm/cmex10.pfb></usr/local/texlive/2022/texmf-dist/fonts/ty
|
||||||
|
pe1/public/amsfonts/cm/cmmi10.pfb></usr/local/texlive/2022/texmf-dist/fonts/typ
|
||||||
|
e1/public/amsfonts/cm/cmmi5.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1
|
||||||
|
/public/amsfonts/cm/cmmi7.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/p
|
||||||
|
ublic/amsfonts/cm/cmr10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/pub
|
||||||
|
lic/amsfonts/cm/cmr5.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public
|
||||||
|
/amsfonts/cm/cmr7.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/am
|
||||||
|
sfonts/cm/cmr8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfo
|
||||||
|
nts/cm/cmss10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfon
|
||||||
|
ts/cm/cmss8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts
|
||||||
|
/cm/cmsy10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/
|
||||||
|
cm/cmsy5.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm
|
||||||
|
/cmsy7.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/c
|
||||||
|
mti10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cm
|
||||||
|
ti8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/symbols
|
||||||
|
/msam10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/sym
|
||||||
|
bols/msbm10.pfb>
|
||||||
|
Output written on paper.pdf (7 pages, 269140 bytes).
|
||||||
|
PDF statistics:
|
||||||
|
128 PDF objects out of 1000 (max. 8388607)
|
||||||
|
78 compressed objects within 1 object stream
|
||||||
|
0 named destinations out of 1000 (max. 500000)
|
||||||
|
1 words of extra memory for PDF output out of 10000 (max. 10000000)
|
||||||
|
|
||||||
@@ -0,0 +1,575 @@
|
|||||||
|
%% filename: amsart-template.tex
|
||||||
|
%% American Mathematical Society
|
||||||
|
%% AMS-LaTeX v.2 template for use with amsart
|
||||||
|
%% ====================================================================
|
||||||
|
|
||||||
|
\documentclass{amsart}
|
||||||
|
|
||||||
|
\usepackage{amssymb}
|
||||||
|
\usepackage{graphicx}
|
||||||
|
|
||||||
|
\newtheorem{theorem}{Theorem}[section]
|
||||||
|
\newtheorem{lemma}[theorem]{Lemma}
|
||||||
|
\newtheorem{corollary}[theorem]{Corollary}
|
||||||
|
\newtheorem{proposition}[theorem]{Proposition}
|
||||||
|
\newtheorem{conjecture}[theorem]{Conjecture}
|
||||||
|
|
||||||
|
\theoremstyle{definition}
|
||||||
|
\newtheorem{definition}[theorem]{Definition}
|
||||||
|
\newtheorem{example}[theorem]{Example}
|
||||||
|
\newtheorem{xca}[theorem]{Exercise}
|
||||||
|
|
||||||
|
\theoremstyle{remark}
|
||||||
|
\newtheorem{remark}[theorem]{Remark}
|
||||||
|
|
||||||
|
\numberwithin{equation}{section}
|
||||||
|
|
||||||
|
\begin{document}
|
||||||
|
|
||||||
|
\title{Heawood Restrictions on Nested Tire Graph Duals}
|
||||||
|
|
||||||
|
% author one information
|
||||||
|
\author{Eric Bauerfeld}
|
||||||
|
\address{}
|
||||||
|
\curraddr{}
|
||||||
|
\email{}
|
||||||
|
\thanks{}
|
||||||
|
|
||||||
|
\subjclass[2010]{Primary }
|
||||||
|
|
||||||
|
\keywords{plane graph, triangulation, plane depth, level edge, dual graph,
|
||||||
|
tire graph, Heawood number}
|
||||||
|
|
||||||
|
\date{}
|
||||||
|
|
||||||
|
\dedicatory{}
|
||||||
|
|
||||||
|
\begin{abstract}
|
||||||
|
%% TODO: abstract. Following \cite{bauerfeld-nested-tires}, which establishes
|
||||||
|
%% the basic vocabulary of tire graphs and dual depth, we study the Heawood
|
||||||
|
%% (mod-3 / face-sum) restrictions imposed on the duals of nested tire graphs.
|
||||||
|
\end{abstract}
|
||||||
|
|
||||||
|
\maketitle
|
||||||
|
|
||||||
|
\section{Introduction}
|
||||||
|
|
||||||
|
A classical theorem of Tait recasts the Four Colour Theorem in dual,
|
||||||
|
edge-colouring terms: a plane triangulation $G$ is properly $4$-vertex-colourable
|
||||||
|
if and only if its dual cubic graph $G'$ is properly $3$-edge-colourable. Thus a
|
||||||
|
minimal counterexample to the Four Colour Theorem -- a smallest triangulation
|
||||||
|
admitting no proper $4$-colouring -- corresponds to a smallest cubic plane graph
|
||||||
|
admitting no proper $3$-edge-colouring.
|
||||||
|
|
||||||
|
This paper continues the series studying that structure through the
|
||||||
|
lens of \emph{nested level duals}. The foundational vocabulary ---
|
||||||
|
level sources, levels, the inner planar dual $G'$ and its dual depth,
|
||||||
|
and tire graphs --- is developed in the companion paper
|
||||||
|
\cite{bauerfeld-nested-tires}; we refer to that paper for those
|
||||||
|
definitions and rely on them throughout. In particular we use,
|
||||||
|
without restating, the notions of:
|
||||||
|
\begin{itemize}
|
||||||
|
\item \emph{level source} $S$ and $G$-vertex levels $\ell_G(v)$;
|
||||||
|
\item the inner planar dual $G'$
|
||||||
|
(\cite[Definition~1.3]{bauerfeld-nested-tires});
|
||||||
|
\item \emph{dual depth} $\delta_G(d_f)$
|
||||||
|
(\cite[Definition~1.4]{bauerfeld-nested-tires});
|
||||||
|
\item \emph{tire graph} $T = (B_{\mathrm{out}}, O, E_{\mathrm{ann}})$
|
||||||
|
with outer/inner boundaries and annular edges
|
||||||
|
(\cite[Definition~1.5]{bauerfeld-nested-tires});
|
||||||
|
\item the \emph{tire-component lemma}
|
||||||
|
(\cite[Lemma~1.8]{bauerfeld-nested-tires}); and
|
||||||
|
\item the \emph{tire-tread partition theorem}
|
||||||
|
(\cite[Theorem~1.9]{bauerfeld-nested-tires}).
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
Throughout, $G = (V, E)$ is a plane maximal planar graph (a triangulation)
|
||||||
|
with a fixed planar embedding $\Pi_G$. We write $|V| = n$, so $|E| = 3n - 6$
|
||||||
|
and $G$ has $2n - 4$ triangular faces.
|
||||||
|
|
||||||
|
The classical input is Heawood's face-sum identity \cite{Heawood1898}:
|
||||||
|
for any proper $3$-edge-colouring of a cubic plane graph $H$, assigning
|
||||||
|
each face of $H$ a number in $\{+1, -1\}$ can be done so that the labels
|
||||||
|
around every vertex of $H$ sum to $0 \pmod 3$. In the triangulation
|
||||||
|
$G$ dual to $H$ this becomes a $\{+1, -1\}$ labelling of the
|
||||||
|
\emph{faces} of $G$ whose incident-face sum at every vertex of $G$
|
||||||
|
vanishes mod $3$. Our aim is to record what this restriction forces
|
||||||
|
along the boundary cycles of a nested tire graph, and to formulate a
|
||||||
|
chain-pigeonhole programme in this Heawood labelling parallel to the
|
||||||
|
medial programme of \cite{bauerfeld-medial-tires}.
|
||||||
|
|
||||||
|
\section{Connected tire clusters}
|
||||||
|
\label{sec:tire-clusters}
|
||||||
|
|
||||||
|
The tire treads at a fixed depth partition the depth-$d$ faces of $G$
|
||||||
|
\cite{bauerfeld-nested-tires}, but distinct depth-$d$ tires need not be
|
||||||
|
vertex-disjoint: a single vertex of $G$ may lie on the source-side
|
||||||
|
boundary of several depth-$d$ tires at once (this occurs exactly when
|
||||||
|
the depth-$d$ faces around that vertex are split into more than one arc
|
||||||
|
by depth-$(d{-}1)$ faces). We organise the depth-$d$ tires by this
|
||||||
|
sharing.
|
||||||
|
|
||||||
|
\begin{lemma}[Same-depth tires meet only in vertices]
|
||||||
|
\label{lem:same-depth-vertex-meet}
|
||||||
|
Let $T \neq T'$ be two distinct tire treads at the same depth $d$ in
|
||||||
|
$\mathcal{T}(G, S)$, arising from connected components $C', C''$ of the
|
||||||
|
depth-$d$ dual subgraph $G'_d$. Then $T$ and $T'$ share no edge of $G$;
|
||||||
|
any intersection $V(T) \cap V(T')$ consists of isolated vertices.
|
||||||
|
\end{lemma}
|
||||||
|
|
||||||
|
\begin{proof}
|
||||||
|
An edge $e$ of $G$ shared by two depth-$d$ annular faces $f_1, f_2$ is,
|
||||||
|
by definition of the inner dual \cite[Definition~1.3]{bauerfeld-nested-tires},
|
||||||
|
a dual edge of $G'$ joining $d_{f_1}$ and $d_{f_2}$; since $\delta(d_{f_1})
|
||||||
|
= \delta(d_{f_2}) = d$, this edge lies in $G'_d$, so $d_{f_1}$ and
|
||||||
|
$d_{f_2}$ belong to the same component of $G'_d$. Hence no edge of $G$
|
||||||
|
is shared by annular faces of two \emph{different} components, and
|
||||||
|
distinct depth-$d$ tires share no edge. Their intersection is therefore
|
||||||
|
a set of isolated vertices.
|
||||||
|
\end{proof}
|
||||||
|
|
||||||
|
\begin{definition}[Connected tire cluster]
|
||||||
|
\label{def:connected-tire-cluster}
|
||||||
|
Fix a nested tire decomposition $\mathcal{T}(G, S)$ and a depth $d$. On
|
||||||
|
the set of depth-$d$ tire treads define the relation
|
||||||
|
\[
|
||||||
|
T \sim T' \quad\Longleftrightarrow\quad V(T) \cap V(T') \neq \varnothing .
|
||||||
|
\]
|
||||||
|
A \emph{connected tire cluster} at depth $d$ is the subgraph of $G$
|
||||||
|
\[
|
||||||
|
\mathsf{K} \;=\; \bigcup_{i} T_i \;\subseteq\; G
|
||||||
|
\]
|
||||||
|
obtained as the union (of underlying plane graphs) of the tires in a
|
||||||
|
single connected component $\{T_i\}$ of the transitive closure of
|
||||||
|
$\sim$. A cluster consisting of a single tire is \emph{trivial}; the
|
||||||
|
connected tire clusters at depth $d$ partition the depth-$d$ tires.
|
||||||
|
\end{definition}
|
||||||
|
|
||||||
|
\begin{remark}
|
||||||
|
\label{rem:cluster-cut-vertices}
|
||||||
|
By Lemma~\ref{lem:same-depth-vertex-meet} the constituent tires of a
|
||||||
|
connected tire cluster are joined only at shared vertices, each of which
|
||||||
|
is a cut vertex of $\mathsf{K}$; a connected tire cluster is thus a
|
||||||
|
``cactus of tires'' and is in general \emph{not} itself a tire graph,
|
||||||
|
since the annulus structure of \cite[Definition~1.5]{bauerfeld-nested-tires}
|
||||||
|
fails at each such pinch. The shared (cut) vertices are precisely the
|
||||||
|
vertices that belong to more than one depth-$d$ tire.
|
||||||
|
\end{remark}
|
||||||
|
|
||||||
|
A single vertex may belong to several tires at one depth --- the
|
||||||
|
high-degree case where its depth-$d$ faces split into many arcs --- so
|
||||||
|
the number of \emph{tires} through a vertex is unbounded. Clustering
|
||||||
|
collapses exactly this multiplicity: all tires through a vertex at a
|
||||||
|
fixed depth share that vertex, hence lie in one cluster. The cluster
|
||||||
|
count is therefore controlled.
|
||||||
|
|
||||||
|
\begin{proposition}[A vertex meets at most two clusters]
|
||||||
|
\label{prop:two-clusters-per-vertex}
|
||||||
|
Every vertex $v \in V(G)$ belongs to at most two connected tire
|
||||||
|
clusters, namely at most one at each of the two consecutive depths
|
||||||
|
$\ell_G(v) - 1$ and $\ell_G(v)$. In particular a source vertex
|
||||||
|
($\ell_G(v) = 0$) belongs to a single cluster.
|
||||||
|
\end{proposition}
|
||||||
|
|
||||||
|
\begin{proof}
|
||||||
|
Write $\ell = \ell_G(v)$.
|
||||||
|
|
||||||
|
\emph{Step 1: every bounded face incident to $v$ has dual depth
|
||||||
|
$\ell - 1$ or $\ell$.} Let $f$ be a bounded triangular face with
|
||||||
|
$v \in V(f)$. Then
|
||||||
|
$\delta_G(d_f) = \min_{u \in V(f)} \ell_G(u) \le \ell_G(v) = \ell$.
|
||||||
|
The other two vertices of $f$ are adjacent to $v$ in $G$, and the level
|
||||||
|
function $\ell_G(\cdot) = \mathrm{dist}_G(\cdot, S)$ is $1$-Lipschitz
|
||||||
|
along edges, so each has level at least $\ell - 1$; hence
|
||||||
|
$\delta_G(d_f) \ge \ell - 1$. Thus $\delta_G(d_f) \in \{\ell-1, \ell\}$
|
||||||
|
(only $\delta_G(d_f) = 0$ when $\ell = 0$), so $v$ bounds faces of, and
|
||||||
|
therefore belongs to tires of, no depth other than $\ell - 1$ or
|
||||||
|
$\ell$.
|
||||||
|
|
||||||
|
\emph{Step 2: at each depth, all tires through $v$ lie in one cluster.}
|
||||||
|
Fix $d \in \{\ell-1, \ell\}$ and let $T, T'$ be depth-$d$ tires with
|
||||||
|
$v \in V(T) \cap V(T')$. Then $V(T) \cap V(T') \ne \varnothing$, so
|
||||||
|
$T \sim T'$ in the sense of
|
||||||
|
Definition~\ref{def:connected-tire-cluster}, and all depth-$d$ tires
|
||||||
|
containing $v$ lie in a single connected component of $\sim$ --- one
|
||||||
|
connected tire cluster $\mathsf{K}_d$.
|
||||||
|
|
||||||
|
Combining the two steps, $v$ belongs to at most the clusters
|
||||||
|
$\mathsf{K}_{\ell-1}$ and $\mathsf{K}_{\ell}$, i.e.\ to at most two
|
||||||
|
connected tire clusters; when $\ell = 0$ only $\mathsf{K}_0$ occurs.
|
||||||
|
\end{proof}
|
||||||
|
|
||||||
|
\section{Heawood restrictions on the tire dual}
|
||||||
|
\label{sec:heawood-restrictions}
|
||||||
|
|
||||||
|
We work inside a fixed nested tire decomposition $\mathcal{T}(G, S)$ of
|
||||||
|
$G$ from a single-vertex level source $S$ \cite{bauerfeld-nested-tires},
|
||||||
|
and use the tire data $T = (B_{\mathrm{out}}, O, E_{\mathrm{ann}})$ with
|
||||||
|
annular faces $F_{\mathrm{ann}}$, outer boundary $B_{\mathrm{out}}$, and
|
||||||
|
inner boundary $B_{\mathrm{in}}$
|
||||||
|
(\cite[Definition~1.5]{bauerfeld-nested-tires}). Since $O$ is
|
||||||
|
outerplanar, every vertex of a tire lies on $B_{\mathrm{out}}$ or on the
|
||||||
|
inner-boundary walk $B_{\mathrm{in}}$; a tire has no interior vertices.
|
||||||
|
|
||||||
|
\begin{definition}[Heawood face-labelling of a tire]
|
||||||
|
\label{def:heawood-labelling}
|
||||||
|
A \emph{Heawood face-labelling} of a tire graph $T$ is a map
|
||||||
|
\[
|
||||||
|
\lambda : F_{\mathrm{ann}} \longrightarrow \{+1, -1\}
|
||||||
|
\]
|
||||||
|
assigning a sign to each annular face of $T$. For a vertex
|
||||||
|
$v \in V(T)$, write $F_{\mathrm{ann}}(v) \subseteq F_{\mathrm{ann}}$ for
|
||||||
|
the set of annular faces of $T$ incident to $v$, and define the
|
||||||
|
\emph{induced vertex value}
|
||||||
|
\[
|
||||||
|
\lambda^{\!*}(v) \;:=\; \sum_{f \in F_{\mathrm{ann}}(v)} \lambda(f)
|
||||||
|
\;\;\bmod 3 \;\in\; \{0, 1, -1\}.
|
||||||
|
\]
|
||||||
|
The value $\lambda^{\!*}(v)$ is the \emph{partial} face-sum at $v$ taken
|
||||||
|
over the annular faces of $T$ alone, not over all faces of $G$ incident
|
||||||
|
to $v$.
|
||||||
|
\end{definition}
|
||||||
|
|
||||||
|
\begin{remark}
|
||||||
|
\label{rem:no-interior-constraint}
|
||||||
|
Because a tire has no interior vertices, every annular face of $T$ is
|
||||||
|
incident to $B_{\mathrm{out}} \cup B_{\mathrm{in}}$, and a Heawood
|
||||||
|
face-labelling is subject to \emph{no} internal constraint: all
|
||||||
|
$2^{|F_{\mathrm{ann}}|}$ sign assignments are admissible. The Heawood
|
||||||
|
restriction is felt only on the two boundary cycles, through the induced
|
||||||
|
vertex values $\lambda^{\!*}$.
|
||||||
|
\end{remark}
|
||||||
|
|
||||||
|
\begin{definition}[Induced boundary sequences]
|
||||||
|
\label{def:boundary-sequences}
|
||||||
|
Let $\lambda$ be a Heawood face-labelling of $T$. Reading the vertices
|
||||||
|
of $B_{\mathrm{out}}$ in clockwise order $v_0, v_1, \dots, v_{p-1}$, the
|
||||||
|
\emph{outer Heawood sequence} of $(T, \lambda)$ is
|
||||||
|
\[
|
||||||
|
\sigma_{\mathrm{out}}(T, \lambda)
|
||||||
|
\;:=\; \bigl(\lambda^{\!*}(v_0), \dots, \lambda^{\!*}(v_{p-1})\bigr)
|
||||||
|
\;\in\; \{0, 1, -1\}^{p}.
|
||||||
|
\]
|
||||||
|
Reading the inner-boundary walk $B_{\mathrm{in}}$ in clockwise order
|
||||||
|
$w_0, \dots, w_{q-1}$ gives the \emph{inner Heawood sequence}
|
||||||
|
$\sigma_{\mathrm{in}}(T, \lambda) \in \{0, 1, -1\}^{q}$. The
|
||||||
|
\emph{Heawood restriction relation} of $T$ is the set
|
||||||
|
\[
|
||||||
|
R_T \;:=\; \bigl\{\,
|
||||||
|
\bigl(\sigma_{\mathrm{out}}(T, \lambda),\,
|
||||||
|
\sigma_{\mathrm{in}}(T, \lambda)\bigr)
|
||||||
|
\;:\; \lambda : F_{\mathrm{ann}} \to \{+1, -1\}
|
||||||
|
\,\bigr\}
|
||||||
|
\]
|
||||||
|
of all (outer, inner) sequence pairs realisable by a single
|
||||||
|
face-labelling, read up to rotation and the global sign-flip
|
||||||
|
$\lambda \mapsto -\lambda$ (equivalently
|
||||||
|
$\sigma \mapsto -\sigma$).
|
||||||
|
\end{definition}
|
||||||
|
|
||||||
|
\begin{definition}[Heawood compatibility across an interface]
|
||||||
|
\label{def:heawood-compatible}
|
||||||
|
Let $T$ be a tire and $T' \in \mathcal{T}(G, S)$ a child of $T$, so the
|
||||||
|
outer boundary cycle $B_{\mathrm{out}}^{(T')}$ coincides with a bounded
|
||||||
|
face of $O^{(T)}$; let $\gamma$ be this shared cycle, of length $L$, and
|
||||||
|
let $v$ range over its vertices. Heawood face-labellings $\lambda$ of
|
||||||
|
$T$ and $\lambda'$ of $T'$ are \emph{compatible along $\gamma$} if at
|
||||||
|
every shared vertex $v$,
|
||||||
|
\[
|
||||||
|
\lambda^{\!*}(v) + (\lambda')^{\!*}(v) \;\equiv\; 0 \pmod 3,
|
||||||
|
\]
|
||||||
|
i.e.\ $0$ is paired with $0$ and $+1$ with $-1$. Equivalently, the
|
||||||
|
inner Heawood sequence of $T$ on $\gamma$ is the pointwise negation
|
||||||
|
mod $3$ of the outer Heawood sequence of $T'$ on $\gamma$, after
|
||||||
|
reversing one of the two clockwise readings to account for the opposite
|
||||||
|
rotational senses in which $T$ and $T'$ traverse $\gamma$.
|
||||||
|
\end{definition}
|
||||||
|
|
||||||
|
\begin{remark}
|
||||||
|
\label{rem:compat-is-heawood}
|
||||||
|
Call $v$ \emph{interior} if it is not incident to the outer face of
|
||||||
|
$\Pi_G$. For an interior vertex every incident face is bounded, and
|
||||||
|
compatibility along $\gamma$ at $v$ is exactly the statement that the
|
||||||
|
incident-face sum at $v$ --- over the parent's annular faces together
|
||||||
|
with the child's --- vanishes mod $3$:
|
||||||
|
\begin{equation}
|
||||||
|
\label{eq:heawood-face-sum-dual}
|
||||||
|
\sum_{f \ni v} \lambda(f) \;\equiv\; 0 \pmod 3
|
||||||
|
\qquad\text{for every interior vertex } v \in V(G),
|
||||||
|
\end{equation}
|
||||||
|
the sum ranging over the bounded faces incident to $v$. The interfaces
|
||||||
|
of $\mathcal{T}(G, S)$ are interior level cycles, so cluster
|
||||||
|
compatibility only ever constrains interior vertices and is untouched by
|
||||||
|
the outer face.
|
||||||
|
|
||||||
|
To pass from \eqref{eq:heawood-face-sum-dual} to a colouring one must
|
||||||
|
account for the outer face: an outer-boundary vertex is incident to the
|
||||||
|
unbounded face $f_\infty$, whose label is omitted from the bounded sum.
|
||||||
|
Extend $\lambda$ by a single label $\lambda(f_\infty) \in \{+1, -1\}$ on
|
||||||
|
$f_\infty$. Then a family of Heawood face-labellings that is pairwise
|
||||||
|
compatible along every interface of $\mathcal{T}(G, S)$ assembles into a
|
||||||
|
$\{+1,-1\}$ labelling of \emph{all} faces of $G$ for which
|
||||||
|
$\sum_{f \ni v} \lambda(f) \equiv 0 \pmod 3$ holds at every vertex ---
|
||||||
|
the outer-boundary vertices now carrying $\lambda(f_\infty)$ in their
|
||||||
|
sum. This is Heawood's face-sum identity \cite{Heawood1898} for a
|
||||||
|
proper $3$-edge-colouring of the full cubic dual of $G$, hence (by Tait)
|
||||||
|
a proper $4$-vertex-colouring of $G$.
|
||||||
|
\end{remark}
|
||||||
|
|
||||||
|
\subsection*{Why the programme runs between nested clusters}
|
||||||
|
|
||||||
|
The vanishing condition \eqref{eq:heawood-face-sum-dual} at a vertex $v$
|
||||||
|
is a constraint on the \emph{full} face-star of $v$. To run a
|
||||||
|
pigeonhole between two objects --- a child and a parent --- we need that
|
||||||
|
full sum to split as exactly two one-sided contributions, so that each
|
||||||
|
vertex label is the combination of a single child value and a single
|
||||||
|
parent value. This is true at the level of connected tire clusters, and
|
||||||
|
\emph{false} at the level of individual tires. Extend a Heawood
|
||||||
|
face-labelling to a connected tire cluster $\mathsf{K}$ by labelling
|
||||||
|
every annular face of every tire of $\mathsf{K}$, and for $v \in
|
||||||
|
V(\mathsf{K})$ write
|
||||||
|
\[
|
||||||
|
\lambda^{\!*}_{\mathsf{K}}(v) \;:=\;
|
||||||
|
\sum_{f} \lambda(f) \;\bmod 3,
|
||||||
|
\]
|
||||||
|
the sum over the annular faces of $\mathsf{K}$ incident to $v$.
|
||||||
|
|
||||||
|
\begin{proposition}[Two-sided cluster decomposition at a vertex]
|
||||||
|
\label{prop:two-sided-decomposition}
|
||||||
|
Let $v \in V(G)$ have level $\ell = \ell_G(v)$, and let
|
||||||
|
$\mathsf{K}_{\ell}$ and $\mathsf{K}_{\ell-1}$ be the at most two
|
||||||
|
connected tire clusters containing $v$, of depths $\ell$ and $\ell-1$
|
||||||
|
respectively (Proposition~\ref{prop:two-clusters-per-vertex}). Then the
|
||||||
|
bounded faces of $G$ incident to $v$ partition into the annular faces of
|
||||||
|
$\mathsf{K}_{\ell}$ at $v$ and the annular faces of $\mathsf{K}_{\ell-1}$
|
||||||
|
at $v$, and
|
||||||
|
\[
|
||||||
|
\sum_{f \ni v} \lambda(f)
|
||||||
|
\;\equiv\;
|
||||||
|
\lambda^{\!*}_{\mathsf{K}_{\ell}}(v) +
|
||||||
|
\lambda^{\!*}_{\mathsf{K}_{\ell-1}}(v)
|
||||||
|
\pmod 3 .
|
||||||
|
\]
|
||||||
|
Each one-sided value $\lambda^{\!*}_{\mathsf{K}_d}(v)$ is the
|
||||||
|
\emph{complete} sum over all depth-$d$ faces at $v$, so the Heawood
|
||||||
|
condition \eqref{eq:heawood-face-sum-dual} at $v$ reads
|
||||||
|
\[
|
||||||
|
\lambda^{\!*}_{\mathsf{K}_{\ell}}(v) +
|
||||||
|
\lambda^{\!*}_{\mathsf{K}_{\ell-1}}(v) \;\equiv\; 0 \pmod 3 ,
|
||||||
|
\]
|
||||||
|
a pairing between the single child cluster $\mathsf{K}_{\ell}$ and the
|
||||||
|
single parent cluster $\mathsf{K}_{\ell-1}$. (When $\ell = 0$, or when
|
||||||
|
$v$ bounds no depth-$\ell$ face, only one term is present.)
|
||||||
|
\end{proposition}
|
||||||
|
|
||||||
|
\begin{proof}
|
||||||
|
By Proposition~\ref{prop:two-clusters-per-vertex} (Step~1) every bounded
|
||||||
|
face incident to $v$ has depth $\ell-1$ or $\ell$, partitioning the
|
||||||
|
incident faces by depth; by Step~2 all depth-$\ell$ faces at $v$ lie in
|
||||||
|
the single cluster $\mathsf{K}_{\ell}$ and all depth-$(\ell-1)$ faces at
|
||||||
|
$v$ in $\mathsf{K}_{\ell-1}$. Hence the depth-$\ell$ part is exactly the
|
||||||
|
annular faces of $\mathsf{K}_{\ell}$ at $v$, the depth-$(\ell-1)$ part
|
||||||
|
those of $\mathsf{K}_{\ell-1}$, and summing $\lambda$ over the two parts
|
||||||
|
gives the identity; \eqref{eq:heawood-face-sum-dual} is its vanishing.
|
||||||
|
\end{proof}
|
||||||
|
|
||||||
|
\begin{remark}[Failure at the tire level]
|
||||||
|
\label{rem:why-clusters}
|
||||||
|
Proposition~\ref{prop:two-sided-decomposition} is what makes the binary
|
||||||
|
parent/child pairing possible, and it requires the cluster. A vertex
|
||||||
|
$v$ may lie on many depth-$\ell$ tires --- the unbounded case of
|
||||||
|
Section~\ref{sec:tire-clusters} --- and the per-tire value
|
||||||
|
$\lambda^{\!*}(v)$ of Definition~\ref{def:heawood-labelling} then records
|
||||||
|
only the faces of \emph{one} tire at $v$, a fragment of $v$'s face-star.
|
||||||
|
No single child tire carries the complete depth-$\ell$ sum, so the label
|
||||||
|
$\sum_{f \ni v}\lambda(f)$ cannot be written as one child value plus one
|
||||||
|
parent value, and per-tire compatibility
|
||||||
|
(Definition~\ref{def:heawood-compatible}) fails to assemble to
|
||||||
|
\eqref{eq:heawood-face-sum-dual}. Clustering repairs this:
|
||||||
|
Proposition~\ref{prop:two-clusters-per-vertex} guarantees exactly one
|
||||||
|
cluster meets $v$ on each side, so $\lambda^{\!*}_{\mathsf{K}_{\ell}}(v)$
|
||||||
|
is the complete child contribution and
|
||||||
|
$\lambda^{\!*}_{\mathsf{K}_{\ell-1}}(v)$ the complete parent
|
||||||
|
contribution. Every vertex label is then realised as the combination of
|
||||||
|
a single child-cluster value with a single parent-cluster value, and the
|
||||||
|
pigeonhole programme below chains \emph{nested connected tire clusters}
|
||||||
|
rather than individual tires.
|
||||||
|
\end{remark}
|
||||||
|
|
||||||
|
We write $R_{\mathsf{K}}$ for the \emph{cluster Heawood restriction
|
||||||
|
relation}: the set of (outer, inner) boundary Heawood sequence pairs
|
||||||
|
realisable by a face-labelling of $\mathsf{K}$, defined as in
|
||||||
|
Definition~\ref{def:boundary-sequences} but with the outer and inner
|
||||||
|
boundaries of the cluster and the complete one-sided values
|
||||||
|
$\lambda^{\!*}_{\mathsf{K}}$ in place of a single tire's, read up to
|
||||||
|
rotation and global sign-flip. By
|
||||||
|
Proposition~\ref{prop:two-sided-decomposition} two nested clusters are
|
||||||
|
compatible along their shared interface exactly when the inner sequence
|
||||||
|
of the parent is the pointwise negation mod $3$ of the outer sequence of
|
||||||
|
the child (after the orientation reversal of
|
||||||
|
Definition~\ref{def:heawood-compatible}).
|
||||||
|
|
||||||
|
\begin{conjecture}[Heawood chain-pigeonhole principle]
|
||||||
|
\label{conj:heawood-chain-pigeonhole}
|
||||||
|
There is a function $N(k)$ such that the following holds. Let
|
||||||
|
\[
|
||||||
|
\mathsf{K}_0 \supset \mathsf{K}_1 \supset \cdots \supset
|
||||||
|
\mathsf{K}_{N(k)}
|
||||||
|
\]
|
||||||
|
be a nested chain of connected tire clusters in $\mathcal{T}(G, S)$ whose
|
||||||
|
shared interfaces have length at most $k$. Then two adjacent cluster
|
||||||
|
restriction relations $R_{\mathsf{K}_i}, R_{\mathsf{K}_{i+1}}$ in the
|
||||||
|
chain admit compatible face-labellings along their shared interface,
|
||||||
|
after rotation and global sign-flip. Equivalently, the chain contains a
|
||||||
|
local gluing step that cannot be obstructed by disjoint Heawood boundary
|
||||||
|
restrictions.
|
||||||
|
\end{conjecture}
|
||||||
|
|
||||||
|
\begin{conjecture}[Heawood cluster route to the Four Colour Theorem]
|
||||||
|
\label{conj:heawood-route-fct}
|
||||||
|
For every plane triangulation $G$ and every level source $S$, the
|
||||||
|
cluster Heawood restriction relations
|
||||||
|
$\{R_{\mathsf{K}} : \mathsf{K} \text{ a connected tire cluster}\}$ admit
|
||||||
|
a selection of face-labellings that is compatible along every cluster
|
||||||
|
interface. By Proposition~\ref{prop:two-sided-decomposition} and
|
||||||
|
Remark~\ref{rem:compat-is-heawood} this yields a $\{+1,-1\}$
|
||||||
|
face-labelling of $G$ satisfying \eqref{eq:heawood-face-sum-dual}, hence
|
||||||
|
$G$ is properly $4$-vertex-colourable.
|
||||||
|
\end{conjecture}
|
||||||
|
|
||||||
|
%% TODO: realisability of $R_{\mathsf{K}}$ per cluster; counting /
|
||||||
|
%% pigeonhole bound giving $N(k)$; orientation/reversal bookkeeping on
|
||||||
|
%% the shared interface.
|
||||||
|
|
||||||
|
\section{The constraint floor}
|
||||||
|
\label{sec:constraint-floor}
|
||||||
|
|
||||||
|
A nested substructure constrains its outer interface through the set of
|
||||||
|
Heawood boundary sequences it can realise. By the self-similarity of the
|
||||||
|
tire decomposition (\cite{bauerfeld-nested-tires}), the region $G_T$
|
||||||
|
enclosed by a tire's outer cycle, away from the source, is itself a
|
||||||
|
triangulated disk; we ask how tightly any such disk can constrain its
|
||||||
|
boundary. The achievable set below depends only on the disk
|
||||||
|
triangulation, not on a tire-tree labelling.
|
||||||
|
|
||||||
|
\begin{definition}[Achievable boundary set of a disk]
|
||||||
|
\label{def:achievable-boundary-set}
|
||||||
|
Let $D$ be a triangulated disk whose boundary is a simple $n$-cycle
|
||||||
|
$C = (v_0, \dots, v_{n-1})$. Call a Heawood face-labelling
|
||||||
|
$\lambda : F(D) \to \{+1,-1\}$ \emph{interior-valid} if
|
||||||
|
$\sum_{f \ni w} \lambda(f) \equiv 0 \pmod 3$ at every interior vertex $w$
|
||||||
|
of $D$ (no condition on $C$). The \emph{achievable boundary set} of $D$
|
||||||
|
is
|
||||||
|
\[
|
||||||
|
\Phi(D) \;:=\; \bigl\{\,
|
||||||
|
(\lambda^{*}(v_0), \dots, \lambda^{*}(v_{n-1}))
|
||||||
|
\;:\; \lambda \text{ interior-valid} \,\bigr\}
|
||||||
|
\;\subseteq\; \{0,1,-1\}^{n} .
|
||||||
|
\]
|
||||||
|
\end{definition}
|
||||||
|
|
||||||
|
\begin{proposition}[Interior-free disks attain $2^{n-2}$]
|
||||||
|
\label{prop:attainment}
|
||||||
|
If $D$ has no interior vertices then $|\Phi(D)| = 2^{\,n-2}$.
|
||||||
|
\end{proposition}
|
||||||
|
|
||||||
|
\begin{proof}
|
||||||
|
A triangulation of the $n$-gon has an \emph{ear}: a face
|
||||||
|
$(v_{i-1}, v_i, v_{i+1})$ whose middle vertex $v_i$ has face-degree $1$,
|
||||||
|
so $\lambda^{*}(v_i)$ equals that face's label and is read directly off
|
||||||
|
the boundary sequence. Deleting the ear leaves a triangulation of the
|
||||||
|
$(n-1)$-gon inducing the restricted boundary sequence; inducting, the
|
||||||
|
$n-2$ face labels are recovered injectively, so $\lambda \mapsto
|
||||||
|
\lambda^{*}|_C$ is a bijection onto a set of size $2^{\,n-2}$.
|
||||||
|
\end{proof}
|
||||||
|
|
||||||
|
\begin{lemma}[Un-stacking]
|
||||||
|
\label{lem:unstack}
|
||||||
|
If $v$ is a degree-$3$ interior vertex of $D$, deleting it and restoring
|
||||||
|
its link triangle as a single face yields a disk $D'$ with one fewer
|
||||||
|
interior vertex and $\Phi(D') = \Phi(D)$.
|
||||||
|
\end{lemma}
|
||||||
|
|
||||||
|
\begin{proof}
|
||||||
|
The constraint at $v$ forces its three faces to a common value $s$,
|
||||||
|
contributing $2s \equiv -s$ to each of the three link vertices; the
|
||||||
|
restored triangle, labelled $-s$, reproduces that contribution at each,
|
||||||
|
and $s \mapsto -s$ is a bijection on $\{+1,-1\}$. The resulting map is a
|
||||||
|
bijection between interior-valid labellings of $D$ and of $D'$ preserving
|
||||||
|
every boundary value, hence $\Phi(D) = \Phi(D')$.
|
||||||
|
\end{proof}
|
||||||
|
|
||||||
|
\begin{conjecture}[Constraint floor]
|
||||||
|
\label{conj:constraint-floor}
|
||||||
|
For every triangulated disk $D$ with boundary an $n$-cycle,
|
||||||
|
$|\Phi(D)| \ge 2^{\,n-2}$. Equivalently, no nested structure constrains
|
||||||
|
the outer cycle below $2^{\,n-2}$ achievable Heawood sequences.
|
||||||
|
\end{conjecture}
|
||||||
|
|
||||||
|
\begin{remark}[Status of Conjecture~\ref{conj:constraint-floor}]
|
||||||
|
\label{rem:floor-status}
|
||||||
|
Iterating Lemma~\ref{lem:unstack} reduces any disk, $\Phi$-faithfully and
|
||||||
|
at fixed $n$, to one with no degree-$3$ interior vertex: either
|
||||||
|
interior-free, where Proposition~\ref{prop:attainment} gives exactly
|
||||||
|
$2^{\,n-2}$, or \emph{irreducible} (every interior vertex of degree
|
||||||
|
$\ge 4$). Thus Conjecture~\ref{conj:constraint-floor} holds for the
|
||||||
|
entire stacked (Apollonian) class and reduces to the irreducible case.
|
||||||
|
Empirically it holds without exception over more than $10^4$ disks, and
|
||||||
|
\emph{strictly}: every irreducible disk satisfies $|\Phi(D)| \ge
|
||||||
|
\tfrac54 \cdot 2^{\,n-2}$, with equality at a single minimal-degree
|
||||||
|
interior vertex; the wheel $W_n$ gives $|\Phi(W_n)| = \lfloor 2^n/3
|
||||||
|
\rfloor$ and is \emph{not} the minimiser. A counting balance makes the
|
||||||
|
floor plausible --- a disk with $k$ interior vertices has $2k+n-2$ faces
|
||||||
|
(Euler) but only $k$ interior constraints, so the linear free dimension
|
||||||
|
$k+n-2$ grows with depth --- but this is only heuristic: $|\Phi(D)|$ is
|
||||||
|
\emph{not} monotone in $k$ (inserting a degree-$4$ vertex can shrink it),
|
||||||
|
it merely never drops below $2^{\,n-2}$. Two natural elementary proofs,
|
||||||
|
a $\Phi$-non-increasing vertex reduction and a direct $(n{-}2)$-face
|
||||||
|
transversal, both provably fail; a proof appears to need a global
|
||||||
|
argument on the Boolean / mod-$3$ structure of $\Phi$.
|
||||||
|
\end{remark}
|
||||||
|
|
||||||
|
\begin{remark}
|
||||||
|
\label{rem:floor-consequences}
|
||||||
|
Two consequences. First, $\Phi(D)$ is a $\mathbb{Z}/3$ zonotope --- a
|
||||||
|
projected cube, sign-closed but not a $\mathrm{GF}(3)$ subspace --- and at
|
||||||
|
the interior-free value it has size $2^{\,n-2}$ with affine hull of
|
||||||
|
dimension $n-2$. Second, granting Conjecture~\ref{conj:constraint-floor},
|
||||||
|
the floor is exponential in the interface length $n$, so a
|
||||||
|
maximally-constraining child still offers $2^{\,n-2}$ outer options, and
|
||||||
|
the gluing of Conjecture~\ref{conj:heawood-chain-pigeonhole} has the least
|
||||||
|
slack at \emph{short} interfaces (e.g.\ $n = 4$ leaves $4$ options) and is
|
||||||
|
easy at long ones; the difficulty of the programme is concentrated at
|
||||||
|
short level cycles.
|
||||||
|
\end{remark}
|
||||||
|
|
||||||
|
\begin{thebibliography}{9}
|
||||||
|
|
||||||
|
\bibitem{Heawood1898}
|
||||||
|
P.~J.~Heawood,
|
||||||
|
\emph{On the four-colour map theorem},
|
||||||
|
Quart. J.~Pure Appl. Math. \textbf{29} (1898), 270--285.
|
||||||
|
|
||||||
|
\bibitem{bauerfeld-depth}
|
||||||
|
E.~Bauerfeld,
|
||||||
|
\emph{Plane Depth},
|
||||||
|
manuscript (math-research repository), 2026.
|
||||||
|
|
||||||
|
\bibitem{bauerfeld-nested-tires}
|
||||||
|
E.~Bauerfeld,
|
||||||
|
\emph{Nested Tire Decompositions of Plane Triangulations},
|
||||||
|
manuscript (math-research repository), 2026.
|
||||||
|
|
||||||
|
\bibitem{bauerfeld-medial-tires}
|
||||||
|
E.~Bauerfeld,
|
||||||
|
\emph{Medial Tire Decompositions of Plane Triangulations},
|
||||||
|
manuscript (math-research repository), 2026.
|
||||||
|
|
||||||
|
\bibitem{bauerfeld-nested-tire-duals}
|
||||||
|
E.~Bauerfeld,
|
||||||
|
\emph{Coloring Nested Tire Dual Graphs},
|
||||||
|
manuscript (math-research repository), 2026.
|
||||||
|
|
||||||
|
\end{thebibliography}
|
||||||
|
|
||||||
|
\end{document}
|
||||||
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 227 KiB |
|
After Width: | Height: | Size: 297 KiB |
@@ -0,0 +1,289 @@
|
|||||||
|
"""Draw the walk-depth labelling and cut of a medial tire decomposition.
|
||||||
|
|
||||||
|
Paper-graphics companion to ``run_medial_tire_cut_experiment.py``: it imports
|
||||||
|
``run_experiment`` from there, runs the pipeline on a random maximal planar
|
||||||
|
graph, and emits TikZ. By default it draws one ``tikzpicture`` (walk-depth
|
||||||
|
labels + cut slits) per recognised full medial tire graph, using ``to_tikz``
|
||||||
|
from ``medial_tire_cut_labelling``. With ``--whole`` it instead draws a
|
||||||
|
two-panel Figure 3 graphic: the source graph with its source highlighted and
|
||||||
|
the whole medial graph M(G) drawn with every medial vertex at the midpoint of
|
||||||
|
its source edge and labelled by that source edge, with the full BFS-level chain
|
||||||
|
shown and the currently computed walk-depth labels and cuts marked.
|
||||||
|
|
||||||
|
This script only renders; the experiment itself draws nothing. Run with the
|
||||||
|
repo venv (networkx): ``.venv/bin/python``.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
.venv/bin/python draw_medial_tire_cut.py -n 20 --seed 59 > panels.tex
|
||||||
|
.venv/bin/python draw_medial_tire_cut.py -n 20 --seed 59 --whole > whole.tex
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import networkx as nx
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
_PAPER_DIR = os.path.dirname(_HERE)
|
||||||
|
_CUT_LIB = os.path.join(_PAPER_DIR, "lib")
|
||||||
|
sys.path.insert(0, _HERE)
|
||||||
|
sys.path.insert(0, _CUT_LIB)
|
||||||
|
|
||||||
|
from run_medial_tire_cut_experiment import run_experiment # noqa: E402
|
||||||
|
from medial_tire_cut_labelling import to_tikz # noqa: E402
|
||||||
|
from tire_realization_analysis import triangular_faces # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
def tikz_panels(n: int, seed: int, scale: float = 1.6,
|
||||||
|
min_degree: int = 5, attempts: int = 1000) -> tuple[dict, list[str]]:
|
||||||
|
"""Run the experiment and return ``(result, panels)``, one TikZ panel per
|
||||||
|
recognised tread, each showing that tread's walk-depth labelling and cut."""
|
||||||
|
result = run_experiment(n=n, seed=seed, min_degree=min_degree, attempts=attempts)
|
||||||
|
panels = []
|
||||||
|
for d in sorted(result["results"]):
|
||||||
|
rec = result["results"][d]
|
||||||
|
panels.append(to_tikz(rec["g"], depth=rec["depth"], cuts=rec["cuts"],
|
||||||
|
entry_edge=rec["entry_edge"], scale=scale))
|
||||||
|
return result, panels
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Figure 3: the source graph and midpoint drawing of the whole medial graph.
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def _source_layout(G: nx.Graph) -> dict[int, tuple[float, float]]:
|
||||||
|
"""Straight-line planar layout for the source graph, normalised to the unit
|
||||||
|
box and reused by the medial drawing."""
|
||||||
|
faces, _ = triangular_faces(G)
|
||||||
|
outer = list(faces[0])
|
||||||
|
outer_set = set(outer)
|
||||||
|
raw = {}
|
||||||
|
for i, v in enumerate(outer):
|
||||||
|
angle = math.radians(90.0 - i * 360.0 / len(outer))
|
||||||
|
raw[v] = np.array([math.cos(angle), math.sin(angle)], dtype=float)
|
||||||
|
|
||||||
|
inner = [v for v in sorted(G.nodes()) if v not in outer_set]
|
||||||
|
if inner:
|
||||||
|
idx = {v: i for i, v in enumerate(inner)}
|
||||||
|
n = len(inner)
|
||||||
|
A = np.zeros((n, n))
|
||||||
|
bx = np.zeros(n)
|
||||||
|
by = np.zeros(n)
|
||||||
|
for i, v in enumerate(inner):
|
||||||
|
nbrs = list(G.neighbors(v))
|
||||||
|
A[i, i] = 1.0
|
||||||
|
for w in nbrs:
|
||||||
|
if w in idx:
|
||||||
|
A[i, idx[w]] -= 1.0 / len(nbrs)
|
||||||
|
else:
|
||||||
|
bx[i] += raw[w][0] / len(nbrs)
|
||||||
|
by[i] += raw[w][1] / len(nbrs)
|
||||||
|
xs = np.linalg.solve(A, bx)
|
||||||
|
ys = np.linalg.solve(A, by)
|
||||||
|
for v in inner:
|
||||||
|
raw[v] = np.array([xs[idx[v]], ys[idx[v]]], dtype=float)
|
||||||
|
|
||||||
|
pts = np.array([raw[v] for v in G.nodes()], dtype=float)
|
||||||
|
center = 0.5 * (pts.max(axis=0) + pts.min(axis=0))
|
||||||
|
span = float(max(*(pts.max(axis=0) - pts.min(axis=0)), 1.0))
|
||||||
|
return {
|
||||||
|
v: tuple((raw[v] - center) / span)
|
||||||
|
for v in G.nodes()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _edge_midpoint(pos: dict, edge) -> tuple[float, float]:
|
||||||
|
u, v = edge
|
||||||
|
ux, uy = pos[u]
|
||||||
|
vx, vy = pos[v]
|
||||||
|
return (0.5 * (ux + vx), 0.5 * (uy + vy))
|
||||||
|
|
||||||
|
|
||||||
|
def _edge_label(edge) -> str:
|
||||||
|
u, v = edge
|
||||||
|
return f"${u}\\!{{-}}\\!{v}$"
|
||||||
|
|
||||||
|
|
||||||
|
def _source_graph_tikz(result: dict, pos: dict, scale: float) -> str:
|
||||||
|
G, source = result["G"], result["source"]
|
||||||
|
L = []
|
||||||
|
A = L.append
|
||||||
|
A(f"\\begin{{tikzpicture}}[scale={scale},")
|
||||||
|
A(" sedge/.style={black!50, line width=0.35pt},")
|
||||||
|
A(" sv/.style={circle, draw=black!60, fill=white, inner sep=1.1pt},")
|
||||||
|
A(" srcv/.style={circle, draw=blue!75!black, fill=blue!18, line width=0.7pt, inner sep=1.8pt}]")
|
||||||
|
|
||||||
|
def pt(v):
|
||||||
|
x, y = pos[v]
|
||||||
|
return f"({x:.3f},{y:.3f})"
|
||||||
|
|
||||||
|
for u, v in sorted(G.edges()):
|
||||||
|
A(f"\\draw[sedge] {pt(u)}--{pt(v)};")
|
||||||
|
for v in sorted(G.nodes()):
|
||||||
|
style = "srcv" if v == source else "sv"
|
||||||
|
A(f"\\node[{style}] at {pt(v)} {{}};")
|
||||||
|
sx, sy = pos[source]
|
||||||
|
A(f"\\node[font=\\scriptsize, text=blue!70!black] at ({sx:.3f},{sy - 0.085:.3f}) {{source {source}}};")
|
||||||
|
A("\\end{tikzpicture}")
|
||||||
|
return "\n".join(L)
|
||||||
|
|
||||||
|
|
||||||
|
def _medial_midpoint_tikz(result: dict, pos: dict, scale: float) -> str:
|
||||||
|
"""Draw M(G) with each medial vertex at the midpoint of its source edge.
|
||||||
|
Every medial vertex is labelled by its source edge; same-level source edges
|
||||||
|
show the BFS level-chain tooth layers, and interlevel source edges show the
|
||||||
|
annular layers. Currently computed tire walk-depth labels and cut labels
|
||||||
|
are overlaid without moving the medial vertices away from their source
|
||||||
|
edges."""
|
||||||
|
G, M = result["G"], result["M"]
|
||||||
|
levels = nx.single_source_shortest_path_length(G, result["source"])
|
||||||
|
medial_pos = {edge: _edge_midpoint(pos, edge) for edge in M.nodes()}
|
||||||
|
|
||||||
|
apex_roles = {}
|
||||||
|
apex_walks = {}
|
||||||
|
for r in result["labels"]:
|
||||||
|
apex_roles[r["apex"]] = r["role"]
|
||||||
|
apex_walks.setdefault(r["apex"], []).append(r["walk"])
|
||||||
|
|
||||||
|
cut_records = []
|
||||||
|
cut_number = 1
|
||||||
|
for c in result.get("cap_cuts", []):
|
||||||
|
cut_records.append((cut_number, c["medial_vertex"], "cap", c))
|
||||||
|
cut_number += 1
|
||||||
|
for d in sorted(result["results"]):
|
||||||
|
rec = result["results"][d]
|
||||||
|
g, bij = rec["g"], rec["bij"]
|
||||||
|
for c in rec["cuts"]:
|
||||||
|
if c.vertex is None:
|
||||||
|
continue
|
||||||
|
cut_records.append((cut_number, bij[f"a{c.vertex}"], d, c))
|
||||||
|
cut_number += 1
|
||||||
|
|
||||||
|
L = []
|
||||||
|
A = L.append
|
||||||
|
A(f"\\begin{{tikzpicture}}[scale={scale},")
|
||||||
|
A(" base/.style={black!12, line width=0.25pt},")
|
||||||
|
A(" med/.style={black!38, line width=0.32pt},")
|
||||||
|
A(" annv/.style={circle, draw=black!70, fill=black!18, inner sep=1.0pt},")
|
||||||
|
A(" levone/.style={circle, draw=orange!75!black, fill=orange!20, inner sep=1.2pt},")
|
||||||
|
A(" levtwo/.style={circle, draw=violet!70!black, fill=violet!18, inner sep=1.2pt},")
|
||||||
|
A(" levthree/.style={circle, draw=teal!70!black, fill=teal!18, inner sep=1.2pt},")
|
||||||
|
A(" knownv/.style={circle, draw=red!70!black, fill=red!24, inner sep=1.5pt},")
|
||||||
|
A(" elbl/.style={font=\\tiny, text=black!70, inner sep=0.2pt},")
|
||||||
|
A(" dlbl/.style={font=\\tiny\\bfseries, text=black, inner sep=0.5pt},")
|
||||||
|
A(" cut/.style={red!80!black, line width=1.0pt},")
|
||||||
|
A(" cutlbl/.style={font=\\tiny, text=red!75!black}]")
|
||||||
|
|
||||||
|
def pt_med(edge):
|
||||||
|
x, y = medial_pos[edge]
|
||||||
|
return f"({x:.3f},{y:.3f})"
|
||||||
|
|
||||||
|
def pt_src(v):
|
||||||
|
x, y = pos[v]
|
||||||
|
return f"({x:.3f},{y:.3f})"
|
||||||
|
|
||||||
|
for u, v in sorted(result["G"].edges()):
|
||||||
|
A(f"\\draw[base] {pt_src(u)}--{pt_src(v)};")
|
||||||
|
for u, v in M.edges():
|
||||||
|
A(f"\\draw[med] {pt_med(u)}--{pt_med(v)};")
|
||||||
|
|
||||||
|
def chain_style(edge):
|
||||||
|
u, v = edge
|
||||||
|
lu, lv = levels[u], levels[v]
|
||||||
|
if lu != lv:
|
||||||
|
return "annv"
|
||||||
|
if edge in apex_roles:
|
||||||
|
return "knownv"
|
||||||
|
return {1: "levone", 2: "levtwo", 3: "levthree"}.get(lu, "annv")
|
||||||
|
|
||||||
|
for mv in sorted(M.nodes()):
|
||||||
|
A(f"\\node[{chain_style(mv)}] at {pt_med(mv)} {{}};")
|
||||||
|
for mv in sorted(M.nodes()):
|
||||||
|
x, y = medial_pos[mv]
|
||||||
|
A(f"\\node[elbl] at ({x:.3f},{y:.3f}) [yshift=-4.8pt] {{{_edge_label(mv)}}};")
|
||||||
|
for mv in sorted(apex_walks):
|
||||||
|
x, y = medial_pos[mv]
|
||||||
|
label = ",".join(str(w) for w in sorted(apex_walks[mv]))
|
||||||
|
A(f"\\node[dlbl] at ({x:.3f},{y:.3f}) [yshift=5.0pt] {{{label}}};")
|
||||||
|
for number, mv, _d, _cut in cut_records:
|
||||||
|
u, v = mv
|
||||||
|
ux, uy = pos[u]
|
||||||
|
vx, vy = pos[v]
|
||||||
|
mx, my = medial_pos[mv]
|
||||||
|
ex, ey = vx - ux, vy - uy
|
||||||
|
length = math.hypot(ex, ey) or 1.0
|
||||||
|
dx, dy = -0.035 * ey / length, 0.035 * ex / length
|
||||||
|
A(f"\\draw[cut] ({mx - dx:.3f},{my - dy:.3f})--({mx + dx:.3f},{my + dy:.3f});")
|
||||||
|
A(f"\\node[cutlbl] at ({mx + 2.4 * dx:.3f},{my + 2.4 * dy:.3f}) {{cut {number}}};")
|
||||||
|
A("\\end{tikzpicture}")
|
||||||
|
return "\n".join(L)
|
||||||
|
|
||||||
|
|
||||||
|
def medial_tikz(result: dict, scale: float = 7.0) -> str:
|
||||||
|
"""Two-panel TikZ for Figure 3: the source graph and the midpoint drawing of
|
||||||
|
its medial graph with all medial vertices labelled, plus the tire
|
||||||
|
walk-depth labels and cuts."""
|
||||||
|
pos = _source_layout(result["G"])
|
||||||
|
source = _source_graph_tikz(result, pos, scale=0.58 * scale)
|
||||||
|
medial = _medial_midpoint_tikz(result, pos, scale=scale)
|
||||||
|
return "\n".join([
|
||||||
|
"\\begin{tabular}{c}",
|
||||||
|
source,
|
||||||
|
"\\\\[-0.25ex]",
|
||||||
|
"{\\scriptsize source graph $G$}",
|
||||||
|
"\\\\[1.0ex]",
|
||||||
|
medial,
|
||||||
|
"\\\\[-0.25ex]",
|
||||||
|
"{\\scriptsize medial graph $M(G)$ at edge midpoints}",
|
||||||
|
"\\end{tabular}",
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__,
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
parser.add_argument("-n", type=int, default=20)
|
||||||
|
parser.add_argument("--seed", type=int, default=72)
|
||||||
|
parser.add_argument("--scale", type=float, default=1.6)
|
||||||
|
parser.add_argument("--min-degree", type=int, default=5,
|
||||||
|
help="reject random graphs below this minimum degree")
|
||||||
|
parser.add_argument("--attempts", type=int, default=1000,
|
||||||
|
help="number of consecutive seeds to try for --min-degree")
|
||||||
|
parser.add_argument("--whole", action="store_true",
|
||||||
|
help="draw the whole medial graph M(G) with all cuts, "
|
||||||
|
"instead of one panel per tread")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.whole:
|
||||||
|
result = run_experiment(n=args.n, seed=args.seed,
|
||||||
|
min_degree=args.min_degree, attempts=args.attempts)
|
||||||
|
treads = sorted(result["results"])
|
||||||
|
print(f"% whole medial graph: n={args.n} seed={args.seed} "
|
||||||
|
f"graph_seed={result['graph_seed']} min_degree={result['min_degree']} "
|
||||||
|
f"source={result['source']} recognised treads={treads} "
|
||||||
|
f"|M(G)|={result['M'].number_of_nodes()}")
|
||||||
|
print(medial_tikz(result, scale=args.scale if args.scale != 1.6 else 7.0))
|
||||||
|
return
|
||||||
|
|
||||||
|
result, panels = tikz_panels(args.n, args.seed, scale=args.scale,
|
||||||
|
min_degree=args.min_degree, attempts=args.attempts)
|
||||||
|
treads = sorted(result["results"])
|
||||||
|
print(f"% medial tire cut: n={args.n} seed={args.seed} "
|
||||||
|
f"graph_seed={result['graph_seed']} min_degree={result['min_degree']} "
|
||||||
|
f"source={result['source']} recognised treads={treads}")
|
||||||
|
if not panels:
|
||||||
|
print("% (no recognised full medial tire graphs for this graph)")
|
||||||
|
for d, panel in zip(treads, panels):
|
||||||
|
g = result["results"][d]["g"]
|
||||||
|
print(f"% --- tread {d}: |A(T)|={g.n} word={g.tooth_word} "
|
||||||
|
f"bites={sorted(g.bites)} ---")
|
||||||
|
print(panel)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Full medial tire cut walk 1
|
||||||
|
|
||||||
|
- base vertices: 20
|
||||||
|
- deep-embedded vertices: 30
|
||||||
|
- deep-embedded edges: 84
|
||||||
|
- graph seed: 59
|
||||||
|
- deep-embedded minimum degree: 3
|
||||||
|
- chosen face: (8, 9, 19)
|
||||||
|
- chosen source cap vertex: 24
|
||||||
|
- root entry tooth: e2 (apex medial vertex = level-1 edge (19, 8))
|
||||||
|
- recognised treads: 11
|
||||||
|
- skipped treads: [((0, 0), 'only 0 up teeth')]
|
||||||
|
- removed source-dual edges: 29
|
||||||
|
- annular/cap cuts: 12
|
||||||
|
- up-apex cuts: 17
|
||||||
|
|
||||||
|
- **source-dual cut is a tree: True** (56 dual faces, 55 edges, 1 component(s), acyclic=True)
|
||||||
|
|
||||||
|
## Walk-distance labelling of the source-dual cut
|
||||||
|
|
||||||
|
Each dual face (vertex of the source-dual cut) is labelled by its distance, within the cut, from the **cap down tooth of the first entry**: the triangular face `(8, 24, 19)` = `{source 24, edge (19, 8)}` (dual node 34).
|
||||||
|
|
||||||
|
- maximum distance: 21
|
||||||
|
- distance histogram (faces by distance): `{0: 1, 1: 2, 2: 2, 3: 2, 4: 2, 5: 2, 6: 3, 7: 3, 8: 4, 9: 4, 10: 4, 11: 4, 12: 3, 13: 3, 14: 2, 15: 2, 16: 1, 17: 2, 18: 3, 19: 4, 20: 2, 21: 1}`
|
||||||
|
|
||||||
|
- dual cut figure: `full_medial_tire_cut_walk_1_dual.png`
|
||||||
|
- tire cut grid: `full_medial_tire_cut_walk_1_tires.png`
|
||||||
|
- combined PDF: `full_medial_tire_cut_walk_1.pdf`
|
||||||
|
|
||||||
|
| tread | depth | component | annular | up | singleton down | bite apexes | entry | closing cuts | up-apex cuts | shared/entry skipped |
|
||||||
|
|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|
|
||||||
|
| T1 | 1 | 0 | 9 | 3 | 6 | 0 | e2 | 1 | 2 | 1 |
|
||||||
|
| T2 | 2 | 0 | 17 | 6 | 11 | 0 | e15 | 1 | 5 | 1 |
|
||||||
|
| T3 | 3 | 0 | 3 | 3 | 0 | 0 | e1 | 1 | 2 | 1 |
|
||||||
|
| T4 | 3 | 1 | 3 | 3 | 0 | 0 | e1 | 1 | 2 | 1 |
|
||||||
|
| T5 | 3 | 2 | 3 | 3 | 0 | 0 | e1 | 1 | 2 | 1 |
|
||||||
|
| T6 | 3 | 3 | 3 | 3 | 0 | 0 | e0 | 1 | 2 | 1 |
|
||||||
|
| T7 | 3 | 4 | 3 | 3 | 0 | 0 | e1 | 1 | 2 | 1 |
|
||||||
|
| T8 | 3 | 5 | 3 | 3 | 0 | 0 | e1 | 1 | 2 | 1 |
|
||||||
|
| T9 | 3 | 6 | 3 | 3 | 0 | 0 | e0 | 1 | 2 | 1 |
|
||||||
|
| T10 | 3 | 7 | 3 | 3 | 0 | 0 | e2 | 1 | 2 | 1 |
|
||||||
|
| T11 | 3 | 8 | 3 | 3 | 0 | 0 | e2 | 1 | 2 | 1 |
|
||||||
|
|
||||||
|
## Removed Source-Dual Edges
|
||||||
|
|
||||||
|
- annular/cap: `[(0, 20), (0, 21), (1, 6), (7, 8), (11, 25), (11, 26), (12, 27), (15, 29), (16, 28), (19, 24), (22, 5), (23, 4)]`
|
||||||
|
- up apexes: `[(0, 5), (1, 5), (2, 3), (2, 7), (4, 5), (8, 9), (10, 3), (10, 18), (11, 16), (12, 15), (12, 16), (13, 14), (13, 15), (14, 4), (16, 17), (18, 6), (19, 9)]`
|
||||||
|
After Width: | Height: | Size: 285 KiB |
|
After Width: | Height: | Size: 458 KiB |
@@ -0,0 +1,39 @@
|
|||||||
|
# Full medial tire cut walk 2
|
||||||
|
|
||||||
|
- base vertices: 20
|
||||||
|
- deep-embedded vertices: 21
|
||||||
|
- deep-embedded edges: 57
|
||||||
|
- graph seed: 2
|
||||||
|
- deep-embedded minimum degree: 3
|
||||||
|
- chosen face: (4, 12, 11)
|
||||||
|
- chosen source cap vertex: 20
|
||||||
|
- root entry tooth: e3 (apex medial vertex = level-1 edge (11, 4))
|
||||||
|
- recognised treads: 3
|
||||||
|
- skipped treads: [((0, 0), 'only 0 up teeth')]
|
||||||
|
- removed source-dual edges: 20
|
||||||
|
- annular/cap cuts: 5
|
||||||
|
- up-apex cuts: 15
|
||||||
|
|
||||||
|
- **source-dual cut is a tree: True** (38 dual faces, 37 edges, 1 component(s), acyclic=True)
|
||||||
|
|
||||||
|
## Walk-distance labelling of the source-dual cut
|
||||||
|
|
||||||
|
Each dual face (vertex of the source-dual cut) is labelled by its distance, within the cut, from the **cap down tooth of the first entry**: the triangular face `(4, 20, 11)` = `{source 20, edge (11, 4)}` (dual node 15).
|
||||||
|
|
||||||
|
- maximum distance: 17
|
||||||
|
- distance histogram (faces by distance): `{0: 1, 1: 2, 2: 2, 3: 2, 4: 2, 5: 3, 6: 3, 7: 3, 8: 3, 9: 3, 10: 4, 11: 3, 12: 2, 13: 1, 14: 1, 15: 1, 16: 1, 17: 1}`
|
||||||
|
|
||||||
|
- dual cut figure: `full_medial_tire_cut_walk_2_dual.png`
|
||||||
|
- tire cut grid: `full_medial_tire_cut_walk_2_tires.png`
|
||||||
|
- combined PDF: `full_medial_tire_cut_walk_2.pdf`
|
||||||
|
|
||||||
|
| tread | depth | component | annular | up | singleton down | bite apexes | entry | closing cuts | up-apex cuts | shared/entry skipped |
|
||||||
|
|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|
|
||||||
|
| T1 | 1 | 0 | 10 | 3 | 7 | 0 | e3 | 1 | 2 | 1 |
|
||||||
|
| T2 | 2 | 0 | 15 | 7 | 8 | 0 | e3 | 1 | 6 | 1 |
|
||||||
|
| T3 | 3 | 0 | 10 | 8 | 0 | 1 | e7 | 2 | 7 | 1 |
|
||||||
|
|
||||||
|
## Removed Source-Dual Edges
|
||||||
|
|
||||||
|
- annular/cap: `[(3, 4), (3, 9), (8, 9), (11, 20), (15, 7)]`
|
||||||
|
- up apexes: `[(0, 3), (0, 5), (1, 2), (1, 6), (2, 9), (10, 18), (11, 12), (12, 4), (13, 5), (13, 19), (14, 6), (14, 15), (15, 16), (16, 17), (18, 19)]`
|
||||||
|
After Width: | Height: | Size: 243 KiB |
|
After Width: | Height: | Size: 191 KiB |
@@ -0,0 +1,200 @@
|
|||||||
|
"""Regenerate the ``full_walk`` contents (``.md`` report + ``_dual.png`` +
|
||||||
|
``_tires.png`` + combined ``.pdf``) for each configured medial tire cut walk.
|
||||||
|
|
||||||
|
Each walk fixes a reproducible source: a base maximal planar 5-connected graph
|
||||||
|
``random_maximal_planar_5_connected(n, seed)``, a chosen triangular face, the
|
||||||
|
deep-embedding cap vertex placed inside that face as the level source, and a
|
||||||
|
root entry tooth. The source-dual cut, its walk-distance labelling, and the
|
||||||
|
figures are all read off the deep embedding ``G'`` (whose dual-cut figure is now
|
||||||
|
drawn on a *valid straight-line embedding* of ``G'`` -- see
|
||||||
|
``medial_tire_dual_cut_experiment._straight_line_source_layout``).
|
||||||
|
|
||||||
|
Run with the repo venv::
|
||||||
|
|
||||||
|
../../../.venv/bin/python generate_full_walk.py # all walks
|
||||||
|
../../../.venv/bin/python generate_full_walk.py --walk 1 # just walk 1
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import networkx as nx
|
||||||
|
|
||||||
|
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
_EXP = os.path.dirname(_HERE)
|
||||||
|
sys.path.insert(0, _EXP)
|
||||||
|
|
||||||
|
import medial_tire_dual_cut_experiment as E # noqa: E402
|
||||||
|
from run_medial_tire_cut_experiment import ( # noqa: E402
|
||||||
|
random_maximal_planar_5_connected,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Walk configurations. Each is fully reproducible from (n, seed, face, entry).
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
WALKS = [
|
||||||
|
{
|
||||||
|
"index": 1,
|
||||||
|
"title": "Full medial tire cut walk 1",
|
||||||
|
"n": 20,
|
||||||
|
"seed": 59,
|
||||||
|
"face": (8, 9, 19),
|
||||||
|
"entry": 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 2,
|
||||||
|
"title": "Full medial tire cut walk 2",
|
||||||
|
"n": 20,
|
||||||
|
"seed": 2,
|
||||||
|
"face": (4, 12, 11),
|
||||||
|
"entry": 3,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def build_result(cfg):
|
||||||
|
"""Build the source-dual cut result dict for one walk configuration."""
|
||||||
|
G, graph_seed = random_maximal_planar_5_connected(
|
||||||
|
cfg["n"], cfg["seed"], min_connectivity=5)
|
||||||
|
G_prime, cap, depth = E.deep_embedding(G, cfg["face"])
|
||||||
|
result = E.medial_tire_dual_cut(G_prime, cap, cfg["entry"])
|
||||||
|
result["base_graph"] = G
|
||||||
|
result["chosen_face"] = tuple(cfg["face"])
|
||||||
|
result["cap_vertex"] = cap
|
||||||
|
result["deep_depth"] = depth
|
||||||
|
result["graph_seed"] = graph_seed
|
||||||
|
result["base_min_degree"] = min(dict(G.degree()).values())
|
||||||
|
result["base_connectivity"] = nx.node_connectivity(G)
|
||||||
|
result["min_degree"] = min(dict(G_prime.degree()).values())
|
||||||
|
result["connectivity"] = nx.node_connectivity(G_prime)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _tree_report(result):
|
||||||
|
"""``(is_tree, n_faces, n_edges, n_components, acyclic)`` for the cut."""
|
||||||
|
cut = E.dual_cut_graph(result)
|
||||||
|
n = cut.number_of_nodes()
|
||||||
|
e = cut.number_of_edges()
|
||||||
|
comps = nx.number_connected_components(cut)
|
||||||
|
return nx.is_tree(cut), n, e, comps, (e == n - comps)
|
||||||
|
|
||||||
|
|
||||||
|
def _distance_histogram(dist):
|
||||||
|
"""Histogram of dual faces by walk distance, as an ordered dict."""
|
||||||
|
hist = {}
|
||||||
|
for d in dist.values():
|
||||||
|
hist[d] = hist.get(d, 0) + 1
|
||||||
|
return {k: hist[k] for k in sorted(hist)}
|
||||||
|
|
||||||
|
|
||||||
|
def render_markdown(result, cfg):
|
||||||
|
"""The walk report in the committed ``.md`` format."""
|
||||||
|
G = result["G"]
|
||||||
|
base = result["base_graph"]
|
||||||
|
removed = result["removed_dual_edges"]
|
||||||
|
res = result["results"]
|
||||||
|
i = cfg["index"]
|
||||||
|
em = result["entry_medial_vertex"]
|
||||||
|
|
||||||
|
is_tree, n_faces, n_edges, comps, acyclic = _tree_report(result)
|
||||||
|
dist, root = E.dual_cut_distances(result)
|
||||||
|
root_face = result["faces"][root]
|
||||||
|
hist = _distance_histogram(dist)
|
||||||
|
max_dist = max(dist.values()) if dist else 0
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
f"# {cfg['title']}",
|
||||||
|
"",
|
||||||
|
f"- base vertices: {base.number_of_nodes()}",
|
||||||
|
f"- deep-embedded vertices: {G.number_of_nodes()}",
|
||||||
|
f"- deep-embedded edges: {G.number_of_edges()}",
|
||||||
|
f"- graph seed: {result['graph_seed']}",
|
||||||
|
f"- deep-embedded minimum degree: {result['min_degree']}",
|
||||||
|
f"- chosen face: {result['chosen_face']}",
|
||||||
|
f"- chosen source cap vertex: {result['source']}",
|
||||||
|
f"- root entry tooth: e{result['entry_edge']} "
|
||||||
|
f"(apex medial vertex = level-1 edge {em})",
|
||||||
|
f"- recognised treads: {len(res)}",
|
||||||
|
f"- skipped treads: {result['skipped']}",
|
||||||
|
f"- removed source-dual edges: {len(removed)}",
|
||||||
|
f"- annular/cap cuts: {len(result['annular_cut_edges'])}",
|
||||||
|
f"- up-apex cuts: {len(result['apex_cut_edges'])}",
|
||||||
|
"",
|
||||||
|
f"- **source-dual cut is a tree: {is_tree}** ({n_faces} dual faces, "
|
||||||
|
f"{n_edges} edges, {comps} component(s), acyclic={acyclic})",
|
||||||
|
"",
|
||||||
|
"## Walk-distance labelling of the source-dual cut",
|
||||||
|
"",
|
||||||
|
"Each dual face (vertex of the source-dual cut) is labelled by its "
|
||||||
|
"distance, within the cut, from the **cap down tooth of the first "
|
||||||
|
f"entry**: the triangular face `{root_face}` = "
|
||||||
|
f"`{{source {result['source']}, edge {em}}}` (dual node {root}).",
|
||||||
|
"",
|
||||||
|
f"- maximum distance: {max_dist}",
|
||||||
|
f"- distance histogram (faces by distance): `{hist}`",
|
||||||
|
"",
|
||||||
|
f"- dual cut figure: `full_medial_tire_cut_walk_{i}_dual.png`",
|
||||||
|
f"- tire cut grid: `full_medial_tire_cut_walk_{i}_tires.png`",
|
||||||
|
f"- combined PDF: `full_medial_tire_cut_walk_{i}.pdf`",
|
||||||
|
"",
|
||||||
|
"| tread | depth | component | annular | up | singleton down | "
|
||||||
|
"bite apexes | entry | closing cuts | up-apex cuts | "
|
||||||
|
"shared/entry skipped |",
|
||||||
|
"|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|",
|
||||||
|
]
|
||||||
|
for idx, key in enumerate(sorted(res), start=1):
|
||||||
|
d, comp = key
|
||||||
|
rec = res[key]
|
||||||
|
g, bij, entry = rec["g"], rec["bij"], rec["entry_edge"]
|
||||||
|
up = len(g.up_edges)
|
||||||
|
apex = len(E.up_apex_cuts(g, entry, bij))
|
||||||
|
lines.append(
|
||||||
|
f"| T{idx} | {d} | {comp} | {g.n} | {up} | "
|
||||||
|
f"{len(g.singleton_down_edges)} | {len(g.bites)} | e{entry} | "
|
||||||
|
f"{len(rec['cuts'])} | {apex} | {up - apex} |")
|
||||||
|
lines += [
|
||||||
|
"",
|
||||||
|
"## Removed Source-Dual Edges",
|
||||||
|
"",
|
||||||
|
f"- annular/cap: `{sorted(result['annular_cut_edges'])}`",
|
||||||
|
f"- up apexes: `{sorted(result['apex_cut_edges'])}`",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def generate(cfg):
|
||||||
|
"""Build one walk and write its ``.md`` report and three figures."""
|
||||||
|
result = build_result(cfg)
|
||||||
|
i = cfg["index"]
|
||||||
|
stem = os.path.join(_HERE, f"full_medial_tire_cut_walk_{i}")
|
||||||
|
|
||||||
|
with open(f"{stem}.md", "w") as fh:
|
||||||
|
fh.write(render_markdown(result, cfg))
|
||||||
|
E.draw_png(result, f"{stem}_dual.png")
|
||||||
|
E.draw_tire_cuts_png(result, f"{stem}_tires.png")
|
||||||
|
E.draw_combined_pdf(result, f"{stem}.pdf")
|
||||||
|
|
||||||
|
print(f"walk {i}: wrote {stem}.md + _dual.png + _tires.png + .pdf")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description=__doc__,
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
parser.add_argument("--walk", type=int, default=None,
|
||||||
|
help="regenerate only this walk index (default: all)")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
for cfg in WALKS:
|
||||||
|
if args.walk is None or cfg["index"] == args.walk:
|
||||||
|
generate(cfg)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
After Width: | Height: | Size: 63 KiB |
@@ -0,0 +1,17 @@
|
|||||||
|
"""Compatibility wrapper for the medial tire cut labelling script."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
PAPER_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
LIB_DIR = os.path.join(PAPER_DIR, "lib")
|
||||||
|
if LIB_DIR not in sys.path:
|
||||||
|
sys.path.insert(0, LIB_DIR)
|
||||||
|
|
||||||
|
from medial_tire_cut_labelling import main
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,620 @@
|
|||||||
|
"""Medial tire cut experiment.
|
||||||
|
|
||||||
|
End-to-end experiment for the *Medial Tire Cuts* paper:
|
||||||
|
|
||||||
|
1. Generate a 5-connected maximal planar graph G on n vertices, using
|
||||||
|
``plantri -c5`` when available and verifying node connectivity.
|
||||||
|
2. Build its medial graph M(G).
|
||||||
|
3. Take the nested tire decomposition at one random vertex level source: the
|
||||||
|
BFS-level treads, each realized as a FullMedialTireGraph.
|
||||||
|
4. Walk-depth label and cut each full medial tire graph, chaining the labels
|
||||||
|
down the tire tree, and assemble one final cut graph of M(G) with a global
|
||||||
|
label map.
|
||||||
|
|
||||||
|
This script produces *data* (the final cut graph and its labels); it draws
|
||||||
|
nothing. Anything for the paper (figures) lives in a separate script that
|
||||||
|
imports ``run_experiment`` from here.
|
||||||
|
|
||||||
|
Chaining rule (walk depths across the tire tree).
|
||||||
|
* The root tread (no recognised parent) is entered at an arbitrary up tooth
|
||||||
|
with walk depth 0.
|
||||||
|
* A child tread is entered at the up tooth whose apex is the *same medial
|
||||||
|
vertex* as the parent's down tooth of lowest walk depth -- a parent down
|
||||||
|
tooth and the child up tooth glued to it across the shared level cycle are
|
||||||
|
the same medial vertex of M(G). The entry up tooth's walk depth is that
|
||||||
|
parent down tooth's depth + 1, and the walk increments locally from there.
|
||||||
|
* The source cap contributes one additional cut. It is placed at the
|
||||||
|
counter-clockwise source edge incident to the cap down tooth whose apex is
|
||||||
|
the root tread's entry up tooth.
|
||||||
|
|
||||||
|
Run with the repo venv (networkx + scipy): ``.venv/bin/python``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
import networkx as nx
|
||||||
|
|
||||||
|
# Reuse the realization pipeline from the medial tire decompositions paper, and
|
||||||
|
# the walk-depth labelling/cut from this paper's companion script.
|
||||||
|
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
_MTD = os.path.normpath(os.path.join(
|
||||||
|
_HERE, "..", "..",
|
||||||
|
"medial_tire_decompositions_of_plane_triangulations", "experiments"))
|
||||||
|
_PAPER_DIR = os.path.dirname(_HERE)
|
||||||
|
_CUT_LIB = os.path.join(_PAPER_DIR, "lib")
|
||||||
|
sys.path.insert(0, _HERE)
|
||||||
|
sys.path.insert(0, _MTD)
|
||||||
|
sys.path.insert(0, _CUT_LIB)
|
||||||
|
|
||||||
|
from tire_realization_analysis import ( # noqa: E402
|
||||||
|
ekey, medial_graph, medial_tire_facemodel,
|
||||||
|
random_maximal_planar, recognise, triangular_faces,
|
||||||
|
)
|
||||||
|
from medial_tire_cut_labelling import door_bite, label_and_cut # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Component-based tread recognition.
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def _edge_face_data(faces):
|
||||||
|
edge_faces = defaultdict(list)
|
||||||
|
for i, face in enumerate(faces):
|
||||||
|
for x, y in ((face[0], face[1]), (face[1], face[2]), (face[2], face[0])):
|
||||||
|
edge_faces[ekey(x, y)].append(i)
|
||||||
|
return edge_faces
|
||||||
|
|
||||||
|
|
||||||
|
def _depth_components(faces, edge_faces, levels):
|
||||||
|
depths = [min(levels[v] for v in face) for face in faces]
|
||||||
|
dual_adj = defaultdict(set)
|
||||||
|
for incident in edge_faces.values():
|
||||||
|
for a in range(len(incident)):
|
||||||
|
for b in range(a + 1, len(incident)):
|
||||||
|
dual_adj[incident[a]].add(incident[b])
|
||||||
|
dual_adj[incident[b]].add(incident[a])
|
||||||
|
|
||||||
|
comps = []
|
||||||
|
seen = [False] * len(faces)
|
||||||
|
for start in range(len(faces)):
|
||||||
|
if seen[start]:
|
||||||
|
continue
|
||||||
|
depth = depths[start]
|
||||||
|
stack = [start]
|
||||||
|
comp = []
|
||||||
|
seen[start] = True
|
||||||
|
while stack:
|
||||||
|
face = stack.pop()
|
||||||
|
comp.append(face)
|
||||||
|
for other in dual_adj[face]:
|
||||||
|
if not seen[other] and depths[other] == depth:
|
||||||
|
seen[other] = True
|
||||||
|
stack.append(other)
|
||||||
|
comps.append((depth, tuple(sorted(comp))))
|
||||||
|
return comps
|
||||||
|
|
||||||
|
|
||||||
|
def _tread_from_component(faces, levels, face_indices):
|
||||||
|
tread_faces = [faces[i] for i in face_indices]
|
||||||
|
if not tread_faces:
|
||||||
|
return None
|
||||||
|
depth = min(min(levels[v] for v in face) for face in tread_faces)
|
||||||
|
annular, up, down = set(), set(), set()
|
||||||
|
face_of_down = defaultdict(int)
|
||||||
|
for face in tread_faces:
|
||||||
|
for x, y in ((face[0], face[1]), (face[1], face[2]), (face[2], face[0])):
|
||||||
|
e = ekey(x, y)
|
||||||
|
lx, ly = levels[x], levels[y]
|
||||||
|
if {lx, ly} == {depth, depth + 1}:
|
||||||
|
annular.add(e)
|
||||||
|
elif lx == ly == depth:
|
||||||
|
up.add(e)
|
||||||
|
elif lx == ly == depth + 1:
|
||||||
|
down.add(e)
|
||||||
|
face_of_down[e] += 1
|
||||||
|
if len(annular) < 3:
|
||||||
|
return None
|
||||||
|
return {
|
||||||
|
"tread_faces": tread_faces,
|
||||||
|
"annular": annular,
|
||||||
|
"up": up,
|
||||||
|
"down": down,
|
||||||
|
"bites": {e for e in down if face_of_down[e] == 2},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _build_treads(faces, levels):
|
||||||
|
"""Recognise simple cycles inside connected depth components.
|
||||||
|
|
||||||
|
The returned ``treads`` keeps the existing simple-tire interface used by
|
||||||
|
the labelling code. ``tread_meta`` records the connected depth component
|
||||||
|
each simple cycle came from, so compound tires can be chained through
|
||||||
|
shared up apexes rather than seeded as unrelated roots.
|
||||||
|
"""
|
||||||
|
treads, skipped, tread_meta = {}, [], {}
|
||||||
|
edge_faces = _edge_face_data(faces)
|
||||||
|
comps = sorted(_depth_components(faces, edge_faces, levels),
|
||||||
|
key=lambda item: (item[0], item[1]))
|
||||||
|
component_count = defaultdict(int)
|
||||||
|
tire_count = defaultdict(int)
|
||||||
|
for depth, face_indices in comps:
|
||||||
|
component = component_count[depth]
|
||||||
|
component_count[depth] += 1
|
||||||
|
tread = _tread_from_component(faces, levels, face_indices)
|
||||||
|
if tread is None:
|
||||||
|
skipped.append(((depth, component), "no tread faces"))
|
||||||
|
continue
|
||||||
|
if len(tread["up"]) < 3:
|
||||||
|
skipped.append(((depth, component), f"only {len(tread['up'])} up teeth"))
|
||||||
|
continue
|
||||||
|
tires = recognise(medial_tire_facemodel(tread["tread_faces"]), tread)
|
||||||
|
if not tires:
|
||||||
|
skipped.append(((depth, component), "no annular cycle recognised as a tire"))
|
||||||
|
continue
|
||||||
|
compound = (depth, component)
|
||||||
|
cycle_count = len(tires)
|
||||||
|
for cycle, gb in enumerate(tires):
|
||||||
|
key = (depth, tire_count[depth])
|
||||||
|
tire_count[depth] += 1
|
||||||
|
treads[key] = gb
|
||||||
|
tread_meta[key] = {
|
||||||
|
"compound": compound,
|
||||||
|
"cycle": cycle,
|
||||||
|
"cycle_count": cycle_count,
|
||||||
|
"face_indices": face_indices,
|
||||||
|
}
|
||||||
|
return treads, skipped, tread_meta
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# 4. Walk-depth labelling and cut, chained down the tire tree.
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def _apex_vertex(g, bij, edge):
|
||||||
|
"""The medial vertex that is the apex of the tooth on annular ``edge``."""
|
||||||
|
return bij[g.apex_of_edge(edge)]
|
||||||
|
|
||||||
|
|
||||||
|
def _label_treads(treads, results, root_entry_edge=None, tread_meta=None):
|
||||||
|
"""Fill ``results[(d, c)]`` with the walk-depth labelling and cuts for every
|
||||||
|
recognised tire ``c`` of every tread depth ``d``, chaining child entries to
|
||||||
|
parent down teeth.
|
||||||
|
|
||||||
|
``treads`` maps ``(depth, component)`` -> ``(g, bij)``; a tread depth may
|
||||||
|
carry several tires (one per disjoint annular cycle). The root tire
|
||||||
|
``(root_d, 0)`` is entered at ``root_entry_edge`` when given -- it must be
|
||||||
|
one of that tire's up teeth -- otherwise at an arbitrary up tooth. Each
|
||||||
|
other tire chains to whichever parent-depth down tooth (across all parent
|
||||||
|
tires) shares its apex, at the lowest parent walk depth.
|
||||||
|
"""
|
||||||
|
if not treads:
|
||||||
|
return
|
||||||
|
depths = sorted({k[0] for k in treads})
|
||||||
|
root_d = depths[0]
|
||||||
|
for d in depths:
|
||||||
|
# apex medial vertex -> child start depth, over all parent-depth tires
|
||||||
|
parent_down = {}
|
||||||
|
for pk in (k for k in treads if k[0] == d - 1):
|
||||||
|
pg, pbij = treads[pk]
|
||||||
|
pdepth = results[pk]["depth"]
|
||||||
|
for e in pg.down_edges:
|
||||||
|
apex = _apex_vertex(pg, pbij, e)
|
||||||
|
value = pdepth[e] + 1
|
||||||
|
if apex not in parent_down or value < parent_down[apex]:
|
||||||
|
parent_down[apex] = value
|
||||||
|
has_parent = any(k[0] == d - 1 for k in treads)
|
||||||
|
pending = sorted(k for k in treads if k[0] == d)
|
||||||
|
while pending:
|
||||||
|
progressed = False
|
||||||
|
deferred = []
|
||||||
|
use_sibling_entries = has_parent and not any(
|
||||||
|
parent_down.keys() & {treads[key][1][f"u{m}"]
|
||||||
|
for m in treads[key][0].up_edges}
|
||||||
|
for key in pending
|
||||||
|
if key not in results
|
||||||
|
)
|
||||||
|
for key in pending:
|
||||||
|
if key in results:
|
||||||
|
continue
|
||||||
|
g, bij = treads[key]
|
||||||
|
child_up_apex = {bij[f"u{m}"]: m for m in g.up_edges}
|
||||||
|
entry = None
|
||||||
|
if not has_parent:
|
||||||
|
if (key == (root_d, 0) and root_entry_edge is not None
|
||||||
|
and root_entry_edge in g.up_edges):
|
||||||
|
entry = (root_entry_edge, 0)
|
||||||
|
else:
|
||||||
|
entry = (g.up_edges[0], 0) # arbitrary entry
|
||||||
|
else:
|
||||||
|
best = None
|
||||||
|
for apex, value in parent_down.items():
|
||||||
|
if apex in child_up_apex and (best is None or value < best[1]):
|
||||||
|
best = (child_up_apex[apex], value)
|
||||||
|
if best is not None: # chains to a parent down tooth
|
||||||
|
entry = best
|
||||||
|
elif use_sibling_entries:
|
||||||
|
compound = (
|
||||||
|
tread_meta.get(key, {}).get("compound")
|
||||||
|
if tread_meta is not None else None
|
||||||
|
)
|
||||||
|
sibling_best = None
|
||||||
|
if compound is not None:
|
||||||
|
for sk, srec in results.items():
|
||||||
|
if sk[0] != d or srec.get("compound") != compound:
|
||||||
|
continue
|
||||||
|
sg, sbij = srec["g"], srec["bij"]
|
||||||
|
for edge in sg.up_edges:
|
||||||
|
apex = sbij[f"u{edge}"]
|
||||||
|
if apex not in child_up_apex:
|
||||||
|
continue
|
||||||
|
value = srec["depth"][edge] + 1
|
||||||
|
if (sibling_best is None
|
||||||
|
or value > sibling_best[1]):
|
||||||
|
sibling_best = (child_up_apex[apex], value)
|
||||||
|
if sibling_best is not None:
|
||||||
|
entry = sibling_best
|
||||||
|
|
||||||
|
if entry is None:
|
||||||
|
deferred.append(key)
|
||||||
|
continue
|
||||||
|
entry_edge, start_depth = entry
|
||||||
|
depth, cuts = label_and_cut(g, entry_edge, start_depth=start_depth)
|
||||||
|
results[key] = {"g": g, "bij": bij, "entry_edge": entry_edge,
|
||||||
|
"start_depth": start_depth, "depth": depth,
|
||||||
|
"cuts": cuts}
|
||||||
|
if tread_meta is not None and key in tread_meta:
|
||||||
|
results[key].update(tread_meta[key])
|
||||||
|
progressed = True
|
||||||
|
|
||||||
|
if progressed:
|
||||||
|
pending = deferred
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Degenerate component: no parent or labelled sibling gives an
|
||||||
|
# entry. Seed it so any remaining sibling cycles can chain to it.
|
||||||
|
key = deferred[0]
|
||||||
|
g, bij = treads[key]
|
||||||
|
entry_edge, start_depth = g.up_edges[0], 0
|
||||||
|
depth, cuts = label_and_cut(g, entry_edge, start_depth=start_depth)
|
||||||
|
results[key] = {"g": g, "bij": bij, "entry_edge": entry_edge,
|
||||||
|
"start_depth": start_depth, "depth": depth,
|
||||||
|
"cuts": cuts}
|
||||||
|
if tread_meta is not None and key in tread_meta:
|
||||||
|
results[key].update(tread_meta[key])
|
||||||
|
pending = deferred[1:]
|
||||||
|
|
||||||
|
|
||||||
|
def _cap_cut(G, emb, source, levels, results):
|
||||||
|
"""The source-cap cut determined by the first recognised tread's entry.
|
||||||
|
|
||||||
|
If the root tread enters at an up tooth whose apex is the level-1 edge
|
||||||
|
``xy``, then ``xy`` is a down tooth of the source cap. Cut the
|
||||||
|
counter-clockwise source edge incident to that down tooth. The returned
|
||||||
|
record also stores the local neighbour split used to unzip the medial
|
||||||
|
vertex in the whole medial graph.
|
||||||
|
"""
|
||||||
|
if not results:
|
||||||
|
return []
|
||||||
|
|
||||||
|
root_depth = min(results)
|
||||||
|
rec = results[root_depth]
|
||||||
|
g, bij = rec["g"], rec["bij"]
|
||||||
|
x, y = _apex_vertex(g, bij, rec["entry_edge"])
|
||||||
|
if levels.get(x) != 1 or levels.get(y) != 1:
|
||||||
|
return []
|
||||||
|
|
||||||
|
order = list(emb.neighbors_cw_order(source))
|
||||||
|
if x not in order or y not in order:
|
||||||
|
return []
|
||||||
|
|
||||||
|
next_cw = {v: order[(i + 1) % len(order)] for i, v in enumerate(order)}
|
||||||
|
prev_cw = {v: order[(i - 1) % len(order)] for i, v in enumerate(order)}
|
||||||
|
if next_cw[x] == y:
|
||||||
|
cut_endpoint, other_endpoint = x, y
|
||||||
|
elif next_cw[y] == x:
|
||||||
|
cut_endpoint, other_endpoint = y, x
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
other_cap_endpoint = prev_cw[cut_endpoint]
|
||||||
|
mv = ekey(source, cut_endpoint)
|
||||||
|
return [{
|
||||||
|
"medial_vertex": mv,
|
||||||
|
"down_tooth": ekey(cut_endpoint, other_endpoint),
|
||||||
|
"neighbours_a": [
|
||||||
|
ekey(source, other_endpoint),
|
||||||
|
ekey(cut_endpoint, other_endpoint),
|
||||||
|
],
|
||||||
|
"neighbours_b": [
|
||||||
|
ekey(source, other_cap_endpoint),
|
||||||
|
ekey(cut_endpoint, other_cap_endpoint),
|
||||||
|
],
|
||||||
|
}]
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Assemble one final cut graph of M(G) with a global label map.
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def _assemble_cut_graph(M, results, cap_cuts=None):
|
||||||
|
"""Apply every tread's cuts to M(G).
|
||||||
|
|
||||||
|
Each cut duplicates an annular medial vertex, splitting its four incident
|
||||||
|
medial edges along the slit between the two teeth meeting there: the tooth
|
||||||
|
on the previous annular edge (with that edge's far annular vertex) goes to
|
||||||
|
one copy, the tooth on the next annular edge to the other.
|
||||||
|
|
||||||
|
Returns ``(H, label_records, warnings)`` where ``H`` is the cut graph (a
|
||||||
|
networkx graph whose split vertices are keyed ``(medial_vertex, "A"/"B",
|
||||||
|
tread)``) and ``label_records`` lists every tooth's walk depth.
|
||||||
|
"""
|
||||||
|
# Per cut annular vertex: map each original neighbour -> which copy keeps it.
|
||||||
|
split = {} # medial_vertex -> {neighbour_medial_vertex: copy_node}
|
||||||
|
warnings = []
|
||||||
|
for i, c in enumerate(cap_cuts or []):
|
||||||
|
mv = c["medial_vertex"]
|
||||||
|
if mv in split:
|
||||||
|
warnings.append(f"cap cut at {mv} was already cut; skipped")
|
||||||
|
continue
|
||||||
|
copy_a = (mv, "A", f"cap{i}")
|
||||||
|
copy_b = (mv, "B", f"cap{i}")
|
||||||
|
split[mv] = {
|
||||||
|
**{v: copy_a for v in c["neighbours_a"]},
|
||||||
|
**{v: copy_b for v in c["neighbours_b"]},
|
||||||
|
}
|
||||||
|
for key in sorted(results):
|
||||||
|
td = key[0]
|
||||||
|
g, bij = results[key]["g"], results[key]["bij"]
|
||||||
|
n = g.n
|
||||||
|
for c in results[key]["cuts"]:
|
||||||
|
kk = c.vertex
|
||||||
|
if kk is None:
|
||||||
|
continue
|
||||||
|
mv = bij[f"a{kk}"]
|
||||||
|
if mv in split:
|
||||||
|
warnings.append(f"annular vertex a{kk} of tread {key} cut twice; "
|
||||||
|
f"second cut not applied")
|
||||||
|
continue
|
||||||
|
e_prev, e_next = (kk - 1) % n, kk
|
||||||
|
copy_a = (mv, "A", td)
|
||||||
|
copy_b = (mv, "B", td)
|
||||||
|
split[mv] = {
|
||||||
|
bij[f"a{(kk - 1) % n}"]: copy_a,
|
||||||
|
_apex_vertex(g, bij, e_prev): copy_a,
|
||||||
|
bij[f"a{(kk + 1) % n}"]: copy_b,
|
||||||
|
_apex_vertex(g, bij, e_next): copy_b,
|
||||||
|
}
|
||||||
|
|
||||||
|
def resolve(node, other):
|
||||||
|
return split[node][other] if node in split else node
|
||||||
|
|
||||||
|
H = nx.Graph()
|
||||||
|
H.add_nodes_from(v for v in M.nodes() if v not in split)
|
||||||
|
for v, copies in split.items():
|
||||||
|
H.add_nodes_from(set(copies.values()))
|
||||||
|
for u, v in M.edges():
|
||||||
|
H.add_edge(resolve(u, v), resolve(v, u))
|
||||||
|
|
||||||
|
label_records = []
|
||||||
|
for key in sorted(results):
|
||||||
|
td = key[0]
|
||||||
|
g, bij, depth = results[key]["g"], results[key]["bij"], results[key]["depth"]
|
||||||
|
for k in range(g.n):
|
||||||
|
role = ("up" if g.tooth_word[k] == "U"
|
||||||
|
else "bite" if door_bite(g, k) is not None else "down")
|
||||||
|
label_records.append({
|
||||||
|
"tread": td, "comp": key[1], "edge": k, "role": role,
|
||||||
|
"apex": _apex_vertex(g, bij, k), "walk": depth[k],
|
||||||
|
})
|
||||||
|
return H, label_records, warnings
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Driver.
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def random_maximal_planar_5_connected(n: int, seed: int, flips: int = 400,
|
||||||
|
min_connectivity: int = 5,
|
||||||
|
attempts: int = 1000) -> tuple[nx.Graph, int]:
|
||||||
|
"""Generate a maximal planar graph with node connectivity at least
|
||||||
|
``min_connectivity``. The returned seed is the actual sample seed used."""
|
||||||
|
if min_connectivity <= 0:
|
||||||
|
return random_maximal_planar(n, seed, flips=flips), seed
|
||||||
|
|
||||||
|
if min_connectivity >= 5:
|
||||||
|
plantri = os.path.normpath(os.path.join(_HERE, "..", "..", "..",
|
||||||
|
"plantri", "plantri"))
|
||||||
|
if os.path.exists(plantri):
|
||||||
|
data = subprocess.check_output(
|
||||||
|
[plantri, "-c5", "-g", str(n)],
|
||||||
|
stderr=subprocess.DEVNULL)
|
||||||
|
graphs = [
|
||||||
|
line for line in data.splitlines()
|
||||||
|
if line and not line.startswith(b">>")
|
||||||
|
]
|
||||||
|
if graphs:
|
||||||
|
for offset in range(len(graphs)):
|
||||||
|
G = nx.from_graph6_bytes(graphs[(seed + offset) % len(graphs)])
|
||||||
|
G = nx.convert_node_labels_to_integers(G)
|
||||||
|
if nx.node_connectivity(G) >= min_connectivity:
|
||||||
|
return G, seed + offset
|
||||||
|
|
||||||
|
for offset in range(attempts):
|
||||||
|
sample_seed = seed + offset
|
||||||
|
G = random_maximal_planar(n, sample_seed, flips=flips)
|
||||||
|
if nx.node_connectivity(G) >= min_connectivity:
|
||||||
|
return G, sample_seed
|
||||||
|
raise RuntimeError(
|
||||||
|
f"no random maximal planar graph on {n} vertices with "
|
||||||
|
f"node connectivity >= {min_connectivity} found in {attempts} attempts "
|
||||||
|
f"starting at seed {seed}")
|
||||||
|
|
||||||
|
|
||||||
|
def run_experiment(n: int = 12, seed: int = 0, flips: int = 400,
|
||||||
|
min_connectivity: int = 5, attempts: int = 1000,
|
||||||
|
min_degree: int | None = None) -> dict:
|
||||||
|
"""Run the full pipeline and return a structured result.
|
||||||
|
|
||||||
|
Result keys: ``n, seed, G, M, source, treads`` (dict depth -> (g, bij)),
|
||||||
|
``results`` (dict depth -> labelling/cut record), ``skipped`` (list of
|
||||||
|
(depth, reason)), ``cut_graph`` (networkx graph), ``labels`` (list of tooth
|
||||||
|
records), ``warnings``.
|
||||||
|
"""
|
||||||
|
if min_degree is not None:
|
||||||
|
min_connectivity = max(min_connectivity, min_degree)
|
||||||
|
G, graph_seed = random_maximal_planar_5_connected(
|
||||||
|
n, seed, flips=flips, min_connectivity=min_connectivity, attempts=attempts)
|
||||||
|
faces, emb = triangular_faces(G)
|
||||||
|
M = medial_graph(G)
|
||||||
|
source = random.Random(f"source-{graph_seed}").choice(sorted(G.nodes()))
|
||||||
|
levels = nx.single_source_shortest_path_length(G, source)
|
||||||
|
|
||||||
|
treads, skipped, tread_meta = _build_treads(faces, levels)
|
||||||
|
|
||||||
|
results = {}
|
||||||
|
_label_treads(treads, results, tread_meta=tread_meta)
|
||||||
|
cap_cuts = _cap_cut(G, emb, source, levels, results)
|
||||||
|
cut_graph, labels, warnings = _assemble_cut_graph(M, results, cap_cuts=cap_cuts)
|
||||||
|
return {
|
||||||
|
"n": n, "seed": seed, "graph_seed": graph_seed,
|
||||||
|
"min_degree": min(dict(G.degree()).values()),
|
||||||
|
"connectivity": nx.node_connectivity(G),
|
||||||
|
"G": G, "M": M, "source": source,
|
||||||
|
"treads": treads, "tread_meta": tread_meta,
|
||||||
|
"results": results, "cap_cuts": cap_cuts,
|
||||||
|
"skipped": skipped,
|
||||||
|
"cut_graph": cut_graph, "labels": labels, "warnings": warnings,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Serialization / reporting.
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
def _vname(v) -> str:
|
||||||
|
"""Stable string name for a medial vertex (an edge key) or a split node."""
|
||||||
|
if isinstance(v, tuple) and len(v) == 3 and v[1] in ("A", "B"):
|
||||||
|
mv, side, d = v
|
||||||
|
return f"{mv[0]}-{mv[1]}#{side}@T{d}"
|
||||||
|
return f"{v[0]}-{v[1]}"
|
||||||
|
|
||||||
|
|
||||||
|
def to_json(result: dict) -> dict:
|
||||||
|
res = result["results"]
|
||||||
|
treads_out = []
|
||||||
|
for key in sorted(res):
|
||||||
|
d, comp = key
|
||||||
|
g, bij = res[key]["g"], res[key]["bij"]
|
||||||
|
depth, cuts = res[key]["depth"], res[key]["cuts"]
|
||||||
|
teeth = [{
|
||||||
|
"edge": k,
|
||||||
|
"role": ("up" if g.tooth_word[k] == "U"
|
||||||
|
else "bite" if door_bite(g, k) is not None else "down"),
|
||||||
|
"apex": _vname(_apex_vertex(g, bij, k)),
|
||||||
|
"walk": depth[k],
|
||||||
|
} for k in range(g.n)]
|
||||||
|
treads_out.append({
|
||||||
|
"depth": d, "comp": comp, "n": g.n, "tooth_word": g.tooth_word,
|
||||||
|
"bites": sorted(list(b) for b in g.bites),
|
||||||
|
"entry_edge": res[key]["entry_edge"], "start_depth": res[key]["start_depth"],
|
||||||
|
"teeth": teeth,
|
||||||
|
"cuts": [{
|
||||||
|
"annular_index": c.vertex,
|
||||||
|
"annular_vertex": _vname(bij[f"a{c.vertex}"]),
|
||||||
|
"last_edge": c.last_tooth, "closing_edge": c.closing_tooth,
|
||||||
|
} for c in cuts],
|
||||||
|
})
|
||||||
|
H = result["cut_graph"]
|
||||||
|
return {
|
||||||
|
"n": result["n"], "seed": result["seed"],
|
||||||
|
"graph_seed": result["graph_seed"], "min_degree": result["min_degree"],
|
||||||
|
"connectivity": result["connectivity"],
|
||||||
|
"source": result["source"],
|
||||||
|
"graph_edges": sorted([int(u), int(v)] for u, v in result["G"].edges()),
|
||||||
|
"medial_vertices": result["M"].number_of_nodes(),
|
||||||
|
"skipped": [[d, why] for d, why in result["skipped"]],
|
||||||
|
"cap_cuts": [{
|
||||||
|
"medial_vertex": _vname(c["medial_vertex"]),
|
||||||
|
"down_tooth": _vname(c["down_tooth"]),
|
||||||
|
} for c in result["cap_cuts"]],
|
||||||
|
"treads": treads_out,
|
||||||
|
"cut_graph": {
|
||||||
|
"nodes": sorted(_vname(v) for v in H.nodes()),
|
||||||
|
"edges": sorted([_vname(u), _vname(v)] for u, v in H.edges()),
|
||||||
|
},
|
||||||
|
"labels": [{
|
||||||
|
"tread": r["tread"], "comp": r.get("comp", 0),
|
||||||
|
"edge": r["edge"], "role": r["role"],
|
||||||
|
"apex": _vname(r["apex"]), "walk": r["walk"],
|
||||||
|
} for r in result["labels"]],
|
||||||
|
"warnings": result["warnings"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def summary(result: dict) -> str:
|
||||||
|
H, res = result["cut_graph"], result["results"]
|
||||||
|
lines = [
|
||||||
|
f"random maximal planar graph: n={result['n']} requested seed={result['seed']} "
|
||||||
|
f"graph seed={result['graph_seed']} "
|
||||||
|
f"({result['G'].number_of_edges()} edges, "
|
||||||
|
f"connectivity {result['connectivity']}, min degree {result['min_degree']})",
|
||||||
|
f"medial graph M(G): {result['M'].number_of_nodes()} vertices",
|
||||||
|
f"level source: vertex {result['source']}",
|
||||||
|
f"recognised tires (depth, component): {sorted(res)}",
|
||||||
|
f"skipped treads: {result['skipped']}",
|
||||||
|
]
|
||||||
|
for key in sorted(res):
|
||||||
|
d, comp = key
|
||||||
|
g = res[key]["g"]
|
||||||
|
ncuts = len(res[key]["cuts"])
|
||||||
|
lines.append(
|
||||||
|
f" tread {d}.{comp}: |A(T)|={g.n} word={g.tooth_word} "
|
||||||
|
f"bites={sorted(g.bites)} entry=e{res[key]['entry_edge']} "
|
||||||
|
f"start_depth={res[key]['start_depth']} cuts={ncuts}")
|
||||||
|
lines.append(
|
||||||
|
f"final cut graph: {H.number_of_nodes()} vertices, "
|
||||||
|
f"{H.number_of_edges()} edges, "
|
||||||
|
f"{len(result['cap_cuts']) + sum(len(r['cuts']) for r in res.values())} cuts total")
|
||||||
|
if result["warnings"]:
|
||||||
|
lines.append("warnings: " + "; ".join(result["warnings"]))
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
parser.add_argument("-n", type=int, default=12, help="number of vertices")
|
||||||
|
parser.add_argument("--seed", type=int, default=0)
|
||||||
|
parser.add_argument("--flips", type=int, default=400,
|
||||||
|
help="number of random diagonal flips when building G")
|
||||||
|
parser.add_argument("--min-connectivity", type=int, default=5,
|
||||||
|
help="reject random graphs below this node connectivity")
|
||||||
|
parser.add_argument("--min-degree", type=int, default=None,
|
||||||
|
help="compatibility alias; also raises min-connectivity")
|
||||||
|
parser.add_argument("--attempts", type=int, default=1000,
|
||||||
|
help="number of consecutive seeds to try for sampling")
|
||||||
|
parser.add_argument("--json", metavar="PATH",
|
||||||
|
help="write the full result as JSON to PATH")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
result = run_experiment(n=args.n, seed=args.seed, flips=args.flips,
|
||||||
|
min_connectivity=args.min_connectivity,
|
||||||
|
min_degree=args.min_degree,
|
||||||
|
attempts=args.attempts)
|
||||||
|
print(summary(result))
|
||||||
|
if args.json:
|
||||||
|
with open(args.json, "w") as fh:
|
||||||
|
json.dump(to_json(result), fh, indent=2)
|
||||||
|
print(f"wrote {args.json}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
After Width: | Height: | Size: 126 KiB |
|
After Width: | Height: | Size: 140 KiB |
@@ -0,0 +1 @@
|
|||||||
|
"""Reusable medial tire cut helpers."""
|
||||||
@@ -0,0 +1,431 @@
|
|||||||
|
"""Walk-depth labelling and cut of a full medial tire graph.
|
||||||
|
|
||||||
|
Implements the procedure of Definition 2.1 ("Walk-depth labelling and cut") of
|
||||||
|
the *Medial Tire Cuts* paper:
|
||||||
|
|
||||||
|
1. Pick an arbitrary up tooth, the entry tooth; it has walk depth d.
|
||||||
|
2. Traverse all teeth bounding the inner face incident to the entry tooth
|
||||||
|
clockwise until reaching the entry tooth, incrementing the walk depth by 1
|
||||||
|
for each tooth traversed.
|
||||||
|
3. On reaching the last tooth in the face, perform a cut by duplicating the
|
||||||
|
annular vertex at which the traversal closes (the annular vertex shared by
|
||||||
|
the last tooth and the closing tooth).
|
||||||
|
4. Find the tooth t of highest walk depth that is a member of a bite.
|
||||||
|
5. If t is incident to a face F with unlabelled teeth, traverse the teeth of F
|
||||||
|
starting from t in the direction of the unlabelled tooth incident to t
|
||||||
|
(sharing an annular vertex), incrementing the walk depth as you go.
|
||||||
|
6. Repeat steps 3-5 until all teeth are labelled.
|
||||||
|
7. Cut the apex of every up tooth, except entry teeth and except any apex
|
||||||
|
vertex that is shared by two up teeth.
|
||||||
|
|
||||||
|
The full medial tire graph model (annular cycle A(T), up/down teeth, bites, the
|
||||||
|
auxiliary plane graph B(T) and its inner faces) is the one from the companion
|
||||||
|
``full_medial_tire_generator.py`` of the medial tire decompositions paper, which
|
||||||
|
we import.
|
||||||
|
|
||||||
|
Teeth are identified with the annular edges that carry them: edge i sits on the
|
||||||
|
annular vertices a_i and a_{(i+1) mod n} and carries exactly one tooth. A bite
|
||||||
|
(i, j) carries two teeth, one on edge i and one on edge j, that share the bite
|
||||||
|
apex p. The inner non-tooth faces of B(T) are the root face (written ``None``)
|
||||||
|
and one inner-gap face per bite.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from collections import Counter
|
||||||
|
from collections.abc import Mapping
|
||||||
|
|
||||||
|
# Import the full medial tire model from the companion paper's lib directory.
|
||||||
|
_GEN_DIR = os.path.normpath(os.path.join(
|
||||||
|
os.path.dirname(__file__), "..", "..",
|
||||||
|
"medial_tire_decompositions_of_plane_triangulations", "lib",
|
||||||
|
))
|
||||||
|
sys.path.insert(0, _GEN_DIR)
|
||||||
|
|
||||||
|
from full_medial_tire_generator import ( # noqa: E402
|
||||||
|
FullMedialTireGraph,
|
||||||
|
has_incident_bite,
|
||||||
|
innermost_bite,
|
||||||
|
satisfies_bite_face_condition,
|
||||||
|
)
|
||||||
|
|
||||||
|
Face = "tuple[int, int] | None" # a bite (i, j), or None for the root face
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Face structure of B(T).
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def parent_face(graph: FullMedialTireGraph, bite: tuple[int, int]) -> Face:
|
||||||
|
"""The face directly enclosing ``bite``: the minimal-span bite strictly
|
||||||
|
containing it, or the root face ``None``."""
|
||||||
|
i, j = bite
|
||||||
|
enclosing = [b for b in graph.bites if b[0] < i and b[1] > j]
|
||||||
|
if not enclosing:
|
||||||
|
return None
|
||||||
|
return min(enclosing, key=lambda b: b[1] - b[0])
|
||||||
|
|
||||||
|
|
||||||
|
def door_bite(graph: FullMedialTireGraph, edge: int) -> tuple[int, int] | None:
|
||||||
|
"""The bite that ``edge`` is a door of (i.e. a bite edge), or None."""
|
||||||
|
for b in graph.bites:
|
||||||
|
if edge in b:
|
||||||
|
return b
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def faces_bordered(graph: FullMedialTireGraph, edge: int) -> list[Face]:
|
||||||
|
"""The inner non-tooth faces whose boundary the tooth on ``edge`` lies on.
|
||||||
|
|
||||||
|
A bite door borders two faces (its bite's gap and that bite's parent); any
|
||||||
|
other tooth borders the single face directly containing its edge.
|
||||||
|
"""
|
||||||
|
bite = door_bite(graph, edge)
|
||||||
|
if bite is not None:
|
||||||
|
return [bite, parent_face(graph, bite)]
|
||||||
|
return [innermost_bite(edge, graph.bites)]
|
||||||
|
|
||||||
|
|
||||||
|
def face_boundary(graph: FullMedialTireGraph, face: Face) -> list[int]:
|
||||||
|
"""The teeth (annular edges) bounding ``face``, in clockwise cyclic order.
|
||||||
|
|
||||||
|
Clockwise is increasing edge index. For the root face the boundary is read
|
||||||
|
around the whole cycle; for a bite gap (i, j) it is read along the arc
|
||||||
|
i, i+1, ..., j and closes through the bite apex. Edges enclosed by a child
|
||||||
|
bite are skipped (they belong to the child's gap face).
|
||||||
|
"""
|
||||||
|
n = graph.n
|
||||||
|
arc = range(n) if face is None else range(face[0], face[1] + 1)
|
||||||
|
return [k for k in arc if face in faces_bordered(graph, k)]
|
||||||
|
|
||||||
|
|
||||||
|
def all_faces(graph: FullMedialTireGraph) -> list[Face]:
|
||||||
|
return [None] + sorted(graph.bites)
|
||||||
|
|
||||||
|
|
||||||
|
def shared_annular_vertex(graph: FullMedialTireGraph, e1: int, e2: int) -> int | None:
|
||||||
|
"""The annular vertex a_k shared by edges ``e1`` and ``e2``, or None."""
|
||||||
|
n = graph.n
|
||||||
|
common = {e1, (e1 + 1) % n} & {e2, (e2 + 1) % n}
|
||||||
|
return next(iter(common)) if common else None
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# The walk-depth labelling and cut.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class Cut:
|
||||||
|
"""A cut performed when a face traversal closes: the duplicated annular
|
||||||
|
vertex, together with the last labelled tooth and the closing tooth that
|
||||||
|
share it, and the face being closed."""
|
||||||
|
|
||||||
|
__slots__ = ("vertex", "last_tooth", "closing_tooth", "face", "order")
|
||||||
|
|
||||||
|
def __init__(self, vertex, last_tooth, closing_tooth, face, order):
|
||||||
|
self.vertex = vertex
|
||||||
|
self.last_tooth = last_tooth
|
||||||
|
self.closing_tooth = closing_tooth
|
||||||
|
self.face = face
|
||||||
|
self.order = order
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
f = "root" if self.face is None else f"bite{self.face}"
|
||||||
|
return (f"Cut(order={self.order}, a{self.vertex}, "
|
||||||
|
f"last=e{self.last_tooth}, closing=e{self.closing_tooth}, face={f})")
|
||||||
|
|
||||||
|
|
||||||
|
def label_and_cut(graph: FullMedialTireGraph, entry_edge: int,
|
||||||
|
start_depth: int = 0) -> tuple[dict[int, int], list[Cut]]:
|
||||||
|
"""Run the procedure starting from up tooth ``entry_edge``.
|
||||||
|
|
||||||
|
Returns ``(depth, cuts)`` where ``depth`` maps each annular edge (tooth) to
|
||||||
|
its walk depth, and ``cuts`` is the list of cuts in the order performed.
|
||||||
|
"""
|
||||||
|
if graph.tooth_word[entry_edge] != "U":
|
||||||
|
raise ValueError(f"entry edge {entry_edge} is not an up tooth")
|
||||||
|
|
||||||
|
depth: dict[int, int] = {}
|
||||||
|
cuts: list[Cut] = []
|
||||||
|
counter = start_depth
|
||||||
|
|
||||||
|
def traverse(face: Face, start_edge: int, is_entry: bool) -> None:
|
||||||
|
nonlocal counter
|
||||||
|
boundary = face_boundary(graph, face)
|
||||||
|
m = len(boundary)
|
||||||
|
pos = boundary.index(start_edge)
|
||||||
|
if is_entry:
|
||||||
|
depth[start_edge] = counter
|
||||||
|
counter += 1
|
||||||
|
direction = +1
|
||||||
|
else:
|
||||||
|
# head toward the unlabelled tooth incident to the door t
|
||||||
|
direction = +1 if boundary[(pos + 1) % m] not in depth else -1
|
||||||
|
last_new = start_edge
|
||||||
|
i = pos
|
||||||
|
while True:
|
||||||
|
i = (i + direction) % m
|
||||||
|
edge = boundary[i]
|
||||||
|
if edge in depth: # the closing tooth
|
||||||
|
cuts.append(Cut(
|
||||||
|
vertex=shared_annular_vertex(graph, last_new, edge),
|
||||||
|
last_tooth=last_new, closing_tooth=edge,
|
||||||
|
face=face, order=len(cuts),
|
||||||
|
))
|
||||||
|
return
|
||||||
|
depth[edge] = counter
|
||||||
|
counter += 1
|
||||||
|
last_new = edge
|
||||||
|
|
||||||
|
# Steps 1-3: the entry face.
|
||||||
|
traverse(innermost_bite(entry_edge, graph.bites), entry_edge, is_entry=True)
|
||||||
|
|
||||||
|
# Steps 4-6: descend (or ascend) through bites, deepest first. The root
|
||||||
|
# face is ``None``, so we use a distinct sentinel for "no unlabelled face".
|
||||||
|
_MISSING = object()
|
||||||
|
while len(depth) < graph.n:
|
||||||
|
labelled_bite_teeth = sorted(
|
||||||
|
(e for e in depth if door_bite(graph, e) is not None),
|
||||||
|
key=lambda e: depth[e], reverse=True,
|
||||||
|
)
|
||||||
|
for t in labelled_bite_teeth:
|
||||||
|
target = next((F for F in faces_bordered(graph, t)
|
||||||
|
if any(e not in depth for e in face_boundary(graph, F))),
|
||||||
|
_MISSING)
|
||||||
|
if target is not _MISSING:
|
||||||
|
traverse(target, t, is_entry=False)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
break # no progress possible
|
||||||
|
|
||||||
|
return depth, cuts
|
||||||
|
|
||||||
|
|
||||||
|
def up_apex_cuts(graph: FullMedialTireGraph, entry_edge: int,
|
||||||
|
bij: Mapping[str, object] | None = None,
|
||||||
|
shared_apexes: set[object] | None = None) -> dict[int, object]:
|
||||||
|
"""Up-tooth apex cuts prescribed after the walk-depth traversal.
|
||||||
|
|
||||||
|
The returned dict maps each cut up-tooth edge to the apex vertex to
|
||||||
|
duplicate. Entry teeth are not cut. If ``bij`` is supplied, it maps the
|
||||||
|
model vertex names (``u{i}``) into the ambient medial graph; this lets a
|
||||||
|
real tread suppress cuts at a vertex that is the shared apex of two up
|
||||||
|
teeth. Without ``bij`` the model vertex names are used directly.
|
||||||
|
"""
|
||||||
|
apex_by_edge = {
|
||||||
|
i: (bij[f"u{i}"] if bij is not None else graph.apex_of_edge(i))
|
||||||
|
for i in graph.up_edges
|
||||||
|
}
|
||||||
|
shared_apexes = shared_apexes or set()
|
||||||
|
multiplicity = Counter(apex_by_edge.values())
|
||||||
|
return {
|
||||||
|
i: apex
|
||||||
|
for i, apex in apex_by_edge.items()
|
||||||
|
if i != entry_edge and multiplicity[apex] == 1 and apex not in shared_apexes
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# TikZ rendering.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _coords(graph: FullMedialTireGraph,
|
||||||
|
r_ann=1.0, r_up=1.46, r_down=0.60) -> dict[str, tuple[float, float]]:
|
||||||
|
n = graph.n
|
||||||
|
|
||||||
|
def ang(k): # a_0 at the top, increasing k clockwise
|
||||||
|
return math.radians(90.0 - k * 360.0 / n)
|
||||||
|
|
||||||
|
def edge_mid_dir(i): # angle of the bisector of edge i's two endpoints
|
||||||
|
a0, a1 = ang(i), ang((i + 1) % n)
|
||||||
|
return math.atan2(math.sin(a0) + math.sin(a1), math.cos(a0) + math.cos(a1))
|
||||||
|
|
||||||
|
pos = {f"a{k}": (r_ann * math.cos(ang(k)), r_ann * math.sin(ang(k)))
|
||||||
|
for k in range(n)}
|
||||||
|
for i in graph.up_edges:
|
||||||
|
a = edge_mid_dir(i)
|
||||||
|
pos[f"u{i}"] = (r_up * math.cos(a), r_up * math.sin(a))
|
||||||
|
for i in graph.singleton_down_edges:
|
||||||
|
a = edge_mid_dir(i)
|
||||||
|
pos[f"d{i}"] = (r_down * math.cos(a), r_down * math.sin(a))
|
||||||
|
for (i, j) in graph.bites:
|
||||||
|
pts = [pos[f"a{i}"], pos[f"a{(i + 1) % n}"],
|
||||||
|
pos[f"a{j}"], pos[f"a{(j + 1) % n}"]]
|
||||||
|
cx = sum(p[0] for p in pts) / 4.0
|
||||||
|
cy = sum(p[1] for p in pts) / 4.0
|
||||||
|
pos[f"p{i}_{j}"] = (0.9 * cx, 0.9 * cy)
|
||||||
|
return pos
|
||||||
|
|
||||||
|
|
||||||
|
def _edge_midpoint(pos, graph, edge):
|
||||||
|
n = graph.n
|
||||||
|
a, b = pos[f"a{edge}"], pos[f"a{(edge + 1) % n}"]
|
||||||
|
return (0.5 * (a[0] + b[0]), 0.5 * (a[1] + b[1]))
|
||||||
|
|
||||||
|
|
||||||
|
def to_tikz(graph: FullMedialTireGraph,
|
||||||
|
depth: dict[int, int] | None = None,
|
||||||
|
cuts: list[Cut] | None = None,
|
||||||
|
entry_edge: int | None = None,
|
||||||
|
scale: float = 2.2) -> str:
|
||||||
|
"""A standalone ``tikzpicture`` for ``graph``; if ``depth`` is given, draw
|
||||||
|
the walk-depth labels and (with ``cuts``) the cut marks."""
|
||||||
|
pos = _coords(graph)
|
||||||
|
n = graph.n
|
||||||
|
L = []
|
||||||
|
A = L.append
|
||||||
|
A(f"\\begin{{tikzpicture}}[scale={scale},")
|
||||||
|
A(" ann/.style={circle, fill=black, inner sep=1.0pt},")
|
||||||
|
A(" upv/.style={circle, draw=blue!70!black, fill=blue!12, inner sep=1.4pt},")
|
||||||
|
A(" downv/.style={circle, draw=red!70!black, fill=red!12, inner sep=1.4pt},")
|
||||||
|
A(" bitev/.style={circle, draw=red!70!black, fill=red!32, inner sep=1.7pt},")
|
||||||
|
A(" cyc/.style={black, line width=1.0pt},")
|
||||||
|
A(" tth/.style={black!55, line width=0.4pt},")
|
||||||
|
A(" lbl/.style={font=\\scriptsize},")
|
||||||
|
A(" dlbl/.style={font=\\scriptsize\\bfseries, text=black},")
|
||||||
|
A(" cut/.style={red!80!black, line width=1.3pt},")
|
||||||
|
A(" cutlbl/.style={font=\\tiny, text=red!75!black}]")
|
||||||
|
|
||||||
|
def pt(name):
|
||||||
|
x, y = pos[name]
|
||||||
|
return f"({x:.3f},{y:.3f})"
|
||||||
|
|
||||||
|
# annular cycle
|
||||||
|
cyc = "--".join(pt(f"a{k}") for k in range(n)) + "--cycle"
|
||||||
|
A(f"\\draw[cyc] {cyc};")
|
||||||
|
# spokes
|
||||||
|
for i in graph.up_edges:
|
||||||
|
A(f"\\draw[tth] {pt(f'u{i}')}--{pt(f'a{i}')} {pt(f'u{i}')}--{pt(f'a{(i+1)%n}')};")
|
||||||
|
for i in graph.singleton_down_edges:
|
||||||
|
A(f"\\draw[tth] {pt(f'd{i}')}--{pt(f'a{i}')} {pt(f'd{i}')}--{pt(f'a{(i+1)%n}')};")
|
||||||
|
for (i, j) in graph.bites:
|
||||||
|
apex = f"p{i}_{j}"
|
||||||
|
for e in (i, j):
|
||||||
|
A(f"\\draw[tth] {pt(apex)}--{pt(f'a{e}')} {pt(apex)}--{pt(f'a{(e+1)%n}')};")
|
||||||
|
# vertices
|
||||||
|
for k in range(n):
|
||||||
|
A(f"\\node[ann] at {pt(f'a{k}')} {{}};")
|
||||||
|
for i in graph.up_edges:
|
||||||
|
A(f"\\node[upv] at {pt(f'u{i}')} {{}};")
|
||||||
|
for i in graph.singleton_down_edges:
|
||||||
|
A(f"\\node[downv] at {pt(f'd{i}')} {{}};")
|
||||||
|
for (i, j) in sorted(graph.bites):
|
||||||
|
A(f"\\node[bitev] at {pt(f'p{i}_{j}')} {{}};")
|
||||||
|
|
||||||
|
# walk-depth labels: placed along the spoke from apex toward the edge mid
|
||||||
|
if depth is not None:
|
||||||
|
for edge in range(n):
|
||||||
|
apex = graph.apex_of_edge(edge)
|
||||||
|
ax, ay = pos[apex]
|
||||||
|
mx, my = _edge_midpoint(pos, graph, edge)
|
||||||
|
f = 0.5
|
||||||
|
lx, ly = ax + f * (mx - ax), ay + f * (my - ay)
|
||||||
|
A(f"\\node[dlbl] at ({lx:.3f},{ly:.3f}) {{{depth[edge]}}};")
|
||||||
|
|
||||||
|
# cut marks: a short red slit across the duplicated annular vertex
|
||||||
|
if cuts:
|
||||||
|
for c in cuts:
|
||||||
|
if c.vertex is None:
|
||||||
|
continue
|
||||||
|
vx, vy = pos[f"a{c.vertex}"]
|
||||||
|
rad = math.atan2(vy, vx)
|
||||||
|
dx, dy = 0.16 * math.cos(rad), 0.16 * math.sin(rad)
|
||||||
|
A(f"\\draw[cut] ({vx-dx:.3f},{vy-dy:.3f})--({vx+dx:.3f},{vy+dy:.3f});")
|
||||||
|
lx, ly = vx + 0.30 * math.cos(rad), vy + 0.30 * math.sin(rad)
|
||||||
|
A(f"\\node[cutlbl] at ({lx:.3f},{ly:.3f}) {{cut {c.order+1}}};")
|
||||||
|
|
||||||
|
# up-tooth apex cuts: tangential slits, excluding the entry tooth and any
|
||||||
|
# up apex shared by two up teeth.
|
||||||
|
if entry_edge is not None:
|
||||||
|
for i in up_apex_cuts(graph, entry_edge):
|
||||||
|
vx, vy = pos[f"u{i}"]
|
||||||
|
rad = math.atan2(vy, vx)
|
||||||
|
tx, ty = -math.sin(rad), math.cos(rad)
|
||||||
|
A(f"\\draw[cut] ({vx-0.12*tx:.3f},{vy-0.12*ty:.3f})--"
|
||||||
|
f"({vx+0.12*tx:.3f},{vy+0.12*ty:.3f});")
|
||||||
|
|
||||||
|
if entry_edge is not None:
|
||||||
|
ex, ey = pos[graph.apex_of_edge(entry_edge)]
|
||||||
|
rad = math.atan2(ey, ex)
|
||||||
|
tx, ty = ex + 0.34 * math.cos(rad), ey + 0.34 * math.sin(rad)
|
||||||
|
A(f"\\node[lbl, text=blue!60!black] at ({tx:.3f},{ty:.3f}) {{entry}};")
|
||||||
|
|
||||||
|
A("\\end{tikzpicture}")
|
||||||
|
return "\n".join(L)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Worked example and CLI.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def worked_example() -> FullMedialTireGraph:
|
||||||
|
"""A clean 8-tooth piece: one bite (0,4), three down singletons 1,2,3 in its
|
||||||
|
gap, three up teeth 5,6,7 in the root face."""
|
||||||
|
return FullMedialTireGraph(n=8, tooth_word="DDDDDUUU", bites=frozenset({(0, 4)}))
|
||||||
|
|
||||||
|
|
||||||
|
def _check(graph: FullMedialTireGraph) -> None:
|
||||||
|
assert not has_incident_bite(graph.bites, graph.n), "bite uses incident edges"
|
||||||
|
assert satisfies_bite_face_condition(graph.tooth_word, graph.bites), \
|
||||||
|
"violates the bite-face condition"
|
||||||
|
assert graph.tooth_word.count("U") >= 3, "fewer than three up teeth"
|
||||||
|
|
||||||
|
|
||||||
|
def _describe(graph, depth, cuts, entry_edge) -> str:
|
||||||
|
lines = ["edge type walk-depth"]
|
||||||
|
for e in range(graph.n):
|
||||||
|
t = graph.tooth_word[e]
|
||||||
|
kind = {"U": "up"}.get(t, "down")
|
||||||
|
if door_bite(graph, e) is not None:
|
||||||
|
kind = "bite"
|
||||||
|
lines.append(f" e{e} {kind:<5} {depth[e]}")
|
||||||
|
lines.append("cuts (in order):")
|
||||||
|
for c in cuts:
|
||||||
|
f = "root" if c.face is None else f"bite{c.face}"
|
||||||
|
lines.append(f" cut {c.order+1}: duplicate a{c.vertex} "
|
||||||
|
f"(closing tooth e{c.closing_tooth} of {f})")
|
||||||
|
apex_cuts = up_apex_cuts(graph, entry_edge)
|
||||||
|
if apex_cuts:
|
||||||
|
lines.append("up-apex cuts:")
|
||||||
|
for edge, apex in apex_cuts.items():
|
||||||
|
lines.append(f" duplicate {apex} for up tooth e{edge}")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__,
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
parser.add_argument("--entry", default="u5",
|
||||||
|
help="entry up tooth, as an edge index or apex name like u5")
|
||||||
|
parser.add_argument("--start-depth", type=int, default=0)
|
||||||
|
parser.add_argument("--tikz", choices=["plain", "labelled", "both"],
|
||||||
|
help="emit TikZ for the worked example")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
entry = args.entry
|
||||||
|
edge = int(entry[1:]) if isinstance(entry, str) and entry.startswith("u") else int(entry)
|
||||||
|
|
||||||
|
graph = worked_example()
|
||||||
|
_check(graph)
|
||||||
|
depth, cuts = label_and_cut(graph, edge, start_depth=args.start_depth)
|
||||||
|
|
||||||
|
if args.tikz == "plain":
|
||||||
|
print(to_tikz(graph))
|
||||||
|
elif args.tikz == "labelled":
|
||||||
|
print(to_tikz(graph, depth=depth, cuts=cuts, entry_edge=edge))
|
||||||
|
elif args.tikz == "both":
|
||||||
|
print("% --- plain ---")
|
||||||
|
print(to_tikz(graph))
|
||||||
|
print("% --- labelled + cut ---")
|
||||||
|
print(to_tikz(graph, depth=depth, cuts=cuts, entry_edge=edge))
|
||||||
|
else:
|
||||||
|
print(f"worked example: n={graph.n} word={graph.tooth_word} "
|
||||||
|
f"bites={sorted(graph.bites)} entry=e{edge}")
|
||||||
|
print(_describe(graph, depth, cuts, edge))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
\relax
|
||||||
|
\citation{bauerfeld-medial-tire}
|
||||||
|
\citation{bauerfeld-medial-tire}
|
||||||
|
\citation{bauerfeld-medial-tire}
|
||||||
|
\@writefile{toc}{\contentsline {section}{\tocsection {}{1}{Introduction}}{1}{}\protected@file@percent }
|
||||||
|
\@writefile{toc}{\contentsline {section}{\tocsection {}{2}{Cutting a full medial tire graph}}{1}{}\protected@file@percent }
|
||||||
|
\newlabel{def:walk-depth-cut}{{2.1}{1}}
|
||||||
|
\citation{bauerfeld-medial-tire}
|
||||||
|
\citation{bauerfeld-medial-tire}
|
||||||
|
\newlabel{rem:closing-tooth}{{2.2}{2}}
|
||||||
|
\newlabel{ex:worked-cut}{{2.3}{2}}
|
||||||
|
\@writefile{toc}{\contentsline {section}{\tocsection {}{3}{Chaining across the tire tree}}{2}{}\protected@file@percent }
|
||||||
|
\citation{bauerfeld-medial-tire}
|
||||||
|
\@writefile{lof}{\contentsline {figure}{\numberline {1}{\ignorespaces A full medial tire graph (left) and its walk-depth labelling and cut (right), from Example\nonbreakingspace 2.3\hbox {}. Black vertices are the annular medial vertices of the cycle $A(T)$; blue vertices are up-tooth apexes, red vertices are down-tooth apexes, and the larger red vertex is the shared apex of the bite on annular edges $0$ and $4$. On the right, each tooth carries its walk depth, and the two red slits mark the cuts: \emph {cut\nonbreakingspace 1} duplicates $a_5$ as the root-face traversal closes, and \emph {cut\nonbreakingspace 2} duplicates $a_1$ as the bite's inner-gap face closes. After the cuts the only bounded faces are the eight teeth.}}{3}{}\protected@file@percent }
|
||||||
|
\newlabel{fig:worked-cut}{{1}{3}}
|
||||||
|
\newlabel{rem:chaining-candidates}{{3.1}{3}}
|
||||||
|
\newlabel{ex:real-cut}{{3.2}{4}}
|
||||||
|
\@writefile{lof}{\contentsline {figure}{\numberline {2}{\ignorespaces The recognised tread $T_2$ of the medial tire decomposition of a random maximal planar graph on $20$ vertices (Example\nonbreakingspace 3.2\hbox {}), with its walk-depth labelling and cut. Black vertices are the annular medial vertices of $A(T)$; blue vertices are up-tooth apexes and red vertices down-tooth apexes, the larger red vertex being the shared apex of the bite on annular edges $2$ and $5$. Each tooth carries its walk depth; the red slits are the two cuts.}}{4}{}\protected@file@percent }
|
||||||
|
\newlabel{fig:real-cut}{{2}{4}}
|
||||||
|
\bibcite{bauerfeld-medial-tire}{1}
|
||||||
|
\newlabel{tocindent-1}{0pt}
|
||||||
|
\newlabel{tocindent0}{12.7778pt}
|
||||||
|
\newlabel{tocindent1}{17.77782pt}
|
||||||
|
\newlabel{tocindent2}{0pt}
|
||||||
|
\newlabel{tocindent3}{0pt}
|
||||||
|
\@writefile{lof}{\contentsline {figure}{\numberline {3}{\ignorespaces The source graph $G$ and the whole medial graph $M(G)$ of the minimum-degree-$5$ maximal planar graph on $20$ vertices generated by \texttt {plantri -m5} at seed $59$. The source vertex $5$ is highlighted in the top panel. In the bottom panel, each medial vertex is placed at the midpoint of its corresponding source edge and labelled by that edge. Black vertices come from source edges between consecutive levels; coloured vertices come from source edges within a single level of the chain. The red-highlighted vertices, walk-depth labels, and seven red slits are the computed source-cap cut and full-medial-tire labelling cuts for the recognised treads $T_1$ and $T_2$. Drawn by \texttt {experiments/draw\_medial\_tire\_cut.py} with \texttt {--whole --min-degree 5}.}}{5}{}\protected@file@percent }
|
||||||
|
\newlabel{fig:whole-medial}{{3}{5}}
|
||||||
|
\@writefile{toc}{\contentsline {section}{\tocsection {}{}{References}}{5}{}\protected@file@percent }
|
||||||
|
\gdef \@abspage@last{5}
|
||||||
@@ -0,0 +1,533 @@
|
|||||||
|
This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 15 JUN 2026 01:07
|
||||||
|
entering extended mode
|
||||||
|
restricted \write18 enabled.
|
||||||
|
%&-line parsing enabled.
|
||||||
|
**paper.tex
|
||||||
|
(./paper.tex
|
||||||
|
LaTeX2e <2021-11-15> patch level 1
|
||||||
|
L3 programming layer <2022-02-24>
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amscls/amsart.cls
|
||||||
|
Document Class: amsart 2020/05/29 v2.20.6
|
||||||
|
\linespacing=\dimen138
|
||||||
|
\normalparindent=\dimen139
|
||||||
|
\normaltopskip=\skip47
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsmath.sty
|
||||||
|
Package: amsmath 2021/10/15 v2.17l AMS math features
|
||||||
|
\@mathmargin=\skip48
|
||||||
|
|
||||||
|
For additional information on amsmath, use the `?' option.
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amstext.sty
|
||||||
|
Package: amstext 2021/08/26 v2.01 AMS text
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsgen.sty
|
||||||
|
File: amsgen.sty 1999/11/30 v2.0 generic functions
|
||||||
|
\@emptytoks=\toks16
|
||||||
|
\ex@=\dimen140
|
||||||
|
))
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsbsy.sty
|
||||||
|
Package: amsbsy 1999/11/29 v1.2d Bold Symbols
|
||||||
|
\pmbraise@=\dimen141
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsopn.sty
|
||||||
|
Package: amsopn 2021/08/26 v2.02 operator names
|
||||||
|
)
|
||||||
|
\inf@bad=\count185
|
||||||
|
LaTeX Info: Redefining \frac on input line 234.
|
||||||
|
\uproot@=\count186
|
||||||
|
\leftroot@=\count187
|
||||||
|
LaTeX Info: Redefining \overline on input line 399.
|
||||||
|
\classnum@=\count188
|
||||||
|
\DOTSCASE@=\count189
|
||||||
|
LaTeX Info: Redefining \ldots on input line 496.
|
||||||
|
LaTeX Info: Redefining \dots on input line 499.
|
||||||
|
LaTeX Info: Redefining \cdots on input line 620.
|
||||||
|
\Mathstrutbox@=\box50
|
||||||
|
\strutbox@=\box51
|
||||||
|
\big@size=\dimen142
|
||||||
|
LaTeX Font Info: Redeclaring font encoding OML on input line 743.
|
||||||
|
LaTeX Font Info: Redeclaring font encoding OMS on input line 744.
|
||||||
|
\macc@depth=\count190
|
||||||
|
\c@MaxMatrixCols=\count191
|
||||||
|
\dotsspace@=\muskip16
|
||||||
|
\c@parentequation=\count192
|
||||||
|
\dspbrk@lvl=\count193
|
||||||
|
\tag@help=\toks17
|
||||||
|
\row@=\count194
|
||||||
|
\column@=\count195
|
||||||
|
\maxfields@=\count196
|
||||||
|
\andhelp@=\toks18
|
||||||
|
\eqnshift@=\dimen143
|
||||||
|
\alignsep@=\dimen144
|
||||||
|
\tagshift@=\dimen145
|
||||||
|
\tagwidth@=\dimen146
|
||||||
|
\totwidth@=\dimen147
|
||||||
|
\lineht@=\dimen148
|
||||||
|
\@envbody=\toks19
|
||||||
|
\multlinegap=\skip49
|
||||||
|
\multlinetaggap=\skip50
|
||||||
|
\mathdisplay@stack=\toks20
|
||||||
|
LaTeX Info: Redefining \[ on input line 2938.
|
||||||
|
LaTeX Info: Redefining \] on input line 2939.
|
||||||
|
)
|
||||||
|
LaTeX Font Info: Trying to load font information for U+msa on input line 397
|
||||||
|
.
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsa.fd
|
||||||
|
File: umsa.fd 2013/01/14 v3.01 AMS symbols A
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/amsfonts.sty
|
||||||
|
Package: amsfonts 2013/01/14 v3.01 Basic AMSFonts support
|
||||||
|
\symAMSa=\mathgroup4
|
||||||
|
\symAMSb=\mathgroup5
|
||||||
|
LaTeX Font Info: Redeclaring math symbol \hbar on input line 98.
|
||||||
|
LaTeX Font Info: Overwriting math alphabet `\mathfrak' in version `bold'
|
||||||
|
(Font) U/euf/m/n --> U/euf/b/n on input line 106.
|
||||||
|
)
|
||||||
|
\copyins=\insert199
|
||||||
|
\abstractbox=\box52
|
||||||
|
\listisep=\skip51
|
||||||
|
\c@part=\count197
|
||||||
|
\c@section=\count198
|
||||||
|
\c@subsection=\count266
|
||||||
|
\c@subsubsection=\count267
|
||||||
|
\c@paragraph=\count268
|
||||||
|
\c@subparagraph=\count269
|
||||||
|
\c@figure=\count270
|
||||||
|
\c@table=\count271
|
||||||
|
\abovecaptionskip=\skip52
|
||||||
|
\belowcaptionskip=\skip53
|
||||||
|
\captionindent=\dimen149
|
||||||
|
\thm@style=\toks21
|
||||||
|
\thm@bodyfont=\toks22
|
||||||
|
\thm@headfont=\toks23
|
||||||
|
\thm@notefont=\toks24
|
||||||
|
\thm@headpunct=\toks25
|
||||||
|
\thm@preskip=\skip54
|
||||||
|
\thm@postskip=\skip55
|
||||||
|
\thm@headsep=\skip56
|
||||||
|
\dth@everypar=\toks26
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/amssymb.sty
|
||||||
|
Package: amssymb 2013/01/14 v3.01 AMS font symbols
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/graphicx.sty
|
||||||
|
Package: graphicx 2021/09/16 v1.2d Enhanced LaTeX Graphics (DPC,SPQR)
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/keyval.sty
|
||||||
|
Package: keyval 2014/10/28 v1.15 key=value parser (DPC)
|
||||||
|
\KV@toks@=\toks27
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/graphics.sty
|
||||||
|
Package: graphics 2021/03/04 v1.4d Standard LaTeX Graphics (DPC,SPQR)
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/trig.sty
|
||||||
|
Package: trig 2021/08/11 v1.11 sin cos tan (DPC)
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics-cfg/graphics.cfg
|
||||||
|
File: graphics.cfg 2016/06/04 v1.11 sample graphics configuration
|
||||||
|
)
|
||||||
|
Package graphics Info: Driver file: pdftex.def on input line 107.
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics-def/pdftex.def
|
||||||
|
File: pdftex.def 2020/10/05 v1.2a Graphics/color driver for pdftex
|
||||||
|
))
|
||||||
|
\Gin@req@height=\dimen150
|
||||||
|
\Gin@req@width=\dimen151
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/pgf/frontendlayer/tikz.sty
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/pgf/basiclayer/pgf.sty
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/pgf/utilities/pgfrcs.sty
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/utilities/pgfutil-common.te
|
||||||
|
x
|
||||||
|
\pgfutil@everybye=\toks28
|
||||||
|
\pgfutil@tempdima=\dimen152
|
||||||
|
\pgfutil@tempdimb=\dimen153
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/utilities/pgfutil-common-li
|
||||||
|
sts.tex))
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/utilities/pgfutil-latex.def
|
||||||
|
\pgfutil@abb=\box53
|
||||||
|
) (/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/utilities/pgfrcs.code.tex
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/pgf.revision.tex)
|
||||||
|
Package: pgfrcs 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
))
|
||||||
|
Package: pgf 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/pgf/basiclayer/pgfcore.sty
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/pgf/systemlayer/pgfsys.sty
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/systemlayer/pgfsys.code.tex
|
||||||
|
Package: pgfsys 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/utilities/pgfkeys.code.tex
|
||||||
|
\pgfkeys@pathtoks=\toks29
|
||||||
|
\pgfkeys@temptoks=\toks30
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/utilities/pgfkeysfiltered.c
|
||||||
|
ode.tex
|
||||||
|
\pgfkeys@tmptoks=\toks31
|
||||||
|
))
|
||||||
|
\pgf@x=\dimen154
|
||||||
|
\pgf@y=\dimen155
|
||||||
|
\pgf@xa=\dimen156
|
||||||
|
\pgf@ya=\dimen157
|
||||||
|
\pgf@xb=\dimen158
|
||||||
|
\pgf@yb=\dimen159
|
||||||
|
\pgf@xc=\dimen160
|
||||||
|
\pgf@yc=\dimen161
|
||||||
|
\pgf@xd=\dimen162
|
||||||
|
\pgf@yd=\dimen163
|
||||||
|
\w@pgf@writea=\write3
|
||||||
|
\r@pgf@reada=\read2
|
||||||
|
\c@pgf@counta=\count272
|
||||||
|
\c@pgf@countb=\count273
|
||||||
|
\c@pgf@countc=\count274
|
||||||
|
\c@pgf@countd=\count275
|
||||||
|
\t@pgf@toka=\toks32
|
||||||
|
\t@pgf@tokb=\toks33
|
||||||
|
\t@pgf@tokc=\toks34
|
||||||
|
\pgf@sys@id@count=\count276
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/systemlayer/pgf.cfg
|
||||||
|
File: pgf.cfg 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
)
|
||||||
|
Driver file for pgf: pgfsys-pdftex.def
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-pdftex.d
|
||||||
|
ef
|
||||||
|
File: pgfsys-pdftex.def 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/systemlayer/pgfsys-common-p
|
||||||
|
df.def
|
||||||
|
File: pgfsys-common-pdf.def 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
)))
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/systemlayer/pgfsyssoftpath.
|
||||||
|
code.tex
|
||||||
|
File: pgfsyssoftpath.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgfsyssoftpath@smallbuffer@items=\count277
|
||||||
|
\pgfsyssoftpath@bigbuffer@items=\count278
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/systemlayer/pgfsysprotocol.
|
||||||
|
code.tex
|
||||||
|
File: pgfsysprotocol.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
)) (/usr/local/texlive/2022/texmf-dist/tex/latex/xcolor/xcolor.sty
|
||||||
|
Package: xcolor 2021/10/31 v2.13 LaTeX color extensions (UK)
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics-cfg/color.cfg
|
||||||
|
File: color.cfg 2016/01/02 v1.6 sample color configuration
|
||||||
|
)
|
||||||
|
Package xcolor Info: Driver file: pdftex.def on input line 227.
|
||||||
|
Package xcolor Info: Model `cmy' substituted by `cmy0' on input line 1352.
|
||||||
|
Package xcolor Info: Model `hsb' substituted by `rgb' on input line 1356.
|
||||||
|
Package xcolor Info: Model `RGB' extended on input line 1368.
|
||||||
|
Package xcolor Info: Model `HTML' substituted by `rgb' on input line 1370.
|
||||||
|
Package xcolor Info: Model `Hsb' substituted by `hsb' on input line 1371.
|
||||||
|
Package xcolor Info: Model `tHsb' substituted by `hsb' on input line 1372.
|
||||||
|
Package xcolor Info: Model `HSB' substituted by `hsb' on input line 1373.
|
||||||
|
Package xcolor Info: Model `Gray' substituted by `gray' on input line 1374.
|
||||||
|
Package xcolor Info: Model `wave' substituted by `hsb' on input line 1375.
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcore.code.tex
|
||||||
|
Package: pgfcore 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmathcalc.code.tex
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmathutil.code.tex)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmathparser.code.tex
|
||||||
|
\pgfmath@dimen=\dimen164
|
||||||
|
\pgfmath@count=\count279
|
||||||
|
\pgfmath@box=\box54
|
||||||
|
\pgfmath@toks=\toks35
|
||||||
|
\pgfmath@stack@operand=\toks36
|
||||||
|
\pgfmath@stack@operation=\toks37
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.code.
|
||||||
|
tex
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.basic
|
||||||
|
.code.tex)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.trigo
|
||||||
|
nometric.code.tex)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.rando
|
||||||
|
m.code.tex)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.compa
|
||||||
|
rison.code.tex)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.base.
|
||||||
|
code.tex)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.round
|
||||||
|
.code.tex)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.misc.
|
||||||
|
code.tex)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmathfunctions.integ
|
||||||
|
erarithmetics.code.tex)))
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmathfloat.code.tex
|
||||||
|
\c@pgfmathroundto@lastzeros=\count280
|
||||||
|
)) (/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfint.code.tex)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepoints.co
|
||||||
|
de.tex
|
||||||
|
File: pgfcorepoints.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgf@picminx=\dimen165
|
||||||
|
\pgf@picmaxx=\dimen166
|
||||||
|
\pgf@picminy=\dimen167
|
||||||
|
\pgf@picmaxy=\dimen168
|
||||||
|
\pgf@pathminx=\dimen169
|
||||||
|
\pgf@pathmaxx=\dimen170
|
||||||
|
\pgf@pathminy=\dimen171
|
||||||
|
\pgf@pathmaxy=\dimen172
|
||||||
|
\pgf@xx=\dimen173
|
||||||
|
\pgf@xy=\dimen174
|
||||||
|
\pgf@yx=\dimen175
|
||||||
|
\pgf@yy=\dimen176
|
||||||
|
\pgf@zx=\dimen177
|
||||||
|
\pgf@zy=\dimen178
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathconst
|
||||||
|
ruct.code.tex
|
||||||
|
File: pgfcorepathconstruct.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgf@path@lastx=\dimen179
|
||||||
|
\pgf@path@lasty=\dimen180
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathusage
|
||||||
|
.code.tex
|
||||||
|
File: pgfcorepathusage.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgf@shorten@end@additional=\dimen181
|
||||||
|
\pgf@shorten@start@additional=\dimen182
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcorescopes.co
|
||||||
|
de.tex
|
||||||
|
File: pgfcorescopes.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgfpic=\box55
|
||||||
|
\pgf@hbox=\box56
|
||||||
|
\pgf@layerbox@main=\box57
|
||||||
|
\pgf@picture@serial@count=\count281
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcoregraphicst
|
||||||
|
ate.code.tex
|
||||||
|
File: pgfcoregraphicstate.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgflinewidth=\dimen183
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcoretransform
|
||||||
|
ations.code.tex
|
||||||
|
File: pgfcoretransformations.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgf@pt@x=\dimen184
|
||||||
|
\pgf@pt@y=\dimen185
|
||||||
|
\pgf@pt@temp=\dimen186
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcorequick.cod
|
||||||
|
e.tex
|
||||||
|
File: pgfcorequick.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreobjects.c
|
||||||
|
ode.tex
|
||||||
|
File: pgfcoreobjects.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepathproce
|
||||||
|
ssing.code.tex
|
||||||
|
File: pgfcorepathprocessing.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcorearrows.co
|
||||||
|
de.tex
|
||||||
|
File: pgfcorearrows.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgfarrowsep=\dimen187
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreshade.cod
|
||||||
|
e.tex
|
||||||
|
File: pgfcoreshade.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgf@max=\dimen188
|
||||||
|
\pgf@sys@shading@range@num=\count282
|
||||||
|
\pgf@shadingcount=\count283
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreimage.cod
|
||||||
|
e.tex
|
||||||
|
File: pgfcoreimage.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcoreexternal.
|
||||||
|
code.tex
|
||||||
|
File: pgfcoreexternal.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgfexternal@startupbox=\box58
|
||||||
|
))
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcorelayers.co
|
||||||
|
de.tex
|
||||||
|
File: pgfcorelayers.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcoretranspare
|
||||||
|
ncy.code.tex
|
||||||
|
File: pgfcoretransparency.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcorepatterns.
|
||||||
|
code.tex
|
||||||
|
File: pgfcorepatterns.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/basiclayer/pgfcorerdf.code.
|
||||||
|
tex
|
||||||
|
File: pgfcorerdf.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
)))
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/modules/pgfmoduleshapes.cod
|
||||||
|
e.tex
|
||||||
|
File: pgfmoduleshapes.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgfnodeparttextbox=\box59
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/modules/pgfmoduleplot.code.
|
||||||
|
tex
|
||||||
|
File: pgfmoduleplot.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version
|
||||||
|
-0-65.sty
|
||||||
|
Package: pgfcomp-version-0-65 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgf@nodesepstart=\dimen189
|
||||||
|
\pgf@nodesepend=\dimen190
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/pgf/compatibility/pgfcomp-version
|
||||||
|
-1-18.sty
|
||||||
|
Package: pgfcomp-version-1-18 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
))
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/pgf/utilities/pgffor.sty
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/pgf/utilities/pgfkeys.sty
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/utilities/pgfkeys.code.tex)
|
||||||
|
) (/usr/local/texlive/2022/texmf-dist/tex/latex/pgf/math/pgfmath.sty
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex))
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/utilities/pgffor.code.tex
|
||||||
|
Package: pgffor 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/math/pgfmath.code.tex)
|
||||||
|
\pgffor@iter=\dimen191
|
||||||
|
\pgffor@skip=\dimen192
|
||||||
|
\pgffor@stack=\toks38
|
||||||
|
\pgffor@toks=\toks39
|
||||||
|
))
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/frontendlayer/tikz/tikz.cod
|
||||||
|
e.tex
|
||||||
|
Package: tikz 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/libraries/pgflibraryplothan
|
||||||
|
dlers.code.tex
|
||||||
|
File: pgflibraryplothandlers.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgf@plot@mark@count=\count284
|
||||||
|
\pgfplotmarksize=\dimen193
|
||||||
|
)
|
||||||
|
\tikz@lastx=\dimen194
|
||||||
|
\tikz@lasty=\dimen195
|
||||||
|
\tikz@lastxsaved=\dimen196
|
||||||
|
\tikz@lastysaved=\dimen197
|
||||||
|
\tikz@lastmovetox=\dimen198
|
||||||
|
\tikz@lastmovetoy=\dimen256
|
||||||
|
\tikzleveldistance=\dimen257
|
||||||
|
\tikzsiblingdistance=\dimen258
|
||||||
|
\tikz@figbox=\box60
|
||||||
|
\tikz@figbox@bg=\box61
|
||||||
|
\tikz@tempbox=\box62
|
||||||
|
\tikz@tempbox@bg=\box63
|
||||||
|
\tikztreelevel=\count285
|
||||||
|
\tikznumberofchildren=\count286
|
||||||
|
\tikznumberofcurrentchild=\count287
|
||||||
|
\tikz@fig@count=\count288
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/modules/pgfmodulematrix.cod
|
||||||
|
e.tex
|
||||||
|
File: pgfmodulematrix.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgfmatrixcurrentrow=\count289
|
||||||
|
\pgfmatrixcurrentcolumn=\count290
|
||||||
|
\pgf@matrix@numberofcolumns=\count291
|
||||||
|
)
|
||||||
|
\tikz@expandcount=\count292
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/frontendlayer/tikz/librarie
|
||||||
|
s/tikzlibrarytopaths.code.tex
|
||||||
|
File: tikzlibrarytopaths.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
)))
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/generic/pgf/frontendlayer/tikz/librarie
|
||||||
|
s/tikzlibrarybackgrounds.code.tex
|
||||||
|
File: tikzlibrarybackgrounds.code.tex 2021/05/15 v3.1.9a (3.1.9a)
|
||||||
|
\pgf@layerbox@background=\box64
|
||||||
|
\pgf@layerboxsaved@background=\box65
|
||||||
|
)
|
||||||
|
\c@theorem=\count293
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/l3backend/l3backend-pdftex.def
|
||||||
|
File: l3backend-pdftex.def 2022-02-07 L3 backend support: PDF output (pdfTeX)
|
||||||
|
\l__color_backend_stack_int=\count294
|
||||||
|
\l__pdf_internal_box=\box66
|
||||||
|
)
|
||||||
|
(./paper.aux)
|
||||||
|
\openout1 = `paper.aux'.
|
||||||
|
|
||||||
|
LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 29.
|
||||||
|
LaTeX Font Info: ... okay on input line 29.
|
||||||
|
LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 29.
|
||||||
|
LaTeX Font Info: ... okay on input line 29.
|
||||||
|
LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 29.
|
||||||
|
LaTeX Font Info: ... okay on input line 29.
|
||||||
|
LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 29.
|
||||||
|
LaTeX Font Info: ... okay on input line 29.
|
||||||
|
LaTeX Font Info: Checking defaults for TS1/cmr/m/n on input line 29.
|
||||||
|
LaTeX Font Info: ... okay on input line 29.
|
||||||
|
LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 29.
|
||||||
|
LaTeX Font Info: ... okay on input line 29.
|
||||||
|
LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 29.
|
||||||
|
LaTeX Font Info: ... okay on input line 29.
|
||||||
|
LaTeX Font Info: Trying to load font information for U+msa on input line 29.
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsa.fd
|
||||||
|
File: umsa.fd 2013/01/14 v3.01 AMS symbols A
|
||||||
|
)
|
||||||
|
LaTeX Font Info: Trying to load font information for U+msb on input line 29.
|
||||||
|
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsb.fd
|
||||||
|
File: umsb.fd 2013/01/14 v3.01 AMS symbols B
|
||||||
|
)
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/context/base/mkii/supp-pdf.mkii
|
||||||
|
[Loading MPS to PDF converter (version 2006.09.02).]
|
||||||
|
\scratchcounter=\count295
|
||||||
|
\scratchdimen=\dimen259
|
||||||
|
\scratchbox=\box67
|
||||||
|
\nofMPsegments=\count296
|
||||||
|
\nofMParguments=\count297
|
||||||
|
\everyMPshowfont=\toks40
|
||||||
|
\MPscratchCnt=\count298
|
||||||
|
\MPscratchDim=\dimen260
|
||||||
|
\MPnumerator=\count299
|
||||||
|
\makeMPintoPDFobject=\count300
|
||||||
|
\everyMPtoPDFconversion=\toks41
|
||||||
|
) (/usr/local/texlive/2022/texmf-dist/tex/latex/epstopdf-pkg/epstopdf-base.sty
|
||||||
|
Package: epstopdf-base 2020-01-24 v2.11 Base part for package epstopdf
|
||||||
|
Package epstopdf-base Info: Redefining graphics rule for `.eps' on input line 4
|
||||||
|
85.
|
||||||
|
|
||||||
|
(/usr/local/texlive/2022/texmf-dist/tex/latex/latexconfig/epstopdf-sys.cfg
|
||||||
|
File: epstopdf-sys.cfg 2010/07/13 v1.3 Configuration of (r)epstopdf for TeX Liv
|
||||||
|
e
|
||||||
|
))
|
||||||
|
[1{/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map}]
|
||||||
|
|
||||||
|
LaTeX Warning: `h' float specifier changed to `ht'.
|
||||||
|
|
||||||
|
[2] [3] (./whole_medial_seed59_min5.tikz) [4] [5] (./paper.aux) )
|
||||||
|
Here is how much of TeX's memory you used:
|
||||||
|
13803 strings out of 478268
|
||||||
|
275582 string characters out of 5846347
|
||||||
|
655790 words of memory out of 5000000
|
||||||
|
31627 multiletter control sequences out of 15000+600000
|
||||||
|
477661 words of font info for 60 fonts, out of 8000000 for 9000
|
||||||
|
1302 hyphenation exceptions out of 8191
|
||||||
|
84i,15n,89p,907b,804s stack positions out of 10000i,1000n,20000p,200000b,200000s
|
||||||
|
</usr/local/te
|
||||||
|
xlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmbx10.pfb></usr/local/tex
|
||||||
|
live/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmbx6.pfb></usr/local/texli
|
||||||
|
ve/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmbx7.pfb></usr/local/texlive
|
||||||
|
/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmcsc10.pfb></usr/local/texlive
|
||||||
|
/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi10.pfb></usr/local/texlive/
|
||||||
|
2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi7.pfb></usr/local/texlive/20
|
||||||
|
22/texmf-dist/fonts/type1/public/amsfonts/cm/cmr10.pfb></usr/local/texlive/2022
|
||||||
|
/texmf-dist/fonts/type1/public/amsfonts/cm/cmr6.pfb></usr/local/texlive/2022/te
|
||||||
|
xmf-dist/fonts/type1/public/amsfonts/cm/cmr7.pfb></usr/local/texlive/2022/texmf
|
||||||
|
-dist/fonts/type1/public/amsfonts/cm/cmr8.pfb></usr/local/texlive/2022/texmf-di
|
||||||
|
st/fonts/type1/public/amsfonts/cm/cmss10.pfb></usr/local/texlive/2022/texmf-dis
|
||||||
|
t/fonts/type1/public/amsfonts/cm/cmsy10.pfb></usr/local/texlive/2022/texmf-dist
|
||||||
|
/fonts/type1/public/amsfonts/cm/cmsy6.pfb></usr/local/texlive/2022/texmf-dist/f
|
||||||
|
onts/type1/public/amsfonts/cm/cmti10.pfb></usr/local/texlive/2022/texmf-dist/fo
|
||||||
|
nts/type1/public/amsfonts/cm/cmti8.pfb></usr/local/texlive/2022/texmf-dist/font
|
||||||
|
s/type1/public/amsfonts/cm/cmtt10.pfb>
|
||||||
|
Output written on paper.pdf (5 pages, 226751 bytes).
|
||||||
|
PDF statistics:
|
||||||
|
103 PDF objects out of 1000 (max. 8388607)
|
||||||
|
63 compressed objects within 1 object stream
|
||||||
|
0 named destinations out of 1000 (max. 500000)
|
||||||
|
13 words of extra memory for PDF output out of 10000 (max. 10000000)
|
||||||
|
|
||||||
@@ -0,0 +1,450 @@
|
|||||||
|
%% filename: amsart-template.tex
|
||||||
|
%% American Mathematical Society
|
||||||
|
%% AMS-LaTeX v.2 template for use with amsart
|
||||||
|
%% ====================================================================
|
||||||
|
|
||||||
|
\documentclass{amsart}
|
||||||
|
|
||||||
|
\usepackage{amssymb}
|
||||||
|
\usepackage{graphicx}
|
||||||
|
\usepackage{tikz}
|
||||||
|
\usetikzlibrary{backgrounds}
|
||||||
|
|
||||||
|
\newtheorem{theorem}{Theorem}[section]
|
||||||
|
\newtheorem{lemma}[theorem]{Lemma}
|
||||||
|
\newtheorem{corollary}[theorem]{Corollary}
|
||||||
|
\newtheorem{proposition}[theorem]{Proposition}
|
||||||
|
\newtheorem{conjecture}[theorem]{Conjecture}
|
||||||
|
|
||||||
|
\theoremstyle{definition}
|
||||||
|
\newtheorem{definition}[theorem]{Definition}
|
||||||
|
\newtheorem{example}[theorem]{Example}
|
||||||
|
\newtheorem{xca}[theorem]{Exercise}
|
||||||
|
|
||||||
|
\theoremstyle{remark}
|
||||||
|
\newtheorem{remark}[theorem]{Remark}
|
||||||
|
|
||||||
|
\numberwithin{equation}{section}
|
||||||
|
|
||||||
|
\begin{document}
|
||||||
|
|
||||||
|
\title{Medial Tire Cuts}
|
||||||
|
|
||||||
|
% author one information
|
||||||
|
\author{Eric Bauerfeld}
|
||||||
|
\address{}
|
||||||
|
\curraddr{}
|
||||||
|
\email{}
|
||||||
|
\thanks{}
|
||||||
|
|
||||||
|
\subjclass[2010]{Primary }
|
||||||
|
|
||||||
|
\keywords{plane graph, triangulation, medial graph, tire graph, Tait coloring, Four Colour Theorem}
|
||||||
|
|
||||||
|
\date{}
|
||||||
|
|
||||||
|
\dedicatory{}
|
||||||
|
|
||||||
|
\begin{abstract}
|
||||||
|
Starting from the medial tire decomposition of a plane triangulation, we
|
||||||
|
study the cuts that medial tires make in the full medial graph. We will
|
||||||
|
show how to use medial tires to decompose the medial graph into a tree of
|
||||||
|
three faces.
|
||||||
|
\end{abstract}
|
||||||
|
|
||||||
|
\maketitle
|
||||||
|
|
||||||
|
\section{Introduction}
|
||||||
|
|
||||||
|
This paper builds on the medial tire decomposition
|
||||||
|
of~\cite{bauerfeld-medial-tire}. For a plane triangulation $G$ with
|
||||||
|
fixed embedding we use freely the terminology and notation introduced
|
||||||
|
there: the full medial graph $M(G)$, its decomposition into full medial
|
||||||
|
tire graphs $\mathsf{M}(T)$ indexed by the treads $T$ of the tire tree
|
||||||
|
$\mathcal{T}(G,S)$ at a level source $S$, the annular medial cycle
|
||||||
|
$A(T)$, and the boundary medial vertex sets.
|
||||||
|
|
||||||
|
We will show how to use medial tires to decompose the medial graph into
|
||||||
|
a tree of three faces.
|
||||||
|
|
||||||
|
\section{Cutting a full medial tire graph}
|
||||||
|
|
||||||
|
We first describe a procedure that simultaneously \emph{labels} and
|
||||||
|
\emph{cuts} a single full medial tire graph $\mathsf{M}(T)$ so that,
|
||||||
|
after the cuts, the only faces are the outer face and $3$-faces
|
||||||
|
(triangles)---the teeth of~\cite{bauerfeld-medial-tire}. The labelling
|
||||||
|
assigns to each tooth an integer \emph{walk depth}; the cuts break the
|
||||||
|
cyclic adjacencies of the teeth so that what remains is a tree of
|
||||||
|
$3$-faces.
|
||||||
|
|
||||||
|
By a \emph{cut} we mean the duplication of a single vertex of
|
||||||
|
$\mathsf{M}(T)$: the vertex is split into two copies and the embedding is
|
||||||
|
slit open along it (a planar unzip), separating the faces that meet only
|
||||||
|
at that vertex. A cut therefore reduces the number of bounded faces that
|
||||||
|
are not teeth.
|
||||||
|
|
||||||
|
Throughout we use the teeth, up and down teeth, apexes, bites, the
|
||||||
|
annular medial cycle $A(T)$, and the auxiliary plane graph $B(T)$
|
||||||
|
of~\cite{bauerfeld-medial-tire}. Each tooth is a $3$-face of
|
||||||
|
$\mathsf{M}(T)$, and the inner faces of $B(T)$ (the root face and the
|
||||||
|
bite inner-gap faces) are the larger faces to be cut into teeth.
|
||||||
|
|
||||||
|
\begin{definition}[Walk-depth labelling and cut]
|
||||||
|
\label{def:walk-depth-cut}
|
||||||
|
Let $\mathsf{M}(T)$ be a full medial tire graph. Assign walk depths and
|
||||||
|
cuts as follows.
|
||||||
|
\begin{enumerate}
|
||||||
|
\item Pick an arbitrary up tooth, the \emph{entry tooth}. It has walk
|
||||||
|
depth $d$.
|
||||||
|
\item Traverse all the teeth that bound the inner face incident to the
|
||||||
|
entry tooth clockwise until we reach the entry tooth, incrementing the
|
||||||
|
walk depth by $1$ for each tooth traversed. (The \emph{inner face
|
||||||
|
incident to the entry tooth} is the inner face of $B(T)$ whose boundary
|
||||||
|
contains the annular edge of $A(T)$ carrying the entry tooth.)
|
||||||
|
\item When you reach the last tooth in the face, perform a \emph{cut}
|
||||||
|
by duplicating the annular vertex at which the traversal closes---the
|
||||||
|
annular vertex of $A(T)$ shared by the last tooth and the entry tooth.
|
||||||
|
\item Find the tooth $t$ with the highest walk depth which is a member
|
||||||
|
of a bite.
|
||||||
|
\item If $t$ is incident to a face $F$ with unlabelled teeth, traverse
|
||||||
|
the teeth in $F$ starting from $t$ in the direction of the tooth
|
||||||
|
incident to $t$ which is unlabelled, and increment the walk depth by
|
||||||
|
$1$ as you travel. (Here a tooth is \emph{incident to $t$} when it
|
||||||
|
shares an annular vertex of $A(T)$ with $t$.)
|
||||||
|
\item Repeat steps (3)--(5) until all teeth have been labelled.
|
||||||
|
\item Finally, perform an apex cut at every up tooth except an entry
|
||||||
|
tooth. If the same medial vertex is the apex of two up teeth, do not
|
||||||
|
cut that shared apex vertex.
|
||||||
|
\end{enumerate}
|
||||||
|
\end{definition}
|
||||||
|
|
||||||
|
\begin{remark}[Entry and shared up-apex exceptions]
|
||||||
|
\label{rem:up-apex-cut-exceptions}
|
||||||
|
For a single full medial tire graph there is one entry tooth. In a
|
||||||
|
chained tire decomposition each tread has its own entry tooth, inherited
|
||||||
|
from the parent side or chosen at the root. These entry triangles are
|
||||||
|
left uncut. Shared up-apex vertices are also left uncut: the intended
|
||||||
|
cut set contains the apexes of singleton up teeth only.
|
||||||
|
\end{remark}
|
||||||
|
|
||||||
|
\begin{remark}[Closing tooth of a descended face]
|
||||||
|
\label{rem:closing-tooth}
|
||||||
|
For the entry face the traversal of step (2) closes by returning to the
|
||||||
|
entry tooth, so the cut of step (3) duplicates the annular vertex shared
|
||||||
|
by the last tooth and the entry tooth. For a face $F$ entered in step
|
||||||
|
(5), the traversal instead closes upon reaching an already-labelled
|
||||||
|
tooth: the other tooth of the bite through which $F$ was entered. In
|
||||||
|
both cases the cut of step (3) duplicates the annular vertex shared by
|
||||||
|
the last newly labelled tooth and this \emph{closing tooth}. Since both
|
||||||
|
teeth of a bite are labelled while traversing its parent face, every
|
||||||
|
descended face closes on such a tooth.
|
||||||
|
\end{remark}
|
||||||
|
|
||||||
|
\begin{example}[A worked walk-depth labelling and cut]
|
||||||
|
\label{ex:worked-cut}
|
||||||
|
Figure~\ref{fig:worked-cut} shows a full medial tire graph with annular
|
||||||
|
cycle of length $8$, generated by the full medial tire generator
|
||||||
|
of~\cite{bauerfeld-medial-tire}. Its eight teeth are: three up teeth on
|
||||||
|
the annular edges $5,6,7$ in the root face; one bite pairing the annular
|
||||||
|
edges $0$ and $4$; and three singleton down teeth on the annular edges
|
||||||
|
$1,2,3$ lying in that bite's inner-gap face.
|
||||||
|
|
||||||
|
Take the up tooth on edge $5$ as the entry tooth, with walk depth $0$.
|
||||||
|
Its inner face is the root face, bounded by the teeth on edges
|
||||||
|
$5,6,7,0,4$ in clockwise order. Step (2) labels them
|
||||||
|
\[
|
||||||
|
5\mapsto 0,\quad 6\mapsto 1,\quad 7\mapsto 2,\quad
|
||||||
|
0\mapsto 3,\quad 4\mapsto 4,
|
||||||
|
\]
|
||||||
|
and step (3) cuts by duplicating the annular vertex $a_5$ shared by the
|
||||||
|
last tooth (edge $4$) and the entry tooth (edge $5$). The highest-depth
|
||||||
|
bite tooth is now the one on edge $4$ (walk depth $4$); it is incident to
|
||||||
|
the still-unlabelled inner-gap face of the bite $(0,4)$. Entering that
|
||||||
|
face from edge $4$ toward its unlabelled neighbour, step (5) labels the
|
||||||
|
three down teeth
|
||||||
|
\[
|
||||||
|
3\mapsto 5,\quad 2\mapsto 6,\quad 1\mapsto 7,
|
||||||
|
\]
|
||||||
|
and closes on the already-labelled bite tooth of edge $0$, so step (3)
|
||||||
|
cuts by duplicating the annular vertex $a_1$
|
||||||
|
(Remark~\ref{rem:closing-tooth}). All eight teeth are now labelled, and
|
||||||
|
the closing cuts are followed by apex cuts at the non-entry up teeth on
|
||||||
|
edges $6$ and $7$. The labelling and cuts are produced by the script
|
||||||
|
\texttt{lib/medial\_tire\_cut\_labelling.py}.
|
||||||
|
\end{example}
|
||||||
|
|
||||||
|
\begin{figure}[h]
|
||||||
|
\centering
|
||||||
|
\begin{tikzpicture}[scale=1.6,
|
||||||
|
ann/.style={circle, fill=black, inner sep=1.0pt},
|
||||||
|
upv/.style={circle, draw=blue!70!black, fill=blue!12, inner sep=1.4pt},
|
||||||
|
downv/.style={circle, draw=red!70!black, fill=red!12, inner sep=1.4pt},
|
||||||
|
bitev/.style={circle, draw=red!70!black, fill=red!32, inner sep=1.7pt},
|
||||||
|
cyc/.style={black, line width=1.0pt},
|
||||||
|
tth/.style={black!55, line width=0.4pt},
|
||||||
|
lbl/.style={font=\scriptsize},
|
||||||
|
dlbl/.style={font=\scriptsize\bfseries, text=black},
|
||||||
|
cut/.style={red!80!black, line width=1.3pt},
|
||||||
|
cutlbl/.style={font=\tiny, text=red!75!black}]
|
||||||
|
\draw[cyc] (0.000,1.000)--(0.707,0.707)--(1.000,0.000)--(0.707,-0.707)--(0.000,-1.000)--(-0.707,-0.707)--(-1.000,-0.000)--(-0.707,0.707)--cycle;
|
||||||
|
\draw[tth] (-1.349,-0.559)--(-0.707,-0.707) (-1.349,-0.559)--(-1.000,-0.000);
|
||||||
|
\draw[tth] (-1.349,0.559)--(-1.000,-0.000) (-1.349,0.559)--(-0.707,0.707);
|
||||||
|
\draw[tth] (-0.559,1.349)--(-0.707,0.707) (-0.559,1.349)--(0.000,1.000);
|
||||||
|
\draw[tth] (0.554,0.230)--(0.707,0.707) (0.554,0.230)--(1.000,0.000);
|
||||||
|
\draw[tth] (0.554,-0.230)--(1.000,0.000) (0.554,-0.230)--(0.707,-0.707);
|
||||||
|
\draw[tth] (0.230,-0.554)--(0.707,-0.707) (0.230,-0.554)--(0.000,-1.000);
|
||||||
|
\draw[tth] (0.000,-0.000)--(0.000,1.000) (0.000,-0.000)--(0.707,0.707);
|
||||||
|
\draw[tth] (0.000,-0.000)--(0.000,-1.000) (0.000,-0.000)--(-0.707,-0.707);
|
||||||
|
\node[ann] at (0.000,1.000) {};
|
||||||
|
\node[ann] at (0.707,0.707) {};
|
||||||
|
\node[ann] at (1.000,0.000) {};
|
||||||
|
\node[ann] at (0.707,-0.707) {};
|
||||||
|
\node[ann] at (0.000,-1.000) {};
|
||||||
|
\node[ann] at (-0.707,-0.707) {};
|
||||||
|
\node[ann] at (-1.000,-0.000) {};
|
||||||
|
\node[ann] at (-0.707,0.707) {};
|
||||||
|
\node[upv] at (-1.349,-0.559) {};
|
||||||
|
\node[upv] at (-1.349,0.559) {};
|
||||||
|
\node[upv] at (-0.559,1.349) {};
|
||||||
|
\node[downv] at (0.554,0.230) {};
|
||||||
|
\node[downv] at (0.554,-0.230) {};
|
||||||
|
\node[downv] at (0.230,-0.554) {};
|
||||||
|
\node[bitev] at (0.000,-0.000) {};
|
||||||
|
\end{tikzpicture}
|
||||||
|
\qquad
|
||||||
|
\begin{tikzpicture}[scale=1.6,
|
||||||
|
ann/.style={circle, fill=black, inner sep=1.0pt},
|
||||||
|
upv/.style={circle, draw=blue!70!black, fill=blue!12, inner sep=1.4pt},
|
||||||
|
downv/.style={circle, draw=red!70!black, fill=red!12, inner sep=1.4pt},
|
||||||
|
bitev/.style={circle, draw=red!70!black, fill=red!32, inner sep=1.7pt},
|
||||||
|
cyc/.style={black, line width=1.0pt},
|
||||||
|
tth/.style={black!55, line width=0.4pt},
|
||||||
|
lbl/.style={font=\scriptsize},
|
||||||
|
dlbl/.style={font=\scriptsize\bfseries, text=black},
|
||||||
|
cut/.style={red!80!black, line width=1.3pt},
|
||||||
|
cutlbl/.style={font=\tiny, text=red!75!black}]
|
||||||
|
\draw[cyc] (0.000,1.000)--(0.707,0.707)--(1.000,0.000)--(0.707,-0.707)--(0.000,-1.000)--(-0.707,-0.707)--(-1.000,-0.000)--(-0.707,0.707)--cycle;
|
||||||
|
\draw[tth] (-1.349,-0.559)--(-0.707,-0.707) (-1.349,-0.559)--(-1.000,-0.000);
|
||||||
|
\draw[tth] (-1.349,0.559)--(-1.000,-0.000) (-1.349,0.559)--(-0.707,0.707);
|
||||||
|
\draw[tth] (-0.559,1.349)--(-0.707,0.707) (-0.559,1.349)--(0.000,1.000);
|
||||||
|
\draw[tth] (0.554,0.230)--(0.707,0.707) (0.554,0.230)--(1.000,0.000);
|
||||||
|
\draw[tth] (0.554,-0.230)--(1.000,0.000) (0.554,-0.230)--(0.707,-0.707);
|
||||||
|
\draw[tth] (0.230,-0.554)--(0.707,-0.707) (0.230,-0.554)--(0.000,-1.000);
|
||||||
|
\draw[tth] (0.000,-0.000)--(0.000,1.000) (0.000,-0.000)--(0.707,0.707);
|
||||||
|
\draw[tth] (0.000,-0.000)--(0.000,-1.000) (0.000,-0.000)--(-0.707,-0.707);
|
||||||
|
\node[ann] at (0.000,1.000) {};
|
||||||
|
\node[ann] at (0.707,0.707) {};
|
||||||
|
\node[ann] at (1.000,0.000) {};
|
||||||
|
\node[ann] at (0.707,-0.707) {};
|
||||||
|
\node[ann] at (0.000,-1.000) {};
|
||||||
|
\node[ann] at (-0.707,-0.707) {};
|
||||||
|
\node[ann] at (-1.000,-0.000) {};
|
||||||
|
\node[ann] at (-0.707,0.707) {};
|
||||||
|
\node[upv] at (-1.349,-0.559) {};
|
||||||
|
\node[upv] at (-1.349,0.559) {};
|
||||||
|
\node[upv] at (-0.559,1.349) {};
|
||||||
|
\node[downv] at (0.554,0.230) {};
|
||||||
|
\node[downv] at (0.554,-0.230) {};
|
||||||
|
\node[downv] at (0.230,-0.554) {};
|
||||||
|
\node[bitev] at (0.000,-0.000) {};
|
||||||
|
\node[dlbl] at (0.177,0.427) {3};
|
||||||
|
\node[dlbl] at (0.704,0.292) {7};
|
||||||
|
\node[dlbl] at (0.704,-0.292) {6};
|
||||||
|
\node[dlbl] at (0.292,-0.704) {5};
|
||||||
|
\node[dlbl] at (-0.177,-0.427) {4};
|
||||||
|
\node[dlbl] at (-1.101,-0.456) {0};
|
||||||
|
\node[dlbl] at (-1.101,0.456) {1};
|
||||||
|
\node[dlbl] at (-0.456,1.101) {2};
|
||||||
|
\draw[cut] (-0.594,-0.594)--(-0.820,-0.820);
|
||||||
|
\node[cutlbl] at (-0.919,-0.919) {cut 1};
|
||||||
|
\draw[cut] (0.594,0.594)--(0.820,0.820);
|
||||||
|
\node[cutlbl] at (0.919,0.919) {cut 2};
|
||||||
|
\node[lbl, text=blue!60!black] at (-1.663,-0.689) {entry};
|
||||||
|
\end{tikzpicture}
|
||||||
|
\caption{A full medial tire graph (left) and its walk-depth labelling and
|
||||||
|
cut (right), from Example~\ref{ex:worked-cut}. Black vertices are the
|
||||||
|
annular medial vertices of the cycle $A(T)$; blue vertices are up-tooth
|
||||||
|
apexes, red vertices are down-tooth apexes, and the larger red vertex is
|
||||||
|
the shared apex of the bite on annular edges $0$ and $4$. On the right,
|
||||||
|
each tooth carries its walk depth, and the two red slits mark the cuts:
|
||||||
|
\emph{cut~1} duplicates $a_5$ as the root-face traversal closes, and
|
||||||
|
\emph{cut~2} duplicates $a_1$ as the bite's inner-gap face closes. After
|
||||||
|
the cuts the only bounded faces are the eight teeth.}
|
||||||
|
\label{fig:worked-cut}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\section{Chaining across the tire tree}
|
||||||
|
|
||||||
|
Definition~\ref{def:walk-depth-cut} labels and cuts a single full medial
|
||||||
|
tire graph. We extend it to the whole medial graph $M(G)$ through the
|
||||||
|
medial tire decomposition of~\cite{bauerfeld-medial-tire}: the tire tree
|
||||||
|
decomposes $M(G)$ into full medial tire graphs $\mathsf{M}(T)$, one per
|
||||||
|
tread $T$, glued along their boundary medial vertices. A parent tread's
|
||||||
|
inner level cycle is a child tread's outer level cycle, and the boundary
|
||||||
|
medial vertices on that shared cycle belong to both treads.
|
||||||
|
|
||||||
|
The key incidence is this. A \emph{boundary} (singleton) down tooth of a
|
||||||
|
parent tread and the up tooth of the child tread glued to it across the
|
||||||
|
shared level cycle have the \emph{same apex}: both apexes are the same
|
||||||
|
medial vertex of $M(G)$, namely the medial vertex of an edge with both
|
||||||
|
endpoints on the shared level cycle. We use this to carry the walk depth
|
||||||
|
from a parent into its children.
|
||||||
|
|
||||||
|
We label tread by tread, outward from the root:
|
||||||
|
\begin{itemize}
|
||||||
|
\item a tread with no parent in the decomposition---in particular the
|
||||||
|
innermost recognised tread---is treated as a \emph{root} and entered at
|
||||||
|
an arbitrary up tooth with walk depth $0$;
|
||||||
|
\item a child tread is entered at the up tooth whose apex is the parent's
|
||||||
|
boundary down tooth of lowest walk depth; that entry up tooth's walk depth
|
||||||
|
is one more than that down tooth's, and the walk then increments locally
|
||||||
|
within the child as in Definition~\ref{def:walk-depth-cut}.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
The source cap contributes one additional cut before the recognised
|
||||||
|
treads are assembled. If the root tread enters at an up tooth whose apex
|
||||||
|
is the cap down tooth $xy$, we cut the cap annular vertex corresponding
|
||||||
|
to the counter-clockwise source edge incident to $xy$. In the example of
|
||||||
|
Figure~\ref{fig:whole-medial}, the root entry apex is the cap down tooth
|
||||||
|
$14\!-\!4$, so the cap cut is placed at the medial vertex $14\!-\!5$.
|
||||||
|
|
||||||
|
\begin{remark}[Candidate down teeth for chaining]
|
||||||
|
\label{rem:chaining-candidates}
|
||||||
|
The down teeth eligible to fix a child's entry are exactly the
|
||||||
|
\emph{boundary} (singleton) down teeth of the parent: those lying in a
|
||||||
|
single tread face, whose apex is the shared boundary medial vertex glued to
|
||||||
|
a child up tooth. A bite's two down teeth are \emph{not} eligible. By the
|
||||||
|
definition of a bite in~\cite{bauerfeld-medial-tire} its annular edge borders
|
||||||
|
two tread faces, so a bite tooth is interior to the parent tread and its
|
||||||
|
apex is a boundary medial vertex of no child. Hence ``the down tooth of
|
||||||
|
lowest walk depth'' is read among the boundary down teeth only; a bite of
|
||||||
|
even lower walk depth is skipped.
|
||||||
|
\end{remark}
|
||||||
|
|
||||||
|
Applying every tread's cuts to $M(G)$ assembles the per-tread labellings
|
||||||
|
and cuts into a single cut graph of $M(G)$ together with a global
|
||||||
|
walk-depth label map. This pipeline---random maximal planar graph, medial
|
||||||
|
graph, tire decomposition at a vertex level source, and chained walk-depth
|
||||||
|
labelling and cut---is carried out by the experiment script
|
||||||
|
\texttt{experiments/run\_medial\_tire\_cut\_experiment.py}.
|
||||||
|
|
||||||
|
\begin{example}[A medial tire cut from a random graph]
|
||||||
|
\label{ex:real-cut}
|
||||||
|
Run on a random maximal planar graph on $20$ vertices (seed $72$, level
|
||||||
|
source vertex $9$), the experiment yields a single recognised tread
|
||||||
|
$T_2$, drawn in Figure~\ref{fig:real-cut} with the walk-depth labelling
|
||||||
|
and cut emitted by the graphics companion
|
||||||
|
\texttt{experiments/draw\_medial\_tire\_cut.py}. Its annular cycle has
|
||||||
|
length $8$, with up teeth on annular edges $0,3,4$, singleton down teeth
|
||||||
|
on $1,6,7$, and a bite on the non-incident annular edges $2$ and $5$ (the
|
||||||
|
central shared apex). Entering at the up tooth on edge $0$ with walk
|
||||||
|
depth $0$, the root face is labelled in order ($0,1,2$ then $3,4,5$) and
|
||||||
|
\emph{cut~1} duplicates $a_0$ as it closes; the walk then descends through
|
||||||
|
the bite into its inner-gap face, labelling the two teeth there ($6,7$),
|
||||||
|
and \emph{cut~2} duplicates $a_3$ as that face closes. The two cuts leave
|
||||||
|
only the outer face and the eight teeth as $3$-faces.
|
||||||
|
\end{example}
|
||||||
|
|
||||||
|
\begin{figure}[h]
|
||||||
|
\centering
|
||||||
|
\begin{tikzpicture}[scale=1.6,
|
||||||
|
ann/.style={circle, fill=black, inner sep=1.0pt},
|
||||||
|
upv/.style={circle, draw=blue!70!black, fill=blue!12, inner sep=1.4pt},
|
||||||
|
downv/.style={circle, draw=red!70!black, fill=red!12, inner sep=1.4pt},
|
||||||
|
bitev/.style={circle, draw=red!70!black, fill=red!32, inner sep=1.7pt},
|
||||||
|
cyc/.style={black, line width=1.0pt},
|
||||||
|
tth/.style={black!55, line width=0.4pt},
|
||||||
|
lbl/.style={font=\scriptsize},
|
||||||
|
dlbl/.style={font=\scriptsize\bfseries, text=black},
|
||||||
|
cut/.style={red!80!black, line width=1.3pt},
|
||||||
|
cutlbl/.style={font=\tiny, text=red!75!black}]
|
||||||
|
\draw[cyc] (0.000,1.000)--(0.707,0.707)--(1.000,0.000)--(0.707,-0.707)--(0.000,-1.000)--(-0.707,-0.707)--(-1.000,-0.000)--(-0.707,0.707)--cycle;
|
||||||
|
\draw[tth] (0.559,1.349)--(0.000,1.000) (0.559,1.349)--(0.707,0.707);
|
||||||
|
\draw[tth] (0.559,-1.349)--(0.707,-0.707) (0.559,-1.349)--(0.000,-1.000);
|
||||||
|
\draw[tth] (-0.559,-1.349)--(0.000,-1.000) (-0.559,-1.349)--(-0.707,-0.707);
|
||||||
|
\draw[tth] (0.554,0.230)--(0.707,0.707) (0.554,0.230)--(1.000,0.000);
|
||||||
|
\draw[tth] (-0.554,0.230)--(-1.000,-0.000) (-0.554,0.230)--(-0.707,0.707);
|
||||||
|
\draw[tth] (-0.230,0.554)--(-0.707,0.707) (-0.230,0.554)--(0.000,1.000);
|
||||||
|
\draw[tth] (0.000,-0.318)--(1.000,0.000) (0.000,-0.318)--(0.707,-0.707);
|
||||||
|
\draw[tth] (0.000,-0.318)--(-0.707,-0.707) (0.000,-0.318)--(-1.000,-0.000);
|
||||||
|
\node[ann] at (0.000,1.000) {};
|
||||||
|
\node[ann] at (0.707,0.707) {};
|
||||||
|
\node[ann] at (1.000,0.000) {};
|
||||||
|
\node[ann] at (0.707,-0.707) {};
|
||||||
|
\node[ann] at (0.000,-1.000) {};
|
||||||
|
\node[ann] at (-0.707,-0.707) {};
|
||||||
|
\node[ann] at (-1.000,-0.000) {};
|
||||||
|
\node[ann] at (-0.707,0.707) {};
|
||||||
|
\node[upv] at (0.559,1.349) {};
|
||||||
|
\node[upv] at (0.559,-1.349) {};
|
||||||
|
\node[upv] at (-0.559,-1.349) {};
|
||||||
|
\node[downv] at (0.554,0.230) {};
|
||||||
|
\node[downv] at (-0.554,0.230) {};
|
||||||
|
\node[downv] at (-0.230,0.554) {};
|
||||||
|
\node[bitev] at (0.000,-0.318) {};
|
||||||
|
\node[dlbl] at (0.456,1.101) {0};
|
||||||
|
\node[dlbl] at (0.704,0.292) {1};
|
||||||
|
\node[dlbl] at (0.427,-0.336) {2};
|
||||||
|
\node[dlbl] at (0.456,-1.101) {7};
|
||||||
|
\node[dlbl] at (-0.456,-1.101) {6};
|
||||||
|
\node[dlbl] at (-0.427,-0.336) {3};
|
||||||
|
\node[dlbl] at (-0.704,0.292) {4};
|
||||||
|
\node[dlbl] at (-0.292,0.704) {5};
|
||||||
|
\draw[cut] (0.000,0.840)--(0.000,1.160);
|
||||||
|
\node[cutlbl] at (0.000,1.300) {cut 1};
|
||||||
|
\draw[cut] (0.594,-0.594)--(0.820,-0.820);
|
||||||
|
\node[cutlbl] at (0.919,-0.919) {cut 2};
|
||||||
|
\node[lbl, text=blue!60!black] at (0.689,1.663) {entry};
|
||||||
|
\end{tikzpicture}
|
||||||
|
\caption{The recognised tread $T_2$ of the medial tire decomposition of a
|
||||||
|
random maximal planar graph on $20$ vertices
|
||||||
|
(Example~\ref{ex:real-cut}), with its walk-depth labelling and cut. Black
|
||||||
|
vertices are the annular medial vertices of $A(T)$; blue vertices are
|
||||||
|
up-tooth apexes and red vertices down-tooth apexes, the larger red vertex
|
||||||
|
being the shared apex of the bite on annular edges $2$ and $5$. Each
|
||||||
|
tooth carries its walk depth; the red slits are the two cuts.}
|
||||||
|
\label{fig:real-cut}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
Figure~\ref{fig:whole-medial} repeats the whole-medial-graph drawing on a
|
||||||
|
random maximal planar graph on $20$ vertices with minimum degree $5$
|
||||||
|
(plantri seed $59$, level source vertex $5$). The experiment recognises
|
||||||
|
two full medial tire treads, $T_1$ and $T_2$, and produces seven cuts:
|
||||||
|
one source-cap cut and six full-tread cuts. The
|
||||||
|
top panel shows the source triangulation with its level source
|
||||||
|
highlighted; the bottom panel draws $M(G)$ on the same straight-line
|
||||||
|
embedding by placing each medial vertex at the midpoint of its
|
||||||
|
corresponding source edge. Every medial vertex is labelled by that source
|
||||||
|
edge. Black vertices correspond to source edges joining consecutive
|
||||||
|
levels, and coloured vertices correspond to source edges within one level.
|
||||||
|
The red-highlighted vertices, walk-depth labels, and red slits are the
|
||||||
|
computed full-medial-tire labelling and cuts.
|
||||||
|
|
||||||
|
\begin{figure}[h]
|
||||||
|
\centering
|
||||||
|
\input{whole_medial_seed59_min5.tikz}
|
||||||
|
\caption{The source graph $G$ and the whole medial graph $M(G)$ of the
|
||||||
|
minimum-degree-$5$ maximal planar graph on $20$ vertices generated by
|
||||||
|
\texttt{plantri -m5} at seed $59$. The source vertex $5$ is highlighted
|
||||||
|
in the top panel. In the bottom panel, each medial vertex is placed at
|
||||||
|
the midpoint of its corresponding source edge and labelled by that edge.
|
||||||
|
Black vertices come from source edges between consecutive levels; coloured
|
||||||
|
vertices come from source edges within a single level of the chain. The
|
||||||
|
red-highlighted vertices, walk-depth labels, and seven red slits are the
|
||||||
|
computed source-cap cut and full-medial-tire labelling cuts for the
|
||||||
|
recognised treads $T_1$ and $T_2$. Drawn by
|
||||||
|
\texttt{experiments/draw\_medial\_tire\_cut.py} with
|
||||||
|
\texttt{--whole --min-degree 5}.}
|
||||||
|
\label{fig:whole-medial}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\begin{thebibliography}{9}
|
||||||
|
|
||||||
|
\bibitem{bauerfeld-medial-tire}
|
||||||
|
E.~Bauerfeld,
|
||||||
|
\emph{Medial Tire Decompositions of Plane Triangulations},
|
||||||
|
manuscript (math-research repository), 2026.
|
||||||
|
|
||||||
|
\end{thebibliography}
|
||||||
|
|
||||||
|
\end{document}
|
||||||
|
After Width: | Height: | Size: 262 KiB |
|
After Width: | Height: | Size: 224 KiB |
|
After Width: | Height: | Size: 283 KiB |
@@ -0,0 +1,403 @@
|
|||||||
|
% whole medial graph: n=20 seed=59 graph_seed=59 min_degree=5 source=5 recognised treads=[1, 2] |M(G)|=54
|
||||||
|
\begin{tabular}{c}
|
||||||
|
\begin{tikzpicture}[scale=4.06,
|
||||||
|
sedge/.style={black!50, line width=0.35pt},
|
||||||
|
sv/.style={circle, draw=black!60, fill=white, inner sep=1.1pt},
|
||||||
|
srcv/.style={circle, draw=blue!75!black, fill=blue!18, line width=0.7pt, inner sep=1.8pt}]
|
||||||
|
\draw[sedge] (0.000,0.433)--(-0.500,-0.433);
|
||||||
|
\draw[sedge] (0.000,0.433)--(0.500,-0.433);
|
||||||
|
\draw[sedge] (0.000,0.433)--(0.084,-0.096);
|
||||||
|
\draw[sedge] (0.000,0.433)--(-0.018,0.026);
|
||||||
|
\draw[sedge] (0.000,0.433)--(-0.128,-0.048);
|
||||||
|
\draw[sedge] (-0.500,-0.433)--(0.500,-0.433);
|
||||||
|
\draw[sedge] (-0.500,-0.433)--(-0.128,-0.048);
|
||||||
|
\draw[sedge] (-0.500,-0.433)--(-0.073,-0.186);
|
||||||
|
\draw[sedge] (-0.500,-0.433)--(0.019,-0.303);
|
||||||
|
\draw[sedge] (0.500,-0.433)--(0.084,-0.096);
|
||||||
|
\draw[sedge] (0.500,-0.433)--(0.019,-0.303);
|
||||||
|
\draw[sedge] (0.500,-0.433)--(0.139,-0.245);
|
||||||
|
\draw[sedge] (0.084,-0.096)--(-0.018,0.026);
|
||||||
|
\draw[sedge] (0.084,-0.096)--(0.139,-0.245);
|
||||||
|
\draw[sedge] (0.084,-0.096)--(0.060,-0.177);
|
||||||
|
\draw[sedge] (0.084,-0.096)--(0.035,-0.147);
|
||||||
|
\draw[sedge] (0.084,-0.096)--(0.024,-0.131);
|
||||||
|
\draw[sedge] (0.084,-0.096)--(0.015,-0.113);
|
||||||
|
\draw[sedge] (0.084,-0.096)--(0.002,-0.076);
|
||||||
|
\draw[sedge] (-0.018,0.026)--(-0.128,-0.048);
|
||||||
|
\draw[sedge] (-0.018,0.026)--(0.002,-0.076);
|
||||||
|
\draw[sedge] (-0.018,0.026)--(-0.048,-0.081);
|
||||||
|
\draw[sedge] (-0.128,-0.048)--(-0.073,-0.186);
|
||||||
|
\draw[sedge] (-0.128,-0.048)--(-0.048,-0.081);
|
||||||
|
\draw[sedge] (-0.073,-0.186)--(0.019,-0.303);
|
||||||
|
\draw[sedge] (-0.073,-0.186)--(-0.048,-0.081);
|
||||||
|
\draw[sedge] (-0.073,-0.186)--(-0.023,-0.119);
|
||||||
|
\draw[sedge] (-0.073,-0.186)--(-0.012,-0.141);
|
||||||
|
\draw[sedge] (-0.073,-0.186)--(-0.003,-0.156);
|
||||||
|
\draw[sedge] (-0.073,-0.186)--(0.010,-0.177);
|
||||||
|
\draw[sedge] (-0.073,-0.186)--(0.031,-0.218);
|
||||||
|
\draw[sedge] (0.019,-0.303)--(0.139,-0.245);
|
||||||
|
\draw[sedge] (0.019,-0.303)--(0.031,-0.218);
|
||||||
|
\draw[sedge] (0.139,-0.245)--(0.060,-0.177);
|
||||||
|
\draw[sedge] (0.139,-0.245)--(0.031,-0.218);
|
||||||
|
\draw[sedge] (0.060,-0.177)--(0.035,-0.147);
|
||||||
|
\draw[sedge] (0.060,-0.177)--(0.010,-0.177);
|
||||||
|
\draw[sedge] (0.060,-0.177)--(0.031,-0.218);
|
||||||
|
\draw[sedge] (0.035,-0.147)--(0.024,-0.131);
|
||||||
|
\draw[sedge] (0.035,-0.147)--(-0.003,-0.156);
|
||||||
|
\draw[sedge] (0.035,-0.147)--(0.010,-0.177);
|
||||||
|
\draw[sedge] (0.024,-0.131)--(0.015,-0.113);
|
||||||
|
\draw[sedge] (0.024,-0.131)--(-0.012,-0.141);
|
||||||
|
\draw[sedge] (0.024,-0.131)--(-0.003,-0.156);
|
||||||
|
\draw[sedge] (0.015,-0.113)--(0.002,-0.076);
|
||||||
|
\draw[sedge] (0.015,-0.113)--(-0.023,-0.119);
|
||||||
|
\draw[sedge] (0.015,-0.113)--(-0.012,-0.141);
|
||||||
|
\draw[sedge] (0.002,-0.076)--(-0.048,-0.081);
|
||||||
|
\draw[sedge] (0.002,-0.076)--(-0.023,-0.119);
|
||||||
|
\draw[sedge] (-0.048,-0.081)--(-0.023,-0.119);
|
||||||
|
\draw[sedge] (-0.023,-0.119)--(-0.012,-0.141);
|
||||||
|
\draw[sedge] (-0.012,-0.141)--(-0.003,-0.156);
|
||||||
|
\draw[sedge] (-0.003,-0.156)--(0.010,-0.177);
|
||||||
|
\draw[sedge] (0.010,-0.177)--(0.031,-0.218);
|
||||||
|
\node[sv] at (0.000,0.433) {};
|
||||||
|
\node[sv] at (-0.500,-0.433) {};
|
||||||
|
\node[sv] at (0.500,-0.433) {};
|
||||||
|
\node[sv] at (0.084,-0.096) {};
|
||||||
|
\node[sv] at (-0.018,0.026) {};
|
||||||
|
\node[srcv] at (-0.128,-0.048) {};
|
||||||
|
\node[sv] at (-0.073,-0.186) {};
|
||||||
|
\node[sv] at (0.019,-0.303) {};
|
||||||
|
\node[sv] at (0.139,-0.245) {};
|
||||||
|
\node[sv] at (0.060,-0.177) {};
|
||||||
|
\node[sv] at (0.035,-0.147) {};
|
||||||
|
\node[sv] at (0.024,-0.131) {};
|
||||||
|
\node[sv] at (0.015,-0.113) {};
|
||||||
|
\node[sv] at (0.002,-0.076) {};
|
||||||
|
\node[sv] at (-0.048,-0.081) {};
|
||||||
|
\node[sv] at (-0.023,-0.119) {};
|
||||||
|
\node[sv] at (-0.012,-0.141) {};
|
||||||
|
\node[sv] at (-0.003,-0.156) {};
|
||||||
|
\node[sv] at (0.010,-0.177) {};
|
||||||
|
\node[sv] at (0.031,-0.218) {};
|
||||||
|
\node[font=\scriptsize, text=blue!70!black] at (-0.128,-0.133) {source 5};
|
||||||
|
\end{tikzpicture}
|
||||||
|
\\[-0.25ex]
|
||||||
|
{\scriptsize source graph $G$}
|
||||||
|
\\[1.0ex]
|
||||||
|
\begin{tikzpicture}[scale=7.0,
|
||||||
|
base/.style={black!12, line width=0.25pt},
|
||||||
|
med/.style={black!38, line width=0.32pt},
|
||||||
|
annv/.style={circle, draw=black!70, fill=black!18, inner sep=1.0pt},
|
||||||
|
levone/.style={circle, draw=orange!75!black, fill=orange!20, inner sep=1.2pt},
|
||||||
|
levtwo/.style={circle, draw=violet!70!black, fill=violet!18, inner sep=1.2pt},
|
||||||
|
levthree/.style={circle, draw=teal!70!black, fill=teal!18, inner sep=1.2pt},
|
||||||
|
knownv/.style={circle, draw=red!70!black, fill=red!24, inner sep=1.5pt},
|
||||||
|
elbl/.style={font=\tiny, text=black!70, inner sep=0.2pt},
|
||||||
|
dlbl/.style={font=\tiny\bfseries, text=black, inner sep=0.5pt},
|
||||||
|
cut/.style={red!80!black, line width=1.0pt},
|
||||||
|
cutlbl/.style={font=\tiny, text=red!75!black}]
|
||||||
|
\draw[base] (0.000,0.433)--(-0.500,-0.433);
|
||||||
|
\draw[base] (0.000,0.433)--(0.500,-0.433);
|
||||||
|
\draw[base] (0.000,0.433)--(0.084,-0.096);
|
||||||
|
\draw[base] (0.000,0.433)--(-0.018,0.026);
|
||||||
|
\draw[base] (0.000,0.433)--(-0.128,-0.048);
|
||||||
|
\draw[base] (-0.500,-0.433)--(0.500,-0.433);
|
||||||
|
\draw[base] (-0.500,-0.433)--(-0.128,-0.048);
|
||||||
|
\draw[base] (-0.500,-0.433)--(-0.073,-0.186);
|
||||||
|
\draw[base] (-0.500,-0.433)--(0.019,-0.303);
|
||||||
|
\draw[base] (0.500,-0.433)--(0.084,-0.096);
|
||||||
|
\draw[base] (0.500,-0.433)--(0.019,-0.303);
|
||||||
|
\draw[base] (0.500,-0.433)--(0.139,-0.245);
|
||||||
|
\draw[base] (0.084,-0.096)--(-0.018,0.026);
|
||||||
|
\draw[base] (0.084,-0.096)--(0.139,-0.245);
|
||||||
|
\draw[base] (0.084,-0.096)--(0.060,-0.177);
|
||||||
|
\draw[base] (0.084,-0.096)--(0.035,-0.147);
|
||||||
|
\draw[base] (0.084,-0.096)--(0.024,-0.131);
|
||||||
|
\draw[base] (0.084,-0.096)--(0.015,-0.113);
|
||||||
|
\draw[base] (0.084,-0.096)--(0.002,-0.076);
|
||||||
|
\draw[base] (-0.018,0.026)--(-0.128,-0.048);
|
||||||
|
\draw[base] (-0.018,0.026)--(0.002,-0.076);
|
||||||
|
\draw[base] (-0.018,0.026)--(-0.048,-0.081);
|
||||||
|
\draw[base] (-0.128,-0.048)--(-0.073,-0.186);
|
||||||
|
\draw[base] (-0.128,-0.048)--(-0.048,-0.081);
|
||||||
|
\draw[base] (-0.073,-0.186)--(0.019,-0.303);
|
||||||
|
\draw[base] (-0.073,-0.186)--(-0.048,-0.081);
|
||||||
|
\draw[base] (-0.073,-0.186)--(-0.023,-0.119);
|
||||||
|
\draw[base] (-0.073,-0.186)--(-0.012,-0.141);
|
||||||
|
\draw[base] (-0.073,-0.186)--(-0.003,-0.156);
|
||||||
|
\draw[base] (-0.073,-0.186)--(0.010,-0.177);
|
||||||
|
\draw[base] (-0.073,-0.186)--(0.031,-0.218);
|
||||||
|
\draw[base] (0.019,-0.303)--(0.139,-0.245);
|
||||||
|
\draw[base] (0.019,-0.303)--(0.031,-0.218);
|
||||||
|
\draw[base] (0.139,-0.245)--(0.060,-0.177);
|
||||||
|
\draw[base] (0.139,-0.245)--(0.031,-0.218);
|
||||||
|
\draw[base] (0.060,-0.177)--(0.035,-0.147);
|
||||||
|
\draw[base] (0.060,-0.177)--(0.010,-0.177);
|
||||||
|
\draw[base] (0.060,-0.177)--(0.031,-0.218);
|
||||||
|
\draw[base] (0.035,-0.147)--(0.024,-0.131);
|
||||||
|
\draw[base] (0.035,-0.147)--(-0.003,-0.156);
|
||||||
|
\draw[base] (0.035,-0.147)--(0.010,-0.177);
|
||||||
|
\draw[base] (0.024,-0.131)--(0.015,-0.113);
|
||||||
|
\draw[base] (0.024,-0.131)--(-0.012,-0.141);
|
||||||
|
\draw[base] (0.024,-0.131)--(-0.003,-0.156);
|
||||||
|
\draw[base] (0.015,-0.113)--(0.002,-0.076);
|
||||||
|
\draw[base] (0.015,-0.113)--(-0.023,-0.119);
|
||||||
|
\draw[base] (0.015,-0.113)--(-0.012,-0.141);
|
||||||
|
\draw[base] (0.002,-0.076)--(-0.048,-0.081);
|
||||||
|
\draw[base] (0.002,-0.076)--(-0.023,-0.119);
|
||||||
|
\draw[base] (-0.048,-0.081)--(-0.023,-0.119);
|
||||||
|
\draw[base] (-0.023,-0.119)--(-0.012,-0.141);
|
||||||
|
\draw[base] (-0.012,-0.141)--(-0.003,-0.156);
|
||||||
|
\draw[base] (-0.003,-0.156)--(0.010,-0.177);
|
||||||
|
\draw[base] (0.010,-0.177)--(0.031,-0.218);
|
||||||
|
\draw[med] (-0.250,0.000)--(-0.064,0.192);
|
||||||
|
\draw[med] (-0.250,0.000)--(0.250,0.000);
|
||||||
|
\draw[med] (-0.250,0.000)--(0.000,-0.433);
|
||||||
|
\draw[med] (-0.250,0.000)--(-0.314,-0.241);
|
||||||
|
\draw[med] (0.250,0.000)--(0.042,0.169);
|
||||||
|
\draw[med] (0.250,0.000)--(0.000,-0.433);
|
||||||
|
\draw[med] (0.250,0.000)--(0.292,-0.264);
|
||||||
|
\draw[med] (0.042,0.169)--(-0.009,0.230);
|
||||||
|
\draw[med] (0.042,0.169)--(0.292,-0.264);
|
||||||
|
\draw[med] (0.042,0.169)--(0.033,-0.035);
|
||||||
|
\draw[med] (-0.009,0.230)--(-0.064,0.192);
|
||||||
|
\draw[med] (-0.009,0.230)--(0.033,-0.035);
|
||||||
|
\draw[med] (-0.009,0.230)--(-0.073,-0.011);
|
||||||
|
\draw[med] (-0.064,0.192)--(-0.073,-0.011);
|
||||||
|
\draw[med] (-0.064,0.192)--(-0.314,-0.241);
|
||||||
|
\draw[med] (0.000,-0.433)--(-0.240,-0.368);
|
||||||
|
\draw[med] (0.000,-0.433)--(0.260,-0.368);
|
||||||
|
\draw[med] (-0.314,-0.241)--(-0.286,-0.310);
|
||||||
|
\draw[med] (-0.314,-0.241)--(-0.100,-0.117);
|
||||||
|
\draw[med] (-0.286,-0.310)--(-0.240,-0.368);
|
||||||
|
\draw[med] (-0.286,-0.310)--(-0.100,-0.117);
|
||||||
|
\draw[med] (-0.286,-0.310)--(-0.027,-0.245);
|
||||||
|
\draw[med] (-0.240,-0.368)--(-0.027,-0.245);
|
||||||
|
\draw[med] (-0.240,-0.368)--(0.260,-0.368);
|
||||||
|
\draw[med] (0.292,-0.264)--(0.319,-0.339);
|
||||||
|
\draw[med] (0.292,-0.264)--(0.111,-0.171);
|
||||||
|
\draw[med] (0.260,-0.368)--(0.319,-0.339);
|
||||||
|
\draw[med] (0.260,-0.368)--(0.079,-0.274);
|
||||||
|
\draw[med] (0.319,-0.339)--(0.079,-0.274);
|
||||||
|
\draw[med] (0.319,-0.339)--(0.111,-0.171);
|
||||||
|
\draw[med] (0.033,-0.035)--(0.043,-0.086);
|
||||||
|
\draw[med] (0.033,-0.035)--(-0.008,-0.025);
|
||||||
|
\draw[med] (0.111,-0.171)--(0.072,-0.136);
|
||||||
|
\draw[med] (0.111,-0.171)--(0.099,-0.211);
|
||||||
|
\draw[med] (0.072,-0.136)--(0.059,-0.122);
|
||||||
|
\draw[med] (0.072,-0.136)--(0.099,-0.211);
|
||||||
|
\draw[med] (0.072,-0.136)--(0.047,-0.162);
|
||||||
|
\draw[med] (0.059,-0.122)--(0.054,-0.113);
|
||||||
|
\draw[med] (0.059,-0.122)--(0.047,-0.162);
|
||||||
|
\draw[med] (0.059,-0.122)--(0.029,-0.139);
|
||||||
|
\draw[med] (0.054,-0.113)--(0.049,-0.104);
|
||||||
|
\draw[med] (0.054,-0.113)--(0.029,-0.139);
|
||||||
|
\draw[med] (0.054,-0.113)--(0.019,-0.122);
|
||||||
|
\draw[med] (0.049,-0.104)--(0.043,-0.086);
|
||||||
|
\draw[med] (0.049,-0.104)--(0.019,-0.122);
|
||||||
|
\draw[med] (0.049,-0.104)--(0.008,-0.095);
|
||||||
|
\draw[med] (0.043,-0.086)--(0.008,-0.095);
|
||||||
|
\draw[med] (0.043,-0.086)--(-0.008,-0.025);
|
||||||
|
\draw[med] (-0.073,-0.011)--(-0.033,-0.027);
|
||||||
|
\draw[med] (-0.073,-0.011)--(-0.088,-0.064);
|
||||||
|
\draw[med] (-0.008,-0.025)--(-0.033,-0.027);
|
||||||
|
\draw[med] (-0.008,-0.025)--(-0.023,-0.079);
|
||||||
|
\draw[med] (-0.033,-0.027)--(-0.023,-0.079);
|
||||||
|
\draw[med] (-0.033,-0.027)--(-0.088,-0.064);
|
||||||
|
\draw[med] (-0.100,-0.117)--(-0.088,-0.064);
|
||||||
|
\draw[med] (-0.100,-0.117)--(-0.060,-0.134);
|
||||||
|
\draw[med] (-0.088,-0.064)--(-0.060,-0.134);
|
||||||
|
\draw[med] (-0.027,-0.245)--(-0.021,-0.202);
|
||||||
|
\draw[med] (-0.027,-0.245)--(0.025,-0.260);
|
||||||
|
\draw[med] (-0.060,-0.134)--(-0.048,-0.153);
|
||||||
|
\draw[med] (-0.060,-0.134)--(-0.035,-0.100);
|
||||||
|
\draw[med] (-0.048,-0.153)--(-0.042,-0.164);
|
||||||
|
\draw[med] (-0.048,-0.153)--(-0.035,-0.100);
|
||||||
|
\draw[med] (-0.048,-0.153)--(-0.018,-0.130);
|
||||||
|
\draw[med] (-0.042,-0.164)--(-0.038,-0.171);
|
||||||
|
\draw[med] (-0.042,-0.164)--(-0.018,-0.130);
|
||||||
|
\draw[med] (-0.042,-0.164)--(-0.008,-0.149);
|
||||||
|
\draw[med] (-0.038,-0.171)--(-0.031,-0.182);
|
||||||
|
\draw[med] (-0.038,-0.171)--(-0.008,-0.149);
|
||||||
|
\draw[med] (-0.038,-0.171)--(0.003,-0.167);
|
||||||
|
\draw[med] (-0.031,-0.182)--(-0.021,-0.202);
|
||||||
|
\draw[med] (-0.031,-0.182)--(0.003,-0.167);
|
||||||
|
\draw[med] (-0.031,-0.182)--(0.021,-0.197);
|
||||||
|
\draw[med] (-0.021,-0.202)--(0.021,-0.197);
|
||||||
|
\draw[med] (-0.021,-0.202)--(0.025,-0.260);
|
||||||
|
\draw[med] (0.079,-0.274)--(0.025,-0.260);
|
||||||
|
\draw[med] (0.079,-0.274)--(0.085,-0.231);
|
||||||
|
\draw[med] (0.025,-0.260)--(0.085,-0.231);
|
||||||
|
\draw[med] (0.099,-0.211)--(0.085,-0.231);
|
||||||
|
\draw[med] (0.099,-0.211)--(0.045,-0.197);
|
||||||
|
\draw[med] (0.085,-0.231)--(0.045,-0.197);
|
||||||
|
\draw[med] (0.047,-0.162)--(0.035,-0.177);
|
||||||
|
\draw[med] (0.047,-0.162)--(0.022,-0.162);
|
||||||
|
\draw[med] (0.035,-0.177)--(0.045,-0.197);
|
||||||
|
\draw[med] (0.035,-0.177)--(0.021,-0.197);
|
||||||
|
\draw[med] (0.035,-0.177)--(0.022,-0.162);
|
||||||
|
\draw[med] (0.045,-0.197)--(0.021,-0.197);
|
||||||
|
\draw[med] (0.029,-0.139)--(0.016,-0.152);
|
||||||
|
\draw[med] (0.029,-0.139)--(0.010,-0.144);
|
||||||
|
\draw[med] (0.016,-0.152)--(0.022,-0.162);
|
||||||
|
\draw[med] (0.016,-0.152)--(0.003,-0.167);
|
||||||
|
\draw[med] (0.016,-0.152)--(0.010,-0.144);
|
||||||
|
\draw[med] (0.022,-0.162)--(0.003,-0.167);
|
||||||
|
\draw[med] (0.019,-0.122)--(0.006,-0.136);
|
||||||
|
\draw[med] (0.019,-0.122)--(0.001,-0.127);
|
||||||
|
\draw[med] (0.006,-0.136)--(0.010,-0.144);
|
||||||
|
\draw[med] (0.006,-0.136)--(-0.008,-0.149);
|
||||||
|
\draw[med] (0.006,-0.136)--(0.001,-0.127);
|
||||||
|
\draw[med] (0.010,-0.144)--(-0.008,-0.149);
|
||||||
|
\draw[med] (0.008,-0.095)--(-0.004,-0.116);
|
||||||
|
\draw[med] (0.008,-0.095)--(-0.011,-0.098);
|
||||||
|
\draw[med] (-0.004,-0.116)--(0.001,-0.127);
|
||||||
|
\draw[med] (-0.004,-0.116)--(-0.018,-0.130);
|
||||||
|
\draw[med] (-0.004,-0.116)--(-0.011,-0.098);
|
||||||
|
\draw[med] (0.001,-0.127)--(-0.018,-0.130);
|
||||||
|
\draw[med] (-0.023,-0.079)--(-0.011,-0.098);
|
||||||
|
\draw[med] (-0.023,-0.079)--(-0.035,-0.100);
|
||||||
|
\draw[med] (-0.011,-0.098)--(-0.035,-0.100);
|
||||||
|
\node[knownv] at (-0.250,0.000) {};
|
||||||
|
\node[annv] at (0.250,0.000) {};
|
||||||
|
\node[annv] at (0.042,0.169) {};
|
||||||
|
\node[knownv] at (-0.009,0.230) {};
|
||||||
|
\node[annv] at (-0.064,0.192) {};
|
||||||
|
\node[annv] at (0.000,-0.433) {};
|
||||||
|
\node[annv] at (-0.314,-0.241) {};
|
||||||
|
\node[knownv] at (-0.286,-0.310) {};
|
||||||
|
\node[annv] at (-0.240,-0.368) {};
|
||||||
|
\node[knownv] at (0.292,-0.264) {};
|
||||||
|
\node[knownv] at (0.260,-0.368) {};
|
||||||
|
\node[annv] at (0.319,-0.339) {};
|
||||||
|
\node[annv] at (0.033,-0.035) {};
|
||||||
|
\node[annv] at (0.111,-0.171) {};
|
||||||
|
\node[annv] at (0.072,-0.136) {};
|
||||||
|
\node[annv] at (-0.073,-0.011) {};
|
||||||
|
\node[annv] at (-0.100,-0.117) {};
|
||||||
|
\node[annv] at (-0.027,-0.245) {};
|
||||||
|
\node[annv] at (0.079,-0.274) {};
|
||||||
|
\node[knownv] at (0.099,-0.211) {};
|
||||||
|
\node[annv] at (0.059,-0.122) {};
|
||||||
|
\node[knownv] at (0.047,-0.162) {};
|
||||||
|
\node[knownv] at (0.029,-0.139) {};
|
||||||
|
\node[annv] at (0.016,-0.152) {};
|
||||||
|
\node[annv] at (0.022,-0.162) {};
|
||||||
|
\node[annv] at (0.054,-0.113) {};
|
||||||
|
\node[knownv] at (0.019,-0.122) {};
|
||||||
|
\node[annv] at (0.006,-0.136) {};
|
||||||
|
\node[annv] at (0.010,-0.144) {};
|
||||||
|
\node[annv] at (0.049,-0.104) {};
|
||||||
|
\node[annv] at (0.008,-0.095) {};
|
||||||
|
\node[annv] at (-0.004,-0.116) {};
|
||||||
|
\node[annv] at (0.001,-0.127) {};
|
||||||
|
\node[knownv] at (0.043,-0.086) {};
|
||||||
|
\node[annv] at (-0.008,-0.025) {};
|
||||||
|
\node[annv] at (-0.023,-0.079) {};
|
||||||
|
\node[knownv] at (-0.011,-0.098) {};
|
||||||
|
\node[knownv] at (-0.033,-0.027) {};
|
||||||
|
\node[annv] at (-0.088,-0.064) {};
|
||||||
|
\node[knownv] at (-0.060,-0.134) {};
|
||||||
|
\node[annv] at (-0.035,-0.100) {};
|
||||||
|
\node[annv] at (-0.048,-0.153) {};
|
||||||
|
\node[knownv] at (-0.018,-0.130) {};
|
||||||
|
\node[annv] at (-0.042,-0.164) {};
|
||||||
|
\node[knownv] at (-0.008,-0.149) {};
|
||||||
|
\node[annv] at (-0.038,-0.171) {};
|
||||||
|
\node[knownv] at (0.003,-0.167) {};
|
||||||
|
\node[annv] at (-0.031,-0.182) {};
|
||||||
|
\node[annv] at (0.035,-0.177) {};
|
||||||
|
\node[knownv] at (0.021,-0.197) {};
|
||||||
|
\node[annv] at (-0.021,-0.202) {};
|
||||||
|
\node[knownv] at (0.025,-0.260) {};
|
||||||
|
\node[annv] at (0.085,-0.231) {};
|
||||||
|
\node[annv] at (0.045,-0.197) {};
|
||||||
|
\node[elbl] at (-0.250,0.000) [yshift=-4.8pt] {$0\!{-}\!1$};
|
||||||
|
\node[elbl] at (0.250,0.000) [yshift=-4.8pt] {$0\!{-}\!2$};
|
||||||
|
\node[elbl] at (0.042,0.169) [yshift=-4.8pt] {$0\!{-}\!3$};
|
||||||
|
\node[elbl] at (-0.009,0.230) [yshift=-4.8pt] {$0\!{-}\!4$};
|
||||||
|
\node[elbl] at (-0.064,0.192) [yshift=-4.8pt] {$0\!{-}\!5$};
|
||||||
|
\node[elbl] at (0.000,-0.433) [yshift=-4.8pt] {$1\!{-}\!2$};
|
||||||
|
\node[elbl] at (-0.314,-0.241) [yshift=-4.8pt] {$1\!{-}\!5$};
|
||||||
|
\node[elbl] at (-0.286,-0.310) [yshift=-4.8pt] {$1\!{-}\!6$};
|
||||||
|
\node[elbl] at (-0.240,-0.368) [yshift=-4.8pt] {$1\!{-}\!7$};
|
||||||
|
\node[elbl] at (0.292,-0.264) [yshift=-4.8pt] {$2\!{-}\!3$};
|
||||||
|
\node[elbl] at (0.260,-0.368) [yshift=-4.8pt] {$2\!{-}\!7$};
|
||||||
|
\node[elbl] at (0.319,-0.339) [yshift=-4.8pt] {$2\!{-}\!8$};
|
||||||
|
\node[elbl] at (0.033,-0.035) [yshift=-4.8pt] {$3\!{-}\!4$};
|
||||||
|
\node[elbl] at (0.111,-0.171) [yshift=-4.8pt] {$3\!{-}\!8$};
|
||||||
|
\node[elbl] at (0.072,-0.136) [yshift=-4.8pt] {$3\!{-}\!9$};
|
||||||
|
\node[elbl] at (-0.073,-0.011) [yshift=-4.8pt] {$4\!{-}\!5$};
|
||||||
|
\node[elbl] at (-0.100,-0.117) [yshift=-4.8pt] {$5\!{-}\!6$};
|
||||||
|
\node[elbl] at (-0.027,-0.245) [yshift=-4.8pt] {$6\!{-}\!7$};
|
||||||
|
\node[elbl] at (0.079,-0.274) [yshift=-4.8pt] {$7\!{-}\!8$};
|
||||||
|
\node[elbl] at (0.099,-0.211) [yshift=-4.8pt] {$8\!{-}\!9$};
|
||||||
|
\node[elbl] at (0.059,-0.122) [yshift=-4.8pt] {$10\!{-}\!3$};
|
||||||
|
\node[elbl] at (0.047,-0.162) [yshift=-4.8pt] {$10\!{-}\!9$};
|
||||||
|
\node[elbl] at (0.029,-0.139) [yshift=-4.8pt] {$10\!{-}\!11$};
|
||||||
|
\node[elbl] at (0.016,-0.152) [yshift=-4.8pt] {$10\!{-}\!17$};
|
||||||
|
\node[elbl] at (0.022,-0.162) [yshift=-4.8pt] {$10\!{-}\!18$};
|
||||||
|
\node[elbl] at (0.054,-0.113) [yshift=-4.8pt] {$11\!{-}\!3$};
|
||||||
|
\node[elbl] at (0.019,-0.122) [yshift=-4.8pt] {$11\!{-}\!12$};
|
||||||
|
\node[elbl] at (0.006,-0.136) [yshift=-4.8pt] {$11\!{-}\!16$};
|
||||||
|
\node[elbl] at (0.010,-0.144) [yshift=-4.8pt] {$11\!{-}\!17$};
|
||||||
|
\node[elbl] at (0.049,-0.104) [yshift=-4.8pt] {$12\!{-}\!3$};
|
||||||
|
\node[elbl] at (0.008,-0.095) [yshift=-4.8pt] {$12\!{-}\!13$};
|
||||||
|
\node[elbl] at (-0.004,-0.116) [yshift=-4.8pt] {$12\!{-}\!15$};
|
||||||
|
\node[elbl] at (0.001,-0.127) [yshift=-4.8pt] {$12\!{-}\!16$};
|
||||||
|
\node[elbl] at (0.043,-0.086) [yshift=-4.8pt] {$13\!{-}\!3$};
|
||||||
|
\node[elbl] at (-0.008,-0.025) [yshift=-4.8pt] {$13\!{-}\!4$};
|
||||||
|
\node[elbl] at (-0.023,-0.079) [yshift=-4.8pt] {$13\!{-}\!14$};
|
||||||
|
\node[elbl] at (-0.011,-0.098) [yshift=-4.8pt] {$13\!{-}\!15$};
|
||||||
|
\node[elbl] at (-0.033,-0.027) [yshift=-4.8pt] {$14\!{-}\!4$};
|
||||||
|
\node[elbl] at (-0.088,-0.064) [yshift=-4.8pt] {$14\!{-}\!5$};
|
||||||
|
\node[elbl] at (-0.060,-0.134) [yshift=-4.8pt] {$14\!{-}\!6$};
|
||||||
|
\node[elbl] at (-0.035,-0.100) [yshift=-4.8pt] {$14\!{-}\!15$};
|
||||||
|
\node[elbl] at (-0.048,-0.153) [yshift=-4.8pt] {$15\!{-}\!6$};
|
||||||
|
\node[elbl] at (-0.018,-0.130) [yshift=-4.8pt] {$15\!{-}\!16$};
|
||||||
|
\node[elbl] at (-0.042,-0.164) [yshift=-4.8pt] {$16\!{-}\!6$};
|
||||||
|
\node[elbl] at (-0.008,-0.149) [yshift=-4.8pt] {$16\!{-}\!17$};
|
||||||
|
\node[elbl] at (-0.038,-0.171) [yshift=-4.8pt] {$17\!{-}\!6$};
|
||||||
|
\node[elbl] at (0.003,-0.167) [yshift=-4.8pt] {$17\!{-}\!18$};
|
||||||
|
\node[elbl] at (-0.031,-0.182) [yshift=-4.8pt] {$18\!{-}\!6$};
|
||||||
|
\node[elbl] at (0.035,-0.177) [yshift=-4.8pt] {$18\!{-}\!9$};
|
||||||
|
\node[elbl] at (0.021,-0.197) [yshift=-4.8pt] {$18\!{-}\!19$};
|
||||||
|
\node[elbl] at (-0.021,-0.202) [yshift=-4.8pt] {$19\!{-}\!6$};
|
||||||
|
\node[elbl] at (0.025,-0.260) [yshift=-4.8pt] {$19\!{-}\!7$};
|
||||||
|
\node[elbl] at (0.085,-0.231) [yshift=-4.8pt] {$19\!{-}\!8$};
|
||||||
|
\node[elbl] at (0.045,-0.197) [yshift=-4.8pt] {$19\!{-}\!9$};
|
||||||
|
\node[dlbl] at (-0.250,0.000) [yshift=5.0pt] {4};
|
||||||
|
\node[dlbl] at (-0.009,0.230) [yshift=5.0pt] {2};
|
||||||
|
\node[dlbl] at (-0.286,-0.310) [yshift=5.0pt] {6};
|
||||||
|
\node[dlbl] at (0.292,-0.264) [yshift=5.0pt] {3,18};
|
||||||
|
\node[dlbl] at (0.260,-0.368) [yshift=5.0pt] {5,17};
|
||||||
|
\node[dlbl] at (0.099,-0.211) [yshift=5.0pt] {13,14};
|
||||||
|
\node[dlbl] at (0.047,-0.162) [yshift=5.0pt] {11,12};
|
||||||
|
\node[dlbl] at (0.029,-0.139) [yshift=5.0pt] {7,8};
|
||||||
|
\node[dlbl] at (0.019,-0.122) [yshift=5.0pt] {5,6};
|
||||||
|
\node[dlbl] at (0.043,-0.086) [yshift=5.0pt] {1,2};
|
||||||
|
\node[dlbl] at (-0.011,-0.098) [yshift=5.0pt] {3,13};
|
||||||
|
\node[dlbl] at (-0.033,-0.027) [yshift=5.0pt] {0};
|
||||||
|
\node[dlbl] at (-0.060,-0.134) [yshift=5.0pt] {12};
|
||||||
|
\node[dlbl] at (-0.018,-0.130) [yshift=5.0pt] {4,11};
|
||||||
|
\node[dlbl] at (-0.008,-0.149) [yshift=5.0pt] {9,10};
|
||||||
|
\node[dlbl] at (0.003,-0.167) [yshift=5.0pt] {9,10};
|
||||||
|
\node[dlbl] at (0.021,-0.197) [yshift=5.0pt] {8,15};
|
||||||
|
\node[dlbl] at (0.025,-0.260) [yshift=5.0pt] {7,16};
|
||||||
|
\draw[cut] (-0.075,-0.032)--(-0.101,-0.097);
|
||||||
|
\node[cutlbl] at (-0.120,-0.142) {cut 1};
|
||||||
|
\draw[cut] (-0.026,-0.044)--(-0.020,-0.114);
|
||||||
|
\node[cutlbl] at (-0.016,-0.162) {cut 2};
|
||||||
|
\draw[cut] (0.058,-0.138)--(0.041,-0.070);
|
||||||
|
\node[cutlbl] at (0.030,-0.023) {cut 3};
|
||||||
|
\draw[cut] (-0.004,-0.102)--(0.016,-0.169);
|
||||||
|
\node[cutlbl] at (0.029,-0.217) {cut 4};
|
||||||
|
\draw[cut] (0.085,-0.146)--(0.034,-0.097);
|
||||||
|
\node[cutlbl] at (-0.001,-0.063) {cut 5};
|
||||||
|
\draw[cut] (0.035,-0.212)--(0.035,-0.142);
|
||||||
|
\node[cutlbl] at (0.034,-0.093) {cut 6};
|
||||||
|
\draw[cut] (0.079,-0.183)--(0.144,-0.158);
|
||||||
|
\node[cutlbl] at (0.190,-0.142) {cut 7};
|
||||||
|
\end{tikzpicture}
|
||||||
|
\\[-0.25ex]
|
||||||
|
{\scriptsize medial graph $M(G)$ at edge midpoints}
|
||||||
|
\end{tabular}
|
||||||
@@ -0,0 +1,382 @@
|
|||||||
|
% whole medial graph: n=20 seed=72 source=9 recognised treads=[2] |M(G)|=54
|
||||||
|
\begin{tabular}{c}
|
||||||
|
\begin{tikzpicture}[scale=4.06,
|
||||||
|
sedge/.style={black!50, line width=0.35pt},
|
||||||
|
sv/.style={circle, draw=black!60, fill=white, inner sep=1.1pt},
|
||||||
|
srcv/.style={circle, draw=blue!75!black, fill=blue!18, line width=0.7pt, inner sep=1.8pt}]
|
||||||
|
\draw[sedge] (0.000,0.433)--(-0.500,-0.433);
|
||||||
|
\draw[sedge] (0.000,0.433)--(0.500,-0.433);
|
||||||
|
\draw[sedge] (0.000,0.433)--(0.027,-0.257);
|
||||||
|
\draw[sedge] (0.000,0.433)--(0.199,-0.203);
|
||||||
|
\draw[sedge] (0.000,0.433)--(-0.158,-0.086);
|
||||||
|
\draw[sedge] (-0.500,-0.433)--(0.500,-0.433);
|
||||||
|
\draw[sedge] (-0.500,-0.433)--(0.027,-0.257);
|
||||||
|
\draw[sedge] (-0.500,-0.433)--(0.012,-0.355);
|
||||||
|
\draw[sedge] (-0.500,-0.433)--(-0.170,-0.348);
|
||||||
|
\draw[sedge] (-0.500,-0.433)--(-0.218,-0.346);
|
||||||
|
\draw[sedge] (-0.500,-0.433)--(-0.158,-0.086);
|
||||||
|
\draw[sedge] (-0.500,-0.433)--(-0.230,-0.345);
|
||||||
|
\draw[sedge] (0.500,-0.433)--(0.027,-0.257);
|
||||||
|
\draw[sedge] (0.500,-0.433)--(0.199,-0.203);
|
||||||
|
\draw[sedge] (0.500,-0.433)--(0.012,-0.355);
|
||||||
|
\draw[sedge] (0.500,-0.433)--(0.234,-0.280);
|
||||||
|
\draw[sedge] (0.500,-0.433)--(0.292,-0.291);
|
||||||
|
\draw[sedge] (0.500,-0.433)--(0.151,-0.341);
|
||||||
|
\draw[sedge] (0.500,-0.433)--(0.254,-0.323);
|
||||||
|
\draw[sedge] (0.027,-0.257)--(0.199,-0.203);
|
||||||
|
\draw[sedge] (0.027,-0.257)--(0.012,-0.355);
|
||||||
|
\draw[sedge] (0.027,-0.257)--(0.234,-0.280);
|
||||||
|
\draw[sedge] (0.027,-0.257)--(-0.170,-0.348);
|
||||||
|
\draw[sedge] (0.027,-0.257)--(0.151,-0.341);
|
||||||
|
\draw[sedge] (0.027,-0.257)--(0.254,-0.323);
|
||||||
|
\draw[sedge] (0.027,-0.257)--(-0.218,-0.346);
|
||||||
|
\draw[sedge] (0.027,-0.257)--(0.063,-0.317);
|
||||||
|
\draw[sedge] (0.027,-0.257)--(0.146,-0.243);
|
||||||
|
\draw[sedge] (0.027,-0.257)--(-0.158,-0.086);
|
||||||
|
\draw[sedge] (0.027,-0.257)--(0.124,-0.234);
|
||||||
|
\draw[sedge] (0.027,-0.257)--(-0.230,-0.345);
|
||||||
|
\draw[sedge] (0.199,-0.203)--(0.234,-0.280);
|
||||||
|
\draw[sedge] (0.199,-0.203)--(0.292,-0.291);
|
||||||
|
\draw[sedge] (0.199,-0.203)--(0.233,-0.249);
|
||||||
|
\draw[sedge] (0.199,-0.203)--(0.221,-0.241);
|
||||||
|
\draw[sedge] (0.199,-0.203)--(0.146,-0.243);
|
||||||
|
\draw[sedge] (0.199,-0.203)--(0.218,-0.231);
|
||||||
|
\draw[sedge] (0.199,-0.203)--(0.124,-0.234);
|
||||||
|
\draw[sedge] (0.012,-0.355)--(-0.170,-0.348);
|
||||||
|
\draw[sedge] (0.012,-0.355)--(0.151,-0.341);
|
||||||
|
\draw[sedge] (0.012,-0.355)--(0.063,-0.317);
|
||||||
|
\draw[sedge] (0.234,-0.280)--(0.292,-0.291);
|
||||||
|
\draw[sedge] (0.234,-0.280)--(0.233,-0.249);
|
||||||
|
\draw[sedge] (0.234,-0.280)--(0.254,-0.323);
|
||||||
|
\draw[sedge] (0.234,-0.280)--(0.221,-0.241);
|
||||||
|
\draw[sedge] (0.234,-0.280)--(0.146,-0.243);
|
||||||
|
\draw[sedge] (0.292,-0.291)--(0.233,-0.249);
|
||||||
|
\draw[sedge] (0.233,-0.249)--(0.221,-0.241);
|
||||||
|
\draw[sedge] (0.233,-0.249)--(0.218,-0.231);
|
||||||
|
\draw[sedge] (-0.170,-0.348)--(-0.218,-0.346);
|
||||||
|
\draw[sedge] (0.151,-0.341)--(0.063,-0.317);
|
||||||
|
\draw[sedge] (-0.218,-0.346)--(-0.230,-0.345);
|
||||||
|
\draw[sedge] (0.221,-0.241)--(0.218,-0.231);
|
||||||
|
\draw[sedge] (0.146,-0.243)--(0.124,-0.234);
|
||||||
|
\node[sv] at (0.000,0.433) {};
|
||||||
|
\node[sv] at (-0.500,-0.433) {};
|
||||||
|
\node[sv] at (0.500,-0.433) {};
|
||||||
|
\node[sv] at (0.027,-0.257) {};
|
||||||
|
\node[sv] at (0.199,-0.203) {};
|
||||||
|
\node[sv] at (0.012,-0.355) {};
|
||||||
|
\node[sv] at (0.234,-0.280) {};
|
||||||
|
\node[sv] at (0.292,-0.291) {};
|
||||||
|
\node[sv] at (0.233,-0.249) {};
|
||||||
|
\node[srcv] at (-0.170,-0.348) {};
|
||||||
|
\node[sv] at (0.151,-0.341) {};
|
||||||
|
\node[sv] at (0.254,-0.323) {};
|
||||||
|
\node[sv] at (-0.218,-0.346) {};
|
||||||
|
\node[sv] at (0.221,-0.241) {};
|
||||||
|
\node[sv] at (0.063,-0.317) {};
|
||||||
|
\node[sv] at (0.146,-0.243) {};
|
||||||
|
\node[sv] at (0.218,-0.231) {};
|
||||||
|
\node[sv] at (-0.158,-0.086) {};
|
||||||
|
\node[sv] at (0.124,-0.234) {};
|
||||||
|
\node[sv] at (-0.230,-0.345) {};
|
||||||
|
\node[font=\scriptsize, text=blue!70!black] at (-0.170,-0.433) {source 9};
|
||||||
|
\end{tikzpicture}
|
||||||
|
\\[-0.25ex]
|
||||||
|
{\scriptsize source graph $G$}
|
||||||
|
\\[1.0ex]
|
||||||
|
\begin{tikzpicture}[scale=7.0,
|
||||||
|
base/.style={black!12, line width=0.25pt},
|
||||||
|
med/.style={black!38, line width=0.32pt},
|
||||||
|
annv/.style={circle, draw=black!70, fill=black!18, inner sep=1.0pt},
|
||||||
|
levone/.style={circle, draw=orange!75!black, fill=orange!20, inner sep=1.2pt},
|
||||||
|
levtwo/.style={circle, draw=violet!70!black, fill=violet!18, inner sep=1.2pt},
|
||||||
|
levthree/.style={circle, draw=teal!70!black, fill=teal!18, inner sep=1.2pt},
|
||||||
|
knownv/.style={circle, draw=red!70!black, fill=red!24, inner sep=1.5pt},
|
||||||
|
elbl/.style={font=\tiny, text=black!70, inner sep=0.2pt},
|
||||||
|
dlbl/.style={font=\tiny\bfseries, text=black, inner sep=0.5pt},
|
||||||
|
cut/.style={red!80!black, line width=1.0pt},
|
||||||
|
cutlbl/.style={font=\tiny, text=red!75!black}]
|
||||||
|
\draw[base] (0.000,0.433)--(-0.500,-0.433);
|
||||||
|
\draw[base] (0.000,0.433)--(0.500,-0.433);
|
||||||
|
\draw[base] (0.000,0.433)--(0.027,-0.257);
|
||||||
|
\draw[base] (0.000,0.433)--(0.199,-0.203);
|
||||||
|
\draw[base] (0.000,0.433)--(-0.158,-0.086);
|
||||||
|
\draw[base] (-0.500,-0.433)--(0.500,-0.433);
|
||||||
|
\draw[base] (-0.500,-0.433)--(0.027,-0.257);
|
||||||
|
\draw[base] (-0.500,-0.433)--(0.012,-0.355);
|
||||||
|
\draw[base] (-0.500,-0.433)--(-0.170,-0.348);
|
||||||
|
\draw[base] (-0.500,-0.433)--(-0.218,-0.346);
|
||||||
|
\draw[base] (-0.500,-0.433)--(-0.158,-0.086);
|
||||||
|
\draw[base] (-0.500,-0.433)--(-0.230,-0.345);
|
||||||
|
\draw[base] (0.500,-0.433)--(0.027,-0.257);
|
||||||
|
\draw[base] (0.500,-0.433)--(0.199,-0.203);
|
||||||
|
\draw[base] (0.500,-0.433)--(0.012,-0.355);
|
||||||
|
\draw[base] (0.500,-0.433)--(0.234,-0.280);
|
||||||
|
\draw[base] (0.500,-0.433)--(0.292,-0.291);
|
||||||
|
\draw[base] (0.500,-0.433)--(0.151,-0.341);
|
||||||
|
\draw[base] (0.500,-0.433)--(0.254,-0.323);
|
||||||
|
\draw[base] (0.027,-0.257)--(0.199,-0.203);
|
||||||
|
\draw[base] (0.027,-0.257)--(0.012,-0.355);
|
||||||
|
\draw[base] (0.027,-0.257)--(0.234,-0.280);
|
||||||
|
\draw[base] (0.027,-0.257)--(-0.170,-0.348);
|
||||||
|
\draw[base] (0.027,-0.257)--(0.151,-0.341);
|
||||||
|
\draw[base] (0.027,-0.257)--(0.254,-0.323);
|
||||||
|
\draw[base] (0.027,-0.257)--(-0.218,-0.346);
|
||||||
|
\draw[base] (0.027,-0.257)--(0.063,-0.317);
|
||||||
|
\draw[base] (0.027,-0.257)--(0.146,-0.243);
|
||||||
|
\draw[base] (0.027,-0.257)--(-0.158,-0.086);
|
||||||
|
\draw[base] (0.027,-0.257)--(0.124,-0.234);
|
||||||
|
\draw[base] (0.027,-0.257)--(-0.230,-0.345);
|
||||||
|
\draw[base] (0.199,-0.203)--(0.234,-0.280);
|
||||||
|
\draw[base] (0.199,-0.203)--(0.292,-0.291);
|
||||||
|
\draw[base] (0.199,-0.203)--(0.233,-0.249);
|
||||||
|
\draw[base] (0.199,-0.203)--(0.221,-0.241);
|
||||||
|
\draw[base] (0.199,-0.203)--(0.146,-0.243);
|
||||||
|
\draw[base] (0.199,-0.203)--(0.218,-0.231);
|
||||||
|
\draw[base] (0.199,-0.203)--(0.124,-0.234);
|
||||||
|
\draw[base] (0.012,-0.355)--(-0.170,-0.348);
|
||||||
|
\draw[base] (0.012,-0.355)--(0.151,-0.341);
|
||||||
|
\draw[base] (0.012,-0.355)--(0.063,-0.317);
|
||||||
|
\draw[base] (0.234,-0.280)--(0.292,-0.291);
|
||||||
|
\draw[base] (0.234,-0.280)--(0.233,-0.249);
|
||||||
|
\draw[base] (0.234,-0.280)--(0.254,-0.323);
|
||||||
|
\draw[base] (0.234,-0.280)--(0.221,-0.241);
|
||||||
|
\draw[base] (0.234,-0.280)--(0.146,-0.243);
|
||||||
|
\draw[base] (0.292,-0.291)--(0.233,-0.249);
|
||||||
|
\draw[base] (0.233,-0.249)--(0.221,-0.241);
|
||||||
|
\draw[base] (0.233,-0.249)--(0.218,-0.231);
|
||||||
|
\draw[base] (-0.170,-0.348)--(-0.218,-0.346);
|
||||||
|
\draw[base] (0.151,-0.341)--(0.063,-0.317);
|
||||||
|
\draw[base] (-0.218,-0.346)--(-0.230,-0.345);
|
||||||
|
\draw[base] (0.221,-0.241)--(0.218,-0.231);
|
||||||
|
\draw[base] (0.146,-0.243)--(0.124,-0.234);
|
||||||
|
\draw[med] (-0.250,0.000)--(-0.079,0.174);
|
||||||
|
\draw[med] (-0.250,0.000)--(0.250,0.000);
|
||||||
|
\draw[med] (-0.250,0.000)--(0.000,-0.433);
|
||||||
|
\draw[med] (-0.250,0.000)--(-0.329,-0.259);
|
||||||
|
\draw[med] (0.250,0.000)--(0.100,0.115);
|
||||||
|
\draw[med] (0.250,0.000)--(0.000,-0.433);
|
||||||
|
\draw[med] (0.250,0.000)--(0.350,-0.318);
|
||||||
|
\draw[med] (0.014,0.088)--(-0.079,0.174);
|
||||||
|
\draw[med] (0.014,0.088)--(0.100,0.115);
|
||||||
|
\draw[med] (0.014,0.088)--(0.113,-0.230);
|
||||||
|
\draw[med] (0.014,0.088)--(-0.065,-0.171);
|
||||||
|
\draw[med] (0.100,0.115)--(0.350,-0.318);
|
||||||
|
\draw[med] (0.100,0.115)--(0.113,-0.230);
|
||||||
|
\draw[med] (-0.079,0.174)--(-0.065,-0.171);
|
||||||
|
\draw[med] (-0.079,0.174)--(-0.329,-0.259);
|
||||||
|
\draw[med] (0.000,-0.433)--(-0.244,-0.394);
|
||||||
|
\draw[med] (0.000,-0.433)--(0.256,-0.394);
|
||||||
|
\draw[med] (-0.236,-0.345)--(-0.365,-0.389);
|
||||||
|
\draw[med] (-0.236,-0.345)--(-0.329,-0.259);
|
||||||
|
\draw[med] (-0.236,-0.345)--(-0.065,-0.171);
|
||||||
|
\draw[med] (-0.236,-0.345)--(-0.102,-0.301);
|
||||||
|
\draw[med] (-0.244,-0.394)--(-0.335,-0.390);
|
||||||
|
\draw[med] (-0.244,-0.394)--(-0.079,-0.351);
|
||||||
|
\draw[med] (-0.244,-0.394)--(0.256,-0.394);
|
||||||
|
\draw[med] (-0.335,-0.390)--(-0.359,-0.389);
|
||||||
|
\draw[med] (-0.335,-0.390)--(-0.194,-0.347);
|
||||||
|
\draw[med] (-0.335,-0.390)--(-0.079,-0.351);
|
||||||
|
\draw[med] (-0.359,-0.389)--(-0.365,-0.389);
|
||||||
|
\draw[med] (-0.359,-0.389)--(-0.224,-0.345);
|
||||||
|
\draw[med] (-0.359,-0.389)--(-0.194,-0.347);
|
||||||
|
\draw[med] (-0.329,-0.259)--(-0.065,-0.171);
|
||||||
|
\draw[med] (-0.365,-0.389)--(-0.102,-0.301);
|
||||||
|
\draw[med] (-0.365,-0.389)--(-0.224,-0.345);
|
||||||
|
\draw[med] (0.264,-0.345)--(0.377,-0.378);
|
||||||
|
\draw[med] (0.264,-0.345)--(0.325,-0.387);
|
||||||
|
\draw[med] (0.264,-0.345)--(0.140,-0.290);
|
||||||
|
\draw[med] (0.264,-0.345)--(0.089,-0.299);
|
||||||
|
\draw[med] (0.350,-0.318)--(0.396,-0.362);
|
||||||
|
\draw[med] (0.350,-0.318)--(0.245,-0.247);
|
||||||
|
\draw[med] (0.256,-0.394)--(0.325,-0.387);
|
||||||
|
\draw[med] (0.256,-0.394)--(0.081,-0.348);
|
||||||
|
\draw[med] (0.367,-0.357)--(0.396,-0.362);
|
||||||
|
\draw[med] (0.367,-0.357)--(0.377,-0.378);
|
||||||
|
\draw[med] (0.367,-0.357)--(0.244,-0.302);
|
||||||
|
\draw[med] (0.367,-0.357)--(0.263,-0.286);
|
||||||
|
\draw[med] (0.396,-0.362)--(0.263,-0.286);
|
||||||
|
\draw[med] (0.396,-0.362)--(0.245,-0.247);
|
||||||
|
\draw[med] (0.325,-0.387)--(0.081,-0.348);
|
||||||
|
\draw[med] (0.325,-0.387)--(0.089,-0.299);
|
||||||
|
\draw[med] (0.377,-0.378)--(0.140,-0.290);
|
||||||
|
\draw[med] (0.377,-0.378)--(0.244,-0.302);
|
||||||
|
\draw[med] (0.113,-0.230)--(0.076,-0.246);
|
||||||
|
\draw[med] (0.113,-0.230)--(0.162,-0.218);
|
||||||
|
\draw[med] (0.019,-0.306)--(-0.071,-0.302);
|
||||||
|
\draw[med] (0.019,-0.306)--(0.045,-0.287);
|
||||||
|
\draw[med] (0.019,-0.306)--(-0.079,-0.351);
|
||||||
|
\draw[med] (0.019,-0.306)--(0.038,-0.336);
|
||||||
|
\draw[med] (0.131,-0.268)--(0.140,-0.290);
|
||||||
|
\draw[med] (0.131,-0.268)--(0.087,-0.250);
|
||||||
|
\draw[med] (0.131,-0.268)--(0.190,-0.262);
|
||||||
|
\draw[med] (0.131,-0.268)--(0.244,-0.302);
|
||||||
|
\draw[med] (-0.071,-0.302)--(-0.096,-0.301);
|
||||||
|
\draw[med] (-0.071,-0.302)--(-0.079,-0.351);
|
||||||
|
\draw[med] (-0.071,-0.302)--(-0.194,-0.347);
|
||||||
|
\draw[med] (0.089,-0.299)--(0.045,-0.287);
|
||||||
|
\draw[med] (0.089,-0.299)--(0.107,-0.329);
|
||||||
|
\draw[med] (0.140,-0.290)--(0.244,-0.302);
|
||||||
|
\draw[med] (-0.096,-0.301)--(-0.102,-0.301);
|
||||||
|
\draw[med] (-0.096,-0.301)--(-0.194,-0.347);
|
||||||
|
\draw[med] (-0.096,-0.301)--(-0.224,-0.345);
|
||||||
|
\draw[med] (0.045,-0.287)--(0.107,-0.329);
|
||||||
|
\draw[med] (0.045,-0.287)--(0.038,-0.336);
|
||||||
|
\draw[med] (0.087,-0.250)--(0.076,-0.246);
|
||||||
|
\draw[med] (0.087,-0.250)--(0.135,-0.239);
|
||||||
|
\draw[med] (0.087,-0.250)--(0.190,-0.262);
|
||||||
|
\draw[med] (0.076,-0.246)--(0.162,-0.218);
|
||||||
|
\draw[med] (0.076,-0.246)--(0.135,-0.239);
|
||||||
|
\draw[med] (-0.102,-0.301)--(-0.224,-0.345);
|
||||||
|
\draw[med] (0.217,-0.241)--(0.173,-0.223);
|
||||||
|
\draw[med] (0.217,-0.241)--(0.210,-0.222);
|
||||||
|
\draw[med] (0.217,-0.241)--(0.190,-0.262);
|
||||||
|
\draw[med] (0.217,-0.241)--(0.227,-0.260);
|
||||||
|
\draw[med] (0.245,-0.247)--(0.216,-0.226);
|
||||||
|
\draw[med] (0.245,-0.247)--(0.262,-0.270);
|
||||||
|
\draw[med] (0.216,-0.226)--(0.209,-0.217);
|
||||||
|
\draw[med] (0.216,-0.226)--(0.262,-0.270);
|
||||||
|
\draw[med] (0.216,-0.226)--(0.225,-0.240);
|
||||||
|
\draw[med] (0.210,-0.222)--(0.209,-0.217);
|
||||||
|
\draw[med] (0.210,-0.222)--(0.219,-0.236);
|
||||||
|
\draw[med] (0.210,-0.222)--(0.227,-0.260);
|
||||||
|
\draw[med] (0.173,-0.223)--(0.162,-0.218);
|
||||||
|
\draw[med] (0.173,-0.223)--(0.190,-0.262);
|
||||||
|
\draw[med] (0.173,-0.223)--(0.135,-0.239);
|
||||||
|
\draw[med] (0.209,-0.217)--(0.225,-0.240);
|
||||||
|
\draw[med] (0.209,-0.217)--(0.219,-0.236);
|
||||||
|
\draw[med] (0.162,-0.218)--(0.135,-0.239);
|
||||||
|
\draw[med] (0.081,-0.348)--(0.038,-0.336);
|
||||||
|
\draw[med] (0.081,-0.348)--(0.107,-0.329);
|
||||||
|
\draw[med] (0.038,-0.336)--(0.107,-0.329);
|
||||||
|
\draw[med] (0.263,-0.286)--(0.233,-0.265);
|
||||||
|
\draw[med] (0.263,-0.286)--(0.262,-0.270);
|
||||||
|
\draw[med] (0.233,-0.265)--(0.227,-0.260);
|
||||||
|
\draw[med] (0.233,-0.265)--(0.227,-0.245);
|
||||||
|
\draw[med] (0.233,-0.265)--(0.262,-0.270);
|
||||||
|
\draw[med] (0.227,-0.260)--(0.227,-0.245);
|
||||||
|
\draw[med] (0.227,-0.245)--(0.225,-0.240);
|
||||||
|
\draw[med] (0.227,-0.245)--(0.219,-0.236);
|
||||||
|
\draw[med] (0.225,-0.240)--(0.219,-0.236);
|
||||||
|
\node[annv] at (-0.250,0.000) {};
|
||||||
|
\node[levtwo] at (0.250,0.000) {};
|
||||||
|
\node[annv] at (0.014,0.088) {};
|
||||||
|
\node[levtwo] at (0.100,0.115) {};
|
||||||
|
\node[levtwo] at (-0.079,0.174) {};
|
||||||
|
\node[annv] at (0.000,-0.433) {};
|
||||||
|
\node[levone] at (-0.236,-0.345) {};
|
||||||
|
\node[levone] at (-0.244,-0.394) {};
|
||||||
|
\node[annv] at (-0.335,-0.390) {};
|
||||||
|
\node[levone] at (-0.359,-0.389) {};
|
||||||
|
\node[annv] at (-0.329,-0.259) {};
|
||||||
|
\node[annv] at (-0.365,-0.389) {};
|
||||||
|
\node[annv] at (0.264,-0.345) {};
|
||||||
|
\node[knownv] at (0.350,-0.318) {};
|
||||||
|
\node[annv] at (0.256,-0.394) {};
|
||||||
|
\node[knownv] at (0.367,-0.357) {};
|
||||||
|
\node[annv] at (0.396,-0.362) {};
|
||||||
|
\node[annv] at (0.113,-0.230) {};
|
||||||
|
\node[levone] at (0.019,-0.306) {};
|
||||||
|
\node[annv] at (0.131,-0.268) {};
|
||||||
|
\node[annv] at (-0.071,-0.302) {};
|
||||||
|
\node[knownv] at (0.217,-0.241) {};
|
||||||
|
\node[annv] at (0.245,-0.247) {};
|
||||||
|
\node[annv] at (0.216,-0.226) {};
|
||||||
|
\node[annv] at (-0.079,-0.351) {};
|
||||||
|
\node[annv] at (0.263,-0.286) {};
|
||||||
|
\node[annv] at (0.233,-0.265) {};
|
||||||
|
\node[knownv] at (0.262,-0.270) {};
|
||||||
|
\node[levtwo] at (0.325,-0.387) {};
|
||||||
|
\node[annv] at (0.089,-0.299) {};
|
||||||
|
\node[annv] at (0.081,-0.348) {};
|
||||||
|
\node[levtwo] at (0.107,-0.329) {};
|
||||||
|
\node[levtwo] at (0.377,-0.378) {};
|
||||||
|
\node[annv] at (0.140,-0.290) {};
|
||||||
|
\node[levtwo] at (0.244,-0.302) {};
|
||||||
|
\node[levone] at (-0.096,-0.301) {};
|
||||||
|
\node[annv] at (-0.194,-0.347) {};
|
||||||
|
\node[annv] at (-0.224,-0.345) {};
|
||||||
|
\node[annv] at (0.210,-0.222) {};
|
||||||
|
\node[annv] at (0.227,-0.260) {};
|
||||||
|
\node[knownv] at (0.227,-0.245) {};
|
||||||
|
\node[knownv] at (0.219,-0.236) {};
|
||||||
|
\node[annv] at (0.045,-0.287) {};
|
||||||
|
\node[annv] at (0.038,-0.336) {};
|
||||||
|
\node[annv] at (0.087,-0.250) {};
|
||||||
|
\node[levtwo] at (0.173,-0.223) {};
|
||||||
|
\node[levtwo] at (0.190,-0.262) {};
|
||||||
|
\node[levtwo] at (0.135,-0.239) {};
|
||||||
|
\node[annv] at (0.209,-0.217) {};
|
||||||
|
\node[knownv] at (0.225,-0.240) {};
|
||||||
|
\node[annv] at (-0.065,-0.171) {};
|
||||||
|
\node[annv] at (0.076,-0.246) {};
|
||||||
|
\node[levtwo] at (0.162,-0.218) {};
|
||||||
|
\node[annv] at (-0.102,-0.301) {};
|
||||||
|
\node[elbl] at (-0.250,0.000) [yshift=-4.8pt] {$0\!{-}\!1$};
|
||||||
|
\node[elbl] at (0.250,0.000) [yshift=-4.8pt] {$0\!{-}\!2$};
|
||||||
|
\node[elbl] at (0.014,0.088) [yshift=-4.8pt] {$0\!{-}\!3$};
|
||||||
|
\node[elbl] at (0.100,0.115) [yshift=-4.8pt] {$0\!{-}\!4$};
|
||||||
|
\node[elbl] at (-0.079,0.174) [yshift=-4.8pt] {$0\!{-}\!17$};
|
||||||
|
\node[elbl] at (0.000,-0.433) [yshift=-4.8pt] {$1\!{-}\!2$};
|
||||||
|
\node[elbl] at (-0.236,-0.345) [yshift=-4.8pt] {$1\!{-}\!3$};
|
||||||
|
\node[elbl] at (-0.244,-0.394) [yshift=-4.8pt] {$1\!{-}\!5$};
|
||||||
|
\node[elbl] at (-0.335,-0.390) [yshift=-4.8pt] {$1\!{-}\!9$};
|
||||||
|
\node[elbl] at (-0.359,-0.389) [yshift=-4.8pt] {$1\!{-}\!12$};
|
||||||
|
\node[elbl] at (-0.329,-0.259) [yshift=-4.8pt] {$1\!{-}\!17$};
|
||||||
|
\node[elbl] at (-0.365,-0.389) [yshift=-4.8pt] {$1\!{-}\!19$};
|
||||||
|
\node[elbl] at (0.264,-0.345) [yshift=-4.8pt] {$2\!{-}\!3$};
|
||||||
|
\node[elbl] at (0.350,-0.318) [yshift=-4.8pt] {$2\!{-}\!4$};
|
||||||
|
\node[elbl] at (0.256,-0.394) [yshift=-4.8pt] {$2\!{-}\!5$};
|
||||||
|
\node[elbl] at (0.367,-0.357) [yshift=-4.8pt] {$2\!{-}\!6$};
|
||||||
|
\node[elbl] at (0.396,-0.362) [yshift=-4.8pt] {$2\!{-}\!7$};
|
||||||
|
\node[elbl] at (0.113,-0.230) [yshift=-4.8pt] {$3\!{-}\!4$};
|
||||||
|
\node[elbl] at (0.019,-0.306) [yshift=-4.8pt] {$3\!{-}\!5$};
|
||||||
|
\node[elbl] at (0.131,-0.268) [yshift=-4.8pt] {$3\!{-}\!6$};
|
||||||
|
\node[elbl] at (-0.071,-0.302) [yshift=-4.8pt] {$3\!{-}\!9$};
|
||||||
|
\node[elbl] at (0.217,-0.241) [yshift=-4.8pt] {$4\!{-}\!6$};
|
||||||
|
\node[elbl] at (0.245,-0.247) [yshift=-4.8pt] {$4\!{-}\!7$};
|
||||||
|
\node[elbl] at (0.216,-0.226) [yshift=-4.8pt] {$4\!{-}\!8$};
|
||||||
|
\node[elbl] at (-0.079,-0.351) [yshift=-4.8pt] {$5\!{-}\!9$};
|
||||||
|
\node[elbl] at (0.263,-0.286) [yshift=-4.8pt] {$6\!{-}\!7$};
|
||||||
|
\node[elbl] at (0.233,-0.265) [yshift=-4.8pt] {$6\!{-}\!8$};
|
||||||
|
\node[elbl] at (0.262,-0.270) [yshift=-4.8pt] {$7\!{-}\!8$};
|
||||||
|
\node[elbl] at (0.325,-0.387) [yshift=-4.8pt] {$10\!{-}\!2$};
|
||||||
|
\node[elbl] at (0.089,-0.299) [yshift=-4.8pt] {$10\!{-}\!3$};
|
||||||
|
\node[elbl] at (0.081,-0.348) [yshift=-4.8pt] {$10\!{-}\!5$};
|
||||||
|
\node[elbl] at (0.107,-0.329) [yshift=-4.8pt] {$10\!{-}\!14$};
|
||||||
|
\node[elbl] at (0.377,-0.378) [yshift=-4.8pt] {$11\!{-}\!2$};
|
||||||
|
\node[elbl] at (0.140,-0.290) [yshift=-4.8pt] {$11\!{-}\!3$};
|
||||||
|
\node[elbl] at (0.244,-0.302) [yshift=-4.8pt] {$11\!{-}\!6$};
|
||||||
|
\node[elbl] at (-0.096,-0.301) [yshift=-4.8pt] {$12\!{-}\!3$};
|
||||||
|
\node[elbl] at (-0.194,-0.347) [yshift=-4.8pt] {$12\!{-}\!9$};
|
||||||
|
\node[elbl] at (-0.224,-0.345) [yshift=-4.8pt] {$12\!{-}\!19$};
|
||||||
|
\node[elbl] at (0.210,-0.222) [yshift=-4.8pt] {$13\!{-}\!4$};
|
||||||
|
\node[elbl] at (0.227,-0.260) [yshift=-4.8pt] {$13\!{-}\!6$};
|
||||||
|
\node[elbl] at (0.227,-0.245) [yshift=-4.8pt] {$13\!{-}\!8$};
|
||||||
|
\node[elbl] at (0.219,-0.236) [yshift=-4.8pt] {$13\!{-}\!16$};
|
||||||
|
\node[elbl] at (0.045,-0.287) [yshift=-4.8pt] {$14\!{-}\!3$};
|
||||||
|
\node[elbl] at (0.038,-0.336) [yshift=-4.8pt] {$14\!{-}\!5$};
|
||||||
|
\node[elbl] at (0.087,-0.250) [yshift=-4.8pt] {$15\!{-}\!3$};
|
||||||
|
\node[elbl] at (0.173,-0.223) [yshift=-4.8pt] {$15\!{-}\!4$};
|
||||||
|
\node[elbl] at (0.190,-0.262) [yshift=-4.8pt] {$15\!{-}\!6$};
|
||||||
|
\node[elbl] at (0.135,-0.239) [yshift=-4.8pt] {$15\!{-}\!18$};
|
||||||
|
\node[elbl] at (0.209,-0.217) [yshift=-4.8pt] {$16\!{-}\!4$};
|
||||||
|
\node[elbl] at (0.225,-0.240) [yshift=-4.8pt] {$16\!{-}\!8$};
|
||||||
|
\node[elbl] at (-0.065,-0.171) [yshift=-4.8pt] {$17\!{-}\!3$};
|
||||||
|
\node[elbl] at (0.076,-0.246) [yshift=-4.8pt] {$18\!{-}\!3$};
|
||||||
|
\node[elbl] at (0.162,-0.218) [yshift=-4.8pt] {$18\!{-}\!4$};
|
||||||
|
\node[elbl] at (-0.102,-0.301) [yshift=-4.8pt] {$19\!{-}\!3$};
|
||||||
|
\node[dlbl] at (0.350,-0.318) [yshift=5.0pt] {6};
|
||||||
|
\node[dlbl] at (0.367,-0.357) [yshift=5.0pt] {7};
|
||||||
|
\node[dlbl] at (0.217,-0.241) [yshift=5.0pt] {0};
|
||||||
|
\node[dlbl] at (0.262,-0.270) [yshift=5.0pt] {2,3};
|
||||||
|
\node[dlbl] at (0.227,-0.245) [yshift=5.0pt] {1};
|
||||||
|
\node[dlbl] at (0.219,-0.236) [yshift=5.0pt] {5};
|
||||||
|
\node[dlbl] at (0.225,-0.240) [yshift=5.0pt] {4};
|
||||||
|
\draw[cut] (0.241,-0.204)--(0.180,-0.239);
|
||||||
|
\node[cutlbl] at (0.137,-0.263) {cut 1};
|
||||||
|
\draw[cut] (0.256,-0.320)--(0.269,-0.251);
|
||||||
|
\node[cutlbl] at (0.279,-0.203) {cut 2};
|
||||||
|
\end{tikzpicture}
|
||||||
|
\\[-0.25ex]
|
||||||
|
{\scriptsize medial graph $M(G)$ at edge midpoints}
|
||||||
|
\end{tabular}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Random medial tire decomposition 1
|
||||||
|
|
||||||
|
- original vertices: 30
|
||||||
|
- original edges: 84
|
||||||
|
- original node connectivity: 5
|
||||||
|
- augmented vertices: 33
|
||||||
|
- augmented edges: 93
|
||||||
|
- same-level faces filled: 3
|
||||||
|
- source vertex: 14
|
||||||
|
- tire-tree nodes: 4
|
||||||
|
- tire-tree edges: 3
|
||||||
|
|
||||||
|
| node | depth | faces | annular cycles | annular | up | singleton down | bite apexes |
|
||||||
|
|--:|--:|--:|--:|--:|--:|--:|--:|
|
||||||
|
| T0 | 2 | 23 | 1 | 23 | 10 | 13 | 0 |
|
||||||
|
| T1 | 1 | 15 | 1 | 15 | 5 | 10 | 0 |
|
||||||
|
| T2 | 3 | 14 | 4 | 14 | 11 | 0 | 0 |
|
||||||
|
| T3 | 3 | 5 | 1 | 5 | 5 | 0 | 0 |
|
||||||
|
After Width: | Height: | Size: 341 KiB |
@@ -0,0 +1,16 @@
|
|||||||
|
"""Compatibility wrapper for the medial tire decomposition drawing script."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
PAPER_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
if PAPER_DIR not in sys.path:
|
||||||
|
sys.path.insert(0, PAPER_DIR)
|
||||||
|
|
||||||
|
from lib.draw_random_medial_tire_decompositions import main
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,311 +1,16 @@
|
|||||||
"""Exhaustive generator for full medial tire graphs, indexed by |A(T)|.
|
"""Compatibility wrapper for the medial tire generator now kept in ../lib."""
|
||||||
|
|
||||||
Model (Definitions/Remarks 3.1--3.9 of the medial tire decompositions paper).
|
|
||||||
|
|
||||||
* The annular medial vertices induce a cycle A(T), the *annular cycle*
|
|
||||||
(Theorem 3.3). Write n = |A(T)| for its number of vertices = number of
|
|
||||||
annular faces = number of annular edges e_0,...,e_{n-1}.
|
|
||||||
|
|
||||||
* Each edge e_i of A(T) carries exactly one tooth (a triangle of M(T))
|
|
||||||
whose third vertex is a non-annular apex (Definition 3.4). A tooth is an
|
|
||||||
*up tooth* (apex in the outer region) or a *down tooth* (apex in the inner
|
|
||||||
region). We record the tooth types as a word in {U, D}^n.
|
|
||||||
|
|
||||||
* No two up teeth share an apex; at most two down teeth share an apex
|
|
||||||
(Remark 3.5). Two down teeth sharing an apex form a *bite* (Definition
|
|
||||||
3.7). So the down teeth are partitioned into singletons and bite pairs.
|
|
||||||
A bite pairs two down-edges and is drawn as an apex inside the disk with
|
|
||||||
spokes to the four endpoints; bites must be mutually non-crossing, i.e.
|
|
||||||
the bite pairs form a non-crossing (laminar) matching of the down-edges.
|
|
||||||
The two annular edges of a bite must be non-incident (Definition 3.7):
|
|
||||||
they share no annular vertex, so cyclically adjacent edges cannot pair.
|
|
||||||
|
|
||||||
* There are at least three up teeth (Remark 3.6).
|
|
||||||
|
|
||||||
* Bite-face condition (Remark 3.8). Let B(T) = A(T) together with the bite
|
|
||||||
apexes. Its interior non-tooth faces are the root face plus one inner-gap
|
|
||||||
face per bite. A singleton down tooth lies in the innermost bite enclosing
|
|
||||||
its edge (or in the root face if none). For every interior non-tooth face
|
|
||||||
the number of down-tooth apexes lying in that face must be 0 or at least 3.
|
|
||||||
Equivalently: no face holds exactly one or two singleton down teeth.
|
|
||||||
|
|
||||||
The generator enumerates, for a given n, every (tooth word, bite matching)
|
|
||||||
pair satisfying these properties and emits the resulting full medial tire
|
|
||||||
graph as an explicit vertex/edge structure. Configurations may optionally be
|
|
||||||
reduced modulo the dihedral symmetry of the cycle.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import os
|
||||||
import itertools
|
import sys
|
||||||
from collections import defaultdict
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from functools import lru_cache
|
|
||||||
from typing import Iterator
|
|
||||||
|
|
||||||
# A bite is an unordered pair of down-edge indices (i, j) with i < j.
|
PAPER_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
Bite = tuple[int, int]
|
if PAPER_DIR not in sys.path:
|
||||||
Matching = frozenset[Bite]
|
sys.path.insert(0, PAPER_DIR)
|
||||||
|
|
||||||
|
from lib.full_medial_tire_generator import * # noqa: F401,F403
|
||||||
# ---------------------------------------------------------------------------
|
from lib.full_medial_tire_generator import main
|
||||||
# Non-crossing (laminar) matchings of the down edges.
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
@lru_cache(maxsize=None)
|
|
||||||
def noncrossing_matchings(positions: tuple[int, ...]) -> tuple[Matching, ...]:
|
|
||||||
"""All non-crossing partial matchings of ``positions`` (sorted ascending).
|
|
||||||
|
|
||||||
Bite pairs drawn inside the disk are non-crossing iff, read in cyclic
|
|
||||||
order, no two pairs interleave. Cutting the cycle at the gap before the
|
|
||||||
first edge turns this into ordinary non-crossing interval matchings, which
|
|
||||||
obey the Catalan recursion below.
|
|
||||||
"""
|
|
||||||
if not positions:
|
|
||||||
return (frozenset(),)
|
|
||||||
head, *rest = positions
|
|
||||||
out: list[Matching] = []
|
|
||||||
# head left unmatched (a singleton down tooth, if its edge is down)
|
|
||||||
for tail in noncrossing_matchings(tuple(rest)):
|
|
||||||
out.append(tail)
|
|
||||||
# head matched with positions[k]; the strictly-enclosed block must be
|
|
||||||
# matched within itself to stay non-crossing.
|
|
||||||
for k in range(1, len(positions)):
|
|
||||||
partner = positions[k]
|
|
||||||
inside = tuple(positions[1:k])
|
|
||||||
outside = tuple(positions[k + 1:])
|
|
||||||
for m_in in noncrossing_matchings(inside):
|
|
||||||
for m_out in noncrossing_matchings(outside):
|
|
||||||
out.append(frozenset({(head, partner)}) | m_in | m_out)
|
|
||||||
return tuple(out)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# The bite-face condition (Remark 3.8).
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def incident_edges(i: int, j: int, n: int) -> bool:
|
|
||||||
"""Whether annular edges i and j share an annular vertex on the n-cycle."""
|
|
||||||
return (j - i) % n == 1 or (i - j) % n == 1
|
|
||||||
|
|
||||||
|
|
||||||
def has_incident_bite(bites: Matching, n: int) -> bool:
|
|
||||||
"""Whether any bite pairs two incident (cyclically adjacent) edges."""
|
|
||||||
return any(incident_edges(i, j, n) for i, j in bites)
|
|
||||||
|
|
||||||
|
|
||||||
def innermost_bite(edge: int, bites: Matching) -> Bite | None:
|
|
||||||
"""The minimal-span bite whose open interval contains ``edge``, or None."""
|
|
||||||
enclosing = [b for b in bites if b[0] < edge < b[1]]
|
|
||||||
if not enclosing:
|
|
||||||
return None
|
|
||||||
return min(enclosing, key=lambda b: b[1] - b[0])
|
|
||||||
|
|
||||||
|
|
||||||
def face_singleton_counts(
|
|
||||||
tooth_word: str, bites: Matching
|
|
||||||
) -> dict[Bite | None, int]:
|
|
||||||
"""Down-singletons per interior non-tooth face of B(T).
|
|
||||||
|
|
||||||
The key ``None`` is the root face; a bite key is that bite's inner-gap
|
|
||||||
face. Faces with no singletons are simply absent from the result.
|
|
||||||
"""
|
|
||||||
matched = {edge for pair in bites for edge in pair}
|
|
||||||
counts: dict[Bite | None, int] = defaultdict(int)
|
|
||||||
for edge, tooth in enumerate(tooth_word):
|
|
||||||
if tooth != "D" or edge in matched:
|
|
||||||
continue # only singleton down teeth contribute apexes
|
|
||||||
counts[innermost_bite(edge, bites)] += 1
|
|
||||||
return dict(counts)
|
|
||||||
|
|
||||||
|
|
||||||
def satisfies_bite_face_condition(tooth_word: str, bites: Matching) -> bool:
|
|
||||||
"""Remark 3.8: every non-tooth face holds 0 or >=3 down-tooth apexes."""
|
|
||||||
return all(count >= 3 for count in face_singleton_counts(tooth_word, bites).values())
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# The full medial tire graph as an explicit object.
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class FullMedialTireGraph:
|
|
||||||
"""A full medial tire graph M(T) determined by its combinatorial data.
|
|
||||||
|
|
||||||
Vertices are named:
|
|
||||||
a{k} annular medial vertex k (k = 0..n-1), forming A(T);
|
|
||||||
u{i} apex of the up tooth on edge i;
|
|
||||||
d{i} apex of the singleton down tooth on edge i;
|
|
||||||
p{i}_{j} apex of the bite pairing edges i and j (i < j).
|
|
||||||
"""
|
|
||||||
|
|
||||||
n: int
|
|
||||||
tooth_word: str
|
|
||||||
bites: Matching
|
|
||||||
|
|
||||||
@property
|
|
||||||
def up_edges(self) -> tuple[int, ...]:
|
|
||||||
return tuple(i for i, t in enumerate(self.tooth_word) if t == "U")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def down_edges(self) -> tuple[int, ...]:
|
|
||||||
return tuple(i for i, t in enumerate(self.tooth_word) if t == "D")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def bite_edges(self) -> frozenset[int]:
|
|
||||||
return frozenset(edge for pair in self.bites for edge in pair)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def singleton_down_edges(self) -> tuple[int, ...]:
|
|
||||||
bite = self.bite_edges
|
|
||||||
return tuple(i for i in self.down_edges if i not in bite)
|
|
||||||
|
|
||||||
def apex_of_edge(self, edge: int) -> str:
|
|
||||||
if self.tooth_word[edge] == "U":
|
|
||||||
return f"u{edge}"
|
|
||||||
for i, j in self.bites:
|
|
||||||
if edge in (i, j):
|
|
||||||
return f"p{i}_{j}"
|
|
||||||
return f"d{edge}"
|
|
||||||
|
|
||||||
def vertices(self) -> list[str]:
|
|
||||||
verts = [f"a{k}" for k in range(self.n)]
|
|
||||||
for i in self.up_edges:
|
|
||||||
verts.append(f"u{i}")
|
|
||||||
for i in self.singleton_down_edges:
|
|
||||||
verts.append(f"d{i}")
|
|
||||||
for i, j in sorted(self.bites):
|
|
||||||
verts.append(f"p{i}_{j}")
|
|
||||||
return verts
|
|
||||||
|
|
||||||
def edges(self) -> list[tuple[str, str]]:
|
|
||||||
n = self.n
|
|
||||||
out: list[tuple[str, str]] = []
|
|
||||||
# annular cycle A(T)
|
|
||||||
for k in range(n):
|
|
||||||
out.append((f"a{k}", f"a{(k + 1) % n}"))
|
|
||||||
# singleton teeth (up and down): two spokes each
|
|
||||||
for i in self.up_edges:
|
|
||||||
out += [(f"u{i}", f"a{i}"), (f"u{i}", f"a{(i + 1) % n}")]
|
|
||||||
for i in self.singleton_down_edges:
|
|
||||||
out += [(f"d{i}", f"a{i}"), (f"d{i}", f"a{(i + 1) % n}")]
|
|
||||||
# bites: a shared apex with four spokes
|
|
||||||
for i, j in sorted(self.bites):
|
|
||||||
apex = f"p{i}_{j}"
|
|
||||||
for edge in (i, j):
|
|
||||||
out += [(apex, f"a{edge}"), (apex, f"a{(edge + 1) % n}")]
|
|
||||||
return [tuple(sorted(e)) for e in out]
|
|
||||||
|
|
||||||
def canonical_key(self) -> tuple:
|
|
||||||
"""Representative under the dihedral group of the cycle (rotations and
|
|
||||||
reflections), so symmetric configurations collapse to one key."""
|
|
||||||
n = self.n
|
|
||||||
best: tuple | None = None
|
|
||||||
for a in (1, -1):
|
|
||||||
for b in range(n):
|
|
||||||
relabel = lambda i: (a * i + b) % n
|
|
||||||
word = [""] * n
|
|
||||||
for i, t in enumerate(self.tooth_word):
|
|
||||||
word[relabel(i)] = t
|
|
||||||
mapped = tuple(sorted(
|
|
||||||
tuple(sorted((relabel(i), relabel(j)))) for i, j in self.bites
|
|
||||||
))
|
|
||||||
key = (tuple(word), mapped)
|
|
||||||
if best is None or key < best:
|
|
||||||
best = key
|
|
||||||
return best
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Enumeration.
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def generate(
|
|
||||||
n: int, min_up_teeth: int = 3, dedup: bool = False
|
|
||||||
) -> Iterator[FullMedialTireGraph]:
|
|
||||||
"""Yield every full medial tire graph whose annular cycle has size ``n``.
|
|
||||||
|
|
||||||
``min_up_teeth`` defaults to 3 (Remark 3.6). With ``dedup`` set, only one
|
|
||||||
representative per dihedral symmetry class is returned.
|
|
||||||
"""
|
|
||||||
seen: set[tuple] = set()
|
|
||||||
for word_tuple in itertools.product("UD", repeat=n):
|
|
||||||
tooth_word = "".join(word_tuple)
|
|
||||||
if tooth_word.count("U") < min_up_teeth:
|
|
||||||
continue
|
|
||||||
down = tuple(i for i, t in enumerate(tooth_word) if t == "D")
|
|
||||||
for bites in noncrossing_matchings(down):
|
|
||||||
if has_incident_bite(bites, n):
|
|
||||||
continue
|
|
||||||
if not satisfies_bite_face_condition(tooth_word, bites):
|
|
||||||
continue
|
|
||||||
graph = FullMedialTireGraph(n=n, tooth_word=tooth_word, bites=bites)
|
|
||||||
if dedup:
|
|
||||||
key = graph.canonical_key()
|
|
||||||
if key in seen:
|
|
||||||
continue
|
|
||||||
seen.add(key)
|
|
||||||
yield graph
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# CLI.
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def figure_one() -> FullMedialTireGraph:
|
|
||||||
"""The example graph of Figure 1 (Remark 3.8): 12 edges, one bite (0,6)."""
|
|
||||||
return FullMedialTireGraph(
|
|
||||||
n=12,
|
|
||||||
tooth_word="DDDDDUDUUUUU", # edges 0-4,6 down; 5,7,8,9,10,11 up
|
|
||||||
bites=frozenset({(0, 6)}),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def describe(graph: FullMedialTireGraph) -> str:
|
|
||||||
counts = face_singleton_counts(graph.tooth_word, graph.bites)
|
|
||||||
face_strs = []
|
|
||||||
for face, c in sorted(counts.items(), key=lambda kv: (kv[0] is not None, kv[0])):
|
|
||||||
name = "root" if face is None else f"bite{face}"
|
|
||||||
face_strs.append(f"{name}:{c}")
|
|
||||||
bites = ",".join(f"({i},{j})" for i, j in sorted(graph.bites)) or "-"
|
|
||||||
faces = " ".join(face_strs) or "-"
|
|
||||||
return (
|
|
||||||
f"word={graph.tooth_word} up={len(graph.up_edges)} "
|
|
||||||
f"down={len(graph.down_edges)} bites={bites} faces[{faces}]"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def run(args: argparse.Namespace) -> None:
|
|
||||||
if args.check_figure:
|
|
||||||
g = figure_one()
|
|
||||||
print("Figure 1 check:")
|
|
||||||
print(f" {describe(g)}")
|
|
||||||
ok = satisfies_bite_face_condition(g.tooth_word, g.bites)
|
|
||||||
print(f" satisfies Remark 3.8: {ok} (expect True; faces 4 and 0)")
|
|
||||||
print()
|
|
||||||
|
|
||||||
for n in range(args.min_n, args.max_n + 1):
|
|
||||||
graphs = list(generate(n, min_up_teeth=args.min_up, dedup=args.dedup))
|
|
||||||
label = "classes" if args.dedup else "graphs"
|
|
||||||
print(f"n={n}: {len(graphs)} {label}")
|
|
||||||
if args.show:
|
|
||||||
for g in graphs[: args.show]:
|
|
||||||
print(f" {describe(g)}")
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
parser = argparse.ArgumentParser(description=__doc__)
|
|
||||||
parser.add_argument("--min-n", type=int, default=3)
|
|
||||||
parser.add_argument("--max-n", type=int, default=8)
|
|
||||||
parser.add_argument("--min-up", type=int, default=3, help="Remark 3.6 bound")
|
|
||||||
parser.add_argument("--dedup", action="store_true",
|
|
||||||
help="reduce modulo dihedral symmetry of the cycle")
|
|
||||||
parser.add_argument("--show", type=int, default=0,
|
|
||||||
help="print up to this many graphs per n")
|
|
||||||
parser.add_argument("--check-figure", action="store_true",
|
|
||||||
help="verify the Figure 1 example against Remark 3.8")
|
|
||||||
run(parser.parse_args())
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Random medial tire decomposition 1
|
||||||
|
|
||||||
|
- original vertices: 30
|
||||||
|
- original edges: 84
|
||||||
|
- original node connectivity: 5
|
||||||
|
- augmented vertices: 31
|
||||||
|
- augmented edges: 87
|
||||||
|
- same-level faces filled: 1
|
||||||
|
- source vertex: 9
|
||||||
|
- tire-tree nodes: 3
|
||||||
|
- tire-tree edges: 2
|
||||||
|
|
||||||
|
| node | depth | faces | annular cycles | annular | up | singleton down | bite apexes |
|
||||||
|
|--:|--:|--:|--:|--:|--:|--:|--:|
|
||||||
|
| T0 | 1 | 16 | 1 | 16 | 6 | 10 | 0 |
|
||||||
|
| T1 | 2 | 20 | 1 | 20 | 10 | 10 | 0 |
|
||||||
|
| T2 | 3 | 16 | 3 | 16 | 12 | 0 | 1 |
|
||||||
|
After Width: | Height: | Size: 294 KiB |
@@ -0,0 +1,18 @@
|
|||||||
|
# Random medial tire decomposition 2
|
||||||
|
|
||||||
|
- original vertices: 30
|
||||||
|
- original edges: 84
|
||||||
|
- original node connectivity: 5
|
||||||
|
- augmented vertices: 32
|
||||||
|
- augmented edges: 90
|
||||||
|
- same-level faces filled: 2
|
||||||
|
- source vertex: 4
|
||||||
|
- tire-tree nodes: 4
|
||||||
|
- tire-tree edges: 3
|
||||||
|
|
||||||
|
| node | depth | faces | annular cycles | annular | up | singleton down | bite apexes |
|
||||||
|
|--:|--:|--:|--:|--:|--:|--:|--:|
|
||||||
|
| T0 | 1 | 16 | 1 | 16 | 7 | 9 | 0 |
|
||||||
|
| T1 | 2 | 17 | 1 | 17 | 9 | 8 | 0 |
|
||||||
|
| T2 | 3 | 14 | 1 | 14 | 8 | 4 | 1 |
|
||||||
|
| T3 | 4 | 6 | 2 | 6 | 5 | 0 | 0 |
|
||||||
|
After Width: | Height: | Size: 328 KiB |
@@ -19,14 +19,25 @@ is a bite.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import itertools
|
import os
|
||||||
import random
|
import random
|
||||||
from collections import defaultdict
|
import sys
|
||||||
|
|
||||||
import networkx as nx
|
import networkx as nx
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import scipy.spatial
|
import scipy.spatial
|
||||||
|
|
||||||
|
PAPER_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
if PAPER_DIR not in sys.path:
|
||||||
|
sys.path.insert(0, PAPER_DIR)
|
||||||
|
|
||||||
|
from lib.medial_tire_decomposition import (
|
||||||
|
ekey,
|
||||||
|
extract_tread,
|
||||||
|
medial_tire_facemodel,
|
||||||
|
recognise,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def random_sphere_triangulation(n: int, seed: int) -> nx.Graph:
|
def random_sphere_triangulation(n: int, seed: int) -> nx.Graph:
|
||||||
"""A random maximal planar graph: convex hull of random points on S^2."""
|
"""A random maximal planar graph: convex hull of random points on S^2."""
|
||||||
@@ -40,19 +51,6 @@ def random_sphere_triangulation(n: int, seed: int) -> nx.Graph:
|
|||||||
return g
|
return g
|
||||||
|
|
||||||
|
|
||||||
def medial_tire_facemodel(tread_faces) -> nx.Graph:
|
|
||||||
"""Full medial tire graph M(T): the ambient tread-face model. Each tread
|
|
||||||
face contributes a triangle on its three edge-medial-vertices; no medial
|
|
||||||
edges between two same-boundary edges (those come from non-tread corners)."""
|
|
||||||
Mt = nx.Graph()
|
|
||||||
for f in tread_faces:
|
|
||||||
es = [ekey(f[0], f[1]), ekey(f[1], f[2]), ekey(f[2], f[0])]
|
|
||||||
Mt.add_nodes_from(es)
|
|
||||||
for a in range(3):
|
|
||||||
Mt.add_edge(es[a], es[(a + 1) % 3])
|
|
||||||
return Mt
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------- #
|
# --------------------------------------------------------------------------- #
|
||||||
# Maximal planar graph on n vertices: stacked seed + random diagonal flips.
|
# Maximal planar graph on n vertices: stacked seed + random diagonal flips.
|
||||||
# --------------------------------------------------------------------------- #
|
# --------------------------------------------------------------------------- #
|
||||||
@@ -89,10 +87,6 @@ def random_maximal_planar(n: int, seed: int, flips: int = 400) -> nx.Graph:
|
|||||||
return g
|
return g
|
||||||
|
|
||||||
|
|
||||||
def ekey(u, v):
|
|
||||||
return (u, v) if (str(u), u) <= (str(v), v) else (v, u)
|
|
||||||
|
|
||||||
|
|
||||||
def triangular_faces(g: nx.Graph):
|
def triangular_faces(g: nx.Graph):
|
||||||
ok, emb = nx.check_planarity(g)
|
ok, emb = nx.check_planarity(g)
|
||||||
if not ok:
|
if not ok:
|
||||||
@@ -146,147 +140,12 @@ def proper_3_colorings(M: nx.Graph, limit: int):
|
|||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------- #
|
# --------------------------------------------------------------------------- #
|
||||||
# Tread extraction from a BFS-level decomposition.
|
# Classify colourings of recognised full medial tire graphs.
|
||||||
# --------------------------------------------------------------------------- #
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
def extract_tread(faces, levels, d):
|
|
||||||
"""Tread T_d: faces spanning levels {d, d+1}. Returns its edge classes."""
|
|
||||||
tread_faces = []
|
|
||||||
for f in faces:
|
|
||||||
lv = [levels[x] for x in f]
|
|
||||||
if min(lv) == d and max(lv) == d + 1:
|
|
||||||
tread_faces.append(f)
|
|
||||||
if not tread_faces:
|
|
||||||
return None
|
|
||||||
annular, up, down = set(), set(), set()
|
|
||||||
face_of_down = defaultdict(int)
|
|
||||||
for f in tread_faces:
|
|
||||||
for x, y in ((f[0], f[1]), (f[1], f[2]), (f[2], f[0])):
|
|
||||||
e = ekey(x, y)
|
|
||||||
lx, ly = levels[x], levels[y]
|
|
||||||
if {lx, ly} == {d, d + 1}:
|
|
||||||
annular.add(e)
|
|
||||||
elif lx == ly == d:
|
|
||||||
up.add(e)
|
|
||||||
elif lx == ly == d + 1:
|
|
||||||
down.add(e)
|
|
||||||
face_of_down[e] += 1
|
|
||||||
bites = {e for e in down if face_of_down[e] == 2}
|
|
||||||
return {
|
|
||||||
"tread_faces": tread_faces,
|
|
||||||
"annular": annular, "up": up, "down": down, "bites": bites,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def annular_cycle_order(M: nx.Graph, annular: set):
|
|
||||||
"""Cyclic order of the annular medial vertices (they induce a cycle)."""
|
|
||||||
sub = M.subgraph(annular)
|
|
||||||
if sub.number_of_nodes() == 0 or any(sub.degree(v) != 2 for v in sub):
|
|
||||||
return None
|
|
||||||
if not nx.is_connected(sub):
|
|
||||||
return None
|
|
||||||
start = next(iter(annular))
|
|
||||||
order = [start]
|
|
||||||
prev = None
|
|
||||||
cur = start
|
|
||||||
while True:
|
|
||||||
nbrs = [w for w in sub.neighbors(cur) if w != prev]
|
|
||||||
if not nbrs:
|
|
||||||
break
|
|
||||||
nxt = nbrs[0]
|
|
||||||
if nxt == start:
|
|
||||||
break
|
|
||||||
order.append(nxt)
|
|
||||||
prev, cur = cur, nxt
|
|
||||||
return order if len(order) == len(annular) else None
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------- #
|
|
||||||
# Recognise a genuine tread as a FullMedialTireGraph and classify colourings.
|
|
||||||
# --------------------------------------------------------------------------- #
|
|
||||||
|
|
||||||
from full_medial_tire_generator import FullMedialTireGraph
|
|
||||||
from kempe_valid_colorings import classify as kempe_classify
|
from kempe_valid_colorings import classify as kempe_classify
|
||||||
|
|
||||||
|
|
||||||
def _linear_cut(n, bite_pairs):
|
|
||||||
"""Rotate the cycle so the bite pairs become linear non-crossing intervals."""
|
|
||||||
for r in range(n):
|
|
||||||
rel = [tuple(sorted(((i - r) % n, (j - r) % n))) for i, j in bite_pairs]
|
|
||||||
ok = True
|
|
||||||
for a, b in rel:
|
|
||||||
for c, d in rel:
|
|
||||||
if (a, b) != (c, d) and (a < c < b < d or c < a < d < b):
|
|
||||||
ok = False
|
|
||||||
break
|
|
||||||
if not ok:
|
|
||||||
break
|
|
||||||
if ok:
|
|
||||||
return r, rel
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def recognise(M, tread):
|
|
||||||
"""Return (FullMedialTireGraph, bijection fmt-name -> medial vertex) or None.
|
|
||||||
|
|
||||||
``M`` here is the tread-face model M(T) (cycle + teeth + bites)."""
|
|
||||||
annular = tread["annular"]
|
|
||||||
order = annular_cycle_order(M, annular)
|
|
||||||
if order is None or len(order) < 3:
|
|
||||||
return None
|
|
||||||
n = len(order)
|
|
||||||
ann_set = set(annular)
|
|
||||||
|
|
||||||
apex_of_edge = []
|
|
||||||
for i in range(n):
|
|
||||||
a, b = order[i], order[(i + 1) % n]
|
|
||||||
common = [w for w in set(M.neighbors(a)) & set(M.neighbors(b)) if w not in ann_set]
|
|
||||||
if len(common) != 1:
|
|
||||||
return None
|
|
||||||
apex_of_edge.append(common[0])
|
|
||||||
|
|
||||||
up = set(tread["up"])
|
|
||||||
# bite apex: serves two cycle edges (== adjacent to four annular vertices)
|
|
||||||
apex_positions = defaultdict(list)
|
|
||||||
for i, ap in enumerate(apex_of_edge):
|
|
||||||
apex_positions[ap].append(i)
|
|
||||||
|
|
||||||
tooth = []
|
|
||||||
bite_pairs = []
|
|
||||||
for ap, positions in apex_positions.items():
|
|
||||||
if len(positions) == 2:
|
|
||||||
bite_pairs.append(tuple(sorted(positions)))
|
|
||||||
for i, ap in enumerate(apex_of_edge):
|
|
||||||
tooth.append("U" if ap in up else "D")
|
|
||||||
|
|
||||||
cut = _linear_cut(n, bite_pairs)
|
|
||||||
if cut is None:
|
|
||||||
return None
|
|
||||||
r, rel_bites = cut
|
|
||||||
word = [""] * n
|
|
||||||
for i in range(n):
|
|
||||||
word[(i - r) % n] = tooth[i]
|
|
||||||
g = FullMedialTireGraph(n=n, tooth_word="".join(word), bites=frozenset(rel_bites))
|
|
||||||
|
|
||||||
# bijection fmt-name -> medial vertex
|
|
||||||
bij = {}
|
|
||||||
for k in range(n):
|
|
||||||
bij[f"a{k}"] = order[(k + r) % n]
|
|
||||||
for i in g.up_edges:
|
|
||||||
bij[f"u{i}"] = apex_of_edge[(i + r) % n]
|
|
||||||
for i in g.singleton_down_edges:
|
|
||||||
bij[f"d{i}"] = apex_of_edge[(i + r) % n]
|
|
||||||
for (i, j) in sorted(g.bites):
|
|
||||||
bij[f"p{i}_{j}"] = apex_of_edge[(i + r) % n]
|
|
||||||
|
|
||||||
# verify the reconstructed graph is edge-faithful to the tread-face M(T)
|
|
||||||
mt_edges = {ekey(*e) for e in M.edges()}
|
|
||||||
rec_edges = {ekey(bij[u], bij[v]) for u, v in g.edges()}
|
|
||||||
if rec_edges != mt_edges:
|
|
||||||
return None
|
|
||||||
return g, bij
|
|
||||||
|
|
||||||
|
|
||||||
def canonical(coloring, ordered):
|
def canonical(coloring, ordered):
|
||||||
remap, out = {}, []
|
remap, out = {}, []
|
||||||
for v in ordered:
|
for v in ordered:
|
||||||
@@ -341,32 +200,29 @@ def iter_pieces(seed: int, color_limit: int = 400000):
|
|||||||
if tread is None or len(tread["up"]) < 3:
|
if tread is None or len(tread["up"]) < 3:
|
||||||
continue
|
continue
|
||||||
mt = medial_tire_facemodel(tread["tread_faces"])
|
mt = medial_tire_facemodel(tread["tread_faces"])
|
||||||
rec = recognise(mt, tread)
|
for comp, (g, bij) in enumerate(recognise(mt, tread)):
|
||||||
if rec is None:
|
mt_nodes = list(bij.values())
|
||||||
continue
|
name_of = {v: k for k, v in bij.items()}
|
||||||
g, bij = rec
|
|
||||||
mt_nodes = list(bij.values())
|
|
||||||
name_of = {v: k for k, v in bij.items()}
|
|
||||||
|
|
||||||
realized = set()
|
realized = set()
|
||||||
for col in global_colorings:
|
for col in global_colorings:
|
||||||
realized.add(canonical({v: col[v] for v in mt_nodes}, mt_nodes))
|
realized.add(canonical({v: col[v] for v in mt_nodes}, mt_nodes))
|
||||||
|
|
||||||
colorings = []
|
colorings = []
|
||||||
seen = set()
|
seen = set()
|
||||||
for col in proper_3_colorings_subgraph(mt, mt_nodes):
|
for col in proper_3_colorings_subgraph(mt, mt_nodes):
|
||||||
key = canonical(col, mt_nodes)
|
key = canonical(col, mt_nodes)
|
||||||
if key in seen:
|
if key in seen:
|
||||||
continue
|
continue
|
||||||
seen.add(key)
|
seen.add(key)
|
||||||
fmt_col = {name_of[v]: c for v, c in col.items()}
|
fmt_col = {name_of[v]: c for v, c in col.items()}
|
||||||
balanced = kempe_classify(g, fmt_col).valid
|
balanced = kempe_classify(g, fmt_col).valid
|
||||||
is_real = key in realized
|
is_real = key in realized
|
||||||
cat = ("Invalid" if not balanced
|
cat = ("Invalid" if not balanced
|
||||||
else "Realized" if is_real else "Unrealized")
|
else "Realized" if is_real else "Unrealized")
|
||||||
colorings.append((fmt_col, cat))
|
colorings.append((fmt_col, cat))
|
||||||
meta = {"source": s, "tread": d}
|
meta = {"source": s, "tread": d, "comp": comp}
|
||||||
yield (meta, g, colorings)
|
yield (meta, g, colorings)
|
||||||
|
|
||||||
|
|
||||||
def analyse(seed: int, color_limit: int = 400000):
|
def analyse(seed: int, color_limit: int = 400000):
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
"""Reusable helpers for medial tire decomposition experiments."""
|
||||||
@@ -0,0 +1,860 @@
|
|||||||
|
"""Draw medial tire decompositions of random 5-connected triangulations.
|
||||||
|
|
||||||
|
The source graphs come from ``plantri -c5`` in graph6 format. For each sampled
|
||||||
|
30-vertex triangulation, this script chooses a random source vertex, builds the
|
||||||
|
BFS depth-component tire tree, and draws both the tire tree and the medial
|
||||||
|
tread model for each depth component.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from collections import defaultdict
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
PAPER_DIR = Path(__file__).resolve().parents[1]
|
||||||
|
REPO_ROOT = PAPER_DIR.parents[1]
|
||||||
|
os.environ.setdefault(
|
||||||
|
"MPLCONFIGDIR", str(PAPER_DIR / "experiments" / ".matplotlib-cache")
|
||||||
|
)
|
||||||
|
os.environ.setdefault("XDG_CACHE_HOME", str(PAPER_DIR / "experiments" / ".cache"))
|
||||||
|
|
||||||
|
import matplotlib
|
||||||
|
|
||||||
|
matplotlib.use("Agg")
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from matplotlib.lines import Line2D
|
||||||
|
from matplotlib.patches import PathPatch
|
||||||
|
from matplotlib.path import Path as MplPath
|
||||||
|
import networkx as nx
|
||||||
|
|
||||||
|
if str(PAPER_DIR) not in sys.path:
|
||||||
|
sys.path.insert(0, str(PAPER_DIR))
|
||||||
|
|
||||||
|
from lib.medial_tire_decomposition import (
|
||||||
|
annular_cycle_components,
|
||||||
|
ekey,
|
||||||
|
medial_tire_facemodel,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class TreadNode:
|
||||||
|
idx: int
|
||||||
|
depth: int
|
||||||
|
face_indices: tuple[int, ...]
|
||||||
|
annular: frozenset
|
||||||
|
up: frozenset
|
||||||
|
down: frozenset
|
||||||
|
bites: frozenset
|
||||||
|
medial: nx.Graph
|
||||||
|
annular_cycles: tuple[tuple, ...]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Augmentation:
|
||||||
|
graph: nx.Graph
|
||||||
|
added_vertices: tuple[int, ...]
|
||||||
|
filled_faces: tuple[tuple[int, tuple[int, int, int], int], ...]
|
||||||
|
|
||||||
|
|
||||||
|
def sample_plantri_graphs(n: int, count: int, seed: int, scan_limit: int) -> list[nx.Graph]:
|
||||||
|
cmd = [str(REPO_ROOT / "plantri" / "plantri"), "-g", "-c5", str(n)]
|
||||||
|
rng = random.Random(seed)
|
||||||
|
sample: list[tuple[int, nx.Graph]] = []
|
||||||
|
seen = 0
|
||||||
|
with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
|
||||||
|
assert proc.stdout is not None
|
||||||
|
for raw in proc.stdout:
|
||||||
|
line = raw.strip()
|
||||||
|
if not line or line.startswith(b">>"):
|
||||||
|
continue
|
||||||
|
graph = nx.from_graph6_bytes(line)
|
||||||
|
if nx.node_connectivity(graph) < 5:
|
||||||
|
continue
|
||||||
|
seen += 1
|
||||||
|
if len(sample) < count:
|
||||||
|
sample.append((seen, graph))
|
||||||
|
else:
|
||||||
|
j = rng.randrange(seen)
|
||||||
|
if j < count:
|
||||||
|
sample[j] = (seen, graph)
|
||||||
|
if seen >= scan_limit:
|
||||||
|
proc.terminate()
|
||||||
|
break
|
||||||
|
proc.wait(timeout=10)
|
||||||
|
if len(sample) < count:
|
||||||
|
raise RuntimeError(f"only found {len(sample)} graphs after scanning {seen}")
|
||||||
|
return [graph for _ordinal, graph in sample]
|
||||||
|
|
||||||
|
|
||||||
|
def triangular_faces(g: nx.Graph):
|
||||||
|
ok, emb = nx.check_planarity(g)
|
||||||
|
if not ok:
|
||||||
|
raise ValueError("not planar")
|
||||||
|
seen = set()
|
||||||
|
faces = []
|
||||||
|
for u, v in list(emb.edges()):
|
||||||
|
if (u, v) in seen:
|
||||||
|
continue
|
||||||
|
face = tuple(emb.traverse_face(u, v, mark_half_edges=seen))
|
||||||
|
faces.append(face)
|
||||||
|
return faces
|
||||||
|
|
||||||
|
|
||||||
|
def edge_face_data(faces):
|
||||||
|
face_edges = []
|
||||||
|
edge_faces: dict[tuple, list[int]] = defaultdict(list)
|
||||||
|
for i, face in enumerate(faces):
|
||||||
|
edges = {
|
||||||
|
ekey(face[0], face[1]),
|
||||||
|
ekey(face[1], face[2]),
|
||||||
|
ekey(face[2], face[0]),
|
||||||
|
}
|
||||||
|
face_edges.append(edges)
|
||||||
|
for edge in edges:
|
||||||
|
edge_faces[edge].append(i)
|
||||||
|
return face_edges, edge_faces
|
||||||
|
|
||||||
|
|
||||||
|
def augment_same_level_faces(g: nx.Graph, source: int) -> Augmentation:
|
||||||
|
"""Stack a new vertex into every facial triangle with one BFS level.
|
||||||
|
|
||||||
|
If a triangular face has all three vertices at level d, the new vertex is
|
||||||
|
adjacent to those three vertices and therefore has level d + 1. This turns
|
||||||
|
the same-level region into three adjacent-level tread faces before the tire
|
||||||
|
decomposition is extracted.
|
||||||
|
"""
|
||||||
|
levels = nx.single_source_shortest_path_length(g, source)
|
||||||
|
faces = triangular_faces(g)
|
||||||
|
augmented = g.copy()
|
||||||
|
next_vertex = max(augmented.nodes()) + 1
|
||||||
|
added = []
|
||||||
|
filled = []
|
||||||
|
|
||||||
|
for face in faces:
|
||||||
|
face_levels = {levels[v] for v in face}
|
||||||
|
if len(face_levels) != 1:
|
||||||
|
continue
|
||||||
|
new_vertex = next_vertex
|
||||||
|
next_vertex += 1
|
||||||
|
augmented.add_node(new_vertex)
|
||||||
|
augmented.add_edges_from((new_vertex, v) for v in face)
|
||||||
|
added.append(new_vertex)
|
||||||
|
filled.append((new_vertex, tuple(face), next(iter(face_levels))))
|
||||||
|
|
||||||
|
return Augmentation(
|
||||||
|
graph=augmented,
|
||||||
|
added_vertices=tuple(added),
|
||||||
|
filled_faces=tuple(filled),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def depth_components(faces, face_edges, edge_faces, levels):
|
||||||
|
depths = [min(levels[v] for v in face) for face in faces]
|
||||||
|
dual_adj: dict[int, set[int]] = defaultdict(set)
|
||||||
|
for incident in edge_faces.values():
|
||||||
|
for a in range(len(incident)):
|
||||||
|
for b in range(a + 1, len(incident)):
|
||||||
|
dual_adj[incident[a]].add(incident[b])
|
||||||
|
dual_adj[incident[b]].add(incident[a])
|
||||||
|
|
||||||
|
comps = []
|
||||||
|
seen = [False] * len(faces)
|
||||||
|
for start in range(len(faces)):
|
||||||
|
if seen[start]:
|
||||||
|
continue
|
||||||
|
depth = depths[start]
|
||||||
|
stack = [start]
|
||||||
|
comp = []
|
||||||
|
seen[start] = True
|
||||||
|
while stack:
|
||||||
|
face = stack.pop()
|
||||||
|
comp.append(face)
|
||||||
|
for other in dual_adj[face]:
|
||||||
|
if not seen[other] and depths[other] == depth:
|
||||||
|
seen[other] = True
|
||||||
|
stack.append(other)
|
||||||
|
comps.append((depth, tuple(sorted(comp))))
|
||||||
|
return comps, depths, dual_adj
|
||||||
|
|
||||||
|
|
||||||
|
def tread_from_component(faces, levels, face_indices):
|
||||||
|
tread_faces = [faces[i] for i in face_indices]
|
||||||
|
if not tread_faces:
|
||||||
|
return None
|
||||||
|
depth = min(min(levels[v] for v in face) for face in tread_faces)
|
||||||
|
annular, up, down = set(), set(), set()
|
||||||
|
face_of_down = defaultdict(int)
|
||||||
|
for face in tread_faces:
|
||||||
|
for x, y in ((face[0], face[1]), (face[1], face[2]), (face[2], face[0])):
|
||||||
|
e = ekey(x, y)
|
||||||
|
lx, ly = levels[x], levels[y]
|
||||||
|
if {lx, ly} == {depth, depth + 1}:
|
||||||
|
annular.add(e)
|
||||||
|
elif lx == ly == depth:
|
||||||
|
up.add(e)
|
||||||
|
elif lx == ly == depth + 1:
|
||||||
|
down.add(e)
|
||||||
|
face_of_down[e] += 1
|
||||||
|
if len(annular) < 3:
|
||||||
|
return None
|
||||||
|
return {
|
||||||
|
"tread_faces": tread_faces,
|
||||||
|
"annular": annular,
|
||||||
|
"up": up,
|
||||||
|
"down": down,
|
||||||
|
"bites": {e for e in down if face_of_down[e] == 2},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def build_tire_tree(g: nx.Graph, source: int, augment: bool = True):
|
||||||
|
augmentation = augment_same_level_faces(g, source) if augment else Augmentation(g, (), ())
|
||||||
|
work_graph = augmentation.graph
|
||||||
|
faces = triangular_faces(work_graph)
|
||||||
|
face_edges, edge_faces = edge_face_data(faces)
|
||||||
|
levels = nx.single_source_shortest_path_length(work_graph, source)
|
||||||
|
comps, depths, dual_adj = depth_components(faces, face_edges, edge_faces, levels)
|
||||||
|
comp_of_face = {}
|
||||||
|
for comp_idx, (_depth, face_indices) in enumerate(comps):
|
||||||
|
for face in face_indices:
|
||||||
|
comp_of_face[face] = comp_idx
|
||||||
|
|
||||||
|
nodes: list[TreadNode] = []
|
||||||
|
comp_to_node = {}
|
||||||
|
for comp_idx, (depth, face_indices) in enumerate(comps):
|
||||||
|
tread = tread_from_component(faces, levels, face_indices)
|
||||||
|
if tread is None or len(tread["up"]) < 3:
|
||||||
|
continue
|
||||||
|
mt = medial_tire_facemodel(tread["tread_faces"])
|
||||||
|
annular_cycles = tuple(annular_cycle_components(mt, tread["annular"]))
|
||||||
|
if not annular_cycles:
|
||||||
|
continue
|
||||||
|
node = TreadNode(
|
||||||
|
idx=len(nodes),
|
||||||
|
depth=depth,
|
||||||
|
face_indices=face_indices,
|
||||||
|
annular=frozenset(tread["annular"]),
|
||||||
|
up=frozenset(tread["up"]),
|
||||||
|
down=frozenset(tread["down"]),
|
||||||
|
bites=frozenset(tread["bites"]),
|
||||||
|
medial=mt,
|
||||||
|
annular_cycles=annular_cycles,
|
||||||
|
)
|
||||||
|
comp_to_node[comp_idx] = node.idx
|
||||||
|
nodes.append(node)
|
||||||
|
|
||||||
|
tree_edges = set()
|
||||||
|
for comp_idx, (depth, face_indices) in enumerate(comps):
|
||||||
|
if comp_idx not in comp_to_node:
|
||||||
|
continue
|
||||||
|
child = comp_to_node[comp_idx]
|
||||||
|
parent_candidates = set()
|
||||||
|
for face in face_indices:
|
||||||
|
for other in dual_adj[face]:
|
||||||
|
other_comp = comp_of_face[other]
|
||||||
|
if depths[other] == depth - 1 and other_comp in comp_to_node:
|
||||||
|
parent_candidates.add(comp_to_node[other_comp])
|
||||||
|
for parent in parent_candidates:
|
||||||
|
tree_edges.add((parent, child))
|
||||||
|
return augmentation, faces, levels, nodes, sorted(tree_edges)
|
||||||
|
|
||||||
|
|
||||||
|
def graph_layout(g: nx.Graph):
|
||||||
|
try:
|
||||||
|
return nx.planar_layout(g)
|
||||||
|
except nx.NetworkXException:
|
||||||
|
return nx.spring_layout(g, seed=0)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_base_graph(ax, g, levels, source, added_vertices=()):
|
||||||
|
pos = graph_layout(g)
|
||||||
|
max_level = max(levels.values())
|
||||||
|
cmap = plt.get_cmap("viridis", max_level + 1)
|
||||||
|
node_colors = [cmap(levels[v]) for v in g.nodes()]
|
||||||
|
nx.draw_networkx_edges(g, pos, ax=ax, edge_color="#cbd5e1", width=0.8)
|
||||||
|
added_set = set(added_vertices)
|
||||||
|
nx.draw_networkx_nodes(
|
||||||
|
g,
|
||||||
|
pos,
|
||||||
|
ax=ax,
|
||||||
|
node_color=node_colors,
|
||||||
|
node_size=[
|
||||||
|
150 if v == source else 96 if v in added_set else 72
|
||||||
|
for v in g.nodes()
|
||||||
|
],
|
||||||
|
edgecolors=[
|
||||||
|
"#dc2626" if v == source else "#7c3aed" if v in added_set else "#111827"
|
||||||
|
for v in g.nodes()
|
||||||
|
],
|
||||||
|
linewidths=[
|
||||||
|
1.8 if v == source else 1.2 if v in added_set else 0.45
|
||||||
|
for v in g.nodes()
|
||||||
|
],
|
||||||
|
)
|
||||||
|
labels = {v: str(v) for v in g.nodes()}
|
||||||
|
nx.draw_networkx_labels(g, pos, labels=labels, ax=ax, font_size=5)
|
||||||
|
ax.set_title(
|
||||||
|
f"Augmented G, source {source}; vertex levels 0..{max_level}",
|
||||||
|
fontsize=10,
|
||||||
|
)
|
||||||
|
ax.set_aspect("equal")
|
||||||
|
ax.axis("off")
|
||||||
|
|
||||||
|
|
||||||
|
def tree_positions(nodes: list[TreadNode], tree_edges):
|
||||||
|
children: dict[int, list[int]] = defaultdict(list)
|
||||||
|
has_parent = set()
|
||||||
|
for parent, child in tree_edges:
|
||||||
|
children[parent].append(child)
|
||||||
|
has_parent.add(child)
|
||||||
|
roots = [node.idx for node in nodes if node.idx not in has_parent]
|
||||||
|
for child_list in children.values():
|
||||||
|
child_list.sort(key=lambda idx: (nodes[idx].depth, idx))
|
||||||
|
|
||||||
|
x_counter = 0
|
||||||
|
pos = {}
|
||||||
|
|
||||||
|
def place(idx, depth):
|
||||||
|
nonlocal x_counter
|
||||||
|
if not children[idx]:
|
||||||
|
pos[idx] = (x_counter, -depth)
|
||||||
|
x_counter += 1
|
||||||
|
return pos[idx][0]
|
||||||
|
xs = [place(child, depth + 1) for child in children[idx]]
|
||||||
|
x = sum(xs) / len(xs)
|
||||||
|
pos[idx] = (x, -depth)
|
||||||
|
return x
|
||||||
|
|
||||||
|
for root in sorted(roots, key=lambda idx: (nodes[idx].depth, idx)):
|
||||||
|
place(root, 0)
|
||||||
|
x_counter += 1
|
||||||
|
return pos
|
||||||
|
|
||||||
|
|
||||||
|
def draw_tire_tree(ax, nodes: list[TreadNode], tree_edges):
|
||||||
|
pos = tree_positions(nodes, tree_edges)
|
||||||
|
for parent, child in tree_edges:
|
||||||
|
x0, y0 = pos[parent]
|
||||||
|
x1, y1 = pos[child]
|
||||||
|
ax.plot([x0, x1], [y0, y1], color="#374151", lw=1.0, zorder=1)
|
||||||
|
for node in nodes:
|
||||||
|
x, y = pos[node.idx]
|
||||||
|
ax.text(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
f"T{node.idx}\nd={node.depth}\n{len(node.annular_cycles)} cycle(s)",
|
||||||
|
ha="center",
|
||||||
|
va="center",
|
||||||
|
fontsize=8,
|
||||||
|
bbox={
|
||||||
|
"boxstyle": "round,pad=0.32",
|
||||||
|
"facecolor": "#fef3c7",
|
||||||
|
"edgecolor": "#111827",
|
||||||
|
"linewidth": 0.9,
|
||||||
|
},
|
||||||
|
zorder=3,
|
||||||
|
)
|
||||||
|
ax.set_title("Depth-component tire tree", fontsize=10)
|
||||||
|
if pos:
|
||||||
|
xs = [p[0] for p in pos.values()]
|
||||||
|
ys = [p[1] for p in pos.values()]
|
||||||
|
ax.set_xlim(min(xs) - 1.0, max(xs) + 1.0)
|
||||||
|
ax.set_ylim(min(ys) - 0.7, max(ys) + 0.7)
|
||||||
|
ax.axis("off")
|
||||||
|
|
||||||
|
|
||||||
|
def vertex_xy(k: int, n: int, radius: float, rotation: float = 0.0) -> tuple[float, float]:
|
||||||
|
angle = math.pi / 2 + rotation - 2 * math.pi * k / n
|
||||||
|
return radius * math.cos(angle), radius * math.sin(angle)
|
||||||
|
|
||||||
|
|
||||||
|
def edge_midpoint_angle(i: int, n: int, rotation: float = 0.0) -> float:
|
||||||
|
return math.pi / 2 + rotation - 2 * math.pi * (i + 0.5) / n
|
||||||
|
|
||||||
|
|
||||||
|
def annular_cycle_edges(node: TreadNode) -> set[tuple]:
|
||||||
|
edges = set()
|
||||||
|
for order in node.annular_cycles:
|
||||||
|
for i, a in enumerate(order):
|
||||||
|
b = order[(i + 1) % len(order)]
|
||||||
|
edges.add(tuple(sorted((a, b))))
|
||||||
|
return edges
|
||||||
|
|
||||||
|
|
||||||
|
def shared_up_apex_occurrences(node: TreadNode) -> dict[tuple, list[tuple[int, int]]]:
|
||||||
|
occurrences: dict[tuple, list[tuple[int, int]]] = defaultdict(list)
|
||||||
|
annular = set(node.annular)
|
||||||
|
for cycle_idx, order in enumerate(node.annular_cycles):
|
||||||
|
n = len(order)
|
||||||
|
for i, a in enumerate(order):
|
||||||
|
b = order[(i + 1) % n]
|
||||||
|
apexes = [
|
||||||
|
w for w in set(node.medial.neighbors(a)) & set(node.medial.neighbors(b))
|
||||||
|
if w not in annular
|
||||||
|
]
|
||||||
|
for apex in apexes:
|
||||||
|
if apex in node.up:
|
||||||
|
occurrences[apex].append((cycle_idx, i))
|
||||||
|
return {apex: where for apex, where in occurrences.items() if len(where) > 1}
|
||||||
|
|
||||||
|
|
||||||
|
def compound_cycle_rotations(node: TreadNode) -> list[float]:
|
||||||
|
rotations = [0.0] * len(node.annular_cycles)
|
||||||
|
shared = shared_up_apex_occurrences(node)
|
||||||
|
by_cycle: dict[int, list[int]] = defaultdict(list)
|
||||||
|
for occurrences in shared.values():
|
||||||
|
for cycle_idx, edge_idx in occurrences:
|
||||||
|
by_cycle[cycle_idx].append(edge_idx)
|
||||||
|
|
||||||
|
for cycle_idx, edge_indices in by_cycle.items():
|
||||||
|
n = len(node.annular_cycles[cycle_idx])
|
||||||
|
sx = sy = 0.0
|
||||||
|
for edge_idx in edge_indices:
|
||||||
|
angle = edge_midpoint_angle(edge_idx, n)
|
||||||
|
sx += math.cos(angle)
|
||||||
|
sy += math.sin(angle)
|
||||||
|
if sx or sy:
|
||||||
|
rotations[cycle_idx] = math.pi / 2 - math.atan2(sy, sx)
|
||||||
|
return rotations
|
||||||
|
|
||||||
|
|
||||||
|
def compound_cycle_order(node: TreadNode) -> list[int]:
|
||||||
|
shared = shared_up_apex_occurrences(node)
|
||||||
|
adj: dict[int, set[int]] = defaultdict(set)
|
||||||
|
for occurrences in shared.values():
|
||||||
|
cycles = sorted({cycle_idx for cycle_idx, _edge_idx in occurrences})
|
||||||
|
for a, b in zip(cycles, cycles[1:]):
|
||||||
|
adj[a].add(b)
|
||||||
|
adj[b].add(a)
|
||||||
|
|
||||||
|
remaining = set(range(len(node.annular_cycles)))
|
||||||
|
order = []
|
||||||
|
while remaining:
|
||||||
|
candidates = sorted(
|
||||||
|
remaining,
|
||||||
|
key=lambda idx: (len(adj[idx]) if adj[idx] else 999, idx),
|
||||||
|
)
|
||||||
|
start = candidates[0]
|
||||||
|
stack = [(start, None)]
|
||||||
|
while stack:
|
||||||
|
current, parent = stack.pop()
|
||||||
|
if current not in remaining:
|
||||||
|
continue
|
||||||
|
remaining.remove(current)
|
||||||
|
order.append(current)
|
||||||
|
children = sorted(
|
||||||
|
(neighbor for neighbor in adj[current] if neighbor != parent),
|
||||||
|
key=lambda idx: (len(adj[idx]), idx),
|
||||||
|
reverse=True,
|
||||||
|
)
|
||||||
|
for child in children:
|
||||||
|
stack.append((child, current))
|
||||||
|
return order
|
||||||
|
|
||||||
|
|
||||||
|
def cycle_layout(
|
||||||
|
node: TreadNode,
|
||||||
|
rotations: list[float],
|
||||||
|
display_order: list[int] | None = None,
|
||||||
|
cycle_spacing: float = 3.25,
|
||||||
|
):
|
||||||
|
cycle_count = len(node.annular_cycles)
|
||||||
|
if display_order is None:
|
||||||
|
display_order = list(range(cycle_count))
|
||||||
|
rank = {cycle_idx: i for i, cycle_idx in enumerate(display_order)}
|
||||||
|
offsets = [cycle_spacing * (rank[i] - (cycle_count - 1) / 2) for i in range(cycle_count)]
|
||||||
|
ann_positions: dict[tuple[int, tuple], tuple[float, float]] = {}
|
||||||
|
apex_positions: dict[tuple[int, tuple], tuple[float, float]] = {}
|
||||||
|
apex_corners: dict[tuple[int, tuple], list[tuple[float, float]]] = defaultdict(list)
|
||||||
|
|
||||||
|
for cycle_idx, order in enumerate(node.annular_cycles):
|
||||||
|
n = len(order)
|
||||||
|
dx = offsets[cycle_idx]
|
||||||
|
rotation = rotations[cycle_idx]
|
||||||
|
for k, vertex in enumerate(order):
|
||||||
|
x, y = vertex_xy(k, n, 1.0, rotation)
|
||||||
|
ann_positions[(cycle_idx, vertex)] = (dx + x, y)
|
||||||
|
|
||||||
|
for i, a in enumerate(order):
|
||||||
|
b = order[(i + 1) % n]
|
||||||
|
apexes = [
|
||||||
|
w for w in set(node.medial.neighbors(a)) & set(node.medial.neighbors(b))
|
||||||
|
if w not in node.annular
|
||||||
|
]
|
||||||
|
for apex in apexes:
|
||||||
|
key = (cycle_idx, apex)
|
||||||
|
apex_corners[key].extend([
|
||||||
|
ann_positions[(cycle_idx, a)],
|
||||||
|
ann_positions[(cycle_idx, b)],
|
||||||
|
])
|
||||||
|
if key in apex_positions:
|
||||||
|
continue
|
||||||
|
angle = edge_midpoint_angle(i, n, rotation)
|
||||||
|
radius = 1.42 if apex in node.up else 0.58
|
||||||
|
apex_positions[key] = (
|
||||||
|
dx + radius * math.cos(angle),
|
||||||
|
radius * math.sin(angle),
|
||||||
|
)
|
||||||
|
|
||||||
|
for key, corners in apex_corners.items():
|
||||||
|
_cycle_idx, apex = key
|
||||||
|
if apex in node.bites and corners:
|
||||||
|
cx = sum(p[0] for p in corners) / len(corners)
|
||||||
|
cy = sum(p[1] for p in corners) / len(corners)
|
||||||
|
apex_positions[key] = (0.82 * cx, 0.82 * cy)
|
||||||
|
|
||||||
|
return offsets, ann_positions, apex_positions, apex_corners
|
||||||
|
|
||||||
|
|
||||||
|
def orientation(a, b, c) -> float:
|
||||||
|
return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0])
|
||||||
|
|
||||||
|
|
||||||
|
def segments_cross(a, b, c, d) -> bool:
|
||||||
|
eps = 1e-9
|
||||||
|
if max(a[0], b[0]) + eps < min(c[0], d[0]):
|
||||||
|
return False
|
||||||
|
if max(c[0], d[0]) + eps < min(a[0], b[0]):
|
||||||
|
return False
|
||||||
|
if max(a[1], b[1]) + eps < min(c[1], d[1]):
|
||||||
|
return False
|
||||||
|
if max(c[1], d[1]) + eps < min(a[1], b[1]):
|
||||||
|
return False
|
||||||
|
o1 = orientation(a, b, c)
|
||||||
|
o2 = orientation(a, b, d)
|
||||||
|
o3 = orientation(c, d, a)
|
||||||
|
o4 = orientation(c, d, b)
|
||||||
|
return (o1 * o2 < -eps) and (o3 * o4 < -eps)
|
||||||
|
|
||||||
|
|
||||||
|
def polylines_cross(first, second) -> bool:
|
||||||
|
for i in range(len(first) - 1):
|
||||||
|
for j in range(len(second) - 1):
|
||||||
|
if segments_cross(first[i], first[i + 1], second[j], second[j + 1]):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def quadratic_points(start, control, end, samples: int = 40):
|
||||||
|
points = []
|
||||||
|
for k in range(samples + 1):
|
||||||
|
t = k / samples
|
||||||
|
x = (1 - t) ** 2 * start[0] + 2 * (1 - t) * t * control[0] + t ** 2 * end[0]
|
||||||
|
y = (1 - t) ** 2 * start[1] + 2 * (1 - t) * t * control[1] + t ** 2 * end[1]
|
||||||
|
points.append((x, y))
|
||||||
|
return points
|
||||||
|
|
||||||
|
|
||||||
|
def crossing_free_shared_arcs(
|
||||||
|
node: TreadNode,
|
||||||
|
apex_positions,
|
||||||
|
top_y: float,
|
||||||
|
bottom_y: float,
|
||||||
|
):
|
||||||
|
shared = shared_up_apex_occurrences(node)
|
||||||
|
arc_specs = []
|
||||||
|
routed = []
|
||||||
|
pairs = []
|
||||||
|
for apex, occurrences in shared.items():
|
||||||
|
ordered = sorted(occurrences, key=lambda item: apex_positions[(item[0], apex)][0])
|
||||||
|
for left, right in zip(ordered, ordered[1:]):
|
||||||
|
pairs.append((apex, left, right))
|
||||||
|
|
||||||
|
pairs.sort(key=lambda item: (
|
||||||
|
abs(apex_positions[(item[1][0], item[0])][0] - apex_positions[(item[2][0], item[0])][0]),
|
||||||
|
min(item[1][0], item[2][0]),
|
||||||
|
item[0],
|
||||||
|
))
|
||||||
|
|
||||||
|
for arc_idx, (apex, left, right) in enumerate(pairs):
|
||||||
|
start = apex_positions[(left[0], apex)]
|
||||||
|
end = apex_positions[(right[0], apex)]
|
||||||
|
if start[0] > end[0]:
|
||||||
|
start, end = end, start
|
||||||
|
for lane in range(64):
|
||||||
|
candidates = [
|
||||||
|
((start[0] + end[0]) / 2, top_y + 0.42 + 0.18 * lane),
|
||||||
|
((start[0] + end[0]) / 2, bottom_y - 0.42 - 0.18 * lane),
|
||||||
|
]
|
||||||
|
for control in candidates:
|
||||||
|
points = quadratic_points(start, control, end)
|
||||||
|
if not any(polylines_cross(points, existing) for existing in routed):
|
||||||
|
routed.append(points)
|
||||||
|
arc_specs.append((start, control, end))
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
control = ((start[0] + end[0]) / 2, top_y + 0.42 + 0.18 * (64 + arc_idx))
|
||||||
|
routed.append(quadratic_points(start, control, end))
|
||||||
|
arc_specs.append((start, control, end))
|
||||||
|
return arc_specs
|
||||||
|
|
||||||
|
|
||||||
|
def draw_tread_cycles(ax, node: TreadNode, connect_shared: bool):
|
||||||
|
rotations = compound_cycle_rotations(node) if connect_shared else [0.0] * len(node.annular_cycles)
|
||||||
|
display_order = compound_cycle_order(node) if connect_shared else None
|
||||||
|
offsets, ann_positions, apex_positions, apex_corners = cycle_layout(
|
||||||
|
node, rotations, display_order=display_order
|
||||||
|
)
|
||||||
|
|
||||||
|
for cycle_idx, order in enumerate(node.annular_cycles):
|
||||||
|
cyc_x = [ann_positions[(cycle_idx, v)][0] for v in order] + [
|
||||||
|
ann_positions[(cycle_idx, order[0])][0]
|
||||||
|
]
|
||||||
|
cyc_y = [ann_positions[(cycle_idx, v)][1] for v in order] + [
|
||||||
|
ann_positions[(cycle_idx, order[0])][1]
|
||||||
|
]
|
||||||
|
ax.plot(cyc_x, cyc_y, color="black", lw=1.3, zorder=2)
|
||||||
|
|
||||||
|
for key, corners in apex_corners.items():
|
||||||
|
pos = apex_positions[key]
|
||||||
|
for corner in corners:
|
||||||
|
ax.plot([pos[0], corner[0]], [pos[1], corner[1]], color="#9ca3af", lw=0.5)
|
||||||
|
|
||||||
|
if connect_shared and apex_positions:
|
||||||
|
all_positions = list(ann_positions.values()) + list(apex_positions.values())
|
||||||
|
top_y = max(p[1] for p in all_positions)
|
||||||
|
bottom_y = min(p[1] for p in all_positions)
|
||||||
|
for start, control, end in crossing_free_shared_arcs(
|
||||||
|
node, apex_positions, top_y, bottom_y
|
||||||
|
):
|
||||||
|
path = MplPath([start, control, end], [MplPath.MOVETO, MplPath.CURVE3, MplPath.CURVE3])
|
||||||
|
ax.add_patch(
|
||||||
|
PathPatch(
|
||||||
|
path,
|
||||||
|
facecolor="none",
|
||||||
|
edgecolor="#475569",
|
||||||
|
lw=0.8,
|
||||||
|
linestyle=(0, (1.2, 2.0)),
|
||||||
|
zorder=1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
for (_cycle_idx, apex), pos in apex_positions.items():
|
||||||
|
if apex in node.up:
|
||||||
|
color, size, edgecolor = "#2563eb", 13, "none"
|
||||||
|
elif apex in node.bites:
|
||||||
|
color, size, edgecolor = "#7f1d1d", 24, "black"
|
||||||
|
else:
|
||||||
|
color, size, edgecolor = "#dc2626", 13, "none"
|
||||||
|
ax.scatter(
|
||||||
|
[pos[0]],
|
||||||
|
[pos[1]],
|
||||||
|
s=size,
|
||||||
|
color=color,
|
||||||
|
edgecolors=edgecolor,
|
||||||
|
linewidths=0.4,
|
||||||
|
zorder=3,
|
||||||
|
)
|
||||||
|
|
||||||
|
if ann_positions:
|
||||||
|
ax.scatter(
|
||||||
|
[p[0] for p in ann_positions.values()],
|
||||||
|
[p[1] for p in ann_positions.values()],
|
||||||
|
s=9,
|
||||||
|
color="black",
|
||||||
|
zorder=4,
|
||||||
|
)
|
||||||
|
|
||||||
|
xs = [p[0] for p in list(ann_positions.values()) + list(apex_positions.values())]
|
||||||
|
ys = [p[1] for p in list(ann_positions.values()) + list(apex_positions.values())]
|
||||||
|
if connect_shared:
|
||||||
|
ys.append(max(ys) + 1.2)
|
||||||
|
ys.append(min(ys) - 1.2)
|
||||||
|
xpad = 1.1 if connect_shared else 1.7
|
||||||
|
ypad = 0.25 if connect_shared else 0.0
|
||||||
|
ax.set_xlim(min(xs, default=min(offsets, default=0.0)) - xpad, max(xs, default=max(offsets, default=0.0)) + xpad)
|
||||||
|
ax.set_ylim(min(ys, default=-1.65) - 0.25, max(ys, default=1.65) + ypad)
|
||||||
|
ax.set_aspect("equal")
|
||||||
|
ax.axis("off")
|
||||||
|
|
||||||
|
|
||||||
|
def draw_tread_model(ax, node: TreadNode):
|
||||||
|
if len(node.annular_cycles) > 1:
|
||||||
|
draw_tread_cycles(ax, node, connect_shared=True)
|
||||||
|
singleton_down = set(node.down) - set(node.bites)
|
||||||
|
ax.set_title(
|
||||||
|
f"T{node.idx} d={node.depth}: {len(node.annular_cycles)} annular cycle(s)\n"
|
||||||
|
f"ann={len(node.annular)} up={len(node.up)} down={len(singleton_down)} "
|
||||||
|
f"bite={len(node.bites)}",
|
||||||
|
fontsize=6.4,
|
||||||
|
pad=1.5,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
draw_tread_cycles(ax, node, connect_shared=False)
|
||||||
|
|
||||||
|
singleton_down = set(node.down) - set(node.bites)
|
||||||
|
ax.set_title(
|
||||||
|
f"T{node.idx} d={node.depth}: {len(node.annular_cycles)} annular cycle(s)\n"
|
||||||
|
f"ann={len(node.annular)} up={len(node.up)} down={len(singleton_down)} "
|
||||||
|
f"bite={len(node.bites)}",
|
||||||
|
fontsize=6.4,
|
||||||
|
pad=1.5,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_medial_tire_grid(fig, outer_spec, nodes):
|
||||||
|
if not nodes:
|
||||||
|
ax = fig.add_subplot(outer_spec)
|
||||||
|
ax.text(0.5, 0.5, "No medial treads extracted", ha="center")
|
||||||
|
ax.axis("off")
|
||||||
|
return
|
||||||
|
cols = min(3, max(1, math.ceil(math.sqrt(len(nodes)))))
|
||||||
|
rows = math.ceil(len(nodes) / cols)
|
||||||
|
sub = outer_spec.subgridspec(rows, cols, wspace=0.08, hspace=0.35)
|
||||||
|
for i in range(rows * cols):
|
||||||
|
ax = fig.add_subplot(sub[i // cols, i % cols])
|
||||||
|
if i < len(nodes):
|
||||||
|
draw_tread_model(ax, nodes[i])
|
||||||
|
else:
|
||||||
|
ax.axis("off")
|
||||||
|
|
||||||
|
|
||||||
|
def write_index(
|
||||||
|
path: Path,
|
||||||
|
graph_idx: int,
|
||||||
|
source: int,
|
||||||
|
original_graph: nx.Graph,
|
||||||
|
augmentation: Augmentation,
|
||||||
|
nodes,
|
||||||
|
tree_edges,
|
||||||
|
):
|
||||||
|
g = augmentation.graph
|
||||||
|
lines = [
|
||||||
|
f"# Random medial tire decomposition {graph_idx}",
|
||||||
|
"",
|
||||||
|
f"- original vertices: {original_graph.number_of_nodes()}",
|
||||||
|
f"- original edges: {original_graph.number_of_edges()}",
|
||||||
|
f"- original node connectivity: {nx.node_connectivity(original_graph)}",
|
||||||
|
f"- augmented vertices: {g.number_of_nodes()}",
|
||||||
|
f"- augmented edges: {g.number_of_edges()}",
|
||||||
|
f"- same-level faces filled: {len(augmentation.added_vertices)}",
|
||||||
|
f"- source vertex: {source}",
|
||||||
|
f"- tire-tree nodes: {len(nodes)}",
|
||||||
|
f"- tire-tree edges: {len(tree_edges)}",
|
||||||
|
"",
|
||||||
|
"| node | depth | faces | annular cycles | annular | up | singleton down | bite apexes |",
|
||||||
|
"|--:|--:|--:|--:|--:|--:|--:|--:|",
|
||||||
|
]
|
||||||
|
for node in nodes:
|
||||||
|
singleton_down = set(node.down) - set(node.bites)
|
||||||
|
lines.append(
|
||||||
|
f"| T{node.idx} | {node.depth} | {len(node.face_indices)} | "
|
||||||
|
f"{len(node.annular_cycles)} | {len(node.annular)} | {len(node.up)} | "
|
||||||
|
f"{len(singleton_down)} | {len(node.bites)} |"
|
||||||
|
)
|
||||||
|
path.write_text("\n".join(lines) + "\n")
|
||||||
|
|
||||||
|
|
||||||
|
def draw_case(out_dir: Path, graph_idx: int, g: nx.Graph, source: int, augment: bool = True):
|
||||||
|
augmentation, _faces, levels, nodes, tree_edges = build_tire_tree(g, source, augment=augment)
|
||||||
|
work_graph = augmentation.graph
|
||||||
|
fig = plt.figure(figsize=(17, 10))
|
||||||
|
spec = fig.add_gridspec(2, 2, width_ratios=[1.15, 1.0], height_ratios=[1.0, 1.25])
|
||||||
|
ax_graph = fig.add_subplot(spec[0, 0])
|
||||||
|
ax_tree = fig.add_subplot(spec[1, 0])
|
||||||
|
draw_base_graph(ax_graph, work_graph, levels, source, augmentation.added_vertices)
|
||||||
|
draw_tire_tree(ax_tree, nodes, tree_edges)
|
||||||
|
draw_medial_tire_grid(fig, spec[:, 1], nodes)
|
||||||
|
fig.suptitle(
|
||||||
|
f"Random 5-connected maximal planar graph {graph_idx}: "
|
||||||
|
f"n={g.number_of_nodes()} (+{len(augmentation.added_vertices)}), "
|
||||||
|
f"source={source}",
|
||||||
|
fontsize=13,
|
||||||
|
)
|
||||||
|
legend = [
|
||||||
|
Line2D([0], [0], marker="o", color="w", label="source",
|
||||||
|
markerfacecolor="#fde68a", markeredgecolor="#dc2626", markersize=8),
|
||||||
|
Line2D([0], [0], marker="o", color="w", label="inserted vertex",
|
||||||
|
markerfacecolor="#fde68a", markeredgecolor="#7c3aed", markersize=8),
|
||||||
|
Line2D([0], [0], color="black", lw=1.3, label="annular cycle A(T)"),
|
||||||
|
Line2D([0], [0], color="#475569", lw=0.8, linestyle=(0, (1.2, 2.0)),
|
||||||
|
label="shared up apex"),
|
||||||
|
Line2D([0], [0], marker="o", color="w", label="up tooth",
|
||||||
|
markerfacecolor="#2563eb", markersize=6),
|
||||||
|
Line2D([0], [0], marker="o", color="w", label="down tooth",
|
||||||
|
markerfacecolor="#dc2626", markersize=6),
|
||||||
|
Line2D([0], [0], marker="o", color="w", label="bite apex",
|
||||||
|
markerfacecolor="#7f1d1d", markeredgecolor="black", markersize=6),
|
||||||
|
]
|
||||||
|
fig.legend(handles=legend, loc="lower center", ncol=6, fontsize=9)
|
||||||
|
fig.subplots_adjust(left=0.03, right=0.99, top=0.92, bottom=0.08, wspace=0.08, hspace=0.16)
|
||||||
|
|
||||||
|
png = out_dir / f"random_c5_n30_medial_tire_decomposition_{graph_idx}.png"
|
||||||
|
pdf = out_dir / f"random_c5_n30_medial_tire_decomposition_{graph_idx}.pdf"
|
||||||
|
fig.savefig(png, dpi=180)
|
||||||
|
fig.savefig(pdf)
|
||||||
|
plt.close(fig)
|
||||||
|
write_index(
|
||||||
|
out_dir / f"random_c5_n30_medial_tire_decomposition_{graph_idx}.md",
|
||||||
|
graph_idx,
|
||||||
|
source,
|
||||||
|
g,
|
||||||
|
augmentation,
|
||||||
|
nodes,
|
||||||
|
tree_edges,
|
||||||
|
)
|
||||||
|
return png, pdf, len(nodes), sum(len(node.annular_cycles) for node in nodes)
|
||||||
|
|
||||||
|
|
||||||
|
def run(args: argparse.Namespace):
|
||||||
|
out_dir = Path(args.out_dir)
|
||||||
|
out_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
if args.graph6:
|
||||||
|
graph = nx.from_graph6_bytes(args.graph6.encode())
|
||||||
|
if nx.node_connectivity(graph) < 5:
|
||||||
|
raise ValueError("--graph6 graph must be 5-connected")
|
||||||
|
graphs = [graph]
|
||||||
|
if args.source is None:
|
||||||
|
raise ValueError("--source is required with --graph6")
|
||||||
|
sources = [args.source]
|
||||||
|
else:
|
||||||
|
graphs = sample_plantri_graphs(args.n, args.count, args.seed, args.scan_limit)
|
||||||
|
rng = random.Random(args.seed + 101)
|
||||||
|
sources = [rng.choice(list(graph.nodes())) for graph in graphs]
|
||||||
|
|
||||||
|
for i, (graph, source) in enumerate(zip(graphs, sources), start=1):
|
||||||
|
png, pdf, node_count, annular_cycle_count = draw_case(
|
||||||
|
out_dir, i, graph, source, augment=not args.no_augment_same_level_faces
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f"case {i}: source={source}, connectivity={nx.node_connectivity(graph)}, "
|
||||||
|
f"tire nodes={node_count}, annular cycles={annular_cycle_count}"
|
||||||
|
)
|
||||||
|
print(f" wrote {png}")
|
||||||
|
print(f" wrote {pdf}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--n", type=int, default=30)
|
||||||
|
parser.add_argument("--count", type=int, default=2)
|
||||||
|
parser.add_argument("--seed", type=int, default=20260615)
|
||||||
|
parser.add_argument("--scan-limit", type=int, default=500)
|
||||||
|
parser.add_argument("--graph6", help="draw this graph6 graph instead of sampling")
|
||||||
|
parser.add_argument("--source", type=int, help="source vertex for --graph6")
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-augment-same-level-faces",
|
||||||
|
action="store_true",
|
||||||
|
help="skip the same-level-face vertex insertion step",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--out-dir",
|
||||||
|
default=str(PAPER_DIR / "experiments" / "random_medial_tire_decompositions"),
|
||||||
|
)
|
||||||
|
run(parser.parse_args())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,312 @@
|
|||||||
|
"""Exhaustive generator for full medial tire graphs, indexed by |A(T)|.
|
||||||
|
|
||||||
|
Model (Definitions/Remarks 3.1--3.9 of the medial tire decompositions paper).
|
||||||
|
|
||||||
|
* The annular medial vertices induce a cycle A(T), the *annular cycle*
|
||||||
|
(Theorem 3.3). Write n = |A(T)| for its number of vertices = number of
|
||||||
|
annular faces = number of annular edges e_0,...,e_{n-1}.
|
||||||
|
|
||||||
|
* Each edge e_i of A(T) carries exactly one tooth (a triangle of M(T))
|
||||||
|
whose third vertex is a non-annular apex (Definition 3.4). A tooth is an
|
||||||
|
*up tooth* (apex in the outer region) or a *down tooth* (apex in the inner
|
||||||
|
region). We record the tooth types as a word in {U, D}^n.
|
||||||
|
|
||||||
|
* No two up teeth share an apex; at most two down teeth share an apex
|
||||||
|
(Remark 3.5). Two down teeth sharing an apex form a *bite* (Definition
|
||||||
|
3.7). So the down teeth are partitioned into singletons and bite pairs.
|
||||||
|
A bite pairs two down-edges and is drawn as an apex inside the disk with
|
||||||
|
spokes to the four endpoints; bites must be mutually non-crossing, i.e.
|
||||||
|
the bite pairs form a non-crossing (laminar) matching of the down-edges.
|
||||||
|
The two annular edges of a bite must be non-incident (Definition 3.7):
|
||||||
|
they share no annular vertex, so cyclically adjacent edges cannot pair.
|
||||||
|
|
||||||
|
* There are at least three up teeth (Remark 3.6).
|
||||||
|
|
||||||
|
* Bite-face condition (Remark 3.8). Let B(T) = A(T) together with the bite
|
||||||
|
apexes. Its interior non-tooth faces are the root face plus one inner-gap
|
||||||
|
face per bite. A singleton down tooth lies in the innermost bite enclosing
|
||||||
|
its edge (or in the root face if none). For every interior non-tooth face
|
||||||
|
the number of down-tooth apexes lying in that face must be 0 or at least 3.
|
||||||
|
Equivalently: no face holds exactly one or two singleton down teeth.
|
||||||
|
|
||||||
|
The generator enumerates, for a given n, every (tooth word, bite matching)
|
||||||
|
pair satisfying these properties and emits the resulting full medial tire
|
||||||
|
graph as an explicit vertex/edge structure. Configurations may optionally be
|
||||||
|
reduced modulo the dihedral symmetry of the cycle.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import itertools
|
||||||
|
from collections import defaultdict
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from functools import lru_cache
|
||||||
|
from typing import Iterator
|
||||||
|
|
||||||
|
# A bite is an unordered pair of down-edge indices (i, j) with i < j.
|
||||||
|
Bite = tuple[int, int]
|
||||||
|
Matching = frozenset[Bite]
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Non-crossing (laminar) matchings of the down edges.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@lru_cache(maxsize=None)
|
||||||
|
def noncrossing_matchings(positions: tuple[int, ...]) -> tuple[Matching, ...]:
|
||||||
|
"""All non-crossing partial matchings of ``positions`` (sorted ascending).
|
||||||
|
|
||||||
|
Bite pairs drawn inside the disk are non-crossing iff, read in cyclic
|
||||||
|
order, no two pairs interleave. Cutting the cycle at the gap before the
|
||||||
|
first edge turns this into ordinary non-crossing interval matchings, which
|
||||||
|
obey the Catalan recursion below.
|
||||||
|
"""
|
||||||
|
if not positions:
|
||||||
|
return (frozenset(),)
|
||||||
|
head, *rest = positions
|
||||||
|
out: list[Matching] = []
|
||||||
|
# head left unmatched (a singleton down tooth, if its edge is down)
|
||||||
|
for tail in noncrossing_matchings(tuple(rest)):
|
||||||
|
out.append(tail)
|
||||||
|
# head matched with positions[k]; the strictly-enclosed block must be
|
||||||
|
# matched within itself to stay non-crossing.
|
||||||
|
for k in range(1, len(positions)):
|
||||||
|
partner = positions[k]
|
||||||
|
inside = tuple(positions[1:k])
|
||||||
|
outside = tuple(positions[k + 1:])
|
||||||
|
for m_in in noncrossing_matchings(inside):
|
||||||
|
for m_out in noncrossing_matchings(outside):
|
||||||
|
out.append(frozenset({(head, partner)}) | m_in | m_out)
|
||||||
|
return tuple(out)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# The bite-face condition (Remark 3.8).
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def incident_edges(i: int, j: int, n: int) -> bool:
|
||||||
|
"""Whether annular edges i and j share an annular vertex on the n-cycle."""
|
||||||
|
return (j - i) % n == 1 or (i - j) % n == 1
|
||||||
|
|
||||||
|
|
||||||
|
def has_incident_bite(bites: Matching, n: int) -> bool:
|
||||||
|
"""Whether any bite pairs two incident (cyclically adjacent) edges."""
|
||||||
|
return any(incident_edges(i, j, n) for i, j in bites)
|
||||||
|
|
||||||
|
|
||||||
|
def innermost_bite(edge: int, bites: Matching) -> Bite | None:
|
||||||
|
"""The minimal-span bite whose open interval contains ``edge``, or None."""
|
||||||
|
enclosing = [b for b in bites if b[0] < edge < b[1]]
|
||||||
|
if not enclosing:
|
||||||
|
return None
|
||||||
|
return min(enclosing, key=lambda b: b[1] - b[0])
|
||||||
|
|
||||||
|
|
||||||
|
def face_singleton_counts(
|
||||||
|
tooth_word: str, bites: Matching
|
||||||
|
) -> dict[Bite | None, int]:
|
||||||
|
"""Down-singletons per interior non-tooth face of B(T).
|
||||||
|
|
||||||
|
The key ``None`` is the root face; a bite key is that bite's inner-gap
|
||||||
|
face. Faces with no singletons are simply absent from the result.
|
||||||
|
"""
|
||||||
|
matched = {edge for pair in bites for edge in pair}
|
||||||
|
counts: dict[Bite | None, int] = defaultdict(int)
|
||||||
|
for edge, tooth in enumerate(tooth_word):
|
||||||
|
if tooth != "D" or edge in matched:
|
||||||
|
continue # only singleton down teeth contribute apexes
|
||||||
|
counts[innermost_bite(edge, bites)] += 1
|
||||||
|
return dict(counts)
|
||||||
|
|
||||||
|
|
||||||
|
def satisfies_bite_face_condition(tooth_word: str, bites: Matching) -> bool:
|
||||||
|
"""Remark 3.8: every non-tooth face holds 0 or >=3 down-tooth apexes."""
|
||||||
|
return all(count >= 3 for count in face_singleton_counts(tooth_word, bites).values())
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# The full medial tire graph as an explicit object.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class FullMedialTireGraph:
|
||||||
|
"""A full medial tire graph M(T) determined by its combinatorial data.
|
||||||
|
|
||||||
|
Vertices are named:
|
||||||
|
a{k} annular medial vertex k (k = 0..n-1), forming A(T);
|
||||||
|
u{i} apex of the up tooth on edge i;
|
||||||
|
d{i} apex of the singleton down tooth on edge i;
|
||||||
|
p{i}_{j} apex of the bite pairing edges i and j (i < j).
|
||||||
|
"""
|
||||||
|
|
||||||
|
n: int
|
||||||
|
tooth_word: str
|
||||||
|
bites: Matching
|
||||||
|
|
||||||
|
@property
|
||||||
|
def up_edges(self) -> tuple[int, ...]:
|
||||||
|
return tuple(i for i, t in enumerate(self.tooth_word) if t == "U")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def down_edges(self) -> tuple[int, ...]:
|
||||||
|
return tuple(i for i, t in enumerate(self.tooth_word) if t == "D")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bite_edges(self) -> frozenset[int]:
|
||||||
|
return frozenset(edge for pair in self.bites for edge in pair)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def singleton_down_edges(self) -> tuple[int, ...]:
|
||||||
|
bite = self.bite_edges
|
||||||
|
return tuple(i for i in self.down_edges if i not in bite)
|
||||||
|
|
||||||
|
def apex_of_edge(self, edge: int) -> str:
|
||||||
|
if self.tooth_word[edge] == "U":
|
||||||
|
return f"u{edge}"
|
||||||
|
for i, j in self.bites:
|
||||||
|
if edge in (i, j):
|
||||||
|
return f"p{i}_{j}"
|
||||||
|
return f"d{edge}"
|
||||||
|
|
||||||
|
def vertices(self) -> list[str]:
|
||||||
|
verts = [f"a{k}" for k in range(self.n)]
|
||||||
|
for i in self.up_edges:
|
||||||
|
verts.append(f"u{i}")
|
||||||
|
for i in self.singleton_down_edges:
|
||||||
|
verts.append(f"d{i}")
|
||||||
|
for i, j in sorted(self.bites):
|
||||||
|
verts.append(f"p{i}_{j}")
|
||||||
|
return verts
|
||||||
|
|
||||||
|
def edges(self) -> list[tuple[str, str]]:
|
||||||
|
n = self.n
|
||||||
|
out: list[tuple[str, str]] = []
|
||||||
|
# annular cycle A(T)
|
||||||
|
for k in range(n):
|
||||||
|
out.append((f"a{k}", f"a{(k + 1) % n}"))
|
||||||
|
# singleton teeth (up and down): two spokes each
|
||||||
|
for i in self.up_edges:
|
||||||
|
out += [(f"u{i}", f"a{i}"), (f"u{i}", f"a{(i + 1) % n}")]
|
||||||
|
for i in self.singleton_down_edges:
|
||||||
|
out += [(f"d{i}", f"a{i}"), (f"d{i}", f"a{(i + 1) % n}")]
|
||||||
|
# bites: a shared apex with four spokes
|
||||||
|
for i, j in sorted(self.bites):
|
||||||
|
apex = f"p{i}_{j}"
|
||||||
|
for edge in (i, j):
|
||||||
|
out += [(apex, f"a{edge}"), (apex, f"a{(edge + 1) % n}")]
|
||||||
|
return [tuple(sorted(e)) for e in out]
|
||||||
|
|
||||||
|
def canonical_key(self) -> tuple:
|
||||||
|
"""Representative under the dihedral group of the cycle (rotations and
|
||||||
|
reflections), so symmetric configurations collapse to one key."""
|
||||||
|
n = self.n
|
||||||
|
best: tuple | None = None
|
||||||
|
for a in (1, -1):
|
||||||
|
for b in range(n):
|
||||||
|
relabel = lambda i: (a * i + b) % n
|
||||||
|
word = [""] * n
|
||||||
|
for i, t in enumerate(self.tooth_word):
|
||||||
|
word[relabel(i)] = t
|
||||||
|
mapped = tuple(sorted(
|
||||||
|
tuple(sorted((relabel(i), relabel(j)))) for i, j in self.bites
|
||||||
|
))
|
||||||
|
key = (tuple(word), mapped)
|
||||||
|
if best is None or key < best:
|
||||||
|
best = key
|
||||||
|
return best
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Enumeration.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def generate(
|
||||||
|
n: int, min_up_teeth: int = 3, dedup: bool = False
|
||||||
|
) -> Iterator[FullMedialTireGraph]:
|
||||||
|
"""Yield every full medial tire graph whose annular cycle has size ``n``.
|
||||||
|
|
||||||
|
``min_up_teeth`` defaults to 3 (Remark 3.6). With ``dedup`` set, only one
|
||||||
|
representative per dihedral symmetry class is returned.
|
||||||
|
"""
|
||||||
|
seen: set[tuple] = set()
|
||||||
|
for word_tuple in itertools.product("UD", repeat=n):
|
||||||
|
tooth_word = "".join(word_tuple)
|
||||||
|
if tooth_word.count("U") < min_up_teeth:
|
||||||
|
continue
|
||||||
|
down = tuple(i for i, t in enumerate(tooth_word) if t == "D")
|
||||||
|
for bites in noncrossing_matchings(down):
|
||||||
|
if has_incident_bite(bites, n):
|
||||||
|
continue
|
||||||
|
if not satisfies_bite_face_condition(tooth_word, bites):
|
||||||
|
continue
|
||||||
|
graph = FullMedialTireGraph(n=n, tooth_word=tooth_word, bites=bites)
|
||||||
|
if dedup:
|
||||||
|
key = graph.canonical_key()
|
||||||
|
if key in seen:
|
||||||
|
continue
|
||||||
|
seen.add(key)
|
||||||
|
yield graph
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# CLI.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def figure_one() -> FullMedialTireGraph:
|
||||||
|
"""The example graph of Figure 1 (Remark 3.8): 12 edges, one bite (0,6)."""
|
||||||
|
return FullMedialTireGraph(
|
||||||
|
n=12,
|
||||||
|
tooth_word="DDDDDUDUUUUU", # edges 0-4,6 down; 5,7,8,9,10,11 up
|
||||||
|
bites=frozenset({(0, 6)}),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def describe(graph: FullMedialTireGraph) -> str:
|
||||||
|
counts = face_singleton_counts(graph.tooth_word, graph.bites)
|
||||||
|
face_strs = []
|
||||||
|
for face, c in sorted(counts.items(), key=lambda kv: (kv[0] is not None, kv[0])):
|
||||||
|
name = "root" if face is None else f"bite{face}"
|
||||||
|
face_strs.append(f"{name}:{c}")
|
||||||
|
bites = ",".join(f"({i},{j})" for i, j in sorted(graph.bites)) or "-"
|
||||||
|
faces = " ".join(face_strs) or "-"
|
||||||
|
return (
|
||||||
|
f"word={graph.tooth_word} up={len(graph.up_edges)} "
|
||||||
|
f"down={len(graph.down_edges)} bites={bites} faces[{faces}]"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def run(args: argparse.Namespace) -> None:
|
||||||
|
if args.check_figure:
|
||||||
|
g = figure_one()
|
||||||
|
print("Figure 1 check:")
|
||||||
|
print(f" {describe(g)}")
|
||||||
|
ok = satisfies_bite_face_condition(g.tooth_word, g.bites)
|
||||||
|
print(f" satisfies Remark 3.8: {ok} (expect True; faces 4 and 0)")
|
||||||
|
print()
|
||||||
|
|
||||||
|
for n in range(args.min_n, args.max_n + 1):
|
||||||
|
graphs = list(generate(n, min_up_teeth=args.min_up, dedup=args.dedup))
|
||||||
|
label = "classes" if args.dedup else "graphs"
|
||||||
|
print(f"n={n}: {len(graphs)} {label}")
|
||||||
|
if args.show:
|
||||||
|
for g in graphs[: args.show]:
|
||||||
|
print(f" {describe(g)}")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--min-n", type=int, default=3)
|
||||||
|
parser.add_argument("--max-n", type=int, default=8)
|
||||||
|
parser.add_argument("--min-up", type=int, default=3, help="Remark 3.6 bound")
|
||||||
|
parser.add_argument("--dedup", action="store_true",
|
||||||
|
help="reduce modulo dihedral symmetry of the cycle")
|
||||||
|
parser.add_argument("--show", type=int, default=0,
|
||||||
|
help="print up to this many graphs per n")
|
||||||
|
parser.add_argument("--check-figure", action="store_true",
|
||||||
|
help="verify the Figure 1 example against Remark 3.8")
|
||||||
|
run(parser.parse_args())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
"""Medial tire decomposition helpers for plane triangulation experiments."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
import networkx as nx
|
||||||
|
|
||||||
|
from .full_medial_tire_generator import FullMedialTireGraph
|
||||||
|
|
||||||
|
|
||||||
|
def ekey(u, v):
|
||||||
|
return (u, v) if (str(u), u) <= (str(v), v) else (v, u)
|
||||||
|
|
||||||
|
|
||||||
|
def medial_tire_facemodel(tread_faces) -> nx.Graph:
|
||||||
|
"""Build the ambient tread-face model of a full medial tire graph M(T)."""
|
||||||
|
mt = nx.Graph()
|
||||||
|
for f in tread_faces:
|
||||||
|
es = [ekey(f[0], f[1]), ekey(f[1], f[2]), ekey(f[2], f[0])]
|
||||||
|
mt.add_nodes_from(es)
|
||||||
|
for a in range(3):
|
||||||
|
mt.add_edge(es[a], es[(a + 1) % 3])
|
||||||
|
return mt
|
||||||
|
|
||||||
|
|
||||||
|
def extract_tread(faces, levels, d):
|
||||||
|
"""Tread T_d: faces spanning levels {d, d+1}. Return its edge classes."""
|
||||||
|
tread_faces = []
|
||||||
|
for f in faces:
|
||||||
|
lv = [levels[x] for x in f]
|
||||||
|
if min(lv) == d and max(lv) == d + 1:
|
||||||
|
tread_faces.append(f)
|
||||||
|
if not tread_faces:
|
||||||
|
return None
|
||||||
|
|
||||||
|
annular, up, down = set(), set(), set()
|
||||||
|
face_of_down = defaultdict(int)
|
||||||
|
for f in tread_faces:
|
||||||
|
for x, y in ((f[0], f[1]), (f[1], f[2]), (f[2], f[0])):
|
||||||
|
e = ekey(x, y)
|
||||||
|
lx, ly = levels[x], levels[y]
|
||||||
|
if {lx, ly} == {d, d + 1}:
|
||||||
|
annular.add(e)
|
||||||
|
elif lx == ly == d:
|
||||||
|
up.add(e)
|
||||||
|
elif lx == ly == d + 1:
|
||||||
|
down.add(e)
|
||||||
|
face_of_down[e] += 1
|
||||||
|
|
||||||
|
bites = {e for e in down if face_of_down[e] == 2}
|
||||||
|
return {
|
||||||
|
"tread_faces": tread_faces,
|
||||||
|
"annular": annular,
|
||||||
|
"up": up,
|
||||||
|
"down": down,
|
||||||
|
"bites": bites,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _cycle_order(sub: nx.Graph, comp):
|
||||||
|
"""Cyclic order of a simple 2-regular component, or None."""
|
||||||
|
csub = sub.subgraph(comp)
|
||||||
|
if csub.number_of_nodes() < 3 or any(csub.degree(v) != 2 for v in csub):
|
||||||
|
return None
|
||||||
|
start = next(iter(comp))
|
||||||
|
order = [start]
|
||||||
|
prev, cur = None, start
|
||||||
|
while True:
|
||||||
|
nbrs = [w for w in csub.neighbors(cur) if w != prev]
|
||||||
|
if not nbrs:
|
||||||
|
break
|
||||||
|
nxt = nbrs[0]
|
||||||
|
if nxt == start:
|
||||||
|
break
|
||||||
|
order.append(nxt)
|
||||||
|
prev, cur = cur, nxt
|
||||||
|
return order if len(order) == csub.number_of_nodes() else None
|
||||||
|
|
||||||
|
|
||||||
|
def annular_cycle_order(M: nx.Graph, annular: set):
|
||||||
|
"""Cyclic order of annular medial vertices when they induce one cycle."""
|
||||||
|
sub = M.subgraph(annular)
|
||||||
|
if not annular or not nx.is_connected(sub):
|
||||||
|
return None
|
||||||
|
return _cycle_order(sub, set(annular))
|
||||||
|
|
||||||
|
|
||||||
|
def annular_cycle_components(M: nx.Graph, annular: set):
|
||||||
|
"""Cyclic orders of annular medial vertices, one per cycle component."""
|
||||||
|
sub = M.subgraph(annular)
|
||||||
|
orders = []
|
||||||
|
for comp in nx.connected_components(sub):
|
||||||
|
order = _cycle_order(sub, comp)
|
||||||
|
if order is not None:
|
||||||
|
orders.append(order)
|
||||||
|
return orders
|
||||||
|
|
||||||
|
|
||||||
|
def _linear_cut(n, bite_pairs):
|
||||||
|
"""Rotate the cycle so bite pairs become linear non-crossing intervals."""
|
||||||
|
for r in range(n):
|
||||||
|
rel = [tuple(sorted(((i - r) % n, (j - r) % n))) for i, j in bite_pairs]
|
||||||
|
ok = True
|
||||||
|
for a, b in rel:
|
||||||
|
for c, d in rel:
|
||||||
|
if (a, b) != (c, d) and (a < c < b < d or c < a < d < b):
|
||||||
|
ok = False
|
||||||
|
break
|
||||||
|
if not ok:
|
||||||
|
break
|
||||||
|
if ok:
|
||||||
|
return r, rel
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _recognise_one(M, order, up, ann_global):
|
||||||
|
"""Recognise one annular cycle as a FullMedialTireGraph."""
|
||||||
|
n = len(order)
|
||||||
|
if n < 3:
|
||||||
|
return None
|
||||||
|
ann_set = set(order)
|
||||||
|
|
||||||
|
apex_of_edge = []
|
||||||
|
for i in range(n):
|
||||||
|
a, b = order[i], order[(i + 1) % n]
|
||||||
|
common = [
|
||||||
|
w for w in set(M.neighbors(a)) & set(M.neighbors(b))
|
||||||
|
if w not in ann_global
|
||||||
|
]
|
||||||
|
if len(common) != 1:
|
||||||
|
return None
|
||||||
|
apex_of_edge.append(common[0])
|
||||||
|
|
||||||
|
apex_positions = defaultdict(list)
|
||||||
|
for i, ap in enumerate(apex_of_edge):
|
||||||
|
apex_positions[ap].append(i)
|
||||||
|
bite_pairs = [
|
||||||
|
tuple(sorted(positions))
|
||||||
|
for positions in apex_positions.values()
|
||||||
|
if len(positions) == 2
|
||||||
|
]
|
||||||
|
tooth = ["U" if ap in up else "D" for ap in apex_of_edge]
|
||||||
|
|
||||||
|
cut = _linear_cut(n, bite_pairs)
|
||||||
|
if cut is None:
|
||||||
|
return None
|
||||||
|
r, rel_bites = cut
|
||||||
|
word = [""] * n
|
||||||
|
for i in range(n):
|
||||||
|
word[(i - r) % n] = tooth[i]
|
||||||
|
graph = FullMedialTireGraph(
|
||||||
|
n=n, tooth_word="".join(word), bites=frozenset(rel_bites)
|
||||||
|
)
|
||||||
|
|
||||||
|
bij = {}
|
||||||
|
for k in range(n):
|
||||||
|
bij[f"a{k}"] = order[(k + r) % n]
|
||||||
|
for i in graph.up_edges:
|
||||||
|
bij[f"u{i}"] = apex_of_edge[(i + r) % n]
|
||||||
|
for i in graph.singleton_down_edges:
|
||||||
|
bij[f"d{i}"] = apex_of_edge[(i + r) % n]
|
||||||
|
for i, j in sorted(graph.bites):
|
||||||
|
bij[f"p{i}_{j}"] = apex_of_edge[(i + r) % n]
|
||||||
|
|
||||||
|
sub_nodes = ann_set | set(apex_of_edge)
|
||||||
|
sub_edges = {ekey(*e) for e in M.subgraph(sub_nodes).edges()}
|
||||||
|
rec_edges = {ekey(bij[u], bij[v]) for u, v in graph.edges()}
|
||||||
|
if rec_edges != sub_edges:
|
||||||
|
return None
|
||||||
|
return graph, bij
|
||||||
|
|
||||||
|
|
||||||
|
def recognise(M, tread):
|
||||||
|
"""Recognise the tread's medial-tire structure.
|
||||||
|
|
||||||
|
A tread's annular frontier may be several disjoint cycles, each its own
|
||||||
|
full medial tire graph. Returns one ``(FullMedialTireGraph, bijection)``
|
||||||
|
pair per annular cycle component that recognises.
|
||||||
|
"""
|
||||||
|
up = set(tread["up"])
|
||||||
|
ann_global = set(tread["annular"])
|
||||||
|
tires = []
|
||||||
|
for order in annular_cycle_components(M, tread["annular"]):
|
||||||
|
rec = _recognise_one(M, order, up, ann_global)
|
||||||
|
if rec is not None:
|
||||||
|
tires.append(rec)
|
||||||
|
return tires
|
||||||
@@ -12,33 +12,33 @@
|
|||||||
\newlabel{def:full-medial-tire}{{3.1}{2}}
|
\newlabel{def:full-medial-tire}{{3.1}{2}}
|
||||||
\newlabel{thm:annular-medial-colour-bound}{{3.3}{3}}
|
\newlabel{thm:annular-medial-colour-bound}{{3.3}{3}}
|
||||||
\newlabel{def:annular-teeth}{{3.4}{3}}
|
\newlabel{def:annular-teeth}{{3.4}{3}}
|
||||||
\newlabel{rem:teeth-sharing}{{3.5}{3}}
|
\citation{bauerfeld-nested-tire-decompositions}
|
||||||
\newlabel{rem:up-teeth-count}{{3.6}{3}}
|
\citation{bauerfeld-nested-tire-decompositions}
|
||||||
|
\newlabel{rem:teeth-sharing}{{3.5}{4}}
|
||||||
|
\newlabel{rem:up-teeth-count}{{3.6}{4}}
|
||||||
\newlabel{def:bite}{{3.7}{4}}
|
\newlabel{def:bite}{{3.7}{4}}
|
||||||
\newlabel{rem:bite-face-count}{{3.8}{4}}
|
\newlabel{rem:bite-face-count}{{3.8}{4}}
|
||||||
\@writefile{lof}{\contentsline {figure}{\numberline {1}{\ignorespaces A full medial tire graph $\mathsf {M}(T)$ illustrating the tooth terminology. The thick cycle is the annular medial cycle $A(T)$, whose black vertices are the annular medial vertices. Each edge of $A(T)$ carries one tooth: up teeth (blue apexes, outer-boundary medial vertices) point into the outer region, and down teeth (red apexes, inner-boundary medial vertices) point into the inner region. The two down teeth meeting at the central shared apex (larger red vertex) form a bite; that shared apex splits the inner region into two faces, one with four down teeth on its boundary and one with none.}}{4}{}\protected@file@percent }
|
|
||||||
\newlabel{fig:medial-teeth-example}{{1}{4}}
|
|
||||||
\newlabel{def:boundary-medial-vertices}{{3.9}{4}}
|
\newlabel{def:boundary-medial-vertices}{{3.9}{4}}
|
||||||
\citation{bauerfeld-nested-tire-decompositions}
|
\newlabel{def:medial-restriction-relation}{{3.10}{4}}
|
||||||
\@writefile{lof}{\contentsline {figure}{\numberline {2}{\ignorespaces Three six-face full medial tire graphs found by the boundary-state restriction search. Black vertices are annular medial vertices; blue vertices are outer boundary medial vertices and red vertices are inner boundary medial vertices. The word below each diagram records the outer/inner type of the six annular faces in cyclic order. Boundary states are identified only up to colour permutation, not by rotation or reflection of the boundary order.}}{5}{}\protected@file@percent }
|
\@writefile{toc}{\contentsline {section}{\tocsection {}{4}{Decomposition}}{4}{}\protected@file@percent }
|
||||||
|
\newlabel{cor:medial-tire-decomposition}{{4.1}{4}}
|
||||||
|
\@writefile{lof}{\contentsline {figure}{\numberline {1}{\ignorespaces A simple medial tire graph $\mathsf {M}(T)$ illustrating the tooth terminology. The thick cycle is the annular medial cycle $A(T)$, whose black vertices are the annular medial vertices. Each edge of $A(T)$ carries one tooth: up teeth (blue apexes, outer-boundary medial vertices) point into the outer region, and down teeth (red apexes, inner-boundary medial vertices) point into the inner region. The two down teeth meeting at the central shared apex (larger red vertex) form a bite; that shared apex splits the inner region into two faces, one with four down teeth on its boundary and one with none.}}{5}{}\protected@file@percent }
|
||||||
|
\newlabel{fig:medial-teeth-example}{{1}{5}}
|
||||||
|
\@writefile{lof}{\contentsline {figure}{\numberline {2}{\ignorespaces Three six-face simple medial tire graphs found by the boundary-state restriction search. Black vertices are annular medial vertices; blue vertices are outer boundary medial vertices and red vertices are inner boundary medial vertices. The word below each diagram records the outer/inner type of the six annular faces in cyclic order. Boundary states are identified only up to colour permutation, not by rotation or reflection of the boundary order.}}{5}{}\protected@file@percent }
|
||||||
\newlabel{fig:medial-restriction-worst-cases}{{2}{5}}
|
\newlabel{fig:medial-restriction-worst-cases}{{2}{5}}
|
||||||
\@writefile{lof}{\contentsline {figure}{\numberline {3}{\ignorespaces A proper vertex $3$-colouring of the full medial graph of the first seven-vertex counterexample found by the experiment. The medial vertex labelled $ij$ corresponds to the edge $(i,j)$ of the triangulation. For the vertex-source decomposition at source $1$, the highlighted annular medial cycle has colour counts $(2,2,2)$, so it is not coloured with two colours except at at most one vertex.}}{5}{}\protected@file@percent }
|
\@writefile{lof}{\contentsline {figure}{\numberline {3}{\ignorespaces A proper vertex $3$-colouring of the full medial graph of the first seven-vertex counterexample found by the experiment. The medial vertex labelled $ij$ corresponds to the edge $(i,j)$ of the triangulation. For the vertex-source decomposition at source $1$, the highlighted annular medial cycle has colour counts $(2,2,2)$, so it is not coloured with two colours except at at most one vertex.}}{6}{}\protected@file@percent }
|
||||||
\newlabel{fig:medial-annular-cycle-counterexample}{{3}{5}}
|
\newlabel{fig:medial-annular-cycle-counterexample}{{3}{6}}
|
||||||
\newlabel{def:medial-restriction-relation}{{3.10}{5}}
|
|
||||||
\citation{bauerfeld-nested-tire-decompositions}
|
|
||||||
\@writefile{toc}{\contentsline {section}{\tocsection {}{4}{Decomposition}}{6}{}\protected@file@percent }
|
|
||||||
\newlabel{cor:medial-tire-decomposition}{{4.1}{6}}
|
|
||||||
\newlabel{def:compatible-family}{{4.2}{6}}
|
\newlabel{def:compatible-family}{{4.2}{6}}
|
||||||
\newlabel{prop:gluing-criterion}{{4.3}{6}}
|
\newlabel{prop:gluing-criterion}{{4.3}{6}}
|
||||||
\@writefile{toc}{\contentsline {section}{\tocsection {}{5}{A medial pigeonhole programme}}{6}{}\protected@file@percent }
|
\@writefile{toc}{\contentsline {section}{\tocsection {}{5}{A medial pigeonhole programme}}{6}{}\protected@file@percent }
|
||||||
\newlabel{def:medial-boundary-state}{{5.1}{6}}
|
\newlabel{def:medial-boundary-state}{{5.1}{7}}
|
||||||
\newlabel{conj:medial-chain-pigeonhole}{{5.2}{7}}
|
\newlabel{conj:medial-chain-pigeonhole}{{5.2}{7}}
|
||||||
\newlabel{conj:medial-route-fct}{{5.3}{7}}
|
\newlabel{conj:medial-route-fct}{{5.3}{7}}
|
||||||
\@writefile{toc}{\contentsline {subsection}{\tocsubsection {}{5.1}{Kempe-cycle conservation across medial tires}}{7}{}\protected@file@percent }
|
\@writefile{toc}{\contentsline {subsection}{\tocsubsection {}{5.1}{Kempe-cycle conservation across medial tires}}{7}{}\protected@file@percent }
|
||||||
\newlabel{lem:kempe-cycles}{{5.5}{7}}
|
\newlabel{lem:kempe-cycles}{{5.5}{7}}
|
||||||
\newlabel{lem:kempe-conservation}{{5.6}{8}}
|
\newlabel{lem:kempe-conservation}{{5.6}{8}}
|
||||||
\newlabel{def:kempe-balanced}{{5.7}{8}}
|
\newlabel{def:kempe-balanced}{{5.7}{8}}
|
||||||
\newlabel{rem:kempe-balance-necessary}{{5.8}{8}}
|
\newlabel{rem:kempe-balance-necessary}{{5.8}{9}}
|
||||||
\bibcite{bauerfeld-nested-tire-decompositions}{1}
|
\bibcite{bauerfeld-nested-tire-decompositions}{1}
|
||||||
\bibcite{tait-original}{2}
|
\bibcite{tait-original}{2}
|
||||||
\newlabel{tocindent-1}{0pt}
|
\newlabel{tocindent-1}{0pt}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Fdb version 3
|
# Fdb version 3
|
||||||
["pdflatex"] 1781210675 "paper.tex" "paper.pdf" "paper" 1781210676
|
["pdflatex"] 1781554446 "paper.tex" "paper.pdf" "paper" 1781554447
|
||||||
"/usr/local/texlive/2022/texmf-dist/fonts/map/fontname/texfonts.map" 1577235249 3524 cb3e574dea2d1052e39280babc910dc8 ""
|
"/usr/local/texlive/2022/texmf-dist/fonts/map/fontname/texfonts.map" 1577235249 3524 cb3e574dea2d1052e39280babc910dc8 ""
|
||||||
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/amsfonts/cmextra/cmex7.tfm" 1246382020 1004 54797486969f23fa377b128694d548df ""
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/amsfonts/cmextra/cmex7.tfm" 1246382020 1004 54797486969f23fa377b128694d548df ""
|
||||||
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/amsfonts/cmextra/cmex8.tfm" 1246382020 988 bdf658c3bfc2d96d3c8b02cfc1c94c20 ""
|
"/usr/local/texlive/2022/texmf-dist/fonts/tfm/public/amsfonts/cmextra/cmex8.tfm" 1246382020 988 bdf658c3bfc2d96d3c8b02cfc1c94c20 ""
|
||||||
@@ -132,8 +132,8 @@
|
|||||||
"/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map" 1647878959 4410336 7d30a02e9fa9a16d7d1f8d037ba69641 ""
|
"/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map" 1647878959 4410336 7d30a02e9fa9a16d7d1f8d037ba69641 ""
|
||||||
"/usr/local/texlive/2022/texmf-var/web2c/pdftex/pdflatex.fmt" 1665017617 2826443 7e98410c533054b636c6470db83a27bc ""
|
"/usr/local/texlive/2022/texmf-var/web2c/pdftex/pdflatex.fmt" 1665017617 2826443 7e98410c533054b636c6470db83a27bc ""
|
||||||
"/usr/local/texlive/2022/texmf.cnf" 1647878952 577 209b46be99c9075fd74d4c0369380e8c ""
|
"/usr/local/texlive/2022/texmf.cnf" 1647878952 577 209b46be99c9075fd74d4c0369380e8c ""
|
||||||
"paper.aux" 1781210676 4206 870862ca1c6762f39fd7ed9def109a09 "pdflatex"
|
"paper.aux" 1781554447 4210 3b62c5dd250f159f0e3d42ba3a3a7308 "pdflatex"
|
||||||
"paper.tex" 1781210650 40922 403b0b9df57192dbf02362b0b06705c3 ""
|
"paper.tex" 1781554440 42216 46cb5902d4210f9324b1231139c3e122 ""
|
||||||
(generated)
|
(generated)
|
||||||
"paper.aux"
|
"paper.aux"
|
||||||
"paper.log"
|
"paper.log"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 11 JUN 2026 16:44
|
This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 15 JUN 2026 16:14
|
||||||
entering extended mode
|
entering extended mode
|
||||||
restricted \write18 enabled.
|
restricted \write18 enabled.
|
||||||
%&-line parsing enabled.
|
%&-line parsing enabled.
|
||||||
@@ -496,7 +496,7 @@ e
|
|||||||
))
|
))
|
||||||
[1{/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map}]
|
[1{/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map}]
|
||||||
[2] [3]
|
[2] [3]
|
||||||
Overfull \hbox (62.13657pt too wide) in paragraph at lines 363--372
|
Overfull \hbox (62.13657pt too wide) in paragraph at lines 371--380
|
||||||
[][]
|
[][]
|
||||||
[]
|
[]
|
||||||
|
|
||||||
@@ -504,17 +504,20 @@ Overfull \hbox (62.13657pt too wide) in paragraph at lines 363--372
|
|||||||
LaTeX Warning: `h' float specifier changed to `ht'.
|
LaTeX Warning: `h' float specifier changed to `ht'.
|
||||||
|
|
||||||
|
|
||||||
|
LaTeX Warning: `h' float specifier changed to `ht'.
|
||||||
|
|
||||||
|
|
||||||
LaTeX Warning: `h' float specifier changed to `ht'.
|
LaTeX Warning: `h' float specifier changed to `ht'.
|
||||||
|
|
||||||
[4] [5] [6] [7] [8] [9] [10] [11] (./paper.aux) )
|
[4] [5] [6] [7] [8] [9] [10] [11] (./paper.aux) )
|
||||||
Here is how much of TeX's memory you used:
|
Here is how much of TeX's memory you used:
|
||||||
14419 strings out of 478268
|
14419 strings out of 478268
|
||||||
283755 string characters out of 5846347
|
283755 string characters out of 5846347
|
||||||
609349 words of memory out of 5000000
|
618029 words of memory out of 5000000
|
||||||
32248 multiletter control sequences out of 15000+600000
|
32248 multiletter control sequences out of 15000+600000
|
||||||
477048 words of font info for 58 fonts, out of 8000000 for 9000
|
477048 words of font info for 58 fonts, out of 8000000 for 9000
|
||||||
1302 hyphenation exceptions out of 8191
|
1302 hyphenation exceptions out of 8191
|
||||||
84i,8n,89p,736b,838s stack positions out of 10000i,1000n,20000p,200000b,200000s
|
84i,8n,89p,738b,838s stack positions out of 10000i,1000n,20000p,200000b,200000s
|
||||||
</usr/local/texlive/2022/texmf
|
</usr/local/texlive/2022/texmf
|
||||||
-dist/fonts/type1/public/amsfonts/cm/cmbx10.pfb></usr/local/texlive/2022/texmf-
|
-dist/fonts/type1/public/amsfonts/cm/cmbx10.pfb></usr/local/texlive/2022/texmf-
|
||||||
dist/fonts/type1/public/amsfonts/cm/cmbx8.pfb></usr/local/texlive/2022/texmf-di
|
dist/fonts/type1/public/amsfonts/cm/cmbx8.pfb></usr/local/texlive/2022/texmf-di
|
||||||
@@ -535,7 +538,7 @@ msfonts/cm/cmti10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/am
|
|||||||
sfonts/cm/cmti8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsf
|
sfonts/cm/cmti8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsf
|
||||||
onts/cm/cmtt8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfon
|
onts/cm/cmtt8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfon
|
||||||
ts/symbols/msam10.pfb>
|
ts/symbols/msam10.pfb>
|
||||||
Output written on paper.pdf (11 pages, 277538 bytes).
|
Output written on paper.pdf (11 pages, 278916 bytes).
|
||||||
PDF statistics:
|
PDF statistics:
|
||||||
138 PDF objects out of 1000 (max. 8388607)
|
138 PDF objects out of 1000 (max. 8388607)
|
||||||
86 compressed objects within 1 object stream
|
86 compressed objects within 1 object stream
|
||||||
|
|||||||
@@ -159,19 +159,27 @@ colours.
|
|||||||
|
|
||||||
\section{Medial tire pieces}
|
\section{Medial tire pieces}
|
||||||
|
|
||||||
\begin{definition}[Full medial tire graph]
|
\begin{definition}[Simple and compound medial tire graphs]
|
||||||
\label{def:full-medial-tire}
|
\label{def:full-medial-tire}
|
||||||
Let $T$ be a tire tread in the tire tree $\mathcal{T}(G,S)$ supplied
|
Let $T$ be a tire tread in the tire tree $\mathcal{T}(G,S)$ supplied
|
||||||
by~\cite{bauerfeld-nested-tire-decompositions}. The \emph{full medial
|
by~\cite{bauerfeld-nested-tire-decompositions}. The \emph{medial tire
|
||||||
tire graph} of $T$, denoted $\mathsf{M}(T)$, is the subgraph of
|
graph} of $T$, denoted $\mathsf{M}(T)$, is the subgraph of $M(G)$
|
||||||
$M(G)$ induced by the medial vertices $m_e$ with $e$ an edge of $G$
|
induced by the medial vertices $m_e$ with $e$ an edge of $G$ incident
|
||||||
incident to at least one triangular face in the tread $T$. The medial
|
to at least one triangular face in the tread $T$. The medial vertices
|
||||||
vertices corresponding to annular edges of $T$ are called
|
corresponding to annular edges of $T$ are called \emph{annular medial
|
||||||
\emph{annular medial vertices}.
|
vertices}.
|
||||||
|
|
||||||
|
We call $\mathsf{M}(T)$ a \emph{simple medial tire graph} if its
|
||||||
|
annular medial vertices induce a single cycle. We call
|
||||||
|
$\mathsf{M}(T)$ a \emph{compound medial tire graph} if it is associated
|
||||||
|
to a connected depth component of tread faces but its annular medial
|
||||||
|
vertices induce more than one cycle. In a compound medial tire graph,
|
||||||
|
annular teeth are understood cycle-by-cycle, and up-tooth apexes
|
||||||
|
belonging to different annular cycles may coincide.
|
||||||
\end{definition}
|
\end{definition}
|
||||||
|
|
||||||
\begin{remark}
|
\begin{remark}
|
||||||
In the ambient-triangulation setting, the full medial tire graph
|
In the ambient-triangulation setting, the simple medial tire graph
|
||||||
$\mathsf{M}(T)$ coincides with the omitted-edge medial tire graph
|
$\mathsf{M}(T)$ coincides with the omitted-edge medial tire graph
|
||||||
studied in~\cite{bauerfeld-nested-tire-decompositions}. Indeed, the
|
studied in~\cite{bauerfeld-nested-tire-decompositions}. Indeed, the
|
||||||
medial edges of $\mathsf{M}(T)$ are contributed by corners of annular
|
medial edges of $\mathsf{M}(T)$ are contributed by corners of annular
|
||||||
@@ -361,7 +369,7 @@ interior.
|
|||||||
\node[lbl, anchor=east] at (192:1.86) (L0) {region with 0 down teeth};
|
\node[lbl, anchor=east] at (192:1.86) (L0) {region with 0 down teeth};
|
||||||
\draw[lead] (L0.east) -- (180:0.45);
|
\draw[lead] (L0.east) -- (180:0.45);
|
||||||
\end{tikzpicture}
|
\end{tikzpicture}
|
||||||
\caption{A full medial tire graph $\mathsf{M}(T)$ illustrating the tooth
|
\caption{A simple medial tire graph $\mathsf{M}(T)$ illustrating the tooth
|
||||||
terminology. The thick cycle is the annular medial cycle $A(T)$, whose
|
terminology. The thick cycle is the annular medial cycle $A(T)$, whose
|
||||||
black vertices are the annular medial vertices. Each edge of $A(T)$
|
black vertices are the annular medial vertices. Each edge of $A(T)$
|
||||||
carries one tooth: up teeth (blue apexes, outer-boundary medial vertices)
|
carries one tooth: up teeth (blue apexes, outer-boundary medial vertices)
|
||||||
@@ -453,7 +461,7 @@ its boundary and one with none.}
|
|||||||
\node at (0,-2.15) {\scriptsize $\min |R_T(\alpha)|=1$};
|
\node at (0,-2.15) {\scriptsize $\min |R_T(\alpha)|=1$};
|
||||||
\end{scope}
|
\end{scope}
|
||||||
\end{tikzpicture}
|
\end{tikzpicture}
|
||||||
\caption{Three six-face full medial tire graphs found by the boundary-state
|
\caption{Three six-face simple medial tire graphs found by the boundary-state
|
||||||
restriction search. Black vertices are annular medial vertices; blue
|
restriction search. Black vertices are annular medial vertices; blue
|
||||||
vertices are outer boundary medial vertices and red vertices are inner
|
vertices are outer boundary medial vertices and red vertices are inner
|
||||||
boundary medial vertices. The word below each diagram records the
|
boundary medial vertices. The word below each diagram records the
|
||||||
@@ -601,9 +609,11 @@ parent and children.
|
|||||||
Let $G$ be a plane triangulation with level source $S$. The tire-tree
|
Let $G$ be a plane triangulation with level source $S$. The tire-tree
|
||||||
decomposition $\mathcal{T}(G,S)$ of
|
decomposition $\mathcal{T}(G,S)$ of
|
||||||
\cite{bauerfeld-nested-tire-decompositions} induces a rooted
|
\cite{bauerfeld-nested-tire-decompositions} induces a rooted
|
||||||
decomposition of the full medial graph $M(G)$ into full medial tire
|
decomposition of the full medial graph $M(G)$ into medial tire graphs
|
||||||
graphs $\{\mathsf{M}(T): T \in V(\mathcal{T}(G,S))\}$, glued along
|
$\{\mathsf{M}(T): T \in V(\mathcal{T}(G,S))\}$, glued along their
|
||||||
their boundary medial vertex sets.
|
boundary medial vertex sets. A node of this decomposition may be a
|
||||||
|
simple medial tire graph or a compound medial tire graph, depending on
|
||||||
|
whether its annular medial vertices induce one cycle or several.
|
||||||
\end{corollary}
|
\end{corollary}
|
||||||
|
|
||||||
\begin{proof}
|
\begin{proof}
|
||||||
@@ -670,6 +680,17 @@ this framework would follow from a structural reason that these
|
|||||||
restriction sets cannot remain mutually disjoint along every branch of
|
restriction sets cannot remain mutually disjoint along every branch of
|
||||||
the tire tree.
|
the tire tree.
|
||||||
|
|
||||||
|
In this chaining step, the inner side of a parent simple medial tire is
|
||||||
|
read by its singleton down-tooth apex vertices. If the child side is a
|
||||||
|
compound medial tire, then the parent's singleton down-tooth apex
|
||||||
|
vertices are incident to---indeed, are identified with---the up-tooth
|
||||||
|
apex vertices of the compound medial tire, interpreted cycle-by-cycle
|
||||||
|
on its annular medial cycles. Equivalently, the primal edges
|
||||||
|
represented by the parent's singleton down-tooth apexes are exactly the
|
||||||
|
level-cycle interface edges represented on the child side as up-tooth
|
||||||
|
apexes. This is the boundary identification along which the medial
|
||||||
|
boundary states are chained.
|
||||||
|
|
||||||
\begin{definition}[Medial boundary state]
|
\begin{definition}[Medial boundary state]
|
||||||
\label{def:medial-boundary-state}
|
\label{def:medial-boundary-state}
|
||||||
A \emph{medial boundary state} on a boundary set
|
A \emph{medial boundary state} on a boundary set
|
||||||
@@ -786,12 +807,12 @@ $C$. Thus every entrance through $C$ is paired with an exit through
|
|||||||
$C$.
|
$C$.
|
||||||
\end{proof}
|
\end{proof}
|
||||||
|
|
||||||
We now use these Kempe cycles to single out the colourings of a full
|
We now use these Kempe cycles to single out the colourings of a simple
|
||||||
medial tire graph that respect the annular tooth structure.
|
medial tire graph that respect the annular tooth structure.
|
||||||
|
|
||||||
\begin{definition}[Kempe-balanced colouring]
|
\begin{definition}[Kempe-balanced colouring]
|
||||||
\label{def:kempe-balanced}
|
\label{def:kempe-balanced}
|
||||||
Let $\varphi$ be a proper $3$-colouring of the full medial tire graph
|
Let $\varphi$ be a proper $3$-colouring of the simple medial tire graph
|
||||||
$\mathsf{M}(T)$. For a colour pair $P=\{a,b\}$, let $\mathsf{M}(T)_P$ be
|
$\mathsf{M}(T)$. For a colour pair $P=\{a,b\}$, let $\mathsf{M}(T)_P$ be
|
||||||
the subgraph induced by the vertices of colours $a$ and $b$. Since
|
the subgraph induced by the vertices of colours $a$ and $b$. Since
|
||||||
$\mathsf{M}(T)$ need not be $4$-regular, the components of
|
$\mathsf{M}(T)$ need not be $4$-regular, the components of
|
||||||
|
|||||||