Source code for tidy3d.components.source.base

"""Defines an abstract base for electromagnetic sources."""

from __future__ import annotations

from abc import ABC
from numbers import Integral
from typing import TYPE_CHECKING, Any

from pydantic import Field, field_validator

from tidy3d.components.base import cached_property
from tidy3d.components.base_sim.source import AbstractSource
from tidy3d.components.geometry.base import Box
from tidy3d.components.types import TYPE_TAG_STR
from tidy3d.components.types.time import SourceTimeType
from tidy3d.components.validators import _assert_min_freq
from tidy3d.components.viz import (
    ARROW_ALPHA,
    ARROW_COLOR_POLARIZATION,
    ARROW_COLOR_SOURCE,
    plot_params_source,
)

if TYPE_CHECKING:
    from typing import Optional

    from tidy3d.components.autograd import AutogradFieldMap
    from tidy3d.components.autograd.derivative_utils import DerivativeInfo
    from tidy3d.components.types import Ax
    from tidy3d.components.viz import PlotParams


[docs] class Source(Box, AbstractSource, ABC): """Abstract base class for all sources. Notes ----- **Practical Advice** **Choosing a Source Type** - ``ModeSource`` — excite a specific waveguide mode. Normalized to inject 1W at the center frequency. Place in a waveguide section with uniform cross-section (or constant bend radius). Typical: waveguides, PICs, couplers. - ``PlaneWave`` — uniform illumination across the full simulation cross-section. Requires periodic or Bloch boundaries in the tangential dimensions. Typical: metasurfaces, thin films, gratings. - ``TFSF`` — localized plane wave that can be placed inside the simulation domain with PML on all sides. Separates total-field (inside) from scattered-field (outside). Typical: nanoparticle scattering, RCS calculations. - ``GaussianBeam`` — focused beam with a finite waist. Typical: fiber coupling, free-space optics. - ``PointDipole`` — single-point current source for emission or LDOS calculations. Typical: Purcell factor, spontaneous emission. """ source_time: SourceTimeType = Field( title="Source Time", description="Specification of the source time-dependence.", discriminator=TYPE_TAG_STR, ) @cached_property def plot_params(self) -> PlotParams: """Default parameters for plotting a Source object.""" return plot_params_source @cached_property def geometry(self) -> Box: """:class:`~tidy3d.Box` representation of source.""" return Box(center=self.center, size=self.size) @cached_property def _injection_axis(self) -> None: """Injection axis of the source.""" return @cached_property def _dir_vector(self) -> None: """Returns a vector indicating the source direction for arrow plotting, if not None.""" return None @cached_property def _pol_vector(self) -> None: """Returns a vector indicating the source polarization for arrow plotting, if not None.""" return None _unsupported_traced_source_fields = ("source_time", "size") def _compute_derivatives(self, derivative_info: DerivativeInfo) -> AutogradFieldMap: """Compute adjoint derivatives for source parameters.""" raise NotImplementedError(f"Can't compute derivative for 'Source': '{type(self)}'.") def _validate_traced_source_path( self, field_path: tuple[Any, ...], *, dataset_key: str, supported_roots: tuple[str, ...], ) -> None: """Validate traced source path against explicitly supported top-level source fields.""" if not field_path: raise ValueError(f"Empty traced source path encountered in '{type(self).__name__}'.") field_root = field_path[0] supported_roots_str = ", ".join(repr(root) for root in supported_roots) if field_root in self._unsupported_traced_source_fields: raise ValueError( f"Automatic differentiation with respect to source field '{field_root}' is not " f"supported for '{type(self).__name__}'. Supported top-level fields are " f"{supported_roots_str}." ) if field_root not in supported_roots: raise ValueError( f"Unsupported traced source path '{field_path}' for '{type(self).__name__}'. " f"Supported top-level fields are {supported_roots_str}." ) if field_root == dataset_key: if len(field_path) < 2: raise ValueError( f"Traced source path '{field_path}' for '{type(self).__name__}' is missing " f"a '{dataset_key}' component key." ) return if field_root == "center": if len(field_path) > 2: raise ValueError( f"Unsupported traced source path '{field_path}' for '{type(self).__name__}'. " "Only full-vector paths or single-axis paths are supported for " f"'{field_root}'." ) if len(field_path) == 2: axis = field_path[1] if not isinstance(axis, Integral) or int(axis) not in (0, 1, 2): raise ValueError( f"Unsupported axis index '{axis}' in traced source path '{field_path}'. " "Axis must be one of 0, 1, 2." ) @field_validator("source_time") @classmethod def _freqs_lower_bound(cls, val: SourceTimeType) -> SourceTimeType: """Raise validation error if central frequency is too low.""" _assert_min_freq(val._freq0_sigma_centroid, msg_start="'source_time' central frequency") return val
[docs] def plot( self, x: Optional[float] = None, y: Optional[float] = None, z: Optional[float] = None, ax: Ax = None, **patch_kwargs: Any, ) -> Ax: """Plot this source.""" kwargs_arrow_base = patch_kwargs.pop("arrow_base", None) # call the `Source.plot()` function first. ax = Box.plot(self, x=x, y=y, z=z, ax=ax, **patch_kwargs) kwargs_alpha = patch_kwargs.get("alpha") arrow_alpha = ARROW_ALPHA if kwargs_alpha is None else kwargs_alpha # then add the arrow based on the propagation direction if self._dir_vector is not None: bend_radius = None bend_axis = None if hasattr(self, "mode_spec") and self.mode_spec.bend_radius is not None: bend_radius = self.mode_spec.bend_radius bend_axis = self._bend_axis sign = 1 if self.direction == "+" else -1 # Curvature has to be reversed because of ploting coordinates if (self.size.index(0), bend_axis) in [(1, 2), (2, 0), (2, 1)]: bend_radius *= -sign else: bend_radius *= sign ax = self._plot_arrow( x=x, y=y, z=z, ax=ax, direction=self._dir_vector, bend_radius=bend_radius, bend_axis=bend_axis, color=ARROW_COLOR_SOURCE, alpha=arrow_alpha, both_dirs=False, arrow_base=kwargs_arrow_base, ) if self._pol_vector is not None: ax = self._plot_arrow( x=x, y=y, z=z, ax=ax, direction=self._pol_vector, color=ARROW_COLOR_POLARIZATION, alpha=arrow_alpha, both_dirs=False, arrow_base=kwargs_arrow_base, ) return ax