Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Various types, e.g. different `Simulation` or `SimulationData` sub-classes, can be loaded from file directly with `Tidy3dBaseModel.from_file()`.
- 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.
- Added `smoothed_projection` for topology optimization of completely binarized designs.
- 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`.

### Breaking Changes
- Edge singularity correction at PEC and lossy metal edges defaults to `True`.
Expand Down
12 changes: 12 additions & 0 deletions schemas/EMESimulation.json
Original file line number Diff line number Diff line change
Expand Up @@ -8152,6 +8152,12 @@
],
"type": "string"
},
"qtem_polarization_threshold": {
"default": 0.95,
"exclusiveMinimum": 0.0,
"maximum": 1.0,
"type": "number"
},
"sort_spec": {
"allOf": [
{
Expand All @@ -8174,6 +8180,12 @@
"exclusiveMinimum": 0,
"type": "number"
},
"tem_polarization_threshold": {
"default": 0.995,
"exclusiveMinimum": 0.0,
"maximum": 1.0,
"type": "number"
},
"track_freq": {
"enum": [
"central",
Expand Down
12 changes: 12 additions & 0 deletions schemas/ModeSimulation.json
Original file line number Diff line number Diff line change
Expand Up @@ -7399,6 +7399,12 @@
],
"type": "string"
},
"qtem_polarization_threshold": {
"default": 0.95,
"exclusiveMinimum": 0.0,
"maximum": 1.0,
"type": "number"
},
"sort_spec": {
"allOf": [
{
Expand All @@ -7421,6 +7427,12 @@
"exclusiveMinimum": 0,
"type": "number"
},
"tem_polarization_threshold": {
"default": 0.995,
"exclusiveMinimum": 0.0,
"maximum": 1.0,
"type": "number"
},
"track_freq": {
"enum": [
"central",
Expand Down
12 changes: 12 additions & 0 deletions schemas/Simulation.json
Original file line number Diff line number Diff line change
Expand Up @@ -11381,6 +11381,12 @@
],
"type": "string"
},
"qtem_polarization_threshold": {
"default": 0.95,
"exclusiveMinimum": 0.0,
"maximum": 1.0,
"type": "number"
},
"sort_spec": {
"allOf": [
{
Expand All @@ -11403,6 +11409,12 @@
"exclusiveMinimum": 0,
"type": "number"
},
"tem_polarization_threshold": {
"default": 0.995,
"exclusiveMinimum": 0.0,
"maximum": 1.0,
"type": "number"
},
"track_freq": {
"enum": [
"central",
Expand Down
12 changes: 12 additions & 0 deletions schemas/TerminalComponentModeler.json
Original file line number Diff line number Diff line change
Expand Up @@ -11723,6 +11723,12 @@
],
"type": "string"
},
"qtem_polarization_threshold": {
"default": 0.95,
"exclusiveMinimum": 0.0,
"maximum": 1.0,
"type": "number"
},
"sort_spec": {
"allOf": [
{
Expand All @@ -11745,6 +11751,12 @@
"exclusiveMinimum": 0,
"type": "number"
},
"tem_polarization_threshold": {
"default": 0.995,
"exclusiveMinimum": 0.0,
"maximum": 1.0,
"type": "number"
},
"track_freq": {
"enum": [
"central",
Expand Down
53 changes: 49 additions & 4 deletions tests/test_components/test_microwave.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ def make_mw_sim(
boundary_spec=boundary_spec,
plot_length_units="mm",
symmetry=(0, 0, 0),
subpixel=False,
)
return sim

Expand Down Expand Up @@ -1099,16 +1100,15 @@ def test_mode_solver_with_microwave_mode_spec():
num_modes = 3
impedance_specs = td.AutoImpedanceSpec()
mode_spec = td.MicrowaveModeSpec(
num_modes=num_modes,
target_neff=2.2,
impedance_specs=impedance_specs,
num_modes=num_modes, target_neff=2.2, impedance_specs=impedance_specs
)
freqs = (1e9, 5e9, 10e9)
mms = ModeSolver(
simulation=stripline_sim,
plane=plane,
mode_spec=mode_spec,
colocate=False,
freqs=[1e9, 5e9, 10e9],
freqs=freqs,
)

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

# Test RF-specific mode characteristics
e_r = 4.4
k0 = 1e6 * 2 * np.pi * np.array(freqs) / td.C_0
n_eff = np.sqrt(e_r)
# 1. Mode classification (stripline should support TEM mode)
assert mms_data.mode_classifications[0] == "TEM", (
f"Expected TEM mode for stripline, got {mms_data.mode_classifications[0]}"
)

assert np.allclose(mms_data.effective_relative_permittivity.sel(mode_index=0).real, e_r)
assert np.allclose(
mms_data.effective_relative_permittivity.sel(mode_index=0).imag, 0.0, atol=1e-6
)

# Attenuation constant (nearly zero for lossless line)
alpha = mms_data.alpha.sel(mode_index=0)
assert np.allclose(alpha, 0.0, atol=1e-6)

# Phase constant (positive, increases with frequency)
beta = mms_data.beta.sel(mode_index=0)
assert np.allclose(beta, k0 * n_eff)

# Propagation constant (gamma = -alpha + j*beta)
gamma = mms_data.gamma.sel(mode_index=0)
assert np.allclose(gamma.real, 0.0, atol=1e-6)
assert np.allclose(gamma.imag, k0 * n_eff)

# Phase velocity (v_p ~ c/n_eff)
v_p = mms_data.phase_velocity.sel(mode_index=0)
expected_v_p = td.C_0 * 1e-6 / n_eff
assert np.allclose(v_p, expected_v_p, rtol=1e-6)

# Wave impedance (should be positive and physically reasonable)
Z_wave = mms_data.wave_impedance.sel(mode_index=0)
assert np.allclose(Z_wave.real, td.ETA_0 / n_eff, rtol=1e-4)
assert np.allclose(Z_wave.imag, 0.0, atol=1e-6)

# Distance for 40dB (very large for low-loss line)
d_40dB = mms_data.distance_40dB.sel(mode_index=0)
assert np.all(d_40dB > 100)

with AssertLogLevel("WARNING", contains_str="The 'group_velocity' was not computed."):
mms_data.group_velocity

# Make sure a single spec can be used
microwave_spec_custom = td.MicrowaveModeSpec(
num_modes=num_modes, target_neff=2.2, impedance_specs=custom_spec
Expand Down Expand Up @@ -1207,6 +1251,7 @@ def test_mode_solver_with_microwave_group_index():

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

# Verify that transmission line data exists
assert mms_data.transmission_line_data is not None, "Transmission line data should exist"
Expand Down
29 changes: 29 additions & 0 deletions tidy3d/components/microwave/data/data_array.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from __future__ import annotations

from tidy3d.components.data.data_array import FreqModeDataArray
from tidy3d.constants import NEPERPERMETER, PERMETER, RADPERMETER, VELOCITY_SI


class PropagationConstantArray(FreqModeDataArray):
__slots__ = ()
_data_attrs = {"units": PERMETER, "long_name": "propagation constant"}


class PhaseConstantArray(FreqModeDataArray):
__slots__ = ()
_data_attrs = {"units": RADPERMETER, "long_name": "phase constant"}


class AttenuationConstantArray(FreqModeDataArray):
__slots__ = ()
_data_attrs = {"units": NEPERPERMETER, "long_name": "attenuation constant"}


class PhaseVelocityArray(FreqModeDataArray):
__slots__ = ()
_data_attrs = {"units": VELOCITY_SI, "long_name": "phase velocity"}


class GroupVelocityArray(FreqModeDataArray):
__slots__ = ()
_data_attrs = {"units": VELOCITY_SI, "long_name": "group velocity"}
Loading