INSTINCT Code Coverage Report


Directory: src/
File: Nodes/Utility/Combiner.hpp
Date: 2025-07-19 10:51:51
Exec Total Coverage
Lines: 15 21 71.4%
Functions: 2 2 100.0%
Branches: 24 68 35.3%

Line Branch Exec Source
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 /// @file Combiner.hpp
10 /// @brief Calculates differences between signals
11 /// @author T. Topp (topp@ins.uni-stuttgart.de)
12 /// @date 2024-01-29
13
14 #pragma once
15
16 #include <limits>
17 #include <memory>
18 #include <unordered_set>
19 #include <map>
20
21 #include "Navigation/Time/InsTime.hpp"
22 #include "NodeData/NodeData.hpp"
23 #include "internal/Node/Node.hpp"
24 #include "internal/gui/widgets/DynamicInputPins.hpp"
25
26 #include "Navigation/Math/PolynomialRegressor.hpp"
27
28 #include "NodeData/GNSS/GnssCombination.hpp"
29 #include "NodeData/GNSS/GnssObs.hpp"
30 #include "NodeData/GNSS/RtklibPosObs.hpp"
31 #include "NodeData/GNSS/RtkSolution.hpp"
32 #include "NodeData/GNSS/SppSolution.hpp"
33 #include "NodeData/IMU/ImuObs.hpp"
34 #include "NodeData/IMU/ImuObsSimulated.hpp"
35 #include "NodeData/IMU/ImuObsWDelta.hpp"
36 #include "NodeData/IMU/KvhObs.hpp"
37 #include "NodeData/IMU/VectorNavBinaryOutput.hpp"
38 #include "NodeData/State/InsGnssLCKFSolution.hpp"
39 #include "NodeData/State/InsGnssTCKFSolution.hpp"
40 #include "NodeData/State/PosVelAtt.hpp"
41
42 #include "util/Logger/CommonLog.hpp"
43 #include "util/Container/ScrollingBuffer.hpp"
44 #include "util/Container/Unordered_map.hpp"
45
46 namespace NAV
47 {
48 /// @brief Calculates differences between signals
49 class Combiner : public Node, public CommonLog
50 {
51 public:
52 /// @brief Default constructor
53 Combiner();
54 /// @brief Destructor
55 ~Combiner() override;
56 /// @brief Copy constructor
57 Combiner(const Combiner&) = delete;
58 /// @brief Move constructor
59 Combiner(Combiner&&) = delete;
60 /// @brief Copy assignment operator
61 Combiner& operator=(const Combiner&) = delete;
62 /// @brief Move assignment operator
63 Combiner& operator=(Combiner&&) = delete;
64
65 /// @brief String representation of the Class Type
66 [[nodiscard]] static std::string typeStatic();
67
68 /// @brief String representation of the Class Type
69 [[nodiscard]] std::string type() const override;
70
71 /// @brief String representation of the Class Category
72 [[nodiscard]] static std::string category();
73
74 /// @brief ImGui config window which is shown on double click
75 /// @attention Don't forget to set _hasConfig to true in the constructor of the node
76 void guiConfig() override;
77
78 /// @brief Saves the node into a json object
79 [[nodiscard]] json save() const override;
80
81 /// @brief Restores the node from a json object
82 /// @param[in] j Json object with the node state
83 void restore(const json& j) override;
84
85 private:
86 constexpr static size_t OUTPUT_PORT_INDEX_DYN_DATA = 0; ///< @brief Flow (DynamicData)
87
88 /// Possible data identifiers to connect
89 static inline std::vector<std::string> _dataIdentifier = { Pos::type(),
90 PosVel::type(),
91 PosVelAtt::type(),
92 InsGnssLCKFSolution::type(),
93 InsGnssTCKFSolution::type(),
94 GnssCombination::type(),
95 GnssObs::type(),
96 SppSolution::type(),
97 RtklibPosObs::type(),
98 RtkSolution::type(),
99 ImuObs::type(),
100 ImuObsWDelta::type(),
101 ImuObsSimulated::type(),
102 KvhObs::type(),
103 VectorNavBinaryOutput::type() };
104
105 /// Combination of data
106 struct Combination
107 {
108 /// Term of a combination equation
109 struct Term
110 {
111 double factor = 1.0; ///< Factor to multiply the term with
112 size_t pinIndex = 0; ///< Pin Index
113 std::variant<size_t, std::string> dataSelection = size_t(0); ///< Data Index or Data identifier
114
115 PolynomialRegressor<double> polyReg{ 1, 2 }; ///< Polynomial Regressor to interpolate data
116 ScrollingBuffer<std::shared_ptr<const NodeData>> rawData{ 2 }; ///< Last raw data to add if we send
117
118 /// @brief Get a string description of the combination
119 /// @param node Combiner node pointer
120 /// @param descriptors Data descriptors
121 624576 [[nodiscard]] std::string description(const Combiner* node, const std::vector<std::string>& descriptors) const
122 {
123
3/6
✓ Branch 1 taken 624576 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 624576 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 624576 times.
✗ Branch 8 not taken.
624576 if (std::holds_alternative<size_t>(dataSelection) && std::get<size_t>(dataSelection) < descriptors.size())
124 {
125
7/16
✓ Branch 1 taken 312288 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 312288 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 312288 times.
✗ Branch 7 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 12 taken 312288 times.
✓ Branch 13 taken 312288 times.
✓ Branch 14 taken 312288 times.
✓ Branch 15 taken 312288 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
2186016 return fmt::format("{} {} ({})", factor == 1.0 ? "+" : (factor == -1.0 ? "-" : fmt::format("{:.2f}", factor)),
126
3/4
✓ Branch 3 taken 312288 times.
✓ Branch 4 taken 312288 times.
✓ Branch 6 taken 624576 times.
✗ Branch 7 not taken.
1249152 descriptors.at(std::get<size_t>(dataSelection)), node->inputPins.at(pinIndex).name);
127 }
128 if (std::holds_alternative<std::string>(dataSelection))
129 {
130 return fmt::format("{} {} ({})", factor == 1.0 ? "+" : (factor == -1.0 ? "-" : fmt::format("{:.2f}", factor)),
131 std::get<std::string>(dataSelection), node->inputPins.at(pinIndex).name);
132 }
133 return fmt::format("N/A ({})", node->inputPins.at(pinIndex).name);
134 }
135 };
136
137 /// List of terms making up the combination
138 std::vector<Term> terms{ Term{ .factor = +1.0, .pinIndex = 0 },
139 Term{ .factor = -1.0, .pinIndex = 1 } };
140
141 /// @brief Get a string description of the combination
142 /// @param node Combiner node pointer
143 156144 [[nodiscard]] std::string description(const Combiner* node) const
144 {
145 156144 std::string desc;
146
2/2
✓ Branch 5 taken 312288 times.
✓ Branch 6 taken 156144 times.
468432 for (const auto& term : terms)
147 {
148
1/2
✓ Branch 1 taken 312288 times.
✗ Branch 2 not taken.
312288 auto descriptors = node->getDataDescriptors(term.pinIndex);
149
1/2
✓ Branch 1 taken 312288 times.
✗ Branch 2 not taken.
312288 auto termDescription = term.description(node, descriptors);
150
151
2/2
✓ Branch 1 taken 156144 times.
✓ Branch 2 taken 156144 times.
312288 if (!desc.empty())
152 {
153
3/6
✓ Branch 1 taken 156144 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 156144 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 156144 times.
✗ Branch 7 not taken.
156144 if (termDescription.starts_with("+ ") || termDescription.starts_with("- "))
154 {
155
1/2
✓ Branch 1 taken 156144 times.
✗ Branch 2 not taken.
156144 desc += " ";
156 }
157 else
158 {
159 desc += " + ";
160 }
161 }
162
1/2
✓ Branch 1 taken 312288 times.
✗ Branch 2 not taken.
312288 desc += termDescription;
163 312288 }
164
165 156144 return desc;
166 }
167 };
168
169 /// Combinations to calculate
170 std::vector<Combination> _combinations{ Combination() };
171
172 /// Pin data
173 struct PinData
174 {
175 /// Time of the last observation processed
176 InsTime lastTime;
177 /// Min time between messages
178 double minTimeStep = std::numeric_limits<double>::infinity();
179 /// Extra data descriptors for dynamic data
180 std::vector<std::string> dynDataDescriptors;
181 };
182
183 /// Data per pin
184 std::vector<PinData> _pinData;
185
186 /// Reference pin
187 size_t _refPinIdx = 0;
188
189 /// Output missing combinations with NaN instead of removing
190 bool _outputMissingAsNaN = false;
191
192 bool _noOutputIfTimeDiffLarge = true; ///< Wether to not output a term if the interpolation time point is too far away
193 double _maxTimeDiffMultiplierFrequency = 2.1; ///< Multiply frequency with this to get maximum allowed time difference to interpolate to
194
195 bool _noOutputIfTimeStepLarge = true; ///< Wether to not output a term if the time step to interpolate in between is large
196 double _maxTimeStepMultiplierFrequency = 5.0; ///< Multiply frequency with this to get maximum allowed time step of incoming observations
197
198 /// Send request information
199 struct SendRequest
200 {
201 size_t combIndex = 0; ///< Combination Index
202 std::unordered_set<size_t> termIndices; ///< Term indices, which are already calculated
203 double result = 0.0; ///< Calculation result
204 bool termNullopt = false; ///< True if one of the terms values returned std::nullopt
205 std::vector<std::pair<std::string, std::shared_ptr<const NodeData>>> rawData; ///< List of the raw data of all terms contributing to the result
206 };
207
208 /// Chronological list of send request
209 std::map<InsTime, std::vector<SendRequest>> _sendRequests;
210
211 /// @brief Function to call to add a new pin
212 /// @param[in, out] node Pointer to this node
213 static void pinAddCallback(Node* node);
214 /// @brief Function to call to delete a pin
215 /// @param[in, out] node Pointer to this node
216 /// @param[in] pinIdx Input pin index to delete
217 static void pinDeleteCallback(Node* node, size_t pinIdx);
218
219 /// @brief Dynamic input pins
220 /// @attention This should always be the last variable in the header, because it accesses others through the function callbacks
221 gui::widgets::DynamicInputPins _dynamicInputPins{ 0, this, pinAddCallback, pinDeleteCallback };
222
223 /// @brief Initialize the node
224 bool initialize() override;
225
226 /// @brief Deinitialize the node
227 void deinitialize() override;
228
229 /// @brief Returns a list of descriptors for the pin
230 /// @param pinIndex Pin Index to look for the descriptor
231 [[nodiscard]] std::vector<std::string> getDataDescriptors(size_t pinIndex) const;
232
233 /// @brief Receive Data Function
234 /// @param[in] queue Queue with all the received data messages
235 /// @param[in] pinIdx Index of the pin the data is received on
236 void receiveData(InputPin::NodeDataQueue& queue, size_t pinIdx);
237
238 /// @brief Write info to a json object
239 /// @param[out] j Json output
240 /// @param[in] data Object to read info from
241 friend void to_json(json& j, const Combination& data);
242 /// @brief Read info from a json object
243 /// @param[in] j Json variable to read info from
244 /// @param[out] data Output object
245 friend void from_json(const json& j, Combination& data);
246 /// @brief Write info to a json object
247 /// @param[out] j Json output
248 /// @param[in] data Object to read info from
249 friend void to_json(json& j, const Combination::Term& data);
250 /// @brief Read info from a json object
251 /// @param[in] j Json variable to read info from
252 /// @param[out] data Output object
253 friend void from_json(const json& j, Combination::Term& data);
254 };
255
256 } // namespace NAV
257