RadCluster
Graph-based cluster dynamics for irradiated materials
Loading...
Searching...
No Matches
check_loop_conversion_kernels.py
Go to the documentation of this file.
1"""
2check_loop_conversion_kernels.py — Phase-3 unit tests for the ½⟨111⟩ → ⟨100⟩
3loop-conversion rate kernels added to ``ReactionRates``.
4
5Checks the 1-D ingredients owned by ReactionRates:
6 1. Γ_uni(n) — nonnegative; zero where ΔF ≤ 0; nonzero on some small loops;
7 zero on large loops (small-loop biased, Phase-2 finding).
8 2. φ_junc — square, in [0, φ_max], symmetric, peaks on the diagonal,
9 zero below n_j_min.
10 3. ⟨100⟩ sessile kernels — K_100_grow / K_100_shrink zero below n_loop_min and
11 positive above; G_100 (emission) nonnegative; k2_100 ≡ 0.
12 4. conversion_dF agrees with the LoopEnergetics driving force.
13
14Run: python codes/Python_Testing/check_loop_conversion_kernels.py
15"""
16import sys
17from pathlib import Path
18
19import numpy as np
20
21ROOT = Path(__file__).resolve().parents[2]
22sys.path.insert(0, str(ROOT))
23
24from py_utils.input_data import InputData
25from py_utils.reaction_rates import ReactionRates
26
27
29 inp = InputData(I=1000, V=1000, physics_option="full_CD_fission")
30 inp.diffusion["i_mobile"] = 5
31 inp.derived["i_mobile"] = 5
32 inp.diffusion["v_mobile"] = 2
33 inp.derived["v_mobile"] = 2
34 inp.reactions["C_floor"] = 1e-25
35 inp.reactions["he_kinetics"] = "quasi_steady_state"
36 return inp, ReactionRates(inp)
37
38
39def main():
40 inp, rr = _make_inputs()
41 I = int(inp.I)
42 fails = 0
43
44 def check(name, cond, *info):
45 nonlocal fails
46 tag = "PASS" if cond else "FAIL"
47 if not cond:
48 fails += 1
49 print(f" {tag} {name} {info if info else ''}")
50
51 # ── 1. Γ_uni ──────────────────────────────────────────────────────────────
52 G = rr.Gamma_uni
53 dF = rr.conversion_dF
54 check("Gamma_uni shape == (I,)", G.shape == (I,), G.shape)
55 check("Gamma_uni >= 0", np.all(G >= 0.0))
56 check("Gamma_uni == 0 where dF <= 0", np.all(G[dF <= 0.0] == 0.0))
57 check("Gamma_uni > 0 for some (small) loops", np.any(G > 0.0),
58 f"max={G.max():.3e} at n={int(G.argmax())+1}")
59 # small-loop biased: the largest converting size is well below I
60 converting = np.where(G > 0.0)[0]
61 if converting.size:
62 check("conversion support is small-loop biased",
63 converting.max() + 1 < I // 2,
64 f"sizes n in [{converting.min()+1}, {converting.max()+1}]")
65
66 # ── 2. φ_junc ─────────────────────────────────────────────────────────────
67 phi = rr.phi_junc
68 phi_max = float(inp.reactions.get("phi_max_junc", 0.5))
69 n_j_min = float(inp.reactions.get("n_j_min_junc", 30.0))
70 check("phi_junc shape == (I, I)", phi.shape == (I, I), phi.shape)
71 check("phi_junc in [0, phi_max]", np.all(phi >= 0.0) and np.all(phi <= phi_max + 1e-12))
72 check("phi_junc symmetric", np.allclose(phi, phi.T, atol=1e-12))
73 check("phi_junc == 0 below n_j_min",
74 np.all(phi[: int(n_j_min) - 1, :] == 0.0) and
75 np.all(phi[:, : int(n_j_min) - 1] == 0.0))
76 # diagonal is the per-row max where defined (size comparability peaks at n=n')
77 k = int(n_j_min) + 20
78 check("phi_junc peaks on the diagonal", abs(phi[k, k] - phi[k].max()) < 1e-12,
79 f"phi[{k},{k}]={phi[k,k]:.3f}")
80
81 # ── 3. ⟨100⟩ sessile kernels ──────────────────────────────────────────────
82 nlm = int(rr.n_loop_min)
83 check("K_100_grow == 0 below n_loop_min", np.all(rr.K_100_grow[: nlm - 1] == 0.0))
84 check("K_100_grow > 0 at/above n_loop_min", np.all(rr.K_100_grow[nlm - 1:] > 0.0))
85 check("K_100_shrink >= 0", np.all(rr.K_100_shrink >= 0.0))
86 check("G_100 (emission) >= 0", np.all(rr.G_100 >= 0.0))
87 check("G_100[0] == 0 (monomer cannot emit)", rr.G_100[0] == 0.0)
88 check("k2_100 == 0 (sessile)", np.all(rr.k2_100 == 0.0))
89
90 # ── 4. driving force consistency ──────────────────────────────────────────
91 le = rr.loop_energetics
92 T = float(inp.derived["T"])
93 check("conversion_dF matches LoopEnergetics",
94 np.allclose(dF, le.driving_force_array(T, I), atol=1e-12))
95
96 print(f"\n{'ALL PASS' if not fails else str(fails) + ' FAILED'}")
97 # report
98 print(f" [info] T={T:.0f} K, Gamma_uni max={G.max():.3e} s^-1, "
99 f"converting sizes n<= {converting.max()+1 if converting.size else 0}, "
100 f"Fhat_100_0={le.Fhat_100_0:.4f} eV/A")
101 return 1 if fails else 0
102
103
104if __name__ == "__main__":
105 sys.exit(main())