Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
043591a
metric static properties simplified
haykh May 25, 2026
8437101
more generic traits for engines in kernels + framework objects
haykh May 25, 2026
d73c80d
communication flags made more generic
haykh May 25, 2026
75c6f7c
engine traits generalized further
haykh May 25, 2026
00f9d77
hybrid engine
haykh May 25, 2026
6ecfa33
hybrid test fixed
haykh Jun 2, 2026
b0e1863
hybrid engine
haykh Jun 2, 2026
e6e5c78
faraday for hybrid
haykh Jun 2, 2026
b0ef229
1d EMF kernel
alisagk Jun 2, 2026
35ddef5
EMF kernel call
alisagk Jun 2, 2026
dbb93e9
formating
alisagk Jun 2, 2026
5596c4e
2D and 3D kernels added
alisagk Jun 3, 2026
a4fcdd7
Ee doesn't need grad N
alisagk Jun 3, 2026
0139de2
hybrid particle pusher
LudwigBoess Jun 3, 2026
41ab5cc
non-relativistic velocities only for hybrid
LudwigBoess Jun 3, 2026
7ba1837
refactor EMF started (WIP)
haykh Jun 3, 2026
025da9f
EMF kernel update
lucaorusa Jun 4, 2026
7d38709
pusher initial commit
LudwigBoess Jun 4, 2026
2a79c51
Merge branch 'dev/hybrid' of github.com:entity-toolkit/entity into de…
LudwigBoess Jun 4, 2026
50ce448
updated EMF call
haykh Jun 4, 2026
0c54c70
updated call in mainloop
haykh Jun 4, 2026
969ce29
mv params from engine_params
haykh Jun 4, 2026
a6da8ea
params for hybrid
haykh Jun 4, 2026
a94bedd
default hybrid params
haykh Jun 4, 2026
4badb76
proper step0 implementation
haykh Jun 4, 2026
10f160a
minor fix in dependencies.py
haykh Jun 4, 2026
85b4650
emf mod
haykh Jun 4, 2026
19e46d2
hotfix for nvcc in LAMBDA
haykh Jun 4, 2026
9968a94
version bump
haykh Jun 4, 2026
dec10ba
Merge branch 'bug/nvcc_hotfix' into dev/hybrid
haykh Jun 4, 2026
435da9f
dens_min + nonrel maxwellian
haykh Jun 4, 2026
f2d9b3e
filtering for moments
LudwigBoess Jun 4, 2026
f375a0a
bugfixes
LudwigBoess Jun 4, 2026
67baa47
moved hybrid parameters to seperate section and introduced density fl…
LudwigBoess Jun 4, 2026
b931aae
apply U-basis dx factors in EMF and Faraday
LudwigBoess Jul 1, 2026
8b4bec6
fix: zero the deposit-sync buff once per SynchronizeFields, not per d…
LudwigBoess Jul 1, 2026
bd75176
make dens_min a vacuum threshold
LudwigBoess Jul 1, 2026
5e8b371
add a per-cell stencil-aware Hall limiter
LudwigBoess Jul 1, 2026
670f870
hybrid: reflecting-wall (perfect-conductor) BC layer for x1 walls
LudwigBoess Jul 1, 2026
b61cc9e
Pegasus-style sub-cycled magnetic-field advance
LudwigBoess Jul 1, 2026
ef2dad6
single-species uniform injector
LudwigBoess Jul 1, 2026
659222f
document hybrid Hall-limiter and subcycle params in example toml
LudwigBoess Jul 1, 2026
764c876
vacuum resistivity for empty cells
LudwigBoess Jul 1, 2026
2bf7a78
hyper-resistivity for the filtered band
LudwigBoess Jul 1, 2026
f84ba8b
document hybrid params
LudwigBoess Jul 1, 2026
91e3e72
guard the subcycle step-count against integer overflow
LudwigBoess Jul 2, 2026
895d01a
Merge branch '1.5.0rc' into dev/hybrid
LudwigBoess Jul 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,8 +367,10 @@ def on_install_confirmed(settings: Settings) -> None:
"kokkos",
settings.kokkos_version,
settings.kokkos_backend,
settings.kokkos_arch.strip().lower(),
)
if settings.kokkos_arch.strip().lower():
kokkos_modfile_file = os.path.join(kokkos_modfile_file, settings.kokkos_arch.strip().lower())

