RadCluster
Graph-based cluster dynamics for irradiated materials
Loading...
Searching...
No Matches
check_binmoment_loop100.py
Go to the documentation of this file.
1"""
2check_binmoment_loop100.py — verify the bin-moment ⟨100⟩ SIA-loop reduction
3and the KLU sparse-direct solver.
4
5Runs four short cases and checks:
6 1. bin_moment + loop_conversion ON → δ_FP small, ⟨100⟩ population grows.
7 2. bin_moment + loop_conversion OFF → δ_FP small (regression: ½⟨111⟩ path).
8 3. discrete + loop_conversion ON → δ_FP small (validated baseline).
9 4. discrete + KLU (linsol='klu') → runs, δ_FP small.
10"""
11import io
12import sys
13from pathlib import Path
14
15MODULE_ROOT = Path(__file__).resolve().parents[2] # RadCluster_2_1
16REPO_ROOT = MODULE_ROOT.parent
17for p in (REPO_ROOT, MODULE_ROOT):
18 if str(p) not in sys.path:
19 sys.path.insert(0, str(p))
20
21import numpy as np
22from radcluster_code.py_utils.simulation import RadClusterSimulation
23
24
25def run_case(equations, loop_conversion, linsol='gmres', label=''):
26 I, V = 200, 80
27 sim = RadClusterSimulation(
28 I=I, V=V, solver_mode='full_system',
29 equations=equations, cascade='fission',
30 C_floor=1e-25, he_kinetics='quasi_steady_state',
31 i_mobile=5, v_mobile=2,
32 )
33
34 overrides = {'T': 673}
35 if equations == 'bin_moment':
36 overrides.update(i_discrete=8, v_discrete=4, I_bin=10, V_bin=6,
37 shape_function='linear')
38 else:
39 overrides.update(i_discrete=I, v_discrete=V, I_bin=0, V_bin=0)
40 overrides['n_loop_min'] = 4
41
42 inp = sim.input_data
43 for key, val in overrides.items():
44 placed = False
45 for d in (inp.production_fission, inp.production_fusion, inp.diffusion,
46 inp.reactions, inp.energetics, inp.dissociation):
47 if key in d:
48 d[key] = val
49 placed = True
50 if not placed:
51 inp.reactions[key] = val
52 inp._calculate_derived()
53 sim.rebuild_rates()
54
55 solver_config = {
56 't_span': (1e-6, 1.0), 'n_points': 40, 'log_time': True,
57 'rtol': 1e-6, 'atol': 1e-20, 'timeout_s': 120,
58 'loop_conversion': int(loop_conversion),
59 'solver_method': {'linsol': linsol, 'preconditioner': 'Jacobi'},
60 }
61
62 buf = io.StringIO()
63 old = sys.stdout, sys.stderr
64 sys.stdout = sys.stderr = buf
65 try:
66 results = sim.run(solver_config=solver_config, save_output=False)
67 except Exception as exc:
68 sys.stdout, sys.stderr = old
69 print(f"[{label}] EXCEPTION: {type(exc).__name__}: {exc}")
70 print(buf.getvalue()[-2000:])
71 return None
72 finally:
73 sys.stdout, sys.stderr = old
74
75 if results is None:
76 print(f"[{label}] no results")
77 print(buf.getvalue()[-2000:])
78 return None
79
80 def last(key):
81 a = results.get(key)
82 return float(a[-1]) if a is not None and len(a) else float('nan')
83
84 dFP = last('delta_FP')
85 n100 = last('N_loops_100')
86 n111 = last('N_loops_111')
87 f111 = last('f_111_loop')
88 mean100 = last('mean_n_100')
89 npts = len(results.get('t', []))
90 print(f"[{label}] pts={npts:3d} delta_FP={dFP:.2e} "
91 f"N_111={n111:.3e} N_100={n100:.3e} f_111={f111:.4f} "
92 f"mean_n_100={mean100:.2f}")
93 return results
94
95
96if __name__ == '__main__':
97 print("=" * 78)
98 r1 = run_case('bin_moment', True, 'gmres', 'bin+conv ON ')
99 r2 = run_case('bin_moment', False, 'gmres', 'bin+conv OFF')
100 r3 = run_case('discrete', True, 'gmres', 'disc+conv ON')
101 r4 = run_case('discrete', False, 'klu', 'disc+KLU ')
102 print("=" * 78)
103
104 ok = True
105 if r1 is not None:
106 dFP = float(r1['delta_FP'][-1]); n100 = float(r1['N_loops_100'][-1])
107 if not (dFP < 1e-3):
108 print(f"FAIL: bin+conv delta_FP={dFP:.2e} not < 1e-3"); ok = False
109 if not (n100 > 0):
110 print(f"FAIL: bin+conv N_100={n100:.2e} did not grow"); ok = False
111 else:
112 ok = False
113 if r2 is not None and not (float(r2['delta_FP'][-1]) < 1e-3):
114 print("FAIL: bin+conv-OFF delta_FP too large"); ok = False
115 if r4 is None:
116 print("FAIL: KLU run produced no results"); ok = False
117 print("RESULT:", "PASS" if ok else "FAIL")
118 sys.exit(0 if ok else 1)
run_case(equations, loop_conversion, linsol='gmres', label='')