emerge 0.6.6__py3-none-any.whl → 0.6.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of emerge might be problematic. Click here for more details.

emerge/__init__.py CHANGED
@@ -18,7 +18,7 @@ along with this program; if not, see
18
18
  """
19
19
  import os
20
20
 
21
- __version__ = "0.6.6"
21
+ __version__ = "0.6.8"
22
22
 
23
23
  ############################################################
24
24
  # HANDLE ENVIRONMENT VARIABLES #
@@ -45,7 +45,7 @@ LOG_CONTROLLER.set_default()
45
45
  logger.debug('Importing modules')
46
46
 
47
47
  from ._emerge.simmodel import Simulation
48
- from ._emerge.material import Material
48
+ from ._emerge.material import Material, FreqCoordDependent, FreqDependent, CoordDependent
49
49
  from ._emerge import bc
50
50
  from ._emerge.solver import SolverBicgstab, SolverGMRES, SolveRoutine, ReverseCuthillMckee, Sorter, SolverPardiso, SolverUMFPACK, SolverSuperLU, EMSolver
51
51
  from ._emerge.cs import CoordinateSystem, CS, GCS, Plane, Axis, XAX, YAX, ZAX, XYPLANE, XZPLANE, YZPLANE, YXPLANE, ZXPLANE, ZYPLANE
@@ -22,7 +22,7 @@ _COMPILE_MESSAGE = """
22
22
  [ EMERGE ]
23
23
  ⚠ Numba is compiling optimized code; this may take a few minutes.
24
24
  • Additional functions may be compiled on-the-fly.
25
- • Compilation happens only oncesubsequent runs load from cache.
25
+ • Compilation happens only once-subsequent runs load from cache.
26
26
  Please wait…"""
27
27
 
28
28
  @njit(cache=True)
@@ -64,7 +64,7 @@ class FEMBasis:
64
64
  def interpolate(self, field: np.ndarray, xs: np.ndarray, ys: np.ndarray, zs: np.ndarray, tetids: np.ndarray | None = None) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
65
65
  raise NotImplementedError()
66
66
 
67
- def interpolate_curl(self, field: np.ndarray, xs: np.ndarray, ys: np.ndarray, zs: np.ndarray, constants: np.ndarray, tetids: np.ndarray | None = None) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
67
+ def interpolate_curl(self, field: np.ndarray, xs: np.ndarray, ys: np.ndarray, zs: np.ndarray, constants: np.ndarray, tetids: np.ndarray | None = None, usenan: bool = True) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
68
68
  """
69
69
  Interpolates the curl of the field at the given points.
