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.
- {emerge-0.6.8 → emerge-0.6.9}/.bumpversion.toml +1 -1
- {emerge-0.6.8 → emerge-0.6.9}/.gitignore +1 -1
- {emerge-0.6.8 → emerge-0.6.9}/PKG-INFO +2 -1
- {emerge-0.6.8 → emerge-0.6.9}/emerge/__init__.py +1 -1
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/geometry.py +9 -2
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/material.py +10 -3
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/microwave_bc.py +16 -20
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/microwave_data.py +29 -2
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/plot/pyvista/display.py +32 -4
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/plot/pyvista/display_settings.py +4 -1
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/plot/simple_plots.py +41 -25
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/projects/_load_base.txt +1 -2
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/selection.py +4 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/simmodel.py +9 -4
- emerge-0.6.9/emerge/lib.py +313 -0
- {emerge-0.6.8 → emerge-0.6.9}/examples/demo10_sgh.py +1 -1
- {emerge-0.6.8 → emerge-0.6.9}/examples/demo11_lumped_element_filter.py +1 -1
- {emerge-0.6.8 → emerge-0.6.9}/examples/demo12_mode_alignment.py +2 -2
- {emerge-0.6.8 → emerge-0.6.9}/examples/demo13_helix_antenna.py +1 -1
- {emerge-0.6.8 → emerge-0.6.9}/examples/demo14_boundary_selection.py +1 -1
- {emerge-0.6.8 → emerge-0.6.9}/examples/demo1_stepped_imp_filter.py +1 -1
- {emerge-0.6.8 → emerge-0.6.9}/examples/demo2_combline_filter.py +1 -1
- {emerge-0.6.8 → emerge-0.6.9}/examples/demo3_coupled_line_filter.py +1 -1
- {emerge-0.6.8 → emerge-0.6.9}/examples/demo4_patch_antenna.py +10 -9
- {emerge-0.6.8 → emerge-0.6.9}/examples/demo5_revolve.py +1 -1
- {emerge-0.6.8 → emerge-0.6.9}/examples/demo6_striplines_with_vias.py +1 -1
- {emerge-0.6.8 → emerge-0.6.9}/examples/demo7_periodic_cells.py +1 -1
- {emerge-0.6.8 → emerge-0.6.9}/examples/demo8_waveguide_bpf_synthesis.py +1 -1
- {emerge-0.6.8 → emerge-0.6.9}/examples/demo9_dielectric_resonator.py +3 -3
- {emerge-0.6.8 → emerge-0.6.9}/pyproject.toml +2 -1
- {emerge-0.6.8 → emerge-0.6.9}/uv.lock +12 -1
- emerge-0.6.8/emerge/lib.py +0 -307
- emerge-0.6.8/src/_img/logo.jpeg +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/.opt +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/.python-version +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/LICENSE +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/README.md +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/THIRD_PARTY_LICENSES.md +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/UMFPACK_Install_windows.md +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/UMFPACK_installer_windows.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/__main__.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/__init__.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/_cache_check.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/bc.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/const.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/coord.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/cs.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/dataset.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/elements/__init__.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/elements/femdata.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/elements/index_interp.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/elements/ned2_interp.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/elements/nedelec2.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/elements/nedleg2.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/geo/__init__.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/geo/horn.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/geo/modeler.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/geo/operations.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/geo/pcb.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/geo/pcb_tools/calculator.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/geo/pcb_tools/macro.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/geo/pmlbox.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/geo/polybased.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/geo/shapes.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/geo/step.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/geo2d.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/howto.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/logsettings.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/mesh3d.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/mesher.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/mth/common_functions.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/mth/integrals.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/mth/optimized.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/mth/pairing.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/periodic.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/__init__.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/__init__.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/adaptive_freq.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/assembly/assembler.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/assembly/curlcurl.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/assembly/generalized_eigen_hb.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/assembly/periodicbc.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/assembly/robinbc.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/microwave_3d.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/periodic.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/port_functions.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/sc.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/simjob.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/sparam.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/physics/microwave/touchstone.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/plot/__init__.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/plot/display.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/plot/matplotlib/mpldisplay.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/plot/pyvista/__init__.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/plot.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/projects/__init__.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/projects/_gen_base.txt +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/projects/generate_project.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/simulation_data.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/solve_interfaces/cudss_interface.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/solve_interfaces/pardiso_interface.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/solver.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/_emerge/system.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/cli.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/ext.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/plot.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/emerge/pyvista.py +0 -0
- {emerge-0.6.8 → emerge-0.6.9}/src/__init__.py +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: emerge
|
|
3
|
-
Version: 0.6.
|
|
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
|
|
@@ -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'''
|
|
@@ -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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
690
|
+
self.active: bool = False
|
|
693
691
|
self.power: float = power
|
|
694
692
|
self.type: str = 'TE'
|
|
695
|
-
self.mode: tuple[int,int] =
|
|
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
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
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 =
|
|
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,
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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([
|
|
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([
|
|
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
|
-
|
|
686
|
+
|
|
671
687
|
ax.plot(
|
|
672
|
-
theta,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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}")
|