Block utilities

Operations on Block instances that don’t belong on the class itself.

Each function below takes one or more Blocks and returns a new one (or mutates in place, per its docstring). Grid-level counterparts such as resample() and interp_from_grid() are thin loops over these block-granularity functions.

Combining and reshaping blocks

concatenate(*blocks[, axis])

Concatenate multiple blocks along a specified axis.

repeat_pitchwise(block, n_passage)

Return copies of block, each one blade pitch further round.

resample(block, factors)

Resample 3D block with vectorized interpolation while preserving patch connectivity.

Interface-aligned velocities

Paired functions that rotate the meridional velocity components (Vx, Vr) by a precomputed 2x2 matrix from rotation_matrices(), each the inverse of its partner.

resolve_to_interface(block, rot_to)

Convert meridional velocity to interface-aligned velocities.

resolve_from_interface(block, rot_from)

Convert interface-aligned velocities back to meridional components.

Solution transfer

interp_from_arrays(block, arrays[, crit])

Interpolate a flow field onto block by index-space trilinear interpolation.

interp_from_grid(block, src)

Interpolate the solution on src onto block.

Post-processing and I/O

wall_yplus(block)

y+ on all six wall-adjacent boundary faces of block.

to_tm3(block, filename[, clip_quantile])

Write a triangulated cut to a tm3 binary file.

block_util.concatenate(*blocks, axis=0)[source]

Concatenate multiple blocks along a specified axis.

Parameters:
  • *blocks (Block) – Blocks to concatenate in order

  • axis (int, optional) – Axis along which to concatenate (0, 1, or 2), default 0

Returns:

New block containing concatenated data

Return type:

Block

Raises:

ValueError – If no blocks provided, blocks have incompatible working fluids, incompatible shapes, or patches on concatenation interfaces

block_util.resolve_to_interface(block, rot_to)[source]

Convert meridional velocity to interface-aligned velocities.

Resolves the meridional velocity components \((V_x, V_r)\) to velocities aligned with an interface: velocity through the interface \(V_n\) and velocity in it \(V_s\),

\[\begin{split}V_n &= \cos\chi\, V_x + \sin\chi\, V_r \\ V_s &= -\sin\chi\, V_x + \cos\chi\, V_r\end{split}\]

for the interface angle \(\chi\) that rot_to was built from by rotation_matrices(). Inverse of resolve_from_interface(). Applies the rotation directly to the nondimensional momentum in conserved_nd, the same approach as resolve_to_interface() on a patch’s averaging plane.

Parameters:
  • block (Block) – Block containing velocity data to be resolved.

  • rot_to (Array, shape chi.shape + (2, 2)) – Rotation matrix, the first of the pair returned by rotation_matrices(). Building it once and reusing it across repeated to/from calls at the same angle – as mix_out() does each Newton iteration – avoids re-deriving the same sine and cosine on every call.

Returns:

The input block with momentum updated to interface-aligned form. \(V_n\) becomes the new Vx, \(V_s\) becomes the new Vr, Vt unchanged.

Return type:

Block

block_util.resolve_from_interface(block, rot_from)[source]

Convert interface-aligned velocities back to meridional components.

Converts interface-aligned velocities (\(V_n\) = block.Vx through the interface, \(V_s\) = block.Vr in it) back to meridional components \((V_x, V_r)\),

\[\begin{split}V_x &= \cos\chi\, V_n - \sin\chi\, V_s \\ V_r &= \sin\chi\, V_n + \cos\chi\, V_s\end{split}\]

for the interface angle \(\chi\) that rot_from was built from by rotation_matrices(). Inverse of resolve_to_interface(). Applies the rotation directly to the nondimensional momentum in conserved_nd, the same approach as resolve_from_interface() on a patch’s averaging plane.

Parameters:
  • block (Block) – Block containing interface-aligned velocities (Vn=block.Vx, Vs=block.Vr).

  • rot_from (Array, shape chi.shape + (2, 2)) – Rotation matrix, the second of the pair returned by rotation_matrices().

Returns:

The input block with momentum updated to meridional form.

Return type:

Block

block_util.resample(block, factors)[source]

Resample 3D block with vectorized interpolation while preserving patch connectivity.

Creates a new block by resampling with given factor(s). Critical indices from patch boundaries are preserved to maintain connectivity. Uses scipy.interpn for efficient multi-dimensional interpolation.

Parameters:
  • block (Block) – Block to resample

  • factors (float or tuple of 3 floats) – Resampling factor(s). Values > 1 increase resolution, < 1 decrease resolution. If scalar, same factor applied to all three dimensions.

Returns:

New resampled block with updated patch indices and preserved metadata

Return type:

Block

block_util.STATE = ('P', 'T', 'Vx', 'Vr', 'Vt', 'mu_turb')