70
70
  """
@@ -151,7 +151,8 @@ class FEMBasis:
151
151
  def interpolate_index(self, xs: np.ndarray,
152
152
  ys: np.ndarray,
153
153
  zs: np.ndarray,
154
- tetids: np.ndarray | None = None) -> np.ndarray:
154
+ tetids: np.ndarray | None = None,
155
+ usenan: bool = True) -> np.ndarray:
155
156
  raise NotImplementedError()
156
157
 
157
158
  def map_edge_to_field(self, edge_ids: np.ndarray) -> np.ndarray:
@@ -27,9 +27,8 @@ def index_interp(coords: np.ndarray,
27
27
  # Solution has shape (nEdges, nsols)
28
28
  nNodes = coords.shape[1]
29
29
 
30
- prop = np.zeros((nNodes, ), dtype=np.int64)
30
+ prop = np.full((nNodes, ), -1, dtype=np.int64)
31
31
 
32
-
33
32
  for i_iter in range(tetids.shape[0]):
34
33
  itet = tetids[i_iter]
35
34
 
@@ -130,10 +130,10 @@ def ned2_tet_interp(coords: np.ndarray,
130
130
  ys = coords[1,:]
131
131
  zs = coords[2,:]
132
132
 
133
- Ex = np.zeros((nNodes, ), dtype=np.complex128)
134
- Ey = np.zeros((nNodes, ), dtype=np.complex128)
135
- Ez = np.zeros((nNodes, ), dtype=np.complex128)
136
-
133
+ Ex = np.full((nNodes, ), np.nan, dtype=np.complex128)
134
+ Ey = np.full((nNodes, ), np.nan, dtype=np.complex128)
135
+ Ez = np.full((nNodes, ), np.nan, dtype=np.complex128)
136
+
137
137
  for i_iter in range(tetids.shape[0]):
138
138
  itet = tetids[i_iter]
139
139
 
@@ -259,9 +259,9 @@ def ned2_tet_interp_curl(coords: np.ndarray,
259
259
  ys = coords[1,:]
260
260
  zs = coords[2,:]
261
261
 
262
- Ex = np.zeros((nNodes, ), dtype=np.complex128)
263
- Ey = np.zeros((nNodes, ), dtype=np.complex128)
264
- Ez = np.zeros((nNodes, ), dtype=np.complex128)
262
+ Ex = np.full((nNodes, ), np.nan, dtype=np.complex128)
263
+ Ey = np.full((nNodes, ), np.nan, dtype=np.complex128)
264
+ Ez = np.full((nNodes, ), np.nan, dtype=np.complex128)
265
265
 
266
266
  for i_iter in range(tetids.shape[0]):
267
267
  itet = tetids[i_iter]
@@ -382,9 +382,9 @@ def ned2_tri_interp(coords: np.ndarray,
382
382
  xs = coords[0,:]
383
383
  ys = coords[1,:]
384
384
 
385
- Ex = np.zeros((nNodes, ), dtype=np.complex128)
386
- Ey = np.zeros((nNodes, ), dtype=np.complex128)
387
- Ez = np.zeros((nNodes, ), dtype=np.complex128)
385
+ Ex = np.full((nNodes, ), np.nan, dtype=np.complex128)
386
+ Ey = np.full((nNodes, ), np.nan, dtype=np.complex128)
387
+ Ez = np.full((nNodes, ), np.nan, dtype=np.complex128)
388
388
 
389
389
  nodes = nodes[:2,:]
390
390
 
@@ -491,9 +491,9 @@ def ned2_tri_interp_full(coords: np.ndarray,
491
491
  xs = coords[0,:]
492
492
  ys = coords[1,:]
493
493
 
494
- Ex = np.zeros((nNodes, ), dtype=np.complex128)
495
- Ey = np.zeros((nNodes, ), dtype=np.complex128)
496
- Ez = np.zeros((nNodes, ), dtype=np.complex128)
494
+ Ex = np.full((nNodes, ), np.nan, dtype=np.complex128)
495
+ Ey = np.full((nNodes, ), np.nan, dtype=np.complex128)
496
+ Ez = np.full((nNodes, ), np.nan, dtype=np.complex128)
497
497
 
498
498
  nodes = nodes[:2,:]
499
499
 
@@ -572,9 +572,9 @@ def ned2_tri_interp_curl(coords: np.ndarray,
572
572
  xs = coords[0,:]
573
573
  ys = coords[1,:]
574
574
  jB = 1j*beta
575
- Ex = np.zeros((nNodes, ), dtype=np.complex128)
576
- Ey = np.zeros((nNodes, ), dtype=np.complex128)
577
- Ez = np.zeros((nNodes, ), dtype=np.complex128)
575
+ Ex = np.full((nNodes, ), np.nan, dtype=np.complex128)
576
+ Ey = np.full((nNodes, ), np.nan, dtype=np.complex128)
577
+ Ez = np.full((nNodes, ), np.nan, dtype=np.complex128)
578
578
 
579
579
  nodes = nodes[:2,:]
580
580
 
@@ -68,30 +68,41 @@ class Nedelec2(FEMBasis):
68
68
 
69
69
  self.empty_tri_rowcol()
70
70
 
71
- def interpolate(self, field: np.ndarray, xs: np.ndarray, ys: np.ndarray, zs:np.ndarray, tetids: np.ndarray | None = None) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
71
+ def interpolate(self, field: np.ndarray, xs: np.ndarray, ys: np.ndarray, zs:np.ndarray, tetids: np.ndarray | None = None, usenan: bool = True) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
72
72
  '''
73
73
  Interpolate the provided field data array at the given xs, ys and zs coordinates
74
74
  '''
75
75
  if tetids is None:
76
76
  tetids = self._all_tet_ids
77
- return ned2_tet_interp(np.array([xs, ys, zs]), field, self.mesh.tets, self.mesh.tris, self.mesh.edges, self.mesh.nodes, self.tet_to_field, tetids)
77
+ vals = ned2_tet_interp(np.array([xs, ys, zs]), field, self.mesh.tets, self.mesh.tris, self.mesh.edges, self.mesh.nodes, self.tet_to_field, tetids)
78
+ if not usenan:
79
+ vals = np.nan_to_num(vals)
80
+ return vals
78
81
 
79
- def interpolate_curl(self, field: np.ndarray, xs: np.ndarray, ys: np.ndarray, zs:np.ndarray, c: np.ndarray, tetids: np.ndarray | None = None) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
82
+ def interpolate_curl(self, field: np.ndarray, xs: np.ndarray, ys: np.ndarray, zs:np.ndarray, c: np.ndarray, tetids: np.ndarray | None = None, usenan: bool = True) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
80
83
  """
81
84
  Interpolates the curl of the field at the given points.
82
85
  """
83
86
  if tetids is None:
84
87
  tetids = self._all_tet_ids
85
- return ned2_tet_interp_curl(np.array([xs, ys, zs]), field, self.mesh.tets, self.mesh.tris, self.mesh.edges, self.mesh.nodes, self.tet_to_field, c, tetids)
88
+ vals = ned2_tet_interp_curl(np.array([xs, ys, zs]), field, self.mesh.tets, self.mesh.tris, self.mesh.edges, self.mesh.nodes, self.tet_to_field, c, tetids)
89
+ if not usenan:
90
+ vals = np.nan_to_num(vals)
91
+ return vals
86
92
 
87
93
  def interpolate_index(self, xs: np.ndarray,
88
94
  ys: np.ndarray,
89
95
  zs: np.ndarray,
90
- tetids: np.ndarray | None = None) -> np.ndarray:
96
+ tetids: np.ndarray | None = None,
97
+ usenan: bool = True) -> np.ndarray:
91
98
  if tetids is None:
92
99
  tetids = self._all_tet_ids
93
100
 
94
- return index_interp(np.array([xs, ys, zs]), self.mesh.tets, self.mesh.nodes, tetids)
101
+ vals = index_interp(np.array([xs, ys, zs]), self.mesh.tets, self.mesh.nodes, tetids)
102
+ if not usenan:
103
+ vals[vals==-1]==0
104
+ return vals
105
+
95
106
  ###### INDEX MAPPINGS
96
107
 
97
108
  def local_tet_to_triid(self, itet: int) -> np.ndarray:
@@ -73,24 +73,30 @@ class FieldFunctionClass:
73
73
  Fx, Fy, Fz = self.cs.in_global_basis(Fxl, Fyl, Fzl)
74
74
  return np.array([Fx, Fy, Fz])*self.constant
75
75
 
76
- def calcE(self, xs: np.ndarray, ys: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
76
+ def calcE(self, xs: np.ndarray, ys: np.ndarray, usenan: bool = False) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
77
77
  coordinates = np.array([xs, ys])
78
- return ned2_tri_interp_full(coordinates,
78
+ vals = ned2_tri_interp_full(coordinates,
79
79
  self.field,
80
80
  self.tris,
81
81
  self.nodes,
82
82
  self.tri_to_field)
83
+ if not usenan:
84
+ vals = np.nan_to_num(vals)
85
+ return vals
83
86
 
84
- def calcH(self, xs: np.ndarray, ys: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
87
+ def calcH(self, xs: np.ndarray, ys: np.ndarray, usenan: bool = False) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
85
88
  coordinates = np.array([xs, ys])
86
89
 
87
- return ned2_tri_interp_curl(coordinates,
90
+ vals = ned2_tri_interp_curl(coordinates,
88
91
  self.field,
89
92
  self.tris,
90
93
  self.nodes,
91
94
  self.tri_to_field,
92
95
  self.diadic,
93
96
  self.beta)
97
+ if not usenan:
98
+ vals = np.nan_to_num(vals)
99
+ return vals
94
100
 
95
101
  ############### Nedelec2 Class
96
102
 
@@ -178,22 +184,28 @@ class NedelecLegrange2(FEMBasis):
178
184
 
179
185
  return FieldFunctionClass(field, self.cs, self.local_nodes, self.mesh.tris, self.tri_to_field, 'H', urinv, beta, constant)
180
186
 
181
- def tri_interpolate(self, field, xs: np.ndarray, ys: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
187
+ def tri_interpolate(self, field, xs: np.ndarray, ys: np.ndarray, usenan: bool = False) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
182
188
  coordinates = np.array([xs, ys])
183
- return ned2_tri_interp_full(coordinates,
189
+ vals = ned2_tri_interp_full(coordinates,
184
190
  field,
185
191
  self.mesh.tris,
186
192
  self.local_nodes,
187
193
  self.tri_to_field)
194
+ if not usenan:
195
+ vals = np.nan_to_num(vals)
196
+ return vals
188
197
 
189
- def tri_interpolate_curl(self, field, xs: np.ndarray, ys: np.ndarray, diadic: np.ndarray | None = None, beta: float = 0.0) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
198
+ def tri_interpolate_curl(self, field, xs: np.ndarray, ys: np.ndarray, diadic: np.ndarray | None = None, beta: float = 0.0, usenan: bool = False) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
190
199
  coordinates = np.array([xs, ys])
191
200
  if diadic is None:
192
201
  diadic = np.eye(3)[:,:,np.newaxis()] * np.ones((self.mesh.n_tris)) # type: ignore
193
- return ned2_tri_interp_curl(coordinates,
202
+ vals = ned2_tri_interp_curl(coordinates,
194
203
  field,
195
204
  self.mesh.tris,
196
205
  self.local_nodes,
197
206
  self.tri_to_field,
198
207
  diadic,
199
- beta)
208
+ beta)
209
+ if not usenan:
210
+ vals = np.nan_to_num(vals)
211
+ return vals
@@ -19,6 +19,6 @@ from .pcb import PCB
19
19
  from .pmlbox import pmlbox
20
20
  from .horn import Horn
21
21
  from .shapes import Cylinder, CoaxCylinder, Box, XYPlate, HalfSphere, Sphere, Plate, OldBox, Alignment, Cone
22
- from .operations import subtract, add, embed, remove, rotate, mirror, change_coordinate_system, translate, intersect, unite
22
+ from .operations import subtract, add, embed, remove, rotate, mirror, change_coordinate_system, translate, intersect, unite, expand_surface, stretch
23
23
  from .polybased import XYPolygon, GeoPrism, Disc, Curve
24
24
  from .step import STEPItems
@@ -17,7 +17,6 @@
17
17
 
18
18
  from ..geometry import GeoVolume
19
19
  from ..cs import CoordinateSystem
20
- from ..selection import FaceSelection
21
20
 
22
21
  import gmsh # type: ignore
23
22
 
@@ -381,7 +381,7 @@ class Modeler:
381
381
  self._add_function(function)
382
382
  return self
383
383
 
384
- def cyllinder(self,
384
+ def cylinder(self,
385
385
  radius: float | Series,
386
386
  height: float | Series,
387
387
  position: tuple[float | Series, float | Series, float | Series],
@@ -60,7 +60,7 @@ def add(main: T, tool: T,
60
60
  main._exists = False
61
61
  if remove_tool:
62
62
  tool._exists = False
63
- return output # type: ignore
63
+ return output.set_material(main.material) # type: ignore
64
64
 
65
65
  def remove(main: T, tool: T,
66
66
  remove_object: bool = True,
@@ -90,7 +90,7 @@ def remove(main: T, tool: T,
90
90
  main._exists = False
91
91
  if remove_tool:
92
92
  tool._exists = False
93
- return output # type: ignore
93
+ return output.set_material(main.material) # type: ignore
94
94
 
95
95
  subtract = remove
96
96
 
@@ -122,7 +122,7 @@ def intersect(main: T, tool: T,
122
122
  main._exists = False
123
123
  if remove_tool:
124
124
  tool._exists = False
125
- return output #type:ignore
125
+ return output.set_material(main.material) #type:ignore
126
126
 
127
127
  def embed(main: GeoVolume, other: GeoSurface) -> None:
128
128
  ''' Embeds a surface into a volume in the GMSH model.
@@ -268,6 +268,19 @@ def change_coordinate_system(main: GeoObject,
268
268
  fp.affine_transform(M2)
269
269
  return main
270
270
 
271
+ def stretch(main: GeoObject, fx: float = 1, fy: float = 1, fz: float = 1, origin: tuple[float, float, float] = (0.0, 0.0, 0.0)) -> GeoObject:
272
+ """Stretches a geometry with a factor fx, fy and fz along the x, y and Z axes respectively
273
+
274
+ The stretch origin is centered at the provided origin.
275
+
276
+ Returns:
277
+ _type_: _description_
278
+ """
279
+ gmsh.model.occ.dilate(main.dimtags, *origin, fx, fy, fz)
280
+
281
+ return main
282
+
283
+
271
284
  @overload
272
285
  def unite(*objects: GeoVolume) -> GeoVolume: ...
273
286
 
@@ -301,4 +314,31 @@ def unite(*objects: GeoObject) -> GeoObject:
301
314
  new_obj.set_material(main.material)
302
315
  new_obj.prio_set(main._priority)
303
316
 
304
- return new_obj
317
+ return new_obj
318
+
319
+ def expand_surface(surface: GeoSurface, distance: float) -> GeoSurface:
320
+ """EXPERIMENTAL: Expands an input surface. The surface must exist on a 2D plane.
321
+
322
+ The output surface does not inherit material properties.
323
+
324
+ If any problems occur, reach out through email.
325
+
326
+ Args:
327
+ surface (GeoSurface): The input surface to expand
328
+ distance (float): The exapansion distance
329
+
330
+ Returns:
331
+ GeoSurface: The output surface
332
+ """
333
+ surfs = []
334
+ for tag in surface.tags:
335
+ looptags, _ = gmsh.model.occ.get_curve_loops(tag)
336
+ new_curves = []
337
+ for looptag in looptags:
338
+ curve_tags = gmsh.model.occ.offset_curve(looptag, distance)
339
+ loop_tag = gmsh.model.occ.addCurveLoop([t for d,t in curve_tags])
340
+ new_curves.append(loop_tag)
341
+ surftag = gmsh.model.occ.addPlaneSurface(new_curves)
342
+ surfs.append(surftag)
343
+ surf = GeoSurface(surfs)
344
+ return surf
emerge/_emerge/geo/pcb.py CHANGED
@@ -963,8 +963,18 @@ class PCB:
963
963
  Returns:
964
964
  float: the z-height
965
965
  """
