rizer.cantera_ext
C++ Cantera 1-D plasma extension (custom Domain1D models and solvers)
Loading...
Searching...
No Matches
RadialDiffusion.cpp
Go to the documentation of this file.
2
7
8#include <cstddef>
9
10namespace rizer
11{
12
13 namespace
14 {
15
16 // -------------------------------------------------------------------------- //
17 // File-local helpers //
18 // -------------------------------------------------------------------------- //
19
29 template <typename Coefficient>
30 double eastFaceFlux(const PlasmaChannel1D &domain,
31 Cantera::span<const double> x, std::size_t component,
32 std::size_t node, double ghost, Coefficient coef)
33 {
34 const std::size_t n_points = domain.nPoints();
35 const double value_here = x[domain.index(component, node)];
36
37 const double value_east = (node < n_points - 1)
38 ? x[domain.index(component, node + 1)]
39 : ghost;
40 const double radius_east = domain.faceRadius(node + 1);
41 const double spacing_east = domain.faceSpacing(node + 1);
42 // One past the last node names the wall ghost half-cell.
43 const double coef_east =
44 0.5 * (coef(value_here, node) + coef(value_east, node + 1));
45 return numeric::faceFlux(radius_east, coef_east, value_here, value_east,
46 spacing_east);
47 }
48
76 template <typename Coefficient>
77 double faceDivergence(const PlasmaChannel1D &domain,
78 Cantera::span<const double> x, std::size_t component,
79 std::size_t node, double ghost, Coefficient coef)
80 {
81 const double value_here = x[domain.index(component, node)];
82 const double flux_east =
83 eastFaceFlux(domain, x, component, node, ghost, coef);
84 const double radius_east = domain.faceRadius(node + 1);
85
86 double flux_west = 0.0;
87 double radius_west = 0.0; // [m]
88 if (node > 0)
89 {
90 const double value_west = x[domain.index(component, node - 1)];
91 radius_west = domain.faceRadius(node);
92 const double coef_west =
93 0.5 * (coef(value_west, node - 1) + coef(value_here, node));
94 flux_west =
95 numeric::faceFlux(radius_west, coef_west, value_west,
96 value_here, domain.faceSpacing(node));
97 }
98 return (flux_east - flux_west) /
99 numeric::annularVolume(radius_east, radius_west);
100 }
101
121 double centredGradient(const PlasmaChannel1D &domain,
122 Cantera::span<const double> x, std::size_t component,
123 std::size_t node, double ghost)
124 {
125 if (node == 0)
126 {
127 return 0.0; // axis symmetry: d(phi)/dr(r=0) = 0
128 }
129 const std::size_t n_points = domain.nPoints();
130 const double value_here = x[domain.index(component, node)];
131 const double value_west = x[domain.index(component, node - 1)];
132 const double gradient_west =
133 (value_here - value_west) / domain.faceSpacing(node);
134
135 const double value_east = (node < n_points - 1)
136 ? x[domain.index(component, node + 1)]
137 : ghost;
138 const double gradient_east =
139 (value_east - value_here) / domain.faceSpacing(node + 1);
140 return 0.5 * (gradient_west + gradient_east);
141 }
142
143 } // namespace
144
145 // ------------------------------------------------------------------------------ //
146 // Free functions //
147 // ------------------------------------------------------------------------------ //
148
151 const RadialDiffusionCoefficients &c, std::size_t j,
152 double lhsTg, double lhsTe, double &dTg, double &dTe,
153 double *dYdt, double *scratch)
154 {
155 // Four steps, each adding onto the caller's rates:
156 // conduction in each pool, then the three terms species diffusion
157 // drives (A: the species equation itself, B: the compressibility it
158 // forces through DP/Dt, C: the enthalpy it carries with it).
159 const std::vector<double> &rho = domain.density();
160 const std::vector<double> &Y_amb = *c.Y_amb;
161 const std::vector<double> &molecular_weights = *c.molecular_weights;
162
163 // ---- conduction, each pool on its own table -------------------------
164 if (!c.kappa->empty())
165 {
166 dTg += faceDivergence(domain, x, 0, j, c.ambient_temperature,
167 [&](double T, std::size_t)
168 { return evalAtState(*c.kappa, T, "radial conduction", "kappa_H(Tg)", "K",
169 "Extend the kappa_H_T/kappa_H_v table."); }) /
170 lhsTg;
171 }
172 if (!c.kappa_e->empty())
173 {
174 dTe += faceDivergence(domain, x, 1, j, c.ambient_temperature,
175 [&](double T, std::size_t)
176 { return evalAtState(*c.kappa_e, T, "radial conduction", "kappa_e(Te)", "K",
177 "Extend the kappa_e_T/kappa_e_v table."); }) /
178 lhsTe;
179 }
180 if (!(c.species_diffusivity > 0.0))
181 {
182 return;
183 }
184
185 const double Tg = x[domain.index(0, j)];
186 const double Te = x[domain.index(1, j)];
187 const double We = molecular_weights[c.electron_index];
188 const std::size_t n_points = domain.nPoints();
189
190 // Term A (the species equation) and Term B (the compressibility source
191 // it drives in both energy equations) both need the divergence of the
192 // Fickian flux, div(rho*D*grad(Y_k)). The density sits INSIDE the
193 // divergence, where no constant can be pulled out of it unless it
194 // happens to be uniform: div(rho*D*grad(Y_k))/rho_j and
195 // D*div(grad(Y_k)) are different quantities in general, and only the
196 // first conserves species mass -- only its face fluxes telescope
197 // against the neighbouring cells'. The face coefficient is averaged
198 // from the two adjacent nodes' rho*D, and the wall ghost resolves to
199 // the ambient density the constructor closed from p_amb.
200 for (std::size_t k = 0; k < c.n_species; k++)
201 {
202 scratch[k] = faceDivergence(
203 domain, x, 2 + k, j, Y_amb[k],
204 [&](double, std::size_t n)
205 {
206 const double density =
207 (n < n_points) ? rho[n] : c.wall_ghost_density;
208 return density * c.species_diffusivity;
209 });
210 }
211
212 // ---- Term A: Fickian species diffusion ------------------------------
213 // Converted to dY_k/dt by dividing through by the node's density.
214 for (std::size_t k = 0; k < c.n_species; k++)
215 {
216 dYdt[k] += scratch[k] / rho[j];
217 }
218
219 // ---- Term B: species-diffusion compressibility ----------------------
220 // From expanding DP/Dt
221 // for the two-temperature ideal-gas EOS under Fick's law
222 // (PHYSICS.md section 5, the species-diffusion compressibility term).
223 // Built on the
224 // same flux divergence Term A uses.
225 dTe += (GasConstant * Te / We) * scratch[c.electron_index] / lhsTe;
226
227 double gas_compressibility_sum = 0.0;
228 for (std::size_t k = 0; k < c.n_species; k++)
229 {
230 if (k == c.electron_index)
231 continue;
232 gas_compressibility_sum += scratch[k] / molecular_weights[k];
233 }
234 dTg += GasConstant * Tg * gas_compressibility_sum / lhsTg;
235
236 // ---- Term C: enthalpy diffusion -------------------------------------
237 // Species heat transport. The full
238 // multicomponent energy equation's heat flux includes +sum_k h_k*j_k
239 // (species carry their own sensible enthalpy as they diffuse); with
240 // Fick's law j_k = -rho*D*grad(Y_k), its divergence -div(sum_k h_k*j_k)
241 // reduces to a mixed-gradient PRODUCT rather than a divergence (see
242 // PHYSICS.md section 5, the enthalpy-diffusion term):
243 // electron: +rho*D*cp_e * (dYe/dr)(dTe/dr)
244 // gas: +rho*D*(dTg/dr) * sum_{k!=e} cp_k * (dYk/dr)
245 // Being a product of local gradients rather than a divergence, this one
246 // takes the node's own density whether or not the density varies with
247 // radius.
248 const std::vector<double> &species_heat_capacity = *c.species_cp;
249 const double Tg_gradient =
250 centredGradient(domain, x, 0, j, c.ambient_temperature);
251 const double Te_gradient =
252 centredGradient(domain, x, 1, j, c.ambient_temperature);
253 const double Ye_gradient = centredGradient(
254 domain, x, 2 + c.electron_index, j, Y_amb[c.electron_index]);
255 dTe += rho[j] * c.species_diffusivity *
256 species_heat_capacity[c.electron_index] * Ye_gradient *
257 Te_gradient / lhsTe;
258
259 double gas_enthalpy_diffusion_sum = 0.0;
260 for (std::size_t k = 0; k < c.n_species; k++)
261 {
262 if (k == c.electron_index)
263 continue;
264 const double Yk_gradient =
265 centredGradient(domain, x, 2 + k, j, Y_amb[k]);
266 gas_enthalpy_diffusion_sum +=
267 species_heat_capacity[k] * Yk_gradient;
268 }
269 dTg += rho[j] * c.species_diffusivity * Tg_gradient *
270 gas_enthalpy_diffusion_sum / lhsTg;
271 }
272
276 const double *species_enthalpy)
277 {
278 // Each term is the east-face flux of the outermost node, formed by the
279 // same eastFaceFlux() call its divergence makes, negated: that flux is
280 // positive INTO the node, and these report what leaves.
281 const std::size_t wall_node = domain.nPoints() - 1;
282 WallEnergyOutflow outflow;
283 if (!c.kappa->empty())
284 {
285 outflow.conduction -= eastFaceFlux(domain, x, 0, wall_node,
286 c.ambient_temperature,
287 [&](double T, std::size_t)
288 { return evalAtState(*c.kappa, T, "radial conduction", "kappa_H(Tg)", "K",
289 "Extend the kappa_H_T/kappa_H_v table."); });
290 }
291 if (!c.kappa_e->empty())
292 {
293 outflow.conduction -= eastFaceFlux(domain, x, 1, wall_node,
294 c.ambient_temperature,
295 [&](double T, std::size_t)
296 { return evalAtState(*c.kappa_e, T, "radial conduction", "kappa_e(Te)", "K",
297 "Extend the kappa_e_T/kappa_e_v table."); });
298 }
299 if (!(c.species_diffusivity > 0.0))
300 {
301 return outflow;
302 }
303
304 const std::vector<double> &rho = domain.density();
305 const std::vector<double> &Y_amb = *c.Y_amb;
306 const std::size_t n_points = domain.nPoints();
307 outflow.species_mass.assign(c.n_species, 0.0);
308 for (std::size_t k = 0; k < c.n_species; k++)
309 {
310 const double mass_inflow = eastFaceFlux(
311 domain, x, 2 + k, wall_node, Y_amb[k],
312 [&](double, std::size_t n)
313 {
314 const double density =
315 (n < n_points) ? rho[n] : c.wall_ghost_density;
316 return density * c.species_diffusivity;
317 });
318 outflow.diffusion -= species_enthalpy[k] * mass_inflow;
319 outflow.species_mass[k] = -mass_inflow;
320 }
321 return outflow;
322 }
323
324} // namespace rizer
Pure-arithmetic primitives for a 1D cylindrical (radial) finite-volume discretization of the diffusiv...
The transient two-temperature radial channel as a Cantera Domain1D: per-node source terms from Plasma...
The two-temperature quantities every model in this extension reads back from a PlasmaPhase,...
The parabolic half of PlasmaChannel1D::eval(): conduction in both pools, Fickian species diffusion,...
Evaluate a PropertyTable at a state, refusing a state outside its range.
size_t nPoints() const
size_t index(size_t n, size_t j) const
Transient, two-temperature (Te != Tg), finite-rate reacting 1-D radial plasma channel (custom Cantera...
const std::vector< double > & density() const
Per-node mass density [kg/m^3], length nPoints().
double faceFlux(double r_face, double coef_face, double value_inner, double value_outer, double spacing)
Conductive/diffusive flux across one radial cell face: r_face * coef_face * (value_outer - value_inne...
double annularVolume(double r_east, double r_west)
Annular cell volume per unit axial length between two face radii, 0.5 * (r_east^2 - r_west^2) [m^2].
void addRadialDiffusion(const PlasmaChannel1D &domain, Cantera::span< const double > x, const RadialDiffusionCoefficients &c, std::size_t j, double lhsTg, double lhsTe, double &dTg, double &dTe, double *dYdt, double *scratch)
Add one node's diffusive contributions to the rates the caller holds.
constexpr double GasConstant
Universal gas constant [J/kmol/K] and Boltzmann's constant [J/K], from units.h rather than Cantera's ...
Definition PlasmaState.h:48
WallEnergyOutflow wallEnergyOutflow(const PlasmaChannel1D &domain, Cantera::span< const double > x, const RadialDiffusionCoefficients &c, const double *species_enthalpy)
What conduction and species diffusion move through the wall face at the state x.
Everything the diffusion block needs that the grid and the state do not carry: the transport coeffici...
Energy leaving through the wall ghost face per unit time, per unit axial length and per radian [W/m] ...
std::vector< double > species_mass
Mass of each species diffusion carries through the ghost face [kg/m/s], length K; empty when species ...
double diffusion
Enthalpy species diffusion carries through the ghost face [W/m].
double conduction
Conduction of both pools against the ghost temperature [W/m].