27inline double thermalVelocity(
double Te)
29 return std::sqrt(8.0 * kKB * Te / (kPI * kME));
45inline double coulombLogMitchner(
double n_e,
double Te,
int Z)
47 if (n_e <= 0.0 || Te <= 0.0)
return 1.0;
48 const double lambda_D = std::sqrt(kEPS0 * kKB * Te / (n_e * kE * kE));
49 const double b0_bar =
static_cast<double>(Z) * kE * kE
50 / (12.0 * kPI * kEPS0 * kKB * Te);
51 const double lnL = std::log(lambda_D / b0_bar);
52 return (lnL > 1.0) ? lnL : 1.0;
64double ionCollisionPrefactor(
int Z)
66 const double a = 4.0 * std::sqrt(2.0 * kPI) / 3.0;
67 const double b = std::pow(kME / kKB, 1.5);
68 const double c = kE * kE / (4.0 * kPI * kEPS0 * kME);
69 return a * b * c * c *
static_cast<double>(Z) *
static_cast<double>(Z);
75 const std::vector<double>& sigma_m2,
double Te,
87 if (N < 2 || energy_J.empty())
return 0.0;
95 const double dx = x_max /
static_cast<double>(N - 1);
96 double integral = 0.0;
98 for (
int i = 1; i < N; i++) {
99 const double x = dx *
static_cast<double>(i);
100 const double energy = kKB * Te * x;
101 const double Q = sigma_table.
eval(energy);
102 const double cur = (2.0 / 3.0) * x * x * std::exp(-x) * Q;
103 integral += 0.5 * (prev + cur) * dx;
110 const std::vector<double>& energy_J,
const std::vector<double>& sigma_m2,
111 const std::vector<double>& Te,
double x_max,
int N)
113 std::vector<double> out(Te.size());
114 for (std::size_t i = 0; i < Te.size(); i++) {
121 double Te_max, std::size_t Te_n,
bool spitzer_correction)
122 : m_spitzer(spitzer_correction)
126 if (Te_n < 2) Te_n = 2;
127 std::vector<double> Te_grid(Te_n);
128 const double llo = std::log(Te_min), lhi = std::log(Te_max);
129 for (std::size_t i = 0; i < Te_n; i++) {
130 Te_grid[i] = std::exp(llo + (lhi - llo) *
static_cast<double>(i)
131 /
static_cast<double>(Te_n - 1));
134 m_species.resize(specs.size());
135 for (std::size_t k = 0; k < specs.size(); k++) {
137 Sp& sp = m_species[k];
143 sp.two_me_over_mh = 2.0 * kME * kNA / s.
molar_mass;
147 sp.kind = Kind::Skip;
148 }
else if (s.
Z > 0) {
154 sp.cst = ionCollisionPrefactor(s.
Z);
156 sp.kind = Kind::Tabulated;
160 std::vector<double> Qbar =
163 }
else if (s.
radius > 0.0) {
164 sp.kind = Kind::HardSphere;
167 sp.kind = Kind::Skip;
173 double& sigma,
double& nu_E,
double& cond_maxwell_2)
const
177 cond_maxwell_2 = 0.0;
178 if (m_species.empty() || Te <= 0.0)
return;
180 const double v_th = thermalVelocity(Te);
181 double nu_eH = 0.0, nu_ions = 0.0, nu_E_sum = 0.0, cond_maxwell_2_sum = 0.0;
183 const std::size_t K = std::min(nsp, m_species.size());
184 for (std::size_t k = 0; k < K; k++) {
185 const Sp& sp = m_species[k];
186 if (sp.kind == Kind::Skip)
continue;
187 const double n = n_k[k];
188 if (n <= 0.0)
continue;
191 if (sp.kind == Kind::Ion) {
198 const double lnL = coulombLogMitchner(n_e, Te, sp.Z);
199 nu1 = n * sp.cst * std::pow(Te, -1.5) * lnL;
208 nu1 = n * sp.Qbar.
eval(Te) * v_th;
217 nu_E_sum += sp.two_me_over_mh * nu1;
222 cond_maxwell_2_sum += sp.inv_mh * nu1;
226 cond_maxwell_2 = cond_maxwell_2_sum;
227 if (nu_eH > 0.0 && n_e > 0.0) {
228 sigma = n_e * kE * kE / (kME * nu_eH);
236 const double f_ion = nu_ions / nu_eH;
237 sigma *= 1.0 + 0.98 * f_ion;
243 std::size_t nsp)
const
245 double sigma = 0.0, nu_E = 0.0, cond_maxwell_2 = 0.0;
246 evaluate(Te, n_e, n_k, nsp, sigma, nu_E, cond_maxwell_2);
256 if (n_e <= 0.0 || Te <= 0.0)
return 0.0;
257 static const double cst_ee = ionCollisionPrefactor(1);
258 const double lnL = coulombLogMitchner(n_e, Te, 1);
259 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...
double conductivity(double Te, double n_e, const double *n_k, std::size_t nsp) const
Conductivity only [S/m].
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...
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.
void evaluate(double Te, double n_e, const double *n_k, std::size_t nsp, double &sigma, double &nu_E, double &cond_maxwell_2) const
Composition-resolved electron transport.
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 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.