Skip to content

Commit 55f9529

Browse files
committed
add a TerminalSpec for RF specific mode information
adding support for colocated fields fixing bug for more complicated shapes adding tests, fixing symmetric conditions, fixing for 2D structures fix simulation validator for terminal spec refactor by splitting the path integral specification away from the integral computation, now the path specification handles pretty much everything, except for the final integral computation and results preparation rename auto patch spec to generator type name, fixed tests add test for sim validation and improved doc strings fix regression fix python 3.9 tests reorg of ModeData, the impedance is now calculated by the ModeSolver class avoiding the need for a duplicate terminal spec add more descriptive warnings to composite current integral
1 parent 68fc687 commit 55f9529

22 files changed

+2169
-578
lines changed

tests/test_components/test_microwave.py

Lines changed: 372 additions & 2 deletions
Large diffs are not rendered by default.

tests/test_components/test_simulation.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3648,3 +3648,54 @@ def test_create_sim_multiphysics_with_incompatibilities():
36483648
),
36493649
],
36503650
)
3651+
3652+
3653+
def test_validate_terminal_spec_generation():
3654+
"""Test that auto generation of path specs is correctly validated for currently unsupported structures."""
3655+
freq0 = 10e9
3656+
mm = 1e3
3657+
run_time_spec = td.RunTimeSpec(quality_factor=3.0)
3658+
size = (10 * mm, 10 * mm, 10 * mm)
3659+
size_mon = (0, 8 * mm, 8 * mm)
3660+
3661+
# Currently limited to generation of axis aligned boxes around conductors,
3662+
# so the path may intersect other nearby conductors, like in this coaxial cable
3663+
coaxial = td.Structure(
3664+
geometry=td.GeometryGroup(
3665+
geometries=(
3666+
td.ClipOperation(
3667+
operation="difference",
3668+
geometry_a=td.Cylinder(
3669+
axis=0, radius=2.5 * mm, center=(0, 0, 0), length=td.inf
3670+
),
3671+
geometry_b=td.Cylinder(
3672+
axis=0, radius=1.3 * mm, center=(0, 0, 0), length=td.inf
3673+
),
3674+
),
3675+
td.Cylinder(axis=0, radius=1 * mm, center=(0, 0, 0), length=td.inf),
3676+
)
3677+
),
3678+
medium=td.PEC,
3679+
)
3680+
mode_spec = td.ModeSpec(num_modes=2, target_neff=1.8, terminal_spec=td.TerminalSpec())
3681+
3682+
mode_mon = td.ModeMonitor(
3683+
center=(0, 0, 0),
3684+
size=size_mon,
3685+
freqs=[freq0],
3686+
name="mode_1",
3687+
colocate=False,
3688+
mode_spec=mode_spec,
3689+
)
3690+
sim = td.Simulation(
3691+
run_time=run_time_spec,
3692+
size=size,
3693+
sources=[],
3694+
structures=[coaxial],
3695+
grid_spec=td.GridSpec.uniform(dl=0.1 * mm),
3696+
monitors=[mode_mon],
3697+
)
3698+
3699+
# check that validation error is caught
3700+
with pytest.raises(SetupError):
3701+
sim._validate_terminal_specs()

tests/test_data/test_data_arrays.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,16 @@ def test_abs():
315315
_ = data.abs
316316

317317

318+
def test_angle():
319+
# Make sure works on real data and the type is correct
320+
data = make_scalar_field_time_data_array("Ex")
321+
angle_data = data.angle
322+
assert type(data) is type(angle_data)
323+
data = make_mode_amps_data_array()
324+
angle_data = data.angle
325+
assert type(data) is type(angle_data)
326+
327+
318328
def test_heat_data_array():
319329
T = [0, 1e-12, 2e-12]
320330
_ = td.HeatDataArray((1 + 1j) * np.random.random((3,)), coords={"T": T})

tests/test_plugins/test_microwave.py

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,18 @@
1414
import tidy3d as td
1515
import tidy3d.plugins.microwave as mw
1616
from tidy3d import FieldData
17+
from tidy3d.components.data.data_array import FreqModeDataArray
1718
from tidy3d.constants import ETA_0
1819
from tidy3d.exceptions import DataError
1920

