Since the question was asked a long time ago, I would give a detailed overview answer.
The first question is how the assembled matrix $A$ will be used later, as the construction of the matrix itself is rarely a goal. In the question, it is clearly defined that one needs to have a dense matrix in the end of the day. However, for some purposes, having a dense matrix instead of its factorization into several matrices might be detrimental and should be avoided. The simplest example would be a solution of the system of linear equations using an iterative solver.
Now, if the construction of the dense matrix is unavoidable and\or required by the further algorithms you have several options.
- As clipper pointed out, the entries of the dense matrix $A$ can be manually computed column-by-column by applying matrix-vector products to the columns of the identity matrix: $D(B(D^H(CI_n)))$, where $I_n$ is the $n$th column of the identity matrix. In such a way, you will be able to avoid forming dense intermediate results and take advantage of the sparsity of $D$, $B$, and $C$.
- Depending on the tools you are using, matrix library can already support sparse matrix-matrix products. For example, a commonly used template-based library Eigen will naturally support such products.
- Explicitly use some SparseBLAS implementation. For example, Intel MKL now supports subroutines
mkl_?csrmultcsr
and mkl_?csrmultd
that compute the product of two sparse matrices and store the results in a sparse CSR or dense format, respectively.
Using some SparseBLAS implementation has an advantage of a well-optimized and debugged code for matrix operations, already built-in potential for parallelization. However, benchmarking it against an MVP-based solution would be required for the sparsity patterns and problem sizes you are interested in. Using Eigen-like library is the easiest options, though you will lack a lot of control and can lose some performance, especially if for those particular functions Eigen does not use external BLAS/LAPACK engines and relies only on its own implementations.