diff --git a/papers/medial_tire_cuts/experiments/medial_tire_dual_cut_experiment.py b/papers/medial_tire_cuts/experiments/medial_tire_dual_cut_experiment.py index 9cf0ca9..883c0f7 100644 --- a/papers/medial_tire_cuts/experiments/medial_tire_dual_cut_experiment.py +++ b/papers/medial_tire_cuts/experiments/medial_tire_dual_cut_experiment.py @@ -113,9 +113,9 @@ def source_dual(G, faces): return D -def removed_dual_edges(results, cap_cuts): - """The set of primal edges whose dual edge a cut removes (cap cut plus every - tread cut).""" +def annular_cut_edges(results, cap_cuts): + """Primal edges whose dual edge a *closing* cut removes: the cap cut plus + each tread's annular-vertex duplications.""" removed = set() for c in cap_cuts or []: removed.add(c["medial_vertex"]) @@ -127,6 +127,27 @@ def removed_dual_edges(results, cap_cuts): return removed +def up_apex_cut_edges(results): + """Primal edges whose dual edge the apex duplications remove: the apex + medial vertex of every (singleton) up tooth across all treads, except the + entry tooth of each tread (its apex is not duplicated).""" + removed = set() + for d in sorted(results): + g, bij = results[d]["g"], results[d]["bij"] + entry = results[d]["entry_edge"] + for i in g.up_edges: + if i == entry: + continue + removed.add(bij[f"u{i}"]) + return removed + + +def removed_dual_edges(results, cap_cuts): + """All primal edges whose dual edge the cut removes: the closing annular + cuts together with the up-tooth apex duplications.""" + return annular_cut_edges(results, cap_cuts) | up_apex_cut_edges(results) + + def dual_face_missing(G, removed): """For each dual face (vertex ``v`` of ``G``), the number of bounding dual edges removed by the cut.""" @@ -164,7 +185,9 @@ def medial_tire_dual_cut(G, source, entry_edge): cut_graph, labels, warnings = _assemble_cut_graph(M, results, cap_cuts=cap_cuts) dual = source_dual(G, faces) - removed = removed_dual_edges(results, cap_cuts) + annular = annular_cut_edges(results, cap_cuts) + apex = up_apex_cut_edges(results) + removed = annular | apex missing = dual_face_missing(G, removed) return { @@ -173,7 +196,9 @@ def medial_tire_dual_cut(G, source, entry_edge): "levels": levels, "treads": treads, "skipped": skipped, "results": results, "cap_cuts": cap_cuts, "cut_graph": cut_graph, "labels": labels, "warnings": warnings, - "dual": dual, "removed_dual_edges": removed, "dual_face_missing": missing, + "dual": dual, "removed_dual_edges": removed, + "annular_cut_edges": annular, "apex_cut_edges": apex, + "dual_face_missing": missing, "max_missing": max(missing.values()) if missing else 0, } @@ -237,7 +262,10 @@ def summary(result): f"recognised treads: {sorted(result['treads'])} " f"skipped: {result['skipped']}", f"removed source-dual edges ({len(removed)}): " - f"{sorted(removed)}", + f"{len(result['annular_cut_edges'])} annular/cap + " + f"{len(result['apex_cut_edges'])} up-tooth apex", + f" annular/cap: {sorted(result['annular_cut_edges'])}", + f" up apexes: {sorted(result['apex_cut_edges'])}", f"dual-face missing-edge histogram (count by #removed around the dual " f"face): {dict(sorted(hist.items()))} max={result['max_missing']}", ] @@ -378,7 +406,7 @@ def _draw_tread(ax, g, depth, cuts, entry_edge, title): lx, ly = ax_ + 0.5 * (mx - ax_), ay + 0.5 * (my - ay) ax.text(lx, ly, str(depth[edge]), fontsize=7, fontweight="bold", ha="center", va="center", zorder=4) - # cut slits + # annular-vertex cut slits (radial) for c in cuts or []: if c.vertex is None: continue @@ -390,6 +418,17 @@ def _draw_tread(ax, g, depth, cuts, entry_edge, title): ax.text(vx + 0.34 * math.cos(rad), vy + 0.34 * math.sin(rad), f"cut {c.order + 1}", fontsize=6, color="#cc2020", ha="center", va="center", zorder=5) + # up-tooth apex duplications (slit tangential, across the apex marker); + # the entry tooth's apex is not duplicated + for i in g.up_edges: + if i == entry_edge: + continue + vx, vy = pos[f"u{i}"] + rad = math.atan2(vy, vx) + tx, ty = -math.sin(rad), math.cos(rad) # tangential + ax.plot([vx - 0.12 * tx, vx + 0.12 * tx], + [vy - 0.12 * ty, vy + 0.12 * ty], + color="#cc2020", lw=2.0, zorder=6) # entry marker if entry_edge is not None: ex, ey = pos[g.apex_of_edge(entry_edge)]