1import matplotlib.pyplot
as plt
2import matplotlib.patches
as mpatches
3from matplotlib.patches
import FancyBboxPatch, FancyArrowPatch
4import matplotlib.patheffects
as pe
8fig, ax = plt.subplots(figsize=(28, 21))
12fig.patch.set_facecolor(
'white')
39C_SRC_ARROW =
'#90A4AE'
45def circle(ax, cx, cy, r, color, zorder=3):
46 c = plt.Circle((cx, cy), r, color=color, zorder=zorder, linewidth=0)
49def node_text(ax, cx, cy, txt, fs=FS_NODE, color='white', zorder=4):
50 ax.text(cx, cy, txt, ha=
'center', va=
'center', fontsize=fs,
51 color=color, fontweight=
'bold', zorder=zorder)
53def fancy_box(ax, x, y, w, h, fc, ec='white', lw=2.0, radius=0.15, zorder=2):
54 p = FancyBboxPatch((x, y), w, h,
55 boxstyle=f
"round,pad={radius}",
56 facecolor=fc, edgecolor=ec, linewidth=lw, zorder=zorder)
59def arr(ax, x1, y1, x2, y2, color, lw=2.2, ls='-', rad=0.0, zorder=2):
60 cs = f
'arc3,rad={rad}' if rad != 0
else 'arc3,rad=0'
61 ax.annotate(
'', xy=(x2, y2), xytext=(x1, y1),
62 arrowprops=dict(arrowstyle=
'->', color=color, lw=lw,
68 fancy_box(ax, x, y, w, h, fc=fc, ec=
'white', lw=2.0)
69 ax.text(x+w/2, y+h*0.65, title, ha=
'center', va=
'center',
70 fontsize=FS_BOX, color=
'white', fontweight=
'bold')
71 ax.text(x+w/2, y+h*0.25, sub, ha=
'center', va=
'center',
72 fontsize=FS_LABEL, color=
'white')
79 'EUROFER97 Reaction Network — Graph-Walker Representation',
80 ha=
'center', va=
'center', fontsize=FS_TITLE, fontweight=
'bold')
82 r'$c_n$: SIA clusters, $c_{m,\ell}$: cavities, $c_h$: free He',
83 ha=
'center', va=
'center', fontsize=FS_SUBT)
91 'Displacement cascade',
r'$G_n,\ G_{m,\ell}$')
95 'He production / transmutation',
r'$G_{\rm He}$')
98fancy_box(ax, 8.8, 17.8, 10.3, 1.7, fc=
'#FFF9C4', ec=
'#F57F17', lw=2.5)
100 'Graph Walker (abstract master equation)',
101 ha=
'center', va=
'center', fontsize=FS_LABEL, fontweight=
'bold',
104 r'$\dfrac{dc_a}{dt} = G_a + \sum_{r\in\{P1\ldots P8\}}'
105 r'S_{ad}\,r_r(\mathbf{c}) - D_a c_a$',
106 ha=
'center', va=
'center', fontsize=FS_EQ)
114SIA_X = [1.4, 3.2, 5.0, 6.8]
115SIA_L = [
r'$c_1$',
r'$c_2$',
r'$c_3$',
r'$c_n$']
117ax.text(0.3, 16.2,
r'$c_I\;(\sigma=+1)$',
118 ha=
'left', va=
'center', fontsize=FS_LABEL+1,
119 color=C_SIA, fontweight=
'bold')
121for x, lbl
in zip(SIA_X, SIA_L):
122 circle(ax, x, SIA_Y, NR, C_SIA)
126ax.text(5.9, SIA_Y,
r'$\cdots$', ha=
'center', va=
'center',
127 fontsize=22, color=C_SIA, zorder=3)
130for i
in range(len(SIA_X)-1):
131 x1 = SIA_X[i] + NR + 0.05
132 x2 = SIA_X[i+1] - NR - 0.05
134 x1 += 0.25; x2 -= 0.1
135 arr(ax, x1, SIA_Y+0.18, x2, SIA_Y+0.18, C_P3, lw=2.5)
137ax.text(3.2, SIA_Y+1.15,
138 r'$P3:\ \kappa_{\ell\ell}^{loop}c_n c_1$',
139 ha=
'center', va=
'center', fontsize=FS_LABEL,
140 color=C_P3, fontweight=
'bold',
141 bbox=dict(boxstyle=
'round,pad=0.25', fc=
'white', ec=C_P3, lw=1.8))
144for i
in range(1, len(SIA_X)):
145 x1 = SIA_X[i] - NR - 0.05
146 x2 = SIA_X[i-1] + NR + 0.05
148 x1 -= 0.1; x2 += 0.25
149 arr(ax, x1, SIA_Y-0.20, x2, SIA_Y-0.20, C_P5, lw=2.0, ls=
'dashed')
151ax.text(4.0, SIA_Y-1.1,
152 r'$P5i:\ \varepsilon_I(q)c_n$',
153 ha=
'center', va=
'center', fontsize=FS_LABEL,
154 color=C_P5, fontweight=
'bold',
155 bbox=dict(boxstyle=
'round,pad=0.25', fc=
'white', ec=C_P5, lw=1.8))
161HE_X, HE_Y = 10.5, 14.5
162fancy_box(ax, HE_X-1.2, HE_Y-0.82, 2.4, 1.65,
163 fc=C_HE, ec=
'white', lw=2.5, zorder=3)
164ax.text(HE_X, HE_Y+0.22,
r'$c_h$',
165 ha=
'center', va=
'center', fontsize=FS_NODE+4,
166 color=
'white', fontweight=
'bold', zorder=5)
167ax.text(HE_X, HE_Y-0.40,
'(free He)',
168 ha=
'center', va=
'center', fontsize=FS_LABEL-1,
169 color=
'white', zorder=5)
176CAV_MX = [13.5, 17.0, 22.5]
177CAV_LY = [14.5, 12.0, 9.5]
178M_LABELS = [
'm=1',
'm=2',
'm']
179L_LABELS = [
r'$\ell=0$',
r'$\ell=1$',
r'$\ell=2$']
181ax.text(12.5, 16.3,
r'$c_V\;(\sigma=-1)$',
182 ha=
'left', va=
'center', fontsize=FS_LABEL+1,
183 color=C_CAV, fontweight=
'bold')
185for j, (mx, ml)
in enumerate(zip(CAV_MX, M_LABELS)):
186 ax.text(mx, 15.55, ml, ha=
'center', va=
'center',
187 fontsize=FS_LABEL, color=C_CAV, fontweight=
'bold')
189for i, (ly, ll)
in enumerate(zip(CAV_LY, L_LABELS)):
190 ax.text(12.0, ly, ll, ha=
'center', va=
'center',
191 fontsize=FS_LABEL, color=C_CAV)
194for i, ly
in enumerate(CAV_LY):
195 for j, mx
in enumerate(CAV_MX):
196 m_str = str(j+1)
if j < 2
else 'm'
197 lbl = rf
'$c_{{{m_str},{i}}}$'
198 circle(ax, mx, ly, CR, C_CAV)
203 ax.text(19.8, ly,
r'$\cdots$', ha=
'center', va=
'center',
204 fontsize=22, color=C_CAV, zorder=3)
206ax.plot([CAV_MX[2]+CR, CAV_MX[2]+1.4], [CAV_LY[0], CAV_LY[0]],
207 ':', color=C_CAV, lw=2.2)
210arr(ax, CAV_MX[0]+CR+0.05, CAV_LY[0]+0.18,
211 CAV_MX[1]-CR-0.05, CAV_LY[0]+0.18, C_P2V, lw=2.5)
212arr(ax, CAV_MX[1]+CR+0.05, CAV_LY[0]+0.18,
213 CAV_MX[2]-CR-0.9, CAV_LY[0]+0.18, C_P2V, lw=2.5)
216 r'$P2v:\ \kappa_{V,m}^{cav}c_{1,0}\,\tilde{c}_m$',
217 ha=
'center', va=
'center', fontsize=FS_LABEL, color=C_P2V,
219 bbox=dict(boxstyle=
'round,pad=0.25', fc=
'white', ec=C_P2V, lw=1.8))
223 for i
in range(len(CAV_LY)-1):
224 y1 = CAV_LY[i] - CR - 0.05
225 y2 = CAV_LY[i+1] + CR + 0.05
226 arr(ax, mx-0.12, y1, mx-0.12, y2, C_P2H, lw=2.5)
229 r'$P2h:\ \kappa_{h,m}^{cav}c_h\,c_{m,\ell}$',
230 ha=
'left', va=
'center', fontsize=FS_LABEL, color=C_P2H,
231 bbox=dict(boxstyle=
'round,pad=0.25', fc=
'white', ec=C_P2H, lw=1.8))
233 'P2h: He trap\nP5h+P8: emit/resolute',
234 ha=
'left', va=
'center', fontsize=FS_LABEL-1, color=C_P2H)
238 for i
in range(1, len(CAV_LY)):
239 y1 = CAV_LY[i] + CR + 0.05
240 y2 = CAV_LY[i-1] - CR - 0.05
241 arr(ax, mx+0.12, y1, mx+0.12, y2, C_P5, lw=2.0, ls=
'dashed')
244 r'$P5v:\ \varepsilon_v(m,\ell)c_{m,\ell}$',
245 ha=
'center', va=
'center', fontsize=FS_LABEL, color=C_P5,
246 bbox=dict(boxstyle=
'round,pad=0.25', fc=
'white', ec=C_P5, lw=1.8))
249arr(ax, HE_X+1.2, HE_Y+0.25, CAV_MX[0]-CR-0.05, CAV_LY[0]+0.20,
252 r'$P2i/P6:\ \kappa_{i,m}^{cav}c_n\,c_m$',
253 ha=
'center', va=
'center', fontsize=FS_LABEL, color=C_P2I,
255 bbox=dict(boxstyle=
'round,pad=0.25', fc=
'#E8F5E9', ec=C_P2I, lw=1.8))
258arr(ax, SIA_X[0]+NR*0.7, SIA_Y-NR*0.7,
259 CAV_MX[0]-CR*0.7, CAV_LY[0]-CR*0.7,
260 C_P1, lw=2.5, rad=0.15)
262 r'$P1:\ \mathcal{K}_{iV}c_1 c_{1,0}$',
263 ha=
'center', va=
'center', fontsize=FS_LABEL, color=C_P1,
264 bbox=dict(boxstyle=
'round,pad=0.25', fc=
'white', ec=C_P1, lw=1.8))
268ax.annotate(
'', xy=(SIA_X[0]-NR*0.5, SIA_Y-NR*0.85),
269 xytext=(CAV_MX[0]+CR*0.5, CAV_LY[1]-CR*0.5),
270 arrowprops=dict(arrowstyle=
'->', color=C_P7, lw=2.2,
271 connectionstyle=
'arc3,rad=-0.35'),
274 r'$P7:\ \Gamma_{TM}c_{m,\ell}\!\to\!I_1$',
275 ha=
'center', va=
'center', fontsize=FS_LABEL, color=C_P7,
276 bbox=dict(boxstyle=
'round,pad=0.25', fc=
'white', ec=C_P7, lw=1.8))
283ax.annotate(
'', xy=(SIA_X[1], SIA_Y+NR),
285 arrowprops=dict(arrowstyle=
'->', color=C_SRC_ARROW, lw=1.8,
287 connectionstyle=
'arc3,rad=0.0'), zorder=5)
289ax.annotate(
'', xy=(CAV_MX[0], CAV_LY[0]+CR),
291 arrowprops=dict(arrowstyle=
'->', color=C_SRC_ARROW, lw=1.8,
293 connectionstyle=
'arc3,rad=-0.15'), zorder=5)
295ax.annotate(
'', xy=(HE_X, HE_Y+0.83),
297 arrowprops=dict(arrowstyle=
'->', color=C_SRC_ARROW, lw=1.8,
299 connectionstyle=
'arc3,rad=0.2'), zorder=5)
310 'Dislocation network',
r'$D_\alpha^d$', fc=C_SINK)
311labeled_box(ax, 10.9, SINK_Y-SINK_H, SINK_W, SINK_H,
312 'Grain boundaries',
r'$D_\alpha^{gb}$', fc=C_SINK)
313labeled_box(ax, 21.2, SINK_Y-SINK_H, SINK_W, SINK_H,
314 'Precipitates',
r'$D_\alpha^p$', fc=C_SINK)
318 r'$P4:\ D_\alpha c_\alpha$',
319 ha=
'center', va=
'center', fontsize=FS_LABEL+1, color=C_P4,
323sink_centres = [3.7, 13.8, 24.1]
325 [(x, SIA_Y-NR)
for x
in SIA_X] +
326 [(HE_X, HE_Y-0.83)] +
327 [(mx, CAV_LY[-1]-CR)
for mx
in CAV_MX]
330 [(3.7, SINK_Y-SINK_H)] * 4 +
331 [(13.8, SINK_Y-SINK_H)] +
332 [(24.1, SINK_Y-SINK_H)] * 3
334for (x1,y1),(x2,y2)
in zip(source_nodes, sink_targets):
335 arr(ax, x1, y1, x2, y2, C_P4, lw=1.5, ls=
'dashed', zorder=1)
347 (C_P3,
'-',
r'P3: loop growth $I_{n-1}+I_1\to I_n$'),
348 (C_P5,
'--',
r'P5: SIA emission (inverse of P3)'),
349 (C_P1,
'-',
r'P1: V-I recombination $I_1+V_1\to\emptyset$'),
353 (C_P2V,
'-',
r'P2v: cavity growth $v+V_m\to V_{m+1}$'),
354 (C_P2H,
'-',
r'P2h: He trapping $c_h+V_{m,\ell}\to V_{m,\ell+1}$'),
355 (C_P2I,
'-',
r'P2i/P6: SIA absorption by cavity'),
359 (C_P7,
'-',
r'P7: trap mutation $V_{m,\ell}\to V_{m+1,\ell}+I_1$'),
360 (C_P8,
'--',
r'P8: radiation re-solution'),
361 (C_P4,
'--',
r'P4: absorption at fixed sinks $D_\alpha c_\alpha$'),
364col_x = [0.5, 10.6, 20.9]
366ax.text(col_x[0], KY,
'Process key',
367 fontsize=FS_KEY_HDR, fontweight=
'bold', va=
'top')
370for col_idx, entries
in enumerate(key_columns):
372 for k, (color, ls, label)
in enumerate(entries):
373 y_row = KY - 0.55 - k * ROW_H
376 ax.plot([lx, lx+0.9], [y_row, y_row],
377 color=color, lw=2.2, linestyle=
'dashed')
379 ax.annotate(
'', xy=(lx+0.9, y_row), xytext=(lx, y_row),
380 arrowprops=dict(arrowstyle=
'->', color=color, lw=2.2))
381 ax.text(lx+1.15, y_row, label,
382 va=
'center', fontsize=FS_KEY, color=
'black')
390module_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
391out_dir = os.path.join(module_root,
'output',
'schematics')
392os.makedirs(out_dir, exist_ok=
True)
394out_path = os.path.join(out_dir,
'eurofer_reaction_network.png')
395old_path = os.path.join(out_dir,
'eurofer_reaction_network_old.png')
397if os.path.exists(out_path):
398 shutil.move(out_path, old_path)
400plt.savefig(out_path, dpi=200, bbox_inches=
'tight',
401 facecolor=
'white', edgecolor=
'none')
403print(
"PNG saved to", out_path)
arr(ax, x1, y1, x2, y2, color, lw=2.2, ls='-', rad=0.0, zorder=2)
node_text(ax, cx, cy, txt, fs=FS_NODE, color='white', zorder=4)
fancy_box(ax, x, y, w, h, fc, ec='white', lw=2.0, radius=0.15, zorder=2)
labeled_box(ax, x, y, w, h, title, sub, fc=C_SRC)
circle(ax, cx, cy, r, color, zorder=3)