| 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 |