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 @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Added support for `tidy3d-extras`, an optional plugin that enables more accurate local mode solving via subpixel averaging.
- Added support for `symlog` and `log` scale plotting in `Scene.plot_eps()` and `Scene.plot_structures_property()` methods. The `symlog` scale provides linear behavior near zero and logarithmic behavior elsewhere, while 'log' is a base 10 logarithmic scale.
- Added `LowFrequencySmoothingSpec` and `ModelerLowFrequencySmoothingSpec` for automatic smoothing of mode monitor data at low frequencies where DFT sampling is insufficient.

### 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
54 changes: 54 additions & 0 deletions schemas/Simulation.json
Original file line number Diff line number Diff line change
Expand Up @@ -9350,6 +9350,53 @@
],
"type": "object"
},
"LowFrequencySmoothingSpec": {
"additionalProperties": false,
"properties": {
"attrs": {
"default": {},
"type": "object"
},
"max_deviation": {
"default": 0.5,
"minimum": 0,
"type": "number"
},
"max_sampling_time": {
"default": 5.0,
"minimum": 0,
"type": "number"
},
"min_sampling_time": {
"default": 1.0,
"minimum": 0,
"type": "number"
},
"monitors": {
"items": {
"type": "string"
},
"type": "array"
},
"order": {
"default": 1,
"maximum": 3,
"minimum": 0,
"type": "integer"
},
"type": {
"default": "LowFrequencySmoothingSpec",
"enum": [
"LowFrequencySmoothingSpec"
],
"type": "string"
}
},
"required": [
"monitors"
],
"type": "object"
},
"LumpedResistor": {
"additionalProperties": false,
"properties": {
Expand Down Expand Up @@ -15539,6 +15586,13 @@
},
"type": "array"
},
"low_freq_smoothing": {
"allOf": [
{
"$ref": "#/definitions/LowFrequencySmoothingSpec"
}
]
},
"lumped_elements": {
"default": [],
"items": {
Expand Down
107 changes: 107 additions & 0 deletions schemas/TerminalComponentModeler.json
Original file line number Diff line number Diff line change
Expand Up @@ -9658,6 +9658,53 @@
],
"type": "object"
},
"LowFrequencySmoothingSpec": {
"additionalProperties": false,
"properties": {
"attrs": {
"default": {},
"type": "object"
},
"max_deviation": {
"default": 0.5,
"minimum": 0,
"type": "number"
},
"max_sampling_time": {
"default": 5.0,
"minimum": 0,
"type": "number"
},
"min_sampling_time": {
"default": 1.0,
"minimum": 0,
"type": "number"
},
"monitors": {
"items": {
"type": "string"
},
"type": "array"
},
"order": {
"default": 1,
"maximum": 3,
"minimum": 0,
"type": "integer"
},
"type": {
"default": "LowFrequencySmoothingSpec",
"enum": [
"LowFrequencySmoothingSpec"
],
"type": "string"
}
},
"required": [
"monitors"
],
"type": "object"
},
"LumpedPort": {
"additionalProperties": false,
"properties": {
Expand Down Expand Up @@ -11484,6 +11531,44 @@
},
"type": "object"
},
"ModelerLowFrequencySmoothingSpec": {
"additionalProperties": false,
"properties": {
"attrs": {
"default": {},
"type": "object"
},
"max_deviation": {
"default": 0.5,
"minimum": 0,
"type": "number"
},
"max_sampling_time": {
"default": 5.0,
"minimum": 0,
"type": "number"
},
"min_sampling_time": {
"default": 1.0,
"minimum": 0,
"type": "number"
},
"order": {
"default": 1,
"maximum": 3,
"minimum": 0,
"type": "integer"
},
"type": {
"default": "ModelerLowFrequencySmoothingSpec",
"enum": [
"ModelerLowFrequencySmoothingSpec"
],
"type": "string"
}
},
"type": "object"
},
"ModulationSpec": {
"additionalProperties": false,
"properties": {
Expand Down Expand Up @@ -14240,6 +14325,13 @@
},
"type": "array"
},
"low_freq_smoothing": {
"allOf": [
{
"$ref": "#/definitions/LowFrequencySmoothingSpec"
}
]
},
"lumped_elements": {
"default": [],
"items": {
Expand Down Expand Up @@ -16922,6 +17014,21 @@
}
]
},
"low_freq_smoothing": {
"allOf": [
{
"$ref": "#/definitions/ModelerLowFrequencySmoothingSpec"
}
],
"default": {
"attrs": {},
"max_deviation": 0.5,
"max_sampling_time": 5.0,
"min_sampling_time": 1.0,
"order": 1,
"type": "ModelerLowFrequencySmoothingSpec"
}
},
"name": {
"default": "",
"type": "string"
Expand Down
50 changes: 50 additions & 0 deletions tests/test_components/test_low_freq_smoothing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Test low frequency smoothing specification."""

from __future__ import annotations

import pydantic.v1 as pydantic
import pytest

import tidy3d as td


def test_low_freq_smoothing_spec_initialization_default_values():
"""Test that LowFrequencySmoothingSpec initializes with correct default values."""
spec = td.LowFrequencySmoothingSpec(monitors=["monitor1"])
assert spec.min_sampling_time == 1
assert spec.max_sampling_time == 5
assert spec.order == 1
assert spec.max_deviation == 0.5


def test_empty_monitors():
"""Test that LowFrequencySmoothingSpec raises an error if monitors are not provided."""
with pytest.raises(pydantic.ValidationError):
td.LowFrequencySmoothingSpec(monitors=[])


def test_monitors_exist():
"""Test that LowFrequencySmoothingSpec raises an error if monitors do not exist."""
sim = td.Simulation(
size=(1, 1, 1),
monitors=[
td.ModeMonitor(
name="monitor1",
mode_spec=td.ModeSpec(num_modes=1),
freqs=[td.C_0],
size=(0.5, 0.5, 0),
)
],
run_time=1e-12,
grid_spec=td.GridSpec.auto(min_steps_per_wvl=20, wavelength=1),
low_freq_smoothing=td.LowFrequencySmoothingSpec(monitors=["monitor1"]),
)

with pytest.raises(pydantic.ValidationError):
sim = td.Simulation(
size=(1, 1, 1),
monitors=[],
run_time=1e-12,
grid_spec=td.GridSpec.auto(min_steps_per_wvl=20, wavelength=1),
low_freq_smoothing=td.LowFrequencySmoothingSpec(monitors=["monitor1"]),
)
112 changes: 112 additions & 0 deletions tests/test_plugins/smatrix/test_terminal_component_modeler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1424,3 +1424,115 @@ def test_wave_port_to_absorber(tmp_path):
sim = list(modeler.sim_dict.values())[0]
absorber = sim.internal_absorbers[0]
assert absorber.boundary_spec == custom_boundary_spec


def test_low_freq_smoothing_spec_initialization_default_values():
"""Test that LowFrequencySmoothingSpec initializes with correct default values."""
from tidy3d.plugins.smatrix.component_modelers.terminal import ModelerLowFrequencySmoothingSpec

spec = ModelerLowFrequencySmoothingSpec()
assert spec.min_sampling_time == 1
assert spec.max_sampling_time == 5
assert spec.order == 1
assert spec.max_deviation == 0.5


def test_low_freq_smoothing_spec_initialization_custom_values():
"""Test that LowFrequencySmoothingSpec initializes with custom values."""
from tidy3d.plugins.smatrix.component_modelers.terminal import ModelerLowFrequencySmoothingSpec

spec = ModelerLowFrequencySmoothingSpec(
min_sampling_time=2, max_sampling_time=8, order=2, max_deviation=0.3
)
assert spec.min_sampling_time == 2
assert spec.max_sampling_time == 8
assert spec.order == 2
assert spec.max_deviation == 0.3


def test_low_freq_smoothing_spec_edge_cases():
"""Test edge cases and boundary conditions."""
from tidy3d.plugins.smatrix.component_modelers.terminal import ModelerLowFrequencySmoothingSpec

# Test with order 0 (constant fit)
spec = ModelerLowFrequencySmoothingSpec(order=0)
assert spec.order == 0

# Test with maximum order
spec = ModelerLowFrequencySmoothingSpec(order=3)
assert spec.order == 3

# Test with zero max_deviation
spec = ModelerLowFrequencySmoothingSpec(max_deviation=0.0)
assert spec.max_deviation == 0.0

# Test with maximum max_deviation
spec = ModelerLowFrequencySmoothingSpec(max_deviation=1.0)
assert spec.max_deviation == 1.0


def test_low_freq_smoothing_spec_validation_sampling_times_invalid():
"""Test validation of sampling time parameters."""
from tidy3d.plugins.smatrix.component_modelers.terminal import ModelerLowFrequencySmoothingSpec

# Test invalid range where min_sampling_time >= max_sampling_time
with pytest.raises(
ValueError, match="The minimum sampling time must be less than the maximum sampling time"
):
ModelerLowFrequencySmoothingSpec(min_sampling_time=5, max_sampling_time=3)

with pytest.raises(
ValueError, match="The minimum sampling time must be less than the maximum sampling time"
):
ModelerLowFrequencySmoothingSpec(min_sampling_time=3, max_sampling_time=3)


def test_low_freq_smoothing_spec_validation_order_bounds():
"""Test validation of order parameter bounds."""
from tidy3d.plugins.smatrix.component_modelers.terminal import ModelerLowFrequencySmoothingSpec

# Test valid orders
ModelerLowFrequencySmoothingSpec(order=0)
ModelerLowFrequencySmoothingSpec(order=3)

# Test invalid orders
with pytest.raises(pd.ValidationError):
ModelerLowFrequencySmoothingSpec(order=-1)

with pytest.raises(pd.ValidationError):
ModelerLowFrequencySmoothingSpec(order=4)


def test_low_freq_smoothing_spec_validation_max_deviation_bounds():
"""Test validation of max_deviation parameter bounds."""
from tidy3d.plugins.smatrix.component_modelers.terminal import ModelerLowFrequencySmoothingSpec

# Test valid max_deviation
ModelerLowFrequencySmoothingSpec(max_deviation=0.0)
ModelerLowFrequencySmoothingSpec(max_deviation=1.0)

# Test invalid max_deviation
with pytest.raises(pd.ValidationError):
ModelerLowFrequencySmoothingSpec(max_deviation=-0.1)


def test_low_freq_smoothing_spec_sim_dict():
"""Test that LowFrequencySmoothingSpec is correctly added to the sim_dict."""
from tidy3d.plugins.smatrix.component_modelers.terminal import ModelerLowFrequencySmoothingSpec

spec = ModelerLowFrequencySmoothingSpec(
min_sampling_time=2, max_sampling_time=8, order=2, max_deviation=0.3
)

modeler = make_coaxial_component_modeler(port_types=(WavePort, WavePort))
modeler = modeler.updated_copy(low_freq_smoothing=spec)
for sim in modeler.sim_dict.values():
assert spec.min_sampling_time == sim.low_freq_smoothing.min_sampling_time
assert spec.max_sampling_time == sim.low_freq_smoothing.max_sampling_time
assert spec.order == sim.low_freq_smoothing.order
assert spec.max_deviation == sim.low_freq_smoothing.max_deviation
assert sim.low_freq_smoothing.monitors == tuple(mnt.name for mnt in sim.monitors[-2:])

modeler = modeler.updated_copy(low_freq_smoothing=None)
for sim in modeler.sim_dict.values():
assert sim.low_freq_smoothing is None
2 changes: 2 additions & 0 deletions tidy3d/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from tidy3d.components.boundary import BroadbandModeABCFitterParam, BroadbandModeABCSpec
from tidy3d.components.data.index import SimulationDataMap
from tidy3d.components.frequency_extrapolation import LowFrequencySmoothingSpec
from tidy3d.components.index import SimulationMap
from tidy3d.components.material.multi_physics import MultiPhysicsMedium
from tidy3d.components.material.tcad.charge import (
Expand Down Expand Up @@ -619,6 +620,7 @@ def set_logging_level(level: str) -> None:
"LinearLumpedElement",
"Lorentz",
"LossyMetalMedium",
"LowFrequencySmoothingSpec",
"LumpedElement",
"LumpedResistor",
"Medium",
Expand Down
Loading