Patch

Patch classes for specifying boundary conditions on structured blocks.

Limiting index rules

Patches are defined by specifying which block face or part of a face they are on. Every Patch subclass is constructed as:

PatchType(i=..., j=..., k=..., label=...)

taking one argument for each of the three indexing directions i, j, and k, subject to the following rules:

  • The first point in a direction is indexed 0; negative indices wrap around such that -1 is the last point.

  • Indices are inclusive, so i=(0,-1) spans the entire range of i coordinate.

  • Integer arguments are interpreted as a constant value of that index: i=0 means the patch spans the first i face; j=-1 means the patch spans the last j face. Integer arguments are shorthand for, e.g. i=(0,0).

  • Patches must be 2D subsets of an external face of the block. This implies that at least one constant dimension must be specified with a value 0 or -1.

  • Omitting a direction argument implies the patch should include every point in that direction, and is shorthand for e.g. j=(0, -1).

  • The elements of a direction tuple should be in ascending order after negative indices are wrapped. k=(6,4) is not valid, and neither is k=(-1, -2).

Types of patches

The different types of patches (e.g. periodic, inlet, outlet) are represented by subclasses of the abstract base class Patch, but are all initialised by passing in the limiting indices and an optional label for later debugging. Storing boundary condition information or matching connections between patches is handled by methods on the subclasses.

Storage and attachment to blocks

A patch can be constructed in isolation without a Block. However, any property that depends on block geometry – resolving negative indices to absolute coordinates, computing patch size, or accessing block coordinate views – will raise an error until the patch is attached to a block.

Patches are stored in a BlockPatchCollection accessible at block.patches. Adding a patch to this collection automatically attaches it to the block, validates its limits against the block shape, and checks that it does not spatially overlap with any existing patch of the same type on the same face. The collection supports retrieval by integer index, by string label, and by patch type:

p = block.patches[0]           # by index
p = block.patches["inlet_hub"] # by label
ps = block.patches.inlet       # list of all InletPatch objects

The type-grouped properties (inlet, outlet, periodic, rotating, etc.) each return a plain list.

Mutation is through the standard collection interface: append, extend, insert, and del:

block.patches.append(InletPatch(i=0, label="inlet_hub"))
del block.patches["inlet_hub"]

len() returns the number of patches on the block.

A GridPatchCollection at grid.patches provides a corresponding read-only aggregate view across all blocks in a Grid. It supports integer indexing, slicing, iteration, len(), and the same type-grouped properties (e.g. grid.patches.periodic), but does not support string-key access or any mutation methods.

Examples

# example: patch_examples
import numpy as np
import ember.patch
from ember.block import Block
from ember.fluid import PerfectFluid

# Build a block with axial (x), radial (r), and circumferential (t) coordinates.
fluid = PerfectFluid(cp=1005.0, gamma=1.4, mu=1.8e-5, Pr=0.7)
block = Block(shape=(4, 5, 6))
block.set_fluid(fluid)
block.set_x(np.linspace(0.0, 1.0, 4).reshape(-1, 1, 1) * np.ones((4, 5, 6)))
block.set_r(np.linspace(0.5, 1.0, 5).reshape(1, -1, 1) * np.ones((4, 5, 6)))
block.set_t(np.linspace(0.0, 0.5, 6).reshape(1, 1, -1) * np.ones((4, 5, 6)))

# Add inlet, outlet, and periodic patches; appending attaches each to the block.
inlet  = ember.patch.InletPatch(i=0,  label="inflow")
outlet = ember.patch.OutletPatch(i=-1, label="outflow")
block.patches.extend([inlet, outlet])
block.patches.extend([
    ember.patch.PeriodicPatch(k=0,  label="lower"),
    ember.patch.PeriodicPatch(k=-1, label="upper"),
])

print(len(block.patches))           # 4
print(len(block.patches.periodic))  # 2

# Patch shape and size are resolved against the block once attached.
# shape is (1, 5, 6): one i-plane, full j and k extent.
print(inlet.size)                        # 30
print(inlet.shape)                       # (1, 5, 6)
# const_dim is the index of the constant face (0=i, 1=j, 2=k).
print(inlet.const_dim)                   # 0
print(block.patches["lower"].const_dim)  # 2

# Negative indices are stored as-is and resolved to absolute coordinates on demand.
# i=(1,-2), k=(1,-2) on shape (4,5,6): i=-2 resolves to 2, k=-2 resolves to 4.
partial = ember.patch.InviscidPatch(j=-1, i=(1, -2), k=(1, -2), label="tip_partial")
block.patches.append(partial)
print(partial.ijk_lim_abs[0, 1])  # 2
print(partial.ijk_lim_abs[2, 1])  # 4
print(partial.shape)              # (2, 1, 4)

