emerge 0.6.8__py3-none-any.whl → 0.6.10__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 +12 -3
- emerge/_emerge/cs.py +36 -0
- emerge/_emerge/geo/polybased.py +58 -26
- emerge/_emerge/geo/shapes.py +6 -0
- emerge/_emerge/geometry.py +21 -5
- emerge/_emerge/logsettings.py +13 -2
- emerge/_emerge/material.py +19 -3
- emerge/_emerge/mesh3d.py +15 -4
- emerge/_emerge/physics/microwave/microwave_3d.py +1 -1
- emerge/_emerge/physics/microwave/microwave_bc.py +33 -39
- emerge/_emerge/physics/microwave/microwave_data.py +29 -2
- emerge/_emerge/plot/pyvista/display.py +33 -5
- emerge/_emerge/plot/pyvista/display_settings.py +4 -1
- emerge/_emerge/plot/simple_plots.py +56 -28
- emerge/_emerge/projects/_load_base.txt +1 -2
- emerge/_emerge/selection.py +4 -0
- emerge/_emerge/simmodel.py +15 -9
- emerge/_emerge/solve_interfaces/cudss_interface.py +10 -4
- emerge/_emerge/solver.py +14 -4
- emerge/lib.py +256 -250
- {emerge-0.6.8.dist-info → emerge-0.6.10.dist-info}/METADATA +2 -1
- {emerge-0.6.8.dist-info → emerge-0.6.10.dist-info}/RECORD +25 -25
- {emerge-0.6.8.dist-info → emerge-0.6.10.dist-info}/WHEEL +0 -0
- {emerge-0.6.8.dist-info → emerge-0.6.10.dist-info}/entry_points.txt +0 -0
- {emerge-0.6.8.dist-info → emerge-0.6.10.dist-info}/licenses/LICENSE +0 -0
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.
|
|
21
|
+
__version__ = "0.6.10"
|
|
22
22
|
|
|
23
23
|
############################################################
|
|
24
24
|
# HANDLE ENVIRONMENT VARIABLES #
|
|
@@ -43,12 +43,13 @@ from loguru import logger
|
|
|
43
43
|
|
|
44
44
|
LOG_CONTROLLER.set_default()
|
|
45
45
|
logger.debug('Importing modules')
|
|
46
|
+
LOG_CONTROLLER._set_log_buffer()
|
|
46
47
|
|
|
47
48
|
from ._emerge.simmodel import Simulation
|
|
48
49
|
from ._emerge.material import Material, FreqCoordDependent, FreqDependent, CoordDependent
|
|
49
50
|
from ._emerge import bc
|
|
50
51
|
from ._emerge.solver import SolverBicgstab, SolverGMRES, SolveRoutine, ReverseCuthillMckee, Sorter, SolverPardiso, SolverUMFPACK, SolverSuperLU, EMSolver
|
|
51
|
-
from ._emerge.cs import CoordinateSystem, CS, GCS, Plane, Axis, XAX, YAX, ZAX, XYPLANE, XZPLANE, YZPLANE, YXPLANE, ZXPLANE, ZYPLANE
|
|
52
|
+
from ._emerge.cs import CoordinateSystem, CS, GCS, Plane, Axis, XAX, YAX, ZAX, XYPLANE, XZPLANE, YZPLANE, YXPLANE, ZXPLANE, ZYPLANE, cs
|
|
52
53
|
from ._emerge.coord import Line
|
|
53
54
|
from ._emerge import geo
|
|
54
55
|
from ._emerge.selection import Selection, FaceSelection, DomainSelection, EdgeSelection
|
|
@@ -62,4 +63,12 @@ from ._emerge.howto import _HowtoClass
|
|
|
62
63
|
|
|
63
64
|
howto = _HowtoClass()
|
|
64
65
|
|
|
65
|
-
logger.debug('Importing complete!')
|
|
66
|
+
logger.debug('Importing complete!')
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
############################################################
|
|
70
|
+
# CONSTANTS #
|
|
71
|
+
############################################################
|
|
72
|
+
|
|
73
|
+
CENTER = geo.Alignment.CENTER
|
|
74
|
+
CORNER = geo.Alignment.CORNER
|
emerge/_emerge/cs.py
CHANGED
|
@@ -530,3 +530,39 @@ CS = CoordinateSystem
|
|
|
530
530
|
# The global coordinate system
|
|
531
531
|
GCS = CoordinateSystem(XAX, YAX, ZAX, np.zeros(3), _is_global=True)
|
|
532
532
|
|
|
533
|
+
def cs(axes: str, origin: tuple[float, float, float] = (0.,0.,0.,)) -> CoordinateSystem:
|
|
534
|
+
"""Generate a coordinate system based on a simple string
|
|
535
|
+
The string must contain the letters x, X, y, Y, z and/or Z.
|
|
536
|
+
Small letters refer to positive axes and capitals to negative axes.
|
|
537
|
+
|
|
538
|
+
The string must be 3 characters long.
|
|
539
|
+
|
|
540
|
+
The first position indices which global axis gets assigned to the new local X-axis
|
|
541
|
+
The second position indicates the Y-axis
|
|
542
|
+
The third position indicates the Z-axis
|
|
543
|
+
|
|
544
|
+
Thus, rotating the global XYZ coordinate system 90 degrees around the Z axis would yield: yXz
|
|
545
|
+
|
|
546
|
+
Args:
|
|
547
|
+
axes (str): The axis description
|
|
548
|
+
origin (tuple[float, float, float], optional): The origin of the coordinate system. Defaults to (0.,0.,0.,).
|
|
549
|
+
|
|
550
|
+
Returns:
|
|
551
|
+
CoordinateSystem: The resultant coordinate system
|
|
552
|
+
"""
|
|
553
|
+
if len(axes) != 3:
|
|
554
|
+
raise ValueError('Axis object must be of length 3')
|
|
555
|
+
|
|
556
|
+
axlib = {
|
|
557
|
+
'x': Axis(np.array([1.0,0.,0.])),
|
|
558
|
+
'X': Axis(np.array([-1.0,0.,0.])),
|
|
559
|
+
'y': Axis(np.array([0.,1.0,0.])),
|
|
560
|
+
'Y': Axis(np.array([0.,-1.0,0.])),
|
|
561
|
+
'z': Axis(np.array([0.,0.,1.0])),
|
|
562
|
+
'Z': Axis(np.array([0.,0.,-1.0])),
|
|
563
|
+
}
|
|
564
|
+
ax_obj = [axlib[ax] for ax in axes]
|
|
565
|
+
|
|
566
|
+
return (ax_obj[0]*ax_obj[1]*ax_obj[2]).displace(*origin)
|
|
567
|
+
|
|
568
|
+
|
emerge/_emerge/geo/polybased.py
CHANGED
|
@@ -206,6 +206,7 @@ def orthonormalize(axis: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray
|
|
|
206
206
|
Zaxis = np.abs(Zaxis/np.linalg.norm(Zaxis))
|
|
207
207
|
return Xaxis, Yaxis, Zaxis
|
|
208
208
|
|
|
209
|
+
|
|
209
210
|
class GeoPrism(GeoVolume):
|
|
210
211
|
"""The GepPrism class generalizes the GeoVolume for extruded convex polygons.
|
|
211
212
|
Besides having a volumetric definitions, the class offers a .front_face
|
|
@@ -216,35 +217,65 @@ class GeoPrism(GeoVolume):
|
|
|
216
217
|
"""
|
|
217
218
|
def __init__(self,
|
|
218
219
|
volume_tag: int,
|
|
219
|
-
front_tag: int,
|
|
220
|
-
side_tags: list[int],
|
|
220
|
+
front_tag: int | None = None,
|
|
221
|
+
side_tags: list[int] | None = None,
|
|
222
|
+
_axis: Axis | None = None):
|
|
221
223
|
super().__init__(volume_tag)
|
|
222
|
-
|
|
223
|
-
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
if front_tag is not None and side_tags is not None:
|
|
228
|
+
self.front_tag: int = front_tag
|
|
229
|
+
self.back_tag: int = None
|
|
224
230
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
n1 = gmsh.model.get_normal(self.front_tag, (0,0))
|
|
228
|
-
self._add_face_pointer('back', o1, n1)
|
|
231
|
+
gmsh.model.occ.synchronize()
|
|
232
|
+
self._add_face_pointer('back', tag=self.front_tag)
|
|
229
233
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
self.back_tag = tag
|
|
239
|
-
break
|
|
234
|
+
tags = gmsh.model.get_boundary(self.dimtags, oriented=False)
|
|
235
|
+
|
|
236
|
+
for dim, tag in tags:
|
|
237
|
+
if (dim,tag) in side_tags:
|
|
238
|
+
continue
|
|
239
|
+
self._add_face_pointer('front',tag=tag)
|
|
240
|
+
self.back_tag = tag
|
|
241
|
+
break
|
|
240
242
|
|
|
241
|
-
|
|
243
|
+
self.side_tags: list[int] = [dt[1] for dt in tags if dt[1]!=self.front_tag and dt[1]!=self.back_tag]
|
|
242
244
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
245
|
+
for tag in self.side_tags:
|
|
246
|
+
|
|
247
|
+
self._add_face_pointer(f'side{tag}', tag=tag)
|
|
248
|
+
self.back_tag = tag
|
|
249
|
+
|
|
250
|
+
elif _axis is not None:
|
|
251
|
+
_axis = _parse_axis(_axis)
|
|
252
|
+
gmsh.model.occ.synchronize()
|
|
253
|
+
tags = gmsh.model.get_boundary(self.dimtags, oriented=False)
|
|
254
|
+
faces = []
|
|
255
|
+
for dim, tag in tags:
|
|
256
|
+
o1 = np.array(gmsh.model.occ.get_center_of_mass(2, tag))
|
|
257
|
+
n1 = np.array(gmsh.model.get_normal(tag, (0,0)))
|
|
258
|
+
if abs(np.sum(n1*_axis.np)) > 0.99:
|
|
259
|
+
dax = sum(o1 * _axis.np)
|
|
260
|
+
faces.append((o1, n1, dax, tag))
|
|
261
|
+
|
|
262
|
+
faces = sorted(faces, key=lambda x: x[2])
|
|
263
|
+
ftags = []
|
|
264
|
+
if len(faces) >= 2:
|
|
265
|
+
ftags.append(faces[0][3])
|
|
266
|
+
ftags.append(faces[-1][3])
|
|
267
|
+
self._add_face_pointer('front',faces[0][0], faces[0][1])
|
|
268
|
+
self._add_face_pointer('back', faces[-1][0], faces[-1][1])
|
|
269
|
+
elif len(faces)==1:
|
|
270
|
+
ftags.append(faces[0][3])
|
|
271
|
+
self._add_face_pointer('cap',faces[0][0], faces[0][1])
|
|
272
|
+
|
|
273
|
+
ictr = 1
|
|
274
|
+
for dim, tag in tags:
|
|
275
|
+
if tag in ftags:
|
|
276
|
+
continue
|
|
277
|
+
self._add_face_pointer(f'side{ictr}', tag=tag)
|
|
278
|
+
ictr += 1
|
|
248
279
|
|
|
249
280
|
def outside(self, *exclude: Literal['front','back']) -> FaceSelection:
|
|
250
281
|
"""Select all outside faces except for the once specified by outside
|
|
@@ -462,9 +493,10 @@ class XYPolygon:
|
|
|
462
493
|
ax, ay, az = axis
|
|
463
494
|
|
|
464
495
|
volume = gmsh.model.occ.revolve(poly_fin.dimtags, x,y,z, ax, ay, az, angle*np.pi/180)
|
|
496
|
+
|
|
465
497
|
tags = [t for d,t in volume if d==3]
|
|
466
|
-
|
|
467
|
-
return GeoPrism(tags,
|
|
498
|
+
poly_fin.remove()
|
|
499
|
+
return GeoPrism(tags, _axis=axis)
|
|
468
500
|
|
|
469
501
|
@staticmethod
|
|
470
502
|
def circle(radius: float,
|
emerge/_emerge/geo/shapes.py
CHANGED
|
@@ -27,6 +27,12 @@ from typing import Literal
|
|
|
27
27
|
from functools import reduce
|
|
28
28
|
|
|
29
29
|
class Alignment(Enum):
|
|
30
|
+
"""The alignment Enum describes if a box, cube or rectangle location
|
|
31
|
+
is specified for the center or the bottom - front - left corner (min X Y and Z)
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
Enum (_type_): _description_
|
|
35
|
+
"""
|
|
30
36
|
CENTER = 1
|
|
31
37
|
CORNER = 2
|
|
32
38
|
|
emerge/_emerge/geometry.py
CHANGED
|
@@ -61,8 +61,11 @@ class _GeometryManager:
|
|
|
61
61
|
self.geometry_list[model].append(geo)
|
|
62
62
|
|
|
63
63
|
def sign_in(self, modelname: str) -> None:
|
|
64
|
-
if modelname not in self.geometry_list:
|
|
65
|
-
|
|
64
|
+
# if modelname not in self.geometry_list:
|
|
65
|
+
# self.geometry_list[modelname] = []
|
|
66
|
+
if modelname is self.geometry_list:
|
|
67
|
+
logger.warning(f'{modelname} already exist, Geometries will be reset.')
|
|
68
|
+
self.geometry_list[modelname] = []
|
|
66
69
|
self.active = modelname
|
|
67
70
|
|
|
68
71
|
def reset(self, modelname: str) -> None:
|
|
@@ -242,6 +245,10 @@ class GeoObject:
|
|
|
242
245
|
def opacity(self) -> float:
|
|
243
246
|
return self.material.opacity
|
|
244
247
|
|
|
248
|
+
@property
|
|
249
|
+
def _metal(self) -> bool:
|
|
250
|
+
return self.material._metal
|
|
251
|
+
|
|
245
252
|
@property
|
|
246
253
|
def select(self) -> Selection:
|
|
247
254
|
'''Returns a corresponding Face/Domain or Edge Selection object'''
|
|
@@ -278,9 +285,18 @@ class GeoObject:
|
|
|
278
285
|
|
|
279
286
|
def _add_face_pointer(self,
|
|
280
287
|
name: str,
|
|
281
|
-
origin: np.ndarray,
|
|
282
|
-
normal: np.ndarray
|
|
283
|
-
|
|
288
|
+
origin: np.ndarray | None = None,
|
|
289
|
+
normal: np.ndarray | None = None,
|
|
290
|
+
tag: int | None = None):
|
|
291
|
+
if tag is not None:
|
|
292
|
+
o = gmsh.model.occ.get_center_of_mass(2, tag)
|
|
293
|
+
n = gmsh.model.get_normal(tag, (0,0))
|
|
294
|
+
self._face_pointers[name] = _FacePointer(o, n)
|
|
295
|
+
return
|
|
296
|
+
if origin is not None and normal is not None:
|
|
297
|
+
self._face_pointers[name] = _FacePointer(origin, normal)
|
|
298
|
+
return
|
|
299
|
+
raise ValueError('Eitehr a tag or an origin + normal must be provided!')
|
|
284
300
|
|
|
285
301
|
def make_copy(self) -> GeoObject:
|
|
286
302
|
new_dimtags = gmsh.model.occ.copy(self.dimtags)
|
emerge/_emerge/logsettings.py
CHANGED
|
@@ -18,11 +18,14 @@
|
|
|
18
18
|
from loguru import logger
|
|
19
19
|
import sys
|
|
20
20
|
from typing import Literal
|
|
21
|
-
from enum import Enum
|
|
22
21
|
from pathlib import Path
|
|
23
22
|
import os
|
|
24
|
-
import
|
|
23
|
+
from collections import deque
|
|
25
24
|
|
|
25
|
+
_LOG_BUFFER = deque()
|
|
26
|
+
|
|
27
|
+
def _log_sink(message):
|
|
28
|
+
_LOG_BUFFER.append(message)
|
|
26
29
|
############################################################
|
|
27
30
|
# FORMATS #
|
|
28
31
|
############################################################
|
|
@@ -82,6 +85,14 @@ class LogController:
|
|
|
82
85
|
format=FORMAT_DICT.get(loglevel, INFO_FORMAT))
|
|
83
86
|
self.std_handlers.append(handle_id)
|
|
84
87
|
|
|
88
|
+
def _set_log_buffer(self):
|
|
89
|
+
logger.add(_log_sink)
|
|
90
|
+
|
|
91
|
+
def _flush_log_buffer(self):
|
|
92
|
+
for msg in list(_LOG_BUFFER):
|
|
93
|
+
logger.opt(depth=6).log(msg.record["level"].name, msg.record["message"])
|
|
94
|
+
_LOG_BUFFER.clear()
|
|
95
|
+
|
|
85
96
|
def set_std_loglevel(self, loglevel: str):
|
|
86
97
|
handler = {"sink": sys.stdout,
|
|
87
98
|
"level": loglevel,
|
emerge/_emerge/material.py
CHANGED
|
@@ -289,7 +289,9 @@ class Material:
|
|
|
289
289
|
cond: float | MatProperty = 0.0,
|
|
290
290
|
_neff: float | None = None,
|
|
291
291
|
color: str ="#BEBEBE",
|
|
292
|
-
opacity: float = 1.0
|
|
292
|
+
opacity: float = 1.0,
|
|
293
|
+
_metal: bool = False,
|
|
294
|
+
name: str = 'unnamed'):
|
|
293
295
|
|
|
294
296
|
if not isinstance(er, MatProperty):
|
|
295
297
|
er = MatProperty(er)
|
|
@@ -300,6 +302,7 @@ class Material:
|
|
|
300
302
|
if not isinstance(cond, MatProperty):
|
|
301
303
|
cond = MatProperty(cond)
|
|
302
304
|
|
|
305
|
+
self.name: str = name
|
|
303
306
|
self.er: MatProperty = er
|
|
304
307
|
self.ur: MatProperty = ur
|
|
305
308
|
self.tand: MatProperty = tand
|
|
@@ -307,12 +310,24 @@ class Material:
|
|
|
307
310
|
|
|
308
311
|
self.color: str = color
|
|
309
312
|
self.opacity: float = opacity
|
|
313
|
+
self._hash_key: int = -1
|
|
314
|
+
|
|
310
315
|
if _neff is None:
|
|
311
316
|
self._neff: Callable = lambda f: np.sqrt(self.ur._fmax(f)*self.er._fmax(f))
|
|
312
317
|
else:
|
|
313
318
|
self._neff: Callable = lambda f: _neff
|
|
314
319
|
hex_str = self.color.lstrip('#')
|
|
315
320
|
self._color_rgb = tuple(int(hex_str[i:i+2], 16)/255.0 for i in (0, 2, 4))
|
|
321
|
+
self._metal: bool = _metal
|
|
322
|
+
|
|
323
|
+
def __hash__(self):
|
|
324
|
+
return self._hash_key
|
|
325
|
+
|
|
326
|
+
def __str__(self) -> str:
|
|
327
|
+
return f'Material({self.name}, {self._hash_key})'
|
|
328
|
+
|
|
329
|
+
def __repr__(self):
|
|
330
|
+
return f'Material({self.name}, {self._hash_key})'
|
|
316
331
|
|
|
317
332
|
def initialize(self, xs: np.ndarray, ys: np.ndarray, zs: np.ndarray, ids: np.ndarray):
|
|
318
333
|
"""Initializes the Material properties to be evaluated at xyz-coordinates for
|
|
@@ -336,6 +351,7 @@ class Material:
|
|
|
336
351
|
self.ur.reset()
|
|
337
352
|
self.tand.reset()
|
|
338
353
|
self.cond.reset()
|
|
354
|
+
self._hash_key = -1
|
|
339
355
|
|
|
340
356
|
@property
|
|
341
357
|
def frequency_dependent(self) -> bool:
|
|
@@ -364,5 +380,5 @@ class Material:
|
|
|
364
380
|
def color_rgb(self) -> tuple[float,float,float]:
|
|
365
381
|
return self._color_rgb
|
|
366
382
|
|
|
367
|
-
AIR = Material(color="#4496f3", opacity=0.05)
|
|
368
|
-
COPPER = Material(cond=5.8e7, color="#62290c")
|
|
383
|
+
AIR = Material(color="#4496f3", opacity=0.05, name='Air')
|
|
384
|
+
COPPER = Material(cond=5.8e7, color="#62290c", _metal=True, name='Copper')
|
emerge/_emerge/mesh3d.py
CHANGED
|
@@ -544,22 +544,33 @@ class Mesh3D(Mesh):
|
|
|
544
544
|
#arry = np.zeros((3,3,self.n_tets,), dtype=np.complex128)
|
|
545
545
|
for vol in volumes:
|
|
546
546
|
vol.material.reset()
|
|
547
|
-
|
|
547
|
+
|
|
548
548
|
materials = []
|
|
549
|
+
i = 0
|
|
550
|
+
for vol in volumes:
|
|
551
|
+
if vol.material not in materials:
|
|
552
|
+
materials.append(vol.material)
|
|
553
|
+
vol.material._hash_key = i
|
|
554
|
+
i += 1
|
|
549
555
|
|
|
550
556
|
xs = self.centers[0,:]
|
|
551
557
|
ys = self.centers[1,:]
|
|
552
558
|
zs = self.centers[2,:]
|
|
553
559
|
|
|
560
|
+
matassign = -1*np.ones((self.n_tets,), dtype=np.int64)
|
|
561
|
+
|
|
554
562
|
for volume in sorted(volumes, key=lambda x: x._priority):
|
|
555
563
|
|
|
556
564
|
for dimtag in volume.dimtags:
|
|
557
565
|
etype, etag_list, ntags = gmsh.model.mesh.get_elements(*dimtag)
|
|
558
566
|
for etags in etag_list:
|
|
559
567
|
tet_ids = np.array([self.tet_t2i[t] for t in etags])
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
568
|
+
matassign[tet_ids] = volume.material._hash_key
|
|
569
|
+
|
|
570
|
+
for mat in materials:
|
|
571
|
+
ids = np.argwhere(matassign==mat._hash_key).flatten()
|
|
572
|
+
mat.initialize(xs[ids], ys[ids], zs[ids], ids)
|
|
573
|
+
|
|
563
574
|
|
|
564
575
|
return materials
|
|
565
576
|
|
|
@@ -53,7 +53,7 @@ def run_job_multi(job: SimJob) -> SimJob:
|
|
|
53
53
|
Returns:
|
|
54
54
|
SimJob: The solved SimJob
|
|
55
55
|
"""
|
|
56
|
-
routine = DEFAULT_ROUTINE.
|
|
56
|
+
routine = DEFAULT_ROUTINE._configure_routine('MP')
|
|
57
57
|
for A, b, ids, reuse, aux in job.iter_Ab():
|
|
58
58
|
solution, report = routine.solve(A, b, ids, reuse, id=job.id)
|
|
59
59
|
report.add(**aux)
|
|
@@ -306,11 +306,17 @@ class AbsorbingBoundary(RobinBC):
|
|
|
306
306
|
Returns:
|
|
307
307
|
complex: The γ-constant
|
|
308
308
|
"""
|
|
309
|
-
|
|
309
|
+
if self.order == 2:
|
|
310
|
+
|
|
311
|
+
p0 = 1.06103
|
|
312
|
+
p2 = -0.84883
|
|
313
|
+
ky = k0*0.5
|
|
314
|
+
return 1j*k0*p0 - 1j*p2*ky**2/k0
|
|
315
|
+
else:
|
|
316
|
+
Factor = 1
|
|
317
|
+
return 1j*self.get_beta(k0)*Factor
|
|
310
318
|
|
|
311
|
-
|
|
312
|
-
return np.zeros((3, len(x_local)), dtype=np.complex128)
|
|
313
|
-
|
|
319
|
+
|
|
314
320
|
@dataclass
|
|
315
321
|
class PortMode:
|
|
316
322
|
modefield: np.ndarray
|
|
@@ -354,7 +360,7 @@ class FloquetPort(PortBC):
|
|
|
354
360
|
if cs is None:
|
|
355
361
|
cs = GCS
|
|
356
362
|
self.port_number: int= port_number
|
|
357
|
-
self.active: bool =
|
|
363
|
+
self.active: bool = False
|
|
358
364
|
self.power: float = power
|
|
359
365
|
self.type: str = 'TEM'
|
|
360
366
|
self.mode: tuple[int,int] = (1,0)
|
|
@@ -439,7 +445,6 @@ class ModalPort(PortBC):
|
|
|
439
445
|
def __init__(self,
|
|
440
446
|
face: FaceSelection | GeoSurface,
|
|
441
447
|
port_number: int,
|
|
442
|
-
active: bool = False,
|
|
443
448
|
cs: CoordinateSystem | None = None,
|
|
444
449
|
power: float = 1,
|
|
445
450
|
TEM: bool = False,
|
|
@@ -457,7 +462,6 @@ class ModalPort(PortBC):
|
|
|
457
462
|
Args:
|
|
458
463
|
face (FaceSelection, GeoSurface): The port mode face
|
|
459
464
|
port_number (int): The port number as an integer
|
|
460
|
-
active (bool, optional): Whether the port is set active. Defaults to False.
|
|
461
465
|
cs (CoordinateSystem, optional): The local coordinate system of the port face. Defaults to None.
|
|
462
466
|
power (float, optional): The radiated power. Defaults to 1.
|
|
463
467
|
TEM (bool, optional): Wether the mode should be considered as a TEM mode. Defaults to False
|
|
@@ -467,7 +471,7 @@ class ModalPort(PortBC):
|
|
|
467
471
|
super().__init__(face)
|
|
468
472
|
|
|
469
473
|
self.port_number: int= port_number
|
|
470
|
-
self.active: bool =
|
|
474
|
+
self.active: bool = False
|
|
471
475
|
self.power: float = power
|
|
472
476
|
self.alignment_vectors: list[Axis] = []
|
|
473
477
|
|
|
@@ -666,7 +670,7 @@ class RectangularWaveguide(PortBC):
|
|
|
666
670
|
def __init__(self,
|
|
667
671
|
face: FaceSelection | GeoSurface,
|
|
668
672
|
port_number: int,
|
|
669
|
-
|
|
673
|
+
mode: tuple[int, int] = (1,0),
|
|
670
674
|
cs: CoordinateSystem | None = None,
|
|
671
675
|
dims: tuple[float, float] | None = None,
|
|
672
676
|
power: float = 1):
|
|
@@ -681,7 +685,7 @@ class RectangularWaveguide(PortBC):
|
|
|
681
685
|
Args:
|
|
682
686
|
face (FaceSelection, GeoSurface): The port boundary face selection
|
|
683
687
|
port_number (int): The port number
|
|
684
|
-
|
|
688
|
+
mode: (tuple[int, int], optional): The TE mode number. Defaults to (1,0).
|
|
685
689
|
cs (CoordinateSystem, optional): The local coordinate system. Defaults to None.
|
|
686
690
|
dims (tuple[float, float], optional): The port face. Defaults to None.
|
|
687
691
|
power (float): The port power. Default to 1.
|
|
@@ -689,10 +693,10 @@ class RectangularWaveguide(PortBC):
|
|
|
689
693
|
super().__init__(face)
|
|
690
694
|
|
|
691
695
|
self.port_number: int= port_number
|
|
692
|
-
self.active: bool =
|
|
696
|
+
self.active: bool = False
|
|
693
697
|
self.power: float = power
|
|
694
698
|
self.type: str = 'TE'
|
|
695
|
-
self.mode: tuple[int,int] =
|
|
699
|
+
self.mode: tuple[int,int] = mode
|
|
696
700
|
|
|
697
701
|
if dims is None:
|
|
698
702
|
logger.info("Determining port face based on selection")
|
|
@@ -701,13 +705,12 @@ class RectangularWaveguide(PortBC):
|
|
|
701
705
|
self.dims = (width, height)
|
|
702
706
|
logger.debug(f'Port CS: {self.cs}')
|
|
703
707
|
logger.debug(f'Detected port {self.port_number} size = {width*1000:.1f} mm x {height*1000:.1f} mm')
|
|
704
|
-
|
|
708
|
+
else:
|
|
709
|
+
self.dims = dims
|
|
710
|
+
self.cs = cs
|
|
705
711
|
if self.cs is None:
|
|
706
712
|
logger.info('Constructing coordinate system from normal port')
|
|
707
713
|
self.cs = Axis(self.selection.normal).construct_cs()
|
|
708
|
-
else:
|
|
709
|
-
self.cs: CoordinateSystem = cs # type: ignore
|
|
710
|
-
|
|
711
714
|
def get_basis(self) -> np.ndarray:
|
|
712
715
|
return self.cs._basis
|
|
713
716
|
|
|
@@ -755,11 +758,12 @@ class RectangularWaveguide(PortBC):
|
|
|
755
758
|
|
|
756
759
|
width = self.dims[0]
|
|
757
760
|
height = self.dims[1]
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
761
|
+
m, n= self.mode
|
|
762
|
+
Ev = self.get_amplitude(k0)*np.cos(np.pi*m*(x_local)/width)*np.cos(np.pi*n*(y_local)/height)
|
|
763
|
+
Eh = self.get_amplitude(k0)*np.sin(np.pi*m*(x_local)/width)*np.sin(np.pi*n*(y_local)/height)
|
|
764
|
+
Ex = Eh
|
|
765
|
+
Ey = Ev
|
|
766
|
+
Ez = 0*Eh
|
|
763
767
|
Exyz = self._qmode(k0) * np.array([Ex, Ey, Ez])
|
|
764
768
|
return Exyz
|
|
765
769
|
|
|
@@ -787,7 +791,6 @@ class LumpedPort(PortBC):
|
|
|
787
791
|
width: float | None = None,
|
|
788
792
|
height: float | None = None,
|
|
789
793
|
direction: Axis | None = None,
|
|
790
|
-
active: bool = False,
|
|
791
794
|
power: float = 1,
|
|
792
795
|
Z0: float = 50):
|
|
793
796
|
"""Generates a lumped power boundary condition.
|
|
@@ -804,7 +807,6 @@ class LumpedPort(PortBC):
|
|
|
804
807
|
width (float): The port width (meters).
|
|
805
808
|
height (float): The port height (meters).
|
|
806
809
|
direction (Axis): The port direction as an Axis object (em.Axis(..) or em.ZAX)
|
|
807
|
-
active (bool, optional): Whether the port is active. Defaults to False.
|
|
808
810
|
power (float, optional): The port output power. Defaults to 1.
|
|
809
811
|
Z0 (float, optional): The port impedance. Defaults to 50.
|
|
810
812
|
"""
|
|
@@ -819,7 +821,7 @@ class LumpedPort(PortBC):
|
|
|
819
821
|
|
|
820
822
|
logger.debug(f'Lumped port: width={1000*width:.1f}mm, height={1000*height:.1f}mm, direction={direction}') # type: ignore
|
|
821
823
|
self.port_number: int= port_number
|
|
822
|
-
self.active: bool =
|
|
824
|
+
self.active: bool = False
|
|
823
825
|
|
|
824
826
|
self.power: float = power
|
|
825
827
|
self.Z0: float = Z0
|
|
@@ -829,9 +831,9 @@ class LumpedPort(PortBC):
|
|
|
829
831
|
self.Vdirection: Axis = direction # type: ignore
|
|
830
832
|
self.type = 'TEM'
|
|
831
833
|
|
|
832
|
-
logger.info('Constructing coordinate system from normal port')
|
|
833
|
-
self.cs = Axis(self.selection.normal).construct_cs() # type: ignore
|
|
834
|
-
|
|
834
|
+
# logger.info('Constructing coordinate system from normal port')
|
|
835
|
+
# self.cs = Axis(self.selection.normal).construct_cs() # type: ignore
|
|
836
|
+
self.cs = GCS
|
|
835
837
|
self.vintline: Line | None = None
|
|
836
838
|
self.v_integration = True
|
|
837
839
|
|
|
@@ -885,14 +887,7 @@ class LumpedPort(PortBC):
|
|
|
885
887
|
k0: float,
|
|
886
888
|
which: Literal['E','H'] = 'E') -> np.ndarray:
|
|
887
889
|
''' Compute the port mode E-field in local coordinates (XY) + Z out of plane.'''
|
|
888
|
-
|
|
889
|
-
px, py, pz = self.cs.in_local_basis(*self.Vdirection.np)
|
|
890
|
-
|
|
891
|
-
Ex = px*np.ones_like(x_local)
|
|
892
|
-
Ey = py*np.ones_like(x_local)
|
|
893
|
-
Ez = pz*np.ones_like(x_local)
|
|
894
|
-
Exyz = np.array([Ex, Ey, Ez])
|
|
895
|
-
return Exyz
|
|
890
|
+
raise RuntimeError('This function should never be called in this context.')
|
|
896
891
|
|
|
897
892
|
def port_mode_3d_global(self,
|
|
898
893
|
x_global: np.ndarray,
|
|
@@ -915,10 +910,9 @@ class LumpedPort(PortBC):
|
|
|
915
910
|
Returns:
|
|
916
911
|
np.ndarray: The E-field in (3,N) indexing.
|
|
917
912
|
"""
|
|
918
|
-
|
|
919
|
-
Ex, Ey, Ez = self.
|
|
920
|
-
|
|
921
|
-
return np.array([Exg, Eyg, Ezg])
|
|
913
|
+
ON = np.ones_like(x_global)
|
|
914
|
+
Ex, Ey, Ez = self.Vdirection.np
|
|
915
|
+
return np.array([Ex*ON, Ey*ON, Ez*ON])
|
|
922
916
|
|
|
923
917
|
|
|
924
918
|
class LumpedElement(RobinBC):
|
|
@@ -20,7 +20,7 @@ from ...simulation_data import BaseDataset, DataContainer
|
|
|
20
20
|
from ...elements.femdata import FEMBasis
|
|
21
21
|
from dataclasses import dataclass
|
|
22
22
|
import numpy as np
|
|
23
|
-
from typing import Literal
|
|
23
|
+
from typing import Literal, Callable
|
|
24
24
|
from loguru import logger
|
|
25
25
|
from .adaptive_freq import SparamModel
|
|
26
26
|
from ...cs import Axis, _parse_axis
|
|
@@ -1018,7 +1018,8 @@ class MWField:
|
|
|
1018
1018
|
k0 = self.k0
|
|
1019
1019
|
return vertices, triangles, E, H, origin, k0
|
|
1020
1020
|
|
|
1021
|
-
def optycal_antenna(self,
|
|
1021
|
+
def optycal_antenna(self,
|
|
1022
|
+
faces: FaceSelection | GeoSurface | None = None,
|
|
1022
1023
|
origin: tuple[float, float, float] | None = None,
|
|
1023
1024
|
syms: list[Literal['Ex','Ey','Ez', 'Hx','Hy','Hz']] | None = None) -> dict:
|
|
1024
1025
|
"""Export this models exterior to an Optical acceptable dataset
|
|
@@ -1036,6 +1037,32 @@ class MWField:
|
|
|
1036
1037
|
|
|
1037
1038
|
return dict(freq=freq, ff_function=function)
|
|
1038
1039
|
|
|
1040
|
+
# def surface_integral(self, faces: FaceSelection | GeoSurface, fieldfunction: Callable) -> float | complex:
|
|
1041
|
+
# """Computes a surface integral on the selected faces.
|
|
1042
|
+
|
|
1043
|
+
# The fieldfunction argument must be a callable of a single argument x, which will
|
|
1044
|
+
# be of type EHField which is restuned by the field.interpolate(x,y,z) function. It has
|
|
1045
|
+
# fields like Ez, Ey, Sx etc that can be called.
|
|
1046
|
+
|
|
1047
|
+
# Args:
|
|
1048
|
+
# faces (FaceSelection | GeoSurface): _description_
|
|
1049
|
+
# fieldfunction (Callable): _description_
|
|
1050
|
+
|
|
1051
|
+
# Returns:
|
|
1052
|
+
# float | complex: _description_
|
|
1053
|
+
# """
|
|
1054
|
+
# from ...mth.integrals import surface_integral
|
|
1055
|
+
|
|
1056
|
+
# def ff(x, y, z):
|
|
1057
|
+
# fieldobj = self.interpolate(x,y,z)
|
|
1058
|
+
# return fieldfunction(fieldobj)
|
|
1059
|
+
|
|
1060
|
+
# nodes = self.mesh.get_nodes(faces.tags)
|
|
1061
|
+
# triangles = self.mesh.get_triangles(faces.tags)
|
|
1062
|
+
|
|
1063
|
+
# return surface_integral(nodes, triangles, ff)
|
|
1064
|
+
|
|
1065
|
+
|
|
1039
1066
|
class MWScalar:
|
|
1040
1067
|
"""The MWDataSet class stores solution data of FEM Time Harmonic simulations.
|
|
1041
1068
|
"""
|