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 ]]
@@ -3278,13 +3283,13 @@ def _projection_monitors_homogeneous(cls, val, values):
3278
3283
@classmethod
3279
3284
def _get_mediums_on_abc (
3280
3285
cls , boundary_spec , medium , center , size , structures
3281
- ) -> Tuple [
3282
- List [MediumType3D ],
3283
- List [MediumType3D ],
3284
- List [MediumType3D ],
3285
- List [MediumType3D ],
3286
- List [MediumType3D ],
3287
- List [MediumType3D ],
3286
+ ) -> tuple [
3287
+ list [MediumType3D ],
3288
+ list [MediumType3D ],
3289
+ list [MediumType3D ],
3290
+ list [MediumType3D ],
3291
+ list [MediumType3D ],
3292
+ list [MediumType3D ],
3288
3293
]:
3289
3294
"""For each ABC boundary that needs an automatic medium detection (permittivity=None)
3290
3295
determine mediums it crosses.
@@ -3303,7 +3308,7 @@ def _get_mediums_on_abc(
3303
3308
center = structure_bg .geometry .center , size = structure_bg .geometry .size
3304
3309
)
3305
3310
3306
- total_structures = [structure_bg ] + list (structures )
3311
+ total_structures = [structure_bg , * list (structures )]
3307
3312
3308
3313
mediums = []
3309
3314
for boundary , surface in zip (np .ravel (boundary_spec .to_list ), surfaces ):
@@ -4537,7 +4542,7 @@ def mediums(self) -> set[MediumType]:
4537
4542
4538
4543
Returns
4539
4544
-------
4540
- List [:class:`.AbstractMedium`]
4545
+ set [:class:`.AbstractMedium`]
4541
4546
Set of distinct mediums in the simulation.
4542
4547
"""
4543
4548
log .warning (
@@ -4599,12 +4604,12 @@ def intersecting_media(
4599
4604
-------
4600
4605
test_object : :class:`.Box`
4601
4606
Object for which intersecting media are to be detected.
4602
- structures : List [:class:`.AbstractMedium`]
4607
+ structures : tuple [:class:`.AbstractMedium`]
4603
4608
List of structures whose media will be tested.
4604
4609
4605
4610
Returns
4606
4611
-------
4607
- List [:class:`.AbstractMedium`]
4612
+ tuple [:class:`.AbstractMedium`]
4608
4613
Set of distinct mediums that intersect with the given planar object.
4609
4614
"""
4610
4615
@@ -4626,12 +4631,12 @@ def intersecting_structures(
4626
4631
-------
4627
4632
test_object : :class:`.Box`
4628
4633
Object for which intersecting media are to be detected.
4629
- structures : List [:class:`.AbstractMedium`]
4634
+ structures : tuple [:class:`.AbstractMedium`]
4630
4635
List of structures whose media will be tested.
4631
4636
4632
4637
Returns
4633
4638
-------
4634
- List [:class:`.Structure`]
4639
+ tuple [:class:`.Structure`]
4635
4640
Set of distinct structures that intersect with the given surface, or with the surfaces
4636
4641
of the given volume.
4637
4642
"""
@@ -5444,3 +5449,101 @@ def from_scene(cls, scene: Scene, **kwargs) -> Simulation:
5444
5449
)
5445
5450
5446
5451
_boundaries_for_zero_dims = validate_boundaries_for_zero_dims ()
5452
+
5453
+ def _make_pec_frame (self , mode_source ) -> Structure :
5454
+ """Make a pec frame around a mode source."""
5455
+
5456
+ coords = self .grid .boundaries .to_list
5457
+ axis = mode_source .injection_axis
5458
+ direction = mode_source .direction
5459
+ length = mode_source .pec_frame
5460
+
5461
+ span_inds = np .array (self .grid .discretize_inds (mode_source ))
5462
+ if direction == "+" :
5463
+ span_inds [axis ][1 ] += length - 1
5464
+ else :
5465
+ span_inds [axis ][0 ] -= length - 1
5466
+
5467
+ box_bounds = [
5468
+ [
5469
+ c [beg ],
5470
+ c [end ],
5471
+ ]
5472
+ for c , (beg , end ) in zip (coords , span_inds )
5473
+ ]
5474
+
5475
+ prev_cell = span_inds [axis ][0 ] - 1
5476
+ if prev_cell >= 0 :
5477
+ box_bounds [axis ][0 ] = (1 - MODE_PEC_FRAME_THICKNESS ) * box_bounds [axis ][0 ] + MODE_PEC_FRAME_THICKNESS * coords [axis ][prev_cell ]
5478
+
5479
+ next_cell = span_inds [axis ][1 ] + 1
5480
+ if next_cell <= len (coords [axis ]) - 1 :
5481
+ box_bounds [axis ][1 ] = (1 - MODE_PEC_FRAME_THICKNESS ) * box_bounds [axis ][1 ] + MODE_PEC_FRAME_THICKNESS * coords [axis ][next_cell ]
5482
+
5483
+ box = Box .from_bounds (* np .transpose (box_bounds ))
5484
+
5485
+ surfaces = Box .surfaces (box .size , box .center )
5486
+ del surfaces [2 * axis : 2 * axis + 2 ]
5487
+
5488
+ structure = Structure (
5489
+ geometry = GeometryGroup (
5490
+ geometries = surfaces ,
5491
+ ),
5492
+ medium = PECMedium (),
5493
+ )
5494
+
5495
+
5496
+ # bounds_outer = [
5497
+ # [
5498
+ # (1 - MODE_PEC_FRAME_THICKNESS) * c[beg]
5499
+ # + MODE_PEC_FRAME_THICKNESS * c[max(0, beg - 1)],
5500
+ # (1 - MODE_PEC_FRAME_THICKNESS) * c[end]
5501
+ # + MODE_PEC_FRAME_THICKNESS * c[min(len(c) - 1, end + 1)],
5502
+ # ]
5503
+ # for c, (beg, end) in zip(coords, span_inds)
5504
+ # ]
5505
+ # bounds_inner = [
5506
+ # [
5507
+ # (1 - MODE_PEC_FRAME_THICKNESS) * c[beg] + MODE_PEC_FRAME_THICKNESS * c[beg + 1],
5508
+ # (1 - MODE_PEC_FRAME_THICKNESS) * c[end] + MODE_PEC_FRAME_THICKNESS * c[end - 1],
5509
+ # ]
5510
+ # for c, (beg, end) in zip(coords, span_inds)
5511
+ # ]
5512
+ # bounds_inner[axis] = [-inf, inf]
5513
+ # structure = Structure(
5514
+ # geometry=ClipOperation(
5515
+ # geometry_a=Box.from_bounds(*np.transpose(bounds_outer)),
5516
+ # geometry_b=Box.from_bounds(*np.transpose(bounds_inner)),
5517
+ # operation="difference",
5518
+ # ),
5519
+ # medium=PECMedium(),
5520
+ # )
5521
+ return structure
5522
+
5523
+ @cached_property
5524
+ def with_mode_source_pec_frames (self ) -> Simulation :
5525
+ """Return an instance with added pec frames around mode sources."""
5526
+
5527
+ pec_frames = [
5528
+ self ._make_pec_frame (src )
5529
+ for src in self .sources
5530
+ if isinstance (src , ModeSource ) and src .pec_frame > 0
5531
+ ]
5532
+
5533
+ if len (pec_frames ) == 0 :
5534
+ return self
5535
+
5536
+ return self .updated_copy (
5537
+ grid_spec = GridSpec .from_grid (self .grid ), structures = list (self .structures ) + pec_frames
5538
+ )
5539
+
5540
+ def _validate_with_mode_source_pec_frames (self ):
5541
+ """Validate that after adding pec frames simulation setup is still valid."""
5542
+
5543
+ try :
5544
+ _ = self .with_mode_source_pec_frames
5545
+ except Exception :
5546
+ log .error (
5547
+ "Simulation fails after requested mode source PEC frames are added. "
5548
+ "Please inspec '.with_mode_source_pec_frames'."
5549
+ )
0 commit comments