# CelerisLab **GPU-Accelerated Lattice Boltzmann Method (LBM) CFD Solver** CelerisLab is a high-performance computational fluid dynamics (CFD) solver based on the Lattice Boltzmann Method, leveraging NVIDIA CUDA for GPU acceleration. It provides a Python interface for easy integration into scientific workflows while maintaining high computational efficiency through CUDA kernels. ## Features - **GPU Acceleration**: CUDA-based kernels for high-performance simulations - **D2Q9 / D3Q19 Lattice**: 2D and 3D lattice implementations - **Multiple Collision Models**: SRT, TRT, and MRT operators; Smagorinsky LES subgrid model - **Dual Streaming Paths**: Standard double-buffer pull and memory-efficient esoteric-pull (EsoPull) - **Immersed Boundary Method (IBM)**: Support for complex geometries (cylinders, arbitrary shapes) - **Flexible Boundary Conditions**: NEQ-extrapolation pressure outlet, parabolic/uniform velocity inlet, half-way bounce-back walls - **Layered Configuration**: Compile-time parameters organized into Global / Method / Case / Debug tiers - **High-Re Validated**: Tested up to Re=5000 (2D cylinder); MRT+LES and SRT+LES stable; TRT+LES stable with tuned Lambda and WMAX - **Python API**: High-level `Simulation` class for scripting and RL integration ## Installation ### Prerequisites - Python 3.8 or higher - NVIDIA GPU with CUDA Compute Capability 6.0 or higher - CUDA Toolkit 11.0 or higher - NVIDIA drivers ### Install from source ```bash git clone cd CelerisLab pip install -e . # Installs from src/ directory ``` ### Dependencies - `pycuda>=2020.1`: CUDA Python bindings - `numpy>=1.19.0`: Numerical computing - `scipy>=1.5.0`: Scientific computing (special functions for vortex initialization) ## Quick Start ```python from CelerisLab import Simulation # Path is optional; see Configuration → paths. Example passes the usual relative name. sim = Simulation("configs/config_lbm.json") sim.add_cylinder(center=(50, 50), radius=10) sim.initialize() for step in range(10000): sim.run(1) macro = sim.get_macroscopic() # {"rho": ..., "ux": ..., "uy": ...} sim.close() ``` Or as a context manager: ```python with Simulation("configs/config_lbm.json") as sim: sim.add_cylinder(center=(96, 64), radius=12) sim.initialize() sim.run(5000) data = sim.get_macroscopic() ``` ## Configuration ### Where `config_lbm.json` is loaded from `load_lbm_config()` resolves `config_lbm.json` in this order: an explicit path argument to `Simulation(...)` / `load_lbm_config(path)`, then `$CELERISLAB_CONFIG_DIR/config_lbm.json`, then `./configs/config_lbm.json` under the current working directory, then the copy shipped inside the installed package at `CelerisLab/configs/config_lbm.json`. In a source checkout the same file lives at `src/CelerisLab/configs/config_lbm.json`. There is **no** top-level `configs/` directory at the repository root; from the clone root you can omit the path (`Simulation()`), set `CELERISLAB_CONFIG_DIR`, or create your own `./configs/config_lbm.json`. ### `config_lbm.json` shape The on-disk schema matches `src/CelerisLab/configs/config_lbm.json` (nested sections). Example fragment: ```json { "grid": { "lattice_model": "D2Q9", "nx": 512, "ny": 256, "nz": 1 }, "physics": { "data_type": "FP32", "viscosity": 0.0035, "velocity": 0.03, "rho": 1.0 }, "method": { "collision": "SRT", "streaming": "double_buffer", "store_precision": "FP32", "ddf_shifting": false, "les": { "enabled": false, "cs": 0.16, "closed_form": true }, "trt": { "magic_param": 0.1875 }, "inlet": { "profile": "parabolic", "scheme": "zou_he_local", "trt_neq_damp": 0.5 }, "outlet": { "mode": "neq_extrap", "backflow_clamp": true, "blend_alpha": 0.7, "srt_neq_damp": 0.5 }, "omega_guard": { "min": 0.01, "max": 1.96 } }, "cuda": { "threads_per_block": 256, "compute_capability": "auto" } } ``` Lattice size and model come from `grid`; viscosity and scales from `physics`; collision, LES, boundaries, and ω clamps from `method` (ω upper bound is `method.omega_guard.max`, not a top-level `omega_max`). For high-Re runs, keep `method.omega_guard.max` in the `1.90-1.96` window. See `src/CelerisLab/configs/CONFIG.md` for the full parameter tables. ### Parameter tiers | Tier | Headers | Examples | |---|---|---| | Global/Grid | `config_grid.h` | `NX`, `NY`, `NZ`, `LATTICE_MODEL`; `DIM` / `NQ` are **derived** from `LATTICE_MODEL` when `cuda/compiler_v2.py` emits headers (they are not separate keys in JSON) | | Global/Physics | `config_physics.h` | VIS, RHO, U0, flag constants | | Method | `config_method.h` | COLLISION_MODEL, USE_LES, TRT_MAGIC_PARAM, OMEGA_COLLISION_MAX | | Case | `config_objects.h`, `config_obs.h` | `N_OBJS`; packed obs macros `OBS_*` from `generate_config(cfg, n_objects=K)` (`max(N_OBJS,1)` × `DIM` per segment; no extra JSON keys) | Headers are auto-generated by `cuda/compiler_v2.py` from `LBMConfig`; do not edit manually. ## API Reference ### `Simulation` ```python sim = Simulation(lbm_config_path=None, body_config_path=None, device_id=0) sim.add_cylinder(center, radius) -> int sim.add_sensor(center, radius) -> int sim.initialize() # recompiles with N_OBJS when bodies were added sim.run(steps, checkpoint_interval=0) # wires bodies.action_gpu / bodies.obs_gpu sim.step(n=1) sim.bodies # ObjectManager: packed buffers + zero_force_segment_async, ... sim.get_macroscopic() -> {"rho": ndarray, "ux": ndarray, "uy": ndarray} sim.get_ddf() -> ndarray sim.get_flags() -> ndarray sim.update_runtime_params(omega=..., fx=..., fy=...) sim.snapshot() / sim.restore() sim.save_checkpoint(path=None) -> str # HDF5; default path if omitted sim.load_checkpoint(path) # restores field, step count, bodies sim.close() ``` ### `LBMStepper` (advanced) ```python stepper.step(n=1, *, action_gpu, obs_gpu, stream=None) ``` Curved BC / sensor lists live on `field.curved` and `field.sensors` (`CurvedLinkSoA` / `SensorSoA`), filled by `ObjectManager.sync_to_gpu(field)`. ### Vortex initialization ```python from CelerisLab.lbm.initializers import add_vortex # Superimpose a Lamb–Oseen vortex on an existing LBMField add_vortex(sim.field, center=(50, 50), radius=10.0, strength=1.0, vortex_type="lamb") ``` ## Collision & LES Recommendations | Use case | Recommended config | |---|---| | Low Re (≤ 500) | SRT or TRT, LES off | | Medium Re (500–2000) | MRT or SRT+LES | | High Re (2000–5000) | MRT+LES (most robust); SRT+LES; TRT+LES with `method.omega_guard.max` in `1.90-1.96` (default `1.96`) and tuned `method.trt.magic_param` (default `0.1875`) | ## Project Layout ``` src/CelerisLab/ simulation.py High-level API config.py LBMConfig / BodyConfig dataclasses cuda/ compiler_v2.py Config header generation + nvcc + PTX load context.py CUDA context lifecycle lbm/ field.py GPU memory + ``curved`` / ``sensors`` SoA handles curved_links.py CurvedLinkSoA / SensorSoA stepper.py Time-step driver (``action_gpu``, ``obs_gpu``) initializers.py Vortex superposition kernels/ kernel_v2.cu Kernel entry (thin wrapper) config/ Auto-generated headers (``config_grid.h``, …, ``config_obs.h``) core/ Descriptors, layout, flags, params operators/ Collision, LES, forcing boundary/ Inlet, outlet, wall, curved, IBM streaming/ Double-buffer & esopull step/ Step orchestration body/ objects.py SimObject / Cylinder / Sensor manager.py ObjectManager; packed ``obs_gpu`` / ``obs_pinned``, B3 helpers common/ preprocess.py Geometry utilities output/ CelerisLab_stage1_architecture.md Architecture specification (v3) refactor_brief_stage1.md Refactoring brief high_re_audit_round1.md 8-round audit log legacy/ Superseded code (FlowField, compiler v1, macros.h) ``` ## Performance Tested on Tesla V100-SXM2-16GB (CUDA 12.4): | Config | Grid | MLUPS | |---|---|---| | Re100 MRT noLES | 384×192 | ~4200 | | Re100 EsoPull SRT | 384×192 | ~3900 | | Re3000 MRT+LES | 384×192 | ~4360 | ### Performance methodology For a "kernel-dominant" baseline (closest to FluidX3D-style throughput testing), use the dedicated script: ```bash conda run -n pycuda_3_10 python tests/run_perf_baseline.py \ --lattice-model D2Q9 --nx 384 --ny 192 --nz 1 \ --steps 4000 --warmup-steps 400 --batch-steps 100 ``` This path times GPU stepping (`stepper.step`) and reports MLUPS and batch latency percentiles. By default it avoids host readbacks inside the timed loop. APIs that trigger device-to-host transfers (DTOH) and can reduce MLUPS: - `Simulation.get_macroscopic()` / `LBMField.get_macroscopic()` (downloads full DDF) - `Simulation.get_ddf()` / `LBMField.download_ddf()` - `Simulation.save_checkpoint()` (downloads field/state buffers) - Body observation downloads (e.g. `ObjectManager.download_obs_full_async(...)`) Use `tests/run_perf_baseline.py` switches (`--macro-every`, `--ddf-every`, `--checkpoint-every`, `--obs-every`) to quantify each overhead path against the pure-step baseline. ## Citation If you use CelerisLab in your research, please cite: ```bibtex @software{celerislab2026, author = {Frank14f}, title = {CelerisLab: GPU-Accelerated Lattice Boltzmann Method Solver}, year = {2026}, url = {https://github.com/frank14f/CelerisLab} } ``` ## License MIT License — see LICENSE file for details.