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.
- emerge/__init__.py +14 -14
- emerge/_emerge/__init__.py +42 -0
- emerge/_emerge/bc.py +197 -0
- emerge/_emerge/coord.py +119 -0
- emerge/_emerge/cs.py +523 -0
- emerge/_emerge/dataset.py +36 -0
- emerge/_emerge/elements/__init__.py +19 -0
- emerge/_emerge/elements/femdata.py +212 -0
- emerge/_emerge/elements/index_interp.py +64 -0
- emerge/_emerge/elements/legrange2.py +172 -0
- emerge/_emerge/elements/ned2_interp.py +645 -0
- emerge/_emerge/elements/nedelec2.py +140 -0
- emerge/_emerge/elements/nedleg2.py +217 -0
- emerge/_emerge/geo/__init__.py +24 -0
- emerge/_emerge/geo/horn.py +107 -0
- emerge/_emerge/geo/modeler.py +449 -0
- emerge/_emerge/geo/operations.py +254 -0
- emerge/_emerge/geo/pcb.py +1244 -0
- emerge/_emerge/geo/pcb_tools/calculator.py +28 -0
- emerge/_emerge/geo/pcb_tools/macro.py +79 -0
- emerge/_emerge/geo/pmlbox.py +204 -0
- emerge/_emerge/geo/polybased.py +529 -0
- emerge/_emerge/geo/shapes.py +427 -0
- emerge/_emerge/geo/step.py +77 -0
- emerge/_emerge/geo2d.py +86 -0
- emerge/_emerge/geometry.py +510 -0
- emerge/_emerge/howto.py +214 -0
- emerge/_emerge/logsettings.py +5 -0
- emerge/_emerge/material.py +118 -0
- emerge/_emerge/mesh3d.py +730 -0
- emerge/_emerge/mesher.py +339 -0
- emerge/_emerge/mth/common_functions.py +33 -0
- emerge/_emerge/mth/integrals.py +71 -0
- emerge/_emerge/mth/optimized.py +357 -0
- emerge/_emerge/periodic.py +263 -0
- emerge/_emerge/physics/__init__.py +0 -0
- emerge/_emerge/physics/microwave/__init__.py +1 -0
- emerge/_emerge/physics/microwave/adaptive_freq.py +279 -0
- emerge/_emerge/physics/microwave/assembly/assembler.py +569 -0
- emerge/_emerge/physics/microwave/assembly/curlcurl.py +448 -0
- emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +426 -0
- emerge/_emerge/physics/microwave/assembly/robinbc.py +433 -0
- emerge/_emerge/physics/microwave/microwave_3d.py +1150 -0
- emerge/_emerge/physics/microwave/microwave_bc.py +915 -0
- emerge/_emerge/physics/microwave/microwave_data.py +1148 -0
- emerge/_emerge/physics/microwave/periodic.py +82 -0
- emerge/_emerge/physics/microwave/port_functions.py +53 -0
- emerge/_emerge/physics/microwave/sc.py +175 -0
- emerge/_emerge/physics/microwave/simjob.py +147 -0
- emerge/_emerge/physics/microwave/sparam.py +138 -0
- emerge/_emerge/physics/microwave/touchstone.py +140 -0
- emerge/_emerge/plot/__init__.py +0 -0
- emerge/_emerge/plot/display.py +394 -0
- emerge/_emerge/plot/grapher.py +93 -0
- emerge/_emerge/plot/matplotlib/mpldisplay.py +264 -0
- emerge/_emerge/plot/pyvista/__init__.py +1 -0
- emerge/_emerge/plot/pyvista/display.py +931 -0
- emerge/_emerge/plot/pyvista/display_settings.py +24 -0
- emerge/_emerge/plot/simple_plots.py +551 -0
- emerge/_emerge/plot.py +225 -0
- emerge/_emerge/projects/__init__.py +0 -0
- emerge/_emerge/projects/_gen_base.txt +32 -0
- emerge/_emerge/projects/_load_base.txt +24 -0
- emerge/_emerge/projects/generate_project.py +40 -0
- emerge/_emerge/selection.py +596 -0
- emerge/_emerge/simmodel.py +444 -0
- emerge/_emerge/simulation_data.py +411 -0
- emerge/_emerge/solver.py +993 -0
- emerge/_emerge/system.py +54 -0
- emerge/cli.py +19 -0
- emerge/lib.py +1 -1
- emerge/plot.py +1 -1
- {emerge-0.4.7.dist-info → emerge-0.4.8.dist-info}/METADATA +1 -1
- emerge-0.4.8.dist-info/RECORD +78 -0
- emerge-0.4.8.dist-info/entry_points.txt +2 -0
- emerge-0.4.7.dist-info/RECORD +0 -9
- emerge-0.4.7.dist-info/entry_points.txt +0 -2
- {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
|