Source code for prismo.sources.point

"""
Point sources for FDTD simulations.

This module implements electric and magnetic dipole sources located at a single
point (or small region) within the simulation domain.
"""

from typing import Literal, Optional

from prismo.core.fields import ElectromagneticFields, FieldComponent
from prismo.sources.base import Source
from prismo.sources.waveform import ContinuousWave, GaussianPulse, Waveform


class PointSource(Source):
    """
    Point dipole source for exciting electromagnetic fields.

    Parameters
    ----------
    position : Tuple[float, float, float]
        Physical coordinates of the source (x, y, z) in meters.
    component : str
        Field component to excite ('Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz').
    waveform : Waveform
        Time-dependent waveform for the source.
    name : str, optional
        Name of the source for identification.
    enabled : bool, optional
        Flag to enable/disable the source, default=True.
    """

    def __init__(
        self,
        position: tuple[float, float, float],
        component: FieldComponent,
        waveform: Waveform,
        name: Optional[str] = None,
        enabled: bool = True,
    ):
        # Point sources have zero size
        super().__init__(center=position, size=(0, 0, 0), name=name, enabled=enabled)

        self.component = component
        self.waveform = waveform

    def update_fields(
        self, fields: ElectromagneticFields, time: float, dt: float
    ) -> None:
        """
        Update electromagnetic fields with point source contribution.

        Parameters
        ----------
        fields : ElectromagneticFields
            Electromagnetic fields to update.
        time : float
            Current simulation time in seconds.
        dt : float
            Time step in seconds.
        """
        if not self.enabled:
            return

        # Calculate waveform value at current time
        amplitude = self.waveform(time)

        # Get source region indices
        indices = self._source_region[self.component]

        # Apply source to the specified component
        field_component = fields[self.component]
        field_component[indices] += amplitude


[docs] class ElectricDipole(PointSource): """ Electric dipole source for exciting electromagnetic fields. Parameters ---------- position : Tuple[float, float, float] Physical coordinates of the source (x, y, z) in meters. polarization : Literal["x", "y", "z"] Polarization direction of the dipole. frequency : float Source frequency in Hz. pulse : bool, optional Whether to use a Gaussian pulse (True) or continuous wave (False), default=True. pulse_width : float, optional Width of the Gaussian pulse in seconds, required if pulse=True. amplitude : float, optional Peak amplitude of the source, default=1.0. phase : float, optional Phase offset in radians, default=0.0. name : str, optional Name of the source for identification. enabled : bool, optional Flag to enable/disable the source, default=True. """
[docs] def __init__( self, position: tuple[float, float, float], polarization: Literal["x", "y", "z"], frequency: float, pulse: bool = True, pulse_width: Optional[float] = None, amplitude: float = 1.0, phase: float = 0.0, name: Optional[str] = None, enabled: bool = True, ): # Map polarization to field component component_map = {"x": "Ex", "y": "Ey", "z": "Ez"} component = component_map[polarization.lower()] # Create waveform based on parameters if pulse: if pulse_width is None: raise ValueError("pulse_width must be provided for pulsed sources") waveform = GaussianPulse( frequency=frequency, pulse_width=pulse_width, amplitude=amplitude, phase=phase, ) else: waveform = ContinuousWave( frequency=frequency, amplitude=amplitude, phase=phase ) super().__init__( position=position, component=component, waveform=waveform, name=name, enabled=enabled, )
[docs] class MagneticDipole(PointSource): """ Magnetic dipole source for exciting electromagnetic fields. Parameters ---------- position : Tuple[float, float, float] Physical coordinates of the source (x, y, z) in meters. polarization : Literal["x", "y", "z"] Polarization direction of the dipole. frequency : float Source frequency in Hz. pulse : bool, optional Whether to use a Gaussian pulse (True) or continuous wave (False), default=True. pulse_width : float, optional Width of the Gaussian pulse in seconds, required if pulse=True. amplitude : float, optional Peak amplitude of the source, default=1.0. phase : float, optional Phase offset in radians, default=0.0. name : str, optional Name of the source for identification. enabled : bool, optional Flag to enable/disable the source, default=True. """
[docs] def __init__( self, position: tuple[float, float, float], polarization: Literal["x", "y", "z"], frequency: float, pulse: bool = True, pulse_width: Optional[float] = None, amplitude: float = 1.0, phase: float = 0.0, name: Optional[str] = None, enabled: bool = True, ): # Map polarization to field component component_map = {"x": "Hx", "y": "Hy", "z": "Hz"} component = component_map[polarization.lower()] # Create waveform based on parameters if pulse: if pulse_width is None: raise ValueError("pulse_width must be provided for pulsed sources") waveform = GaussianPulse( frequency=frequency, pulse_width=pulse_width, amplitude=amplitude, phase=phase, ) else: waveform = ContinuousWave( frequency=frequency, amplitude=amplitude, phase=phase ) super().__init__( position=position, component=component, waveform=waveform, name=name, enabled=enabled, )