emerge 1.0.6__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 +1 -1
- emerge/_emerge/elements/index_interp.py +45 -0
- emerge/_emerge/material.py +1 -1
- emerge/_emerge/mesh3d.py +33 -3
- emerge/_emerge/mesher.py +22 -4
- emerge/_emerge/physics/microwave/adaptive_mesh.py +576 -0
- emerge/_emerge/physics/microwave/assembly/assembler.py +6 -5
- emerge/_emerge/physics/microwave/microwave_3d.py +125 -12
- emerge/_emerge/physics/microwave/microwave_bc.py +25 -3
- emerge/_emerge/physics/microwave/microwave_data.py +23 -6
- emerge/_emerge/plot/pyvista/display.py +5 -2
- emerge/_emerge/simmodel.py +86 -15
- emerge/_emerge/solver.py +1 -1
- {emerge-1.0.6.dist-info → emerge-1.0.7.dist-info}/METADATA +3 -2
- {emerge-1.0.6.dist-info → emerge-1.0.7.dist-info}/RECORD +18 -17
- {emerge-1.0.6.dist-info → emerge-1.0.7.dist-info}/WHEEL +0 -0
- {emerge-1.0.6.dist-info → emerge-1.0.7.dist-info}/entry_points.txt +0 -0
- {emerge-1.0.6.dist-info → emerge-1.0.7.dist-info}/licenses/LICENSE +0 -0
|
@@ -19,7 +19,7 @@ from ...mesher import Mesher
|
|
|
19
19
|
from ...material import Material
|
|
20
20
|
from ...mesh3d import Mesh3D
|
|
21
21
|
from ...coord import Line
|
|
22
|
-
from ...geometry import GeoSurface
|
|
22
|
+
from ...geometry import GeoSurface, _GEOMANAGER
|
|
23
23
|
from ...elements.femdata import FEMBasis
|
|
24
24
|
from ...elements.nedelec2 import Nedelec2
|
|
25
25
|
from ...solver import DEFAULT_ROUTINE, SolveRoutine
|
|
@@ -140,11 +140,13 @@ class Microwave3D:
|
|
|
140
140
|
def reset_data(self):
|
|
141
141
|
self.data = MWData()
|
|
142
142
|
|
|
143
|
-
def reset(self):
|
|
144
|
-
|
|
143
|
+
def reset(self, _reset_bc: bool = True):
|
|
144
|
+
if _reset_bc:
|
|
145
|
+
self.bc.reset()
|
|
146
|
+
self.bc = MWBoundaryConditionSet(None)
|
|
145
147
|
self.basis: FEMBasis = None
|
|
146
|
-
self.bc = MWBoundaryConditionSet(None)
|
|
147
148
|
self.solveroutine.reset()
|
|
149
|
+
self.assembler.cached_matrices = None
|
|
148
150
|
|
|
149
151
|
def set_order(self, order: int) -> None:
|
|
150
152
|
"""Sets the order of the basis functions used. Currently only supports second order.
|
|
@@ -401,8 +403,10 @@ class Microwave3D:
|
|
|
401
403
|
raise ValueError('The field basis is not yet defined.')
|
|
402
404
|
|
|
403
405
|
logger.debug(' - Finding PEC TEM conductors')
|
|
404
|
-
pecs: list[PEC] = self.bc.get_conductors() # type: ignore
|
|
405
406
|
mesh = self.mesh
|
|
407
|
+
|
|
408
|
+
# Find all BC conductors
|
|
409
|
+
pecs: list[PEC] = self.bc.get_conductors() # type: ignore
|
|
406
410
|
|
|
407
411
|
# Process all PEC Boundary Conditions
|
|
408
412
|
pec_edges = []
|
|
@@ -418,19 +422,37 @@ class Microwave3D:
|
|
|
418
422
|
edge_ids = list(mesh.tri_to_edge[:,itri].flatten())
|
|
419
423
|
pec_edges.extend(edge_ids)
|
|
420
424
|
|
|
425
|
+
# All PEC edges
|
|
421
426
|
pec_edges = list(set(pec_edges))
|
|
422
427
|
|
|
428
|
+
# Port mesh data
|
|
423
429
|
tri_ids = mesh.get_triangles(port.tags)
|
|
424
430
|
edge_ids = list(mesh.tri_to_edge[:,tri_ids].flatten())
|
|
425
431
|
|
|
426
|
-
|
|
432
|
+
port_pec_edges = np.array([i for i in pec_edges if i in set(edge_ids)])
|
|
427
433
|
|
|
428
|
-
pec_islands = mesh.find_edge_groups(
|
|
434
|
+
pec_islands = mesh.find_edge_groups(port_pec_edges)
|
|
429
435
|
|
|
436
|
+
|
|
430
437
|
logger.debug(f' - Found {len(pec_islands)} PEC islands.')
|
|
431
438
|
|
|
432
439
|
if len(pec_islands) != 2:
|
|
433
|
-
|
|
440
|
+
pec_island_tags = {i: self.mesh._get_dimtags(edges=pec_edge_group) for i,pec_edge_group in enumerate(pec_islands)}
|
|
441
|
+
plus_term = None
|
|
442
|
+
min_term = None
|
|
443
|
+
|
|
444
|
+
for i, dimtags in pec_island_tags.items():
|
|
445
|
+
if not set(dimtags).isdisjoint(port.plus_terminal):
|
|
446
|
+
plus_term = i
|
|
447
|
+
|
|
448
|
+
if not set(dimtags).isdisjoint(port.minus_terminal):
|
|
449
|
+
min_term = i
|
|
450
|
+
|
|
451
|
+
if plus_term is None or min_term is None:
|
|
452
|
+
raise ValueError(f' - Found {len(pec_islands)} PEC islands without a terminal definition. Please use .set_terminals() to define which conductors are which polarity.')
|
|
453
|
+
logger.debug(f'Positive island = {pec_island_tags[plus_term]}')
|
|
454
|
+
logger.debug(f'Negative island = {pec_island_tags[min_term]}')
|
|
455
|
+
pec_islands = [pec_islands[plus_term], pec_islands[min_term]]
|
|
434
456
|
|
|
435
457
|
groups = []
|
|
436
458
|
for island in pec_islands:
|
|
@@ -603,6 +625,7 @@ class Microwave3D:
|
|
|
603
625
|
elif TEM:
|
|
604
626
|
G1, G2 = self._find_tem_conductors(port, sigtri=cond)
|
|
605
627
|
cs, dls = self._compute_integration_line(G1,G2)
|
|
628
|
+
logger.debug(f'Integrating portmode from {cs[:,0]} to {cs[:,-1]}')
|
|
606
629
|
mode.modetype = 'TEM'
|
|
607
630
|
Ex, Ey, Ez = portfE(cs[0,:], cs[1,:], cs[2,:])
|
|
608
631
|
voltage = np.sum(Ex*dls[0,:] + Ey*dls[1,:] + Ez*dls[2,:])
|
|
@@ -835,6 +858,100 @@ class Microwave3D:
|
|
|
835
858
|
self._post_process(results, matset)
|
|
836
859
|
return self.data
|
|
837
860
|
|
|
861
|
+
def _run_adaptive_mesh(self,
|
|
862
|
+
iteration: int,
|
|
863
|
+
frequency: float,
|
|
864
|
+
automatic_modal_analysis: bool = True) -> MWData:
|
|
865
|
+
"""Executes a frequency domain study
|
|
866
|
+
|
|
867
|
+
The study is distributed over "n_workers" workers.
|
|
868
|
+
As optional parameter you may set a harddisc_threshold as integer. This determines the maximum
|
|
869
|
+
number of degrees of freedom before which the jobs will be cahced to the harddisk. The
|
|
870
|
+
path that will be used to cache the sparse matrices can be specified.
|
|
871
|
+
Additionally the term frequency_groups may be specified. This number will define in how
|
|
872
|
+
many groups the matrices will be pre-computed before they are send to workers. This can minimize
|
|
873
|
+
the total amound of RAM memory used. For example with 11 frequencies in gruops of 4, the following
|
|
874
|
+
frequency indices will be precomputed and then solved: [[1,2,3,4],[5,6,7,8],[9,10,11]]
|
|
875
|
+
|
|
876
|
+
Args:
|
|
877
|
+
iteration (int): The iteration number
|
|
878
|
+
frequency (float): The simulation frequency
|
|
879
|
+
|
|
880
|
+
Raises:
|
|
881
|
+
SimulationError: An error associated witha a problem during the simulation.
|
|
882
|
+
|
|
883
|
+
Returns:
|
|
884
|
+
MWSimData: The dataset.
|
|
885
|
+
"""
|
|
886
|
+
|
|
887
|
+
self._simstart = time.time()
|
|
888
|
+
if self.bc._initialized is False:
|
|
889
|
+
raise SimulationError('Cannot run a modal analysis because no boundary conditions have been assigned.')
|
|
890
|
+
|
|
891
|
+
self._initialize_field()
|
|
892
|
+
self._initialize_bc_data()
|
|
893
|
+
self._check_physics()
|
|
894
|
+
|
|
895
|
+
if self.basis is None:
|
|
896
|
+
raise SimulationError('Cannot proceed, the simulation basis class is undefined.')
|
|
897
|
+
|
|
898
|
+
materials = self.mesh._get_material_assignment(self.mesher.volumes)
|
|
899
|
+
|
|
900
|
+
### Does this move
|
|
901
|
+
logger.debug('Initializing single frequency settings.')
|
|
902
|
+
|
|
903
|
+
#### Port settings
|
|
904
|
+
all_ports = self.bc.oftype(PortBC)
|
|
905
|
+
|
|
906
|
+
##### FOR PORT SWEEP SET ALL ACTIVE TO FALSE. THIS SHOULD BE FIXED LATER
|
|
907
|
+
### COMPUTE WHICH TETS ARE CONNECTED TO PORT INDICES
|
|
908
|
+
|
|
909
|
+
for port in all_ports:
|
|
910
|
+
port.active=False
|
|
911
|
+
|
|
912
|
+
|
|
913
|
+
def run_job_single(job: SimJob):
|
|
914
|
+
for A, b, ids, reuse, aux in job.iter_Ab():
|
|
915
|
+
solution, report = self.solveroutine.solve(A, b, ids, reuse, id=job.id)
|
|
916
|
+
report.add(**aux)
|
|
917
|
+
job.submit_solution(solution, report)
|
|
918
|
+
return job
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
matset: list[tuple[np.ndarray, np.ndarray, np.ndarray]] = []
|
|
922
|
+
|
|
923
|
+
self._compute_modes(frequency)
|
|
924
|
+
|
|
925
|
+
logger.debug(f'Simulation frequency = {frequency/1e9:.3f} GHz')
|
|
926
|
+
|
|
927
|
+
if automatic_modal_analysis:
|
|
928
|
+
self._compute_modes(frequency)
|
|
929
|
+
|
|
930
|
+
job, mats = self.assembler.assemble_freq_matrix(self.basis, materials,
|
|
931
|
+
self.bc.boundary_conditions,
|
|
932
|
+
frequency,
|
|
933
|
+
cache_matrices=self.cache_matrices)
|
|
934
|
+
|
|
935
|
+
job.id = 0
|
|
936
|
+
|
|
937
|
+
logger.info('Starting solve')
|
|
938
|
+
job = run_job_single(job)
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
logger.info('Solving complete')
|
|
942
|
+
|
|
943
|
+
self.data.setreport(job.reports, freq=frequency, **self._params)
|
|
944
|
+
|
|
945
|
+
for variables, data in self.data.sim.iterate():
|
|
946
|
+
logger.trace(f'Sim variable: {variables}')
|
|
947
|
+
for item in data['report']:
|
|
948
|
+
item.logprint(logger.trace)
|
|
949
|
+
|
|
950
|
+
self.solveroutine.reset()
|
|
951
|
+
### Compute S-parameters and return
|
|
952
|
+
self._post_process([job,], [mats,])
|
|
953
|
+
return self.data
|
|
954
|
+
|
|
838
955
|
def eigenmode(self, search_frequency: float,
|
|
839
956
|
nmodes: int = 6,
|
|
840
957
|
k0_limit: float = 1,
|
|
@@ -868,10 +985,6 @@ class Microwave3D:
|
|
|
868
985
|
|
|
869
986
|
materials = self.mesh._get_material_assignment(self.mesher.volumes)
|
|
870
987
|
|
|
871
|
-
# er = self.mesh.retreive(lambda mat,x,y,z: mat.fer3d_mat(x,y,z), self.mesher.volumes)
|
|
872
|
-
# ur = self.mesh.retreive(lambda mat,x,y,z: mat.fur3d_mat(x,y,z), self.mesher.volumes)
|
|
873
|
-
# cond = self.mesh.retreive(lambda mat,x,y,z: mat.cond, self.mesher.volumes)[0,0,:]
|
|
874
|
-
|
|
875
988
|
### Does this move
|
|
876
989
|
logger.debug('Initializing frequency domain sweep.')
|
|
877
990
|
|
|
@@ -73,7 +73,6 @@ class MWBoundaryConditionSet(BoundaryConditionSet):
|
|
|
73
73
|
for bc in self.oftype(SurfaceImpedance):
|
|
74
74
|
if bc.sigma > 10.0:
|
|
75
75
|
bcs.append(bc)
|
|
76
|
-
|
|
77
76
|
return bcs
|
|
78
77
|
|
|
79
78
|
def get_type(self, bctype: Literal['PEC','ModalPort','LumpedPort','PMC','LumpedElement','RectangularWaveguide','Periodic','FloquetPort','SurfaceImpedance']) -> FaceSelection:
|
|
@@ -497,6 +496,8 @@ class ModalPort(PortBC):
|
|
|
497
496
|
self._first_k0: float | None = None
|
|
498
497
|
self._last_k0: float | None = None
|
|
499
498
|
|
|
499
|
+
self.plus_terminal: list[tuple[int, int]] = []
|
|
500
|
+
self.minus_terminal: list[tuple[int, int]] = []
|
|
500
501
|
|
|
501
502
|
if cs is None:
|
|
502
503
|
logger.info('Constructing coordinate system from normal port')
|
|
@@ -522,6 +523,26 @@ class ModalPort(PortBC):
|
|
|
522
523
|
*axes (tuple, np.ndarray, Axis): The alignment vectors.
|
|
523
524
|
"""
|
|
524
525
|
self.alignment_vectors = [_parse_axis(ax) for ax in axes]
|
|
526
|
+
|
|
527
|
+
def set_terminals(self, positive: Selection | GeoObject | None = None,
|
|
528
|
+
negative: Selection | GeoObject | None = None,
|
|
529
|
+
ground: Selection | GeoObject | None = None) -> None:
|
|
530
|
+
"""Define which objects/faces/selection should be assigned the positive terminal
|
|
531
|
+
and which one the negative terminal.
|
|
532
|
+
|
|
533
|
+
The terminal assignment will be used to find an integration line for the impedance calculation.
|
|
534
|
+
|
|
535
|
+
Note: Ground is currently unused.
|
|
536
|
+
|
|
537
|
+
Args:
|
|
538
|
+
positive (Selection | GeoObject | None, optional): The postive terminal. Defaults to None.
|
|
539
|
+
negative (Selection | GeoObject | None, optional): The negative terminal. Defaults to None.
|
|
540
|
+
ground (Selection | GeoObject | None, optional): _description_. Defaults to None.
|
|
541
|
+
"""
|
|
542
|
+
if positive is not None:
|
|
543
|
+
self.plus_terminal = positive.dimtags
|
|
544
|
+
if negative is not None:
|
|
545
|
+
self.minus_terminal = negative.dimtags
|
|
525
546
|
|
|
526
547
|
@property
|
|
527
548
|
def nmodes(self) -> int:
|
|
@@ -569,9 +590,10 @@ class ModalPort(PortBC):
|
|
|
569
590
|
Returns:
|
|
570
591
|
PortMode: The requested PortMode object
|
|
571
592
|
"""
|
|
593
|
+
options = self.modes[min(self.modes.keys(), key=lambda k: abs(k - k0))]
|
|
572
594
|
if i is None:
|
|
573
|
-
i = self.selected_mode
|
|
574
|
-
return
|
|
595
|
+
i = min(len(options)-1, self.selected_mode)
|
|
596
|
+
return options[i]
|
|
575
597
|
|
|
576
598
|
def global_field_function(self, k0: float = 0, which: Literal['E','H'] = 'E') -> Callable:
|
|
577
599
|
''' The field function used to compute the E-field.
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
from ...simulation_data import BaseDataset, DataContainer
|
|
20
20
|
from ...elements.femdata import FEMBasis
|
|
21
|
-
from dataclasses import dataclass
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
22
|
import numpy as np
|
|
23
23
|
from typing import Literal
|
|
24
24
|
from loguru import logger
|
|
@@ -372,6 +372,7 @@ class EHField:
|
|
|
372
372
|
freq: float
|
|
373
373
|
er: np.ndarray
|
|
374
374
|
ur: np.ndarray
|
|
375
|
+
aux: dict[str, np.ndarray] = field(default_factory=dict)
|
|
375
376
|
|
|
376
377
|
@property
|
|
377
378
|
def k0(self) -> float:
|
|
@@ -537,7 +538,7 @@ class EHField:
|
|
|
537
538
|
|
|
538
539
|
return self.x, self.y, self.z, Fx, Fy, Fz
|
|
539
540
|
|
|
540
|
-
def scalar(self, field: Literal['Ex','Ey','Ez','Hx','Hy','Hz','normE','normH'], metric: Literal['abs','real','imag','complex'] = 'real') -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
|
541
|
+
def scalar(self, field: Literal['Ex','Ey','Ez','Hx','Hy','Hz','normE','normH'] | str, metric: Literal['abs','real','imag','complex'] = 'real') -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
|
541
542
|
"""Returns the data X, Y, Z, Field based on the interpolation
|
|
542
543
|
|
|
543
544
|
For animations, make sure to select the complex metric.
|
|
@@ -549,7 +550,11 @@ class EHField:
|
|
|
549
550
|
Returns:
|
|
550
551
|
(X,Y,Z,Field): The coordinates plus field scalar
|
|
551
552
|
"""
|
|
552
|
-
|
|
553
|
+
if field in self.aux:
|
|
554
|
+
field_arry = self.aux[field]
|
|
555
|
+
else:
|
|
556
|
+
field_arry = getattr(self, field)
|
|
557
|
+
|
|
553
558
|
if metric=='abs':
|
|
554
559
|
field = np.abs(field_arry)
|
|
555
560
|
elif metric=='real':
|
|
@@ -666,6 +671,10 @@ class MWField:
|
|
|
666
671
|
self.excitation = {key: 0.0 for key in self._fields.keys()}
|
|
667
672
|
self.excitation[self.port_modes[0].port_number] = 1.0 + 0j
|
|
668
673
|
|
|
674
|
+
def excite_port(self, number: int) -> None:
|
|
675
|
+
self.excitation = {key: 0.0 for key in self._fields.keys()}
|
|
676
|
+
self.excitation[self.port_modes[number].port_number] = 1.0 + 0j
|
|
677
|
+
|
|
669
678
|
@property
|
|
670
679
|
def EH(self) -> tuple[np.ndarray, np.ndarray]:
|
|
671
680
|
''' Return the electric and magnetic field as a tuple of numpy arrays '''
|
|
@@ -726,12 +735,20 @@ class MWField:
|
|
|
726
735
|
self.Hx = Hx.reshape(shp)
|
|
727
736
|
self.Hy = Hy.reshape(shp)
|
|
728
737
|
self.Hz = Hz.reshape(shp)
|
|
729
|
-
|
|
738
|
+
|
|
730
739
|
self._x = xs
|
|
731
740
|
self._y = ys
|
|
732
741
|
self._z = zs
|
|
733
|
-
|
|
734
|
-
|
|
742
|
+
field = EHField(xs, ys, zs, self.Ex, self.Ey, self.Ez, self.Hx, self.Hy, self.Hz, self.freq, self.er, self.ur)
|
|
743
|
+
|
|
744
|
+
return field
|
|
745
|
+
|
|
746
|
+
def _solution_quality(self) -> tuple[np.ndarray, np.ndarray]:
|
|
747
|
+
from .adaptive_mesh import compute_error_estimate
|
|
748
|
+
|
|
749
|
+
error_tet, max_elem_size = compute_error_estimate(self)
|
|
750
|
+
return error_tet, max_elem_size
|
|
751
|
+
|
|
735
752
|
def boundary(self,
|
|
736
753
|
selection: FaceSelection) -> EHField:
|
|
737
754
|
nodes = self.mesh.nodes
|
|
@@ -539,7 +539,7 @@ 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
|
|
@@ -584,7 +584,10 @@ class PVDisplay(BaseDisplay):
|
|
|
584
584
|
k0 = port.get_mode(0).k0
|
|
585
585
|
else:
|
|
586
586
|
k0 = 1
|
|
587
|
-
|
|
587
|
+
|
|
588
|
+
if isinstance(mode_number, int):
|
|
589
|
+
port.selected_mode = mode_number
|
|
590
|
+
|
|
588
591
|
F = port.port_mode_3d_global(xf,yf,zf,k0, which=field)
|
|
589
592
|
|
|
590
593
|
Fx = F[0,:].reshape(X.shape).T
|
emerge/_emerge/simmodel.py
CHANGED
|
@@ -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
|
|
emerge/_emerge/solver.py
CHANGED
|
@@ -343,7 +343,7 @@ class Solver:
|
|
|
343
343
|
return None
|
|
344
344
|
|
|
345
345
|
def duplicate(self) -> Solver:
|
|
346
|
-
return self.__class__()
|
|
346
|
+
return self.__class__(self.pre)
|
|
347
347
|
|
|
348
348
|
def set_options(self, pivoting_threshold: float | None = None) -> None:
|
|
349
349
|
"""Write generic simulation options to the solver object.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: emerge
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.7
|
|
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
|
|
@@ -11,7 +11,8 @@ Requires-Dist: joblib>=1.5.1
|
|
|
11
11
|
Requires-Dist: loguru>=0.7.3
|
|
12
12
|
Requires-Dist: matplotlib>=3.8.0
|
|
13
13
|
Requires-Dist: mkl!=2024.0; platform_machine == 'x86_64' or platform_machine == 'AMD64'
|
|
14
|
-
Requires-Dist: numba
|
|
14
|
+
Requires-Dist: numba<=0.60.0,>=0.57.0; platform_system == 'Linux'
|
|
15
|
+
Requires-Dist: numba>=0.57.0; platform_system != 'Linux'
|
|
15
16
|
Requires-Dist: numpy<2.3,>=1.24
|
|
16
17
|
Requires-Dist: pyvista>=0.45.2
|
|
17
18
|
Requires-Dist: scipy>=1.14.0
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
emerge/__init__.py,sha256=
|
|
1
|
+
emerge/__init__.py,sha256=xexiF7GihNOUHqPZ2KViJFFaNNfRyc6l3Qz1PzWO_5w,3162
|
|
2
2
|
emerge/__main__.py,sha256=WVf16sfrOI910QWohrQDaChZdRifMNoS6VKzCT6f3ZA,92
|
|
3
3
|
emerge/cli.py,sha256=NU1uhwuZ6i50680v3_I4kDZPTHqz74gOYK71UBhb8oE,666
|
|
4
4
|
emerge/ext.py,sha256=IBoHH5PQFj5pYMfp6r-uMpNNgbSe8c0g9x8qjBzzVmU,223
|
|
@@ -17,20 +17,20 @@ emerge/_emerge/geo2d.py,sha256=e_HkX1GQ2iYrdO0zeEgzVOzfGyU1WGJyjeGBAobOttE,3323
|
|
|
17
17
|
emerge/_emerge/geometry.py,sha256=9WnZt2xtF4HEJfdPyT41i_q8gRMZRrG0C9PILaz1PvA,25523
|
|
18
18
|
emerge/_emerge/howto.py,sha256=c4UxUNpA1tygr3OoR-LH-h0UZv-Tf9K8tpCiAU18BKE,8173
|
|
19
19
|
emerge/_emerge/logsettings.py,sha256=OpsH_1dQAdZry_as8y0OQt4yKP_AMFfQG2d5Nz5yywg,4789
|
|
20
|
-
emerge/_emerge/material.py,sha256=
|
|
21
|
-
emerge/_emerge/mesh3d.py,sha256=
|
|
22
|
-
emerge/_emerge/mesher.py,sha256=
|
|
20
|
+
emerge/_emerge/material.py,sha256=furDHD4rwkYCOjnh0GLz-Utjl5Z_NxDv0zWrk23Pygc,17459
|
|
21
|
+
emerge/_emerge/mesh3d.py,sha256=zPKOo1whf6yBpJxfEt89p3LXO4k1j7LSSMAGCDSK318,36946
|
|
22
|
+
emerge/_emerge/mesher.py,sha256=Ozks6t0LbdWEfDuh_06Hmm9nZz8vQLJl2FsYA7wzMMo,16854
|
|
23
23
|
emerge/_emerge/periodic.py,sha256=dUuWqjlDR8mHtQR3ecINP2FFjJJ0cKg0blOVZ0PCcAo,12087
|
|
24
24
|
emerge/_emerge/plot.py,sha256=cf1I9mj7EIUJcq8vmANlUkqoV6QqVaJaP-zlC-T9E18,8041
|
|
25
25
|
emerge/_emerge/selection.py,sha256=ltT8ubMFG3-sFrAh7K7iAgSoBt3r25B02ShzMlOGSXQ,21527
|
|
26
26
|
emerge/_emerge/settings.py,sha256=wZFMzQSnMEEJptatu--67El1L0I_YEv9S8QjqI69bfs,265
|
|
27
|
-
emerge/_emerge/simmodel.py,sha256=
|
|
27
|
+
emerge/_emerge/simmodel.py,sha256=Iu971xxJ5ggeB-KkrtxmGyV_-zHQN76z8_w1nSwpWJY,29371
|
|
28
28
|
emerge/_emerge/simulation_data.py,sha256=23o-xbm6Q6s9sYisyK9u1GTmuaXRl0WDGQwzPO7lkr0,15115
|
|
29
|
-
emerge/_emerge/solver.py,sha256=
|
|
29
|
+
emerge/_emerge/solver.py,sha256=3P-pcDWEpCEVnT2iFRRzC1hdYGUaz-xRzE68G3xvF1Q,52988
|
|
30
30
|
emerge/_emerge/system.py,sha256=p4HNz7d_LMRNE9Gk75vVdFecDH2iN_groAM9u-yQTpk,1618
|
|
31
31
|
emerge/_emerge/elements/__init__.py,sha256=I3n9aic6lJW-oGeqTEZ-Fpxvyl2i-WqsHdnrM3v1oB8,799
|
|
32
32
|
emerge/_emerge/elements/femdata.py,sha256=ZSB7dICyU3GOWbxpuowBc-Khh2mpXa02ZfMkHwdDol4,8377
|
|
33
|
-
emerge/_emerge/elements/index_interp.py,sha256=
|
|
33
|
+
emerge/_emerge/elements/index_interp.py,sha256=X7WcXKC9Qg7UABvLbAktda7InRkOhm7qOU-Wnefflp4,3381
|
|
34
34
|
emerge/_emerge/elements/ned2_interp.py,sha256=BGRdJGXCcDdqMBf3pOvc_-m-vngRI1VvE5MUsqqf9Cw,27981
|
|
35
35
|
emerge/_emerge/elements/nedelec2.py,sha256=OssFCnPp7wkMBcUISGvTgT94jayfGOmX0x-RctIYSuI,6645
|
|
36
36
|
emerge/_emerge/elements/nedleg2.py,sha256=1SA6AvnbChhU2iodS1PVMUdbSn9wF2XKam12WTBDtsw,9049
|
|
@@ -54,16 +54,17 @@ emerge/_emerge/mth/pairing.py,sha256=i8bBvTeMmzgF0JdiDNJiTXxx913x4f10777pzD6FJo0
|
|
|
54
54
|
emerge/_emerge/physics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
55
55
|
emerge/_emerge/physics/microwave/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
56
56
|
emerge/_emerge/physics/microwave/adaptive_freq.py,sha256=aWhijhCVAbnuwkru-I1AaRdY20uyozf6OWRIh9r2ijg,9786
|
|
57
|
-
emerge/_emerge/physics/microwave/
|
|
58
|
-
emerge/_emerge/physics/microwave/
|
|
59
|
-
emerge/_emerge/physics/microwave/
|
|
57
|
+
emerge/_emerge/physics/microwave/adaptive_mesh.py,sha256=bcPPbWoTaMsLK1QCIL6an5WaIIeKddCPPs2u7vKfAEs,22089
|
|
58
|
+
emerge/_emerge/physics/microwave/microwave_3d.py,sha256=OxZ3uJI1hhyGuUPV6ofWXCQfCu23URkyK2UJnpa9CLI,49141
|
|
59
|
+
emerge/_emerge/physics/microwave/microwave_bc.py,sha256=70zrxTZG_i9n2vBQAU1FOtK2Ia_KMi3qQeC9MU5aDHo,44872
|
|
60
|
+
emerge/_emerge/physics/microwave/microwave_data.py,sha256=eSWBrFmdYnOmCchf31jORwCriq2Ix1piGoGYHsvcF38,50551
|
|
60
61
|
emerge/_emerge/physics/microwave/periodic.py,sha256=wYSUgLFVtCLqSG3EDKoCDRU93iPUzBdXzVRdHTRmbpI,3000
|
|
61
62
|
emerge/_emerge/physics/microwave/port_functions.py,sha256=d-W1D-7P05MfXdOs7WlhPi_RqlSpC0HkYU6yl3GrxgE,2173
|
|
62
63
|
emerge/_emerge/physics/microwave/sc.py,sha256=WZvoPhmHkfEv619RhmN09sXDBV0ryTqybwErA8Rc7lU,4735
|
|
63
64
|
emerge/_emerge/physics/microwave/simjob.py,sha256=aCWCs7IXBfVBWWYhwyHvXSRHY3FOd3CK5ABcaFEsNnM,4927
|
|
64
65
|
emerge/_emerge/physics/microwave/sparam.py,sha256=1SXGyr1UsrPnCIi4ffwobM4pzgkj50y4LrWCr_J5IRY,4946
|
|
65
66
|
emerge/_emerge/physics/microwave/touchstone.py,sha256=pMcCOLWVqIKctcShcJxyaV-0rhRWXMSS1Jz14dVQEyY,5799
|
|
66
|
-
emerge/_emerge/physics/microwave/assembly/assembler.py,sha256=
|
|
67
|
+
emerge/_emerge/physics/microwave/assembly/assembler.py,sha256=ZRaf_5zSZ2U0D5DmuYKCiyyxjonJLQV5pYsUm6aChj0,23982
|
|
67
68
|
emerge/_emerge/physics/microwave/assembly/curlcurl.py,sha256=4CwMSUua8pvbp39gq2e3AxwVHOIVuPdr53qQM2BigQo,19688
|
|
68
69
|
emerge/_emerge/physics/microwave/assembly/generalized_eigen.py,sha256=7jNU_hWd7q-ni53wmlhB5kmyJxp2xsx_WyBXZ6hPwo0,16907
|
|
69
70
|
emerge/_emerge/physics/microwave/assembly/generalized_eigen_hb.py,sha256=tGf9EOzmp-dqtTodwYzcswuT_eLeZRtgdAZvgPz9QJo,17340
|
|
@@ -76,7 +77,7 @@ emerge/_emerge/plot/simple_plots.py,sha256=szIpmQmO8o6ubzB_E3zTJfEx16mJ3-OXrMYdD
|
|
|
76
77
|
emerge/_emerge/plot/matplotlib/mpldisplay.py,sha256=e8V6EhGdCW7nRkSFvjHCcRO5uR-FcD0yHQ1nxPQCbp4,8674
|
|
77
78
|
emerge/_emerge/plot/pyvista/__init__.py,sha256=CPclatEu6mFnJZzCQk09g6T6Fh20WTbiLAJGSwAnPXU,30
|
|
78
79
|
emerge/_emerge/plot/pyvista/cmap_maker.py,sha256=_GVXYdtXpJwAO9O-Iekjzn6YcR0MVT8LNh12nqvF2IA,2498
|
|
79
|
-
emerge/_emerge/plot/pyvista/display.py,sha256=
|
|
80
|
+
emerge/_emerge/plot/pyvista/display.py,sha256=nsiElERHYZPWX0uHwNoaocvfGtVTMsBdaO3hjTVDPvk,45429
|
|
80
81
|
emerge/_emerge/plot/pyvista/display_settings.py,sha256=gV5hjRGEAl3oQeBPobas6b6JzYfMFrXIGtVSaeml4N0,1074
|
|
81
82
|
emerge/_emerge/projects/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
82
83
|
emerge/_emerge/projects/_gen_base.txt,sha256=DqQz36PZg6v1ovQjHvPjd0t4AIbmikZdb9dmrNYsK3w,598
|
|
@@ -88,8 +89,8 @@ emerge/beta/dxf.py,sha256=Bw4lVk0TquOgCxTZV23BZN7PrgqxBrMZxbHV1waC5U0,50
|
|
|
88
89
|
emerge/materials/__init__.py,sha256=Z9tu3m_nqj6F9I-FwoVoN0vCTYUlFesH3KxJ38wkZck,19
|
|
89
90
|
emerge/materials/isola.py,sha256=kSDxHJZVn2CcanoUjlwRVKIPvadRbBybURTdIHWx728,18660
|
|
90
91
|
emerge/materials/rogers.py,sha256=4u6ma_XQdXGKWE3WsFkaMTamCQNo9kTYSTU8S1gCAYU,3388
|
|
91
|
-
emerge-1.0.
|
|
92
|
-
emerge-1.0.
|
|
93
|
-
emerge-1.0.
|
|
94
|
-
emerge-1.0.
|
|
95
|
-
emerge-1.0.
|
|
92
|
+
emerge-1.0.7.dist-info/METADATA,sha256=tv3bFqll_HiWD6MDGr2K5tUC4Vy97vCO84EKQ2iGkQU,3400
|
|
93
|
+
emerge-1.0.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
94
|
+
emerge-1.0.7.dist-info/entry_points.txt,sha256=8rFvAXticpKg4OTC8JEvAksnduW72KIEskCGG9XnFf8,43
|
|
95
|
+
emerge-1.0.7.dist-info/licenses/LICENSE,sha256=VOCXWddrjMN5j7TvnSAOh1Dx7jkugdwq9Lqhycf5inc,17852
|
|
96
|
+
emerge-1.0.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|