Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 21 additions & 17 deletions MIGRATION_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,9 +326,10 @@ cpu_features_t cfd_get_cpu_features(void);
- Automatic CUDA library detection

- [x] **2.5.3 Update CI test infrastructure**
- Install CUDA runtime (12.0.0) for CUDA wheel tests
- Install CUDA runtime (12.4.0) for CUDA wheel tests
- Use standard `pip` instead of `uv` for stable ABI wheel installation
- Test matrix: Python 3.9 and 3.13 on all platforms
- Use apt-based CUDA installation on Linux (more reliable than runfile)

- [x] **2.5.4 Ensure PEP 427 compliance**
- Standard wheel filenames (no variant suffixes)
Expand Down Expand Up @@ -383,30 +384,33 @@ cpu_features_t cfd_get_cpu_features(void);

**Estimated effort:** 1 day

### Phase 5: Add Backend Availability API (v0.1.6 Feature)
### Phase 5: Add Backend Availability API (v0.1.6 Feature) ✅ COMPLETED

**Priority:** P1 - Important for v0.1.6 compatibility

**Status:** Completed on 2025-12-31

**Tasks:**

- [ ] **5.1 Expose backend enum**
- `BACKEND_SCALAR`, `BACKEND_SIMD`, `BACKEND_OMP`, `BACKEND_CUDA` constants
- Map to `ns_solver_backend_t` enum
- [x] **5.1 Expose backend enum**
- Added `BACKEND_SCALAR`, `BACKEND_SIMD`, `BACKEND_OMP`, `BACKEND_CUDA` constants
- Map to `ns_solver_backend_t` enum (values 0-3)

- [ ] **5.2 Implement backend availability functions**
- [x] **5.2 Implement backend availability functions**
- `backend_is_available(backend)` → bool
- `backend_get_name(backend)` → string
- `list_solvers_by_backend(backend)` → list of solver names

- [ ] **5.3 Add solver creation with validation**
- `create_solver_checked(name)` → raises exception if backend unavailable
- Better error messages for unsupported backends

- [ ] **5.4 Add backend query helpers**
- [x] **5.3 Add backend query helpers**
- `get_available_backends()` → list of available backend names
- `get_solver_backend(solver_name)` → backend enum

**Estimated effort:** 1 day
- [x] **5.4 Add tests**
- Created `tests/test_backend_availability.py` with comprehensive tests
- Tests for constants, availability checking, name queries, solver listing

**Note:** `create_solver_checked()` and `get_solver_backend()` deferred to Phase 4 (error handling integration).

**Actual effort:** 0.5 days

### Phase 6: Add CPU Features & Misc (Enhancement)

Expand Down Expand Up @@ -539,11 +543,11 @@ find_library(CFD_LIBRARY cfd_library) # Unified library name
| Phase 2.5: CI/Build System (v0.1.6) | ✅ 1 day | 3 days |
| Phase 3: Derived Fields | 1-2 days | 4-5 days |
| Phase 4: Error Handling | 1 day | 5-6 days |
| Phase 5: Backend Availability (v0.1.6) | 1 day | 6-7 days |
| Phase 6: CPU Features | 1 day | 7-8 days |
| Phase 7: Docs & Tests | 2 days | 9-10 days |
| Phase 5: Backend Availability (v0.1.6) | ✅ 0.5 days | 3.5 days |
| Phase 6: CPU Features | 1 day | 4.5 days |
| Phase 7: Docs & Tests | 2 days | 6.5 days |

**Total estimated effort:** 9-10 days (3 days completed)
**Total estimated effort:** ~~9-10 days~~ 6.5 days (3.5 days completed)

---

Expand Down
29 changes: 28 additions & 1 deletion cfd_python/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""CFD Python - Python bindings for CFD simulation library v0.1.5+.
"""CFD Python - Python bindings for CFD simulation library v0.1.6+.

This package provides Python bindings for the C-based CFD simulation library,
enabling high-performance computational fluid dynamics simulations from Python.
Expand Down Expand Up @@ -49,6 +49,10 @@
- BC_BACKEND_CUDA: GPU acceleration

Functions:
- bc_get_backend(): Get current BC backend
- bc_get_backend_name(): Get current BC backend name
- bc_set_backend(backend): Set BC backend
- bc_backend_available(backend): Check if BC backend is available
- bc_apply_scalar(field, nx, ny, bc_type): Apply BC to scalar field
- bc_apply_velocity(u, v, nx, ny, bc_type): Apply BC to velocity
- bc_apply_dirichlet(field, nx, ny, left, right, bottom, top): Fixed values
Expand All @@ -57,6 +61,19 @@
- bc_apply_inlet_parabolic(u, v, nx, ny, max_velocity, edge): Parabolic inlet
- bc_apply_outlet_scalar(field, nx, ny, edge): Zero-gradient outlet
- bc_apply_outlet_velocity(u, v, nx, ny, edge): Zero-gradient outlet

Solver backend availability (v0.1.6):
Backends:
- BACKEND_SCALAR: Basic scalar CPU implementation
- BACKEND_SIMD: SIMD-optimized (AVX2/SSE)
- BACKEND_OMP: OpenMP parallelized
- BACKEND_CUDA: CUDA GPU acceleration

Functions:
- backend_is_available(backend): Check if backend is available
- backend_get_name(backend): Get backend name string
- list_solvers_by_backend(backend): Get solvers for a backend
- get_available_backends(): Get list of all available backends
"""

