Skip to content
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
34 changes: 28 additions & 6 deletions tests/test_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,20 +173,37 @@ def _test_monitor_size():
s.validate_contents()


@pytest.mark.parametrize("fwidth,log_level", [(0.001, None), (3, 30)])
def test_sim_frequency_range(caplog, fwidth, log_level):
# small fwidth should be inside range, large one should throw warning
@pytest.mark.parametrize("freq, log_level", [(1.5, 30), (2.5, None), (3.5, 30)])
def test_monitor_medium_frequency_range(caplog, freq, log_level):
# monitor frequency above or below a given medium's range should throw a warning

size = (1, 1, 1)
medium = Medium(frequency_range=(2, 3))
box = Structure(geometry=Box(size=(0.1, 0.1, 0.1)), medium=medium)
mnt = FieldMonitor(size=(0, 0, 0), name="freq", freqs=[freq])
src = VolumeSource(
source_time=GaussianPulse(freq0=2.4, fwidth=fwidth),
source_time=GaussianPulse(freq0=2.5, fwidth=0.5),
size=(0, 0, 0),
polarization="Ex",
)
_ = Simulation(size=(1, 1, 1), grid_size=(0.1, 0.1, 0.1), structures=[box], sources=[src])
sim = Simulation(
size=(1, 1, 1), grid_size=(0.1, 0.1, 0.1), structures=[box], monitors=[mnt], sources=[src]
)
assert_log_level(caplog, log_level)


@pytest.mark.parametrize("fwidth, log_level", [(0.1, 30), (2, None)])
def test_monitor_simulation_frequency_range(caplog, fwidth, log_level):
# monitor frequency outside of the simulation's frequency range should throw a warning

size = (1, 1, 1)
src = VolumeSource(
source_time=GaussianPulse(freq0=2.0, fwidth=fwidth),
size=(0, 0, 0),
polarization="Ex",
)
mnt = FieldMonitor(size=(0, 0, 0), name="freq", freqs=[1.5])
sim = Simulation(size=(1, 1, 1), grid_size=(0.1, 0.1, 0.1), monitors=[mnt], sources=[src])
assert_log_level(caplog, log_level)


Expand Down Expand Up @@ -273,8 +290,13 @@ def test_sim_plane_wave_error():
def test_sim_structure_extent(caplog, box_size, log_level):
"""Make sure we warn if structure extends exactly to simulation edges."""

src = VolumeSource(
source_time=GaussianPulse(freq0=3e14, fwidth=1e13),
size=(0, 0, 0),
polarization="Ex",
)
box = Structure(geometry=Box(size=box_size), medium=Medium(permittivity=2))
sim = Simulation(size=(1, 1, 1), grid_size=(0.1, 0.1, 0.1), structures=[box])
sim = Simulation(size=(1, 1, 1), grid_size=(0.1, 0.1, 0.1), structures=[box], sources=[src])

assert_log_level(caplog, log_level)

Expand Down
2 changes: 1 addition & 1 deletion tidy3d/components/grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ def _dual_steps(self) -> Coords:

primal_steps = self._primal_steps.dict(exclude={TYPE_TAG_STR})
dsteps = {}
for dim, (key, psteps) in enumerate(primal_steps.items()):
for (key, psteps) in primal_steps.items():
dsteps[key] = (psteps + np.roll(psteps, -1)) / 2

return Coords(**dsteps)
Expand Down
65 changes: 45 additions & 20 deletions tidy3d/components/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,8 @@
import numpy as np
import xarray as xr
import matplotlib.pylab as plt

try:
import matplotlib as mpl
from mpl_toolkits.axes_grid1 import make_axes_locatable
except Exception: # pylint: disable=broad-except
print("Could not import matplotlib!")
import matplotlib as mpl
from mpl_toolkits.axes_grid1 import make_axes_locatable
from descartes import PolygonPatch

from .validators import assert_unique_names, assert_objects_in_sim_bounds
Expand All @@ -33,7 +29,7 @@
# for docstring examples
from .geometry import Sphere, Cylinder, PolySlab # pylint:disable=unused-import
from .source import VolumeSource, GaussianPulse # pylint:disable=unused-import
from .monitor import FieldMonitor, FluxMonitor, Monitor # pylint:disable=unused-import
from .monitor import FieldMonitor, FluxMonitor, Monitor, FreqMonitor # pylint:disable=unused-import


# minimum number of grid points allowed per central wavelength in a medium
Expand Down Expand Up @@ -317,9 +313,9 @@ def warn(istruct, side):

return val

