emerge 1.0.7__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 +15 -3
- 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 +162 -71
- emerge/_emerge/geo/shapes.py +12 -7
- emerge/_emerge/geo/step.py +177 -41
- emerge/_emerge/geometry.py +189 -27
- emerge/_emerge/logsettings.py +26 -2
- emerge/_emerge/material.py +2 -0
- emerge/_emerge/mesh3d.py +6 -8
- emerge/_emerge/mesher.py +67 -11
- emerge/_emerge/mth/common_functions.py +1 -1
- emerge/_emerge/mth/optimized.py +2 -2
- emerge/_emerge/physics/microwave/adaptive_mesh.py +549 -116
- emerge/_emerge/physics/microwave/assembly/assembler.py +9 -1
- emerge/_emerge/physics/microwave/microwave_3d.py +133 -83
- emerge/_emerge/physics/microwave/microwave_bc.py +158 -8
- emerge/_emerge/physics/microwave/microwave_data.py +94 -5
- emerge/_emerge/plot/pyvista/display.py +36 -23
- emerge/_emerge/selection.py +17 -2
- emerge/_emerge/settings.py +124 -6
- emerge/_emerge/simmodel.py +273 -150
- emerge/_emerge/simstate.py +106 -0
- emerge/_emerge/simulation_data.py +11 -23
- emerge/_emerge/solve_interfaces/cudss_interface.py +20 -1
- emerge/_emerge/solver.py +4 -4
- {emerge-1.0.7.dist-info → emerge-1.1.1.dist-info}/METADATA +7 -3
- {emerge-1.0.7.dist-info → emerge-1.1.1.dist-info}/RECORD +33 -32
- {emerge-1.0.7.dist-info → emerge-1.1.1.dist-info}/WHEEL +0 -0
- {emerge-1.0.7.dist-info → emerge-1.1.1.dist-info}/entry_points.txt +0 -0
- {emerge-1.0.7.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()
|
|
@@ -19,13 +19,16 @@ 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
|
|
23
23
|
from ...elements.femdata import FEMBasis
|
|
24
24
|
from ...elements.nedelec2 import Nedelec2
|
|
25
25
|
from ...solver import DEFAULT_ROUTINE, SolveRoutine
|
|
26
26
|
from ...system import called_from_main_function
|
|
27
27
|
from ...selection import FaceSelection
|
|
28
28
|
from ...settings import Settings
|
|
29
|
+
from ...simstate import SimState
|
|
30
|
+
from ...logsettings import DEBUG_COLLECTOR
|
|
31
|
+
|
|
29
32
|
from .microwave_bc import MWBoundaryConditionSet, PEC, ModalPort, LumpedPort, PortBC
|
|
30
33
|
from .microwave_data import MWData
|
|
31
34
|
from .assembly.assembler import Assembler
|
|
@@ -34,10 +37,10 @@ from .simjob import SimJob
|
|
|
34
37
|
|
|
35
38
|
from concurrent.futures import ThreadPoolExecutor
|
|
36
39
|
from loguru import logger
|
|
37
|
-
from typing import Callable, Literal
|
|
40
|
+
from typing import Callable, Literal, Any
|
|
38
41
|
import multiprocessing as mp
|
|
39
42
|
from cmath import sqrt as csqrt
|
|
40
|
-
|
|
43
|
+
from itertools import product
|
|
41
44
|
import numpy as np
|
|
42
45
|
import threading
|
|
43
46
|
import time
|
|
@@ -54,16 +57,14 @@ def run_job_multi(job: SimJob) -> SimJob:
|
|
|
54
57
|
Returns:
|
|
55
58
|
SimJob: The solved SimJob
|
|
56
59
|
"""
|
|
57
|
-
|
|
60
|
+
nr = int(mp.current_process().name.split('-')[1])
|
|
61
|
+
routine = DEFAULT_ROUTINE._configure_routine('MP', proc_nr=nr)
|
|
58
62
|
for A, b, ids, reuse, aux in job.iter_Ab():
|
|
59
63
|
solution, report = routine.solve(A, b, ids, reuse, id=job.id)
|
|
60
64
|
report.add(**aux)
|
|
61
65
|
job.submit_solution(solution, report)
|
|
62
66
|
return job
|
|
63
67
|
|
|
64
|
-
def _init_worker():
|
|
65
|
-
nr = int(mp.current_process().name.split('-')[1])
|
|
66
|
-
DEFAULT_ROUTINE._configure_routine(proc_nr=nr)
|
|
67
68
|
|
|
68
69
|
def _dimstring(data: list[float] | np.ndarray) -> str:
|
|
69
70
|
"""A String formatter for dimensions in millimeters
|
|
@@ -111,14 +112,17 @@ class Microwave3D:
|
|
|
111
112
|
formulation.
|
|
112
113
|
|
|
113
114
|
"""
|
|
114
|
-
def __init__(self, mesher: Mesher, settings: Settings,
|
|
115
|
+
def __init__(self, state: SimState, mesher: Mesher, settings: Settings, order: int = 2):
|
|
116
|
+
|
|
117
|
+
self._settings: Settings = settings
|
|
118
|
+
|
|
115
119
|
self.frequencies: list[float] = []
|
|
116
120
|
self.current_frequency = 0
|
|
117
121
|
self.order: int = order
|
|
118
|
-
self.resolution: float =
|
|
119
|
-
|
|
122
|
+
self.resolution: float = 0.33
|
|
123
|
+
|
|
120
124
|
self.mesher: Mesher = mesher
|
|
121
|
-
self.
|
|
125
|
+
self._state: SimState = state
|
|
122
126
|
|
|
123
127
|
self.assembler: Assembler = Assembler(self._settings)
|
|
124
128
|
self.bc: MWBoundaryConditionSet = MWBoundaryConditionSet(None)
|
|
@@ -128,41 +132,34 @@ class Microwave3D:
|
|
|
128
132
|
|
|
129
133
|
## States
|
|
130
134
|
self._bc_initialized: bool = False
|
|
131
|
-
self.data: MWData = mwdata
|
|
132
|
-
|
|
133
|
-
## Data
|
|
134
|
-
self._params: dict[str, float] = dict()
|
|
135
135
|
self._simstart: float = 0.0
|
|
136
136
|
self._simend: float = 0.0
|
|
137
|
+
|
|
138
|
+
self._container: dict[str, Any] = dict()
|
|
137
139
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
@property
|
|
141
|
+
def _params(self) -> dict[str, float]:
|
|
142
|
+
return self._state.params
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def mesh(self) -> Mesh3D:
|
|
146
|
+
return self._state.mesh
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def data(self) -> MWData:
|
|
150
|
+
return self._state.data.mw
|
|
151
|
+
|
|
143
152
|
def reset(self, _reset_bc: bool = True):
|
|
144
153
|
if _reset_bc:
|
|
145
|
-
self.bc.reset()
|
|
146
154
|
self.bc = MWBoundaryConditionSet(None)
|
|
155
|
+
else:
|
|
156
|
+
for bc in self.bc.oftype(ModalPort):
|
|
157
|
+
bc.reset()
|
|
158
|
+
|
|
147
159
|
self.basis: FEMBasis = None
|
|
148
160
|
self.solveroutine.reset()
|
|
149
161
|
self.assembler.cached_matrices = None
|
|
150
162
|
|
|
151
|
-
def set_order(self, order: int) -> None:
|
|
152
|
-
"""Sets the order of the basis functions used. Currently only supports second order.
|
|
153
|
-
|
|
154
|
-
Args:
|
|
155
|
-
order (int): The order to use.
|
|
156
|
-
|
|
157
|
-
Raises:
|
|
158
|
-
ValueError: An error if a wrong order is used.
|
|
159
|
-
"""
|
|
160
|
-
if order not in (2,):
|
|
161
|
-
raise ValueError(f'Order {order} not supported. Only order-2 allowed.')
|
|
162
|
-
|
|
163
|
-
self.order = order
|
|
164
|
-
self.resolution = {1: 0.15, 2: 0.3}[order]
|
|
165
|
-
|
|
166
163
|
@property
|
|
167
164
|
def nports(self) -> int:
|
|
168
165
|
"""The number of ports in the physics.
|
|
@@ -219,7 +216,15 @@ class Microwave3D:
|
|
|
219
216
|
self.frequencies = list(frequency)
|
|
220
217
|
else:
|
|
221
218
|
self.frequencies = [frequency]
|
|
222
|
-
|
|
219
|
+
|
|
220
|
+
# Safety tests
|
|
221
|
+
if len(self.frequencies) > 200:
|
|
222
|
+
DEBUG_COLLECTOR.add_report(f'More than 200 frequency points are detected ({len(frequency)}). This may cause slow simulations. Consider using Vector Fitting to subsample S-parameters.')
|
|
223
|
+
if min(self.frequencies) < 1e6:
|
|
224
|
+
DEBUG_COLLECTOR.add_report(f'A frequency smaller than 1MHz has been detected ({min(frequency)} Hz). Perhaps you forgot to include usints like 1e6 for MHz etc.')
|
|
225
|
+
if max(self.frequencies) > 1e12:
|
|
226
|
+
DEBUG_COLLECTOR.add_report(f'A frequency greater than THz has been detected ({min(frequency)} Hz). Perhaps you double counted frequency units like twice 1e6 for MHz etc.')
|
|
227
|
+
|
|
223
228
|
self.mesher.max_size = self.resolution * 299792458 / max(self.frequencies)
|
|
224
229
|
self.mesher.min_size = 0.1 * self.mesher.max_size
|
|
225
230
|
|
|
@@ -361,29 +366,6 @@ class Microwave3D:
|
|
|
361
366
|
logger.trace(f' - Port[{port.port_number}] integration line {start} -> {end}.')
|
|
362
367
|
|
|
363
368
|
port.v_integration = True
|
|
364
|
-
|
|
365
|
-
def _compute_integration_line(self, group1: list[int], group2: list[int]) -> tuple[np.ndarray, np.ndarray]:
|
|
366
|
-
"""Computes an integration line for two node island groups by finding the closest two nodes.
|
|
367
|
-
|
|
368
|
-
This method is used for the modal TEM analysis to find an appropriate voltage integration path
|
|
369
|
-
by looking for the two closest points for the two conductor islands that where discovered.
|
|
370
|
-
|
|
371
|
-
Currently it defaults to 11 integration line points.
|
|
372
|
-
|
|
373
|
-
Args:
|
|
374
|
-
group1 (list[int]): The first island node group
|
|
375
|
-
group2 (list[int]): The second island node group
|
|
376
|
-
|
|
377
|
-
Returns:
|
|
378
|
-
centers (np.ndarray): The center points of the line segments
|
|
379
|
-
dls (np.ndarray): The delta-path vectors for each line segment.
|
|
380
|
-
"""
|
|
381
|
-
nodes1 = self.mesh.nodes[:,group1]
|
|
382
|
-
nodes2 = self.mesh.nodes[:,group2]
|
|
383
|
-
path = shortest_path(nodes1, nodes2, 21)
|
|
384
|
-
centres = (path[:,1:] + path[:,:-1])/2
|
|
385
|
-
dls = path[:,1:] - path[:,:-1]
|
|
386
|
-
return centres, dls
|
|
387
369
|
|
|
388
370
|
def _find_tem_conductors(self, port: ModalPort, sigtri: np.ndarray) -> tuple[list[int], list[int]]:
|
|
389
371
|
''' Returns two lists of global node indices corresponding to the TEM port conductors.
|
|
@@ -449,7 +431,8 @@ class Microwave3D:
|
|
|
449
431
|
min_term = i
|
|
450
432
|
|
|
451
433
|
if plus_term is None or min_term is None:
|
|
452
|
-
|
|
434
|
+
logger.error(f' - Found {len(pec_islands)} PEC islands without a terminal definition. Please use .set_terminals() to define which conductors are which polarity, or define the integration line manually.')
|
|
435
|
+
return None, None
|
|
453
436
|
logger.debug(f'Positive island = {pec_island_tags[plus_term]}')
|
|
454
437
|
logger.debug(f'Negative island = {pec_island_tags[min_term]}')
|
|
455
438
|
pec_islands = [pec_islands[plus_term], pec_islands[min_term]]
|
|
@@ -479,7 +462,11 @@ class Microwave3D:
|
|
|
479
462
|
if not bc.mixed_materials and bc.initialized:
|
|
480
463
|
continue
|
|
481
464
|
|
|
482
|
-
|
|
465
|
+
if bc.forced_modetype=='TEM':
|
|
466
|
+
TEM = True
|
|
467
|
+
else:
|
|
468
|
+
TEM = False
|
|
469
|
+
self.modal_analysis(bc, 1, direct=False, freq=freq, TEM=TEM)
|
|
483
470
|
|
|
484
471
|
def modal_analysis(self,
|
|
485
472
|
port: ModalPort,
|
|
@@ -573,7 +560,7 @@ class Microwave3D:
|
|
|
573
560
|
target_kz = k0*target_neff
|
|
574
561
|
|
|
575
562
|
if target_kz is None:
|
|
576
|
-
if TEM:
|
|
563
|
+
if TEM or port.forced_modetype=='TEM':
|
|
577
564
|
target_kz = ermean*urmean*1.1*k0
|
|
578
565
|
else:
|
|
579
566
|
|
|
@@ -595,19 +582,28 @@ class Microwave3D:
|
|
|
595
582
|
Emode = np.zeros((nlf.n_field,), dtype=np.complex128)
|
|
596
583
|
eigenmode = eigen_modes[:,i]
|
|
597
584
|
Emode[solve_ids] = np.squeeze(eigenmode)
|
|
598
|
-
Emode = Emode * np.exp(-1j*np.angle(np.
|
|
585
|
+
Emode = Emode * np.exp(-1j*np.angle(Emode[np.argmax(np.abs(Emode))]))
|
|
599
586
|
|
|
600
587
|
beta_base = np.emath.sqrt(-eigen_values[i])
|
|
601
588
|
beta = min(k0*np.sqrt(ermax*urmax), beta_base)
|
|
602
589
|
|
|
603
590
|
residuals = -1
|
|
604
591
|
|
|
592
|
+
if port._get_alignment_vector(i) is not None:
|
|
593
|
+
vec = port._get_alignment_vector(i)
|
|
594
|
+
xyz_centers = self.mesh.tri_centers[:,self.mesh.get_triangles(port.tags)]
|
|
595
|
+
E_centers = np.mean(nlf.interpolate_Ef(Emode)(xyz_centers[0,:], xyz_centers[1,:], xyz_centers[2,:]), axis=1)
|
|
596
|
+
EdotVec = vec[0]*E_centers[0] + vec[1]*E_centers[1] + vec[2]*E_centers[2]
|
|
597
|
+
if EdotVec.real < 0:
|
|
598
|
+
logger.debug(f'Mode polarization along alignment axis {vec} = {EdotVec.real:.3f}, inverting.')
|
|
599
|
+
Emode = -Emode
|
|
600
|
+
|
|
605
601
|
portfE = nlf.interpolate_Ef(Emode)
|
|
606
602
|
portfH = nlf.interpolate_Hf(Emode, k0, ur, beta)
|
|
607
|
-
|
|
608
603
|
P = compute_avg_power_flux(nlf, Emode, k0, ur, beta)
|
|
609
604
|
|
|
610
|
-
mode = port.add_mode(Emode, portfE, portfH, beta, k0, residuals,
|
|
605
|
+
mode = port.add_mode(Emode, portfE, portfH, beta, k0, residuals, number=i, freq=freq)
|
|
606
|
+
|
|
611
607
|
if mode is None:
|
|
612
608
|
continue
|
|
613
609
|
|
|
@@ -616,21 +612,40 @@ class Microwave3D:
|
|
|
616
612
|
Ez = np.max(np.abs(Efz))
|
|
617
613
|
Exy = np.max(np.abs(Efxy))
|
|
618
614
|
|
|
619
|
-
if
|
|
615
|
+
if port.forced_modetype == 'TEM' or TEM:
|
|
616
|
+
mode.modetype = 'TEM'
|
|
617
|
+
|
|
618
|
+
if len(port.vintline)>0:
|
|
619
|
+
line = port.vintline[0]
|
|
620
|
+
else:
|
|
621
|
+
G1, G2 = self._find_tem_conductors(port, sigtri=cond)
|
|
622
|
+
if G1 is None or G2 is None:
|
|
623
|
+
logger.warning('Skipping characteristic impedance calculation.')
|
|
624
|
+
continue
|
|
625
|
+
|
|
626
|
+
nodes1 = self.mesh.nodes[:,G1]
|
|
627
|
+
nodes2 = self.mesh.nodes[:,G2]
|
|
628
|
+
path = shortest_path(nodes1, nodes2, 2)
|
|
629
|
+
line = Line.from_points(path[:,0], path[:,1], 21)
|
|
630
|
+
port.vintline.append(line)
|
|
631
|
+
|
|
632
|
+
cs = np.array(line.cmid)
|
|
633
|
+
|
|
634
|
+
logger.debug(f'Integrating portmode from {cs[:,0]} to {cs[:,-1]}')
|
|
635
|
+
voltage = line.line_integral(portfE)
|
|
636
|
+
# Align mode polarity to positive voltage
|
|
637
|
+
if voltage < 0:
|
|
638
|
+
mode.polarity = mode.polarity * -1
|
|
639
|
+
|
|
640
|
+
mode.Z0 = abs(voltage**2/(2*P))
|
|
641
|
+
logger.debug(f'Port Z0 = {mode.Z0}')
|
|
642
|
+
elif Ez/Exy < 1e-1 or port.forced_modetype=='TE':
|
|
620
643
|
logger.debug('Low Ez/Et ratio detected, assuming TE mode')
|
|
621
644
|
mode.modetype = 'TE'
|
|
622
|
-
elif Ez/Exy > 1e-1
|
|
645
|
+
elif Ez/Exy > 1e-1 or port.forced_modetype=='TM':
|
|
623
646
|
logger.debug('High Ez/Et ratio detected, assuming TM mode')
|
|
624
647
|
mode.modetype = 'TM'
|
|
625
|
-
|
|
626
|
-
G1, G2 = self._find_tem_conductors(port, sigtri=cond)
|
|
627
|
-
cs, dls = self._compute_integration_line(G1,G2)
|
|
628
|
-
logger.debug(f'Integrating portmode from {cs[:,0]} to {cs[:,-1]}')
|
|
629
|
-
mode.modetype = 'TEM'
|
|
630
|
-
Ex, Ey, Ez = portfE(cs[0,:], cs[1,:], cs[2,:])
|
|
631
|
-
voltage = np.sum(Ex*dls[0,:] + Ey*dls[1,:] + Ez*dls[2,:])
|
|
632
|
-
mode.Z0 = abs(voltage**2/(2*P))
|
|
633
|
-
logger.debug(f'Port Z0 = {mode.Z0}')
|
|
648
|
+
|
|
634
649
|
|
|
635
650
|
mode.set_power(P*port._qmode(k0)**2)
|
|
636
651
|
|
|
@@ -810,7 +825,7 @@ class Microwave3D:
|
|
|
810
825
|
"if __name__ == '__main__' guard in the top-level script."
|
|
811
826
|
)
|
|
812
827
|
# Start parallel pool
|
|
813
|
-
with mp.Pool(processes=n_workers
|
|
828
|
+
with mp.Pool(processes=n_workers) as pool:
|
|
814
829
|
for i_group, fgroup in enumerate(freq_groups):
|
|
815
830
|
logger.debug(f'Precomputing group {i_group}.')
|
|
816
831
|
jobs = []
|
|
@@ -861,7 +876,7 @@ class Microwave3D:
|
|
|
861
876
|
def _run_adaptive_mesh(self,
|
|
862
877
|
iteration: int,
|
|
863
878
|
frequency: float,
|
|
864
|
-
automatic_modal_analysis: bool = True) -> MWData:
|
|
879
|
+
automatic_modal_analysis: bool = True) -> tuple[MWData, list[int]]:
|
|
865
880
|
"""Executes a frequency domain study
|
|
866
881
|
|
|
867
882
|
The study is distributed over "n_workers" workers.
|
|
@@ -950,7 +965,7 @@ class Microwave3D:
|
|
|
950
965
|
self.solveroutine.reset()
|
|
951
966
|
### Compute S-parameters and return
|
|
952
967
|
self._post_process([job,], [mats,])
|
|
953
|
-
return self.data
|
|
968
|
+
return self.data, job._pec_tris
|
|
954
969
|
|
|
955
970
|
def eigenmode(self, search_frequency: float,
|
|
956
971
|
nmodes: int = 6,
|
|
@@ -1045,14 +1060,21 @@ class Microwave3D:
|
|
|
1045
1060
|
"""
|
|
1046
1061
|
if self.basis is None:
|
|
1047
1062
|
raise SimulationError('Cannot post-process. Simulation basis function is undefined.')
|
|
1063
|
+
|
|
1048
1064
|
mesh = self.mesh
|
|
1049
1065
|
all_ports = self.bc.oftype(PortBC)
|
|
1050
1066
|
port_numbers = [port.port_number for port in all_ports]
|
|
1051
1067
|
|
|
1052
1068
|
logger.info('Computing S-parameters')
|
|
1053
1069
|
|
|
1054
|
-
|
|
1055
|
-
|
|
1070
|
+
not_conserved = False
|
|
1071
|
+
|
|
1072
|
+
single_corr = self._settings.mw_cap_sp_single
|
|
1073
|
+
col_corr = self._settings.mw_cap_sp_col
|
|
1074
|
+
recip_corr = self._settings.mw_recip_sp
|
|
1075
|
+
|
|
1076
|
+
for job, mats in zip(results, materials):
|
|
1077
|
+
freq = job.freq
|
|
1056
1078
|
er, ur, cond = mats
|
|
1057
1079
|
ertri = np.zeros((3,3,self.mesh.n_tris), dtype=np.complex128)
|
|
1058
1080
|
urtri = np.zeros((3,3,self.mesh.n_tris), dtype=np.complex128)
|
|
@@ -1078,6 +1100,7 @@ class Microwave3D:
|
|
|
1078
1100
|
|
|
1079
1101
|
logger.info(f'Post Processing simulation frequency = {freq/1e9:.3f} GHz')
|
|
1080
1102
|
|
|
1103
|
+
|
|
1081
1104
|
# Recording port information
|
|
1082
1105
|
for active_port in all_ports:
|
|
1083
1106
|
port_tets = self.mesh.get_face_tets(active_port.tags)
|
|
@@ -1123,10 +1146,36 @@ class Microwave3D:
|
|
|
1123
1146
|
pfield, pmode = self._compute_s_data(bc, fieldf,tri_vertices, k0, ertri[:,:,tris], urtri[:,:,tris])
|
|
1124
1147
|
logger.debug(f'[{bc.port_number}] Passive amplitude = {np.abs(pfield):.3f}')
|
|
1125
1148
|
scalardata.write_S(bc.port_number, active_port.port_number, pfield/Pout)
|
|
1149
|
+
if abs(pfield/Pout) > 1.0:
|
|
1150
|
+
logger.warning(f'S-parameter > 1.0 detected: {np.abs(pfield/Pout)}')
|
|
1151
|
+
not_conserved = True
|
|
1126
1152
|
active_port.active=False
|
|
1127
1153
|
|
|
1154
|
+
|
|
1128
1155
|
fielddata.set_field_vector()
|
|
1156
|
+
|
|
1157
|
+
N = scalardata.Sp.shape[1]
|
|
1158
|
+
|
|
1159
|
+
# Enforce reciprocity
|
|
1160
|
+
if recip_corr:
|
|
1161
|
+
scalardata.Sp = (scalardata.Sp + scalardata.Sp.T)/2
|
|
1162
|
+
|
|
1163
|
+
# Enforce energy conservation
|
|
1164
|
+
if col_corr:
|
|
1165
|
+
for j in range(N):
|
|
1166
|
+
scalardata.Sp[:,j] = scalardata.Sp[:,j] / max(1.0, np.sum(np.abs(scalardata.Sp[:,j])**2))
|
|
1167
|
+
|
|
1168
|
+
# Enforce S-parameter limit to 1.0
|
|
1169
|
+
if single_corr:
|
|
1170
|
+
for i,j in product(range(N), range(N)):
|
|
1171
|
+
scalardata.Sp[i,j] = scalardata.Sp[i,j] / max(1.0, np.abs(scalardata.Sp[i,j]))
|
|
1172
|
+
|
|
1173
|
+
|
|
1129
1174
|
|
|
1175
|
+
if not_conserved:
|
|
1176
|
+
DEBUG_COLLECTOR.add_report('S-parameters with an amplitude greater than 1.0 detected. This could be due to a ModalPort with the wrong mode type.\n' +
|
|
1177
|
+
'Specify the type of mode (TE/TM/TEM) in the constructor using ModalPort(..., modetype=\'TE\') for example.\n' +
|
|
1178
|
+
f'Values slightly greater than 1 are possible due to numerical accuracy. Automatic normalization = {single_corr or col_corr}')
|
|
1130
1179
|
logger.info('Simulation Complete!')
|
|
1131
1180
|
self._simend = time.time()
|
|
1132
1181
|
logger.info(f'Elapsed time = {(self._simend-self._simstart):.2f} seconds.')
|
|
@@ -1152,6 +1201,7 @@ class Microwave3D:
|
|
|
1152
1201
|
tuple[complex, complex]: _description_
|
|
1153
1202
|
"""
|
|
1154
1203
|
from .sparam import sparam_field_power, sparam_mode_power
|
|
1204
|
+
|
|
1155
1205
|
if bc.v_integration:
|
|
1156
1206
|
if bc.vintline is None:
|
|
1157
1207
|
raise SimulationError('Trying to compute characteristic impedance but no integration line is defined.')
|
|
@@ -1177,7 +1227,7 @@ class Microwave3D:
|
|
|
1177
1227
|
else:
|
|
1178
1228
|
if bc.modetype(k0) == 'TEM':
|
|
1179
1229
|
const = 1/(np.sqrt((urp[0,0,:] + urp[1,1,:] + urp[2,2,:])/(erp[0,0,:] + erp[1,1,:] + erp[2,2,:])))
|
|
1180
|
-
|
|
1230
|
+
elif bc.modetype(k0) == 'TE':
|
|
1181
1231
|
const = 1/((urp[0,0,:] + urp[1,1,:] + urp[2,2,:])/3)
|
|
1182
1232
|
elif bc.modetype(k0) == 'TM':
|
|
1183
1233
|
const = 1/((erp[0,0,:] + erp[1,1,:] + erp[2,2,:])/3)
|