from ._version import get_version
Expand Down Expand Up @@ -130,6 +147,16 @@
"bc_apply_inlet_parabolic",
"bc_apply_outlet_scalar",
"bc_apply_outlet_velocity",
# Solver backend constants (v0.1.6)
"BACKEND_SCALAR",
"BACKEND_SIMD",
"BACKEND_OMP",
"BACKEND_CUDA",
# Solver backend availability functions (v0.1.6)
"backend_is_available",
"backend_get_name",
"list_solvers_by_backend",
"get_available_backends",
]

# Load C extension and populate module namespace
Expand Down
20 changes: 20 additions & 0 deletions cfd_python/_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ def load_extension():
try:
from . import cfd_python as _cfd_module
from .cfd_python import (
BACKEND_CUDA,
BACKEND_OMP,
# Solver backend constants (v0.1.6)
BACKEND_SCALAR,
BACKEND_SIMD,
# Boundary condition backends
BC_BACKEND_AUTO,
BC_BACKEND_CUDA,
Expand Down Expand Up @@ -66,6 +71,9 @@ def load_extension():
OUTPUT_FULL_FIELD,
OUTPUT_VELOCITY,
OUTPUT_VELOCITY_MAGNITUDE,
backend_get_name,
# Solver backend availability functions (v0.1.6)
backend_is_available,
bc_apply_dirichlet,
bc_apply_inlet_parabolic,
bc_apply_inlet_uniform,
Expand All @@ -82,13 +90,15 @@ def load_extension():
clear_error,
# Core functions
create_grid,
get_available_backends,
get_default_solver_params,
get_error_string,
get_last_error,
get_last_status,
get_solver_info,
has_solver,
list_solvers,
list_solvers_by_backend,
run_simulation,
run_simulation_with_params,
set_output_dir,
Expand Down Expand Up @@ -164,6 +174,16 @@ def load_extension():
"bc_apply_inlet_parabolic": bc_apply_inlet_parabolic,
"bc_apply_outlet_scalar": bc_apply_outlet_scalar,
"bc_apply_outlet_velocity": bc_apply_outlet_velocity,
# Solver backend constants (v0.1.6)
"BACKEND_SCALAR": BACKEND_SCALAR,
"BACKEND_SIMD": BACKEND_SIMD,
"BACKEND_OMP": BACKEND_OMP,
"BACKEND_CUDA": BACKEND_CUDA,
# Solver backend availability functions (v0.1.6)
"backend_is_available": backend_is_available,
"backend_get_name": backend_get_name,
"list_solvers_by_backend": list_solvers_by_backend,
"get_available_backends": get_available_backends,
}

# Collect dynamic SOLVER_* constants
Expand Down
167 changes: 167 additions & 0 deletions src/cfd_python.c
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,141 @@ static PyObject* bc_backend_available_py(PyObject* self, PyObject* args) {
return PyBool_FromLong(available);
}

/*
* ========================================
* Solver Backend Availability API (v0.1.6)
* ========================================
*/

/*
* Check if a solver backend is available at runtime
*/
static PyObject* backend_is_available_py(PyObject* self, PyObject* args) {
(void)self;
int backend;

if (!PyArg_ParseTuple(args, "i", &backend)) {
return NULL;
}

int available = cfd_backend_is_available((ns_solver_backend_t)backend);
return PyBool_FromLong(available);
}

/*
* Get human-readable name for a solver backend
*/
static PyObject* backend_get_name_py(PyObject* self, PyObject* args) {
(void)self;
int backend;

if (!PyArg_ParseTuple(args, "i", &backend)) {
return NULL;
}

const char* name = cfd_backend_get_name((ns_solver_backend_t)backend);
if (name == NULL) {
Py_RETURN_NONE;
}
return PyUnicode_FromString(name);
}

