23#include <gtest/gtest.h>
38 constexpr double kGamma = 1.4;
39 constexpr double kMolecularWeight = 28.96;
40 constexpr double kPressure = 101325.0;
41 constexpr double kTemperature = 300.0;
42 constexpr double kGasConstant = 8314.462618153242;
53 : m_backing(kGamma, {kMolecularWeight}, -1)
57 void pressures(
double r,
double t,
double rho,
double Tg,
double Te,
58 const double *Y,
double &p_heavy,
59 double &p_electron)
const override
62 m_backing.pressures(r, t, rho, Tg, Te, Y, p_heavy, p_electron);
65 double soundSpeed(
double r,
double t,
double rho,
double Tg,
double Te,
66 const double *Y)
const override
68 return m_backing.soundSpeed(r, t, rho, Tg, Te, Y);
71 double heavyEnergy(
double Tg,
const double *Y)
const override
73 return m_backing.heavyEnergy(Tg, Y);
76 double electronCv()
const override {
return m_backing.electronCv(); }
78 double densityFromPressure(
double pressure,
double Tg,
double Te,
79 const double *Y)
const override
81 return m_backing.densityFromPressure(pressure, Tg, Te, Y);
84 double heavyTemperature(
double rho,
const double *Y,
85 double heavy_internal_energy_target,
86 double Tg_seed)
const override
88 return m_backing.heavyTemperature(rho, Y, heavy_internal_energy_target,
92 mutable std::vector<double> radii;
95 GammaLawThermo m_backing;
103 std::vector<double> r_node;
104 std::vector<double> r_face;
105 std::vector<double> V_cell;
114 Grid makeGrid(std::size_t n_points,
double r_max,
double r_min = 0.0,
115 double stretch = 1.0)
118 grid.r_node.resize(n_points);
119 for (std::size_t i = 0; i < n_points; i++)
122 static_cast<double>(i) /
static_cast<double>(n_points - 1);
123 grid.r_node[i] = r_min + (r_max - r_min) * std::pow(x, stretch);
125 grid.r_face.resize(n_points + 1);
126 grid.r_face[0] = (r_min == 0.0)
129 0.5 * (grid.r_node[1] - grid.r_node[0]);
130 for (std::size_t i = 1; i < n_points; i++)
132 grid.r_face[i] = 0.5 * (grid.r_node[i - 1] + grid.r_node[i]);
134 grid.r_face[n_points] =
135 grid.r_node[n_points - 1] +
136 0.5 * (grid.r_node[n_points - 1] - grid.r_node[n_points - 2]);
137 grid.V_cell.resize(n_points);
138 for (std::size_t i = 0; i < n_points; i++)
140 grid.V_cell[i] = 0.5 * (grid.r_face[i + 1] * grid.r_face[i + 1] -
141 grid.r_face[i] * grid.r_face[i]);
147 HydroStepState makeState(std::size_t n_points)
149 HydroStepState state;
151 const double rho = kPressure * kMolecularWeight / (kGasConstant * kTemperature);
152 state.rho.assign(n_points, rho);
153 state.u_r.assign(n_points, 0.0);
154 state.Tg.assign(n_points, kTemperature);
155 state.Te.assign(n_points, kTemperature);
156 state.Y.assign(n_points, 1.0);
165 cfg.r_node = grid.r_node;
166 cfg.r_face = grid.r_face;
167 cfg.V_cell = grid.V_cell;
168 cfg.cylindrical_source =
true;
169 cfg.p_amb = kPressure;
170 cfg.T_amb = kTemperature;
172 cfg.dt_total = 1.0e-12;
174 cfg.electron_index = -1;
175 cfg.thermo = &thermo;
185 TEST(HydroStepGhostRadius, OuterGhostSitsAtItsOwnCellNode)
187 const Grid grid = makeGrid(21, 4.0e-3);
188 RecordingThermo thermo;
189 HydroStepConfig cfg = makeConfig(grid, thermo);
191 std::vector<double> offered;
192 cfg.ghost_state = [&](
double r_ghost, double,
bool is_inner,
193 const NodeState &interior) {
194 EXPECT_FALSE(is_inner) <<
"a full disk must not consult the axis face";
195 offered.push_back(r_ghost);
199 HydroStepState state = makeState(21);
202 const double expected = 2.0 * grid.r_node[20] - grid.r_node[19];
203 ASSERT_FALSE(offered.empty());
204 EXPECT_DOUBLE_EQ(offered.front(), expected);
206 EXPECT_GT(std::abs(offered.front() - grid.r_face[21]), 1.0e-5);
211 TEST(HydroStepGhostRadius, BothGhostsAreOfferedOnAnAnnulus)
213 const Grid grid = makeGrid(21, 4.0e-3, 1.0e-3);
214 RecordingThermo thermo;
215 HydroStepConfig cfg = makeConfig(grid, thermo);
219 cfg.ghost_state = [&](
double r_ghost, double,
bool is_inner,
220 const NodeState &interior) {
221 (is_inner ? inner : outer) = r_ghost;
225 HydroStepState state = makeState(21);
228 EXPECT_DOUBLE_EQ(inner, 2.0 * grid.r_node[0] - grid.r_node[1]);
229 EXPECT_DOUBLE_EQ(outer, 2.0 * grid.r_node[20] - grid.r_node[19]);
234 TEST(HydroStepGhostRadius, EachEndUsesItsOwnSpacing)
236 const Grid grid = makeGrid(21, 4.0e-3, 1.0e-3, 2.5);
237 const double inner_spacing = grid.r_node[1] - grid.r_node[0];
238 const double outer_spacing = grid.r_node[20] - grid.r_node[19];
240 ASSERT_GT(outer_spacing / inner_spacing, 5.0);
242 RecordingThermo thermo;
243 HydroStepConfig cfg = makeConfig(grid, thermo);
246 cfg.ghost_state = [&](
double r_ghost, double,
bool is_inner,
247 const NodeState &interior) {
248 (is_inner ? inner : outer) = r_ghost;
252 HydroStepState state = makeState(21);
255 EXPECT_DOUBLE_EQ(inner, grid.r_node[0] - inner_spacing);
256 EXPECT_DOUBLE_EQ(outer, grid.r_node[20] + outer_spacing);
263 TEST(HydroStepGhostRadius, ThermoSeesTheSameGhostRadiiAsTheCallback)
265 const Grid grid = makeGrid(21, 4.0e-3, 1.0e-3, 2.5);
266 RecordingThermo thermo;
267 HydroStepConfig cfg = makeConfig(grid, thermo);
270 cfg.ghost_state = [&](
double r_ghost, double,
bool is_inner,
271 const NodeState &interior) {
272 (is_inner ? inner : outer) = r_ghost;
276 HydroStepState state = makeState(21);
279 ASSERT_FALSE(thermo.radii.empty());
280 const auto bounds = std::minmax_element(thermo.radii.begin(), thermo.radii.end());
281 EXPECT_DOUBLE_EQ(*bounds.first, inner);
282 EXPECT_DOUBLE_EQ(*bounds.second, outer);
287 TEST(HydroStepGhostRadius, TheAxisGhostStaysOnTheAxis)
289 const Grid grid = makeGrid(21, 4.0e-3);
290 RecordingThermo thermo;
291 HydroStepConfig cfg = makeConfig(grid, thermo);
294 HydroStepState state = makeState(21);
297 ASSERT_FALSE(thermo.radii.empty());
298 const double smallest =
299 *std::min_element(thermo.radii.begin(), thermo.radii.end());
303 EXPECT_DOUBLE_EQ(smallest, 0.0);
315 TEST(HydroStepGuards, RefusesASingleNode)
318 grid.r_node = {1.0e-3};
319 grid.r_face = {0.5e-3, 1.5e-3};
320 grid.V_cell = {1.0e-6};
321 RecordingThermo thermo;
322 HydroStepConfig cfg = makeConfig(grid, thermo);
323 HydroStepState state = makeState(1);
329 TEST(HydroStepGuards, RefusesANullThermo)
331 const Grid grid = makeGrid(21, 4.0e-3);
332 RecordingThermo thermo;
333 HydroStepConfig cfg = makeConfig(grid, thermo);
334 cfg.thermo =
nullptr;
335 HydroStepState state = makeState(21);
340 TEST(HydroStepGuards, RefusesACflOutsideTheUnitInterval)
342 const Grid grid = makeGrid(21, 4.0e-3);
343 RecordingThermo thermo;
344 HydroStepState state = makeState(21);
345 for (
const double cfl : {0.0, -0.5, 1.5})
347 HydroStepConfig cfg = makeConfig(grid, thermo);
357 TEST(HydroStepGuards, RefusesAnAnnulusWithNoGhostCallback)
359 const Grid grid = makeGrid(21, 4.0e-3, 1.0e-3);
360 RecordingThermo thermo;
361 HydroStepConfig cfg = makeConfig(grid, thermo);
362 HydroStepState state = makeState(21);
368 TEST(HydroStepGuards, RefusesAGhostWhoseCompositionDoesNotSumToOne)
370 const Grid grid = makeGrid(21, 4.0e-3);
371 RecordingThermo thermo;
372 HydroStepConfig cfg = makeConfig(grid, thermo);
373 cfg.ghost_state = [](double, double, bool,
const NodeState &interior) {
374 NodeState ghost = interior;
378 HydroStepState state = makeState(21);
384 TEST(HydroStepGuards, RefusesANonFiniteGhost)
386 const Grid grid = makeGrid(21, 4.0e-3);
387 RecordingThermo thermo;
388 HydroStepConfig cfg = makeConfig(grid, thermo);
389 cfg.ghost_state = [](double, double, bool,
const NodeState &interior) {
390 NodeState ghost = interior;
391 ghost.u_r = std::nan(
"");
394 HydroStepState state = makeState(21);
399 TEST(HydroStepGuards, AZeroLengthCallAdvancesNothing)
401 const Grid grid = makeGrid(21, 4.0e-3);
402 RecordingThermo thermo;
403 HydroStepConfig cfg = makeConfig(grid, thermo);
406 HydroStepState state = makeState(21);
407 const HydroStepState before = state;
410 EXPECT_EQ(state.rho, before.rho);
411 EXPECT_EQ(state.u_r, before.u_r);
412 EXPECT_EQ(state.Tg, before.Tg);
422 TEST(HydroStepWellBalanced, AStateAtRestStaysAtRestOnAStretchedMesh)
424 const Grid grid = makeGrid(41, 4.0e-3, 0.0, 2.0);
425 RecordingThermo thermo;
426 HydroStepConfig cfg = makeConfig(grid, thermo);
428 cfg.dt_total = 1.0e-9;
431 HydroStepState state = makeState(41);
434 for (std::size_t i = 0; i < state.u_r.size(); i++)
436 EXPECT_NEAR(state.u_r[i], 0.0, 1.0e-14) <<
"node " << i;
Calorically perfect ThermoAccessor: one constant gamma for the heavy pool, ElectronGamma for the elec...
Standalone explicit hydrodynamic sub-stepper for the 1D radial two-temperature plasma channel: the "A...
Calorically perfect ideal-gas accessor: one constant for the heavy pool, ElectronGamma for the elect...
Equation-of-state and caloric accessor injected into hydroStep().
@ AmbientGhost
Fix rho, u_r, p, T and Y_s to ambient.
HydroStepResult hydroStep(const HydroStepConfig &cfg, HydroStepState &state)
Advance state over one Strang half-step of cfg.dt_total.
Geometry, boundary treatment and stepping controls for one call.
Diagnostics from one call, enough to assert NUMERICS.md section "Positivity under a strong rarefactio...
std::size_t n_subcycles_used
M actually taken [-].
Per-node primitive state, advanced in place.
Primitive state at one radial node, as the ghost-state callback sees it and returns it.