Skip to content

Commit e7ae68d

Browse files
committed
Warn if too many frequencies; too many modes; or too many grid points in mode source or monitor
1 parent 021b72e commit e7ae68d

File tree

7 files changed

+159
-18
lines changed

7 files changed

+159
-18
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
## [Unreleased]
88

99
### Added
10+
- Warnings for too many frequencies in monitors; too many modes requested in a ``ModeSpec``; too many number of grid points in a mode monitor or mode source.
1011

1112
### Changed
1213

tests/test_components/test_monitor.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ def test_monitor_colocate(log_capture):
208208

209209
monitor = td.FieldMonitor(
210210
size=(td.inf, td.inf, td.inf),
211-
freqs=np.linspace(0, 200e12, 10001),
211+
freqs=np.linspace(0, 200e12, 1001),
212212
name="test",
213213
interval_space=(1, 2, 3),
214214
)
@@ -217,14 +217,40 @@ def test_monitor_colocate(log_capture):
217217

218218
monitor = td.FieldMonitor(
219219
size=(td.inf, td.inf, td.inf),
220-
freqs=np.linspace(0, 200e12, 10001),
220+
freqs=np.linspace(0, 200e12, 1001),
221221
name="test",
222222
interval_space=(1, 2, 3),
223223
colocate=False,
224224
)
225225
assert monitor.colocate is False
226226

227227

228+
@pytest.mark.parametrize("freqs, log_level", [(np.arange(2500), "WARNING"), (np.arange(100), None)])
229+
def test_monitor_num_freqs(log_capture, freqs, log_level):
230+
"""test default colocate value, and warning if not set"""
231+
232+
monitor = td.FieldMonitor(
233+
size=(td.inf, td.inf, td.inf),
234+
freqs=freqs,
235+
name="test",
236+
colocate=True,
237+
)
238+
assert_log_level(log_capture, log_level)
239+
240+
241+
@pytest.mark.parametrize("num_modes, log_level", [(101, "WARNING"), (100, None)])
242+
def test_monitor_num_modes(log_capture, num_modes, log_level):
243+
"""test default colocate value, and warning if not set"""
244+
245+
monitor = td.ModeMonitor(
246+
size=(td.inf, 0, td.inf),
247+
freqs=np.linspace(1e14, 2e14, 100),
248+
name="test",
249+
mode_spec=td.ModeSpec(num_modes=num_modes),
250+
)
251+
assert_log_level(log_capture, log_level)
252+
253+
228254
def test_diffraction_validators():
229255

230256
# ensure error if boundaries are not periodic

tests/test_components/test_simulation.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1594,6 +1594,51 @@ def test_warn_large_epsilon(log_capture, size, num_struct, log_level):
15941594
assert_log_level(log_capture, log_level)
15951595

15961596

