Reputation: 85
I'm searching for an equivalent of A=Spdiags(B,d,N,N)in C++. This function extracts the diagonals element of the matrix B by taking the columns of B and placing them along the diagonals specified by the vector d. N N are the size of the output matrix A.
I've searched in Eigen, but it seems that it does not exist.
any ideas?
Upvotes: 1
Views: 1514
Reputation: 85
here is a solution i've made. I've implemented the diagonal(i) because this function is not taken account by my eigen version (how can i know which version i use?). I obtain a good results with this, but i don't know if can more optimize it :
void spdiags(Eigen::SparseMatrix<double> A)
{
//Extraction of the diagnols before the main diagonal
vector<double> vec1; int flag=0;int l=0;
int i=0; int j=0; vector<vector<double> > diagD;
vector<vector<double> > diagG; int z=0; int z1=0;
for(int i=0;i<A.rows();i++)
{l=i;
do
{
if(A.coeff(l,j)!=0)
flag=1;
vec1.push_back(A.coeff(l,j));
l++;j++;
}while(l<A.rows() && j<A.cols());
if(flag==1) {diagG.resize(diagG.size()+1);diagG[z]=vec1; z++; }
vec1.clear(); l=0;j=0; flag=0; cout<<endl;
}
flag=0;z=0; vec1.clear();
// Extraction of the diagonals after the main diagonal
for(int i=1;i<A.cols();i++)
{l=i;
do
{
if(A.coeff(j,l)!=0)
flag=1;
vec1.push_back(A.coeff(j,l));
l++;j++;
}while(l<A.cols() && j<A.rows());
if(flag==1) {diagD.resize(diagD.size()+1);diagD[z]=vec1; z++; }
vec1.clear(); l=0;j=0; flag=0; cout<<endl;
}
// End extraction of the diagonals
Eigen::VectorXi d = Eigen::VectorXi::Zero(A.rows() + A.cols() - 1);
for (int k=0; k < A.outerSize(); ++k)
{
for (SparseMatrix<double>::InnerIterator it(A,k); it; ++it)
{
d(it.col() - it.row() + A.rows() - 1) = 1;
}
}
int num_diags = d.sum();
Eigen::MatrixXd B(std::min(A.cols(), A.rows()), num_diags);
// fill B with diagonals
Eigen::ArrayXd v;
int B_col_idx = 0;
int B_row_sign = A.rows() >= A.cols() ? 1 : -1;
int indG=diagG.size()-1; int indD=0;
for (int i = 1 - A.rows(); i <=A.cols() - 1; i++)
{
if (d(i + A.rows() - 1))
{
if(i<1)
{ v.resize(diagG[indG].size());
for(int i=0;i<diagG[indG].size();i++)
{
v(i)=diagG[indG][i];
}
int B_row_start = std::max(0, B_row_sign * i);
B.block(B_row_start, B_col_idx, diagG[indG].size(), 1) = v;
B_col_idx++;
indG--;
}
else
{
v.resize(diagD[indD].size());
for(int i=0;i<diagD[indD].size();i++)
{
v(i)=diagD[indD][i] ;
}
int B_row_start = std::max(0, B_row_sign * i);
B.block(B_row_start, B_col_idx, diagD[indD].size(), 1) = v;
B_col_idx++;
indD++;
}
}
}
cout<<B<<endl; //the result of the function
}//end of the function
Upvotes: 0
Reputation: 3062
There's no built in method as far as I know but it's not too hard to do this by building a new matrix via indices. Notice that the k
th diagonal runs from index (max(1, 1-k)
, max(1, 1-k)+k
) to (min(m, n-k)
, min(m, n-k)+k
)
template <typename Scalar>
Eigen::SparseMatrix<Scalar> spdiags(const Matrix<Scalar, -1, -1>& B, const Eigen::Matrix<int, -1, 1>& d, size_t m, size_t n) {
Eigen::SparseMatrix<Scalar> A(m,n);
typedef Eigen::Triplet<Scalar> T;
std::vector<T> triplets;
triplets.reserve(std::min(m,n)*d.size());
for (int k = 0; k < d.size(); k++) {
int i_min = std::max(0, -d(k));
int i_max = std::min(m - 1, n - d(k) - 1);
int B_idx_start = m >= n ? d(k) : 0;
for (int i = i_min; i <= i_max; i++) {
triplets.push_back( T(i, i+k, B(B_idx_start + i, k)) );
}
}
A.setFromTriplets(triplets.begin(), triplets.end());
return A;
}
Note I haven't tested this but you get the idea. The first index into B is a little weird but I think it's right.
Other version, spdiags(A):
Eigen::MatrixXd spdiags(const Eigen::SparseMatrix<double>& A) {
// find nonzero diagonals by iterating over all nonzero elements
// d(i) = 1 if the ith diagonal of A contains a nonzero, 0 else
Eigen::VectorXi d = Eigen::VectorXi::Zero(A.rows() + A.cols() - 1);
for (int k=0; k < A.outerSize(); ++k) {
for (SparseMatrix<double>::InnerIterator it(A,k); it; ++it) {
d(it.col() - it.row() + A.rows() - 1) = 1;
}
}
int num_diags = d.sum();
Eigen::MatrixXd B(std::min(A.cols(), A.rows()), num_diags);
// fill B with diagonals
int B_col_idx = 0;
int B_row_sign = A.rows() >= A.cols() ? 1 : -1;
for (int i = 1 - A.rows(); i <= A.cols() - 1; i++) {
if (d(i + A.rows() - 1)) {
const auto& diag = A.diagonal(i);
int B_row_start = std::max(0, B_row_sign * i);
B.block(B_row_start, B_col_idx, diag.size(), 1) = diag;
B_col_idx++;
}
}
return B;
}
same disclaimer: haven't tested, but should work. Replace double
with template <typename Scalar>
as before if you want
Upvotes: 1