Skip to content

Adding support for current density monitor #2651

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

Draft
wants to merge 2 commits into
base: develop
Choose a base branch
from
Draft
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
108 changes: 71 additions & 37 deletions tests/test_components/test_heat_charge.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,10 @@ def monitors():

electric_field_mnt = td.SteadyElectricFieldMonitor(size=(1.6, 2, 3), name="electric_field_test")

current_density_mnt = td.SteadyCurrentDensityMonitor(
size=(1.6, 2, 3), name="electric_field_mnt"
)

return [
temp_mnt1, # 0
temp_mnt2, # 1
Expand All @@ -270,6 +274,7 @@ def monitors():
energy_band_mnt1, # 10
mesh_mnt, # 11
electric_field_mnt, # 12
current_density_mnt, # 13
]


Expand Down Expand Up @@ -755,6 +760,19 @@ def electric_field_monitor_data(monitors):
return (mnt_data1, mnt_data2, mnt_data3)


@pytest.fixture(scope="module")
def current_density_monitor_data(monitors, electric_field_monitor_data):
"""Creates different current density monitor data."""
monitor = monitors[13]
e_data1, e_data2, e_data3 = electric_field_monitor_data

mnt_data1 = td.SteadyCurrentDensityData(monitor=monitor, J=e_data1.E)
mnt_data2 = td.SteadyCurrentDensityData(monitor=monitor, J=e_data2.E)
mnt_data3 = td.SteadyCurrentDensityData(monitor=monitor, J=e_data3.E)

return (mnt_data1, mnt_data2, mnt_data3)


@pytest.fixture(scope="module")
def simulation_data(
heat_simulation,
Expand Down Expand Up @@ -929,52 +947,68 @@ def test_monitor_crosses_medium(mediums, structures, heat_simulation, conduction


def test_heat_charge_mnt_data(
temperature_monitor_data, voltage_monitor_data, electric_field_monitor_data
temperature_monitor_data,
voltage_monitor_data,
electric_field_monitor_data,
current_density_monitor_data,
):
"""Tests whether different heat-charge monitor data can be created."""
assert len(temperature_monitor_data) == 4, "Expected 4 temperature monitor data entries."
assert len(voltage_monitor_data) == 4, "Expected 4 voltage monitor data entries."
assert len(electric_field_monitor_data) == 3, "Expected 3 electric field monitor data entries."
assert len(current_density_monitor_data) == 3, (
"Expected 3 current density monitor data entries."
)

for var, mnt_data_lists in [
("E", electric_field_monitor_data),
("J", current_density_monitor_data),
]:
for mnt_data in mnt_data_lists:
assert var in mnt_data.field_components.keys()

symm_data = mnt_data.symmetry_expanded_copy
if var == "E":
assert symm_data.E == mnt_data.E
elif var == "J":
assert symm_data.J == mnt_data.J

names = mnt_data.field_name("abs^2")
assert names == var + "²"
names = mnt_data.field_name()
assert names == var

# make sure an error is raised if we don't use a field data array
# TriangularGridDataset
tri_grid_points = td.PointDataArray(
[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]],
dims=("index", "axis"),
)

for mnt_data in electric_field_monitor_data:
assert "E" in mnt_data.field_components.keys()

symm_data = mnt_data.symmetry_expanded_copy
assert symm_data.E == mnt_data.E

names = mnt_data.field_name("abs^2")
assert names == "E²"
names = mnt_data.field_name()
assert names == "E"

# make sure an error is raised if we don't use a field data array
# TriangularGridDataset
tri_grid_points = td.PointDataArray(
[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]],
dims=("index", "axis"),
)

tri_grid_cells = td.CellDataArray(
[[0, 1, 2], [1, 2, 3]],
dims=("cell_index", "vertex_index"),
)
tri_grid_cells = td.CellDataArray(
[[0, 1, 2], [1, 2, 3]],
dims=("cell_index", "vertex_index"),
)

tri_grid_values = td.IndexedDataArray(
[1.0, 2.0, 3.0, 4.0],
dims=("index",),
name="T",
)
tri_grid_values = td.IndexedDataArray(
[1.0, 2.0, 3.0, 4.0],
dims=("index",),
name="T",
)

