#include "touchstone.h" #include "Util/util.h" #include #include #include #include #include #include #include #include using namespace std; Touchstone::Touchstone(unsigned int ports) { this->m_ports = ports; referenceImpedance = 50.0; m_datapoints.clear(); } void Touchstone::AddDatapoint(Touchstone::Datapoint p) { if (p.S.size() != m_ports * m_ports) { throw runtime_error("Invalid number of parameters"); } bool needs_sort = false; if (m_datapoints.size() > 0 && m_datapoints.back().frequency >= p.frequency) { needs_sort = true; } m_datapoints.push_back(p); if(needs_sort) { sort(m_datapoints.begin(), m_datapoints.end(), [](Datapoint &a, Datapoint &b) { return a.frequency < b.frequency; }); } } void Touchstone::toFile(QString filename, Scale unit, Format format) { // add correct file extension if not already present QString extension = ".s"+QString::number(m_ports)+"p"; if(!filename.endsWith(extension)) { filename.append(extension); } // create file ofstream file; file.open(filename.toStdString()); file << toString(unit, format).rdbuf(); file.close(); this->filename = filename; } stringstream Touchstone::toString(Touchstone::Scale unit, Touchstone::Format format) { stringstream s; s << std::fixed << std::setprecision(12); // write option line s << "# "; switch(unit) { case Scale::Hz: s << "HZ "; break; case Scale::kHz: s << "KHZ "; break; case Scale::MHz: s << "MHZ "; break; case Scale::GHz: s << "GHZ "; break; } // only S parameters supported so far s << "S "; switch(format) { case Format::DBAngle: s << "DB "; break; case Format::RealImaginary: s << "RI "; break; case Format::MagnitudeAngle: s << "MA "; break; } // reference impedance s << "R " << referenceImpedance << "\n"; auto printParameter = [format](ostream &out, complex &c) { switch (format) { case Format::RealImaginary: out << c.real() << " " << c.imag(); break; case Format::MagnitudeAngle: out << abs(c) << " " << arg(c) / M_PI * 180.0; break; case Format::DBAngle: out << Util::SparamTodB(c) << " " << arg(c) / M_PI * 180.0; break; } }; for(auto p : m_datapoints) { switch(unit) { case Scale::Hz: s << p.frequency; break; case Scale::kHz: s << p.frequency / 1e3; break; case Scale::MHz: s << p.frequency / 1e6; break; case Scale::GHz: s << p.frequency / 1e9; break; } s << " "; // special cases for 1 and 2 port if (m_ports == 1) { printParameter(s, p.S[0]); s << "\n"; } else if (m_ports == 2){ printParameter(s, p.S[0]); // touchstone expects S11 S21 S12 S22 order, swap S12 and S21 s << " "; printParameter(s, p.S[2]); s << " "; printParameter(s, p.S[1]); s << " "; printParameter(s, p.S[3]); s << "\n"; } else { // print parameters in matrix form for(unsigned int i=0;i '9' || filename[index_extension+3] != 'p') { throw runtime_error("Invalid filename extension"); } unsigned int ports = filename[index_extension + 2] - '0'; auto ret = Touchstone(ports); Scale unit = Scale::GHz; Format format = Format::RealImaginary; bool option_line_found = false; unsigned int parameter_cnt = 0; Datapoint point; string line; while(getline(file, line)) { // remove comments auto comment = line.find_first_of('!'); if(comment != string::npos) { line.erase(comment); } // remove leading whitespace size_t first = line.find_first_not_of(" \t"); if (string::npos == first) { // string does only contain whitespace, skip line continue; } line.erase(0, first); if (line[0] == '#') { // this is the option line if (option_line_found) { throw runtime_error("Additional option line present"); } option_line_found = true; transform(line.begin(), line.end(), line.begin(), ::toupper); // check individual options istringstream iss(line); bool last_R = false; string s; // throw away the option line start character iss >> s; for(;iss>>s;) { if(last_R) { last_R = false; // read reference impedance ret.referenceImpedance = stod(s, nullptr); break; } if (!s.compare("HZ")) { unit = Scale::Hz; } else if (!s.compare("KHZ")) { unit = Scale::kHz; } else if (!s.compare("MHZ")) { unit = Scale::MHz; } else if (!s.compare("GHZ")) { unit = Scale::GHz; } else if (!s.compare("S")) { // S parameter, nothing to do } else if (!s.compare("Y")) { throw runtime_error("Y parameters not supported"); } else if (!s.compare("Z")) { throw runtime_error("Z parameters not supported"); } else if (!s.compare("G")) { throw runtime_error("G parameters not supported"); } else if (!s.compare("H")) { throw runtime_error("H parameters not supported"); } else if(!s.compare("MA")) { format = Format::MagnitudeAngle; } else if(!s.compare("DB")) { format = Format::DBAngle; } else if(!s.compare("RI")) { format = Format::RealImaginary; } else if(!s.compare("R")) { // next option is the reference impedance last_R = true; } else { throw runtime_error("Unexpected option in option line"); } } } else { // not the option line if(!option_line_found) { throw runtime_error("First dataline before option line"); } auto parseDatapoint = [format](istream &in) -> complex { double part1, part2; in >> part1; in >> part2; complex ret; switch(format) { case Format::MagnitudeAngle: ret = polar(part1, part2 / 180.0 * M_PI); break; case Format::DBAngle: ret = polar(pow(10, part1/20), part2 / 180.0 * M_PI); break; case Format::RealImaginary: ret = complex(part1, part2); break; } return ret; }; istringstream iss(line); if (parameter_cnt == 0) { iss >> point.frequency; point.S.clear(); switch(unit) { case Scale::Hz: break; case Scale::kHz: point.frequency *= 1e3; break; case Scale::MHz: point.frequency *= 1e6; break; case Scale::GHz: point.frequency *= 1e9; break; } } unsigned int parameters_per_line; if(ports == 1) { parameters_per_line = 1; } else if(ports == 3) { parameters_per_line = 3; } else { parameters_per_line = 4; } unsigned int parameters_per_point = ports * ports; for(unsigned int i=0;i= parameters_per_point) { parameter_cnt = 0; if(ports == 2) { // 2 port touchstone has S11 S21 S12 S22 order, swap S12 and S21 swap(point.S[1], point.S[2]); } ret.AddDatapoint(point); break; } } } } ret.filename = QString::fromStdString(filename); return ret; } double Touchstone::minFreq() { if (m_datapoints.size() > 0) { return m_datapoints.front().frequency; } else { return numeric_limits::quiet_NaN(); } } double Touchstone::maxFreq() { if (m_datapoints.size() > 0) { return m_datapoints.back().frequency; } else { return numeric_limits::quiet_NaN(); } } Touchstone::Datapoint Touchstone::interpolate(double frequency) { if(m_datapoints.size() == 0) { throw runtime_error("Trying to interpolate empty touchstone data"); } // Check if requested frequency is outside of points and return first/last datapoint respectively if(frequency <= m_datapoints.front().frequency) { return m_datapoints.front(); } else if(frequency >= m_datapoints.back().frequency) { return m_datapoints.back(); } // frequency within points, interpolate auto lower = lower_bound(m_datapoints.begin(), m_datapoints.end(), frequency, [](const Datapoint &lhs, double rhs) -> bool { return lhs.frequency < rhs; }); auto lowPoint = *lower; advance(lower, 1); auto highPoint = *lower; double alpha = (frequency - lowPoint.frequency) / (highPoint.frequency - lowPoint.frequency); Datapoint ret; ret.frequency = frequency; for(unsigned int i=0;i= m_ports || port2 >= m_ports || port1 == port2) { throw runtime_error("Invalid port number"); } unsigned int S11_index = port1 * m_ports + port1; unsigned int S22_index = port2 * m_ports + port2; unsigned int S12_index = port1 * m_ports + port2; unsigned int S21_index = port2 * m_ports + port1; if(m_ports == 2) { swap(S21_index, S12_index); } for(auto &p : m_datapoints) { auto S11 = p.S[S11_index]; auto S12 = p.S[S12_index]; auto S21 = p.S[S21_index]; auto S22 = p.S[S22_index]; p.S.clear(); p.S.push_back(S11); p.S.push_back(S21); p.S.push_back(S12); p.S.push_back(S22); } m_ports = 2; } void Touchstone::reduceTo1Port(unsigned int port) { if (port >= m_ports) { throw runtime_error("Invalid port number"); } if(m_ports == 1) { // already at one port, nothing to do return; } unsigned int S11_index = port * m_ports + port; for(auto &p : m_datapoints) { auto S11 = p.S[S11_index]; p.S.clear(); p.S.push_back(S11); } m_ports = 1; } QString Touchstone::getFilename() const { return filename; } nlohmann::json Touchstone::toJSON() { nlohmann::json j; j["ports"] = m_ports; j["filename"] = filename.toStdString(); if(m_datapoints.size() > 0) { nlohmann::json json_points; for(auto d : m_datapoints) { nlohmann::json point; point["frequency"] = d.frequency; nlohmann::json sparams; for(auto s : d.S) { nlohmann::json sparam; sparam["real"] = s.real(); sparam["imag"] = s.imag(); sparams.push_back(sparam); } point["Sparams"] = sparams; json_points.push_back(point); } j["datapoints"] = json_points; } return j; } void Touchstone::fromJSON(nlohmann::json j) { m_datapoints.clear(); filename = QString::fromStdString(j.value("filename", "")); m_ports = j.value("ports", 0); if(!m_ports || !j.contains("datapoints")) { return; } auto json_points = j["datapoints"]; for(auto point : json_points) { Datapoint d; if(!point.contains("frequency") || !point.contains("Sparams")) { // missing data, abort here qWarning() << "Touchstone data point does not contain frequency or S parameters"; break; } d.frequency = point["frequency"]; if(point["Sparams"].size() != m_ports * m_ports) { // invalid number of Sparams, abort here qWarning() << "Invalid number of S parameters, got" << point["Sparams"].size() << "expected" << m_ports*m_ports; break; } for(auto Sparam : point["Sparams"]) { d.S.push_back(complex(Sparam.value("real", 0.0), Sparam.value("imag", 0.0))); } m_datapoints.push_back(d); } } double Touchstone::getReferenceImpedance() const { return referenceImpedance; } void Touchstone::setReferenceImpedance(double value) { referenceImpedance = value; }