os.makedirs(os.path.dirname(kokkos_modfile_file), exist_ok=True)
if os.path.exists(kokkos_modfile_file) and not settings.overwrite:
raise FileExistsError(
Expand Down
74 changes: 73 additions & 1 deletion input.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# Simulation engine to use
# @required
# @type: string
# @enum: "SRPIC", "GRPIC"
# @enum: "SRPIC", "GRPIC", "HYBRID"
engine = ""
# Max runtime in physical (code) units
# @required
Expand Down Expand Up @@ -224,6 +224,78 @@
# @from: `scales.larmor0`
# @value: `1 / larmor0`

# Electron-fluid closure for the hybrid Ohm's law
# @note: Only read by the `HYBRID` engine (ignored by `SRPIC`/`GRPIC`)
[hybrid]
# Adiabatic index of the massless electron fluid
# @type: float [> 0.0]
# @default: 1.66667 (= 5/3)
# @note: `1.0` corresponds to an isothermal closure
gamma_ad = ""
# Electron temperature `T_e` in physical (code) units
# @type: float [>= 0.0]
# @default: 0.0
# @note: `0.0` corresponds to cold electrons
theta0 = ""
# Vacuum threshold for the hybrid Ohm's law, in units of `n0`
# @type: float [> 0.0]
# @default: 1e-3
# @note: Above it `E` follows the usual `[...]/N`; below it `E` ramps continuously
# to zero so plasma-free cells keep `B` frozen instead of amplifying the
# right-hand side by `1/dens_min` (statistically-empty or unfilled ghost
# cells, a fraction `e^-ppc` for finite `ppc`)
dens_min = ""
# Cap on the local whistler Courant the Ohm's-law Hall term may imply
# @type: float
# @default: 0.5
# @note: The implied whistler speed `v_w ~ |B|/N` can outrun `dx/dt` at shock
# overshoots and magnetic cavities; the Hall term is scaled so its
# whistler Courant never exceeds this. Identity in resolvable cells
# @note: `<= 0.0` disables the limiter
hall_lim = ""
# Vacuum resistivity, in physical (code) units
# @type: float [>= 0.0]
# @default: 0.0
# @note: `> 0.0` makes the sub-`dens_min` vacuum resistive: `E` crossfades to
# `eta * curl B` below the threshold, so vacuum regions diffuse toward a
# curl-free field instead of freezing. Needed when fast plasma sweeps
# along a vacuum boundary, where a hard `E = 0` mask pumps `B` at `~ u/dx`
# @note: Internally clamped to the explicit-diffusion stability limit; any value is safe
# @note: `0.0` disables it (hard `E = 0` vacuum)
resist_vac = ""
# Hyper-resistivity coefficient `eta_H` (adds `-eta_H grad^4 B` to the field advance)
# @type: float [>= 0.0]
# @default: 0.0
# @note: Damps the few-cell band the moments filter leaves without kinetic
# feedback, while leaving physical scales (`k dx < 0.3`) essentially
# untouched; size so `eta_H (pi/dx)^4` exceeds the drive
# @note: Internally clamped to the explicit `grad^4` stability limit; any value is safe
# @note: `0.0` disables it
resist_hyper = ""
# Pegasus-style sub-cycled magnetic-field advance
# @type: bool
# @default: true
# @note: Integrates the whistler-stiff Ohm/Faraday loop with adaptive SSP-RK3
# sub-steps at its own CFL; `false` uses the legacy 3-push scheme
subcycle = ""
# Target whistler Courant per field sub-step
# @type: float [> 0.0]
# @default: 0.5
# @note: Only read when `subcycle = true`
subcycle_courant = ""
# Cap on the number of field sub-steps per advance
# @type: int [>= 1]
# @default: 64
# @note: Beyond the cap the per-cell Hall limiter (`hall_lim`) takes over
subcycle_max = ""
# Characteristic flow speed for the hybrid CFL, in physical (code) units
# @type: float [>= 0.0]
# @default: 0.0
# @note: Added to the Alfven + whistler signal speeds in the Pegasus CFL
# `dt <= CFL * min(dx0 / v_max, 2*pi/Omega)` [Kunz, Stone & Bai (2014), §3.5]
# @note: `0.0` lets the Alfven + whistler speeds set `dt`; raise it for super-whistler flows
v_max = ""

[radiation]
[radiation.drag]
[radiation.drag.synchrotron]
Expand Down
50 changes: 50 additions & 0 deletions pgens/hybrid/hybrid.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
[simulation]
name = "hybrid"
engine = "hybrid"
runtime = 1.0

[grid]
resolution = [512, 256]
extent = [[0.0, 2.0], [0.0, 1.0]]

[grid.metric]
metric = "minkowski"

[grid.boundaries]
fields = [["PERIODIC"], ["PERIODIC"]]
particles = [["PERIODIC"], ["PERIODIC"]]

[scales]
larmor0 = 1.0
skindepth0 = 1.0

[algorithms]
[algorithms.timestep]
CFL = 0.5

[particles]
ppc0 = 10.0

[[particles.species]]
label = "ions"
mass = 1.0
charge = 1.0
maxnpart = 1e5

[setup]

[output]
interval_time = 0.1

[output.fields]
quantities = ["N_1", "B"]

[output.particles]
species = [1]
stride = 10

[checkpoint]
keep = 0

[diagnostics]
colored_stdout = true
30 changes: 30 additions & 0 deletions pgens/hybrid/pgen.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#ifndef PROBLEM_GENERATOR_H
#define PROBLEM_GENERATOR_H

#include "enums.h"
#include "global.h"

#include "traits/pgen.h"

#include "framework/domain/metadomain.h"
#include "framework/parameters/parameters.h"

namespace user {

template <SimEngine::type S, class M>
struct PGen {
static constexpr auto D { M::Dim };

static constexpr auto engines =
::traits::pgen::compatible_with<SimEngine::HYBRID> {};
static constexpr auto metrics =
::traits::pgen::compatible_with<Metric::Minkowski> {};
static constexpr auto dimensions =
::traits::pgen::compatible_with<Dim::_1D, Dim::_2D, Dim::_3D> {};

PGen(const SimulationParams&, const Metadomain<S, M>&) {}
};

} // namespace user

#endif
2 changes: 1 addition & 1 deletion pgens/shock/pgen.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ namespace user {
domain.fields.em,
init_flds,
domain.mesh.metric });
metadomain.CommunicateFields(domain, Comm::E | Comm::B);
metadomain.CommunicateFields(domain, Comm::EM);

