INSTINCT Code Coverage Report


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