# Use the patch slice to index block coordinate arrays directly.
print(block[inlet.slice].x.shape)   # (1, 5, 6)
print(block.xrt[inlet.slice].shape) # (1, 5, 6, 3)

# Set inlet stagnation conditions; values can be scalars or arrays
# that broadcast to the patch shape.
inlet.set_Po_To_Alpha_Beta(Po=2e5, To=1200.0, Alpha=0.0, Beta=0.0)
print(inlet.Po)  # 200000.0
print(inlet.To)  # 1200.0

nj, nk = inlet.shape[1], inlet.shape[2]
Po_array = np.linspace(1.8e5, 2.2e5, nj * nk).reshape(1, nj, nk)
inlet.set_Po_To_Alpha_Beta(Po=Po_array)
print(inlet.Po.shape)  # (1, 5, 6)

# Set static pressure on the outlet, or attach a mass-flow PID throttle.
outlet.set_P(1e5)
print(outlet.P)  # 100000.0

outlet.set_throttle(mdot_target=3.0, K_pid=(1.0, 0.1, 0.0))

# Set angular velocity on a rotating wall patch.
rot_patch = ember.patch.RotatingPatch(j=0, label="hub")
block.patches.append(rot_patch)
rot_patch.set_Omega(500.0)
print(rot_patch.rpm)  # 4774.648

Base classes

class ember.basepatch.Patch(i=(0, -1), j=(0, -1), k=(0, -1), label=None)[source]

Bases: ABC

Initialize with start and end indices for each dimension.

Indices are inclusive and a single integer sets a constant value in that dimension. See ember.patch for the full index rules.

Parameters:
  • i (int or tuple) – Start and end indices along the 1st axis.

  • j (int or tuple) – Start and end indices along the 2nd axis.

  • k (int or tuple) – Start and end indices along the 3rd axis.

  • label (str, optional) – String identifier for the patch.

set_i_lim(i)[source]

Set the start and end indices on the i dimension.

Indices are inclusive and a single integer sets a constant value in that dimension. See ember.patch for the full index rules.

Parameters:

i (int or tuple) – Start and end indices along the 1st axis.

set_j_lim(j)[source]

Set the start and end indices on the j dimension.

Indices are inclusive and a single integer is shorthand for a constant face, e.g. j=0 is equivalent to j=(0, 0). See ember.patch for the full index rules.

Parameters:

j (int or tuple) – Start and end indices along the 2nd axis.

set_k_lim(k)[source]

Set the start and end indices on the k dimension.

Indices are inclusive and a single integer is shorthand for a constant face, e.g. k=0 is equivalent to k=(0, 0). See ember.patch for the full index rules.

Parameters:

k (int or tuple) – Start and end indices along the 3rd axis.

set_label(label)[source]

Set patch label.

get_ijk_face(perm=(0, 1, 2), flip=())[source]

Block indices for faces on the patch.

For example the constant k face bounded by (i -> i+1) and (j -> j+1) has indices (i, j, k).

Parameters:
  • perm (tuple of int, optional) – Permutation of the dimensions for the output. Default is (0, 1, 2) which corresponds to (i, j, k).

  • flip (tuple of int, optional) – Dimensions to flip in the output. Default is () which means no flipping.

get_ijk_node(perm=(0, 1, 2), flip=())[source]

Block indices for nodes on the patch.

Parameters:
  • perm (tuple of int, optional) – Permutation of the dimensions for the output. Default is (0, 1, 2) which corresponds to (i, j, k).

  • flip (tuple of int, optional) – Dimensions to flip in the output. Default is () which means no flipping.

attach_to_block(block)[source]

Attach this patch to a block and validate limits against block shape.

Do not call directly; attachment is handled automatically when a patch is added to patches via BlockPatchCollection.

Parameters:

block (Block) – The block this patch belongs to. A weak reference is stored.

check_match(other, rtol=1e-06)[source]

Check if this patch matches another patch for pairing purposes.

Base implementation always returns None. Subclasses should override this method to implement their specific matching criteria.

Parameters:
  • other (Patch) – The other patch to compare with

  • rtol (float, optional) – Relative tolerance for matching

copy()[source]

Return a new unattached patch of the same type with the same limits, label, and boundary condition state.

The returned patch is fully independent: it shares no mutable state with the original and is not attached to any block. Attach it to a block via block.patches.append(copy) before using geometry-dependent properties.

Boundary condition parameters (e.g. stagnation conditions on an inlet, static pressure on an outlet) are copied; any cached solver state that depends on block geometry is not.

property block

Access the parent block this patch is attached to.

property block_view

Sliced view of the parent block at this patch location; Block with shape shape.

