Skip to content

Commit 7e11342

Browse files
--amend
1 parent 8fcbe0b commit 7e11342

File tree

6 files changed

+162
-255
lines changed

6 files changed

+162
-255
lines changed

CHANGELOG.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99

1010
### Added
11-
- Add automatic structure extrusion for waveports defined on boundaries, controlled by the `extrude_structures` field in `WavePort`.
12-
- The extrusion method, implemented in `TerminalComponentModeler`, ensures that mode sources, absorbers, and PEC frames are fully contained within the extruded structures; extrusion occurs only when `extrude_structures` is set to `True`.
11+
- Added optional automatic extrusion of boundary waveport structures via `WavePort.extrude_structures`, ensuring mode sources, absorbers, and PEC frames are fully contained.
1312
- Added rectangular and radial taper support to `RectangularAntennaArrayCalculator` for phased array amplitude weighting; refactored array factor calculation for improved clarity and performance.
1413
- Selective simulation capabilities to `TerminalComponentModeler` via `run_only` and `element_mappings` fields, allowing users to run fewer simulations and extract only needed scattering matrix elements.
1514
- Added KLayout plugin, with DRC functionality for running design rule checks in `plugins.klayout.drc`. Supports running DRC on GDS files as well as `Geometry`, `Structure`, and `Simulation` objects.

