Element selection with boolean vector / matrix

Issue #285 new
Johannes Czech created an issue

Hello @Klaus Iglberger ,

non-contiguous views have been introduced into Blaze 3.3 in the form of the function elements() which allows selecting elements based on a given list of indices:

blaze::DynamicVector<int> x{ 3, 2, 5 };
const std::initializer_list<size_t> list{ 0UL, 2UL };
auto e2 = elements( x, { 0UL, 2UL });                    // Results in { 3, 5 }
auto e3 = elements( x, list );                           // Results in { 3, 5 }

I am interested in selecting elements based on a boolean vector which is e.g. the result of a (custom) logical vector operation:

Would it make sense to overload elements() for this use case?

blaze::DynamicVector<bool> sel{ true, false, true };
auto e4 = elements( x, sel );                           // should result in { 3, 5 }

// e.g. in combination with logical operation
blaze::DynamicVector<bool> v1{ true, true, true };
blaze::DynamicVector<bool> v2{ true, false, true };
auto e5 = elements( x, v1 && v2 );                      // should result in { 3, 5 }

Or would you prefer overloading the operator[] or to create a new function instead?

auto e5 = x[sel]                                       // should result in { 3, 5 }

This issue is related to

Best regards,

~Johannes Czech

Comments (5)

  1. Klaus Iglberger

    Hi Johannes!

    Thanks for your proposal. It is indeed possible to overload the elements() function for boolean initializer_lists and vectors:

    template< typename VT         // Type of the vector
            , typename... REAs >  // Optional arguments
    inline decltype(auto) elements( VT&& vector, blaze::initializer_list<bool> selection, REAs... args )
    {
       BLAZE_FUNCTION_TRACE;
    
       blaze::SmallArray<size_t,8UL> indices;
    
       auto begin( cbegin( selection ) );
       for( size_t i=0UL; i<selection.size(); ++i, ++begin ) {
          if( *begin )
             indices.pushBack( i );
       }
    
       return elements( std::forward<VT>( vector ), indices.begin(), indices.size(), args... );
    }
    
    template< typename VT         // Type of the vector
            , typename VT2        // Type of the selection vector
            , bool TF2            // Transpose flag of the selection vector
            , typename... REAs >  // Optional arguments
    inline decltype(auto) elements( VT&& vector, const blaze::DenseVector<VT2,TF2>& selection, REAs... args )
    {
       BLAZE_FUNCTION_TRACE;
    
       blaze::SmallArray<size_t,8UL> indices;
    
       auto begin( cbegin( selection ) );
       for( size_t i=0UL; i<(~selection).size(); ++i, ++begin ) {
          if( *begin )
             indices.pushBack( i );
       }
    
       return elements( std::forward<VT>( vector ), indices.begin(), indices.size(), args... );
    }
    

    These overloads can now be used as intended:

    #include <iostream>
    #include <blaze/Math.h>
    
    int main()
    {
       blaze::DynamicVector<int> a{ 1, 2, 3, 4 };
       blaze::DynamicVector<bool> s1{ false,  true, false, false };
       blaze::DynamicVector<bool> s2{ false, false, false,  true };
    
       auto e1 = elements( a, { false, true, false, true } );  // Element selection via 'initializer_list<bool>'
       auto e2 = elements( a, s1 || s2 );                      // Element selection via logical operation
    
       std::cout << "\n e1 = " << trans(e1) << "\n";    // Prints { 2 4 }
       std::cout << "\n e2 = " << trans(e2) << "\n\n";  // Prints { 2 4 }
    }
    

    Is this what you have in mind?

    Best regards,

    Klaus!

  2. Johannes Czech reporter

    Thank you for the reply.
    That is exactly what I had in mind and this could also be useful for other people.

    However, I wondered if it were better to avoid building a helper list of indices in this case similar to this implementation:

    template< typename VT         // Type of the vector
            , typename VT2        // Type of the selection vector
            , bool TF2            // Transpose flag of the selection vector
            , typename... REAs >  // Optional arguments
    inline decltype(auto) elements2( VT&& vector, const blaze::DenseVector<VT2,TF2>& selection, REAs... args )
    {
       BLAZE_FUNCTION_TRACE;
       blaze::SmallArray<float, 8UL> out;
    
       auto it_selection( cbegin( selection ) );
       auto it_vec( cbegin( vector ) );
       for( ; it_selection != cend(selection); ++it_vec, ++it_selection ) {
          if( *it_selection )
             out.pushBack( *it_vec );
       }
       return out;
    }
    

    Here is a small runtime comparison:

    size_t it = 1e7;
    
    blaze::DynamicVector<float> a{ 1, 2, 3, 4 };
    blaze::DynamicVector<bool> sel{ true, false, true, false };
    
    std::chrono::steady_clock::time_point start_el = std::chrono::steady_clock::now();
    for (size_t i = 0; i < it; ++i) {
        auto e1 = elements( a, sel );
    }
    std::chrono::steady_clock::time_point end_el = std::chrono::steady_clock::now();
    std::cout << "Elapsed time elements(): \t" << std::chrono::duration_cast<std::chrono::milliseconds>(end_el - start_el).count() << "ms" << std::endl;
    
    std::chrono::steady_clock::time_point start_el2 = std::chrono::steady_clock::now();
    for (size_t i = 0; i < it; ++i) {
        auto e2 = elements2( a, sel );
    }
    std::chrono::steady_clock::time_point end_el2 = std::chrono::steady_clock::now();
    std::cout << "Elapsed time elements2():\t" << std::chrono::duration_cast<std::chrono::milliseconds>(end_el2 - start_el2).count() << "ms" << std::endl;
    

    Unfortunately, elements2() doesn't behave exactly as elements() because it returns a deep copy instead, but it is still faster in terms of runtime in this case:

    Elapsed time elements():    134ms
    Elapsed time elements2():   66ms
    

    Best regards,
    ~Johannes Czech

  3. Log in to comment