v2.0.0
Loading...
Searching...
No Matches
mna_param_tree.cpp
Go to the documentation of this file.
1//=============================================================================================================
34
35//=============================================================================================================
36// INCLUDES
37//=============================================================================================================
38
39#include "mna_param_tree.h"
40
41#include <QJsonArray>
42#include <QRegularExpression>
43
44#include <cmath>
45
46//=============================================================================================================
47// USED NAMESPACES
48//=============================================================================================================
49
50using namespace MNALIB;
51
52//=============================================================================================================
53// DEFINE MEMBER METHODS
54//=============================================================================================================
55
59
60//=============================================================================================================
61
62void MnaParamTree::setParam(const QString& path, const QVariant& value)
63{
64 m_params.insert(path, value);
65}
66
67//=============================================================================================================
68
69QVariant MnaParamTree::param(const QString& path) const
70{
71 return m_params.value(path);
72}
73
74//=============================================================================================================
75
76bool MnaParamTree::hasParam(const QString& path) const
77{
78 return m_params.contains(path);
79}
80
81//=============================================================================================================
82
83QStringList MnaParamTree::allPaths() const
84{
85 return m_params.keys();
86}
87
88//=============================================================================================================
89
91{
92 m_bindings.insert(binding.targetPath, binding);
93}
94
95//=============================================================================================================
96
97void MnaParamTree::removeBinding(const QString& targetPath)
98{
99 m_bindings.remove(targetPath);
100}
101
102//=============================================================================================================
103
104QList<MnaParamBinding> MnaParamTree::bindings() const
105{
106 return m_bindings.values();
107}
108
109//=============================================================================================================
110
111bool MnaParamTree::hasBinding(const QString& targetPath) const
112{
113 return m_bindings.contains(targetPath);
114}
115
116//=============================================================================================================
117
118QStringList MnaParamTree::evaluate(const QMap<QString, QVariant>& results)
119{
120 QStringList changed;
121
122 for (auto it = m_bindings.constBegin(); it != m_bindings.constEnd(); ++it) {
123 const MnaParamBinding& binding = it.value();
124
125 // Skip manual bindings — they require explicit invocation
126 if (binding.trigger == QStringLiteral("manual")) {
127 continue;
128 }
129
130 QVariant newValue = evaluateExpression(binding.expression, results);
131
132 if (newValue.isValid()) {
133 QVariant oldValue = m_params.value(binding.targetPath);
134 if (newValue != oldValue) {
135 m_params.insert(binding.targetPath, newValue);
136 changed.append(binding.targetPath);
137 }
138 }
139 }
140
141 return changed;
142}
143
144//=============================================================================================================
145
146QVariant MnaParamTree::evaluateExpression(const QString& expr,
147 const QMap<QString, QVariant>& results) const
148{
149 // Built-in function: ref('path') — look up a parameter or result value
150 static const QRegularExpression refRe(QStringLiteral("ref\\('([^']+)'\\)"));
151
152 // Simple case: bare ref('path')
153 QRegularExpressionMatch refMatch = refRe.match(expr.trimmed());
154 if (refMatch.hasMatch() && refMatch.capturedStart() == 0
155 && refMatch.capturedEnd() == expr.trimmed().length()) {
156 const QString path = refMatch.captured(1);
157 if (m_params.contains(path)) {
158 return m_params.value(path);
159 }
160 return results.value(path);
161 }
162
163 // Built-in: clamp(value, min, max)
164 static const QRegularExpression clampRe(
165 QStringLiteral("clamp\\((.+),\\s*([\\d.eE+-]+),\\s*([\\d.eE+-]+)\\)"));
166 QRegularExpressionMatch clampMatch = clampRe.match(expr.trimmed());
167 if (clampMatch.hasMatch()) {
168 QVariant inner = evaluateExpression(clampMatch.captured(1).trimmed(), results);
169 double val = inner.toDouble();
170 double lo = clampMatch.captured(2).toDouble();
171 double hi = clampMatch.captured(3).toDouble();
172 return QVariant(std::clamp(val, lo, hi));
173 }
174
175 // Built-in: scale(value, factor)
176 static const QRegularExpression scaleRe(
177 QStringLiteral("scale\\((.+),\\s*([\\d.eE+-]+)\\)"));
178 QRegularExpressionMatch scaleMatch = scaleRe.match(expr.trimmed());
179 if (scaleMatch.hasMatch()) {
180 QVariant inner = evaluateExpression(scaleMatch.captured(1).trimmed(), results);
181 double factor = scaleMatch.captured(2).toDouble();
182 return QVariant(inner.toDouble() * factor);
183 }
184
185 // Built-in: threshold(value, thresh, above, below)
186 static const QRegularExpression threshRe(
187 QStringLiteral("threshold\\((.+),\\s*([\\d.eE+-]+),\\s*([\\d.eE+-]+),\\s*([\\d.eE+-]+)\\)"));
188 QRegularExpressionMatch threshMatch = threshRe.match(expr.trimmed());
189 if (threshMatch.hasMatch()) {
190 QVariant inner = evaluateExpression(threshMatch.captured(1).trimmed(), results);
191 double val = inner.toDouble();
192 double thresh = threshMatch.captured(2).toDouble();
193 double above = threshMatch.captured(3).toDouble();
194 double below = threshMatch.captured(4).toDouble();
195 return QVariant(val > thresh ? above : below);
196 }
197
198 // Built-in: lerp(a, b, t)
199 static const QRegularExpression lerpRe(
200 QStringLiteral("lerp\\(([\\d.eE+-]+),\\s*([\\d.eE+-]+),\\s*(.+)\\)"));
201 QRegularExpressionMatch lerpMatch = lerpRe.match(expr.trimmed());
202 if (lerpMatch.hasMatch()) {
203 double a = lerpMatch.captured(1).toDouble();
204 double b = lerpMatch.captured(2).toDouble();
205 QVariant tVal = evaluateExpression(lerpMatch.captured(3).trimmed(), results);
206 double t = tVal.toDouble();
207 return QVariant(a + (b - a) * t);
208 }
209
210 // Try to parse as a simple arithmetic expression with one ref and operators
211 // Pattern: ref('path') * number or number / (ref('path') + number)
212 // For now, handle "expr1 * expr2" and "expr1 / expr2" and "expr1 + expr2"
213 // by simple splitting on top-level operators (no parentheses nesting for now)
214
215 // Numeric literal
216 bool ok = false;
217 double numVal = expr.trimmed().toDouble(&ok);
218 if (ok) {
219 return QVariant(numVal);
220 }
221
222 // Simple binary: left * right
223 static const QRegularExpression mulRe(QStringLiteral("^(.+)\\s*\\*\\s*([\\d.eE+-]+)$"));
224 QRegularExpressionMatch mulMatch = mulRe.match(expr.trimmed());
225 if (mulMatch.hasMatch()) {
226 QVariant left = evaluateExpression(mulMatch.captured(1).trimmed(), results);
227 double right = mulMatch.captured(2).toDouble();
228 return QVariant(left.toDouble() * right);
229 }
230
231 // Simple binary: number / (expr)
232 static const QRegularExpression divRe(QStringLiteral("^([\\d.eE+-]+)\\s*/\\s*\\((.+)\\)$"));
233 QRegularExpressionMatch divMatch = divRe.match(expr.trimmed());
234 if (divMatch.hasMatch()) {
235 double left = divMatch.captured(1).toDouble();
236 QVariant right = evaluateExpression(divMatch.captured(2).trimmed(), results);
237 double rightVal = right.toDouble();
238 if (std::abs(rightVal) < 1e-15) {
239 return QVariant();
240 }
241 return QVariant(left / rightVal);
242 }
243
244 // Simple binary: expr + number
245 static const QRegularExpression addRe(QStringLiteral("^(.+)\\s*\\+\\s*([\\d.eE+-]+)$"));
246 QRegularExpressionMatch addMatch = addRe.match(expr.trimmed());
247 if (addMatch.hasMatch()) {
248 QVariant left = evaluateExpression(addMatch.captured(1).trimmed(), results);
249 double right = addMatch.captured(2).toDouble();
250 return QVariant(left.toDouble() + right);
251 }
252
253 return QVariant();
254}
255
256//=============================================================================================================
257
258QJsonObject MnaParamTree::toJson() const
259{
260 QJsonObject json;
261
262 // Bindings
263 QJsonArray bindingsArr;
264 for (auto it = m_bindings.constBegin(); it != m_bindings.constEnd(); ++it) {
265 bindingsArr.append(it.value().toJson());
266 }
267 json[QStringLiteral("bindings")] = bindingsArr;
268
269 // Static parameters (only those without a binding)
270 QJsonObject paramsObj;
271 for (auto it = m_params.constBegin(); it != m_params.constEnd(); ++it) {
272 if (!m_bindings.contains(it.key())) {
273 paramsObj.insert(it.key(), QJsonValue::fromVariant(it.value()));
274 }
275 }
276 if (!paramsObj.isEmpty()) {
277 json[QStringLiteral("parameters")] = paramsObj;
278 }
279
280 return json;
281}
282
283//=============================================================================================================
284
286{
287 MnaParamTree tree;
288
289 const QJsonArray bindingsArr = obj.value(QStringLiteral("bindings")).toArray();
290 for (const QJsonValue& v : bindingsArr) {
291 tree.addBinding(MnaParamBinding::fromJson(v.toObject()));
292 }
293
294 const QJsonObject paramsObj = obj.value(QStringLiteral("parameters")).toObject();
295 for (auto it = paramsObj.constBegin(); it != paramsObj.constEnd(); ++it) {
296 tree.setParam(it.key(), it.value().toVariant());
297 }
298
299 return tree;
300}
MnaParamTree class declaration — hierarchical parameter store with formula-driven bindings.
MNE Analysis Container Format (mna/mnx).
Dynamic parameter binding for the MNA parameter tree.
QString expression
Formula string, e.g. "clamp(ref('noise_est_01/snr') * 0.1, 0.01, 1.0)".
QString targetPath
Parameter to control: "nodeId/attrKey".
static MnaParamBinding fromJson(const QJsonObject &json)
QString trigger
"on_change", "periodic", "manual"
void removeBinding(const QString &targetPath)
QVariant evaluateExpression(const QString &expr, const QMap< QString, QVariant > &results) const
bool hasParam(const QString &path) const
QList< MnaParamBinding > bindings() const
QJsonObject toJson() const
void addBinding(const MnaParamBinding &binding)
QStringList evaluate(const QMap< QString, QVariant > &results)
QStringList allPaths() const
bool hasBinding(const QString &targetPath) const
QVariant param(const QString &path) const
void setParam(const QString &path, const QVariant &value)
static MnaParamTree fromJson(const QJsonObject &obj)