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 CHANGED
@@ -18,7 +18,7 @@ along with this program; if not, see
18
18
  """
19
19
  import os
20
20
 
21
- __version__ = "0.6.8"
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
+
@@ -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
- self.front_tag: int = front_tag
223
- self.back_tag: int = None
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
- gmsh.model.occ.synchronize()
226
- o1 = gmsh.model.occ.get_center_of_mass(2, self.front_tag)
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
- tags = gmsh.model.get_boundary(self.dimtags, oriented=False)
231
-
232
- for dim, tag in tags:
233
- if (dim,tag) in side_tags:
234
- continue
235
- o2 = gmsh.model.occ.get_center_of_mass(2, tag)
236
- n2 = gmsh.model.get_normal(tag, (0,0))
237
- self._add_face_pointer('front', o2, n2)
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
- self.side_tags: list[int] = [dt[1] for dt in tags if dt[1]!=self.front_tag and dt[1]!=self.back_tag]
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
- for tag in self.side_tags:
244
- o2 = gmsh.model.occ.get_center_of_mass(2, tag)
245
- n2 = gmsh.model.get_normal(tag, (0,0))
246
- self._add_face_pointer(f'side{tag}', o2, n2)
247
- self.back_tag = tag
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
- surftags = [t for d,t in volume if d==2]
467
- return GeoPrism(tags, surftags[0], surftags)
498
+ poly_fin.remove()
499
+ return GeoPrism(tags, _axis=axis)
468
500
 
469
501
  @staticmethod
470
502
  def circle(radius: float,
@@ -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
 
@@ -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
- self.geometry_list[modelname] = []
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
- self._face_pointers[name] = _FacePointer(origin, normal)
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)
@@ -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 gmsh
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,
@@ -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
- volume.material.initialize(xs[tet_ids], ys[tet_ids], zs[tet_ids], tet_ids)
561
- if volume.material not in materials:
562
- materials.append(volume.material)
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.duplicate()._configure_routine('MP')
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
- return 1j*self.get_beta(k0)
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
- def get_Uinc(self, x_local: np.ndarray, y_local: np.ndarray, k0: float) -> np.ndarray:
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 = True
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 = active
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
- active: bool = False,
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
- active (bool, optional): Ther the port is active. Defaults to False.
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 = active
696
+ self.active: bool = False
693
697
  self.power: float = power
694
698
  self.type: str = 'TE'
695
- self.mode: tuple[int,int] = (1,0)
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
- E = self.get_amplitude(k0)*np.cos(np.pi*self.mode[0]*(x_local)/width)*np.cos(np.pi*self.mode[1]*(y_local)/height)
760
- Ex = 0*E
761
- Ey = E
762
- Ez = 0*E
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 = active
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
- xl, yl, _ = self.cs.in_local_cs(x_global, y_global, z_global)
919
- Ex, Ey, Ez = self.port_mode_3d(xl, yl, k0)
920
- Exg, Eyg, Ezg = self.cs.in_global_basis(Ex, Ey, Ez)
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, faces: FaceSelection | GeoSurface | None = None,
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
  """