Skip to content

Commit

Permalink
Improved documentation.
Browse files Browse the repository at this point in the history
Revised api of path integrals and impedance calculator to accept monitor data directly and error if the data is the wrong type.
Added more testing coverage.
Extended utils.py to create random data for time and mode solver monitors. Plus fixed some ruff errors.
  • Loading branch information
dmarek-flex committed Mar 28, 2024
1 parent e5a0f27 commit ce9b7db
Show file tree
Hide file tree
Showing 8 changed files with 306 additions and 119 deletions.
2 changes: 2 additions & 0 deletions docs/api/plugins/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Plugins
adjoint
waveguide
design
microwave


.. include:: /api/plugins/mode_solver.rst
Expand All @@ -22,3 +23,4 @@ Plugins
.. include:: /api/plugins/adjoint.rst
.. include:: /api/plugins/waveguide.rst
.. include:: /api/plugins/design.rst
.. include:: /api/plugins/microwave.rst
13 changes: 13 additions & 0 deletions docs/api/plugins/microwave.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.. currentmodule:: tidy3d

Microwave
----------------------------

.. autosummary::
:toctree: ../_autosummary/
:template: module.rst

tidy3d.plugins.microwave.AxisAlignedPathIntegral
tidy3d.plugins.microwave.VoltageIntegralAxisAligned
tidy3d.plugins.microwave.CurrentIntegralAxisAligned
tidy3d.plugins.microwave.ImpedanceCalculator
131 changes: 96 additions & 35 deletions tests/test_plugins/test_microwave.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
import tidy3d as td
from tidy3d import FieldData
from tidy3d.constants import ETA_0
from tidy3d.plugins.microwave import VoltageIntegralAA, CurrentIntegralAA, ImpedanceCalculator
from tidy3d.plugins.microwave import (
VoltageIntegralAxisAligned,
CurrentIntegralAxisAligned,
ImpedanceCalculator,
)
import pydantic.v1 as pydantic
from tidy3d.exceptions import DataError
from ..utils import run_emulated
Expand All @@ -17,7 +21,7 @@
FSTOP = 1.5e9
F0 = (FSTART + FSTOP) / 2
FWIDTH = FSTOP - FSTART
FS = np.linspace(FSTART, FSTOP, 5)
FS = np.linspace(FSTART, FSTOP, 3)
FIELD_MONITOR = td.FieldMonitor(
size=MON_SIZE, fields=FIELDS, name="strip_field", freqs=FS, colocate=False
)
Expand All @@ -33,8 +37,13 @@
td.FieldMonitor(
center=(0, 0, 0), size=(1, 1, 1), freqs=FS, fields=["Ex", "Hx"], name="ExHx"
),
td.ModeMonitor(
center=(0, 0, 0), size=(1, 1, 0), freqs=FS, mode_spec=td.ModeSpec(), name="mode"
td.FieldTimeMonitor(center=(0, 0, 0), size=(1, 1, 0), colocate=False, name="field_time"),
td.ModeSolverMonitor(
center=(0, 0, 0),
size=(1, 1, 0),
freqs=FS,
mode_spec=td.ModeSpec(num_modes=2),
name="mode",
),
],
sources=[
Expand All @@ -44,9 +53,11 @@
source_time=td.GaussianPulse(freq0=F0, fwidth=FWIDTH),
)
],
run_time=2e-12,
run_time=5e-16,
)

SIM_Z_DATA = run_emulated(SIM_Z)

""" Generate the data arrays for testing path integral computations """


