#include "calibration.h" #include "ui_calibrationdialogui.h" #include "CustomWidgets/informationbox.h" #include "Util/app_common.h" #include "unit.h" #include "Util/util.h" #include "LibreCAL/librecaldialog.h" #include "Eigen/Dense" #include #include #include #include #include #include #include using namespace std; using Eigen::MatrixXcd; bool operator==(const Calibration::CalType &lhs, const Calibration::CalType &rhs) { if(lhs.type != rhs.type) { return false; } if(lhs.usedPorts.size() != rhs.usedPorts.size()) { return false; } for(unsigned int i=0;i QString { if(params.size() != 1) { return SCPI::getResultName(SCPI::Result::Error); } else { auto availableCals = getAvailableCalibrations(); for(auto caltype : availableCals) { if(caltype.getShortString().compare(params[0], Qt::CaseInsensitive) == 0) { // found a match // check if calibration can be activated if(canCompute(caltype)) { compute(caltype); return SCPI::getResultName(SCPI::Result::Empty); } else { return SCPI::getResultName(SCPI::Result::Error); } } } // if we get here, the supplied parameter did not match any of the available calibrations return SCPI::getResultName(SCPI::Result::Error); } return SCPI::getResultName(SCPI::Result::Empty); }, [=](QStringList) -> QString { auto availableCals = getAvailableCalibrations(); if(availableCals.size() == 0) { return SCPI::getResultName(SCPI::Result::Empty); } auto ret = availableCals[0].getShortString(); for(unsigned int i=1;i QString { return caltype.getShortString(); })); add(new SCPICommand("NUMber", nullptr, [=](QStringList) -> QString { return QString::number(measurements.size()); })); add(new SCPICommand("RESET", [=](QStringList) -> QString { reset(); return SCPI::getResultName(SCPI::Result::Empty); }, nullptr)); add(new SCPICommand("ADD", [=](QStringList params) -> QString { if(params.size() < 1) { // no measurement type specified return SCPI::getResultName(SCPI::Result::Error); } else { // parse measurement type auto type = CalibrationMeasurement::Base::TypeFromString(params[0]); auto newMeas = newMeasurement(type); if(!newMeas) { // failed to create this type of measurement return SCPI::getResultName(SCPI::Result::Error); } if(params.size() == 2) { // standard name given CalStandard::Virtual *standard = nullptr; for(auto s : newMeas->supportedStandards()) { if(s->getName().compare(params[1], Qt::CaseInsensitive) == 0) { // use this standard standard = s; } } if(!standard) { // specified standard not available return SCPI::getResultName(SCPI::Result::Error); } newMeas->setStandard(standard); } measurements.push_back(newMeas); return SCPI::getResultName(SCPI::Result::Empty); } }, nullptr)); add(new SCPICommand("TYPE", nullptr, [=](QStringList params) -> QString { if(params.size() < 1) { // no measurement number specified return SCPI::getResultName(SCPI::Result::Error); } else { bool okay; unsigned int number = params[0].toUInt(&okay); if(!okay || number >= measurements.size()) { // invalid measurement specified return SCPI::getResultName(SCPI::Result::Error); } return CalibrationMeasurement::Base::TypeToString(measurements[number]->getType()); } })); add(new SCPICommand("PORT", [=](QStringList params) -> QString { if(params.size() < 1) { // invalid number of parameters return SCPI::getResultName(SCPI::Result::Error); } else { bool okay; unsigned int number = params[0].toUInt(&okay); if(!okay || number >= measurements.size()) { // invalid measurement specified return SCPI::getResultName(SCPI::Result::Error); } auto meas = measurements[number]; auto onePort = dynamic_cast(meas); if(onePort) { if(params.size() != 2) { // invalid number of parameters return SCPI::getResultName(SCPI::Result::Error); } bool okay; unsigned int number = params[1].toInt(&okay); if(!okay || number < 1 || number > DeviceDriver::getInfo(DeviceDriver::getActiveDriver()).Limits.VNA.ports) { // invalid port specified return SCPI::getResultName(SCPI::Result::Error); } onePort->setPort(number); return SCPI::getResultName(SCPI::Result::Empty); } auto twoPort = dynamic_cast(meas); if(twoPort) { if(params.size() != 3) { // invalid number of parameters return SCPI::getResultName(SCPI::Result::Error); } bool okay1; unsigned int port1 = params[1].toInt(&okay1); bool okay2; unsigned int port2 = params[2].toInt(&okay2); if(!okay1 || !okay2 || port1 < 1 || port2 > DeviceDriver::getInfo(DeviceDriver::getActiveDriver()).Limits.VNA.ports || port2 < 1 || port2 > DeviceDriver::getInfo(DeviceDriver::getActiveDriver()).Limits.VNA.ports) { // invalid port specified return SCPI::getResultName(SCPI::Result::Error); } twoPort->setPort1(port1); twoPort->setPort2(port2); return SCPI::getResultName(SCPI::Result::Empty); } return SCPI::getResultName(SCPI::Result::Error); } }, [=](QStringList params) -> QString { if(params.size() != 1) { // invalid number of parameters return SCPI::getResultName(SCPI::Result::Error); } else { bool okay; unsigned int number = params[0].toUInt(&okay); if(!okay || number >= measurements.size()) { // invalid measurement specified return SCPI::getResultName(SCPI::Result::Error); } auto meas = measurements[number]; auto onePort = dynamic_cast(meas); if(onePort) { return QString::number(onePort->getPort()); } auto twoPort = dynamic_cast(meas); if(twoPort) { return QString::number(twoPort->getPort1()) + " " + QString::number(twoPort->getPort2()); } return SCPI::getResultName(SCPI::Result::Error); } })); add(new SCPICommand("STANDARD", [=](QStringList params) -> QString { if(params.size() != 2) { // invalid number of parameters return SCPI::getResultName(SCPI::Result::Error); } else { bool okay; unsigned int number = params[0].toUInt(&okay); if(!okay || number >= measurements.size()) { // invalid measurement specified return SCPI::getResultName(SCPI::Result::Error); } auto meas = measurements[number]; for(auto s : meas->supportedStandards()) { if(s->getName().compare(params[1], Qt::CaseInsensitive) == 0) { // use this standard meas->setStandard(s); return SCPI::getResultName(SCPI::Result::Empty); } } // specified standard not available return SCPI::getResultName(SCPI::Result::Error); } }, [=](QStringList params) -> QString { if(params.size() != 1) { // invalid number of parameters return SCPI::getResultName(SCPI::Result::Error); } else { bool okay; unsigned int number = params[0].toUInt(&okay); if(!okay || number >= measurements.size()) { // invalid measurement specified return SCPI::getResultName(SCPI::Result::Error); } auto s = measurements[number]->getStandard(); if(s) { return s->getName(); } else { // no standard set return "None"; } } })); add(new SCPICommand("MEASure", [=](QStringList params) -> QString { if(params.size() < 1 ) { // no measurement specified // TODO check if measurement is already running return SCPI::getResultName(SCPI::Result::Error); } else { // assemble list of measurements to take std::set m; for(QString p : params) { bool okay = false; unsigned int number = p.toUInt(&okay); if(!okay || number >= measurements.size()) { // invalid measurement specified return SCPI::getResultName(SCPI::Result::Error); } else { m.insert(measurements[number]); } } if(CalibrationMeasurement::Base::canMeasureSimultaneously(m)) { emit startMeasurements(m); return SCPI::getResultName(SCPI::Result::Empty); } else { // can't start these measurements simultaneously return SCPI::getResultName(SCPI::Result::Error); } } }, nullptr)); add(new SCPICommand("SAVE", [=](QStringList params) -> QString { if(params.size() != 1 || getCaltype().type == Calibration::Type::None) { // no filename given or no calibration active return SCPI::getResultName(SCPI::Result::Error); } if(!toFile(params[0])) { // some error when writing the calibration file return SCPI::getResultName(SCPI::Result::Error); } return SCPI::getResultName(SCPI::Result::Empty); }, nullptr)); add(new SCPICommand("LOAD", nullptr, [=](QStringList params) -> QString { if(params.size() != 1) { // no filename given or no calibration active return SCPI::getResultName(SCPI::Result::False); } if(!fromFile(params[0])) { // some error when loading the calibration file return SCPI::getResultName(SCPI::Result::False); } return SCPI::getResultName(SCPI::Result::True); })); add(&kit); } QString Calibration::TypeToString(Calibration::Type type) { switch(type) { case Type::None: return "None"; case Type::SOLT: return "SOLT"; case Type::ThroughNormalization: return "ThroughNormalization"; case Type::TRL: return "TRL"; case Type::Last: return "Invalid"; } return ""; } Calibration::Type Calibration::TypeFromString(QString s) { for(int i=0;i<(int) Type::Last;i++) { if(TypeToString((Type) i) == s) { return (Type) i; } } return Type::None; } void Calibration::correctMeasurement(DeviceDriver::VNAMeasurement &d) { lock_guard guard(access); if(caltype.type == Type::None) { // no calibration active, nothing to do return; } // formulas from "Multi-Port Calibration Techniques for Differential Parameter Measurements with Network Analyzers", variable names also losely follow this document MatrixXcd S(caltype.usedPorts.size(), caltype.usedPorts.size()); MatrixXcd a(caltype.usedPorts.size(), caltype.usedPorts.size()); MatrixXcd b(caltype.usedPorts.size(), caltype.usedPorts.size()); // gab point and interpolate Point p; if(d.frequency <= points.front().frequency) { p = points.front(); } else if(d.frequency >= points.back().frequency) { p = points.back(); } else { // needs to interpolate auto lower = lower_bound(points.begin(), points.end(), d.frequency, [](const Point &lhs, double rhs) -> bool { return lhs.frequency < rhs; }); auto highPoint = *lower; auto lowPoint = *prev(lower); double alpha = (d.frequency - lowPoint.frequency) / (highPoint.frequency - lowPoint.frequency); p = lowPoint.interpolate(highPoint, alpha); } // Grab measurements (easier to access by index later) for(unsigned int i=0;i traceSet) { auto points = Trace::assembleDatapoints(traceSet); if(points.size()) { // succeeded in assembling datapoints for(auto &p : points) { correctMeasurement(p); } Trace::fillFromDatapoints(traceSet, points); } } void Calibration::edit() { auto d = new QDialog(); d->setAttribute(Qt::WA_DeleteOnClose); auto ui = new Ui::CalibrationDialog; ui->setupUi(d); ui->calMinFreq->setUnit("Hz"); ui->calMinFreq->setPrecision(4); ui->calMinFreq->setPrefixes(" kMG"); ui->calMaxFreq->setUnit("Hz"); ui->calMaxFreq->setPrecision(4); ui->calMaxFreq->setPrefixes(" kMG"); // generate all possible calibration with the connected device vector availableCals = getAvailableCalibrations(); for(auto c : availableCals) { ui->calibrationList->addItem(c.getReadableDescription()); } auto updateCalStatistics = [=](){ lock_guard guard(access); ui->activeCalibration->setText(caltype.getReadableDescription()); ui->calPoints->setValue(points.size()); if(points.size() > 0) { ui->calMinFreq->setValue(points.front().frequency); ui->calMaxFreq->setValue(points.back().frequency); } else { ui->calMinFreq->setValue(0); ui->calMaxFreq->setValue(0); } }; auto updateCalButtons = [=](){ lock_guard guard(access); auto row = ui->calibrationList->currentRow(); if(row < 0) { ui->activate->setEnabled(false); ui->deactivate->setEnabled(false); } else { if(caltype == availableCals[row]) { ui->deactivate->setEnabled(true); ui->activate->setEnabled(false); } else { ui->deactivate->setEnabled(false); ui->activate->setEnabled(canCompute(availableCals[row])); } } }; auto updateCalibrationList = [=](){ auto style = QApplication::style(); for(unsigned int i=0;istandardIcon(QStyle::SP_DialogApplyButton); } else { icon = style->standardIcon(QStyle::SP_MessageBoxCritical); } ui->calibrationList->item(i)->setIcon(icon); } updateCalButtons(); }; updateCalibrationList(); updateCalStatistics(); updateCalButtons(); connect(ui->calibrationList, &QListWidget::doubleClicked, [=](const QModelIndex&) { ui->activate->clicked(); }); connect(ui->calibrationList, &QListWidget::currentRowChanged, [=](){ updateCalButtons(); }); connect(this, &Calibration::activated, d, [=](){ updateCalibrationList(); updateCalStatistics(); updateCalButtons(); }); connect(this, &Calibration::deactivated, d, [=](){ updateCalibrationList(); updateCalStatistics(); updateCalButtons(); }); connect(ui->activate, &QPushButton::clicked, [=](){ auto cal = availableCals[ui->calibrationList->currentRow()]; compute(cal); }); connect(ui->deactivate, &QPushButton::clicked, this, &Calibration::deactivate); ui->table->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); auto updateTableEditButtons = [=](){ ui->bDelete->setEnabled(ui->table->currentRow() >= 0); ui->bMoveUp->setEnabled(ui->table->currentRow() >= 1); ui->bMoveDown->setEnabled(ui->table->currentRow() >= 0 && ui->table->currentRow() < ui->table->rowCount() - 1); }; auto updateMeasurementTable = [=](){ int row = ui->table->currentRow(); ui->table->clear(); ui->table->setColumnCount(5); ui->table->setHorizontalHeaderItem(0, new QTableWidgetItem("Type")); ui->table->setHorizontalHeaderItem(1, new QTableWidgetItem("Calkit Standard")); ui->table->setHorizontalHeaderItem(2, new QTableWidgetItem("Settings")); ui->table->setHorizontalHeaderItem(3, new QTableWidgetItem("Statistics")); ui->table->setHorizontalHeaderItem(4, new QTableWidgetItem("Timestamp")); ui->table->setRowCount(measurements.size()); for(unsigned int i=0;itable->setItem(i, 0, new QTableWidgetItem(CalibrationMeasurement::Base::TypeToString(measurements[i]->getType()))); ui->table->setCellWidget(i, 1, measurements[i]->createStandardWidget()); ui->table->setCellWidget(i, 2, measurements[i]->createSettingsWidget()); ui->table->setItem(i, 3, new QTableWidgetItem(measurements[i]->getStatistics())); ui->table->setItem(i, 4, new QTableWidgetItem(measurements[i]->getTimestamp().toString())); } ui->table->selectRow(row); updateTableEditButtons(); }; ui->createDefault->addItem(" "); for(unsigned int i=0;i<(int) DefaultMeasurements::Last;i++) { ui->createDefault->addItem(DefaultMeasurementsToString((DefaultMeasurements) i)); } QObject::connect(ui->createDefault, qOverload(&QComboBox::currentIndexChanged), [=](){ if(measurements.size() > 0) { if(!InformationBox::AskQuestion("Create default entries?", "Do you want to remove all existing entries and create default calibration measurements instead?", true)) { // user aborted ui->createDefault->blockSignals(true); ui->createDefault->setCurrentIndex(0); ui->createDefault->blockSignals(false); return; } deleteMeasurements(); } createDefaultMeasurements((DefaultMeasurements) (ui->createDefault->currentIndex() - 1)); updateMeasurementTable(); updateCalibrationList(); ui->createDefault->blockSignals(true); ui->createDefault->setCurrentIndex(0); ui->createDefault->blockSignals(false); }); QObject::connect(ui->bDelete, &QPushButton::clicked, [=](){ auto selected = ui->table->selectionModel()->selectedRows(); set toDelete; for(auto s : selected) { toDelete.insert(measurements[s.row()]); } while(toDelete.size() > 0) { for(unsigned int i=0;ibMoveUp, &QPushButton::clicked, [=](){ auto row = ui->table->currentRow(); if(row >= 1) { swap(measurements[row], measurements[row-1]); ui->table->selectRow(row-1); updateMeasurementTable(); } }); QObject::connect(ui->bMoveDown, &QPushButton::clicked, [=](){ auto row = ui->table->currentRow(); if(row >= 0) { swap(measurements[row], measurements[row+1]); ui->table->selectRow(row+1); updateMeasurementTable(); } }); connect(ui->measure, &QPushButton::clicked, [=](){ std::set m; auto selected = ui->table->selectionModel()->selectedRows(); for(auto s : selected) { auto meas = measurements[s.row()]; if(!meas->readyForMeasurement()) { InformationBox::ShowError("Unable to measure", CalibrationMeasurement::Base::TypeToString(meas->getType())+" measurement is not ready, please check that a valid calibration standard is selected"); return; } m.insert(measurements[s.row()]); } if(!CalibrationMeasurement::Base::canMeasureSimultaneously(m)) { InformationBox::ShowError("Unable to measure", "Different selected measurements require the same port, unable to perform measurement"); return; } emit startMeasurements(m); }); connect(this, &Calibration::measurementsUpdated, d, [=](){ updateMeasurementTable(); updateCalibrationList(); }); connect(ui->clearMeasurement, &QPushButton::clicked, [=](){ auto selected = ui->table->selectionModel()->selectedRows(); for(auto s : selected) { measurements[s.row()]->clearPoints(); } // update calibration (may have changed due to deleted measurement) if(canCompute(caltype)) { compute(caltype); } else { deactivate(); } updateMeasurementTable(); updateCalibrationList(); }); connect(ui->eCal, &QPushButton::clicked, [=](){ auto d = new LibreCALDialog(this); d->show(); }); connect(ui->editCalkit, &QPushButton::clicked, [=](){ kit.edit([=](){ updateMeasurementTable(); // update calibration (may have changed due to edited calibration standard) if(canCompute(caltype)) { compute(caltype); } else { deactivate(); } }); }); QObject::connect(ui->table, &QTableWidget::currentCellChanged, updateTableEditButtons); auto addMenu = new QMenu(); for(auto t : CalibrationMeasurement::Base::availableTypes()) { auto action = new QAction(CalibrationMeasurement::Base::TypeToString(t)); QObject::connect(action, &QAction::triggered, [=](){ auto newMeas = newMeasurement(t); if(newMeas) { measurements.push_back(newMeas); updateMeasurementTable(); } }); addMenu->addAction(action); } ui->bAdd->setMenu(addMenu); updateMeasurementTable(); d->show(); } CalibrationMeasurement::Base *Calibration::newMeasurement(CalibrationMeasurement::Base::Type type) { CalibrationMeasurement::Base *m = nullptr; switch(type) { case CalibrationMeasurement::Base::Type::Open: m = new CalibrationMeasurement::Open(this); break; case CalibrationMeasurement::Base::Type::Short: m = new CalibrationMeasurement::Short(this); break; case CalibrationMeasurement::Base::Type::Load: m = new CalibrationMeasurement::Load(this); break; case CalibrationMeasurement::Base::Type::SlidingLoad: m = new CalibrationMeasurement::SlidingLoad(this); break; case CalibrationMeasurement::Base::Type::Reflect: m = new CalibrationMeasurement::Reflect(this); break; case CalibrationMeasurement::Base::Type::Through: m = new CalibrationMeasurement::Through(this); break; case CalibrationMeasurement::Base::Type::Isolation: m = new CalibrationMeasurement::Isolation(this); break; case CalibrationMeasurement::Base::Type::Line: m = new CalibrationMeasurement::Line(this); break; case CalibrationMeasurement::Base::Type::Last: break; } return m; } Calibration::Point Calibration::createInitializedPoint(double f) { Point point; point.frequency = f; // resize vectors point.D.resize(caltype.usedPorts.size(), 0.0); point.R.resize(caltype.usedPorts.size(), 0.0); point.S.resize(caltype.usedPorts.size(), 0.0); point.L.resize(caltype.usedPorts.size()); point.T.resize(caltype.usedPorts.size()); point.I.resize(caltype.usedPorts.size()); fill(point.L.begin(), point.L.end(), vector>(caltype.usedPorts.size(), 0.0)); fill(point.T.begin(), point.T.end(), vector>(caltype.usedPorts.size(), 0.0)); fill(point.I.begin(), point.I.end(), vector>(caltype.usedPorts.size(), 0.0)); return point; } Calibration::Point Calibration::computeSOLT(double f) { Point point = createInitializedPoint(f); // Calculate SOL coefficients for(unsigned int i=0;i(findMeasurement(CalibrationMeasurement::Base::Type::Short, p)); auto open = static_cast(findMeasurement(CalibrationMeasurement::Base::Type::Open, p)); auto s_m = _short->getMeasured(f); auto o_m = open->getMeasured(f); auto s_c = _short->getActual(f); auto o_c = open->getActual(f); complex l_c, l_m; auto slidingMeasurements = findMeasurements(CalibrationMeasurement::Base::Type::SlidingLoad, p); if(slidingMeasurements.size() >= 3) { // use sliding load vector> slidingMeasured; for(auto m : slidingMeasurements) { auto slidingLoad = static_cast(m); auto value = slidingLoad->getMeasured(f); if(isnan(abs(value))) { throw runtime_error("missing sliding load measurement"); } slidingMeasured.push_back(value); } // use center of measurement for ideal load measurement l_m = Util::findCenterOfCircle(slidingMeasured); // assume perfect sliding load l_c = 0.0; } else { // use normal load standard auto load = static_cast(findMeasurement(CalibrationMeasurement::Base::Type::Load, p)); l_c = load->getActual(f); l_m = load->getMeasured(f); } auto denom = l_c * o_c * (o_m - l_m) + l_c * s_c * (l_m - s_m) + o_c * s_c * (s_m - o_m); point.D[i] = (l_c * o_m * (s_m * (o_c - s_c) + l_m * s_c) - l_c * o_c * l_m * s_m + o_c * l_m * s_c * (s_m - o_m)) / denom; point.S[i] = (l_c * (o_m - s_m) + o_c * (s_m - l_m) + s_c * (l_m - o_m)) / denom; auto delta = (l_c * l_m * (o_m - s_m) + o_c * o_m * (s_m - l_m) + s_c * s_m * (l_m - o_m)) / denom; point.R[i] = point.D[i] * point.S[i] - delta; } // calculate forward match and transmission for(unsigned int i=0;i(findMeasurement(CalibrationMeasurement::Base::Type::Through, p1, p2)); auto throughReverse = static_cast(findMeasurement(CalibrationMeasurement::Base::Type::Through, p2, p1)); complex S11, S21; Sparam Sideal; if(throughForward) { S11 = throughForward->getMeasured(f).m11; S21 = throughForward->getMeasured(f).m21; Sideal = throughForward->getActual(f); } else if(throughReverse) { S11 = throughReverse->getMeasured(f).m22; S21 = throughReverse->getMeasured(f).m12; Sideal = throughReverse->getActual(f); swap(Sideal.m11, Sideal.m22); swap(Sideal.m12, Sideal.m21); } auto isoMeas = static_cast(findMeasurement(CalibrationMeasurement::Base::Type::Isolation)); auto isolation = complex(0.0,0.0); if(isoMeas) { isolation = isoMeas->getMeasured(f, p2, p1); } auto deltaS = Sideal.m11*Sideal.m22 - Sideal.m21 * Sideal.m12; point.L[i][j] = ((S11 - point.D[i])*(1.0 - point.S[i] * Sideal.m11)-Sideal.m11*point.R[i]) / ((S11 - point.D[i])*(Sideal.m22-point.S[i]*deltaS)-deltaS*point.R[i]); point.T[i][j] = (S21 - isolation)*(1.0 - point.S[i]*Sideal.m11 - point.L[i][j]*Sideal.m22 + point.S[i]*point.L[i][j]*deltaS) / Sideal.m21; point.I[i][j] = isolation; } } return point; } Calibration::Point Calibration::computeThroughNormalization(double f) { Point point = createInitializedPoint(f); // Calculate SOL coefficients for(unsigned int i=0;i(findMeasurement(CalibrationMeasurement::Base::Type::Through, p1, p2)); auto throughReverse = static_cast(findMeasurement(CalibrationMeasurement::Base::Type::Through, p2, p1)); complex S21 = 0.0; Sparam Sideal; if(throughForward) { S21 = throughForward->getMeasured(f).m21; Sideal = throughForward->getActual(f); } else if(throughReverse) { S21 = throughReverse->getMeasured(f).m12; Sideal = throughReverse->getActual(f); swap(Sideal.m12, Sideal.m21); } point.L[i][j] = 0.0; point.T[i][j] = S21 / Sideal.m21; point.I[i][j] = 0.0; } } return point; } Calibration::Point Calibration::computeTRL(double freq) { Point point = createInitializedPoint(freq); // calculate forward match and transmission for(unsigned int i=0;i(findMeasurement(CalibrationMeasurement::Base::Type::Reflect, p1)); auto S22_reflection = static_cast(findMeasurement(CalibrationMeasurement::Base::Type::Reflect, p2)); bool S11_short, S22_short; { auto S11_short_standard = dynamic_cast(S11_reflection->getStandard()); auto S11_open_standard = dynamic_cast(S11_reflection->getStandard()); auto S11_reflect_standard = dynamic_cast(S11_reflection->getStandard()); if(S11_short_standard) { S11_short = true; } else if(S11_open_standard) { S11_short = false; } else if(S11_reflect_standard) { S11_short = S11_reflect_standard->getIsShort(); } else { // invalid standard throw runtime_error("Invalid standard defined for reflection measurement"); } auto S22_short_standard = dynamic_cast(S22_reflection->getStandard()); auto S22_open_standard = dynamic_cast(S22_reflection->getStandard()); auto S22_reflect_standard = dynamic_cast(S22_reflection->getStandard()); if(S22_short_standard) { S22_short = true; } else if(S22_open_standard) { S22_short = false; } else if(S22_reflect_standard) { S22_short = S22_reflect_standard->getIsShort(); } else { // invalid standard throw runtime_error("Invalid standard defined for reflection measurement"); } } bool reflectionIsNegative; if(S11_short && S22_short) { reflectionIsNegative = true; } else if(!S11_short && !S22_short) { reflectionIsNegative = false; } else { throw runtime_error("Reflection measurements must all use the same standard (either open or short)"); } // grab through measurement auto throughForward = static_cast(findMeasurement(CalibrationMeasurement::Base::Type::Through, p1, p2)); auto throughReverse = static_cast(findMeasurement(CalibrationMeasurement::Base::Type::Through, p2, p1)); Sparam Sthrough; if(throughForward) { Sthrough = throughForward->getMeasured(freq); } else if(throughReverse) { Sthrough = throughReverse->getMeasured(freq); swap(Sthrough.m11, Sthrough.m22); swap(Sthrough.m12, Sthrough.m21); } // grab line measurement auto forwardLines = findMeasurements(CalibrationMeasurement::Base::Type::Line, p1, p2); auto reverseLines = findMeasurements(CalibrationMeasurement::Base::Type::Line, p2, p1); // find the closest (geometric) match for the current frequency double closestIdealFreqRatio = numeric_limits::max(); bool closestLineIsReversed = false; CalibrationMeasurement::Line* closestLine = nullptr; CalStandard::Line* closestStandard = nullptr; for(int i=0;i<2;i++) { auto list = i ? reverseLines : forwardLines; for(auto l : list) { auto line = static_cast(l); auto standard = static_cast(line->getStandard()); double idealFreq = (standard->minFrequency() + standard->maxFrequency()) / 2; double mismatch = idealFreq / freq; if(mismatch < 0) { mismatch = 1.0 / mismatch; } if(mismatch < closestIdealFreqRatio) { closestIdealFreqRatio = mismatch; closestLineIsReversed = i > 0; closestLine = line; closestStandard = standard; } } } if(!closestLine) { throw runtime_error("Unable to find required line measurement"); } if(freq < closestStandard->minFrequency() || freq > closestStandard->maxFrequency()) { throw runtime_error("No line standard supports the required frequency ("+QString::number(freq).toStdString()+")"); } Sparam Sline = closestLine->getMeasured(freq); if(closestLineIsReversed) { swap(Sline.m11, Sline.m22); swap(Sline.m12, Sline.m21); } // got all required measurements // calculate TRL calibration // variable names and formulas according to http://emlab.uiuc.edu/ece451/notes/new_TRL.pdf // page 19 auto R_T = Tparam(Sthrough); auto R_D = Tparam(Sline); auto T = R_D*R_T.inverse(); complex a_over_c, b; // page 21-22 Util::solveQuadratic(T.m21, T.m22 - T.m11, -T.m12, b, a_over_c); // ensure correct root selection // page 23 if(abs(b) >= abs(a_over_c)) { swap(b, a_over_c); } // page 24 auto g = R_T.m22; auto d = R_T.m11 / g; auto e = R_T.m12 / g; auto f = R_T.m21 / g; // page 25 auto r22_rho22 = g * (1.0 - e / a_over_c) / (1.0 - b / a_over_c); auto gamma = (f - d / a_over_c) / (1.0 - e / a_over_c); auto beta_over_alpha = (e - b) / (d - b * f); // page 26 auto alpha_a = (d - b * f) / (1.0 - e / a_over_c); auto w1 = S11_reflection->getMeasured(freq); auto w2 = S22_reflection->getMeasured(freq); // page 28 auto a = sqrt((w1 - b) / (w2 + gamma) * (1.0 + w2 * beta_over_alpha) / (1.0 - w1 / a_over_c) * alpha_a); // page 29, check sign of a auto reflection = (w1 - b) / (a * (1.0 - w1 / a_over_c)); if((reflection.real() > 0 && reflectionIsNegative) || (reflection.real() < 0 && !reflectionIsNegative)) { // wrong sign for a a = -a; } // Revert back from error boxes with T parameters to S paramaters, // page 17 + formulas for calculating S parameters from T parameters. // Forward coefficients, normalize for S21 = 1.0 -> r22 = 1.0 auto r22 = complex(1.0); auto rho22 = r22_rho22 / r22; auto alpha = alpha_a / a; auto beta = beta_over_alpha * alpha; auto c = a / a_over_c; auto Box_A = Tparam(r22 * a, r22 * b, r22 * c, r22); auto Box_B = Tparam(rho22 * alpha, rho22 * beta, rho22 * gamma, rho22); auto S_A = Sparam(Box_A); point.D[i] = S_A.m11; point.R[i] = S_A.m12; point.S[i] = S_A.m22; auto S_B = Sparam(Box_B); point.L[i][j] = S_B.m11; point.T[i][j] = S_B.m21; // no isolation measurement available point.I[i][j] = 0.0; // Reverse coefficients, will be handled in loop iteration where i=j and j=i } } return point; } Calibration::CalType Calibration::getCaltype() const { return caltype; } Calibration::InterpolationType Calibration::getInterpolation(double f_start, double f_stop, int npoints) { lock_guard guard(access); if(!points.size()) { return InterpolationType::NoCalibration; } if(f_start < points.front().frequency || f_stop > points.back().frequency) { return InterpolationType::Extrapolate; } // Either exact or interpolation, check individual frequencies uint32_t f_step; if(npoints > 1) { f_step = (f_stop - f_start) / (npoints - 1); } else { f_step = f_stop - f_start; } uint64_t f = f_start; do { if(find_if(points.begin(), points.end(), [&f](const Point& p){ return abs(f - p.frequency) < 100; }) == points.end()) { return InterpolationType::Interpolate; } f += f_step; } while(f <= f_stop && f_step > std::numeric_limits::epsilon()); // if we get here all frequency points were matched if(points.front().frequency == f_start && points.back().frequency == f_stop) { return InterpolationType::Unchanged; } else { return InterpolationType::Exact; } } std::vector Calibration::getErrorTermTraces() { lock_guard guard(access); vector ret; if(points.size() == 0) { return ret; } for(unsigned int i=0;isetReflection(true); tDir->setCalibration(); auto tSM = new Trace("SourceMatch_Port"+QString::number(p)); tSM->setReflection(true); tSM->setCalibration(); auto tRT = new Trace("ReflectionTracking_Port"+QString::number(p)); tRT->setReflection(false); tRT->setCalibration(); for(auto p : points) { Trace::Data td; td.x = p.frequency; td.y = p.D[i]; tDir->addData(td, Trace::DataType::Frequency); td.y = p.S[i]; tSM->addData(td, Trace::DataType::Frequency); td.y = p.R[i]; tRT->addData(td, Trace::DataType::Frequency); } ret.push_back(tDir); ret.push_back(tSM); ret.push_back(tRT); for(unsigned int j=0;jsetReflection(true); tRM->setCalibration(); auto tTT = new Trace("TransmissionTracking_"+QString::number(p)+QString::number(p2)); tTT->setReflection(false); tTT->setCalibration(); auto tTI = new Trace("TransmissionIsolation_"+QString::number(p)+QString::number(p2)); tTI->setReflection(false); tTI->setCalibration(); for(auto p : points) { Trace::Data td; td.x = p.frequency; td.y = p.L[i][j]; tRM->addData(td, Trace::DataType::Frequency); td.y = p.T[i][j]; tTT->addData(td, Trace::DataType::Frequency); td.y = p.I[i][j]; tTI->addData(td, Trace::DataType::Frequency); } ret.push_back(tRM); ret.push_back(tTT); ret.push_back(tTI); } } return ret; } std::vector Calibration::getMeasurementTraces() { lock_guard guard(access); vector ret; for(auto m : measurements) { switch(m->getType()) { case CalibrationMeasurement::Base::Type::Open: case CalibrationMeasurement::Base::Type::Short: case CalibrationMeasurement::Base::Type::Load: case CalibrationMeasurement::Base::Type::SlidingLoad: case CalibrationMeasurement::Base::Type::Reflect: { auto onePort = static_cast(m); auto t = new Trace(CalibrationMeasurement::Base::TypeToString(onePort->getType())+"_Port"+QString::number(onePort->getPort())); t->setCalibration(); t->setReflection(true); for(auto d : onePort->getPoints()) { Trace::Data td; td.x = d.frequency; td.y = d.S; t->addData(td, Trace::DataType::Frequency); } ret.push_back(t); } break; case CalibrationMeasurement::Base::Type::Through: case CalibrationMeasurement::Base::Type::Line: { auto twoPort = static_cast(m); auto ts11 = new Trace(CalibrationMeasurement::Base::TypeToString(twoPort->getType())+"_Port"+QString::number(twoPort->getPort1())+QString::number(twoPort->getPort2())+"_S11"); auto ts12 = new Trace(CalibrationMeasurement::Base::TypeToString(twoPort->getType())+"_Port"+QString::number(twoPort->getPort1())+QString::number(twoPort->getPort2())+"_S12"); auto ts21 = new Trace(CalibrationMeasurement::Base::TypeToString(twoPort->getType())+"_Port"+QString::number(twoPort->getPort1())+QString::number(twoPort->getPort2())+"_S21"); auto ts22 = new Trace(CalibrationMeasurement::Base::TypeToString(twoPort->getType())+"_Port"+QString::number(twoPort->getPort1())+QString::number(twoPort->getPort2())+"_S22"); ts11->setCalibration(); ts11->setReflection(true); ts12->setCalibration(); ts12->setReflection(false); ts21->setCalibration(); ts21->setReflection(false); ts22->setCalibration(); ts22->setReflection(true); for(auto d : twoPort->getPoints()) { Trace::Data td; td.x = d.frequency; td.y = d.S.m11; ts11->addData(td, Trace::DataType::Frequency); td.y = d.S.m12; ts12->addData(td, Trace::DataType::Frequency); td.y = d.S.m21; ts21->addData(td, Trace::DataType::Frequency); td.y = d.S.m22; ts22->addData(td, Trace::DataType::Frequency); } ret.push_back(ts11); ret.push_back(ts12); ret.push_back(ts21); ret.push_back(ts22); } break; case CalibrationMeasurement::Base::Type::Isolation: { auto iso = static_cast(m); int ports = iso->getPoints()[0].S.size(); // Create the traces vector> traces; traces.resize(ports); for(int i=0;igetType())+"_S"+QString::number(i+1)+QString::number(j+1)); t->setCalibration(); t->setReflection(i==j); traces[i].push_back(t); // also add to main return vector ret.push_back(t); } } // Fill the traces for(auto p : iso->getPoints()) { Trace::Data td; td.x = p.frequency; for(unsigned int i=0;iaddData(td, Trace::DataType::Frequency); } } } } break; case CalibrationMeasurement::Base::Type::Last: break; } } return ret; } QString Calibration::getCurrentCalibrationFile() { return currentCalFile; } double Calibration::getMinFreq() { if(points.size() > 0) { return points.front().frequency; } else { return numeric_limits::quiet_NaN(); } } double Calibration::getMaxFreq() { if(points.size() > 0) { return points.back().frequency; } else { return numeric_limits::quiet_NaN(); } } int Calibration::getNumPoints() { return points.size(); } QString Calibration::descriptiveCalName() { if(points.size() == 0) { return QString(); } int precision = 3; QString lo = Unit::ToString(points.front().frequency, "", " kMG", precision); QString hi = Unit::ToString(points.back().frequency, "", " kMG", precision); // due to rounding up 123.66M and 123.99M -> we get lo="124M" and hi="124M" // so let's add some precision if (lo == hi) { // Only in case of 123.66M and 123.69M we would need 5 digits, but that kind of narrow cal. is very unlikely. precision = 4; lo = Unit::ToString(points.front().frequency, "", " kMG", precision); hi = Unit::ToString(points.back().frequency, "", " kMG", precision); } QString tmp = caltype.getShortString() + "_" + lo + "-" + hi + "_" + QString::number(this->points.size()) + "pt"; tmp = tmp.replace(" ", "_"); tmp = tmp.replace("[", ""); tmp = tmp.replace("]", ""); tmp = tmp.replace(".", "_"); tmp = tmp.replace(",", "_"); return tmp; } QString Calibration::getValidDevice() const { return validDevice; } bool Calibration::validForDevice(QString serial) const { if(validDevice.isEmpty()) { // no device indicated, always assume that the calibration is valid return true; } if(validDevice == serial) { return true; } else { return false; } } bool Calibration::hasUnsavedChanges() const { return unsavedChanges; } Calkit &Calibration::getKit() { return kit; } nlohmann::json Calibration::toJSON() { lock_guard guard(access); nlohmann::json j; j["format"] = 3; nlohmann::json jmeasurements; for(auto m : measurements) { nlohmann::json jmeas; jmeas["type"] = CalibrationMeasurement::Base::TypeToString(m->getType()).toStdString(); jmeas["data"] = m->toJSON(); jmeasurements.push_back(jmeas); } j["measurements"] = jmeasurements; j["calkit"] = kit.toJSON(); j["type"] = TypeToString(caltype.type).toStdString(); nlohmann::json jports; for(auto p : caltype.usedPorts) { jports.push_back(p); } j["ports"] = jports; j["version"] = qlibrevnaApp->applicationVersion().toStdString(); j["device"] = validDevice.toStdString(); return j; } void Calibration::fromJSON(nlohmann::json j) { reset(); lock_guard guard(access); if(j.contains("calkit")) { kit.fromJSON(j["calkit"]); } unsigned int format = 0; if(j.contains("format")) { format = j["format"]; } else { // no clear format indicated, attempt to guess from json content if(j.contains("port1StandardMale")) { format = 2; } else if(j.contains("version")){ format = 3; } } switch(format) { case 3: { if(j.contains("measurements")) { for(auto jm : j["measurements"]) { auto type = CalibrationMeasurement::Base::TypeFromString(QString::fromStdString(jm.value("type", ""))); auto m = newMeasurement(type); if(m && jm.contains("data")) { m->fromJSON(jm["data"]); measurements.push_back(m); } } } CalType ct; ct.type = TypeFromString(QString::fromStdString(j.value("type", ""))); if(j.contains("ports")) { for(auto jp : j["ports"]) { ct.usedPorts.push_back(jp); } } if(ct.type != Type::None) { compute(ct); } validDevice = QString::fromStdString(j.value("device", "")); } break; case 2: { // associated calkit should already be loaded if(j.contains("measurements")) { // grab measurements for(auto j_m : j["measurements"]) { if(!j_m.contains("name")) { throw runtime_error("Measurement without name given"); } CalibrationMeasurement::Base *m = nullptr; auto name = QString::fromStdString(j_m["name"]); if(name == "Port 1 Open") { m = newMeasurement(CalibrationMeasurement::Base::Type::Open); static_cast(m)->setPort(1); } else if(name == "Port 1 Short") { m = newMeasurement(CalibrationMeasurement::Base::Type::Short); static_cast(m)->setPort(1); } else if(name == "Port 1 Load") { m = newMeasurement(CalibrationMeasurement::Base::Type::Load); static_cast(m)->setPort(1); } else if(name == "Port 2 Open") { m = newMeasurement(CalibrationMeasurement::Base::Type::Open); static_cast(m)->setPort(2); } else if(name == "Port 2 Short") { m = newMeasurement(CalibrationMeasurement::Base::Type::Short); static_cast(m)->setPort(2); } else if(name == "Port 2 Load") { m = newMeasurement(CalibrationMeasurement::Base::Type::Load); static_cast(m)->setPort(2); } else if(name == "Through") { m = newMeasurement(CalibrationMeasurement::Base::Type::Through); static_cast(m)->setPort1(1); static_cast(m)->setPort2(2); } else if(name == "Isolation") { m = newMeasurement(CalibrationMeasurement::Base::Type::Isolation); } else if(name == "Line") { m = newMeasurement(CalibrationMeasurement::Base::Type::Line); static_cast(m)->setPort1(1); static_cast(m)->setPort2(2); } if(!m) { // unknown measurement name throw runtime_error("Measurement name unknown: "+std::string(j_m["name"])); } // attempt to select the correct standard bool p1male = j.value("port1StandardMale", true); bool p2male = j.value("port2StandardMale", true); bool throughZeroLength = j.value("throughZeroLength", true); auto onePort = dynamic_cast(m); if(onePort) { bool male = onePort->getPort() == 1 ? p1male : p2male; QString name = male ? "Default male standard" : "Default female standard"; for(auto s : m->supportedStandards()) { if(s->getName() == name) { m->setStandard(s); break; } } } auto through = dynamic_cast(m); if(through) { if(throughZeroLength) { // needs to create a zero length through standard in the calkit in addition to the already existing through standard auto zerolength = new CalStandard::Through(); zerolength->setName("Zero length"); kit.addStandard(zerolength); // use this standard m->setStandard(zerolength); } } if(!m->getStandard()) { // failed to find specific standard, use the first available m->setFirstSupportedStandard(); } // extract points if(!j_m.contains("points")) { throw runtime_error("Measurement "+name.toStdString()+" does not contain any points"); } for(auto j_p : j_m["points"]) { DeviceDriver::VNAMeasurement p; p.frequency = j_p.value("frequency", 0.0); p.Z0 = 50.0; p.measurements["S11"] = complex(j_p.value("S11_real", 0.0), j_p.value("S11_imag", 0.0)); p.measurements["S12"] = complex(j_p.value("S12_real", 0.0), j_p.value("S12_imag", 0.0)); p.measurements["S21"] = complex(j_p.value("S21_real", 0.0), j_p.value("S21_imag", 0.0)); p.measurements["S22"] = complex(j_p.value("S22_real", 0.0), j_p.value("S22_imag", 0.0)); m->addPoint(p); } measurements.push_back(m); } } // got all measurements, construct calibration according to type if(j.contains("type")) { auto type = QString::fromStdString(j["type"]); CalType ct; ct.type = Calibration::Type::None; if(type == "Port 1") { ct.type = Calibration::Type::SOLT; ct.usedPorts = {1}; } else if(type == "Port 2") { ct.type = Calibration::Type::SOLT; ct.usedPorts = {2}; } else if(type == "SOLT") { ct.type = Calibration::Type::SOLT; ct.usedPorts = {1,2}; } else if(type == "Normalize") { ct.type = Calibration::Type::ThroughNormalization; ct.usedPorts = {1,2}; } else if(type == "TRL") { ct.type = Calibration::Type::TRL; ct.usedPorts = {1,2}; } if(ct.type != Type::None) { compute(ct); } } InformationBox::ShowMessage("Old calibration format", "The selected calibration file was saved in an old format. Please check the imported result " "carefully and save the calibration to migrate to the new format"); } break; default: InformationBox::ShowError("Failed to load calibration", "Unable to load calibration, unknown json format: "+QString::number(format)); break; } } bool Calibration::toFile(QString filename) { if(filename.isEmpty()) { QString fn = descriptiveCalName(); filename = QFileDialog::getSaveFileName(nullptr, "Save calibration data", fn, "Calibration files (*.cal)", nullptr, QFileDialog::DontUseNativeDialog); if(filename.isEmpty()) { // aborted selection return false; } } if(filename.toLower().endsWith(".cal")) { filename.chop(4); } auto calibration_file = filename + ".cal"; ofstream file; file.open(calibration_file.toStdString()); file << setw(1) << toJSON(); this->currentCalFile = calibration_file; // if all ok, remember this unsavedChanges = false; return true; } bool Calibration::fromFile(QString filename) { if(filename.isEmpty()) { filename = QFileDialog::getOpenFileName(nullptr, "Load calibration data", "", "Calibration files (*.cal)", nullptr, QFileDialog::DontUseNativeDialog); if(filename.isEmpty()) { // aborted selection return false; } } // force correct file ending if(filename.toLower().endsWith(".cal")) { filename.chop(4); filename += ".cal"; } qDebug() << "Attempting to open calibration from file" << filename; // attempt to load associated calibration kit first. It needs to be available when using the old calibration format. If the // newer calibration format is used, the calibration kit is overwritten by the calibration file anyway auto calkit_file = filename; auto dotPos = calkit_file.lastIndexOf('.'); if(dotPos >= 0) { calkit_file.truncate(dotPos); } calkit_file.append(".calkit"); qDebug() << "Associated calibration kit expected in" << calkit_file; try { kit = Calkit::fromFile(calkit_file); } catch (runtime_error &e) { qDebug() << "Parsing of calibration kit failed while opening calibration file: " << e.what() << " (ignore for calibration format >= 3)"; } ifstream file; file.open(filename.toStdString()); if(!file.good()) { QString msg = "Unable to open file: "+filename; InformationBox::ShowError("Error", msg); qWarning() << msg; return false; } try { nlohmann::json j; file >> j; currentCalFile = filename; // if all ok, remember this fromJSON(j); } catch(exception &e) { currentCalFile.clear(); InformationBox::ShowError("File parsing error", e.what()); qWarning() << "Calibration file parsing failed: " << e.what(); return false; } unsavedChanges = false; return true; } std::vector Calibration::getAvailableCalibrations() { unsigned int ports = DeviceDriver::getInfo(DeviceDriver::getActiveDriver()).Limits.VNA.ports; vector ret; for(auto t : getTypes()) { CalType cal; cal.type = t; auto minPorts = minimumPorts(t); for(unsigned int pnum = minPorts;pnum <= ports;pnum++) { std::string bitmask(pnum, 1); bitmask.resize(ports, 0); // assemble selected ports and permute bitmask do { vector usedPorts; for (unsigned int i = 0; i < ports; ++i) { if (bitmask[i]) { usedPorts.push_back(i+1); } } cal.usedPorts = usedPorts; ret.push_back(cal); } while (std::prev_permutation(bitmask.begin(), bitmask.end())); } } return ret; } std::vector Calibration::getTypes() { vector types; // Start at index 1, skip Type::None for(int i=1;i<(int) Type::Last;i++) { types.push_back((Type) i); } return types; } bool Calibration::canCompute(Calibration::CalType type, double *startFreq, double *stopFreq, int *points) { using RequiredMeasurements = struct { CalibrationMeasurement::Base::Type type; unsigned int port1, port2; }; vector required; switch(type.type) { case Type::None: return true; // Always possible to reset the calibration case Type::SOLT: // SOL measurements for every port for(auto p : type.usedPorts) { required.push_back({.type = CalibrationMeasurement::Base::Type::Short, .port1 = p, .port2 = 0}); required.push_back({.type = CalibrationMeasurement::Base::Type::Open, .port1 = p, .port2 = 0}); if(findMeasurements(CalibrationMeasurement::Base::Type::SlidingLoad, p).size() >= 3) { // got enough sliding load measurements, use these required.push_back({.type = CalibrationMeasurement::Base::Type::SlidingLoad, .port1 = p, .port2 = 0}); } else { // not enough sliding load measurement, use normal load required.push_back({.type = CalibrationMeasurement::Base::Type::Load, .port1 = p, .port2 = 0}); } } // through measurements between all ports for(unsigned int i=1;i<=type.usedPorts.size();i++) { for(unsigned int j=i+1;j<=type.usedPorts.size();j++) { required.push_back({.type = CalibrationMeasurement::Base::Type::Through, .port1 = i, .port2 = j}); } } break; case Type::ThroughNormalization: // through measurements between all ports for(unsigned int i=1;i<=type.usedPorts.size();i++) { for(unsigned int j=i+1;j<=type.usedPorts.size();j++) { required.push_back({.type = CalibrationMeasurement::Base::Type::Through, .port1 = i, .port2 = j}); } } break; case Type::TRL: // Reflect measurement for every port for(auto p : type.usedPorts) { required.push_back({.type = CalibrationMeasurement::Base::Type::Reflect, .port1 = p, .port2 = 0}); } // through and line measurements between all ports for(unsigned int i=1;i<=type.usedPorts.size();i++) { for(unsigned int j=i+1;j<=type.usedPorts.size();j++) { required.push_back({.type = CalibrationMeasurement::Base::Type::Through, .port1 = i, .port2 = j}); required.push_back({.type = CalibrationMeasurement::Base::Type::Line, .port1 = i, .port2 = j}); } } break; case Type::Last: // Invalid selection return false; } if(required.size() > 0) { vector foundMeasurements; for(auto m : required) { auto meas = findMeasurement(m.type, m.port1, m.port2); if(!meas) { // missing measurement return false; } else if (!meas->readyForCalculation()){ // measurement not ready (either not calkit standard definded or no measurements) return false; } else { foundMeasurements.push_back(meas); } } return hasFrequencyOverlap(foundMeasurements, startFreq, stopFreq, points); } return false; } bool Calibration::compute(Calibration::CalType type) { lock_guard guard(access); if(type.type == Type::None) { deactivate(); return true; } double start, stop; int numPoints; if(!canCompute(type, &start, &stop, &numPoints)) { return false; } caltype = type; try { points.clear(); for(int i=0;i m, const DeviceDriver::VNAMeasurement &data) { for(auto meas : m) { meas->addPoint(data); } unsavedChanges = true; if(DeviceDriver::getActiveDriver()) { validDevice = DeviceDriver::getActiveDriver()->getSerial(); } } void Calibration::clearMeasurements(std::set m) { for(auto meas : m) { meas->clearPoints(); } unsavedChanges = true; } void Calibration::measurementsComplete() { emit measurementsUpdated(); } void Calibration::deactivate() { lock_guard guard(access); points.clear(); caltype.type = Type::None; caltype.usedPorts.clear(); unsavedChanges = true; emit deactivated(); } QString Calibration::DefaultMeasurementsToString(Calibration::DefaultMeasurements dm) { switch(dm) { case DefaultMeasurements::SOL1Port: return "1 Port SOL"; case DefaultMeasurements::SOLT2Port: return "2 Port SOLT"; case DefaultMeasurements::SOLT3Port: return "3 Port SOLT"; case DefaultMeasurements::SOLT4Port: return "4 Port SOLT"; case DefaultMeasurements::Last: return "Invalid"; } return ""; } void Calibration::createDefaultMeasurements(Calibration::DefaultMeasurements dm) { lock_guard guard(access); auto createSOL = [=](int port) { auto _short = new CalibrationMeasurement::Short(this); _short->setPort(port); measurements.push_back(_short); auto open = new CalibrationMeasurement::Open(this); open->setPort(port); measurements.push_back(open); auto load = new CalibrationMeasurement::Load(this); load->setPort(port); measurements.push_back(load); }; auto createThrough = [=](int port1, int port2) { auto through = new CalibrationMeasurement::Through(this); through->setPort1(port1); through->setPort2(port2); measurements.push_back(through); }; switch(dm) { case DefaultMeasurements::SOL1Port: createSOL(1); break; case DefaultMeasurements::SOLT2Port: createSOL(1); createSOL(2); createThrough(1, 2); break; case DefaultMeasurements::SOLT3Port: createSOL(1); createSOL(2); createSOL(3); createThrough(1, 2); createThrough(1, 3); createThrough(2, 3); break; case DefaultMeasurements::SOLT4Port: createSOL(1); createSOL(2); createSOL(3); createSOL(4); createThrough(1, 2); createThrough(1, 3); createThrough(1, 4); createThrough(2, 3); createThrough(2, 4); createThrough(3, 4); break; case DefaultMeasurements::Last: break; } } void Calibration::deleteMeasurements() { lock_guard guard(access); for(auto m : measurements) { delete m; } measurements.clear(); } bool Calibration::hasFrequencyOverlap(std::vector m, double *startFreq, double *stopFreq, int *points) { double minResolution = std::numeric_limits::max(); double minFreq = 0; double maxFreq = std::numeric_limits::max(); for(auto meas : m) { if(meas->numPoints() < 2) { return false; } auto resolution = (meas->maxFreq() - meas->minFreq()) / (meas->numPoints() - 1); if(meas->maxFreq() < maxFreq) { maxFreq = meas->maxFreq(); } if(meas->minFreq() > minFreq) { minFreq = meas->minFreq(); } if(resolution < minResolution) { minResolution = resolution; } } if(startFreq) { *startFreq = minFreq; } if(stopFreq) { *stopFreq = maxFreq; } if(points) { *points = (maxFreq - minFreq) / minResolution + 1; } if(maxFreq > minFreq) { return true; } else { return false; } } std::vector Calibration::findMeasurements(CalibrationMeasurement::Base::Type type, int port1, int port2) { vector ret; for(auto m : measurements) { if(m->getType() != type) { continue; } auto onePort = dynamic_cast(m); if(onePort) { if(onePort->getPort() != port1) { continue; } } auto twoPort = dynamic_cast(m); if(twoPort) { if(twoPort->getPort1() != port1 || twoPort->getPort2() != port2) { continue; } } // if we get here, we have a match ret.push_back(m); } return ret; } CalibrationMeasurement::Base *Calibration::findMeasurement(CalibrationMeasurement::Base::Type type, int port1, int port2) { auto meas = findMeasurements(type, port1, port2); if(meas.size() > 0) { return meas[0]; } else { return nullptr; } } QString Calibration::CalType::getReadableDescription() { QString ret = TypeToString(this->type); if(usedPorts.size() == 1) { ret += ", Port: "+QString::number(usedPorts[0]); } else if(usedPorts.size() > 0) { ret += ", Ports: ["; for(auto p : usedPorts) { ret += QString::number(p)+","; } // remove the last trailing comma ret.chop(1); ret += "]"; } return ret; } QString Calibration::CalType::getShortString() { QString ret = TypeToString(this->type); if(usedPorts.size() > 0) { ret += "_"; } for(auto p : usedPorts) { ret += QString::number(p); } return ret; } Calibration::CalType Calibration::CalType::fromShortString(QString s) { CalType ret; auto list = s.split("_"); if(list.size() != 2) { ret.type = Type::None; } else { ret.type = TypeFromString(list[0]); for(auto c : list[1]) { ret.usedPorts.push_back(QString(c).toInt()); } } return ret; } Calibration::Point Calibration::Point::interpolate(const Calibration::Point &to, double alpha) { Point ret; ret.frequency = frequency * (1.0-alpha) + to.frequency * alpha; ret.D.resize(D.size(), 0.0); for(unsigned int i=0;i