Source code for west_ic_antenna.topica

# -*- coding: utf-8 -*-
"""
TOPICA Utilities

.. module:: west_ic_antenna.topica

.. autosummary::
    :toctree: generated/
    
    TopicaResult
"""
import numpy as np
import skrf as rf

[docs] class TopicaResult: """TOPICA result. TOPICA result object, which provides convenience methods. Parameters ---------- filename : str filename of the Impedance ascii file generated by TOPICA z0 : float, optional characteristic impedance of the port in Ohm (default: 50) deemb : float, optionnal de-embedding length to take into account (in meter) """
[docs] def __init__(self, filename: str, z0: float = 50, deemb: float = 0): self.filename = filename self.nbPorts = len(self.z) self._z0 = z0 self._deemb = deemb
@property def z0(self) -> np.ndarray: """Characteristic Impedance of the TOPICA Model's ports Returns ------- z0 : array """ return self._z0 @z0.setter def z0(self, z0): """Set the characteristic impedance of the ports in Ohm. Parameters ---------- z0 : :class:`numpy.ndarray` of length n characteristic impedance for network """ self._z0 = z0 @property def raw_data(self) -> str: """raw TOPICA data from txt file Returns ------- data : array raw TOPICA data : [index_row, index_col, real_z, z_imag] """ return np.loadtxt(self.filename, skiprows=3) @property def z(self) -> np.ndarray: """Get the impedance matrix as an NxN array, N being the number of ports. Returns ---------- z : :class:`numpy.ndarray` impedance matrix """ data = self.raw_data # fill the Z matrix (Nport x Nport) z = np.zeros((int(np.sqrt(data.shape[0])), int(np.sqrt(data.shape[0]))), dtype='complex') for id1 in range(data.shape[0]): z[int(data[id1, 0])-1, int(data[id1, 1])-1] = complex(data[id1, 2], data[id1, 3]) return(z) @property def s(self) -> np.ndarray: """Get the scattering parameters as an NxN array, N being the number of ports. Does NOT take into account possible deembbeding. Returns ------- s : :class:`numpy.ndarray` of shape n x n scattering parameters of the TOPICA result """ # Zref = np.diag(np.repeat(self.z0, self.nbPorts)) # G = 1/np.sqrt(np.real(self.z0)) # Gref = np.diag(np.repeat(G, self.nbPorts)) # # Z to S formulae # s = Gref @ (self.z - Zref) @ (np.linalg.inv(self.z + Zref)) @ (np.linalg.inv(Gref)) _z = self.z.reshape(1, self.nbPorts, self.nbPorts) s = rf.z2s(_z, z0=self.z0) return(s)
[docs] def to_network(self, skrf_frequency: rf.Frequency, name: str = 'front-face') -> rf.Network: """Convert into a skrf Network. Assume the scattering parameters of the network are the same for all the frequencies of the network. Parameters ---------- skrf_frequency : :class:`skrf.frequency.Frequency` Frequency of the network (for compatibility with skrf) name : string, optional name of the network. Default is 'front-face' Returns ------- network : :class:`skrf.network.Network` """ network = rf.Network(name=name) network.s = np.tile(self.s, (len(skrf_frequency), 1, 1)) network.z0 = self.z0 network.frequency = skrf_frequency # take into deembbeding if necessary if self._deemb != 0: # create a coaxial line of length = deemb coax_media = rf.DefinedGammaZ0(network.frequency, z0=self.z0) # Create a circuit in which we connect the inverse of the coaxial # lines to each ports coax1 = coax_media.line(d=self._deemb, unit='m', name='line1').inv coax2 = coax_media.line(d=self._deemb, unit='m', name='line2').inv coax3 = coax_media.line(d=self._deemb, unit='m', name='line3').inv coax4 = coax_media.line(d=self._deemb, unit='m', name='line4').inv port1 = rf.Circuit.Port(network.frequency, 'port1', z0=self.z0) port2 = rf.Circuit.Port(network.frequency, 'port2', z0=self.z0) port3 = rf.Circuit.Port(network.frequency, 'port3', z0=self.z0) port4 = rf.Circuit.Port(network.frequency, 'port4', z0=self.z0) cnx = [ [(port1, 0), (coax1, 0)], [(coax1, 1), (network, 0)], [(port2, 0), (coax2, 0)], [(coax2, 1), (network, 1)], [(port3, 0), (coax3, 0)], [(coax3, 1), (network, 2)], [(port4, 0), (coax4, 0)], [(coax4, 1), (network, 3)], ] network = rf.Circuit(cnx).network.copy() return(network)
[docs] def write_touchstone(self, filename: str, skrf_frequency: rf.Frequency, name: str = 'front-face'): """Write the scattering parameters into a Touchstone file format. (.sNp where N is the number of ports). Parameters ---------- filename : string Touchstone filename skrf_frequency : :class: ' skrf.frequency' skrf Frequency object name : string: name of the network """ network = self.to_network(skrf_frequency, name=name) network.write_touchstone(filename=filename, write_z0=True)
[docs] def Rc(self, I: list = [1, -1, -1, 1]) -> np.ndarray: """ Calculate the coupling resistance of the TOPICA Z-matrix for a given current excitation. Coupling resistance is defined as in [#]_ Parameters ---------- I : list Current excitation at port. Default is [1, -1, -1, 1], ie. dipole phasing of 2x2 straps antenna Returns ------- Rc : numpy.ndarray Coupling Resistance References ---------- .. [#] W. Helou, L. Colas, J. Hillairet et al., Fusion Eng. Des. 96–97 (2015) 5–8. doi:10.1016/j.fusengdes.2015.01.005. """ N = len(I) # number of ports V = self.z @ I Pt = np.real(V.conj() @ I) / 2 Is = np.sqrt(np.sum(np.abs(I)) / N) # divided by 2 as the current average is made on 4 straps (2 per side) Rc = Pt / (2*Is**2) return Rc