28inline double thermalVelocity(
double Te)
30 return std::sqrt(8.0 * kKB * Te / (kPI * kME));
46inline double coulombLogMitchner(
double n_e,
double Te,
int Z)
48 if (n_e <= 0.0 || Te <= 0.0)
return 1.0;
49 const double lambda_D = std::sqrt(kEPS0 * kKB * Te / (n_e * kE * kE));
50 const double b0_bar =
static_cast<double>(Z) * kE * kE
51 / (12.0 * kPI * kEPS0 * kKB * Te);
52 const double lnL = std::log(lambda_D / b0_bar);
53 return (lnL > 1.0) ? lnL : 1.0;
65double ionCollisionPrefactor(
int Z)
67 const double a = 4.0 * std::sqrt(2.0 * kPI) / 3.0;
68 const double b = std::pow(kME / kKB, 1.5);
69 const double c = kE * kE / (4.0 * kPI * kEPS0 * kME);
70 return a * b *
c *
c *
static_cast<double>(Z) *
static_cast<double>(Z);
76 const std::vector<double>& sigma_m2,
double Te,
88 if (N < 2 || energy_J.empty())
return 0.0;
96 const double dx = x_max /
static_cast<double>(N - 1);
97 double integral = 0.0;
99 for (
int i = 1; i < N; i++) {
100 const double x = dx *
static_cast<double>(i);
101 const double energy = kKB * Te * x;
102 const double Q = sigma_table.
eval(energy);
103 const double cur = (2.0 / 3.0) * x * x * std::exp(-x) * Q;
104 integral += 0.5 * (prev + cur) * dx;
111 const std::vector<double>& energy_J,
const std::vector<double>& sigma_m2,
112 const std::vector<double>& Te,
double x_max,
int N)
114 std::vector<double> out(Te.size());
115 for (std::size_t i = 0; i < Te.size(); i++) {
122 double Te_max, std::size_t Te_n,
bool spitzer_correction)
123 : m_spitzer(spitzer_correction)
127 if (Te_n < 2) Te_n = 2;
128 std::vector<double> Te_grid(Te_n);
129 const double llo = std::log(Te_min), lhi = std::log(Te_max);
130 for (std::size_t i = 0; i < Te_n; i++) {
131 Te_grid[i] = std::exp(llo + (lhi - llo) *
static_cast<double>(i)
132 /
static_cast<double>(Te_n - 1));
135 m_species.resize(specs.size());
136 for (std::size_t k = 0; k < specs.size(); k++) {
138 Sp& sp = m_species[k];
144 sp.two_me_over_mh = 2.0 * kME * kNA / s.
molar_mass;
148 sp.kind = Kind::Skip;
149 }
else if (s.
Z > 0) {
155 sp.cst = ionCollisionPrefactor(s.
Z);
157 sp.kind = Kind::Tabulated;
161 std::vector<double> Qbar =
164 }
else if (s.
radius > 0.0) {
165 sp.kind = Kind::HardSphere;
168 sp.kind = Kind::Skip;
174 double& sigma,
double& nu_E,
double& nu_eH,
double& nu_eI,
175 double& nu_eH_mass_weighted)
const
181 nu_eH_mass_weighted = 0.0;
182 if (m_species.empty() || Te <= 0.0)
return;
184 const double v_th = thermalVelocity(Te);
185 double nu_eH_sum = 0.0, nu_ions_sum = 0.0, nu_E_sum = 0.0, nu_eH_mass_weighted_sum = 0.0;
187 const std::size_t K = std::min(nsp, m_species.size());
188 for (std::size_t k = 0; k < K; k++) {
189 const Sp& sp = m_species[k];
190 if (sp.kind == Kind::Skip)
continue;
191 const double n = n_k[k];
192 if (n <= 0.0)
continue;
195 if (sp.kind == Kind::Ion) {
202 const double lnL = coulombLogMitchner(n_e, Te, sp.Z);
203 nu1 = n * sp.cst * std::pow(Te, -1.5) * lnL;
212 nu1 = n * sp.Qbar.
eval(Te) * v_th;
221 nu_E_sum += sp.two_me_over_mh * nu1;
226 nu_eH_mass_weighted_sum += sp.inv_mh * nu1;
232 nu_eH_mass_weighted = nu_eH_mass_weighted_sum;
233 if (nu_eH > 0.0 && n_e > 0.0) {
234 sigma = n_e * kE * kE / (kME * nu_eH);
242 const double f_ion = nu_eI / nu_eH;
243 sigma *= 1.0 + 0.98 * f_ion;
254 if (n_e <= 0.0 || Te <= 0.0)
return 0.0;
255 static const double cst_ee = ionCollisionPrefactor(1);
256 const double lnL = coulombLogMitchner(n_e, Te, 1);
257 return n_e * cst_ee * std::pow(Te, -1.5) * lnL;
static double electronElectronCollisionFrequency(double n_e, double Te)
Electron-electron collision frequency [1/s] (Mitchner II-8.11e with Z=1), the closed-form ingredient...
static double meanCrossSection(const std::vector< double > &energy_J, const std::vector< double > &sigma_m2, double Te, double x_max=20.0, int N=1000)
The Maxwellian momentum-transfer kernel Qbar(Te) [m^2] (single source of truth; also exposed to Pytho...
void evaluate(double Te, double n_e, const double *n_k, std::size_t nsp, double &sigma, double &nu_E, double &nu_eH, double &nu_eI, double &nu_eH_mass_weighted) const
Composition-resolved electron transport.
static std::vector< double > meanCrossSectionTable(const std::vector< double > &energy_J, const std::vector< double > &sigma_m2, const std::vector< double > &Te, double x_max=20.0, int N=1000)
Tabulated variant of meanCrossSection, evaluated at each Te in the input vector.
static PropertyTable constant(double value)
A two-point constant table returning value for any T.
double eval(double T) const
Linearly interpolated value at T; clamped to the table endpoints.
static PropertyTable fromTableOrScalar(const std::vector< double > &x, const std::vector< double > &v)
Build a table from a grid/value pair, a single scalar, or neither.
constexpr double k_b
Boltzmann constant [J/K]. Exact (CODATA).
constexpr double epsilon_0
Vacuum permittivity [F/m] (CODATA 2022).
constexpr double pi
pi (matches numpy's np.pi used in units.py).
constexpr double N_a
Avogadro's number [1/mol]. Exact (CODATA).
constexpr double c
Speed of light [m/s]. Exact (CODATA).
constexpr double e
Elementary charge [C]. Exact (CODATA).
constexpr double m_e
Electron mass [kg] (CODATA 2022).
Per-species spec, in species-index order.
bool is_electron
true for the electron species (skipped)
std::vector< double > energy_J
tabulated cross-section abscissa [J]
double molar_mass
[kg/mol], for the elastic 2 m_e/m_h factor
double radius
hard-sphere radius [m] (>0 if used)
std::vector< double > sigma_m2
tabulated cross-section [m^2]
int Z
ion charge number (>0 marks an ion)
Physical constants and unit conversions, in SI by default.