Skip to content

Commit 0792e87

Browse files
committed
Add aux field monitors
1 parent 42afa41 commit 0792e87

14 files changed

+288
-2
lines changed

CHANGELOG.md

+1
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
### Added
1111
- New `LobeMeasurer` tool in the `microwave` plugin that locates lobes in antenna patterns and calculates lobe measures like half-power beamwidth and sidelobe level.
1212
- Validation step that raises a `ValueError` when no frequency-domain monitors are present, preventing invalid adjoint runs.
13+
- Monitor `AuxFieldTimeMonitor` for aux fields like the free carrier density in `TwoPhotonAbsorption`.
1314

1415
### Changed
1516

tests/test_components/test_medium.py

+23
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,29 @@ def test_nonlinear_medium():
769769
)
770770
_ = sim.updated_copy(medium=med, path="structures/0")
771771

772+
grid_spec = td.GridSpec.auto(min_steps_per_wvl=10, wavelength=1)
773+
sim = sim.updated_copy(grid_spec=grid_spec)
774+
aux_fields = ("Nfz",)
775+
with AssertLogLevel(None):
776+
med = td.Medium(
777+
nonlinear_spec=td.NonlinearSpec(models=[td.TwoPhotonAbsorption(beta=1, tau=1)])
778+
)
779+
monitor = td.AuxFieldTimeMonitor(
780+
interval=1, size=(0, 0, 0), name="aux_field_time", fields=aux_fields
781+
)
782+
sim = sim.updated_copy(medium=med, path="structures/0")
783+
sim = sim.updated_copy(monitors=[monitor])
784+
785+
with AssertLogLevel("WARNING", contains_str="stores field"):
786+
med = td.Medium(
787+
nonlinear_spec=td.NonlinearSpec(models=[td.TwoPhotonAbsorption(beta=1, tau=0)])
788+
)
789+
_ = sim.updated_copy(medium=med, path="structures/0")
790+
791+
with AssertLogLevel("WARNING", contains_str="stores field"):
792+
med = td.Medium(nonlinear_spec=td.NonlinearSpec(models=[td.KerrNonlinearity(n2=1)]))
793+
_ = sim.updated_copy(medium=med, path="structures/0")
794+
772795

773796
def test_custom_medium():
774797
Nx, Ny, Nz, Nf = 4, 3, 1, 1

tests/test_components/test_monitor.py

+1
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,7 @@ def test_monitor():
376376
name="directivity",
377377
)
378378
m10 = td.PermittivityMonitor(size=size, center=center, freqs=FREQS, name="perm")
379+
m11 = td.AuxFieldTimeMonitor(size=size, center=center, name="aux_field_time", fields=("Nfx",))
379380

380381
tmesh = np.linspace(0, 1, 10)
381382

tests/test_data/test_data_arrays.py

