Skip to content

Add energy bandgap monitors for the CHARGE simulations. #2337

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions tidy3d/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
)
from tidy3d.components.tcad.data.types import (
SteadyCapacitanceData,
SteadyEnergyBandData,
SteadyFreeCarrierData,
SteadyPotentialData,
TemperatureData,
Expand All @@ -41,6 +42,7 @@
from tidy3d.components.tcad.grid import DistanceUnstructuredGrid, UniformUnstructuredGrid
from tidy3d.components.tcad.monitors.charge import (
SteadyCapacitanceMonitor,
SteadyEnergyBandMonitor,
SteadyFreeCarrierMonitor,
SteadyPotentialMonitor,
)
Expand Down Expand Up @@ -597,6 +599,7 @@ def set_logging_level(level: str) -> None:
"HeatChargeSimulation",
"SteadyPotentialData",
"SteadyFreeCarrierData",
"SteadyEnergyBandData",
"SteadyCapacitanceData",
"CaugheyThomasMobility",
"ConstantMobilityModel",
Expand All @@ -610,6 +613,7 @@ def set_logging_level(level: str) -> None:
"HeatChargeBoundarySpec",
"SteadyPotentialMonitor",
"SteadyFreeCarrierMonitor",
"SteadyEnergyBandMonitor",
"SteadyCapacitanceMonitor",
"SpaceTimeModulation",
"SpaceModulation",
Expand Down
208 changes: 207 additions & 1 deletion tidy3d/components/tcad/data/monitor_data/charge.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@
from tidy3d.components.tcad.data.monitor_data.abstract import HeatChargeMonitorData
from tidy3d.components.tcad.monitors.charge import (
SteadyCapacitanceMonitor,
SteadyEnergyBandMonitor,
SteadyFreeCarrierMonitor,
SteadyPotentialMonitor,
)
from tidy3d.components.types import TYPE_TAG_STR, annotate_type
from tidy3d.components.types import TYPE_TAG_STR, Ax, Axis, annotate_type
from tidy3d.components.viz import add_ax_if_none
from tidy3d.exceptions import DataError
from tidy3d.log import log

FieldDataset = Union[
Expand Down Expand Up @@ -172,6 +175,209 @@ def field_name(self, val: str = "") -> str:
return "Electrons, Holes"


class SteadyEnergyBandData(HeatChargeMonitorData):
"""
Stores energy bands in charge simulations.

Notes
-----

This data contains the energy bands data:
Ec -> Energy of the bottom of the conduction band, [eV]
Ev -> Energy of the top of the valence band, [eV]
Ei -> Intrinsic Fermi level, [eV]
Efn -> Quasi-Fermi level for electrons, [eV]
Efp -> Quasi-Fermi level for holes, [eV]
as defined in the ``monitor``.
"""

monitor: SteadyEnergyBandMonitor = pd.Field(
...,
title="Energy band monitor",
description="Energy bands data associated with a Charge simulation.",
)

Ec: UnstructuredFieldType = pd.Field(
None,
title="Conduction band series",
description=r"Contains the computed energy of the bottom of the conduction band $Ec$.",
discriminator=TYPE_TAG_STR,
)

Ev: UnstructuredFieldType = pd.Field(
None,
title="Valence band series",
description=r"Contains the computed energy of the top of the valence band $Ec$.",
discriminator=TYPE_TAG_STR,
)

Ei: UnstructuredFieldType = pd.Field(
None,
title="Intrinsic Fermi level series",
description=r"Contains the computed intrinsic Fermi level for the material $Ei$.",
discriminator=TYPE_TAG_STR,
)

Efn: UnstructuredFieldType = pd.Field(
None,
title="Electron's quasi-Fermi level series",
description=r"Contains the computed quasi-Fermi level for electrons $Efn$.",
discriminator=TYPE_TAG_STR,
)

Efp: UnstructuredFieldType = pd.Field(
None,
title="Hole's quasi-Fermi level series",
description=r"Contains the computed quasi-Fermi level for holes $Efp$.",
discriminator=TYPE_TAG_STR,
)

@property
def field_components(self) -> Dict[str, DataArray]:
"""Maps the field components to their associated data."""
return dict(Ec=self.Ec, Ev=self.Ev, Ei=self.Ei, Efn=self.Efn, Efp=self.Efp)

@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 ["Ec", "Ev", "Ei", "Efn", "Efp"]}

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

@pd.root_validator(skip_on_failure=True)
def warn_no_data(cls, values):
"""Warn if no data provided."""

mnt = values.get("monitor")
fields = ["Ec", "Ev", "Ei", "Efn", "Efp"]
for field_name in fields:
field_data = values.get(field_name)

if field_data 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) -> SteadyEnergyBandData:
"""Return copy of self with symmetry applied."""

new_Ec = self._symmetry_expanded_copy(property=self.Ec)
new_Ev = self._symmetry_expanded_copy(property=self.Ev)
new_Ei = self._symmetry_expanded_copy(property=self.Ei)
new_Efn = self._symmetry_expanded_copy(property=self.Efn)
new_Efp = self._symmetry_expanded_copy(property=self.Efp)

return self.updated_copy(
Ec=new_Ec,
Ev=new_Ev,
Ei=new_Ei,
Efn=new_Efn,
Efp=new_Efp,
symmetry=(0, 0, 0),
)

def field_name(self, val: str = "") -> str:
"""Gets the name of the fields to be plot."""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

plotted

I see that this is just copied from above, so could you correct it everywhere?

if val == "abs^2":
return "|Ec|², |Ev|², |Ei|², |Efn|², |Efp|²"
else:
return "Ec, Ev, Ei, Efn, Efp"