966
+ if layer <= 0:
967
+ return self._zs[layer]
966
968
  return self._zs[layer-1]
967
969
 
970
+ @property
971
+ def top(self) -> float:
972
+ return self._zs[-1]
973
+
974
+ @property
975
+ def bottom(self) -> float:
976
+ return self._zs[0]
977
+
968
978
  def _get_z(self, element: RouteElement) -> float :
969
979
  """Return the z-height of a given Route Element
970
980
 
@@ -979,6 +989,31 @@ class PCB:
979
989
  return path.z
980
990
  raise RouteException('Requesting z-height of route element that is not contained in a path.')
981
991
 
992
+ def add_vias(self, *coordinates: tuple[float, float], radius: float,
993
+ z1: float | None = None,
994
+ z2: float | None = None,
995
+ segments: int = 6) -> None:
996
+ """Add a series of vias provided by a list of coordinates.
997
+
998
+ Make sure to define the radius explicitly, otherwise the radius gets interpreted as a coordinate:
999
+
1000
+ >>> pcb.add_vias((x1,y1), (x1,y2), radius=1)
1001
+
1002
+ Args:
1003
+ *coordinates (tuple(float, float)): A series of coordinates
1004
+ radius (float): The radius
1005
+ z1 (float | None, optional): The bottom z-coordinate. Defaults to None.
1006
+ z2 (float | None, optional): The top z-coordinate. Defaults to None.
1007
+ segments (int, optional): The number of segmets for the via. Defaults to 6.
1008
+ """
1009
+ if z1 is None:
1010
+ z1 = self.z(0)
1011
+ if z2 is None:
1012
+ z2 = self.z(-1)
1013
+
1014
+ for x,y in coordinates:
1015
+ self.vias.append(Via(x,y,z1,z2,radius,segments))
1016
+
982
1017
  def load(self, name: str) -> StripLine:
983
1018
  """Acquire the x,y, coordinate associated with the label name.
