"""Plot the depth distribution over time for the random n=40 trajectory under the leaf-distance algorithm. Shows whether progress is being made or whether the algorithm is just grinding.""" import os import sys sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import matplotlib.pyplot as plt from leaf_distance_algorithm import compute_x from stress_test_termination import ( compute_depths, apply_switch, check_balanced, face_edges, random_triangulation ) OUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir) def neighbour_at_edge(faces, F_idx, e): for j, fj in enumerate(faces): if j != F_idx and e in face_edges(fj): return j return None def run_with_full_tracking(faces, outer_set, max_steps=3000): """Run algorithm; record at each step: (#faces at each depth), and whether the step was BALANCED or x-preprocess.""" history = [] for step in range(max_steps): depth = compute_depths(faces, outer_set) d_max = max(depth.values()) # Record current state d_dist = {} for d in depth.values(): d_dist[d] = d_dist.get(d, 0) + 1 history.append({'step': step, 'd_max': d_max, 'd_dist': dict(d_dist)}) if d_max == 0: return history, faces max_d_faces = [i for i, d in depth.items() if d == d_max] F_idx = max_d_faces[0] F = faces[F_idx] ok, _, fp_idx, e = check_balanced(F_idx, faces, depth, outer_set) if ok: history[-1]['action'] = 'BALANCED' Fp = faces[fp_idx] u, v = tuple(e) w = [vert for vert in F if vert not in (u, v)][0] x = [vert for vert in Fp if vert not in (u, v)][0] faces = apply_switch(faces, (u, v), (w, x)) continue history[-1]['action'] = 'preprocess' x_vals, _ = compute_x(faces, outer_set) nb_choices = [] for e_test in face_edges(F): if e_test in outer_set: continue nb_idx = neighbour_at_edge(faces, F_idx, e_test) if nb_idx is None: continue nb_choices.append((x_vals[nb_idx], e_test, nb_idx)) if not nb_choices: return history, faces nb_choices.sort() _, e_chosen, nb_idx = nb_choices[0] Fnb = faces[nb_idx] u, v = tuple(e_chosen) w = [vert for vert in F if vert not in (u, v)][0] xv = [vert for vert in Fnb if vert not in (u, v)][0] faces = apply_switch(faces, (u, v), (w, xv)) return history, faces seed = 94476710 outer, _, faces = random_triangulation(40, seed=seed) outer_set = {frozenset(e) for e in outer} print(f'Running algorithm on n=40, seed={seed}...') history, final_faces = run_with_full_tracking(faces, outer_set, max_steps=3000) print(f'finished at step {len(history) - 1}, ' f'final max depth = {history[-1]["d_max"]}') # Plot count of depth-d faces for d = 1 and 2 over time, and mark # balanced vs preprocessing steps fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 7), sharex=True) steps = [h['step'] for h in history] n_d1 = [h['d_dist'].get(1, 0) for h in history] n_d2 = [h['d_dist'].get(2, 0) for h in history] balanced_steps = [h['step'] for h in history if h.get('action') == 'BALANCED'] preprocess_steps = [h['step'] for h in history if h.get('action') == 'preprocess'] ax1.plot(steps, n_d1, color='#d97706', linewidth=1.2, label='depth-1 faces') ax1.plot(steps, n_d2, color='#dc2626', linewidth=1.2, label='depth-2 faces') ax1.set_ylabel('count of faces at depth') ax1.legend(loc='upper right') ax1.set_title(f'n=40 trajectory under leaf-distance algorithm (seed={seed}, ' f'{len(history) - 1} steps)') ax1.grid(alpha=0.3) # Add markers showing balanced vs preprocess at bottom ax2.scatter(balanced_steps, [1] * len(balanced_steps), color='#16a34a', s=8, label=f'BALANCED ({len(balanced_steps)} steps)') ax2.scatter(preprocess_steps, [0] * len(preprocess_steps), color='#3b82f6', s=8, label=f'preprocess ({len(preprocess_steps)} steps)') ax2.set_yticks([0, 1]) ax2.set_yticklabels(['preprocess', 'BALANCED']) ax2.set_xlabel('step') ax2.legend(loc='upper right') ax2.grid(alpha=0.3) fig.tight_layout() out = os.path.join(OUT_DIR, 'fig_n40_trajectory.png') fig.savefig(out, dpi=150, bbox_inches='tight') plt.close(fig) print(f'wrote {out}') # Summary print(f'\n#BALANCED switches: {len(balanced_steps)}') print(f'#preprocess switches: {len(preprocess_steps)}') print(f'Max depth-1 count seen: {max(n_d1)}') print(f'Max depth-2 count seen: {max(n_d2)}')