emerge 0.6.7__py3-none-any.whl → 0.6.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.

@@ -269,7 +269,7 @@ class Microwave3D:
269
269
  Callable: The discretizer function
270
270
  """
271
271
  def disc(material: Material):
272
- return 299792458/(max(self.frequencies) * np.real(material.neff))
272
+ return 299792458/(max(self.frequencies) * np.real(material.neff(max(self.frequencies))))
273
273
  return disc
274
274
 
275
275
  def _initialize_field(self):
@@ -467,10 +467,25 @@ class Microwave3D:
467
467
  raise SimulationError('Cannot proceed, the current basis class is undefined.')
468
468
 
469
469
  logger.debug('Retreiving material properties.')
470
- ertet = self.mesh.retreive(lambda mat,x,y,z: mat.fer3d_mat(x,y,z), self.mesher.volumes)
471
- urtet = self.mesh.retreive(lambda mat,x,y,z: mat.fur3d_mat(x,y,z), self.mesher.volumes)
472
- condtet = self.mesh.retreive(lambda mat,x,y,z: mat.cond, self.mesher.volumes)[0,0,:]
470
+
471
+ if freq is None:
472
+ freq = self.frequencies[0]
473
+
474
+ materials = self.mesh.retreive(self.mesher.volumes)
473
475
 
476
+ ertet = np.zeros((3,3,self.mesh.n_tets), dtype=np.complex128)
477
+ tandtet = np.zeros((3,3,self.mesh.n_tets), dtype=np.complex128)
478
+ urtet = np.zeros((3,3,self.mesh.n_tets), dtype=np.complex128)
479
+ condtet = np.zeros((3,3,self.mesh.n_tets), dtype=np.complex128)
480
+
481
+ for mat in materials:
482
+ ertet = mat.er(freq, ertet)
483
+ tandtet = mat.tand(freq, tandtet)
484
+ urtet = mat.ur(freq, urtet)
485
+ condtet = mat.cond(freq, condtet)
486
+
487
+ ertet = ertet * (1-1j*tandtet)
488
+
474
489
  er = np.zeros((3,3,self.mesh.n_tris,), dtype=np.complex128)
475
490
  ur = np.zeros((3,3,self.mesh.n_tris,), dtype=np.complex128)
476
491
  cond = np.zeros((self.mesh.n_tris,), dtype=np.complex128)
@@ -479,7 +494,7 @@ class Microwave3D:
479
494
  itet = self.mesh.tri_to_tet[0,itri]
480
495
  er[:,:,itri] = ertet[:,:,itet]
481
496
  ur[:,:,itri] = urtet[:,:,itet]
482
- cond[itri] = condtet[itet]
497
+ cond[itri] = condtet[0,0,itet]
483
498
 
484
499
  itri_port = self.mesh.get_triangles(port.tags)
485
500
 
@@ -488,11 +503,7 @@ class Microwave3D:
488
503
  ermax = np.max(er[:,:,itri_port].flatten())
489
504
  urmax = np.max(ur[:,:,itri_port].flatten())
490
505
 
491
- if freq is None:
492
- freq = self.frequencies[0]
493
-
494
506
  k0 = 2*np.pi*freq/299792458
495
- kmax = k0*np.sqrt(ermax.real*urmax.real)
496
507
 
497
508
  Amatrix, Bmatrix, solve_ids, nlf = self.assembler.assemble_bma_matrices(self.basis, er, ur, cond, k0, port, self.bc)
498
509
 
@@ -511,8 +522,7 @@ class Microwave3D:
511
522
  else:
512
523
 
513
524
  target_kz = ermean*urmean*0.7*k0
514
-
515
-
525
+
516
526
  logger.debug(f'Solving for {solve_ids.shape[0]} degrees of freedom.')
517
527
 
518
528
  eigen_values, eigen_modes, report = self.solveroutine.eig_boundary(Amatrix, Bmatrix, solve_ids, nmodes, direct, target_kz, sign=-1)
@@ -550,12 +560,10 @@ class Microwave3D:
550
560
  Ez = np.max(np.abs(Efz))
551
561
  Exy = np.max(np.abs(Efxy))
552
562
 
553
- # Exy = np.max(np.max(Emode))
554
- # Ez = 0
555
- if Ez/Exy < 1e-3 and not TEM:
563
+ if Ez/Exy < 1e-1 and not TEM:
556
564
  logger.debug('Low Ez/Et ratio detected, assuming TE mode')
557
565
  mode.modetype = 'TE'
558
- elif Ez/Exy > 1e-3 and not TEM:
566
+ elif Ez/Exy > 1e-1 and not TEM:
559
567
  logger.debug('High Ez/Et ratio detected, assuming TM mode')
560
568
  mode.modetype = 'TM'
561
569
  elif TEM:
@@ -621,9 +629,7 @@ class Microwave3D:
621
629
  if self.basis is None:
622
630
  raise SimulationError('Cannot proceed, the simulation basis class is undefined.')
623
631
 
624
- er = self.mesh.retreive(lambda mat,x,y,z: mat.fer3d_mat(x,y,z), self.mesher.volumes)
625
- ur = self.mesh.retreive(lambda mat,x,y,z: mat.fur3d_mat(x,y,z), self.mesher.volumes)
626
- cond = self.mesh.retreive(lambda mat,x,y,z: mat.cond, self.mesher.volumes)[0,0,:]
632
+ materials = self.mesh.retreive(self.mesher.volumes)
627
633
 
628
634
  ### Does this move
629
635
  logger.debug('Initializing frequency domain sweep.')
@@ -674,7 +680,8 @@ class Microwave3D:
674
680
  freq_groups = [self.frequencies[i:i+n] for i in range(0, len(self.frequencies), n)]
675
681
 
676
682
  results: list[SimJob] = []
677
-
683
+ matset: list[tuple[np.ndarray, np.ndarray, np.ndarray]] = []
684
+
678
685
  ## Single threaded
679
686
  job_id = 1
680
687
 
@@ -691,7 +698,7 @@ class Microwave3D:
691
698
  logger.debug(f'Simulation frequency = {freq/1e9:.3f} GHz')
692
699
  if automatic_modal_analysis:
693
700
  self._compute_modes(freq)
694
- job = self.assembler.assemble_freq_matrix(self.basis, er, ur, cond,
701
+ job, mats = self.assembler.assemble_freq_matrix(self.basis, materials,
695
702
  self.bc.boundary_conditions,
696
703
  freq,
697
704
  cache_matrices=self.cache_matrices)
@@ -700,6 +707,7 @@ class Microwave3D:
700
707
  job.id = job_id
701
708
  job_id += 1
702
709
  jobs.append(job)
710
+ matset.append(mats)
703
711
 
704
712
  logger.info(f'Starting single threaded solve of {len(jobs)} jobs.')
705
713
  group_results = [run_job_single(job) for job in jobs]
@@ -716,7 +724,7 @@ class Microwave3D:
716
724
  logger.debug(f'Simulation frequency = {freq/1e9:.3f} GHz')
717
725
  if automatic_modal_analysis:
718
726
  self._compute_modes(freq)
719
- job = self.assembler.assemble_freq_matrix(self.basis, er, ur, cond,
727
+ job, mats = self.assembler.assemble_freq_matrix(self.basis, materials,
720
728
  self.bc.boundary_conditions,
721
729
  freq,
722
730
  cache_matrices=self.cache_matrices)
@@ -725,6 +733,7 @@ class Microwave3D:
725
733
  job.id = job_id
726
734
  job_id += 1
727
735
  jobs.append(job)
736
+ matset.append(mats)
728
737
 
729
738
  logger.info(f'Starting distributed solve of {len(jobs)} jobs with {njobs} threads.')
730
739
  group_results = list(executor.map(run_job, jobs))
@@ -749,8 +758,8 @@ class Microwave3D:
749
758
  if automatic_modal_analysis:
750
759
  self._compute_modes(freq)
751
760
 
752
- job = self.assembler.assemble_freq_matrix(
753
- self.basis, er, ur, cond,
761
+ job, mats = self.assembler.assemble_freq_matrix(
762
+ self.basis, materials,
754
763
  self.bc.boundary_conditions,
755
764
  freq,
756
765
  cache_matrices=self.cache_matrices
@@ -761,6 +770,7 @@ class Microwave3D:
761
770
  job.id = job_id
762
771
  job_id += 1
763
772
  jobs.append(job)
773
+ matset.append(mats)
764
774
 
765
775
  logger.info(
766
776
  f'Starting distributed solve of {len(jobs)} jobs '
@@ -783,7 +793,7 @@ class Microwave3D:
783
793
 
784
794
  self.solveroutine.reset()
785
795
  ### Compute S-parameters and return
786
- self._post_process(results, er, ur, cond)
796
+ self._post_process(results, matset)
787
797
  return self.data
788
798
 
789
799
  def eigenmode(self, search_frequency: float,
@@ -817,19 +827,21 @@ class Microwave3D:
817
827
  if self.basis is None:
818
828
  raise SimulationError('Cannot proceed. The simulation basis class is undefined.')
819
829
 
820
- er = self.mesh.retreive(lambda mat,x,y,z: mat.fer3d_mat(x,y,z), self.mesher.volumes)
821
- ur = self.mesh.retreive(lambda mat,x,y,z: mat.fur3d_mat(x,y,z), self.mesher.volumes)
822
- cond = self.mesh.retreive(lambda mat,x,y,z: mat.cond, self.mesher.volumes)[0,0,:]
830
+ materials = self.mesh.retreive(self.mesher.volumes)
831
+
832
+ # er = self.mesh.retreive(lambda mat,x,y,z: mat.fer3d_mat(x,y,z), self.mesher.volumes)
833
+ # ur = self.mesh.retreive(lambda mat,x,y,z: mat.fur3d_mat(x,y,z), self.mesher.volumes)
834
+ # cond = self.mesh.retreive(lambda mat,x,y,z: mat.cond, self.mesher.volumes)[0,0,:]
823
835
 
824
836
  ### Does this move
825
837
  logger.debug('Initializing frequency domain sweep.')
826
838
 
827
839
  logger.info(f'Pre-assembling matrices of {len(self.frequencies)} frequency points.')
828
840
 
829
- job = self.assembler.assemble_eig_matrix(self.basis, er, ur, cond,
841
+ job, matset = self.assembler.assemble_eig_matrix(self.basis, materials,
830
842
  self.bc.boundary_conditions, search_frequency)
831
843
 
832
-
844
+ er, ur, cond = matset
833
845
  logger.info('Solving complete')
834
846
 
835
847
  A, C, solve_ids = job.yield_AC()
@@ -846,7 +858,7 @@ class Microwave3D:
846
858
 
847
859
  for i in range(nmodes_found):
848
860
 
849
- Emode = np.zeros((self.basis.n_field,), dtype=np.complex128)
861
+
850
862
  eig_k0 = np.sqrt(eigen_values[i])
851
863
  if eig_k0 < k0_limit:
852
864
  logger.debug(f'Ignoring mode due to low k0: {eig_k0} < {k0_limit}')
@@ -856,11 +868,11 @@ class Microwave3D:
856
868
  logger.debug(f'Found k0={eig_k0:.2f}, f0={eig_freq/1e9:.2f} GHz')
857
869
  Emode = eigen_modes[:,i]
858
870
 
859
- scalardata = self.data.scalar.new(freq=eig_freq, **self._params)
871
+ scalardata = self.data.scalar.new(**self._params)
860
872
  scalardata.k0 = eig_k0
861
873
  scalardata.freq = eig_freq
862
874
 
863
- fielddata = self.data.field.new(freq=eig_freq, **self._params)
875
+ fielddata = self.data.field.new(**self._params)
864
876
  fielddata.freq = eig_freq
865
877
  fielddata._der = np.squeeze(er[0,0,:])
866
878
  fielddata._dur = np.squeeze(ur[0,0,:])
@@ -870,7 +882,7 @@ class Microwave3D:
870
882
 
871
883
  return self.data
872
884
 
873
- def _post_process(self, results: list[SimJob], er: np.ndarray, ur: np.ndarray, cond: np.ndarray):
885
+ def _post_process(self, results: list[SimJob], materials: list[tuple[np.ndarray, np.ndarray, np.ndarray]]):
874
886
  """Compute the S-parameters after Frequency sweep
875
887
 
876
888
  Args:
@@ -888,18 +900,19 @@ class Microwave3D:
888
900
 
889
901
  logger.info('Computing S-parameters')
890
902
 
891
- ertri = np.zeros((3,3,self.mesh.n_tris), dtype=np.complex128)
892
- urtri = np.zeros((3,3,self.mesh.n_tris), dtype=np.complex128)
893
- condtri = np.zeros((self.mesh.n_tris,), dtype=np.complex128)
894
-
895
- for itri in range(self.mesh.n_tris):
896
- itet = self.mesh.tri_to_tet[0,itri]
897
- ertri[:,:,itri] = er[:,:,itet]
898
- urtri[:,:,itri] = ur[:,:,itet]
899
- condtri[itri] = cond[itet]
900
-
901
- for freq, job in zip(self.frequencies, results):
902
903
 
904
+ for freq, job, mats in zip(self.frequencies, results, materials):
905
+ er, ur, cond = mats
906
+ ertri = np.zeros((3,3,self.mesh.n_tris), dtype=np.complex128)
907
+ urtri = np.zeros((3,3,self.mesh.n_tris), dtype=np.complex128)
908
+ condtri = np.zeros((self.mesh.n_tris,), dtype=np.complex128)
909
+
910
+ for itri in range(self.mesh.n_tris):
911
+ itet = self.mesh.tri_to_tet[0,itri]
912
+ ertri[:,:,itri] = er[:,:,itet]
913
+ urtri[:,:,itri] = ur[:,:,itet]
914
+ condtri[itri] = cond[0,0,itet]
915
+
903
916
  k0 = 2*np.pi*freq/299792458
