Skip to content

Commit

Permalink
add wave ports
Browse files Browse the repository at this point in the history
added path integral plotting functionality

added convenience function for defining voltage path integral from end points
  • Loading branch information
dmarek-flex committed Jul 25, 2024
1 parent 68407d2 commit e2915f8
Show file tree
Hide file tree
Showing 23 changed files with 1,573 additions and 337 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added `plot_length_units` to `Simulation` and `Scene` to allow for specifying units, which improves axis labels and scaling when plotting.
- Added the helper function `compute_power_delivered_by_port` to `TerminalComponentModeler` which computes power delivered to a microwave network from a port.
- Added the account function `account` to check credit balance and daily free simulation balance.
- Added `WavePort` to the `TerminalComponentModeler` which is the suggested port type for exciting transmission lines.
- Added plotting functionality to voltage and current path integrals in the `microwave` plugin.
- Added convenience functions `from_terminal_positions` and `from_circular_path` to simplify setup of `VoltageIntegralAxisAligned` and `CustomCurrentIntegral2D`, respectively.

### Changed
- Priority is given to `snapping_points` in `GridSpec` when close to structure boundaries, which reduces the chance of them being skipped.
Expand Down
5 changes: 3 additions & 2 deletions docs/api/plugins/smatrix.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Scattering Matrix Calculator
tidy3d.plugins.smatrix.Port
tidy3d.plugins.smatrix.ModalPortDataArray
tidy3d.plugins.smatrix.TerminalComponentModeler
tidy3d.plugins.smatrix.TerminalPortDataArray
tidy3d.plugins.smatrix.LumpedPort
tidy3d.plugins.smatrix.LumpedPortDataArray
tidy3d.plugins.smatrix.CoaxialLumpedPort
tidy3d.plugins.smatrix.CoaxialLumpedPort
tidy3d.plugins.smatrix.WavePort
71 changes: 55 additions & 16 deletions tests/test_plugins/terminal_component_modeler_def.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
from typing import Union

import numpy as np
import tidy3d as td
import tidy3d.plugins.microwave as microwave
from tidy3d.plugins.smatrix import (
CoaxialLumpedPort,
LumpedPort,
TerminalComponentModeler,
WavePort,
)

# Microstrip dimensions
Expand Down Expand Up @@ -240,31 +244,66 @@ def make_coaxial_component_modeler(
length: float = None,
port_refinement: bool = True,
grid_spec: td.GridSpec = None,
port_types: tuple[Union[CoaxialLumpedPort, WavePort], Union[CoaxialLumpedPort, WavePort]] = (
CoaxialLumpedPort,
CoaxialLumpedPort,
),
**kwargs,
):
if not length:
length = default_strip_length

sim = make_coaxial_simulation(length=length, grid_spec=grid_spec)

center_src1 = [0, 0, -length / 2]

port_cells = None
if port_refinement:
port_cells = 21
def make_port(center, direction, type, name) -> Union[CoaxialLumpedPort, WavePort]:
if type is CoaxialLumpedPort:
port_cells = None
if port_refinement:
port_cells = 21
port = CoaxialLumpedPort(
center=center,
outer_diameter=2 * Router,
inner_diameter=2 * Rinner,
normal_axis=2,
direction=direction,
name=name,
num_grid_cells=port_cells,
impedance=reference_impedance,
)
else:
mean_radius = (Router + Rinner) / 2
voltage_center = list(center)
voltage_center[0] += mean_radius
voltage_size = [Router - Rinner, 0, 0]

port = WavePort(
center=center,
size=[2 * Router, 2 * Router, 0],
direction=direction,
name=name,
mode_spec=td.ModeSpec(num_modes=1),
mode_index=0,
voltage_integral=microwave.VoltageIntegralAxisAligned(
center=voltage_center,
size=voltage_size,
extrapolate_to_endpoints=True,
snap_path_to_grid=True,
sign="+",
),
current_integral=microwave.CustomCurrentIntegral2D.from_circular_path(
center=center,
radius=mean_radius,
num_points=41,
normal_axis=2,
clockwise=False,
),
)
return port

port_1 = CoaxialLumpedPort(
center=center_src1,
outer_diameter=2 * Router,
inner_diameter=2 * Rinner,
normal_axis=2,
direction="+",
name="coax_port_1",
num_grid_cells=port_cells,
impedance=reference_impedance,
)
center_src1 = [0, 0, -length / 2]
port_1 = make_port(center_src1, direction="+", type=port_types[0], name="coax_port_1")
center_src2 = [0, 0, length / 2]
port_2 = port_1.updated_copy(name="coax_port_2", center=center_src2, direction="-")
port_2 = make_port(center_src2, direction="-", type=port_types[1], name="coax_port_2")
ports = [port_1, port_2]
freqs = np.linspace(freq_start, freq_stop, 100)

