Calibration Examples

One-Port Reflect Only

Create the calibration from measurements of short, open and load standards.

from libvna.cal import Calset, CalType, Solver
import numpy as np


# Set the calibration frequency points.
fmin = 1.0e+06
fmax = 1.0e+09
f_vector = np.logspace(np.log10(fmin), np.log10(fmax), num=10)

# Set up libvna.cal's error term solver.
calset = Calset()
solver = Solver(calset, CalType.E12, rows=1, columns=1,
                frequency_vector=f_vector)

# Add measurement of the short standard.
m = [
    [[-1.01169-0.08178j]],
    [[-1.05243-0.16674j]],
    [[-1.20666-0.28098j]],
    [[-1.54265-0.20526j]],
    [[-1.66092+0.28266j]],
    [[-1.29731+0.70918j]],
    [[-0.80194+0.97590j]],
    [[+0.15821+1.10673j]],
    [[+0.77049-0.57232j]],
    [[+0.29792+1.14489j]]
]
solver.add_single_reflect(m, s11=-1)

# Add measurement of the open standard.
m = [
    [[+0.98810-0.09858j]],
    [[+0.94704-0.20293j]],
    [[+0.79152-0.35892j]],
    [[+0.44984-0.37301j]],
    [[+0.30572-0.07716j]],
    [[+0.55152-0.05051j]],
    [[+0.53161-0.51164j]],
    [[-0.31312-0.83148j]],
    [[-0.67507+0.78451j]],
    [[-0.58037-0.58961j]]
]
solver.add_single_reflect(m, s11=1)

# Add measurement of the load standard.
m = [
    [[-0.01180-0.09001j]],
    [[-0.05269-0.18447j]],
    [[-0.20754-0.31917j]],
    [[-0.54626-0.28746j]],
    [[-0.67691+0.10631j]],
    [[-0.36976+0.33649j]],
    [[-0.12206+0.24276j]],
    [[-0.04274+0.12511j]],
    [[-0.02391+0.05943j]],
    [[-0.01977+0.02772j]]
]
solver.add_single_reflect(m, s11=0)

# Solve, add to Calset and save.
solver.solve()
solver.add_to_calset('1-port')
calset.save('1x1.vnacal')

Apply the calibration to a device under test.

from libvna.cal import Calset


# Load the calibration from file.
calset = Calset('1x1.vnacal')
calibration = calset.calibrations['1-port']
f_vector = calibration.frequency_vector

# Measured response:
measured = [
    [[-0.67645-0.04236j]],
    [[-0.71010-0.08205j]],
    [[-0.83118-0.10097j]],
    [[-1.00917+0.15441j]],
    [[-0.41429+0.62681j]],
    [[-0.52314-0.53839j]],
    [[-1.09587+0.39093j]],
    [[-0.13675+1.11155j]],
    [[+0.83937-0.45120j]],
    [[+0.21395+1.14235j]]
]

# Apply calibration and save in Touchstone format.
result = calibration.apply(f_vector, measured)
result.save('1x1-corrected.s1p')

One-Port Reflect Only with Measured Standards

Create the calibration from measurements of imperfect standards that have been measured on a more trusted instrument, loading the S-parameters of each standard from a file.

from libvna.cal import Calset, CalType, Solver, VectorParameter
from libvna.data import NPData, PType
import numpy as np


# Set the calibration frequency points.
fmin = 1.0e+06
fmax = 1.0e+09
f_vector = np.logspace(np.log10(fmin), np.log10(fmax), num=10)

# Set up libvna.cal's error term solver.
calset = Calset()
solver = Solver(calset, CalType.E12, rows=1, columns=1,
                frequency_vector=f_vector)

# Load the parameters of the short standard, convert to S-parameters
# (if not already in S), and form into a VectorParameter.
short = NPData(filename='short.s1p', ptype=PType.S)
s11 = VectorParameter(calset,
                      short.frequency_vector,
                      short.data_array[:, 0, 0])

# Add measurement of the short standard.
m = [
    [[-0.81608+0.06378j]],
    [[-0.80886+0.13717j]],
    [[-0.77555+0.29313j]],
    [[-0.62099+0.60669j]],
    [[+0.06501+1.01261j]],
    [[+1.09381-0.14071j]],
    [[-0.30518-0.70402j]],
    [[-0.22007+0.72124j]],
    [[-0.59979+0.77297j]],
    [[-0.23131+0.35142j]]
]
solver.add_single_reflect(m, s11)

# Load the parameters of the open standard, convert to S-parameters,
# and form into a VectorParameter.
open = NPData(filename='open.s1p', ptype=PType.S)
s11 = VectorParameter(calset,
                      open.frequency_vector,
                      open.data_array[:, 0, 0])

# Add measurement of the open standard.
m = [
    [[+0.99498-0.09207j]],
    [[+0.97742-0.19653j]],
    [[+0.89986-0.40609j]],
    [[+0.60434-0.73272j]],
    [[-0.09013-0.85534j]],
    [[-0.74558-0.33237j]],
    [[-0.61756+0.93791j]],
    [[+1.04825-1.39521j]],
    [[+1.58722-0.97489j]],
    [[+1.98515-0.00032j]]
]
solver.add_single_reflect(m, s11)

# Load the parameters of the load standard, convert to S-parameters,
# and form into a VectorParameter.
load = NPData(filename='load.s1p', ptype=PType.S)
s11 = VectorParameter(calset,
                      load.frequency_vector,
                      load.data_array[:, 0, 0])

# Add measurement of the load standard.
m = [
    [[-0.99980+0.01315j]],
    [[-0.99960+0.02832j]],
    [[-0.99894+0.06104j]],
    [[-0.99632+0.13181j]],
    [[-0.98477+0.28769j]],
    [[-0.91774+0.67149j]],
    [[+0.11479+1.59876j]],
    [[+0.63138-1.86446j]],
    [[+1.93785-0.35041j]],
    [[+2.00545+1.96703j]]
]
solver.add_single_reflect(m, s11)

# Solve, add to Calset and save.
solver.solve()
solver.add_to_calset('1-port')
calset.save('1x1m.vnacal')

Apply the calibration to a device under test.

from libvna.cal import Calset


