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