emerge 0.4.6__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 (80) hide show
  1. emerge/__init__.py +54 -0
  2. emerge/__main__.py +5 -0
  3. emerge/_emerge/__init__.py +42 -0
  4. emerge/_emerge/bc.py +197 -0
  5. emerge/_emerge/coord.py +119 -0
  6. emerge/_emerge/cs.py +523 -0
  7. emerge/_emerge/dataset.py +36 -0
  8. emerge/_emerge/elements/__init__.py +19 -0
  9. emerge/_emerge/elements/femdata.py +212 -0
  10. emerge/_emerge/elements/index_interp.py +64 -0
  11. emerge/_emerge/elements/legrange2.py +172 -0
  12. emerge/_emerge/elements/ned2_interp.py +645 -0
  13. emerge/_emerge/elements/nedelec2.py +140 -0
  14. emerge/_emerge/elements/nedleg2.py +217 -0
  15. emerge/_emerge/geo/__init__.py +24 -0
  16. emerge/_emerge/geo/horn.py +107 -0
  17. emerge/_emerge/geo/modeler.py +449 -0
  18. emerge/_emerge/geo/operations.py +254 -0
  19. emerge/_emerge/geo/pcb.py +1244 -0
  20. emerge/_emerge/geo/pcb_tools/calculator.py +28 -0
  21. emerge/_emerge/geo/pcb_tools/macro.py +79 -0
  22. emerge/_emerge/geo/pmlbox.py +204 -0
  23. emerge/_emerge/geo/polybased.py +529 -0
  24. emerge/_emerge/geo/shapes.py +427 -0
  25. emerge/_emerge/geo/step.py +77 -0
  26. emerge/_emerge/geo2d.py +86 -0
  27. emerge/_emerge/geometry.py +510 -0
  28. emerge/_emerge/howto.py +214 -0
  29. emerge/_emerge/logsettings.py +5 -0
  30. emerge/_emerge/material.py +118 -0
  31. emerge/_emerge/mesh3d.py +730 -0
  32. emerge/_emerge/mesher.py +339 -0
  33. emerge/_emerge/mth/common_functions.py +33 -0
  34. emerge/_emerge/mth/integrals.py +71 -0
  35. emerge/_emerge/mth/optimized.py +357 -0
  36. emerge/_emerge/periodic.py +263 -0
  37. emerge/_emerge/physics/__init__.py +0 -0
  38. emerge/_emerge/physics/microwave/__init__.py +1 -0
  39. emerge/_emerge/physics/microwave/adaptive_freq.py +279 -0
  40. emerge/_emerge/physics/microwave/assembly/assembler.py +569 -0
  41. emerge/_emerge/physics/microwave/assembly/curlcurl.py +448 -0
  42. emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +426 -0
  43. emerge/_emerge/physics/microwave/assembly/robinbc.py +433 -0
  44. emerge/_emerge/physics/microwave/microwave_3d.py +1150 -0
  45. emerge/_emerge/physics/microwave/microwave_bc.py +915 -0
  46. emerge/_emerge/physics/microwave/microwave_data.py +1148 -0
  47. emerge/_emerge/physics/microwave/periodic.py +82 -0
  48. emerge/_emerge/physics/microwave/port_functions.py +53 -0
  49. emerge/_emerge/physics/microwave/sc.py +175 -0
  50. emerge/_emerge/physics/microwave/simjob.py +147 -0
  51. emerge/_emerge/physics/microwave/sparam.py +138 -0
  52. emerge/_emerge/physics/microwave/touchstone.py +140 -0
  53. emerge/_emerge/plot/__init__.py +0 -0
  54. emerge/_emerge/plot/display.py +394 -0
  55. emerge/_emerge/plot/grapher.py +93 -0
  56. emerge/_emerge/plot/matplotlib/mpldisplay.py +264 -0
  57. emerge/_emerge/plot/pyvista/__init__.py +1 -0
  58. emerge/_emerge/plot/pyvista/display.py +931 -0
  59. emerge/_emerge/plot/pyvista/display_settings.py +24 -0
  60. emerge/_emerge/plot/simple_plots.py +551 -0
  61. emerge/_emerge/plot.py +225 -0
  62. emerge/_emerge/projects/__init__.py +0 -0
  63. emerge/_emerge/projects/_gen_base.txt +32 -0
  64. emerge/_emerge/projects/_load_base.txt +24 -0
  65. emerge/_emerge/projects/generate_project.py +40 -0
  66. emerge/_emerge/selection.py +596 -0
  67. emerge/_emerge/simmodel.py +444 -0
  68. emerge/_emerge/simulation_data.py +411 -0
  69. emerge/_emerge/solver.py +993 -0
  70. emerge/_emerge/system.py +54 -0
  71. emerge/cli.py +19 -0
  72. emerge/lib.py +57 -0
  73. emerge/plot.py +1 -0
  74. emerge/pyvista.py +1 -0
  75. {emerge-0.4.6.dist-info → emerge-0.4.8.dist-info}/METADATA +1 -1
  76. emerge-0.4.8.dist-info/RECORD +78 -0
  77. emerge-0.4.8.dist-info/entry_points.txt +2 -0
  78. emerge-0.4.6.dist-info/RECORD +0 -4
  79. emerge-0.4.6.dist-info/entry_points.txt +0 -2
  80. {emerge-0.4.6.dist-info → emerge-0.4.8.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[&#39;xy&#39;,&#39;ij&#39;], 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