Skip to content

Commit 12b7693

Browse files
assume sorted
1 parent 0d64dad commit 12b7693

File tree

3 files changed

+91
-3
lines changed

3 files changed

+91
-3
lines changed

tests/test_components/test_mode_interp.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,84 @@ def test_mode_solver_data_interp_extrapolation_warning():
657657
mode_data.interp_in_freq(freqs=freqs_extrap, method="linear")
658658

659659

660+
# ============================================================================
661+
# assume_sorted Tests (for source frequencies)
662+
# ============================================================================
663+
664+
665+
@pytest.mark.parametrize("method", ["linear", "cubic", "cheb"])
666+
def test_interp_assume_sorted_source_frequencies(method):
667+
"""Test that assume_sorted correctly handles sorted vs unsorted source frequencies.
668+
669+
This test verifies that:
670+
- assume_sorted=True works correctly when source frequencies are sorted
671+
- assume_sorted=False correctly handles unsorted source frequencies
672+
- Both approaches produce identical interpolation results
673+
674+
Parameters
675+
----------
676+
method : str
677+
Interpolation method to test: "linear", "cubic", or "cheb"
678+
"""
679+
from tidy3d.components.data.data_array import ModeIndexDataArray
680+
from tidy3d.components.data.dataset import FreqDataset
681+
682+
# Define number of source frequencies based on method requirements
683+
num_source_points = {"linear": 5, "cubic": 5, "cheb": 5}[method]
684+
685+
# Generate sorted source frequencies using ModeInterpSpec.sampling_points
686+
interp_spec = td.ModeInterpSpec(num_points=num_source_points, method=method)
687+
freqs_full_range = np.linspace(1e14, 2e14, 100) # Full frequency range
688+
freqs_source_sorted = interp_spec.sampling_points(freqs_full_range)
689+
690+
# Create unsorted version by shuffling
691+
rng = np.random.RandomState(42) # Fixed seed for reproducibility
692+
shuffle_indices = np.arange(len(freqs_source_sorted))
693+
rng.shuffle(shuffle_indices)
694+
freqs_source_unsorted = freqs_source_sorted[shuffle_indices]
695+
696+
# Create test data values
697+
mode_indices = np.arange(2)
698+
data_values = (1.5 + 0.1j) * np.random.random((num_source_points, 2))
699+
700+
# Create dataset with SORTED source frequencies
701+
data_sorted = ModeIndexDataArray(
702+
data_values.copy(), coords={"f": freqs_source_sorted, "mode_index": mode_indices}
703+
)
704+
705+
# Create dataset with UNSORTED source frequencies
706+
# Need to reorder data values to match the shuffled frequencies
707+
data_unsorted = ModeIndexDataArray(
708+
data_values[shuffle_indices],
709+
coords={"f": freqs_source_unsorted, "mode_index": mode_indices},
710+
)
711+
712+
# Define target frequencies for interpolation
713+
freqs_target = np.linspace(1e14, 2e14, 50)
714+
715+
# Interpolate with sorted source frequencies and assume_sorted=True
716+
result_sorted = FreqDataset._interp_dataarray_in_freq(
717+
data_sorted, freqs_target, method=method, assume_sorted=True
718+
)
719+
720+
# Interpolate with unsorted source frequencies and assume_sorted=False
721+
# This should internally sort the data before interpolating
722+
result_unsorted = FreqDataset._interp_dataarray_in_freq(
723+
data_unsorted, freqs_target, method=method, assume_sorted=False
724+
)
725+
726+
# Both approaches should produce identical results
727+
assert np.allclose(result_sorted.values, result_unsorted.values, rtol=1e-10), (
728+
f"{method} interpolation: sorted and unsorted source frequencies "
729+
"should produce identical results"
730+
)
731+
732+
# Verify the interpolation actually happened correctly
733+
assert result_sorted.shape == (50, 2)
734+
assert len(result_sorted.coords["f"]) == 50
735+
assert np.allclose(result_sorted.coords["f"].values, freqs_target)
736+
737+
660738
# ============================================================================
661739
# ModeSolver Integration Tests (Phase 5)
662740
# ============================================================================

