Extend outer mu branches so the trajectory reaches the centre

x(mu) only approaches the centre as |mu| -> inf, so the sampling in
[mean(poles) - 3, mean(poles) + 3] left a gap around it. The outer
branches now continue geometrically up to |mu| ~ 1e4 / lambda_min; the
extra samples lie outside the mu axes and only show in the trajectory.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
loicvhandClaude Opus 5 committed 2026-09-15 21:00:33 +02:00
1 parent 72c19852d8
commit 55cb27c8de
1 file changed
+14 -5
+14 -5
View File
@@ -26,6 +26,7 @@ CASES = {
} }
N_PER_BRANCH = 266 # mu samples between consecutive poles N_PER_BRANCH = 266 # mu samples between consecutive poles
N_TAIL = 100 # extra samples extending the outer branches geometrically up to |mu| ~ 1e4 / lambda_min
EPS = 0.01 # gap left on each side of a pole EPS = 0.01 # gap left on each side of a pole
MU_HALF_WIDTH = 3 # mu is sampled in [m - MU_HALF_WIDTH, m + MU_HALF_WIDTH], m the mean of the poles MU_HALF_WIDTH = 3 # mu is sampled in [m - MU_HALF_WIDTH, m + MU_HALF_WIDTH], m the mean of the poles
N_DOTTED_OUTER = 2 # points drawn dotted at the ends of the outer branches N_DOTTED_OUTER = 2 # points drawn dotted at the ends of the outer branches
@@ -68,11 +69,19 @@ def plot_trajectories(Q, x0):
mu_min = poles.mean() - MU_HALF_WIDTH mu_min = poles.mean() - MU_HALF_WIDTH
mu_max = poles.mean() + MU_HALF_WIDTH mu_max = poles.mean() + MU_HALF_WIDTH
mu = np.concatenate([np.linspace(lo, hi, N_PER_BRANCH) for lo, hi in # x(mu) only reaches the centre as |mu| -> inf, so the outer branches continue geometrically
[(mu_min, poles[0] - EPS), (poles[0] + EPS, poles[1] - EPS), (poles[1] + EPS, mu_max)]]) # beyond [mu_min, mu_max]; these samples show up in the trajectory but not on the mu axes.
tail = np.geomspace(MU_HALF_WIDTH / N_PER_BRANCH, 1e4 / Q.eig.min(), N_TAIL)
branch_samples = [
np.concatenate((mu_min - tail[::-1], np.linspace(mu_min, poles[0] - EPS, N_PER_BRANCH))),
np.linspace(poles[0] + EPS, poles[1] - EPS, N_PER_BRANCH),
np.concatenate((np.linspace(poles[1] + EPS, mu_max, N_PER_BRANCH), mu_max + tail)),
]
mu = np.concatenate(branch_samples)
idx = np.arange(len(mu)) idx = np.arange(len(mu))
pole_indices = np.cumsum([len(samples) for samples in branch_samples[:-1]])
# A pole where x0 has a zero standardized component vanishes and joins its neighbouring branches. # A pole where x0 has a zero standardized component vanishes and joins its neighbouring branches.
branches = np.split(idx, [k * N_PER_BRANCH for k in (1, 2) if pole_active[k - 1]]) branches = np.split(idx, pole_indices[pole_active])
cmap = colormaps[COLORMAP] cmap = colormaps[COLORMAP]
norm = Normalize(0, len(mu) - 1) norm = Normalize(0, len(mu) - 1)
@@ -115,8 +124,8 @@ def plot_trajectories(Q, x0):
ax_x.scatter(*Q.x(root, x0), **style) ax_x.scatter(*Q.x(root, x0), **style)
if not degenerate: if not degenerate:
for k, label in zip((1, 2), POLE_LABELS): for pole_index, label in zip(pole_indices, POLE_LABELS):
for i in (k * N_PER_BRANCH - 1, k * N_PER_BRANCH): for i in (pole_index - 1, pole_index):
ax_x.text(*xs[i], label, **TEXT_STYLE) ax_x.text(*xs[i], label, **TEXT_STYLE)
for k in range(Q.dim): for k in range(Q.dim):
point, direction = Q.pole_line(x0, k) point, direction = Q.pole_line(x0, k)