/*
tag particles inside the injection zone as dead
Expand Down
43 changes: 30 additions & 13 deletions pgens/streaming/pgen.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ namespace user {
struct PGen {
static constexpr auto D { M::Dim };
// compatibility traits for the problem generator
static constexpr auto engines = ::traits::pgen::compatible_with<SimEngine::SRPIC> {};
static constexpr auto engines =
::traits::pgen::compatible_with<SimEngine::SRPIC, SimEngine::HYBRID> {};
static constexpr auto metrics =
::traits::pgen::compatible_with<Metric::Minkowski> {};
static constexpr auto dimensions =
Expand Down Expand Up @@ -87,13 +88,13 @@ namespace user {
raise::ErrorIf(nspec % 2 != 0,
"Number of species must be even for this setup",
HERE);
for (auto n = 0u; n < nspec; n += 2) {
raise::ErrorIf(
global_domain.species_params()[n].charge() !=
-global_domain.species_params()[n + 1].charge(),
"Charges of i-th and i+1-th species must be opposite for this setup",
HERE);
}
// for (auto n = 0u; n < nspec; n += 2) {
// raise::ErrorIf(
// global_domain.species_params()[n].charge() !=
// -global_domain.species_params()[n + 1].charge(),
// "Charges of i-th and i+1-th species must be opposite for this
// setup", HERE);
// }
for (auto* specs :
{ &drifts_in_x, &drifts_in_y, &drifts_in_z, &temperatures }) {
if (specs->empty()) {
Expand Down Expand Up @@ -126,13 +127,29 @@ namespace user {
const auto drift_2 = prmvec_t { drifts_in_x[n + 1],
drifts_in_y[n + 1],
drifts_in_z[n + 1] };
arch::InjectUniformMaxwellians<S, M>(
const auto edist1 = arch::energy_dist::MaxwellianNonRel<M::Dim> {
domain.random_pool(),
temperatures[n],
drift_1
};
const auto edist2 = arch::energy_dist::MaxwellianNonRel<M::Dim> {
domain.random_pool(),
temperatures[n + 1],
drift_2
};
arch::InjectUniform<S, M, decltype(edist1), decltype(edist2)>(
params,
domain,
densities[n / 2],
{ temperatures[n], temperatures[n + 1] },
{ n + 1, n + 2 },
{ drift_1, drift_2 });
{ 1u, 2u },
{ edist1, edist2 },
ONE);
// arch::InjectUniformMaxwellians<S, M>(
// params,
// domain,
// densities[n / 2],
// { temperatures[n], temperatures[n + 1] },
// { n + 1, n + 2 },
// { drift_1, drift_2 });
}
}
};
Expand Down
74 changes: 74 additions & 0 deletions src/archetypes/energy_dist.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,38 @@ namespace arch::energy_dist {
random_number_pool_t pool;
};

Inline void NonRelMaxwellian(vec_t<Dim::_3D>& v,
real_t temp,
const random_number_pool_t& pool) {
auto rand_gen = pool.get_state();
// Juttner-Synge distribution using the Box-Muller method - non-relativistic
real_t randX1 = Random<real_t>(rand_gen);
while (cmp::AlmostZero(randX1)) {
randX1 = Random<real_t>(rand_gen);
}
randX1 = math::sqrt(-TWO * math::log(randX1));
real_t randX2 = static_cast<real_t>(constant::TWO_PI) *
Random<real_t>(rand_gen);
v[0] = randX1 * math::cos(randX2) * math::sqrt(temp);

randX1 = Random<real_t>(rand_gen);
while (cmp::AlmostZero(randX1)) {
randX1 = Random<real_t>(rand_gen);
}
randX1 = math::sqrt(-TWO * math::log(randX1));
randX2 = static_cast<real_t>(constant::TWO_PI) * Random<real_t>(rand_gen);
v[1] = randX1 * math::cos(randX2) * math::sqrt(temp);

randX1 = Random<real_t>(rand_gen);
while (cmp::AlmostZero(randX1)) {
randX1 = Random<real_t>(rand_gen);
}
randX1 = math::sqrt(-TWO * math::log(randX1));
randX2 = static_cast<real_t>(constant::TWO_PI) * Random<real_t>(rand_gen);
v[2] = randX1 * math::cos(randX2) * math::sqrt(temp);
pool.free_state(rand_gen);
}

Inline void JuttnerSinge(vec_t<Dim::_3D>& v,
real_t temp,
const random_number_pool_t& pool) {
Expand Down Expand Up @@ -291,6 +323,48 @@ namespace arch::energy_dist {
short drift_dir { 0 };
};

template <Dimension D>
struct MaxwellianNonRel {
MaxwellianNonRel(random_number_pool_t& pool,
real_t temperature,
const std::vector<real_t>& drift_vel = { ZERO, ZERO, ZERO })
: pool { pool }
, temperature { temperature } {
raise::ErrorIf(drift_vel.size() != 3,
"Maxwellian: Drift velocity must be a 3D vector",
HERE);
raise::ErrorIf(temperature < ZERO,
"Maxwellian: Temperature must be non-negative",
HERE);
if (not cmp::AlmostZero_host(NORM(drift_vel[0], drift_vel[1], drift_vel[2]))) {
drift_vel_x1 = drift_vel[0];
drift_vel_x2 = drift_vel[1];
drift_vel_x3 = drift_vel[2];
}
}

Inline void operator()(const coord_t<D>&, vec_t<Dim::_3D>& v) const {

if (cmp::AlmostZero(temperature)) {
v[0] = ZERO;
v[1] = ZERO;
v[2] = ZERO;
} else {
NonRelMaxwellian(v, temperature, pool);
}
v[0] += drift_vel_x1;
v[1] += drift_vel_x2;
v[2] += drift_vel_x3;
}

private:
random_number_pool_t pool;

const real_t temperature;

real_t drift_vel_x1 { ZERO }, drift_vel_x2 { ZERO }, drift_vel_x3 { ZERO };
};

} // namespace arch::energy_dist

#endif // ARCHETYPES_ENERGY_DIST_HPP
Loading
Loading