dynare/mex/sources/k_order_welfare/k_ord_objective.hh

653 lines
20 KiB
C++
Raw Normal View History

/*
* Copyright © 2021-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/>.
*/
#ifndef K_ORD_OBJECTIVE_H
#define K_ORD_OBJECTIVE_H
#include "k_ord_dynare.hh"
#include "objective_abstract_class.hh"
class KordwDynare;
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<ObjectiveAC> objectiveFile;
public:
KordwDynare(KordpDynare& m, ConstVector& NNZD_arg, Journal& jr, Vector& inParams,
std::unique_ptr<ObjectiveAC> 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
{
return ud;
}
[[nodiscard]] const KordpDynare&
getModel() const
{
return model;
}
[[nodiscard]] const Vector&
getResid() const
{
return resid;
}
private:
/* Computes the permutations mapping back and forth between Dynare and
Dynare++ orderings of variables */
void computeJacobianPermutation(const std::vector<int>& var_order);
};
class KOrderWelfare
{
public:
const PartitionY ypart;
const int ny;
const int nu;
const int maxk;
const int order;
const double discount_factor;
/* Tensor variable dimension: variable sizes of all tensors in
containers, sizes of y, u and σ */
IntSequence nvs;
UGSContainer _uU;
FGSContainer _fU;
UGSContainer _uW;
FGSContainer _fW;
UGSContainer _uWrond;
FGSContainer _fWrond;
UGSContainer _ug;
FGSContainer _fg;
UGSContainer _ugs;
FGSContainer _fgs;
UnfoldedXContainer _uXstack;
FoldedXContainer _fXstack;
UnfoldedGContainer _uGstack;
FoldedGContainer _fGstack;
/* Moments: both folded and unfolded normal moment containers, both are
calculated at initialization */
UNormalMoments _um;
FNormalMoments _fm;
/* Planner objective derivatives: just a reference to the container of sparse
tensors of the derivatives, lives outside the class */
const TensorContainer<FSSparseTensor>& u;
/* These are the declarations of the template functions accessing the
containers. We declare template methods for accessing containers depending
on fold and unfold flag, we implement their specializations*/
template<Storage t>
typename ctraits<t>::Tg& g();
template<Storage t>
const typename ctraits<t>::Tg& g() const;
template<Storage t>
typename ctraits<t>::Tgs& gs();
template<Storage t>
const typename ctraits<t>::Tgs& gs() const;
template<Storage t>
typename ctraits<t>::TU& U();
template<Storage t>
const typename ctraits<t>::TU& U() const;
template<Storage t>
typename ctraits<t>::TW& W();
template<Storage t>
const typename ctraits<t>::TW& W() const;
template<Storage t>
typename ctraits<t>::TWrond& Wrond();
template<Storage t>
const typename ctraits<t>::TWrond& Wrond() const;
template<Storage t>
typename ctraits<t>::TXstack& Xstack();
template<Storage t>
const typename ctraits<t>::TXstack& Xstack() const;
template<Storage t>
typename ctraits<t>::TGstack& Gstack();
template<Storage t>
const typename ctraits<t>::TGstack& Gstack() const;
template<Storage t>
typename ctraits<t>::Tm& m();
template<Storage t>
const typename ctraits<t>::Tm& m() const;
Journal& journal;
public:
KOrderWelfare(int num_stat, int num_pred, int num_both, int num_forw, int nu, int ord,
double discount_factor, const TensorContainer<FSSparseTensor>& ucont,
FGSContainer g, FGSContainer gs, const TwoDMatrix& v, Journal& jr);
/* Performs k-order step provided that k=2 or the k1-th step has been
run, this is the core method */
template<Storage t>
void performStep(int order);
/* Calculates residuals of all solved equations for k-order and reports their
sizes, it is runnable after k-order performStep() has been run */
template<Storage t>
[[nodiscard]] double check(int dim) const;
[[nodiscard]] const FGSContainer&
getFoldU() const
{
return _fU;
}
[[nodiscard]] const UGSContainer&
getUnfoldU() const
{
return _uU;
}
[[nodiscard]] const FGSContainer&
getFoldW() const
{
return _fW;
}
[[nodiscard]] const UGSContainer&
getUnfoldW() const
{
return _uW;
}
static bool
is_even(int i)
{
return i % 2 == 0;
}
protected:
/* Calculates derivatives of U by Faà Di Bruno for the sparse container of
planner's objective derivatives and the container for the decision rule derivatives*/
template<Storage t>
std::unique_ptr<typename ctraits<t>::Ttensor> faaDiBrunoU(const Symmetry& sym) const;
/* Calculates derivatives of the compounded functions W and Gstack using the Faà Di Bruno
* formula*/
template<Storage t>
std::unique_ptr<typename ctraits<t>::Ttensor> faaDiBrunoW(const Symmetry& sym) const;
/* Solves the sylvester equation (templated fold, and unfold) */
template<Storage t>
void sylvesterSolve(typename ctraits<t>::Ttensor& der) const;
// Recovers W_y*ⁱ
template<Storage t>
void recover_y(int i);
// Recovers W_y*ⁱuʲ
template<Storage t>
void recover_yu(int i, int j);
// Recovers W_y*ⁱσʲ
template<Storage t>
void recover_ys(int i, int j);
// Recovers W_y*ⁱuʲσ
template<Storage t>
void recover_yus(int i, int j, int k);
// Recovers W_σ
template<Storage t>
void recover_s(int i);
// Calculates specified derivatives of Wrond and inserts them to the container
template<Storage t>
void fillWrond(int i, int j, int k);
// Calculates Dᵢⱼₖ
template<Storage t>
typename ctraits<t>::Ttensor calcH_ijk(int i, int j, int k) const;
template<Storage t>
typename ctraits<t>::Ttensor calcH_ik(int i, int k) const;
template<Storage t>
typename ctraits<t>::Ttensor calcH_k(int k) const;
// Calculates Eᵢⱼₖ
template<Storage t>
typename ctraits<t>::Ttensor calcJ_ijk(int i, int j, int k) const;
template<Storage t>
typename ctraits<t>::Ttensor calcJ_ik(int i, int k) const;
template<Storage t>
typename ctraits<t>::Ttensor calcJ_k(int k) const;
};
/* Here we implement Faà Di Bruno formula
[u_zˡ]_γγ [h_{s^|c|}]^γ
ˡ¹ c, ¹
where s is a given outer symmetry and k is the dimension of the symmetry. */
template<Storage t>
std::unique_ptr<typename ctraits<t>::Ttensor>
KOrderWelfare::faaDiBrunoU(const Symmetry& sym) const
{
JournalRecordPair pa(journal);
pa << "Faà Di Bruno U container for " << sym << endrec;
auto res = std::make_unique<typename ctraits<t>::Ttensor>(1, TensorDimens(sym, nvs));
FaaDiBruno bruno(journal);
bruno.calculate(Xstack<t>(), u, *res);
return res;
}
/* The same as KOrder::faaDiBrunoW(), but for W and G stack. */
template<Storage t>
std::unique_ptr<typename ctraits<t>::Ttensor>
KOrderWelfare::faaDiBrunoW(const Symmetry& sym) const
{
JournalRecordPair pa(journal);
pa << "Faà Di Bruno G container for " << sym << endrec;
TensorDimens tdims(sym, nvs);
auto res = std::make_unique<typename ctraits<t>::Ttensor>(1, tdims);
FaaDiBruno bruno(journal);
bruno.calculate(Gstack<t>(), W<t>(), *res);
return res;
}
template<Storage t>
void
KOrderWelfare::performStep(int order)
{
KORD_RAISE_IF(order - 1 != W<t>().getMaxDim() and order > 1,
"Wrong order for KOrder::performStep");
JournalRecordPair pa(journal);
pa << "Performing step for order = " << order << endrec;
recover_y<t>(order);
for (int i = 0; i < order; i++)
recover_yu<t>(i, order - i);
recover_ys<t>(order - 1, 1);
for (int j = 2; j < order; j++)
{
for (int i = 1; i <= j - 1; i++)
recover_yus<t>(order - j, i, j - i);
recover_ys<t>(order - j, j);
recover_yus<t>(0, order - j, j);
}
recover_s<t>(order);
}
/* Here we solve [F_yⁱ]=0. First we calculate conditional W_yⁱ (it misses l=i since W_yⁱ does not
exist yet). Then calculate conditional F_y and we have the right hand side of equation. Since we
miss two orders, we solve by Sylvester, and the solution as the derivative g_y. Then we need to
update G_y running multAndAdd() for both dimensions 1 and i.
Requires: everything at order i1
Provides: W_y and Wrond_y
*/
template<Storage t>
void
KOrderWelfare::recover_y(int i)
{
Symmetry sym {i, 0, 0, 0};
JournalRecordPair pa(journal);
pa << "Conditional welfare: recovering symmetry " << sym << "\n" << endrec;
auto Wrond_yi = faaDiBrunoW<t>(sym);
auto Wrond_yi_ptr = Wrond_yi.get();
Wrond<t>().insert(std::move(Wrond_yi));
auto W_yi = U<t>().get(sym);
W_yi.add(discount_factor, *Wrond_yi_ptr);
sylvesterSolve<t>(W_yi);
W<t>().insert(std::make_unique<typename ctraits<t>::Ttensor>(W_yi));
Gstack<t>().multAndAdd(i, W<t>(), *Wrond_yi_ptr);
}
/* Here we solve [F_yⁱuʲ]=0 to obtain W_yⁱuʲ for j>0. We calculate conditional
Wrond_yuʲ and calculate conditional F_yuʲ and we have
the right hand side.
Requires: everything at order i+j1, Wrond_yʲ and W_yʲ.
Provides: W_yuʲ and Wrond_yuʲ
*/
template<Storage t>
void
KOrderWelfare::recover_yu(int i, int j)
{
Symmetry sym {i, j, 0, 0};
JournalRecordPair pa(journal);
pa << "Conditional welfare: recovering symmetry " << sym << endrec;
auto Wrond_yiuj = faaDiBrunoW<t>(sym);
auto Wrond_yiuj_ptr = Wrond_yiuj.get();
Wrond<t>().insert(std::move(Wrond_yiuj));
auto W_yiuj = U<t>().get(sym);
W_yiuj.add(discount_factor, *Wrond_yiuj_ptr);
W<t>().insert(std::make_unique<typename ctraits<t>::Ttensor>(W_yiuj));
}
/* Here we solve [F_yⁱσʲ]+[Dᵢⱼ]+[Eᵢⱼ]=0 to obtain W_yⁱσʲ. We calculate
conditional Wrond_yσʲ (missing dimensions i+j), calculate conditional
F_yσʲ. Before we can calculate D and E, we have to calculate
Wrond_yuσʲ for m=1,,j. Then we add the D and E to obtain the right
hand side. Then we solve the sylvester to obtain g_yσʲ. Then we update
G_yσʲ for l=1 and l=i+j.
Requires: everything at order i+j1, g_yʲ, G_yuʲ and g_yuʲ through
D, g_yuσʲ for m=1,,j1 through E.
Provides: g_yσʲ and G_yσʲ, and finally G_yuσʲ for m=1,,j. The latter
is calculated by fillG() before the actual calculation.
*/
template<Storage t>
void
KOrderWelfare::recover_ys(int i, int j)
{
Symmetry sym {i, 0, 0, j};
JournalRecordPair pa(journal);
pa << "Conditional welfare: recovering symmetry " << sym << endrec;
fillWrond<t>(i, 0, j);
if (is_even(j))
{
auto Wrond_yisj = faaDiBrunoW<t>(sym);
auto Wrond_yisj_ptr = Wrond_yisj.get();
Wrond<t>().insert(std::move(Wrond_yisj));
auto W_yisj = U<t>().get(sym);
W_yisj.add(discount_factor, *Wrond_yisj_ptr);
{
auto H_ij = calcH_ik<t>(i, j);
W_yisj.add(-1.0, H_ij);
}
if (j >= 3)
{
auto J_ij = calcJ_ik<t>(i, j);
W_yisj.add(-1.0, J_ij);
}
sylvesterSolve<t>(W_yisj);
W<t>().insert(std::make_unique<typename ctraits<t>::Ttensor>(W_yisj));
Gstack<t>().multAndAdd(i + j, W<t>(), *Wrond_yisj_ptr);
}
}
/* Here we solve [F_yⁱuʲσᵏ]+[Dᵢⱼₖ]+[Eᵢⱼₖ]=0 to obtain g_yⁱuʲσᵏ. First we
calculate conditional G_yuʲσ (missing only for dimension l=1), then we
evaluate conditional F_yuʲσ. Before we can calculate D, and E, we
need to G_yuʲuσ for m=1,,k. This is done by fillG(). Then we
have right hand side and we multiply by A¹ to obtain g_yuʲσ. Finally we
have to update G_yuʲσ by multAndAdd() for dimension l=1.
Requires: everything at order i+j+k, g_yʲσ through G_yuʲσ involved in
right hand side, then g_yuʲ through D, and g_yuʲσ for m=1,,k1
through E.
Provides: g_yuʲσ, G_yuʲσ, and G_yuʲuσ for m=1,,k
*/
template<Storage t>
void
KOrderWelfare::recover_yus(int i, int j, int k)
{
Symmetry sym {i, j, 0, k};
JournalRecordPair pa(journal);
pa << "Conditional welfare: recovering symmetry " << sym << endrec;
fillWrond<t>(i, j, k);
if (is_even(k))
{
auto Wrond_yiujsk = faaDiBrunoW<t>(sym);
auto Wrond_yiujsk_ptr = Wrond_yiujsk.get();
Wrond<t>().insert(std::move(Wrond_yiujsk));
auto W_yiujsk = U<t>().get(sym);
W_yiujsk.add(discount_factor, *Wrond_yiujsk_ptr);
auto H_ijk = calcH_ijk<t>(i, j, k);
W_yiujsk.add(-1.0, H_ijk);
if (k >= 3)
{
auto J_ijk = calcJ_ijk<t>(i, j, k);
W_yiujsk.add(-1.0, J_ijk);
}
W<t>().insert(std::make_unique<typename ctraits<t>::Ttensor>(W_yiujsk));
Gstack<t>().multAndAdd(i + j + k, W<t>(), *Wrond_yiujsk_ptr);
}
}
/* Here we solve [F_{σⁱ}]+[Dᵢ]+[Eᵢ]=0 to recover g_σⁱ. First we calculate
conditional Wrond_σ (missing dimension and l=i), then we calculate
conditional F_σ. Before we can calculate D and E, we have to obtain
G_uσ for m=1,,i. Then adding D and E we have the right hand side. We
solve by S¹ multiplication and update G_σ by calling multAndAdd() for
dimension l=1.
Recall that the solved equation here is:
ny(ny), nu(nu), F_uʲσʲ.
Provides: g_σ, G_σ, and G_uσ for m=1,,i
*/
template<Storage t>
void
KOrderWelfare::recover_s(int i)
{
Symmetry sym {0, 0, 0, i};
JournalRecordPair pa(journal);
pa << "Conditional welfare: recovering symmetry " << sym << endrec;
fillWrond<t>(0, 0, i);
if (is_even(i))
{
auto Wrond_si = faaDiBrunoW<t>(sym);
auto Wrond_si_ptr = Wrond_si.get();
Wrond<t>().insert(std::move(Wrond_si));
auto W_si = U<t>().get(sym);
W_si.add(discount_factor, *Wrond_si_ptr);
{
auto H_i = calcH_k<t>(i);
W_si.add(-1.0, H_i);
}
if (i >= 3)
{
auto J_i = calcJ_k<t>(i);
W_si.add(-1.0, J_i);
}
W_si.mult(1 / (1 - discount_factor));
W<t>().insert(std::make_unique<typename ctraits<t>::Ttensor>(W_si));
Gstack<t>().multAndAdd(i, W<t>(), *Wrond_si_ptr);
}
}
/* Here we calculate and insert Wrond_yⁱuʲuσᵏ⁻ᵐ for m=1,…,k. The derivatives are inserted only for
* km being even. */
template<Storage t>
void
KOrderWelfare::fillWrond(int i, int j, int k)
{
for (int m = 1; m <= k; m++)
if (is_even(k - m))
{
auto Wrond_yiujupms = faaDiBrunoW<t>(Symmetry {i, j, m, k - m});
Wrond<t>().insert(std::move(Wrond_yiujupms));
}
}
/* Here we calculate:
[H]_ααββ = [F_yuʲu]_ααββγγ [Σ]^γγ
= -β [Wrond_yuʲu]_ααββγγ [Σ]^γγ
So it is non zero only for even k. */
template<Storage t>
typename ctraits<t>::Ttensor
KOrderWelfare::calcH_ijk(int i, int j, int k) const
{
typename ctraits<t>::Ttensor res(1, TensorDimens(Symmetry {i, j, 0, 0}, nvs));
res.zeros();
if (is_even(k))
{
auto tmp = faaDiBrunoW<t>(Symmetry {i, j, k, 0});
tmp->contractAndAdd(2, res, m<t>().get(Symmetry {k}));
}
res.mult(-discount_factor);
return res;
}
/* Here we calculate
k
[J]_ααββ = m [F_yuʲuσ]_ααββγγ [Σ]^γγ
¹
k
= -β m [Wrond_yuʲuσ]_ααββγγ [Σ]^γγ
¹
The sum can sum only for even m. */
template<Storage t>
typename ctraits<t>::Ttensor
KOrderWelfare::calcJ_ijk(int i, int j, int k) const
{
typename ctraits<t>::Ttensor res(1, TensorDimens(Symmetry {i, j, 0, 0}, nvs));
res.zeros();
for (int n = 2; n <= k - 1; n += 2)
{
auto tmp = faaDiBrunoW<t>(Symmetry {i, j, n, k - n});
tmp->mult(static_cast<double>(PascalTriangle::noverk(k, n)));
tmp->contractAndAdd(2, res, m<t>().get(Symmetry {n}));
}
res.mult(-discount_factor);
return res;
}
template<Storage t>
typename ctraits<t>::Ttensor
KOrderWelfare::calcH_ik(int i, int k) const
{
return calcH_ijk<t>(i, 0, k);
}
template<Storage t>
typename ctraits<t>::Ttensor
KOrderWelfare::calcH_k(int k) const
{
return calcH_ijk<t>(0, 0, k);
}
template<Storage t>
typename ctraits<t>::Ttensor
KOrderWelfare::calcJ_ik(int i, int k) const
{
return calcJ_ijk<t>(i, 0, k);
}
template<Storage t>
typename ctraits<t>::Ttensor
KOrderWelfare::calcJ_k(int k) const
{
return calcJ_ijk<t>(0, 0, k);
}
/* Here we check for residuals of all the solved equations at the given order.
The method returns the largest residual size. Each check simply evaluates
the equation. */
template<Storage t>
double
KOrderWelfare::check(int dim) const
{
KORD_RAISE_IF(dim > W<t>().getMaxDim(), "Wrong dimension for KOrderWelfare::check");
JournalRecordPair pa(journal);
pa << "Checking residuals for order = " << dim << endrec;
double maxerror = 0.0;
// Check for F_yⁱuʲ=0
for (int i = 0; i <= dim; i++)
{
Symmetry sym {dim - i, i, 0, 0};
auto r = W<t>().get(sym);
r.add(-1.0, U<t>().get(sym));
r.add(-discount_factor, Wrond<t>().get(sym));
double err = r.getData().getMax();
JournalRecord(journal) << "\terror for symmetry " << sym << "\tis " << err << endrec;
maxerror = std::max(err, maxerror);
}
// Check for F_yⁱuʲuᵏ+Hᵢⱼₖ+Jᵢⱼₖ=0
for (auto& si : SymmetrySet(dim, 3))
{
int i = si[0];
int j = si[1];
int k = si[2];
if (i + j > 0 && k > 0 && is_even(k))
{
Symmetry sym {i, j, 0, k};
auto r = W<t>().get(sym);
r.add(-1.0, U<t>().get(sym));
r.add(-discount_factor, Wrond<t>().get(sym));
auto H_ijk = calcH_ijk<t>(i, j, k);
r.add(1.0, H_ijk);
auto J_ijk = calcJ_ijk<t>(i, j, k);
r.add(1.0, J_ijk);
double err = r.getData().getMax();
JournalRecord(journal) << "\terror for symmetry " << sym << "\tis " << err << endrec;
maxerror = std::max(err, maxerror);
}
}
// Check for F_σⁱ+Dᵢ+Eᵢ=0
if (is_even(dim))
{
Symmetry sym {0, 0, 0, dim};
auto r = W<t>().get(sym);
r.add(-1.0, U<t>().get(sym));
r.add(-discount_factor, Wrond<t>().get(sym));
auto H_k = calcH_k<t>(dim);
r.add(1.0, H_k);
auto J_k = calcJ_k<t>(dim);
r.add(1.0, J_k);
double err = r.getData().getMax();
JournalRecord(journal) << "\terror for symmetry " << sym << "\tis " << err << endrec;
maxerror = std::max(err, maxerror);
}
return maxerror;
}
#endif