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