49
49
from .data .unstructured .tetrahedral import TetrahedralGridDataset
50
50
from .data .unstructured .triangular import TriangularGridDataset
51
51
from .data .utils import CustomSpatialDataType
52
- from .geometry .base import Box , Geometry
52
+ from .geometry .base import Box , ClipOperation , Geometry , GeometryGroup
53
53
from .geometry .mesh import TriangleMesh
54
54
from .geometry .utils import flatten_groups , traverse_geometries
55
55
from .geometry .utils_2d import get_bounds , get_thickened_geom , snap_coordinate_to_grid , subdivide
67
67
Medium2D ,
68
68
MediumType ,
69
69
MediumType3D ,
70
+ PECMedium ,
70
71
)
71
72
from .monitor import (
72
73
AbstractFieldProjectionMonitor ,
175
176
# RF frequency warning
176
177
RF_FREQ_WARNING = 300e9
177
178
179
+ # length and thickness of optional PEC frames around mode sources (in cells)
180
+ MODE_PEC_FRAME_LENGTH = 2
181
+ MODE_PEC_FRAME_THICKNESS = 1e-3
182
+
178
183
179
184
def validate_boundaries_for_zero_dims (warn_on_change : bool = True ):
180
185
"""Error if absorbing boundaries, bloch boundaries, unmatching pec/pmc, or symmetry is used along a zero dimension."""
@@ -849,7 +854,7 @@ def pml_thicknesses(self) -> list[tuple[float, float]]:
849
854
850
855
Returns
851
856
-------
852
- List [Tuple[float, float]]
857
+ list [Tuple[float, float]]
853
858
List containing the absorber thickness (micron) in - and + boundaries.
854
859
"""
855
860
num_layers = self .num_pml_layers
@@ -867,7 +872,7 @@ def internal_override_structures(self) -> list[MeshOverrideStructure]:
867
872
868
873
Returns
869
874
-------
870
- List [MeshOverrideSructure]
875
+ list [MeshOverrideSructure]
871
876
List of override structures.
872
877
"""
873
878
wavelength = self .grid_spec .get_wavelength (self .sources )
@@ -884,7 +889,7 @@ def internal_snapping_points(self) -> list[CoordinateOptional]:
884
889
885
890
Returns
886
891
-------
887
- List [CoordinateOptional]
892
+ list [CoordinateOptional]
888
893
List of snapping points coordinates.
889
894
"""
890
895
return self .grid_spec .internal_snapping_points (
@@ -1223,7 +1228,7 @@ def _grid_and_snapping_lines(self) -> tuple[Grid, list[CoordinateOptional]]:
1223
1228
1224
1229
Returns
1225
1230
-------
1226
- Tuple[:class:`.Grid`, List [CoordinateOptional]]
1231
+ Tuple[:class:`.Grid`, list [CoordinateOptional]]
1227
1232
:class:`.Grid` storing the spatial locations relevant to the simulation
1228
1233
the list of snapping points generated during iterative gap meshing.
1229
1234
"""
@@ -1281,7 +1286,7 @@ def _gap_meshing_snapping_lines(self) -> list[CoordinateOptional]:
1281
1286
1282
1287
Returns
1283
1288
-------
1284
- List [CoordinateOptional]
1289
+ list [CoordinateOptional]
1285
1290
List of snapping lines resolving thin gaps and strips.
1286
1291
"""
1287
1292
@@ -1345,7 +1350,7 @@ def num_pml_layers(self) -> list[tuple[float, float]]:
1345
1350
1346
1351
Returns
1347
1352
-------
1348
- List [Tuple[float, float]]
1353
+ list [Tuple[float, float]]
1349
1354
List containing the number of absorber layers in - and + boundaries.
1350
1355
"""
1351
1356
num_layers = [[0 , 0 ], [0 , 0 ], [0 , 0 ]]
@@ -3285,13 +3290,13 @@ def _projection_monitors_homogeneous(cls, val, values):
3285
3290
@classmethod
3286
3291
def _get_mediums_on_abc (
3287
3292
cls , boundary_spec , medium , center , size , structures
3288
- ) -> Tuple [
3289
- List [MediumType3D ],
3290
- List [MediumType3D ],
3291
- List [MediumType3D ],
3292
- List [MediumType3D ],
3293
- List [MediumType3D ],
3294
- List [MediumType3D ],
3293
+ ) -> tuple [
3294
+ list [MediumType3D ],
3295
+ list [MediumType3D ],
3296
+ list [MediumType3D ],
3297
+ list [MediumType3D ],
3298
+ list [MediumType3D ],
3299
+ list [MediumType3D ],
3295
3300
]:
3296
3301
"""For each ABC boundary that needs an automatic medium detection (permittivity=None)
3297
3302
determine mediums it crosses.
@@ -3310,7 +3315,7 @@ def _get_mediums_on_abc(
3310
3315
center = structure_bg .geometry .center , size = structure_bg .geometry .size
3311
3316
)
3312
3317
3313
- total_structures = [structure_bg ] + list (structures )
3318
+ total_structures = [structure_bg , * list (structures )]
3314
3319
3315
3320
mediums = []
3316
3321
for boundary , surface in zip (np .ravel (boundary_spec .to_list ), surfaces ):
@@ -4544,7 +4549,7 @@ def mediums(self) -> set[MediumType]:
4544
4549
4545
4550
Returns
4546
4551
-------
4547
- List [:class:`.AbstractMedium`]
4552
+ set [:class:`.AbstractMedium`]
4548
4553
Set of distinct mediums in the simulation.
4549
4554
"""
4550
4555
log .warning (
@@ -4606,12 +4611,12 @@ def intersecting_media(
4606
4611
-------
4607
4612
test_object : :class:`.Box`
4608
4613
Object for which intersecting media are to be detected.
4609
- structures : List [:class:`.AbstractMedium`]
4614
+ structures : tuple [:class:`.AbstractMedium`]
4610
4615
List of structures whose media will be tested.
4611
4616
4612
4617
Returns
4613
4618
-------
4614
- List [:class:`.AbstractMedium`]
4619
+ tuple [:class:`.AbstractMedium`]
4615
4620
Set of distinct mediums that intersect with the given planar object.
4616
4621
"""
4617
4622
@@ -4633,12 +4638,12 @@ def intersecting_structures(
4633
4638
-------
4634
4639
test_object : :class:`.Box`
4635
4640
Object for which intersecting media are to be detected.
4636
- structures : List [:class:`.AbstractMedium`]
4641
+ structures : tuple [:class:`.AbstractMedium`]
4637
4642
List of structures whose media will be tested.
4638
4643
4639
4644
Returns
4640
4645
-------
4641
- List [:class:`.Structure`]
4646
+ tuple [:class:`.Structure`]
4642
4647
Set of distinct structures that intersect with the given surface, or with the surfaces
4643
4648
of the given volume.
4644
4649
"""
@@ -5451,3 +5456,101 @@ def from_scene(cls, scene: Scene, **kwargs) -> Simulation:
5451
5456
)
5452
5457
5453
5458
_boundaries_for_zero_dims = validate_boundaries_for_zero_dims ()
5459
+
5460
+ def _make_pec_frame (self , mode_source ) -> Structure :
5461
+ """Make a pec frame around a mode source."""
5462
+
5463
+ coords = self .grid .boundaries .to_list
5464
+ axis = mode_source .injection_axis
5465
+ direction = mode_source .direction
5466
+ length = mode_source .pec_frame
5467
+
5468
+ span_inds = np .array (self .grid .discretize_inds (mode_source ))
5469
+ if direction == "+" :
5470
+ span_inds [axis ][1 ] += length - 1
5471
+ else :
5472
+ span_inds [axis ][0 ] -= length - 1
5473
+
5474
+ box_bounds = [
5475
+ [
5476
+ c [beg ],
5477
+ c [end ],
5478
+ ]
5479
+ for c , (beg , end ) in zip (coords , span_inds )
5480
+ ]
5481
+
5482
+ prev_cell = span_inds [axis ][0 ] - 1
5483
+ if prev_cell >= 0 :
5484
+ box_bounds [axis ][0 ] = (1 - MODE_PEC_FRAME_THICKNESS ) * box_bounds [axis ][0 ] + MODE_PEC_FRAME_THICKNESS * coords [axis ][prev_cell ]
5485
+
5486
+ next_cell = span_inds [axis ][1 ] + 1
5487
+ if next_cell <= len (coords [axis ]) - 1 :
5488
+ box_bounds [axis ][1 ] = (1 - MODE_PEC_FRAME_THICKNESS ) * box_bounds [axis ][1 ] + MODE_PEC_FRAME_THICKNESS * coords [axis ][next_cell ]
5489
+
5490
+ box = Box .from_bounds (* np .transpose (box_bounds ))
5491
+
5492
+ surfaces = Box .surfaces (box .size , box .center )
5493
+ del surfaces [2 * axis : 2 * axis + 2 ]
5494
+
5495
+ structure = Structure (
5496
+ geometry = GeometryGroup (
5497
+ geometries = surfaces ,
5498
+ ),
5499
+ medium = PECMedium (),
5500
+ )
5501
+
5502
+
5503
+ # bounds_outer = [
5504
+ # [
5505
+ # (1 - MODE_PEC_FRAME_THICKNESS) * c[beg]
5506
+ # + MODE_PEC_FRAME_THICKNESS * c[max(0, beg - 1)],
5507
+ # (1 - MODE_PEC_FRAME_THICKNESS) * c[end]
5508
+ # + MODE_PEC_FRAME_THICKNESS * c[min(len(c) - 1, end + 1)],
5509
+ # ]
5510
+ # for c, (beg, end) in zip(coords, span_inds)
5511
+ # ]
5512
+ # bounds_inner = [
5513
+ # [
5514
+ # (1 - MODE_PEC_FRAME_THICKNESS) * c[beg] + MODE_PEC_FRAME_THICKNESS * c[beg + 1],
5515
+ # (1 - MODE_PEC_FRAME_THICKNESS) * c[end] + MODE_PEC_FRAME_THICKNESS * c[end - 1],
5516
+ # ]
5517
+ # for c, (beg, end) in zip(coords, span_inds)
5518
+ # ]
5519
+ # bounds_inner[axis] = [-inf, inf]
5520
+ # structure = Structure(
5521
+ # geometry=ClipOperation(
5522
+ # geometry_a=Box.from_bounds(*np.transpose(bounds_outer)),
5523
+ # geometry_b=Box.from_bounds(*np.transpose(bounds_inner)),
5524
+ # operation="difference",
5525
+ # ),
5526
+ # medium=PECMedium(),
5527
+ # )
5528
+ return structure
5529
+
5530
+ @cached_property
5531
+ def with_mode_source_pec_frames (self ) -> Simulation :
5532
+ """Return an instance with added pec frames around mode sources."""
5533
+
5534
+ pec_frames = [
5535
+ self ._make_pec_frame (src )
5536
+ for src in self .sources
5537
+ if isinstance (src , ModeSource ) and src .pec_frame > 0
5538
+ ]
5539
+
5540
+ if len (pec_frames ) == 0 :
5541
+ return self
5542
+
5543
+ return self .updated_copy (
5544
+ grid_spec = GridSpec .from_grid (self .grid ), structures = list (self .structures ) + pec_frames
5545
+ )
5546
+
5547
+ def _validate_with_mode_source_pec_frames (self ):
5548
+ """Validate that after adding pec frames simulation setup is still valid."""
5549
+
5550
+ try :
5551
+ _ = self .with_mode_source_pec_frames
5552
+ except Exception :
5553
+ log .error (
5554
+ "Simulation fails after requested mode source PEC frames are added. "
5555
+ "Please inspec '.with_mode_source_pec_frames'."
5556
+ )
0 commit comments