diff --git a/papers/nested_level_duals/experiments/draw_dual_depth.py b/papers/coloring_nested_tire_graphs/experiments/draw_dual_depth.py similarity index 100% rename from papers/nested_level_duals/experiments/draw_dual_depth.py rename to papers/coloring_nested_tire_graphs/experiments/draw_dual_depth.py diff --git a/papers/coloring_nested_tire_graphs/experiments/tire_graph.py b/papers/coloring_nested_tire_graphs/experiments/tire_graph.py new file mode 100644 index 0000000..7ba691f --- /dev/null +++ b/papers/coloring_nested_tire_graphs/experiments/tire_graph.py @@ -0,0 +1,246 @@ +"""Tire graphs: triangulations of the annular region between an outer +cycle and the outer face of an inner outerplanar graph. + +Definition. + A *tire graph* T = (C_out, O, Tann) consists of: + - an outer cycle C_out of length m, on vertices V_out, + - an outerplanar graph O whose outer-face boundary is a simple cycle + C_in of length k, on vertices V_in (V_in disjoint from V_out), + - a triangulation Tann of the closed annulus with outer boundary + C_out and inner boundary C_in, using ONLY V_out ∪ V_in (no Steiner + points). + As a plane graph: V(T) = V_out ∪ V_in, + E(T) = E(C_out) ∪ E(O) ∪ E(Tann). + +Random generation. + Annular triangulations between C_m and C_k (no interior vertices) are + in bijection with sequences of m "O" moves and k "I" moves, once an + anchor spoke (V_out[0]–V_in[0]) is fixed. We sample uniformly over + the C(m+k, m) such sequences. Inner-outerplanar-graph chords are + sampled by greedily adding non-crossing chords until n_chords are + placed (or no more fit). + +Run: + python3 experiments/tire_graph.py +""" +import math +import os +import random + +import matplotlib.pyplot as plt +import matplotlib.patches as patches + + +def random_tire(m, k, n_chords=0, seed=None): + """Generate a random tire graph with outer cycle length m and inner + outerplanar graph whose outer face cycle has length k.""" + rng = random.Random(seed) + + outer = list(range(m)) # outer-cycle vertex labels + inner = list(range(m, m + k)) # inner-cycle vertex labels + + edges = set() + + # outer cycle + for i in range(m): + edges.add(frozenset({outer[i], outer[(i + 1) % m]})) + + # inner cycle (= outer face of inner outerplanar graph) + for j in range(k): + edges.add(frozenset({inner[j], inner[(j + 1) % k]})) + + # random non-crossing chords inside the inner cycle + inner_chords = set() + candidates = [] + for a in range(k): + for b in range(a + 2, k): + if not (a == 0 and b == k - 1): # skip the cycle edge + candidates.append((a, b)) + rng.shuffle(candidates) + for (a, b) in candidates: + if len(inner_chords) >= n_chords: + break + ok = True + for (a2, b2) in inner_chords: + # chords (a,b), (a2,b2) cross iff one strictly separates the + # other on the cycle + if (a < a2 < b < b2) or (a2 < a < b2 < b): + ok = False + break + if ok: + inner_chords.add((a, b)) + edges.add(frozenset({inner[a], inner[b]})) + + # anchor spoke + edges.add(frozenset({outer[0], inner[0]})) + + # annular triangulation via random lattice path + 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(frozenset({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(frozenset({outer[i % m], inner[(j + 1) % k]})) + j += 1 + + return { + 'm': m, 'k': k, 'n_chords': len(inner_chords), + 'outer': outer, 'inner': inner, + 'edges': [tuple(sorted(e)) for e in edges], + 'triangles': triangles, + 'inner_chords': sorted(inner_chords), + 'lattice_path': ''.join(moves), + 'seed': seed, + } + + +def planar_positions(tire, R_out=1.0, R_in=0.45): + """Compute crossing-free straight-line positions on concentric circles. + + Walking spokes in cyclic order starting from the anchor, each outer + (resp. inner) vertex appears in a contiguous arc of spokes; we + place each vertex at the angular centroid of those spoke positions + on its circle, which preserves the cyclic order implied by the + planar embedding and so makes all annular edges crossing-free.""" + m, k = tire['m'], tire['k'] + outer, inner = tire['outer'], tire['inner'] + moves = tire['lattice_path'] + + # Build spoke list in cyclic order: spoke t connects outer[i_t] to + # inner[j_t] where (i_t, j_t) is the lattice position after t moves. + spokes = [(outer[0], inner[0])] # anchor at t=0 + i, j = 0, 0 + for mv in moves: + if mv == 'O': + i += 1 + else: + j += 1 + spokes.append((outer[i % m], inner[j % k])) + spokes = spokes[:-1] # drop wrap-back duplicate + n = len(spokes) # = m + k + + out_t = {v: [] for v in outer} + in_t = {v: [] for v in inner} + for t, (ov, iv) in enumerate(spokes): + out_t[ov].append(t) + in_t[iv].append(t) + + def circ_mean(ts): + x = sum(math.cos(2 * math.pi * t / n) for t in ts) + y = sum(math.sin(2 * math.pi * t / n) for t in ts) + return math.atan2(y, x) + + pos = {} + for v in outer: + theta = circ_mean(out_t[v]) + pos[v] = (R_out * math.cos(theta), R_out * math.sin(theta)) + for v in inner: + theta = circ_mean(in_t[v]) + pos[v] = (R_in * math.cos(theta), R_in * math.sin(theta)) + return pos + + +def draw_tire(tire, filename, title=None): + """Draw the tire on concentric circles with annular triangulation as + straight-line edges. Outer cycle = blue, inner cycle = red, inner + chords = orange, annular spokes/chords = grey.""" + m, k = tire['m'], tire['k'] + outer, inner = tire['outer'], tire['inner'] + edges = tire['edges'] + + R_out, R_in = 1.0, 0.45 + pos = planar_positions(tire, R_out=R_out, R_in=R_in) + + fig, ax = plt.subplots(figsize=(5.5, 5.5)) + + # faint guide circles + for r in (R_out + 0.05, R_in - 0.05): + ax.add_patch(patches.Circle((0, 0), r, fill=False, + edgecolor='lightgray', + linewidth=0.5, linestyle='--')) + + outer_set = set(outer) + inner_set = set(inner) + C = { + 'outer_cycle': '#1f77b4', + 'inner_cycle': '#d62728', + 'inner_chord': '#ff7f0e', + 'spoke': '#7f7f7f', + } + + for (u, v) in edges: + if u in outer_set and v in outer_set: + color, lw = C['outer_cycle'], 2.4 + elif u in inner_set and v in inner_set: + ia, ib = u - m, v - m + d = abs(ia - ib) + d = min(d, k - d) + if d == 1: + color, lw = C['inner_cycle'], 2.4 + else: + color, lw = C['inner_chord'], 1.6 + else: + color, lw = C['spoke'], 1.0 + x1, y1 = pos[u]; x2, y2 = pos[v] + ax.plot([x1, x2], [y1, y2], color=color, linewidth=lw, zorder=1) + + for v in outer: + x, y = pos[v] + ax.plot(x, y, 'o', color=C['outer_cycle'], markersize=14, zorder=2) + ax.annotate(str(v), (x, y), color='white', ha='center', + va='center', fontsize=8, fontweight='bold', zorder=3) + for v in inner: + x, y = pos[v] + ax.plot(x, y, 'o', color=C['inner_cycle'], markersize=12, zorder=2) + ax.annotate(str(v), (x, y), color='white', ha='center', + va='center', fontsize=7, fontweight='bold', zorder=3) + + ax.set_xlim(-1.18, 1.18) + ax.set_ylim(-1.18, 1.18) + ax.set_aspect('equal') + ax.axis('off') + + if title is None: + title = (f"tire(m={m}, k={k}, chords={tire['n_chords']}, " + f"seed={tire['seed']})") + ax.set_title(title, fontsize=11) + + plt.savefig(filename, dpi=140, bbox_inches='tight') + plt.close() + + +def main(): + out_dir = os.path.dirname(os.path.abspath(__file__)) + # (m, k, n_chords, seed) -- a mix of small/medium, no chords / chords + examples = [ + (6, 3, 0, 1), + (8, 5, 0, 7), + (10, 4, 1, 11), + (12, 7, 3, 23), + (9, 9, 2, 42), + (14, 5, 0, 100), + ] + paths = [] + for (m, k, nc, seed) in examples: + tire = random_tire(m, k, n_chords=nc, seed=seed) + fn = os.path.join(out_dir, f"tire_m{m}_k{k}_c{nc}_s{seed}.png") + draw_tire(tire, fn) + paths.append(fn) + print(f" wrote {os.path.basename(fn)} " + f"path={tire['lattice_path']} " + f"chords={tire['inner_chords']}") + print(f"\nGenerated {len(paths)} tires.") + return paths + + +if __name__ == '__main__': + main() diff --git a/papers/coloring_nested_tire_graphs/experiments/tire_m10_k4_c1_s11.png b/papers/coloring_nested_tire_graphs/experiments/tire_m10_k4_c1_s11.png new file mode 100644 index 0000000..a445458 Binary files /dev/null and b/papers/coloring_nested_tire_graphs/experiments/tire_m10_k4_c1_s11.png differ diff --git a/papers/coloring_nested_tire_graphs/experiments/tire_m12_k7_c3_s23.png b/papers/coloring_nested_tire_graphs/experiments/tire_m12_k7_c3_s23.png new file mode 100644 index 0000000..3d0c48d Binary files /dev/null and b/papers/coloring_nested_tire_graphs/experiments/tire_m12_k7_c3_s23.png differ diff --git a/papers/coloring_nested_tire_graphs/experiments/tire_m14_k5_c0_s100.png b/papers/coloring_nested_tire_graphs/experiments/tire_m14_k5_c0_s100.png new file mode 100644 index 0000000..ca279ab Binary files /dev/null and b/papers/coloring_nested_tire_graphs/experiments/tire_m14_k5_c0_s100.png differ diff --git a/papers/coloring_nested_tire_graphs/experiments/tire_m6_k3_c0_s1.png b/papers/coloring_nested_tire_graphs/experiments/tire_m6_k3_c0_s1.png new file mode 100644 index 0000000..085769d Binary files /dev/null and b/papers/coloring_nested_tire_graphs/experiments/tire_m6_k3_c0_s1.png differ diff --git a/papers/coloring_nested_tire_graphs/experiments/tire_m8_k5_c0_s7.png b/papers/coloring_nested_tire_graphs/experiments/tire_m8_k5_c0_s7.png new file mode 100644 index 0000000..9f1b539 Binary files /dev/null and b/papers/coloring_nested_tire_graphs/experiments/tire_m8_k5_c0_s7.png differ diff --git a/papers/coloring_nested_tire_graphs/experiments/tire_m9_k9_c2_s42.png b/papers/coloring_nested_tire_graphs/experiments/tire_m9_k9_c2_s42.png new file mode 100644 index 0000000..00de6c0 Binary files /dev/null and b/papers/coloring_nested_tire_graphs/experiments/tire_m9_k9_c2_s42.png differ diff --git a/papers/nested_level_duals/fig_dual_depth.png b/papers/coloring_nested_tire_graphs/fig_dual_depth.png similarity index 100% rename from papers/nested_level_duals/fig_dual_depth.png rename to papers/coloring_nested_tire_graphs/fig_dual_depth.png diff --git a/papers/nested_level_duals/paper.aux b/papers/coloring_nested_tire_graphs/paper.aux similarity index 100% rename from papers/nested_level_duals/paper.aux rename to papers/coloring_nested_tire_graphs/paper.aux diff --git a/papers/nested_level_duals/paper.fdb_latexmk b/papers/coloring_nested_tire_graphs/paper.fdb_latexmk similarity index 100% rename from papers/nested_level_duals/paper.fdb_latexmk rename to papers/coloring_nested_tire_graphs/paper.fdb_latexmk diff --git a/papers/nested_level_duals/paper.fls b/papers/coloring_nested_tire_graphs/paper.fls similarity index 100% rename from papers/nested_level_duals/paper.fls rename to papers/coloring_nested_tire_graphs/paper.fls diff --git a/papers/nested_level_duals/paper.log b/papers/coloring_nested_tire_graphs/paper.log similarity index 100% rename from papers/nested_level_duals/paper.log rename to papers/coloring_nested_tire_graphs/paper.log diff --git a/papers/nested_level_duals/paper.pdf b/papers/coloring_nested_tire_graphs/paper.pdf similarity index 100% rename from papers/nested_level_duals/paper.pdf rename to papers/coloring_nested_tire_graphs/paper.pdf diff --git a/papers/nested_level_duals/paper.tex b/papers/coloring_nested_tire_graphs/paper.tex similarity index 100% rename from papers/nested_level_duals/paper.tex rename to papers/coloring_nested_tire_graphs/paper.tex