Skip to content

Surface monitors #2360

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
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
15 changes: 15 additions & 0 deletions tidy3d/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@
FluxTimeDataArray,
HeatDataArray,
IndexedDataArray,
IndexedFieldDataArray,
IndexedFieldTimeDataArray,
IndexedFreqDataArray,
IndexedTimeDataArray,
IndexedVoltageDataArray,
ModeAmpsDataArray,
ModeIndexDataArray,
Expand Down Expand Up @@ -158,6 +162,7 @@
PermittivityData,
)
from .components.data.sim_data import DATA_TYPE_MAP, SimulationData
from .components.data.unstructured.surface import TriangularSurfaceDataset
from .components.data.utils import (
TetrahedralGridDataset,
TriangularGridDataset,
Expand Down Expand Up @@ -284,6 +289,8 @@
ModeSolverMonitor,
Monitor,
PermittivityMonitor,
SurfaceFieldMonitor,
SurfaceFieldTimeMonitor,
)
from .components.parameter_perturbation import (
CustomChargePerturbation,
Expand Down Expand Up @@ -625,9 +632,14 @@ def set_logging_level(level: str) -> None:
"CellDataArray",
"IndexedDataArray",
"IndexedVoltageDataArray",
"IndexedFieldDataArray",
"IndexedFieldTimeDataArray",
"IndexedFreqDataArray",
"IndexedTimeDataArray",
"SteadyVoltageDataArray",
"TriangularGridDataset",
"TetrahedralGridDataset",
"TriangularSurfaceDataset",
"medium_from_nk",
"SubpixelSpec",
"Staircasing",
Expand Down Expand Up @@ -676,4 +688,7 @@ def set_logging_level(level: str) -> None:
"IsothermalSteadyChargeDCAnalysis",
"ChargeToleranceSpec",
"AntennaMetricsData",
"SurfaceFieldMonitor",
"SurfaceFieldTimeMonitor",
"TriangularSurfaceDataset",
]
74 changes: 73 additions & 1 deletion tidy3d/components/data/data_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -1253,6 +1253,66 @@ class SpatialVoltageDataArray(AbstractSpatialDataArray):
_dims = ("x", "y", "z", "voltage")


class IndexedFieldDataArray(DataArray):
"""Stores indexed values of vector fields in frequency domain. It is typically used
in conjuction with a ``PointDataArray`` to store point-associated vector data.

Example
-------
>>> indexed_array = IndexedFieldDataArray(
... (1+1j) * np.random.random((4,3,1)), coords=dict(index=np.arange(4), axis=np.arange(3), f=[1e9])
... )
"""

__slots__ = ()
_dims = ("index", "axis", "f")


class IndexedFieldTimeDataArray(DataArray):
"""Stores indexed values of vector fields in time domain. It is typically used
in conjuction with a ``PointDataArray`` to store point-associated vector data.

Example
-------
>>> indexed_array = IndexedFieldDataArray(
... (1+1j) * np.random.random((4,3,1)), coords=dict(index=np.arange(4), axis=np.arange(3), t=[0])
... )
"""

__slots__ = ()
_dims = ("index", "axis", "t")


class IndexedFreqDataArray(DataArray):
"""Stores indexed values of scalar fields in frequency domain. It is typically used
in conjuction with a ``PointDataArray`` to store point-associated vector data.

Example
-------
>>> indexed_array = IndexedFieldDataArray(
... (1+1j) * np.random.random((4,1)), coords=dict(index=np.arange(4), f=[1e9])
... )
"""

__slots__ = ()
_dims = ("index", "f")


class IndexedTimeDataArray(DataArray):
"""Stores indexed values of scalar fields in time domain. It is typically used
in conjuction with a ``PointDataArray`` to store point-associated vector data.

Example
-------
>>> indexed_array = IndexedFieldDataArray(
... (1+1j) * np.random.random((4,1)), coords=dict(index=np.arange(4), t=[0])
... )
"""

__slots__ = ()
_dims = ("index", "t")


