diff --git a/Software/PC_Application/LibreVNA-GUI/Calibration/calibrationmeasurement.cpp b/Software/PC_Application/LibreVNA-GUI/Calibration/calibrationmeasurement.cpp index 751fe39..bd4d15a 100644 --- a/Software/PC_Application/LibreVNA-GUI/Calibration/calibrationmeasurement.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Calibration/calibrationmeasurement.cpp @@ -429,7 +429,7 @@ void CalibrationMeasurement::TwoPort::addPoint(const DeviceDriver::VNAMeasuremen { Point p; p.frequency = m.frequency; - p.S = m.toSparam(port1, port2); + p.S = m.toSparam().reduceTo({port1, port2}); points.push_back(p); timestamp = QDateTime::currentDateTimeUtc(); } diff --git a/Software/PC_Application/LibreVNA-GUI/Device/devicedriver.cpp b/Software/PC_Application/LibreVNA-GUI/Device/devicedriver.cpp index 1443b5a..f1f389a 100644 --- a/Software/PC_Application/LibreVNA-GUI/Device/devicedriver.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Device/devicedriver.cpp @@ -61,33 +61,55 @@ unsigned int DeviceDriver::SApoints() { } } -Sparam DeviceDriver::VNAMeasurement::toSparam(int port1, int port2) const +Sparam DeviceDriver::VNAMeasurement::toSparam(int ports) const { - Sparam S; - S.set(1,1, measurements.at("S"+QString::number(port1)+QString::number(port1))); - S.set(1,2, measurements.at("S"+QString::number(port1)+QString::number(port2))); - S.set(2,1, measurements.at("S"+QString::number(port2)+QString::number(port1))); - S.set(2,2, measurements.at("S"+QString::number(port2)+QString::number(port2))); + if(ports == 0) { + // determine number of ports by highest available S parameter + for(const auto &m : measurements) { + if(!m.first.startsWith("S")) { + // something else we can not handle + continue; + } + int to = m.first.mid(1,1).toUInt(); + int from = m.first.mid(2,1).toUInt(); + if(to > ports) { + ports = to; + } + if(from > ports) { + ports = from; + } + } + } + // create S paramters + auto S = Sparam(ports); + // fill data + for(const auto &m : measurements) { + if(!m.first.startsWith("S")) { + // something else we can not handle + continue; + } + int to = m.first.mid(1,1).toUInt(); + int from = m.first.mid(2,1).toUInt(); + S.set(to, from, m.second); + } return S; } -void DeviceDriver::VNAMeasurement::fromSparam(Sparam S, int port1, int port2) +void DeviceDriver::VNAMeasurement::fromSparam(Sparam S, std::vector portMapping) { - QString s11 = "S"+QString::number(port1)+QString::number(port1); - QString s12 = "S"+QString::number(port1)+QString::number(port2); - QString s21 = "S"+QString::number(port2)+QString::number(port1); - QString s22 = "S"+QString::number(port2)+QString::number(port2); - if(measurements.count(s11)) { - measurements[s11] = S.get(1,1); + if(portMapping.size() == 0) { + // set up default port mapping + for(unsigned int i=1;i<=S.ports();i++) { + portMapping.push_back(i); + } } - if(measurements.count(s12)) { - measurements[s12] = S.get(1,2); - } - if(measurements.count(s21)) { - measurements[s21] = S.get(2,1); - } - if(measurements.count(s22)) { - measurements[s22] = S.get(2,2); + for(unsigned int i=0;i> measurements; - Sparam toSparam(int port1, int port2) const; - void fromSparam(Sparam S, int port1, int port2); + Sparam toSparam(int ports = 0) const; + /* Sets the measurement values in the VNAmeasurement (if existent) to the values from the S parameter matrix. + * The portMapping parameter can be used to specify which values to set from which S parameter: + * Example: S parameter contains 4 port S parameters, but the VNAmeasurement is 2 port only with this mapping: + * VNAMeasurement port | S parameter port + * --------------------|----------------- + * 1 | 2 + * 2 | 4 + * This means that we want S22 (from the 4 port S parameter) stored as S11 (in the VNAMeasurement). + * Function call for this example: fromSparam(S, {2,4}) + * + * If no portMapping is specified, the port order (and mapping) from the S paramters are kept. + */ + void fromSparam(Sparam S, std::vector portMapping = {}); VNAMeasurement interpolateTo(const VNAMeasurement &to, double a); }; diff --git a/Software/PC_Application/LibreVNA-GUI/Tools/parameters.cpp b/Software/PC_Application/LibreVNA-GUI/Tools/parameters.cpp index ccdb96e..d2843c5 100644 --- a/Software/PC_Application/LibreVNA-GUI/Tools/parameters.cpp +++ b/Software/PC_Application/LibreVNA-GUI/Tools/parameters.cpp @@ -24,18 +24,52 @@ Sparam::Sparam(const ABCDparam &a, Type Z0) { } +Sparam::Sparam(const Zparam &Z, std::vector Z0n) +{ + if(Z.ports() != Z0n.size()) { + throw std::runtime_error("number of supplied characteristic impedances does not match number of ports"); + } + /* general formula for converting S parameters to Z parameters: + * S = (sqrt(y)*Z*sqrt(y)-1)*(sqrt(y)*Z*sqrt(y)+1)^-1 + * with: + * Z = Z parameter matrix + * 1 = identity matrix + * sqrt(y) = diagonal matrix with the root of characteristic admittances as it non-zero elements + */ + // create identity matrix + auto ident = Eigen::MatrixXcd::Identity(Z.ports(), Z.ports()); + // create sqrt(y) matrix + Eigen::MatrixXcd sqrty = Eigen::MatrixXcd::Zero(Z.ports(), Z.ports()); + // fill with characteristic admittance + for(unsigned int i=0;i(Z.ports(), Z0)) +{ + +} + void Sparam::swapPorts(unsigned int p1, unsigned int p2) { - // swap columns data.col(p1-1).swap(data.col(p2-1)); data.row(p1-1).swap(data.row(p2-1)); - // auto cbuf = data.col(p1-1); - // data.col(p1-1) = data.col(p2-1); - // data.col(p2-1) = cbuf; - // // swap rows - // auto rbuf = data.row(p1-1); - // data.row(p1-1) = data.row(p2-1); - // data.row(p2-1) = rbuf; +} + +Sparam Sparam::reduceTo(std::vector ports) const +{ + auto ret = Sparam(ports.size()); + for(unsigned int from=0;from Z0n) +{ + if(S.ports() != Z0n.size()) { + throw std::runtime_error("number of supplied characteristic impedances does not match number of ports"); + } + /* general formula for converting S parameters to Z parameters: + * Z = sqrt(z)*(1+S)*(1-S)^-1*sqrt(z) + * with: + * S = S parameter matrix + * 1 = identity matrix + * sqrt(z) = diagonal matrix with the root of characteristic impedances as it non-zero elements + */ + // create identity matrix + auto ident = Eigen::MatrixXcd::Identity(S.ports(), S.ports()); + // create sqrt(z) matrix + Eigen::MatrixXcd sqrtz = Eigen::MatrixXcd::Zero(S.ports(), S.ports()); + // fill with characteristic impedance + for(unsigned int i=0;i(S.ports(), Z0)) +{ + +} diff --git a/Software/PC_Application/LibreVNA-GUI/Tools/parameters.h b/Software/PC_Application/LibreVNA-GUI/Tools/parameters.h index ea723c6..179dc8a 100644 --- a/Software/PC_Application/LibreVNA-GUI/Tools/parameters.h +++ b/Software/PC_Application/LibreVNA-GUI/Tools/parameters.h @@ -11,6 +11,7 @@ using Type = std::complex; class Parameters : public Savable { public: + Parameters(Type m11); Parameters(Type m11, Type m12, Type m21, Type m22); Parameters(int num_ports); Parameters() : Parameters(2){} @@ -29,6 +30,7 @@ public: // forward declaration of parameter classes class Sparam; +class Zparam; class Tparam; class ABCDparam; @@ -38,6 +40,8 @@ public: Sparam(const Tparam &t); Sparam(const ABCDparam &a, Type Z01, Type Z02); Sparam(const ABCDparam &a, Type Z0); + Sparam(const Zparam &Z, std::vector Z0n); + Sparam(const Zparam &Z, Type Z0); Sparam operator+(const Sparam &r) const { Sparam p(ports()); p.data = data+r.data; @@ -49,6 +53,19 @@ public: return p; } void swapPorts(unsigned int p1, unsigned int p2); + // reduces the S parameter matrix to specified ports. + // Example: 4 port S parameters as an input but we want the 2 port data from the original ports 1 and 3 + // Call: S.reduceTo(1, 3) + // Result: 2 port S parameters (S11, S12, S21, S22) which are set to the original (S11, S13, S31, S33) + Sparam reduceTo(std::vector ports) const; +}; + +class Zparam : public Parameters { +public: + using Parameters::Parameters; + Zparam(int num_ports) : Parameters(num_ports){} + Zparam(const Sparam &S, std::vector Z0n); + Zparam(const Sparam &S, Type Z0); }; class ABCDparam : public Parameters { @@ -66,7 +83,7 @@ public: ABCDparam inverse() { ABCDparam i; // by hand, this is faster because the Eigen matrix is using dynamic size - Type det = data(0,0)*data(1,1) - data(0,1)*data(2,1); + Type det = data(0,0)*data(1,1) - data(0,1)*data(1,0); i.data(0,0) = data(1,1) / det; i.data(0,1) = -data(0,1) / det; i.data(1,0) = -data(1,0) / det; @@ -87,7 +104,7 @@ public: ABCDparam r = *this; r.data(0,0) += s; r.data(1,1) += s; - r = r * (1.0/t); + r.data = r.data * (1.0/t); return r; } }; @@ -109,7 +126,7 @@ public: } Tparam inverse() { Tparam i; - Type det = data(0,0)*data(1,1) - data(0,1)*data(2,1); + Type det = data(0,0)*data(1,1) - data(0,1)*data(1,0); i.data(0,0) = data(1,1) / det; i.data(0,1) = -data(0,1) / det; i.data(1,0) = -data(1,0) / det; diff --git a/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/deembedding.cpp b/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/deembedding.cpp index cee2079..bbb2fdd 100644 --- a/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/deembedding.cpp +++ b/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/deembedding.cpp @@ -25,7 +25,8 @@ void Deembedding::measurementCompleted() measuringOption = nullptr; } - delete measurementDialog; + measurementDialog->close(); + measurementDialog->deleteLater(); measurementDialog = nullptr; measurementUI = nullptr; } @@ -37,7 +38,7 @@ void Deembedding::startMeasurementDialog(DeembeddingOption *option) auto ui = new Ui_DeembeddingMeasurementDialog; measurementUI = ui; ui->setupUi(measurementDialog); - connect(measurementDialog, &QDialog::finished, [=](){ + connect(measurementDialog, &QDialog::finished, this, [=](){ if(measuring) { measuring = false; emit finishedMeasurement(); @@ -53,7 +54,7 @@ void Deembedding::startMeasurementDialog(DeembeddingOption *option) connect(traceChooser, &SparamTraceSelector::selectionValid, ui->buttonBox, &QDialogButtonBox::setEnabled); - connect(ui->bMeasure, &QPushButton::clicked, [=](){ + connect(ui->bMeasure, &QPushButton::clicked, this, [=](){ ui->bMeasure->setEnabled(false); traceChooser->setEnabled(false); ui->buttonBox->setEnabled(false); @@ -61,7 +62,7 @@ void Deembedding::startMeasurementDialog(DeembeddingOption *option) emit triggerMeasurement(); }); - connect(ui->buttonBox, &QDialogButtonBox::accepted, [=](){ + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, [=](){ // create datapoints from individual traces measurements.clear(); auto points = Trace::assembleDatapoints(traceChooser->getTraces()); diff --git a/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/impedancerenormalization.cpp b/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/impedancerenormalization.cpp index 2ccb30b..d1ee2ef 100644 --- a/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/impedancerenormalization.cpp +++ b/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/impedancerenormalization.cpp @@ -37,36 +37,10 @@ std::set ImpedanceRenormalization::getAffectedPorts() void ImpedanceRenormalization::transformDatapoint(DeviceDriver::VNAMeasurement &p) { - std::map> transformed; - int ports = 0; - QString name = "S11"; - while(p.measurements.count(name) > 0) { - ports++; - name = "S"+QString::number(ports+1)+QString::number(ports+1); - } - for(auto i=1;i<=ports;i++) { - // handle reflection parameters - auto S11name = "S"+QString::number(i)+QString::number(i); - auto S11 = p.measurements[S11name]; - transformed[S11name] = Util::ImpedanceToSparam(Util::SparamToImpedance(S11, p.Z0), impedance); - // handle transmission parameters - for(auto j=i+1;j<=ports;j++) { - auto S12name = "S"+QString::number(i)+QString::number(j); - auto S21name = "S"+QString::number(j)+QString::number(i); - auto S22name = "S"+QString::number(j)+QString::number(j); - if(!p.measurements.count(S12name) || !p.measurements.count(S21name) || !p.measurements.count(S22name)) { - // not all measurements available, skip this - continue; - } - auto S12 = p.measurements[S12name]; - auto S21 = p.measurements[S21name]; - auto S22 = p.measurements[S22name]; - auto S_t = Sparam(ABCDparam(Sparam(S11, S12, S21, S22), p.Z0), impedance); - transformed[S12name] = S_t.get(1,2); - transformed[S21name] = S_t.get(2,1); - } - } - p.measurements = transformed; + auto S = p.toSparam(); + auto Z = Zparam(S, p.Z0); + auto S_renorm = Sparam(Z, impedance); + p.fromSparam(S_renorm); p.Z0 = impedance; } diff --git a/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/matchingnetwork.cpp b/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/matchingnetwork.cpp index 7aec3ae..52823fc 100644 --- a/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/matchingnetwork.cpp +++ b/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/matchingnetwork.cpp @@ -114,6 +114,9 @@ void MatchingNetwork::transformDatapoint(DeviceDriver::VNAMeasurement &p) // handle the measurements for(auto &meas : p.measurements) { QString name = meas.first; + if(!name.startsWith("S")) { + continue; + } unsigned int i = name.mid(1,1).toUInt(); unsigned int j = name.mid(2,1).toUInt(); if(i == j) { @@ -126,9 +129,9 @@ void MatchingNetwork::transformDatapoint(DeviceDriver::VNAMeasurement &p) } else { // another reflection measurement try { - auto S = uncorrected.toSparam(i, port); + auto S = uncorrected.toSparam().reduceTo({i, port}); auto corrected = Sparam(ABCDparam(S, p.Z0) * m.reverse, p.Z0); - p.fromSparam(corrected, i, port); + p.fromSparam(corrected, {i, port}); } catch (...) { // missing measurements, nothing can be done } diff --git a/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/twothru.cpp b/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/twothru.cpp index 824bf57..3fe84fe 100644 --- a/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/twothru.cpp +++ b/Software/PC_Application/LibreVNA-GUI/VNA/Deembedding/twothru.cpp @@ -29,7 +29,7 @@ void TwoThru::transformDatapoint(DeviceDriver::VNAMeasurement &p) { // correct measurement if(points.size() > 0) { - Tparam meas(p.toSparam(port1,port2)); + Tparam meas(p.toSparam().reduceTo({port1, port2})); Tparam inv1, inv2; if(p.frequency < points.front().freq) { @@ -59,7 +59,7 @@ void TwoThru::transformDatapoint(DeviceDriver::VNAMeasurement &p) // perform correction Tparam corrected = inv1*meas*inv2; // transform back into S parameters - p.fromSparam(Sparam(corrected), port1, port2); + p.fromSparam(Sparam(corrected), {port1, port2}); } } @@ -280,7 +280,7 @@ std::vector TwoThru::calculateErrorBoxes(std::vector TwoThru::calculateErrorBoxes(std::vector p; vector f; for(auto d : data_2xthru) { - p.push_back(d.toSparam(1, 2)); + p.push_back(d.toSparam().reduceTo({port1, port2})); f.push_back(d.frequency); } auto data_2xthru_Sparam = p; vector data_fix_dut_fix_Sparam; for(auto d : data_fix_dut_fix) { - data_fix_dut_fix_Sparam.push_back(d.toSparam(1, 2)); + data_fix_dut_fix_Sparam.push_back(d.toSparam().reduceTo({port1, port2})); } // grabbing S21 diff --git a/Software/PC_Application/LibreVNA-Test/parametertests.cpp b/Software/PC_Application/LibreVNA-Test/parametertests.cpp index 78490d7..c3abb5a 100644 --- a/Software/PC_Application/LibreVNA-Test/parametertests.cpp +++ b/Software/PC_Application/LibreVNA-Test/parametertests.cpp @@ -61,3 +61,109 @@ void ParameterTests::ABCD2S() QVERIFY(qFuzzyCompare(s.get(2,2).real(), S22.real())); QVERIFY(qFuzzyCompare(s.get(2,2).imag(), S22.imag())); } + +void ParameterTests::S2Z_1P() +{ + using namespace std::complex_literals; + + std::complex S11 = 0.0038 + 0.0248i; + auto S = Sparam(S11); + + // test for various characteristic impedances + for(auto Z0 = 10.0; Z0 <= 100.0; Z0 += 10.0) { + auto Z = Zparam(S, Z0); + + // calculate expected Z values based on two-port formulas + auto Z11 = (1.0+S11) / (1.0-S11) * Z0; + + // error due to floating point calculations are too big for qFuzzyCompare(double, double), use qFuzzyCompare(float, float) instead + QVERIFY(qFuzzyCompare((float)Z.get(1,1).real(), (float)Z11.real())); + QVERIFY(qFuzzyCompare((float)Z.get(1,1).imag(), (float)Z11.imag())); + } +} + +void ParameterTests::S2Z_2P() +{ + using namespace std::complex_literals; + + std::complex S11 = 0.0038 + 0.0248i; + std::complex S12 = 0.9961 - 0.0250i; + std::complex S21 = 0.9964 - 0.0254i; + std::complex S22 = 0.0037 + 0.0249i; + auto S = Sparam(S11, S12, S21, S22); + + // test for various characteristic impedances + for(auto Z0 = 10.0; Z0 <= 100.0; Z0 += 10.0) { + auto Z = Zparam(S, Z0); + + // calculate expected Z values based on two-port formulas + auto deltaS = (1.0-S.get(1,1))*(1.0-S.get(2,2))-S.get(1,2)*S.get(2,1); + auto Z11 = ((1.0+S.get(1,1))*(1.0-S.get(2,2))+S.get(1,2)*S.get(2,1))/deltaS*Z0; + auto Z12 = 2.0*S.get(1,2)/deltaS*Z0; + auto Z21 = 2.0*S.get(2,1)/deltaS*Z0; + auto Z22 = ((1.0-S.get(1,1))*(1.0+S.get(2,2))+S.get(1,2)*S.get(2,1))/deltaS*Z0; + + // error due to floating point calculations are too big for qFuzzyCompare(double, double), use qFuzzyCompare(float, float) instead + QVERIFY(qFuzzyCompare((float)Z.get(1,1).real(), (float)Z11.real())); + QVERIFY(qFuzzyCompare((float)Z.get(1,1).imag(), (float)Z11.imag())); + QVERIFY(qFuzzyCompare((float)Z.get(1,2).real(), (float)Z12.real())); + QVERIFY(qFuzzyCompare((float)Z.get(1,2).imag(), (float)Z12.imag())); + QVERIFY(qFuzzyCompare((float)Z.get(2,1).real(), (float)Z21.real())); + QVERIFY(qFuzzyCompare((float)Z.get(2,1).imag(), (float)Z21.imag())); + QVERIFY(qFuzzyCompare((float)Z.get(2,2).real(), (float)Z22.real())); + QVERIFY(qFuzzyCompare((float)Z.get(2,2).imag(), (float)Z22.imag())); + } +} + +void ParameterTests::Z2S_1P() +{ + using namespace std::complex_literals; + + std::complex Z11 = 0.0038 + 0.0248i; + auto Z = Zparam(Z11); + + // test for various characteristic impedances + for(auto Z0 = 10.0; Z0 <= 100.0; Z0 += 10.0) { + auto S = Sparam(Z, Z0); + + // calculate expected Z values based on two-port formulas + auto S11 = (Z11/Z0-1.0) / (Z11/Z0+1.0); + + // error due to floating point calculations are too big for qFuzzyCompare(double, double), use qFuzzyCompare(float, float) instead + QVERIFY(qFuzzyCompare((float)S.get(1,1).real(), (float)S11.real())); + QVERIFY(qFuzzyCompare((float)S.get(1,1).imag(), (float)S11.imag())); + } +} + +void ParameterTests::Z2S_2P() +{ + using namespace std::complex_literals; + + std::complex Z11 = 0.0038 + 0.0248i; + std::complex Z12 = 0.9961 - 0.0250i; + std::complex Z21 = 0.9964 - 0.0254i; + std::complex Z22 = 0.0037 + 0.0249i; + auto Z = Zparam(Z11, Z12, Z21, Z22); + + // test for various characteristic impedances + for(auto Z0 = 10.0; Z0 <= 100.0; Z0 += 10.0) { + auto S = Sparam(Z, Z0); + + // calculate expected Z values based on two-port formulas + auto delta = (Z.get(1,1)+Z0)*(Z.get(2,2)+Z0)-Z.get(1,2)*Z.get(2,1); + auto S11 = ((Z.get(1,1)-Z0)*(Z.get(2,2)+Z0)-Z.get(1,2)*Z.get(2,1))/delta; + auto S12 = 2.0*Z0*Z.get(1,2)/delta; + auto S21 = 2.0*Z0*Z.get(2,1)/delta; + auto S22 = ((Z.get(1,1)+Z0)*(Z.get(2,2)-Z0)-Z.get(1,2)*Z.get(2,1))/delta; + + // error due to floating point calculations are too big for qFuzzyCompare(double, double), use qFuzzyCompare(float, float) instead + QVERIFY(qFuzzyCompare((float)S.get(1,1).real(), (float)S11.real())); + QVERIFY(qFuzzyCompare((float)S.get(1,1).imag(), (float)S11.imag())); + QVERIFY(qFuzzyCompare((float)S.get(1,2).real(), (float)S12.real())); + QVERIFY(qFuzzyCompare((float)S.get(1,2).imag(), (float)S12.imag())); + QVERIFY(qFuzzyCompare((float)S.get(2,1).real(), (float)S21.real())); + QVERIFY(qFuzzyCompare((float)S.get(2,1).imag(), (float)S21.imag())); + QVERIFY(qFuzzyCompare((float)S.get(2,2).real(), (float)S22.real())); + QVERIFY(qFuzzyCompare((float)S.get(2,2).imag(), (float)S22.imag())); + } +} diff --git a/Software/PC_Application/LibreVNA-Test/parametertests.h b/Software/PC_Application/LibreVNA-Test/parametertests.h index 743187d..f40fde6 100644 --- a/Software/PC_Application/LibreVNA-Test/parametertests.h +++ b/Software/PC_Application/LibreVNA-Test/parametertests.h @@ -12,6 +12,10 @@ public: private slots: void S2ABCD(); void ABCD2S(); + void S2Z_1P(); + void S2Z_2P(); + void Z2S_1P(); + void Z2S_2P(); }; #endif // PARAMETERTESTS_H