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: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[MASTER]
extension-pkg-allow-list=pydantic
ignore=material_library.py, plugins
good-names=ax, im, Lx, Ly, Lz, x0, y0, z0, x, y, z, f, t, y1, y2, x1, x2, xs, ys, zs, Ax, Nx, Ny, Nz, dl, rr, E, H, xx, yy, zz, dx, dy, Jx, Jy, Hx, Hy, dz, e, fp, dt, a, c
good-names=ax, im, Lx, Ly, Lz, x0, y0, z0, x, y, z, f, t, y1, y2, x1, x2, xs, ys, zs, Ax, Nx, Ny, Nz, dl, rr, E, H, xx, yy, zz, dx, dy, Jx, Jy, Jz, Ex, Ey, Ez, Mx, My, Mz, Hx, Hy, Hz, dz, e, fp, dt, a, c,

[BASIC]

Expand Down
2 changes: 1 addition & 1 deletion tests/test_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from .utils import clear_tmp


def test_near2far():
def _test_near2far():
"""make sure mode solver runs"""

def rand_data():
Expand Down
4 changes: 3 additions & 1 deletion tidy3d/components/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from .viz import add_ax_if_none
from ..log import DataError

# TODO: add warning if fields didnt fully decay

""" Helper functions """

Expand Down Expand Up @@ -203,7 +204,8 @@ def data(self) -> xr.Dataset:
data_arrays = {name: arr.data for name, arr in self.data_dict.items()}

# make an xarray dataset
return xr.Dataset(data_arrays)
# return xr.Dataset(data_arrays) # datasets are annoying
return data_arrays

def __eq__(self, other):
"""Check for equality against other :class:`CollectionData` object."""
Expand Down
21 changes: 20 additions & 1 deletion tidy3d/components/grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from .base import Tidy3dBaseModel
from .types import Array, Axis

from ..log import SetupError

# data type of one dimensional coordinate array.
Coords1D = Array[float]
Expand Down Expand Up @@ -206,6 +206,25 @@ def yee(self) -> YeeGrid:
yee_h = FieldGrid(**yee_h_kwargs)
return YeeGrid(E=yee_e, H=yee_h)

def __getitem__(self, coord_key: str) -> Coords:
"""quickly get the grid element by grid[key]."""

coord_dict = {
"centers": self.centers,
"sizes": self.sizes,
"boundaries": self.boundaries,
"Ex": self.yee.E.x,
"Ey": self.yee.E.y,
"Ez": self.yee.E.z,
"Hx": self.yee.H.x,
"Hy": self.yee.H.y,
"Hz": self.yee.H.z,
}
if coord_key not in coord_dict:
raise SetupError(f"key {coord_key} not found in grid with {list(coord_dict.keys())} ")

return coord_dict.get(coord_key)

def _yee_e(self, axis: Axis):
"""E field yee lattice sites for axis."""