Quantities transferred by the interpolation functions below.

Primitives, not the conserved variables. Conserved energy is measured from its fluid’s datum, so copying it between blocks whose fluids differ silently reinterprets it – a datum 600 K apart turns 400 K into 1000 K, with nothing raised. Pressure, temperature and velocity are datum-free and cross unchanged, which is also why interpolating them cannot produce a negative temperature the way interpolating rhoe can.

block_util.interp_from_arrays(block, arrays, crit=None)[source]

Interpolate a flow field onto block by index-space trilinear interpolation.

The caller must have already set the fluid on block.

Parameters:
  • block (Block) – Target block to receive the field.

  • arrays (sequence of Array) – One array per entry of STATE, in that order, all of the same shape and all dimensional. They need not match block’s shape.

  • crit (list of tuple, optional) – One (src, block) pair of critical index arrays per dimension, locations to be held fixed through the mapping. None for a bare field, which maps end to end instead. Built from patches by interp_from_grid(); this function has no notion of a patch.

Raises:

AssertionError – If a different-shape interpolation produces values outside the source’s range – trilinear interpolation must not create new extrema.

block_util.interp_from_grid(block, src)[source]

Interpolate the solution on src onto block.

A thin unpacking of interp_from_arrays(): the state is read off src dimensionally, so the two blocks may carry different fluids – different reference scales, and different entropy and energy datums – without any conversion being needed.

Parameters:
  • block (Block) – Target block to receive the interpolated solution.

  • src (Block) – Source block providing the solution.

block_util.repeat_pitchwise(block, n_passage)[source]

Return copies of block, each one blade pitch further round.

A single passage is all a periodic solver computes, but it is rarely all anyone wants to look at: repeating it shows the flow as a cascade, where the passage-to-passage picture reads at a glance. The copies are successively rotated by pitch, the first left where it is.

Copies rather than one concatenated block, deliberately. Joining them would need to know which index runs pitchwise, which is a property of a grid’s topology and not something to guess; and a consumer that draws blocks — a contour plot, a mesh view — draws a list just as happily, while keeping one colour scale across the set.

Patches are dropped. A rotated copy is a view of the flow, not a member of a connected grid, and its periodic patches would claim connections that no longer hold.

Parameters:
  • block (Block) – Block to repeat. The rotation is pitch, so it follows the blade count: on a block whose Nb was never set, that is the default of one blade, a pitch of a whole revolution, and copies that coincide.

  • n_passage (int) – Number of passages to return, including the original.

Returns:

n_passage blocks, rotated by 0, 1, … pitches.

Return type:

list of Block

Raises:

ValueError – If n_passage is less than one.

block_util.wall_yplus(block)[source]

y+ on all six wall-adjacent boundary faces of block.

Post-processing only – NOT part of the per-step viscous kernel. wall_yplus_field (_fortran/viscous.f90) reuses the exact Re/cf/d that set_visc_force’s own wall function uses (both call the shared wall_core), so this cannot silently drift from what the solver actually modeled at a face; it carries none of set_visc_force’s k-slab/rolling-buffer machinery since it costs O(surface) per call, not O(volume) per step.

The height is the wall-adjacent cell thickness vol/|dA|, i.e. the distance to the first off-wall node – the point whose velocity the wall function actually samples in this cell-vertex scheme, so this is y+ where the closure is evaluated. Cell-centred codes report y+ at the first cell centroid instead, which is the usual “first cell y+” of mesh-sizing guidance; halve these values for that convention, since y+ is linear in wall distance at fixed friction velocity. That is not a conversion for comparing against another code’s number on the same mesh, though: a cell-centred solver samples its velocity at the half height too, so it infers its own friction velocity and does not simply see half of this.

Parameters:

block (Block)

Returns:

Keys yplus_i1, yplus_j1, yplus_k1, yplus_ni, yplus_nj, yplus_nk, each shaped like the corresponding ijk_wall_visc face array, zero on non-wall cells.

Return type:

dict[str, numpy.ndarray]

block_util.to_tm3(block, filename, clip_quantile=0.01, **kwargs)[source]

Write a triangulated cut to a tm3 binary file.

Parameters:
  • block (Block) – Triangulated block with shape (ntri, 3).

  • filename (str or path-like) – Output file path.

  • clip_quantile (float, optional) – Quantile used to clip the property colour range. The range is set to [q, 1-q] percentiles so that extreme outliers do not dominate the colour scale. Default is 0.01 (1%). Set to 0 to use the full min/max range.

  • **kwargs (array_like, shape (ntri, 3)) – Exactly one keyword argument: the key is used as the property name in the file, the value is the per-vertex scalar array.

Raises:

ValueError – If the block is not triangulated, kwargs count != 1, or the value shape does not match the block shape.