diff --git a/CMakeLists.txt b/CMakeLists.txt index 3003aaf..81ca938 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,20 @@ include_directories(include include/andres) file(GLOB headers include/andres/*.hxx) enable_testing() +############################################################################## +# C++11 support +############################################################################## +include(CheckCXXCompilerFlag) +CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) +CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) +if(COMPILER_SUPPORTS_CXX11) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +elseif(COMPILER_SUPPORTS_CXX0X) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") +else() + message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Some functionality will not be available.") +endif() + find_package(Matlab QUIET) if(MATLAB_FOUND) INCLUDE_DIRECTORIES(${MATLAB_INCLUDE_DIR}) @@ -14,6 +28,7 @@ endif() add_executable(test-partition-comparison src/unittest/partition-comparison.cxx ${headers}) add_test(test-partition-comparison test-partition-comparison) + if(MATLAB_FOUND) add_library(andres_rand_index SHARED src/mex/andres_rand_index.cxx src/mex/andres_rand_index.def ${headers}) target_link_libraries(andres_rand_index ${MATLAB_LIBRARIES}) diff --git a/include/andres/partition-comparison.hxx b/include/andres/partition-comparison.hxx index 24580b7..cae81d7 100644 --- a/include/andres/partition-comparison.hxx +++ b/include/andres/partition-comparison.hxx @@ -8,274 +8,242 @@ #include // log #include // runtime_error + namespace andres { -// interface -template - size_t matchingPairs(ITERATOR_0, ITERATOR_0, ITERATOR_1, const bool = false); -template - size_t matchingPairs(ITERATOR_0, ITERATOR_0, ITERATOR_1, const bool, size_t&); -template - double randIndex(ITERATOR_0, ITERATOR_0, ITERATOR_1, const bool = false); -template - double variationOfInformation(ITERATOR_0, ITERATOR_0, ITERATOR_1, const bool = false); - -// brute force code for unit tests -template - size_t matchingPairsBruteForce(ITERATOR_0, ITERATOR_0, ITERATOR_1, const bool = false); -template - size_t matchingPairsBruteForce(ITERATOR_0, ITERATOR_0, ITERATOR_1, const bool, size_t&); -template - double randIndexBruteForce(ITERATOR_0, ITERATOR_0, ITERATOR_1, const bool = false); - -// implementation - -template -inline size_t -matchingPairsBruteForce -( - ITERATOR_0 begin0, - ITERATOR_0 end0, - ITERATOR_1 begin1, - const bool ignoreDefaultLabel -) -{ - size_t N; - return matchingPairsBruteForce(begin0, end0, begin1, ignoreDefaultLabel, N); -} - -template -size_t -matchingPairsBruteForce -( - ITERATOR_0 begin0, - ITERATOR_0 end0, - ITERATOR_1 begin1, - const bool ignoreDefaultLabel, - size_t& N // output: number of elements which have a non-zero label in both partitions -) -{ - typedef typename std::iterator_traits::value_type Label0; - typedef typename std::iterator_traits::value_type Label1; - - size_t AB = 0; - if(ignoreDefaultLabel) { - N = 0; - ITERATOR_1 it1 = begin1; - for(ITERATOR_0 it0 = begin0; it0 != end0; ++it0, ++it1) { - if(*it0 != Label0() && *it1 != Label1()) { - ++N; - ITERATOR_1 it1b = it1 + 1; - for(ITERATOR_0 it0b = it0 + 1; it0b != end0; ++it0b, ++it1b) { - if(*it0b != Label0() && *it1b != Label1()) { - if((*it0 == *it0b && *it1 == *it1b) || (*it0 != *it0b && *it1 != *it1b)) { - ++AB; - } - } +template +class RandError { +public: + typedef T value_type; + + template + RandError(ITERATOR_TRUTH begin0, ITERATOR_TRUTH end0, ITERATOR_PRED begin1, bool ignoreDefaultLabel = false) + { + typedef typename std::iterator_traits::value_type Label0; + typedef typename std::iterator_traits::value_type Label1; + typedef std::pair Pair; + typedef std::map OverlapMatrix; + typedef std::map TruthSumMap; + typedef std::map PredSumMap; + + OverlapMatrix n; + TruthSumMap truthSum; + PredSumMap predSum; + + elements_ = std::distance(begin0, end0); + + if (ignoreDefaultLabel) + { + elements_ = 0; + + for(; begin0 != end0; ++begin0, ++begin1) + if (*begin0 != Label0() && *begin1 != Label1()) + { + ++n[Pair(*begin0, *begin1)]; + ++truthSum[*begin0]; + ++predSum[*begin1]; + ++elements_; } - } + + if (elements_ == 0) + throw std::runtime_error("No element is labeled in both partitions."); } - } - else { - N = std::distance(begin0, end0); - ITERATOR_1 it1 = begin1; - for(ITERATOR_0 it0 = begin0; it0 != end0; ++it0, ++it1) { - ITERATOR_1 it1b = it1 + 1; - for(ITERATOR_0 it0b = it0 + 1; it0b != end0; ++it0b, ++it1b) { - if( (*it0 == *it0b && *it1 == *it1b) || (*it0 != *it0b && *it1 != *it1b) ) { - ++AB; - } + else + for(; begin0 != end0; ++begin0, ++begin1) + { + ++n[Pair(*begin0, *begin1)]; + ++truthSum[*begin0]; + ++predSum[*begin1]; } + + for (auto const& it : predSum) + falseJoins_ += it.second * it.second; + + for (auto const& it : truthSum) + falseCuts_ += it.second * it.second; + + for (auto const& it : n) + { + const size_t i = it.first.first; + const size_t j = it.first.second; + const size_t n_ij = it.second; + + trueJoins_ += n_ij * (n_ij - 1) / 2; + falseCuts_ -= n_ij * n_ij; + falseJoins_ -= n_ij * n_ij; } - } - return AB; -} - -template -inline size_t -matchingPairs -( - ITERATOR_0 begin0, - ITERATOR_0 end0, - ITERATOR_1 begin1, - const bool ignoreDefaultLabel -) -{ - size_t N; - return matchingPairs(begin0, end0, begin1, ignoreDefaultLabel, N); -} - -template -size_t -matchingPairs -( - ITERATOR_0 begin0, - ITERATOR_0 end0, - ITERATOR_1 begin1, - const bool ignoreDefaultLabel, - size_t& N // output: number of elements which have a non-zero label in both partitions -) -{ - typedef typename std::iterator_traits::value_type Label0; - typedef typename std::iterator_traits::value_type Label1; - typedef std::pair Pair; - typedef std::map OverlapMatrix; - typedef std::map RowSumMap; - typedef std::map ColumnSumMap; - - OverlapMatrix n; - RowSumMap rowSum; - ColumnSumMap columnSum; - if(ignoreDefaultLabel) { - N = 0; - for(; begin0 != end0; ++begin0, ++begin1) { - if(*begin0 != Label0() && *begin1 != Label1()) { - ++n[Pair(*begin0, *begin1)]; - ++rowSum[*begin0]; - ++columnSum[*begin1]; - ++N; - } + falseJoins_ /= 2; + falseCuts_ /= 2; + + trueCuts_ = pairs() - joinsInPrediction() - falseCuts_; + } + + size_t elements() const + { return elements_; } + size_t pairs() const + { return elements_ * (elements_ - 1) / 2; } + + size_t trueJoins() const + { return trueJoins_; } + size_t trueCuts() const + { return trueCuts_; } + size_t falseJoins() const + { return falseJoins_; } + size_t falseCuts() const + { return falseCuts_; } + + size_t joinsInPrediction() const + { return trueJoins_ + falseJoins_; } + size_t cutsInPrediction() const + { return trueCuts_ + falseCuts_; } + size_t joinsInTruth() const + { return trueJoins_ + falseCuts_; } + size_t cutsInTruth() const + { return trueCuts_ + falseJoins_; } + + value_type recallOfCuts() const + { + if(cutsInTruth() == 0) + return 1; + else + return static_cast(trueCuts()) / cutsInTruth(); } - } - else { - N = std::distance(begin0, end0); - for(; begin0 != end0; ++begin0, ++begin1) { - ++n[Pair(*begin0, *begin1)]; - ++rowSum[*begin0]; - ++columnSum[*begin1]; + value_type precisionOfCuts() const + { + if(cutsInPrediction() == 0) + return 1; + else + return static_cast(trueCuts()) / cutsInPrediction(); } - } - size_t A = 0.0; - for(typename OverlapMatrix::const_iterator it = n.begin(); it != n.end(); ++it) { - A += (it->second) * (it->second - 1); - } - size_t B = N * N; - for(typename OverlapMatrix::const_iterator it = n.begin(); it != n.end(); ++it) { - B += it->second * it->second; - } - for(typename RowSumMap::const_iterator it = rowSum.begin(); it != rowSum.end(); ++it) { - B -= it->second * it->second; - } - for(typename ColumnSumMap::const_iterator it = columnSum.begin(); it != columnSum.end(); ++it) { - B -= it->second * it->second; - } - return (A + B) / 2; -} - -template -inline double -randIndexBruteForce -( - ITERATOR_0 begin0, - ITERATOR_0 end0, - ITERATOR_1 begin1, - const bool ignoreDefaultLabel -) -{ - size_t N; - const size_t n = matchingPairsBruteForce(begin0, end0, begin1, ignoreDefaultLabel, N); - if(N == 0) { - throw std::runtime_error("No element is labeled in both partitions."); - } - else { - return static_cast(n) * 2 / static_cast(N * (N-1)); - } -} - -template -inline double -randIndex -( - ITERATOR_0 begin0, - ITERATOR_0 end0, - ITERATOR_1 begin1, - const bool ignoreDefaultLabel -) -{ - size_t N; - const size_t n = matchingPairs(begin0, end0, begin1, ignoreDefaultLabel, N); - if(N == 0) { - throw std::runtime_error("No element is labeled in both partitions."); - } - else { - return static_cast(n) * 2 / static_cast(N * (N-1)); - } -} - -template -double -variationOfInformation -( - ITERATOR_0 begin0, - ITERATOR_0 end0, - ITERATOR_1 begin1, - const bool ignoreDefaultLabel -) -{ - typedef typename std::iterator_traits::value_type Label0; - typedef typename std::iterator_traits::value_type Label1; - typedef std::pair Pair; - typedef std::map PMatrix; - typedef std::map PVector0; - typedef std::map PVector1; - - // count - size_t N = std::distance(begin0, end0); - PMatrix pjk; - PVector0 pj; - PVector1 pk; - if(ignoreDefaultLabel) { - N = 0; - for(; begin0 != end0; ++begin0, ++begin1) { - if(*begin0 != Label0() && *begin1 != Label1()) { + + value_type recallOfJoins() const + { + if(joinsInTruth() == 0) + return 1; + else + return static_cast(trueJoins()) / joinsInTruth(); + } + value_type precisionOfJoins() const + { + if(joinsInPrediction() == 0) + return 1; + else + return static_cast(trueJoins()) / joinsInPrediction(); + } + + value_type error() const + { return static_cast(falseJoins() + falseCuts()) / pairs(); } + value_type index() const + { return static_cast(trueJoins() + trueCuts()) / pairs(); } + +private: + size_t elements_; + size_t trueJoins_ { size_t() }; + size_t trueCuts_ { size_t() }; + size_t falseJoins_ { size_t() }; + size_t falseCuts_ { size_t() }; +}; + +template +class VariationOfInformation { +public: + typedef T value_type; + + template + VariationOfInformation(ITERATOR_TRUTH begin0, ITERATOR_TRUTH end0, ITERATOR_PRED begin1, bool ignoreDefaultLabel = false) + { + typedef typename std::iterator_traits::value_type Label0; + typedef typename std::iterator_traits::value_type Label1; + typedef std::pair Pair; + typedef std::map PMatrix; + typedef std::map PVector0; + typedef std::map PVector1; + + // count + size_t N = std::distance(begin0, end0); + + PMatrix pjk; + PVector0 pj; + PVector1 pk; + + if (ignoreDefaultLabel) + { + N = 0; + + for (; begin0 != end0; ++begin0, ++begin1) + if (*begin0 != Label0() && *begin1 != Label1()) + { + ++pj[*begin0]; + ++pk[*begin1]; + ++pjk[Pair(*begin0, *begin1)]; + ++N; + } + } + else + for (; begin0 != end0; ++begin0, ++begin1) + { ++pj[*begin0]; ++pk[*begin1]; ++pjk[Pair(*begin0, *begin1)]; - ++N; } + + // normalize + for (auto& p : pj) + p.second /= N; + + for (auto& p : pk) + p.second /= N; + + for (auto& p : pjk) + p.second /= N; + + // compute information + auto H0 = value_type(); + for (auto const& p : pj) + H0 -= p.second * std::log2(p.second); + + auto H1 = value_type(); + for (auto const& p : pk) + H1 -= p.second * std::log2(p.second); + + auto I = value_type(); + for (auto const& p : pjk) + { + auto j = p.first.first; + auto k = p.first.second; + auto pjk_here = p.second; + auto pj_here = pj[j]; + auto pk_here = pk[k]; + + I += pjk_here * std::log2( pjk_here / (pj_here * pk_here) ); } - } - else { - for(; begin0 != end0; ++begin0, ++begin1) { - ++pj[*begin0]; - ++pk[*begin1]; - ++pjk[Pair(*begin0, *begin1)]; - } - } - // normalize - for(typename PVector0::iterator it = pj.begin(); it != pj.end(); ++it) { - it->second /= N; - } - for(typename PVector1::iterator it = pk.begin(); it != pk.end(); ++it) { - it->second /= N; - } - for(typename PMatrix::iterator it = pjk.begin(); it != pjk.end(); ++it) { - it->second /= N; + value_ = H0 + H1 - 2.0 * I; + precision_ = H1 - I; + recall_ = H0 - I; } - // compute information - double H0 = 0.0; - for(typename PVector0::const_iterator it = pj.begin(); it != pj.end(); ++it) { - H0 -= it->second * std::log(it->second); + value_type value() const + { + return value_; } - double H1 = 0.0; - for(typename PVector1::const_iterator it = pk.begin(); it != pk.end(); ++it) { - H1 -= it->second * std::log(it->second); + + value_type valueFalseCut() const + { + return precision_; } - double I = 0.0; - for(typename PMatrix::const_iterator it = pjk.begin(); it != pjk.end(); ++it) { - const Label0 j = it->first.first; - const Label1 k = it->first.second; - const double pjk_here = it->second; - const double pj_here = pj[j]; - const double pk_here = pk[k]; - I += pjk_here * std::log( pjk_here / (pj_here * pk_here) ); + + value_type valueFalseJoin() const + { + return recall_; } - return H0 + H1 - 2.0 * I; -} +private: + value_type value_; + value_type precision_; + value_type recall_; +}; } // namespace andres diff --git a/src/mex/andres_rand_index.cxx b/src/mex/andres_rand_index.cxx index 8ea68fa..f1730b7 100644 --- a/src/mex/andres_rand_index.cxx +++ b/src/mex/andres_rand_index.cxx @@ -20,7 +20,7 @@ matlabRandIndexHelper( const size_t n = mxGetNumberOfElements(array0); const T* p0 = static_cast(mxGetData(array0)); const T* p1 = static_cast(mxGetData(array1)); - return andres::randIndex(p0, p0 + n, p1, true); // ignores default label 0 + return andres::RandError<>(p0, p0 + n, p1, true).index(); // ignores default label 0 } void mexFunction( diff --git a/src/mex/andres_variation_of_information.cxx b/src/mex/andres_variation_of_information.cxx index bf2e158..2665e62 100644 --- a/src/mex/andres_variation_of_information.cxx +++ b/src/mex/andres_variation_of_information.cxx @@ -20,7 +20,7 @@ matlabVIHelper( const size_t n = mxGetNumberOfElements(array0); const T* p0 = static_cast(mxGetData(array0)); const T* p1 = static_cast(mxGetData(array1)); - return andres::variationOfInformation(p0, p0 + n, p1, true); // ignores default label 0 + return andres::VariationOfInformation<>(p0, p0 + n, p1, true).value(); // ignores default label 0 } void mexFunction( diff --git a/src/unittest/partition-comparison.cxx b/src/unittest/partition-comparison.cxx index b9fbc67..743dd56 100644 --- a/src/unittest/partition-comparison.cxx +++ b/src/unittest/partition-comparison.cxx @@ -11,66 +11,137 @@ throw std::logic_error(s.str().c_str()); \ } +// implementation +template +inline size_t +matchingPairsBruteForce +( + ITERATOR_0 begin0, + ITERATOR_0 end0, + ITERATOR_1 begin1, + const bool ignoreDefaultLabel = false +) +{ + size_t N; + return matchingPairsBruteForce(begin0, end0, begin1, ignoreDefaultLabel, N); +} + +template +size_t +matchingPairsBruteForce +( + ITERATOR_0 begin0, + ITERATOR_0 end0, + ITERATOR_1 begin1, + const bool ignoreDefaultLabel, + size_t& N // output: number of elements which have a non-zero label in both partitions +) +{ + typedef typename std::iterator_traits::value_type Label0; + typedef typename std::iterator_traits::value_type Label1; + + size_t AB = 0; + if(ignoreDefaultLabel) { + N = 0; + ITERATOR_1 it1 = begin1; + for(ITERATOR_0 it0 = begin0; it0 != end0; ++it0, ++it1) { + if(*it0 != Label0() && *it1 != Label1()) { + ++N; + ITERATOR_1 it1b = it1 + 1; + for(ITERATOR_0 it0b = it0 + 1; it0b != end0; ++it0b, ++it1b) { + if(*it0b != Label0() && *it1b != Label1()) { + if((*it0 == *it0b && *it1 == *it1b) || (*it0 != *it0b && *it1 != *it1b)) { + ++AB; + } + } + } + } + } + } + else { + N = std::distance(begin0, end0); + ITERATOR_1 it1 = begin1; + for(ITERATOR_0 it0 = begin0; it0 != end0; ++it0, ++it1) { + ITERATOR_1 it1b = it1 + 1; + for(ITERATOR_0 it0b = it0 + 1; it0b != end0; ++it0b, ++it1b) { + if( (*it0 == *it0b && *it1 == *it1b) || (*it0 != *it0b && *it1 != *it1b) ) { + ++AB; + } + } + } + + } + return AB; +} + +template +inline double +randIndexBruteForce +( + ITERATOR_0 begin0, + ITERATOR_0 end0, + ITERATOR_1 begin1, + const bool ignoreDefaultLabel = false +) +{ + size_t N; + const size_t n = matchingPairsBruteForce(begin0, end0, begin1, ignoreDefaultLabel, N); + if(N == 0) { + throw std::runtime_error("No element is labeled in both partitions."); + } + else { + return static_cast(n) * 2 / static_cast(N * (N-1)); + } +} + int main() { { size_t p0[4] = {0, 1, 1, 1}; size_t p1[4] = {0, 1, 2, 2}; - TEST(andres::matchingPairs(p0, p0+4, p1) == 4); - TEST(andres::matchingPairs(p0, p0+4, p1, true) == 1); - - TEST(andres::randIndex(p0, p0+4, p1) == 2.0 / 3.0); - TEST(andres::randIndex(p0, p0+4, p1, true) == 1.0 / 3.0); - TEST(andres::randIndex(p0, p0+4, p1) == andres::randIndexBruteForce(p0, p0+4, p1)); + TEST(andres::RandError<>(p0, p0+4, p1).index() == 2.0 / 3.0); + TEST(andres::RandError<>(p0, p0+4, p1, true).index() == 1.0 / 3.0); + TEST(andres::RandError<>(p0, p0+4, p1).index() == randIndexBruteForce(p0, p0+4, p1)); } { size_t p0[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; size_t p1[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - TEST(andres::matchingPairs(p0, p0+10, p1) == 0); - TEST(andres::matchingPairs(p0, p0+10, p1, true) == 0); - TEST(andres::matchingPairs(p0, p0+10, p1) == andres::matchingPairsBruteForce(p0, p0+10, p1)); - TEST(andres::matchingPairs(p0, p0+10, p1, true) == andres::matchingPairsBruteForce(p0, p0+10, p1, true)); - - TEST(andres::randIndex(p0, p0+10, p1) == 0.0); - TEST(andres::randIndex(p0, p0+10, p1) == andres::randIndexBruteForce(p0, p0+10, p1)); + TEST(andres::RandError<>(p0, p0+10, p1).index() == 0.0); + TEST(andres::RandError<>(p0, p0+10, p1).index() == randIndexBruteForce(p0, p0+10, p1)); try { - andres::randIndex(p0, p0+10, p1, true); + andres::RandError<>(p0, p0+10, p1, true).index(); TEST(false); } catch(std::runtime_error& e) { } - TEST(andres::variationOfInformation(p0, p0+10, p1, true) == 0.0); + TEST(andres::VariationOfInformation<>(p0, p0+10, p1, true).value() == 0.0); } { size_t p0[10] = {0, 0, 0, 0, 0, 1, 1, 1, 1, 1}; size_t p1[10] = {6, 6, 6, 6, 6, 7, 7, 7, 7, 7}; - TEST(andres::matchingPairs(p0, p0+10, p1) == 45); - TEST(andres::matchingPairs(p0, p0+10, p1, true) == 10); - TEST(andres::matchingPairs(p0, p0+10, p1) == andres::matchingPairsBruteForce(p0, p0+10, p1)); - TEST(andres::matchingPairs(p0, p0+10, p1, true) == andres::matchingPairsBruteForce(p0, p0+10, p1, true)); + TEST(andres::RandError<>(p0, p0+10, p1).index() == 1.0); + TEST(andres::RandError<>(p0, p0+10, p1, true).index() == 1.0); + TEST(andres::RandError<>(p0, p0+10, p1).index() == randIndexBruteForce(p0, p0+10, p1)); + TEST(andres::RandError<>(p0, p0+10, p1, true).index() == randIndexBruteForce(p0, p0+10, p1, true)); - TEST(andres::randIndex(p0, p0+10, p1) == 1.0); - TEST(andres::randIndex(p0, p0+10, p1, true) == 1.0); - TEST(andres::randIndex(p0, p0+10, p1) == andres::randIndexBruteForce(p0, p0+10, p1)); - TEST(andres::randIndex(p0, p0+10, p1, true) == andres::randIndexBruteForce(p0, p0+10, p1, true)); - - TEST(andres::variationOfInformation(p0, p0+10, p1) == 0.0) - TEST(andres::variationOfInformation(p0, p0+10, p1, true) == 0.0) + TEST(andres::VariationOfInformation<>(p0, p0+10, p1).value() == 0.0) + TEST(andres::VariationOfInformation<>(p0, p0+10, p1, true).value() == 0.0) } { size_t p0[10] = {0, 0, 0, 0, 4, 4, 4, 4, 8, 9}; size_t p1[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - TEST(andres::matchingPairs(p0, p0+10, p1) == 33); - TEST(andres::matchingPairs(p0, p0+10, p1, true) == 9); - TEST(andres::matchingPairs(p0, p0+10, p1) == andres::matchingPairsBruteForce(p0, p0+10, p1)); - TEST(andres::matchingPairs(p0, p0+10, p1, true) == andres::matchingPairsBruteForce(p0, p0+10, p1, true)); + TEST(andres::RandError<>(p0, p0+10, p1).index() == randIndexBruteForce(p0, p0+10, p1)); + TEST(andres::RandError<>(p0, p0+10, p1, true).index() == randIndexBruteForce(p0, p0+10, p1, true)); + + TEST(andres::RandError<>(p0, p0+10, p1).index() == andres::RandError<>(p1, p1+10, p0).index()); + TEST(andres::RandError<>(p0, p0+10, p1, true).index() == andres::RandError<>(p1, p1+10, p0, true).index()); - TEST(andres::randIndex(p0, p0+10, p1) == andres::randIndexBruteForce(p0, p0+10, p1)); - TEST(andres::randIndex(p0, p0+10, p1, true) == andres::randIndexBruteForce(p0, p0+10, p1, true)); + TEST(andres::VariationOfInformation<>(p0, p0+10, p1).value() == andres::VariationOfInformation<>(p1, p1+10, p0).value()); + TEST(andres::VariationOfInformation<>(p0, p0+10, p1, true).value() == andres::VariationOfInformation<>(p1, p1+10, p0, true).value()); } return 0;