984
1019
 
@@ -1060,7 +1095,7 @@ class PCB:
1060
1095
  GeoSurface: _description_
1061
1096
  """
1062
1097
  if width is None or height is None or origin is None:
1063
- if self.width is None or self.length is None or self.origin:
1098
+ if self.width is None or self.length is None or self.origin is None:
1064
1099
  raise RouteException('Cannot define a plane with no possible definition of its size.')
1065
1100
  width = self.width
1066
1101
  height = self.length
@@ -1075,6 +1110,7 @@ class PCB:
1075
1110
 
1076
1111
  plane = Plate(origin, (width*self.unit, 0, 0), (0, height*self.unit, 0)) # type: ignore
1077
1112
  plane = change_coordinate_system(plane, self.cs) # type: ignore
1113
+ plane.set_material(COPPER)
1078
1114
  return plane # type: ignore
1079
1115
 
1080
1116
  def generate_pcb(self,
@@ -1121,7 +1157,7 @@ class PCB:
1121
1157
  """Generate the Air Block object
1122
1158
 
1123
1159
  This requires that the width, depth and origin are deterimed. This
1124
- can either be done manually or via the .determine_boudns() method.
1160
+ can either be done manually or via the .determine_bounds() method.
1125
1161
 
1126
1162
  Returns:
1127
1163
  GeoVolume: The PCB Block
@@ -1357,20 +1393,19 @@ class PCB:
1357
1393
  poly = self._gen_poly(pcbpoly.xys, pcbpoly.z)
1358
1394
  poly.material = pcbpoly.material
1359
1395
  polys.append(poly)
1396
+ xs, ys = zip(*pcbpoly.xys)
1397
+ allx.extend(xs)
1398
+ ally.extend(ys)
1399
+
1360
1400
 
1361
1401
  self.xs = allx
1362
1402
  self.ys = ally
1363
1403
 
1364
1404
  self.traces = polys
1405
+
1365
1406
  if merge:
1366
1407
  polys = unite(*polys)
1367
- # tags = []
1368
- # for p in polys:
1369
- # tags.extend(p.tags)
1370
- # if p.material != COPPER:
1371
- # logger.warning(f'Merging a polygon with material {p.material} into a single polygon that will be COPPER.')
1372
- # polys = GeoSurface(tags)
1373
- # polys.material = COPPER
1408
+
1374
1409
  return polys
1375
1410
 
1376
1411
  ############################################################
@@ -20,9 +20,8 @@ class PCBCalculator:
20
20
  self.mat = material
21
21
  self.unit = unit
22
22
 
23
-
24
- def z0(self, Z0: float, layer: int = -1, ground_layer: int = 0):
23
+ def z0(self, Z0: float, layer: int = -1, ground_layer: int = 0, f0: float = 1e9):
25
24
  th = abs(self.layers[layer] - self.layers[ground_layer])*self.unit
26
25
  ws = np.geomspace(1e-6,1e-1,101)
27
- Z0ms = microstrip_z0(ws, th, self.mat.er)
26
+ Z0ms = microstrip_z0(ws, th, self.mat.er.scalar(f0))
28
27
  return np.interp(Z0, Z0ms[::-1], ws[::-1])/self.unit
@@ -17,7 +17,7 @@
17
17
 
18
18
  from ..geometry import GeoVolume
19
19
  from .shapes import Box, Alignment, Plate
20
- from ..material import Material, AIR
20
+ from ..material import Material, AIR, CoordDependent
21
21
  import numpy as np
22
22
  from functools import partial
23
23
 
@@ -31,6 +31,15 @@ def _add_pml_layer(center: tuple[float, float, float],
31
31
  exponent: float,
32
32
  deltamax: float,
33
33
  material: Material) -> GeoVolume:
34
+
35
+ if material.frequency_dependent:
36
+ raise ValueError('EMerge cannot handle frequency dependent material properties for PML layers at this point.')
37
+ if material.coordinate_dependent:
38
+ raise ValueError('Its not possible to define PML regions for materials that are coordinate dependent.')
39
+
40
+ mater = material.er.scalar(1e9)
41
+ matur = material.ur.scalar(1e9)
42
+
34
43
  px, py, pz = center
35
44
  W,D,H = dims
36
45
  dx, dy, dz = direction