Equivalent to block[patch.slice]. Cached at attach_to_block() to avoid repeated sliced Block creation overhead.

property block_view_offset_1

Sliced view one layer interior to the patch face; Block with shape shape.

Used to read the outgoing characteristic state (e.g. entropy at a subsonic outlet) from the first interior layer. Equivalent to block[patch.slice] offset by one along the constant dimension. Cached at attach_to_block() to avoid repeated sliced Block creation overhead.

property block_view_offset_2

Sliced view two layers interior to the patch face; Block with shape shape.

Used together with block_view_offset_1 to linearly (two-point) extrapolate the outgoing characteristic state to the boundary face, X_face = 2 * X_1 - X_2. Equivalent to block[patch.slice] offset by two along the constant dimension. Cached at attach_to_block() to avoid repeated sliced Block creation overhead.

property const_dim

Axis of the constant dimension; int in {0, 1, 2}.

property ien

End index in the i dimension; int.

property ijk_lim_abs

Limits with negative indices resolved to positive; ndarray of shape (3, 2).

property ist

Start index in the i dimension; int.

property jen

End index in the j dimension; int.

property jst

Start index in the j dimension; int.

property ken

End index in the k dimension; int.

property kst

Start index in the k dimension; int.

property label

String identifier for the patch; str or None.

property shape

Extent of the patch in each dimension as (ni, nj, nk); the constant dimension is always 1.

property size

Number of nodes on the patch; int, equal to the product of shape.

property slice

tuple of slice objects for indexing the parent block array.

property xrt_centre

Centre coordinates of the patch as (x, r, t); ndarray of shape (3,).

class ember.basepatch.RevolutionPatch(i=(0, -1), j=(0, -1), k=(0, -1), label=None)[source]

Bases: Patch

Patch on a surface of revolution.

Intermediate base class for patches that require surface-of-revolution geometry (inlet, outlet, mixing). A surface of revolution is an annular or axisymmetric surface where one patch axis is purely circumferential (the pitch direction, along which only theta varies) and the other runs meridionally from hub to tip (the span direction, along which both x and r vary).

When a patch is added to a block via patches, attach_to_block() automatically identifies the span and pitch axes from the block geometry. It raises ValueError if the geometry is not a surface of revolution (i.e. a pitch axis with constant x and r cannot be found).

The key operation provided by this class is pitch-averaging: computing a circumferentially averaged flow state that subclasses (inlet, outlet, mixing) use to apply boundary conditions.

set_block_avg()[source]

Compute pitch-averaged conserved variables and store in block_avg.

Uses node-based pitch weights to compute a weighted sum of block_view.conserved_nd over the pitch dimension, writing the result directly into self.block_avg.conserved_nd.

attach_to_block(block)[source]

Attach to block and detect surface-of-revolution geometry.

Calls the base Patch attach, then determines span/pitch dimensions and computes meridional properties. Raises ValueError if the patch is not a surface of revolution.

resolve_from_interface()[source]

Rotate block_view momentum in-place from (norm, span) to (x, r) coordinates.

Inverse of resolve_to_interface:

rhoV_norm -> rhoVx = cosxi * rhoV_norm - sinxi * rhoV_span
rhoV_span -> rhoVr = sinxi * rhoV_norm + cosxi * rhoV_span
resolve_to_interface()[source]

Rotate block_view momentum in-place from (x, r) to (norm, span) coordinates.

Modifies block_view.conserved so that the axial and radial momentum components become the interface-normal and interface-span components:

rhoVx -> rhoV_norm =  cosxi * rhoVx + sinxi * rhoVr
rhoVr -> rhoV_span = -sinxi * rhoVx + cosxi * rhoVr

Uses the pre-computed rotation matrix _rot_to broadcast along span_dim to match the full block shape.

smooth_pitch_121(field, alpha)[source]

Apply a periodic 1-2-1 smoothing pass along the pitch axis.

Returns alpha * smoothed + (1 - alpha) * field where smoothed is one pass of the discrete 1-2-1 filter f[i] = (f[i-1] + 2*f[i] + f[i+1]) / 4 with periodic wrap along pitch_dim. The pitch direction is circumferential, so periodic wrap is exact for an annular passage.

The 1-2-1 filter has amplification \(\cos^2(k\Delta/2)\): it preserves the pitch mean and smooth variation, and annihilates the Nyquist (sawtooth) mode. Blending with the unsmoothed field by alpha tunes the strength: alpha=1 is a full 1-2-1 pass, alpha=0 leaves the field unchanged.

Parameters:
  • field (ndarray) – Field to smooth; any shape with axis pitch_dim.

  • alpha (float) – Blend factor in [0, 1]. 0 disables, 1 is a full pass.

Returns:

Smoothed field, same shape and dtype as field.