tri_grid = td.TriangularGridDataset(
normal_axis=1,
normal_pos=0,
points=tri_grid_points,
cells=tri_grid_cells,
values=tri_grid_values,
)
tri_grid = td.TriangularGridDataset(
normal_axis=1,
normal_pos=0,
points=tri_grid_points,
cells=tri_grid_cells,
values=tri_grid_values,
)

with pytest.raises(pd.ValidationError):
_ = mnt_data.updated_copy(E=tri_grid)
with pytest.raises(pd.ValidationError):
if var == "E":
_ = mnt_data.updated_copy(E=tri_grid)
elif var == "J":
_ = mnt_data.updated_copy(J=tri_grid)


def test_grid_spec_validation(grid_specs):
Expand Down
4 changes: 4 additions & 0 deletions tidy3d/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
)
from tidy3d.components.tcad.data.types import (
SteadyCapacitanceData,
SteadyCurrentDensityData,
SteadyElectricFieldData,
SteadyEnergyBandData,
SteadyFreeCarrierData,
Expand All @@ -54,6 +55,7 @@
from tidy3d.components.tcad.mesher import VolumeMesher
from tidy3d.components.tcad.monitors.charge import (
SteadyCapacitanceMonitor,
SteadyCurrentDensityMonitor,
SteadyElectricFieldMonitor,
SteadyEnergyBandMonitor,
SteadyFreeCarrierMonitor,
Expand Down Expand Up @@ -659,6 +661,8 @@ def set_logging_level(level: str) -> None:
"Staircasing",
"SteadyCapacitanceData",
"SteadyCapacitanceMonitor",
"SteadyCurrentDensityData",
"SteadyCurrentDensityMonitor",
"SteadyElectricFieldData",
"SteadyElectricFieldMonitor",
"SteadyEnergyBandData",
Expand Down
80 changes: 78 additions & 2 deletions tidy3d/components/tcad/data/monitor_data/charge.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from tidy3d.components.tcad.data.monitor_data.abstract import HeatChargeMonitorData
from tidy3d.components.tcad.monitors.charge import (
SteadyCapacitanceMonitor,
SteadyCurrentDensityMonitor,
SteadyElectricFieldMonitor,
SteadyEnergyBandMonitor,
SteadyFreeCarrierMonitor,
Expand Down Expand Up @@ -467,7 +468,7 @@ def symmetry_expanded_copy(self) -> SteadyCapacitanceData:

class SteadyElectricFieldData(HeatChargeMonitorData):
"""
Stores electric field :math:`\\vec{E}` from a charge simulation.
Stores electric field :math:`\\vec{E}` from a Charge/Conduction simulation.

Notes
-----
Expand All @@ -478,7 +479,7 @@ class SteadyElectricFieldData(HeatChargeMonitorData):
monitor: SteadyElectricFieldMonitor = pd.Field(
...,
title="Electric field monitor",
description="Electric field data associated with a Charge simulation.",
description="Electric field data associated with a Charge/Conduction simulation.",
)

E: UnstructuredFieldType = pd.Field(
Expand Down Expand Up @@ -542,3 +543,78 @@ def field_name(self, val: str = "") -> str:
return "E²"
else:
return "E"


class SteadyCurrentDensityData(HeatChargeMonitorData):
"""
Stores current density :math:`\\vec{J}` from a Charge/Conduction simulation. It is given in
units of :math:`A/\\mu m^2`
"""

monitor: SteadyCurrentDensityMonitor = pd.Field(
...,
title="Current density monitor",
description="Current density data associated with a Charge/Conduction simulation.",
)

J: UnstructuredFieldType = pd.Field(
None,
title="Current density",
description=r"Contains the computed current density in :math:`A/\\mu m^2`.",
discriminator=TYPE_TAG_STR,
)

@property
def field_components(self) -> dict[str, UnstructuredFieldType]:
"""Maps the field components to their associated data."""
return {"J": self.J}

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

mnt = values.get("monitor")
J = values.get("J")

if J 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

@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")
J = values.get("J")

if isinstance(J, TetrahedralGridDataset) or isinstance(J, TriangularGridDataset):
AcceptedTypes = (IndexedFieldVoltageDataArray, PointDataArray)
if not isinstance(J.values, AcceptedTypes):
raise ValueError(
f"In the data associated with monitor {mnt}, must contain a field. This can be "
"defined with IndexedFieldVoltageDataArray or PointDataArray."
)

return values

@property
def symmetry_expanded_copy(self) -> SteadyCurrentDensityData:
"""Return copy of self with symmetry applied."""

new_J = self._symmetry_expanded_copy(property=self.J)

return self.updated_copy(
J=new_J,
symmetry=(0, 0, 0),
)

def field_name(self, val: str = "") -> str:
"""Gets the name of the fields to be plotted."""
if val == "abs^2":
return "J²"
else:
return "J"
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,
SteadyCurrentDensityData,
SteadyElectricFieldData,
SteadyEnergyBandData,
SteadyFreeCarrierData,
Expand All @@ -20,4 +21,5 @@
SteadyElectricFieldData,
SteadyEnergyBandData,
SteadyCapacitanceData,
SteadyCurrentDensityData,
]
21 changes: 20 additions & 1 deletion tidy3d/components/tcad/monitors/charge.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class SteadyCapacitanceMonitor(HeatChargeMonitor):

class SteadyElectricFieldMonitor(HeatChargeMonitor):
"""
Electric field monitor for Charge simulations.
Electric field monitor for Charge/Conduction simulations.

Example
-------
Expand All @@ -99,3 +99,22 @@ class SteadyElectricFieldMonitor(HeatChargeMonitor):
title="Unstructured Grid",
description="Return data on the original unstructured grid.",
)


class SteadyCurrentDensityMonitor(HeatChargeMonitor):
"""
Current density monitor for Charge/Conduction simulations.

Example
-------
>>> import tidy3d as td
>>> current_density_monitor_z0 = td.SteadyCurrentDensityMonitor(
... center=(0, 0.14, 0), size=(0.6, 0.3, 0), name="current_density_z0",
... )
"""

unstructured: Literal[True] = pd.Field(
True,
title="Unstructured Grid",
description="Return data on the original unstructured grid.",
)
4 changes: 3 additions & 1 deletion tidy3d/components/tcad/simulation/heat_charge.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
)
from tidy3d.components.tcad.monitors.charge import (
SteadyCapacitanceMonitor,
SteadyCurrentDensityMonitor,
SteadyFreeCarrierMonitor,
SteadyPotentialMonitor,
)
Expand Down Expand Up @@ -546,6 +547,7 @@ def check_charge_simulation(cls, values):
SteadyPotentialMonitor,
SteadyFreeCarrierMonitor,
SteadyCapacitanceMonitor,
SteadyCurrentDensityMonitor,
)