904
917
 
905
918
  scalardata = self.data.scalar.new(freq=freq, **self._params)
@@ -73,7 +73,7 @@ class MWBoundaryConditionSet(BoundaryConditionSet):
73
73
  """
74
74
  bcs = self.oftype(PEC)
75
75
  for bc in self.oftype(SurfaceImpedance):
76
- if bc.material.cond > 1e3:
76
+ if bc.sigma > 1e3:
77
77
  bcs.append(bc)
78
78
 
79
79
  return bcs
@@ -1034,12 +1034,13 @@ class SurfaceImpedance(RobinBC):
1034
1034
  self._material: Material | None = material
1035
1035
  self._mur: float | complex = 1.0
1036
1036
  self._epsr: float | complex = 1.0
1037
-
1038
1037
  self.sigma: float = 0.0
1038
+
1039
1039
  if material is not None:
1040
1040
  self.sigma = material.cond
1041
1041
  self._mur = material.ur
1042
1042
  self._epsr = material.er
1043
+
1043
1044
  if surface_conductance is not None:
1044
1045
  self.sigma = surface_conductance
1045
1046
 
@@ -1066,10 +1067,15 @@ class SurfaceImpedance(RobinBC):
1066
1067
  Returns:
1067
1068
  complex: The γ-constant
1068
1069
  """
