Skip to content

Commit 4abf725

Browse files
committed
Fixed overlap_sort to use the same value of the conjugated_dot_product field
Added the conjugated_dot_product field to ModeSimulation and ModeSolver
1 parent 3fd39ff commit 4abf725

File tree

7 files changed

+61
-14
lines changed

7 files changed

+61
-14
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4242
- Fixed `ElectromagneticFieldData.to_zbf()` to support single frequency monitors and apply the correct flattening order.
4343
- Bug in `TerminalComponentModeler.get_antenna_metrics_data` when port amplitudes are set to zero.
4444
- Added missing `solver_version` keyword argument to `run_async`.
45-
- Fixed `interpn` data array method to be compatible with extrapolation outside of data array coordinates.
45+
- Fixed `interpn` data array method to be compatible with extrapolation outside of data array coordinates.
46+
- Fixed `overlap_sort` to use the same value of the `conjugated_dot_product` field in `ModeMonitor`, and added the `conjugated_dot_product` field to `ModeSolver` and `ModeSimulation`.
4647

4748
## [2.9.0] - 2025-08-04
4849

schemas/ModeSimulation.json

Lines changed: 7 additions & 1 deletion
Large diffs are not rendered by default.

schemas/TerminalComponentModeler.json

Lines changed: 14 additions & 2 deletions
Large diffs are not rendered by default.

tests/test_data/test_monitor_data.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,11 @@ def make_mode_solver_data():
162162
return mode_data_norm
163163

164164

