dynare/mex/sources/k_order_welfare/k_ord_objective.hh

650 lines
20 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 © 2021-2024 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_HH
#define K_ORD_OBJECTIVE_HH
#include "k_ord_dynare.hh"
#include "objective_m.hh"
class KordwDynare;
class KordwDynare
{
public:
KordpDynare& model;
private:
Journal& journal;
Vector& params;
Vector resid;
TensorContainer<FSSparseTensor> ud; // planner's objective derivatives, in Dynare++ form
std::vector<int> dynToDynpp; // Maps Dynare jacobian variable indices to Dynare++ ones
std::unique_ptr<ObjectiveMFile> objectiveFile;
public:
KordwDynare(KordpDynare& m, Journal& jr, Vector& inParams,
std::unique_ptr<ObjectiveMFile> objectiveFile_arg, const std::vector<int>& varOrder);
void calcDerivativesAtSteady();
[[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_yⁱuʲ and calculate conditional F_yⁱuʲ and we have
the right hand side.
Requires: everything at order ≤ i+j1, Wrond_yⁱ⁺ʲ and W_yⁱ⁺ʲ.
Provides: W_yⁱuʲ and Wrond_yⁱuʲ
*/
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_yⁱuσʲ⁻ᵐ 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_yⁱuʲ and g_yⁱuʲ through
Dᵢⱼ, g_yⁱuᵐσʲ⁻ᵐ for m=1,…,j1 through Eᵢⱼ.
Provides: g_yⁱσʲ and G_yⁱσʲ, and finally G_yⁱuσʲ⁻ᵐ 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_yⁱuʲσᵏ (missing only for dimension l=1), then we
evaluate conditional F_yⁱuʲσᵏ. Before we can calculate Dᵢⱼₖ, and Eᵢⱼₖ, we
need to G_yⁱuʲuσᵏ⁻ᵐ for m=1,…,k. This is done by fillG(). Then we
have right hand side and we multiply by A⁻¹ to obtain g_yⁱuʲσᵏ. Finally we
have to update G_yⁱuʲσᵏ by multAndAdd() for dimension l=1.
Requires: everything at order ≤ i+j+k, g_yⁱ⁺ʲσᵏ through G_yⁱuʲσᵏ involved in
right hand side, then g_yⁱuʲ⁺ᵏ through Dᵢⱼₖ, and g_yⁱuʲ⁺ᵐσᵏ⁻ᵐ for m=1,…,k1
through Eᵢⱼₖ.
Provides: g_yⁱuʲσᵏ, G_yⁱuʲσᵏ, and G_yⁱuʲ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_yⁱuʲuᵏ]_α₁…αᵢβ₁…βⱼγ₁…γₖ [Σ]^γ₁…γₖ
= -β [Wrond_yⁱuʲ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_yⁱuʲuσᵏ⁻ᵐ]_α₁…αᵢβ₁…βⱼγ₁…γₘ [Σ]^γ₁…γₘ
ᵐ⁼¹
ₖ₋₁ ⎛k⎞
= -β ∑ ⎝m⎠ [Wrond_yⁱuʲ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