emerge 0.4.7__py3-none-any.whl → 0.4.8__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (78) hide show
  1. emerge/__init__.py +14 -14
  2. emerge/_emerge/__init__.py +42 -0
  3. emerge/_emerge/bc.py +197 -0
  4. emerge/_emerge/coord.py +119 -0
  5. emerge/_emerge/cs.py +523 -0
  6. emerge/_emerge/dataset.py +36 -0
  7. emerge/_emerge/elements/__init__.py +19 -0
  8. emerge/_emerge/elements/femdata.py +212 -0
  9. emerge/_emerge/elements/index_interp.py +64 -0
  10. emerge/_emerge/elements/legrange2.py +172 -0
  11. emerge/_emerge/elements/ned2_interp.py +645 -0
  12. emerge/_emerge/elements/nedelec2.py +140 -0
  13. emerge/_emerge/elements/nedleg2.py +217 -0
  14. emerge/_emerge/geo/__init__.py +24 -0
  15. emerge/_emerge/geo/horn.py +107 -0
  16. emerge/_emerge/geo/modeler.py +449 -0
  17. emerge/_emerge/geo/operations.py +254 -0
  18. emerge/_emerge/geo/pcb.py +1244 -0
  19. emerge/_emerge/geo/pcb_tools/calculator.py +28 -0
  20. emerge/_emerge/geo/pcb_tools/macro.py +79 -0
  21. emerge/_emerge/geo/pmlbox.py +204 -0
  22. emerge/_emerge/geo/polybased.py +529 -0
  23. emerge/_emerge/geo/shapes.py +427 -0
  24. emerge/_emerge/geo/step.py +77 -0
  25. emerge/_emerge/geo2d.py +86 -0
  26. emerge/_emerge/geometry.py +510 -0
  27. emerge/_emerge/howto.py +214 -0
  28. emerge/_emerge/logsettings.py +5 -0
  29. emerge/_emerge/material.py +118 -0
  30. emerge/_emerge/mesh3d.py +730 -0
  31. emerge/_emerge/mesher.py +339 -0
  32. emerge/_emerge/mth/common_functions.py +33 -0
  33. emerge/_emerge/mth/integrals.py +71 -0
  34. emerge/_emerge/mth/optimized.py +357 -0
  35. emerge/_emerge/periodic.py +263 -0
  36. emerge/_emerge/physics/__init__.py +0 -0
  37. emerge/_emerge/physics/microwave/__init__.py +1 -0
  38. emerge/_emerge/physics/microwave/adaptive_freq.py +279 -0
  39. emerge/_emerge/physics/microwave/assembly/assembler.py +569 -0
  40. emerge/_emerge/physics/microwave/assembly/curlcurl.py +448 -0
  41. emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +426 -0
  42. emerge/_emerge/physics/microwave/assembly/robinbc.py +433 -0
  43. emerge/_emerge/physics/microwave/microwave_3d.py +1150 -0
  44. emerge/_emerge/physics/microwave/microwave_bc.py +915 -0
  45. emerge/_emerge/physics/microwave/microwave_data.py +1148 -0
  46. emerge/_emerge/physics/microwave/periodic.py +82 -0
  47. emerge/_emerge/physics/microwave/port_functions.py +53 -0
  48. emerge/_emerge/physics/microwave/sc.py +175 -0
  49. emerge/_emerge/physics/microwave/simjob.py +147 -0
  50. emerge/_emerge/physics/microwave/sparam.py +138 -0
  51. emerge/_emerge/physics/microwave/touchstone.py +140 -0
  52. emerge/_emerge/plot/__init__.py +0 -0
  53. emerge/_emerge/plot/display.py +394 -0
  54. emerge/_emerge/plot/grapher.py +93 -0
  55. emerge/_emerge/plot/matplotlib/mpldisplay.py +264 -0
  56. emerge/_emerge/plot/pyvista/__init__.py +1 -0
  57. emerge/_emerge/plot/pyvista/display.py +931 -0
  58. emerge/_emerge/plot/pyvista/display_settings.py +24 -0
  59. emerge/_emerge/plot/simple_plots.py +551 -0
  60. emerge/_emerge/plot.py +225 -0
  61. emerge/_emerge/projects/__init__.py +0 -0
  62. emerge/_emerge/projects/_gen_base.txt +32 -0
  63. emerge/_emerge/projects/_load_base.txt +24 -0
  64. emerge/_emerge/projects/generate_project.py +40 -0
  65. emerge/_emerge/selection.py +596 -0
  66. emerge/_emerge/simmodel.py +444 -0
  67. emerge/_emerge/simulation_data.py +411 -0
  68. emerge/_emerge/solver.py +993 -0
  69. emerge/_emerge/system.py +54 -0
  70. emerge/cli.py +19 -0
  71. emerge/lib.py +1 -1
  72. emerge/plot.py +1 -1
  73. {emerge-0.4.7.dist-info → emerge-0.4.8.dist-info}/METADATA +1 -1
  74. emerge-0.4.8.dist-info/RECORD +78 -0
  75. emerge-0.4.8.dist-info/entry_points.txt +2 -0
  76. emerge-0.4.7.dist-info/RECORD +0 -9
  77. emerge-0.4.7.dist-info/entry_points.txt +0 -2
  78. {emerge-0.4.7.dist-info → emerge-0.4.8.dist-info}/WHEEL +0 -0
