|
7 | 7 | import numpy as np
|
8 | 8 | import pydantic.v1 as pd
|
9 | 9 |
|
| 10 | +from tidy3d import ModeData |
10 | 11 | from tidy3d.components.base import Tidy3dBaseModel, cached_property
|
11 | 12 | from tidy3d.components.data.data_array import FreqDataArray
|
12 | 13 | from tidy3d.components.data.monitor_data import MonitorData
|
13 | 14 | from tidy3d.components.data.sim_data import SimulationData
|
14 | 15 | from tidy3d.components.microwave.data.monitor_data import AntennaMetricsData
|
| 16 | +from tidy3d.constants import C_0 |
15 | 17 | from tidy3d.log import log
|
16 | 18 | from tidy3d.plugins.smatrix.component_modelers.terminal import TerminalComponentModeler
|
17 | 19 | from tidy3d.plugins.smatrix.data.base import AbstractComponentModelerData
|
@@ -117,6 +119,65 @@ def smatrix(
|
117 | 119 | )
|
118 | 120 | return smatrix_data
|
119 | 121 |
|
| 122 | + def change_port_reference_planes( |
| 123 | + self, smatrix: MicrowaveSMatrixData, port_shifts: np.array |
| 124 | + ) -> MicrowaveSMatrixData: |
| 125 | + """ |
| 126 | + Performs S-parameter de-embedding by shifting reference planes `port_shifts` um. |
| 127 | +
|
| 128 | + Parameters |
| 129 | + ---------- |
| 130 | + smatrix: :class:`.MicrowaveSMatrixData` |
| 131 | + S-parameters before reference planes are shifted. |
| 132 | + port_shifts: np.array |
| 133 | + Numpy array of shifts of wave ports' reference planes. |
| 134 | +
|
| 135 | + Returns |
| 136 | + ------- |
| 137 | + :class:`MicrowaveSMatrixData` |
| 138 | + De-embedded S-parameters with respect to updated reference frames. |
| 139 | + """ |
| 140 | + |
| 141 | + # get s-parameters with respect to current `WavePort` locations |
| 142 | + S_matrix = smatrix.data.values |
| 143 | + S_new = np.zeros_like(S_matrix, dtype=complex) |
| 144 | + N_freq, N_ports, _ = S_matrix.shape |
| 145 | + |
| 146 | + if len(port_shifts) != N_ports: |
| 147 | + raise ValueError( |
| 148 | + "A vector of WavePort reference plane shifts has to match a total number of Waveports in a simulation." |
| 149 | + f"The expected length was {N_ports}, while a vector of {len(port_shifts)} was provided." |
| 150 | + ) |
| 151 | + |
| 152 | + # extract `ModeSource` directions to ensure correct sign is used |
| 153 | + port_shifts = np.ravel(port_shifts) |
| 154 | + directions = np.array([1 if port.direction == "+" else -1 for port in self.modeler.ports]) |
| 155 | + directions = np.ravel(directions) |
| 156 | + |
| 157 | + # pre-allocate memory for effective propagation constants |
| 158 | + kvecs = np.zeros((N_ports, N_freq), dtype=complex) |
| 159 | + |
| 160 | + # extract mode data |
| 161 | + key = self.data.keys_tuple[0] |
| 162 | + data = self.data[key].data |
| 163 | + modes_data = tuple(mode_data for mode_data in data if isinstance(mode_data, ModeData)) |
| 164 | + |
| 165 | + # infer propagation constants from modal data |
| 166 | + for i, mode_data in enumerate(modes_data): |
| 167 | + n_complex = mode_data.n_complex |
| 168 | + kvecs[i, :] = (2 * np.pi * n_complex.f * n_complex / C_0).squeeze() |
| 169 | + |
| 170 | + # updated/de-embed S-parameters with respect to shifted reference planes |
| 171 | + for i in range(N_freq): |
| 172 | + phase = kvecs[:, i] * port_shifts * directions |
| 173 | + P_inv = np.diag(np.exp(-1j * phase)) |
| 174 | + S_new[i, :, :] = P_inv @ S_matrix[i, :, :] @ P_inv |
| 175 | + |
| 176 | + # create a new Port Data Array |
| 177 | + smat_data = TerminalPortDataArray(S_new, coords=smatrix.data.coords) |
| 178 | + |
| 179 | + return smatrix.updated_copy(data=smat_data) |
| 180 | + |
120 | 181 | @pd.root_validator(pre=False)
|
121 | 182 | def _warn_rf_license(cls, values):
|
122 | 183 | log.warning(
|
|
0 commit comments