# Load the calibration from file.
calset = Calset('1x1m.vnacal')
calibration = calset.calibrations['1-port']
f_vector = calibration.frequency_vector

# Measured response:
measured = [
    [[-0.66444+0.05259j]],
    [[-0.65664+0.11316j]],
    [[-0.61990+0.24217j]],
    [[-0.43531+0.49781j]],
    [[+0.45289+0.43847j]],
    [[-0.45306-0.53515j]],
    [[-0.66816+0.81382j]],
    [[+1.19883-0.98067j]],
    [[+1.96143+0.24846j]],
    [[+1.78310+2.15216j]]
]

# Apply calibration and save in Touchstone format.
result = calibration.apply(f_vector, measured)
result.save('1x1m-corrected.s1p')

2x1 SOLT

Example of SOLT calibration for a VNA that measures only \(S_{11}\) and \(S_{21}\)

from libvna.cal import Calset, CalType, Solver
import numpy as np


# Set the calibration frequency points.
fmin = 1.0e+06
fmax = 1.0e+09
f_vector = np.logspace(np.log10(fmin), np.log10(fmax), num=10)

# Set up libvna.cal's error term solver.
calset = Calset()
solver = Solver(calset, CalType.E12, rows=2, columns=1,
                frequency_vector=f_vector)

# Add measurement of the short standard.
m = [
    [[-1.12816-0.03698j],
     [-0.02474-0.08640j]],
    [[-1.12910-0.00517j],
     [-0.09143-0.14659j]],
    [[-1.12894+0.02306j],
     [-0.21783-0.15396j]],
    [[-1.12748+0.06542j],
     [-0.30870-0.07633j]],
    [[-1.12096+0.14746j],
     [-0.33066+0.02022j]],
    [[-1.09571+0.31404j],
     [-0.29853+0.12749j]],
    [[-1.08065+0.68396j],
     [-0.17594+0.23137j]],
    [[+1.00635+1.33602j],
     [+0.01150+0.22822j]],
    [[-0.19749-1.29087j],
     [+0.11994+0.13833j]],
    [[+0.84812-0.75113j],
     [+0.15236+0.06870j]]
]
solver.add_single_reflect(m, s11=-1)

# Add measurement of the open standard.
m = [
    [[+0.87156-0.05703j],
     [-0.02474-0.08640j]],
    [[+0.87011-0.04837j],
     [-0.09143-0.14659j]],
    [[+0.86813-0.06996j],
     [-0.21783-0.15396j]],
    [[+0.86011-0.13444j],
     [-0.30870-0.07633j]],
    [[+0.82358-0.27792j],
     [-0.33066+0.02022j]],
    [[+0.66068-0.55068j],
     [-0.29853+0.12749j]],
    [[+0.11645-0.76189j],
     [-0.17594+0.23137j]],
    [[-0.48958-0.59506j],
     [+0.01150+0.22822j]],
    [[-0.23146+0.80045j],
     [+0.11994+0.13833j]],
    [[-0.83221+0.44862j],
     [+0.15236+0.06870j]]
]
solver.add_single_reflect(m, s11=1)

# Add measurement of the load standard.
m = [
    [[-0.12826-0.04397j],
     [-0.02474-0.08640j]],
    [[-0.12930-0.02022j],
     [-0.09143-0.14659j]],
    [[-0.12952-0.00937j],
     [-0.21783-0.15396j]],
    [[-0.12956-0.00435j],
     [-0.30870-0.07633j]],
    [[-0.12957-0.00202j],
     [-0.33066+0.02022j]],
    [[-0.12958-0.00094j],
     [-0.29853+0.12749j]],
    [[-0.12958-0.00043j],
     [-0.17594+0.23137j]],
    [[-0.12958-0.00020j],
     [+0.01150+0.22822j]],
    [[-0.12958-0.00009j],
     [+0.11994+0.13833j]],
    [[-0.12958-0.00004j],
     [+0.15236+0.06870j]]
]
solver.add_single_reflect(m, s11=0)

# Add measurement of the through standard.
m = [
    [[-0.28747+0.14196j],
     [+0.97565-0.09835j]],
    [[-0.19941+0.08934j],
     [+0.90879-0.17292j]],
    [[-0.17513+0.04578j],
     [+0.78107-0.21102j]],
    [[-0.16944+0.02464j],
     [+0.68420-0.19916j]],
    [[-0.16752+0.01806j],
     [+0.63483-0.24204j]],
    [[-0.16403+0.02212j],
     [+0.54363-0.41383j]],
    [[-0.15014+0.03551j],
     [+0.16007-0.71907j]],
    [[-0.10532+0.03369j],
     [-0.89926-0.22222j]],
    [[-0.12789-0.04133j],
     [+0.99938+0.62757j]],
    [[-0.09589-0.02364j],
     [+1.14648+0.19814j]]
]
solver.add_through(m)

# Solve, add to Calset and save.
solver.solve()
solver.add_to_calset('cal2x1')
calset.save('2x1.vnacal')

Example of applying the calibration to a device under test, where we first measure the raw values of \(S_{11}\) and \(S_{21}\), then exchange the probes and measure the raw values of \(S_{22}\) and \(S_{12}\). With all four values and the calibration, we can solve for the full S-parameters of the device.

from libvna.cal import Calset
import numpy as np


# Load the calibration from file.
calset = Calset('2x1.vnacal')
calibration = calset.calibrations[0]
f_vector = calibration.frequency_vector

# Measured response with VNA port1 = DUT port 1, VNA port2 = DUT port 2:
m1 = np.asarray([
    [[-0.27597+0.15159j],
     [+0.97473-0.12971j]],
    [[-0.18627+0.09799j],
     [+0.90464-0.24038j]],
    [[-0.16775+0.05859j],
     [+0.76190-0.35548j]],
    [[-0.18952+0.06254j],
     [+0.59504-0.50152j]],
    [[-0.25163+0.21753j],
     [+0.22412-0.79020j]],
    [[+0.30504+0.68301j],
     [-0.74584-0.49732j]],
    [[+0.59691-0.60501j],
     [-0.29762+0.38784j]],
    [[-0.35862-0.65621j],
     [+0.03507+0.24922j]],
    [[-0.31816+0.77944j],
     [+0.11391+0.13329j]],
    [[-0.85395+0.40992j],
     [+0.15064+0.06832j]]
])