Return type:

ndarray

property block_avg

Pitch-averaged flow field; Block of shape (nspan,).

Coordinates are the pitch-mean x, r, t at each span station. The conserved variables are populated by calling set_block_avg(); before that call the flow-field arrays contain uninitialised values.

property pitch_dim

Axis of the pitchwise (circumferential) dimension; int in {0, 1, 2}.

Detected automatically from block geometry on attach_to_block(): the axis along which only theta varies while x and r remain constant.

property span_dim

Axis of the spanwise (meridional) dimension; int in {0, 1, 2}.

Detected automatically from block geometry on attach_to_block(): the axis along which x and r vary (hub to tip).

property spf

Span fraction at each node, normalised to [0, 1] by meridional arc-length; ndarray of shape (nspan,).

spf[0] == 0.0 at the hub/start corner and spf[-1] == 1.0 at the tip/end corner. Spacing reflects the actual meridional distances between nodes, not their indices.

property weight_pitch

Pitch weights per node as a fraction of block.pitch; ndarray broadcastable against block_view.

Weights sum to 1 along pitch_dim, so a pitch-averaged scalar field is (field * patch.weight_pitch).sum(axis=patch.pitch_dim).

Concrete patch types

class ember.periodic.PeriodicPatch(i=(0, -1), j=(0, -1), k=(0, -1), label=None)[source]

Bases: Patch

Connected boundary to another block.

This applies to patches which are exactly coincident, and patches which are periodic in the circumferential direction of an annular domain. The patches should come in pairs, and connectivity between the two patches is detected automatically. The conserved variables on the two patches are averaged at each time step to enforce periodicity.

check_match(other, rtol=1e-06)[source]

Check if this PeriodicPatch matches another for pairing purposes.

PeriodicPatch matching requires all x, r, t coordinates to match within tolerance, accounting for periodicity in theta and allowing for permutations and flips.

Parameters:
  • other (Patch) – The other patch to compare with

  • rtol (float, optional) – Relative tolerance for matching

Returns:

(perm, flip) if patches match, None otherwise

Return type:

Optional[Tuple[tuple, tuple]]

class ember.inlet.InletPatch(i=(0, -1), j=(0, -1), k=(0, -1), label=None)[source]

Bases: RevolutionPatch

Inflow boundary condition.

Enforces prescribed stagnation pressure \(p_0\), stagnation temperature \(T_0\), yaw angle \(\alpha\), and pitch angle \(\beta\) at the face. Stagnation enthalpy and entropy are derived from \(p_0\) and \(T_0\) and imposed directly. Flow direction is set from \(\alpha\) and \(\beta\).

Static pressure is not imposed directly. Each call to apply() relaxes \(p_\mathrm{soln}\) toward the static pressure at the first interior node, then clamps it below the stagnation pressure. This damps acoustic transients at startup without prescribing a fixed pressure. Call update_soln() once per timestep (before the Runge-Kutta stages) to advance \(p_\mathrm{soln}\).

All four boundary condition values must be set via set_Po_To_Alpha_Beta() before apply() is called.

set_Po_To_Alpha_Beta(Po=None, To=None, Alpha=None, Beta=None)[source]

Set inlet boundary condition values.

Each argument is independent; omitted arguments retain their current value. All four must be set before apply() can be called. Stagnation pressure and stagnation temperature must be positive and finite. Yaw and pitch angles are in degrees. Each value accepts a scalar or an array that broadcasts to shape.

Parameters:
  • Po (float or array, optional) – Prescribed stagnation pressure [Pa].

  • To (float or array, optional) – Prescribed stagnation temperature [K].

  • Alpha (float or array, optional) – Prescribed inflow yaw angle [deg].

  • Beta (float or array, optional) – Prescribed inflow pitch angle [deg].

apply()[source]

Impose inlet boundary conditions on the patch.

Stagnation enthalpy, entropy, and trigonometric flow-direction factors derived from Po, To, Alpha, and Beta are cached on the first call and combined with a relaxed static pressure to reconstruct the velocity vector, which is stored via set_rho_u_Vxrt_nd().

Static pressure is not prescribed; instead it is relaxed from the previous-step solution toward the static pressure at the first interior node (a Multall-style pressure extrapolation):

\[p_\mathrm{new} = p_\mathrm{soln} + rf\,(p_\mathrm{interior} - p_\mathrm{soln})\]

using the relaxation factor rf, then clamped below the stagnation pressure Po. The solution reference is advanced by calling update_soln() once per timestep. rf is used directly as a convex weight (no Mach scaling); higher values converge faster but may excite acoustics.

Raises:

ValueError – If the incoming solution has a negative axial velocity at any inlet boundary node (backflow), the calculation is ill-posed and is stopped rather than continued into an unphysical state.

