Source code for tidy3d.plugins.invdes.contours

from __future__ import annotations

from typing import TYPE_CHECKING

import autograd.numpy as np
import numpy as npo

if TYPE_CHECKING:
    from collections.abc import Callable

    from tidy3d.components.types import ArrayFloat1D, ArrayFloat2D

    from .polyslab_set import PolySlabSet


def _consecutive_triplet_penalties(
    vertices: ArrayFloat2D, penalty_fn: Callable[[ArrayFloat2D], float]
) -> ArrayFloat1D:
    """Evaluate a local 3-point penalty on each cyclic vertex window of a closed ring."""
    ring = vertices
    n_ring_vertices = int(ring.shape[0])
    if n_ring_vertices < 3:
        raise ValueError("Curvature penalty requires polygon rings with at least 3 vertices.")

    ring_triplets = np.stack((np.roll(ring, 1, axis=0), ring, np.roll(ring, -1, axis=0)), axis=1)
    penalty_values = [penalty_fn(triplet) for triplet in ring_triplets]
    return np.stack(penalty_values, axis=0)


[docs] def curvature_penalty( polyslab_set: PolySlabSet, penalty_fn: Callable[[ArrayFloat2D], float], *, ignore_boundary_vertices: bool = True, ) -> float: """Aggregate a local 3-point curvature-style penalty across contour rings. The penalty is evaluated on cyclic 3-point windows so masking boundary vertices preserves local adjacency for curvature-based penalties on closed rings. """ weighted_sum = 0.0 total_contributions = 0 for polyslab, frame_mask in zip( polyslab_set.polyslabs, polyslab_set.frame_boundary_vertex_mask ): vertices = np.array(polyslab.vertices) n_ring_vertices = int(vertices.shape[0]) if n_ring_vertices < 3: raise ValueError("Curvature penalty requires polygon rings with at least 3 vertices.") boundary_mask = npo.asarray(frame_mask, dtype=bool).reshape(-1) if boundary_mask.shape[0] != n_ring_vertices: raise ValueError("Boundary mask length must match the number of ring vertices.") triplet_penalties = _consecutive_triplet_penalties(vertices, penalty_fn) if ignore_boundary_vertices: contribution_mask = ~boundary_mask else: contribution_mask = npo.ones(n_ring_vertices, dtype=bool) contribution_count = int(npo.sum(contribution_mask)) if contribution_count == 0: continue weighted_sum += np.sum(triplet_penalties[contribution_mask]) total_contributions += contribution_count if total_contributions == 0: return 0.0 return weighted_sum / float(total_contributions)