k_order_welfare MEX: use the sparse representation of the static model

dprior
Sébastien Villemot 2024-02-07 18:50:34 +01:00
parent 0d9857e737
commit 6f2af6943f
No known key found for this signature in database
GPG Key ID: 2CECE9350ECEBE4A
5 changed files with 159 additions and 182 deletions

View File

@ -22,21 +22,19 @@
#include <cassert>
#include <utility>
KordwDynare::KordwDynare(KordpDynare& m, ConstVector& NNZD_arg, Journal& jr, Vector& inParams,
KordwDynare::KordwDynare(KordpDynare& m, Journal& jr, Vector& inParams,
std::unique_ptr<ObjectiveMFile> objectiveFile_arg,
const std::vector<int>& dr_order) :
model {m},
NNZD {NNZD_arg},
journal {jr},
params {inParams},
resid(1),
ud {1},
objectiveFile {std::move(objectiveFile_arg)}
{
dynppToDyn = dr_order;
dynToDynpp.resize(model.ny());
for (int i = 0; i < model.ny(); i++)
dynToDynpp[dynppToDyn[i]] = i;
dynToDynpp[dr_order[i]] = i;
}
void
@ -45,71 +43,10 @@ KordwDynare::calcDerivativesAtSteady()
assert(ud.begin() == ud.end());
std::vector<TwoDMatrix> dyn_ud; // Planner's objective derivatives, in Dynare form
dyn_ud.emplace_back(1, model.ny()); // Allocate Jacobian
dyn_ud.back().zeros();
for (int i = 2; i <= model.order(); i++)
{
// Higher order derivatives, as sparse (3-column) matrices
dyn_ud.emplace_back(static_cast<int>(NNZD[i - 1]), 3);
dyn_ud.back().zeros();
}
Vector xx(model.nexog());
xx.zeros();
resid.zeros();
objectiveFile->eval(model.getSteady(), xx, params, resid, dyn_ud);
for (int i = 1; i <= model.order(); i++)
populateDerivativesContainer(dyn_ud, i);
}
void
KordwDynare::populateDerivativesContainer(const std::vector<TwoDMatrix>& dyn_ud, int ord)
{
const TwoDMatrix& u = dyn_ud[ord - 1];
// utility derivatives FSSparseTensor instance
auto udTi = std::make_unique<FSSparseTensor>(ord, model.ny(), 1);
IntSequence s(ord, 0);
if (ord == 1)
for (int i = 0; i < u.ncols(); i++)
{
for (int j = 0; j < u.nrows(); j++)
{
double x = u.get(j, dynppToDyn[s[0]]);
if (x != 0.0)
udTi->insert(s, j, x);
}
s[0]++;
}
else // ord ≥ 2
for (int i = 0; i < u.nrows(); i++)
{
int j = static_cast<int>(u.get(i, 0)) - 1;
int i1 = static_cast<int>(u.get(i, 1)) - 1;
if (j < 0 || i1 < 0)
continue; // Discard empty entries (see comment in DynamicModelAC::unpackSparseMatrix())
for (int k = 0; k < ord; k++)
{
s[k] = dynToDynpp[i1 % model.ny()];
i1 /= model.ny();
}
if (ord == 2 && !s.isSorted())
continue; // Skip symmetric elements (only needed at order 2)
else if (ord > 2)
s.sort(); // For higher order, canonicalize the multi-index
double x = u.get(i, 2);
udTi->insert(s, j, x);
}
ud.insert(std::move(udTi));
objectiveFile->eval(model.getSteady(), xx, params, resid, dynToDynpp, ud);
}
template<>

View File