1597+
@pytest.mark.parametrize("dl, log_level", [(0.1, None), (0.005, "WARNING")])
1598+
def test_warn_large_mode_monitor(log_capture, dl, log_level):
1599+
"""Make sure we get a warning if the epsilon grid is too large."""
1600+
1601+
sim = td.Simulation(
1602+
size=(2.0, 2.0, 2.0),
1603+
grid_spec=td.GridSpec.uniform(dl=dl),
1604+
run_time=1e-12,
1605+
sources=[
1606+
td.ModeSource(
1607+
size=(0.1, 0.1, 0),
1608+
direction="+",
1609+
source_time=td.GaussianPulse(freq0=1, fwidth=0.1),
1610+
)
1611+
],
1612+
monitors=[
1613+
td.ModeMonitor(
1614+
size=(td.inf, 0, td.inf), freqs=[1], name="test", mode_spec=td.ModeSpec()
1615+
)
1616+
],
1617+
)
1618+
sim.validate_pre_upload()
1619+
assert_log_level(log_capture, log_level)
1620+
1621+
1622+
@pytest.mark.parametrize("dl, log_level", [(0.1, None), (0.005, "WARNING")])
1623+
def test_warn_large_mode_source(log_capture, dl, log_level):
1624+
"""Make sure we get a warning if the epsilon grid is too large."""
1625+
1626+
sim = td.Simulation(
1627+
size=(2.0, 2.0, 2.0),
1628+
grid_spec=td.GridSpec.uniform(dl=dl),
1629+
run_time=1e-12,
1630+
sources=[
1631+
td.ModeSource(
1632+
size=(td.inf, td.inf, 0),
1633+
direction="+",
1634+
source_time=td.GaussianPulse(freq0=1, fwidth=0.1),
1635+
)
1636+
],
1637+
)
1638+
sim.validate_pre_upload()
1639+
assert_log_level(log_capture, log_level)
1640+
1641+
15971642
def test_dt():
15981643
"""make sure dt is reduced when there is a medium with eps_inf < 1."""
15991644
sim = td.Simulation(

tests/test_package/test_log.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def test_logging_warning_capture():
7070
# 1 warning: too long run_time
7171
run_time = 10000 / fwidth
7272

73-
# 1 warning: frequency outside of source frequency range
73+
# 2 warnings: frequency outside of source frequency range; too many points
7474
mode_mnt = td.ModeMonitor(
7575
center=(0, 0, 0),
7676
size=(domain_size, 0, domain_size),
@@ -79,7 +79,7 @@ def test_logging_warning_capture():
7979
name="mode",
8080
)
8181

82-
# 1 warning: too high num_freqs
82+
# 2 warnings: too high num_freqs; too many points
8383
mode_source = td.ModeSource(
8484
size=(domain_size, 0, domain_size),
8585
source_time=source_time,
@@ -214,7 +214,7 @@ def test_logging_warning_capture():
214214
sim.validate_pre_upload()
215215
warning_list = td.log.captured_warnings()
216216
print(json.dumps(warning_list, indent=4))
217-
assert len(warning_list) == 30
217+
assert len(warning_list) == 32
218218
td.log.set_capture(False)
219219

220220
# check that capture doesn't change validation errors

tidy3d/components/monitor.py

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
BYTES_REAL = 4
2222
BYTES_COMPLEX = 8
23+
WARN_NUM_FREQS = 2000
24+
WARN_NUM_MODES = 100
2325

2426

2527
class Monitor(Box, ABC):
@@ -146,6 +148,19 @@ def _freqs_non_empty(cls, val):
146148
raise ValidationError("'freqs' must not be empty.")
147149
return val
148150

151+
@pydantic.validator("freqs", always=True)
152+
def _warn_num_freqs(cls, val, values):
153+
"""Warn if number of frequencies is too large."""
154+
if len(val) > WARN_NUM_FREQS:
155+
log.warning(
156+
f"A large number ({len(val)}) of frequencies detected in monitor "
157+
f"'{values['name']}'. This can lead to solver slow-down and increased cost. "
158+
"Consider decreasing the number of frequencies in the monitor. This may become a "
159+
"hard limit in future Tidy3D versions.",
160+
custom_loc=["freqs"],
161+
)
162+
return val
163+
149164
@cached_property
150165
def frequency_range(self) -> FreqBound:
151166
"""Frequency range of the array ``self.freqs``.
@@ -256,14 +271,13 @@ class AbstractFieldMonitor(Monitor, ABC):
256271
@pydantic.validator("colocate", always=True)
257272
def warn_set_colocate(cls, val):
258273
"""If ``colocate`` not provided, set to true, but warn that behavior has changed."""
259-
with log as consolidated_logger:
260-
if val is None:
261-
consolidated_logger.warning(
262-
"Default value for the field monitor 'colocate' setting has changed to "
263-
"'True' in Tidy3D 2.4.0. All field components will be colocated to the grid "
264-
"boundaries. Set to 'False' to get the raw fields on the Yee grid instead."
265-
)
266-
return True
274+
if val is None:
275+
log.warning(
276+
"Default value for the field monitor 'colocate' setting has changed to "
277+
"'True' in Tidy3D 2.4.0. All field components will be colocated to the grid "
278+
"boundaries. Set to 'False' to get the raw fields on the Yee grid instead."
279+
)
280+
return True
267281
return val
268282

269283

@@ -334,6 +348,20 @@ def _bend_axis(self) -> Axis:
334348
direction = self.unpop_axis(0, in_plane, axis=self.normal_axis)
335349
return direction.index(1)
336350

351+
@pydantic.validator("mode_spec", always=True)
352+
def _warn_num_modes(cls, val, values):
353+
"""Warn if number of modes is too large."""
354+
if val.num_modes > WARN_NUM_MODES:
355+
log.warning(
356+
f"A large number ({val.num_modes}) of modes requested in monitor "
357+
f"'{values['name']}'. This can lead to solver slow-down and increased cost. "
358+
"Consider decreasing the number of modes and using 'ModeSpec.target_neff' "
359+
"to target the modes of interest. This may become a hard limit in future "
360+
"Tidy3D versions.",
361+
custom_loc=["mode_spec", "num_modes"],
362+
)
363+
return val
364+
337365

338366
class FieldMonitor(AbstractFieldMonitor, FreqMonitor):
339367
""":class:`Monitor` that records electromagnetic fields in the frequency domain.

tidy3d/components/simulation.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@
3030
from .structure import Structure
3131
from .source import SourceType, PlaneWave, GaussianBeam, AstigmaticGaussianBeam, CustomFieldSource
3232
from .source import CustomCurrentSource, CustomSourceTime
33-
from .source import TFSF, Source
33+
from .source import TFSF, Source, ModeSource
3434
from .monitor import MonitorType, Monitor, FreqMonitor, SurfaceIntegrationMonitor
35+
from .monitor import AbstractModeMonitor, FieldMonitor
3536
from .monitor import PermittivityMonitor, DiffractionMonitor, AbstractFieldProjectionMonitor
3637
from .data.dataset import Dataset
3738
from .data.data_array import SpatialDataArray
@@ -64,6 +65,7 @@
6465
MAX_CELLS_TIMES_STEPS = 1e16
6566
WARN_MONITOR_DATA_SIZE_GB = 10
6667
MAX_SIMULATION_DATA_SIZE_GB = 50
68+
WARN_MODE_NUM_CELLS = 1e5
6769

6870
# number of grid cells at which we warn about slow Simulation.epsilon()
6971
NUM_CELLS_WARN_EPSILON = 100_000_000
@@ -996,6 +998,7 @@ def validate_pre_upload(self, source_required: bool = True) -> None:
996998
log.begin_capture()
997999
self._validate_size()
9981000
self._validate_monitor_size()
1001+
self._validate_modes_size()
9991002
self._validate_datasets_not_none()
10001003
self._validate_tfsf_structure_intersections()
10011004
# self._validate_run_time()
@@ -1058,6 +1061,43 @@ def _validate_monitor_size(self) -> None:
10581061
f"a maximum of {MAX_SIMULATION_DATA_SIZE_GB:.2f}GB are allowed."
10591062
)
10601063

1064+
def _validate_modes_size(self) -> None:
1065+
"""Warn if mode sources or monitors have a large number of points."""
1066+
1067+
def warn_mode_size(monitor: AbstractModeMonitor, msg_header: str, custom_loc: List):
1068+
"""Warn if a mode component has a large number of points."""
1069+
num_cells = np.prod(self.discretize_monitor(monitor).num_cells)
1070+
if num_cells > WARN_MODE_NUM_CELLS:
1071+
consolidated_logger.warning(
1072+
msg_header + f"has a large number ({num_cells:1.2e}) of grid points. "
1073+
"This can lead to solver slow-down and increased cost. "
1074+
"Consider making the size of the component smaller, as long as the modes "
1075+
"of interest decay by the plane boundaries.",
1076+
custom_loc=custom_loc,
1077+
)
1078+
1079+
with log as consolidated_logger:
1080+
for src_ind, source in enumerate(self.sources):
1081+
if isinstance(source, ModeSource):
1082+
# Make a monitor so we can call ``discretize_monitor``
1083+
monitor = FieldMonitor(
1084+
center=source.center,
1085+
size=source.size,
1086+
name="tmp",
1087+
freqs=[source.source_time.freq0],
1088+
colocate=False,
1089+
)
1090+
msg_header = f"Mode source at sources[{src_ind}] "
1091+
custom_loc = ["sources", src_ind]
1092+
warn_mode_size(monitor=monitor, msg_header=msg_header, custom_loc=custom_loc)
1093+
1094+
with log as consolidated_logger:
1095+
for mnt_ind, monitor in enumerate(self.monitors):
1096+
if isinstance(monitor, AbstractModeMonitor):
1097+
msg_header = f"Mode monitor '{monitor.name}' "
1098+
custom_loc = ["monitors", mnt_ind]
1099+
warn_mode_size(monitor=monitor, msg_header=msg_header, custom_loc=custom_loc)
1100+
10611101
@cached_property
10621102
def monitors_data_size(self) -> Dict[str, float]:
10631103
"""Dictionary mapping monitor names to their estimated storage size in bytes."""

tidy3d/components/source.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
DATA_SPAN_TOL = 1e-8
3434
# width of Chebyshev grid used for broadband sources (in units of pulse width)
3535
CHEB_GRID_WIDTH = 1.5
36+
# Number of frequencies in a broadband source above which to issue a warning
37+
WARN_NUM_FREQS = 20
3638

3739

3840
class SourceTime(ABC, Tidy3dBaseModel):
@@ -743,12 +745,11 @@ def _warn_if_large_number_of_freqs(cls, val):
743745
if val is None:
744746
return val
745747

746-
if val >= 20:
748+
if val >= WARN_NUM_FREQS:
747749
log.warning(
748750
f"A large number ({val}) of frequency points is used in a broadband source. "
749-
"This can slow down simulation time and is only needed if the mode fields are "
750-
"expected to have a very sharp frequency dependence. 'num_freqs' < 20 is "
751-
"sufficient in most cases.",
751+
"This can lead to solver slow-down and increased cost, and even introduce "
752+
"numerical noise. This may become a hard limit in future Tidy3D versions.",
752753
custom_loc=["num_freqs"],
753754
)
754755

0 commit comments

Comments
 (0)