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 @@ -64,6 +64,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Bug in `TerminalComponentModelerData.get_antenna_metrics_data()` where `WavePort` mode indices were not properly handled. Improved docstrings and type hints to make the usage clearer.
- Improved type hints for `Tidy3dBaseModel`, so that all derived classes will have more accurate return types.
- More robust method for suppressing RF license warnings during tests.
- Fixed frequency sampling of `TransmissionLineDataset` within `MicrowaveModeData` when using group index calculation.

### Removed
- Removed deprecated `use_complex_fields` parameter from `TwoPhotonAbsorption` and `KerrNonlinearity`. Parameters `beta` and `n2` are now real-valued only, as is `n0` if specified.
Expand Down
82 changes: 82 additions & 0 deletions tests/test_components/test_microwave.py
Original file line number Diff line number Diff line change
Expand Up @@ -1156,6 +1156,88 @@ def test_mode_solver_with_microwave_mode_spec():
)


def test_mode_solver_with_microwave_group_index():
"""Test that group_index calculation with MicrowaveModeSpec correctly filters frequencies."""

width = 1.0 * mm
height = 0.5 * mm
metal_thickness = 0.1 * mm

stripline_sim = make_mw_sim(
transmission_line_type="stripline",
width=width,
height=height,
metal_thickness=metal_thickness,
)
dl = 0.05 * mm
stripline_sim = stripline_sim.updated_copy(grid_spec=td.GridSpec.uniform(dl=dl))

plane = td.Box(center=(0, 0, 0), size=(0, 10 * width, 2 * height + metal_thickness))
num_modes = 1

# Define original frequencies that we want in the final result
original_freqs = [1e9, 5e9, 10e9]

# Create custom impedance spec (AutoImpedanceSpec won't work with local mode solver)
custom_spec = td.CustomImpedanceSpec(
voltage_spec=None,
current_spec=td.AxisAlignedCurrentIntegralSpec(
size=(0, width + dl, metal_thickness + dl), sign="+"
),
)

# Enable group_index calculation
mode_spec = td.MicrowaveModeSpec(
num_modes=num_modes,
target_neff=2.2,
impedance_specs=custom_spec,
group_index_step=True, # This will expand frequencies to triplets
)

mms = ModeSolver(
simulation=stripline_sim,
plane=plane,
mode_spec=mode_spec,
colocate=False,
freqs=original_freqs,
)

# Get the mode solver data
mms_data: td.MicrowaveModeSolverData = mms.data

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

# Verify that transmission line data exists
assert mms_data.transmission_line_data is not None, "Transmission line data should exist"

# Verify that the frequencies in transmission_line_data match the original frequencies
# (not the expanded triplet used internally for group index calculation)
tl_freqs_Z0 = mms_data.transmission_line_data.Z0.coords["f"].values
tl_freqs_voltage = mms_data.transmission_line_data.voltage_coeffs.coords["f"].values
tl_freqs_current = mms_data.transmission_line_data.current_coeffs.coords["f"].values

assert len(tl_freqs_Z0) == len(original_freqs), (
f"Z0 should have {len(original_freqs)} frequencies, got {len(tl_freqs_Z0)}"
)
assert len(tl_freqs_voltage) == len(original_freqs), (
f"voltage_coeffs should have {len(original_freqs)} frequencies, got {len(tl_freqs_voltage)}"
)
assert len(tl_freqs_current) == len(original_freqs), (
f"current_coeffs should have {len(original_freqs)} frequencies, got {len(tl_freqs_current)}"
)

assert np.allclose(tl_freqs_Z0, original_freqs), (
f"Z0 frequencies {tl_freqs_Z0} should match original {original_freqs}"
)
assert np.allclose(tl_freqs_voltage, original_freqs), (
f"voltage_coeffs frequencies {tl_freqs_voltage} should match original {original_freqs}"
)
assert np.allclose(tl_freqs_current, original_freqs), (
f"current_coeffs frequencies {tl_freqs_current} should match original {original_freqs}"
)


@pytest.mark.parametrize("axis", [0, 1, 2])
def test_voltage_integral_axes(axis):
"""Check AxisAlignedVoltageIntegral runs."""
Expand Down
26 changes: 20 additions & 6 deletions tidy3d/components/data/monitor_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -1890,6 +1890,24 @@ def _find_closest_pairs(arr: Numpy) -> tuple[Numpy, Numpy]:

return pairs, values

def _group_index_freq_slices(self) -> tuple[slice, slice, slice]:
"""Get frequency slices for group index numerical differentiation.

Group index calculation uses three-point finite differences, requiring
backward, center, and forward frequency points organized as triplets.

Returns
-------
tuple[slice, slice, slice]
Slices for (backward, center, forward) frequencies from the frequency array.
"""
freqs = self.n_complex.coords["f"].values
num_freqs = freqs.size
back = slice(0, num_freqs, 3)
center = slice(1, num_freqs, 3)
fwd = slice(2, num_freqs, 3)
return back, center, fwd

def _group_index_post_process(self, frequency_step: float) -> ModeData:
"""Calculate group index and remove added frequencies used only for this calculation.

Expand All @@ -1904,12 +1922,8 @@ def _group_index_post_process(self, frequency_step: float) -> ModeData:
Filtered data with calculated group index.
"""

freqs = self.n_complex.coords["f"].values
num_freqs = freqs.size
back = slice(0, num_freqs, 3)
center = slice(1, num_freqs, 3)
fwd = slice(2, num_freqs, 3)
freqs = freqs[center]
back, center, fwd = self._group_index_freq_slices()
freqs = self.n_complex.coords["f"].values[center]

# calculate group index
n_center = self.n_eff.isel(f=center).values
Expand Down
24 changes: 24 additions & 0 deletions tidy3d/components/microwave/data/monitor_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,30 @@ def modes_info(self) -> xr.Dataset:
super_info["Im(Z0)"] = self.transmission_line_data.Z0.imag
return super_info

def _group_index_post_process(self, frequency_step: float) -> ModeData:
"""Calculate group index and remove added frequencies used only for this calculation.

Parameters
----------
frequency_step: float
Fractional frequency step used to calculate the group index.

Returns
-------
:class:`.ModeData`
Filtered data with calculated group index.
"""
super_data = super()._group_index_post_process(frequency_step)
if self.transmission_line_data is not None:
_, center_inds, _ = self._group_index_freq_slices()
update_dict = {
"Z0": self.transmission_line_data.Z0.isel(f=center_inds),
"voltage_coeffs": self.transmission_line_data.voltage_coeffs.isel(f=center_inds),
"current_coeffs": self.transmission_line_data.current_coeffs.isel(f=center_inds),
}
super_data = super_data.updated_copy(**update_dict, path="transmission_line_data")
return super_data


class MicrowaveModeSolverData(ModeSolverData, MicrowaveModeData):
"""
Expand Down