591 self.
rr = reaction_rates
602 sf_raw = str(input_data.reactions.get(
'shape_function',
'linear')).lower()
603 if sf_raw
not in _SHAPE_FUNCTIONS:
605 f
"Unknown shape_function='{sf_raw}'. "
606 f
"Must be one of {list(_SHAPE_FUNCTIONS.keys())}.")
608 self.
n_mom = _SHAPE_FUNCTIONS[sf_raw]
615 I_bin_target = input_data.I_bin
618 r_i = (float(I) / max(self.
i_discrete, 1)) ** (1.0 / I_bin_target)
624 self.
bins, self.
edges = [], np.array([I + 1])
634 V_bin_target = input_data.V_bin
636 r_v = (float(V) / max(self.
v_discrete, 1)) ** (1.0 / V_bin_target)
648 self.
vac_mid = np.array([(mlo + mhi - 1) / 2.0
652 raw = str(input_data.reactions.get(
'he_kinetics',
'dynamic')).lower()
654 self.
qss_He = (raw ==
'quasi_steady_state')
657 kBT_val = input_data.derived[
'kBT']
658 E_b_hV1 = float(input_data.energetics.get(
'E_b_hV_1', 2.30))
659 E_m_h = float(input_data.derived[
'E_m_h'])
660 nu_h = float(input_data.derived[
'nu_h'])
661 self.
beta_He = nu_h * np.exp(-(E_b_hV1 + E_m_h) / kBT_val)
668 po = input_data.physics_option
682 n_he = self.
V_bin + 1
692 self.
i_He = n_sia + n_vac + 1
695 n_phys = n_sia + n_vac + n_he
706 spectrum = input_data.derived[
'spectrum']
707 G = input_data.derived[
'G']
708 G_He_r = input_data.derived[
'G_He_r']
709 Pr_SIA, Pr_VAC, G_He = production_rates(
710 G, spectrum, I, V, G_He_r,
711 spec_over=(input_data.production_fission
if 'fiss' in spectrum.lower()
712 else input_data.production_fusion))
717 print(f
"BinMomentRateEquations: i_discrete={self.i_discrete}"
718 f
" I_bin={self.I_bin} V_bin={self.V_bin} N_eq={self.N_eq}"
719 f
" he_mode='{self.he_mode}' r_i={r_i:.3f}"
720 f
" shape_function='{self.shape_function}' ({P} mom/bin)")
722 print(f
" SIA bin edges (first 5): {list(self.edges[:6])}")
724 print(f
" VAC bin edges (first 5): {list(self.vac_edges[:6])}")
801 """ODE RHS using hybrid discrete + bin-moment layout.
803 REFERENCE ONLY — not used by the solver; the production RHS is the
804 C++ implementation. May be stale. Scheduled for replacement by the
805 Stage-3 graph-walker.
817 dydt = np.zeros(self.
N_eq)
822 c_n[:i_d] = np.maximum(y[:i_d], 0.0)
827 y[mom_start:mom_start + P * I_bin], I_bin)
829 sf, sia_mu0, sia_mu1, sia_mu2, self.
bins, I)
830 c_n[i_d:] = c_binned[i_d:]
836 c_v[:v_d] = np.maximum(y[iv:iv + v_d], 0.0)
841 y[vac_start:vac_start + P * V_bin], V_bin)
843 sf, vac_mu0, vac_mu1, vac_mu2, self.
vac_bins, V)
844 c_v[v_d:] = c_v_binned[v_d:]
848 Q_tot = max(y[self.
i_Qtot], 0.0)
853 c_h = max(y[self.
i_He], 0.0)
855 Q_k = np.maximum(y[self.
i_Q:self.
i_Q + V_bin], 0.0)
860 c_h = max(y[self.
i_He], 0.0)
862 ci1 = c_n[0]
if len(c_n) > 0
else 0.0
864 i_mobile = inp.derived[
'i_mobile']
865 reflect = inp.derived.get(
'boundary_flux',
'absorption') ==
'reflection'
866 m13 = np.arange(1.0, V + 1) ** (1.0 / 3.0)
867 denom_m = 1.0 + rr.B_rot * rr.L_hat**2 / m13
875 dc_n[:-1] += rr.G_SIA[1:] * c_n[1:]
876 dc_n -= rr.G_SIA * c_n
877 dc_n[1:] += rr.K_SIA_grow[:-1] * ci1 * c_n[:-1]
878 dc_n -= rr.K_SIA_grow * ci1 * c_n
880 dc_n[-1] += rr.K_SIA_grow[-1] * ci1 * c_n[-1]
881 dc_n[0] += ci1 * rr.K_SIA_grow[-1] * c_n[-1]
883 dc_n[0] -= ci1 * np.dot(rr.K_SIA_grow, c_n)
886 dc_n[0] -= rr.K_iv * cv1 * ci1
887 dc_n[1:] -= rr.K_SIA_shrink[1:] * cv1 * c_n[1:]
888 dc_n[:-1] += rr.K_SIA_shrink[1:] * cv1 * c_n[1:]
891 sum_K3D_cv_m2 = rr.K_3D_cav_pref * np.dot(m13[1:], c_v[1:])
892 dc_n[0] -= c_n[0] * sum_K3D_cv_m2
893 sum_K3D_cv = rr.K_3D_cav_pref * np.dot(m13, c_v)
894 for n
in range(2, min(4, i_mobile + 1)):
895 dc_n[n - 1] -= c_n[n - 1] * sum_K3D_cv
896 for n
in range(4, min(I, i_mobile) + 1):
897 k_pref = rr.K_1D_pref[n - 1]
900 dc_n[n - 1] -= c_n[n - 1] * k_pref * np.dot(m13 / denom_m, c_v)
902 dc_n -= rr.k2_SIA * c_n
906 dydt[:i_d] = dc_n[:i_d]
913 C_vac_tot = np.sum(c_v)
914 ell_bar = Q_tot / max(C_vac_tot, 1e-200)
915 G_VAC_eff = rr.G_VAC.copy()
917 for m
in range(1, min(V, 20) + 1):
918 G_VAC_eff[m - 1] = rr.alpha_bubble_fn(m, ell_bar * m**(2.0/3.0))
927 dc_v -= G_VAC_eff * c_v
928 dc_v[:-1] += G_VAC_eff[1:] * c_v[1:]
929 dc_v[0] += np.sum(G_VAC_eff[1:] * c_v[1:])
932 dc_v -= rr.K_VAC_grow * cv1 * c_v
933 dc_v[1:] += rr.K_VAC_grow[:-1] * cv1 * c_v[:-1]
935 dc_v[-1] += rr.K_VAC_grow[-1] * cv1 * c_v[-1]
936 dc_v[0] += cv1 * rr.K_VAC_grow[-1] * c_v[-1]
938 dc_v[0] -= cv1 * np.dot(rr.K_VAC_grow, c_v)
941 dc_v[0] -= rr.K_iv * ci1 * cv1
943 dc_v[0] += rr.K_VAC_shrink[1] * ci1 * c_v[1]
944 dc_v[1:] -= rr.K_VAC_shrink[1:] * ci1 * c_v[1:]
946 dc_v[1:-1] += rr.K_VAC_shrink[2:] * ci1 * c_v[2:]
947 for n
in range(2, min(4, i_mobile + 1)):
949 dc_v -= rr.K_VAC_shrink * cn * c_v
951 dc_v[:V - n] += rr.K_VAC_shrink[n:] * cn * c_v[n:]
952 m_arr = np.arange(1.0, V + 1)
953 for n
in range(4, min(I, i_mobile) + 1):
954 k_pref = rr.K_1D_pref[n - 1]
958 k_loss = k_pref * m13 / denom_m
959 dc_v -= k_loss * cn * c_v
961 mp = m_arr[:V - n] + float(n)
962 mp13 = mp ** (1.0 / 3.0)
963 denom = 1.0 + rr.B_rot * rr.L_hat**2 / mp13
964 dc_v[:V - n] += (k_pref * mp13 / denom) * cn * c_v[n:]
968 dc_v[0] -= cv1 * np.dot(rr.K_SIA_shrink[1:], c_n[1:])
971 v_mobile = self.
inp.derived[
'v_mobile']
972 mask_mobile_v = np.zeros(V)
973 mask_mobile_v[:min(v_mobile, V)] = 1.0
974 dc_v -= rr.k2_vac_scalar * c_v * mask_mobile_v
978 dydt[iv:iv + v_d] = dc_v[:v_d]
980 vac_mom_start = iv + v_d
982 vac_mom_start, V_bin)
988 He_uptake = np.sum(rr.K_HeV * c_h * c_v)
989 He_release = rr.alpha_He * Q_tot
if hasattr(rr,
'alpha_He')
else 0.0
991 ms_arr = np.arange(1.0, V + 1.0)
992 ell_m_arr = ell_bar * ms_arr ** (2.0 / 3.0)
993 He_sink = rr.k2_vac_scalar * np.sum(ell_m_arr[:v_mobile] * c_v[:v_mobile])
994 dydt[self.
i_Qtot] = He_uptake - He_release - He_sink
998 - rr.k2_He_scalar * c_h
1002 dQ = dydt[self.
i_Q:self.
i_Q + V_bin]
1005 for kv, (mlo, mhi)
in enumerate(self.
vac_bins):
1006 mu0_kv = max(vac_mu0[kv], 1e-200)
1007 ell_kv = Q_k[kv] / mu0_kv
1008 for m
in range(mlo, mhi):
1010 if mi < 0
or mi >= V:
1013 he_cap = rr.K_HeV[mi] * c_h * cm
1014 alpha_h = rr.alpha_He_emit_fn(m, max(int(round(ell_kv)), 0))
1015 q_m_approx = ell_kv * cm
1016 he_emit = alpha_h * q_m_approx
1017 dQ[kv] += he_cap - he_emit
1019 dQ[kv] -= rr.k2_vac_scalar * q_m_approx
1020 He_cap_total += he_cap
1021 He_emit_total += he_emit
1025 - rr.k2_He_scalar * c_h
1033 ns_all = np.arange(1.0, I + 1)
1039 mutual = rr.K_iv * ci1 * cv1
1040 sum_K3D_cv_m2 = rr.K_3D_cav_pref * np.dot(m13[1:], c_v[1:])
1041 mutual += ci1 * sum_K3D_cv_m2
1042 mutual += cv1 * np.sum(rr.K_SIA_shrink[1:] * c_n[1:])
1043 for n
in range(2, min(4, i_mobile + 1)):
1044 mutual += n * c_n[n-1] * rr.K_3D_cav_pref * np.dot(m13, c_v)
1045 for n
in range(4, min(I, i_mobile) + 1):
1046 k_pref = rr.K_1D_pref[n-1]
1049 mutual += n * c_n[n-1] * k_pref * np.dot(m13 / denom_m, c_v)
1053 vm = min(v_mobile, V)
1054 ms_v = np.arange(1.0, vm + 1)
1055 dydt[self.
i_J_VAC_fixed] = rr.k2_vac_scalar * np.dot(ms_v, c_v[:vm])
1063 sum_cv = np.sum(c_v)
1064 he_sink = rr.k2_He_scalar * c_h
1065 if sum_cv > 1e-300
and Q_tot > 0:
1066 ell_bar_loc = Q_tot / sum_cv
1067 ell_m_v = ell_bar_loc * np.arange(1.0, vm + 1) ** (2.0/3.0)
1068 he_sink += rr.k2_vac_scalar * np.dot(ell_m_v, c_v[:vm])