@q $Id: kron_prod.cweb 1834 2008-05-18 20:23:54Z kamenik $ @> @q Copyright 2004, Ondra Kamenik @> @ Start of {\tt kron\_prod.cpp} file. @c #include "kron_prod.h" #include "tl_exception.h" #include @<|KronProdDimens| constructor code@>; @<|KronProd::checkDimForMult| code@>; @<|KronProd::kronMult| code@>; @<|KronProdAll::setMat| code@>; @<|KronProdAll::setUnit| code@>; @<|KronProdAll::isUnit| code@>; @<|KronProdAll::multRows| code@>; @<|KronProdIA::mult| code@>; @<|KronProdAI| constructor code@>; @<|KronProdAI::mult| code@>; @<|KronProdIAI::mult| code@>; @<|KronProdAll::mult| code@>; @<|KronProdAllOptim::optimizeOrder| code@>; @ Here we construct Kronecker product dimensions from Kronecker product dimensions by picking a given matrix and all other set to identity. The constructor takes dimensions of $A_1\otimes A_2\otimes\ldots\otimes A_n$, and makes dimensions of $I\otimes A_i\otimes I$, or $I\otimes A_n$, or $A_1\otimes I$ for a given $i$. The identity matrices must fit into the described order. See header file. We first decide what is a length of the resulting dimensions. Possible length is three for $I\otimes A\otimes I$, and two for $I\otimes A$, or $A\otimes I$. Then we fork according to |i|. @<|KronProdDimens| constructor code@>= KronProdDimens::KronProdDimens(const KronProdDimens& kd, int i) : rows((i==0 || i==kd.dimen()-1)? (2):(3)), cols((i==0 || i==kd.dimen()-1)? (2):(3)) { TL_RAISE_IF(i < 0 || i >= kd.dimen(), "Wrong index for pickup in KronProdDimens constructor"); int kdim = kd.dimen(); if (i == 0) { @; } else if (i == kdim-1){ @; } else { @; } } @ The first rows and cols are taken from |kd|. The dimensions of identity matrix is a number of rows in $A_2\otimes\ldots\otimes A_n$ since the matrix $A_1\otimes I$ is the first. @= rows[0] = kd.rows[0]; rows[1] = kd.rows.mult(1, kdim); cols[0] = kd.cols[0]; cols[1] = rows[1]; @ The second dimension is taken from |kd|. The dimensions of identity matrix is a number of columns of $A_1\otimes\ldots A_{n-1}$, since the matrix $I\otimes A_n$ is the last. @= rows[0] = kd.cols.mult(0, kdim-1); rows[1] = kd.rows[kdim-1]; cols[0] = rows[0]; cols[1] = kd.cols[kdim-1]; @ The dimensions of the middle matrix are taken from |kd|. The dimensions of the first identity matrix are a number of columns of $A_1\otimes\ldots\otimes A_{i-1}$, and the dimensions of the last identity matrix are a number of rows of $A_{i+1}\otimes\ldots\otimes A_n$. @= rows[0] = kd.cols.mult(0, i); cols[0] = rows[0]; rows[1] = kd.rows[i]; cols[1] = kd.cols[i]; cols[2] = kd.rows.mult(i+1, kdim); rows[2] = cols[2]; @ This raises an exception if dimensions are bad for multiplication |out = in*this|. @<|KronProd::checkDimForMult| code@>= void KronProd::checkDimForMult(const ConstTwoDMatrix& in, const TwoDMatrix& out) const { int my_rows; int my_cols; kpd.getRC(my_rows, my_cols); TL_RAISE_IF(in.nrows() != out.nrows() || in.ncols() != my_rows, "Wrong dimensions for KronProd in KronProd::checkDimForMult"); } @ Here we Kronecker multiply two given vectors |v1| and |v2| and store the result in preallocated |res|. @<|KronProd::kronMult| code@>= void KronProd::kronMult(const ConstVector& v1, const ConstVector& v2, Vector& res) { TL_RAISE_IF(res.length() != v1.length()*v2.length(), "Wrong vector lengths in KronProd::kronMult"); res.zeros(); for (int i = 0; i < v1.length(); i++) { Vector sub(res, i*v2.length(), v2.length()); sub.add(v1[i], v2); } } @ @<|KronProdAll::setMat| code@>= void KronProdAll::setMat(int i, const TwoDMatrix& m) { matlist[i] = &m; kpd.setRC(i, m.nrows(), m.ncols()); } @ @<|KronProdAll::setUnit| code@>= void KronProdAll::setUnit(int i, int n) { matlist[i] = NULL; kpd.setRC(i, n, n); } @ @<|KronProdAll::isUnit| code@>= bool KronProdAll::isUnit() const { int i = 0; while (i < dimen() && matlist[i] == NULL) i++; return i == dimen(); } @ Here we multiply $B\cdot(I\otimes A)$. If $m$ is a dimension of the identity matrix, then the product is equal to $B\cdot\hbox{diag}_m(A)$. If $B$ is partitioned accordingly, then the result is $[B_1A, B_2A,\ldots B_mA]$. Here, |outi| are partitions of |out|, |ini| are const partitions of |in|, and |id_cols| is $m$. We employ level-2 BLAS. @<|KronProdIA::mult| code@>= void KronProdIA::mult(const ConstTwoDMatrix& in, TwoDMatrix& out) const { checkDimForMult(in, out); int id_cols = kpd.cols[0]; ConstTwoDMatrix a(mat); for (int i = 0; i < id_cols; i++) { TwoDMatrix outi(out, i*a.ncols(), a.ncols()); ConstTwoDMatrix ini(in, i*a.nrows(), a.nrows()); outi.mult(ini, a); } } @ Here we construct |KronProdAI| from |KronProdIAI|. It is clear. @<|KronProdAI| constructor code@>= KronProdAI::KronProdAI(const KronProdIAI& kpiai) : KronProd(KronProdDimens(2)), mat(kpiai.mat) { kpd.rows[0] = mat.nrows(); kpd.cols[0] = mat.ncols(); kpd.rows[1] = kpiai.kpd.rows[2]; kpd.cols[1] = kpiai.kpd.cols[2]; } @ Here we multiply $B\cdot(A\otimes I)$. Let the dimension of the matrix $A$ be $m\times n$, the dimension of $I$ be $p$, and a number of rows of $B$ be $q$. We use the fact that $B\cdot(A\otimes I)=\hbox{reshape}(\hbox{reshape}(B, q, mp)\cdot A, q, np)$. This works only for matrix $B$, whose storage has leading dimension equal to number of rows. For cases where the leading dimension is not equal to the number of rows, we partition the matrix $A\otimes I$ to $m\times n$ square partitions $a_{ij}I$. Therefore, we partition $B$ to $m$ partitions $[B_1, B_2,\ldots,B_m]$. Each partition of $B$ has the same number of columns as the identity matrix. If $R$ denotes the resulting matrix, then it can be partitioned to $n$ partitions $[R_1,R_2,\ldots,R_n]$. Each partition of $R$ has the same number of columns as the identity matrix. Then we have $R_i=\sum a_{ji}B_j$. In code, |outi| is $R_i$, |ini| is $B_j$, and |id_cols| is a dimension of the identity matrix @<|KronProdAI::mult| code@>= void KronProdAI::mult(const ConstTwoDMatrix& in, TwoDMatrix& out) const { checkDimForMult(in, out); int id_cols = kpd.cols[1]; ConstTwoDMatrix a(mat); if (in.getLD() == in.nrows()) { ConstTwoDMatrix in_resh(in.nrows()*id_cols, a.nrows(), in.getData().base()); TwoDMatrix out_resh(in.nrows()*id_cols, a.ncols(), out.getData().base()); out_resh.mult(in_resh, a); } else { out.zeros(); for (int i = 0; i < a.ncols(); i++) { TwoDMatrix outi(out, i*id_cols, id_cols); for (int j = 0; j < a.nrows(); j++) { ConstTwoDMatrix ini(in, j*id_cols, id_cols); outi.add(a.get(j,i), ini); } } } } @ Here we multiply $B\cdot(I\otimes A\otimes I)$. If $n$ is a dimension of the first identity matrix, then we multiply $B\cdot\hbox{diag}_n(A\otimes I)$. So we partition $B$ and result $R$ accordingly, and multiply $B_i\cdot(A\otimes I)$, which is in fact |KronProdAI::mult|. Note that number of columns of partitions of $B$ are number of rows of $A\otimes I$, and number of columns of $R$ are number of columns of $A\otimes I$. In code, |id_cols| is $n$, |akronid| is a Kronecker product object of $A\otimes I$, and |in_bl_width|, and |out_bl_width| are rows and cols of $A\otimes I$. @<|KronProdIAI::mult| code@>= void KronProdIAI::mult(const ConstTwoDMatrix& in, TwoDMatrix& out) const { checkDimForMult(in, out); int id_cols = kpd.cols[0]; KronProdAI akronid(*this); int in_bl_width; int out_bl_width; akronid.kpd.getRC(in_bl_width, out_bl_width); for (int i = 0; i < id_cols; i++) { TwoDMatrix outi(out, i*out_bl_width, out_bl_width); ConstTwoDMatrix ini(in, i*in_bl_width, in_bl_width); akronid.mult(ini, outi); } } @ Here we multiply $B\cdot(A_1\otimes\ldots\otimes A_n)$. First we multiply $B\cdot(A_1\otimes)$, then this is multiplied by all $I\otimes A_i\otimes I$, and finally by $I\otimes A_n$. If the dimension of the Kronecker product is only 1, then we multiply two matrices in straight way and return. The intermediate results are stored on heap pointed by |last|. A new result is allocated, and then the former storage is deallocated. We have to be careful in cases when last or first matrix is unit and no calculations are performed in corresponding codes. The codes should handle |last| safely also if no calcs are done. @<|KronProdAll::mult| code@>= void KronProdAll::mult(const ConstTwoDMatrix& in, TwoDMatrix& out) const { @; @; @; int c; TwoDMatrix* last = NULL; @; @; @; } @ @= if (isUnit()) { out.zeros(); out.add(1.0, in); return; } @ If one of the matrices is exactly zero or the |in| matrix is zero, set out to zero and return @= bool is_zero = false; for (int i = 0; i < dimen() && ! is_zero; i++) is_zero = matlist[i] && matlist[i]->isZero(); if (is_zero || in.isZero()) { out.zeros(); return; } @ @= if (dimen() == 1) { if (matlist[0]) // always true out.mult(in, ConstTwoDMatrix(*(matlist[0]))); return; } @ Here we have to construct $A_1\otimes I$, allocate intermediate result |last|, and perform the multiplication. @= if (matlist[0]) { KronProdAI akronid(*this); c = akronid.kpd.ncols(); last = new TwoDMatrix(in.nrows(), c); akronid.mult(in, *last); } else { last = new TwoDMatrix(in.nrows(), in.ncols(), in.getData().base()); } @ Here we go through all $I\otimes A_i\otimes I$, construct the product, allocate new storage for result |newlast|, perform the multiplication, deallocate old |last|, and set |last| to |newlast|. @= for (int i = 1; i < dimen()-1; i++) { if (matlist[i]) { KronProdIAI interkron(*this, i); c = interkron.kpd.ncols(); TwoDMatrix* newlast = new TwoDMatrix(in.nrows(), c); interkron.mult(*last, *newlast); delete last; last = newlast; } } @ Here just construct $I\otimes A_n$ and perform multiplication and deallocate |last|. @= if (matlist[dimen()-1]) { KronProdIA idkrona(*this); idkrona.mult(*last, out); } else { out = *last; } delete last; @ This calculates a Kornecker product of rows of matrices, the row indices are given by the integer sequence. The result is allocated and returned. The caller is repsonsible for its deallocation. @<|KronProdAll::multRows| code@>= Vector* KronProdAll::multRows(const IntSequence& irows) const { TL_RAISE_IF(irows.size() != dimen(), "Wrong length of row indices in KronProdAll::multRows"); Vector* last = NULL; ConstVector* row; vector to_delete; for (int i = 0; i < dimen(); i++) { int j = dimen()-1-i; @; @; delete row; } for (unsigned int i = 0; i < to_delete.size(); i++) delete to_delete[i]; return last; } @ If the |j|-th matrix is real matrix, then the row is constructed from the matrix. It the matrix is unit, we construct a new vector, fill it with zeros, than set the unit to appropriate place, and make the |row| as ConstVector of this vector, which sheduled for deallocation. @= if (matlist[j]) row = new ConstVector(irows[j], *(matlist[j])); else { Vector* aux = new Vector(ncols(j)); aux->zeros(); (*aux)[irows[j]] = 1.0; to_delete.push_back(aux); row = new ConstVector(*aux); } @ If the |last| is exists, we allocate new storage, Kronecker multiply, deallocate the old storage. If the |last| does not exist, then we only make |last| equal to |row|. @= if (last) { Vector* newlast; newlast = new Vector(last->length()*row->length()); kronMult(*row, ConstVector(*last), *newlast); delete last; last = newlast; } else { last = new Vector(*row); } @ This permutes the matrices so that the new ordering would minimize memory consumption. As shown in |@<|KronProdAllOptim| class declaration@>|, we want ${m_k\over n_k}\leq{m_{k-1}\over n_{k-1}}\ldots\leq{m_1\over n_1}$, where $(m_i,n_i)$ is the dimension of $A_i$. So we implement the bubble sort. @<|KronProdAllOptim::optimizeOrder| code@>= void KronProdAllOptim::optimizeOrder() { for (int i = 0; i < dimen(); i++) { int swaps = 0; for (int j = 0; j < dimen()-1; j++) { if (((double)kpd.rows[j])/kpd.cols[j] < ((double)kpd.rows[j+1])/kpd.cols[j+1]) { @; @; } } if (swaps == 0) { return; } } } @ @= int s = kpd.rows[j+1]; kpd.rows[j+1] = kpd.rows[j]; kpd.rows[j] = s; s = kpd.cols[j+1]; kpd.cols[j+1] = kpd.cols[j]; kpd.cols[j] = s; const TwoDMatrix* m = matlist[j+1]; matlist[j+1] = matlist[j]; matlist[j] = m; @ @= s = oper.getMap()[j+1]; oper.getMap()[j+1] = oper.getMap()[j]; oper.getMap()[j] = s; swaps++; @ End of {\tt kron\_prod.cpp} file.