Simulation Engine

The runners turn a flat parameter dictionary into a complete solver, run it, and package the result. There are two: run_direct_simulation for the PDE itself and run_kinetic_simulation for its kinetic relaxation approximation.

Pipeline

Both runners follow the same steps, sharing one context dictionary (see Builder API; the numerical builders are HyperCloudCore's config-driven builders):

  1. Split the parameters into namespaces with extract_namespace (PDE, Grid, Weight, Flux, Limiter, MOOD, Scheme, IC, Time, Adapt, and Kinetic for the kinetic runner).
  2. Build the equation. It determines the dimension D and system size M.
  3. Build the geometry and particle grid, including the MLS weight function.
  4. Build the spatial discretization: flux, limiter, MOOD and scheme.
  5. Build the time stepper, HyperCloudCore's GeneralRKTimeStepper, from the Butcher tableau (explicit RK or IMEX), and optionally the particle adaptivity (Adapt namespace).
  6. Apply the initial condition.
  7. Integrate with solve_equation up to tmax, using either a fixed dt or a CFL number.
  8. Package the snapshots into an AbstractSimData and add the statistics Runtime (wall-clock seconds), k_step (number of steps) and final_particle_count, plus reserved_particles and reserved_bytes with a memory limit.

The simulation precision is params[:T] (default Float64).

The returned object holds one entry per snapshot: sim.x[k] are the positions, sim.u[k] the states and sim.t[k] the time of snapshot k.

Errors are caught

Both runners catch every exception, log it with @error (including the backtrace), and return nothing. Always check the result, e.g. @assert sim !== nothing, when calling them directly.

Running on a GPU

The runners place a run where HyperCloudCore's execution backend runs (storage_for; see Execution backends). The CPU backends (PolyesterBackend(), the default, ThreadsBackend() and KernelAbstractions.CPU()) keep the run in host memory. With a GPU backend, the grid, the scheme and the time stepper live on the device (context[:Storage]). No parameter changes, so a GPU run has the same parameter hash as its CPU counterpart:

using HyperCloud, AMDGPU          # or CUDA / Metal / oneAPI
set_backend!(ROCBackend())
sim = run_direct_simulation(params)
set_backend!(PolyesterBackend())  # back to the CPU

with_backend(() -> run_direct_simulation(params), ROCBackend())   # the same, scoped
  • The grid is constructed on the host and then moved to the device; the time steps (neighbour search, MOOD, boundary conditions, CFL estimate) run there.
  • Initial conditions are evaluated on the host and copied over once; each snapshot copies the active particles back. The returned sim therefore holds plain host arrays.
  • Particle adaptivity works; a step that actually inserts or merges particles copies the grid to the host and back.
  • All schemes, including :WENO, and all grid movers run on the device (since HyperCloudCore 0.6.1). What still runs on the host is listed under Not yet on the device.
  • Custom equations, boundary conditions and source terms run inside the time step and must follow the rules for kernel bodies (bits types, no allocation, no dynamic dispatch). Initial conditions only run on the host.

Memory limit

By default the buffers of a run grow on demand; growing a buffer briefly holds the old and the new array at once, which is where a run on a nearly full device fails. set_memory_limit!(bytes) instead sizes every buffer of the following runs once, to the largest particle capacity under the limit (HyperCloudCore's reserve!), and a run that outgrows it stops with a CapacityError:

set_memory_limit!(12 * 2^30; slots_per_particle = 40)   # 12 GiB; room for neighbourhoods to grow
sim = run_direct_simulation(params)                       # stats: reserved_particles, reserved_bytes
set_memory_limit!(nothing)

Like the backend it is a session setting, not a parameter, so it does not change the run hash. slots_per_particle defaults to what the initial neighbour lists need; raise it for moving or adaptive grids.

Device reservations (AMDGPU 2.9.1, ROCm 7.2)

A reservation on a device can zero grid buffers when earlier device runs happened in the same Julia session (see HyperCloudCore's memory budget notes). The runners compare the grid before and after the reservation and refuse to run when it changed. Until this is fixed upstream, start reserved GPU runs in a fresh session.

Direct simulation

run_direct_simulation evolves the PDE given in the PDE namespace.

  • No source terms are attached.
  • The initial condition is evaluated at every particle.
  • The stored states are the variables the equation evolves (conservative variables for Euler).

Kinetic simulation

run_kinetic_simulation replaces the macroscopic system with $M$ variables by a linear kinetic system with $N_K$ variables plus a stiff relaxation term (see Kinetic Relaxation).

  • Two equations. The macroscopic equation is built first; build_kinetic_system then builds the kinetic LinearAdvection system and the RelaxationSourceTerm from the Kinetic namespace.
  • Kinetic context. The context is switched to the kinetic equation and M = N_K, so the same schemes run on the kinetic variables. The relaxation term is registered as an implicit source, which requires an IMEX stepper (:IMEXEuler, :ARS222, :ARS233, :PRSSP3, :SSP332).
  • Initialization. The macroscopic initial condition is mapped to the kinetic equilibrium (Maxwellian) at every particle.
  • Output. Snapshots are summed back to the macroscopic variables with the source term's Kin2Macro map. Set :save_relax => true to keep the raw kinetic variables instead.

API

HyperCloud.run_direct_simulation — Function
run_direct_simulation(params::ParamDict) -> Union{AbstractSimData, Nothing}

Orchestrates the standard execution pipeline for explicit macroscopic PDEs. Extracts configuration namespaces, builds the mathematical equation system, continuous domain, and particle grid, and instantiates the spatial numerical schemes. Applies the initial condition directly to the particle grid and executes the main solver loop, packaging the raw conservative state vectors into an AbstractSimData object.

source
HyperCloud.run_kinetic_simulation — Function
run_kinetic_simulation(params::ParamDict) -> Union{AbstractSimData, Nothing}

Manages the execution pipeline for highly coupled kinetic relaxation systems. Builds a two-tier equation architecture by first constructing the base macroscopic equation, then constructing the kinetic system and its associated RelaxationSourceTerm. Dynamically overwrites the execution context to operate in the higher NK-dimensional space during integration. Automatically collapses the NK-dimensional kinetic data back into the macroscopic state before returning the output, unless save_relax is set to true.

source
HyperCloud.extract_namespace — Function
extract_namespace(params::Dict, prefix::Symbol; separator::String="_")

Extracts all keys from a dictionary that start with the given prefix followed by the separator (e.g., PDE_name). Leaves the values exactly as they are (preserving tuples for hashing) and returns a new dictionary with the stripped keys.

source
HyperCloud.storage_for — Function
storage_for(backend = get_backend()) -> Type

The array type a run lives on for backend: Array for the CPU backends (PolyesterBackend, ThreadsBackend, KABackend(CPU())), the device array type of a GPU KABackend (e.g. ROCArray).

source
HyperCloud.with_backend — Function
with_backend(f, backend)

Calls f() with backend as the execution backend and restores the previous backend afterwards.

source
HyperCloud.set_memory_limit! — Function
set_memory_limit!(limit::Integer; tolerance = 0.05, slots_per_particle = nothing)
set_memory_limit!(nothing)

Reserves the buffers of every following run under limit bytes (HyperCloudCore's reserve!): tolerance is the share of the limit kept free for the runtime, slots_per_particle the neighbour slots reserved per particle (default: what the initial neighbour lists need; raise it for moving or adaptive grids). nothing removes the limit (buffers grow on demand, the default).

source