update_soln()[source]

Update \(p_\mathrm{soln}\) from the current inlet-face pressure.

Should be called once per timestep before the Runge-Kutta stages so that each stage’s relaxation in apply() is anchored to the start-of-step pressure rather than drifting across stages.

property Alpha

Prescribed inflow yaw angle \(\alpha\) [deg]; broadcasts to shape. See Alpha.

property Beta

Prescribed inflow pitch angle \(\beta\) [deg]; broadcasts to shape. See Beta.

property Po

Prescribed inflow stagnation pressure \(p_0\) [Pa]; broadcasts to shape. See Po.

property To

Prescribed inflow stagnation temperature \(T_0\) [K]; broadcasts to shape. See To.

class ember.outlet.OutletPatch(i=(0, -1), j=(0, -1), k=(0, -1), label=None)[source]

Bases: RevolutionPatch

Outflow boundary condition.

Enforces a prescribed static pressure \(p\) at the face. Entropy and all three velocity components are linearly (two-point) extrapolated from the first two interior layers, X_face = 2 * X_1 - X_2, so the outgoing entropy and vorticity characteristics are carried to the boundary with their interior gradient.

Three optional features extend the constant uniform pressure condition:

  • Throttle set_throttle(): a PID controller adjusts the pressure each step to drive the patch mass flow toward a target value.

  • Spanwise adjustment set_adjustment(): adds zero-mean radial equilibrium and dynamic head profiles to the uniform pressure target.

  • Backflow handling set_backflow(): when reversed flow is detected, affected nodes are treated as inlets; see InletPatch for the density relaxation scheme.

Static pressure must be set via set_P() before apply() is called.

set_adjustment(K_dyn=0.0, radial_equilibrium=True, rf=0.1)[source]

Configure nonuniform adjustments to outlet pressure.

The pressure at each node is:

\[p = p_\mathrm{out} + \Delta p_\mathrm{throttle} + \Delta p_\mathrm{nonunif}\]

\(\Delta p_\mathrm{nonunif} = \Delta p_\mathrm{dyn} + \Delta p_\mathrm{swirl}\) is computed from the boundary solution each step and the total nonuniformity is relaxed toward the new value:

\[\Delta p_\mathrm{nonunif}^\mathrm{new} = \mathit{rf} \, \Delta p_\mathrm{nonunif} + (1 - \mathit{rf}) \, \Delta p_\mathrm{nonunif}^\mathrm{old}\]

\(\overline{\Box}\) denotes the area-weighted mean. When radial_equilibrium=True it is the pitch mean at each spanwise location, preserving spanwise variation; when radial_equilibrium=False it is the annulus mean.

Dynamic head offset \(\Delta p_\mathrm{dyn}\):

Mimics a physical gauze or screen to suppress backflow by driving the exit velocity toward a more uniform profile. \(\tfrac{1}{2}\rho V_m^2\) is the meridional dynamic head. The offset has zero net area average by construction:

\[\Delta p_\mathrm{dyn} = K_\mathrm{dyn} \left(\tfrac{1}{2}\rho V_m^2 - \overline{\tfrac{1}{2}\rho V_m^2}\right)\]

Swirl offset \(\Delta p_\mathrm{swirl}\) (radial_equilibrium=True):

Swirling flow exiting the domain carries a centrifugal pressure gradient. Enforcing a uniform pressure in opposition to this gradient would induce unnatural streamline curvature; instead the boundary pressure is adjusted to satisfy radial equilibrium. Pitch-averaged flow quantities are taken from the first interior layer (the offset-1 slice), the same source the outlet uses to extrapolate the boundary state:

\[\frac{dp_\mathrm{swirl}}{dr} = \frac{\overline{\rho V_\theta}\;\overline{V_\theta}}{r}\]

where \(\overline{\Box}\) is the pitch mean. This product-of-means form matches the Multall EXBCONDS radial-equilibrium treatment. Integrated from hub to tip, then broadcast uniformly in the pitch direction. The integration starts from zero at the hub, so the profile is zero at the hub, i.e. \(p_\mathrm{out}\) is enforced at the hub.

Parameters:
  • K_dyn (float, optional) – Scale factor for the dynamic head offset. Default 0 (off).

  • radial_equilibrium (bool, optional) – Include the radial equilibrium offset and use pitch-area-averaging for the dynamic head offset. Default True.

  • rf (float, optional) – Relaxation factor applied to the combined offset each step. Default 0.1.

set_backflow(ho, s, Vr, Vt)[source]

Enable reversed-flow handling at the outlet.

