Skip to content

Commit

Permalink
apodization for frequency monitors
Browse files Browse the repository at this point in the history
  • Loading branch information
dbochkov-flexcompute committed Nov 17, 2022
1 parent 425b972 commit 79fe521
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 1 deletion.
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.",
)

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

0 comments on commit 79fe521

Please sign in to comment.