1
2 '''
3 Module to export network parameters to the Touchstone v1.1 format.
4
5 The Touchstone file format can contain S, Y, Z, H or G parameters,
6 though in EMPro, it's primarily used to export simulated S parameters.
7 The following example retrieves the S parameters from the first simulation
8 of the active project (a two port), and exports it to results.s2p.
9 The default reference impedance is 50 ohms, and complex numbers are
10 always written using the real-imaginary format.
11
12 Example:
13
14 from empro.toolkit import touchstone, portparam
15 S = portparam.getSMatrix(sim=1)
16 touchstone.write('results.s2p', S)
17 '''
18
19 FORMATS = DB, MA, RI = 'DB', 'MA', 'RI'
20
21 -def write(path, matrix, parameter="S", reference=50, format=RI):
22 '''
23 write(path, matrix [, parameter="S" [, reference=50 [, format=MA]]])
24 -> None
25
26 export an frequency dependent parameter matrix to a Touchstone file:
27 - path: path to touchstone file, with or without the .snp extension.
28 - matrix: square matrix of one-dimensional datasets
29 - parameter: parameter indicator as written in the option line.
30 Legal values are: S, Y, Z, H, G. The default is S.
31 - reference: reference impedance (ohms) as written in the option line.
32 The default reference resistance is 50 ohms.
33 - format: format of complex data. Legal values are DB (dB-angle),
34 MA (magnitude-angle) and RI (real-imaginary). Angles are in
35 degrees. The default is MA.
36 '''
37 import math
38 assert len(matrix.dimensions()) == 1, "Touchstone format expects matrices with one-dimensional datasets"
39 available_ports = set(matrix.rows) | set(matrix.cols)
40 assert all(isinstance(x, int) or isinstance(x, long) for x in available_ports), "Touchstone format expects all port indices to be integer: %r" % list(available_ports)
41 assert all(x > 0 for x in available_ports), "Touchstone format expects all port numbers to be greater than zero: %r" % list(available_ports)
42 assert format in FORMATS, "format %r must be one of %r" % (format, FORMATS)
43
44 n = max(available_ports)
45 all_ports = tuple(range(1, n + 1))
46 zero = matrix.values()[0] * 0
47 freq, = matrix.dimensions()
48 fScale = 1e-9
49
50 extension = '.s%dp' % n
51 if not (path.lower().endswith(extension) or path.lower().endswith('.snp')):
52 path += extension
53
54 angle = lambda z: (math.degrees(math.atan2(z.imag, z.real)), 0)[z == 0]
55 db = lambda z: 20 * math.log10(max(abs(z), 1e-50))
56 formatters = {
57 DB: lambda z: "%r %r" % (db(z), angle(z)),
58 MA: lambda z: "%r %r" % (abs(z), angle(z)),
59 RI: lambda z: "%r %r" % (z.real, z.imag)
60 }
61 formatter = formatters[format]
62
63 def value(i, j, k):
64 z = complex(matrix.get((i, j), zero)[k])
65 return formatter(z)
66
67 def comment(i, j, p=parameter):
68 x, y = {
69 DB: ('dB', 'ang'),
70 MA: ('mag', 'ang'),
71 RI: ('Re', 'Im')
72 }[format]
73 return "%(x)s%(p)s%(i)s%(j)s %(y)s%(p)s%(i)s%(j)s" % vars()
74
75 def block(fun, linesep='\n', **kwargs):
76 max_per_line = 4
77 if n <= 2:
78 return " ".join(fun(i, j, **kwargs) for j in all_ports for i in all_ports)
79 lines = []
80 for i in all_ports:
81 for j0 in range(0, n, max_per_line):
82 lines.append(" ".join(fun(i, j, **kwargs) for j in all_ports[j0:j0+max_per_line]))
83 lines.append("")
84 return linesep.join(lines)
85
86 out = open(path, "w")
87 out.write("# GHZ %(parameter)s %(format)s R %(reference)r\n" % vars())
88 out.write("! freq %s\n" % block(comment, linesep='\n! '))
89 for k, f in enumerate(freq):
90 out.write("%r %s\n" % (f * fScale, block(value, k=k)))
91 out.close()
92