emerge 0.6.6__py3-none-any.whl → 0.6.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of emerge might be problematic. Click here for more details.
- emerge/__init__.py +2 -2
- emerge/_emerge/_cache_check.py +1 -1
- emerge/_emerge/elements/femdata.py +3 -2
- emerge/_emerge/elements/index_interp.py +1 -2
- emerge/_emerge/elements/ned2_interp.py +16 -16
- emerge/_emerge/elements/nedelec2.py +17 -6
- emerge/_emerge/elements/nedleg2.py +21 -9
- emerge/_emerge/geo/__init__.py +1 -1
- emerge/_emerge/geo/horn.py +0 -1
- emerge/_emerge/geo/modeler.py +1 -1
- emerge/_emerge/geo/operations.py +44 -4
- emerge/_emerge/geo/pcb.py +44 -9
- emerge/_emerge/geo/pcb_tools/calculator.py +2 -3
- emerge/_emerge/geo/pmlbox.py +35 -11
- emerge/_emerge/geo/shapes.py +61 -26
- emerge/_emerge/geometry.py +4 -4
- emerge/_emerge/material.py +326 -81
- emerge/_emerge/mesh3d.py +14 -8
- emerge/_emerge/physics/microwave/assembly/assembler.py +43 -20
- emerge/_emerge/physics/microwave/microwave_3d.py +57 -44
- emerge/_emerge/physics/microwave/microwave_bc.py +12 -8
- emerge/_emerge/physics/microwave/microwave_data.py +74 -5
- emerge/_emerge/plot/pyvista/display.py +132 -24
- emerge/_emerge/plot/simple_plots.py +1 -1
- emerge/_emerge/simmodel.py +22 -13
- emerge/_emerge/solver.py +49 -22
- {emerge-0.6.6.dist-info → emerge-0.6.8.dist-info}/METADATA +1 -1
- {emerge-0.6.6.dist-info → emerge-0.6.8.dist-info}/RECORD +31 -31
- {emerge-0.6.6.dist-info → emerge-0.6.8.dist-info}/licenses/LICENSE +2 -2
- {emerge-0.6.6.dist-info → emerge-0.6.8.dist-info}/WHEEL +0 -0
- {emerge-0.6.6.dist-info → emerge-0.6.8.dist-info}/entry_points.txt +0 -0
emerge/_emerge/mesh3d.py
CHANGED
|
@@ -28,6 +28,7 @@ from loguru import logger
|
|
|
28
28
|
from functools import cache
|
|
29
29
|
from .bc import Periodic
|
|
30
30
|
from .mth.pairing import pair_coordinates
|
|
31
|
+
from .material import Material
|
|
31
32
|
|
|
32
33
|
@njit(f8(f8[:], f8[:], f8[:]), cache=True, nogil=True)
|
|
33
34
|
def area(x1: np.ndarray, x2: np.ndarray, x3: np.ndarray):
|
|
@@ -538,10 +539,14 @@ class Mesh3D(Mesh):
|
|
|
538
539
|
return conv_map, np.array(node_ids_2_unsorted), np.array(node_ids_2_sorted)
|
|
539
540
|
|
|
540
541
|
|
|
541
|
-
def retreive(self,
|
|
542
|
+
def retreive(self, volumes: list[GeoVolume]) -> list[Material]:
|
|
542
543
|
'''Retrieve the material properties of the geometry'''
|
|
543
|
-
arry = np.zeros((3,3,self.n_tets,), dtype=np.complex128)
|
|
544
|
-
|
|
544
|
+
#arry = np.zeros((3,3,self.n_tets,), dtype=np.complex128)
|
|
545
|
+
for vol in volumes:
|
|
546
|
+
vol.material.reset()
|
|
547
|
+
|
|
548
|
+
materials = []
|
|
549
|
+
|
|
545
550
|
xs = self.centers[0,:]
|
|
546
551
|
ys = self.centers[1,:]
|
|
547
552
|
zs = self.centers[2,:]
|
|
@@ -551,11 +556,12 @@ class Mesh3D(Mesh):
|
|
|
551
556
|
for dimtag in volume.dimtags:
|
|
552
557
|
etype, etag_list, ntags = gmsh.model.mesh.get_elements(*dimtag)
|
|
553
558
|
for etags in etag_list:
|
|
554
|
-
tet_ids = [self.tet_t2i[t] for t in etags]
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
+
tet_ids = np.array([self.tet_t2i[t] for t in etags])
|
|
560
|
+
volume.material.initialize(xs[tet_ids], ys[tet_ids], zs[tet_ids], tet_ids)
|
|
561
|
+
if volume.material not in materials:
|
|
562
|
+
materials.append(volume.material)
|
|
563
|
+
|
|
564
|
+
return materials
|
|
559
565
|
|
|
560
566
|
def plot_gmsh(self) -> None:
|
|
561
567
|
gmsh.fltk.run()
|
|
@@ -21,7 +21,7 @@ from ....elements.nedelec2 import Nedelec2
|
|
|
21
21
|
from ....elements.nedleg2 import NedelecLegrange2
|
|
22
22
|
from ....mth.optimized import gaus_quad_tri
|
|
23
23
|
from ....mth.pairing import pair_coordinates
|
|
24
|
-
|
|
24
|
+
from ....material import Material
|
|
25
25
|
from scipy.sparse import csr_matrix
|
|
26
26
|
from loguru import logger
|
|
27
27
|
from ..simjob import SimJob
|
|
@@ -188,9 +188,7 @@ class Assembler:
|
|
|
188
188
|
return E, B, np.array(solve_ids), nedlegfield
|
|
189
189
|
|
|
190
190
|
def assemble_freq_matrix(self, field: Nedelec2,
|
|
191
|
-
|
|
192
|
-
ur: np.ndarray,
|
|
193
|
-
sig: np.ndarray,
|
|
191
|
+
materials: list[Material],
|
|
194
192
|
bcs: list[BoundaryCondition],
|
|
195
193
|
frequency: float,
|
|
196
194
|
cache_matrices: bool = False) -> SimJob:
|
|
@@ -215,11 +213,29 @@ class Assembler:
|
|
|
215
213
|
# PREDEFINE CONSTANTS
|
|
216
214
|
W0 = 2*np.pi*frequency
|
|
217
215
|
K0 = W0/C0
|
|
218
|
-
|
|
216
|
+
|
|
217
|
+
is_frequency_dependent = False
|
|
219
218
|
mesh = field.mesh
|
|
220
|
-
|
|
221
|
-
|
|
219
|
+
|
|
220
|
+
for mat in materials:
|
|
221
|
+
if mat.frequency_dependent:
|
|
222
|
+
is_frequency_dependent = True
|
|
223
|
+
break
|
|
224
|
+
|
|
225
|
+
er = np.zeros((3,3,field.mesh.n_tets), dtype=np.complex128)
|
|
226
|
+
tand = np.zeros((3,3,field.mesh.n_tets), dtype=np.complex128)
|
|
227
|
+
cond = np.zeros((3,3,field.mesh.n_tets), dtype=np.complex128)
|
|
228
|
+
ur = np.zeros((3,3,field.mesh.n_tets), dtype=np.complex128)
|
|
229
|
+
|
|
230
|
+
for mat in materials:
|
|
231
|
+
er = mat.er(frequency, er)
|
|
232
|
+
ur = mat.ur(frequency, ur)
|
|
233
|
+
tand = mat.tand(frequency, tand)
|
|
234
|
+
cond = mat.cond(frequency, cond)
|
|
222
235
|
|
|
236
|
+
er = er*(1-1j*tand) - 1j*cond/(W0*EPS0)
|
|
237
|
+
|
|
238
|
+
is_frequency_dependent = is_frequency_dependent or np.any((cond > 0) & (cond < self.conductivity_limit)) # type: ignore
|
|
223
239
|
|
|
224
240
|
if cache_matrices and not is_frequency_dependent and self.cached_matrices is not None:
|
|
225
241
|
# IF CACHED AND AVAILABLE PULL E AND B FROM CACHE
|
|
@@ -253,10 +269,11 @@ class Assembler:
|
|
|
253
269
|
|
|
254
270
|
logger.debug('Implementing PEC Boundary Conditions.')
|
|
255
271
|
pec_ids: list[int] = []
|
|
272
|
+
|
|
256
273
|
# Conductivity above al imit, consider it all PEC
|
|
257
274
|
ipec = 0
|
|
258
275
|
for itet in range(field.n_tets):
|
|
259
|
-
if
|
|
276
|
+
if cond[0,0,itet] > self.conductivity_limit:
|
|
260
277
|
ipec+=1
|
|
261
278
|
pec_ids.extend(field.tet_to_field[:,itet])
|
|
262
279
|
if ipec>0:
|
|
@@ -388,12 +405,10 @@ class Assembler:
|
|
|
388
405
|
simjob.Pd = Pmat.getH()
|
|
389
406
|
simjob.has_periodic = has_periodic
|
|
390
407
|
|
|
391
|
-
return simjob
|
|
408
|
+
return simjob, (er, ur, cond)
|
|
392
409
|
|
|
393
410
|
def assemble_eig_matrix(self, field: Nedelec2,
|
|
394
|
-
|
|
395
|
-
ur: np.ndarray,
|
|
396
|
-
sig: np.ndarray,
|
|
411
|
+
materials: list[Material],
|
|
397
412
|
bcs: list[BoundaryCondition],
|
|
398
413
|
frequency: float) -> SimJob:
|
|
399
414
|
"""Assembles the eigenmode analysis matrix
|
|
@@ -420,9 +435,21 @@ class Assembler:
|
|
|
420
435
|
w0 = 2*np.pi*frequency
|
|
421
436
|
k0 = w0/C0
|
|
422
437
|
|
|
423
|
-
er =
|
|
438
|
+
er = np.zeros((3,3,field.mesh.n_tets), dtype=np.complex128)
|
|
439
|
+
tand = np.zeros((3,3,field.mesh.n_tets), dtype=np.complex128)
|
|
440
|
+
cond = np.zeros((3,3,field.mesh.n_tets), dtype=np.complex128)
|
|
441
|
+
ur = np.zeros((3,3,field.mesh.n_tets), dtype=np.complex128)
|
|
442
|
+
|
|
443
|
+
for mat in materials:
|
|
444
|
+
er = mat.er(frequency, er)
|
|
445
|
+
ur = mat.ur(frequency, ur)
|
|
446
|
+
tand = mat.tand(frequency, tand)
|
|
447
|
+
cond = mat.cond(frequency, cond)
|
|
448
|
+
|
|
449
|
+
er = er*(1-1j*tand) - 1j*cond/(w0*EPS0)
|
|
424
450
|
|
|
425
451
|
logger.debug('Assembling matrices')
|
|
452
|
+
|
|
426
453
|
E, B = tet_mass_stiffness_matrices(field, er, ur)
|
|
427
454
|
self.cached_matrices = (E, B)
|
|
428
455
|
|
|
@@ -439,7 +466,7 @@ class Assembler:
|
|
|
439
466
|
|
|
440
467
|
# Conductivity above a limit, consider it all PEC
|
|
441
468
|
for itet in range(field.n_tets):
|
|
442
|
-
if
|
|
469
|
+
if cond[0,0,itet] > self.conductivity_limit:
|
|
443
470
|
pec_ids.extend(field.tet_to_field[:,itet])
|
|
444
471
|
|
|
445
472
|
# PEC Boundary conditions
|
|
@@ -461,11 +488,7 @@ class Assembler:
|
|
|
461
488
|
# Robin BCs
|
|
462
489
|
if len(robin_bcs) > 0:
|
|
463
490
|
logger.debug('Implementing Robin Boundary Conditions.')
|
|
464
|
-
|
|
465
|
-
if len(robin_bcs) > 0:
|
|
466
|
-
logger.debug('Implementing Robin Boundary Conditions.')
|
|
467
|
-
|
|
468
|
-
gauss_points = gaus_quad_tri(4)
|
|
491
|
+
|
|
469
492
|
Bempty = field.empty_tri_matrix()
|
|
470
493
|
for bc in robin_bcs:
|
|
471
494
|
|
|
@@ -550,4 +573,4 @@ class Assembler:
|
|
|
550
573
|
simjob.Pd = Pmat.getH()
|
|
551
574
|
simjob.has_periodic = has_periodic
|
|
552
575
|
|
|
553
|
-
return simjob
|
|
576
|
+
return simjob, (er, ur, cond)
|
|
@@ -269,7 +269,7 @@ class Microwave3D:
|
|
|
269
269
|
Callable: The discretizer function
|
|
270
270
|
"""
|
|
271
271
|
def disc(material: Material):
|
|
272
|
-
return 299792458/(max(self.frequencies) * np.real(material.neff))
|
|
272
|
+
return 299792458/(max(self.frequencies) * np.real(material.neff(max(self.frequencies))))
|
|
273
273
|
return disc
|
|
274
274
|
|
|
275
275
|
def _initialize_field(self):
|
|
@@ -467,10 +467,25 @@ class Microwave3D:
|
|
|
467
467
|
raise SimulationError('Cannot proceed, the current basis class is undefined.')
|
|
468
468
|
|
|
469
469
|
logger.debug('Retreiving material properties.')
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
470
|
+
|
|
471
|
+
if freq is None:
|
|
472
|
+
freq = self.frequencies[0]
|
|
473
|
+
|
|
474
|
+
materials = self.mesh.retreive(self.mesher.volumes)
|
|
473
475
|
|
|
476
|
+
ertet = np.zeros((3,3,self.mesh.n_tets), dtype=np.complex128)
|
|
477
|
+
tandtet = np.zeros((3,3,self.mesh.n_tets), dtype=np.complex128)
|
|
478
|
+
urtet = np.zeros((3,3,self.mesh.n_tets), dtype=np.complex128)
|
|
479
|
+
condtet = np.zeros((3,3,self.mesh.n_tets), dtype=np.complex128)
|
|
480
|
+
|
|
481
|
+
for mat in materials:
|
|
482
|
+
ertet = mat.er(freq, ertet)
|
|
483
|
+
tandtet = mat.tand(freq, tandtet)
|
|
484
|
+
urtet = mat.ur(freq, urtet)
|
|
485
|
+
condtet = mat.cond(freq, condtet)
|
|
486
|
+
|
|
487
|
+
ertet = ertet * (1-1j*tandtet)
|
|
488
|
+
|
|
474
489
|
er = np.zeros((3,3,self.mesh.n_tris,), dtype=np.complex128)
|
|
475
490
|
ur = np.zeros((3,3,self.mesh.n_tris,), dtype=np.complex128)
|
|
476
491
|
cond = np.zeros((self.mesh.n_tris,), dtype=np.complex128)
|
|
@@ -479,7 +494,7 @@ class Microwave3D:
|
|
|
479
494
|
itet = self.mesh.tri_to_tet[0,itri]
|
|
480
495
|
er[:,:,itri] = ertet[:,:,itet]
|
|
481
496
|
ur[:,:,itri] = urtet[:,:,itet]
|
|
482
|
-
cond[itri] = condtet[itet]
|
|
497
|
+
cond[itri] = condtet[0,0,itet]
|
|
483
498
|
|
|
484
499
|
itri_port = self.mesh.get_triangles(port.tags)
|
|
485
500
|
|
|
@@ -488,11 +503,7 @@ class Microwave3D:
|
|
|
488
503
|
ermax = np.max(er[:,:,itri_port].flatten())
|
|
489
504
|
urmax = np.max(ur[:,:,itri_port].flatten())
|
|
490
505
|
|
|
491
|
-
if freq is None:
|
|
492
|
-
freq = self.frequencies[0]
|
|
493
|
-
|
|
494
506
|
k0 = 2*np.pi*freq/299792458
|
|
495
|
-
kmax = k0*np.sqrt(ermax.real*urmax.real)
|
|
496
507
|
|
|
497
508
|
Amatrix, Bmatrix, solve_ids, nlf = self.assembler.assemble_bma_matrices(self.basis, er, ur, cond, k0, port, self.bc)
|
|
498
509
|
|
|
@@ -511,8 +522,7 @@ class Microwave3D:
|
|
|
511
522
|
else:
|
|
512
523
|
|
|
513
524
|
target_kz = ermean*urmean*0.7*k0
|
|
514
|
-
|
|
515
|
-
|
|
525
|
+
|
|
516
526
|
logger.debug(f'Solving for {solve_ids.shape[0]} degrees of freedom.')
|
|
517
527
|
|
|
518
528
|
eigen_values, eigen_modes, report = self.solveroutine.eig_boundary(Amatrix, Bmatrix, solve_ids, nmodes, direct, target_kz, sign=-1)
|
|
@@ -550,12 +560,10 @@ class Microwave3D:
|
|
|
550
560
|
Ez = np.max(np.abs(Efz))
|
|
551
561
|
Exy = np.max(np.abs(Efxy))
|
|
552
562
|
|
|
553
|
-
|
|
554
|
-
# Ez = 0
|
|
555
|
-
if Ez/Exy < 1e-3 and not TEM:
|
|
563
|
+
if Ez/Exy < 1e-1 and not TEM:
|
|
556
564
|
logger.debug('Low Ez/Et ratio detected, assuming TE mode')
|
|
557
565
|
mode.modetype = 'TE'
|
|
558
|
-
elif Ez/Exy > 1e-
|
|
566
|
+
elif Ez/Exy > 1e-1 and not TEM:
|
|
559
567
|
logger.debug('High Ez/Et ratio detected, assuming TM mode')
|
|
560
568
|
mode.modetype = 'TM'
|
|
561
569
|
elif TEM:
|
|
@@ -621,9 +629,7 @@ class Microwave3D:
|
|
|
621
629
|
if self.basis is None:
|
|
622
630
|
raise SimulationError('Cannot proceed, the simulation basis class is undefined.')
|
|
623
631
|
|
|
624
|
-
|
|
625
|
-
ur = self.mesh.retreive(lambda mat,x,y,z: mat.fur3d_mat(x,y,z), self.mesher.volumes)
|
|
626
|
-
cond = self.mesh.retreive(lambda mat,x,y,z: mat.cond, self.mesher.volumes)[0,0,:]
|
|
632
|
+
materials = self.mesh.retreive(self.mesher.volumes)
|
|
627
633
|
|
|
628
634
|
### Does this move
|
|
629
635
|
logger.debug('Initializing frequency domain sweep.')
|
|
@@ -674,7 +680,8 @@ class Microwave3D:
|
|
|
674
680
|
freq_groups = [self.frequencies[i:i+n] for i in range(0, len(self.frequencies), n)]
|
|
675
681
|
|
|
676
682
|
results: list[SimJob] = []
|
|
677
|
-
|
|
683
|
+
matset: list[tuple[np.ndarray, np.ndarray, np.ndarray]] = []
|
|
684
|
+
|
|
678
685
|
## Single threaded
|
|
679
686
|
job_id = 1
|
|
680
687
|
|
|
@@ -691,7 +698,7 @@ class Microwave3D:
|
|
|
691
698
|
logger.debug(f'Simulation frequency = {freq/1e9:.3f} GHz')
|
|
692
699
|
if automatic_modal_analysis:
|
|
693
700
|
self._compute_modes(freq)
|
|
694
|
-
job = self.assembler.assemble_freq_matrix(self.basis,
|
|
701
|
+
job, mats = self.assembler.assemble_freq_matrix(self.basis, materials,
|
|
695
702
|
self.bc.boundary_conditions,
|
|
696
703
|
freq,
|
|
697
704
|
cache_matrices=self.cache_matrices)
|
|
@@ -700,6 +707,7 @@ class Microwave3D:
|
|
|
700
707
|
job.id = job_id
|
|
701
708
|
job_id += 1
|
|
702
709
|
jobs.append(job)
|
|
710
|
+
matset.append(mats)
|
|
703
711
|
|
|
704
712
|
logger.info(f'Starting single threaded solve of {len(jobs)} jobs.')
|
|
705
713
|
group_results = [run_job_single(job) for job in jobs]
|
|
@@ -716,7 +724,7 @@ class Microwave3D:
|
|
|
716
724
|
logger.debug(f'Simulation frequency = {freq/1e9:.3f} GHz')
|
|
717
725
|
if automatic_modal_analysis:
|
|
718
726
|
self._compute_modes(freq)
|
|
719
|
-
job = self.assembler.assemble_freq_matrix(self.basis,
|
|
727
|
+
job, mats = self.assembler.assemble_freq_matrix(self.basis, materials,
|
|
720
728
|
self.bc.boundary_conditions,
|
|
721
729
|
freq,
|
|
722
730
|
cache_matrices=self.cache_matrices)
|
|
@@ -725,6 +733,7 @@ class Microwave3D:
|
|
|
725
733
|
job.id = job_id
|
|
726
734
|
job_id += 1
|
|
727
735
|
jobs.append(job)
|
|
736
|
+
matset.append(mats)
|
|
728
737
|
|
|
729
738
|
logger.info(f'Starting distributed solve of {len(jobs)} jobs with {njobs} threads.')
|
|
730
739
|
group_results = list(executor.map(run_job, jobs))
|
|
@@ -749,8 +758,8 @@ class Microwave3D:
|
|
|
749
758
|
if automatic_modal_analysis:
|
|
750
759
|
self._compute_modes(freq)
|
|
751
760
|
|
|
752
|
-
job = self.assembler.assemble_freq_matrix(
|
|
753
|
-
self.basis,
|
|
761
|
+
job, mats = self.assembler.assemble_freq_matrix(
|
|
762
|
+
self.basis, materials,
|
|
754
763
|
self.bc.boundary_conditions,
|
|
755
764
|
freq,
|
|
756
765
|
cache_matrices=self.cache_matrices
|
|
@@ -761,6 +770,7 @@ class Microwave3D:
|
|
|
761
770
|
job.id = job_id
|
|
762
771
|
job_id += 1
|
|
763
772
|
jobs.append(job)
|
|
773
|
+
matset.append(mats)
|
|
764
774
|
|
|
765
775
|
logger.info(
|
|
766
776
|
f'Starting distributed solve of {len(jobs)} jobs '
|
|
@@ -783,7 +793,7 @@ class Microwave3D:
|
|
|
783
793
|
|
|
784
794
|
self.solveroutine.reset()
|
|
785
795
|
### Compute S-parameters and return
|
|
786
|
-
self._post_process(results,
|
|
796
|
+
self._post_process(results, matset)
|
|
787
797
|
return self.data
|
|
788
798
|
|
|
789
799
|
def eigenmode(self, search_frequency: float,
|
|
@@ -817,19 +827,21 @@ class Microwave3D:
|
|
|
817
827
|
if self.basis is None:
|
|
818
828
|
raise SimulationError('Cannot proceed. The simulation basis class is undefined.')
|
|
819
829
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
830
|
+
materials = self.mesh.retreive(self.mesher.volumes)
|
|
831
|
+
|
|
832
|
+
# er = self.mesh.retreive(lambda mat,x,y,z: mat.fer3d_mat(x,y,z), self.mesher.volumes)
|
|
833
|
+
# ur = self.mesh.retreive(lambda mat,x,y,z: mat.fur3d_mat(x,y,z), self.mesher.volumes)
|
|
834
|
+
# cond = self.mesh.retreive(lambda mat,x,y,z: mat.cond, self.mesher.volumes)[0,0,:]
|
|
823
835
|
|
|
824
836
|
### Does this move
|
|
825
837
|
logger.debug('Initializing frequency domain sweep.')
|
|
826
838
|
|
|
827
839
|
logger.info(f'Pre-assembling matrices of {len(self.frequencies)} frequency points.')
|
|
828
840
|
|
|
829
|
-
job = self.assembler.assemble_eig_matrix(self.basis,
|
|
841
|
+
job, matset = self.assembler.assemble_eig_matrix(self.basis, materials,
|
|
830
842
|
self.bc.boundary_conditions, search_frequency)
|
|
831
843
|
|
|
832
|
-
|
|
844
|
+
er, ur, cond = matset
|
|
833
845
|
logger.info('Solving complete')
|
|
834
846
|
|
|
835
847
|
A, C, solve_ids = job.yield_AC()
|
|
@@ -846,7 +858,7 @@ class Microwave3D:
|
|
|
846
858
|
|
|
847
859
|
for i in range(nmodes_found):
|
|
848
860
|
|
|
849
|
-
|
|
861
|
+
|
|
850
862
|
eig_k0 = np.sqrt(eigen_values[i])
|
|
851
863
|
if eig_k0 < k0_limit:
|
|
852
864
|
logger.debug(f'Ignoring mode due to low k0: {eig_k0} < {k0_limit}')
|
|
@@ -856,11 +868,11 @@ class Microwave3D:
|
|
|
856
868
|
logger.debug(f'Found k0={eig_k0:.2f}, f0={eig_freq/1e9:.2f} GHz')
|
|
857
869
|
Emode = eigen_modes[:,i]
|
|
858
870
|
|
|
859
|
-
scalardata = self.data.scalar.new(
|
|
871
|
+
scalardata = self.data.scalar.new(**self._params)
|
|
860
872
|
scalardata.k0 = eig_k0
|
|
861
873
|
scalardata.freq = eig_freq
|
|
862
874
|
|
|
863
|
-
fielddata = self.data.field.new(
|
|
875
|
+
fielddata = self.data.field.new(**self._params)
|
|
864
876
|
fielddata.freq = eig_freq
|
|
865
877
|
fielddata._der = np.squeeze(er[0,0,:])
|
|
866
878
|
fielddata._dur = np.squeeze(ur[0,0,:])
|
|
@@ -870,7 +882,7 @@ class Microwave3D:
|
|
|
870
882
|
|
|
871
883
|
return self.data
|
|
872
884
|
|
|
873
|
-
def _post_process(self, results: list[SimJob],
|
|
885
|
+
def _post_process(self, results: list[SimJob], materials: list[tuple[np.ndarray, np.ndarray, np.ndarray]]):
|
|
874
886
|
"""Compute the S-parameters after Frequency sweep
|
|
875
887
|
|
|
876
888
|
Args:
|
|
@@ -888,18 +900,19 @@ class Microwave3D:
|
|
|
888
900
|
|
|
889
901
|
logger.info('Computing S-parameters')
|
|
890
902
|
|
|
891
|
-
ertri = np.zeros((3,3,self.mesh.n_tris), dtype=np.complex128)
|
|
892
|
-
urtri = np.zeros((3,3,self.mesh.n_tris), dtype=np.complex128)
|
|
893
|
-
condtri = np.zeros((self.mesh.n_tris,), dtype=np.complex128)
|
|
894
|
-
|
|
895
|
-
for itri in range(self.mesh.n_tris):
|
|
896
|
-
itet = self.mesh.tri_to_tet[0,itri]
|
|
897
|
-
ertri[:,:,itri] = er[:,:,itet]
|
|
898
|
-
urtri[:,:,itri] = ur[:,:,itet]
|
|
899
|
-
condtri[itri] = cond[itet]
|
|
900
|
-
|
|
901
|
-
for freq, job in zip(self.frequencies, results):
|
|
902
903
|
|
|
904
|
+
for freq, job, mats in zip(self.frequencies, results, materials):
|
|
905
|
+
er, ur, cond = mats
|
|
906
|
+
ertri = np.zeros((3,3,self.mesh.n_tris), dtype=np.complex128)
|
|
907
|
+
urtri = np.zeros((3,3,self.mesh.n_tris), dtype=np.complex128)
|
|
908
|
+
condtri = np.zeros((self.mesh.n_tris,), dtype=np.complex128)
|
|
909
|
+
|
|
910
|
+
for itri in range(self.mesh.n_tris):
|
|
911
|
+
itet = self.mesh.tri_to_tet[0,itri]
|
|
912
|
+
ertri[:,:,itri] = er[:,:,itet]
|
|
913
|
+
urtri[:,:,itri] = ur[:,:,itet]
|
|
914
|
+
condtri[itri] = cond[0,0,itet]
|
|
915
|
+
|
|
903
916
|
k0 = 2*np.pi*freq/299792458
|
|
904
917
|
|
|
905
918
|
scalardata = self.data.scalar.new(freq=freq, **self._params)
|
|
@@ -73,7 +73,7 @@ class MWBoundaryConditionSet(BoundaryConditionSet):
|
|
|
73
73
|
"""
|
|
74
74
|
bcs = self.oftype(PEC)
|
|
75
75
|
for bc in self.oftype(SurfaceImpedance):
|
|
76
|
-
if bc.
|
|
76
|
+
if bc.sigma > 1e3:
|
|
77
77
|
bcs.append(bc)
|
|
78
78
|
|
|
79
79
|
return bcs
|
|
@@ -787,7 +787,6 @@ class LumpedPort(PortBC):
|
|
|
787
787
|
width: float | None = None,
|
|
788
788
|
height: float | None = None,
|
|
789
789
|
direction: Axis | None = None,
|
|
790
|
-
Idirection: Axis | None = None,
|
|
791
790
|
active: bool = False,
|
|
792
791
|
power: float = 1,
|
|
793
792
|
Z0: float = 50):
|
|
@@ -814,7 +813,7 @@ class LumpedPort(PortBC):
|
|
|
814
813
|
if width is None:
|
|
815
814
|
if not isinstance(face, GeoObject):
|
|
816
815
|
raise ValueError(f'The width, height and direction must be defined. Information cannot be extracted from {face}')
|
|
817
|
-
width, height, direction
|
|
816
|
+
width, height, direction = face._data('width','height','vdir')
|
|
818
817
|
if width is None or height is None or direction is None:
|
|
819
818
|
raise ValueError(f'The width, height and direction could not be extracted from {face}')
|
|
820
819
|
|
|
@@ -828,7 +827,6 @@ class LumpedPort(PortBC):
|
|
|
828
827
|
self.width: float = width
|
|
829
828
|
self.height: float = height # type: ignore
|
|
830
829
|
self.Vdirection: Axis = direction # type: ignore
|
|
831
|
-
self.Idirection: Axis = Idirection # type: ignore
|
|
832
830
|
self.type = 'TEM'
|
|
833
831
|
|
|
834
832
|
logger.info('Constructing coordinate system from normal port')
|
|
@@ -836,7 +834,6 @@ class LumpedPort(PortBC):
|
|
|
836
834
|
|
|
837
835
|
self.vintline: Line | None = None
|
|
838
836
|
self.v_integration = True
|
|
839
|
-
self.iintline: Line | None = None
|
|
840
837
|
|
|
841
838
|
@property
|
|
842
839
|
def surfZ(self) -> float:
|
|
@@ -923,6 +920,7 @@ class LumpedPort(PortBC):
|
|
|
923
920
|
Exg, Eyg, Ezg = self.cs.in_global_basis(Ex, Ey, Ez)
|
|
924
921
|
return np.array([Exg, Eyg, Ezg])
|
|
925
922
|
|
|
923
|
+
|
|
926
924
|
class LumpedElement(RobinBC):
|
|
927
925
|
|
|
928
926
|
_include_stiff: bool = True
|
|
@@ -1036,12 +1034,13 @@ class SurfaceImpedance(RobinBC):
|
|
|
1036
1034
|
self._material: Material | None = material
|
|
1037
1035
|
self._mur: float | complex = 1.0
|
|
1038
1036
|
self._epsr: float | complex = 1.0
|
|
1039
|
-
|
|
1040
1037
|
self.sigma: float = 0.0
|
|
1038
|
+
|
|
1041
1039
|
if material is not None:
|
|
1042
1040
|
self.sigma = material.cond
|
|
1043
1041
|
self._mur = material.ur
|
|
1044
1042
|
self._epsr = material.er
|
|
1043
|
+
|
|
1045
1044
|
if surface_conductance is not None:
|
|
1046
1045
|
self.sigma = surface_conductance
|
|
1047
1046
|
|
|
@@ -1068,10 +1067,15 @@ class SurfaceImpedance(RobinBC):
|
|
|
1068
1067
|
Returns:
|
|
1069
1068
|
complex: The γ-constant
|
|
1070
1069
|
"""
|
|
1070
|
+
|
|
1071
1071
|
w0 = k0*C0
|
|
1072
|
-
|
|
1072
|
+
f0 = w0/(2*np.pi)
|
|
1073
|
+
sigma = self.sigma.scalar(f0)
|
|
1074
|
+
mur = self._material.ur.scalar(f0)
|
|
1075
|
+
er = self._material.er.scalar(f0)
|
|
1076
|
+
|
|
1073
1077
|
rho = 1/sigma
|
|
1074
|
-
d_skin = (2*rho/(w0*MU0*
|
|
1078
|
+
d_skin = (2*rho/(w0*MU0*mur) * ((1+(w0*EPS0*er*rho)**2)**0.5 + rho*w0*EPS0*er))**0.5
|
|
1075
1079
|
R = rho/d_skin
|
|
1076
1080
|
if self._sr_model=='Hammerstad-Jensen' and self._sr > 0.0:
|
|
1077
1081
|
R = R * (1 + 2/np.pi * np.arctan(1.4*(self._sr/d_skin)**2))
|
|
@@ -282,9 +282,9 @@ class FarFieldData:
|
|
|
282
282
|
|
|
283
283
|
@property
|
|
284
284
|
def Etheta(self) -> np.ndarray:
|
|
285
|
-
thx =
|
|
286
|
-
thy =
|
|
287
|
-
thz = np.sin(self.theta)
|
|
285
|
+
thx = np.cos(self.theta)*np.cos(self.phi)
|
|
286
|
+
thy = np.cos(self.theta)*np.sin(self.phi)
|
|
287
|
+
thz = -np.sin(self.theta)
|
|
288
288
|
return thx*self.E[0,:] + thy*self.E[1,:] + thz*self.E[2,:]
|
|
289
289
|
|
|
290
290
|
@property
|
|
@@ -296,11 +296,11 @@ class FarFieldData:
|
|
|
296
296
|
|
|
297
297
|
@property
|
|
298
298
|
def Erhcp(self) -> np.ndarray:
|
|
299
|
-
return (self.Etheta
|
|
299
|
+
return (self.Etheta + 1j*self.Ephi)/np.sqrt(2)
|
|
300
300
|
|
|
301
301
|
@property
|
|
302
302
|
def Elhcp(self) -> np.ndarray:
|
|
303
|
-
return (self.Etheta
|
|
303
|
+
return (self.Etheta - 1j*self.Ephi)/np.sqrt(2)
|
|
304
304
|
|
|
305
305
|
@property
|
|
306
306
|
def AR(self) -> np.ndarray:
|
|
@@ -702,6 +702,11 @@ class MWField:
|
|
|
702
702
|
|
|
703
703
|
def interpolate(self, xs: np.ndarray, ys: np.ndarray, zs: np.ndarray) -> EHField:
|
|
704
704
|
''' Interpolate the dataset in the provided xs, ys, zs values'''
|
|
705
|
+
if isinstance(xs, (float, int, complex)):
|
|
706
|
+
xs = np.array([xs,])
|
|
707
|
+
ys = np.array([ys,])
|
|
708
|
+
zs = np.array([zs,])
|
|
709
|
+
|
|
705
710
|
shp = xs.shape
|
|
706
711
|
xf = xs.flatten()
|
|
707
712
|
yf = ys.flatten()
|
|
@@ -731,10 +736,24 @@ class MWField:
|
|
|
731
736
|
x: float | None = None,
|
|
732
737
|
y: float | None = None,
|
|
733
738
|
z: float | None = None) -> EHField:
|
|
739
|
+
"""Create a cartesian cut plane (XY, YZ or XZ) and compute the E and H-fields there
|
|
740
|
+
|
|
741
|
+
Only one coordiante and thus cutplane may be defined. If multiple are defined only the last (x->y->z) is used.
|
|
742
|
+
|
|
743
|
+
Args:
|
|
744
|
+
ds (float): The discretization step size
|
|
745
|
+
x (float | None, optional): The X-coordinate in case of a YZ-plane. Defaults to None.
|
|
746
|
+
y (float | None, optional): The Y-coordinate in case of an XZ-plane. Defaults to None.
|
|
747
|
+
z (float | None, optional): The Z-coordinate in case of an XY-plane. Defaults to None.
|
|
748
|
+
|
|
749
|
+
Returns:
|
|
750
|
+
EHField: The resultant EHField object
|
|
751
|
+
"""
|
|
734
752
|
xb, yb, zb = self.basis.bounds
|
|
735
753
|
xs = np.linspace(xb[0], xb[1], int((xb[1]-xb[0])/ds))
|
|
736
754
|
ys = np.linspace(yb[0], yb[1], int((yb[1]-yb[0])/ds))
|
|
737
755
|
zs = np.linspace(zb[0], zb[1], int((zb[1]-zb[0])/ds))
|
|
756
|
+
|
|
738
757
|
if x is not None:
|
|
739
758
|
Y,Z = np.meshgrid(ys, zs)
|
|
740
759
|
X = x*np.ones_like(Y)
|
|
@@ -746,6 +765,56 @@ class MWField:
|
|
|
746
765
|
Z = z*np.ones_like(Y)
|
|
747
766
|
return self.interpolate(X,Y,Z)
|
|
748
767
|
|
|
768
|
+
def cutplane_normal(self,
|
|
769
|
+
point=(0,0,0),
|
|
770
|
+
normal=(0,0,1),
|
|
771
|
+
npoints: int = 300) -> EHField:
|
|
772
|
+
"""
|
|
773
|
+
Take a 2D slice of the field along an arbitrary plane.
|
|
774
|
+
Args:
|
|
775
|
+
point: (x0,y0,z0), a point on the plane
|
|
776
|
+
normal: (nx,ny,nz), plane normal vector
|
|
777
|
+
npoints: number of grid points per axis
|
|
778
|
+
"""
|
|
779
|
+
|
|
780
|
+
n = np.array(normal, dtype=float)
|
|
781
|
+
n /= np.linalg.norm(n)
|
|
782
|
+
point = np.array(point)
|
|
783
|
+
|
|
784
|
+
tmp = np.array([1,0,0]) if abs(n[0]) < 0.9 else np.array([0,1,0])
|
|
785
|
+
u = np.cross(n, tmp)
|
|
786
|
+
u /= np.linalg.norm(u)
|
|
787
|
+
v = np.cross(n, u)
|
|
788
|
+
|
|
789
|
+
xb, yb, zb = self.basis.bounds
|
|
790
|
+
nx, ny, nz = 5, 5, 5
|
|
791
|
+
Xg = np.linspace(xb[0], xb[1], nx)
|
|
792
|
+
Yg = np.linspace(yb[0], yb[1], ny)
|
|
793
|
+
Zg = np.linspace(zb[0], zb[1], nz)
|
|
794
|
+
Xg, Yg, Zg = np.meshgrid(Xg, Yg, Zg, indexing='ij')
|
|
795
|
+
geometry = np.vstack([Xg.ravel(), Yg.ravel(), Zg.ravel()]).T # Nx3
|
|
796
|
+
|
|
797
|
+
rel_pts = geometry - point
|
|
798
|
+
S = rel_pts @ u
|
|
799
|
+
T = rel_pts @ v
|
|
800
|
+
|
|
801
|
+
margin = 0.01
|
|
802
|
+
s_min, s_max = S.min(), S.max()
|
|
803
|
+
t_min, t_max = T.min(), T.max()
|
|
804
|
+
s_bounds = (s_min - margin*(s_max-s_min), s_max + margin*(s_max-s_min))
|
|
805
|
+
t_bounds = (t_min - margin*(t_max-t_min), t_max + margin*(t_max-t_min))
|
|
806
|
+
|
|
807
|
+
S_grid = np.linspace(s_bounds[0], s_bounds[1], npoints)
|
|
808
|
+
T_grid = np.linspace(t_bounds[0], t_bounds[1], npoints)
|
|
809
|
+
S_mesh, T_mesh = np.meshgrid(S_grid, T_grid)
|
|
810
|
+
|
|
811
|
+
X = point[0] + S_mesh*u[0] + T_mesh*v[0]
|
|
812
|
+
Y = point[1] + S_mesh*u[1] + T_mesh*v[1]
|
|
813
|
+
Z = point[2] + S_mesh*u[2] + T_mesh*v[2]
|
|
814
|
+
|
|
815
|
+
return self.interpolate(X, Y, Z)
|
|
816
|
+
|
|
817
|
+
|
|
749
818
|
def grid(self, ds: float) -> EHField:
|
|
750
819
|
"""Interpolate a uniform grid sampled at ds
|
|
751
820
|
|