Skip to content

Commit c2f5d0f

Browse files
New field priority in Structure and structure_priority_mode in Scene
1 parent e6ed4b2 commit c2f5d0f

File tree

11 files changed

+195
-31
lines changed

11 files changed

+195
-31
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- Added `eps_lim` keyword argument to `Simulation.plot_eps()` for manual control over the permittivity color limits.
1313
- Added `thickness` parameter to `LossyMetalMedium` for computing surface impedance of a thin conductor.
14+
- `priority` field in `Structure` and `MeshOverrideStructure` for setting the behavior in structure overlapping region. When its value is `None`, the priority is automatically determined based on the material property and simulation's `structure_priority_mode`.
1415

1516
### Changed
1617
- Relaxed bounds checking of path integrals during `WavePort` validation.

tests/test_components/test_scene.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,3 +307,48 @@ def test_max_geometry_validation():
307307
]
308308
with pytest.raises(pd.ValidationError, match=f" {MAX_GEOMETRY_COUNT + 2} "):
309309
_ = td.Scene(structures=not_fine)
310+
311+
312+
def test_structure_manual_priority():
313+
"""make sure structure is properly orderd based on the priority settings."""
314+
315+
box = td.Structure(
316+
geometry=td.Box(size=(1, 1, 1), center=(0, 0, 0)),
317+
medium=td.Medium(permittivity=2.0),
318+
)
319+
structures = []
320+
priorities = [2, 4, -1, -4, 0]
321+
for priority in priorities:
322+
structures.append(box.updated_copy(priority=priority))
323+
scene = td.Scene(
324+
structures=structures,
325+
)
326+
327+
sorted_priorities = [s.priority for s in scene.sorted_structures]
328+
assert all(np.diff(sorted_priorities) >= 0)
329+
330+
331+
def test_structure_automatic_priority():
332+
"""make sure metallic structure has the highest priority in `conductor` mode."""
333+
334+
box = td.Structure(
335+
geometry=td.Box(size=(1, 1, 1), center=(0, 0, 0)),
336+
medium=td.Medium(permittivity=2.0),
337+
)
338+
box_pec = box.updated_copy(medium=td.PEC)
339+
box_lossymetal = box.updated_copy(
340+
medium=td.LossyMetalMedium(conductivity=1.0, frequency_range=(1e14, 2e14))
341+
)
342+
structures = [box_pec, box_lossymetal, box]
343+
scene = td.Scene(
344+
structures=structures,
345+
structure_priority_mode="equal",
346+
)
347+
348+
# in equal mode, the order is preserved
349+
scene.sorted_structures == structures
350+
351+
# conductor mode
352+
scene = scene.updated_copy(structure_priority_mode="conductor")
353+
assert scene.sorted_structures[-1].medium == td.PEC
354+
assert isinstance(scene.sorted_structures[-2].medium, td.LossyMetalMedium)

tidy3d/components/base_sim/simulation.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from ..medium import Medium, MediumType3D
1717
from ..scene import Scene
1818
from ..structure import Structure
19-
from ..types import TYPE_TAG_STR, Ax, Axis, Bound, LengthUnit, Symmetry
19+
from ..types import TYPE_TAG_STR, Ax, Axis, Bound, LengthUnit, PriorityMode, Symmetry
2020
from ..validators import (
2121
_warn_unsupported_traced_argument,
2222
assert_objects_in_sim_bounds,
@@ -119,6 +119,15 @@ class AbstractSimulation(Box, ABC):
119119
"include the desired unit specifier in labels.",
120120
)
121121

122+
structure_priority_mode: PriorityMode = pd.Field(
123+
"equal",
124+
title="Structure Priority Setting",
125+
description="This field only affects structures of `priority=None`. "
126+
"If `equal`, the priority of those structures is set to 0; if `conductor`, "
127+
"the priority of structures made of `LossyMetalMedium` is set to 90, "
128+
"`PECMedium` to 100, and others to 0.",
129+
)
130+
122131
""" Validating setup """
123132

124133
@pd.root_validator(pre=True)
@@ -191,7 +200,10 @@ def scene(self) -> Scene:
191200
"""Scene instance associated with the simulation."""
192201

193202
return Scene(
194-
medium=self.medium, structures=self.structures, plot_length_units=self.plot_length_units
203+
medium=self.medium,
204+
structures=self.structures,
205+
plot_length_units=self.plot_length_units,
206+
structure_priority_mode=self.structure_priority_mode,
195207
)
196208

197209
def get_monitor_by_name(self, name: str) -> AbstractMonitor:

tidy3d/components/grid/grid_spec.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
Axis,
2323
Coordinate,
2424
CoordinateOptional,
25+
PriorityMode,
2526
Symmetry,
2627
annotate_type,
2728
)
@@ -959,6 +960,7 @@ def override_structure(
959960
dl=dl_list,
960961
shadow=False,
961962
drop_outside_sim=drop_outside_sim,
963+
priority=-1,
962964
)
963965

964966

@@ -1518,6 +1520,7 @@ def _override_structures_along_axis(
15181520
dl=self._unpop_axis(ax_coord=dl, plane_coord=None),
15191521
shadow=False,
15201522
drop_outside_sim=self.refinement_inside_sim_only,
1523+
priority=-1,
15211524
)
15221525
)
15231526