Expand Down
8 changes: 8 additions & 0 deletions tidy3d/components/medium.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ def ensure_freq_in_range(eps_model: Callable[[float], complex]) -> Callable[[flo

def _eps_model(self, frequency: float) -> complex:
"""New eps_model function."""

# if frequency is none, don't check, return original function
if frequency is None:
return eps_model(self, frequency)

fmin, fmax = self.frequency_range
if np.any(frequency < fmin) or np.any(frequency > fmax):
log.warning(
Expand Down Expand Up @@ -759,11 +764,14 @@ def eps_sigma_to_eps_complex(eps_real: float, sigma: float, freq: float) -> comp
Conductivity.
freq : float
Frequency to evaluate permittivity at (Hz).
If not supplied, returns real part of permittivity (limit as frequency -> infinity.)

Returns
-------
complex
Complex-valued relative permittivity.
"""
if not freq:
return eps_real
omega = 2 * np.pi * freq
return eps_real + 1j * sigma / omega
22 changes: 9 additions & 13 deletions tidy3d/components/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ def set_medium_names(cls, val, values):
# - check PW in homogeneous medium
# - check nonuniform grid covers the whole simulation domain
# - check any structures close to PML (in lambda) without intersecting.
# - check any structures.bounds == simulation.bounds -> warn

""" Accounting """

Expand Down Expand Up @@ -913,14 +914,19 @@ def discretize(self, box: Box) -> Grid:
sub_boundaries = Coords(**sub_cell_boundary_dict)
return Grid(boundaries=sub_boundaries)

def epsilon(self, box: Box, freq: float = None) -> Dict[str, xr.DataArray]:
def epsilon(self, box: Box, coord_key: str, freq: float = None) -> Dict[str, xr.DataArray]:
# pylint:disable=line-too-long
"""Get array of permittivity at volume specified by box and freq

Parameters
----------
box : :class:`Box`
Rectangular geometry specifying where to measure the permittivity.
coord_key : str
Specifies at what part of the grid to return the permittivity at.
Accepted values are ``{'centers', 'boundaries', 'Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz'}``.
The field values (eg. 'Ex')
correspond to the correponding field locations on the yee lattice.
freq : float = None
The frequency to evaluate the mediums at.
If not specified, evaluates at infinite frequency.
Expand Down Expand Up @@ -953,15 +959,5 @@ def make_eps_data(coords: Coords):
return xr.DataArray(eps_array, coords={"x": xs, "y": ys, "z": zs})

# combine all data into dictionary
data_arrays = {
"centers": make_eps_data(sub_grid.centers),
"boundaries": make_eps_data(sub_grid.boundaries),
"Ex": make_eps_data(sub_grid.yee.E.x),
"Ey": make_eps_data(sub_grid.yee.E.y),
"Ez": make_eps_data(sub_grid.yee.E.z),
"Hx": make_eps_data(sub_grid.yee.H.x),
"Hy": make_eps_data(sub_grid.yee.H.y),
"Hz": make_eps_data(sub_grid.yee.H.z),
}

return data_arrays
coords = sub_grid[coord_key]
return make_eps_data(coords)
101 changes: 64 additions & 37 deletions tidy3d/plugins/mode/mode_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
from ...components import Simulation
from ...components import Mode
from ...components import FieldData, ScalarFieldData
from ...components import ModeMonitor, FieldMonitor
from ...components import ModeMonitor
from ...components import ModeSource, GaussianPulse
from ...components import eps_complex_to_nk
from ...components.types import Direction
from ...log import SetupError

from .solver import compute_modes

Expand Down Expand Up @@ -106,23 +106,33 @@ def solve(self, mode: Mode) -> ModeInfo:
"""

# note discretizing, need to make consistent
eps_data = self.simulation.epsilon(self.plane, self.freq)
eps_cross_xx = np.squeeze(eps_data["Ex"].values)
eps_cross_yy = np.squeeze(eps_data["Ey"].values)
eps_cross_zz = np.squeeze(eps_data["Ez"].values)
eps_cross = np.stack((eps_cross_xx, eps_cross_yy, eps_cross_zz))
eps_xx = np.squeeze(self.simulation.epsilon(self.plane, "Ex", self.freq).values)
eps_yy = np.squeeze(self.simulation.epsilon(self.plane, "Ey", self.freq).values)
eps_zz = np.squeeze(self.simulation.epsilon(self.plane, "Ez", self.freq).values)

# swap axes to waveguide coordinates (propagating in z)
normal_axis = self.plane.size.index(0.0)
eps_wg_zz, (eps_wg_xx, eps_wg_yy) = self.plane.pop_axis(
(eps_xx, eps_yy, eps_zz), axis=normal_axis
)

# note: from this point on, in waveguide coordinates (propagating in z)

# construct eps_cross section to feed to mode solver
eps_cross = np.stack((eps_wg_xx, eps_wg_yy, eps_wg_zz))

Nx, Ny = eps_cross_xx.shape
Nx, Ny = eps_cross.shape[1:]
if mode.symmetries[0] != 0:
eps_cross = np.stack(tuple(e[Nx // 2, :] for e in eps_cross))
if mode.symmetries[1] != 0:
eps_cross = np.stack(tuple(e[:, Ny // 2] for e in eps_cross))

num_modes = mode.num_modes if mode.num_modes else mode.mode_index + 1
if num_modes <= mode.mode_index:
log.error(
f"mode index = {mode.mode_index} "
f"is out of bounds for the number of modes specified = {mode.um_modes}."

raise SetupError(
f"Mode.mode_index = {mode.mode_index} "
f"is out of bounds for the number of modes given: Mode.num_modes={mode.um_modes}."
)

# note, internally discretizing, need to make consistent.
Expand All @@ -137,44 +147,60 @@ def solve(self, mode: Mode) -> ModeInfo:
coords=None,
)

# field.shape = (2, 3, Nx, Ny, 1, Nmodes)
# Get fields at the Mode.mode_index
field_values = field[..., mode.mode_index]
E, H = field_values

# note: need to handle signs correctly and refactor symmetry
# Handle symmetries
if mode.symmetries[0] != 0:
E_tmp = E[:, 1:, ...]
H_tmp = H[:, 1:, ...]
E = np.concatenate((+E_tmp[:, ::-1, ...], E_tmp), axis=1)
H = np.concatenate((-H_tmp[:, ::-1, ...], H_tmp), axis=1)
E_half = E[:, 1:, ...]
H_half = H[:, 1:, ...]
E = np.concatenate((+E_half[:, ::-1, ...], E_half), axis=1)
H = np.concatenate((-H_half[:, ::-1, ...], H_half), axis=1)
if mode.symmetries[1] != 0:
E_tmp = E[:, :, 1:, ...]
H_tmp = H[:, :, 1:, ...]
E = np.concatenate((+E_tmp[:, :, ::-1, ...], E_tmp), axis=2)
H = np.concatenate((-H_tmp[:, :, ::-1, ...], H_tmp), axis=2)
E_half = E[:, :, 1:, ...]
H_half = H[:, :, 1:, ...]
E = np.concatenate((+E_half[:, :, ::-1, ...], E_half), axis=2)
H = np.concatenate((-H_half[:, :, ::-1, ...], H_half), axis=2)
Ex, Ey, Ez = E[..., None]
Hx, Hy, Hz = H[..., None]

# # return the fields in the correct
# normal_axis = [p == 0 for p in self.plane.size].index(True)
# Ex, Ey, Ez = self.simulation.unpop_axis(Ez, (Ex, Ey), axis=normal_axis)
# Hx, Hy, Hz = self.simulation.unpop_axis(Hz, (Hx, Hy), axis=normal_axis)
# add in the normal coordinate for each of the fields
def rotate_field_coords(field_array):
"""move the propagation axis=z to the proper order in the array"""
return np.moveaxis(field_array, source=2, destination=normal_axis)

Ex = rotate_field_coords(Ex)
Ey = rotate_field_coords(Ey)
Ez = rotate_field_coords(Ez)
Hx = rotate_field_coords(Hx)
Hy = rotate_field_coords(Hy)
Hz = rotate_field_coords(Hz)

# return the fields and coordinates in the original coordinate system
Ex, Ey, Ez = self.simulation.unpop_axis(Ez, (Ex, Ey), axis=normal_axis)
Hx, Hy, Hz = self.simulation.unpop_axis(Hz, (Hx, Hy), axis=normal_axis)

# apply -1 to H fields if needed, due to how they transform under reflections
if normal_axis == 1:
Hx *= -1
Hy *= -1
Hz *= -1

# note: from this point on, back in original coordinates

fields = {"Ex": Ex, "Ey": Ey, "Ez": Ez, "Hx": Hx, "Hy": Hy, "Hz": Hz}

# note: re-discretizing, need to make consistent.
data_dict = {}
for field_name, field in fields.items():
Nx = field.shape[0]
Ny = field.shape[1]
(xmin, ymin, zmin), (xmax, ymax, zmax) = self.plane.bounds
x = np.linspace(xmin, xmax, Nx)
y = np.linspace(ymin, ymax, Ny)
z = np.linspace(zmin, zmax, 1)
plane_grid = self.simulation.discretize(self.plane)
plane_coords = plane_grid[field_name]

data_dict[field_name] = ScalarFieldData(
x=x,
y=y,
z=z,
x=plane_coords.x,
y=plane_coords.y,
z=plane_coords.z,
f=np.array([self.freq]),
values=field,
)
Expand Down Expand Up @@ -214,7 +240,7 @@ def make_source(self, mode: Mode, fwidth: float, direction: Direction) -> ModeSo
center=center, size=size, source_time=source_time, mode=mode, direction=direction
)

def make_monitor(self, mode: Mode, freqs: List[float]) -> ModeMonitor:
def make_monitor(self, mode: Mode, freqs: List[float], name: str) -> ModeMonitor:
"""Creates ``ModeMonitor`` from a Mode + additional specifications.

Parameters
Expand All @@ -223,12 +249,13 @@ def make_monitor(self, mode: Mode, freqs: List[float]) -> ModeMonitor:
``Mode`` object containing specifications of mode.
freqs : List[float]
Frequencies to include in Monitor (Hz).

name : str
Required name of monitor.
Returns
-------
ModeMonitor
Monitor that measures ``Mode`` on ``plane`` at ``freqs``.
"""
center = self.plane.center
size = self.plane.size
return ModeMonitor(center=center, size=size, freqs=freqs, modes=[mode])
return ModeMonitor(center=center, size=size, freqs=freqs, modes=[mode], name=name)
15 changes: 12 additions & 3 deletions tidy3d/web/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

from enum import Enum
from abc import ABC
from typing import Any

import pydantic

from ..components.base import Tidy3dBaseModel


class TaskStatus(Enum):
"""the statuses that the task can be in"""
Expand All @@ -20,9 +19,14 @@ class TaskStatus(Enum):
ERROR = "error"


class TaskBase(Tidy3dBaseModel, ABC):
class TaskBase(pydantic.BaseModel, ABC):
"""base config for all task objects"""

class Config:
"""configure class"""

arbitrary_types_allowed = True


# type of the task_id
TaskId = str
Expand Down Expand Up @@ -80,6 +84,11 @@ class TaskInfo(TaskBase):
solverEndTimeAsLong: float = None
solverStartTimeAsLong: float = None
submitTimeAsLong: float = None
credential: Any = None
loopSolverTime: Any = None
shutoffNt: Any = None
startSolverTime: Any = None
totalSolverTime: Any = None


class RunInfo(TaskBase):
Expand Down