@@ -0,0 +1,82 @@
1
+ # from ...selection import FaceSelection, SELECTOR_OBJ
2
+ # import numpy as np
3
+ # from typing import Generator
4
+ # from .microwave_bc import Periodic, FloquetPort
5
+ # from ...geo.extrude import XYPolygon, GeoPrism
6
+ # from ...geo import XYPlate, Alignment
7
+ # from ...periodic import PeriodicCell
8
+
9
+ # class MWPeriodicCell(PeriodicCell):
10
+
11
+ # def __post_init__(self):
12
+ # self._bcs: list[Periodic] = []
13
+ # self._ports: list[FloquetPort] = []
14
+
15
+ # def volume(self, z1: float, z2: float) -> GeoPrism:
16
+ # """Genereates a volume with the cell geometry ranging from z1 tot z2
17
+
18
+ # Args:
19
+ # z1 (float): The start height
20
+ # z2 (float): The end height
21
+
22
+ # Returns:
23
+ # GeoPrism: The resultant prism
24
+ # """
25
+ # raise NotImplementedError('This method is not implemented for this subclass.')
26
+
27
+ # def floquet_port(self, z: float) -> FloquetPort:
28
+ # raise NotImplementedError('This method is not implemented for this subclass.')
29
+
30
+ # def cell_data(self) -> Generator[tuple[FaceSelection,FaceSelection,tuple[float, float, float]], None, None]:
31
+ # """An iterator that yields the two faces of the hex cell plus a cell periodicity vector
32
+
33
+ # Yields:
34
+ # Generator[np.ndarray, np.ndarray, np.ndarray]: The face and periodicity data
35
+ # """
36
+ # raise NotImplementedError('This method is not implemented for this subclass.')
37
+
38
+ # @property
39
+ # def bcs(self) -> list[Periodic]:
40
+ # """Returns a list of Periodic boundary conditions for the given PeriodicCell
41
+
42
+ # Args:
43
+ # exclude_faces (list[FaceSelection], optional): A possible list of faces to exclude from the bcs. Defaults to None.
44
+
45
+ # Returns:
46
+ # list[Periodic]: The list of Periodic boundary conditions
47
+ # """
48
+ # if not self._bcs:
49
+ # bcs = []
50
+ # for f1, f2, a in self.cell_data():
51
+ # if self.excluded_faces is not None:
52
+ # f1 = f1 - self.excluded_faces
53
+ # f2 = f2 - self.excluded_faces
54
+ # bcs.append(Periodic(f1, f2, a))
55
+ # self._bcs = bcs
56
+ # return self._bcs
57
+
58
+ # def set_scanangle(self, theta: float, phi: float, degree: bool = True) -> None:
59
+ # """Sets the scanangle for the periodic condition. (0,0) is defined along the Z-axis
60
+
61
+ # Args:
62
+ # theta (float): The theta angle
63
+ # phi (float): The phi angle
64
+ # degree (bool): If the angle is in degrees. Defaults to True
65
+ # """
66
+ # if degree:
67
+ # theta = theta*np.pi/180
68
+ # phi = phi*np.pi/180
69
+
70
+
71
+ # ux = np.sin(theta)*np.cos(phi)
72
+ # uy = np.sin(theta)*np.sin(phi)
73
+ # uz = np.cos(theta)
74
+ # for bc in self._bcs:
75
+ # bc.ux = ux
76
+ # bc.uy = uy
77
+ # bc.uz = uz
78
+ # for port in self._ports:
79
+ # port.scan_theta = theta
80
+ # port.scan_phi = phi
81
+
82
+
@@ -0,0 +1,53 @@
1
+ # EMerge is an open source Python based FEM EM simulation module.
2
+ # Copyright (C) 2025 Robert Fennis.
3
+
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, see
16
+ # <https://www.gnu.org/licenses/>.
17
+
18
+ import numpy as np
19
+ from ...elements.nedleg2 import NedelecLegrange2
20
+ from ...mth.integrals import surface_integral
21
+
22
+
23
+ def compute_avg_power_flux(field: NedelecLegrange2, mode: np.ndarray, k0: float, ur: np.ndarray, beta: float):
24
+
25
+ Efunc = field.interpolate_Ef(mode)
26
+ Hfunc = field.interpolate_Hf(mode, k0, ur, beta)
27
+ nx, ny, nz = field.mesh.normals[:,0]
28
+ def S(x,y,z):
29
+ Ex, Ey, Ez = Efunc(x,y,z)
30
+ Hx, Hy, Hz = Hfunc(x,y,z)
31
+ Sx = 1/2*np.real(Ey*np.conj(Hz) - Ez*np.conj(Hy))
32
+ Sy = 1/2*np.real(Ez*np.conj(Hx) - Ex*np.conj(Hz))
33
+ Sz = 1/2*np.real(Ex*np.conj(Hy) - Ey*np.conj(Hx))
34
+ return nx*Sx + ny*Sy + nz*Sz
35
+
36
+ Ptot = surface_integral(field.mesh.nodes, field.mesh.tris, S, None, 4)
37
+ return Ptot
38
+
39
+ def compute_peak_power_flux(field: NedelecLegrange2, mode: np.ndarray, k0: float, ur: np.ndarray, beta: float):
40
+
41
+ Efunc = field.interpolate_Ef(mode)
42
+ Hfunc = field.interpolate_Hf(mode, k0, ur, beta)
43
+ nx, ny, nz = field.mesh.normals[:,0]
44
+ def S(x,y,z):
45
+ Ex, Ey, Ez = Efunc(x,y,z)
46
+ Hx, Hy, Hz = Hfunc(x,y,z)
47
+ Sx = np.real(Ey*np.conj(Hz) - Ez*np.conj(Hy))
48
+ Sy = np.real(Ez*np.conj(Hx) - Ex*np.conj(Hz))
49
+ Sz = np.real(Ex*np.conj(Hy) - Ey*np.conj(Hx))
50
+ return nx*Sx + ny*Sy + nz*Sz
51
+
52
+ Ptot = surface_integral(field.mesh.nodes, field.mesh.tris, S, None, 4)
53
+ return Ptot
@@ -0,0 +1,175 @@
1
+ # EMerge is an open source Python based FEM EM simulation module.
2
+ # Copyright (C) 2025 Robert Fennis.
3
+
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, see
16
+ # <https://www.gnu.org/licenses/>.
17
+
18
+ from numba_progress import ProgressBar
19
+ from ...mesh3d import SurfaceMesh
20
+ import numpy as np
21
+ from loguru import logger
22
+ from numba import c8, c16, f8, i8, njit, prange, typeof, f4
23
+ from numba.types import Tuple as TupleType
24
+ from numba_progress.progress import ProgressBarType
25
+
26
+ LR = 0.001
27
+ @njit(
28
+ TupleType((c8[:, :], c8[:, :]))(
29
+ c8[:, :],
30
+ c8[:, :],
31
+ f4[:, :],
32
+ f4[:, :],
33
+ f4[:, :],
34
+ f4,
35
+ ProgressBarType,
36
+ ),
37
+ parallel=True,
38
+ fastmath=True,
39
+ cache=True,
40
+ nogil=True,
41
+ )
42
+ def stratton_chu_ff(Ein, Hin, vis, wns, tpout, k0, pgb):
43
+
44
+ Ex = Ein[0, :].flatten()
45
+ Ey = Ein[1, :].flatten()
46
+ Ez = Ein[2, :].flatten()
47
+ Hx = Hin[0, :].flatten()
48
+ Hy = Hin[1, :].flatten()
49
+ Hz = Hin[2, :].flatten()
50
+ vx = vis[0, :].flatten()
51
+ vy = vis[1, :].flatten()
52
+ vz = vis[2, :].flatten()
53
+ nx = wns[0, :].flatten()
54
+ ny = wns[1, :].flatten()
55
+ nz = wns[2, :].flatten()
56
+
57
+ Emag = np.sqrt(np.abs(Ex)**2 + np.abs(Ey)**2 + np.abs(Ez)**2)
58
+
59
+ Elevel = np.max(Emag) * LR
60
+ ids = np.argwhere(Emag > Elevel)
61
+ Nids = ids.shape[0]
62
+ #iadd = NT // Nids
63
+ Ex = Ex[Emag > Elevel]
64
+ Ey = Ey[Emag > Elevel]
65
+ Ez = Ez[Emag > Elevel]
66
+ Hx = Hx[Emag > Elevel]
67
+ Hy = Hy[Emag > Elevel]
68
+ Hz = Hz[Emag > Elevel]
69
+ vx = vx[Emag > Elevel]
70
+ vy = vy[Emag > Elevel]
71
+ vz = vz[Emag > Elevel]
72
+ nx = nx[Emag > Elevel]
73
+ ny = ny[Emag > Elevel]
74
+ nz = nz[Emag > Elevel]
75
+
76
+ thout = tpout[0, :]
77
+ phout = tpout[1, :]
78
+
79
+ rx = np.sin(thout) * np.cos(phout)
80
+ ry = np.sin(thout) * np.sin(phout)
81
+ rz = np.cos(thout)
82
+
83
+ kx = k0 * rx
84
+ ky = k0 * ry
85
+ kz = k0 * rz
86
+
87
+ N = tpout.shape[1]
88
+
89
+ Eout = np.zeros((3, N)).astype(np.complex64)
90
+ Hout = np.zeros((3, N)).astype(np.complex64)
91
+
92
+ Eoutx = np.zeros((N,)).astype(np.complex64)
93
+ Eouty = np.zeros((N,)).astype(np.complex64)
94
+ Eoutz = np.zeros((N,)).astype(np.complex64)
95
+
96
+ Z0 = np.float32(376.73031366857)
97
+
98
+ Q = np.complex64(-1j * k0 / (4 * np.pi))
99
+ ii = np.complex64(1j)
100
+
101
+ NxHx = ny * Hz - nz * Hy
102
+ NxHy = nz * Hx - nx * Hz
103
+ NxHz = nx * Hy - ny * Hx
104
+
105
+ NxEx = ny * Ez - nz * Ey
106
+ NxEy = nz * Ex - nx * Ez
107
+ NxEz = nx * Ey - ny * Ex
108
+
109
+ for j in prange(Nids):
110
+ xi = vx[j]
111
+ yi = vy[j]
112
+ zi = vz[j]
113
+ G = np.exp(ii * (kx * xi + ky * yi + kz * zi))
114
+
115
+ RxNxHx = ry * NxHz[j] - rz * NxHy[j]
116
+ RxNxHy = rz * NxHx[j] - rx * NxHz[j]
117
+ RxNxHz = rx * NxHy[j] - ry * NxHx[j]
118
+
119
+ ie1x = (NxEx[j] - Z0 * RxNxHx) * G
120
+ ie1y = (NxEy[j] - Z0 * RxNxHy) * G
121
+ ie1z = (NxEz[j] - Z0 * RxNxHz) * G
122
+
123
+ Eoutx += Q * (ry * ie1z - rz * ie1y)
124
+ Eouty += Q * (rz * ie1x - rx * ie1z)
125
+ Eoutz += Q * (rx * ie1y - ry * ie1x)
126
+
127
+ # ii += iadd
128
+ pgb.update(1)
129
+ Eout[0, :] = Eoutx
130
+ Eout[1, :] = Eouty
131
+ Eout[2, :] = Eoutz
132
+
133
+ Hout[0, :] = (ry * Eoutz - rz * Eouty) / Z0
134
+ Hout[1, :] = (rz * Eoutx - rx * Eoutz) / Z0
135
+ Hout[2, :] = (rx * Eouty - ry * Eoutx) / Z0
136
+
137
+ return Eout, Hout
138
+
139
+ def stratton_chu(Ein, Hin, mesh: SurfaceMesh, theta: np.ndarray, phi: np.ndarray, k0: float):
140
+
141
+ Ein = np.array(Ein)
142
+ Hin = np.array(Hin)
143
+
144
+ Emag = np.sqrt(np.abs(Ein[0,:])**2 + np.abs(Ein[1,:])**2 + np.abs(Ein[2,:])**2)
145
+ Ntot = np.argwhere(Emag>0.000001*np.max(Emag)).shape[0]
146
+ logger.debug(f'Percentage Included: {Ntot/Emag.shape[0]*100:.0f}%')
147
+ areas = mesh.areas
148
+ vis = mesh.edge_centers
149
+
150
+ wns = np.zeros_like(vis).astype(np.float32)
151
+
152
+ tri_normals = mesh.normals
153
+ tri_ids = mesh.tri_to_edge
154
+
155
+ for i in range(mesh.n_tris):
156
+ n = tri_normals[:,i]
157
+ i1, i2, i3 = tri_ids[:,i]
158
+ wns[:,i1] += n*areas[i]/3
159
+ wns[:,i2] += n*areas[i]/3
160
+ wns[:,i3] += n*areas[i]/3
161
+
162
+ Eout = None
163
+ Hout = None
164
+ tpout = np.array([theta, phi])
165
+ with ProgressBar(total=Ntot, ncols=100, dynamic_ncols=False) as pgb:
166
+ Eout, Hout = stratton_chu_ff(
167
+ Ein.astype(np.complex64),
168
+ Hin.astype(np.complex64),
169
+ vis.astype(np.float32),
170
+ wns.astype(np.float32),
171
+ tpout.astype(np.float32),
172
+ np.float32(k0),
173
+ pgb,
174
+ )
175
+ return Eout.astype(np.complex128), Hout.astype(np.complex128)
@@ -0,0 +1,147 @@
1
+ # EMerge is an open source Python based FEM EM simulation module.
2
+ # Copyright (C) 2025 Robert Fennis.
3
+
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, see
16
+ # <https://www.gnu.org/licenses/>.
17
+
18
+ import numpy as np
19
+ import os
20
+ from scipy.sparse import csr_matrix, save_npz, load_npz
21
+ from ...solver import SolveReport
22
+
23
+ class SimJob:
24
+
25
+ def __init__(self,
26
+ A: csr_matrix,
27
+ b: np.ndarray,
28
+ freq: float,
29
+ cache_factorization: bool,
30
+ B: csr_matrix = None
31
+ ):
32
+
33
+ self.A: csr_matrix = A
34
+ self.B: csr_matrix = B
35
+ self.b: np.ndarray = b
36
+ self.P: csr_matrix = None
37
+ self.Pd: csr_matrix = None
38
+ self.has_periodic: bool = False
39
+
40
+ self.freq: float = freq
41
+ self.k0: float = 2*np.pi*freq/299792458
42
+ self.cache_factorization: bool = cache_factorization
43
+ self._fields: dict[int, np.ndarray] = dict()
44
+ self.port_vectors: dict = None
45
+ self.solve_ids = None
46
+
47
+ self.store_limit = None
48
+ self.relative_path = None
49
+ self._store_location = {}
50
+ self._stored: bool = False
51
+
52
+ self._active_port: int = None
53
+ self.reports: list[SolveReport] = []
54
+ self.id: int = -1
55
+ self.store_if_needed()
56
+
57
+ def maybe_store(self, matrix, name):
58
+ if self.store_limit is None:
59
+ return matrix
60
+
61
+ if matrix is not None and matrix.nnz > self.store_limit:
62
+ # Create temp directory if needed
63
+ os.makedirs(self.relative_path, exist_ok=True)
64
+ path = os.path.join(self.relative_path, f"csr_{str(self.freq).replace('.','_')}_{name}.npz")
65
+ save_npz(path, matrix, compressed=False)
66
+ self._store_location[name] = path
67
+ self._stored = True
68
+ return None # Unload from memory
69
+ return matrix
70
+
71
+ def store_if_needed(self):
72
+ self.A = self.maybe_store(self.A, 'A')
73
+ if self.has_periodic:
74
+ self.P = self.maybe_store(self.P, 'P')
75
+ self.Pd = self.maybe_store(self.Pd, 'Pd')
76
+
77
+ def load_if_needed(self, name):
78
+ if name in self._store_location:
79
+ return load_npz(self._store_location[name])
80
+ return getattr(self, name)
81
+
82
+ def iter_Ab(self):
83
+ reuse_factorization = False
84
+
85
+ for key, mode in self.port_vectors.items():
86
+ # Set port as active and add the port mode to the forcing vector
87
+ self._active_port = key
88
+
89
+ b_active = self.b + mode
90
+ A = self.load_if_needed('A')
91
+
92
+
93
+ if self.has_periodic:
94
+ P = self.load_if_needed('P')
95
+ Pd = self.load_if_needed('Pd')
96
+ b_active = Pd @ b_active
97
+ A = Pd @ A @ P
98
+
99
+ yield A, b_active, self.solve_ids, reuse_factorization
100
+
101
+ reuse_factorization = True
102
+
103
+ self.cleanup()
104
+
105
+ def yield_AC(self):
106
+ A = self.A
107
+ B = self.B
108
+
109
+ if self.has_periodic:
110
+ P = self.P
111
+ Pd = self.Pd
112
+ A = Pd @ A @ P
113
+ B = Pd @ B @ P
114
+
115
+ return A, B, self.solve_ids
116
+
117
+ def fix_solutions(self, solution: np.ndarray) -> np.ndarray:
118
+ if self.has_periodic:
119
+ solution = self.P @ solution
120
+ return solution
121
+
122
+ def submit_solution(self, solution: np.ndarray, report: SolveReport):
123
+ # Solve the Ax=b problem
124
+
125
+ if self.has_periodic:
126
+ solution = self.P @ solution
127
+ # From now reuse the factorization
128
+
129
+ self._fields[self._active_port] = solution
130
+ self.reports.append(report)
131
+ self.routine = None
132
+
133
+ def cleanup(self):
134
+ if not self._stored:
135
+ return
136
+
137
+ if not os.path.isdir(self.relative_path):
138
+ return
139
+
140
+ # Remove only the files we saved
141
+ for path in self._store_location.values():
142
+ if os.path.isfile(path):
143
+ os.remove(path)
144
+
145
+ # If the directory is now empty, remove it
146
+ if not os.listdir(self.relative_path):
147
+ os.rmdir(self.relative_path)
@@ -0,0 +1,138 @@
1
+ # EMerge is an open source Python based FEM EM simulation module.
2
+ # Copyright (C) 2025 Robert Fennis.
3
+
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, see
16
+ # <https://www.gnu.org/licenses/>.
17
+
18
+ from .microwave_bc import RobinBC
19
+ from ...mth.integrals import surface_integral
20
+ import numpy as np
21
+ from typing import Callable
22
+
23
+ def sparam_waveport(nodes: np.ndarray,
24
+ tri_vertices: np.ndarray,
25
+ bc: RobinBC,
26
+ freq: float,
27
+ fieldf: Callable,
28
+ ndpts: int = 4):
29
+ ''' Compute the S-parameters assuming a wave port mode
30
+
31
+ Arguments:
32
+ ----------
33
+ nodes: np.ndarray = (3,:) np.ndarray of all nodes in the mesh.
34
+ tri_vertices: np.ndarray = (3,:) np.ndarray of triangle indices that need to be integrated,
35
+ bc: RobinBC = The port boundary condition object
36
+ freq: float = The frequency at which to do the calculation
37
+ fielf: Callable = The interpolation fuction that computes the E-field from the simulation
38
+ ndpts: int = 4 the number of Duvanant integration points to use (default = 4)
39
+ '''
40
+
41
+ def modef(x, y, z):
42
+ return bc.port_mode_3d_global(x, y, z, freq)
43
+
44
+ def modef_c(x, y, z):
45
+ return np.conj(modef(x, y, z))
46
+
47
+ Q = 0
48
+ if bc.active:
49
+ Q = 1
50
+
51
+ def fieldf_p(x, y, z):
52
+ return fieldf(x,y,z) - Q * modef(x,y,z)
53
+
54
+
55
+ def inproduct1(x, y, z):
56
+ Ex1, Ey1, Ez1 = fieldf_p(x,y,z)
57
+ Ex2, Ey2, Ez2 = modef_c(x,y,z)
58
+ return Ex1*Ex2 + Ey1*Ey2 + Ez1*Ez2
59
+
60
+ def inproduct2(x, y, z):
61
+ Ex1, Ey1, Ez1 = modef(x,y,z)
62
+ Ex2, Ey2, Ez2 = modef_c(x,y,z)
63
+ return Ex1*Ex2 + Ey1*Ey2 + Ez1*Ez2
64
+
65
+ mode_dot_field = surface_integral(nodes, tri_vertices, inproduct1, ndpts=ndpts)
66
+ norm = surface_integral(nodes, tri_vertices, inproduct2, ndpts=ndpts)
67
+
68
+ svec = mode_dot_field/norm
69
+ return svec
70
+
71
+ def sparam_mode_power(nodes: np.ndarray,
72
+ tri_vertices: np.ndarray,
73
+ bc: RobinBC,
74
+ k0: float,
75
+ const: np.ndarray,
76
+ ndpts: int = 4):
77
+ ''' Compute the S-parameters assuming a wave port mode
78
+
79
+ Arguments:
80
+ ----------
81
+ nodes: np.ndarray = (3,:) np.ndarray of all nodes in the mesh.
82
+ tri_vertices: np.ndarray = (3,:) np.ndarray of triangle indices that need to be integrated,
83
+ bc: RobinBC = The port boundary condition object
84
+ freq: float = The frequency at which to do the calculation
85
+ fielf: Callable = The interpolation fuction that computes the E-field from the simulation
86
+ ndpts: int = 4 the number of Duvanant integration points to use (default = 4)
87
+ '''
88
+
89
+ def modef(x, y, z):
90
+ return bc.port_mode_3d_global(x, y, z, k0)
91
+
92
+ def inproduct2(x, y, z):
93
+ Ex1, Ey1, Ez1 = modef(x,y,z)
94
+ Ex2, Ey2, Ez2 = np.conj(modef(x,y,z))
95
+ return (Ex1*Ex2 + Ey1*Ey2 + Ez1*Ez2)/(2*bc.Zmode(k0))
96
+
97
+ norm = surface_integral(nodes, tri_vertices, inproduct2, const, ndpts=ndpts)
98
+
99
+ return norm
100
+
101
+ def sparam_field_power(nodes: np.ndarray,
102
+ tri_vertices: np.ndarray,
103
+ bc: RobinBC,
104
+ k0: float,
105
+ fieldf: Callable,
106
+ const: np.ndarray,
107
+ ndpts: int = 4):
108
+ ''' Compute the S-parameters assuming a wave port mode
109
+
110
+ Arguments:
111
+ ----------
112
+ nodes: np.ndarray = (3,:) np.ndarray of all nodes in the mesh.
113
+ tri_vertices: np.ndarray = (3,:) np.ndarray of triangle indices that need to be integrated,
114
+ bc: RobinBC = The port boundary condition object
115
+ freq: float = The frequency at which to do the calculation
116
+ fielf: Callable = The interpolation fuction that computes the E-field from the simulation
117
+ ndpts: int = 4 the number of Duvanant integration points to use (default = 4)
118
+ '''
119
+
120
+ def modef(x, y, z):
121
+ return bc.port_mode_3d_global(x, y, z, k0)
122
+
123
+ Q = 0
124
+ if bc.active:
125
+ Q = 1
126
+
127
+ def fieldf_p(x, y, z):
128
+ return fieldf(x,y,z) - Q * modef(x,y,z)
129
+
130
+ def inproduct1(x, y, z):
131
+ Ex1, Ey1, Ez1 = fieldf_p(x,y,z)
132
+ Ex2, Ey2, Ez2 = np.conj(modef(x,y,z))
133
+ return (Ex1*Ex2 + Ey1*Ey2 + Ez1*Ez2) / (2*bc.Zmode(k0))
134
+
135
+ mode_dot_field = surface_integral(nodes, tri_vertices, inproduct1, const, ndpts=ndpts)
136
+
137
+ svec = mode_dot_field
138
+ return svec