Source code for tidy3d.plugins.autograd.invdes.penalties

from __future__ import annotations

from typing import Callable, Optional, Union

import autograd.numpy as np
import pydantic.v1 as pd
from numpy.typing import NDArray

from tidy3d.components.base import Tidy3dBaseModel
from tidy3d.components.types import ArrayFloat2D
from tidy3d.plugins.autograd.types import PaddingType

from .parametrizations import FilterAndProject


[docs] class ErosionDilationPenalty(Tidy3dBaseModel): """A class that computes a penalty for erosion/dilation of a parameter map not being unity.""" radius: Union[float, tuple[float, ...]] = pd.Field( ..., title="Radius", description="The radius of the kernel." ) dl: Union[float, tuple[float, ...]] = pd.Field( ..., title="Grid Spacing", description="The grid spacing." ) size_px: Union[int, tuple[int, ...]] = pd.Field( None, title="Size in Pixels", description="The size of the kernel in pixels." ) beta: pd.NonNegativeFloat = pd.Field( 20.0, title="Beta", description="The beta parameter for the tanh projection." ) eta: pd.NonNegativeFloat = pd.Field( 0.5, title="Eta", description="The eta parameter for the tanh projection." ) filter_type: str = pd.Field( "conic", title="Filter Type", description="The type of filter to create." ) padding: PaddingType = pd.Field( "reflect", title="Padding", description="The padding mode to use." ) delta_eta: float = pd.Field( 0.01, title="Delta Eta", description="The binarization threshold for erosion and dilation operations.", )
[docs] def __call__(self, array: NDArray) -> float: """Compute the erosion/dilation penalty for a given array. Parameters ---------- array : np.ndarray The input array to compute the penalty for. Returns ------- float The computed erosion/dilation penalty. """ filtproj = FilterAndProject( radius=self.radius, dl=self.dl, size_px=self.size_px, beta=self.beta, eta=self.eta, filter_type=self.filter_type, padding=self.padding, ) eta_dilate = 0.0 + self.delta_eta eta_eroded = 1.0 - self.delta_eta def _dilate(arr: NDArray): return filtproj(arr, eta=eta_dilate) def _erode(arr: NDArray): return filtproj(arr, eta=eta_eroded) def _open(arr: NDArray): return _dilate(_erode(arr)) def _close(arr: NDArray): return _erode(_dilate(arr)) diff = _close(array) - _open(array) if not np.any(diff): return 0.0 return np.linalg.norm(diff) / np.sqrt(diff.size)
[docs] def make_erosion_dilation_penalty( radius: Union[float, tuple[float, ...]], dl: Union[float, tuple[float, ...]], *, size_px: Optional[Union[int, tuple[int, ...]]] = None, beta: float = 20.0, eta: float = 0.5, delta_eta: float = 0.01, padding: PaddingType = "reflect", ) -> Callable: """Computes a penalty for erosion/dilation of a parameter map not being unity. See Also -------- :func:`~penalties.ErosionDilationPenalty`. """ return ErosionDilationPenalty( radius=radius, dl=dl, size_px=size_px, beta=beta, eta=eta, delta_eta=delta_eta, padding=padding, )
def curvature(dp: NDArray, ddp: NDArray) -> NDArray: """Calculate the curvature at a point given the first and second derivatives. Parameters ---------- dp : np.ndarray The first derivative at the point, with (x, y) entries in the first dimension. ddp : np.ndarray The second derivative at the point, with (x, y) entries in the first dimension. Returns ------- np.ndarray The curvature at the given point. Notes ----- The curvature can be positive or negative, indicating the direction of curvature. The radius of curvature is defined as 1 / |ฮบ|, where ฮบ is the curvature. """ num = dp[0] * ddp[1] - dp[1] * ddp[0] den = np.power(dp[0] ** 2 + dp[1] ** 2, 1.5) return num / den def bezier_with_grads( t: float, p0: NDArray, pc: NDArray, p2: NDArray ) -> tuple[NDArray, NDArray, NDArray]: """Compute the Bezier curve and its first and second derivatives at a given point. Parameters ---------- t : float The parameter at which to evaluate the Bezier curve. p0 : np.ndarray The first control point of the Bezier curve. pc : np.ndarray The central control point of the Bezier curve. p2 : np.ndarray The last control point of the Bezier curve. Returns ------- tuple[np.ndarray, np.ndarray, np.ndarray] A tuple containing the Bezier curve value, its first derivative, and its second derivative at the given point. """ p1 = 2 * pc - p0 / 2 - p2 / 2 b = (1 - t) ** 2 * (p0 - p1) + p1 + t**2 * (p2 - p1) dbdt = 2 * ((1 - t) * (p1 - p0) + t * (p2 - p1)) dbd2t = 2 * (p0 - 2 * p1 + p2) return b, dbdt, dbd2t def bezier_curvature(x: NDArray, y: NDArray, t: Union[NDArray, float] = 0.5) -> NDArray: """ Calculate the curvature of a Bezier curve at a given parameter t. Parameters ---------- x : np.ndarray The x-coordinates of the control points. y : np.ndarray The y-coordinates of the control points. t : Union[np.ndarray, float] = 0.5 The parameter at which to evaluate the curvature. Returns ------- np.ndarray The curvature of the Bezier curve at the given parameter t. """ p = np.stack((x, y), axis=1) _, dbdt, dbd2t = bezier_with_grads(t, p[:-2], p[1:-1], p[2:]) return curvature(dbdt.T, dbd2t.T)
[docs] def make_curvature_penalty( min_radius: float, alpha: float = 1.0, kappa: float = 10.0, *, eps: float = 1e-6 ) -> Callable: """Create a penalty function based on the curvature of a set of points. Parameters ---------- min_radius : float The minimum radius of curvature. alpha : float = 1.0 Scaling factor for the penalty. kappa : float = 10.0 Exponential factor for the penalty. eps : float = 1e-6 A small value to avoid division by zero. Returns ------- Callable A function that computes the curvature penalty for a given set of points. Notes ----- The penalty function is defined as: .. math:: p(r) = \\frac{\\mathrm{exp}(-\\kappa(r - r_{min}))}{1 + \\mathrm{exp}(-\\kappa(r - r_{min}))} This formula was described by A. Micheals et al. "Leveraging continuous material averaging for inverse electromagnetic design", Optics Express (2018). """ def _curvature_penalty(points: ArrayFloat2D) -> float: xs, ys = np.array(points).T crv = bezier_curvature(xs, ys) curvature_radius = 1 / (np.abs(crv) + eps) arg = kappa * (curvature_radius - min_radius) exp_arg = np.exp(-arg) penalty = alpha * (exp_arg / (1 + exp_arg)) return np.mean(penalty) return _curvature_penalty