/*
* Get list of solvers for a specific backend
*/
static PyObject* list_solvers_by_backend_py(PyObject* self, PyObject* args) {
(void)self;
int backend;

if (!PyArg_ParseTuple(args, "i", &backend)) {
return NULL;
}

if (g_registry == NULL) {
PyErr_SetString(PyExc_RuntimeError, "Solver registry not initialized");
return NULL;
}

// First, get the count
int count = cfd_registry_list_by_backend(g_registry, (ns_solver_backend_t)backend, NULL, 0);
if (count <= 0) {
return PyList_New(0); // Return empty list
}

// Allocate array for names
const char** names = (const char**)malloc(count * sizeof(const char*));
if (names == NULL) {
PyErr_SetString(PyExc_MemoryError, "Failed to allocate names array");
return NULL;
}

// Get the actual names
int actual_count = cfd_registry_list_by_backend(g_registry, (ns_solver_backend_t)backend, names, count);

// Build Python list
PyObject* result = PyList_New(actual_count);
if (result == NULL) {
free(names);
return NULL;
}

for (int i = 0; i < actual_count; i++) {
PyObject* name = PyUnicode_FromString(names[i]);
if (name == NULL) {
Py_DECREF(result);
free(names);
return NULL;
}
PyList_SetItem(result, i, name); // Steals reference
}

free(names);
return result;
}

/*
* Get list of all available backends
*/
static PyObject* get_available_backends_py(PyObject* self, PyObject* args) {
(void)self;
(void)args;

PyObject* result = PyList_New(0);
if (result == NULL) {
return NULL;
}

// Check each backend
ns_solver_backend_t backends[] = {
NS_SOLVER_BACKEND_SCALAR,
NS_SOLVER_BACKEND_SIMD,
NS_SOLVER_BACKEND_OMP,
NS_SOLVER_BACKEND_CUDA
};
int num_backends = sizeof(backends) / sizeof(backends[0]);

for (int i = 0; i < num_backends; i++) {
if (cfd_backend_is_available(backends[i])) {
const char* name = cfd_backend_get_name(backends[i]);
if (name != NULL) {
PyObject* name_obj = PyUnicode_FromString(name);
if (name_obj == NULL) {
Py_DECREF(result);
return NULL;
}
if (PyList_Append(result, name_obj) < 0) {
Py_DECREF(name_obj);
Py_DECREF(result);
return NULL;
}
Py_DECREF(name_obj);
}
}
}

return result;
}

/*
* Apply boundary conditions to scalar field
*/
Expand Down Expand Up @@ -1659,6 +1794,29 @@ static PyMethodDef cfd_python_methods[] = {
" nx (int): Grid points in x direction\n"
" ny (int): Grid points in y direction\n"
" edge (int, optional): Boundary edge (default: BC_EDGE_RIGHT)"},
// Solver Backend Availability API (v0.1.6)
{"backend_is_available", backend_is_available_py, METH_VARARGS,
"Check if a solver backend is available at runtime.\n\n"
"Args:\n"
" backend (int): Backend type (BACKEND_SCALAR, BACKEND_SIMD, etc.)\n\n"
"Returns:\n"
" bool: True if backend is available"},
{"backend_get_name", backend_get_name_py, METH_VARARGS,
"Get human-readable name for a solver backend.\n\n"
"Args:\n"
" backend (int): Backend type constant\n\n"
"Returns:\n"
" str or None: Backend name (e.g., 'scalar', 'simd', 'omp', 'cuda')"},
{"list_solvers_by_backend", list_solvers_by_backend_py, METH_VARARGS,
"Get list of solver types for a specific backend.\n\n"
"Args:\n"
" backend (int): Backend type constant\n\n"
"Returns:\n"
" list: Solver type names for the specified backend"},
{"get_available_backends", get_available_backends_py, METH_NOARGS,
"Get list of all available backends.\n\n"
"Returns:\n"
" list: Names of available backends (e.g., ['scalar', 'simd', 'omp'])"},
{NULL, NULL, 0, NULL}
};

Expand Down Expand Up @@ -1808,5 +1966,14 @@ PyMODINIT_FUNC PyInit_cfd_python(void) {
return NULL;
}

// Add solver backend constants (v0.1.6 API)
if (PyModule_AddIntConstant(m, "BACKEND_SCALAR", NS_SOLVER_BACKEND_SCALAR) < 0 ||
PyModule_AddIntConstant(m, "BACKEND_SIMD", NS_SOLVER_BACKEND_SIMD) < 0 ||
PyModule_AddIntConstant(m, "BACKEND_OMP", NS_SOLVER_BACKEND_OMP) < 0 ||
PyModule_AddIntConstant(m, "BACKEND_CUDA", NS_SOLVER_BACKEND_CUDA) < 0) {
Py_DECREF(m);
return NULL;
}

return m;
}
Loading
Loading