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.

@@ -539,15 +539,22 @@ class PVDisplay(BaseDisplay):
539
539
  XYZ=None,
540
540
  field: Literal['E','H'] = 'E',
541
541
  k0: float | None = None,
542
- mode_number: int = 0) -> None:
542
+ mode_number: int | None = None) -> None:
543
543
 
544
544
  if XYZ:
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]
@@ -577,7 +584,10 @@ class PVDisplay(BaseDisplay):
577
584
  k0 = port.get_mode(0).k0
578
585
  else:
579
586
  k0 = 1
580
- port.selected_mode = mode_number
587
+
588
+ if isinstance(mode_number, int):
589
+ port.selected_mode = mode_number
590
+
581
591
  F = port.port_mode_3d_global(xf,yf,zf,k0, which=field)
582
592
 
583
593
  Fx = F[0,:].reshape(X.shape).T
@@ -680,6 +690,87 @@ class PVDisplay(BaseDisplay):
680
690
  self._objs.append(_AnimObject(field_flat, T, grid, grid_no_nan, actor, on_update))
681
691
 
682
692
  self._reset_cbar()
693
+
694
+ def add_boundary_field(self,
695
+ selection: FaceSelection,
696
+ field: tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray],
697
+ scale: Literal['lin','log','symlog'] = 'lin',
698
+ cmap: cmap_names | None = None,
699
+ clim: tuple[float, float] | None = None,
700
+ opacity: float = 1.0,
701
+ symmetrize: bool = False,
702
+ _fieldname: str | None = None,
703
+ **kwargs,):
704
+ """Add a surface plot to the display based on a boundary surface
705
+
706
+ The X,Y,Z coordinates must be a 2D grid of data points. The field must be a real field with the same size.
707
+
708
+ Example:
709
+ >>> display.add_boundary_field(selection, field.boundary(selction).scalar('normE','real'))
710
+
711
+ Args:
712
+ selection (FaceSelection): The boundary to show the field on
713
+ field (tuple[np.ndarray]): The output of EMField().boundary().scalar()
714
+ scale (Literal["lin","log","symlog"], optional): The colormap scaling¹. Defaults to 'lin'.
715
+ cmap (cmap_names, optional): The colormap. Defaults to 'coolwarm'.
716
+ clim (tuple[float, float], optional): Specific color limits (min, max). Defaults to None.
717
+ opacity (float, optional): The opacity of the surface. Defaults to 1.0.
718
+ symmetrize (bool, optional): Wether to force a symmetrical color limit (-A,A). Defaults to True.
719
+
720
+ (¹): lin: f(x)=x, log: f(x)=log₁₀(|x|), symlog: f(x)=sgn(x)·log₁₀(1+|x·ln(10)|)
721
+ """
722
+
723
+ grid = self.mesh_surface(selection)
724
+
725
+ field = field[3]
726
+ field_flat = field.flatten(order='F')
727
+
728
+ if scale=='log':
729
+ T = lambda x: np.log10(np.abs(x+1e-12))
730
+ elif scale=='symlog':
731
+ T = lambda x: np.sign(x) * np.log10(1 + np.abs(x*np.log(10)))
732
+ else:
733
+ T = lambda x: x
734
+
735
+ static_field = T(np.real(field_flat))
736
+
737
+ if _fieldname is None:
738
+ name = 'anim'+str(self._ctr)
739
+ else:
740
+ name = _fieldname
741
+ self._ctr += 1
742
+
743
+ grid[name] = static_field
744
+
745
+ default_cmap = EMERGE_AMP
746
+ # Determine color limits
747
+ if clim is None:
748
+ if self._cbar_lim is not None:
749
+ clim = self._cbar_lim
750
+ else:
751
+ fmin = np.nanmin(static_field)
752
+ fmax = np.nanmax(static_field)
753
+ clim = (fmin, fmax)
754
+
755
+ if symmetrize:
756
+ lim = max(abs(clim[0]), abs(clim[1]))
757
+ clim = (-lim, lim)
758
+ default_cmap = EMERGE_WAVE
759
+
760
+ if cmap is None:
761
+ cmap = default_cmap
762
+
763
+ kwargs = setdefault(kwargs, cmap=cmap, clim=clim, opacity=opacity, pickable=False, multi_colors=True)
764
+ actor = self._plot.add_mesh(grid, scalars=name, scalar_bar_args=self._cbar_args, **kwargs)
765
+
766
+ if self._do_animate:
767
+ def on_update(obj: _AnimObject, phi: complex):
768
+ field_anim = obj.T(np.real(obj.field * phi))
769
+ obj.grid[name] = field_anim
770
+ #obj.fgrid replace with thresholded scalar data.
771
+ self._objs.append(_AnimObject(field_flat, T, grid, grid, actor, on_update))
772
+
773
+ self._reset_cbar()
683
774
 
684
775
  def add_title(self, title: str) -> None:
685
776
  """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
@@ -547,7 +547,7 @@ class Simulation:
547
547
  self._set_mesh(dataset['mesh'])
548
548
  return self
549
549
 
550
- def generate_mesh(self) -> None:
550
+ def generate_mesh(self, regenerate: bool = False) -> None:
551
551
  """Generate the mesh.
552
552
  This can only be done after commit_geometry(...) is called and if frequencies are defined.
553
553
 
@@ -557,26 +557,27 @@ class Simulation:
557
557
  Raises:
558
558
  ValueError: ValueError if no frequencies are defined.
