Skip to content

Commit d5e1b87

Browse files
committed
Add VolumeMesher and VOLUME_MESH task type
1 parent e486b44 commit d5e1b87

File tree

13 files changed

+255
-20
lines changed

13 files changed

+255
-20
lines changed

tests/test_components/test_heat_charge.py

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,8 @@ def monitors():
252252

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

255+
mesh_mnt = td.VolumeMeshMonitor(size=(1.6, 2, 3), name="mesh_test")
256+
255257
return [
256258
temp_mnt1,
257259
temp_mnt2,
@@ -264,6 +266,7 @@ def monitors():
264266
capacitance_mnt1,
265267
free_carrier_mnt1,
266268
energy_band_mnt1,
269+
mesh_mnt,
267270
]
268271

269272

@@ -516,7 +519,7 @@ def temperature_monitor_data(monitors):
516519
@pytest.fixture(scope="module")
517520
def voltage_monitor_data(monitors):
518521
"""Creates different voltage monitor data."""
519-
_, _, _, _, volt_mnt1, volt_mnt2, volt_mnt3, volt_mnt4, _, _, _ = monitors
522+
_, _, _, _, volt_mnt1, volt_mnt2, volt_mnt3, volt_mnt4, _, _, _, _ = monitors
520523

521524
# SpatialDataArray
522525
nx, ny, nz = 9, 6, 5
@@ -598,6 +601,40 @@ def capacitance_monitor_data(monitors):
598601
return (cap_data1,)
599602

600603

604+
@pytest.fixture(scope="module")
605+
def mesh_monitor_data(monitors):
606+
"""Creates different voltage monitor data."""
607+
mesh_mnt = monitors[11]
608+
609+
# TetrahedralGridDataset
610+
tet_grid_points = td.PointDataArray(
611+
[[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]],
612+
dims=("index", "axis"),
613+
)
614+
615+
tet_grid_cells = td.CellDataArray(
616+
[[0, 1, 2, 4], [1, 2, 3, 4]],
617+
dims=("cell_index", "vertex_index"),
618+
)
619+
620+
tet_grid_values = td.IndexedDataArray(
621+
np.zeros((tet_grid_points.shape[0],)),
622+
dims=("index",),
623+
name="Mesh",
624+
)
625+
626+
tet_grid = td.TetrahedralGridDataset(
627+
points=tet_grid_points,
628+
cells=tet_grid_cells,
629+
values=tet_grid_values,
630+
)
631+
632+
# SpatialDataArray
633+
mesh_data = td.VolumeMeshData(monitor=mesh_mnt, mesh=tet_grid)
634+
635+
return (mesh_data,)
636+
637+
601638
@pytest.fixture(scope="module")
602639
def free_carrier_monitor_data(monitors):
603640
"""Creates different voltage monitor data."""
@@ -653,6 +690,7 @@ def simulation_data(
653690
capacitance_monitor_data,
654691
free_carrier_monitor_data,
655692
energy_band_monitor_data,
693+
mesh_monitor_data,
656694
):
657695
"""Creates 'HeatChargeSimulationData' for both Heat and Conduction simulations."""
658696
heat_sim_data = td.HeatChargeSimulationData(
@@ -675,7 +713,20 @@ def simulation_data(
675713
data=(voltage_monitor_data[0], free_carrier_monitor_data[0]),
676714
)
677715

678-
return [heat_sim_data, cond_sim_data, voltage_capacitance_sim_data, current_voltage_sim_data]
716+
mesh_monitor = mesh_monitor_data[0].monitor
717+
mesh_data = td.VolumeMesherData(
718+
simulation=conduction_simulation,
719+
data=mesh_monitor_data,
720+
monitors=[mesh_monitor],
721+
)
722+
723+
return [
724+
heat_sim_data,
725+
cond_sim_data,
726+
voltage_capacitance_sim_data,
727+
current_voltage_sim_data,
728+
mesh_data,
729+
]
679730

680731

681732
# --------------------------
@@ -854,9 +905,13 @@ def test_heat_charge_sources(structures):
854905

855906
def test_heat_charge_simulation(simulation_data):
856907
"""Tests 'HeatChargeSimulation' and 'ConductionSimulation' objects."""
857-
heat_sim_data, cond_sim_data, voltage_capacitance_sim_data, current_voltage_simulation_data = (
858-
simulation_data
859-
)
908+
(
909+
heat_sim_data,
910+
cond_sim_data,
911+
voltage_capacitance_sim_data,
912+
current_voltage_simulation_data,
913+
mesh_data,
914+
) = simulation_data
860915

861916
# Test Heat Simulation
862917
heat_sim = heat_sim_data.simulation
@@ -876,10 +931,13 @@ def test_heat_charge_simulation(simulation_data):
876931
"Current-Voltage simulation should be created successfully."
877932
)
878933