Expand Down
190 changes: 190 additions & 0 deletions tests/test_plugins/test_microwave.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Test the microwave plugin."""

import matplotlib.pyplot as plt
import numpy as np
import pydantic.v1 as pydantic
import pytest
Expand Down Expand Up @@ -31,6 +32,8 @@
FIELD_MONITOR = td.FieldMonitor(size=MON_SIZE, fields=FIELDS, name="strip_field", freqs=FS)
STRIP_WIDTH = 1.5
STRIP_HEIGHT = 0.5
COAX_R1 = 0.04
COAX_R2 = 0.5

SIM_Z = td.Simulation(
size=(2, 1, 1),
Expand Down Expand Up @@ -93,6 +96,51 @@ def make_stripline_scalar_field_data_array(grid_key: str):
return td.ScalarFieldDataArray(values, coords=dict(x=XS, y=YS, z=ZS, f=FS))


def make_coaxial_field_data_array(grid_key: str):
"""Populate FIELD_MONITOR with a coaxial transmission line mode."""

# Get a normalized electric field that represents the electric field within a coaxial cable transmission line.
def compute_coax_radial_electric(rin, rout, x, y, is_x):
# Radial distance
r = np.sqrt((x) ** 2 + (y) ** 2)
# Remove division by 0
r_valid = np.where(r == 0.0, 1, r)
# Compute current density so that the total current
# is 1 flowing through surfaces of constant r
# Extra r is for changing to Cartesian coordinates
denominator = 2 * np.pi * r_valid**2
if is_x:
Exy = np.where(r <= rin, 0, (x / denominator))
Exy = np.where(r >= rout, 0, Exy)
else:
Exy = np.where(r <= rin, 0, (y / denominator))
Exy = np.where(r >= rout, 0, Exy)
return Exy

XS, YS, ZS = get_spatial_coords_dict(SIM_Z, FIELD_MONITOR, grid_key).values()
XGRID, YGRID = np.meshgrid(XS, YS, indexing="ij")
XGRID = XGRID.reshape((len(XS), len(YS), 1, 1))
YGRID = YGRID.reshape((len(XS), len(YS), 1, 1))
values = np.zeros((len(XS), len(YS), len(ZS), len(FS)))

XGRID = np.broadcast_to(XGRID, values.shape)
YGRID = np.broadcast_to(YGRID, values.shape)

is_x = grid_key[1] == "x"
if grid_key[0] == "E":
field = compute_coax_radial_electric(COAX_R1, COAX_R2, XGRID, YGRID, is_x)
else:
field = compute_coax_radial_electric(COAX_R1, COAX_R2, XGRID, YGRID, not is_x)
# H field is perpendicular and oriented for positive z propagation
# We want to compute Hx which is -Ey/eta0, Hy which is Ex/eta0
if is_x:
field /= -ETA_0
else:
field /= ETA_0

return td.ScalarFieldDataArray(field, coords=dict(x=XS, y=YS, z=ZS, f=FS))


def make_field_data():
return FieldData(
monitor=FIELD_MONITOR,
Expand All @@ -106,6 +154,19 @@ def make_field_data():
)


def make_coax_field_data():
return FieldData(
monitor=FIELD_MONITOR,
Ex=make_coaxial_field_data_array("Ex"),
Ey=make_coaxial_field_data_array("Ey"),
Hx=make_coaxial_field_data_array("Hx"),
Hy=make_coaxial_field_data_array("Hy"),
symmetry=SIM_Z.symmetry,
symmetry_center=SIM_Z.center,
grid_expanded=SIM_Z.discretize_monitor(FIELD_MONITOR),
)


@pytest.mark.parametrize("axis", [0, 1, 2])
def test_voltage_integral_axes(axis):
"""Check VoltageIntegralAxisAligned runs."""
Expand Down Expand Up @@ -200,6 +261,15 @@ def test_current_missing_fields():
with pytest.raises(DataError):
_ = current_integral.compute_current(SIM_Z_DATA["ExHx"])

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

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


def test_time_monitor_voltage_integral():
"""Check VoltageIntegralAxisAligned runs on time domain data."""
Expand Down Expand Up @@ -407,6 +477,20 @@ def test_fields_missing_custom_current_integral():
current_integral.compute_current(SIM_Z_DATA["ExHx"])


def test_wrong_monitor_data():
"""Check that the function arguments to integrals are correctly validated."""
length = 0.5
size = [0, 0, 0]
size[1] = length
# Make box
vertices = [(0.2, -0.2), (0.2, 0.2), (-0.2, 0.2), (-0.2, -0.2), (0.2, -0.2)]
current_integral = CustomCurrentIntegral2D(axis=2, position=0, vertices=vertices)
with pytest.raises(DataError):
current_integral.compute_current(SIM_Z.sources[0].source_time)
with pytest.raises(DataError):
current_integral.compute_current(em_field=SIM_Z.sources[0].source_time)


def test_time_monitor_custom_current_integral():
length = 0.5
size = [0, 0, 0]
Expand All @@ -425,3 +509,109 @@ def test_mode_solver_custom_current_integral():
vertices = [(0.2, -0.2), (0.2, 0.2), (-0.2, 0.2), (-0.2, -0.2), (0.2, -0.2)]
current_integral = CustomCurrentIntegral2D(axis=2, position=0, vertices=vertices)
current_integral.compute_current(SIM_Z_DATA["mode"])


def test_custom_current_integral_normal_y():
current_integral = CustomCurrentIntegral2D.from_circular_path(
center=(0, 0, 0), radius=0.4, num_points=31, normal_axis=1, clockwise=False
)
current_integral.compute_current(SIM_Z_DATA["field"])


def test_custom_path_integral_accuracy():
"""Test the accuracy of the custom path integral."""
field_data = make_coax_field_data()

current_integral = CustomCurrentIntegral2D.from_circular_path(
center=(0, 0, 0), radius=0.4, num_points=31, normal_axis=2, clockwise=False
)
current = current_integral.compute_current(field_data)
assert np.allclose(current.values, 1.0 / ETA_0, rtol=0.01)

current_integral = CustomCurrentIntegral2D.from_circular_path(
center=(0, 0, 0), radius=0.4, num_points=31, normal_axis=2, clockwise=True
)
current = current_integral.compute_current(field_data)
assert np.allclose(current.values, -1.0 / ETA_0, rtol=0.01)


def test_impedance_accuracy_on_coaxial():
"""Test the accuracy of the ImpedanceCalculator."""
field_data = make_coax_field_data()
# Setup path integrals
mean_radius = (COAX_R2 + COAX_R1) * 0.5
size = [COAX_R2 - COAX_R1, 0, 0]
center = [mean_radius, 0, 0]

voltage_integral = VoltageIntegralAxisAligned(
center=center, size=size, sign="-", extrapolate_to_endpoints=True, snap_path_to_grid=True
)
current_integral = CustomCurrentIntegral2D.from_circular_path(
center=(0, 0, 0), radius=mean_radius, num_points=31, normal_axis=2, clockwise=False
)

def impedance_of_coaxial_cable(r1, r2, wave_impedance=td.ETA_0):
return np.log(r2 / r1) * wave_impedance / 2 / np.pi

Z_analytic = impedance_of_coaxial_cable(COAX_R1, COAX_R2)

# Compute impedance using the tool
Z_calculator = ImpedanceCalculator(
voltage_integral=voltage_integral, current_integral=current_integral
)
Z_calc = Z_calculator.compute_impedance(field_data)
assert np.allclose(Z_calc, Z_analytic, rtol=0.04)


def test_path_integral_plotting():
"""Test that all types of path integrals correctly plot themselves."""

mean_radius = (COAX_R2 + COAX_R1) * 0.5
size = [COAX_R2 - COAX_R1, 0, 0]
center = [mean_radius, 0, 0]

voltage_integral = VoltageIntegralAxisAligned(
center=center, size=size, sign="-", extrapolate_to_endpoints=True, snap_path_to_grid=True
)

current_integral = CustomCurrentIntegral2D.from_circular_path(
center=(0, 0, 0), radius=0.4, num_points=31, normal_axis=2, clockwise=False
)

ax = voltage_integral.plot(z=0)
current_integral.plot(z=0, ax=ax)
plt.close()

# Test off center plotting
ax = voltage_integral.plot(z=2)
current_integral.plot(z=2, ax=ax)
plt.close()

# Plot
voltage_integral = CustomVoltageIntegral2D(
axis=1, position=0, vertices=[(-1, -1), (0, 0), (1, 1)]
)

current_integral = CurrentIntegralAxisAligned(
center=(0, 0, 0),
size=(2, 0, 1),
sign="-",
extrapolate_to_endpoints=False,
snap_contour_to_grid=False,
)

ax = voltage_integral.plot(y=0)
current_integral.plot(y=0, ax=ax)
plt.close()

# Test off center plotting
ax = voltage_integral.plot(y=2)
current_integral.plot(y=2, ax=ax)
plt.close()


def test_creation_from_terminal_positions():
"""Test creating an VoltageIntegralAxisAligned using terminal positions."""
_ = VoltageIntegralAxisAligned.from_terminal_positions(
plus_terminal=2, minus_terminal=1, y=2.2, z=1
)
Loading

0 comments on commit e2915f8

Please sign in to comment.