Basic working support for math operations between traces

This commit is contained in:
Jan Käberich 2022-07-11 21:37:06 +02:00
parent 97fceb4ff4
commit cf31fd7908
8 changed files with 579 additions and 64 deletions

View File

@ -672,7 +672,7 @@ std::vector<Trace *> Calibration::getErrorTermTraces()
constexpr bool reflection[12] = {true, true, false, false, true, false, true, true, false, false, true, false}; constexpr bool reflection[12] = {true, true, false, false, true, false, true, true, false, false, true, false};
for(int i=0;i<12;i++) { for(int i=0;i<12;i++) {
auto t = new Trace(traceNames[i], Qt::red); auto t = new Trace(traceNames[i], Qt::red);
t->setCalibration(true); t->setCalibration();
t->setReflection(reflection[i]); t->setReflection(reflection[i]);
traces.push_back(t); traces.push_back(t);
} }
@ -728,7 +728,7 @@ std::vector<Trace *> Calibration::getMeasurementTraces()
} }
for(auto prefix : usedPrefixes) { for(auto prefix : usedPrefixes) {
auto t = new Trace(prefix + " " + info.name); auto t = new Trace(prefix + " " + info.name);
t->setCalibration(true); t->setCalibration();
t->setReflection(prefix == "S11" || prefix == "S22"); t->setReflection(prefix == "S11" || prefix == "S22");
for(auto p : m.second.datapoints) { for(auto p : m.second.datapoints) {
Trace::Data d; Trace::Data d;

View File

@ -870,7 +870,7 @@ std::set<Marker::Type> Marker::getSupportedTypes()
supported.insert(Type::Highpass); supported.insert(Type::Highpass);
supported.insert(Type::Bandpass); supported.insert(Type::Bandpass);
} }
if(parentTrace->isLive()) { if(parentTrace->getSource() == Trace::Source::Live) {
switch(parentTrace->liveParameter()) { switch(parentTrace->liveParameter()) {
case Trace::LiveParameter::S11: case Trace::LiveParameter::S11:
case Trace::LiveParameter::S12: case Trace::LiveParameter::S12:

View File

@ -4,6 +4,8 @@
#include "Util/util.h" #include "Util/util.h"
#include "Marker/marker.h" #include "Marker/marker.h"
#include "traceaxis.h" #include "traceaxis.h"
#include "tracemodel.h"
#include "Math/parser/mpParser.h"
#include <math.h> #include <math.h>
#include <QDebug> #include <QDebug>
@ -12,19 +14,23 @@
#include <functional> #include <functional>
using namespace std; using namespace std;
using namespace mup;
Trace::Trace(QString name, QColor color, LiveParameter live) Trace::Trace(QString name, QColor color, LiveParameter live)
: _name(name), : model(nullptr),
_name(name),
_color(color), _color(color),
source(Source::Live),
hash(0),
hashSet(false),
JSONskipHash(false),
_liveType(LivedataType::Overwrite), _liveType(LivedataType::Overwrite),
_liveParam(live), _liveParam(live),
vFactor(0.66), vFactor(0.66),
reflection(true), reflection(true),
reference_impedance(50.0),
visible(true), visible(true),
paused(false), paused(false),
createdFromFile(false), reference_impedance(50.0),
calibration(false),
domain(DataType::Frequency), domain(DataType::Frequency),
lastMath(nullptr) lastMath(nullptr)
{ {
@ -32,6 +38,10 @@ Trace::Trace(QString name, QColor color, LiveParameter live)
mathOps.push_back(self); mathOps.push_back(self);
updateLastMath(mathOps.rbegin()); updateLastMath(mathOps.rbegin());
lastMathUpdate = QTime::currentTime();
mathCalcTimer.setSingleShot(true);
connect(&mathCalcTimer, &QTimer::timeout, this, &Trace::calculateMath);
self.enabled = false; self.enabled = false;
dataType = DataType::Frequency; dataType = DataType::Frequency;
connect(this, &Trace::typeChanged, [=](){ connect(this, &Trace::typeChanged, [=](){
@ -52,8 +62,8 @@ Trace::~Trace()
emit deleted(this); emit deleted(this);
} }
void Trace::clear() { void Trace::clear(bool force) {
if(paused) { if(paused && !force) {
return; return;
} }
data.clear(); data.clear();
@ -166,7 +176,8 @@ void Trace::fillFromTouchstone(Touchstone &t, unsigned int parameter)
break; break;
} }
} }
createdFromFile = true; clearMathSources();
source = Source::File;
reference_impedance = t.getReferenceImpedance(); reference_impedance = t.getReferenceImpedance();
emit typeChanged(this); emit typeChanged(this);
emit outputSamplesChanged(0, data.size()); emit outputSamplesChanged(0, data.size());
@ -241,7 +252,8 @@ QString Trace::fillFromCSV(CSV &csv, unsigned int parameter)
addData(d, domain); addData(d, domain);
} }
reflection = false; reflection = false;
createdFromFile = true; clearMathSources();
source = Source::File;
emit typeChanged(this); emit typeChanged(this);
emit outputSamplesChanged(0, data.size()); emit outputSamplesChanged(0, data.size());
return lastTraceName; return lastTraceName;
@ -269,7 +281,8 @@ void Trace::fillFromDatapoints(Trace &S11, Trace &S12, Trace &S21, Trace &S22, c
void Trace::fromLivedata(Trace::LivedataType type, LiveParameter param) void Trace::fromLivedata(Trace::LivedataType type, LiveParameter param)
{ {
createdFromFile = false; clearMathSources();
source = Source::Live;
_liveType = type; _liveType = type;
_liveParam = param; _liveParam = param;
if(param == LiveParameter::S11 || param == LiveParameter::S22) { if(param == LiveParameter::S11 || param == LiveParameter::S22) {
@ -280,6 +293,15 @@ void Trace::fromLivedata(Trace::LivedataType type, LiveParameter param)
emit typeChanged(this); emit typeChanged(this);
} }
void Trace::fromMath()
{
source = Source::Math;
clear();
updateMathTracePoints();
scheduleMathCalculation(0, data.size());
emit typeChanged(this);
}
void Trace::setColor(QColor color) { void Trace::setColor(QColor color) {
if(_color != color) { if(_color != color) {
_color = color; _color = color;
@ -309,6 +331,281 @@ void Trace::markerVisibilityChanged(Marker *m)
emit visibilityChanged(this); emit visibilityChanged(this);
} }
const QString &Trace::getMathFormula() const
{
return mathFormula;
}
void Trace::setMathFormula(const QString &newMathFormula)
{
mathFormula = newMathFormula;
}
bool Trace::mathFormularValid() const
{
if(mathFormula.isEmpty()) {
return false;
}
try {
ParserX parser(pckCOMMON | pckUNIT | pckCOMPLEX);
parser.SetExpr(mathFormula.toStdString());
auto vars = parser.GetExprVar();
for(auto var : vars) {
auto varName = QString::fromStdString(var.first);
// try to find variable name
bool found = false;
for(auto ms : mathSourceTraces) {
if(ms.second == varName) {
found = true;
break;
}
}
if(!found) {
return false;
}
}
} catch (const ParserError &e) {
// parser error occurred
return false;
}
// all variables used in the expression are set as math sources
return true;
}
bool Trace::resolveMathSourceHashes()
{
bool success = true;
for(auto unresolved : mathSourceUnresolvedHashes) {
if(!addMathSource(unresolved.first, unresolved.second)) {
success = false;
}
}
if(success) {
mathSourceUnresolvedHashes.clear();
}
return success;
}
bool Trace::updateMathTracePoints()
{
if(!mathSourceTraces.size()) {
return false;
}
double startX = std::numeric_limits<double>::lowest();
double stopX = std::numeric_limits<double>::max();
double stepSize = std::numeric_limits<double>::max();
for(auto t : mathSourceTraces) {
if(t.first->minX() > startX) {
startX = t.first->minX();
}
if(t.first->maxX() < stopX) {
stopX = t.first->maxX();
}
double traceStepSize = std::numeric_limits<double>::max();
if(t.first->numSamples() > 1) {
traceStepSize = (t.first->maxX() - t.first->minX()) / (t.first->numSamples() - 1);
}
if(traceStepSize < stepSize) {
stepSize = traceStepSize;
}
}
unsigned int samples = round((stopX - startX) / stepSize + 1);
bool fullUpdate = false;
if(samples != data.size()) {
data.resize(samples);
fullUpdate = true;
}
if(samples > 0 && (startX != data.front().x || stopX != data.back().x)) {
fullUpdate = true;
}
if(fullUpdate) {
// steps changed, complete update required
mathUpdateBegin = 0;
mathUpdateEnd = samples;
for(unsigned int i=0;i<samples;i++) {
data[i].x = startX + i * stepSize;
data[i].y = numeric_limits<complex<double>>::quiet_NaN();
}
return true;
} else {
return false;
}
}
void Trace::mathSourceTraceDeleted(Trace *t)
{
if (mathSourceTraces.count(t)) {
removeMathSource(t);
}
}
void Trace::scheduleMathCalculation(unsigned int begin, unsigned int end)
{
if(source != Source::Math) {
return;
}
if(begin < mathUpdateBegin) {
mathUpdateBegin = begin;
}
if(end > mathUpdateEnd) {
mathUpdateEnd = end;
}
auto now = QTime::currentTime();
if (lastMathUpdate.msecsTo(now) >= MinMathUpdateInterval) {
calculateMath();
} else {
mathCalcTimer.start(MinMathUpdateInterval);
}
}
void Trace::calculateMath()
{
lastMathUpdate = QTime::currentTime();
if(mathUpdateBegin >= data.size() || mathUpdateEnd >= data.size() + 1) {
return;
}
if(mathFormula.isEmpty()) {
error("Expression is empty");
return;
}
if(!isPaused()) {
try {
ParserX parser(pckCOMMON | pckUNIT | pckCOMPLEX);
parser.SetExpr(mathFormula.toStdString());
map<Trace*,Value> values;
Value x;
parser.DefineVar("x", Variable(&x));
for(const auto &ts : mathSourceTraces) {
values[ts.first] = Value();
parser.DefineVar(ts.second.toStdString(), Variable(&values[ts.first]));
}
for(unsigned int i=mathUpdateBegin;i<mathUpdateEnd;i++) {
x = data[i].x;
for(auto &val : values) {
val.second = val.first->interpolatedSample(data[i].x).y;
}
Value res = parser.Eval();
data[i].y = res.GetComplex();
}
} catch (const ParserError &e) {
error(QString::fromStdString(e.GetMsg()));
// parser error occurred
for(unsigned int i=mathUpdateBegin;i<mathUpdateEnd;i++) {
data[i].y = numeric_limits<complex<double>>::quiet_NaN();
}
}
success();
emit outputSamplesChanged(mathUpdateBegin, mathUpdateEnd + 1);
}
mathUpdateBegin = data.size();
mathUpdateEnd = 0;
}
void Trace::clearMathSources()
{
while(mathSourceTraces.size() > 0) {
removeMathSource(mathSourceTraces.begin()->first);
}
}
bool Trace::addMathSource(unsigned int hash, QString variableName)
{
if(!model) {
return false;
}
for(auto t : model->getTraces()) {
if(t->toHash() == hash) {
return addMathSource(t, variableName);
}
}
return false;
}
bool Trace::mathDependsOn(Trace *t, bool onlyDirectDependency)
{
if(mathSourceTraces.count(t)) {
return true;
}
if(onlyDirectDependency) {
return false;
} else {
// also check math source traces recursively
for(auto m : mathSourceTraces) {
if(m.first->mathDependsOn(t)) {
return true;
}
}
return false;
}
}
bool Trace::canAddAsMathSource(Trace *t)
{
if(t == this) {
// can't add itself
return false;
}
// check if we would create a loop of math traces depending on each other
if(t->mathDependsOn(this)) {
return false;
}
if(mathSourceTraces.size() == 0) {
// no traces used as source yet, can add anything
return true;
} else {
// can only add traces of the same domain
if(mathSourceTraces.begin()->first->outputType() == t->outputType()) {
return true;
} else {
return false;
}
}
}
bool Trace::addMathSource(Trace *t, QString variableName)
{
// qDebug() << "Adding trace" << t << "as a math source to" << this << "as variable" << variableName;
if(!canAddAsMathSource(t)) {
return false;
}
mathSourceTraces[t] = variableName;
connect(t, &Trace::deleted, this, &Trace::mathSourceTraceDeleted, Qt::UniqueConnection);
connect(t, &Trace::dataChanged, this, [=](unsigned int begin, unsigned int end){
updateMathTracePoints();
auto startX = t->sample(begin).x;
auto stopX = t->sample(end).x;
scheduleMathCalculation(index(startX), index(stopX));
});
return true;
}
void Trace::removeMathSource(Trace *t)
{
// qDebug() << "Removing trace" << t << "as a math source from" << this;
mathSourceTraces.erase(t);
disconnect(t, &Trace::deleted, this, &Trace::mathSourceTraceDeleted);
disconnect(t, &Trace::dataChanged, this, nullptr);
}
QString Trace::getSourceVariableName(Trace *t)
{
if(mathSourceTraces.count(t)) {
return mathSourceTraces[t];
} else {
return QString();
}
}
TraceModel *Trace::getModel() const
{
return model;
}
void Trace::setModel(TraceModel *model)
{
this->model = model;
}
double Trace::getReferenceImpedance() const double Trace::getReferenceImpedance() const
{ {
return reference_impedance; return reference_impedance;
@ -347,22 +644,41 @@ double Trace::distanceToTime(double distance)
nlohmann::json Trace::toJSON() nlohmann::json Trace::toJSON()
{ {
nlohmann::json j; nlohmann::json j;
if(isCalibration()) { if(!JSONskipHash) {
j["hash"] = toHash(true);
}
if(source == Source::Calibration) {
// calibration traces can't be saved // calibration traces can't be saved
return j; return j;
} }
j["name"] = _name.toStdString(); j["name"] = _name.toStdString();
j["color"] = _color.name().toStdString(); j["color"] = _color.name().toStdString();
j["visible"] = visible; j["visible"] = visible;
if(isLive()) { switch(source) {
case Source::Live:
j["type"] = "Live"; j["type"] = "Live";
j["parameter"] = _liveParam; j["parameter"] = _liveParam;
j["livetype"] = _liveType; j["livetype"] = _liveType;
j["paused"] = paused; j["paused"] = paused;
} else if(isFromFile()) { break;
case Source::File:
j["type"] = "File"; j["type"] = "File";
j["filename"] = filename.toStdString(); j["filename"] = filename.toStdString();
j["parameter"] = fileParameter; j["parameter"] = fileParameter;
break;
case Source::Math: {
j["type"] = "Math";
j["expression"] = mathFormula.toStdString();
nlohmann::json jsources;
for(auto ms : mathSourceTraces) {
nlohmann::json jsource;
jsource["trace"] = ms.first->toHash();
jsource["variable"] = ms.second.toStdString();
jsources.push_back(jsource);
}
j["sources"] = jsources;
}
break;
} }
j["velocityFactor"] = vFactor; j["velocityFactor"] = vFactor;
j["reflection"] = reflection; j["reflection"] = reflection;
@ -388,8 +704,13 @@ nlohmann::json Trace::toJSON()
void Trace::fromJSON(nlohmann::json j) void Trace::fromJSON(nlohmann::json j)
{ {
createdFromFile = false; source = Source::Live;
calibration = false; if(j.contains("hash")) {
hash = j["hash"];
hashSet = true;
} else {
hashSet = false;
}
_name = QString::fromStdString(j.value("name", "Missing name")); _name = QString::fromStdString(j.value("name", "Missing name"));
_color = QColor(QString::fromStdString(j.value("color", "yellow"))); _color = QColor(QString::fromStdString(j.value("color", "yellow")));
visible = j.value("visible", true); visible = j.value("visible", true);
@ -414,6 +735,19 @@ void Trace::fromJSON(nlohmann::json j)
std::string what = e.what(); std::string what = e.what();
throw runtime_error("Failed to create from file:" + what); throw runtime_error("Failed to create from file:" + what);
} }
} else if(type == "Math") {
mathFormula = QString::fromStdString(j.value("expression", ""));
if(j.contains("sources")) {
for(auto js : j["sources"]) {
auto hash = js.value("trace", 0);
QString varName = QString::fromStdString(js.value("variable", ""));
if(!addMathSource(hash, varName)) {
qWarning() << "Unable to find requested math source trace ( hash:"<<hash<<"), probably not loaded yet";
mathSourceUnresolvedHashes[hash] = varName;
}
}
}
fromMath();
} }
vFactor = j.value("velocityFactor", 0.66); vFactor = j.value("velocityFactor", 0.66);
reflection = j.value("reflection", false); reflection = j.value("reflection", false);
@ -453,12 +787,18 @@ void Trace::fromJSON(nlohmann::json j)
enableMath(j.value("math_enabled", true)); enableMath(j.value("math_enabled", true));
} }
unsigned int Trace::toHash() unsigned int Trace::toHash(bool forceUpdate)
{ {
if(!hashSet || forceUpdate) {
// taking the easy way: create the json string and hash it (already contains all necessary information) // taking the easy way: create the json string and hash it (already contains all necessary information)
// This is slower than it could be, but this function is only used when loading setups, so this isn't a big problem // This is slower than it could be, but this function is only used when loading setups, so this isn't a big problem
JSONskipHash = true;
std::string json_string = toJSON().dump(); std::string json_string = toJSON().dump();
return hash<std::string>{}(json_string); JSONskipHash = false;
hash = std::hash<std::string>{}(json_string);
hashSet = true;
}
return hash;
} }
std::vector<Trace *> Trace::createFromTouchstone(Touchstone &t) std::vector<Trace *> Trace::createFromTouchstone(Touchstone &t)
@ -687,9 +1027,9 @@ QString Trace::description()
return name() + ": measured data"; return name() + ": measured data";
} }
void Trace::setCalibration(bool value) void Trace::setCalibration()
{ {
calibration = value; source = Source::Calibration;
} }
std::set<Marker *> Trace::getMarkers() const std::set<Marker *> Trace::getMarkers() const
@ -731,21 +1071,6 @@ bool Trace::isPaused()
return paused; return paused;
} }
bool Trace::isFromFile()
{
return createdFromFile;
}
bool Trace::isCalibration()
{
return calibration;
}
bool Trace::isLive()
{
return !isCalibration() && !isFromFile();
}
bool Trace::isReflection() bool Trace::isReflection()
{ {
return reflection; return reflection;
@ -997,7 +1322,7 @@ unsigned int Trace::getFileParameter() const
double Trace::getNoise(double frequency) double Trace::getNoise(double frequency)
{ {
if(!isLive() || !settings.valid || (_liveParam != LiveParameter::Port1 && _liveParam != LiveParameter::Port2) || lastMath->getDataType() != DataType::Frequency) { if(source != Trace::Source::Live || !settings.valid || (_liveParam != LiveParameter::Port1 && _liveParam != LiveParameter::Port2) || lastMath->getDataType() != DataType::Frequency) {
// data not suitable for noise calculation // data not suitable for noise calculation
return std::numeric_limits<double>::quiet_NaN(); return std::numeric_limits<double>::quiet_NaN();
} }

View File

@ -6,7 +6,6 @@
#include "Device/device.h" #include "Device/device.h"
#include "Math/tracemath.h" #include "Math/tracemath.h"
#include "Tools/parameters.h" #include "Tools/parameters.h"
#include "tracemodel.h"
#include "VNA/vnadata.h" #include "VNA/vnadata.h"
#include <QObject> #include <QObject>
@ -14,8 +13,10 @@
#include <map> #include <map>
#include <QColor> #include <QColor>
#include <set> #include <set>
#include <QTime>
class Marker; class Marker;
class TraceModel;
class Trace : public TraceMath class Trace : public TraceMath
{ {
@ -24,6 +25,14 @@ public:
using Data = TraceMath::Data; using Data = TraceMath::Data;
enum class Source {
Live,
File,
Math,
Calibration,
Last,
};
enum class LiveParameter { enum class LiveParameter {
S11, S11,
S12, S12,
@ -44,7 +53,7 @@ public:
Invalid, Invalid,
}; };
void clear(); void clear(bool force = false);
void addData(const Data& d, DataType domain, double reference_impedance = 50.0, int index = -1); void addData(const Data& d, DataType domain, double reference_impedance = 50.0, int index = -1);
void addData(const Data& d, const Protocol::SpectrumAnalyzerSettings& s, int index = -1); void addData(const Data& d, const Protocol::SpectrumAnalyzerSettings& s, int index = -1);
void setName(QString name); void setName(QString name);
@ -53,15 +62,14 @@ public:
QString fillFromCSV(CSV &csv, unsigned int parameter); // returns the suggested trace name (not yet set in member data) QString fillFromCSV(CSV &csv, unsigned int parameter); // returns the suggested trace name (not yet set in member data)
static void fillFromDatapoints(Trace &S11, Trace &S12, Trace &S21, Trace &S22, const std::vector<VNAData> &data); static void fillFromDatapoints(Trace &S11, Trace &S12, Trace &S21, Trace &S22, const std::vector<VNAData> &data);
void fromLivedata(LivedataType type, LiveParameter param); void fromLivedata(LivedataType type, LiveParameter param);
void fromMath();
QString name() { return _name; } QString name() { return _name; }
QColor color() { return _color; } QColor color() { return _color; }
bool isVisible(); bool isVisible();
void pause(); void pause();
void resume(); void resume();
bool isPaused(); bool isPaused();
bool isFromFile(); Source getSource() {return source;}
bool isCalibration();
bool isLive();
bool isReflection(); bool isReflection();
LiveParameter liveParameter() { return _liveParam; } LiveParameter liveParameter() { return _liveParam; }
LivedataType liveType() { return _liveType; } LivedataType liveType() { return _liveType; }
@ -92,7 +100,7 @@ public:
double getNoise(double frequency); double getNoise(double frequency);
int index(double x); int index(double x);
std::set<Marker *> getMarkers() const; std::set<Marker *> getMarkers() const;
void setCalibration(bool value); void setCalibration();
void setReflection(bool value); void setReflection(bool value);
DataType outputType(DataType inputType) override; DataType outputType(DataType inputType) override;
@ -129,7 +137,7 @@ public:
// When saving the current graph configuration, the pointer is not useful. Instead a trace // When saving the current graph configuration, the pointer is not useful. Instead a trace
// hash is saved to identify the correct trace. The hash should be influenced by every setting // hash is saved to identify the correct trace. The hash should be influenced by every setting
// the trace can have (and its math function). It should not depend on the acquired trace samples // the trace can have (and its math function). It should not depend on the acquired trace samples
unsigned int toHash(); unsigned int toHash(bool forceUpdate = false);
static std::vector<Trace*> createFromTouchstone(Touchstone &t); static std::vector<Trace*> createFromTouchstone(Touchstone &t);
static std::vector<Trace*> createFromCSV(CSV &csv); static std::vector<Trace*> createFromCSV(CSV &csv);
@ -148,12 +156,28 @@ public:
double getReferenceImpedance() const; double getReferenceImpedance() const;
void setModel(TraceModel *newModel);
TraceModel *getModel() const;
const QString &getMathFormula() const;
void setMathFormula(const QString &newMathFormula);
bool mathFormularValid() const;
bool resolveMathSourceHashes();
public slots: public slots:
void setVisible(bool visible); void setVisible(bool visible);
void setColor(QColor color); void setColor(QColor color);
void addMarker(Marker *m); void addMarker(Marker *m);
void removeMarker(Marker *m); void removeMarker(Marker *m);
// functions for handling source == Source::Math
bool mathDependsOn(Trace *t, bool onlyDirectDependency = false);
bool canAddAsMathSource(Trace *t);
bool addMathSource(Trace *t, QString variableName);
void removeMathSource(Trace *t);
QString getSourceVariableName(Trace *t);
signals: signals:
void cleared(Trace *t); void cleared(Trace *t);
void typeChanged(Trace *t); void typeChanged(Trace *t);
@ -170,21 +194,50 @@ signals:
private slots: private slots:
void markerVisibilityChanged(Marker *m); void markerVisibilityChanged(Marker *m);
// functions for handling source == Source::Math
bool updateMathTracePoints();
void mathSourceTraceDeleted(Trace *t);
void scheduleMathCalculation(unsigned int begin, unsigned int end);
void calculateMath();
void clearMathSources();
bool addMathSource(unsigned int hash, QString variableName);
private: private:
TraceModel *model; // model which this trace will be part of
QString _name; QString _name;
QColor _color; QColor _color;
Source source;
unsigned int hash;
bool hashSet;
bool JSONskipHash;
// Members for when source == Source::Live
LivedataType _liveType; LivedataType _liveType;
LiveParameter _liveParam; LiveParameter _liveParam;
// Members for when source == Source::File
QString filename;
unsigned int fileParameter;
// Members for when source == Source::Math
std::map<Trace*,QString> mathSourceTraces;
std::map<unsigned int,QString> mathSourceUnresolvedHashes;
QString mathFormula;
static constexpr int MinMathUpdateInterval = 100;
QTime lastMathUpdate;
QTimer mathCalcTimer;
unsigned int mathUpdateBegin;
unsigned int mathUpdateEnd;
double vFactor; double vFactor;
bool reflection; bool reflection;
bool visible; bool visible;
bool paused; bool paused;
bool createdFromFile;
bool calibration;
double reference_impedance; double reference_impedance;
DataType domain; DataType domain;
QString filename;
unsigned int fileParameter;
std::set<Marker*> markers; std::set<Marker*> markers;
struct { struct {
union { union {

View File

@ -26,6 +26,11 @@ TraceEditDialog::TraceEditDialog(Trace &t, QWidget *parent) :
ui->impedance->setPrecision(3); ui->impedance->setPrecision(3);
ui->impedance->setValue(t.getReferenceImpedance()); ui->impedance->setValue(t.getReferenceImpedance());
if(!t.getModel()) {
// without information about the other traces in the model, math is not available as a source
ui->bMath->setEnabled(false);
}
connect(ui->bLive, &QPushButton::clicked, [=](bool live) { connect(ui->bLive, &QPushButton::clicked, [=](bool live) {
if(live) { if(live) {
ui->stack->setCurrentIndex(0); ui->stack->setCurrentIndex(0);
@ -45,6 +50,12 @@ TraceEditDialog::TraceEditDialog(Trace &t, QWidget *parent) :
} }
} }
}); });
connect(ui->bMath, &QPushButton::clicked, [&](bool math){
if(math) {
ui->stack->setCurrentIndex(3);
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(t.mathFormularValid());
}
});
connect(ui->color, &ColorPickerButton::colorChanged, [=](const QColor& color){ connect(ui->color, &ColorPickerButton::colorChanged, [=](const QColor& color){
trace.setColor(color); trace.setColor(color);
@ -52,8 +63,9 @@ TraceEditDialog::TraceEditDialog(Trace &t, QWidget *parent) :
ui->GSource->setId(ui->bLive, 0); ui->GSource->setId(ui->bLive, 0);
ui->GSource->setId(ui->bFile, 1); ui->GSource->setId(ui->bFile, 1);
ui->GSource->setId(ui->bFile, 2);
if(t.isCalibration()) { if(t.getSource() == Trace::Source::Calibration) {
// prevent editing imported calibration traces (and csv files for now) // prevent editing imported calibration traces (and csv files for now)
ui->bLive->setEnabled(false); ui->bLive->setEnabled(false);
ui->bFile->setEnabled(false); ui->bFile->setEnabled(false);
@ -136,8 +148,83 @@ TraceEditDialog::TraceEditDialog(Trace &t, QWidget *parent) :
connect(ui->touchstoneImport, &TouchstoneImport::filenameChanged, updateTouchstoneFileStatus); connect(ui->touchstoneImport, &TouchstoneImport::filenameChanged, updateTouchstoneFileStatus);
connect(ui->csvImport, &CSVImport::filenameChanged, updateCSVFileStatus); connect(ui->csvImport, &CSVImport::filenameChanged, updateCSVFileStatus);
if(t.isFromFile()) { // Math source configuration
ui->bFile->click(); if(t.getModel()) {
ui->lMathFormula->setText(t.getMathFormula());
connect(ui->lMathFormula, &QLineEdit::editingFinished, [&](){
t.setMathFormula(ui->lMathFormula->text());
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(t.mathFormularValid());
});
ui->mathTraceTable->setColumnCount(2);
ui->mathTraceTable->setHorizontalHeaderItem(0, new QTableWidgetItem("Trace Name"));
ui->mathTraceTable->setHorizontalHeaderItem(1, new QTableWidgetItem("Variable Name"));
auto traces = t.getModel()->getTraces();
ui->mathTraceTable->setRowCount(traces.size());
for(unsigned int i=0;i<traces.size();i++) {
auto ts = traces[i];
auto traceItem = new QTableWidgetItem(ts->name());
auto flags = traceItem->flags() | Qt::ItemIsUserCheckable;
flags &= ~(Qt::ItemIsEditable | Qt::ItemIsEnabled);
if(t.canAddAsMathSource(ts)) {
flags |= Qt::ItemIsEnabled;
}
traceItem->setFlags(flags);
auto variableItem = new QTableWidgetItem(t.getSourceVariableName(ts));
variableItem->setFlags(variableItem->flags() & ~Qt::ItemIsEditable);
if(t.mathDependsOn(ts, true)) {
traceItem->setCheckState(Qt::Checked);
variableItem->setFlags(variableItem->flags() | Qt::ItemIsEnabled | Qt::ItemIsEditable);
} else {
traceItem->setCheckState(Qt::Unchecked);
}
ui->mathTraceTable->setItem(i, 0, traceItem);
ui->mathTraceTable->setItem(i, 1, variableItem);
}
connect(ui->mathTraceTable, &QTableWidget::itemChanged, [&](QTableWidgetItem *item){
auto row = ui->mathTraceTable->row(item);
auto column = ui->mathTraceTable->column(item);
qDebug() << "Item changed at row"<<row<<"column"<<column;
ui->mathTraceTable->blockSignals(true);
auto trace = t.getModel()->trace(row);
if(column == 0) {
auto variableItem = ui->mathTraceTable->item(row, 1);
// checked state changed
if(item->checkState() == Qt::Checked) {
// add this trace to the math sources, enable editing of variable name
t.addMathSource(trace, trace->name());
variableItem->setText(trace->name());
variableItem->setFlags(variableItem->flags() | Qt::ItemIsEnabled | Qt::ItemIsEditable);
} else {
// trace disabled, remove from math sources
t.removeMathSource(trace);
variableItem->setText("");
variableItem->setFlags(variableItem->flags() & ~(Qt::ItemIsEnabled | Qt::ItemIsEditable));
}
// available trace selections may have changed, disable/enable other rows
for(unsigned int i=0;i<t.getModel()->getTraces().size();i++) {
auto traceItem = ui->mathTraceTable->item(i, 0);
auto flags = traceItem->flags();
if(t.canAddAsMathSource(t.getModel()->trace(i))) {
traceItem->setFlags(flags | Qt::ItemIsEnabled);
} else {
traceItem->setFlags(flags & ~Qt::ItemIsEnabled);
}
}
} else {
// changed the variable name text
t.addMathSource(trace, item->text());
}
ui->mathTraceTable->blockSignals(false);
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(t.mathFormularValid());
});
}
switch(t.getSource()) {
case Trace::Source::Live: ui->bLive->click(); break;
case Trace::Source::File: ui->bFile->click(); break;
case Trace::Source::Math: ui->bMath->click(); break;
default: break;
} }
// setup math part of the GUI // setup math part of the GUI
@ -254,7 +341,7 @@ void TraceEditDialog::on_buttonBox_accepted()
{ {
trace.setName(ui->name->text()); trace.setName(ui->name->text());
trace.setVelocityFactor(ui->vFactor->value()); trace.setVelocityFactor(ui->vFactor->value());
if(!trace.isCalibration()) { if(trace.getSource() != Trace::Source::Calibration) {
// only apply changes if it is not a calibration trace // only apply changes if it is not a calibration trace
if (ui->bFile->isChecked()) { if (ui->bFile->isChecked()) {
if(ui->stack->currentIndex() == 1) { if(ui->stack->currentIndex() == 1) {
@ -265,7 +352,7 @@ void TraceEditDialog::on_buttonBox_accepted()
// CSV page active // CSV page active
ui->csvImport->fillTrace(trace); ui->csvImport->fillTrace(trace);
} }
} else { } else if(ui->bLive->isChecked()) {
Trace::LivedataType type = Trace::LivedataType::Overwrite; Trace::LivedataType type = Trace::LivedataType::Overwrite;
Trace::LiveParameter param = Trace::LiveParameter::S11; Trace::LiveParameter param = Trace::LiveParameter::S11;
switch(ui->CLiveType->currentIndex()) { switch(ui->CLiveType->currentIndex()) {
@ -287,6 +374,9 @@ void TraceEditDialog::on_buttonBox_accepted()
} }
} }
trace.fromLivedata(type, param); trace.fromLivedata(type, param);
} else {
// math operation trace
trace.fromMath();
} }
} }
delete this; delete this;

View File

@ -9,8 +9,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>931</width> <width>979</width>
<height>392</height> <height>487</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -110,6 +110,16 @@
</attribute> </attribute>
</widget> </widget>
</item> </item>
<item>
<widget class="QRadioButton" name="bMath">
<property name="text">
<string>From Math</string>
</property>
<attribute name="buttonGroup">
<string notr="true">GSource</string>
</attribute>
</widget>
</item>
</layout> </layout>
</item> </item>
<item> <item>
@ -133,7 +143,7 @@
<item> <item>
<widget class="QStackedWidget" name="stack"> <widget class="QStackedWidget" name="stack">
<property name="currentIndex"> <property name="currentIndex">
<number>0</number> <number>3</number>
</property> </property>
<widget class="QWidget" name="LivePage"> <widget class="QWidget" name="LivePage">
<layout class="QFormLayout" name="formLayout_2"> <layout class="QFormLayout" name="formLayout_2">
@ -203,6 +213,34 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="MathPage">
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<widget class="QTableWidget" name="mathTraceTable">
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLabel" name="label_8">
<property name="text">
<string>Formula:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lMathFormula"/>
</item>
</layout>
</item>
</layout>
</widget>
</widget> </widget>
</item> </item>
</layout> </layout>

View File

@ -32,6 +32,7 @@ void TraceModel::addTrace(Trace *t)
}); });
traces.push_back(t); traces.push_back(t);
endInsertRows(); endInsertRows();
t->setModel(this);
emit traceAdded(t); emit traceAdded(t);
} }
@ -117,7 +118,7 @@ QVariant TraceModel::data(const QModelIndex &index, int role) const
} }
break; break;
case ColIndexPlayPause: case ColIndexPlayPause:
if (role == Qt::DecorationRole && trace->isLive()) { if (role == Qt::DecorationRole && trace->getSource() == Trace::Source::Live) { // TODO trace needs function to check if it may change due to live data
if (trace->isPaused()) { if (trace->isPaused()) {
return QIcon(":/icons/pause.svg"); return QIcon(":/icons/pause.svg");
} else { } else {
@ -157,7 +158,7 @@ std::vector<Trace *> TraceModel::getTraces() const
bool TraceModel::PortExcitationRequired(int port) bool TraceModel::PortExcitationRequired(int port)
{ {
for(auto t : traces) { for(auto t : traces) {
if(t->isLive() && !t->isPaused()) { if(t->getSource() == Trace::Source::Live && !t->isPaused()) {
// this trace needs measurements from VNA, check if port has to be excited for its measurement // this trace needs measurements from VNA, check if port has to be excited for its measurement
auto param = t->liveParameter(); auto param = t->liveParameter();
if(port == 1 && (param == Trace::LiveParameter::S11 || param == Trace::LiveParameter::S21)) { if(port == 1 && (param == Trace::LiveParameter::S11 || param == Trace::LiveParameter::S21)) {
@ -188,6 +189,7 @@ void TraceModel::fromJSON(nlohmann::json j)
} }
for(auto jt : j) { for(auto jt : j) {
auto trace = new Trace(); auto trace = new Trace();
trace->setModel(this);
try { try {
trace->fromJSON(jt); trace->fromJSON(jt);
addTrace(trace); addTrace(trace);
@ -195,12 +197,17 @@ void TraceModel::fromJSON(nlohmann::json j)
qWarning() << "Failed to create trace:" << e.what(); qWarning() << "Failed to create trace:" << e.what();
} }
} }
for(auto t : traces) {
if(!t->resolveMathSourceHashes()) {
qWarning() << "Failed to resolve all math source hashes for"<<t;
}
}
} }
void TraceModel::clearLiveData() void TraceModel::clearLiveData()
{ {
for(auto t : traces) { for(auto t : traces) {
if (t->isLive()) { if (t->getSource() == Trace::Source::Live) {
// this trace is fed from live data // this trace is fed from live data
t->clear(); t->clear();
} }
@ -211,7 +218,7 @@ void TraceModel::addVNAData(const VNAData& d, TraceMath::DataType datatype)
{ {
source = DataSource::VNA; source = DataSource::VNA;
for(auto t : traces) { for(auto t : traces) {
if (t->isLive() && !t->isPaused()) { if (t->getSource() == Trace::Source::Live && !t->isPaused()) {
int index = -1; int index = -1;
Trace::Data td; Trace::Data td;
switch(datatype) { switch(datatype) {
@ -247,7 +254,7 @@ void TraceModel::addSAData(const Protocol::SpectrumAnalyzerResult& d, const Prot
{ {
source = DataSource::SA; source = DataSource::SA;
for(auto t : traces) { for(auto t : traces) {
if (t->isLive() && !t->isPaused()) { if (t->getSource() == Trace::Source::Live && !t->isPaused()) {
int index = -1; int index = -1;
Trace::Data td; Trace::Data td;
if(settings.f_start == settings.f_stop) { if(settings.f_start == settings.f_stop) {

View File

@ -426,6 +426,8 @@ void TraceWidget::contextMenuEvent(QContextMenuEvent *event)
auto duplicate = new Trace(); auto duplicate = new Trace();
duplicate->fromJSON(json); duplicate->fromJSON(json);
duplicate->setName(duplicate->name() + " - Duplicate"); duplicate->setName(duplicate->name() + " - Duplicate");
// force update of hash
duplicate->toHash(true);
model.addTrace(duplicate); model.addTrace(duplicate);
}); });
ctxmenu->addAction(action_duplicate); ctxmenu->addAction(action_duplicate);