emerge 0.6.8__tar.gz → 0.6.9__tar.gz

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.

Files changed (111) hide show
  1. {emerge-0.6.8 → emerge-0.6.9}/.bumpversion.toml +1 -1
  2. {emerge-0.6.8 → emerge-0.6.9}/.gitignore +1 -1
  3. {emerge-0.6.8 → emerge-0.6.9}/PKG-INFO +2 -1
  4. {emerge-0.6.8 → emerge-0.6.9}/emerge/__init__.py +1 -1
  5. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/geometry.py +9 -2
  6. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/material.py +10 -3
  7. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/microwave_bc.py +16 -20
  8. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/microwave_data.py +29 -2
  9. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/plot/pyvista/display.py +32 -4
  10. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/plot/pyvista/display_settings.py +4 -1
  11. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/plot/simple_plots.py +41 -25
  12. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/projects/_load_base.txt +1 -2
  13. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/selection.py +4 -0
  14. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/simmodel.py +9 -4
  15. emerge-0.6.9/emerge/lib.py +313 -0
  16. {emerge-0.6.8 → emerge-0.6.9}/examples/demo10_sgh.py +1 -1
  17. {emerge-0.6.8 → emerge-0.6.9}/examples/demo11_lumped_element_filter.py +1 -1
  18. {emerge-0.6.8 → emerge-0.6.9}/examples/demo12_mode_alignment.py +2 -2
  19. {emerge-0.6.8 → emerge-0.6.9}/examples/demo13_helix_antenna.py +1 -1
  20. {emerge-0.6.8 → emerge-0.6.9}/examples/demo14_boundary_selection.py +1 -1
  21. {emerge-0.6.8 → emerge-0.6.9}/examples/demo1_stepped_imp_filter.py +1 -1
  22. {emerge-0.6.8 → emerge-0.6.9}/examples/demo2_combline_filter.py +1 -1
  23. {emerge-0.6.8 → emerge-0.6.9}/examples/demo3_coupled_line_filter.py +1 -1
  24. {emerge-0.6.8 → emerge-0.6.9}/examples/demo4_patch_antenna.py +10 -9
  25. {emerge-0.6.8 → emerge-0.6.9}/examples/demo5_revolve.py +1 -1
  26. {emerge-0.6.8 → emerge-0.6.9}/examples/demo6_striplines_with_vias.py +1 -1
  27. {emerge-0.6.8 → emerge-0.6.9}/examples/demo7_periodic_cells.py +1 -1
  28. {emerge-0.6.8 → emerge-0.6.9}/examples/demo8_waveguide_bpf_synthesis.py +1 -1
  29. {emerge-0.6.8 → emerge-0.6.9}/examples/demo9_dielectric_resonator.py +3 -3
  30. {emerge-0.6.8 → emerge-0.6.9}/pyproject.toml +2 -1
  31. {emerge-0.6.8 → emerge-0.6.9}/uv.lock +12 -1
  32. emerge-0.6.8/emerge/lib.py +0 -307
  33. emerge-0.6.8/src/_img/logo.jpeg +0 -0
  34. {emerge-0.6.8 → emerge-0.6.9}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  35. {emerge-0.6.8 → emerge-0.6.9}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  36. {emerge-0.6.8 → emerge-0.6.9}/.opt +0 -0
  37. {emerge-0.6.8 → emerge-0.6.9}/.python-version +0 -0
  38. {emerge-0.6.8 → emerge-0.6.9}/LICENSE +0 -0
  39. {emerge-0.6.8 → emerge-0.6.9}/README.md +0 -0
  40. {emerge-0.6.8 → emerge-0.6.9}/THIRD_PARTY_LICENSES.md +0 -0
  41. {emerge-0.6.8 → emerge-0.6.9}/UMFPACK_Install_windows.md +0 -0
  42. {emerge-0.6.8 → emerge-0.6.9}/UMFPACK_installer_windows.py +0 -0
  43. {emerge-0.6.8 → emerge-0.6.9}/emerge/__main__.py +0 -0
  44. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/__init__.py +0 -0
  45. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/_cache_check.py +0 -0
  46. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/bc.py +0 -0
  47. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/const.py +0 -0
  48. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/coord.py +0 -0
  49. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/cs.py +0 -0
  50. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/dataset.py +0 -0
  51. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/elements/__init__.py +0 -0
  52. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/elements/femdata.py +0 -0
  53. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/elements/index_interp.py +0 -0
  54. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/elements/ned2_interp.py +0 -0
  55. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/elements/nedelec2.py +0 -0
  56. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/elements/nedleg2.py +0 -0
  57. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/geo/__init__.py +0 -0
  58. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/geo/horn.py +0 -0
  59. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/geo/modeler.py +0 -0
  60. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/geo/operations.py +0 -0
  61. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/geo/pcb.py +0 -0
  62. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/geo/pcb_tools/calculator.py +0 -0
  63. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/geo/pcb_tools/macro.py +0 -0
  64. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/geo/pmlbox.py +0 -0
  65. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/geo/polybased.py +0 -0
  66. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/geo/shapes.py +0 -0
  67. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/geo/step.py +0 -0
  68. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/geo2d.py +0 -0
  69. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/howto.py +0 -0
  70. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/logsettings.py +0 -0
  71. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/mesh3d.py +0 -0
  72. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/mesher.py +0 -0
  73. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/mth/common_functions.py +0 -0
  74. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/mth/integrals.py +0 -0
  75. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/mth/optimized.py +0 -0
  76. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/mth/pairing.py +0 -0
  77. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/periodic.py +0 -0
  78. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/__init__.py +0 -0
  79. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/__init__.py +0 -0
  80. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/adaptive_freq.py +0 -0
  81. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/assembly/assembler.py +0 -0
  82. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/assembly/curlcurl.py +0 -0
  83. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +0 -0
  84. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/assembly/generalized_eigen_hb.py +0 -0
  85. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/assembly/periodicbc.py +0 -0
  86. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/assembly/robinbc.py +0 -0
  87. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/microwave_3d.py +0 -0
  88. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/periodic.py +0 -0
  89. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/port_functions.py +0 -0
  90. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/sc.py +0 -0
  91. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/simjob.py +0 -0
  92. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/sparam.py +0 -0
  93. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/touchstone.py +0 -0
  94. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/plot/__init__.py +0 -0
  95. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/plot/display.py +0 -0
  96. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/plot/matplotlib/mpldisplay.py +0 -0
  97. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/plot/pyvista/__init__.py +0 -0
  98. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/plot.py +0 -0
  99. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/projects/__init__.py +0 -0
  100. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/projects/_gen_base.txt +0 -0
  101. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/projects/generate_project.py +0 -0
  102. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/simulation_data.py +0 -0
  103. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/solve_interfaces/cudss_interface.py +0 -0
  104. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/solve_interfaces/pardiso_interface.py +0 -0
  105. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/solver.py +0 -0
  106. {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/system.py +0 -0
  107. {emerge-0.6.8 → emerge-0.6.9}/emerge/cli.py +0 -0
  108. {emerge-0.6.8 → emerge-0.6.9}/emerge/ext.py +0 -0
  109. {emerge-0.6.8 → emerge-0.6.9}/emerge/plot.py +0 -0
  110. {emerge-0.6.8 → emerge-0.6.9}/emerge/pyvista.py +0 -0
  111. {emerge-0.6.8 → emerge-0.6.9}/src/__init__.py +0 -0
@@ -1,5 +1,5 @@
1
1
  [tool.bumpversion]
2
- current_version = "0.6.8"
2
+ current_version = "0.6.9"
3
3
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
4
4
  serialize = ["{major}.{minor}.{patch}"]
5
5
  search = "{current_version}"
@@ -42,7 +42,7 @@ manual_scripts/
42
42
  tests/
43
43
  trial_scripts/
44
44
  wheelhouse/
45
-
45
+ benchmarks/
46
46
  .pytest_cache/
47
47
 
48
48
  .vscode/
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: emerge
3
- Version: 0.6.8
3
+ Version: 0.6.9
4
4
  Summary: An open source EM FEM simulator in Python
5
5
  Project-URL: Homepage, https://github.com/FennisRobert/EMerge
6
6
  Project-URL: Issues, https://github.com/FennisRobert/EMerge/issues
7
7
  License-File: LICENSE
8
8
  Requires-Python: <4.0,>=3.10
9
+ Requires-Dist: cloudpickle>=3.1.1
9
10
  Requires-Dist: gmsh<4.14.0,>=4.13.0
10
11
  Requires-Dist: joblib>=1.5.1
11
12
  Requires-Dist: loguru>=0.7.3
@@ -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.9"
22
22
 
23
23
  ############################################################
24
24
  # HANDLE ENVIRONMENT VARIABLES #
@@ -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'''
@@ -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
@@ -313,6 +316,10 @@ class Material:
313
316
  self._neff: Callable = lambda f: _neff
314
317
  hex_str = self.color.lstrip('#')
315
318
  self._color_rgb = tuple(int(hex_str[i:i+2], 16)/255.0 for i in (0, 2, 4))
319
+ self._metal: bool = _metal
320
+
321
+ def __str__(self) -> str:
322
+ return f'Material({self.name})'
316
323
 
317
324
  def initialize(self, xs: np.ndarray, ys: np.ndarray, zs: np.ndarray, ids: np.ndarray):
318
325
  """Initializes the Material properties to be evaluated at xyz-coordinates for
@@ -364,5 +371,5 @@ class Material:
364
371
  def color_rgb(self) -> tuple[float,float,float]:
365
372
  return self._color_rgb
366
373
 
367
- AIR = Material(color="#4496f3", opacity=0.05)
368
- COPPER = Material(cond=5.8e7, color="#62290c")
374
+ AIR = Material(color="#4496f3", opacity=0.05, name='Air')
375
+ COPPER = Material(cond=5.8e7, color="#62290c", _metal=True, name='Copper')
@@ -354,7 +354,7 @@ class FloquetPort(PortBC):
354
354
  if cs is None:
355
355
  cs = GCS
356
356
  self.port_number: int= port_number
357
- self.active: bool = True
357
+ self.active: bool = False
358
358
  self.power: float = power
359
359
  self.type: str = 'TEM'
360
360
  self.mode: tuple[int,int] = (1,0)
@@ -439,7 +439,6 @@ class ModalPort(PortBC):
439
439
  def __init__(self,
440
440
  face: FaceSelection | GeoSurface,
441
441
  port_number: int,
442
- active: bool = False,
443
442
  cs: CoordinateSystem | None = None,
444
443
  power: float = 1,
445
444
  TEM: bool = False,
@@ -457,7 +456,6 @@ class ModalPort(PortBC):
457
456
  Args:
458
457
  face (FaceSelection, GeoSurface): The port mode face
459
458
  port_number (int): The port number as an integer
460
- active (bool, optional): Whether the port is set active. Defaults to False.
461
459
  cs (CoordinateSystem, optional): The local coordinate system of the port face. Defaults to None.
462
460
  power (float, optional): The radiated power. Defaults to 1.
463
461
  TEM (bool, optional): Wether the mode should be considered as a TEM mode. Defaults to False
@@ -467,7 +465,7 @@ class ModalPort(PortBC):
467
465
  super().__init__(face)
468
466
 
469
467
  self.port_number: int= port_number
470
- self.active: bool = active
468
+ self.active: bool = False
471
469
  self.power: float = power
472
470
  self.alignment_vectors: list[Axis] = []
473
471
 
@@ -666,7 +664,7 @@ class RectangularWaveguide(PortBC):
666
664
  def __init__(self,
667
665
  face: FaceSelection | GeoSurface,
668
666
  port_number: int,
669
- active: bool = False,
667
+ mode: tuple[int, int] = (1,0),
670
668
  cs: CoordinateSystem | None = None,
671
669
  dims: tuple[float, float] | None = None,
672
670
  power: float = 1):
@@ -681,7 +679,7 @@ class RectangularWaveguide(PortBC):
681
679
  Args:
682
680
  face (FaceSelection, GeoSurface): The port boundary face selection
683
681
  port_number (int): The port number
684
- active (bool, optional): Ther the port is active. Defaults to False.
682
+ mode: (tuple[int, int], optional): The TE mode number. Defaults to (1,0).
685
683
  cs (CoordinateSystem, optional): The local coordinate system. Defaults to None.
686
684
  dims (tuple[float, float], optional): The port face. Defaults to None.
687
685
  power (float): The port power. Default to 1.
@@ -689,10 +687,10 @@ class RectangularWaveguide(PortBC):
689
687
  super().__init__(face)
690
688
 
691
689
  self.port_number: int= port_number
692
- self.active: bool = active
690
+ self.active: bool = False
693
691
  self.power: float = power
694
692
  self.type: str = 'TE'
695
- self.mode: tuple[int,int] = (1,0)
693
+ self.mode: tuple[int,int] = mode
696
694
 
697
695
  if dims is None:
698
696
  logger.info("Determining port face based on selection")
@@ -701,13 +699,12 @@ class RectangularWaveguide(PortBC):
701
699
  self.dims = (width, height)
702
700
  logger.debug(f'Port CS: {self.cs}')
703
701
  logger.debug(f'Detected port {self.port_number} size = {width*1000:.1f} mm x {height*1000:.1f} mm')
704
-
702
+ else:
703
+ self.dims = dims
704
+ self.cs = cs
705
705
  if self.cs is None:
706
706
  logger.info('Constructing coordinate system from normal port')
707
707
  self.cs = Axis(self.selection.normal).construct_cs()
708
- else:
709
- self.cs: CoordinateSystem = cs # type: ignore
710
-
711
708
  def get_basis(self) -> np.ndarray:
712
709
  return self.cs._basis
713
710
 
@@ -755,11 +752,12 @@ class RectangularWaveguide(PortBC):
755
752
 
756
753
  width = self.dims[0]
757
754
  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
755
+ m, n= self.mode
756
+ Ev = self.get_amplitude(k0)*np.cos(np.pi*m*(x_local)/width)*np.cos(np.pi*n*(y_local)/height)
757
+ Eh = self.get_amplitude(k0)*np.sin(np.pi*m*(x_local)/width)*np.sin(np.pi*n*(y_local)/height)
758
+ Ex = Eh
759
+ Ey = Ev
760
+ Ez = 0*Eh
763
761
  Exyz = self._qmode(k0) * np.array([Ex, Ey, Ez])
764
762
  return Exyz
765
763
 
@@ -787,7 +785,6 @@ class LumpedPort(PortBC):
787
785
  width: float | None = None,
788
786
  height: float | None = None,
789
787
  direction: Axis | None = None,
790
- active: bool = False,
791
788
  power: float = 1,
792
789
  Z0: float = 50):
793
790
  """Generates a lumped power boundary condition.
@@ -804,7 +801,6 @@ class LumpedPort(PortBC):
804
801
  width (float): The port width (meters).
805
802
  height (float): The port height (meters).
806
803
  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
804
  power (float, optional): The port output power. Defaults to 1.
809
805
  Z0 (float, optional): The port impedance. Defaults to 50.
810
806
  """
@@ -819,7 +815,7 @@ class LumpedPort(PortBC):
819
815
 
820
816
  logger.debug(f'Lumped port: width={1000*width:.1f}mm, height={1000*height:.1f}mm, direction={direction}') # type: ignore
821
817
  self.port_number: int= port_number
822
- self.active: bool = active
818
+ self.active: bool = False
823
819
 
824
820
  self.power: float = power
825
821
  self.Z0: float = Z0
@@ -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
  """
@@ -284,15 +284,16 @@ class PVDisplay(BaseDisplay):
284
284
  self._ruler.min_length = max(1e-3, min(self._mesh.edge_lengths))
285
285
  self._update_camera()
286
286
  self._add_aux_items()
287
+ # self._plot.renderer.enable_depth_peeling(20, 0.8)
288
+ # self._plot.enable_anti_aliasing(self.set.anti_aliassing)
287
289
  if self._do_animate:
288
290
  self._wire_close_events()
289
291
  self.add_text('Press Q to close!',color='red', position='upper_left')
290
292
  self._plot.show(auto_close=False, interactive_update=True, before_close_callback=self._close_callback)
291
293
  self._animate()
292
-
293
-
294
294
  else:
295
295
  self._plot.show()
296
+
296
297
  self._reset()
297
298
 
298
299
  def set_mesh(self, mesh: Mesh3D):
@@ -440,8 +441,20 @@ class PVDisplay(BaseDisplay):
440
441
  opacity = obj.opacity
441
442
  line_width = 0.5
442
443
  color = obj.color_rgb
444
+ metal = obj._metal
443
445
  style='surface'
444
446
 
447
+ # Default render settings
448
+ metallic = 0.05
449
+ roughness = 0.5
450
+ pbr = False
451
+
452
+ if metal:
453
+ pbr = True
454
+ metallic = 0.8
455
+ roughness = 0.3
456
+
457
+ # Default keyword arguments when plotting Mesh mode.
445
458
  if mesh is True:
446
459
  show_edges = True
447
460
  opacity = 0.7
@@ -449,13 +462,28 @@ class PVDisplay(BaseDisplay):
449
462
  style='wireframe'
450
463
  color=next(C_CYCLE)
451
464
 
452
- kwargs = setdefault(kwargs, color=color, opacity=opacity, line_width=line_width, show_edges=show_edges, pickable=True, style=style)
465
+ # Defining the default keyword arguments for PyVista
466
+ kwargs = setdefault(kwargs, color=color,
467
+ opacity=opacity,
468
+ metallic=metallic,
469
+ pbr=pbr,
470
+ roughness=roughness,
471
+ line_width=line_width,
472
+ show_edges=show_edges,
473
+ pickable=True,
474
+ style=style)
453
475
  mesh_obj = self.mesh(obj)
454
476
 
455
477
  if mesh is True and volume_mesh is True:
456
478
  mesh_obj = mesh_obj.extract_all_edges()
457
-
458
479
  actor = self._plot.add_mesh(mesh_obj, *args, **kwargs)
480
+
481
+ # Push 3D Geometries back to avoid Z-fighting with 2D geometries.
482
+ if obj.dim==3:
483
+ mapper = actor.GetMapper()
484
+ mapper.SetResolveCoincidentTopology(1)
485
+ mapper.SetRelativeCoincidentTopologyPolygonOffsetParameters(1,1)
486
+
459
487
  self._plot.add_mesh(self._volume_edges(_select(obj)), color='#000000', line_width=2, show_edges=True)
460
488
 
461
489
  def add_scatter(self, xs: np.ndarray, ys: np.ndarray, zs: np.ndarray):
@@ -1,3 +1,4 @@
1
+ from typing import Literal
1
2
 
2
3
  class PVDisplaySettings:
3
4
 
@@ -22,4 +23,6 @@ class PVDisplaySettings:
22
23
  self.background_bottom: str = "#c0d2e8"
23
24
  self.background_top: str = "#ffffff"
24
25
  self.grid_line_color: str = "#8e8e8e"
25
- self.z_boost: float = 1e-6
26
+ self.z_boost: float = 0#1e-9
27
+ self.depth_peeling: bool = True
28
+ self.anti_aliassing: Literal["msaa","ssaa",'fxaa'] = "msaa"
@@ -406,21 +406,21 @@ and sparse frequency annotations (e.g., labeled by frequency).
406
406
  plt.tight_layout()
407
407
  plt.show()
408
408
 
409
- def plot_sp(f: np.ndarray, S: list[np.ndarray] | np.ndarray,
410
- dblim=[-40, 5],
411
- xunit="GHz",
412
- levelindicator: int | float | None = None,
413
- noise_floor=-150,
414
- fill_areas: list[tuple] | None = None,
415
- spec_area: list[tuple[float,...]] | None = None,
416
- unwrap_phase=False,
417
- logx: bool = False,
418
- labels: list[str] | None = None,
419
- linestyles: list[str] | None = None,
420
- colorcycle: list[int] | None = None,
421
- filename: str | None = None,
422
- show_plot: bool = True,
423
- figdata: tuple | None = None) -> tuple[plt.Figure, plt.Axes, plt.Axes]:
409
+ def plot_sp(f: np.ndarray | list[np.ndarray], S: list[np.ndarray] | np.ndarray,
410
+ dblim=[-40, 5],
411
+ xunit="GHz",
412
+ levelindicator: int | float | None = None,
413
+ noise_floor=-150,
414
+ fill_areas: list[tuple] | None = None,
415
+ spec_area: list[tuple[float,...]] | None = None,
416
+ unwrap_phase=False,
417
+ logx: bool = False,
418
+ labels: list[str] | None = None,
419
+ linestyles: list[str] | None = None,
420
+ colorcycle: list[int] | None = None,
421
+ filename: str | None = None,
422
+ show_plot: bool = True,
423
+ figdata: tuple | None = None) -> tuple[plt.Figure, plt.Axes, plt.Axes]:
424
424
  """Plot S-parameters in dB and phase
425
425
 
426
426
  Args:
@@ -444,7 +444,12 @@ def plot_sp(f: np.ndarray, S: list[np.ndarray] | np.ndarray,
444
444
  Ss = [S]
445
445
  else:
446
446
  Ss = S
447
-
447
+
448
+ if not isinstance(f, list):
449
+ fs = [f for _ in Ss]
450
+ else:
451
+ fs = f
452
+
448
453
  if linestyles is None:
449
454
  linestyles = ['-' for _ in S]
450
455
 
@@ -452,7 +457,8 @@ def plot_sp(f: np.ndarray, S: list[np.ndarray] | np.ndarray,
452
457
  colorcycle = [i for i, S in enumerate(S)]
453
458
 
454
459
  unitdivider: dict[str, float] = {"MHz": 1e6, "GHz": 1e9, "kHz": 1e3}
455
- fnew = f / unitdivider[xunit]
460
+
461
+ fs = [f / unitdivider[xunit] for f in fs]
456
462
 
457
463
  if figdata is None:
458
464
  # Create two subplots: one for magnitude and one for phase
@@ -463,10 +469,10 @@ def plot_sp(f: np.ndarray, S: list[np.ndarray] | np.ndarray,
463
469
  minphase, maxphase = -180, 180
464
470
 
465
471
  maxy = 0
466
- for s, ls, cid in zip(Ss, linestyles, colorcycle):
472
+ for f, s, ls, cid in zip(fs, Ss, linestyles, colorcycle):
467
473
  # Calculate and plot magnitude in dB
468
474
  SdB = 20 * np.log10(np.abs(s) + 10**(noise_floor/20) * np.random.rand(*s.shape) + 10**((noise_floor-30)/20))
469
- ax_mag.plot(fnew, SdB, label="Magnitude (dB)", linestyle=ls, color=EMERGE_COLORS[cid % len(EMERGE_COLORS)])
475
+ ax_mag.plot(f, SdB, label="Magnitude (dB)", linestyle=ls, color=EMERGE_COLORS[cid % len(EMERGE_COLORS)])
470
476
  if np.max(SdB) > maxy:
471
477
  maxy = np.max(SdB)
472
478
  # Calculate and plot phase in degrees
@@ -475,12 +481,12 @@ def plot_sp(f: np.ndarray, S: list[np.ndarray] | np.ndarray,
475
481
  phase = np.unwrap(phase, period=360)
476
482
  minphase = min(np.min(phase), minphase)
477
483
  maxphase = max(np.max(phase), maxphase)
478
- ax_phase.plot(fnew, phase, label="Phase (degrees)", linestyle=ls, color=EMERGE_COLORS[cid % len(EMERGE_COLORS)])
484
+ ax_phase.plot(f, phase, label="Phase (degrees)", linestyle=ls, color=EMERGE_COLORS[cid % len(EMERGE_COLORS)])
479
485
 
480
486
  # Annotate level indicators if specified
481
487
  if isinstance(levelindicator, (int, float)) and levelindicator is not None:
482
488
  lvl = levelindicator
483
- fcross = hintersections(fnew, SdB, lvl)
489
+ fcross = hintersections(f, SdB, lvl)
484
490
  for fs in fcross:
485
491
  ax_mag.annotate(
486
492
  f"{str(fs)[:4]}{xunit}",
@@ -500,16 +506,18 @@ def plot_sp(f: np.ndarray, S: list[np.ndarray] | np.ndarray,
500
506
  f2 = fmax / unitdivider[xunit]
501
507
  ax_mag.fill_between([f1, f2], vmin,vmax, color='red', alpha=0.2)
502
508
  # Configure magnitude plot (ax_mag)
509
+ fmin = min([min(f) for f in fs])
510
+ fmax = max([max(f) for f in fs])
503
511
  ax_mag.set_ylabel("Magnitude (dB)")
504
512
  ax_mag.set_xlabel(f"Frequency ({xunit})")
505
- ax_mag.axis([min(fnew), max(fnew), dblim[0], max(maxy*1.1,dblim[1])]) # type: ignore
513
+ ax_mag.axis([fmin, fmax, dblim[0], max(maxy*1.1,dblim[1])]) # type: ignore
506
514
  ax_mag.axhline(y=0, color="k", linewidth=1)
507
515
  ax_mag.xaxis.set_minor_locator(tck.AutoMinorLocator(2))
508
516
  ax_mag.yaxis.set_minor_locator(tck.AutoMinorLocator(2))
509
517
  # Configure phase plot (ax_phase)
510
518
  ax_phase.set_ylabel("Phase (degrees)")
511
519
  ax_phase.set_xlabel(f"Frequency ({xunit})")
512
- ax_phase.axis([min(fnew), max(fnew), minphase, maxphase]) # type: ignore
520
+ ax_phase.axis([fmin, fmax, minphase, maxphase]) # type: ignore
513
521
  ax_phase.xaxis.set_minor_locator(tck.AutoMinorLocator(2))
514
522
  ax_phase.yaxis.set_minor_locator(tck.AutoMinorLocator(2))
515
523
  if logx:
@@ -612,6 +620,8 @@ def plot_ff(
612
620
  def plot_ff_polar(
613
621
  theta: np.ndarray,
614
622
  E: Union[np.ndarray, Sequence[np.ndarray]],
623
+ dB: bool = False,
624
+ dBfloor: float = -30,
615
625
  labels: Optional[List[str]] = None,
616
626
  linestyles: Union[str, List[str]] = "-",
617
627
  linewidth: float = 2.0,
@@ -649,6 +659,8 @@ def plot_ff_polar(
649
659
  E_list = list(E)
650
660
  n_series = len(E_list)
651
661
 
662
+ if dB:
663
+ E_list = [20*np.log10(np.clip(np.abs(e), a_min=10**(dBfloor/20), a_max = 1e9)) for e in E_list]
652
664
  # Style broadcasting
653
665
  def _broadcast(param, default):
654
666
  if isinstance(param, list):
@@ -665,11 +677,15 @@ def plot_ff_polar(
665
677
  ax.set_theta_zero_location(zero_location) # type: ignore
666
678
  ax.set_theta_direction(-1 if clockwise else 1) # type: ignore
667
679
  ax.set_rlabel_position(rlabel_angle) # type: ignore
680
+ ymin = min([min(E) for E in E_list])
681
+ ymax = max([max(E) for E in E_list])
682
+ yrange = ymax-ymin
668
683
 
684
+ ax.set_ylim(ymin-0.05*yrange, ymax+0.05*yrange)
669
685
  for i, Ei in enumerate(E_list):
670
- mag = np.abs(Ei)
686
+
671
687
  ax.plot(
672
- theta, mag,
688
+ theta, Ei,
673
689
  linestyle=linestyles[i],
674
690
  linewidth=linewidth,
675
691
  marker=markers[i],
@@ -17,8 +17,7 @@ with em.Simulation("myfile", load_file=True) as m:
17
17
  S11 = data.S(1,1)
18
18
  S21 = data.S(2,1)
19
19
  plt.plot_sp(f/1e9, [S11, S21])
20
-
21
- m.set_mesh(m.data.mw.field[0].mesh)
20
+
22
21
  m.display.add_object(m['box'])
23
22
  m.display.add_surf(*m.data.mw.field[0].cutplane(1*mm, z=5*mm).scalar('Ez','real'))
24
23
  m.display.show()
@@ -205,6 +205,10 @@ class Selection:
205
205
  def centers(self) -> list[tuple[float, float, float],]:
206
206
  return [gmsh.model.occ.get_center_of_mass(self.dim, tag) for tag in self.tags]
207
207
 
208
+ @property
209
+ def _metal(self) -> bool:
210
+ return False
211
+
208
212
  @property
209
213
  def opacity(self) -> float:
210
214
  return 0.6
@@ -31,7 +31,7 @@ from typing import Literal, Generator, Any
31
31
  from loguru import logger
32
32
  import numpy as np
33
33
  import gmsh # type: ignore
34
- import joblib # type: ignore
34
+ import cloudpickle
35
35
  import os
36
36
  import inspect
37
37
  from pathlib import Path
@@ -181,7 +181,10 @@ class Simulation:
181
181
 
182
182
  # Restier the Exit GMSH function on proper program abortion
183
183
  register(self._exit_gmsh)
184
-
184
+ else:
185
+ gmsh.finalize()
186
+ gmsh.initialize()
187
+
185
188
  # Create a new GMSH model or load it
186
189
  if not self.load_file:
187
190
  gmsh.model.add(self.modelname)
@@ -283,7 +286,8 @@ class Simulation:
283
286
  # Pack and save data
284
287
  dataset = dict(simdata=self.data, mesh=self.mesh)
285
288
  data_path = self.modelpath / 'simdata.emerge'
286
- joblib.dump(dataset, str(data_path))
289
+ with open(str(data_path), "wb") as f_out:
290
+ cloudpickle.dump(dataset, f_out)
287
291
  logger.info(f"Saved simulation data to: {data_path}")
288
292
 
289
293
  def load(self) -> None:
@@ -304,7 +308,8 @@ class Simulation:
304
308
  #self.mesh.update([])
305
309
 
306
310
  # Load data
307
- datapack = joblib.load(str(data_path))
311
+ with open(str(data_path), "rb") as f_in:
312
+ datapack= cloudpickle.load(f_in)
308
313
  self.data = datapack['simdata']
309
314
  self._set_mesh(datapack['mesh'])
310
315
  logger.info(f"Loaded simulation data from: {data_path}")