Is DenseMatrix a Submatrix and is DenseMatrix.submatrix() also a DenseMatrix?

Issue #234 resolved
Mario Emmenlauer created an issue

Dear Klaus and all,

thanks for all the nice work on blaze! I've mostly used blazemark and just started looking into blaze. One question that came up immediately was the existence of the dedicated Submatrix class. I've used vigra and blitz++ before and they both can represent a view as an array. So the two types can be used mostly interchangeably. Is the same true for blaze::Submatrix? From the documentation I was under the impression that Submatrix is a dedicated type, and assigning a Submatrix to i.e. a DenseMatrix creates a deep copy(?)

Is my understanding correct? And may I kindly ask why this is the case?

In vigra and blitz++ I used to use arrays as function arguments, and it is up to the caller to pass a dense array or a view. Is the same possible in blaze?

All the best, Mario

Comments (4)

  1. Klaus Iglberger

    Hi Mario!

    I'm not sure if I understand all aspects of your question correctly. Therefore allow me to try to explain submatrices and Blaze views in general with different words than the documentation in the wiki. For that purpose I will draw parallels to std::string_view. The following code snippet demonstrates the initialisation of and the assignment to a std::string_view:

    std::string s1( "The Blaze C++ Math Library" );
    std::string_view sv1( s1.data()+4, 5 );  // Represents the substring "Blaze"
    std::string_view sv2;
    sv2 = sv1;  // Changes the view sv2; sv1 and sv2 are now identical
    

    A std::string_view represents a pointer to some (sub-)string, i.e. assigning another std::string_view changes the view itself. It doesn't own and doesn't allow modification of the (sub-)string.

    In contrast, a Blaze view represents a reference to some part of a Blaze vector or matrix. That means that during construction of a view you bind it to some specific vector or matrix and henceforth it can only be used to access the referenced elements:

    blaze::DynamicMatrix<int> A{ { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
    auto sm1 = submatrix( A, 0, 0, 1, 2 );  // Represents the two elements { 1, 2 }
    auto sm2 = submatrix( A, 2, 1, 1, 2 );  // Represents the two elements { 8, 9 }
    sm1 = sm2;  // Does not change the view, but changes { 1, 2 } to { 8, 9 }
    

    A Blaze view never owns, but allows modifications of the underlying vector or matrix (depending on the constness of the given matrix and the view).

    Since a view represents a specific part of a data structure, it can be used as an alias of this part in all functions that would also accept the data structure itself:

    template< typename MT, bool SO >  // MT = Matrix Type of the dense matrix, SO = Storage order of the dense matrix
    void foo( const DenseMatrix<MT,SO>& dm )
    {}
    
    foo( sm1 );  // Works, since sm1 represents a part of a dense matrix and is there a dense matrix itself
    
    CompressedMatrix<int> B{ { 1, 0, 3 }, { 0, 5, 0 }, { 7, 0, 9 } };
    auto sm3 = submatrix( B, 1, 1, 1, 2 );  // Represents the elements { 5, 0 } in the compressed (sparse) matrix
    
    foo( sm3 );  // Compilation error: sm3 represents a part of a sparse matrix and is therefore a sparse matrix itself
    

    The difference in semantics between std::string_view (pointer-like) and the Blaze views (reference-like) can be explained by the underlying inheritance hierarchy (DenseMatrix, SparseMatrix, etc.). If Blaze views would behave like pointers and would allow you to change the view, you would have different behavior depending on whether you use a concrete vector/matrix type or a view:

    template< typename MT1, bool SO1, typename MT2, bool SO2 >
    void assign( DenseMatrix<MT1,SO1>& lhs, const DenseMatrix<MT2,SO2>& rhs )
    {
       (~lhs) = (~rhs);  // Assigning the right-hand side dense matrix to the left-hand side dense matrix
                         // ~ means cast back to the actual type, i.e. MT1 and MT2 (CRTP)
    }
    
    DynamicMatrix<int> C{ { 99, 99 } };
    assign( sm1, C   );  // Would assign { 99, 99 } to the two elements represented by sm1
    assign( sm1, sm2 );  // Would change the view, would not assign to the underlying elements
    

    This mixed semantics would render views a problem in generic code. As a consequence, the semantics of views (e.g. Submatrix) needs to resemble the behavior of concrete vector and matrix types (e.g. DynamicMatrix), which is achieved by considering it as a reference to a part of a vector/matrix instead of as a pointer.

    I hope this answers your questions. If yes, feel free to resolve the issue (please don't close), if no please feel free to ask again.

    Best regards,

    Klaus!

  2. Mario Emmenlauer reporter

    Dear Klaus,

    thanks for this awesome clarification! I see now that "a view represents a specific part of a data structure," and " it can be used as an alias of this part in all functions that would also accept the data structure itself". This is exactly what I was looking for.

    Just to be 150% sure: When using a view as an argument to a method that accepts the data structure itself, the view will not be transformed into the data structure but remain a view (not owning the data), correct? So to speak with your example:

    template< typename MT, bool SO >  // MT = Matrix Type of the dense matrix, SO = Storage order of the dense matrix
    void foo( const DenseMatrix<MT,SO>& dm )
    {
        dm(0,0) = 42; // Assigns the first element of A the value 42(?)
    
        DenseMatrix<MT,SO> dm2 = dm; // Creates a deep copy of the view(?)
    }
    
    foo( sm1 ); 
    

    I'm so sorry that I could not understand this from the documentation. Maybe your clarification could be added to the section on Submatrix?

  3. Klaus Iglberger

    Hi Mario!

    You are correct, the view will not be transformed into any other data structure, but remains a view (not owning data).

    template< typename MT, bool SO >  // MT = Matrix Type of the dense matrix, SO = Storage order of the dense matrix
    void foo( const DenseMatrix<MT,SO>& dm )
    {
        (~dm)(0,0) = 42; // Assigns the first element of A the value 42(?) -> Yes, but you have to convert back to 'MT' via ~ (Blaze implementation details).
    
        MT dm2 = dm; // Creates a deep copy of the view(?) -> Yes, but since 'DenseMatrix' is only a base class you would have to select a concrete matrix type, e.g. 'MT' itself (CRTP)
    }
    
    foo( sm1 ); 
    

    Best regards,

    Klaus!

  4. Log in to comment