934+
mesher = mesh_data.mesher
935+
assert mesher is not None, "VolumeMesher should be created successfully."
936+
879937

880938
def test_sim_data_plotting(simulation_data):
881939
"""Tests whether simulation data can be plotted and appropriate errors are raised."""
882-
heat_sim_data, cond_sim_data, cap_sim_data, fc_sim_data = simulation_data
940+
heat_sim_data, cond_sim_data, cap_sim_data, fc_sim_data, mesh_data = simulation_data
883941

884942
# Plotting temperature data
885943
heat_sim_data.plot_field("test", z=0)
@@ -1415,7 +1473,7 @@ def test_dynamic_simulation_updates(heat_simulation):
14151473

14161474
def test_plotting_functions(simulation_data):
14171475
"""Test plotting functions with various data."""
1418-
heat_sim_data, cond_sim_data, cap_sim_data, fc_sim_data = simulation_data
1476+
heat_sim_data, cond_sim_data, cap_sim_data, fc_sim_data, mesh_data = simulation_data
14191477

14201478
# Valid plotting
14211479
try:

tidy3d/__init__.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@
2828
HeatBoundarySpec,
2929
HeatChargeBoundarySpec,
3030
)
31+
from tidy3d.components.tcad.data.monitor_data.mesh import VolumeMeshData
3132
from tidy3d.components.tcad.data.sim_data import (
3233
DeviceCharacteristics,
3334
HeatChargeSimulationData,
3435
HeatSimulationData,
36+
VolumeMesherData,
3537
)
3638
from tidy3d.components.tcad.data.types import (
3739
SteadyCapacitanceData,
@@ -48,15 +50,15 @@
4850
GridRefinementRegion,
4951
UniformUnstructuredGrid,
5052
)
53+
from tidy3d.components.tcad.mesher import VolumeMesher
5154
from tidy3d.components.tcad.monitors.charge import (
5255
SteadyCapacitanceMonitor,
5356
SteadyEnergyBandMonitor,
5457
SteadyFreeCarrierMonitor,
5558
SteadyPotentialMonitor,
5659
)
57-
from tidy3d.components.tcad.monitors.heat import (
58-
TemperatureMonitor,
59-
)
60+
from tidy3d.components.tcad.monitors.heat import TemperatureMonitor
61+
from tidy3d.components.tcad.monitors.mesh import VolumeMeshMonitor
6062
from tidy3d.components.tcad.simulation.heat import HeatSimulation
6163
from tidy3d.components.tcad.simulation.heat_charge import HeatChargeSimulation
6264
from tidy3d.components.tcad.types import (
@@ -683,6 +685,10 @@ def set_logging_level(level: str) -> None:
683685
"VisualizationSpec",
684686
"VoltageBC",
685687
"VoltageSourceType",
688+
"VolumeMesher",
689+
"VolumeMesherData",
690+
"VolumeMeshData",
691+
"VolumeMeshMonitor",
686692
"VolumetricAveraging",
687693
"YeeGrid",
688694
"__version__",

tidy3d/components/base_sim/data/sim_data.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
from tidy3d.components.data.utils import UnstructuredGridDatasetType
1515
from tidy3d.components.types import FieldVal
1616
from tidy3d.exceptions import DataError, Tidy3dKeyError, ValidationError
17-
18-
from .monitor_data import AbstractMonitorData
17+
from tidy3d.components.monitor import AbstractMonitor
18+
from tidy3d.components.base_sim.data.monitor_data import AbstractMonitorData
1919

2020

2121
class AbstractSimulationData(Tidy3dBaseModel, ABC):
@@ -128,3 +128,7 @@ def _field_component_value(
128128
)
129129

130130
return field_value
131+
132+
def get_monitor_by_name(self, name: str) -> AbstractMonitor:
133+
"""Return monitor named 'name'."""
134+
return self.simulation.get_monitor_by_name(name)

tidy3d/components/data/unstructured/base.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ def number_of_values_matches_points(cls, val, values):
122122

123123
points = values.get("points")
124124
num_points = len(points)
125-
126125
if num_points != num_values:
127126
raise ValidationError(
128127
f"The number of data values ({num_values}) does not match the number of grid "
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""Monitor data for unstructured volume mesh monitors."""
2+
3+
from __future__ import annotations
4+
5+
from typing import Union
6+
7+
import pydantic.v1 as pd
8+
9+
from tidy3d.components.data.utils import TetrahedralGridDataset, TriangularGridDataset
10+
from tidy3d.components.tcad.data.monitor_data.abstract import HeatChargeMonitorData
11+
from tidy3d.components.tcad.monitors.mesh import VolumeMeshMonitor
12+
13+
UnstructuredFieldType = Union[TriangularGridDataset, TetrahedralGridDataset]
14+
15+
16+
class VolumeMeshData(HeatChargeMonitorData):
17+
"""Data associated with a :class:`VolumeMeshMonitor`: stores the unstructured mesh."""
18+
19+
monitor: VolumeMeshMonitor = pd.Field(
20+
..., title="Monitor", description="Mesh monitor associated with the data."
21+
)
22+
23+
mesh: UnstructuredFieldType = pd.Field(
24+
...,
25+
title="Mesh",
26+
description="Dataset storing the mesh.",
27+
)
28+
29+
@property
30+
def field_components(self) -> dict[str, UnstructuredFieldType]:
31+
"""Maps the field components to their associated data."""
32+
return {"mesh": self.mesh}
33+
34+
def field_name(self, val: str) -> str:
35+
"""Gets the name of the fields to be plot."""
36+
return "Mesh"
37+
38+
@property
39+
def symmetry_expanded_copy(self) -> VolumeMeshData:
40+
"""Return copy of self with symmetry applied."""
41+
42+
new_mesh = self._symmetry_expanded_copy(property=self.mesh)
43+
return self.updated_copy(mesh=new_mesh, symmetry=(0, 0, 0))

tidy3d/components/tcad/data/sim_data.py

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22

33
from __future__ import annotations
44

5+
from abc import ABC
56
from typing import Literal, Optional
67

78
import numpy as np
89
import pydantic.v1 as pd
910

10-
from tidy3d.components.base import Tidy3dBaseModel
11+
from tidy3d.components.base import Tidy3dBaseModel, skip_if_fields_missing
1112
from tidy3d.components.base_sim.data.sim_data import AbstractSimulationData
1213
from tidy3d.components.data.data_array import (
1314
SpatialDataArray,
@@ -18,16 +19,19 @@
1819
TriangularGridDataset,
1920
UnstructuredGridDataset,
2021
)
22+
from tidy3d.components.tcad.data.monitor_data.mesh import VolumeMeshData
2123
from tidy3d.components.tcad.data.types import (
2224
SteadyPotentialData,
2325
TCADMonitorDataType,
2426
TemperatureData,
2527
)
28+
from tidy3d.components.tcad.mesher import VolumeMesher
29+
from tidy3d.components.tcad.monitors.mesh import VolumeMeshMonitor
2630
from tidy3d.components.tcad.simulation.heat import HeatSimulation
2731
from tidy3d.components.tcad.simulation.heat_charge import HeatChargeSimulation
2832
from tidy3d.components.types import Ax, RealFieldVal, annotate_type
2933
from tidy3d.components.viz import add_ax_if_none, equal_aspect
30-
from tidy3d.exceptions import DataError
34+
from tidy3d.exceptions import DataError, Tidy3dKeyError
3135
from tidy3d.log import log
3236

3337

@@ -81,7 +85,11 @@ class DeviceCharacteristics(Tidy3dBaseModel):
8185
)
8286

8387

84-
class HeatChargeSimulationData(AbstractSimulationData):
88+
class AbstractHeatChargeSimulationData(AbstractSimulationData, ABC):
89+
"""Abstract class for HeatChargeSimulation results, or VolumeMesher results."""
90+
91+
92+
class HeatChargeSimulationData(AbstractHeatChargeSimulationData):
8593
"""Stores results of a :class:`HeatChargeSimulation`.
8694
8795
Example
@@ -218,7 +226,7 @@ def plot_field(
218226
# field.name = field_name
219227
field_data = self._field_component_value(field, val)
220228

221-
if isinstance(monitor_data, TemperatureData):
229+
if isinstance(monitor_data, (TemperatureData, VolumeMeshData)):
222230
property_to_plot = "heat_conductivity"
223231
elif isinstance(monitor_data, SteadyPotentialData):
224232
property_to_plot = "electric_conductivity"
@@ -376,3 +384,58 @@ def issue_warning_deprecated(cls, values):
376384
"'HeatChargeSimulationData' instead"
377385
)
378386
return values
387+
388+
389+
class VolumeMesherData(AbstractHeatChargeSimulationData):
390+
"""Stores results of a :class:`VolumeMesher`."""
391+
392+
simulation: HeatChargeSimulation = pd.Field(
393+
title="Volume mesher",
394+
description="Original :class:`VolumeMesher` associated with the data.",
395+
)
396+
397+
monitors: tuple[VolumeMeshMonitor, ...] = pd.Field(
398+
...,
399+
title="Monitors",
400+
description="List of monitors to be used for the mesher.",
401+
)
402+
403+
data: tuple[VolumeMeshData, ...] = pd.Field(
404+
...,
405+
title="Monitor Data",
406+
description="List of :class:`.MonitorData` instances "
407+
"associated with the monitors of the original :class:`.VolumeMesher`.",
408+
)
409+
410+
@property
411+
def mesher(self) -> VolumeMesher:
412+
"""Get the mesher associated with this mesher data."""
413+
return VolumeMesher(
414+
simulation=self.simulation,
415+
monitors=self.monitors,
416+
)
417+
418+
@pd.validator("data", always=True)
419+
@skip_if_fields_missing(["monitors"])
420+
def data_monitors_match_sim(cls, val, values):
421+
"""Ensure each :class:`AbstractMonitorData` in ``.data`` corresponds to a monitor in
422+
``.simulation``.
423+
"""
424+
monitors = values.get("monitors")
425+
mnt_names = {mnt.name for mnt in monitors}
426+
427+
for mnt_data in val:
428+
monitor_name = mnt_data.monitor.name
429+
if monitor_name not in mnt_names:
430+
raise DataError(
431+
f"Data with monitor name '{monitor_name}' supplied "
432+
f"but not found in the list of monitors."
433+
)
434+
return val
435+
436+
def get_monitor_by_name(self, name: str) -> VolumeMeshMonitor:
437+
"""Return monitor named 'name'."""
438+
for monitor in self.monitors:
439+
if monitor.name == name:
440+
return monitor
441+
raise Tidy3dKeyError(f"No monitor named '{name}'")

tidy3d/components/tcad/mesher.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from __future__ import annotations
2+
3+
import pydantic as pd
4+
5+
from tidy3d.components.base import Tidy3dBaseModel
6+
from tidy3d.components.tcad.monitors.mesh import VolumeMeshMonitor
7+
from tidy3d.components.tcad.simulation.heat_charge import HeatChargeSimulation, TCADAnalysisTypes
8+
9+
10+
class VolumeMesher(Tidy3dBaseModel):
11+
"""Specification for a standalone volume mesher."""
12+
13+
simulation: HeatChargeSimulation = pd.Field(
14+
..., description="HeatCharge simulation instance for the mesh specification."
15+
)
16+
17+
monitors: tuple[VolumeMeshMonitor, ...] = pd.Field(
18+
(),
19+
title="Monitors",
20+
description="List of monitors to be used for the mesher.",
21+
)
22+
23+
def _get_simulation_types(self) -> list[TCADAnalysisTypes]:
24+
return [TCADAnalysisTypes.MESH]

0 commit comments

Comments
 (0)