@pydantic.validator("sources", always=True)
def _warn_sources_mediums_frequency_range(cls, val, values):
"""Warn user if any sources have frequency range outside of medium frequency range."""
@pydantic.validator("monitors", always=True)
def _warn_monitor_mediums_frequency_range(cls, val, values):
"""Warn user if any DFT monitors have frequencies outside of medium frequency range."""

if val is None:
return val
Expand All @@ -329,25 +325,56 @@ def _warn_sources_mediums_frequency_range(cls, val, values):
medium_bg = values.get("medium")
mediums = [medium_bg] + [structure.medium for structure in structures]

for source in val:
fmin_src, fmax_src = source.source_time.frequency_range
for monitor in val:
if not isinstance(monitor, FreqMonitor):
continue

freqs = np.array(monitor.freqs)
for medium in mediums:

# skip mediums that have no freq range (all freqs valid)
if medium.frequency_range is None:
continue

# make sure medium frequency range includes the source frequency range
# make sure medium frequency range includes all monitor frequencies
fmin_med, fmax_med = medium.frequency_range
if fmin_med > fmin_src or fmax_med < fmax_src:
if np.any(freqs < fmin_med) or np.any(freqs > fmax_med):
log.warning(
f"A medium in the simulation:\n\n({medium})\n\nhas a frequency "
"range that does not fully cover the spectrum of a source:"
f"\n\n({source})\n\nThis can cause innacuracies in the "
"simulation results."
"range that does not fully cover the frequencies of a monitor:"
f"\n\n({monitor})\n\nThis can cause innacuracies in the "
"recorded results."
)
return val

@pydantic.validator("monitors", always=True)
def _warn_monitor_simulation_frequency_range(cls, val, values):
"""Warn if any DFT monitors have frequencies outside of the simulation frequency range."""

if val is None:
return val

# Get simulation frequency range
source_ranges = [source.source_time.frequency_range for source in values["sources"]]
if len(source_ranges) == 0:
log.warning("No sources in simulation.")
return val

freq_min = min([freq_range[0] for freq_range in source_ranges], default=0.0)
freq_max = max([freq_range[1] for freq_range in source_ranges], default=0.0)

for monitor in val:
if not isinstance(monitor, FreqMonitor):
continue

freqs = np.array(monitor.freqs)
if np.any(freqs < freq_min) or np.any(freqs > freq_max):
log.warning(
f"A monitor in the simulation:\n\n({monitor})\n\nhas frequencies "
"outside of the simulation frequency range as defined by the sources."
)
return val

@pydantic.validator("sources", always=True)
def _warn_grid_size_too_small(cls, val, values): # pylint:disable=too-many-locals
"""Warn user if any grid size is too large compared to minimum wavelength in material."""
Expand Down Expand Up @@ -801,7 +828,6 @@ def plot_symmetries(
new_kwargs = SymParams(sym_value=sym_value).update_params(**kwargs)
ax = sym_box.plot(ax=ax, x=x, y=y, z=z, **new_kwargs)
ax = self._set_plot_bounds(ax=ax, x=x, y=y, z=z)
ax = self._set_plot_bounds(ax=ax, x=x, y=y, z=z)
return ax

@property
Expand Down Expand Up @@ -1014,8 +1040,7 @@ def frequency_range(self) -> FreqBound:
Returns
-------
Tuple[float, float]
Minumum and maximum frequencies of the power spectrum of the sources
at 5 standard deviations.
Minumum and maximum frequencies of the power spectrum of the sources.
"""
source_ranges = [source.source_time.frequency_range for source in self.sources]
freq_min = min([freq_range[0] for freq_range in source_ranges], default=0.0)
Expand Down
4 changes: 2 additions & 2 deletions tidy3d/components/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,9 @@ def frequency_range(self) -> FreqBound:
Tuple[float, float]
Minimum and maximum frequencies of the
:class:`GaussianPulse` or :class:`ContinuousWave` power
within 6 standard deviations.
within 4 standard deviations.
"""
width_std = 6
width_std = 4
freq_width_range = width_std * self.fwidth
freq_min = max(0, self.freq0 - freq_width_range)
freq_max = self.freq0 + freq_width_range
Expand Down
7 changes: 2 additions & 5 deletions tidy3d/components/viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@
from abc import abstractmethod
from functools import wraps

try:
import matplotlib.pylab as plt
from matplotlib import colors
except Exception: # pylint: disable=broad-except
print("Could not import matplotlib!")
import matplotlib.pylab as plt
from matplotlib import colors
from pydantic import BaseModel

from .types import Ax
Expand Down