|
| 1 | +# Flamelet Solver — Python Wrapper Wall Boundary Conditions |
| 2 | + |
| 3 | +This document explains how to use the Python wrapper (`pysu2`) to set |
| 4 | +custom per-vertex boundary conditions for the flamelet (FGM) scalar transport |
| 5 | +solver, and how the setup differs between the two `FLAMELET_ENTHALPY_BC` modes. |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +## Background |
| 10 | + |
| 11 | +The flamelet solver transports `nVar` scalar variables. Their order is: |
| 12 | + |
| 13 | +| Index | Variable | |
| 14 | +|-------|----------| |
| 15 | +| 0 | `PROGVAR` (progress variable) | |
| 16 | +| 1 | `ENTH` (total enthalpy) | |
| 17 | +| 2 … n\_CV−1 | additional control variables (e.g. `MIXFRAC`) | |
| 18 | +| n\_CV … nVar−1 | auxiliary / user-defined species | |
| 19 | + |
| 20 | +Two modes control how the enthalpy boundary condition is derived at thermal |
| 21 | +walls (`MARKER_ISOTHERMAL`, `MARKER_HEATFLUX`): |
| 22 | + |
| 23 | +- **`FLOW_MARKERS`** — enthalpy is derived from the flow thermal field (wall |
| 24 | + temperature via `MARKER_ISOTHERMAL`, or wall heat flux via `MARKER_HEATFLUX`). |
| 25 | +- **`SPECIES_MARKERS`** (default) — enthalpy and all other scalars are taken |
| 26 | + directly from `MARKER_WALL_SPECIES`, or from the Python wrapper when |
| 27 | + `MARKER_PYTHON_CUSTOM` is active. |
| 28 | + |
| 29 | +--- |
| 30 | + |
| 31 | +## Mode 1: `FLAMELET_ENTHALPY_BC = FLOW_MARKERS` |
| 32 | + |
| 33 | +### How it works |
| 34 | + |
| 35 | +The C++ function `BC_HeatFlux_Wall` reads the wall heat flux and applies it as |
| 36 | +a Neumann condition on `I_ENTH`. When the marker is also listed in |
| 37 | +`MARKER_PYTHON_CUSTOM`, it instead reads a **per-vertex heat flux** set by the |
| 38 | +Python wrapper. |
| 39 | + |
| 40 | +``` |
| 41 | +driver.SetMarkerCustomNormalHeatFlux(iMarker, iVertex, q_wall) |
| 42 | + → geometry->CustomBoundaryHeatFlux[iMarker][iVertex] |
| 43 | + → BC_HeatFlux_Wall reads geometry->GetCustomBoundaryHeatFlux(...) |
| 44 | +``` |
| 45 | + |
| 46 | +> `BC_Isothermal_Wall` in `FLOW_MARKERS` mode always converts the wall |
| 47 | +> temperature (from `MARKER_ISOTHERMAL`) to enthalpy via `GetEnthFromTemp`. |
| 48 | +> Per-vertex customisation of the enthalpy Dirichlet value is not supported in |
| 49 | +> this mode — use `SPECIES_MARKERS` instead. |
| 50 | +
|
| 51 | +### Limitations in this mode |
| 52 | + |
| 53 | +- Only `I_ENTH` is reachable via the Python wrapper. |
| 54 | +- Non-enthalpy scalars (`PROGVAR`, auxiliary species) receive implicit zero |
| 55 | + Neumann at the wall and cannot be set via Python. |
| 56 | + |
| 57 | +### config.cfg |
| 58 | + |
| 59 | +```cfg |
| 60 | +FLAMELET_ENTHALPY_BC = FLOW_MARKERS |
| 61 | +
|
| 62 | +% Flow solver wall BC — sets KindBC and provides the fallback heat flux value |
| 63 | +% used when MARKER_PYTHON_CUSTOM is not active. |
| 64 | +MARKER_HEATFLUX = ( burner_wall, 0.0 ) |
| 65 | +
|
| 66 | +% Enable the per-vertex Python override path. |
| 67 | +MARKER_PYTHON_CUSTOM = ( burner_wall ) |
| 68 | +``` |
| 69 | + |
| 70 | +### run.py |
| 71 | + |
| 72 | +```python |
| 73 | +import pysu2 |
| 74 | + |
| 75 | +driver = pysu2.CSinglezoneDriver("config.cfg", 1, False) |
| 76 | + |
| 77 | +marker_name = "burner_wall" |
| 78 | +iMarker = list(driver.GetMarkerTags()).index(marker_name) |
| 79 | +n_vertex = driver.GetNumberMarkerNodes(iMarker) |
| 80 | + |
| 81 | +def compute_heat_flux(coord): |
| 82 | + """Heat flux [W/m²], positive = into the domain.""" |
| 83 | + import math |
| 84 | + r = math.sqrt(coord[0]**2 + coord[1]**2) |
| 85 | + return -5000.0 * math.exp(-r**2 / 0.01) # Gaussian profile, heat out |
| 86 | + |
| 87 | +for iteration in range(driver.GetNumberIter()): |
| 88 | + |
| 89 | + for iVertex in range(n_vertex): |
| 90 | + node_id = driver.GetMarkerNode(iMarker, iVertex) |
| 91 | + coord = driver.GetInitialMeshCoord(node_id) # [x, y(, z)] |
| 92 | + q_wall = compute_heat_flux(coord) |
| 93 | + driver.SetMarkerCustomNormalHeatFlux(iMarker, iVertex, q_wall) |
| 94 | + |
| 95 | + driver.Preprocess(iteration) |
| 96 | + driver.Run() |
| 97 | + driver.Postprocess() |
| 98 | + driver.Monitor(iteration) |
| 99 | + driver.Output(iteration) |
| 100 | + |
| 101 | +driver.Finalize() |
| 102 | +``` |
| 103 | + |
| 104 | +--- |
| 105 | + |
| 106 | +## Mode 2: `FLAMELET_ENTHALPY_BC = SPECIES_MARKERS` (default) |
| 107 | + |
| 108 | +### How it works |
| 109 | + |
| 110 | +Both `BC_HeatFlux_Wall` and `BC_Isothermal_Wall` delegate immediately to |
| 111 | +`CSpeciesSolver::BC_Wall_Generic`, which processes **all `nVar` scalars** |
| 112 | +independently. When `MARKER_PYTHON_CUSTOM` is active, the value for each |
| 113 | +scalar is taken from the array set by `driver.SetMarkerCustomScalar()`. |
| 114 | + |
| 115 | +``` |
| 116 | +driver.SetMarkerCustomScalar(iMarker, iVertex, [val_0, val_1, ..., val_nVar-1]) |
| 117 | + → CustomBoundaryScalar[iMarker](iVertex, iVar) |
| 118 | + → BC_Wall_Generic reads CustomBoundaryScalar for every iVar |
| 119 | +``` |
| 120 | + |
| 121 | +The **type** of BC for each scalar (Dirichlet `VALUE` or Neumann `FLUX`) is |
| 122 | +read from `MARKER_WALL_SPECIES` in the config and cannot be changed from |
| 123 | +Python. The Python wrapper only overrides the **magnitude** per vertex. |
| 124 | + |
| 125 | +### config.cfg |
| 126 | + |
| 127 | +The example below uses `nVar = 3` (PROGVAR, ENTH, one auxiliary species). |
| 128 | +Adjust the number of `BC_TYPE, value` pairs to match the actual number of |
| 129 | +scalar variables in your setup. |
| 130 | + |
| 131 | +```cfg |
| 132 | +FLAMELET_ENTHALPY_BC = SPECIES_MARKERS |
| 133 | +
|
| 134 | +% Flow solver wall BC — still required to set KindBC. |
| 135 | +% The choice between MARKER_ISOTHERMAL and MARKER_HEATFLUX only affects |
| 136 | +% the flow solver; the species solver uses BC_Wall_Generic in both cases. |
| 137 | +MARKER_ISOTHERMAL = ( burner_wall, 300.0 ) |
| 138 | +
|
| 139 | +% Per-variable BC types for the species solver. |
| 140 | +% Format: (marker_name, BC_TYPE, fallback_value, BC_TYPE, fallback_value, ...) |
| 141 | +% One BC_TYPE+value pair per scalar variable, in index order. |
| 142 | +% BC_TYPE: FLUX → Neumann (value is normal flux density [unit/m²]) |
| 143 | +% VALUE → Dirichlet (value is the scalar value at the wall) |
| 144 | +% |
| 145 | +% When MARKER_PYTHON_CUSTOM is active, the fallback_value is ignored and |
| 146 | +% the Python-supplied value is used instead. The BC_TYPE is always from config. |
| 147 | +% |
| 148 | +% idx 0 PROGVAR : zero Neumann (no progress variable source at the wall) |
| 149 | +% idx 1 ENTH : Dirichlet (enthalpy value set per-vertex by Python) |
| 150 | +% idx 2 aux_1 : zero Neumann (no auxiliary species flux at the wall) |
| 151 | +MARKER_WALL_SPECIES = ( burner_wall, FLUX, 0.0, VALUE, 0.0, FLUX, 0.0 ) |
| 152 | +
|
| 153 | +% Enable the per-vertex Python override path. |
| 154 | +MARKER_PYTHON_CUSTOM = ( burner_wall ) |
| 155 | +``` |
| 156 | + |
| 157 | +### run.py |
| 158 | + |
| 159 | +```python |
| 160 | +import pysu2 |
| 161 | + |
| 162 | +driver = pysu2.CSinglezoneDriver("config.cfg", 1, False) |
| 163 | + |
| 164 | +marker_name = "burner_wall" |
| 165 | +iMarker = list(driver.GetMarkerTags()).index(marker_name) |
| 166 | +n_vertex = driver.GetNumberMarkerNodes(iMarker) |
| 167 | + |
| 168 | +def compute_wall_enthalpy(coord): |
| 169 | + """Return wall enthalpy [J/kg] at this vertex.""" |
| 170 | + # Example: uniform value corresponding to T=300 K from your LUT. |
| 171 | + return 3.5e5 |
| 172 | + |
| 173 | +for iteration in range(driver.GetNumberIter()): |
| 174 | + |
| 175 | + for iVertex in range(n_vertex): |
| 176 | + node_id = driver.GetMarkerNode(iMarker, iVertex) |
| 177 | + coord = driver.GetInitialMeshCoord(node_id) |
| 178 | + |
| 179 | + enth_wall = compute_wall_enthalpy(coord) |
| 180 | + |
| 181 | + # Pass one value per scalar variable (length = nVar). |
| 182 | + # PROGVAR (idx 0): 0.0 → applied as FLUX → zero Neumann (BC_TYPE from config) |
| 183 | + # ENTH (idx 1): enth_wall → applied as VALUE → Dirichlet |
| 184 | + # aux_1 (idx 2): 0.0 → applied as FLUX → zero Neumann |
| 185 | + driver.SetMarkerCustomScalar(iMarker, iVertex, [0.0, enth_wall, 0.0]) |
| 186 | + |
| 187 | + driver.Preprocess(iteration) |
| 188 | + driver.Run() |
| 189 | + driver.Postprocess() |
| 190 | + driver.Monitor(iteration) |
| 191 | + driver.Output(iteration) |
| 192 | + |
| 193 | +driver.Finalize() |
| 194 | +``` |
| 195 | + |
| 196 | +--- |
| 197 | + |
| 198 | +## Comparison |
| 199 | + |
| 200 | +| | `FLOW_MARKERS` | `SPECIES_MARKERS` | |
| 201 | +|---|---|---| |
| 202 | +| **Python API** | `SetMarkerCustomNormalHeatFlux` | `SetMarkerCustomScalar` | |
| 203 | +| **Storage** | `geometry->CustomBoundaryHeatFlux` | `CustomBoundaryScalar[iMarker](iVertex, iVar)` | |
| 204 | +| **What you set** | Per-vertex heat flux (W/m²) for `I_ENTH` only | Per-vertex values for **all** `nVar` scalars | |
| 205 | +| **BC type control** | Hard-coded Neumann in C++ | Per-variable via `MARKER_WALL_SPECIES` in config | |
| 206 | +| **`MARKER_ISOTHERMAL` walls** | Enthalpy computed from T via `GetEnthFromTemp`; not overridable by Python | Enthalpy set as Dirichlet `VALUE` from Python | |
| 207 | +| **Non-enthalpy scalars** | Not reachable via Python | All controlled via the same `SetMarkerCustomScalar` call | |
| 208 | +| **Required config keys** | `MARKER_HEATFLUX`, `MARKER_PYTHON_CUSTOM` | `MARKER_ISOTHERMAL` or `MARKER_HEATFLUX`, `MARKER_WALL_SPECIES`, `MARKER_PYTHON_CUSTOM` | |
| 209 | + |
| 210 | +--- |
| 211 | + |
| 212 | +## Notes |
| 213 | + |
| 214 | +- `MARKER_PYTHON_CUSTOM` is a purely additive flag. A marker can appear in |
| 215 | + both `MARKER_HEATFLUX` (or `MARKER_ISOTHERMAL`) and `MARKER_PYTHON_CUSTOM` |
| 216 | + simultaneously. |
| 217 | +- The fallback values in `MARKER_WALL_SPECIES` are used when `py_custom` is |
| 218 | + false (e.g., during a plain SU2\_CFD run without the Python driver). They |
| 219 | + allow the same config to be used both ways. |
| 220 | +- In `SPECIES_MARKERS` mode, `SetMarkerCustomScalar` must supply a vector of |
| 221 | + exactly `nVar` values. Indices that correspond to `FLUX`-type variables |
| 222 | + should receive `0.0` unless you intentionally want a non-zero flux there. |
| 223 | +- The `CHT` (conjugate heat transfer) path (`BC_ConjugateHeat_Interface`) |
| 224 | + always uses `BC_Isothermal_Wall_Generic` and is unaffected by |
| 225 | + `FLAMELET_ENTHALPY_BC`. Python custom BCs are not applicable to CHT markers. |
0 commit comments