@@ -69,15 +78,16 @@ def _add_pml_layer(center: tuple[float, float, float],
69
78
 
70
79
  def ermat(x, y, z):
71
80
  ers = np.zeros((3,3,x.shape[0]), dtype=np.complex128)
72
- ers[0,0,:] = material.er * syf(x,y,z)*szf(x,y,z)/sxf(x,y,z)
73
- ers[1,1,:] = material.er * szf(x,y,z)*sxf(x,y,z)/syf(x,y,z)
74
- ers[2,2,:] = material.er * sxf(x,y,z)*syf(x,y,z)/szf(x,y,z)
81
+ ers[0,0,:] = mater * syf(x,y,z)*szf(x,y,z)/sxf(x,y,z)
82
+ ers[1,1,:] = mater * szf(x,y,z)*sxf(x,y,z)/syf(x,y,z)
83
+ ers[2,2,:] = mater * sxf(x,y,z)*syf(x,y,z)/szf(x,y,z)
75
84
  return ers
85
+
76
86
  def urmat(x, y, z):
77
87
  urs = np.zeros((3,3,x.shape[0]), dtype=np.complex128)
78
- urs[0,0,:] = material.ur * syf(x,y,z)*szf(x,y,z)/sxf(x,y,z)
79
- urs[1,1,:] = material.ur * szf(x,y,z)*sxf(x,y,z)/syf(x,y,z)
80
- urs[2,2,:] = material.ur * sxf(x,y,z)*syf(x,y,z)/szf(x,y,z)
88
+ urs[0,0,:] = matur * syf(x,y,z)*szf(x,y,z)/sxf(x,y,z)
89
+ urs[1,1,:] = matur * szf(x,y,z)*sxf(x,y,z)/syf(x,y,z)
90
+ urs[2,2,:] = matur * sxf(x,y,z)*syf(x,y,z)/szf(x,y,z)
81
91
  return urs
82
92
 
83
93
  pml_box = Box(*pml_block_size, new_center, alignment=Alignment.CENTER)
@@ -106,7 +116,9 @@ def _add_pml_layer(center: tuple[float, float, float],
106
116
  plate = Plate(np.array([p0x-tW/2, p0y-tD/2, p0z-dz*thickness/2 + dz*(n+1)*thl]), ax1, ax2)
107
117
  planes.append(plate)
108
118
 
109
- pml_box.material = Material(_neff=np.sqrt(material.er*material.ur), _fer=ermat, _fur=urmat, color='#bbbbff', opacity=0.1)
119
+ erfunc = CoordDependent(max_value=mater, matrix=ermat)
120
+ urfunc = CoordDependent(max_value=matur, matrix=urmat)
121
+ pml_box.material = Material(er=erfunc, ur=urfunc,_neff=np.sqrt(mater*matur), color='#bbbbff', opacity=0.1)
110
122
  pml_box.max_meshsize = thickness/N_mesh_layers
111
123
  pml_box._embeddings = planes
112
124
 
@@ -121,9 +133,10 @@ def pmlbox(width: float,
121
133
  material: Material = AIR,
122
134
  thickness: float = 0.1,
123
135
  Nlayers: int = 1,
124
- N_mesh_layers: int = 8,
136
+ N_mesh_layers: int = 5,
125
137
  exponent: float = 1.5,
126
138
  deltamax: float = 8.0,
139
+ sides: str = '',
127
140
  top: bool = False,
128
141
  bottom: bool = False,
129
142
  left: bool = False,
@@ -145,10 +158,11 @@ def pmlbox(width: float,
145
158
  alignment (Alignment, optional): Which point of the box is placed at the given coordinate. Defaults to Alignment.CORNER.
146
159
  material (Material, optional): The material of the box. Defaults to AIR.
147
160
  thickness (float, optional): The thickness of the PML Layer. Defaults to 0.1.
148
- Nlayers (int, optional): The number of PML layers (1 is reccomended). Defaults to 1.
149
- N_mesh_layers (int, optional): The number of mesh layers. Sets the discretization size accordingly. Defaults to 8
161
+ Nlayers (int, optional): The number of geometrical PML layers. Defaults to 1.
162
+ N_mesh_layers (int, optional): The number of mesh layers. Sets the discretization size accordingly. Defaults to 5
150
163
  exponent (float, optional): The PML gradient growth function. Defaults to 1.5.
151
164
  deltamax (float, optional): A PML matching coefficient. Defaults to 8.0.
165
+ sides (str, optional): A string of pml sides as characters ([T]op, [B]ottom, [L]eft, [R]ight, [F]ront, b[A]ck)
152
166
  top (bool, optional): Add a top PML layer. Defaults to True.
153
167
  bottom (bool, optional): Add a bottom PML layer. Defaults to False.
154
168
  left (bool, optional): Add a left PML layer. Defaults to False.
@@ -159,6 +173,16 @@ def pmlbox(width: float,
159
173
  Returns:
160
174
  list[GeoVolume]: A list of objects [main box, *pml boxes]
161
175
  """
176
+
177
+ sides = sides.lower()
178
+
179
+ top = "t" in sides or top
180
+ bottom = "b" in sides or bottom
181
+ left = "l" in sides or left
182
+ right = "r" in sides or right
183
+ front = "f" in sides or front
184
+ back = "a" in sides or back
185
+
162
186
  px, py, pz = position
163
187
  if alignment == Alignment.CORNER:
164
188
  px = px + width / 2