Skip to content

Electric field monitor for Charge #2566

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

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
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
126 changes: 113 additions & 13 deletions tests/test_components/test_heat_charge.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,18 +252,21 @@ def monitors():

energy_band_mnt1 = td.SteadyEnergyBandMonitor(size=(1.6, 2, 3), name="bandgap_test")

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

return [
temp_mnt1,
temp_mnt2,
temp_mnt3,
temp_mnt4,
volt_mnt1,
volt_mnt2,
volt_mnt3,
volt_mnt4,
capacitance_mnt1,
free_carrier_mnt1,
energy_band_mnt1,
temp_mnt1, # 0
temp_mnt2, # 1
temp_mnt3, # 2
temp_mnt4, # 3
volt_mnt1, # 4
volt_mnt2, # 5
volt_mnt3, # 6
volt_mnt4, # 7
capacitance_mnt1, # 8
free_carrier_mnt1, # 9
energy_band_mnt1, # 10
electric_field_mnt, # 11
]


Expand Down Expand Up @@ -516,7 +519,10 @@ def temperature_monitor_data(monitors):
@pytest.fixture(scope="module")
def voltage_monitor_data(monitors):
"""Creates different voltage monitor data."""
_, _, _, _, volt_mnt1, volt_mnt2, volt_mnt3, volt_mnt4, _, _, _ = monitors
volt_mnt1 = monitors[4]
volt_mnt2 = monitors[5]
volt_mnt3 = monitors[6]
volt_mnt4 = monitors[7]

# SpatialDataArray
nx, ny, nz = 9, 6, 5
Expand Down Expand Up @@ -642,6 +648,84 @@ def energy_band_monitor_data(monitors):
return (eb_data1,)


@pytest.fixture(scope="module")
def electric_field_monitor_data(monitors):
"""Creates different electric field monitor data."""
monitor = monitors[11]

# TetrahedralGridDataset
tet_grid_points = td.PointDataArray(
[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
dims=("index", "axis"),
)

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

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

tet_grid = td.TetrahedralGridDataset(
points=tet_grid_points,
cells=tet_grid_cells,
values=tet_grid_values,
)

mnt_data1 = td.SteadyElectricFieldData(monitor=monitor, Ex=tet_grid, Ey=tet_grid, Ez=tet_grid)

# 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_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,
)

mnt_data2 = td.SteadyElectricFieldData(monitor=monitor, Ex=tri_grid, Ey=tri_grid, Ez=tri_grid)

mnt_data3 = td.SteadyElectricFieldData(monitor=monitor, Ex=None, Ey=None, Ez=None)

data_v = td.IndexedVoltageDataArray(
[[0.0, 1.5], [1.5, 2], [2.5, 3.0], [3.5, -4.0]],
coords={"index": np.arange(4), "voltage": [-1, 1]},
name="test",
)
tri_grid_v = td.TriangularGridDataset(
normal_axis=1,
normal_pos=0,
points=tri_grid_points,
cells=tri_grid_cells,
values=data_v,
)
mnt_data4 = td.SteadyElectricFieldData(
monitor=monitor, Ex=tri_grid_v, Ey=tri_grid_v, Ez=tri_grid_v
)

return (mnt_data1, mnt_data2, mnt_data3, mnt_data4)


