Skip to content

Commit a35e2f5

Browse files
authored
Merge pull request #385 from boutproject/more-diagnostics
More diagnostics
2 parents a9d1000 + e5fe24a commit a35e2f5

File tree

5 files changed

+142
-22
lines changed

5 files changed

+142
-22
lines changed

include/amjuel_reaction.hxx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,13 @@ protected:
146146
add(electron["density_source"], (to_charge - from_charge) * reaction_rate);
147147
if (electron.isSet("velocity")) {
148148
// Transfer of electron kinetic to thermal energy due to density source
149+
// For ionisation:
150+
// Electrons with zero average velocity are created, diluting the kinetic energy.
151+
// Total energy conservation requires a corresponding internal energy source.
152+
//
153+
// For recombination:
154+
// Electrons with some velocity are incorporated into a neutral species with their kinetic
155+
// energy converted to an internal energy source of that species.
149156
auto Ve = get<Field3D>(electron["velocity"]);
150157
auto Ae = get<BoutReal>(electron["AA"]);
151158
add(electron["energy_source"], 0.5 * Ae * (to_charge - from_charge) * reaction_rate * SQ(Ve));
@@ -182,6 +189,11 @@ protected:
182189
// d/dt(3/2 p_2) = - m R v_1 v_2 + (1/2) m R v_1^2 + (1/2) m R v_2^2
183190
// = (1/2) m R (v_1 - v_2)^2
184191
//
192+
// This term accounts for the broadening of the species velocity
193+
// distribution when combining the two underlying distributions.
194+
// The greater the difference in velocities of the underlying species,
195+
// the wider the resultant distribution, which corresponds to an
196+
// increase in temperature and therefore internal energy.
185197

186198
add(to_ion["energy_source"], 0.5 * AA * reaction_rate * SQ(V1 - V2));
187199

include/collisions.hxx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ private:
6161

6262
/// Calculated collision rates saved for post-processing and use by other components
6363
/// Saved in options, the BOUT++ dictionary-like object
64-
Options collision_rates;
64+
Options collision_rates, energy_channels, friction_energy_channels, momentum_channels;
6565

6666
/// Save more diagnostics?
6767
bool diagnose;

include/evolve_pressure.hxx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,13 @@ private:
116116
bool source_time_dependent; ///< Is the input source time dependent?
117117
Field3D flow_xlow, flow_ylow; ///< Energy flow diagnostics
118118
Field3D flow_ylow_conduction; ///< Conduction energy flow diagnostics
119-
Field3D flow_ylow_kinetic; ///< Parallel flow of kinetic energy
119+
Field3D flow_ylow_advection; ///< Advection energy flow diagnostics
120+
Field3D flow_ylow_viscous_heating; ///< Flow of kinetic energy due to numerical viscosity
120121

121122
bool numerical_viscous_heating; ///< Include heating due to numerical viscosity?
122123
bool fix_momentum_boundary_flux; ///< Fix momentum flux to boundary condition?
123124
Field3D Sp_nvh; ///< Pressure source due to artificial viscosity
125+
Field3D E_PdivV, E_VgradP; ///< Diagnostic energy source terms for p*Div(V) and V*Grad(P)
124126
};
125127

