simpleEMS 0.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.
- simpleEMS/__init__.py +89 -0
- simpleEMS/calc.py +397 -0
- simpleEMS/cli.py +650 -0
- simpleEMS/console.py +54 -0
- simpleEMS/export_gerber.py +266 -0
- simpleEMS/export_step.py +203 -0
- simpleEMS/filter_coefficient.py +405 -0
- simpleEMS/mesh.py +898 -0
- simpleEMS/microstrip_line.py +483 -0
- simpleEMS/patch_antenna.py +905 -0
- simpleEMS/quarterwave_stub_filter.py +1060 -0
- simpleEMS/sim_params.py +295 -0
- simpleEMS/sim_tools.py +2202 -0
- simpleEMS/standalone_model.py +234 -0
- simpleems-0.1.0.dist-info/METADATA +129 -0
- simpleems-0.1.0.dist-info/RECORD +18 -0
- simpleems-0.1.0.dist-info/WHEEL +4 -0
- simpleems-0.1.0.dist-info/entry_points.txt +3 -0
simpleEMS/__init__.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# simpleEMS
|
|
2
|
+
# Copyright (C) 2026 Mohamoud Ali
|
|
3
|
+
#
|
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU Affero General Public License as published by
|
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU Affero General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
simpleEMS — A Python library built on openEMS for simplified antenna
|
|
19
|
+
and RF structure design, simulation, optimisation, and visualisation.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import importlib
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
_NAMES_BY_MODULE = {
|
|
26
|
+
"microstrip_line": ["MicrostripLine", "MicrostripLineParams"],
|
|
27
|
+
"patch_antenna": [
|
|
28
|
+
"InsetFedPatchAntenna",
|
|
29
|
+
"InsetFedPatchParams",
|
|
30
|
+
"ProbeFedPatchAntenna",
|
|
31
|
+
"ProbeFedPatchParams",
|
|
32
|
+
],
|
|
33
|
+
"quarterwave_stub_filter": [
|
|
34
|
+
"QuarterWaveFilterParams",
|
|
35
|
+
"BandStopQuarterWaveFilter",
|
|
36
|
+
"BandPassQuarterWaveFilter",
|
|
37
|
+
],
|
|
38
|
+
"standalone_model": ["simulate_model"],
|
|
39
|
+
"sim_tools": [
|
|
40
|
+
"DumpType",
|
|
41
|
+
"SimTools",
|
|
42
|
+
"optimize_s11",
|
|
43
|
+
"optimize_s21",
|
|
44
|
+
"optimize_s_params",
|
|
45
|
+
"param_sweep",
|
|
46
|
+
"setup_simulation",
|
|
47
|
+
],
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
_NAME_TO_MODULE = {}
|
|
51
|
+
|
|
52
|
+
for _module_name, _names in _NAMES_BY_MODULE.items():
|
|
53
|
+
for _name in _names:
|
|
54
|
+
_NAME_TO_MODULE[_name] = _module_name
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def __getattr__(name: str) -> object:
|
|
58
|
+
module_name = _NAME_TO_MODULE.get(name)
|
|
59
|
+
if module_name is not None:
|
|
60
|
+
module = importlib.import_module(f".{module_name}", __package__)
|
|
61
|
+
value = getattr(module, name)
|
|
62
|
+
globals()[name] = value
|
|
63
|
+
return value
|
|
64
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
__all__ = [
|
|
68
|
+
"InsetFedPatchAntenna",
|
|
69
|
+
"ProbeFedPatchAntenna",
|
|
70
|
+
"MicrostripLine",
|
|
71
|
+
"InsetFedPatchParams",
|
|
72
|
+
"ProbeFedPatchParams",
|
|
73
|
+
"MicrostripLineParams",
|
|
74
|
+
"QuarterWaveFilterParams",
|
|
75
|
+
"BandStopQuarterWaveFilter",
|
|
76
|
+
"BandPassQuarterWaveFilter",
|
|
77
|
+
"DumpType",
|
|
78
|
+
"SimTools",
|
|
79
|
+
"setup_simulation",
|
|
80
|
+
"optimize_s11",
|
|
81
|
+
"optimize_s21",
|
|
82
|
+
"optimize_s_params",
|
|
83
|
+
"param_sweep",
|
|
84
|
+
"simulate_model",
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
__version__ = "0.1.0"
|
|
88
|
+
__author__ = "Mohamoud Ali"
|
|
89
|
+
__license__ = "AGPL-3.0-or-later"
|
simpleEMS/calc.py
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
# simpleEMS
|
|
2
|
+
# Copyright (C) 2026 Mohamoud Ali
|
|
3
|
+
#
|
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU Affero General Public License as published by
|
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU Affero General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
Calculation utilities for microstrip patch antenna design.
|
|
19
|
+
|
|
20
|
+
Provides functions for computing patch dimensions, microstrip line
|
|
21
|
+
width from target impedance, phase shift lengths, conductance, and
|
|
22
|
+
inset depths. All formulas follow standard references (Balanis,
|
|
23
|
+
Hammerstad-Jensen).
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from typing import NamedTuple
|
|
27
|
+
|
|
28
|
+
import numpy as np
|
|
29
|
+
from openEMS.physical_constants import C0
|
|
30
|
+
from scipy.integrate import quad
|
|
31
|
+
|
|
32
|
+
from .sim_tools import m_to_mm, mm_to_m
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def calculate_electrical_length_mm(
|
|
36
|
+
elec_length_deg_rad: float,
|
|
37
|
+
eps_eff: float,
|
|
38
|
+
frequency: float,
|
|
39
|
+
radians: bool = False,
|
|
40
|
+
) -> float:
|
|
41
|
+
"""
|
|
42
|
+
Compute transmission line length (mm) required to obtain
|
|
43
|
+
a specified phase shift at a given frequency.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
elec_length_deg_rad : float
|
|
48
|
+
Phase shift in degrees (default) or radians.
|
|
49
|
+
eps_eff : float
|
|
50
|
+
Effective dielectric constant.
|
|
51
|
+
frequency : float
|
|
52
|
+
Frequency in Hz.
|
|
53
|
+
radians : bool
|
|
54
|
+
If True, elec_length_deg_rad is already in radians.
|
|
55
|
+
|
|
56
|
+
Returns
|
|
57
|
+
-------
|
|
58
|
+
float
|
|
59
|
+
Required length in mm.
|
|
60
|
+
"""
|
|
61
|
+
theta = elec_length_deg_rad if radians else np.deg2rad(elec_length_deg_rad)
|
|
62
|
+
|
|
63
|
+
# Guided phase constant (rad/m)
|
|
64
|
+
beta = 2 * np.pi * frequency * np.sqrt(eps_eff) / C0
|
|
65
|
+
|
|
66
|
+
# Length in meters
|
|
67
|
+
length_m = theta / beta
|
|
68
|
+
|
|
69
|
+
return length_m * 1e3
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def microstrip_width_from_impedance(
|
|
73
|
+
charac_imp: float,
|
|
74
|
+
subs_height: float,
|
|
75
|
+
copper_thickness: float,
|
|
76
|
+
subst_eps_r: float,
|
|
77
|
+
freq_hz: float,
|
|
78
|
+
tolerance: float = 0.01,
|
|
79
|
+
) -> tuple[float, float]:
|
|
80
|
+
"""
|
|
81
|
+
Calculates microstrip width for a target impedance including dispersion
|
|
82
|
+
and thickness using Hammerstad-Jensen equations.
|
|
83
|
+
|
|
84
|
+
Parameters
|
|
85
|
+
----------
|
|
86
|
+
|
|
87
|
+
charac_imp: float
|
|
88
|
+
The required characteristic impedance of the microstrip line
|
|
89
|
+
subs_height: float
|
|
90
|
+
The height of the substrate
|
|
91
|
+
copper_thickness: float
|
|
92
|
+
The thickness of the copper trace
|
|
93
|
+
subst_eps_r: float
|
|
94
|
+
Dielectric constant of the substrate material.
|
|
95
|
+
freq_hz: float
|
|
96
|
+
Frequency in Hz
|
|
97
|
+
tolerance: float
|
|
98
|
+
Maximum deviation allowed for calculated characteristic impedance
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
width: float
|
|
103
|
+
Width of trace for given characteristic impedance in mm.
|
|
104
|
+
er_eff: float
|
|
105
|
+
Effective dielectric constant at the given frequency.
|
|
106
|
+
|
|
107
|
+
Notes
|
|
108
|
+
-----
|
|
109
|
+
For the formulas used, refer to:
|
|
110
|
+
`<https://qucs.github.io/docs/technical/technical.pdf>`_.
|
|
111
|
+
`<https://ieeexplore.ieee.org/document/1124303>`_.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
def get_Z_at_freq(
|
|
115
|
+
w: float,
|
|
116
|
+
h: float,
|
|
117
|
+
t: float,
|
|
118
|
+
er: float,
|
|
119
|
+
f_hz: float,
|
|
120
|
+
) -> tuple[float, float]:
|
|
121
|
+
"""Calculate microstrip characteristic impedance at a given
|
|
122
|
+
frequency using Hammerstad-Jensen equations."""
|
|
123
|
+
# Constants
|
|
124
|
+
eta0 = 376.730313668
|
|
125
|
+
mue0 = 4 * np.pi * 1e-7
|
|
126
|
+
h_m = mm_to_m(h)
|
|
127
|
+
|
|
128
|
+
if t == 0:
|
|
129
|
+
t = 1e-6
|
|
130
|
+
|
|
131
|
+
# 1. Thickness Correction
|
|
132
|
+
u = w / h
|
|
133
|
+
tau = t / h
|
|
134
|
+
# coth(x) = 1/tanh(x)
|
|
135
|
+
coth_term = 1.0 / np.tanh(np.sqrt(6.517 * u))
|
|
136
|
+
delta_u1 = (tau / np.pi) * np.log(1 + (4 * np.e) / (tau * (coth_term**2)))
|
|
137
|
+
delta_ur = 0.5 * delta_u1 * (1 + 1.0 / np.cosh(np.sqrt(er - 1)))
|
|
138
|
+
|
|
139
|
+
# Effective width ratio to be used in all static formulas
|
|
140
|
+
ur = u + delta_ur
|
|
141
|
+
|
|
142
|
+
# 2. Static Impedance (Air-filled Z01)
|
|
143
|
+
f_ur = 6 + (2 * np.pi - 6) * np.exp(-((30.666 / ur) ** 0.7528))
|
|
144
|
+
Z01 = (eta0 / (2 * np.pi)) * np.log((f_ur / ur) + np.sqrt(1 + (2 / ur) ** 2))
|
|
145
|
+
|
|
146
|
+
# 3. Static Effective Permittivity
|
|
147
|
+
a_u = (
|
|
148
|
+
1
|
|
149
|
+
+ (1 / 49) * np.log((ur**4 + (ur / 52) ** 2) / (ur**4 + 0.432))
|
|
150
|
+
+ (1 / 18.7) * np.log(1 + (ur / 18.1) ** 3)
|
|
151
|
+
)
|
|
152
|
+
b_er = 0.564 * ((er - 0.9) / (er + 3)) ** 0.053
|
|
153
|
+
er_eff_0 = (er + 1) / 2 + ((er - 1) / 2) * (1 + 10 / ur) ** (-a_u * b_er)
|
|
154
|
+
|
|
155
|
+
# 4. Static Impedance
|
|
156
|
+
Z0 = Z01 / np.sqrt(er_eff_0)
|
|
157
|
+
|
|
158
|
+
# 5. Frequency Dispersion
|
|
159
|
+
if f_hz <= 0:
|
|
160
|
+
return Z0
|
|
161
|
+
|
|
162
|
+
# Transition frequency (fp) in Hz
|
|
163
|
+
fp = Z0 / (2 * mue0 * h_m)
|
|
164
|
+
|
|
165
|
+
# Dispersion factor G
|
|
166
|
+
G = (np.pi**2 / 12) * ((er - 1) / er_eff_0) * np.sqrt(2 * np.pi * Z0 / eta0)
|
|
167
|
+
|
|
168
|
+
# Frequency-dependent effective permittivity
|
|
169
|
+
er_eff_f = er - (er - er_eff_0) / (1 + G * (f_hz / fp) ** 2)
|
|
170
|
+
|
|
171
|
+
# Frequency-dependent impedance
|
|
172
|
+
Z_f = Z0 * ((er_eff_f - 1) / (er_eff_0 - 1)) * np.sqrt(er_eff_0 / er_eff_f)
|
|
173
|
+
|
|
174
|
+
return Z_f, er_eff_f
|
|
175
|
+
|
|
176
|
+
# --- Convergence check ---
|
|
177
|
+
Z_thinnest, _ = get_Z_at_freq(
|
|
178
|
+
0.001, subs_height, copper_thickness, subst_eps_r, freq_hz
|
|
179
|
+
)
|
|
180
|
+
Z_widest, _ = get_Z_at_freq(
|
|
181
|
+
100, subs_height, copper_thickness, subst_eps_r, freq_hz
|
|
182
|
+
)
|
|
183
|
+
Z_min = min(Z_thinnest, Z_widest)
|
|
184
|
+
Z_max = max(Z_thinnest, Z_widest)
|
|
185
|
+
if not (Z_min <= charac_imp <= Z_max):
|
|
186
|
+
raise ValueError(
|
|
187
|
+
f"Target impedance {charac_imp} Ω not realizable on this substrate"
|
|
188
|
+
f" (range [{Z_min:.1f}, {Z_max:.1f}] Ω)"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# --- Binary Search for Width ---
|
|
192
|
+
low, high = 0.001, 100
|
|
193
|
+
mid_w = 0.0
|
|
194
|
+
for _ in range(100):
|
|
195
|
+
mid_w = (low + high) / 2
|
|
196
|
+
Z_curr, _ = get_Z_at_freq(
|
|
197
|
+
mid_w, subs_height, copper_thickness, subst_eps_r, freq_hz
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
if abs(Z_curr - charac_imp) < tolerance:
|
|
201
|
+
break
|
|
202
|
+
if Z_curr > charac_imp:
|
|
203
|
+
low = mid_w
|
|
204
|
+
else:
|
|
205
|
+
high = mid_w
|
|
206
|
+
_, er_eff = get_Z_at_freq(
|
|
207
|
+
mid_w, subs_height, copper_thickness, subst_eps_r, freq_hz
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
return mid_w, er_eff
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def conductance_G1(patch_width: float, frequency: float) -> float:
|
|
214
|
+
"""
|
|
215
|
+
Computes the conductance of patch antenna.
|
|
216
|
+
|
|
217
|
+
Parameters
|
|
218
|
+
----------
|
|
219
|
+
|
|
220
|
+
patch_width: float
|
|
221
|
+
width of patch antenna in meters
|
|
222
|
+
frequency: float
|
|
223
|
+
frequency in Hz
|
|
224
|
+
|
|
225
|
+
Returns
|
|
226
|
+
-------
|
|
227
|
+
conductance_G1: float
|
|
228
|
+
conductance of patch antenna
|
|
229
|
+
|
|
230
|
+
Notes
|
|
231
|
+
-----
|
|
232
|
+
Refer to Balanis (3rd ed.) 14-12 formula for radiation conductance of one slot.
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
lambda0 = C0 / frequency
|
|
236
|
+
k0 = 2 * np.pi / lambda0
|
|
237
|
+
|
|
238
|
+
def integrand(theta: float) -> float:
|
|
239
|
+
"""Integrand for conductance calculation using the cavity model."""
|
|
240
|
+
ct = np.cos(theta)
|
|
241
|
+
if abs(ct) < 1e-5:
|
|
242
|
+
return 0.0
|
|
243
|
+
term = np.sin((k0 * patch_width / 2) * ct) / ct
|
|
244
|
+
return (term**2) * (np.sin(theta) ** 3)
|
|
245
|
+
|
|
246
|
+
integral, _ = quad(integrand, 0, np.pi, limit=1000)
|
|
247
|
+
return integral / (120 * np.pi**2)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def inset_depth(
|
|
251
|
+
charac_imp: float,
|
|
252
|
+
patch_width: float,
|
|
253
|
+
patch_length: float,
|
|
254
|
+
frequency: float,
|
|
255
|
+
) -> tuple[float, float]:
|
|
256
|
+
"""
|
|
257
|
+
Compute the inset feed depth of an inset fed patch antenna
|
|
258
|
+
and probe position of probe fed patch antenna.
|
|
259
|
+
|
|
260
|
+
Parameters
|
|
261
|
+
----------
|
|
262
|
+
charac_imp: float
|
|
263
|
+
characteristic impedance of the antenna.
|
|
264
|
+
patch_width: float
|
|
265
|
+
patch antenna width in meters.
|
|
266
|
+
patch_length: float
|
|
267
|
+
patch antenna length in meters.
|
|
268
|
+
frequency: float
|
|
269
|
+
frequency in Hz.
|
|
270
|
+
|
|
271
|
+
Returns
|
|
272
|
+
-------
|
|
273
|
+
inset_length: float
|
|
274
|
+
inset length in meters
|
|
275
|
+
probe_pos: float
|
|
276
|
+
probe position in meters. note that probe position is in Y direction and X is 0.
|
|
277
|
+
|
|
278
|
+
Notes
|
|
279
|
+
-----
|
|
280
|
+
Refer to Balanis formula 14-20a
|
|
281
|
+
|
|
282
|
+
"""
|
|
283
|
+
|
|
284
|
+
G1 = conductance_G1(patch_width, frequency)
|
|
285
|
+
R_edge = 1.0 / (2.0 * G1)
|
|
286
|
+
|
|
287
|
+
if charac_imp > R_edge:
|
|
288
|
+
raise ValueError("characteristic impedance must be <= edge resistance")
|
|
289
|
+
|
|
290
|
+
inset_length = (patch_length / np.pi) * np.arccos(np.sqrt(charac_imp / R_edge))
|
|
291
|
+
probe_pos = inset_length
|
|
292
|
+
return inset_length, probe_pos
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
class PatchDims(NamedTuple):
|
|
296
|
+
"""
|
|
297
|
+
Named tuple holding all rectangular patch antenna dimensions.
|
|
298
|
+
|
|
299
|
+
Attributes
|
|
300
|
+
----------
|
|
301
|
+
patch_width_mm : float
|
|
302
|
+
Width of the patch in the x-direction in mm.
|
|
303
|
+
patch_length_mm : float
|
|
304
|
+
Length of the patch in the y-direction in mm.
|
|
305
|
+
inset_length_mm : float
|
|
306
|
+
Inset depth of the patch in mm.
|
|
307
|
+
inset_width_mm : float
|
|
308
|
+
Inset gap width of the patch in mm.
|
|
309
|
+
probe_pos_mm : float
|
|
310
|
+
Feed position for probe-fed patch in the y-direction in mm.
|
|
311
|
+
"""
|
|
312
|
+
|
|
313
|
+
patch_width_mm: float
|
|
314
|
+
patch_length_mm: float
|
|
315
|
+
inset_length_mm: float
|
|
316
|
+
inset_width_mm: float
|
|
317
|
+
probe_pos_mm: float
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def patch_dims(
|
|
321
|
+
frequency_hz: float,
|
|
322
|
+
subst_eps_r: float,
|
|
323
|
+
subst_height: float,
|
|
324
|
+
charac_imp: float,
|
|
325
|
+
copper_thickness: float,
|
|
326
|
+
) -> PatchDims:
|
|
327
|
+
"""
|
|
328
|
+
Calculate rectangular microstrip patch dimensions.
|
|
329
|
+
|
|
330
|
+
Parameters
|
|
331
|
+
----------
|
|
332
|
+
frequency_hz: float
|
|
333
|
+
resonant frequency of antenna in Hz.
|
|
334
|
+
subst_eps_r: float
|
|
335
|
+
Dielectric constant of the substrate.
|
|
336
|
+
subst_height: float
|
|
337
|
+
Thickness of the substrate.
|
|
338
|
+
charac_imp: float
|
|
339
|
+
characteristic impedance of the feed line.
|
|
340
|
+
copper_thickness: float
|
|
341
|
+
Thickness of the copper trace.
|
|
342
|
+
|
|
343
|
+
Returns
|
|
344
|
+
-------
|
|
345
|
+
PatchDims
|
|
346
|
+
a named tuple with the following attributes.
|
|
347
|
+
patch_width_mm: float
|
|
348
|
+
Width of the patch antenna in direction of x in mm.
|
|
349
|
+
patch_length_mm: float
|
|
350
|
+
Length of the patch antenna in direction of y in mm
|
|
351
|
+
inset_length_mm: float
|
|
352
|
+
Inset length of the patch in direction of y in mm
|
|
353
|
+
inset_width_mm: float
|
|
354
|
+
Inset width of the patch in the direction of x in mm
|
|
355
|
+
probe_pos_mm: float
|
|
356
|
+
The feed position of probe fed patch antenna in direction of y in mm
|
|
357
|
+
"""
|
|
358
|
+
|
|
359
|
+
patch_width = C0 / (2 * frequency_hz) * np.sqrt(2 / (subst_eps_r + 1))
|
|
360
|
+
patch_width_mm = m_to_mm(patch_width)
|
|
361
|
+
|
|
362
|
+
eff_eps_r = (subst_eps_r + 1) / 2 + (subst_eps_r - 1) / 2 * (
|
|
363
|
+
1 / np.sqrt(1 + 12 * subst_height / patch_width)
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
delta_length = (
|
|
367
|
+
0.412
|
|
368
|
+
* subst_height
|
|
369
|
+
* ((eff_eps_r + 0.3) * (patch_width / subst_height + 0.264))
|
|
370
|
+
/ ((eff_eps_r - 0.258) * (patch_width / subst_height + 0.8))
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
patch_length = (C0 / (2 * frequency_hz * np.sqrt(eff_eps_r))) - 2 * delta_length
|
|
374
|
+
patch_length_mm = m_to_mm(patch_length)
|
|
375
|
+
|
|
376
|
+
inset_length, probe_pos = inset_depth(
|
|
377
|
+
charac_imp, patch_width, patch_length, frequency_hz
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
inset_length_mm = m_to_mm(inset_length)
|
|
381
|
+
probe_pos_mm = m_to_mm(probe_pos)
|
|
382
|
+
|
|
383
|
+
inset_width_mm, _ = microstrip_width_from_impedance(
|
|
384
|
+
charac_imp,
|
|
385
|
+
m_to_mm(subst_height),
|
|
386
|
+
copper_thickness,
|
|
387
|
+
subst_eps_r,
|
|
388
|
+
frequency_hz,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
return PatchDims(
|
|
392
|
+
patch_width_mm,
|
|
393
|
+
patch_length_mm,
|
|
394
|
+
inset_length_mm,
|
|
395
|
+
inset_width_mm,
|
|
396
|
+
probe_pos_mm,
|
|
397
|
+
)
|