Skip to content

Size-field composition

Compose multiple per-point edge-length contributions into a single SizeFieldFn suitable for passing to triangulate(size_field=...).

SizeFieldFn

admesh.SizeFieldFn module-attribute

SizeFieldFn = Callable[[np.ndarray], np.ndarray]

compose_size_field

admesh.compose_size_field

compose_size_field(builtins: Iterable[SizeFieldFn], user_contribs: Iterable[SizeFieldFn] = (), combine: Callable[[list[ndarray]], ndarray] = np.minimum.reduce, *, hmin: float | None = None, hmax: float | None = None) -> SizeFieldFn

Return a closure that evaluates the two-phase size field at points.

Parameters:

Name Type Description Default
builtins Iterable[SizeFieldFn]

Phase-1 built-in stages — wrapped faithful-port functions (curvature, medial axis, bathymetry, tide, etc.). Always combined with np.minimum.reduce regardless of combine.

required
user_contribs Iterable[SizeFieldFn]

Phase-2 user-supplied contributions. Each is sanitized against hmin/hmax (NaN, non-positive, out-of-range values clamped with a :class:UserWarning).

()
combine Callable[[list[ndarray]], ndarray]

Phase-2 reduction: takes a list of (N,) arrays, returns (N,). Default np.minimum.reduce (refinement-only).

reduce
hmin float | None

Optional clamp bounds applied to user contributions before Phase 2 combination. None disables clamping on that side.

None
hmax float | None

Optional clamp bounds applied to user contributions before Phase 2 combination. None disables clamping on that side.

None

Returns:

Type Description
Callable[[ndarray], ndarray]

A pure function (N, 2) -> (N,) suitable for passing to :func:admesh.api.triangulate as size_field=.

Source code in src/admesh/size_field.py
def compose_size_field(
    builtins: Iterable[SizeFieldFn],
    user_contribs: Iterable[SizeFieldFn] = (),
    combine: Callable[[list[np.ndarray]], np.ndarray] = np.minimum.reduce,
    *,
    hmin: float | None = None,
    hmax: float | None = None,
) -> SizeFieldFn:
    """Return a closure that evaluates the two-phase size field at points.

    Parameters
    ----------
    builtins
        Phase-1 built-in stages — wrapped faithful-port functions
        (curvature, medial axis, bathymetry, tide, etc.). Always
        combined with ``np.minimum.reduce`` regardless of ``combine``.
    user_contribs
        Phase-2 user-supplied contributions. Each is sanitized against
        ``hmin``/``hmax`` (NaN, non-positive, out-of-range values
        clamped with a :class:`UserWarning`).
    combine
        Phase-2 reduction: takes a list of ``(N,)`` arrays, returns
        ``(N,)``. Default ``np.minimum.reduce`` (refinement-only).
    hmin, hmax
        Optional clamp bounds applied to user contributions before
        Phase 2 combination. ``None`` disables clamping on that side.

    Returns
    -------
    Callable[[np.ndarray], np.ndarray]
        A pure function ``(N, 2) -> (N,)`` suitable for passing to
        :func:`admesh.api.triangulate` as ``size_field=``.
    """
    builtins_t = tuple(builtins)
    user_t = tuple(user_contribs)

    def _composed(pts: np.ndarray) -> np.ndarray:
        pts_arr = np.asarray(pts, dtype=np.float64)
        if pts_arr.ndim != 2 or pts_arr.shape[1] != 2:
            raise ValueError(
                f"size-field input must be (N, 2); got shape {pts_arr.shape}"
            )

        # Phase 1 — always min-stack.
        phase1_inputs = [_evaluate(f, pts_arr) for f in builtins_t]
        if phase1_inputs:
            phase1 = np.minimum.reduce(phase1_inputs)
        else:
            # Empty builtins → no constraint from Phase 1; act as +inf so
            # min-combine with user contribs degenerates to user only.
            phase1 = np.full(pts_arr.shape[0], np.inf, dtype=np.float64)

        if not user_t:
            return phase1

        # Phase 2 — sanitize each user contribution, then combine.
        user_results = [
            _sanitize_user_value(
                f, _evaluate(f, pts_arr), hmin=hmin, hmax=hmax
            )
            for f in user_t
        ]
        return np.asarray(
            combine([phase1, *user_results]), dtype=np.float64
        )

    return _composed