@pytest.fixture(scope="module")
def simulation_data(
heat_simulation,
Expand Down Expand Up @@ -797,11 +881,27 @@ def test_monitor_crosses_medium(mediums, structures, heat_simulation, conduction


def test_heat_charge_mnt_data(
temperature_monitor_data, voltage_monitor_data, capacitance_monitor_data
temperature_monitor_data, voltage_monitor_data, electric_field_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) == 4, "Expected 4 electric field monitor data entries."

for mnt_data in electric_field_monitor_data:
assert "Ex" in mnt_data.field_components.keys()
assert "Ey" in mnt_data.field_components.keys()
assert "Ez" in mnt_data.field_components.keys()

symm_data = mnt_data.symmetry_expanded_copy
assert symm_data.Ex == mnt_data.Ex
assert symm_data.Ey == mnt_data.Ey
assert symm_data.Ez == mnt_data.Ez

names = mnt_data.field_name("abs^2")
assert names == "Ex², Ey², Ez²"
names = mnt_data.field_name()
assert names == "Ex, Ey, Ez"


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 @@ -35,6 +35,7 @@
)
from tidy3d.components.tcad.data.types import (
SteadyCapacitanceData,
SteadyElectricFieldData,
SteadyEnergyBandData,
SteadyFreeCarrierData,
SteadyPotentialData,
Expand All @@ -50,6 +51,7 @@
)
from tidy3d.components.tcad.monitors.charge import (
SteadyCapacitanceMonitor,
SteadyElectricFieldMonitor,
SteadyEnergyBandMonitor,
SteadyFreeCarrierMonitor,
SteadyPotentialMonitor,
Expand Down Expand Up @@ -651,6 +653,8 @@ def set_logging_level(level: str) -> None:
"Staircasing",
"SteadyCapacitanceData",
"SteadyCapacitanceMonitor",
"SteadyElectricFieldData",
"SteadyElectricFieldMonitor",
"SteadyEnergyBandData",
"SteadyEnergyBandMonitor",
"SteadyFreeCarrierData",
Expand Down
14 changes: 14 additions & 0 deletions tidy3d/components/data/unstructured/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,20 @@ def to_vtu(self, fname: str):
writer.SetInputData(self._vtk_obj)
writer.Write()

@classmethod
@requires_vtk
def _cell_to_point_data(
cls,
vtk_obj,
):
"""Get point data values from a VTK object."""

cellDataToPointData = vtk["mod"].vtkCellDataToPointData()
cellDataToPointData.SetInputData(vtk_obj)
cellDataToPointData.Update()

return cellDataToPointData.GetOutput()

@classmethod
@requires_vtk
def _get_values_from_vtk(
Expand Down
83 changes: 83 additions & 0 deletions tidy3d/components/tcad/data/monitor_data/charge.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from tidy3d.components.tcad.data.monitor_data.abstract import HeatChargeMonitorData
from tidy3d.components.tcad.monitors.charge import (
SteadyCapacitanceMonitor,
SteadyElectricFieldMonitor,
SteadyEnergyBandMonitor,
SteadyFreeCarrierMonitor,
SteadyPotentialMonitor,
Expand Down Expand Up @@ -460,3 +461,85 @@ def symmetry_expanded_copy(self) -> SteadyCapacitanceData:
electron_capacitance=new_electron_capacitance,
symmetry=(0, 0, 0),
)


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

Notes
-----
The electric field is computed as the negative gradient of the electric potential :math:`\\vec{E} = -\\nabla \\psi`.
It is given in units of :math:`V/\\mu m` (Volts per micrometer).
"""

monitor: SteadyElectricFieldMonitor = pd.Field(
...,
title="Electric field monitor",
description="Electric field data associated with a Charge simulation.",
)

Ex: UnstructuredFieldType = pd.Field(
None,
title="x component of the electric field",
description=r"Contains the computed x component of the electric field in :math:`V/\\mu m`.",
discriminator=TYPE_TAG_STR,
)

Ey: UnstructuredFieldType = pd.Field(
None,
title="y component of the electric field",
description=r"Contains the computed y component of the electric field in :math:`V/\\mu m`.",
discriminator=TYPE_TAG_STR,
)

Ez: UnstructuredFieldType = pd.Field(
None,
title="z component of the electric field",
description=r"Contains the computed z component of the electric field in :math:`V/\\mu m`.",
discriminator=TYPE_TAG_STR,
)

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

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

mnt = values.get("monitor")
Ex = values.get("Ex")
Ey = values.get("Ey")
Ez = values.get("Ez")

if Ex is None or Ey is None or Ez 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) -> SteadyElectricFieldData:
"""Return copy of self with symmetry applied."""

new_Ex = self._symmetry_expanded_copy(property=self.Ex)
new_Ey = self._symmetry_expanded_copy(property=self.Ey)
new_Ez = self._symmetry_expanded_copy(property=self.Ez)

return self.updated_copy(
Ex=new_Ex,
Ey=new_Ey,
Ez=new_Ez,
symmetry=(0, 0, 0),
)
Comment on lines +527 to +538
Copy link

Choose a reason for hiding this comment

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

logic: No validation for correct data type (IndexedVoltageDataArray) unlike SteadyFreeCarrierData and SteadyEnergyBandData


def field_name(self, val: str = "") -> str:
"""Gets the name of the fields to be plotted."""
if val == "abs^2":
return "Ex², Ey², Ez²"
else:
return "Ex, Ey, Ez"
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,
SteadyElectricFieldData,
SteadyEnergyBandData,
SteadyFreeCarrierData,
SteadyPotentialData,
Expand All @@ -16,6 +17,7 @@
TemperatureData,
SteadyPotentialData,
SteadyFreeCarrierData,
SteadyElectricFieldData,
SteadyEnergyBandData,
SteadyCapacitanceData,
]
19 changes: 19 additions & 0 deletions tidy3d/components/tcad/monitors/charge.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,22 @@ class SteadyCapacitanceMonitor(HeatChargeMonitor):
title="Unstructured Grid",
description="Return data on the original unstructured grid.",
)


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

Example
-------
>>> import tidy3d as td
>>> electric_field_monitor_z0 = td.SteadyElectricFieldMonitor(
... center=(0, 0.14, 0), size=(0.6, 0.3, 0), name="electric_field_z0",
... )
"""

unstructured: Literal[True] = pd.Field(
True,
title="Unstructured Grid",
description="Return data on the original unstructured grid.",
)
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,
SteadyElectricFieldMonitor,
SteadyEnergyBandMonitor,
SteadyFreeCarrierMonitor,
SteadyPotentialMonitor,
Expand All @@ -34,6 +35,7 @@
SteadyPotentialMonitor,
SteadyFreeCarrierMonitor,
SteadyEnergyBandMonitor,
SteadyElectricFieldMonitor,
SteadyCapacitanceMonitor,
]
HeatChargeSourceType = Union[HeatSource, HeatFromElectricSource, UniformHeatSource]
Expand Down