feat: Add per-face thermal BC config to solver params#178
feat: Add per-face thermal BC config to solver params#178
Conversation
Thermal boundary conditions (Dirichlet/Neumann) are now configured via ns_solver_params_t.thermal_bc instead of being applied manually by the caller. The solver applies them automatically after each energy step. - Add ns_thermal_bc_config_t with per-face bc_type_t + Dirichlet values - Add energy_apply_thermal_bcs() for 3D-aware thermal BC application - Remove save/restore T memcpy hack from explicit Euler, projection, RK2 - Update natural convection test to use config-driven thermal BCs - Add unit tests for mixed Dirichlet/Neumann and all-periodic no-op
There was a problem hiding this comment.
Pull request overview
This PR moves thermal boundary condition (BC) handling into solver parameters (ns_solver_params_t.thermal_bc) and applies configured thermal BCs automatically within solver steps, eliminating the need for callers to manually set temperature boundaries each iteration.
Changes:
- Introduces
ns_thermal_bc_config_t(per-face Dirichlet/Neumann/Periodic) inns_solver_params_t. - Adds
energy_apply_thermal_bcs()and calls it from CPU solvers after the energy update. - Updates validation and unit tests to use config-driven thermal BCs (and adds new thermal BC unit tests).
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
lib/include/cfd/solvers/navier_stokes_solver.h |
Adds ns_thermal_bc_config_t and embeds it into ns_solver_params_t. |
lib/include/cfd/solvers/energy_solver.h |
Declares public energy_apply_thermal_bcs() API. |
lib/src/solvers/energy/cpu/energy_solver.c |
Implements per-face thermal BC application (including 3D faces). |
lib/src/solvers/navier_stokes/cpu/solver_explicit_euler.c |
Removes T save/restore hack; applies configured thermal BCs after periodic BC application. |
lib/src/solvers/navier_stokes/cpu/solver_rk2.c |
Removes T save/restore hack; applies configured thermal BCs after periodic BC application. |
lib/src/solvers/navier_stokes/cpu/solver_projection.c |
Applies configured thermal BCs after the energy step. |
tests/validation/test_natural_convection.c |
Switches from manual temperature BC enforcement to params.thermal_bc; keeps velocity no-slip applied by the test. |
tests/solvers/energy/test_energy_solver.c |
Adds unit tests for mixed Dirichlet/Neumann and all-periodic no-op behavior. |
ROADMAP.md |
Updates backend coverage wording and documents current GPU backend limitations. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Neumann BCs read from adjacent interior cells, which is out-of-bounds when nx<2 or ny<2. Add early-return guards for undersized grids. Periodic temperature BCs were previously a no-op, relying on the caller to have already called apply_boundary_conditions(). This left periodic temperature boundaries unenforced in the projection solver. Handle all three BC types (Dirichlet, Neumann, Periodic) directly so the function is self-contained across all solvers.
Exercises nz>1 code paths: Dirichlet on back face, Neumann on front face, Periodic on left/right, and Dirichlet on bottom/top. Verifies corner overwrite ordering and periodic copy from opposite interior.
Update all-periodic test to verify periodic copy behavior instead of no-op (periodic now actively copies from opposite interior cells). Fix 3D test: back/front faces run after bottom/top, so z-face BCs overwrite corner intersections at k=0 and k=nz-1.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| void energy_apply_thermal_bcs(flow_field* field, | ||
| const ns_solver_params_t* params) { | ||
| if (!field || !params || !field->T) return; | ||
| if (params->alpha <= 0.0) return; | ||
|
|
||
| const ns_thermal_bc_config_t* tbc = ¶ms->thermal_bc; | ||
|
|
||
| size_t nx = field->nx; | ||
| size_t ny = field->ny; | ||
| size_t nz = field->nz; | ||
| size_t plane = nx * ny; | ||
|
|
||
| /* Guard: Neumann needs >= 2 cells, Periodic needs >= 3 cells */ | ||
| if ((tbc->left == BC_TYPE_NEUMANN || tbc->right == BC_TYPE_NEUMANN) && nx < 2) return; | ||
| if ((tbc->bottom == BC_TYPE_NEUMANN || tbc->top == BC_TYPE_NEUMANN) && ny < 2) return; | ||
| if ((tbc->left == BC_TYPE_PERIODIC || tbc->right == BC_TYPE_PERIODIC) && nx < 3) return; | ||
| if ((tbc->bottom == BC_TYPE_PERIODIC || tbc->top == BC_TYPE_PERIODIC) && ny < 3) return; | ||
| if (nz > 1 && (tbc->back == BC_TYPE_NEUMANN || tbc->front == BC_TYPE_NEUMANN) && nz < 2) return; | ||
| if (nz > 1 && (tbc->back == BC_TYPE_PERIODIC || tbc->front == BC_TYPE_PERIODIC) && nz < 3) return; |
There was a problem hiding this comment.
energy_apply_thermal_bcs() is exported but silently returns on invalid inputs (NULLs, alpha<=0, or too-small dimensions for the requested BC types). This makes misconfiguration hard to detect and is inconsistent with other energy APIs (e.g., energy_step_explicit_with_workspace) that return cfd_status_t and set an error. Consider returning cfd_status_t here (or at least calling cfd_set_error on invalid configs) so callers/solvers can surface actionable failures instead of quietly skipping BC application.
| size_t idx = base + j * nx; | ||
| if (tbc->left == BC_TYPE_DIRICHLET) | ||
| field->T[idx] = tbc->dirichlet_values.left; | ||
| else if (tbc->left == BC_TYPE_NEUMANN) | ||
| field->T[idx] = field->T[idx + 1]; | ||
| else if (tbc->left == BC_TYPE_PERIODIC) | ||
| field->T[idx] = field->T[base + j * nx + (nx - 2)]; | ||
| } |
There was a problem hiding this comment.
ns_thermal_bc_config_t uses bc_type_t, but energy_apply_thermal_bcs() only handles PERIODIC/NEUMANN/DIRICHLET; any other bc_type_t value will currently fall through and leave that face unchanged without warning. This can produce silently-wrong BCs if a caller accidentally sets BC_TYPE_NOSLIP/INLET/OUTLET/SYMMETRY. Consider validating that each face is one of the supported thermal types and treating any other value as CFD_ERROR_INVALID (or explicitly mapping unsupported values to PERIODIC).
| * Zero-initialization produces all-PERIODIC (BC_TYPE_PERIODIC == 0), | ||
| * preserving backward compatibility. |
There was a problem hiding this comment.
The docs state that zero-initialization yields “all-PERIODIC … no change”, but with the new design the solver will actively enforce periodic temperature boundaries (and overwrite any caller-set boundary temperatures) unless thermal_bc is configured. Consider rewording this to clarify that {0} means “all PERIODIC (matches apply_boundary_conditions default)” rather than “no change”, and explicitly note that callers must set thermal_bc when they want non-periodic thermal walls.
| * Zero-initialization produces all-PERIODIC (BC_TYPE_PERIODIC == 0), | |
| * preserving backward compatibility. | |
| * Zero-initialization produces an all-PERIODIC configuration | |
| * (BC_TYPE_PERIODIC == 0), matching the solver's default | |
| * `apply_boundary_conditions` behavior. This is not a "no change" mode: | |
| * periodic thermal boundaries will be actively enforced unless callers | |
| * explicitly set `thermal_bc` for non-periodic thermal walls (for example, | |
| * DIRICHLET or NEUMANN faces). |
| /** | ||
| * Apply thermal boundary conditions to the temperature field. | ||
| * | ||
| * For each face configured as DIRICHLET, sets T to the specified value. | ||
| * For each face configured as NEUMANN, copies from adjacent interior cell. | ||
| * For PERIODIC faces (default), copies from the opposite interior cell. | ||
| * | ||
| * Only active when params->alpha > 0. Returns immediately otherwise. | ||
| * | ||
| * @param field Flow field (T is updated in-place) | ||
| * @param params Solver parameters containing thermal_bc config | ||
| */ | ||
| CFD_LIBRARY_EXPORT void energy_apply_thermal_bcs(flow_field* field, | ||
| const ns_solver_params_t* params); |
There was a problem hiding this comment.
The header comment says PERIODIC “copies from the opposite interior cell”, but energy_apply_thermal_bcs applies faces sequentially and later faces overwrite edges/corners shared with earlier faces (and in 3D, back/front can overwrite entire planes). Consider documenting the precedence/order for edges/corners (or adjusting the implementation to make corner behavior independent of application order) so users don’t assume the periodic/Neumann rule holds universally at intersections.
Thermal boundary conditions (Dirichlet/Neumann) are now configured via ns_solver_params_t.thermal_bc instead of being applied manually by the caller. The solver applies them automatically after each energy step.