dynare/mex/sources/libkorder/tl/kron_prod.cc

409 lines
13 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* Copyright © 2004 Ondra Kamenik
* Copyright © 2019-2023 Dynare Team
*
* This file is part of Dynare.
*
* Dynare is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dynare is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Dynare. If not, see <https://www.gnu.org/licenses/>.
*/
#include "kron_prod.hh"
#include "tl_exception.hh"
#include <tuple>
/* 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₁⊗A₂⊗…⊗Aₙ and makes dimensions of A₁⊗I,
I⊗Aᵢ⊗I or I⊗Aₙ for a given i. The identity matrices must fit into the
described order. See header file.
We first decide what is the length of the resulting dimensions. Possible
length is three for I⊗A⊗I, and two for I⊗A or A⊗I.
Then we fork according to i. */
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 A⊗I dimensions
/* The first rows and cols are taken from kd. The dimension of the
identity matrix is the number of rows in A₂⊗…⊗Aₙ since the matrix A₁⊗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];
}
else if (i == kdim - 1)
{
// set I⊗A dimensions
/* The second dimension is taken from kd. The dimension of the identity
matrix is the number of columns of A₁⊗…⊗Aₙ₋₁, since the matrix I⊗Aₙ 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];
}
else
{
// set I⊗A⊗I dimensions
/* The dimensions of the middle matrix are taken from kd. The dimension
of the first identity matrix is the number of columns of A₁⊗…⊗Aᵢ₋₁,
and the dimension of the last identity matrix is the number of rows of
Aᵢ₊₁⊗…⊗Aₙ. */
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. */
void
KronProd::checkDimForMult(const ConstTwoDMatrix& in, const TwoDMatrix& out) const
{
auto [my_rows, my_cols] = kpd.getRC();
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 v₁ and v₂ and
store the result in preallocated res. */
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);
}
}
void
KronProdAll::setMat(int i, const TwoDMatrix& m)
{
matlist[i] = &m;
kpd.setRC(i, m.nrows(), m.ncols());
}
void
KronProdAll::setUnit(int i, int n)
{
matlist[i] = nullptr;
kpd.setRC(i, n, n);
}
bool
KronProdAll::isUnit() const
{
int i = 0;
while (i < dimen() && matlist[i] == nullptr)
i++;
return i == dimen();
}
/* Here we compute B·(I⊗A). If m is the dimension of the identity matrix, and B
is partitioned accordingly, then the result is (B₁·A B₂·A … Bₘ·A).
In the implementation, outi are partitions of out, ini are const
partitions of in, and id_cols is m. We employ level-2 BLAS. */
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::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 compute B·(A⊗I). Let the dimension of the
matrix A be m×n, the dimension of I be p, and the number
of rows of B be q. We use the fact that:
B·(A⊗I)=reshape(reshape(B, q, mp)·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⊗I into m×n square partitions aᵢⱼI.
Therefore, we partition B into m partitions (B₁ B₂ … Bₘ). 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
into n partitions (R₁ R₂ … Rₙ). Each partition of R has the same number of columns as the
identity matrix. Then we have Rᵢ=∑aⱼᵢBⱼ.
In the implementation, outi is Rᵢ, ini is Bⱼ, and id_cols is the
dimension of the identity matrix. */
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());
TwoDMatrix out_resh(in.nrows() * id_cols, a.ncols(), out.getData());
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 compute R=B·(I⊗A⊗I). If n is the dimension of the first identity
matrix, and B is partitioned accordingly, then the result is:
(B₁·(A⊗I) B₂·(A⊗I) … Bₘ·(A⊗I)).
Each Bᵢ·(A⊗I) is in fact KronProdAI::mult(). Note that the number of columns
of partitions of B is the number of rows of A⊗I, and the number of columns
of partitions of R is the number of columns of A⊗I.
In the implementation, id_cols is n, akronid is A⊗I, and in_bl_width
and out_bl_width are the rows and cols of A⊗I. */
void
KronProdIAI::mult(const ConstTwoDMatrix& in, TwoDMatrix& out) const
{
checkDimForMult(in, out);
int id_cols = kpd.cols[0];
KronProdAI akronid(*this);
auto [in_bl_width, out_bl_width] = akronid.kpd.getRC();
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 compute B·(A₁⊗…⊗Aₙ). First we compute B·(A₁⊗I), then this is
multiplied by all I⊗Aᵢ⊗I, and finally by I⊗Aₙ.
If the dimension of the Kronecker product is only 1, then we multiply
two matrices in a straight manner 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. */
void
KronProdAll::mult(const ConstTwoDMatrix& in, TwoDMatrix& out) const
{
// quick copy if product is unit
if (isUnit())
{
out.zeros();
out.add(1.0, in);
return;
}
// quick zero if one of the matrices is zero
/* 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;
}
// quick multiplication if dimension is 1
if (dimen() == 1)
{
if (matlist[0]) // always true
out.mult(in, ConstTwoDMatrix(*(matlist[0])));
return;
}
int c;
std::unique_ptr<TwoDMatrix> last;
// perform first multiplication by A₁⊗I
/* Here we have to construct A₁⊗I, allocate intermediate result last, and
perform the multiplication. */
if (matlist[0])
{
KronProdAI akronid(*this);
c = akronid.kpd.ncols();
last = std::make_unique<TwoDMatrix>(in.nrows(), c);
akronid.mult(in, *last);
}
else
last = std::make_unique<TwoDMatrix>(in.nrows(), in.ncols(), Vector {in.getData()});
// perform intermediate multiplications by I⊗Aᵢ⊗I
/* Here we go through all I⊗Aᵢ⊗I, construct the product, allocate new storage
for result newlast, perform the multiplication and set last to
newlast. */
for (int i = 1; i < dimen() - 1; i++)
if (matlist[i])
{
KronProdIAI interkron(*this, i);
c = interkron.kpd.ncols();
auto newlast = std::make_unique<TwoDMatrix>(in.nrows(), c);
interkron.mult(*last, *newlast);
last = std::move(newlast);
}
// perform last multiplication by I⊗Aₙ
if (matlist[dimen() - 1])
{
KronProdIA idkrona(*this);
idkrona.mult(*last, out);
}
else
out = *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. */
std::unique_ptr<Vector>
KronProdAll::multRows(const IntSequence& irows) const
{
TL_RAISE_IF(irows.size() != dimen(), "Wrong length of row indices in KronProdAll::multRows");
std::unique_ptr<Vector> last;
std::unique_ptr<ConstVector> row;
std::vector<std::unique_ptr<Vector>> to_delete;
for (int i = 0; i < dimen(); i++)
{
int j = dimen() - 1 - i;
// set row to the number of rows of j-th matrix
/* If the j-th matrix is a 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 is sheduled for deallocation. */
if (matlist[j])
row = std::make_unique<ConstVector>(matlist[j]->getRow(irows[j]));
else
{
auto aux = std::make_unique<Vector>(ncols(j));
aux->zeros();
(*aux)[irows[j]] = 1.0;
row = std::make_unique<ConstVector>(*aux);
to_delete.emplace_back(std::move(aux));
}
// set last to product of row and last
/* If the last exists, we allocate new storage and Kronecker multiply.
If the last does not exist, then we only make last equal to
row. */
if (last)
{
auto newlast = std::make_unique<Vector>(last->length() * row->length());
kronMult(*row, ConstVector(*last), *newlast);
last = std::move(newlast);
}
else
last = std::make_unique<Vector>(*row);
}
return last;
}
/* This permutes the matrices so that the new ordering would minimize memory
consumption. As shown in KronProdAllOptim class declaration, we want:
mₖ/nₖ ≤ mₖ₋₁/nₖ₋₁ ≤ … ≤ m₁/n₁
where mᵢ×nᵢ is the dimension of Aᵢ. So we implement the bubble sort. */
void
KronProdAllOptim::optimizeOrder()
{
for (int i = 0; i < dimen(); i++)
{
int swaps = 0;
for (int j = 0; j < dimen() - 1; j++)
if (static_cast<double>(kpd.rows[j]) / kpd.cols[j]
< static_cast<double>(kpd.rows[j + 1]) / kpd.cols[j + 1])
{
// 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++;
}
if (swaps == 0)
return;
}
}