559
559
  """
560
- logger.trace('Starting mesh generation phase.')
561
- if not self._defined_geometries:
562
- self.commit_geometry()
563
-
564
- logger.trace(' (1) Installing periodic boundaries in mesher.')
565
- # Set the cell periodicity in GMSH
566
- if self._cell is not None:
567
- self.mesher.set_periodic_cell(self._cell)
568
-
569
- self.mw._initialize_bcs(_GEOMANAGER.get_surfaces())
560
+ if not regenerate:
561
+ logger.trace('Starting mesh generation phase.')
562
+ if not self._defined_geometries:
563
+ self.commit_geometry()
564
+
565
+ logger.trace(' (1) Installing periodic boundaries in mesher.')
566
+ # Set the cell periodicity in GMSH
567
+ if self._cell is not None:
568
+ self.mesher.set_periodic_cell(self._cell)
569
+
570
+ self.mw._initialize_bcs(_GEOMANAGER.get_surfaces())
570
571
 
571
- # Check if frequencies are defined: TODO: Replace with a more generic check
572
- if self.mw.frequencies is None:
573
- raise ValueError('No frequencies defined for the simulation. Please set frequencies before generating the mesh.')
572
+ # Check if frequencies are defined: TODO: Replace with a more generic check
573
+ if self.mw.frequencies is None:
574
+ raise ValueError('No frequencies defined for the simulation. Please set frequencies before generating the mesh.')
574
575
 
575
576
  gmsh.model.occ.synchronize()
576
577
 
577
578
  # Set the mesh size
578
579
  self.mesher._configure_mesh_size(self.mw.get_discretizer(), self.mw.resolution)
579
-
580
+
580
581
  logger.trace(' (2) Calling GMSH mesher')
581
582
  try:
582
583
  gmsh.logger.start()
@@ -589,6 +590,7 @@ class Simulation:
589
590
  logger.error('GMSH Mesh error detected.')
590
591
  print(_GMSH_ERROR_TEXT)
591
592
  raise
593
+
592
594
  logger.info('GMSH Meshing complete!')
593
595
  self.mesh._pre_update(self.mesher._get_periodic_bcs())
594
596
  self.mesh.exterior_face_tags = self.mesher.domain_boundary_face_tags
@@ -596,6 +598,14 @@ class Simulation:
596
598
  self._set_mesh(self.mesh)
597
599
  logger.trace(' (3) Mesh routine complete')
598
600
 
601
+ def _reset_mesh(self):
602
+ #gmsh.clear()
603
+ gmsh.model.mesh.clear()
604
+ mesh = Mesh3D(self.mesher)
605
+
606
+ self.mw.reset(False)
607
+ self._set_mesh(mesh)
608
+
599
609
  def parameter_sweep(self, clear_mesh: bool = True, **parameters: np.ndarray) -> Generator[tuple[float,...], None, None]:
600
610
  """Executes a parameteric sweep iteration.
601
611
 
@@ -654,6 +664,67 @@ class Simulation:
654
664
 
655
665
  self.mw.cache_matrices = True
656
666
 
667
+
668
+ def _beta_adaptive_mesh_refinement(self,
669
+ max_steps: int = 6,
670
+ convergence: float = 0.02,
671
+ refinement_percentage: float = 0.1,
672
+ refinement_ratio: float = 0.3,
673
+ growth_rate: float = 3) -> None:
674
+ """Beta implementation of Adaptive Mesh Refinement
675
+
676
+ Args:
677
+ max_steps (int, optional): _description_. Defaults to 6.
678
+ convergence (float, optional): _description_. Defaults to 0.02.
679
+ refinement_percentage (float, optional): _description_. Defaults to 0.1.
680
+ refinement_ratio (float, optional): _description_. Defaults to 0.3.
681
+ growth_rate (float, optional): _description_. Defaults to 3.
682
+ """
683
+ from .physics.microwave.adaptive_mesh import select_refinement_indices, reduce_point_set, compute_convergence
684
+
685
+ max_freq = np.max(self.mw.frequencies)
686
+
687
+ regenerate = False
688
+
689
+ Smats = []
690
+
691
+ for step in range(max_steps):
692
+ amr_params = dict(iter_step=step)
693
+ self.mw._params = amr_params
694
+ self.data.sim.new(**amr_params)
695
+
696
+
697
+ data = self.mw._run_adaptive_mesh(step, max_freq)
698
+
699
+ field = data.field[-1]
700
+
701
+ Smat_new = data.scalar[-1].Sp
702
+ Smats.append(Smat_new)
703
+ if step > 0:
704
+ S0 = Smats[-2]
705
+ S1 = Smats[-1]
706
+ conv = compute_convergence(S0, S1)
707
+ logger.info(f'Convergence = {conv}')
708
+ if conv < convergence:
709
+ logger.info('Mesh refinement passed!')
710
+ break
711
+ error, lengths = field._solution_quality()
712
+
713
+ idx = select_refinement_indices(error, refinement_percentage)
714
+
715
+ idx = idx[reduce_point_set(self.mw.mesh.centers[:,idx], growth_rate, lengths[idx], refinement_ratio)]
716
+ centers = self.mw.mesh.centers
717
+
718
+ self.mesher._reset_amr_points()
719
+ self._reset_mesh()
720
+ logger.debug(f'Adding {len(idx)} refinement points.')
721
+ for i in idx:
722
+ coord = centers[:,i]
723
+ size = lengths[i]
724
+ self.mesher.add_refinement_point(coord, refinement_ratio, size, growth_rate)
725
+
726
+ self.generate_mesh(True)
727
+ self.view(plot_mesh=True)
657
728
  def export(self, filename: str):
658
729
  """Exports the model or mesh depending on the extension.
659
730