simulation_types = cls._check_simulation_types(values=values)
Expand All @@ -568,7 +570,7 @@ def check_charge_simulation(cls, values):
if not any(isinstance(mnt, ChargeMonitorType) for mnt in monitors):
raise SetupError(
"Charge simulations require the definition of, at least, one of these monitors: "
"'[SteadyPotentialMonitor, SteadyFreeCarrierMonitor, SteadyCapacitanceMonitor]' "
"'[SteadyPotentialMonitor, SteadyFreeCarrierMonitor, SteadyCapacitanceMonitor, SteadyCurrentDensityMonitor]' "
"but none have been defined."
)

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 @@ -13,6 +13,7 @@
from tidy3d.components.tcad.mobility import CaugheyThomasMobility, ConstantMobilityModel
from tidy3d.components.tcad.monitors.charge import (
SteadyCapacitanceMonitor,
SteadyCurrentDensityMonitor,
SteadyElectricFieldMonitor,
SteadyEnergyBandMonitor,
SteadyFreeCarrierMonitor,
Expand All @@ -37,6 +38,7 @@
SteadyEnergyBandMonitor,
SteadyElectricFieldMonitor,
SteadyCapacitanceMonitor,
SteadyCurrentDensityMonitor,
]
HeatChargeSourceType = Union[HeatSource, HeatFromElectricSource, UniformHeatSource]
HeatChargeBCType = Union[
Expand Down