Once enabled, apply() detects cells with reversed axial flow and treats them as inlets: stagnation enthalpy, entropy, and transverse velocities are imposed from the values given here, and density is relaxed from the interior — exactly as in InletPatch. If reversed flow is detected without this method having been called, apply() raises a RuntimeError.

All four arguments must be scalars.

Parameters:
  • ho (float) – Stagnation enthalpy [J/kg].

  • s (float) – Specific entropy [J/(kg K)].

  • Vr (float) – Radial velocity [m/s].

  • Vt (float) – Tangential velocity [m/s].

set_P(P)[source]

Set the prescribed outlet static pressure \(p_\mathrm{out}\).

Accepts a scalar or an array that broadcasts to shape. Must be positive and finite. Non-scalar values are incompatible with set_throttle() and set_adjustment().

Parameters:

P (float or array) – Prescribed static pressure \(p_\mathrm{out}\) [Pa].

set_throttle(mdot_target, K_pid)[source]

Set the mass flow target and PID gains for throttle control.

Each time step update_target() computes a pressure correction \(\Delta p_\mathrm{throttle}\) and adds it to \(p_\mathrm{out}\):

\[\Delta p_\mathrm{throttle} = K_p\, \varepsilon + K_i \int \varepsilon\, d\tau + K_d \frac{d\varepsilon}{d\tau}, \qquad \varepsilon = \dot{m} - \dot{m}_\mathrm{target}\]

where \(\tau\) is pseudo-time, advanced each step by \(d\tau = \mathrm{cfl} \cdot L_\mathrm{ref} / V_\mathrm{ref}\).

Incompatible with non-scalar P.

Parameters:
  • mdot_target (float) – Target mass flow rate \(\dot{m}_\mathrm{target}\) [kg/s]. Must be positive and finite.

  • K_pid (tuple of float) – PID gains \((K_p, K_i, K_d)\) with units [Pa/(kg/s)], [Pa/kg], [Pa·s/kg].

get_throttle_stats()[source]

Return a dict of throttle state for convergence logging.

Keys

mdot_targetfloat

Mass flow setpoint [kg/s]; zero when throttle is inactive.

mdot_throttlefloat

Mass flow measured at the patch on the last call to update_target() [kg/s].

P_throttlefloat

Total PID pressure correction \(\Delta p_\mathrm{throttle}\) [Pa].

dP_Pfloat

Proportional contribution [Pa].

dP_Ifloat

Integral contribution [Pa].

dP_Dfloat

Derivative contribution [Pa].

apply(rf=1.0)[source]

Impose outlet boundary conditions on the patch.

Forward-flow cells have static pressure imposed, with entropy and all three velocity components linearly extrapolated from the first two interior layers (X_face = 2 * X_1 - X_2) and stored via set_rho_u_Vxrt_nd(). If backflow handling has been enabled via set_backflow(), reversed-flow cells are treated as inlets; see InletPatch. If reversed flow is detected without set_backflow() having been called, a RuntimeError is raised.

Parameters:

rf (float) – Relaxation factor for the reversed-cell density update.

update_soln()[source]

Update \(\rho_\mathrm{soln}\) from the current interior density.

Should be called once per timestep before the Runge-Kutta stages so that density relaxation in reversed-flow cells during apply() is anchored to the start-of-step density.

update_target(cfl=None)[source]

Recompute the pressure target for the current timestep.

Runs the throttle PID (if active) and applies the spanwise adjustment (if configured). Should be called once per outer timestep before the Runge-Kutta stages.

Parameters:

cfl (float, optional) – CFL number controlling the pseudo-time step \(d\tau = \mathrm{cfl} \cdot L_\mathrm{ref} / V_\mathrm{ref}\), where \(L_\mathrm{ref}\) is L_ref and \(V_\mathrm{ref}\) is the fluid reference velocity. Defaults to the internal _cfl_pid value if not provided.

property K_pid

PID gains (Kp, Ki, Kd) for throttle control; set via set_throttle().

property mdot_target

Mass flow target [kg/s] for throttle control; zero when throttle is inactive.

property P

Prescribed outlet static pressure \(p_\mathrm{out}\) [Pa]. See P.

class ember.mixing.MixingPatch(i=(0, -1), j=(0, -1), k=(0, -1), label=None)[source]

Bases: RevolutionPatch

Circumferentially averaged connection between blade rows.

Connects two block faces at a mixing plane by exchanging pitch-averaged flow state. Inflow nodes are treated as in InletPatch; outflow nodes as in OutletPatch. Circumferential variations on each side are permitted; only the pitch average crosses the interface.

Both sides must have the same spanwise resolution. Pitchwise resolution may differ.

set_flux_avg()[source]

Compute pitch-averaged node fluxes and store in flux_avg_nd.

