Grid¶
Grid collection for managing multiple Block objects with connectivity analysis.
This module provides the Grid class which serves as the top-level container for multi-block structured CFD simulations. The Grid manages collections of Block objects with support for label-based access, automatic connectivity detection between blocks, and methods for computing domain-wide flow statistics. Key features include spatial indexing using KD-trees for efficient neighbor finding, detection and tracking of periodic and mixing-plane patch pairs, computation of circumferentially-averaged flow properties, and utilities for extracting boundary flows at inlets and outlets. The connectivity property provides detailed inter-block topology information essential for implementing multi-block boundary conditions, while the averaging capabilities enable efficient turbomachinery performance analysis across blade rows.
- class ember.grid.Convergence(residual, mdot, ho, s, mdot_target=0.0, mdot_throttle=0.0, P_throttle=0.0, dP_P=0.0, dP_I=0.0, dP_D=0.0)¶
Bases:
tupleCreate new instance of Convergence(residual, mdot, ho, s, mdot_target, mdot_throttle, P_throttle, dP_P, dP_I, dP_D)
- P_throttle¶
Alias for field number 6
- count(value, /)¶
Return number of occurrences of value.
- dP_D¶
Alias for field number 9
- dP_I¶
Alias for field number 8
- dP_P¶
Alias for field number 7
- ho¶
Alias for field number 2
- index(value, start=0, stop=9223372036854775807, /)¶
Return first index of value.
Raises ValueError if the value is not present.
- mdot¶
Alias for field number 1
- mdot_target¶
Alias for field number 4
- mdot_throttle¶
Alias for field number 5
- residual¶
Alias for field number 0
- s¶
Alias for field number 3
- exception ember.grid.SolverDivergedError[source]¶
Bases:
RuntimeErrorRaised when a block’s conserved field contains a NaN.
A dedicated type lets a solver loop catch divergence precisely and exit cleanly (leaving the invalid field in place for debugging) while genuinely unexpected errors still propagate. See
Grid.check_nan().- add_note(object, /)¶
Exception.add_note(note) – add a note to the exception
- args¶
- with_traceback(object, /)¶
Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.
- class ember.grid.Grid(blocks=None)[source]¶
Bases:
_LabelledListInitialize grid with optional list of blocks.
- Parameters:
blocks (list, optional) – Initial list of blocks to add to the grid.
- classmethod read_emb(filename)[source]¶
Read grid from EMB binary format file.
Automatically detects and handles both uncompressed and gzip-compressed EMB files.
- Parameters:
filename (str) – Input EMB file to read
- Returns:
New grid containing all blocks, patches, flow data, and metadata from EMB file
- Return type:
- Raises:
FileNotFoundError – If file does not exist
- classmethod read_plot3d(p3d_file, fvbnd_file=None, flip_k=True)[source]¶
Read grid from Plot3D format file with optional boundary patches.
- Parameters:
- Returns:
New grid containing blocks with coordinates and optional patches from files
- Return type:
- set_conserved_cart_unstr(xyz, conserved_cart)[source]¶
Set conserved variables from Cartesian unstructured data.
Useful for importing flow solutions from unstructured CFD solvers that store data on arbitrary point clouds in Cartesian coordinates.
Automatically detects the coordinate permutation and sign mapping that aligns the Cartesian data with the structured grid, converts coordinates to polar, and transforms the momentum vector accordingly.
The input conserved state vector is:
\[\mathcal{U}_{\mathrm{cart}} = \begin{pmatrix} \rho,\ \rho V_x,\ \rho V_y,\ \rho V_z,\ \rho e \end{pmatrix}\]The Cartesian momentum components are first converted to velocities, rotated into the polar frame \((x, r, \theta)\), then reassembled as polar conserved variables:
\[\mathcal{U} = \begin{pmatrix} \rho,\ \rho V_x,\ \rho V_r,\ \rho r V_\theta,\ \rho e \end{pmatrix}\]where the polar velocity components are:
\[\begin{split}V_r &= V_y \cos\theta - V_z \sin\theta \\ V_\theta &= -V_y \sin\theta - V_z \cos\theta\end{split}\]with \(\theta = \mathrm{atan2}(-z,\, y)\).
- set_fluid(fluid_obj)[source]¶
Set equation of state on all blocks, preserving any existing flow field.
The fluid object specifies the reference scales used to normalise stored thermodynamic quantities. This method rescales the stored nondimensional flow field so that the underlying dimensional state is unchanged. See
ember.block.Block.set_fluid()for full details.- Parameters:
fluid_obj (Fluid) – New fluid / equation of state object.
- set_L_ref(L_ref)[source]¶
Set reference length scale on all blocks, preserving dimensional geometry and flow field.
The reference length scale is used to normalise stored coordinates. This method rescales the stored nondimensional values so that the underlying dimensional geometry and flow field are unchanged. See
ember.block.Block.set_L_ref()for full details of the nondimensionalisation.- Parameters:
L_ref (float) – Reference length scale [m].
- set_primitive_cart_unstr(xyz, primitive_cart)[source]¶
Set primitive variables from Cartesian unstructured data.
Useful for importing flow solutions from unstructured CFD solvers that store data on arbitrary point clouds in Cartesian coordinates.
Automatically detects the coordinate permutation and sign mapping that aligns the Cartesian data with the structured grid, converts coordinates to polar, and rotates the velocity vector accordingly.
The input primitive state vector is:
\[\mathcal{P}_{\mathrm{cart}} = \begin{pmatrix} \rho,\ V_x,\ V_y,\ V_z,\ p \end{pmatrix}\]The Cartesian velocity components are rotated into the polar frame \((x, r, \theta)\) to give the polar primitive state:
\[\mathcal{P} = \begin{pmatrix} \rho,\ V_x,\ V_r,\ V_\theta,\ p \end{pmatrix}\]where:
\[\begin{split}V_r &= V_y \cos\theta - V_z \sin\theta \\ V_\theta &= -V_y \sin\theta - V_z \cos\theta\end{split}\]with \(\theta = \mathrm{atan2}(-z,\, y)\).
- get_convergence()[source]¶
Grid-representative convergence monitors at one step, non-dimensional.
Stations are taken from
row_station_bid_pid, derived from the grid’s own topology.- Returns:
Convergence – Namedtuple
(residual, mdot, ho, s, mdot_target, mdot_throttle, P_throttle, dP_P, dP_I, dP_D).residualis the block-mean|residual_nd|per conserved variable, shape(5,).mdot,ho,sare shape(2*n_row,)station vectors at each row’s upstream then downstream face, inlet to outlet ([row0_up, row0_dn, row1_up, row1_dn, ...]), all non-dimensionalised by the fluid reference scales. The remaining scalar fields are the outlet PID throttle state fromember.outlet.OutletPatch.get_throttle_stats().The
rhorVt(angular-momentum) residual is divided per block byblock.r_mid_ndso its reported magnitude is a tangential-momentumscale comparable to the
rhoVx/rhoVrresiduals. This rescaling isfor monitoring only;
residual_nditself (which drives the RK sweep)is untouched.
- get_r_ref()[source]¶
Calculate reference radial coordinates for each row of blocks.
For each element in Grid.rows, calculates the maximum and minimum r coordinates across all blocks, then stores the mean value in Grid.r_ref as a numpy array with the same length as rows.
- accumulate_avg(n_step_avg)[source]¶
Add the current conserved field into the running time-average.
Accumulates each block’s
conserved_nd / n_step_avginto itsconserved_avg_ndbuffer via the Fortran kernel. Called once per step over the finaln_step_avgsteps of a march, this builds the mean of the converged limit cycle. The buffer is a read-only cached array, so itsflags.writeableis toggled around the in-place kernel write (mirrorsupdate_sources()).- Returns:
self, for method chaining.
- Return type:
- apply_bconds()[source]¶
Apply all boundary conditions across the grid once.
Refreshes the mixing-plane targets, imposes the physical inlet, outlet, and mixing patch conditions on every block (each using its own relaxation factor
rf), then closes the point-matched periodic seams.Unlike the time-marching stepper this always applies the full set with no freeze or multigrid-level gating. The first call builds the periodic and mixing communicators lazily via
connectivity; subsequent calls reuse the cached communicators untilconnectivity.clear().- Returns:
self, for method chaining.
- Return type:
- apply_guess_meridional(block_guess, refine_factor=1)[source]¶
Apply meridional flow field guess using curvilinear interpolation.
Uses a 1D meridional block as initial guess, interpolating flow properties to all blocks in the grid using nearest-neighbor search in the (x, r) meridional plane. Optionally refines the guess using curvilinear arc-length coordinates for improved interpolation quality.
- Parameters:
block_guess (Block) – Block containing the 1D guess flow field with shape (ni,). Use block_guess.squeeze() first if your block has singleton dimensions.
refine_factor (int, optional) – Refinement factor for the guess block (default=1, no refinement). If > 1, the guess is interpolated along curvilinear arc length, providing a denser point cloud and smoother results.
- Raises:
ValueError – If block_guess does not have shape (ni,)
- apply_guess_quasi3d(block_guess)[source]¶
Apply quasi-3D flow field guess by interpolating between two meridional planes.
Uses two 2D meridional faces as boundary conditions on the low- and high-theta k-faces of each block, then linearly interpolates conserved variables across k as a function of the circumferential coordinate theta.
- Parameters:
block_guess (Block, shape (ni, nj, 2)) – A single block whose k=0 face is the low-theta meridional plane and k=1 face is the high-theta meridional plane. i is streamwise, j is radial.
- Raises:
ValueError – If block_guess does not have shape (ni, nj, 2).
- apply_guess_restart(restarts)[source]¶
Apply a list of BlockRestart objects to this Grid, block by block.
Use this to initialize a fresh grid from a previously-saved solution. Same-shape blocks are set directly; differing shapes are trilinearly interpolated in index space. Only conserved variables are transferred; mu_turb is untouched.
- apply_rotation(row_types, Omega)[source]¶
Apply rotation settings to blocks based on row types.
Sets angular velocity and adds appropriate rotating wall patches to blocks based on the specified row type configuration. Supports stationary, tip_gap, and shroud configurations for turbomachinery applications.
- Parameters:
row_types (list of str) – List of row type strings, one per row in the grid. Valid values: - “stationary”: No rotating patches (fixed frame) - “tip_gap”: Rotating patches on i=0, i=-1, j=0, k=0, k=-1 - “shroud”: Rotating patches on all boundaries (i=0, i=-1, j=0, j=-1, k=0, k=-1)
Omega (list of float) – List of angular velocities [rad/s], one per row in the grid. Positive values indicate rotation direction.
- Raises:
AssertionError – If length of row_types and Omega don’t match
ValueError – If unknown row_type is specified
Examples
>>> grid = Grid([block1, block2]) >>> grid.apply_rotation(["tip_gap"], [1000.0]) # Single rotating row
- calculate_wdist(limit_pitch=inf)[source]¶
This method creates a pitchwise-repeated grid to include neighboring passages, extracts all wall nodes from the repeated blocks, builds a KDTree for efficient nearest neighbor search, and calculates the distance from each node to the nearest wall surface. The results are stored in each block using set_wdist().
The method uses real Cartesian coordinates (xyz) for accurate 3D distance calculations in turbomachinery applications.
- Parameters:
limit_pitch (float, optional) – Cap the wall distance at
limit_pitchtimes each row’s reference blade pitch (2*pi*r_ref/Nb). This bakes the mixing-length limit directly into the stored wall distance, so downstream turbulence models need no separate cap. Defaults tonp.inf(no limit).
Examples
>>> grid = Grid([block1, block2]) >>> # Wall distances are now available as block.wdist for each block
- check_nan()[source]¶
Scan every block’s density field for NaN; report the first bad block.
Cheap enough to call each solver step: only the density component
conserved_nd[..., 0]is inspected, since a NaN in any conserved variable propagates into density within a step through the pressure/flux coupling. On the duct smoke test this costs ~0.5% of a single full-field residual evaluation.- Raises:
SolverDivergedError – If any block contains a NaN, naming the first such block (index and label). The grid is left untouched so the invalid field can be inspected.
- copy(keep_patches=True)[source]¶
Create a deep copy of the grid with copied blocks.
- Returns:
New Grid instance containing copies of all blocks.
- Return type:
Example
>>> grid_copy = grid.copy() >>> grid_copy[0].conserved[...] = 0 # Does not affect original grid
- finalise_average()[source]¶
Commit the accumulated time-average as the solution.
Copies each block’s
conserved_avg_ndintoconserved_nd, refreshes the conserved-dependent caches, then re-zeros the accumulator so any subsequent averaging window starts clean. Owns theflags.writeabletoggle on the read-only average buffer.- Returns:
self, for method chaining.
- Return type:
- flat()[source]¶
Return concatenated flattened view of all blocks.
- Returns:
Single block containing concatenated flat data from all blocks
- Return type:
- smooth(sf4, sf2)[source]¶
Apply constant-coefficient artificial dissipation to every block.
Fans the fixed-
sf2/sf4ember.fortran.smooth3d_const()kernel across the grid, filtering each block’sconserved_ndin place. Unlike the adaptive (P/T-weighted) smoother it has no thermodynamic input at all and never touches the pressure cache, so it is safe to run on the post-march solution while P/T are frozen (Denton-style “smooth with the old pressure”). Purely per-block; the nodal work array is borrowed from each block’s transient scratch buffer.sf4/sf2are the final coefficients – any CFL scaling is the caller’s responsibility.
- update_bconds(freeze=False)[source]¶
Refresh boundary-condition targets across the grid once.
Advances the slowly-varying BC state that the per-substep
apply_bconds()then imposes: exchanges mixing-plane data, snapshots the inlet pressure datum, and re-derives the outlet PID/spanwise pressure target. Should be called once per outer timestep, before the Runge-Kutta stages.When
freezeis True the targets are held stationary – the mixing exchange and the outlet target re-derivation are skipped – so an averaging window sees a fixed boundary. Theupdate_solnsnapshots still run so backflow density relaxation stays anchored to the current step.- Returns:
self, for method chaining.
- Return type:
- update_cached_conserved()[source]¶
Refresh conserved-dependent caches on every block.
Fans
update_cached_conserved()out across the grid, forcing each block’s cached properties keyed on the conserved variables to recompute on next access. Needed after writingconserved_nddirectly (bypassing the setters), e.g. the explicit scree march.- Returns:
self, for method chaining.
- Return type:
- update_residual(dampin=None, sf=0.0)[source]¶
Rebuild the unintegrated net-flow residual on every block.
Fans the fused
set_residualkernel across the grid, writing each block’sresidual_ndfrom its frozen P/T cache, face areas, and body force. Purely per-block (no inter-block exchange), so it simply loops.Optional post-processing runs in place on each block’s residual, in order: implicit residual smoothing (
sf), then the change limiter (dampin).- Parameters:
dampin (float, optional) – Negative-feedback change limiter (multall’s
DAMP). When given, each block’s residual is passed throughdamp_residual, which soft-clips cells whose per-step changeresidual * dt_volis a large outlier relative to the per-variable block mean, shrinking them by1/(1 + fdamp/dampin)withfdamp = |change|/mean. Large outliers saturate towardsdampin * mean.None(the default) disables it. multall recommends2..100. Requiresdt_vol_ndto have been populated byupdate_timestep().sf (float, optional) – Implicit residual-smoothing (IRS) coefficient (epsilon).
0(the default) disables IRS;> 0applies the exact factored-tridiagonal smoother to each block viasmooth_residual_tri_tiled. IRS damps high-frequency residual content so the explicit march tolerates a higher CFL; because it acts only on the residual (which vanishes at convergence) it does not change the steady-state solution. Per-block only: block/periodic interfaces are treated as zero-gradient. Borrowsblock.scratchas its work buffer – free at this point, sinceset_residualhas finished using it asflow_iand the march reuses it only afterwards.
- Returns:
self, for method chaining.
- Return type:
- update_sources(inviscid, gain_filt)[source]¶
Zero and rebuild the body force on every block of this grid level.
Assembles, into each
block.F_body_nd, the viscous shear stresses (unlessinviscid), the polar source, and the optional SFD force (whengain_filtis nonzero) – in that order, so the viscous momentum/energy negation does not flip the polar source added afterwards.The viscous calculation is phased across the whole grid: every block’s tau/q is computed first, then a single periodic seam halo exchange runs, then the face fluxes are accumulated. This keeps the seam consistent for block-to-block periodic interfaces, where a per-block exchange would read a stale neighbour halo.
- update_timestep(rf, fac_visc=1.0)[source]¶
Recompute the volumetric time step on every block.
Uses the JST/Blazek multidimensional definition
dt_vol = 1 / max(lam_conv, lam_diff), wherelam_convsums the convective spectral radiiLambda_d = |V_rel . dA_d| + a*||dA_d||over the three directions andlam_diff = fac_visc * (mu_turb/rho)*sum_d||dA_d||^2/volis the turbulent-diffusion radius over the same faces (set_timestep_spectral()). Building both from the directional face areas makes the stable CFL aspect-ratio-independent (pinned near2*sqrt(2)for the 4-stage RK march) for the viscous limit too.rfis the relaxation factor blending the newdt_volwith the existing buffer asrf*new + (1-rf)*old(passrf=1.0for a fresh recompute). This is the lone writer of each block’s read-onlydt_vol_ndbuffer, so it owns theflags.writeabletoggle.fac_visc(>= 1) multiplies the diffusion radius so the viscous march tolerates the same cfl as the inviscid one;1.0leaves the bare directional radius untouched.- Returns:
self, for method chaining.
- Return type:
- write_emb(filename, compress=False)[source]¶
Write grid to EMB binary format file.
- Parameters:
- Raises:
ValueError – If grid is empty (contains no blocks)
- write_fvbnd(filename, region_id=0)[source]¶
Write boundary conditions in FieldView boundary (.fvbnd) format.
The FVBND format is used by FieldView to specify boundary regions for visualization purposes. See FieldView Reference Manual page 520.
- Parameters:
- Raises:
ValueError – If grid is empty (contains no blocks)
- write_plot3d(p3d_filename, fvbnd_filename=None, flip_k=True, iregion=0)[source]¶
Write grid to Plot3D format with optional FVBND boundary file.
- Parameters:
p3d_filename (
str) – Output filename for Plot3D grid filefvbnd_filename (
str) – Output filename for FVBND boundary file. If None, no boundary file is writtenflip_k (
bool) – Whether to flip the k-axis for proper volume orientation in Pointwise (default True)iregion (
int) – Region number for labeling in FVBND file (default 0)
- Raises:
ValueError – If grid is empty (contains no blocks)
- Return type:
- write_ts3(filename, strict=False)[source]¶
Write grid to TS3 format file.
- Parameters:
- Raises:
ValueError – If grid is empty (contains no blocks)
- property connectivity¶
Get the cached connectivity manager for this grid.
The manager is built once and cached on the grid, so the pairings and communicators it owns are computed lazily and reused. Call
grid.connectivity.clear()to invalidate the cache after changing the grid topology (adding/removing blocks or patches).- Returns:
Connectivity manager providing access to patch connections via patch-type-specific properties and methods.
- Return type:
- property n_row¶
Get the number of blade rows in the grid.
- Returns:
Number of blade rows (groups of blocks connected by periodic patches).
- Return type:
Examples
>>> grid = Grid([block1, block2]) # Single row >>> grid.n_row # Returns 1 >>> >>> grid_multi = Grid([stator_blocks, rotor_blocks]) # Two rows >>> grid_multi.n_row # Returns 2
- property patches¶
Get read-only view of all patches across all blocks.
- Returns:
Read-only collection providing access to all patches from all blocks with patch type filtering (periodic, mixing, inlet, outlet, wall).
- Return type:
- property row_station_bid_pid¶
Per-row upstream/downstream measurement-station patch identifiers.
- Returns:
[(up_idx, dn_idx), ...], one pair per blade row ordered inlet to outlet. Each entry is a list of(bid, pid)patch identifiers: the row’s upstream face (inlet for the first row, otherwise mixing) then its downstream face (outlet for the last row, otherwise mixing). Consumed byget_convergence()andember.convergence_history.ConvergenceHistory.from_grid().- Return type:
- property rows¶
Get blade rows by grouping blocks connected by periodic patches.
Blocks connected by periodic patches are in the same blade row. Multiple rows are separated by mixing patches. Rows are ordered from inlet to outlet based on mixing patch connectivity.
- Returns:
List of blade rows, where each row is a list of Block objects. Blocks within a row are connected by periodic patches. Rows are ordered from inlet (upstream) to outlet (downstream).
- Return type:
List[List[Block]]
Examples
>>> # Single-row grid >>> grid = Grid([block1, block2]) >>> rows = grid.rows # Returns [[block1, block2]] >>> >>> # Multi-row grid with mixing patches >>> grid = Grid([stator_blocks, rotor_blocks]) >>> rows = grid.rows # Returns [[stator_blocks...], [rotor_blocks...]]
- property size¶
Total number of grid points across all blocks.
- Returns:
Sum of all block sizes in the grid
- Return type:
- append(item)¶
Add item to collection.
- Parameters:
item (Any) – Item to add to the collection. The item manages its own label.
- Raises:
ValueError – If item’s label already exists in the collection.
- clear()¶
Remove all items from collection.
- extend(items)¶
Add multiple items to collection.
- Parameters:
items (list) – List of items to add. Each item manages its own label.
- Raises:
ValueError – If any item’s label already exists in the collection.
- index(item, start=0, stop=None)¶
Return index of first occurrence of item.
- insert(index, item)¶
Insert item at specific index.
- Parameters:
index (int) – Index at which to insert the item.
item (Any) – Item to insert. The item manages its own label.
- property labels¶
List of item labels, in order, including
Nonefor unlabelled items.- Returns:
The
labelof each item in the collection. Entries areNonewhere an item has no label.- Return type:
- pop(index=-1)¶
Remove and return item at index (default last).
- Parameters:
index (int, optional) – Index of item to remove and return. Default is -1 (last item).
- Returns:
The removed item.
- Return type:
Any
- remove(item)¶
Remove first occurrence of item.
- Parameters:
item (Any) – Item to remove.
- class ember.grid.GridConnectivity(grid, patch_class)[source]¶
Bases:
objectInitialize connectivity manager for a specific patch type.
- Parameters:
- pair(rtol=1e-06)[source]¶
Pair patches of the specified type, caching the result.
Filters patches to only include instances of self.patch_class, then uses spatial proximity (KDTree in x,r coordinates) to find potential matches and delegates to each patch’s check_match method for validation. The result is cached; call
clear()to recompute after topology changes.- Parameters:
rtol (float, optional) – Relative tolerance for matching (passed to patch check_match methods)
- Returns:
Dictionary where keys are (bid, pid) tuples and values are ((matching_bid, matching_pid), transform) tuples. Both patches in each pair are included as separate keys.
- Return type:
- Raises:
ValueError – If any patch does not have a matching pair
- class ember.grid.GridConnectivityManager(grid)[source]¶
Bases:
objectInitialize connectivity manager for a grid.
- Parameters:
grid (Grid) – The grid containing blocks with patches
- property cusp¶
Get connectivity manager for cusp patches.
- property mixing¶
Get connectivity manager for mixing patches.
- property nonmatch¶
Get connectivity manager for non-matching patches.
- property periodic¶
Get connectivity manager for periodic patches.