emerge 0.5.1__py3-none-any.whl → 0.5.3__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 (54) hide show
  1. emerge/_emerge/bc.py +14 -20
  2. emerge/_emerge/const.py +5 -0
  3. emerge/_emerge/cs.py +2 -2
  4. emerge/_emerge/elements/femdata.py +14 -14
  5. emerge/_emerge/elements/index_interp.py +1 -1
  6. emerge/_emerge/elements/ned2_interp.py +1 -1
  7. emerge/_emerge/elements/nedelec2.py +4 -4
  8. emerge/_emerge/elements/nedleg2.py +10 -10
  9. emerge/_emerge/geo/horn.py +1 -1
  10. emerge/_emerge/geo/modeler.py +18 -19
  11. emerge/_emerge/geo/operations.py +13 -10
  12. emerge/_emerge/geo/pcb.py +180 -82
  13. emerge/_emerge/geo/pcb_tools/calculator.py +2 -2
  14. emerge/_emerge/geo/pcb_tools/macro.py +14 -13
  15. emerge/_emerge/geo/pmlbox.py +1 -1
  16. emerge/_emerge/geometry.py +47 -33
  17. emerge/_emerge/logsettings.py +15 -16
  18. emerge/_emerge/material.py +15 -11
  19. emerge/_emerge/mesh3d.py +81 -59
  20. emerge/_emerge/mesher.py +26 -21
  21. emerge/_emerge/mth/integrals.py +1 -1
  22. emerge/_emerge/mth/pairing.py +2 -2
  23. emerge/_emerge/periodic.py +34 -31
  24. emerge/_emerge/physics/microwave/adaptive_freq.py +15 -16
  25. emerge/_emerge/physics/microwave/assembly/assembler.py +120 -93
  26. emerge/_emerge/physics/microwave/assembly/curlcurl.py +1 -8
  27. emerge/_emerge/physics/microwave/assembly/generalized_eigen.py +43 -8
  28. emerge/_emerge/physics/microwave/assembly/robinbc.py +5 -5
  29. emerge/_emerge/physics/microwave/microwave_3d.py +71 -44
  30. emerge/_emerge/physics/microwave/microwave_bc.py +206 -117
  31. emerge/_emerge/physics/microwave/microwave_data.py +36 -38
  32. emerge/_emerge/physics/microwave/sc.py +26 -26
  33. emerge/_emerge/physics/microwave/simjob.py +20 -15
  34. emerge/_emerge/physics/microwave/sparam.py +12 -12
  35. emerge/_emerge/physics/microwave/touchstone.py +1 -1
  36. emerge/_emerge/plot/display.py +12 -6
  37. emerge/_emerge/plot/pyvista/display.py +44 -39
  38. emerge/_emerge/plot/pyvista/display_settings.py +1 -1
  39. emerge/_emerge/plot/simple_plots.py +15 -15
  40. emerge/_emerge/selection.py +35 -39
  41. emerge/_emerge/simmodel.py +41 -47
  42. emerge/_emerge/simulation_data.py +24 -15
  43. emerge/_emerge/solve_interfaces/cudss_interface.py +238 -0
  44. emerge/_emerge/solve_interfaces/pardiso_interface.py +24 -18
  45. emerge/_emerge/solver.py +314 -136
  46. emerge/cli.py +1 -1
  47. emerge/lib.py +245 -248
  48. {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/METADATA +5 -1
  49. emerge-0.5.3.dist-info/RECORD +83 -0
  50. emerge/_emerge/plot/grapher.py +0 -93
  51. emerge-0.5.1.dist-info/RECORD +0 -82
  52. {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/WHEEL +0 -0
  53. {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/entry_points.txt +0 -0
  54. {emerge-0.5.1.dist-info → emerge-0.5.3.dist-info}/licenses/LICENSE +0 -0
@@ -20,24 +20,26 @@ from ...material import Material
20
20
  from ...mesh3d import Mesh3D
21
21
  from ...coord import Line
22
22
  from ...elements.femdata import FEMBasis
23
+ from ...elements.nedelec2 import Nedelec2
23
24
  from ...solver import DEFAULT_ROUTINE, SolveRoutine
24
25
  from ...system import called_from_main_function
25
26
  from ...selection import FaceSelection
26
- from scipy.sparse.linalg import inv as sparse_inverse
27
27
  from .microwave_bc import MWBoundaryConditionSet, PEC, ModalPort, LumpedPort, PortBC
28
28
  from .microwave_data import MWData
29
29
  from .assembly.assembler import Assembler
30
30
  from .port_functions import compute_avg_power_flux
31
31
  from .simjob import SimJob
32
+
32
33
  from concurrent.futures import ThreadPoolExecutor
33
- import numpy as np
34
34
  from loguru import logger
35
35
  from typing import Callable, Literal
36
- import time
37
- import threading
38
36
  import multiprocessing as mp
39
37
  from cmath import sqrt as csqrt
40
38
 
39
+ import numpy as np
40
+ import threading
41
+ import time
42
+
41
43
  class SimulationError(Exception):
42
44
  pass
43
45
 
@@ -50,9 +52,10 @@ def run_job_multi(job: SimJob) -> SimJob:
50
52
  Returns:
51
53
  SimJob: The solved SimJob
52
54
  """
53
- routine = DEFAULT_ROUTINE.duplicate().configure('MP')
54
- for A, b, ids, reuse in job.iter_Ab():
55
+ routine = DEFAULT_ROUTINE.duplicate()._configure_routine('MP')
56
+ for A, b, ids, reuse, aux in job.iter_Ab():
55
57
  solution, report = routine.solve(A, b, ids, reuse, id=job.id)
58
+ report.add(**aux)
56
59
  job.submit_solution(solution, report)
57
60
  return job
58
61
 
@@ -110,13 +113,12 @@ class Microwave3D:
110
113
  self.resolution: float = 1
111
114
 
112
115
  self.mesher: Mesher = mesher
113
- self.mesh: Mesh3D = None
116
+ self.mesh: Mesh3D = Mesh3D(self.mesher)
114
117
 
115
118
  self.assembler: Assembler = Assembler()
116
119
  self.bc: MWBoundaryConditionSet = MWBoundaryConditionSet(None)
117
- self.basis: FEMBasis = None
120
+ self.basis: Nedelec2 | None = None
118
121
  self.solveroutine: SolveRoutine = DEFAULT_ROUTINE
119
- self.set_order(order)
120
122
  self.cache_matrices: bool = True
121
123
 
122
124
  ## States
@@ -125,8 +127,10 @@ class Microwave3D:
125
127
 
126
128
  ## Data
127
129
  self._params: dict[str, float] = dict()
128
- self._simstart: int = 0
129
- self._simend: int = 0
130
+ self._simstart: float = 0.0
131
+ self._simend: float = 0.0
132
+
133
+ self.set_order(order)
130
134
 
131
135
  def reset_data(self):
132
136
  self.data = MWData()
@@ -167,7 +171,7 @@ class Microwave3D:
167
171
  Returns:
168
172
  list[PortBC]: A list of all port boundary conditions
169
173
  """
170
- return sorted(self.bc.oftype(PortBC), key=lambda x: x.number)
174
+ return sorted(self.bc.oftype(PortBC), key=lambda x: x.number) # type: ignore
171
175
 
172
176
 
173
177
  def _initialize_bcs(self) -> None:
@@ -190,7 +194,7 @@ class Microwave3D:
190
194
  Args:
191
195
  frequency (float | list[float] | np.ndarray): The frequency points.
192
196
  """
193
- logger.info(f'Setting frequency as {frequency/1e6}MHz.')
197
+ logger.info(f'Setting frequency as {frequency}Hz.')
194
198
  if isinstance(frequency, (tuple, list, np.ndarray)):
195
199
  self.frequencies = list(frequency)
196
200
  else:
@@ -307,7 +311,7 @@ class Microwave3D:
307
311
  port.vintline = Line.from_points(start, end, 21)
308
312
 
309
313
  logger.info(f'Ending node = {_dimstring(end)}')
310
- port.voltage_integration_points = (start, end)
314
+
311
315
  port.v_integration = True
312
316
 
313
317
  def _compute_integration_line(self, group1: list[int], group2: list[int]) -> tuple[np.ndarray, np.ndarray]:
@@ -347,9 +351,11 @@ class Microwave3D:
347
351
  list[int]: A list of node integers of island 1.
348
352
  list[int]: A list of node integers of island 2.
349
353
  '''
354
+ if self.basis is None:
355
+ raise ValueError('The field basis is not yet defined.')
350
356
 
351
357
  logger.debug('Finding PEC TEM conductors')
352
- pecs: list[PEC] = self.bc.oftype(PEC)
358
+ pecs: list[PEC] = self.bc.get_conductors() # type: ignore
353
359
  mesh = self.mesh
354
360
 
355
361
  # Process all PEC Boundary Conditions
@@ -366,16 +372,15 @@ class Microwave3D:
366
372
  edge_ids = list(mesh.tri_to_edge[:,itri].flatten())
367
373
  pec_edges.extend(edge_ids)
368
374
 
369
- pec_edges = set(pec_edges)
375
+ pec_edges = list(set(pec_edges))
370
376
 
371
377
  tri_ids = mesh.get_triangles(port.tags)
372
378
  edge_ids = list(mesh.tri_to_edge[:,tri_ids].flatten())
373
379
 
374
- pec_port = np.array([i for i in list(pec_edges) if i in set(edge_ids)])
380
+ pec_port = np.array([i for i in pec_edges if i in set(edge_ids)])
375
381
 
376
382
  pec_islands = mesh.find_edge_groups(pec_port)
377
383
 
378
- self.basis._pec_islands = pec_islands
379
384
  logger.debug(f'Found {len(pec_islands)} PEC islands.')
380
385
 
381
386
  if len(pec_islands) != 2:
@@ -415,7 +420,7 @@ class Microwave3D:
415
420
  TEM: bool = False,
416
421
  target_kz = None,
417
422
  target_neff = None,
418
- freq: float = None) -> None:
423
+ freq: float | None = None) -> None:
419
424
  ''' Execute a modal analysis on a given ModalPort boundary condition.
420
425
 
421
426
  Parameters:
@@ -442,6 +447,9 @@ class Microwave3D:
442
447
  self._initialize_field()
443
448
  self._initialize_bc_data()
444
449
 
450
+ if self.basis is None:
451
+ raise SimulationError('Cannot proceed, the current basis class is undefined.')
452
+
445
453
  logger.debug('Retreiving material properties.')
446
454
  ertet = self.mesh.retreive(lambda mat,x,y,z: mat.fer3d_mat(x,y,z), self.mesher.volumes)
447
455
  urtet = self.mesh.retreive(lambda mat,x,y,z: mat.fur3d_mat(x,y,z), self.mesher.volumes)
@@ -467,13 +475,10 @@ class Microwave3D:
467
475
  if freq is None:
468
476
  freq = self.frequencies[0]
469
477
 
470
-
471
478
  k0 = 2*np.pi*freq/299792458
472
479
  kmax = k0*np.sqrt(ermax.real*urmax.real)
473
-
474
- logger.info('Assembling BMA Matrices')
475
480
 
476
- Amatrix, Bmatrix, solve_ids, nlf = self.assembler.assemble_bma_matrices(self.basis, er, ur, cond, k0, port, self.bc.boundary_conditions)
481
+ Amatrix, Bmatrix, solve_ids, nlf = self.assembler.assemble_bma_matrices(self.basis, er, ur, cond, k0, port, self.bc)
477
482
 
478
483
  logger.debug(f'Total of {Amatrix.shape[0]} Degrees of freedom.')
479
484
  logger.debug(f'Applied frequency: {freq/1e9:.2f}GHz')
@@ -510,7 +515,7 @@ class Microwave3D:
510
515
  Emode = Emode * np.exp(-1j*np.angle(np.max(Emode)))
511
516
 
512
517
  beta = min(k0*np.sqrt(ermax*urmax), np.emath.sqrt(-eigen_values[i]))
513
- #beta = np.emath.sqrt(eigen_values[i])
518
+
514
519
  residuals = -1
515
520
 
516
521
  portfE = nlf.interpolate_Ef(Emode)
@@ -557,7 +562,7 @@ class Microwave3D:
557
562
  def frequency_domain(self,
558
563
  parallel: bool = False,
559
564
  njobs: int = 2,
560
- harddisc_threshold: int = None,
565
+ harddisc_threshold: int | None = None,
561
566
  harddisc_path: str = 'EMergeSparse',
562
567
  frequency_groups: int = -1,
563
568
  multi_processing: bool = False,
@@ -595,6 +600,9 @@ class Microwave3D:
595
600
  self._initialize_field()
596
601
  self._initialize_bc_data()
597
602
 
603
+ if self.basis is None:
604
+ raise SimulationError('Cannot proceed, the simulation basis class is undefined.')
605
+
598
606
  er = self.mesh.retreive(lambda mat,x,y,z: mat.fer3d_mat(x,y,z), self.mesher.volumes)
599
607
  ur = self.mesh.retreive(lambda mat,x,y,z: mat.fur3d_mat(x,y,z), self.mesher.volumes)
600
608
  cond = self.mesh.retreive(lambda mat,x,y,z: mat.cond, self.mesher.volumes)[0,0,:]
@@ -620,19 +628,21 @@ class Microwave3D:
620
628
  ## DEFINE SOLVE FUNCTIONS
621
629
  def get_routine():
622
630
  if not hasattr(thread_local, "routine"):
623
- thread_local.routine = self.solveroutine.duplicate().configure('MT')
631
+ thread_local.routine = self.solveroutine.duplicate()._configure_routine('MT')
624
632
  return thread_local.routine
625
633
 
626
634
  def run_job(job: SimJob):
627
635
  routine = get_routine()
628
- for A, b, ids, reuse in job.iter_Ab():
636
+ for A, b, ids, reuse, aux in job.iter_Ab():
629
637
  solution, report = routine.solve(A, b, ids, reuse, id=job.id)
638
+ report.add(**aux)
630
639
  job.submit_solution(solution, report)
631
640
  return job
632
641
 
633
642
  def run_job_single(job: SimJob):
634
- for A, b, ids, reuse in job.iter_Ab():
643
+ for A, b, ids, reuse, aux in job.iter_Ab():
635
644
  solution, report = self.solveroutine.solve(A, b, ids, reuse, id=job.id)
645
+ report.add(**aux)
636
646
  job.submit_solution(solution, report)
637
647
  return job
638
648
 
@@ -745,6 +755,15 @@ class Microwave3D:
745
755
  thread_local.__dict__.clear()
746
756
  logger.info('Solving complete')
747
757
 
758
+ for freq, job in zip(self.frequencies, results):
759
+ self.data.setreport(job.reports, freq=freq, **self._params)
760
+
761
+ for variables, data in self.data.sim.iterate():
762
+ logger.trace(f'Sim variable: {variables}')
763
+ for item in data['report']:
764
+ item.pretty_print(logger.trace)
765
+
766
+ self.solveroutine.reset()
748
767
  ### Compute S-parameters and return
749
768
  self._post_process(results, er, ur, cond)
750
769
  return self.data
@@ -777,6 +796,9 @@ class Microwave3D:
777
796
  self._initialize_field()
778
797
  self._initialize_bc_data()
779
798
 
799
+ if self.basis is None:
800
+ raise SimulationError('Cannot proceed. The simulation basis class is undefined.')
801
+
780
802
  er = self.mesh.retreive(lambda mat,x,y,z: mat.fer3d_mat(x,y,z), self.mesher.volumes)
781
803
  ur = self.mesh.retreive(lambda mat,x,y,z: mat.fur3d_mat(x,y,z), self.mesher.volumes)
782
804
  cond = self.mesh.retreive(lambda mat,x,y,z: mat.cond, self.mesher.volumes)[0,0,:]
@@ -839,6 +861,8 @@ class Microwave3D:
839
861
  ur (np.ndarray): The domain μᵣ
840
862
  cond (np.ndarray): The domain conductivity
841
863
  """
864
+ if self.basis is None:
865
+ raise SimulationError('Cannot post-process. Simulation basis function is undefined.')
842
866
  mesh = self.mesh
843
867
  all_ports = self.bc.oftype(PortBC)
844
868
  port_numbers = [port.port_number for port in all_ports]
@@ -863,15 +887,13 @@ class Microwave3D:
863
887
  scalardata = self.data.scalar.new(freq=freq, **self._params)
864
888
  scalardata.k0 = k0
865
889
  scalardata.freq = freq
866
- scalardata.init_sp(port_numbers)
890
+ scalardata.init_sp(port_numbers) # type: ignore
867
891
 
868
892
  fielddata = self.data.field.new(freq=freq, **self._params)
869
893
  fielddata.freq = freq
870
894
  fielddata._der = np.squeeze(er[0,0,:])
871
895
  fielddata._dur = np.squeeze(ur[0,0,:])
872
896
 
873
- self.data.setreport(job.reports, freq=freq, **self._params)
874
-
875
897
  logger.info(f'Post Processing simulation frequency = {freq/1e9:.3f} GHz')
876
898
 
877
899
  # Recording port information
@@ -881,7 +903,7 @@ class Microwave3D:
881
903
  k0 = k0,
882
904
  beta = active_port.get_beta(k0),
883
905
  Z0 = active_port.portZ0(k0),
884
- Pout= active_port.power)
906
+ Pout = active_port.power)
885
907
  scalardata.add_port_properties(active_port.port_number,
886
908
  mode_number=active_port.mode_number,
887
909
  k0 = k0,
@@ -899,23 +921,21 @@ class Microwave3D:
899
921
  # Compute the S-parameters
900
922
  # Define the field interpolation function
901
923
  fieldf = self.basis.interpolate_Ef(solution, tetids=all_port_tets)
902
- Pout = 0
924
+ Pout = 0.0 + 0j
903
925
 
904
926
  # Active port power
905
- logger.debug('Active ports:')
906
927
  tris = mesh.get_triangles(active_port.tags)
907
928
  tri_vertices = mesh.tris[:,tris]
908
929
  pfield, pmode = self._compute_s_data(active_port, fieldf, tri_vertices, k0, ertri[:,:,tris], urtri[:,:,tris])
909
- logger.debug(f' Field Amplitude = {np.abs(pfield):.3f}, Excitation = {np.abs(pmode):.2f}')
930
+ logger.debug(f'[{active_port.port_number}] Active port amplitude = {np.abs(pfield):.3f} (Excitation = {np.abs(pmode):.2f})')
910
931
  Pout = pmode
911
932
 
912
933
  #Passive ports
913
- logger.debug('Passive ports:')
914
934
  for bc in all_ports:
915
935
  tris = mesh.get_triangles(bc.tags)
916
936
  tri_vertices = mesh.tris[:,tris]
917
937
  pfield, pmode = self._compute_s_data(bc, fieldf,tri_vertices, k0, ertri[:,:,tris], urtri[:,:,tris])
918
- logger.debug(f' Field amplitude = {np.abs(pfield):.3f}, Excitation= {np.abs(pmode):.2f}')
938
+ logger.debug(f'[{bc.port_number}] Passive amplitude = {np.abs(pfield):.3f}')
919
939
  scalardata.write_S(bc.port_number, active_port.port_number, pfield/Pout)
920
940
  active_port.active=False
921
941
 
@@ -947,19 +967,26 @@ class Microwave3D:
947
967
  """
948
968
  from .sparam import sparam_field_power, sparam_mode_power
949
969
  if bc.v_integration:
970
+ if bc.vintline is None:
971
+ raise SimulationError('Trying to compute characteristic impedance but no integration line is defined.')
972
+ if bc.Z0 is None:
973
+ raise SimulationError('Trying to compute the impedance of a boundary condition with no characteristic impedance.')
974
+
950
975
  V = bc.vintline.line_integral(fieldfunction)
951
976
 
952
977
  if bc.active:
978
+ if bc.voltage is None:
979
+ raise ValueError('Cannot compute port S-paramer with a None port voltage.')
953
980
  a = bc.voltage
954
981
  b = (V-bc.voltage)
955
982
  else:
956
983
  a = 0
957
984
  b = V
958
985
 
959
- a = a*csqrt(1/(2*bc.Z0))
960
- b = b*csqrt(1/(2*bc.Z0))
986
+ a_sig = a*csqrt(1/(2*bc.Z0))
987
+ b_sig = b*csqrt(1/(2*bc.Z0))
961
988
 
962
- return b, a
989
+ return b_sig, a_sig
963
990
  else:
964
991
  if bc.modetype(k0) == 'TEM':
965
992
  const = 1/(np.sqrt((urp[0,0,:] + urp[1,1,:] + urp[2,2,:])/(erp[0,0,:] + erp[1,1,:] + erp[2,2,:])))
@@ -968,8 +995,8 @@ class Microwave3D:
968
995
  elif bc.modetype(k0) == 'TM':
969
996
  const = 1/((erp[0,0,:] + erp[1,1,:] + erp[2,2,:])/3)
970
997
  const = np.squeeze(const)
971
- field_p = sparam_field_power(self.mesh.nodes, tri_vertices, bc, k0, fieldfunction, const)
972
- mode_p = sparam_mode_power(self.mesh.nodes, tri_vertices, bc, k0, const)
998
+ field_p = sparam_field_power(self.mesh.nodes, tri_vertices, bc, k0, fieldfunction, const, 5)
999
+ mode_p = sparam_mode_power(self.mesh.nodes, tri_vertices, bc, k0, const, 5)
973
1000
  return field_p, mode_p
974
1001
 
975
1002
  # def frequency_domain_single(self, automatic_modal_analysis: bool = False) -> MWData:
@@ -1079,7 +1106,7 @@ class Microwave3D:
1079
1106
  # tris = mesh.get_triangles(active_port.tags)
1080
1107
  # tri_vertices = mesh.tris[:,tris]
1081
1108
  # pfield, pmode = self._compute_s_data(active_port, fieldf, tri_vertices, k0, ertri[:,:,tris], urtri[:,:,tris])
1082
- # logger.debug(f' Field Amplitude = {np.abs(pfield):.3f}, Excitation = {np.abs(pmode):.2f}')
1109
+ # logger.debug(f'Field Amplitude = {np.abs(pfield):.3f}, Excitation = {np.abs(pmode):.2f}')
1083
1110
  # Pout = pmode
1084
1111
 
1085
1112
  # #Passive ports
@@ -1088,7 +1115,7 @@ class Microwave3D:
1088
1115
  # tris = mesh.get_triangles(bc.tags)
1089
1116
  # tri_vertices = mesh.tris[:,tris]
1090
1117
  # pfield, pmode = self._compute_s_data(bc, fieldf, tri_vertices, k0, ertri[:,:,tris], urtri[:,:,tris])
1091
- # logger.debug(f' Field amplitude = {np.abs(pfield):.3f}, Excitation= {np.abs(pmode):.2f}')
1118
+ # logger.debug(f'Field amplitude = {np.abs(pfield):.3f}, Excitation= {np.abs(pmode):.2f}')
1092
1119
  # scalardata.write_S(bc.port_number, active_port.port_number, pfield/Pout)
1093
1120
 
1094
1121
  # active_port.active=False