schemas/TerminalComponentModeler.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16108,6 +16108,10 @@
1610816108
],
1610916109
"type": "string"
1611016110
},
16111+
"extrude_structures": {
16112+
"default": false,
16113+
"type": "boolean"
16114+
},
1611116115
"frame": {
1611216116
"allOf": [
1611316117
{

tests/test_plugins/smatrix/test_terminal_component_modeler.py

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1370,40 +1370,44 @@ def test_wave_port_extrusion_coaxial():
13701370
port_1 = ports[0]
13711371
port_2 = ports[1]
13721372
port_1 = port_1.updated_copy(center=(0, 0, -50000), extrude_structures=True)
1373+
1374+
# test that structure extrusion requires an internal absorber (should raise ValidationError)
1375+
with pytest.raises(pd.ValidationError):
1376+
_ = port_2.updated_copy(center=(0, 0, 50000), extrude_structures=True, absorber=False)
1377+
1378+
# define a valid waveport
13731379
port_2 = port_2.updated_copy(center=(0, 0, 50000), extrude_structures=True)
13741380

13751381
# update component modeler
13761382
tcm = tcm.updated_copy(ports=[port_1, port_2])
13771383

13781384
# generate simulations from component modeler
1379-
sims = list(tcm.sim_dict.values())
1385+
sim = tcm.base_sim
13801386

1381-
# loop over simulations
1382-
for sim in sims:
1383-
# get injection axis that would be used to extrude structure
1384-
inj_axis = sim.sources[0].injection_axis
1387+
# get injection axis that would be used to extrude structure
1388+
inj_axis = sim.internal_absorbers[0].size.index(0.0)
13851389

1386-
# get grid boundaries
1387-
bnd_coords = sim.grid.boundaries.to_list[inj_axis]
1390+
# get grid boundaries
1391+
bnd_coords = sim.grid.boundaries.to_list[inj_axis]
13881392

1389-
# get size of structures along injection axis directions
1390-
str_bnds = [
1391-
np.min(sim.structures[0].geometry.geometries[0].slab_bounds),
1392-
np.max(sim.structures[2].geometry.geometries[0].slab_bounds),
1393-
]
1393+
# get size of structures along injection axis directions
1394+
str_bnds = [
1395+
np.min(sim.structures[0].geometry.geometries[1].geometries[0].slab_bounds),
1396+
np.max(sim.structures[0].geometry.geometries[0].slab_bounds),
1397+
]
13941398

1395-
pec_bnds = []
1399+
pec_bnds = []
13961400

1397-
# infer placement of PEC plates beyond internal absorber
1398-
for absorber in sim.internal_absorbers:
1399-
absorber_cntr = absorber.center[inj_axis]
1400-
right_ind = np.searchsorted(bnd_coords, absorber_cntr, side="right")
1401-
left_ind = np.searchsorted(bnd_coords, absorber_cntr, side="left") - 1
1402-
pec_bnds.append(bnd_coords[right_ind + 1])
1403-
pec_bnds.append(bnd_coords[left_ind - 1])
1401+
# infer placement of PEC plates beyond internal absorber
1402+
for absorber in sim.internal_absorbers:
1403+
absorber_cntr = absorber.center[inj_axis]
1404+
right_ind = np.searchsorted(bnd_coords, absorber_cntr, side="right")
1405+
left_ind = np.searchsorted(bnd_coords, absorber_cntr, side="left") - 1
1406+
pec_bnds.append(bnd_coords[right_ind + 1])
1407+
pec_bnds.append(bnd_coords[left_ind - 1])
14041408

1405-
# get range of coordinates along injection axis for PEC plates
1406-
pec_bnds = [np.min(pec_bnds), np.max(pec_bnds)]
1409+
# get range of coordinates along injection axis for PEC plates
1410+
pec_bnds = [np.min(pec_bnds), np.max(pec_bnds)]
14071411

1408-
# ensure that structures were extruded up to PEC plates
1409-
assert all(np.isclose(str_bnd, pec_bnd) for str_bnd, pec_bnd in zip(str_bnds, pec_bnds))
1412+
# ensure that structures were extruded up to PEC plates
1413+
assert all(np.isclose(str_bnd, pec_bnd) for str_bnd, pec_bnd in zip(str_bnds, pec_bnds))

tidy3d/components/simulation.py

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5638,33 +5638,9 @@ def _make_pec_frame(self, obj: Union[ModeSource, InternalAbsorber]) -> Structure
56385638
the frame is added around the injection plane. For internal absorbers, a backing pec
56395639
plate is also added on the non-absorbing side.
56405640
"""
5641-
span_inds = np.array(self.grid.discretize_inds(obj))
5642-
5643-
coords = self.grid.boundaries.to_list
5644-
direction = obj.direction
5645-
if isinstance(obj, ModeSource):
5646-
axis = obj.injection_axis
5647-
length = obj.frame.length
5648-
else:
5649-
axis = obj.size.index(0.0)
5650-
length = 1
5651-
5652-
if direction == "+":
5653-
span_inds[axis][1] += length - 1
5654-
span_inds[axis][0] -= 1
5655-
else:
5656-
span_inds[axis][1] += 1
5657-
span_inds[axis][0] -= length - 1
5658-
5659-
box_bounds = [
5660-
[
5661-
c[beg],
5662-
c[end],
5663-
]
5664-
for c, (beg, end) in zip(coords, span_inds)
5665-
]
56665641

5667-
box = Box.from_bounds(*np.transpose(box_bounds))
5642+
# get pec frame bounding box, object's axis and direction
5643+
(box, axis, direction) = self._pec_frame_box(obj)
56685644

56695645
surfaces = Box.surfaces(box.size, box.center)
56705646
if isinstance(obj, ModeSource):
@@ -5723,3 +5699,30 @@ def _validate_finalized(self):
57235699
"Simulation fails after requested mode source PEC frames are added. "
57245700
"Please inspect '._finalized'."
57255701
)
5702+
5703+
def _pec_frame_box(self, obj: Union[ModeSource, InternalAbsorber]) -> tuple[Box, int, str]:
5704+
"""Return pec bounding box, frame axis and object's direction"""
5705+
5706+
span_inds = np.array(self.grid.discretize_inds(obj))
5707+
5708+
coords = self.grid.boundaries.to_list
5709+
direction = obj.direction
5710+
if isinstance(obj, ModeSource):
5711+
axis = obj.injection_axis
5712+
length = obj.frame.length
5713+
if direction == "+":
5714+
span_inds[axis][1] += length - 1
5715+
else:
5716+
span_inds[axis][0] -= length - 1
5717+
else:
5718+
axis = obj.size.index(0.0)
5719+
5720+
box_bounds = [
5721+
[
5722+
c[beg],
5723+
c[end],
5724+
]
5725+
for c, (beg, end) in zip(coords, span_inds)
5726+
]
5727+
5728+
return (Box.from_bounds(*np.transpose(box_bounds)), axis, direction)

0 commit comments

Comments
 (0)