Skip to content

Commit e579b17

Browse files
feat: low freq smoothing of mode monitors
1 parent ef3d257 commit e579b17

File tree

12 files changed

+488
-5
lines changed

12 files changed

+488
-5
lines changed

CHANGELOG.md

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

1111
### Added
1212
- Added support for `tidy3d-extras`, an optional plugin that enables more accurate local mode solving via subpixel averaging.
13+
- Added `LowFrequencySmoothingSpec` and `ModelerLowFrequencySmoothingSpec` for automatic smoothing of mode monitor data at low frequencies where DFT sampling is insufficient.
1314

1415
### Changed
1516
- Improved performance of antenna metrics calculation by utilizing cached wave amplitude calculations instead of recomputing wave amplitudes for each port excitation in the `TerminalComponentModelerData`.

schemas/Simulation.json

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9350,6 +9350,53 @@
93509350
],
93519351
"type": "object"
93529352
},
9353+
"LowFrequencySmoothingSpec": {
9354+
"additionalProperties": false,
9355+
"properties": {
9356+
"attrs": {
9357+
"default": {},
9358+
"type": "object"
9359+
},
9360+
"max_deviation": {
9361+
"default": 0.5,
9362+
"minimum": 0,
9363+
"type": "number"
9364+
},
9365+
"max_sampling_time": {
9366+
"default": 5.0,
9367+
"minimum": 0,
9368+
"type": "number"
9369+
},
9370+
"min_sampling_time": {
9371+
"default": 1.0,
9372+
"minimum": 0,
9373+
"type": "number"
9374+
},
9375+
"monitors": {
9376+
"items": {
9377+
"type": "string"
9378+
},
9379+
"type": "array"
9380+
},
9381+
"order": {
9382+
"default": 1,
9383+
"maximum": 3,
9384+
"minimum": 0,
9385+
"type": "integer"
9386+
},
9387+
"type": {
9388+
"default": "LowFrequencySmoothingSpec",
9389+
"enum": [
9390+
"LowFrequencySmoothingSpec"
9391+
],
9392+
"type": "string"
9393+
}
9394+
},
9395+
"required": [
9396+
"monitors"
9397+
],
9398+
"type": "object"
9399+
},
93539400
"LumpedResistor": {
93549401
"additionalProperties": false,
93559402
"properties": {
@@ -15539,6 +15586,13 @@
1553915586
},
1554015587
"type": "array"
1554115588
},
15589+
"low_freq_smoothing": {
15590+
"allOf": [
15591+
{
15592+
"$ref": "#/definitions/LowFrequencySmoothingSpec"
15593+
}
15594+
]
15595+
},
1554215596
"lumped_elements": {
1554315597
"default": [],
1554415598
"items": {

schemas/TerminalComponentModeler.json

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9658,6 +9658,53 @@
96589658
],
96599659
"type": "object"
96609660
},
9661+
"LowFrequencySmoothingSpec": {
9662+
"additionalProperties": false,
9663+
"properties": {
9664+
"attrs": {
9665+
"default": {},
9666+
"type": "object"
9667+
},
9668+
"max_deviation": {
9669+
"default": 0.5,
9670+
"minimum": 0,
9671+
"type": "number"
9672+
},
9673+
"max_sampling_time": {
9674+
"default": 5.0,
9675+
"minimum": 0,
9676+
"type": "number"
9677+
},
9678+
"min_sampling_time": {
9679+
"default": 1.0,
9680+
"minimum": 0,
9681+
"type": "number"
9682+
},
9683+
"monitors": {
9684+
"items": {
9685+
"type": "string"
9686+
},
9687+
"type": "array"
9688+
},
9689+
"order": {
9690+
"default": 1,
9691+
"maximum": 3,
9692+
"minimum": 0,
9693+
"type": "integer"
9694+
},
9695+
"type": {
9696+
"default": "LowFrequencySmoothingSpec",
9697+
"enum": [
9698+
"LowFrequencySmoothingSpec"
9699+
],
9700+
"type": "string"
9701+
}
9702+
},
9703+
"required": [
9704+
"monitors"
9705+
],
9706+
"type": "object"
9707+
},
96619708
"LumpedPort": {
96629709
"additionalProperties": false,
96639710
"properties": {
@@ -11484,6 +11531,44 @@
1148411531
},
1148511532
"type": "object"
1148611533
},
11534+
"ModelerLowFrequencySmoothingSpec": {
11535+
"additionalProperties": false,
11536+
"properties": {
11537+
"attrs": {
11538+
"default": {},
11539+
"type": "object"
11540+
},
11541+
"max_deviation": {
11542+
"default": 0.5,
11543+
"minimum": 0,
11544+
"type": "number"
11545+
},
11546+
"max_sampling_time": {
11547+
"default": 5.0,
11548+
"minimum": 0,
11549+
"type": "number"
11550+
},
11551+
"min_sampling_time": {
11552+
"default": 1.0,
11553+
"minimum": 0,
11554+
"type": "number"
11555+
},
11556+
"order": {
11557+
"default": 1,
11558+
"maximum": 3,
11559+
"minimum": 0,
11560+
"type": "integer"
11561+
},
11562+
"type": {
11563+
"default": "ModelerLowFrequencySmoothingSpec",
11564+
"enum": [
11565+
"ModelerLowFrequencySmoothingSpec"
11566+
],
11567+
"type": "string"
11568+
}
11569+
},
11570+
"type": "object"
11571+
},
1148711572
"ModulationSpec": {
1148811573
"additionalProperties": false,
1148911574
"properties": {
@@ -14240,6 +14325,13 @@
1424014325
},
1424114326
"type": "array"
1424214327
},
14328+
"low_freq_smoothing": {
14329+
"allOf": [
14330+
{
14331+
"$ref": "#/definitions/LowFrequencySmoothingSpec"
14332+
}
14333+
]
14334+
},
1424314335
"lumped_elements": {
1424414336
"default": [],
1424514337
"items": {
@@ -16922,6 +17014,21 @@
1692217014
}
1692317015
]
1692417016
},
17017+
"low_freq_smoothing": {
17018+
"allOf": [
17019+
{
17020+
"$ref": "#/definitions/ModelerLowFrequencySmoothingSpec"
17021+
}
17022+
],
17023+
"default": {
17024+
"attrs": {},
17025+
"max_deviation": 0.5,
17026+
"max_sampling_time": 5.0,
17027+
"min_sampling_time": 1.0,
17028+
"order": 1,
17029+
"type": "ModelerLowFrequencySmoothingSpec"
17030+
}
17031+
},
1692517032
"name": {
1692617033
"default": "",
1692717034
"type": "string"
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""Test low frequency smoothing specification."""
2+
3+
from __future__ import annotations
4+
5+
import pydantic.v1 as pydantic
6+
import pytest
7+
8+
import tidy3d as td
9+
10+
11+
def test_low_freq_smoothing_spec_initialization_default_values():
12+
"""Test that LowFrequencySmoothingSpec initializes with correct default values."""
13+
spec = td.LowFrequencySmoothingSpec(monitors=["monitor1"])
14+
assert spec.min_sampling_time == 1
15+
assert spec.max_sampling_time == 5
16+
assert spec.order == 1
17+
assert spec.max_deviation == 0.5
18+
19+
20+
def test_empty_monitors():
21+
"""Test that LowFrequencySmoothingSpec raises an error if monitors are not provided."""
22+
with pytest.raises(pydantic.ValidationError):
23+
td.LowFrequencySmoothingSpec(monitors=[])
24+
25+
26+
def test_monitors_exist():
27+
"""Test that LowFrequencySmoothingSpec raises an error if monitors do not exist."""
28+
sim = td.Simulation(
29+
size=(1, 1, 1),
30+
monitors=[
31+
td.ModeMonitor(
32+
name="monitor1",
33+
mode_spec=td.ModeSpec(num_modes=1),
34+
freqs=[td.C_0],
35+
size=(0.5, 0.5, 0),
36+
)
37+
],
38+
run_time=1e-12,
39+
grid_spec=td.GridSpec.auto(min_steps_per_wvl=20, wavelength=1),
40+
low_freq_smoothing=td.LowFrequencySmoothingSpec(monitors=["monitor1"]),
41+
)
42+
43+
with pytest.raises(pydantic.ValidationError):
44+
sim = td.Simulation(
45+
size=(1, 1, 1),
46+
monitors=[],
47+
run_time=1e-12,
48+
grid_spec=td.GridSpec.auto(min_steps_per_wvl=20, wavelength=1),
49+
low_freq_smoothing=td.LowFrequencySmoothingSpec(monitors=["monitor1"]),
50+
)

tests/test_plugins/smatrix/test_terminal_component_modeler.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,3 +1393,115 @@ def test_wave_port_to_absorber(tmp_path):
13931393
sim = list(modeler.sim_dict.values())[0]
13941394
absorber = sim.internal_absorbers[0]
13951395
assert absorber.boundary_spec == custom_boundary_spec
1396+
1397+
1398+
def test_low_freq_smoothing_spec_initialization_default_values():
1399+
"""Test that LowFrequencySmoothingSpec initializes with correct default values."""
1400+
from tidy3d.plugins.smatrix.component_modelers.terminal import ModelerLowFrequencySmoothingSpec
1401+
1402+
spec = ModelerLowFrequencySmoothingSpec()
1403+
assert spec.min_sampling_time == 1
1404+
assert spec.max_sampling_time == 5
1405+
assert spec.order == 1
1406+
assert spec.max_deviation == 0.5
1407+
1408+
1409+
def test_low_freq_smoothing_spec_initialization_custom_values():
1410+
"""Test that LowFrequencySmoothingSpec initializes with custom values."""
1411+
from tidy3d.plugins.smatrix.component_modelers.terminal import ModelerLowFrequencySmoothingSpec
1412+
1413+
spec = ModelerLowFrequencySmoothingSpec(
1414+
min_sampling_time=2, max_sampling_time=8, order=2, max_deviation=0.3
1415+
)
1416+
assert spec.min_sampling_time == 2
1417+
assert spec.max_sampling_time == 8
1418+
assert spec.order == 2
1419+
assert spec.max_deviation == 0.3
1420+
1421+
1422+
def test_low_freq_smoothing_spec_edge_cases():
1423+
"""Test edge cases and boundary conditions."""
1424+
from tidy3d.plugins.smatrix.component_modelers.terminal import ModelerLowFrequencySmoothingSpec
1425+
1426+
# Test with order 0 (constant fit)
1427+
spec = ModelerLowFrequencySmoothingSpec(order=0)
1428+
assert spec.order == 0
1429+
1430+
# Test with maximum order
1431+
spec = ModelerLowFrequencySmoothingSpec(order=3)
1432+
assert spec.order == 3
1433+
1434+
# Test with zero max_deviation
1435+
spec = ModelerLowFrequencySmoothingSpec(max_deviation=0.0)
1436+
assert spec.max_deviation == 0.0
1437+
1438+
# Test with maximum max_deviation
1439+
spec = ModelerLowFrequencySmoothingSpec(max_deviation=1.0)
1440+
assert spec.max_deviation == 1.0
1441+
1442+
1443+
def test_low_freq_smoothing_spec_validation_sampling_times_invalid():
1444+
"""Test validation of sampling time parameters."""
1445+
from tidy3d.plugins.smatrix.component_modelers.terminal import ModelerLowFrequencySmoothingSpec
1446+
1447+
# Test invalid range where min_sampling_time >= max_sampling_time
1448+
with pytest.raises(
1449+
ValueError, match="The minimum sampling time must be less than the maximum sampling time"
1450+
):
1451+
ModelerLowFrequencySmoothingSpec(min_sampling_time=5, max_sampling_time=3)
1452+
1453+
with pytest.raises(
1454+
ValueError, match="The minimum sampling time must be less than the maximum sampling time"
1455+
):
1456+
ModelerLowFrequencySmoothingSpec(min_sampling_time=3, max_sampling_time=3)
1457+
1458+
1459+
def test_low_freq_smoothing_spec_validation_order_bounds():
1460+
"""Test validation of order parameter bounds."""
1461+
from tidy3d.plugins.smatrix.component_modelers.terminal import ModelerLowFrequencySmoothingSpec
1462+
1463+
# Test valid orders
1464+
ModelerLowFrequencySmoothingSpec(order=0)
1465+
ModelerLowFrequencySmoothingSpec(order=3)
1466+
1467+
# Test invalid orders
1468+
with pytest.raises(pd.ValidationError):
1469+
ModelerLowFrequencySmoothingSpec(order=-1)
1470+
1471+
with pytest.raises(pd.ValidationError):
1472+
ModelerLowFrequencySmoothingSpec(order=4)
1473+
1474+
1475+
def test_low_freq_smoothing_spec_validation_max_deviation_bounds():
1476+
"""Test validation of max_deviation parameter bounds."""
1477+
from tidy3d.plugins.smatrix.component_modelers.terminal import ModelerLowFrequencySmoothingSpec
1478+
1479+
# Test valid max_deviation
1480+
ModelerLowFrequencySmoothingSpec(max_deviation=0.0)
1481+
ModelerLowFrequencySmoothingSpec(max_deviation=1.0)
1482+
1483+
# Test invalid max_deviation
1484+
with pytest.raises(pd.ValidationError):
1485+
ModelerLowFrequencySmoothingSpec(max_deviation=-0.1)
1486+
1487+
1488+
def test_low_freq_smoothing_spec_sim_dict():
1489+
"""Test that LowFrequencySmoothingSpec is correctly added to the sim_dict."""
1490+
from tidy3d.plugins.smatrix.component_modelers.terminal import ModelerLowFrequencySmoothingSpec
1491+
1492+
spec = ModelerLowFrequencySmoothingSpec(
1493+
min_sampling_time=2, max_sampling_time=8, order=2, max_deviation=0.3
1494+
)
1495+
1496+
modeler = make_coaxial_component_modeler(port_types=(WavePort, WavePort))
1497+
modeler = modeler.updated_copy(low_freq_smoothing=spec)
1498+
for sim in modeler.sim_dict.values():
1499+
assert spec.min_sampling_time == sim.low_freq_smoothing.min_sampling_time
1500+
assert spec.max_sampling_time == sim.low_freq_smoothing.max_sampling_time
1501+
assert spec.order == sim.low_freq_smoothing.order
1502+
assert spec.max_deviation == sim.low_freq_smoothing.max_deviation
1503+
assert sim.low_freq_smoothing.monitors == tuple(mnt.name for mnt in sim.monitors[-2:])
1504+
1505+
modeler = modeler.updated_copy(low_freq_smoothing=None)
1506+
for sim in modeler.sim_dict.values():
1507+
assert sim.low_freq_smoothing is None

tidy3d/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from tidy3d.components.boundary import BroadbandModeABCFitterParam, BroadbandModeABCSpec
66
from tidy3d.components.data.index import SimulationDataMap
7+
from tidy3d.components.frequency_extrapolation import LowFrequencySmoothingSpec
78
from tidy3d.components.index import SimulationMap
89
from tidy3d.components.material.multi_physics import MultiPhysicsMedium
910
from tidy3d.components.material.tcad.charge import (
@@ -619,6 +620,7 @@ def set_logging_level(level: str) -> None:
619620
"LinearLumpedElement",
620621
"Lorentz",
621622
"LossyMetalMedium",
623+
"LowFrequencySmoothingSpec",
622624
"LumpedElement",
623625
"LumpedResistor",
624626
"Medium",

0 commit comments

Comments
 (0)