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
@@ -0,0 +1,264 @@
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 ...mesh3d import Mesh3D, SurfaceMesh
19
+ from ...geometry import GeoObject, GeoSurface, GeoVolume
20
+ from ...selection import FaceSelection, DomainSelection, EdgeSelection, Selection
21
+ from ...bc import PortBC
22
+ import numpy as np
23
+ from typing import Iterable, Literal, Callable
24
+ from functools import wraps
25
+ from ..display import BaseDisplay
26
+ from matplotlib.colors import Normalize
27
+ from matplotlib import cm
28
+ from collections import defaultdict
29
+
30
+ import matplotlib.pyplot as plt
31
+ from mpl_toolkits.mplot3d import Axes3D
32
+
33
+ def align_triangle_normals(nodes: np.ndarray, triangles: np.ndarray):
34
+ """
35
+ Flip triangle winding to align with given normals.
36
+
37
+ Parameters:
38
+ - nodes: (3, N) array of 3D points.
39
+ - triangles: (3, M) array of indices into nodes.
40
+ - normals: (3, M) array of desired triangle normals.
41
+
42
+ Returns:
43
+ - triangles_aligned: (3, M) array with consistent winding.
44
+ """
45
+ # Get triangle vertices
46
+ p0 = nodes[:, triangles[0,:]]
47
+ p1 = nodes[:, triangles[1,:]]
48
+ p2 = nodes[:, triangles[2,:]]
49
+
50
+ # Compute current normals (not normalized)
51
+ ns = np.cross(p1 - p0, p2 - p0, axis=0)
52
+ tris_out = np.zeros_like(triangles, dtype=np.int64)
53
+ for it in range(triangles.shape[1]):
54
+ if (-ns[0,it]-ns[1,it]-ns[2,it]) < 0:
55
+ tris_out[:,it] = triangles[:,it]
56
+ else:
57
+ tris_out[:,it] = triangles[(0,2,1),it]
58
+ return tris_out
59
+
60
+ def _logscale(dx, dy, dz):
61
+ """
62
+ Logarithmically scales vector magnitudes so that the largest remains unchanged
63
+ and others are scaled down logarithmically.
64
+
65
+ Parameters:
66
+ dx, dy, dz (np.ndarray): Components of vectors.
67
+
68
+ Returns:
69
+ Tuple[np.ndarray, np.ndarray, np.ndarray]: Scaled dx, dy, dz arrays.
70
+ """
71
+ dx = np.asarray(dx)
72
+ dy = np.asarray(dy)
73
+ dz = np.asarray(dz)
74
+
75
+ # Compute original magnitudes
76
+ mags = np.sqrt(dx**2 + dy**2 + dz**2)
77
+ mags_nonzero = np.where(mags == 0, 1e-10, mags) # avoid log(0)
78
+
79
+ # Logarithmic scaling (scaled to max = original max)
80
+ log_mags = np.log10(mags_nonzero)
81
+ log_min = np.min(log_mags)
82
+ log_max = np.max(log_mags)
83
+
84
+ if log_max == log_min:
85
+ # All vectors have the same length
86
+ return dx, dy, dz
87
+
88
+ # Normalize log magnitudes to [0, 1]
89
+ log_scaled = (log_mags - log_min) / (log_max - log_min)
90
+
91
+ # Scale back to original max magnitude
92
+ max_mag = np.max(mags)
93
+ new_mags = log_scaled * max_mag
94
+
95
+ # Compute unit vectors
96
+ unit_dx = dx / mags_nonzero
97
+ unit_dy = dy / mags_nonzero
98
+ unit_dz = dz / mags_nonzero
99
+
100
+ # Apply scaled magnitudes
101
+ scaled_dx = unit_dx * new_mags
102
+ scaled_dy = unit_dy * new_mags
103
+ scaled_dz = unit_dz * new_mags
104
+
105
+ return scaled_dx, scaled_dy, scaled_dz
106
+
107
+ def _make_facecolors(C, cmap='viridis'):
108
+ """
109
+ Convert C (N, M) to facecolors (N-1, M-1, 4) using a colormap.
110
+ """
111
+ from matplotlib import cm
112
+ C_avg = 0.25 * (C[:-1, :-1] + C[1:, :-1] + C[:-1, 1:] + C[1:, 1:])
113
+ normed = (C_avg - np.min(C_avg)) / np.ptp(C_avg)
114
+ return plt.get_cmap(cmap)(normed)
115
+
116
+ def _min_distance(xs, ys, zs):
117
+ """
118
+ Compute the minimum Euclidean distance between any two points
119
+ defined by the 1D arrays xs, ys, zs.
120
+
121
+ Parameters:
122
+ xs (np.ndarray): x-coordinates of the points
123
+ ys (np.ndarray): y-coordinates of the points
124
+ zs (np.ndarray): z-coordinates of the points
125
+
126
+ Returns:
127
+ float: The minimum Euclidean distance between any two points
128
+ """
129
+ # Stack the coordinates into a (N, 3) array
130
+ points = np.stack((xs, ys, zs), axis=-1)
131
+
132
+ # Compute pairwise squared distances using broadcasting
133
+ diff = points[:, np.newaxis, :] - points[np.newaxis, :, :]
134
+ dists_squared = np.sum(diff ** 2, axis=-1)
135
+
136
+ # Set diagonal to infinity to ignore zero distances to self
137
+ np.fill_diagonal(dists_squared, np.inf)
138
+
139
+ # Get the minimum distance
140
+ min_dist = np.sqrt(np.min(dists_squared))
141
+ return min_dist
142
+
143
+ def _norm(x, y, z):
144
+ return np.sqrt(np.abs(x)**2 + np.abs(y)**2 + np.abs(z)**2)
145
+
146
+ def _select(obj: GeoObject | Selection) -> Selection:
147
+ if isinstance(obj, GeoObject):
148
+ return obj.select
149
+ return obj
150
+
151
+ def _merge(lst: list[GeoObject | Selection]) -> Selection:
152
+ selections = [_select(item) for item in lst]
153
+ dim = selections[0].dim
154
+ all_tags = []
155
+ for item in lst:
156
+ all_tags.extend(_select(item).tags)
157
+
158
+ if dim==1:
159
+ return EdgeSelection(all_tags)
160
+ elif dim==2:
161
+ return FaceSelection(all_tags)
162
+ elif dim==3:
163
+ return DomainSelection(all_tags)
164
+
165
+ class MPLDisplay(BaseDisplay):
166
+ COLORS: dict = {'green': (0,0.8,0), 'red': (0.8,0,0), 'blue': (0,0,0.8)}
167
+ def __init__(self, mesh: Mesh3D):
168
+ self._mesh: Mesh3D = mesh
169
+ self._fig = None
170
+ self._ax = None
171
+
172
+ def init(self):
173
+ if self._fig is None or self._ax is None:
174
+ self._fig = plt.figure()
175
+ self._ax = self._fig.add_subplot(111, projection='3d')
176
+ self._ax.axis('equal')
177
+ self._ax.set_aspect('equal')
178
+
179
+ def show(self):
180
+ plt.show()
181
+ self._fig = None
182
+ self._ax = None
183
+
184
+ ## OBLIGATORY METHODS
185
+ def add_object(self, obj: GeoObject | Selection | Iterable, opacity: float = 1, color: str = None, **kwargs):
186
+ self.init()
187
+
188
+ if color is not None:
189
+ color = self.COLORS.get(color,(0,0.5,0))
190
+
191
+ boundary_tris = self._mesh.boundary_triangles(obj.dimtags)
192
+ tris = self._mesh.tris[:,boundary_tris]
193
+ tris = align_triangle_normals(self._mesh.nodes, tris)
194
+ x = self._mesh.nodes[0,:]
195
+ y = self._mesh.nodes[1,:]
196
+ z = self._mesh.nodes[2,:]
197
+ surf = self._ax.plot_trisurf(
198
+ x, y, z,triangles=tris.T,
199
+ color=obj.color_rgb + (opacity,),
200
+ linewidth=0.2,
201
+ antialiased=True,
202
+ shade=True
203
+ )
204
+ # Equal aspect ratio
205
+
206
+
207
+ def add_scatter(self, xs: np.ndarray, ys: np.ndarray, zs: np.ndarray):
208
+ self.init()
209
+ self._ax.scatter(xs,ys,zs)
210
+
211
+ def add_portmode(self, port: PortBC, k0: float, Npoints: int = 5, dv=(0,0,0), XYZ=None,
212
+ field: Literal['E','H'] = 'E'):
213
+ if XYZ:
214
+ X,Y,Z = XYZ
215
+ else:
216
+ X,Y,Z = port.selection.sample(Npoints)
217
+ for x,y,z in zip(X,Y,Z):
218
+ self.add_portmode(port, k0, Npoints, dv, (x,y,z), field)
219
+ return
220
+ X = X+dv[0]
221
+ Y = Y+dv[1]
222
+ Z = Z+dv[2]
223
+ xf = X.flatten()
224
+ yf = Y.flatten()
225
+ zf = Z.flatten()
226
+
227
+ d1 = np.sqrt((X[0,1]-X[0,0])**2 + (Y[0,1]-Y[0,0])**2 + (Z[0,1]-Z[0,0])**2)
228
+ d2 = np.sqrt((X[1,0]-X[0,0])**2 + (Y[1,0]-Y[0,0])**2 + (Z[1,0]-Z[0,0])**2)
229
+ d = min(d1, d2)
230
+
231
+ F = port.port_mode_3d_global(xf,yf,zf, k0, which=field)
232
+
233
+ Fx = F[0,:].reshape(X.shape)
234
+ Fy = F[1,:].reshape(X.shape)
235
+ Fz = F[2,:].reshape(X.shape)
236
+
237
+ if field=='H':
238
+ F = np.imag(F)
239
+ Fnorm = np.sqrt(Fx.imag**2 + Fy.imag**2 + Fz.imag**2)
240
+ else:
241
+ F = np.real(F)
242
+ Fnorm = np.sqrt(Fx.real**2 + Fy.real**2 + Fz.real**2)
243
+
244
+ cmap = 'viridis'
245
+
246
+ colors = _make_facecolors(Fnorm)
247
+ surf = self._ax.plot_surface(X, Y, Z,facecolors=colors,
248
+ rstride=1, cstride=1, antialiased=True, linewidth=0)
249
+
250
+
251
+
252
+ N = np.max(Fnorm.flatten())*d*3
253
+ self.add_quiver(xf, yf, zf, F[0,:].real/N, F[1,:].real/N, F[2,:].real/N)
254
+
255
+ def add_quiver(self, x: np.ndarray, y: np.ndarray, z: np.ndarray,
256
+ dx: np.ndarray, dy: np.ndarray, dz: np.ndarray,
257
+ scale: float = 1,
258
+ scalemode: Literal['lin','log'] = 'lin'):
259
+
260
+ self.init()
261
+ dlmax = np.sqrt(dx**2+dy**2+dz**2)
262
+ dmin = _min_distance(x,y,z)
263
+ scale = dmin/max(dlmax)
264
+ self._ax.quiver(x,y,z,dx*scale,dy*scale,dz*scale, color='black')
@@ -0,0 +1 @@
1
+ from .display import PVDisplay