/* * 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 . */ #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 ud; // planner's objective derivatives, in Dynare++ form std::vector dynppToDyn; // Maps Dynare++ jacobian variable indices to Dynare ones std::vector dynToDynpp; // Maps Dynare jacobian variable indices to Dynare++ ones std::unique_ptr objectiveFile; public: KordwDynare(KordpDynare& m, ConstVector& NNZD_arg, Journal& jr, Vector& inParams, std::unique_ptr objectiveFile_arg, const std::vector& varOrder); void calcDerivativesAtSteady(); void populateDerivativesContainer(const std::vector& dyn_ud, int ord); [[nodiscard]] const TensorContainer& 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& 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& 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 typename ctraits::Tg& g(); template const typename ctraits::Tg& g() const; template typename ctraits::Tgs& gs(); template const typename ctraits::Tgs& gs() const; template typename ctraits::TU& U(); template const typename ctraits::TU& U() const; template typename ctraits::TW& W(); template const typename ctraits::TW& W() const; template typename ctraits::TWrond& Wrond(); template const typename ctraits::TWrond& Wrond() const; template typename ctraits::TXstack& Xstack(); template const typename ctraits::TXstack& Xstack() const; template typename ctraits::TGstack& Gstack(); template const typename ctraits::TGstack& Gstack() const; template typename ctraits::Tm& m(); template const typename ctraits::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& ucont, FGSContainer g, FGSContainer gs, const TwoDMatrix& v, Journal& jr); /* Performs k-order step provided that k=2 or the k−1-th step has been run, this is the core method */ template 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 [[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 std::unique_ptr::Ttensor> faaDiBrunoU(const Symmetry& sym) const; /* Calculates derivatives of the compounded functions W and Gstack using the Faà Di Bruno * formula*/ template std::unique_ptr::Ttensor> faaDiBrunoW(const Symmetry& sym) const; /* Solves the sylvester equation (templated fold, and unfold) */ template void sylvesterSolve(typename ctraits::Ttensor& der) const; // Recovers W_y*ⁱ template void recover_y(int i); // Recovers W_y*ⁱuʲ template void recover_yu(int i, int j); // Recovers W_y*ⁱσʲ template void recover_ys(int i, int j); // Recovers W_y*ⁱuʲσᵏ template void recover_yus(int i, int j, int k); // Recovers W_σⁱ template void recover_s(int i); // Calculates specified derivatives of Wrond and inserts them to the container template void fillWrond(int i, int j, int k); // Calculates Dᵢⱼₖ template typename ctraits::Ttensor calcH_ijk(int i, int j, int k) const; template typename ctraits::Ttensor calcH_ik(int i, int k) const; template typename ctraits::Ttensor calcH_k(int k) const; // Calculates Eᵢⱼₖ template typename ctraits::Ttensor calcJ_ijk(int i, int j, int k) const; template typename ctraits::Ttensor calcJ_ik(int i, int k) const; template typename ctraits::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 std::unique_ptr::Ttensor> KOrderWelfare::faaDiBrunoU(const Symmetry& sym) const { JournalRecordPair pa(journal); pa << "Faà Di Bruno U container for " << sym << endrec; auto res = std::make_unique::Ttensor>(1, TensorDimens(sym, nvs)); FaaDiBruno bruno(journal); bruno.calculate(Xstack(), u, *res); return res; } /* The same as KOrder::faaDiBrunoW(), but for W and G stack. */ template std::unique_ptr::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::Ttensor>(1, tdims); FaaDiBruno bruno(journal); bruno.calculate(Gstack(), W(), *res); return res; } template void KOrderWelfare::performStep(int order) { KORD_RAISE_IF(order - 1 != W().getMaxDim() and order > 1, "Wrong order for KOrder::performStep"); JournalRecordPair pa(journal); pa << "Performing step for order = " << order << endrec; recover_y(order); for (int i = 0; i < order; i++) recover_yu(i, order - i); recover_ys(order - 1, 1); for (int j = 2; j < order; j++) { for (int i = 1; i <= j - 1; i++) recover_yus(order - j, i, j - i); recover_ys(order - j, j); recover_yus(0, order - j, j); } recover_s(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 ≤ i−1 Provides: W_yⁱ and Wrond_yⁱ */ template 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(sym); auto Wrond_yi_ptr = Wrond_yi.get(); Wrond().insert(std::move(Wrond_yi)); auto W_yi = U().get(sym); W_yi.add(discount_factor, *Wrond_yi_ptr); sylvesterSolve(W_yi); W().insert(std::make_unique::Ttensor>(W_yi)); Gstack().multAndAdd(i, W(), *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+j−1, Wrond_yⁱ⁺ʲ and W_yⁱ⁺ʲ. Provides: W_yⁱuʲ and Wrond_yⁱuʲ */ template 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(sym); auto Wrond_yiuj_ptr = Wrond_yiuj.get(); Wrond().insert(std::move(Wrond_yiuj)); auto W_yiuj = U().get(sym); W_yiuj.add(discount_factor, *Wrond_yiuj_ptr); W().insert(std::make_unique::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+j−1, g_yⁱ⁺ʲ, G_yⁱu′ʲ and g_yⁱuʲ through Dᵢⱼ, g_yⁱuᵐσʲ⁻ᵐ for m=1,…,j−1 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 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(i, 0, j); if (is_even(j)) { auto Wrond_yisj = faaDiBrunoW(sym); auto Wrond_yisj_ptr = Wrond_yisj.get(); Wrond().insert(std::move(Wrond_yisj)); auto W_yisj = U().get(sym); W_yisj.add(discount_factor, *Wrond_yisj_ptr); { auto H_ij = calcH_ik(i, j); W_yisj.add(-1.0, H_ij); } if (j >= 3) { auto J_ij = calcJ_ik(i, j); W_yisj.add(-1.0, J_ij); } sylvesterSolve(W_yisj); W().insert(std::make_unique::Ttensor>(W_yisj)); Gstack().multAndAdd(i + j, W(), *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,…,k−1 through Eᵢⱼₖ. Provides: g_yⁱuʲσᵏ, G_yⁱuʲσᵏ, and G_yⁱuʲu′ᵐσᵏ⁻ᵐ for m=1,…,k */ template 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(i, j, k); if (is_even(k)) { auto Wrond_yiujsk = faaDiBrunoW(sym); auto Wrond_yiujsk_ptr = Wrond_yiujsk.get(); Wrond().insert(std::move(Wrond_yiujsk)); auto W_yiujsk = U().get(sym); W_yiujsk.add(discount_factor, *Wrond_yiujsk_ptr); auto H_ijk = calcH_ijk(i, j, k); W_yiujsk.add(-1.0, H_ijk); if (k >= 3) { auto J_ijk = calcJ_ijk(i, j, k); W_yiujsk.add(-1.0, J_ijk); } W().insert(std::make_unique::Ttensor>(W_yiujsk)); Gstack().multAndAdd(i + j + k, W(), *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 void KOrderWelfare::recover_s(int i) { Symmetry sym {0, 0, 0, i}; JournalRecordPair pa(journal); pa << "Conditional welfare: recovering symmetry " << sym << endrec; fillWrond(0, 0, i); if (is_even(i)) { auto Wrond_si = faaDiBrunoW(sym); auto Wrond_si_ptr = Wrond_si.get(); Wrond().insert(std::move(Wrond_si)); auto W_si = U().get(sym); W_si.add(discount_factor, *Wrond_si_ptr); { auto H_i = calcH_k(i); W_si.add(-1.0, H_i); } if (i >= 3) { auto J_i = calcJ_k(i); W_si.add(-1.0, J_i); } W_si.mult(1 / (1 - discount_factor)); W().insert(std::make_unique::Ttensor>(W_si)); Gstack().multAndAdd(i, W(), *Wrond_si_ptr); } } /* Here we calculate and insert Wrond_yⁱuʲu′ᵐσᵏ⁻ᵐ for m=1,…,k. The derivatives are inserted only for * k−m being even. */ template 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(Symmetry {i, j, m, k - m}); Wrond().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 typename ctraits::Ttensor KOrderWelfare::calcH_ijk(int i, int j, int k) const { typename ctraits::Ttensor res(1, TensorDimens(Symmetry {i, j, 0, 0}, nvs)); res.zeros(); if (is_even(k)) { auto tmp = faaDiBrunoW(Symmetry {i, j, k, 0}); tmp->contractAndAdd(2, res, m().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 typename ctraits::Ttensor KOrderWelfare::calcJ_ijk(int i, int j, int k) const { typename ctraits::Ttensor res(1, TensorDimens(Symmetry {i, j, 0, 0}, nvs)); res.zeros(); for (int n = 2; n <= k - 1; n += 2) { auto tmp = faaDiBrunoW(Symmetry {i, j, n, k - n}); tmp->mult(static_cast(PascalTriangle::noverk(k, n))); tmp->contractAndAdd(2, res, m().get(Symmetry {n})); } res.mult(-discount_factor); return res; } template typename ctraits::Ttensor KOrderWelfare::calcH_ik(int i, int k) const { return calcH_ijk(i, 0, k); } template typename ctraits::Ttensor KOrderWelfare::calcH_k(int k) const { return calcH_ijk(0, 0, k); } template typename ctraits::Ttensor KOrderWelfare::calcJ_ik(int i, int k) const { return calcJ_ijk(i, 0, k); } template typename ctraits::Ttensor KOrderWelfare::calcJ_k(int k) const { return calcJ_ijk(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 double KOrderWelfare::check(int dim) const { KORD_RAISE_IF(dim > W().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().get(sym); r.add(-1.0, U().get(sym)); r.add(-discount_factor, Wrond().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().get(sym); r.add(-1.0, U().get(sym)); r.add(-discount_factor, Wrond().get(sym)); auto H_ijk = calcH_ijk(i, j, k); r.add(1.0, H_ijk); auto J_ijk = calcJ_ijk(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().get(sym); r.add(-1.0, U().get(sym)); r.add(-discount_factor, Wrond().get(sym)); auto H_k = calcH_k(dim); r.add(1.0, H_k); auto J_k = calcJ_k(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