Expand Down Expand Up @@ -105,13 +116,13 @@ def test_voltage_integral_axes(axis):
size = [0, 0, 0]
size[axis] = length
center = [0, 0, 0]
voltage_integral = VoltageIntegralAA(
voltage_integral = VoltageIntegralAxisAligned(
center=center,
size=size,
sign="+",
)
sim = SIM_Z
sim_data = run_emulated(sim)
_ = voltage_integral.compute_voltage(sim_data["field"].field_components)

_ = voltage_integral.compute_voltage(SIM_Z_DATA["field"])


@pytest.mark.parametrize("axis", [0, 1, 2])
Expand All @@ -120,105 +131,155 @@ def test_current_integral_axes(axis):
size = [length, length, length]
size[axis] = 0.0
center = [0, 0, 0]
current_integral = CurrentIntegralAA(
current_integral = CurrentIntegralAxisAligned(
center=center,
size=size,
sign="+",
)
sim = SIM_Z
sim_data = run_emulated(sim)
_ = current_integral.compute_current(sim_data["field"].field_components)
_ = current_integral.compute_current(SIM_Z_DATA["field"])


def test_voltage_integral_toggles():
length = 0.5
size = [0, 0, 0]
size[0] = length
center = [0, 0, 0]
voltage_integral = VoltageIntegralAA(
voltage_integral = VoltageIntegralAxisAligned(
center=center,
size=size,
extrapolate_to_endpoints=True,
snap_path_to_grid=True,
sign="-",
)
sim = SIM_Z
sim_data = run_emulated(sim)
_ = voltage_integral.compute_voltage(sim_data["field"].field_components)
_ = voltage_integral.compute_voltage(SIM_Z_DATA["field"])


def test_current_integral_toggles():
length = 0.5
size = [length, length, length]
size[0] = 0.0
center = [0, 0, 0]
current_integral = CurrentIntegralAA(
current_integral = CurrentIntegralAxisAligned(
center=center,
size=size,
extrapolate_to_endpoints=True,
snap_contour_to_grid=True,
sign="-",
)
sim = SIM_Z
sim_data = run_emulated(sim)
_ = current_integral.compute_current(sim_data["field"].field_components)
_ = current_integral.compute_current(SIM_Z_DATA["field"])


def test_voltage_missing_fields():
length = 0.5
size = [0, 0, 0]
size[1] = length
center = [0, 0, 0]
voltage_integral = VoltageIntegralAA(
voltage_integral = VoltageIntegralAxisAligned(
center=center,
size=size,
sign="+",
)
sim = SIM_Z
sim_data = run_emulated(sim)

with pytest.raises(DataError):
_ = voltage_integral.compute_voltage(sim_data["ExHx"].field_components)
_ = voltage_integral.compute_voltage(SIM_Z_DATA["ExHx"])


def test_current_missing_fields():
length = 0.5
size = [length, length, length]
size[0] = 0.0
center = [0, 0, 0]
current_integral = CurrentIntegralAA(
current_integral = CurrentIntegralAxisAligned(
center=center,
size=size,
sign="+",
)
sim = SIM_Z
sim_data = run_emulated(sim)

with pytest.raises(DataError):
_ = current_integral.compute_current(sim_data["ExHx"].field_components)
_ = current_integral.compute_current(SIM_Z_DATA["ExHx"])


def test_time_monitor_voltage_integral():
length = 0.5
size = [0, 0, 0]
size[1] = length
center = [0, 0, 0]
voltage_integral = VoltageIntegralAxisAligned(
center=center,
size=size,
sign="+",
)

voltage_integral.compute_voltage(SIM_Z_DATA["field_time"])


def test_mode_solver_monitor_voltage_integral():
length = 0.5
size = [0, 0, 0]
size[1] = length
center = [0, 0, 0]
voltage_integral = VoltageIntegralAxisAligned(
center=center,
size=size,
sign="+",
)

voltage_integral.compute_voltage(SIM_Z_DATA["mode"])


def test_tiny_voltage_path():
length = 0.02
size = [0, 0, 0]
size[1] = length
center = [0, 0, 0]
voltage_integral = VoltageIntegralAA(center=center, size=size, extrapolate_to_endpoints=True)
sim = SIM_Z
sim_data = run_emulated(sim)
_ = voltage_integral.compute_voltage(sim_data["field"].field_components)
voltage_integral = VoltageIntegralAxisAligned(
center=center, size=size, sign="+", extrapolate_to_endpoints=True
)

_ = voltage_integral.compute_voltage(SIM_Z_DATA["field"])


def test_impedance_calculator():
with pytest.raises(pydantic.ValidationError):
_ = ImpedanceCalculator(voltage_integral=None, current_integral=None)


def test_impedance_calculator_on_time_data():
# Setup path integrals
length = 0.5
size = [0, length, 0]
size[1] = length
center = [0, 0, 0]
voltage_integral = VoltageIntegralAxisAligned(
center=center, size=size, sign="+", extrapolate_to_endpoints=True
)

size = [length, length, 0]
current_integral = CurrentIntegralAxisAligned(center=center, size=size, sign="+")

# Compute impedance using the tool
Z_calc = ImpedanceCalculator(
voltage_integral=voltage_integral, current_integral=current_integral
)
_ = Z_calc.compute_impedance(SIM_Z_DATA["field_time"])
Z_calc = ImpedanceCalculator(voltage_integral=voltage_integral, current_integral=None)
_ = Z_calc.compute_impedance(SIM_Z_DATA["field_time"])
Z_calc = ImpedanceCalculator(voltage_integral=None, current_integral=current_integral)
_ = Z_calc.compute_impedance(SIM_Z_DATA["field_time"])


def test_impedance_accuracy():
field_data = make_field_data()
# Setup path integrals
size = [0, STRIP_HEIGHT / 2, 0]
center = [0, -STRIP_HEIGHT / 4, 0]
voltage_integral = VoltageIntegralAA(center=center, size=size, extrapolate_to_endpoints=True)
voltage_integral = VoltageIntegralAxisAligned(
center=center, size=size, sign="+", extrapolate_to_endpoints=True
)

size = [STRIP_WIDTH * 1.25, STRIP_HEIGHT / 2, 0]
center = [0, 0, 0]
current_integral = CurrentIntegralAA(center=center, size=size)
current_integral = CurrentIntegralAxisAligned(center=center, size=size, sign="+")

def impedance_of_stripline(width, height):
# Assuming no fringing fields, is the same as a parallel plate
Expand Down
73 changes: 71 additions & 2 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from tidy3d.log import _get_level_int
from tidy3d.web import BatchData
from tidy3d.components.base import Tidy3dBaseModel
from tidy3d import ModeIndexDataArray

""" utilities shared between all tests """
np.random.seed(4)
Expand Down Expand Up @@ -78,7 +79,7 @@ def cartesian_to_unstructured(
xyz = [array.x, array.y, array.z]
lens = [len(coord) for coord in xyz]

num_len_zero = sum(l == 1 for l in lens)
num_len_zero = sum(length == 1 for length in lens)

if num_len_zero == 1:
normal_axis = lens.index(1)
Expand Down Expand Up @@ -245,7 +246,7 @@ def cartesian_to_unstructured(
def make_spatial_data(
size,
bounds,
lims=[0, 1],
lims=(0, 1),
seed_data=None,
unstructured=False,
perturbation=0.1,
Expand Down Expand Up @@ -875,6 +876,72 @@ def make_field_data(monitor: td.FieldMonitor) -> td.FieldData:
**field_cmps,
)

def make_field_time_data(monitor: td.FieldTimeMonitor) -> td.FieldTimeData:
"""make a random FieldTimeData from a FieldTimeMonitor."""
field_cmps = {}
coords = {}
grid = simulation.discretize_monitor(monitor)
tmesh = simulation.tmesh
for field_name in monitor.fields:
spatial_coords_dict = grid[field_name].dict()

for axis, dim in enumerate("xyz"):
if monitor.size[axis] == 0:
coords[dim] = [monitor.center[axis]]
else:
coords[dim] = np.array(spatial_coords_dict[dim])

(idx_begin, idx_end) = monitor.time_inds(tmesh)
tcoords = tmesh[idx_begin:idx_end]
coords["t"] = tcoords
field_cmps[field_name] = make_data(
coords=coords, data_array_type=td.ScalarFieldTimeDataArray, is_complex=False
)

return td.FieldTimeData(
monitor=monitor,
symmetry=(0, 0, 0),
symmetry_center=simulation.center,
grid_expanded=grid,
**field_cmps,
)

def make_mode_solver_data(monitor: td.ModeSolverMonitor) -> td.ModeSolverData:
"""make a random ModeSolverData from a ModeSolverMonitor."""
field_cmps = {}
coords = {}
grid = simulation.discretize_monitor(monitor)
index_coords = {}
index_coords["f"] = list(monitor.freqs)
index_coords["mode_index"] = np.arange(monitor.mode_spec.num_modes)
index_data_shape = (len(index_coords["f"]), len(index_coords["mode_index"]))
index_data = ModeIndexDataArray(
(1 + 1j) * np.random.random(index_data_shape), coords=index_coords
)
for field_name in ["Ex", "Ey", "Ez", "Hx", "Hy", "Hz"]:
spatial_coords_dict = grid[field_name].dict()

for axis, dim in enumerate("xyz"):
if monitor.size[axis] == 0:
coords[dim] = [monitor.center[axis]]
else:
coords[dim] = np.array(spatial_coords_dict[dim])

coords["f"] = list(monitor.freqs)
coords["mode_index"] = index_coords["mode_index"]
field_cmps[field_name] = make_data(
coords=coords, data_array_type=td.ScalarModeFieldDataArray, is_complex=True
)

return td.ModeSolverData(
monitor=monitor,
symmetry=(0, 0, 0),
symmetry_center=simulation.center,
grid_expanded=grid,
n_complex=index_data,
**field_cmps,
)

def make_eps_data(monitor: td.PermittivityMonitor) -> td.PermittivityData:
"""make a random PermittivityData from a PermittivityMonitor."""
field_mnt = td.FieldMonitor(**monitor.dict(exclude={"type", "fields"}))
Expand Down Expand Up @@ -920,6 +987,8 @@ def make_mode_data(monitor: td.ModeMonitor) -> td.ModeData:

MONITOR_MAKER_MAP = {
td.FieldMonitor: make_field_data,
td.FieldTimeMonitor: make_field_time_data,
td.ModeSolverMonitor: make_mode_solver_data,
td.ModeMonitor: make_mode_data,
td.PermittivityMonitor: make_eps_data,
td.DiffractionMonitor: make_diff_data,
Expand Down
Loading

0 comments on commit ce9b7db

Please sign in to comment.