File: circuit.py

package info (click to toggle)
python-qmix 1.0.6-11
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 9,460 kB
  • sloc: python: 4,312; makefile: 215
file content (618 lines) | stat: -rw-r--r-- 24,595 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
""" This module contains classes and functions to describe the embedding 
circuit.

**Description:**
    
    In experimental systems, SIS junctions are embedded within complex RF 
    networks. These networks are referred to as the embedding circuit. Since
    all of the components in these embedding circuits are **linear**, the 
    embedding circuit can be reduced to a **Thevenin equivalent circuit**, with
    one for **each tone and harmonic.**

    To fully describe the embedding circuit, 3 bits of information are needed
    for each signal that is applied to the junction:

        1. the frequency of the applied signal,
        2. the Thevenin voltage of the embedding circuit at this freq., and
        3. the Thevenin impedance of the embedding circuit at this freq.

The main class in this module (``EmbeddingCircuit``) allows the user to 
build an embedding circuit in the proper format.
    
"""

import re

import numpy as np
import scipy.constants as sc

from qmix.misc.terminal import cprint


# EMBEDDING CIRCUIT CLASS ----------------------------------------------------

# TODO: Use @property decorators to avoid setters/getters

class EmbeddingCircuit(object):
    """Class for building and describing the embedding circuit. 

    This includes the frequencies, Thevenin voltages and Thevenin impedances 
    of all signals applied to the junction.

    Note:

        Unless specified otherwise, **all input values are normalized**. The
        voltages are normalized to the gap voltage, resistances are normalized
        to the normal-state resistance, currents are normalized to gap current,
        and frequencies are normalized to the gap frequency. Refer to the
        argument descriptions below to see if the value should be normalized or
        not.

        Creating an instance of this class will set the sizes and data types
        of all of the class attributes, but the actual values will need to be
        set manually. In this way, this class is sort of like a fancy struct.
        The class attributes that have to be set manually are:

           - ``freq``:  frequencies (normalized to the gap frequency)
           - ``vt``:  Thevenin voltage (normalized to the gap voltage)
           - ``zt``:  Thevenin impedance (normalized to the normal resistance)

        See the attribute descriptions below for information about how to do
        this.

    Example:

        Here we will create an instance of the embedding circuit class with
        2 tones and 3 harmonics:

        >>> cct1 = EmbeddingCircuit(2, 3, name='Test 1')
        >>> print(cct1)
        Embedding circuit (Tones:2, Harmonics:3): Test 1

        Once initialized, we can begin defining the properties of the
        embedding circuit. I normally start with the frequencies:

        >>> cct1.freq[1] = 0.30  # first tone
        >>> cct1.freq[2] = 0.32  # second tone

        Then, we have to set the voltages and impedances for all of the
        different signals. For example, for the 1st harmonic of the 1st tone:

        >>> cct1.vt[1,1] = 0.5             # Thevenin voltage
        >>> cct1.zt[1,1] = 0.3 + 1j * 0.1  # Thevenin impedance

        This has to be done for each signal (a total of 6 times in this case).

        In order to use non-normalized values (e.g., set the available power of
        a signal in units [W]), we need to define the electrical properties of
        the junction during initialization. For example:

        >>> cct2 = EmbeddingCircuit(1, 1, vgap=2.8e-3, rn=14.)

        You can now set the frequencies. E.g.:

        >>> cct2.set_freq(250, f=1, units='GHz')
        >>> round(cct2.freq[1], 4)
        0.3693

        Once an impedance has been set, you can also set the power of the
        signal using absolute units. Here we will set the available power for
        the first harmonic of the first tone to 10 nW (10e-9 W).

        >>> cct2.zt[1,1] = 0.5
        >>> cct2.set_available_power(10, 1, 1, 'nW')

        And we can then display this power in units [dBm].

        >>> cct2.available_power(1, 1, 'dBm')
        -50.0

    Args:
        num_f (int, optional, default is 1): Number of fundamental frequencies
            (tones) applied to the junction.
        num_p (int, optional, default is 1): Number of harmonics included for
            each tone.
        vb_min (float, optional, default is 0): Minimum bias voltage.
        vb_max (float, optional, default is 2): Maximum bias voltage.
        vb_npts (int, optional, default is 201): Number of points in bias
            voltage sweep.
        fgap (float, optional): Gap frequency of the junction in units [Hz].
            This is equal to ``e*Vgap/h``, where ``e`` is the charge of an
            electron, ``Vgap`` is the gap voltage, and ``h`` is the Planck
            constant. ``fgap`` is used to normalize and de-normalize frequency
            values (that't it!).
        vgap (float, optional): Gap voltage of the junction in units [V]. This is the
            voltage where the sharp non-linearity in the DC I-V curve occurs 
            (a.k.a., the transition voltage).
        rn (float, optional): Normal-state resistance of the junction in units [ohms].
            This is the resistance of the junction at a temperature slight 
            above the critical temperature. It is found by calculating the 
            dynamic resistance of the DC I-V curve above the gap voltage.
        name (str, optional): Name used to describe this specific instance.

    Attributes:
        freq (numpy.ndarray): An array for the frequencies normalized to the
            gap frequency. This is a 1-dimensional array of real numbers. It
            contains the frequencies of all of the fundamental tones that
            are applied to the junction. The normalized frequency is
            equivalent to the normalized photon voltage. The photon voltage is
            defined as ``hf/e``, where ``h`` is the Planck constant, ``f`` is
            the frequency of the fundamental tone, and ``e`` is the charge of
            an electron. Note that this array is 1-based, meaning that the
            frequency of the 1st tone is found in ``.freq[1]``. (The index
            represents the tone number.) **This attribute must be set after
            initialization!**
        vt (numpy.ndarray): An array for the Thevenin voltage normalized to the
            gap voltage. This is a 2-dimensional array of complex values. It
            contains the voltages for all of the Thevenin equivalent circuits
            (which describe the embedding circuit). In order, the indices are:
            ``.vt[f,p]`` for tone ``f`` and harmonic ``p``. Note that this
            array is 1-based, meaning that the voltage for tone number 2 /
            harmonic number 3 is stored in ``vt[2,3]``. **This attribute must
            be set after initialization!**
        zt (numpy.ndarray): An array for the Thevenin impedance array
            normalized to the normal-state resistance. This is a 2-dimensional
            array of complex values. It contains the impedances of all of the
            Thevenin equivalent circuits (which describe the embedding
            circuit). In order, the indices are: ``.zt[f,p]``, for tone
            ``f`` and harmonic ``p``. Note that this array is 1-based, meaning
            that the impedance for tone number 2, and harmonic number 3 is
            stored in ``zt[2,3]``. **This attribute must be set after
            initialization!**
        num_f (int): Number of fundamental frequencies (tones) applied to the 
            junction.
        num_p (int): Number of harmonics included for each tone.
        num_n (int): Total number of signals. This is equal to ``num_f*num_p``.
        fgap (float): Gap frequency of the junction in units [Hz]. This is 
            equal to ``e*Vgap/h``, where ``e`` is the charge of an electron, 
            ``Vgap`` is the gap voltage, and ``h`` is the Planck constant. Note
            that ``E=fgap*e`` is the energy required to break Cooper pairs, so
            at frequencies above the ``fgap`` the superconductors will begin to
            become lossy. Here, ``fgap`` is used to normalize and de-normalize
            frequency values (that't it).
        vgap (float): Gap voltage of the junction in units [V]. This is the 
            voltage where the sharp non-linearity in the DC I-V curve occurs 
            (i.e., the transition voltage). This value is used to normalize
            and de-normalize voltages.
        igap (float): Gap current of the junction in units [A]. This is equal 
            to ``vgap/rn``. This value is used to normalize and de-normalize 
            currents.
        rn (float): Normal-state resistance of the junction in units [ohms]. 
            This is the resistance of the junction at a temperature slightly
            above the critical temperature. It is found by calculating the 
            dynamic resistance of the DC I-V curve above the gap voltage. This 
            value is used to normalize and de-normalize resistances.
        vb (numpy.ndarray): Array for the DC bias voltage sweep. This value is
            normalized to the gap voltage.
        vb_npts (int): Number of points in the bias voltage sweep.
        name (str): A name to describe this instance of the embedding circuit
            class.
        comment (list): A list of comments to describe the different signals.
            For example, to describe tone 1/harmonic 1 as the local-oscillator
            signal, you might use ``cct.comment[1][1] = "LO"``. This has to be
            set after the initialization of the EmbeddingCircuit class.

    """

    def __init__(self, num_f=1, num_p=1, vb_min=0, vb_max=2, vb_npts=201, fgap=None, vgap=None, rn=None, name=''):

        self.name = name  # used to identify the instance

        # Check input
        assert num_f in [1, 2, 3, 4], \
            "Number of tones (num_f) must be equal to 1, 2, 3, or 4."
        assert num_p >= 1 and isinstance(num_p, int), \
            "Number of harmonics (num_p) must be an integer equal or greater than 1."
        assert isinstance(vb_npts, int) and vb_npts >= 1, \
            "Number of bias points (vb_npts) must be an integer greater or equal to 1."
        assert vb_max >= vb_min, \
            "Maximum voltage (vb_max) must be larger than the minimum voltage (vb_min)."

        # Number of tones/harmonics
        self.num_f = int(num_f)
        self.num_p = int(num_p)
        self.num_n = self.num_f * self.num_p

        # Junction properties (optional)
        self.fgap = None
        self.vgap = None
        self.igap = None
        self.rn = None
        # Gap voltage
        if vgap is not None:
            self.vgap = float(vgap)
        elif fgap is not None:
            self.vgap = float(fgap) * sc.h / sc.e
        # Gap frequency
        if fgap is not None:
            self.fgap = float(fgap)
        elif vgap is not None:
            self.fgap = float(vgap) * sc.e / sc.h
        # Normal-state resistance
        if rn is not None:
            self.rn = float(rn)
        if vgap is not None and rn is not None:
            self.igap = vgap / rn

        # Initialize embedding circuit
        self.freq = np.zeros((num_f + 1), dtype=float)
        self.vt = np.zeros((num_f + 1, num_p + 1), dtype=complex)
        self.zt = np.zeros((num_f + 1, num_p + 1), dtype=complex)

        # Set up bias voltage sweep
        self.vb_npts = vb_npts
        self.vb = np.linspace(vb_min, vb_max, vb_npts)

        # Initialize comment list to label tones/harmonics (optional)
        self.comment = []
        for f in range(num_f + 1):
            self.comment.append(['' for _ in range(num_p + 1)])

    def __str__(self):

        if self.name != '':
            name = ": " + self.name
        else:
            name = ""

        return "Embedding circuit (Tones:{}, Harmonics:{}){}".format(self.num_f, self.num_p, name)

    def __repr__(self):  # pragma: no cover

        return self.__str__()

    def initialize_vj(self):
        """Initialize junction voltage array.

        Returns an empty matrix that is the shape that ``vj`` should be (the
        voltage across the junction). Strictly speaking, ``vj`` shouldn't be
        saved within this class, but it is okay for this class to initialize
        ``vj`` since it has all the data about what the matrix sizes should be.

        This function is useful when you want to set the voltage across the 
        junction directly (skipping the harmonic balance procedure).

        Returns:
            ndarray: An empty matrix for the junction voltage

        """

        return np.zeros((self.num_f + 1, self.num_p + 1, self.vb_npts), dtype=complex)

    def available_power(self, f=1, p=1, units='W'):
        """Return available power of tone ``f`` and harmonic ``p``.

        Note: 
        
            Gap voltage and normal resistance must be set prior to using this
            method. If they are not, an error will be raised.

        Args:
            f (int, optional, default is 1): Tone index number.
            p (int, optional, default is 1): Harmonic index number.
            units (str, optional, default is 'W'): Units for power. One of 'W',
                'mW', 'uW', 'nW', 'pW', 'fW', 'dBm', or 'dBW'.

        Returns:
            float: Available power in specified units

        """

        assert self.vgap is not None, 'Gap voltage not set'
        assert self.rn is not None, 'Normal resistance not set'

        v_v = self.vt[f, p] * self.vgap
        r_ohms = self.zt[f, p].real * self.rn

        if r_ohms != 0.:
            power = np.abs(v_v) ** 2 / r_ohms / 8.
        else:
            power = 0

        if units.lower() == 'w':
            return power
        elif units.lower() == 'mw':
            return power / sc.milli
        elif units.lower() == 'uw':
            return power / sc.micro
        elif units.lower() == 'nw':
            return power / sc.nano
        elif units.lower() == 'pw':
            return power / sc.pico
        elif units.lower() == 'fw':
            return power / sc.femto
        elif units.lower() == 'dbm':
            return 10 * np.log10(power * 1e3)
        elif units.lower() == 'dbw':
            return 10 * np.log10(power)
        else:
            raise ValueError('Not a recognized unit for power.')

    def set_available_power(self, power, f=1, p=1, units='W'):
        """Set available power of tone ``f`` and harmonic ``p``.

        This method will set the Thevenin voltage in order to provide the 
        correct power level.

        Note: 

            The gap voltage, normal resistance and Thevenin impedance must be 
            set prior to using this method. Otherwise, an assertion error will
            be raised.

        Args:
            power (float): power, in given units
            f (int, optional, default is 1): tone
            p (int, optional, default is 1): harmonic
            units (str, optional, default is 'W'): units for power. One of 'W',
            'mW', 'uW', 'nW', 'pW', 'fW', 'dBm', or 'dBW'.

        """

        assert self.vgap is not None, 'Gap voltage not set'
        assert self.rn is not None, 'Normal resistance not set'
        assert self.zt[f, p] != 0, 'Embedding impedance not set'

        if units.lower() == 'w':
            pass
        elif units.lower() == 'mw':
            power *= sc.milli
        elif units.lower() == 'uw':
            power *= sc.micro
        elif units.lower() == 'nw':
            power *= sc.nano
        elif units.lower() == 'pw':
            power *= sc.pico
        elif units.lower() == 'fw':
            power *= sc.femto
        elif units.lower() == 'dbm':
            power = 10 ** (power / 10) * 1e-3
        elif units.lower() == 'dbw':
            power = 10 ** (power / 10)
        else:
            raise ValueError('Unit not recognized.')

        # Thevenin resistance, in units [ohms]
        r_ohms = self.zt[f, p].real * self.rn

        # Thevenin voltage, in units [V]
        volt_v = np.sqrt(8 * power * r_ohms)

        self.vt[f, p] = volt_v / self.vgap

    def set_alpha(self, alpha, f=1, p=1, zj=0.66):
        """Set the drive level of tone ``f`` and harmonic ``p`` (approximate).

        This method guesses what the Thevenin voltage should be in order to get
        the desired drive level, but you won't actually know what the drive
        level is until you run the simulation.

        Note: 

            Frequency and Thevenin impedance must be set prior to using
            this method. Otherwise, an assertion error will be raised.

        Args:
            alpha (float): drive level, alpha = voltage / vph
            f (int, optional, default is 1): tone
            p (int, optional, default is 1): harmonic
            zj (float, optional, default is 0.66): the impedance to assume for
                the junction (normalized to the normal-state resistance). This
                value will depend on frequency and pump level.

        """

        assert self.zt[f, p] != 0, 'Embedding impedance must be defined!'
        assert self.freq[f] != 0, 'Frequency must be defined!'

        self.vt[f, p] = alpha * self.freq[f] * (self.zt[f, p] / zj + 1) 

    def set_freq(self, value, f=1, units='Hz'):
        """Set the frequency of tone ``f``.

        Normally, this can be done by setting the value of the attribute
        directly. E.g.:

        >>> cct = EmbeddingCircuit(1, 1, vgap=2.8e-3, rn=14.)
        >>> cct.freq[1] = 0.5

        However, if you would instead like to use non-normalized units, this
        method can be very handy. E.g.:

        >>> cct.set_freq(350, f=1, units='GHz')
        >>> round(cct.freq[1], 2)
        0.52

        Note:

            The gap frequency or the gap voltage must be defined in order to
            use this method.

        Args:
            value (float): value to set using given units
            f (int, optional, default is 1): tone number
            units (str, optional, default is 'Hz'): units for input value, 'Hz'
                for frequency in units Hz, 'V' for photon voltage in units V, 
                and 'norm' for either normalized photon voltage or normalized 
                frequency. SI prefixes can also be included: 'MHz', 'GHz',
                'THz', and 'mV'.

        """

        if units.lower != 'norm':
            assert self.fgap is not None, 'Gap frequency must be defined!'

        if units.lower() == 'hz':
            self.freq[f] = value / self.fgap
        elif units.lower() == 'mhz':
            self.freq[f] = value * sc.mega / self.fgap
        elif units.lower() == 'ghz':
            self.freq[f] = value * sc.giga / self.fgap
        elif units.lower() == 'thz':
            self.freq[f] = value * sc.tera / self.fgap
        elif units.lower() == 'v':
            self.freq[f] = value / self.vgap
        elif units.lower() == 'mv':
            self.freq[f] = value * sc.milli / self.vgap
        elif units.lower() == 'norm':
            self.freq[f] = value
        else:
            raise ValueError('Units not recognized.')

    def set_name(self, name, f=1, p=1):
        """ Set a name for a given tone and harmonic combination.

        This has no effect on the simulation. It's just nice for keeping track
        of the different signals.

        Args:
            name (str): name of tone/harmonic
            f (int, optional, default is 1): frequency number to set
            p (int, optional, default is 1): harmonic number to set

        """

        self.comment[f][p] = name

    def print_info(self):
        """Print information about the embedding circuit to the terminal."""

        print(self)

        str1 = "   f={0}, p={1}\t\t\tfreq = {2:.4f} x {1}\t\t{3}"
        str2 = "   f={0}, p={1}\t\t\t{2:.1f} GHz x {1}\t\t{3}"
        str3 = "\tThev. voltage:\t\t{:.4f} * Vgap"
        str6 = "\t              \t\t{:.4f} * Vph"
        str4 = "\tThev. impedance:\t{:.2f} * Rn"
        str7 = "\tAvail. power:   \t{:.2E} W"
        str8 = "\t                \t{:.3f} dBm"

        if self.fgap is None or self.vgap is None or self.rn is None:
            for f in range(1, self.num_f + 1):
                for p in range(1, self.num_p + 1):
                    freq = self.freq[f]
                    vt = self.vt[f, p]
                    zt = self.zt[f, p]
                    cprint(str1.format(f, p, freq, self.comment[f][p]), 'GREEN')
                    print(str3.format(float(vt.real)))
                    print(str4.format(zt))
        else:
            for f in range(1, self.num_f + 1):
                for p in range(1, self.num_p + 1):
                    fq = self.freq[f] * self.fgap / 1e9
                    vt = self.vt[f, p]
                    zt = self.zt[f, p]
                    with np.errstate(divide='ignore'):
                        power_w = self.available_power(f, p, units='W')
                        power_dbm = self.available_power(f, p, units='dBm')
                    cprint(str2.format(f, p, fq, self.comment[f][p]), 'GREEN')
                    print(str3.format(float(vt.real)))
                    print(str6.format(float(vt.real / (self.freq[f] * p))))
                    print(str4.format(zt))
                    print(str7.format(power_w))
                    print(str8.format(power_dbm))
        print("")

    def save_info(self, filename='embedding-circuit.txt'):
        """Save this embedding circuit to a text file.

        This text file can then be read in by ``read_circuit`` in order to
        regenerate the embedding circuit.

        Args:
            filename (string): Filename for embedding circuit file

        """

        str1 = "   f={0}, p={1}\t\t\tfreq = {2:.4f} x {1}\n"
        str2 = "\tThev. voltage:\t\t{:.4f} V / V_gap\n"
        str3 = "\tThev. impedance:\t{:.2f} ohms / R_N\n"

        with open(filename, 'w') as fout:
            fout.write("Embedding Circuit:\n")
            fout.write("\tNumber of tones:     {0}\n".format(self.num_f))
            fout.write("\tNumber of harmonics: {0}\n".format(self.num_p))

            for f in range(1, self.num_f + 1):
                for p in range(1, self.num_p + 1):
                    freq = self.freq[f]
                    vt = self.vt[f, p]
                    zt = self.zt[f, p]
                    fout.write(str1.format(f, p, freq))
                    fout.write(str2.format(vt))
                    fout.write(str3.format(zt))

    def lock(self):
        """Make all Numpy arrays contained within this class unwriteable.

        This can be useful for debugging. An error will be raised if you try to
        change the values of the Numpy arrays while they are locked.

        """

        self.freq.flags.writeable = False
        self.vt.flags.writeable = False
        self.zt.flags.writeable = False
        self.vb.flags.writeable = False

    def unlock(self):
        """Make all Numpy arrays contained within this class writeable.
        
        This can be useful for debugging.

        """

        self.freq.flags.writeable = True
        self.vt.flags.writeable = True
        self.zt.flags.writeable = True
        self.vb.flags.writeable = True

    @property
    def vph(self):
        """Get photon voltage.

        For backwards compatibility.

        """

        return self.cct


def read_circuit(filename):
    """Build an embedding circuit from an embedding circuit file.

    This function will build an instance of the ``EmbeddingCircuit`` class 
    based on a file previously generated by the
    ``EmbeddingCircuit.save_info`` method.

    Args:
        filename (str): filename of the embedding circuit file

    Returns:
        qmix.circuit.EmbeddingCircuit: instance of the embedding circuit class

    """

    with open(filename, 'r') as fin:
        data = fin.readlines()

    num_f = int(data[1].split()[-1])
    num_p = int(data[2].split()[-1])

    cct = EmbeddingCircuit(num_f, num_p)

    data = data[3:]

    for i, line in enumerate(data):
        split_line = line.split()
        if split_line[0][0] == 'f':
            f = int(re.search(r'\d+', split_line[0]).group())
            p = int(re.search(r'\d+', split_line[1]).group())
            freq = float(split_line[4])
            vt = complex(data[i + 1].split()[2])
            zt = complex(data[i + 2].split()[2])
            cct.freq[f] = freq
            cct.vt[f, p] = vt
            cct.zt[f, p] = zt

    return cct