Skip to content

Commit 46d7d5b

Browse files
committed
fix impedance calculation for lossy transmission lines
1 parent 46433a7 commit 46d7d5b

File tree

3 files changed

+47
-17
lines changed

3 files changed

+47
-17
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2828
- Solver error for EME simulations with bends, introduced when support for 2D EME simulations was added.
2929
- Internal interpolation errors with some versions of `xarray` and `numpy`.
3030
- If `ModeSpec.angle_rotation=True` for a mode object, validate that the structure rotation can be successfully done. Also, error if the medium cannot be rotated (e.g. anisotropic or custom medium), which would previously have just produced wrong results.
31+
- Characteristic impedance calculations in the `ImpedanceCalculator` using definitions that rely on flux, which were giving incorrect results for lossy transmission lines.
3132

3233
### Changed
3334
- Relaxed bounds checking of path integrals during `WavePort` validation.

tidy3d/components/data/monitor_data.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,7 @@ def intensity(self) -> ScalarFieldDataArray:
660660
return intensity.squeeze(dim=normal_dim, drop=True)
661661

662662
@property
663-
def poynting(self) -> ScalarFieldDataArray:
663+
def complex_poynting(self) -> ScalarFieldDataArray:
664664
"""Time-averaged Poynting vector for frequency-domain data associated to a 2D monitor,
665665
projected to the direction normal to the monitor plane."""
666666

@@ -677,27 +677,36 @@ def poynting(self) -> ScalarFieldDataArray:
677677
e2_h1 = e2 * h1.conj()
678678

679679
e_x_h_star = e1_h2 - e2_h1
680-
poynting = 0.5 * np.real(e_x_h_star)
680+
return 0.5 * e_x_h_star
681681

682-
return poynting
682+
@property
683+
def poynting(self) -> ScalarFieldDataArray:
684+
"""Time-averaged Poynting vector for frequency-domain data associated to a 2D monitor,
685+
projected to the direction normal to the monitor plane."""
686+
return self.complex_poynting.real
683687

684688
def package_flux_results(self, flux_values: DataArray) -> Any:
685689
"""How to package flux"""
686690
return FluxDataArray(flux_values)
687691

688692
@cached_property
689-
def flux(self) -> FluxDataArray:
693+
def complex_flux(self) -> FluxDataArray:
690694
"""Flux for data corresponding to a 2D monitor."""
691695

692696
# Compute flux by integrating Poynting vector in-plane
693697
d_area = self._diff_area
694-
poynting = self.poynting
698+
poynting = self.complex_poynting
695699

696700
flux_values = poynting * d_area
697701
flux_values = flux_values.sum(dim=d_area.dims)
698702

699703
return self.package_flux_results(flux_values)
700704

705+
@cached_property
706+
def flux(self) -> FluxDataArray:
707+
"""Flux for data corresponding to a 2D monitor."""
708+
return self.complex_flux.real
709+
701710
@cached_property
702711
def mode_area(self) -> FreqModeDataArray:
703712
r"""Effective mode area corresponding to a 2D monitor.

tidy3d/plugins/microwave/impedance_calculator.py

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
import pydantic.v1 as pd
99

1010
from tidy3d.components.base import Tidy3dBaseModel
11+
from tidy3d.components.data.data_array import FreqDataArray, FreqModeDataArray, TimeDataArray
1112
from tidy3d.components.data.monitor_data import FieldTimeData
13+
from tidy3d.components.monitor import ModeMonitor, ModeSolverMonitor
1214
from tidy3d.constants import OHM
1315
from tidy3d.exceptions import ValidationError
1416
from tidy3d.log import log
@@ -60,9 +62,9 @@ def compute_impedance(self, em_field: MonitorDataTypes) -> IntegralResultTypes:
6062
AxisAlignedPathIntegral._check_monitor_data_supported(em_field=em_field)
6163

6264
# If both voltage and current integrals have been defined then impedance is computed directly
63-
if self.voltage_integral:
65+
if self.voltage_integral is not None:
6466
voltage = self.voltage_integral.compute_voltage(em_field)
65-
if self.current_integral:
67+
if self.current_integral is not None:
6668
current = self.current_integral.compute_current(em_field)
6769

6870
# If only one of the integrals has been provided, then the computation falls back to using
@@ -71,20 +73,31 @@ def compute_impedance(self, em_field: MonitorDataTypes) -> IntegralResultTypes:
7173
# a time signal, then it is real and flux corresponds to the instantaneous power. Otherwise
7274
# the input field is in frequency domain, where flux indicates the time-averaged power
7375
# 0.5*Re(V*conj(I)).
74-
if not self.voltage_integral:
75-
flux = em_field.flux
76+
# We explicitly take the real part, in case Bloch BCs were used in the simulation.
77+
flux_sign = 1.0
78+
# Determine flux sign
79+
if isinstance(em_field.monitor, ModeSolverMonitor):
80+
flux_sign = 1 if em_field.monitor.direction == "+" else -1
81+
if isinstance(em_field.monitor, ModeMonitor):
82+
flux_sign = 1 if em_field.monitor.store_fields_direction == "+" else -1
83+
84+
if self.voltage_integral is None:
85+
flux = flux_sign * em_field.complex_flux
7686
if isinstance(em_field, FieldTimeData):
77-
voltage = flux.abs / current
87+
impedance = flux / np.real(current) ** 2
7888
else:
79-
voltage = 2 * flux.abs / np.conj(current)
80-
if not self.current_integral:
81-
flux = em_field.flux
89+
impedance = 2 * flux / (current * np.conj(current))
90+
elif self.current_integral is None:
91+
flux = flux_sign * em_field.complex_flux
8292
if isinstance(em_field, FieldTimeData):
83-
current = flux.abs / voltage
93+
impedance = np.real(voltage) ** 2 / flux
8494
else:
85-
current = np.conj(2 * flux.abs / voltage)
86-
87-
impedance = voltage / current
95+
impedance = (voltage * np.conj(voltage)) / (2 * np.conj(flux))
96+
else:
97+
if isinstance(em_field, FieldTimeData):
98+
impedance = np.real(voltage) / np.real(current)
99+
else:
100+
impedance = voltage / current
88101
impedance = ImpedanceCalculator._set_data_array_attributes(impedance)
89102
return impedance
90103

@@ -101,6 +114,13 @@ def check_voltage_or_current(cls, val, values):
101114
@staticmethod
102115
def _set_data_array_attributes(data_array: IntegralResultTypes) -> IntegralResultTypes:
103116
"""Helper to set additional metadata for ``IntegralResultTypes``."""
117+
# Determine type based on coords present
118+
if "mode_index" in data_array.coords:
119+
data_array = FreqModeDataArray(data_array)
120+
elif "f" in data_array.coords:
121+
data_array = FreqDataArray(data_array)
122+
else:
123+
data_array = TimeDataArray(data_array)
104124
data_array.name = "Z0"
105125
return data_array.assign_attrs(units=OHM, long_name="characteristic impedance")
106126

0 commit comments

Comments
 (0)