Skip to content

Commit 6672930

Browse files
momchil-flexcopybara-bot
authored andcommitted
chore: improve TCAD mesher logging and fix unstructured data plotting (#3729)
Flex-RevId: 59d167cf0099796aa9c4a74821863c78804a927d
1 parent 76b53cd commit 6672930

4 files changed

Lines changed: 126 additions & 119 deletions

File tree

tests/test_components/test_heat_charge.py

Lines changed: 82 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1291,17 +1291,6 @@ def test_heat_charge_mnt_data(
12911291
_ = mnt_data.updated_copy(J=tri_grid)
12921292

12931293

1294-
def test_heat_charge_monitor_data_warns_for_missing_fields(monitors):
1295-
"""Ensure monitor-data missing-field warnings are emitted at initialization."""
1296-
temp_monitor_empty = monitors[3]
1297-
with AssertLogLevel("WARNING", contains_str="field 'temperature'"):
1298-
_ = td.TemperatureData(monitor=temp_monitor_empty, temperature=None)
1299-
1300-
potential_monitor_empty = monitors[7]
1301-
with AssertLogLevel("WARNING", contains_str="field 'potential'"):
1302-
_ = td.SteadyPotentialData(monitor=potential_monitor_empty, potential=None)
1303-
1304-
13051294
def test_grid_spec_validation(grid_specs):
13061295
"""Tests whether unstructured grids can be created and different validators for them."""
13071296
# Test UniformUnstructuredGrid
@@ -1460,6 +1449,7 @@ def test_mesh_plotting(simulation_data):
14601449

14611450
# Plotting mesh from unstructured temperature data
14621451
heat_sim_data.plot_mesh("tri")
1452+
heat_sim_data.plot_mesh("tri", y=0) # redundant normal-axis sel should be a no-op
14631453
heat_sim_data.plot_mesh("tet", y=0.5)
14641454

14651455
# Plotting mesh from unstructured voltage data
@@ -1492,6 +1482,87 @@ def test_mesh_plotting(simulation_data):
14921482
mesh_data.plot_mesh("mesh_test", z=0, field_name="wrong")
14931483

14941484

1485+
def test_plot_mesh_2d_auto_sel():
1486+
"""Test that plot_mesh auto-selects the collapsed dimension for 2D simulations."""
1487+
1488+
solid_medium = td.Medium(
1489+
permittivity=2.0,
1490+
heat_spec=td.SolidSpec(conductivity=1, capacity=1),
1491+
name="solid",
1492+
)
1493+
fluid_medium = td.Medium(permittivity=3.0, heat_spec=td.FluidSpec(), name="fluid")
1494+
structure = td.Structure(
1495+
geometry=td.Box(size=(0.5, 0.5, 0.5), center=(0, 0, 0)),
1496+
medium=solid_medium,
1497+
name="box",
1498+
)
1499+
1500+
temp_mnt_2d = td.TemperatureMonitor(
1501+
center=(0, 0, 0), size=(0.8, 0, 0.8), name="temp_2d", unstructured=True
1502+
)
1503+
mesh_mnt_2d = td.VolumeMeshMonitor(center=(0, 0, 0), size=(0.8, 0, 0.8), name="mesh_2d")
1504+
1505+
heat_sim_2d = td.HeatChargeSimulation(
1506+
medium=fluid_medium,
1507+
structures=[structure],
1508+
center=(0, 0, 0),
1509+
size=(1, 0, 1),
1510+
grid_spec=td.UniformUnstructuredGrid(dl=0.1),
1511+
sources=[td.HeatSource(rate=1, structures=["box"])],
1512+
boundary_spec=[
1513+
td.HeatChargeBoundarySpec(
1514+
placement=td.StructureBoundary(structure="box"),
1515+
condition=td.TemperatureBC(temperature=500),
1516+
)
1517+
],
1518+
monitors=[temp_mnt_2d],
1519+
)
1520+
1521+
tet_grid_points = td.PointDataArray(
1522+
[
1523+
[0.0, -0.01, 0.0],
1524+
[0.4, -0.01, 0.0],
1525+
[0.0, -0.01, 0.4],
1526+
[0.4, -0.01, 0.4],
1527+
[0.2, 0.01, 0.2],
1528+
],
1529+
dims=("index", "axis"),
1530+
)
1531+
tet_grid_cells = td.CellDataArray(
1532+
[[0, 1, 2, 4], [1, 2, 3, 4]],
1533+
dims=("cell_index", "vertex_index"),
1534+
)
1535+
tet_grid_values = td.IndexedDataArray(
1536+
[300.0, 310.0, 320.0, 330.0, 340.0],
1537+
dims=("index",),
1538+
name="T",
1539+
)
1540+
tet_grid = td.TetrahedralGridDataset(
1541+
points=tet_grid_points, cells=tet_grid_cells, values=tet_grid_values
1542+
)
1543+
1544+
sim_data_2d = td.HeatChargeSimulationData(
1545+
simulation=heat_sim_2d,
1546+
data=[td.TemperatureData(monitor=temp_mnt_2d, temperature=tet_grid)],
1547+
)
1548+
1549+
# Should auto-detect the zero-size y dimension and slice without needing y=0
1550+
sim_data_2d.plot_mesh("temp_2d")
1551+
plt.close()
1552+
1553+
# Also test plot_field auto-selection
1554+
sim_data_2d.plot_field("temp_2d")
1555+
plt.close()
1556+
1557+
# VolumeMesherData case
1558+
mesh_mnt_data = td.VolumeMeshData(monitor=mesh_mnt_2d, mesh=tet_grid)
1559+
mesher_data_2d = td.VolumeMesherData(
1560+
simulation=heat_sim_2d, data=[mesh_mnt_data], monitors=[mesh_mnt_2d]
1561+
)
1562+
mesher_data_2d.plot_mesh("mesh_2d")
1563+
plt.close()
1564+
1565+
14951566
def test_conduction_simulation_has_conductors(conduction_simulation, structures):
14961567
"""Test whether error is raised if conduction simulation has no conductors."""
14971568

tests/test_data/test_datasets.py

Lines changed: 4 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,10 @@ def test_triangular_dataset(tmp_path, ds_name, dataset_type_ind, no_vtk=False):
294294
result = tri_grid.sel(x=np.linspace(0, 1, 3), y=tri_grid.normal_pos, z=[0.3, 0.4, 0.5])
295295
assert result.name == ds_name
296296

297+
# selecting only along the normal axis is a no-op and should return self
298+
result_normal_only = tri_grid.sel(y=tri_grid.normal_pos)
299+
assert result_normal_only == tri_grid
300+
297301
# can't select out of plane
298302
with pytest.raises(DataError):
299303
_ = tri_grid.sel(x=np.linspace(0, 1, 3), y=1.2, z=[0.3, 0.4, 0.5])
@@ -779,105 +783,6 @@ def test_from_vtk():
779783
_ = td.TriangularGridDataset.from_vtk("tests/data/gmsh_2d.vtk")
780784

781785

782-
def test_triangular_from_vtu_near_planar_large_coordinates(tmp_path):
783-
"""Regression for large-coordinate planar slices with small normal-axis jitter."""
784-
pytest.importorskip("vtk")
785-
import vtk
786-
787-
import tidy3d as td
788-
789-
tri_grid = td.TriangularGridDataset(
790-
normal_axis=1,
791-
normal_pos=0.0,
792-
points=td.PointDataArray(
793-
[
794-
[100000.0, 0.0],
795-
[101000.0, 0.0],
796-
[100000.0, 690.0],
797-
[101000.0, 690.0],
798-
],
799-
dims=("index", "axis"),
800-
),
801-
cells=td.CellDataArray([[0, 1, 2], [1, 2, 3]], dims=("cell_index", "vertex_index")),
802-
values=td.IndexedDataArray(
803-
[1.0, 2.0, 3.0, 4.0], coords={"index": np.arange(4)}, name="temp"
804-
),
805-
)
806-
807-
vtu_path = tmp_path / "near_planar_large_coords.vtu"
808-
tri_grid.to_vtu(vtu_path)
809-
810-
# Inject tiny jitter in the nominally planar normal direction.
811-
reader = vtk.vtkXMLUnstructuredGridReader()
812-
reader.SetFileName(str(vtu_path))
813-
reader.Update()
814-
grid = reader.GetOutput()
815-
points = grid.GetPoints()
816-
for ind in range(points.GetNumberOfPoints()):
817-
x, _, z = points.GetPoint(ind)
818-
points.SetPoint(ind, x, 6e-6 if (ind % 2) == 0 else -6e-6, z)
819-
points.Modified()
820-
821-
writer = vtk.vtkXMLUnstructuredGridWriter()
822-
writer.SetFileName(str(vtu_path))
823-
writer.SetInputData(grid)
824-
writer.Write()
825-
826-
loaded = td.TriangularGridDataset.from_vtu(vtu_path, field="temp")
827-
assert loaded.normal_axis == 1
828-
assert abs(float(loaded.normal_pos)) < 1e-4
829-
assert loaded.points.sizes["index"] == 4
830-
831-
832-
def test_triangular_from_vtu_far_from_origin_small_extent(tmp_path):
833-
"""Regression: tolerance must depend on extent, not absolute position."""
834-
pytest.importorskip("vtk")
835-
import vtk
836-
837-
import tidy3d as td
838-
839-
tri_grid = td.TriangularGridDataset(
840-
normal_axis=1,
841-
normal_pos=0.0,
842-
points=td.PointDataArray(
843-
[
844-
[1e9, -2e9],
845-
[1e9 + 2.0, -2e9],
846-
[1e9, -2e9 + 1.0],
847-
[1e9 + 2.0, -2e9 + 1.0],
848-
],
849-
dims=("index", "axis"),
850-
),
851-
cells=td.CellDataArray([[0, 1, 2], [1, 2, 3]], dims=("cell_index", "vertex_index")),
852-
values=td.IndexedDataArray(
853-
[1.0, 2.0, 3.0, 4.0], coords={"index": np.arange(4)}, name="temp"
854-
),
855-
)
856-
857-
vtu_path = tmp_path / "far_origin_small_extent.vtu"
858-
tri_grid.to_vtu(vtu_path)
859-
860-
reader = vtk.vtkXMLUnstructuredGridReader()
861-
reader.SetFileName(str(vtu_path))
862-
reader.Update()
863-
grid = reader.GetOutput()
864-
points = grid.GetPoints()
865-
for ind in range(points.GetNumberOfPoints()):
866-
x, _, z = points.GetPoint(ind)
867-
points.SetPoint(ind, x, 2e-7 if (ind % 2) == 0 else -2e-7, z)
868-
points.Modified()
869-
870-
writer = vtk.vtkXMLUnstructuredGridWriter()
871-
writer.SetFileName(str(vtu_path))
872-
writer.SetInputData(grid)
873-
writer.Write()
874-
875-
loaded = td.TriangularGridDataset.from_vtu(vtu_path, field="temp")
876-
assert loaded.normal_axis == 1
877-
assert abs(float(loaded.normal_pos)) < 1e-5
878-
assert loaded.points.sizes["index"] == 4
879-
880-
881786
def test_tetrahedral_from_vtk_obj_without_cell_types_array():
882787
"""Regression test for VTK objects with missing ``GetCellTypesArray`` output."""
883788
pytest.importorskip("vtk")

tidy3d/components/data/unstructured/triangular.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,7 @@ def _from_vtk_obj(
175175

176176
# detect zero size dimension
177177
bounds = np.max(points_numpy, axis=0) - np.min(points_numpy, axis=0)
178-
# VTU slices can accumulate small floating-point jitter in the nominally
179-
# zero-thickness direction. Scale tolerance with geometry extent (rather than
180-
# absolute coordinate value) to avoid origin-dependent behavior.
181-
size_scale = float(np.max(bounds)) if bounds.size > 0 else 0.0
182-
zero_dim_tol = max(1e-6, 2e-8 * size_scale)
183-
zero_dims = np.where(np.isclose(bounds, 0, atol=zero_dim_tol))[0]
178+
zero_dims = np.where(np.isclose(bounds, 0, atol=1e-6))[0]
184179

185180
if len(zero_dims) != 1:
186181
raise DataError(
@@ -496,6 +491,9 @@ def sel(
496491
axes = [ind for ind, comp in enumerate(xyz) if comp is not None]
497492
num_provided = len(axes)
498493

494+
if num_provided == 0 and len(sel_kwargs) == 0:
495+
raise DataError("At least one dimension for selection must be provided.")
496+
499497
if self.normal_axis in axes:
500498
if xyz[self.normal_axis] != self.normal_pos:
501499
raise DataError(
@@ -507,9 +505,6 @@ def sel(
507505
num_provided -= 1
508506
axes.remove(self.normal_axis)
509507

510-
if num_provided == 0 and len(sel_kwargs) == 0:
511-
raise DataError("At least one dimension for selection must be provided.")
512-
513508
self_after_non_spatial_sel = self._non_spatial_sel(method=method, **sel_kwargs)
514509

515510
if num_provided == 1:

tidy3d/components/tcad/data/sim_data.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,28 @@
4545
from tidy3d.components.types import Ax, RealFieldVal
4646

4747

48+
def _auto_sel_kwargs_for_zero_size_dims(
49+
monitor: Any,
50+
simulation: Any,
51+
sel_kwargs: dict,
52+
) -> dict:
53+
"""Find the first zero-size spatial dimension to auto-select on.
54+
55+
For 2D simulations or 2D monitors producing 3D unstructured data, this detects
56+
which spatial dimension is collapsed and returns the appropriate sel kwarg
57+
so that a TetrahedralGridDataset can be sliced to a TriangularGridDataset.
58+
"""
59+
for dim in range(3):
60+
dim_name = "xyz"[dim]
61+
if dim_name in sel_kwargs:
62+
continue
63+
if monitor.size[dim] == 0:
64+
return {dim_name: monitor.center[dim]}
65+
if simulation.size[dim] == 0:
66+
return {dim_name: simulation.center[dim]}
67+
return {}
68+
69+
4870
def _compute_monitor_axis_limits(
4971
monitor: Any,
5072
sim_bounds: tuple,
@@ -235,6 +257,13 @@ def plot_mesh(
235257
if len(sel_kwargs) > 0:
236258
field_data = field_data.sel(**sel_kwargs)
237259

260+
if isinstance(field_data, TetrahedralGridDataset):
261+
auto_sel = _auto_sel_kwargs_for_zero_size_dims(
262+
monitor_data.monitor, self.simulation, sel_kwargs
263+
)
264+
if auto_sel:
265+
field_data = field_data.sel(**auto_sel)
266+
238267
if isinstance(field_data, TetrahedralGridDataset):
239268
raise DataError(
240269
"Must select a two-dimensional slice of unstructured dataset for plotting"
@@ -422,6 +451,13 @@ def plot_field(
422451
if isinstance(field_data, UnstructuredGridDataset) and len(sel_kwargs) > 0:
423452
field_data = field_data.sel(**sel_kwargs)
424453

454+
if isinstance(field_data, TetrahedralGridDataset):
455+
auto_sel = _auto_sel_kwargs_for_zero_size_dims(
456+
monitor_data.monitor, self.simulation, sel_kwargs
457+
)
458+
if auto_sel:
459+
field_data = field_data.sel(**auto_sel)
460+
425461
if isinstance(field_data, TetrahedralGridDataset):
426462
raise DataError(
427463
"Must select a two-dimensional slice of unstructured dataset for plotting"

0 commit comments

Comments
 (0)