Skip to content

Commit 61cebca

Browse files
committed
feat: Adding gaussian monitors
1 parent 7e2fe59 commit 61cebca

File tree

8 files changed

+1228
-56
lines changed

8 files changed

+1228
-56
lines changed

schemas/Simulation.json

Lines changed: 465 additions & 0 deletions
Large diffs are not rendered by default.

schemas/TerminalComponentModeler.json

Lines changed: 465 additions & 0 deletions
Large diffs are not rendered by default.

tests/test_components/test_monitor.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,14 @@ def test_diffraction_validators():
344344
FREQS = np.array([1, 2, 3]) * 1e12
345345

346346

347+
def test_gaussian_overlap_monitors_basic():
348+
g = td.GaussianOverlapMonitor(size=(1, 1, 0), name="g", freqs=FREQS)
349+
a = td.AstigmaticGaussianOverlapMonitor(size=(1, 1, 0), name="a", freqs=FREQS)
350+
for m in (g, a):
351+
s = m.storage_size(num_cells=10, tmesh=[0.0, 1.0])
352+
assert isinstance(s, int) and s > 0
353+
354+
347355
def test_monitor():
348356
size = (1, 2, 3)
349357
center = (1, 2, 3)
@@ -381,10 +389,14 @@ def test_monitor():
381389
m10 = td.PermittivityMonitor(size=size, center=center, freqs=FREQS, name="perm")
382390
m11 = td.AuxFieldTimeMonitor(size=size, center=center, name="aux_field_time", fields=("Nfx",))
383391
m12 = td.MediumMonitor(size=size, center=center, freqs=FREQS, name="mat")
392+
m13 = td.GaussianOverlapMonitor(size=(1, 1, 0), center=center, freqs=FREQS, name="gauss")
393+
m14 = td.AstigmaticGaussianOverlapMonitor(
394+
size=(1, 1, 0), center=center, freqs=FREQS, name="astigauss"
395+
)
384396

385397
tmesh = np.linspace(0, 1, 10)
386398

387-
for m in [m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m12]:
399+
for m in [m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14]:
388400
m.storage_size(num_cells=100, tmesh=tmesh)
389401

390402
for m in [m2, m4]:

tests/test_data/test_monitor_data.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
DiffractionData,
1919
DirectivityData,
2020
FieldData,
21+
FieldOverlapData,
2122
FieldTimeData,
2223
FluxData,
2324
FluxTimeData,
@@ -223,6 +224,16 @@ def make_mode_data():
223224
return ModeData(monitor=MODE_MONITOR, amps=AMPS.copy(), n_complex=N_COMPLEX.copy())
224225

225226

227+
def make_field_overlap_data():
228+
monitor = td.GaussianOverlapMonitor(
229+
size=(0, 2, 2),
230+
freqs=[1e14, 1.1e14],
231+
name="gaussian_overlap_monitor",
232+
store_fields_direction="+",
233+
)
234+
return FieldOverlapData(monitor=monitor, amps=AMPS)
235+
236+
226237
def make_flux_data():
227238
return FluxData(monitor=FLUX_MONITOR, flux=FLUX.copy())
228239

@@ -412,6 +423,11 @@ def test_mode_data():
412423
_ = data.k_eff
413424

414425

426+
def test_overlap_data():
427+
data = make_field_overlap_data()
428+
_ = data.amps
429+
430+
415431
def test_flux_data():
416432
data = make_flux_data()
417433
_ = data.flux

tidy3d/__init__.py

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

