Add walk-distance labelling to source-dual cut
Label each dual face by its distance, within the source-dual cut, from the first entry's cap down tooth. Regenerate the seed 1 and seed 2 full walk figures and metadata with the new labelling. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -5,13 +5,24 @@
|
||||
- deep-embedded edges: 84
|
||||
- graph seed: 59
|
||||
- deep-embedded minimum degree: 3
|
||||
- chosen face: (8, 9, 19)
|
||||
- chosen source cap vertex: 24
|
||||
- root entry tooth: e2 (apex medial vertex = level-1 edge (19, 8))
|
||||
- recognised treads: 11
|
||||
- skipped treads: [(0, 'only 0 up teeth')]
|
||||
- skipped treads: [((0, 0), 'only 0 up teeth')]
|
||||
- removed source-dual edges: 29
|
||||
- annular/cap cuts: 12
|
||||
- up-apex cuts: 17
|
||||
|
||||
- **source-dual cut is a tree: True** (56 dual faces, 55 edges, 1 component(s), acyclic=True)
|
||||
|
||||
## Walk-distance labelling of the source-dual cut
|
||||
|
||||
Each dual face (vertex of the source-dual cut) is labelled by its distance, within the cut, from the **cap down tooth of the first entry**: the triangular face `(8, 24, 19)` = `{source 24, edge (19, 8)}` (dual node 34).
|
||||
|
||||
- maximum distance: 21
|
||||
- distance histogram (faces by distance): `{0: 1, 1: 2, 2: 2, 3: 2, 4: 2, 5: 2, 6: 3, 7: 3, 8: 4, 9: 4, 10: 4, 11: 4, 12: 3, 13: 3, 14: 2, 15: 2, 16: 1, 17: 2, 18: 3, 19: 4, 20: 2, 21: 1}`
|
||||
|
||||
- dual cut figure: `full_medial_tire_cut_walk_1_dual.png`
|
||||
- tire cut grid: `full_medial_tire_cut_walk_1_tires.png`
|
||||
- combined PDF: `full_medial_tire_cut_walk_1.pdf`
|
||||
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 282 KiB After Width: | Height: | Size: 310 KiB |
@@ -7,7 +7,7 @@
|
||||
- deep-embedded minimum degree: 3
|
||||
- chosen face: (4, 12, 11)
|
||||
- chosen source cap vertex: 20
|
||||
- root entry tooth: e3
|
||||
- root entry tooth: e3 (apex medial vertex = level-1 edge (11, 4))
|
||||
- recognised treads: 3
|
||||
- skipped treads: [((0, 0), 'only 0 up teeth')]
|
||||
- removed source-dual edges: 20
|
||||
@@ -16,6 +16,13 @@
|
||||
|
||||
- **source-dual cut is a tree: True** (38 dual faces, 37 edges, 1 component(s), acyclic=True)
|
||||
|
||||
## Walk-distance labelling of the source-dual cut
|
||||
|
||||
Each dual face (vertex of the source-dual cut) is labelled by its distance, within the cut, from the **cap down tooth of the first entry**: the triangular face `(4, 20, 11)` = `{source 20, edge (11, 4)}` (dual node 15).
|
||||
|
||||
- maximum distance: 17
|
||||
- distance histogram (faces by distance): `{0: 1, 1: 2, 2: 2, 3: 2, 4: 2, 5: 3, 6: 3, 7: 3, 8: 3, 9: 3, 10: 4, 11: 3, 12: 2, 13: 1, 14: 1, 15: 1, 16: 1, 17: 1}`
|
||||
|
||||
- dual cut figure: `full_medial_tire_cut_walk_2_dual.png`
|
||||
- tire cut grid: `full_medial_tire_cut_walk_2_tires.png`
|
||||
- combined PDF: `full_medial_tire_cut_walk_2.pdf`
|
||||
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 205 KiB |
@@ -221,6 +221,44 @@ def dual_face_missing(G, removed):
|
||||
for v in G.nodes()}
|
||||
|
||||
|
||||
def dual_cut_graph(result):
|
||||
"""The source-dual cut itself: the dual graph with every removed (cut) dual
|
||||
edge deleted. This is the graph the walk distances are read on."""
|
||||
D = result["dual"].copy()
|
||||
removed = result["removed_dual_edges"]
|
||||
D.remove_edges_from([(u, v) for u, v, d in D.edges(data=True)
|
||||
if d["primal"] in removed])
|
||||
return D
|
||||
|
||||
|
||||
def entry_down_tooth_face(result):
|
||||
"""Index of the cap down tooth of the first entry: the triangular face
|
||||
``{source, x, y}`` where ``(x, y)`` is the first entry medial vertex (the
|
||||
root tread's entry up-tooth apex, a level-1 edge that is a down tooth of the
|
||||
source cap). This dual node roots the walk-distance labelling. Returns
|
||||
None if no such face exists."""
|
||||
em = result.get("entry_medial_vertex")
|
||||
if em is None:
|
||||
return None
|
||||
target = frozenset((result["source"], em[0], em[1]))
|
||||
for fi, f in enumerate(result["faces"]):
|
||||
if frozenset(f) == target:
|
||||
return fi
|
||||
return None
|
||||
|
||||
|
||||
def dual_cut_distances(result):
|
||||
"""Distance of every dual face from the first entry's cap down tooth,
|
||||
measured in the source-dual cut.
|
||||
|
||||
Returns ``(dist, root)`` where ``dist`` maps each reachable dual face to its
|
||||
distance and ``root`` is the rooting dual face (or None if absent)."""
|
||||
root = entry_down_tooth_face(result)
|
||||
if root is None:
|
||||
return {}, None
|
||||
return nx.single_source_shortest_path_length(dual_cut_graph(result), root), root
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# The four chained entry points.
|
||||
# --------------------------------------------------------------------------- #
|
||||
@@ -420,6 +458,7 @@ def _draw_dual_cut_ax(ax, result):
|
||||
missing = result["dual_face_missing"]
|
||||
source = result["source"]
|
||||
entry_medial = result.get("entry_medial_vertex")
|
||||
dist, root = dual_cut_distances(result)
|
||||
pos_v = _radial_source_layout(G, source, result["levels"])
|
||||
|
||||
def centroid(fi):
|
||||
@@ -454,9 +493,20 @@ def _draw_dual_cut_ax(ax, result):
|
||||
color="0.80" if cut else "0.25",
|
||||
lw=1.0 if cut else 1.3,
|
||||
linestyle=(0, (2, 2)) if cut else "solid", zorder=1)
|
||||
# dual nodes labelled by walk distance from the first entry's cap down
|
||||
# tooth; the rooting dual face is gold.
|
||||
for fi in dual.nodes():
|
||||
x, y = pos[fi]
|
||||
ax.plot(x, y, "o", ms=4, color="#3a6ea5", zorder=3)
|
||||
d = dist.get(fi)
|
||||
if fi == root:
|
||||
ax.plot(x, y, "o", ms=12, mfc="#ffd24d", mec="#b8860b",
|
||||
mew=1.3, zorder=3)
|
||||
else:
|
||||
ax.plot(x, y, "o", ms=10, mfc="white", mec="#3a6ea5",
|
||||
mew=0.8, zorder=3)
|
||||
ax.text(x, y, str(d) if d is not None else "∞",
|
||||
color="#13335c", fontsize=5.5, fontweight="bold",
|
||||
ha="center", va="center", zorder=4)
|
||||
# label each source-graph vertex by its id; the cap source is flagged.
|
||||
for v in G.nodes():
|
||||
x, y = pos_v[v]
|
||||
@@ -475,12 +525,15 @@ def _draw_dual_cut_ax(ax, result):
|
||||
ha="left", va="bottom", zorder=7,
|
||||
bbox=dict(boxstyle="circle,pad=0.05", fc="white",
|
||||
ec="#b03030", lw=0.6))
|
||||
root_face = result["faces"][root] if root is not None else None
|
||||
max_dist = max(dist.values()) if dist else 0
|
||||
ax.set_title(f"source-dual cut (cap source {source}, entry "
|
||||
f"e{result['entry_edge']} = medial vtx {entry_medial}); "
|
||||
f"gray = edges missing after cuts\n"
|
||||
f"green star = first entry medial vertex; red numbers = "
|
||||
f"#missing dual edges around each dual face; "
|
||||
f"max {result['max_missing']}", fontsize=9)
|
||||
f"blue dual-node numbers = distance from gold root "
|
||||
f"(cap down tooth {root_face} of first entry; max {max_dist}); "
|
||||
f"green star = first entry; red = #missing dual edges per face",
|
||||
fontsize=9)
|
||||
ax.set_aspect("equal")
|
||||
ax.axis("off")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user