emerge 1.0.5__py3-none-any.whl → 1.0.6__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.

@@ -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
@@ -190,7 +193,6 @@ class Microwave3D:
190
193
  logger.debug(f'Assinging PEC to {surf}')
191
194
  self.bc.PEC(surf)
192
195
  elif surf.material.cond.scalar(1e9) > self._settings.mw_2dbc_lim:
193
- logger.debug(f'Assigning SurfaceImpedance to {surf}')
194
196
  self.bc.SurfaceImpedance(surf, surf.material)
195
197
 
196
198
 
@@ -619,7 +621,7 @@ class Microwave3D:
619
621
 
620
622
  def run_sweep(self,
621
623
  parallel: bool = False,
622
- njobs: int = 2,
624
+ n_workers: int = 2,
623
625
  harddisc_threshold: int | None = None,
624
626
  harddisc_path: str = 'EMergeSparse',
625
627
  frequency_groups: int = -1,
@@ -627,7 +629,7 @@ class Microwave3D:
627
629
  automatic_modal_analysis: bool = True) -> MWData:
628
630
  """Executes a frequency domain study
629
631
 
630
- The study is distributed over "njobs" workers.
632
+ The study is distributed over "n_workers" workers.
631
633
  As optional parameter you may set a harddisc_threshold as integer. This determines the maximum
632
634
  number of degrees of freedom before which the jobs will be cahced to the harddisk. The
633
635
  path that will be used to cache the sparse matrices can be specified.
@@ -637,7 +639,7 @@ class Microwave3D:
637
639
  frequency indices will be precomputed and then solved: [[1,2,3,4],[5,6,7,8],[9,10,11]]
638
640
 
639
641
  Args:
640
- njobs (int, optional): The number of jobs. Defaults to 2.
642
+ n_workers (int, optional): The number of workers. Defaults to 2.
641
643
  harddisc_threshold (int, optional): The number of DOF limit. Defaults to None.
642
644
  harddisc_path (str, optional): The cached matrix path name. Defaults to 'EMergeSparse'.
643
645
  frequency_groups (int, optional): The number of frequency points in a solve group. Defaults to -1.
@@ -687,7 +689,8 @@ class Microwave3D:
687
689
  ## DEFINE SOLVE FUNCTIONS
688
690
  def get_routine():
689
691
  if not hasattr(thread_local, "routine"):
690
- thread_local.routine = self.solveroutine.duplicate()._configure_routine('MT')
692
+ worker_nr = int(threading.current_thread().name.split('_')[1])+1
693
+ thread_local.routine = self.solveroutine.duplicate()._configure_routine('MT', thread_nr=worker_nr)
691
694
  return thread_local.routine
692
695
 
693
696
  def run_job(job: SimJob):
@@ -750,7 +753,7 @@ class Microwave3D:
750
753
  results.extend(group_results)
751
754
  elif not multi_processing:
752
755
  # MULTI THREADED
753
- with ThreadPoolExecutor(max_workers=njobs) as executor:
756
+ with ThreadPoolExecutor(max_workers=n_workers, thread_name_prefix='WKR') as executor:
754
757
  # ITERATE OVER FREQUENCIES
755
758
  for i_group, fgroup in enumerate(freq_groups):
756
759
  logger.info(f'Precomputing group {i_group}.')
@@ -771,7 +774,7 @@ class Microwave3D:
771
774
  jobs.append(job)
772
775
  matset.append(mats)
773
776
 
774
- logger.info(f'Starting distributed solve of {len(jobs)} jobs with {njobs} threads.')
777
+ logger.info(f'Starting distributed solve of {len(jobs)} jobs with {n_workers} threads.')
775
778
  group_results = list(executor.map(run_job, jobs))
776
779
  results.extend(group_results)
777
780
  executor.shutdown()
@@ -784,7 +787,7 @@ class Microwave3D:
784
787
  "if __name__ == '__main__' guard in the top-level script."
785
788
  )
786
789
  # Start parallel pool
787
- with mp.Pool(processes=njobs) as pool:
790
+ with mp.Pool(processes=n_workers, initializer=_init_worker) as pool:
788
791
  for i_group, fgroup in enumerate(freq_groups):
789
792
  logger.debug(f'Precomputing group {i_group}.')
790
793
  jobs = []
@@ -810,7 +813,7 @@ class Microwave3D:
810
813
 
811
814
  logger.info(
812
815
  f'Starting distributed solve of {len(jobs)} jobs '
813
- f'with {njobs} processes in parallel'
816
+ f'with {n_workers} processes in parallel'
814
817
  )
815
818
  # Distribute taks
816
819
  group_results = pool.map(run_job_multi, jobs)
@@ -805,7 +805,7 @@ class LumpedPort(PortBC):
805
805
  port_number: int,
806
806
  width: float | None = None,
807
807
  height: float | None = None,
808
- direction: Axis | None = None,
808
+ direction: Axis | tuple[float, float, float] | None = None,
809
809
  power: float = 1,
810
810
  Z0: float = 50):
811
811
  """Generates a lumped power boundary condition.
@@ -843,7 +843,7 @@ class LumpedPort(PortBC):
843
843
 
844
844
  self.width: float = width
845
845
  self.height: float = height # type: ignore
846
- self.Vdirection: Axis = direction # type: ignore
846
+ self.Vdirection: Axis = _parse_axis(direction) # type: ignore
847
847
  self.type = 'TEM'
848
848
 
849
849
  # logger.info('Constructing coordinate system from normal port')
@@ -732,6 +732,15 @@ class MWField:
732
732
  self._z = zs
733
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
734
 
735
+ def boundary(self,
736
+ selection: FaceSelection) -> EHField:
737
+ nodes = self.mesh.nodes
738
+ x = nodes[0,:]
739
+ y = nodes[1,:]
740
+ z = nodes[2,:]
741
+ field = self.interpolate(x, y, z, False)
742
+ return field
743
+
735
744
  def cutplane(self,
736
745
  ds: float,
737
746
  x: float | None = None,
@@ -545,9 +545,16 @@ class PVDisplay(BaseDisplay):
545
545
  X,Y,Z = XYZ
546
546
  else:
547
547
  tris = self._mesh.get_triangles(port.selection.tags)
548
+ ids = np.sort(np.unique(self._mesh.tris[:,tris].flatten()))
548
549
  X = self._mesh.tri_centers[0,tris]
549
550
  Y = self._mesh.tri_centers[1,tris]
550
551
  Z = self._mesh.tri_centers[2,tris]
552
+ X2 = self._mesh.nodes[0,ids]
553
+ Y2 = self._mesh.nodes[1,ids]
554
+ Z2 = self._mesh.nodes[2,ids]
555
+ X = np.concatenate((X,X2))
556
+ Y = np.concatenate((Y,Y2))
557
+ Z = np.concatenate((Z,Z2))
551
558
 
552
559
  X = X+dv[0]
553
560
  Y = Y+dv[1]
@@ -680,6 +687,87 @@ class PVDisplay(BaseDisplay):
680
687
  self._objs.append(_AnimObject(field_flat, T, grid, grid_no_nan, actor, on_update))
681
688
 
682
689
  self._reset_cbar()
690
+
691
+ def add_boundary_field(self,
692
+ selection: FaceSelection,
693
+ field: tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray],
694
+ scale: Literal['lin','log','symlog'] = 'lin',
695
+ cmap: cmap_names | None = None,
696
+ clim: tuple[float, float] | None = None,
697
+ opacity: float = 1.0,
698
+ symmetrize: bool = False,
699
+ _fieldname: str | None = None,
700
+ **kwargs,):
701
+ """Add a surface plot to the display based on a boundary surface
702
+
703
+ The X,Y,Z coordinates must be a 2D grid of data points. The field must be a real field with the same size.
704
+
705
+ Example:
706
+ >>> display.add_boundary_field(selection, field.boundary(selction).scalar('normE','real'))
707
+
708
+ Args:
709
+ selection (FaceSelection): The boundary to show the field on
710
+ field (tuple[np.ndarray]): The output of EMField().boundary().scalar()
711
+ scale (Literal["lin","log","symlog"], optional): The colormap scaling¹. Defaults to 'lin'.
712
+ cmap (cmap_names, optional): The colormap. Defaults to 'coolwarm'.
713
+ clim (tuple[float, float], optional): Specific color limits (min, max). Defaults to None.
714
+ opacity (float, optional): The opacity of the surface. Defaults to 1.0.
715
+ symmetrize (bool, optional): Wether to force a symmetrical color limit (-A,A). Defaults to True.
716
+
717
+ (¹): lin: f(x)=x, log: f(x)=log₁₀(|x|), symlog: f(x)=sgn(x)·log₁₀(1+|x·ln(10)|)
718
+ """
719
+
720
+ grid = self.mesh_surface(selection)
721
+
722
+ field = field[3]
723
+ field_flat = field.flatten(order='F')
724
+
725
+ if scale=='log':
726
+ T = lambda x: np.log10(np.abs(x+1e-12))
727
+ elif scale=='symlog':
728
+ T = lambda x: np.sign(x) * np.log10(1 + np.abs(x*np.log(10)))
729
+ else:
730
+ T = lambda x: x
731
+
732
+ static_field = T(np.real(field_flat))
733
+
734
+ if _fieldname is None:
735
+ name = 'anim'+str(self._ctr)
736
+ else:
737
+ name = _fieldname
738
+ self._ctr += 1
739
+
740
+ grid[name] = static_field
741
+
742
+ default_cmap = EMERGE_AMP
743
+ # Determine color limits
744
+ if clim is None:
745
+ if self._cbar_lim is not None:
746
+ clim = self._cbar_lim
747
+ else:
748
+ fmin = np.nanmin(static_field)
749
+ fmax = np.nanmax(static_field)
750
+ clim = (fmin, fmax)
751
+
752
+ if symmetrize:
753
+ lim = max(abs(clim[0]), abs(clim[1]))
754
+ clim = (-lim, lim)
755
+ default_cmap = EMERGE_WAVE
756
+
757
+ if cmap is None:
758
+ cmap = default_cmap
759
+
760
+ kwargs = setdefault(kwargs, cmap=cmap, clim=clim, opacity=opacity, pickable=False, multi_colors=True)
761
+ actor = self._plot.add_mesh(grid, scalars=name, scalar_bar_args=self._cbar_args, **kwargs)
762
+
763
+ if self._do_animate:
764
+ def on_update(obj: _AnimObject, phi: complex):
765
+ field_anim = obj.T(np.real(obj.field * phi))
766
+ obj.grid[name] = field_anim
767
+ #obj.fgrid replace with thresholded scalar data.
768
+ self._objs.append(_AnimObject(field_flat, T, grid, grid, actor, on_update))
769
+
770
+ self._reset_cbar()
683
771
 
684
772
  def add_title(self, title: str) -> None:
685
773
  """Adds a title
@@ -479,7 +479,7 @@ class Simulation:
479
479
  for geo in _GEOMANAGER.all_geometries():
480
480
  self.display.add_object(geo, mesh=plot_mesh, opacity=opacity, volume_mesh=volume_mesh, label=labels)
481
481
  if selections:
482
- [self.display.add_object(sel, color='red', opacity=0.3, label=labels) for sel in selections]
482
+ [self.display.add_object(sel, color='red', opacity=0.6, label=labels) for sel in selections]
483
483
  self.display.show()
484
484
 
485
485
  return None