Skip to content

Commit edd19b1

Browse files
New GaussianPulse.from_frequency_range for maximizing amplitude in frequency range
1 parent af5c124 commit edd19b1

File tree

4 files changed

+87
-6
lines changed

4 files changed

+87
-6
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- Periodic repetition of EME subgrids via `num_reps` or `EMEPeriodicitySweep`.
1313
- Methods `EMEExplicitGrid.from_structures` and `EMECompositeGrid.from_structure_groups` to place EME cell boundaries at structure bounds.
1414
- 'ModeSimulation' now supports 'PermittivityMonitor'.
15+
- Classmethod `from_frequency_range` in `GaussianPulse` for generating a pulse whose amplitude in the frequency_range [fmin, fmax] is maximized, which is particularly useful for running broadband simulations.
1516

1617
### Changed
1718
- Performance enhancement for adjoint gradient calculations by optimizing field interpolation.

tests/test_components/test_source.py

+41
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,47 @@ def test_source_times():
9393
assert abs(dc_comp) ** 2 > 1e-32
9494

9595

96+
def test_gaussian_from_frequency_range():
97+
# error with negative fmin
98+
with pytest.raises(ValueError):
99+
_ = td.GaussianPulse.from_frequency_range(fmin=-1e10, fmax=1e10)
100+
# error with fmin = 0
101+
with pytest.raises(ValueError):
102+
_ = td.GaussianPulse.from_frequency_range(fmin=0, fmax=1e10)
103+
# error with fmin >= fmax
104+
with pytest.raises(ValueError):
105+
_ = td.GaussianPulse.from_frequency_range(fmin=1e10, fmax=0.9e10)
106+
107+
fmin = 1e9
108+
fmax = 20e9
109+
# dc component on
110+
g = td.GaussianPulse.from_frequency_range(fmin=fmin, fmax=fmax, remove_dc_component=False)
111+
assert g.freq0 == 0.5 * (fmin + fmax)
112+
assert g.fwidth == 0.5 * (fmax - fmin)
113+
114+
# dc component removed
115+
g1 = td.GaussianPulse.from_frequency_range(fmin=fmin, fmax=fmax, remove_dc_component=True)
116+
# default to dc removed
117+
g2 = td.GaussianPulse.from_frequency_range(fmin=fmin, fmax=fmax)
118+
assert g2.remove_dc_component
119+
120+
# 1) broadband: assert enough amplitude at fmin and fmax
121+
time = np.linspace(0, 5 / fmin, 10001)
122+
freqs = np.linspace(fmin, fmax, 101)
123+
spectrum = np.abs(g2.spectrum(time, freqs, time[1] - time[0]))
124+
max_amp = np.max(spectrum)
125+
assert spectrum[0] / max_amp > 0.1
126+
assert spectrum[-1] / max_amp > 0.1
127+
128+
# 2) narrow band: close to regular Gaussian result
129+
fmin = 10e9
130+
bandwidth = 1e6
131+
fmax = fmin + 2 * bandwidth
132+
g = td.GaussianPulse.from_frequency_range(fmin=fmin, fmax=fmax)
133+
assert abs(g.fwidth - bandwidth) / bandwidth < 1e-4
134+
assert abs(g.freq0 - fmin) / fmin < 1e-4
135+
136+
96137
def test_dipole():
97138
g = td.GaussianPulse(freq0=1e12, fwidth=0.1e12)
98139
_ = td.PointDipole(center=(1, 2, 3), source_time=g, polarization="Ex", interpolate=True)

tidy3d/components/source/time.py

+42
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,48 @@ def from_amp_complex(cls, amp: complex, **kwargs) -> GaussianPulse:
191191
phase = np.angle(amp)
192192
return cls(amplitude=amplitude, phase=phase, **kwargs)
193193

194+
@classmethod
195+
def from_frequency_range(
196+
cls, fmin: pydantic.PositiveFloat, fmax: pydantic.PositiveFloat, **kwargs
197+
) -> GaussianPulse:
198+
"""Create a ``GaussianPulse`` that maximizes its amplitude in the frequency range [fmin, fmax].
199+
200+
Parameters
201+
----------
202+
fmin : float
203+
Lower bound of frequency of interest.
204+
fmax : float
205+
Upper bound of frequency of interest.
206+
kwargs : dict
207+
Keyword arguments passed to ``GaussianPulse()``, excluding ``freq0`` & ``fwidth``.
208+
209+
Returns
210+
-------
211+
GaussianPulse
212+
A ``GaussianPulse`` that maximizes its amplitude in the frequency range [fmin, fmax].
213+
"""
214+
# validate that fmin and fmax must positive, and fmax > fmin
215+
if fmin <= 0:
216+
raise ValidationError("'fmin' must be positive.")
217+
if fmax <= fmin:
218+
raise ValidationError("'fmax' must be greater than 'fmin'.")
219+
220+
# frequency range and center
221+
freq_range = fmax - fmin
222+
freq_center = (fmax + fmin) / 2.0
223+
224+
# If remove_dc_component=False, simply return the standard GaussianPulse parameters
225+
if kwargs.get("remove_dc_component", True) is False:
226+
return cls(freq0=freq_center, fwidth=freq_range / 2.0, **kwargs)
227+
228+
# If remove_dc_component=True, the Gaussian pulse is distorted
229+
kwargs.update({"remove_dc_component": True})
230+
log_ratio = np.log(fmax / fmin)
231+
coeff = ((1 + log_ratio**2) ** 0.5 - 1) / 2.0
232+
freq0 = freq_center - coeff / log_ratio * freq_range
233+
fwidth = freq_range / log_ratio * coeff**0.5
234+
return cls(freq0=freq0, fwidth=fwidth, **kwargs)
235+
194236

195237
class ContinuousWave(Pulse):
196238
"""Source time dependence that ramps up to continuous oscillation

tidy3d/plugins/smatrix/component_modelers/terminal.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from ..ports.coaxial_lumped import CoaxialLumpedPort
3232
from ..ports.rectangular_lumped import LumpedPort
3333
from ..ports.wave import WavePort
34-
from .base import FWIDTH_FRAC, AbstractComponentModeler, TerminalPortType
34+
from .base import AbstractComponentModeler, TerminalPortType
3535

3636

3737
class TerminalComponentModeler(AbstractComponentModeler):
@@ -164,11 +164,8 @@ def sim_dict(self) -> Dict[str, Simulation]:
164164
@cached_property
165165
def _source_time(self):
166166
"""Helper to create a time domain pulse for the frequency range of interest."""
167-
freq0 = np.mean(self.freqs)
168-
fdiff = max(self.freqs) - min(self.freqs)
169-
fwidth = max(fdiff, freq0 * FWIDTH_FRAC)
170-
return GaussianPulse(
171-
freq0=freq0, fwidth=fwidth, remove_dc_component=self.remove_dc_component
167+
return GaussianPulse.from_frequency_range(
168+
fmin=min(self.freqs), fmax=max(self.freqs), remove_dc_component=self.remove_dc_component
172169
)
173170

174171
def _construct_smatrix(self) -> TerminalPortDataArray:

0 commit comments

Comments
 (0)