DATA_ARRAY_TYPES = [
SpatialDataArray,
ScalarFieldDataArray,
Expand Down Expand Up @@ -1286,7 +1346,19 @@ class SpatialVoltageDataArray(AbstractSpatialDataArray):
CellDataArray,
IndexedDataArray,
IndexedVoltageDataArray,
IndexedFieldDataArray,
IndexedFieldTimeDataArray,
IndexedFreqDataArray,
IndexedTimeDataArray,
]
DATA_ARRAY_MAP = {data_array.__name__: data_array for data_array in DATA_ARRAY_TYPES}

IndexedDataArrayTypes = Union[IndexedDataArray, IndexedVoltageDataArray]
IndexedDataArrayTypes = Union[
IndexedDataArray,
IndexedVoltageDataArray,
IndexedFieldDataArray,
IndexedFieldTimeDataArray,
IndexedFreqDataArray,
IndexedTimeDataArray,
PointDataArray,
]
73 changes: 72 additions & 1 deletion tidy3d/components/data/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Any, Callable, Dict, Optional, Union
from typing import Any, Callable, Dict, Optional, Tuple, Union

import numpy as np
import pydantic.v1 as pd
Expand All @@ -28,6 +28,7 @@
TimeDataArray,
TriangleMeshDataArray,
)
from .unstructured.surface import TriangularSurfaceDataset

DEFAULT_MAX_SAMPLES_PER_STEP = 10_000
DEFAULT_MAX_CELLS_PER_STEP = 10_000
Expand Down Expand Up @@ -394,6 +395,76 @@ class AuxFieldTimeDataset(AuxFieldDataset):
)


class ElectromagneticSurfaceFieldDataset(AbstractFieldDataset, ABC):
"""Stores a collection of E and H fields with x, y, z components."""

E: Tuple[Optional[TriangularSurfaceDataset], Optional[TriangularSurfaceDataset]] = pd.Field(
(None, None),
title="E",
description="Spatial distribution of the electric field on the internal and external sides of the surface.",
)

H: Tuple[Optional[TriangularSurfaceDataset], Optional[TriangularSurfaceDataset]] = pd.Field(
(None, None),
title="H",
description="Spatial distribution of the magnetic field on the internal and external sides of the surface.",
)

normal: TriangularSurfaceDataset = pd.Field(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please clarify if this points outwards for closed surfaces. Also, will this work for an open surface (2D PEC)?

None,
title="Surface Normal",
description="Spatial distribution of the surface normal.",
)

@property
def field_components(self) -> Dict[str, DataArray]:
"""Maps the field components to their associated data."""
fields = {
"E": self.E,
"H": self.H,
}
return {field_name: field for field_name, field in fields.items() if field is not None}

@property
def current_density(self) -> ElectromagneticSurfaceFieldDataset:
"""Surface current density."""

h_diff = 0
template = None
# we assume that is data is None it means field is zero on that side (e.g. PEC)
if self.H[0] is not None:
h_diff += self.H[0].values
template = self.H[0]
if self.H[1] is not None:
h_diff -= self.H[1].values
template = self.H[1]

if template is None:
raise ValueError(
"Could not calculate current density: the dataset does not contain H field information."
)

return template.updated_copy(values=xr.cross(h_diff, self.normal.values, dim="axis"))

@property
def grid_locations(self) -> Dict[str, str]:
"""Maps field components to the string key of their grid locations on the yee lattice."""
raise RuntimeError("Function 'grid_location' does not apply to surface monitors.")

@property
def symmetry_eigenvalues(self) -> Dict[str, Callable[[Axis], float]]:
"""Maps field components to their (positive) symmetry eigenvalues."""

return dict(
Ex=lambda dim: -1 if (dim == 0) else +1,
Ey=lambda dim: -1 if (dim == 1) else +1,
Ez=lambda dim: -1 if (dim == 2) else +1,
Hx=lambda dim: +1 if (dim == 0) else -1,
Hy=lambda dim: +1 if (dim == 1) else -1,
Hz=lambda dim: +1 if (dim == 2) else -1,
)


class ModeSolverDataset(ElectromagneticFieldDataset):
"""Dataset storing scalar components of E and H fields as a function of freq. and mode_index.

Expand Down
Loading
Loading