1070
+
1069
1071
  w0 = k0*C0
1070
- sigma = self.sigma
1072
+ f0 = w0/(2*np.pi)
1073
+ sigma = self.sigma.scalar(f0)
1074
+ mur = self._material.ur.scalar(f0)
1075
+ er = self._material.er.scalar(f0)
1076
+
1071
1077
  rho = 1/sigma
1072
- d_skin = (2*rho/(w0*MU0*self._mur) * ((1+(w0*EPS0*self._epsr*rho)**2)**0.5 + rho*w0*EPS0*self._epsr))**0.5
1078
+ d_skin = (2*rho/(w0*MU0*mur) * ((1+(w0*EPS0*er*rho)**2)**0.5 + rho*w0*EPS0*er))**0.5
1073
1079
  R = rho/d_skin
1074
1080
  if self._sr_model=='Hammerstad-Jensen' and self._sr > 0.0:
1075
1081
  R = R * (1 + 2/np.pi * np.arctan(1.4*(self._sr/d_skin)**2))
@@ -282,9 +282,9 @@ class FarFieldData:
282
282
 
283
283
  @property
284
284
  def Etheta(self) -> np.ndarray:
285
- thx = -np.cos(self.theta)*np.cos(self.phi)
286
- thy = -np.cos(self.theta)*np.sin(self.phi)
287
- thz = np.sin(self.theta)
285
+ thx = np.cos(self.theta)*np.cos(self.phi)
286
+ thy = np.cos(self.theta)*np.sin(self.phi)
287
+ thz = -np.sin(self.theta)
288
288
  return thx*self.E[0,:] + thy*self.E[1,:] + thz*self.E[2,:]
289
289
 
290
290
  @property