Called by MixingCommunicator before reading flux_avg_nd to form the cross-plane flux difference. Must be called with the block in interface coordinates so that Vx is the face-normal velocity and Vr is the spanwise velocity.

set_target(target=None)[source]

Set the pitch-uniform inout target from the current block state or an explicit array.

MixingCommunicator calls this to write back the updated cross-plane target after each exchange. If target is omitted, the pitch mean of the current block state is used to initialise the target before the first exchange.

Parameters:

target (array of shape (nspan, 5), optional) – Nondimensional [ho, s, Vr, Vt, P] target values.

get_target()[source]

Return the inout target as a nondimensional (nspan, 5) array of [ho, s, Vr, Vt, P].

Read by MixingCommunicator during the exchange to form the average target across the mixing plane.

apply()[source]

Impose mixing-plane boundary conditions on the patch.

Called each Runge-Kutta stage after MixingCommunicator has updated the target via set_target(). Inflow nodes are treated as in InletPatch: stagnation enthalpy, entropy, and transverse velocities are imposed from the target and static pressure is relaxed toward a linear extrapolation of the first two interior layers using the relaxation factor rf (used directly as the convex weight toward the first interior layer, no Mach scaling). Outflow nodes are treated as in OutletPatch: target static pressure is imposed and entropy plus all three velocity components are linearly extrapolated from the first two interior layers.

attach_to_block(block)[source]

Attach to block and allocate internal storage.

Safe to call repeatedly; an existing _target of the correct shape is preserved so targets set before re-attachment are not lost. _target itself is left unallocated (None) until first used – set_target() and get_target() seed it lazily from the current block state – so a fresh attach never masks the “nothing has set a real target yet” state behind a zero-filled array.

check_match(other, rtol=1e-05)[source]

Check if this MixingPatch matches another for pairing purposes.

MixingPatch matching requires x,r coordinates to match at all spanwise nodes (pitch-averaged), ignoring theta. Patches must have the same spanwise resolution but can have different pitchwise resolutions.

Parameters:
  • other (Patch) – The other patch to compare with

  • rtol (float, optional) – Relative tolerance for matching

Returns:

None if patches do not match. False if they match with no spanwise flip needed. True if they match but other’s span must be reversed. Always test with is not None; do not use as a bare truthiness check since False is a valid match result.

Return type:

bool or None

update_soln()[source]

Update \(p_\mathrm{soln}\) from the current face static pressure.

Should be called once per timestep before the Runge-Kutta stages so that the inflow pressure relaxation in apply() is anchored to the start-of-step pressure; see InletPatch.

property flux_avg_nd

Pitch-averaged flux array, shape (nspan, 5); populated by set_flux_avg() and read by MixingCommunicator to form the cross-plane flux difference.

class ember.nonmatch.NonMatchPatch(i=(0, -1), j=(0, -1), k=(0, -1), label=None)[source]

Bases: Patch

Non-matching interface between block faces with different node counts.

Connects two block faces that occupy the same physical space but have different nodal distributions. Conserved variables are transferred via bilinear parametric interpolation by NonMatchCommunicator, which precomputes parametric coordinates at initialisation and interpolates each step.

Unlike PeriodicPatch, which requires identical node distributions, and MixingPatch, which exchanges only the pitch average, NonMatchPatch transfers the full pointwise solution across the interface.

Corner x-r coordinates must match between paired faces. Pitchwise and spanwise node counts may differ freely.

check_match(other, rtol=1e-06)[source]

Check if this NonMatchPatch matches another for pairing purposes.

NonMatchPatch matching requires only x,r coordinates to match at corners, allowing for different nodal distributions in the varying dimensions. Theta coordinates are ignored to allow for circumferential mismatch.

Parameters:
  • other (Patch) – The other patch to compare with

  • rtol (float, optional) – Relative tolerance for matching

Returns:

(perm, flip) if patches match, None otherwise

Return type:

Optional[Tuple[tuple, tuple]]

class ember.rotating.RotatingPatch(i=(0, -1), j=(0, -1), k=(0, -1), label=None)[source]

Bases: Patch

Rotating wall boundary with a prescribed angular velocity.

Overrides the wall angular velocity on the patch face used by the Fortran flux routines to compute wall-relative velocities. Faces without a RotatingPatch default to the block angular velocity Omega.

Angular velocity must be set via set_Omega() or set_rpm() before the solver runs.

set_Omega(Omega)[source]

Set the wall angular velocity.

Parameters:

Omega (float) – Angular velocity [rad/s]. Must be a scalar.

set_rpm(rpm)[source]

Set the wall angular velocity from revolutions per minute.

Converts via \(\Omega = \mathrm{rpm} \cdot 2\pi / 60\).

Parameters:

rpm (float) – Rotational speed [rev/min].

property Omega

Angular velocity [rad/s].

