-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathmain_2d.cpp
More file actions
364 lines (311 loc) · 13.9 KB
/
Copy pathmain_2d.cpp
File metadata and controls
364 lines (311 loc) · 13.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
// Copyright 2025 the samurai team
// SPDX-License-Identifier: BSD-3-Clause
#include <numbers>
#include <cassert>
#include <memory>
#include <samurai/algorithm/update.hpp>
#include <samurai/field.hpp>
#include <samurai/io/hdf5.hpp>
#include <samurai/io/restart.hpp>
#include <samurai/mr/adapt.hpp>
#include <samurai/mr/mesh.hpp>
#include <samurai/samurai.hpp>
#include <samurai/timers.hpp>
#include "euler/config.hpp"
#include "euler/eos.hpp"
#include "euler/init/cases.hpp"
#include "euler/metrics.hpp"
#include "euler/prediction.hpp"
#include "euler/save.hpp"
#include "euler/reconstruction.hpp"
#include "euler/schemes.hpp"
#include "euler/time_stepping.hpp"
#include "euler/utils.hpp"
#include "euler/variables.hpp"
template <class Field>
void init_bc(Field& u, double& t, const std::string& test_case_name, auto eos)
{
auto& registry = test_case::TestCaseRegistry<Field>::instance();
auto& test_case = registry.get(test_case_name);
test_case.bc(u, t, eos);
}
template <class Field>
void init_sol(Field& u, auto& config, int jump, auto& mra_config, const std::string& test_case_name, auto eos)
{
samurai::ScopedTimer timer("initialization");
static constexpr std::size_t dim = Field::dim;
using mesh_t = typename Field::mesh_t;
using cl_type = typename mesh_t::cl_type;
auto& registry = test_case::TestCaseRegistry<Field>::instance();
auto& test_case = registry.get(test_case_name);
auto& mesh = u.mesh();
u.resize();
samurai::for_each_cell(mesh,
[&](auto& cell)
{
test_case.init(u, cell, eos);
});
std::cout << "Refining to level " << mesh.max_level() << std::endl;
// NOTE this deliberately uses the DEFAULT prediction, not the positivity
// preserving one used in the time loop. main_3d.cpp does the opposite and
// says so explicitly. Left as is so that this commit changes no result;
// to be reconciled in lot 1.
auto MRadaptation = samurai::make_MRAdapt(u);
MRadaptation(mra_config);
while (jump > 0)
{
cl_type cl;
for_each_interval(mesh,
[&](std::size_t level, const auto& i, const auto& index)
{
samurai::static_nested_loop<dim - 1, 0, 2>(
[&](const auto& stencil)
{
auto new_index = 2 * index + stencil;
for (auto ii = i.start; ii < i.end; ++ii)
{
cl[level + 1][new_index].add_interval(i << 1);
}
});
});
config.max_level()++;
mesh = {cl, config};
std::cout << "Refining to level " << mesh.max_level() << std::endl;
u.resize();
samurai::for_each_cell(mesh,
[&](auto& cell)
{
test_case.init(u, cell, eos);
});
MRadaptation(mra_config);
jump--;
}
}
int main(int argc, char* argv[])
{
constexpr std::size_t dim = 2;
std::size_t default_level = 10;
using field_t = config<dim>::field_t;
auto& app = samurai::initialize("Euler equations solver (2D)", argc, argv);
double Tf = .25;
double cfl = 0.4;
double t = 0.;
std::string restart_file;
std::size_t order = 1;
std::string slope_limiter = "moncen";
std::string time_integrator = "auto";
std::string scheme = "hllc";
std::string test_case = "double_mach_reflection";
double gamma = 0.; // only used when --gamma is given
bool check_positivity = false;
// Output parameters
fs::path path = "results";
std::string filename;
std::size_t nfiles = 1;
std::string metrics_file;
auto available = test_case::TestCaseRegistry<field_t>::instance().available_test_cases();
app.add_option("--cfl", cfl, "The CFL")->capture_default_str()->group("Simulation parameters");
app.add_option("--Ti", t, "Initial time")->capture_default_str()->group("Simulation parameters");
app.add_option("--Tf", Tf, "Final time")->capture_default_str()->group("Simulation parameters");
app.add_option("--scheme", scheme, "Finite volume scheme")
->capture_default_str()
->check(CLI::IsMember({"rusanov", "hll", "hllc"}))
->group("Simulation parameters");
app.add_option("--order", order, "Order of the scheme in space: 1 for cell averages, 2 for a MUSCL reconstruction")
->capture_default_str()
->check(CLI::IsMember({1, 2}))
->group("Simulation parameters");
app.add_option("--slope-limiter", slope_limiter, "Slope limiter of the MUSCL reconstruction")
->capture_default_str()
->check(CLI::IsMember({"none", "minmod", "vanleer", "moncen"}))
->group("Simulation parameters");
app.add_option("--time-integrator", time_integrator, "Time integration")
->capture_default_str()
->check(CLI::IsMember({"auto", "euler", "ssprk2", "strang"}))
->group("Simulation parameters");
app.add_option("--test-case", test_case, "Test case")->capture_default_str()->check(CLI::IsMember(available))->group("Simulation parameters");
auto* gamma_opt = app.add_option("--gamma", gamma, "Ratio of specific heats (defaults to the value of the test case)")
->group("Simulation parameters");
app.add_option("--restart-file", restart_file, "Restart file")->capture_default_str()->group("Simulation parameters");
app.add_flag("--check-positivity", check_positivity, "Check positivity of density and pressure at each iteration")
->group("Simulation parameters");
app.add_option("--path", path, "Output path")->capture_default_str()->group("Output");
app.add_option("--filename", filename, "File name prefix (defaults to <test-case>_<scheme>)")->group("Output");
app.add_option("--nfiles", nfiles, "Number of output files")->capture_default_str()->group("Output");
app.add_option("--metrics-file", metrics_file, "Write the performance metrics of the run, as JSON, to this file")->group("Output");
// The cases that take a parameter of their own declare it here, before the
// parse. All of them do, not only the selected one: which case runs is
// itself decided by the parse.
test_case::TestCaseRegistry<field_t>::instance().add_options(app);
SAMURAI_PARSE(argc, argv);
std::cout << "Samurai version: " << SAMURAI_VERSION << std::endl; // Print Samurai version info
const auto& selected = test_case::TestCaseRegistry<field_t>::instance().get(test_case);
// The test case carries the gas it was designed for; --gamma overrides it.
EOS::IdealGas eos = selected.eos;
if (gamma_opt->count() > 0)
{
eos.gamma = gamma;
}
std::cout << "Using gamma = " << eos.gamma << std::endl;
// The default integrator follows the order: explicit Euler is all a
// first-order flux can use, and SSP-RK2 is the one that reaches second
// order in every dimension. Naming one explicitly always wins.
if (time_integrator == "auto")
{
time_integrator = default_time_integrator(order);
}
const auto integrator = time_integrator_from_name(time_integrator);
if (filename.empty())
{
filename = fmt::format("{}_{}", test_case, scheme);
}
// Initialize the mesh
auto box = selected.box();
auto config = samurai::mesh_config<dim>().min_level(8).max_level(8).max_stencil_size(4).disable_minimal_ghost_width();
config.periodic(selected.periodic);
config.parse_args();
config.disable_args_parse();
auto mesh = samurai::mra::make_empty_mesh(config);
auto u = samurai::make_vector_field<double, 2 + dim>("euler", mesh);
// The operator function stores what it is handed; pass a prvalue so that it
// owns its own copy of the equation of state rather than a reference to this
// closure's member.
auto prediction_fn = [eos](auto& new_field, const auto& old_field)
{
return make_field_operator_function<Euler_prediction_op>(new_field, old_field, EOS::IdealGas{eos});
};
auto MRadaptation = samurai::make_MRAdapt(prediction_fn, u);
auto mra_config = samurai::mra_config().relative_detail(true);
if (restart_file.empty())
{
int jump = 0;
default_level = std::max(config.min_level(), default_level);
if (config.min_level() != config.max_level())
{
jump = static_cast<int>(config.max_level() - default_level);
if (jump > 0)
{
config.max_level() = default_level;
}
}
std::cout << "jump = " << jump << " min-level = " << config.min_level() << " max-level = " << config.max_level() << std::endl;
mesh = samurai::mra::make_mesh(box, config);
init_sol(u, config, jump, mra_config, test_case, eos);
std::cout << "Mesh initialized with " << mesh.nb_cells() << " cells." << std::endl;
}
else
{
samurai::load(restart_file, mesh, u);
}
// A MUSCL reconstruction reads two layers of ghost cells where the
// first-order flux reads one, so the boundary conditions are built to the
// width the scheme asks for.
bc::ghost_layers() = order;
init_bc(u, t, test_case, eos);
auto unp1 = samurai::make_vector_field<double, 2 + dim>("euler", mesh);
auto unp2 = samurai::make_vector_field<double, 2 + dim>("euler", mesh);
// SSP-RK2 evaluates the scheme on an intermediate state, and evaluating a
// scheme fills the ghost cells: the scratch fields need the same boundary
// conditions as the solution itself.
unp1.copy_bc_from(u);
unp2.copy_bc_from(u);
double dx = mesh.cell_length(config.max_level());
const double dt_save = Tf / static_cast<double>(nfiles);
std::size_t nsave = 0;
std::size_t nt = 0;
save(path.string(), fmt::format("{}_init", filename), u, eos);
// The conservative state, which is what --restart-file reloads. save()
// writes primitives for post-processing and cannot be read back.
samurai::dump(path, fmt::format("{}_restart_init", filename), mesh, u);
std::cout << fmt::format("Using scheme: {}, order {}, {} in time", scheme, order, time_integrator) << std::endl;
// Built once, called with a different time step at every iteration: the
// Hancock predictor reads the current one through this.
auto dt_for_flux = std::make_shared<double>(0.);
// The Hancock predictor belongs to the single-step integrators: with SSP-RK2
// the second order comes from the stages instead, and tracing as well would
// count it twice.
MusclOptions muscl_options{.limiter = slope_limiter_from_name(slope_limiter),
.hancock = integrator != TimeIntegrator::ssprk2,
.dt = dt_for_flux};
// Both orders are built, and the one the time loop uses is chosen per step.
// They are different types, a wider stencil being a different scheme.
auto first_order = make_first_order_scheme<decltype(u)>(scheme, eos);
auto second_order = make_second_order_scheme<decltype(u)>(scheme, eos, muscl_options);
// The same two, restricted to one direction each, for Strang.
auto directional = [&](auto&& make_one)
{
return [&]<std::size_t... D>(std::index_sequence<D...>)
{
return std::array{make_one(static_cast<int>(D))...};
}(std::make_index_sequence<dim>{});
};
auto first_order_sweeps = directional(
[&](int d)
{
return make_first_order_scheme<decltype(u)>(scheme, eos, d);
});
auto second_order_sweeps = directional(
[&](int d)
{
auto options = muscl_options;
options.direction = d;
return make_second_order_scheme<decltype(u)>(scheme, eos, options);
});
// The three numbers the article reports per run, measured here rather than
// reconstructed afterwards from a log: see euler/metrics.hpp.
Metrics metrics(mesh);
samurai::times::timers.start("TimeLoop");
metrics.start(mesh);
bool done = false;
while (!done)
{
double dt = cfl * dx / get_max_lambda(u, eos);
metrics.adapt(
[&]
{
MRadaptation(mra_config);
});
if (check_positivity)
{
check(u, eos);
}
if (std::isnan(t))
{
std::cerr << "Error: Time became NaN, stopping simulation" << std::endl;
break;
}
if (t + dt > Tf)
{
dt = Tf - t;
done = true;
}
std::cout << fmt::format("iteration {}: t = {}, dt = {}", nt++, t, dt) << "\r";
metrics.step(mesh);
if (order == 1)
{
advance(u, unp1, unp2, first_order, first_order_sweeps, dt_for_flux, dt, integrator);
}
else
{
advance(u, unp1, unp2, second_order, second_order_sweeps, dt_for_flux, dt, integrator);
}
t += dt;
if (t >= static_cast<double>(nsave + 1) * dt_save || t == Tf)
{
const std::string suffix = (nfiles != 1) ? fmt::format("_ite_{}", nsave++) : "";
// Writing is not part of the time to solution: how many files a run
// produces is a choice of the person running it.
metrics.output(
[&]
{
save(path.string(), fmt::format("{}{}", filename, suffix), u, eos);
samurai::dump(path, fmt::format("{}_restart{}", filename, suffix), mesh, u);
});
}
}
metrics.stop(mesh);
samurai::times::timers.stop("TimeLoop");
metrics.report(metrics_file);
samurai::finalize();
return 0;
}