Augment same-level faces before medial tire extraction
This commit is contained in:
+12
-6
@@ -1,19 +1,25 @@
|
||||
# Random medial tire decomposition 1
|
||||
|
||||
- vertices: 30
|
||||
- edges: 84
|
||||
- node connectivity: 5
|
||||
- original vertices: 30
|
||||
- original edges: 84
|
||||
- original node connectivity: 5
|
||||
- augmented vertices: 33
|
||||
- augmented edges: 93
|
||||
- same-level faces filled: 3
|
||||
- source vertex: 14
|
||||
- tire-tree nodes: 4
|
||||
- tire-tree edges: 3
|
||||
|
||||
| node | depth | faces | annular | up | down | bites | full medial tires |
|
||||
| node | depth | faces | annular | up | singleton down | bite apexes | full medial tires |
|
||||
|--:|--:|--:|--:|--:|--:|--:|--:|
|
||||
| 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 | 8 | 5 | 11 | 0 | 0 | 1 |
|
||||
| T2.0 | | | | 5 | 0 | - | `UUUUU` |
|
||||
| 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` |
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 365 KiB After Width: | Height: | Size: 376 KiB |
+101
-21
@@ -52,6 +52,13 @@ class TreadNode:
|
||||
tires: tuple[tuple[FullMedialTireGraph, dict], ...]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Augmentation:
|
||||
graph: nx.Graph
|
||||
added_vertices: tuple[int, ...]
|
||||
filled_faces: tuple[tuple[int, tuple[int, int, int], int], ...]
|
||||
|
||||
|
||||
def sample_plantri_graphs(n: int, count: int, seed: int, scan_limit: int) -> list[nx.Graph]:
|
||||
cmd = [str(REPO_ROOT / "plantri" / "plantri"), "-g", "-c5", str(n)]
|
||||
rng = random.Random(seed)
|
||||
@@ -111,6 +118,39 @@ def edge_face_data(faces):
|
||||
return face_edges, edge_faces
|
||||
|
||||
|
||||
def augment_same_level_faces(g: nx.Graph, source: int) -> Augmentation:
|
||||
"""Stack a new vertex into every facial triangle with one BFS level.
|
||||
|
||||
If a triangular face has all three vertices at level d, the new vertex is
|
||||
adjacent to those three vertices and therefore has level d + 1. This turns
|
||||
the same-level region into three adjacent-level tread faces before the tire
|
||||
decomposition is extracted.
|
||||
"""
|
||||
levels = nx.single_source_shortest_path_length(g, source)
|
||||
faces = triangular_faces(g)
|
||||
augmented = g.copy()
|
||||
next_vertex = max(augmented.nodes()) + 1
|
||||
added = []
|
||||
filled = []
|
||||
|
||||
for face in faces:
|
||||
face_levels = {levels[v] for v in face}
|
||||
if len(face_levels) != 1:
|
||||
continue
|
||||
new_vertex = next_vertex
|
||||
next_vertex += 1
|
||||
augmented.add_node(new_vertex)
|
||||
augmented.add_edges_from((new_vertex, v) for v in face)
|
||||
added.append(new_vertex)
|
||||
filled.append((new_vertex, tuple(face), next(iter(face_levels))))
|
||||
|
||||
return Augmentation(
|
||||
graph=augmented,
|
||||
added_vertices=tuple(added),
|
||||
filled_faces=tuple(filled),
|
||||
)
|
||||
|
||||
|
||||
def depth_components(faces, face_edges, edge_faces, levels):
|
||||
depths = [min(levels[v] for v in face) for face in faces]
|
||||
dual_adj: dict[int, set[int]] = defaultdict(set)
|
||||
@@ -169,10 +209,12 @@ def tread_from_component(faces, levels, face_indices):
|
||||
}
|
||||
|
||||
|
||||
def build_tire_tree(g: nx.Graph, source: int):
|
||||
faces = triangular_faces(g)
|
||||
def build_tire_tree(g: nx.Graph, source: int, augment: bool = True):
|
||||
augmentation = augment_same_level_faces(g, source) if augment else Augmentation(g, (), ())
|
||||
work_graph = augmentation.graph
|
||||
faces = triangular_faces(work_graph)
|
||||
face_edges, edge_faces = edge_face_data(faces)
|
||||
levels = nx.single_source_shortest_path_length(g, source)
|
||||
levels = nx.single_source_shortest_path_length(work_graph, source)
|
||||
comps, depths, dual_adj = depth_components(faces, face_edges, edge_faces, levels)
|
||||
comp_of_face = {}
|
||||
for comp_idx, (_depth, face_indices) in enumerate(comps):
|
||||
@@ -215,7 +257,7 @@ def build_tire_tree(g: nx.Graph, source: int):
|
||||
parent_candidates.add(comp_to_node[other_comp])
|
||||
for parent in parent_candidates:
|
||||
tree_edges.add((parent, child))
|
||||
return faces, levels, nodes, sorted(tree_edges)
|
||||
return augmentation, faces, levels, nodes, sorted(tree_edges)
|
||||
|
||||
|
||||
def graph_layout(g: nx.Graph):
|
||||
@@ -225,24 +267,37 @@ def graph_layout(g: nx.Graph):
|
||||
return nx.spring_layout(g, seed=0)
|
||||
|
||||
|
||||
def draw_base_graph(ax, g, levels, source):
|
||||
def draw_base_graph(ax, g, levels, source, added_vertices=()):
|
||||
pos = graph_layout(g)
|
||||
max_level = max(levels.values())
|
||||
cmap = plt.get_cmap("viridis", max_level + 1)
|
||||
node_colors = [cmap(levels[v]) for v in g.nodes()]
|
||||
nx.draw_networkx_edges(g, pos, ax=ax, edge_color="#cbd5e1", width=0.8)
|
||||
added_set = set(added_vertices)
|
||||
nx.draw_networkx_nodes(
|
||||
g,
|
||||
pos,
|
||||
ax=ax,
|
||||
node_color=node_colors,
|
||||
node_size=[150 if v == source else 72 for v in g.nodes()],
|
||||
edgecolors=["#dc2626" if v == source else "#111827" for v in g.nodes()],
|
||||
linewidths=[1.8 if v == source else 0.45 for v in g.nodes()],
|
||||
node_size=[
|
||||
150 if v == source else 96 if v in added_set else 72
|
||||
for v in g.nodes()
|
||||
],
|
||||
edgecolors=[
|
||||
"#dc2626" if v == source else "#7c3aed" if v in added_set else "#111827"
|
||||
for v in g.nodes()
|
||||
],
|
||||
linewidths=[
|
||||
1.8 if v == source else 1.2 if v in added_set else 0.45
|
||||
for v in g.nodes()
|
||||
],
|
||||
)
|
||||
labels = {v: str(v) for v in g.nodes()}
|
||||
nx.draw_networkx_labels(g, pos, labels=labels, ax=ax, font_size=5)
|
||||
ax.set_title(f"G, source {source}; vertex levels 0..{max_level}", fontsize=10)
|
||||
ax.set_title(
|
||||
f"Augmented G, source {source}; vertex levels 0..{max_level}",
|
||||
fontsize=10,
|
||||
)
|
||||
ax.set_aspect("equal")
|
||||
ax.axis("off")
|
||||
|
||||
@@ -374,52 +429,69 @@ def draw_medial_tire_grid(fig, outer_spec, nodes):
|
||||
ax.axis("off")
|
||||
|
||||
|
||||
def write_index(path: Path, graph_idx: int, source: int, g: nx.Graph, nodes, tree_edges):
|
||||
def write_index(
|
||||
path: Path,
|
||||
graph_idx: int,
|
||||
source: int,
|
||||
original_graph: nx.Graph,
|
||||
augmentation: Augmentation,
|
||||
nodes,
|
||||
tree_edges,
|
||||
):
|
||||
g = augmentation.graph
|
||||
lines = [
|
||||
f"# Random medial tire decomposition {graph_idx}",
|
||||
"",
|
||||
f"- vertices: {g.number_of_nodes()}",
|
||||
f"- edges: {g.number_of_edges()}",
|
||||
f"- node connectivity: {nx.node_connectivity(g)}",
|
||||
f"- original vertices: {original_graph.number_of_nodes()}",
|
||||
f"- original edges: {original_graph.number_of_edges()}",
|
||||
f"- original node connectivity: {nx.node_connectivity(original_graph)}",
|
||||
f"- augmented vertices: {g.number_of_nodes()}",
|
||||
f"- augmented edges: {g.number_of_edges()}",
|
||||
f"- same-level faces filled: {len(augmentation.added_vertices)}",
|
||||
f"- source vertex: {source}",
|
||||
f"- tire-tree nodes: {len(nodes)}",
|
||||
f"- tire-tree edges: {len(tree_edges)}",
|
||||
"",
|
||||
"| node | depth | faces | annular | up | down | bites | full medial tires |",
|
||||
"| node | depth | faces | annular | up | singleton down | bite apexes | full medial tires |",
|
||||
"|--:|--:|--:|--:|--:|--:|--:|--:|",
|
||||
]
|
||||
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(node.down)} | "
|
||||
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.down_edges)} | {bites} | `{tire.tooth_word}` |"
|
||||
f"{len(tire.singleton_down_edges)} | {bites} | `{tire.tooth_word}` |"
|
||||
)
|
||||
path.write_text("\n".join(lines) + "\n")
|
||||
|
||||
|
||||
def draw_case(out_dir: Path, graph_idx: int, g: nx.Graph, source: int):
|
||||
_faces, levels, nodes, tree_edges = build_tire_tree(g, source)
|
||||
def draw_case(out_dir: Path, graph_idx: int, g: nx.Graph, source: int, augment: bool = True):
|
||||
augmentation, _faces, levels, nodes, tree_edges = build_tire_tree(g, source, augment=augment)
|
||||
work_graph = augmentation.graph
|
||||
fig = plt.figure(figsize=(17, 10))
|
||||
spec = fig.add_gridspec(2, 2, width_ratios=[1.15, 1.0], height_ratios=[1.0, 1.25])
|
||||
ax_graph = fig.add_subplot(spec[0, 0])
|
||||
ax_tree = fig.add_subplot(spec[1, 0])
|
||||
draw_base_graph(ax_graph, g, levels, source)
|
||||
draw_base_graph(ax_graph, work_graph, levels, source, augmentation.added_vertices)
|
||||
draw_tire_tree(ax_tree, nodes, tree_edges)
|
||||
draw_medial_tire_grid(fig, spec[:, 1], nodes)
|
||||
fig.suptitle(
|
||||
f"Random 5-connected maximal planar graph {graph_idx}: "
|
||||
f"n={g.number_of_nodes()}, source={source}",
|
||||
f"n={g.number_of_nodes()} (+{len(augmentation.added_vertices)}), "
|
||||
f"source={source}",
|
||||
fontsize=13,
|
||||
)
|
||||
legend = [
|
||||
Line2D([0], [0], marker="o", color="w", label="source",
|
||||
markerfacecolor="#fde68a", markeredgecolor="#dc2626", markersize=8),
|
||||
Line2D([0], [0], marker="o", color="w", label="inserted vertex",
|
||||
markerfacecolor="#fde68a", markeredgecolor="#7c3aed", markersize=8),
|
||||
Line2D([0], [0], color="black", lw=1.3, label="annular cycle A(T)"),
|
||||
Line2D([0], [0], marker="o", color="w", label="up tooth",
|
||||
markerfacecolor="#2563eb", markersize=6),
|
||||
@@ -441,6 +513,7 @@ def draw_case(out_dir: Path, graph_idx: int, g: nx.Graph, source: int):
|
||||
graph_idx,
|
||||
source,
|
||||
g,
|
||||
augmentation,
|
||||
nodes,
|
||||
tree_edges,
|
||||
)
|
||||
@@ -462,7 +535,9 @@ 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(out_dir, i, graph, source)
|
||||
png, pdf, node_count, tire_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}"
|
||||
@@ -479,6 +554,11 @@ def main():
|
||||
parser.add_argument("--scan-limit", type=int, default=500)
|
||||
parser.add_argument("--graph6", help="draw this graph6 graph instead of sampling")
|
||||
parser.add_argument("--source", type=int, help="source vertex for --graph6")
|
||||
parser.add_argument(
|
||||
"--no-augment-same-level-faces",
|
||||
action="store_true",
|
||||
help="skip the same-level-face vertex insertion step",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--out-dir",
|
||||
default=str(PAPER_DIR / "experiments" / "random_medial_tire_decompositions"),
|
||||
|
||||
+11
-7
@@ -1,18 +1,22 @@
|
||||
# Random medial tire decomposition 1
|
||||
|
||||
- vertices: 30
|
||||
- edges: 84
|
||||
- node connectivity: 5
|
||||
- original vertices: 30
|
||||
- original edges: 84
|
||||
- original node connectivity: 5
|
||||
- augmented vertices: 31
|
||||
- augmented edges: 87
|
||||
- same-level faces filled: 1
|
||||
- source vertex: 9
|
||||
- tire-tree nodes: 3
|
||||
- tire-tree edges: 2
|
||||
|
||||
| node | depth | faces | annular | up | down | bites | full medial tires |
|
||||
| node | depth | faces | annular | up | singleton down | bite apexes | full medial tires |
|
||||
|--:|--:|--:|--:|--:|--:|--:|--:|
|
||||
| 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 | 14 | 13 | 12 | 1 | 1 | 2 |
|
||||
| T2.0 | | | | 6 | 2 | (1,5) | `UDUUUDUU` |
|
||||
| T2.1 | | | | 5 | 0 | - | `UUUUU` |
|
||||
| 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` |
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 359 KiB After Width: | Height: | Size: 326 KiB |
+14
-8
@@ -1,17 +1,23 @@
|
||||
# Random medial tire decomposition 2
|
||||
|
||||
- vertices: 30
|
||||
- edges: 84
|
||||
- node connectivity: 5
|
||||
- original vertices: 30
|
||||
- original edges: 84
|
||||
- original node connectivity: 5
|
||||
- augmented vertices: 32
|
||||
- augmented edges: 90
|
||||
- same-level faces filled: 2
|
||||
- source vertex: 4
|
||||
- tire-tree nodes: 3
|
||||
- tire-tree edges: 2
|
||||
- tire-tree nodes: 4
|
||||
- tire-tree edges: 3
|
||||
|
||||
| node | depth | faces | annular | up | down | bites | full medial tires |
|
||||
| node | depth | faces | annular | up | singleton down | bite apexes | full medial tires |
|
||||
|--:|--:|--:|--:|--:|--:|--:|--:|
|
||||
| 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 | 5 | 1 | 1 |
|
||||
| T2.0 | | | | 8 | 6 | (1,5) | `DDUUUDDUUDUDUU` |
|
||||
| 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` |
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 338 KiB After Width: | Height: | Size: 331 KiB |
Reference in New Issue
Block a user