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 // Built-in function: ref('path') — look up a parameter or result value
141 static const QRegularExpression refRe(QStringLiteral("ref\\('([^']+)'\\)"));
142
143 // Simple case: bare ref('path')
144 QRegularExpressionMatch refMatch = refRe.match(expr.trimmed());
145 if (refMatch.hasMatch() && refMatch.capturedStart() == 0
146 && refMatch.capturedEnd() == expr.trimmed().length()) {
147 const QString path = refMatch.captured(1);
148 if (m_params.contains(path)) {
149 return m_params.value(path);
150 }
151 return results.value(path);
152 }
153
154 // Built-in: clamp(value, min, max)
155 static const QRegularExpression clampRe(
156 QStringLiteral("clamp\\((.+),\\s*([\\d.eE+-]+),\\s*([\\d.eE+-]+)\\)"));
157 QRegularExpressionMatch clampMatch = clampRe.match(expr.trimmed());
158 if (clampMatch.hasMatch()) {
159 QVariant inner = evaluateExpression(clampMatch.captured(1).trimmed(), results);
160 double val = inner.toDouble();
161 double lo = clampMatch.captured(2).toDouble();
162 double hi = clampMatch.captured(3).toDouble();
163 return QVariant(std::clamp(val, lo, hi));
164 }
165
166 // Built-in: scale(value, factor)
167 static const QRegularExpression scaleRe(
168 QStringLiteral("scale\\((.+),\\s*([\\d.eE+-]+)\\)"));
169 QRegularExpressionMatch scaleMatch = scaleRe.match(expr.trimmed());
170 if (scaleMatch.hasMatch()) {
171 QVariant inner = evaluateExpression(scaleMatch.captured(1).trimmed(), results);
172 double factor = scaleMatch.captured(2).toDouble();
173 return QVariant(inner.toDouble() * factor);
174 }
175
176 // Built-in: threshold(value, thresh, above, below)
177 static const QRegularExpression threshRe(
178 QStringLiteral("threshold\\((.+),\\s*([\\d.eE+-]+),\\s*([\\d.eE+-]+),\\s*([\\d.eE+-]+)\\)"));
179 QRegularExpressionMatch threshMatch = threshRe.match(expr.trimmed());
180 if (threshMatch.hasMatch()) {
181 QVariant inner = evaluateExpression(threshMatch.captured(1).trimmed(), results);
182 double val = inner.toDouble();
183 double thresh = threshMatch.captured(2).toDouble();
184 double above = threshMatch.captured(3).toDouble();
185 double below = threshMatch.captured(4).toDouble();
186 return QVariant(val > thresh ? above : below);
187 }
188
189 // Built-in: lerp(a, b, t)
190 static const QRegularExpression lerpRe(
191 QStringLiteral("lerp\\(([\\d.eE+-]+),\\s*([\\d.eE+-]+),\\s*(.+)\\)"));
192 QRegularExpressionMatch lerpMatch = lerpRe.match(expr.trimmed());
193 if (lerpMatch.hasMatch()) {
194 double a = lerpMatch.captured(1).toDouble();
195 double b = lerpMatch.captured(2).toDouble();
196 QVariant tVal = evaluateExpression(lerpMatch.captured(3).trimmed(), results);
197 double t = tVal.toDouble();
198 return QVariant(a + (b - a) * t);
199 }
200
201 // Try to parse as a simple arithmetic expression with one ref and operators
202 // Pattern: ref('path') * number or number / (ref('path') + number)
203 // For now, handle "expr1 * expr2" and "expr1 / expr2" and "expr1 + expr2"
204 // by simple splitting on top-level operators (no parentheses nesting for now)
205
206 // Numeric literal
207 bool ok = false;
208 double numVal = expr.trimmed().toDouble(&ok);
209 if (ok) {
210 return QVariant(numVal);
211 }
212
213 // Simple binary: left * right
214 static const QRegularExpression mulRe(QStringLiteral("^(.+)\\s*\\*\\s*([\\d.eE+-]+)$"));
215 QRegularExpressionMatch mulMatch = mulRe.match(expr.trimmed());
216 if (mulMatch.hasMatch()) {
217 QVariant left = evaluateExpression(mulMatch.captured(1).trimmed(), results);
218 double right = mulMatch.captured(2).toDouble();
219 return QVariant(left.toDouble() * right);
220 }
221
222 // Simple binary: number / (expr)
223 static const QRegularExpression divRe(QStringLiteral("^([\\d.eE+-]+)\\s*/\\s*\\((.+)\\)$"));
224 QRegularExpressionMatch divMatch = divRe.match(expr.trimmed());
225 if (divMatch.hasMatch()) {
226 double left = divMatch.captured(1).toDouble();
227 QVariant right = evaluateExpression(divMatch.captured(2).trimmed(), results);
228 double rightVal = right.toDouble();
229 if (std::abs(rightVal) < 1e-15) {
230 return QVariant();
231 }
232 return QVariant(left / rightVal);
233 }
234
235 // Simple binary: expr + number
236 static const QRegularExpression addRe(QStringLiteral("^(.+)\\s*\\+\\s*([\\d.eE+-]+)$"));
237 QRegularExpressionMatch addMatch = addRe.match(expr.trimmed());
238 if (addMatch.hasMatch()) {
239 QVariant left = evaluateExpression(addMatch.captured(1).trimmed(), results);
240 double right = addMatch.captured(2).toDouble();
241 return QVariant(left.toDouble() + right);
242 }
243
244 return QVariant();
245}
246
247//=============================================================================================================
248
249QJsonObject MnaParamTree::toJson() const
250{
251 QJsonObject json;
252
253 // Bindings
254 QJsonArray bindingsArr;
255 for (auto it = m_bindings.constBegin(); it != m_bindings.constEnd(); ++it) {
256 bindingsArr.append(it.value().toJson());
257 }
258 json[QStringLiteral("bindings")] = bindingsArr;
259
260 // Static parameters (only those without a binding)
261 QJsonObject paramsObj;
262 for (auto it = m_params.constBegin(); it != m_params.constEnd(); ++it) {
263 if (!m_bindings.contains(it.key())) {
264 paramsObj.insert(it.key(), QJsonValue::fromVariant(it.value()));
265 }
266 }
267 if (!paramsObj.isEmpty()) {
268 json[QStringLiteral("parameters")] = paramsObj;
269 }
270
271 return json;
272}
273
274//=============================================================================================================
275
277{
278 MnaParamTree tree;
279
280 const QJsonArray bindingsArr = obj.value(QStringLiteral("bindings")).toArray();
281 for (const QJsonValue& v : bindingsArr) {
282 tree.addBinding(MnaParamBinding::fromJson(v.toObject()));
283 }
284
285 const QJsonObject paramsObj = obj.value(QStringLiteral("parameters")).toObject();
286 for (auto it = paramsObj.constBegin(); it != paramsObj.constEnd(); ++it) {
287 tree.setParam(it.key(), it.value().toVariant());
288 }
289
290 return tree;
291}
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)