174 """One hyperedge: two tails -> reaction vertex -> one head."""
175 colour = _HYPER[edge_class]
176 tail_xy = [(x0, y + 1.25), (x0, y - 1.25)]
177 sq, hx = x0 + 3.5, x0 + 7.0
179 def vertex(cx, cy, pop, n, side):
180 ax.add_patch(Circle((cx, cy), _EX_R, facecolor=_POP[pop][0],
181 alpha=0.30, edgecolor=
"none", zorder=5))
182 ax.add_patch(Circle((cx, cy), _EX_R, facecolor=
"none",
183 edgecolor=_NODE_RING, lw=_LW_THIN, zorder=6))
184 ax.text(cx, cy, str(n), ha=
"center", va=
"center", color=_INK,
185 fontweight=
"bold", fontsize=_NODE_FONTSIZE, zorder=7)
186 dx = -(_EX_R + 0.3)
if side ==
"left" else (_EX_R + 0.3)
187 ax.text(cx + dx, cy, _POP_SHORT[pop], ha=side
and
188 (
"right" if side ==
"left" else "left"), va=
"center",
189 color=_GREY_TEXT, fontsize=_PLOT_FONTSIZE, zorder=7)
191 for (tx, ty), (pop, n)
in zip(tail_xy, tails):
192 vertex(tx, ty, pop, n,
"left")
193 ax.add_patch(FancyArrowPatch((tx, ty), (sq, y), arrowstyle=
"-|>",
194 mutation_scale=17, lw=_LW_THIN,
195 color=colour, shrinkA=38, shrinkB=14,
197 ax.add_patch(Rectangle((sq - 0.42, y - 0.42), 0.84, 0.84,
198 facecolor=colour, edgecolor=
"none", zorder=6))
199 ax.add_patch(FancyArrowPatch((sq, y), (hx, y), arrowstyle=
"-|>",
200 mutation_scale=20, lw=_LW, color=colour,
201 shrinkA=14, shrinkB=38, zorder=4))
202 vertex(hx, y, head[0], head[1],
"right")
204 ax.text(x0 + 3.5, y + 2.45, f
"{edge_class} · {label}", ha=
"center",
205 va=
"bottom", color=_INK, fontsize=_PLOT_FONTSIZE, zorder=8,
206 bbox=dict(facecolor=
"white", edgecolor=
"none", pad=2.0))
208 ax.text(x0 + 3.5, y - 2.45, note, ha=
"center", va=
"top",
209 color=_GREY_TEXT, fontsize=_PLOT_FONTSIZE, zorder=8,
210 bbox=dict(facecolor=
"white", edgecolor=
"none", pad=2.0))
224 leader_pts = list(zip(tail_xy, tails)) + [((hx, y), tuple(head))]
225 for (cx, cy), (pop, n)
in leader_pts:
226 if pop
not in _LANE_Y
or n > n_max:
232 ax.plot([cx, _X_SCALE * n], [cy - _EX_R, _LANE_Y[pop] + _R],
233 ls=(0, (9, 4)), lw=2.2, color=_INK, alpha=0.95, zorder=2)
237 inp =
InputData(I=200, V=200, physics_option=
"full_CD_fission")
240 G = to_networkx(rag, n_max=n_max, include_binary=
False)
241 Gfull = to_networkx(rag, n_max=n_max, include_binary=
True)
242 n_binary = Gfull.number_of_edges() - G.number_of_edges()
248 pos[node] = (_X_SCALE * n, _LANE_Y[pop])
249 x_lo, x_hi = 0.0, _X_SCALE * (n_max + 1)
250 pos[
"SOURCE"] = (x_lo, _LANE_Y[
"bulk-111"] + 2.7)
251 pos[
"SINK"] = (x_hi, _LANE_Y[
"bulk"] - 2.7)
253 _w = 1.95 * n_max + 8.0
254 fig, ax = plt.subplots(figsize=(_w, _w / 1.42
if examples
else 12.0))
257 for pop, y
in _LANE_Y.items():
258 ax.add_patch(Rectangle((x_lo - 0.6, y - 0.95),
259 x_hi - x_lo + 1.2, 1.9,
260 facecolor=_POP[pop][0], alpha=0.12,
261 edgecolor=
"none", zorder=0))
268 seen: dict[tuple, int] = {}
269 for u, v, d
in G.edges(data=
True):
271 if u
not in pos
or v
not in pos:
275 colour, ls, lw = _EDGE.get(ec, (_INK,
"-", _LW_THIN))
278 if ec
in (
"source",
"sink"):
283 fan_pop = vpop
if ec ==
"source" else upop
284 colour = _FAN_VAC
if fan_pop ==
"bulk" else _FAN_SIA
285 alpha, zorder = 0.55, 1
287 idx = seen.get((u, v), 0)
288 seen[(u, v)] = idx + 1
298 cx, cy, r = x, y - _R - 0.27, 0.27
299 ax.add_patch(Circle((cx, cy), r, fill=
False, ec=
"white",
300 lw=lw + 3.0, zorder=6))
301 ax.add_patch(Circle((cx, cy), r, fill=
False, ec=colour,
302 lw=lw, ls=ls, zorder=7))
303 ax.plot([cx - r], [cy], marker=(3, 0, 90), markersize=8,
304 color=colour, zorder=7)
307 if un
is not None and vn
is not None and upop == vpop:
309 base = 0.24
if vn > un
else -0.24
310 rad = base * (1.0 + 0.62 * idx)
312 rad = 0.10 * (1 + idx)
314 ax.add_patch(FancyArrowPatch(
315 pos[u], pos[v], connectionstyle=f
"arc3,rad={rad}",
316 arrowstyle=
"-|>", mutation_scale=19,
317 shrinkA=24, shrinkB=24,
318 lw=lw, ls=ls, color=colour, alpha=alpha, zorder=zorder))
323 for node, (x, y)
in pos.items():
327 ax.add_patch(Circle((x, y), _R, facecolor=
"white",
328 edgecolor=_NODE_RING, lw=_LW_THIN, zorder=5))
329 ax.text(x, y, str(n), ha=
"center", va=
"center", color=_INK,
330 fontweight=
"bold", fontsize=_NODE_FONTSIZE, zorder=6)
332 for pop, y
in _LANE_Y.items():
333 ax.text(x_lo - 0.9, y, _POP[pop][1], ha=
"right", va=
"center",
334 color=_INK, fontsize=_LABEL_FONTSIZE)
336 _box = dict(boxstyle=
"round,pad=0.45", fc=
"white", ec=_SENTINEL,
338 ax.text(*pos[
"SOURCE"],
"cascade\nsource", ha=
"center", va=
"center",
339 color=_GREY_TEXT, fontsize=_PLOT_FONTSIZE, bbox=_box, zorder=6)
340 ax.text(*pos[
"SINK"],
"sinks\n(dislocations, GBs, precipitates)",
341 ha=
"center", va=
"center", color=_GREY_TEXT,
342 fontsize=_PLOT_FONTSIZE, bbox=_box, zorder=6)
346 Line2D([], [], color=c, ls=ls, lw=max(lw, 2.6),
347 label=name.replace(
"_",
" "))
348 for name, (c, ls, lw)
in _EDGE.items()
349 if name
not in (
"source",
"sink")
353 Line2D([], [], color=_FAN_SIA, ls=
"-", lw=2.0, label=
"source → SIA"),
354 Line2D([], [], color=_FAN_VAC, ls=
"-", lw=2.0, label=
"source → vacancy"),
355 Line2D([], [], color=_FAN_SIA, ls=_dash, lw=2.0, label=
"sink ← SIA"),
356 Line2D([], [], color=_FAN_VAC, ls=_dash, lw=2.0, label=
"sink ← vacancy"),
358 fig.legend(handles=handles, loc=
"lower center",
359 bbox_to_anchor=(0.5, 0.008), frameon=
False,
360 title=
"edge class", ncol=4)
363 ey = _LANE_Y[
"bulk-111"] + 5.6
365 [(
"bulk", 2), (
"bulk-111", 4)], (
"bulk-111", 2))
367 [(
"bulk-111", 4), (
"bulk", 2)], (
"bulk-111", 2),
368 "annihilation", lab_a, n_max,
369 "the larger loop survives, shrunk by\nthe vacancy "
370 "content it absorbed")
372 [(
"bulk-111", 3), (
"bulk-111", 8)],
375 [(
"bulk-111", 3), (
"bulk-111", 8)], (
"bulk-111", 11),
376 "coalescence", lab_c, n_max,
377 f
"head $n{{+}}m=11$ lies beyond the cut-off\n"
378 f
"$n_{{max}}={n_max}$: not an arc in the ladder")
379 ax.set_xlabel(
"cluster size $n$")
380 ax.set_xlim(x_lo - 1.4, x_hi + 1.4)
381 top = _LANE_Y[
"bulk-111"] + (9.6
if examples
else 4.3)
382 ax.set_ylim(_LANE_Y[
"bulk"] - 4.4, top)
383 ax.set_xticks([_X_SCALE * n
for n
in range(1, n_max + 1)])
384 ax.set_xticklabels([str(n)
for n
in range(1, n_max + 1)])
386 for side
in (
"top",
"right",
"left"):
387 ax.spines[side].set_visible(
False)
388 ax.spines[
"bottom"].set_color(_SENTINEL)
389 ax.set_aspect(
"equal")
392 fig.tight_layout(rect=(0.0, 0.15
if examples
else 0.23, 1.0, 1.0))
393 out.parent.mkdir(parents=
True, exist_ok=
True)
394 fig.savefig(out, dpi=_DPI, bbox_inches=
"tight", facecolor=
"white")
395 print(f
"nodes {G.number_of_nodes()} unary arcs {G.number_of_edges()} "
396 f
"binary omitted {n_binary}")
397 print(f
"figure -> {out}")