3Z_ii sweep — the SIA-SIA coalescence bias factor.
5Z_ii multiplies the full K_ii_coal rate (SIA absorption by SIA loops)
6but does NOT affect vacancy absorption. This makes it the primary knob
7for shifting the growth/shrink balance.
9With corrected loop geometry (A_loop * n^{1/2}):
10 growth_ratio = Z_ii * Z_i_loop * Di * c_i1 / (Dv * c_v1)
12Baseline ratio ≈ 0.742 at Z_ii=1.2. Need ratio > 1.0 for net growth.
13Estimated crossover: Z_ii ≈ 1.6-1.7.
16import sys, os, io, time
17from pathlib
import Path
21import matplotlib.pyplot
as plt
23MODULE_ROOT = Path(__file__).resolve().parent.parent.parent
24REPO_ROOT = MODULE_ROOT.parent
25for p
in [str(REPO_ROOT), str(MODULE_ROOT)]:
30import radcluster_code.py_utils.defect_production
as _dp_mod
31import radcluster_code.py_utils.binding_energies
as _be_mod
32import radcluster_code.py_utils.bin_moment_rates
as _bmr
33import radcluster_code.py_utils.input_data
as _inp_mod
34import radcluster_code.py_utils.reaction_rates
as _rr_mod
35import radcluster_code.py_utils.rate_equations
as _re_mod
36import radcluster_code.py_utils.cpp_bridge
as _cb_mod
37import radcluster_code.py_utils.post_process
as _pp_mod
38import radcluster_code.py_utils.simulation
as _sim_mod
39for _m
in [_dp_mod, _be_mod, _bmr, _inp_mod, _rr_mod, _re_mod,
40 _cb_mod, _pp_mod, _sim_mod]:
42from radcluster_code.py_utils.simulation
import RadClusterSimulation
43from radcluster_code.py_utils.defect_production
import FISSION
as _FISSION_DICT
48 return 2.0 * np.sqrt(np.maximum(n, 1) * Omega / (np.pi * b_111)) * 1e9
50I_SIM, V_SIM = 3000, 3000
52 't_span': (1e-6, 1e5),
60 'concentration_threshold': 1e-18,
'window_pad': 10,
65 'eta': 0.30,
'f_cl_i': 0.30,
'f_cl_v': 0.25,
66 'E_m_1D': 0.4,
'i_mobile': 10,
'L_hat': 71.8,
67 'c_C': 1.94e-4,
'E_b_C_SIA': 0.65,
68 'rho_d': 1e13,
'Z_i': 1.05,
69 'shape_function':
'linear',
75 params[
'Z_ii'] = Z_ii_val
77 fission_bak = dict(_FISSION_DICT)
78 for key
in [
'eta',
'f_cl_i',
'f_cl_v']:
80 _FISSION_DICT[key] = params[key]
82 saved = sys.stdout, sys.stderr
83 sys.stdout = sys.stderr = io.StringIO()
85 sim = RadClusterSimulation(
87 solver_mode=
'active_window', physics_option=
'full_CD_fission',
88 C_floor=1e-25, he_kinetics=
'quasi_steady_state',
89 i_mobile=10, v_mobile=5,
92 for key, val
in params.items():
94 for d
in [inp.production_fission, inp.production_fusion,
95 inp.diffusion, inp.reactions,
96 inp.energetics, inp.dissociation]:
101 inp.reactions[key] = val
102 inp.diffusion[
'i_mobile'] = 10
103 inp.reactions[
'i_mobile'] = 10
104 inp.reactions[
'i_discrete'] = 10
105 inp.reactions[
'v_discrete'] = 5
106 inp.reactions[
'I_bin'] = 0
107 inp.reactions[
'V_bin'] = 0
108 inp._calculate_derived()
111 results = sim.run_adaptive(
112 solver_config=SOLVER_CONFIG, save_output=
False,
113 progress_callback=
None, boundary_threshold=0.1,
114 max_doublings=0, points_per_segment=10,
117 sys.stdout, sys.stderr = saved
118 _FISSION_DICT.update(fission_bak)
124 rr = sim.reaction_rates
125 s2m = 1.0 / d[
'Omega']
127 dose = results[
'dose']
128 idx = np.argmin(np.abs(dose - 0.1))
130 ci1 = np.maximum(y[0, idx], 0.0)
131 cv1 = np.maximum(y[I_SIM, idx], 0.0)
132 c_i = np.maximum(y[0:I_SIM, idx], 0.0) * s2m
133 c_v = np.maximum(y[I_SIM:2*I_SIM, idx], 0.0) * s2m
136 ipk = np.argmax(c_i[1:]) + 1
141 ns = np.arange(1, I_SIM + 1, dtype=float)
142 mask2 = c_i[1:] > 1e5
144 mean_d = np.sum(
n_to_d_nm(ns[1:])[mask2] * c_i[1:][mask2]) / np.sum(c_i[1:][mask2])
149 vpk = np.argmax(c_v[1:]) + 1
153 K_g = rr.K_SIA_grow[99]
154 K_s = rr.K_SIA_shrink[99]
155 v_net = K_g * ci1 - K_s * cv1
156 ratio = (K_g * ci1) / max(K_s * cv1, 1e-300)
159 omega_i = d[
'omega_i_eff']
160 omega_v = d[
'omega_v_eff']
161 flux_i = omega_i * np.maximum(y[0, :], 0.0) * s2m
162 flux_v = omega_v * np.maximum(y[I_SIM, :], 0.0) * s2m
165 'name': name,
'Z_ii': Z_ii_val,
166 'dose': dose,
'flux_i': flux_i,
'flux_v': flux_v,
167 'sia_pk_n': sia_pk_n,
'sia_pk_d': sia_pk_d,
168 'sia_mean_d': mean_d,
169 'vac_pk_n': vac_pk_n,
170 'v_net': v_net,
'ratio': ratio,
171 'N_loops': results[
'N_loops'][idx],
172 'swelling': results[
'swelling'][idx] * 100,
173 'c_i': c_i,
'c_v': c_v,
174 'ci1_m3': ci1 * s2m,
'cv1_m3': cv1 * s2m,
179if __name__ ==
'__main__':
180 out_dir = Path(__file__).parent /
'zii_study_output'
181 out_dir.mkdir(exist_ok=
True)
183 Z_ii_values = [1.0, 1.2, 1.5, 1.7, 2.0, 2.5, 3.0]
186 print(
' Z_ii SWEEP — SIA-SIA Coalescence Bias')
187 print(
' Loop geometry corrected (A_loop * n^{1/2})')
192 for i, zii
in enumerate(Z_ii_values):
193 name = f
'Zii_{zii:.1f}'
194 print(f
'\n[{i+1}/{len(Z_ii_values)}] Z_ii = {zii}', flush=
True)
198 except Exception
as e:
199 print(f
' FAILED: {e}')
200 import traceback; traceback.print_exc()
202 dt = time.time() - t1
205 marker =
' *** GROWING ***' if r[
'ratio'] > 1.0
else ''
206 print(f
' Done ({dt:.0f}s) | peak: n={r["sia_pk_n"]}, d={r["sia_pk_d"]:.2f} nm | '
207 f
'mean_d={r["sia_mean_d"]:.2f} nm | ratio={r["ratio"]:.3f}{marker}')
209 total = time.time() - t0
210 print(f
'\nTotal: {total:.0f}s ({total/60:.1f} min)')
213 fig1, ax1 = plt.subplots(figsize=(10, 5))
217 rat = r[
'flux_i'][mask] / np.maximum(r[
'flux_v'][mask], 1e-30)
218 ax1.semilogx(dose[mask], rat, label=f
'Z_ii={r["Z_ii"]:.1f}', linewidth=1.5)
219 ax1.axhline(y=1.0, color=
'red', ls=
'--', lw=2, label=
'ratio=1 (balance)')
220 ax1.set_xlabel(
'Dose [dpa]')
221 ax1.set_ylabel(
r'$\omega_i C_{i1}\;/\;\omega_v C_{v1}$')
222 ax1.set_title(
r'Monomer flux ratio vs $Z_{ii}$ — above 1 = net loop growth')
223 ax1.legend(fontsize=9)
224 ax1.grid(
True, alpha=0.3)
225 ax1.set_ylim(0, max(3.0, ax1.get_ylim()[1]))
227 fig1.savefig(str(out_dir /
'flux_ratio_vs_Zii.png'), dpi=150)
228 print(f
'\nSaved: {out_dir / "flux_ratio_vs_Zii.png"}')
231 fig2, (ax2a, ax2b) = plt.subplots(1, 2, figsize=(14, 6))
232 ns = np.arange(1, I_SIM + 1)
237 ax2a.semilogy(d_nm[m], c_i[m], label=f
'Z_ii={r["Z_ii"]:.1f}', lw=1.3)
238 ax2b.plot(d_nm[m], c_i[m], label=f
'Z_ii={r["Z_ii"]:.1f}', lw=1.3)
239 for ax
in [ax2a, ax2b]:
240 ax.set_xlabel(
'SIA loop diameter [nm]')
241 ax.set_ylabel(
'Concentration [m$^{-3}$]')
242 ax.legend(fontsize=8)
243 ax.grid(
True, alpha=0.3)
244 ax2a.set_title(
'SIA distribution at 0.1 dpa (log)')
246 ax2b.set_title(
'SIA distribution at 0.1 dpa (linear)')
249 fig2.savefig(str(out_dir /
'sia_dist_vs_Zii.png'), dpi=150)
250 print(f
'Saved: {out_dir / "sia_dist_vs_Zii.png"}')
253 fig3, (ax3a, ax3b) = plt.subplots(1, 2, figsize=(12, 5))
254 zvals = [r[
'Z_ii']
for r
in results]
255 ratios = [r[
'ratio']
for r
in results]
256 peaks = [r[
'sia_pk_d']
for r
in results]
257 means = [r[
'sia_mean_d']
for r
in results]
259 ax3a.plot(zvals, ratios,
'bo-', markersize=8, linewidth=2)
260 ax3a.axhline(y=1.0, color=
'red', ls=
'--', lw=2, label=
'ratio=1 (balance)')
261 ax3a.set_xlabel(
r'$Z_{ii}$')
262 ax3a.set_ylabel(
'Growth/Shrink ratio at n=100')
263 ax3a.set_title(
'Growth ratio vs Z_ii')
265 ax3a.grid(
True, alpha=0.3)
267 ax3b.plot(zvals, peaks,
'rs-', markersize=8, linewidth=2, label=
'Peak diameter')
268 ax3b.plot(zvals, means,
'g^-', markersize=8, linewidth=2, label=
'Mean diameter')
269 ax3b.axhspan(2, 5, alpha=0.15, color=
'green', label=
'Target: 2-5 nm')
270 ax3b.set_xlabel(
r'$Z_{ii}$')
271 ax3b.set_ylabel(
'Diameter [nm]')
272 ax3b.set_title(
'SIA loop size vs Z_ii at 0.1 dpa')
274 ax3b.grid(
True, alpha=0.3)
277 fig3.savefig(str(out_dir /
'ratio_and_peak_vs_Zii.png'), dpi=150)
278 print(f
'Saved: {out_dir / "ratio_and_peak_vs_Zii.png"}')
281 print(
'\n' +
'=' * 95)
282 print(f
'{"Z_ii":>6} {"SIA pk n":>9} {"SIA pk d":>9} {"Mean d":>8} '
283 f
'{"VAC pk n":>9} {"ratio":>7} {"v_net(100)":>11} '
284 f
'{"N_loops":>10} {"Swell%":>8} {"Status":>10}')
287 status =
'GROWING' if r[
'ratio'] > 1.0
else 'shrinking'
288 print(f
'{r["Z_ii"]:>6.1f} {r["sia_pk_n"]:>9d} {r["sia_pk_d"]:>9.2f} '
289 f
'{r["sia_mean_d"]:>8.2f} {r["vac_pk_n"]:>9d} {r["ratio"]:>7.3f} '
290 f
'{r["v_net"]:>11.3e} {r["N_loops"]:>10.2e} {r["swelling"]:>8.4f} '