Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apodization for frequency monitors #574

Merged
merged 1 commit into from
Nov 17, 2022
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
2 changes: 1 addition & 1 deletion tests/sims/simulation_1_8_0.json

Large diffs are not rendered by default.

51 changes: 51 additions & 0 deletions tests/test_components/test_apodization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""Tests mode objects."""
import pytest
import pydantic
import tidy3d as td
from tidy3d.log import SetupError
import matplotlib.pylab as plt


def test_apodization():

a = td.ApodizationSpec(width=0.2)
a = td.ApodizationSpec(start=1, width=0.2)
a = td.ApodizationSpec(end=2, width=0.2)
a = td.ApodizationSpec(start=1, end=2, width=0.2)


def test_end_lt_start():
with pytest.raises(SetupError):
_ = td.ApodizationSpec(start=2, end=1, width=0.2)


def test_no_width():
with pytest.raises(SetupError):
_ = td.ApodizationSpec(start=1, end=2)
with pytest.raises(SetupError):
_ = td.ApodizationSpec(start=1)
with pytest.raises(SetupError):
_ = td.ApodizationSpec(end=2)


def test_negative_times():
with pytest.raises(pydantic.ValidationError):
_ = td.ApodizationSpec(start=-2, end=-1, width=0.2)

with pytest.raises(pydantic.ValidationError):
_ = td.ApodizationSpec(start=1, end=2, width=-0.2)

with pytest.raises(pydantic.ValidationError):
_ = td.ApodizationSpec(start=1, end=2, width=0)


def test_plot():

run_time = 1.0e-13
times = [0, 2.0e-14, 4.0e-14, 6.0e-14, 8.0e-14, 1.0e-13]

a = td.ApodizationSpec(start=0.2 * run_time, end=0.8 * run_time, width=0.02 * run_time)
a.plot(times)

fig, ax = plt.subplots(1, 1)
a.plot(times, ax=ax)
3 changes: 3 additions & 0 deletions tidy3d/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
# modes
from .components.mode import ModeSpec

# apodization
from .components.apodization import ApodizationSpec

# sources
from .components.source import GaussianPulse, ContinuousWave
from .components.source import UniformCurrentSource, PlaneWave, ModeSource, PointDipole
Expand Down
94 changes: 94 additions & 0 deletions tidy3d/components/apodization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"""Defines specification for apodization."""

import pydantic as pd
import numpy as np

from .base import Tidy3dBaseModel
from ..constants import SECOND
from ..log import SetupError
from .types import ArrayLike, Ax
from .viz import add_ax_if_none


class ApodizationSpec(Tidy3dBaseModel):
"""Stores specifications for the apodizaton of frequency-domain monitors.

Example
-------
>>> apod_spec = ApodizationSpec(start=1, end=2, width=0.5)
"""

start: pd.NonNegativeFloat = pd.Field(
None,
title="Start Interval",
description="Defines the time at which the start apodization ends.",
units=SECOND,
)

end: pd.NonNegativeFloat = pd.Field(
None,
title="End Interval",
description="Defines the time at which the end apodization begins.",
units=SECOND,
)

width: pd.PositiveFloat = pd.Field(
None,
title="Apodization Width",
description="Characteristic decay length of the apodization function.",
units=SECOND,
)

@pd.validator("end", always=True, allow_reuse=True)
def end_greater_than_start(cls, val, values):
"""Ensure end is greater than or equal to start."""
start = values.get("start")
if val is not None and start is not None and val < start:
raise SetupError("End apodization begins before start apodization ends.")
return val

@pd.validator("width", always=True, allow_reuse=True)
def width_provided(cls, val, values):
"""Check that width is provided if either start or end apodization is requested."""
start = values.get("start")
end = values.get("end")
if (start is not None or end is not None) and val is None:
raise SetupError("Apodization width must be set.")
return val

@add_ax_if_none
def plot(self, times: ArrayLike[float, 1], ax: Ax = None) -> Ax:
"""Plot the apodization function.

Parameters
----------
times : np.ndarray
Array of times (seconds) to plot source at.
To see source time amplitude for a specific :class:`Simulation`,
pass ``simulation.tmesh``.
ax : matplotlib.axes._subplots.Axes = None
Matplotlib axes to plot on, if not specified, one is created.

Returns
-------
matplotlib.axes._subplots.Axes
The supplied or created matplotlib axes.
"""
times = np.array(times)
amp = np.ones_like(times)

if self.start is not None:
start_ind = times < self.start
time_scaled = (times[start_ind] - self.start) / self.width
amp[start_ind] *= np.exp(-0.5 * time_scaled**2)

if self.end is not None:
end_ind = times > self.end
time_scaled = (times[end_ind] - self.end) / self.width
amp[end_ind] *= np.exp(-0.5 * time_scaled**2)

ax.plot(times, amp, color="blueviolet")
ax.set_xlabel("time (s)")
ax.set_title("apodization function")
ax.set_aspect("auto")
return ax
11 changes: 11 additions & 0 deletions tidy3d/components/monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from .validators import assert_plane, validate_unique
from .base import cached_property
from .mode import ModeSpec
from .apodization import ApodizationSpec
from .viz import PlotParams, plot_params_monitor, ARROW_COLOR_MONITOR, ARROW_ALPHA
from ..constants import HERTZ, SECOND, MICROMETER, RADIAN, inf
from ..log import SetupError, log, ValidationError
Expand Down Expand Up @@ -74,6 +75,16 @@ class FreqMonitor(Monitor, ABC):
units=HERTZ,
)

apodization: ApodizationSpec = pydantic.Field(
ApodizationSpec(width=1),
title="Apodization Specification",
description="Sets parameters of (optional) apodization. Apodization applies a windowing "
"function to the Fourier transform of the time-domain fields into frequency-domain ones, "
"and can be used to truncate the beginning and/or end of the time signal, for example "
"to eliminate the source pulse when studying the eigenmodes of a system. Note: apodization "
"affects the normalization of the frequency-domain fields.",
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Sets parameters of (optional) apodization. Apodization applies a windowing function to the Fourier transform of the time-domain fields into frequency-domain ones, and can be used to truncate the beginning and/or end of the time signal, for example to eliminate the source pulse when studying the eigenmodes of a system. Note: apodization affects the normalization of the frequency-domain fields.",

or something along those lines


@pydantic.validator("freqs", always=True)
def _freqs_non_empty(cls, val):
"""Assert one frequency present."""
Expand Down