165-
def make_mode_solver_data_smooth():
165+
def make_mode_solver_data_smooth(conjugated_dot_product: bool = True):
166166
mode_data = ModeData(
167-
monitor=MODE_MONITOR_WITH_FIELDS,
167+
monitor=MODE_MONITOR_WITH_FIELDS.updated_copy(
168+
conjugated_dot_product=conjugated_dot_product
169+
),
168170
Ex=make_scalar_mode_field_data_array_smooth("Ex", rot=0.13 * np.pi),
169171
Ey=make_scalar_mode_field_data_array_smooth("Ey", rot=0.26 * np.pi),
170172
Ez=make_scalar_mode_field_data_array_smooth("Ez", rot=0.39 * np.pi),
@@ -668,7 +670,8 @@ def test_diffraction_data_use_medium():
668670
assert np.allclose(data.eta, np.real(td.ETA_0 / 2.0))
669671

670672

671-
def test_mode_solver_data_sort():
673+
@pytest.mark.parametrize("conjugated_dot_product", [True, False])
674+
def test_mode_solver_data_sort(conjugated_dot_product):
672675
# test basic matching algorithm
673676
arr = np.array([[1, 2, 3], [6, 5, 4], [7, 9, 8]])
674677
pairs, values = ModeData._find_closest_pairs(arr)
@@ -677,7 +680,7 @@ def test_mode_solver_data_sort():
677680

678681
# test sorting function
679682
# get smooth data
680-
data = make_mode_solver_data_smooth()
683+
data = make_mode_solver_data_smooth(conjugated_dot_product=conjugated_dot_product)
681684
# make it unsorted
682685
num_modes = len(data.Ex.coords["mode_index"])
683686
num_freqs = len(data.Ex.coords["f"])

tidy3d/components/data/monitor_data.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1639,7 +1639,7 @@ def overlap_sort(
16391639
frequency according to their overlap values with the modes at the previous frequency.
16401640
That is, it attempts to rearrange modes in such a way that a given ``mode_index``
16411641
corresponds to physically the same mode at all frequencies. Modes with overlap values over
1642-
``overlap_tresh`` are considered matching and not rearranged.
1642+
``overlap_thresh`` are considered matching and not rearranged.
16431643
16441644
Parameters
16451645
----------
@@ -1664,12 +1664,20 @@ def overlap_sort(
16641664
elif track_freq == "central":
16651665
f0_ind = num_freqs // 2
16661666

1667+
# Normalizing the flux to 1, does not guarantee self terms of overlap integrals
1668+
# are also normalized to 1 when the non-conjugated product is used.
1669+
if self.monitor.conjugated_dot_product:
1670+
self_overlap = np.ones((num_freqs, num_modes))
1671+
else:
1672+
self_overlap = np.abs(self.dot(self, self.monitor.conjugated_dot_product).values)
1673+
threshold_array = overlap_thresh * self_overlap
1674+
16671675
# Compute sorting order and overlaps with neighboring frequencies
16681676
sorting = -np.ones((num_freqs, num_modes), dtype=int)
16691677
overlap = np.zeros((num_freqs, num_modes))
16701678
phase = np.zeros((num_freqs, num_modes))
16711679
sorting[f0_ind, :] = np.arange(num_modes) # base frequency won't change
1672-
overlap[f0_ind, :] = np.ones(num_modes)
1680+
overlap[f0_ind, :] = self_overlap[f0_ind, :]
16731681

16741682
# Sort in two directions from the base frequency
16751683
for step, last_ind in zip([-1, 1], [-1, num_freqs]):
@@ -1678,6 +1686,9 @@ def overlap_sort(
16781686

16791687
# March to lower/higher frequencies
16801688
for freq_id in range(f0_ind + step, last_ind, step):
1689+
# Calculate threshold array for this frequency
1690+
if not self.monitor.conjugated_dot_product:
1691+
overlap_thresh = threshold_array[freq_id, :]
16811692
# Get next frequency to sort
16821693
data_to_sort = self._isel(f=[freq_id])
16831694
# Assign to the base frequency so that outer_dot will compare them
@@ -1746,15 +1757,14 @@ def _assign_coords(self, **assign_coords_kwargs):
17461757
def _find_ordering_one_freq(
17471758
self,
17481759
data_to_sort: ModeData,
1749-
overlap_thresh: float,
1760+
overlap_thresh: Union[float, np.array],
17501761
) -> tuple[Numpy, Numpy]:
17511762
"""Find new ordering of modes in data_to_sort based on their similarity to own modes."""
1752-
17531763
num_modes = self.n_complex.sizes["mode_index"]
17541764

17551765
# Current pairs and their overlaps
17561766
pairs = np.arange(num_modes)
1757-
complex_amps = self.dot(data_to_sort).data.ravel()
1767+
complex_amps = self.dot(data_to_sort, self.monitor.conjugated_dot_product).data.ravel()
17581768
if self.monitor.store_fields_direction == "-":
17591769
complex_amps *= -1
17601770

@@ -1768,7 +1778,7 @@ def _find_ordering_one_freq(
17681778
data_template_reduced = self._isel(mode_index=modes_to_sort)
17691779

17701780
amps_reduced = data_template_reduced.outer_dot(
1771-
data_to_sort._isel(mode_index=modes_to_sort)
1781+
data_to_sort._isel(mode_index=modes_to_sort), self.monitor.conjugated_dot_product
17721782
).to_numpy()[0, :, :]
17731783

17741784
if self.monitor.store_fields_direction == "-":

tidy3d/components/mode/mode_solver.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,12 @@ class ModeSolver(Tidy3dBaseModel):
170170
"primal grid nodes). Default is ``True``.",
171171
)
172172

173+
conjugated_dot_product: bool = pydantic.Field(
174+
True,
175+
title="Conjugated Dot Product",
176+
description="Use conjugated or non-conjugated dot product for mode decomposition.",
177+
)
178+
173179
fields: tuple[EMField, ...] = pydantic.Field(
174180
["Ex", "Ey", "Ez", "Hx", "Hy", "Hz"],
175181
title="Field Components",
@@ -1949,6 +1955,7 @@ def to_monitor(
19491955
size=self.plane.size,
19501956
freqs=freqs,
19511957
mode_spec=self.mode_spec,
1958+
conjugated_dot_product=self.conjugated_dot_product,
19521959
name=name,
19531960
)
19541961

@@ -1981,6 +1988,7 @@ def to_mode_solver_monitor(
19811988
freqs=self.freqs,
19821989
direction=self.direction,
19831990
colocate=colocate,
1991+
conjugated_dot_product=self.conjugated_dot_product,
19841992
name=name,
19851993
)
19861994

tidy3d/components/mode/simulation.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"freqs",
4444
"direction",
4545
"colocate",
46+
"conjugated_dot_product",
4647
"fields",
4748
]
4849
# attributes shared between ModeSimulation class and AbstractYeeGridSimulation
@@ -136,6 +137,12 @@ class ModeSimulation(AbstractYeeGridSimulation):
136137
"primal grid nodes). Default is ``True``.",
137138
)
138139

140+
conjugated_dot_product: bool = pd.Field(
141+
True,
142+
title="Conjugated Dot Product",
143+
description="Use conjugated or non-conjugated dot product for mode decomposition.",
144+
)
145+
139146
fields: tuple[EMField, ...] = pd.Field(
140147
["Ex", "Ey", "Ez", "Hx", "Hy", "Hz"],
141148
title="Field Components",

0 commit comments

Comments
 (0)