20-
from ..utils import get_spatial_coords_dict, run_emulated
21+
from ..utils import AssertLogLevel, get_spatial_coords_dict, run_emulated
22+
23+
MAKE_PLOTS = False
24+
if MAKE_PLOTS:
25+
# Interative plotting for debugging
26+
from matplotlib import use
27+
28+
use("TkAgg")
2129

2230
# Using similar code as "test_data/test_data_arrays.py"
2331
MON_SIZE = (2, 1, 0)
@@ -527,6 +535,41 @@ def test_custom_current_integral_normal_y():
527535
current_integral.compute_current(SIM_Z_DATA["field"])
528536

529537

538+
def test_composite_current_integral_warnings():
539+
"""Ensures that the checks function correctly on some test data."""
540+
f = [2e9, 3e9, 4e9]
541+
mode_index = list(np.arange(5))
542+
coords = {"f": f, "mode_index": mode_index}
543+
values = np.ones((3, 5))
544+
545+
path_spec = td.CurrentIntegralAxisAlignedSpec(center=(0, 0, 0), size=(2, 2, 0), sign="+")
546+
composite_integral = mw.CompositeCurrentIntegral(
547+
center=(0, 0, 0), size=(4, 4, 0), path_specs=[path_spec], sum_spec="split"
548+
)
549+
550+
phase_diff = FreqModeDataArray(np.angle(values), coords=coords)
551+
with AssertLogLevel(None):
552+
assert composite_integral._check_phase_sign_consistency(phase_diff)
553+
554+
values[1, 2:] = -1
555+
phase_diff = FreqModeDataArray(np.angle(values), coords=coords)
556+
with AssertLogLevel("WARNING"):
557+
assert not composite_integral._check_phase_sign_consistency(phase_diff)
558+
559+
values = np.ones((3, 5))
560+
in_phase = FreqModeDataArray(values, coords=coords)
561+
values = 0.5 * np.ones((3, 5))
562+
out_phase = FreqModeDataArray(values, coords=coords)
563+
with AssertLogLevel(None):
564+
assert composite_integral._check_phase_amplitude_consistency(in_phase, out_phase)
565+
566+
values = 0.5 * np.ones((3, 5))
567+
values[2, 4:] = 1.5
568+
out_phase = FreqModeDataArray(values, coords=coords)
569+
with AssertLogLevel("WARNING"):
570+
assert not composite_integral._check_phase_amplitude_consistency(in_phase, out_phase)
571+
572+
530573
def test_custom_path_integral_accuracy():
531574
"""Test the accuracy of the custom path integral."""
532575
field_data = make_coax_field_data()
@@ -785,13 +828,11 @@ def test_lobe_measurements(apply_cyclic_extension, include_endpoint):
785828
@pytest.mark.parametrize("min_value", [0.0, 1.0])
786829
def test_lobe_plots(min_value):
787830
"""Run the lobe measurer on some test data and plot the results."""
788-
# Interative plotting for debugging
789-
# from matplotlib import use
790-
# use("TkAgg")
791831
theta = np.linspace(0, 2 * np.pi, 301)
792832
Urad = np.cos(theta) ** 2 * np.cos(3 * theta) ** 2 + min_value
793833
lobe_measurer = mw.LobeMeasurer(angle=theta, radiation_pattern=Urad)
794834
_, ax = plt.subplots(1, 1, subplot_kw={"projection": "polar"})
795835
ax.plot(theta, Urad, "k")
796836
lobe_measurer.plot(0, ax)
797-
plt.show()
837+
if MAKE_PLOTS:
838+
plt.show()

