@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 <cstdio>

@<|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) {
		@<set AI dimensions@>;
	} else if (i == kdim-1){
		@<set IA dimensions@>;
	} else {
		@<set IAI dimensions@>;
	}
}

@ 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.
 
@<set AI dimensions@>=
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.

@<set IA dimensions@>=
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$.
 
@<set IAI dimensions@>=
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
{
	@<quick copy if product is unit@>;
	@<quick zero if one of the matrices is zero@>;
	@<quick multiplication if dimension is 1@>;
	int c;
	TwoDMatrix* last = NULL;
	@<perform first multiplication AI@>;
	@<perform intermediate multiplications IAI@>;
	@<perform last multiplication IA@>;
}

@ 
@<quick copy if product is unit@>=
	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

@<quick zero if one of the matrices is zero@>=
	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;
	}

@ 
@<quick multiplication if dimension is 1@>=
	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.

@<perform first multiplication AI@>=
	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|.

@<perform intermediate multiplications IAI@>=
	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|.

@<perform last multiplication IA@>=
	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<Vector*> to_delete;
	for (int i = 0; i < dimen(); i++) {
		int j = dimen()-1-i;
		@<set |row| to the row of |j|-th matrix@>;
		@<set |last| to product of |row| and |last|@>;
		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.

@<set |row| to the row of |j|-th matrix@>=
	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|.
 
@<set |last| to product of |row| and |last|@>=
	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]) {
				@<swap dimensions and matrices at |j| and |j+1|@>;
				@<project the swap to the permutation |oper|@>;
			}
		}
		if (swaps == 0) {
			return;
		}
	}
}

@ 
@<swap dimensions and matrices at |j| and |j+1|@>=
	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;

@ 
@<project the swap to the permutation |oper|@>=
	s = oper.getMap()[j+1];
	oper.getMap()[j+1] = oper.getMap()[j];
	oper.getMap()[j] = s;
	swaps++;


@ End of {\tt kron\_prod.cpp} file.