# Measured response with VNA port1 = DUT port 2, VNA port2 = DUT port 1:
m2 = np.asarray([
    [[-0.27497+0.15161j],
     [+0.97488-0.12991j]],
    [[-0.18165+0.09765j],
     [+0.90488-0.24095j]],
    [[-0.14679+0.05462j],
     [+0.76236-0.35717j]],
    [[-0.09910+0.02259j],
     [+0.59423-0.50829j]],
    [[+0.01470-0.14401j],
     [+0.18827-0.80114j]],
    [[-0.42472-0.59227j],
     [-0.66061-0.36906j]],
    [[-1.02239-0.01367j],
     [-0.34539+0.27450j]],
    [[+0.17374+1.72389j],
     [+0.05903+0.28426j]],
    [[+0.03620-1.30310j],
     [+0.10999+0.13188j]],
    [[+0.90793-0.67402j],
     [+0.14998+0.06833j]]
])

# Combine the measurements into a vector of 2x2 matrices,
# flipping the rows of m2 in the reversed measurement.
measured = np.concatenate((m1, np.flip(m2, axis=1)), axis=2)

# Apply calibration and save in Touchstone format.
result = calibration.apply(f_vector, measured)
result.save('2x1-corrected.s2p')

2x2 with A & B Measurements

In this example, our VNA measures full S parameters. In addition, it measures the transmitted power (a matrix) as well as the reflected power (b matrix). Having the a matrix makes it possible to compensate for errors in the RF switch(es) without requiring separate calibration error terms for each switch setting. We use the TE10 calibration type which consists of 8-term T error terms and two internal leakage terms. For the calibration, we use three standards: short-open, short-match and through.

from libvna.cal import Calset, CalType, Solver
import numpy as np

# Set the calibration frequency points.
fmin = 1.0e+06
fmax = 1.0e+09
f_vector = np.logspace(np.log10(fmin), np.log10(fmax), num=10)

# Set up libvna.cal's error term solver.
calset = Calset()
solver = Solver(calset, CalType.TE10, rows=2, columns=2,
                frequency_vector=f_vector)

# Add measurement of the short-open standard..
a = [
    [[+0.99990-0.01414j, -0.00010+0.00000j],
     [+0.00010-0.00000j, +0.99990-0.01414j]],
    [[+0.99954-0.03047j, -0.00046+0.00001j],
     [+0.00046-0.00001j, +0.99954-0.03047j]],
    [[+0.99784-0.06564j, -0.00215+0.00014j],
     [+0.00215-0.00014j, +0.99784-0.06564j]],
    [[+0.98990-0.14141j, -0.00990+0.00141j],
     [+0.00990-0.00141j, +0.98990-0.14141j]],
    [[+0.95153-0.30403j, -0.04417+0.01411j],
     [+0.04417-0.01411j, +0.95153-0.30403j]],
    [[+0.74976-0.62730j, -0.16153+0.13515j],
     [+0.16153-0.13515j, +0.74976-0.62730j]],
    [[+0.00000-0.70711j, -0.00000+0.70711j],
     [+0.00000-0.70711j, +0.00000-0.70711j]],
    [[-0.16153-0.13515j, +0.74976+0.62730j],
     [-0.74976-0.62730j, -0.16153-0.13515j]],
    [[-0.04417-0.01411j, +0.95153+0.30403j],
     [-0.95153-0.30403j, -0.04417-0.01411j]],
    [[-0.00990-0.00141j, +0.98990+0.14141j],
     [-0.98990-0.14141j, -0.00990-0.00141j]]
]
b = [
    [[-0.99983+0.01818j, -0.49420-0.37929j],
     [+0.00010+0.00068j, +0.93151+0.31446j]],
    [[-0.99963+0.03923j, -0.80549-0.01230j],
     [+0.00060+0.00150j, +1.19157+0.20896j]],
    [[-0.99811+0.08547j, -0.53818+0.45135j],
     [+0.00303+0.00297j, +1.29566-0.02059j]],
    [[-0.98719+0.18602j, -0.07206+0.42846j],
     [+0.01390+0.00331j, +1.27926-0.31174j]],
    [[-0.93214+0.39780j, +0.15832+0.19174j],
     [+0.05497-0.02220j, +1.07439-0.76019j]],
    [[-0.68997+0.80171j, +0.29790-0.14528j],
     [+0.06984-0.24895j, +0.21894-1.25154j]],
    [[+24.34897-5.19882j, -24.27325+5.04926j],
     [-0.78049-0.14346j, -0.86548-0.11872j]],
    [[-0.11743-0.26999j, +0.22707+0.83293j],
     [+0.02899+1.07236j, -0.01043+0.35137j]],
    [[-0.10846-0.00563j, +0.17976-0.82966j],
     [+1.46659-0.67259j, +0.17191+0.11505j]],
    [[-0.11423-0.01701j, +0.72801-0.36959j],
     [-0.18989+1.09229j, +0.18393+0.11107j]]
]
solver.add_double_reflect(a=a, b=b, s11=-1, s22=1)

# Add measurement of the short-match stnadard.
a = [
    [[+0.99990-0.01414j, -0.00010+0.00000j],
     [+0.00010-0.00000j, +0.99990-0.01414j]],
    [[+0.99954-0.03047j, -0.00046+0.00001j],
     [+0.00046-0.00001j, +0.99954-0.03047j]],
    [[+0.99784-0.06564j, -0.00215+0.00014j],
     [+0.00215-0.00014j, +0.99784-0.06564j]],
    [[+0.98990-0.14141j, -0.00990+0.00141j],
     [+0.00990-0.00141j, +0.98990-0.14141j]],
    [[+0.95153-0.30403j, -0.04417+0.01411j],
     [+0.04417-0.01411j, +0.95153-0.30403j]],
    [[+0.74976-0.62730j, -0.16153+0.13515j],
     [+0.16153-0.13515j, +0.74976-0.62730j]],
    [[+0.00000-0.70711j, -0.00000+0.70711j],
     [+0.00000-0.70711j, +0.00000-0.70711j]],
    [[-0.16153-0.13515j, +0.74976+0.62730j],
     [-0.74976-0.62730j, -0.16153-0.13515j]],
    [[-0.04417-0.01411j, +0.95153+0.30403j],
     [-0.95153-0.30403j, -0.04417-0.01411j]],
    [[-0.00990-0.00141j, +0.98990+0.14141j],
     [-0.98990-0.14141j, -0.00990-0.00141j]]
]
b = [
    [[-0.99983+0.01818j, -0.49420-0.37929j],
     [+0.00001+0.00065j, -0.00027-0.01007j]],
    [[-0.99963+0.03923j, -0.80549-0.01230j],
     [+0.00005+0.00139j, -0.00126-0.02168j]],
    [[-0.99811+0.08547j, -0.53818+0.45135j],
     [+0.00023+0.00292j, -0.00583-0.04640j]],
    [[-0.98719+0.18602j, -0.07206+0.42846j],
     [+0.00084+0.00546j, -0.02682-0.09703j]],
    [[-0.93214+0.39780j, +0.15832+0.19174j],
     [-0.00042+0.00473j, -0.11904-0.17989j]],
    [[-0.68997+0.80171j, +0.29790-0.14528j],
     [-0.06916-0.00593j, -0.42625-0.12354j]],
    [[+24.34897-5.19882j, -24.27325+5.04926j],
     [-0.11613+0.52133j, -0.20111+0.54607j]],
    [[-0.11743-0.26999j, +0.22707+0.83293j],
     [+0.72574-0.02372j, +0.13968+0.11523j]],
    [[-0.10846-0.00563j, +0.17976-0.82966j],
     [+0.17215-0.38448j, +0.11183+0.12843j]],
    [[-0.11423-0.01701j, +0.72801-0.36959j],
     [-0.07435-0.22825j, +0.18509+0.09786j]]
]
solver.add_double_reflect(a=a, b=b, s11=-1, s22=0)

# Add measurement of the through standard.
a = [
    [[+0.99990-0.01414j, -0.00010+0.00000j],
     [+0.00010-0.00000j, +0.99990-0.01414j]],
    [[+0.99954-0.03047j, -0.00046+0.00001j],
     [+0.00046-0.00001j, +0.99954-0.03047j]],
    [[+0.99784-0.06564j, -0.00215+0.00014j],
     [+0.00215-0.00014j, +0.99784-0.06564j]],
    [[+0.98990-0.14141j, -0.00990+0.00141j],
     [+0.00990-0.00141j, +0.98990-0.14141j]],
    [[+0.95153-0.30403j, -0.04417+0.01411j],
     [+0.04417-0.01411j, +0.95153-0.30403j]],
    [[+0.74976-0.62730j, -0.16153+0.13515j],
     [+0.16153-0.13515j, +0.74976-0.62730j]],
    [[+0.00000-0.70711j, -0.00000+0.70711j],
     [+0.00000-0.70711j, +0.00000-0.70711j]],
    [[-0.16153-0.13515j, +0.74976+0.62730j],
     [-0.74976-0.62730j, -0.16153-0.13515j]],
    [[-0.04417-0.01411j, +0.95153+0.30403j],
     [-0.95153-0.30403j, -0.04417-0.01411j]],
    [[-0.00990-0.00141j, +0.98990+0.14141j],
     [-0.98990-0.14141j, -0.00990-0.00141j]]
]
b = [
    [[+0.05936+0.35373j, +0.50746-0.40278j],
     [+1.00178-0.02281j, -0.00056-0.01605j]],
    [[+0.21118+0.18370j, +0.19492-0.06485j],
     [+1.00102-0.05110j, -0.00260-0.03450j]],
    [[+0.24859+0.06588j, +0.45479+0.33745j],
     [+0.99588-0.11109j, -0.01207-0.07364j]],
    [[+0.25820-0.01786j, +0.88633+0.18634j],
     [+0.97140-0.23925j, -0.05545-0.15165j]],
    [[+0.26515-0.12613j, +0.96031-0.29204j],
     [+0.85135-0.50697j, -0.24364-0.25760j]],
    [[+0.19493-0.39957j, +0.48031-0.80194j],
     [+0.22146-0.88039j, -0.80886+0.06680j]],
    [[-0.51987-0.50854j, -0.29097-0.35206j],
     [-0.15575+0.54949j, +0.64582+1.28526j]],
    [[-0.49580+0.84285j, -0.05596-0.05891j],
     [+0.29437-0.07813j, +0.48609-0.81719j]],
    [[+0.71967-0.68681j, -0.08380+0.21800j],
     [+0.37949-0.25864j, -0.69445+0.76682j]],
    [[+0.72947+0.59054j, -0.25605+0.15246j],
     [-0.16487-0.07509j, -0.64968-0.51337j]]
]
solver.add_through(a=a, b=b)

# Solve, add to Calset and save.
solver.solve()
solver.add_to_calset('cal2x2')
calset.save('2x2ab.vnacal')

Apply the calibration to a device under test.

from libvna.cal import Calset


# Load the calibration from file.
calset = Calset('2x2ab.vnacal')
calibration = calset.calibrations[0]
f_vector = calibration.frequency_vector

# Measured response
a = [
    [[+0.99990-0.01414j, -0.00010+0.00000j],
     [+0.00010-0.00000j, +0.99990-0.01414j]],
    [[+0.99954-0.03047j, -0.00046+0.00001j],
     [+0.00046-0.00001j, +0.99954-0.03047j]],
    [[+0.99784-0.06564j, -0.00215+0.00014j],
     [+0.00215-0.00014j, +0.99784-0.06564j]],
    [[+0.98990-0.14141j, -0.00990+0.00141j],
     [+0.00990-0.00141j, +0.98990-0.14141j]],
    [[+0.95153-0.30403j, -0.04417+0.01411j],
     [+0.04417-0.01411j, +0.95153-0.30403j]],
    [[+0.74976-0.62730j, -0.16153+0.13515j],
     [+0.16153-0.13515j, +0.74976-0.62730j]],
    [[+0.00000-0.70711j, -0.00000+0.70711j],
     [+0.00000-0.70711j, +0.00000-0.70711j]],
    [[-0.16153-0.13515j, +0.74976+0.62730j],
     [-0.74976-0.62730j, -0.16153-0.13515j]],
    [[-0.04417-0.01411j, +0.95153+0.30403j],
     [-0.95153-0.30403j, -0.04417-0.01411j]],
    [[-0.00990-0.00141j, +0.98990+0.14141j],
     [-0.98990-0.14141j, -0.00990-0.00141j]]
]
b = [
    [[+0.08095+0.34934j, +0.50623-0.43420j],
     [+1.00056-0.05423j, -0.00044-0.01605j]],
    [[+0.23177+0.15380j, +0.18919-0.13235j],
     [+0.99530-0.11861j, -0.00204-0.03451j]],
    [[+0.24663-0.00571j, +0.42827+0.19398j],
     [+0.96936-0.25471j, -0.00945-0.07369j]],
    [[+0.15559-0.13860j, +0.76590-0.10379j],
     [+0.85007-0.53060j, -0.04243-0.15250j]],
    [[-0.13260-0.05708j, +0.45053-0.74258j],
     [+0.32648-0.95536j, -0.17117-0.28028j]],
    [[+0.72827+0.36602j, -0.89125-0.16845j],
     [-1.02281-0.17874j, -0.75140-0.38242j]],
    [[-0.14509-0.32478j, +0.29943+0.27350j],
     [+0.06509+1.14138j, -0.09852+1.06784j]],
    [[-0.16484+0.02223j, +0.56092-0.60702j],
     [+1.29696-0.57511j, +0.23823+0.01444j]],
    [[-0.07311-0.07951j, -0.74749+0.82816j],
     [-0.62316-0.29592j, +0.08259+0.12946j]],
    [[-0.09682-0.02547j, -1.14836+0.34622j],
     [+0.03499-1.01931j, +0.18753+0.09126j]]
]

# Apply calibration and save in Touchstone format.
result = calibration.apply(f_vector, a=a, b=b)
result.save('2x2ab-corrected.s2p')

Two Port Reflect Only

In this example, we calibrate a two-port VNA for reflection measurements only, useful in cases where we don’t need to make through measurements. This example demonstrates using multiple solvers simultaneously and saving more than one calibration in the same calibration file.

from libvna.cal import Calset, CalType, Solver
import numpy as np


# Set the calibration frequency points.
fmin = 1.0e+06
fmax = 1.0e+09
f_vector = np.logspace(np.log10(fmin), np.log10(fmax), num=10)

# Create two error solvers.
calset = Calset()
solver1 = Solver(calset, CalType.E12, rows=1, columns=1,
                 frequency_vector=f_vector)
solver2 = Solver(calset, CalType.E12, rows=1, columns=1,
                 frequency_vector=f_vector)

# Add measurement of load standard on port 1 and short standard on port 2.
m = np.asarray([
    [[-0.01180-0.09001j, -0.81093-0.35645j]],
    [[-0.05269-0.18447j, -1.52614-0.59009j]],
    [[-0.20754-0.31917j, -1.73095-0.20402j]],
    [[-0.54626-0.28746j, -1.74720+0.00347j]],
    [[-0.67691+0.10631j, -1.74734+0.20124j]],
    [[-0.36976+0.33649j, -2.04869+1.24089j]],
    [[-0.12206+0.24276j, -0.93170+0.84301j]],
    [[-0.04274+0.12511j, +0.09995+0.26026j]],
    [[-0.02391+0.05943j, -1.63104-0.17917j]],
    [[-0.01977+0.02772j, -1.52562+0.44927j]]
])
solver1.add_single_reflect(m[:, :, 0].reshape((len(f_vector), 1, 1)), s11=0)
solver2.add_single_reflect(m[:, :, 1].reshape((len(f_vector), 1, 1)), s11=-1)

# Add measurement of short standard on port 1 and open standard on port 2.
m = np.asarray([
    [[-1.01169-0.08178j, +1.18869-0.38263j]],
    [[-1.05243-0.16674j, +0.47274-0.64647j]],
    [[-1.20666-0.28098j, +0.26482-0.32540j]],
    [[-1.54265-0.20526j, +0.23472-0.25719j]],
    [[-1.66092+0.28266j, +0.17309-0.35161j]],
    [[-1.29731+0.70918j, -0.16708-0.37945j]],
    [[-0.80194+0.97590j, -0.37034-1.10800j]],
    [[+0.15821+1.10673j, -1.81573-0.38866j]],
    [[+0.77049-0.57232j, +0.34349+0.24061j]],
    [[+0.29792+1.14489j, +0.21909-0.55609j]]
])
solver1.add_single_reflect(m[:, :, 0].reshape((len(f_vector), 1, 1)), s11=-1)
solver2.add_single_reflect(m[:, :, 1].reshape((len(f_vector), 1, 1)), s11=1)

# Add measurement of open standard on port 1 and load standard on port 2.
m = np.asarray([
    [[+0.98810-0.09858j, +0.18894-0.36824j]],
    [[+0.94704-0.20293j, -0.52640-0.61548j]],
    [[+0.79152-0.35892j, -0.73167-0.25872j]],
    [[+0.44984-0.37301j, -0.74959-0.11430j]],
    [[+0.30572-0.07716j, -0.75225-0.05244j]],
    [[+0.55152-0.05051j, -0.75277-0.02428j]],
    [[+0.53161-0.51164j, -0.75288-0.01126j]],
    [[-0.31312-0.83148j, -0.75291-0.00523j]],
    [[-0.67507+0.78451j, -0.75291-0.00243j]],
    [[-0.58037-0.58961j, -0.75291-0.00113j]]
])
solver1.add_single_reflect(m[:, :, 0].reshape((len(f_vector), 1, 1)), s11=1)
solver2.add_single_reflect(m[:, :, 1].reshape((len(f_vector), 1, 1)), s11=0)

# Solve both calibrations, add to Calset and save.
solver1.solve()
solver2.solve()
solver1.add_to_calset('port 1')
solver2.add_to_calset('port 2')
calset.save('2PR.vnacal')

Apply the calibration to a device under test.

from libvna.cal import Calset
import numpy as np


# Load the calibration from file.
calset = Calset('2PR.vnacal')
calibration1 = calset.calibrations['port 1']
calibration2 = calset.calibrations['port 2']
f_vector = calibration1.frequency_vector[...]

# Measured response:
measured = np.asarray([
    [[+0.98752-0.12496j, -0.81035-0.33133j]],
    [[+0.94435-0.25969j, -1.52345-0.53600j]],
    [[+0.77911-0.48052j, -1.71848-0.08794j]],
    [[+0.39317-0.62825j, -1.68945+0.24931j]],
    [[+0.06125-0.56493j, -1.47950+0.69155j]],
    [[-0.29191-0.65293j, -0.19263+0.85016j]],
    [[-1.00221-0.19591j, +0.19039+0.16323j]],
    [[-0.48443+0.99848j, -1.21179-0.97528j]],
    [[+0.90388-0.27750j, +0.16359+0.62292j]],
    [[+0.10333+1.12897j, +0.29941-0.37361j]]
])

# Apply calibration to each port and save in Touchstone format.
m1 = measured[:, 0, 0].reshape((len(f_vector), 1, 1))
m2 = measured[:, 0, 1].reshape((len(f_vector), 1, 1))
result1 = calibration1.apply(f_vector, m1)
result1.save('2PR1-corrected.s1p')
result2 = calibration2.apply(f_vector, m2)
result2.save('2PR2-corrected.s1p')

TRL

Example of through, reflect, line (TRL) calibration. We need to know our reflect and line standards only approximately – the calibration process solves for the actual parameters of the standards as well as the error terms.

from libvna.cal import Calset, CalType, Solver, UnknownParameter
from libvna.data import NPData, PType
import math
import numpy as np


# Set the calibration frequency points.
fmin = 1.0e+09
fmax = 8.0e+09
f_vector = np.linspace(fmin, fmax, num=10)

# Set up libvna.cal's error term solver.
calset = Calset()
solver = Solver(calset, CalType.TE10, rows=2, columns=2,
                frequency_vector=f_vector)

# Our line is a 1cm long trace of microstrip on FR4 at 50 ohms.
# We estimate εr_eff to be 2.75.  From these, we can estimate the
# transmission parameter.
#
line_length = 0.01              # length in meters
εr_eff = 2.75                   # effective permittivity
vf = 1 / math.sqrt(εr_eff)      # velocity factor
c = 2.9979246e+8                # speed of light (m/s)
line_estimated = np.exp(-2.0j * math.pi / (c * vf) * line_length * f_vector)

# Create Unknown parameters for the reflect and line, giving
# estimated values for each.
R = UnknownParameter(calset, -1)
L = UnknownParameter(calset, (f_vector, line_estimated))

# Add measurement of the through standard.
m = [
    [[-0.29630+0.48252j, -0.59909+0.51223j],
     [-0.89901+0.89700j, +0.58755+0.02250j]],
    [[-0.12459+0.35219j, -0.13993+0.60023j],
     [-0.25379+0.88516j, +0.56030+0.18737j]],
    [[-0.06195+0.28954j, +0.28609+0.25693j],
     [+0.23171+0.47036j, +0.79102+0.24929j]],
    [[-0.02606+0.25749j, +0.38300-0.32714j],
     [+0.35375-0.15858j, +0.92442+0.02775j]],
    [[-0.00059+0.23929j, +0.07529-0.87009j],
     [+0.05879-0.73139j, +0.74655-0.14929j]],
    [[+0.01907+0.22807j, -0.52075-1.11894j],
     [-0.52994-1.00133j, +0.55001-0.01018j]],
    [[+0.03462+0.22078j, -1.15762-0.95197j],
     [-1.16225-0.84997j, +0.65857+0.20505j]],
    [[+0.04687+0.21599j, -1.56569-0.42951j],
     [-1.56727-0.33952j, +0.89465+0.13388j]],
    [[+0.05636+0.21297j, -1.57109+0.23211j],
     [-1.57055+0.31260j, +0.87006-0.11318j]],
    [[+0.06349+0.21130j, -1.17214+0.75424j],
     [-1.17006+0.82703j, +0.62546-0.13428j]]
]
solver.add_through(m)

# Add measurement of the unknown reflect standard.
m = [
    [[+0.48731-0.08414j, -0.33113-0.44493j],
     [-0.63104-0.06016j, +0.11237-0.39064j]],
    [[+0.57954-0.30715j, -0.51553-0.31842j],
     [-0.62939-0.03349j, +0.12043+0.67175j]],
    [[+0.55816-0.44976j, -0.57461-0.23667j],
     [-0.62898-0.02324j, +1.29196+0.72319j]],
    [[+0.50932-0.54722j, -0.59957-0.18636j],
     [-0.62883-0.01780j, +1.43766-0.49559j]],
    [[+0.45309-0.61686j, -0.61225-0.15312j],
     [-0.62875-0.01442j, +0.19850-0.70497j]],
    [[+0.39692-0.66719j, -0.61952-0.12974j],
     [-0.62871-0.01213j, -0.04933+0.56030j]],
    [[+0.34437-0.70333j, -0.62406-0.11245j],
     [-0.62868-0.01046j, +1.24429+0.84817j]],
    [[+0.29729-0.72885j, -0.62708-0.09919j],
     [-0.62866-0.00920j, +1.58144-0.45558j]],
    [[+0.25654-0.74640j, -0.62919-0.08870j],
     [-0.62865-0.00820j, +0.29147-0.84365j]],
    [[+0.22236-0.75802j, -0.63072-0.08020j],
     [-0.62864-0.00741j, -0.14870+0.41719j]]
]
solver.add_double_reflect(m, s11=R, s22=R)

# Add measurement of the unknown line standard.
m = [
    [[-0.29167+0.45881j, -0.28024+0.36828j],
     [-0.58016+0.75305j, +0.58372+0.14435j]],
    [[-0.12297+0.28874j, +0.12124+0.10043j],
     [+0.00739+0.38536j, +0.74185+0.20842j]],
    [[-0.08215+0.18091j, +0.10290-0.48214j],
     [+0.04852-0.26870j, +0.82389+0.02301j]],
    [[-0.08641+0.11395j, -0.40128-0.84056j],
     [-0.43053-0.67200j, +0.66979-0.01283j]],
    [[-0.10879+0.08128j, -0.99575-0.67796j],
     [-1.01225-0.53926j, +0.69984+0.11689j]],
    [[-0.13128+0.07658j, -1.23999-0.14148j],
     [-1.24918-0.02387j, +0.81206+0.04324j]],
    [[-0.14243+0.08992j, -0.99514+0.35387j],
     [-0.99976+0.45587j, +0.72045-0.03922j]],
    [[-0.13753+0.10998j, -0.48758+0.45971j],
     [-0.48916+0.54971j, +0.67959+0.06143j]],
    [[-0.11760+0.12649j, -0.11509+0.13336j],
     [-0.11455+0.21385j, +0.78497+0.06350j]],
    [[-0.08789+0.13190j, -0.15495-0.34897j],
     [-0.15287-0.27618j, +0.75702-0.03458j]]
]
solver.add_line(m, [[0, L], [L, 0]])

# Solve, add to Calset and save.
solver.solve()
solver.add_to_calset('mycal')
calset.save('TRL.vnacal')

# Save the solved R
r_array = np.asarray(R.get_value(f_vector)).reshape((len(f_vector), 1, 1))
r_data = NPData(PType.S, len(f_vector), 1, 1)
r_data.frequency_vector = f_vector
r_data.data_array = r_array
r_data.format = "Sma"
r_data.save('TRL-R.s1p')

# Save the solved L
l_array = np.asarray(L.get_value(f_vector)).reshape((len(f_vector), 1, 1))
l_data = NPData(PType.S, len(f_vector), 1, 1)
l_data.frequency_vector = f_vector
l_data.data_array = l_array
l_data.format = "Sma"
l_data.save('TRL-L.s1p')

Apply the calibration to a device under test.

from libvna.cal import Calset


# Load the calibration from file.
calset = Calset('TRL.vnacal')
calibration = calset.calibrations[0]
f_vector = calibration.frequency_vector

# Measured response
m = [
    [[-1.10620+0.70536j, -0.33053-0.44651j],
     [-0.63044-0.06174j, +0.17643-0.66758j]],
    [[-0.95860+0.52394j, -0.51569-0.31893j],
     [-0.62955-0.03400j, -0.19852+0.47679j]],
    [[-0.92211+0.40116j, -0.57482-0.23682j],
     [-0.62920-0.02339j, +0.93354+1.06226j]],
    [[-0.91045+0.30844j, -0.59973-0.18635j],
     [-0.62898-0.01779j, +1.76631+0.04317j]],
    [[-0.90558+0.23079j, -0.61233-0.15305j],
     [-0.62883-0.01436j, +0.92357-0.99164j]],
    [[-0.90200+0.16160j, -0.61954-0.12966j],
     [-0.62872-0.01205j, -0.26351-0.34645j]],
    [[-0.89761+0.09771j, -0.62403-0.11240j],
     [-0.62866-0.01041j, +0.17092+0.96103j]],
    [[-0.89149+0.03748j, -0.62704-0.09917j],
     [-0.62862-0.00918j, +1.56608+0.75996j]],
    [[-0.88317-0.01998j, -0.62915-0.08871j],
     [-0.62862-0.00821j, +1.61704-0.67572j]],
    [[-0.87243-0.07516j, -0.63070-0.08022j],
     [-0.62862-0.00743j, +0.19268-0.98060j]]
]

# Apply calibration and save in Touchstone format.
result = calibration.apply(f_vector, m)
result.save('TRL-corrected.s2p')

Unknown Through

Example of unknown through calibration. For this calibration, we need three reflect standards on each port and the unknown through between them. To reduce calibration steps, we measure two reflect standards at a time. We have arbitrarily selected them as short-open, open-match, and match-short.

from libvna.cal import Calset, CalType, Solver, UnknownParameter
from libvna.data import NPData, PType
import math
import numpy as np


# Set the calibration frequency points.
fmin = 1.0e+06
fmax = 1.0e+09
f_vector = np.logspace(np.log10(fmin), np.log10(fmax), num=10)

# Set up libvna.cal's error term solver.
calset = Calset()
solver = Solver(calset, CalType.TE10, rows=2, columns=2,
                frequency_vector=f_vector)

# Create an unknown parameter for our through standard.  As long as the
# phase shift in the through remains safely below 90 degrees, we can
# give 1 as the estimate.  For a larger phase shift, we would need to
# give a more accurate estimate for each frequency.
T = UnknownParameter(calset, 1)

# Add measurement of the short-open standard.
m = [
    [[-1.00115+0.01823j, -0.00000-0.00076j],
     [-0.69226-0.87194j, +1.02247+0.03248j]],
    [[-1.00587+0.03933j, -0.00001-0.00163j],
     [-0.84393-0.13638j, +1.05650+0.03556j]],
    [[-1.02907+0.08529j, -0.00003-0.00351j],
     [-0.75883-0.03999j, +1.11818+0.00642j]],
    [[-1.16507+0.19259j, -0.00012-0.00757j],
     [-0.74020-0.01662j, +1.20304-0.14326j]],
    [[-4.06525+4.52910j, -0.00057-0.01629j],
     [-0.73620-0.00753j, +1.17539-0.51408j]],
    [[+0.28765+0.80816j, -0.00266-0.03501j],
     [-0.73534-0.00348j, +0.76245-1.11498j]],
    [[+1.01380+1.00627j, -0.01228-0.07447j],
     [-0.73516-0.00161j, -0.63037-1.36896j]],
    [[+1.52267-0.74041j, -0.05573-0.15092j],
     [-0.73512-0.00075j, -1.00389+1.02694j]],
    [[+1.64437+1.04262j, -0.22755-0.23377j],
     [-0.73511-0.00035j, -1.41068-0.58542j]],
    [[-0.57359+0.57666j, -0.48283+0.01743j],
     [-0.73511-0.00016j, +1.01068-0.34822j]]
]
solver.add_double_reflect(m, s11=-1, s22=1)

