Skip to content

Commit 37291fd

Browse files
committed
fix: small updates to diffraction monitor data
1 parent 1d320b9 commit 37291fd

File tree

2 files changed

+45
-7
lines changed

2 files changed

+45
-7
lines changed

tidy3d/components/data/monitor_data.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import pydantic.v1 as pd
1414
import xarray as xr
1515
from pandas import DataFrame
16-
from xarray.core.types import Self
1716

1817
from tidy3d.components.base import cached_property, skip_if_fields_missing
1918
from tidy3d.components.base_sim.data.monitor_data import AbstractMonitorData
@@ -102,6 +101,8 @@
102101
AXIAL_RATIO_CAP = 100
103102
# At this sampling rate, the computed area of a sphere is within ~1% of the true value.
104103
MIN_ANGULAR_SAMPLES_SPHERE = 10
104+
# Threshold for cos(theta) to avoid unphysically large amplitudes near grazing angles
105+
COS_THETA_THRESH = 1e-5
105106

106107

107108
class MonitorData(AbstractMonitorData, ABC):
@@ -2776,8 +2777,10 @@ def radar_cross_section(self) -> DataArray:
27762777
if not np.all(index_k == 0):
27772778
raise SetupError("Can't compute RCS for a lossy background medium.")
27782779

2779-
k = self.k[None, None, None, ...]
2780-
eta = self.eta[None, None, None, ...]
2780+
n_leading = max(0, len(self.dims) - 1)
2781+
expand_idx = (None,) * n_leading + (Ellipsis,)
2782+
k = self.k[expand_idx]
2783+
eta = self.eta[expand_idx]
27812784

27822785
if self.is_2d_simulation:
27832786
constant = k**2 / (16 * np.pi * eta)
@@ -3307,6 +3310,17 @@ class DiffractionData(AbstractFieldProjectionData):
33073310
to x, P(S) corresponds to ``Ex``(``Ez``) polarization for monitor normal to y, and P(S)
33083311
corresponds to ``Ex``(``Ey``) polarization for monitor normal to z.
33093312
3313+
Note
3314+
----
3315+
3316+
The power amplitudes per polarization and diffraction order, and correspondingly the power
3317+
per diffraction order, correspond to the power carried by each diffraction order in the
3318+
monitor normal direction. They are not to be confused with power carried by plane waves
3319+
in the propagation direction of each diffraction order, which can be obtained from the
3320+
spherical-coordinate fields which are also stored. The power definition is such that the
3321+
grating efficiency is the recorded power over the input source power, and the direct sum
3322+
over the power in all orders should equal the total power flowing through the monitor.
3323+
33103324
33113325
Example
33123326
-------
@@ -3473,10 +3487,10 @@ def amps(self) -> DataArray:
34733487
"""Complex power amplitude in each order for 's' and 'p' polarizations, normalized so that
34743488
the power carried by the wave of that order and polarization equals ``abs(amps)^2``.
34753489
"""
3490+
# use a small threshold to avoid blow-up near grazing angles
34763491
cos_theta = np.cos(np.nan_to_num(self.angles[0]))
3477-
3478-
# will set amplitudes to 0 for glancing or negative angles
3479-
cos_theta[cos_theta <= 0] = np.inf
3492+
# set amplitudes to 0 for angles with cos(theta) <= COS_THETA_THRESH (glancing or negative)
3493+
cos_theta[cos_theta <= COS_THETA_THRESH] = np.inf
34803494

34813495
norm = 1.0 / np.sqrt(2.0 * self.eta) / np.sqrt(cos_theta)
34823496
amp_theta = self.Etheta.values * norm
@@ -3491,10 +3505,15 @@ def amps(self) -> DataArray:
34913505
return DataArray(np.stack([amp_phi, amp_theta], axis=3), coords=coords)
34923506

34933507
@property
3494-
def power(self) -> Self:
3508+
def power(self) -> DataArray:
34953509
"""Total power in each order, summed over both polarizations."""
34963510
return (np.abs(self.amps) ** 2).sum(dim="polarization")
34973511

3512+
@property
3513+
def radar_cross_section(self) -> DataArray:
3514+
"""Radar cross section in units of incident power."""
3515+
raise ValueError("RCS is not a well-defined quantity for diffraction data.")
3516+
34983517
@property
34993518
def fields_spherical(self) -> xr.Dataset:
35003519
"""Get all field components in spherical coordinates relative to the monitor's

tidy3d/components/monitor.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1535,6 +1535,25 @@ class DiffractionMonitor(PlanarMonitor, FreqMonitor):
15351535
""":class:`Monitor` that uses a 2D Fourier transform to compute the
15361536
diffraction amplitudes and efficiency for allowed diffraction orders.
15371537
1538+
Note
1539+
----
1540+
1541+
The diffraction data are separated into S and P polarizations. At normal incidence when
1542+
S and P are undefined, P(S) corresponds to ``Ey``(``Ez``) polarization for monitor normal
1543+
to x, P(S) corresponds to ``Ex``(``Ez``) polarization for monitor normal to y, and P(S)
1544+
corresponds to ``Ex``(``Ey``) polarization for monitor normal to z.
1545+
1546+
Note
1547+
----
1548+
1549+
The power amplitudes per polarization and diffraction order, and correspondingly the power
1550+
per diffraction order, correspond to the power carried by each diffraction order in the
1551+
monitor normal direction. They are not to be confused with power carried by plane waves
1552+
in the propagation direction of each diffraction order, which can be obtained from the
1553+
spherical-coordinate fields which are also stored. The power definition is such that the
1554+
grating efficiency is the recorded power over the input source power, and the direct sum
1555+
over the power in all orders should equal the total power flowing through the monitor.
1556+
15381557
Example
15391558
-------
15401559
>>> monitor = DiffractionMonitor(

0 commit comments

Comments
 (0)