RadCluster
Graph-based cluster dynamics for irradiated materials
Loading...
Searching...
No Matches
check_he_conservation.py
Go to the documentation of this file.
1"""He-conservation audit — runs all four physics options x both He-kinetics
2modes at small system size and reports the delta_He / delta_FP diagnostics.
3
4The conservation identity tested (Eq. delta_He):
5 dynamic: c_h(t) + Q(t) + J_He_sink(t) = c_h(0) + Q(0) + int_0^t G_He dt'
6 QSS: Q(t) + J_He_sink(t) = Q(0) + int_0^t G_He dt'
7where Q = Q_tot (Case 2) or sum_m Q_m (Case 1), and J_He_sink is the CVODE
8accounting integral of all He sink losses. delta_He is the relative defect;
9values <~ 1e-6 mean every He channel is balanced, > 1e-3 indicates a leak.
10
11Each configuration runs in its own subprocess (pass "<equations> <cascade>
12<he_kinetics>" as argv to run one config) so a failure in one cannot affect
13the others.
14"""
15import sys, io, subprocess, traceback
16from pathlib import Path
17
18MODULE_ROOT = Path(__file__).resolve().parent.parent.parent
19REPO_ROOT = MODULE_ROOT.parent
20
21I = V = 200
22i_mobile, v_mobile = 5, 3
23
24SOLVER_CONFIG = {
25 't_span': (1e-6, 1e3),
26 'n_points': 25,
27 'log_time': True,
28 'rtol': 1e-8, 'atol': 1e-30,
29 'solver_method': {'linsol': 'gmres'},
30}
31
32BIN_KW = dict(i_discrete=i_mobile, v_discrete=v_mobile, I_bin=15, V_bin=15)
33
34
35def run_single(eq, cas, hk):
36 sys.path.insert(0, str(REPO_ROOT))
37 sys.path.insert(0, str(MODULE_ROOT))
38 import numpy as np
39 from radcluster_code.py_utils.simulation import RadClusterSimulation
40
41 po = ('full_CD_' if eq == 'discrete' else 'bin_moment_CD_') + cas
42 _saved = sys.stdout, sys.stderr
43 sys.stdout = sys.stderr = io.StringIO()
44 try:
45 sim = RadClusterSimulation(I=I, V=V, solver_mode='full_system',
46 physics_option=po, C_floor=1e-25,
47 he_kinetics=hk,
48 i_mobile=i_mobile, v_mobile=v_mobile)
49 inp = sim.input_data
50 if eq == 'bin_moment':
51 for k, v in BIN_KW.items():
52 inp.reactions[k] = v
53 inp.reactions['shape_function'] = 'linear'
54 inp._calculate_derived()
55 sim.rebuild_rates()
56 results = sim.run_adaptive(
57 solver_config=SOLVER_CONFIG, save_output=False,
58 boundary_threshold=0.1, max_doublings=0, points_per_segment=10)
59 finally:
60 sys.stdout, sys.stderr = _saved
61
62 if results is None:
63 print('RESULT FAILED solver-returned-None')
64 return 1
65 dHe = np.asarray(results['delta_He'])
66 dFP = np.asarray(results['delta_FP'])
67 He_tot = np.asarray(results['C_He_tot'])
68 # Ignore the start-up region where the He inventory is still ~0 and the
69 # relative diagnostic is dominated by round-off of tiny numbers.
70 mask = He_tot > (He_tot[-1] * 1e-6 if He_tot[-1] > 0 else 0.0)
71 dHe_max = float(np.max(dHe[mask])) if mask.any() else float(np.max(dHe))
72 print(f'RESULT OK {dHe_max:.3e} {dHe[-1]:.3e} {dFP[-1]:.3e}')
73 return 0
74
75
76def main():
77 configs = [(eq, cas, hk)
78 for eq in ('discrete', 'bin_moment')
79 for cas in ('fission', 'fusion')
80 for hk in ('quasi_steady_state', 'dynamic')]
81 rows = []
82 for eq, cas, hk in configs:
83 label = f'{eq:>10s}/{cas:<7s}/{hk}'
84 proc = subprocess.run([sys.executable, __file__, eq, cas, hk],
85 capture_output=True, text=True, timeout=1800)
86 line = next((l for l in proc.stdout.splitlines()
87 if l.startswith('RESULT')), None)
88 if line is None or 'FAILED' in line or proc.returncode != 0:
89 rows.append((label, None))
90 print(f'{label}: FAILED', flush=True)
91 tail = (proc.stderr or proc.stdout).strip().splitlines()
92 for l in tail[-4:]:
93 print(f' {l}', flush=True)
94 continue
95 _, _, dmax, dlast, dfp = line.split()
96 rows.append((label, float(dmax)))
97 print(f'{label}: max delta_He = {dmax} final delta_He = {dlast}'
98 f' final delta_FP = {dfp}', flush=True)
99
100 print('\n' + '=' * 78, flush=True)
101 bad = [r for r in rows if r[1] is None or r[1] > 1e-6]
102 if not bad:
103 print(f'ALL {len(rows)} CONFIGS PASS: max delta_He <= 1e-6.', flush=True)
104 else:
105 print(f'{len(bad)} of {len(rows)} configs exceed delta_He = 1e-6:', flush=True)
106 for r in bad:
107 print(f' {r[0]}: {r[1]}', flush=True)
108
109
110if __name__ == '__main__':
111 if len(sys.argv) == 4:
112 sys.exit(run_single(*sys.argv[1:4]))
113 main()