@add_ax_if_none
def plot_slice(self, axis: Axis, pos: float, voltage: float, ax: Ax = None) -> Ax:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a general comment (@marc-flex ) in the EM data plot_field we do not explicitly pass things like frequency, mode inde, etc. We just bundle all unknown arguments into **sel_kwargs and pass this to .sel, which allows a bit more freedom. So here, voltage would go into these not-explicitly-defined arguments. Then someone could even do plot_slice(... , voltage=1, method="nearest") to avoid errors that might sometimes happen if they pass a value that is slightly off from an actual recorded value (sometimes just due to numerical precision...)

"""Plot the data field and/or the unstructured grid.

Parameters
----------
axis : Axis
The normal direction of the slicing plane.
pos : float
Position of the slicing plane along its normal direction.
voltage : float
Applied bias voltage
ax : matplotlib.axes._subplots.Axes = None
matplotlib axes to plot on, if not specified, one is created.
"""

if not isinstance(self.Ec, TriangularGridDataset):
raise DataError(
"Bandgap monitor slice plot can be done only for a 2D unstructured dataset."
)

if axis == self.Ec.normal_axis:
raise DataError(
f"Triangular grid (normal: {self.Ec.normal_axis}) cannot be sliced by a parallel "
"plane."
)

Ec_slice = self.Ec.sel(voltage=voltage).plane_slice(axis=axis, pos=pos)
Ev_slice = self.Ev.sel(voltage=voltage).plane_slice(axis=axis, pos=pos)
Ei_slice = self.Ei.sel(voltage=voltage).plane_slice(axis=axis, pos=pos)
Efn_slice = self.Efn.sel(voltage=voltage).plane_slice(axis=axis, pos=pos)
Efp_slice = self.Efp.sel(voltage=voltage).plane_slice(axis=axis, pos=pos)

Ec_slice.plot(ax=ax, label="Ec")
Ev_slice.plot(ax=ax, label="Ev")
Ei_slice.plot(ax=ax, label="Ei")
Efn_slice.plot(ax=ax, label="Efn")
Efp_slice.plot(ax=ax, label="Efp")
ax.legend()

return ax

def plane_slice(self, axis: Axis, pos: float) -> SteadyEnergyBandData:
"""Slice data with a plane and return the resulting :class:`.SteadyEnergyBandData`.

Parameters
----------
axis : Axis
The normal direction of the slicing plane.
pos : float
Position of the slicing plane along its normal direction.

Returns
-------
SteadyEnergyBandData
The resulting slice.
"""

if not isinstance(self.Ec, TetrahedralGridDataset):
raise DataError(
"Bandgap monitor plane slice can be done only for a 3D unstructured dataset."
)

Ec_slice = self.Ec.plane_slice(axis=axis, pos=pos)
Ev_slice = self.Ev.plane_slice(axis=axis, pos=pos)
Ei_slice = self.Ei.plane_slice(axis=axis, pos=pos)
Efn_slice = self.Efn.plane_slice(axis=axis, pos=pos)
Efp_slice = self.Efp.plane_slice(axis=axis, pos=pos)

return SteadyEnergyBandData(
Ec=Ec_slice,
Ev=Ev_slice,
Ei=Ei_slice,
Efn=Efn_slice,
Efp=Efp_slice,
monitor=self.monitor,
symmetry=self.symmetry,
symmetry_center=self.symmetry_center,
)


class SteadyCapacitanceData(HeatChargeMonitorData):
"""
Class that stores capacitance data from a Charge simulation.
Expand Down
2 changes: 2 additions & 0 deletions tidy3d/components/tcad/data/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from tidy3d.components.tcad.data.monitor_data.charge import (
SteadyCapacitanceData,
SteadyEnergyBandData,
SteadyFreeCarrierData,
SteadyPotentialData,
)
Expand All @@ -15,5 +16,6 @@
TemperatureData,
SteadyPotentialData,
SteadyFreeCarrierData,
SteadyEnergyBandData,
SteadyCapacitanceData,
]
20 changes: 20 additions & 0 deletions tidy3d/components/tcad/monitors/charge.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,26 @@ class SteadyFreeCarrierMonitor(HeatChargeMonitor):
)


class SteadyEnergyBandMonitor(HeatChargeMonitor):
"""
Free-carrier monitor for Charge simulations.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Free-carrier -> Energy band


Example
-------
>>> import tidy3d as td
>>> energy_monitor_z0 = td.SteadyEnergyBandMonitor(
... center=(0, 0.14, 0), size=(0.6, 0.3, 0), name="bands_z0", unstructured=True,
... )
"""

# NOTE: for the time being supporting unstructured
unstructured: Literal[True] = pd.Field(
True,
title="Unstructured Grid",
description="Return data on the original unstructured grid.",
)


class SteadyCapacitanceMonitor(HeatChargeMonitor):
"""
Capacitance monitor associated with a charge simulation.
Expand Down
2 changes: 2 additions & 0 deletions tidy3d/components/tcad/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from tidy3d.components.tcad.mobility import CaugheyThomasMobility, ConstantMobilityModel
from tidy3d.components.tcad.monitors.charge import (
SteadyCapacitanceMonitor,
SteadyEnergyBandMonitor,
SteadyFreeCarrierMonitor,
SteadyPotentialMonitor,
)
Expand All @@ -30,6 +31,7 @@
TemperatureMonitor,
SteadyPotentialMonitor,
SteadyFreeCarrierMonitor,
SteadyEnergyBandMonitor,
SteadyCapacitanceMonitor,
]
HeatChargeSourceType = Union[HeatSource, HeatFromElectricSource, UniformHeatSource]
Expand Down