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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Port names in `ModalComponentModeler` and `TerminalComponentModeler` can no longer include the `@` symbol.
- Improved speed of convolutions for large inputs.
- Default value of `EMEModeSpec.interp_spec` is `ModeInterpSpec.cheb(num_points=3, reduce_data=True)` for faster multi-frequency EME simulations.
- Default value of `num_sweep` in `EMECoefficientMonitor` is now `None`, recording all sweep indices.
- Maximum number of frequencies in an `EMESimulation` is now larger if mode interpolation via `ModeInterpSpec` is used.

### Fixed
- Ensured the legacy `Env` proxy mirrors `config.web` profile switches and preserves API URL.
Expand Down
1 change: 0 additions & 1 deletion schemas/EMESimulation.json
Original file line number Diff line number Diff line change
Expand Up @@ -4718,7 +4718,6 @@
"type": "integer"
},
"num_sweep": {
"default": 1,
"minimum": 0,
"type": "integer"
},
Expand Down
Binary file modified tests/sims/full_fdtd.h5
Binary file not shown.
33 changes: 23 additions & 10 deletions tests/sims/full_fdtd.json
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@
"type": "CustomMedium",
"interp_method": "nearest",
"subpixel": false,
"derived_from": null,
"eps_dataset": null,
"permittivity": "SpatialDataArray",
"conductivity": null
Expand Down Expand Up @@ -988,6 +989,7 @@
"type": "CustomMedium",
"interp_method": "nearest",
"subpixel": false,
"derived_from": null,
"eps_dataset": null,
"permittivity": "SpatialDataArray",
"conductivity": null
Expand Down Expand Up @@ -1032,7 +1034,8 @@
]
],
"interp_method": "nearest",
"subpixel": false
"subpixel": false,
"derived_from": null
}
},
{
Expand Down Expand Up @@ -1075,7 +1078,8 @@
]
],
"interp_method": "nearest",
"subpixel": false
"subpixel": false,
"derived_from": null
}
},
{
Expand Down Expand Up @@ -1117,7 +1121,8 @@
]
],
"interp_method": "nearest",
"subpixel": false
"subpixel": false,
"derived_from": null
}
},
{
Expand Down Expand Up @@ -1159,7 +1164,8 @@
]
],
"interp_method": "nearest",
"subpixel": false
"subpixel": false,
"derived_from": null
}
},
{
Expand Down Expand Up @@ -1200,7 +1206,8 @@
]
],
"interp_method": "nearest",
"subpixel": false
"subpixel": false,
"derived_from": null
}
},
{
Expand Down Expand Up @@ -1236,6 +1243,7 @@
"type": "CustomMedium",
"interp_method": "nearest",
"subpixel": false,
"derived_from": null,
"eps_dataset": null,
"permittivity": {
"attrs": {},
Expand Down Expand Up @@ -1304,7 +1312,8 @@
]
],
"interp_method": "nearest",
"subpixel": false
"subpixel": false,
"derived_from": null
}
},
{
Expand Down Expand Up @@ -1371,7 +1380,8 @@
]
],
"interp_method": "nearest",
"subpixel": false
"subpixel": false,
"derived_from": null
}
},
{
Expand Down Expand Up @@ -1431,7 +1441,8 @@
]
],
"interp_method": "nearest",
"subpixel": false
"subpixel": false,
"derived_from": null
}
},
{
Expand Down Expand Up @@ -1491,7 +1502,8 @@
]
],
"interp_method": "nearest",
"subpixel": false
"subpixel": false,
"derived_from": null
}
},
{
Expand Down Expand Up @@ -1544,7 +1556,8 @@
]
],
"interp_method": "nearest",
"subpixel": false
"subpixel": false,
"derived_from": null
}
},
{
Expand Down
19 changes: 17 additions & 2 deletions tests/test_components/test_eme.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,14 +419,29 @@ def test_eme_simulation():
with AssertLogLevel("WARNING", "slow-down"):
sim_bad.validate_pre_upload()

sim_bad = sim.updated_copy(
sim_ok = sim.updated_copy(
freqs=list(sim.freqs) + list(1e14 * np.linspace(1, 2, 1000)),
grid_spec=sim.grid_spec.updated_copy(wavelength=1),
)
sim_ok.validate_pre_upload()
eme_grid_spec_no_interp = td.EMECompositeGrid(
subgrids=[
s.updated_copy(interp_spec=None, path="mode_spec")
for s in sim_ok.eme_grid_spec.subgrids
],
subgrid_boundaries=[-1, 1],
)
sim_bad = sim_ok.updated_copy(eme_grid_spec=eme_grid_spec_no_interp)
with pytest.raises(SetupError):
sim_bad.validate_pre_upload()
sim_bad = sim.updated_copy(
sim_bad = sim_ok.updated_copy(
freqs=list(sim.freqs) + list(1e14 * np.linspace(1, 2, 5000)),
)
with pytest.raises(SetupError):
sim_bad.validate_pre_upload()
sim_bad = sim_ok.updated_copy(
freqs=list(sim.freqs) + list(1e14 * np.linspace(1, 2, 100)),
eme_grid_spec=eme_grid_spec_no_interp,
)
with AssertLogLevel("WARNING", contains_str="expensive"):
sim_bad.validate_pre_upload()
Expand Down
17 changes: 13 additions & 4 deletions tidy3d/components/eme/monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class EMEMonitor(AbstractMonitor, ABC):
eme_cell_interval_space: Literal[1] = pd.Field(
1,
title="EME Cell Interval",
description="Number of eme cells between monitor recordings. If equal to 1, "
description="Number of EME cells between monitor recordings. If equal to 1, "
"there will be no downsampling. If greater than 1, the step will be applied, but the "
"first and last cells are always included. Not used in all monitors. "
"Not all monitors support values different from 1.",
Expand Down Expand Up @@ -141,7 +141,7 @@ class EMEModeSolverMonitor(EMEMonitor):
eme_cell_interval_space: pd.PositiveInt = pd.Field(
1,
title="EME Cell Interval",
description="Number of eme cells between monitor recordings. If equal to 1, "
description="Number of EME cells between monitor recordings. If equal to 1, "
"there will be no downsampling. If greater than 1, the step will be applied, but the "
"first and last cells are always included. Not used in all monitors. "
"Not all monitors support values different from 1.",
Expand Down Expand Up @@ -214,7 +214,7 @@ class EMEFieldMonitor(EMEMonitor, AbstractFieldMonitor):
eme_cell_interval_space: Literal[1] = pd.Field(
1,
title="EME Cell Interval",
description="Number of eme cells between monitor recordings. If equal to 1, "
description="Number of EME cells between monitor recordings. If equal to 1, "
"there will be no downsampling. If greater than 1, the step will be applied, but the "
"first and last cells are always included. Not used in all monitors. "
"Not all monitors support values different from 1. Note: this field is not used for "
Expand Down Expand Up @@ -280,12 +280,21 @@ class EMECoefficientMonitor(EMEMonitor):
eme_cell_interval_space: pd.PositiveInt = pd.Field(
1,
title="EME Cell Interval",
description="Number of eme cells between monitor recordings. If equal to 1, "
description="Number of EME cells between monitor recordings. If equal to 1, "
"there will be no downsampling. If greater than 1, the step will be applied, but the "
"first and last cells are always included. Not used in all monitors. "
"Not all monitors support values different from 1.",
)

num_sweep: Optional[pd.NonNegativeInt] = pd.Field(
None,
title="Number of Sweep Indices",
description="Number of sweep indices for the monitor to record. "
"Cannot exceed the number of sweep indices for the simulation. "
"If the sweep does not change the monitor data, the sweep index "
"will be omitted. A value of 'None' will record all sweep indices.",
)

def storage_size(
self,
num_cells: int,
Expand Down
50 changes: 34 additions & 16 deletions tidy3d/components/eme/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@


# eme specific simulation parameters
WARN_NUM_FREQS = 20
MAX_NUM_FREQS = 500
WARN_NUM_SAMPLING_POINTS = 20
MAX_NUM_SAMPLING_POINTS = 500
MAX_NUM_FREQS = 2000
MAX_NUM_SWEEP = 100


Expand Down Expand Up @@ -105,7 +106,7 @@ class EMESimulation(AbstractYeeGridSimulation):

**Frequency Sweeps**

Frequency sweeps are supported by including multiple frequencies in the `freqs` field. However, our EME solver repeats the mode solving for each new frequency, so frequency sweeps involving a large number of frequencies can be slow and expensive. If a large number of frequencies are required, consider using our FDTD solver instead.
Frequency sweeps are supported by including multiple frequencies in the `freqs` field. To avoid recomputing the modes at each frequency, the modes are interpolated according to the `EMEModeSpec.interp_spec` in the cells `eme_grid_spec`. By setting this `interp_spec`, the interpolation can be changed or disabled (repeating the solve at each frequency, which can be slow).

**Passivity and Unitarity Constraints**

Expand Down Expand Up @@ -159,10 +160,9 @@ class EMESimulation(AbstractYeeGridSimulation):
...,
title="Frequencies",
description="Frequencies for the EME simulation. "
"The field is propagated independently at each provided frequency. "
"This can be slow when the number of frequencies is large. In this case, "
"consider using the approximate 'EMEFreqSweep' as the 'sweep_spec' "
"instead of providing all desired frequencies here.",
"The field is propagated independently at each provided frequency, "
"but the modes are only computed at a few sampling points and interpolated. "
"To change this behavior, you can use 'EMEModeSpec.interp_spec'.",
)

axis: Axis = pd.Field(
Expand Down Expand Up @@ -817,17 +817,23 @@ def _validate_size(self) -> None:
if num_freqs > MAX_NUM_FREQS:
raise SetupError(
f"Simulation has {num_freqs:.2e} frequencies, "
f"a maximum of {MAX_NUM_FREQS:.2e} are allowed. Mode solving "
f"is repeated at each frequency, so EME simulations with too many frequencies "
f"can be slower and more expensive than FDTD simulations. "
f"Consider using an 'EMEFreqSweep' instead for a faster approximate solution."
f"a maximum of {MAX_NUM_FREQS:.2e} are allowed."
)
if num_freqs > WARN_NUM_FREQS:
num_sampling_points = self._num_sampling_points
if num_sampling_points > MAX_NUM_SAMPLING_POINTS:
raise SetupError(
f"Simulation has {num_sampling_points:.2e} frequency sampling points, "
f"a maximum of {MAX_NUM_SAMPLING_POINTS:.2e} are allowed. Mode solving "
f"is repeated at each sampling point, so EME simulations with too many "
f"frequencies can be slower and more expensive than FDTD simulations. "
f"Consider using 'EMEModeSpec.interp_spec' instead for a faster approximate solution."
)
if num_sampling_points > WARN_NUM_SAMPLING_POINTS:
log.warning(
f"Simulation has {num_freqs:.2e} frequencies. Mode solving "
f"is repeated at each frequency, so EME simulations with too many frequencies "
f"can be slower and more expensive than FDTD simulations. "
f"Consider using an 'EMEFreqSweep' instead for a faster approximate solution."
f"Simulation has {num_sampling_points:.2e} frequency sampling points. Mode solving "
f"is repeated at each sampling point, so EME simulations with too many "
f"frequencies can be slower and more expensive than FDTD simulations. "
f"Consider using 'EMEModeSpec.interp_spec' instead for a faster approximate solution."
)

def _validate_monitor_size(self) -> None:
Expand Down Expand Up @@ -934,6 +940,18 @@ def monitors_data_size(self) -> dict[str, float]:
data_size[monitor.name] = storage_size
return data_size

@property
def _num_sampling_points(self) -> int:
"""Max number of sampling freqs in the simulation."""
freqs = set()
for mode_spec in self.eme_grid.mode_specs:
interp_spec = mode_spec.interp_spec
if interp_spec is None:
freqs |= set(self.freqs)
else:
freqs |= set(interp_spec.sampling_points(self.freqs))
return len(freqs)

@property
def _num_sweep(self) -> pd.PositiveInt:
"""Number of sweep indices."""
Expand Down
Loading