|  | 
| 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