#include "mocca/kernel.hpp" #include "config_bridge.hpp" #include "embedded_tables.hpp" #include "line_codec.hpp" #include "physics_engine.hpp" #include #include #include #include #include namespace mocca { class ModernCascadeKernel::Impl { public: explicit Impl(const SimulationConfig& config) : config_(config), tables_(detail::bundled_numeric_tables()), primitives_(tables_, config_.numerics.matrix_element_precision_digits) {} [[nodiscard]] SimulationResult run() const { detail::KernelState cfg = detail::build_kernel_config(tables_, config_); primitives_.prepare_case(cfg); auto [lyman_sum, states] = propagate_cascade(cfg); auto lines = detail::collect_lines(cfg); return SimulationResult{ lyman_sum, static_cast(lines.size()), std::move(lines), std::move(states), cfg.warnings, }; } private: [[nodiscard]] static std::array popj(int l1, int l2, int ll) { std::array p{}; const int li = ll + 1; const int l = std::max(l1, l2); if (li == 1) { p[1] = static_cast(l + 1) / static_cast(2 * l + 1); p[2] = 1.0 - p[1]; return p; } if (li == 2) { p[1] = static_cast((l + 1) * (2 * l - 1)) / static_cast(4 * l * l - 1); p[2] = 1.0 / static_cast(4 * l * l - 1); p[3] = 1.0 - (p[1] + p[2]); return p; } if (li == 3) { if (l1 != l2) { p[1] = static_cast(l + 1) / static_cast(2 * l + 1); p[2] = 2.0 / static_cast((2 * l - 3) * (2 * l + 1)); p[3] = 1.0 - (p[1] + p[2]); return p; } const double d = static_cast((2 * l + 1) * (2 * l + 1)); p[1] = static_cast((l + 2) * (2 * l - 1)) / d; p[2] = static_cast((l - 1) * (2 * l + 3)) / d; p[3] = 3.0 / d; p[4] = p[3]; return p; } if (std::abs(l1 - l2) != 1) { p[1] = static_cast(l + 1) / static_cast(2 * l + 1); p[2] = 3.0 / static_cast((2 * l - 5) * (2 * l + 1)); p[3] = 1.0 - (p[1] + p[2]); return p; } const double d = static_cast(4 * l * l - 1); p[1] = static_cast((2 * l - 3) * (l + 2)) / d; p[2] = 5.0 / d; p[3] = 6.0 / d; p[4] = 1.0 - (p[1] + p[2] + p[3]); return p; } [[nodiscard]] static double beta(int l1, int j1, int l2, int j2, int l) { const double a1 = 0.25 * static_cast(j1 * (j1 + 2)); const double a2 = 0.25 * static_cast(j2 * (j2 + 2)); return ( (a2 - static_cast(l2 * (l2 + 1)) + 0.750) / (a1 - static_cast(l1 * (l1 + 1)) + 0.750)) * ((a1 + a2 - static_cast(l * (l + 1))) / (2.0 * a2)); } [[nodiscard]] std::pair> propagate_cascade( detail::KernelState& cfg) const { // The kernel keeps the original one-based workspace layout to preserve // the validated cascade-update order while expressing it in explicit // C++ containers instead of the monolithic runner entry point. std::vector popt(4, 0.0); const std::vector pop1 = cfg.pop; std::vector pop2(7, 0.0); popt[1] = 2.0 * cfg.pop[1]; popt[2] = 2.0 * cfg.pop[2] + 6.0 * cfg.pop[3]; popt[3] = 2.0 * cfg.pop[4] + 6.0 * cfg.pop[5] + 10.0 * cfg.pop[6]; pop2[1] = pop1[1]; pop2[2] = (popt[2] <= 1.0e-20) ? 1.0 : 1.0 / popt[2]; pop2[3] = (popt[2] <= 1.0e-20) ? 1.0 : 1.0 / popt[2]; pop2[4] = (popt[3] <= 1.0e-20) ? 1.0 : 1.0 / popt[3]; pop2[5] = (popt[3] <= 1.0e-20) ? 1.0 : 1.0 / popt[3]; pop2[6] = (popt[3] <= 1.0e-20) ? 1.0 : 1.0 / popt[3]; const int ms = 3; const double rk = cfg.widthk / cfg.hbar; const int nu = cfg.nmax * (cfg.nmax + 1) / 2; std::vector pnl(nu + 1, 0.0); std::vector polpos(nu + 1, 0.0); std::vector polneg(nu + 1, 0.0); std::vector width(nu + 1, 0.0); std::vector convc(nu + 1, 0.0); std::vector sporb(nu + 1, 0.0); std::vector pc0(nu + 1, 0.0); std::vector pc1(nu + 1, 0.0); std::vector pc2(nu + 1, 0.0); std::vector> pc(ms + 1, std::vector(nu + 1, 0.0)); const int mu = cfg.nmax * (cfg.nmax - 1) / 2; for (int j = 1; j <= nu; ++j) { if (cfg.ip8 != 0) { pnl[j] = cfg.pln[j]; } } for (int j = 1; j <= cfg.nmax; ++j) { const int jj = mu + j; if (cfg.ip8 == 0) { pnl[jj] = cfg.pl[j]; } pc1[jj] = 2.0 - 2.0 * cfg.pop[1]; pc2[jj] = 2.0 * cfg.pop[1] - 1.0; if (cfg.pop[1] <= 0.5) { pc0[jj] = 1.0 - 2.0 * cfg.pop[1]; pc1[jj] = 2.0 * cfg.pop[1]; pc2[jj] = 0.0; } if (pnl[jj] > 1.0e-20) { polpos[jj] = (1.0 + 2.0 / static_cast(2 * j - 1)) / 3.0; } if (pnl[jj] > 1.0e-20 && j != 1) { polneg[jj] = (1.0 - 2.0 / static_cast(2 * j - 1)) / 3.0; } for (int shell = 1; shell <= ms; ++shell) { pc[shell][jj] = popt[shell]; } } double rlyman = 0.0; std::vector zt(131, 0.0); std::vector> zt_shell(4, std::vector(131, 0.0)); std::vector zr(131, 0.0); std::vector za(131, 0.0); std::vector za0(131, 0.0); std::vector za1(131, 0.0); std::vector za2(131, 0.0); for (int i1 = 1; i1 <= cfg.nmax; ++i1) { const int n1 = cfg.nmax + 1 - i1; for (int i2 = 1; i2 <= n1; ++i2) { const int l1 = i2 - 1; double rategt = 0.0; double rate0 = 2.0 * rk; double rate1 = rk; double rateauger = 0.0; double raterd = 0.0; const int k1 = detail::state_index(n1, l1); if (i1 > 1) { if (pnl[k1] < 1.0e-20) { pc[2][k1] = popt[2]; pc[3][k1] = popt[3]; pc0[k1] = 0.0; pc1[k1] = 2.0 - 2.0 * pop2[1]; pc2[k1] = 2.0 * pop2[1] - 1.0; if (pop2[1] < 0.5) { pc0[k1] = 1.0 - 2.0 * pop2[1]; pc1[k1] = 2.0 * pop2[1]; pc2[k1] = 0.0; } pc[1][k1] = pc1[k1] + 2.0 * pc2[k1]; } else { const double xnorm = 1.0 / pnl[k1]; pc0[k1] *= xnorm; pc1[k1] *= xnorm; pc2[k1] *= xnorm; for (int shell = 2; shell <= ms; ++shell) { pc[shell][k1] = std::max(pc[shell][k1] * xnorm, 0.0); } pc[1][k1] = pc1[k1] + 2.0 * pc2[k1]; pc[1][k1] = std::min(std::max(pc[1][k1], 0.0), 2.0); for (int shell = 1; shell <= ms; ++shell) { if (cfg.ipc[shell] != 0) { pc[shell][k1] = popt[shell]; } } if (cfg.ipc[1] != 0) { pc2[k1] = 2.0 * pop1[1] - 1.0; pc1[k1] = 2.0 - 2.0 * pop1[1]; pc0[k1] = 0.0; if (pop1[1] < 0.5) { pc2[k1] = 0.0; pc1[k1] = 2.0 * pop1[1]; pc0[k1] = 1.0 - 2.0 * pop1[1]; } } polpos[k1] = polpos[k1] * xnorm * static_cast(2 * l1 + 1) / static_cast(l1 + 1); if (cfg.npol[n1] - l1 == 0 || cfg.npol[n1] - l1 == 1) { polpos[k1] = (1.0 + 2.0 / static_cast(2 * l1 + 1)) / 3.0; } if (l1 > 0) { polneg[k1] = polneg[k1] * xnorm * static_cast(2 * l1 + 1) / static_cast(l1); if (cfg.npol[n1] - l1 == 0 || cfg.npol[n1] - l1 == 1) { polneg[k1] = (1.0 - 2.0 / static_cast(2 * l1 + 1)) / 3.0; } } } } if (n1 == 1) { continue; } double pc012 = pc0[k1] + pc1[k1] + pc2[k1]; if (std::abs(pc012) < 1.0e-20) { pc012 = 1.0; } pc0[k1] /= pc012; pc1[k1] /= pc012; pc2[k1] /= pc012; cfg.pop[1] = 0.5 * pc[1][k1]; cfg.pop[2] = pop2[2] * pc[2][k1]; cfg.pop[3] = pop2[3] * pc[2][k1]; cfg.pop[4] = pop2[4] * pc[3][k1]; cfg.pop[5] = pop2[5] * pc[3][k1]; cfg.pop[6] = pop2[6] * pc[3][k1]; std::fill(zt.begin(), zt.end(), 0.0); for (auto& shell : zt_shell) { std::fill(shell.begin(), shell.end(), 0.0); } std::fill(zr.begin(), zr.end(), 0.0); std::fill(za.begin(), za.end(), 0.0); std::fill(za0.begin(), za0.end(), 0.0); std::fill(za1.begin(), za1.end(), 0.0); std::fill(za2.begin(), za2.end(), 0.0); int k = 0; for (int i3 = 1; i3 <= 7; ++i3) { const int l2 = l1 - 4 + i3; if (l2 < 0) { continue; } for (int i4 = 1; i4 <= n1; ++i4) { const int n2 = n1 - i4 + 1; if (n2 <= l2) { continue; } if (n2 == n1 && (n2 != 2 || l1 != 0 || l2 != 1)) { continue; } if (n1 == n2 && cfg.espm <= 1.0e-20) { continue; } k += 1; if (n1 == n2) { cfg.ijk = 1; cfg.energ = cfg.espm; } else { cfg.ijk = 0; } const double popq = cfg.pop[1]; cfg.pop[1] = 1.0; zt[k] = primitives_.transition_rate(cfg, n1, l1, n2, l2) + 1.0e-10; rategt += zt[k]; if (cfg.pop[1] < 1.0e-20) { cfg.pop[1] = 1.0; } cfg.rsa[1] = cfg.rsa[1] / cfg.pop[1]; cfg.pop[1] = popq; zr[k] = cfg.rad; raterd += cfg.rad; za[k] = cfg.rsa[1]; // `rad_to_auger` used to reconstruct the effective Auger // width from `effective_total - radiative`, which loses // several digits when the state is almost purely // radiative. Accumulating the Auger contribution directly // keeps the same physics while avoiding that cancellation. const double transition_auger = cfg.rau + 1.0e-10; rateauger += pc2[k1] * transition_auger + pc1[k1] * (transition_auger - 0.5 * za[k]) + pc0[k1] * (transition_auger - za[k]); const double xr = zt[k] - cfg.rsa[1]; double t0 = xr + 0.5 * cfg.rsa[1] + rk; double g0 = xr + 2.0 * rk + 1.0e-10; rate1 = rate1 + t0 - rk + 1.0e-10; rate0 = rate0 + xr + 1.0e-10; double xm = zt[k] - za[k] * (pc0[k1] + 0.5 * pc1[k1]); if (std::abs(xm) < 1.0e-10) { xm = 1.0; } if (std::abs(t0) < 1.0e-10) { t0 = 1.0; } if (std::abs(g0) < 1.0e-10) { g0 = 1.0; } if (std::abs(zt[k]) < 1.0e-10) { zt[k] = 1.0; } zt_shell[3][k] = pc[3][k1] - cfg.rsa[3] / xm; if (zt_shell[3][k] < 0.0) { zt_shell[3][k] = 0.0; } za2[k] = xr / zt[k] * (pc2[k1] + rk * pc1[k1] / t0 + 2.0 * rk * rk * pc0[k1] / t0 / g0); za1[k] = za[k] * pc2[k1] / zt[k] + (xr + rk * za[k] / zt[k]) / t0 * (pc1[k1] + 2.0 * rk * pc0[k1] / g0); za0[k] = pc1[k1] * za[k] / (2.0 * t0) + pc0[k1] / g0 * (xr + rk * za[k] / t0); const double ym = za[k] * ( pc2[k1] / zt[k] + pc1[k1] * (0.5 + rk / zt[k]) / t0 + pc0[k1] * rk / t0 / g0 * (1.0 + 2.0 * rk / zt[k])); const double xl = rk * (pc1[k1] / t0 + 2.0 * pc0[k1] / t0 / g0 * (t0 + rk)); zt_shell[1][k] = pc[1][k1] - ym + xl; zt_shell[2][k] = pc[2][k1] - cfg.rsa[2] / xm - xl; if (zt_shell[1][k] < 0.0) { zt_shell[1][k] = 0.0; } if (zt_shell[2][k] < 0.0) { zt_shell[2][k] = 0.0; } } } width[k1] = (rategt * pc2[k1] + (rate1 - rk) * pc1[k1] + (rate0 - 2.0 * rk) * pc0[k1]) * cfg.hbar; if (width[k1] < 0.0) { width[k1] = 0.0; } if (std::abs(rate0) < 1.0e-10) { rate0 = 1.0; } if (std::abs(rate1) < 1.0e-10) { rate1 = 1.0; } if (std::abs(rategt) < 1.0e-10) { rategt = 1.0; } convc[k1] = raterd / (rateauger + 1.0e-10); if (convc[k1] < 0.0) { convc[k1] = 9.999e99; } if (l1 != 0) { sporb[k1] = 0.150 * std::pow(cfg.z, 4) / static_cast(n1 * n1 * n1 * l1 * (l1 + 1)); } k = 0; for (int i3 = 1; i3 <= 7; ++i3) { const int l2 = l1 - 4 + i3; if (l2 < 0) { continue; } for (int i4 = 1; i4 <= n1; ++i4) { const int n2 = n1 - i4 + 1; if (n2 <= l2) { continue; } if (n2 == n1 && (n2 != 2 || l1 != 0 || l2 != 1)) { continue; } if (n2 == n1 && cfg.espm <= 1.0e-20) { continue; } k += 1; const int k2 = detail::state_index(n2, l2); const double bnorm = pnl[k1] * ( pc2[k1] * zt[k] / rategt + pc1[k1] * (zt[k] - 0.5 * za[k] + rk * zt[k] / rategt) / rate1 + pc0[k1] * (zt[k] - za[k] + 2.0 * rk / rate1 * (zt[k] - 0.5 * za[k] + rk * zt[k] / rategt)) / rate0); for (int shell = 1; shell <= ms; ++shell) { pc[shell][k2] += zt_shell[shell][k] * bnorm; } pnl[k2] += bnorm; pc2[k2] += za2[k] * bnorm; pc1[k2] += za1[k] * bnorm; pc0[k2] += za0[k] * bnorm; const double radint = pnl[k1] * zr[k] * (pc2[k1] / rategt + pc1[k1] * (1.0 + rk / rategt) / rate1 + pc0[k1] * (1.0 + 2.0 * rk / rate1 * (1.0 + rk / rategt)) / rate0); const int ll = std::abs(l1 - l2); const auto p = popj(l1, l2, ll); if (cfg.ipol == 0) { const int li = ll + 1; const int j1u = 2 * l1 + 1; const int j2u = 2 * l2 + 1; const int j1d = j1u - 2; const int j2d = j2u - 2; if (li <= 1) { polpos[k2] += bnorm * p[1] * polpos[k1]; polneg[k2] += bnorm * p[2] * polneg[k1]; } else if (l2 <= l1) { polpos[k2] += bnorm * (p[1] * polpos[k1] * beta(l1, j1u, l2, j2u, ll) + p[2] * polneg[k1] * beta(l1, j1d, l2, j2u, ll)); if (j1d != -1 && j2d != -1) { polneg[k2] += bnorm * p[3] * polneg[k1] * beta(l1, j1d, l2, j2d, ll); } } else { polpos[k2] += bnorm * p[1] * polpos[k1] * beta(l1, j1u, l2, j2u, ll); polneg[k2] += bnorm * (p[2] * polpos[k1] * beta(l1, j1u, l2, j2d, ll) + p[3] * polneg[k1] * beta(l1, j1d, l2, j2d, ll)); } } const int li = (ll != 0) ? ll : 2; detail::record_lines(cfg, n1, l1, n2, l2, li, radint); if (l1 == 1 && l2 == 0 && n2 == 1) { rlyman += radint; } } } } } std::vector states; states.reserve(static_cast(cfg.nmax * (cfg.nmax + 1) / 2)); pc[1][1] = pc1[1] + 2.0 * pc2[1]; for (int m1 = 1; m1 <= cfg.nmax; ++m1) { const int n1 = cfg.nmax + 1 - m1; for (int ll1 = 1; ll1 <= n1; ++ll1) { const int l1 = ll1 - 1; const int k1 = detail::state_index(n1, l1); std::optional polar_down; if (l1 > 0) { polar_down = -polneg[k1] * (static_cast(l1) + 0.5) / (static_cast(l1) - 0.5); } std::optional polar_up = (cfg.ipol == 1) ? std::optional{} : std::optional{polpos[k1]}; std::optional width_ev = (n1 == 1 || (l1 == 0 && pnl[k1] > 1.0e-20)) ? std::optional{} : std::optional{width[k1]}; std::optional rad_to_auger = (n1 == 1 || (l1 == 0 && pnl[k1] > 1.0e-20)) ? std::optional{} : std::optional{convc[k1]}; std::optional spin_orbit_ev = (cfg.ipol == 1 || n1 == 1 || (l1 == 0 && pnl[k1] > 1.0e-20)) ? std::optional{} : std::optional{sporb[k1]}; states.push_back(StateSummary{ n1, l1, pnl[k1], polar_up, polar_down, width_ev, rad_to_auger, spin_orbit_ev, pc[1][k1], pc[2][k1], pc[3][k1], }); } } for (int i = 1; i <= 6; ++i) { cfg.pop[i] = pop1[i]; } return {rlyman, std::move(states)}; } SimulationConfig config_; detail::NumericTables tables_; detail::PhysicsEngine primitives_; }; ModernCascadeKernel::ModernCascadeKernel(const SimulationConfig& config) : impl_(std::make_unique(config)) {} ModernCascadeKernel::~ModernCascadeKernel() = default; ModernCascadeKernel::ModernCascadeKernel(ModernCascadeKernel&&) noexcept = default; ModernCascadeKernel& ModernCascadeKernel::operator=(ModernCascadeKernel&&) noexcept = default; SimulationResult ModernCascadeKernel::run() const { return impl_->run(); } SimulationResult run_modern_kernel(const SimulationConfig& config) { return ModernCascadeKernel(config).run(); } } // namespace mocca