Compare commits
78 Commits
6400fdfc5e
..
master
| 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 | |||
| b2439e4bac | |||
| 20e2cc94b4 | |||
| e7e8536559 | |||
| faf9e01139 | |||
| 9d296eb9c8 | |||
| 2ff712b994 | |||
| c6e2c3e1a5 | |||
| d547076cba | |||
| 28d3d55b92 | |||
| 8b47af6036 | |||
| 2b016bc1ca | |||
| bacbdaaf26 | |||
| 1aa76a5226 | |||
| a724a50344 | |||
| b1100b41d9 | |||
| b656b6aed3 | |||
| aecbc5ed28 | |||
| c56da7bb23 | |||
| d094a310d8 | |||
| d8b5975f81 | |||
| d93e8d137a | |||
| a4b3a6fb50 | |||
| dacef25cbb | |||
| cf035243f6 | |||
| 5bed8b4dfb | |||
| 79cbca8e00 | |||
| 8cc94fb6b9 | |||
| 4062e87c61 | |||
| 20fe6c24ca |
@@ -16,6 +16,7 @@ All papers are at `papers/<name>/paper.tex`. The current set:
|
||||
| `iterated_reduction_in_reduced_dual` | An Iterated Reduction in the Reduced Dual |
|
||||
| `level_resolutions_of_maximal_planar_graphs` | Level Resolutions of Maximal Planar Graphs |
|
||||
| `level_switching` | Level Switching |
|
||||
| `medial_tire_decompositions_of_plane_triangulations` | Medial Tire Decompositions of Plane Triangulations |
|
||||
| `nested_tire_decompositions_of_plane_triangulations` | Nested Tire Decompositions of Plane Triangulations |
|
||||
| `plane_depth` | Plane Depth |
|
||||
| `plane_depth_sequencing` | Plane Depth Sequencing |
|
||||
|
||||
@@ -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-coloring-structure}{{4.3}{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{tocindent0}{0pt}
|
||||
\newlabel{tocindent1}{17.77782pt}
|
||||
\newlabel{tocindent2}{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 }
|
||||
\newlabel{fig:flip-proof-case-two}{{2}{4}}
|
||||
\newlabel{thm:no-colored-class-contains-G}{{4.5}{4}}
|
||||
\gdef \@abspage@last{4}
|
||||
\@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{tab:self-flip}{{1}{5}}
|
||||
\gdef \@abspage@last{5}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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/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 ""
|
||||
@@ -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/web2c/pdftex/pdflatex.fmt" 1665017617 2826443 7e98410c533054b636c6470db83a27bc ""
|
||||
"/usr/local/texlive/2022/texmf.cnf" 1647878952 577 209b46be99c9075fd74d4c0369380e8c ""
|
||||
"paper.aux" 1778743331 1709 057e58fcb5472314b0a7029f2c0f7505 "pdflatex"
|
||||
"paper.tex" 1778743323 14730 0431b5dd1f68c135b8365d9286869b8f ""
|
||||
"paper.aux" 1781844090 2204 9c1b0b970c8aeef1caf450ea82c0c00d "pdflatex"
|
||||
"paper.tex" 1781844079 17938 6757143b0e59891047a9dd2db3f626cf ""
|
||||
(generated)
|
||||
"paper.aux"
|
||||
"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
|
||||
restricted \write18 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
|
||||
))
|
||||
[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:
|
||||
13206 strings out of 478268
|
||||
266409 string characters out of 5846347
|
||||
540812 words of memory out of 5000000
|
||||
31041 multiletter control sequences out of 15000+600000
|
||||
13208 strings out of 478268
|
||||
266448 string characters out of 5846347
|
||||
541829 words of memory out of 5000000
|
||||
31043 multiletter control sequences out of 15000+600000
|
||||
477211 words of font info for 59 fonts, out of 8000000 for 9000
|
||||
1302 hyphenation exceptions out of 8191
|
||||
100i,9n,104p,495b,794s stack positions out of 10000i,1000n,20000p,200000b,200000s
|
||||
</usr/local/texlive/2022/texmf-dist/fonts/type1/publ
|
||||
ic/amsfonts/cm/cmbx10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/publi
|
||||
c/amsfonts/cm/cmcsc10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/publi
|
||||
c/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/cmmi5.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/am
|
||||
sfonts/cm/cmmi7.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsf
|
||||
onts/cm/cmmi8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfon
|
||||
ts/cm/cmmi9.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts
|
||||
/cm/cmr10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/c
|
||||
m/cmr6.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/c
|
||||
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/cmr9.pf
|
||||
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/cmsy7.pfb><
|
||||
/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy8.pfb></u
|
||||
sr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy9.pfb></usr
|
||||
/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti10.pfb></usr/
|
||||
local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti8.pfb></usr/lo
|
||||
cal/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/symbols/msam10.pfb>
|
||||
Output written on paper.pdf (4 pages, 246274 bytes).
|
||||
</usr/local/texlive/2022/texmf-dist/fonts/type1/public/a
|
||||
msfonts/cm/cmbx10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/am
|
||||
sfonts/cm/cmcsc10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/am
|
||||
sfonts/cm/cmex10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/ams
|
||||
fonts/cm/cmmi10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsf
|
||||
onts/cm/cmmi5.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfon
|
||||
ts/cm/cmmi7.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts
|
||||
/cm/cmmi8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/c
|
||||
m/cmmi9.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/
|
||||
cmr10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cm
|
||||
r6.pfb></usr/local/texlive/2022/texmf-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-dist/fonts/type1/public/amsfonts/cm/cmr9.pfb></
|
||||
usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy10.pfb></u
|
||||
sr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy7.pfb></usr
|
||||
/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy8.pfb></usr/l
|
||||
ocal/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmsy9.pfb></usr/loc
|
||||
al/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti10.pfb></usr/loca
|
||||
l/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmti8.pfb></usr/local/
|
||||
texlive/2022/texmf-dist/fonts/type1/public/amsfonts/symbols/msam10.pfb>
|
||||
Output written on paper.pdf (5 pages, 251916 bytes).
|
||||
PDF statistics:
|
||||
120 PDF objects out of 1000 (max. 8388607)
|
||||
73 compressed objects within 1 object stream
|
||||
123 PDF objects out of 1000 (max. 8388607)
|
||||
75 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)
|
||||
|
||||
|
||||
@@ -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.
|
||||
\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}
|
||||
|
||||
%-----------------------------------------------------------------------
|
||||
|
||||
@@ -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{thm:intertwining-iff-hamiltonian-dual}{{4.7}{7}{}{theorem.4.7}{}}
|
||||
\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}
|
||||
\@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 }
|
||||
\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}{}}
|
||||
\@writefile{toc}{\contentsline {subsection}{\tocsubsection {}{}{The cyclically-$5$-connected case: $n = 24$}}{8}{section*.3}\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}{{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{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}{}}
|
||||
\@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 }
|
||||
\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 {}{}{The cyclically-$5$-connected case: $n = 24$}}{10}{section*.3}\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 }
|
||||
\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}
|
||||
\newlabel{tocindent-1}{0pt}
|
||||
\newlabel{tocindent0}{14.69437pt}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Fdb version 3
|
||||
["pdflatex"] 1779469001 "/Users/didericis/Code/math-research/papers/even_level_graph_generators/paper.tex" "paper.pdf" "paper" 1779469003
|
||||
"/Users/didericis/Code/math-research/papers/even_level_graph_generators/paper.tex" 1779468999 23834 39061385c4cc2522155026d2f8574bbd ""
|
||||
["pdflatex"] 1781848805 "paper.tex" "paper.pdf" "paper" 1781848808
|
||||
"/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 ""
|
||||
@@ -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/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/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/cmcsc10.pfb" 1248133631 32001 6aeea3afe875097b1eb0da29acd61e28 ""
|
||||
"/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/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/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/tex/context/base/mkii/supp-pdf.mkii" 1461363279 71627 94eb9990bed73c364d7f53f960cc8c5b ""
|
||||
"/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_levels.png" 1779389598 88029 5564f46c0a183f3777727b651e7dc461 ""
|
||||
"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 ""
|
||||
"paper.aux" 1779469003 8486 a43934b41579f5535915f5341c4d1db7 "pdflatex"
|
||||
"paper.out" 1779469003 1088 cf07a31709ba02be3ba2bc89322768d0 "pdflatex"
|
||||
"paper.tex" 1779468999 23834 39061385c4cc2522155026d2f8574bbd ""
|
||||
"paper.aux" 1781848808 13255 0b6591b567d7fefa0f2a1ac1716e57fc "pdflatex"
|
||||
"paper.out" 1781848808 2030 d310c1d6d9f73494fc676a3dd19e31e8 "pdflatex"
|
||||
"paper.tex" 1781848188 38745 a8bea15e6bfeb354af5c8a7ce030fc53 ""
|
||||
(generated)
|
||||
"paper.aux"
|
||||
"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-dist/web2c/texmf.cnf
|
||||
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
|
||||
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 /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.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/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/cmtt10.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
|
||||
restricted \write18 enabled.
|
||||
%&-line parsing enabled.
|
||||
@@ -409,86 +409,88 @@ Underfull \hbox (badness 1112) in paragraph at lines 391--391
|
||||
the automorphism-free count
|
||||
[]
|
||||
|
||||
[6]
|
||||
[6] [7]
|
||||
|
||||
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):
|
||||
(hyperref) removing `math shift' on input line 497.
|
||||
(hyperref) removing `math shift' on input line 536.
|
||||
|
||||
[7]
|
||||
<figures/n21_duals.png, id=137, 1373.13pt x 867.24pt>
|
||||
[8]
|
||||
<figures/n21_duals.png, id=143, 1373.13pt x 867.24pt>
|
||||
File: figures/n21_duals.png Graphic file (type 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.
|
||||
|
||||
|
||||
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):
|
||||
(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):
|
||||
(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):
|
||||
(hyperref) removing `math shift' on input line 570.
|
||||
(hyperref) removing `math shift' on input line 609.
|
||||
|
||||
[8]
|
||||
<figures/fig210_dual.png, id=144, 542.025pt x 542.025pt>
|
||||
[9 <./figures/n21_duals.png>]
|
||||
<figures/fig210_dual.png, id=152, 542.025pt x 542.025pt>
|
||||
File: figures/fig210_dual.png Graphic file (type 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.
|
||||
[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):
|
||||
(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):
|
||||
(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):
|
||||
(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
|
||||
Faulkner--Younger's min-i-mal-ity (no cycli-cally $5$-connected
|
||||
[]
|
||||
|
||||
[10 <./figures/fig210_dual.png>]
|
||||
<figures/core_n25_dual.png, id=160, 578.16pt x 578.16pt>
|
||||
[10]
|
||||
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)
|
||||
<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.
|
||||
[11] [12 <./figures/core_n25_dual.png>]
|
||||
[13] (./paper.aux)
|
||||
[12 <./figures/core_n25_dual.png>] [13] (./paper.aux)
|
||||
Package rerunfilecheck Info: File `paper.out' has not changed.
|
||||
(rerunfilecheck) Checksum: D310C1D6D9F73494FC676A3DD19E31E8;2030.
|
||||
)
|
||||
Here is how much of TeX's memory you used:
|
||||
9791 strings out of 478268
|
||||
151651 string characters out of 5846347
|
||||
455389 words of memory out of 5000000
|
||||
27676 multiletter control sequences out of 15000+600000
|
||||
9793 strings out of 478268
|
||||
151677 string characters out of 5846347
|
||||
455969 words of memory out of 5000000
|
||||
27677 multiletter control sequences out of 15000+600000
|
||||
475834 words of font info for 54 fonts, out of 8000000 for 9000
|
||||
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/cmcsc10.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
|
||||
/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmtt10.pfb></usr/local/texlive/
|
||||
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:
|
||||
253 PDF objects out of 1000 (max. 8388607)
|
||||
192 compressed objects within 2 object streams
|
||||
48 named destinations out of 1000 (max. 500000)
|
||||
256 PDF objects out of 1000 (max. 8388607)
|
||||
195 compressed objects within 2 object streams
|
||||
49 named destinations out of 1000 (max. 500000)
|
||||
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
|
||||
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$}
|
||||
|
||||
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 |
|
After Width: | Height: | Size: 160 KiB |
@@ -0,0 +1,283 @@
|
||||
# Chained seams: the medial pigeonhole, located
|
||||
|
||||
Companion to the interface-alphabet results (`kempe_interface_admissibility_probe.py`,
|
||||
`kempe_tile_overlap_probe.py`). This note pursues the paper's §6 *medial pigeonhole
|
||||
programme* — the restriction relation `R_T` between a tire's outer and inner boundary
|
||||
states, and the open *chain-pigeonhole conjecture* — at the data level.
|
||||
|
||||
Scripts: `kempe_transfer_relation_probe.py`, `kempe_uniform_family_probe.py`.
|
||||
|
||||
## Background
|
||||
|
||||
A nested chain `T_0 ⊃ T_1 ⊃ …` glues into a proper 3-colouring of `M(G)` iff
|
||||
consecutive boundary states match: `inner-state(T_i) = outer-state(T_{i+1})`
|
||||
(compatible family, paper Prop "gluing criterion"). Boundary states are necklaces
|
||||
(colours permuted, boundary walk rotated/reflected). The restriction relation
|
||||
|
||||
```
|
||||
R_T ⊆ outer-states × inner-states
|
||||
```
|
||||
|
||||
records which (outer, inner) boundary-state pairs one Kempe-balanced colouring of `T`
|
||||
realises jointly. The chain-pigeonhole conjecture asks whether nested `R_T`'s can ever
|
||||
compose to empty.
|
||||
|
||||
Established earlier: the realised boundary alphabet (each side) is exactly the full
|
||||
parity-admissible set, identical inner vs outer, `n`-independent (n=9, n=12, m=3..8);
|
||||
and every *pair* of tiles overlaps, so single seams never dead-end.
|
||||
|
||||
## Finding 1 — `R_T` is genuinely coupled
|
||||
|
||||
`R_T` is **not** a product of its projections: a tile's inner boundary necklace
|
||||
constrains its outer one. Seen at n=9 in the `(p,q) = (4,5)` and `(5,4)` size classes
|
||||
(`product? = False`), and broadly at n=12. So "every seam individually realisable"
|
||||
does **not** trivially pass through a tile.
|
||||
|
||||
## Finding 2 — the uniform shortcut works at n=9, breaks at n=12
|
||||
|
||||
Question: is there one boundary state `σ_m` per level-cycle *size* that threads every
|
||||
tile simultaneously (outer face and every inner face)? If so, painting every level
|
||||
cycle `σ_m` glues any tree with no pigeonhole.
|
||||
|
||||
- **n=9: FEASIBLE.** Unique universal per size (`|D[m]| = 1`), and notably *not*
|
||||
monochromatic — the balanced-block necklaces
|
||||
|
||||
```
|
||||
σ3 = 012 σ4 = 0011 σ5 = 00012 σ6 = 000011
|
||||
```
|
||||
|
||||
threads all 66 tiles. (Caveat: n=9 has **no** branching tiles.)
|
||||
|
||||
- **n=12: INFEASIBLE.** 1237 tiles (1029 bite, 175 genuinely branching). The CSP fails
|
||||
for one sharp reason: **size-7 seams admit no universal state** (`|D[7]| = 0`). The
|
||||
211 size-7 boundaries realise all 10 admissible necklaces between them, but their
|
||||
intersection is empty — the near-universal `0001112` lands on **210 of 211**,
|
||||
blocked by a single tile.
|
||||
|
||||
Per-size universal domain sizes at n=12: `3:1 4:1 5:1 6:2 7:0 8:4 9:1`.
|
||||
|
||||
## Finding 3 — the failure is SPORADIC, not a monotone trend
|
||||
|
||||
`kempe_universal_trend_probe.py` tracks `|D[m]|` (universal count) per size across
|
||||
`n = 6..13`. Legend `|D[m]| (best_coverage / num_boundaries)`; `*` = odd size:
|
||||
|
||||
```
|
||||
n= 6: m3*=1(6/6) m4=2(2/2) m6=4(1/1)
|
||||
n= 7: m3*=1(10/10) m4=1(8/8) m5*=1(2/2) m7*=3(1/1)
|
||||
n= 8: m3*=1(28/28) m4=2(21/21) m5*=1(10/10) m6=2(3/3) m8=8(1/1)
|
||||
n= 9: m3*=1(49/49) m4=1(52/52) m5*=1(28/28) m6=1(14/14) m7*=2(3/3) m9*=8(1/1)
|
||||
n=10: m3*=1(118) m4=2(106) m5*=1(86) m6=2(46) m7*=1(16/16) m8=4(4/4) m10=18(1/1)
|
||||
n=11: m3*=1(310) m4=1(263) m5*=1(188) m6=1(139) m7*=2(60/60) m8=2(20/20) m9*=3(4/4) m11*=21(1/1)
|
||||
n=12: m3*=1(849) m4=1(691) m5*=1(534) m6=2(353) m7*=0(210/211) m8=3(88) m9*=1(24/24) m10=6(5/5) m12=48(1/1)
|
||||
n=13: m3*=1(2457) m4=1(1805) m5*=1(1386) m6=1(1070) m7*=2(579/579) m8=1(315) m9*=2(110) m10=1(28) m11*=7(5) m13*=63(1/1)
|
||||
```
|
||||
|
||||
The earlier "universals vanish as the tile population grows" reading is **wrong**.
|
||||
Across all `n = 6..13` and all sizes, the **only** empty universal anywhere is
|
||||
`(n=12, m=7)`. Size 7 has *more* boundaries at n=13 (579) than n=12 (211), yet its
|
||||
universal is back (`|D|=2`). So the failure is **sporadic**, not monotone — a
|
||||
number-theoretic quirk of `n=12`, not a scaling law. Note also that best_coverage is
|
||||
always near-total: the most-shared necklace reaches all-but-rarely-one boundary, so
|
||||
the universal is "almost there" even when it fails.
|
||||
|
||||
### The single breaker (n=12, m=7)
|
||||
The lone size-7 boundary that kills the universal is the **outer rim** of
|
||||
|
||||
```
|
||||
word = UUUDUDUDUDUD bite = (3,11) (7 up teeth)
|
||||
```
|
||||
|
||||
— the most-alternating word with a near-full-span bite. Its outer rim realises 9 of
|
||||
the 10 admissible size-7 necklaces, missing exactly `0001112` (the balanced two-block
|
||||
`3+3+1` pattern); every other size-7 boundary realises `0001112`. One exceptional tile
|
||||
breaks the shortcut.
|
||||
|
||||
## Finding 4 — interpretation
|
||||
|
||||
The uniform "paint every seam the same" shortcut **almost always works** (universals
|
||||
non-empty with near-total coverage, even at thousands of boundaries), but is
|
||||
**fragile**: a single exceptional tile can empty a per-size universal, as at
|
||||
`(n=12, m=7)`. So the shortcut cannot be relied on in general — yet its failures are
|
||||
rare, not systematic. The per-interface pigeonhole choice is needed precisely to
|
||||
absorb these sporadic breakers. **This is not an obstruction to gluing** — pairwise
|
||||
overlap always holds, so chains still glue by choosing states per interface. The
|
||||
conjecture's difficulty is thus concentrated in rare exceptional tiles and the
|
||||
branch-coupled selection around them, not in any single seam or in a scaling trend.
|
||||
|
||||
## Finding 5 — restricting to no separating triangles REMOVES the obstruction
|
||||
|
||||
The 4CT reduces to triangulations with no separating triangles (internally
|
||||
4-connected). In the tread model, a separating (non-facial) triangle in `G` shows up
|
||||
as a **length-3 boundary walk** of a tread: an outer rim of 3 up teeth, or an inner
|
||||
face holding exactly 3 singleton down teeth (the `012` seam). Restricting to tiles
|
||||
with **no length-3 boundary** (`kempe_universal_trend_probe.py --no-tri`,
|
||||
`kempe_uniform_family_probe.py --no-tri`):
|
||||
|
||||
- The n=12 breaker `UUUDUDUDUDUD` bite=(3,11) has a size-3 inner face (the bite
|
||||
encloses exactly `d5,d7,d9`), so it is **excluded** — along with its kin.
|
||||
- The size-7 universal at n=12 is **restored**: `|D[7]|` goes `0 → 2`, on a reduced
|
||||
population (211 → 76 size-7 boundaries). Across `n = 6..13` and all sizes, **every
|
||||
`|D[m]| ≥ 1`** — no empty universal anywhere.
|
||||
- The uniform-family CSP becomes **FEASIBLE at n=12** (was infeasible). The threading
|
||||
family is now the simplest one — **monochromatic on even sizes, min-cut on odd**:
|
||||
|
||||
```
|
||||
σ4 = 0000 σ5 = 00012 σ6 = 000000 σ7 = 0000012 σ8 = 00000000
|
||||
```
|
||||
|
||||
(Without the restriction, `σ4` had to be `0011`, not monochromatic, because
|
||||
separating-triangle tiles blocked the all-0 rim. Removing them lets monochromatic
|
||||
even seams work.)
|
||||
|
||||
So the **only** universal failure we found (n=12, size 7) was an artifact of admitting
|
||||
tiles that correspond to **non-4-connected** triangulations. On the 4CT-relevant class
|
||||
(no separating triangles), the uniform seam family exists and gluing is constructively
|
||||
trivial throughout the tested range — no pigeonhole needed.
|
||||
|
||||
Caveat: even at n=12 the restricted population has **0 branching tiles** (multi-inner
|
||||
faces all require a size-3 face at this n), so the branching case stays untested under
|
||||
the restriction; and this is `n ≤ 13` only.
|
||||
|
||||
## Finding 6 — smallest branching n (`kempe_branching_min_probe.py`)
|
||||
|
||||
Branching tile = `≥2` inner faces carrying singleton-down interfaces (a tree node with
|
||||
`≥2` children).
|
||||
|
||||
```
|
||||
n #tiles branching no-tri branching
|
||||
9 81 0 0
|
||||
10 203 0 0
|
||||
11 503 30 0 <- unrestricted branching first appears
|
||||
12 1344 175 0
|
||||
13 3586 789 0
|
||||
14 9929 3024 193 <- no-separating-triangle branching first appears
|
||||
15 27481 10538 1022
|
||||
```
|
||||
|
||||
- Unrestricted branching first appears at **n=11**.
|
||||
- No-separating-triangle branching first appears at **n=14**. Smallest example:
|
||||
|
||||
```
|
||||
word = UUUUDDDDDDDDDD bite = (8,13) p = 4
|
||||
inner faces: root {4,5,6,7} (size 4) + bite(8,13) {9,10,11,12} (size 4)
|
||||
```
|
||||
|
||||
4 up teeth, two size-4 inner faces — branching, no length-3 boundary. (Reason for the
|
||||
gap: a branching node needs `≥2` inner faces each `≥4` singletons plus `≥4` up teeth
|
||||
plus the bite pair = `4 + 4 + 4 + 2 = 14` edges.)
|
||||
|
||||
So **n=14** is the smallest place to test the uniform family / `R_T` composition on a
|
||||
genuine *branching* no-separating-triangle tile — the conjecture's real case.
|
||||
|
||||
## Finding 7 — the branching case (n=14) is FEASIBLE with one regular family
|
||||
|
||||
Full uniform-family CSP at n=14 `--no-tri` (4403 tiles, 3766 bite, **193 branching**):
|
||||
**FEASIBLE** — a single uniform family threads every tile, branching nodes included
|
||||
(each branching tile shows the uniform state on its outer rim AND both inner faces at
|
||||
once). Domains `|D[m]|`: `4:2 5:1 6:2 7:2 8:3 9:3 10:5`. The witness family is fully
|
||||
regular:
|
||||
|
||||
```
|
||||
σ_m = 0^m (monochromatic) if m even
|
||||
σ_m = 0^(m-2) 1 2 (one block + 1 + 2) if m odd
|
||||
4:0000 5:00012 6:000000 7:0000012 8:00000000 9:000000012 10:0000000000
|
||||
```
|
||||
|
||||
A direct candidate test (just the 193 branching tiles) confirmed it independently:
|
||||
the monochromatic-even / min-cut-odd family threads **193/193**.
|
||||
|
||||
So on the 4CT-relevant class (no separating triangles), the chained-seam pigeonhole is
|
||||
**constructively resolved throughout the tested range** (n = 9, 12, 14, including the
|
||||
first branching nodes) by one explicit regular seam family: paint every even level
|
||||
cycle one colour, and every odd level cycle as a single monochromatic block plus the
|
||||
two parity-forced off-colour vertices.
|
||||
|
||||
## Finding 8 — the regular family is REFUTED at n=15
|
||||
|
||||
`kempe_regular_family_test.py` (tests the fixed regular family with per-tile
|
||||
early-exit). At n=12 it threads 614/614 (matches the CSP). **At n=15 it FAILS**, on two
|
||||
distinct classes of no-separating-triangle tiles:
|
||||
|
||||
- **non-branching, large even outer + odd inner:** e.g. `UUUUUUDUDUDUDUD` (no bite),
|
||||
p=10, inner face size 5. Monochromatic `σ10 = 0^10` on the 10-up rim cannot coexist
|
||||
with `σ5 = 00012` on the inner face.
|
||||
- **branching, odd outer + two even inner faces:** e.g. `UDUDUDDUDUDDDDD` bite=(5,12),
|
||||
p=5, faces `[4,4]`. `σ5` outer with monochromatic `σ4` on *both* inner faces is not
|
||||
jointly realisable. Branching tiles: **1011/1022 threaded, 11 fail.**
|
||||
|
||||
So the clean regular conjecture is **false**. Crucially this refutation is exactly the
|
||||
`R_T` **coupling** (Finding 1) asserting itself at scale: the regular family sets outer
|
||||
and inner states *independently per size*, but `R_T` is not a product, so a large
|
||||
monochromatic rim over-constrains the annular cycle and forbids the paired inner
|
||||
necklace. The failure is not about branching per se — it hits large even rims (non-
|
||||
branching) and small odd rims with two even children (branching) alike.
|
||||
|
||||
### What it means for strategy
|
||||
The uniform "one state per size, everywhere" family was a **too-strong shortcut** —
|
||||
much stronger than the chain-pigeonhole conjecture, which only needs *some* compatible
|
||||
selection per chain with freedom to choose a *different* state at each interface. Its
|
||||
failure costs a cheap constructive route, **not** the conjecture: pairwise overlap
|
||||
still always holds. The load transfers to the genuine object — **per-interface
|
||||
selection respecting `R_T` coupling**, i.e. composing `R_T` along chains/trees with
|
||||
per-seam freedom rather than a global family.
|
||||
|
||||
## Finding 9 — `R_T` composition: the pigeonhole verified exhaustively for n ≤ 14
|
||||
|
||||
`kempe_rt_composition_probe.py` — the conjecture proper, with full per-interface
|
||||
freedom. Two modeling facts were established first:
|
||||
|
||||
- **The seam is exactly the singleton down apexes** of one inner face. A bite apex's
|
||||
edge has parent tread faces on *both* sides, so it is internal to the parent's tile
|
||||
and shares no medial vertex with the child.
|
||||
- **Necklace states are exact, not approximate**: a child tile attaches with free
|
||||
dihedral placement, so its realisable outer-sequence set is dihedral-closed and
|
||||
"necklace match ⟺ some aligned sequence match". (This is the paper's own Def. of
|
||||
medial boundary state.)
|
||||
|
||||
Define `Ext(T)` = necklaces realisable on a subtree's outer seam by a compatible
|
||||
Kempe-balanced selection: `Ext(T) = {o : ∃(o, i⃗) ∈ R_T^joint, i_j ∈ Ext(C_j)}`,
|
||||
leaves = all-bite tiles (no singleton interfaces, degenerate inner boundaries). The
|
||||
maps are monotone, so the **antichain of minimal reachable Ext sets per seam size**
|
||||
decides whether ∅ is reachable. Relations are cached (`kempe_rt_relations_cache.json`).
|
||||
|
||||
Result over all no-length-3-boundary tiles with `n ≤ 14` (7750 tiles, 1966 distinct
|
||||
relations, 149 leaf, **27 branching**):
|
||||
|
||||
- **∅ is NOT reachable.** Every tree assemblable from this universe — branching
|
||||
included — admits a compatible Kempe-balanced selection. Since every real tire tree
|
||||
whose treads have `n ≤ 14` and no separating triangles is such a tree, the
|
||||
chain-pigeonhole conjecture is **verified exhaustively for that class**.
|
||||
- **Composition saturates in 2 rounds.** The minimal antichains are already closed
|
||||
under every tile map after one pass: restriction does *not* accumulate along chains
|
||||
— it bottoms out immediately. This is exactly the structural behaviour the paper's
|
||||
§6 programme hoped for ("restriction sets cannot remain mutually disjoint").
|
||||
- **Restriction is real but bounded.** Tightest subtrees per seam size: m=4 forces
|
||||
2 of 3 necklaces, **m=5 forces a single necklace `{00012}`**, m=7 forces 3 of 10,
|
||||
m=8 forces 8 of 34, m=14 forces 133 of 7515. Yet no parent relation ever misses a
|
||||
minimal set entirely.
|
||||
- **The blocky states are always offered.** Every smallest minimal Ext set contains
|
||||
the regular necklace of its size (`0^m` or `0^{m-2}12`-type). The regular family
|
||||
failed (Finding 8) because a single tile sometimes cannot take blocky-in and
|
||||
blocky-out *jointly* — but per-interface freedom routes around it, and the data
|
||||
shows subtrees always keep the blocky option available downward.
|
||||
|
||||
Caveats: (i) terminal facial triangles (innermost treads ending on a face of `G`) are
|
||||
not yet modelled — our leaves are only the degenerate-inner-boundary all-bite tiles;
|
||||
adding 3-faces as terminal children with `Ext = {012}` is a small extension. (ii) The
|
||||
universe is abstract: it is a *superset* of real trees (good for the positive verdict;
|
||||
an abstract obstruction, had one appeared, would still have needed a realisability
|
||||
check).
|
||||
|
||||
## Open threads
|
||||
|
||||
- **Terminal-3-face leaves.** Extend the fixpoint with facial-triangle termination
|
||||
(parent tiles with one 3-singleton face allowed as terminal); rerun.
|
||||
- **Push `max_n`.** n=15 adds ~1 hr of one-time classification to the cache; the
|
||||
2-round saturation suggests verdicts stabilise quickly, but a deeper universe is
|
||||
the only way an obstruction could still appear.
|
||||
- **Why saturation?** The 2-round fixpoint convergence is the empirical shadow of a
|
||||
provable statement: composing tire restriction relations stabilises after one
|
||||
level. A proof of that, with the minimal antichains characterised, would *be* the
|
||||
pigeonhole lemma for this class.
|
||||
- **Structural lemma for the n=15 uniform failures** (why a monochromatic large rim
|
||||
forbids the paired odd-inner necklace) — still open, now lower priority.
|
||||
@@ -0,0 +1,358 @@
|
||||
"""Check the medial annular-cycle almost-two-colour condition.
|
||||
|
||||
For each generated plane triangulation G and each requested level source:
|
||||
|
||||
1. Build the full medial graph M(G).
|
||||
2. Find depth-component tire annular medial subgraphs.
|
||||
3. Enumerate simple cycles in those annular subgraphs.
|
||||
4. Search for a proper vertex 3-colouring of M(G) such that every
|
||||
such cycle uses two colours except at at most one vertex.
|
||||
|
||||
Run with Sage, for example:
|
||||
|
||||
sage -python papers/medial_tire_decompositions_of_plane_triangulations/experiments/check_medial_annular_cycle_condition.py --n-min 4 --n-max 8
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from collections import defaultdict, deque
|
||||
from itertools import combinations
|
||||
from typing import Any, Iterable, Iterator, Sequence, cast
|
||||
|
||||
from sage.all import Graph, graphs # type: ignore[attr-defined] # pylint: disable=no-name-in-module
|
||||
from sage.graphs.graph_coloring import all_graph_colorings # type: ignore[attr-defined] # pylint: disable=no-name-in-module
|
||||
|
||||
|
||||
Edge = tuple[Any, Any]
|
||||
Coloring = dict[Edge, int]
|
||||
Source = tuple[Any, ...]
|
||||
|
||||
|
||||
def vertex_key(v: Any) -> str:
|
||||
return repr(v)
|
||||
|
||||
|
||||
def edge_key(u: Any, v: Any) -> Edge:
|
||||
return (u, v) if vertex_key(u) <= vertex_key(v) else (v, u)
|
||||
|
||||
|
||||
def is_induced_cycle(g: Graph, vertices: Sequence[Any]) -> bool:
|
||||
if len(vertices) < 3:
|
||||
return False
|
||||
h = cast(Graph, g.subgraph(list(vertices)))
|
||||
return h.is_connected() and h.num_edges() == len(vertices) and all(
|
||||
h.degree(v) == 2 for v in h.vertices()
|
||||
)
|
||||
|
||||
|
||||
def induced_cycle_sources(g: Graph, max_size: int | None = None) -> Iterator[Source]:
|
||||
vertices = sorted(g.vertices(), key=vertex_key)
|
||||
upper = len(vertices) if max_size is None else min(max_size, len(vertices))
|
||||
for k in range(3, upper + 1):
|
||||
for subset in combinations(vertices, k):
|
||||
if is_induced_cycle(g, subset):
|
||||
yield tuple(subset)
|
||||
|
||||
|
||||
def level_sources(g: Graph, mode: str, max_cycle_source_size: int | None) -> Iterator[Source]:
|
||||
if mode in ("vertex", "all"):
|
||||
for v in sorted(g.vertices(), key=vertex_key):
|
||||
yield (v,)
|
||||
if mode in ("cycle", "all"):
|
||||
yield from induced_cycle_sources(g, max_cycle_source_size)
|
||||
|
||||
|
||||
def distances_from_source(g: Graph, source: Source) -> dict[Any, int]:
|
||||
if len(source) == 1:
|
||||
return dict(g.shortest_path_lengths(source[0]))
|
||||
distances = {v: 0 for v in source}
|
||||
queue: deque[Any] = deque(source)
|
||||
while queue:
|
||||
v = queue.popleft()
|
||||
for w in g.neighbor_iterator(v):
|
||||
if w in distances:
|
||||
continue
|
||||
distances[w] = distances[v] + 1
|
||||
queue.append(w)
|
||||
return distances
|
||||
|
||||
|
||||
def embedded_copy(g: Graph) -> Graph:
|
||||
emb = cast(Graph, g.copy())
|
||||
if not emb.is_planar(set_embedding=True):
|
||||
raise ValueError("graph is not planar")
|
||||
return emb
|
||||
|
||||
|
||||
def medial_graph(g: Graph) -> Graph:
|
||||
"""Build the full medial graph from the embedding rotation at vertices."""
|
||||
emb = embedded_copy(g)
|
||||
rotation = emb.get_embedding()
|
||||
m = Graph()
|
||||
medial_vertices = [edge_key(u, v) for u, v, _ in emb.edge_iterator()]
|
||||
m.add_vertices(medial_vertices)
|
||||
for v, neighbors in rotation.items():
|
||||
if len(neighbors) < 2:
|
||||
continue
|
||||
n = len(neighbors)
|
||||
for i in range(n):
|
||||
e1 = edge_key(v, neighbors[i])
|
||||
e2 = edge_key(v, neighbors[(i + 1) % n])
|
||||
if e1 != e2:
|
||||
m.add_edge(e1, e2)
|
||||
return m
|
||||
|
||||
|
||||
def face_vertices(face: Sequence[tuple[Any, Any]]) -> set[Any]:
|
||||
out: set[Any] = set()
|
||||
for u, v in face:
|
||||
out.add(u)
|
||||
out.add(v)
|
||||
return out
|
||||
|
||||
|
||||
def face_edges(face: Sequence[tuple[Any, Any]]) -> set[Edge]:
|
||||
return {edge_key(u, v) for u, v in face}
|
||||
|
||||
|
||||
def dual_components_by_depth(
|
||||
g: Graph, source: Source
|
||||
) -> list[tuple[int, list[int], set[Edge]]]:
|
||||
"""Return (depth, face-indices, annular-edge-set) for each depth component."""
|
||||
emb = embedded_copy(g)
|
||||
distances = distances_from_source(emb, source)
|
||||
faces = emb.faces()
|
||||
f_vertices = [face_vertices(face) for face in faces]
|
||||
f_edges = [face_edges(face) for face in faces]
|
||||
depths = [min(distances[v] for v in verts) for verts in f_vertices]
|
||||
|
||||
edge_faces: dict[Edge, list[int]] = defaultdict(list)
|
||||
for idx, edges in enumerate(f_edges):
|
||||
for edge in edges:
|
||||
edge_faces[edge].append(idx)
|
||||
|
||||
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])
|
||||
|
||||
components = []
|
||||
seen = [False] * len(faces)
|
||||
for start in range(len(faces)):
|
||||
if seen[start]:
|
||||
continue
|
||||
depth = depths[start]
|
||||
comp = [start]
|
||||
seen[start] = True
|
||||
stack = [start]
|
||||
while stack:
|
||||
f = stack.pop()
|
||||
for h in dual_adj[f]:
|
||||
if not seen[h] and depths[h] == depth:
|
||||
seen[h] = True
|
||||
comp.append(h)
|
||||
stack.append(h)
|
||||
|
||||
annular_edges: set[Edge] = set()
|
||||
for f in comp:
|
||||
for u, v in f_edges[f]:
|
||||
if {distances[u], distances[v]} == {depth, depth + 1}:
|
||||
annular_edges.add(edge_key(u, v))
|
||||
if len(annular_edges) >= 3:
|
||||
components.append((depth, comp, annular_edges))
|
||||
return components
|
||||
|
||||
|
||||
def simple_cycle_vertex_sets(g: Graph) -> set[frozenset[Any]]:
|
||||
vertices = sorted(g.vertices(), key=repr)
|
||||
index = {v: i for i, v in enumerate(vertices)}
|
||||
cycles: set[frozenset[Any]] = set()
|
||||
|
||||
def dfs(start: Any, current: Any, path: list[Any], seen: set[Any]) -> None:
|
||||
for nxt in g.neighbor_iterator(current):
|
||||
if nxt == start:
|
||||
if len(path) >= 3:
|
||||
cycles.add(frozenset(path))
|
||||
continue
|
||||
if nxt in seen or index[nxt] <= index[start]:
|
||||
continue
|
||||
seen.add(nxt)
|
||||
path.append(nxt)
|
||||
dfs(start, nxt, path, seen)
|
||||
path.pop()
|
||||
seen.remove(nxt)
|
||||
|
||||
for start in vertices:
|
||||
dfs(start, start, [start], {start})
|
||||
return cycles
|
||||
|
||||
|
||||
def annular_medial_cycles(g: Graph, source: Source) -> list[frozenset[Edge]]:
|
||||
m = medial_graph(g)
|
||||
cycles: list[frozenset[Edge]] = []
|
||||
seen: set[frozenset[Edge]] = set()
|
||||
for _depth, _faces, annular_edges in dual_components_by_depth(g, source):
|
||||
sub = cast(Graph, m.subgraph(list(annular_edges)))
|
||||
for cycle in simple_cycle_vertex_sets(sub):
|
||||
typed = frozenset(cast(Iterable[Edge], cycle))
|
||||
if typed not in seen:
|
||||
seen.add(typed)
|
||||
cycles.append(typed)
|
||||
return cycles
|
||||
|
||||
|
||||
def almost_two_coloured(cycle: frozenset[Edge], coloring: Coloring) -> bool:
|
||||
counts = defaultdict(int)
|
||||
for vertex in cycle:
|
||||
counts[coloring[vertex]] += 1
|
||||
return min(counts.get(c, 0) for c in range(3)) <= 1
|
||||
|
||||
|
||||
def first_cycle_violation(
|
||||
cycles: Sequence[frozenset[Edge]], coloring: Coloring
|
||||
) -> frozenset[Edge] | None:
|
||||
for cycle in cycles:
|
||||
if not almost_two_coloured(cycle, coloring):
|
||||
return cycle
|
||||
return None
|
||||
|
||||
|
||||
def color_counts(cycle: frozenset[Edge], coloring: Coloring) -> dict[int, int]:
|
||||
counts = {0: 0, 1: 0, 2: 0}
|
||||
for vertex in cycle:
|
||||
counts[coloring[vertex]] += 1
|
||||
return counts
|
||||
|
||||
|
||||
def coloring_witness(
|
||||
m: Graph,
|
||||
cycles: Sequence[frozenset[Edge]],
|
||||
max_colorings: int | None,
|
||||
) -> tuple[Coloring | None, int, bool, frozenset[Edge] | None, Coloring | None]:
|
||||
checked = 0
|
||||
last_violation = None
|
||||
for raw in all_graph_colorings(m, 3, vertex_color_dict=True):
|
||||
coloring = cast(Coloring, raw)
|
||||
checked += 1
|
||||
violation = first_cycle_violation(cycles, coloring)
|
||||
if violation is None:
|
||||
return coloring, checked, True, None, None
|
||||
last_violation = violation
|
||||
if max_colorings is not None and checked >= max_colorings:
|
||||
return None, checked, False, last_violation, coloring
|
||||
return None, checked, True, last_violation, coloring
|
||||
|
||||
|
||||
def source_label(source: Source) -> str:
|
||||
if len(source) == 1:
|
||||
return f"vertex:{source[0]}"
|
||||
return "cycle:{" + ",".join(map(str, source)) + "}"
|
||||
|
||||
|
||||
def graphs_to_check(n: int, max_graphs: int | None):
|
||||
for idx, g in enumerate(graphs.triangulations(n)):
|
||||
if max_graphs is not None and idx >= max_graphs:
|
||||
break
|
||||
yield idx, cast(Graph, g)
|
||||
|
||||
|
||||
def run(args: argparse.Namespace) -> None:
|
||||
total_cases = 0
|
||||
skipped_no_cycles = 0
|
||||
witnesses = 0
|
||||
failures = []
|
||||
inconclusive = []
|
||||
|
||||
for n in range(args.n_min, args.n_max + 1):
|
||||
print(f"n={n}")
|
||||
for graph_idx, g in graphs_to_check(n, args.max_graphs_per_n):
|
||||
m = medial_graph(g)
|
||||
sources = list(level_sources(g, args.sources, args.max_cycle_source_size))
|
||||
if args.max_sources_per_graph is not None:
|
||||
sources = sources[: args.max_sources_per_graph]
|
||||
for source in sources:
|
||||
cycles = annular_medial_cycles(g, source)
|
||||
if not cycles:
|
||||
skipped_no_cycles += 1
|
||||
continue
|
||||
total_cases += 1
|
||||
witness, checked, exhausted, violation, last_coloring = coloring_witness(
|
||||
m, cycles, args.max_colorings
|
||||
)
|
||||
if witness is not None:
|
||||
witnesses += 1
|
||||
if args.verbose:
|
||||
print(
|
||||
f" graph={graph_idx} source={source_label(source)} "
|
||||
f"cycles={len(cycles)} witness_after={checked}"
|
||||
)
|
||||
continue
|
||||
record = {
|
||||
"n": n,
|
||||
"graph_idx": graph_idx,
|
||||
"graph_edges": sorted(edge_key(u, v) for u, v, _ in g.edge_iterator()),
|
||||
"source": source_label(source),
|
||||
"cycles": len(cycles),
|
||||
"checked": checked,
|
||||
"exhausted": exhausted,
|
||||
"violation_size": len(violation) if violation else None,
|
||||
}
|
||||
if args.failure_details and violation is not None and last_coloring is not None:
|
||||
record["violation_cycle"] = sorted(violation)
|
||||
record["violation_counts"] = color_counts(violation, last_coloring)
|
||||
record["violation_coloring"] = {
|
||||
edge: last_coloring[edge] for edge in sorted(violation)
|
||||
}
|
||||
if exhausted:
|
||||
failures.append(record)
|
||||
print(" FAILURE", record)
|
||||
if args.stop_on_failure:
|
||||
print_summary(total_cases, skipped_no_cycles, witnesses, failures, inconclusive)
|
||||
return
|
||||
else:
|
||||
inconclusive.append(record)
|
||||
print(" INCONCLUSIVE", record)
|
||||
|
||||
print_summary(total_cases, skipped_no_cycles, witnesses, failures, inconclusive)
|
||||
|
||||
|
||||
def print_summary(
|
||||
total_cases: int,
|
||||
skipped_no_cycles: int,
|
||||
witnesses: int,
|
||||
failures: Sequence[dict],
|
||||
inconclusive: Sequence[dict],
|
||||
) -> None:
|
||||
print()
|
||||
print("summary")
|
||||
print(f" checked source decompositions with annular cycles: {total_cases}")
|
||||
print(f" skipped source decompositions with no annular cycles: {skipped_no_cycles}")
|
||||
print(f" witnesses found: {witnesses}")
|
||||
print(f" failures: {len(failures)}")
|
||||
print(f" inconclusive: {len(inconclusive)}")
|
||||
if failures:
|
||||
print(f" first failure: {failures[0]}")
|
||||
if inconclusive:
|
||||
print(f" first inconclusive: {inconclusive[0]}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--n-min", type=int, default=4)
|
||||
parser.add_argument("--n-max", type=int, default=8)
|
||||
parser.add_argument("--sources", choices=("vertex", "cycle", "all"), default="vertex")
|
||||
parser.add_argument("--max-cycle-source-size", type=int, default=6)
|
||||
parser.add_argument("--max-graphs-per-n", type=int)
|
||||
parser.add_argument("--max-sources-per-graph", type=int)
|
||||
parser.add_argument("--max-colorings", type=int)
|
||||
parser.add_argument("--stop-on-failure", action="store_true")
|
||||
parser.add_argument("--failure-details", action="store_true")
|
||||
parser.add_argument("--verbose", action="store_true")
|
||||
run(parser.parse_args())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,181 @@
|
||||
"""Probe the parity mechanism behind Remark 5.8.
|
||||
|
||||
Claim X: in a proper 3-colouring of the 4-regular medial graph M(G) of a plane
|
||||
triangulation G, for every face f of M(G) and every colour pair P = {a,b}, the
|
||||
number of vertices on the boundary of f coloured a or b is even.
|
||||
|
||||
M(G) has two kinds of faces: a "vertex-face" per vertex v of G (the cyclic
|
||||
sequence of edges around v) and a "face-face" per triangular face of G (its
|
||||
three edges). The face-faces are triangles, trivially even (count 2); the
|
||||
vertex-faces are the non-obvious case.
|
||||
|
||||
We build M(G) from a planar embedding's rotation system, enumerate proper
|
||||
3-colourings of M(G), and check Claim X on every face / pair.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
|
||||
import networkx as nx
|
||||
|
||||
|
||||
# --- a handful of small plane triangulations (maximal planar graphs) ---------
|
||||
|
||||
def tetrahedron() -> nx.Graph:
|
||||
return nx.complete_graph(4)
|
||||
|
||||
|
||||
def octahedron() -> nx.Graph:
|
||||
# K_{2,2,2}: antipodal pairs non-adjacent
|
||||
g = nx.Graph()
|
||||
pairs = [(0, 1), (2, 3), (4, 5)]
|
||||
nonadj = set(map(frozenset, pairs))
|
||||
for u in range(6):
|
||||
for v in range(u + 1, 6):
|
||||
if frozenset((u, v)) not in nonadj:
|
||||
g.add_edge(u, v)
|
||||
return g
|
||||
|
||||
|
||||
def stacked(levels: int) -> nx.Graph:
|
||||
"""Apollonian-style: repeatedly insert a vertex in a triangular face."""
|
||||
g = nx.Graph()
|
||||
g.add_edges_from([(0, 1), (1, 2), (0, 2)])
|
||||
faces = [(0, 1, 2)]
|
||||
nxt = 3
|
||||
for _ in range(levels):
|
||||
a, b, c = faces.pop(0)
|
||||
v = nxt
|
||||
nxt += 1
|
||||
g.add_edges_from([(v, a), (v, b), (v, c)])
|
||||
faces += [(a, b, v), (b, c, v), (a, c, v)]
|
||||
return g
|
||||
|
||||
|
||||
def icosahedron() -> nx.Graph:
|
||||
return nx.icosahedral_graph()
|
||||
|
||||
|
||||
def double_wheel(rim: int) -> nx.Graph:
|
||||
"""Two apexes over a rim cycle: a simple triangulated 'tire' with caps."""
|
||||
g = nx.Graph()
|
||||
g.add_cycle = None
|
||||
for i in range(rim):
|
||||
g.add_edge(i, (i + 1) % rim)
|
||||
g.add_edge(i, "N")
|
||||
g.add_edge(i, "S")
|
||||
return g
|
||||
|
||||
|
||||
# --- medial graph from a rotation system -------------------------------------
|
||||
|
||||
def rotation_system(g: nx.Graph) -> dict:
|
||||
ok, emb = nx.check_planarity(g)
|
||||
if not ok:
|
||||
raise ValueError("graph is not planar")
|
||||
return {v: list(emb.neighbors_cw_order(v)) for v in g.nodes()}, emb
|
||||
|
||||
|
||||
def medial_graph(g: nx.Graph):
|
||||
"""Return (M, vertex_faces, face_faces) built from the rotation system.
|
||||
|
||||
Medial vertices are edges of g (as sorted tuples). Around each vertex the
|
||||
incident edges form a face cycle (vertex-face); around each triangular face
|
||||
of g its three edges form a face cycle (face-face).
|
||||
"""
|
||||
rot, emb = rotation_system(g)
|
||||
|
||||
def ekey(u, v):
|
||||
return (u, v) if u <= v else (v, u)
|
||||
|
||||
M = nx.Graph()
|
||||
M.add_nodes_from(ekey(u, v) for u, v in g.edges())
|
||||
|
||||
vertex_faces = []
|
||||
for v, order in rot.items():
|
||||
edges = [ekey(v, w) for w in order]
|
||||
vertex_faces.append(edges)
|
||||
for i in range(len(edges)):
|
||||
M.add_edge(edges[i], edges[(i + 1) % len(edges)])
|
||||
|
||||
# face-faces: traverse each face of the embedding once
|
||||
seen = set()
|
||||
face_faces = []
|
||||
for u, v in list(emb.edges()):
|
||||
if (u, v) in seen:
|
||||
continue
|
||||
face = emb.traverse_face(u, v, mark_half_edges=seen)
|
||||
edges = [ekey(face[i], face[(i + 1) % len(face)]) for i in range(len(face))]
|
||||
face_faces.append(edges)
|
||||
return M, vertex_faces, face_faces
|
||||
|
||||
|
||||
# --- proper 3-colourings of M(G) ---------------------------------------------
|
||||
|
||||
def proper_3_colorings(M: nx.Graph, limit: int | None = None):
|
||||
nodes = list(M.nodes())
|
||||
adj = {v: set(M.neighbors(v)) for v in nodes}
|
||||
coloring: dict = {}
|
||||
out = []
|
||||
|
||||
def rec(i):
|
||||
if limit is not None and len(out) >= limit:
|
||||
return
|
||||
if i == len(nodes):
|
||||
out.append(dict(coloring))
|
||||
return
|
||||
v = nodes[i]
|
||||
used = {coloring[w] for w in adj[v] if w in coloring}
|
||||
for c in (0, 1, 2):
|
||||
if c not in used:
|
||||
coloring[v] = c
|
||||
rec(i + 1)
|
||||
del coloring[v]
|
||||
|
||||
rec(0)
|
||||
return out
|
||||
|
||||
|
||||
def check_claim_x(name: str, g: nx.Graph, color_limit: int = 200):
|
||||
M, vfaces, ffaces = medial_graph(g)
|
||||
colorings = proper_3_colorings(M, limit=color_limit)
|
||||
if not colorings:
|
||||
print(f"{name}: M(G) has no proper 3-colouring (skip)")
|
||||
return
|
||||
faces = [("vertex", f) for f in vfaces] + [("face", f) for f in ffaces]
|
||||
violations = 0
|
||||
odd_vertex_faces = 0
|
||||
for col in colorings:
|
||||
for kind, face in faces:
|
||||
for pair in ((0, 1), (0, 2), (1, 2)):
|
||||
cnt = sum(1 for v in face if col[v] in pair)
|
||||
if cnt % 2 != 0:
|
||||
violations += 1
|
||||
if kind == "vertex":
|
||||
odd_vertex_faces += 1
|
||||
deg = sorted({len(f) for f in vfaces})
|
||||
print(f"{name}: |V(G)|={g.number_of_nodes()} |M|={M.number_of_nodes()} "
|
||||
f"colourings tested={len(colorings)} vertex-face sizes={deg}")
|
||||
print(f" Claim X violations: {violations} "
|
||||
f"(vertex-face violations: {odd_vertex_faces})")
|
||||
|
||||
|
||||
def main():
|
||||
cases = [
|
||||
("tetrahedron", tetrahedron()),
|
||||
("octahedron", octahedron()),
|
||||
("stacked-3", stacked(3)),
|
||||
("stacked-6", stacked(6)),
|
||||
("double_wheel-5", double_wheel(5)),
|
||||
("double_wheel-6", double_wheel(6)),
|
||||
("double_wheel-7", double_wheel(7)),
|
||||
("icosahedron", icosahedron()),
|
||||
]
|
||||
for name, g in cases:
|
||||
# ensure it is a triangulation (every face a triangle)
|
||||
check_claim_x(name, g)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,116 @@
|
||||
"""Directly test Remark 5.8 on a genuine tire piece that contains a BITE.
|
||||
|
||||
A bite arises when the inner outerplanar graph O has a bridge: the bridge edge
|
||||
is traversed twice by the outer-face walk, so it borders two tread triangles and
|
||||
its medial vertex is adjacent to four annular medial vertices.
|
||||
|
||||
Minimal construction. Outer 4-cycle o0,o1,o2,o3; two interior vertices u,w
|
||||
joined by a bridge u-w (V_in = {u,w}). Triangulate the disk so that u-w lies in
|
||||
two tread triangles:
|
||||
|
||||
(o0,o1,u) (o0,u,o3) (o1,w,u) (o1,o2,w) (o2,o3,w) (o3,u,w)
|
||||
|
||||
Cap the outer cycle with an apex N (the bridge bounds no inner hole, so no inner
|
||||
cap is needed). The result G is a closed plane triangulation; M(G) is 4-regular.
|
||||
|
||||
Edge classification (by endpoints): annular = one endpoint outer & one inner;
|
||||
up tooth = both endpoints outer (outer-cycle edge); down tooth = both endpoints
|
||||
inner (here only the bridge u-w). The bridge's medial vertex is the bite apex.
|
||||
|
||||
Remark 5.8 predicts every proper 3-colouring of M(G) restricts to a
|
||||
Kempe-balanced colouring. Here the only non-trivial condition is the outer face
|
||||
(the four up apexes), since the single bite contributes no singleton down teeth.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import networkx as nx
|
||||
|
||||
from check_remark58_bitefree import ekey, medial_graph, proper_3_colorings
|
||||
|
||||
PAIRS = ((0, 1), (0, 2), (1, 2))
|
||||
|
||||
OUTER = ["o0", "o1", "o2", "o3"]
|
||||
INNER = ["u", "w"]
|
||||
|
||||
TREAD_TRIANGLES = [
|
||||
("o0", "o1", "u"),
|
||||
("o0", "u", "o3"),
|
||||
("o1", "w", "u"),
|
||||
("o1", "o2", "w"),
|
||||
("o2", "o3", "w"),
|
||||
("o3", "u", "w"),
|
||||
]
|
||||
|
||||
|
||||
def build():
|
||||
g = nx.Graph()
|
||||
for tri in TREAD_TRIANGLES:
|
||||
a, b, c = tri
|
||||
g.add_edges_from([(a, b), (b, c), (a, c)])
|
||||
# outer cap
|
||||
for i in range(4):
|
||||
g.add_edges_from([("N", OUTER[i]), ("N", OUTER[(i + 1) % 4])])
|
||||
return g
|
||||
|
||||
|
||||
def classify_tread_edges(g):
|
||||
out = set(OUTER)
|
||||
inn = set(INNER)
|
||||
tread_edges = set()
|
||||
for tri in TREAD_TRIANGLES:
|
||||
a, b, c = tri
|
||||
tread_edges |= {ekey(a, b), ekey(b, c), ekey(a, c)}
|
||||
annular, up, down = [], [], []
|
||||
for e in tread_edges:
|
||||
a, b = e
|
||||
ao, bo = a in out, b in out
|
||||
ai, bi = a in inn, b in inn
|
||||
if (ao and bi) or (ai and bo):
|
||||
annular.append(e)
|
||||
elif ao and bo:
|
||||
up.append(e)
|
||||
elif ai and bi:
|
||||
down.append(e)
|
||||
return annular, up, down
|
||||
|
||||
|
||||
def run():
|
||||
g = build()
|
||||
assert nx.check_planarity(g)[0]
|
||||
M = medial_graph(g)
|
||||
annular, up, down = classify_tread_edges(g)
|
||||
annular_set = set(annular)
|
||||
|
||||
# confirm there is a bite: a down edge whose medial vertex has 4 annular nbrs
|
||||
bites = [e for e in down if sum(1 for nb in M.neighbors(e) if nb in annular_set) == 4]
|
||||
print(f"tread: annular={len(annular)} up={len(up)} down={len(down)} "
|
||||
f"bite apexes={len(bites)} (bite edge: {bites})")
|
||||
|
||||
colorings = proper_3_colorings(M, limit=20000)
|
||||
balanced = 0
|
||||
bad = []
|
||||
for col in colorings:
|
||||
ok = all(
|
||||
sum(1 for e in up if col[e] in pair) % 2 == 0
|
||||
for pair in PAIRS
|
||||
)
|
||||
if ok:
|
||||
balanced += 1
|
||||
else:
|
||||
bad.append(col)
|
||||
|
||||
print(f"|V(G)|={g.number_of_nodes()} |M(G)|={M.number_of_nodes()} "
|
||||
f"colourings tested={len(colorings)}")
|
||||
print(f" outer-face (up-apex) balanced={balanced} UNBALANCED={len(bad)}")
|
||||
if bad:
|
||||
print(f" first unbalanced up colours: {[bad[0][e] for e in up]}")
|
||||
print()
|
||||
print("Remark 5.8 holds on this bite tread"
|
||||
if not bad else
|
||||
"Remark 5.8 FAILS on this bite tread")
|
||||
return len(bad)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
@@ -0,0 +1,132 @@
|
||||
"""Test Remark 5.8 on a bite tread that also has singleton down teeth in the
|
||||
bite's inner-gap face -- the subtle case of the condition.
|
||||
|
||||
Inner outerplanar graph O = triangle (a,b,c) plus a pendant bridge a-d. Its
|
||||
outer-face walk is the cyclic sequence W = [d, a, b, c, a]: the bridge a-d is
|
||||
traversed twice (-> a bite), the triangle edges a-b, b-c, c-a once each (-> three
|
||||
singleton down teeth, all sitting in the bite's inner-gap face).
|
||||
|
||||
We triangulate the annulus between an outer m-cycle and W by the lattice-path
|
||||
method, searching interleavings for one giving a simple closed triangulation
|
||||
after capping the outer cycle with an apex N. Then we test Remark 5.8: every
|
||||
proper 3-colouring of M(G) restricts to a Kempe-balanced colouring, i.e.
|
||||
|
||||
* the up apexes (outer edges) are even per colour pair, and
|
||||
* the three singleton down apexes (a-b, b-c, c-a), which share the bite-gap
|
||||
face, are even per colour pair (equivalently: a rainbow).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
|
||||
import networkx as nx
|
||||
|
||||
from check_remark58_bitefree import ekey, medial_graph, proper_3_colorings
|
||||
|
||||
PAIRS = ((0, 1), (0, 2), (1, 2))
|
||||
INNER_WALK = ["d", "a", "b", "c", "a"] # bridge a-d traversed twice
|
||||
SINGLETON_DOWN = [ekey("a", "b"), ekey("b", "c"), ekey("c", "a")]
|
||||
BITE_EDGE = ekey("a", "d")
|
||||
|
||||
|
||||
def build_tread(m: int, path: str):
|
||||
"""Build the annular triangulation for a given lattice path (m 'O', L 'I')."""
|
||||
outer = [f"o{t}" for t in range(m)]
|
||||
W = INNER_WALK
|
||||
L = len(W)
|
||||
g = nx.Graph()
|
||||
g.add_edge(outer[0], W[0]) # anchor
|
||||
i = j = 0
|
||||
tread_triangles = []
|
||||
for mv in path:
|
||||
if mv == "O":
|
||||
tri = (outer[i % m], W[j % L], outer[(i + 1) % m])
|
||||
i += 1
|
||||
else:
|
||||
tri = (outer[i % m], W[j % L], W[(j + 1) % L])
|
||||
j += 1
|
||||
a, b, c = tri
|
||||
g.add_edges_from([(a, b), (b, c), (a, c)])
|
||||
tread_triangles.append(tri)
|
||||
if (i, j) != (m, L):
|
||||
return None
|
||||
return g, outer, tread_triangles
|
||||
|
||||
|
||||
def cap_and_validate(g, outer):
|
||||
"""Cap the outer cycle with apex N; require a simple closed triangulation."""
|
||||
h = g.copy()
|
||||
for t in range(len(outer)):
|
||||
h.add_edges_from([("N", outer[t]), ("N", outer[(t + 1) % len(outer)])])
|
||||
if not nx.check_planarity(h)[0]:
|
||||
return None
|
||||
V, E = h.number_of_nodes(), h.number_of_edges()
|
||||
if E != 3 * V - 6: # maximal planar == triangulation
|
||||
return None
|
||||
return h
|
||||
|
||||
|
||||
def find_construction(m: int):
|
||||
L = len(INNER_WALK)
|
||||
for combo in itertools.combinations(range(m + L), L):
|
||||
path = "".join("I" if t in combo else "O" for t in range(m + L))
|
||||
built = build_tread(m, path)
|
||||
if built is None:
|
||||
continue
|
||||
g, outer, tris = built
|
||||
h = cap_and_validate(g, outer)
|
||||
if h is not None:
|
||||
return h, outer, tris, path
|
||||
return None
|
||||
|
||||
|
||||
def run():
|
||||
for m in (4, 5, 6, 7):
|
||||
found = find_construction(m)
|
||||
if found:
|
||||
break
|
||||
if not found:
|
||||
print("no valid bite-with-singletons triangulation found")
|
||||
return 1
|
||||
h, outer, tris, path = found
|
||||
M = medial_graph(h)
|
||||
|
||||
annular = set()
|
||||
for tri in tris:
|
||||
a, b, c = tri
|
||||
for e in (ekey(a, b), ekey(b, c), ekey(a, c)):
|
||||
x, y = e
|
||||
xo, yo = x in outer, y in outer
|
||||
if (xo and not yo) or (yo and not xo):
|
||||
annular.add(e)
|
||||
|
||||
n_bite_nbrs = sum(1 for nb in M.neighbors(BITE_EDGE) if nb in annular)
|
||||
up = [ekey(outer[t], outer[(t + 1) % len(outer)]) for t in range(len(outer))]
|
||||
up = [e for e in up if e in M]
|
||||
|
||||
print(f"m={len(outer)} path={path} |V(G)|={h.number_of_nodes()} "
|
||||
f"|M(G)|={M.number_of_nodes()}")
|
||||
print(f"bite edge {BITE_EDGE}: annular neighbours={n_bite_nbrs} (4 => bite)")
|
||||
print(f"up apexes={len(up)} singleton down apexes={SINGLETON_DOWN}")
|
||||
|
||||
colorings = proper_3_colorings(M, limit=50000)
|
||||
bad_outer = bad_bitegap = 0
|
||||
for col in colorings:
|
||||
if any(sum(1 for e in up if col[e] in p) % 2 for p in PAIRS):
|
||||
bad_outer += 1
|
||||
if any(sum(1 for e in SINGLETON_DOWN if col[e] in p) % 2 for p in PAIRS):
|
||||
bad_bitegap += 1
|
||||
|
||||
print(f"colourings tested={len(colorings)}")
|
||||
print(f" outer face unbalanced: {bad_outer}")
|
||||
print(f" bite-gap face (3 singletons) unbalanced: {bad_bitegap}")
|
||||
print()
|
||||
ok = (bad_outer == 0 and bad_bitegap == 0)
|
||||
print("Remark 5.8 holds on this bite-with-singletons tread"
|
||||
if ok else "Remark 5.8 FAILS on this bite-with-singletons tread")
|
||||
return 0 if ok else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
@@ -0,0 +1,150 @@
|
||||
"""Directly test Remark 5.8 on genuine (bite-free) tire pieces.
|
||||
|
||||
Construction. Build a triangulated annulus (an antiprism band) between an outer
|
||||
p-cycle O = o_0..o_{p-1} and an inner p-cycle I = i_0..i_{p-1}, with the 2p
|
||||
triangles
|
||||
|
||||
(o_k, o_{k+1}, i_k) and (o_{k+1}, i_k, i_{k+1}).
|
||||
|
||||
Cap the outer disk with an apex N joined to all o_k and the inner disk with an
|
||||
apex S joined to all i_k. The result G is a closed plane triangulation, so its
|
||||
medial graph M(G) is 4-regular.
|
||||
|
||||
The tread T is the annulus; its full medial tire graph M(T) is the subgraph of
|
||||
M(G) on the medial vertices of the tread edges (outer, inner and annular edges).
|
||||
This tread has simple boundaries, hence no bites: the up teeth are the outer
|
||||
edges, the down teeth the inner edges, and the only valid faces are the outer
|
||||
face (up apexes) and the root face (down apexes).
|
||||
|
||||
Remark 5.8 predicts: every proper 3-colouring of M(G), restricted to M(T), is
|
||||
Kempe-balanced, i.e. for each colour pair P the up apexes coloured in P are even
|
||||
in number, and likewise the down apexes. We enumerate colourings of M(G) and
|
||||
check this.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
|
||||
import networkx as nx
|
||||
|
||||
PAIRS = ((0, 1), (0, 2), (1, 2))
|
||||
|
||||
|
||||
def ekey(u, v):
|
||||
return (u, v) if (str(u), u) <= (str(v), v) else (v, u)
|
||||
|
||||
|
||||
def build_capped_annulus(p: int):
|
||||
g = nx.Graph()
|
||||
O = [("o", k) for k in range(p)]
|
||||
I = [("i", k) for k in range(p)]
|
||||
outer_edges, inner_edges, annular_edges = [], [], []
|
||||
for k in range(p):
|
||||
o, on = O[k], O[(k + 1) % p]
|
||||
i, ino = I[k], I[(k + 1) % p]
|
||||
outer_edges.append(ekey(o, on))
|
||||
inner_edges.append(ekey(i, ino))
|
||||
annular_edges += [ekey(o, i), ekey(on, i)]
|
||||
# tread triangles
|
||||
g.add_edges_from([(o, on), (on, i), (o, i)]) # (o_k,o_{k+1},i_k)
|
||||
g.add_edges_from([(on, i), (i, ino), (on, ino)]) # (o_{k+1},i_k,i_{k+1})
|
||||
# caps
|
||||
for k in range(p):
|
||||
g.add_edges_from([("N", O[k]), ("N", O[(k + 1) % p])])
|
||||
g.add_edges_from([("S", I[k]), ("S", I[(k + 1) % p])])
|
||||
meta = {
|
||||
"outer_edges": [ekey(*e) for e in outer_edges],
|
||||
"inner_edges": [ekey(*e) for e in inner_edges],
|
||||
"annular_edges": [ekey(*e) for e in annular_edges],
|
||||
}
|
||||
return g, meta
|
||||
|
||||
|
||||
def medial_graph(g: nx.Graph) -> nx.Graph:
|
||||
ok, emb = nx.check_planarity(g)
|
||||
if not ok:
|
||||
raise ValueError("not planar")
|
||||
M = nx.Graph()
|
||||
M.add_nodes_from(ekey(u, v) for u, v in g.edges())
|
||||
for v in g.nodes():
|
||||
order = list(emb.neighbors_cw_order(v))
|
||||
edges = [ekey(v, w) for w in order]
|
||||
for a in range(len(edges)):
|
||||
M.add_edge(edges[a], edges[(a + 1) % len(edges)])
|
||||
return M
|
||||
|
||||
|
||||
def proper_3_colorings(M: nx.Graph, limit: int):
|
||||
nodes = list(M.nodes())
|
||||
adj = {v: set(M.neighbors(v)) for v in nodes}
|
||||
coloring: dict = {}
|
||||
out = []
|
||||
|
||||
def rec(i):
|
||||
if len(out) >= limit:
|
||||
return
|
||||
if i == len(nodes):
|
||||
out.append(dict(coloring))
|
||||
return
|
||||
v = nodes[i]
|
||||
used = {coloring[w] for w in adj[v] if w in coloring}
|
||||
for c in (0, 1, 2):
|
||||
if c not in used:
|
||||
coloring[v] = c
|
||||
rec(i + 1)
|
||||
del coloring[v]
|
||||
|
||||
rec(0)
|
||||
return out
|
||||
|
||||
|
||||
def is_kempe_balanced(coloring, up_apexes, down_apexes):
|
||||
for face in (up_apexes, down_apexes):
|
||||
for pair in PAIRS:
|
||||
if sum(1 for e in face if coloring[e] in pair) % 2 != 0:
|
||||
return False, face is down_apexes
|
||||
return True, None
|
||||
|
||||
|
||||
def run(p: int, limit: int = 4000):
|
||||
g, meta = build_capped_annulus(p)
|
||||
M = medial_graph(g)
|
||||
up = meta["outer_edges"]
|
||||
down = meta["inner_edges"]
|
||||
colorings = proper_3_colorings(M, limit)
|
||||
|
||||
balanced = 0
|
||||
unbalanced = []
|
||||
for col in colorings:
|
||||
ok, _ = is_kempe_balanced(col, up, down)
|
||||
if ok:
|
||||
balanced += 1
|
||||
else:
|
||||
unbalanced.append(col)
|
||||
|
||||
n_ann = len(meta["annular_edges"])
|
||||
print(f"p={p}: |V(G)|={g.number_of_nodes()} |M(G)|={M.number_of_nodes()} "
|
||||
f"|A(T)|={n_ann} up={len(up)} down={len(down)}")
|
||||
print(f" colourings tested={len(colorings)} (cap {limit}) "
|
||||
f"balanced={balanced} UNBALANCED={len(unbalanced)}")
|
||||
if unbalanced:
|
||||
col = unbalanced[0]
|
||||
upc = [col[e] for e in up]
|
||||
dnc = [col[e] for e in down]
|
||||
print(f" first unbalanced restriction: up colours={upc} down colours={dnc}")
|
||||
return len(unbalanced)
|
||||
|
||||
|
||||
def main():
|
||||
total_bad = 0
|
||||
for p in (3, 4, 5, 6):
|
||||
total_bad += run(p)
|
||||
print()
|
||||
print("Remark 5.8 (bite-free) holds on all tested colourings"
|
||||
if total_bad == 0 else
|
||||
f"Remark 5.8 (bite-free) FAILS: {total_bad} unbalanced restrictions found")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,350 @@
|
||||
"""Compare full and reduced medial tire graphs on generated tires.
|
||||
|
||||
The new medial decomposition paper defines:
|
||||
|
||||
* full medial tire graph: the subgraph of M(G) induced by medial
|
||||
vertices corresponding to edges incident to tread triangles;
|
||||
* reduced medial tire graph: delete same-boundary medial edges and
|
||||
chord-only medial edges.
|
||||
|
||||
For a tire tread inside an ambient triangulation, the medial edges
|
||||
visible in the tread come from annular triangular faces. This script
|
||||
checks whether any same-boundary medial edges are actually present in
|
||||
that model. It also compares against the older standalone drawing
|
||||
model, which added artificial outer/inner boundary faces.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import itertools
|
||||
import random
|
||||
from collections import Counter
|
||||
|
||||
|
||||
Edge = tuple[int, int]
|
||||
MedialEdge = tuple[Edge, Edge]
|
||||
|
||||
|
||||
def random_tire(m: int, k: int, n_chords: int = 0, seed: int | None = None) -> dict:
|
||||
"""Generate the same labelled annular tires used in earlier experiments."""
|
||||
rng = random.Random(seed)
|
||||
outer = list(range(m))
|
||||
inner = list(range(m, m + k))
|
||||
edges: set[Edge] = set()
|
||||
|
||||
for i in range(m):
|
||||
edges.add(edge_key(outer[i], outer[(i + 1) % m]))
|
||||
for j in range(k):
|
||||
edges.add(edge_key(inner[j], inner[(j + 1) % k]))
|
||||
|
||||
inner_chords = set()
|
||||
candidates = []
|
||||
for a in range(k):
|
||||
for b in range(a + 2, k):
|
||||
if not (a == 0 and b == k - 1):
|
||||
candidates.append((a, b))
|
||||
rng.shuffle(candidates)
|
||||
for a, b in candidates:
|
||||
if len(inner_chords) >= n_chords:
|
||||
break
|
||||
if any((a < a2 < b < b2) or (a2 < a < b2 < b) for a2, b2 in inner_chords):
|
||||
continue
|
||||
inner_chords.add((a, b))
|
||||
edges.add(edge_key(inner[a], inner[b]))
|
||||
|
||||
edges.add(edge_key(outer[0], inner[0]))
|
||||
moves = ["O"] * m + ["I"] * k
|
||||
rng.shuffle(moves)
|
||||
triangles = []
|
||||
i, j = 0, 0
|
||||
for move in moves:
|
||||
if move == "O":
|
||||
tri = (outer[i % m], inner[j % k], outer[(i + 1) % m])
|
||||
triangles.append(tri)
|
||||
edges.add(edge_key(inner[j % k], outer[(i + 1) % m]))
|
||||
i += 1
|
||||
else:
|
||||
tri = (outer[i % m], inner[j % k], inner[(j + 1) % k])
|
||||
triangles.append(tri)
|
||||
edges.add(edge_key(outer[i % m], inner[(j + 1) % k]))
|
||||
j += 1
|
||||
|
||||
return {
|
||||
"m": m,
|
||||
"k": k,
|
||||
"n_chords": len(inner_chords),
|
||||
"outer": outer,
|
||||
"inner": inner,
|
||||
"edges": sorted(edges),
|
||||
"triangles": triangles,
|
||||
"inner_chords": sorted(inner_chords),
|
||||
"lattice_path": "".join(moves),
|
||||
"seed": seed,
|
||||
}
|
||||
|
||||
|
||||
def tire_from_path(m: int, k: int, chords: tuple[tuple[int, int], ...], path: str) -> dict:
|
||||
outer = list(range(m))
|
||||
inner = list(range(m, m + k))
|
||||
edges: set[Edge] = set()
|
||||
|
||||
for i in range(m):
|
||||
edges.add(edge_key(outer[i], outer[(i + 1) % m]))
|
||||
for j in range(k):
|
||||
edges.add(edge_key(inner[j], inner[(j + 1) % k]))
|
||||
for a, b in chords:
|
||||
edges.add(edge_key(inner[a], inner[b]))
|
||||
|
||||
edges.add(edge_key(outer[0], inner[0]))
|
||||
triangles = []
|
||||
i, j = 0, 0
|
||||
for move in path:
|
||||
if move == "O":
|
||||
tri = (outer[i % m], inner[j % k], outer[(i + 1) % m])
|
||||
triangles.append(tri)
|
||||
edges.add(edge_key(inner[j % k], outer[(i + 1) % m]))
|
||||
i += 1
|
||||
else:
|
||||
tri = (outer[i % m], inner[j % k], inner[(j + 1) % k])
|
||||
triangles.append(tri)
|
||||
edges.add(edge_key(outer[i % m], inner[(j + 1) % k]))
|
||||
j += 1
|
||||
|
||||
return {
|
||||
"m": m,
|
||||
"k": k,
|
||||
"n_chords": len(chords),
|
||||
"outer": outer,
|
||||
"inner": inner,
|
||||
"edges": sorted(edges),
|
||||
"triangles": triangles,
|
||||
"inner_chords": sorted(chords),
|
||||
"lattice_path": path,
|
||||
"seed": None,
|
||||
}
|
||||
|
||||
|
||||
def chord_crosses(c1: tuple[int, int], c2: tuple[int, int]) -> bool:
|
||||
a, b = c1
|
||||
c, d = c2
|
||||
return (a < c < b < d) or (c < a < d < b)
|
||||
|
||||
|
||||
def chord_sets(k: int, max_chords: int) -> list[tuple[tuple[int, int], ...]]:
|
||||
candidates = []
|
||||
for a in range(k):
|
||||
for b in range(a + 2, k):
|
||||
if not (a == 0 and b == k - 1):
|
||||
candidates.append((a, b))
|
||||
|
||||
out = [()]
|
||||
|
||||
def rec(start: int, chosen: tuple[tuple[int, int], ...]) -> None:
|
||||
if len(chosen) >= max_chords:
|
||||
return
|
||||
for idx in range(start, len(candidates)):
|
||||
chord = candidates[idx]
|
||||
if any(chord_crosses(chord, old) for old in chosen):
|
||||
continue
|
||||
nxt = chosen + (chord,)
|
||||
out.append(nxt)
|
||||
rec(idx + 1, nxt)
|
||||
|
||||
rec(0, ())
|
||||
return out
|
||||
|
||||
|
||||
def lattice_paths(m: int, k: int):
|
||||
for o_positions in itertools.combinations(range(m + k), m):
|
||||
o_set = set(o_positions)
|
||||
yield "".join("O" if idx in o_set else "I" for idx in range(m + k))
|
||||
|
||||
|
||||
def edge_key(u: int, v: int) -> Edge:
|
||||
return tuple(sorted((u, v)))
|
||||
|
||||
|
||||
def face_edges(face: tuple[int, ...]) -> list[Edge]:
|
||||
return [edge_key(face[i], face[(i + 1) % len(face)]) for i in range(len(face))]
|
||||
|
||||
|
||||
def is_cycle_edge(edge: Edge, cycle: list[int]) -> bool:
|
||||
cycle_set = set(cycle)
|
||||
if not set(edge) <= cycle_set:
|
||||
return False
|
||||
n = len(cycle)
|
||||
idx = {v: i for i, v in enumerate(cycle)}
|
||||
a, b = idx[edge[0]], idx[edge[1]]
|
||||
return (a - b) % n in (1, n - 1)
|
||||
|
||||
|
||||
def is_inner_chord(edge: Edge, m: int, k: int) -> bool:
|
||||
u, v = edge
|
||||
if not (m <= u < m + k and m <= v < m + k):
|
||||
return False
|
||||
a, b = u - m, v - m
|
||||
d = abs(a - b)
|
||||
return min(d, k - d) != 1
|
||||
|
||||
|
||||
def suppress_reason(e1: Edge, e2: Edge, tire: dict) -> str | None:
|
||||
outer = tire["outer"]
|
||||
inner = tire["inner"]
|
||||
if is_cycle_edge(e1, outer) and is_cycle_edge(e2, outer):
|
||||
return "outer_boundary"
|
||||
if is_cycle_edge(e1, inner) and is_cycle_edge(e2, inner):
|
||||
return "inner_boundary"
|
||||
m, k = tire["m"], tire["k"]
|
||||
if is_inner_chord(e1, m, k) or is_inner_chord(e2, m, k):
|
||||
return "inner_chord"
|
||||
return None
|
||||
|
||||
|
||||
def medial_from_faces(faces: list[tuple[int, ...]], retained: set[Edge]) -> set[MedialEdge]:
|
||||
medial_edges: set[MedialEdge] = set()
|
||||
for face in faces:
|
||||
boundary = [e for e in face_edges(face) if e in retained]
|
||||
if len(boundary) < 2:
|
||||
continue
|
||||
for i, e in enumerate(boundary):
|
||||
nxt = boundary[(i + 1) % len(boundary)]
|
||||
if e != nxt:
|
||||
medial_edges.add(tuple(sorted((e, nxt))))
|
||||
return medial_edges
|
||||
|
||||
|
||||
def compare_tire(tire: dict, *, standalone_boundary_faces: bool) -> dict:
|
||||
annular_faces = [tuple(tri) for tri in tire["triangles"]]
|
||||
faces = list(annular_faces)
|
||||
if standalone_boundary_faces:
|
||||
faces.append(tuple(tire["outer"]))
|
||||
faces.append(tuple(reversed(tire["inner"])))
|
||||
|
||||
# Definition 3.1 includes edges incident to at least one tread triangle.
|
||||
retained = {e for face in annular_faces for e in face_edges(face)}
|
||||
full_edges = medial_from_faces(faces, retained)
|
||||
removed = {me for me in full_edges if suppress_reason(me[0], me[1], tire)}
|
||||
reduced_edges = full_edges - removed
|
||||
reasons = Counter(suppress_reason(me[0], me[1], tire) for me in removed)
|
||||
reasons.pop(None, None)
|
||||
return {
|
||||
"vertices": len(retained),
|
||||
"full_edges": len(full_edges),
|
||||
"reduced_edges": len(reduced_edges),
|
||||
"removed": len(removed),
|
||||
"reasons": reasons,
|
||||
"examples": sorted(removed)[:5],
|
||||
}
|
||||
|
||||
|
||||
def run_sweep(args: argparse.Namespace) -> None:
|
||||
ambient_cases = 0
|
||||
ambient_differ = []
|
||||
standalone_cases = 0
|
||||
standalone_differ = []
|
||||
ambient_reasons: Counter[str] = Counter()
|
||||
standalone_reasons: Counter[str] = Counter()
|
||||
|
||||
max_chords = args.max_chords
|
||||
for m in range(args.min_cycle, args.max_cycle + 1):
|
||||
for k in range(args.min_cycle, args.max_cycle + 1):
|
||||
for chords in range(max_chords + 1):
|
||||
for seed in range(args.seeds):
|
||||
tire = random_tire(m=m, k=k, n_chords=chords, seed=seed)
|
||||
|
||||
ambient = compare_tire(tire, standalone_boundary_faces=False)
|
||||
ambient_cases += 1
|
||||
ambient_reasons.update(ambient["reasons"])
|
||||
if ambient["removed"]:
|
||||
ambient_differ.append((m, k, chords, seed, tire, ambient))
|
||||
|
||||
standalone = compare_tire(tire, standalone_boundary_faces=True)
|
||||
standalone_cases += 1
|
||||
standalone_reasons.update(standalone["reasons"])
|
||||
if standalone["removed"]:
|
||||
standalone_differ.append((m, k, chords, seed, tire, standalone))
|
||||
|
||||
print("ambient tread-face model")
|
||||
print(f" cases checked: {ambient_cases}")
|
||||
print(f" cases where full != reduced: {len(ambient_differ)}")
|
||||
print(f" removed-edge reasons: {dict(sorted(ambient_reasons.items()))}")
|
||||
if ambient_differ:
|
||||
m, k, chords, seed, tire, result = ambient_differ[0]
|
||||
print(" first difference:")
|
||||
print(f" m={m} k={k} requested_chords={chords} seed={seed}")
|
||||
print(f" path={tire['lattice_path']} chords={tire['inner_chords']}")
|
||||
print(f" removed examples={result['examples']}")
|
||||
|
||||
print()
|
||||
print("standalone tire-with-boundary-faces model")
|
||||
print(f" cases checked: {standalone_cases}")
|
||||
print(f" cases where full != reduced: {len(standalone_differ)}")
|
||||
print(f" removed-edge reasons: {dict(sorted(standalone_reasons.items()))}")
|
||||
if standalone_differ:
|
||||
m, k, chords, seed, tire, result = standalone_differ[0]
|
||||
print(" first difference:")
|
||||
print(f" m={m} k={k} requested_chords={chords} seed={seed}")
|
||||
print(f" path={tire['lattice_path']} chords={tire['inner_chords']}")
|
||||
print(f" full_edges={result['full_edges']} reduced_edges={result['reduced_edges']}")
|
||||
print(f" removed examples={result['examples']}")
|
||||
|
||||
|
||||
def run_exhaustive(args: argparse.Namespace) -> None:
|
||||
ambient_cases = 0
|
||||
ambient_differ = []
|
||||
standalone_cases = 0
|
||||
standalone_differ = []
|
||||
for m in range(args.min_cycle, args.max_cycle + 1):
|
||||
for k in range(args.min_cycle, args.max_cycle + 1):
|
||||
for chords in chord_sets(k, args.max_chords):
|
||||
for path in lattice_paths(m, k):
|
||||
tire = tire_from_path(m, k, chords, path)
|
||||
|
||||
ambient = compare_tire(tire, standalone_boundary_faces=False)
|
||||
ambient_cases += 1
|
||||
if ambient["removed"]:
|
||||
ambient_differ.append((m, k, chords, path, ambient))
|
||||
|
||||
standalone = compare_tire(tire, standalone_boundary_faces=True)
|
||||
standalone_cases += 1
|
||||
if standalone["removed"]:
|
||||
standalone_differ.append((m, k, chords, path, standalone))
|
||||
|
||||
print("exhaustive ambient tread-face model")
|
||||
print(f" cases checked: {ambient_cases}")
|
||||
print(f" cases where full != reduced: {len(ambient_differ)}")
|
||||
if ambient_differ:
|
||||
m, k, chords, path, result = ambient_differ[0]
|
||||
print(" first difference:")
|
||||
print(f" m={m} k={k} chords={chords} path={path}")
|
||||
print(f" removed examples={result['examples']}")
|
||||
|
||||
print()
|
||||
print("exhaustive standalone tire-with-boundary-faces model")
|
||||
print(f" cases checked: {standalone_cases}")
|
||||
print(f" cases where full != reduced: {len(standalone_differ)}")
|
||||
if standalone_differ:
|
||||
m, k, chords, path, result = standalone_differ[0]
|
||||
print(" first difference:")
|
||||
print(f" m={m} k={k} chords={chords} path={path}")
|
||||
print(f" full_edges={result['full_edges']} reduced_edges={result['reduced_edges']}")
|
||||
print(f" removed examples={result['examples']}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--min-cycle", type=int, default=3)
|
||||
parser.add_argument("--max-cycle", type=int, default=8)
|
||||
parser.add_argument("--max-chords", type=int, default=3)
|
||||
parser.add_argument("--seeds", type=int, default=50)
|
||||
parser.add_argument("--exhaustive", action="store_true")
|
||||
args = parser.parse_args()
|
||||
if args.exhaustive:
|
||||
run_exhaustive(args)
|
||||
else:
|
||||
run_sweep(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,144 @@
|
||||
"""Picture: evening a terminal (leaf) triangle by the two-vertex operation:
|
||||
add y at the midpoint of uv and z at the centroid of uvt, delete uv, add edges
|
||||
xy, uy, vy, zy, zu, zv, zt. The leaf becomes a 4-wheel tread with hub z.
|
||||
|
||||
Panels:
|
||||
A before: terminal face uvt, level cycle C_k = the 3-cycle (odd seam)
|
||||
B after: seam u-y-v-t (length 4, even); leaf = 4-wheel with hub z (level k+1)
|
||||
C medial overlay with the canonical colouring: seam apexes mono-3,
|
||||
leaf annular 4-cycle alternating 1,2 -- proper, no ears, no defect.
|
||||
"""
|
||||
|
||||
import os
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
PAL = {0: "#e6550d", 1: "#3182bd", 2: "#31a354"} # colours "1","2","3"
|
||||
GRAY = "#999999"
|
||||
|
||||
u, v, t, x = (-1.0, 0.0), (1.0, 0.0), (0.0, -1.6), (0.0, 1.0)
|
||||
y = (0.0, 0.0) # midpoint of uv
|
||||
z = (0.0, -1.6 / 3) # centroid of uvt
|
||||
|
||||
|
||||
def mid(a, b):
|
||||
return ((a[0] + b[0]) / 2, (a[1] + b[1]) / 2)
|
||||
|
||||
|
||||
def vertex(ax, p, name, dx, dy, color="black"):
|
||||
ax.plot(*p, "o", color=color, ms=5.5, zorder=6)
|
||||
ax.annotate(name, p, textcoords="offset points", xytext=(dx, dy),
|
||||
fontsize=10, fontweight="bold", zorder=6)
|
||||
|
||||
|
||||
def panel_before(ax):
|
||||
ax.fill([u[0], v[0], t[0]], [u[1], v[1], t[1]], color="#fde9d9")
|
||||
for a, b in [(u, v), (v, t), (t, u)]:
|
||||
ax.plot([a[0], b[0]], [a[1], b[1]], color="black", lw=2.4)
|
||||
for p in (u, v):
|
||||
ax.plot([x[0], p[0]], [x[1], p[1]], color=GRAY, lw=0.9)
|
||||
ax.annotate("terminal face\n(leaf of tire tree)", (0, -0.62), ha="center",
|
||||
fontsize=8, color="#b06030")
|
||||
vertex(ax, u, "u", -12, -2); vertex(ax, v, "v", 8, -2)
|
||||
vertex(ax, t, "t", 0, -14); vertex(ax, x, "x", 8, 2)
|
||||
ax.annotate("(apex in tread above)", x, textcoords="offset points",
|
||||
xytext=(16, -2), fontsize=7, color=GRAY)
|
||||
|
||||
|
||||
def panel_after(ax, faint=1.0):
|
||||
# wheel faces
|
||||
for tri, c in [((u, y, z), "#fde9d9"), ((y, v, z), "#fdf3d9"),
|
||||
((v, t, z), "#fde9d9"), ((t, u, z), "#fdf3d9")]:
|
||||
ax.fill([p[0] for p in tri], [p[1] for p in tri], color=c, alpha=faint)
|
||||
# seam (level cycle) bold: u-y, y-v, v-t, t-u
|
||||
for a, b in [(u, y), (y, v), (v, t), (t, u)]:
|
||||
ax.plot([a[0], b[0]], [a[1], b[1]], color="black", lw=2.4, alpha=faint)
|
||||
# parent spokes xu, xy, xv
|
||||
for p in (u, y, v):
|
||||
ax.plot([x[0], p[0]], [x[1], p[1]], color=GRAY, lw=0.9, alpha=faint)
|
||||
# leaf spokes zu, zy, zv, zt
|
||||
for p in (u, y, v, t):
|
||||
ax.plot([z[0], p[0]], [z[1], p[1]], color="black", lw=1.0, alpha=faint)
|
||||
vertex(ax, u, "u", -12, -2); vertex(ax, v, "v", 8, -2)
|
||||
vertex(ax, t, "t", 0, -14); vertex(ax, x, "x", 8, 2)
|
||||
vertex(ax, y, "y", 6, 6); vertex(ax, z, "z", 7, -4)
|
||||
|
||||
|
||||
def panel_medial(ax):
|
||||
panel_after(ax, faint=0.35)
|
||||
apexes = {"uy": mid(u, y), "yv": mid(y, v), "vt": mid(v, t), "tu": mid(t, u)}
|
||||
leaf_ann = {"zu": mid(z, u), "zy": mid(z, y), "zv": mid(z, v), "zt": mid(z, t)}
|
||||
par_ann = {"ux": mid(u, x), "xy": mid(x, y), "xv": mid(x, v)}
|
||||
# leaf annular 4-cycle (faces uyz, yvz, vtz, tuz)
|
||||
ring = ["zu", "zy", "zv", "zt"]
|
||||
for i in range(4):
|
||||
a, b = leaf_ann[ring[i]], leaf_ann[ring[(i + 1) % 4]]
|
||||
ax.plot([a[0], b[0]], [a[1], b[1]], color="#555555", lw=1.6, zorder=4)
|
||||
# parent annular path m_ux - m_xy - m_xv (faces xuy, xyv)
|
||||
for a, b in [("ux", "xy"), ("xy", "xv")]:
|
||||
ax.plot([par_ann[a][0], par_ann[b][0]], [par_ann[a][1], par_ann[b][1]],
|
||||
color="#555555", lw=1.6, zorder=4)
|
||||
# apex spokes: each seam apex to its two leaf-annular and two parent nbrs
|
||||
spokes = [("uy", "zu"), ("uy", "zy"), ("yv", "zy"), ("yv", "zv"),
|
||||
("vt", "zv"), ("vt", "zt"), ("tu", "zt"), ("tu", "zu")]
|
||||
for a, b in spokes:
|
||||
pa, pb = apexes[a], leaf_ann[b]
|
||||
ax.plot([pa[0], pb[0]], [pa[1], pb[1]], color="#aaaaaa", lw=1.0, zorder=3)
|
||||
for a, b in [("uy", "ux"), ("uy", "xy"), ("yv", "xy"), ("yv", "xv")]:
|
||||
pa, pb = apexes[a], par_ann[b]
|
||||
ax.plot([pa[0], pb[0]], [pa[1], pb[1]], color="#aaaaaa", lw=1.0, zorder=3)
|
||||
# off-picture parent stubs for m_vt, m_tu
|
||||
for k, d in [("vt", (0.25, -0.18)), ("tu", (-0.25, -0.18))]:
|
||||
p = apexes[k]
|
||||
ax.plot([p[0], p[0] + d[0]], [p[1], p[1] + d[1]], color="#cccccc",
|
||||
lw=0.9, linestyle=":", zorder=2)
|
||||
# colours: apexes mono-3 (green); leaf ring alternating 1,2; parent 1,2,1
|
||||
col = {}
|
||||
for k in apexes: col[("a", k)] = 2
|
||||
for k, c in zip(ring, (0, 1, 0, 1)): col[("l", k)] = c
|
||||
for k, c in zip(("ux", "xy", "xv"), (0, 1, 0)): col[("p", k)] = c
|
||||
for k, p in apexes.items():
|
||||
ax.plot(*p, "o", color=PAL[2], ms=11, markeredgecolor="black", zorder=5)
|
||||
for k, p in leaf_ann.items():
|
||||
ax.plot(*p, "o", color=PAL[col[("l", k)]], ms=9,
|
||||
markeredgecolor="black", zorder=5)
|
||||
for k, p in par_ann.items():
|
||||
ax.plot(*p, "o", color=PAL[col[("p", k)]], ms=9,
|
||||
markeredgecolor="black", zorder=5)
|
||||
lbl = {"uy": (-26, 4), "yv": (12, 4), "vt": (12, -2), "tu": (-30, -2)}
|
||||
for k, p in apexes.items():
|
||||
ax.annotate(f"m_{k}", p, textcoords="offset points", xytext=lbl[k],
|
||||
fontsize=7, zorder=6)
|
||||
|
||||
|
||||
fig, axes = plt.subplots(1, 3, figsize=(14, 4.8))
|
||||
for ax in axes:
|
||||
ax.set_xlim(-1.7, 1.7)
|
||||
ax.set_ylim(-2.1, 1.45)
|
||||
ax.set_aspect("equal")
|
||||
ax.axis("off")
|
||||
|
||||
panel_before(axes[0])
|
||||
axes[0].set_title("A. before: leaf = terminal face uvt\nseam C_k = 3-cycle (odd)",
|
||||
fontsize=9)
|
||||
panel_after(axes[1])
|
||||
axes[1].set_title("B. add y = mid(uv), z = centroid; delete uv;\n"
|
||||
"add xy, uy, vy, zy, zu, zv, zt\n"
|
||||
"seam u-y-v-t (even); leaf = 4-wheel, hub z (level k+1)",
|
||||
fontsize=9)
|
||||
panel_medial(axes[2])
|
||||
axes[2].set_title("C. medial + canonical colouring:\nseam apexes all 3 (green), "
|
||||
"leaf ring alternates 1,2 — proper, no ears", fontsize=9)
|
||||
|
||||
fig.suptitle(
|
||||
"Evening a terminal leaf with the two-vertex operation (y splits uv under x; "
|
||||
"z stellates the leaf as a wheel hub).\n"
|
||||
"Both new vertices have degree 4; the leaf tread is a 4-wheel with an even "
|
||||
"annular cycle, so the monochromatic-3 seam is VALID — no leaf defect.",
|
||||
fontsize=10)
|
||||
fig.tight_layout(rect=(0, 0, 1, 0.86))
|
||||
out = os.path.join(HERE, "evened_leaf.png")
|
||||
fig.savefig(out, dpi=170)
|
||||
print("wrote", out)
|
||||
@@ -0,0 +1,145 @@
|
||||
"""Straight-line planar drawing of the minimal genuine obstruction found by the
|
||||
even-level-cycle programme: the ring triangulation sizes=[3,6,3], leaf='face'
|
||||
(generator random.Random(2), the 27th graph), 12 vertices. It survives
|
||||
exhausting insertion sites x tread phases x root colour-orders (residue_phase_
|
||||
sweep.py: 24 settings, 0 ok) and fails at the leaf-gadget removal step.
|
||||
|
||||
Embedding: networkx planar_layout (a canonical-ordering straight-line embedding
|
||||
of a planar graph), recentred. We additionally VERIFY no two non-incident edges
|
||||
cross before drawing. Every triangulation on >=4 vertices is 3-connected, so a
|
||||
crossing-free straight-line embedding is guaranteed to exist.
|
||||
"""
|
||||
import os, random
|
||||
import numpy as np
|
||||
import networkx as nx
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.patches import Polygon
|
||||
import kempe_even_program_harness as H
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
LEVCOL = {0: "#d9d9d9", 1: "#9ecae1", 2: "#fc9272"} # by BFS level
|
||||
|
||||
|
||||
def reconstruct(seed, idx):
|
||||
rng = random.Random(seed)
|
||||
for _ in range(idx + 1):
|
||||
sizes, leaf = H.random_profile(rng)
|
||||
g, outer = H.ring_triangulation(sizes, leaf, rng)
|
||||
return g, outer
|
||||
|
||||
|
||||
def planar_pos(g):
|
||||
nxg = nx.Graph()
|
||||
nxg.add_nodes_from(g.rot)
|
||||
for ed in g.edges():
|
||||
a, b = tuple(ed); nxg.add_edge(a, b)
|
||||
ok, _ = nx.check_planarity(nxg)
|
||||
assert ok, "graph is not planar?!"
|
||||
pos = nx.planar_layout(nxg)
|
||||
pts = np.array([pos[v] for v in g.rot])
|
||||
c = pts.mean(axis=0); s = np.abs(pts - c).max()
|
||||
return {v: ((pos[v][0] - c[0]) / s, (pos[v][1] - c[1]) / s) for v in g.rot}
|
||||
|
||||
|
||||
def seg_cross(p, q, r, s):
|
||||
def o(a, b, c):
|
||||
return (b[0]-a[0])*(c[1]-a[1]) - (b[1]-a[1])*(c[0]-a[0])
|
||||
d1, d2, d3, d4 = o(r, s, p), o(r, s, q), o(p, q, r), o(p, q, s)
|
||||
return ((d1 > 0) != (d2 > 0)) and ((d3 > 0) != (d4 > 0))
|
||||
|
||||
|
||||
def verify_planar(g, pos):
|
||||
edges = [tuple(e) for e in g.edges()]
|
||||
bad = []
|
||||
for i in range(len(edges)):
|
||||
a, b = edges[i]
|
||||
for j in range(i + 1, len(edges)):
|
||||
c, d = edges[j]
|
||||
if len({a, b, c, d}) < 4:
|
||||
continue
|
||||
if seg_cross(pos[a], pos[b], pos[c], pos[d]):
|
||||
bad.append((edges[i], edges[j]))
|
||||
return bad
|
||||
|
||||
|
||||
g, outer = reconstruct(2, 26)
|
||||
g.check()
|
||||
an = H.Analysis(g.copy(), outer)
|
||||
pos = planar_pos(g)
|
||||
bad = verify_planar(g, pos)
|
||||
print("crossing edge-pairs:", bad if bad else "NONE -- valid straight-line planar embedding")
|
||||
assert not bad, "embedding has crossings"
|
||||
|
||||
terminal = tuple(an.terminal[0])
|
||||
odd_seam = [c for k, c in an.seams if len(c) % 2][0]
|
||||
faces = [tuple(f) for f in g.faces()]
|
||||
outer_set = frozenset(outer)
|
||||
|
||||
fig, axes = plt.subplots(1, 2, figsize=(13.5, 6.8))
|
||||
xs = [p[0] for p in pos.values()]; ys = [p[1] for p in pos.values()]
|
||||
mx = max(abs(min(xs)), abs(max(xs))); my = max(abs(min(ys)), abs(max(ys)))
|
||||
for ax in axes:
|
||||
ax.set_aspect("equal"); ax.axis("off")
|
||||
ax.set_xlim(-mx - 0.25, mx + 0.25); ax.set_ylim(-my - 0.25, my + 0.35)
|
||||
|
||||
|
||||
def draw_faces(ax):
|
||||
for f in faces:
|
||||
if frozenset(f) == outer_set:
|
||||
continue
|
||||
ax.add_patch(Polygon([pos[v] for v in f], closed=True,
|
||||
facecolor="#fbfbfb", edgecolor="none", zorder=0))
|
||||
|
||||
|
||||
def draw_edges(ax, bold=None):
|
||||
bold = bold or set()
|
||||
for ed in g.edges():
|
||||
a, b = tuple(ed)
|
||||
hot = frozenset((a, b)) in bold
|
||||
ax.plot([pos[a][0], pos[b][0]], [pos[a][1], pos[b][1]],
|
||||
color="#cc2222" if hot else "#7a7a7a",
|
||||
lw=2.8 if hot else 1.1, zorder=2)
|
||||
|
||||
|
||||
def draw_verts(ax, by_level=False):
|
||||
for v, p in pos.items():
|
||||
fc = LEVCOL[an.level[v]] if by_level else "white"
|
||||
ax.plot(*p, "o", ms=20, mfc=fc, mec="#222222", mew=1.6, zorder=4)
|
||||
ax.annotate(str(v), p, ha="center", va="center", fontsize=10,
|
||||
fontweight="bold", zorder=5)
|
||||
|
||||
|
||||
draw_faces(axes[0]); draw_edges(axes[0]); draw_verts(axes[0])
|
||||
axes[0].set_title("A. ring [3,6,3] + face leaf, 12 vertices\n"
|
||||
"straight-line planar embedding (verified crossing-free)",
|
||||
fontsize=10)
|
||||
|
||||
draw_faces(axes[1])
|
||||
seam_edges = {frozenset((odd_seam[i], odd_seam[(i+1) % len(odd_seam)]))
|
||||
for i in range(len(odd_seam))}
|
||||
axes[1].add_patch(Polygon([pos[v] for v in terminal], closed=True,
|
||||
facecolor="#fee0d2", edgecolor="none", zorder=1))
|
||||
draw_edges(axes[1], bold=seam_edges)
|
||||
draw_verts(axes[1], by_level=True)
|
||||
tc = np.mean([pos[v] for v in terminal], axis=0)
|
||||
axes[1].annotate("terminal triangle " + "-".join(map(str, terminal)) +
|
||||
"\n(level-2 odd seam; carries the leaf\ngadget whose removal "
|
||||
"STILL FAILS)",
|
||||
xy=tc, xytext=(0.02, 0.99), textcoords="axes fraction",
|
||||
ha="left", va="top", fontsize=8, color="#a63603",
|
||||
arrowprops=dict(arrowstyle="->", color="#a63603", lw=1.2),
|
||||
zorder=6)
|
||||
axes[1].set_title("B. BFS levels from source 0-1-2 "
|
||||
"(grey 0 / blue 1 / red 2)\nodd level-2 seam "
|
||||
+ "-".join(map(str, odd_seam)) + " bold red",
|
||||
fontsize=10)
|
||||
|
||||
fig.suptitle("Minimal genuine obstruction (seed2 #26): the programme fails here "
|
||||
"even after exhausting\nsites x tread-phases x root colour-orders "
|
||||
"(24 settings, 0 ok) -- a face-leaf / gadget case.", fontsize=10)
|
||||
fig.tight_layout(rect=(0, 0, 1, 0.9))
|
||||
out = os.path.join(HERE, "failing_graph_seed2_26.png")
|
||||
fig.savefig(out, dpi=160)
|
||||
print("wrote", out)
|
||||
@@ -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()
|
||||
@@ -0,0 +1,166 @@
|
||||
"""Draw every full medial tire graph from the seed-1 analysis, one note each.
|
||||
|
||||
For each M(T) found by tire_realization_analysis.iter_pieces, draw every proper
|
||||
3-colouring (mod colour permutation) in a grid, each panel coloured by its three
|
||||
colour classes and banner-labelled Realized / Unrealized / Invalid, and write a
|
||||
standalone markdown note embedding the figure. Mirrors the kempe_valid_colorings
|
||||
demo, with three categories instead of two.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import os
|
||||
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from tire_realization_analysis import iter_pieces
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
CLASS_PALETTE = {0: "#e6550d", 1: "#3182bd", 2: "#31a354"} # colour classes
|
||||
CAT_COLOR = {"Realized": "#2ca02c", "Unrealized": "#ff7f0e", "Invalid": "#d62728"}
|
||||
CAT_ORDER = {"Realized": 0, "Unrealized": 1, "Invalid": 2}
|
||||
|
||||
|
||||
def _positions(g):
|
||||
n = g.n
|
||||
matched = g.bite_edges
|
||||
|
||||
def ann(k):
|
||||
a = math.pi / 2 - 2 * math.pi * k / n
|
||||
return math.cos(a), math.sin(a)
|
||||
|
||||
def mid(i):
|
||||
return math.pi / 2 - 2 * math.pi * (i + 0.5) / n
|
||||
|
||||
pos = {f"a{k}": ann(k) for k in range(n)}
|
||||
for i, t in enumerate(g.tooth_word):
|
||||
if t == "U":
|
||||
pos[f"u{i}"] = (1.42 * math.cos(mid(i)), 1.42 * math.sin(mid(i)))
|
||||
elif i not in matched:
|
||||
pos[f"d{i}"] = (0.58 * math.cos(mid(i)), 0.58 * math.sin(mid(i)))
|
||||
for i, j in sorted(g.bites):
|
||||
corners = [ann(i), ann((i + 1) % n), ann(j), ann((j + 1) % n)]
|
||||
cx = sum(p[0] for p in corners) / 4.0
|
||||
cy = sum(p[1] for p in corners) / 4.0
|
||||
pos[f"p{i}_{j}"] = (cx * 0.82, cy * 0.82)
|
||||
return pos
|
||||
|
||||
|
||||
def _draw(ax, g, pos, coloring, category):
|
||||
n = g.n
|
||||
for u, v in g.edges():
|
||||
ax.plot([pos[u][0], pos[v][0]], [pos[u][1], pos[v][1]],
|
||||
color="#cccccc", lw=0.4, zorder=1)
|
||||
for k in range(n):
|
||||
a, b = f"a{k}", f"a{(k + 1) % n}"
|
||||
ax.plot([pos[a][0], pos[b][0]], [pos[a][1], pos[b][1]],
|
||||
color="#777777", lw=0.8, zorder=2)
|
||||
for v, (x, y) in pos.items():
|
||||
big = v.startswith("p")
|
||||
ax.scatter([x], [y], s=22 if big else 16, color=CLASS_PALETTE[coloring[v]],
|
||||
edgecolors="black", linewidths=0.3, zorder=3)
|
||||
ax.set_xlim(-1.6, 1.6)
|
||||
ax.set_ylim(-1.7, 1.6)
|
||||
ax.set_aspect("equal")
|
||||
ax.axis("off")
|
||||
ax.set_title(category, fontsize=6, color=CAT_COLOR[category], pad=1.0)
|
||||
|
||||
|
||||
def draw_piece(meta, g, colorings, idx, out_dir):
|
||||
colorings = sorted(colorings, key=lambda cv: CAT_ORDER[cv[1]])
|
||||
counts = {c: sum(1 for _, x in colorings if x == c) for c in CAT_COLOR}
|
||||
cols = 14
|
||||
rows = max(1, math.ceil(len(colorings) / cols))
|
||||
fig, axes = plt.subplots(rows, cols, figsize=(cols * 1.15, rows * 1.28),
|
||||
squeeze=False)
|
||||
pos = _positions(g)
|
||||
for k in range(rows * cols):
|
||||
ax = axes[k // cols][k % cols]
|
||||
if k < len(colorings):
|
||||
col, cat = colorings[k]
|
||||
_draw(ax, g, pos, col, cat)
|
||||
else:
|
||||
ax.axis("off")
|
||||
bites = ",".join(f"({i},{j})" for i, j in sorted(g.bites)) or "none"
|
||||
fig.suptitle(
|
||||
f"M(T) from source {meta['source']}, tread T{meta['tread']}: "
|
||||
f"|A(T)|={g.n}, word={g.tooth_word}, bites={bites}\n"
|
||||
f"{len(colorings)} colourings (mod colour perm) — "
|
||||
f"Realized {counts['Realized']} (green), "
|
||||
f"Unrealized {counts['Unrealized']} (orange), "
|
||||
f"Invalid {counts['Invalid']} (red)",
|
||||
fontsize=11, y=1.0 - 0.0,
|
||||
)
|
||||
fig.tight_layout(rect=(0, 0, 1, 0.985))
|
||||
base = f"piece_{idx:02d}_src{meta['source']}_T{meta['tread']}"
|
||||
png = os.path.join(out_dir, base + ".png")
|
||||
pdf = os.path.join(out_dir, base + ".pdf")
|
||||
fig.savefig(png, dpi=110)
|
||||
fig.savefig(pdf)
|
||||
plt.close(fig)
|
||||
|
||||
note = os.path.join(out_dir, base + ".md")
|
||||
with open(note, "w") as fh:
|
||||
fh.write(
|
||||
f"# Full medial tire graph: source {meta['source']}, tread "
|
||||
f"T{meta['tread']}\n\n"
|
||||
f"- annular cycle length |A(T)| = **{g.n}**\n"
|
||||
f"- tooth word: `{g.tooth_word}` "
|
||||
f"({len(g.up_edges)} up, {len(g.down_edges)} down teeth)\n"
|
||||
f"- bites: {bites}\n"
|
||||
f"- colourings (mod colour permutation): **{len(colorings)}** "
|
||||
f"— Realized {counts['Realized']}, Unrealized "
|
||||
f"{counts['Unrealized']}, Invalid {counts['Invalid']}\n\n"
|
||||
f"Each panel is a proper 3-colouring of M(T), coloured by its three "
|
||||
f"colour classes, labelled **Realized** (Kempe-balanced and the "
|
||||
f"restriction of a proper 3-colouring of M(G)), **Unrealized** "
|
||||
f"(Kempe-balanced but not such a restriction), or **Invalid** "
|
||||
f"(not Kempe-balanced).\n\n"
|
||||
f"\n\n"
|
||||
f"Vector copy: [`{base}.pdf`]({base}.pdf).\n"
|
||||
)
|
||||
return base, counts
|
||||
|
||||
|
||||
def main(seed: int = 1):
|
||||
out_dir = os.path.join(HERE, f"tire_realization_seed{seed}")
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
index = []
|
||||
idx = 0
|
||||
ctx = None
|
||||
for item in iter_pieces(seed):
|
||||
if item[0] == "__context__":
|
||||
ctx = item
|
||||
continue
|
||||
meta, g, colorings = item
|
||||
base, counts = draw_piece(meta, g, colorings, idx, out_dir)
|
||||
print(f"piece {idx}: {base} {counts}")
|
||||
index.append((idx, meta, g, counts, base))
|
||||
idx += 1
|
||||
|
||||
_, G, M, n_global = ctx
|
||||
with open(os.path.join(out_dir, "README.md"), "w") as fh:
|
||||
fh.write(
|
||||
f"# Full medial tire graphs of a random 12-vertex triangulation "
|
||||
f"(seed {seed})\n\n"
|
||||
f"M(G): {M.number_of_nodes()} medial vertices, {n_global} proper "
|
||||
f"3-colourings. {len(index)} full medial tire graphs, one note each "
|
||||
f"below.\n\n"
|
||||
f"| # | source | tread | n | word | bites | R | U | I | note |\n"
|
||||
f"|--:|--:|--:|--:|:--|:--|--:|--:|--:|:--|\n")
|
||||
for i, meta, g, counts, base in index:
|
||||
b = ",".join(f"({x},{y})" for x, y in sorted(g.bites)) or "-"
|
||||
fh.write(
|
||||
f"| {i} | {meta['source']} | T{meta['tread']} | {g.n} | "
|
||||
f"`{g.tooth_word}` | {b} | {counts['Realized']} | "
|
||||
f"{counts['Unrealized']} | {counts['Invalid']} | "
|
||||
f"[{base}.md]({base}.md) |\n")
|
||||
print(f"wrote {len(index)} notes to {out_dir}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,238 @@
|
||||
"""Step-by-step picture of the even-level-cycle programme on the smallest clean
|
||||
example: the ring triangulation sizes=[3,5], leaf='hub' (rng seed 0), 9 vertices.
|
||||
|
||||
One odd level cycle (level 1, the 5-cycle 3-4-5-6-7), no terminal triangles, so
|
||||
the only surgery is a single DIAMOND. We walk the FIRST successful choice-set
|
||||
found by the sweep: insertion site = edge (3,4); colour phase = (0,); root DFS
|
||||
colour order = (1,0,2). Panels:
|
||||
|
||||
A G with its odd level-5 seam (BFS levels from the outer triangle 0-1-2).
|
||||
B G' = G + diamond w(=9) on edge (3,4): seam is now an even 6-cycle; the
|
||||
diamond quad 3-0-4-8 (restored diagonal 3-4) shaded.
|
||||
C medial M(G') with the canonical colouring BEFORE any switch: the four quad
|
||||
medials m(0,3),m(0,4),m(4,8),m(3,8) are ALL colour 1 -> diamond_condition
|
||||
fails (the obstruction).
|
||||
D after one {1,2}-Kempe switch on the component through m(0,3)
|
||||
{(0,3),(3,7),(3,8),(3,9)}: quad medials become 2,1,1,2 -> reducible;
|
||||
remove w, restored diagonal (3,4) takes the third colour 0.
|
||||
|
||||
Embedding: networkx planar_layout (canonical-ordering straight-line embedding),
|
||||
recentred, with EVERY panel verified crossing-free before drawing -- G, G', and
|
||||
the medial M(G') drawn at edge midpoints. G' is embedded once and reused for
|
||||
panels B/C/D; G reuses it (minus w, with the diagonal 3-4 restored) when that is
|
||||
still crossing-free, else it is embedded independently. Run with the repo venv
|
||||
python (numpy + matplotlib + networkx).
|
||||
"""
|
||||
import os, random
|
||||
import numpy as np
|
||||
import networkx as nx
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.patches import Polygon
|
||||
import kempe_even_program_harness as H
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
PAL = {0: "#e6550d", 1: "#3182bd", 2: "#31a354"} # colours "1","2","3"(=2)
|
||||
|
||||
|
||||
def planar_pos(g):
|
||||
nxg = nx.Graph()
|
||||
nxg.add_nodes_from(g.rot)
|
||||
for ed in g.edges():
|
||||
a, b = tuple(ed); nxg.add_edge(a, b)
|
||||
ok, _ = nx.check_planarity(nxg)
|
||||
assert ok, "graph not planar?!"
|
||||
pos = nx.planar_layout(nxg)
|
||||
pts = np.array([pos[v] for v in g.rot]); c = pts.mean(axis=0)
|
||||
s = np.abs(pts - c).max()
|
||||
return {v: ((pos[v][0]-c[0])/s, (pos[v][1]-c[1])/s) for v in g.rot}
|
||||
|
||||
|
||||
def seg_cross(p, q, r, s):
|
||||
def o(a, b, c):
|
||||
return (b[0]-a[0])*(c[1]-a[1]) - (b[1]-a[1])*(c[0]-a[0])
|
||||
d1, d2, d3, d4 = o(r, s, p), o(r, s, q), o(p, q, r), o(p, q, s)
|
||||
return ((d1 > 0) != (d2 > 0)) and ((d3 > 0) != (d4 > 0))
|
||||
|
||||
|
||||
def crossings(edges, pos):
|
||||
bad = []
|
||||
for i in range(len(edges)):
|
||||
a, b = edges[i]
|
||||
for j in range(i+1, len(edges)):
|
||||
c, d = edges[j]
|
||||
if len({a, b, c, d}) < 4:
|
||||
continue
|
||||
if seg_cross(pos[a], pos[b], pos[c], pos[d]):
|
||||
bad.append((edges[i], edges[j]))
|
||||
return bad
|
||||
|
||||
|
||||
def mid(p, q): return ((p[0]+q[0])/2, (p[1]+q[1])/2)
|
||||
|
||||
# ---- build G and G' ------------------------------------------------------
|
||||
rng = random.Random(0)
|
||||
g, outer = H.ring_triangulation([3, 5], 'hub', rng)
|
||||
an = H.Analysis(g.copy(), outer)
|
||||
ring = [c for k, c in an.seams if k == 1][0]
|
||||
|
||||
prep = H._prep_gadgets(g.copy(), outer)
|
||||
template, an_g, gadgets = prep
|
||||
gg = template.copy()
|
||||
w, u, v, x, t = gg.insert_diamond(3, 4)
|
||||
an2 = H.Analysis(gg, outer)
|
||||
ring2 = [c for k, c in an2.seams if k == 1][0]
|
||||
quad = H.quad_of(gg, w, u, v) # (3,0,4,8)
|
||||
|
||||
# embed G' (verified), reuse for G if still crossing-free else embed G alone
|
||||
posGp = planar_pos(gg)
|
||||
edgesGp = [tuple(e) for e in gg.edges()]
|
||||
badGp = crossings(edgesGp, posGp)
|
||||
print("G' crossings:", badGp if badGp else "NONE")
|
||||
assert not badGp
|
||||
|
||||
posG = {vv: posGp[vv] for vv in g.rot}
|
||||
edgesG = [tuple(e) for e in g.edges()]
|
||||
badG = crossings(edgesG, posG)
|
||||
if badG:
|
||||
print("G reuse crossed; embedding G independently")
|
||||
posG = planar_pos(g)
|
||||
badG = crossings([tuple(e) for e in g.edges()], posG)
|
||||
print("G crossings:", badG if badG else "NONE")
|
||||
assert not badG
|
||||
|
||||
# medial drawn at edge midpoints. The medial drawing at midpoints is planar
|
||||
# EXCEPT for the medial triangle of whichever face is geometrically OUTER
|
||||
# (its three midpoint-chords would cut straight across the unbounded region), so
|
||||
# we omit exactly those three edges, then verify the rest are crossing-free.
|
||||
def convex_hull(points):
|
||||
pts = sorted(points)
|
||||
def cross(o, a, b):
|
||||
return (a[0]-o[0])*(b[1]-o[1]) - (a[1]-o[1])*(b[0]-o[0])
|
||||
lo = []
|
||||
for p in pts:
|
||||
while len(lo) >= 2 and cross(lo[-2], lo[-1], p) <= 0:
|
||||
lo.pop()
|
||||
lo.append(p)
|
||||
up = []
|
||||
for p in reversed(pts):
|
||||
while len(up) >= 2 and cross(up[-2], up[-1], p) <= 0:
|
||||
up.pop()
|
||||
up.append(p)
|
||||
return lo[:-1] + up[:-1]
|
||||
|
||||
adjm = H.medial_adj(gg)
|
||||
mpos = {m: mid(posGp[tuple(m)[0]], posGp[tuple(m)[1]]) for m in adjm}
|
||||
hull = convex_hull(list(posGp.values()))
|
||||
pos2v = {tuple(p): v for v, p in posGp.items()}
|
||||
outer_face = {pos2v[tuple(p)] for p in hull}
|
||||
print("geometric outer face (hull):", sorted(outer_face))
|
||||
medges = []
|
||||
seen = set()
|
||||
for m in adjm:
|
||||
for b in adjm[m]:
|
||||
k = frozenset((m, b))
|
||||
if k in seen:
|
||||
continue
|
||||
seen.add(k)
|
||||
# skip the medial edge joining two edges of the outer face
|
||||
if set(m) <= outer_face and set(b) <= outer_face:
|
||||
continue
|
||||
medges.append((m, b))
|
||||
badM = crossings(medges, mpos)
|
||||
print("M(G') crossings (outer-face medial triangle omitted):",
|
||||
badM if badM else "NONE")
|
||||
assert not badM
|
||||
|
||||
# ---- colourings ----------------------------------------------------------
|
||||
col0, _ = H.canonical_coloring_explicit(gg, an2.level, outer, (0,), [1, 0, 2])
|
||||
col1 = dict(col0)
|
||||
comp = H.kempe_component(col1, adjm, H.e(0, 3), (1, 2))
|
||||
H.switch(col1, comp, (1, 2))
|
||||
third = H.diamond_condition(col1, quad)
|
||||
col1[H.e(3, 4)] = third
|
||||
|
||||
# ---- drawing -------------------------------------------------------------
|
||||
def lims(ax, pos):
|
||||
xs = [p[0] for p in pos.values()]; ys = [p[1] for p in pos.values()]
|
||||
ax.set_xlim(min(xs)-0.25, max(xs)+0.25); ax.set_ylim(min(ys)-0.25, max(ys)+0.3)
|
||||
|
||||
def draw_graph(ax, gr, pos, level=None, bold_cycle=None, shade_quad=None, wvert=None):
|
||||
if shade_quad:
|
||||
ax.add_patch(Polygon([pos[vv] for vv in shade_quad], closed=True,
|
||||
color="#ffe2bf", zorder=0))
|
||||
bold = set()
|
||||
if bold_cycle:
|
||||
for i in range(len(bold_cycle)):
|
||||
bold.add(frozenset((bold_cycle[i], bold_cycle[(i+1) % len(bold_cycle)])))
|
||||
for ed in gr.edges():
|
||||
a, b = tuple(ed); pa, pb = pos[a], pos[b]
|
||||
hot = ed in bold
|
||||
ax.plot([pa[0], pb[0]], [pa[1], pb[1]],
|
||||
color="#d62728" if hot else "#888888",
|
||||
lw=2.8 if hot else 1.1, zorder=2)
|
||||
for vv, p in pos.items():
|
||||
c = "#d62728" if (wvert is not None and vv == wvert) else "#222222"
|
||||
ax.plot(*p, "o", ms=18, mfc="white", mec=c, mew=1.7, zorder=5)
|
||||
ax.annotate(str(vv), p, ha="center", va="center", fontsize=9,
|
||||
fontweight="bold", color=c, zorder=6)
|
||||
if level is not None:
|
||||
ax.annotate(f"L{level[vv]}", p, textcoords="offset points",
|
||||
xytext=(11, 10), fontsize=6.5, color="#999999", zorder=6)
|
||||
|
||||
def draw_medial(ax, pos, col, halo=None, restored=None):
|
||||
# medial graph only -- no base graph underneath
|
||||
for m, b in medges:
|
||||
pa, pb = mpos[m], mpos[b]
|
||||
ax.plot([pa[0], pb[0]], [pa[1], pb[1]], color="#c6c6c6", lw=0.8, zorder=1)
|
||||
if restored is not None:
|
||||
a, b = restored
|
||||
ax.plot(*mid(pos[a], pos[b]), "s", color=PAL[col[H.e(a, b)]], ms=13,
|
||||
mec="#d62728", mew=2.0, zorder=7)
|
||||
halo = halo or set()
|
||||
for m, p in mpos.items():
|
||||
if m not in col:
|
||||
continue
|
||||
if m in halo:
|
||||
ax.plot(*p, "o", color="#000000", ms=16, zorder=5)
|
||||
ax.plot(*p, "o", color=PAL[col[m]], ms=10.5, mec="black", mew=0.8, zorder=6)
|
||||
|
||||
fig, axes = plt.subplots(1, 4, figsize=(19, 5.4))
|
||||
for ax in axes:
|
||||
ax.set_aspect("equal"); ax.axis("off")
|
||||
|
||||
draw_graph(axes[0], g, posG, level=an.level, bold_cycle=ring)
|
||||
lims(axes[0], posG)
|
||||
axes[0].set_title("A. G (BFS levels from source triangle 0-1-2)\n"
|
||||
"odd level-1 seam = 5-cycle 3-4-5-6-7 (red)\n"
|
||||
"verified straight-line planar embedding", fontsize=9)
|
||||
|
||||
draw_graph(axes[1], gg, posGp, level=an2.level, bold_cycle=ring2,
|
||||
shade_quad=quad, wvert=w)
|
||||
lims(axes[1], posGp)
|
||||
axes[1].set_title("B. G' = G + diamond w=9 on edge (3,4)\n"
|
||||
"seam now even 6-cycle; quad 3-0-4-8 shaded", fontsize=9)
|
||||
|
||||
quad_med = {H.e(quad[i], quad[(i+1) % 4]) for i in range(4)}
|
||||
draw_medial(axes[2], posGp, col0, halo=quad_med)
|
||||
lims(axes[2], posGp)
|
||||
axes[2].set_title("C. M(G') canonical colour (phase 0, DFS order 1,0,2)\n"
|
||||
"quad medials m(0,3)m(0,4)m(4,8)m(3,8) ALL =1 (haloed)\n"
|
||||
"-> diamond_condition FAILS", fontsize=9)
|
||||
|
||||
draw_medial(axes[3], posGp, col1, halo=comp, restored=(3, 4))
|
||||
lims(axes[3], posGp)
|
||||
axes[3].set_title("D. after {1,2}-Kempe switch on comp through m(0,3)\n"
|
||||
"{(0,3),(3,7),(3,8),(3,9)} (haloed): quad -> 2,1,1,2\n"
|
||||
f"remove w; restored edge (3,4)=square takes colour {third}",
|
||||
fontsize=9)
|
||||
|
||||
fig.suptitle("Even-level-cycle programme, worked example (ring [3,5]+hub, 9 "
|
||||
"vertices): one odd seam -> one diamond -> one Kempe switch -> "
|
||||
"proper 3-colouring of M(G). Colours: 1=orange(0), 2=blue(1), "
|
||||
"3=green(2).", fontsize=10)
|
||||
fig.tight_layout(rect=(0, 0, 1, 0.9))
|
||||
out = os.path.join(HERE, "even_program_walkthrough.png")
|
||||
fig.savefig(out, dpi=160)
|
||||
print("wrote", out)
|
||||
@@ -0,0 +1,90 @@
|
||||
"""Print every stage of the even-level-cycle programme on the smallest clean
|
||||
example (ring sizes=[3,5], leaf='hub', rng seed 0; 9 vertices) for the first
|
||||
choice-set the sweep succeeds on: site (3,4), phase (0,), DFS order (1,0,2).
|
||||
|
||||
This is the textual companion to even_program_walkthrough.md / .png.
|
||||
"""
|
||||
import random
|
||||
import kempe_even_program_harness as H
|
||||
|
||||
|
||||
def fz(m): # pretty-print an edge-medial
|
||||
return tuple(sorted(tuple(m)))
|
||||
|
||||
|
||||
def main():
|
||||
rng = random.Random(0)
|
||||
g, outer = H.ring_triangulation([3, 5], 'hub', rng)
|
||||
print("OUTER (source/root triangle):", outer)
|
||||
|
||||
print("\n# STEP 1: graph G (rotation system: vertex -> embedding-order neighbours)")
|
||||
for v in sorted(g.rot):
|
||||
print(f" {v}: {g.rot[v]}")
|
||||
print("faces:", [tuple(f) for f in g.faces()])
|
||||
|
||||
print("\n# STEP 2: levels (BFS from the source triangle) + seams")
|
||||
an = H.Analysis(g.copy(), outer)
|
||||
for v in sorted(an.level):
|
||||
print(f" v{v}: level {an.level[v]}")
|
||||
for k, cyc in an.seams:
|
||||
print(f" seam level {k}: {cyc} (len {len(cyc)}, "
|
||||
f"{'ODD' if len(cyc) % 2 else 'even'})")
|
||||
print(" terminal triangles (need leaf gadget):", an.terminal)
|
||||
|
||||
print("\n# STEP 3: diamond sites + chosen edge")
|
||||
template, an_g, gadgets = H._prep_gadgets(g.copy(), outer)
|
||||
sites = H._candidate_sites(an_g)
|
||||
print(" gadgets inserted:", gadgets)
|
||||
print(" candidate diamond edges (odd seam):", sites)
|
||||
combo = ((3, 4),)
|
||||
print(" chosen combo (first successful):", combo)
|
||||
gg = template.copy()
|
||||
dia = [gg.insert_diamond(a, b) for (a, b) in combo]
|
||||
print(" inserted (w,u,v,x,t):", dia)
|
||||
an2 = H.Analysis(gg, outer)
|
||||
print(" rot[w]:", gg.rot[dia[0][0]], " level[w]:", an2.level[dia[0][0]])
|
||||
for k, cyc in an2.seams:
|
||||
print(f" seam level {k} now: len {len(cyc)} "
|
||||
f"{'ODD' if len(cyc) % 2 else 'even'} {cyc}")
|
||||
|
||||
print("\n# STEP 4: medial graph M(G') (one vertex per edge of G')")
|
||||
adj = H.medial_adj(gg)
|
||||
print(f" |V(M)| = {len(adj)}")
|
||||
for m in sorted(adj, key=fz):
|
||||
print(f" m{fz(m)}: {sorted(fz(b) for b in adj[m])}")
|
||||
|
||||
print("\n# STEP 5: canonical colouring phases=(0,) colorder=(1,0,2)")
|
||||
phases, colorder = (0,), [1, 0, 2]
|
||||
sk, _ = H.coloring_skeleton(gg, an2.level, outer)
|
||||
for i, cyc in enumerate(sk['nonroot']):
|
||||
print(f" non-root annulus #{i} (len {len(cyc)}): {[fz(m) for m in cyc]}")
|
||||
print(" root annulus:", [fz(m) for m in sk['root']])
|
||||
print(" outer-trio (free/DFS):", [fz(m) for m in sk['outer_es']])
|
||||
col, _ = H.canonical_coloring_explicit(gg, an2.level, outer, phases, colorder)
|
||||
for m in sorted(col, key=fz):
|
||||
print(f" m{fz(m)} = {col[m]}")
|
||||
|
||||
print("\n# STEP 6: Kempe switch + diamond collapse")
|
||||
w, u, v, x, t = dia[0]
|
||||
quad = H.quad_of(gg, w, u, v)
|
||||
support = [H.e(quad[i], quad[(i + 1) % 4]) for i in range(4)]
|
||||
print(f" diamond w={w}, quad {quad} (diagonal {u}-{v})")
|
||||
print(" quad medials:", [fz(s) for s in support])
|
||||
print(" diamond_condition BEFORE switch:", H.diamond_condition(col, quad),
|
||||
" support:", {fz(s): col[s] for s in support})
|
||||
adjm = H.medial_adj(gg)
|
||||
comp = H.kempe_component(col, adjm, H.e(0, 3), (1, 2))
|
||||
print(" switch {1,2}-component through m(0,3):", sorted(fz(m) for m in comp))
|
||||
H.switch(col, comp, (1, 2))
|
||||
third = H.diamond_condition(col, quad)
|
||||
print(" diamond_condition AFTER switch:", third,
|
||||
" support:", {fz(s): col[s] for s in support})
|
||||
H.collapse_degree4(gg, col, w, u, v)
|
||||
col[H.e(u, v)] = third
|
||||
print(f" removed w; restored edge ({u},{v}) takes colour {third}")
|
||||
print(" proper 3-colouring of M(G)?", H.verify_proper(gg, col))
|
||||
print(" vertices back to original G?", set(gg.rot) == set(g.rot))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,141 @@
|
||||
# The even-level-cycle colouring program
|
||||
|
||||
A constructive route distinct from the `R_T` composition line. Idea: surger a
|
||||
triangulation `G` so that **every level cycle is even**, take the resulting
|
||||
*canonical even colouring* of `M(G')` (no 4CT used), then **remove the planted
|
||||
vertices** by Kempe switches, landing on a proper 3-colouring of `M(G)` — i.e. a
|
||||
Tait/4CT colouring of `G`.
|
||||
|
||||
Scripts: `kempe_even_program_harness.py`, `draw_evened_leaf.py` (`evened_leaf.png`).
|
||||
|
||||
## The two surgeries
|
||||
|
||||
- **Leaf gadget (two vertices).** On a terminal triangle `uvt` with outer apex
|
||||
`x`: add `y = mid(uv)` and hub `z`; delete `uv`; add `xy, uy, vy, zy, zu, zv,
|
||||
zt`. Both new vertices have degree 4; the seam becomes `u-y-v-t` (even) and the
|
||||
leaf becomes a **4-wheel** with hub `z`. No ears, no chord — the monochromatic-3
|
||||
seam stays valid, so **leaves create no colouring defect**. (Earlier one-vertex
|
||||
chord version forced a `{0011,0101}` defect; this is strictly better.)
|
||||
- **Diamond.** On an odd internal seam edge `uv` with apexes `x,t`: delete `uv`,
|
||||
add `w ~ u,v,x,t` (degree 4). Flips that seam's parity.
|
||||
|
||||
By `n_T = p + Σq_i + 2b`, evening every internal seam makes every annular cycle
|
||||
even **except the root** (the outer triangle's odd charge `Σ_T n_T ≡ 3 (mod 2)` is
|
||||
invariant — confirmed; the root is handled as the one unavoidable defect region,
|
||||
solved by local backtracking).
|
||||
|
||||
## Canonical even colouring (constructive, no 4CT)
|
||||
|
||||
Every level-edge medial vertex → colour 3; every non-root annular cycle alternates
|
||||
1,2; the root region solved by DFS. Proper because each apex is forced 3 between
|
||||
two `{1,2}` pairs and (in the non-degenerate tread model) no two level edges are
|
||||
consecutive around a vertex or face.
|
||||
|
||||
## Removal conditions (degree-4 Kempe reduction — the historically *safe* case)
|
||||
|
||||
- **Diamond** `w` (quad `u-x-v-t`, restore diagonal `uv`): removable iff the pair
|
||||
`(m_ux,m_xv)` is distinct, `(m_vt,m_tu)` is distinct, ≤2 colours total; then
|
||||
`m_uv` takes the third.
|
||||
- **Gadget**: collapse `z` then `y` (or `y` then `z`), ending in a degree-3
|
||||
unstellation needing a rainbow triangle. Two orders = free choice.
|
||||
|
||||
## Status (synthetic ring triangulations, the clean-level-structure domain)
|
||||
|
||||
Pipeline runs end to end. Surgery, canonical colouring, and gadget removal all
|
||||
work. The program now lands squarely on the **cycle layer**.
|
||||
|
||||
The original `60 random ring triangulations: 39 ok, 21 fail` figure was the
|
||||
**first-match heuristic** — one diamond per odd seam, placed at the *first*
|
||||
admissible seam edge, only the colouring phase varied (≤4 random tread phases).
|
||||
That is one point in the insertion-site design space, not a sweep of it.
|
||||
|
||||
**Site sweep (`run_graph` now enumerates every combination of insertion sites,
|
||||
one per odd seam, ≤4 colour phases each; `--max-combos` caps the product).**
|
||||
A graph counts `ok` iff *some* placement fully descends:
|
||||
|
||||
```
|
||||
seed 1, 60 graphs: first-match 31 ok / 29 fail -> sweep 54 ok / 6 fail (rescued 23)
|
||||
seed 2, 60 graphs: first-match 36 ok / 24 fail -> sweep 57 ok / 3 fail (rescued 21)
|
||||
```
|
||||
|
||||
(First-match is seed-sensitive — 31–39 depending on seed; the 39 was one such
|
||||
seed. What is robust is the *gap*: sweeping insertion sites rescues ~20 of the
|
||||
~24 first-match failures, leaving a small stubborn residue of ~3–6
|
||||
`fail:diamond-switch` graphs.) Design space is real but modest: ~50 graphs need
|
||||
a diamond, ~2900 combos total, max ~900–1200 on a single graph (a handful hit
|
||||
the cap). So the answer to "did we test every way of adding a diamond?" is:
|
||||
**now yes** (per odd seam, up to the cap), and most of the apparent failures
|
||||
were heuristic, not intrinsic.
|
||||
|
||||
**Crucial diagnostic:** for a failing case, a simultaneously-removable proper
|
||||
3-colouring of `M(G')` was shown to **exist** (it must — `M(G)` is 3-colourable).
|
||||
So `fail:diamond-switch` is **not** non-existence; it is **Kempe-reachability** —
|
||||
whether switches carry the *canonical even* colouring to a descendable one. That is
|
||||
exactly the conjecture's core, and the harness has localised the entire program
|
||||
difficulty to it, with everything upstream constructive.
|
||||
|
||||
**Why greedy fails (and what's next).** Diamonds on different odd seams share
|
||||
*vertical* `{1,3}`-Kempe cycles, so per-diamond local switching cannot satisfy them
|
||||
simultaneously. The principled solve is joint: vertices = `{1,3}`-Kempe cycles,
|
||||
one edge per diamond joining its two side cycles; removability for all diamonds at
|
||||
once = a consistent XOR assignment = **bipartiteness** of that graph (no self-loop =
|
||||
the side cycles differ; no odd cycle = no three diamonds whose side cycles form a
|
||||
triangle). Insertion-site choice (which seam edge) and tread phase are the control
|
||||
knobs. Building this joint solver — and finding the smallest configuration, if any,
|
||||
forcing a self-loop or odd cycle — is the next step and the exact thing a proof
|
||||
would need to rule out.
|
||||
|
||||
## Exhausting the control knobs over the residue
|
||||
|
||||
The site sweep above counts a graph `ok` if some placement works over only **4
|
||||
random** colour phases per combo. `residue_phase_sweep.py` takes the graphs that
|
||||
sweep still fails (the residue) and **exhaustively enumerates the colour/tread
|
||||
phase and the root-DFS colour order** on top of every insertion site:
|
||||
|
||||
```
|
||||
phases in {0,1}^A (A = # non-root annuli; the tread phases)
|
||||
colorder in perms(0,1,2) (root-region DFS colour priority)
|
||||
```
|
||||
|
||||
over all site combos (cap 512). Result on the seed-1/seed-2 residue
|
||||
(`residue_phase_sweep_results.txt`):
|
||||
|
||||
```
|
||||
seed1 #16 [3,8,3,5] hub RESCUED (720 settings, 18 ok)
|
||||
seed1 #51 [3,7,3,3,7] hub RESCUED (42336 settings, 196 ok)
|
||||
seed1 #3 [3,7,4,6,3] face STILL FAILS (672 settings, 0 ok)
|
||||
seed1 #4 [3,4,5,5,3] face STILL FAILS (2400 settings, 0 ok)
|
||||
seed2 #26 [3,6,3] face STILL FAILS (24 settings, 0 ok)
|
||||
seed2 #30 [3,3,6,7,3] face STILL FAILS (2016 settings, 0 ok)
|
||||
seed2 #54 [3,3,5,3] face STILL FAILS (720 settings, 0 ok)
|
||||
```
|
||||
|
||||
Two things fall out:
|
||||
|
||||
1. **Phase reachability explains part of the residue.** The two `hub` graphs are
|
||||
*rescued* once the phase/colour-order is enumerated rather than sampled — they
|
||||
were never genuine obstructions, just unlucky random phases. So the
|
||||
random-phase `fail` count overstates the true difficulty.
|
||||
2. **The genuine obstructions are exactly the `face`-leaf graphs.** Every graph
|
||||
that survives exhausting sites × phases × colour-orders has `leaf='face'` —
|
||||
i.e. an inner terminal triangle carrying a leaf gadget. The smallest is
|
||||
`seed2 #26 [3,6,3]` (one site combo, 24 settings, all fail at
|
||||
`gadget-removal`): a minimal target for the joint solver / an obstruction
|
||||
hunt. (#26 fails at the gadget step; #3/#4/#30/#54 at `diamond-switch`.)
|
||||
|
||||
**Caveat on "STILL FAILS".** `try_establish` is a *bounded* local Kempe search
|
||||
(≤3 components anchored at the quad support). So `STILL FAILS` means *no (site,
|
||||
phase, colour-order) lets the bounded search from the canonical-even colouring
|
||||
reach a descendable one* — not that no Kempe path exists. A descendable colouring
|
||||
provably exists (M(G) is 3-colourable); whether it is reachable under a principled
|
||||
(joint, unbounded) switch is the open question, now sharply localised to the
|
||||
`face`-leaf family.
|
||||
|
||||
## Caveats / domain
|
||||
|
||||
- Real plantri triangulations mostly `skip:chord-level-edge` under BFS-from-outer
|
||||
level structure — a reflection of how restrictive the clean nested-tire level
|
||||
structure is, not a harness bug. The synthetic concentric-ring generator produces
|
||||
the clean domain the program is stated for.
|
||||
- Root defect and the (deferred) outer-face handling are localised; the user has a
|
||||
separate idea for the outer face.
|
||||
@@ -0,0 +1,196 @@
|
||||
# Even-level-cycle programme — a fully worked example
|
||||
|
||||
A step-by-step trace of the whole pipeline on the **smallest clean graph**: the
|
||||
synthetic ring triangulation `sizes=[3,5]`, `leaf='hub'` (generator
|
||||
`random.Random(0)`), **9 vertices**. It has exactly one odd level cycle and no
|
||||
terminal triangles, so the only surgery is a **single diamond** — which makes
|
||||
every stage small enough to print in full.
|
||||
|
||||
Everything below is the *actual* state produced by `kempe_even_program_harness.py`
|
||||
(regenerate the data with the dump at the end of this note; the figure is
|
||||
`even_program_walkthrough.png`, drawn by `draw_walkthrough.py`). We walk the
|
||||
**first choice-set the sweep finds that succeeds**:
|
||||
|
||||
> insertion site = edge `(3,4)` · colour phase = `(0,)` · root-DFS colour order = `(1,0,2)`
|
||||
|
||||
Colour convention throughout: values `{0,1,2}` are Tait colours "1,2,3"; `2` is
|
||||
the "colour 3" the seam is painted with. In the figure: `0`=orange, `1`=blue,
|
||||
`2`=green.
|
||||
|
||||
---
|
||||
|
||||
## Step 1 — Generate the triangulation with a plane embedding *(panel A)*
|
||||
|
||||
`ring_triangulation([3,5], 'hub')` builds three concentric rings — an outer
|
||||
triangle, a 5-ring, and a hub — triangulating each annulus with a random tooth
|
||||
word and capping the centre with a hub vertex. The result is a genuine plane
|
||||
triangulation given by its rotation system (neighbours in embedding order):
|
||||
|
||||
```
|
||||
0: [4, 3, 7, 6, 5, 2, 1] 3: [0, 4, 8, 7] 6: [5, 0, 7, 8]
|
||||
1: [4, 0, 2, 5] 4: [3, 0, 1, 5, 8] 7: [6, 0, 3, 8]
|
||||
2: [5, 1, 0] 5: [4, 1, 2, 0, 6, 8] 8: [3, 4, 5, 6, 7]
|
||||
```
|
||||
|
||||
The 14 triangular faces (one is the outer/unbounded face) are
|
||||
```
|
||||
(4,3,8) (4,0,3) (4,1,0) (4,5,1) (4,8,5) (3,0,7) (3,7,8)
|
||||
(0,6,7) (0,5,6) (0,2,5) (0,1,2) (1,5,2) (5,8,6) (6,8,7)
|
||||
```
|
||||
and the 21 edges are the pairs appearing above. (Euler check: 9 − 21 + 14 = 2.)
|
||||
The figure draws G (and G′, and the medial M(G′) at edge midpoints) with a
|
||||
straight-line planar embedding from `networkx.planar_layout`, each **verified
|
||||
crossing-free** before rendering (the medial triangle of the geometric outer
|
||||
face is omitted, since its midpoint-chords would otherwise cut across the
|
||||
unbounded region).
|
||||
|
||||
## Step 2 — Pick the source and read off levels *(panel A)*
|
||||
|
||||
The **source** is the outer triangle, taken as the unbounded face `(0,1,2)`.
|
||||
A BFS from those three vertices assigns each vertex its **level** (graph distance
|
||||
to the source tread):
|
||||
|
||||
| level | vertices |
|
||||
|------:|----------|
|
||||
| 0 | 0, 1, 2 (the source triangle) |
|
||||
| 1 | 3, 4, 5, 6, 7 (the ring) |
|
||||
| 2 | 8 (the hub) |
|
||||
|
||||
The **level cycles ("seams")** are the same-level edge cycles at each depth ≥1:
|
||||
|
||||
```
|
||||
level 1: cycle 3-4-5-6-7 length 5 -> ODD
|
||||
```
|
||||
|
||||
There is exactly one seam and it is **odd**. There are **no terminal
|
||||
triangles**, so the leaf gadget never fires — the only surgery needed is a
|
||||
diamond on this one odd seam.
|
||||
|
||||
## Step 3 — Choose the edge(s) that make the level cycles even *(panel B)*
|
||||
|
||||
A diamond can be inserted on any seam edge whose two apexes straddle the
|
||||
neighbouring levels (`k−1` and `k+1`). For the level-1 seam, **all five** seam
|
||||
edges qualify:
|
||||
|
||||
```
|
||||
candidate diamond sites: (3,4) (4,5) (5,6) (6,7) (7,3)
|
||||
```
|
||||
|
||||
This is the choice the **site sweep** ranges over (here a 5-element design
|
||||
space). We take the **first one that leads to a full success: `(3,4)`**.
|
||||
|
||||
Insert the diamond on `(3,4)`:
|
||||
- delete the edge `(3,4)`;
|
||||
- add a new degree-4 vertex `w = 9` adjacent to `u=3, v=4` and the two apexes
|
||||
`x=0` (level 0) and `t=8` (level 2), with rotation `rot[9] = [3,0,4,8]`.
|
||||
|
||||
`w` lands at level 1, so the level-1 seam becomes the cycle
|
||||
```
|
||||
3-9-4-5-6-7 length 6 -> EVEN
|
||||
```
|
||||
Every level cycle is now even. The four-cycle `3-0-4-8` around `w` (diagonal the
|
||||
restored edge `3-4`) is the **diamond quad** we must later collapse — shaded in
|
||||
panel B.
|
||||
|
||||
## Step 4 — Build the medial graph M(G′) *(panels C, D)*
|
||||
|
||||
The medial graph has **one vertex per edge of G′** (24 of them) and joins two
|
||||
edge-medials iff the edges are consecutive around a common face. A 4-colouring
|
||||
of the triangulation = a proper **3-colouring of M(G′)**. The adjacency (each
|
||||
medial `m(a,b)` listed with its neighbours):
|
||||
|
||||
```
|
||||
m(0,1): (0,2)(0,4)(1,2)(1,4) m(3,7): (0,3)(0,7)(3,8)(7,8)
|
||||
m(0,2): (0,1)(0,5)(1,2)(2,5) m(3,8): (3,7)(3,9)(7,8)(8,9)
|
||||
m(0,3): (0,7)(0,9)(3,7)(3,9) m(3,9): (0,3)(0,9)(3,8)(8,9)
|
||||
m(0,4): (0,1)(0,9)(1,4)(4,9) m(4,5): (1,4)(1,5)(4,8)(5,8)
|
||||
m(0,5): (0,2)(0,6)(2,5)(5,6) m(4,8): (4,5)(4,9)(5,8)(8,9)
|
||||
m(0,6): (0,5)(0,7)(5,6)(6,7) m(4,9): (0,4)(0,9)(4,8)(8,9)
|
||||
m(0,7): (0,3)(0,6)(3,7)(6,7) m(5,6): (0,5)(0,6)(5,8)(6,8)
|
||||
m(0,9): (0,3)(0,4)(3,9)(4,9) m(5,8): (4,5)(4,8)(5,6)(6,8)
|
||||
m(1,2): (0,1)(0,2)(1,5)(2,5) m(6,7): (0,6)(0,7)(6,8)(7,8)
|
||||
m(1,4): (0,1)(0,4)(1,5)(4,5) m(6,8): (5,6)(5,8)(6,7)(7,8)
|
||||
m(1,5): (1,2)(1,4)(2,5)(4,5) m(7,8): (3,7)(3,8)(6,7)(6,8)
|
||||
m(2,5): (0,2)(0,5)(1,2)(1,5) m(8,9): (3,8)(3,9)(4,8)(4,9)
|
||||
```
|
||||
|
||||
## Step 5 — Canonical colouring (no 4CT): seam = 3, annuli alternate, root by DFS *(panel C)*
|
||||
|
||||
The canonical colouring is assembled from three deterministic ingredients plus
|
||||
the two control knobs (phase, DFS order):
|
||||
|
||||
1. **Every level-edge medial → colour 3 (=2).** The even seam `3-9-4-5-6-7`
|
||||
becomes **monochromatic 3**:
|
||||
`m(3,9)=m(4,9)=m(4,5)=m(5,6)=m(6,7)=m(3,7)=2`.
|
||||
2. **Each non-root annulus alternates {0,1} with a phase bit.** Here there is one
|
||||
non-root annulus — the hub spokes between levels 1 and 2:
|
||||
`[(8,9),(3,8),(7,8),(6,8),(5,8),(4,8)]` (length 6). With **phase 0** it is
|
||||
coloured `0,1,0,1,0,1`:
|
||||
`m(8,9)=0, m(3,8)=1, m(7,8)=0, m(6,8)=1, m(5,8)=0, m(4,8)=1`.
|
||||
3. **The root region** — the level-0↔1 spokes plus the three outer-triangle
|
||||
medials `m(0,1),m(0,2),m(1,2)` — is solved by a small DFS using colour
|
||||
priority **`(1,0,2)`**.
|
||||
|
||||
The resulting proper colouring of M(G′):
|
||||
|
||||
```
|
||||
m(0,1)=2 m(0,2)=1 m(0,3)=1 m(0,4)=1 m(0,5)=0 m(0,6)=1 m(0,7)=0 m(0,9)=0
|
||||
m(1,2)=0 m(1,4)=0 m(1,5)=1 m(2,5)=2 m(3,7)=2 m(3,8)=1 m(3,9)=2 m(4,5)=2
|
||||
m(4,8)=1 m(4,9)=2 m(5,6)=2 m(5,8)=0 m(6,7)=2 m(6,8)=1 m(7,8)=0 m(8,9)=0
|
||||
```
|
||||
|
||||
This is the "no-4CT" colouring of the **evened** graph — proper because the seam
|
||||
is even (a monochromatic-3 cycle around even-length annuli is consistent). The
|
||||
only thing standing between it and a colouring of the *original* G is the
|
||||
diamond.
|
||||
|
||||
## Step 6 — Kempe switch, then collapse the diamond *(panel D)*
|
||||
|
||||
To remove `w=9` we restore the diagonal `(3,4)` and recolour. The **degree-4
|
||||
removal condition** on the quad `3-0-4-8` reads: the opposite-corner medial pairs
|
||||
`(m_{30}, m_{04})` and `(m_{48}, m_{83})` must each be *distinct*, using ≤2
|
||||
colours total; the restored edge then takes the third.
|
||||
|
||||
Read the four quad medials off the canonical colouring:
|
||||
```
|
||||
m(0,3)=1 m(0,4)=1 m(4,8)=1 m(3,8)=1 ALL EQUAL
|
||||
```
|
||||
The first pair `(m_{30},m_{04}) = (1,1)` is **not** distinct → `diamond_condition`
|
||||
returns `None`. **This is the obstruction** the bare canonical colouring hits
|
||||
(haloed in panel C). It is *not* non-existence — a removable colouring exists; we
|
||||
just have to reach one by a Kempe switch.
|
||||
|
||||
**The switch.** The bounded search picks the **`{1,2}`-Kempe component through
|
||||
`m(0,3)`**:
|
||||
```
|
||||
component = { m(0,3), m(3,7), m(3,8), m(3,9) } (pair {1,2})
|
||||
```
|
||||
Swapping colours `1↔2` on this component flips `m(0,3): 1→2` and `m(3,8): 1→2`
|
||||
(the other two are already in `{1,2}` and toggle within the class). The quad
|
||||
medials become:
|
||||
```
|
||||
m(0,3)=2 m(0,4)=1 m(4,8)=1 m(3,8)=2
|
||||
```
|
||||
Now `(m_{30},m_{04}) = (2,1)` distinct ✓ and `(m_{48},m_{83}) = (1,2)` distinct ✓,
|
||||
two colours `{1,2}` used → `diamond_condition` returns the **third colour 0**.
|
||||
|
||||
**Collapse.** Delete `w`, restore edge `(3,4)`, and colour the restored medial
|
||||
`m(3,4) = 0` (the orange square in panel D). The result is verified to be a
|
||||
**proper 3-colouring of M(G)** on exactly the original 9 vertices — i.e. a
|
||||
Tait/4CT colouring of the original triangulation, obtained with no appeal to the
|
||||
4CT.
|
||||
|
||||
---
|
||||
|
||||
## Reproduce
|
||||
|
||||
```bash
|
||||
python3 dump_walkthrough.py # prints every step's data verbatim
|
||||
../../../.venv/bin/python draw_walkthrough.py # the 4-panel figure (repo venv: numpy+matplotlib)
|
||||
```
|
||||
|
||||
The reduction here genuinely exercises a Kempe switch. For larger graphs the
|
||||
same six steps run with more diamonds (one per odd seam, swept over all sites)
|
||||
and more phase/colour-order choices; the open difficulty is purely whether some
|
||||
(site, phase) choice lets every diamond's quad become reducible simultaneously
|
||||
— see `even_program_findings.md`.
|
||||
|
After Width: | Height: | Size: 172 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 156 KiB |
@@ -0,0 +1,17 @@
|
||||
"""Compatibility wrapper for the medial tire generator now kept in ../lib."""
|
||||
|
||||
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.full_medial_tire_generator import * # noqa: F401,F403
|
||||
from lib.full_medial_tire_generator import main
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
After Width: | Height: | Size: 469 KiB |
@@ -0,0 +1,69 @@
|
||||
# Atlas of full medial tire graphs with |A(T)| = 9
|
||||
|
||||
This note collects every full medial tire graph whose annular cycle `A(T)` has
|
||||
nine vertices, generated exhaustively from the structural properties in
|
||||
Definitions/Remarks 3.1–3.9 of `paper.tex`.
|
||||
|
||||
## What is being enumerated
|
||||
|
||||
A full medial tire graph of size `n = |A(T)|` is determined by:
|
||||
|
||||
- a tooth word in `{U, D}^n` — one up (`U`) or down (`D`) tooth per annular
|
||||
edge (Def. 3.4), with **at least three up teeth** (Rem. 3.6);
|
||||
- a **non-crossing matching** of the down edges into *bites* — pairs of down
|
||||
teeth sharing an apex (Rem. 3.5, Def. 3.7); unmatched down teeth are
|
||||
singletons. The two annular edges of a bite must be **non-incident**
|
||||
(Def. 3.7): they share no annular vertex, so cyclically adjacent edges
|
||||
cannot pair;
|
||||
- subject to the **bite-face condition** (Rem. 3.8): in `B(T) = A(T) + bite
|
||||
apexes`, every interior non-tooth face must contain `0` or `≥ 3`
|
||||
down-tooth apexes in its interior (equivalently, no face holds exactly one
|
||||
or two singleton down teeth).
|
||||
|
||||
Graphs are identified up to the dihedral symmetry of the annular cycle
|
||||
(rotations and reflections), since these give isomorphic plane graphs.
|
||||
|
||||
## The atlas
|
||||
|
||||

|
||||
|
||||
High-resolution vector copy: [`full_medial_tire_n9.pdf`](full_medial_tire_n9.pdf).
|
||||
Full textual index: [`full_medial_tire_n9_index.txt`](full_medial_tire_n9_index.txt).
|
||||
|
||||
In each diagram the thick black ring is `A(T)`; **blue** outer apexes are up
|
||||
teeth, **red** inner apexes are singleton down teeth, and a **dark-red** inner
|
||||
apex with four spokes is a bite (its two paired annular edges). The label under
|
||||
each diagram is the tooth word and the bite pairs (edge indices).
|
||||
|
||||
## Counts
|
||||
|
||||
There are **81** classes for `n = 9` (cf. `3:1, 4:1, 5:2, 6:6, 7:13, 8:36,
|
||||
9:81` for `n = 3..9`, with the non-incidence stipulation in force). Breakdown
|
||||
of the 81 classes:
|
||||
|
||||
| down teeth | classes | | bites | classes | | up teeth | classes |
|
||||
|-----------:|--------:|---|------:|--------:|---|---------:|--------:|
|
||||
| 0 | 1 | | 0 | 35 | | 3 | 23 |
|
||||
| 2 | 3 | | 1 | 35 | | 4 | 29 |
|
||||
| 3 | 7 | | 2 | 8 | | 5 | 18 |
|
||||
| 4 | 18 | | 3 | 3 | | 6 | 7 |
|
||||
| 5 | 29 | | | | | 7 | 3 |
|
||||
| 6 | 23 | | | | | 9 | 1 |
|
||||
|
||||
46 of the 81 classes contain at least one bite. (Every singleton down tooth
|
||||
must sit in a face holding `≥ 3` of them, so e.g. words with exactly one or two
|
||||
down teeth only survive when those down teeth are paired into a bite — and now
|
||||
only when the paired edges are non-incident, which is why the counts fall
|
||||
sharply from the unrestricted `n = 9` total of 159.)
|
||||
|
||||
## Reproduce
|
||||
|
||||
```sh
|
||||
# from this directory, using the repo .venv
|
||||
../../../.venv/bin/python plot_full_medial_tire_n9.py # figure + index
|
||||
python full_medial_tire_generator.py --min-n 9 --max-n 9 --dedup --show 5
|
||||
```
|
||||
|
||||
`full_medial_tire_generator.py` is the generator (`generate(n, dedup=True)`
|
||||
yields `FullMedialTireGraph` objects); `plot_full_medial_tire_n9.py` draws the
|
||||
atlas.
|
||||
@@ -0,0 +1,81 @@
|
||||
0 word=UUUUUUUUU up=9 down=0 bites=-
|
||||
1 word=UUUUUUDUD up=7 down=2 bites=(6,8)
|
||||
2 word=UUUUUUDDD up=6 down=3 bites=-
|
||||
3 word=UUUUUDUUD up=7 down=2 bites=(5,8)
|
||||
4 word=UUUUUDUDD up=6 down=3 bites=-
|
||||
5 word=UUUUUDDDD up=5 down=4 bites=-
|
||||
6 word=UUUUDUUUD up=7 down=2 bites=(4,8)
|
||||
7 word=UUUUDUUDD up=6 down=3 bites=-
|
||||
8 word=UUUUDUDUD up=6 down=3 bites=-
|
||||
9 word=UUUUDUDDD up=5 down=4 bites=-
|
||||
10 word=UUUUDDUDD up=5 down=4 bites=-
|
||||
11 word=UUUUDDUDD up=5 down=4 bites=(4,8),(5,7)
|
||||
12 word=UUUUDDDDD up=4 down=5 bites=-
|
||||
13 word=UUUUDDDDD up=4 down=5 bites=(4,8)
|
||||
14 word=UUUDUUUDD up=6 down=3 bites=-
|
||||
15 word=UUUDUUDUD up=6 down=3 bites=-
|
||||
16 word=UUUDUUDDD up=5 down=4 bites=-
|
||||
17 word=UUUDUDUDD up=5 down=4 bites=-
|
||||
18 word=UUUDUDUDD up=5 down=4 bites=(3,8),(5,7)
|
||||
19 word=UUUDUDDUD up=5 down=4 bites=-
|
||||
20 word=UUUDUDDUD up=5 down=4 bites=(3,5),(6,8)
|
||||
21 word=UUUDUDDDD up=4 down=5 bites=-
|
||||
22 word=UUUDUDDDD up=4 down=5 bites=(3,5)
|
||||
23 word=UUUDUDDDD up=4 down=5 bites=(3,8)
|
||||
24 word=UUUDDUUDD up=5 down=4 bites=-
|
||||
25 word=UUUDDUUDD up=5 down=4 bites=(3,8),(4,7)
|
||||
26 word=UUUDDUDDD up=4 down=5 bites=-
|
||||
27 word=UUUDDUDDD up=4 down=5 bites=(4,6)
|
||||
28 word=UUUDDUDDD up=4 down=5 bites=(3,8)
|
||||
29 word=UUUDDDDDD up=3 down=6 bites=-
|
||||
30 word=UUUDDDDDD up=3 down=6 bites=(3,8)
|
||||
31 word=UUDUUDUUD up=6 down=3 bites=-
|
||||
32 word=UUDUUDUDD up=5 down=4 bites=-
|
||||
33 word=UUDUUDUDD up=5 down=4 bites=(2,8),(5,7)
|
||||
34 word=UUDUUDDDD up=4 down=5 bites=-
|
||||
35 word=UUDUUDDDD up=4 down=5 bites=(2,5)
|
||||
36 word=UUDUDUUDD up=5 down=4 bites=-
|
||||
37 word=UUDUDUUDD up=5 down=4 bites=(2,8),(4,7)
|
||||
38 word=UUDUDUDUD up=5 down=4 bites=-
|
||||
39 word=UUDUDUDUD up=5 down=4 bites=(2,4),(6,8)
|
||||
40 word=UUDUDUDUD up=5 down=4 bites=(2,8),(4,6)
|
||||
41 word=UUDUDUDDD up=4 down=5 bites=-
|
||||
42 word=UUDUDUDDD up=4 down=5 bites=(4,6)
|
||||
43 word=UUDUDUDDD up=4 down=5 bites=(2,4)
|
||||
44 word=UUDUDUDDD up=4 down=5 bites=(2,8)
|
||||
45 word=UUDUDDUDD up=4 down=5 bites=-
|
||||
46 word=UUDUDDUDD up=4 down=5 bites=(5,7)
|
||||
47 word=UUDUDDUDD up=4 down=5 bites=(2,4)
|
||||
48 word=UUDUDDUDD up=4 down=5 bites=(2,8)
|
||||
49 word=UUDUDDDUD up=4 down=5 bites=-
|
||||
50 word=UUDUDDDUD up=4 down=5 bites=(6,8)
|
||||
51 word=UUDUDDDUD up=4 down=5 bites=(2,8)
|
||||
52 word=UUDUDDDDD up=3 down=6 bites=-
|
||||
53 word=UUDUDDDDD up=3 down=6 bites=(2,4)
|
||||
54 word=UUDUDDDDD up=3 down=6 bites=(2,8)
|
||||
55 word=UUDDUUDDD up=4 down=5 bites=-
|
||||
56 word=UUDDUUDDD up=4 down=5 bites=(3,6)
|
||||
57 word=UUDDUDUDD up=4 down=5 bites=-
|
||||
58 word=UUDDUDUDD up=4 down=5 bites=(5,7)
|
||||
59 word=UUDDUDUDD up=4 down=5 bites=(2,8)
|
||||
60 word=UUDDUDDDD up=3 down=6 bites=-
|
||||
61 word=UUDDUDDDD up=3 down=6 bites=(3,5)
|
||||
62 word=UUDDUDDDD up=3 down=6 bites=(2,8)
|
||||
63 word=UUDDDUDDD up=3 down=6 bites=-
|
||||
64 word=UUDDDUDDD up=3 down=6 bites=(4,6)
|
||||
65 word=UUDDDUDDD up=3 down=6 bites=(2,8)
|
||||
66 word=UUDDDUDDD up=3 down=6 bites=(2,8),(3,7),(4,6)
|
||||
67 word=UDUDUDUDD up=4 down=5 bites=-
|
||||
68 word=UDUDUDUDD up=4 down=5 bites=(5,7)
|
||||
69 word=UDUDUDUDD up=4 down=5 bites=(3,5)
|
||||
70 word=UDUDUDDDD up=3 down=6 bites=-
|
||||
71 word=UDUDUDDDD up=3 down=6 bites=(3,5)
|
||||
72 word=UDUDUDDDD up=3 down=6 bites=(1,3)
|
||||
73 word=UDUDDUDDD up=3 down=6 bites=-
|
||||
74 word=UDUDDUDDD up=3 down=6 bites=(4,6)
|
||||
75 word=UDUDDUDDD up=3 down=6 bites=(1,3)
|
||||
76 word=UDUDDUDDD up=3 down=6 bites=(1,8)
|
||||
77 word=UDUDDUDDD up=3 down=6 bites=(1,8),(3,7),(4,6)
|
||||
78 word=UDDUDDUDD up=3 down=6 bites=-
|
||||
79 word=UDDUDDUDD up=3 down=6 bites=(5,7)
|
||||
80 word=UDDUDDUDD up=3 down=6 bites=(1,8),(2,4),(5,7)
|
||||
@@ -0,0 +1,82 @@
|
||||
# Full vs Reduced Medial Tire Findings
|
||||
|
||||
Question: do Definition 3.1 (full medial tire graph) and Definition 3.2
|
||||
(reduced medial tire graph) differ?
|
||||
|
||||
## Experiment
|
||||
|
||||
Script:
|
||||
|
||||
```bash
|
||||
python3 papers/medial_tire_decompositions_of_plane_triangulations/experiments/compare_full_reduced_medial_tires.py
|
||||
```
|
||||
|
||||
The script compares two models.
|
||||
|
||||
- Ambient tread-face model: medial edges are contributed by annular
|
||||
triangular faces of the tire tread inside the ambient triangulation.
|
||||
- Standalone tire-with-boundary-faces model: the outer and inner
|
||||
boundary walks are also treated as faces, as in the older drawing
|
||||
script.
|
||||
|
||||
## Random Sweep
|
||||
|
||||
Command:
|
||||
|
||||
```bash
|
||||
python3 papers/medial_tire_decompositions_of_plane_triangulations/experiments/compare_full_reduced_medial_tires.py
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```text
|
||||
ambient tread-face model
|
||||
cases checked: 7200
|
||||
cases where full != reduced: 0
|
||||
removed-edge reasons: {}
|
||||
|
||||
standalone tire-with-boundary-faces model
|
||||
cases checked: 7200
|
||||
cases where full != reduced: 7200
|
||||
removed-edge reasons: {'inner_boundary': 39600, 'outer_boundary': 39600}
|
||||
first difference:
|
||||
m=3 k=3 requested_chords=0 seed=0
|
||||
path=IOOOII chords=[]
|
||||
full_edges=24 reduced_edges=18
|
||||
removed examples=[((0, 1), (0, 2)), ((0, 1), (1, 2)), ((0, 2), (1, 2)), ((3, 4), (3, 5)), ((3, 4), (4, 5))]
|
||||
```
|
||||
|
||||
## Exhaustive Small Sweep
|
||||
|
||||
Command:
|
||||
|
||||
```bash
|
||||
python3 papers/medial_tire_decompositions_of_plane_triangulations/experiments/compare_full_reduced_medial_tires.py --exhaustive --max-cycle 5 --max-chords 2
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```text
|
||||
exhaustive ambient tread-face model
|
||||
cases checked: 5578
|
||||
cases where full != reduced: 0
|
||||
|
||||
exhaustive standalone tire-with-boundary-faces model
|
||||
cases checked: 5578
|
||||
cases where full != reduced: 5578
|
||||
first difference:
|
||||
m=3 k=3 chords=() path=OOOIII
|
||||
full_edges=24 reduced_edges=18
|
||||
removed examples=[((0, 1), (0, 2)), ((0, 1), (1, 2)), ((0, 2), (1, 2)), ((3, 4), (3, 5)), ((3, 4), (4, 5))]
|
||||
```
|
||||
|
||||
## Interpretation
|
||||
|
||||
For the intended ambient-triangulation definition, the experiments
|
||||
support the suspicion that Definition 3.1 and Definition 3.2 coincide:
|
||||
same-boundary medial edges do not arise from annular triangular tread
|
||||
faces, and inner chords are not incident to tread triangles.
|
||||
|
||||
They differ only in the standalone tire-with-boundary-faces model,
|
||||
where the artificial outer and inner boundary faces create medial edges
|
||||
between consecutive boundary edges.
|
||||