Add planar counterexample figure
This commit is contained in:
+132
@@ -0,0 +1,132 @@
|
|||||||
|
"""Draw the 8-vertex counterexample to the universal-source form.
|
||||||
|
|
||||||
|
The figure highlights the source vertex 7, the two BFS levels, and the
|
||||||
|
level cycle (3,4,5,8) that forces all four colours for that source.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import networkx as nx
|
||||||
|
from matplotlib.lines import Line2D
|
||||||
|
|
||||||
|
|
||||||
|
OUT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
|
||||||
|
def build_graph() -> nx.Graph:
|
||||||
|
g = nx.Graph()
|
||||||
|
g.add_edges_from(
|
||||||
|
[
|
||||||
|
(1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7),
|
||||||
|
(2, 3), (2, 6), (2, 7),
|
||||||
|
(3, 4), (3, 5), (3, 6), (3, 8),
|
||||||
|
(4, 5),
|
||||||
|
(5, 6), (5, 8),
|
||||||
|
(6, 7), (6, 8),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return g
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
g = build_graph()
|
||||||
|
levels = nx.single_source_shortest_path_length(g, 7)
|
||||||
|
is_planar, embedding = nx.check_planarity(g)
|
||||||
|
if not is_planar:
|
||||||
|
raise RuntimeError("counterexample graph should be planar")
|
||||||
|
pos = nx.planar_layout(g, scale=3.4)
|
||||||
|
|
||||||
|
fig, ax = plt.subplots(figsize=(8.6, 7.2))
|
||||||
|
|
||||||
|
nx.draw_networkx_edges(g, pos, ax=ax, edge_color="#cbd5e1", width=1.4)
|
||||||
|
|
||||||
|
cycle_edges = [(3, 4), (4, 5), (5, 8), (8, 3)]
|
||||||
|
nx.draw_networkx_edges(
|
||||||
|
g,
|
||||||
|
pos,
|
||||||
|
ax=ax,
|
||||||
|
edgelist=cycle_edges,
|
||||||
|
edge_color="#dc2626",
|
||||||
|
width=2.8,
|
||||||
|
)
|
||||||
|
|
||||||
|
level_colors = {0: "#0f172a", 1: "#475569", 2: "#94a3b8"}
|
||||||
|
for v, (x, y) in pos.items():
|
||||||
|
lev = levels[v]
|
||||||
|
ax.scatter(
|
||||||
|
[x],
|
||||||
|
[y],
|
||||||
|
s=600 if v == 7 else 500,
|
||||||
|
color=level_colors[lev],
|
||||||
|
edgecolors="black",
|
||||||
|
linewidths=1.1,
|
||||||
|
zorder=3,
|
||||||
|
)
|
||||||
|
ax.text(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
f"{v}\n$\\ell={lev}$",
|
||||||
|
ha="center",
|
||||||
|
va="center",
|
||||||
|
color="white",
|
||||||
|
fontsize=10,
|
||||||
|
fontweight="bold",
|
||||||
|
zorder=4,
|
||||||
|
)
|
||||||
|
|
||||||
|
legend = [
|
||||||
|
Line2D(
|
||||||
|
[0],
|
||||||
|
[0],
|
||||||
|
marker="o",
|
||||||
|
color="w",
|
||||||
|
label=r"source $S=\{7\}$",
|
||||||
|
markerfacecolor=level_colors[0],
|
||||||
|
markeredgecolor="black",
|
||||||
|
markersize=12,
|
||||||
|
),
|
||||||
|
Line2D(
|
||||||
|
[0],
|
||||||
|
[0],
|
||||||
|
marker="o",
|
||||||
|
color="w",
|
||||||
|
label=r"level $L_1$",
|
||||||
|
markerfacecolor=level_colors[1],
|
||||||
|
markeredgecolor="black",
|
||||||
|
markersize=12,
|
||||||
|
),
|
||||||
|
Line2D(
|
||||||
|
[0],
|
||||||
|
[0],
|
||||||
|
marker="o",
|
||||||
|
color="w",
|
||||||
|
label=r"level $L_2$",
|
||||||
|
markerfacecolor=level_colors[2],
|
||||||
|
markeredgecolor="black",
|
||||||
|
markersize=12,
|
||||||
|
),
|
||||||
|
Line2D([0], [0], color="#dc2626", lw=2.8, label=r"problem cycle $(3,4,5,8)$"),
|
||||||
|
]
|
||||||
|
|
||||||
|
ax.legend(handles=legend, loc="lower center", ncol=2, framealpha=0.95)
|
||||||
|
ax.set_title(
|
||||||
|
"Counterexample to the universal-source form",
|
||||||
|
fontsize=14,
|
||||||
|
pad=18,
|
||||||
|
)
|
||||||
|
ax.set_aspect("equal")
|
||||||
|
ax.axis("off")
|
||||||
|
fig.tight_layout(rect=[0, 0.05, 1, 1])
|
||||||
|
|
||||||
|
out = os.path.join(OUT_DIR, "fig_universal_level_cycle_counterexample.png")
|
||||||
|
fig.savefig(out, dpi=180, bbox_inches="tight")
|
||||||
|
plt.close(fig)
|
||||||
|
print(f"wrote {out}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
"""Generate and test candidate graph families for the level-cycle conjecture.
|
||||||
|
|
||||||
|
This script builds a few rigid maximal planar families and then runs the
|
||||||
|
existing level-cycle checker on them. It is meant as a small Sage-side
|
||||||
|
driver for probing likely counterexample sources before moving on to larger
|
||||||
|
families.
|
||||||
|
|
||||||
|
Available families:
|
||||||
|
stacked-ring concentric triangular rings like the paper's G_1
|
||||||
|
face-stack a chain of stacked triangulations built by repeatedly
|
||||||
|
stacking a vertex into a triangular face
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
sage -python experiments/generate_candidate_families.py stacked-ring 1 6
|
||||||
|
sage -python experiments/generate_candidate_families.py face-stack 4 12 --full
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
from sage.all import Graph # type: ignore[attr-defined] # pylint: disable=no-name-in-module
|
||||||
|
|
||||||
|
from check_level_cycle_three_color import level_sources, test_graph
|
||||||
|
|
||||||
|
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def build_stacked_ring(levels: int) -> Graph:
|
||||||
|
"""Return the concentric triangular-ring family used in the paper figure."""
|
||||||
|
if levels < 1:
|
||||||
|
raise ValueError("stacked-ring requires at least one ring")
|
||||||
|
|
||||||
|
g = Graph()
|
||||||
|
g.add_vertex(0)
|
||||||
|
next_vertex = 1
|
||||||
|
|
||||||
|
ring1 = [next_vertex + i for i in range(3)]
|
||||||
|
next_vertex += 3
|
||||||
|
g.add_vertices(ring1)
|
||||||
|
g.add_edges([(0, v) for v in ring1])
|
||||||
|
g.add_edges([(ring1[i], ring1[(i + 1) % 3]) for i in range(3)])
|
||||||
|
|
||||||
|
inner = ring1
|
||||||
|
for _ in range(1, levels):
|
||||||
|
outer = [next_vertex + i for i in range(3)]
|
||||||
|
next_vertex += 3
|
||||||
|
g.add_vertices(outer)
|
||||||
|
g.add_edges([(outer[i], outer[(i + 1) % 3]) for i in range(3)])
|
||||||
|
g.add_edges([(inner[i], outer[i]) for i in range(3)])
|
||||||
|
g.add_edges([(inner[i], outer[(i + 1) % 3]) for i in range(3)])
|
||||||
|
inner = outer
|
||||||
|
|
||||||
|
expected_edges = 3 * g.n_vertices() - 6
|
||||||
|
if g.num_edges() != expected_edges:
|
||||||
|
raise AssertionError(
|
||||||
|
f"stacked-ring is not maximal planar: |V|={g.n_vertices()}, "
|
||||||
|
f"|E|={g.num_edges()}, expected {expected_edges}"
|
||||||
|
)
|
||||||
|
return g
|
||||||
|
|
||||||
|
|
||||||
|
def build_face_stack(steps: int) -> Graph:
|
||||||
|
"""Return a stacked triangulation built by repeatedly subdividing one face."""
|
||||||
|
if steps < 1:
|
||||||
|
raise ValueError("face-stack requires at least one step")
|
||||||
|
|
||||||
|
g = Graph()
|
||||||
|
g.add_vertices([0, 1, 2, 3])
|
||||||
|
g.add_edges([(0, 1), (1, 2), (2, 0)])
|
||||||
|
g.add_edges([(3, 0), (3, 1), (3, 2)])
|
||||||
|
|
||||||
|
active_face = (0, 1, 3)
|
||||||
|
next_vertex = 4
|
||||||
|
for _ in range(steps - 1):
|
||||||
|
v = next_vertex
|
||||||
|
next_vertex += 1
|
||||||
|
a, b, c = active_face
|
||||||
|
g.add_vertex(v)
|
||||||
|
g.add_edges([(v, a), (v, b), (v, c)])
|
||||||
|
active_face = (a, c, v)
|
||||||
|
|
||||||
|
expected_edges = 3 * g.n_vertices() - 6
|
||||||
|
if g.num_edges() != expected_edges:
|
||||||
|
raise AssertionError(
|
||||||
|
f"face-stack is not maximal planar: |V|={g.n_vertices()}, "
|
||||||
|
f"|E|={g.num_edges()}, expected {expected_edges}"
|
||||||
|
)
|
||||||
|
return g
|
||||||
|
|
||||||
|
|
||||||
|
FAMILY_BUILDERS: dict[str, Callable[[int], Graph]] = {
|
||||||
|
"stacked-ring": build_stacked_ring,
|
||||||
|
"face-stack": build_face_stack,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> argparse.Namespace:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("family", choices=sorted(FAMILY_BUILDERS))
|
||||||
|
parser.add_argument("n_min", type=int, nargs="?", default=1)
|
||||||
|
parser.add_argument("n_max", type=int, nargs="?", default=6)
|
||||||
|
parser.add_argument(
|
||||||
|
"--sources",
|
||||||
|
choices=("vertex", "cycle", "all"),
|
||||||
|
default="vertex",
|
||||||
|
help="candidate source family to search",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--quantifier",
|
||||||
|
choices=("exists-source", "all-sources"),
|
||||||
|
default="exists-source",
|
||||||
|
help="whether to test the weakened or stronger conjecture",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--max-cycle-source-size",
|
||||||
|
type=int,
|
||||||
|
default=None,
|
||||||
|
help="optional cap on induced cycle source size",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--max-colorings",
|
||||||
|
type=int,
|
||||||
|
default=None,
|
||||||
|
help="optional cap per graph/source; capped searches report UNKNOWN",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--full",
|
||||||
|
action="store_true",
|
||||||
|
help="continue after failures/unknowns instead of stopping at first",
|
||||||
|
)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
args = parse_args()
|
||||||
|
builder = FAMILY_BUILDERS[args.family]
|
||||||
|
stop_first = not args.full
|
||||||
|
|
||||||
|
total_graphs = 0
|
||||||
|
total_sources = 0
|
||||||
|
unknown = 0
|
||||||
|
|
||||||
|
for n in range(args.n_min, args.n_max + 1):
|
||||||
|
g = builder(n)
|
||||||
|
total_graphs += 1
|
||||||
|
print(
|
||||||
|
f"{args.family} n={n}: "
|
||||||
|
f"|V|={g.n_vertices()} |E|={g.num_edges()} "
|
||||||
|
f"sources={args.sources} quantifier={args.quantifier}"
|
||||||
|
)
|
||||||
|
sources = list(
|
||||||
|
level_sources(g, args.sources, args.max_cycle_source_size)
|
||||||
|
)
|
||||||
|
passed, complete, checked_sources = test_graph(
|
||||||
|
g,
|
||||||
|
sources,
|
||||||
|
args.max_colorings,
|
||||||
|
stop_first,
|
||||||
|
args.quantifier,
|
||||||
|
)
|
||||||
|
total_sources += checked_sources
|
||||||
|
if not complete:
|
||||||
|
unknown += 1
|
||||||
|
if not passed and not args.full:
|
||||||
|
print(f"ABORT: first failure at family={args.family} n={n}")
|
||||||
|
print(
|
||||||
|
f"SUMMARY: checked {total_graphs} graphs and {total_sources} "
|
||||||
|
f"sources; unknown_graphs={unknown}"
|
||||||
|
)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"SUMMARY: checked {total_graphs} graphs and {total_sources} "
|
||||||
|
f"sources; unknown_graphs={unknown}"
|
||||||
|
)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
|
||||||
|
try:
|
||||||
|
raise SystemExit(main())
|
||||||
|
except Exception as exc: # pragma: no cover - surfaced to the shell
|
||||||
|
LOGGER.exception("candidate family generation failed: %s", exc)
|
||||||
|
raise
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 73 KiB |
Binary file not shown.
@@ -1298,6 +1298,15 @@ level source. Then $G$ admits a proper $4$-vertex-colouring with the
|
|||||||
level-cycle three-colour restriction with respect to $S$.
|
level-cycle three-colour restriction with respect to $S$.
|
||||||
\end{conjecture}
|
\end{conjecture}
|
||||||
|
|
||||||
|
\begin{figure}[htbp]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=0.78\textwidth]{fig_universal_level_cycle_counterexample.png}
|
||||||
|
\caption{The $8$-vertex counterexample to the universal-source form.
|
||||||
|
With source $S=\{7\}$, the level cycle $(3,4,5,8)$ lies in $L_2$ and
|
||||||
|
forces all four colours in every proper $4$-vertex-colouring.}
|
||||||
|
\label{fig:universal-level-cycle-counterexample}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
\begin{example}[Counterexample to Conjecture~\ref{conj:false-universal-level-cycle-three-colour}]
|
\begin{example}[Counterexample to Conjecture~\ref{conj:false-universal-level-cycle-three-colour}]
|
||||||
\label{ex:universal-level-cycle-counterexample}
|
\label{ex:universal-level-cycle-counterexample}
|
||||||
Let $G$ be the maximal planar graph on vertex set
|
Let $G$ be the maximal planar graph on vertex set
|
||||||
|
|||||||
Reference in New Issue
Block a user