emerge 1.1.0__py3-none-any.whl → 1.1.1__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 -1
- emerge/_emerge/const.py +2 -1
- emerge/_emerge/elements/ned2_interp.py +122 -42
- emerge/_emerge/geo/__init__.py +1 -1
- emerge/_emerge/geo/operations.py +20 -0
- emerge/_emerge/geo/pcb.py +34 -16
- emerge/_emerge/geo/shapes.py +1 -0
- emerge/_emerge/geo/step.py +177 -41
- emerge/_emerge/geometry.py +166 -19
- emerge/_emerge/material.py +2 -0
- emerge/_emerge/mesh3d.py +1 -3
- emerge/_emerge/mesher.py +33 -9
- emerge/_emerge/mth/common_functions.py +1 -1
- emerge/_emerge/mth/optimized.py +2 -2
- emerge/_emerge/physics/microwave/adaptive_mesh.py +437 -95
- emerge/_emerge/physics/microwave/assembly/assembler.py +9 -1
- emerge/_emerge/physics/microwave/microwave_3d.py +2 -2
- emerge/_emerge/physics/microwave/microwave_bc.py +1 -0
- emerge/_emerge/physics/microwave/microwave_data.py +94 -5
- emerge/_emerge/plot/pyvista/display.py +10 -7
- emerge/_emerge/selection.py +17 -2
- emerge/_emerge/simmodel.py +76 -25
- emerge/_emerge/solver.py +3 -3
- {emerge-1.1.0.dist-info → emerge-1.1.1.dist-info}/METADATA +1 -1
- {emerge-1.1.0.dist-info → emerge-1.1.1.dist-info}/RECORD +28 -28
- {emerge-1.1.0.dist-info → emerge-1.1.1.dist-info}/WHEEL +0 -0
- {emerge-1.1.0.dist-info → emerge-1.1.1.dist-info}/entry_points.txt +0 -0
- {emerge-1.1.0.dist-info → emerge-1.1.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -273,13 +273,17 @@ class Assembler:
|
|
|
273
273
|
|
|
274
274
|
logger.debug('Implementing PEC Boundary Conditions.')
|
|
275
275
|
pec_ids: list[int] = []
|
|
276
|
+
pec_tris: list[int] = []
|
|
276
277
|
|
|
277
278
|
# Conductivity above al imit, consider it all PEC
|
|
278
279
|
ipec = 0
|
|
280
|
+
|
|
279
281
|
for itet in range(field.n_tets):
|
|
280
282
|
if cond[0,0,itet] > self.settings.mw_3d_peclim:
|
|
281
283
|
ipec+=1
|
|
282
284
|
pec_ids.extend(field.tet_to_field[:,itet])
|
|
285
|
+
for tri in field.mesh.tet_to_tri[:,itet]:
|
|
286
|
+
pec_tris.append(tri)
|
|
283
287
|
if ipec>0:
|
|
284
288
|
logger.trace(f'Extended PEC with {ipec} tets with a conductivity > {self.settings.mw_3d_peclim}.')
|
|
285
289
|
|
|
@@ -295,9 +299,12 @@ class Assembler:
|
|
|
295
299
|
eids = field.edge_to_field[:, ii]
|
|
296
300
|
pec_ids.extend(list(eids))
|
|
297
301
|
|
|
302
|
+
|
|
298
303
|
for ii in tri_ids:
|
|
299
304
|
tids = field.tri_to_field[:, ii]
|
|
300
305
|
pec_ids.extend(list(tids))
|
|
306
|
+
|
|
307
|
+
pec_tris.extend(tri_ids)
|
|
301
308
|
|
|
302
309
|
|
|
303
310
|
############################################################
|
|
@@ -412,7 +419,8 @@ class Assembler:
|
|
|
412
419
|
|
|
413
420
|
simjob.port_vectors = port_vectors
|
|
414
421
|
simjob.solve_ids = solve_ids
|
|
415
|
-
|
|
422
|
+
simjob._pec_tris = pec_tris
|
|
423
|
+
|
|
416
424
|
if has_periodic:
|
|
417
425
|
simjob.P = Pmat
|
|
418
426
|
simjob.Pd = Pmat.getH()
|
|
@@ -876,7 +876,7 @@ class Microwave3D:
|
|
|
876
876
|
def _run_adaptive_mesh(self,
|
|
877
877
|
iteration: int,
|
|
878
878
|
frequency: float,
|
|
879
|
-
automatic_modal_analysis: bool = True) -> MWData:
|
|
879
|
+
automatic_modal_analysis: bool = True) -> tuple[MWData, list[int]]:
|
|
880
880
|
"""Executes a frequency domain study
|
|
881
881
|
|
|
882
882
|
The study is distributed over "n_workers" workers.
|
|
@@ -965,7 +965,7 @@ class Microwave3D:
|
|
|
965
965
|
self.solveroutine.reset()
|
|
966
966
|
### Compute S-parameters and return
|
|
967
967
|
self._post_process([job,], [mats,])
|
|
968
|
-
return self.data
|
|
968
|
+
return self.data, job._pec_tris
|
|
969
969
|
|
|
970
970
|
def eigenmode(self, search_frequency: float,
|
|
971
971
|
nmodes: int = 6,
|
|
@@ -773,6 +773,7 @@ class RectangularWaveguide(PortBC):
|
|
|
773
773
|
logger.info(' - Constructing coordinate system from normal port')
|
|
774
774
|
self.cs = Axis(self.selection.normal).construct_cs()
|
|
775
775
|
logger.debug(f' - Port CS: {self.cs}')
|
|
776
|
+
|
|
776
777
|
def get_basis(self) -> np.ndarray:
|
|
777
778
|
return self.cs._basis
|
|
778
779
|
|
|
@@ -667,13 +667,51 @@ class MWField:
|
|
|
667
667
|
return sum([self.excitation[mode.port_number]*self._fields[mode.port_number] for mode in self.port_modes]) # type: ignore
|
|
668
668
|
|
|
669
669
|
def set_field_vector(self) -> None:
|
|
670
|
-
"""Defines the default excitation coefficients for the current dataset"""
|
|
670
|
+
"""Defines the default excitation coefficients for the current dataset as an excitation of only port 1."""
|
|
671
671
|
self.excitation = {key: 0.0 for key in self._fields.keys()}
|
|
672
672
|
self.excitation[self.port_modes[0].port_number] = 1.0 + 0j
|
|
673
673
|
|
|
674
|
-
def excite_port(self, number: int) -> None:
|
|
674
|
+
def excite_port(self, number: int, excitation: complex = 1.0 + 0.0j) -> None:
|
|
675
|
+
"""Excite a single port provided by a given port number
|
|
676
|
+
|
|
677
|
+
Args:
|
|
678
|
+
number (int): The port number to excite
|
|
679
|
+
coefficient (complex): The port excitation. Defaults to 1.0 + 0.0j
|
|
680
|
+
"""
|
|
681
|
+
self.excitation = {key: 0.0 for key in self._fields.keys()}
|
|
682
|
+
self.excitation[self.port_modes[number-1].port_number] = excitation
|
|
683
|
+
|
|
684
|
+
def set_excitations(self, *excitations: complex) -> None:
|
|
685
|
+
"""Set bulk port excitations by an ordered array of excitation coefficients.
|
|
686
|
+
|
|
687
|
+
Returns:
|
|
688
|
+
*complex: A sequence of complex numbers
|
|
689
|
+
"""
|
|
675
690
|
self.excitation = {key: 0.0 for key in self._fields.keys()}
|
|
676
|
-
|
|
691
|
+
for iport, coeff in enumerate(excitations):
|
|
692
|
+
self.excitation[self.port_modes[iport].port_number] = coeff
|
|
693
|
+
|
|
694
|
+
def combine_ports(self, p1: int, p2: int) -> MWField:
|
|
695
|
+
"""Combines ports p1 and p2 into a cifferential and common mode port respectively.
|
|
696
|
+
|
|
697
|
+
The p1 index becomes the differential mode port
|
|
698
|
+
The p2 index becomes the common mode port
|
|
699
|
+
|
|
700
|
+
Args:
|
|
701
|
+
p1 (int): The first port number
|
|
702
|
+
p2 (int): The second port number
|
|
703
|
+
|
|
704
|
+
Returns:
|
|
705
|
+
MWField: _description_
|
|
706
|
+
"""
|
|
707
|
+
|
|
708
|
+
fp1 = self._fields[p1]
|
|
709
|
+
fp2 = self._fields[p2]
|
|
710
|
+
|
|
711
|
+
self._fields[p1] = (fp1-fp2)/np.sqrt(2)
|
|
712
|
+
self._fields[p2] = (fp1+fp2)/np.sqrt(2)
|
|
713
|
+
|
|
714
|
+
return self
|
|
677
715
|
|
|
678
716
|
@property
|
|
679
717
|
def EH(self) -> tuple[np.ndarray, np.ndarray]:
|
|
@@ -720,7 +758,9 @@ class MWField:
|
|
|
720
758
|
xf = xs.flatten()
|
|
721
759
|
yf = ys.flatten()
|
|
722
760
|
zf = zs.flatten()
|
|
761
|
+
logger.debug(f'Interpolating {xf.shape[0]} field points')
|
|
723
762
|
Ex, Ey, Ez = self.basis.interpolate(self._field, xf, yf, zf, usenan=usenan)
|
|
763
|
+
logger.debug('E Interpolation complete')
|
|
724
764
|
self.Ex = Ex.reshape(shp)
|
|
725
765
|
self.Ey = Ey.reshape(shp)
|
|
726
766
|
self.Ez = Ez.reshape(shp)
|
|
@@ -728,6 +768,7 @@ class MWField:
|
|
|
728
768
|
|
|
729
769
|
constants = 1/ (-1j*2*np.pi*self.freq*(self._dur*MU0) )
|
|
730
770
|
Hx, Hy, Hz = self.basis.interpolate_curl(self._field, xf, yf, zf, constants, usenan=usenan)
|
|
771
|
+
logger.debug('H Interpolation complete')
|
|
731
772
|
ids = self.basis.interpolate_index(xf, yf, zf)
|
|
732
773
|
|
|
733
774
|
self.er = self._der[ids].reshape(shp)
|
|
@@ -743,10 +784,10 @@ class MWField:
|
|
|
743
784
|
|
|
744
785
|
return field
|
|
745
786
|
|
|
746
|
-
def _solution_quality(self) -> tuple[np.ndarray, np.ndarray]:
|
|
787
|
+
def _solution_quality(self, solve_ids: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
|
|
747
788
|
from .adaptive_mesh import compute_error_estimate
|
|
748
789
|
|
|
749
|
-
error_tet, max_elem_size = compute_error_estimate(self)
|
|
790
|
+
error_tet, max_elem_size = compute_error_estimate(self, solve_ids)
|
|
750
791
|
return error_tet, max_elem_size
|
|
751
792
|
|
|
752
793
|
def boundary(self,
|
|
@@ -1146,6 +1187,54 @@ class MWScalarNdim:
|
|
|
1146
1187
|
def S(self, i1: int, i2: int) -> np.ndarray:
|
|
1147
1188
|
return self.Sp[...,self._portmap[i1], self._portmap[i2]]
|
|
1148
1189
|
|
|
1190
|
+
def combine_ports(self, p1: int, p2: int) -> MWScalarNdim:
|
|
1191
|
+
"""Combine ports p1 and p2 into a differential and common mode port respectively.
|
|
1192
|
+
|
|
1193
|
+
The p1 index becomes the differential mode port
|
|
1194
|
+
The p2 index becomes the common mode port
|
|
1195
|
+
|
|
1196
|
+
Args:
|
|
1197
|
+
p1 (int): The first port number
|
|
1198
|
+
p2 (int): The second port number
|
|
1199
|
+
|
|
1200
|
+
Returns:
|
|
1201
|
+
MWScalarNdim: _description_
|
|
1202
|
+
"""
|
|
1203
|
+
if p1==p2:
|
|
1204
|
+
raise ValueError('p1 and p2 must be different port numbers')
|
|
1205
|
+
|
|
1206
|
+
F, N, _ = self.Sp.shape
|
|
1207
|
+
p1 = p1-1
|
|
1208
|
+
p2 = p2-1
|
|
1209
|
+
|
|
1210
|
+
if not (0 <= p1 < N and 0 <= p2 < N):
|
|
1211
|
+
raise IndexError(f'Ports {p1+1} or {p2+1} are out of range {N}')
|
|
1212
|
+
|
|
1213
|
+
Sout = self.Sp.copy()
|
|
1214
|
+
ii, jj = p1, p2
|
|
1215
|
+
idx = np.ones(N, dtype=np.bool)
|
|
1216
|
+
idx[[ii,jj]] = False
|
|
1217
|
+
others = np.nonzero(idx)[0]
|
|
1218
|
+
isqrt2 = 1.0 / np.sqrt(2.0)
|
|
1219
|
+
|
|
1220
|
+
Sout[:, others, ii] = (self.Sp[:, others, ii] - self.Sp[:, others, jj]) * isqrt2
|
|
1221
|
+
Sout[:, others, jj] = (self.Sp[:, others, ii] + self.Sp[:, others, jj]) * isqrt2
|
|
1222
|
+
Sout[:, ii, others] = (self.Sp[:, ii, others] - self.Sp[:, jj, others]) * isqrt2
|
|
1223
|
+
Sout[:, jj, others] = (self.Sp[:, ii, others] + self.Sp[:, jj, others]) * isqrt2
|
|
1224
|
+
|
|
1225
|
+
Sii = self.Sp[:, ii, ii]
|
|
1226
|
+
Sij = self.Sp[:, ii, jj]
|
|
1227
|
+
Sji = self.Sp[:, jj, ii]
|
|
1228
|
+
Sjj = self.Sp[:, jj, jj]
|
|
1229
|
+
|
|
1230
|
+
Sout[:, ii, ii] = 0.5 *(Sii - Sij - Sji + Sjj)
|
|
1231
|
+
Sout[:, ii, jj] = 0.5 *(Sii + Sij - Sji - Sjj)
|
|
1232
|
+
Sout[:, jj, ii] = 0.5 *(Sii - Sij + Sji - Sjj)
|
|
1233
|
+
Sout[:, jj, jj] = 0.5 *(Sii + Sij + Sji + Sjj)
|
|
1234
|
+
|
|
1235
|
+
self.Sp = Sout
|
|
1236
|
+
|
|
1237
|
+
return self
|
|
1149
1238
|
@property
|
|
1150
1239
|
def Smat(self) -> np.ndarray:
|
|
1151
1240
|
"""Returns the full S-matrix
|
|
@@ -242,6 +242,7 @@ class PVDisplay(BaseDisplay):
|
|
|
242
242
|
self._stop: bool = False
|
|
243
243
|
self._objs: list[_AnimObject] = []
|
|
244
244
|
self._do_animate: bool = False
|
|
245
|
+
self._animate_next: bool = False
|
|
245
246
|
self._closed_via_x: bool = False
|
|
246
247
|
self._Nsteps: int = 0
|
|
247
248
|
self._fps: int = 25
|
|
@@ -334,6 +335,7 @@ class PVDisplay(BaseDisplay):
|
|
|
334
335
|
self._plot = pv.Plotter()
|
|
335
336
|
self._stop = False
|
|
336
337
|
self._objs = []
|
|
338
|
+
self._animate_next = False
|
|
337
339
|
C_CYCLE = _gen_c_cycle()
|
|
338
340
|
|
|
339
341
|
def _close_callback(self, arg):
|
|
@@ -403,6 +405,7 @@ class PVDisplay(BaseDisplay):
|
|
|
403
405
|
print('If you closed the animation without using (Q) press Ctrl+C to kill the process.')
|
|
404
406
|
self._Nsteps = Nsteps
|
|
405
407
|
self._fps = fps
|
|
408
|
+
self._animate_next = True
|
|
406
409
|
self._do_animate = True
|
|
407
410
|
return self
|
|
408
411
|
|
|
@@ -518,7 +521,7 @@ class PVDisplay(BaseDisplay):
|
|
|
518
521
|
|
|
519
522
|
self._plot.add_mesh(self._volume_edges(_select(obj)), color='#000000', line_width=2, show_edges=True)
|
|
520
523
|
|
|
521
|
-
if
|
|
524
|
+
if label:
|
|
522
525
|
points = []
|
|
523
526
|
labels = []
|
|
524
527
|
for dt in obj.dimtags:
|
|
@@ -691,14 +694,14 @@ class PVDisplay(BaseDisplay):
|
|
|
691
694
|
actor = self._plot.add_mesh(grid_no_nan, scalars=name, scalar_bar_args=self._cbar_args, **kwargs)
|
|
692
695
|
|
|
693
696
|
|
|
694
|
-
if self.
|
|
697
|
+
if self._animate_next:
|
|
695
698
|
def on_update(obj: _AnimObject, phi: complex):
|
|
696
699
|
field_anim = obj.T(np.real(obj.field * phi))
|
|
697
700
|
obj.grid[name] = field_anim
|
|
698
701
|
obj.fgrid[name] = obj.grid.threshold(scalars=name)[name]
|
|
699
702
|
#obj.fgrid replace with thresholded scalar data.
|
|
700
703
|
self._objs.append(_AnimObject(field_flat, T, grid, grid_no_nan, actor, on_update))
|
|
701
|
-
|
|
704
|
+
self._animate_next = False
|
|
702
705
|
self._reset_cbar()
|
|
703
706
|
|
|
704
707
|
def add_boundary_field(self,
|
|
@@ -773,13 +776,13 @@ class PVDisplay(BaseDisplay):
|
|
|
773
776
|
kwargs = setdefault(kwargs, cmap=cmap, clim=clim, opacity=opacity, pickable=False, multi_colors=True)
|
|
774
777
|
actor = self._plot.add_mesh(grid, scalars=name, scalar_bar_args=self._cbar_args, **kwargs)
|
|
775
778
|
|
|
776
|
-
if self.
|
|
779
|
+
if self._animate_next:
|
|
777
780
|
def on_update(obj: _AnimObject, phi: complex):
|
|
778
781
|
field_anim = obj.T(np.real(obj.field * phi))
|
|
779
782
|
obj.grid[name] = field_anim
|
|
780
783
|
#obj.fgrid replace with thresholded scalar data.
|
|
781
784
|
self._objs.append(_AnimObject(field_flat, T, grid, grid, actor, on_update))
|
|
782
|
-
|
|
785
|
+
self._animate_next = False
|
|
783
786
|
self._reset_cbar()
|
|
784
787
|
|
|
785
788
|
def add_title(self, title: str) -> None:
|
|
@@ -921,7 +924,7 @@ class PVDisplay(BaseDisplay):
|
|
|
921
924
|
|
|
922
925
|
actor = self._plot.add_mesh(contour, opacity=opacity, cmap=cmap, clim=clim, pickable=False, scalar_bar_args=self._cbar_args)
|
|
923
926
|
|
|
924
|
-
if self.
|
|
927
|
+
if self._animate_next:
|
|
925
928
|
def on_update(obj: _AnimObject, phi: complex):
|
|
926
929
|
new_vals = obj.T(np.real(obj.field * phi))
|
|
927
930
|
obj.grid['anim'] = new_vals
|
|
@@ -929,7 +932,7 @@ class PVDisplay(BaseDisplay):
|
|
|
929
932
|
obj.actor.GetMapper().SetInputData(new_contour) # type: ignore
|
|
930
933
|
|
|
931
934
|
self._objs.append(_AnimObject(field, T, grid, None, actor, on_update)) # type: ignore
|
|
932
|
-
|
|
935
|
+
self._animate_next = False
|
|
933
936
|
self._reset_cbar()
|
|
934
937
|
|
|
935
938
|
def _add_aux_items(self) -> None:
|
emerge/_emerge/selection.py
CHANGED
|
@@ -21,6 +21,9 @@ import numpy as np
|
|
|
21
21
|
from .cs import Axis, CoordinateSystem, _parse_vector, Plane
|
|
22
22
|
from typing import Callable, TypeVar, Iterable, Any
|
|
23
23
|
|
|
24
|
+
class SelectionError(Exception):
|
|
25
|
+
pass
|
|
26
|
+
|
|
24
27
|
def align_rectangle_frame(pts3d: np.ndarray, normal: np.ndarray) -> dict[str, Any]:
|
|
25
28
|
"""Tries to find a rectangle as convex-hull of a set of points with a given normal vector.
|
|
26
29
|
|
|
@@ -174,7 +177,7 @@ class Selection:
|
|
|
174
177
|
"""
|
|
175
178
|
dim: int = -1
|
|
176
179
|
def __init__(self, tags: list[int] | set[int] | tuple[int] | None = None):
|
|
177
|
-
|
|
180
|
+
self.name: str = 'Selection'
|
|
178
181
|
self._tags: set[int] = set()
|
|
179
182
|
|
|
180
183
|
if tags is not None:
|
|
@@ -252,7 +255,19 @@ class Selection:
|
|
|
252
255
|
maxy = max(maxy, y1)
|
|
253
256
|
maxz = max(maxz, z1)
|
|
254
257
|
return (minx, miny, minz), (maxx, maxy, maxz)
|
|
255
|
-
|
|
258
|
+
|
|
259
|
+
def _named(self, name: str) -> Selection:
|
|
260
|
+
"""Sets the name of the selection and returns it
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
name (str): The name of the selection
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
Selection: The same selection object
|
|
267
|
+
"""
|
|
268
|
+
self.name = name
|
|
269
|
+
return self
|
|
270
|
+
|
|
256
271
|
def exclude(self, xyz_excl_function: Callable = lambda x,y,z: True, plane: Plane | None = None, axis: Axis | None = None) -> Selection:
|
|
257
272
|
"""Exclude points by evaluating a function(x,y,z)-> bool
|
|
258
273
|
|
emerge/_emerge/simmodel.py
CHANGED
|
@@ -458,7 +458,8 @@ class Simulation:
|
|
|
458
458
|
plot_mesh: bool = False,
|
|
459
459
|
volume_mesh: bool = True,
|
|
460
460
|
opacity: float | None = None,
|
|
461
|
-
labels: bool = False
|
|
461
|
+
labels: bool = False,
|
|
462
|
+
face_labels: bool = False) -> None:
|
|
462
463
|
"""View the current geometry in either the BaseDisplay object (PVDisplay only) or
|
|
463
464
|
the GMSH viewer.
|
|
464
465
|
|
|
@@ -476,8 +477,13 @@ class Simulation:
|
|
|
476
477
|
return
|
|
477
478
|
for geo in self.state.current_geo_state:
|
|
478
479
|
self.display.add_object(geo, mesh=plot_mesh, opacity=opacity, volume_mesh=volume_mesh, label=labels)
|
|
480
|
+
|
|
481
|
+
if face_labels and geo.dim==3:
|
|
482
|
+
for face_name in geo._face_pointers.keys():
|
|
483
|
+
self.display.add_object(geo.face(face_name), color='yellow', opacity=0.1, label=face_name)
|
|
479
484
|
if selections:
|
|
480
|
-
[self.display.add_object(sel, color='red', opacity=0.6, label=
|
|
485
|
+
[self.display.add_object(sel, color='red', opacity=0.6, label=sel.name) for sel in selections]
|
|
486
|
+
|
|
481
487
|
self.display.show()
|
|
482
488
|
|
|
483
489
|
return None
|
|
@@ -688,9 +694,9 @@ class SimulationBeta(Simulation):
|
|
|
688
694
|
|
|
689
695
|
|
|
690
696
|
def __post_init__(self):
|
|
691
|
-
|
|
692
|
-
self.mesher.set_algorithm(Algorithm3D.HXT)
|
|
693
|
-
logger.debug('Setting mesh algorithm to HXT')
|
|
697
|
+
pass
|
|
698
|
+
#self.mesher.set_algorithm(Algorithm3D.HXT)
|
|
699
|
+
#logger.debug('Setting mesh algorithm to HXT')
|
|
694
700
|
|
|
695
701
|
|
|
696
702
|
def _reset_mesh(self):
|
|
@@ -706,10 +712,10 @@ class SimulationBeta(Simulation):
|
|
|
706
712
|
convergence: float = 0.02,
|
|
707
713
|
magnitude_convergence: float = 2.0,
|
|
708
714
|
phase_convergence: float = 180,
|
|
709
|
-
refinement_ratio: float = 0.
|
|
710
|
-
growth_rate: float =
|
|
715
|
+
refinement_ratio: float = 0.6,
|
|
716
|
+
growth_rate: float = 2,
|
|
711
717
|
minimum_refinement_percentage: float = 20.0,
|
|
712
|
-
error_field_inclusion_percentage: float =
|
|
718
|
+
error_field_inclusion_percentage: float = 60.0,
|
|
713
719
|
frequency: float = None,
|
|
714
720
|
show_mesh: bool = False) -> SimulationDataset:
|
|
715
721
|
""" A beta-version of adaptive mesh refinement.
|
|
@@ -736,7 +742,7 @@ class SimulationBeta(Simulation):
|
|
|
736
742
|
SimulationDataset: _description_
|
|
737
743
|
"""
|
|
738
744
|
from .physics.microwave.adaptive_mesh import select_refinement_indices, reduce_point_set, compute_convergence
|
|
739
|
-
|
|
745
|
+
from collections import defaultdict
|
|
740
746
|
|
|
741
747
|
max_freq = np.max(self.mw.frequencies)
|
|
742
748
|
|
|
@@ -752,11 +758,18 @@ class SimulationBeta(Simulation):
|
|
|
752
758
|
|
|
753
759
|
self.state.stash()
|
|
754
760
|
|
|
761
|
+
a0 = 0.26
|
|
762
|
+
c0 = 0.75
|
|
763
|
+
x0 = 12
|
|
764
|
+
q0 = (1-a0)*2/np.pi
|
|
765
|
+
b0 = np.tan((c0-a0)/q0)/x0
|
|
766
|
+
q0 = (0.8-a0)*2/np.pi
|
|
767
|
+
|
|
755
768
|
for step in range(1,max_steps+1):
|
|
756
769
|
|
|
757
770
|
self.data.sim.new(iter_step=step)
|
|
758
771
|
|
|
759
|
-
data = self.mw._run_adaptive_mesh(step, max_freq)
|
|
772
|
+
data, solve_ids = self.mw._run_adaptive_mesh(step, max_freq)
|
|
760
773
|
|
|
761
774
|
field = data.field[-1]
|
|
762
775
|
|
|
@@ -775,51 +788,89 @@ class SimulationBeta(Simulation):
|
|
|
775
788
|
passed = 0
|
|
776
789
|
|
|
777
790
|
if passed >= min_refined_passes:
|
|
778
|
-
logger.info('Adaptive mesh refinement successfull')
|
|
791
|
+
logger.info(f'Adaptive mesh refinement successfull with {self.mesh.n_tets} tetrahedra.')
|
|
779
792
|
break
|
|
780
793
|
|
|
781
|
-
error, lengths = field._solution_quality()
|
|
794
|
+
error, lengths = field._solution_quality(solve_ids)
|
|
782
795
|
|
|
783
796
|
idx = select_refinement_indices(error, error_field_inclusion_percentage/100)
|
|
784
797
|
idx = idx[::-1]
|
|
785
798
|
|
|
786
|
-
|
|
799
|
+
npts = idx.shape[0]
|
|
800
|
+
np_percentage = npts/self.mesh.n_tets * 100
|
|
801
|
+
|
|
802
|
+
refinement_ratio = (a0 + np.arctan(b0*np_percentage)*q0)
|
|
803
|
+
#calc_refinement_ratio(refinement_throttle, point_percentage)
|
|
804
|
+
logger.info(f'Adding {npts} refinement points with a ratio: {refinement_ratio}')
|
|
805
|
+
|
|
806
|
+
# tet_nodes = defaultdict(lambda: 1000.)
|
|
807
|
+
# for itet in idx:
|
|
808
|
+
# for inode in self.mesh.tets[:,itet]:
|
|
809
|
+
# tet_nodes[inode] = min(tet_nodes[inode], lengths[itet])
|
|
810
|
+
|
|
811
|
+
# N = len(tet_nodes)
|
|
812
|
+
# coords = np.zeros((3,N), dtype=np.float64)
|
|
813
|
+
# sizes = np.zeros((N,), dtype=np.float64)
|
|
814
|
+
# for i, (index, size) in enumerate(tet_nodes.items()):
|
|
815
|
+
# coords[:,i] = self.mesh.nodes[:,index]
|
|
816
|
+
# sizes[i] = size
|
|
817
|
+
|
|
818
|
+
# self.mesher.add_refinement_points(coords, sizes, refinement_ratio*np.ones((N,)))
|
|
819
|
+
|
|
820
|
+
self.mesher.add_refinement_points(self.mw.mesh.centers[:,idx], lengths[idx], refinement_ratio*np.ones_like(lengths[idx]))
|
|
787
821
|
|
|
788
822
|
logger.debug(f'Pass {step}: Adding {len(idx)} new refinement points.')
|
|
789
823
|
|
|
790
824
|
new_ids = reduce_point_set(self.mesher._amr_coords, growth_rate, self.mesher._amr_sizes, refinement_ratio, 0.20)
|
|
791
825
|
|
|
792
|
-
|
|
826
|
+
nremoved = self.mesher._amr_coords.shape[1] - len(new_ids)
|
|
827
|
+
if nremoved > 0:
|
|
828
|
+
logger.info(f'Cleanup of step {step}: Removing {nremoved} points.')
|
|
793
829
|
|
|
794
830
|
self.mesher._amr_coords = self.mesher._amr_coords[:,new_ids]
|
|
795
831
|
self.mesher._amr_sizes = self.mesher._amr_sizes[new_ids]
|
|
832
|
+
self.mesher._amr_ratios = self.mesher._amr_ratios[new_ids]
|
|
833
|
+
self.mesher._amr_new = self.mesher._amr_new[new_ids]
|
|
796
834
|
|
|
835
|
+
def clip(value: float):
|
|
836
|
+
return max(1.3, value)
|
|
837
|
+
|
|
838
|
+
logger.debug(f'Initial refinement ratio: {refinement_ratio}')
|
|
839
|
+
|
|
840
|
+
over = False
|
|
841
|
+
under = False
|
|
842
|
+
throttle = 1.0
|
|
797
843
|
|
|
798
844
|
while True:
|
|
799
845
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
logger.debug(f'Pass {step}: Adding {len(idx)} refinement points.')
|
|
803
|
-
|
|
804
|
-
self.mesher.set_refinement_function(refinement_ratio, growth_rate, 1.0)
|
|
846
|
+
if over and under:
|
|
847
|
+
throttle *= 2
|
|
805
848
|
|
|
849
|
+
self._reset_mesh()
|
|
850
|
+
logger.debug(f'Pass {step}')
|
|
851
|
+
self.mesher.set_refinement_function(growth_rate, 2.0)
|
|
806
852
|
self.generate_mesh(True)
|
|
807
|
-
|
|
808
853
|
percentage = (self.mesh.n_tets/last_n_tets - 1) * 100
|
|
809
854
|
logger.info(f'Pass {step}: New mesh has {self.mesh.n_tets} (+{percentage:.1f}%) tetrahedra.')
|
|
810
855
|
|
|
811
856
|
if percentage < minimum_refinement_percentage:
|
|
857
|
+
F = (2*minimum_refinement_percentage-percentage)/(throttle*minimum_refinement_percentage)
|
|
812
858
|
logger.debug('Not enough mesh refinement, decreasing mesh size constraint.')
|
|
813
|
-
refinement_ratio =
|
|
859
|
+
refinement_ratio = self.mesher.refine_finer(F)
|
|
814
860
|
logger.debug(f'New refinement ratio: {refinement_ratio}')
|
|
861
|
+
under=True
|
|
815
862
|
continue
|
|
816
|
-
|
|
817
|
-
|
|
863
|
+
if percentage > (minimum_refinement_percentage*2):
|
|
864
|
+
F = (percentage - minimum_refinement_percentage)/(throttle*minimum_refinement_percentage)
|
|
818
865
|
logger.debug('Too much mesh refinement, decreasing mesh size constraint.')
|
|
819
|
-
refinement_ratio =
|
|
866
|
+
refinement_ratio = self.mesher.refine_coarser(F)
|
|
820
867
|
logger.debug(f'New refinement ratio: {refinement_ratio}')
|
|
868
|
+
over=True
|
|
869
|
+
continue
|
|
821
870
|
|
|
822
|
-
|
|
871
|
+
over = False
|
|
872
|
+
under = False
|
|
873
|
+
throttle = 1.0
|
|
823
874
|
last_n_tets = self.mesh.n_tets
|
|
824
875
|
break
|
|
825
876
|
|
emerge/_emerge/solver.py
CHANGED
|
@@ -209,7 +209,7 @@ def filter_real_modes(eigvals: np.ndarray, eigvecs: np.ndarray,
|
|
|
209
209
|
filtered_vals = eigvals[mask]
|
|
210
210
|
filtered_vecs = eigvecs[:, mask]
|
|
211
211
|
k0vals = np.sqrt(sign*filtered_vals)
|
|
212
|
-
order = np.argsort(np.abs(k0vals))# ascending distance
|
|
212
|
+
order = np.argsort(np.abs(k0vals)) # ascending distance
|
|
213
213
|
filtered_vals = filtered_vals[order] # reorder eigenvalues
|
|
214
214
|
filtered_vecs = filtered_vecs[:, order]
|
|
215
215
|
return filtered_vals, filtered_vecs
|
|
@@ -1282,7 +1282,7 @@ class SolveRoutine:
|
|
|
1282
1282
|
NF = A.shape[0]
|
|
1283
1283
|
NS = solve_ids.shape[0]
|
|
1284
1284
|
|
|
1285
|
-
logger.debug(self.pre + f'Removing {NF-NS} prescribed DOFs ({NS} left)')
|
|
1285
|
+
logger.debug(self.pre + f' Removing {NF-NS} prescribed DOFs ({NS} left)')
|
|
1286
1286
|
|
|
1287
1287
|
Asel = A[np.ix_(solve_ids, solve_ids)]
|
|
1288
1288
|
Bsel = B[np.ix_(solve_ids, solve_ids)]
|
|
@@ -1325,7 +1325,7 @@ class SolveRoutine:
|
|
|
1325
1325
|
NF = A.shape[0]
|
|
1326
1326
|
NS = solve_ids.shape[0]
|
|
1327
1327
|
|
|
1328
|
-
logger.debug(self.pre + f'Removing {NF-NS} prescribed DOFs ({NS} left)')
|
|
1328
|
+
logger.debug(self.pre + f' Removing {NF-NS} prescribed DOFs ({NS} left)')
|
|
1329
1329
|
|
|
1330
1330
|
Asel = A[np.ix_(solve_ids, solve_ids)]
|
|
1331
1331
|
Bsel = B[np.ix_(solve_ids, solve_ids)]
|