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.
- emerge/__init__.py +2 -2
- emerge/_emerge/elements/index_interp.py +45 -0
- emerge/_emerge/geo/horn.py +17 -1
- emerge/_emerge/geo/pcb.py +10 -6
- emerge/_emerge/geo/polybased.py +16 -1
- emerge/_emerge/geo/shapes.py +93 -1
- emerge/_emerge/geometry.py +22 -4
- emerge/_emerge/material.py +32 -3
- emerge/_emerge/mesh3d.py +33 -3
- emerge/_emerge/mesher.py +24 -5
- emerge/_emerge/physics/microwave/adaptive_mesh.py +576 -0
- emerge/_emerge/physics/microwave/assembly/assembler.py +7 -6
- emerge/_emerge/physics/microwave/assembly/curlcurl.py +78 -77
- emerge/_emerge/physics/microwave/microwave_3d.py +137 -21
- emerge/_emerge/physics/microwave/microwave_bc.py +27 -5
- emerge/_emerge/physics/microwave/microwave_data.py +32 -6
- emerge/_emerge/plot/pyvista/display.py +93 -2
- emerge/_emerge/simmodel.py +87 -16
- emerge/_emerge/solver.py +93 -73
- {emerge-1.0.5.dist-info → emerge-1.0.7.dist-info}/METADATA +3 -2
- {emerge-1.0.5.dist-info → emerge-1.0.7.dist-info}/RECORD +24 -23
- {emerge-1.0.5.dist-info → emerge-1.0.7.dist-info}/WHEEL +0 -0
- {emerge-1.0.5.dist-info → emerge-1.0.7.dist-info}/entry_points.txt +0 -0
- {emerge-1.0.5.dist-info → emerge-1.0.7.dist-info}/licenses/LICENSE +0 -0
|
@@ -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 =
|
|
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
|
-
|
|
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
|
emerge/_emerge/simmodel.py
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
561
|
-
|
|
562
|
-
self.
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
self.
|
|
568
|
-
|
|
569
|
-
|
|
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
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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
|
|