tidy3d/components/data/dataset.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ def _interp_in_freq_update_dict(
5757
self,
5858
freqs: FreqArray,
5959
method: Literal["linear", "cubic", "cheb"] = "linear",
60+
assume_sorted: bool = False,
6061
) -> dict[str, DataArray]:
6162
"""Interpolate mode data to new frequency points.
6263
@@ -76,6 +77,8 @@ def _interp_in_freq_update_dict(
7677
frequencies), ``"cheb"`` for Chebyshev polynomial interpolation using barycentric
7778
formula (requires 3+ source frequencies at Chebyshev nodes).
7879
For complex-valued data, real and imaginary parts are interpolated independently.
80+
assume_sorted: bool = False,
81+
Whether to assume the frequency points are sorted.
7982
8083
Returns
8184
-------
@@ -112,7 +115,7 @@ def _interp_in_freq_update_dict(
112115

113116
modify_data = {}
114117
for key, data in self.data_arrs.items():
115-
modify_data[key] = self._interp_dataarray_in_freq(data, freqs, method)
118+
modify_data[key] = self._interp_dataarray_in_freq(data, freqs, method, assume_sorted)
116119

117120
return modify_data
118121

@@ -121,6 +124,7 @@ def _interp_dataarray_in_freq(
121124
data: DataArray,
122125
freqs: FreqArray,
123126
method: Literal["linear", "cubic", "cheb", "nearest"],
127+
assume_sorted: bool = False,
124128
) -> DataArray:
125129
"""Interpolate a DataArray along the frequency coordinate.
126130
@@ -133,6 +137,8 @@ def _interp_dataarray_in_freq(
133137
method : Literal["linear", "cubic", "cheb", "nearest"]
134138
Interpolation method (``"linear"``, ``"cubic"``, ``"cheb"``, or ``"nearest"``).
135139
For ``"cheb"``, uses barycentric formula for Chebyshev interpolation.
140+
assume_sorted: bool = False,
141+
Whether to assume the frequency points are sorted.
136142
137143
Returns
138144
-------
@@ -151,7 +157,7 @@ def _interp_dataarray_in_freq(
151157
else:
152158
if method != "cheb":
153159
interp_kwargs["kwargs"] = {"fill_value": "extrapolate"}
154-
return data.interp(f=freqs, **interp_kwargs)
160+
return data.interp(f=freqs, assume_sorted=assume_sorted, **interp_kwargs)
155161

156162

157163
class ModeFreqDataset(FreqDataset, ABC):

tidy3d/components/data/monitor_data.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2608,6 +2608,7 @@ def interp_in_freq(
26082608
method: Literal["linear", "cubic", "cheb"] = "linear",
26092609
renormalize: bool = True,
26102610
recalculate_grid_correction: bool = True,
2611+
assume_sorted: bool = False,
26112612
) -> ModeSolverData:
26122613
"""Interpolate mode data to new frequency points.
26132614
@@ -2632,6 +2633,8 @@ def interp_in_freq(
26322633
recalculate_grid_correction : bool = True
26332634
Whether to recalculate the grid correction factors after interpolation or use interpolated
26342635
grid corrections.
2636+
assume_sorted: bool = False,
2637+
Whether to assume the frequency points are sorted.
26352638
26362639
Returns
26372640
-------
@@ -2705,7 +2708,7 @@ def interp_in_freq(
27052708
)
27062709

27072710
# Build update dictionary
2708-
update_dict = self._interp_in_freq_update_dict(freqs, method)
2711+
update_dict = self._interp_in_freq_update_dict(freqs, method, assume_sorted)
27092712

27102713
# Handle eps_spec if present - use nearest neighbor interpolation
27112714
if self.eps_spec is not None:
@@ -2753,6 +2756,7 @@ def interpolated_copy(self) -> ModeSolverData:
27532756
method=self.monitor.mode_spec.interp_spec.method,
27542757
renormalize=True,
27552758
recalculate_grid_correction=True,
2759+
assume_sorted=True,
27562760
)
27572761
return interpolated_data
27582762

0 commit comments

Comments
 (0)