From 4bcd26ccd0ec1fb30a2aba6b8da258f0e8734f00 Mon Sep 17 00:00:00 2001 From: Thomas Wodtko Date: Thu, 9 Apr 2026 13:07:57 +0200 Subject: [PATCH] [UPDATE] publications and cuda impl --- CITATION.cff | 31 ++ README.md | 35 +- .../container/long_short_term_memory.hpp | 254 ++++++++++ .../multi_source/conflict_operators.hpp | 453 +++++++++++++----- .../multi_source/fusion_operators.hpp | 241 ++++++++-- .../multi_source/trust_revision_operators.hpp | 246 +++++++--- .../multi_source/trusted_fusion_operators.hpp | 205 ++++++-- .../subjective_logic_lib/opinions/opinion.hpp | 107 ++++- .../opinions/opinion_no_base.hpp | 270 ++++++++--- .../opinions/trusted_opinion.hpp | 143 +++++- .../subjective_logic_lib/types/convert.hpp | 46 +- .../types/cuda_compatible_array.hpp | 182 +++++-- .../types/dirichlet_distribution.hpp | 38 +- .../types/fusion_types.hpp | 67 +++ .../types/limited_float.hpp | 336 +++++++++++++ library/include/subjective_logic_lib/util.hpp | 88 +++- library/python/subjective_logic/__init__.py | 27 +- .../subjective_logic/instance_generator.py | 8 + library/scripts/test_multi_source.py | 37 ++ library/src/bindings/CMakeLists.txt | 9 + library/src/bindings/bindings.cpp | 2 + library/src/bindings/bindings.hpp | 5 +- .../bindings/container/container_bindings.hpp | 14 + .../container/long_short_term_memory.cpp | 65 +++ .../multi_source/conflict_operators.cpp | 27 +- .../multi_source/fusion_operators.cpp | 18 +- .../multi_source/trust_revision_operators.cpp | 28 +- .../multi_source/trusted_fusion_operators.cpp | 32 +- library/src/bindings/opinions/opinion.cpp | 8 + .../src/bindings/opinions/opinion_no_base.cpp | 9 +- .../src/bindings/opinions/trusted_opinion.cpp | 15 +- .../bindings/template_combination_helper.hpp | 47 ++ .../bindings/types/cuda_compatible_array.cpp | 18 +- .../bindings/types/dirichlet_distribution.cpp | 14 +- library/src/bindings/types/fusion_types.cpp | 51 ++ library/src/bindings/types/types_bindings.hpp | 1 + library/test/CMakeLists.txt | 1 + library/test/standalone/CMakeLists.txt | 58 +++ .../standalone/multi_source/CMakeLists.txt | 27 ++ .../multi_source/multi_source_conflict.cu | 153 ++++++ .../multi_source/multi_source_fusion.cu | 169 +++++++ .../multi_source_trust_revision.cu | 104 ++++ .../multi_source_trusted_fusion.cu | 108 +++++ library/test/standalone/multi_source/test.cu | 37 ++ library/test/standalone/operator_test.cpp | 172 +++++++ library/test/standalone/operator_test.cu | 53 ++ .../unittest/subjective_logic/CMakeLists.txt | 29 +- .../container/long_short_term_memory.cpp | 223 +++++++++ .../multi_source/conflict_operators.cpp | 24 +- .../multi_source/fusion_operators.cpp | 36 +- .../multi_source/trusted_fusion_operators.cpp | 30 +- .../binomial_opinion_no_base_test.cpp | 19 + .../opinions/binomial_opinion_test.cpp | 29 +- .../multinomial_opinion_no_base_test.cpp | 31 ++ .../opinions/multinomial_opinion_test.cpp | 26 + .../opinions/trusted_opinion.cpp | 50 +- .../subjective_logic/types/limited_float.cpp | 105 ++++ .../2024_fusion_wodtko_conflict.bib | 12 + publications/2024_fusion/README.md | 48 ++ publications/2024_fusion/deps_to_remember.txt | 12 + .../2024_fusion/paper/barycentric_triangle.py | 64 +++ .../paper/time_series_weather_forecast.py | 170 +++++++ .../paper/trust_revision_josang_ours.py | 303 ++++++++++++ .../2024_fusion/paper/trust_revision_sweep.py | 158 ++++++ .../2024_fusion/presentation/.gitignore | 1 + .../presentation/barycentric_triangle.py | 83 ++++ .../presentation/conflict_shares.py | 162 +++++++ .../presentation/degree_of_conflict.py | 128 +++++ .../2024_fusion/presentation/sl_fusion.py | 143 ++++++ .../presentation/sl_trust_discount.py | 115 +++++ .../presentation/sl_trusted_fusion.py | 367 ++++++++++++++ ...2025_unidasFAST_wodtko_griebel_unified.bib | 6 + publications/2025_fas/README.md | 31 ++ publications/2025_fas/__init__.py | 0 .../__pycache__/system_state.cpython-312.pyc | Bin 0 -> 5828 bytes publications/2025_fas/barycentric_triangle.py | 66 +++ .../2025_fas/colored_opinion_triangle.py | 97 ++++ .../evaluate_lucid_system_architectur.py | 261 ++++++++++ publications/2025_fas/safety_formular.py | 81 ++++ publications/2025_fas/system_state.py | 70 +++ .../2025_mfi_wodtko_balancing.bib | 15 +- publications/2025_mfi_wodtko/README.md | 17 +- .../2025_mfi_wodtko/exp_distribution.py | 17 +- .../exp_nonconstant_parameter.py | 15 +- publications/README.md | 15 +- publications/dissertation_wodtko/README.md | 98 ++++ .../__pycache__/system_state.cpython-312.pyc | Bin 0 -> 4259 bytes publications/dissertation_wodtko/beta.py | 27 ++ .../evaluate_lucid_system_architectur.py | 162 +++++++ .../dissertation_wodtko/exp_distribution.py | 144 ++++++ .../exp_nonconstant_parameter.py | 161 +++++++ .../dissertation_wodtko/requirements.txt | 1 + .../dissertation_wodtko/system_state.py | 56 +++ .../time_dependent_weather_forecast.py | 176 +++++++ .../dissertation_wodtko/tr_cs_hscs.py | 376 +++++++++++++++ .../dissertation_wodtko/tr_josang_cs.py | 386 +++++++++++++++ .../eSLIM++/2025_mfi_wodtko_eslimpp.bib | 16 +- publications/eSLIM++/README.md | 17 + 98 files changed, 8083 insertions(+), 655 deletions(-) create mode 100644 CITATION.cff create mode 100644 library/include/subjective_logic_lib/container/long_short_term_memory.hpp create mode 100644 library/include/subjective_logic_lib/types/fusion_types.hpp create mode 100644 library/include/subjective_logic_lib/types/limited_float.hpp create mode 100644 library/scripts/test_multi_source.py create mode 100644 library/src/bindings/container/container_bindings.hpp create mode 100644 library/src/bindings/container/long_short_term_memory.cpp create mode 100644 library/src/bindings/types/fusion_types.cpp create mode 100644 library/test/standalone/CMakeLists.txt create mode 100644 library/test/standalone/multi_source/CMakeLists.txt create mode 100644 library/test/standalone/multi_source/multi_source_conflict.cu create mode 100644 library/test/standalone/multi_source/multi_source_fusion.cu create mode 100644 library/test/standalone/multi_source/multi_source_trust_revision.cu create mode 100644 library/test/standalone/multi_source/multi_source_trusted_fusion.cu create mode 100644 library/test/standalone/multi_source/test.cu create mode 100644 library/test/standalone/operator_test.cpp create mode 100644 library/test/standalone/operator_test.cu create mode 100644 library/test/unittest/subjective_logic/container/long_short_term_memory.cpp create mode 100644 library/test/unittest/subjective_logic/types/limited_float.cpp create mode 100644 publications/2024_fusion/2024_fusion_wodtko_conflict.bib create mode 100644 publications/2024_fusion/README.md create mode 100644 publications/2024_fusion/deps_to_remember.txt create mode 100644 publications/2024_fusion/paper/barycentric_triangle.py create mode 100644 publications/2024_fusion/paper/time_series_weather_forecast.py create mode 100644 publications/2024_fusion/paper/trust_revision_josang_ours.py create mode 100644 publications/2024_fusion/paper/trust_revision_sweep.py create mode 100644 publications/2024_fusion/presentation/.gitignore create mode 100644 publications/2024_fusion/presentation/barycentric_triangle.py create mode 100644 publications/2024_fusion/presentation/conflict_shares.py create mode 100644 publications/2024_fusion/presentation/degree_of_conflict.py create mode 100644 publications/2024_fusion/presentation/sl_fusion.py create mode 100644 publications/2024_fusion/presentation/sl_trust_discount.py create mode 100644 publications/2024_fusion/presentation/sl_trusted_fusion.py create mode 100644 publications/2025_fas/2025_unidasFAST_wodtko_griebel_unified.bib create mode 100644 publications/2025_fas/README.md create mode 100644 publications/2025_fas/__init__.py create mode 100644 publications/2025_fas/__pycache__/system_state.cpython-312.pyc create mode 100644 publications/2025_fas/barycentric_triangle.py create mode 100644 publications/2025_fas/colored_opinion_triangle.py create mode 100644 publications/2025_fas/evaluate_lucid_system_architectur.py create mode 100644 publications/2025_fas/safety_formular.py create mode 100644 publications/2025_fas/system_state.py create mode 100644 publications/dissertation_wodtko/README.md create mode 100644 publications/dissertation_wodtko/__pycache__/system_state.cpython-312.pyc create mode 100644 publications/dissertation_wodtko/beta.py create mode 100644 publications/dissertation_wodtko/evaluate_lucid_system_architectur.py create mode 100644 publications/dissertation_wodtko/exp_distribution.py create mode 100644 publications/dissertation_wodtko/exp_nonconstant_parameter.py create mode 100644 publications/dissertation_wodtko/requirements.txt create mode 100644 publications/dissertation_wodtko/system_state.py create mode 100644 publications/dissertation_wodtko/time_dependent_weather_forecast.py create mode 100644 publications/dissertation_wodtko/tr_cs_hscs.py create mode 100644 publications/dissertation_wodtko/tr_josang_cs.py diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..6c07315 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,31 @@ +cff-version: 1.2.0 +title: "eSLIM++" +message: "If you use this software, please cite the following paper." +type: software + +authors: + - family-names: "Wodtko" + given-names: "Thomas" + - family-names: "Griebel" + given-names: "Thomas" + - family-names: "Buchholz" + given-names: "Michael" + +preferred-citation: + type: conference + title: "eSLIM++ - An Efficient Subjective Logic Implementation in C++ Providing Easy-to-Use Python Interfaces" + booktitle: "2025 IEEE International Conference on Multisensor Fusion and Integration for Intelligent Systems (MFI)" + year: 2025 + doi: "10.1109/MFI67357.2025.11259193" + pages: 1-7 + authors: + - family-names: "Wodtko" + given-names: "Thomas" + - family-names: "Griebel" + given-names: "Thomas" + - family-names: "Buchholz" + given-names: "Michael" + +# Optional metadata (recommended) +url: "https://github.com/uulm-mrm/eslimpp" +license: "Apache-2.0" diff --git a/README.md b/README.md index 9903995..d5e149d 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,3 @@ -# For the Reviewers -## Review of the paper: "eSLIM++ - an Efficient Subjective Logic Implementation in C++ Providing Easy-to-Use Python Interfaces" -To run the evaluation scripts reproducing the paper presentations, please refer to the following install/use instructions for [Python](publications/README.md) and [C++](publications/eSLIM++/README.md)\ -For general use, please follow the instructions below. -## Review of the paper: "Balancing Conflict and Harmony - Persistent Trust Revision in Time-Dependent Subjective Networks" -A description of the experiment scripts is provided [here](publications/2025_mfi_wodtko/README.md), which uses the eslimpp library. -General information for Python evaluations apply [see here](publications/README.md). - -[![tests](https://github.com/uulm-mrm/eslimpp/actions/workflows/cpp_test.yml/badge.svg?branch=main)](https://github.com/uulm-mrm/eslimpp/actions/workflows/cpp_test.yml) -[![codecov](https://codecov.io/gh/uulm-mrm/eslimpp/graph/badge.svg?token=WQR9SF8QOU)](https://codecov.io/gh/uulm-mrm/eslimpp) ![](misc/eSLIM_logo.png) # eSLIM++ - efficient Subjective Logic IMplementation in C++ @@ -121,12 +111,25 @@ Community driven updates are highly welcomed, if doing so, please be aware of th # Acompaning publication -Introducing this library, there is currently a paper under review at the IEEE International Conference on Multisensor Fusion and Integration. -The reviewers are highly welcome to check out the code and run the example scripts producing the results presented in this work. -Details of the publication will follow, if applicable. +Introducing this library, a paper was published at the 2025 IEEE International Conference on Multisensor Fusion and Integration. +This paper introduces some fundamentals and outlines some methods and algorithms from the references/applications above, which are implemented in eSLIM++. +The paper is called\ +"eSLIM++ - An Efficient Subjective Logic Implementation in C++ Providing Easy-to-Use Python Interfaces" -# Citation -I would be delighted if eSLIM++ were used in other projects that might get published at some point. +## Citation +I would be delighted if eSLIM++ were used in other projects that might get published at some point.\ In this case, please cite the library with its introduction paper. -A citation description will be available upon publication. +```bibtex +@INPROCEEDINGS{wodtko2025eslimpp, + author={Wodtko, Thomas and Griebel, Thomas and Buchholz, Michael}, + booktitle={2025 IEEE International Conference on Multisensor Fusion and Integration for Intelligent Systems (MFI)}, + title={eSLIM++ - An Efficient Subjective Logic Implementation in C++ Providing Easy-to-Use Python Interfaces}, + year={2025}, + volume={}, + number={}, + pages={1-7}, + keywords={Computer science;Uncertainty;Autonomous systems;Decision making;C++ languages;Sensor fusion;Libraries;Reproducibility of results;Logic;Object tracking}, + doi={10.1109/MFI67357.2025.11259193} +} +``` diff --git a/library/include/subjective_logic_lib/container/long_short_term_memory.hpp b/library/include/subjective_logic_lib/container/long_short_term_memory.hpp new file mode 100644 index 0000000..101ffe8 --- /dev/null +++ b/library/include/subjective_logic_lib/container/long_short_term_memory.hpp @@ -0,0 +1,254 @@ +#pragma once + +#include +#include + +#include "subjective_logic_lib/util.hpp" + +namespace subjective_logic::container +{ +template +class LongShortTermMemory +{ +public: + using FloatT = typename OpinionT::FLOAT_t; + using FusionFunc = std::function; + + LongShortTermMemory(std::size_t short_max_size, FloatT threshold, FloatT discount, FusionFunc fuse_func); + + // there is no point in only resetting the short memory, either long only or both + void reset(); + void reset_long_memory(); + + FloatT get_threshold() const; + void set_threshold(FloatT new_thresh); + + FloatT get_discount() const; + void set_discount(FloatT new_discount); + + [[nodiscard]] std::size_t get_short_max_size() const; + + bool is_last_conflicted() const; + + /** + * @brief Set the maximal size of the short-term memory accordingly. + * IMPORTANT: if the long-term part is currently in use, it is reset to adhere to the new max short-term size + * Thus, if the size changes, + * the long-term memory is reset and + * the LSTMemory size is set according to the currently available short-term memory opinions. + */ + void set_short_max_size(std::size_t short_max_size); + + // # of opinions considered for the current opinion (long + short) + [[nodiscard]] std::size_t size() const; + + /** + * @brief get the current size of the short memory, which may be less that short_max_size + * if less than short_max_size opinions have been added, this number equals the size() + */ + [[nodiscard]] std::size_t get_short_size() const; + + /** + * @brief Adds an opinions to the memory, popped short-term opinions are automatically added to the long-term memory. + * This function returns the output prior to any conflict considerations, + * i.e., + * adds the opinion to the short-term memory and pushed the last short-term opinion to the long-term opinion. + * + * However, conflict handling is performed after adding the opinion and + * the next get_opinion() may no longer consider the "old" long-term memory + */ + OpinionT add(const OpinionT& new_opinion); + + /** + * @brief overall opinion of the current LSTMemory + * IMPORTANT: not all fusion operators have a neutral element, + * however, the long-term part is neglected of no opinion was put into it + * as are all "unused" opinions in the short-term part + */ + [[nodiscard]] OpinionT get_opinion() const; + // outputs the long-term opinion only, including all pseudo time-based trust discounts + [[nodiscard]] OpinionT get_long_opinion() const; + // outputs the short-term opinion only by applying the fusion_func to all short-term opinions + [[nodiscard]] OpinionT get_short_opinion() const; + +protected: + FloatT threshold_; + FloatT discount_; + FusionFunc fuse_func_; + + bool last_conflicted_{ false }; + + std::size_t current_size_{ 0 }; + std::size_t short_max_size_; + std::size_t current_short_ring_idx_{ 0 }; + std::vector short_term_memory_; + + OpinionT long_opinion_{}; +}; + +template +LongShortTermMemory::LongShortTermMemory(std::size_t short_max_size, + FloatT threshold, + FloatT discount, + FusionFunc fuse_func) + : threshold_{ threshold }, discount_{ discount }, fuse_func_(fuse_func), short_max_size_(short_max_size) +{ + short_term_memory_.reserve(short_max_size_); +} + +template +void LongShortTermMemory::reset() +{ + reset_long_memory(); + // keep reserved short mem size + short_term_memory_.clear(); + current_short_ring_idx_ = 0; + current_size_ = 0; +} + +template +void LongShortTermMemory::reset_long_memory() +{ + long_opinion_ = OpinionT::VacuousBeliefOpinion(); + // current_size_ must be reset, to avoid considering long_opinion in next output + current_size_ = std::min(current_size_, short_max_size_); +} + +template +typename LongShortTermMemory::FloatT LongShortTermMemory::get_threshold() const +{ + return threshold_; +} + +template +void LongShortTermMemory::set_threshold(const FloatT new_thresh) +{ + threshold_ = new_thresh; +} +template +typename LongShortTermMemory::FloatT LongShortTermMemory::get_discount() const +{ + return discount_; +} + +template +void LongShortTermMemory::set_discount(const FloatT new_discount) +{ + discount_ = new_discount; +} + +template +std::size_t LongShortTermMemory::get_short_max_size() const +{ + return short_max_size_; +} + +template +bool LongShortTermMemory::is_last_conflicted() const +{ + return last_conflicted_; +} + +template +void LongShortTermMemory::set_short_max_size(const std::size_t short_max_size) +{ + // avoid resetting if nothing changes + if (short_max_size == short_max_size_) + { + return; + } + // long-term must be ditched if currently in use + if (current_size_ > short_max_size_) + { + reset_long_memory(); + } + short_max_size_ = short_max_size; + + // short_max_size might be smaller now + if (current_size_ > short_max_size_) + { + reset_long_memory(); + } +} + +template +std::size_t LongShortTermMemory::size() const +{ + return current_size_; +} + +template +std::size_t LongShortTermMemory::get_short_size() const +{ + return short_term_memory_.size(); +} + +template +OpinionT LongShortTermMemory::add(const OpinionT& new_opinion) +{ + current_size_++; + if (short_term_memory_.size() < short_max_size_) + { + short_term_memory_.push_back(new_opinion); + return get_opinion(); + } + + OpinionT hopped_opinion = short_term_memory_[current_short_ring_idx_]; + short_term_memory_[current_short_ring_idx_++] = new_opinion; + current_short_ring_idx_ %= short_max_size_; + + long_opinion_.trust_discount_(discount_); + long_opinion_ = fuse_func_(long_opinion_, hopped_opinion); + + auto output = get_opinion(); + + // floating point type needs to be selected according to OpinionT + auto conflict = get_short_opinion().degree_of_conflict(long_opinion_); + + if (conflict > threshold_) + { + reset_long_memory(); + last_conflicted_ = true; + } + else + { + last_conflicted_ = false; + } + + return output; +} + +template +OpinionT LongShortTermMemory::get_opinion() const +{ + // if less than short_max_size_ opinions have been added so far, + // fusion vacuous opinions (here long opinion) may lead to false outputs for some fusion operators + if (current_size_ < short_max_size_) + { + return get_short_opinion(); + } + return fuse_func_(get_long_opinion(), get_short_opinion()); +} + +template +OpinionT LongShortTermMemory::get_long_opinion() const +{ + return long_opinion_; +} + +template +OpinionT LongShortTermMemory::get_short_opinion() const +{ + if (short_term_memory_.empty()) + { + return OpinionT::VacuousBeliefOpinion(); + } + OpinionT fused_op = short_term_memory_[0]; + for (std::size_t i = 1; i < short_term_memory_.size(); ++i) + { + fused_op = fuse_func_(fused_op, short_term_memory_[i]); + } + return fused_op; +} + +} // namespace subjective_logic::container \ No newline at end of file diff --git a/library/include/subjective_logic_lib/multi_source/conflict_operators.hpp b/library/include/subjective_logic_lib/multi_source/conflict_operators.hpp index 1c161c9..8ce3bcf 100644 --- a/library/include/subjective_logic_lib/multi_source/conflict_operators.hpp +++ b/library/include/subjective_logic_lib/multi_source/conflict_operators.hpp @@ -10,6 +10,7 @@ #include #include "subjective_logic_lib/util.hpp" +#include "subjective_logic_lib/types/fusion_types.hpp" #include "subjective_logic_lib/opinions/opinion.hpp" #include "subjective_logic_lib/opinions/trusted_opinion.hpp" #include "subjective_logic_lib/multi_source/fusion_operators.hpp" @@ -19,89 +20,68 @@ namespace subjective_logic::multisource struct Conflict { - enum class RelationType : int - { - CONFLICT, - HARMONY - }; - - enum class ConflictType : int - { - ACCUMULATE, - AVERAGE, - BELIEF_CUMULATIVE, - BELIEF_BELIEF_CONSTRAINT, - BELIEF_AVERAGE, - BELIEF_WEIGHTED, - }; - - static constexpr Fusion::FusionType get_belief_fusion_type(ConflictType conflict_type); - template - static inline typename OpinionT::FLOAT_t conflict(ConflictType conflict_type, std::initializer_list inputs) + static typename OpinionT::FLOAT_t conflict(ConflictType conflict_type, std::initializer_list inputs) requires is_opinion or is_opinion_no_base; template - static inline FirstType::type::FLOAT_t conflict(ConflictType conflict_type, Opinions... opinions) + static typename FirstType::type::FLOAT_t conflict(ConflictType conflict_type, Opinions... opinions) requires is_opinion_no_base_list or is_opinion_list; template - static inline typename OpinionT::FLOAT_t conflict(ConflictType conflict_type, - std::vector opinions, - std::optional> use_opinion = std::nullopt) + static typename OpinionT::FLOAT_t conflict(ConflictType conflict_type, + std::vector opinions, + std::optional> use_opinion = std::nullopt) requires is_opinion or is_opinion_no_base; template - static inline typename OpinionT::FLOAT_t harmony(ConflictType conflict_type, std::initializer_list inputs) + static typename OpinionT::FLOAT_t harmony(ConflictType conflict_type, std::initializer_list inputs) requires is_opinion or is_opinion_no_base; template - static inline FirstType::type::FLOAT_t harmony(ConflictType conflict_type, Opinions... opinions) + static typename FirstType::type::FLOAT_t harmony(ConflictType conflict_type, Opinions... opinions) requires is_opinion_no_base_list or is_opinion_list; template - static inline typename OpinionT::FLOAT_t harmony(ConflictType conflict_type, - std::vector opinions, - std::optional> use_opinion = std::nullopt) + static typename OpinionT::FLOAT_t harmony(ConflictType conflict_type, + std::vector opinions, + std::optional> use_opinion = std::nullopt) requires is_opinion or is_opinion_no_base; /** * calculates the share to the average conflict of each opiniont - * @tparam N - * @tparam typename OpinionT::FLOAT_t * @tparam OpinionT + * @param relation_type + * @param conflict_type * @param opinions * @return the average conflict and the share of each opinion to that conflict */ - template + template static inline std::pair> - conflict_shares(ConflictType conflict_type, std::vector opinions) + conflict_shares(RelationType relation_type, ConflictType conflict_type, std::vector opinions) requires is_opinion or is_opinion_no_base; /** * calculated all components for the belief conflict from josang. * the optional reference fusion is only required to implement the specific proposal of the paper.... - * by only considering Opinions here, there is no trust, which would be required. - * I (wodtko) guess that there is little sense in not using the discounted opinions to get the fusion reference, - * but the paper suggests it. + * by only considering Opinions here, there is no trust that would be required. * @tparam OpinionT + * @param relation_type * @param reference_fusion_type * @param opinions * @param reference_fusion * @return */ - template - static typename std:: - tuple, typename OpinionT::FLOAT_t, typename OpinionT::FLOAT_t> - belief_conflicts(Fusion::FusionType reference_fusion_type, - std::vector opinions, - std::optional reference_fusion = std::nullopt) + template + static std::tuple, typename OpinionT::FLOAT_t, typename OpinionT::FLOAT_t> + belief_conflicts(RelationType relation_type, + FusionType reference_fusion_type, + std::vector opinions, + std::optional reference_fusion = std::nullopt) requires is_opinion or is_opinion_no_base; /** * calculates the uncertainty differentials for each opinion individually - * @tparam N - * @tparam typename OpinionT::FLOAT_t * @tparam OpinionT * @param opinions * @return @@ -136,74 +116,135 @@ struct Conflict /** * accumulated conflict of all "connections" within the given set of opinions used - * @tparam N - SL opinion dimension - * @tparam typename OpinionT::FLOAT_t - SL opinion floating point type * @tparam OpinionT - SL opinion type depending on the previous two parameters + * @param relation_type * @param opinions - list of SL opinions - * @param use_opinion - flags which opinions to use * @return accumulated conflict */ - template - static typename OpinionT::FLOAT_t accumulated_operator(std::vector opinions) + template + static typename OpinionT::FLOAT_t accumulated_operator(RelationType relation_type, std::vector opinions) requires is_opinion or is_opinion_no_base; /** * averaged accumulated conflict over all "connections" - * @tparam N - SL opinion dimension - * @tparam typename OpinionT::FLOAT_t - SL opinion floating point type * @tparam OpinionT - SL opinion type depending on the previous two parameters + * @param relation_type * @param opinions - list of SL opinions - * @param use_opinion - flags which opinions to use * @return averaged conflict */ - template - static typename OpinionT::FLOAT_t average_operator(std::vector opinions) + template + static typename OpinionT::FLOAT_t average_operator(RelationType relation_type, std::vector opinions) requires is_opinion or is_opinion_no_base; /** * belief conflict using the given fusion operation to create the reference opinion - * @tparam N - SL opinion dimension - * @tparam typename OpinionT::FLOAT_t - SL opinion floating point type * @tparam OpinionT - SL opinion type depending on the previous two parameters + * @param relation_type + * @param reference_fusion_type * @param opinions - list of SL opinions - * @param use_opinion - flags which opinions to use * @return */ - template - static typename OpinionT::FLOAT_t belief_conflict_operator(Fusion::FusionType reference_fusion_type, + template + static typename OpinionT::FLOAT_t belief_conflict_operator(RelationType relation_type, + FusionType reference_fusion_type, std::vector opinions) requires is_opinion or is_opinion_no_base; + + // in order to allow cuda available generic lambda functions, the function access must be public + // necessity might vanish, when not using generic lambdas + // access for test function may be provided using a Test friend class +public: + /** + * calculates the uncertainty differentials for each opinion individually + * this function implements a cuda ready implementation with a fixed number of opinions + * @tparam OpinionT + * @param opinions + * @return + */ + template + CUDA_AVAIL static inline Array + uncertainty_differentials(const Array& opinions) + requires is_opinion or is_opinion_no_base; + /** + * calculates the uncertainty differentials for each opinion individually. + * Here, the trust of each trusted opinion is used. + * Thus, this function yields the uncertainty differentials (see above) for the trusts of all trusted opinons + * this function implements a cuda ready implementation with a fixed number of opinions + * @tparam TrustedOpinionT + * @param opinions + * @return + */ + template + static inline Array + CUDA_AVAIL uncertainty_differentials(const Array& opinions) + requires is_trusted_opinion; + + template + CUDA_AVAIL static inline typename OpinionT::FLOAT_t function_switch(ConflictType conflict_type, + Array opinions, + int skip = -1) + requires is_opinion or is_opinion_no_base; + + /** + * accumulated conflict of all "connections" within the given set of opinions used + * this function implements a cuda ready implementation with a fixed number of opinions + * @tparam OpinionT - SL opinion type depending on the previous two parameters + * @param relation_type + * @param opinions - list of SL opinions + * @return accumulated conflict + */ + template + CUDA_AVAIL static typename OpinionT::FLOAT_t accumulated_operator(RelationType relation_type, + Array opinions, + int skip = -1) + requires is_opinion or is_opinion_no_base; + + /** + * averaged accumulated conflict over all "connections" + * this function implements a cuda ready implementation with a fixed number of opinions + * @tparam OpinionT - SL opinion type depending on the previous two parameters + * @param relation_type + * @param opinions - list of SL opinions + * @return averaged conflict + */ + template + CUDA_AVAIL static typename OpinionT::FLOAT_t average_operator(RelationType relation_type, + Array opinions, + int skip = -1) + requires is_opinion or is_opinion_no_base; + + template + CUDA_AVAIL static typename OpinionT::FLOAT_t conflict(ConflictType conflict_type, + Array opinions, + int skip = -1) + requires is_opinion or is_opinion_no_base; + + template + CUDA_AVAIL static typename OpinionT::FLOAT_t harmony(ConflictType conflict_type, + Array opinions, + int skip = -1) + requires is_opinion or is_opinion_no_base; + + /** + * calculates the share to the average conflict of each opiniont + * this function implements a cuda ready implementation with a fixed number of opinions + * @tparam OpinionT + * @param relation_type + * @param conflict_type + * @param opinions + * @return the average conflict and the share of each opinion to that conflict + */ + template + CUDA_AVAIL static inline Array + conflict_shares(RelationType relation_type, + ConflictType conflict_type, + Array opinions, + typename OpinionT::FLOAT_t& average_conflict_return) + requires is_opinion or is_opinion_no_base; }; -constexpr Fusion::FusionType Conflict::get_belief_fusion_type(ConflictType conflict_type) -{ - switch (conflict_type) - { - case ConflictType::BELIEF_CUMULATIVE: - { - return Fusion::FusionType::CUMULATIVE; - } - case ConflictType::BELIEF_BELIEF_CONSTRAINT: - { - return Fusion::FusionType::BELIEF_CONSTRAINT; - } - case ConflictType::BELIEF_AVERAGE: - { - return Fusion::FusionType::AVERAGE; - } - case ConflictType::BELIEF_WEIGHTED: - { - return Fusion::FusionType::WEIGHTED; - } - default: - { - throw std::logic_error{ "Fusion types are only availalbe for Belief Constraint Conflict types, not for: " + - std::to_string(static_cast(conflict_type)) }; - } - } -} -template -inline typename OpinionT::FLOAT_t Conflict::function_switch(Conflict::ConflictType conflict_type, +template +inline typename OpinionT::FLOAT_t Conflict::function_switch(ConflictType conflict_type, std::vector opinions, std::optional> use_opinion) requires is_opinion or is_opinion_no_base @@ -232,18 +273,18 @@ inline typename OpinionT::FLOAT_t Conflict::function_switch(Conflict::ConflictTy { case ConflictType::ACCUMULATE: { - return accumulated_operator(opinions_used); + return accumulated_operator(RelationT, opinions_used); } case ConflictType::AVERAGE: { - return average_operator(opinions_used); + return average_operator(RelationT, opinions_used); } case ConflictType::BELIEF_CUMULATIVE: case ConflictType::BELIEF_BELIEF_CONSTRAINT: case ConflictType::BELIEF_AVERAGE: case ConflictType::BELIEF_WEIGHTED: { - return belief_conflict_operator(get_belief_fusion_type(conflict_type), opinions_used); + return belief_conflict_operator(RelationT, get_belief_fusion_type(conflict_type), opinions_used); } default: { @@ -253,24 +294,54 @@ inline typename OpinionT::FLOAT_t Conflict::function_switch(Conflict::ConflictTy } } +template +inline typename OpinionT::FLOAT_t Conflict::function_switch(ConflictType conflict_type, + Array opinions, + int skip) + requires is_opinion or is_opinion_no_base +{ + switch (conflict_type) + { + case ConflictType::ACCUMULATE: + { + return accumulated_operator(RelationT, opinions, skip); + } + case ConflictType::AVERAGE: + { + return average_operator(RelationT, opinions, skip); + } + // case ConflictType::BELIEF_CUMULATIVE: + // case ConflictType::BELIEF_BELIEF_CONSTRAINT: + // case ConflictType::BELIEF_AVERAGE: + // case ConflictType::BELIEF_WEIGHTED: + // { + // return belief_conflict_operator(RelationT, get_belief_fusion_type(conflict_type), opinions_used); + // } + default: + { + printf("conflict func not yet implemented"); + return typename OpinionT::FLOAT_t{}; + } + } +} + template -inline typename OpinionT::FLOAT_t Conflict::conflict(Conflict::ConflictType conflict_type, - std::initializer_list inputs) +inline typename OpinionT::FLOAT_t Conflict::conflict(ConflictType conflict_type, std::initializer_list inputs) requires is_opinion or is_opinion_no_base { return conflict(conflict_type, std::vector{ inputs }); } template -inline FirstType::type::FLOAT_t Conflict::conflict(Conflict::ConflictType conflict_type, - Opinions... opinions) +inline typename FirstType::type::FLOAT_t Conflict::conflict(ConflictType conflict_type, + Opinions... opinions) requires is_opinion_no_base_list or is_opinion_list { return conflict(conflict_type, { opinions... }); } template -inline typename OpinionT::FLOAT_t Conflict::conflict(Conflict::ConflictType conflict_type, +inline typename OpinionT::FLOAT_t Conflict::conflict(ConflictType conflict_type, std::vector opinions, std::optional> use_opinion) requires is_opinion or is_opinion_no_base @@ -278,24 +349,30 @@ inline typename OpinionT::FLOAT_t Conflict::conflict(Conflict::ConflictType conf return function_switch(conflict_type, opinions, use_opinion); } +template +inline typename OpinionT::FLOAT_t Conflict::conflict(ConflictType conflict_type, Array opinions, int skip) + requires is_opinion or is_opinion_no_base +{ + return function_switch(conflict_type, opinions, skip); +} + template -inline typename OpinionT::FLOAT_t Conflict::harmony(Conflict::ConflictType conflict_type, - std::initializer_list inputs) +inline typename OpinionT::FLOAT_t Conflict::harmony(ConflictType conflict_type, std::initializer_list inputs) requires is_opinion or is_opinion_no_base { return harmony(conflict_type, std::vector{ inputs }); } template -inline FirstType::type::FLOAT_t Conflict::harmony(Conflict::ConflictType conflict_type, - Opinions... opinions) +inline typename FirstType::type::FLOAT_t Conflict::harmony(ConflictType conflict_type, + Opinions... opinions) requires is_opinion_no_base_list or is_opinion_list { return harmony(conflict_type, { opinions... }); } template -inline typename OpinionT::FLOAT_t Conflict::harmony(Conflict::ConflictType conflict_type, +inline typename OpinionT::FLOAT_t Conflict::harmony(ConflictType conflict_type, std::vector opinions, std::optional> use_opinion) requires is_opinion or is_opinion_no_base @@ -303,8 +380,16 @@ inline typename OpinionT::FLOAT_t Conflict::harmony(Conflict::ConflictType confl return function_switch(conflict_type, opinions, use_opinion); } -template -typename OpinionT::FLOAT_t Conflict::accumulated_operator(std::vector opinions) +template +inline typename OpinionT::FLOAT_t Conflict::harmony(ConflictType conflict_type, Array opinions, int skip) + requires is_opinion or is_opinion_no_base +{ + return function_switch(conflict_type, opinions, skip); +} + +template +typename OpinionT::FLOAT_t Conflict::accumulated_operator(const RelationType relation_type, + std::vector opinions) requires is_opinion or is_opinion_no_base { if (opinions.size() < 2) @@ -317,7 +402,7 @@ typename OpinionT::FLOAT_t Conflict::accumulated_operator(std::vector { for (std::size_t idx_inner{ idx_outer + 1 }; idx_inner < opinions.size(); ++idx_inner) { - if constexpr (RelationT == RelationType::CONFLICT) + if (relation_type == RelationType::CONFLICT) { accumulated_conflict += opinions[idx_outer].degree_of_conflict(opinions[idx_inner]); } @@ -331,8 +416,44 @@ typename OpinionT::FLOAT_t Conflict::accumulated_operator(std::vector return accumulated_conflict; } -template -typename OpinionT::FLOAT_t Conflict::average_operator(std::vector opinions) +template +typename OpinionT::FLOAT_t Conflict::accumulated_operator(const RelationType relation_type, + const Array opinions, + const int skip) + requires is_opinion or is_opinion_no_base +{ + if constexpr (N < 2) + { + return 0; + } + + typename OpinionT::FLOAT_t accumulated_conflict{ 0 }; + constexpr_for<0, N>([&](const std::size_t idx_outer) constexpr { + if (idx_outer == skip) + return; + constexpr_for<0, N>([&](const std::size_t idx_inner) constexpr { + // this condition should be optimized out when the constexpr_for is unrolled + if (idx_inner >= idx_outer) + return; + // this condition is run time dependent + if (idx_inner == skip) + return; + + if (relation_type == RelationType::CONFLICT) + { + accumulated_conflict += opinions[idx_outer].degree_of_conflict(opinions[idx_inner]); + } + else + { + accumulated_conflict += opinions[idx_outer].degree_of_harmony(opinions[idx_inner]); + } + }); + }); + return accumulated_conflict; +} + +template +typename OpinionT::FLOAT_t Conflict::average_operator(const RelationType relation_type, std::vector opinions) requires is_opinion or is_opinion_no_base { std::size_t num_used = opinions.size(); @@ -341,31 +462,55 @@ typename OpinionT::FLOAT_t Conflict::average_operator(std::vector opin return 0; } - typename OpinionT::FLOAT_t accumulated_conflict = accumulated_operator(opinions); + typename OpinionT::FLOAT_t accumulated_conflict = accumulated_operator(relation_type, opinions); // integer division intended, since the number of connections must be an integer auto num_connections = static_cast((num_used * (num_used - 1)) / 2); return accumulated_conflict / num_connections; } -template -typename OpinionT::FLOAT_t Conflict::belief_conflict_operator(Fusion::FusionType reference_fusion_type, +template +typename OpinionT::FLOAT_t Conflict::average_operator(const RelationType relation_type, + const Array opinions, + const int skip) + requires is_opinion or is_opinion_no_base +{ + using FloatT = OpinionT::FLOAT_t; + if constexpr (N < 2) + { + return 0; + } + + FloatT accumulated_conflict = accumulated_operator(relation_type, opinions, skip); + // integer division intended, since the number of connections must be an integer + constexpr auto num_connections = (N * (N - 1)) / 2; + if (skip >= 0) + { + // number of connections are smaller by N-1 when an index was skipped + return FloatT{ accumulated_conflict / (num_connections - (N - 1)) }; + } + return FloatT{ accumulated_conflict / num_connections }; +} + +template +typename OpinionT::FLOAT_t Conflict::belief_conflict_operator(const RelationType relation_type, + FusionType reference_fusion_type, std::vector opinions) requires is_opinion or is_opinion_no_base { typename OpinionT::FLOAT_t avg_conflict = - std::get<2>(Conflict::belief_conflicts(reference_fusion_type, opinions)); + std::get<2>(Conflict::belief_conflicts(relation_type, reference_fusion_type, opinions)); return avg_conflict; } -template +template std::pair> -Conflict::conflict_shares(ConflictType conflict_type, std::vector opinions) +Conflict::conflict_shares(const RelationType relation_type, ConflictType conflict_type, std::vector opinions) requires is_opinion or is_opinion_no_base { const std::size_t number_ops = opinions.size(); double avg_conflict; - if constexpr (RelationT == RelationType::CONFLICT) + if (relation_type == RelationType::CONFLICT) { avg_conflict = Conflict::conflict(conflict_type, opinions); } @@ -386,7 +531,7 @@ Conflict::conflict_shares(ConflictType conflict_type, std::vector opin { use_opinions[idx] = false; double conflict_wo_self; - if constexpr (RelationT == RelationType::CONFLICT) + if (relation_type == RelationType::CONFLICT) { conflict_wo_self = Conflict::conflict(conflict_type, opinions, use_opinions); } @@ -402,9 +547,55 @@ Conflict::conflict_shares(ConflictType conflict_type, std::vector opin return { avg_conflict, conflict_shares }; } -template -typename std::tuple, typename OpinionT::FLOAT_t, typename OpinionT::FLOAT_t> -Conflict::belief_conflicts(Fusion::FusionType reference_fusion_type, +template +Array Conflict::conflict_shares(const RelationType relation_type, + ConflictType conflict_type, + Array opinions, + typename OpinionT::FLOAT_t& average_conflict_return) + requires is_opinion or is_opinion_no_base +{ + using FloatT = OpinionT::FLOAT_t; + + FloatT avg_conflict; + if (relation_type == RelationType::CONFLICT) + { + avg_conflict = Conflict::conflict(conflict_type, opinions); + } + else + { + avg_conflict = Conflict::harmony(conflict_type, opinions); + } + + if (avg_conflict < EPS_v) + { + average_conflict_return = 0; + return Array(FloatT{ 0. }); + } + + Array conflict_shares(FloatT{ 0. }); + + constexpr_for<0, N>([&](std::size_t idx) { + FloatT conflict_wo_self; + if (relation_type == RelationType::CONFLICT) + { + conflict_wo_self = Conflict::conflict(conflict_type, opinions, idx); + } + else + { + conflict_wo_self = Conflict::harmony(conflict_type, opinions, idx); + } + + conflict_shares[idx] = 1.0 - conflict_wo_self / avg_conflict; + }); + + average_conflict_return = avg_conflict; + return conflict_shares; +} + +template +std::tuple, typename OpinionT::FLOAT_t, typename OpinionT::FLOAT_t> +Conflict::belief_conflicts(const RelationType relation_type, + FusionType reference_fusion_type, std::vector opinions, std::optional reference_fusion) requires is_opinion or is_opinion_no_base @@ -424,7 +615,7 @@ Conflict::belief_conflicts(Fusion::FusionType reference_fusion_type, for (auto const& opinion : opinions) { typename OpinionT::FLOAT_t reference_conflict; - if constexpr (RelationT == RelationType::CONFLICT) + if (relation_type == RelationType::CONFLICT) { reference_conflict = reference.degree_of_conflict(opinion); } @@ -475,6 +666,26 @@ std::vector Conflict::uncertainty_differentials(std: return differentials; } +template +Array Conflict::uncertainty_differentials(const Array& opinions) + requires is_opinion or is_opinion_no_base +{ + using FloatT = typename OpinionT::FLOAT_t; + FloatT sum_of_uncertainty{ 0. }; + constexpr_for<0, N>([&](std::size_t idx) { sum_of_uncertainty += opinions[idx].uncertainty(); }); + + if (sum_of_uncertainty < EPS_v) + { + return Array(0.); + } + + Array differentials; + + constexpr_for<0, N>([&](std::size_t idx) { differentials[idx] = opinions[idx].uncertainty() / sum_of_uncertainty; }); + + return differentials; +} + template std::vector Conflict::uncertainty_differentials(std::vector opinions) @@ -483,4 +694,12 @@ Conflict::uncertainty_differentials(std::vector opinions) return uncertainty_differentials(TrustedOpinionT::extractTrusts(opinions)); } +template +Array +Conflict::uncertainty_differentials(const Array& opinions) + requires is_trusted_opinion +{ + return uncertainty_differentials(TrustedOpinionT::extractTrusts(opinions)); +} + } // namespace subjective_logic::multisource diff --git a/library/include/subjective_logic_lib/multi_source/fusion_operators.hpp b/library/include/subjective_logic_lib/multi_source/fusion_operators.hpp index c9dc9a7..fef41fa 100644 --- a/library/include/subjective_logic_lib/multi_source/fusion_operators.hpp +++ b/library/include/subjective_logic_lib/multi_source/fusion_operators.hpp @@ -18,21 +18,17 @@ #include #include "subjective_logic_lib/util.hpp" +#include "subjective_logic_lib/types/fusion_types.hpp" +#include "subjective_logic_lib/types/cuda_compatible_array.hpp" #include "subjective_logic_lib/opinions/opinion.hpp" +// used in later tests also for protected member functions + namespace subjective_logic::multisource { struct Fusion { - enum class FusionType : int - { - CUMULATIVE = 0, - BELIEF_CONSTRAINT, - AVERAGE, - WEIGHTED, - }; - template static inline OpinionT fuse_opinions(FusionType fusion_type, std::vector opinions) requires is_opinion or is_opinion_no_base; @@ -42,9 +38,12 @@ struct Fusion requires is_opinion or is_opinion_no_base; template - static inline FirstType::type fuse_opinions(FusionType fusion_type, Opinions... opinions) + static inline typename FirstType::type fuse_opinions(FusionType fusion_type, Opinions... opinions) requires is_opinion_no_base_list or is_opinion_list; + // template + // CUDA_AVAIL static inline OpinionT fuse_opinions(FusionType fusion_type, const Array& inputs); + protected: // FusionOperator is used internally, to type the functions for the specific operator implementation template @@ -52,11 +51,9 @@ struct Fusion OpinionT(const std::vector&, std::vector, typename OpinionT::FLOAT_t)>; /** - * preprocessing steps of all multi source fusion operators are combined in this function + * preprocessing steps of all multi-source fusion operators are combined in this function * in case that some opinions are dogmatic, the result is almost always just a mean of all dogmatic opinions, - * thus, in this case, the result is precalculated an returned by this function - * @tparam N - * @tparam FloatT + * thus, in this case, the result is precalculated a returned by this function * @param opinions * @return */ @@ -66,7 +63,7 @@ struct Fusion requires is_opinion or is_opinion_no_base; /** - * the prior is not handled by any multi source fusion model, instead it gets averaged over all available opinions + * the prior is not handled by any multi-source fusion model, instead it gets averaged over all available opinions * @tparam OpinionT * @param opinions * @return @@ -80,9 +77,8 @@ struct Fusion * this functions handles everything except the actual fusion operation * the given fusion operator is provided with precalculated uncertainties and the uncertainty product * further, using the preprocess_opinions function, dogmatic opinions are handled. - * @tparam N - * @tparam FloatT * @param opinions + * @param fusion_operator * @return */ template @@ -90,9 +86,7 @@ struct Fusion requires is_opinion or is_opinion_no_base; /** - * includes all operator specific calculations and is used together with fuse_opinions - * @tparam N - * @tparam FloatT + * includes all operator-specific calculations and is used together with fuse_opinions * @tparam OpinionT * @param opinions * @param uncertainties @@ -106,9 +100,7 @@ struct Fusion requires is_opinion or is_opinion_no_base; /** - * includes all operator specific calculations and is used together with fuse_opinions - * @tparam N - * @tparam FloatT + * includes all operator-specific calculations and is used together with fuse_opinions * @tparam OpinionT * @param opinions * @param uncertainties @@ -122,9 +114,7 @@ struct Fusion requires is_opinion or is_opinion_no_base; /** - * includes all operator specific calculations and is used together with fuse_opinions - * @tparam N - * @tparam FloatT + * includes all operator-specific calculations and is used together with fuse_opinions * @tparam OpinionT * @param opinions * @param uncertainties @@ -136,40 +126,110 @@ struct Fusion std::vector uncertainties, typename OpinionT::FLOAT_t uncertainty_product) requires is_opinion or is_opinion_no_base; + + // in order to allow cuda available generic lambda functions, the function access must be public + // necessity might vanish, when not using generic lambdas + // access for test function may be provided using a Test friend class +public: + // template + // using FusionOperatorArr = CUDA_AVAIL std::function< + // OpinionT(const Array&, Array, typename OpinionT::FLOAT_t)>; + + template + CUDA_AVAIL static inline OpinionT fuse_opinions(FusionType fusion_type, const Array& opinions) + requires is_opinion or is_opinion_no_base; + + /** + * the prior is not handled by any multi-source fusion model, instead it gets averaged over all available opinions + * this function implements a cuda ready implementation with a fixed number of opinions + * @tparam OpinionT + * @param opinions + * @return + */ + template + CUDA_AVAIL static inline typename OpinionT::BeliefType average_prior(const Array& opinions) + requires is_opinion; + + /** + * preprocessing steps of all multi-source fusion operators are combined in this function + * in case that some opinions are dogmatic, the result is almost always just a mean of all dogmatic opinions, + * thus, in this case, the result is precalculated a returned by this function. + * this function implements a cuda ready implementation with a fixed number of opinions + * @param opinions + * @return + */ + template + CUDA_AVAIL static inline bool preprocess_opinions(const Array& opinions, + Array& uncertainties) + requires is_opinion or is_opinion_no_base; + + /** + * includes all operator-specific calculations and is used together with fuse_opinions + * this function implements a cuda ready implementation with a fixed number of opinions + * @tparam OpinionT + * @param opinions + * @param uncertainties + * @param uncertainty_product + * @return + */ + template + CUDA_AVAIL static inline OpinionT cumulative_fusion_operator(const Array& opinions, + Array uncertainties, + typename OpinionT::FLOAT_t uncertainty_product) + requires is_opinion or is_opinion_no_base; + + /** + * fuse all opinions using a given fusion operator + * this functions handles everything except the actual fusion operation + * the given fusion operator is provided with precalculated uncertainties and the uncertainty product + * further, using the preprocess_opinions function, dogmatic opinions are handled. + * @param opinions + * @param fusion_operator + * @return + */ + template + CUDA_AVAIL static inline OpinionT fuse_opinions_(const Array& opinions, Func&& fusion_operator) + requires is_opinion or is_opinion_no_base; }; template -inline OpinionT Fusion::fuse_opinions(Fusion::FusionType fusion_type, std::initializer_list& inputs) +inline OpinionT Fusion::fuse_opinions(FusionType fusion_type, std::initializer_list& inputs) requires is_opinion or is_opinion_no_base { return fuse_opinions(fusion_type, std::vector{ inputs }); } template -inline FirstType::type Fusion::fuse_opinions(Fusion::FusionType fusion_type, Opinions... opinions) +inline typename FirstType::type Fusion::fuse_opinions(FusionType fusion_type, Opinions... opinions) requires is_opinion_no_base_list or is_opinion_list { - using OutType = FirstType::type; + using OutType = typename FirstType::type; return OutType{ fuse_opinions(fusion_type, std::vector{ opinions... }) }; } +// template +// CUDA_AVAIL inline OpinionT Fusion::fuse_opinions(FusionType fusion_type, const Array& inputs) { +// +// return inputs.at(N); +// } + template -inline OpinionT Fusion::fuse_opinions(Fusion::FusionType fusion_type, std::vector opinions) +inline OpinionT Fusion::fuse_opinions(FusionType fusion_type, std::vector opinions) requires is_opinion or is_opinion_no_base { switch (fusion_type) { case FusionType::CUMULATIVE: { - return fuse_opinions_(opinions, Fusion::cumulative_fusion_operator); + return fuse_opinions_(opinions, &Fusion::cumulative_fusion_operator); } case FusionType::BELIEF_CONSTRAINT: { - return fuse_opinions_(opinions, Fusion::belief_constraint_fusion_operator); + return fuse_opinions_(opinions, &Fusion::belief_constraint_fusion_operator); } case FusionType::AVERAGE: { - return fuse_opinions_(opinions, Fusion::average_fusion_operator); + return fuse_opinions_(opinions, &Fusion::average_fusion_operator); } default: { @@ -179,6 +239,33 @@ inline OpinionT Fusion::fuse_opinions(Fusion::FusionType fusion_type, std::vecto } } +template +inline OpinionT Fusion::fuse_opinions(FusionType fusion_type, const Array& opinions) + requires is_opinion or is_opinion_no_base +{ + switch (fusion_type) + { + case FusionType::CUMULATIVE: + { + return fuse_opinions_(opinions, Fusion::cumulative_fusion_operator); + } + // case FusionType::BELIEF_CONSTRAINT: + // { + // return fuse_opinions_(opinions, Fusion::belief_constraint_fusion_operator); + // } + // case FusionType::AVERAGE: + // { + // return fuse_opinions_(opinions, Fusion::average_fusion_operator); + // } + default: + { + // should not be used, it's more like spamming the console to tell the user somethings off + printf("fusion func not yet implemented"); + return OpinionT{}; + } + } +} + template inline typename OpinionT::BeliefType Fusion::average_prior(const std::vector& opinions) requires is_opinion @@ -200,6 +287,23 @@ inline typename OpinionT::BeliefType Fusion::average_prior(const std::vector +CUDA_AVAIL inline typename OpinionT::BeliefType Fusion::average_prior(const Array& opinions) + requires is_opinion +{ + using ReturnType = typename OpinionT::BeliefType; + ReturnType prior{ 0 }; + + constexpr_for<0, N>([&](std::size_t opin_idx) { + constexpr_for<0, OpinionT::SIZE>( + [&](std::size_t idx) { prior[idx] += opinions[opin_idx].prior_belief_masses()[idx]; }); + }); + + constexpr_for<0, OpinionT::SIZE>([&](std::size_t idx) { prior[idx] /= opinions.size(); }); + + return prior; +} + template inline std::tuple, std::optional> Fusion::preprocess_opinions(const std::vector& opinions) @@ -251,6 +355,25 @@ Fusion::preprocess_opinions(const std::vector& opinions) return std::make_tuple(std::move(uncertainties), std::nullopt); } +template +inline bool Fusion::preprocess_opinions(const Array& opinions, + Array& uncertainties) + requires is_opinion or is_opinion_no_base +{ + using InputArray = const Array; + + if constexpr (N == 1) + { + return false; + } + + constexpr_for<0, N>([&](std::size_t idx, InputArray& opins) { uncertainties[idx] = opins[idx].uncertainty(); }, + opinions); + + // todo(wodtko) check for dogmatic for cuda impl here + return true; +} + template OpinionT Fusion::fuse_opinions_(const std::vector& opinions, FusionOperator fusion_operator) requires is_opinion or is_opinion_no_base @@ -275,6 +398,32 @@ OpinionT Fusion::fuse_opinions_(const std::vector& opinions, FusionOpe return result; } +template +inline OpinionT Fusion::fuse_opinions_(const Array& opinions, Func&& fusion_operator) + requires is_opinion or is_opinion_no_base +{ + using FloatT = typename OpinionT::FLOAT_t; + Array uncertainties; + // auto [uncertainties, pre_result] = Fusion::preprocess_opinions(opinions); + if (not Fusion::preprocess_opinions(opinions, uncertainties)) + { + return opinions[0]; + } + + // the product of all uncertainties can later be used to calculate specific products + // by dividing the respective uncertainty which is omitted + FloatT uncert_prod{ 1. }; + constexpr_for<0, N>([&](std::size_t uncert_idx) { uncert_prod *= uncertainties[uncert_idx]; }); + + OpinionT result = fusion_operator(opinions, uncertainties, uncert_prod); + if constexpr (is_opinion) + { + result.prior_belief_masses() = Fusion::average_prior(opinions); + } + + return result; +} + template OpinionT Fusion::cumulative_fusion_operator(const std::vector& opinions, std::vector uncertainties, @@ -312,6 +461,34 @@ OpinionT Fusion::cumulative_fusion_operator(const std::vector& opinion return result; } +template +inline OpinionT Fusion::cumulative_fusion_operator(const Array& opinions, + Array uncertainties, + typename OpinionT::FLOAT_t uncertainty_product) + requires is_opinion or is_opinion_no_base +{ + using FloatT = typename OpinionT::FLOAT_t; + std::size_t n_elements = opinions.size(); + OpinionT result; + + // calculate the nominator, during calculation the belief_mass entries of the result are invalid + constexpr_for<0, OpinionT::SIZE>([&](std::size_t mass_idx) { + constexpr_for<0, N>([&](std::size_t opin_idx) { + result.belief_masses()[mass_idx] += + opinions[opin_idx].belief_masses()[mass_idx] * uncertainty_product / uncertainties[opin_idx]; + }); + }); + + FloatT denom{ 0. }; + constexpr_for<0, N>([&](std::size_t uncert_idx) { denom += uncertainty_product / uncertainties[uncert_idx]; }); + + denom -= (n_elements - 1) * uncertainty_product; + + constexpr_for<0, OpinionT::SIZE>([&, denom](std::size_t mass_idx) { result.belief_masses()[mass_idx] /= denom; }); + + return result; +} + template OpinionT Fusion::belief_constraint_fusion_operator(const std::vector& opinions, std::vector uncertainties, diff --git a/library/include/subjective_logic_lib/multi_source/trust_revision_operators.hpp b/library/include/subjective_logic_lib/multi_source/trust_revision_operators.hpp index fc86f1b..3156a82 100644 --- a/library/include/subjective_logic_lib/multi_source/trust_revision_operators.hpp +++ b/library/include/subjective_logic_lib/multi_source/trust_revision_operators.hpp @@ -9,6 +9,7 @@ #include #include "subjective_logic_lib/util.hpp" +#include "subjective_logic_lib/types/fusion_types.hpp" #include "subjective_logic_lib/opinions/opinion.hpp" #include "subjective_logic_lib/opinions/trusted_opinion.hpp" #include "subjective_logic_lib/multi_source/fusion_operators.hpp" @@ -21,34 +22,27 @@ namespace subjective_logic::multisource struct TrustRevision { - enum class TrustRevisionType : int - { - NORMAL, - HARMONY_NORMAL, - CONFLICT_SHARES, - CONFLICT_SHARES_ALLOW_NEGATIVE, - HARMONY_SHARES, - HARMONY_SHARES_ALLOW_NEGATIVE, - REFERENCE_FUSION, - HARMONY_REFERENCE_FUSION - }; - template static inline std::vector - revision_factors(TrustRevisionType trust_revision_type, - Conflict::ConflictType conflict_type, + revision_factors(RelationType relation_type, + TrustRevisionType trust_revision_type, + ConflictType conflict_type, std::initializer_list inputs) requires is_trusted_opinion; template static inline std::vector::type::FLOAT_t> - revision_factors(TrustRevisionType trust_revision_type, Conflict::ConflictType conflict_type, Opinions... opinions) + revision_factors(RelationType relation_type, + TrustRevisionType trust_revision_type, + ConflictType conflict_type, + Opinions... opinions) requires is_trusted_opinion_list and (sizeof...(Opinions) > 0); template static inline std::vector - revision_factors(TrustRevisionType trust_revision_type, - Conflict::ConflictType conflict_type, + revision_factors(RelationType relation_type, + TrustRevisionType trust_revision_type, + ConflictType conflict_type, std::vector opinions, std::optional> use_opinion = std::nullopt) requires is_trusted_opinion; @@ -57,7 +51,7 @@ struct TrustRevision // RevisionOperator is used internally, to type the functions for the specific operator implementation template using RevisionOperator = - std::function(Conflict::ConflictType conflict_type, + std::function(ConflictType conflict_type, const std::vector&)>; /** @@ -68,49 +62,77 @@ struct TrustRevision * @param opinions * @return */ - template + template static inline std::vector - normal_trust_revision(Conflict::ConflictType conflict_type, const std::vector& opinions) + normal_trust_revision(RelationType relation_type, + ConflictType conflict_type, + const std::vector& opinions) requires is_trusted_opinion; - template + template static inline std::vector - conflict_shares_trust_revision(Conflict::ConflictType conflict_type, + conflict_shares_trust_revision(RelationType relation_type, + ConflictType conflict_type, const std::vector& opinions, bool positive_scores_only = false) requires is_trusted_opinion; - template + template static inline std::vector - reference_fusion_trust_revision(Conflict::ConflictType conflict_type, + reference_fusion_trust_revision(RelationType relation_type, + ConflictType conflict_type, const std::vector& trusted_opinions) requires is_trusted_opinion; + + // in order to allow cuda available generic lambda functions, the function access must be public + // necessity might vanish, when not using generic lambdas + // access for test function may be provided using a Test friend class +public: + template + CUDA_AVAIL static inline Array + conflict_shares_trust_revision(RelationType relation_type, + ConflictType conflict_type, + const Array& opinions, + bool positive_scores_only = false) + requires is_trusted_opinion; + + template + CUDA_AVAIL static inline Array + revision_factors(RelationType relation_type, + TrustRevisionType trust_revision_type, + ConflictType conflict_type, + Array opinions) + requires is_trusted_opinion; }; template inline std::vector -TrustRevision::revision_factors(TrustRevisionType trust_revision_type, - Conflict::ConflictType conflict_type, +TrustRevision::revision_factors(RelationType relation_type, + TrustRevisionType trust_revision_type, + ConflictType conflict_type, std::initializer_list inputs) requires is_trusted_opinion { - return TrustRevision::revision_factors(trust_revision_type, conflict_type, std::vector{ inputs }); + return TrustRevision::revision_factors( + relation_type, trust_revision_type, conflict_type, std::vector{ inputs }); } template inline std::vector::type::FLOAT_t> -TrustRevision::revision_factors(TrustRevisionType trust_revision_type, - Conflict::ConflictType conflict_type, +TrustRevision::revision_factors(RelationType relation_type, + TrustRevisionType trust_revision_type, + ConflictType conflict_type, Opinions... opinions) requires is_trusted_opinion_list and (sizeof...(Opinions) > 0) { - return TrustRevision::revision_factors(trust_revision_type, conflict_type, { opinions... }); + return TrustRevision::revision_factors(relation_type, trust_revision_type, conflict_type, { opinions... }); } template inline std::vector -TrustRevision::revision_factors(TrustRevisionType trust_revision_type, - Conflict::ConflictType conflict_type, +TrustRevision::revision_factors(RelationType relation_type, + TrustRevisionType trust_revision_type, + ConflictType conflict_type, std::vector opinions, std::optional> use_opinion) requires is_trusted_opinion @@ -139,56 +161,70 @@ TrustRevision::revision_factors(TrustRevisionType trust_revision_type, { case TrustRevisionType::NORMAL: { - return normal_trust_revision(conflict_type, opinions_used); - } - case TrustRevisionType::HARMONY_NORMAL: - { - return normal_trust_revision(conflict_type, opinions_used); + return normal_trust_revision(relation_type, conflict_type, opinions_used); } - case TrustRevisionType::CONFLICT_SHARES: + case TrustRevisionType::SHARES: { - return conflict_shares_trust_revision( - conflict_type, opinions_used, true); + return conflict_shares_trust_revision(relation_type, conflict_type, opinions_used, true); } - case TrustRevisionType::CONFLICT_SHARES_ALLOW_NEGATIVE: + case TrustRevisionType::SHARES_ALLOW_NEGATIVE: { - return conflict_shares_trust_revision( - conflict_type, opinions_used, false); + return conflict_shares_trust_revision(relation_type, conflict_type, opinions_used, false); } - case TrustRevisionType::HARMONY_SHARES: + case TrustRevisionType::REFERENCE_FUSION: { - return conflict_shares_trust_revision( - conflict_type, opinions_used, true); + return reference_fusion_trust_revision(relation_type, conflict_type, opinions_used); } - case TrustRevisionType::HARMONY_SHARES_ALLOW_NEGATIVE: + default: { - return conflict_shares_trust_revision( - conflict_type, opinions_used, false); + throw std::logic_error{ "TrustRevision is not yet implemented for: " + + std::to_string(static_cast(trust_revision_type)) }; } - case TrustRevisionType::REFERENCE_FUSION: + } +} + +template +inline Array +TrustRevision::revision_factors(const RelationType relation_type, + const TrustRevisionType trust_revision_type, + const ConflictType conflict_type, + Array opinions) + requires is_trusted_opinion +{ + switch (trust_revision_type) + { + // case TrustRevisionType::NORMAL: + // { + // return normal_trust_revision(relation_type, conflict_type, opinions_used); + // } + case TrustRevisionType::SHARES: { - return reference_fusion_trust_revision(conflict_type, - opinions_used); + return conflict_shares_trust_revision(relation_type, conflict_type, opinions, true); } - case TrustRevisionType::HARMONY_REFERENCE_FUSION: + case TrustRevisionType::SHARES_ALLOW_NEGATIVE: { - return reference_fusion_trust_revision(conflict_type, - opinions_used); + return conflict_shares_trust_revision(relation_type, conflict_type, opinions, false); } + // case trustrevisiontype::reference_fusion: + // { + // return reference_fusion_trust_revision(relation_type, conflict_type, opinions_used); + // } default: { - throw std::logic_error{ "TrustRevision is not yet implemented for: " + - std::to_string(static_cast(trust_revision_type)) }; + printf("TrustRevision is not yet implemented"); + return Array{}; } } } -template +template inline std::vector -TrustRevision::normal_trust_revision(Conflict::ConflictType conflict_type, const std::vector& opinions) +TrustRevision::normal_trust_revision(const RelationType relation_type, + ConflictType conflict_type, + const std::vector& opinions) requires is_trusted_opinion { - using FloatT = typename TrustedOpinionT::FLOAT_t; + using FloatT = TrustedOpinionT::FLOAT_t; std::size_t num_ops{ opinions.size() }; std::vector discounted_opinions = @@ -196,7 +232,7 @@ TrustRevision::normal_trust_revision(Conflict::ConflictType conflict_type, const std::vector uncertainty_differentials = Conflict::uncertainty_differentials(opinions); FloatT conflict; - if constexpr (RelationT == Conflict::RelationType::CONFLICT) + if (relation_type == RelationType::CONFLICT) { conflict = Conflict::conflict(conflict_type, discounted_opinions); } @@ -210,7 +246,7 @@ TrustRevision::normal_trust_revision(Conflict::ConflictType conflict_type, const for (std::size_t idx{ 0 }; idx < num_ops; ++idx) { FloatT score = uncertainty_differentials[idx] * conflict; - if constexpr (RelationT == Conflict::RelationType::HARMONY) + if (relation_type == RelationType::HARMONY) { score *= -1; } @@ -220,31 +256,31 @@ TrustRevision::normal_trust_revision(Conflict::ConflictType conflict_type, const return revision_factors; } -template +template inline std::vector -TrustRevision::conflict_shares_trust_revision(Conflict::ConflictType conflict_type, +TrustRevision::conflict_shares_trust_revision(const RelationType relation_type, + ConflictType conflict_type, const std::vector& opinions, bool positive_scores_only) requires is_trusted_opinion { - using FloatT = typename TrustedOpinionT::FLOAT_t; + using FloatT = TrustedOpinionT::FLOAT_t; std::size_t num_ops{ opinions.size() }; auto raw_opinions = TrustedOpinionT::extractOpinions(opinions); std::vector discounted_opinions = TrustedOpinionT::extractDiscountedOpinions(opinions); - // it only makes sense to distribute conflict based on average fusion, thus conflict_shares are calculated using + // it only makes sense to distribute conflict based on average fusion; thus conflict_shares are calculated using // AVERAGE if, however, the demanded conflict type differs, the absolute overall conflict is calculated with the // respective conflict type - // auto [conflict, conflict_shares] = Conflict::conflict_shares(Conflict::ConflictType::AVERAGE, + // auto [conflict, conflict_shares] = Conflict::conflict_shares(ConflictType::AVERAGE, // discounted_opinions); - auto [conflict, conflict_shares] = - Conflict::conflict_shares(Conflict::ConflictType::AVERAGE, raw_opinions); - if (conflict_type != Conflict::ConflictType::AVERAGE) + auto [conflict, conflict_shares] = Conflict::conflict_shares(relation_type, ConflictType::AVERAGE, raw_opinions); + if (conflict_type != ConflictType::AVERAGE) { - if constexpr (RelationT == Conflict::RelationType::CONFLICT) + if (relation_type == RelationType::CONFLICT) { conflict = Conflict::conflict(conflict_type, discounted_opinions); } @@ -265,7 +301,7 @@ TrustRevision::conflict_shares_trust_revision(Conflict::ConflictType conflict_ty continue; } FloatT scaled_share = conflict * share; - if constexpr (RelationT == Conflict::RelationType::HARMONY) + if (relation_type == RelationType::HARMONY) { scaled_share *= -1; } @@ -275,13 +311,65 @@ TrustRevision::conflict_shares_trust_revision(Conflict::ConflictType conflict_ty return revision_factors; } -template +template +inline Array +TrustRevision::conflict_shares_trust_revision(const RelationType relation_type, + ConflictType conflict_type, + const Array& opinions, + bool positive_scores_only) + requires is_trusted_opinion +{ + using FloatT = TrustedOpinionT::FLOAT_t; + + auto raw_opinions = TrustedOpinionT::extractOpinions(opinions); + auto discounted_opinions = TrustedOpinionT::extractDiscountedOpinions(opinions); + + // it only makes sense to distribute conflict based on average fusion; thus conflict_shares are calculated using + // AVERAGE if, however, the demanded conflict type differs, the absolute overall conflict is calculated with the + // respective conflict type + + FloatT conflict{ 0. }; + Array conflict_shares = + Conflict::conflict_shares(relation_type, ConflictType::AVERAGE, raw_opinions, conflict); + if (conflict_type != ConflictType::AVERAGE) + { + if (relation_type == RelationType::CONFLICT) + { + conflict = Conflict::conflict(conflict_type, discounted_opinions); + } + else + { + conflict = Conflict::harmony(conflict_type, discounted_opinions); + } + } + + Array revision_factors; + constexpr_for<0, N>([&, conflict](std::size_t idx) { + FloatT share = conflict_shares[idx]; + if (positive_scores_only and share < 0) + { + revision_factors[idx] = 0; + return; + } + FloatT scaled_share{ conflict * share }; + if (relation_type == RelationType::HARMONY) + { + scaled_share *= -1; + } + revision_factors[idx] = scaled_share; + }); + + return revision_factors; +} + +template inline std::vector -TrustRevision::reference_fusion_trust_revision(Conflict::ConflictType conflict_type, +TrustRevision::reference_fusion_trust_revision(RelationType relation_type, + ConflictType conflict_type, const std::vector& trusted_opinions) requires is_trusted_opinion { - using FloatT = typename TrustedOpinionT::FLOAT_t; + using FloatT = TrustedOpinionT::FLOAT_t; std::size_t num_ops{ trusted_opinions.size() }; auto discounted_opinions = TrustedOpinionT::extractDiscountedOpinions(trusted_opinions); @@ -290,9 +378,9 @@ TrustRevision::reference_fusion_trust_revision(Conflict::ConflictType conflict_t auto opinions = TrustedOpinionT::extractOpinions(trusted_opinions); // use the discounted opinions for fusion here typename TrustedOpinionT::OpinionT reference_fusion = - Fusion::fuse_opinions(Conflict::get_belief_fusion_type(conflict_type), discounted_opinions); - auto [belief_conflicts, max_conflict, avg_conflict] = Conflict::belief_conflicts( - Conflict::get_belief_fusion_type(conflict_type), opinions, { reference_fusion }); + Fusion::fuse_opinions(get_belief_fusion_type(conflict_type), discounted_opinions); + auto [belief_conflicts, max_conflict, avg_conflict] = + Conflict::belief_conflicts(relation_type, get_belief_fusion_type(conflict_type), opinions, { reference_fusion }); #else auto [belief_conflicts, max_conflict, avg_conflict] = Conflict::belief_conflicts(Conflict::get_belief_fusion_type(conflict_type), discounted_opinions); @@ -313,7 +401,7 @@ TrustRevision::reference_fusion_trust_revision(Conflict::ConflictType conflict_t } FloatT revision_factor = max_conflict * relative_conflict / denom; - if constexpr (RelationT == Conflict::RelationType::HARMONY) + if (relation_type == RelationType::HARMONY) { revision_factor *= -1; } diff --git a/library/include/subjective_logic_lib/multi_source/trusted_fusion_operators.hpp b/library/include/subjective_logic_lib/multi_source/trusted_fusion_operators.hpp index bc6317c..8a65820 100644 --- a/library/include/subjective_logic_lib/multi_source/trusted_fusion_operators.hpp +++ b/library/include/subjective_logic_lib/multi_source/trusted_fusion_operators.hpp @@ -18,6 +18,7 @@ #include "subjective_logic_lib/util.hpp" #include "subjective_logic_lib/opinions/trusted_opinion.hpp" +#include "subjective_logic_lib/types/fusion_types.hpp" #include "subjective_logic_lib/multi_source/conflict_operators.hpp" #include "subjective_logic_lib/multi_source/fusion_operators.hpp" #include "subjective_logic_lib/multi_source/trust_revision_operators.hpp" @@ -27,37 +28,46 @@ namespace subjective_logic::multisource struct TrustedFusion { - using WeightedTypes = std::tuple; + // using WeightedTypes = std::tuple; + struct WeightedTypes + { + RelationType relation_type; + TrustRevisionType trust_revision_type; + ConflictType conflict_type; + double weight; + }; template - static inline TrustedOpinionT::OpinionT fuse_opinions(Fusion::FusionType fusion_type, - TrustRevision::TrustRevisionType trust_revision_type, - Conflict::ConflictType conflict_type, - const std::vector& trusted_opinions) + static inline typename TrustedOpinionT::OpinionT fuse_opinions(FusionType fusion_type, + RelationType relation_type, + TrustRevisionType trust_revision_type, + ConflictType conflict_type, + const std::vector& trusted_opinions) requires is_trusted_opinion; template - static inline TrustedOpinionT::OpinionT fuse_opinions(Fusion::FusionType fusion_type, - std::vector weighted_conflict_types, - const std::vector& trusted_opinions) + static inline typename TrustedOpinionT::OpinionT fuse_opinions(FusionType fusion_type, + std::vector weighted_conflict_types, + const std::vector& trusted_opinions) requires is_trusted_opinion; template - static inline TrustedOpinionT::OpinionT fuse_opinions(Fusion::FusionType fusion_type, - const std::vector& trusted_opinions) + static inline typename TrustedOpinionT::OpinionT fuse_opinions(FusionType fusion_type, + const std::vector& trusted_opinions) requires is_trusted_opinion; template - static inline TrustedOpinionT::OpinionT fuse_opinions_(Fusion::FusionType fusion_type, - TrustRevision::TrustRevisionType trust_revision_type, - Conflict::ConflictType conflict_type, - std::vector& trusted_opinions) + static inline typename TrustedOpinionT::OpinionT fuse_opinions_(FusionType fusion_type, + RelationType relation_type, + TrustRevisionType trust_revision_type, + ConflictType conflict_type, + std::vector& trusted_opinions) requires is_trusted_opinion; template - static inline TrustedOpinionT::OpinionT fuse_opinions_(Fusion::FusionType fusion_type, - std::vector weighted_conflict_types, - std::vector& trusted_opinions) + static inline typename TrustedOpinionT::OpinionT fuse_opinions_(FusionType fusion_type, + std::vector weighted_conflict_types, + std::vector& trusted_opinions) requires is_trusted_opinion; protected: @@ -74,44 +84,85 @@ struct TrustedFusion * @return */ template - static inline TrustedOpinionT::OpinionT fusion_calculation(Fusion::FusionType fusion_type, - std::vector weighted_types, - Vector trusted_opinions, - RevisionFunction&& revision_function) + static inline typename TrustedOpinionT::OpinionT fusion_calculation(FusionType fusion_type, + const std::vector& weighted_types, + Vector trusted_opinions, + RevisionFunction&& revision_function) + requires is_trusted_opinion; + + // in order to allow cuda available generic lambda functions, the function access must be public + // necessity might vanish, when not using generic lambdas + // access for test function may be provided using a Test friend class +public: + /** + * applies the fusion operation and can be used for inplace or copy based operation + * in order to allow const and non const types, additional template parameter have been added + * this function implements a cuda ready implementation with a fixed number of opinions + * @tparam TrustedOpinionT - trusted opinion type + * @tparam ArrayT - allows for using const and non const references + * @tparam RevisionFunction - revision function either uses const or non const types + * @param fusion_type + * @param weighted_types + * @param trusted_opinions + * @param revision_function + * @return + */ + template + CUDA_AVAIL static inline typename TrustedOpinionT::OpinionT + fusion_calculation(FusionType fusion_type, + const Array& weighted_types, + ArrayT trusted_opinions, + RevisionFunction&& revision_function) + requires is_trusted_opinion; + + template + CUDA_AVAIL static inline typename TrustedOpinionT::OpinionT + fuse_opinions(FusionType fusion_type, + Array weighted_conflict_types, + const Array& trusted_opinions) + requires is_trusted_opinion; + + template + CUDA_AVAIL static inline typename TrustedOpinionT::OpinionT + fuse_opinions_(FusionType fusion_type, + Array weighted_conflict_types, + Array& trusted_opinions) requires is_trusted_opinion; }; template -inline TrustedOpinionT::OpinionT TrustedFusion::fuse_opinions(Fusion::FusionType fusion_type, - TrustRevision::TrustRevisionType trust_revision_type, - Conflict::ConflictType conflict_type, - const std::vector& trusted_opinions) +inline typename TrustedOpinionT::OpinionT +TrustedFusion::fuse_opinions(FusionType fusion_type, + const RelationType relation_type, + const TrustRevisionType trust_revision_type, + const ConflictType conflict_type, + const std::vector& trusted_opinions) requires is_trusted_opinion { - WeightedTypes types = { trust_revision_type, conflict_type, 1.0 }; + WeightedTypes types{ relation_type, trust_revision_type, conflict_type, 1.0 }; return fuse_opinions(fusion_type, { types }, trusted_opinions); } template -inline TrustedOpinionT::OpinionT TrustedFusion::fuse_opinions_(Fusion::FusionType fusion_type, - TrustRevision::TrustRevisionType trust_revision_type, - Conflict::ConflictType conflict_type, - std::vector& trusted_opinions) +inline typename TrustedOpinionT::OpinionT TrustedFusion::fuse_opinions_(FusionType fusion_type, + const RelationType relation_type, + const TrustRevisionType trust_revision_type, + const ConflictType conflict_type, + std::vector& trusted_opinions) requires is_trusted_opinion { - WeightedTypes types = { trust_revision_type, conflict_type, 1.0 }; + WeightedTypes types{ relation_type, trust_revision_type, conflict_type, 1.0 }; return fuse_opinions_(fusion_type, { types }, trusted_opinions); } template -TrustedOpinionT::OpinionT TrustedFusion::fuse_opinions(Fusion::FusionType fusion_type, - std::vector weighted_conflict_types, - const std::vector& trusted_opinions) +typename TrustedOpinionT::OpinionT TrustedFusion::fuse_opinions(FusionType fusion_type, + std::vector weighted_conflict_types, + const std::vector& trusted_opinions) requires is_trusted_opinion { auto revision_function = [](const TrustedOpinionT& top, typename TrustedOpinionT::FLOAT_t revision_factor) { - TrustedOpinionT copy{ top.revise_trust(revision_factor) }; - return copy.discounted_opinion(); + return top.revise_trust(revision_factor).discounted_opinion(); }; return fusion_calculation&>( @@ -119,39 +170,65 @@ TrustedOpinionT::OpinionT TrustedFusion::fuse_opinions(Fusion::FusionType fusion } template -TrustedOpinionT::OpinionT TrustedFusion::fuse_opinions(Fusion::FusionType fusion_type, - const std::vector& trusted_opinions) +typename TrustedOpinionT::OpinionT TrustedFusion::fuse_opinions(FusionType fusion_type, + const std::vector& trusted_opinions) requires is_trusted_opinion { auto revision_function = [](const TrustedOpinionT& top, typename TrustedOpinionT::FLOAT_t revision_factor) { - TrustedOpinionT copy{ top.revise_trust(revision_factor) }; - return copy.discounted_opinion(); + return top.revise_trust(revision_factor).discounted_opinion(); }; return fusion_calculation&>( fusion_type, {}, trusted_opinions, revision_function); } +template +typename TrustedOpinionT::OpinionT TrustedFusion::fuse_opinions(FusionType fusion_type, + Array weighted_conflict_types, + const Array& trusted_opinions) + requires is_trusted_opinion +{ + auto revision_function = [](const TrustedOpinionT& top, typename TrustedOpinionT::FLOAT_t revision_factor) { + return top.revise_trust(revision_factor).discounted_opinion(); + }; + + return fusion_calculation( + fusion_type, weighted_conflict_types, trusted_opinions, revision_function); +} + template -TrustedOpinionT::OpinionT TrustedFusion::fuse_opinions_(Fusion::FusionType fusion_type, - std::vector weighted_conflict_types, - std::vector& trusted_opinions) +typename TrustedOpinionT::OpinionT TrustedFusion::fuse_opinions_(FusionType fusion_type, + std::vector weighted_conflict_types, + std::vector& trusted_opinions) requires is_trusted_opinion { auto revision_function = [](TrustedOpinionT& top, typename TrustedOpinionT::FLOAT_t revision_factor) { - top.revise_trust_(revision_factor); - return top.discounted_opinion(); + return top.revise_trust_(revision_factor).discounted_opinion(); }; return fusion_calculation&>( fusion_type, weighted_conflict_types, trusted_opinions, revision_function); } +template +typename TrustedOpinionT::OpinionT TrustedFusion::fuse_opinions_(FusionType fusion_type, + Array weighted_conflict_types, + Array& trusted_opinions) + requires is_trusted_opinion +{ + auto revision_function = [](TrustedOpinionT& top, typename TrustedOpinionT::FLOAT_t revision_factor) { + return top.revise_trust_(revision_factor).discounted_opinion(); + }; + + return fusion_calculation( + fusion_type, weighted_conflict_types, trusted_opinions, revision_function); +} + template -TrustedOpinionT::OpinionT TrustedFusion::fusion_calculation(Fusion::FusionType fusion_type, - std::vector weighted_types, - Vector trusted_opinions, - RevisionFunction&& revision_function) +typename TrustedOpinionT::OpinionT TrustedFusion::fusion_calculation(FusionType fusion_type, + const std::vector& weighted_types, + Vector trusted_opinions, + RevisionFunction&& revision_function) requires is_trusted_opinion { using FloatT = typename TrustedOpinionT::FloatT; @@ -160,10 +237,10 @@ TrustedOpinionT::OpinionT TrustedFusion::fusion_calculation(Fusion::FusionType f // in case that the list of types is empty, there is simply no trust revision std::vector weighted_revision_factors(number_ops, 0.); - for (auto [trust_revision_type, conflict_type, weight] : weighted_types) + for (auto [relation_type, trust_revision_type, conflict_type, weight] : weighted_types) { std::vector revision_factors = - TrustRevision::revision_factors(trust_revision_type, conflict_type, trusted_opinions); + TrustRevision::revision_factors(relation_type, trust_revision_type, conflict_type, trusted_opinions); for (std::size_t op_idx{ 0 }; op_idx < number_ops; ++op_idx) { @@ -182,4 +259,30 @@ TrustedOpinionT::OpinionT TrustedFusion::fusion_calculation(Fusion::FusionType f return Fusion::fuse_opinions(fusion_type, discounted_opinions); } +template +typename TrustedOpinionT::OpinionT TrustedFusion::fusion_calculation(FusionType fusion_type, + const Array& weighted_types, + ArrayT trusted_opinions, + RevisionFunction&& revision_function) + requires is_trusted_opinion +{ + using FloatT = TrustedOpinionT::FloatT; + using OpinionT = TrustedOpinionT::OpinionT; + + Array weighted_revision_factors; + constexpr_for<0, M>([&](std::size_t idx) { + auto [relation_type, trust_revision_type, conflict_type, weight] = weighted_types[idx]; + Array revision_factors = + TrustRevision::revision_factors(relation_type, trust_revision_type, conflict_type, trusted_opinions); + weighted_revision_factors += weight * revision_factors; + }); + + Array discounted_opinions; + constexpr_for<0, N>([&](std::size_t idx) { + discounted_opinions[idx] = revision_function(trusted_opinions[idx], weighted_revision_factors[idx]); + }); + + return Fusion::fuse_opinions(fusion_type, discounted_opinions); +} + } // namespace subjective_logic::multisource diff --git a/library/include/subjective_logic_lib/opinions/opinion.hpp b/library/include/subjective_logic_lib/opinions/opinion.hpp index 8a23e1e..4d8925d 100644 --- a/library/include/subjective_logic_lib/opinions/opinion.hpp +++ b/library/include/subjective_logic_lib/opinions/opinion.hpp @@ -21,6 +21,9 @@ using Trust = Opinion<2, FloatT>; template class DirichletDistribution; +template +using QuantizedOpinion = Opinion>; + /** * @brief this class is meant to be used in large arrays and, * thus, size and fast access are sometime more important than an easy and intuitive use. @@ -40,6 +43,11 @@ template class Opinion { public: + using QuantizedT = QuantizedOpinion; + using DeQuantizedT = Opinion>; + static constexpr bool is_quantized = is_quantized_type; + static constexpr bool is_not_quantized = (not is_quantized); + using NoBaseType = OpinionNoBase; using FLOAT_t = typename NoBaseType::FLOAT_t; static constexpr std::size_t SIZE = NoBaseType::SIZE; @@ -69,6 +77,17 @@ class Opinion CUDA_AVAIL constexpr Opinion(NoBaseType opinion_no_base, BeliefType prior); + /** + * @brief creates an Opinion from either a quantized or normal version of it + * thus, it may either be a usual copy ctor, or the conversion between quantized and dequantized + * @param other_opinion + */ + template + CUDA_AVAIL explicit constexpr Opinion(T other_opinion) + requires( + // either of both will always be the same as the current OpinionNoBase + std::is_same_v, QuantizedT> or std::is_same_v, DeQuantizedT>); + /** * @brief allows to use the ctor with the correct number of arguments instead of requiring an initializer list * when init an opinion like this, the prior is set to a neutral belief distr (see parent class) @@ -100,6 +119,13 @@ class Opinion constexpr Opinion(NoBaseType opinion_no_base, FloatT prior) requires is_binomial; + /** + * @brief convenience conversion between Opinions and DirichletDistributions + */ + template + CUDA_AVAIL constexpr explicit Opinion(DirichletDistribution dirichlet) + requires std::is_convertible_v; + /** * @brief default copy ctor * @param other @@ -130,7 +156,21 @@ class Opinion /** * @brief checks for the validity of OpinionNoBase and sum of prior values to be 1. */ - constexpr bool is_valid() const; + [[nodiscard]] constexpr bool is_valid() const; + + /** + * @brief returns a quantized opinion that uses a 1-byte float representation + */ + CUDA_AVAIL + constexpr QuantizedT get_quantized() const + requires(is_not_quantized); + + /** + * @brief returns a dequantized opinion + */ + CUDA_AVAIL + constexpr DeQuantizedT get_dequantized() const + requires(is_quantized); /** * @brief interface to the base class which allows public access of protected inherited members @@ -683,7 +723,7 @@ class Opinion * @brief converts opinion to a Dirichlet distribution while preserving prior and evidence */ CUDA_AVAIL - constexpr operator DirichletDistribution() const; + constexpr explicit operator DirichletDistribution() const; /** * @brief generates a readable string containing the belief masses and the uncertainty @@ -722,6 +762,16 @@ constexpr Opinion::Opinion(NoBaseType opinion_no_base, BeliefType pri { } +template +template +constexpr Opinion::Opinion(T other_opinion) + requires( + // either of both will always be the same as the current OpinionNoBase + std::is_same_v, QuantizedT> or std::is_same_v, DeQuantizedT>) + : Opinion(BeliefType{ other_opinion.belief_masses() }, BeliefType{ other_opinion.prior_belief_masses() }) +{ +} + template template constexpr Opinion::Opinion(VALUES... values) @@ -755,6 +805,20 @@ constexpr bool Opinion::is_valid() const return base_valid and valid_entries and prior_sum > 1.0 - EPS_v and prior_sum < 1.0 + EPS_v; } +template +constexpr QuantizedOpinion Opinion::get_quantized() const + requires(is_not_quantized) +{ + return QuantizedT(opinion_no_base_.get_quantized(), static_cast(prior_)); +} + +template +constexpr Opinion::DeQuantizedT Opinion::get_dequantized() const + requires(is_quantized) +{ + return DeQuantizedT(opinion_no_base_.get_dequantized(), static_cast(prior_)); +} + template constexpr inline FloatT Opinion::uncertainty() const { @@ -842,7 +906,8 @@ constexpr Opinion Opinion::NeutralBeliefOpinion() template constexpr Opinion Opinion::VacuousBeliefOpinion() { - return Opinion{ NoBaseType::VacuousBeliefDistr(), NoBaseType::VacuousBeliefDistr() }; + // prior must sum up to 1. + return Opinion{ NoBaseType::VacuousBeliefDistr(), NoBaseType::NeutralBeliefDistr() }; } template @@ -1064,13 +1129,15 @@ constexpr Opinion Opinion::comultiply(Opinion o template constexpr Opinion& Opinion::cum_fuse_(Opinion other) { + using std::abs; + FloatT uncert_this = uncertainty(); FloatT uncert_other = other.uncertainty(); opinion_no_base_.cum_fuse_(other.opinion_no_base_); FloatT denom = uncert_this + uncert_other - 2. * uncert_this * uncert_other; - if (std::abs(denom) < EPS_v) + if (abs(denom) < EPS_v) { constexpr_for<0, N, 1>([this, other](std::size_t idx) { prior_[idx] = (prior_[idx] + other.prior_[idx]) / 2.; }); return *this; @@ -1114,13 +1181,14 @@ constexpr FloatT Opinion::conflict(Opinion other) const template constexpr Opinion& Opinion::bc_fuse_(Opinion other) { + using std::abs; FloatT uncert_this = uncertainty(); FloatT uncert_other = other.uncertainty(); opinion_no_base_.bc_fuse_(other.opinion_no_base_); FloatT denom = 2 - uncert_this - uncert_other; - if (std::abs(denom) < EPS_v) + if (abs(denom) < EPS_v) { constexpr_for<0, N, 1>([&](std::size_t idx) { prior_[idx] = (prior_[idx] + other.prior_[idx]) / 2.; }); return *this; @@ -1155,13 +1223,15 @@ constexpr Opinion Opinion::average_fuse(Opinion other) con template constexpr Opinion& Opinion::wb_fuse_(Opinion other) { + using std::abs; + FloatT uncert_this = uncertainty(); FloatT uncert_other = other.uncertainty(); opinion_no_base_.wb_fuse_(other.opinion_no_base_); FloatT denom = 2 - uncert_this - uncert_other; - if (std::abs(denom) < EPS_v) + if (abs(denom) < EPS_v) { constexpr_for<0, N, 1>([&](std::size_t idx) { prior_[idx] = (prior_[idx] + other.prior_[idx]) / 2.; }); return *this; @@ -1323,12 +1393,7 @@ Opinion constexpr Opinion::getReducedOpinion( template constexpr bool Opinion::operator==(const Opinion& other) const { - FloatT diff{ 0. }; - for (std::size_t idx{ 0 }; idx < N; ++idx) - { - diff += std::abs(prior_[idx] - other.prior_[idx]); - } - return diff < EPS_v and opinion_no_base_ == other.opinion_no_base_; + return prior_ == other.prior_ and opinion_no_base_ == other.opinion_no_base_; } template @@ -1341,27 +1406,27 @@ inline std::ostream& operator<<(std::ostream& out, Opinion const& opi template std::string Opinion::to_string() const { + using std::to_string; if constexpr (is_binomial) { - std::string opinion = std::string{ "[bel: " } + std::to_string(belief()) + - "; disbel: " + std::to_string(disbelief()) + - "; uncertainty: " + std::to_string(uncertainty()) + "]"; - std::string prior = std::string{ "[bel: " } + std::to_string(prior_belief()) + - "; disbel: " + std::to_string(prior_disbelief()) + "]"; + std::string opinion = std::string{ "[bel: " } + to_string(belief()) + "; disbel: " + to_string(disbelief()) + + "; uncertainty: " + to_string(uncertainty()) + "]"; + std::string prior = + std::string{ "[bel: " } + to_string(prior_belief()) + "; disbel: " + to_string(prior_disbelief()) + "]"; return std::string{ "opinion: " } + opinion + " | prior: " + prior; } std::string opinion{ "[bel masses: " }; for (const auto& mass : this->belief_masses()) { - opinion += std::to_string(mass) + ", "; + opinion += to_string(mass) + ", "; } - opinion += "uncertainty: " + std::to_string(uncertainty()) + "]"; + opinion += "uncertainty: " + to_string(uncertainty()) + "]"; std::string prior{ "[bel masses: " }; for (const auto& mass : this->prior_) { - prior += std::to_string(mass) + ", "; + prior += to_string(mass) + ", "; } prior += "]"; @@ -1375,3 +1440,5 @@ Opinion::operator std::string() const } } // namespace subjective_logic + +#include "subjective_logic_lib/types/convert.hpp" \ No newline at end of file diff --git a/library/include/subjective_logic_lib/opinions/opinion_no_base.hpp b/library/include/subjective_logic_lib/opinions/opinion_no_base.hpp index bd4b1a6..d9366e1 100644 --- a/library/include/subjective_logic_lib/opinions/opinion_no_base.hpp +++ b/library/include/subjective_logic_lib/opinions/opinion_no_base.hpp @@ -13,6 +13,7 @@ #include "subjective_logic_lib/util.hpp" #include "subjective_logic_lib/types/cuda_compatible_array.hpp" +#include "subjective_logic_lib/types/limited_float.hpp" namespace subjective_logic { @@ -21,6 +22,12 @@ namespace subjective_logic template class DirichletDistribution; +template +class OpinionNoBase; + +template +using QuantizedOpinionNoBase = OpinionNoBase>; + /** * @brief this class is meant to be used in large arrays and, * thus, size and fast access are sometime more important than an easy and intuitive use. @@ -40,6 +47,11 @@ template class OpinionNoBase { public: + using QuantizedT = QuantizedOpinionNoBase; + using DeQuantizedT = OpinionNoBase>; + static constexpr bool is_quantized = is_quantized_type; + static constexpr bool is_not_quantized = (not is_quantized); + using BeliefType = Array; // helper to have accessible types/values when used from outside; @@ -58,6 +70,17 @@ class OpinionNoBase CUDA_AVAIL explicit constexpr OpinionNoBase(BeliefType belief_masses); + /** + * @brief creates a OpinionNoBase from either a quantized or normal version of it + * thus, it may either be a usual copy ctor, or the conversion between quantized and dequantized + * @param other_opinion + */ + template + CUDA_AVAIL explicit constexpr OpinionNoBase(T other_opinion) + requires( + // either of both will always be the same as the current OpinionNoBase + std::is_same_v, QuantizedT> or std::is_same_v, DeQuantizedT>); + /** * @brief allows to use the ctor with the correct number of arguments instead of requiring an initializer list */ @@ -65,6 +88,14 @@ class OpinionNoBase CUDA_AVAIL constexpr explicit OpinionNoBase(VALUES... values) requires(is_arithmetic_list and sizeof...(VALUES) == N); + /** + * @brief convenience conversion between Opinions and DirichletDistributions + * prior is implicitly ignored. + */ + template + CUDA_AVAIL constexpr explicit OpinionNoBase(DirichletDistribution dirichlet) + requires std::is_convertible_v; + /** * @brief default copy ctor * @param other @@ -97,17 +128,32 @@ class OpinionNoBase /** * @brief checks for the sum of belief_masses to be 1 up to an error of EPS */ - constexpr bool is_valid() const; + CUDA_AVAIL + [[nodiscard]] constexpr bool is_valid() const; + + /** + * @brief returns a quantized opinion that uses a 1-byte float representation + */ + CUDA_AVAIL + constexpr QuantizedT get_quantized() const + requires(is_not_quantized); + + /** + * @brief returns a dequantized opinion + */ + CUDA_AVAIL + constexpr DeQuantizedT get_dequantized() const + requires(is_quantized); /** - * @brief allows a convenient access to the belief_distribution since it is inherited its access must be explicitly + * @brief allows convenient access to the belief_distribution since it is inherited its access must be explicitly * defined * @return */ CUDA_AVAIL constexpr BeliefType& belief_masses(); /** - * @brief allows a convenient access to the belief_distribution since it is inherited its access must be explicitly + * @brief allows convenient access to the belief_distribution since it is inherited its access must be explicitly * defined * @return */ @@ -115,7 +161,7 @@ class OpinionNoBase constexpr const BeliefType& belief_masses() const; /** - * @brief allows a convenient access to a belief_distribution entry + * @brief allows convenient access to a belief_distribution entry * defined * @return */ @@ -123,7 +169,7 @@ class OpinionNoBase constexpr FloatT& belief_mass(std::size_t idx); /** - * @brief allows a convenient access to a belief_distribution entry + * @brief allows convenient access to a belief_distribution entry * defined * @return */ @@ -196,7 +242,7 @@ class OpinionNoBase constexpr OpinionNoBase interpolate(OpinionNoBase other, FloatT interp_fac) const; /** - * @brief a neutral distribution assigns the an equal amount of evidence to each belief_mass entry + * @brief a neutral distribution assigns an equal amount of evidence to each belief_mass entry * the resulting belief distribution sums up to 1, i.e., if interpreted as an Opinion, the uncertainty would be * 0. * @return a neutral opinion @@ -259,7 +305,7 @@ class OpinionNoBase * @return the projected probability */ CUDA_AVAIL - constexpr FloatT getBinomialProjection(FloatT base_rate = 0.5) const + constexpr FloatT getBinomialProjection(FloatT base_rate = FloatT{ 0.5 }) const requires is_binomial; /** @@ -373,7 +419,9 @@ class OpinionNoBase * @return multiplied opinion */ CUDA_AVAIL - constexpr OpinionNoBase& multiply_(OpinionNoBase other, FloatT base_this = 0.5, FloatT base_other = 0.5) + constexpr OpinionNoBase& multiply_(OpinionNoBase other, + FloatT base_this = FloatT{ 0.5 }, + FloatT base_other = FloatT{ 0.5 }) requires is_binomial; /** @@ -384,25 +432,35 @@ class OpinionNoBase * @return multiplied opinion */ CUDA_AVAIL - constexpr OpinionNoBase multiply(OpinionNoBase other, FloatT base_this = 0.5, FloatT base_other = 0.5) const + constexpr OpinionNoBase multiply(OpinionNoBase other, + FloatT base_this = FloatT{ 0.5 }, + FloatT base_other = FloatT{ 0.5 }) const requires is_binomial; /** * @brief applies the concept of comultiplication of [1] inplace * @param other + * @param base_this + * @param base_other * @return comultiplied opinion */ CUDA_AVAIL - constexpr OpinionNoBase& comultiply_(OpinionNoBase other, FloatT base_this = 0.5, FloatT base_other = 0.5) + constexpr OpinionNoBase& comultiply_(OpinionNoBase other, + FloatT base_this = FloatT{ 0.5 }, + FloatT base_other = FloatT{ 0.5 }) requires is_binomial; /** * @brief applies the concept of comultiplication of [1] using a copy * @param other + * @param base_this + * @param base_other * @return comultiplied opinion */ CUDA_AVAIL - constexpr OpinionNoBase comultiply(OpinionNoBase other, FloatT base_this = 0.5, FloatT base_other = 0.5) const + constexpr OpinionNoBase comultiply(OpinionNoBase other, + FloatT base_this = FloatT{ 0.5 }, + FloatT base_other = FloatT{ 0.5 }) const requires is_binomial; /** @@ -540,7 +598,7 @@ class OpinionNoBase constexpr OpinionNoBase moment_matching_update(BeliefType probabilities) const; /** - * @brief applies the concept of trust discounting of [1] inplace, when using a Opinion for discounting its base rate + * @brief applies the concept of trust discounting of [1] inplace, when using an Opinion for discounting its base rate * must be available the projected probability of the other trust Opinion tells, how much information is kept. i.e., * prop=1 means no discounting, prop=0 means the output has an uncertainty of 1. * @param other @@ -548,16 +606,16 @@ class OpinionNoBase * @return the trust discounted opinion */ CUDA_AVAIL - constexpr OpinionNoBase& trust_discount_(OpinionNoBase<2, FloatT> other, FloatT base_rate = 0.5); + constexpr OpinionNoBase& trust_discount_(OpinionNoBase<2, FloatT> other, FloatT base_rate = FloatT{ 0.5 }); /** - * @brief applies the concept of trust discounting of [1] using a copy, when using a Opinion for discounting its base + * @brief applies the concept of trust discounting of [1] using a copy, when using an Opinion for discounting its base * rate must be available * @param other * @param base_rate * @return the trust discounted opinion */ CUDA_AVAIL - constexpr OpinionNoBase trust_discount(OpinionNoBase<2, FloatT> other, FloatT base_rate = 0.5) const; + constexpr OpinionNoBase trust_discount(OpinionNoBase<2, FloatT> other, FloatT base_rate = FloatT{ 0.5 }) const; /** * @brief applies the concept of trust discounting of [1] inplace * the probability is interpreted as the projected probability of a trust opinion @@ -589,7 +647,7 @@ class OpinionNoBase CUDA_AVAIL constexpr OpinionNoBase& limited_trust_discount_(FloatT limit, OpinionNoBase<2, FloatT> other, - FloatT base_rate = 0.5); + FloatT base_rate = FloatT{ 0.5 }); /** * @brief applies the concept of trust discounting of [1] using a copy * in addition to the trust discount a limit for the resulting uncertainty can be set. @@ -604,7 +662,7 @@ class OpinionNoBase CUDA_AVAIL constexpr OpinionNoBase limited_trust_discount(FloatT limit, OpinionNoBase<2, FloatT> other, - FloatT base_rate = 0.5) const; + FloatT base_rate = FloatT{ 0.5 }) const; /** * @brief applies the concept of trust discounting of [1] inplace * the probability is interpreted as the projected probability of a trust opinion @@ -714,7 +772,7 @@ class OpinionNoBase * @brief converts opinion_no_base to a Dirichlet distribution preserving evidence and using a non informative prior */ CUDA_AVAIL - constexpr operator DirichletDistribution() const; + constexpr explicit operator DirichletDistribution() const; /** * @brief generates a readable string containing the belief masses and the uncertainty @@ -728,14 +786,30 @@ class OpinionNoBase */ [[nodiscard]] std::string to_string() const; + /** + * @brief generates a readable char output given a suitable input buffer + * @return + */ + CUDA_AVAIL + void to_char_buffer(char* buffer, std::size_t max_length) const; + protected: // belief distribution represents the evidence for each hypothesis of the considered domain BeliefType belief_masses_; }; template -constexpr OpinionNoBase::OpinionNoBase(OpinionNoBase::BeliefType belief_masses) - : belief_masses_{ belief_masses } +constexpr OpinionNoBase::OpinionNoBase(BeliefType belief_masses) : belief_masses_{ belief_masses } +{ +} + +template +template +constexpr OpinionNoBase::OpinionNoBase(T other_opinion) + requires( + // either of both will always be the same as the current OpinionNoBase + std::is_same_v, QuantizedT> or std::is_same_v, DeQuantizedT>) + : OpinionNoBase{ BeliefType(other_opinion.belief_masses()) } { } @@ -756,6 +830,20 @@ constexpr bool OpinionNoBase::is_valid() const return valid_entries and belief_masses_.sum() < static_cast(1.0) + EPS_v; } +template +constexpr QuantizedOpinionNoBase OpinionNoBase::get_quantized() const + requires(is_not_quantized) +{ + return QuantizedT(static_cast(belief_masses_)); +} + +template +constexpr OpinionNoBase::DeQuantizedT OpinionNoBase::get_dequantized() const + requires(is_quantized) +{ + return DeQuantizedT(static_cast(belief_masses_)); +} + template constexpr const FloatT& OpinionNoBase::belief() const requires is_binomial @@ -819,7 +907,7 @@ constexpr FloatT OpinionNoBase::belief_mass(std::size_t idx) const template constexpr FloatT OpinionNoBase::uncertainty() const { - return static_cast(1.0) - belief_masses_.sum(); + return FloatT{ static_cast(1.0) - belief_masses_.sum() }; } template @@ -845,7 +933,7 @@ constexpr OpinionNoBase OpinionNoBase::interpolate(Opinion template constexpr typename OpinionNoBase::BeliefType OpinionNoBase::NeutralBeliefDistr() { - return BeliefType{ static_cast(1.0) / N }; + return BeliefType{ FloatT{ static_cast(1.0) / N } }; } template @@ -928,7 +1016,7 @@ template constexpr FloatT OpinionNoBase::getBinomialProjection(FloatT base_rate) const requires is_binomial { - return belief() + uncertainty() * base_rate; + return FloatT{ belief() + uncertainty() * base_rate }; } template @@ -945,7 +1033,7 @@ template constexpr FloatT OpinionNoBase::uncertainty_differential(OpinionNoBase other) const { FloatT uncert{ this->uncertainty() }; - return uncert / (uncert + other.uncertainty()); + return FloatT{ uncert / (uncert + other.uncertainty()) }; } template @@ -954,11 +1042,12 @@ constexpr FloatT OpinionNoBase::degree_of_conflict(OpinionNoBase othe FloatT base_rate_other) const requires is_binomial { - FloatT proj_prob_distance = - std::abs(this->getBinomialProjection(base_rate) - other.getBinomialProjection(base_rate_other)); - FloatT conjunctive_certainty = (1 - this->uncertainty()) * (1 - other.uncertainty()); + using std::abs; + FloatT proj_prob_distance{ abs(this->getBinomialProjection(base_rate) - + other.getBinomialProjection(base_rate_other)) }; + FloatT conjunctive_certainty{ (1 - this->uncertainty()) * (1 - other.uncertainty()) }; - return proj_prob_distance * conjunctive_certainty; + return FloatT{ proj_prob_distance * conjunctive_certainty }; } template @@ -966,6 +1055,7 @@ constexpr FloatT OpinionNoBase::degree_of_conflict(OpinionNoBase othe BeliefType base_rate, BeliefType base_rate_other) const { + using std::abs; if constexpr (is_binomial) { return degree_of_conflict(other, base_rate.front(), base_rate_other.front()); @@ -974,12 +1064,12 @@ constexpr FloatT OpinionNoBase::degree_of_conflict(OpinionNoBase othe BeliefType prob_this = getProjection(base_rate); BeliefType prob_other = other.getProjection(base_rate); - constexpr_for<0, N, 1>([&](std::size_t idx) { proj_prob_distance += std::abs(prob_this[idx] - prob_other[idx]); }); + constexpr_for<0, N, 1>([&](std::size_t idx) { proj_prob_distance += abs(prob_this[idx] - prob_other[idx]); }); proj_prob_distance /= 2.; - FloatT conjunctive_certainty = (1 - this->uncertainty()) * (1 - other.uncertainty()); + FloatT conjunctive_certainty{ (1 - this->uncertainty()) * (1 - other.uncertainty()) }; - return proj_prob_distance * conjunctive_certainty; + return FloatT{ proj_prob_distance * conjunctive_certainty }; } template @@ -988,11 +1078,12 @@ constexpr FloatT OpinionNoBase::degree_of_harmony(OpinionNoBase other FloatT base_rate_other) const requires is_binomial { - FloatT proj_prob_distance = - std::abs(this->getBinomialProjection(base_rate) - other.getBinomialProjection(base_rate_other)); - FloatT conjunctive_certainty = (1 - this->uncertainty()) * (1 - other.uncertainty()); + using std::abs; + FloatT proj_prob_distance{ abs(this->getBinomialProjection(base_rate) - + other.getBinomialProjection(base_rate_other)) }; + FloatT conjunctive_certainty{ (1 - this->uncertainty()) * (1 - other.uncertainty()) }; - return (1 - proj_prob_distance) * conjunctive_certainty; + return FloatT{ (1 - proj_prob_distance) * conjunctive_certainty }; } template @@ -1000,6 +1091,7 @@ constexpr FloatT OpinionNoBase::degree_of_harmony(OpinionNoBase other BeliefType base_rate, BeliefType base_rate_other) const { + using std::abs; if constexpr (is_binomial) { return degree_of_conflict(other, base_rate.front(), base_rate_other.front()); @@ -1008,12 +1100,12 @@ constexpr FloatT OpinionNoBase::degree_of_harmony(OpinionNoBase other BeliefType prob_this = getProjection(base_rate); BeliefType prob_other = other.getProjection(base_rate); - constexpr_for<0, N, 1>([&](std::size_t idx) { proj_prob_distance += std::abs(prob_this[idx] - prob_other[idx]); }); + constexpr_for<0, N, 1>([&](std::size_t idx) { proj_prob_distance += abs(prob_this[idx] - prob_other[idx]); }); proj_prob_distance /= 2.; - FloatT conjunctive_certainty = (1 - this->uncertainty()) * (1 - other.uncertainty()); + FloatT conjunctive_certainty{ (1 - this->uncertainty()) * (1 - other.uncertainty()) }; - return (1 - proj_prob_distance) * conjunctive_certainty; + return FloatT{ (1 - proj_prob_distance) * conjunctive_certainty }; } template @@ -1037,7 +1129,16 @@ template constexpr OpinionNoBase& OpinionNoBase::revise_trust_(FloatT revision_factor) requires is_binomial { - revision_factor = std::clamp(revision_factor, static_cast(-1.0), static_cast(1.0)); + // revision_factor = std::clamp(revision_factor, static_cast(-1.0), static_cast(1.0)); + // std::clamp not available with CUDA + if (revision_factor > 1.0) + { + revision_factor = 1.0; + } + else if (revision_factor < -1.0) + { + revision_factor = -1.0; + } if (revision_factor < 0) { @@ -1112,11 +1213,12 @@ constexpr OpinionNoBase OpinionNoBase::comultiply(OpinionN template constexpr OpinionNoBase& OpinionNoBase::cum_fuse_(OpinionNoBase other) { + using std::abs; + FloatT uncert_this = this->uncertainty(); FloatT uncert_other = other.uncertainty(); - FloatT denom = uncert_this + uncert_other - uncert_this * uncert_other; - - if (std::abs(denom) < EPS_v) + FloatT denom{ uncert_this + uncert_other - uncert_this * uncert_other }; + if (abs(denom) < EPS_v) { // Jøsang suggests a boundary value consideration, // however, since no further information about the uncertainty values is available, @@ -1137,11 +1239,13 @@ constexpr OpinionNoBase& OpinionNoBase::cum_fuse_(OpinionN template constexpr OpinionNoBase& OpinionNoBase::cum_unfuse_(OpinionNoBase other) { + using std::abs; + FloatT uncert_this = this->uncertainty(); FloatT uncert_other = other.uncertainty(); - FloatT denom = uncert_other - uncert_this + uncert_other * uncert_this; + FloatT denom = { uncert_other - uncert_this + uncert_other * uncert_this }; - if (std::abs(denom) < EPS_v) + if (abs(denom) < EPS_v) { // Jøsang suggests a boundary value consideration, // however, since no further information about the uncertainty values is available, @@ -1209,16 +1313,18 @@ constexpr FloatT OpinionNoBase::conflict(OpinionNoBase other) const template constexpr OpinionNoBase& OpinionNoBase::bc_fuse_(OpinionNoBase other) { + using std::abs; + BeliefType harmony = this->harmony(other); FloatT conflict = this->conflict(other); - if (std::abs(1 - conflict) < EPS_v) + if (abs(1 - conflict) < EPS_v) { belief_masses_ = NeutralBeliefDistr(); return *this; } - FloatT normalizer = 1 - conflict; + auto normalizer = FloatT{ 1 - conflict }; constexpr_for<0, N, 1>( [this, harmony, normalizer] CUDA_AVAIL(std::size_t idx) { belief_masses_[idx] = harmony[idx] / normalizer; }); @@ -1234,11 +1340,12 @@ constexpr OpinionNoBase OpinionNoBase::bc_fuse(OpinionNoBa template constexpr OpinionNoBase& OpinionNoBase::average_fuse_(OpinionNoBase other) { + using std::abs; + FloatT uncert_this = this->uncertainty(); FloatT uncert_other = other.uncertainty(); - FloatT denom = uncert_this + uncert_other; - - if (std::abs(denom) < EPS_v) + FloatT denom{ uncert_this + uncert_other }; + if (abs(denom) < EPS_v) { constexpr_for<0, N, 1>([&](std::size_t idx) { // Jøsang suggests a boundary value consideration, @@ -1264,11 +1371,13 @@ constexpr OpinionNoBase OpinionNoBase::average_fuse(Opinio template constexpr OpinionNoBase& OpinionNoBase::average_unfuse_(OpinionNoBase other) { + using std::abs; + FloatT uncert_this = this->uncertainty(); FloatT uncert_other = other.uncertainty(); FloatT denom = static_cast(2.0) * uncert_other - uncert_this; - if (std::abs(denom) < EPS_v) + if (abs(denom) < EPS_v) { constexpr_for<0, N, 1>([&](std::size_t idx) { // Jøsang suggests a boundary value consideration, @@ -1299,16 +1408,16 @@ constexpr OpinionNoBase& OpinionNoBase::cc_fuse_(OpinionNo FloatT uncert_this = this->uncertainty(); FloatT uncert_other = other.uncertainty(); - BeliefType consensus{ 0. }; + BeliefType consensus{ FloatT{ 0. } }; FloatT consensus_sum{ 0. }; BeliefType resA = belief_masses_; BeliefType resB = other.belief_masses_; - BeliefType compromise{ 0 }; + BeliefType compromise{ FloatT{ 0. } }; FloatT compromise_sum{ 0. }; constexpr_for<0, N, 1>([&](std::size_t idx) { - FloatT consens = fminf(belief_masses_[idx], other.belief_masses_[idx]); + FloatT consens{ fminf(static_cast(belief_masses_[idx]), static_cast(other.belief_masses_[idx])) }; consensus[idx] = consens; consensus_sum += consens; resA[idx] -= consensus[idx]; @@ -1337,9 +1446,9 @@ constexpr OpinionNoBase& OpinionNoBase::cc_fuse_(OpinionNo compromise_sum += compromise[idx]; }); - FloatT uncert_pre = uncert_this * uncert_other; + FloatT uncert_pre{ uncert_this * uncert_other }; - if (fabs(compromise_sum) < EPS_v) + if (fabs(static_cast(compromise_sum)) < EPS_v) { // compromise gets smaller with a decreasing amount of belief masses // thus return a vacuous opinion as a result @@ -1347,7 +1456,7 @@ constexpr OpinionNoBase& OpinionNoBase::cc_fuse_(OpinionNo return *this; } - FloatT normalization = (1 - consensus_sum - uncert_pre) / compromise_sum; + auto normalization = FloatT{ (1 - consensus_sum - uncert_pre) / compromise_sum }; // merge consensus and compromise constexpr_for<0, N, 1>([this, consensus, compromise, normalization] CUDA_AVAIL(std::size_t idx) { @@ -1381,13 +1490,15 @@ constexpr OpinionNoBase OpinionNoBase::moment_matching_upd template constexpr OpinionNoBase& OpinionNoBase::wb_fuse_(OpinionNoBase other) { + using std::abs; + FloatT uncert_this = this->uncertainty(); FloatT uncert_other = other.uncertainty(); - FloatT denom = uncert_this + uncert_other - 2 * uncert_this * uncert_other; + FloatT denom{ uncert_this + uncert_other - 2 * uncert_this * uncert_other }; - if (std::abs(denom) < EPS_v) + if (abs(denom) < EPS_v) { - if (std::abs(uncert_this * uncert_other) < EPS_v) + if (abs(uncert_this * uncert_other) < EPS_v) { // Jøsang suggests a boundary value consideration, // however, since no further information about the uncertainty values is available, @@ -1488,13 +1599,15 @@ constexpr OpinionNoBase& OpinionNoBase::deduction_(FloatT OpinionNoBase cond_2) requires is_binomial { + using std::abs; + FloatT a_y_nom = base_x * cond_1.belief() + (1 - base_x) * cond_2.belief(); FloatT a_y_denom = 1 - (base_x * cond_1.uncertainty() + (1 - base_x) * cond_2.uncertainty()); // if denom is too small (combination of vacuous conditional and base rate of x) simply take x's base rate // nothing is specifically defined in [1] FloatT a_y = base_x; - if (std::abs(a_y_denom) > EPS_v) + if (abs(a_y_denom) > EPS_v) { a_y = a_y_nom / a_y_denom; } @@ -1644,13 +1757,7 @@ OpinionNoBase constexpr OpinionNoBase::getReducedOpinio template constexpr bool OpinionNoBase::operator==(const OpinionNoBase& other) const { - FloatT diff{ 0. }; - - for (std::size_t idx{ 0 }; idx < N; ++idx) - { - diff += std::abs(belief_masses_[idx] - other.belief_masses_[idx]); - } - return diff < EPS_v; + return belief_masses_ == other.belief_masses_; } template @@ -1663,22 +1770,43 @@ inline std::ostream& operator<<(std::ostream& out, OpinionNoBase cons template std::string OpinionNoBase::to_string() const { + using std::to_string; if constexpr (is_binomial) { - return std::string{ "[bel: " } + std::to_string(belief()) + "; disbel: " + std::to_string(disbelief()) + - "; uncertainty: " + std::to_string(uncertainty()) + "]"; + return std::string{ "[bel: " } + to_string(belief()) + "; disbel: " + to_string(disbelief()) + + "; uncertainty: " + to_string(uncertainty()) + "]"; } std::string out{ "[bel masses: " }; for (const auto& mass : belief_masses_) { - out += std::to_string(mass) + ", "; + out += to_string(mass) + ", "; } - out += "uncertainty: " + std::to_string(uncertainty()) + "]"; + out += "uncertainty: " + to_string(uncertainty()) + "]"; return out; } +template +void OpinionNoBase::to_char_buffer(char* buffer, std::size_t max_length) const +{ + if constexpr (is_binomial) + { + snprintf(buffer, max_length, "[bel: %f; disbel: %f; uncert: %f]", belief(), disbelief(), uncertainty()); + return; + } + + int offset = 0; + offset += snprintf(buffer + offset, max_length - offset, "["); + for (std::size_t idx{ 0 }; idx < N; ++idx) + { + offset += snprintf(buffer + offset, max_length - offset, "%.2f%s", belief_masses_[idx], (idx < N - 1) ? ", " : ""); + if (offset >= max_length) + break; // prevent overflow + } + snprintf(buffer + offset, max_length - offset, "]\0"); +} + template OpinionNoBase::operator std::string() const { diff --git a/library/include/subjective_logic_lib/opinions/trusted_opinion.hpp b/library/include/subjective_logic_lib/opinions/trusted_opinion.hpp index c720de8..2da8a52 100644 --- a/library/include/subjective_logic_lib/opinions/trusted_opinion.hpp +++ b/library/include/subjective_logic_lib/opinions/trusted_opinion.hpp @@ -4,28 +4,37 @@ // Subjective Logic - A Formalism for Reasoning Under Uncertainty, // Audun Jøsang, 2016, https://doi.org/10.1007/978-3-319-42337-1 -#include #include #include #include #include "subjective_logic_lib/util.hpp" -#include "subjective_logic_lib/opinions/opinion.hpp" #include "subjective_logic_lib/opinions/opinion_no_base.hpp" +#include "subjective_logic_lib/opinions/opinion.hpp" namespace subjective_logic { +template +class TrustedOpinion; + +template +using QuantizedTrustedOpinion = TrustedOpinion; template class TrustedOpinion { public: - using OpinionT = OpinionTemplate; - static constexpr std::size_t SIZE = OpinionT::SIZE; + using QuantizedT = QuantizedTrustedOpinion; + using DeQuantizedT = TrustedOpinion; + static constexpr bool is_quantized = OpinionTemplate::is_quantized; + static constexpr bool is_not_quantized = (not is_quantized); + + static constexpr std::size_t SIZE = OpinionTemplate::SIZE; // define Float in two ways, so that it is compatible with Opinions and nice to use (FloatT) - using FLOAT_t = typename OpinionT::FLOAT_t; + using FLOAT_t = typename OpinionTemplate::FLOAT_t; using FloatT = FLOAT_t; using TrustT = Trust; + using OpinionT = OpinionTemplate; /** * @brief accessor to simplify the access to the Opinions of TrustedOpinions stored in a vector @@ -33,7 +42,16 @@ class TrustedOpinion * @param trusted_opinions * @return */ - static std::vector extractOpinions(const std::vector>& trusted_opinions); + static std::vector extractOpinions(const std::vector& trusted_opinions); + /** + * @brief accessor to simplify the access to the Opinions of TrustedOpinions stored in a fixed sized array + * opinions are copied during extraction + * this function is cuda ready + * @param trusted_opinions + * @return + */ + template + CUDA_AVAIL static Array extractOpinions(const Array& trusted_opinions); /** * @brief accessor to simplify the access to the Opinions of TrustedOpinions stored in a vector @@ -52,6 +70,16 @@ class TrustedOpinion */ static std::vector extractTrusts(const std::vector& trusted_opinions); + /** + * @brief accessor to simplify the access to the Trusts of TrustedOpinions stored in a fixed sized array + * opinions are copied during extraction + * this function is cuda ready + * @param trusted_opinions + * @return + */ + template + CUDA_AVAIL static Array extractTrusts(const Array& trusted_opinions); + /** * @brief accessor to simplify the access to the Trusts of TrustedOpinions stored in a vector * opinions are NOT copied, but referenced during extraction @@ -68,6 +96,16 @@ class TrustedOpinion */ static std::vector extractDiscountedOpinions(const std::vector& trusted_opinions); + /** + * @brief accessor to simplify the access to the Opinions of TrustedOpinions stored in a fixed sized array + * opinions are copied during extraction + * this function is cuda ready + * @param trusted_opinions + * @return + */ + template + CUDA_AVAIL static Array extractDiscountedOpinions(const Array& trusted_opinions); + /** * @brief default ctor leads to a vacuous opinion with vacuous trust */ @@ -78,39 +116,71 @@ class TrustedOpinion * @param trust * @param opinion */ + CUDA_AVAIL TrustedOpinion(TrustT trust, OpinionT opinion); + /** + * @brief creates a TrustedOpinion from a quantized or normal version of it () + * thus, it may either be a usual copy ctor, or the conversion between quantized and dequantized + * @param other_opinion + */ + template + CUDA_AVAIL explicit constexpr TrustedOpinion(T other_opinion) + requires( + // either of both will always be the same as the current OpinionNoBase + std::is_same_v, QuantizedT> or std::is_same_v, DeQuantizedT>); + /** * @brief checks for both opinion and trust to be valid */ - constexpr bool is_valid() const; + CUDA_AVAIL + [[nodiscard]] constexpr bool is_valid() const; + + /** + * @brief returns a quantized opinion that uses a 1-byte float representation + */ + CUDA_AVAIL + constexpr QuantizedT get_quantized() const + requires is_not_quantized; + + /** + * @brief returns a dequantized opinion + */ + CUDA_AVAIL + constexpr DeQuantizedT get_dequantized() const + requires is_quantized; /** * @brief accessor to the trust part * @return */ + CUDA_AVAIL TrustT& trust(); /** * @brief const accessor to the trust part * @return */ + CUDA_AVAIL const TrustT& trust() const; /** * @brief accessor to the opinion part * @return */ + CUDA_AVAIL OpinionT& opinion(); /** * @brief const accessor to the opinion part * @return */ + CUDA_AVAIL const OpinionT& opinion() const; /** * @brief calculates and returns the opinion which is already discounted by the trust part of this class * @return */ + CUDA_AVAIL OpinionT discounted_opinion() const; /** @@ -119,6 +189,7 @@ class TrustedOpinion * @param revision_factor * @return */ + CUDA_AVAIL TrustedOpinion& revise_trust_(FloatT revision_factor); /** @@ -127,6 +198,7 @@ class TrustedOpinion * @param revision_factor * @return */ + CUDA_AVAIL TrustedOpinion revise_trust(FloatT revision_factor) const; /** @@ -135,6 +207,7 @@ class TrustedOpinion * @param other * @return */ + CUDA_AVAIL std::pair revise_trust_(TrustedOpinion& other); /** * @brief applies the concept of trust revision described in [1] @@ -142,6 +215,7 @@ class TrustedOpinion * @param other * @return */ + CUDA_AVAIL std::pair revise_trust(TrustedOpinion other) const; /** @@ -156,7 +230,6 @@ class TrustedOpinion * @brief generates a readable string containing the belief masses and the uncertainty for opinion and trust * @return */ - CUDA_AVAIL explicit operator std::string() const; /** @@ -185,6 +258,14 @@ TrustedOpinion::extractOpinions(const std::vector top) { return top.opinion(); }); return opinions; } +template +template +Array TrustedOpinion::extractOpinions(const Array>& trusted_opinions) +{ + Array opinions; + constexpr_for<0, N>([&](std::size_t idx) { opinions[idx] = trusted_opinions[idx].opinion(); }); + return opinions; +} template std::vector> @@ -212,6 +293,16 @@ TrustedOpinion::extractTrusts(const std::vector& trust return trusts; } +template +template +Array::TrustT> +TrustedOpinion::extractTrusts(const Array& trusted_opinions) +{ + Array trusts; + constexpr_for<0, N>([&](std::size_t idx) { trusts[idx] = trusted_opinions[idx].trust(); }); + return trusts; +} + template std::vector::TrustT>> TrustedOpinion::extractTrustsRef(std::vector& trusted_opinions) @@ -234,21 +325,55 @@ TrustedOpinion::extractDiscountedOpinions(const std::vector +template +Array::OpinionT> +TrustedOpinion::extractDiscountedOpinions(const Array& trusted_opinions) +{ + Array opinions; + constexpr_for<0, N>([&](std::size_t idx) { opinions[idx] = trusted_opinions[idx].discounted_opinion(); }); + return opinions; +} + template TrustedOpinion::TrustedOpinion(TrustT trust, OpinionT opinion) : trust_{ trust }, opinion_{ opinion } { } +template +template +constexpr TrustedOpinion::TrustedOpinion(T other_opinion) + requires( + // either of both will always be the same as the current OpinionNoBase + std::is_same_v, QuantizedT> or std::is_same_v, DeQuantizedT>) + : TrustedOpinion(TrustT{ other_opinion.trust() }, OpinionT{ other_opinion.opinion() }) +{ +} + template constexpr bool TrustedOpinion::is_valid() const { return trust_.is_valid() and opinion_.is_valid(); } +template +constexpr QuantizedTrustedOpinion TrustedOpinion::get_quantized() const + requires is_not_quantized +{ + return QuantizedT(trust_.get_quantized(), opinion_.get_quantized()); +} + +template +constexpr TrustedOpinion::DeQuantizedT TrustedOpinion::get_dequantized() const + requires is_quantized +{ + return DeQuantizedT(trust_.get_dequantized(), opinion_.get_dequantized()); +} + template typename TrustedOpinion::TrustT& TrustedOpinion::trust() { diff --git a/library/include/subjective_logic_lib/types/convert.hpp b/library/include/subjective_logic_lib/types/convert.hpp index ddc9178..6e0f18d 100644 --- a/library/include/subjective_logic_lib/types/convert.hpp +++ b/library/include/subjective_logic_lib/types/convert.hpp @@ -6,33 +6,59 @@ namespace subjective_logic { +template +template +constexpr Opinion::Opinion(DirichletDistribution dirichlet) + requires std::is_convertible_v + : Opinion{ dirichlet.evidences() / (dirichlet.evidences().sum() + N), dirichlet.priors() } +{ +} + template constexpr Opinion::operator DirichletDistribution() const { - DirichletDistribution dist(N * this->belief_masses() / this->uncertainty(), this->prior_belief_masses()); - return dist; + return DirichletDistribution(*this); +} + +template +template +constexpr OpinionNoBase::OpinionNoBase(DirichletDistribution dirichlet) + requires std::is_convertible_v + : OpinionNoBase{ dirichlet.evidences() / (dirichlet.evidences().sum() + N) } +{ } template constexpr OpinionNoBase::operator DirichletDistribution() const { - DirichletDistribution dist(N * this->belief_masses() / this->uncertainty(), this->NeutralBeliefDistr()); - return dist; + return DirichletDistribution(*this); +} + +template +template +constexpr DirichletDistribution::DirichletDistribution(OpinionNoBase opinion) + requires std::is_convertible_v + : DirichletDistribution{ N * opinion.belief_masses() / opinion.uncertainty(), opinion.NeutralBeliefDistr() } +{ +} + +template +template +constexpr DirichletDistribution::DirichletDistribution(Opinion opinion) + requires std::is_convertible_v + : DirichletDistribution{ N * opinion.belief_masses() / opinion.uncertainty(), opinion.prior_belief_masses() } +{ } template constexpr DirichletDistribution::operator Opinion() const { - FloatT denom = this->evidence_.sum() + N; - Opinion opinion(this->evidence_ / denom, this->prior_); - return opinion; + return Opinion(*this); } template constexpr DirichletDistribution::operator OpinionNoBase() const { - FloatT denom = this->evidence_.sum() + N; - OpinionNoBase opinion(this->evidence_ / denom); - return opinion; + return OpinionNoBase(*this); } } // namespace subjective_logic diff --git a/library/include/subjective_logic_lib/types/cuda_compatible_array.hpp b/library/include/subjective_logic_lib/types/cuda_compatible_array.hpp index aa4380e..762d1c8 100644 --- a/library/include/subjective_logic_lib/types/cuda_compatible_array.hpp +++ b/library/include/subjective_logic_lib/types/cuda_compatible_array.hpp @@ -28,6 +28,9 @@ namespace subjective_logic template struct Array { + template + friend struct Array; + using iterator = IteratorClass; using reverse_iterator = IteratorClass; using const_iterator = IteratorClass; @@ -36,6 +39,7 @@ struct Array /** * @brief constexpr access to the template parameter N, similar interface to std container */ + CUDA_AVAIL static constexpr std::size_t size() { return N; @@ -43,10 +47,18 @@ struct Array using value_type = T; /** - * @brief default ctor will fill every entry explicitly with 0 + * @brief default ctor will fill every entry explicitly with 0, if T is constructable with 0 + */ + CUDA_AVAIL + constexpr Array() + requires requires { T{ 0 }; }; + + /** + * @brief default ctor will fill every entry using the default ctor of T */ CUDA_AVAIL - constexpr Array(); + constexpr Array() + requires(!requires { T{ 0 }; }); /** * @brief default_entry is copy assigned to each element @@ -55,6 +67,13 @@ struct Array CUDA_AVAIL constexpr explicit Array(T default_entry); + /** + * @brief ctor with implicit value conversion + */ + template + CUDA_AVAIL explicit constexpr Array(Array other) + requires is_static_castable; + constexpr Array(const Array& other) = default; constexpr Array(Array&& other) = default; @@ -69,14 +88,14 @@ struct Array */ template CUDA_AVAIL constexpr explicit Array(VALUES... values) - requires(sizeof...(VALUES) == N and (std::is_convertible_v && ...)); + requires(sizeof...(VALUES) == N and (is_static_castable && ...)); /** * @brief factory function allowing the initialization with a value list */ template CUDA_AVAIL constexpr void array_factory(T value, VALUES... values) - requires(sizeof...(VALUES) == 0 or (std::is_convertible_v && ...)); + requires(sizeof...(VALUES) == 0 or (is_static_castable && ...)); constexpr Array& operator=(const Array& other) = default; constexpr Array& operator=(Array&& other) = default; @@ -116,10 +135,8 @@ struct Array CUDA_AVAIL constexpr const T& operator[](std::size_t idx) const; - CUDA_AVAIL explicit operator std::string() const; - CUDA_AVAIL [[nodiscard]] std::string to_string() const; /** @@ -131,6 +148,15 @@ struct Array */ std::array as_array() const; + /** + * @brief get direct const access to the entries to avoid copy to array for raw access + */ + CUDA_AVAIL + auto const& entries() const + { + return entries_; + } + /////////////// // all non assigning operators with scalar values are defined outside class // partly necessary, to allow left hand side multiplication @@ -187,6 +213,13 @@ struct Array requires is_dividable; ///@} + template + CUDA_AVAIL constexpr explicit operator Array() const + requires is_static_castable; + + CUDA_AVAIL + constexpr bool operator==(const Array& other) const; + /** * @brief summing all elements in the array */ @@ -195,31 +228,55 @@ struct Array requires requires(T a, T b) { a + b; }; protected: - T entries[N]; + T entries_[N]; }; template constexpr Array::Array() + requires requires { T{ 0 }; } { - fill(static_cast(0)); + fill(T{ 0 }); +} + +template +constexpr Array::Array() + requires(!requires { T{ 0 }; }) +{ + fill(T{}); } template constexpr Array::Array(T default_entry) { - constexpr_for<0, N, 1>([this, &default_entry](std::size_t idx) { entries[idx] = default_entry; }); + fill(default_entry); +} + +template +template +constexpr Array::Array(Array other) + requires is_static_castable +{ + constexpr_for<0, N>([this, other](std::size_t idx) { entries_[idx] = static_cast(other.entries_[idx]); }); +} + +template +template +constexpr Array::operator Array() const + requires is_static_castable +{ + return Array(*this); } template constexpr Array::Array(std::array other) { - std::memcpy(entries, other.data(), sizeof(T) * other.size()); + std::memcpy(entries_, other.data(), sizeof(T) * other.size()); } template template constexpr Array::Array(VALUES... values) - requires(sizeof...(VALUES) == N and (std::is_convertible_v && ...)) + requires(sizeof...(VALUES) == N and (is_static_castable && ...)) { array_factory(values...); } @@ -227,11 +284,11 @@ constexpr Array::Array(VALUES... values) template template constexpr void Array::array_factory(T value, VALUES... values) - requires(sizeof...(VALUES) == 0 or (std::is_convertible_v && ...)) + requires(sizeof...(VALUES) == 0 or (is_static_castable && ...)) { constexpr std::size_t idx{ sizeof...(VALUES) }; - entries[(N - 1) - idx] = static_cast(value); + entries_[(N - 1) - idx] = static_cast(value); if constexpr (idx > 0) { array_factory(values...); @@ -241,79 +298,79 @@ constexpr void Array::array_factory(T value, VALUES... values) template constexpr void Array::fill(T entry) { - constexpr_for<0, N, 1>([this, entry](std::size_t idx) { entries[idx] = entry; }); + constexpr_for<0, N, 1>([this, entry](std::size_t idx) { entries_[idx] = entry; }); } template -constexpr typename Array::iterator Array::begin() +constexpr Array::iterator Array::begin() { - return iterator{ &entries[0] }; + return iterator{ &entries_[0] }; } template -constexpr typename Array::const_iterator Array::begin() const +constexpr Array::const_iterator Array::begin() const { - return const_iterator{ &entries[0] }; + return const_iterator{ &entries_[0] }; } template -constexpr typename Array::iterator Array::end() +constexpr Array::iterator Array::end() { - return iterator{ &entries[N - 1] + 1 }; + return iterator{ &entries_[N - 1] + 1 }; } template -constexpr typename Array::const_iterator Array::end() const +constexpr Array::const_iterator Array::end() const { - return const_iterator{ &entries[N - 1] + 1 }; + return const_iterator{ &entries_[N - 1] + 1 }; } template -constexpr typename Array::reverse_iterator Array::rbegin() +constexpr Array::reverse_iterator Array::rbegin() { - return reverse_iterator{ &entries[N - 1] }; + return reverse_iterator{ &entries_[N - 1] }; } template -constexpr typename Array::const_reverse_iterator Array::rbegin() const +constexpr Array::const_reverse_iterator Array::rbegin() const { - return const_reverse_iterator{ &entries[N - 1] }; + return const_reverse_iterator{ &entries_[N - 1] }; } template -constexpr typename Array::reverse_iterator Array::rend() +constexpr Array::reverse_iterator Array::rend() { - return reverse_iterator{ &entries[0] - 1 }; + return reverse_iterator{ &entries_[0] - 1 }; } template -constexpr typename Array::const_reverse_iterator Array::rend() const +constexpr Array::const_reverse_iterator Array::rend() const { - return const_reverse_iterator{ &entries[0] - 1 }; + return const_reverse_iterator{ &entries_[0] - 1 }; } template constexpr T& Array::front() { - return entries[0]; + return entries_[0]; } template constexpr const T& Array::front() const { - return entries[0]; + return entries_[0]; } template constexpr T& Array::back() { - return entries[N - 1]; + return entries_[N - 1]; } template constexpr const T& Array::back() const { - return entries[N - 1]; + return entries_[N - 1]; } template @@ -333,7 +390,7 @@ template CUDA_AVAIL Array operator-(U value, Array array) requires is_substractable { - constexpr_for<0, N, 1>([&] CUDA_AVAIL(std::size_t idx) { array[idx] = value - array[idx]; }); + constexpr_for<0, N>([&] CUDA_AVAIL(std::size_t idx) { array[idx] = value - array[idx]; }); return array; // return value + ((-1) * array); although possible, it would make use of multiple loops } @@ -361,7 +418,7 @@ template CUDA_AVAIL Array operator/(U value, Array array) requires is_dividable { - constexpr_for<0, N, 1>([&] CUDA_AVAIL(std::size_t idx) { array[idx] = value / array[idx]; }); + constexpr_for<0, N>([&] CUDA_AVAIL(std::size_t idx) { array[idx] = value / array[idx]; }); return array; } template @@ -383,7 +440,7 @@ template constexpr Array& Array::operator+=(const Array& other) requires is_addable { - constexpr_for<0, N, 1>([this, &other] CUDA_AVAIL(std::size_t idx) { entries[idx] += other[idx]; }); + constexpr_for<0, N>([this, &other](std::size_t idx) { entries_[idx] += other[idx]; }); return *this; } template @@ -391,7 +448,7 @@ template constexpr Array& Array::operator+=(const U& value) requires is_addable { - constexpr_for<0, N, 1>([this, value] CUDA_AVAIL(std::size_t idx) { entries[idx] += value; }); + constexpr_for<0, N>([this, value](std::size_t idx) { entries_[idx] += value; }); return *this; } @@ -407,7 +464,7 @@ template constexpr Array& Array::operator-=(const Array& other) requires is_substractable { - constexpr_for<0, N, 1>([this, &other] CUDA_AVAIL(std::size_t idx) { entries[idx] -= other[idx]; }); + constexpr_for<0, N>([this, &other](std::size_t idx) { entries_[idx] -= other[idx]; }); return *this; } template @@ -415,7 +472,7 @@ template constexpr Array& Array::operator-=(const U& value) requires is_substractable { - constexpr_for<0, N, 1>([this, value] CUDA_AVAIL(std::size_t idx) { entries[idx] -= value; }); + constexpr_for<0, N>([this, value](std::size_t idx) { entries_[idx] -= value; }); return *this; } @@ -431,7 +488,7 @@ template constexpr Array& Array::operator*=(const Array& other) requires is_multipliable { - constexpr_for<0, N, 1>([this, &other] CUDA_AVAIL(std::size_t idx) { entries[idx] *= other[idx]; }); + constexpr_for<0, N>([this, &other](std::size_t idx) { entries_[idx] *= other[idx]; }); return *this; } template @@ -439,7 +496,7 @@ template constexpr Array& Array::operator*=(const U& value) requires is_multipliable { - constexpr_for<0, N, 1>([this, value] CUDA_AVAIL(std::size_t idx) { entries[idx] *= value; }); + constexpr_for<0, N>([this, value](std::size_t idx) { entries_[idx] *= value; }); return *this; } @@ -455,7 +512,7 @@ template constexpr Array& Array::operator/=(const Array& other) requires is_dividable { - constexpr_for<0, N, 1>([this, &other] CUDA_AVAIL(std::size_t idx) { entries[idx] /= other[idx]; }); + constexpr_for<0, N>([this, &other](std::size_t idx) { entries_[idx] /= other[idx]; }); return *this; } template @@ -463,7 +520,7 @@ template constexpr Array& Array::operator/=(const U& value) requires is_dividable { - constexpr_for<0, N, 1>([this, value] CUDA_AVAIL(std::size_t idx) { entries[idx] /= value; }); + constexpr_for<0, N>([this, value](std::size_t idx) { entries_[idx] /= value; }); return *this; } @@ -471,21 +528,42 @@ template constexpr T Array::sum() const requires requires(T a, T b) { a + b; } { - T sum{ entries[0] }; - constexpr_for<1, N, 1>([this, &sum] CUDA_AVAIL(std::size_t idx) { sum += entries[idx]; }); +#ifdef __CUDA_ARCH__ + T sum{ entries_[0] }; + for (int i{ 1 }; i < N; ++i) + { + sum += entries_[i]; + } return sum; +#else + T sum{ entries_[0] }; + // start by 1 is indented, since sum already contains first element + constexpr_for<1, N>([this, &sum](std::size_t idx) { sum += entries_[idx]; }); + return sum; +#endif } template constexpr T& Array::operator[](std::size_t idx) { - return entries[idx]; + return entries_[idx]; } template constexpr const T& Array::operator[](std::size_t idx) const { - return entries[idx]; + return entries_[idx]; +} + +template +constexpr bool Array::operator==(const Array& other) const +{ + bool equal{ true }; + constexpr_for<0, N>([this, &other, &equal](std::size_t idx) { + using std::abs; + equal &= abs(entries_[idx] - other.entries_[idx]) < EPS_v; + }); + return equal; } template @@ -511,7 +589,7 @@ std::string Array::to_string() const { out += " "; } - out += std::to_string(entries[idx]); + out += std::to_string(entries_[idx]); } out += "]"; return out; @@ -522,7 +600,9 @@ std::vector Array::as_vector() const { std::vector out; out.reserve(N); - constexpr_for<1, N>([this, &out](std::size_t idx) { out.push_back(this->entries[idx]); }); + // if compiled in nvcc, the constexpr_for is also available on __device__ and therefor has issues with host functions + // even on host... thus, a workaround is used here. + constexpr_for_host<0, N>([this, &out](std::size_t idx) { out.push_back(this->entries_[idx]); }); return out; } @@ -530,7 +610,7 @@ template std::array Array::as_array() const { std::array out; - constexpr_for<1, N>([this, &out](std::size_t idx) { out[idx] = this->entries[idx]; }); + constexpr_for_host<0, N>([this, &out](std::size_t idx) { out[idx] = this->entries_[idx]; }); return out; } diff --git a/library/include/subjective_logic_lib/types/dirichlet_distribution.hpp b/library/include/subjective_logic_lib/types/dirichlet_distribution.hpp index 3a38188..7250f0c 100644 --- a/library/include/subjective_logic_lib/types/dirichlet_distribution.hpp +++ b/library/include/subjective_logic_lib/types/dirichlet_distribution.hpp @@ -22,6 +22,9 @@ namespace subjective_logic template class Opinion; +template +class OpinionNoBase; + template class DirichletDistribution { @@ -59,42 +62,53 @@ class DirichletDistribution CUDA_AVAIL constexpr explicit DirichletDistribution(VALUES... values) requires(is_arithmetic_list and sizeof...(VALUES) == N); + /** + * @brief convenience conversion between Opinions and DirichletDistributions + * prior is implicitly ignored/ default constructed. + */ + template + CUDA_AVAIL constexpr explicit DirichletDistribution(OpinionNoBase opinion) + requires std::is_convertible_v; + + /** + * @brief convenience conversion between Opinions and DirichletDistributions + * prior is implicitly ignored. + */ + template + CUDA_AVAIL constexpr explicit DirichletDistribution(Opinion opinion) + requires std::is_convertible_v; + /** * @brief default copy ctor * @param other */ - CUDA_AVAIL constexpr DirichletDistribution(const DirichletDistribution& other) = default; /** * @brief default move ctor * @param other */ - CUDA_AVAIL constexpr DirichletDistribution(DirichletDistribution&& other) = default; /** * @brief default copy assignment * @param other */ - CUDA_AVAIL constexpr DirichletDistribution& operator=(const DirichletDistribution& other) = default; /** * @brief default move assignment * @param other */ - CUDA_AVAIL constexpr DirichletDistribution& operator=(DirichletDistribution&& other) = default; /** * @brief default dtor (not virtual!!!!, see description above) */ - CUDA_AVAIL ~DirichletDistribution() = default; /** - * @brief allows to construct a Dirichlet distribution with evidences only, ctor only allows alpha values + * @brief allows constructing a Dirichlet distribution with evidences only, ctor only allows alpha values * for this, a neutral prior is assumed */ static constexpr DirichletDistribution from_evidences(WeightType evidences); @@ -197,6 +211,12 @@ class DirichletDistribution CUDA_AVAIL constexpr DirichletDistribution moment_matching_update(WeightType probabilities) const; + /** @brief compares to DirichletDistributions by separately comparing evidences and priors + * thus, two different DirDistr. can provide the same alphas but are not equal. + */ + CUDA_AVAIL + constexpr bool operator==(const DirichletDistribution&) const; + protected: WeightType evidence_; WeightType prior_; @@ -367,6 +387,12 @@ DirichletDistribution::moment_matching_update(WeightType probabilitie return DirichletDistribution{ *this }.moment_matching_update_(probabilities); } +template +constexpr bool DirichletDistribution::operator==(const DirichletDistribution& other) const +{ + return evidence_ == other.evidence_ and prior_ == other.prior_; +} + } // namespace subjective_logic #include "subjective_logic_lib/types/convert.hpp" diff --git a/library/include/subjective_logic_lib/types/fusion_types.hpp b/library/include/subjective_logic_lib/types/fusion_types.hpp new file mode 100644 index 0000000..2b0a139 --- /dev/null +++ b/library/include/subjective_logic_lib/types/fusion_types.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include + +namespace subjective_logic +{ +enum class FusionType : int +{ + CUMULATIVE = 0, + BELIEF_CONSTRAINT, + AVERAGE, + WEIGHTED, +}; + +enum class TrustRevisionType : int +{ + NORMAL, + SHARES, + SHARES_ALLOW_NEGATIVE, + REFERENCE_FUSION, +}; + +enum class RelationType : int +{ + CONFLICT, + HARMONY +}; + +enum class ConflictType : int +{ + ACCUMULATE, + AVERAGE, + BELIEF_CUMULATIVE, + BELIEF_BELIEF_CONSTRAINT, + BELIEF_AVERAGE, + BELIEF_WEIGHTED, +}; + +constexpr FusionType get_belief_fusion_type(ConflictType conflict_type) +{ + switch (conflict_type) + { + case ConflictType::BELIEF_CUMULATIVE: + { + return FusionType::CUMULATIVE; + } + case ConflictType::BELIEF_BELIEF_CONSTRAINT: + { + return FusionType::BELIEF_CONSTRAINT; + } + case ConflictType::BELIEF_AVERAGE: + { + return FusionType::AVERAGE; + } + case ConflictType::BELIEF_WEIGHTED: + { + return FusionType::WEIGHTED; + } + default: + { + throw std::logic_error{ "Fusion types are only availalbe for Belief Constraint Conflict types, not for: " + + std::to_string(static_cast(conflict_type)) }; + } + } +} + +} // namespace subjective_logic diff --git a/library/include/subjective_logic_lib/types/limited_float.hpp b/library/include/subjective_logic_lib/types/limited_float.hpp new file mode 100644 index 0000000..2dc1303 --- /dev/null +++ b/library/include/subjective_logic_lib/types/limited_float.hpp @@ -0,0 +1,336 @@ +#pragma once + +#include +#include +#include + +namespace subjective_logic +{ + +template +struct LimitedFloat; + +template +constexpr LimitT abs(LimitedFloat x); +template +constexpr std::string to_string(LimitedFloat x); + +template +using ZeroOneFloat = LimitedFloat(0.0), static_cast(1.0)>; + +template +struct LimitedFloat +{ + using QuantizedT = std::uint8_t; + using LIMIT_TYPE = LimitT; + + constexpr static LimitT LIMIT_LOWER_BOUND{ LOWER_BOUND }; + constexpr static LimitT LIMIT_UPPER_BOUND{ UPPER_BOUND }; + constexpr static LimitT LIMIT_RANGE{ UPPER_BOUND - LOWER_BOUND }; + + constexpr static LimitT VALUE_RANGE{ std::numeric_limits::max() }; + + constexpr LimitedFloat() = default; + + // ReSharper disable once CppNonExplicitConvertingConstructor + + // implicit assignment from LimitT is intended, which allows, e.g., + // LimitedFloat lf_test = 0.5; + // otherwise the float value must be cast explicitly every time. + template + CUDA_AVAIL explicit constexpr LimitedFloat(T float_value) + requires is_static_castable; + + template + CUDA_AVAIL explicit constexpr operator T() const + requires is_static_castable; + CUDA_AVAIL constexpr LimitT as_limit_type() const; + + template + CUDA_AVAIL constexpr LimitedFloat& operator=(T float_value) + requires is_static_castable; + + template + CUDA_AVAIL constexpr LimitedFloat& operator+=(T other_value) + requires is_addable; + template + CUDA_AVAIL constexpr LimitT operator+(T other_value) const + requires is_addable; + + CUDA_AVAIL constexpr LimitT operator+(LimitedFloat other_value) const; + + template + CUDA_AVAIL constexpr LimitedFloat& operator-=(T other_value) + requires is_substractable; + template + CUDA_AVAIL constexpr LimitT operator-(T other_value) const + requires is_substractable; + + CUDA_AVAIL constexpr LimitT operator-(LimitedFloat other_value) const; + + template + CUDA_AVAIL constexpr LimitedFloat& operator*=(T other_value) + requires is_multipliable; + template + CUDA_AVAIL constexpr LimitT operator*(T other_value) const + requires is_multipliable; + + CUDA_AVAIL constexpr LimitT operator*(LimitedFloat other_value) const; + + template + CUDA_AVAIL constexpr LimitedFloat& operator/=(T other_value) + requires is_dividable; + template + CUDA_AVAIL constexpr LimitT operator/(T other_value) const + requires is_dividable; + + CUDA_AVAIL constexpr LimitT operator/(LimitedFloat other_value) const; + + CUDA_AVAIL constexpr auto operator<=>(LimitedFloat other_value) const; + + CUDA_AVAIL constexpr auto operator<=>(LimitT other_value) const; + + QuantizedT value; +}; + +template +constexpr LimitT abs(LimitedFloat x) +{ + return std::abs(x.as_limit_type()); +} +template +constexpr std::string to_string(LimitedFloat x) +{ + using std::to_string; + return std::string("lFloat: ") + to_string(x.as_limit_type()) + " / " + to_string(x.LIMIT_LOWER_BOUND) + " < " + + to_string(x.value) + " > " + to_string(x.LIMIT_UPPER_BOUND); +} + +template +template +constexpr LimitedFloat::LimitedFloat(T float_value) + requires is_static_castable +{ + // std::clamp not available with nvcc for cuda building on devices + if (float_value < LOWER_BOUND) + { + float_value = LOWER_BOUND; + } + else if (float_value > UPPER_BOUND) + { + float_value = UPPER_BOUND; + } + value = (float_value - LOWER_BOUND) / LIMIT_RANGE * VALUE_RANGE; +} + +template +template +constexpr LimitedFloat::operator T() const + requires is_static_castable +{ + return static_cast(static_cast(value) / VALUE_RANGE * LIMIT_RANGE + LOWER_BOUND); +} +template +constexpr LimitT LimitedFloat::as_limit_type() const +{ + return static_cast(*this); +} + +template +template +constexpr LimitedFloat& +LimitedFloat::operator=(T float_value) + requires is_static_castable +{ + *this = LimitedFloat(static_cast(float_value)); + return *this; +} + +template +constexpr LimitT operator+(T value, LimitedFloat lf_value) + requires is_addable +{ + return value + lf_value.as_limit_type(); +} + +template +template +constexpr LimitedFloat& +LimitedFloat::operator+=(T other_value) + requires is_addable +{ + *this = LimitedFloat{ *this + other_value }; + return *this; +} +template +template +constexpr LimitT LimitedFloat::operator+(T other_value) const + requires is_addable +{ + return this->as_limit_type() + other_value; +} + +template +constexpr LimitT LimitedFloat::operator+(LimitedFloat other_value) const +{ + return this->as_limit_type() + other_value.as_limit_type(); +} + +template +constexpr LimitT operator-(T value, LimitedFloat lf_value) + requires is_substractable +{ + return value - lf_value.as_limit_type(); +} + +template +template +constexpr LimitedFloat& +LimitedFloat::operator-=(T other_value) + requires is_substractable +{ + *this = LimitedFloat{ *this - other_value }; + return *this; +} + +template +template +constexpr LimitT LimitedFloat::operator-(T other_value) const + requires is_substractable +{ + return this->as_limit_type() - other_value; +} + +template +constexpr LimitT LimitedFloat::operator-(LimitedFloat other_value) const +{ + return this->as_limit_type() - other_value.as_limit_type(); +} + +template +constexpr LimitT operator*(T value, LimitedFloat lf_value) + requires is_multipliable +{ + return value * lf_value.as_limit_type(); +} + +template +template +constexpr LimitedFloat& +LimitedFloat::operator*=(T other_value) + requires is_multipliable +{ + *this = LimitedFloat{ *this * other_value }; + return *this; +} + +template +template +constexpr LimitT LimitedFloat::operator*(T other_value) const + requires is_multipliable +{ + return this->as_limit_type() * other_value; +} + +template +constexpr LimitT LimitedFloat::operator*(LimitedFloat other_value) const +{ + return this->as_limit_type() * other_value.as_limit_type(); +} + +template +constexpr LimitT operator/(T value, LimitedFloat lf_value) + requires is_dividable +{ + return value / lf_value.as_limit_type(); +} + +template +template +constexpr LimitedFloat& +LimitedFloat::operator/=(T other_value) + requires is_dividable +{ + *this = LimitedFloat{ *this / other_value }; + return *this; +} + +template +template +constexpr LimitT LimitedFloat::operator/(T other_value) const + requires is_dividable +{ + return this->as_limit_type() / other_value; +} + +template +constexpr LimitT LimitedFloat::operator/(LimitedFloat other_value) const +{ + return this->as_limit_type() / other_value.as_limit_type(); +} + +template +constexpr auto LimitedFloat::operator<=>(LimitedFloat other_value) const +{ + if (value < other_value.value) + { + return -1; + } + if (value > other_value.value) + { + return 1; + } + return 0; +} + +template +constexpr auto LimitedFloat::operator<=>(LimitT other_value) const +{ + if (static_cast(*this) < other_value) + { + return -1; + } + if (static_cast(*this) > other_value) + { + return 1; + } + return 0; +} + +} // namespace subjective_logic + +// specialize numeric_limits for LimitedFloat +namespace std +{ +template +struct numeric_limits> +{ + using lFloatT = subjective_logic::LimitedFloat; + + static constexpr bool is_specialized = true; + static constexpr bool is_signed = true; + static constexpr bool is_integer = false; + static constexpr bool is_exact = false; + static constexpr bool has_infinity = (UPPER_BOUND == std::numeric_limits::infinity()); + static constexpr int radix = 2; + static constexpr int digits = 0; + static constexpr int digits10 = 0; + + static constexpr LimitT min() noexcept + { + return LOWER_BOUND; + } + static constexpr LimitT max() noexcept + { + return LOWER_BOUND; + } + static constexpr LimitT lowest() noexcept + { + return min(); + } + static constexpr LimitT epsilon() noexcept + { + return 1.0 / std::numeric_limits::max(); + } +}; +} // namespace std \ No newline at end of file diff --git a/library/include/subjective_logic_lib/util.hpp b/library/include/subjective_logic_lib/util.hpp index 11204ef..3e63582 100644 --- a/library/include/subjective_logic_lib/util.hpp +++ b/library/include/subjective_logic_lib/util.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #ifdef __CUDA_ARCH__ @@ -21,12 +20,13 @@ concept is_binomial = N == 2; /** * @brief EPS allows to specifiy the epsilon used during comparisons within eSLIM++ - * If no type specific value is specified, the std::nummeric_limits::epsilon is used + * If no type-specific value is specified, the std::nummeric_limits::epsilon is used */ template struct EPS { - static constexpr FloatT epsilon = std::numeric_limits::epsilon(); + // auto required to allow automatic deduction of a float type with, e.g. LimitedFloat + static constexpr auto value = std::numeric_limits::epsilon(); }; /** @@ -50,7 +50,8 @@ struct EPS * @brief shortcut definition to access the value of a specific EPS struct */ template -static constexpr FloatT EPS_v{ EPS::value }; +// auto required to allow automatic deduction of a float type with, e.g. LimitedFloat +static constexpr auto EPS_v{ EPS::value }; /** @} */ /** @defgroup OperatorConcepts concepts for operator definitions @@ -65,8 +66,49 @@ template concept is_multipliable = requires(T a, U b) { a* b; }; template concept is_dividable = requires(T a, U b) { a / b; }; +template +concept is_static_castable = requires(T a) { static_cast(a); }; /** @} */ +template +struct LimitedFloat; + +template +struct quantized_type +{ + using type = LimitedFloat; +}; + +template +struct quantized_type> +{ + using type = LimitedFloat; +}; + +template +using quantized_type_t = typename quantized_type::type; + +template +struct dequantized_type +{ + using type = T; +}; + +template +struct dequantized_type> +{ + using type = U; +}; + +template +using dequantized_type_t = typename dequantized_type::type; + +template +static constexpr bool is_quantized_type = std::is_same_v>; + +template +static constexpr bool is_not_quantized_type = std::is_same_v>; + /** @defgroup HelperConcept concepts for function definitions * the concepts below mostly check for different Opinion types or types within lists * @{ @@ -122,6 +164,15 @@ concept is_opinion_list = (is_opinion::type> template concept is_trusted_opinion_list = (is_trusted_opinion::type> && is_list_of_same); + +template +struct LimitedFloat; + +template +concept is_limited_float = std::is_same_v< + LimitedT, + LimitedFloat>; + /** @} */ /** @defgroup ConstExprHelper constexpr definitions @@ -139,11 +190,34 @@ concept is_trusted_opinion_list = * @param types - list of arbitrary parameters that are passed to func after the index of the current loop */ template -CUDA_AVAIL constexpr void constexpr_for(Func&& func, TYPES... types) +CUDA_AVAIL constexpr void constexpr_for(Func&& func, TYPES&&... types) +{ + if constexpr (Start < End) + { + func(Start, std::forward(types)...); + + // use explicit check before creating template function... + // when not checking here, the functionality might be the same, however, a function is created with Start=End. + // thus, even that it is within if constexpr, an invalid function call syntax is created but not executed. + // unfortunately, by this, the compiler can detect invalid array accesses and produce warnings + if constexpr (Start + Inc < End) + { + constexpr_for(std::forward(func), std::forward(types)...); + } + } +} + +// when building code using nvcc, the constexpr_for above MUST always be buildable for device and host. +// thus, when using constexpr_for in cu code but using std c++ functions (e.g., std::vector::push_back) +// this workaround function must be used. +// If there would be a way of compiling the constexpr_for above only for the context it is used for, this one would no +// longer be needed... +template +constexpr void constexpr_for_host(Func&& func, TYPES&&... types) { if constexpr (Start < End) { - func(Start, types...); + func(Start, std::forward(types)...); // use explicit check before creating template function... // when not checking here, the functionality might be the same, however, a function is created with Start=End. @@ -151,7 +225,7 @@ CUDA_AVAIL constexpr void constexpr_for(Func&& func, TYPES... types) // unfortunately, by this, the compiler can detect invalid array accesses and produce warnings if constexpr (Start + Inc < End) { - constexpr_for(func, types...); + constexpr_for(std::forward(func), std::forward(types)...); } } } diff --git a/library/python/subjective_logic/__init__.py b/library/python/subjective_logic/__init__.py index 9a56c6f..f56cea2 100644 --- a/library/python/subjective_logic/__init__.py +++ b/library/python/subjective_logic/__init__.py @@ -1,5 +1,5 @@ from .trusted_opinion import TrustedOpinion -from .instance_generator import DirichletDistribution, OpinionNoBase, Opinion +from .instance_generator import DirichletDistribution, OpinionNoBase, Opinion, LongShortTermMemory from .draw_sl_opinions import * @@ -8,6 +8,12 @@ from subjective_logic._subjective_logic_lib_python_api import Conflict from subjective_logic._subjective_logic_lib_python_api import TrustRevision +from subjective_logic._subjective_logic_lib_python_api import FusionType +from subjective_logic._subjective_logic_lib_python_api import RelationType +from subjective_logic._subjective_logic_lib_python_api import ConflictType +from subjective_logic._subjective_logic_lib_python_api import TrustRevisionType +from subjective_logic._subjective_logic_lib_python_api import WeightedTypes + from subjective_logic._subjective_logic_lib_python_api import OpinionNoBase2f from subjective_logic._subjective_logic_lib_python_api import OpinionNoBase2d from subjective_logic._subjective_logic_lib_python_api import OpinionNoBase3f @@ -121,3 +127,22 @@ from subjective_logic._subjective_logic_lib_python_api import DirichletDistribution9d from subjective_logic._subjective_logic_lib_python_api import DirichletDistribution10f from subjective_logic._subjective_logic_lib_python_api import DirichletDistribution10d + +from subjective_logic._subjective_logic_lib_python_api import LongShortTermMemory2f +from subjective_logic._subjective_logic_lib_python_api import LongShortTermMemory2d +from subjective_logic._subjective_logic_lib_python_api import LongShortTermMemory3f +from subjective_logic._subjective_logic_lib_python_api import LongShortTermMemory3d +from subjective_logic._subjective_logic_lib_python_api import LongShortTermMemory4f +from subjective_logic._subjective_logic_lib_python_api import LongShortTermMemory4d +from subjective_logic._subjective_logic_lib_python_api import LongShortTermMemory5f +from subjective_logic._subjective_logic_lib_python_api import LongShortTermMemory5d +from subjective_logic._subjective_logic_lib_python_api import LongShortTermMemory6f +from subjective_logic._subjective_logic_lib_python_api import LongShortTermMemory6d +from subjective_logic._subjective_logic_lib_python_api import LongShortTermMemory7f +from subjective_logic._subjective_logic_lib_python_api import LongShortTermMemory7d +from subjective_logic._subjective_logic_lib_python_api import LongShortTermMemory8f +from subjective_logic._subjective_logic_lib_python_api import LongShortTermMemory8d +from subjective_logic._subjective_logic_lib_python_api import LongShortTermMemory9f +from subjective_logic._subjective_logic_lib_python_api import LongShortTermMemory9d +from subjective_logic._subjective_logic_lib_python_api import LongShortTermMemory10f +from subjective_logic._subjective_logic_lib_python_api import LongShortTermMemory10d diff --git a/library/python/subjective_logic/instance_generator.py b/library/python/subjective_logic/instance_generator.py index e70cd7c..7c97008 100644 --- a/library/python/subjective_logic/instance_generator.py +++ b/library/python/subjective_logic/instance_generator.py @@ -1,6 +1,7 @@ import numpy as np import subjective_logic as sl import inspect +import re def gen_instance(name: str, *args): if len(args) == 1: @@ -33,3 +34,10 @@ def Opinion(*args): def OpinionNoBase(*args): return gen_instance(inspect.stack()[0][3], *args) + +def LongShortTermMemory(short_max_size: int, threshold: float, discount: float, fuse_func): + type_match = re.search(r'Opinion(\d+[d,f])',str(fuse_func.__nb_signature__)) + if type_match is None: + raise AttributeError(f'{str(fuse_func)} is not supported by LSTM, did you use Opinion2d? NoBase Opinions are not supported') + lstm_name = f'LongShortTermMemory{type_match.group(1)}' + return getattr(sl, lstm_name)(short_max_size, threshold, discount, fuse_func) diff --git a/library/scripts/test_multi_source.py b/library/scripts/test_multi_source.py new file mode 100644 index 0000000..82485ed --- /dev/null +++ b/library/scripts/test_multi_source.py @@ -0,0 +1,37 @@ +import subjective_logic as sl + +test_a = sl.Opinion2d(0.2,0.5) +test_b = sl.Opinion2d(0.8,0.1) +test_c = sl.Opinion2d(0.3,0.3) + +test_cum = test_a.cum_fuse(test_b) +print('test_cum is ', test_cum) + +multi_cum = sl.Fusion.fuse_opinions(sl.FusionType.CUMULATIVE, [test_a, test_b]) +print('multi_cum is ', multi_cum) +print() + +test_cum.cum_fuse_(test_c) +print('test_cum2 is ', test_cum) +multi_cum = sl.Fusion.fuse_opinions(sl.FusionType.CUMULATIVE, [test_a, test_b, test_c]) +print('multi_cum2 is ', multi_cum) + + + +obs_list = [ + sl.Opinion(0.3, 0.4), + sl.Opinion(0.7, 0.1), + sl.Opinion(0.4, 0.1), + sl.Opinion(0.3, 0.3), + sl.Opinion(0.4, 0.1), + sl.Opinion(0.1, 0.1), + sl.Opinion(0.7, 0.2), + sl.Opinion(0.4, 0.2), +] +cbf_seq = obs_list[0] +for i in range(1, len(obs_list)): + cbf_seq.cum_fuse_(obs_list[i]) +msf_cbf = sl.Fusion.fuse_opinions(sl.FusionType.CUMULATIVE, obs_list) + +print('seq:', cbf_seq) +print('multi:', cbf_seq) diff --git a/library/src/bindings/CMakeLists.txt b/library/src/bindings/CMakeLists.txt index 62250c2..18d0d3f 100644 --- a/library/src/bindings/CMakeLists.txt +++ b/library/src/bindings/CMakeLists.txt @@ -2,6 +2,12 @@ ## Python Bindings ## ##################### +# skip python bindings in coverage build, as it confused the gcovr by producing tons of template instantiations +if (${COVERAGE}) + return() +endif () + + find_package(Python 3.8 COMPONENTS Interpreter Development.Module REQUIRED) if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) @@ -22,8 +28,11 @@ if(nanobind_FOUND) # single opinion type bindings bindings.cpp + container/long_short_term_memory.cpp + types/cuda_compatible_array.cpp types/dirichlet_distribution.cpp + types/fusion_types.cpp opinions/opinion_no_base.cpp opinions/opinion.cpp diff --git a/library/src/bindings/bindings.cpp b/library/src/bindings/bindings.cpp index 1030069..8bb8803 100644 --- a/library/src/bindings/bindings.cpp +++ b/library/src/bindings/bindings.cpp @@ -8,11 +8,13 @@ namespace nb = nanobind; NB_MODULE(_subjective_logic_lib_python_api, m) { + loadLongShortTermMemoryBindings(m); loadCudaCompatibleArrayBindings(m); loadDirichletDistributionBindings(m); loadOpinionBindings(m); loadOpinionNoBaseBindings(m); loadTrustedOpinionBindings(m); + loadAllFusionTypes(m); loadMultiSourceFusionOperatorBindings(m); loadMultiSourceConflictOperatorBindings(m); loadMultiSourceTrustRevisionOperatorBindings(m); diff --git a/library/src/bindings/bindings.hpp b/library/src/bindings/bindings.hpp index ab318e8..062176c 100644 --- a/library/src/bindings/bindings.hpp +++ b/library/src/bindings/bindings.hpp @@ -2,6 +2,7 @@ #include -#include "types/types_bindings.hpp" -#include "opinions/opinions_bindings.hpp" +#include "container/container_bindings.hpp" #include "multi_source/multi_source_bindings.hpp" +#include "opinions/opinions_bindings.hpp" +#include "types/types_bindings.hpp" diff --git a/library/src/bindings/container/container_bindings.hpp b/library/src/bindings/container/container_bindings.hpp new file mode 100644 index 0000000..2b62d44 --- /dev/null +++ b/library/src/bindings/container/container_bindings.hpp @@ -0,0 +1,14 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../template_combination_helper.hpp" + +void loadLongShortTermMemoryBindings(::nanobind::module_& bound_module); diff --git a/library/src/bindings/container/long_short_term_memory.cpp b/library/src/bindings/container/long_short_term_memory.cpp new file mode 100644 index 0000000..0826507 --- /dev/null +++ b/library/src/bindings/container/long_short_term_memory.cpp @@ -0,0 +1,65 @@ +#include "container_bindings.hpp" + +#include "subjective_logic_lib/opinions/opinion.hpp" +#include "subjective_logic_lib/container/long_short_term_memory.hpp" + +#include + +namespace nb = nanobind; +namespace sl = subjective_logic; + +template +struct LSTMemoryLoader +{ + static void load(::nanobind::module_& bound_module) + { + using OpinionT = sl::Opinion; + using LSTM = sl::container::LongShortTermMemory; + std::string module_name{ "LongShortTermMemory" }; + module_name += std::to_string(N); + if constexpr (std::is_same_v) + { + module_name += "d"; + } + else + { + module_name += "f"; + } + + auto bound_class = nb::class_(bound_module, module_name.c_str()) + .def(nb::init()) + .def("add", &LSTM::add) + .def("is_last_conflicted", &LSTM::is_last_conflicted) + .def("get_opinion", &LSTM::get_opinion) + .def("get_long_opinion", &LSTM::get_long_opinion) + .def("get_short_opinion", &LSTM::get_short_opinion); + // .def(nb::init()) + // .def(nb::init()) + // .def_static("from_evidences", &Dirichlet::from_evidences) + // .def_prop_rw( + // "evidences", + // [](Dirichlet& dir) -> WeightType& { return dir.evidences(); }, + // [](Dirichlet dir, WeightType weights) { dir.evidences() = weights; }, + // nb::rv_policy::reference) + // .def("evidences_copy", nb::overload_cast<>(&Dirichlet::evidences, nb::const_)) + // .def_prop_rw( + // "priors", + // [](Dirichlet& dir) -> WeightType& { return dir.priors(); }, + // [](Dirichlet dir, WeightType weights) { dir.priors() = weights; }, + // nb::rv_policy::reference) + // .def("priors_copy", nb::overload_cast<>(&Dirichlet::priors, nb::const_)) + // .def("alphas", &Dirichlet::alphas) + // .def("as_opinion", [](Dirichlet& dir) { return static_cast>(dir); }) + // .def("as_opinion_no_base", [](Dirichlet& dir) { return static_cast>(dir); + // }) .def("evaluate", nb::overload_cast(&Dirichlet::evaluate, nb::const_)) .def("mean", + // &Dirichlet::mean) .def("variances", &Dirichlet::variance) .def("moment_matching_update_", + // &Dirichlet::moment_matching_update_, nb::rv_policy::reference) .def("moment_matching_update", + // &Dirichlet::moment_matching_update) .def("copy", [](const Dirichlet& dir) -> Dirichlet { return dir; + // }); + } +}; + +void loadLongShortTermMemoryBindings(::nanobind::module_& bound_module) +{ + loadBindings(bound_module); +} diff --git a/library/src/bindings/multi_source/conflict_operators.cpp b/library/src/bindings/multi_source/conflict_operators.cpp index e4d86a7..d334f87 100644 --- a/library/src/bindings/multi_source/conflict_operators.cpp +++ b/library/src/bindings/multi_source/conflict_operators.cpp @@ -1,6 +1,7 @@ #include "multi_source_bindings.hpp" #include +#include #include "subjective_logic_lib/multi_source/conflict_operators.hpp" namespace nb = nanobind; @@ -15,9 +16,8 @@ struct MultiSourceConflictLoader static void loadArbitraryNumberOfArguments(::nanobind::class_& bound_class) requires(sizeof...(ARGS) > 0) { - bound_class.def_static( - "conflict", - nb::overload_cast(&slm::Conflict::template conflict)); + bound_class.def_static("conflict", + nb::overload_cast(&slm::Conflict::template conflict)); // add another ARG to the list if constexpr (sizeof...(ARGS) < kMaxArgNumber_) @@ -33,14 +33,14 @@ struct MultiSourceConflictLoader // nb_mod.def_static( // "conflict", - // nb::overload_cast, std::optional>>( + // nb::overload_cast, std::optional>>( // &slm::Conflict::template conflict)); - nb_mod.def_static("conflict", [](slm::Conflict::ConflictType conflict_type, std::vector vec) -> FloatT { + nb_mod.def_static("conflict", [](sl::ConflictType conflict_type, std::vector vec) -> FloatT { return slm::Conflict::conflict(conflict_type, vec); }); nb_mod.def_static( "conflict_selective", - [](slm::Conflict::ConflictType conflict_type, std::vector vec, std::vector vec_bool) -> FloatT { + [](sl::ConflictType conflict_type, std::vector vec, std::vector vec_bool) -> FloatT { return slm::Conflict::conflict(conflict_type, vec, vec_bool); }); @@ -49,25 +49,10 @@ struct MultiSourceConflictLoader } }; -void loadConflictTypes(::nanobind::class_& nb_mod) -{ - nb::enum_(nb_mod, "ConflictType") - .value("ACCUMULATE", slm::Conflict::ConflictType::ACCUMULATE) - .value("AVERAGE", slm::Conflict::ConflictType::AVERAGE) - .value("BELIEF_CUMULATIVE", slm::Conflict::ConflictType::BELIEF_CUMULATIVE) - .value("BELIEF_BELIEF_CONSTRAINT", slm::Conflict::ConflictType::BELIEF_BELIEF_CONSTRAINT) - .value("BELIEF_AVERAGE", slm::Conflict::ConflictType::BELIEF_AVERAGE) - .value("BELIEF_WEIGHTED", slm::Conflict::ConflictType::BELIEF_WEIGHTED); - - nb_mod.def_static("get_belief_fusion_type", &slm::Conflict::get_belief_fusion_type); -} - void loadMultiSourceConflictOperatorBindings(::nanobind::module_& bound_module) { std::string module_name{ "Conflict" }; auto bound_class = nb::class_(bound_module, module_name.c_str()); - loadConflictTypes(bound_class); - loadBindings(bound_class); } diff --git a/library/src/bindings/multi_source/fusion_operators.cpp b/library/src/bindings/multi_source/fusion_operators.cpp index fbec24c..6c1fa21 100644 --- a/library/src/bindings/multi_source/fusion_operators.cpp +++ b/library/src/bindings/multi_source/fusion_operators.cpp @@ -17,9 +17,8 @@ struct MultiSourceFusionLoader static void loadArbitraryNumberOfArguments(::nanobind::class_& bound_class) requires(sizeof...(ARGS) > 0) { - bound_class.def_static( - "fuse_opinions", - nb::overload_cast(&slm::Fusion::template fuse_opinions)); + bound_class.def_static("fuse_opinions", + nb::overload_cast(&slm::Fusion::template fuse_opinions)); // add another ARG to the list if constexpr (sizeof...(ARGS) < kMaxArgNumber_) @@ -34,7 +33,7 @@ struct MultiSourceFusionLoader using OpinionNoBase = sl::OpinionNoBase; nb_mod.def_static("fuse_opinions", - nb::overload_cast>( + nb::overload_cast>( &sl::multisource::Fusion::template fuse_opinions)); loadArbitraryNumberOfArguments(nb_mod); @@ -42,21 +41,10 @@ struct MultiSourceFusionLoader } }; -void loadFusionTypes(::nanobind::class_& nb_mod) -{ - nb::enum_(nb_mod, "FusionType") - .value("CUMULATIVE", slm::Fusion::FusionType::CUMULATIVE) - .value("BELIEF_CONSTRAINT", slm::Fusion::FusionType::BELIEF_CONSTRAINT) - .value("AVERAGE", slm::Fusion::FusionType::AVERAGE) - .value("WEIGHTED", slm::Fusion::FusionType::WEIGHTED); -} - void loadMultiSourceFusionOperatorBindings(::nanobind::module_& bound_module) { std::string module_name{ "Fusion" }; auto bound_class = nb::class_(bound_module, module_name.c_str()); - loadFusionTypes(bound_class); - loadBindings(bound_class); } diff --git a/library/src/bindings/multi_source/trust_revision_operators.cpp b/library/src/bindings/multi_source/trust_revision_operators.cpp index aa2ace5..3ca5894 100644 --- a/library/src/bindings/multi_source/trust_revision_operators.cpp +++ b/library/src/bindings/multi_source/trust_revision_operators.cpp @@ -17,10 +17,9 @@ struct MultiSourceTrustRevisionLoader static void loadArbitraryNumberOfArguments(::nanobind::class_& bound_class) requires(sizeof...(ARGS) > 0) { - bound_class.def_static( - "revision_factors", - nb::overload_cast( - &slm::TrustRevision::template revision_factors)); + bound_class.def_static("revision_factors", + nb::overload_cast( + &slm::TrustRevision::template revision_factors)); // add another ARG to the list if constexpr (sizeof...(ARGS) < kMaxArgNumber_) @@ -35,11 +34,13 @@ struct MultiSourceTrustRevisionLoader using TOpinionNoBase = sl::TrustedOpinion>; nb_mod.def_static("revision_factors", - nb::overload_cast, std::optional>>( &sl::multisource::TrustRevision::template revision_factors), + nb::arg("relation_type"), nb::arg("trust_revision_type"), nb::arg("conflict_type"), nb::arg("opinions"), @@ -50,25 +51,10 @@ struct MultiSourceTrustRevisionLoader } }; -void loadTrustRevisionTypes(::nanobind::class_& nb_mod) -{ - nb::enum_(nb_mod, "TrustRevisionType") - .value("NORMAL", slm::TrustRevision::TrustRevisionType::NORMAL) - .value("HARMONY_NORMAL", slm::TrustRevision::TrustRevisionType::HARMONY_NORMAL) - .value("CONFLICT_SHARES", slm::TrustRevision::TrustRevisionType::CONFLICT_SHARES) - .value("CONFLICT_SHARES_ALLOW_NEGATIVE", slm::TrustRevision::TrustRevisionType::CONFLICT_SHARES_ALLOW_NEGATIVE) - .value("HARMONY_SHARES", slm::TrustRevision::TrustRevisionType::HARMONY_SHARES) - .value("HARMONY_SHARES_ALLOW_NEGATIVE", slm::TrustRevision::TrustRevisionType::HARMONY_SHARES_ALLOW_NEGATIVE) - .value("REFERENCE_FUSION", slm::TrustRevision::TrustRevisionType::REFERENCE_FUSION) - .value("HARMONY_REFERENCE_FUSION", slm::TrustRevision::TrustRevisionType::HARMONY_REFERENCE_FUSION); -} - void loadMultiSourceTrustRevisionOperatorBindings(::nanobind::module_& bound_module) { std::string module_name{ "TrustRevision" }; auto bound_class = nb::class_(bound_module, module_name.c_str()); - loadTrustRevisionTypes(bound_class); - loadBindings(bound_class); } diff --git a/library/src/bindings/multi_source/trusted_fusion_operators.cpp b/library/src/bindings/multi_source/trusted_fusion_operators.cpp index b6c6aed..93387d2 100644 --- a/library/src/bindings/multi_source/trusted_fusion_operators.cpp +++ b/library/src/bindings/multi_source/trusted_fusion_operators.cpp @@ -3,6 +3,7 @@ #include #include +#include #include "subjective_logic_lib/multi_source/trusted_fusion_operators.hpp" namespace nb = nanobind; @@ -17,30 +18,32 @@ struct MultiSourceTrustedFusionLoader { using TrustedOpinionT = sl::TrustedOpinion; nb_mod.def_static("fuse_opinions", - nb::overload_cast&>( + nb::overload_cast&>( &sl::multisource::TrustedFusion::template fuse_opinions)); nb_mod.def_static("fuse_opinions", - nb::overload_cast&>( &sl::multisource::TrustedFusion::template fuse_opinions)); // by explicitly defining the function call, the result is made available in python, since nanobind does not seem to // support input/output parameter nb_mod.def_static("fuse_opinions_", - [](slm::Fusion::FusionType fusion_type, - slm::TrustRevision::TrustRevisionType revision_type, - slm::Conflict::ConflictType conflict_type, + [](sl::FusionType fusion_type, + sl::RelationType relation_type, + sl::TrustRevisionType revision_type, + sl::ConflictType conflict_type, std::vector& t_vec) { auto fusion = slm::TrustedFusion::fuse_opinions_( - fusion_type, revision_type, conflict_type, t_vec); + fusion_type, relation_type, revision_type, conflict_type, t_vec); return std::make_tuple(fusion, t_vec); }); nb_mod.def_static("fuse_opinions", - nb::overload_cast, const std::vector&>( &sl::multisource::TrustedFusion::template fuse_opinions)); @@ -48,7 +51,7 @@ struct MultiSourceTrustedFusionLoader // by explicitly defining the function call, the result is made available in python, since nanobind does not seem to // support input/output parameter nb_mod.def_static("fuse_opinions_", - [](slm::Fusion::FusionType fusion_type, + [](sl::FusionType fusion_type, std::vector types_vec, std::vector& t_vec) { auto fusion = @@ -69,6 +72,15 @@ struct MultiSourceTrustedFusionLoader void loadMultiSourceTrustedFusionOperatorBindings(::nanobind::module_& bound_module) { + using WType = sl::multisource::TrustedFusion::WeightedTypes; + nb::class_(bound_module, "WeightedTypes") + .def(nb::init<>()) + .def(nb::init()) + .def_rw("relation_type", &WType::relation_type) + .def_rw("trust_revision_type", &WType::trust_revision_type) + .def_rw("conflict_type", &WType::conflict_type) + .def_rw("weight", &WType::weight); + std::string module_name{ "TrustedFusion" }; auto bound_class = nb::class_(bound_module, module_name.c_str()); diff --git a/library/src/bindings/opinions/opinion.cpp b/library/src/bindings/opinions/opinion.cpp index f00c478..af47f0a 100644 --- a/library/src/bindings/opinions/opinion.cpp +++ b/library/src/bindings/opinions/opinion.cpp @@ -114,6 +114,8 @@ struct OpinionLoader .def("degree_of_conflict", nb::overload_cast(&Opinion::degree_of_conflict, nb::const_)) .def("degree_of_harmony", nb::overload_cast(&Opinion::degree_of_harmony, nb::const_)) .def("as_dirichlet", [](Opinion& op) { return static_cast>(op); }) + .def("moment_matching_update_", &Opinion::moment_matching_update_, nb::rv_policy::reference) + .def("moment_matching_update", &Opinion::moment_matching_update) .def("cum_fuse_", &Opinion::cum_fuse_, nb::rv_policy::reference) .def("cum_fuse", &Opinion::cum_fuse) .def("cum_unfuse_", &Opinion::cum_unfuse_, nb::rv_policy::reference) @@ -151,6 +153,12 @@ struct OpinionLoader .def("__repr__", &Opinion::to_string) .def("copy", [](const Opinion& opin) -> Opinion { return opin; }); + bound_class.def("__reduce__", [=](const Opinion& a) { + // return its own class to allow calling the ctor later on + return std::make_tuple(bound_class, + std::make_tuple(tuplefy_array(a.belief_masses().entries()), + tuplefy_array(a.prior_belief_masses().entries()))); + }); defineOpinionCtorArgs(bound_class); defineBinomialDependentFields(bound_class); } diff --git a/library/src/bindings/opinions/opinion_no_base.cpp b/library/src/bindings/opinions/opinion_no_base.cpp index d2b8cce..0e3d785 100644 --- a/library/src/bindings/opinions/opinion_no_base.cpp +++ b/library/src/bindings/opinions/opinion_no_base.cpp @@ -80,7 +80,7 @@ struct OpinionNoBaseLoader .def(nb::init(), nb::arg("belief_masses")) .def(nb::init()) .def(nb::init()) - .def("__deepcopy__", [](const Opinion& a, nb::dict memo) -> Opinion { return a; }) + .def("__deepcopy__", [](const Opinion& a, const nb::dict& memo) -> Opinion { return a; }) // access to prop with reference is important!! // otherwise, opinion.belief_masses[0] = 1 inside python would not write to the current opinion .def_prop_rw( @@ -104,6 +104,8 @@ struct OpinionNoBaseLoader .def("degree_of_harmony", nb::overload_cast(&Opinion::degree_of_harmony, nb::const_)) .def("as_dirichlet", [](Opinion& op) { return static_cast>(op); }) + .def("moment_matching_update_", &Opinion::moment_matching_update_, nb::rv_policy::reference) + .def("moment_matching_update", &Opinion::moment_matching_update) .def("cum_fuse_", &Opinion::cum_fuse_, nb::rv_policy::reference) .def("cum_fuse", &Opinion::cum_fuse) .def("cum_unfuse_", &Opinion::cum_unfuse_, nb::rv_policy::reference) @@ -143,6 +145,11 @@ struct OpinionNoBaseLoader .def("__repr__", &Opinion::to_string) .def("copy", [](const Opinion& opin) -> Opinion { return opin; }); + bound_class.def("__reduce__", [=](const Opinion& a) { + // return its own class to allow calling the ctor later on + return std::make_tuple(bound_class, tuplefy_array(a.belief_masses().entries())); + }); + defineOpinionNoBaseCtorArgs(bound_class); defineBinomialDependentFields(bound_class); } diff --git a/library/src/bindings/opinions/trusted_opinion.cpp b/library/src/bindings/opinions/trusted_opinion.cpp index b8b6e3c..eb6f2bb 100644 --- a/library/src/bindings/opinions/trusted_opinion.cpp +++ b/library/src/bindings/opinions/trusted_opinion.cpp @@ -34,9 +34,10 @@ struct TrustedOpinionLoader auto bound_class = nb::class_(bound_module, module_name.c_str()) .def_ro_static("dimension", &TOp::SIZE) - .def_static("extractOpinions", &TOp::extractOpinions) - .def_static("extractTrusts", &TOp::extractTrusts) - .def_static("extractDiscountedOpinions", &TOp::extractDiscountedOpinions) + .def_static("extractOpinions", [](const std::vector& vec) { return TOp::extractOpinions(vec); }) + .def_static("extractTrusts", [](const std::vector& vec) { return TOp::extractTrusts(vec); }) + .def_static("extractDiscountedOpinions", + [](const std::vector& vec) { return TOp::extractDiscountedOpinions(vec); }) .def(nb::init()) .def(nb::init(), nb::arg("belief_masses"), nb::arg("prior_belief_masses")) .def("__deepcopy__", [](const TOp& a, nb::dict memo) -> TOp { return a; }) @@ -60,7 +61,13 @@ struct TrustedOpinionLoader .def("revise_trust_", nb::overload_cast(&TOp::revise_trust_), nb::rv_policy::reference) .def("revise_trust", nb::overload_cast(&TOp::revise_trust, nb::const_)) .def("__repr__", &TOp::to_string) - .def("copy", [](const TOp& top) -> TOp { return top; }); + .def("copy", [](const TOp& top) -> TOp { return top; }) + .def(nb::self == nb::self); + + bound_class.def("__reduce__", [=](const TOp& a) { + // return its own class to allow calling the ctor later on + return std::make_tuple(bound_class, std::make_tuple(a.trust(), a.opinion())); + }); } static void load(::nanobind::module_& bound_module) diff --git a/library/src/bindings/template_combination_helper.hpp b/library/src/bindings/template_combination_helper.hpp index e924e1f..4351716 100644 --- a/library/src/bindings/template_combination_helper.hpp +++ b/library/src/bindings/template_combination_helper.hpp @@ -1,10 +1,13 @@ #pragma once +#include "template_combination_helper.hpp" + #include #include #include #include #include +#include template struct NumberList @@ -58,3 +61,47 @@ void loadBindings(T& nb_mod) TypeList types; loadCombination(nb_mod, numbers, types); } + +template +auto make_tuple_with_type_and_length(std::index_sequence) + -> std::tuple())...>; + +template +using TupleWithTypeAnLength = decltype(make_tuple_with_type_and_length(std::make_index_sequence{})); + +template +auto tuplefy_array(FloatT (&data)[N], std::index_sequence) +{ + return std::make_tuple(data[INDICES]...); +} + +template +auto tuplefy_array(FloatT (&data)[N]) +{ + return tuplefy_array(data, std::make_index_sequence{}); +} + +template +auto tuplefy_array(const std::array& data, std::index_sequence) +{ + return std::make_tuple(data[INDICES]...); +} +template +auto tuplefy_array(const std::array& data) +{ + return tuplefy_array(data, std::make_index_sequence{}); +} + +template +auto arrayfy_tuple(Tuple&& data, std::index_sequence) +{ + using EntryType = std::remove_cvref_t>>; + return std::array{ std::get(data)... }; +} + +template +auto arrayfy_tuple(Tuple&& data) +{ + return arrayfy_tuple(std::forward(data), + std::make_index_sequence>>{}); +} diff --git a/library/src/bindings/types/cuda_compatible_array.cpp b/library/src/bindings/types/cuda_compatible_array.cpp index 2ed15f1..5cb7075 100644 --- a/library/src/bindings/types/cuda_compatible_array.cpp +++ b/library/src/bindings/types/cuda_compatible_array.cpp @@ -81,13 +81,17 @@ struct ArrayLoader return a[idx]; }, nb::rv_policy::reference) - .def("__setitem__", [](Array& a, int idx, FloatT value) { - if (idx < 0) - { - idx += a.size(); - } - a[idx] = value; - }); + .def("__setitem__", + [](Array& a, int idx, FloatT value) { + if (idx < 0) + { + idx += a.size(); + } + a[idx] = value; + }) + .def("__getstate__", [](const Array& a) { return tuplefy_array(a.entries()); }) + .def("__setstate__", + [](Array& a, TupleWithTypeAnLength tuple_data) { a = Array(arrayfy_tuple(tuple_data)); }); defineArrayCtorArgs(bound_class); } }; diff --git a/library/src/bindings/types/dirichlet_distribution.cpp b/library/src/bindings/types/dirichlet_distribution.cpp index 252572e..5704a20 100644 --- a/library/src/bindings/types/dirichlet_distribution.cpp +++ b/library/src/bindings/types/dirichlet_distribution.cpp @@ -60,8 +60,8 @@ struct DirichletLoader auto bound_class = nb::class_(bound_module, module_name.c_str()) .def(nb::init()) - .def(nb::init()) - .def(nb::init()) + .def(nb::init(), nb::arg("alphas")) + .def(nb::init(), nb::arg("evidences"), nb::arg("prior")) .def_static("from_evidences", &Dirichlet::from_evidences) .def_prop_rw( "evidences", @@ -83,7 +83,15 @@ struct DirichletLoader .def("variances", &Dirichlet::variance) .def("moment_matching_update_", &Dirichlet::moment_matching_update_, nb::rv_policy::reference) .def("moment_matching_update", &Dirichlet::moment_matching_update) - .def("copy", [](const Dirichlet& dir) -> Dirichlet { return dir; }); + .def("copy", [](const Dirichlet& dir) -> Dirichlet { return dir; }) + .def(nb::self == nb::self); + + bound_class.def("__reduce__", [=](const Dirichlet& a) { + // return its own class to allow calling the ctor later on + return std::make_tuple( + bound_class, std::make_tuple(tuplefy_array(a.evidences().entries()), tuplefy_array(a.priors().entries()))); + }); + defineArrayCtorArgs(bound_class); defineBinomialDependentFields(bound_class); } diff --git a/library/src/bindings/types/fusion_types.cpp b/library/src/bindings/types/fusion_types.cpp new file mode 100644 index 0000000..9dead92 --- /dev/null +++ b/library/src/bindings/types/fusion_types.cpp @@ -0,0 +1,51 @@ +#include "types_bindings.hpp" + +#include "subjective_logic_lib/types/fusion_types.hpp" + +namespace nb = nanobind; +namespace sl = subjective_logic; + +void loadFusionTypes(::nanobind::module_& bound_module) +{ + nb::enum_(bound_module, "FusionType") + .value("CUMULATIVE", sl::FusionType::CUMULATIVE) + .value("BELIEF_CONSTRAINT", sl::FusionType::BELIEF_CONSTRAINT) + .value("AVERAGE", sl::FusionType::AVERAGE) + .value("WEIGHTED", sl::FusionType::WEIGHTED); +} + +void loadRelationTypes(::nanobind::module_& bound_module) +{ + nb::enum_(bound_module, "RelationType") + .value("HARMONY", sl::RelationType::HARMONY) + .value("CONFLICT", sl::RelationType::CONFLICT); +} + +void loadConflictTypes(::nanobind::module_& bound_module) +{ + nb::enum_(bound_module, "ConflictType") + .value("ACCUMULATE", sl::ConflictType::ACCUMULATE) + .value("AVERAGE", sl::ConflictType::AVERAGE) + .value("BELIEF_CUMULATIVE", sl::ConflictType::BELIEF_CUMULATIVE) + .value("BELIEF_BELIEF_CONSTRAINT", sl::ConflictType::BELIEF_BELIEF_CONSTRAINT) + .value("BELIEF_AVERAGE", sl::ConflictType::BELIEF_AVERAGE) + .value("BELIEF_WEIGHTED", sl::ConflictType::BELIEF_WEIGHTED); + + bound_module.def("get_belief_fusion_type", &sl::get_belief_fusion_type); +} +void loadTrustRevisionTypes(::nanobind::module_& bound_module) +{ + nb::enum_(bound_module, "TrustRevisionType") + .value("NORMAL", sl::TrustRevisionType::NORMAL) + .value("SHARES", sl::TrustRevisionType::SHARES) + .value("SHARES_ALLOW_NEGATIVE", sl::TrustRevisionType::SHARES_ALLOW_NEGATIVE) + .value("REFERENCE_FUSION", sl::TrustRevisionType::REFERENCE_FUSION); +} + +void loadAllFusionTypes(::nanobind::module_& bound_module) +{ + loadFusionTypes(bound_module); + loadRelationTypes(bound_module); + loadConflictTypes(bound_module); + loadTrustRevisionTypes(bound_module); +} diff --git a/library/src/bindings/types/types_bindings.hpp b/library/src/bindings/types/types_bindings.hpp index 9f3da00..c594cd3 100644 --- a/library/src/bindings/types/types_bindings.hpp +++ b/library/src/bindings/types/types_bindings.hpp @@ -13,3 +13,4 @@ void loadCudaCompatibleArrayBindings(::nanobind::module_& bound_module); void loadDirichletDistributionBindings(::nanobind::module_& bound_module); +void loadAllFusionTypes(::nanobind::module_& bound_module); \ No newline at end of file diff --git a/library/test/CMakeLists.txt b/library/test/CMakeLists.txt index f9219b9..fd5a704 100644 --- a/library/test/CMakeLists.txt +++ b/library/test/CMakeLists.txt @@ -10,6 +10,7 @@ else() set(UNITTEST_PLAYGROUND_EXECUTABLES "") add_subdirectory(unittest/subjective_logic) + add_subdirectory(standalone) # append the unittests to the global list of tests set(TEST_EXECUTABLES "${TEST_EXECUTABLES};${UNITTEST_EXECUTABLES}" PARENT_SCOPE) diff --git a/library/test/standalone/CMakeLists.txt b/library/test/standalone/CMakeLists.txt new file mode 100644 index 0000000..3ee9cdc --- /dev/null +++ b/library/test/standalone/CMakeLists.txt @@ -0,0 +1,58 @@ + +if (${COVERAGE}) +# by linking the subjective_logic_lib to the operator tests, they automatically get ftest_coverage flagged during coverage builds... +# as a result, gcovr tries to parse those files and becomes super unhappy... +# thus, they are simply omitted... + MESSAGE(WARNING "Skipping Operator test build in Coverage mode, due to errors with cuda compiled files and gcovc") +else () + set(SL_OPERATOR_TEST_FILES + operator_test.cpp + ) + add_executable(operator_test + ${SL_OPERATOR_TEST_FILES} + ) + + foreach(target operator_test) + target_link_libraries(${target} + PUBLIC + subjective_logic_lib::subjective_logic_lib + ) + + target_compile_features(${target} PUBLIC cxx_std_20) + endforeach() + + set(CUDA_TEST_TARGETS "") + include(CheckLanguage) + check_language(CUDA) + + if (CMAKE_CUDA_COMPILER) + message(STATUS "CUDA compiler found: ${CMAKE_CUDA_COMPILER}\nbuilding CUDA tests for subjective_logic_lib") + enable_language(CUDA) + set(SL_OPERATOR_TEST_CU_FILES + operator_test.cu + ) + add_executable(operator_test_cu + ${SL_OPERATOR_TEST_CU_FILES} + ) + + foreach(target operator_test_cu) + target_link_libraries(${target} + PUBLIC + -lstdc++ + subjective_logic_lib::subjective_logic_lib + ) + + target_compile_features(${target} PUBLIC cxx_std_20) + target_compile_options(${target} INTERFACE $<$:--extended-lambda>) + target_compile_features(${target} PUBLIC cuda_std_20) + endforeach() + set(CUDA_TEST_TARGETS "operator_test_cu") + + add_subdirectory(multi_source) + else() + message(WARNING "CUDA compiler not found, skipping CUDA tests") + endif() + + set(UNITTEST_PLAYGROUND_EXECUTABLES "${UNITTEST_PLAYGROUND_EXECUTABLES};operator_test;${CUDA_TEST_TARGETS}" PARENT_SCOPE) +endif () + diff --git a/library/test/standalone/multi_source/CMakeLists.txt b/library/test/standalone/multi_source/CMakeLists.txt new file mode 100644 index 0000000..a6920ef --- /dev/null +++ b/library/test/standalone/multi_source/CMakeLists.txt @@ -0,0 +1,27 @@ +enable_language(CUDA) + +add_executable(multi_operator_test_cu + multi_source_conflict.cu + multi_source_fusion.cu + multi_source_trust_revision.cu + multi_source_trusted_fusion.cu + test.cu +) + + +foreach(target multi_operator_test_cu) + target_link_libraries(${target} + PUBLIC + -lstdc++ + subjective_logic_lib::subjective_logic_lib + ) + + target_compile_features(${target} PUBLIC cxx_std_20) + target_compile_options(${target} INTERFACE $<$:--extended-lambda>) +# target_compile_options(${target} PUBLIC $<$:--expt-relaxed-constexpr>) + target_compile_features(${target} PUBLIC cuda_std_20) + + set(CUDA_TEST_TARGETS "${CUDA_TEST_TARGETS};${target}" PARENT_SCOPE) +endforeach() + + diff --git a/library/test/standalone/multi_source/multi_source_conflict.cu b/library/test/standalone/multi_source/multi_source_conflict.cu new file mode 100644 index 0000000..afa00d8 --- /dev/null +++ b/library/test/standalone/multi_source/multi_source_conflict.cu @@ -0,0 +1,153 @@ +#include +#include +#include "subjective_logic_lib/opinions/opinion.hpp" +#include "subjective_logic_lib/multi_source/conflict_operators.hpp" + + +namespace sl = subjective_logic; +using Opinion = sl::Opinion<2,float>; +constexpr std::size_t n_opinions{1 << 20}; +// constexpr std::size_t n_opinions{1 << 0}; + +#define CUDA_CHECK(call) do { cudaError_t err = call; \ +if (err != cudaSuccess) { \ +fprintf(stderr, "CUDA Error: %s (%d) ", cudaGetErrorString(err), err); exit(EXIT_FAILURE); \ +}\ +} while (0) + + +__global__ static void run_uncert_differentials(Opinion* a, Opinion* b, Opinion* c, sl::Array<3, float>* dest) { + std::size_t idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx >= n_opinions) {return;} + + sl::Array<3,Opinion> opins{a[idx], b[idx], c[idx]}; + dest[idx] = sl::multisource::Conflict::uncertainty_differentials(opins); +} + +void check_run_uncert_differentials() { + std::cout << "testing uncert_differentials with 3 opinions " << std::to_string(n_opinions) << " times" << std::endl; + std::vector input_a(n_opinions); + std::vector input_b(n_opinions); + std::vector input_c(n_opinions); + for (std::size_t idx = 0; idx < n_opinions; ++idx) { + // a uncert = 0.2 + input_a[idx].belief_masses()[0] = 0.8; + // b uncert = 0.8 + input_b[idx].belief_masses()[1] = 0.2; + // c uncert = 0.6 + input_c[idx].belief_masses()[0] = 0.2; + input_c[idx].belief_masses()[1] = 0.2; + + // sum of uncert = 1.6 + } + // average prior + Opinion *op_a, *op_b, *op_c; + using DestType = sl::Array<3,Opinion::FLOAT_t>; + DestType *dest; + cudaMalloc(&op_a, sizeof(Opinion)*n_opinions); + cudaMalloc(&op_b, sizeof(Opinion)*n_opinions); + cudaMalloc(&op_c, sizeof(Opinion)*n_opinions); + cudaMalloc(&dest, sizeof(DestType)*n_opinions); + + cudaMemcpy(op_a, input_a.data(), sizeof(Opinion)*n_opinions,cudaMemcpyHostToDevice); + cudaMemcpy(op_b, input_b.data(), sizeof(Opinion)*n_opinions,cudaMemcpyHostToDevice); + cudaMemcpy(op_c, input_c.data(), sizeof(Opinion)*n_opinions,cudaMemcpyHostToDevice); + + CUDA_CHECK(cudaDeviceSynchronize()); + auto start_time = std::chrono::high_resolution_clock::now(); + run_uncert_differentials<<>>(op_a, op_b, op_c, dest); + CUDA_CHECK(cudaDeviceSynchronize()); + auto end_time = std::chrono::high_resolution_clock::now(); + + std::vector dest_cpu; + dest_cpu.resize(n_opinions); + + cudaMemcpy(dest_cpu.data(), dest, sizeof(DestType)*n_opinions,cudaMemcpyDeviceToHost); + + std::cout << dest_cpu[0] << std::endl; + std::cout << "took " << std::chrono::duration(end_time-start_time) << std::endl; + + cudaFree(&op_a); + cudaFree(&op_b); + cudaFree(&dest); +} + +__global__ static void run_conflict_shares(Opinion* a, Opinion* b, Opinion* c, Opinion* d, sl::Array<4, float>* dest) { + std::size_t idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx >= n_opinions) {return;} + + sl::Array<4,Opinion> opins{a[idx], b[idx], c[idx], d[idx]}; + + float avg_conflict = 0; + dest[idx] = sl::multisource::Conflict::conflict_shares(sl::RelationType::CONFLICT, sl::ConflictType::AVERAGE, opins, avg_conflict); +} + +void check_run_conflict_shares() { + std::cout << "testing conflict_shares with 4 opinions " << std::to_string(n_opinions) << " times" << std::endl; + std::vector input_a(n_opinions); + std::vector input_b(n_opinions); + std::vector input_c(n_opinions); + std::vector input_d(n_opinions); + for (std::size_t idx = 0; idx < n_opinions; ++idx) { + // a uncert = 0.2 + input_a[idx].belief_masses()[0] = 0.8; + // b uncert = 0.8 + input_b[idx].belief_masses()[1] = 0.2; + // c uncert = 0.6 + input_c[idx].belief_masses()[0] = 0.2; + input_c[idx].belief_masses()[1] = 0.2; + // d uncert = 0.3 + input_c[idx].belief_masses()[0] = 0.5; + input_c[idx].belief_masses()[1] = 0.2; + + // sum of uncert = 1.9 + } + // average prior + Opinion *op_a, *op_b, *op_c, *op_d; + using DestType = sl::Array<4,Opinion::FLOAT_t>; + DestType *dest; + cudaMalloc(&op_a, sizeof(Opinion)*n_opinions); + cudaMalloc(&op_b, sizeof(Opinion)*n_opinions); + cudaMalloc(&op_c, sizeof(Opinion)*n_opinions); + cudaMalloc(&op_d, sizeof(Opinion)*n_opinions); + cudaMalloc(&dest, sizeof(DestType)*n_opinions); + + cudaMemcpy(op_a, input_a.data(), sizeof(Opinion)*n_opinions,cudaMemcpyHostToDevice); + cudaMemcpy(op_b, input_b.data(), sizeof(Opinion)*n_opinions,cudaMemcpyHostToDevice); + cudaMemcpy(op_c, input_c.data(), sizeof(Opinion)*n_opinions,cudaMemcpyHostToDevice); + cudaMemcpy(op_d, input_d.data(), sizeof(Opinion)*n_opinions,cudaMemcpyHostToDevice); + + CUDA_CHECK(cudaDeviceSynchronize()); + auto start_time = std::chrono::high_resolution_clock::now(); + run_conflict_shares<<>>(op_a, op_b, op_c, op_d, dest); + CUDA_CHECK(cudaDeviceSynchronize()); + auto end_time = std::chrono::high_resolution_clock::now(); + + std::vector dest_cpu; + dest_cpu.resize(n_opinions); + + cudaMemcpy(dest_cpu.data(), dest, sizeof(DestType)*n_opinions,cudaMemcpyDeviceToHost); + + std::cout << dest_cpu[0] << std::endl; + std::cout << "took " << std::chrono::duration(end_time-start_time) << std::endl; + auto shares = sl::multisource::Conflict::conflict_shares( + sl::RelationType::CONFLICT, + sl::ConflictType::AVERAGE, + std::vector{ + input_a.front(), + input_b.front(), + input_c.front(), + input_d.front() + }) ; + + std::cout << "reference conflict: avg " << shares.first << " | " ; + for (auto entry:shares.second) + std::cout << entry << ", "; + std::cout << std::endl; + + cudaFree(&op_a); + cudaFree(&op_b); + cudaFree(&op_c); + cudaFree(&op_d); + cudaFree(&dest); +} diff --git a/library/test/standalone/multi_source/multi_source_fusion.cu b/library/test/standalone/multi_source/multi_source_fusion.cu new file mode 100644 index 0000000..940e724 --- /dev/null +++ b/library/test/standalone/multi_source/multi_source_fusion.cu @@ -0,0 +1,169 @@ +#include +#include +#include "subjective_logic_lib/opinions/opinion.hpp" +#include "subjective_logic_lib/multi_source/fusion_operators.hpp" + +namespace sl = subjective_logic; +using Opinion = sl::Opinion<2,float>; +constexpr std::size_t n_opinions{1 << 20}; +// constexpr std::size_t n_opinions{1 << 0}; + +#define CUDA_CHECK(call) do { cudaError_t err = call; \ +if (err != cudaSuccess) { \ +fprintf(stderr, "CUDA Error: %s (%d) ", cudaGetErrorString(err), err); exit(EXIT_FAILURE); \ +}\ +} while (0) + + +__global__ static void run_prior_average(Opinion* a, Opinion* b, Opinion::BeliefType* dest) { + std::size_t idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx >= n_opinions) {return;} + + sl::Array<2,Opinion> opins{a[idx],b[idx]}; + + dest[idx] = sl::multisource::Fusion::average_prior(opins); +} + +void check_run_prior_average() { + std::cout << "testing average_prior with " << std::to_string(n_opinions) << " opinions" << std::endl; + std::vector input_a(n_opinions); + std::vector input_b(n_opinions); + for (std::size_t idx = 0; idx < n_opinions; ++idx) { + input_a[idx].prior_belief_masses()[0] = 0.8; + input_a[idx].prior_belief_masses()[1] = 0.2; + } + // average prior + Opinion *op_a, *op_b; + using DestType = Opinion::BeliefType; + DestType *dest; + cudaMalloc(&op_a, sizeof(Opinion)*n_opinions); + cudaMalloc(&op_b, sizeof(Opinion)*n_opinions); + cudaMalloc(&dest, sizeof(DestType)*n_opinions); + + cudaMemcpy(op_a, input_a.data(), sizeof(Opinion)*n_opinions,cudaMemcpyHostToDevice); + cudaMemcpy(op_b, input_b.data(), sizeof(Opinion)*n_opinions,cudaMemcpyHostToDevice); + + CUDA_CHECK(cudaDeviceSynchronize()); + auto start_time = std::chrono::high_resolution_clock::now(); + run_prior_average<<>>(op_a, op_b, dest); + CUDA_CHECK(cudaDeviceSynchronize()); + auto end_time = std::chrono::high_resolution_clock::now(); + + std::vector dest_cpu; + dest_cpu.resize(n_opinions); + + cudaMemcpy(dest_cpu.data(), dest, sizeof(DestType)*n_opinions,cudaMemcpyDeviceToHost); + + std::cout << dest_cpu[0] << std::endl; + std::cout << "took " << std::chrono::duration(end_time-start_time) << std::endl; + + cudaFree(&op_a); + cudaFree(&op_b); + cudaFree(&dest); +} + +__global__ static void run_preprocess_uncertainties(Opinion* a, Opinion* b, sl::Array<2,Opinion::FLOAT_t>* dest) { + std::size_t idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx >= n_opinions) {return;} + + sl::Array<2,Opinion> opins{a[idx],b[idx]}; + + auto result= sl::multisource::Fusion::preprocess_opinions(opins, dest[idx]); +} + +void check_run_preprocess_uncertainties() { + std::cout << "testing preprocess uncertainties with " << std::to_string(n_opinions) << " opinions" << std::endl; + std::vector input_a(n_opinions); + std::vector input_b(n_opinions); + for (std::size_t idx = 0; idx < n_opinions; ++idx) { + input_a[idx].belief() = 0.3; + input_b[idx].disbelief() = 0.5; + } + // average prior + Opinion *op_a, *op_b; + using DestType = sl::Array<2,Opinion::FLOAT_t>; + DestType *dest; + cudaMalloc(&op_a, sizeof(Opinion)*n_opinions); + cudaMalloc(&op_b, sizeof(Opinion)*n_opinions); + cudaMalloc(&dest, sizeof(DestType)*n_opinions); + + cudaMemcpy(op_a, input_a.data(), sizeof(Opinion)*n_opinions,cudaMemcpyHostToDevice); + cudaMemcpy(op_b, input_b.data(), sizeof(Opinion)*n_opinions,cudaMemcpyHostToDevice); + + CUDA_CHECK(cudaDeviceSynchronize()); + auto start_time = std::chrono::high_resolution_clock::now(); + run_preprocess_uncertainties<<>>(op_a, op_b, dest); + CUDA_CHECK(cudaDeviceSynchronize()); + auto end_time = std::chrono::high_resolution_clock::now(); + + std::vector dest_cpu; + dest_cpu.resize(n_opinions); + + cudaMemcpy(dest_cpu.data(), dest, sizeof(DestType)*n_opinions,cudaMemcpyDeviceToHost); + + std::cout << dest_cpu[0] << std::endl; + std::cout << "took " << std::chrono::duration(end_time-start_time) << std::endl; + + cudaFree(&op_a); + cudaFree(&op_b); + cudaFree(&dest); +} + + +__global__ static void run_fuse_opinions(Opinion* a, Opinion* b, Opinion* c, Opinion* dest) { + std::size_t idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx >= n_opinions) {return;} + + sl::Array<3,Opinion> opins{a[idx], b[idx], c[idx]}; + + dest[idx] = sl::multisource::Fusion::fuse_opinions(sl::FusionType::CUMULATIVE, opins); +} + +void check_run_fuse_opinions() { + std::cout << "testing multi-source opinion fusion with 3 opinions " << std::to_string(n_opinions) << " times" << std::endl; + std::vector input_a(n_opinions); + std::vector input_b(n_opinions); + std::vector input_c(n_opinions); + for (std::size_t idx = 0; idx < n_opinions; ++idx) { + input_a[idx].belief() = 0.3; + input_b[idx].disbelief() = 0.5; + input_c[idx].belief() = 0.5; + input_c[idx].disbelief() = 0.2; + } + // average prior + Opinion *op_a, *op_b, *op_c; + using DestType = Opinion; + DestType *dest; + cudaMalloc(&op_a, sizeof(Opinion)*n_opinions); + cudaMalloc(&op_b, sizeof(Opinion)*n_opinions); + cudaMalloc(&op_c, sizeof(Opinion)*n_opinions); + cudaMalloc(&dest, sizeof(DestType)*n_opinions); + + cudaMemcpy(op_a, input_a.data(), sizeof(Opinion)*n_opinions,cudaMemcpyHostToDevice); + cudaMemcpy(op_b, input_b.data(), sizeof(Opinion)*n_opinions,cudaMemcpyHostToDevice); + cudaMemcpy(op_c, input_c.data(), sizeof(Opinion)*n_opinions,cudaMemcpyHostToDevice); + + CUDA_CHECK(cudaDeviceSynchronize()); + auto start_time = std::chrono::high_resolution_clock::now(); + run_fuse_opinions<<>>(op_a, op_b, op_c, dest); + CUDA_CHECK(cudaDeviceSynchronize()); + auto end_time = std::chrono::high_resolution_clock::now(); + + std::vector dest_cpu; + dest_cpu.resize(n_opinions); + + cudaMemcpy(dest_cpu.data(), dest, sizeof(DestType)*n_opinions,cudaMemcpyDeviceToHost); + + std::cout << dest_cpu[0] << std::endl; + std::cout << "took " << std::chrono::duration(end_time-start_time) << std::endl; + std::cout << "reference fusion: " << sl::multisource::Fusion::fuse_opinions(sl::FusionType::CUMULATIVE, + std::vector{ + input_a.front(), + input_b.front(), + input_c.front() + }) << std::endl; + + cudaFree(&op_a); + cudaFree(&op_b); + cudaFree(&dest); +} diff --git a/library/test/standalone/multi_source/multi_source_trust_revision.cu b/library/test/standalone/multi_source/multi_source_trust_revision.cu new file mode 100644 index 0000000..cb0afc0 --- /dev/null +++ b/library/test/standalone/multi_source/multi_source_trust_revision.cu @@ -0,0 +1,104 @@ +#include +#include +#include "subjective_logic_lib/opinions/opinion.hpp" +#include "subjective_logic_lib/opinions/trusted_opinion.hpp" +#include "subjective_logic_lib/multi_source/trust_revision_operators.hpp" + +namespace sl = subjective_logic; +using Opinion = sl::Opinion<2,float>; +using TrustedOpinion = sl::TrustedOpinion; +constexpr Opinion trust{Opinion::BeliefType{0.,0.},Opinion::BeliefType{1.0,0.0}}; +constexpr std::size_t n_opinions{1 << 20}; +// constexpr std::size_t n_opinions{1 << 0}; + +#define CUDA_CHECK(call) do { cudaError_t err = call; \ +if (err != cudaSuccess) { \ +fprintf(stderr, "CUDA Error: %s (%d) ", cudaGetErrorString(err), err); exit(EXIT_FAILURE); \ +}\ +} while (0) + + +__global__ static void run_revision_factors(Opinion* a, Opinion* b, Opinion* c, Opinion* d, sl::Array<4, float>* dest) { + std::size_t idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx >= n_opinions) {return;} + + sl::Array<4,TrustedOpinion> topins{ + sl::TrustedOpinion{trust,a[idx]}, + sl::TrustedOpinion{trust,b[idx]}, + sl::TrustedOpinion{trust,c[idx]}, + sl::TrustedOpinion{trust,d[idx]} + }; + // float avg_conflict = 0; + dest[idx] = sl::multisource::TrustRevision::revision_factors<4, TrustedOpinion>(sl::RelationType::CONFLICT, sl::TrustRevisionType::SHARES, sl::ConflictType::AVERAGE, topins); +} + +void check_run_revsion_factors() { + std::cout << "testing revision factors with 4 opinions " << std::to_string(n_opinions) << " times" << std::endl; + std::vector input_a(n_opinions); + std::vector input_b(n_opinions); + std::vector input_c(n_opinions); + std::vector input_d(n_opinions); + for (std::size_t idx = 0; idx < n_opinions; ++idx) { + // a uncert = 0.2 + input_a[idx].belief_masses()[0] = 0.8; + // b uncert = 0.8 + input_b[idx].belief_masses()[1] = 0.2; + // c uncert = 0.6 + input_c[idx].belief_masses()[0] = 0.2; + input_c[idx].belief_masses()[1] = 0.2; + // d uncert = 0.3 + input_c[idx].belief_masses()[0] = 0.5; + input_c[idx].belief_masses()[1] = 0.2; + + // sum of uncert = 1.9 + } + // average prior + Opinion *op_a, *op_b, *op_c, *op_d; + using DestType = sl::Array<4,Opinion::FLOAT_t>; + DestType *dest; + cudaMalloc(&op_a, sizeof(Opinion)*n_opinions); + cudaMalloc(&op_b, sizeof(Opinion)*n_opinions); + cudaMalloc(&op_c, sizeof(Opinion)*n_opinions); + cudaMalloc(&op_d, sizeof(Opinion)*n_opinions); + cudaMalloc(&dest, sizeof(DestType)*n_opinions); + + cudaMemcpy(op_a, input_a.data(), sizeof(Opinion)*n_opinions,cudaMemcpyHostToDevice); + cudaMemcpy(op_b, input_b.data(), sizeof(Opinion)*n_opinions,cudaMemcpyHostToDevice); + cudaMemcpy(op_c, input_c.data(), sizeof(Opinion)*n_opinions,cudaMemcpyHostToDevice); + cudaMemcpy(op_d, input_d.data(), sizeof(Opinion)*n_opinions,cudaMemcpyHostToDevice); + + CUDA_CHECK(cudaDeviceSynchronize()); + auto start_time = std::chrono::high_resolution_clock::now(); + run_revision_factors<<>>(op_a, op_b, op_c, op_d, dest); + CUDA_CHECK(cudaDeviceSynchronize()); + auto end_time = std::chrono::high_resolution_clock::now(); + + std::vector dest_cpu; + dest_cpu.resize(n_opinions); + + cudaMemcpy(dest_cpu.data(), dest, sizeof(DestType)*n_opinions,cudaMemcpyDeviceToHost); + + std::cout << dest_cpu[0] << std::endl; + std::cout << "took " << std::chrono::duration(end_time-start_time) << std::endl; + auto revision_factors = sl::multisource::TrustRevision::revision_factors( + sl::RelationType::CONFLICT, + sl::TrustRevisionType::SHARES, + sl::ConflictType::AVERAGE, + std::vector{ + {trust,input_a.front()}, + {trust,input_b.front()}, + {trust,input_c.front()}, + {trust,input_d.front()}, + }) ; + + std::cout << "revision factors "; + for (auto entry:revision_factors) + std::cout << entry << ", "; + std::cout << std::endl; + + cudaFree(&op_a); + cudaFree(&op_b); + cudaFree(&op_c); + cudaFree(&op_d); + cudaFree(&dest); +} diff --git a/library/test/standalone/multi_source/multi_source_trusted_fusion.cu b/library/test/standalone/multi_source/multi_source_trusted_fusion.cu new file mode 100644 index 0000000..564c904 --- /dev/null +++ b/library/test/standalone/multi_source/multi_source_trusted_fusion.cu @@ -0,0 +1,108 @@ +#include +#include +#include "subjective_logic_lib/opinions/opinion.hpp" +#include "subjective_logic_lib/opinions/trusted_opinion.hpp" +#include "subjective_logic_lib/multi_source/trusted_fusion_operators.hpp" + +namespace sl = subjective_logic; +using Opinion = sl::Opinion<2,float>; +using TrustedOpinion = sl::TrustedOpinion; +constexpr Opinion trust{Opinion::BeliefType{0.,0.},Opinion::BeliefType{1.0,0.0}}; +constexpr std::size_t n_opinions{1 << 20}; +// constexpr std::size_t n_opinions{1 << 0}; + +constexpr sl::Array<1,sl::multisource::TrustedFusion::WeightedTypes> weights{sl::multisource::TrustedFusion::WeightedTypes{ + sl::RelationType::CONFLICT, + sl::TrustRevisionType::SHARES, + sl::ConflictType::AVERAGE, + 1.0 +}}; + +#define CUDA_CHECK(call) do { cudaError_t err = call; \ +if (err != cudaSuccess) { \ +fprintf(stderr, "CUDA Error: %s (%d) ", cudaGetErrorString(err), err); exit(EXIT_FAILURE); \ +}\ +} while (0) + + +__global__ static void run_trusted_fusion(Opinion* a, Opinion* b, Opinion* c, Opinion* d, Opinion* dest) { + std::size_t idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx >= n_opinions) {return;} + + sl::Array<4,TrustedOpinion> topins{ + sl::TrustedOpinion{trust,a[idx]}, + sl::TrustedOpinion{trust,b[idx]}, + sl::TrustedOpinion{trust,c[idx]}, + sl::TrustedOpinion{trust,d[idx]} + }; + + dest[idx] = sl::multisource::TrustedFusion::fuse_opinions(sl::FusionType::CUMULATIVE, weights, topins); +} + +void check_run_trusted_fusion() { + std::cout << "testing trusted fusion with 4 opinions " << std::to_string(n_opinions) << " times" << std::endl; + std::vector input_a(n_opinions); + std::vector input_b(n_opinions); + std::vector input_c(n_opinions); + std::vector input_d(n_opinions); + for (std::size_t idx = 0; idx < n_opinions; ++idx) { + // a uncert = 0.2 + input_a[idx].belief_masses()[0] = 0.8; + // b uncert = 0.8 + input_b[idx].belief_masses()[1] = 0.2; + // c uncert = 0.6 + input_c[idx].belief_masses()[0] = 0.2; + input_c[idx].belief_masses()[1] = 0.2; + // d uncert = 0.3 + input_c[idx].belief_masses()[0] = 0.5; + input_c[idx].belief_masses()[1] = 0.2; + + // sum of uncert = 1.9 + } + // average prior + Opinion *op_a, *op_b, *op_c, *op_d; + using DestType = Opinion; + DestType *dest; + cudaMalloc(&op_a, sizeof(Opinion)*n_opinions); + cudaMalloc(&op_b, sizeof(Opinion)*n_opinions); + cudaMalloc(&op_c, sizeof(Opinion)*n_opinions); + cudaMalloc(&op_d, sizeof(Opinion)*n_opinions); + cudaMalloc(&dest, sizeof(DestType)*n_opinions); + + cudaMemcpy(op_a, input_a.data(), sizeof(Opinion)*n_opinions,cudaMemcpyHostToDevice); + cudaMemcpy(op_b, input_b.data(), sizeof(Opinion)*n_opinions,cudaMemcpyHostToDevice); + cudaMemcpy(op_c, input_c.data(), sizeof(Opinion)*n_opinions,cudaMemcpyHostToDevice); + cudaMemcpy(op_d, input_d.data(), sizeof(Opinion)*n_opinions,cudaMemcpyHostToDevice); + + CUDA_CHECK(cudaDeviceSynchronize()); + auto start_time = std::chrono::high_resolution_clock::now(); + run_trusted_fusion<<>>(op_a, op_b, op_c, op_d, dest); + CUDA_CHECK(cudaDeviceSynchronize()); + auto end_time = std::chrono::high_resolution_clock::now(); + + std::vector dest_cpu; + dest_cpu.resize(n_opinions); + + cudaMemcpy(dest_cpu.data(), dest, sizeof(DestType)*n_opinions,cudaMemcpyDeviceToHost); + + std::cout << dest_cpu[0] << std::endl; + std::cout << "took " << std::chrono::duration(end_time-start_time) << std::endl; + auto weight_vec = weights.as_vector(); + auto fused_opin = sl::multisource::TrustedFusion::fuse_opinions( + sl::FusionType::CUMULATIVE, + weight_vec, + std::vector{ + {trust,input_a.front()}, + {trust,input_b.front()}, + {trust,input_c.front()}, + {trust,input_d.front()}, + }) ; + + std::cout << "reference fused opinion: " << fused_opin << std::endl; + + cudaFree(&op_a); + cudaFree(&op_b); + cudaFree(&op_c); + cudaFree(&op_d); + cudaFree(&dest); +} diff --git a/library/test/standalone/multi_source/test.cu b/library/test/standalone/multi_source/test.cu new file mode 100644 index 0000000..10b2025 --- /dev/null +++ b/library/test/standalone/multi_source/test.cu @@ -0,0 +1,37 @@ + + + +// Fusion +void check_run_prior_average(); +void check_run_preprocess_uncertainties(); +void check_run_fuse_opinions(); + +// Conflict +void check_run_uncert_differentials(); +void check_run_conflict_shares(); + +// Revision factors +void check_run_revsion_factors(); + +// Trusted fusion +void check_run_trusted_fusion(); + +int main(int argc, char **argv) { + + // Fusion + check_run_prior_average(); + check_run_preprocess_uncertainties(); + check_run_fuse_opinions(); + + // Conflict + check_run_uncert_differentials(); + check_run_conflict_shares(); + + // Revision factors + check_run_revsion_factors(); + + // Trusted fusion + check_run_trusted_fusion(); + + return 0; +} diff --git a/library/test/standalone/operator_test.cpp b/library/test/standalone/operator_test.cpp new file mode 100644 index 0000000..34c2017 --- /dev/null +++ b/library/test/standalone/operator_test.cpp @@ -0,0 +1,172 @@ +#include +#include + +#include "subjective_logic_lib/opinions/opinion.hpp" +namespace sl = subjective_logic; +using OpT = sl::OpinionNoBase<2, float>; + +int main(int argc, char** argv) +{ + constexpr long long n_runs{ 1LL << 20 }; + + OpT op_a{ 0.2, 0.4 }; + OpT op_b{ 0.5, 0.1 }; + OpT op_c; + asm volatile("" : : "r,m"(op_c) : "memory"); + float proj{}; + asm volatile("" : : "r,m"(proj) : "memory"); + + std::cout << "running an experiment with " << n_runs << " runs" << std::endl; + + auto start = std::chrono::system_clock::now(); + + for (std::size_t idx{ 0 }; idx < n_runs; ++idx) + { + op_c = op_a.cum_fuse(op_b); + // proj = op_a.getBinomialProjection(); + } + + auto end = std::chrono::system_clock::now(); + + std::cout << "last op: " << op_c << std::endl; + // std::cout << "last proj: " << proj << std::endl; + std::chrono::duration diff_time = end - start; + std::cout << "time float: " << diff_time << std::endl; + std::cout << "avg time float: " << (diff_time / n_runs) << std::endl; + + auto op_a_quant = op_a.get_quantized(); + auto op_b_quant = op_b.get_quantized(); + decltype(op_a_quant) op_c_quant; + asm volatile("" : : "r,m"(op_c_quant) : "memory"); + float proj2{}; + asm volatile("" : : "r,m"(proj2) : "memory"); + + constexpr bool test_type = + std::is_same_v>>; + static_assert(test_type); + + start = std::chrono::system_clock::now(); + + for (std::size_t idx{ 0 }; idx < n_runs; ++idx) + { + op_c_quant = op_a_quant.cum_fuse(op_b_quant); + // proj2 = op_a_quant.getBinomialProjection().as_limit_type(); + } + + end = std::chrono::system_clock::now(); + + // std::cout << "last proj: " << proj2 << std::endl; + std::cout << "last op: " << op_c_quant << std::endl; + diff_time = end - start; + std::cout << "time quant: " << diff_time << std::endl; + std::cout << "avg time quant: " << (diff_time / n_runs) << std::endl; + + start = std::chrono::system_clock::now(); + + for (std::size_t idx{ 0 }; idx < n_runs; ++idx) + { + } + + end = std::chrono::system_clock::now(); + diff_time = end - start; + std::cout << "time ref: " << diff_time << std::endl; + std::cout << "avg time ref: " << (diff_time / n_runs) << std::endl; +} + +// +// namespace sl = subjective_logic; +// using Variable = sl::OpinionNoBase<2, float>; +// +// void printVariable(Variable a, std::string pretext = {}) +// { +// std::cout << pretext << a << std::endl; +// // std::cout << a.belief_mass_ << " | " << a.uncertainty_mass_ << std::endl; +// } +// +// int main(int argc, char* argv[]) +// { +// Variable A{ 0.7, 0.30 }; +// Variable B{ 0.1, 0.90 }; +// +// if (argc >= 5) +// { +// A = Variable{ std::stof(argv[1]), std::stof(argv[2]) }; +// B = Variable{ std::stof(argv[3]), std::stof(argv[4]) }; +// } +// +// printVariable(A, "A: "); +// printVariable(B, "B: "); +// +// auto test = A.cc_fuse(B); +// printVariable(test, "cc:"); +// // std::cout << "test: " << test.belief_mass_ << " | " << test.uncertainty_mass_ << " | p: " << +// test.getProbability() +// // << std::endl; +// // Opinion M_l_a{ 0.8, 0.1 }; +// // Opinion M_c_a{ 0.45, 0.1 }; +// // +// // Opinion M_l_b{ 0.8, 0.1 }; +// // Opinion M_c_b{ 0.1, 0.1 }; +// // +// // std::cout << "harmony: " << M_l_b.harmony(M_c_b) << std::endl; +// // // std::cout << "harmony dis: " << M_l_b.harmony_dis(M_c_b) << std::endl; +// // std::cout << "conflict: " << M_l_b.conflict(M_c_b) << std::endl; +// // Opinion bel_fuse{ M_l_b.belief_const_fuse(M_c_b) }; +// // std::cout << "bel fusion: " << bel_fuse.belief_mass_ << " | " << bel_fuse.uncertainty_mass_ << std::endl; +// // std::cout << "bel projected: " << bel_fuse.getProjection(0.5) << std::endl; +// // +// // Opinion cum_l = M_l_a.cum_fuse(M_l_b); +// // Opinion cum_c = M_c_a.cum_fuse(M_c_b); +// // +// // Opinion bel_a = M_l_a.belief_const_fuse(M_c_a); +// // Opinion bel_b = M_l_b.belief_const_fuse(M_c_b); +// // +// // printVariable(bel_a); +// // printVariable(bel_b); +// // printVariable(cum_l); +// // printVariable(cum_c); +// // +// // std::cout << "\n\n"; +// // +// // Opinion cum_bel{ bel_a.cum_fuse(bel_b) }; +// // printVariable(cum_bel); +// // std::cout << "cum_bel projected: " << cum_bel.getProjection(0.5) << std::endl; +// // +// // Opinion bel_cum{ cum_l.belief_const_fuse(cum_c) }; +// // printVariable(bel_cum); +// // std::cout << "bel_cum projected: " << bel_cum.getProjection(0.5) << std::endl; +// +// // Opinion a{.5, .5}; +// // Opinion b{.45, .5}; +// // +// // Opinion cum_a{a}; +// // Opinion cum_b{b}; +// // +// // Opinion bel_fuse = a.belief_const_fuse(b); +// // printVariable(bel_fuse); +// // +// // Opinion bel_cum_fused; +// // bel_cum_fused.cum_fuse_(a.belief_const_fuse(b)); +// // printVariable(bel_cum_fused); +// // +// // std::cout << "loop\n"; +// // constexpr std::size_t N_RUNS{10}; +// // for (std::size_t i{0}; i +#include "subjective_logic_lib/opinions/opinion.hpp" +namespace sl = subjective_logic; +using Opinion = sl::Opinion<2,float>; +constexpr std::size_t n_opinions{1 << 20}; + + + +__global__ void run_fusion(Opinion* a, Opinion* b, Opinion* dest) { + std::size_t idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx >= n_opinions) {return;} + dest[idx] = a[idx].cum_fuse(b[idx]); + + dest[idx].belief() = 0.5; + auto q_test = dest[idx].as_no_base().get_quantized(); + dest[idx].belief() = static_cast(q_test.belief()); + + // printf("idx %ld \n", idx); + // if (idx == 0) { + // printf("test %f", q_test.belief().as_limit_type()); + // printf("test %f", dest[idx].belief()); + // } +} + +#define CUDA_CHECK(call) do { cudaError_t err = call; \ + if (err != cudaSuccess) { \ + fprintf(stderr, "CUDA Error: %s (%d) ", cudaGetErrorString(err), err); exit(EXIT_FAILURE); \ + }\ + } while (0) + +int main(int argc, char **argv) { + + Opinion *op_a, *op_b, *dest; + cudaMalloc(&op_a, sizeof(Opinion)*n_opinions); + cudaMalloc(&op_b, sizeof(Opinion)*n_opinions); + cudaMalloc(&dest, sizeof(Opinion)*n_opinions); + + run_fusion<<>>(op_a, op_b, dest); + CUDA_CHECK(cudaDeviceSynchronize()); + + std::vector dest_cpu; + dest_cpu.resize(n_opinions); + + cudaMemcpy(dest_cpu.data(), dest, sizeof(Opinion)*n_opinions,cudaMemcpyDeviceToHost); + + std::cout << dest_cpu[0].belief() << std::endl; + + cudaFree(&op_a); + cudaFree(&op_b); + cudaFree(&dest); + + return 0; +} diff --git a/library/test/unittest/subjective_logic/CMakeLists.txt b/library/test/unittest/subjective_logic/CMakeLists.txt index 0e17a9e..5e524c8 100644 --- a/library/test/unittest/subjective_logic/CMakeLists.txt +++ b/library/test/unittest/subjective_logic/CMakeLists.txt @@ -2,9 +2,13 @@ SET(TEST_NAME subjective_logic_test) set(SL_VARIABLE_TEST_FILES + + container/long_short_term_memory.cpp + # single opinion type tests types/cuda_compatible_array_test.cpp types/dirichlet_distribution_test.cpp + types/limited_float.cpp opinions/binomial_opinion_no_base_test.cpp opinions/binomial_opinion_test.cpp @@ -22,22 +26,13 @@ set(SL_VARIABLE_TEST_FILES add_executable(${TEST_NAME} ${SL_VARIABLE_TEST_FILES} ) - -set(SL_OPERATOR_TEST_FILES - operator_test.cpp -) -add_executable(operator_test - ${SL_OPERATOR_TEST_FILES} -) - -foreach(target ${TEST_NAME} operator_test) +foreach(target ${TEST_NAME} ) target_link_libraries(${target} - PUBLIC - subjective_logic_lib::subjective_logic_lib + PUBLIC + subjective_logic_lib::subjective_logic_lib ) target_compile_features(${target} PUBLIC cxx_std_20) - endforeach() @@ -46,17 +41,17 @@ if (${COVERAGE}) target_link_libraries(${TEST_NAME} PRIVATE -fprofile-arcs -ftest-coverage - ) + ) target_compile_options(${TEST_NAME} PRIVATE --coverage -g -O0) endif() target_link_libraries( ${TEST_NAME} - PRIVATE - GTest::gtest_main + PRIVATE + GTest::gtest + GTest::gtest_main ) gtest_discover_tests(${TEST_NAME}) - # append target to test executables (for correct installation) set(UNITTEST_EXECUTABLES "${UNITTEST_EXECUTABLES};${TEST_NAME}" PARENT_SCOPE) -set(UNITTEST_PLAYGROUND_EXECUTABLES "${UNITTEST_PLAYGROUND_EXECUTABLES};operator_test" PARENT_SCOPE) + diff --git a/library/test/unittest/subjective_logic/container/long_short_term_memory.cpp b/library/test/unittest/subjective_logic/container/long_short_term_memory.cpp new file mode 100644 index 0000000..7578ac6 --- /dev/null +++ b/library/test/unittest/subjective_logic/container/long_short_term_memory.cpp @@ -0,0 +1,223 @@ +#include "gtest/gtest.h" + +#include "subjective_logic_lib/container/long_short_term_memory.hpp" +#include "subjective_logic_lib/opinions/opinion_no_base.hpp" +#include "subjective_logic_lib/opinions/opinion.hpp" + +namespace subjective_logic::container +{ +using TestTypes = + ::testing::Types, OpinionNoBase<6, double>, Opinion<3, double>, Opinion<6, float> >; + +template +class LSTMemoryTest : public ::testing::Test +{ + using FloatT = typename OpinionT::FLOAT_t; + static constexpr std::size_t N = OpinionT::SIZE; + +protected: + void SetUp() override + { + } +}; + +TYPED_TEST_SUITE(LSTMemoryTest, TestTypes); + +TYPED_TEST(LSTMemoryTest, Ctor) +{ + using LSTMemory = LongShortTermMemory; + + constexpr double threshold{ 0.5 }; + constexpr double discount{ 0.5 }; + + LSTMemory memory(5, threshold, discount, [](TypeParam a, TypeParam b) { return a.cum_fuse(b); }); + LSTMemory memory2(8, threshold, discount, [](TypeParam a, TypeParam b) { return a.bc_fuse(b); }); + LSTMemory memory3(8, threshold, discount, [](TypeParam a, TypeParam b) { return a.average_fuse(b); }); +} + +TYPED_TEST(LSTMemoryTest, FusionSelection) +{ + using LSTMemory = LongShortTermMemory; + + constexpr double threshold{ 0.5 }; + constexpr double discount{ 0.5 }; + + LSTMemory memory(5, threshold, discount, [](TypeParam a, TypeParam b) { return a.cum_fuse(b); }); + LSTMemory memory2(8, threshold, discount, [](TypeParam a, TypeParam b) { return a.bc_fuse(b); }); + LSTMemory memory3(8, threshold, discount, [](TypeParam a, TypeParam b) { return a.average_fuse(b); }); + + // test if execution works, not if the fusion result is + TypeParam default_op{}; + default_op.belief_masses()[0] = 0.2; + default_op.belief_masses()[1] = 0.6; + memory.add(default_op); + memory.add(default_op); + EXPECT_EQ(memory.get_opinion(), default_op.cum_fuse(default_op)); + + memory2.add(default_op); + memory2.add(default_op); + EXPECT_EQ(memory2.get_opinion(), default_op.bc_fuse(default_op)); + + memory3.add(default_op); + memory3.add(default_op); + EXPECT_EQ(memory3.get_opinion(), default_op.average_fuse(default_op)); +} + +TYPED_TEST(LSTMemoryTest, Add) +{ + using LSTMemory = LongShortTermMemory; + constexpr double threshold{ 0.5 }; + constexpr double discount{ 0.8 }; + + constexpr std::size_t n_test = 5; + LSTMemory memory(n_test, threshold, discount, [](TypeParam a, TypeParam b) { return a.cum_fuse(b); }); + + TypeParam a{}; + a.belief_masses()[0] = 0.5; + memory.add(a); + + TypeParam expected = a; + EXPECT_EQ(memory.get_opinion(), a); + + for (std::size_t idx{ 1 }; idx < n_test; ++idx) + { + memory.add(a); + expected.cum_fuse_(a); + } + auto short_term_a = expected; + EXPECT_EQ(memory.get_opinion(), expected); + + auto long_expected = a; + memory.add(a); + EXPECT_EQ(memory.get_opinion(), short_term_a.cum_fuse(long_expected)); + EXPECT_EQ(memory.get_long_opinion(), long_expected); + + memory.add(a); + long_expected.trust_discount_(discount); + long_expected.cum_fuse_(a); + EXPECT_EQ(memory.get_opinion(), short_term_a.cum_fuse(long_expected)); + EXPECT_EQ(memory.get_long_opinion(), long_expected); +} + +TYPED_TEST(LSTMemoryTest, AddWithReset) +{ + using LSTMemory = LongShortTermMemory; + constexpr double threshold{ 0.5 }; + constexpr double discount{ 0.8 }; + + constexpr std::size_t n_test = 5; + LSTMemory memory(n_test, threshold, discount, [](TypeParam a, TypeParam b) { return a.cum_fuse(b); }); + + TypeParam a{}; + a.belief_masses()[0] = 0.9; + memory.add(a); + + TypeParam expected = a; + + for (std::size_t idx{ 1 }; idx < n_test; ++idx) + { + memory.add(a); + expected.cum_fuse_(a); + } + auto short_term_a = expected; + + TypeParam b{}; + // all opinions have at least dim 2 + b.belief_masses()[1] = 0.9; + + // integer div indented + for (std::size_t idx{ 0 }; idx < n_test / 2; ++idx) + { + memory.add(b); + } + EXPECT_EQ(memory.size(), n_test + n_test / 2); + + TypeParam last_output; + last_output = memory.add(b); + // conflict-based reset should be applied by now, + // this depends on the threshold and opinions and was set such that it is triggered here + // may change with different values in the future. + EXPECT_EQ(memory.size(), memory.get_short_max_size()); + // conflict during last "add" leads to different output + EXPECT_NE(memory.get_opinion(), last_output); + + // pushing another a from the short to the long buffer -> another reset expected + last_output = memory.add(b); + EXPECT_EQ(memory.size(), memory.get_short_max_size()); +} + +TYPED_TEST(LSTMemoryTest, SetShortMaxSize) +{ + using LSTMemory = LongShortTermMemory; + constexpr double threshold{ 0.5 }; + constexpr double discount{ 0.8 }; + + constexpr std::size_t n_test = 5; + LSTMemory memory(n_test, threshold, discount, [](TypeParam a, TypeParam b) { return a.cum_fuse(b); }); + + TypeParam a{}; + a.belief_masses()[0] = 0.9; + memory.add(a); + + TypeParam expected = a; + + for (std::size_t idx{ 1 }; idx < n_test; ++idx) + { + memory.add(a); + expected.cum_fuse_(a); + } + auto short_term_a = expected; + + memory.add(a); + EXPECT_EQ(memory.size(), n_test + 1); + + // reset long-term mem during short term resize + // size after reset corresponds to the number of short-term opinions prior to change + memory.set_short_max_size(n_test + 1); + EXPECT_EQ(memory.size(), n_test); + + memory.add(a); + // no reset long-term mem during short term resizes as no actual change + memory.set_short_max_size(n_test + 1); + EXPECT_EQ(memory.size(), n_test + 1); + + memory.set_short_max_size(n_test - 1); + EXPECT_EQ(memory.size(), n_test - 1); +} + +TYPED_TEST(LSTMemoryTest, Reset) +{ + using LSTMemory = LongShortTermMemory; + constexpr double threshold{ 0.5 }; + constexpr double discount{ 0.8 }; + + constexpr std::size_t n_test = 5; + LSTMemory memory(n_test, threshold, discount, [](TypeParam a, TypeParam b) { return a.cum_fuse(b); }); + + TypeParam a{}; + a.belief_masses()[0] = 0.5; + memory.add(a); + + TypeParam expected = a; + for (std::size_t idx{ 1 }; idx < n_test; ++idx) + { + memory.add(a); + expected.cum_fuse_(a); + } + memory.reset_long_memory(); + EXPECT_EQ(memory.get_opinion(), expected); + + auto long_expected = a; + auto short_term_a = expected; + memory.add(a); + EXPECT_EQ(memory.get_opinion(), short_term_a.cum_fuse(long_expected)); + + memory.reset_long_memory(); + EXPECT_EQ(memory.get_opinion(), expected); + + memory.reset(); + EXPECT_EQ(memory.get_opinion(), TypeParam::VacuousBeliefOpinion()); + EXPECT_EQ(memory.get_long_opinion(), TypeParam::VacuousBeliefOpinion()); +} + +} // namespace subjective_logic::container \ No newline at end of file diff --git a/library/test/unittest/subjective_logic/multi_source/conflict_operators.cpp b/library/test/unittest/subjective_logic/multi_source/conflict_operators.cpp index 871f91d..064babd 100644 --- a/library/test/unittest/subjective_logic/multi_source/conflict_operators.cpp +++ b/library/test/unittest/subjective_logic/multi_source/conflict_operators.cpp @@ -20,10 +20,10 @@ TEST(MultiSourceNoBaseConflictTest, JosangExampleCumFuse) expected_acc_conflict += op_c2.degree_of_conflict(op_c3); float expected_avg_conflict = expected_acc_conflict / 3; - float acc_conflict = Conflict::conflict(Conflict::ConflictType::ACCUMULATE, op_c1, op_c2, op_c3); + float acc_conflict = Conflict::conflict(ConflictType::ACCUMULATE, op_c1, op_c2, op_c3); EXPECT_FLOAT_EQ(expected_acc_conflict, acc_conflict); - float avg_conflict = Conflict::conflict(Conflict::ConflictType::AVERAGE, op_c1, op_c2, op_c3); + float avg_conflict = Conflict::conflict(ConflictType::AVERAGE, op_c1, op_c2, op_c3); EXPECT_FLOAT_EQ(expected_avg_conflict, avg_conflict); } @@ -55,11 +55,11 @@ TYPED_TEST(MultiSourceNoBaseConflictTest, MaxConflictMaxDim) } FloatT expected_acc_conflict = N * (N - 1) / 2.; - FloatT acc_conflict = Conflict::conflict(Conflict::ConflictType::ACCUMULATE, opinions); + FloatT acc_conflict = Conflict::conflict(ConflictType::ACCUMULATE, opinions); EXPECT_FLOAT_EQ(expected_acc_conflict, acc_conflict); FloatT expected_avg_conflict = 1.; - FloatT avg_conflict = Conflict::conflict(Conflict::ConflictType::AVERAGE, opinions); + FloatT avg_conflict = Conflict::conflict(ConflictType::AVERAGE, opinions); EXPECT_FLOAT_EQ(expected_avg_conflict, avg_conflict); } @@ -74,7 +74,7 @@ TYPED_TEST(MultiSourceNoBaseConflictTest, MultiSourceConflictSharesMaxConflict) } auto [avg_conflict, conflict_shares] = - Conflict::conflict_shares(Conflict::ConflictType::AVERAGE, opinions); + Conflict::conflict_shares(RelationType::CONFLICT, ConflictType::AVERAGE, opinions); EXPECT_FLOAT_EQ(avg_conflict, 1.0F); for (std::size_t idx{ 0 }; idx < N; ++idx) @@ -101,7 +101,7 @@ TYPED_TEST(MultiSourceNoBaseConflictTest, MultiSourceConflictSharesZeroConflict) } auto [avg_conflict, conflict_shares] = - Conflict::conflict_shares(Conflict::ConflictType::AVERAGE, opinions); + Conflict::conflict_shares(RelationType::CONFLICT, ConflictType::AVERAGE, opinions); EXPECT_FLOAT_EQ(avg_conflict, 0.0F); for (std::size_t idx{ 0 }; idx < N; ++idx) @@ -110,7 +110,7 @@ TYPED_TEST(MultiSourceNoBaseConflictTest, MultiSourceConflictSharesZeroConflict) } auto [acc_conflict, conflict_shares2] = - Conflict::conflict_shares(Conflict::ConflictType::ACCUMULATE, opinions); + Conflict::conflict_shares(RelationType::CONFLICT, ConflictType::ACCUMULATE, opinions); EXPECT_FLOAT_EQ(acc_conflict, 0.0F); for (std::size_t idx{ 0 }; idx < N; ++idx) @@ -127,7 +127,7 @@ TYPED_TEST(MultiSourceNoBaseConflictTest, MultiSourceConflictSharesOutlier) opinions[2].belief_masses()[1] = 1.0; auto [avg_conflict, conflict_shares] = - Conflict::conflict_shares(Conflict::ConflictType::AVERAGE, opinions); + Conflict::conflict_shares(RelationType::CONFLICT, ConflictType::AVERAGE, opinions); EXPECT_FLOAT_EQ(conflict_shares[0], -0.5); EXPECT_FLOAT_EQ(conflict_shares[1], -0.5); @@ -145,11 +145,11 @@ TYPED_TEST(MultiSourceNoBaseConflictTest, MultiSourceBeliefConflictZeroConflict) } auto [cum_conflict, conflict_shares] = - Conflict::conflict_shares(Conflict::ConflictType::BELIEF_CUMULATIVE, opinions); - auto [bc_conflict, conflict_shares2] = Conflict::conflict_shares( - Conflict::ConflictType::BELIEF_BELIEF_CONSTRAINT, opinions); + Conflict::conflict_shares(RelationType::CONFLICT, ConflictType::BELIEF_CUMULATIVE, opinions); + auto [bc_conflict, conflict_shares2] = + Conflict::conflict_shares(RelationType::CONFLICT, ConflictType::BELIEF_BELIEF_CONSTRAINT, opinions); auto [avg_conflict, conflict_shares3] = - Conflict::conflict_shares(Conflict::ConflictType::BELIEF_AVERAGE, opinions); + Conflict::conflict_shares(RelationType::CONFLICT, ConflictType::BELIEF_AVERAGE, opinions); EXPECT_FLOAT_EQ(cum_conflict, 0.0F); EXPECT_FLOAT_EQ(bc_conflict, 0.0F); diff --git a/library/test/unittest/subjective_logic/multi_source/fusion_operators.cpp b/library/test/unittest/subjective_logic/multi_source/fusion_operators.cpp index 07c4b37..10289c3 100644 --- a/library/test/unittest/subjective_logic/multi_source/fusion_operators.cpp +++ b/library/test/unittest/subjective_logic/multi_source/fusion_operators.cpp @@ -14,15 +14,15 @@ TEST(MultiSourceNoBaseFusionTest, JosangExampleCumFuse) OpinionNoBase op_c2(0.4, 0.2); OpinionNoBase op_c3(0.7, 0.1); - auto result = Fusion::fuse_opinions(Fusion::FusionType::CUMULATIVE, op_c1, op_c2, op_c3); + auto result = Fusion::fuse_opinions(FusionType::CUMULATIVE, op_c1, op_c2, op_c3); EXPECT_FLOAT_EQ(result.belief(), 0.6511628); EXPECT_FLOAT_EQ(result.disbelief(), 0.20930232); - result = Fusion::fuse_opinions(Fusion::FusionType::CUMULATIVE, op_c2, op_c1, op_c3); + result = Fusion::fuse_opinions(FusionType::CUMULATIVE, op_c2, op_c1, op_c3); EXPECT_FLOAT_EQ(result.belief(), 0.6511628); EXPECT_FLOAT_EQ(result.disbelief(), 0.20930232); - result = Fusion::fuse_opinions(Fusion::FusionType::CUMULATIVE, op_c3, op_c1, op_c2); + result = Fusion::fuse_opinions(FusionType::CUMULATIVE, op_c3, op_c1, op_c2); EXPECT_FLOAT_EQ(result.belief(), 0.6511628); EXPECT_FLOAT_EQ(result.disbelief(), 0.20930232); } @@ -33,15 +33,15 @@ TEST(MultiSourceNoBaseFusionTest, JosangExampleAvgFuse) OpinionNoBase op_c2(0.4, 0.2); OpinionNoBase op_c3(0.7, 0.1); - auto result = Fusion::fuse_opinions(Fusion::FusionType::AVERAGE, op_c1, op_c2, op_c3); + auto result = Fusion::fuse_opinions(FusionType::AVERAGE, op_c1, op_c2, op_c3); EXPECT_FLOAT_EQ(result.belief(), 0.5090909); EXPECT_FLOAT_EQ(result.disbelief(), 0.16363636); - result = Fusion::fuse_opinions(Fusion::FusionType::AVERAGE, op_c2, op_c1, op_c3); + result = Fusion::fuse_opinions(FusionType::AVERAGE, op_c2, op_c1, op_c3); EXPECT_FLOAT_EQ(result.belief(), 0.5090909); EXPECT_FLOAT_EQ(result.disbelief(), 0.16363636); - result = Fusion::fuse_opinions(Fusion::FusionType::AVERAGE, op_c3, op_c1, op_c2); + result = Fusion::fuse_opinions(FusionType::AVERAGE, op_c3, op_c1, op_c2); EXPECT_FLOAT_EQ(result.belief(), 0.5090909); EXPECT_FLOAT_EQ(result.disbelief(), 0.16363636); } @@ -67,7 +67,7 @@ TYPED_TEST(MultiSourceNoBaseFusionTest, CumFuseTwoVariablesVacuous) TypeParam var1{}; TypeParam var2{}; - auto result_vacuous = Fusion::fuse_opinions(Fusion::FusionType::CUMULATIVE, var1, var2); + auto result_vacuous = Fusion::fuse_opinions(FusionType::CUMULATIVE, var1, var2); auto expected_result = var1.cum_fuse(var2); EXPECT_FLOAT_EQ(result_vacuous.uncertainty(), expected_result.uncertainty()); @@ -84,7 +84,7 @@ TYPED_TEST(MultiSourceNoBaseFusionTest, CumFuseTwoVariablesDogmatic) var1.belief_masses().front() = 1; var2.belief_masses().back() = 1; - auto result_dogmatic = Fusion::fuse_opinions(Fusion::FusionType::CUMULATIVE, var1, var2); + auto result_dogmatic = Fusion::fuse_opinions(FusionType::CUMULATIVE, var1, var2); auto expected_result = var1.cum_fuse(var2); EXPECT_FLOAT_EQ(result_dogmatic.uncertainty(), expected_result.uncertainty()); @@ -101,7 +101,7 @@ TYPED_TEST(MultiSourceNoBaseFusionTest, CumFuseTwoVariables) var1.belief_masses().front() = 0.2; var2.belief_masses().back() = 0.5; - auto result = Fusion::fuse_opinions(Fusion::FusionType::CUMULATIVE, var1, var2); + auto result = Fusion::fuse_opinions(FusionType::CUMULATIVE, var1, var2); auto expected_result = var1.cum_fuse(var2); EXPECT_FLOAT_EQ(result.uncertainty(), expected_result.uncertainty()); @@ -118,7 +118,7 @@ TYPED_TEST(MultiSourceNoBaseFusionTest, CumFuseVariablesVacuous) TypeParam var3{}; TypeParam var4{}; - auto result = Fusion::fuse_opinions(Fusion::FusionType::CUMULATIVE, var1, var2, var3, var4); + auto result = Fusion::fuse_opinions(FusionType::CUMULATIVE, var1, var2, var3, var4); auto expected_result = var1; EXPECT_FLOAT_EQ(result.uncertainty(), expected_result.uncertainty()); @@ -139,7 +139,7 @@ TYPED_TEST(MultiSourceNoBaseFusionTest, CumFuseVariablesDogmatic) TypeParam var4{}; var4.belief_masses().back() = 1.; - auto result = Fusion::fuse_opinions(Fusion::FusionType::CUMULATIVE, var1, var2, var3, var4); + auto result = Fusion::fuse_opinions(FusionType::CUMULATIVE, var1, var2, var3, var4); TypeParam expected_result{}; expected_result.belief_masses().front() = 0.5; expected_result.belief_masses().back() = 0.5; @@ -168,7 +168,7 @@ TYPED_TEST(MultiSourceNoBaseFusionTest, CumFuseVariables) auto comperator = [](TypeParam tp1, TypeParam tp2) { return tp1.belief_masses()[0] < tp2.belief_masses()[0]; }; do { - results.push_back(Fusion::fuse_opinions(Fusion::FusionType::CUMULATIVE, opinions)); + results.push_back(Fusion::fuse_opinions(FusionType::CUMULATIVE, opinions)); } while (std::ranges::next_permutation(opinions, comperator).found); for (std::size_t idx{ 1 }; idx < results.size(); ++idx) @@ -186,7 +186,7 @@ TYPED_TEST(MultiSourceNoBaseFusionTest, AvgFuseTwoVariablesVacuous) TypeParam var1{}; TypeParam var2{}; - auto result_vacuous = Fusion::fuse_opinions(Fusion::FusionType::AVERAGE, var1, var2); + auto result_vacuous = Fusion::fuse_opinions(FusionType::AVERAGE, var1, var2); auto expected_result = var1.average_fuse(var2); EXPECT_FLOAT_EQ(result_vacuous.uncertainty(), expected_result.uncertainty()); @@ -203,7 +203,7 @@ TYPED_TEST(MultiSourceNoBaseFusionTest, AvgFuseTwoVariablesDogmatic) var1.belief_masses().front() = 1; var2.belief_masses().back() = 1; - auto result_dogmatic = Fusion::fuse_opinions(Fusion::FusionType::AVERAGE, var1, var2); + auto result_dogmatic = Fusion::fuse_opinions(FusionType::AVERAGE, var1, var2); auto expected_result = var1.average_fuse(var2); EXPECT_FLOAT_EQ(result_dogmatic.uncertainty(), expected_result.uncertainty()); @@ -220,7 +220,7 @@ TYPED_TEST(MultiSourceNoBaseFusionTest, AvgFuseTwoVariables) var1.belief_masses().front() = 0.2; var2.belief_masses().back() = 0.5; - auto result = Fusion::fuse_opinions(Fusion::FusionType::AVERAGE, var1, var2); + auto result = Fusion::fuse_opinions(FusionType::AVERAGE, var1, var2); auto expected_result = var1.average_fuse(var2); EXPECT_FLOAT_EQ(result.uncertainty(), expected_result.uncertainty()); @@ -237,7 +237,7 @@ TYPED_TEST(MultiSourceNoBaseFusionTest, AvgFuseVariablesVacuous) TypeParam var3{}; TypeParam var4{}; - auto result = Fusion::fuse_opinions(Fusion::FusionType::AVERAGE, var1, var2, var3, var4); + auto result = Fusion::fuse_opinions(FusionType::AVERAGE, var1, var2, var3, var4); auto expected_result = var1; EXPECT_FLOAT_EQ(result.uncertainty(), expected_result.uncertainty()); @@ -258,7 +258,7 @@ TYPED_TEST(MultiSourceNoBaseFusionTest, AvgFuseVariablesDogmatic) TypeParam var4{}; var4.belief_masses().back() = 1.; - auto result = Fusion::fuse_opinions(Fusion::FusionType::AVERAGE, var1, var2, var3, var4); + auto result = Fusion::fuse_opinions(FusionType::AVERAGE, var1, var2, var3, var4); TypeParam expected_result{}; expected_result.belief_masses().front() = 0.5; expected_result.belief_masses().back() = 0.5; @@ -287,7 +287,7 @@ TYPED_TEST(MultiSourceNoBaseFusionTest, AvgFuseVariables) auto comperator = [](TypeParam tp1, TypeParam tp2) { return tp1.belief_masses()[0] < tp2.belief_masses()[0]; }; do { - results.push_back(Fusion::fuse_opinions(Fusion::FusionType::AVERAGE, opinions)); + results.push_back(Fusion::fuse_opinions(FusionType::AVERAGE, opinions)); } while (std::ranges::next_permutation(opinions, comperator).found); for (std::size_t idx{ 1 }; idx < results.size(); ++idx) diff --git a/library/test/unittest/subjective_logic/multi_source/trusted_fusion_operators.cpp b/library/test/unittest/subjective_logic/multi_source/trusted_fusion_operators.cpp index fa529a2..9dd1b2f 100644 --- a/library/test/unittest/subjective_logic/multi_source/trusted_fusion_operators.cpp +++ b/library/test/unittest/subjective_logic/multi_source/trusted_fusion_operators.cpp @@ -24,14 +24,14 @@ TEST(MultiSourceTrustedFusionTest, JosangExampleCumFuse) std::vector>> t_ops{ a_c1_x, a_c2_x, a_c3_x }; - auto cum_fused_no_revision = TrustedFusion::fuse_opinions(Fusion::FusionType::CUMULATIVE, t_ops); + auto cum_fused_no_revision = TrustedFusion::fuse_opinions(FusionType::CUMULATIVE, t_ops); // since the example is copied from a paper table, numbers are rather a rough estimate EXPECT_NEAR(cum_fused_no_revision.belief(), 0.36, 0.05); EXPECT_NEAR(cum_fused_no_revision.disbelief(), 0.62, 0.05); EXPECT_NEAR(cum_fused_no_revision.uncertainty(), 0.02, 0.05); - auto avg_fused_no_revision = TrustedFusion::fuse_opinions(Fusion::FusionType::AVERAGE, t_ops); + auto avg_fused_no_revision = TrustedFusion::fuse_opinions(FusionType::AVERAGE, t_ops); // since the example is copied from a paper table, numbers are rather a rough estimate EXPECT_NEAR(avg_fused_no_revision.belief(), 0.35, 0.05); @@ -39,9 +39,10 @@ TEST(MultiSourceTrustedFusionTest, JosangExampleCumFuse) EXPECT_NEAR(avg_fused_no_revision.uncertainty(), 0.05, 0.05); // for the trust revision josang always uses the average reference fusion - auto cum_fused_cum_revision = TrustedFusion::fuse_opinions(Fusion::FusionType::CUMULATIVE, - TrustRevision::TrustRevisionType::REFERENCE_FUSION, - Conflict::ConflictType::BELIEF_AVERAGE, + auto cum_fused_cum_revision = TrustedFusion::fuse_opinions(FusionType::CUMULATIVE, + RelationType::CONFLICT, + TrustRevisionType::REFERENCE_FUSION, + ConflictType::BELIEF_AVERAGE, t_ops); #ifdef BELIEF_REVISION_FOLLOWING_JOSAN // since the example is copied from a paper table, numbers are rather a rough estimate @@ -50,9 +51,10 @@ TEST(MultiSourceTrustedFusionTest, JosangExampleCumFuse) EXPECT_NEAR(cum_fused_cum_revision.uncertainty(), 0.03, 0.06); #endif - auto avg_fused_avg_revision = TrustedFusion::fuse_opinions(Fusion::FusionType::AVERAGE, - TrustRevision::TrustRevisionType::REFERENCE_FUSION, - Conflict::ConflictType::BELIEF_AVERAGE, + auto avg_fused_avg_revision = TrustedFusion::fuse_opinions(FusionType::AVERAGE, + RelationType::CONFLICT, + TrustRevisionType::REFERENCE_FUSION, + ConflictType::BELIEF_AVERAGE, t_ops); #ifdef BELIEF_REVISION_FOLLOWING_JOSAN @@ -83,18 +85,16 @@ TYPED_TEST(MultiSourceTrustedFusionTest, CumFuseTwoVariablesVacuous) EXPECT_FLOAT_EQ(var1.opinion().uncertainty(), 1.0); std::vector vec{ var1, var2 }; - OpinionT cum_fused = TrustedFusion::fuse_opinions(Fusion::FusionType::CUMULATIVE, - TrustRevision::TrustRevisionType::CONFLICT_SHARES, - Conflict::ConflictType::AVERAGE, - vec); + OpinionT cum_fused = TrustedFusion::fuse_opinions( + FusionType::CUMULATIVE, RelationType::CONFLICT, TrustRevisionType::SHARES, ConflictType::AVERAGE, vec); // std::vector weighted_types; - // weighted_types.push_back({TrustRevision::TrustRevisionType::CONFLICT_SHARES, Conflict::ConflictType::AVERAGE, - // .5}); weighted_types.push_back({TrustRevision::TrustRevisionType::NORMAL, Conflict::ConflictType::ACCUMULATE, + // weighted_types.push_back({TrustRevisionType::CONFLICT_SHARES, ConflictType::AVERAGE, + // .5}); weighted_types.push_back({TrustRevisionType::NORMAL, ConflictType::ACCUMULATE, // .5}); // // OpinionT cum_fused2 = TrustedFusion::fuse( - // Fusion::FusionType::CUMULATIVE, + // FusionType::CUMULATIVE, // weighted_types, // vec // ); diff --git a/library/test/unittest/subjective_logic/opinions/binomial_opinion_no_base_test.cpp b/library/test/unittest/subjective_logic/opinions/binomial_opinion_no_base_test.cpp index 3dcfcd6..bbd7339 100644 --- a/library/test/unittest/subjective_logic/opinions/binomial_opinion_no_base_test.cpp +++ b/library/test/unittest/subjective_logic/opinions/binomial_opinion_no_base_test.cpp @@ -140,6 +140,25 @@ TYPED_TEST(BinomialOpinionNoBaseTest, IsValid) EXPECT_FALSE(negative.is_valid()); } +TYPED_TEST(BinomialOpinionNoBaseTest, Quantization) +{ + // precision is set higher than the best possible, since numbers are converted multiple times on the way + double precision = 1.2 / 255; + const auto quant = this->variable_.get_quantized(); + + EXPECT_NEAR(this->variable_.belief(), quant.belief().as_limit_type(), precision); + EXPECT_NEAR(this->variable_.disbelief(), quant.disbelief().as_limit_type(), precision); + EXPECT_NEAR(this->variable_.uncertainty(), quant.uncertainty().as_limit_type(), precision); + EXPECT_NEAR(this->variable_.getBinomialProjection(), quant.getBinomialProjection().as_limit_type(), precision); + + const auto recovered_op = decltype(this->variable_)(quant); + EXPECT_NEAR(this->variable_.belief(), recovered_op.belief(), precision); + EXPECT_NEAR(this->variable_.disbelief(), recovered_op.disbelief(), precision); + + EXPECT_EQ(this->variable_.get_quantized(), decltype(quant){ this->variable_ }); + EXPECT_EQ(quant.get_dequantized(), decltype(this->variable_){ quant }); +} + TYPED_TEST(BinomialOpinionNoBaseTest, Complement) { OpinionT test_var = this->variable_.complement(); diff --git a/library/test/unittest/subjective_logic/opinions/binomial_opinion_test.cpp b/library/test/unittest/subjective_logic/opinions/binomial_opinion_test.cpp index 69d66dd..c613394 100644 --- a/library/test/unittest/subjective_logic/opinions/binomial_opinion_test.cpp +++ b/library/test/unittest/subjective_logic/opinions/binomial_opinion_test.cpp @@ -130,6 +130,32 @@ TYPED_TEST(BinomialOpinionTest, IsValid) EXPECT_FALSE(negative.is_valid()); } +TYPED_TEST(BinomialOpinionTest, Quantization) +{ + // precision is set higher than the best possible, since numbers are converted multiple times on the way + double precision = 1.2 / 255; + const auto quant = this->variable_.get_quantized(); + + // uncertainty is calculated using the belief masses -> multiple precision losses + EXPECT_NEAR(this->variable_.belief(), quant.belief().as_limit_type(), precision); + EXPECT_NEAR(this->variable_.disbelief(), quant.disbelief().as_limit_type(), precision); + EXPECT_NEAR(this->variable_.uncertainty(), quant.uncertainty().as_limit_type(), 2.0 * precision); + EXPECT_NEAR(this->variable_.getBinomialProjection(), quant.getBinomialProjection().as_limit_type(), 2.0 * precision); + EXPECT_NEAR(this->variable_.prior_belief(), quant.prior_belief().as_limit_type(), precision); + EXPECT_NEAR(this->variable_.prior_disbelief(), quant.prior_disbelief().as_limit_type(), precision); + + const auto recovered_op = decltype(this->variable_)(quant); + EXPECT_NEAR(this->variable_.belief(), recovered_op.belief(), precision); + EXPECT_NEAR(this->variable_.disbelief(), recovered_op.disbelief(), precision); + EXPECT_NEAR(this->variable_.uncertainty(), recovered_op.uncertainty(), 2.0 * precision); + EXPECT_NEAR(this->variable_.getBinomialProjection(), recovered_op.getBinomialProjection(), 2.0 * precision); + EXPECT_NEAR(this->variable_.prior_belief(), recovered_op.prior_belief(), precision); + EXPECT_NEAR(this->variable_.prior_disbelief(), recovered_op.prior_disbelief(), precision); + + EXPECT_EQ(this->variable_.get_quantized(), decltype(quant){ this->variable_ }); + EXPECT_EQ(quant.get_dequantized(), decltype(this->variable_){ quant }); +} + TYPED_TEST(BinomialOpinionTest, Complement) { TypeParam test_var = this->variable_.complement(); @@ -184,7 +210,8 @@ TYPED_TEST(BinomialOpinionTest, NeutralBeliefOpinion) TYPED_TEST(BinomialOpinionTest, VacuousBeliefOpinion) { TypeParam test = TypeParam::VacuousBeliefOpinion(); - TypeParam expected{ TypeParam::VacuousBeliefDistr(), TypeParam::VacuousBeliefDistr() }; + // prior must always be set valid -> Vacuous is invalid here + TypeParam expected{ TypeParam::VacuousBeliefDistr(), TypeParam::NeutralBeliefDistr() }; EXPECT_EQ(test, expected); } diff --git a/library/test/unittest/subjective_logic/opinions/multinomial_opinion_no_base_test.cpp b/library/test/unittest/subjective_logic/opinions/multinomial_opinion_no_base_test.cpp index f91b259..d6b64e9 100644 --- a/library/test/unittest/subjective_logic/opinions/multinomial_opinion_no_base_test.cpp +++ b/library/test/unittest/subjective_logic/opinions/multinomial_opinion_no_base_test.cpp @@ -133,6 +133,37 @@ TYPED_TEST(MultinomialOpinionNoBaseTest, Uncertainty) ASSERT_FLOAT_EQ(this->variable_.uncertainty(), this->kUncertaintyMass_); } +TYPED_TEST(MultinomialOpinionNoBaseTest, Quantization) +{ + // precision is set higher than the best possible, since numbers are converted multiple times on the way + double precision = 1.2 / 255; + const auto quant = this->variable_.get_quantized(); + + const typename TypeParam::BeliefType prior(0.5); + const typename TypeParam::QuantizedT::BeliefType quant_prior(prior); + + // in some testcases the precision is multiplied with TypeParam::Size + // this is necessary and OK I guess since the uncertainty is obtained by subtracting imprecise numbers from 1.0 + // respectively often. also everything that needs the uncertainty may also be incorrect to the same extend. + for (std::size_t idx{ 0 }; idx < TypeParam::SIZE; ++idx) + { + EXPECT_NEAR(this->variable_.belief_masses()[idx], quant.belief_masses()[idx].as_limit_type(), precision); + EXPECT_NEAR(this->variable_.getProjection(prior)[idx], + quant.getProjection(quant_prior)[idx].as_limit_type(), + TypeParam::SIZE * precision); + } + EXPECT_NEAR(this->variable_.uncertainty(), quant.uncertainty().as_limit_type(), TypeParam::SIZE * precision); + + const auto recovered_op = decltype(this->variable_)(quant); + for (std::size_t idx{ 0 }; idx < TypeParam::SIZE; ++idx) + { + EXPECT_NEAR(this->variable_.belief_masses()[idx], recovered_op.belief_masses()[idx], precision); + EXPECT_NEAR( + this->variable_.getProjection(prior)[idx], recovered_op.getProjection(prior)[idx], TypeParam::SIZE * precision); + } + EXPECT_NEAR(this->variable_.uncertainty(), recovered_op.uncertainty(), TypeParam::SIZE * precision); +} + TYPED_TEST(MultinomialOpinionNoBaseTest, GetProjection) { typename TypeParam::FLOAT_t expected_projection = diff --git a/library/test/unittest/subjective_logic/opinions/multinomial_opinion_test.cpp b/library/test/unittest/subjective_logic/opinions/multinomial_opinion_test.cpp index 2887bd0..f66a848 100644 --- a/library/test/unittest/subjective_logic/opinions/multinomial_opinion_test.cpp +++ b/library/test/unittest/subjective_logic/opinions/multinomial_opinion_test.cpp @@ -51,6 +51,32 @@ TYPED_TEST(MultinomialOpinionTest, Ctor) } } +TYPED_TEST(MultinomialOpinionTest, Quantization) +{ + // precision is set higher than the best possible, since numbers are converted multiple times on the way + double precision = 1.2 / 255; + const auto quant = this->variable_.get_quantized(); + + // in some testcases the precision is multiplied with TypeParam::Size + // this is necessary and OK I guess since the uncertainty is obtained by subtracting imprecise numbers from 1.0 + // respectively often. also everything that needs the uncertainty may also be incorrect to the same extend. + for (std::size_t idx{ 0 }; idx < TypeParam::SIZE; ++idx) + { + EXPECT_NEAR(this->variable_.belief_masses()[idx], quant.belief_masses()[idx].as_limit_type(), precision); + EXPECT_NEAR( + this->variable_.getProjection()[idx], quant.getProjection()[idx].as_limit_type(), TypeParam::SIZE * precision); + } + EXPECT_NEAR(this->variable_.uncertainty(), quant.uncertainty().as_limit_type(), TypeParam::SIZE * precision); + + const auto recovered_op = decltype(this->variable_)(quant); + for (std::size_t idx{ 0 }; idx < TypeParam::SIZE; ++idx) + { + EXPECT_NEAR(this->variable_.belief_masses()[idx], recovered_op.belief_masses()[idx], precision); + EXPECT_NEAR(this->variable_.getProjection()[idx], recovered_op.getProjection()[idx], TypeParam::SIZE * precision); + } + EXPECT_NEAR(this->variable_.uncertainty(), recovered_op.uncertainty(), TypeParam::SIZE * precision); +} + TYPED_TEST(MultinomialOpinionTest, Uncertainty) { ASSERT_FLOAT_EQ(this->variable_.uncertainty(), this->kUncertaintyMass_); diff --git a/library/test/unittest/subjective_logic/opinions/trusted_opinion.cpp b/library/test/unittest/subjective_logic/opinions/trusted_opinion.cpp index 273f970..2840f53 100644 --- a/library/test/unittest/subjective_logic/opinions/trusted_opinion.cpp +++ b/library/test/unittest/subjective_logic/opinions/trusted_opinion.cpp @@ -20,7 +20,7 @@ TYPED_TEST_SUITE(TrustedOpinionTest, TestTypes); TYPED_TEST(TrustedOpinionTest, ExtracOpinionsAndTrust) { - using FloatT = TypeParam::FLOAT_t; + using FloatT = typename TypeParam::FLOAT_t; std::size_t N = TypeParam::SIZE; Trust default_trust{ 1.0, 0. }; @@ -34,7 +34,7 @@ TYPED_TEST(TrustedOpinionTest, ExtracOpinionsAndTrust) auto ops = default_opinion; ops.belief_masses()[idx % N] = 0.5; auto trust = default_trust; - trust.belief() = 1.0 / N * idx; + trust.belief() = 1.0 / static_cast(N * idx); trusted_ops.emplace_back(default_trust, default_opinion); } @@ -74,7 +74,7 @@ TYPED_TEST(TrustedOpinionTest, ExtracOpinionsAndTrust) TYPED_TEST(TrustedOpinionTest, Ctor) { - using FloatT = TypeParam::FLOAT_t; + using FloatT = typename TypeParam::FLOAT_t; Trust expected_trust{ 1.0, 0. }; TypeParam expected_opinion; @@ -91,7 +91,7 @@ TYPED_TEST(TrustedOpinionTest, Ctor) TYPED_TEST(TrustedOpinionTest, IsValid) { - using FloatT = TypeParam::FLOAT_t; + using FloatT = typename TypeParam::FLOAT_t; Trust trust; TypeParam opinion; @@ -107,12 +107,46 @@ TYPED_TEST(TrustedOpinionTest, IsValid) EXPECT_FALSE(to3.is_valid()); } +TYPED_TEST(TrustedOpinionTest, Quantization) +{ + using FloatT = typename TypeParam::FLOAT_t; + + Trust trust1{ 1.0, 0. }; + TypeParam opinion = TypeParam::NeutralBeliefOpinion(); + + TrustedOpinion to1{ trust1, opinion }; + + FloatT precision = 1.2 / 255; + auto to_quant = to1.get_quantized(); + + EXPECT_NEAR(to1.trust().belief(), to_quant.trust().belief().as_limit_type(), precision); + EXPECT_NEAR(to1.trust().disbelief(), to_quant.trust().disbelief().as_limit_type(), precision); + + for (std::size_t idx{ 0 }; idx < to1.SIZE; idx++) + { + EXPECT_NEAR(to1.opinion().belief_masses()[idx], to_quant.opinion().belief_masses()[idx].as_limit_type(), precision); + } + + auto recovered_op = decltype(to1){ to_quant }; + EXPECT_NEAR(to1.trust().belief(), recovered_op.trust().belief(), precision); + EXPECT_NEAR(to1.trust().disbelief(), recovered_op.trust().disbelief(), precision); + + for (std::size_t idx{ 0 }; idx < to1.SIZE; idx++) + { + EXPECT_NEAR(to1.opinion().belief_masses()[idx], recovered_op.opinion().belief_masses()[idx], precision); + } + + EXPECT_EQ(to1.get_quantized(), decltype(to_quant){ to1 }); + EXPECT_EQ(to_quant.get_dequantized(), decltype(to1){ to_quant }); +} + TYPED_TEST(TrustedOpinionTest, DiscountedOpinion) { - using FloatT = TypeParam::FLOAT_t; + using FloatT = typename TypeParam::FLOAT_t; using BeliefType = typename Trust::BeliefType; Trust trust1{ 1.0, 0. }; + TypeParam opinion; opinion.belief_masses()[0] = 1.; @@ -129,7 +163,7 @@ TYPED_TEST(TrustedOpinionTest, DiscountedOpinion) TYPED_TEST(TrustedOpinionTest, TrustAccess) { - using FloatT = TypeParam::FLOAT_t; + using FloatT = typename TypeParam::FLOAT_t; // using BeliefType = typename Trust::BeliefType; Trust trust{ 0.8, 0.2 }; @@ -151,7 +185,7 @@ TYPED_TEST(TrustedOpinionTest, TrustAccess) TYPED_TEST(TrustedOpinionTest, OpinionAccess) { - using FloatT = TypeParam::FLOAT_t; + using FloatT = typename TypeParam::FLOAT_t; Trust trust{}; TypeParam opinion{}; @@ -173,7 +207,7 @@ TYPED_TEST(TrustedOpinionTest, OpinionAccess) TYPED_TEST(TrustedOpinionTest, TrustRevision) { - using FloatT = TypeParam::FLOAT_t; + using FloatT = typename TypeParam::FLOAT_t; Trust trust_1{ 0.8, 0. }; Trust trust_2{ 0.5, 0.1 }; diff --git a/library/test/unittest/subjective_logic/types/limited_float.cpp b/library/test/unittest/subjective_logic/types/limited_float.cpp new file mode 100644 index 0000000..f7de853 --- /dev/null +++ b/library/test/unittest/subjective_logic/types/limited_float.cpp @@ -0,0 +1,105 @@ +#include "gtest/gtest.h" + +#include "subjective_logic_lib/types/limited_float.hpp" + +namespace subjective_logic +{ + +TEST(LimitedFloatTest, Traits) +{ + EXPECT_EQ(sizeof(LimitedFloat), 1); +} + +TEST(LimitedFloatTest, Ctor) +{ + LimitedFloat lf{ 0.5 }; + EXPECT_EQ(lf.value, static_cast(0.5 * std::numeric_limits::max())); + + LimitedFloat lf2{ 0.5 }; + EXPECT_EQ(lf2.value, 0); +} + +TEST(LimitedFloatTest, Cast) +{ + double precision = 1.0 / 255; + LimitedFloat lf{ 0.5 }; + EXPECT_NEAR(static_cast(lf), 0.5, precision); + + LimitedFloat lf2{ 0.75 }; + EXPECT_NEAR(static_cast(lf2), 0.75, precision); + + LimitedFloat lf3{ 0.75 }; + EXPECT_NEAR(static_cast(lf3), 1.0, precision); +} + +TEST(LimitedFloatTest, Operators) +{ + // precision would actually state what the limited float can express. + // however, due to different conversions, the actual error might be a bit higher + // this is mostly due to putting the start value inside a limited float and + // then calculate with the incorrect, approximated value afterward + double precision = 1.2 / 255; + + constexpr double lower_bound = 0.0; + constexpr double upper_bound = 1.0; + + constexpr double value = 0.5; + constexpr double value2 = 0.2; + constexpr double diff = 0.1; + constexpr double diff2 = 2.1; + + using lFloat = LimitedFloat; + constexpr lFloat lf{ value }; + constexpr lFloat lf2{ value2 }; + + auto test_plus = lf + diff; + constexpr bool type_plus = std::is_same_v; + static_assert(type_plus); + EXPECT_NEAR(test_plus, value + diff, precision); + test_plus = lf + diff2; + EXPECT_NEAR(test_plus, value + diff2, precision); + lFloat lf_plus{ lf + diff }; + EXPECT_NEAR(static_cast(lf_plus), value + diff, precision); + lf_plus = lf + diff2; + EXPECT_NEAR(static_cast(lf_plus), upper_bound, precision); + + lFloat lf_plus2{ lf + lf2 }; + EXPECT_NEAR(static_cast(lf_plus2), value + value2, 2 * precision); + double lf_plus2_float = lf + lf2; + EXPECT_NEAR(lf_plus2_float, value + value2, 2 * precision); + + auto test_minus = lf - diff; + constexpr bool type_minus = std::is_same_v; + static_assert(type_minus); + EXPECT_NEAR(test_minus, value - diff, precision); + test_minus = lf - diff2; + EXPECT_NEAR(test_minus, value - diff2, precision); + lFloat lf_minus{ lf - diff }; + EXPECT_NEAR(static_cast(lf_minus), value - diff, precision); + lf_minus = lf - diff2; + EXPECT_NEAR(static_cast(lf_minus), lower_bound, precision); + + auto test_mult = lf * diff; + constexpr bool type_mult = std::is_same_v; + static_assert(type_mult); + EXPECT_NEAR(test_mult, value * diff, precision); + test_mult = lf * diff2; + EXPECT_NEAR(test_mult, value * diff2, precision); + lFloat lf_mult{ lf * diff }; + EXPECT_NEAR(static_cast(lf_mult), value * diff, precision); + lf_mult = lf * diff2; + EXPECT_NEAR(static_cast(lf_mult), upper_bound, precision); + + auto test_div = lf / diff; + constexpr bool type_div = std::is_same_v; + static_assert(type_div); + EXPECT_NEAR(test_div, value / diff, precision / diff); + test_div = lf / diff2; + EXPECT_NEAR(test_div, value / diff2, precision); + lFloat lf_div{ lf / diff }; + EXPECT_NEAR(static_cast(lf_div), upper_bound, precision); + lf_div = lf / diff2; + EXPECT_NEAR(static_cast(lf_div), value / diff2, precision); +} + +} // namespace subjective_logic \ No newline at end of file diff --git a/publications/2024_fusion/2024_fusion_wodtko_conflict.bib b/publications/2024_fusion/2024_fusion_wodtko_conflict.bib new file mode 100644 index 0000000..f24da48 --- /dev/null +++ b/publications/2024_fusion/2024_fusion_wodtko_conflict.bib @@ -0,0 +1,12 @@ +@inproceedings{wodtko2024conflict, + author={Wodtko, Thomas and Griebel, Thomas and Scheible, Alexander and Buchholz, Michael}, + booktitle={2024 27th International Conference on Information Fusion (FUSION)}, + title={Conflict Handling in Time-Dependent Subjective Networks}, + year={2024}, + volume={}, + number={}, + pages={1-8}, + keywords={Proposals;Logic}, + doi={10.23919/FUSION59988.2024.10706464} +} + diff --git a/publications/2024_fusion/README.md b/publications/2024_fusion/README.md new file mode 100644 index 0000000..05957a1 --- /dev/null +++ b/publications/2024_fusion/README.md @@ -0,0 +1,48 @@ +# Conflict Handling in Time-Dependent Subjective Networks + +The scripts in the `paper` directory will create some of the images used in the paper with the title above. +Additionally, the `presentation` directory contains some contains scripts to generate images used in the presentation of the paper at the conference (they might not be up to date with the newest library interface, sorry...).\ +Evaluations can be run as described in [here](../README.md). + +## Experiment description + +A barycentric triangle showing a binomial opinion is generated by `barycentric_triangle.py`. +It includes axis labels and the projection properties of an opinion.\ +The in depth investigation on trust revision operators is available in `trust_revision_josang_ours.py` and `trust_revision_sweep.py`. +Using barycentric triangles, the impact of trust revision is demonstrated.\ +Next, the evaluation on time series is available in `time_series_weather_forecast.py`. +Here, the progression of the projected probability and the errors are plotted in two different figures. + + + +TLDR: +1) barycentric triangle illustration: +```bash + barycentric_triangle.py +``` +2) single step trust revision: +```bash + trust_revision_josang_ours.py + trust_revision_sweep.py +``` +3) time series evaluation using trust revision: +```bash + time_series_weather_forecast.py +``` + +# Citation +Please use the following citation for reference to the publication in this directory (`2024_fusion_wodtko_conflict.bib`): +```bibtex +@inproceedings{wodtko2024conflict, + author={Wodtko, Thomas and Griebel, Thomas and Scheible, Alexander and Buchholz, Michael}, + booktitle={2024 27th International Conference on Information Fusion (FUSION)}, + title={Conflict Handling in Time-Dependent Subjective Networks}, + year={2024}, + volume={}, + number={}, + pages={1-8}, + keywords={Proposals;Logic}, + doi={10.23919/FUSION59988.2024.10706464} +} +``` + diff --git a/publications/2024_fusion/deps_to_remember.txt b/publications/2024_fusion/deps_to_remember.txt new file mode 100644 index 0000000..1695076 --- /dev/null +++ b/publications/2024_fusion/deps_to_remember.txt @@ -0,0 +1,12 @@ +install bezier and matplotlib (pip): +sudo python3 -m pip install bezier matplotlib + +in case of issues with matplotlib reinstall is after: +sudo apt remove python3-matplotlib +sudo python3 -m pip uninstall matplotlib + +install texlive: +sudo apt install texlive-latex-extra + +install text stuff for matplotlib: +sudo apt install cm-super dvipng diff --git a/publications/2024_fusion/paper/barycentric_triangle.py b/publications/2024_fusion/paper/barycentric_triangle.py new file mode 100644 index 0000000..528c2ed --- /dev/null +++ b/publications/2024_fusion/paper/barycentric_triangle.py @@ -0,0 +1,64 @@ +import subjective_logic as sl +import matplotlib.pyplot as plt + +plt.rcParams.update({ + 'font.size': 8, + 'text.usetex': True, + 'text.latex.preamble': " ".join([ + r'\usepackage{amsmath}', + r'\usepackage{amssymb}', + r'\usepackage{amsfonts}', + r'\usepackage{bm}', + ]) +}) +plt.rc('text', usetex=True) +plt.rc('font', family='serif') + +draw_flags_opinion = { + 'draw_hypo_texts': True, + 'draw_axis': True, + 'draw_axis_label': True, + 'draw_opinion': True, + 'draw_opinion_label': True, + 'draw_prior': True, + 'draw_prior_label': True, + 'draw_projection': True, + 'draw_projection_label': True, + 'belief_label_position': 0.5, + 'disbelief_label_position': 0.7, + 'uncertainty_label_position': 0.7, +} + +export_args = { + "format": 'pdf', + "dpi": 500, + "transparent": True, + "bbox_inches": 'tight', + "pad_inches": 0, +} + +opinion = sl.Opinion(0.4, 0.3) +opinion.prior_belief_masses = [0.3, 0.7] +variable_postfix = '_X^A' +# fig, ax = sl.create_triangle_plot(hypo_texts=('disbelief\n(sunny)', 'belief\n(rainy)')) +fig, ax = sl.draw_full_opinion_triangle(opinion, '_X', ('disbelief','belief'), draw_flags_opinion) + +x_start = -0.15 +x_end = 1.15 +y_start = -0.1 +y_end = 0.95 + +x_diff = x_end - x_start +y_diff = y_end - y_start + +relative_size = x_diff / y_diff + +figure_size_y = 5 + +for _ax, _fig in zip([ax],[fig]): + _ax.set_xlim(x_start, x_end) + _ax.set_ylim(y_start, y_end) + _fig.set_size_inches(figure_size_y * relative_size,figure_size_y) + +plt.show() +# fig.savefig("barycentric_triangle.pdf", **export_args) \ No newline at end of file diff --git a/publications/2024_fusion/paper/time_series_weather_forecast.py b/publications/2024_fusion/paper/time_series_weather_forecast.py new file mode 100644 index 0000000..a16f681 --- /dev/null +++ b/publications/2024_fusion/paper/time_series_weather_forecast.py @@ -0,0 +1,170 @@ +#!/usr/bin/python3 +import numpy as np +import subjective_logic as sl +import matplotlib.pyplot as plt + + +incorrect_observer = 5 +incorrect_prediction = 15 +rain_start = 30 +rain_stop = 40 + +n_steps = 55 +###################################################### +# create opinions +###################################################### + +trust_A_A = sl.Opinion2d([0.9,0.05],[1.0,0.0]) +trust_A_S = sl.Opinion2d([0.5,0.0],[1.0,0.0]) +trust_A_P = sl.Opinion2d([0.5,0.0],[1.0,0.0]) + +rainy_observation = sl.Opinion(0.8,0.0) +sunny_observation = sl.Opinion(0.0,0.8) + +predict_const_rainy = sl.Opinion(0.99, 0.0) +predict_const_sunny = sl.Opinion(0.0,0.99) + +predict_change_rainy = sl.Opinion(0.0,0.99) +predict_change_sunny = sl.Opinion(0.99,0.0) + +last_A_tr_ours = sl.Opinion2d() +last_A_tr_josang = sl.Opinion2d() +last_A_normal = sl.Opinion2d() +last_A_no_trust = sl.Opinion2d() + +tr_weighted_types_ours = [ + sl.WeightedTypes(sl.RelationType.CONFLICT, sl.TrustRevisionType.SHARES, sl.ConflictType.AVERAGE, 1), +] +tr_weighted_types_josang = [ + sl.WeightedTypes(sl.RelationType.CONFLICT, sl.TrustRevisionType.REFERENCE_FUSION, sl.ConflictType.BELIEF_AVERAGE, 1), +] + +values_tr_ours = np.zeros((n_steps,2)) +values_tr_josang = np.zeros((n_steps,2)) +values_normal = np.zeros((n_steps,2)) +values_no_trust = np.zeros((n_steps,2)) +values_gt = np.zeros((n_steps,2)) +values_gt[rain_start:rain_stop,1] = 1.0 +for k in range(n_steps): + # print(k) + # discount last opinion + last_A_tr_ours.trust_discount_(trust_A_A) + last_A_tr_josang.trust_discount_(trust_A_A) + last_A_normal.trust_discount_(trust_A_A) + + # prediction + last_A_pred_const_tr_ours = last_A_tr_ours.deduction(predict_const_rainy, predict_const_sunny) + last_A_pred_change_tr_ours = last_A_tr_ours.deduction(predict_change_rainy, predict_change_sunny) + last_A_pred_const_tr_josang = last_A_tr_josang.deduction(predict_const_rainy, predict_const_sunny) + last_A_pred_change_tr_josang = last_A_tr_josang.deduction(predict_change_rainy, predict_change_sunny) + last_A_pred_const_normal = last_A_normal.deduction(predict_const_rainy, predict_const_sunny) + last_A_pred_change_normal = last_A_normal.deduction(predict_change_rainy, predict_change_sunny) + last_A_pred_const_no_trust = last_A_no_trust.deduction(predict_const_rainy, predict_const_sunny) + last_A_pred_change_no_trust = last_A_no_trust.deduction(predict_change_rainy, predict_change_sunny) + + last_A_pred_tr_ours = last_A_pred_const_tr_ours + last_A_pred_tr_josang = last_A_pred_const_tr_josang + last_A_pred_normal = last_A_pred_const_normal + last_A_pred_no_trust = last_A_pred_const_no_trust + if k == rain_start or k == incorrect_prediction: + last_A_pred_tr_ours = last_A_pred_change_tr_ours + last_A_pred_tr_josang = last_A_pred_change_tr_josang + last_A_pred_normal = last_A_pred_change_normal + last_A_pred_no_trust = last_A_pred_change_no_trust + + t_pred_tr_ours = sl.TrustedOpinion2d(trust_A_P, last_A_pred_tr_ours) + t_pred_tr_josang = sl.TrustedOpinion2d(trust_A_P, last_A_pred_tr_josang) + t_pred_normal = sl.TrustedOpinion2d(trust_A_P, last_A_pred_normal) + + # sources + source_op = sunny_observation + if rain_start <= k < rain_stop: + source_op = rainy_observation + + t_source_vec = [ + sl.TrustedOpinion(trust_A_S, source_op), + sl.TrustedOpinion(trust_A_S, source_op), + sl.TrustedOpinion(trust_A_S, source_op), + ] + if k == incorrect_observer: + t_source_vec[0] = sl.TrustedOpinion(trust_A_S, rainy_observation) + + t_vec_tr_ours = t_source_vec + [t_pred_tr_ours] + t_vec_tr_josang = t_source_vec + [t_pred_tr_josang] + t_vec_normal = t_source_vec + [t_pred_normal] + discounted_opinions = sl.TrustedOpinion2d.extractDiscountedOpinions(t_vec_normal) + opinions_no_trust = sl.TrustedOpinion2d.extractOpinions(t_source_vec) + [last_A_pred_no_trust] + + op_A_normal = sl.Fusion.fuse_opinions(sl.FusionType.CUMULATIVE, discounted_opinions) + op_A_tr_ours = sl.TrustedFusion.fuse_opinions( + sl.FusionType.CUMULATIVE, + tr_weighted_types_ours, + t_vec_tr_ours, + ) + op_A_tr_josang = sl.TrustedFusion.fuse_opinions( + sl.FusionType.CUMULATIVE, + tr_weighted_types_josang, + t_vec_tr_josang, + ) + op_A_no_trust = sl.Fusion.fuse_opinions(sl.FusionType.CUMULATIVE, opinions_no_trust) + + values_tr_ours[k] = [k, op_A_tr_ours.getBinomialProjection()] + values_tr_josang[k] = [k, op_A_tr_josang.getBinomialProjection()] + values_normal[k] = [k, op_A_normal.getBinomialProjection()] + values_no_trust[k] = [k, op_A_no_trust.getBinomialProjection()] + values_gt[k,0] = k + + last_A_tr_ours = op_A_tr_ours + last_A_tr_josang = op_A_tr_josang + last_A_normal = op_A_normal + last_A_no_trust = op_A_no_trust + +plt.plot(values_no_trust[:,1], label='no trust') +# plt.plot(values_normal[:,1], label='normal') +plt.plot(values_tr_josang[:,1], label='josang') +plt.plot(values_tr_ours[:,1], label='cs') +plt.plot(values_gt[:,1], 'r', label='gt') +plt.legend() + +plt.figure() +plt.plot(np.abs((values_no_trust - values_gt)[:,1])) +plt.plot(np.abs((values_tr_josang - values_gt)[:,1])) +plt.plot(np.abs((values_tr_ours - values_gt)[:,1])) + +plt.show() + + +plot_data = { + "no_trust": values_no_trust, + # "trusted_fusion": values_normal, + "trust_revision_josang": values_tr_josang, + "trust_revision_ours": values_tr_ours, + "ground_truth": values_gt, +} + +pgfplots_add_plot_line = "\\addplot [name path={name}, color={color}, line width=1.5pt{extra}]\n\ttable[col sep=comma]{{%\n" +pgfplots_add_legend_entry = "\\addlegendentry{{{name}}}\n" +pgfplots_end = "};\n\n" + + +def export_values(name, data, filename): + with open(filename, 'w') as f: + f.write(pgfplots_add_plot_line.format(name=name, color=name + "_color", extra="")) + np.savetxt(f, data, delimiter=', ', ) # newline='\\\\\n') + f.write(pgfplots_end) + # f.write(pgfplots_add_legend_entry.format(name=name.replace('_',' '))) + f.write("\n") + + f.close() + + +# for name, data in plot_data.items(): +# filename_points =f"{name}_tikz_points.txt" +# filename_diffs =f"{name}_diffs_tikz_points.txt" +# +# export_values(name, data, filename_points) +# if not "truth" in name: +# diffs = data +# diffs[:,1] -= values_gt[:,1] +# export_values(name, np.abs(diffs), filename_diffs) + diff --git a/publications/2024_fusion/paper/trust_revision_josang_ours.py b/publications/2024_fusion/paper/trust_revision_josang_ours.py new file mode 100644 index 0000000..17d79e9 --- /dev/null +++ b/publications/2024_fusion/paper/trust_revision_josang_ours.py @@ -0,0 +1,303 @@ +#!/usr/bin/python3 +import subjective_logic as sl +import matplotlib.pyplot as plt + +plt.rcParams.update({ + 'font.size': 8, + 'text.usetex': True, + 'text.latex.preamble': " ".join([ + r'\usepackage{amsmath}', + r'\usepackage{amssymb}', + r'\usepackage{amsfonts}', + ]) +}) +plt.rc('text', usetex=True) +plt.rc('font', family='serif') + + +default_point_draw_args = { + "color" : 'tab:gray', + "zorder": 500, + "s": 80, +} +default_text_draw_args = { + "ha": 'left', + "va": 'top', + "fontsize": 18, + "offset": [0.01, 0.00], +} +default_arrow_draw_args = { + "length_includes_head": True, + "head_width": 0.02, + "overhang": 0.2, + "color": 'tab:gray', + "length_cap": 0.92, +} +export_args = { + "format": 'pdf', + "dpi": 500, + "transparent": True, + "bbox_inches": 'tight', + "pad_inches": 0, +} + +SHOW_ACC = False +SHOW_AVG = not SHOW_ACC +SHOW_NO_TR = True +SHOW_NO_TR_FUSION = False + +###################################################### +# create opinions +###################################################### +meas_s1 = sl.Opinion(0.1, 0.75) +trust_s1 = sl.Opinion2d([0.3, 0.3], [1.0, 0.0]) +t_s1 = sl.TrustedOpinion(trust_s1, meas_s1) + +meas_s2 = sl.Opinion(0.0, 0.7) +trust_s2 = sl.Opinion2d([0.3, 0.2], [1.0, 0.0]) +t_s2 = sl.TrustedOpinion(trust_s2, meas_s2) + +meas_s3 = sl.Opinion(0.7, 0.05) +trust_s3 = sl.Opinion2d([0.7, 0.0], [1.0, 0.0]) +t_s3 = sl.TrustedOpinion(trust_s3, meas_s3) + +t_vec = [t_s1, t_s2, t_s3] +opinions = sl.TrustedOpinion2d.extractOpinions(t_vec) +discounted_opinions = sl.TrustedOpinion2d.extractDiscountedOpinions(t_vec) + + +###################################################### +# calc normal and trusted fusion +###################################################### +normal_fusion = sl.Fusion.fuse_opinions(sl.FusionType.CUMULATIVE, opinions) +trusted_fusion = sl.TrustedFusion.fuse_opinions(sl.FusionType.CUMULATIVE, t_vec) + +###################################################### +# calc trusted fusion using trust revision +###################################################### +weighted_types_cs_acc = [ + sl.WeightedTypes(sl.RelationType.CONFLICT, sl.TrustRevisionType.SHARES, sl.ConflictType.ACCUMULATE, 2), +] +weighted_types_cs_avg = [ + sl.WeightedTypes(sl.RelationType.CONFLICT, sl.TrustRevisionType.SHARES, sl.ConflictType.AVERAGE, 2), +] +weighted_types_bc = [ + sl.WeightedTypes(sl.RelationType.CONFLICT, sl.TrustRevisionType.REFERENCE_FUSION, sl.ConflictType.BELIEF_AVERAGE, 2), +] + +fusion_cs_acc, t_vec_updated_cs_acc = sl.TrustedFusion.fuse_opinions_(sl.FusionType.CUMULATIVE, weighted_types_cs_acc, t_vec) +fusion_cs_avg, t_vec_updated_cs_avg = sl.TrustedFusion.fuse_opinions_(sl.FusionType.CUMULATIVE, weighted_types_cs_avg, t_vec) +fusion_bc, t_vec_updated_bc = sl.TrustedFusion.fuse_opinions_(sl.FusionType.CUMULATIVE, weighted_types_bc, t_vec) + +discounted_opinions_cs_acc = sl.TrustedOpinion2d.extractDiscountedOpinions(t_vec_updated_cs_acc) +discounted_opinions_cs_avg = sl.TrustedOpinion2d.extractDiscountedOpinions(t_vec_updated_cs_avg) +discounted_opinions_bc = sl.TrustedOpinion2d.extractDiscountedOpinions(t_vec_updated_bc) + +###################################################### +# illustrate results +###################################################### +oranges = plt.get_cmap('Oranges') +greens = plt.get_cmap('Greens') + +op_str = r"$\omega_{{{l}}}^{{{u}}}$" +op_tr_str = r"$\check{{\omega}}_{{{l}}}^{{{u}}}$" + +cs_shift = 2 +bc_shift = 0 + +###################################################### +# source opinions + +fig, ax = sl.create_triangle_plot(hypo_texts=('disbelief\n(sunny)', 'belief\n(rainy)')) +point_draw_args = default_point_draw_args.copy() +text_draw_args = default_text_draw_args.copy() +if SHOW_NO_TR: + for idx, opinion in enumerate(opinions): + sl.draw_point(opinion, **point_draw_args) + # op_str=r"$\omega_X^{S_{{idx}}}$" + if idx == 2: + text_draw_args["ha"] = "right" + text_draw_args["va"] = "top" + text_draw_args["offset"] = [0.0, -0.02] + text = op_str.format(l="X", u="S_" + str(idx)) + " = " + op_str.format(l="X", u="[A;S_" + str(idx) + "]") + sl.draw_text_at_point(opinion, text=text, **text_draw_args) + continue + sl.draw_text_at_point(opinion, text=op_str.format(l="X", u="S_" + str(idx)), **text_draw_args) + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:blue' +text_draw_args = default_text_draw_args.copy() +for idx, opinion in enumerate(discounted_opinions): + sl.draw_point(opinion, **point_draw_args) + if idx == 2 and SHOW_NO_TR: + continue + if idx == 1: + text_draw_args["ha"] = "right" + text_draw_args["va"] = "bottom" + sl.draw_text_at_point(opinion, text=op_str.format(l="X", u="[A;S_" + str(idx) + "]"), **text_draw_args) + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:green' +text_draw_args = default_text_draw_args.copy() +text_draw_args["ha"] = "right" +text_draw_args["va"] = "bottom" +text_draw_args["offset"] = [-0.01, 0.] +if SHOW_ACC: + for idx, (opinion, opinion_up) in enumerate(zip(discounted_opinions, discounted_opinions_cs_acc)): + if opinion == opinion_up: + continue + sl.draw_point(opinion_up, **point_draw_args) + sl.draw_text_at_point(opinion_up, text=op_tr_str.format(l="X", u="[A;S_" + str(idx) + "]"), **text_draw_args) + +if SHOW_AVG: + for idx, (opinion, opinion_up) in enumerate(zip(discounted_opinions, discounted_opinions_cs_avg)): + if opinion == opinion_up: + continue + sl.draw_point(opinion_up, **point_draw_args) + sl.draw_text_at_point(opinion_up, text=op_tr_str.format(l="X", u="[A;S_" + str(idx) + "]"), **text_draw_args) + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:orange' +text_draw_args = default_text_draw_args.copy() +text_draw_args["va"] = "bottom" +for idx, (opinion, opinion_up) in enumerate(zip(discounted_opinions, discounted_opinions_bc)): + if opinion == opinion_up: + continue + sl.draw_point(opinion_up, **point_draw_args) + sl.draw_text_at_point(opinion_up, text=op_tr_str.format(l="X", u="[A;S_" + str(idx) + "]"), **text_draw_args) + +sl.draw_arrow(discounted_opinions[cs_shift], discounted_opinions_cs_avg[cs_shift], **default_arrow_draw_args) +sl.draw_arrow(discounted_opinions[bc_shift], discounted_opinions_bc[bc_shift], **default_arrow_draw_args) + +###################################################### +# trust opinions + +sl.reset_plot() +fig2, ax2 = sl.create_triangle_plot(hypo_texts=('distrust', 'trust')) + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:blue' +text_draw_args = default_text_draw_args.copy() +text_draw_args["ha"] = "right" +text_draw_args["va"] = "bottom" +text_draw_args["offset"] = [-0.01, 0.02] +for idx, t_op in enumerate(t_vec): + trust = t_op.trust + if idx == 2: + text_draw_args["ha"] = "left" + text_draw_args["va"] = "bottom" + text_draw_args["offset"] = [0.01, 0.02] + sl.draw_point(trust, **point_draw_args) + sl.draw_text_at_point(trust, text=op_str.format(u="A", l="S_" + str(idx)), **text_draw_args) + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:green' +text_draw_args = default_text_draw_args.copy() +text_draw_args["offset"] = [0.01, -0.01] +if SHOW_ACC: + for idx, (t_op, t_op_up) in enumerate(zip(t_vec, t_vec_updated_cs_acc)): + if t_op.trust == t_op_up.trust: + continue + trust = t_op_up.trust + sl.draw_point(trust, **point_draw_args) + sl.draw_text_at_point(trust, text=op_tr_str.format(u="A", l="S_" + str(idx)), **text_draw_args) + +if SHOW_AVG: + for idx, (t_op, t_op_up) in enumerate(zip(t_vec, t_vec_updated_cs_avg)): + if t_op.trust == t_op_up.trust: + continue + trust = t_op_up.trust + sl.draw_point(trust, **point_draw_args) + sl.draw_text_at_point(trust, text=op_tr_str.format(u="A", l="S_" + str(idx)), **text_draw_args) + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:orange' +text_draw_args = default_text_draw_args.copy() +text_draw_args["offset"] = [0.01, -0.01] +for idx, (t_op, t_op_up) in enumerate(zip(t_vec, t_vec_updated_bc)): + if t_op.trust == t_op_up.trust: + continue + trust = t_op_up.trust + sl.draw_point(trust, **point_draw_args) + sl.draw_text_at_point(trust, text=op_tr_str.format(u="A", l="S_" + str(idx)), **text_draw_args) + +sl.draw_arrow(t_vec[cs_shift].trust, t_vec_updated_cs_avg[cs_shift].trust, **default_arrow_draw_args) +sl.draw_arrow(t_vec[bc_shift].trust, t_vec_updated_bc[bc_shift].trust, **default_arrow_draw_args) + +###################################################### +# fusion opinions + + +sl.reset_plot() +fig3, ax3 = sl.create_triangle_plot(hypo_texts=('disbelief\n(sunny)', 'belief\n(rainy)')) + +point_draw_args = default_point_draw_args.copy() +text_draw_args = default_text_draw_args.copy() +if SHOW_NO_TR_FUSION: + point_draw_args["color"] = 'tab:gray' + sl.draw_point(normal_fusion, **point_draw_args) + text_draw_args["offset"] = [-0.015, 0.03] + text_draw_args["ha"] = 'right' + sl.draw_text_at_point(normal_fusion, text=op_str.format(l="X", u="\diamond (\mathbb{S})"), **text_draw_args) + +point_draw_args["color"] = 'tab:blue' +sl.draw_point(trusted_fusion, **point_draw_args) +text_draw_args["offset"] = [0., -0.02] +text_draw_args["ha"] = 'center' +text_draw_args["va"] = 'top' +sl.draw_text_at_point(trusted_fusion, text=op_str.format(l="X", u="\diamond ([A;\mathbb{S}])"), **text_draw_args) + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:green' +text_draw_args = default_text_draw_args.copy() +text_draw_args["offset"] = [0., 0.01] +text_draw_args["ha"] = 'center' +text_draw_args["va"] = 'bottom' +if SHOW_ACC: + sl.draw_point(fusion_cs_acc, **point_draw_args) + sl.draw_text_at_point(fusion_cs_acc, text=op_tr_str.format(l="X", u="\diamond ([A;\mathbb{S}])"), **text_draw_args) +if SHOW_AVG: + sl.draw_point(fusion_cs_avg, **point_draw_args) + sl.draw_text_at_point(fusion_cs_avg, text=op_tr_str.format(l="X", u="\diamond ([A;\mathbb{S}])"), **text_draw_args) + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:orange' +text_draw_args = default_text_draw_args.copy() +text_draw_args["offset"] = [0., 0.01] +text_draw_args["ha"] = 'left' +text_draw_args["va"] = 'bottom' +sl.draw_point(fusion_bc, **point_draw_args) +sl.draw_text_at_point(fusion_bc, text=op_tr_str.format(l="X", u="\diamond ([A;\mathbb{S}])"), **text_draw_args) + +arrow_draw_args = default_arrow_draw_args.copy() +arrow_draw_args["length_cap"] = 0.9 +sl.draw_arrow(trusted_fusion, fusion_cs_avg, **arrow_draw_args) +arrow_draw_args["length_cap"] = 0.8 +sl.draw_arrow(trusted_fusion, fusion_bc, **arrow_draw_args) + + +x_start = -0.05 +x_end = 1.05 +y_start = -0.175 +y_end = 0.95 + +x_diff = x_end - x_start +y_diff = y_end - y_start + +relative_size = x_diff / y_diff + +figure_size_y = 5 + +for _ax, _fig in zip([ax,ax2,ax3],[fig,fig2, fig3]): + _ax.set_xlim(x_start, x_end) + _ax.set_ylim(y_start, y_end) + _fig.set_size_inches(figure_size_y * relative_size,figure_size_y) + +# fig.savefig("exp_tr_opinions.pdf", **export_args) +# fig2.savefig("exp_tr_trust.pdf", **export_args) +# fig3.savefig("exp_tr_fusion.pdf", **export_args) + +plt.show() + + + diff --git a/publications/2024_fusion/paper/trust_revision_sweep.py b/publications/2024_fusion/paper/trust_revision_sweep.py new file mode 100644 index 0000000..e71ddc0 --- /dev/null +++ b/publications/2024_fusion/paper/trust_revision_sweep.py @@ -0,0 +1,158 @@ +#!/usr/bin/python3 +import numpy as np +import subjective_logic as sl +import matplotlib.pyplot as plt + +plt.rcParams.update({ + 'font.size': 8, + 'text.usetex': True, + 'text.latex.preamble': " ".join([ + r'\usepackage{amsmath}', + r'\usepackage{amssymb}', + r'\usepackage{amsfonts}', + ]) +}) +plt.rc('text', usetex=True) +plt.rc('font', family='serif') + + +default_point_draw_args = { + "color" : 'tab:gray', + "zorder": 500, + "s": 80, +} +default_text_draw_args = { + "ha": 'left', + "va": 'top', + "fontsize": 18, + "offset": [0.01, 0.00], +} +default_arrow_draw_args = { + "length_includes_head": True, + "head_width": 0.02, + "overhang": 0.2, + "color": 'tab:gray', + "length_cap": 0.92, +} +export_args = { + "format": 'pdf', + "dpi": 500, + "transparent": True, + "bbox_inches": 'tight', + "pad_inches": 0, +} + +SHOW_ACC = False +SHOW_AVG = not SHOW_ACC +SHOW_NO_TR = True +SHOW_NO_TR_FUSION = False + +conflict_weights_ours = np.linspace(0.,5.,20) +conflict_weights_josang = np.linspace(0.,5.,20) +# conflict_weights_josang = np.linspace(0.,20.,50) + +###################################################### +# create opinions +###################################################### +meas_s1 = sl.Opinion(0.1, 0.75) +trust_s1 = sl.Opinion2d([0.3, 0.3], [1.0, 0.0]) +t_s1 = sl.TrustedOpinion(trust_s1, meas_s1) + +meas_s2 = sl.Opinion(0.0, 0.7) +trust_s2 = sl.Opinion2d([0.3, 0.2], [1.0, 0.0]) +t_s2 = sl.TrustedOpinion(trust_s2, meas_s2) + +meas_s3 = sl.Opinion(0.7, 0.05) +trust_s3 = sl.Opinion2d([0.7, 0.0], [1.0, 0.0]) +t_s3 = sl.TrustedOpinion(trust_s3, meas_s3) + +t_vec = [t_s1, t_s2, t_s3] + +###################################################### +# calc normal and trusted fusion +###################################################### +trusted_fusion = sl.TrustedFusion.fuse_opinions(sl.FusionType.CUMULATIVE, t_vec) + +###################################################### +# calc trusted fusion using trust revision +###################################################### + +t_fusion = [] +for c_ours in conflict_weights_ours: + t_fusion_normal = [] + for c_josan in conflict_weights_josang: + weighted_types = [ + sl.WeightedTypes(sl.RelationType.CONFLICT, sl.TrustRevisionType.SHARES, sl.ConflictType.AVERAGE, c_ours), + sl.WeightedTypes(sl.RelationType.CONFLICT, sl.TrustRevisionType.REFERENCE_FUSION, sl.ConflictType.BELIEF_AVERAGE, c_josan), + ] + fusion = sl.TrustedFusion.fuse_opinions(sl.FusionType.CUMULATIVE, weighted_types, t_vec) + t_fusion_normal.append(fusion) + + t_fusion.append(t_fusion_normal) + +###################################################### +# illustrate results +###################################################### +oranges = plt.get_cmap('Oranges') +greens = plt.get_cmap('Greens') + +op_str = r"$\omega_{{{l}}}^{{{u}}}$" +op_tr_str = r"$\check{{\omega}}_{{{l}}}^{{{u}}}$" + +cs_shift = 2 +bc_shift = 0 + +###################################################### +# fusion opinions + +sl.reset_plot() +fig, ax = sl.create_triangle_plot(hypo_texts=('disbelief\n(sunny)', 'belief\n(rainy)')) + +point_draw_args = default_point_draw_args.copy() +point_draw_args["edgecolor"] = 'none' +for idx1, c_ours in enumerate(conflict_weights_ours): + for idx2, c_josang in enumerate(conflict_weights_josang): + + color_1 = greens(0.5 - idx2 / len(conflict_weights_josang) / 2.) + color_2 = oranges(0.5 - idx1 / len(conflict_weights_ours) / 2.) + + div = idx1 + idx2 + if div == 0: + color = 0.5 * np.array(color_1) + 0.5 * np.array(color_2) + else: + color = idx1 / div * np.array(color_1) + idx2 / div * np.array(color_2) + + point_draw_args["color"] = color + # marker='D' + sl.draw_point(t_fusion[idx1][idx2], **point_draw_args) + + +point_draw_args["color"] = 'tab:blue' +sl.draw_point(trusted_fusion, **point_draw_args) + +###################################################### +# export results + +x_start = -0.05 +x_end = 1.05 +y_start = -0.175 +y_end = 0.95 + +x_diff = x_end - x_start +y_diff = y_end - y_start + +relative_size = x_diff / y_diff + +figure_size_y = 5 + +for _ax, _fig in zip([ax],[fig]): + _ax.set_xlim(x_start, x_end) + _ax.set_ylim(y_start, y_end) + _fig.set_size_inches(figure_size_y * relative_size,figure_size_y) + +# fig.savefig("exp_tr_seep_2_opinions.pdf", **export_args) + +plt.show() + + + diff --git a/publications/2024_fusion/presentation/.gitignore b/publications/2024_fusion/presentation/.gitignore new file mode 100644 index 0000000..e33609d --- /dev/null +++ b/publications/2024_fusion/presentation/.gitignore @@ -0,0 +1 @@ +*.png diff --git a/publications/2024_fusion/presentation/barycentric_triangle.py b/publications/2024_fusion/presentation/barycentric_triangle.py new file mode 100644 index 0000000..b85c2bf --- /dev/null +++ b/publications/2024_fusion/presentation/barycentric_triangle.py @@ -0,0 +1,83 @@ +import subjective_logic as sl +import matplotlib.pyplot as plt + +plt.rcParams.update({ + 'font.size': 8, + 'text.usetex': True, + 'text.latex.preamble': " ".join([ + r'\usepackage{amsmath}', + r'\usepackage{amssymb}', + r'\usepackage{amsfonts}', + r'\usepackage{bm}', + ]) +}) +plt.rc('text', usetex=True) +plt.rc('font', family='serif') + +draw_flags_opinion_start = { + 'draw_hypo_texts': True, + 'draw_axis': True, + 'draw_axis_label': True, + 'draw_opinion': True, + 'draw_opinion_label': True, + 'draw_prior': False, + 'draw_prior_label': False, + 'draw_projection': False, + 'draw_projection_label': False, + 'belief_label_position': 0.5, + 'disbelief_label_position': 0.7, + 'uncertainty_label_position': 0.7, +} + +draw_flags_opinion_end = draw_flags_opinion_start.copy() +draw_flags_opinion_end['draw_prior'] = True +draw_flags_opinion_end['draw_prior_label'] = True +draw_flags_opinion_end['draw_projection'] = True +draw_flags_opinion_end['draw_projection_label'] = True + +export_args = { + "format": 'png', + "dpi": 500, + "transparent": True, + "bbox_inches": 'tight', + "pad_inches": 0, +} + +opinion = sl.Opinion(0.4, 0.3) +opinion.prior_belief_masses = [0.2, 0.8] +# variable_postfix = '_X^A' + +fig_start, ax_start, _= sl.draw_full_opinion_triangle( + opinion, + '_X', + ('$\overline{x}$\n(disbelief)', '$x$\n(belief)'), + draw_flags_opinion_start +) +sl.reset_plot() +fig_end, ax_end, _= sl.draw_full_opinion_triangle( + opinion, + '_X', + ('$\overline{x}$\n(disbelief)', '$x$\n(belief)'), + draw_flags_opinion_end +) + +x_start = -0.15 +x_end = 1.15 +y_start = -0.1 +y_end = 0.95 + +x_diff = x_end - x_start +y_diff = y_end - y_start + +relative_size = x_diff / y_diff + +figure_size_y = 5 + +for _ax, _fig in zip([ax_start, ax_end],[fig_start, fig_end]): + _ax.set_xlim(x_start, x_end) + _ax.set_ylim(y_start, y_end) + _fig.set_size_inches(figure_size_y * relative_size,figure_size_y) + +plt.show() +fig_start.savefig("barycentric_triangle_start.png", **export_args) +fig_end.savefig("barycentric_triangle_end.png", **export_args) diff --git a/publications/2024_fusion/presentation/conflict_shares.py b/publications/2024_fusion/presentation/conflict_shares.py new file mode 100644 index 0000000..1b72d41 --- /dev/null +++ b/publications/2024_fusion/presentation/conflict_shares.py @@ -0,0 +1,162 @@ +import itertools + +import subjective_logic as sl +import matplotlib.pyplot as plt +import math + +plt.rcParams.update({ + 'font.size': 8, + 'text.usetex': True, + 'text.latex.preamble': " ".join([ + r'\usepackage{amsmath}', + r'\usepackage{amssymb}', + r'\usepackage{amsfonts}', + r'\usepackage{bm}', + ]) +}) +plt.rc('text', usetex=True) +plt.rc('font', family='serif') + +draw_flags_opinion = { + 'draw_hypo_texts': True, + 'draw_axis': False, + 'draw_axis_label': False, + 'draw_opinion': True, + 'draw_opinion_label': True, + 'draw_prior': False, + 'draw_prior_label': False, + 'draw_projection': False, + 'draw_projection_label': False, + 'belief_label_position': 0.5, + 'disbelief_label_position': 0.7, + 'uncertainty_label_position': 0.7, +} + +default_point_draw_args = { + "color" : 'tab:gray', + "zorder": 500, + "s": 80, +} + +default_text_draw_args = { + "ha": 'left', + "va": 'top', + "fontsize": 18, + "offset": [0.01, 0.00], +} +default_arrow_draw_args = { + "length_includes_head": True, + "head_width": 0.02, + "overhang": 0.2, + "color": 'tab:gray', + "length_cap": 0.8, +} + +export_args = { + "format": 'png', + "dpi": 500, + "transparent": True, + "bbox_inches": 'tight', + "pad_inches": 0, +} +op_str = r"$\omega_{{{l}}}^{{{u}}}$" + +def size_figure(fig, ax): + x_start = -0.15 + x_end = 1.15 + y_start = -0.1 + y_end = 0.95 + + x_diff = x_end - x_start + y_diff = y_end - y_start + + relative_size = x_diff / y_diff + + figure_size_y = 5 + + ax.set_xlim(x_start, x_end) + ax.set_ylim(y_start, y_end) + fig.set_size_inches(figure_size_y * relative_size, figure_size_y) + + +def store_figure(fig, ax, filename, export_arguments=None): + if export_arguments is None: + export_arguments = {} + size_figure(fig, ax) + if STORE_FIGS: + fig.savefig(filename, **export_arguments) + + +STORE_FIGS = True + +opinion_0 = sl.Opinion(0.65, 0.2) +opinion_1 = sl.Opinion(0.1, 0.75) +opinion_2 = sl.Opinion(0.1, 0.6) +opinions = [opinion_0, opinion_1, opinion_2] + +fig, ax = sl.create_triangle_plot(hypo_texts=('disbelief\n(sunny)', 'belief\n(rainy)')) +for idx, opinion in enumerate(opinions): + point_draw_args = default_point_draw_args.copy() + text_draw_args = default_text_draw_args.copy() + if idx == 1: + text_draw_args['ha'] = 'center' + text_draw_args['va'] = 'top' + text_draw_args['offset'] = [0.01, -0.03] + elif idx == 2: + text_draw_args['ha'] = 'center' + text_draw_args['va'] = 'bottom' + text_draw_args['offset'] = [0.01, 0.03] + sl.draw_point(opinion, **point_draw_args) + sl.draw_text_at_point(opinion, text=op_str.format(l="X", u="S_" + str(idx)), **text_draw_args) + +arrow_draw_args = default_arrow_draw_args.copy() +arrow_draw_args['color'] = 'tab:green' +arrow_draw_args['y_offset'] = 0.05 +sl.draw_arrow(opinions[0], opinions[1], start_offset=0.5, **arrow_draw_args) +arrow_draw_args['y_offset'] = -0.05 +sl.draw_arrow(opinions[1], opinions[0], start_offset=0.5, **arrow_draw_args) + +store_figure(fig, ax, 'ms_cs_2.png', export_args) + +sl.reset_plot() + +fig, ax = sl.create_triangle_plot(hypo_texts=('disbelief\n(sunny)', 'belief\n(rainy)')) +for idx, opinion in enumerate(opinions): + point_draw_args = default_point_draw_args.copy() + text_draw_args = default_text_draw_args.copy() + if idx == 1: + text_draw_args['ha'] = 'center' + text_draw_args['va'] = 'top' + text_draw_args['offset'] = [0.01, -0.03] + elif idx == 2: + text_draw_args['ha'] = 'center' + text_draw_args['va'] = 'bottom' + text_draw_args['offset'] = [0.01, 0.03] + sl.draw_point(opinion, **point_draw_args) + sl.draw_text_at_point(opinion, text=op_str.format(l="X", u="S_" + str(idx)), **text_draw_args) + +arrow_draw_args = default_arrow_draw_args.copy() +arrow_draw_args['color'] = 'tab:green' +# since opinions circle size is not considered, shorten arrow for close opinions +arrow_draw_args['length_cap'] = 0.6 +arrow_draw_args['y_offset'] = 0.05 +sl.draw_arrow(opinions[1], opinions[2], start_offset=0.5, **arrow_draw_args) +arrow_draw_args['y_offset'] = -0.05 +sl.draw_arrow(opinions[2], opinions[1], start_offset=0.5, **arrow_draw_args) + +store_figure(fig, ax, 'ms_cs_0.png', export_args) + +# calc revision factors for this specific example +weighted_types_cs_avg = [ + (sl.TrustRevision.TrustRevisionType.CONFLICT_SHARES, sl.Conflict.ConflictType.AVERAGE, 2.), +] + +revision_factors = sl.TrustRevision.revision_factors( + sl.TrustRevision.TrustRevisionType.CONFLICT_SHARES, + sl.Conflict.ConflictType.AVERAGE, + [sl.TrustedOpinion(sl.Opinion2d(1.0,0.), op) for op in opinions] + # [True,True,True] +) +print(revision_factors) + +plt.show() diff --git a/publications/2024_fusion/presentation/degree_of_conflict.py b/publications/2024_fusion/presentation/degree_of_conflict.py new file mode 100644 index 0000000..bc1666d --- /dev/null +++ b/publications/2024_fusion/presentation/degree_of_conflict.py @@ -0,0 +1,128 @@ +import itertools + +import subjective_logic as sl +import matplotlib.pyplot as plt +import numpy as np +import math + +plt.rcParams.update({ + 'font.size': 8, + 'text.usetex': True, + 'text.latex.preamble': " ".join([ + r'\usepackage{amsmath}', + r'\usepackage{amssymb}', + r'\usepackage{amsfonts}', + r'\usepackage{bm}', + ]) +}) +plt.rc('text', usetex=True) +plt.rc('font', family='serif') + +draw_flags_opinion = { + 'draw_hypo_texts': True, + 'draw_axis': False, + 'draw_axis_label': False, + 'draw_opinion': True, + 'draw_opinion_label': True, + 'draw_prior': False, + 'draw_prior_label': False, + 'draw_projection': False, + 'draw_projection_label': False, + 'belief_label_position': 0.5, + 'disbelief_label_position': 0.7, + 'uncertainty_label_position': 0.7, +} + +default_point_draw_args = { + "color" : 'tab:gray', + "zorder": 500, + "s": 80, +} + +default_text_draw_args = { + "ha": 'left', + "va": 'top', + "fontsize": 18, + "offset": [0.01, 0.00], +} +default_arrow_draw_args = { + "length_includes_head": True, + "head_width": 0.02, + "overhang": 0.2, + "color": 'tab:gray', + "length_cap": 0.8, +} + +export_args = { + "format": 'png', + "dpi": 500, + "transparent": True, + "bbox_inches": 'tight', + "pad_inches": 0, +} +op_str = r"$\omega_{{{l}}}^{{{u}}}$" + +def size_figure(fig, ax): + x_start = -0.15 + x_end = 1.15 + y_start = -0.1 + y_end = 0.95 + + x_diff = x_end - x_start + y_diff = y_end - y_start + + relative_size = x_diff / y_diff + + figure_size_y = 5 + + ax.set_xlim(x_start, x_end) + ax.set_ylim(y_start, y_end) + fig.set_size_inches(figure_size_y * relative_size, figure_size_y) + + +def store_figure(fig, ax, filename, export_arguments=None): + if export_arguments is None: + export_arguments = {} + size_figure(fig, ax) + if STORE_FIGS: + fig.savefig(filename, **export_arguments) + + +STORE_FIGS = True + +opinion_0 = sl.Opinion(0.65, 0.2) +opinion_1 = sl.Opinion(0.1, 0.75) +opinion_2 = sl.Opinion(0.1, 0.6) +opinions = [opinion_0, opinion_1, opinion_2] + +fig, ax = sl.create_triangle_plot(hypo_texts=('disbelief\n(sunny)', 'belief\n(rainy)')) +for idx, opinion in enumerate(opinions): + point_draw_args = default_point_draw_args.copy() + text_draw_args = default_text_draw_args.copy() + if idx == 1: + text_draw_args['ha'] = 'center' + text_draw_args['va'] = 'top' + text_draw_args['offset'] = [0.01, -0.03] + elif idx == 2: + text_draw_args['ha'] = 'center' + text_draw_args['va'] = 'bottom' + text_draw_args['offset'] = [0.01, 0.03] + sl.draw_point(opinion, **point_draw_args) + sl.draw_text_at_point(opinion, text=op_str.format(l="X", u="S_" + str(idx)), **text_draw_args) + +store_figure(fig, ax, 'ms_dc_init.png', export_args) + +for subset in itertools.combinations(opinions, 2): + + arrow_draw_args = default_arrow_draw_args.copy() + arrow_draw_args['color'] = 'tab:red' + middle = sl.get_bari_point_between(subset[0], subset[1]) + # since opinions circle size is not considered, shorten arrow for close opinions + if subset[0].disbelief() > 0.5 and subset[1].disbelief() > 0.5: + arrow_draw_args['length_cap'] = 0.6 + sl.draw_arrow(subset[0], subset[1], start_offset=0.5, **arrow_draw_args) + sl.draw_arrow(subset[1], subset[0], start_offset=0.5, **arrow_draw_args) + +store_figure(fig, ax, 'ms_dc_arrows.png', export_args) + +plt.show() diff --git a/publications/2024_fusion/presentation/sl_fusion.py b/publications/2024_fusion/presentation/sl_fusion.py new file mode 100644 index 0000000..0d77306 --- /dev/null +++ b/publications/2024_fusion/presentation/sl_fusion.py @@ -0,0 +1,143 @@ +import subjective_logic as sl +import matplotlib.pyplot as plt +import numpy as np +import math + +plt.rcParams.update({ + 'font.size': 8, + 'text.usetex': True, + 'text.latex.preamble': " ".join([ + r'\usepackage{amsmath}', + r'\usepackage{amssymb}', + r'\usepackage{amsfonts}', + r'\usepackage{bm}', + ]) +}) +plt.rc('text', usetex=True) +plt.rc('font', family='serif') + +draw_flags_opinion = { + 'draw_hypo_texts': True, + 'draw_axis': True, + 'draw_axis_label': False, + 'draw_opinion': True, + 'draw_opinion_label': True, + 'draw_prior': False, + 'draw_prior_label': False, + 'draw_projection': False, + 'draw_projection_label': False, + 'belief_label_position': 0.5, + 'disbelief_label_position': 0.7, + 'uncertainty_label_position': 0.7, +} + + +export_args = { + "format": 'png', + "dpi": 500, + "transparent": True, + "bbox_inches": 'tight', + "pad_inches": 0, +} + +opinion_0 = sl.Opinion(0.55, 0.1) +opinion_1 = sl.Opinion(0.55, 0.1) +opinion_2 = sl.Opinion(0.1, 0.55) +# opinion.prior_belief_masses = [0.2, 0.8] +# variable_postfix = '_X^A' + +fig_0, ax_0, _= sl.draw_full_opinion_triangle( + opinion_0, + '^A_X', + ('$\overline{x}$\n(sunny)', '$x$\n(rainy)'), + draw_flags_opinion +) +sl.reset_plot() +fig_1, ax_1, _ = sl.draw_full_opinion_triangle( + opinion_1, + '^B_X', + ('$\overline{x}$\n(sunny)', '$x$\n(rainy)'), + draw_flags_opinion +) +sl.reset_plot() +fig_2, ax_2, _ = sl.draw_full_opinion_triangle( + opinion_2, + '^C_X', + ('$\overline{x}$\n(sunny)', '$x$\n(rainy)'), + draw_flags_opinion +) +sl.reset_plot() + +fused_opinion = sl.Fusion.fuse_opinions(sl.Fusion.FusionType.CUMULATIVE, [opinion_0, opinion_1, opinion_2]) + +fig_f, ax_f, _= sl.draw_full_opinion_triangle( + fused_opinion, + '^{\diamond[\{A,B,C\}]}_X', + ('$\overline{x}$\n(sunny)', '$x$\n(rainy)'), + draw_flags_opinion +) + +sl.reset_plot() +draw_flags_opinion['draw_axis'] = False + +fig_f2, ax_f2, _= sl.draw_full_opinion_triangle( + fused_opinion, + '^{\diamond[\{A,B,C\}]}_X', + ('$\overline{x}$\n(sunny)', '$x$\n(rainy)'), + draw_flags_opinion +) + +sl.reset_plot() + +fig_all, ax_all, _ = sl.draw_full_opinion_triangle( + opinion_0, + '^A_X', + ('$\overline{x}$\n(sunny)', '$x$\n(rainy)'), + draw_flags_opinion +) +sl.draw_full_opinion_triangle( + opinion_2, + '^C_X', + ('$\overline{x}$\n(sunny)', '$x$\n(rainy)'), + draw_flags_opinion +) +draw_flags_opinion['draw_opinion'] = False +sl.draw_full_opinion_triangle( + opinion_1, + '^B_X', + ('$\overline{x}$\n(sunny)', '$x$\n(rainy)'), + draw_flags_opinion +) +text_draw_args = { + "ha": 'right', + "va": 'bottom', + "fontsize": 18, + "offset": [-0.01, 0.02], +} +text = '$w^B_X$' +sl.draw_text_at_point(opinion_1, text=text, **text_draw_args) + +x_start = -0.15 +x_end = 1.15 +y_start = -0.1 +y_end = 0.95 + +x_diff = x_end - x_start +y_diff = y_end - y_start + +relative_size = x_diff / y_diff + +figure_size_y = 5 + +for _ax, _fig in zip([ax_0, ax_1, ax_2, ax_f, ax_f2, ax_all],[fig_0, fig_1, fig_2, fig_f, fig_f2, fig_all]): + _ax.set_xlim(x_start, x_end) + _ax.set_ylim(y_start, y_end) + _fig.set_size_inches(figure_size_y * relative_size,figure_size_y) + +plt.show() +fig_0.savefig("sl_fusion_0.png", **export_args) +fig_1.savefig("sl_fusion_1.png", **export_args) +fig_2.savefig("sl_fusion_2.png", **export_args) +fig_f.savefig("sl_fusion_f.png", **export_args) +fig_f2.savefig("sl_fusion_f2.png", **export_args) +fig_all.savefig("sl_fusion_all.png", **export_args) diff --git a/publications/2024_fusion/presentation/sl_trust_discount.py b/publications/2024_fusion/presentation/sl_trust_discount.py new file mode 100644 index 0000000..e2a3a87 --- /dev/null +++ b/publications/2024_fusion/presentation/sl_trust_discount.py @@ -0,0 +1,115 @@ +import subjective_logic as sl +import matplotlib.pyplot as plt +import numpy as np +import math + +plt.rcParams.update({ + 'font.size': 8, + 'text.usetex': True, + 'text.latex.preamble': " ".join([ + r'\usepackage{amsmath}', + r'\usepackage{amssymb}', + r'\usepackage{amsfonts}', + r'\usepackage{bm}', + ]) +}) +plt.rc('text', usetex=True) +plt.rc('font', family='serif') + +draw_flags_opinion = { + 'draw_hypo_texts': True, + 'draw_axis': True, + 'draw_axis_label': True, + 'draw_opinion': True, + 'draw_opinion_label': True, + 'draw_prior': True, + 'draw_prior_label': True, + 'draw_projection': True, + 'draw_projection_label': True, + 'belief_label_position': 0.5, + 'disbelief_label_position': 0.7, + 'uncertainty_label_position': 0.7, + 'prior_offset': [-0.02, -0.02], + 'projection_offset': [0.03, -0.02], +} + +export_args = { + "format": 'png', + "dpi": 500, + "transparent": True, + "bbox_inches": 'tight', + "pad_inches": 0, +} + +trust = sl.Opinion([0.3,0.4],[0.85,0.15]) +opinion = sl.Opinion([0.4,0.3],[0.2,0.8]) + +discounted_opinion = opinion.trust_discount(trust) + +axs = [] +figs = [] + +fig, ax, _= sl.draw_full_opinion_triangle( + trust, + '^A_B', + ('distrust', 'trust'), + draw_flags_opinion +) +figs.append(fig) +axs.append(ax) +sl.reset_plot() + +fig, ax, _ = sl.draw_full_opinion_triangle( + opinion, + '^B_X', + ('$\overline{x}$\n(sunny)', '$x$\n(rainy)'), + draw_flags_opinion +) +figs.append(fig) +axs.append(ax) +sl.reset_plot() +draw_flags_opinion['belief_label_position'] = 1.6 +draw_flags_opinion['disbelief_label_position'] = 1.8 + +fig, ax, _ = sl.draw_full_opinion_triangle( + discounted_opinion, + '^{A;B}_X', + ('$\overline{x}$\n(sunny)', '$x$\n(rainy)'), + draw_flags_opinion +) +lower_left = sl.get_bari_point_between(sl.Opinion(0., 0.), sl.Opinion(0., 1.), position=trust.getBinomialProjection()) +lower_right = sl.get_bari_point_between(sl.Opinion(0., 0.), sl.Opinion(1., 0.), position=trust.getBinomialProjection()) + +# define nodes for triangle and plot empty triangle +nodes = np.asfortranarray([ + [lower_left[0], lower_right[0], 0.5], + [lower_left[1], lower_right[1], math.sqrt(3) / 2], +]) +triangle = bezier.Triangle(nodes, degree=1) +triangle.plot(2, ax=ax, alpha=0.3, color="Black") +figs.append(fig) +axs.append(ax) + + +x_start = -0.15 +x_end = 1.15 +y_start = -0.1 +y_end = 0.95 + +x_diff = x_end - x_start +y_diff = y_end - y_start + +relative_size = x_diff / y_diff + +figure_size_y = 5 + +for _ax, _fig in zip(axs , figs): + _ax.set_xlim(x_start, x_end) + _ax.set_ylim(y_start, y_end) + _fig.set_size_inches(figure_size_y * relative_size,figure_size_y) + +plt.show() + +figs[0].savefig("sl_trust_discount_trust.png", **export_args) +figs[1].savefig("sl_trust_discount_opinion.png", **export_args) +figs[2].savefig("sl_trust_discount_discounted.png", **export_args) diff --git a/publications/2024_fusion/presentation/sl_trusted_fusion.py b/publications/2024_fusion/presentation/sl_trusted_fusion.py new file mode 100644 index 0000000..38aace1 --- /dev/null +++ b/publications/2024_fusion/presentation/sl_trusted_fusion.py @@ -0,0 +1,367 @@ +#!/usr/bin/python3 +import numpy as np +import subjective_logic as sl +import matplotlib +import matplotlib.pyplot as plt +from matplotlib.patches import Rectangle +import math + +plt.rcParams.update({ + 'font.size': 8, + 'text.usetex': True, + 'text.latex.preamble': " ".join([ + r'\usepackage{amsmath}', + r'\usepackage{amssymb}', + r'\usepackage{amsfonts}', + ]) +}) +plt.rc('text', usetex=True) +plt.rc('font', family='serif') + + +default_point_draw_args = { + "color" : 'tab:gray', + "zorder": 500, + "s": 80, +} +default_text_draw_args = { + "ha": 'left', + "va": 'top', + "fontsize": 18, + "offset": [0.01, 0.00], +} +default_arrow_draw_args = { + "length_includes_head": True, + "head_width": 0.02, + "overhang": 0.2, + "color": 'tab:gray', + "length_cap": 0.92, +} +export_args = { + "format": 'png', + "dpi": 500, + "transparent": True, + "bbox_inches": 'tight', + "pad_inches": 0, +} + +# general drawing settings (what exactly should be drawn) +SHOW_ACC = False +# SHOW_AVG = not SHOW_ACC +SHOW_AVG = False +SHOW_NO_TR = False +SHOW_NO_TR_FUSION = False +SHOW_TR_FUSION = False +SHOW_DISCOUNTED = False +SHOW_JOSANG = False + +# config settings, sets what should be drawn for a certain slide state +INIT_PICS = False +TRUSTED_FUSION = False +TRUST_REVISION = False +TRUST_REVISION_COMPARISON = True + +SHOW_NO_TR = True +SHOW_NO_TR_FUSION = True + +if TRUSTED_FUSION or TRUST_REVISION or TRUST_REVISION_COMPARISON: + SHOW_DISCOUNTED = True + SHOW_TR_FUSION = True + +if TRUST_REVISION or TRUST_REVISION_COMPARISON: + SHOW_JOSANG = True + +if TRUST_REVISION_COMPARISON: + SHOW_AVG = True + + + + +###################################################### +# create opinions +###################################################### +meas_s1 = sl.Opinion(0.1, 0.75) +trust_s1 = sl.Opinion2d([0.3, 0.3], [1.0, 0.0]) +t_s1 = sl.TrustedOpinion(trust_s1, meas_s1) + +meas_s2 = sl.Opinion(0.0, 0.7) +trust_s2 = sl.Opinion2d([0.3, 0.2], [1.0, 0.0]) +t_s2 = sl.TrustedOpinion(trust_s2, meas_s2) + +meas_s3 = sl.Opinion(0.7, 0.05) +trust_s3 = sl.Opinion2d([0.7, 0.0], [1.0, 0.0]) +t_s3 = sl.TrustedOpinion(trust_s3, meas_s3) + +t_vec = [t_s1, t_s2, t_s3] +opinions = sl.TrustedOpinion2d.extractOpinions(t_vec) +discounted_opinions = sl.TrustedOpinion2d.extractDiscountedOpinions(t_vec) + + +###################################################### +# calc normal and trusted fusion +###################################################### +normal_fusion = sl.Fusion.fuse_opinions(sl.Fusion.FusionType.CUMULATIVE, opinions) +trusted_fusion = sl.TrustedFusion.fuse_opinions(sl.Fusion.FusionType.CUMULATIVE, t_vec) + +###################################################### +# calc trusted fusion using trust revision +###################################################### +weighted_types_cs_acc = [ + (sl.TrustRevision.TrustRevisionType.CONFLICT_SHARES, sl.Conflict.ConflictType.ACCUMULATE, 1.), +] +weighted_types_cs_avg = [ + (sl.TrustRevision.TrustRevisionType.CONFLICT_SHARES, sl.Conflict.ConflictType.AVERAGE, 2.), +] +weighted_types_bc = [ + (sl.TrustRevision.TrustRevisionType.REFERENCE_FUSION, sl.Conflict.ConflictType.BELIEF_CUMULATIVE, 2.), +] + +fusion_cs_acc, t_vec_updated_cs_acc = sl.TrustedFusion.fuse_opinions_(sl.Fusion.FusionType.CUMULATIVE, weighted_types_cs_acc, t_vec) +fusion_cs_avg, t_vec_updated_cs_avg = sl.TrustedFusion.fuse_opinions_(sl.Fusion.FusionType.CUMULATIVE, weighted_types_cs_avg, t_vec) +fusion_bc, t_vec_updated_bc = sl.TrustedFusion.fuse_opinions_(sl.Fusion.FusionType.CUMULATIVE, weighted_types_bc, t_vec) + +discounted_opinions_cs_acc = sl.TrustedOpinion2d.extractDiscountedOpinions(t_vec_updated_cs_acc) +discounted_opinions_cs_avg = sl.TrustedOpinion2d.extractDiscountedOpinions(t_vec_updated_cs_avg) +discounted_opinions_bc = sl.TrustedOpinion2d.extractDiscountedOpinions(t_vec_updated_bc) + +###################################################### +# illustrate results +###################################################### +oranges = plt.get_cmap('Oranges') +greens = plt.get_cmap('Greens') + +op_str = r"$\omega_{{{l}}}^{{{u}}}$" +op_tr_str = r"$\check{{\omega}}_{{{l}}}^{{{u}}}$" + +cs_shift = 2 +bc_shift = 0 + +###################################################### +# source opinions + +fig, ax = sl.create_triangle_plot(hypo_texts=('disbelief\n(sunny)', 'belief\n(rainy)')) +point_draw_args = default_point_draw_args.copy() +text_draw_args = default_text_draw_args.copy() +if SHOW_NO_TR: + for idx, opinion in enumerate(opinions): + sl.draw_point(opinion, **point_draw_args) + # op_str=r"$\omega_X^{S_{{idx}}}$" + if idx == 2 : + text_draw_args["ha"] = "right" + text_draw_args["va"] = "top" + text_draw_args["offset"] = [0.0, -0.02] + if SHOW_DISCOUNTED: + text = op_str.format(l="X", u="S_" + str(idx)) + " = " + op_str.format(l="X", u="[A;S_" + str(idx) + "]") + sl.draw_text_at_point(opinion, text=text, **text_draw_args) + continue + sl.draw_text_at_point(opinion, text=op_str.format(l="X", u="S_" + str(idx)), **text_draw_args) + +if SHOW_DISCOUNTED: + point_draw_args = default_point_draw_args.copy() + point_draw_args["color"] = 'tab:blue' + text_draw_args = default_text_draw_args.copy() + for idx, opinion in enumerate(discounted_opinions): + sl.draw_point(opinion, **point_draw_args) + if idx == 2 and SHOW_NO_TR: + continue + if idx == 1: + text_draw_args["ha"] = "right" + text_draw_args["va"] = "bottom" + sl.draw_text_at_point(opinion, text=op_str.format(l="X", u="[A;S_" + str(idx) + "]"), **text_draw_args) + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:green' +text_draw_args = default_text_draw_args.copy() +text_draw_args["ha"] = "right" +text_draw_args["va"] = "bottom" +text_draw_args["offset"] = [-0.01, 0.] +if SHOW_ACC: + for idx, (opinion, opinion_up) in enumerate(zip(discounted_opinions, discounted_opinions_cs_acc)): + if opinion == opinion_up: + continue + sl.draw_point(opinion_up, **point_draw_args) + sl.draw_text_at_point(opinion_up, text=op_tr_str.format(l="X", u="[A;S_" + str(idx) + "]"), **text_draw_args) + +if SHOW_AVG: + for idx, (opinion, opinion_up) in enumerate(zip(discounted_opinions, discounted_opinions_cs_avg)): + if opinion == opinion_up: + continue + sl.draw_point(opinion_up, **point_draw_args) + sl.draw_text_at_point(opinion_up, text=op_tr_str.format(l="X", u="[A;S_" + str(idx) + "]"), **text_draw_args) + +if SHOW_AVG or SHOW_ACC: + sl.draw_arrow(discounted_opinions[cs_shift], discounted_opinions_cs_avg[cs_shift], **default_arrow_draw_args) + +if SHOW_JOSANG: + point_draw_args = default_point_draw_args.copy() + point_draw_args["color"] = 'tab:orange' + text_draw_args = default_text_draw_args.copy() + text_draw_args["va"] = "bottom" + for idx, (opinion, opinion_up) in enumerate(zip(discounted_opinions, discounted_opinions_bc)): + if opinion == opinion_up: + continue + sl.draw_point(opinion_up, **point_draw_args) + sl.draw_text_at_point(opinion_up, text=op_tr_str.format(l="X", u="[A;S_" + str(idx) + "]"), **text_draw_args) + + sl.draw_arrow(discounted_opinions[bc_shift], discounted_opinions_bc[bc_shift], **default_arrow_draw_args) + + +###################################################### +# trust opinions + +sl.reset_plot() +fig2, ax2 = sl.create_triangle_plot(hypo_texts=('distrust', 'trust')) + +if SHOW_TR_FUSION: + point_draw_args = default_point_draw_args.copy() + point_draw_args["color"] = 'tab:blue' + text_draw_args = default_text_draw_args.copy() + text_draw_args["ha"] = "right" + text_draw_args["va"] = "bottom" + text_draw_args["offset"] = [-0.01, 0.02] + for idx, t_op in enumerate(t_vec): + trust = t_op.trust() + if idx == 2: + text_draw_args["ha"] = "left" + text_draw_args["va"] = "bottom" + text_draw_args["offset"] = [0.01, 0.02] + sl.draw_point(trust, **point_draw_args) + sl.draw_text_at_point(trust, text=op_str.format(u="A", l="S_" + str(idx)), **text_draw_args) + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:green' +text_draw_args = default_text_draw_args.copy() +text_draw_args["offset"] = [0.01, -0.01] +if SHOW_ACC: + for idx, (t_op, t_op_up) in enumerate(zip(t_vec, t_vec_updated_cs_acc)): + if t_op.trust() == t_op_up.trust(): + continue + trust = t_op_up.trust() + sl.draw_point(trust, **point_draw_args) + sl.draw_text_at_point(trust, text=op_tr_str.format(u="A", l="S_" + str(idx)), **text_draw_args) + +if SHOW_AVG: + for idx, (t_op, t_op_up) in enumerate(zip(t_vec, t_vec_updated_cs_avg)): + if t_op.trust() == t_op_up.trust(): + continue + trust = t_op_up.trust() + sl.draw_point(trust, **point_draw_args) + sl.draw_text_at_point(trust, text=op_tr_str.format(u="A", l="S_" + str(idx)), **text_draw_args) + +if SHOW_AVG or SHOW_ACC: + sl.draw_arrow(t_vec[cs_shift].trust(), t_vec_updated_cs_avg[cs_shift].trust(), **default_arrow_draw_args) + +if SHOW_JOSANG: + point_draw_args = default_point_draw_args.copy() + point_draw_args["color"] = 'tab:orange' + text_draw_args = default_text_draw_args.copy() + text_draw_args["offset"] = [0.01, -0.01] + for idx, (t_op, t_op_up) in enumerate(zip(t_vec, t_vec_updated_bc)): + if t_op.trust() == t_op_up.trust(): + continue + trust = t_op_up.trust() + sl.draw_point(trust, **point_draw_args) + sl.draw_text_at_point(trust, text=op_tr_str.format(u="A", l="S_" + str(idx)), **text_draw_args) + + sl.draw_arrow(t_vec[bc_shift].trust(), t_vec_updated_bc[bc_shift].trust(), **default_arrow_draw_args) + +###################################################### +# fusion opinions + + +sl.reset_plot() +fig3, ax3 = sl.create_triangle_plot(hypo_texts=('disbelief\n(sunny)', 'belief\n(rainy)')) + +point_draw_args = default_point_draw_args.copy() +text_draw_args = default_text_draw_args.copy() +if SHOW_NO_TR_FUSION: + point_draw_args["color"] = 'tab:gray' + sl.draw_point(normal_fusion, **point_draw_args) + text_draw_args["offset"] = [-0.015, 0.03] + text_draw_args["ha"] = 'right' + sl.draw_text_at_point(normal_fusion, text=op_str.format(l="X", u="\diamond (\mathbb{S})"), **text_draw_args) + +if SHOW_TR_FUSION: + point_draw_args["color"] = 'tab:blue' + sl.draw_point(trusted_fusion, **point_draw_args) + text_draw_args["offset"] = [0., -0.02] + text_draw_args["ha"] = 'center' + text_draw_args["va"] = 'top' + sl.draw_text_at_point(trusted_fusion, text=op_str.format(l="X", u="\diamond ([A;\mathbb{S}])"), **text_draw_args) + + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:green' +text_draw_args = default_text_draw_args.copy() +text_draw_args["offset"] = [0., 0.01] +text_draw_args["ha"] = 'center' +text_draw_args["va"] = 'bottom' + +arrow_draw_args = default_arrow_draw_args.copy() + +if SHOW_ACC: + sl.draw_point(fusion_cs_acc, **point_draw_args) + sl.draw_text_at_point(fusion_cs_acc, text=op_tr_str.format(l="X", u="\diamond ([A;\mathbb{S}])"), **text_draw_args) + +if SHOW_AVG: + sl.draw_point(fusion_cs_avg, **point_draw_args) + sl.draw_text_at_point(fusion_cs_avg, text=op_tr_str.format(l="X", u="\diamond ([A;\mathbb{S}])"), **text_draw_args) + +if SHOW_AVG or SHOW_ACC: + arrow_draw_args["length_cap"] = 0.9 + sl.draw_arrow(trusted_fusion, fusion_cs_avg, **arrow_draw_args) + +if SHOW_JOSANG: + point_draw_args = default_point_draw_args.copy() + point_draw_args["color"] = 'tab:orange' + text_draw_args = default_text_draw_args.copy() + text_draw_args["offset"] = [0., 0.01] + text_draw_args["ha"] = 'left' + text_draw_args["va"] = 'bottom' + sl.draw_point(fusion_bc, **point_draw_args) + sl.draw_text_at_point(fusion_bc, text=op_tr_str.format(l="X", u="\diamond ([A;\mathbb{S}])"), **text_draw_args) + + arrow_draw_args["length_cap"] = 0.8 + sl.draw_arrow(trusted_fusion, fusion_bc, **arrow_draw_args) + + +x_start = -0.05 +x_end = 1.05 +y_start = -0.175 +y_end = 0.95 + +x_diff = x_end - x_start +y_diff = y_end - y_start + +relative_size = x_diff / y_diff + +figure_size_y = 5 + +for _ax, _fig in zip([ax,ax2,ax3],[fig,fig2, fig3]): + _ax.set_xlim(x_start, x_end) + _ax.set_ylim(y_start, y_end) + _fig.set_size_inches(figure_size_y * relative_size,figure_size_y) + + +plt.show() + +if INIT_PICS: + fig.savefig("sl_trusted_fusion_init_opinions.png", **export_args) + fig2.savefig("sl_trusted_fusion_init_trust.png", **export_args) + fig3.savefig("sl_trusted_fusion_init_fusion.png", **export_args) +if TRUSTED_FUSION: + fig.savefig("sl_trusted_fusion_tf_opinions.png", **export_args) + fig2.savefig("sl_trusted_fusion_tf_trust.png", **export_args) + fig3.savefig("sl_trusted_fusion_tf_fusion.png", **export_args) +if TRUST_REVISION: + fig.savefig("sl_trusted_fusion_tr_opinions.png", **export_args) + fig2.savefig("sl_trusted_fusion_tr_trust.png", **export_args) + fig3.savefig("sl_trusted_fusion_tr_fusion.png", **export_args) +if TRUST_REVISION_COMPARISON: + fig.savefig("sl_trusted_fusion_tr_opinions_comp.png", **export_args) + fig2.savefig("sl_trusted_fusion_tr_trust_comp.png", **export_args) + fig3.savefig("sl_trusted_fusion_tr_fusion_comp.png", **export_args) + + + + diff --git a/publications/2025_fas/2025_unidasFAST_wodtko_griebel_unified.bib b/publications/2025_fas/2025_unidasFAST_wodtko_griebel_unified.bib new file mode 100644 index 0000000..140ed9a --- /dev/null +++ b/publications/2025_fas/2025_unidasFAST_wodtko_griebel_unified.bib @@ -0,0 +1,6 @@ +@InProceedings{wodtko_griebel2025unified, + author = {Thomas Wodtko and Thomas Griebel and Michael Buchholz and Klaus Dietmayer}, + title = {A Unified Self-Assessment Framework for Autonomous Driving Stacks Using Subjective Logic}, + booktitle = {16. Uni-DAS e.V. Workshop Fahrerassistenz und automatisiertes Fahren}, + year = {2025}, +} diff --git a/publications/2025_fas/README.md b/publications/2025_fas/README.md new file mode 100644 index 0000000..a7cfb15 --- /dev/null +++ b/publications/2025_fas/README.md @@ -0,0 +1,31 @@ +# A Unified Self-Assessment Framework for Autonomous Driving Stacks Using Subjective Logic + +This directory contains scripts used to generate figures and reproduce the evaluation results presented in the paper with the title above. +Generally, evaluations can be run as described in the main [README](../README.md). + +# Experiment Description + +To reproduce the evaluation results for the Lucid System Architecture, run: + +```bash + evaluate_lucid_system_architectur.py +``` + +This script evaluates both self-assessment formulations introduced in the paper: +- Overall State of Health +- Safety-Critical Check + +It outputs the performance and reliability metrics for each formulation, as discussed in the publication. + + +# Citation +Please use the following citation for reference to the publication in this directory (`2025_unidasFAS_wodtko_griebel_unified.bib`): +```bibtex +@InProceedings{wodtko_griebel2025unified, + author = {Thomas Wodtko and Thomas Griebel and Michael Buchholz and Klaus Dietmayer}, + title = {A Unified Self-Assessment Framework for Autonomous Driving Stacks Using Subjective Logic}, + booktitle = {16. Uni-DAS e.V. Workshop Fahrerassistenz und automatisiertes Fahren}, + year = {2025}, +} +``` + diff --git a/publications/2025_fas/__init__.py b/publications/2025_fas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/publications/2025_fas/__pycache__/system_state.cpython-312.pyc b/publications/2025_fas/__pycache__/system_state.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..285f032f7d1ae4735670a3ead60bf84eddb39d7e GIT binary patch literal 5828 zcmc&2OKcm*b(dUHlqi~_J|wNLq+Hemx62JnIgO4qGs1~bYnMDgIXp6!(H8M~%r@l8NS6q6j zG(e%w;qA=4H*aR%f9DS#PXhz(r>h&+{|&vr;-K6drOw`up|in=jL4>#G#g{nwwR5@ zu`Oj!b1?_)+f&YTU965}Y|KSQ=naC9&WiaI_8SG^C;51)glU?d zicC#UToES26Va*jSHikCIy*5N7G@@eOW}ztvzHz)d$|4A>Cn5`v=q9T6>~STp)^T{ zmPmF{O625_9M6dJ+4m(m6c_XPR9Yxagk+Kk$@zJtO1>uvsqFP+BD9pBPbCxa9Nbq9 z4ZS|}hOiI^9?No0f|a>=PCB);OkA)h1$EGoJpk@7g+OqNYc2Q#Kbs-GGTtPql0KNd zC6L6vq^^Q)V|Iy?9HMO$7D~>=Bk-wephYK8*pb4qo2@kXjdPV(<$WM(!R zp4B>a>uUo!TtRKlU`PwnT$yAL16-Kp7H-~CYbM;9p> z;HPt+yi+zn=R#hF+;A8Ypn|WE{!%KQ$$$guwxpOSWpOxUF-+OvyZ0Qx2D8G7?2VIP zIVV<_MZ{ONMWn52K*?22!LXcNqR7)`@N~XNS%W7!a8;||$t=?P$4pRzQ=^#*NBf+3 zdxX@(3_*T;9qECO?n*-tO)jOD$#F#WApof`q#wbn0HEICWGS^k24NthM2{UK7y$L= z>r(D5BjVA7ajUyBiob}*J>@unRi@zftouGZQ)mfnjD0kwweU&{f4^7jA5r>8)Rxh$ z#?gYuyFU2g+yN9jwHMPslrZuUY>CApHGm^<2`~UlwZG5i45P79HdXHp7FT0cQ`JPa zEbc{=bXBXd##UZa{@!L+*hP2MVi>~u0~CDQwqpAhyV}dUmA_G@;^r2m6}|aljXUh$ z8{>aftFh*PdGG&zSBrZuGdx$}8qd&3_8NX^=Mn=naW%*DWQukTO3zCy553xgzbL#l_)!;kpjzY(= zdt-OU?zd|Eh{BJk9i!{6LZC+r^ech>`=_;m^UA>ar-SN1SPe{WHBJ7_*QWVSZ2L}B z^B+-yBbyhr(Wo*S-I~3sj$TuPa|iH0sRT~quICi~+*6;*zo`Z;Y&Bijtz(+|Ui4QK z74_F&K!^Hkr8mPIwsM_WBOo3Su4}BY2;2J}LTg2yBV({L8Afml!Rr8cmTFwaGnV3G z1H`|X_aqWerEdAF;$mqYdB`CEpiXa#=Iv9weX94Q<{ebLgO7Tj3_KoCz2lnqqT;>y zJMNF3-+NT=wJq1RVwu%It!oKu-~>vl-0;>^a`OPKs`A@*@JS=im6yR!0(%Pv5Pd9g z6k`_*9RLRl=~_ia;AcU(Vk`e-uh<1+Kj3PS4r_3=pfqtcb_K3hNz@{O!&xmTO5 z4mIL&jk8)rZWC9-?%=Ez(YFRy3rZ7LjcmOYQ@ek#=G=C56#kE^cY0pddIpuA zL9J&@=^5LUwDGr<@wd04KU8~ugs1D=33lFd-*uM~q~l5BY9dFy3 zhsN6o-%e2R1bvy{dlpr&ZkKaJ_X-01cx2(#1Ruo&q4@fR{#h&}P+_#>CLP#2j-VHT zMajLNRdhQjl9nFDoO2ddz?)+#8_!XtS3g-$t3!b2q;V>tJRW7)LiM&to z^})DT<@&x1b}6UMZ3ib-FRfiyxrr}2Us8rHYGd-z zH~Ee9-^9<~p}ac2?LWIZxpqZO=Iy8(cg6oPp1-i?O-?W&0lM-(>*o* 0: + start_state = states[idx_state-1] + interpol_fac = 0.5 + idx_current_state / transition_steps / 2 + elif idx_current_state >= steps_per_state - transition_steps and idx_state < num_states - 1: + interpol_fac = (idx_current_state - transition_steps - constant_steps) / transition_steps / 2 + end_state = states[idx_state+1] + + interpol_facs.append(interpol_fac) + current_state = start_state.interpolate(end_state, interpol_fac) + + current_state.mode = Mode.STATE_OF_HEALTH + current_overall = current_state.getOverall() + overall.append(current_overall) + overall_projections.append(current_overall.getBinomialProjection()) + + current_state.mode = Mode.MINIMAL_FEASIBLE + current_crit = current_state.getOverall() + safety_critical.append(current_crit) + safety_critical_projections.append(current_crit.getBinomialProjection()) + + +# plt.plot(interpol_facs) +plt.figure("progressions") +plt.plot(overall_projections) +plt.plot(safety_critical_projections) + +overall_projections_export = np.array([[n,val] for n,val in enumerate(overall_projections)]) +critical_projection_export = np.array([[n,val] for n,val in enumerate(safety_critical_projections)]) +np.savetxt('overall_projections.csv', overall_projections_export, delimiter=',', fmt='%10.5f') +np.savetxt('critical_projections.csv', critical_projection_export, delimiter=',', fmt='%10.5f') + + + +# plt.figure("opinions") +plt.rcParams.update({ + 'font.size': 8, + 'text.usetex': True, + 'text.latex.preamble': " ".join([ + r'\usepackage{amsmath}', + r'\usepackage{amssymb}', + r'\usepackage{amsfonts}', + r'\usepackage{bm}', + ]) +}) +plt.rc('text', usetex=True) +plt.rc('font', family='serif') + +# draw_flags_opinion = { +# 'draw_hypo_texts': False, +# 'draw_axis': False, +# 'draw_axis_label': False, +# 'draw_opinion': True, +# 'draw_opinion_label': False, +# 'draw_prior': False, +# 'draw_prior_label': False, +# 'draw_projection': False, +# 'draw_projection_label': False, +# 'belief_label_position': 0.5, +# 'disbelief_label_position': 0.6, +# 'uncertainty_label_position': 0.7, +# } + +point_draw_args = { + "color" : 'tab:gray', + "zorder": 500, + "s": 80, +} + +default_text_draw_args = { + "ha": 'left', + "va": 'top', + "fontsize": 20, + "offset": [0.01, 0.00], +} + +export_args = { + "format": 'pdf', + "dpi": 500, + "transparent": True, + "bbox_inches": 'tight', + "pad_inches": 0, +} + +fig, ax = sl.create_triangle_plot(hypo_texts=('disbelief\n(unhealty)', 'belief\n(healthy)')) + +state_offset = steps_per_state // 2 +# for idx in range(num_states): +for idx in range(5): + cur_step = idx * steps_per_state + state_offset + cur_overall = overall[idx * steps_per_state + state_offset] + + point_draw_args['color'] = '#0072BD' + sl.draw_point(cur_overall, **point_draw_args) + + draw_options = default_text_draw_args.copy() + draw_options['offset'] = [0.05,0.01] + draw_options['ha'] = 'left' + draw_options['va'] = 'center' + + if idx == 5: + draw_options['offset'] = [-0.05, 0.01] + draw_options['ha'] = 'right' + draw_options['va'] = 'bottom' + + sl.draw_text_at_point(cur_overall, f'$^{{{idx+1}}}\\omega_Z^A$', **draw_options) + +sl.reset_plot() +fig2, ax2 = sl.create_triangle_plot(hypo_texts=('disbelief\n(unsafe)', 'belief\n(safe)')) + +state_offset = steps_per_state // 2 +for idx in range(num_states): +# for idx in range(5): + cur_step = idx * steps_per_state + state_offset + cur_crit = safety_critical[idx * steps_per_state + state_offset] + + point_draw_args['color'] = '#D95319' + sl.draw_point(cur_crit, **point_draw_args) + + draw_options = default_text_draw_args.copy() + draw_options['offset'] = [0.025,0.01] + draw_options['ha'] = 'left' + draw_options['va'] = 'center' + + opinion_text = f'$^{{{idx+1}}}\\omega_Z^A$' + # skip all double opinions when drawing names + if idx in [1,2,5]: + continue + if idx == 0: + draw_options['offset'] = [-0.03, 0.01] + draw_options['ha'] = 'right' + # opinion_text = f'$^1\\omega_Z^A$' + # opinion_text = f'$^{{\\{{1,2\\}}}}\\omega_Z^A$' + opinion_text = f'$^{{\\{{1,2,3\\}}}}\\omega_Z^A$' + if idx == 3: + # opinion_text = f'$^4\\omega_Z^A$' + opinion_text = f'$^{{\\{{4,6\\}}}}\\omega_Z^A$' + + sl.draw_text_at_point(cur_crit, opinion_text, **draw_options) + +x_start = -0.2 +x_end = 1.2 +y_start = -0.05 +y_end = 0.95 + +x_diff = x_end - x_start +y_diff = y_end - y_start + +relative_size = x_diff / y_diff + +figure_size_y = 5 + +for _ax, _fig in zip([ax,ax2],[fig,fig2]): + _ax.set_xlim(x_start, x_end) + _ax.set_ylim(y_start, y_end) + _fig.set_size_inches(figure_size_y * relative_size,figure_size_y) + +plt.show() + +fig.savefig("06-overall_bary_triangle.pdf", **export_args) +fig2.savefig("06-critical_bary_triangle.pdf", **export_args) + + + + + + + + diff --git a/publications/2025_fas/safety_formular.py b/publications/2025_fas/safety_formular.py new file mode 100644 index 0000000..31c3968 --- /dev/null +++ b/publications/2025_fas/safety_formular.py @@ -0,0 +1,81 @@ +#!/usr/bin/python3 +from subjective_logic import Opinion2d as Opinion + +from system_state import SystemState, Mode + +state = SystemState() + +state.sensor_1 = Opinion([0.9,0.0],[0.9,0.1]) +state.processing_1 = Opinion([0.9,0.0],[0.9,0.1]) +state.concurrent_sa = Opinion([0.9,0.0],[0.9,0.1]) + +state.sensor_2 = Opinion([0.9,0.0],[0.9,0.1]) +state.processing_2 = Opinion([0.9,0.0],[0.9,0.1]) + +state.sensor_3 = Opinion([0.9,0.0],[0.9,0.1]) +state.processing_3 = Opinion([0.9,0.0],[0.9,0.1]) + + +state.fusion = Opinion([0.9,0.0],[0.9,0.1]) +state.planning = Opinion([0.9,0.0],[0.9,0.1]) + + +state.fusion = Opinion([0.0,0.9],[0.9,0.1]) +state.mode = Mode.MINIMAL_FEASIBLE +print('minimal:') +print('overall proj: ', state.getOverall().getBinomialProjection()) + +state.mode = Mode.STATE_OF_HEALTH +print('soh:') +print('overall proj: ', state.getOverall().getBinomialProjection()) +print('\n\n') +state.fusion = Opinion([0.9,0.0],[0.9,0.1]) + +# print('overall: ', state.getOverall()) +state.mode = Mode.MINIMAL_FEASIBLE +print('minimal:') +print('overall proj: ', state.getOverall().getBinomialProjection()) + +state.mode = Mode.STATE_OF_HEALTH +print('soh:') +print('overall proj: ', state.getOverall().getBinomialProjection()) +print('\n\n') + + +state.sensor_1 = Opinion([0.0,0.9],[0.9,0.1]) +state.concurrent_sa = Opinion([0.0,0.9],[0.9,0.1]) + +print('one sensor bad') +state.mode = Mode.MINIMAL_FEASIBLE +print('minimal:') +print('overall proj: ', state.getOverall().getBinomialProjection()) + +state.mode = Mode.STATE_OF_HEALTH +print('soh:') +print('overall proj: ', state.getOverall().getBinomialProjection()) +print('\n\n') + + +state.sensor_2 = Opinion([0.0,0.9],[0.9,0.1]) + +print('two sensors bad') +state.mode = Mode.MINIMAL_FEASIBLE +print('minimal:') +print('overall proj: ', state.getOverall().getBinomialProjection()) + +state.mode = Mode.STATE_OF_HEALTH +print('soh:') +print('overall proj: ', state.getOverall().getBinomialProjection()) +print('\n\n') + +state.sensor_3 = Opinion([0.0,0.9],[0.9,0.1]) + +print('three sensors bad') +state.mode = Mode.MINIMAL_FEASIBLE +print('minimal:') +print('overall proj: ', state.getOverall().getBinomialProjection()) + +state.mode = Mode.STATE_OF_HEALTH +print('soh:') +print('overall proj: ', state.getOverall().getBinomialProjection()) +print('\n\n') diff --git a/publications/2025_fas/system_state.py b/publications/2025_fas/system_state.py new file mode 100644 index 0000000..6519ca9 --- /dev/null +++ b/publications/2025_fas/system_state.py @@ -0,0 +1,70 @@ + +from dataclasses import dataclass, field + +from subjective_logic import Opinion2d as Opinion +from enum import Enum + +class Mode(Enum): + MINIMAL_FEASIBLE = 1 + STATE_OF_HEALTH = 2 + +@dataclass +class SystemState: + name: str = "NOTSET" + + mode: Mode = Mode.MINIMAL_FEASIBLE + + sensor_1: Opinion = field(default_factory=lambda : Opinion()) + processing_1: Opinion = field(default_factory=lambda : Opinion()) + + concurrent_sa: Opinion = field(default_factory=lambda : Opinion()) + + sensor_2: Opinion = field(default_factory=lambda : Opinion()) + processing_2: Opinion = field(default_factory=lambda : Opinion()) + + sensor_3: Opinion = field(default_factory=lambda : Opinion()) + processing_3: Opinion = field(default_factory=lambda : Opinion()) + + fusion: Opinion = field(default_factory=lambda : Opinion()) + planning: Opinion = field(default_factory=lambda : Opinion()) + + def getProcessingState(self, id: int): + if id == 1: + return self.sensor_1.multiply(self.processing_1) + elif id == 2: + return self.sensor_2.multiply(self.processing_2) + else: + return self.sensor_3.multiply(self.processing_3) + + def getPerceptionState(self): + match self.mode: + case Mode.MINIMAL_FEASIBLE: + inter = self.getProcessingState(1).comultiply(self.getProcessingState(2)) + sa_fused = inter.wb_fuse(self.concurrent_sa) + return sa_fused.comultiply(self.getProcessingState(3)) + + case Mode.STATE_OF_HEALTH: + inter = self.getProcessingState(1).cum_fuse(self.getProcessingState(2)) + sa_fused = inter.wb_fuse(self.concurrent_sa) + return sa_fused.cum_fuse(self.getProcessingState(3)) + + def getOverall(self): + return self.getPerceptionState().multiply(self.fusion).multiply(self.planning) + + def interpolate(self, other, interp_fac: float): + interp = SystemState() + interp.name = self.name + "_interpolate_" + other.name + + interp.sensor_1 = self.sensor_1.interpolate(other.sensor_1, interp_fac) + interp.processing_1 = self.processing_1.interpolate(other.processing_1, interp_fac) + interp.sensor_2 = self.sensor_2.interpolate(other.sensor_2, interp_fac) + interp.processing_2 = self.processing_2.interpolate(other.processing_2, interp_fac) + interp.sensor_3 = self.sensor_3.interpolate(other.sensor_3, interp_fac) + interp.processing_3 = self.processing_3.interpolate(other.processing_3, interp_fac) + + interp.concurrent_sa = self.concurrent_sa.interpolate(other.concurrent_sa, interp_fac) + + interp.fusion = self.fusion.interpolate(other.fusion, interp_fac) + interp.planning = self.planning.interpolate(other.planning, interp_fac) + + return interp diff --git a/publications/2025_mfi_wodtko/2025_mfi_wodtko_balancing.bib b/publications/2025_mfi_wodtko/2025_mfi_wodtko_balancing.bib index fa46438..b3a0c88 100644 --- a/publications/2025_mfi_wodtko/2025_mfi_wodtko_balancing.bib +++ b/publications/2025_mfi_wodtko/2025_mfi_wodtko_balancing.bib @@ -1,7 +1,12 @@ -@inproceedings{wodtko2025balancing, - author = {Thomas Wodtko and Michael Buchholz}, - booktitle = {IEEE International Conference on Multisensor Fusion and Integration for Intelligent Systems (MFI), submitted for publication}, - title = {Balancing Conflict and Harmony - Persistent Trust Revision in Time-Dependent Subjective Networks}, - year = {2025} +@INPROCEEDINGS{wodtko2025balancing, + author={Wodtko, Thomas and Buchholz, Michael}, + booktitle={2025 IEEE International Conference on Multisensor Fusion and Integration for Intelligent Systems (MFI)}, + title={Balancing Conflict and Harmony - Persistent Trust Revision in Time-Dependent Subjective Networks}, + year={2025}, + volume={}, + number={}, + pages={1-8}, + keywords={Monte Carlo methods;Uncertainty;Estimation;Sensor systems and applications;Random variables;Reliability;Logic;Intelligent systems}, + doi={10.1109/MFI67357.2025.11259128} } diff --git a/publications/2025_mfi_wodtko/README.md b/publications/2025_mfi_wodtko/README.md index 175f05d..53d5c14 100644 --- a/publications/2025_mfi_wodtko/README.md +++ b/publications/2025_mfi_wodtko/README.md @@ -2,7 +2,7 @@ The scripts in this directory will create some of the images used in the paper with the title above. More importantly, it also reproduces the evaluation results presented in the paper. -Evaluations can be run as described in [here](../README.md), for generating the images, some additional packages are required (`cm-super`,`texlive`, `texlive-latex-extra`). +Evaluations can be run as described in [here](../README.md). ## Experiment description To obtain the results of the first experiment run the script `symmetric_tr.py` > @@ -28,3 +28,18 @@ TLDR: exp_distribution.py ``` +# Citation +Please use the following citation for reference to the publication in this directory (`2025_mfi_wodtko_balancing.bib`): +```bibtex +@INPROCEEDINGS{wodtko2025balancing, + author={Wodtko, Thomas and Buchholz, Michael}, + booktitle={2025 IEEE International Conference on Multisensor Fusion and Integration for Intelligent Systems (MFI)}, + title={Balancing Conflict and Harmony - Persistent Trust Revision in Time-Dependent Subjective Networks}, + year={2025}, + volume={}, + number={}, + pages={1-8}, + keywords={Monte Carlo methods;Uncertainty;Estimation;Sensor systems and applications;Random variables;Reliability;Logic;Intelligent systems}, + doi={10.1109/MFI67357.2025.11259128} +} +``` diff --git a/publications/2025_mfi_wodtko/exp_distribution.py b/publications/2025_mfi_wodtko/exp_distribution.py index 0a06aab..db50561 100644 --- a/publications/2025_mfi_wodtko/exp_distribution.py +++ b/publications/2025_mfi_wodtko/exp_distribution.py @@ -12,7 +12,7 @@ num_agents = 5 num_runs = 800 -num_mc_runs = 1000 +num_mc_runs = 10 opinion_dimension = 4 possible_events = list(range(opinion_dimension)) @@ -41,8 +41,8 @@ reliabilities = [0.00, 0.1, 0.7, 1.0, 1.0] weighted_types_cs_avg = [ - (sl.TrustRevision.TrustRevisionType.REFERENCE_FUSION, sl.Conflict.ConflictType.BELIEF_AVERAGE, 0.3), - (sl.TrustRevision.TrustRevisionType.HARMONY_REFERENCE_FUSION, sl.Conflict.ConflictType.BELIEF_AVERAGE, 0.3), + sl.WeightedTypes(sl.RelationType.CONFLICT, sl.TrustRevisionType.REFERENCE_FUSION, sl.ConflictType.BELIEF_AVERAGE, 0.3), + sl.WeightedTypes(sl.RelationType.HARMONY, sl.TrustRevisionType.REFERENCE_FUSION, sl.ConflictType.BELIEF_AVERAGE, 0.3), ] trusts = [[] for _ in range(num_agents)] @@ -92,14 +92,15 @@ def run_experiment(reliability, visibility): avg_uncertainty = uncertainty_sum / num_agents updated_weights = [] for entry in weighted_types_cs_avg: - updated_weights.append(( - entry[0], - entry[1], - entry[2] * avg_uncertainty, + updated_weights.append(sl.WeightedTypes( + entry.relation_type, + entry.trust_revision_type, + entry.conflict_type, + entry.weight * avg_uncertainty, # entry[2], )) - fusion_result, trusted_opinions = sl.TrustedFusion.fuse_opinions_(sl.Fusion.FusionType.CUMULATIVE, + fusion_result, trusted_opinions = sl.TrustedFusion.fuse_opinions_(sl.FusionType.CUMULATIVE, updated_weights, trusted_opinions) bar() diff --git a/publications/2025_mfi_wodtko/exp_nonconstant_parameter.py b/publications/2025_mfi_wodtko/exp_nonconstant_parameter.py index ec1d26b..fb468cb 100644 --- a/publications/2025_mfi_wodtko/exp_nonconstant_parameter.py +++ b/publications/2025_mfi_wodtko/exp_nonconstant_parameter.py @@ -35,8 +35,8 @@ reliabilities_end = [0.6, 0.1, 0.3, 0.95, 0.95] weighted_types_cs_avg = [ - (sl.TrustRevision.TrustRevisionType.REFERENCE_FUSION, sl.Conflict.ConflictType.BELIEF_AVERAGE, 0.2), - (sl.TrustRevision.TrustRevisionType.HARMONY_REFERENCE_FUSION, sl.Conflict.ConflictType.BELIEF_AVERAGE, 0.2), + sl.WeightedTypes(sl.RelationType.CONFLICT, sl.TrustRevisionType.REFERENCE_FUSION, sl.ConflictType.BELIEF_AVERAGE, 0.2), + sl.WeightedTypes(sl.RelationType.HARMONY, sl.TrustRevisionType.REFERENCE_FUSION, sl.ConflictType.BELIEF_AVERAGE, 0.2), ] trusts = [[] for _ in range(num_agents)] @@ -88,14 +88,15 @@ avg_uncertainty = uncertainty_sum / num_agents updated_weights = [] for entry in weighted_types_cs_avg: - updated_weights.append(( - entry[0], - entry[1], - entry[2] + updated_weights.append(sl.WeightedTypes( + entry.relation_type, + entry.trust_revision_type, + entry.conflict_type, + entry.weight # entry[2] * avg_uncertainty, )) - fusion_result, trusted_opinions = sl.TrustedFusion.fuse_opinions_(sl.Fusion.FusionType.CUMULATIVE, + fusion_result, trusted_opinions = sl.TrustedFusion.fuse_opinions_(sl.FusionType.CUMULATIVE, updated_weights, trusted_opinions) for t_op in trusted_opinions: t_op.trust.trust_discount_(0.999) diff --git a/publications/README.md b/publications/README.md index 776857b..749b48f 100644 --- a/publications/README.md +++ b/publications/README.md @@ -19,14 +19,23 @@ If the repository is already available and a venv with eslimpp installed was cre cd eslimpp/publications # install evaluation specific requirements - pip install -r eSLIM++/requirements.txt + pip install -r [publication directory]/requirements.txt # run desired evaluation script - python eSLIM++/classification_task.py + python [publication directory]/classification_task.py + + # for example: + # pip install -r eSLIM++/requirements.txt + # python eSLIM++/classification_task.py ``` +# Script Generating Images +Some scripts in the subsequent publication directories will create images used in the accompanied paper. +In contrast to other evaluation scripts, these may require some additional packages, such as `cm-super`, `dvipng`, `texlive`, and `texlive-latex-extra` for generating the barycentric triangle pictures. +Evaluations in general can be run as described above, in case of errors, the additional packages may be required. + # Citation -For each of the publications in this directory, citation information should be available in the respective directories. +For each of the publications in this directory, citation information is available in the respective directories. If you are using the reference implementations provided by any such publication for your research, please consider adding the respective citation to your work. If you implement your work using eSLIM++ please cite the eSLIM++ publication as described [here](../README.md) in any case. diff --git a/publications/dissertation_wodtko/README.md b/publications/dissertation_wodtko/README.md new file mode 100644 index 0000000..b5284c2 --- /dev/null +++ b/publications/dissertation_wodtko/README.md @@ -0,0 +1,98 @@ +# Self-Assessment in Autonomous Driving Perception Systems + +This directory contains scripts that reproduce the results of some experiments in my dissertation (Thomas Wodtko). +The Thesis is submitted and is currently being reviewed, thus, not yet publicly available. +Reviewers on any kind ("Gutachter"/"Prüfer") and all others are highly welcomed to view the provided scripts and run the evaluations. + +## Information with respect to the thesis +As mentioned, the script in this directory reproduce some of my results. +More specifically, they reproduce experiments from Chapter 2, dealing with subjective-logic-based reasoning in general, and Chapter 3, considering self-assessment. +Evaluations of Chapter 4 are not within this library, as they consider the grid map application. +With all scripts, parameters that are described in the thesis are set towards the beginning of the script. +They can be changed, but their validity is explicitly not verified, hence, changes to the parameters must be reasonable and senseful. + + +# Experiment Description +In the following, the scripts of this directory are linked to the location in the thesis. + +## Chapter 2 +Experiments of chapter 2 are: + +### Dirichlet +For the illustration in Figure 2.2 in Section 2.1.2, opinions are generated and converted to Dirichlet distributions. +The Dirichlet evidence parameter can be obtained using the script: +```bash + beta.py +``` + +### Trusted Fusion and Trust Revision +Considering the example for trust discount and trusted fusion from Section 2.1.7, +the example for trust revision of Section 2.1.8, +and the experiments to the proposed trust revision operators two scripts are available. +First, in +```bash + tr_josang_cs.py +``` +the trust revision approach from Jøsang is compared to the proposed conflict-shares-based trust revision (cf. Figure 2.11 in Section 2.5.1). +This script also includes the basic trusted fusion without trust revision. +Next, in +```bash + tr_cs_hscs.py +``` +the conflict-shares-based trust revision is compared to the combined harmony- and conflict-based trust revision (cf. again Figure 2.11 in Section 2.5.1). + +### Time-Dependent Trust Revision +The time series evaluation of Section 2.5.1 "Time-Dependent Trust Revision" is available with +```bash + time_dependent_weather_forecast.py +``` +Here, the progression of the projected probability is plotted with the ground truth in one plot, and a second plot shows the errors for each time step. + +### Reliability Estimation +The reliability estimation evaluation of Section 2.5.2 is split into "Observing Single Events" and "Observing Distributions". +Both the scripts below require the package `alive_progress` to be installed for python. +For the first, the evaluation is available with +```bash + exp_nonconstant_parameter.py +``` +It runs multiple Monte Carlo iterations (default 5000) and shows the progress while running. +Then the results from Figure 2.14 are illustrated. +By setting the parameter +```python3 + # agents can see all outcomes + test_uncertain = 0.0 + # agents miss 30% of outcomes + test_uncertain = 0.3 +``` +the visibility of agents is contolled.\ +For the second part "Observing Distributions", the evaluation is available with +```bash + exp_distribution.py +``` +In contrast to the thesis's evaluations, only 5 Monte Carlo iterations are calculated to reduce wait time. +By changing the parameter for trust revision, i.e., +```python3 + # last entry is the used weight + weighted_types_cs_avg = [ + # first setting (conflict weight higher than harmonty weight) + sl.WeightedTypes(sl.RelationType.CONFLICT, sl.TrustRevisionType.REFERENCE_FUSION, sl.ConflictType.BELIEF_AVERAGE,0.75), + sl.WeightedTypes(sl.RelationType.HARMONY, sl.TrustRevisionType.REFERENCE_FUSION, sl.ConflictType.BELIEF_AVERAGE, 0.25), + + # second setting (both equally weighted with 0.5) + # sl.WeightedTypes(sl.RelationType.CONFLICT, sl.TrustRevisionType.REFERENCE_FUSION, sl.ConflictType.BELIEF_AVERAGE,0.5), + # sl.WeightedTypes(sl.RelationType.HARMONY, sl.TrustRevisionType.REFERENCE_FUSION, sl.ConflictType.BELIEF_AVERAGE,0.5), + ] +``` +The two described behaviors are obtained (cf. Section 2.5.2 "Observing Distributions") + +## Chapter 3 +Experiments of chapter 3 include: + +### Unified Self-Assessment Framework +The evaluation of the self-assessment framework from Section 3.3.4 is available with +```bash + evaluate_lucid_system_architecture.py +``` +Using the values specified in Equations (3.49),(3.50), and (3.51), configured as shows in Figure 3.13 (a), +the script yields the results of Figure 3.13 (a)&(b) + diff --git a/publications/dissertation_wodtko/__pycache__/system_state.cpython-312.pyc b/publications/dissertation_wodtko/__pycache__/system_state.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..181c0bba4d199edbe328e17831198f65a6d7c88d GIT binary patch literal 4259 zcmcgvO>7&-6`ox#e?*F2Q4-0GY(;KuGukLIAxl9GL{_3U`ZH`Q0hOP!KO2g>a>y)6 zb(XYEK>=96Em)vH9i)KZlY8hvf(yuo12{l?=&?l))piw-U9^CLwkUj4LjwhJ>YL$m zBT}mXEl>}@w=?s-dGqG|4d)NRpdUeF*m(AJFG7DKgLZRUo#wmH*+epu85QN36q9FD zEJMbu>dJE|H|@JrPu`pIGKfXyuV_UQLS|60^srba6 z$7Ce3W{;dMr88<;(@ghjR#9bv(Ah)^N-;7@ zu`-i#$!v;(@us51({j12<|S()uHj5vE3Xi)>~%#_i`TN5_*!{I&1TXiIIk9$ zvzn&h5*CqQWjW*oZ`bxP(J(yu)agYf#e< zM)}fcZ-SdsE`?LvGCKte6;JL2e7YJq)B_wY!oeb?hdBIK?t7LaQhp@>sK-XV9cV8_ zeKy+Dfd(n+x6wccdV-?8HX7_ed5WH}QN9E1qi7#N<lGxYuB$k$wv&$w&0>b1VOq32Yl3*gHY({`=qEb5+Q9HVmXq!oFGp5&y zVcZ9dEuZv5?+f3Qn$EAt>FM>sL;Id@&l2Y~iHED`(cmts~UcWC^JMzr&{Q^r^+lpwyJh9Y62 z_AnvDNaXw%p2oJ(Bil|qjctjiEVF<7_QP+t&F)z(Yfvekh6W@|+8W$=SZQnETT|18 zLbh-%W5+%)x~yOGPqe&jpd3tex0_7Kx(g7>aos3F?~j6Wzsn$4?Kz6(dXCNkC%w@+ zk#=GFfZ1Sjq(F8xSmjXU>{aXrd(2)WX}Vf_RT3RK8_D#pM$h)0V@^gwyQ}@*PUgJV zSDvhbXyFh8m?i_gfQSL{2oTesDdwRtW!KaSK0_wFH&!GFQw4vG@SG!rC-c5cIZpx6 zWpbLLuA1&_p`>8br={t$RPQ?FB=4>9)IZ^7P$HcP?!W z*2R}~@nz%W)NNlQI#Q25uScKXn%ud#ebI3q!KN9+8!&X3!(*g6aCS(wgAb%S z6y;;2I&gMKwY>pS9ZK6VQXM!uq}q1ha3$gGk}!i(D-w$;{CAYZ9!y>&nEW+tmBzp! znAXNtHKcX%%M9XZ2AS?+={*JGmteK&X)V-m!%+kxQQ__31bC={I(h-!n$OU-RtwPVGba4crOTg}5%n z@4FxMKIr`C(&z)-deWv@|6QhjCWNS?eFWr3XunUWzE(S9aKb*hCt`Xi2II2^7yE2LfO}K8{8{@7KZinukHym)rHzM zgPZxB^SdWL7Z~^3%z-E9j%>a81i^>ejD}=F^qT?up{;0`R7p%MV)k`ENHNKCY9o_V z;OUJ?hPI4JPfe1fCE0q 0: + start_state = states[idx_state-1] + interpol_fac = 0.5 + idx_current_state / transition_steps / 2 + elif idx_current_state >= steps_per_state - transition_steps and idx_state < num_states - 1: + interpol_fac = (idx_current_state - transition_steps - constant_steps) / transition_steps / 2 + end_state = states[idx_state+1] + + interpol_facs.append(interpol_fac) + current_state = start_state.interpolate(end_state, interpol_fac) + + current_state.mode = Mode.STATE_OF_HEALTH + current_overall = current_state.getOverall() + state_of_health.append(current_overall) + + current_state.mode = Mode.MINIMAL_FEASIBLE + current_crit = current_state.getOverall() + safety_critical.append(current_crit) + +soh_projection = [op.getBinomialProjection() for op in state_of_health] +soh_uncerts = [op.uncertainty() for op in state_of_health] +sc_projection = [op.getBinomialProjection() for op in safety_critical] +sc_uncerts = [op.uncertainty() for op in safety_critical] + +fig, (ax1, ax2) = plt.subplots(2, 1) + +ax1.set_title("State of Health") +ax1.plot(soh_projection, label="projection") +ax1.plot(soh_uncerts, label="uncertainty") + +ax2.set_title("Safety Critical") +ax2.plot(sc_projection, label="projection") +ax2.plot(sc_uncerts, label="uncertainty") + +plt.show() + +# steps = (np.array(range(len(state_of_health))) + steps_per_state//2) / steps_per_state +# export = { +# "steps": steps, +# "soh_proj" : soh_projection, +# "soh_uncerts": soh_uncerts, +# "sc_proj": sc_projection, +# "sc_uncerts": sc_uncerts, +# } +# +# import pandas as pd +# filename = "usf_results.csv" +# df = pd.DataFrame(export) +# df.to_csv(filename, index=False) +# diff --git a/publications/dissertation_wodtko/exp_distribution.py b/publications/dissertation_wodtko/exp_distribution.py new file mode 100644 index 0000000..608baeb --- /dev/null +++ b/publications/dissertation_wodtko/exp_distribution.py @@ -0,0 +1,144 @@ +import copy + +import subjective_logic as sl +import random +import numpy as np + +import matplotlib.pyplot as plt +from alive_progress import alive_bar + +random.seed(5) +# random.seed(42) +np.random.seed(5) +# np.random.seed(42) +test_uncertain = 0.0 + +# num_agents = 5 +num_agents = 5 +num_runs = 500 +# num_runs = 1 +num_mc_runs = 5 + +opinion_dimension = 10 +possible_events = list(range(opinion_dimension)) + +gt_distribution = [1/opinion_dimension]*opinion_dimension + +num_runs_per_experiment = 4 * opinion_dimension + +colors = ['tab:orange', 'tab:blue', 'tab:green', 'tab:olive', 'tab:red', 'tab:purple', 'tab:orange', 'tab:cyan'] +draw_divider = 1 +triangle_divider = 15 + +uncertain_opinion = sl.Opinion(*[0.]*opinion_dimension) + +reliabilities = [0.00, 0.2, 0.7, 1.0, 1.0] + + +weighted_types_cs_avg = [ + sl.WeightedTypes(sl.RelationType.CONFLICT, sl.TrustRevisionType.REFERENCE_FUSION, sl.ConflictType.BELIEF_AVERAGE,0.75), + sl.WeightedTypes(sl.RelationType.HARMONY, sl.TrustRevisionType.REFERENCE_FUSION, sl.ConflictType.BELIEF_AVERAGE, 0.25), + # sl.WeightedTypes(sl.RelationType.CONFLICT, sl.TrustRevisionType.REFERENCE_FUSION, sl.ConflictType.BELIEF_AVERAGE,0.5), + # sl.WeightedTypes(sl.RelationType.HARMONY, sl.TrustRevisionType.REFERENCE_FUSION, sl.ConflictType.BELIEF_AVERAGE,0.5), +] + +trusts = [[] for _ in range(num_agents)] + +def run_experiment(reliability, visibility, agent_id): + observations = sl.DirichletDistribution([1.]*opinion_dimension) + distribution = gt_distribution + + cur_reliable = reliability >= np.random.uniform() + if not cur_reliable: + # get random n-dim vector + rand_dist = np.random.uniform(0,1,opinion_dimension) + # normalized with 1 norm -> normalized vec sums up to 1 + distribution = rand_dist / np.linalg.norm(rand_dist, ord=1) + + + for _ in range(num_runs_per_experiment): + cur_event = np.random.choice(possible_events,p=distribution) + observations.evidences[cur_event] += 1 + return observations + + +baseline_decisions=np.zeros((num_mc_runs, num_agents, num_runs)) +projections = np.zeros((num_mc_runs, num_agents, num_runs)) +with alive_bar(num_mc_runs) as bar: + for mc_run in range(num_mc_runs): + trusted_opinions = [ + sl.TrustedOpinion(sl.Opinion([0, 0], [0.5, 0.5]), uncertain_opinion) for _ in range(num_agents) + ] + for run in range(num_runs): + + uncertainty_sum = 0 + # ground truth event for this run + event = random.choice(possible_events) + other_events = copy.copy(possible_events) + other_events.remove(event) + for idx in range(num_agents): + if mc_run == 0: + trusts[idx].append(trusted_opinions[idx].trust_copy()) + + projections[mc_run, idx, run] = trusted_opinions[idx].trust.getBinomialProjection() + + uncertainty_sum += trusted_opinions[idx].trust.uncertainty() + + trusted_opinions[idx].opinion = run_experiment(reliabilities[idx], 1.0 - test_uncertain, idx).as_opinion() + + avg_uncertainty = uncertainty_sum / num_agents + # avg_uncertainty = 2*uncertainty_sum / num_agents + # if avg_uncertainty > 1.0: + # avg_uncertainty = 1.0 + updated_weights = [] + for entry in weighted_types_cs_avg: + updated_weights.append(sl.WeightedTypes( + entry.relation_type, + entry.trust_revision_type, + entry.conflict_type, + entry.weight * avg_uncertainty, + # entry.weight, + )) + + fusion_result, trusted_opinions = sl.TrustedFusion.fuse_opinions_(sl.FusionType.CUMULATIVE, + updated_weights, trusted_opinions) + # print() + bar() + + +gt_draw_window = 100 +end_first = num_runs +start_first = end_first - gt_draw_window +for idx in range(num_agents): + plt.plot([start_first, end_first], [reliabilities[idx]]*2, color='black', linewidth=1.5) + + +median_projections = np.median(projections, axis=0) +print(median_projections[:,-1]) +q25_projections = np.quantile(projections, 0.25, axis=0) +q75_projections = np.quantile(projections, 0.75, axis=0) + +plt.ylim(0,1) +for color, idx in zip(colors, range(num_agents)): + plt.plot(median_projections[idx, ::draw_divider], color=color, label=f"agent {idx}") + plt.fill_between( + range(0, num_runs // draw_divider), + q75_projections[idx, ::draw_divider], + q25_projections[idx, ::draw_divider], + color='gray', alpha=0.3) + +plt.legend() +plt.show() +# +# import pandas as pd +# +# data = {'time': range(median_projections.shape[1])} +# for idx in range(num_agents): +# data[str(idx) + '_median'] = median_projections[idx,:] +# data[str(idx) + '_upper'] = q75_projections[idx, :] +# data[str(idx) + '_lower'] = q25_projections[idx, :] +# +# df = pd.DataFrame(data) +# weight1 = weighted_types_cs_avg[0].weight +# weight2 = weighted_types_cs_avg[1].weight +# df.to_csv(f'04-exp_distribution_{num_mc_runs}_{weight1}_{weight2}.csv', index=False) diff --git a/publications/dissertation_wodtko/exp_nonconstant_parameter.py b/publications/dissertation_wodtko/exp_nonconstant_parameter.py new file mode 100644 index 0000000..7e8085c --- /dev/null +++ b/publications/dissertation_wodtko/exp_nonconstant_parameter.py @@ -0,0 +1,161 @@ +import copy + +import subjective_logic as sl +import random +import numpy as np + +import matplotlib.pyplot as plt +from alive_progress import alive_bar + +test_uncertain = 0.3 +num_agents = 5 +num_runs = 400 +num_mc_runs = 5000 + +switches = [ + num_runs // 2 +] + +colors = ['tab:orange', 'tab:olive', 'tab:green', 'tab:blue', 'tab:red', 'tab:purple', 'tab:orange', 'tab:cyan'] + +draw_divider = 1 + +opinion_dimension = 4 +default_opinions = [] +for i in range(opinion_dimension): + belief_masses = [0.0] * opinion_dimension + belief_masses[i] = 0.9 + prior = [1 / opinion_dimension] * opinion_dimension + prior[0] = prior[0] - 0.1 + prior[-1] = prior[-1] + 0.1 + default_opinions.append(sl.Opinion(belief_masses, prior)) +uncertain_opinion = sl.Opinion(*[0.] * opinion_dimension) + +reliabilities_start = [0.1, 0.95, 0.95, 0.95, 0.95] +reliabilities_end = [0.6, 0.1, 0.3, 0.95, 0.95] + +weighted_types_cs_avg = [ + sl.WeightedTypes(sl.RelationType.CONFLICT, sl.TrustRevisionType.REFERENCE_FUSION, sl.ConflictType.BELIEF_AVERAGE, 0.2), + sl.WeightedTypes(sl.RelationType.HARMONY, sl.TrustRevisionType.REFERENCE_FUSION, sl.ConflictType.BELIEF_AVERAGE, 0.2), +] + +trusts = [[] for _ in range(num_agents)] + +baseline_decisions = np.zeros((num_mc_runs, num_agents, num_runs)) +projections = np.zeros((num_mc_runs, num_agents, num_runs)) +with alive_bar(num_mc_runs) as bar: + for mc_run in range(num_mc_runs): + trusted_opinions = [ + sl.TrustedOpinion(sl.Opinion([0.0, 0.0], [0.5, 0.5]), uncertain_opinion) for _ in range(num_agents) + ] + for run in range(num_runs): + + if run < switches[0]: + reliabilities = reliabilities_start + else: + reliabilities = reliabilities_end + + uncertainty_sum = 0 + possible_events = list(range(opinion_dimension)) + # ground truth event for this run + event = random.choice(possible_events) + other_events = copy.copy(possible_events) + other_events.remove(event) + for idx in range(num_agents): + if mc_run == 0: + trusts[idx].append(trusted_opinions[idx].trust_copy()) + + projections[mc_run, idx, run] = trusted_opinions[idx].trust.getBinomialProjection() + + uncertainty_sum += trusted_opinions[idx].trust.uncertainty() + + # invisible and unreliable events are selected randomly for each agent independently + invisible_event = random.choice(possible_events) + unreliable_event = random.choice(other_events) + # important, sample to independent random numbers + reliable = reliabilities[idx] > np.random.uniform() + visible = np.random.uniform() > test_uncertain + if reliable and visible: + trusted_opinions[idx].opinion = default_opinions[event] + baseline_decisions[mc_run, idx, run] = event + elif not visible: + trusted_opinions[idx].opinion = uncertain_opinion + baseline_decisions[mc_run, idx, run] = invisible_event + else: # unreliable + trusted_opinions[idx].opinion = default_opinions[unreliable_event] + baseline_decisions[mc_run, idx, run] = unreliable_event + + avg_uncertainty = uncertainty_sum / num_agents + updated_weights = [] + for entry in weighted_types_cs_avg: + updated_weights.append(sl.WeightedTypes( + entry.relation_type, + entry.trust_revision_type, + entry.conflict_type, + entry.weight + # entry[2] * avg_uncertainty, + )) + + fusion_result, trusted_opinions = sl.TrustedFusion.fuse_opinions_(sl.FusionType.CUMULATIVE, + updated_weights, trusted_opinions) + for t_op in trusted_opinions: + t_op.trust.trust_discount_(0.999) + bar() + +gt_draw_window = 100 +end_first = switches[0] +start_first = end_first - gt_draw_window +end_second = num_runs +start_second = end_second - gt_draw_window +for idx in range(num_agents): + plt.plot([start_first, end_first], [reliabilities_start[idx]] * 2, color='black', linewidth=1.5) + plt.plot([start_second, end_second], [reliabilities_end[idx]] * 2, color='black', linewidth=1.5) + +baseline_reliabilities = np.zeros((num_mc_runs, num_agents, num_runs)) + +for mc_run in range(num_mc_runs): + current_run = baseline_decisions[mc_run, :, :].astype(int) + answers = np.zeros([num_agents, num_runs]) + distributions = [sl.DirichletDistribution2f([0, 0]) for _ in range(num_agents)] + for idx in range(num_runs): + pseudo_gt = np.argmax(np.bincount(current_run[:, idx])) + answers[:, idx] = (current_run[:, idx] == pseudo_gt).astype(int) + + for agent_idx in range(num_agents): + distributions[agent_idx].evidences[int(answers[agent_idx, idx])] += 1 + baseline_reliabilities[mc_run, agent_idx, idx] = distributions[agent_idx].mean()[1] + distributions[agent_idx].evidences *= 0.95 + +baseline_reliabilities_mc = np.median(baseline_reliabilities, axis=0) +baseline_q25_projections = np.quantile(baseline_reliabilities, 0.25, axis=0) +baseline_q75_projections = np.quantile(baseline_reliabilities, 0.75, axis=0) +for color, idx in zip(colors, range(num_agents)): + plt.plot(baseline_reliabilities_mc[idx, ::draw_divider], ':', color=color) +print('final baseline reliability estimation\n', baseline_reliabilities_mc[:, -1]) + +median_projections = np.median(projections, axis=0) +print(median_projections[:, -1]) +q25_projections = np.quantile(projections, 0.25, axis=0) +q75_projections = np.quantile(projections, 0.75, axis=0) + +plt.ylim(0, 1) +for color, idx in zip(colors, range(num_agents)): + plt.plot(median_projections[idx, ::draw_divider], color=color, label=f"agent {idx}") + +plt.legend() +plt.show() +# +# import pandas as pd +# +# data = {'time': range(median_projections.shape[1])} +# for idx in range(num_agents): +# data[str(idx) + '_median'] = median_projections[idx,:] +# data[str(idx) + '_upper'] = q75_projections[idx, :] +# data[str(idx) + '_lower'] = q25_projections[idx, :] +# +# data['base_' + str(idx) + '_median'] = baseline_reliabilities_mc[idx,:] +# data['base_' + str(idx) + '_upper'] = baseline_q75_projections[idx, :] +# data['base_' + str(idx) + '_lower'] = baseline_q25_projections[idx, :] +# +# df = pd.DataFrame(data) +# df.to_csv(f'04-exp_nonconstant_parameter_{num_mc_runs}_{test_uncertain}.csv', index=False) diff --git a/publications/dissertation_wodtko/requirements.txt b/publications/dissertation_wodtko/requirements.txt new file mode 100644 index 0000000..04967ca --- /dev/null +++ b/publications/dissertation_wodtko/requirements.txt @@ -0,0 +1 @@ +alive_progress>=3.2.0 \ No newline at end of file diff --git a/publications/dissertation_wodtko/system_state.py b/publications/dissertation_wodtko/system_state.py new file mode 100644 index 0000000..611e52a --- /dev/null +++ b/publications/dissertation_wodtko/system_state.py @@ -0,0 +1,56 @@ + +from dataclasses import dataclass, field + +from subjective_logic import Opinion2d as Opinion +from enum import Enum + +class Mode(Enum): + MINIMAL_FEASIBLE = 1 + STATE_OF_HEALTH = 2 + +@dataclass +class SystemState: + name: str = "NOTSET" + + mode: Mode = Mode.MINIMAL_FEASIBLE + + v_1: Opinion = field(default_factory=lambda : Opinion()) + + concurrent_sa: Opinion = field(default_factory=lambda : Opinion()) + + v_2: Opinion = field(default_factory=lambda : Opinion()) + + v_3: Opinion = field(default_factory=lambda : Opinion()) + + fusion: Opinion = field(default_factory=lambda : Opinion()) + planning: Opinion = field(default_factory=lambda : Opinion()) + + def getPerceptionState(self): + match self.mode: + case Mode.MINIMAL_FEASIBLE: + inter = self.v_1.comultiply(self.v_2) + sa_fused = inter.wb_fuse(self.concurrent_sa) + return sa_fused.comultiply(self.v_3) + + case Mode.STATE_OF_HEALTH: + inter = self.v_1.cum_fuse(self.v_2) + sa_fused = inter.wb_fuse(self.concurrent_sa) + return sa_fused.cum_fuse(self.v_3) + + def getOverall(self): + return self.getPerceptionState().multiply(self.fusion).multiply(self.planning) + + def interpolate(self, other, interp_fac: float): + interp = SystemState() + interp.name = self.name + "_interpolate_" + other.name + + interp.v_1 = self.v_1.interpolate(other.v_1, interp_fac) + interp.v_2 = self.v_2.interpolate(other.v_2, interp_fac) + interp.v_3 = self.v_3.interpolate(other.v_3, interp_fac) + + interp.concurrent_sa = self.concurrent_sa.interpolate(other.concurrent_sa, interp_fac) + + interp.fusion = self.fusion.interpolate(other.fusion, interp_fac) + interp.planning = self.planning.interpolate(other.planning, interp_fac) + + return interp diff --git a/publications/dissertation_wodtko/time_dependent_weather_forecast.py b/publications/dissertation_wodtko/time_dependent_weather_forecast.py new file mode 100644 index 0000000..f98a8a6 --- /dev/null +++ b/publications/dissertation_wodtko/time_dependent_weather_forecast.py @@ -0,0 +1,176 @@ +#!/usr/bin/python3 +import numpy as np +import subjective_logic as sl +import matplotlib +import matplotlib.pyplot as plt +from matplotlib.patches import Rectangle +import math + + +incorrect_observer = [10,75] +incorrect_prediction = 25 +rain_start = 50 +rain_stop = 90 + +n_steps = 100 +###################################################### +# create opinions +###################################################### + +trust_A_A = sl.Opinion2d([0.9,0.05],[1.0,0.0]) +trust_A_S = sl.Opinion2d([0.5,0.0],[1.0,0.0]) +trust_A_P = sl.Opinion2d([0.5,0.0],[1.0,0.0]) + +rainy_observation = sl.Opinion(0.8,0.0) +sunny_observation = sl.Opinion(0.0,0.8) + +predict_const_rainy = sl.Opinion(0.99, 0.0) +predict_const_sunny = sl.Opinion(0.0,0.99) + +predict_change_rainy = sl.Opinion(0.0,0.99) +predict_change_sunny = sl.Opinion(0.99,0.0) + +last_A_tr_ours = sl.Opinion2d() +last_A_tr_josang = sl.Opinion2d() +last_A_normal = sl.Opinion2d() +last_A_no_trust = sl.Opinion2d() + +tr_weighted_types_ours = [ + sl.WeightedTypes(sl.RelationType.CONFLICT, sl.TrustRevisionType.SHARES, sl.ConflictType.AVERAGE, 1), +] +tr_weighted_types_josang = [ + sl.WeightedTypes(sl.RelationType.CONFLICT, sl.TrustRevisionType.REFERENCE_FUSION, sl.ConflictType.BELIEF_AVERAGE, 1), +] + +values_tr_ours = np.zeros((n_steps,2)) +values_tr_josang = np.zeros((n_steps,2)) +values_normal = np.zeros((n_steps,2)) +values_no_trust = np.zeros((n_steps,2)) +values_gt = np.zeros((n_steps,2)) +values_gt[rain_start:rain_stop,1] = 1.0 +for k in range(n_steps): + # print(k) + # discount last opinion + last_A_tr_ours.trust_discount_(trust_A_A) + last_A_tr_josang.trust_discount_(trust_A_A) + last_A_normal.trust_discount_(trust_A_A) + + # prediction + last_A_pred_const_tr_ours = last_A_tr_ours.deduction(predict_const_rainy, predict_const_sunny) + last_A_pred_change_tr_ours = last_A_tr_ours.deduction(predict_change_rainy, predict_change_sunny) + last_A_pred_const_tr_josang = last_A_tr_josang.deduction(predict_const_rainy, predict_const_sunny) + last_A_pred_change_tr_josang = last_A_tr_josang.deduction(predict_change_rainy, predict_change_sunny) + last_A_pred_const_normal = last_A_normal.deduction(predict_const_rainy, predict_const_sunny) + last_A_pred_change_normal = last_A_normal.deduction(predict_change_rainy, predict_change_sunny) + last_A_pred_const_no_trust = last_A_no_trust.deduction(predict_const_rainy, predict_const_sunny) + last_A_pred_change_no_trust = last_A_no_trust.deduction(predict_change_rainy, predict_change_sunny) + + last_A_pred_tr_ours = last_A_pred_const_tr_ours + last_A_pred_tr_josang = last_A_pred_const_tr_josang + last_A_pred_normal = last_A_pred_const_normal + last_A_pred_no_trust = last_A_pred_const_no_trust + if k == rain_stop or k == incorrect_prediction: + last_A_pred_tr_ours = last_A_pred_change_tr_ours + last_A_pred_tr_josang = last_A_pred_change_tr_josang + last_A_pred_normal = last_A_pred_change_normal + last_A_pred_no_trust = last_A_pred_change_no_trust + + t_pred_tr_ours = sl.TrustedOpinion2d(trust_A_P, last_A_pred_tr_ours) + t_pred_tr_josang = sl.TrustedOpinion2d(trust_A_P, last_A_pred_tr_josang) + t_pred_normal = sl.TrustedOpinion2d(trust_A_P, last_A_pred_normal) + + # sources + source_op = sunny_observation + if rain_start <= k < rain_stop: + source_op = rainy_observation + + t_source_vec = [ + sl.TrustedOpinion(trust_A_S, source_op), + sl.TrustedOpinion(trust_A_S, source_op), + sl.TrustedOpinion(trust_A_S, source_op), + ] + if k in incorrect_observer: + if k < rain_start or k > rain_stop: + t_source_vec[0] = sl.TrustedOpinion(trust_A_S, rainy_observation) + else: + t_source_vec[0] = sl.TrustedOpinion(trust_A_S, sunny_observation) + t_source_vec[1] = sl.TrustedOpinion(trust_A_S, sunny_observation) + + t_vec_tr_ours = t_source_vec + [t_pred_tr_ours] + t_vec_tr_josang = t_source_vec + [t_pred_tr_josang] + t_vec_normal = t_source_vec + [t_pred_normal] + discounted_opinions = sl.TrustedOpinion2d.extractDiscountedOpinions(t_vec_normal) + opinions_no_trust = sl.TrustedOpinion2d.extractOpinions(t_source_vec) + [last_A_pred_no_trust] + + op_A_normal = sl.Fusion.fuse_opinions(sl.FusionType.CUMULATIVE, discounted_opinions) + op_A_tr_ours = sl.TrustedFusion.fuse_opinions( + sl.FusionType.CUMULATIVE, + tr_weighted_types_ours, + t_vec_tr_ours, + ) + op_A_tr_josang = sl.TrustedFusion.fuse_opinions( + sl.FusionType.CUMULATIVE, + tr_weighted_types_josang, + t_vec_tr_josang, + ) + op_A_no_trust = sl.Fusion.fuse_opinions(sl.FusionType.CUMULATIVE, opinions_no_trust) + + values_tr_ours[k] = [k, op_A_tr_ours.getBinomialProjection()] + values_tr_josang[k] = [k, op_A_tr_josang.getBinomialProjection()] + values_normal[k] = [k, op_A_normal.getBinomialProjection()] + values_no_trust[k] = [k, op_A_no_trust.getBinomialProjection()] + values_gt[k,0] = k + + last_A_tr_ours = op_A_tr_ours + last_A_tr_josang = op_A_tr_josang + last_A_normal = op_A_normal + last_A_no_trust = op_A_no_trust + +plt.plot(values_no_trust[:,1], label='no trust') +# plt.plot(values_normal[:,1], label='normal') +plt.plot(values_tr_josang[:,1], label='josang') +plt.plot(values_tr_ours[:,1], label='cs') +plt.plot(values_gt[:,1], 'r', label='gt') +plt.legend() + +plt.figure() +plt.plot(np.abs((values_no_trust - values_gt)[:,1])) +plt.plot(np.abs((values_tr_josang - values_gt)[:,1])) +plt.plot(np.abs((values_tr_ours - values_gt)[:,1])) + +plt.show() + +# plot_data = { +# "no_trust": values_no_trust, +# # "trusted_fusion": values_normal, +# "trust_revision_josang": values_tr_josang, +# "trust_revision_ours": values_tr_ours, +# "ground_truth": values_gt, +# } +# +# pgfplots_add_plot_line = "\\addplot [name path={name}, color={color}, line width=1.5pt{extra}]\n\ttable[col sep=comma]{{%\n" +# pgfplots_add_legend_entry = "\\addlegendentry{{{name}}}\n" +# pgfplots_end = "};\n\n" +# +# +# def export_values(name, data, filename): +# with open(filename, 'w') as f: +# f.write(pgfplots_add_plot_line.format(name=name, color=name + "_color", extra="")) +# np.savetxt(f, data, delimiter=', ', ) # newline='\\\\\n') +# f.write(pgfplots_end) +# # f.write(pgfplots_add_legend_entry.format(name=name.replace('_',' '))) +# f.write("\n") +# +# f.close() +# +# +# for name, data in plot_data.items(): +# filename_points =f"{name}_tikz_points.txt" +# filename_diffs =f"{name}_diffs_tikz_points.txt" +# +# export_values(name, data, filename_points) +# if not "truth" in name: +# diffs = data +# diffs[:,1] -= values_gt[:,1] +# export_values(name, np.abs(diffs), filename_diffs) +# diff --git a/publications/dissertation_wodtko/tr_cs_hscs.py b/publications/dissertation_wodtko/tr_cs_hscs.py new file mode 100644 index 0000000..6c9ddc8 --- /dev/null +++ b/publications/dissertation_wodtko/tr_cs_hscs.py @@ -0,0 +1,376 @@ +#!/usr/bin/python3 +import numpy as np +import subjective_logic as sl +import matplotlib +import matplotlib.pyplot as plt +from matplotlib.markers import MarkerStyle +from matplotlib.patches import Rectangle +import math + +plt.rcParams.update({ + 'font.size': 8, + 'text.usetex': True, + 'text.latex.preamble': " ".join([ + r'\usepackage{amsmath}', + r'\usepackage{amssymb}', + r'\usepackage{amsfonts}', + ]) +}) +plt.rc('text', usetex=True) +plt.rc('font', family='serif') + + +default_point_draw_args = { + "color" : 'tab:gray', + "zorder": 500, + "s": 80, +} +default_text_draw_args = { + "ha": 'left', + "va": 'top', + "fontsize": 18, + "offset": [0.01, 0.00], +} +default_arrow_draw_args = { + "length_includes_head": True, + "head_width": 0.02, + "overhang": 0.2, + "color": 'tab:gray', + "length_cap": 0.92, +} +export_args = { + "format": 'pdf', + "dpi": 500, + "transparent": True, + "bbox_inches": 'tight', + "pad_inches": 0, +} + +SHOW_NO_TR = True +SHOW_NO_TR_FUSION = False + +define_opinion_tikz_string = "\\defineOpinion[{prior}]{{{name}}}{{{belief}}}{{{disbelief}}}{{{uncertainty}}}" +tikz_filename_trust = "opinion_definitions_trust.tex" +tikz_filename_observ = "opinion_definitions_observ.tex" +tikz_filename_fusion = "opinion_definitions_fusion.tex" + + +###################################################### +# create opinions +###################################################### +meas_s1 = sl.Opinion(0.2, 0.7) +trust_s1 = sl.Opinion2d([0.2, 0.3], [1.0, 0.0]) +t_s1 = sl.TrustedOpinion(trust_s1, meas_s1) + +meas_s2 = sl.Opinion(0.0, 0.75) +trust_s2 = sl.Opinion2d([0.2, 0.2], [1.0, 0.0]) +t_s2 = sl.TrustedOpinion(trust_s2, meas_s2) + +meas_s3 = sl.Opinion(0.8, 0.05) +trust_s3 = sl.Opinion2d([0.7, 0.0], [1.0, 0.0]) +t_s3 = sl.TrustedOpinion(trust_s3, meas_s3) + +t_vec = [t_s1, t_s2, t_s3] +opinions = sl.TrustedOpinion2d.extractOpinions(t_vec) +discounted_opinions = sl.TrustedOpinion2d.extractDiscountedOpinions(t_vec) + + +normal_fusion = sl.Fusion.fuse_opinions(sl.FusionType.CUMULATIVE, opinions) +trusted_fusion = sl.TrustedFusion.fuse_opinions(sl.FusionType.CUMULATIVE, t_vec) + +###################################################### +# calc trusted fusion using trust revision +###################################################### +weighted_types_conflict = [ + sl.WeightedTypes(sl.RelationType.CONFLICT, sl.TrustRevisionType.SHARES, sl.ConflictType.AVERAGE, 2), +] +weighted_types_harmony = [ + sl.WeightedTypes(sl.RelationType.CONFLICT, sl.TrustRevisionType.SHARES, sl.ConflictType.AVERAGE, 2), + sl.WeightedTypes(sl.RelationType.HARMONY, sl.TrustRevisionType.SHARES, sl.ConflictType.AVERAGE, 2), +] + +fusion_conflict, t_vec_updated_conflict = sl.TrustedFusion.fuse_opinions_(sl.FusionType.CUMULATIVE, weighted_types_conflict, t_vec) +fusion_harmony, t_vec_updated_harmony = sl.TrustedFusion.fuse_opinions_(sl.FusionType.CUMULATIVE, weighted_types_harmony, t_vec) + +discounted_opinions_conflict = sl.TrustedOpinion2d.extractDiscountedOpinions(t_vec_updated_conflict) +discounted_opinions_harmony = sl.TrustedOpinion2d.extractDiscountedOpinions(t_vec_updated_harmony) + +###################################################### +# illustrate results +###################################################### +oranges = plt.get_cmap('Oranges') +greens = plt.get_cmap('Greens') + +op_str = r"$\omega_{{{l}}}^{{{u}}}$" +op_tr_str = r"$\check{{\omega}}_{{{l}}}^{{{u}}}$" + +cs_shift = 2 +bc_shift = [0,1,2] + + +###################################################### +# source opinions + +fig, ax = sl.create_triangle_plot(hypo_texts=('disbelief', 'belief')) +point_draw_args = default_point_draw_args.copy() +text_draw_args = default_text_draw_args.copy() +if SHOW_NO_TR: + for idx, opinion in enumerate(opinions): + sl.draw_point(opinion, **point_draw_args) + # op_str=r"$\omega_X^{S_{{idx}}}$" + if idx == 2: + text_draw_args["ha"] = "right" + text_draw_args["va"] = "top" + text_draw_args["offset"] = [0.0, -0.02] + # text = op_str.format(l="X", u="S_" + str(idx)) + " = " + op_str.format(l="X", u="[A;S_" + str(idx) + "]") + # sl.draw_text_at_point(opinion, text=text, **text_draw_args) + # continue + sl.draw_text_at_point(opinion, text=op_str.format(l="X", u="S_" + str(idx)), **text_draw_args) + + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:blue' +text_draw_args = default_text_draw_args.copy() +for idx, opinion in enumerate(discounted_opinions): + sl.draw_point(opinion, **point_draw_args) + # if idx == 2 and SHOW_NO_TR: + # continue + if idx == 1: + text_draw_args["ha"] = "right" + text_draw_args["va"] = "bottom" + text_draw_args["offset"] = [0.04, 0.02] + elif idx == 0: + text_draw_args["ha"] = "left" + text_draw_args["va"] = "bottom" + text_draw_args["offset"] = [0.04, 0.0] + else: + text_draw_args["ha"] = "right" + text_draw_args["va"] = "top" + text_draw_args["offset"] = [-0.02, 0.0] + + sl.draw_text_at_point(opinion, text=op_str.format(l="X", u="[A;S_" + str(idx) + "]"), **text_draw_args) + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:orange' +text_draw_args = default_text_draw_args.copy() +text_draw_args["ha"] = "left" +text_draw_args["va"] = "bottom" +text_draw_args["offset"] = [0.01, 0.01] +for idx, (opinion, opinion_up) in enumerate(zip(discounted_opinions, discounted_opinions_conflict)): + if opinion == opinion_up: + continue + sl.draw_point(opinion_up, **point_draw_args) + sl.draw_text_at_point(opinion_up, text=op_tr_str.format(l="X", u="[A;S_" + str(idx) + "]"), **text_draw_args) + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:green' +text_draw_args = default_text_draw_args.copy() +text_draw_args["va"] = "bottom" +for idx, (opinion, opinion_up) in enumerate(zip(discounted_opinions, discounted_opinions_harmony)): + if opinion == opinion_up: + continue + if idx == cs_shift: + point_draw_args['marker'] = MarkerStyle("o", fillstyle="right") + sl.draw_point(opinion_up, **point_draw_args) + continue + elif 'marker' in text_draw_args: + point_draw_args.pop('marker') + + if idx == 0: + text_draw_args["ha"] = "left" + text_draw_args["va"] = "top" + text_draw_args["offset"] = [0.00, 0.00] + else: + text_draw_args["ha"] = "right" + text_draw_args["va"] = "top" + text_draw_args["offset"] = [-0.02, 0.0] + + sl.draw_point(opinion_up, **point_draw_args) + sl.draw_text_at_point(opinion_up, text=op_tr_str.format(l="X", u="[A;S_" + str(idx) + "]"), **text_draw_args) + +sl.draw_arrow(discounted_opinions[cs_shift], discounted_opinions_conflict[cs_shift], **default_arrow_draw_args) +sl.draw_arrow(discounted_opinions[bc_shift[0]], discounted_opinions_harmony[bc_shift[0]], **default_arrow_draw_args) +sl.draw_arrow(discounted_opinions[bc_shift[1]], discounted_opinions_harmony[bc_shift[1]], **default_arrow_draw_args) + +###################################################### +# trust opinions + +sl.reset_plot() +fig2, ax2 = sl.create_triangle_plot(hypo_texts=('distrust', 'trust')) + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:blue' +text_draw_args = default_text_draw_args.copy() +text_draw_args["ha"] = "right" +text_draw_args["va"] = "bottom" +text_draw_args["offset"] = [-0.01, 0.02] +for idx, t_op in enumerate(t_vec): + trust = t_op.trust + if idx == 2: + text_draw_args["ha"] = "left" + text_draw_args["va"] = "bottom" + text_draw_args["offset"] = [0.01, 0.02] + sl.draw_point(trust, **point_draw_args) + sl.draw_text_at_point(trust, text=op_str.format(u="A", l="S_" + str(idx)), **text_draw_args) + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:orange' +text_draw_args = default_text_draw_args.copy() +text_draw_args["offset"] = [0.01, -0.01] + +for idx, (t_op, t_op_up) in enumerate(zip(t_vec, t_vec_updated_conflict)): + if t_op.trust == t_op_up.trust: + continue + trust = t_op_up.trust + sl.draw_point(trust, **point_draw_args) + sl.draw_text_at_point(trust, text=op_tr_str.format(u="A", l="S_" + str(idx)), **text_draw_args) + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:green' +text_draw_args = default_text_draw_args.copy() +text_draw_args["ha"] = "left" +text_draw_args["va"] = "bottom" +text_draw_args["offset"] = [0.01, 0.01] +for idx, (t_op, t_op_up) in enumerate(zip(t_vec, t_vec_updated_harmony)): + if t_op.trust == t_op_up.trust: + continue + trust = t_op_up.trust + if idx == cs_shift: + point_draw_args['marker'] = MarkerStyle("o", fillstyle="right") + elif 'marker' in text_draw_args: + point_draw_args.pop('marker') + sl.draw_point(trust, **point_draw_args) + if idx != cs_shift: + sl.draw_text_at_point(trust, text=op_tr_str.format(u="A", l="S_" + str(idx)), **text_draw_args) + +sl.draw_arrow(t_vec[cs_shift].trust, t_vec_updated_conflict[cs_shift].trust, **default_arrow_draw_args) +sl.draw_arrow(t_vec[bc_shift[0]].trust, t_vec_updated_harmony[bc_shift[0]].trust, **default_arrow_draw_args) +sl.draw_arrow(t_vec[bc_shift[1]].trust, t_vec_updated_harmony[bc_shift[1]].trust, **default_arrow_draw_args) + +###################################################### +# fusion opinions + +sl.reset_plot() +fig3, ax3 = sl.create_triangle_plot(hypo_texts=('disbelief', 'belief')) + +point_draw_args = default_point_draw_args.copy() +text_draw_args = default_text_draw_args.copy() +if SHOW_NO_TR_FUSION: + point_draw_args["color"] = 'tab:gray' + sl.draw_point(normal_fusion, **point_draw_args) + text_draw_args["offset"] = [-0.015, 0.03] + text_draw_args["ha"] = 'right' + sl.draw_text_at_point(normal_fusion, text=op_str.format(l="X", u="\diamond (\mathbb{S})"), **text_draw_args) + +point_draw_args["color"] = 'tab:blue' +sl.draw_point(trusted_fusion, **point_draw_args) +text_draw_args["offset"] = [0., -0.02] +text_draw_args["ha"] = 'center' +text_draw_args["va"] = 'top' +sl.draw_text_at_point(trusted_fusion, text=op_str.format(l="X", u="\diamond ([A;\mathbb{S}])"), **text_draw_args) + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:orange' +text_draw_args = default_text_draw_args.copy() +text_draw_args["offset"] = [0., 0.01] +text_draw_args["ha"] = 'center' +text_draw_args["va"] = 'bottom' +sl.draw_point(fusion_conflict, **point_draw_args) +sl.draw_text_at_point(fusion_conflict, text=op_tr_str.format(l="X", u="\diamond ([A;\mathbb{S}])"), **text_draw_args) + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:green' +text_draw_args = default_text_draw_args.copy() +text_draw_args["offset"] = [-0.02, -0.04] +text_draw_args["ha"] = 'center' +text_draw_args["va"] = 'top' +sl.draw_point(fusion_harmony, **point_draw_args) +sl.draw_text_at_point(fusion_harmony, text=op_tr_str.format(l="X", u="\diamond ([A;\mathbb{S}])"), **text_draw_args) + +arrow_draw_args = default_arrow_draw_args.copy() +arrow_draw_args["length_cap"] = 0.9 +sl.draw_arrow(trusted_fusion, fusion_conflict, **arrow_draw_args) +arrow_draw_args["length_cap"] = 0.8 +sl.draw_arrow(trusted_fusion, fusion_harmony, **arrow_draw_args) + + +x_start = -0.05 +x_end = 1.05 +y_start = -0.175 +y_end = 0.95 + +x_diff = x_end - x_start +y_diff = y_end - y_start + +relative_size = x_diff / y_diff + +figure_size_y = 5 + +for _ax, _fig in zip([ax,ax2,ax3],[fig,fig2, fig3]): + _ax.set_xlim(x_start, x_end) + _ax.set_ylim(y_start, y_end) + _fig.set_size_inches(figure_size_y * relative_size,figure_size_y) + +plt.show() + +# fig.savefig("exp_tr_opinions.pdf", **export_args) +# fig2.savefig("exp_tr_trust.pdf", **export_args) +# fig3.savefig("exp_tr_fusion.pdf", **export_args) + +trust_file = open(tikz_filename_trust, 'w') +observ_file = open(tikz_filename_observ, 'w') +fusion_file = open(tikz_filename_fusion, 'w') + +for idx, t_op in enumerate(t_vec): + trust = t_op.trust + name = f'TAS{idx}' + trust_file.write(define_opinion_tikz_string.format(prior=trust.getBinomialPrior(), name=name, belief=trust.belief(), disbelief=trust.disbelief(), uncertainty=trust.uncertainty())) + trust_file.write('\n') + +for idx, t_op in enumerate(t_vec_updated_conflict): + trust = t_op.trust + name = f'RCTAS{idx}' + trust_file.write(define_opinion_tikz_string.format(prior=trust.getBinomialPrior(), name=name, belief=trust.belief(), disbelief=trust.disbelief(), uncertainty=trust.uncertainty())) + trust_file.write('\n') + +for idx, t_op in enumerate(t_vec_updated_harmony): + trust = t_op.trust + name = f'RHTAS{idx}' + trust_file.write(define_opinion_tikz_string.format(prior=trust.getBinomialPrior(), name=name, belief=trust.belief(), disbelief=trust.disbelief(), uncertainty=trust.uncertainty())) + trust_file.write('\n') + +for idx, op in enumerate(opinions): + name = f'OSX{idx}' + observ_file.write(define_opinion_tikz_string.format(prior=op.getBinomialPrior(), name=name, belief=op.belief(), disbelief=op.disbelief(), uncertainty=op.uncertainty())) + observ_file.write('\n') + +for idx, op in enumerate(discounted_opinions): + name = f'DOSX{idx}' + observ_file.write(define_opinion_tikz_string.format(prior=op.getBinomialPrior(), name=name, belief=op.belief(), disbelief=op.disbelief(), uncertainty=op.uncertainty())) + observ_file.write('\n') + +for idx, t_op in enumerate(t_vec_updated_conflict): + op = t_op.discounted_opinion() + name = f'RCDOSX{idx}' + observ_file.write(define_opinion_tikz_string.format(prior=op.getBinomialPrior(), name=name, belief=op.belief(), disbelief=op.disbelief(), uncertainty=op.uncertainty())) + observ_file.write('\n') + +for idx, t_op in enumerate(t_vec_updated_harmony): + op = t_op.discounted_opinion() + name = f'RHDOSX{idx}' + observ_file.write(define_opinion_tikz_string.format(prior=op.getBinomialPrior(), name=name, belief=op.belief(), disbelief=op.disbelief(), uncertainty=op.uncertainty())) + observ_file.write('\n') + +fusion_ops = [normal_fusion, trusted_fusion, fusion_conflict, fusion_harmony] +fusion_names = ['FN', 'TF', 'TRC', 'TRH'] +for name, op in zip(fusion_names, fusion_ops): + fusion_file.write(define_opinion_tikz_string.format(prior=op.getBinomialPrior(), name=name, belief=op.belief(), disbelief=op.disbelief(), uncertainty=op.uncertainty())) + fusion_file.write('\n') + + +trust_file.close() +observ_file.close() +fusion_file.close() + + + diff --git a/publications/dissertation_wodtko/tr_josang_cs.py b/publications/dissertation_wodtko/tr_josang_cs.py new file mode 100644 index 0000000..ed074e1 --- /dev/null +++ b/publications/dissertation_wodtko/tr_josang_cs.py @@ -0,0 +1,386 @@ +#!/usr/bin/python3 +import numpy as np +import subjective_logic as sl +import matplotlib +import matplotlib.pyplot as plt +from matplotlib.patches import Rectangle +import math + +plt.rcParams.update({ + 'font.size': 8, + 'text.usetex': True, + 'text.latex.preamble': " ".join([ + r'\usepackage{amsmath}', + r'\usepackage{amssymb}', + r'\usepackage{amsfonts}', + ]) +}) +plt.rc('text', usetex=True) +plt.rc('font', family='serif') + + +default_point_draw_args = { + "color" : 'tab:gray', + "zorder": 500, + "s": 80, +} +default_text_draw_args = { + "ha": 'left', + "va": 'top', + "fontsize": 18, + "offset": [0.01, 0.00], +} +default_arrow_draw_args = { + "length_includes_head": True, + "head_width": 0.02, + "overhang": 0.2, + "color": 'tab:gray', + "length_cap": 0.92, +} +export_args = { + "format": 'pdf', + "dpi": 500, + "transparent": True, + "bbox_inches": 'tight', + "pad_inches": 0, +} + +SHOW_ACC = False +SHOW_AVG = not SHOW_ACC +SHOW_NO_TR = True +SHOW_NO_TR_FUSION = False + +define_opinion_tikz_string = "\\defineOpinion[{prior}]{{{name}}}{{{belief}}}{{{disbelief}}}{{{uncertainty}}}" +tikz_filename_trust = "opinion_definitions_trust.tex" +tikz_filename_observ = "opinion_definitions_observ.tex" +tikz_filename_fusion = "opinion_definitions_fusion.tex" + +###################################################### +# create opinions +###################################################### +meas_s1 = sl.Opinion(0.2, 0.7) +trust_s1 = sl.Opinion2d([0.2, 0.3], [1.0, 0.0]) +t_s1 = sl.TrustedOpinion(trust_s1, meas_s1) + +meas_s2 = sl.Opinion(0.0, 0.75) +trust_s2 = sl.Opinion2d([0.2, 0.2], [1.0, 0.0]) +t_s2 = sl.TrustedOpinion(trust_s2, meas_s2) + +meas_s3 = sl.Opinion(0.8, 0.05) +trust_s3 = sl.Opinion2d([0.7, 0.0], [1.0, 0.0]) +t_s3 = sl.TrustedOpinion(trust_s3, meas_s3) +# meas_s1 = sl.Opinion(0.2, 0.7) +# trust_s1 = sl.Opinion2d([0.3, 0.3], [1.0, 0.0]) +# t_s1 = sl.TrustedOpinion(trust_s1, meas_s1) +# +# meas_s2 = sl.Opinion(0.0, 0.7) +# trust_s2 = sl.Opinion2d([0.3, 0.2], [1.0, 0.0]) +# t_s2 = sl.TrustedOpinion(trust_s2, meas_s2) +# +# meas_s3 = sl.Opinion(0.8, 0.05) +# trust_s3 = sl.Opinion2d([0.7, 0.0], [1.0, 0.0]) +# t_s3 = sl.TrustedOpinion(trust_s3, meas_s3) + +t_vec = [t_s1, t_s2, t_s3] +opinions = sl.TrustedOpinion2d.extractOpinions(t_vec) +discounted_opinions = sl.TrustedOpinion2d.extractDiscountedOpinions(t_vec) + + +###################################################### +# calc normal and trusted fusion +###################################################### +normal_fusion = sl.Fusion.fuse_opinions(sl.FusionType.CUMULATIVE, opinions) +trusted_fusion = sl.TrustedFusion.fuse_opinions(sl.FusionType.CUMULATIVE, t_vec) + +###################################################### +# calc trusted fusion using trust revision +###################################################### +weighted_types_cs_acc = [ + sl.WeightedTypes(sl.RelationType.CONFLICT, sl.TrustRevisionType.SHARES, sl.ConflictType.ACCUMULATE, 2), +] +weighted_types_cs_avg = [ + sl.WeightedTypes(sl.RelationType.CONFLICT, sl.TrustRevisionType.SHARES, sl.ConflictType.AVERAGE, 2), +] +weighted_types_bc = [ + sl.WeightedTypes(sl.RelationType.CONFLICT, sl.TrustRevisionType.REFERENCE_FUSION, sl.ConflictType.BELIEF_AVERAGE, 2), +] + +fusion_cs_acc, t_vec_updated_cs_acc = sl.TrustedFusion.fuse_opinions_(sl.FusionType.CUMULATIVE, weighted_types_cs_acc, t_vec) +fusion_cs_avg, t_vec_updated_cs_avg = sl.TrustedFusion.fuse_opinions_(sl.FusionType.CUMULATIVE, weighted_types_cs_avg, t_vec) +fusion_bc, t_vec_updated_bc = sl.TrustedFusion.fuse_opinions_(sl.FusionType.CUMULATIVE, weighted_types_bc, t_vec) + +discounted_opinions_cs_acc = sl.TrustedOpinion2d.extractDiscountedOpinions(t_vec_updated_cs_acc) +discounted_opinions_cs_avg = sl.TrustedOpinion2d.extractDiscountedOpinions(t_vec_updated_cs_avg) +discounted_opinions_bc = sl.TrustedOpinion2d.extractDiscountedOpinions(t_vec_updated_bc) + +###################################################### +# illustrate results +###################################################### +oranges = plt.get_cmap('Oranges') +greens = plt.get_cmap('Greens') + +op_str = r"$\omega_{{{l}}}^{{{u}}}$" +op_tr_str = r"$\check{{\omega}}_{{{l}}}^{{{u}}}$" + +cs_shift = 2 +bc_shift = 0 + +###################################################### +# source opinions + +fig, ax = sl.create_triangle_plot(hypo_texts=('disbelief\n(sunny)', 'belief\n(rainy)')) +point_draw_args = default_point_draw_args.copy() +text_draw_args = default_text_draw_args.copy() +if SHOW_NO_TR: + for idx, opinion in enumerate(opinions): + sl.draw_point(opinion, **point_draw_args) + # op_str=r"$\omega_X^{S_{{idx}}}$" + if idx == 2: + text_draw_args["ha"] = "right" + text_draw_args["va"] = "top" + text_draw_args["offset"] = [0.0, -0.02] + text = op_str.format(l="X", u="S_" + str(idx)) + " = " + op_str.format(l="X", u="[A;S_" + str(idx) + "]") + sl.draw_text_at_point(opinion, text=text, **text_draw_args) + continue + sl.draw_text_at_point(opinion, text=op_str.format(l="X", u="S_" + str(idx)), **text_draw_args) + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:blue' +text_draw_args = default_text_draw_args.copy() +for idx, opinion in enumerate(discounted_opinions): + sl.draw_point(opinion, **point_draw_args) + if idx == 2 and SHOW_NO_TR: + continue + if idx == 1: + text_draw_args["ha"] = "right" + text_draw_args["va"] = "bottom" + sl.draw_text_at_point(opinion, text=op_str.format(l="X", u="[A;S_" + str(idx) + "]"), **text_draw_args) + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:green' +text_draw_args = default_text_draw_args.copy() +text_draw_args["ha"] = "right" +text_draw_args["va"] = "bottom" +text_draw_args["offset"] = [-0.01, 0.] +if SHOW_ACC: + for idx, (opinion, opinion_up) in enumerate(zip(discounted_opinions, discounted_opinions_cs_acc)): + if opinion == opinion_up: + continue + sl.draw_point(opinion_up, **point_draw_args) + sl.draw_text_at_point(opinion_up, text=op_tr_str.format(l="X", u="[A;S_" + str(idx) + "]"), **text_draw_args) + +if SHOW_AVG: + for idx, (opinion, opinion_up) in enumerate(zip(discounted_opinions, discounted_opinions_cs_avg)): + if opinion == opinion_up: + continue + sl.draw_point(opinion_up, **point_draw_args) + sl.draw_text_at_point(opinion_up, text=op_tr_str.format(l="X", u="[A;S_" + str(idx) + "]"), **text_draw_args) + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:orange' +text_draw_args = default_text_draw_args.copy() +text_draw_args["va"] = "bottom" +for idx, (opinion, opinion_up) in enumerate(zip(discounted_opinions, discounted_opinions_bc)): + if opinion == opinion_up: + continue + sl.draw_point(opinion_up, **point_draw_args) + sl.draw_text_at_point(opinion_up, text=op_tr_str.format(l="X", u="[A;S_" + str(idx) + "]"), **text_draw_args) + +sl.draw_arrow(discounted_opinions[cs_shift], discounted_opinions_cs_avg[cs_shift], **default_arrow_draw_args) +sl.draw_arrow(discounted_opinions[bc_shift], discounted_opinions_bc[bc_shift], **default_arrow_draw_args) +sl.draw_arrow(discounted_opinions[bc_shift+1], discounted_opinions_bc[bc_shift+1], **default_arrow_draw_args) + +###################################################### +# trust opinions + +sl.reset_plot() +fig2, ax2 = sl.create_triangle_plot(hypo_texts=('distrust', 'trust')) + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:blue' +text_draw_args = default_text_draw_args.copy() +text_draw_args["ha"] = "right" +text_draw_args["va"] = "bottom" +text_draw_args["offset"] = [-0.01, 0.02] +for idx, t_op in enumerate(t_vec): + trust = t_op.trust + if idx == 2: + text_draw_args["ha"] = "left" + text_draw_args["va"] = "bottom" + text_draw_args["offset"] = [0.01, 0.02] + sl.draw_point(trust, **point_draw_args) + sl.draw_text_at_point(trust, text=op_str.format(u="A", l="S_" + str(idx)), **text_draw_args) + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:green' +text_draw_args = default_text_draw_args.copy() +text_draw_args["offset"] = [0.01, -0.01] +if SHOW_ACC: + for idx, (t_op, t_op_up) in enumerate(zip(t_vec, t_vec_updated_cs_acc)): + if t_op.trust() == t_op_up.trust(): + continue + trust = t_op_up.trust() + sl.draw_point(trust, **point_draw_args) + sl.draw_text_at_point(trust, text=op_tr_str.format(u="A", l="S_" + str(idx)), **text_draw_args) + +if SHOW_AVG: + for idx, (t_op, t_op_up) in enumerate(zip(t_vec, t_vec_updated_cs_avg)): + if t_op.trust == t_op_up.trust: + continue + trust = t_op_up.trust + sl.draw_point(trust, **point_draw_args) + sl.draw_text_at_point(trust, text=op_tr_str.format(u="A", l="S_" + str(idx)), **text_draw_args) + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:orange' +text_draw_args = default_text_draw_args.copy() +text_draw_args["offset"] = [0.01, -0.01] +for idx, (t_op, t_op_up) in enumerate(zip(t_vec, t_vec_updated_bc)): + if t_op.trust == t_op_up.trust: + continue + trust = t_op_up.trust + sl.draw_point(trust, **point_draw_args) + sl.draw_text_at_point(trust, text=op_tr_str.format(u="A", l="S_" + str(idx)), **text_draw_args) + +sl.draw_arrow(t_vec[cs_shift].trust, t_vec_updated_cs_avg[cs_shift].trust, **default_arrow_draw_args) +sl.draw_arrow(t_vec[bc_shift].trust, t_vec_updated_bc[bc_shift].trust, **default_arrow_draw_args) +sl.draw_arrow(t_vec[bc_shift+1].trust, t_vec_updated_bc[bc_shift+1].trust, **default_arrow_draw_args) + +###################################################### +# fusion opinions + + +sl.reset_plot() +fig3, ax3 = sl.create_triangle_plot(hypo_texts=('disbelief\n(sunny)', 'belief\n(rainy)')) + +point_draw_args = default_point_draw_args.copy() +text_draw_args = default_text_draw_args.copy() +if SHOW_NO_TR_FUSION: + point_draw_args["color"] = 'tab:gray' + sl.draw_point(normal_fusion, **point_draw_args) + text_draw_args["offset"] = [-0.015, 0.03] + text_draw_args["ha"] = 'right' + sl.draw_text_at_point(normal_fusion, text=op_str.format(l="X", u="\diamond (\mathbb{S})"), **text_draw_args) + +point_draw_args["color"] = 'tab:blue' +sl.draw_point(trusted_fusion, **point_draw_args) +text_draw_args["offset"] = [0., -0.02] +text_draw_args["ha"] = 'center' +text_draw_args["va"] = 'top' +sl.draw_text_at_point(trusted_fusion, text=op_str.format(l="X", u="\diamond ([A;\mathbb{S}])"), **text_draw_args) + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:green' +text_draw_args = default_text_draw_args.copy() +text_draw_args["offset"] = [0., 0.01] +text_draw_args["ha"] = 'center' +text_draw_args["va"] = 'bottom' +if SHOW_ACC: + sl.draw_point(fusion_cs_acc, **point_draw_args) + sl.draw_text_at_point(fusion_cs_acc, text=op_tr_str.format(l="X", u="\diamond ([A;\mathbb{S}])"), **text_draw_args) +if SHOW_AVG: + sl.draw_point(fusion_cs_avg, **point_draw_args) + sl.draw_text_at_point(fusion_cs_avg, text=op_tr_str.format(l="X", u="\diamond ([A;\mathbb{S}])"), **text_draw_args) + +point_draw_args = default_point_draw_args.copy() +point_draw_args["color"] = 'tab:orange' +text_draw_args = default_text_draw_args.copy() +text_draw_args["offset"] = [0., 0.01] +text_draw_args["ha"] = 'left' +text_draw_args["va"] = 'bottom' +sl.draw_point(fusion_bc, **point_draw_args) +sl.draw_text_at_point(fusion_bc, text=op_tr_str.format(l="X", u="\diamond ([A;\mathbb{S}])"), **text_draw_args) + +arrow_draw_args = default_arrow_draw_args.copy() +arrow_draw_args["length_cap"] = 0.9 +sl.draw_arrow(trusted_fusion, fusion_cs_avg, **arrow_draw_args) +arrow_draw_args["length_cap"] = 0.8 +sl.draw_arrow(trusted_fusion, fusion_bc, **arrow_draw_args) + + +x_start = -0.05 +x_end = 1.05 +y_start = -0.175 +y_end = 0.95 + +x_diff = x_end - x_start +y_diff = y_end - y_start + +relative_size = x_diff / y_diff + +figure_size_y = 5 + +for _ax, _fig in zip([ax,ax2,ax3],[fig,fig2, fig3]): + _ax.set_xlim(x_start, x_end) + _ax.set_ylim(y_start, y_end) + _fig.set_size_inches(figure_size_y * relative_size,figure_size_y) + +# fig.savefig("exp_tr_opinions.pdf", **export_args) +# fig2.savefig("exp_tr_trust.pdf", **export_args) +# fig3.savefig("exp_tr_fusion.pdf", **export_args) + +plt.show() + + +# trust_file = open(tikz_filename_trust, 'w') +# observ_file = open(tikz_filename_observ, 'w') +# fusion_file = open(tikz_filename_fusion, 'w') +# +# for idx, t_op in enumerate(t_vec): +# trust = t_op.trust +# name = f'TAS{idx}' +# trust_file.write(define_opinion_tikz_string.format(prior=trust.getBinomialPrior(), name=name, belief=trust.belief(), disbelief=trust.disbelief(), uncertainty=trust.uncertainty())) +# trust_file.write('\n') +# +# for idx, t_op in enumerate(t_vec_updated_bc): +# trust = t_op.trust +# name = f'JTAS{idx}' +# trust_file.write(define_opinion_tikz_string.format(prior=trust.getBinomialPrior(), name=name, belief=trust.belief(), disbelief=trust.disbelief(), uncertainty=trust.uncertainty())) +# trust_file.write('\n') +# +# for idx, t_op in enumerate(t_vec_updated_cs_avg): +# trust = t_op.trust +# name = f'CTAS{idx}' +# trust_file.write(define_opinion_tikz_string.format(prior=trust.getBinomialPrior(), name=name, belief=trust.belief(), disbelief=trust.disbelief(), uncertainty=trust.uncertainty())) +# trust_file.write('\n') +# +# for idx, op in enumerate(opinions): +# name = f'OSX{idx}' +# observ_file.write(define_opinion_tikz_string.format(prior=op.getBinomialPrior(), name=name, belief=op.belief(), disbelief=op.disbelief(), uncertainty=op.uncertainty())) +# observ_file.write('\n') +# +# for idx, op in enumerate(discounted_opinions): +# name = f'DOSX{idx}' +# observ_file.write(define_opinion_tikz_string.format(prior=op.getBinomialPrior(), name=name, belief=op.belief(), disbelief=op.disbelief(), uncertainty=op.uncertainty())) +# observ_file.write('\n') +# +# for idx, t_op in enumerate(t_vec_updated_bc): +# op = t_op.discounted_opinion() +# name = f'JDOSX{idx}' +# observ_file.write(define_opinion_tikz_string.format(prior=op.getBinomialPrior(), name=name, belief=op.belief(), disbelief=op.disbelief(), uncertainty=op.uncertainty())) +# observ_file.write('\n') +# +# for idx, t_op in enumerate(t_vec_updated_cs_avg): +# op = t_op.discounted_opinion() +# name = f'CDOSX{idx}' +# observ_file.write(define_opinion_tikz_string.format(prior=op.getBinomialPrior(), name=name, belief=op.belief(), disbelief=op.disbelief(), uncertainty=op.uncertainty())) +# observ_file.write('\n') +# +# fusion_ops = [normal_fusion, trusted_fusion, fusion_bc, fusion_cs_avg] +# fusion_names = ['FN', 'TF', 'TRJ', 'TRC'] +# for name, op in zip(fusion_names, fusion_ops): +# fusion_file.write(define_opinion_tikz_string.format(prior=op.getBinomialPrior(), name=name, belief=op.belief(), disbelief=op.disbelief(), uncertainty=op.uncertainty())) +# fusion_file.write('\n') +# +# +# trust_file.close() +# observ_file.close() +# fusion_file.close() + + + + + + + + diff --git a/publications/eSLIM++/2025_mfi_wodtko_eslimpp.bib b/publications/eSLIM++/2025_mfi_wodtko_eslimpp.bib index 926b39d..5621329 100644 --- a/publications/eSLIM++/2025_mfi_wodtko_eslimpp.bib +++ b/publications/eSLIM++/2025_mfi_wodtko_eslimpp.bib @@ -1,7 +1,11 @@ -@inproceedings{wodtko2025eslim, - author = {Thomas Wodtko and Thomas Griebel and Michael Buchholz}, - booktitle = {IEEE International Conference on Multisensor Fusion and Integration for Intelligent Systems (MFI), submitted for publication}, - title = {eSLIM++ - an Efficient Subjective Logic Implementation in C++ Providing Easy-to-Use Python Interfaces}, - year = {2025} +@INPROCEEDINGS{wodtko2025eslimpp, + author={Wodtko, Thomas and Griebel, Thomas and Buchholz, Michael}, + booktitle={2025 IEEE International Conference on Multisensor Fusion and Integration for Intelligent Systems (MFI)}, + title={eSLIM++ - An Efficient Subjective Logic Implementation in C++ Providing Easy-to-Use Python Interfaces}, + year={2025}, + volume={}, + number={}, + pages={1-7}, + keywords={Computer science;Uncertainty;Autonomous systems;Decision making;C++ languages;Sensor fusion;Libraries;Reproducibility of results;Logic;Object tracking}, + doi={10.1109/MFI67357.2025.11259193} } - diff --git a/publications/eSLIM++/README.md b/publications/eSLIM++/README.md index 6626059..2e1fb99 100644 --- a/publications/eSLIM++/README.md +++ b/publications/eSLIM++/README.md @@ -41,3 +41,20 @@ It shall be mentioned that, at the of writing, the use of the dst library was no How certain (fusion)operators are meant to be applied was not fully apparent to the author. Respectively, the DST evaluation uses one specific operator for fusion, and the categorization is deactivated, since the author was not aware of how to access the belief_mass of a certain set. That being said, the DST Evaluation is available, but take a little longer; for more runs (>10) the eval execution takes some time. + + +# Citation +Please use the following citation for reference to the publication in this directory (`2025_mfi_wodtko_eslimpp.bib`): +```bibtex +@INPROCEEDINGS{wodtko2025eslimpp, + author={Wodtko, Thomas and Griebel, Thomas and Buchholz, Michael}, + booktitle={2025 IEEE International Conference on Multisensor Fusion and Integration for Intelligent Systems (MFI)}, + title={eSLIM++ - An Efficient Subjective Logic Implementation in C++ Providing Easy-to-Use Python Interfaces}, + year={2025}, + volume={}, + number={}, + pages={1-7}, + keywords={Computer science;Uncertainty;Autonomous systems;Decision making;C++ languages;Sensor fusion;Libraries;Reproducibility of results;Logic;Object tracking}, + doi={10.1109/MFI67357.2025.11259193} +} +```