重构body api,性能分析,项目整理

This commit is contained in:
Frank14f
2026-05-31 01:42:58 +08:00
parent 4758eb3215
commit 2e052480c2
64 changed files with 5196 additions and 1693 deletions
+262 -140
View File
@@ -2,27 +2,77 @@
**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.
CelerisLab is a high-performance computational fluid dynamics solver based on the Lattice Boltzmann Method, leveraging NVIDIA CUDA for GPU acceleration. It provides a Python API for scripting, real-time control loop integration, and scientific workflow automation.
## Features
- **GPU Acceleration**: CUDA-based kernels for high-performance simulations
- **GPU Acceleration**: CUDA kernels for high-performance simulation (384x192 D2Q9: ~4400 MLUPS on V100)
- **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)
- **Curved Boundary Bouzidi**: Immersed boundary support for complex geometries with wall velocity control
- **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
- **Rotating Body Control**: Real-time setting of body rotation speeds via `sim.set_body()`
- **Force / Torque / Sensor Readback**: On-demand force, torque, and area-averaged sensor velocity
- **Physics Validated**: Strouhal numbers match Sah04 (confined cylinder) and Kan99b (rotating cylinder) references
## Quick Start
### Single cylinder
```python
from CelerisLab import Simulation
sim = Simulation()
sim.add_body("circle", center=(50, 50), radius=10)
sim.initialize()
for step in range(10000):
sim.run(1)
macro = sim.get_macroscopic() # {"rho": ..., "ux": ..., "uy": ...}
force = sim.read_force(0) # [fx, fy] on body 0
sim.close()
```
### Multi-body control loop
```python
from CelerisLab import Simulation
sim = Simulation()
# Three rotating cylinders
sim.add_body("circle", center=(1006, 150), radius=10)
sim.add_body("circle", center=(1015, 140), radius=10)
sim.add_body("circle", center=(1015, 160), radius=10)
# Downstream velocity sensor
sim.add_body("sensor", center=(1050, 150), radius=10)
sim.initialize()
for step in range(100):
# Set body rotation speeds (implicit GPU upload)
sim.set_body(0, omega=0.002)
sim.set_body(1, omega=-0.001)
sim.set_body(2, omega=0.001)
# Advance 10 LBM steps
sim.run(10)
# Read telemetry
fx, fy = sim.read_force(0)
ux, uy = sim.read_sensor(3)
print(f"force=({fx:.4f},{fy:.4f}) sensor=({ux:.4f},{uy:.4f})")
sim.close()
```
## Installation
### Prerequisites
- Python 3.8 or higher
- NVIDIA GPU with CUDA Compute Capability 6.0 or higher
- CUDA Toolkit 11.0 or higher
- Python 3.8+
- NVIDIA GPU with CUDA Compute Capability 6.0+
- CUDA Toolkit 11.0+
- NVIDIA drivers
### Install from source
@@ -30,59 +80,115 @@ CelerisLab is a high-performance computational fluid dynamics (CFD) solver based
```bash
git clone <repository_url>
cd CelerisLab
pip install -e . # Installs from src/ directory
pip install -e .
```
### Dependencies
- `pycuda>=2020.1`: CUDA Python bindings
- `numpy>=1.19.0`: Numerical computing
- `scipy>=1.5.0`: Scientific computing (special functions for vortex initialization)
- `pycuda>=2020.1` — CUDA Python bindings
- `numpy>=1.19.0` — numerical computing
- `scipy>=1.5.0` — special functions for vortex initialization
## Quick Start
## API Reference
### Simulation
```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()
sim = Simulation(
lbm_config_path: Optional[str] = None, # path to config JSON
body_config_path: Optional[str] = None, # path to body config JSON
device_id: int = 0, # GPU device index
)
```
Or as a context manager:
#### Body creation
| Method | Returns | Description |
|--------|---------|-------------|
| `sim.add_body(type="circle", center=(x,y), radius=r)` | int body_id | Add a cylinder body |
| `sim.add_body(type="sensor", center=(x,y), radius=r)` | int body_id | Add a velocity sensor |
| `sim.add_cylinder(center, radius)` | int body_id | Backward-compat alias |
| `sim.add_sensor(center, radius)` | int body_id | Backward-compat alias |
| `sim.add_object(obj)` | int body_id | Add pre-configured SimObject |
Future geometry types (polygon, mesh) will use the same `add_body()` function with a different `type` parameter.
#### Runtime control
| Method | Description |
|--------|-------------|
| `sim.initialize()` | Recompile if needed, flow field + sync objects to GPU |
| `sim.run(steps, checkpoint_interval=0)` | Run N LBM steps |
| `sim.set_body(id, omega=...)` | Set body rotation speed (implicit GPU upload, ~1 μs) |
| `sim.read_force(id)` -> ndarray | Force vector [fx, fy] (2D) |
| `sim.read_torque(id)` -> ndarray | Torque [tz] (2D) |
| `sim.read_sensor(id)` -> ndarray | Area-averaged velocity via GPU sensor kernel |
#### Data access
| Method | Description |
|--------|-------------|
| `sim.get_macroscopic()` | Download DDF, return dict with rho/ux/uy |
| `sim.get_ddf()` | Download raw DDF array |
| `sim.get_flags()` | Copy host-side flag array |
| `sim.update_runtime_params(omega=..., fx=..., fy=...)` | Update runtime constants without recompile |
#### Checkpoint / Snapshot
| Method | Description |
|--------|-------------|
| `sim.save_checkpoint(path)` -> str | HDF5 checkpoint with full state |
| `sim.load_checkpoint(path)` | Restore from HDF5 (config must match) |
| `sim.snapshot()` / `sim.restore()` | In-memory field snapshot |
#### Low-level access
| Attribute | Description |
|-----------|-------------|
| `sim.bodies` | ObjectManager for direct GPU buffer access (action_gpu, obs_gpu) |
| `sim.stream` | Internal CUDA stream for async operations |
| `sim.field` | LBMField (GPU memory + curved/sensor SoA handles) |
| `sim.stepper` | LBMStepper for fine-grained step control |
### LBMStepper (advanced usage)
```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()
stepper.step(n=1, *, action_gpu, obs_gpu, stream=None)
```
When fine-grained control is needed (e.g., custom async patterns), step manually:
```python
stream = cuda.Stream()
sim.bodies.zero_force_segment_async(stream)
sim.stepper.step(
1,
action_gpu=sim.bodies.action_gpu,
obs_gpu=sim.bodies.obs_gpu,
stream=stream,
)
stream.synchronize()
force = sim.read_force(0)
```
## Configuration
### Where `config_lbm.json` is loaded from
### Config file location
`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`.
`Simulation()` resolves `config_lbm.json` in this order:
### `config_lbm.json` shape
1. Explicit path argument to `Simulation(path)`
2. `$CELERISLAB_CONFIG_DIR/config_lbm.json`
3. `./configs/config_lbm.json` (current working directory)
4. The copy shipped inside the installed package
The on-disk schema matches `src/CelerisLab/configs/config_lbm.json` (nested sections). Example fragment:
### Config structure
```json
{
"grid": {
"lattice_model": "D2Q9",
"nx": 512,
"ny": 256,
"nz": 1
"nx": 512, "ny": 256, "nz": 1
},
"physics": {
"data_type": "FP32",
@@ -97,14 +203,20 @@ The on-disk schema matches `src/CelerisLab/configs/config_lbm.json` (nested sect
"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 },
"inlet": {
"profile": "parabolic",
"scheme": "zou_he_local",
"trt_neq_damp": 0.5,
"regularized_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 }
"y_wall_bc": "bounce_back",
"omega_guard": { "min": 0.01, "max": 1.99 }
},
"cuda": {
"threads_per_block": 256,
@@ -113,139 +225,149 @@ The on-disk schema matches `src/CelerisLab/configs/config_lbm.json` (nested sect
}
```
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.
Full parameter documentation lives in `src/CelerisLab/configs/CONFIG.md`.
### Parameter tiers
## Performance
| 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) |
### Benchmarks (V100, D2Q9, 384x192)
Headers are auto-generated by `cuda/compiler_v2.py` from `LBMConfig`; do not edit manually.
| Config | MLUPS |
|--------|-------|
| Re100 MRT noLES | ~4400 |
## API Reference
### Performance characteristics
### `Simulation`
The GPU is the primary runtime cost. Python overhead is minimal.
```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()
**384x192 (validation grid):** GPU kernel time is ~78 μs/step, of which OneStep is ~5.9 μs (MRT D2Q9). The remaining time is dominated by pycuda kernel launch overhead (~37 μs per launch).
**3000x300 (production grid):** Estimated GPU compute time is ~530 μs/step, with pycuda overhead fixed at ~111 μs, yielding ~83% GPU utilization.
`sim.set_body()` and `sim.read_force()` data transfers are negligible (~1 μs for 72 bytes).
For a detailed breakdown, see [docs/performance_analysis.md](docs/performance_analysis.md).
## Body Module Architecture
```
body/
__init__.py Package exports
objects.py SimObject container + ObjectState / ObjectControl
manager.py ObjectManager: GPU buffer lifecycle, sync, telemetry
registry.py BodyRegistry: pure add/remove/query
action_smoother.py ActionSmoother for control input ramping
geometry/ Shape implementations (CircleGeometry, Geometry ABC)
coupling/ Body-fluid coupling: SoA packing, force/torque
preprocess/ Grid preprocessing: flag overlay, cut-link building
```
### `LBMStepper` (advanced)
## Module Boundaries
```python
stepper.step(n=1, *, action_gpu, obs_gpu, stream=None)
- `body/` — geometry, rigid-body state, preprocessing, force/torque readback
- `lbm/` — lattice Boltzmann kernels, field memory, stepper
- `cuda/` — compilation pipeline, context lifecycle
- `common/` — shared utilities (checkpoint, render, streakline pathline)
Geometry is **separated from boundary methods**. CircleGeometry produces geometry-agnostic CutLink records. The SoA packer (`body/coupling/soa_packer.py`) is the single point that knows the kernel memory layout. Adding a new shape (polygon, mesh) requires only a new `Geometry` subclass.
## Validated Benchmarks
| Benchmark | Description | Key metrics | Precision |
|-----------|-------------|-------------|-----------|
| Sah04 S1-S4 | Confined stationary cylinder | Strouhal matching Sahin & Owens (2004) | St error < 5% |
| Kan99b K0-K5 | Rotating cylinder in open domain | St, Cd, Cl matching Kang et al. (1999) | See tolerance table |
| Sensor accuracy | GPU sensor vs CPU flow-field average | Match to 1e-9 | Verified |
Run validation scripts:
```bash
conda run -n pycuda_3_10 python tests/validation/run_kan99b_rotating_cylinder.py
conda run -n pycuda_3_10 python tests/validation/run_sah04_st_matrix.py
conda run -n pycuda_3_10 python tests/validation/test_sensor_accuracy.py
```
Curved BC / sensor lists live on `field.curved` and `field.sensors` (`CurvedLinkSoA` / `SensorSoA`), filled by `ObjectManager.sync_to_gpu(field)`.
## Performance baseline
### 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")
```bash
conda run -n pycuda_3_10 python tests/validation/run_perf_baseline.py \
--lattice-model D2Q9 --nx 384 --ny 192 --collision MRT
```
## 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)
body/ Object management, geometry, GPU sync
cuda/ CUDA context, compilation, PTX load
lbm/ Field, stepper, kernels (CUDA source)
common/ Preprocess, checkpoint, render, streakline
tests/
validation/ Regression runners (Kan99b, Sah04, sensor, perf)
postproc/ Post-processing scripts (exp_ctrl_matrix, streakline)
specs/ Validation spec documents
audit/ Audit reports (archived, see docs/)
output/ Test outputs (force CSV, vorticity PNG, checkpoints)
docs/
performance_analysis.md GPU/Python profiling report
audit/ Audit findings (round 1-2, kernel layer, body refactor notes)
validation_specs/ Validation methodology documents
legacy/ Superseded code (FlowField, compiler v1, macros.h)
ref/ External reference implementations (FluidX3D)
```
## Performance
## Collision model recommendations
Tested on Tesla V100-SXM2-16GB (CUDA 12.4):
| 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 `omega_guard.max` in 1.90-1.99 |
| Config | Grid | MLUPS |
|---|---|---|
| Re100 MRT noLES | 384×192 | ~4200 |
| Re100 EsoPull SRT | 384×192 | ~3900 |
| Re3000 MRT+LES | 384×192 | ~4360 |
## Common control loop patterns
### Performance methodology
### Sync control (simple)
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
```python
sim.set_body(0, omega=0.002)
sim.run(10)
force = sim.read_force(0)
```
This path times GPU stepping (`stepper.step`) and reports MLUPS and batch latency
percentiles. By default it avoids host readbacks inside the timed loop.
### Async control (performance-oriented)
APIs that trigger device-to-host transfers (DTOH) and can reduce MLUPS:
```python
sim.set_body(0, omega=0.002) # implicit H2D, ~1 μs
sim.stepper.step(10, ..., stream=sim.stream)
sim.bodies.download_obs_full_async(sim.stream)
sim.stream.synchronize()
force = sim.read_force(0)
```
- `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(...)`)
## Vortex initialization
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.
```python
from CelerisLab.lbm.initializers import add_vortex
add_vortex(sim.field, center=(50, 50), radius=10.0, strength=1.0, vortex_type="lamb")
```
## Streakline visualization
```python
from CelerisLab.common.streakline import Streakline, ReleaseConfig, IntegratorConfig
streak = Streakline(release_points=..., nx=nx, ny=ny)
for step in range(steps):
sim.run(1)
if step % sample_every == 0:
macro = sim.get_macroscopic()
streak.observe(ux=macro["ux"], uy=macro["uy"], step=step)
streak.render("streakline.png")
```
## Citation
If you use CelerisLab in your research, please cite:
```bibtex
@software{celerislab2026,
author = {Frank14f},