Skip to content

Commit cca3130

Browse files
committed
feat(rf): Add RF specific mode characteristics to MicrowaveModeData
1 parent 0547714 commit cca3130

File tree

12 files changed

+350
-6
lines changed

12 files changed

+350
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3939
- Added warning if port mesh refinement is incompatible with the `GridSpec` in the `TerminalComponentModeler`.
4040
- Various types, e.g. different `Simulation` or `SimulationData` sub-classes, can be loaded from file directly with `Tidy3dBaseModel.from_file()`.
4141
- Added `interp_spec` in `EMEModeSpec` to enable faster multi-frequency EME simulations. Note that the default is now `ModeInterpSpec.cheb(num_points=3, reduce_data=True)`; previously the computation was repeated at all frequencies.
42+
- Added more RF-specific mode characteristics to `MicrowaveModeData`, including propagation constants (alpha, beta, gamma), phase/group velocities, wave impedance, and automatic mode classification with configurable polarization thresholds in `MicrowaveModeSpec`.
4243

4344
### Breaking Changes
4445
- Edge singularity correction at PEC and lossy metal edges defaults to `True`.

schemas/EMESimulation.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8152,6 +8152,12 @@
81528152
],
81538153
"type": "string"
81548154
},
8155+
"qtem_polarization_threshold": {
8156+
"default": 0.95,
8157+
"exclusiveMinimum": 0.0,
8158+
"maximum": 1.0,
8159+
"type": "number"
8160+
},
81558161
"sort_spec": {
81568162
"allOf": [
81578163
{
@@ -8174,6 +8180,12 @@
81748180
"exclusiveMinimum": 0,
81758181
"type": "number"
81768182
},
8183+
"tem_polarization_threshold": {
8184+
"default": 0.995,
8185+
"exclusiveMinimum": 0.0,
8186+
"maximum": 1.0,
8187+
"type": "number"
8188+
},
81778189
"track_freq": {
81788190
"enum": [
81798191
"central",

schemas/ModeSimulation.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7399,6 +7399,12 @@
73997399
],
74007400
"type": "string"
74017401
},
7402+
"qtem_polarization_threshold": {
7403+
"default": 0.95,
7404+
"exclusiveMinimum": 0.0,
7405+
"maximum": 1.0,
7406+
"type": "number"
7407+
},
74027408
"sort_spec": {
74037409
"allOf": [
74047410
{
@@ -7421,6 +7427,12 @@
74217427
"exclusiveMinimum": 0,
74227428
"type": "number"
74237429
},
7430+
"tem_polarization_threshold": {
7431+
"default": 0.995,
7432+
"exclusiveMinimum": 0.0,
7433+
"maximum": 1.0,
7434+
"type": "number"
7435+
},
74247436
"track_freq": {
74257437
"enum": [
74267438
"central",

schemas/Simulation.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11381,6 +11381,12 @@
1138111381
],
1138211382
"type": "string"
1138311383
},
11384+
"qtem_polarization_threshold": {
11385+
"default": 0.95,
11386+
"exclusiveMinimum": 0.0,
11387+
"maximum": 1.0,
11388+
"type": "number"
11389+
},
1138411390
"sort_spec": {
1138511391
"allOf": [
1138611392
{
@@ -11403,6 +11409,12 @@
1140311409
"exclusiveMinimum": 0,
1140411410
"type": "number"
1140511411
},
11412+
"tem_polarization_threshold": {
11413+
"default": 0.995,
11414+
"exclusiveMinimum": 0.0,
11415+
"maximum": 1.0,
11416+
"type": "number"
11417+
},
1140611418
"track_freq": {
1140711419
"enum": [
1140811420
"central",

schemas/TerminalComponentModeler.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11723,6 +11723,12 @@
1172311723
],
1172411724
"type": "string"
1172511725
},
11726+
"qtem_polarization_threshold": {
11727+
"default": 0.95,
11728+
"exclusiveMinimum": 0.0,
11729+
"maximum": 1.0,
11730+
"type": "number"
11731+
},
1172611732
"sort_spec": {
1172711733
"allOf": [
1172811734
{
@@ -11745,6 +11751,12 @@
1174511751
"exclusiveMinimum": 0,
1174611752
"type": "number"
1174711753
},
11754+
"tem_polarization_threshold": {
11755+
"default": 0.995,
11756+
"exclusiveMinimum": 0.0,
11757+
"maximum": 1.0,
11758+
"type": "number"
11759+
},
1174811760
"track_freq": {
1174911761
"enum": [
1175011762
"central",

tests/test_components/test_microwave.py

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ def make_mw_sim(
384384
boundary_spec=boundary_spec,
385385
plot_length_units="mm",
386386
symmetry=(0, 0, 0),
387+
subpixel=False,
387388
)
388389
return sim
389390

@@ -1099,16 +1100,15 @@ def test_mode_solver_with_microwave_mode_spec():
10991100
num_modes = 3
11001101
impedance_specs = td.AutoImpedanceSpec()
11011102
mode_spec = td.MicrowaveModeSpec(
1102-
num_modes=num_modes,
1103-
target_neff=2.2,
1104-
impedance_specs=impedance_specs,
1103+
num_modes=num_modes, target_neff=2.2, impedance_specs=impedance_specs
11051104
)
1105+
freqs = (1e9, 5e9, 10e9)
11061106
mms = ModeSolver(
11071107
simulation=stripline_sim,
11081108
plane=plane,
11091109
mode_spec=mode_spec,
11101110
colocate=False,
1111-
freqs=[1e9, 5e9, 10e9],
1111+
freqs=freqs,
11121112
)
11131113

11141114
# _, ax = plt.subplots(1, 1, tight_layout=True, figsize=(15, 15))
@@ -1145,6 +1145,50 @@ def test_mode_solver_with_microwave_mode_spec():
11451145
np.isclose(mms_data.transmission_line_data.Z0.real.sel(mode_index=0), 28.6, rtol=0.2)
11461146
)
11471147

1148+
# Test RF-specific mode characteristics
1149+
e_r = 4.4
1150+
k0 = 1e6 * 2 * np.pi * np.array(freqs) / td.C_0
1151+
n_eff = np.sqrt(e_r)
1152+
# 1. Mode classification (stripline should support TEM mode)
1153+
assert mms_data.mode_classifications[0] == "TEM", (
1154+
f"Expected TEM mode for stripline, got {mms_data.mode_classifications[0]}"
1155+
)
1156+
1157+
assert np.allclose(mms_data.effective_relative_permittivity.sel(mode_index=0).real, e_r)
1158+
assert np.allclose(
1159+
mms_data.effective_relative_permittivity.sel(mode_index=0).imag, 0.0, atol=1e-6
1160+
)
1161+
1162+
# Attenuation constant (nearly zero for lossless line)
1163+
alpha = mms_data.alpha.sel(mode_index=0)
1164+
assert np.allclose(alpha, 0.0, atol=1e-6)
1165+
1166+
# Phase constant (positive, increases with frequency)
1167+
beta = mms_data.beta.sel(mode_index=0)
1168+
assert np.allclose(beta, k0 * n_eff)
1169+
1170+
# Propagation constant (gamma = -alpha + j*beta)
1171+
gamma = mms_data.gamma.sel(mode_index=0)
1172+
assert np.allclose(gamma.real, 0.0, atol=1e-6)
1173+
assert np.allclose(gamma.imag, k0 * n_eff)
1174+
1175+
# Phase velocity (v_p ~ c/n_eff)
1176+
v_p = mms_data.phase_velocity.sel(mode_index=0)
1177+
expected_v_p = td.C_0 * 1e-6 / n_eff
1178+
assert np.allclose(v_p, expected_v_p, rtol=1e-6)
1179+
1180+
# Wave impedance (should be positive and physically reasonable)
1181+
Z_wave = mms_data.wave_impedance.sel(mode_index=0)
1182+
assert np.allclose(Z_wave.real, td.ETA_0 / n_eff, rtol=1e-4)
1183+
assert np.allclose(Z_wave.imag, 0.0, atol=1e-6)
1184+
1185+
# Distance for 40dB (very large for low-loss line)
1186+
d_40dB = mms_data.distance_40dB.sel(mode_index=0)
1187+
assert np.all(d_40dB > 100)
1188+
1189+
with AssertLogLevel("WARNING", contains_str="The 'group_velocity' was not computed."):
1190+
mms_data.group_velocity
1191+
11481192
# Make sure a single spec can be used
11491193
microwave_spec_custom = td.MicrowaveModeSpec(
11501194
num_modes=num_modes, target_neff=2.2, impedance_specs=custom_spec
@@ -1207,6 +1251,7 @@ def test_mode_solver_with_microwave_group_index():
12071251

12081252
# Verify that group index was calculated
12091253
assert mms_data.n_group is not None, "Group index should be calculated"
1254+
assert mms_data.group_velocity is not None, "Group velocity should be calculated"
12101255

12111256
# Verify that transmission line data exists
12121257
assert mms_data.transmission_line_data is not None, "Transmission line data should exist"
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from __future__ import annotations
2+
3+
from tidy3d.components.data.data_array import FreqModeDataArray
4+
from tidy3d.constants import NEPERPERMETER, PERMETER, RADPERMETER, VELOCITY_SI
5+
6+
7+
class PropagationConstantArray(FreqModeDataArray):
8+
__slots__ = ()
9+
_data_attrs = {"units": PERMETER, "long_name": "propagation constant"}
10+
11+
12+
class PhaseConstantArray(FreqModeDataArray):
13+
__slots__ = ()
14+
_data_attrs = {"units": RADPERMETER, "long_name": "phase constant"}
15+
16+
17+
class AttenuationConstantArray(FreqModeDataArray):
18+
__slots__ = ()
19+
_data_attrs = {"units": NEPERPERMETER, "long_name": "attenuation constant"}
20+
21+
22+
class PhaseVelocityArray(FreqModeDataArray):
23+
__slots__ = ()
24+
_data_attrs = {"units": VELOCITY_SI, "long_name": "phase velocity"}
25+
26+
27+
class GroupVelocityArray(FreqModeDataArray):
28+
__slots__ = ()
29+
_data_attrs = {"units": VELOCITY_SI, "long_name": "group velocity"}

0 commit comments

Comments
 (0)