12#include "cantera/numerics/eigen_dense.h"
21typedef Eigen::SparseMatrix<double> SparseMat;
23EEDFTwoTermApproximation::EEDFTwoTermApproximation(
PlasmaPhase* s)
39 for (
size_t j = 0; j <
m_points; j++) {
57 double n =
static_cast<double>(
m_points);
59 for (
size_t j = 0; j <=
m_points; j++) {
60 double x =
static_cast<double>(j);
61 m_gridEdge[j] = kTe_max * x * (x + 1.0) / (n * (n + 1.0));
64 for (
size_t j = 0; j <
m_points; j++) {
76 throw CanteraError(
"EEDFTwoTermApproximation::setDirectGeometricGrid",
77 "Number of cells must be positive.");
81 throw CanteraError(
"EEDFTwoTermApproximation::setDirectGeometricGrid",
82 "Maximum electron energy must be positive.");
86 throw CanteraError(
"EEDFTwoTermApproximation::setDirectGeometricGrid",
87 "Geometric ratio must be positive.");
90 if (std::abs(ratio - 1.0) < 1e-14) {
96 throw CanteraError(
"EEDFTwoTermApproximation::setDirectGeometricGrid",
97 "For an increasing direct geometric grid, ratio must be larger than 1.");
122 double firstEdge = kTe_max / std::pow(ratio,
static_cast<double>(
m_points - 1));
126 for (
size_t j = 2; j <=
m_points; j++) {
133 for (
size_t j = 0; j <
m_points; j++) {
143 checkArraySize(
"EEDFTwoTermApproximation::setCustomGrid", levels.size(), 2);
152 for (
size_t j = 0; j <
m_points + 1; j++) {
153 if (!std::isfinite(levels[j])) {
154 throw CanteraError(
"EEDFTwoTermApproximation::setCustomGrid",
155 "Energy grid contains a non-finite value.");
157 if (levels[j] < 0.0) {
158 throw CanteraError(
"EEDFTwoTermApproximation::setCustomGrid",
159 "Energy grid values must be non-negative.");
161 if (j > 0 && levels[j] <= levels[j - 1]) {
162 throw CanteraError(
"EEDFTwoTermApproximation::setCustomGrid",
163 "Energy grid values must be strictly increasing.");
169 for (
size_t j = 0; j <
m_points; j++) {
207 for (
size_t i = 0; i <
m_points + 1; i++) {
219 double x, span<const double> xpts, span<const double> fpts,
double below_value,
222 AssertThrowMsg(!xpts.empty(),
"linearInterpBounded",
"x data empty");
223 AssertThrowMsg(!fpts.empty(),
"linearInterpBounded",
"f(x) data empty");
225 "len(xpts) = {}, len(fpts) = {}", xpts.size(), fpts.size());
227 if (x < xpts.front()) {
231 if (x > xpts.back()) {
239 const Eigen::VectorXd& oldGridCenter,
const Eigen::VectorXd& oldF0)
241 if (oldGridCenter.size() != oldF0.size() || oldGridCenter.size() < 2) {
242 throw CanteraError(
"EEDFTwoTermApproximation::projectPreviousEEDFOnCurrentGrid",
243 "Previous EEDF and grid must have matching sizes of at least two points.");
246 const double fFloor = 1e-300;
248 vector<double> oldGrid(oldGridCenter.data(),
249 oldGridCenter.data() + oldGridCenter.size());
251 vector<double> oldF(oldF0.data(),
252 oldF0.data() + oldF0.size());
254 for (
size_t j = 0; j <
m_points; j++) {
255 m_f0(j) = std::max(fFloor,
261 if (!std::isfinite(fnorm) || fnorm <= 0.0) {
262 throw CanteraError(
"EEDFTwoTermApproximation::projectPreviousEEDFOnCurrentGrid",
263 "Invalid norm after projecting previous EEDF onto the adapted grid.");
270 const double fFloor = 1e-300;
273 double fLeft = std::max(std::abs(
m_f0(0)), fFloor);
274 double fRight = std::max(std::abs(
m_f0(
m_points - 1)), fFloor);
275 double decades = std::log10(fLeft) - std::log10(fRight);
277 if (!std::isfinite(decades)) {
278 throw CanteraError(
"EEDFTwoTermApproximation::adaptEnergyGrid",
279 "Non-finite EEDF decay detected during grid adaptation.");
286 Eigen::VectorXd oldF0 =
m_f0;
300 Eigen::VectorXd oldF0 =
m_f0;
318 if (!std::isfinite(kTe) || kTe <= 0.0) {
319 throw CanteraError(
"EEDFTwoTermApproximation::setMaxwellianDistribution",
320 "Invalid electron temperature for Maxwellian EEDF: {}", kTe);
323 const double prefactor = 2.0 * std::numbers::inv_sqrtpi * std::pow(kTe, -1.5);
325 for (
size_t j = 0; j <
m_points; j++) {
331 if (!std::isfinite(fNorm) || fNorm <= 0.0) {
332 throw CanteraError(
"EEDFTwoTermApproximation::setMaxwellianDistribution",
333 "Invalid normalization factor for Maxwellian EEDF.");
346 throw CanteraError(
"EEDFTwoTermApproximation::converge",
347 "m_maxn is zero; no iterations will occur.");
350 throw CanteraError(
"EEDFTwoTermApproximation::converge",
351 "m_points is zero; the EEDF grid is empty.");
353 if (isnan(delta) || delta == 0.0) {
354 throw CanteraError(
"EEDFTwoTermApproximation::converge",
355 "m_delta0 is NaN or zero; solver cannot update.");
358 for (
size_t n = 0; n <
m_maxn; n++) {
359 if (0.0 < err1 && err1 < err0) {
360 delta *= log(
m_factorM) / (log(err0) - log(err1));
363 Eigen::VectorXd f0_old = f0;
368 Eigen::VectorXd Df0 = (f0_old - f0).cwiseAbs();
372 }
else if (n ==
m_maxn - 1) {
373 throw CanteraError(
"WeaklyIonizedGas::converge",
"Convergence failed");
395 for (
size_t i = 0; i <
m_points; i++) {
403 Eigen::SparseLU<SparseMat> solver(A);
404 if (solver.info() == Eigen::NumericalIssue) {
406 "Error SparseLU solver: NumericalIssue");
407 }
else if (solver.info() == Eigen::InvalidInput) {
409 "Error SparseLU solver: InvalidInput");
411 if (solver.info() != Eigen::Success) {
413 "Error SparseLU solver",
"Decomposition failed");
418 Eigen::VectorXd f1 = solver.solve(f0);
419 if(solver.info() != Eigen::Success) {
420 throw CanteraError(
"EEDFTwoTermApproximation::iterate",
"Solving failed");
435 double expm1a = expm1(g * (-a + x0));
436 double expm1b = expm1(g * (-b + x0));
441 A1 = (expm1a * ag1 + ag - expm1b * bg1 - bg) / (g*g);
442 A2 = (expm1a * (2 * ag1 + ag * ag) + ag * (ag + 2) -
443 expm1b * (2 * bg1 + bg * bg) - bg * (bg + 2)) / (g*g*g);
445 A1 = 0.5 * (b*b - a*a);
446 A2 = 1.0 / 3.0 * (b*b*b - a*a*a);
450 double c0 = (a * u1 - b * u0) / (a - b);
451 double c1 = (u0 - u1) / (a - b);
453 return c0 * A1 + c1 * A2;
459 const double f_min = 1e-300;
462 double f1 = std::max(f0(1), f_min);
463 double f0_ = std::max(f0(0), f_min);
468 double fN = std::max(f0(N), f_min);
469 double fNm1 = std::max(f0(N - 1), f_min);
473 for (
size_t i = 1; i < N; ++i) {
474 double f_up = std::max(f0(i + 1), f_min);
475 double f_down = std::max(f0(i - 1), f_min);
483 SparseTriplets tripletList;
484 for (
size_t n = 0; n <
m_eps[k].size(); n++) {
485 double eps_a =
m_eps[k][n][0];
486 double eps_b =
m_eps[k][n][1];
487 double sigma_a =
m_sigma[k][n][0];
488 double sigma_b =
m_sigma[k][n][1];
489 auto j =
static_cast<SparseMat::StorageIndex
>(
m_j[k][n]);
493 tripletList.emplace_back(j, j, p);
496 P.setFromTriplets(tripletList.begin(), tripletList.end());
502 SparseTriplets tripletList;
503 for (
size_t n = 0; n <
m_eps[k].size(); n++) {
504 double eps_a =
m_eps[k][n][0];
505 double eps_b =
m_eps[k][n][1];
506 double sigma_a =
m_sigma[k][n][0];
507 double sigma_b =
m_sigma[k][n][1];
508 auto i =
static_cast<SparseMat::StorageIndex
>(
m_i[k][n]);
509 auto j =
static_cast<SparseMat::StorageIndex
>(
m_j[k][n]);
513 tripletList.emplace_back(i, j, q);
516 Q.setFromTriplets(tripletList.begin(), tripletList.end());
538 alpha = (mu * E - sqrt(pow(mu * E, 2) - 4 * D * nu * nDensity)) / 2.0 / D / nDensity;
545 for (
size_t j = 1; j <
m_points; j++) {
553 double F = sigma_tilde * sigma_tilde / (sigma_tilde * sigma_tilde + q * q);
556 double D = DA / sigma_tilde * F + DB;
562 if (!std::isfinite(z)) {
563 throw CanteraError(
"matrix_A",
"Non-finite Peclet number encountered");
565 if (std::abs(z) > 500) {
566 warn_user(
"EEDFTwoTermApproximation::matrix_A",
567 "Large Peclet number z = {:.3e} at j = {}. "
568 "W = {:.3e}, D = {:.3e}, E/N = {:.3e}\n",
569 z, j, W, D, E / nDensity);
571 a0[j] = W / (1 - std::exp(-z));
572 a1[j] = W / (1 - std::exp(z));
575 SparseTriplets tripletList;
578 tripletList.emplace_back(0, 0, a0[1]);
580 for (
size_t j = 1; j <
m_points - 1; j++) {
581 tripletList.emplace_back(j, j, a0[j+1] - a1[j]);
585 for (
size_t j = 0; j <
m_points - 1; j++) {
586 tripletList.emplace_back(j, j+1, a1[j+1]);
590 for (
size_t j = 1; j <
m_points; j++) {
591 tripletList.emplace_back(j, j-1, -a0[j]);
595 tripletList.emplace_back(N, N, -a1[N]);
598 A.setFromTriplets(tripletList.begin(), tripletList.end());
603 for (
size_t i = 0; i <
m_points; i++) {
608 for (
size_t i = 0; i <
m_points; i++) {
627 Eigen::VectorXd s = PQ * f0;
628 checkFinite(
"EEDFTwoTermApproximation::netProductionFrequency: s",
640 for (
size_t i = 0; i <
m_points; i++) {
647 auto f = Eigen::Map<const Eigen::ArrayXd>(y.data(), y.size());
655 vector<double> y(
m_points + 1, 0.0);
656 for (
size_t i = 1; i <
m_points; i++) {
688 if (kind ==
"ionization") {
690 }
else if (kind ==
"attachment") {
724 double tmp_sum = 0.0;
744 for (
size_t i = 0; i <
m_points; i++) {
748 for (
size_t i = 0; i <
m_points + 1; i++) {
765 for (
size_t i = 0; i <
m_points; i++) {
784 auto x = collision->energyLevels();
785 auto y = collision->crossSections();
787 int shiftFactor = (collision->kind() ==
"ionization") ? 2 : 1;
789 for (
size_t i = 0; i <
m_points + 1; i++) {
790 eps1[i] =
clip(shiftFactor *
m_gridEdge[i] + collision->threshold(),
793 vector<double> nodes = eps1;
794 for (
size_t i = 0; i <
m_points + 1; i++) {
799 for (
size_t i = 0; i < x.size(); i++) {
800 if (x[i] >= eps1[0] && x[i] <= eps1[
m_points]) {
801 nodes.push_back(x[i]);
805 std::sort(nodes.begin(), nodes.end());
806 auto last = std::unique(nodes.begin(), nodes.end());
807 nodes.resize(std::distance(nodes.begin(), last));
808 vector<double> sigma0(nodes.size());
809 for (
size_t i = 0; i < nodes.size(); i++) {
814 for (
size_t i = 1; i < nodes.size(); i++) {
820 for (
size_t i = 1; i < nodes.size(); i++) {
821 auto low = std::lower_bound(eps1.begin(), eps1.end(), nodes[i]);
822 m_i[k].push_back(low - eps1.begin() - 1);
826 for (
size_t i = 0; i < nodes.size() - 1; i++) {
827 m_sigma[k].push_back({sigma0[i], sigma0[i+1]});
831 for (
size_t i = 0; i < nodes.size() - 1; i++) {
832 m_eps[k].push_back({nodes[i], nodes[i+1]});
836 vector<double> x_offset(collision->energyLevels().begin(),
837 collision->energyLevels().end());
838 for (
auto& element : x_offset) {
839 element -= collision->threshold();
846 string m_quadratureMethod =
"simpson";
847 Eigen::VectorXd p(f.size());
848 for (
int i = 0; i < f.size(); i++) {
849 p[i] = f(i) * pow(grid[i], 0.5);
855 double initialMaxEnergy,
size_t nGridCells,
const string& gridType)
857 if (!std::isfinite(initialMaxEnergy) || initialMaxEnergy <= 0.0) {
858 throw CanteraError(
"EEDFTwoTermApproximation::setInitialGridParameters",
859 "initialMaxEnergy must be finite and greater than zero.");
862 if (nGridCells == 0) {
863 throw CanteraError(
"EEDFTwoTermApproximation::setInitialGridParameters",
864 "nGridCells must be greater than zero.");
867 if (gridType !=
"linear" &&
868 gridType !=
"quadratic" &&
869 gridType !=
"geometric") {
870 throw CanteraError(
"EEDFTwoTermApproximation::setInitialGridParameters",
871 "Unknown energy grid type '{}'. Expected linear, quadratic or geometric.",
886 double minDecayDecades,
double maxDecayDecades,
double updateFactor,
887 size_t maxIterations,
bool maxwellianReset)
889 if (!std::isfinite(minDecayDecades) || !std::isfinite(maxDecayDecades) ||
890 minDecayDecades <= 0.0 || maxDecayDecades <= minDecayDecades) {
891 throw CanteraError(
"EEDFTwoTermApproximation::setGridAdaptationParameters",
892 "Require 0 < min_decay_decades < max_decay_decades.");
895 if (!std::isfinite(updateFactor) || updateFactor <= 0.0) {
896 throw CanteraError(
"EEDFTwoTermApproximation::setGridAdaptationParameters",
897 "update_factor must be finite and greater than zero.");
900 if (maxIterations == 0) {
901 throw CanteraError(
"EEDFTwoTermApproximation::setGridAdaptationParameters",
902 "max_iterations must be greater than zero.");
914 if (!std::isfinite(maxEnergy) || maxEnergy <= 0.0) {
915 throw CanteraError(
"EEDFTwoTermApproximation::updateGrid",
916 "Maximum grid energy must be finite and greater than zero.");
928 throw CanteraError(
"EEDFTwoTermApproximation::updateGrid",
929 "Unknown energy grid type '{}'.",
m_gridType);
EEDF Two-Term approximation solver.
Header for plasma reaction rates parameterized by electron collision cross section and electron energ...
Header file for class PlasmaPhase.
Base class for exceptions thrown by Cantera classes.
double m_rtol
Error tolerance for convergence.
Eigen::VectorXd m_f0
Normalized electron energy distribution function.
vector< double > m_gridEdge
Grid of electron energy (cell boundary i-1/2) [eV].
void enableGridAdaptation(bool enabled)
Enable or disable automatic grid adaptation for the EEDF solver energy grid.
vector< vector< size_t > > m_i
Location of cell i for grid cache.
void calculateTotalCrossSection()
Compute the total (elastic + inelastic) cross section.
vector< vector< size_t > > m_j
Location of cell j for grid cache.
vector< double > m_X_targets_prev
Previous mole fraction of targets used to compute eedf.
double m_thresholdToMaxwellian
The threshold in reduced electric field [townsend, Td] below which no EEDF will be computed,...
void projectPreviousEEDFOnCurrentGrid(const Eigen::VectorXd &oldGridCenter, const Eigen::VectorXd &oldF0)
Projects a previously converged EEDF onto the current energy grid.
void adaptEnergyGrid()
Runs the energy grid adaptation script when this feature is activated.
vector< vector< vector< double > > > m_eps
The energy boundaries of the overlap of cell i and j.
double m_maxEedfDecay
Maximum amount of decades decay at the tail of the EEDF when grid adaptation is on.
void setGeometricGrid(double kTe_max, size_t ncell, double ratio=1.01)
Sets a geometric energy grid for the EEDF solver, defined by the maximum energy and the number of gri...
vector< vector< vector< double > > > m_sigma
Cross section at the boundaries of the overlap of cell i and j.
void setGridAdaptationParameters(double minDecayDecades, double maxDecayDecades, double updateFactor, size_t maxIterations, bool maxwellianReset)
Set parameters controlling automatic adaptation of the EEDF energy grid.
vector< size_t > m_k_lg_Targets
Local to global indices.
string m_gridType
Energy grid spacing type. Can be linear, quadratic or geometric.
bool m_first_call
First call to calculateDistributionFunction.
double m_kTeMax
Maximum value of the energy grid [eV].
size_t m_initialGridCells
Number of cells for the starting energy grid.
double m_gamma
Defined by the formula: pow(2.0 * ElectronCharge / ElectronMass, 0.5) and comupted during phase initi...
void converge(Eigen::VectorXd &f0)
Iterate f0 (EEDF) until convergence.
double m_delta0
Formerly options for the EEDF solver.
double electronDiffusivity(const Eigen::VectorXd &f0)
Diffusivity.
PlasmaPhase * m_phase
Pointer to the PlasmaPhase object used to initialize this object.
double norm(const Eigen::VectorXd &f, const Eigen::VectorXd &grid)
Compute the L1 norm of a function f defined over a given energy grid.
void updateMoleFractions()
Update the vector of species mole fractions.
vector< size_t > m_kTargets
List of target species indices in global Cantera numbering (1 index per cs)
bool m_has_EEDF
Flag of having an EEDF.
void setCustomGrid(span< const double > levels)
Sets a custom energy grid for the EEDF solver, defined by the user-provided vector of energy levels.
void setQuadraticGrid(double kTe_max, size_t ncell)
Sets a quadratic energy grid for the EEDF solver, defined by the maximum energy and the number of gri...
double electronMobility(const Eigen::VectorXd &f0)
Mobility.
void setMaxwellianDistribution(double kTe)
Sets a Maxwellian distribution with the specified electron temperature [eV].
Eigen::VectorXd m_gridCenter
Grid of electron energy (cell center) [eV].
size_t m_maxn
Maximum number of iterations.
Eigen::SparseMatrix< double > matrix_A(const Eigen::VectorXd &f0)
Matrix A (Ax = b) of the equation of EEDF, which is discretized by the exponential scheme of Scharfet...
Eigen::SparseMatrix< double > matrix_Q(span< const double > g, size_t k)
The matrix of scattering-in.
Eigen::VectorXd iterate(const Eigen::VectorXd &f0, double delta)
An iteration of solving electron energy distribution function.
double m_gridUpdateFactor
Factor by which the EEDF grid maximum energy is increased of shrunk when grid adaptation is on.
double m_electronMobility
Electron mobility [m²/V·s].
void setInitialGridParameters(double initialMaxEnergy, size_t nGridCells, const string &gridType)
Set the initial grid parameters used by generated EEDF grids.
size_t m_points
The number of points in the EEDF grid.
vector< size_t > m_kOthers
Indices of species which has no cross-section data.
double m_factorM
The factor for step size change.
void calculateTotalElasticCrossSection()
Compute the total elastic collision cross section.
double m_minEedfDecay
Minimum amount of decades decay at the tail of the EEDF when grid adaptation is on.
void updateCrossSections()
Update the total cross sections based on the current state.
size_t m_maxGridAdaptIterations
Maximum number of iterations on the maximum energy accepted for grid adaptation.
double m_geometricRatio
In the case where a geometric grid is employed, this stores the corresponding geometric ratio.
void initSpeciesIndexCrossSections()
Initialize species indices associated with cross-section data.
double m_init_kTe
The initial electron temperature [eV].
double netProductionFrequency(const Eigen::VectorXd &f0)
Reduced net production frequency.
double linearInterpBounded(double x, span< const double > xpts, span< const double > fpts, double below_value, double above_value)
An extension of the linearInterp function that returns specified values when the input is out of boun...
bool m_maxwellianReset
Boolean flag to reset the EEDF to a Maxwellian distribution at the gas temperature when the grid is a...
std::string m_growth
The growth model of EEDF.
void setGridCache()
Build or rebuild the grid-dependent cache used for scattering matrices.
int calculateDistributionFunction()
compute the EEDF given an electric field CQM The solver will take the species to consider and the set...
vector< double > m_totalCrossSectionEdge
Total electron cross section on the cell boundary (i-1/2) of energy grid.
Eigen::SparseMatrix< double > matrix_P(span< const double > g, size_t k)
The matrix of scattering-out.
vector< double > m_f0_edge
EEDF at grid edges (cell boundaries)
bool m_adaptGrid
Flag activating or deactivating automatic grid adaptation.
vector< size_t > m_klocTargets
List of target species indices in local X EEDF numbering (1 index per cs)
vector< double > m_X_targets
Mole fraction of targets.
void updateGrid(double maxEnergy)
Updates the grid according to the grid type and the new maximum energy when running grid adaptation.
double integralPQ(double a, double b, double u0, double u1, double g, double x0)
The integral in [a, b] of assuming that u is linear with u(a) = u0 and u(b) = u1.
vector< double > m_totalCrossSectionCenter
Total electron cross section on the cell center of energy grid.
vector< double > vector_g(const Eigen::VectorXd &f0)
Vector g is used by matrix_P() and matrix_Q().
vector< int > m_inFactor
In factor.
vector< double > m_sigmaElastic
Vector of total elastic cross section weighted with mass ratio.
void setLinearGrid(double kTe_max, size_t ncell)
Sets a linear energy grid for the EEDF solver, defined by the maximum energy and the number of grid c...
virtual double molarDensity() const
Molar density (kmol/m^3).
size_t nSpecies() const
Returns the number of species in the phase.
double temperature() const
Temperature (K).
double moleFraction(size_t k) const
Return the mole fraction of a single species.
double molecularWeight(size_t k) const
Molecular weight of species k.
Base class for handling plasma properties, specifically focusing on the electron energy distribution.
double electricFieldFrequency() const
Get the frequency of the applied electric field [Hz].
size_t nCollisions() const
Number of electron collision cross sections.
double electricField() const
Get the applied electric field strength [V/m].
const vector< size_t > & kElastic() const
Get the indices for elastic electron collisions.
const shared_ptr< ElectronCollisionPlasmaRate > collisionRate(size_t i) const
Get the ElectronCollisionPlasmaRate object associated with electron collision i.
double reducedElectricField() const
Calculate the degree of ionization.
size_t targetIndex(size_t i) const
Return the target of a specific process.
const vector< size_t > & kInelastic() const
Get the indicies for inelastic electron collisions.
Definitions for the classes that are thrown when Cantera experiences an error condition (also contain...
Header for a file containing miscellaneous numerical functions.
#define AssertThrowMsg(expr, procedure,...)
Assertion must be true or an error is thrown.
T clip(const T &value, const T &lower, const T &upper)
Clip value such that lower <= value <= upper.
double numericalQuadrature(const string &method, const Eigen::ArrayXd &f, const Eigen::ArrayXd &x)
Numerical integration of a function.
double simpson(const Eigen::ArrayXd &f, const Eigen::ArrayXd &x)
Numerical integration of a function using Simpson's rule with flexibility of taking odd and even numb...
double linearInterp(double x, span< const double > xpts, span< const double > fpts)
Linearly interpolate a function defined on a discrete grid.
const double Boltzmann
Boltzmann constant [J/K].
const double Avogadro
Avogadro's Number [number/kmol].
const double ElectronCharge
Elementary charge [C].
const double ElectronMass
Electron Mass [kg].
void warn_user(const string &method, const string &msg, const Args &... args)
Print a user warning raised from method as CanteraWarning.
Namespace for the Cantera kernel.
void checkFinite(const double tmp)
Check to see that a number is finite (not NaN, +Inf or -Inf)
MappedVector asVectorXd(vector< double > &v)
Convenience wrapper for accessing std::vector as an Eigen VectorXd.
span< double > asSpan(Eigen::DenseBase< Derived > &v)
Convenience wrapper for accessing Eigen vector/array/map data as a span.
void checkArraySize(const char *procedure, size_t available, size_t required)
Wrapper for throwing ArraySizeError.