346346
# monitors
347347
from .components.monitor import (
348+
AstigmaticGaussianOverlapMonitor,
348349
AuxFieldTimeMonitor,
349350
DiffractionMonitor,
350351
DirectivityMonitor,
@@ -356,6 +357,7 @@
356357
FieldTimeMonitor,
357358
FluxMonitor,
358359
FluxTimeMonitor,
360+
GaussianOverlapMonitor,
359361
MediumMonitor,
360362
ModeMonitor,
361363
ModeSolverMonitor,
@@ -492,6 +494,7 @@ def set_logging_level(level: str) -> None:
492494
"ApodizationSpec",
493495
"AstigmaticGaussianBeam",
494496
"AstigmaticGaussianBeamProfile",
497+
"AstigmaticGaussianOverlapMonitor",
495498
"AugerRecombination",
496499
"AutoGrid",
497500
"AutoImpedanceSpec",
@@ -631,6 +634,7 @@ def set_logging_level(level: str) -> None:
631634
"GaussianBeam",
632635
"GaussianBeamProfile",
633636
"GaussianDoping",
637+
"GaussianOverlapMonitor",
634638
"GaussianPulse",
635639
"Geometry",
636640
"GeometryGroup",

tidy3d/components/data/monitor_data.py

Lines changed: 55 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from tidy3d.components.grid.grid import Coords, Grid
2020
from tidy3d.components.medium import Medium, MediumType
2121
from tidy3d.components.monitor import (
22+
AstigmaticGaussianOverlapMonitor,
2223
AuxFieldTimeMonitor,
2324
DiffractionMonitor,
2425
DirectivityMonitor,
@@ -30,6 +31,7 @@
3031
FieldTimeMonitor,
3132
FluxMonitor,
3233
FluxTimeMonitor,
34+
GaussianOverlapMonitor,
3335
MediumMonitor,
3436
ModeMonitor,
3537
ModeSolverMonitor,
@@ -1596,7 +1598,58 @@ class MediumData(MediumDataset, AbstractFieldData):
15961598
)
15971599

15981600

1599-
class ModeData(ModeSolverDataset, ElectromagneticFieldData):
1601+
class AbstractOverlapData(ElectromagneticFieldData):
1602+
amps: ModeAmpsDataArray = pd.Field(
1603+
...,
1604+
title="Amplitudes",
1605+
description="Complex-valued amplitudes of the overlap decomposition.",
1606+
)
1607+
1608+
def normalize(self, source_spectrum_fn) -> AbstractOverlapData:
1609+
"""Return copy of self after normalization is applied using source spectrum function."""
1610+
if self.amps is None:
1611+
return self.copy()
1612+
source_freq_amps = source_spectrum_fn(self.amps.f)[None, :, None]
1613+
new_amps = (self.amps / source_freq_amps).astype(self.amps.dtype)
1614+
return self.copy(update={"amps": new_amps})
1615+
1616+
@property
1617+
def time_reversed_copy(self) -> FieldData:
1618+
"""Make a copy of the data with direction-reversed fields. In lossy or gyrotropic systems,
1619+
the time-reversed fields will not be the same as the backward-propagating modes."""
1620+
1621+
# Time reversal
1622+
new_data = {}
1623+
for comp, field in self.field_components.items():
1624+
if comp[0] == "H":
1625+
new_data[comp] = -np.conj(field)
1626+
else:
1627+
new_data[comp] = np.conj(field)
1628+
1629+
# switch direction in the monitor
1630+
mnt = self.monitor
1631+
new_dir = "+" if mnt.store_fields_direction == "-" else "-"
1632+
update_dict = {"store_fields_direction": new_dir}
1633+
if hasattr(mnt, "direction"):
1634+
update_dict["direction"] = new_dir
1635+
new_data["monitor"] = mnt.updated_copy(**update_dict)
1636+
return self.copy(update=new_data)
1637+
1638+
1639+
class FieldOverlapData(AbstractOverlapData):
1640+
monitor: Union[GaussianOverlapMonitor, AstigmaticGaussianOverlapMonitor] = pd.Field(
1641+
..., title="Monitor", description="Monitor associated with the data."
1642+
)
1643+
1644+
def _make_adjoint_sources(
1645+
self, dataset_names: list[str], fwidth: float
1646+
) -> list[Union[CustomCurrentSource, PointDipole]]:
1647+
"""Converts a :class:`.FieldData` to a list of adjoint current or point sources."""
1648+
1649+
raise NotImplementedError("Could not formulate adjoint source for overlap monitor output.")
1650+
1651+
1652+
class ModeData(ModeSolverDataset, AbstractOverlapData):
16001653
"""
16011654
Data associated with a :class:`.ModeMonitor`: modal amplitudes, propagation indices and mode profiles.
16021655
@@ -1636,11 +1689,7 @@ class ModeData(ModeSolverDataset, ElectromagneticFieldData):
16361689
"""
16371690

16381691
monitor: ModeMonitor = pd.Field(
1639-
..., title="Monitor", description="Mode monitor associated with the data."
1640-
)
1641-
1642-
amps: ModeAmpsDataArray = pd.Field(
1643-
..., title="Amplitudes", description="Complex-valued amplitudes associated with the mode."
1692+
..., title="Monitor", description="Monitor associated with the data."
16441693
)
16451694

16461695
eps_spec: list[EpsSpecType] = pd.Field(
@@ -1662,12 +1711,6 @@ def eps_spec_match_mode_spec(cls, val, values):
16621711
)
16631712
return val
16641713

1665-
def normalize(self, source_spectrum_fn) -> ModeData:
1666-
"""Return copy of self after normalization is applied using source spectrum function."""
1667-
source_freq_amps = source_spectrum_fn(self.amps.f)[None, :, None]
1668-
new_amps = (self.amps / source_freq_amps).astype(self.amps.dtype)
1669-
return self.copy(update={"amps": new_amps})
1670-
16711714
def overlap_sort(
16721715
self,
16731716
track_freq: TrackFreq,
@@ -1965,25 +2008,6 @@ def _group_index_post_process(self, frequency_step: float) -> ModeData:
19652008

19662009
return self.copy(update=update_dict)
19672010

1968-
@property
1969-
def time_reversed_copy(self) -> FieldData:
1970-
"""Make a copy of the data with direction-reversed fields. In lossy or gyrotropic systems,
1971-
the time-reversed fields will not be the same as the backward-propagating modes."""
1972-
1973-
# Time reversal
1974-
new_data = {}
1975-
for comp, field in self.field_components.items():
1976-
if comp[0] == "H":
1977-
new_data[comp] = -np.conj(field)
1978-
else:
1979-
new_data[comp] = np.conj(field)
1980-
1981-
# switch direction in the monitor
1982-
mnt = self.monitor
1983-
new_dir = "+" if mnt.store_fields_direction == "-" else "-"
1984-
new_data["monitor"] = mnt.updated_copy(store_fields_direction=new_dir)
1985-
return self.copy(update=new_data)
1986-
19872011
def _colocated_propagation_axes_field(self, field_name: Literal["E", "H"]) -> DataArray:
19882012
"""Collect a field DataArray containing all 3 field components and rotate from frame
19892013
with normal axis along z to frame with propagation axis along z.
@@ -2262,29 +2286,6 @@ class ModeSolverData(ModeData):
22622286
None, title="Amplitudes", description="Unused for ModeSolverData."
22632287
)
22642288

2265-
def normalize(self, source_spectrum_fn: Callable[[float], complex]) -> ModeSolverData:
2266-
"""Return copy of self after normalization is applied using source spectrum function."""
2267-
return self.copy()
2268-
2269-
@property
2270-
def time_reversed_copy(self) -> FieldData:
2271-
"""Make a copy of the data with direction-reversed fields. In lossy or gyrotropic systems,
2272-
the time-reversed fields will not be the same as the backward-propagating modes."""
2273-
2274-
# Time reversal
2275-
new_data = {}
2276-
for comp, field in self.field_components.items():
2277-
if comp[0] == "H":
2278-
new_data[comp] = -np.conj(field)
2279-
else:
2280-
new_data[comp] = np.conj(field)
2281-
2282-
# switch direction in the monitor
2283-
mnt = self.monitor
2284-
new_dir = "+" if mnt.store_fields_direction == "-" else "-"
2285-
new_data["monitor"] = mnt.updated_copy(direction=new_dir, store_fields_direction=new_dir)
2286-
return self.copy(update=new_data)
2287-
22882289
def _check_fields_stored(self, components: list[str]):
22892290
"""Check that all requested field components are stored in the data."""
22902291
missing_comps = [comp for comp in components if comp not in self.field_components.keys()]

0 commit comments

Comments
 (0)