Shamrock 2025.10.0
Astrophysical Code
Loading...
Searching...
No Matches
Solver.hpp
Go to the documentation of this file.
1// -------------------------------------------------------//
2//
3// SHAMROCK code for hydrodynamics
4// Copyright (c) 2021-2026 Timothée David--Cléris <tim.shamrock@proton.me>
5// SPDX-License-Identifier: CeCILL Free Software License Agreement v2.1
6// Shamrock is licensed under the CeCILL 2.1 License, see LICENSE for more information
7//
8// -------------------------------------------------------//
9
10#pragma once
11
19
21#include "SolverConfig.hpp"
22#include "shambackends/vec.hpp"
35#include <functional>
36#include <limits>
37#include <memory>
38#include <optional>
39#include <stdexcept>
40#include <variant>
41#include <vector>
42namespace shammodels::sph {
43
44 struct TimestepLog {
45 i32 rank;
46 f64 rate;
47 u64 npart;
48 f64 tcompute;
49
50 inline f64 rate_sum() { return shamalgs::collective::allreduce_sum(rate); }
51
52 inline u64 npart_sum() { return shamalgs::collective::allreduce_sum(npart); }
53
54 inline f64 tcompute_max() { return shamalgs::collective::allreduce_max(tcompute); }
55 };
56
58 bool reach_target_time;
59 bool reach_niter_max;
60 bool reach_max_walltime;
61
62 i32 iter_count;
63 };
64
69 struct WalltimeLimiter {
70 bool active;
71 f64 max_walltime;
72 f64 start_wall_time;
73 i32 next_check_iter;
74
75 inline WalltimeLimiter(bool active, f64 max_walltime)
76 : active(active), max_walltime(max_walltime) {
77 start_wall_time = active ? synced_wtime() : 0;
78 next_check_iter = active ? 1 : std::numeric_limits<i32>::max();
79 }
80
81 inline f64 synced_wtime() {
82 if (active) {
83 return shamalgs::collective::allreduce_max(shambase::details::get_wtime());
84 }
85 return 0;
86 }
87
89 inline bool due(i32 iter_count) const { return active && iter_count >= next_check_iter; }
90
93 inline bool exceeded(i32 iter_count) {
94 f64 global_walltime = synced_wtime();
95
96 // if the global walltime is greater than the max walltime
97 if (global_walltime >= max_walltime) {
98 if (shamcomm::world_rank() == 0) {
100 "SPH",
101 sham::format(
102 "stopping evolve until because of "
103 "max_walltime = {:.2f}s > {:.2f}s",
104 global_walltime,
105 max_walltime));
106 }
107 return true;
108 }
109
110 f64 sec_per_iter = (global_walltime - start_wall_time) / static_cast<f64>(iter_count);
111
112 auto get_remaining_iters = [&](f64 delta_walltime, f64 factor) -> i32 {
113 if (sec_per_iter > 0) {
114 f64 tmp = factor * delta_walltime / sec_per_iter;
115 if (tmp > std::numeric_limits<i32>::max()) {
116 return std::numeric_limits<i32>::max();
117 }
118 return static_cast<i32>(tmp);
119 }
120 return 1000; // default to 1000 iterations if sec_per_iter is 0
121 };
122
123 i32 iters_to_limit = get_remaining_iters(max_walltime - global_walltime, 0.25);
124 i32 iters_to_next_check = iters_to_limit;
125
126 next_check_iter = iter_count + std::max(1, iters_to_next_check);
127
128 if (shamcomm::world_rank() == 0) {
130 "SPH",
131 sham::format(
132 "next walltime check in {:.2f}s (niter = {}) global walltime = "
133 "{:.2f}s (max_walltime = {:.2f}s)",
134 iters_to_next_check * sec_per_iter,
135 iters_to_next_check,
136 global_walltime,
137 max_walltime));
138 }
139
140 return false;
141 }
142 };
143
150 template<class Tvec, template<class> class SPHKernel>
151 class Solver {
152 public:
153 using Tscal = shambase::VecComponent<Tvec>;
154 static constexpr u32 dim = shambase::VectorProperties<Tvec>::dimension;
155 using Kernel = SPHKernel<Tscal>;
156
157 using Config = SolverConfig<Tvec, SPHKernel>;
158
159 using u_morton = typename Config::u_morton;
160
161 static constexpr Tscal Rkern = Kernel::Rkern;
162
163 ShamrockCtx &context;
164 inline PatchScheduler &scheduler() { return shambase::get_check_ref(context.sched); }
165
167
168 Config solver_config;
169 SolverLog solve_logs;
170
172 inline Tscal &time_edge_value() {
173 return scheduler()
174 .synchronized_data
175 .template get_edge_ref<shamrock::solvergraph::IDataEdgeSerializable<Tscal>>("time")
176 .data;
177 }
178
180 inline Tscal &dt_edge_value() {
181 return scheduler()
182 .synchronized_data
183 .template get_edge_ref<shamrock::solvergraph::IDataEdgeSerializable<Tscal>>("dt")
184 .data;
185 }
186
189 return scheduler()
190 .synchronized_data
191 .template get_edge_ref<shamrock::solvergraph::IDataEdgeSerializable<Tscal>>(
192 "cfl_multiplier")
193 .data;
194 }
195
196 inline Tscal get_time() { return time_edge_value(); }
197 inline void set_time(Tscal t) { time_edge_value() = t; }
198 inline Tscal get_dt_sph() { return dt_edge_value(); }
199 inline void set_next_dt(Tscal dt) { dt_edge_value() = dt; }
200 inline Tscal get_cfl_multipler() { return cfl_multiplier_edge_value(); }
201 inline void set_cfl_multipler(Tscal lambda) { cfl_multiplier_edge_value() = lambda; }
202
205 auto &sync = scheduler().synchronized_data;
206 auto names = sync.get_edge_names();
207 auto has_edge = [&](const std::string &name) {
208 return std::find(names.begin(), names.end(), name) != names.end();
209 };
210
211 if (!has_edge("time")) {
212 auto edge = sync.register_edge(
214 edge->data = 0;
215 }
216 if (!has_edge("dt")) {
217 auto edge = sync.register_edge(
219 edge->data = 0;
220 }
221 if (!has_edge("cfl_multiplier")) {
222 auto edge = sync.register_edge(
223 "cfl_multiplier",
225 "cfl_multiplier", "C_{\\rm CFL}"));
226 edge->data = 1e-2;
227 }
228 }
229
231 std::optional<std::function<void(void)>> step_begin_callback;
232 std::optional<std::function<void(void)>> step_end_callback;
233 };
234 std::vector<SolverStepCallback> timestep_callbacks{};
235
236 inline void init_required_fields() { solver_config.set_layout(context.get_pdl_write()); }
237
238 // serial patch tree control
239 void gen_serial_patch_tree();
240 inline void reset_serial_patch_tree() { storage.serial_patch_tree.reset(); }
241
242 // interface_control
243 using GhostHandle = sph::BasicSPHGhostHandler<Tvec>;
244 using GhostHandleCache = typename GhostHandle::CacheMap;
245
246 inline void gen_ghost_handler(Tscal time_val) {
247
248 using CfgClass = sph::BasicSPHGhostHandlerConfig<Tvec>;
249 using BCConfig = typename CfgClass::Variant;
250
251 using BCFree = typename CfgClass::Free;
252 using BCPeriodic = typename CfgClass::Periodic;
253 using BCShearingPeriodic = typename CfgClass::ShearingPeriodic;
254
255 using SolverConfigBC = typename Config::BCConfig;
256 using SolverBCFree = typename SolverConfigBC::Free;
257 using SolverBCPeriodic = typename SolverConfigBC::Periodic;
258 using SolverBCShearingPeriodic = typename SolverConfigBC::ShearingPeriodic;
259
260 // boundary condition selections
261 if (SolverBCFree *c
262 = std::get_if<SolverBCFree>(&solver_config.boundary_config.config)) {
263 storage.ghost_handler.set(
264 GhostHandle{
265 scheduler(),
266 BCFree{},
267 storage.patch_rank_owner,
268 storage.xyzh_ghost_layout});
269 } else if (
270 SolverBCPeriodic *c
271 = std::get_if<SolverBCPeriodic>(&solver_config.boundary_config.config)) {
272 storage.ghost_handler.set(
273 GhostHandle{
274 scheduler(),
275 BCPeriodic{},
276 storage.patch_rank_owner,
277 storage.xyzh_ghost_layout});
278 } else if (
279 SolverBCShearingPeriodic *c
280 = std::get_if<SolverBCShearingPeriodic>(&solver_config.boundary_config.config)) {
281 storage.ghost_handler.set(
282 GhostHandle{
283 scheduler(),
284 BCShearingPeriodic{
285 c->shear_base, c->shear_dir, c->shear_speed * time_val, c->shear_speed},
286 storage.patch_rank_owner,
287 storage.xyzh_ghost_layout});
288 }
289 }
290 inline void reset_ghost_handler() { storage.ghost_handler.reset(); }
291
293 void build_ghost_cache();
295 void clear_ghost_cache();
296
299
300 // trees
301 using RTree = typename Config::RTree;
306
310 void reset_presteps_rint();
311
316
318 void sph_prestep(Tscal time_val, Tscal dt);
319
321 void apply_position_boundary(Tscal time_val);
322
324 void update_artificial_viscosity(Tscal dt);
325
327 void init_ghost_layout();
328
333
335 void compute_eos_fields();
336
338 void reset_eos_fields();
339
341 void prepare_corrector();
343 void update_derivs(Tscal dt_hydro);
350 bool apply_corrector(Tscal dt, u64 Npart_all);
351
354
355 Solver(ShamrockCtx &context) : context(context) {}
356
358 void init_solver_graph();
359
361 void vtk_do_dump(std::string filename, bool add_patch_world_id);
362
363 void set_debug_dump(bool _do_debug_dump, std::string _debug_dump_filename) {
364 solver_config.set_debug_dump(_do_debug_dump, _debug_dump_filename);
365 }
366
367 inline void print_timestep_logs() {
368 if (shamcomm::world_rank() == 0) {
369 logger::info_ln("SPH", "iteration since start :", solve_logs.get_iteration_count());
370 logger::info_ln("SPH", "time since start :", shambase::details::get_wtime(), "(s)");
371 }
372 }
373
375 TimestepLog evolve_once();
376
378 Tscal evolve_once_time_expl(Tscal t_current, Tscal dt_input) {
379 set_time(t_current);
380 set_next_dt(dt_input);
381 evolve_once();
382 return get_dt_sph();
383 }
384
385 inline EvolveUntilResults evolve_until(
386 Tscal target_time, i32 niter_max, f64 max_walltime = -1) {
387
388 const bool niter_limit_active = (niter_max >= 0);
389 const bool walltime_limit_active = (max_walltime >= 0);
390
391 if (shamcomm::world_rank() == 0) {
393 "SPH",
394 sham::format(
395 "evolve_until (target_time = {:.2f}s, niter_max = {}, max_walltime = "
396 "{:.2f}s)",
397 target_time,
398 niter_max,
399 max_walltime));
400 }
401
402 auto step = [&]() {
403 Tscal dt = get_dt_sph();
404 Tscal t = get_time();
405
406 if (t > target_time) {
408 "the target time is higher than the current time");
409 }
410
411 if (t + dt > target_time) {
412 set_next_dt(target_time - t);
413 }
414 evolve_once();
415 };
416
417 WalltimeLimiter walltime_limiter(walltime_limit_active, max_walltime);
418
419 i32 iter_count = 0;
420
421 while (get_time() < target_time) {
422 step();
423 iter_count++;
424
425 // if the iteration count is greater than the maximum iteration count
426 if (niter_limit_active && iter_count >= niter_max) {
427 if (shamcomm::world_rank() == 0) {
428 logger::info_ln(
429 "SPH", "stopping evolve until because of niter =", iter_count);
430 }
431 return {
432 .reach_target_time = false,
433 .reach_niter_max = true,
434 .reach_max_walltime = false,
435 .iter_count = iter_count,
436 };
437 }
438
439 // if walltime limit is active and the next walltime check is due
440 if (walltime_limiter.due(iter_count)) {
441 // must be inside the .due if since there is a MPI reduction in exceeded
442 if (walltime_limiter.exceeded(iter_count)) {
443 return {
444 .reach_target_time = false,
445 .reach_niter_max = false,
446 .reach_max_walltime = true,
447 .iter_count = iter_count,
448 };
449 }
450 }
451 }
452
453 print_timestep_logs();
454
455 return {
456 .reach_target_time = true,
457 .reach_niter_max = false,
458 .reach_max_walltime = false,
459 .iter_count = iter_count,
460 };
461 }
462 };
463
464} // namespace shammodels::sph
double f64
Alias for double.
std::uint32_t u32
32 bit unsigned integer
std::uint64_t u64
64 bit unsigned integer
std::int32_t i32
32 bit integer
The MPI scheduler.
void reset_presteps_rint()
Resets tree radius interval field.
Definition Solver.cpp:1884
void ensure_time_state_edges()
Register time/dt/cfl_multiplier synchronized edges if missing (idempotent).
Definition Solver.hpp:204
void reset_merge_ghosts_fields()
Resets merged ghost field data.
Definition Solver.cpp:2160
void update_sync_load_values()
Updates load balancing values and synchronizes patch ownership.
Definition Solver.cpp:2459
Tscal & cfl_multiplier_edge_value()
Access synchronized CFL multiplier (scheduler edge "cfl_multiplier").
Definition Solver.hpp:188
bool apply_corrector(Tscal dt, u64 Npart_all)
Definition Solver.cpp:2454
void merge_position_ghost()
Merges ghost particle positions from neighboring patches.
Definition Solver.cpp:1529
void reset_eos_fields()
Frees memory allocated for EOS fields.
Definition Solver.cpp:2186
void prepare_corrector()
Saves old derivative fields for predictor-corrector integration.
Definition Solver.cpp:2192
void build_ghost_cache()
Builds ghost particle interface cache for inter-patch communication.
Definition Solver.cpp:1507
void update_artificial_viscosity(Tscal dt)
Updates artificial viscosity coefficients for shock capturing.
Definition Solver.cpp:2169
TimestepLog evolve_once()
Performs one complete SPH timestep evolution.
Definition Solver.cpp:2466
void vtk_do_dump(std::string filename, bool add_patch_world_id)
Writes VTK dump file for visualization.
Definition Solver.cpp:1284
void update_derivs(Tscal dt_hydro)
Updates time derivatives and applies external forces.
Definition Solver.cpp:2288
void build_merged_pos_trees()
Builds spatial BVH trees for merged positions including ghosts.
Definition Solver.cpp:1572
void clear_merged_pos_trees()
Clears merged position trees to free memory.
Definition Solver.cpp:1577
void init_solver_graph()
Initializes the solver graph for computation pipeline.
Definition Solver.cpp:263
void sph_prestep(Tscal time_val, Tscal dt)
Performs pre-step operations for SPH timestep.
Definition Solver.cpp:1583
void compute_presteps_rint()
Computes maximum smoothing length in tree nodes for neighbor search.
Definition Solver.cpp:1847
void compute_eos_fields()
Computes equation of state fields (pressure, sound speed).
Definition Solver.cpp:2180
void apply_position_boundary(Tscal time_val)
Applies position-based boundary conditions.
Definition Solver.cpp:1461
void reset_neighbors_cache()
Resets neighbor cache.
Definition Solver.cpp:1913
Tscal evolve_once_time_expl(Tscal t_current, Tscal dt_input)
Evolves system by one explicit timestep with specified time and dt.
Definition Solver.hpp:378
Tscal & dt_edge_value()
Access synchronized next dt (scheduler edge "dt", not solver_graph "dt").
Definition Solver.hpp:180
void communicate_merge_ghosts_fields()
Communicates and merges ghost particle fields across processes.
Definition Solver.cpp:1918
void clear_ghost_cache()
Clears ghost particle cache to free memory.
Definition Solver.cpp:1523
void init_ghost_layout()
Initializes data layout for ghost particle fields.
Definition Solver.cpp:1832
void start_neighbors_cache()
Builds neighbor particle cache for SPH calculations.
Definition Solver.cpp:1889
Tscal & time_edge_value()
Access synchronized simulation time (scheduler edge "time").
Definition Solver.hpp:172
This header file contains utility functions related to exception handling in the code.
T & get_check_ref(const std::unique_ptr< T > &ptr, SourceLocation loc=SourceLocation())
Takes a std::unique_ptr and returns a reference to the object it holds. It throws a std::runtime_erro...
Definition memory.hpp:112
ExcptTypes make_except_with_loc(std::string message, SourceLocation loc=SourceLocation{})
Create an exception with a message and a location.
i32 world_rank()
Gives the rank of the current process in the MPI communicator.
Definition worldInfo.cpp:41
namespace for the sph model
void info_ln(std::string module_name, Types... var2)
Prints a log message with multiple arguments followed by a newline.
Definition logs.hpp:132
f64 get_wtime()
Returns the current wall clock time in seconds.
Class holding the logs of the solver /todo add a variable to keep only a definite number of steps in ...
Definition SolverLog.hpp:34
The configuration for a sph solver.
BCConfig boundary_config
Boundary condition configuration.
u32 u_morton
The type of the Morton code for the tree.
BCConfig< Tvec > BCConfig
Configuration of the boundary conditions.
bool due(i32 iter_count) const
True if the next walltime check is due at this iteration count.
Definition Solver.hpp:89
bool exceeded(i32 iter_count)
Definition Solver.hpp:93