@@ -2293,10 +2296,11 @@ def all_override_structures(
22932296
wavelength: pd.PositiveFloat,
22942297
sim_size: Tuple[float, 3],
22952298
lumped_elements: List[LumpedElementType],
2299+
structure_priority_mode: PriorityMode = "equal",
22962300
internal_override_structures: List[MeshOverrideStructure] = None,
22972301
) -> List[StructureType]:
2298-
"""Internal and external mesh override structures. External override structures take higher priority.
2299-
So far, internal override structures all come from `layer_refinement_specs`.
2302+
"""Internal and external mesh override structures sorted based on their priority. By default,
2303+
the priority of internal override structures is -1, and 0 for external ones.
23002304
23012305
Parameters
23022306
----------
@@ -2308,22 +2312,23 @@ def all_override_structures(
23082312
Simulation domain size.
23092313
lumped_elements : List[LumpedElementType]
23102314
List of lumped elements.
2315+
structure_priority_mode : PriorityMode
2316+
Structure priority setting.
23112317
internal_override_structures : List[MeshOverrideStructure]
23122318
If `None`, recomputes internal override structures.
23132319
23142320
Returns
23152321
-------
23162322
List[StructureType]
2317-
List of override structures.
2323+
List of sorted override structures.
23182324
"""
23192325

23202326
if internal_override_structures is None:
2321-
return (
2322-
self.internal_override_structures(structures, wavelength, sim_size, lumped_elements)
2323-
+ self.external_override_structures
2327+
internal_override_structures = self.internal_override_structures(
2328+
structures, wavelength, sim_size, lumped_elements
23242329
)
2325-
2326-
return internal_override_structures + self.external_override_structures
2330+
all_structures = internal_override_structures + self.external_override_structures
2331+
return Structure._sort_structures(all_structures, structure_priority_mode)
23272332

23282333
def _min_vacuum_dl_in_autogrid(self, wavelength: float, sim_size: Tuple[float, 3]) -> float:
23292334
"""Compute grid step size in vacuum for Autogrd. If AutoGrid is applied along more than 1 dimension,
@@ -2398,6 +2403,7 @@ def make_grid(
23982403
[None, None],
23992404
[None, None],
24002405
],
2406+
structure_priority_mode: PriorityMode = "equal",
24012407
) -> Grid:
24022408
"""Make the entire simulation grid based on some simulation parameters.
24032409
@@ -2425,6 +2431,8 @@ def make_grid(
24252431
boundary_types : Tuple[Tuple[str, str], Tuple[str, str], Tuple[str, str]] = [[None, None], [None, None], [None, None]]
24262432
Type of boundary conditions along each dimension: "pec/pmc", "periodic", or
24272433
None for any other. This is relevant only for gap meshing.
2434+
structure_priority_mode : PriorityMode
2435+
Structure priority setting.
24282436
24292437
Returns
24302438
-------
@@ -2441,6 +2449,7 @@ def make_grid(
24412449
lumped_elements=lumped_elements,
24422450
internal_override_structures=internal_override_structures,
24432451
internal_snapping_points=internal_snapping_points,
2452+
structure_priority_mode=structure_priority_mode,
24442453
)
24452454

24462455
return grid
@@ -2460,6 +2469,7 @@ def _make_grid_and_snapping_lines(
24602469
[None, None],
24612470
[None, None],
24622471
],
2472+
structure_priority_mode: PriorityMode = "equal",
24632473
) -> Tuple[Grid, List[CoordinateOptional]]:
24642474
"""Make the entire simulation grid based on some simulation parameters.
24652475
Also return snappiung point resulted from iterative gap meshing.
@@ -2488,6 +2498,8 @@ def _make_grid_and_snapping_lines(
24882498
boundary_types : Tuple[Tuple[str, str], Tuple[str, str], Tuple[str, str]] = [[None, None], [None, None], [None, None]]
24892499
Type of boundary conditions along each dimension: "pec/pmc", "periodic", or
24902500
None for any other. This is relevant only for gap meshing.
2501+
structure_priority_mode : PriorityMode
2502+
Structure priority setting.
24912503
24922504
Returns
24932505
-------
@@ -2504,6 +2516,7 @@ def _make_grid_and_snapping_lines(
25042516
lumped_elements=lumped_elements,
25052517
internal_override_structures=internal_override_structures,
25062518
internal_snapping_points=internal_snapping_points,
2519+
structure_priority_mode=structure_priority_mode,
25072520
)
25082521

25092522
sim_geometry = structures[0].geometry
@@ -2549,6 +2562,7 @@ def _make_grid_and_snapping_lines(
25492562
internal_override_structures=internal_override_structures,
25502563
internal_snapping_points=snapping_lines + internal_snapping_points,
25512564
dl_min_from_gaps=0.45 * min_gap_width,
2565+
structure_priority_mode=structure_priority_mode,
25522566
)
25532567

25542568
same = old_grid == new_grid
@@ -2575,6 +2589,7 @@ def _make_grid_one_iteration(
25752589
internal_override_structures: List[MeshOverrideStructure] = None,
25762590
internal_snapping_points: List[CoordinateOptional] = None,
25772591
dl_min_from_gaps: pd.PositiveFloat = inf,
2592+
structure_priority_mode: PriorityMode = "equal",
25782593
) -> Grid:
25792594
"""Make the entire simulation grid based on some simulation parameters.
25802595
@@ -2601,7 +2616,8 @@ def _make_grid_one_iteration(
26012616
If `None`, recomputes internal snapping points.
26022617
dl_min_from_gaps : pd.PositiveFloat
26032618
Minimal grid size computed based on autodetected gaps.
2604-
2619+
structure_priority_mode : PriorityMode
2620+
Structure priority setting.
26052621
26062622
Returns
26072623
-------
@@ -2664,6 +2680,7 @@ def _make_grid_one_iteration(
26642680
wavelength,
26652681
sim_size,
26662682
lumped_elements,
2683+
structure_priority_mode,
26672684
internal_override_structures,
26682685
)
26692686

tidy3d/components/lumped_element.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ def to_mesh_overrides(self) -> list[MeshOverrideStructure]:
203203
geometry=Box(center=self.center, size=override_size),
204204
dl=(dl, dl, dl),
205205
shadow=False,
206+
priority=-1,
206207
)
207208
]
208209

@@ -408,6 +409,7 @@ def to_mesh_overrides(self) -> list[MeshOverrideStructure]:
408409
geometry=Box(center=self.center, size=override_size),
409410
dl=override_dl,
410411
shadow=False,
412+
priority=-1,
411413
)
412414
]
413415

tidy3d/components/scene.py

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
InterpMethod,
5555
LengthUnit,
5656
PermittivityComponent,
57+
PriorityMode,
5758
Shapely,
5859
Size,
5960
)
@@ -106,8 +107,20 @@ class Scene(Tidy3dBaseModel):
106107
(),
107108
title="Structures",
108109
description="Tuple of structures present in scene. "
109-
"Note: Structures defined later in this list override the "
110-
"simulation material properties in regions of spatial overlap.",
110+
"Note: In regions of spatial overlap between structures, "
111+
"material properties are dictated by structure of higher priority. "
112+
"The priority for structure of `priority=None` is set automatically "
113+
"based on `structure_priority_mode`. For structures of equal priority, "
114+
"the structure added later to the structure list takes precedence.",
115+
)
116+
117+
structure_priority_mode: PriorityMode = pd.Field(
118+
"equal",
119+
title="Structure Priority Setting",
120+
description="This field only affects structures of `priority=None`. "
121+
"If `equal`, the priority of those structures is set to 0; if `conductor`, "
122+
"the priority of structures made of `LossyMetalMedium` is set to 90, "
123+
"`PECMedium` to 100, and others to 0.",
111124
)
112125

113126
plot_length_units: Optional[LengthUnit] = pd.Field(
@@ -245,6 +258,17 @@ def medium_map(self) -> Dict[StructureMediumType, pd.NonNegativeInt]:
245258

246259
return {medium: index for index, medium in enumerate(self.mediums)}
247260

261+
@cached_property
262+
def sorted_structures(self) -> List[Structure]:
263+
"""Returns a list of sorted structures based on their priority.In the sorted list,
264+
latter added structures take higher priority.
265+
266+
Returns
267+
-------
268+
List[:class:`.Structure`]
269+
"""
270+
return Structure._sort_structures(self.structures, self.structure_priority_mode)
271+
248272
@cached_property
249273
def background_structure(self) -> Structure:
250274
"""Returns structure representing the background of the :class:`.Scene`."""
@@ -254,7 +278,7 @@ def background_structure(self) -> Structure:
254278
@cached_property
255279
def all_structures(self) -> List[Structure]:
256280
"""List of all structures in the simulation including the background."""
257-
return [self.background_structure] + list(self.structures)
281+
return [self.background_structure] + self.sorted_structures
258282

259283
@staticmethod
260284
def intersecting_media(
@@ -443,7 +467,7 @@ def plot_structures(
443467
"""
444468

445469
medium_shapes = self._get_structures_2dbox(
446-
structures=self.to_static().structures, x=x, y=y, z=z, hlim=hlim, vlim=vlim
470+
structures=self.to_static().sorted_structures, x=x, y=y, z=z, hlim=hlim, vlim=vlim
447471
)
448472
medium_map = self.medium_map
449473
for medium, shape in medium_shapes:
@@ -889,7 +913,7 @@ def plot_structures_property(
889913
The supplied or created matplotlib axes.
890914
"""
891915

892-
structures = self.structures
916+
structures = self.sorted_structures
893917

894918
# alpha is None just means plot without any transparency
895919
if alpha is None:
@@ -1466,7 +1490,7 @@ def plot_structures_heat_charge_property(
14661490
The supplied or created matplotlib axes.
14671491
"""
14681492

1469-
structures = self.structures
1493+
structures = self.sorted_structures
14701494

14711495
# alpha is None just means plot without any transparency
14721496
if alpha is None:
@@ -1744,7 +1768,7 @@ def perturbed_mediums_copy(
17441768
"""
17451769

17461770
scene_dict = self.dict()
1747-
structures = self.structures
1771+
structures = self.sorted_structures
17481772
array_dict = {
17491773
"temperature": temperature,
17501774
"electron_density": electron_density,
@@ -1795,7 +1819,7 @@ def doping_bounds(self):
17951819
acceptors_lims = [1e50, -1e50]
17961820
donors_lims = [1e50, -1e50]
17971821

1798-
for struct in [self.background_structure] + list(self.structures):
1822+
for struct in self.all_structures:
17991823
if isinstance(struct.medium.charge, SemiconductorMedium):
18001824
electric_spec = struct.medium.charge
18011825
for doping, limits in zip(

0 commit comments

Comments
 (0)