3benchmark_woodbury.py — Benchmark Woodbury vs Jacobi preconditioner.
5Uses a SHORT time span (t=1e-6 to 0.1) to isolate the preconditioner
6effect from the physics stiffness at later times. All 4 cases complete
10import sys, os, io, time, importlib, subprocess, json
11from pathlib
import Path
13MODULE_ROOT = Path(__file__).resolve().parent.parent.parent
14REPO_ROOT = MODULE_ROOT.parent
15for p
in [str(REPO_ROOT), str(MODULE_ROOT)]:
20build_dir = MODULE_ROOT /
'build'
21build_dir.mkdir(exist_ok=
True)
22cmake_src = MODULE_ROOT /
'cpp_utils'
23print(
"Building C++ solver...", flush=
True)
25 [
'cmake',
'-S', str(cmake_src),
'-B', str(build_dir),
'-DCMAKE_BUILD_TYPE=Release'],
26 [
'cmake',
'--build', str(build_dir),
'--config',
'Release'],
28 res = subprocess.run(cmd, capture_output=
True, text=
True)
29 if res.returncode != 0:
30 print(f
"Build failed: {res.stderr[-1000:]}", flush=
True)
32print(
"Build OK\n", flush=
True)
35import radcluster_code.py_utils.defect_production
as _dp
36import radcluster_code.py_utils.binding_energies
as _be
37import radcluster_code.py_utils.bin_moment_rates
as _bmr
38import radcluster_code.py_utils.input_data
as _inp
39import radcluster_code.py_utils.reaction_rates
as _rr
40import radcluster_code.py_utils.rate_equations
as _re
41import radcluster_code.py_utils.cpp_bridge
as _cb
42import radcluster_code.py_utils.post_process
as _pp
43import radcluster_code.py_utils.simulation
as _sim
44for _m
in [_dp, _be, _bmr, _inp, _rr, _re, _cb, _pp, _sim]:
46from radcluster_code.py_utils.simulation
import RadClusterSimulation
53 'eta': 0.3,
'f_cl_i': 0.0,
'f_cl_v': 0.5,
54 'E_m_1D': 0.4,
'L_hat': 71.8,
55 'c_C': 1.94e-4,
'E_b_C_SIA': 0.65,
56 'rho_d': 1e16,
'Z_i': 1.05,
'Z_ii': 5,
57 'i_discrete': I,
'v_discrete': V,
58 'I_bin': 0,
'V_bin': 0,
59 'shape_function':
'linear',
65 't_span': (1e-6, t_end),
73 'concentration_threshold': 1e-20,
'window_pad': 100,
74 'prec_type': prec_type,
80 saved = sys.stdout, sys.stderr
81 sys.stdout = sys.stderr = io.StringIO()
83 sim = RadClusterSimulation(
84 I=I, V=V, solver_mode=
'active_window',
85 physics_option=
'full_CD_fission',
86 C_floor=1e-25, he_kinetics=
'quasi_steady_state',
87 i_mobile=i_mob, v_mobile=v_mob,
89 ov = dict(BASE_OVERRIDES)
90 ov[
'i_mobile'] = i_mob
92 for key, val
in ov.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'] = int(ov[
'i_mobile'])
103 inp.reactions[
'i_mobile'] = int(ov[
'i_mobile'])
104 inp._calculate_derived()
107 sys.stdout, sys.stderr = saved
111def run_case(label, i_mob, v_mob, prec_type, t_end):
112 prec_name =
'Woodbury' if prec_type == 1
else 'Jacobi'
113 print(f
"\n{'='*60}", flush=
True)
114 print(f
" {label}", flush=
True)
115 print(f
" i_mobile={i_mob}, v_mobile={v_mob}, prec={prec_name}, t_end={t_end:.0e}", flush=
True)
116 print(f
"{'='*60}", flush=
True)
121 t0 = time.perf_counter()
122 results = sim.run(solver_config=cfg, save_output=
False)
123 wall = time.perf_counter() - t0
125 info = {
'label': label,
'i_mobile': i_mob,
'v_mobile': v_mob,
126 'prec_type': prec_type,
'wall_time': wall,
't_end': t_end}
128 if results
is not None:
129 info[
'n_pts'] = len(results[
't'])
130 info[
'swelling'] = results[
'swelling'][-1] * 100
131 info[
'delta_FP'] = results[
'delta_FP'][-1]
132 print(f
" Wall time: {wall:.2f} s", flush=
True)
133 print(f
" Swelling: {info['swelling']:.6f} %", flush=
True)
134 print(f
" delta_FP: {info['delta_FP']:.2e}", flush=
True)
136 print(f
" FAILED ({wall:.1f} s)", flush=
True)
146for t_end
in [1e-1, 1e0]:
147 tag = f
"t={t_end:.0e}"
148 all_results.append(
run_case(f
"Mono+Woodbury ({tag})", 1, 1, 1, t_end))
149 all_results.append(
run_case(f
"Mono+Jacobi ({tag})", 1, 1, 0, t_end))
150 all_results.append(
run_case(f
"Coal+Woodbury ({tag})", 10, 5, 1, t_end))
151 all_results.append(
run_case(f
"Coal+Jacobi ({tag})", 10, 5, 0, t_end))
157print(f
"\n\n{'='*60}", flush=
True)
158print(f
" BENCHMARK SUMMARY (I={I}, V={V})", flush=
True)
159print(f
"{'='*60}", flush=
True)
160print(f
" {'Case':<35s} {'Wall(s)':>8s} {'Swell%':>10s} {'dFP':>10s}", flush=
True)
161print(f
" {'-'*33} {'-'*8} {'-'*10} {'-'*10}", flush=
True)
163 sw = f
"{r.get('swelling',0):.5f}" if 'swelling' in r
else 'FAIL'
164 dfp = f
"{r.get('delta_FP',0):.1e}" if 'delta_FP' in r
else 'FAIL'
165 print(f
" {r['label']:<35s} {r['wall_time']:>8.2f} {sw:>10s} {dfp:>10s}", flush=
True)
168print(f
"\n Speedup (Jacobi time / Woodbury time):", flush=
True)
169for t_end
in [1e-1, 1e0]:
170 tag = f
"t={t_end:.0e}"
171 for mob_label, im, vm
in [(
"Monomer", 1, 1), (
"Coalescence", 10, 5)]:
172 wood = [r
for r
in all_results
if r[
'i_mobile']==im
and r[
'prec_type']==1
and r[
't_end']==t_end]
173 jac = [r
for r
in all_results
if r[
'i_mobile']==im
and r[
'prec_type']==0
and r[
't_end']==t_end]
174 if wood
and jac
and wood[0][
'wall_time'] > 0:
175 ratio = jac[0][
'wall_time'] / wood[0][
'wall_time']
176 print(f
" {mob_label} ({tag}): {ratio:.2f}x", flush=
True)
178print(f
"\n Coalescence overhead (Coal time / Mono time):", flush=
True)
179for t_end
in [1e-1, 1e0]:
180 tag = f
"t={t_end:.0e}"
181 for pt, pn
in [(1,
"Woodbury"), (0,
"Jacobi")]:
182 mono = [r
for r
in all_results
if r[
'i_mobile']==1
and r[
'prec_type']==pt
and r[
't_end']==t_end]
183 coal = [r
for r
in all_results
if r[
'i_mobile']==10
and r[
'prec_type']==pt
and r[
't_end']==t_end]
184 if mono
and coal
and mono[0][
'wall_time'] > 0:
185 ratio = coal[0][
'wall_time'] / mono[0][
'wall_time']
186 print(f
" {pn} ({tag}): {ratio:.2f}x", flush=
True)
run_case(label, i_mob, v_mob, prec_type, t_end)
make_config(prec_type, t_end)