Source code for tidy3d.components.tcad.data.monitor_data.charge

"""Monitor level data, store the DataArrays associated with a single heat-charge monitor."""

from __future__ import annotations

from typing import Dict, Union

import pydantic.v1 as pd

from tidy3d.components.base import skip_if_fields_missing
from tidy3d.components.data.data_array import (
    DataArray,
    IndexedVoltageDataArray,
    SpatialDataArray,
    SteadyVoltageDataArray,
)
from tidy3d.components.data.utils import TetrahedralGridDataset, TriangularGridDataset
from tidy3d.components.tcad.data.monitor_data.abstract import HeatChargeMonitorData
from tidy3d.components.tcad.monitors.charge import (
    SteadyCapacitanceMonitor,
    SteadyFreeCarrierMonitor,
    SteadyPotentialMonitor,
)
from tidy3d.components.types import TYPE_TAG_STR, annotate_type
from tidy3d.log import log

FieldDataset = Union[
    SpatialDataArray, annotate_type(Union[TriangularGridDataset, TetrahedralGridDataset])
]

UnstructuredFieldType = Union[TriangularGridDataset, TetrahedralGridDataset]


[docs] class SteadyPotentialData(HeatChargeMonitorData): """Stores electric potential :math:`\\psi` from a charge simulation.""" monitor: SteadyPotentialMonitor = pd.Field( ..., title="Electric potential monitor", description="Electric potential monitor associated with a `charge` simulation.", ) potential: FieldDataset = pd.Field( None, title="Electric potential series", description="Contains the electric potential series.", ) @property def field_components(self) -> Dict[str, DataArray]: """Maps the field components to their associated data.""" return dict(potential=self.potential)
[docs] @pd.validator("potential", always=True) @skip_if_fields_missing(["monitor"]) def warn_no_data(cls, val, values): """Warn if no data provided.""" mnt = values.get("monitor") if val is None: log.warning( f"No data is available for monitor '{mnt.name}'. This is typically caused by " "monitor not intersecting any solid medium." ) return val
@property def symmetry_expanded_copy(self) -> SteadyPotentialData: """Return copy of self with symmetry applied.""" new_potential = self._symmetry_expanded_copy(property=self.potential) return self.updated_copy(potential=new_potential, symmetry=(0, 0, 0))
[docs] def field_name(self, val: str) -> str: """Gets the name of the fields to be plot.""" if val == "abs^2": return "|V|²" else: return "V"
[docs] class SteadyFreeCarrierData(HeatChargeMonitorData): """ Stores free-carrier concentration in charge simulations. Notes ----- This data contains the carrier concentrations: the amount of electrons and holes per unit volume as defined in the ``monitor``. """ monitor: SteadyFreeCarrierMonitor = pd.Field( ..., title="Free carrier monitor", description="Free carrier data associated with a Charge simulation.", ) electrons: UnstructuredFieldType = pd.Field( None, title="Electrons series", description=r"Contains the computed electrons concentration $n$.", discriminator=TYPE_TAG_STR, ) # n = electrons holes: UnstructuredFieldType = pd.Field( None, title="Holes series", description=r"Contains the computed holes concentration $p$.", discriminator=TYPE_TAG_STR, ) # p = holes @property def field_components(self) -> Dict[str, DataArray]: """Maps the field components to their associated data.""" return dict(electrons=self.electrons, holes=self.holes)
[docs] @pd.root_validator(skip_on_failure=True) def check_correct_data_type(cls, values): """Issue error if incorrect data type is used""" mnt = values.get("monitor") field_data = {field: values.get(field) for field in ["electrons", "holes"]} for field, data in field_data.items(): if isinstance(data, TetrahedralGridDataset) or isinstance(data, TriangularGridDataset): if not isinstance(data.values, IndexedVoltageDataArray): raise ValueError( f"In the data associated with monitor {mnt}, the field {field} does not contain " "data associated to any voltage value." ) return values
[docs] @pd.root_validator(skip_on_failure=True) def warn_no_data(cls, values): """Warn if no data provided.""" mnt = values.get("monitor") electrons = values.get("electrons") holes = values.get("holes") if electrons is None or holes is None: log.warning( f"No data is available for monitor '{mnt.name}'. This is typically caused by " "monitor not intersecting any solid medium." ) return values
@property def symmetry_expanded_copy(self) -> SteadyFreeCarrierData: """Return copy of self with symmetry applied.""" new_electrons = self._symmetry_expanded_copy(property=self.electrons) new_holes = self._symmetry_expanded_copy(property=self.holes) return self.updated_copy( electrons=new_electrons, holes=new_holes, symmetry=(0, 0, 0), )
[docs] def field_name(self, val: str = "") -> str: """Gets the name of the fields to be plot.""" if val == "abs^2": return "Electrons², Holes²" else: return "Electrons, Holes"
[docs] class SteadyCapacitanceData(HeatChargeMonitorData): """ Class that stores capacitance data from a Charge simulation. Notes ----- The small signal-capacitance of electrons :math:`C_n` and holes :math:`C_p` is computed from the charge due to electrons :math:`Q_n` and holes :math:`Q_p` at an applied voltage :math:`V` at a voltage difference :math:`\\Delta V` between two simulations. .. math:: C_{n,p} = \\frac{Q_{n,p}(V + \\Delta V) - Q_{n,p}(V)}{\\Delta V} This is only computed when a voltage source with more than two sources is included within the simulation and determines the :math:`\\Delta V`. """ monitor: SteadyCapacitanceMonitor = pd.Field( ..., title="Capacitance monitor", description="Capacitance data associated with a Charge simulation.", ) hole_capacitance: SteadyVoltageDataArray = pd.Field( None, title="Hole capacitance", description=r"Small signal capacitance ($\frac{dQ_p}{dV}$) associated to the monitor.", ) # C_p = hole_capacitance electron_capacitance: SteadyVoltageDataArray = pd.Field( None, title="Electron capacitance", description=r"Small signal capacitance ($\frac{dQn}{dV}$) associated to the monitor.", ) # C_n = electron_capacitance
[docs] @pd.validator("hole_capacitance", always=True) @skip_if_fields_missing(["monitor"]) def warn_no_data(cls, val, values): """Warn if no data provided.""" mnt = values.get("monitor") if val is None: log.warning( f"No data is available for monitor '{mnt.name}'. This is typically caused by " "monitor not intersecting any solid medium." ) return val
[docs] def field_name(self, val: str) -> str: """Gets the name of the fields to be plot.""" return ""
@property def symmetry_expanded_copy(self) -> SteadyCapacitanceData: """Return copy of self with symmetry applied.""" return self