tidy3d/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@
1717
from tidy3d.components.microwave.data.monitor_data import (
1818
AntennaMetricsData,
1919
)
20+
from tidy3d.components.microwave.path_spec import (
21+
CompositeCurrentIntegralSpec,
22+
CurrentIntegralAxisAlignedSpec,
23+
CustomCurrentIntegral2DSpec,
24+
CustomVoltageIntegral2DSpec,
25+
VoltageIntegralAxisAlignedSpec,
26+
)
27+
from tidy3d.components.microwave.terminal_spec import (
28+
TerminalSpec,
29+
)
2030
from tidy3d.components.spice.analysis.dc import (
2131
ChargeToleranceSpec,
2232
IsothermalSteadyChargeDCAnalysis,
@@ -439,6 +449,7 @@ def set_logging_level(level: str) -> None:
439449
"ChargeToleranceSpec",
440450
"ClipOperation",
441451
"CoaxialLumpedResistor",
452+
"CompositeCurrentIntegralSpec",
442453
"ConstantDoping",
443454
"ConstantMobilityModel",
444455
"ContinuousWave",
@@ -449,8 +460,10 @@ def set_logging_level(level: str) -> None:
449460
"Coords1D",
450461
"CornerFinderSpec",
451462
"CurrentBC",
463+
"CurrentIntegralAxisAlignedSpec",
452464
"CustomAnisotropicMedium",
453465
"CustomChargePerturbation",
466+
"CustomCurrentIntegral2DSpec",
454467
"CustomCurrentSource",
455468
"CustomDebye",
456469
"CustomDrude",
@@ -463,6 +476,7 @@ def set_logging_level(level: str) -> None:
463476
"CustomPoleResidue",
464477
"CustomSellmeier",
465478
"CustomSourceTime",
479+
"CustomVoltageIntegral2DSpec",
466480
"Cylinder",
467481
"DCCurrentSource",
468482
"DCVoltageSource",
@@ -668,6 +682,7 @@ def set_logging_level(level: str) -> None:
668682
"TemperatureBC",
669683
"TemperatureData",
670684
"TemperatureMonitor",
685+
"TerminalSpec",
671686
"TetrahedralGridDataset",
672687
"Transformed",
673688
"TriangleMesh",
@@ -682,6 +697,7 @@ def set_logging_level(level: str) -> None:
682697
"Updater",
683698
"VisualizationSpec",
684699
"VoltageBC",
700+
"VoltageIntegralAxisAlignedSpec",
685701
"VoltageSourceType",
686702
"VolumetricAveraging",
687703
"YeeGrid",

tidy3d/components/data/data_array.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,13 @@ def abs(self):
205205
"""Absolute value of data array."""
206206
return abs(self)
207207

208+
@property
209+
def angle(self):
210+
"""Angle or phase value of data array."""
211+
values = np.angle(self.values)
212+
SelfType = type(self)
213+
return SelfType(values, coords=self.coords)
214+
208215
@property
209216
def is_uniform(self):
210217
"""Whether each element is of equal value in the data array"""

tidy3d/components/data/monitor_data.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1602,11 +1602,19 @@ class ModeData(ModeSolverDataset, ElectromagneticFieldData):
16021602

16031603
eps_spec: list[EpsSpecType] = pd.Field(
16041604
None,
1605-
title="Permettivity Specification",
1605+
title="Permittivity Specification",
16061606
description="Characterization of the permittivity profile on the plane where modes are "
16071607
"computed. Possible values are 'diagonal', 'tensorial_real', 'tensorial_complex'.",
16081608
)
16091609

1610+
Z0: Optional[FreqModeDataArray] = pd.Field(
1611+
None,
1612+
title="Characteristic Impedance",
1613+
description="Optional quantity calculated for transmission lines."
1614+
"The characteristic impedance is only calculated when a :class:``TerminalSpec`` "
1615+
"is provided to the :class:``ModeSpec`` associated with this data.",
1616+
)
1617+
16101618
@pd.validator("eps_spec", always=True)
16111619
@skip_if_fields_missing(["monitor"])
16121620
def eps_spec_match_mode_spec(cls, val, values):
@@ -2055,6 +2063,10 @@ def modes_info(self) -> xr.Dataset:
20552063
info["wg TE fraction"] = self.pol_fraction_waveguide["te"]
20562064
info["wg TM fraction"] = self.pol_fraction_waveguide["tm"]
20572065

2066+
if self.Z0 is not None:
2067+
info["Re(Z0)"] = self.Z0.real
2068+
info["Im(Z0)"] = self.Z0.imag
2069+
20582070
return xr.Dataset(data_vars=info)
20592071

20602072
def to_dataframe(self) -> DataFrame:

0 commit comments

Comments
 (0)