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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added voltage integral specification classes: `AxisAlignedVoltageIntegralSpec` and `Custom2DVoltageIntegralSpec`.
- Added current integral specification classes: `AxisAlignedCurrentIntegralSpec`, `CompositeCurrentIntegralSpec`, and `Custom2DCurrentIntegralSpec`.
- `sort_spec` in `ModeSpec` allows for fine-grained filtering and sorting of modes. This also deprecates `filter_pol`. The equivalent usage for example to `filter_pol="te"` is `sort_spec=ModeSortSpec(filter_key="TE_polarization", filter_reference=0.5)`. `ModeSpec.track_freq` has also been deprecated and moved to `ModeSortSpec.track_freq`.
- Added `custom_source_time` parameter to `ComponentModeler` classes (`ModalComponentModeler` and `TerminalComponentModeler`), allowing specification of custom source time dependence.

### Changed
- Improved performance of antenna metrics calculation by utilizing cached wave amplitude calculations instead of recomputing wave amplitudes for each port excitation in the `TerminalComponentModelerData`.
Expand Down
13 changes: 13 additions & 0 deletions schemas/TerminalComponentModeler.json
Original file line number Diff line number Diff line change
Expand Up @@ -18180,6 +18180,19 @@
"default": {},
"type": "object"
},
"custom_source_time": {
"anyOf": [
{
"$ref": "#/definitions/ContinuousWave"
},
{
"$ref": "#/definitions/CustomSourceTime"
},
{
"$ref": "#/definitions/GaussianPulse"
}
]
},
"element_mappings": {
"default": [],
"items": {
Expand Down
36 changes: 35 additions & 1 deletion tests/test_plugins/smatrix/test_component_modeler.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from tidy3d.plugins.smatrix import ModalComponentModeler, ModalComponentModelerData, Port
from tidy3d.web.api.container import Batch

from ...utils import run_emulated
from ...utils import AssertLogStr, run_emulated

# Waveguide height
wg_height = 0.22
Expand Down Expand Up @@ -445,3 +445,37 @@ def test_get_task_name():
# Test with invalid format
with pytest.raises(ValueError):
ModalComponentModeler.get_task_name(port=port, format="invalid")


def test_custom_source_time(monkeypatch):
"""Test that custom_source_time is properly used in the component modeler."""
modeler = make_component_modeler()
freqs = modeler.freqs
custom_source = td.GaussianPulse.from_frequency_range(fmin=min(freqs), fmax=max(freqs))

# Create modeler with custom source time
with AssertLogStr(
log_level_expected="WARNING", excludes_str="Custom source time does not cover all"
):
modeler = make_component_modeler(custom_source_time=custom_source)

# Run the modeler and verify it works with custom source time
modeler_data = run_component_modeler(monkeypatch, modeler=modeler)

# Verify that simulations were created and run successfully
s_matrix = modeler_data.smatrix()
assert s_matrix is not None

# Verify that the simulations in sim_dict use the custom source time
for sim in modeler.sim_dict.values():
# Each simulation should have sources with the custom source time
assert len(sim.sources) > 0
for source in sim.sources:
assert source.source_time.freq0 == custom_source.freq0
assert source.source_time.fwidth == custom_source.fwidth

with AssertLogStr(
log_level_expected="WARNING", contains_str="Custom source time does not cover all"
):
custom_source = td.GaussianPulse(freq0=td.C_0, fwidth=1e12)
modeler = make_component_modeler(custom_source_time=custom_source)
26 changes: 26 additions & 0 deletions tests/test_plugins/smatrix/test_terminal_component_modeler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1778,3 +1778,29 @@ def test_wave_port_extrusion_differential_stripline():
# make sure that the error is triggered even when ports are reshuffled
with pytest.raises(SetupError):
sim = tcm.base_sim


def test_custom_source_time(monkeypatch, tmp_path):
"""Test that custom_source_time is properly used in the terminal component modeler."""
# Create a custom source time
custom_source = td.GaussianPulse(freq0=2e9, fwidth=1e8)

# Create modeler with custom source time
modeler = make_component_modeler(
planar_pec=True, port_refinement=False, custom_source_time=custom_source
)

# Run the modeler and verify it works with custom source time
modeler_data = run_component_modeler(monkeypatch, modeler=modeler)

# Verify that simulations were created and run successfully
s_matrix = modeler_data.smatrix()
assert s_matrix is not None

# Verify that the simulations in sim_dict use the custom source time
for sim in modeler.sim_dict.values():
# Each simulation should have sources with the custom source time
assert len(sim.sources) > 0
for source in sim.sources:
assert source.source_time.freq0 == custom_source.freq0
assert source.source_time.fwidth == custom_source.fwidth
24 changes: 23 additions & 1 deletion tidy3d/plugins/smatrix/component_modelers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
import pydantic.v1 as pd

from tidy3d.components.autograd.constants import MAX_NUM_ADJOINT_PER_FWD
from tidy3d.components.base import Tidy3dBaseModel, cached_property
from tidy3d.components.base import Tidy3dBaseModel, cached_property, skip_if_fields_missing
from tidy3d.components.geometry.utils import _shift_value_signed
from tidy3d.components.simulation import Simulation
from tidy3d.components.source.time import SourceTimeType
from tidy3d.components.types import Complex, FreqArray
from tidy3d.components.validators import (
assert_unique_names,
Expand Down Expand Up @@ -94,6 +95,12 @@ class AbstractComponentModeler(ABC, Tidy3dBaseModel):
"matrix element. If all elements of a given column of the scattering matrix are defined "
"by ``element_mappings``, the simulation corresponding to this column is skipped automatically.",
)
custom_source_time: Optional[SourceTimeType] = pd.Field(
None,
title="Custom Source Time",
description="If provided, this will be used as specification of the source time-dependence in simulations. "
"Otherwise, a default source time will be constructed.",
)

@pd.validator("simulation", always=True)
def _sim_has_no_sources(cls, val):
Expand Down Expand Up @@ -130,6 +137,21 @@ def _validate_element_mappings(cls, element_mappings, values):
_freqs_lower_bound = validate_freqs_min()
_freqs_unique = validate_freqs_unique()

@pd.validator("custom_source_time", always=True)
@skip_if_fields_missing(["freqs"])
def _freqs_in_custom_source_time(cls, val, values):
"""Make sure freqs is in the range of the custom source time."""
if val is None:
return val
freq_range = val._frequency_range_sigma_cached
freqs = values["freqs"]

if freq_range[0] > min(freqs) or max(freqs) > freq_range[1]:
log.warning(
"Custom source time does not cover all 'freqs'.",
)
return val

@staticmethod
def get_task_name(
port: Port, mode_index: Optional[int] = None, format: Optional[TaskNameFormat] = "RF"
Expand Down
4 changes: 3 additions & 1 deletion tidy3d/plugins/smatrix/component_modelers/modal.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,9 @@ def to_source(
return ModeSource(
center=port.center,
size=port.size,
source_time=GaussianPulse(freq0=freq0, fwidth=fwidth),
source_time=self.custom_source_time
if self.custom_source_time is not None
else GaussianPulse(freq0=freq0, fwidth=fwidth),
mode_spec=port.mode_spec,
mode_index=mode_index,
direction=port.direction,
Expand Down
2 changes: 2 additions & 0 deletions tidy3d/plugins/smatrix/component_modelers/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,8 @@ def _add_source_to_sim(self, source_index: NetworkIndex) -> tuple[str, Simulatio
@cached_property
def _source_time(self):
"""Helper to create a time domain pulse for the frequency range of interest."""
if self.custom_source_time is not None:
return self.custom_source_time
if len(self.freqs) == 1:
freq0 = self.freqs[0]
return GaussianPulse(freq0=self.freqs[0], fwidth=freq0 * FWIDTH_FRAC)
Expand Down