-
Notifications
You must be signed in to change notification settings - Fork 1
Architecture
This page describes the internal architecture of the CFD library.
cfd/
├── lib/
│ ├── include/cfd/ # Public header files
│ │ ├── api/
│ │ │ └── simulation_api.h # High-level simulation API
│ │ ├── core/
│ │ │ ├── grid.h # Grid data structures
│ │ │ ├── cfd_status.h # Status/error codes
│ │ │ ├── cfd_init.h # Library init/finalize
│ │ │ ├── cfd_version.h # Version constants
│ │ │ ├── derived_fields.h # Computed field quantities
│ │ │ ├── logging.h # Logging utilities
│ │ │ ├── cpu_features.h # Runtime CPU feature detection
│ │ │ ├── filesystem.h # Filesystem helpers
│ │ │ ├── gpu_device.h # GPU device query
│ │ │ └── memory.h # Memory allocation helpers
│ │ ├── solvers/
│ │ │ ├── navier_stokes_solver.h # NS solver interface and types
│ │ │ └── poisson_solver.h # Poisson solver interface
│ │ ├── boundary/
│ │ │ └── boundary_conditions.h # Boundary condition API
│ │ └── io/
│ │ ├── vtk_output.h # VTK file generation
│ │ ├── csv_output.h # CSV file generation
│ │ └── output_registry.h # Output registration API
│ └── src/
│ ├── api/ # API implementations
│ │ ├── simulation_api.c
│ │ └── solver_registry.c
│ ├── core/ # Core functionality
│ │ ├── grid.c
│ │ ├── flow_field.c
│ │ └── derived_fields.c
│ ├── io/ # Input/Output
│ │ ├── vtk_output.c
│ │ ├── csv_output.c
│ │ └── output_registry.c
│ ├── boundary/ # Boundary conditions
│ │ ├── boundary_conditions.c # Dispatcher (technology-agnostic)
│ │ ├── cpu/
│ │ ├── simd/
│ │ ├── omp/
│ │ └── gpu/
│ └── solvers/ # Solver implementations
│ ├── navier_stokes/
│ │ ├── cpu/
│ │ ├── avx2/
│ │ ├── omp/
│ │ └── gpu/
│ └── linear/
│ ├── cpu/
│ ├── avx2/
│ ├── neon/
│ └── omp/
├── examples/ # 19 example programs
└── tests/ # Unit tests
├── core/
├── math/
├── validation/
├── solvers/
├── simulation/
└── io/
The simulation_api.h provides the high-level interface:
simulation_data
├── grid* # Computational mesh (2D or 3D)
├── flow_field* # Solution variables (u, v, w, p, rho, T)
├── ns_solver_params_t # Configuration (dt, CFL, viscosity, etc.)
├── ns_solver_t* # Active solver instance
├── ns_solver_registry_t* # Solver registry
├── ns_solver_stats_t # Statistics from last step
├── output_registry_t* # Registered outputs
├── run_prefix # Output directory prefix
└── current_time # Accumulated simulation time
The grid structure holds mesh information for both 2D and 3D:
typedef struct {
double *x, *y, *z; // Cell-center coordinates
double *dx, *dy, *dz; // Cell spacing
double *inv_dx2, *inv_dy2, *inv_dz2; // Precomputed 1/dx^2
size_t nx, ny, nz; // Grid dimensions (nz=1 for 2D)
size_t stride_z; // nx*ny for 3D, 0 for 2D
double xmin, xmax, ymin, ymax, zmin, zmax; // Domain bounds
int i_start, i_end; // Interior x-range
int j_start, j_end; // Interior y-range
int k_start, k_end; // Interior z-range
} grid;Solution variables are stored in flow_field:
typedef struct {
double* u; // x-velocity
double* v; // y-velocity
double* w; // z-velocity (allocated but zero for 2D)
double* p; // pressure
double* rho; // density
double* T; // temperature
size_t nx, ny, nz; // Dimensions (nz=1 for 2D)
} flow_field;The solver system uses a vtable (function-pointer) architecture:
ns_solver_t
├── name, description, version
├── capabilities (bitflags)
├── backend (SCALAR / SIMD / OMP / CUDA)
├── context (opaque solver state)
└── Function pointers:
├── init()
├── destroy()
├── step()
├── solve()
├── apply_boundary()
└── compute_dt()
The output system uses a registry pattern:
output_registry_t
└── entries[]
├── field_type (output_field_type enum)
├── interval (steps between outputs)
└── prefix (filename prefix)
Solvers are created through a named registry:
// Registration (done internally by the library)
ns_solver_registry_t* registry = cfd_registry_create();
cfd_registry_register_defaults(registry);
// Create a solver instance by name
ns_solver_t* solver = cfd_solver_create(registry, "explicit_euler");All solvers implement the same interface via function pointers:
solver_init(solver, grid, params);
solver_step(solver, field, grid, params, stats);
solver_destroy(solver);Outputs are registered once and processed automatically:
simulation_register_output(sim, OUTPUT_VELOCITY, 10, "vel");
simulation_register_output(sim, OUTPUT_CSV_TIMESERIES, 1, "ts");
// Writes all registered outputs at their intervals
simulation_write_outputs(sim, step);User Code
│
▼
Simulation API (simulation_api.c)
│
├──► Output Registry (output_registry.c)
│ │
│ ├──► VTK Output (vtk_output.c)
│ └──► CSV Output (csv_output.c)
│
└──► Solver Registry (solver_registry.c)
│
└──► Solver Implementations
├── CPU (navier_stokes/cpu/)
├── AVX2 (navier_stokes/avx2/)
├── OpenMP (navier_stokes/omp/)
└── GPU (navier_stokes/gpu/)
Arrays use row-major ordering with a flat index:
2D indexing:
index = j * nx + i (IDX_2D macro)
Where:
i = column index (0 to nx-1)
j = row index (0 to ny-1)
3D indexing:
index = k * stride_z + j * nx + i (IDX_3D macro)
Where:
stride_z = nx * ny
i = x-index (0 to nx-1)
j = y-index (0 to ny-1)
k = z-index (0 to nz-1)
Eleven solvers are registered. Each algorithm is available in multiple backends.
Basic forward-Euler time stepping:
- Compute convective fluxes
- Add diffusive terms
- Apply source terms
- Update solution: u^(n+1) = u^n + dt * RHS
Fractional-step method for incompressible flow:
- Compute intermediate velocity (without pressure)
- Solve pressure Poisson equation (using CG solver)
- Project velocity to divergence-free field
Second-order Runge-Kutta time integration:
- Stage 1: forward-Euler predictor
- Stage 2: evaluate RHS at predicted state
- Combine with equal weights for O(dt^2) accuracy
Uses periodic stencil indexing in RHS evaluation; BCs applied only after the full step.
AVX2 intrinsics process 4 doubles simultaneously using 256-bit registers. Scalar tail loop handles remainder elements. Runtime CPU feature detection selects SIMD vs. scalar paths.
CUDA implementations with data transferred CPU → GPU at init, GPU → CPU for output. Returns CFD_ERROR_UNSUPPORTED if CUDA is unavailable.
- Create solver source file:
// lib/src/solvers/navier_stokes/cpu/solver_mysolver.c
#include "cfd/solvers/navier_stokes_solver.h"
static cfd_status_t mysolver_init(ns_solver_t* s, const grid* g,
const ns_solver_params_t* p) {
// Allocate context, initialize buffers
return CFD_SUCCESS;
}
static cfd_status_t mysolver_step(ns_solver_t* s, flow_field* f,
const grid* g,
const ns_solver_params_t* p,
ns_solver_stats_t* stats) {
// Implement time stepping
return CFD_SUCCESS;
}
ns_solver_t* create_mysolver(void) {
ns_solver_t* s = calloc(1, sizeof(ns_solver_t));
s->name = "mysolver";
s->init = mysolver_init;
s->step = mysolver_step;
s->destroy = mysolver_destroy;
return s;
}- Register in
lib/src/api/solver_registry.c:
cfd_registry_register(registry, "mysolver", create_mysolver);- Add enum value to
output_field_typeinlib/include/cfd/api/simulation_api.h - Implement writer in
lib/src/io/vtk_output.corlib/src/io/csv_output.c - Handle new type in
lib/src/io/output_registry.c
CMake-based build with options:
| Option | Default | Description |
|---|---|---|
CFD_ENABLE_CUDA |
OFF | Enable GPU solvers |
CAVITY_FULL_VALIDATION |
OFF | Expensive 129x129 cavity benchmark |
ENABLE_ASAN |
OFF | Address sanitizer (GCC/Clang) |
ENABLE_UBSAN |
OFF | Undefined-behavior sanitizer (GCC/Clang) |
ENABLE_COVERAGE |
OFF | Code coverage (GCC/Clang) |
| Library | CMake Target | Contents |
|---|---|---|
cfd_core |
CFD::Core |
Grid, memory, I/O, status, utilities |
cfd_scalar |
CFD::Scalar |
Scalar CPU solvers |
cfd_simd |
CFD::SIMD |
AVX2/NEON SIMD solvers |
cfd_omp |
CFD::OMP |
OpenMP parallelized solvers |
cfd_cuda |
CFD::CUDA |
CUDA GPU solvers |
cfd_library |
CFD::Library |
Unified library (all backends) |
- Required: C99 compiler, CMake 3.10+
-
Optional:
- CUDA Toolkit (for GPU solvers)
- OpenMP (for parallel solvers, typically bundled with compiler)
- Unity (for tests, fetched via CMake FetchContent)
-
simulation_datainstances are NOT thread-safe across threads - Each thread must use its own
simulation_data - OpenMP solvers handle internal parallelization
- GPU solvers manage CUDA synchronization internally
- Function-static variables are prohibited; use context structs or
_Thread_local
- Solver Guide - Solver usage
- Output System - Output configuration
- Examples - Code examples