126128
namespace {

src/collisions.cxx

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ void Collisions::collide(Options& species1, Options& species2, const Field3D& nu
104104

105105
add(species1["momentum_source"], F12);
106106
subtract(species2["momentum_source"], F12);
107+
108+
// Diagnostics
109+
set(momentum_channels[species1.name()][species2.name()], F12);
110+
set(momentum_channels[species2.name()][species1.name()], -F12);
107111

108112
if (frictional_heating) {
109113
// Heating due to friction and energy transfer
@@ -127,8 +131,15 @@ void Collisions::collide(Options& species1, Options& species2, const Field3D& nu
127131
// 1) This term is always positive: Collisions don't lead to cooling
128132
// 2) In the limit that m_2 << m_1 (e.g. electron-ion collisions),
129133
// the lighter species is heated more than the heavy species.
130-
add(species1["energy_source"], (A2 / (A1 + A2)) * (velocity2 - velocity1) * F12);
131-
add(species2["energy_source"], (A1 / (A1 + A2)) * (velocity2 - velocity1) * F12);
134+
Field3D species1_source = (A2 / (A1 + A2)) * (velocity2 - velocity1) * F12;
135+
Field3D species2_source = (A1 / (A1 + A2)) * (velocity2 - velocity1) * F12;
136+
137+
add(species1["energy_source"], species1_source);
138+
add(species2["energy_source"], species2_source);
139+
140+
// Diagnostics
141+
set(friction_energy_channels[species1.name()][species2.name()], species1_source);
142+
set(friction_energy_channels[species2.name()][species1.name()], species2_source);
132143
}
133144
}
134145

@@ -144,6 +155,10 @@ void Collisions::collide(Options& species1, Options& species2, const Field3D& nu
144155

145156
add(species1["energy_source"], Q12);
146157
subtract(species2["energy_source"], Q12);
158+
159+
// Diagnostics
160+
set(energy_channels[species1.name()][species2.name()], Q12);
161+
set(energy_channels[species2.name()][species1.name()], -Q12);
147162
}
148163
}
149164
}
@@ -488,6 +503,10 @@ void Collisions::outputVars(Options& state) {
488503

489504
// Normalisations
490505
auto Omega_ci = get<BoutReal>(state["Omega_ci"]);
506+
auto Nnorm = get<BoutReal>(state["Nnorm"]);
507+
auto Tnorm = get<BoutReal>(state["Tnorm"]);
508+
BoutReal Pnorm = SI::qe * Tnorm * Nnorm; // Pressure normalisation
509+
auto Cs0 = get<BoutReal>(state["Cs0"]);
491510

492511
/// Iterate through the first species in each collision pair
493512
const std::map<std::string, Options>& level1 = collision_rates.getChildren();
@@ -498,17 +517,65 @@ void Collisions::outputVars(Options& state) {
498517
const std::map<std::string, Options>& level2 = section.getChildren();
499518
for (auto s2 = std::begin(level2); s2 != std::end(level2); ++s2) {
500519

501-
std::string name = s1->first + s2->first;
520+
std::string A = s1->first;
521+
std::string B = s2->first;
522+
std::string AB = A + B;
502523

503-
set_with_attrs(state[std::string("K") + name + std::string("_coll")],
524+
// Collision frequencies
525+
set_with_attrs(state[std::string("K") + AB + std::string("_coll")],
504526
getNonFinal<Field3D>(section[s2->first]),
505527
{{"time_dimension", "t"},
506528
{"units", "s-1"},
507529
{"conversion", Omega_ci},
508-
{"standard_name", "collision frequency"},
509-
{"long_name", name + " collision frequency"},
510-
{"species", name},
530+
{"standard_name", AB + "collision frequency"},
531+
{"long_name", AB + " collision frequency"},
532+
{"species", A},
511533
{"source", "collisions"}});
534+
535+
// Collisional energy transfer channels (i.e. thermal equilibration)
536+
if ((energy_channels.isSection(A)) and (energy_channels[A].isSet(B))) {
537+
538+
set_with_attrs(state[std::string("E") + AB + std::string("_coll")],
539+
getNonFinal<Field3D>(energy_channels[A][B]),
540+
{{"time_dimension", "t"},
541+
{"units", "W / m^3"},
542+
{"conversion", Pnorm * Omega_ci},
543+
{"standard_name", AB + "collisional energy transfer source"},
544+
{"long_name", AB + "collisional energy transfer source"},
545+
{"species", A},
546+
{"source", "collisions"}});
547+
}
548+
549+
// Frictional energy sources (both species heat through friction)
550+
if ((friction_energy_channels.isSection(A)) and (friction_energy_channels[A].isSet(B))) {
551+
552+
set_with_attrs(state[std::string("E") + AB + std::string("_coll_friction")],
553+
getNonFinal<Field3D>(friction_energy_channels[A][B]),
554+
{{"time_dimension", "t"},
555+
{"units", "W / m^3"},
556+
{"conversion", Pnorm * Omega_ci},
557+
{"standard_name", AB + "frictional energy source"},
558+
{"long_name", AB + "frictional energy source"},
559+
{"species", A},
560+
{"source", "collisions"}});
561+
}
562+
563+
// Momentum exchange channels
564+
if ((momentum_channels.isSection(A)) and (momentum_channels[A].isSet(B))) {
565+
566+
set_with_attrs(state[std::string("F") + AB + std::string("_coll")],
567+
getNonFinal<Field3D>(friction_energy_channels[A][B]),
568+
{{"time_dimension", "t"},
569+
{"units", "kg m^-2 s^-2"},
570+
{"conversion", SI::Mp * Nnorm * Cs0 * Omega_ci},
571+
{"standard_name", AB + "collisional momentum transfer"},
572+
{"long_name", AB + "collisional momentum transfer"},
573+
{"species", A},
574+
{"source", "collisions"}});
575+
}
576+
512577
}
513578
}
579+
580+
514581
}

src/evolve_pressure.cxx

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -289,21 +289,24 @@ void EvolvePressure::finally(const Options& state) {
289289

290290
if (p_div_v) {
291291
// Use the P * Div(V) form
292-
ddt(P) -= FV::Div_par_mod<hermes::Limiter>(P, V, fastest_wave, flow_ylow);
292+
ddt(P) -= FV::Div_par_mod<hermes::Limiter>(P, V, fastest_wave, flow_ylow_advection);
293293

294294
// Work done. This balances energetically a term in the momentum equation
295-
ddt(P) -= (2. / 3) * Pfloor * Div_par(V);
295+
E_PdivV = -Pfloor * Div_par(V);
296+
ddt(P) += (2. / 3) * E_PdivV;
296297

297298
} else {
298299
// Use V * Grad(P) form
299300
// Note: A mixed form has been tried (on 1D neon example)
300301
// -(4/3)*FV::Div_par(P,V) + (1/3)*(V * Grad_par(P) - P * Div_par(V))
301302
// Caused heating of charged species near sheath like p_div_v
302-
ddt(P) -= (5. / 3) * FV::Div_par_mod<hermes::Limiter>(P, V, fastest_wave, flow_ylow);
303+
ddt(P) -= (5. / 3) * FV::Div_par_mod<hermes::Limiter>(P, V, fastest_wave, flow_ylow_advection);
303304

304-
ddt(P) += (2. / 3) * V * Grad_par(P);
305+
E_VgradP = V * Grad_par(P);
306+
ddt(P) += (2. / 3) * E_VgradP;
305307
}
306-
flow_ylow *= 5. / 2; // Energy flow
308+
flow_ylow_advection *= 5. / 2; // Energy flow
309+
flow_ylow = flow_ylow_advection;
307310

308311
if (state.isSection("fields") and state["fields"].isSet("Apar_flutter")) {
309312
// Magnetic flutter term
@@ -313,12 +316,12 @@ void EvolvePressure::finally(const Options& state) {
313316
}
314317

315318
if (numerical_viscous_heating || diagnose) {
316-
// Viscous heating coming from numerical viscosity
319+
// Viscous heating coming from numerical viscosity from the Lax flux
317320
Field3D Nlim = softFloor(N, density_floor);
318321
const BoutReal AA = get<BoutReal>(species["AA"]); // Atomic mass
319-
Sp_nvh = (2. / 3) * AA * FV::Div_par_fvv_heating(Nlim, V, fastest_wave, flow_ylow_kinetic, fix_momentum_boundary_flux);
320-
flow_ylow_kinetic *= AA;
321-
flow_ylow += flow_ylow_kinetic;
322+
Sp_nvh = (2. / 3) * AA * FV::Div_par_fvv_heating(Nlim, V, fastest_wave, flow_ylow_viscous_heating, fix_momentum_boundary_flux);
323+
flow_ylow_viscous_heating *= AA;
324+
flow_ylow += flow_ylow_viscous_heating;
322325
if (numerical_viscous_heating) {
323326
ddt(P) += Sp_nvh;
324327
}
@@ -652,6 +655,30 @@ void EvolvePressure::outputVars(Options& state) {
652655
{"species", name},
653656
{"source", "evolve_pressure"}});
654657

658+
if (p_div_v) {
659+
660+
set_with_attrs(state["E" + name + "_PdivV"], E_PdivV,
661+
{{"time_dimension", "t"},
662+
{"units", "W / m^-3"},
663+
{"conversion", Pnorm * Omega_ci},
664+
{"standard_name", "energy source"},
665+
{"long_name", name + " energy source due to pressure gradient"},
666+
{"species", name},
667+
{"source", "evolve_pressure"}});
668+
} else {
669+
670+
set_with_attrs(state["E" + name + "_VgradP"], E_VgradP,
671+
{{"time_dimension", "t"},
672+
{"units", "W / m^-3"},
673+
{"conversion", Pnorm * Omega_ci},
674+
{"standard_name", "energy source"},
675+
{"long_name", name + " energy source due to pressure gradient"},
676+
{"species", name},
677+
{"source", "evolve_pressure"}});
678+
679+
}
680+
681+
655682
if (flow_xlow.isAllocated()) {
656683
set_with_attrs(state[fmt::format("ef{}_tot_xlow", name)], flow_xlow,
657684
{{"time_dimension", "t"},
@@ -679,22 +706,34 @@ void EvolvePressure::outputVars(Options& state) {
679706
{"units", "W"},
680707
{"conversion", rho_s0 * SQ(rho_s0) * Pnorm * Omega_ci},
681708
{"standard_name", "power"},
682-
{"long_name", name + " conduction through Y cell face. Note: May be incomplete."},
709+
{"long_name", name + " conduction through Y cell face."},
683710
{"species", name},
684711
{"source", "evolve_pressure"}});
685712
}
686713

687-
if (flow_ylow_kinetic.isAllocated()) {
688-
set_with_attrs(state[fmt::format("ef{}_kin_ylow", name)], flow_ylow_kinetic,
714+
if (flow_ylow_advection.isAllocated()) {
715+
set_with_attrs(state[fmt::format("ef{}_adv_ylow", name)], flow_ylow_advection,
689716
{{"time_dimension", "t"},
690717
{"units", "W"},
691718
{"conversion", rho_s0 * SQ(rho_s0) * Pnorm * Omega_ci},
692719
{"standard_name", "power"},
693-
{"long_name", name + " kinetic energy flow through Y cell face. Note: May be incomplete."},
720+
{"long_name", name + " advected energy flow through Y cell face."},
694721
{"species", name},
695722
{"source", "evolve_pressure"}});
696723
}
697724

725+
if (flow_ylow_viscous_heating.isAllocated()) {
726+
set_with_attrs(state[fmt::format("ef{}_visc_heat_ylow", name)], flow_ylow_viscous_heating,
727+
{{"time_dimension", "t"},
728+
{"units", "W"},
729+
{"conversion", rho_s0 * SQ(rho_s0) * Pnorm * Omega_ci},
730+
{"standard_name", "power"},
731+
{"long_name", name + " energy flow due to Lax flux numerical viscosity through Y cell face"},
732+
{"species", name},
733+
{"source", "evolve_pressure"}});
734+
}
735+
736+
698737
if (numerical_viscous_heating) {
699738
set_with_attrs(state[std::string("E") + name + std::string("_nvh")], Sp_nvh * 3/.2,
700739
{{"time_dimension", "t"},

0 commit comments

Comments
 (0)