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