+5
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
),
3131
]
3232
FIELDS = ("Ex", "Ey", "Ez", "Hx", "Hz")
33+
AUX_FIELDS = ("Nfz",)
3334
INTERVAL = 2
3435
ORDERS_X = list(range(-1, 2))
3536
ORDERS_Y = list(range(-2, 3))
@@ -51,6 +52,9 @@
5152
FIELD_TIME_MONITOR = td.FieldTimeMonitor(
5253
size=SIZE_3D, fields=FIELDS, name="field_time", interval=INTERVAL
5354
)
55+
AUX_FIELD_TIME_MONITOR = td.AuxFieldTimeMonitor(
56+
size=SIZE_3D, fields=AUX_FIELDS, name="aux_field_time", interval=INTERVAL
57+
)
5458
FIELD_MONITOR_2D = td.FieldMonitor(size=SIZE_2D, fields=FIELDS, name="field_2d", freqs=FREQS)
5559
FIELD_TIME_MONITOR_2D = td.FieldTimeMonitor(
5660
size=SIZE_2D, fields=FIELDS, name="field_time_2d", interval=INTERVAL
@@ -80,6 +84,7 @@
8084
MONITORS = [
8185
FIELD_MONITOR,
8286
FIELD_TIME_MONITOR,
87+
AUX_FIELD_TIME_MONITOR,
8388
MODE_MONITOR_WITH_FIELDS,
8489
PERMITTIVITY_MONITOR,
8590
MODE_MONITOR,

tests/test_data/test_monitor_data.py

+13
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
FreqModeDataArray,
1212
)
1313
from tidy3d.components.data.monitor_data import (
14+
AuxFieldTimeData,
1415
DiffractionData,
1516
DirectivityData,
1617
FieldData,
@@ -24,6 +25,7 @@
2425

2526
from ..utils import AssertLogLevel
2627
from .test_data_arrays import (
28+
AUX_FIELD_TIME_MONITOR,
2729
DIFFRACTION_MONITOR,
2830
DIRECTIVITY_MONITOR,
2931
FIELD_MONITOR,
@@ -120,6 +122,17 @@ def make_field_time_data_2d(symmetry: bool = True):
120122
)
121123

122124

125+
def make_aux_field_time_data(symmetry: bool = True):
126+
sim = SIM_SYM if symmetry else SIM
127+
return AuxFieldTimeData(
128+
monitor=AUX_FIELD_TIME_MONITOR,
129+
Nfz=make_scalar_field_time_data_array("Ez", symmetry),
130+
symmetry=sim.symmetry,
131+
symmetry_center=sim.center,
132+
grid_expanded=sim.discretize_monitor(AUX_FIELD_TIME_MONITOR),
133+
)
134+
135+
123136
def make_mode_solver_data():
124137
mode_data = ModeData(
125138
monitor=MODE_MONITOR_WITH_FIELDS,

tests/test_data/test_sim_data.py

+5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from ..utils import get_nested_shape
1616
from .test_data_arrays import FIELD_MONITOR, SIM, SIM_SYM
1717
from .test_monitor_data import (
18+
make_aux_field_time_data,
1819
make_diffraction_data,
1920
make_directivity_data,
2021
make_field_data,
@@ -32,6 +33,8 @@
3233
FIELD = make_field_data(symmetry=False)
3334
FIELD_TIME_SYM = make_field_time_data()
3435
FIELD_TIME = make_field_time_data(symmetry=False)
36+
AUX_FIELD_TIME_SYM = make_aux_field_time_data()
37+
AUX_FIELD_TIME = make_aux_field_time_data(symmetry=False)
3538
PERMITTIVITY_SYM = make_permittivity_data()
3639
PERMITTIVITY = make_permittivity_data(symmetry=False)
3740
MODE = make_mode_data()
@@ -45,6 +48,7 @@
4548
MONITOR_DATA = (
4649
FIELD,
4750
FIELD_TIME,
51+
AUX_FIELD_TIME,
4852
MODE_SOLVER,
4953
PERMITTIVITY,
5054
MODE,
@@ -56,6 +60,7 @@
5660
MONITOR_DATA_SYM = (
5761
FIELD_SYM,
5862
FIELD_TIME_SYM,
63+
AUX_FIELD_TIME_SYM,
5964
MODE_SOLVER,
6065
PERMITTIVITY_SYM,
6166
MODE,

tests/utils.py

+3
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,9 @@ def make_custom_data(lims, unstructured):
780780
size=(0, 0, 0), center=(0, 0, 0), fields=["Ex"], freqs=[1.5e14, 2e14], name="field"
781781
),
782782
td.FieldTimeMonitor(size=(0, 0, 0), center=(0, 0, 0), name="field_time", interval=100),
783+
td.AuxFieldTimeMonitor(
784+
size=(0, 0, 0), center=(0, 0, 0), fields=("Nfx",), name="aux_field_time", interval=100
785+
),
783786
td.FluxMonitor(size=(1, 1, 0), center=(0, 0, 0), freqs=[2e14, 2.5e14], name="flux"),
784787
td.FluxTimeMonitor(size=(1, 1, 0), center=(0, 0, 0), name="flux_time"),
785788
td.PermittivityMonitor(size=(1, 1, 0.1), name="eps", freqs=[1e14]),

tidy3d/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@
267267

268268
# monitors
269269
from .components.monitor import (
270+
AuxFieldTimeMonitor,
270271
DiffractionMonitor,
271272
DirectivityMonitor,
272273
FieldMonitor,
@@ -460,6 +461,7 @@ def set_logging_level(level: str) -> None:
460461
"PlaneWaveBeamProfile",
461462
"FieldMonitor",
462463
"FieldTimeMonitor",
464+
"AuxFieldTimeMonitor",
463465
"FluxMonitor",
464466
"FluxTimeMonitor",
465467
"ModeMonitor",
@@ -496,6 +498,7 @@ def set_logging_level(level: str) -> None:
496498
"ModeSolverDataset",
497499
"FieldData",
498500
"FieldTimeData",
501+
"AuxFieldTimeData",
499502
"PermittivityData",
500503
"FluxData",
501504
"FluxTimeData",

tidy3d/components/data/dataset.py

+82
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,88 @@ def apply_phase(self, phase: float) -> AbstractFieldDataset:
312312
return self
313313

314314

315+
class AuxFieldDataset(AbstractFieldDataset, ABC):
316+
"""Stores a collection of aux fields with x, y, z components."""
317+
318+
Nfx: EMScalarFieldType = pd.Field(
319+
None,
320+
title="Nfx",
321+
description="Spatial distribution of the free carrier density for "
322+
"polarization in the x-direction.",
323+
)
324+
Nfy: EMScalarFieldType = pd.Field(
325+
None,
326+
title="Nfy",
327+
description="Spatial distribution of the free carrier density for "
328+
"polarization in the y-direction.",
329+
)
330+
Nfz: EMScalarFieldType = pd.Field(
331+
None,
332+
title="Nfz",
333+
description="Spatial distribution of the free carrier density for "
334+
"polarization in the z-direction.",
335+
)
336+
337+
@property
338+
def field_components(self) -> Dict[str, DataArray]:
339+
"""Maps the field components to their associated data."""
340+
fields = {
341+
"Nfx": self.Nfx,
342+
"Nfy": self.Nfy,
343+
"Nfz": self.Nfz,
344+
}
345+
return {field_name: field for field_name, field in fields.items() if field is not None}
346+
347+
@property
348+
def grid_locations(self) -> Dict[str, str]:
349+
"""Maps field components to the string key of their grid locations on the yee lattice."""
350+
return dict(Nfx="Ex", Nfy="Ey", Nfz="Ez")
351+
352+
@property
353+
def symmetry_eigenvalues(self) -> Dict[str, Callable[[Axis], float]]:
354+
"""Maps field components to their (positive) symmetry eigenvalues."""
355+
356+
return dict(
357+
Nfx=lambda dim: +1,
358+
Nfy=lambda dim: +1,
359+
Nfz=lambda dim: +1,
360+
)
361+
362+
363+
class AuxFieldTimeDataset(AuxFieldDataset):
364+
"""Dataset storing a collection of the scalar components of aux fields in the time domain
365+
366+
Example
367+
-------
368+
>>> x = [-1,1]
369+
>>> y = [-2,0,2]
370+
>>> z = [-3,-1,1,3]
371+
>>> t = [0, 1e-12, 2e-12]
372+
>>> coords = dict(x=x, y=y, z=z, t=t)
373+
>>> scalar_field = ScalarFieldTimeDataArray(np.random.random((2,3,4,3)), coords=coords)
374+
>>> data = AuxFieldTimeDataset(Nfx=scalar_field)
375+
"""
376+
377+
Nfx: ScalarFieldTimeDataArray = pd.Field(
378+
None,
379+
title="Nfx",
380+
description="Spatial distribution of the free carrier density for polarization "
381+
"in the x-direction.",
382+
)
383+
Nfy: ScalarFieldTimeDataArray = pd.Field(
384+
None,
385+
title="Nfy",
386+
description="Spatial distribution of the free carrier density for polarization "
387+
"in the y-direction.",
388+
)
389+
Nfz: ScalarFieldTimeDataArray = pd.Field(
390+
None,
391+
title="Nfz",
392+
description="Spatial distribution of the free carrier density for polarization "
393+
"in the z-direction.",
394+
)
395+
396+
315397
class ModeSolverDataset(ElectromagneticFieldDataset):
316398
"""Dataset storing scalar components of E and H fields as a function of freq. and mode_index.
317399

tidy3d/components/data/monitor_data.py

+41-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from ..grid.grid import Coords, Grid
2222
from ..medium import Medium, MediumType
2323
from ..monitor import (
24+
AuxFieldTimeMonitor,
2425
DiffractionMonitor,
2526
DirectivityMonitor,
2627
FieldMonitor,
@@ -85,6 +86,7 @@
8586
)
8687
from .dataset import (
8788
AbstractFieldDataset,
89+
AuxFieldTimeDataset,
8890
Dataset,
8991
ElectromagneticFieldDataset,
9092
FieldDataset,
@@ -196,7 +198,9 @@ def get_amplitude(x) -> complex:
196198
class AbstractFieldData(MonitorData, AbstractFieldDataset, ABC):
197199
"""Collection of scalar fields with some symmetry properties."""
198200

199-
monitor: Union[FieldMonitor, FieldTimeMonitor, PermittivityMonitor, ModeMonitor]
201+
monitor: Union[
202+
FieldMonitor, FieldTimeMonitor, AuxFieldTimeMonitor, PermittivityMonitor, ModeMonitor
203+
]
200204

201205
symmetry: Tuple[Symmetry, Symmetry, Symmetry] = pd.Field(
202206
(0, 0, 0),
@@ -1328,6 +1332,41 @@ def time_reversed_copy(self) -> FieldTimeData:
13281332
return self.copy(update=new_data)
13291333

13301334

1335+
class AuxFieldTimeData(AuxFieldTimeDataset, AbstractFieldData):
1336+
"""
1337+
Data associated with a :class:`.AuxFieldTimeMonitor`: scalar components of aux fields.
1338+
1339+
Notes
1340+
-----
1341+
1342+
The data is stored as a `DataArray <https://docs.xarray.dev/en/stable/generated/xarray.DataArray.html>`_
1343+
object using the `xarray <https://docs.xarray.dev/en/stable/index.html>`_ package.
1344+
1345+
Example
1346+
-------
1347+
>>> from tidy3d import ScalarFieldTimeDataArray
1348+
>>> x = [-1,1,3]
1349+
>>> y = [-2,0,2,4]
1350+
>>> z = [-3,-1,1,3,5]
1351+
>>> t = [0, 1e-12, 2e-12]
1352+
>>> coords = dict(x=x[:-1], y=y[:-1], z=z[:-1], t=t)
1353+
>>> grid = Grid(boundaries=Coords(x=x, y=y, z=z))
1354+
>>> scalar_field = ScalarFieldTimeDataArray(np.random.random((2,3,4,3)), coords=coords)
1355+
>>> monitor = AuxFieldTimeMonitor(
1356+
... size=(2,4,6), interval=100, name='field', fields=['Nfx'], colocate=True
1357+
... )
1358+
>>> data = AuxFieldTimeData(monitor=monitor, Nfx=scalar_field, grid_expanded=grid)
1359+
"""
1360+
1361+
monitor: AuxFieldTimeMonitor = pd.Field(
1362+
...,
1363+
title="Monitor",
1364+
description="Time-domain auxiliary field monitor associated with the data.",
1365+
)
1366+
1367+
_contains_monitor_fields = enforce_monitor_fields_present()
1368+
1369+
13311370
class PermittivityData(PermittivityDataset, AbstractFieldData):
13321371
"""Data for a :class:`.PermittivityMonitor`: diagonal components of the permittivity tensor.
13331372
@@ -3684,6 +3723,7 @@ def fields_circular_polarization(self) -> xr.Dataset:
36843723
ModeData,
36853724
FluxData,
36863725
FluxTimeData,
3726+
AuxFieldTimeData,
36873727
FieldProjectionKSpaceData,
36883728
FieldProjectionCartesianData,
36893729
FieldProjectionAngleData,

tidy3d/components/medium.py

+20
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,11 @@ def complex_fields(self) -> bool:
260260
"""Whether the model uses complex fields."""
261261
return False
262262

263+
@property
264+
def aux_fields(self) -> List[str]:
265+
"""List of available aux fields in this model."""
266+
return []
267+
263268

264269
class NonlinearSusceptibility(NonlinearModel):
265270
"""Model for an instantaneous nonlinear chi3 susceptibility.
@@ -496,6 +501,13 @@ def complex_fields(self) -> bool:
496501
"""Whether the model uses complex fields."""
497502
return self.use_complex_fields
498503

504+
@property
505+
def aux_fields(self) -> List[str]:
506+
"""List of available aux fields in this model."""
507+
if self.tau == 0:
508+
return []
509+
return ["Nfx", "Nfy", "Nfz"]
510+
499511

500512
class KerrNonlinearity(NonlinearModel):
501513
"""Model for Kerr nonlinearity which gives an intensity-dependent refractive index
@@ -713,6 +725,14 @@ def _hardcode_medium_freqs(
713725
new_models.append(new_model)
714726
return self.updated_copy(models=new_models)
715727

728+
@property
729+
def aux_fields(self) -> List[str]:
730+
"""List of available aux fields in all present models."""
731+
fields = []
732+
for model in self.models:
733+
fields += model.aux_fields
734+
return fields
735+
716736

717737
class AbstractMedium(ABC, Tidy3dBaseModel):
718738
"""A medium within which electromagnetic waves propagate."""

0 commit comments

Comments
 (0)