Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Immersed boundaries: SDF-based domain segmentation

The pymrm immersed-boundary tools specify conditions per wall crossing. Unlike domain-wall boundary conditions, there is no natural way to broadcast a value along an immersed wall. For dispersed systems the pattern we usually want is piecewise constant per body: give every particle its own surface or interface condition.

pymrm.segmentation restores that workflow. segment_domain labels the disjoint regions of the signed distance field; crossing_segments tells you which body each crossing bounds; and segment_values / combine_interface_conditions expand per-body data to the per-crossing arrays that apply_ibm / apply_ibm_interface consume. Point data follows the canonical shape (n_crossings, *ns_shape), so per-component values broadcast exactly like they do for wall boundary conditions.

Three immersed bodies

Three well-separated disks in a fluid domain. The SDF is the union (pointwise minimum) of the three disk SDFs.

<Figure size 460x460 with 1 Axes>

Segment the domain

segment_domain(sdf) labels the connected solid regions (sdf < 0) with scipy.ndimage.label. Face connectivity (connectivity=1, the default) matches the staircase boundary of the IBM crossings. wall_contact reports which bodies reach a domain wall — here none do.

n_segments: 3   sizes: [347 351 348]
<Figure size 500x440 with 2 Axes>
touches any domain wall: [False False False]

Per-body Dirichlet condition

Hold each body at its own surface concentration. segment_values maps the {label: value} dictionary onto the crossings; apply_ibm then folds the immersed Dirichlet condition into the diffusion operator. The fluid diffuses between the bodies and the zero-concentration domain walls.

<Figure size 500x440 with 2 Axes>

Multi-dimensional broadcasting: per body and per component

With a component axis the point data has canonical shape (n_crossings, n_c). A per-body value is now a length-n_c vector, and segment_values broadcasts it over the crossings automatically — exactly the broadcasting you get for wall boundary conditions.

per-body / per-component point values: (252, 2)
<Figure size 940x400 with 4 Axes>

Per-body interface conditions (conjugate diffusion)

Give each body its own internal diffusivity D_in and partition coefficient K and solve the conjugate problem across the interface. combine_interface_conditions merges the per-body ic dictionaries into a single per-crossing ic for apply_ibm_interface; segment_field builds the matching cell-centred diffusivity. A left→right gradient (c=0 at x=0, c=1 at x=1) drives the transport.

<Figure size 500x440 with 2 Axes>

Domain-wall patches

Where a body intersects a domain wall, wall_patch returns the body labels on that wall shaped as a full-field coefficient, and wall_values turns a per-body value into a ready {a, b, d} coefficient. This lets a wall-touching body carry the same condition on its wall patch as on its immersed part.

wall contact per body [axis, (lower, upper)]:
[[[ True False]
  [False False]]

 [[False False]
  [False False]]]
patch shape (full-field rank): (1, 81, 1)  labels on x=0 wall: [1]
wall_values shape (BC coefficient): (1, 81, 2)
<Figure size 460x460 with 1 Axes>

Isolated-pocket detection

On the fluid side (region="positive"), a segment that touches no domain wall is an enclosed pocket. With all-Neumann surroundings such a pocket makes the operator singular, so it is worth detecting up front. Here a solid ring encloses one fluid pocket.

fluid segments: 2   isolated pocket labels: [2]
<Figure size 460x460 with 1 Axes>