property rpm

Angular velocity in revolutions per minute [rpm].

class ember.cooling.CoolingPatch(i=(0, -1), j=(0, -1), k=(0, -1), label=None)[source]

Bases: Patch

Cooling flow through a boundary.

set_cool(type=None, mass=None, pstag=None, tstag=None, sangle=None, xangle=None, mach=None, angle_def=None)[source]

Set cooling parameters.

class ember.inviscid.InviscidPatch(i=(0, -1), j=(0, -1), k=(0, -1), label=None)[source]

Bases: Patch

Frictionless slip-wall boundary.

Marks a face as impermeable but frictionless. The patch is included in the slip collection, which causes the Fortran flux routines to apply a slip (zero normal-velocity) condition without viscous stress. Use this instead of a no-slip wall when viscous effects on that face should be suppressed, for example on a symmetry plane or an inviscid endwall.

class ember.cusp.CuspPatch(i=(0, -1), j=(0, -1), k=(0, -1), label=None)[source]

Bases: Patch

Cusp boundary at a modelled trailing edge.

Marks the faces either side of a zero-thickness trailing edge where two block faces meet at a cusp. Each solver step the conserved variables at the cusp nodes (the axial range covered by the patch) are averaged across the two faces so that the solution remains continuous at the trailing edge.

Must be on a constant-k face and must span the full j extent of the block. Paired with the corresponding face on the other side of the trailing edge via check_match().

attach_to_block(block)[source]

Attach to block and validate cusp patch constraints.

check_match(other, rtol=1e-06)[source]

Check if this CuspPatch matches another for pairing purposes.

CuspPatch matching requires x and r coordinates to match within tolerance, but allows theta to differ.

Parameters:
  • other (Patch) – The other patch to compare with

  • rtol (float, optional) – Relative tolerance for matching

Returns:

(perm, flip) if patches match, None otherwise

Return type:

Optional[Tuple[tuple, tuple]]

class ember.probe.ProbePatch(i=(0, -1), j=(0, -1), k=(0, -1), label=None)[source]

Bases: Patch

Passive flow-sampling probe (Turbostream 3 kind 8).

Records flow history at a point, face, or interior plane. Unlike boundary patches it does not affect wall/slip treatment of the sampled face – it is deliberately excluded from PERMEABLE_TYPES/SLIP_TYPES. The two class flags below relax the generic patch rules so a probe can be placed on an interior constant plane and may coincide with another patch.

Collections

class ember.collections.BlockPatchCollection(block)[source]

Bases: _LabelledList

Initialise with a reference to the parent block.

Parameters:

block (Block) – The block whose boundary conditions this collection manages.

append(patch)[source]

Attach patch to the block, validate limits and overlaps, then add.

extend(patches)[source]

Attach each patch to the block, validate limits and overlaps, then add all.

insert(index, patch)[source]

Attach patch to the block, validate limits and overlaps, then insert at index.

property cooling

All CoolingPatch objects.

property cusp

All CuspPatch objects.

property inlet

All InletPatch objects.

property inviscid

All InviscidPatch objects.

property mixing

All MixingPatch objects.

property outlet

All OutletPatch objects.

property periodic

All PeriodicPatch objects.

property permeable

Patches through which flow passes (non-wall faces).

Includes InletPatch, OutletPatch, PeriodicPatch, MixingPatch, NonMatchPatch, and CuspPatch. Used to identify which boundary faces are not solid walls when computing block boundary fluxes.

property probe

All ProbePatch objects.

property rotating

All RotatingPatch objects.

property slip

Patches that impose no friction (permeable faces and inviscid walls).

Union of permeable and InviscidPatch. Used when applying viscous wall functions: faces in this set are treated as frictionless so no friction is applied at those boundaries.

clear()

Remove all items from collection.

index(item, start=0, stop=None)

Return index of first occurrence of item.

Parameters:
  • item (Any) – Item to find.

  • start (int, optional) – Start index for search.

  • stop (int, optional) – Stop index for search.

Returns:

Index of the item.

Return type:

int

property labels

List of item labels, in order, including None for unlabelled items.

Returns:

The label of each item in the collection. Entries are None where an item has no label.

Return type:

list

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.collections.GridPatchCollection(grid)[source]

Bases: object

Initialise with a reference to the parent grid.

Parameters:

grid (Grid) – The grid whose block patch collections this view aggregates.

property cooling

Return all cooling patches from all blocks.

property inlet

Return all inlet patches from all blocks.

property mixing

Return all mixing patches from all blocks.

property outlet

Return all outlet patches from all blocks.

property periodic

Return all periodic patches from all blocks.

property probe

Return all probe patches from all blocks.

property rotating

Return all rotating patches from all blocks.