Stop splitting random medial treads into components
This commit is contained in:
+5
-12
@@ -10,16 +10,9 @@
|
||||
- tire-tree nodes: 4
|
||||
- tire-tree edges: 3
|
||||
|
||||
| node | depth | faces | annular | up | singleton down | bite apexes | full medial tires |
|
||||
| node | depth | faces | annular cycles | annular | up | singleton down | bite apexes |
|
||||
|--:|--:|--:|--:|--:|--:|--:|--:|
|
||||
| T0 | 2 | 23 | 23 | 10 | 13 | 0 | 1 |
|
||||
| T0.0 | | | | 10 | 13 | - | `UDDUDUUDDUDUDUDUDDDUUDD` |
|
||||
| T1 | 1 | 15 | 15 | 5 | 10 | 0 | 1 |
|
||||
| T1.0 | | | | 5 | 10 | - | `UDDDUDDUDUDDDUD` |
|
||||
| T2 | 3 | 14 | 14 | 11 | 0 | 0 | 4 |
|
||||
| T2.0 | | | | 3 | 0 | - | `UUU` |
|
||||
| T2.1 | | | | 3 | 0 | - | `UUU` |
|
||||
| T2.2 | | | | 5 | 0 | - | `UUUUU` |
|
||||
| T2.3 | | | | 3 | 0 | - | `UUU` |
|
||||
| T3 | 3 | 5 | 5 | 5 | 0 | 0 | 1 |
|
||||
| T3.0 | | | | 5 | 0 | - | `UUUUU` |
|
||||
| T0 | 2 | 23 | 1 | 23 | 10 | 13 | 0 |
|
||||
| T1 | 1 | 15 | 1 | 15 | 5 | 10 | 0 |
|
||||
| T2 | 3 | 14 | 4 | 14 | 11 | 0 | 0 |
|
||||
| T3 | 3 | 5 | 1 | 5 | 5 | 0 | 0 |
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 376 KiB After Width: | Height: | Size: 394 KiB |
+113
-60
@@ -2,9 +2,8 @@
|
||||
|
||||
The source graphs come from ``plantri -c5`` in graph6 format. For each sampled
|
||||
30-vertex triangulation, this script chooses a random source vertex, builds the
|
||||
BFS depth-component tire tree, recognizes every full medial tire graph in the
|
||||
decomposition, and draws both the tire tree and the realized full medial tire
|
||||
graphs.
|
||||
BFS depth-component tire tree, and draws both the tire tree and the medial
|
||||
tread model for each depth component.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -36,8 +35,11 @@ import networkx as nx
|
||||
if str(PAPER_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(PAPER_DIR))
|
||||
|
||||
from lib.medial_tire_decomposition import ekey, medial_tire_facemodel, recognise
|
||||
from lib.full_medial_tire_generator import FullMedialTireGraph
|
||||
from lib.medial_tire_decomposition import (
|
||||
annular_cycle_components,
|
||||
ekey,
|
||||
medial_tire_facemodel,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -49,7 +51,8 @@ class TreadNode:
|
||||
up: frozenset
|
||||
down: frozenset
|
||||
bites: frozenset
|
||||
tires: tuple[tuple[FullMedialTireGraph, dict], ...]
|
||||
medial: nx.Graph
|
||||
annular_cycles: tuple[tuple, ...]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -228,8 +231,8 @@ def build_tire_tree(g: nx.Graph, source: int, augment: bool = True):
|
||||
if tread is None or len(tread["up"]) < 3:
|
||||
continue
|
||||
mt = medial_tire_facemodel(tread["tread_faces"])
|
||||
tires = tuple(recognise(mt, tread))
|
||||
if not tires:
|
||||
annular_cycles = tuple(annular_cycle_components(mt, tread["annular"]))
|
||||
if not annular_cycles:
|
||||
continue
|
||||
node = TreadNode(
|
||||
idx=len(nodes),
|
||||
@@ -239,7 +242,8 @@ def build_tire_tree(g: nx.Graph, source: int, augment: bool = True):
|
||||
up=frozenset(tread["up"]),
|
||||
down=frozenset(tread["down"]),
|
||||
bites=frozenset(tread["bites"]),
|
||||
tires=tires,
|
||||
medial=mt,
|
||||
annular_cycles=annular_cycles,
|
||||
)
|
||||
comp_to_node[comp_idx] = node.idx
|
||||
nodes.append(node)
|
||||
@@ -343,7 +347,7 @@ def draw_tire_tree(ax, nodes: list[TreadNode], tree_edges):
|
||||
ax.text(
|
||||
x,
|
||||
y,
|
||||
f"T{node.idx}\nd={node.depth}\n{len(node.tires)} tire(s)",
|
||||
f"T{node.idx}\nd={node.depth}\n{len(node.annular_cycles)} cycle(s)",
|
||||
ha="center",
|
||||
va="center",
|
||||
fontsize=8,
|
||||
@@ -373,58 +377,113 @@ def edge_midpoint_angle(i: int, n: int) -> float:
|
||||
return math.pi / 2 - 2 * math.pi * (i + 0.5) / n
|
||||
|
||||
|
||||
def draw_full_medial_tire(ax, graph: FullMedialTireGraph, title: str):
|
||||
n = graph.n
|
||||
ann = [vertex_xy(k, n, 1.0) for k in range(n)]
|
||||
matched = graph.bite_edges
|
||||
cyc_x = [p[0] for p in ann] + [ann[0][0]]
|
||||
cyc_y = [p[1] for p in ann] + [ann[0][1]]
|
||||
def draw_tread_model(ax, node: TreadNode):
|
||||
cycle_count = len(node.annular_cycles)
|
||||
offsets = [3.25 * (i - (cycle_count - 1) / 2) for i in range(cycle_count)]
|
||||
apex_positions: dict[tuple, tuple[float, float]] = {}
|
||||
apex_corners: dict[tuple, list[tuple[float, float]]] = defaultdict(list)
|
||||
ann_positions: dict[tuple, tuple[float, float]] = {}
|
||||
|
||||
for cycle_idx, order in enumerate(node.annular_cycles):
|
||||
n = len(order)
|
||||
dx = offsets[cycle_idx]
|
||||
ann = {
|
||||
vertex: (dx + x, y)
|
||||
for vertex, (x, y) in zip(order, [vertex_xy(k, n, 1.0) for k in range(n)])
|
||||
}
|
||||
ann_positions.update(ann)
|
||||
|
||||
cyc_x = [ann[v][0] for v in order] + [ann[order[0]][0]]
|
||||
cyc_y = [ann[v][1] for v in order] + [ann[order[0]][1]]
|
||||
ax.plot(cyc_x, cyc_y, color="black", lw=1.3, zorder=2)
|
||||
for i, tooth in enumerate(graph.tooth_word):
|
||||
if tooth == "U":
|
||||
r, color = 1.42, "#2563eb"
|
||||
elif i not in matched:
|
||||
r, color = 0.58, "#dc2626"
|
||||
else:
|
||||
|
||||
for i, a in enumerate(order):
|
||||
b = order[(i + 1) % n]
|
||||
apexes = [
|
||||
w for w in set(node.medial.neighbors(a)) & set(node.medial.neighbors(b))
|
||||
if w not in node.annular
|
||||
]
|
||||
for apex in apexes:
|
||||
apex_corners[apex].extend([ann[a], ann[b]])
|
||||
if apex in apex_positions:
|
||||
continue
|
||||
ang = edge_midpoint_angle(i, n)
|
||||
apex = (r * math.cos(ang), r * math.sin(ang))
|
||||
for corner in (ann[i], ann[(i + 1) % n]):
|
||||
ax.plot([apex[0], corner[0]], [apex[1], corner[1]], color="#9ca3af", lw=0.5)
|
||||
ax.scatter([apex[0]], [apex[1]], s=12, color=color, zorder=3)
|
||||
for i, j in sorted(graph.bites):
|
||||
corners = [ann[i], ann[(i + 1) % n], ann[j], ann[(j + 1) % n]]
|
||||
apex = (0.82 * sum(p[0] for p in corners) / 4, 0.82 * sum(p[1] for p in corners) / 4)
|
||||
angle = edge_midpoint_angle(i, n)
|
||||
if apex in node.up:
|
||||
radius = 1.42
|
||||
else:
|
||||
radius = 0.58
|
||||
apex_positions[apex] = (
|
||||
dx + radius * math.cos(angle),
|
||||
radius * math.sin(angle),
|
||||
)
|
||||
|
||||
for apex, corners in apex_corners.items():
|
||||
if apex in node.bites and corners:
|
||||
cx = sum(p[0] for p in corners) / len(corners)
|
||||
cy = sum(p[1] for p in corners) / len(corners)
|
||||
center_x = sum(offsets) / len(offsets) if offsets else 0.0
|
||||
apex_positions[apex] = (
|
||||
center_x + 0.82 * (cx - center_x),
|
||||
0.82 * cy,
|
||||
)
|
||||
pos = apex_positions[apex]
|
||||
for corner in corners:
|
||||
ax.plot([apex[0], corner[0]], [apex[1], corner[1]], color="#9ca3af", lw=0.5)
|
||||
ax.scatter([apex[0]], [apex[1]], s=22, color="#7f1d1d", edgecolors="black", lw=0.4)
|
||||
ax.scatter([p[0] for p in ann], [p[1] for p in ann], s=9, color="black", zorder=4)
|
||||
bites = ",".join(f"{i}{j}" for i, j in sorted(graph.bites)) or "-"
|
||||
ax.set_title(f"{title}\n{graph.tooth_word} b:{bites}", fontsize=5.8, pad=1.5)
|
||||
ax.set_xlim(-1.6, 1.6)
|
||||
ax.set_ylim(-1.6, 1.6)
|
||||
ax.plot([pos[0], corner[0]], [pos[1], corner[1]], color="#9ca3af", lw=0.5)
|
||||
|
||||
for apex, pos in apex_positions.items():
|
||||
if apex in node.up:
|
||||
color, size, edgecolor = "#2563eb", 13, "none"
|
||||
elif apex in node.bites:
|
||||
color, size, edgecolor = "#7f1d1d", 24, "black"
|
||||
else:
|
||||
color, size, edgecolor = "#dc2626", 13, "none"
|
||||
ax.scatter(
|
||||
[pos[0]],
|
||||
[pos[1]],
|
||||
s=size,
|
||||
color=color,
|
||||
edgecolors=edgecolor,
|
||||
linewidths=0.4,
|
||||
zorder=3,
|
||||
)
|
||||
|
||||
if ann_positions:
|
||||
ax.scatter(
|
||||
[p[0] for p in ann_positions.values()],
|
||||
[p[1] for p in ann_positions.values()],
|
||||
s=9,
|
||||
color="black",
|
||||
zorder=4,
|
||||
)
|
||||
|
||||
singleton_down = set(node.down) - set(node.bites)
|
||||
ax.set_title(
|
||||
f"T{node.idx} d={node.depth}: {len(node.annular_cycles)} annular cycle(s)\n"
|
||||
f"ann={len(node.annular)} up={len(node.up)} down={len(singleton_down)} "
|
||||
f"bite={len(node.bites)}",
|
||||
fontsize=6.4,
|
||||
pad=1.5,
|
||||
)
|
||||
pad = 1.7
|
||||
ax.set_xlim(min(offsets, default=0.0) - pad, max(offsets, default=0.0) + pad)
|
||||
ax.set_ylim(-1.65, 1.65)
|
||||
ax.set_aspect("equal")
|
||||
ax.axis("off")
|
||||
|
||||
|
||||
def draw_medial_tire_grid(fig, outer_spec, nodes):
|
||||
tires = []
|
||||
for node in nodes:
|
||||
for comp_idx, (graph, _bij) in enumerate(node.tires):
|
||||
tires.append((node.idx, comp_idx, graph))
|
||||
if not tires:
|
||||
if not nodes:
|
||||
ax = fig.add_subplot(outer_spec)
|
||||
ax.text(0.5, 0.5, "No full medial tire graphs recognized", ha="center")
|
||||
ax.text(0.5, 0.5, "No medial treads extracted", ha="center")
|
||||
ax.axis("off")
|
||||
return
|
||||
cols = min(5, max(1, math.ceil(math.sqrt(len(tires)))))
|
||||
rows = math.ceil(len(tires) / cols)
|
||||
cols = min(3, max(1, math.ceil(math.sqrt(len(nodes)))))
|
||||
rows = math.ceil(len(nodes) / cols)
|
||||
sub = outer_spec.subgridspec(rows, cols, wspace=0.08, hspace=0.35)
|
||||
for i in range(rows * cols):
|
||||
ax = fig.add_subplot(sub[i // cols, i % cols])
|
||||
if i < len(tires):
|
||||
node_idx, comp_idx, graph = tires[i]
|
||||
draw_full_medial_tire(ax, graph, f"T{node_idx}.{comp_idx} n={graph.n}")
|
||||
if i < len(nodes):
|
||||
draw_tread_model(ax, nodes[i])
|
||||
else:
|
||||
ax.axis("off")
|
||||
|
||||
@@ -452,21 +511,15 @@ def write_index(
|
||||
f"- tire-tree nodes: {len(nodes)}",
|
||||
f"- tire-tree edges: {len(tree_edges)}",
|
||||
"",
|
||||
"| node | depth | faces | annular | up | singleton down | bite apexes | full medial tires |",
|
||||
"| node | depth | faces | annular cycles | annular | up | singleton down | bite apexes |",
|
||||
"|--:|--:|--:|--:|--:|--:|--:|--:|",
|
||||
]
|
||||
for node in nodes:
|
||||
singleton_down = set(node.down) - set(node.bites)
|
||||
lines.append(
|
||||
f"| T{node.idx} | {node.depth} | {len(node.face_indices)} | "
|
||||
f"{len(node.annular)} | {len(node.up)} | {len(singleton_down)} | "
|
||||
f"{len(node.bites)} | {len(node.tires)} |"
|
||||
)
|
||||
for comp_idx, (tire, _bij) in enumerate(node.tires):
|
||||
bites = ",".join(f"({i},{j})" for i, j in sorted(tire.bites)) or "-"
|
||||
lines.append(
|
||||
f"| T{node.idx}.{comp_idx} | | | | {len(tire.up_edges)} | "
|
||||
f"{len(tire.singleton_down_edges)} | {bites} | `{tire.tooth_word}` |"
|
||||
f"{len(node.annular_cycles)} | {len(node.annular)} | {len(node.up)} | "
|
||||
f"{len(singleton_down)} | {len(node.bites)} |"
|
||||
)
|
||||
path.write_text("\n".join(lines) + "\n")
|
||||
|
||||
@@ -517,7 +570,7 @@ def draw_case(out_dir: Path, graph_idx: int, g: nx.Graph, source: int, augment:
|
||||
nodes,
|
||||
tree_edges,
|
||||
)
|
||||
return png, pdf, len(nodes), sum(len(node.tires) for node in nodes)
|
||||
return png, pdf, len(nodes), sum(len(node.annular_cycles) for node in nodes)
|
||||
|
||||
|
||||
def run(args: argparse.Namespace):
|
||||
@@ -535,12 +588,12 @@ def run(args: argparse.Namespace):
|
||||
sources = [rng.choice(list(graph.nodes())) for graph in graphs]
|
||||
|
||||
for i, (graph, source) in enumerate(zip(graphs, sources), start=1):
|
||||
png, pdf, node_count, tire_count = draw_case(
|
||||
png, pdf, node_count, annular_cycle_count = draw_case(
|
||||
out_dir, i, graph, source, augment=not args.no_augment_same_level_faces
|
||||
)
|
||||
print(
|
||||
f"case {i}: source={source}, connectivity={nx.node_connectivity(graph)}, "
|
||||
f"tire nodes={node_count}, full medial tires={tire_count}"
|
||||
f"tire nodes={node_count}, annular cycles={annular_cycle_count}"
|
||||
)
|
||||
print(f" wrote {png}")
|
||||
print(f" wrote {pdf}")
|
||||
|
||||
+4
-9
@@ -10,13 +10,8 @@
|
||||
- tire-tree nodes: 3
|
||||
- tire-tree edges: 2
|
||||
|
||||
| node | depth | faces | annular | up | singleton down | bite apexes | full medial tires |
|
||||
| node | depth | faces | annular cycles | annular | up | singleton down | bite apexes |
|
||||
|--:|--:|--:|--:|--:|--:|--:|--:|
|
||||
| T0 | 1 | 16 | 16 | 6 | 10 | 0 | 1 |
|
||||
| T0.0 | | | | 6 | 10 | - | `DUDUDUDDUDDDUDDU` |
|
||||
| T1 | 2 | 20 | 20 | 10 | 10 | 0 | 1 |
|
||||
| T1.0 | | | | 10 | 10 | - | `UUDUUDUDDUDUDUDUUDDD` |
|
||||
| T2 | 3 | 16 | 16 | 12 | 0 | 1 | 3 |
|
||||
| T2.0 | | | | 6 | 0 | (1,5) | `UDUUUDUU` |
|
||||
| T2.1 | | | | 3 | 0 | - | `UUU` |
|
||||
| T2.2 | | | | 5 | 0 | - | `UUUUU` |
|
||||
| T0 | 1 | 16 | 1 | 16 | 6 | 10 | 0 |
|
||||
| T1 | 2 | 20 | 1 | 20 | 10 | 10 | 0 |
|
||||
| T2 | 3 | 16 | 3 | 16 | 12 | 0 | 1 |
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 326 KiB After Width: | Height: | Size: 340 KiB |
+5
-10
@@ -10,14 +10,9 @@
|
||||
- tire-tree nodes: 4
|
||||
- tire-tree edges: 3
|
||||
|
||||
| node | depth | faces | annular | up | singleton down | bite apexes | full medial tires |
|
||||
| node | depth | faces | annular cycles | annular | up | singleton down | bite apexes |
|
||||
|--:|--:|--:|--:|--:|--:|--:|--:|
|
||||
| T0 | 1 | 16 | 16 | 7 | 9 | 0 | 1 |
|
||||
| T0.0 | | | | 7 | 9 | - | `DUDUDUDUDUDDUDUD` |
|
||||
| T1 | 2 | 17 | 17 | 9 | 8 | 0 | 1 |
|
||||
| T1.0 | | | | 9 | 8 | - | `UUUDDUDUDUDUDDUUD` |
|
||||
| T2 | 3 | 14 | 14 | 8 | 4 | 1 | 1 |
|
||||
| T2.0 | | | | 8 | 4 | (1,5) | `DDUUUDDUUDUDUU` |
|
||||
| T3 | 4 | 6 | 6 | 5 | 0 | 0 | 2 |
|
||||
| T3.0 | | | | 3 | 0 | - | `UUU` |
|
||||
| T3.1 | | | | 3 | 0 | - | `UUU` |
|
||||
| T0 | 1 | 16 | 1 | 16 | 7 | 9 | 0 |
|
||||
| T1 | 2 | 17 | 1 | 17 | 9 | 8 | 0 |
|
||||
| T2 | 3 | 14 | 1 | 14 | 8 | 4 | 1 |
|
||||
| T3 | 4 | 6 | 2 | 6 | 5 | 0 | 0 |
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 331 KiB After Width: | Height: | Size: 386 KiB |
Reference in New Issue
Block a user