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):
- Split the parameters into namespaces with
extract_namespace(PDE,Grid,Weight,Flux,Limiter,MOOD,Scheme,IC,Time,Adapt, andKineticfor the kinetic runner). - Build the equation. It determines the dimension
Dand system sizeM. - Build the geometry and particle grid, including the MLS weight function.
- Build the spatial discretization: flux, limiter, MOOD and scheme.
- Build the time stepper, HyperCloudCore's
GeneralRKTimeStepper, from the Butcher tableau (explicit RK or IMEX), and optionally the particle adaptivity (Adaptnamespace). - Apply the initial condition.
- Integrate with
solve_equationup totmax, using either a fixeddtor a CFL number. - Package the snapshots into an
AbstractSimDataand add the statisticsRuntime(wall-clock seconds),k_step(number of steps) andfinal_particle_count, plusreserved_particlesandreserved_byteswith 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.
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
simtherefore 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.
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_systemthen builds the kineticLinearAdvectionsystem and theRelaxationSourceTermfrom theKineticnamespace. - 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
Kin2Macromap. Set:save_relax => trueto 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.
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.
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.
HyperCloud.storage_for — Function
storage_for(backend = get_backend()) -> TypeThe 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).
HyperCloud.with_backend — Function
with_backend(f, backend)Calls f() with backend as the execution backend and restores the previous backend afterwards.
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).
HyperCloud.get_memory_limit — Function
The memory limit set with set_memory_limit! as (; limit, tolerance, slots_per_particle), or nothing.