"""Draw a diagram illustrating dual depth. We build a concentric ("stacked rings") triangulation G with a single level source S = {0} at the centre, so the vertices fall into clean BFS levels 0, 1, 2, 3 by radius. We then overlay the inner (weak) dual G' -- one dual vertex per bounded triangular face -- and colour each dual vertex by its dual depth: the minimum level among the three vertices of the corresponding face. The construction makes all three attainable dual depths (0, 1, 2) appear; the outer face (the level-3 triangle) is excluded from the inner dual. """ import math import os import networkx as nx import matplotlib.pyplot as plt from matplotlib.lines import Line2D OUT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # --------------------------------------------------------------------------- # Construct G: centre 0 (level 0) plus three triangular rings at radii 1,2,3. # Vertex j of every ring sits at angle 90 + 120*j degrees, so spokes are radial. # --------------------------------------------------------------------------- RINGS = 3 # ring 3 is the outer-face triangle pos = {0: (0.0, 0.0)} ring = {0: [0]} # ring index -> vertex ids nxt = 1 for r in range(1, RINGS + 1): ids = [] for j in range(3): ang = math.radians(90 + 120 * j) pos[nxt] = (r * math.cos(ang), r * math.sin(ang)) ids.append(nxt) nxt += 1 ring[r] = ids G = nx.Graph() G.add_nodes_from(pos) # centre fan for v in ring[1]: G.add_edge(0, v) # ring triangles for r in range(1, RINGS + 1): a, b, c = ring[r] G.add_edges_from([(a, b), (b, c), (c, a)]) # radial spokes + annulus diagonals (inner_j -- outer_{j+1}) for r in range(1, RINGS): inner, outer = ring[r], ring[r + 1] for j in range(3): G.add_edge(inner[j], outer[j]) # spoke G.add_edge(inner[j], outer[(j + 1) % 3]) # diagonal assert G.number_of_edges() == 3 * G.number_of_nodes() - 6, "not a triangulation" # --------------------------------------------------------------------------- # Levels: BFS distance from the source S = {0}. # --------------------------------------------------------------------------- S = {0} level = nx.multi_source_dijkstra_path_length(G, S) # --------------------------------------------------------------------------- # Bounded faces (the inner dual's vertices). Listed explicitly from the # construction; the outer face (ring 3 triangle) is omitted. # --------------------------------------------------------------------------- faces = [] a, b, c = ring[1] faces += [(0, a, b), (0, b, c), (0, c, a)] # centre fan for r in range(1, RINGS): inn, out = ring[r], ring[r + 1] for j in range(3): i0, i1 = inn[j], inn[(j + 1) % 3] o1 = out[(j + 1) % 3] o0 = out[j] faces += [(i0, i1, o1), (i0, o1, o0)] # the two annulus triangles def dual_depth(face): return min(level[v] for v in face) # dual vertex positions = face centroids dpos = {} ddepth = {} for f in faces: cx = sum(pos[v][0] for v in f) / 3.0 cy = sum(pos[v][1] for v in f) / 3.0 dpos[f] = (cx, cy) ddepth[f] = dual_depth(f) # dual edges: two bounded faces sharing an edge of G edge_faces = {} for f in faces: for i in range(3): e = frozenset((f[i], f[(i + 1) % 3])) edge_faces.setdefault(e, []).append(f) dual_edges = [fs for fs in edge_faces.values() if len(fs) == 2] # --------------------------------------------------------------------------- # Draw. # --------------------------------------------------------------------------- LEVEL_COLOR = {0: '#1e293b', 1: '#475569', 2: '#94a3b8', 3: '#cbd5e1'} DEPTH_COLOR = {0: '#16a34a', 1: '#2563eb', 2: '#dc2626'} fig, ax = plt.subplots(figsize=(9, 9)) # G edges (light) and vertices (labelled by level) nx.draw_networkx_edges(G, pos, ax=ax, edge_color='#d1d5db', width=1.4) for v, (x, y) in pos.items(): ax.scatter([x], [y], s=520, color=LEVEL_COLOR[level[v]], edgecolors='black', linewidths=1.0, zorder=3) ax.text(x, y, f'{v}\n$\\ell{{=}}{level[v]}$', ha='center', va='center', color='white', fontsize=8.5, fontweight='bold', zorder=4) # dual edges (dashed) and dual vertices (squares, coloured by dual depth) for f0, f1 in dual_edges: (x0, y0), (x1, y1) = dpos[f0], dpos[f1] ax.plot([x0, x1], [y0, y1], color='#fb923c', lw=1.3, ls='--', zorder=2) for f, (x, y) in dpos.items(): ax.scatter([x], [y], s=300, marker='s', color=DEPTH_COLOR[ddepth[f]], edgecolors='black', linewidths=0.8, zorder=5) ax.text(x, y, str(ddepth[f]), ha='center', va='center', color='white', fontsize=9, fontweight='bold', zorder=6) legend = [ Line2D([0], [0], marker='o', color='w', label='$G$ vertex (label = level $\\ell$)', markerfacecolor='#475569', markeredgecolor='black', markersize=12), Line2D([0], [0], color='#fb923c', ls='--', lw=1.3, label="dual edge of $G'$"), Line2D([0], [0], marker='s', color='w', label='dual depth $\\delta = 0$', markerfacecolor=DEPTH_COLOR[0], markeredgecolor='black', markersize=11), Line2D([0], [0], marker='s', color='w', label='dual depth $\\delta = 1$', markerfacecolor=DEPTH_COLOR[1], markeredgecolor='black', markersize=11), Line2D([0], [0], marker='s', color='w', label='dual depth $\\delta = 2$', markerfacecolor=DEPTH_COLOR[2], markeredgecolor='black', markersize=11), ] ax.legend(handles=legend, loc='upper left', fontsize=10, framealpha=0.95) ax.set_aspect('equal') ax.axis('off') ax.set_title("Dual depth in a stacked-ring triangulation $G$ with source $S=\\{0\\}$.\n" "Each bounded face carries a dual vertex (square) coloured by its dual " "depth\n$\\delta(d_f)=\\min_{v\\in V(f)}\\ell(v)$. " "The outer face (level-3 triangle) has no dual vertex.", fontsize=11) fig.tight_layout() out = os.path.join(OUT_DIR, 'fig_dual_depth.png') fig.savefig(out, dpi=180, bbox_inches='tight') plt.close(fig) # console summary from collections import Counter print(f'n={G.number_of_nodes()} edges={G.number_of_edges()} ' f'bounded faces={len(faces)} dual edges={len(dual_edges)}') print('dual depth distribution:', dict(sorted(Counter(ddepth.values()).items()))) print(f'wrote {out}')