Source code for tidy3d.plugins.microwave.impedance_calculator

"""Class for computing characteristic impedance of transmission lines."""

from __future__ import annotations

from typing import Optional, Union

import numpy as np
import pydantic.v1 as pd

from ...components.base import Tidy3dBaseModel
from ...components.data.monitor_data import FieldTimeData
from ...constants import OHM
from ...exceptions import ValidationError
from .custom_path_integrals import CustomCurrentIntegral2D, CustomVoltageIntegral2D
from .path_integrals import (
    AxisAlignedPathIntegral,
    CurrentIntegralAxisAligned,
    IntegralResultTypes,
    MonitorDataTypes,
    VoltageIntegralAxisAligned,
)

VoltageIntegralTypes = Union[VoltageIntegralAxisAligned, CustomVoltageIntegral2D]
CurrentIntegralTypes = Union[CurrentIntegralAxisAligned, CustomCurrentIntegral2D]


[docs] class ImpedanceCalculator(Tidy3dBaseModel): """Tool for computing the characteristic impedance of a transmission line.""" voltage_integral: Optional[VoltageIntegralTypes] = pd.Field( None, title="Voltage Integral", description="Definition of path integral for computing voltage.", ) current_integral: Optional[CurrentIntegralTypes] = pd.Field( None, title="Current Integral", description="Definition of contour integral for computing current.", )
[docs] def compute_impedance(self, em_field: MonitorDataTypes) -> IntegralResultTypes: """Compute impedance for the supplied ``em_field`` using ``voltage_integral`` and ``current_integral``. If only a single integral has been defined, impedance is computed using the total flux in ``em_field``. Parameters ---------- em_field : :class:`.MonitorDataTypes` The electromagnetic field data that will be used for computing the characteristic impedance. Returns ------- :class:`.IntegralResultTypes` Result of impedance computation over remaining dimensions (frequency, time, mode indices). """ AxisAlignedPathIntegral._check_monitor_data_supported(em_field=em_field) # If both voltage and current integrals have been defined then impedance is computed directly if self.voltage_integral: voltage = self.voltage_integral.compute_voltage(em_field) if self.current_integral: current = self.current_integral.compute_current(em_field) # If only one of the integrals has been provided, then the computation falls back to using # total power (flux) with Ohm's law to compute the missing quantity. The input field should # cover an area large enough to render the flux computation accurate. If the input field is # a time signal, then it is real and flux corresponds to the instantaneous power. Otherwise # the input field is in frequency domain, where flux indicates the time-averaged power # 0.5*Re(V*conj(I)). if not self.voltage_integral: flux = em_field.flux if isinstance(em_field, FieldTimeData): voltage = flux / current else: voltage = 2 * flux / np.conj(current) if not self.current_integral: flux = em_field.flux if isinstance(em_field, FieldTimeData): current = flux / voltage else: current = np.conj(2 * flux / voltage) impedance = voltage / current impedance = ImpedanceCalculator._set_data_array_attributes(impedance) return impedance
[docs] @pd.validator("current_integral", always=True) def check_voltage_or_current(cls, val, values): """Raise validation error if both ``voltage_integral`` and ``current_integral`` are not provided.""" if not values.get("voltage_integral") and not val: raise ValidationError( "At least one of 'voltage_integral' or 'current_integral' must be provided." ) return val
@staticmethod def _set_data_array_attributes(data_array: IntegralResultTypes) -> IntegralResultTypes: """Helper to set additional metadata for ``IntegralResultTypes``.""" data_array.name = "Z0" return data_array.assign_attrs(units=OHM, long_name="characteristic impedance")