Shamrock 2025.10.0
Astrophysical Code
Loading...
Searching...
No Matches
pyGSPHModel.cpp
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
25
27#include "shambase/memory.hpp"
35#include <pybind11/cast.h>
36#include <pybind11/numpy.h>
37#include <memory>
38
39template<class Tvec, template<class> class SPHKernel>
40void add_gsph_instance(py::module &m, std::string name_config, std::string name_model) {
41 using namespace shammodels::gsph;
42
43 using Tscal = shambase::VecComponent<Tvec>;
44
45 using T = Model<Tvec, SPHKernel>;
46 using TConfig = typename T::SolverConfig;
47
48 shamlog_debug_ln("[Py]", "registering class :", name_config, typeid(T).name());
49 shamlog_debug_ln("[Py]", "registering class :", name_model, typeid(T).name());
50
51 py::class_<TConfig> config_cls(m, name_config.c_str());
52
53 shammodels::common::add_json_defs<TConfig>(config_cls);
54
55 config_cls.def("print_status", &TConfig::print_status)
56 .def("set_tree_reduction_level", &TConfig::set_tree_reduction_level)
57 .def("set_two_stage_search", &TConfig::set_two_stage_search)
58 // Riemann solver config
59 .def(
60 "set_riemann_iterative",
61 [](TConfig &self, Tscal tol, u32 max_iter) {
62 self.set_riemann_iterative(tol, max_iter);
63 },
64 py::kw_only(),
65 py::arg("tolerance") = Tscal{1e-6},
66 py::arg("max_iter") = 20,
67 R"==(
68 Set iterative Riemann solver (van Leer 1997).
69
70 This is the most accurate but slower Riemann solver.
71 Uses Newton-Raphson iteration to find the pressure in the star region.
72
73 Parameters
74 ----------
75 tolerance : float
76 Convergence tolerance for Newton-Raphson iteration (default: 1e-6)
77 max_iter : int
78 Maximum number of iterations (default: 20)
79)==")
80 .def(
81 "set_riemann_hllc",
82 [](TConfig &self) {
83 self.set_riemann_hllc();
84 },
85 R"==(
86 Set HLLC approximate Riemann solver.
87
88 Fast approximate Riemann solver that captures contact discontinuities.
89 Recommended for general use - good balance of accuracy and speed.
90)==")
91 .def(
92 "set_riemann_exact",
93 [](TConfig &self, Tscal tol, u32 max_iter) {
94 self.set_riemann_exact(tol, max_iter);
95 },
96 py::kw_only(),
97 py::arg("tolerance") = Tscal{1e-8},
98 py::arg("max_iter") = 100,
99 R"==(
100 Set exact Riemann solver (Toro 2009).
101
102 Classifies the wave pattern (shock/rarefaction on each side) from the
103 initial states, then solves the matching closed-form relation via
104 bisection. Most accurate but computationally expensive; unlike the
105 iterative (van Leer) solver, it also remains accurate for strong
106 rarefactions / near-vacuum conditions.
107
108 Parameters
109 ----------
110 tolerance : float
111 Bisection convergence tolerance (default: 1e-8)
112 max_iter : int
113 Maximum number of bisection iterations (default: 100)
114)==")
115 // Reconstruction config
116 .def(
117 "set_reconstruct_piecewise_constant",
118 [](TConfig &self) {
119 self.set_reconstruct_piecewise_constant();
120 },
121 R"==(
122 Set first-order piecewise constant reconstruction.
123
124 Sets all gradients to zero. Most diffusive but most stable.
125 Good for very strong shocks or initial testing.
126)==")
127 // Force formulation config
128 .def(
129 "set_force_cha_whitworth",
130 [](TConfig &self) {
131 self.set_force_cha_whitworth();
132 },
133 R"==(
134 Set the Cha & Whitworth (2003) symmetric SPH force formulation (default).
135
136 Uses the standard SPH momentum equation (nabla_W/rho^2/Omega) with the
137 Riemann-solved interface pressure p* substituted for pressure.
138)==")
139 .def(
140 "set_force_inutsuka_v2",
141 [](TConfig &self) {
142 self.set_force_inutsuka_v2();
143 },
144 R"==(
145 Set the Inutsuka (2002) effective volume/face force formulation.
146
147 Uses linear (1st order) interpolation of the volume element between each
148 particle pair to build an effective face (V2_ij, s*), following the
149 original GSPH momentum equation: acc -= m * p* * V2_ij * grad_W_ij.
150)==")
151 // EOS config
152 .def(
153 "set_eos_adiabatic",
154 [](TConfig &self, Tscal gamma) {
155 self.set_eos_adiabatic(gamma);
156 },
157 py::arg("gamma"),
158 R"==(
159 Set adiabatic equation of state: P = (\gamma-1) \rho u
160
161 Parameters
162 ----------
163 gamma : float
164 Adiabatic index (e.g., 5/3 for monatomic gas, 7/5 for diatomic)
165)==")
166 .def(
167 "set_eos_isothermal",
168 [](TConfig &self, Tscal cs) {
169 self.set_eos_isothermal(cs);
170 },
171 py::arg("cs"),
172 R"==(
173 Set isothermal equation of state: P = cs^2 \rho
174
175 Parameters
176 ----------
177 cs : float
178 Sound speed
179)==")
180 // Boundary config
181 .def("set_boundary_free", &TConfig::set_boundary_free)
182 .def("set_boundary_periodic", &TConfig::set_boundary_periodic)
183 // External forces
184 .def(
185 "add_ext_force_point_mass",
186 [](TConfig &self, Tscal central_mass, Tscal Racc) {
187 self.add_ext_force_point_mass(central_mass, Racc);
188 },
189 py::kw_only(),
190 py::arg("central_mass"),
191 py::arg("Racc"))
192 // Units
193 .def("set_units", &TConfig::set_units)
194 // CFL
195 .def(
196 "set_cfl_cour",
197 [](TConfig &self, Tscal cfl_cour) {
198 self.cfl_config.cfl_cour = cfl_cour;
199 })
200 .def(
201 "set_cfl_force",
202 [](TConfig &self, Tscal cfl_force) {
203 self.cfl_config.cfl_force = cfl_force;
204 })
205 .def(
206 "set_particle_mass",
207 [](TConfig &self, Tscal gpart_mass) {
208 self.gpart_mass = gpart_mass;
209 })
210 .def(
211 "set_scheduler_config",
212 [](TConfig &self, u64 split_crit, u64 merge_crit) {
213 self.scheduler_conf.split_load_value = split_crit;
214 self.scheduler_conf.merge_load_value = merge_crit;
215 },
216 py::kw_only(),
217 py::arg("split_load_value"),
218 py::arg("merge_load_value"));
219
220 py::class_<T>(m, name_model.c_str())
221 .def(py::init([](ShamrockCtx &ctx) {
222 return std::make_unique<T>(ctx);
223 }))
224 .def("init", &T::init)
225 .def("init_scheduler", &T::init_scheduler)
226 .def("evolve_once", &T::evolve_once)
227 .def(
228 "evolve_until",
229 [](T &self, f64 target_time, i32 niter_max) {
230 return self.evolve_until(target_time, niter_max);
231 },
232 py::arg("target_time"),
233 py::kw_only(),
234 py::arg("niter_max") = -1)
235 .def("timestep", &T::timestep)
236 .def("set_cfl_cour", &T::set_cfl_cour, py::arg("cfl_cour"))
237 .def("set_cfl_force", &T::set_cfl_force, py::arg("cfl_force"))
238 .def("set_particle_mass", &T::set_particle_mass, py::arg("gpart_mass"))
239 .def("get_particle_mass", &T::get_particle_mass)
240 .def("rho_h", &T::rho_h)
241 .def("get_hfact", &T::get_hfact)
242 .def(
243 "get_box_dim_fcc_3d",
244 [](T &self, f64 dr, u32 xcnt, u32 ycnt, u32 zcnt) {
245 return self.get_box_dim_fcc_3d(dr, xcnt, ycnt, zcnt);
246 })
247 .def(
248 "get_ideal_fcc_box",
249 [](T &self, f64 dr, f64_3 box_min, f64_3 box_max) {
250 return self.get_ideal_fcc_box(dr, {box_min, box_max});
251 })
252 .def(
253 "get_ideal_hcp_box",
254 [](T &self, f64 dr, f64_3 box_min, f64_3 box_max) {
255 return self.get_ideal_hcp_box(dr, {box_min, box_max});
256 })
257 .def(
258 "resize_simulation_box",
259 [](T &self, f64_3 box_min, f64_3 box_max) {
260 return self.resize_simulation_box({box_min, box_max});
261 })
262 .def(
263 "add_cube_fcc_3d",
264 [](T &self, f64 dr, f64_3 box_min, f64_3 box_max) {
265 return self.add_cube_fcc_3d(dr, {box_min, box_max});
266 })
267 .def(
268 "add_cube_hcp_3d",
269 [](T &self, f64 dr, f64_3 box_min, f64_3 box_max) {
270 return self.add_cube_hcp_3d(dr, {box_min, box_max});
271 })
272 .def("get_total_part_count", &T::get_total_part_count)
273 .def("total_mass_to_part_mass", &T::total_mass_to_part_mass)
274 .def(
275 "set_field_in_box",
276 [](T &self,
277 std::string field_name,
278 std::string field_type,
279 pybind11::object value,
280 f64_3 box_min,
281 f64_3 box_max,
282 u32 ivar) {
283 if (field_type == "f64") {
284 f64 val = value.cast<f64>();
285 self.set_field_in_box(field_name, val, {box_min, box_max}, ivar);
286 } else if (field_type == "f64_3") {
287 f64_3 val = value.cast<f64_3>();
288 self.set_field_in_box(field_name, val, {box_min, box_max}, ivar);
289 } else if (field_type == "u32") {
290 u32 val = value.cast<u32>();
291 self.set_field_in_box(field_name, val, {box_min, box_max}, ivar);
292 } else {
294 "unknown field type: " + field_type + ". Valid types: f64, f64_3, u32");
295 }
296 },
297 py::arg("field_name"),
298 py::arg("field_type"),
299 py::arg("value"),
300 py::arg("box_min"),
301 py::arg("box_max"),
302 py::kw_only(),
303 py::arg("ivar") = 0,
304 R"==(
305 Set field value for particles within a box region.
306
307 Useful for setting up discontinuous initial conditions like Sod shock tube.
308
309 Parameters
310 ----------
311 field_name : str
312 Name of the field to set (e.g., "vxyz", "uint", "hpart")
313 field_type : str
314 Type of the field: "f64", "f64_3", or "u32"
315 value : float, tuple, or int
316 Value to set (type must match field_type)
317 box_min : tuple
318 Minimum corner of the box (x, y, z)
319 box_max : tuple
320 Maximum corner of the box (x, y, z)
321 ivar : int
322 Variable index for multi-component fields (default: 0)
323
324 Examples
325 --------
326 >>> # Sod shock tube: set left state internal energy
327 >>> model.set_field_in_box("uint", "f64", u_left, (-1,-1,-1), (0,1,1))
328 >>> # Set right state
329 >>> model.set_field_in_box("uint", "f64", u_right, (0,-1,-1), (1,1,1))
330)==")
331 .def(
332 "set_field_in_sphere",
333 [](T &self,
334 std::string field_name,
335 std::string field_type,
336 pybind11::object value,
337 f64_3 center,
338 f64 radius) {
339 if (field_type == "f64") {
340 f64 val = value.cast<f64>();
341 self.set_field_in_sphere(field_name, val, center, radius);
342 } else if (field_type == "f64_3") {
343 f64_3 val = value.cast<f64_3>();
344 self.set_field_in_sphere(field_name, val, center, radius);
345 } else {
347 "unknown field type");
348 }
349 },
350 py::arg("field_name"),
351 py::arg("field_type"),
352 py::arg("value"),
353 py::arg("center"),
354 py::arg("radius"),
355 R"==(
356 Set field value for particles within a spherical region.
357
358 Useful for setting up point-source initial conditions like Sedov blast.
359
360 Parameters
361 ----------
362 field_name : str
363 Name of the field to set (e.g., "uint")
364 field_type : str
365 Type of the field: "f64" or "f64_3"
366 value : float or tuple
367 Value to set (type must match field_type)
368 center : tuple
369 Center of the sphere (x, y, z)
370 radius : float
371 Radius of the sphere
372
373 Examples
374 --------
375 >>> # Sedov blast: inject energy in central sphere
376 >>> model.set_field_in_sphere("uint", "f64", u_blast, (0,0,0), r_blast)
377)==")
378 .def("apply_field_from_position_f64_3", &T::template apply_field_from_position<f64_3>)
379 .def("apply_field_from_position_f64", &T::template apply_field_from_position<f64>)
380 .def(
381 "get_sum",
382 [](T &self, std::string field_name, std::string field_type) {
383 if (field_type == "f64") {
384 return py::cast(self.template get_sum<f64>(field_name));
385 } else if (field_type == "f64_3") {
386 return py::cast(self.template get_sum<f64_3>(field_name));
387 } else {
389 "unknown field type");
390 }
391 })
392 .def(
393 "gen_default_config",
394 [](T &self) {
395 return self.gen_default_config();
396 })
397 .def(
398 "get_current_config",
399 [](T &self) {
400 return self.solver.solver_config;
401 })
402 .def("set_solver_config", &T::set_solver_config)
403 .def("do_vtk_dump", &T::do_vtk_dump)
404 .def("solver_logs_last_rate", &T::solver_logs_last_rate)
405 .def("solver_logs_last_obj_count", &T::solver_logs_last_obj_count)
406 .def(
407 "get_time",
408 [](T &self) {
409 return self.solver.get_time();
410 })
411 .def(
412 "get_dt",
413 [](T &self) {
414 return self.solver.get_dt();
415 })
416 .def(
417 "set_time",
418 [](T &self, Tscal t) {
419 return self.solver.set_time(t);
420 })
421 .def(
422 "set_next_dt",
423 [](T &self, Tscal dt) {
424 return self.solver.set_next_dt(dt);
425 })
426 .def(
427 "load_from_dump",
428 &T::load_from_dump,
429 py::arg("filename"),
430 R"==(
431 Load simulation state from a Shamrock dump file.
432
433 Uses the shared ShamrockDump mechanism (same as SPH).
434
435 Parameters
436 ----------
437 filename : str
438 Path to the dump file
439
440 Example
441 -------
442 >>> model.load_from_dump("checkpoint.shamrock")
443)==")
444 .def(
445 "dump",
446 &T::dump,
447 py::arg("filename"),
448 R"==(
449 Write simulation state to a Shamrock dump file.
450
451 Uses the shared ShamrockDump mechanism (same as SPH).
452
453 Parameters
454 ----------
455 filename : str
456 Path to the dump file
457
458 Example
459 -------
460 >>> model.dump("checkpoint.shamrock")
461)==");
462}
463
464using namespace shammodels::gsph;
465
467 auto &m = root_module;
468
469 py::module mgsph = m.def_submodule("model_gsph", "Shamrock GSPH (Godunov SPH) solver");
470
471 using namespace shammodels::gsph;
472
473 // Register GSPH models for different kernels
474 add_gsph_instance<f64_3, shammath::M4>(
475 mgsph, "GSPHModel_f64_3_M4_SolverConfig", "GSPHModel_f64_3_M4");
476 add_gsph_instance<f64_3, shammath::M6>(
477 mgsph, "GSPHModel_f64_3_M6_SolverConfig", "GSPHModel_f64_3_M6");
478 add_gsph_instance<f64_3, shammath::M8>(
479 mgsph, "GSPHModel_f64_3_M8_SolverConfig", "GSPHModel_f64_3_M8");
480
481 add_gsph_instance<f64_3, shammath::C2>(
482 mgsph, "GSPHModel_f64_3_C2_SolverConfig", "GSPHModel_f64_3_C2");
483 add_gsph_instance<f64_3, shammath::C4>(
484 mgsph, "GSPHModel_f64_3_C4_SolverConfig", "GSPHModel_f64_3_C4");
485 add_gsph_instance<f64_3, shammath::C6>(
486 mgsph, "GSPHModel_f64_3_C6_SolverConfig", "GSPHModel_f64_3_C6");
487
488 using VariantGSPHModelBind = std::variant<
489 std::unique_ptr<Model<f64_3, shammath::M4>>,
490 std::unique_ptr<Model<f64_3, shammath::M6>>,
491 std::unique_ptr<Model<f64_3, shammath::M8>>,
492 std::unique_ptr<Model<f64_3, shammath::C2>>,
493 std::unique_ptr<Model<f64_3, shammath::C4>>,
494 std::unique_ptr<Model<f64_3, shammath::C6>>>;
495
496 m.def(
497 "get_Model_GSPH",
498 [](ShamrockCtx &ctx, std::string vector_type, std::string kernel) -> VariantGSPHModelBind {
499 VariantGSPHModelBind ret;
500
501 if (vector_type == "f64_3" && kernel == "M4") {
502 ret = std::make_unique<Model<f64_3, shammath::M4>>(ctx);
503 } else if (vector_type == "f64_3" && kernel == "M6") {
504 ret = std::make_unique<Model<f64_3, shammath::M6>>(ctx);
505 } else if (vector_type == "f64_3" && kernel == "M8") {
506 ret = std::make_unique<Model<f64_3, shammath::M8>>(ctx);
507 } else if (vector_type == "f64_3" && kernel == "C2") {
508 ret = std::make_unique<Model<f64_3, shammath::C2>>(ctx);
509 } else if (vector_type == "f64_3" && kernel == "C4") {
510 ret = std::make_unique<Model<f64_3, shammath::C4>>(ctx);
511 } else if (vector_type == "f64_3" && kernel == "C6") {
512 ret = std::make_unique<Model<f64_3, shammath::C6>>(ctx);
513 } else {
515 "unknown combination of representation and kernel");
516 }
517
518 return ret;
519 },
520 py::kw_only(),
521 py::arg("context"),
522 py::arg("vector_type") = "f64_3",
523 py::arg("sph_kernel") = "M4",
524 R"==(
525 Create a GSPH (Godunov SPH) model.
526
527 GSPH uses Riemann solvers at particle interfaces instead of artificial viscosity,
528 giving sharper shock resolution.
529
530 Parameters
531 ----------
532 context : ShamrockCtx
533 Shamrock context
534 vector_type : str
535 Vector type, e.g., "f64_3" for 3D double precision (default: "f64_3")
536 sph_kernel : str
537 SPH kernel type: "M4" (cubic spline, default), "M6", "M8" (quintic spline),
538 "C2", "C4", "C6" (Wendland kernels)
539
540 Returns
541 -------
542 GSPHModel
543 A GSPH model instance
544
545 Examples
546 --------
547 >>> ctx = shamrock.ShamrockCtx()
548 >>> model = shamrock.get_Model_GSPH(context=ctx) # Uses M4 kernel by default
549 >>> config = model.gen_default_config()
550 >>> config.set_riemann_hllc()
551 >>> config.set_eos_adiabatic(1.4)
552 >>> model.set_solver_config(config)
553)==");
554}
MPI scheduler.
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 GSPH Model class.
Definition Model.hpp:63
This header file contains utility functions related to exception handling in the code.
GSPH Model class - high-level interface for GSPH simulations.
ExcptTypes make_except_with_loc(std::string message, SourceLocation loc=SourceLocation{})
Create an exception with a message and a location.
Pybind11 include and definitions.
#define ON_PYTHON_INIT
Register a Python module init function using static initialization.
Utilities to convert JSON objects to Python objects and vice versa. TODO: try to convert directly wit...
sph kernels
Functions related to the MPI communicator.