emerge 0.4.7__py3-none-any.whl → 0.4.9__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.9.dist-info}/METADATA +7 -6
- emerge-0.4.9.dist-info/RECORD +78 -0
- emerge-0.4.9.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.9.dist-info}/WHEEL +0 -0
emerge/_emerge/cs.py
ADDED
|
@@ -0,0 +1,523 @@
|
|
|
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 __future__ import annotations
|
|
19
|
+
import numpy as np
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from typing import Literal
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class Point:
|
|
27
|
+
""" A representation of a point."""
|
|
28
|
+
x: float
|
|
29
|
+
y: float
|
|
30
|
+
z: float
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class Axis:
|
|
35
|
+
"""A representation of an axis.
|
|
36
|
+
An Axis object always has length 1 and points in some 3D direction.
|
|
37
|
+
By default XAX, YAX, and ZAX are constructed and defined in the global namespace of the
|
|
38
|
+
FEM module.
|
|
39
|
+
"""
|
|
40
|
+
vector: np.ndarray
|
|
41
|
+
|
|
42
|
+
def __repr__(self) -> str:
|
|
43
|
+
return f"Axis({self.vector})"
|
|
44
|
+
|
|
45
|
+
def __post_init__(self):
|
|
46
|
+
|
|
47
|
+
self.vector = self.vector/np.linalg.norm(self.vector)
|
|
48
|
+
self.np: np.ndarray = self.vector
|
|
49
|
+
self.x: float = self.vector[0]
|
|
50
|
+
self.y: float = self.vector[1]
|
|
51
|
+
self.z: float = self.vector[2]
|
|
52
|
+
|
|
53
|
+
def __neg__(self):
|
|
54
|
+
return Axis(-self.vector)
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def neg(self) -> Axis:
|
|
58
|
+
return Axis(-self.vector)
|
|
59
|
+
|
|
60
|
+
def cross(self, other: Axis) -> Axis:
|
|
61
|
+
"""Take the cross produt with another vector
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
other (Axis): Vector B in AxB
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Axis: The resultant Axis.
|
|
68
|
+
"""
|
|
69
|
+
return Axis(np.cross(self.vector, other.vector))
|
|
70
|
+
|
|
71
|
+
def dot(self, other: Axis) -> float:
|
|
72
|
+
"""Take the dot product of two vectors A·B
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
other (Axis): Vector B
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
float: The resultant vector (A·B)
|
|
79
|
+
"""
|
|
80
|
+
return np.dot(self.vector, other.vector)
|
|
81
|
+
|
|
82
|
+
def pair(self, other: Axis) -> Plane:
|
|
83
|
+
"""Pair this vector with another to span the plane A⨂B
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
other (Axis): Vector B
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Plane: The plane spanned by A and B.
|
|
90
|
+
"""
|
|
91
|
+
return Plane(self, other)
|
|
92
|
+
|
|
93
|
+
def __mul__(self, other: Axis) -> Plane:
|
|
94
|
+
"""The multiply binary operator
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
other (Axis): _description_
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Plane: The output plane
|
|
101
|
+
"""
|
|
102
|
+
return self.pair(other)
|
|
103
|
+
|
|
104
|
+
def construct_cs(self) -> CoordinateSystem:
|
|
105
|
+
"""Constructs a coordinate system where this vector is the Z-axis
|
|
106
|
+
and the X and Y axis are normal to this axis but with an arbitrary rotation.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
CoordinateSystem: The resultant coordinate system
|
|
110
|
+
"""
|
|
111
|
+
ax = Axis(np.array([1, 0, 0]))
|
|
112
|
+
if np.abs(self.dot(ax)) > 0.999:
|
|
113
|
+
|
|
114
|
+
ax = Axis(np.array([0, 1, 0]))
|
|
115
|
+
ax1 = self.cross(ax)
|
|
116
|
+
ax2 = self.cross(ax1).neg
|
|
117
|
+
return CoordinateSystem(ax2, ax1, self, np.zeros(3))
|
|
118
|
+
|
|
119
|
+
XAX: Axis = Axis(np.array([1, 0, 0]))
|
|
120
|
+
YAX: Axis = Axis(np.array([0, 1, 0]))
|
|
121
|
+
ZAX: Axis = Axis(np.array([0, 0, 1]))
|
|
122
|
+
|
|
123
|
+
def _parse_vector(vec: np.ndarray | tuple | list | Axis) -> np.ndarray:
|
|
124
|
+
""" Takes an array, tuple, list or Axis and alwasys returns an array."""
|
|
125
|
+
if isinstance(vec, np.ndarray):
|
|
126
|
+
return vec
|
|
127
|
+
elif isinstance(vec, (list,tuple)):
|
|
128
|
+
return np.array(vec)
|
|
129
|
+
elif isinstance(vec, Axis):
|
|
130
|
+
return vec.vector
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
def _parse_axis(vec: np.ndarray | tuple | list | Axis) -> Axis:
|
|
134
|
+
"""Takes an array, tuple, list or Axis and always returns an Axis.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
vec (np.ndarray | tuple | list | Axis): The Axis data
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Axis: The Axis object.
|
|
141
|
+
"""
|
|
142
|
+
if isinstance(vec, np.ndarray):
|
|
143
|
+
return Axis(vec)
|
|
144
|
+
elif isinstance(vec, (list,tuple)):
|
|
145
|
+
return Axis(np.array(vec))
|
|
146
|
+
elif isinstance(vec, Axis):
|
|
147
|
+
return vec
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
@dataclass
|
|
151
|
+
class Plane:
|
|
152
|
+
"""A generalization of any plane of inifinite size spanned by two Axis objects.
|
|
153
|
+
|
|
154
|
+
"""
|
|
155
|
+
uax: Axis
|
|
156
|
+
vax: Axis
|
|
157
|
+
|
|
158
|
+
def __post_init__(self):
|
|
159
|
+
# Check if axes are orthogonal
|
|
160
|
+
if not np.isclose(np.dot(self.uax.vector, self.vax.vector), 0):
|
|
161
|
+
raise ValueError("Axes are not orthogonal")
|
|
162
|
+
|
|
163
|
+
def __repr__(self) -> str:
|
|
164
|
+
return f"Plane({self.uax}, {self.vax})"
|
|
165
|
+
|
|
166
|
+
def __mul__(self, other: Axis) -> CoordinateSystem:
|
|
167
|
+
return CoordinateSystem(self.uax, self.vax, other)
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def normal(self) -> Axis:
|
|
171
|
+
"""Returns the normal of the plane as u ⨉ v.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Axis: The axis object normal to the plane.
|
|
175
|
+
"""
|
|
176
|
+
return self.uax.cross(self.vax)
|
|
177
|
+
|
|
178
|
+
def flip(self) -> Plane:
|
|
179
|
+
"""Flips the planes U and V axes.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Plane: A new plane object.
|
|
183
|
+
"""
|
|
184
|
+
return Plane(self.vax, self.uax)
|
|
185
|
+
|
|
186
|
+
def cs(self, x0: float = 0, y0: float = 0, z0: float = 0) -> CoordinateSystem:
|
|
187
|
+
"""Returns a CoordinateSystem object for the plane where the XY axes are aligned
|
|
188
|
+
with the plane UV axis and Z is normal.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
x0 (float): The x coordinate of the origin
|
|
192
|
+
y0 (float): The y coordinate of the origin
|
|
193
|
+
z0 (float): The z coordinate of the origin
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
CoordinateSystem: The coordinate system object
|
|
197
|
+
"""
|
|
198
|
+
origin = np.array([x0, y0, z0])
|
|
199
|
+
return CoordinateSystem(self.uax, self.vax, self.normal, origin)
|
|
200
|
+
|
|
201
|
+
def grid(self,
|
|
202
|
+
uax: np.ndarray | tuple[float, float, int],
|
|
203
|
+
vax: np.ndarray | tuple[float, float, int],
|
|
204
|
+
origin: np.ndarray | tuple[float, float, float],
|
|
205
|
+
indexing: Literal['xy','ij'] = 'xy') -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
206
|
+
"""Spans a grid of points in the plane based on a np.linspace like argument type.
|
|
207
|
+
The first uax argument should be a start, finish, Npoints type tuple of a float, float and integer.
|
|
208
|
+
Item for the vax. The origin defines the coordinate at which u,v = 0 will be placed.
|
|
209
|
+
The return type is an N,M np.meshgrid defined by the indexing 'xy' or 'ij'.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
uax (np.ndarray | tuple[float, float, int]): The uax linspace argument values
|
|
213
|
+
vax (np.ndarray | tuple[float, float, int]): The vax linspace argument values
|
|
214
|
+
origin (np.ndarray | tuple[float, float, float]): The origin for u,v = 0
|
|
215
|
+
indexing (Literal['xy','ij'], optional): The indexing type. Defaults to 'xy'.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
tuple[np.ndarray, np.ndarray, np.ndarray]: The X, Y, Z (N,M) meshgrid of coordinates.
|
|
219
|
+
"""
|
|
220
|
+
if isinstance(uax, tuple):
|
|
221
|
+
uax = np.linspace(*uax)
|
|
222
|
+
if isinstance(vax, tuple):
|
|
223
|
+
vax = np.linspace(*vax)
|
|
224
|
+
if isinstance(origin, tuple):
|
|
225
|
+
origin = np.array(origin)
|
|
226
|
+
|
|
227
|
+
U, V = np.meshgrid(uax, vax, indexing=indexing)
|
|
228
|
+
uax = U.flatten()
|
|
229
|
+
vax = V.flatten()
|
|
230
|
+
shp = U.shape
|
|
231
|
+
xs = self.uax.np[0]*uax + self.vax.np[0]*vax + origin[0]
|
|
232
|
+
ys = self.uax.np[1]*uax + self.vax.np[1]*vax + origin[1]
|
|
233
|
+
zs = self.uax.np[2]*uax + self.vax.np[2]*vax + origin[2]
|
|
234
|
+
|
|
235
|
+
return xs.reshape(shp), ys.reshape(shp), zs.reshape(shp)
|
|
236
|
+
|
|
237
|
+
def span(self, u: float, v: float, N: int, origin: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
238
|
+
"""Create a grid of XYZ coordinates in the plane reaching from 0 to u and 0 to v in N steps at the given origin
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
u (float): The distance along the first axis
|
|
242
|
+
v (float): The distance along the second axis
|
|
243
|
+
N (int): The number of sample points per axis
|
|
244
|
+
origin (np.ndarray): The origin of the planar grid.
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
tuple[np.ndarray, np.ndarray, np.ndarray]: The set of X,Y,Z coordinates.
|
|
248
|
+
"""
|
|
249
|
+
uax = np.linspace(0, u, N)
|
|
250
|
+
vax = np.linspace(0, v, N)
|
|
251
|
+
U, V = np.meshgrid(uax, vax, indexing='ij')
|
|
252
|
+
uax = U.flatten()
|
|
253
|
+
vax = V.flatten()
|
|
254
|
+
shp = U.shape
|
|
255
|
+
xs = self.uax.np[0]*uax + self.vax.np[0]*vax + origin[0]
|
|
256
|
+
ys = self.uax.np[1]*uax + self.vax.np[1]*vax + origin[1]
|
|
257
|
+
zs = self.uax.np[2]*uax + self.vax.np[2]*vax + origin[2]
|
|
258
|
+
|
|
259
|
+
return xs.reshape(shp), ys.reshape(shp), zs.reshape(shp)
|
|
260
|
+
|
|
261
|
+
XYPLANE = Plane(XAX, YAX)
|
|
262
|
+
XZPLANE = Plane(XAX, ZAX)
|
|
263
|
+
YZPLANE = Plane(YAX, ZAX)
|
|
264
|
+
YXPLANE = Plane(YAX, XAX)
|
|
265
|
+
ZXPLANE = Plane(ZAX, XAX)
|
|
266
|
+
ZYPLANE = Plane(ZAX, YAX)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@dataclass
|
|
270
|
+
class CoordinateSystem:
|
|
271
|
+
"""A class representing CoordinateSystem information.
|
|
272
|
+
|
|
273
|
+
This class is widely used throughout the FEM solver to embed objects in space properly.
|
|
274
|
+
The x,y and z unit vectors are defined by Axis objects. The origin by a np.ndarray.
|
|
275
|
+
|
|
276
|
+
The property _is_global is should only be set for any CoordinateSystem class that is wished to
|
|
277
|
+
be considered as global. This is reserved for the GCS intance create automatically with:
|
|
278
|
+
xax = (1,0,0)
|
|
279
|
+
yax = (0,1,0)
|
|
280
|
+
zax = (0,0,1)
|
|
281
|
+
origin = (0., 0., 0.) meters
|
|
282
|
+
"""
|
|
283
|
+
xax: Axis
|
|
284
|
+
yax: Axis
|
|
285
|
+
zax: Axis
|
|
286
|
+
origin: np.ndarray = field(default_factory=lambda: np.array([0,0,0]))
|
|
287
|
+
_is_global: bool = False
|
|
288
|
+
|
|
289
|
+
def __post_init__(self):
|
|
290
|
+
self.xax = _parse_axis(self.xax)
|
|
291
|
+
self.yax = _parse_axis(self.yax)
|
|
292
|
+
self.zax = _parse_axis(self.zax)
|
|
293
|
+
|
|
294
|
+
self._basis = np.array([self.xax.np, self.yax.np, self.zax.np]).T
|
|
295
|
+
|
|
296
|
+
self._basis_inv = np.linalg.pinv(self._basis)
|
|
297
|
+
|
|
298
|
+
def __repr__(self) -> str:
|
|
299
|
+
return f"CoordinateSystem({self.xax}, {self.yax}, {self.zax}, {self.origin})"
|
|
300
|
+
|
|
301
|
+
def copy(self) -> CoordinateSystem:
|
|
302
|
+
""" Creates a copy of this coordinate system."""
|
|
303
|
+
return CoordinateSystem(self.xax, self.yax, self.zax, self.origin)
|
|
304
|
+
|
|
305
|
+
def displace(self, dx: float, dy: float, dz: float) -> CoordinateSystem:
|
|
306
|
+
"""Creates a displaced version of this coordinate system. The basis is kept the same.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
dx (float): The X-displacement (meters)
|
|
310
|
+
dy (float): The Y-displacement (meters)
|
|
311
|
+
dz (float): The Z-displacement (meters)
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
CoordinateSystem: The new CoordinateSystem object.
|
|
315
|
+
"""
|
|
316
|
+
csnew = CoordinateSystem(self.xax, self.yax, self.zax, self.origin + np.array([dx, dy, dz]))
|
|
317
|
+
return csnew
|
|
318
|
+
|
|
319
|
+
def rotate(self, axis: tuple | list | np.ndarray | Axis,
|
|
320
|
+
angle: float,
|
|
321
|
+
degrees: bool = True) -> CoordinateSystem:
|
|
322
|
+
"""Return a new CoordinateSystem rotated about the given axis (through the global origin)
|
|
323
|
+
by `angle`. If `degrees` is True, `angle` is interpreted in degrees.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
axis (tuple | list | np.ndarray | Axis): The rotation axis
|
|
327
|
+
angle (float): The rotation angle (in degrees if degrees = True)
|
|
328
|
+
degrees (bool, optional): Whether to use degrees. Defaults to True.
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
CoordinateSystem: The new rotated coordinate system
|
|
332
|
+
"""
|
|
333
|
+
|
|
334
|
+
# Convert to radians if needed
|
|
335
|
+
if degrees:
|
|
336
|
+
theta = angle * np.pi/180
|
|
337
|
+
|
|
338
|
+
# Normalize the rotation axis
|
|
339
|
+
u = _parse_vector(axis)
|
|
340
|
+
u = u / np.linalg.norm(u)
|
|
341
|
+
|
|
342
|
+
# Build the skew-symmetric cross-product matrix K for u
|
|
343
|
+
K = np.array([
|
|
344
|
+
[ 0, -u[2], u[1]],
|
|
345
|
+
[ u[2], 0, -u[0]],
|
|
346
|
+
[-u[1], u[0], 0]
|
|
347
|
+
], dtype=float)
|
|
348
|
+
|
|
349
|
+
# Rodrigues' rotation formula: R = I + sinθ·K + (1−cosθ)·K²
|
|
350
|
+
Imat = np.eye(3, dtype=float)
|
|
351
|
+
R = Imat + np.sin(theta) * K + (1 - np.cos(theta)) * (K @ K)
|
|
352
|
+
|
|
353
|
+
# Rotate each axis and the origin
|
|
354
|
+
new_x = R @ self.xax.vector
|
|
355
|
+
new_y = R @ self.yax.vector
|
|
356
|
+
new_z = R @ self.zax.vector
|
|
357
|
+
#new_o = R @ self.origin
|
|
358
|
+
|
|
359
|
+
return CoordinateSystem(
|
|
360
|
+
xax=new_x,
|
|
361
|
+
yax=new_y,
|
|
362
|
+
zax=new_z,
|
|
363
|
+
origin=self.origin,
|
|
364
|
+
_is_global=self._is_global
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
def swapxy(self) -> None:
|
|
368
|
+
"""Swaps the XY axes of the CoordinateSystem.
|
|
369
|
+
"""
|
|
370
|
+
self.xax, self.yax = self.yax, self.xax
|
|
371
|
+
self.__post_init__()
|
|
372
|
+
|
|
373
|
+
def affine_from_global(self) -> np.ndarray:
|
|
374
|
+
"""Returns an Affine transformation matrix in order to transform coordinates from
|
|
375
|
+
the global coordinate system to this coordinate system.
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
np.ndarray: The affine transformation matrix.
|
|
379
|
+
"""
|
|
380
|
+
# ensure they’re 1-D
|
|
381
|
+
x = self.xax.vector
|
|
382
|
+
y = self.yax.vector
|
|
383
|
+
z = self.zax.vector
|
|
384
|
+
o = self.origin
|
|
385
|
+
|
|
386
|
+
T = np.eye(4, dtype=float)
|
|
387
|
+
T[0:3, 0] = x
|
|
388
|
+
T[0:3, 1] = y
|
|
389
|
+
T[0:3, 2] = z
|
|
390
|
+
T[0:3, 3] = o
|
|
391
|
+
return T
|
|
392
|
+
|
|
393
|
+
def affine_to_global(self) -> np.ndarray:
|
|
394
|
+
"""Returns an Affine transformation matrix in order to transform coordinates from
|
|
395
|
+
this local coordinate system to the coordinate system.
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
np.ndarray: The affine transformation matrix.
|
|
399
|
+
"""
|
|
400
|
+
T = self.affine_from_global()
|
|
401
|
+
R = T[0:3, 0:3]
|
|
402
|
+
o = T[0:3, 3]
|
|
403
|
+
R_inv = np.linalg.inv(R)
|
|
404
|
+
o_new = - R_inv @ o
|
|
405
|
+
T_inv = np.eye(4, dtype=float)
|
|
406
|
+
T_inv[0:3, 0:3] = R_inv
|
|
407
|
+
T_inv[0:3, 3] = o_new
|
|
408
|
+
return T_inv
|
|
409
|
+
|
|
410
|
+
def in_global_cs(self, x: np.ndarray, y: np.ndarray, z: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
411
|
+
"""Converts x,y,z coordinates into the global coordinate system.
|
|
412
|
+
|
|
413
|
+
Args:
|
|
414
|
+
x (np.ndarray): The x-coordinates (meter)
|
|
415
|
+
y (np.ndarray): The y-coordinates (meter)
|
|
416
|
+
z (np.ndarray): The z-coordinates (meter)
|
|
417
|
+
|
|
418
|
+
Returns:
|
|
419
|
+
tuple[np.ndarray, np.ndarray, np.ndarray]: The resultant x, y and z coordinates.
|
|
420
|
+
"""
|
|
421
|
+
xg = self.xax.np[0]*x + self.yax.np[0]*y + self.zax.np[0]*z + self.origin[0]
|
|
422
|
+
yg = self.xax.np[1]*x + self.yax.np[1]*y + self.zax.np[1]*z + self.origin[1]
|
|
423
|
+
zg = self.xax.np[2]*x + self.yax.np[2]*y + self.zax.np[2]*z + self.origin[2]
|
|
424
|
+
return xg, yg, zg
|
|
425
|
+
|
|
426
|
+
def in_local_cs(self, x: np.ndarray,
|
|
427
|
+
y: np.ndarray,
|
|
428
|
+
z: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
429
|
+
"""Converts x,y,z coordinates into the local coordinate system.
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
x (np.ndarray): The x-coordinates (meter)
|
|
433
|
+
y (np.ndarray): The y-coordinates (meter)
|
|
434
|
+
z (np.ndarray): The z-coordinates (meter)
|
|
435
|
+
|
|
436
|
+
Returns:
|
|
437
|
+
tuple[np.ndarray, np.ndarray, np.ndarray]: The resultant x, y and z coordinates.
|
|
438
|
+
"""
|
|
439
|
+
B = self._basis_inv
|
|
440
|
+
xg = x - self.origin[0]
|
|
441
|
+
yg = y - self.origin[1]
|
|
442
|
+
zg = z - self.origin[2]
|
|
443
|
+
x = B[0,0]*xg + B[0,1]*yg + B[0,2]*zg
|
|
444
|
+
y = B[1,0]*xg + B[1,1]*yg + B[1,2]*zg
|
|
445
|
+
z = B[2,0]*xg + B[2,1]*yg + B[2,2]*zg
|
|
446
|
+
return x, y, z
|
|
447
|
+
|
|
448
|
+
def in_global_basis(self, x: np.ndarray,
|
|
449
|
+
y: np.ndarray,
|
|
450
|
+
z: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
451
|
+
"""Converts x,y,z vector components into the global coordinate basis.
|
|
452
|
+
|
|
453
|
+
Args:
|
|
454
|
+
x (np.ndarray): The x-vector components (meter)
|
|
455
|
+
y (np.ndarray): The y-vector components (meter)
|
|
456
|
+
z (np.ndarray): The z-vector components (meter)
|
|
457
|
+
|
|
458
|
+
Returns:
|
|
459
|
+
tuple[np.ndarray, np.ndarray, np.ndarray]: The resultant x, y and z vectors.
|
|
460
|
+
"""
|
|
461
|
+
xg = self.xax.np[0]*x + self.yax.np[0]*y + self.zax.np[0]*z
|
|
462
|
+
yg = self.xax.np[1]*x + self.yax.np[1]*y + self.zax.np[1]*z
|
|
463
|
+
zg = self.xax.np[2]*x + self.yax.np[2]*y + self.zax.np[2]*z
|
|
464
|
+
return xg, yg, zg
|
|
465
|
+
|
|
466
|
+
def in_local_basis(self, x: np.ndarray,
|
|
467
|
+
y: np.ndarray,
|
|
468
|
+
z: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
469
|
+
"""Converts x,y,z vector components into the local coordinate basis.
|
|
470
|
+
|
|
471
|
+
Args:
|
|
472
|
+
x (np.ndarray): The x-vector components (meter)
|
|
473
|
+
y (np.ndarray): The y-vector components (meter)
|
|
474
|
+
z (np.ndarray): The z-vector components (meter)
|
|
475
|
+
|
|
476
|
+
Returns:
|
|
477
|
+
tuple[np.ndarray, np.ndarray, np.ndarray]: The resultant x, y and z vectors.
|
|
478
|
+
"""
|
|
479
|
+
B = self._basis_inv
|
|
480
|
+
xg = x
|
|
481
|
+
yg = y
|
|
482
|
+
zg = z
|
|
483
|
+
x = B[0,0]*xg + B[0,1]*yg + B[0,2]*zg
|
|
484
|
+
y = B[1,0]*xg + B[1,1]*yg + B[1,2]*zg
|
|
485
|
+
z = B[2,0]*xg + B[2,1]*yg + B[2,2]*zg
|
|
486
|
+
return x, y, z
|
|
487
|
+
|
|
488
|
+
@property
|
|
489
|
+
def gx(self) -> float:
|
|
490
|
+
""" The origin x-coordinate in global coordinates."""
|
|
491
|
+
return self.origin[0]
|
|
492
|
+
|
|
493
|
+
@property
|
|
494
|
+
def gy(self) -> float:
|
|
495
|
+
""" The origin y-coordinate in global coordinates."""
|
|
496
|
+
return self.origin[1]
|
|
497
|
+
|
|
498
|
+
@property
|
|
499
|
+
def gz(self) -> float:
|
|
500
|
+
""" The origin z-coordinate in global coordinates."""
|
|
501
|
+
return self.origin[2]
|
|
502
|
+
|
|
503
|
+
@property
|
|
504
|
+
def gxhat(self) -> np.ndarray:
|
|
505
|
+
""" The x-axis unit vector in global coordinates."""
|
|
506
|
+
return self.xax.np
|
|
507
|
+
|
|
508
|
+
@property
|
|
509
|
+
def gyhat(self) -> np.ndarray:
|
|
510
|
+
""" The y-axis unit vector in global coordinates."""
|
|
511
|
+
return self.yax.np
|
|
512
|
+
|
|
513
|
+
@property
|
|
514
|
+
def gzhat(self) -> np.ndarray:
|
|
515
|
+
""" The z-axis unit vector in global coordinates."""
|
|
516
|
+
return self.zax.np
|
|
517
|
+
|
|
518
|
+
# A shorthand alias for the CoordinateSystem Class
|
|
519
|
+
CS = CoordinateSystem
|
|
520
|
+
|
|
521
|
+
# The global coordinate system
|
|
522
|
+
GCS = CoordinateSystem(XAX, YAX, ZAX, np.zeros(3), _is_global=True)
|
|
523
|
+
|
|
@@ -0,0 +1,36 @@
|
|
|
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 typing import TypeVar, Generic, Any
|
|
20
|
+
from loguru import logger
|
|
21
|
+
from .physics.microwave.microwave_data import MWData
|
|
22
|
+
from .simulation_data import DataContainer, DataEntry
|
|
23
|
+
|
|
24
|
+
class SimulationDataset:
|
|
25
|
+
"""This simple class contains the different kinds of data sets in the Simulation Model. It includes
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
self.mw: MWData - The Microwave physics data
|
|
29
|
+
self.globals: dict[str, Any] - Any globally defined data of choice in the Simulation
|
|
30
|
+
self.sim: DataContainer - Generic simulation data associated with different instantiation of your at parameter level.
|
|
31
|
+
"""
|
|
32
|
+
def __init__(self):
|
|
33
|
+
self.mw: MWData = MWData()
|
|
34
|
+
self.globals: dict[str, Any] = dict()
|
|
35
|
+
self.sim: DataContainer = DataContainer()
|
|
36
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
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 .femdata import FEMBasis
|
|
19
|
+
from .nedelec2 import Nedelec2
|