Skip to content

Commit a161adc

Browse files
Gregory Robertsyaugenst-flex
authored andcommitted
prevent very small bandwidth sources from being used in TerminalComponentModeler
1 parent c643e74 commit a161adc

File tree

4 files changed

+85
-2
lines changed

4 files changed

+85
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3434
- Adaptive minimum spacing for `PolySlab` integration is now wavelength relative and a minimum discretization is set for computing gradients for cylinders.
3535
- The `TerminalComponentModeler` defaults to the pseudo wave definition of scattering parameters. The new field `s_param_def` can be used to switch between either pseudo or power wave definitions.
3636
- Restructured the smatrix plugin with backwards-incompatible changes for a more robust architecture. Notably, `ComponentModeler` has been renamed to `ModalComponentModeler` and internal web API methods have been removed. Please see our migration guide for details on updating your workflows.
37+
- Prevent small bandwidth sources from being created in `TerminalComponentModeler` when modeler frequencies are close together.
3738

3839
### Fixed
3940
- Fixed missing amplitude factor and handling of negative normal direction case when making adjoint sources from `DiffractionMonitor`.

tests/test_components/test_source.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
ST = td.GaussianPulse(freq0=2e14, fwidth=1e14)
1717
S = td.PointDipole(source_time=ST, polarization="Ex")
18+
FWIDTH_FRAC = 0.1
1819

1920

2021
ATOL = 1e-8
@@ -106,6 +107,18 @@ def test_gaussian_from_frequency_range():
106107
# error with fmin >= fmax
107108
with pytest.raises(ValueError):
108109
_ = td.GaussianPulse.from_frequency_range(fmin=1e10, fmax=0.9e10)
110+
# error with minimum_source_bandwidth negative or 0
111+
with pytest.raises(ValueError):
112+
_ = td.GaussianPulse.from_frequency_range(
113+
fmin=9e10, fmax=11e10, minimum_source_bandwidth=0.0
114+
)
115+
_ = td.GaussianPulse.from_frequency_range(
116+
fmin=9e10, fmax=11e10, minimum_source_bandwidth=-1.0
117+
)
118+
# error with minimum_source_bandwidth greater than 1
119+
_ = td.GaussianPulse.from_frequency_range(
120+
fmin=9e10, fmax=11e10, minimum_source_bandwidth=1.0
121+
)
109122

110123
fmin = 1e9
111124
fmax = 20e9
@@ -137,6 +150,41 @@ def test_gaussian_from_frequency_range():
137150
assert abs(g.freq0 - fmin) / fmin < 1e-4
138151

139152

153+
def test_frequency_source_width():
154+
"""Ensure the source bandwidth has a lower bound regardless of the input frequencies."""
155+
156+
middle_freq = 1e10
157+
fmin_large_bw = middle_freq * (1 - 2 * FWIDTH_FRAC)
158+
fmax_large_bw = middle_freq * (1 + 2 * FWIDTH_FRAC)
159+
fmin, fmax = td.GaussianPulse._minimum_source_bandwidth(
160+
fmin=fmin_large_bw, fmax=fmax_large_bw, minimum_source_bandwidth=FWIDTH_FRAC
161+
)
162+
163+
assert np.isclose(fmin, fmin_large_bw), (
164+
"Expected no change in bandwidth (fmin unexpectedly changed)."
165+
)
166+
167+
assert np.isclose(fmax, fmax_large_bw), (
168+
"Expected no change in bandwidth (fmax unexpectedly changed)."
169+
)
170+
171+
fmin_small_bw = middle_freq * (1 - 0.1 * FWIDTH_FRAC)
172+
fmax_small_bw = middle_freq * (1 + 0.1 * FWIDTH_FRAC)
173+
fmin, fmax = td.GaussianPulse._minimum_source_bandwidth(
174+
fmin=fmin_small_bw, fmax=fmax_small_bw, minimum_source_bandwidth=FWIDTH_FRAC
175+
)
176+
fmin_expected = middle_freq * (1 - 0.5 * FWIDTH_FRAC)
177+
fmax_expected = middle_freq * (1 + 0.5 * FWIDTH_FRAC)
178+
179+
assert np.isclose(fmin, fmin_expected), (
180+
"Expected increase in bandwidth (fmin unexpected value)."
181+
)
182+
183+
assert np.isclose(fmax, fmax_expected), (
184+
"Expected increase in bandwidth (fmax unexpected value)."
185+
)
186+
187+
140188
def test_dipole():
141189
g = td.GaussianPulse(freq0=1e12, fwidth=0.1e12)
142190
_ = td.PointDipole(center=(1, 2, 3), source_time=g, polarization="Ex", interpolate=True)

