emerge 1.0.6__py3-none-any.whl → 1.1.0__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 +14 -3
- emerge/_emerge/elements/index_interp.py +45 -0
- emerge/_emerge/geo/pcb.py +132 -59
- emerge/_emerge/geo/shapes.py +11 -7
- emerge/_emerge/geometry.py +23 -8
- emerge/_emerge/logsettings.py +26 -2
- emerge/_emerge/material.py +1 -1
- emerge/_emerge/mesh3d.py +38 -8
- emerge/_emerge/mesher.py +61 -11
- emerge/_emerge/physics/microwave/adaptive_mesh.py +667 -0
- emerge/_emerge/physics/microwave/assembly/assembler.py +6 -5
- emerge/_emerge/physics/microwave/microwave_3d.py +251 -88
- emerge/_emerge/physics/microwave/microwave_bc.py +182 -11
- emerge/_emerge/physics/microwave/microwave_data.py +23 -6
- emerge/_emerge/plot/pyvista/display.py +31 -18
- emerge/_emerge/settings.py +124 -6
- emerge/_emerge/simmodel.py +238 -95
- 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 +2 -2
- {emerge-1.0.6.dist-info → emerge-1.1.0.dist-info}/METADATA +9 -4
- {emerge-1.0.6.dist-info → emerge-1.1.0.dist-info}/RECORD +26 -24
- {emerge-1.0.6.dist-info → emerge-1.1.0.dist-info}/WHEEL +0 -0
- {emerge-1.0.6.dist-info → emerge-1.1.0.dist-info}/entry_points.txt +0 -0
- {emerge-1.0.6.dist-info → emerge-1.1.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -26,6 +26,9 @@ 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,38 +132,33 @@ 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
|
-
|
|
143
|
-
def
|
|
144
|
-
self.
|
|
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
|
+
|
|
152
|
+
def reset(self, _reset_bc: bool = True):
|
|
153
|
+
if _reset_bc:
|
|
154
|
+
self.bc = MWBoundaryConditionSet(None)
|
|
155
|
+
else:
|
|
156
|
+
for bc in self.bc.oftype(ModalPort):
|
|
157
|
+
bc.reset()
|
|
158
|
+
|
|
145
159
|
self.basis: FEMBasis = None
|
|
146
|
-
self.bc = MWBoundaryConditionSet(None)
|
|
147
160
|
self.solveroutine.reset()
|
|
148
|
-
|
|
149
|
-
def set_order(self, order: int) -> None:
|
|
150
|
-
"""Sets the order of the basis functions used. Currently only supports second order.
|
|
151
|
-
|
|
152
|
-
Args:
|
|
153
|
-
order (int): The order to use.
|
|
154
|
-
|
|
155
|
-
Raises:
|
|
156
|
-
ValueError: An error if a wrong order is used.
|
|
157
|
-
"""
|
|
158
|
-
if order not in (2,):
|
|
159
|
-
raise ValueError(f'Order {order} not supported. Only order-2 allowed.')
|
|
160
|
-
|
|
161
|
-
self.order = order
|
|
162
|
-
self.resolution = {1: 0.15, 2: 0.3}[order]
|
|
161
|
+
self.assembler.cached_matrices = None
|
|
163
162
|
|
|
164
163
|
@property
|
|
165
164
|
def nports(self) -> int:
|
|
@@ -217,7 +216,15 @@ class Microwave3D:
|
|
|
217
216
|
self.frequencies = list(frequency)
|
|
218
217
|
else:
|
|
219
218
|
self.frequencies = [frequency]
|
|
220
|
-
|
|
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
|
+
|
|
221
228
|
self.mesher.max_size = self.resolution * 299792458 / max(self.frequencies)
|
|
222
229
|
self.mesher.min_size = 0.1 * self.mesher.max_size
|
|
223
230
|
|
|
@@ -359,29 +366,6 @@ class Microwave3D:
|
|
|
359
366
|
logger.trace(f' - Port[{port.port_number}] integration line {start} -> {end}.')
|
|
360
367
|
|
|
361
368
|
port.v_integration = True
|
|
362
|
-
|
|
363
|
-
def _compute_integration_line(self, group1: list[int], group2: list[int]) -> tuple[np.ndarray, np.ndarray]:
|
|
364
|
-
"""Computes an integration line for two node island groups by finding the closest two nodes.
|
|
365
|
-
|
|
366
|
-
This method is used for the modal TEM analysis to find an appropriate voltage integration path
|
|
367
|
-
by looking for the two closest points for the two conductor islands that where discovered.
|
|
368
|
-
|
|
369
|
-
Currently it defaults to 11 integration line points.
|
|
370
|
-
|
|
371
|
-
Args:
|
|
372
|
-
group1 (list[int]): The first island node group
|
|
373
|
-
group2 (list[int]): The second island node group
|
|
374
|
-
|
|
375
|
-
Returns:
|
|
376
|
-
centers (np.ndarray): The center points of the line segments
|
|
377
|
-
dls (np.ndarray): The delta-path vectors for each line segment.
|
|
378
|
-
"""
|
|
379
|
-
nodes1 = self.mesh.nodes[:,group1]
|
|
380
|
-
nodes2 = self.mesh.nodes[:,group2]
|
|
381
|
-
path = shortest_path(nodes1, nodes2, 21)
|
|
382
|
-
centres = (path[:,1:] + path[:,:-1])/2
|
|
383
|
-
dls = path[:,1:] - path[:,:-1]
|
|
384
|
-
return centres, dls
|
|
385
369
|
|
|
386
370
|
def _find_tem_conductors(self, port: ModalPort, sigtri: np.ndarray) -> tuple[list[int], list[int]]:
|
|
387
371
|
''' Returns two lists of global node indices corresponding to the TEM port conductors.
|
|
@@ -401,8 +385,10 @@ class Microwave3D:
|
|
|
401
385
|
raise ValueError('The field basis is not yet defined.')
|
|
402
386
|
|
|
403
387
|
logger.debug(' - Finding PEC TEM conductors')
|
|
404
|
-
pecs: list[PEC] = self.bc.get_conductors() # type: ignore
|
|
405
388
|
mesh = self.mesh
|
|
389
|
+
|
|
390
|
+
# Find all BC conductors
|
|
391
|
+
pecs: list[PEC] = self.bc.get_conductors() # type: ignore
|
|
406
392
|
|
|
407
393
|
# Process all PEC Boundary Conditions
|
|
408
394
|
pec_edges = []
|
|
@@ -418,19 +404,38 @@ class Microwave3D:
|
|
|
418
404
|
edge_ids = list(mesh.tri_to_edge[:,itri].flatten())
|
|
419
405
|
pec_edges.extend(edge_ids)
|
|
420
406
|
|
|
407
|
+
# All PEC edges
|
|
421
408
|
pec_edges = list(set(pec_edges))
|
|
422
409
|
|
|
410
|
+
# Port mesh data
|
|
423
411
|
tri_ids = mesh.get_triangles(port.tags)
|
|
424
412
|
edge_ids = list(mesh.tri_to_edge[:,tri_ids].flatten())
|
|
425
413
|
|
|
426
|
-
|
|
414
|
+
port_pec_edges = np.array([i for i in pec_edges if i in set(edge_ids)])
|
|
427
415
|
|
|
428
|
-
pec_islands = mesh.find_edge_groups(
|
|
416
|
+
pec_islands = mesh.find_edge_groups(port_pec_edges)
|
|
429
417
|
|
|
418
|
+
|
|
430
419
|
logger.debug(f' - Found {len(pec_islands)} PEC islands.')
|
|
431
420
|
|
|
432
421
|
if len(pec_islands) != 2:
|
|
433
|
-
|
|
422
|
+
pec_island_tags = {i: self.mesh._get_dimtags(edges=pec_edge_group) for i,pec_edge_group in enumerate(pec_islands)}
|
|
423
|
+
plus_term = None
|
|
424
|
+
min_term = None
|
|
425
|
+
|
|
426
|
+
for i, dimtags in pec_island_tags.items():
|
|
427
|
+
if not set(dimtags).isdisjoint(port.plus_terminal):
|
|
428
|
+
plus_term = i
|
|
429
|
+
|
|
430
|
+
if not set(dimtags).isdisjoint(port.minus_terminal):
|
|
431
|
+
min_term = i
|
|
432
|
+
|
|
433
|
+
if plus_term is None or min_term is None:
|
|
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
|
|
436
|
+
logger.debug(f'Positive island = {pec_island_tags[plus_term]}')
|
|
437
|
+
logger.debug(f'Negative island = {pec_island_tags[min_term]}')
|
|
438
|
+
pec_islands = [pec_islands[plus_term], pec_islands[min_term]]
|
|
434
439
|
|
|
435
440
|
groups = []
|
|
436
441
|
for island in pec_islands:
|
|
@@ -457,7 +462,11 @@ class Microwave3D:
|
|
|
457
462
|
if not bc.mixed_materials and bc.initialized:
|
|
458
463
|
continue
|
|
459
464
|
|
|
460
|
-
|
|
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)
|
|
461
470
|
|
|
462
471
|
def modal_analysis(self,
|
|
463
472
|
port: ModalPort,
|
|
@@ -551,7 +560,7 @@ class Microwave3D:
|
|
|
551
560
|
target_kz = k0*target_neff
|
|
552
561
|
|
|
553
562
|
if target_kz is None:
|
|
554
|
-
if TEM:
|
|
563
|
+
if TEM or port.forced_modetype=='TEM':
|
|
555
564
|
target_kz = ermean*urmean*1.1*k0
|
|
556
565
|
else:
|
|
557
566
|
|
|
@@ -573,19 +582,28 @@ class Microwave3D:
|
|
|
573
582
|
Emode = np.zeros((nlf.n_field,), dtype=np.complex128)
|
|
574
583
|
eigenmode = eigen_modes[:,i]
|
|
575
584
|
Emode[solve_ids] = np.squeeze(eigenmode)
|
|
576
|
-
Emode = Emode * np.exp(-1j*np.angle(np.
|
|
585
|
+
Emode = Emode * np.exp(-1j*np.angle(Emode[np.argmax(np.abs(Emode))]))
|
|
577
586
|
|
|
578
587
|
beta_base = np.emath.sqrt(-eigen_values[i])
|
|
579
588
|
beta = min(k0*np.sqrt(ermax*urmax), beta_base)
|
|
580
589
|
|
|
581
590
|
residuals = -1
|
|
582
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
|
+
|
|
583
601
|
portfE = nlf.interpolate_Ef(Emode)
|
|
584
602
|
portfH = nlf.interpolate_Hf(Emode, k0, ur, beta)
|
|
585
|
-
|
|
586
603
|
P = compute_avg_power_flux(nlf, Emode, k0, ur, beta)
|
|
587
604
|
|
|
588
|
-
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
|
+
|
|
589
607
|
if mode is None:
|
|
590
608
|
continue
|
|
591
609
|
|
|
@@ -594,20 +612,40 @@ class Microwave3D:
|
|
|
594
612
|
Ez = np.max(np.abs(Efz))
|
|
595
613
|
Exy = np.max(np.abs(Efxy))
|
|
596
614
|
|
|
597
|
-
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':
|
|
598
643
|
logger.debug('Low Ez/Et ratio detected, assuming TE mode')
|
|
599
644
|
mode.modetype = 'TE'
|
|
600
|
-
elif Ez/Exy > 1e-1
|
|
645
|
+
elif Ez/Exy > 1e-1 or port.forced_modetype=='TM':
|
|
601
646
|
logger.debug('High Ez/Et ratio detected, assuming TM mode')
|
|
602
647
|
mode.modetype = 'TM'
|
|
603
|
-
|
|
604
|
-
G1, G2 = self._find_tem_conductors(port, sigtri=cond)
|
|
605
|
-
cs, dls = self._compute_integration_line(G1,G2)
|
|
606
|
-
mode.modetype = 'TEM'
|
|
607
|
-
Ex, Ey, Ez = portfE(cs[0,:], cs[1,:], cs[2,:])
|
|
608
|
-
voltage = np.sum(Ex*dls[0,:] + Ey*dls[1,:] + Ez*dls[2,:])
|
|
609
|
-
mode.Z0 = abs(voltage**2/(2*P))
|
|
610
|
-
logger.debug(f'Port Z0 = {mode.Z0}')
|
|
648
|
+
|
|
611
649
|
|
|
612
650
|
mode.set_power(P*port._qmode(k0)**2)
|
|
613
651
|
|
|
@@ -787,7 +825,7 @@ class Microwave3D:
|
|
|
787
825
|
"if __name__ == '__main__' guard in the top-level script."
|
|
788
826
|
)
|
|
789
827
|
# Start parallel pool
|
|
790
|
-
with mp.Pool(processes=n_workers
|
|
828
|
+
with mp.Pool(processes=n_workers) as pool:
|
|
791
829
|
for i_group, fgroup in enumerate(freq_groups):
|
|
792
830
|
logger.debug(f'Precomputing group {i_group}.')
|
|
793
831
|
jobs = []
|
|
@@ -835,6 +873,100 @@ class Microwave3D:
|
|
|
835
873
|
self._post_process(results, matset)
|
|
836
874
|
return self.data
|
|
837
875
|
|
|
876
|
+
def _run_adaptive_mesh(self,
|
|
877
|
+
iteration: int,
|
|
878
|
+
frequency: float,
|
|
879
|
+
automatic_modal_analysis: bool = True) -> MWData:
|
|
880
|
+
"""Executes a frequency domain study
|
|
881
|
+
|
|
882
|
+
The study is distributed over "n_workers" workers.
|
|
883
|
+
As optional parameter you may set a harddisc_threshold as integer. This determines the maximum
|
|
884
|
+
number of degrees of freedom before which the jobs will be cahced to the harddisk. The
|
|
885
|
+
path that will be used to cache the sparse matrices can be specified.
|
|
886
|
+
Additionally the term frequency_groups may be specified. This number will define in how
|
|
887
|
+
many groups the matrices will be pre-computed before they are send to workers. This can minimize
|
|
888
|
+
the total amound of RAM memory used. For example with 11 frequencies in gruops of 4, the following
|
|
889
|
+
frequency indices will be precomputed and then solved: [[1,2,3,4],[5,6,7,8],[9,10,11]]
|
|
890
|
+
|
|
891
|
+
Args:
|
|
892
|
+
iteration (int): The iteration number
|
|
893
|
+
frequency (float): The simulation frequency
|
|
894
|
+
|
|
895
|
+
Raises:
|
|
896
|
+
SimulationError: An error associated witha a problem during the simulation.
|
|
897
|
+
|
|
898
|
+
Returns:
|
|
899
|
+
MWSimData: The dataset.
|
|
900
|
+
"""
|
|
901
|
+
|
|
902
|
+
self._simstart = time.time()
|
|
903
|
+
if self.bc._initialized is False:
|
|
904
|
+
raise SimulationError('Cannot run a modal analysis because no boundary conditions have been assigned.')
|
|
905
|
+
|
|
906
|
+
self._initialize_field()
|
|
907
|
+
self._initialize_bc_data()
|
|
908
|
+
self._check_physics()
|
|
909
|
+
|
|
910
|
+
if self.basis is None:
|
|
911
|
+
raise SimulationError('Cannot proceed, the simulation basis class is undefined.')
|
|
912
|
+
|
|
913
|
+
materials = self.mesh._get_material_assignment(self.mesher.volumes)
|
|
914
|
+
|
|
915
|
+
### Does this move
|
|
916
|
+
logger.debug('Initializing single frequency settings.')
|
|
917
|
+
|
|
918
|
+
#### Port settings
|
|
919
|
+
all_ports = self.bc.oftype(PortBC)
|
|
920
|
+
|
|
921
|
+
##### FOR PORT SWEEP SET ALL ACTIVE TO FALSE. THIS SHOULD BE FIXED LATER
|
|
922
|
+
### COMPUTE WHICH TETS ARE CONNECTED TO PORT INDICES
|
|
923
|
+
|
|
924
|
+
for port in all_ports:
|
|
925
|
+
port.active=False
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
def run_job_single(job: SimJob):
|
|
929
|
+
for A, b, ids, reuse, aux in job.iter_Ab():
|
|
930
|
+
solution, report = self.solveroutine.solve(A, b, ids, reuse, id=job.id)
|
|
931
|
+
report.add(**aux)
|
|
932
|
+
job.submit_solution(solution, report)
|
|
933
|
+
return job
|
|
934
|
+
|
|
935
|
+
|
|
936
|
+
matset: list[tuple[np.ndarray, np.ndarray, np.ndarray]] = []
|
|
937
|
+
|
|
938
|
+
self._compute_modes(frequency)
|
|
939
|
+
|
|
940
|
+
logger.debug(f'Simulation frequency = {frequency/1e9:.3f} GHz')
|
|
941
|
+
|
|
942
|
+
if automatic_modal_analysis:
|
|
943
|
+
self._compute_modes(frequency)
|
|
944
|
+
|
|
945
|
+
job, mats = self.assembler.assemble_freq_matrix(self.basis, materials,
|
|
946
|
+
self.bc.boundary_conditions,
|
|
947
|
+
frequency,
|
|
948
|
+
cache_matrices=self.cache_matrices)
|
|
949
|
+
|
|
950
|
+
job.id = 0
|
|
951
|
+
|
|
952
|
+
logger.info('Starting solve')
|
|
953
|
+
job = run_job_single(job)
|
|
954
|
+
|
|
955
|
+
|
|
956
|
+
logger.info('Solving complete')
|
|
957
|
+
|
|
958
|
+
self.data.setreport(job.reports, freq=frequency, **self._params)
|
|
959
|
+
|
|
960
|
+
for variables, data in self.data.sim.iterate():
|
|
961
|
+
logger.trace(f'Sim variable: {variables}')
|
|
962
|
+
for item in data['report']:
|
|
963
|
+
item.logprint(logger.trace)
|
|
964
|
+
|
|
965
|
+
self.solveroutine.reset()
|
|
966
|
+
### Compute S-parameters and return
|
|
967
|
+
self._post_process([job,], [mats,])
|
|
968
|
+
return self.data
|
|
969
|
+
|
|
838
970
|
def eigenmode(self, search_frequency: float,
|
|
839
971
|
nmodes: int = 6,
|
|
840
972
|
k0_limit: float = 1,
|
|
@@ -868,10 +1000,6 @@ class Microwave3D:
|
|
|
868
1000
|
|
|
869
1001
|
materials = self.mesh._get_material_assignment(self.mesher.volumes)
|
|
870
1002
|
|
|
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
1003
|
### Does this move
|
|
876
1004
|
logger.debug('Initializing frequency domain sweep.')
|
|
877
1005
|
|
|
@@ -932,14 +1060,21 @@ class Microwave3D:
|
|
|
932
1060
|
"""
|
|
933
1061
|
if self.basis is None:
|
|
934
1062
|
raise SimulationError('Cannot post-process. Simulation basis function is undefined.')
|
|
1063
|
+
|
|
935
1064
|
mesh = self.mesh
|
|
936
1065
|
all_ports = self.bc.oftype(PortBC)
|
|
937
1066
|
port_numbers = [port.port_number for port in all_ports]
|
|
938
1067
|
|
|
939
1068
|
logger.info('Computing S-parameters')
|
|
940
1069
|
|
|
941
|
-
|
|
942
|
-
|
|
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
|
|
943
1078
|
er, ur, cond = mats
|
|
944
1079
|
ertri = np.zeros((3,3,self.mesh.n_tris), dtype=np.complex128)
|
|
945
1080
|
urtri = np.zeros((3,3,self.mesh.n_tris), dtype=np.complex128)
|
|
@@ -965,6 +1100,7 @@ class Microwave3D:
|
|
|
965
1100
|
|
|
966
1101
|
logger.info(f'Post Processing simulation frequency = {freq/1e9:.3f} GHz')
|
|
967
1102
|
|
|
1103
|
+
|
|
968
1104
|
# Recording port information
|
|
969
1105
|
for active_port in all_ports:
|
|
970
1106
|
port_tets = self.mesh.get_face_tets(active_port.tags)
|
|
@@ -1010,10 +1146,36 @@ class Microwave3D:
|
|
|
1010
1146
|
pfield, pmode = self._compute_s_data(bc, fieldf,tri_vertices, k0, ertri[:,:,tris], urtri[:,:,tris])
|
|
1011
1147
|
logger.debug(f'[{bc.port_number}] Passive amplitude = {np.abs(pfield):.3f}')
|
|
1012
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
|
|
1013
1152
|
active_port.active=False
|
|
1014
1153
|
|
|
1154
|
+
|
|
1015
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
|
+
|
|
1016
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}')
|
|
1017
1179
|
logger.info('Simulation Complete!')
|
|
1018
1180
|
self._simend = time.time()
|
|
1019
1181
|
logger.info(f'Elapsed time = {(self._simend-self._simstart):.2f} seconds.')
|
|
@@ -1039,6 +1201,7 @@ class Microwave3D:
|
|
|
1039
1201
|
tuple[complex, complex]: _description_
|
|
1040
1202
|
"""
|
|
1041
1203
|
from .sparam import sparam_field_power, sparam_mode_power
|
|
1204
|
+
|
|
1042
1205
|
if bc.v_integration:
|
|
1043
1206
|
if bc.vintline is None:
|
|
1044
1207
|
raise SimulationError('Trying to compute characteristic impedance but no integration line is defined.')
|
|
@@ -1064,7 +1227,7 @@ class Microwave3D:
|
|
|
1064
1227
|
else:
|
|
1065
1228
|
if bc.modetype(k0) == 'TEM':
|
|
1066
1229
|
const = 1/(np.sqrt((urp[0,0,:] + urp[1,1,:] + urp[2,2,:])/(erp[0,0,:] + erp[1,1,:] + erp[2,2,:])))
|
|
1067
|
-
|
|
1230
|
+
elif bc.modetype(k0) == 'TE':
|
|
1068
1231
|
const = 1/((urp[0,0,:] + urp[1,1,:] + urp[2,2,:])/3)
|
|
1069
1232
|
elif bc.modetype(k0) == 'TM':
|
|
1070
1233
|
const = 1/((erp[0,0,:] + erp[1,1,:] + erp[2,2,:])/3)
|