emerge 0.4.6__tar.gz → 0.4.8__tar.gz
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-0.4.8/.bumpversion.toml +27 -0
- emerge-0.4.8/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- emerge-0.4.8/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- emerge-0.4.8/.python-version +1 -0
- emerge-0.4.8/.vscode/settings.json +3 -0
- {emerge-0.4.6 → emerge-0.4.8}/PKG-INFO +1 -1
- {emerge-0.4.6 → emerge-0.4.8}/emerge/__init__.py +14 -14
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/assembly/curlcurl.py +1 -1
- {emerge-0.4.6 → emerge-0.4.8}/emerge/lib.py +1 -1
- emerge-0.4.8/emerge/plot.py +1 -0
- emerge-0.4.8/examples/demo10_sgh.py +111 -0
- emerge-0.4.8/examples/demo11_lumped_element_filter.py +118 -0
- emerge-0.4.8/examples/demo1_stepped_imp_filter.py +149 -0
- emerge-0.4.8/examples/demo2_combline_filter.py +130 -0
- emerge-0.4.8/examples/demo3_coupled_line_filter.py +133 -0
- emerge-0.4.8/examples/demo3_patch_antenna.py +141 -0
- emerge-0.4.8/examples/demo4_boundary_selection.py +136 -0
- emerge-0.4.8/examples/demo5_revolve.py +24 -0
- emerge-0.4.8/examples/demo6_striplines_with_vias.py +95 -0
- emerge-0.4.8/examples/demo7_periodic_cells.py +111 -0
- emerge-0.4.8/examples/demo8_waveguide_bpf_synthesis.py +191 -0
- emerge-0.4.8/examples/demo9_dielectric_resonator.py +95 -0
- {emerge-0.4.6 → emerge-0.4.8}/pyproject.toml +2 -13
- emerge-0.4.8/src/__init__.py +0 -0
- emerge-0.4.8/src/_img/logo.jpeg +0 -0
- emerge-0.4.8/uv.lock +955 -0
- emerge-0.4.6/emerge/plot.py +0 -1
- {emerge-0.4.6 → emerge-0.4.8}/.gitignore +0 -0
- {emerge-0.4.6 → emerge-0.4.8}/README.md +0 -0
- {emerge-0.4.6 → emerge-0.4.8}/emerge/__main__.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/__init__.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/bc.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/coord.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/cs.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/dataset.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/elements/__init__.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/elements/femdata.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/elements/index_interp.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/elements/legrange2.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/elements/ned2_interp.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/elements/nedelec2.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/elements/nedleg2.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/geo/__init__.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/geo/horn.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/geo/modeler.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/geo/operations.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/geo/pcb.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/geo/pcb_tools/calculator.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/geo/pcb_tools/macro.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/geo/pmlbox.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/geo/polybased.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/geo/shapes.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/geo/step.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/geo2d.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/geometry.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/howto.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/logsettings.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/material.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/mesh3d.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/mesher.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/mth/common_functions.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/mth/integrals.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/mth/optimized.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/periodic.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/__init__.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/__init__.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/adaptive_freq.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/assembly/assembler.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/assembly/generalized_eigen.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/assembly/robinbc.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/microwave_3d.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/microwave_bc.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/microwave_data.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/periodic.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/port_functions.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/sc.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/simjob.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/sparam.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/physics/microwave/touchstone.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/plot/__init__.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/plot/display.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/plot/grapher.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/plot/matplotlib/mpldisplay.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/plot/pyvista/__init__.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/plot/pyvista/display.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/plot/pyvista/display_settings.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/plot/simple_plots.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/plot.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/projects/__init__.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/projects/_gen_base.txt +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/projects/_load_base.txt +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/projects/generate_project.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/selection.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/simmodel.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/simulation_data.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/solver.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8/emerge}/_emerge/system.py +0 -0
- {emerge-0.4.6/_emerge → emerge-0.4.8/emerge}/cli.py +0 -0
- {emerge-0.4.6 → emerge-0.4.8}/emerge/pyvista.py +0 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[tool.bumpversion]
|
|
2
|
+
current_version = "0.4.8"
|
|
3
|
+
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
|
|
4
|
+
serialize = ["{major}.{minor}.{patch}"]
|
|
5
|
+
search = "{current_version}"
|
|
6
|
+
replace = "{new_version}"
|
|
7
|
+
regex = false
|
|
8
|
+
ignore_missing_version = false
|
|
9
|
+
ignore_missing_files = false
|
|
10
|
+
tag = true
|
|
11
|
+
sign_tags= false
|
|
12
|
+
tag_name = "v{new_version}"
|
|
13
|
+
tag_message = "Bump version: {current_version} → {new_version}"
|
|
14
|
+
allow_dirty = false
|
|
15
|
+
commit = true
|
|
16
|
+
message = "Bump version: {current_version} → {new_version}"
|
|
17
|
+
moveable_tags = []
|
|
18
|
+
commit_args = ""
|
|
19
|
+
setup_hooks = []
|
|
20
|
+
pre_commit_hooks = []
|
|
21
|
+
post_commit_hooks = []
|
|
22
|
+
|
|
23
|
+
[[tool.bumpversion.files]]
|
|
24
|
+
filename = "uv.lock"
|
|
25
|
+
|
|
26
|
+
[[tool.bumpversion.files]]
|
|
27
|
+
filename = "pyproject.toml"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Create a report to help us improve
|
|
4
|
+
title: ''
|
|
5
|
+
labels: ''
|
|
6
|
+
assignees: ''
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
**Describe the bug**
|
|
11
|
+
A clear and concise description of what the bug is.
|
|
12
|
+
|
|
13
|
+
**To Reproduce**
|
|
14
|
+
Steps to reproduce the behavior:
|
|
15
|
+
1. Go to '...'
|
|
16
|
+
2. Click on '....'
|
|
17
|
+
3. Scroll down to '....'
|
|
18
|
+
4. See error
|
|
19
|
+
|
|
20
|
+
**Expected behavior**
|
|
21
|
+
A clear and concise description of what you expected to happen.
|
|
22
|
+
|
|
23
|
+
**Screenshots**
|
|
24
|
+
If applicable, add screenshots to help explain your problem.
|
|
25
|
+
|
|
26
|
+
**Desktop (please complete the following information):**
|
|
27
|
+
- OS: [e.g. iOS]
|
|
28
|
+
- Browser [e.g. chrome, safari]
|
|
29
|
+
- Version [e.g. 22]
|
|
30
|
+
|
|
31
|
+
**Smartphone (please complete the following information):**
|
|
32
|
+
- Device: [e.g. iPhone6]
|
|
33
|
+
- OS: [e.g. iOS8.1]
|
|
34
|
+
- Browser [e.g. stock browser, safari]
|
|
35
|
+
- Version [e.g. 22]
|
|
36
|
+
|
|
37
|
+
**Additional context**
|
|
38
|
+
Add any other context about the problem here.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: Suggest an idea for this project
|
|
4
|
+
title: ''
|
|
5
|
+
labels: ''
|
|
6
|
+
assignees: ''
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
**Is your feature request related to a problem? Please describe.**
|
|
11
|
+
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
12
|
+
|
|
13
|
+
**Describe the solution you'd like**
|
|
14
|
+
A clear and concise description of what you want to happen.
|
|
15
|
+
|
|
16
|
+
**Describe alternatives you've considered**
|
|
17
|
+
A clear and concise description of any alternative solutions or features you've considered.
|
|
18
|
+
|
|
19
|
+
**Additional context**
|
|
20
|
+
Add any other context or screenshots about the feature request here.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.10
|
|
@@ -28,7 +28,7 @@ os.environ["NUMEXPR_NUM_THREADS"] = NTHREADS
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
from loguru import logger
|
|
31
|
-
from _emerge.logsettings import logger_format
|
|
31
|
+
from ._emerge.logsettings import logger_format
|
|
32
32
|
import sys
|
|
33
33
|
|
|
34
34
|
logger.remove()
|
|
@@ -36,19 +36,19 @@ logger.add(sys.stderr, format=logger_format)
|
|
|
36
36
|
|
|
37
37
|
logger.debug('Importing modules')
|
|
38
38
|
|
|
39
|
-
from _emerge.simmodel import Simulation3D
|
|
40
|
-
from _emerge.material import Material
|
|
41
|
-
from _emerge import bc
|
|
42
|
-
from _emerge.solver import SolverBicgstab, SolverGMRES, SolveRoutine, ReverseCuthillMckee, Sorter, SolverPardiso, SolverUMFPACK, SolverSuperLU, EMSolver
|
|
43
|
-
from _emerge.cs import CoordinateSystem, CS, GCS, Plane, Axis, XAX, YAX, ZAX, XYPLANE, XZPLANE, YZPLANE, YXPLANE, ZXPLANE, ZYPLANE
|
|
44
|
-
from _emerge.coord import Line
|
|
45
|
-
from _emerge import geo
|
|
46
|
-
from _emerge.selection import Selection, FaceSelection, DomainSelection, EdgeSelection
|
|
47
|
-
from _emerge.mth.common_functions import norm, coax_rout, coax_rin
|
|
48
|
-
from _emerge.physics.microwave.sc import stratton_chu
|
|
49
|
-
from _emerge.periodic import RectCell, HexCell
|
|
50
|
-
from _emerge.mesher import Algorithm2D, Algorithm3D
|
|
39
|
+
from ._emerge.simmodel import Simulation3D
|
|
40
|
+
from ._emerge.material import Material
|
|
41
|
+
from ._emerge import bc
|
|
42
|
+
from ._emerge.solver import SolverBicgstab, SolverGMRES, SolveRoutine, ReverseCuthillMckee, Sorter, SolverPardiso, SolverUMFPACK, SolverSuperLU, EMSolver
|
|
43
|
+
from ._emerge.cs import CoordinateSystem, CS, GCS, Plane, Axis, XAX, YAX, ZAX, XYPLANE, XZPLANE, YZPLANE, YXPLANE, ZXPLANE, ZYPLANE
|
|
44
|
+
from ._emerge.coord import Line
|
|
45
|
+
from ._emerge import geo
|
|
46
|
+
from ._emerge.selection import Selection, FaceSelection, DomainSelection, EdgeSelection
|
|
47
|
+
from ._emerge.mth.common_functions import norm, coax_rout, coax_rin
|
|
48
|
+
from ._emerge.physics.microwave.sc import stratton_chu
|
|
49
|
+
from ._emerge.periodic import RectCell, HexCell
|
|
50
|
+
from ._emerge.mesher import Algorithm2D, Algorithm3D
|
|
51
51
|
from . import lib
|
|
52
|
-
from _emerge.howto import _HowtoClass
|
|
52
|
+
from ._emerge.howto import _HowtoClass
|
|
53
53
|
howto = _HowtoClass()
|
|
54
54
|
logger.debug('Importing complete!')
|
|
@@ -85,7 +85,7 @@ for I in range(NFILL):
|
|
|
85
85
|
VOLUME_COEFF_CACHE = VOLUME_COEFF_CACHE_BASE
|
|
86
86
|
|
|
87
87
|
@njit(types.Tuple((f8[:], f8[:], f8[:], f8))(f8[:], f8[:], f8[:]), cache = True, nogil=True)
|
|
88
|
-
def tet_coefficients_bcd(xs: np.ndarray, ys: np.ndarray, zs: np.
|
|
88
|
+
def tet_coefficients_bcd(xs: np.ndarray, ys: np.ndarray, zs: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray, float]:
|
|
89
89
|
"""Computes the a,b,c and d coefficients of a tet barycentric coordinate functions and the volume
|
|
90
90
|
|
|
91
91
|
Args:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from ._emerge.plot.simple_plots import smith, plot_sp, plot, plot_ff, plot_ff_polar
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import emerge as em
|
|
2
|
+
from emerge.plot import plot_sp, plot_ff
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
""" STANDARD GAIN HORN ANTENNA
|
|
6
|
+
|
|
7
|
+
This demo sets up and simulates a rectangular horn antenna in an
|
|
8
|
+
absorbing domain with PML layers. We compute return loss (S11) over a
|
|
9
|
+
90–110 GHz band and plot the far-field radiation pattern.
|
|
10
|
+
|
|
11
|
+
The dimensions come from this paper:
|
|
12
|
+
https://pure.tue.nl/ws/portalfiles/portal/332971061/Uncertainties_in_the_Estimation_of_the_Gain_of_a_Standard_Gain_Horn_in_the_Frequency_Range_of_90_GHz_to_140_GHz.pdf
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
# --- Units ---------------------------------------------------------------
|
|
16
|
+
mm = 0.001 # meters per millimeter
|
|
17
|
+
|
|
18
|
+
# --- Horn and feed dimensions -------------------------------------------
|
|
19
|
+
wga = 2.01 * mm # waveguide width
|
|
20
|
+
wgb = 1.01 * mm # waveguide height
|
|
21
|
+
WH = 10 * mm # Aperture width
|
|
22
|
+
HH = 7 * mm # Aperture height
|
|
23
|
+
Lhorn = 21 * mm # Horn length
|
|
24
|
+
|
|
25
|
+
# --- Feed and simulation setup ------------------------------------------
|
|
26
|
+
Lfeed = 2 * mm # length of feed waveguide
|
|
27
|
+
th = 1 * mm # PML thickness
|
|
28
|
+
dx = 2 * mm # distance from horn exit to PML start
|
|
29
|
+
|
|
30
|
+
# Create simulation object
|
|
31
|
+
m = em.Simulation3D('HornAntenna', loglevel='DEBUG')
|
|
32
|
+
|
|
33
|
+
# --- Coordinate system for horn geometry -------------------------------
|
|
34
|
+
hornCS = em.CS(em.YAX, em.ZAX, em.XAX)
|
|
35
|
+
|
|
36
|
+
# Feed waveguide as rectangular box (metal)
|
|
37
|
+
feed = em.geo.Box(
|
|
38
|
+
Lfeed, # length along X
|
|
39
|
+
wga/2, # half-width along Y (centered)
|
|
40
|
+
wgb/2, # half-height along Z
|
|
41
|
+
position=(-Lfeed, 0, 0)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# --- Horn geometry ------------------------------------------------------
|
|
45
|
+
# Inner horn taper from (D,E) at throat to (B,C) at mouth over length F
|
|
46
|
+
horn_in = em.geo.Horn(
|
|
47
|
+
(wga, wgb), (WH, HH), Lhorn, hornCS,
|
|
48
|
+
)
|
|
49
|
+
# Outer horn (including metal thickness) helps define PML subtraction
|
|
50
|
+
horn_out = em.geo.Horn(
|
|
51
|
+
(wga+2*th, wgb+2*th), (WH+2*th, HH+2*th), Lhorn, hornCS
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# --- Bounding objects and PML -------------------------------------------
|
|
55
|
+
# Define large intersection box to trim horn geometry
|
|
56
|
+
ibox = em.geo.Box(30*mm, 30*mm, 30*mm)
|
|
57
|
+
horn_in = em.geo.intersect(horn_in, ibox, remove_tool=False)
|
|
58
|
+
horn_out = em.geo.intersect(horn_out, ibox)
|
|
59
|
+
|
|
60
|
+
# Create airbox with PML layers on +X, +Y, +Z faces
|
|
61
|
+
rat = 1.6 # PML extension ratio
|
|
62
|
+
air, *pmls = em.geo.pmlbox(
|
|
63
|
+
4*mm, # air padding before PML
|
|
64
|
+
rat*WH/2, # half-height in Y
|
|
65
|
+
rat*HH/2, # half-width in Z
|
|
66
|
+
(Lhorn - dx, 0, 0),# PML origin offset along X
|
|
67
|
+
thickness=4*mm,
|
|
68
|
+
N_mesh_layers=4,
|
|
69
|
+
top=True, right=True, back=True
|
|
70
|
+
)
|
|
71
|
+
# Subtract horn volume from airbox so PML does not cover metal
|
|
72
|
+
air2 = em.geo.subtract(air, horn_out)
|
|
73
|
+
|
|
74
|
+
# --- Solver parameters --------------------------------------------------
|
|
75
|
+
m.mw.set_frequency_range(90e9, 110e9, 11) # 90–110 GHz sweep
|
|
76
|
+
m.mw.set_resolution(0.27) # mesh resolution fraction
|
|
77
|
+
|
|
78
|
+
# --- Assemble geometry and mesh -----------------------------------------
|
|
79
|
+
m.generate_mesh()
|
|
80
|
+
|
|
81
|
+
# --- Boundary conditions ------------------------------------------------
|
|
82
|
+
p1 = m.mw.bc.ModalPort(feed.face('left'), 1) # excite TE10 in feed
|
|
83
|
+
PMC = m.mw.bc.PMC(m.select.face.inplane(0, 0, 0, 0, 1, 0)) # perfect magnetic on symmetry
|
|
84
|
+
radiation_boundary = air.outside('front', 'left', 'bottom') # open faces
|
|
85
|
+
abc = m.mw.bc.AbsorbingBoundary(m.select.face.inplane(Lhorn-dx,0,0,1,0,0))
|
|
86
|
+
# View mesh and BC selections
|
|
87
|
+
m.view(selections=[p1.selection, PMC.selection, radiation_boundary])
|
|
88
|
+
|
|
89
|
+
# --- Run frequency-domain solver ----------------------------------------
|
|
90
|
+
data = m.mw.frequency_domain(True, 2, frequency_groups=4)
|
|
91
|
+
|
|
92
|
+
# --- Plot return loss ---------------------------------------------------
|
|
93
|
+
scal = data.scalar.grid
|
|
94
|
+
plot_sp(scal.freq, scal.S(1,1)) # S11 vs frequency
|
|
95
|
+
|
|
96
|
+
# --- Far-field radiation pattern ----------------------------------------
|
|
97
|
+
# Compute E and H on 2D cut for phi=0 plane over -90° to 90°
|
|
98
|
+
ang, E, H = data.field[0].farfield_2d(
|
|
99
|
+
(1, 0, 0), (0, 1, 0), radiation_boundary,
|
|
100
|
+
(-90, 90), syms=['Ez','Hy']
|
|
101
|
+
)
|
|
102
|
+
# Normalize to free-space impedance and convert to dB
|
|
103
|
+
|
|
104
|
+
m.display.add_object(horn_in, opacity=0.1)
|
|
105
|
+
m.display.add_object(air2, opacity=0.1)
|
|
106
|
+
m.display.add_object(feed, opacity=0.1)
|
|
107
|
+
m.display.add_surf(*data.field[0].farfield_3d(radiation_boundary, syms=['Ez','Hy'])\
|
|
108
|
+
.surfplot('normE', True, True, -30, 5*mm, (Lhorn,0,0)), cmap='viridis', symmetrize=False)
|
|
109
|
+
m.display.add_surf(*data.field[0].cutplane(0.5*mm, z=0).scalar('Ez','real'))
|
|
110
|
+
m.display.show()
|
|
111
|
+
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import emerge as em
|
|
2
|
+
from emerge.plot import plot_sp
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
""" LUMPED COMPONENT FILTER DEMO
|
|
6
|
+
|
|
7
|
+
This demo constructs a microstrip filter using discrete lumped inductors and
|
|
8
|
+
capacitors mounted on a PCB. We extract the characteristic impedance, place
|
|
9
|
+
lumped elements in the trace, mesh the structure, and compute S-parameters.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
# --- Unit definitions -----------------------------------------------------
|
|
13
|
+
mm = 0.001 # meters per millimeter
|
|
14
|
+
pF = 1e-12 # picofarad in farads
|
|
15
|
+
fF = 1e-15 # femtofarad in farads
|
|
16
|
+
pH = 1e-12 # picohenry in henrys
|
|
17
|
+
nH = 1e-9 # nanohenry in henrys
|
|
18
|
+
|
|
19
|
+
# --- Lumped element impedance functions ---------------------------------
|
|
20
|
+
def Lf(L):
|
|
21
|
+
"""Return series impedance function for inductor L."""
|
|
22
|
+
return lambda f: 1j * 2 * np.pi * f * L
|
|
23
|
+
|
|
24
|
+
def Cf(C):
|
|
25
|
+
"""Return shunt admittance function for capacitor C."""
|
|
26
|
+
return lambda f: 1 / (1j * 2 * np.pi * f * C)
|
|
27
|
+
|
|
28
|
+
# --- PCB and lumped-component parameters ---------------------------------
|
|
29
|
+
pack = '0603' # package footprint for lumped components
|
|
30
|
+
# Create simulation and PCB layouter with substrate thickness and material
|
|
31
|
+
m = em.Simulation3D('LumpedFilter', loglevel='DEBUG')
|
|
32
|
+
th = 0.5 # substrate thickness (meters)
|
|
33
|
+
Hair = 2.0
|
|
34
|
+
pcb = em.geo.PCBLayouter(th, unit=mm, cs=em.GCS,
|
|
35
|
+
material=em.lib.ROGERS_RO4003C, layers=2)
|
|
36
|
+
# Compute 50-ohm microstrip width
|
|
37
|
+
w0 = pcb.calc.z0(50)
|
|
38
|
+
|
|
39
|
+
# Define lengths and lumped element values
|
|
40
|
+
l0 = 2 # straight segment length (mm)
|
|
41
|
+
Lshunt = 35 * nH # first inductor value
|
|
42
|
+
Cshunt = 35 * pF # first capacitor value
|
|
43
|
+
Lseries = 70 * nH # second inductor
|
|
44
|
+
Cseries = 18 * pF # second capacitor
|
|
45
|
+
|
|
46
|
+
# --- Route trace with lumped elements via method chaining ----------------
|
|
47
|
+
# Input matching section
|
|
48
|
+
pcb.new(0, 0, w0, (1, 0)).store('p1').straight(3)\
|
|
49
|
+
.lumped_element(Cf(Cseries), pack).straight(l0)\
|
|
50
|
+
.lumped_element(Lf(Lseries), pack).straight(l0)\
|
|
51
|
+
.split((0, -1)).straight(l0)\
|
|
52
|
+
.lumped_element(Cf(Cshunt), pack).straight(l0 / 2, w0)\
|
|
53
|
+
.via(pcb.z(1), w0 / 6, False).merge()\
|
|
54
|
+
.split((0, 1)).straight(l0)\
|
|
55
|
+
.lumped_element(Lf(Lshunt), pack).straight(l0 / 2, w0)\
|
|
56
|
+
.via(pcb.z(1), w0 / 6, False).merge()\
|
|
57
|
+
.straight(l0).lumped_element(Cf(Cseries), pack).straight(l0)\
|
|
58
|
+
.lumped_element(Lf(Lseries), pack).straight(l0)\
|
|
59
|
+
.straight(3).store('p2')
|
|
60
|
+
|
|
61
|
+
# Retrieve lumped element and via objects
|
|
62
|
+
LEs = pcb.lumped_elements
|
|
63
|
+
vias = pcb.generate_vias(merge=True)
|
|
64
|
+
# Compile trace geometry and determine bounds
|
|
65
|
+
traces = pcb.compile_paths(merge=True)
|
|
66
|
+
pcb.determine_bounds(leftmargin=0, topmargin=1, rightmargin=0, bottommargin=1)
|
|
67
|
+
|
|
68
|
+
# --- Define modal ports and generate environment ------------------------
|
|
69
|
+
mp1 = pcb.modal_port(pcb.load('p1'), Hair)
|
|
70
|
+
mp2 = pcb.modal_port(pcb.load('p2'), Hair)
|
|
71
|
+
diel = pcb.gen_pcb() # substrate dielectric block
|
|
72
|
+
air = pcb.gen_air(Hair) # surrounding air block
|
|
73
|
+
|
|
74
|
+
# Add all geometry to simulation
|
|
75
|
+
m.define_geometry()
|
|
76
|
+
|
|
77
|
+
# --- Solver and mesh settings -------------------------------------------
|
|
78
|
+
m.mw.set_frequency_range(0.05e9, 0.3e9, 51) # 50–300 MHz sweep
|
|
79
|
+
m.mesher.set_boundary_size(traces, 0.5 * mm)
|
|
80
|
+
# Refine mesh around lumped component faces
|
|
81
|
+
for le in LEs:
|
|
82
|
+
m.mesher.set_face_size(le, 0.1 * mm)
|
|
83
|
+
# Domain mesh refinement
|
|
84
|
+
m.mesher.set_domain_size(diel, 1 * mm)
|
|
85
|
+
m.mesher.set_domain_size(air, 3 * mm)
|
|
86
|
+
|
|
87
|
+
# Build mesh and view
|
|
88
|
+
m.generate_mesh()
|
|
89
|
+
m.view()
|
|
90
|
+
|
|
91
|
+
# --- Boundary conditions -----------------------------------------------
|
|
92
|
+
# Define modal (TEM) ports at input and output
|
|
93
|
+
p1 = m.mw.bc.ModalPort(mp1, 1, TEM=True)
|
|
94
|
+
p2 = m.mw.bc.ModalPort(mp2, 2, TEM=True)
|
|
95
|
+
# Add lumped element BCs for each element
|
|
96
|
+
for le in LEs:
|
|
97
|
+
m.mw.bc.LumpedElement(le)
|
|
98
|
+
# Perfect conductor on copper traces and vias
|
|
99
|
+
m.mw.bc.PEC(traces)
|
|
100
|
+
m.mw.bc.PEC(vias.outside())
|
|
101
|
+
|
|
102
|
+
# --- Run frequency-domain simulation ------------------------------------
|
|
103
|
+
data = m.mw.frequency_domain(parallel=True, njobs=4, frequency_groups=8)
|
|
104
|
+
|
|
105
|
+
# --- Post-processing: plot S-parameters ---------------------------------
|
|
106
|
+
f = data.scalar.grid.freq
|
|
107
|
+
S11 = data.scalar.grid.S(1, 1)
|
|
108
|
+
S21 = data.scalar.grid.S(2, 1)
|
|
109
|
+
plot_sp(f, [S11, S21], xunit='MHz', labels=['S11', 'S21'])
|
|
110
|
+
|
|
111
|
+
# --- Visualize field distribution ---------------------------------------
|
|
112
|
+
m.display.add_object(diel, opacity=0.1)
|
|
113
|
+
m.display.add_object(traces, opacity=0.1)
|
|
114
|
+
# Cut-plane of Ez field through substrate center
|
|
115
|
+
cut = data.field.find(freq=2.25e9)\
|
|
116
|
+
.cutplane(0.1 * mm, z=-th/2 * mm)
|
|
117
|
+
m.display.add_surf(*cut.scalar('Ez', 'real'))
|
|
118
|
+
m.display.show()
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import emerge as em
|
|
2
|
+
import numpy as np
|
|
3
|
+
from emerge.plot import smith, plot_sp
|
|
4
|
+
|
|
5
|
+
""" STEPPED IMPEDANCE FILTER
|
|
6
|
+
|
|
7
|
+
In this demo we will look at how we can construct a stepped impedance filter using the
|
|
8
|
+
PCB Layouter interface in EMerge.
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
# First we will define some constants/variables/parameters for our simulation.
|
|
12
|
+
mm = 0.001
|
|
13
|
+
mil = 0.0254*mm
|
|
14
|
+
|
|
15
|
+
L0, L1, L2, L3 = 400, 660, 660, 660 # The lengths of the sections in mil's
|
|
16
|
+
W0, W1, W2, W3 = 50, 128, 8, 224 # The widths of the sections in mil's
|
|
17
|
+
|
|
18
|
+
th = 62 # The PCB Thickness
|
|
19
|
+
er = 2.2 # The Dielectric constant
|
|
20
|
+
|
|
21
|
+
Hair = 60
|
|
22
|
+
|
|
23
|
+
## Material definition
|
|
24
|
+
|
|
25
|
+
# We can define the material using the Material class. Just supply the dielectric properties and you are done!
|
|
26
|
+
pcbmat = em.Material(er=2.2, tand=0.00, color="#217627")
|
|
27
|
+
|
|
28
|
+
# We start by creating our simulation object.
|
|
29
|
+
|
|
30
|
+
m = em.Simulation3D('Demo1_SIF', loglevel='DEBUG')
|
|
31
|
+
|
|
32
|
+
# To accomodate PCB routing we make use of the PCBLayouter class. To use it we need to
|
|
33
|
+
# supply it with a thickness, the desired air-box height, the units at which we supply
|
|
34
|
+
# the dimensions and the PCB material.
|
|
35
|
+
|
|
36
|
+
layouter = em.geo.PCBLayouter(th, unit=mil, material=pcbmat, layers=3)
|
|
37
|
+
|
|
38
|
+
# We will route our PCB using the "method chaining" syntax. First we call the .new() method
|
|
39
|
+
# to start a new trace. This will returna StripPath object on which we may call methods that
|
|
40
|
+
# sequentially constructs our stripline trace. In this case, it is sipmly a sequence of straight
|
|
41
|
+
# sections.
|
|
42
|
+
|
|
43
|
+
layouter.new(0,0,W0, (1,0), z=layouter.z(2)).store('p1').straight(L0, W0).straight(L1,W1).straight(L2,W2).straight(L3,W3)\
|
|
44
|
+
.straight(L2,W2).straight(L1,W1).straight(L0,W0).store('p2')
|
|
45
|
+
|
|
46
|
+
# Next we generate a wave port surface to use for our simulation. A wave port can be automatically
|
|
47
|
+
# generated for a given stripoline section. To easily reference it we use the .ref() method to
|
|
48
|
+
# recall the sections we created earlier.
|
|
49
|
+
p1 = layouter.modal_port(layouter.load('p1'), height=0)
|
|
50
|
+
p2 = layouter.modal_port(layouter.load('p2'), height=0)
|
|
51
|
+
|
|
52
|
+
# Finally we compile the stirpline into a polygon. The compile_paths function will return
|
|
53
|
+
# GeoSurface objects that form the polygon. Additionally, we may turn on the Merge feature
|
|
54
|
+
# which will then return a single GeoSurface type object that we can use later.
|
|
55
|
+
polies = layouter.compile_paths(True)
|
|
56
|
+
|
|
57
|
+
# We can manually define blocks for the dielectric or air or let the PCBLayouter do it for us.
|
|
58
|
+
# First we must determine the bounds of our PCB. This function by default will make a PCB
|
|
59
|
+
# just large enough to contain all the coordinates in it (in the XY plane). By adding extra
|
|
60
|
+
# margins we can make sure to add sufficient space next to the trace. Just make sure that there
|
|
61
|
+
# is no margin where the wave ports need to go.
|
|
62
|
+
layouter.determine_bounds(leftmargin=0, topmargin=200, rightmargin=0, bottommargin=200)
|
|
63
|
+
|
|
64
|
+
# We can now generate the PCB and air box. The material assignment is automatic!
|
|
65
|
+
|
|
66
|
+
pcb = layouter.gen_pcb(True, merge=True)
|
|
67
|
+
|
|
68
|
+
# We now pass all the geometries we have created to the .define_geometry() method.
|
|
69
|
+
m.define_geometry()
|
|
70
|
+
|
|
71
|
+
# We set our desired resolution (fraction of the wavelength)
|
|
72
|
+
m.mw.set_resolution(0.08)
|
|
73
|
+
|
|
74
|
+
# And we define our frequency range
|
|
75
|
+
m.mw.set_frequency_range(0.2e9, 8e9, 41)
|
|
76
|
+
|
|
77
|
+
# EMerge also has a convenient interface to improve surface meshing quality.
|
|
78
|
+
# With the set_boundary_size(method) we can define a meshing resolution for the edges of boundaries.
|
|
79
|
+
# This is adviced for small stripline structures.
|
|
80
|
+
# The growth_rate setting allows us to change how fast the mesh size will recover to the original size.
|
|
81
|
+
m.mesher.set_boundary_size(polies, 1*mm, growth_rate=1.2)
|
|
82
|
+
m.mesher.set_face_size(p1, 1*mm)
|
|
83
|
+
m.mesher.set_face_size(p2, 1*mm)
|
|
84
|
+
|
|
85
|
+
# Finally we generate our mesh and view it
|
|
86
|
+
m.generate_mesh()
|
|
87
|
+
|
|
88
|
+
#m.view(use_gmsh=True)
|
|
89
|
+
|
|
90
|
+
# We can now define the modal ports for the in and outputs and set the conductor to PEC.
|
|
91
|
+
port1 = m.mw.bc.ModalPort(p1, 1, TEM=True)
|
|
92
|
+
port2 = m.mw.bc.ModalPort(p2, 2, TEM=True)
|
|
93
|
+
pec = m.mw.bc.PEC(polies)
|
|
94
|
+
|
|
95
|
+
## OPTIONAL
|
|
96
|
+
# If we want to view the port mode we have to first know it. The modes are computed using a modal analysis.
|
|
97
|
+
# Since the latest version of EMerge. Modal analysis is executed automatically. Here we compute them manually
|
|
98
|
+
# as the mode is only calculated during the frequency domain solution.
|
|
99
|
+
if False:
|
|
100
|
+
# Make sure to set the TEM property to True so that
|
|
101
|
+
# EMerge knows to handle the port mode as a TEM boundary. This also includes the automatic
|
|
102
|
+
# determination of a voltage integration line used for computing the port impedance.
|
|
103
|
+
m.mw.modal_analysis(port1, 1, TEM=True)
|
|
104
|
+
m.mw.modal_analysis(port2, 1, TEM=True)
|
|
105
|
+
|
|
106
|
+
# Finally we import the display class to view the resultant modes
|
|
107
|
+
m.display.add_object(pcb, opacity=0.1)
|
|
108
|
+
m.display.add_object(polies)
|
|
109
|
+
m.display.add_portmode(port1, 21)
|
|
110
|
+
m.display.add_portmode(port2, 21)
|
|
111
|
+
m.display.show()
|
|
112
|
+
|
|
113
|
+
# Finally we execute the frequency domain sweep and compute the Scattering Parameters.
|
|
114
|
+
sol = m.mw.frequency_domain(parallel=True, njobs=4, frequency_groups=8)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# Our "sol" variable is of type MWData (Microwave Data). This contains a set of scalar data
|
|
118
|
+
# like S-parameters and field data like the E/H field. The scalar data is in sol.scalar and the
|
|
119
|
+
# field data in sol.field. Our data is currently a large list of entries in the dataset simply in order
|
|
120
|
+
# at which it is computed. We can structure our data in cases where we do a single frequency sweep
|
|
121
|
+
# or with a structured parameter sweep. In this case we can use the .grid property which will attempt
|
|
122
|
+
# to construct an N-dimensional grid from our results.
|
|
123
|
+
|
|
124
|
+
gritted_data = sol.scalar.grid
|
|
125
|
+
|
|
126
|
+
# The gritted_data is of type MWGlobalNdim which means its an N-dimensional set of data.
|
|
127
|
+
# From this we can simply take all that we need.
|
|
128
|
+
|
|
129
|
+
f = gritted_data.freq
|
|
130
|
+
S11 = gritted_data.S(1,1)
|
|
131
|
+
S21 = gritted_data.S(2,1)
|
|
132
|
+
|
|
133
|
+
# This extracts the actual simulation data.
|
|
134
|
+
plot_sp(f/1e9, [S11, S21], labels=['S11','S21'], dblim=[-40,6], logx=True)
|
|
135
|
+
|
|
136
|
+
# We can also supersample our data by constructing a model using the Vector Fitting algorithm
|
|
137
|
+
|
|
138
|
+
f = np.linspace(0.2e9, 8e9, 2001)
|
|
139
|
+
S11 = gritted_data.model_S(1,1,f)
|
|
140
|
+
S21 = gritted_data.model_S(2,1,f)
|
|
141
|
+
|
|
142
|
+
smith(f,S11)
|
|
143
|
+
|
|
144
|
+
plot_sp(f/1e9, [S11, S21], labels=['S11','S21'], dblim=[-40,6], logx=True)
|
|
145
|
+
|
|
146
|
+
m.display.add_object(pcb, opacity=0.1)
|
|
147
|
+
m.display.add_object(polies, opacity=0.5)
|
|
148
|
+
m.display.add_surf(*sol.field[0].cutplane(1*mm, z=-0.75*th*mil).scalar('Ez','real'))
|
|
149
|
+
m.display.show()
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import emerge as em
|
|
2
|
+
import numpy as np
|
|
3
|
+
from emerge.plot import plot_sp
|
|
4
|
+
|
|
5
|
+
""" DEMO: COMBLINE FILTER
|
|
6
|
+
|
|
7
|
+
In this demo we will look at the design of a combline filter in EMerge. The filter design was taken from
|
|
8
|
+
the book "Modern RF and Microwave Filter Design" by Protap Pramanick and Prakash Bhartia.
|
|
9
|
+
Some of the dimensions where not clear.
|
|
10
|
+
|
|
11
|
+
In this demo we will look at the Modeler class and how it can help us quickly create complicated geometries.
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
# First we define some quantities for our simulation.
|
|
16
|
+
mm = 0.001
|
|
17
|
+
mil = 0.0254*mm
|
|
18
|
+
|
|
19
|
+
a = 240*mil
|
|
20
|
+
b = 248*mil
|
|
21
|
+
d1 = 10*mil
|
|
22
|
+
d2 = 10*mil
|
|
23
|
+
dc = 8.5*mil
|
|
24
|
+
lr1 = b-d1
|
|
25
|
+
lr2 = b-d2
|
|
26
|
+
W = 84*mil
|
|
27
|
+
S1 = 117*mil
|
|
28
|
+
S2 = 136*mil
|
|
29
|
+
C1 = b-dc
|
|
30
|
+
h = 74*mil
|
|
31
|
+
wi = 84*mil
|
|
32
|
+
Lbox = 5*W + 2*(S1+S2+wi)
|
|
33
|
+
|
|
34
|
+
x1 = wi+W/2
|
|
35
|
+
x2 = x1 + W + S1
|
|
36
|
+
x3 = x2 + W + S2
|
|
37
|
+
x4 = x3 + W + S2
|
|
38
|
+
x5 = x4 + W + S1
|
|
39
|
+
|
|
40
|
+
rout = 40.5*mil
|
|
41
|
+
rin = 12.5*mil
|
|
42
|
+
lfeed = 100*mil
|
|
43
|
+
|
|
44
|
+
# A usual we start our simulation file
|
|
45
|
+
model = em.Simulation3D('Combline_DEMO', loglevel='DEBUG')
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# The filter consists of quarter lamba cylindrical pins inside an airbox.
|
|
49
|
+
# First we create the airbox
|
|
50
|
+
box = em.geo.Box(Lbox, a, b, position=(0,-a/2,0))
|
|
51
|
+
|
|
52
|
+
# Next we create 5 cyllinders using the Modeler class.
|
|
53
|
+
# The modeler class also implements a method chaining interface. In this example we stick to simpler features.
|
|
54
|
+
# The modeler class allows us to create a parameter series using the modeler.series() method. We provid it with quantities.
|
|
55
|
+
# We can do this for multiple at the same time (as you can also see with the position). The modeler class
|
|
56
|
+
# will recognize the multiple quantities and simply create 5 different cyllinders, one for each parameter pair.
|
|
57
|
+
stubs = model.modeler.cyllinder(W/2, model.modeler.series(C1, lr1, lr2, lr1, C1), position=(model.modeler.series(x1, x2, x3, x4, x5), 0, 0), NPoly=10)
|
|
58
|
+
|
|
59
|
+
# Next we create the in and output feed cyllinders for the coaxial cable. We will use the Nsections feature in order to guarantee a better
|
|
60
|
+
# adherence to the boundary.
|
|
61
|
+
feed1out = em.geo.Cyllinder(rout, lfeed, em.CoordinateSystem(em.ZAX, em.YAX, em.XAX, np.array([-lfeed, 0, h])), Nsections=12)
|
|
62
|
+
feed1in = em.geo.Cyllinder(rin, lfeed+wi+W/2, em.CoordinateSystem(em.ZAX, em.YAX, em.XAX, np.array([-lfeed, 0, h])), Nsections=8)
|
|
63
|
+
feed2out = em.geo.Cyllinder(rout, lfeed, em.CoordinateSystem(em.ZAX, em.YAX, em.XAX, np.array([Lbox, 0, h])), Nsections=12)
|
|
64
|
+
feed2in = em.geo.Cyllinder(rin, lfeed+wi+W/2, em.CoordinateSystem(em.ZAX, em.YAX, em.XAX, np.array([Lbox-wi-W/2, 0, h])), Nsections=8)
|
|
65
|
+
|
|
66
|
+
# Next we subtract the stubs and the center conductor from the box and feedline.
|
|
67
|
+
for ro in stubs:
|
|
68
|
+
box = em.geo.subtract(box, ro)
|
|
69
|
+
box = em.geo.subtract(box, feed1in, remove_tool=False)
|
|
70
|
+
box = em.geo.subtract(box, feed2in, remove_tool=False)
|
|
71
|
+
feed1out = em.geo.subtract(feed1out, feed1in)
|
|
72
|
+
feed2out = em.geo.subtract(feed2out, feed2in)
|
|
73
|
+
|
|
74
|
+
# Finally we may define our geometry
|
|
75
|
+
model.define_geometry()
|
|
76
|
+
|
|
77
|
+
model.view()
|
|
78
|
+
|
|
79
|
+
# We define our frequency range and a fine sampling.
|
|
80
|
+
model.mw.set_frequency_range(6e9, 8e9, 21)
|
|
81
|
+
|
|
82
|
+
model.mw.resolution = 0.05
|
|
83
|
+
|
|
84
|
+
# To improve simulation quality we refine the faces at the top of the cylinders.
|
|
85
|
+
for stub in stubs:
|
|
86
|
+
model.mesher.set_boundary_size(box.face('back', tool=stub), 0.0002)
|
|
87
|
+
|
|
88
|
+
# Finally we may create our mesh.
|
|
89
|
+
model.generate_mesh()
|
|
90
|
+
|
|
91
|
+
model.view()
|
|
92
|
+
|
|
93
|
+
# We define our modal ports, assign the boundary condition and execute a modal analysis to solve for the
|
|
94
|
+
# coaxial field mode.
|
|
95
|
+
port1 = model.mw.bc.ModalPort(model.select.face.near(-lfeed, 0, h), 1, TEM=True)
|
|
96
|
+
port2 = model.mw.bc.ModalPort(model.select.face.near(Lbox+lfeed, 0, h), 2, TEM=True)
|
|
97
|
+
|
|
98
|
+
# At last we can compute the frequency domain study
|
|
99
|
+
data = model.mw.frequency_domain(parallel=True)
|
|
100
|
+
|
|
101
|
+
# Next we will use the Vector Fitting algorithm to model our S-parameters with a Rational function
|
|
102
|
+
|
|
103
|
+
fdense = np.linspace(6e9, 9e9, 2001)
|
|
104
|
+
|
|
105
|
+
S11 = data.scalar.grid.model_S(1,1,fdense)
|
|
106
|
+
S21 = data.scalar.grid.model_S(2,1,fdense)
|
|
107
|
+
|
|
108
|
+
plot_sp(fdense/1e9, [S11, S21], labels=['S11','S21'])
|
|
109
|
+
|
|
110
|
+
# We can also plot the field inside. First we create a grid of sample point coordinates
|
|
111
|
+
xs = np.linspace(0, Lbox, 41)
|
|
112
|
+
ys = np.linspace(-a/2, a/2, 11)
|
|
113
|
+
zs = np.linspace(0, b, 15)
|
|
114
|
+
|
|
115
|
+
X, Y, Z = np.meshgrid(xs, ys, zs, indexing='ij')
|
|
116
|
+
X = X.flatten()
|
|
117
|
+
Y = Y.flatten()
|
|
118
|
+
Z = Z.flatten()
|
|
119
|
+
|
|
120
|
+
# The E-field can be interpolated by selecting a desired solution and then interpolating it.
|
|
121
|
+
Ex, Ey, Ez = data.field[3].interpolate(X,Y,Z).E
|
|
122
|
+
|
|
123
|
+
# We can add the objects we want and fields using the shown methods.
|
|
124
|
+
model.display.add_object(box, opacity=0.1, show_edges=True)
|
|
125
|
+
model.display.add_quiver(X,Y,Z, Ex.real, Ey.real, Ez.real)
|
|
126
|
+
model.display.add_object(feed1out, opacity=0.1)
|
|
127
|
+
model.display.add_portmode(port1, 21)
|
|
128
|
+
model.display.add_portmode(port2, 21)
|
|
129
|
+
model.display.add_surf(*data.field[3].cutplane(ds=0.5*mm, y=0).scalar('normE'))
|
|
130
|
+
model.display.show()
|