RadCluster
Graph-based cluster dynamics for irradiated materials
Loading...
Searching...
No Matches
check_eurofer_rag.py
Go to the documentation of this file.
1"""
2check_eurofer_rag.py — validation harness for the EUROFER-97 RAG declaration.
3
4Constructs the im5vm2 case (I=V=1000, i_mobile=5, v_mobile=2,
5full_CD_fission, quasi-steady-state helium, C_floor=1e-25), builds the
6EUROFER-97 reaction admissibility graph and state layout via
7``build_eurofer_rag``, and asserts:
8
9 * ``rag.validate()`` passes (every edge's kernel is registered);
10 * every edge has a registered kernel of the correct shape;
11 * the SIA / vacancy monomer populations are registered;
12 * the StateLayout's N_eq is sensible (SIA + VAC ladders + He + aux);
13 * the bin-moment variant also builds and validates.
14
15Run: python check_eurofer_rag.py
16"""
17import sys
18from pathlib import Path
19
20# The EUROFER instantiation imports as radcluster_code.py_utils.materials...
21ROOT = Path(r"d:/GitHub/RadCluster/radcluster_code")
22sys.path.insert(0, str(ROOT))
23
24from py_utils.input_data import InputData
25from py_utils.reaction_rates import ReactionRates
26from py_utils.materials import build_eurofer_rag
27
28
30 """Build the im5vm2 InputData + ReactionRates the way RadClusterSimulation does.
31
32 The i_mobile / v_mobile / he_kinetics / C_floor knobs are
33 RadClusterSimulation constructor kwargs that it injects into the
34 InputData object *after* construction; we replicate that here so the
35 standalone harness exercises the exact im5vm2 configuration.
36 """
37 inp = InputData(I=1000, V=1000, physics_option="full_CD_fission")
38
39 # Injections performed by RadClusterSimulation.__init__ for im5vm2.
40 inp.diffusion["i_mobile"] = 5
41 inp.derived["i_mobile"] = 5
42 inp.diffusion["v_mobile"] = 2
43 inp.derived["v_mobile"] = 2
44 inp.reactions["C_floor"] = 1e-25
45 inp.reactions["he_kinetics"] = "quasi_steady_state"
46
47 rr = ReactionRates(inp)
48 return inp, rr
49
50
51def check(equations):
52 print(f"\n{'='*70}\n EUROFER-97 RAG check — equations={equations!r}\n{'='*70}")
53 inp, rr = make_im5vm2_inputs()
54 rag, layout = build_eurofer_rag(
55 inp, rr, equations=equations, cascade="fission")
56
57 # ── rag.validate() must pass ─────────────────────────────────────────────
58 rag.validate()
59 print("rag.validate() ......................... OK")
60
61 # ── every edge has a registered, correctly-shaped kernel ─────────────────
62 assert len(rag.edges) > 0, "RAG has no edges"
63 for e in rag.edges:
64 k = rag.kernel(e.kernel) # raises KeyError if unregistered
65 import numpy as np
66 arr = np.asarray(k, dtype=float)
67 assert np.all(np.isfinite(arr)), f"edge {e.label!r}: non-finite kernel"
68 # Binary edges (COALESCENCE / ANNIHILATION) need a 2-D kernel.
69 if e.edge_class.value in ("coalescence", "annihilation"):
70 assert arr.ndim == 2, \
71 f"edge {e.label!r}: {e.edge_class} needs a 2-D kernel"
72 # Unary/source size-axis edges need scalar or 1-D kernels.
73 else:
74 assert arr.ndim in (0, 1), \
75 f"edge {e.label!r}: expected scalar/1-D kernel, got {arr.ndim}-D"
76 print(f"every edge has a registered kernel ..... OK ({len(rag.edges)} edges)")
77
78 # ── monomer populations are registered for both polarities ───────────────
79 from py_utils.core import Polarity
80 sia_mono = rag.monomer_population(Polarity.SIA)
81 vac_mono = rag.monomer_population(Polarity.VACANCY)
82 assert sia_mono.name == "bulk-111" and sia_mono.polarity is Polarity.SIA
83 assert vac_mono.name == "bulk" and vac_mono.polarity is Polarity.VACANCY
84 print("SIA + vacancy monomer populations set .. OK")
85
86 # ── gas list is helium-only ──────────────────────────────────────────────
87 assert rag.gas_species == ["He"], f"gas list = {rag.gas_species}"
88 print("gas list = ['He'] ...................... OK")
89
90 # ── StateLayout sanity ───────────────────────────────────────────────────
91 assert layout.has("SIA") and layout.has("SIA100") and layout.has("VAC")
92 assert layout.has("He") and layout.has("conservation")
93 assert layout.block("conservation").length == 5
94 sia_len = layout.block("SIA").length
95 sia100_len = layout.block("SIA100").length
96 vac_len = layout.block("VAC").length
97 he_len = layout.block("He").length
98 expected = sia_len + sia100_len + vac_len + he_len + 5
99 assert layout.N_eq == expected, \
100 f"N_eq {layout.N_eq} != {expected}"
101 assert layout.N_eq > 0
102 if equations == "discrete":
103 # discrete im5vm2: SIA(1000) + SIA100(1000) + VAC(1000)
104 # + He(2: Q_tot + c_h) + aux(5)
105 assert sia_len == 1000 and sia100_len == 1000 and vac_len == 1000, \
106 f"discrete ladders should be 1000, got {sia_len}/{sia100_len}/{vac_len}"
107 assert he_len == 2, f"fission He block should be 2 (Q_tot + c_h), got {he_len}"
108 print(f"StateLayout N_eq sensible .............. OK (N_eq = {layout.N_eq})")
109
110 # ── reports ──────────────────────────────────────────────────────────────
111 print("\n--- rag.summary() ---")
112 print(rag.summary())
113 print("\n--- state_layout.describe() ---")
114 print(layout.describe())
115 return rag, layout
116
117
118def main():
119 # Primary requested case: discrete (full_CD_fission).
120 check("discrete")
121 # Also exercise the bin-moment variant to confirm both layout paths build.
122 check("bin_moment")
123 print(f"\n{'='*70}\n ALL CHECKS PASSED\n{'='*70}")
124
125
126if __name__ == "__main__":
127 main()