Skip to content

Commit 54f45d3

Browse files
committed
feat: add EMESimulationData.coeffs (FXC-4275)
1 parent 603c8a8 commit 54f45d3

File tree

9 files changed

+276
-11
lines changed

9 files changed

+276
-11
lines changed

CHANGELOG.md

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

1010
### Added
1111
- Added `symmetrize_mirror`, `symmetrize_rotation`, `symmetrize_diagonal` functions to the autograd plugin. They can be used for enforcing symmetries in topology optimization.
12+
- Added `EMESimulationData.coeffs` to store coefficients from the EME solver, including interface S matrices and effective propagation indices.
1213

1314
### Changed
1415
- Removed validator that would warn if `PerturbationMedium` values could become numerically unstable, since an error will anyway be raised if this actually happens when the medium is converted using actual perturbation data.

schemas/EMESimulation.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12879,6 +12879,10 @@
1287912879
},
1288012880
"type": "array"
1288112881
},
12882+
"store_coeffs": {
12883+
"default": true,
12884+
"type": "boolean"
12885+
},
1288212886
"store_port_modes": {
1288312887
"default": true,
1288412888
"type": "boolean"

tests/sims/full_fdtd.h5

0 Bytes
Binary file not shown.

tests/test_components/test_eme.py

Lines changed: 100 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -311,16 +311,16 @@ def test_eme_simulation():
311311

312312
# test warning for not providing wavelength in autogrid
313313
grid_spec = td.GridSpec.auto(min_steps_per_wvl=20)
314+
sim = sim.updated_copy(grid_spec=grid_spec)
314315
with AssertLogLevel("INFO", contains_str="wavelength"):
315-
sim = sim.updated_copy(grid_spec=grid_spec)
316+
_ = sim.updated_copy(monitors=[])
316317
# multiple freqs are ok, but not for autogrid
317318
_ = sim.updated_copy(
318319
grid_spec=td.GridSpec.uniform(dl=0.2), freqs=[10000000000.0, *list(sim.freqs)]
319320
)
320321
with AssertLogLevel("INFO", contains_str="wavelength"):
321322
_ = sim.updated_copy(
322-
freqs=[*list(sim.freqs), 10000000000.0],
323-
grid_spec=grid_spec,
323+
freqs=[*list(sim.freqs), 10000000000.0], grid_spec=grid_spec, monitors=[]
324324
)
325325

326326
# test port offsets
@@ -558,12 +558,14 @@ def test_eme_simulation():
558558
constraint="passive",
559559
eme_grid_spec=td.EMEUniformGrid(num_cells=1, mode_spec=td.EMEModeSpec(num_modes=40)),
560560
grid_spec=sim.grid_spec.updated_copy(wavelength=1),
561+
monitors=[],
561562
)
562563
sim_good.validate_pre_upload()
563564
sim_good = sim.updated_copy(
564565
constraint=None,
565566
eme_grid_spec=td.EMEUniformGrid(num_cells=1, mode_spec=td.EMEModeSpec(num_modes=60)),
566567
grid_spec=sim.grid_spec.updated_copy(wavelength=1),
568+
monitors=[],
567569
)
568570
sim_good.validate_pre_upload()
569571
# warn about num modes with constraint
@@ -731,6 +733,47 @@ def _get_eme_smatrix_data_array(num_modes_in=2, num_modes_out=3, num_freqs=2, nu
731733
return smatrix_entry
732734

733735

736+
def _get_eme_interface_smatrix_data_array(
737+
num_modes_in=2, num_modes_out=3, num_freqs=2, num_sweep=0
738+
):
739+
if num_modes_in != 0:
740+
mode_index_in = np.arange(num_modes_in)
741+
else:
742+
mode_index_in = [0]
743+
if num_modes_out != 0:
744+
mode_index_out = np.arange(num_modes_out)
745+
else:
746+
mode_index_out = [0]
747+
if num_sweep != 0:
748+
sweep_index = np.arange(num_sweep)
749+
else:
750+
sweep_index = [0]
751+
eme_cell_index = np.arange(3)
752+
753+
f = td.C_0 * np.linspace(1, 2, num_freqs)
754+
755+
data = (1 + 1j) * np.random.random(
756+
(len(f), len(mode_index_out), len(mode_index_in), len(sweep_index), len(eme_cell_index))
757+
)
758+
coords = {
759+
"f": f,
760+
"mode_index_out": mode_index_out,
761+
"mode_index_in": mode_index_in,
762+
"sweep_index": sweep_index,
763+
"eme_cell_index": eme_cell_index,
764+
}
765+
smatrix_entry = td.EMEInterfaceSMatrixDataArray(data, coords=coords)
766+
767+
if num_modes_in == 0:
768+
smatrix_entry = smatrix_entry.drop_vars("mode_index_in")
769+
if num_modes_out == 0:
770+
smatrix_entry = smatrix_entry.drop_vars("mode_index_out")
771+
if num_sweep == 0:
772+
smatrix_entry = smatrix_entry.drop_vars("sweep_index")
773+
774+
return smatrix_entry
775+
776+
734777
def _get_eme_smatrix_dataset(num_modes_1=3, num_modes_2=4, num_sweep=0):
735778
S11 = _get_eme_smatrix_data_array(
736779
num_modes_in=num_modes_1, num_modes_out=num_modes_1, num_sweep=num_sweep
@@ -747,6 +790,22 @@ def _get_eme_smatrix_dataset(num_modes_1=3, num_modes_2=4, num_sweep=0):
747790
return td.EMESMatrixDataset(S11=S11, S12=S12, S21=S21, S22=S22)
748791

749792

793+
def _get_eme_interface_smatrix_dataset(num_modes_1=3, num_modes_2=4, num_sweep=0):
794+
S11 = _get_eme_interface_smatrix_data_array(
795+
num_modes_in=num_modes_1, num_modes_out=num_modes_1, num_sweep=num_sweep
796+
)
797+
S12 = _get_eme_interface_smatrix_data_array(
798+
num_modes_in=num_modes_2, num_modes_out=num_modes_1, num_sweep=num_sweep
799+
)
800+
S21 = _get_eme_interface_smatrix_data_array(
801+
num_modes_in=num_modes_1, num_modes_out=num_modes_2, num_sweep=num_sweep
802+
)
803+
S22 = _get_eme_interface_smatrix_data_array(
804+
num_modes_in=num_modes_2, num_modes_out=num_modes_2, num_sweep=num_sweep
805+
)
806+
return td.EMEInterfaceSMatrixDataset(S11=S11, S12=S12, S21=S21, S22=S22)
807+
808+
750809
def _get_eme_coeff_data_array(num_sweep=0):
751810
f = [2e14]
752811
mode_index_out = [0, 1]
@@ -787,7 +846,20 @@ def _get_eme_coeff_data_array(num_sweep=0):
787846
def _get_eme_coeff_dataset(num_sweep=0):
788847
A = _get_eme_coeff_data_array(num_sweep=num_sweep)
789848
B = _get_eme_coeff_data_array(num_sweep=num_sweep)
790-
return td.EMECoefficientDataset(A=A, B=B)
849+
flux = _get_eme_flux_data_array(num_sweep=num_sweep)
850+
n_complex = _get_eme_mode_index_data_array(num_sweep=num_sweep)
851+
interface_smatrices = _get_eme_interface_smatrix_dataset(num_sweep=num_sweep)
852+
return td.EMECoefficientDataset(
853+
A=A, B=B, flux=flux, n_complex=n_complex, interface_smatrices=interface_smatrices
854+
)
855+
856+
857+
def test_eme_normalize_coeff_dataset():
858+
coeffs = _get_eme_coeff_dataset()
859+
coeffs_normalized = coeffs.normalized_copy
860+
assert coeffs_normalized.flux is None
861+
with pytest.raises(ValidationError):
862+
_ = coeffs_normalized.normalized_copy
791863

792864

793865
def test_eme_coeff_data_array():
@@ -819,6 +891,29 @@ def _get_eme_mode_index_data_array(num_sweep=0):
819891
return data
820892

821893

894+
def _get_eme_flux_data_array(num_sweep=0):
895+
f = [td.C_0, 3e14]
896+
mode_index = np.arange(10)
897+
eme_cell_index = np.arange(7)
898+
if num_sweep != 0:
899+
sweep_index = np.arange(num_sweep)
900+
else:
901+
sweep_index = [0]
902+
coords = {
903+
"f": f,
904+
"sweep_index": sweep_index,
905+
"eme_cell_index": eme_cell_index,
906+
"mode_index": mode_index,
907+
}
908+
data = td.EMEModeIndexDataArray(
909+
np.random.random((len(f), len(sweep_index), len(eme_cell_index), len(mode_index))),
910+
coords=coords,
911+
)
912+
if num_sweep == 0:
913+
data = data.drop_vars("sweep_index")
914+
return data
915+
916+
822917
def test_eme_mode_index_data_array():
823918
_ = _get_eme_mode_index_data_array()
824919

@@ -1305,7 +1400,7 @@ def test_eme_periodicity():
13051400

13061401
# remove the field monitor, now it passes
13071402
desired_cell_index_pairs = set([(i, i + 1) for i in range(6)] + [(5, 1)])
1308-
with AssertLogLevel(None):
1403+
with AssertLogLevel("WARNING", contains_str="deprecated"):
13091404
sim = sim.updated_copy(
13101405
monitors=[m for m in sim.monitors if not isinstance(m, td.EMEFieldMonitor)]
13111406
)

tidy3d/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@
185185
ChargeDataArray,
186186
DiffractionDataArray,
187187
EMECoefficientDataArray,
188+
EMEFluxDataArray,
189+
EMEInterfaceSMatrixDataArray,
188190
EMEModeIndexDataArray,
189191
EMEScalarFieldDataArray,
190192
EMEScalarModeFieldDataArray,
@@ -243,6 +245,7 @@
243245
from .components.eme.data.dataset import (
244246
EMECoefficientDataset,
245247
EMEFieldDataset,
248+
EMEInterfaceSMatrixDataset,
246249
EMEModeSolverDataset,
247250
EMESMatrixDataset,
248251
)
@@ -605,8 +608,11 @@ def set_logging_level(level: str) -> None:
605608
"EMEFieldData",
606609
"EMEFieldDataset",
607610
"EMEFieldMonitor",
611+
"EMEFluxDataArray",
608612
"EMEFreqSweep",
609613
"EMEGrid",
614+
"EMEInterfaceSMatrixDataArray",
615+
"EMEInterfaceSMatrixDataset",
610616
"EMELengthSweep",
611617
"EMEModeIndexDataArray",
612618
"EMEModeSolverData",

tidy3d/components/data/data_array.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,6 +1188,31 @@ class EMESMatrixDataArray(DataArray):
11881188
_data_attrs = {"long_name": "scattering matrix element"}
11891189

11901190

1191+
class EMEInterfaceSMatrixDataArray(DataArray):
1192+
"""Scattering matrix elements at a single cell interface for a fixed pair of ports,
1193+
possibly with an extra sweep index.
1194+
Example
1195+
-------
1196+
>>> mode_index_in = [0, 1]
1197+
>>> mode_index_out = [0, 1, 2]
1198+
>>> eme_cell_index = [2, 4]
1199+
>>> f = [2e14]
1200+
>>> sweep_index = np.arange(10)
1201+
>>> coords = dict(
1202+
... f=f,
1203+
... sweep_index=sweep_index,
1204+
... eme_cell_index=eme_cell_index,
1205+
... mode_index_out=mode_index_out,
1206+
... mode_index_in=mode_index_in,
1207+
... )
1208+
>>> fd = EMEInterfaceSMatrixDataArray((1 + 1j) * np.random.random((1, 10, 2, 3, 2)), coords=coords)
1209+
"""
1210+
1211+
__slots__ = ()
1212+
_dims = ("f", "sweep_index", "eme_cell_index", "mode_index_out", "mode_index_in")
1213+
_data_attrs = {"long_name": "scattering matrix element"}
1214+
1215+
11911216
class EMEModeIndexDataArray(DataArray):
11921217
"""Complex-valued effective propagation index of an EME mode,
11931218
also indexed by EME cell.
@@ -1206,6 +1231,24 @@ class EMEModeIndexDataArray(DataArray):
12061231
_data_attrs = {"long_name": "Propagation index"}
12071232

12081233

1234+
class EMEFluxDataArray(DataArray):
1235+
"""Power flux of an EME mode, also indexed by EME cell.
1236+
1237+
Example
1238+
-------
1239+
>>> f = [2e14, 3e14]
1240+
>>> sweep_index = np.arange(2)
1241+
>>> eme_cell_index = np.arange(5)
1242+
>>> mode_index = np.arange(4)
1243+
>>> coords = dict(f=f, sweep_index=sweep_index, eme_cell_index=eme_cell_index, mode_index=mode_index)
1244+
>>> data = EMEFluxDataArray(np.random.random((2,2,5,4)), coords=coords)
1245+
"""
1246+
1247+
__slots__ = ()
1248+
_dims = ("f", "sweep_index", "eme_cell_index", "mode_index")
1249+
_data_attrs = {"units": WATT, "long_name": "flux"}
1250+
1251+
12091252
class ChargeDataArray(DataArray):
12101253
"""Charge data array.
12111254
@@ -1595,8 +1638,10 @@ def _make_impedance_data_array(result: DataArray) -> ImpedanceResultType:
15951638
EMEScalarFieldDataArray,
15961639
EMEScalarModeFieldDataArray,
15971640
EMESMatrixDataArray,
1641+
EMEInterfaceSMatrixDataArray,
15981642
EMECoefficientDataArray,
15991643
EMEModeIndexDataArray,
1644+
EMEFluxDataArray,
16001645
EMEFreqModeDataArray,
16011646
ChargeDataArray,
16021647
SteadyVoltageDataArray,

0 commit comments

Comments
 (0)