tidy3d/components/source/time.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,34 @@ def from_amp_complex(cls, amp: complex, **kwargs) -> GaussianPulse:
185185
phase = np.angle(amp)
186186
return cls(amplitude=amplitude, phase=phase, **kwargs)
187187

188+
@staticmethod
189+
def _minimum_source_bandwidth(
190+
fmin: float, fmax: float, minimum_source_bandwidth: float
191+
) -> tuple[float, float]:
192+
"""Define a source bandwidth based on fmin and fmax, but enforce a minimum bandwidth."""
193+
if minimum_source_bandwidth <= 0:
194+
raise ValidationError("'minimum_source_bandwidth' must be positive")
195+
if minimum_source_bandwidth >= 1:
196+
raise ValidationError("'minimum_source_bandwidth' must less than or equal to 1")
197+
198+
f_difference = fmax - fmin
199+
f_middle = 0.5 * (fmin + fmax)
200+
201+
full_width = minimum_source_bandwidth * f_middle
202+
if f_difference < full_width:
203+
half_width = 0.5 * full_width
204+
fmin = f_middle - half_width
205+
fmax = f_middle + half_width
206+
207+
return fmin, fmax
208+
188209
@classmethod
189210
def from_frequency_range(
190-
cls, fmin: pydantic.PositiveFloat, fmax: pydantic.PositiveFloat, **kwargs
211+
cls,
212+
fmin: pydantic.PositiveFloat,
213+
fmax: pydantic.PositiveFloat,
214+
minimum_source_bandwidth: pydantic.PositiveFloat = None,
215+
**kwargs,
191216
) -> GaussianPulse:
192217
"""Create a ``GaussianPulse`` that maximizes its amplitude in the frequency range [fmin, fmax].
193218
@@ -211,6 +236,9 @@ def from_frequency_range(
211236
if fmax <= fmin:
212237
raise ValidationError("'fmax' must be greater than 'fmin'.")
213238

239+
if minimum_source_bandwidth is not None:
240+
fmin, fmax = cls._minimum_source_bandwidth(fmin, fmax, minimum_source_bandwidth)
241+
214242
# frequency range and center
215243
freq_range = fmax - fmin
216244
freq_center = (fmax + fmin) / 2.0

tidy3d/plugins/smatrix/component_modelers/terminal.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,8 +342,14 @@ def _source_time(self):
342342
if len(self.freqs) == 1:
343343
freq0 = self.freqs[0]
344344
return GaussianPulse(freq0=self.freqs[0], fwidth=freq0 * FWIDTH_FRAC)
345+
346+
# Using the minimum_source_bandwidth, ensure we don't create a pulse that is too narrowband
347+
# when fmin and fmax are close together
345348
return GaussianPulse.from_frequency_range(
346-
fmin=min(self.freqs), fmax=max(self.freqs), remove_dc_component=self.remove_dc_component
349+
fmin=np.min(self.freqs),
350+
fmax=np.max(self.freqs),
351+
remove_dc_component=self.remove_dc_component,
352+
minimum_source_bandwidth=FWIDTH_FRAC,
347353
)
348354

349355
@pd.validator("simulation")

0 commit comments

Comments
 (0)