Source code for ember.nonmatch

"""Non-matching interface patch for EMBER CFD.

NonMatchPatch connects two block faces that occupy the same physical space
but have different nodal distributions, using parametric interpolation.

See Also
--------
ember.patch.Patch : Base class for all patches
ember.periodic.PeriodicPatch : Matching periodic boundaries
"""

import itertools

import numpy as np

from ember.basepatch import Patch


[docs] class NonMatchPatch(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 :class:`~ember.nonmatch_communicator.NonMatchCommunicator`, which precomputes parametric coordinates at initialisation and interpolates each step. Unlike :class:`~ember.periodic.PeriodicPatch`, which requires identical node distributions, and :class:`~ember.mixing.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. """ _collection_name = "nonmatch" def _get_viable_transforms_nonmatch(self, other_shape): """Get viable permutation/flip combinations for non-matching patches. Non-matching patches allow arbitrary dimension mismatches in the two varying dimensions. Only requires that the constant dimension matches after transformation. Parameters ---------- other_shape : tuple Shape of the other patch to match Returns ------- list List of (perm, flip) tuples that could map patches together """ self_shape = np.array(self.shape) other_shape = np.array(other_shape) transforms = [] # Generate all permutations for perm in itertools.permutations([0, 1, 2]): # For non-matching patches, we don't require dimension sizes to match # We only require that the constant dimension is maintained # (both patches must have same const_dim after transformation) # Get which dimension is constant in self self_const_idx = np.where(self_shape == 1)[0] if len(self_const_idx) != 1: continue # Skip if ambiguous # After permutation, where does self's constant dimension map? const_dim_after_perm = perm.index(self_const_idx[0]) # Check if other also has constant dimension at this position if other_shape[const_dim_after_perm] == 1: # Valid permutation - constant dimensions align # Now generate flipping combinations for non-constant dimensions # Only flip non-unit dimensions flip_dims = [i for i in range(3) if self_shape[i] > 1] # No flipping transforms.append((perm, ())) # Generate all combinations of flipping non-constant dimensions for r in range(1, len(flip_dims) + 1): for flip_subset in itertools.combinations(flip_dims, r=r): transforms.append((perm, flip_subset)) return transforms
[docs] def check_match(self, other, rtol=1e-6): """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 ------- Optional[Tuple[tuple, tuple]] (perm, flip) if patches match, None otherwise """ if not isinstance(other, NonMatchPatch): return None transforms = self._get_viable_transforms_nonmatch(other.shape) for transform in transforms: if self._compare_coords( other, transform, corners_only=True, xr_only=True, rtol=rtol ): return transform return None