emerge 1.0.5__py3-none-any.whl → 1.0.7__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 +2 -2
- emerge/_emerge/elements/index_interp.py +45 -0
- emerge/_emerge/geo/horn.py +17 -1
- emerge/_emerge/geo/pcb.py +10 -6
- emerge/_emerge/geo/polybased.py +16 -1
- emerge/_emerge/geo/shapes.py +93 -1
- emerge/_emerge/geometry.py +22 -4
- emerge/_emerge/material.py +32 -3
- emerge/_emerge/mesh3d.py +33 -3
- emerge/_emerge/mesher.py +24 -5
- emerge/_emerge/physics/microwave/adaptive_mesh.py +576 -0
- emerge/_emerge/physics/microwave/assembly/assembler.py +7 -6
- emerge/_emerge/physics/microwave/assembly/curlcurl.py +78 -77
- emerge/_emerge/physics/microwave/microwave_3d.py +137 -21
- emerge/_emerge/physics/microwave/microwave_bc.py +27 -5
- emerge/_emerge/physics/microwave/microwave_data.py +32 -6
- emerge/_emerge/plot/pyvista/display.py +93 -2
- emerge/_emerge/simmodel.py +87 -16
- emerge/_emerge/solver.py +93 -73
- {emerge-1.0.5.dist-info → emerge-1.0.7.dist-info}/METADATA +3 -2
- {emerge-1.0.5.dist-info → emerge-1.0.7.dist-info}/RECORD +24 -23
- {emerge-1.0.5.dist-info → emerge-1.0.7.dist-info}/WHEEL +0 -0
- {emerge-1.0.5.dist-info → emerge-1.0.7.dist-info}/entry_points.txt +0 -0
- {emerge-1.0.5.dist-info → emerge-1.0.7.dist-info}/licenses/LICENSE +0 -0
|
@@ -244,24 +244,24 @@ def ned2_tet_stiff_mass(tet_vertices, edge_lengths, local_edge_map, local_tri_ma
|
|
|
244
244
|
|
|
245
245
|
L2 = edge_lengths[ej]
|
|
246
246
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
Dmat[ei+0,ej+0] =
|
|
257
|
-
Dmat[ei+0,ej+10] =
|
|
258
|
-
Dmat[ei+10,ej+0] =
|
|
259
|
-
Dmat[ei+10,ej+10] =
|
|
247
|
+
erGF = matmul(Mm,GC)
|
|
248
|
+
erGC = matmul(Mm,GD)
|
|
249
|
+
erGD = dot_c(GA,erGF)
|
|
250
|
+
GE_MUL_erGF = dot_c(GA,erGC)
|
|
251
|
+
GE_MUL_erGC = dot_c(GB,erGF)
|
|
252
|
+
GA_MUL_erGF = dot_c(GB,erGC)
|
|
253
|
+
|
|
254
|
+
L12 = L1*L2
|
|
255
|
+
Factor = L12*9*dot_c(cross_c(GA,GB),matmul(Ms,cross_c(GC,GD)))
|
|
256
|
+
Dmat[ei+0,ej+0] = Factor*VAC
|
|
257
|
+
Dmat[ei+0,ej+10] = Factor*VAD
|
|
258
|
+
Dmat[ei+10,ej+0] = Factor*VBC
|
|
259
|
+
Dmat[ei+10,ej+10] = Factor*VBD
|
|
260
260
|
|
|
261
|
-
Fmat[ei+0,ej+0] =
|
|
262
|
-
Fmat[ei+0,ej+10] =
|
|
263
|
-
Fmat[ei+10,ej+0] =
|
|
264
|
-
Fmat[ei+10,ej+10] =
|
|
261
|
+
Fmat[ei+0,ej+0] = L12*(VABCD*erGD-VABCC*GE_MUL_erGF-VAACD*GE_MUL_erGC+VAACC*GA_MUL_erGF)
|
|
262
|
+
Fmat[ei+0,ej+10] = L12*(VABDD*erGD-VABCD*GE_MUL_erGF-VAADD*GE_MUL_erGC+VAACD*GA_MUL_erGF)
|
|
263
|
+
Fmat[ei+10,ej+0] = L12*(VBBCD*erGD-VBBCC*GE_MUL_erGF-VABCD*GE_MUL_erGC+VABCC*GA_MUL_erGF)
|
|
264
|
+
Fmat[ei+10,ej+10] = L12*(VBBDD*erGD-VBBCD*GE_MUL_erGF-VABDD*GE_MUL_erGC+VABCD*GA_MUL_erGF)
|
|
265
265
|
|
|
266
266
|
for ej in range(4):
|
|
267
267
|
ej1, ej2, fj = local_tri_map[:, ej]
|
|
@@ -292,29 +292,29 @@ def ned2_tet_stiff_mass(tet_vertices, edge_lengths, local_edge_map, local_tri_ma
|
|
|
292
292
|
Lab2 = Ds[ej1, ej2]
|
|
293
293
|
Lac2 = Ds[ej1, fj]
|
|
294
294
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
295
|
+
CROSS_AE = cross_c(GA,GB)
|
|
296
|
+
CROSS_DF = dot_c(CROSS_AE,matmul(Ms,cross_c(GC,GF)))
|
|
297
|
+
CROSS_CD = dot_c(CROSS_AE,matmul(Ms,cross_c(GD,GF)))
|
|
298
|
+
AE_MUL_CF = dot_c(CROSS_AE,matmul(Ms,cross_c(GC,GD)))
|
|
299
|
+
erGF = matmul(Mm,GF)
|
|
300
|
+
erGC = matmul(Mm,GC)
|
|
301
|
+
erGD = matmul(Mm,GD)
|
|
302
|
+
GE_MUL_erGF = dot_c(GA,erGF)
|
|
303
|
+
GE_MUL_erGC = dot_c(GA,erGC)
|
|
304
|
+
GA_MUL_erGF = dot_c(GB,erGF)
|
|
305
|
+
GA_MUL_erGC = dot_c(GB,erGC)
|
|
306
|
+
GE_MUL_erGD = dot_c(GA,erGD)
|
|
307
|
+
GA_MUL_erGD = dot_c(GB,erGD)
|
|
308
308
|
|
|
309
|
-
Dmat[ei+0,ej+6] = L1*Lac2*(-6*VAD*
|
|
310
|
-
Dmat[ei+0,ej+16] = L1*Lab2*(6*VAF*
|
|
311
|
-
Dmat[ei+10,ej+6] = L1*Lac2*(-6*VBD*
|
|
312
|
-
Dmat[ei+10,ej+16] = L1*Lab2*(6*VBF*
|
|
313
|
-
|
|
314
|
-
Fmat[ei+0,ej+6] = L1*Lac2*(VABCD*
|
|
315
|
-
Fmat[ei+0,ej+16] = L1*Lab2*(VABDF*
|
|
316
|
-
Fmat[ei+10,ej+6] = L1*Lac2*(VBBCD*
|
|
317
|
-
Fmat[ei+10,ej+16] = L1*Lab2*(VBBDF*
|
|
309
|
+
Dmat[ei+0,ej+6] = L1*Lac2*(-6*VAD*CROSS_DF-3*VAC*CROSS_CD-3*VAF*AE_MUL_CF)
|
|
310
|
+
Dmat[ei+0,ej+16] = L1*Lab2*(6*VAF*AE_MUL_CF+3*VAD*CROSS_DF-3*VAC*CROSS_CD)
|
|
311
|
+
Dmat[ei+10,ej+6] = L1*Lac2*(-6*VBD*CROSS_DF-3*VBC*CROSS_CD-3*VBF*AE_MUL_CF)
|
|
312
|
+
Dmat[ei+10,ej+16] = L1*Lab2*(6*VBF*AE_MUL_CF+3*VBD*CROSS_DF-3*VBC*CROSS_CD)
|
|
313
|
+
|
|
314
|
+
Fmat[ei+0,ej+6] = L1*Lac2*(VABCD*GE_MUL_erGF-VABDF*GE_MUL_erGC-VAACD*GA_MUL_erGF+VAADF*GA_MUL_erGC)
|
|
315
|
+
Fmat[ei+0,ej+16] = L1*Lab2*(VABDF*GE_MUL_erGC-VABCF*GE_MUL_erGD-VAADF*GA_MUL_erGC+VAACF*GA_MUL_erGD)
|
|
316
|
+
Fmat[ei+10,ej+6] = L1*Lac2*(VBBCD*GE_MUL_erGF-VBBDF*GE_MUL_erGC-VABCD*GA_MUL_erGF+VABDF*GA_MUL_erGC)
|
|
317
|
+
Fmat[ei+10,ej+16] = L1*Lab2*(VBBDF*GE_MUL_erGC-VBBCF*GE_MUL_erGD-VABDF*GA_MUL_erGC+VABCF*GA_MUL_erGD)
|
|
318
318
|
|
|
319
319
|
## Mirror the transpose part of the previous iteration as its symmetrical
|
|
320
320
|
|
|
@@ -335,6 +335,7 @@ def ned2_tet_stiff_mass(tet_vertices, edge_lengths, local_edge_map, local_tri_ma
|
|
|
335
335
|
GE = GLs[fi]
|
|
336
336
|
Lac1 = Ds[ei1, fi]
|
|
337
337
|
Lab1 = Ds[ei1, ei2]
|
|
338
|
+
|
|
338
339
|
for ej in range(4):
|
|
339
340
|
ej1, ej2, fj = local_tri_map[:, ej]
|
|
340
341
|
|
|
@@ -366,44 +367,44 @@ def ned2_tet_stiff_mass(tet_vertices, edge_lengths, local_edge_map, local_tri_ma
|
|
|
366
367
|
Lac2 = Ds[ej1, fj]
|
|
367
368
|
Lab2 = Ds[ej1, ej2]
|
|
368
369
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
Q1 = 2*VAD*
|
|
398
|
-
|
|
399
|
-
Dmat[ei+6,ej+6] = Lac1*Lac2*(4*VBD*
|
|
400
|
-
Dmat[ei+6,ej+16] = Lac1*Lab2*(-4*VBF*
|
|
401
|
-
Dmat[ei+16,ej+6] = Lab1*Lac2*(-4*VDE*
|
|
402
|
-
Dmat[ei+16,ej+16] = Lab1*Lab2*(4*VEF*
|
|
403
|
-
Fmat[ei+6,ej+6] = Lac1*Lac2*(VABCD*
|
|
404
|
-
Fmat[ei+6,ej+16] = Lac1*Lab2*(VABDF*
|
|
405
|
-
Fmat[ei+16,ej+6] = Lab1*Lac2*(VBCDE*
|
|
406
|
-
Fmat[ei+16,ej+16] = Lab1*Lab2*(VBDEF*
|
|
370
|
+
CROSS_AE = cross_c(GA,GE)
|
|
371
|
+
CROSS_BE = cross_c(GB,GE)
|
|
372
|
+
CROSS_AB = cross_c(GA,GB)
|
|
373
|
+
CROSS_CF = matmul(Ms,cross_c(GC,GF))
|
|
374
|
+
CROSS_DF = matmul(Ms,cross_c(GD,GF))
|
|
375
|
+
CROSS_CD = matmul(Ms,cross_c(GC,GD))
|
|
376
|
+
AE_MUL_CF = dot_c(CROSS_AE,CROSS_CF)
|
|
377
|
+
AE_MUL_DF = dot_c(CROSS_AE,CROSS_DF)
|
|
378
|
+
AE_MUL_CD = dot_c(CROSS_AE,CROSS_CD)
|
|
379
|
+
BE_MUL_CF = dot_c(CROSS_BE,CROSS_CF)
|
|
380
|
+
BE_MUL_DF = dot_c(CROSS_BE,CROSS_DF)
|
|
381
|
+
BE_MUL_CD = dot_c(CROSS_BE,CROSS_CD)
|
|
382
|
+
AB_MUL_CF = dot_c(CROSS_AB,CROSS_CF)
|
|
383
|
+
AB_MUL_DF = dot_c(CROSS_AB,CROSS_DF)
|
|
384
|
+
AB_MUL_CD = dot_c(CROSS_AB,CROSS_CD)
|
|
385
|
+
erGF = matmul(Mm,GF)
|
|
386
|
+
erGC = matmul(Mm,GC)
|
|
387
|
+
erGD = matmul(Mm,GD)
|
|
388
|
+
GE_MUL_erGF = dot_c(GE,erGF)
|
|
389
|
+
GE_MUL_erGC = dot_c(GE,erGC)
|
|
390
|
+
GA_MUL_erGF = dot_c(GA,erGF)
|
|
391
|
+
GA_MUL_erGC = dot_c(GA,erGC)
|
|
392
|
+
GE_MUL_erGD = dot_c(GE,erGD)
|
|
393
|
+
GA_MUL_erGD = dot_c(GA,erGD)
|
|
394
|
+
GB_MUL_erGF = dot_c(GB,erGF)
|
|
395
|
+
GB_MUL_erGC = dot_c(GB,erGC)
|
|
396
|
+
GB_MUL_erGD = dot_c(GB,erGD)
|
|
397
|
+
|
|
398
|
+
Q1 = 2*VAD*BE_MUL_CF+VAC*BE_MUL_DF+VAF*BE_MUL_CD
|
|
399
|
+
L12 = -2*VAF*BE_MUL_CD-VAD*BE_MUL_CF+VAC*BE_MUL_DF
|
|
400
|
+
Dmat[ei+6,ej+6] = Lac1*Lac2*(4*VBD*AE_MUL_CF+2*VBC*AE_MUL_DF+2*VBF*AE_MUL_CD+Q1+2*VDE*AB_MUL_CF+VCE*AB_MUL_DF+VEF*AB_MUL_CD)
|
|
401
|
+
Dmat[ei+6,ej+16] = Lac1*Lab2*(-4*VBF*AE_MUL_CD-2*VBD*AE_MUL_CF+2*VBC*AE_MUL_DF+L12-2*VEF*AB_MUL_CD-VDE*AB_MUL_CF+VCE*AB_MUL_DF)
|
|
402
|
+
Dmat[ei+16,ej+6] = Lab1*Lac2*(-4*VDE*AB_MUL_CF-2*VCE*AB_MUL_DF-2*VEF*AB_MUL_CD-2*VBD*AE_MUL_CF-VBC*AE_MUL_DF-VBF*AE_MUL_CD+Q1)
|
|
403
|
+
Dmat[ei+16,ej+16] = Lab1*Lab2*(4*VEF*AB_MUL_CD+2*VDE*AB_MUL_CF-2*VCE*AB_MUL_DF+2*VBF*AE_MUL_CD+VBD*AE_MUL_CF-VBC*AE_MUL_DF+L12)
|
|
404
|
+
Fmat[ei+6,ej+6] = Lac1*Lac2*(VABCD*GE_MUL_erGF-VABDF*GE_MUL_erGC-VBCDE*GA_MUL_erGF+VBDEF*GA_MUL_erGC)
|
|
405
|
+
Fmat[ei+6,ej+16] = Lac1*Lab2*(VABDF*GE_MUL_erGC-VABCF*GE_MUL_erGD-VBDEF*GA_MUL_erGC+VBCEF*GA_MUL_erGD)
|
|
406
|
+
Fmat[ei+16,ej+6] = Lab1*Lac2*(VBCDE*GA_MUL_erGF-VBDEF*GA_MUL_erGC-VACDE*GB_MUL_erGF+VADEF*GB_MUL_erGC)
|
|
407
|
+
Fmat[ei+16,ej+16] = Lab1*Lab2*(VBDEF*GA_MUL_erGC-VBCEF*GA_MUL_erGD-VADEF*GB_MUL_erGC+VACEF*GB_MUL_erGD)
|
|
407
408
|
|
|
408
409
|
Dmat = Dmat*KA
|
|
409
410
|
Fmat = Fmat*KB
|
|
@@ -19,7 +19,7 @@ from ...mesher import Mesher
|
|
|
19
19
|
from ...material import Material
|
|
20
20
|
from ...mesh3d import Mesh3D
|
|
21
21
|
from ...coord import Line
|
|
22
|
-
from ...geometry import GeoSurface
|
|
22
|
+
from ...geometry import GeoSurface, _GEOMANAGER
|
|
23
23
|
from ...elements.femdata import FEMBasis
|
|
24
24
|
from ...elements.nedelec2 import Nedelec2
|
|
25
25
|
from ...solver import DEFAULT_ROUTINE, SolveRoutine
|
|
@@ -61,6 +61,9 @@ def run_job_multi(job: SimJob) -> SimJob:
|
|
|
61
61
|
job.submit_solution(solution, report)
|
|
62
62
|
return job
|
|
63
63
|
|
|
64
|
+
def _init_worker():
|
|
65
|
+
nr = int(mp.current_process().name.split('-')[1])
|
|
66
|
+
DEFAULT_ROUTINE._configure_routine(proc_nr=nr)
|
|
64
67
|
|
|
65
68
|
def _dimstring(data: list[float] | np.ndarray) -> str:
|
|
66
69
|
"""A String formatter for dimensions in millimeters
|
|
@@ -137,11 +140,13 @@ class Microwave3D:
|
|
|
137
140
|
def reset_data(self):
|
|
138
141
|
self.data = MWData()
|
|
139
142
|
|
|
140
|
-
def reset(self):
|
|
141
|
-
|
|
143
|
+
def reset(self, _reset_bc: bool = True):
|
|
144
|
+
if _reset_bc:
|
|
145
|
+
self.bc.reset()
|
|
146
|
+
self.bc = MWBoundaryConditionSet(None)
|
|
142
147
|
self.basis: FEMBasis = None
|
|
143
|
-
self.bc = MWBoundaryConditionSet(None)
|
|
144
148
|
self.solveroutine.reset()
|
|
149
|
+
self.assembler.cached_matrices = None
|
|
145
150
|
|
|
146
151
|
def set_order(self, order: int) -> None:
|
|
147
152
|
"""Sets the order of the basis functions used. Currently only supports second order.
|
|
@@ -190,7 +195,6 @@ class Microwave3D:
|
|
|
190
195
|
logger.debug(f'Assinging PEC to {surf}')
|
|
191
196
|
self.bc.PEC(surf)
|
|
192
197
|
elif surf.material.cond.scalar(1e9) > self._settings.mw_2dbc_lim:
|
|
193
|
-
logger.debug(f'Assigning SurfaceImpedance to {surf}')
|
|
194
198
|
self.bc.SurfaceImpedance(surf, surf.material)
|
|
195
199
|
|
|
196
200
|
|
|
@@ -399,8 +403,10 @@ class Microwave3D:
|
|
|
399
403
|
raise ValueError('The field basis is not yet defined.')
|
|
400
404
|
|
|
401
405
|
logger.debug(' - Finding PEC TEM conductors')
|
|
402
|
-
pecs: list[PEC] = self.bc.get_conductors() # type: ignore
|
|
403
406
|
mesh = self.mesh
|
|
407
|
+
|
|
408
|
+
# Find all BC conductors
|
|
409
|
+
pecs: list[PEC] = self.bc.get_conductors() # type: ignore
|
|
404
410
|
|
|
405
411
|
# Process all PEC Boundary Conditions
|
|
406
412
|
pec_edges = []
|
|
@@ -416,19 +422,37 @@ class Microwave3D:
|
|
|
416
422
|
edge_ids = list(mesh.tri_to_edge[:,itri].flatten())
|
|
417
423
|
pec_edges.extend(edge_ids)
|
|
418
424
|
|
|
425
|
+
# All PEC edges
|
|
419
426
|
pec_edges = list(set(pec_edges))
|
|
420
427
|
|
|
428
|
+
# Port mesh data
|
|
421
429
|
tri_ids = mesh.get_triangles(port.tags)
|
|
422
430
|
edge_ids = list(mesh.tri_to_edge[:,tri_ids].flatten())
|
|
423
431
|
|
|
424
|
-
|
|
432
|
+
port_pec_edges = np.array([i for i in pec_edges if i in set(edge_ids)])
|
|
425
433
|
|
|
426
|
-
pec_islands = mesh.find_edge_groups(
|
|
434
|
+
pec_islands = mesh.find_edge_groups(port_pec_edges)
|
|
427
435
|
|
|
436
|
+
|
|
428
437
|
logger.debug(f' - Found {len(pec_islands)} PEC islands.')
|
|
429
438
|
|
|
430
439
|
if len(pec_islands) != 2:
|
|
431
|
-
|
|
440
|
+
pec_island_tags = {i: self.mesh._get_dimtags(edges=pec_edge_group) for i,pec_edge_group in enumerate(pec_islands)}
|
|
441
|
+
plus_term = None
|
|
442
|
+
min_term = None
|
|
443
|
+
|
|
444
|
+
for i, dimtags in pec_island_tags.items():
|
|
445
|
+
if not set(dimtags).isdisjoint(port.plus_terminal):
|
|
446
|
+
plus_term = i
|
|
447
|
+
|
|
448
|
+
if not set(dimtags).isdisjoint(port.minus_terminal):
|
|
449
|
+
min_term = i
|
|
450
|
+
|
|
451
|
+
if plus_term is None or min_term is None:
|
|
452
|
+
raise ValueError(f' - Found {len(pec_islands)} PEC islands without a terminal definition. Please use .set_terminals() to define which conductors are which polarity.')
|
|
453
|
+
logger.debug(f'Positive island = {pec_island_tags[plus_term]}')
|
|
454
|
+
logger.debug(f'Negative island = {pec_island_tags[min_term]}')
|
|
455
|
+
pec_islands = [pec_islands[plus_term], pec_islands[min_term]]
|
|
432
456
|
|
|
433
457
|
groups = []
|
|
434
458
|
for island in pec_islands:
|
|
@@ -601,6 +625,7 @@ class Microwave3D:
|
|
|
601
625
|
elif TEM:
|
|
602
626
|
G1, G2 = self._find_tem_conductors(port, sigtri=cond)
|
|
603
627
|
cs, dls = self._compute_integration_line(G1,G2)
|
|
628
|
+
logger.debug(f'Integrating portmode from {cs[:,0]} to {cs[:,-1]}')
|
|
604
629
|
mode.modetype = 'TEM'
|
|
605
630
|
Ex, Ey, Ez = portfE(cs[0,:], cs[1,:], cs[2,:])
|
|
606
631
|
voltage = np.sum(Ex*dls[0,:] + Ey*dls[1,:] + Ez*dls[2,:])
|
|
@@ -619,7 +644,7 @@ class Microwave3D:
|
|
|
619
644
|
|
|
620
645
|
def run_sweep(self,
|
|
621
646
|
parallel: bool = False,
|
|
622
|
-
|
|
647
|
+
n_workers: int = 2,
|
|
623
648
|
harddisc_threshold: int | None = None,
|
|
624
649
|
harddisc_path: str = 'EMergeSparse',
|
|
625
650
|
frequency_groups: int = -1,
|
|
@@ -627,7 +652,7 @@ class Microwave3D:
|
|
|
627
652
|
automatic_modal_analysis: bool = True) -> MWData:
|
|
628
653
|
"""Executes a frequency domain study
|
|
629
654
|
|
|
630
|
-
The study is distributed over "
|
|
655
|
+
The study is distributed over "n_workers" workers.
|
|
631
656
|
As optional parameter you may set a harddisc_threshold as integer. This determines the maximum
|
|
632
657
|
number of degrees of freedom before which the jobs will be cahced to the harddisk. The
|
|
633
658
|
path that will be used to cache the sparse matrices can be specified.
|
|
@@ -637,7 +662,7 @@ class Microwave3D:
|
|
|
637
662
|
frequency indices will be precomputed and then solved: [[1,2,3,4],[5,6,7,8],[9,10,11]]
|
|
638
663
|
|
|
639
664
|
Args:
|
|
640
|
-
|
|
665
|
+
n_workers (int, optional): The number of workers. Defaults to 2.
|
|
641
666
|
harddisc_threshold (int, optional): The number of DOF limit. Defaults to None.
|
|
642
667
|
harddisc_path (str, optional): The cached matrix path name. Defaults to 'EMergeSparse'.
|
|
643
668
|
frequency_groups (int, optional): The number of frequency points in a solve group. Defaults to -1.
|
|
@@ -687,7 +712,8 @@ class Microwave3D:
|
|
|
687
712
|
## DEFINE SOLVE FUNCTIONS
|
|
688
713
|
def get_routine():
|
|
689
714
|
if not hasattr(thread_local, "routine"):
|
|
690
|
-
|
|
715
|
+
worker_nr = int(threading.current_thread().name.split('_')[1])+1
|
|
716
|
+
thread_local.routine = self.solveroutine.duplicate()._configure_routine('MT', thread_nr=worker_nr)
|
|
691
717
|
return thread_local.routine
|
|
692
718
|
|
|
693
719
|
def run_job(job: SimJob):
|
|
@@ -750,7 +776,7 @@ class Microwave3D:
|
|
|
750
776
|
results.extend(group_results)
|
|
751
777
|
elif not multi_processing:
|
|
752
778
|
# MULTI THREADED
|
|
753
|
-
with ThreadPoolExecutor(max_workers=
|
|
779
|
+
with ThreadPoolExecutor(max_workers=n_workers, thread_name_prefix='WKR') as executor:
|
|
754
780
|
# ITERATE OVER FREQUENCIES
|
|
755
781
|
for i_group, fgroup in enumerate(freq_groups):
|
|
756
782
|
logger.info(f'Precomputing group {i_group}.')
|
|
@@ -771,7 +797,7 @@ class Microwave3D:
|
|
|
771
797
|
jobs.append(job)
|
|
772
798
|
matset.append(mats)
|
|
773
799
|
|
|
774
|
-
logger.info(f'Starting distributed solve of {len(jobs)} jobs with {
|
|
800
|
+
logger.info(f'Starting distributed solve of {len(jobs)} jobs with {n_workers} threads.')
|
|
775
801
|
group_results = list(executor.map(run_job, jobs))
|
|
776
802
|
results.extend(group_results)
|
|
777
803
|
executor.shutdown()
|
|
@@ -784,7 +810,7 @@ class Microwave3D:
|
|
|
784
810
|
"if __name__ == '__main__' guard in the top-level script."
|
|
785
811
|
)
|
|
786
812
|
# Start parallel pool
|
|
787
|
-
with mp.Pool(processes=
|
|
813
|
+
with mp.Pool(processes=n_workers, initializer=_init_worker) as pool:
|
|
788
814
|
for i_group, fgroup in enumerate(freq_groups):
|
|
789
815
|
logger.debug(f'Precomputing group {i_group}.')
|
|
790
816
|
jobs = []
|
|
@@ -810,7 +836,7 @@ class Microwave3D:
|
|
|
810
836
|
|
|
811
837
|
logger.info(
|
|
812
838
|
f'Starting distributed solve of {len(jobs)} jobs '
|
|
813
|
-
f'with {
|
|
839
|
+
f'with {n_workers} processes in parallel'
|
|
814
840
|
)
|
|
815
841
|
# Distribute taks
|
|
816
842
|
group_results = pool.map(run_job_multi, jobs)
|
|
@@ -832,6 +858,100 @@ class Microwave3D:
|
|
|
832
858
|
self._post_process(results, matset)
|
|
833
859
|
return self.data
|
|
834
860
|
|
|
861
|
+
def _run_adaptive_mesh(self,
|
|
862
|
+
iteration: int,
|
|
863
|
+
frequency: float,
|
|
864
|
+
automatic_modal_analysis: bool = True) -> MWData:
|
|
865
|
+
"""Executes a frequency domain study
|
|
866
|
+
|
|
867
|
+
The study is distributed over "n_workers" workers.
|
|
868
|
+
As optional parameter you may set a harddisc_threshold as integer. This determines the maximum
|
|
869
|
+
number of degrees of freedom before which the jobs will be cahced to the harddisk. The
|
|
870
|
+
path that will be used to cache the sparse matrices can be specified.
|
|
871
|
+
Additionally the term frequency_groups may be specified. This number will define in how
|
|
872
|
+
many groups the matrices will be pre-computed before they are send to workers. This can minimize
|
|
873
|
+
the total amound of RAM memory used. For example with 11 frequencies in gruops of 4, the following
|
|
874
|
+
frequency indices will be precomputed and then solved: [[1,2,3,4],[5,6,7,8],[9,10,11]]
|
|
875
|
+
|
|
876
|
+
Args:
|
|
877
|
+
iteration (int): The iteration number
|
|
878
|
+
frequency (float): The simulation frequency
|
|
879
|
+
|
|
880
|
+
Raises:
|
|
881
|
+
SimulationError: An error associated witha a problem during the simulation.
|
|
882
|
+
|
|
883
|
+
Returns:
|
|
884
|
+
MWSimData: The dataset.
|
|
885
|
+
"""
|
|
886
|
+
|
|
887
|
+
self._simstart = time.time()
|
|
888
|
+
if self.bc._initialized is False:
|
|
889
|
+
raise SimulationError('Cannot run a modal analysis because no boundary conditions have been assigned.')
|
|
890
|
+
|
|
891
|
+
self._initialize_field()
|
|
892
|
+
self._initialize_bc_data()
|
|
893
|
+
self._check_physics()
|
|
894
|
+
|
|
895
|
+
if self.basis is None:
|
|
896
|
+
raise SimulationError('Cannot proceed, the simulation basis class is undefined.')
|
|
897
|
+
|
|
898
|
+
materials = self.mesh._get_material_assignment(self.mesher.volumes)
|
|
899
|
+
|
|
900
|
+
### Does this move
|
|
901
|
+
logger.debug('Initializing single frequency settings.')
|
|
902
|
+
|
|
903
|
+
#### Port settings
|
|
904
|
+
all_ports = self.bc.oftype(PortBC)
|
|
905
|
+
|
|
906
|
+
##### FOR PORT SWEEP SET ALL ACTIVE TO FALSE. THIS SHOULD BE FIXED LATER
|
|
907
|
+
### COMPUTE WHICH TETS ARE CONNECTED TO PORT INDICES
|
|
908
|
+
|
|
909
|
+
for port in all_ports:
|
|
910
|
+
port.active=False
|
|
911
|
+
|
|
912
|
+
|
|
913
|
+
def run_job_single(job: SimJob):
|
|
914
|
+
for A, b, ids, reuse, aux in job.iter_Ab():
|
|
915
|
+
solution, report = self.solveroutine.solve(A, b, ids, reuse, id=job.id)
|
|
916
|
+
report.add(**aux)
|
|
917
|
+
job.submit_solution(solution, report)
|
|
918
|
+
return job
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
matset: list[tuple[np.ndarray, np.ndarray, np.ndarray]] = []
|
|
922
|
+
|
|
923
|
+
self._compute_modes(frequency)
|
|
924
|
+
|
|
925
|
+
logger.debug(f'Simulation frequency = {frequency/1e9:.3f} GHz')
|
|
926
|
+
|
|
927
|
+
if automatic_modal_analysis:
|
|
928
|
+
self._compute_modes(frequency)
|
|
929
|
+
|
|
930
|
+
job, mats = self.assembler.assemble_freq_matrix(self.basis, materials,
|
|
931
|
+
self.bc.boundary_conditions,
|
|
932
|
+
frequency,
|
|
933
|
+
cache_matrices=self.cache_matrices)
|
|
934
|
+
|
|
935
|
+
job.id = 0
|
|
936
|
+
|
|
937
|
+
logger.info('Starting solve')
|
|
938
|
+
job = run_job_single(job)
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
logger.info('Solving complete')
|
|
942
|
+
|
|
943
|
+
self.data.setreport(job.reports, freq=frequency, **self._params)
|
|
944
|
+
|
|
945
|
+
for variables, data in self.data.sim.iterate():
|
|
946
|
+
logger.trace(f'Sim variable: {variables}')
|
|
947
|
+
for item in data['report']:
|
|
948
|
+
item.logprint(logger.trace)
|
|
949
|
+
|
|
950
|
+
self.solveroutine.reset()
|
|
951
|
+
### Compute S-parameters and return
|
|
952
|
+
self._post_process([job,], [mats,])
|
|
953
|
+
return self.data
|
|
954
|
+
|
|
835
955
|
def eigenmode(self, search_frequency: float,
|
|
836
956
|
nmodes: int = 6,
|
|
837
957
|
k0_limit: float = 1,
|
|
@@ -865,10 +985,6 @@ class Microwave3D:
|
|
|
865
985
|
|
|
866
986
|
materials = self.mesh._get_material_assignment(self.mesher.volumes)
|
|
867
987
|
|
|
868
|
-
# er = self.mesh.retreive(lambda mat,x,y,z: mat.fer3d_mat(x,y,z), self.mesher.volumes)
|
|
869
|
-
# ur = self.mesh.retreive(lambda mat,x,y,z: mat.fur3d_mat(x,y,z), self.mesher.volumes)
|
|
870
|
-
# cond = self.mesh.retreive(lambda mat,x,y,z: mat.cond, self.mesher.volumes)[0,0,:]
|
|
871
|
-
|
|
872
988
|
### Does this move
|
|
873
989
|
logger.debug('Initializing frequency domain sweep.')
|
|
874
990
|
|
|
@@ -73,7 +73,6 @@ class MWBoundaryConditionSet(BoundaryConditionSet):
|
|
|
73
73
|
for bc in self.oftype(SurfaceImpedance):
|
|
74
74
|
if bc.sigma > 10.0:
|
|
75
75
|
bcs.append(bc)
|
|
76
|
-
|
|
77
76
|
return bcs
|
|
78
77
|
|
|
79
78
|
def get_type(self, bctype: Literal['PEC','ModalPort','LumpedPort','PMC','LumpedElement','RectangularWaveguide','Periodic','FloquetPort','SurfaceImpedance']) -> FaceSelection:
|
|
@@ -497,6 +496,8 @@ class ModalPort(PortBC):
|
|
|
497
496
|
self._first_k0: float | None = None
|
|
498
497
|
self._last_k0: float | None = None
|
|
499
498
|
|
|
499
|
+
self.plus_terminal: list[tuple[int, int]] = []
|
|
500
|
+
self.minus_terminal: list[tuple[int, int]] = []
|
|
500
501
|
|
|
501
502
|
if cs is None:
|
|
502
503
|
logger.info('Constructing coordinate system from normal port')
|
|
@@ -522,6 +523,26 @@ class ModalPort(PortBC):
|
|
|
522
523
|
*axes (tuple, np.ndarray, Axis): The alignment vectors.
|
|
523
524
|
"""
|
|
524
525
|
self.alignment_vectors = [_parse_axis(ax) for ax in axes]
|
|
526
|
+
|
|
527
|
+
def set_terminals(self, positive: Selection | GeoObject | None = None,
|
|
528
|
+
negative: Selection | GeoObject | None = None,
|
|
529
|
+
ground: Selection | GeoObject | None = None) -> None:
|
|
530
|
+
"""Define which objects/faces/selection should be assigned the positive terminal
|
|
531
|
+
and which one the negative terminal.
|
|
532
|
+
|
|
533
|
+
The terminal assignment will be used to find an integration line for the impedance calculation.
|
|
534
|
+
|
|
535
|
+
Note: Ground is currently unused.
|
|
536
|
+
|
|
537
|
+
Args:
|
|
538
|
+
positive (Selection | GeoObject | None, optional): The postive terminal. Defaults to None.
|
|
539
|
+
negative (Selection | GeoObject | None, optional): The negative terminal. Defaults to None.
|
|
540
|
+
ground (Selection | GeoObject | None, optional): _description_. Defaults to None.
|
|
541
|
+
"""
|
|
542
|
+
if positive is not None:
|
|
543
|
+
self.plus_terminal = positive.dimtags
|
|
544
|
+
if negative is not None:
|
|
545
|
+
self.minus_terminal = negative.dimtags
|
|
525
546
|
|
|
526
547
|
@property
|
|
527
548
|
def nmodes(self) -> int:
|
|
@@ -569,9 +590,10 @@ class ModalPort(PortBC):
|
|
|
569
590
|
Returns:
|
|
570
591
|
PortMode: The requested PortMode object
|
|
571
592
|
"""
|
|
593
|
+
options = self.modes[min(self.modes.keys(), key=lambda k: abs(k - k0))]
|
|
572
594
|
if i is None:
|
|
573
|
-
i = self.selected_mode
|
|
574
|
-
return
|
|
595
|
+
i = min(len(options)-1, self.selected_mode)
|
|
596
|
+
return options[i]
|
|
575
597
|
|
|
576
598
|
def global_field_function(self, k0: float = 0, which: Literal['E','H'] = 'E') -> Callable:
|
|
577
599
|
''' The field function used to compute the E-field.
|
|
@@ -805,7 +827,7 @@ class LumpedPort(PortBC):
|
|
|
805
827
|
port_number: int,
|
|
806
828
|
width: float | None = None,
|
|
807
829
|
height: float | None = None,
|
|
808
|
-
direction: Axis | None = None,
|
|
830
|
+
direction: Axis | tuple[float, float, float] | None = None,
|
|
809
831
|
power: float = 1,
|
|
810
832
|
Z0: float = 50):
|
|
811
833
|
"""Generates a lumped power boundary condition.
|
|
@@ -843,7 +865,7 @@ class LumpedPort(PortBC):
|
|
|
843
865
|
|
|
844
866
|
self.width: float = width
|
|
845
867
|
self.height: float = height # type: ignore
|
|
846
|
-
self.Vdirection: Axis = direction # type: ignore
|
|
868
|
+
self.Vdirection: Axis = _parse_axis(direction) # type: ignore
|
|
847
869
|
self.type = 'TEM'
|
|
848
870
|
|
|
849
871
|
# logger.info('Constructing coordinate system from normal port')
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
from ...simulation_data import BaseDataset, DataContainer
|
|
20
20
|
from ...elements.femdata import FEMBasis
|
|
21
|
-
from dataclasses import dataclass
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
22
|
import numpy as np
|
|
23
23
|
from typing import Literal
|
|
24
24
|
from loguru import logger
|
|
@@ -372,6 +372,7 @@ class EHField:
|
|
|
372
372
|
freq: float
|
|
373
373
|
er: np.ndarray
|
|
374
374
|
ur: np.ndarray
|
|
375
|
+
aux: dict[str, np.ndarray] = field(default_factory=dict)
|
|
375
376
|
|
|
376
377
|
@property
|
|
377
378
|
def k0(self) -> float:
|
|
@@ -537,7 +538,7 @@ class EHField:
|
|
|
537
538
|
|
|
538
539
|
return self.x, self.y, self.z, Fx, Fy, Fz
|
|
539
540
|
|
|
540
|
-
def scalar(self, field: Literal['Ex','Ey','Ez','Hx','Hy','Hz','normE','normH'], metric: Literal['abs','real','imag','complex'] = 'real') -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
|
541
|
+
def scalar(self, field: Literal['Ex','Ey','Ez','Hx','Hy','Hz','normE','normH'] | str, metric: Literal['abs','real','imag','complex'] = 'real') -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
|
541
542
|
"""Returns the data X, Y, Z, Field based on the interpolation
|
|
542
543
|
|
|
543
544
|
For animations, make sure to select the complex metric.
|
|
@@ -549,7 +550,11 @@ class EHField:
|
|
|
549
550
|
Returns:
|
|
550
551
|
(X,Y,Z,Field): The coordinates plus field scalar
|
|
551
552
|
"""
|
|
552
|
-
|
|
553
|
+
if field in self.aux:
|
|
554
|
+
field_arry = self.aux[field]
|
|
555
|
+
else:
|
|
556
|
+
field_arry = getattr(self, field)
|
|
557
|
+
|
|
553
558
|
if metric=='abs':
|
|
554
559
|
field = np.abs(field_arry)
|
|
555
560
|
elif metric=='real':
|
|
@@ -666,6 +671,10 @@ class MWField:
|
|
|
666
671
|
self.excitation = {key: 0.0 for key in self._fields.keys()}
|
|
667
672
|
self.excitation[self.port_modes[0].port_number] = 1.0 + 0j
|
|
668
673
|
|
|
674
|
+
def excite_port(self, number: int) -> None:
|
|
675
|
+
self.excitation = {key: 0.0 for key in self._fields.keys()}
|
|
676
|
+
self.excitation[self.port_modes[number].port_number] = 1.0 + 0j
|
|
677
|
+
|
|
669
678
|
@property
|
|
670
679
|
def EH(self) -> tuple[np.ndarray, np.ndarray]:
|
|
671
680
|
''' Return the electric and magnetic field as a tuple of numpy arrays '''
|
|
@@ -726,12 +735,29 @@ class MWField:
|
|
|
726
735
|
self.Hx = Hx.reshape(shp)
|
|
727
736
|
self.Hy = Hy.reshape(shp)
|
|
728
737
|
self.Hz = Hz.reshape(shp)
|
|
729
|
-
|
|
738
|
+
|
|
730
739
|
self._x = xs
|
|
731
740
|
self._y = ys
|
|
732
741
|
self._z = zs
|
|
733
|
-
|
|
734
|
-
|
|
742
|
+
field = EHField(xs, ys, zs, self.Ex, self.Ey, self.Ez, self.Hx, self.Hy, self.Hz, self.freq, self.er, self.ur)
|
|
743
|
+
|
|
744
|
+
return field
|
|
745
|
+
|
|
746
|
+
def _solution_quality(self) -> tuple[np.ndarray, np.ndarray]:
|
|
747
|
+
from .adaptive_mesh import compute_error_estimate
|
|
748
|
+
|
|
749
|
+
error_tet, max_elem_size = compute_error_estimate(self)
|
|
750
|
+
return error_tet, max_elem_size
|
|
751
|
+
|
|
752
|
+
def boundary(self,
|
|
753
|
+
selection: FaceSelection) -> EHField:
|
|
754
|
+
nodes = self.mesh.nodes
|
|
755
|
+
x = nodes[0,:]
|
|
756
|
+
y = nodes[1,:]
|
|
757
|
+
z = nodes[2,:]
|
|
758
|
+
field = self.interpolate(x, y, z, False)
|
|
759
|
+
return field
|
|
760
|
+
|
|
735
761
|
def cutplane(self,
|
|
736
762
|
ds: float,
|
|
737
763
|
x: float | None = None,
|