36inline double thermalVelocity(
double Te)
38 return std::sqrt(8.0 * kKB * Te / (kPI * kME));
56inline double coulombLogMitchner(
double n_e,
double Te,
int Z)
58 if (!(n_e > 0.0) || !(Te > 0.0)) {
60 "CollisionModel::coulombLogMitchner",
61 "the Coulomb logarithm needs a positive electron "
62 "density and temperature, got n_e = " + std::to_string(n_e)
63 +
" m^-3, Te = " + std::to_string(Te) +
" K.");
65 const double lambda_D = std::sqrt(kEPS0 * kKB * Te / (n_e * kE * kE));
66 const double b0_bar =
static_cast<double>(Z) * kE * kE
67 / (12.0 * kPI * kEPS0 * kKB * Te);
68 if (lambda_D < b0_bar) {
70 "CollisionModel::coulombLogMitchner",
71 "the Debye length " + std::to_string(lambda_D)
72 +
" m is below the mean impact parameter " + std::to_string(b0_bar)
73 +
" m (n_e = " + std::to_string(n_e) +
" m^-3, Te = "
74 + std::to_string(Te) +
" K), so the Coulomb logarithm is undefined.");
76 return std::log(lambda_D / b0_bar);
88double ionCollisionPrefactor(
int Z)
90 const double a = 4.0 * std::sqrt(2.0 * kPI) / 3.0;
91 const double b = std::pow(kME / kKB, 1.5);
92 const double c = kE * kE / (4.0 * kPI * kEPS0 * kME);
93 return a * b *
c *
c *
static_cast<double>(Z) *
static_cast<double>(Z);
103 double Te_max, std::size_t Te_n)
108 throw std::invalid_argument(
109 "CollisionModel: Te_n must be at least 2 to span [Te_min, Te_max], got "
110 + std::to_string(Te_n) +
".");
112 if (!(Te_min > 0.0 && Te_max > Te_min)) {
113 throw std::invalid_argument(
114 "CollisionModel: the Te grid needs 0 < Te_min < Te_max, got Te_min = "
115 + std::to_string(Te_min) +
" K, Te_max = " + std::to_string(Te_max) +
" K.");
117 std::vector<double> Te_grid(Te_n);
118 const double llo = std::log(Te_min), lhi = std::log(Te_max);
119 for (std::size_t i = 0; i < Te_n; i++) {
120 Te_grid[i] = std::exp(llo + (lhi - llo) *
static_cast<double>(i)
121 /
static_cast<double>(Te_n - 1));
124 m_species.resize(specs.size());
125 for (std::size_t k = 0; k < specs.size(); k++) {
127 SpeciesCollisionConfig& sp = m_species[k];
133 sp.two_me_over_mh = 2.0 * kME * kNA / s.
molar_mass;
137 sp.kind = SpeciesCollisionKind::Skip;
138 }
else if (s.
Z > 0) {
139 sp.kind = SpeciesCollisionKind::Ion;
144 sp.cst = ionCollisionPrefactor(s.
Z);
152 throw std::invalid_argument(
153 "CollisionModel: species " + std::to_string(k)
154 +
" has a tabulated cross section of "
155 + std::to_string(s.
energy_J.size()) +
" energies against "
157 +
" values. They must be the same length and at least 2 "
158 "points. Leave both empty to use the hard-sphere radius "
159 "or to skip this species.");
161 sp.kind = SpeciesCollisionKind::Tabulated;
165 std::vector<double> Qbar =
168 }
else if (s.
radius > 0.0) {
169 sp.kind = SpeciesCollisionKind::HardSphere;
172 sp.kind = SpeciesCollisionKind::Skip;
182 double& sigma,
double& nu_E,
double& nu_eH,
double& nu_eI,
183 double& nu_eH_mass_weighted)
const
189 nu_eH_mass_weighted = 0.0;
196 "CollisionModel::evaluate",
"Te = " + std::to_string(Te)
197 +
" K; the electron temperature must be positive.");
199 if (m_species.empty())
return;
202 if (nsp != m_species.size()) {
203 throw std::invalid_argument(
204 "CollisionModel::evaluate: got number densities for "
205 + std::to_string(nsp) +
" species, but the model was built for "
206 + std::to_string(m_species.size()) +
".");
209 const double v_th = thermalVelocity(Te);
210 double nu_eH_sum = 0.0, nu_ions_sum = 0.0, nu_E_sum = 0.0, nu_eH_mass_weighted_sum = 0.0;
212 for (std::size_t k = 0; k < nsp; k++) {
213 const SpeciesCollisionConfig& sp = m_species[k];
214 if (sp.kind == SpeciesCollisionKind::Skip)
continue;
215 const double n = n_k[k];
216 if (n <= 0.0)
continue;
219 if (sp.kind == SpeciesCollisionKind::Ion) {
226 const double lnL = coulombLogMitchner(n_e, Te, sp.Z);
227 nu1 = n * sp.cst * std::pow(Te, -1.5) * lnL;
236 if (!sp.Qbar.
covers(Te)) {
238 "the collision cross section Qbar(Te) of species " +
240 "K",
"Widen the collision-model grid with Te_min/Te_max.");
242 nu1 = n * sp.Qbar.
eval(Te) * v_th;
251 nu_E_sum += sp.two_me_over_mh * nu1;
256 nu_eH_mass_weighted_sum += sp.inv_mh * nu1;
262 nu_eH_mass_weighted = nu_eH_mass_weighted_sum;
263 if (nu_eH > 0.0 && n_e > 0.0) {
264 sigma = n_e * kE * kE / (kME * nu_eH);
271 const double f_ion = nu_eI / nu_eH;
272 sigma *= 1.0 + 0.98 * f_ion;
289 "CollisionModel::electronElectronCollisionFrequency",
290 "Te = " + std::to_string(Te) +
" K; the electron temperature must be positive.");
292 if (n_e <= 0.0)
return 0.0;
293 static const double cst_ee = ionCollisionPrefactor(1);
294 const double lnL = coulombLogMitchner(n_e, Te, 1);
295 return n_e * cst_ee * std::pow(Te, -1.5) * lnL;
300 const std::vector<double>& sigma_m2,
double Te,
313 throw std::invalid_argument(
314 "CollisionModel::meanCrossSection: the trapezoid rule needs N >= 2 "
315 "points, got N = " + std::to_string(N) +
".");
317 if (energy_J.empty()) {
318 throw std::invalid_argument(
319 "CollisionModel::meanCrossSection: the cross section table is empty.");
329 "the cross section energy_J/sigma_m2");
330 const double dx = x_max /
static_cast<double>(N - 1);
331 double integral = 0.0;
333 for (
int i = 1; i < N; i++) {
334 const double x = dx *
static_cast<double>(i);
335 const double energy = kKB * Te * x;
336 const double Q = sigma_table.
eval(energy);
337 const double cur = (2.0 / 3.0) * x * x * std::exp(-x) * Q;
338 integral += 0.5 * (prev + cur) * dx;
346 const std::vector<double>& energy_J,
const std::vector<double>& sigma_m2,
347 const std::vector<double>& Te,
double x_max,
int N)
349 std::vector<double> out(Te.size());
350 for (std::size_t i = 0; i < Te.size(); i++) {
Evaluate a PropertyTable at a state, refusing a state outside its range.
The error a closure raises for a state it cannot evaluate, so that the integrator proposing that stat...
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.
bool covers(double x) const
Whether eval() interpolates at x rather than holding an end value.
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, const char *name="property table")
Build a table from a grid/value pair, a single scalar, or neither.
TrialStateError(const std::string &procedure, std::string message)
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).
void throwOutsideTable(const PropertyTable &table, double x, const char *procedure, const std::string &what, const char *unit, const char *remedy)
Throw the TrialStateError for x outside table.
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.