# 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 <repository_url>
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", "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 |

## 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.