@ -29,22 +29,19 @@ class KordwDynare
{
public:
KordpDynare& model;
const ConstVector& NNZD;
private:
Journal& journal;
Vector& params;
Vector resid;
TensorContainer<FSSparseTensor> ud; // planner's objective derivatives, in Dynare++ form
std::vector<int> dynppToDyn; // Maps Dynare++ jacobian variable indices to Dynare ones
std::vector<int> dynToDynpp; // Maps Dynare jacobian variable indices to Dynare++ ones
std::unique_ptr<ObjectiveMFile> objectiveFile;
public:
KordwDynare(KordpDynare& m, ConstVector& NNZD_arg, Journal& jr, Vector& inParams,
KordwDynare(KordpDynare& m, Journal& jr, Vector& inParams,
std::unique_ptr<ObjectiveMFile> objectiveFile_arg, const std::vector<int>& varOrder);
void calcDerivativesAtSteady();
void populateDerivativesContainer(const std::vector<TwoDMatrix>& dyn_ud, int ord);
[[nodiscard]] const TensorContainer<FSSparseTensor>&
getPlannerObjDerivatives() const
{

View File

@ -31,6 +31,7 @@
#include <algorithm>
#include <cassert>
#include <string>
#include "dynmex.h"
@ -214,15 +215,6 @@ extern "C"
mexErrMsgTxt("The derivatives were not computed for the required order. Make sure that you "
"used the right order option inside the `stoch_simul' command");
const mxArray* nnzderivatives_obj_mx = mxGetField(M_mx, 0, "NNZDerivatives_objective");
if (!(nnzderivatives_obj_mx && mxIsDouble(nnzderivatives_obj_mx)
&& !mxIsComplex(nnzderivatives_obj_mx) && !mxIsSparse(nnzderivatives_obj_mx)))
mexErrMsgTxt("M_.NNZDerivatives should be a real dense array");
ConstVector NNZD_obj {nnzderivatives_obj_mx};
if (NNZD.length() < kOrder || NNZD_obj[kOrder - 1] == -1)
mexErrMsgTxt("The derivatives were not computed for the required order. Make sure that you "
"used the right order option inside the `stoch_simul' command");
const mxArray* endo_names_mx = mxGetField(M_mx, 0, "endo_names");
if (!(endo_names_mx && mxIsCell(endo_names_mx)
&& mxGetNumberOfElements(endo_names_mx) == static_cast<size_t>(nEndo)))
@ -261,6 +253,32 @@ extern "C"
std::transform(mxGetPr(order_var_mx), mxGetPr(order_var_mx) + nEndo, dr_order.begin(),
[](double x) { return static_cast<int>(x) - 1; });
const mxArray* objective_g1_sparse_rowval_mx
= mxGetField(M_mx, 0, "objective_g1_sparse_rowval");
if (!(objective_g1_sparse_rowval_mx && mxIsInt32(objective_g1_sparse_rowval_mx)))
mexErrMsgTxt("M_.objective_g1_sparse_rowval should be an int32 array");
const mxArray* objective_g1_sparse_colval_mx
= mxGetField(M_mx, 0, "objective_g1_sparse_colval");
if (!(objective_g1_sparse_colval_mx && mxIsInt32(objective_g1_sparse_colval_mx)))
mexErrMsgTxt("M_.objective_g1_sparse_colval should be an int32 array");
const mxArray* objective_g1_sparse_colptr_mx
= mxGetField(M_mx, 0, "objective_g1_sparse_colptr");
if (!(objective_g1_sparse_colptr_mx && mxIsInt32(objective_g1_sparse_colptr_mx)))
mexErrMsgTxt("M_.objective_g1_sparse_colptr should be an int32 array");
std::vector<const mxArray*> objective_gN_sparse_indices;
for (int o {2}; o <= kOrder; o++)
{
using namespace std::string_literals;
auto fieldname {"objective_g"s + std::to_string(o) + "_sparse_indices"};
const mxArray* indices = mxGetField(M_mx, 0, fieldname.c_str());
if (!(indices && mxIsInt32(indices)))
mexErrMsgTxt(("M_."s + fieldname + " should be an int32 array").c_str());
objective_gN_sparse_indices.push_back(indices);
}
const int nSteps
= 0; // Dynare++ solving steps, for time being default to 0 = deterministic steady state
@ -291,21 +309,14 @@ extern "C"
// run stochastic steady
app.walkStochSteady();
const mxArray* objective_tmp_nbr_mx = mxGetField(M_mx, 0, "objective_tmp_nbr");
if (!(objective_tmp_nbr_mx && mxIsDouble(objective_tmp_nbr_mx)
&& !mxIsComplex(objective_tmp_nbr_mx) && !mxIsSparse(objective_tmp_nbr_mx)
&& mxGetNumberOfElements(objective_tmp_nbr_mx) >= static_cast<size_t>(kOrder + 1)))
mexErrMsgTxt("M_.objective_tmp_nbr should be a real dense array with strictly more elements "
"than the order of derivation");
int ntt_objective = std::accumulate(mxGetPr(objective_tmp_nbr_mx),
mxGetPr(objective_tmp_nbr_mx) + kOrder + 1, 0);
// Getting derivatives of the planner's objective function
std::unique_ptr<ObjectiveMFile> objectiveFile;
objectiveFile = std::make_unique<ObjectiveMFile>(fname, ntt_objective);
objectiveFile = std::make_unique<ObjectiveMFile>(
fname, kOrder, objective_g1_sparse_rowval_mx, objective_g1_sparse_colval_mx,
objective_g1_sparse_colptr_mx, objective_gN_sparse_indices);
// make KordwDynare object
KordwDynare welfare(dynare, NNZD_obj, journal, modParams, std::move(objectiveFile), dr_order);
KordwDynare welfare(dynare, journal, modParams, std::move(objectiveFile), dr_order);
// construct main K-order approximation class of welfare
ApproximationWelfare appwel(welfare, discount_factor, app.get_rule_ders(),

View File

@ -26,88 +26,41 @@
#include "objective_m.hh"
ObjectiveMFile::ObjectiveMFile(const std::string& modName, int ntt_arg) :
ntt {ntt_arg}, ObjectiveMFilename {modName + ".objective.static"}
ObjectiveMFile::ObjectiveMFile(const std::string& modName, int kOrder_arg,
const mxArray* objective_g1_sparse_rowval_mx_arg,
const mxArray* objective_g1_sparse_colval_mx_arg,
const mxArray* objective_g1_sparse_colptr_mx_arg,
const std::vector<const mxArray*> objective_gN_sparse_indices_arg) :
ObjectiveMFilename {modName + ".objective.sparse.static"},
kOrder {kOrder_arg},
objective_g1_sparse_rowval_mx {objective_g1_sparse_rowval_mx_arg},
objective_g1_sparse_colval_mx {objective_g1_sparse_colval_mx_arg},
objective_g1_sparse_colptr_mx {objective_g1_sparse_colptr_mx_arg},
objective_gN_sparse_indices {objective_gN_sparse_indices_arg}
{
}
void
ObjectiveMFile::unpackSparseMatrixAndCopyIntoTwoDMatData(mxArray* sparseMat, TwoDMatrix& tdm)
{
int totalCols = mxGetN(sparseMat);
mwIndex* rowIdxVector = mxGetIr(sparseMat);
mwIndex* colIdxVector = mxGetJc(sparseMat);
assert(tdm.ncols() == 3);
/* Under MATLAB, the following check always holds at equality; under Octave,
there may be an inequality, because Octave diminishes nzmax if one gives
zeros in the values vector when calling sparse(). */
assert(tdm.nrows() >= static_cast<int>(mxGetNzmax(sparseMat)));
double* ptr = mxGetPr(sparseMat);
int rind = 0;
int output_row = 0;
for (int i = 0; i < totalCols; i++)
for (int j = 0; j < static_cast<int>((colIdxVector[i + 1] - colIdxVector[i])); j++, rind++)
{
tdm.get(output_row, 0) = rowIdxVector[rind] + 1;
tdm.get(output_row, 1) = i + 1;
tdm.get(output_row, 2) = ptr[rind];
output_row++;
}
/* If there are less elements than expected (that might happen if some
derivative is symbolically not zero but numerically zero at the evaluation
point), then fill in the matrix with empty entries, that will be
recognized as such by KordpDynare::populateDerivativesContainer() */
while (output_row < tdm.nrows())
{
tdm.get(output_row, 0) = 0;
tdm.get(output_row, 1) = 0;
tdm.get(output_row, 2) = 0;
output_row++;
}
}
void
ObjectiveMFile::eval(const Vector& y, const Vector& x, const Vector& modParams, Vector& residual,
std::vector<TwoDMatrix>& md) noexcept(false)
const std::vector<int>& dynToDynpp,
TensorContainer<FSSparseTensor>& derivatives) const
{
mxArray* T_m = mxCreateDoubleMatrix(ntt, 1, mxREAL);
mxArray* y_mx = mxCreateDoubleMatrix(y.length(), 1, mxREAL);
std::copy_n(y.base(), y.length(), mxGetPr(y_mx));
mxArray* y_m = mxCreateDoubleMatrix(y.length(), 1, mxREAL);
std::copy_n(y.base(), y.length(), mxGetPr(y_m));
mxArray* x_mx = mxCreateDoubleMatrix(1, x.length(), mxREAL);
std::copy_n(x.base(), x.length(), mxGetPr(x_mx));
mxArray* x_m = mxCreateDoubleMatrix(1, x.length(), mxREAL);
std::copy_n(x.base(), x.length(), mxGetPr(x_m));
mxArray* params_mx = mxCreateDoubleMatrix(modParams.length(), 1, mxREAL);
std::copy_n(modParams.base(), modParams.length(), mxGetPr(params_mx));
mxArray* params_m = mxCreateDoubleMatrix(modParams.length(), 1, mxREAL);
std::copy_n(modParams.base(), modParams.length(), mxGetPr(params_m));
mxArray* T_flag_m = mxCreateLogicalScalar(false);
{
// Compute temporary terms (for all orders)
std::string funcname = ObjectiveMFilename + "_g" + std::to_string(md.size()) + "_tt";
std::array<mxArray*, 1> plhs;
std::array prhs {T_m, y_m, x_m, params_m};
int retVal
= mexCallMATLAB(plhs.size(), plhs.data(), prhs.size(), prhs.data(), funcname.c_str());
if (retVal != 0)
throw DynareException(__FILE__, __LINE__, "Trouble calling " + funcname);
mxDestroyArray(T_m);
T_m = plhs[0];
}
mxArray *T_order_mx, *T_mx;
{
// Compute residuals
std::string funcname = ObjectiveMFilename + "_resid";
std::array<mxArray*, 1> plhs;
std::array prhs {T_m, y_m, x_m, params_m, T_flag_m};
std::array<mxArray*, 3> plhs;
std::array prhs {y_mx, x_mx, params_mx};
int retVal
= mexCallMATLAB(plhs.size(), plhs.data(), prhs.size(), prhs.data(), funcname.c_str());
@ -116,35 +69,103 @@ ObjectiveMFile::eval(const Vector& y, const Vector& x, const Vector& modParams,
residual = Vector {plhs[0]};
mxDestroyArray(plhs[0]);
T_order_mx = plhs[1];
T_mx = plhs[2];
}
for (size_t i = 1; i <= md.size(); i++)
{
// Compute Jacobian
std::string funcname = ObjectiveMFilename + "_g1";
std::array<mxArray*, 3> plhs;
std::array prhs {y_mx,
x_mx,
params_mx,
const_cast<mxArray*>(objective_g1_sparse_rowval_mx),
const_cast<mxArray*>(objective_g1_sparse_colval_mx),
const_cast<mxArray*>(objective_g1_sparse_colptr_mx),
T_order_mx,
T_mx};
int retVal
= mexCallMATLAB(plhs.size(), plhs.data(), prhs.size(), prhs.data(), funcname.c_str());
if (retVal != 0)
throw DynareException(__FILE__, __LINE__, "Trouble calling " + funcname);
assert(static_cast<int>(mxGetN(plhs[0])) == y.length());
double* g1_v {mxGetPr(plhs[0])};
mwIndex* g1_ir {mxGetIr(plhs[0])};
mwIndex* g1_jc {mxGetJc(plhs[0])};
IntSequence s(1, 0);
auto tensor = std::make_unique<FSSparseTensor>(1, y.length(), 1);
for (int j {0}; j < y.length(); j++)
for (mwIndex k {g1_jc[j]}; k < g1_jc[j + 1]; k++)
{
s[0] = dynToDynpp[j];
tensor->insert(s, g1_ir[k], g1_v[k]);
}
mxDestroyArray(plhs[0]);
mxDestroyArray(T_order_mx);
T_order_mx = plhs[1];
mxDestroyArray(T_mx);
T_mx = plhs[2];
derivatives.insert(std::move(tensor));
}
for (int o {2}; o <= kOrder; o++)
{
// Compute model derivatives
std::string funcname = ObjectiveMFilename + "_g" + std::to_string(i);
std::array<mxArray*, 1> plhs;
std::array prhs {T_m, y_m, x_m, params_m, T_flag_m};
// Compute higher derivatives
std::string funcname = ObjectiveMFilename + "_g" + std::to_string(o);
std::array<mxArray*, 3> plhs;
std::array prhs {y_mx, x_mx, params_mx, T_order_mx, T_mx};
int retVal
= mexCallMATLAB(plhs.size(), plhs.data(), prhs.size(), prhs.data(), funcname.c_str());
if (retVal != 0)
throw DynareException(__FILE__, __LINE__, "Trouble calling " + funcname);
if (i == 1)
const mxArray* sparse_indices_mx {objective_gN_sparse_indices[o - 2]};
size_t nnz {mxGetM(sparse_indices_mx)};
#if MX_HAS_INTERLEAVED_COMPLEX
const int32_T* sparse_indices {mxGetInt32s(sparse_indices_mx)};
#else
const int32_T* sparse_indices {static_cast<const int32_T*>(mxGetData(sparse_indices_mx))};
#endif
assert(mxGetNumberOfElements(plhs[0]) == nnz);
double* gN_v {mxGetPr(plhs[0])};
IntSequence s(o, 0);
auto tensor = std::make_unique<FSSparseTensor>(o, y.length(), 1);
for (size_t k {0}; k < nnz; k++)
{
assert(static_cast<int>(mxGetM(plhs[0])) == md[i - 1].nrows());
assert(static_cast<int>(mxGetN(plhs[0])) == md[i - 1].ncols());
std::copy_n(mxGetPr(plhs[0]), mxGetM(plhs[0]) * mxGetN(plhs[0]), md[i - 1].base());
for (int i {0}; i < o; i++)
s[i] = dynToDynpp[sparse_indices[k + (i + 1) * nnz] - 1];
assert(s.isSorted());
tensor->insert(s, sparse_indices[k] - 1, gN_v[k]);
}
else
unpackSparseMatrixAndCopyIntoTwoDMatData(plhs[0], md[i - 1]);
mxDestroyArray(plhs[0]);
mxDestroyArray(T_order_mx);
T_order_mx = plhs[1];
mxDestroyArray(T_mx);
T_mx = plhs[2];
derivatives.insert(std::move(tensor));
}
mxDestroyArray(T_m);
mxDestroyArray(y_m);
mxDestroyArray(x_m);
mxDestroyArray(params_m);
mxDestroyArray(T_flag_m);
mxDestroyArray(y_mx);
mxDestroyArray(x_mx);
mxDestroyArray(params_mx);
mxDestroyArray(T_order_mx);
mxDestroyArray(T_mx);
}

View File

@ -22,21 +22,32 @@
#include <vector>
#include <dynmex.h>
#include "dynmex.h"
#include "twod_matrix.hh"
#include "Vector.hh"
#include "sparse_tensor.hh"
#include "t_container.hh"
// Handles calls to <model>/+objective/static.m
// Handles calls to <model>/+objective/+sparse/static*.m
class ObjectiveMFile
{
private:
int ntt; // Size of vector of temporary terms
const std::string ObjectiveMFilename;
static void unpackSparseMatrixAndCopyIntoTwoDMatData(mxArray* sparseMat, TwoDMatrix& tdm);
const int kOrder;
const mxArray *const objective_g1_sparse_rowval_mx, *const objective_g1_sparse_colval_mx,
*const objective_g1_sparse_colptr_mx;
// Stores M_.objective_gN_sparse_indices, starting from N=2
const std::vector<const mxArray*> objective_gN_sparse_indices;
public:
explicit ObjectiveMFile(const std::string& modName, int ntt_arg);
ObjectiveMFile(const std::string& modName, int kOrder_arg,
const mxArray* objective_g1_sparse_rowval_mx_arg,
const mxArray* objective_g1_sparse_colval_mx_arg,
const mxArray* objective_g1_sparse_colptr_mx_arg,
const std::vector<const mxArray*> objective_gN_sparse_indices_arg);
void eval(const Vector& y, const Vector& x, const Vector& params, Vector& residual,
std::vector<TwoDMatrix>& md);
const std::vector<int>& dynToDynpp, TensorContainer<FSSparseTensor>& derivatives) const
noexcept(false);
};
#endif