Skip to content

Geometry Functions

Every transducer in eSDIva is a mosaic of small flat rectangular patches, because the spatial impulse response has a closed-form (trapezoidal) solution only for a rectangle. Curved and arbitrary surfaces are therefore tessellated into rectangles before any field is computed. Two tilers in esdiva.utilities.surface_subdivision do this; the transducer classes call them for you, but you can call them directly to build a custom aperture.

A patch is only valid in the far field of its own size — w ≪ √(4·l·c/f), with l the patch-to-field distance. That is why finer subdivision (no_sub_*, no_sub_diameter, n_rings) means more patches and a more accurate field: each patch stays small enough for the trapezoidal SIR to hold.

Parametrization: how a surface is described

A surface is a map from a 2-D parameter domain (u, v) to 3-D space, r(u, v) → (x, y, z) in metres. The two tangent vectors ∂r/∂u, ∂r/∂v span the local tangent plane; their cross product is the patch normal (the direction the patch radiates along). Each patch is the flat rectangle in that tangent plane, with physical width wu and height wv — the quantities the SIR kernels consume.

Two natural parametrizations, one per tiler:

Parametrization Domain Best for
Cartesian (u, v) a rectangle in the parameter plane flat, mildly curved, or single-axis surfaces
Radial (r, φ) rings × azimuth any axisymmetric cap z(r) — bowl, dome, aspheric

The choice matters because the grid must not shear: if the two tangents stop being orthogonal, the "rectangles" become parallelograms and the patch area the SIR assumes is wrong. A cartesian grid shears on a doubly-curved cap; a radial grid never does on a surface of revolution.

Tessellation: the two tilers

subdivide_radial — axisymmetric caps

from esdiva.utilities.surface_subdivision import subdivide_radial
import numpy as np

mosaic = subdivide_radial(
    sag=lambda r: -np.sqrt(0.05**2 - r**2) + 0.05,   # z(r) in metres
    radius=0.02,       # aperture radius (m)
    n_rings=20,        # coarse rings, centre → rim
)

Concentric rings, each split into near-square azimuthal patches. For a surface of revolution the meridian tangent (cos, sin, z′) and the azimuth tangent (−sin, cos, 0) are exactly orthogonal, so every patch is a true rectangle, the outer ring is the exact circular rim (no staircase), and Σ wv = 2πr_c per ring gives ~100 % coverage. Refinement here thins the innermost rings (where azimuthal patches would otherwise overlap at the hub) — the opposite of the cartesian tiler. It warns when the meridian normal turns more than max_ring_turn_deg (default 15°) between rings, meaning the flat patches chord too coarsely across the curvature: raise n_rings. This is the engine behind every circular transducer and RadialProfileTransducer.

subdivide_cartesian — flat / mildly curved / single-axis

from esdiva.utilities.surface_subdivision import subdivide_cartesian
import numpy as np

mosaic = subdivide_cartesian(
    surface_fn=lambda u, v: np.array([u, v, 0.0]),   # r(u, v) in metres
    u_range=(-0.01, 0.01), v_range=(-0.006, 0.006),
    n_u=40, n_v=24,
)

A grid in (u, v); each cell becomes a flat rectangle in the tangent plane at its centre. An optional inside_fn masks the aperture shape — cells fully outside are dropped, boundary cells are subdivided border_refine² ways so a curved rim is resolved cleanly. normalize_patch_size=True sets each patch to the parameter-space step (right when u, v are already arc length, so patches come out uniform). It warns when the tangents depart from orthogonal by more than max_shear_deg (default 5°): on a doubly-curved cap the grid shears and the round rim staircases — switch to subdivide_radial.

What both return

A dict describing the mosaic, consumed directly by the SIR kernels and the plotting routines:

Key Meaning
corners the four 3-D vertices of each patch
centers patch centre positions (m)
normals outward unit normal per patch
tangents_u, tangents_v the two in-plane unit tangents
wu, wv physical patch width and height (m)
el_idx which element each patch belongs to
coverage tiled area ÷ true surface area (≈ 1.0 when well resolved)

coverage is the quick sanity check: far from 1.0 means the tessellation is under- or over-covering the surface and the field amplitude will be off.

Choosing a tiler

  • Axisymmetric cap (bowl, dome, lens, any z(r)) → subdivide_radial.
  • Flat piston, linear-array element, single-axis (cylindrical) curve → subdivide_cartesian.
  • Doubly-curved but not a surface of revolution → cartesian with a fine grid, and heed the shear warning; there is no shear-free tiling for a general doubly-curved patch.

See Mono-element Transducers for RadialProfileTransducer, which wraps subdivide_radial around an arbitrary z(r), and the Transducers API for the subdivision parameters exposed on each class.