From 8d7b5bb5bd0a97461bcf9fe18b8ca32aec6c05a8 Mon Sep 17 00:00:00 2001 From: Qiming Date: Tue, 23 Jul 2024 10:31:20 -0400 Subject: [PATCH] Introduced axial_ratio to DirectivityMonitor --- CHANGELOG.md | 3 ++- docs/api/output_data.rst | 1 + tests/test_data/test_data_arrays.py | 10 ++++++++ tests/test_data/test_monitor_data.py | 7 +++++- tidy3d/__init__.py | 2 ++ tidy3d/components/data/data_array.py | 35 +++++++++++++++++++++++--- tidy3d/components/data/monitor_data.py | 14 ++++++++--- tidy3d/components/monitor.py | 17 ++++++++++++- 8 files changed, 79 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdedbdfad..31da58387 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Support for differentiation with respect to `ComplexPolySlab.vertices`. -- Introduce RF material library. Users can now export `rf_material_library` from `tidy3d.plugins.microwave`. +- Introduce RF material library. Users can now import `rf_material_library` from `tidy3d.plugins.microwave`. - Users can specify the background medium for a structure in automatic differentiation by supplying `Structure.autograd_background_permittivity`. - `DirectivityMonitor` to compute antenna directivity. - Added `plot_length_units` to `Simulation` and `Scene` to allow for specifying units, which improves axis labels and scaling when plotting. @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `WavePort` to the `TerminalComponentModeler` which is the suggested port type for exciting transmission lines. - Added plotting functionality to voltage and current path integrals in the `microwave` plugin. - Added convenience functions `from_terminal_positions` and `from_circular_path` to simplify setup of `VoltageIntegralAxisAligned` and `CustomCurrentIntegral2D`, respectively. +- Added `axial_ratio` to `DirectivityData.axial_ratio` for the `DirectivityMonitor`, defined as the ratio of the major axis to the minor axis of the polarization ellipse. ### Changed - Priority is given to `snapping_points` in `GridSpec` when close to structure boundaries, which reduces the chance of them being skipped. diff --git a/docs/api/output_data.rst b/docs/api/output_data.rst index c25e38306..dfe025682 100644 --- a/docs/api/output_data.rst +++ b/docs/api/output_data.rst @@ -53,3 +53,4 @@ Individual Datasets tidy3d.FieldProjectionKSpaceDataArray tidy3d.DiffractionDataArray tidy3d.DirectivityDataArray + tidy3d.AxialRatioDataArray diff --git a/tests/test_data/test_data_arrays.py b/tests/test_data/test_data_arrays.py index 214881c3f..152844b2b 100644 --- a/tests/test_data/test_data_arrays.py +++ b/tests/test_data/test_data_arrays.py @@ -193,6 +193,11 @@ def make_directivity_data_array(): return td.DirectivityDataArray(values, coords=dict(r=PD, theta=THETAS, phi=PHIS, f=FS)) +def make_axial_ratio_data_array(): + values = np.random.random((len(PD), len(THETAS), len(PHIS), len(FS))) + return td.AxialRatioDataArray(values, coords=dict(r=PD, theta=THETAS, phi=PHIS, f=FS)) + + def make_flux_time_data_array(): values = np.random.random(len(TS)) return td.FluxTimeDataArray(values, coords=dict(t=TS)) @@ -260,6 +265,11 @@ def test_directivity_data_array(): data = data.sel(f=1e14, phi=0) +def test_axial_ratio_data_array(): + data = make_axial_ratio_data_array() + data = data.sel(f=1e14, phi=0) + + def test_diffraction_data_array(): _, _, data = make_diffraction_data_array() data = data.interp(f=1.5e14) diff --git a/tests/test_data/test_monitor_data.py b/tests/test_data/test_monitor_data.py index 09b70bfed..3ceb8fbae 100644 --- a/tests/test_data/test_monitor_data.py +++ b/tests/test_data/test_monitor_data.py @@ -33,6 +33,7 @@ PERMITTIVITY_MONITOR, SIM, SIM_SYM, + make_axial_ratio_data_array, make_diffraction_data_array, make_directivity_data_array, make_flux_data_array, @@ -54,6 +55,7 @@ 1 + 0.01 * np.random.rand(*N_COMPLEX.shape), coords=N_COMPLEX.coords ) DIRECTIVITY = make_directivity_data_array() +AXIALRATIO = make_axial_ratio_data_array() """ Make the montor data """ @@ -188,7 +190,9 @@ def make_flux_data(): def make_directivity_data(): - return DirectivityData(monitor=DIRECTIVITY_MONITOR, directivity=DIRECTIVITY.copy()) + return DirectivityData( + monitor=DIRECTIVITY_MONITOR, directivity=DIRECTIVITY.copy(), axial_ratio=AXIALRATIO.copy() + ) def make_flux_time_data(): @@ -318,6 +322,7 @@ def test_flux_time_data(): def test_directivity_data(): data = make_directivity_data() _ = data.directivity + _ = data.axial_ratio def test_diffraction_data(): diff --git a/tidy3d/__init__.py b/tidy3d/__init__.py index 988c8b697..bce015872 100644 --- a/tidy3d/__init__.py +++ b/tidy3d/__init__.py @@ -37,6 +37,7 @@ # data from .components.data.data_array import ( + AxialRatioDataArray, CellDataArray, ChargeDataArray, DiffractionDataArray, @@ -387,6 +388,7 @@ def set_logging_level(level: str) -> None: "FieldProjectionKSpaceDataArray", "DiffractionDataArray", "DirectivityDataArray", + "AxialRatioDataArray", "HeatDataArray", "ChargeDataArray", "FieldDataset", diff --git a/tidy3d/components/data/data_array.py b/tidy3d/components/data/data_array.py index 8054c3405..4c4a2b595 100644 --- a/tidy3d/components/data/data_array.py +++ b/tidy3d/components/data/data_array.py @@ -779,9 +779,9 @@ class FieldProjectionAngleDataArray(DataArray): class DirectivityDataArray(DataArray): - """Directivity in the frequency domain as a function of angles theta and phi. Directivity is a - dimensionless quantity defined as the ratio of the radiation intensity in a given direction - to the average radiation intensity over all directions. + """Directivity in the frequency domain as a function of angles theta and phi. + Directivity is a dimensionless quantity defined as the ratio of the radiation + intensity in a given direction to the average radiation intensity over all directions. Example ------- @@ -799,6 +799,34 @@ class DirectivityDataArray(DataArray): _data_attrs = {"long_name": "radiation intensity"} +class AxialRatioDataArray(DataArray): + """Axial Ratio (AR) in the frequency domain as a function of angles theta and phi. + AR is a dimensionless quantity defined as the ratio of the major axis to the minor + axis of the polarization ellipse. + + Note + ---- + The axial ratio computation is based on: + + Balanis, Constantine A., "Antenna Theory: Analysis and Design," + John Wiley & Sons, Chapter 2.12 (2016). + + Example + ------- + >>> f = np.linspace(1e14, 2e14, 10) + >>> r = np.atleast_1d(5) + >>> theta = np.linspace(0, np.pi, 10) + >>> phi = np.linspace(0, 2*np.pi, 20) + >>> coords = dict(r=r, theta=theta, phi=phi, f=f) + >>> values = np.random.random((len(r), len(theta), len(phi), len(f))) + >>> data = AxialRatioDataArray(values, coords=coords) + """ + + __slots__ = () + _dims = ("r", "theta", "phi", "f") + _data_attrs = {"long_name": "axial ratio"} + + class FieldProjectionCartesianDataArray(DataArray): """Far fields in frequency domain as a function of local x and y coordinates. @@ -1097,6 +1125,7 @@ class IndexedDataArray(DataArray): FieldProjectionKSpaceDataArray, DiffractionDataArray, DirectivityDataArray, + AxialRatioDataArray, FreqModeDataArray, FreqDataArray, TimeDataArray, diff --git a/tidy3d/components/data/monitor_data.py b/tidy3d/components/data/monitor_data.py index d2868af5b..54b523aeb 100644 --- a/tidy3d/components/data/monitor_data.py +++ b/tidy3d/components/data/monitor_data.py @@ -58,6 +58,7 @@ ) from ..validators import enforce_monitor_fields_present, required_if_symmetry_present from .data_array import ( + AxialRatioDataArray, DataArray, DiffractionDataArray, DirectivityDataArray, @@ -1989,16 +1990,17 @@ class DirectivityData(MonitorData): Example ------- - >>> from tidy3d import DirectivityDataArray + >>> from tidy3d import DirectivityDataArray, AxialRatioDataArray >>> f = np.linspace(1e14, 2e14, 10) - >>> r = np.atleast_1d(5) + >>> r = np.atleast_1d(1e6) >>> theta = np.linspace(0, np.pi, 10) >>> phi = np.linspace(0, 2*np.pi, 20) >>> coords = dict(r=r, theta=theta, phi=phi, f=f) >>> values = np.random.random((len(r), len(theta), len(phi), len(f))) - >>> scalar_field = DirectivityDataArray(values, coords=coords) + >>> scalar_directivity_field = DirectivityDataArray(values, coords=coords) + >>> scalar_axial_ratio_field = AxialRatioDataArray(values, coords=coords) >>> monitor = DirectivityMonitor(center=(1,2,3), size=(2,2,2), freqs=f, name='n2f_monitor', phi=phi, theta=theta) - >>> data = DirectivityData(monitor=monitor,directivity=scalar_field) + >>> data = DirectivityData(monitor=monitor, directivity=scalar_directivity_field, axial_ratio=scalar_axial_ratio_field) """ monitor: DirectivityMonitor = pd.Field( @@ -2011,6 +2013,10 @@ class DirectivityData(MonitorData): ..., title="Directivity", description="Directivity with an angle-based projection grid." ) + axial_ratio: AxialRatioDataArray = pd.Field( + ..., title="Axial Ratio", description="Axial ratio with an angle-based projection grid." + ) + ProjFieldType = Union[ FieldProjectionAngleDataArray, diff --git a/tidy3d/components/monitor.py b/tidy3d/components/monitor.py index 3a5c30e0f..c1b42bf49 100644 --- a/tidy3d/components/monitor.py +++ b/tidy3d/components/monitor.py @@ -1088,8 +1088,23 @@ def storage_size(self, num_cells: int, tmesh: ArrayFloat1D) -> int: class DirectivityMonitor(FieldProjectionAngleMonitor, FluxMonitor): - """:class:`Monitor` that records the directivity of antennas in the frequency domain + """ + :class:`Monitor` that records the radiation characteristics of antennas in the frequency domain at specified observation angles. + + Note + ---- + For directivity, the computation is based on the ratio of the radiation + intensity in a given direction to the average radiation intensity + over all directions: + + Balanis, Constantine A., "Antenna Theory: Analysis and Design," + John Wiley & Sons, Chapter 2.6 (2016). + + For axial ratio, the computation is based on: + + Balanis, Constantine A., "Antenna Theory: Analysis and Design," + John Wiley & Sons, Chapter 2.12 (2016). """ def storage_size(self, num_cells: int, tmesh: ArrayFloat1D) -> int: