<jupyter_start><jupyter_text>Measuring a 4-Port With The 1.5-Port NanoVNA V2 The NanoVNA V2 is a 1.5-port VNA, meaning it can only measure the forward reflection and transmission coefficients $S_{11}$ and $S_{21}$ with its two ports. To measure the reverse coefficients $S_{12}$ and $S_{22}$ of a 2-port, it needs to be measured a second time with the port orientation physically flipped (or fake-flipped), as explained in [this example](TwoPortOnePath, EnhancedResponse, and FakeFlip.ipynb). The issue gets worse if the device under test (DUT) has even more ports, as described in [this example](./Measuring a Mutiport Device with a 2-Port Network Analyzer.ipynb).In this example, a [4-port SMA power splitter](https://www.minicircuits.com/WebStore/dashboard.html?model=ZX10Q-2-19-S%2B) is measured with a NanoVNA V2 using the Matched Port technique. It involves measuring the 4-port in all port combinations (12 measurements) with the unused ports terminated with a 50-Ohm match. For the VNA calibration, four more measurements with the SMA calibration standards are required (SHORT, OPEN, MATCH, THROUGH). The manufacturer of the DUT provides measured S-parameters on their website, which will later be used for comparison.  Data Aquisition To use scikit-rf's 2-port calibration classes, such as `TwoPortOnePath`, the individual measurement results of the DUT and of the calibration standards need to be provided as 2-port networks holding the data as $S_{11}$ and $S_{21}$. The following code snippet can be used to configure the NanoVNA, and aquire and save the data: ```pythonimport skrffrom skrf.vi import vna connect to NanoVNA on /dev/ttyACM0 (Linux)nanovna = skrf.vi.vna.NanoVNAv2('ASRL/dev/ttyACM0::INSTR') for Windows users: ASRL1 for COM1 nanovna = skrf.vi.vna.NanoVNAv2('ASRL1::INSTR') configure frequency sweep (for example 1 MHz to 4.4 GHz in 1 MHz steps)f_start = 1e6f_stop = 4.4e9f_step = 1e6num = int(1 + (f_stop - f_start) / f_step)nanovna.set_frequency_sweep(f_start, f_stop, num) measure all 12 combinations of the 4-portn_ports = 4for i_src in range(n_ports):    for i_sink in range(n_ports):        if i_sink != i_src:            input('Connect vna_p1 -> dut_p{}, vna_p2 -> dut_p{} and press ENTER:'.format(i_src + 1, i_sink + 1))            nw_raw = nanovna.get_snp_network(ports=(0, 1)            nw_raw.write_touchstone('./data_MiniCircuits_splitter/dut_raw_{}{}'.format(i_sink + 1, i_src + 1))``` The calibration standards should be measured with the same repeated calls of `get_snp_network(ports=(0, 1))` and `write_touchstone()`.  Offline Calibration Using `TwoPortOnePath` The measured data transferred from the NanoVNA via USB is always raw (uncalibrated), regardless of any calibration preformed on the NanoVNA itself. This requires the correction of the data using an offline calibration. With the measurements of the calibration standards stored as individual 2-ports, a TwoPortOnePath calibration is easily created using scikit-rf. In this example, the impedances and phase delays of the measured SHORT, OPEN, MATCH, and THROUGH are assumed to be ideal, i.e. without any loss or offset:<jupyter_code>import skrf
from skrf.calibration import TwoPortOnePath

# load networks of the raw calibration standard measurements
short_raw = skrf.Network('./data_MiniCircuits_splitter/cal_short_raw.s2p')
open_raw = skrf.Network('./data_MiniCircuits_splitter/cal_open_raw.s2p')
match_raw = skrf.Network('./data_MiniCircuits_splitter/cal_match_raw.s2p')
thru_raw = skrf.Network('./data_MiniCircuits_splitter/cal_thru_raw.s2p')

# create an ideal 50-Ohm line for the short, open, match and through reference responses ("ideals")
line = skrf.DefinedGammaZ0(frequency=short_raw.frequency, Z0=50)

# create and run the calibration
cal = TwoPortOnePath(ideals=[line.short(nports=2), line.open(nports=2), line.match(nports=2), line.thru()],
                     measured=[short_raw, open_raw, match_raw, thru_raw],
                     n_thrus=1, source_port=1)
cal.run()<jupyter_output><empty_output><jupyter_text>The 12 individual 2-port subnetworks can now be corrected with this calibration. For a full correction, the subnetworks with the forward and reverse measurements need to be provided in pairs, for example ($S_{32}$, $S_{22}$) paired with ($S_{23}$, $S_{33}$). A nested loop can take care of this. For the comparison with the measurements provided by the manufacturer, it is convenient to store the calibrated results in a single 4-port network, which can then easily be plotted:<jupyter_code>import numpy as np

# create an empty array (f x 4 x 4) for the 4-port to be filled
s = np.zeros((len(short_raw.frequency), 4, 4), dtype=complex)
splitter_cal = skrf.Network(frequency=short_raw.frequency, s=s)

# loop through all 12 measurements, apply the calibration and save it inside 4-port network
for i_src in range(4):
    for i_recv in range(4):
        if i_src != i_recv:
            dut_raw_fwd = skrf.Network('./data_MiniCircuits_splitter/dut_raw_{}{}.s2p'.format(i_recv + 1, i_src + 1))
            dut_raw_rev = skrf.Network('./data_MiniCircuits_splitter/dut_raw_{}{}.s2p'.format(i_src + 1, i_recv + 1))
            dut_cal = cal.apply_cal((dut_raw_fwd, dut_raw_rev))
            
            # dut_cal is now a fully populated and corrected 2-port; save it in splitter_cal
            splitter_cal.s[:, i_src, i_src] = dut_cal.s[:, 0, 0]
            splitter_cal.s[:, i_recv, i_src] = dut_cal.s[:, 1, 0]
            splitter_cal.s[:, i_src, i_recv] = dut_cal.s[:, 0, 1]
            splitter_cal.s[:, i_recv, i_recv] = dut_cal.s[:, 1, 1]<jupyter_output><empty_output><jupyter_text>The results are now corrected and assembled as a single 4-port network. For comparison, the magnitudes are plotted together with the measurements provided by the manufacturer:<jupyter_code>import matplotlib.pyplot as mplt

# load reference results by MiniCircuits
splitter_mc = skrf.Network('./data_MiniCircuits_splitter/MiniCircuits_ZX10Q-2-19-S+___Plus25degC.s4p')

# plot both results
fig, ax = mplt.subplots(4, 4)
fig.set_size_inches(12, 8)

for i in range(4):
    for j in range(4):
        splitter_cal.plot_s_db(i, j, ax=ax[i][j])
        splitter_mc.plot_s_db(i, j, ax=ax[i][j])
        ax[i][j].get_legend().remove()
        ax[i][j].set_xlim(0, 4.4e9)
fig.legend(['NanoVNA_cal', 'Manufacturer'], loc='upper center', ncol=2)
fig.tight_layout(rect=(0, 0, 1, 0.95))
mplt.show()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10027985074626866, 'fraction_numerical': 0.023165422885572138, 'mean_word_length': 4.550474547023296, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 15, 'https://': 1, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '3', 'dir': 'jupyter-structured-clean-dedup', 'id': '317878', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 2222, 'n_tokens_neox': 2081, 'n_words': 703, 'path': 'doc/source/examples/metrology/NanoVNA_V2_4port-splitter.ipynb', 'repo_name': 'nmaterise/scikit-rf', 'size': '6432'}