# Add measurement of the open-match standard.
m = [
    [[+0.99819-0.01927j, -0.00000-0.00076j],
     [-0.69226-0.87194j, -0.00005-0.00391j]],
    [[+0.99204-0.04145j, -0.00001-0.00163j],
     [-0.84393-0.13638j, -0.00023-0.00842j]],
    [[+0.96266-0.08856j, -0.00003-0.00351j],
     [-0.75883-0.03999j, -0.00107-0.01810j]],
    [[+0.79876-0.18016j, -0.00012-0.00757j],
     [-0.74020-0.01662j, -0.00493-0.03853j]],
    [[-2.22816+3.74350j, -0.00057-0.01629j],
     [-0.73620-0.00753j, -0.02176-0.07866j]],
    [[+1.57386-0.71763j, -0.00266-0.03501j],
     [-0.73534-0.00348j, -0.08227-0.13568j]],
    [[+0.42178-0.89159j, -0.01228-0.07447j],
     [-0.73516-0.00161j, -0.20435-0.14374j]],
    [[+0.26115+0.78302j, -0.05573-0.15092j],
     [-0.73512-0.00075j, -0.29175-0.05679j]],
    [[+0.12033-0.45898j, -0.22755-0.23377j],
     [-0.73511-0.00035j, -0.28263+0.04953j]],
    [[+1.39277+0.22433j, -0.48283+0.01743j],
     [-0.73511-0.00016j, -0.19822+0.10213j]]
]
solver.add_double_reflect(m, s11=1, s22=0)

# Add measurement of the match-short standard.
m = [
    [[-0.00146+0.00018j, -0.00000-0.00076j],
     [-0.69226-0.87194j, -0.97240+0.06563j]],
    [[-0.00685+0.00045j, -0.00001-0.00163j],
     [-0.84393-0.13638j, -0.93638+0.10000j]],
    [[-0.03288+0.00162j, -0.00003-0.00351j],
     [-0.75883-0.03999j, -0.88193+0.14025j]],
    [[-0.18165+0.01309j, -0.00012-0.00757j],
     [-0.74020-0.01662j, -0.81986+0.18311j]],
    [[-3.13991+4.14985j, -0.00057-0.01629j],
     [-0.73620-0.00753j, -0.75824+0.28223j]],
    [[+0.95820+0.06283j, -0.00266-0.03501j],
     [-0.73534-0.00348j, -0.58004+0.50725j]],
    [[+0.77593+0.01887j, -0.01228-0.07447j],
     [-0.73516-0.00161j, +0.08102+0.61542j]],
    [[+0.74527+0.00806j, -0.05573-0.15092j],
     [-0.73512-0.00075j, +0.14506-0.73864j]],
    [[+0.73898+0.00368j, -0.22755-0.23377j],
     [-0.73511-0.00035j, +0.42373+0.44212j]],
    [[+0.73763+0.00170j, -0.48283+0.01743j],
     [-0.73511-0.00016j, -0.95217+0.38515j]]
]
solver.add_double_reflect(m, s11=0, s22=-1)


# Add measurement of the unknown through standard.  Note that we
# have to add it as a "line".
m = [
    [[+0.02360+0.05265j, +0.99459-0.02028j],
     [+0.30232-0.89146j, -0.00007-0.00461j]],
    [[+0.05353+0.07507j, +0.99143-0.04363j],
     [+0.14750-0.17838j, -0.00031-0.00992j]],
    [[+0.08570+0.08770j, +0.98456-0.09375j],
     [+0.22576-0.13023j, -0.00142-0.02128j]],
    [[+0.00589+0.06052j, +0.96398-0.20036j],
     [+0.22390-0.20941j, -0.00651-0.04515j]],
    [[-2.93241+4.10689j, +0.88675-0.41863j],
     [+0.15112-0.40987j, -0.02872-0.09127j]],
    [[+1.10163-0.09286j, +0.58081-0.79680j],
     [-0.15187-0.76527j, -0.10895-0.14966j]],
    [[+0.69830-0.16839j, -0.37905-0.93306j],
     [-1.10192-0.86020j, -0.24849-0.10084j]],
    [[+0.67213+0.17835j, -0.45544+0.63807j],
     [-1.13482+0.78825j, -0.17838-0.09393j]],
    [[+0.58224-0.02113j, -0.98985-0.49775j],
     [-1.49741-0.26433j, -0.08364+0.10423j]],
    [[+0.79968-0.11880j, +0.10897-0.47753j],
     [-0.14331-0.49512j, -0.00674+0.33675j]]
]
solver.add_line(m, [[0, T], [T, 0]])

# Solve, add to Calset and save.
solver.solve()
solver.add_to_calset('mycal')
calset.save('UT.vnacal')

# Save the solved T
t_array = np.asarray(T.get_value(f_vector)).reshape((len(f_vector), 1, 1))
t_data = NPData(PType.S, len(f_vector), 1, 1)
t_data.frequency_vector = f_vector
t_data.data_array = t_array
t_data.format = "Sma"
t_data.save('UT-T.s1p')

Apply the calibration to a device under test.

from libvna.cal import Calset


# Load the calibration from file.
calset = Calset('UT.vnacal')
calibration = calset.calibrations[0]
f_vector = calibration.frequency_vector

# Measured response
m = [
    [[+0.02658+0.05156j, +0.99862-0.05113j],
     [+0.30636-0.92231j, +0.00038-0.00464j]],
    [[+0.06166+0.06770j, +0.99413-0.10998j],
     [+0.15020-0.24473j, +0.00177-0.01016j]],
    [[+0.09815+0.05439j, +0.97384-0.23564j],
     [+0.21503-0.27212j, +0.00802-0.02360j]],
    [[-0.03734-0.03705j, +0.88016-0.49854j],
     [+0.14008-0.50759j, +0.03308-0.06750j]],
    [[-3.25124+4.12993j, +0.41278-0.94616j],
     [-0.32285-0.93740j, +0.05712-0.28641j]],
    [[+1.49116+0.61118j, -0.62349-0.35706j],
     [-1.35617-0.32553j, -0.71393-0.46152j]],
    [[+1.08167-0.94321j, -0.03858+0.09589j],
     [-0.76146+0.16875j, -0.36794+0.66927j]],
    [[+0.04391+0.64222j, -0.03328-0.17617j],
     [-0.71266-0.02599j, +0.30123-0.61687j]],
    [[+0.16764-0.52748j, -0.22268-0.23032j],
     [-0.73024+0.00310j, +0.37661+0.51925j]],
    [[+1.37622+0.24988j, -0.48395+0.01728j],
     [-0.73622-0.00031j, -0.96608+0.34587j]]
]

# Apply calibration and save in Touchstone format.
result = calibration.apply(f_vector, m)
result.save('UT-corrected.s2p')