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
emerge/_emerge/mesher.py
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
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 gmsh
|
|
19
|
+
from .geometry import GeoVolume, GeoObject, GeoSurface
|
|
20
|
+
from .selection import Selection, FaceSelection
|
|
21
|
+
from .periodic import PeriodicCell
|
|
22
|
+
import numpy as np
|
|
23
|
+
from typing import Iterable, Callable
|
|
24
|
+
from loguru import logger
|
|
25
|
+
from enum import Enum
|
|
26
|
+
from .bc import Periodic
|
|
27
|
+
|
|
28
|
+
class MeshError(Exception):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
class Algorithm2D(Enum):
|
|
32
|
+
MESHADAPT = 1
|
|
33
|
+
AUTOMATIC = 2
|
|
34
|
+
INITIAL_MESH_ONLY = 3
|
|
35
|
+
DELAUNAY = 5
|
|
36
|
+
FRONTAL_DELAUNAY = 6
|
|
37
|
+
BAMG = 7
|
|
38
|
+
FRONTAL_DELAUNAY_QUADS = 8
|
|
39
|
+
PACKING_PARALLELOGRAMS = 9
|
|
40
|
+
QUASI_STRUCTURED_QUAD = 11
|
|
41
|
+
|
|
42
|
+
#(1: Delaunay, 3: Initial mesh only, 4: Frontal, 7: MMG3D, 9: R-tree, 10: HXT)
|
|
43
|
+
|
|
44
|
+
class Algorithm3D(Enum):
|
|
45
|
+
DELAUNAY = 1
|
|
46
|
+
INITIAL_MESH_ONLY = 3
|
|
47
|
+
FRONTAL = 4
|
|
48
|
+
MMG3D = 7
|
|
49
|
+
RTREE = 9
|
|
50
|
+
HXT = 10
|
|
51
|
+
|
|
52
|
+
_DOM_TO_STR = {
|
|
53
|
+
0: "point",
|
|
54
|
+
1: "edge",
|
|
55
|
+
2: "face",
|
|
56
|
+
3: "volume",
|
|
57
|
+
}
|
|
58
|
+
def unpack_lists(_list: list[list], collector: list = None) -> list:
|
|
59
|
+
'''Unpack a recursive list of lists'''
|
|
60
|
+
if collector is None:
|
|
61
|
+
collector = []
|
|
62
|
+
for item in _list:
|
|
63
|
+
if isinstance(item, list):
|
|
64
|
+
unpack_lists(item, collector)
|
|
65
|
+
else:
|
|
66
|
+
collector.append(item)
|
|
67
|
+
|
|
68
|
+
return collector
|
|
69
|
+
|
|
70
|
+
class Mesher:
|
|
71
|
+
|
|
72
|
+
def __init__(self):
|
|
73
|
+
self.objects: list[GeoObject] = []
|
|
74
|
+
self.size_definitions: list[tuple[int, float]] = []
|
|
75
|
+
self.mesh_fields: list[int] = []
|
|
76
|
+
self.min_size: float = None
|
|
77
|
+
self.max_size: float = None
|
|
78
|
+
self.periodic_cell: PeriodicCell = None
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def edge_tags(self) -> list[int]:
|
|
82
|
+
return [tag[1] for tag in gmsh.model.getEntities(1)]
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def face_tags(self) -> list[int]:
|
|
86
|
+
return [tag[1] for tag in gmsh.model.getEntities(2)]
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def node_tags(self) -> list[int]:
|
|
90
|
+
return [tag[1] for tag in gmsh.model.getEntities(0)]
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def volumes(self) -> list[GeoVolume]:
|
|
94
|
+
return [obj for obj in self.objects if isinstance(obj, GeoVolume)]
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def domain_boundary_face_tags(self) -> list[int]:
|
|
98
|
+
'''Get the face tags of the domain boundaries'''
|
|
99
|
+
domain_tags = gmsh.model.getEntities(3)
|
|
100
|
+
tags = gmsh.model.getBoundary(domain_tags, combined=True, oriented=False)
|
|
101
|
+
return [int(tag[1]) for tag in tags]
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def domain_internal_face_tags(self) -> list[int]:
|
|
105
|
+
alltags = self.face_tags
|
|
106
|
+
boundary = self.domain_boundary_face_tags
|
|
107
|
+
return [tag for tag in alltags if tag not in boundary]
|
|
108
|
+
|
|
109
|
+
def _get_periodic_bcs(self) -> list[Periodic]:
|
|
110
|
+
if self.periodic_cell is None:
|
|
111
|
+
return []
|
|
112
|
+
return self.periodic_cell._bcs
|
|
113
|
+
|
|
114
|
+
def _check_ready(self) -> None:
|
|
115
|
+
if self.max_size is None or self.min_size is None:
|
|
116
|
+
raise MeshError('Either maximum or minimum mesh size is undefined. Make sure \
|
|
117
|
+
to set the simulation frequency range before calling mesh instructions.')
|
|
118
|
+
|
|
119
|
+
def submit_objects(self, objects: list[GeoObject]) -> None:
|
|
120
|
+
"""Takes al ist of GeoObjects and computes the fragment.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
objects (list[GeoObject]): The set of GeoObjects
|
|
124
|
+
"""
|
|
125
|
+
if not isinstance(objects, list):
|
|
126
|
+
objects = [objects,]
|
|
127
|
+
|
|
128
|
+
objects = unpack_lists(objects)
|
|
129
|
+
embeddings = []
|
|
130
|
+
|
|
131
|
+
gmsh.model.occ.synchronize()
|
|
132
|
+
|
|
133
|
+
final_dimtags = unpack_lists([domain.dimtags for domain in objects])
|
|
134
|
+
|
|
135
|
+
dom_mapping = dict()
|
|
136
|
+
for dom in objects:
|
|
137
|
+
embeddings.extend(dom._embeddings)
|
|
138
|
+
for dt in dom.dimtags:
|
|
139
|
+
dom_mapping[dt] = dom
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
embedding_dimtags = unpack_lists([emb.dimtags for emb in embeddings])
|
|
143
|
+
|
|
144
|
+
tag_mapping: dict[int, dict] = {0: dict(),
|
|
145
|
+
1: dict(),
|
|
146
|
+
2: dict(),
|
|
147
|
+
3: dict()}
|
|
148
|
+
if len(objects) > 0:
|
|
149
|
+
dimtags, output_mapping = gmsh.model.occ.fragment(final_dimtags, embedding_dimtags)
|
|
150
|
+
|
|
151
|
+
for domain, mapping in zip(final_dimtags + embedding_dimtags, output_mapping):
|
|
152
|
+
tag_mapping[domain[0]][domain[1]] = [o[1] for o in mapping]
|
|
153
|
+
for dom in objects:
|
|
154
|
+
dom.update_tags(tag_mapping)
|
|
155
|
+
else:
|
|
156
|
+
dimtags = final_dimtags
|
|
157
|
+
|
|
158
|
+
self.objects = objects
|
|
159
|
+
|
|
160
|
+
gmsh.model.occ.synchronize()
|
|
161
|
+
|
|
162
|
+
def _set_mesh_periodicity(self,
|
|
163
|
+
face1: FaceSelection,
|
|
164
|
+
face2: FaceSelection,
|
|
165
|
+
lattice: tuple[float,float,float]):
|
|
166
|
+
translation = [1,0,0,lattice[0],
|
|
167
|
+
0,1,0,lattice[1],
|
|
168
|
+
0,0,1,lattice[2],
|
|
169
|
+
0,0,0,1]
|
|
170
|
+
gmsh.model.mesh.set_periodic(2, face2.tags, face1.tags, translation)
|
|
171
|
+
|
|
172
|
+
def set_algorithm(self,
|
|
173
|
+
obj: GeoObject,
|
|
174
|
+
algorithm: Algorithm3D) -> None:
|
|
175
|
+
for tag in obj.tags:
|
|
176
|
+
gmsh.model.mesh.setAlgorithm(obj.dim, tag, algorithm.value)
|
|
177
|
+
|
|
178
|
+
def set_periodic_cell(self, cell: PeriodicCell, excluded_faces: list[FaceSelection] = None):
|
|
179
|
+
"""Sets the periodic cell information based on the PeriodicCell class object"""
|
|
180
|
+
if excluded_faces is None:
|
|
181
|
+
for f1, f2, lat in cell.cell_data():
|
|
182
|
+
self._set_mesh_periodicity(f1, f2, lat)
|
|
183
|
+
else:
|
|
184
|
+
for f1, f2, lat in cell.cell_data():
|
|
185
|
+
self._set_mesh_periodicity(f1 - excluded_faces, f2 - excluded_faces, lat)
|
|
186
|
+
self.periodic_cell = cell
|
|
187
|
+
|
|
188
|
+
def _set_size_in_domain(self, tags: list[int], max_size: float) -> None:
|
|
189
|
+
"""Define the size of the mesh inside a domain
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
tags (list[int]): The tags of the geometry
|
|
193
|
+
max_size (float): The maximum size (in meters)
|
|
194
|
+
"""
|
|
195
|
+
ctag = gmsh.model.mesh.field.add("Constant")
|
|
196
|
+
gmsh.model.mesh.field.set_numbers(ctag, "VolumesList", tags)
|
|
197
|
+
gmsh.model.mesh.field.set_number(ctag, "VIn", max_size)
|
|
198
|
+
self.mesh_fields.append(ctag)
|
|
199
|
+
|
|
200
|
+
def _set_size_on_face(self, tags: list[int], max_size: float) -> None:
|
|
201
|
+
"""Define the size of the mesh on a face
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
tags (list[int]): The tags of the geometry
|
|
205
|
+
max_size (float): The maximum size (in meters)
|
|
206
|
+
"""
|
|
207
|
+
ctag = gmsh.model.mesh.field.add("Constant")
|
|
208
|
+
gmsh.model.mesh.field.set_numbers(ctag, "SurfacesList", tags)
|
|
209
|
+
gmsh.model.mesh.field.set_number(ctag, "VIn", max_size)
|
|
210
|
+
self.mesh_fields.append(ctag)
|
|
211
|
+
|
|
212
|
+
def set_mesh_size(self, discretizer: Callable, resolution: float):
|
|
213
|
+
|
|
214
|
+
dimtags = gmsh.model.occ.get_entities(2)
|
|
215
|
+
|
|
216
|
+
for dim, tag in dimtags:
|
|
217
|
+
gmsh.model.mesh.setSizeFromBoundary(2, tag, 0)
|
|
218
|
+
|
|
219
|
+
mintag = gmsh.model.mesh.field.add("Min")
|
|
220
|
+
size_mapping = dict()
|
|
221
|
+
|
|
222
|
+
for obj in sorted(self.objects, key=lambda x: x._priority):
|
|
223
|
+
if obj._unset_constraints:
|
|
224
|
+
self.unset_constraints(obj.dimtags)
|
|
225
|
+
|
|
226
|
+
size = discretizer(obj.material)*resolution*obj.mesh_multiplier
|
|
227
|
+
size = min(size, obj.max_meshsize)
|
|
228
|
+
logger.info(f'Setting mesh size for {_DOM_TO_STR[obj.dim]} domain with tags {obj.tags} to {1000*size:.3f}mm')
|
|
229
|
+
for tag in obj.tags:
|
|
230
|
+
size_mapping[tag] = size
|
|
231
|
+
|
|
232
|
+
for tag, size in size_mapping.items():
|
|
233
|
+
self._set_size_in_domain([tag,], size)
|
|
234
|
+
|
|
235
|
+
gmsh.model.mesh.field.setNumbers(mintag, "FieldsList", self.mesh_fields)
|
|
236
|
+
gmsh.model.mesh.field.setAsBackgroundMesh(mintag)
|
|
237
|
+
|
|
238
|
+
for tag, size in self.size_definitions:
|
|
239
|
+
gmsh.model.mesh.setSize([tag,], size)
|
|
240
|
+
|
|
241
|
+
def unset_constraints(self, dimtags: list[tuple[int,int]]):
|
|
242
|
+
'''Unset the mesh constraints for the given dimension tags.'''
|
|
243
|
+
for dimtag in dimtags:
|
|
244
|
+
gmsh.model.mesh.setSizeFromBoundary(dimtag[0], dimtag[1], 0)
|
|
245
|
+
|
|
246
|
+
def set_boundary_size(self, object: GeoSurface | FaceSelection,
|
|
247
|
+
size:float,
|
|
248
|
+
growth_rate: float = 1.4,
|
|
249
|
+
max_size: float = None):
|
|
250
|
+
"""Refine the mesh size along the boundary of a conducting surface
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
object (GeoSurface | FaceSelection): _description_
|
|
254
|
+
size (float): _description_
|
|
255
|
+
growth_rate (float, optional): _description_. Defaults to 1.1.
|
|
256
|
+
max_size (float, optional): _description_. Defaults to None.
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
dimtags = object.dimtags
|
|
260
|
+
|
|
261
|
+
if max_size is None:
|
|
262
|
+
self._check_ready()
|
|
263
|
+
max_size = self.max_size
|
|
264
|
+
|
|
265
|
+
growth_distance = np.log10(max_size/size)/np.log10(growth_rate)
|
|
266
|
+
|
|
267
|
+
nodes = gmsh.model.getBoundary(dimtags, combined=False, oriented=False, recursive=False)
|
|
268
|
+
|
|
269
|
+
disttag = gmsh.model.mesh.field.add("Distance")
|
|
270
|
+
|
|
271
|
+
gmsh.model.mesh.field.setNumbers(disttag, "CurvesList", [n[1] for n in nodes])
|
|
272
|
+
gmsh.model.mesh.field.setNumber(disttag, "Sampling", 100)
|
|
273
|
+
|
|
274
|
+
thtag = gmsh.model.mesh.field.add("Threshold")
|
|
275
|
+
gmsh.model.mesh.field.setNumber(thtag, "InField", disttag)
|
|
276
|
+
gmsh.model.mesh.field.setNumber(thtag, "SizeMin", size)
|
|
277
|
+
gmsh.model.mesh.field.setNumber(thtag, "SizeMax", max_size)
|
|
278
|
+
gmsh.model.mesh.field.setNumber(thtag, "DistMin", size)
|
|
279
|
+
gmsh.model.mesh.field.setNumber(thtag, "DistMax", growth_distance*size)
|
|
280
|
+
|
|
281
|
+
self.mesh_fields.append(thtag)
|
|
282
|
+
|
|
283
|
+
def set_domain_size(self, obj: GeoVolume | Selection, size: float):
|
|
284
|
+
"""Manually set the maximum element size inside a domain
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
obj (GeoVolume | Selection): The volumetric domain
|
|
288
|
+
size (float): The maximum mesh size
|
|
289
|
+
"""
|
|
290
|
+
self._set_size_in_domain(obj.tags, size)
|
|
291
|
+
|
|
292
|
+
def set_face_size(self, obj: GeoSurface | Selection, size: float):
|
|
293
|
+
"""Manually set the maximum element size on a face
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
obj (GeoSurface | Selection): The surface domain
|
|
297
|
+
size (float): The maximum size
|
|
298
|
+
"""
|
|
299
|
+
self._set_size_on_face(obj.tags, size)
|
|
300
|
+
|
|
301
|
+
def refine_conductor_edge(self, dimtags: list[tuple[int,int]], size):
|
|
302
|
+
nodes = gmsh.model.getBoundary(dimtags, combined=False, recursive=False)
|
|
303
|
+
|
|
304
|
+
# for node in nodes:
|
|
305
|
+
# pcoords = np.linspace(0, 0.5, 10)
|
|
306
|
+
# gmsh.model.mesh.setSizeAtParametricPoints(node[0], node[1], pcoords, size*np.ones_like(pcoords))
|
|
307
|
+
# #self.size_definitions.append((node, size))
|
|
308
|
+
# gmsh.model.mesh.setSizeFromBoundary(dimtag[0], dimtag[1], 0)
|
|
309
|
+
|
|
310
|
+
tag = gmsh.model.mesh.field.add("Distance")
|
|
311
|
+
|
|
312
|
+
#gmsh.model.mesh.field.setNumbers(1, "PointsList", [5])
|
|
313
|
+
gmsh.model.mesh.field.setNumbers(tag, "CurvesList", [n[1] for n in nodes])
|
|
314
|
+
gmsh.model.mesh.field.setNumber(tag, "Sampling", 100)
|
|
315
|
+
|
|
316
|
+
# We then define a `Threshold' field, which uses the return value of the
|
|
317
|
+
# `Distance' field 1 in order to define a simple change in element size
|
|
318
|
+
# depending on the computed distances
|
|
319
|
+
#
|
|
320
|
+
# SizeMax - /------------------
|
|
321
|
+
# /
|
|
322
|
+
# /
|
|
323
|
+
# /
|
|
324
|
+
# SizeMin -o----------------/
|
|
325
|
+
# | | |
|
|
326
|
+
# Point DistMin DistMax
|
|
327
|
+
thtag = gmsh.model.mesh.field.add("Threshold")
|
|
328
|
+
gmsh.model.mesh.field.setNumber(thtag, "InField", tag)
|
|
329
|
+
gmsh.model.mesh.field.setNumber(thtag, "SizeMin", size)
|
|
330
|
+
gmsh.model.mesh.field.setNumber(thtag, "SizeMax", 100)
|
|
331
|
+
gmsh.model.mesh.field.setNumber(thtag, "DistMin", 0.2*size)
|
|
332
|
+
gmsh.model.mesh.field.setNumber(thtag, "DistMax", 5*size)
|
|
333
|
+
|
|
334
|
+
self.mesh_fields.append(thtag)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
for dimtag in dimtags:
|
|
338
|
+
gmsh.model.mesh.setSizeFromBoundary(dimtag[0], dimtag[1], 0)
|
|
339
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
|
|
20
|
+
def norm(Field: np.ndarray) -> np.ndarray:
|
|
21
|
+
''' Computes the complex norm of a field (3,N)'''
|
|
22
|
+
return np.sqrt(np.abs(Field[0,:])**2 + np.abs(Field[1,:])**2 + np.abs(Field[2,:])**2)
|
|
23
|
+
|
|
24
|
+
def coax_rout(rin: float,
|
|
25
|
+
eps_r: float = 1,
|
|
26
|
+
Z0: float = 50) -> float:
|
|
27
|
+
return rin*10**(Z0*np.sqrt(eps_r)/138)
|
|
28
|
+
|
|
29
|
+
def coax_rin(rout: float,
|
|
30
|
+
eps_r: float = 1,
|
|
31
|
+
Z0: float = 50) -> float:
|
|
32
|
+
return rout/10**(Z0*np.sqrt(eps_r)/138)
|
|
33
|
+
|
|
@@ -0,0 +1,71 @@
|
|
|
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 Callable
|
|
20
|
+
from numba import njit, f8, i8, types, c16
|
|
21
|
+
from .optimized import gaus_quad_tri, generate_int_points_tri, calc_area
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@njit(c16(f8[:,:], i8[:,:], c16[:], f8[:,:], c16[:,:]), cache=True, nogil=True)
|
|
25
|
+
def _fast_integral_c(nodes, triangles, constants, DPTs, field_values):
|
|
26
|
+
tot = np.complex128(0.0)
|
|
27
|
+
|
|
28
|
+
for it in range(triangles.shape[1]):
|
|
29
|
+
vertex_ids = triangles[:, it]
|
|
30
|
+
v1 = nodes[:,vertex_ids[0]]
|
|
31
|
+
v2 = nodes[:,vertex_ids[1]]
|
|
32
|
+
v3 = nodes[:,vertex_ids[2]]
|
|
33
|
+
A = calc_area(v1, v2, v3)
|
|
34
|
+
field = np.sum(DPTs[0,:]*field_values[:,it])
|
|
35
|
+
tot = tot + constants[it] * field * A
|
|
36
|
+
return tot
|
|
37
|
+
|
|
38
|
+
@njit(f8(f8[:,:], i8[:,:], f8[:], f8[:,:], f8[:,:]), cache=True, nogil=True)
|
|
39
|
+
def _fast_integral_f(nodes, triangles, constants, DPTs, field_values):
|
|
40
|
+
tot = np.float64(0.0)
|
|
41
|
+
for it in range(triangles.shape[1]):
|
|
42
|
+
vertex_ids = triangles[:, it]
|
|
43
|
+
v1 = nodes[:,vertex_ids[0]]
|
|
44
|
+
v2 = nodes[:,vertex_ids[1]]
|
|
45
|
+
v3 = nodes[:,vertex_ids[2]]
|
|
46
|
+
A = calc_area(v1, v2, v3)
|
|
47
|
+
field = np.sum(DPTs[0,:]*field_values[:,it])
|
|
48
|
+
tot = tot + constants[it] * field * A
|
|
49
|
+
return tot
|
|
50
|
+
|
|
51
|
+
def surface_integral(nodes: np.ndarray,
|
|
52
|
+
triangles: np.ndarray,
|
|
53
|
+
function: Callable,
|
|
54
|
+
constants: np.ndarray = None,
|
|
55
|
+
ndpts: int = 4):
|
|
56
|
+
|
|
57
|
+
if constants is None:
|
|
58
|
+
constants = np.ones(triangles.shape[1])
|
|
59
|
+
|
|
60
|
+
DPTs = gaus_quad_tri(ndpts)
|
|
61
|
+
xall_flat, yall_flat, zall_flat, shape = generate_int_points_tri(nodes, triangles, DPTs)
|
|
62
|
+
|
|
63
|
+
fvals = function(xall_flat, yall_flat, zall_flat)
|
|
64
|
+
|
|
65
|
+
fA = fvals.reshape(shape)
|
|
66
|
+
|
|
67
|
+
if np.iscomplexobj(fA) or np.iscomplexobj(constants):
|
|
68
|
+
return _fast_integral_c(nodes, triangles, constants.astype(np.complex128), DPTs, fA.astype(np.complex128))
|
|
69
|
+
else:
|
|
70
|
+
return _fast_integral_f(nodes, triangles, constants, DPTs, fA)
|
|
71
|
+
|