Files
math-research/quad_sequence_coloring_check.py
T
didericis 41bbe40c32 Frame the 4-coloring motivation and add an online greedy check
Adds a Motivation section to paper.tex explaining that the
quadrilateral sequencing is intended to support an inductive 4-coloring
of the underlying maximal planar graph, with ring completion as the
suspected obstacle.

Adds commentary.tex recording (a) why a pure pigeonhole argument is
unlikely to close the conjecture, (b) the observation that under any
strictly local online rule every G'-edge constraint is enforced when
its second endpoint is colored (so ring completions cannot fail at the
moment they fire), and (c) the empirical finding that pure greedy
fails at non-ring-completion moves on every 3-connected triangulation
of order 5-7.

Adds quad_sequence_coloring_check.py, an enumeration check over small
triangulations via Sage's planar_graphs that runs greedy online
4-coloring under the canonical sequence and classifies failures.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 03:05:44 -04:00

156 lines
6.3 KiB
Python

"""Empirical check: does pure greedy online 4-coloring succeed under the canonical quadrilateral sequencing?
The user's conjecture-of-the-day: for the canonical sequence, every non-ring-completion
move (anchor drop, level add, join) admits a local 4-coloring choice, and ring completions
introduce no new vertices, so the only question is whether the existing coloring agrees
on the ring-completion quad's edges.
Under proper greedy online coloring this latter check is automatic: every G'-edge
constraint is enforced as the second endpoint is colored. So the empirical question
becomes: does pure greedy (smallest color avoiding already-colored G'-neighbors) ever
get stuck at a non-ring-completion move with all four colors forbidden?
We run greedy in a canonical vertex order (depth, then vertex id) and classify every
failure as one of:
- 'no_color_available': a new vertex saw all 4 colors among colored G'-neighbors
- 'ring_completion_monochromatic': a ring completion's quad has a monochromatic edge
- 'success': global proper 4-coloring of G'.
"""
from typing import Any
from sage.all import Graph, graphs # type: ignore[attr-defined] # pylint: disable=no-name-in-module
from lib.colored_graphs import canonize_and_save_graph
from plane_depth_sequencing import (
quadrilateral_sequencing,
_quad_vertices,
)
def greedy_4_color_under_sequence(
g_prime: Graph,
sequence: list[frozenset[frozenset[Any]]],
depth_labelling: dict[Any, int],
) -> tuple[dict[Any, int], str, int | None]:
"""Run greedy online 4-coloring under the canonical sequence.
Returns (partial_coloring, status, failing_step).
status is one of:
- 'success'
- 'no_color_available' (failing_step is the sequence index)
- 'ring_completion_monochromatic' (failing_step is the sequence index)
"""
coloring: dict[Any, int] = {}
slice_vertices: set[Any] = set()
adj: dict[Any, set[Any]] = {v: set(g_prime.neighbors(v)) for v in g_prime.vertices()}
for step, quad in enumerate(sequence):
quad_v = _quad_vertices(quad)
new_vertices = quad_v - slice_vertices
if not new_vertices:
# Ring completion: verify the existing coloring is proper on the quad's edges.
quad_v_list = list(quad_v)
for i in range(len(quad_v_list)):
for j in range(i + 1, len(quad_v_list)):
u, v = quad_v_list[i], quad_v_list[j]
if v in adj[u] and coloring[u] == coloring[v]:
return coloring, 'ring_completion_monochromatic', step
continue
# Color new vertices in a canonical order (depth, then vertex id).
for v in sorted(new_vertices, key=lambda x: (depth_labelling[x], x)):
used = {coloring[u] for u in adj[v] if u in coloring}
avail = [c for c in (0, 1, 2, 3) if c not in used]
if not avail:
return coloring, 'no_color_available', step
coloring[v] = min(avail)
slice_vertices.add(v)
return coloring, 'success', None
def _outer_cycle_from_embedding(g: Graph) -> list[Any]:
"""Pick a deterministic outer cycle: the first face Sage returns from the embedding."""
g.is_planar(set_embedding=True)
embedding = g.get_embedding()
faces = g.faces(embedding)
return [u for u, v in faces[0]]
def check_graph(g: Graph) -> dict[str, Any]:
"""Run sequencing + greedy coloring on a single graph. Returns a result dict."""
outer_cycle = _outer_cycle_from_embedding(g)
try:
seq_result = quadrilateral_sequencing(g, outer_cycle)
except Exception as exc: # pylint: disable=broad-except
return {
'graph6': g.graph6_string(),
'status': 'sequencing_error',
'error': str(exc),
}
coloring, status, failing_step = greedy_4_color_under_sequence(
seq_result['deep_embedding'],
seq_result['sequence'],
seq_result['depth_labelling'],
)
return {
'graph6': g.graph6_string(),
'status': status,
'failing_step': failing_step,
'coloring_size': len(coloring),
'num_quads': len(seq_result['quadrilaterals']),
'move_codes': seq_result['move_codes'],
}
def run_enumeration(min_order: int, max_order: int) -> dict[int, dict[str, Any]]:
"""Iterate over all 3-connected triangulations of order in [min_order, max_order]."""
summary: dict[int, dict[str, Any]] = {}
for n in range(min_order, max_order + 1):
total = 0
by_status: dict[str, int] = {}
failures: list[dict[str, Any]] = []
for g in graphs.planar_graphs(n, minimum_connectivity=3, maximum_face_size=3):
total += 1
result = check_graph(g)
status = result['status']
by_status[status] = by_status.get(status, 0) + 1
if status != 'success':
failures.append(result)
if total % 200 == 0:
print(f" n={n}: checked {total}, statuses so far: {by_status}")
summary[n] = {'total': total, 'by_status': by_status, 'failures': failures}
print(f"n={n}: total={total}, by_status={by_status}")
for f in failures[:5]:
print(f" failure {f['graph6']}: {f['status']} at step {f['failing_step']}")
if len(failures) > 5:
print(f" ... ({len(failures) - 5} more failures)")
return summary
if __name__ == "__main__":
import sys
min_order = int(sys.argv[1]) if len(sys.argv) > 1 else 4
max_order = int(sys.argv[2]) if len(sys.argv) > 2 else 9
summary = run_enumeration(min_order, max_order)
print()
print("=" * 60)
print("Final summary:")
for n, s in summary.items():
print(f" n={n}: total={s['total']}, by_status={s['by_status']}")
# Save up to first 10 failure graphs per order for inspection.
saved = 0
for n, s in summary.items():
for f in s['failures'][:10]:
try:
g = Graph(f['graph6'])
canonical, graph_dir = canonize_and_save_graph(g)
print(f" saved n={n} {f['graph6']} ({f['status']}) -> {graph_dir}")
saved += 1
except Exception as exc: # pylint: disable=broad-except
print(f" failed to save {f['graph6']}: {exc}")
print(f"Saved {saved} failure graphs total.")