@@ -296,11 +296,11 @@ class FarFieldData:
296
296
 
297
297
  @property
298
298
  def Erhcp(self) -> np.ndarray:
299
- return (self.Etheta - 1j*self.Ephi)/np.sqrt(2)
299
+ return (self.Etheta + 1j*self.Ephi)/np.sqrt(2)
300
300
 
301
301
  @property
302
302
  def Elhcp(self) -> np.ndarray:
303
- return (self.Etheta + 1j*self.Ephi)/np.sqrt(2)
303
+ return (self.Etheta - 1j*self.Ephi)/np.sqrt(2)
304
304
 
305
305
  @property
306
306
  def AR(self) -> np.ndarray:
@@ -702,6 +702,11 @@ class MWField:
702
702
 
703
703
  def interpolate(self, xs: np.ndarray, ys: np.ndarray, zs: np.ndarray) -> EHField:
704
704
  ''' Interpolate the dataset in the provided xs, ys, zs values'''
705
+ if isinstance(xs, (float, int, complex)):
706
+ xs = np.array([xs,])
707
+ ys = np.array([ys,])
708
+ zs = np.array([zs,])
709
+
705
710
  shp = xs.shape
706
711
  xf = xs.flatten()
707
712
  yf = ys.flatten()
@@ -748,6 +753,7 @@ class MWField:
748
753
  xs = np.linspace(xb[0], xb[1], int((xb[1]-xb[0])/ds))
749
754
  ys = np.linspace(yb[0], yb[1], int((yb[1]-yb[0])/ds))
750
755
  zs = np.linspace(zb[0], zb[1], int((zb[1]-zb[0])/ds))
756
+
751
757
  if x is not None:
752
758
  Y,Z = np.meshgrid(ys, zs)
753
759
  X = x*np.ones_like(Y)
@@ -759,6 +765,56 @@ class MWField:
759
765
  Z = z*np.ones_like(Y)
760
766
  return self.interpolate(X,Y,Z)
761
767
 
768
+ def cutplane_normal(self,
769
+ point=(0,0,0),
770
+ normal=(0,0,1),
771
+ npoints: int = 300) -> EHField:
772
+ """
773
+ Take a 2D slice of the field along an arbitrary plane.
774
+ Args:
775
+ point: (x0,y0,z0), a point on the plane
776
+ normal: (nx,ny,nz), plane normal vector
777
+ npoints: number of grid points per axis
778
+ """
779
+
780
+ n = np.array(normal, dtype=float)
781
+ n /= np.linalg.norm(n)
782
+ point = np.array(point)
783
+
784
+ tmp = np.array([1,0,0]) if abs(n[0]) < 0.9 else np.array([0,1,0])
785
+ u = np.cross(n, tmp)
786
+ u /= np.linalg.norm(u)
787
+ v = np.cross(n, u)
788
+
789
+ xb, yb, zb = self.basis.bounds
790
+ nx, ny, nz = 5, 5, 5
791
+ Xg = np.linspace(xb[0], xb[1], nx)
792
+ Yg = np.linspace(yb[0], yb[1], ny)
793
+ Zg = np.linspace(zb[0], zb[1], nz)
794
+ Xg, Yg, Zg = np.meshgrid(Xg, Yg, Zg, indexing='ij')
795
+ geometry = np.vstack([Xg.ravel(), Yg.ravel(), Zg.ravel()]).T # Nx3
796
+
797
+ rel_pts = geometry - point
798
+ S = rel_pts @ u
799
+ T = rel_pts @ v
800
+
801
+ margin = 0.01
802
+ s_min, s_max = S.min(), S.max()
803
+ t_min, t_max = T.min(), T.max()
804
+ s_bounds = (s_min - margin*(s_max-s_min), s_max + margin*(s_max-s_min))
805
+ t_bounds = (t_min - margin*(t_max-t_min), t_max + margin*(t_max-t_min))
806
+
807
+ S_grid = np.linspace(s_bounds[0], s_bounds[1], npoints)
808
+ T_grid = np.linspace(t_bounds[0], t_bounds[1], npoints)
809
+ S_mesh, T_mesh = np.meshgrid(S_grid, T_grid)
810
+
811
+ X = point[0] + S_mesh*u[0] + T_mesh*v[0]
812
+ Y = point[1] + S_mesh*u[1] + T_mesh*v[1]
813
+ Z = point[2] + S_mesh*u[2] + T_mesh*v[2]
814
+
815
+ return self.interpolate(X, Y, Z)
816
+
817
+
762
818
  def grid(self, ds: float) -> EHField:
763
819
  """Interpolate a uniform grid sampled at ds
764
820
 
@@ -315,7 +315,6 @@ class PVDisplay(BaseDisplay):
315
315
  """The private callback function that stops the animation.
316
316
  """
317
317
  self._stop = True
318
- print('CLOSE!')
319
318
 
320
319
  def _animate(self) -> None:
321
320
  """Private function that starts the animation loop.
@@ -567,6 +566,7 @@ class PVDisplay(BaseDisplay):
567
566
  grid = pv.StructuredGrid(x,y,z)
568
567
  field_flat = field.flatten(order='F')
569
568
 
569
+
570
570
  if scale=='log':
571
571
  T = lambda x: np.log10(np.abs(x))
572
572
  elif scale=='symlog':
@@ -582,25 +582,28 @@ class PVDisplay(BaseDisplay):
582
582
  self._ctr += 1
583
583
  grid[name] = static_field
584
584
 
585
+ grid_no_nan = grid.threshold(scalars=name)
586
+
587
+ # Determine color limits
585
588
  if clim is None:
586
- fmin = np.min(static_field)
587
- fmax = np.max(static_field)
589
+ fmin = np.nanmin(static_field)
590
+ fmax = np.nanmax(static_field)
588
591
  clim = (fmin, fmax)
589
-
590
592
  if symmetrize:
591
- lim = max(abs(clim[0]),abs(clim[1]))
593
+ lim = max(abs(clim[0]), abs(clim[1]))
592
594
  clim = (-lim, lim)
593
595
 
594
596
  kwargs = setdefault(kwargs, cmap=cmap, clim=clim, opacity=opacity, pickable=False, multi_colors=True)
595
- actor = self._plot.add_mesh(grid, scalars=name, **kwargs)
597
+ actor = self._plot.add_mesh(grid_no_nan, scalars=name, **kwargs)
598
+
596
599
 
597
600
  if self._do_animate:
598
601
  def on_update(obj: _AnimObject, phi: complex):
599
- field = obj.T(np.real(obj.field*phi))
600
- obj.grid[name] = field
601
- self._objs.append(_AnimObject(field_flat, T, grid, actor, on_update)) # type: ignore
602
-
603
-
602
+ field_anim = obj.T(np.real(obj.field * phi))
603
+ obj.grid[name] = field_anim
604
+ self._objs.append(_AnimObject(field_flat, T, grid_no_nan, actor, on_update))
605
+
606
+
604
607
  def add_title(self, title: str) -> None:
605
608
  """Adds a title
606
609
 
@@ -652,6 +655,11 @@ class PVDisplay(BaseDisplay):
652
655
  dx = dx.flatten().real
653
656
  dy = dy.flatten().real
654
657
  dz = dz.flatten().real
658
+
659
+ ids = np.invert(np.isnan(dx))
660
+
661
+ x, y, z, dx, dy, dz = x[ids], y[ids], z[ids], dx[ids], dy[ids], dz[ids]
662
+
655
663
  dmin = _min_distance(x,y,z)
656
664
 
657
665
  dmax = np.max(_norm(dx,dy,dz))
@@ -667,8 +675,10 @@ class PVDisplay(BaseDisplay):
667
675
  kwargs = dict()
668
676
  if color is not None:
669
677
  kwargs['color'] = color
678
+
670
679
  pl = self._plot.add_arrows(Coo, Vec, scalars=None, clim=None, cmap=None, **kwargs)
671
680
 
681
+
672
682
  def add_contour(self,
673
683
  X: np.ndarray,
674
684
  Y: np.ndarray,
@@ -533,7 +533,7 @@ def plot_ff(
533
533
  dB: bool = False,
534
534
  labels: Optional[List[str]] = None,
535
535
  xlabel: str = "Theta (rad)",
536
- ylabel: str = "|E|",
536
+ ylabel: str = "",
537
537
  linestyles: Union[str, List[str]] = "-",
538
538
  linewidth: float = 2.0,
539
539
  markers: Optional[Union[str, List[Optional[str]]]] = None,
@@ -245,13 +245,20 @@ class Simulation:
245
245
  vM, vm, vp = [float(x) for x in version.split('.')]
246
246
  cM, cm, cp = [float(x) for x in __version__.split('.')]
247
247
  if vM != cM:
248
- raise VersionError(f"You are running a script designed for version {version} with a possibly incompatible version of EMerge {__version__}")
248
+ raise VersionError(f"You are running a script designed for version {version} with a possibly incompatible version of EMerge {__version__}. \n You can upgrade your version of emerge with: pip --upgrade emerge")
249
249
  if vm != cm:
250
- raise VersionError(f"You are running a script designed for version {version} with a possibly incompatible version of EMerge {__version__}")
250
+ raise VersionError(f"You are running a script designed for version {version} with a possibly incompatible version of EMerge {__version__}. \n You can upgrade your version of emerge with: pip --upgrade emerge")
251
251
  if vp != cp:
252
- logger.warning(f"You are running a script designed for version {version} with a possibly incompatible version of EMerge {__version__}")
252
+ logger.warning("You are running a script designed for a different version of EMerge.")
253
+ logger.warning(f"The script version: {version}")
254
+ logger.warning(f"EMerge version: {__version__}")
255
+ logger.warning("Usually EMerge works without a problem but Errors may occur.")
256
+ logger.warning("You can upgrade your version of emerge with: pip --upgrade emerge")
253
257
  logger.warning("You may suppress this error by removing the call to .check_version().")
254
- input('Press enter to proceed...')
258
+ logger.warning("Press Ctrl+C to abort.")
259
+ ans = input('Press enter to proceed or [Q] to quit:')
260
+ if ans.lower().strip()=='q':
261
+ quit()
255
262
 
256
263
  def save(self) -> None:
257
264
  """Saves the current model in the provided project directory."""
@@ -458,7 +465,7 @@ class Simulation:
458
465
 
459
466
  logger.info(f'Iterating: {params}')
460
467
  if len(dims_flat)==1:
461
- yield (dims_flat[0][i_iter],)
468
+ yield dims_flat[0][i_iter]
462
469
  else:
463
470
  yield (dim[i_iter] for dim in dims_flat) # type: ignore
464
471
  self.mw.cache_matrices = True
emerge/_emerge/solver.py CHANGED
@@ -289,10 +289,14 @@ class Solver:
289
289
 
290
290
  def __init__(self):
291
291
  self.own_preconditioner: bool = False
292
+ self.initialized: bool = False
292
293
 
293
294
  def __str__(self) -> str:
294
295
  return f'{self.__class__.__name__}'
295
296
 
297
+ def initialize(self) -> None:
298
+ return None
299
+
296
300
  def duplicate(self) -> Solver:
297
301
  return self.__class__()
298
302
 
@@ -324,6 +328,9 @@ class EigSolver:
324
328
  def __init__(self):
325
329
  self.own_preconditioner: bool = False
326
330
 
331
+ def initialize(self) -> None:
332
+ return None
333
+
327
334
  def __str__(self) -> str:
328
335
  return f'{self.__class__.__name__}'
329
336
 
@@ -513,7 +520,19 @@ class SolverUMFPACK(Solver):
513
520
  super().__init__()
514
521
  self.A: np.ndarray = None
515
522
  self.b: np.ndarray = None
516
- self.umfpack: um.UmfpackContext = um.UmfpackContext('zl')
523
+
524
+ self.umfpack: um.UmfpackContext | None = None
525
+
526
+ # SETTINGS
527
+ self._pivoting_threshold: float = 0.001
528
+
529
+ self.fact_symb: bool = False
530
+ self.initalized: bool = False
531
+
532
+ def initialize(self):
533
+ if self.initalized:
534
+ return
535
+ self.umfpack = um.UmfpackContext('zl')
517
536
  self.umfpack.control[um.UMFPACK_PRL] = 0 # ty: ignore
518
537
  self.umfpack.control[um.UMFPACK_IRSTEP] = 2 # ty: ignore
519
538
  self.umfpack.control[um.UMFPACK_STRATEGY] = um.UMFPACK_STRATEGY_SYMMETRIC # ty: ignore
@@ -522,21 +541,16 @@ class SolverUMFPACK(Solver):
522
541
  self.umfpack.control[um.UMFPACK_SYM_PIVOT_TOLERANCE] = 0.001 # ty: ignore
523
542
  self.umfpack.control[um.UMFPACK_BLOCK_SIZE] = 64 # ty: ignore
524
543
  self.umfpack.control[um.UMFPACK_FIXQ] = -1 # ty: ignore
525
-
526
- # SETTINGS
527
- self._pivoting_threshold: float = 0.001
528
-
529
- self.fact_symb: bool = False
530
-
544
+ self.initalized = True
531
545
  def reset(self) -> None:
532
546
  self.fact_symb = False
533
547
 
534
- def set_options(self,
535
- pivoting_threshold: float | None = None) -> None:
536
- if pivoting_threshold is not None:
537
- self.umfpack.control[um.UMFPACK_PIVOT_TOLERANCE] = pivoting_threshold # ty: ignore
538
- self.umfpack.control[um.UMFPACK_SYM_PIVOT_TOLERANCE] = pivoting_threshold # ty: ignore
539
- self._pivoting_threshold = pivoting_threshold
548
+ def set_options(self, pivoting_threshold: float | None = None) -> None:
549
+ self.initialize()
550
+ if pivoting_threshold is not None:
551
+ self.umfpack.control[um.UMFPACK_PIVOT_TOLERANCE] = pivoting_threshold # ty: ignore
552
+ self.umfpack.control[um.UMFPACK_SYM_PIVOT_TOLERANCE] = pivoting_threshold # ty: ignore
553
+ self._pivoting_threshold = pivoting_threshold
540
554
 
541
555
  def duplicate(self) -> Solver:
542
556
  new_solver = self.__class__()
@@ -568,11 +582,17 @@ class SolverPardiso(Solver):
568
582
 
569
583
  def __init__(self):
570
584
  super().__init__()
571
- self.solver: PardisoInterface = PardisoInterface()
585
+ self.solver: PardisoInterface | None = None
572
586
  self.fact_symb: bool = False
573
587
  self.A: np.ndarray = None
574
588
  self.b: np.ndarray = None
575
589
 
590
+ def initialize(self) -> None:
591
+ if self.initialized:
592
+ return
593
+ self.solver = PardisoInterface()
594
+ self.initialized = True
595
+
576
596
  def solve(self, A, b, precon, reuse_factorization: bool = False, id: int = -1) -> tuple[np.ndarray, SolveReport]:
577
597
  logger.info(f'[ID={id}] Calling Pardiso Solver')
578
598
  if self.fact_symb is False:
@@ -594,11 +614,18 @@ class SolverPardiso(Solver):
594
614
  class CuDSSSolver(Solver):
595
615
  real_only = False
596
616
  def __init__(self):
597
- self._cudss = CuDSSInterface()
617
+ super().__init__()
618
+ self._cudss: CuDSSInterface | None = None
598
619
  self.fact_symb: bool = False
599
620
  self.fact_numb: bool = False
621
+
622
+ def initialize(self) -> None:
623
+ if self.initialized:
624
+ return
625
+ self._cudss = CuDSSInterface()
600
626
  self._cudss._PRES = 2
601
-
627
+ self.initialized = True
628
+
602
629
  def reset(self) -> None:
603
630
  self.fact_symb = False
604
631
  self.fact_numb = False
@@ -1085,7 +1112,7 @@ class SolveRoutine:
1085
1112
  np.ndarray: The resultant solution.
1086
1113
  """
1087
1114
  solver: Solver = self._get_solver(A, b)
1088
-
1115
+ solver.initialize()
1089
1116
  NF = A.shape[0]
1090
1117
  NS = solve_ids.shape[0]
1091
1118
 
@@ -1178,7 +1205,7 @@ class SolveRoutine:
1178
1205
  SolveReport: The solution report
1179
1206
  """
1180
1207
  solver = self._get_eig_solver_bma(A, B, direct=direct)
1181
-
1208
+ solver.initialize()
1182
1209
  NF = A.shape[0]
1183
1210
  NS = solve_ids.shape[0]
1184
1211
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: emerge
3
- Version: 0.6.7
3
+ Version: 0.6.8
4
4
  Summary: An open source EM FEM simulator in Python
5
5
  Project-URL: Homepage, https://github.com/FennisRobert/EMerge
6
6
  Project-URL: Issues, https://github.com/FennisRobert/EMerge/issues