41bbe40c32
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>
156 lines
6.3 KiB
Python
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.")
|