-
Notifications
You must be signed in to change notification settings - Fork 55
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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[ | ||
|
@@ -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.""" | ||
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: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As a general comment (@marc-flex ) in the EM data |
||
"""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. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,6 +40,26 @@ class SteadyFreeCarrierMonitor(HeatChargeMonitor): | |
) | ||
|
||
|
||
class SteadyEnergyBandMonitor(HeatChargeMonitor): | ||
""" | ||
Free-carrier monitor for Charge simulations. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
There was a problem hiding this comment.
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?