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.

@@ -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
- BB1 = matmul(Mm,GC)
248
- BC1 = matmul(Mm,GD)
249
- BD1 = dot_c(GA,BB1)
250
- BE1 = dot_c(GA,BC1)
251
- BF1 = dot_c(GB,BB1)
252
- BG1 = dot_c(GB,BC1)
253
-
254
- Q2 = L1*L2
255
- Q = Q2*9*dot_c(cross_c(GA,GB),matmul(Ms,cross_c(GC,GD)))
256
- Dmat[ei+0,ej+0] = Q*VAC
257
- Dmat[ei+0,ej+10] = Q*VAD
258
- Dmat[ei+10,ej+0] = Q*VBC
259
- Dmat[ei+10,ej+10] = Q*VBD
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] = Q2*(VABCD*BD1-VABCC*BE1-VAACD*BF1+VAACC*BG1)
262
- Fmat[ei+0,ej+10] = Q2*(VABDD*BD1-VABCD*BE1-VAADD*BF1+VAACD*BG1)
263
- Fmat[ei+10,ej+0] = Q2*(VBBCD*BD1-VBBCC*BE1-VABCD*BF1+VABCC*BG1)
264
- Fmat[ei+10,ej+10] = Q2*(VBBDD*BD1-VBBCD*BE1-VABDD*BF1+VABCD*BG1)
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
- AB1 = cross_c(GA,GB)
296
- AI1 = dot_c(AB1,matmul(Ms,cross_c(GC,GF)))
297
- AJ1 = dot_c(AB1,matmul(Ms,cross_c(GD,GF)))
298
- AK1 = dot_c(AB1,matmul(Ms,cross_c(GC,GD)))
299
- BB1 = matmul(Mm,GF)
300
- BC1 = matmul(Mm,GC)
301
- BD1 = matmul(Mm,GD)
302
- BE1 = dot_c(GA,BB1)
303
- BF1 = dot_c(GA,BC1)
304
- BG1 = dot_c(GB,BB1)
305
- BH1 = dot_c(GB,BC1)
306
- BI1 = dot_c(GA,BD1)
307
- BJ1 = dot_c(GB,BD1)
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*AI1-3*VAC*AJ1-3*VAF*AK1)
310
- Dmat[ei+0,ej+16] = L1*Lab2*(6*VAF*AK1+3*VAD*AI1-3*VAC*AJ1)
311
- Dmat[ei+10,ej+6] = L1*Lac2*(-6*VBD*AI1-3*VBC*AJ1-3*VBF*AK1)
312
- Dmat[ei+10,ej+16] = L1*Lab2*(6*VBF*AK1+3*VBD*AI1-3*VBC*AJ1)
313
-
314
- Fmat[ei+0,ej+6] = L1*Lac2*(VABCD*BE1-VABDF*BF1-VAACD*BG1+VAADF*BH1)
315
- Fmat[ei+0,ej+16] = L1*Lab2*(VABDF*BF1-VABCF*BI1-VAADF*BH1+VAACF*BJ1)
316
- Fmat[ei+10,ej+6] = L1*Lac2*(VBBCD*BE1-VBBDF*BF1-VABCD*BG1+VABDF*BH1)
317
- Fmat[ei+10,ej+16] = L1*Lab2*(VBBDF*BF1-VBBCF*BI1-VABDF*BH1+VABCF*BJ1)
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
- AB1 = cross_c(GA,GE)
370
- AF1 = cross_c(GB,GE)
371
- AG1 = cross_c(GA,GB)
372
- AH1 = matmul(Ms,cross_c(GC,GF))
373
- AI1 = matmul(Ms,cross_c(GD,GF))
374
- AJ1 = matmul(Ms,cross_c(GC,GD))
375
- AK1 = dot_c(AB1,AH1)
376
- AL1 = dot_c(AB1,AI1)
377
- AM1 = dot_c(AB1,AJ1)
378
- AN1 = dot_c(AF1,AH1)
379
- AO1 = dot_c(AF1,AI1)
380
- AP1 = dot_c(AF1,AJ1)
381
- AQ1 = dot_c(AG1,AH1)
382
- AR1 = dot_c(AG1,AI1)
383
- AS1 = dot_c(AG1,AJ1)
384
- BB1 = matmul(Mm,GF)
385
- BC1 = matmul(Mm,GC)
386
- BD1 = matmul(Mm,GD)
387
- BE1 = dot_c(GE,BB1)
388
- BF1 = dot_c(GE,BC1)
389
- BG1 = dot_c(GA,BB1)
390
- BH1 = dot_c(GA,BC1)
391
- BI1 = dot_c(GE,BD1)
392
- BJ1 = dot_c(GA,BD1)
393
- BK1 = dot_c(GB,BB1)
394
- BL1 = dot_c(GB,BC1)
395
- BM1 = dot_c(GB,BD1)
396
-
397
- Q1 = 2*VAD*AN1+VAC*AO1+VAF*AP1
398
- Q2 = -2*VAF*AP1-VAD*AN1+VAC*AO1
399
- Dmat[ei+6,ej+6] = Lac1*Lac2*(4*VBD*AK1+2*VBC*AL1+2*VBF*AM1+Q1+2*VDE*AQ1+VCE*AR1+VEF*AS1)
400
- Dmat[ei+6,ej+16] = Lac1*Lab2*(-4*VBF*AM1-2*VBD*AK1+2*VBC*AL1+Q2-2*VEF*AS1-VDE*AQ1+VCE*AR1)
401
- Dmat[ei+16,ej+6] = Lab1*Lac2*(-4*VDE*AQ1-2*VCE*AR1-2*VEF*AS1-2*VBD*AK1-VBC*AL1-VBF*AM1+Q1)
402
- Dmat[ei+16,ej+16] = Lab1*Lab2*(4*VEF*AS1+2*VDE*AQ1-2*VCE*AR1+2*VBF*AM1+VBD*AK1-VBC*AL1+Q2)
403
- Fmat[ei+6,ej+6] = Lac1*Lac2*(VABCD*BE1-VABDF*BF1-VBCDE*BG1+VBDEF*BH1)
404
- Fmat[ei+6,ej+16] = Lac1*Lab2*(VABDF*BF1-VABCF*BI1-VBDEF*BH1+VBCEF*BJ1)
405
- Fmat[ei+16,ej+6] = Lab1*Lac2*(VBCDE*BG1-VBDEF*BH1-VACDE*BK1+VADEF*BL1)
406
- Fmat[ei+16,ej+16] = Lab1*Lab2*(VBDEF*BH1-VBCEF*BJ1-VADEF*BL1+VACEF*BM1)
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
- self.bc.reset()
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
- pec_port = np.array([i for i in pec_edges if i in set(edge_ids)])
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(pec_port)
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
- raise ValueError(f' - Found {len(pec_islands)} PEC islands. Expected 2.')
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
- njobs: int = 2,
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 "njobs" workers.
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
- njobs (int, optional): The number of jobs. Defaults to 2.
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
- thread_local.routine = self.solveroutine.duplicate()._configure_routine('MT')
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=njobs) as executor:
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 {njobs} threads.')
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=njobs) as pool:
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 {njobs} processes in parallel'
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 self.modes[min(self.modes.keys(), key=lambda k: abs(k - k0))][i]
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
- field_arry = getattr(self, field)
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
- return EHField(xs, ys, zs, self.Ex, self.Ey, self.Ez, self.Hx, self.Hy, self.Hz, self.freq, self.er, self.ur)
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,