0.3.0
Loading...
Searching...
No Matches
Combiner.cpp
Go to the documentation of this file.
1// This file is part of INSTINCT, the INS Toolkit for Integrated
2// Navigation Concepts and Training by the Institute of Navigation of
3// the University of Stuttgart, Germany.
4//
5// This Source Code Form is subject to the terms of the Mozilla Public
6// License, v. 2.0. If a copy of the MPL was not distributed with this
7// file, You can obtain one at https://mozilla.org/MPL/2.0/.
8
9#include "Combiner.hpp"
10#include "NodeRegistry.hpp"
13#include "internal/Node/Pin.hpp"
15
16#include <algorithm>
17#include <cmath>
18#include <cstddef>
19#include <imgui.h>
20#include <imgui_internal.h>
21#include <limits>
22#include <vector>
23#include <spdlog/common.h>
24
26#include "util/Assert.h"
27#include "util/Logger.hpp"
28
30#include <fmt/core.h>
31namespace nm = NAV::NodeManager;
33
35
37
38namespace NAV
39{
40
41/// @brief Write info to a json object
42/// @param[out] j Json output
43/// @param[in] data Object to read info from
45{
46 j = json{
47 { "factor", data.factor },
48 { "pinIndex", data.pinIndex },
49 { "dataSelection", data.dataSelection },
50 };
51}
52/// @brief Read info from a json object
53/// @param[in] j Json variable to read info from
54/// @param[out] data Output object
56{
57 if (j.contains("factor")) { j.at("factor").get_to(data.factor); }
58 if (j.contains("pinIndex")) { j.at("pinIndex").get_to(data.pinIndex); }
59 if (j.contains("dataSelection")) { j.at("dataSelection").get_to(data.dataSelection); }
60}
61
62/// @brief Write info to a json object
63/// @param[out] j Json output
64/// @param[in] data Object to read info from
65void to_json(json& j, const Combiner::Combination& data)
66{
67 j = json{
68 { "terms", data.terms },
69 };
70}
71/// @brief Read info from a json object
72/// @param[in] j Json variable to read info from
73/// @param[out] data Output object
75{
76 if (j.contains("terms")) { j.at("terms").get_to(data.terms); }
77}
78
80 : Node(typeStatic())
81{
82 LOG_TRACE("{}: called", name);
83
84 _hasConfig = true;
85 _guiConfigDefaultWindowSize = { 430, 410 };
86
87 _dynamicInputPins.addPin(this);
88 _dynamicInputPins.addPin(this);
90}
91
93{
94 LOG_TRACE("{}: called", nameId());
95}
96
98{
99 return "Combiner";
100}
101
102std::string Combiner::type() const
103{
104 return typeStatic();
105}
106
108{
109 return "Utility";
110}
111
113{
114 ImGui::SetNextItemWidth(150.0F * gui::NodeEditorApplication::windowFontRatio());
115 if (ImGui::BeginCombo(fmt::format("Reference pin##{}", size_t(id)).c_str(), inputPins.at(_refPinIdx).name.c_str()))
116 {
117 for (size_t i = 0; i < inputPins.size(); i++)
118 {
119 const bool is_selected = _refPinIdx == i;
120 if (ImGui::Selectable(inputPins.at(i).name.c_str(), is_selected))
121 {
122 _refPinIdx = i;
124 }
125 if (is_selected) { ImGui::SetItemDefaultFocus(); }
126 }
127 ImGui::EndCombo();
128 }
129 ImGui::SameLine();
130 gui::widgets::HelpMarker("Outputs will be sent at epochs of this input pin");
131
132 ImGui::SameLine();
133 if (ImGui::Checkbox(fmt::format("Output missing as NaN##{}", size_t(id)).c_str(), &_outputMissingAsNaN))
134 {
136 }
137
138 if (ImGui::Checkbox(fmt::format("##_noOutputIfTimeDiffLarge{}", size_t(id)).c_str(), &_noOutputIfTimeDiffLarge))
139 {
141 }
142 ImGui::SameLine();
143 if (!_noOutputIfTimeDiffLarge) { ImGui::BeginDisabled(); }
144 ImGui::SetNextItemWidth(200.0F * gui::NodeEditorApplication::windowFontRatio());
145 if (ImGui::DragDouble(fmt::format("Max time diff to interpolate##{}", size_t(id)).c_str(),
146 &_maxTimeDiffMultiplierFrequency, 1.0F, 0.0, std::numeric_limits<double>::max(), "%.1f * dt_min"))
147 {
149 }
150 if (!_noOutputIfTimeDiffLarge) { ImGui::EndDisabled(); }
151
152 if (ImGui::Checkbox(fmt::format("##_noOutputIfTimeStepLarge{}", size_t(id)).c_str(), &_noOutputIfTimeStepLarge))
153 {
155 }
156 ImGui::SameLine();
157 if (!_noOutputIfTimeStepLarge) { ImGui::BeginDisabled(); }
158 ImGui::SetNextItemWidth(200.0F * gui::NodeEditorApplication::windowFontRatio());
159 if (ImGui::DragDouble(fmt::format("Max observation time diff##{}", size_t(id)).c_str(),
160 &_maxTimeStepMultiplierFrequency, 1.0F, 0.0, std::numeric_limits<double>::max(), "%.1f * dt_min"))
161 {
163 }
164 if (!_noOutputIfTimeStepLarge) { ImGui::EndDisabled(); }
165
166 if (CommonLog::ShowOriginInput(nameId().c_str()))
167 {
169 }
170
171 ImGui::SetNextItemOpen(false, ImGuiCond_FirstUseEver);
172 if (ImGui::CollapsingHeader(fmt::format("Pins##{}", size_t(id)).c_str()))
173 {
174 std::vector<size_t> pinIds;
175 pinIds.reserve(inputPins.size());
176 for (const auto& pin : inputPins) { pinIds.push_back(size_t(pin.id)); }
177 if (_dynamicInputPins.ShowGuiWidgets(size_t(id), inputPins, this))
178 {
179 if (_refPinIdx > inputPins.size()) { _refPinIdx--; }
180 std::vector<size_t> inputPinIds;
181 inputPinIds.reserve(inputPins.size());
182 for (const auto& pin : inputPins) { inputPinIds.push_back(size_t(pin.id)); }
183 LOG_DATA("{}: old Input pin ids {}", nameId(), fmt::join(pinIds, ", "));
184 LOG_DATA("{}: new Input pin ids {}", nameId(), fmt::join(inputPinIds, ", "));
185 if (inputPins.size() == pinIds.size()) // Reorder performed
186 {
187 for (size_t i = 0; i < pinIds.size(); i++)
188 {
189 auto pinId = pinIds.at(i);
190 const auto& inputPinId = size_t(inputPins.at(i).id);
191 if (pinId != inputPinId)
192 {
193 size_t newPinIdx = inputPinIndexFromId(pinId);
194 LOG_DATA("{}: Pin {} moved index {} -> {}", nameId(), pinId, i, newPinIdx);
195 for (auto& comb : _combinations)
196 {
197 for (auto& term : comb.terms)
198 {
199 if (term.pinIndex == i) { term.pinIndex = newPinIdx + 10000; }
200 }
201 }
202 if (_refPinIdx == i) { _refPinIdx = newPinIdx + 10000; }
203 }
204 }
205 for (auto& comb : _combinations)
206 {
207 for (auto& term : comb.terms)
208 {
209 if (term.pinIndex >= 10000) { term.pinIndex -= 10000; }
210 }
211 }
212 if (_refPinIdx >= 10000) { _refPinIdx -= 10000; }
213 }
215 }
216 }
217
218 std::vector<size_t> combToDelete;
219 for (size_t c = 0; c < _combinations.size(); c++)
220 {
221 auto& comb = _combinations.at(c);
222
223 bool keepCombination = true;
224 if (ImGui::CollapsingHeader(fmt::format("{}##id{} c{}", comb.description(this), size_t(id), c).c_str(),
225 _combinations.size() > 1 ? &keepCombination : nullptr, ImGuiTreeNodeFlags_DefaultOpen))
226 {
227 constexpr int COL_PER_TERM = 3;
228 const float COL_SIGN_WIDTH = 50.0F * gui::NodeEditorApplication::windowFontRatio();
229 const float COL_PIN_WIDTH = 100.0F * gui::NodeEditorApplication::windowFontRatio();
230
231 std::vector<size_t> termToDelete;
232 bool addTerm = false;
233 if (ImGui::BeginTable(fmt::format("##Table id{} c{}", size_t(id), c).c_str(), COL_PER_TERM * static_cast<int>(comb.terms.size()),
234 ImGuiTableFlags_NoHostExtendX | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollX,
236 {
237 ImGui::TableNextRow();
238
239 for (size_t t = 0; t < comb.terms.size(); t++)
240 {
241 auto& term = comb.terms.at(t);
242
243 ImGui::TableSetColumnIndex(static_cast<int>(t) * COL_PER_TERM);
244 ImGui::SetNextItemWidth(COL_SIGN_WIDTH);
245 if (ImGui::InputDouble(fmt::format("##factor id{} c{} t{}", size_t(id), c, t).c_str(), &term.factor, 0.0, 0.0, "%.2f"))
246 {
248 }
249 ImGui::SameLine();
250 ImGui::TextUnformatted("*");
251
252 ImGui::SameLine();
253 ImGui::SetNextItemWidth(COL_PIN_WIDTH);
254 if (ImGui::BeginCombo(fmt::format("##pinIndex id{} c{} t{}", size_t(id), c, t).c_str(), inputPins.at(term.pinIndex).name.c_str()))
255 {
256 for (size_t i = 0; i < inputPins.size(); i++)
257 {
258 const bool is_selected = term.pinIndex == i;
259 if (ImGui::Selectable(inputPins.at(i).name.c_str(), is_selected))
260 {
261 term.pinIndex = i;
262 term.dataSelection = size_t(0);
264 }
265 if (is_selected) { ImGui::SetItemDefaultFocus(); }
266 }
267 ImGui::EndCombo();
268 }
269
270 ImGui::TableSetColumnIndex(static_cast<int>(t) * COL_PER_TERM + 1);
271 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 2.0F);
272 float radius = 8.0F * gui::NodeEditorApplication::windowFontRatio();
273 ImGui::GetWindowDrawList()->AddCircleFilled(ImGui::GetCursorScreenPos() + ImVec2(radius, ImGui::GetTextLineHeight() / 2.0F + 2.0F), radius,
274 term.polyReg.empty() ? IM_COL32(255, 0, 0, 255) : IM_COL32(0, 255, 0, 255));
275 ImGui::Dummy(ImVec2(radius * 2.0F, ImGui::GetTextLineHeight()));
276 if (ImGui::IsItemHovered()) { ImGui::SetTooltip(term.polyReg.empty() ? "Signal was not received" : "Signal was received"); }
277
278 ImGui::TableSetColumnIndex(static_cast<int>(t) * COL_PER_TERM + 2);
279 if (static_cast<int>(t) * COL_PER_TERM + 2 == COL_PER_TERM * static_cast<int>(comb.terms.size()) - 1)
280 {
281 addTerm |= ImGui::Button(fmt::format("+## Add Term id{} c{}", size_t(id), c).c_str());
282 if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Add Term?"); }
283 }
284 else
285 {
286 ImGui::TextUnformatted("+");
287 }
288 }
289
290 ImGui::TableNextRow();
291 for (size_t t = 0; t < comb.terms.size(); t++)
292 {
293 auto& term = comb.terms.at(t);
294
295 ImGui::TableSetColumnIndex(static_cast<int>(t) * COL_PER_TERM);
296 auto dataDescriptors = getDataDescriptors(term.pinIndex);
297 if ((std::holds_alternative<size_t>(term.dataSelection) && std::get<size_t>(term.dataSelection) < dataDescriptors.size())
298 || std::holds_alternative<std::string>(term.dataSelection))
299 {
300 std::string previewText = std::holds_alternative<size_t>(term.dataSelection)
301 ? dataDescriptors.at(std::get<size_t>(term.dataSelection))
302 : std::get<std::string>(term.dataSelection);
303 float comboWidth = COL_SIGN_WIDTH + COL_PIN_WIDTH + ImGui::CalcTextSize("*").x + 2.0F * ImGui::GetStyle().ItemSpacing.x;
304 ImGui::SetNextItemWidth(comboWidth);
305 if (ImGui::BeginCombo(fmt::format("##dataIndex id{} c{} t{}", size_t(id), c, t).c_str(), previewText.c_str()))
306 {
307 for (size_t i = 0; i < dataDescriptors.size(); i++)
308 {
309 const bool is_selected = std::holds_alternative<size_t>(term.dataSelection)
310 ? std::get<size_t>(term.dataSelection) == i
311 : dataDescriptors.at(i) == previewText;
312 if (ImGui::Selectable(dataDescriptors.at(i).c_str(), is_selected))
313 {
314 if (dataDescriptors.size() - i <= _pinData.at(term.pinIndex).dynDataDescriptors.size())
315 {
316 term.dataSelection = dataDescriptors.at(i);
317 }
318 else
319 {
320 term.dataSelection = i;
321 }
322 for (size_t t2 = 0; t2 < comb.terms.size(); t2++) // Set other terms to same data if possible
323 {
324 if (t2 == t) { continue; }
325 auto& term2 = comb.terms.at(t2);
326 auto dataDescriptors2 = getDataDescriptors(term2.pinIndex);
327 auto iter = std::ranges::find(dataDescriptors2,
328 std::holds_alternative<size_t>(term.dataSelection)
329 ? dataDescriptors.at(std::get<size_t>(term.dataSelection))
330 : std::get<std::string>(term.dataSelection));
331 if (iter != dataDescriptors2.end())
332 {
333 if (std::holds_alternative<size_t>(term.dataSelection))
334 {
335 term2.dataSelection = static_cast<size_t>(std::distance(dataDescriptors2.begin(), iter));
336 }
337 else
338 {
339 term2.dataSelection = term.dataSelection;
340 }
341 }
342 }
343
345 }
346 if (is_selected) { ImGui::SetItemDefaultFocus(); }
347 }
348 ImGui::EndCombo();
349 }
350 if (ImGui::IsItemHovered() && ImGui::CalcTextSize(previewText.c_str()).x > comboWidth - 15.0F)
351 {
352 ImGui::SetTooltip("%s", previewText.c_str());
353 }
354 }
355 else if (inputPins.at(term.pinIndex).isPinLinked())
356 {
357 ImGui::TextUnformatted("Please run the flow");
358 ImGui::SameLine();
359 gui::widgets::HelpMarker("Available data is collected when running the flow");
360 }
361
362 if (comb.terms.size() > 1)
363 {
364 ImGui::TableSetColumnIndex(static_cast<int>(t) * COL_PER_TERM + 1);
365 if (ImGui::Button(fmt::format("X##id{} c{} t{}", size_t(id), c, t).c_str()))
366 {
368 termToDelete.push_back(t);
369 }
370 if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Remove Term?"); }
371 }
372 }
373
374 ImGui::EndTable();
375 }
376
377 for (const auto& t : termToDelete) { comb.terms.erase(std::next(comb.terms.begin(), static_cast<std::ptrdiff_t>(t))); }
378 if (addTerm)
379 {
381 comb.terms.emplace_back();
382 }
383 }
384
385 if (!keepCombination) { combToDelete.push_back(c); }
386 }
387 for (const auto& c : combToDelete) { _combinations.erase(std::next(_combinations.begin(), static_cast<std::ptrdiff_t>(c))); }
388
389 ImGui::Separator();
390 if (ImGui::Button(fmt::format("Add Combination##id{}", size_t(id)).c_str()))
391 {
393 _combinations.emplace_back();
394 }
395}
396
397[[nodiscard]] json Combiner::save() const
398{
399 LOG_TRACE("{}: called", nameId());
400
401 return {
402 { "dynamicInputPins", _dynamicInputPins },
403 { "combinations", _combinations },
404 { "refPinIdx", _refPinIdx },
405 { "outputMissingAsNaN", _outputMissingAsNaN },
406 { "noOutputIfTimeDiffLarge", _noOutputIfTimeDiffLarge },
407 { "maxTimeDiffMultiplierFrequency", _maxTimeDiffMultiplierFrequency },
408 { "noOutputIfTimeStepLarge", _noOutputIfTimeStepLarge },
409 { "maxTimeStepMultiplierFrequency", _maxTimeStepMultiplierFrequency },
410 };
411}
412
414{
415 LOG_TRACE("{}: called", nameId());
416
417 if (j.contains("dynamicInputPins")) { NAV::gui::widgets::from_json(j.at("dynamicInputPins"), _dynamicInputPins, this); }
418 if (j.contains("combinations")) { j.at("combinations").get_to(_combinations); }
419 if (j.contains("refPinIdx")) { j.at("refPinIdx").get_to(_refPinIdx); }
420 if (j.contains("outputMissingAsNaN")) { j.at("outputMissingAsNaN").get_to(_outputMissingAsNaN); }
421 if (j.contains("noOutputIfTimeDiffLarge")) { j.at("noOutputIfTimeDiffLarge").get_to(_noOutputIfTimeDiffLarge); }
422 if (j.contains("maxTimeDiffMultiplierFrequency")) { j.at("maxTimeDiffMultiplierFrequency").get_to(_maxTimeDiffMultiplierFrequency); }
423 if (j.contains("noOutputIfTimeStepLarge")) { j.at("noOutputIfTimeStepLarge").get_to(_noOutputIfTimeStepLarge); }
424 if (j.contains("maxTimeStepMultiplierFrequency")) { j.at("maxTimeStepMultiplierFrequency").get_to(_maxTimeStepMultiplierFrequency); }
425}
426
428{
429 auto* combiner = static_cast<Combiner*>(node); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
430 combiner->_pinData.emplace_back();
431 nm::CreateInputPin(node, fmt::format("Pin {}", node->inputPins.size() + 1).c_str(), Pin::Type::Flow, _dataIdentifier, &Combiner::receiveData);
432}
433
434void Combiner::pinDeleteCallback(Node* node, size_t pinIdx)
435{
436 auto* combiner = static_cast<Combiner*>(node); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
437 if (pinIdx == combiner->_refPinIdx && combiner->_refPinIdx && combiner->_refPinIdx >= combiner->inputPins.size()) { combiner->_refPinIdx--; }
438 combiner->_pinData.erase(std::next(combiner->_pinData.begin(), static_cast<int64_t>(pinIdx)));
439 nm::DeleteInputPin(node->inputPins.at(pinIdx));
440
441 for (auto& comb : combiner->_combinations)
442 {
443 for (int64_t t = 0; t < static_cast<int64_t>(comb.terms.size()); ++t)
444 {
445 auto& term = comb.terms.at(static_cast<size_t>(t));
446 if (term.pinIndex == pinIdx) // The index we want to delete
447 {
448 comb.terms.erase(comb.terms.begin() + t);
449 --t;
450 }
451 else if (term.pinIndex > pinIdx) // Index higher -> Decrement
452 {
453 --(term.pinIndex);
454 }
455 }
456 }
457}
458
460{
461 LOG_TRACE("{}: called", nameId());
462
464
465 for (auto& comb : _combinations)
466 {
467 for (auto& term : comb.terms)
468 {
469 term.polyReg.reset();
470 }
471 }
472
473 for (auto& pinData : _pinData)
474 {
475 pinData.lastTime.reset();
476 pinData.minTimeStep = std::numeric_limits<double>::infinity();
477 pinData.dynDataDescriptors.clear();
478 }
479
480 _sendRequests.clear();
481
482 return true;
483}
484
486{
487 LOG_TRACE("{}: called", nameId());
488}
489
490std::vector<std::string> Combiner::getDataDescriptors(size_t pinIndex) const
491{
492 std::vector<std::string> dataDescriptors;
493 if (OutputPin* sourcePin = inputPins.at(pinIndex).link.getConnectedPin())
494 {
495 dataDescriptors = NAV::NodeRegistry::GetStaticDataDescriptors(sourcePin->dataIdentifier);
496 }
497 if (!_pinData.at(pinIndex).dynDataDescriptors.empty())
498 {
499 dataDescriptors.reserve(dataDescriptors.size() + _pinData.at(pinIndex).dynDataDescriptors.size());
500 std::copy(_pinData.at(pinIndex).dynDataDescriptors.begin(), _pinData.at(pinIndex).dynDataDescriptors.end(), std::back_inserter(dataDescriptors));
501 }
502 return dataDescriptors;
503}
504
506{
507 auto nodeData = queue.extract_front();
508 auto nodeDataTimeIntoRun = math::round(calcTimeIntoRun(nodeData->insTime), 8);
509 LOG_DATA("{}: [{:.3f}s][{} ({})] Received obs for time [{} GPST] ", nameId(),
510 nodeDataTimeIntoRun, inputPins.at(pinIdx).name, pinIdx, nodeData->insTime.toYMDHMS(GPST));
511
512 if (!_pinData.at(pinIdx).lastTime.empty())
513 {
514 auto dt = static_cast<double>((nodeData->insTime - _pinData.at(pinIdx).lastTime).count());
515 if (dt > 1e-6) { _pinData.at(pinIdx).minTimeStep = std::min(_pinData.at(pinIdx).minTimeStep, dt); }
516 }
517 _pinData.at(pinIdx).lastTime = nodeData->insTime;
518
519 if (pinIdx == _refPinIdx)
520 {
521 std::vector<SendRequest> requests;
522 for (size_t c = 0; c < _combinations.size(); ++c)
523 {
524 if (std::any_of(_combinations.at(c).terms.begin(),
525 _combinations.at(c).terms.end(),
526 [&](const Combination::Term& term) { return _pinData.at(term.pinIndex).lastTime.empty()
527 && (inputPins.at(term.pinIndex).queue.empty() || inputPins.at(term.pinIndex).queue.front()->insTime != nodeData->insTime); }))
528 {
529 continue; // We cannot add a term when the first data appears later
530 }
531 SendRequest sr{
532 .combIndex = c,
533 .termIndices = {},
534 .result = 0.0,
535 .rawData = {},
536 };
537 requests.push_back(sr);
538 }
539 if (!requests.empty())
540 {
541 LOG_DATA("{}: Adding new send request with", nameId(), nodeDataTimeIntoRun, nodeData->insTime.toYMDHMS(GPST));
542 for ([[maybe_unused]] const auto& request : requests)
543 {
544 LOG_DATA("{}: Combination: {}", nameId(), _combinations.at(request.combIndex).description(this));
545 }
546 _sendRequests.emplace(nodeData->insTime, requests);
547 }
548 }
549
550 // Add dynamic data descriptors to display in GUI
551 auto& dataDescriptors = _pinData.at(pinIdx).dynDataDescriptors;
552 const std::vector<std::string> nodeDataDescriptors = nodeData->dynamicDataDescriptors();
553 for (const auto& desc : nodeDataDescriptors)
554 {
555 if (std::ranges::find(dataDescriptors, desc) == dataDescriptors.end())
556 {
557 dataDescriptors.push_back(desc);
558 }
559 }
560
561 auto* sourcePin = inputPins.at(pinIdx).link.getConnectedPin();
562 if (sourcePin == nullptr) { return; }
563
564 for (size_t c = 0; c < _combinations.size(); ++c)
565 {
566 auto& comb = _combinations.at(c);
567 LOG_DATA("{}: Combination: {}", nameId(), comb.description(this));
568 for (size_t t = 0; t < comb.terms.size(); ++t)
569 {
570 auto& term = comb.terms.at(t);
571 if (term.pinIndex != pinIdx) { continue; }
572 if (std::holds_alternative<size_t>(term.dataSelection) && nodeData->staticDescriptorCount() <= std::get<size_t>(term.dataSelection)
573 && std::get<size_t>(term.dataSelection) < dataDescriptors.size())
574 {
575 term.dataSelection = dataDescriptors.at(std::get<size_t>(term.dataSelection));
577 }
578
579 if (auto value = std::holds_alternative<size_t>(term.dataSelection) ? nodeData->getValueAt(std::get<size_t>(term.dataSelection))
580 : nodeData->getDynamicDataAt(std::get<std::string>(term.dataSelection)))
581 {
582 LOG_DATA("{}: Term '{}': {:.3g}", nameId(), term.description(this, getDataDescriptors(term.pinIndex)), *value);
583 term.polyReg.push_back(std::make_pair(nodeDataTimeIntoRun, *value));
584 }
585 else
586 {
587 LOG_DATA("{}: Term '{}': Value not available", nameId(), term.description(this, getDataDescriptors(term.pinIndex)));
588 }
589 term.rawData.push_back(nodeData);
590 LOG_DATA("{}: Adding NodeData to the end of term.rawData. It now includes:", nameId());
591 for ([[maybe_unused]] const auto& data : term.rawData)
592 {
593 LOG_DATA("{}: {}", nameId(), data->insTime.toYMDHMS(GPST));
594 }
595
596 // Check for all combinations with new info:
597
598 if (std::ranges::any_of(comb.terms, [&](const auto& t) {
599 bool termHasNoData = t.rawData.empty();
600 if (termHasNoData)
601 {
602 LOG_DATA("{}: Skipping, because no data on other term '{}' yet", nameId(), t.description(this, getDataDescriptors(t.pinIndex)));
603 }
604 return termHasNoData;
605 }))
606 {
607 continue;
608 }
609
610 if (!_sendRequests.empty()) { LOG_DATA("{}: Checking if term is in a send request", nameId()); }
611 for (auto& [sendRequestTime, sendRequests] : _sendRequests)
612 {
613 std::set<size_t> combsToRemove;
614 for (auto& sendRequest : sendRequests)
615 {
616 if (sendRequest.combIndex != c) { continue; }
617 const auto& srComb = _combinations.at(sendRequest.combIndex);
618
619 for (const auto& srTerm : srComb.terms)
620 {
621 if (combsToRemove.contains(sendRequest.combIndex)) { continue; }
622 if (srTerm.pinIndex != term.pinIndex || srTerm.dataSelection != term.dataSelection) { continue; }
623 LOG_DATA("{}: [{:.3f}s] Term found in combination and term is {}", nameId(), math::round(calcTimeIntoRun(sendRequestTime), 8),
624 sendRequest.termIndices.contains(t) ? "already calculated." : "still missing");
625
626 if (sendRequest.termIndices.contains(t)) { continue; } // The term was already calculated
627
628 if (auto dt = static_cast<double>((nodeData->insTime - sendRequestTime).count()); // Out of bounds (do not interpolate)
629 (_noOutputIfTimeDiffLarge && dt > _maxTimeDiffMultiplierFrequency * _pinData.at(pinIdx).minTimeStep)
630 || (_noOutputIfTimeStepLarge && srTerm.rawData.full()
631 && static_cast<double>((srTerm.rawData.back()->insTime - srTerm.rawData.front()->insTime).count())
632 > _maxTimeStepMultiplierFrequency * _pinData.at(pinIdx).minTimeStep))
633 {
635 {
636 sendRequest.result = std::nan("");
637 LOG_DATA("{}: Setting combination {} to NaN (({} && dt = {} > dt_min = {}) || ({} && dt_interp = {} > {}))", nameId(),
638 _combinations.at(sendRequest.combIndex).description(this),
641 static_cast<double>((srTerm.rawData.back()->insTime - srTerm.rawData.front()->insTime).count()),
642 _maxTimeStepMultiplierFrequency * _pinData.at(pinIdx).minTimeStep);
643 }
644 else
645 {
646 combsToRemove.emplace(sendRequest.combIndex);
647 LOG_DATA("{}: Removing combination {} (({} && dt = {} > dt_min = {}) || ({} && dt_interp = {} > {}))", nameId(),
648 _combinations.at(sendRequest.combIndex).description(this),
651 static_cast<double>((srTerm.rawData.back()->insTime - srTerm.rawData.front()->insTime).count()),
652 _maxTimeStepMultiplierFrequency * _pinData.at(pinIdx).minTimeStep);
653 continue;
654 }
655 }
656
657 if (term.polyReg.empty())
658 {
659 sendRequest.termNullopt = true;
660 LOG_DATA("{}: Flagging send request as term missing", nameId());
661 }
662 else
663 {
664 auto poly = term.polyReg.calcPolynomial();
665 LOG_DATA("{}: Updating send request: {} += {:.2f} * {:.3g} (by interpolating to time [{:.3f}s])", nameId(),
666 sendRequest.result, term.factor, poly.f(math::round(calcTimeIntoRun(sendRequestTime), 8)),
667 math::round(calcTimeIntoRun(sendRequestTime), 8));
668 sendRequest.result += term.factor * poly.f(math::round(calcTimeIntoRun(sendRequestTime), 8));
669 }
670 sendRequest.termIndices.insert(t);
671
672 bool exactTimeFound = false;
673 for (const auto& rawData : term.rawData)
674 {
675 if (rawData->insTime == sendRequestTime)
676 {
677 LOG_DATA("{}: Adding rawData [{}] {}", nameId(),
678 rawData->insTime.toYMDHMS(GPST),
679 term.description(this, getDataDescriptors(term.pinIndex)));
680
681 sendRequest.rawData.emplace_back(term.description(this, getDataDescriptors(term.pinIndex)),
682 rawData);
683 exactTimeFound = true;
684 break;
685 }
686 }
687 if (exactTimeFound) { continue; }
688 for (const auto& rawData : term.rawData)
689 {
690 LOG_DATA("{}: Adding rawData [{}] {}", nameId(),
691 rawData->insTime.toYMDHMS(GPST),
692 term.description(this, getDataDescriptors(term.pinIndex)));
693
694 sendRequest.rawData.emplace_back(term.description(this, getDataDescriptors(term.pinIndex)),
695 rawData);
696 }
697 }
698 }
699 for (const auto& comb : combsToRemove)
700 {
701 std::erase_if(sendRequests, [&](const SendRequest& sr) { return sr.combIndex == comb; });
702 }
703 }
704 }
705 }
706
707 std::vector<InsTime> emptySendRequests;
708 for (const auto& [sendRequestTime, sendRequests] : _sendRequests)
709 {
710 if (sendRequests.empty())
711 {
712 LOG_DATA("{}: Discarding send request at [{}]", nameId(), sendRequestTime.toYMDHMS(GPST));
713 emptySendRequests.push_back(sendRequestTime);
714 }
715 }
716 for (const auto& sendRequestTime : emptySendRequests) { _sendRequests.erase(sendRequestTime); }
717
718 if (!_sendRequests.empty()) { LOG_DATA("{}: Send requests ({})", nameId(), _sendRequests.size()); }
719 for (const auto& [sendRequestTime, sendRequests] : _sendRequests)
720 {
721 LOG_DATA("{}: [{:.3f}s] [{}]", nameId(), math::round(calcTimeIntoRun(sendRequestTime), 8), sendRequestTime.toYMDHMS(GPST));
722 for (const auto& sendRequest : sendRequests)
723 {
724 const auto& comb = _combinations.at(sendRequest.combIndex);
725 LOG_DATA("{}: Combination: {}{}", nameId(), comb.description(this), sendRequest.termNullopt ? " (some term war nullopt)" : "");
726 for (size_t t = 0; t < comb.terms.size(); ++t)
727 {
728 LOG_DATA("{}: Term '{}' is {}", nameId(), comb.terms.at(t).description(this, getDataDescriptors(comb.terms.at(t).pinIndex)),
729 sendRequest.termIndices.contains(t) ? "added" : "missing");
730 }
731 }
732 }
733
734 LOG_DATA("{}: [{:.3f}s] Checking wether a send request can be sent ({} requests)", nameId(), nodeDataTimeIntoRun, _sendRequests.size());
735 std::vector<InsTime> requestsToRemove;
736 for (const auto& [sendRequestTime, sendRequests] : _sendRequests)
737 {
738 LOG_DATA("{}: [{:.3f}s] [{}]", nameId(), math::round(calcTimeIntoRun(sendRequestTime), 8), sendRequestTime.toYMDHMS(GPST));
739 LOG_DATA("{}: Combinations (all terms in all combinations must be calculated)", nameId());
740 if (std::ranges::all_of(sendRequests, [&](const auto& sendRequest) {
741 const auto& comb = _combinations.at(sendRequest.combIndex);
742 LOG_DATA("{}: '{}' has {}/{} terms set", nameId(), comb.description(this), sendRequest.termIndices.size(), comb.terms.size());
743 return comb.terms.size() == sendRequest.termIndices.size();
744 }))
745 {
746 // If all combinations for this send request epoch are calculated, send it out
747 auto dynData = std::make_shared<DynamicData>();
748 dynData->insTime = sendRequestTime;
749 LOG_DATA("{}: Sending out dynamic data at [{}] and deleting send request", nameId(), dynData->insTime.toYMDHMS(GPST));
750
751 for (const auto& sendRequest : sendRequests)
752 {
753 if (sendRequest.termNullopt) { continue; }
754 const auto& comb = _combinations.at(sendRequest.combIndex);
756 .description = comb.description(this),
757 .value = sendRequest.result,
758 .rawData = sendRequest.rawData
759 };
760 LOG_DATA("{}: {} includes raw data", nameId(), data.description);
761 for ([[maybe_unused]] const auto& raw : data.rawData)
762 {
763 LOG_DATA("{}: [{}] from '{}'", nameId(), raw.second->insTime.toYMDHMS(GPST), raw.first);
764 }
765 dynData->data.push_back(data);
766 }
767
768 invokeCallbacks(OUTPUT_PORT_INDEX_DYN_DATA, dynData);
769
770 requestsToRemove.push_back(sendRequestTime);
771 }
772 else
773 {
774 // If not, break, because we always have to send out the first request first
775 break;
776 }
777 }
778 for (const auto& insTime : requestsToRemove)
779 {
780 _sendRequests.erase(insTime);
781 }
782}
783
784} // namespace NAV
Assertion helpers.
Calculates differences between signals.
Dynamic Data container.
Save/Load the Nodes.
nlohmann::json json
json namespace
Text Help Marker (?) with Tooltip.
The class is responsible for all time-related tasks.
Utility class for logging to console and file.
#define LOG_DATA
All output which occurs repeatedly every time observations are received.
Definition Logger.hpp:29
#define LOG_TRACE
Detailled info to trace the execution of the program. Should not be called on functions which receive...
Definition Logger.hpp:65
Manages all Nodes.
Utility class which specifies available nodes.
Pin class.
Time System defintions.
static void pinAddCallback(Node *node)
Function to call to add a new pin.
Definition Combiner.cpp:427
bool initialize() override
Initialize the node.
Definition Combiner.cpp:459
gui::widgets::DynamicInputPins _dynamicInputPins
Dynamic input pins.
Definition Combiner.hpp:219
std::vector< std::string > getDataDescriptors(size_t pinIndex) const
Returns a list of descriptors for the pin.
Definition Combiner.cpp:490
double _maxTimeDiffMultiplierFrequency
Multiply frequency with this to get maximum allowed time difference to interpolate to.
Definition Combiner.hpp:191
static std::vector< std::string > _dataIdentifier
Possible data identifiers to connect.
Definition Combiner.hpp:88
void guiConfig() override
ImGui config window which is shown on double click.
Definition Combiner.cpp:112
void deinitialize() override
Deinitialize the node.
Definition Combiner.cpp:485
bool _outputMissingAsNaN
Output missing combinations with NaN instead of removing.
Definition Combiner.hpp:188
static void pinDeleteCallback(Node *node, size_t pinIdx)
Function to call to delete a pin.
Definition Combiner.cpp:434
void receiveData(InputPin::NodeDataQueue &queue, size_t pinIdx)
Receive Data Function.
Definition Combiner.cpp:505
double _maxTimeStepMultiplierFrequency
Multiply frequency with this to get maximum allowed time step of incoming observations.
Definition Combiner.hpp:194
bool _noOutputIfTimeStepLarge
Wether to not output a term if the time step to interpolate in between is large.
Definition Combiner.hpp:193
size_t _refPinIdx
Reference pin.
Definition Combiner.hpp:185
static std::string category()
String representation of the Class Category.
Definition Combiner.cpp:107
std::string type() const override
String representation of the Class Type.
Definition Combiner.cpp:102
std::map< InsTime, std::vector< SendRequest > > _sendRequests
Chronological list of send request.
Definition Combiner.hpp:207
static std::string typeStatic()
String representation of the Class Type.
Definition Combiner.cpp:97
std::vector< PinData > _pinData
Data per pin.
Definition Combiner.hpp:182
Combiner()
Default constructor.
Definition Combiner.cpp:79
void restore(const json &j) override
Restores the node from a json object.
Definition Combiner.cpp:413
bool _noOutputIfTimeDiffLarge
Wether to not output a term if the interpolation time point is too far away.
Definition Combiner.hpp:190
~Combiner() override
Destructor.
Definition Combiner.cpp:92
json save() const override
Saves the node into a json object.
Definition Combiner.cpp:397
std::vector< Combination > _combinations
Combinations to calculate.
Definition Combiner.hpp:168
static bool ShowOriginInput(const char *id)
Shows a GUI to input a origin location.
void initialize() const
Initialize the common log variables.
Definition CommonLog.cpp:51
static double calcTimeIntoRun(const InsTime &insTime)
Calculates the relative time into he run.
Definition CommonLog.cpp:70
static std::string type()
Returns the type of the data class.
TsDeque< std::shared_ptr< const NAV::NodeData > > NodeDataQueue
Node data queue type.
Definition Pin.hpp:707
ImVec2 _guiConfigDefaultWindowSize
Definition Node.hpp:410
Node(std::string name)
Constructor.
Definition Node.cpp:30
std::vector< InputPin > inputPins
List of input pins.
Definition Node.hpp:397
std::string nameId() const
Node name and id.
Definition Node.cpp:253
size_t inputPinIndexFromId(ax::NodeEditor::PinId pinId) const
Returns the index of the pin.
Definition Node.cpp:233
std::string name
Name of the Node.
Definition Node.hpp:395
bool _hasConfig
Flag if the config window should be shown.
Definition Node.hpp:413
Output pins of nodes.
Definition Pin.hpp:338
auto extract_front()
Returns a copy of the first element in the container and removes it from the container.
Definition TsDeque.hpp:494
static float windowFontRatio()
Ratio to multiply for GUI window elements.
ImGui extensions.
bool DragDouble(const char *label, double *v, float v_speed, double v_min, double v_max, const char *format, ImGuiSliderFlags flags)
Shows a Drag GUI element for 'double'.
Definition imgui_ex.cpp:19
OutputPin * CreateOutputPin(Node *node, const char *name, Pin::Type pinType, const std::vector< std::string > &dataIdentifier, OutputPin::PinData data=static_cast< void * >(nullptr), int idx=-1)
Create an Output Pin object.
bool DeleteInputPin(InputPin &pin)
Deletes the input pin. Invalidates the pin reference given.
InputPin * CreateInputPin(Node *node, const char *name, Pin::Type pinType, const std::vector< std::string > &dataIdentifier={}, InputPin::Callback callback=static_cast< InputPin::FlowFirableCallbackFunc >(nullptr), InputPin::FlowFirableCheckFunc firable=nullptr, int priority=0, int idx=-1)
Create an Input Pin object.
std::vector< std::string > GetStaticDataDescriptors(const std::vector< std::string > &dataIdentifier)
Returns a vector of data descriptors for the pin data identifiers.
void ApplyChanges()
Signals that there have been changes to the flow.
void from_json(const json &j, DynamicInputPins &obj, Node *node)
Converts the provided json object into a node object.
void HelpMarker(const char *desc, const char *symbol="(?)")
Text Help Marker, e.g. '(?)', with Tooltip.
constexpr T round(const T &value, size_t digits)
Round the number to the specified amount of digits.
Definition Math.hpp:41
@ GPST
GPS Time.
void to_json(json &j, const Node &node)
Converts the provided node into a json object.
Definition Node.cpp:990
void from_json(const json &j, Node &node)
Converts the provided json object into a node object.
Definition Node.cpp:1007
Term of a combination equation.
Definition Combiner.hpp:108
double factor
Factor to multiply the term with.
Definition Combiner.hpp:109
std::variant< size_t, std::string > dataSelection
Data Index or Data identifier.
Definition Combiner.hpp:111
Combination of data.
Definition Combiner.hpp:105
std::vector< Term > terms
List of terms making up the combination.
Definition Combiner.hpp:136
Send request information.
Definition Combiner.hpp:198
size_t combIndex
Combination Index.
Definition Combiner.hpp:199
@ Flow
NodeData Trigger.
Definition Pin.hpp:52