kth-order approximation of conditional welfare

Partially addresses issue #1680:
- unconditional welfare resorts to dynare++ simulation tools, which shall be updated very soon
TO DO:
- implement a function computing kth-order approximation of simulated moments of y
time-shift
Normann Rion 2021-06-15 14:30:32 +02:00 committed by Sébastien Villemot
parent edeb7911f3
commit 3d27672c58
No known key found for this signature in database
GPG Key ID: 2CECE9350ECEBE4A
32 changed files with 2229 additions and 17 deletions

View File

@ -1,6 +1,6 @@
/*
* Copyright © 2005 Ondra Kamenik
* Copyright © 2019 Dynare Team
* Copyright © 2019-2021 Dynare Team
*
* This file is part of Dynare.
*
@ -240,11 +240,14 @@ void
Approximation::saveRuleDerivs(const FGSContainer &g)
{
rule_ders = std::make_unique<FGSContainer>(g);
rule_ders_s = std::make_unique<FGSContainer>(4);
rule_ders_ss = std::make_unique<FGSContainer>(4);
for (auto &run : *rule_ders)
{
auto ten = std::make_unique<FGSTensor>(ypart.nstat+ypart.npred, ypart.nyss(), *(run.second));
rule_ders_ss->insert(std::move(ten));
auto ten_s = std::make_unique<FGSTensor>(ypart.nstat, ypart.nys(), *(run.second));
rule_ders_s->insert(std::move(ten_s));
auto ten_ss = std::make_unique<FGSTensor>(ypart.nstat+ypart.npred, ypart.nyss(), *(run.second));
rule_ders_ss->insert(std::move(ten_ss));
}
}

View File

@ -1,6 +1,6 @@
/*
* Copyright © 2005 Ondra Kamenik
* Copyright © 2019 Dynare Team
* Copyright © 2019-2021 Dynare Team
*
* This file is part of Dynare.
*
@ -122,6 +122,7 @@ class Approximation
DynamicModel &model;
Journal &journal;
std::unique_ptr<FGSContainer> rule_ders;
std::unique_ptr<FGSContainer> rule_ders_s;
std::unique_ptr<FGSContainer> rule_ders_ss;
std::unique_ptr<FoldDecisionRule> fdr;
std::unique_ptr<UnfoldDecisionRule> udr;
@ -156,6 +157,11 @@ public:
return *rule_ders;
}
const FGSContainer &
get_rule_ders_s() const
{
return *rule_ders_s;
}
const FGSContainer &
get_rule_ders_ss() const
{
return *rule_ders_ss;

View File

@ -1,6 +1,6 @@
/*
* Copyright © 2004 Ondra Kamenik
* Copyright © 2019 Dynare Team
* Copyright © 2019-2021 Dynare Team
*
* This file is part of Dynare.
*
@ -126,6 +126,7 @@ class DecisionRuleImpl : public ctraits<t>::Tpol, public DecisionRule
protected:
using _Tpol = typename ctraits<t>::Tpol;
using _Tg = typename ctraits<t>::Tg;
using _TW = typename ctraits<t>::TW;
using _Ttensor = typename ctraits<t>::Ttensor;
using _Ttensym = typename ctraits<t>::Ttensym;
const Vector ysteady;
@ -149,6 +150,12 @@ public:
{
fillTensors(g, sigma);
}
DecisionRuleImpl(const _TW &W, int nys, int nuu,
const ConstVector &ys)
: ctraits<t>::Tpol(1, nys+nuu), ysteady(ys), nu(nuu)
{
fillTensors(W, nys);
}
DecisionRuleImpl(const DecisionRuleImpl<t> &dr, const ConstVector &fixpoint)
: ctraits<t>::Tpol(dr.ypart.ny(), dr.ypart.nys()+dr.nu),
ysteady(fixpoint), ypart(dr.ypart), nu(dr.nu)
@ -179,6 +186,7 @@ public:
}
protected:
void fillTensors(const _Tg &g, double sigma);
void fillTensors(const _TW &W, int nys);
void centralize(const DecisionRuleImpl &dr);
public:
void eval(emethod em, Vector &out, const ConstVector &v) const override;
@ -265,6 +273,50 @@ DecisionRuleImpl<t>::fillTensors(const _Tg &g, double sigma)
}
}
template<Storage t>
void
DecisionRuleImpl<t>::fillTensors(const _TW &W, int nys)
{
IntSequence tns{nys, nu};
int dfact = 1;
for (int d = 0; d <= W.getMaxDim(); d++, dfact *= d)
{
auto W_yud = std::make_unique<_Ttensym>(1, nys+nu, d);
W_yud->zeros();
// fill tensor of g_yud of dimension d
/* Here we have to fill the tensor [g_(yu)ᵈ]. So we go through all pairs
(i,j) such that i+j=d, and through all k from zero up to maximal
dimension minus d. In this way we go through all symmetries of
[g_yuʲσ] which will be added to [g_(yu)].
Note that at the beginning, dfact is a factorial of d. We
calculate kfact is equal to k!. As indicated in
DecisionRuleImpl::fillTensors(), the added tensor is thus multiplied
with 1/(d!k!)·σ. */
for (int i = 0; i <= d; i++)
{
int j = d-i;
int kfact = 1;
_Ttensor tmp(1, TensorDimens(Symmetry{i, j}, tns));
tmp.zeros();
for (int k = 0; k+d <= W.getMaxDim(); k++, kfact *= k)
{
Symmetry sym{i, j, 0, k};
if (W.check(sym))
{
double mult = 1.0/dfact/kfact;
tmp.add(mult, W.get(sym));
}
}
W_yud->addSubTensor(tmp);
}
this->insert(std::move(W_yud));
}
}
/* The centralization is straightforward. We suppose here that the objects
steady state is the fix point . It is clear that the new derivatives
[g~_(yu)] will be equal to the derivatives of the original decision rule
@ -457,6 +509,11 @@ public:
: DecisionRuleImpl<Storage::fold>(g, yp, nuu, ys, sigma)
{
}
FoldDecisionRule(const ctraits<Storage::fold>::TW &W, int nys, int nuu,
const ConstVector &ys)
: DecisionRuleImpl<Storage::fold>(W, nys, nuu, ys)
{
}
FoldDecisionRule(const DecisionRuleImpl<Storage::fold> &dr, const ConstVector &fixpoint)
: DecisionRuleImpl<Storage::fold>(dr, fixpoint)
{

View File

@ -1,6 +1,6 @@
/*
* Copyright © 2004 Ondra Kamenik
* Copyright © 2019 Dynare Team
* Copyright © 2019-2021 Dynare Team
*
* This file is part of Dynare.
*
@ -82,7 +82,11 @@ public:
using Tgs = std::conditional_t<type == Storage::fold, FGSContainer, UGSContainer>;
using Tgss = std::conditional_t<type == Storage::fold, FGSContainer, UGSContainer>;
using TG = std::conditional_t<type == Storage::fold, FGSContainer, UGSContainer>;
using TU = std::conditional_t<type == Storage::fold, FGSContainer, UGSContainer>;
using TW = std::conditional_t<type == Storage::fold, FGSContainer, UGSContainer>;
using TWrond = std::conditional_t<type == Storage::fold, FGSContainer, UGSContainer>;
using TZstack = std::conditional_t<type == Storage::fold, FoldedZContainer, UnfoldedZContainer>;
using TXstack = std::conditional_t<type == Storage::fold, FoldedXContainer, UnfoldedXContainer>;
using TGstack = std::conditional_t<type == Storage::fold, FoldedGContainer, UnfoldedGContainer>;
using Tm = std::conditional_t<type == Storage::fold, FNormalMoments, UNormalMoments>;
using Tpol = std::conditional_t<type == Storage::fold, FTensorPolynomial, UTensorPolynomial>;
@ -113,6 +117,7 @@ struct PartitionY
const int npred;
const int nboth;
const int nforw;
PartitionY() : PartitionY(0,0,0,0) {}
PartitionY(int num_stat, int num_pred,
int num_both, int num_forw)
: nstat(num_stat), npred(num_pred),
@ -322,6 +327,11 @@ public:
{
return _ug;
}
const FGSContainer &
getFoldDersS() const
{
return _fgs;
}
static bool
is_even(int i)
{

View File

@ -1,6 +1,6 @@
/*
* Copyright © 2005 Ondra Kamenik
* Copyright © 2019 Dynare Team
* Copyright © 2019-2021 Dynare Team
*
* This file is part of Dynare.
*
@ -458,6 +458,11 @@ public:
{
return _ug;
}
const FGSContainer &
getFoldDersS() const
{
return _fgs;
}
protected:
template<Storage t>
std::unique_ptr<typename ctraits<t>::Ttensor> faaDiBrunoZ(const Symmetry &sym) const;

View File

@ -337,6 +337,65 @@ protected:
UGSTensor &out, std::mutex &mut) const;
};
/* Here is the specialization of the StackContainer. We implement
here the x needed in DSGE context for welfare assessment. We implement getType() and define a constructor feeding the data and sizes.
It depends on four variables U(y,u,u',σ), the variable u' being introduced to enable additions with 4-variable tensors*/
template<class _Ttype>
class XContainer : public StackContainer<_Ttype>
{
public:
using _Tparent = StackContainer<_Ttype>;
using _Stype = StackContainerInterface<_Ttype>;
using _Ctype = typename _Tparent::_Ctype;
using itype = typename _Tparent::itype;
XContainer(const _Ctype *g, int ng)
: _Tparent(1, 1)
{
_Tparent::stack_sizes = { ng };
_Tparent::conts[0] = g;
_Tparent::calculateOffsets();
}
/* Here we say, what happens if we derive z. recall the top of the
file, how z looks, and code is clear. */
itype
getType(int i, const Symmetry &s) const override
{
if (i==0)
if (s[2] > 0)
return itype::zero;
else
return itype::matrix;
TL_RAISE("Wrong stack index in XContainer::getType");
}
};
class FoldedXContainer : public XContainer<FGSTensor>,
public FoldedStackContainer
{
public:
using _Ctype = TensorContainer<FGSTensor>;
FoldedXContainer(const _Ctype *g, int ng)
: XContainer<FGSTensor>(g, ng)
{
}
};
class UnfoldedXContainer : public XContainer<UGSTensor>,
public UnfoldedStackContainer
{
public:
using _Ctype = TensorContainer<UGSTensor>;
UnfoldedXContainer(const _Ctype *g, int ng)
: XContainer<UGSTensor>(g, ng)
{
}
};
/* Here is the specialization of the StackContainer. We implement
here the z needed in DSGE context. We implement getType() and define
a constructor feeding the data and sizes.

View File

@ -169,8 +169,8 @@ if options_.ramsey_policy
planner_objective_value(2) = W;
else
%Order k code will go here!
fprintf('\nevaluate_planner_objective: order>2 not yet supported\n')
planner_objective_value(1) = NaN;
fprintf('\nevaluate_planner_objective: order>2 unconditional welfare calculation not yet supported\n')
planner_objective_value(1) = k_order_welfare(dr, M_, options_);
planner_objective_value(2) = NaN;
return
end

View File

@ -0,0 +1,60 @@
% [unc_welfare, U_dynpp_derivs, W_dynpp_derivs, U_dyn_derivs, W_dyn_derivs] = k_order_welfare(dr, DynareModel, DynareOptions)
% computes a k-th order approximation of welfare
%
% INPUTS
% dr: struct describing the reduced form solution of the model.
% DynareModel: struct jobs's parameters
% DynareOptions: struct job's options
%
% OUTPUTS
%
% unc_welfare double approximation of conditional welfare from the non-stochastic steady state and allowing stochastic shocks
%
% U_dynpp_derivs struct Derivatives of the felicity function in Dynare++ format.
% The tensors are folded and the Taylor coefficients (1/n!) are
% included.
% Fieldnames are U_0, U_1, U_2, …
% W_dynpp_derivs struct Derivatives of the welfare function in Dynare++ format.
% The tensors are folded and the Taylor coefficients (1/n!) are
% included.
% Fieldnames are W_0, W_1, W_2, …
% U_dyn_derivs struct Derivatives of the felicity function in Dynare format, up to third order.
% Matrices are dense and unfolded. The Taylor coefficients (1/2
% and 1/6) aren't included.
% The derivatives w.r.t. different categories of variables
% are separated.
% Fieldnames are:
% + Uy, Uu
% + if order ≥ 2: Uyy, Uyu, Uuu, Uss
% + if order ≥ 3: Uyyy, Uyyu, Uyuu, Uuuu, Uyss, Uuss
%
% W_dyn_derivs struct Derivatives of the welfare function in Dynare format, up to third order.
% Matrices are dense and unfolded. The Taylor coefficients (1/2
% and 1/6) aren't included.
% The derivatives w.r.t. different categories of variables
% are separated.
% Fieldnames are:
% + Wy, Wu
% + if order ≥ 2: Wyy, Wyu, Wuu, Wss
% + if order ≥ 3: Wyyy, Wyyu, Wyuu, Wuuu, Wyss, Wuss
%
% k_order_welfare is a compiled MEX function. Its source code is in
% dynare/mex/sources/k_order_welfare/k_order_welfare.cc and it uses code provided by
% dynare++
% Copyright (C) 2021 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/>.

View File

@ -7,13 +7,9 @@ k_order_perturbation_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/../../../dynare++
k_order_perturbation_CXXFLAGS = $(AM_CXXFLAGS) $(THREAD_CXXFLAGS)
k_order_perturbation_LDFLAGS = $(AM_LDFLAGS) $(LDFLAGS_MATIO)
k_order_perturbation_LDADD = ../libdynare++/libdynare++.a $(LIBADD_DLOPEN) $(LIBADD_MATIO)
k_order_perturbation_LDADD = ../libdynare++/libdynare++.a ../libkorder/libkorder.a $(LIBADD_DLOPEN) $(LIBADD_MATIO)
nodist_k_order_perturbation_SOURCES = \
k_order_perturbation.cc \
k_ord_dynare.cc \
dynamic_dll.cc \
dynamic_m.cc
nodist_k_order_perturbation_SOURCES = k_order_perturbation.cc
BUILT_SOURCES = $(nodist_k_order_perturbation_SOURCES)
CLEANFILES = $(nodist_k_order_perturbation_SOURCES)

View File

@ -0,0 +1,22 @@
mex_PROGRAMS = k_order_welfare
TOPDIR = $(top_srcdir)/../../sources/k_order_welfare
k_order_welfare_CPPFLAGS = $(AM_CPPFLAGS) -I$(TOPDIR) -I$(TOPDIR)/../k_order_perturbation -I$(top_srcdir)/../../../dynare++/tl/cc -I$(top_srcdir)/../../../dynare++/kord -I$(top_srcdir)/../../../dynare++/src -I$(top_srcdir)/../../../dynare++/sylv/cc -I$(top_srcdir)/../../../dynare++/utils/cc $(CPPFLAGS_MATIO)
k_order_welfare_CXXFLAGS = $(AM_CXXFLAGS) $(THREAD_CXXFLAGS)
k_order_welfare_LDFLAGS = $(AM_LDFLAGS) $(LDFLAGS_MATIO)
k_order_welfare_LDADD = ../libdynare++/libdynare++.a ../libkorder/libkorder.a $(LIBADD_DLOPEN) $(LIBADD_MATIO)
nodist_k_order_welfare_SOURCES = \
k_order_welfare.cc \
approximation_welfare.cc \
k_ord_objective.cc \
objective_m.cc
BUILT_SOURCES = $(nodist_k_order_welfare_SOURCES)
CLEANFILES = $(nodist_k_order_welfare_SOURCES)
%.cc: $(TOPDIR)/%.cc
$(LN_S) -f $< $@

18
mex/build/libkorder.am Normal file
View File

@ -0,0 +1,18 @@
noinst_LIBRARIES = libkorder.a
TOPDIR = $(top_srcdir)/../../sources/k_order_perturbation
libkorder_a_CPPFLAGS = $(AM_CPPFLAGS) -I$(TOPDIR) -I$(top_srcdir)/../../../dynare++/tl/cc -I$(top_srcdir)/../../../dynare++/sylv/cc -I$(top_srcdir)/../../../dynare++/src -I$(top_srcdir)/../../../dynare++/kord -I$(top_srcdir)/../../../dynare++/utils/cc $(CPPFLAGS_MATIO)
libkorder_a_CXXFLAGS = $(AM_CXXFLAGS) $(THREAD_CXXFLAGS)
nodist_libkorder_a_SOURCES = \
k_ord_dynare.cc \
dynamic_dll.cc \
dynamic_m.cc
BUILT_SOURCES = $(nodist_libkorder_a_SOURCES)
CLEANFILES = $(nodist_libkorder_a_SOURCES)
%.cc: $(TOPDIR)/%.cc
$(LN_S) -f $< $@

View File

@ -4,7 +4,7 @@ SUBDIRS = mjdgges kronecker bytecode block_kalman_filter sobol perfect_foresight
# libdynare++ must come before gensylv, k_order_perturbation, dynare_simul_
if ENABLE_MEX_DYNAREPLUSPLUS
SUBDIRS += libdynare++ gensylv k_order_perturbation dynare_simul_ local_state_space_iterations
SUBDIRS += libdynare++ gensylv libkorder dynare_simul_ k_order_perturbation k_order_welfare local_state_space_iterations
endif
if ENABLE_MEX_MS_SBVAR

View File

@ -153,7 +153,9 @@ AC_CONFIG_FILES([Makefile
bytecode/Makefile
libdynare++/Makefile
gensylv/Makefile
libkorder/Makefile
k_order_perturbation/Makefile
k_order_welfare/Makefile
dynare_simul_/Makefile
kalman_steady_state/Makefile
ms_sbvar/Makefile

View File

@ -0,0 +1,2 @@
include ../mex.am
include ../../k_order_welfare.am

View File

@ -0,0 +1,2 @@
include ../mex.am
include ../../libkorder.am

View File

@ -4,7 +4,7 @@ SUBDIRS = mjdgges kronecker bytecode block_kalman_filter sobol perfect_foresight
# libdynare++ must come before gensylv, k_order_perturbation, dynare_simul_
if ENABLE_MEX_DYNAREPLUSPLUS
SUBDIRS += libdynare++ gensylv k_order_perturbation dynare_simul_ local_state_space_iterations
SUBDIRS += libdynare++ gensylv libkorder dynare_simul_ k_order_perturbation k_order_welfare local_state_space_iterations
endif
if ENABLE_MEX_MS_SBVAR

View File

@ -135,7 +135,9 @@ AC_CONFIG_FILES([Makefile
bytecode/Makefile
libdynare++/Makefile
gensylv/Makefile
libkorder/Makefile
k_order_perturbation/Makefile
k_order_welfare/Makefile
dynare_simul_/Makefile
kalman_steady_state/Makefile
ms_sbvar/Makefile

View File

@ -0,0 +1,3 @@
EXEEXT = .mex
include ../mex.am
include ../../k_order_welfare.am

View File

@ -0,0 +1,3 @@
EXEEXT = .mex
include ../mex.am
include ../../libkorder.am

View File

@ -10,6 +10,7 @@ EXTRA_DIST = \
kronecker \
bytecode \
k_order_perturbation \
k_order_welfare \
kalman_steady_state \
ms-sbvar \
block_kalman_filter \

View File

@ -138,10 +138,30 @@ public:
return nExog;
}
int
ny() const
{
return nStat+nBoth+nPred+nForw;
}
int
nys() const
{
return nBoth+nPred;
}
int
order() const override
{
return nOrder;
}
const std::vector<int> &
getDynppToDyn() const
{
return dynppToDyn;
}
const std::vector<int> &
getDynToDynpp() const
{
return dynToDynpp;
}
const NameList &
getAllEndoNames() const override
{

View File

@ -0,0 +1,96 @@
/*
* Copyright © 2005 Ondra Kamenik
* Copyright © 2019-2021 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 <utility>
#include "kord_exception.hh"
#include "approximation_welfare.hh"
ApproximationWelfare::ApproximationWelfare(KordwDynare &w, double discount_factor_arg, const FGSContainer &rule_ders_arg, const FGSContainer &rule_ders_s_arg, Journal &j)
: welfare{w}, discount_factor(discount_factor_arg), cond(1),
nvs{welfare.getModel().nys(), welfare.getModel().nexog(), welfare.getModel().nexog(), 1}, journal{j}
{
rule_ders = std::make_unique<FGSContainer>(rule_ders_arg);
rule_ders_s = std::make_unique<FGSContainer>(rule_ders_s_arg);
}
/* This methods assumes that the deterministic steady state is
model.getSteady(). It makes an approximation about it and stores the
derivatives to rule_ders and rule_ders_ss. Also it runs a check() for
σ=0. */
void
ApproximationWelfare::approxAtSteady()
{
welfare.calcDerivativesAtSteady();
KOrderWelfare korderwel(welfare.getModel().nstat(), welfare.getModel().npred(), welfare.getModel().nboth(), welfare.getModel().nforw(), welfare.getModel().nexog(), welfare.getModel().order(), discount_factor, welfare.getPlannerObjDerivatives(), get_rule_ders(), get_rule_ders_s(), welfare.getModel().getVcov(), journal);
for (int k = 1; k <= welfare.getModel().order(); k++)
korderwel.performStep<Storage::fold>(k);
saveRuleDerivs(korderwel.getFoldU(), korderwel.getFoldW());
// construct the welfare approximations
calcStochShift(); //conditional welfare
// construct the resulting decision rules
unc_fdr = std::make_unique<FoldDecisionRule>(*unc_ders, welfare.getModel().nys(), welfare.getModel().nexog(), welfare.getModel().getSteady());
cond_fdr = std::make_unique<FoldDecisionRule>(*cond_ders, welfare.getModel().nys(), welfare.getModel().nexog(), welfare.getModel().getSteady());
}
/* This just returns fdr with a check that it is created. */
const FoldDecisionRule &
ApproximationWelfare::getFoldUncWel() const
{
KORD_RAISE_IF(!unc_fdr,
"Folded decision rule has not been created in ApproximationWelfare::getFoldUncWel");
return *unc_fdr;
}
const FoldDecisionRule &
ApproximationWelfare::getFoldCondWel() const
{
KORD_RAISE_IF(!cond_fdr,
"Folded decision rule has not been created in ApproximationWelfare::getFoldCondWel");
return *cond_fdr;
}
void
ApproximationWelfare::saveRuleDerivs(const FGSContainer &U, const FGSContainer &W)
{
unc_ders = std::make_unique<FGSContainer>(U);
cond_ders = std::make_unique<FGSContainer>(W);
}
/* This method calculates the shift of the conditional welfare function w.r.t its steady-state value because of the presence of stochastic shocks, i.e.
1
W Wbar + W_σ
d=1 d!
*/
void
ApproximationWelfare::calcStochShift()
{
cond.zeros();
cond.add(1.0/(1.0-discount_factor), welfare.getResid());
KORD_RAISE_IF(cond.length() != 1,
"Wrong length of output vector for ApproximationWelfare::calcStochShift");
int dfac = 1;
for (int d = 1; d <= cond_ders->getMaxDim(); d++, dfac *= d)
if (KOrderWelfare::is_even(d))
{
Symmetry sym{0, 0, 0, d};
cond.add(1.0/dfac, cond_ders->get(sym).getData());
}
}

View File

@ -0,0 +1,88 @@
/*
* Copyright © 2021 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 APPROXIMATION_WELFARE_H
#define APPROXIMATION_WELFARE_H
#include "k_ord_objective.hh"
#include "journal.hh"
#include <memory>
class ApproximationWelfare
{
KordwDynare &welfare;
double discount_factor;
std::unique_ptr<FGSContainer> rule_ders;
std::unique_ptr<FGSContainer> rule_ders_s;
std::unique_ptr<FGSContainer> unc_ders;
std::unique_ptr<FGSContainer> cond_ders;
std::unique_ptr<FoldDecisionRule> unc_fdr;
std::unique_ptr<FoldDecisionRule> cond_fdr;
Vector cond;
// const FNormalMoments mom;
IntSequence nvs;
// TwoDMatrix ss;
Journal &journal;
public:
ApproximationWelfare(KordwDynare &w, double discount_factor, const FGSContainer &rule_ders, const FGSContainer &rule_ders_s, Journal &j);
const KordwDynare &
getWelfare() const
{
return welfare;
}
const FGSContainer &
get_rule_ders() const
{
return *rule_ders;
}
const FGSContainer &
get_rule_ders_s() const
{
return *rule_ders_s;
}
const FGSContainer &
get_unc_ders() const
{
return *unc_ders;
}
const FGSContainer &
get_cond_ders() const
{
return *cond_ders;
}
const Vector &
getCond() const
{
return cond;
}
const FoldDecisionRule & getFoldUncWel() const;
const FoldDecisionRule & getUnfoldUncWel() const;
const FoldDecisionRule & getFoldCondWel() const;
const FoldDecisionRule & getUnfoldCondWel() const;
void approxAtSteady();
protected:
void saveRuleDerivs(const FGSContainer &U, const FGSContainer &W);
void calcStochShift();
};
#endif

View File

@ -0,0 +1,389 @@
/*
* Copyright © 2021 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 "k_ord_objective.hh"
#include "objective_abstract_class.hh"
#include <utility>
#include <cassert>
KordwDynare::KordwDynare(KordpDynare &m, ConstVector &NNZD_arg, Journal &jr, Vector &inParams, std::unique_ptr<ObjectiveAC> 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;
}
void
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));
}
template<>
ctraits<Storage::unfold>::Tg &
KOrderWelfare::g<Storage::unfold>()
{
return _ug;
}
template<>
const ctraits<Storage::unfold>::Tg &
KOrderWelfare::g<Storage::unfold>() const
{
return _ug;
}
template<>
ctraits<Storage::fold>::Tg &
KOrderWelfare::g<Storage::fold>()
{
return _fg;
}
template<>
const ctraits<Storage::fold>::Tg &
KOrderWelfare::g<Storage::fold>() const
{
return _fg;
}
template<>
ctraits<Storage::unfold>::Tgs &
KOrderWelfare::gs<Storage::unfold>()
{
return _ugs;
}
template<>
const ctraits<Storage::unfold>::Tgs &
KOrderWelfare::gs<Storage::unfold>() const
{
return _ugs;
}
template<>
ctraits<Storage::fold>::Tgs &
KOrderWelfare::gs<Storage::fold>()
{
return _fgs;
}
template<>
const ctraits<Storage::fold>::Tgs &
KOrderWelfare::gs<Storage::fold>() const
{
return _fgs;
}
template<>
ctraits<Storage::unfold>::TU &
KOrderWelfare::U<Storage::unfold>()
{
return _uU;
}
template<>
const ctraits<Storage::unfold>::TU &
KOrderWelfare::U<Storage::unfold>() const
{
return _uU;
}
template<>
ctraits<Storage::fold>::TU &
KOrderWelfare::U<Storage::fold>()
{
return _fU;
}
template<>
const ctraits<Storage::fold>::TU &
KOrderWelfare::U<Storage::fold>() const
{
return _fU;
}
template<>
ctraits<Storage::unfold>::TW &
KOrderWelfare::W<Storage::unfold>()
{
return _uW;
}
template<>
const ctraits<Storage::unfold>::TW &
KOrderWelfare::W<Storage::unfold>() const
{
return _uW;
}
template<>
ctraits<Storage::fold>::TW &
KOrderWelfare::W<Storage::fold>()
{
return _fW;
}
template<>
const ctraits<Storage::fold>::TW &
KOrderWelfare::W<Storage::fold>() const
{
return _fW;
}
template<>
ctraits<Storage::unfold>::TWrond &
KOrderWelfare::Wrond<Storage::unfold>()
{
return _uWrond;
}
template<>
const ctraits<Storage::unfold>::TWrond &
KOrderWelfare::Wrond<Storage::unfold>() const
{
return _uWrond;
}
template<>
ctraits<Storage::fold>::TWrond &
KOrderWelfare::Wrond<Storage::fold>()
{
return _fWrond;
}
template<>
const ctraits<Storage::fold>::TWrond &
KOrderWelfare::Wrond<Storage::fold>() const
{
return _fWrond;
}
template<>
ctraits<Storage::unfold>::TGstack &
KOrderWelfare::Gstack<Storage::unfold>()
{
return _uGstack;
}
template<>
const ctraits<Storage::unfold>::TGstack &
KOrderWelfare::Gstack<Storage::unfold>() const
{
return _uGstack;
}
template<>
ctraits<Storage::fold>::TGstack &
KOrderWelfare::Gstack<Storage::fold>()
{
return _fGstack;
}
template<>
const ctraits<Storage::fold>::TGstack &
KOrderWelfare::Gstack<Storage::fold>() const
{
return _fGstack;
}
template<>
ctraits<Storage::unfold>::TXstack &
KOrderWelfare::Xstack<Storage::unfold>()
{
return _uXstack;
}
template<>
const ctraits<Storage::unfold>::TXstack &
KOrderWelfare::Xstack<Storage::unfold>() const
{
return _uXstack;
}
template<>
ctraits<Storage::fold>::TXstack &
KOrderWelfare::Xstack<Storage::fold>()
{
return _fXstack;
}
template<>
const ctraits<Storage::fold>::TXstack &
KOrderWelfare::Xstack<Storage::fold>() const
{
return _fXstack;
}
template<>
ctraits<Storage::unfold>::Tm &
KOrderWelfare::m<Storage::unfold>()
{
return _um;
}
template<>
const ctraits<Storage::unfold>::Tm &
KOrderWelfare::m<Storage::unfold>() const
{
return _um;
}
template<>
ctraits<Storage::fold>::Tm &
KOrderWelfare::m<Storage::fold>()
{
return _fm;
}
template<>
const ctraits<Storage::fold>::Tm &
KOrderWelfare::m<Storage::fold>() const
{
return _fm;
}
KOrderWelfare::KOrderWelfare(int num_stat, int num_pred,
int num_both, int num_forw, int nu, int ord,
double discount_factor,
const TensorContainer<FSSparseTensor> &ucont,
const FGSContainer &g_arg,
const FGSContainer &gs_arg,
const TwoDMatrix &v,
Journal &jr) :
ypart(num_stat, num_pred, num_both, num_forw),
ny(ypart.ny()), nu(nu), maxk(ucont.getMaxDim()), order(ord),
discount_factor{discount_factor}, nvs{ypart.nys(), nu, nu, 1},
_uU(4), _fU(4), _uW(4), _fW(4), _uWrond(4), _fWrond(4), _ug(4),
_fg(g_arg), _ugs(4), _fgs(gs_arg), _uXstack(&_ug, ny),
_fXstack(&_fg, ny), _uGstack(&_ugs, ypart.nys(), nu),
_fGstack(&_fgs, ypart.nys(), nu), _um(maxk, v),
_fm(_um), u(ucont), journal(jr)
{
KORD_RAISE_IF(v.ncols() != nu,
"Wrong number of columns of Vcov in KOrderWelfare constructor");
KORD_RAISE_IF(nu != v.nrows(),
"Wrong number of rows of Vcov in KOrderWelfare constructor");
for (int ord = 1; ord <= order; ord++)
{
JournalRecordPair pa(journal);
pa << u8"Unconditional welfare : performing step for order = " << ord << "\n" << endrec;
for (int j = 0; j <= ord; j++)
for (int i = 0; i <= j; i++)
{
Symmetry sym{ord-j, i, 0, j-i};
pa << "Recovering symmetry " << sym << "\n" << endrec;
auto U_sym = faaDiBrunoU<Storage::fold>(sym);
U<Storage::fold>().insert(std::move(U_sym));
}
}
U<Storage::unfold>() = UGSContainer(U<Storage::fold>());
g<Storage::unfold>() = UGSContainer(g<Storage::fold>());
gs<Storage::unfold>() = UGSContainer(gs<Storage::fold>());
}
// KOrderWelfare::sylvesterSolve() unfolded specialization
/* Here we have an unfolded specialization of sylvesterSolve(). We simply
create the sylvester object and solve it. Note that the W_y is not
continuous in memory as assumed by the sylvester code, so we make a
temporary copy and pass it as matrix C.
If the B matrix is empty, in other words there are now forward looking
variables, then the system becomes AX=D which is solved by simple
matA.multInv().
If one wants to display the diagnostic messages from the Sylvester module,
then after the sylv.solve() one needs to call sylv.getParams().print(""). */
template<>
void
KOrderWelfare::sylvesterSolve<Storage::unfold>(ctraits<Storage::unfold>::Ttensor &der) const
{
JournalRecordPair pa(journal);
pa << "Sylvester equation for dimension = " << der.getSym()[0] << endrec;
KORD_RAISE_IF(!der.isFinite(),
"RHS of Sylverster is not finite");
TwoDMatrix gs_y(gs<Storage::unfold>().get(Symmetry{1, 0, 0, 0}));
TwoDMatrix A(1,1);
A.unit();
TwoDMatrix B(1,1);
B.unit();
B.mult(-discount_factor);
GeneralSylvester sylv(der.getSym()[0], 1, ypart.nys(),
0,
A.getData(), B.getData(),
gs_y.getData(), der.getData());
sylv.solve();
}
// KOrder::sylvesterSolve() folded specialization
/* Here is the folded specialization of sylvester. We unfold the right hand
side. Then we solve it by the unfolded version of sylvesterSolve(), and fold
it back and copy to output vector. */
template<>
void
KOrderWelfare::sylvesterSolve<Storage::fold>(ctraits<Storage::fold>::Ttensor &der) const
{
ctraits<Storage::unfold>::Ttensor tmp(der);
sylvesterSolve<Storage::unfold>(tmp);
ctraits<Storage::fold>::Ttensor ftmp(tmp);
der.getData() = const_cast<const Vector &>(ftmp.getData());
}

View File

@ -0,0 +1,653 @@
/*
* Copyright © 2021 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);
const TensorContainer<FSSparseTensor> &
getPlannerObjDerivatives() const
{
return ud;
}
const KordpDynare &
getModel() const
{
return model;
}
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,
const FGSContainer &g,
const 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>
double check(int dim) const;
const FGSContainer &
getFoldU() const
{
return _fU;
}
const UGSContainer &
getUnfoldU() const
{
return _uU;
}
const FGSContainer &
getFoldW() const
{
return _fW;
}
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 << u8"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 << u8"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

View File

@ -0,0 +1,392 @@
/*
* Copyright © 2021 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 "dynamic_m.hh"
#include "dynamic_dll.hh"
#include "objective_m.hh"
#include "approximation.hh"
#include "approximation_welfare.hh"
#include "exception.hh"
#include "dynare_exception.hh"
#include "kord_exception.hh"
#include "tl_exception.hh"
#include "SylvException.hh"
#include <algorithm>
#include <cassert>
#include "dynmex.h"
/* Convert MATLAB Dynare endo and exo names cell array to a vector<string> array of
string pointers. */
std::vector<std::string>
DynareMxArrayToString(const mxArray *mxFldp)
{
assert(mxIsCell(mxFldp));
std::vector<std::string> r;
for (size_t i = 0; i < mxGetNumberOfElements(mxFldp); i++)
{
const mxArray *cell_mx = mxGetCell(mxFldp, i);
if (!(cell_mx && mxIsChar(cell_mx)))
mexErrMsgTxt("Cell is not a character array");
r.emplace_back(mxArrayToString(cell_mx));
}
return r;
}
/* Vector for storing field names like “W_0”, “W_1”, …
A static structure is needed since MATLAB apparently does not create its own
copy of the strings (contrary to what is said at:
https://fr.mathworks.com/matlabcentral/answers/315937-mxcreatestructarray-and-mxcreatestructmatrix-field-name-memory-management
)
*/
std::vector<std::string> U_fieldnames;
std::vector<std::string> W_fieldnames;
void
copy_derivatives(mxArray *destin, const Symmetry &sym, const FGSContainer &derivs, const char *fieldname)
{
const FGSTensor &x = derivs.get(sym);
auto x_unfolded = x.unfold();
int n = x_unfolded->nrows();
int m = x_unfolded->ncols();
mxArray *tmp = mxCreateDoubleMatrix(n, m, mxREAL);
std::copy_n(x_unfolded->getData().base(), n*m, mxGetPr(tmp));
mxSetField(destin, 0, fieldname, tmp);
}
/* The k_order_welfare function computes the conditional welfare starting from the non-stochastic steady state and the derivatives of the felicity and welfare functions U(h(yₜ₋₁, uₜ, σ)) and W(yₜ₋₁, uₜ, σ). The unconditional welfare shall be introduced in a future version with the update of the dynare_simul_.
The routine proceeds in several steps:
1. Computation of the kth-order policy functions: the code is a copy-paste from the k_order_perturbation.cc file
2. Computation of the kth-order derivatives of the felicity and of the welfare functions. The call to the approxAtSteady method in the ApproximationWelfare class carries out the necessary operation
- Importing the derivatives of the felicity function with the calcDerivativesAtSteady() method of the KordwDynare class. It relies on the Matlab-generated files, which are handled by the ObjectiveAC and ObjectiveMFile classes
- Pinpointing the derivatives of the felicity and welfare functions. The performStep method of the KOrderWelfare class carries out the calculations,resorting to the FaaDiBruno class and its methods to get the needed intermediary results.
*/
extern "C" {
void mexFunction(int nlhs, mxArray *plhs[],
int nrhs, const mxArray *prhs[])
{
mexPrintf("k_order_welfare\n");
const mxArray *dr_mx = prhs[0];
const mxArray *M_mx = prhs[1];
const mxArray *options_mx = prhs[2];
auto get_int_field = [](const mxArray *struct_mx, const std::string &fieldname)
{
mxArray *field_mx = mxGetField(struct_mx, 0, fieldname.c_str());
if (!(field_mx && mxIsScalar(field_mx) && mxIsNumeric(field_mx)))
mexErrMsgTxt(("Field `" + fieldname + "' should be a numeric scalar").c_str());
return static_cast<int>(mxGetScalar(field_mx));
};
// Extract various fields from options_
const mxArray *ramsey_policy_mx = mxGetField(options_mx, 0, "ramsey_policy");
if (!(ramsey_policy_mx && mxIsLogicalScalar(ramsey_policy_mx)))
mexErrMsgTxt("options_.ramsey_policy should be a logical scalar");
bool ramsey_policy = static_cast<bool>(mxGetScalar(ramsey_policy_mx));
if (ramsey_policy == false)
mexErrMsgTxt("The considered model must be a Ramsey-typed model !");
const int kOrder = get_int_field(options_mx, "order");
if (kOrder < 1)
mexErrMsgTxt("options_.order must be at least 1");
const mxArray *use_dll_mx = mxGetField(options_mx, 0, "use_dll");
if (!(use_dll_mx && mxIsLogicalScalar(use_dll_mx)))
mexErrMsgTxt("options_.use_dll should be a logical scalar");
bool use_dll = static_cast<bool>(mxGetScalar(use_dll_mx));
double qz_criterium = 1+1e-6;
const mxArray *qz_criterium_mx = mxGetField(options_mx, 0, "qz_criterium");
if (qz_criterium_mx && mxIsScalar(qz_criterium_mx) && mxIsNumeric(qz_criterium_mx))
qz_criterium = mxGetScalar(qz_criterium_mx);
const mxArray *threads_mx = mxGetField(options_mx, 0, "threads");
if (!threads_mx)
mexErrMsgTxt("Can't find field options_.threads");
const mxArray *num_threads_mx = mxGetField(threads_mx, 0, "k_order_perturbation");
if (!(num_threads_mx && mxIsScalar(num_threads_mx) && mxIsNumeric(num_threads_mx)))
mexErrMsgTxt("options_.threads.k_order_perturbation be a numeric scalar");
int num_threads = static_cast<int>(mxGetScalar(num_threads_mx));
const mxArray *debug_mx = mxGetField(options_mx, 0, "debug");
if (!(debug_mx && mxIsLogicalScalar(debug_mx)))
mexErrMsgTxt("options_.debug should be a logical scalar");
bool debug = static_cast<bool>(mxGetScalar(debug_mx));
// Extract various fields from M_
const mxArray *fname_mx = mxGetField(M_mx, 0, "fname");
if (!(fname_mx && mxIsChar(fname_mx) && mxGetM(fname_mx) == 1))
mexErrMsgTxt("M_.fname should be a character string");
std::string fname{mxArrayToString(fname_mx)};
const mxArray *params_mx = mxGetField(M_mx, 0, "params");
if (!(params_mx && mxIsDouble(params_mx)))
mexErrMsgTxt("M_.params should be a double precision array");
Vector modParams{ConstVector{params_mx}};
if (!modParams.isFinite())
mexErrMsgTxt("M_.params contains NaN or Inf");
const mxArray *param_names_mx = mxGetField(M_mx, 0, "param_names");
if (!(param_names_mx && mxIsCell(param_names_mx)))
mexErrMsgTxt("M_.param_names should be a cell array");
std::vector<std::string> paramNames = DynareMxArrayToString(param_names_mx);
auto it = std::find(paramNames.begin(), paramNames.end(), "optimal_policy_discount_factor");
double discount_factor;
if (it != paramNames.end())
discount_factor = modParams[std::distance(paramNames.begin(), it)];
else
{
mexErrMsgTxt("M_.param_names does not contain any \"optimal_policy_discount_factor\".");
return; // To silence a GCC warning about discount_factor unitialized
}
const mxArray *sigma_e_mx = mxGetField(M_mx, 0, "Sigma_e");
if (!(sigma_e_mx && mxIsDouble(sigma_e_mx) && mxGetM(sigma_e_mx) == mxGetN(sigma_e_mx)))
mexErrMsgTxt("M_.Sigma_e should be a double precision square matrix");
TwoDMatrix vCov{ConstTwoDMatrix{sigma_e_mx}};
if (!vCov.isFinite())
mexErrMsgTxt("M_.Sigma_e contains NaN or Inf");
const int nStat = get_int_field(M_mx, "nstatic");
const int nPred = get_int_field(M_mx, "npred");
const int nBoth = get_int_field(M_mx, "nboth");
const int nForw = get_int_field(M_mx, "nfwrd");
const int nExog = get_int_field(M_mx, "exo_nbr");
const int nEndo = get_int_field(M_mx, "endo_nbr");
const int nPar = get_int_field(M_mx, "param_nbr");
const mxArray *lead_lag_incidence_mx = mxGetField(M_mx, 0, "lead_lag_incidence");
if (!(lead_lag_incidence_mx && mxIsDouble(lead_lag_incidence_mx) && mxGetM(lead_lag_incidence_mx) == 3
&& mxGetN(lead_lag_incidence_mx) == static_cast<size_t>(nEndo)))
mexErrMsgTxt("M_.lead_lag_incidence should be a double precision matrix with 3 rows and M_.endo_nbr columns");
ConstTwoDMatrix llincidence{lead_lag_incidence_mx};
const mxArray *nnzderivatives_mx = mxGetField(M_mx, 0, "NNZDerivatives");
if (!(nnzderivatives_mx && mxIsDouble(nnzderivatives_mx)))
mexErrMsgTxt("M_.NNZDerivatives should be a double precision array");
ConstVector NNZD{nnzderivatives_mx};
if (NNZD.length() < kOrder || NNZD[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 *nnzderivatives_obj_mx = mxGetField(M_mx, 0, "NNZDerivatives_objective");
if (!(nnzderivatives_obj_mx && mxIsDouble(nnzderivatives_obj_mx)))
mexErrMsgTxt("M_.NNZDerivatives should be a double precision 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)))
mexErrMsgTxt("M_.endo_names should be a cell array of M_.endo_nbr elements");
std::vector<std::string> endoNames = DynareMxArrayToString(endo_names_mx);
const mxArray *exo_names_mx = mxGetField(M_mx, 0, "exo_names");
if (!(exo_names_mx && mxIsCell(exo_names_mx) && mxGetNumberOfElements(exo_names_mx) == static_cast<size_t>(nExog)))
mexErrMsgTxt("M_.exo_names should be a cell array of M_.exo_nbr elements");
std::vector<std::string> exoNames = DynareMxArrayToString(exo_names_mx);
const mxArray *dynamic_tmp_nbr_mx = mxGetField(M_mx, 0, "dynamic_tmp_nbr");
if (!(dynamic_tmp_nbr_mx && mxIsDouble(dynamic_tmp_nbr_mx) && mxGetNumberOfElements(dynamic_tmp_nbr_mx) >= static_cast<size_t>(kOrder+1)))
mexErrMsgTxt("M_.dynamic_tmp_nbr should be a double precision array with strictly more elements than the order of derivation");
int ntt = std::accumulate(mxGetPr(dynamic_tmp_nbr_mx), mxGetPr(dynamic_tmp_nbr_mx)+kOrder+1, 0);
// Extract various fields from dr
const mxArray *ys_mx = mxGetField(dr_mx, 0, "ys"); // and not in order of dr.order_var
if (!(ys_mx && mxIsDouble(ys_mx)))
mexErrMsgTxt("dr.ys should be a double precision array");
Vector ySteady{ConstVector{ys_mx}};
if (!ySteady.isFinite())
mexErrMsgTxt("dr.ys contains NaN or Inf");
const mxArray *order_var_mx = mxGetField(dr_mx, 0, "order_var");
if (!(order_var_mx && mxIsDouble(order_var_mx) && mxGetNumberOfElements(order_var_mx) == static_cast<size_t>(nEndo)))
mexErrMsgTxt("dr.order_var should be a double precision array of M_.endo_nbr elements");
std::vector<int> dr_order(nEndo);
std::transform(mxGetPr(order_var_mx), mxGetPr(order_var_mx)+nEndo, dr_order.begin(),
[](double x) { return static_cast<int>(x)-1; });
const int nSteps = 0; // Dynare++ solving steps, for time being default to 0 = deterministic steady state
// Journal is not written on-disk, unless options_.debug = true (see #1735)
Journal journal;
if (debug)
journal = Journal{fname + ".jnl"};
std::unique_ptr<DynamicModelAC> dynamicModelFile;
if (use_dll)
dynamicModelFile = std::make_unique<DynamicModelDLL>(fname, ntt, kOrder);
else
dynamicModelFile = std::make_unique<DynamicModelMFile>(fname, ntt);
// intiate tensor library
TLStatic::init(kOrder, nStat+2*nPred+3*nBoth+2*nForw+nExog);
// Set number of parallel threads
sthread::detach_thread_group::max_parallel_threads = num_threads;
// make KordpDynare object
KordpDynare dynare(endoNames, exoNames, nExog, nPar,
ySteady, vCov, modParams, nStat, nPred, nForw, nBoth,
NNZD, nSteps, kOrder, journal, std::move(dynamicModelFile),
dr_order, llincidence);
// construct main K-order approximation class
Approximation app(dynare, journal, nSteps, false, qz_criterium);
// 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) && mxGetNumberOfElements(objective_tmp_nbr_mx) >= static_cast<size_t>(kOrder+1)))
mexErrMsgTxt("M_.objective_tmp_nbr should be a double precision 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<ObjectiveAC> objectiveFile;
objectiveFile = std::make_unique<ObjectiveMFile>(fname, ntt_objective);
// make KordwDynare object
KordwDynare welfare(dynare, NNZD_obj, journal, modParams, std::move(objectiveFile), dr_order);
// construct main K-order approximation class of welfare
ApproximationWelfare appwel(welfare, discount_factor, app.get_rule_ders(), app.get_rule_ders_s(), journal);
appwel.approxAtSteady();
// Conditional welfare approximation at non-stochastic steady state when stochastic shocks are enabled
const Vector &cond_approx = appwel.getCond();
plhs[0] = mxCreateDoubleMatrix(cond_approx.length(), 1, mxREAL);
std::copy_n(cond_approx.base(), cond_approx.length(), mxGetPr(plhs[0]));
if (nlhs > 1)
{
const FoldDecisionRule &unc_fdr = appwel.getFoldUncWel();
// Add possibly missing field names
for (int i = static_cast<int>(U_fieldnames.size()); i <= kOrder; i++)
U_fieldnames.emplace_back("U_" + std::to_string(i));
// Create structure for storing derivatives in Dynare++ format
const char *U_fieldnames_c[kOrder+1];
for (int i = 0; i <= kOrder; i++)
U_fieldnames_c[i] = U_fieldnames[i].c_str();
plhs[1] = mxCreateStructMatrix(1, 1, kOrder+1, U_fieldnames_c);
// Fill that structure
for (int i = 0; i <= kOrder; i++)
{
const FFSTensor &t = unc_fdr.get(Symmetry{i});
mxArray *tmp = mxCreateDoubleMatrix(t.nrows(), t.ncols(), mxREAL);
const ConstVector &vec = t.getData();
assert(vec.skip() == 1);
std::copy_n(vec.base(), vec.length(), mxGetPr(tmp));
mxSetField(plhs[1], 0, ("U_" + std::to_string(i)).c_str(), tmp);
}
if (nlhs > 2)
{
const FoldDecisionRule &cond_fdr = appwel.getFoldCondWel();
// Add possibly missing field names
for (int i = static_cast<int>(W_fieldnames.size()); i <= kOrder; i++)
W_fieldnames.emplace_back("W_" + std::to_string(i));
// Create structure for storing derivatives in Dynare++ format
const char *W_fieldnames_c[kOrder+1];
for (int i = 0; i <= kOrder; i++)
W_fieldnames_c[i] = W_fieldnames[i].c_str();
plhs[2] = mxCreateStructMatrix(1, 1, kOrder+1, W_fieldnames_c);
// Fill that structure
for (int i = 0; i <= kOrder; i++)
{
const FFSTensor &t = cond_fdr.get(Symmetry{i});
mxArray *tmp = mxCreateDoubleMatrix(t.nrows(), t.ncols(), mxREAL);
const ConstVector &vec = t.getData();
assert(vec.skip() == 1);
std::copy_n(vec.base(), vec.length(), mxGetPr(tmp));
mxSetField(plhs[2], 0, ("W_" + std::to_string(i)).c_str(), tmp);
}
if (nlhs > 3)
{
const FGSContainer &U = appwel.get_unc_ders();
size_t nfields = (kOrder == 1 ? 2 : (kOrder == 2 ? 6 : 12));
const char *c_fieldnames[] = { "Uy", "Uu", "Uyy", "Uyu", "Uuu", "Uss", "Uyyy", "Uyyu", "Uyuu", "Uuuu", "Uyss", "Uuss" };
plhs[3] = mxCreateStructMatrix(1, 1, nfields, c_fieldnames);
copy_derivatives(plhs[3], Symmetry{1, 0, 0, 0}, U, "Uy");
copy_derivatives(plhs[3], Symmetry{0, 1, 0, 0}, U, "Uu");
if (kOrder >= 2)
{
copy_derivatives(plhs[3], Symmetry{2, 0, 0, 0}, U, "Uyy");
copy_derivatives(plhs[3], Symmetry{0, 2, 0, 0}, U, "Uuu");
copy_derivatives(plhs[3], Symmetry{1, 1, 0, 0}, U, "Uyu");
copy_derivatives(plhs[3], Symmetry{0, 0, 0, 2}, U, "Uss");
}
if (kOrder >= 3)
{
copy_derivatives(plhs[3], Symmetry{3, 0, 0, 0}, U, "Uyyy");
copy_derivatives(plhs[3], Symmetry{0, 3, 0, 0}, U, "Uuuu");
copy_derivatives(plhs[3], Symmetry{2, 1, 0, 0}, U, "Uyyu");
copy_derivatives(plhs[3], Symmetry{1, 2, 0, 0}, U, "Uyuu");
copy_derivatives(plhs[3], Symmetry{1, 0, 0, 2}, U, "Uyss");
copy_derivatives(plhs[3], Symmetry{0, 1, 0, 2}, U, "Uuss");
}
if (nlhs > 4)
{
const FGSContainer &W = appwel.get_cond_ders();
size_t nfields = (kOrder == 1 ? 2 : (kOrder == 2 ? 6 : 12));
const char *c_fieldnames[] = { "Wy", "Wu", "Wyy", "Wyu", "Wuu", "Wss", "Wyyy", "Wyyu", "Wyuu", "Wuuu", "Wyss", "Wuss" };
plhs[4] = mxCreateStructMatrix(1, 1, nfields, c_fieldnames);
copy_derivatives(plhs[4], Symmetry{1, 0, 0, 0}, W, "Wy");
copy_derivatives(plhs[4], Symmetry{0, 1, 0, 0}, W, "Wu");
if (kOrder >= 2)
{
copy_derivatives(plhs[4], Symmetry{2, 0, 0, 0}, W, "Wyy");
copy_derivatives(plhs[4], Symmetry{0, 2, 0, 0}, W, "Wuu");
copy_derivatives(plhs[4], Symmetry{1, 1, 0, 0}, W, "Wyu");
copy_derivatives(plhs[4], Symmetry{0, 0, 0, 2}, W, "Wss");
}
if (kOrder >= 3)
{
copy_derivatives(plhs[4], Symmetry{3, 0, 0, 0}, W, "Wyyy");
copy_derivatives(plhs[4], Symmetry{0, 3, 0, 0}, W, "Wuuu");
copy_derivatives(plhs[4], Symmetry{2, 1, 0, 0}, W, "Wyyu");
copy_derivatives(plhs[4], Symmetry{1, 2, 0, 0}, W, "Wyuu");
copy_derivatives(plhs[4], Symmetry{1, 0, 0, 2}, W, "Wyss");
copy_derivatives(plhs[4], Symmetry{0, 1, 0, 2}, W, "Wuss");
}
}
}
}
}
} // end of mexFunction()
} // end of extern C

View File

@ -0,0 +1,38 @@
/*
* Copyright © 2021 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 _OBJECTIVEAC_HH
#define _OBJECTIVEAC_HH
#include <vector>
#include "twod_matrix.hh"
class ObjectiveAC
{
protected:
int ntt; // Size of vector of temporary terms
public:
ObjectiveAC(int ntt_arg) : ntt{ntt_arg}
{
};
virtual ~ObjectiveAC() = default;
virtual void eval(const Vector &y, const Vector &x, const Vector &params, Vector &residual, std::vector<TwoDMatrix> &md) = 0;
};
#endif

View File

@ -0,0 +1,143 @@
/*
* Copyright © 2021 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 <algorithm>
#include <cassert>
#include "dynare_exception.hh"
#include "objective_m.hh"
ObjectiveMFile::ObjectiveMFile(const std::string &modName, int ntt_arg) :
ObjectiveAC(ntt_arg),
ObjectiveMFilename{modName + ".objective.static"}
{
}
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() >= 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)
{
mxArray *T_m = mxCreateDoubleMatrix(ntt, 1, mxREAL);
mxArray *y_m = mxCreateDoubleMatrix(y.length(), 1, mxREAL);
std::copy_n(y.base(), y.length(), mxGetPr(y_m));
mxArray *x_m = mxCreateDoubleMatrix(1, x.length(), mxREAL);
std::copy_n(x.base(), x.length(), mxGetPr(x_m));
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";
mxArray *plhs[1], *prhs[] = { T_m, y_m, x_m, params_m };
int retVal = mexCallMATLAB(1, plhs, 4, prhs, funcname.c_str());
if (retVal != 0)
throw DynareException(__FILE__, __LINE__, "Trouble calling " + funcname);
mxDestroyArray(T_m);
T_m = plhs[0];
}
{
// Compute residuals
std::string funcname = ObjectiveMFilename + "_resid";
mxArray *plhs[1], *prhs[] = { T_m, y_m, x_m, params_m, T_flag_m };
int retVal = mexCallMATLAB(1, plhs, 5, prhs, funcname.c_str());
if (retVal != 0)
throw DynareException(__FILE__, __LINE__, "Trouble calling " + funcname);
residual = Vector{plhs[0]};
mxDestroyArray(plhs[0]);
}
for (size_t i = 1; i <= md.size(); i++)
{
// Compute model derivatives
std::string funcname = ObjectiveMFilename + "_g" + std::to_string(i);
mxArray *plhs[1], *prhs[] = { T_m, y_m, x_m, params_m, T_flag_m };
int retVal = mexCallMATLAB(1, plhs, 5, prhs, funcname.c_str());
if (retVal != 0)
throw DynareException(__FILE__, __LINE__, "Trouble calling " + funcname);
if (i == 1)
{
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());
}
else
unpackSparseMatrixAndCopyIntoTwoDMatData(plhs[0], md[i-1]);
mxDestroyArray(plhs[0]);
}
mxDestroyArray(T_m);
mxDestroyArray(y_m);
mxDestroyArray(x_m);
mxDestroyArray(params_m);
mxDestroyArray(T_flag_m);
}

View File

@ -0,0 +1,43 @@
/*
* Copyright © 2021 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 _OBJECTIVE_M_HH
#define _OBJECTIVE_M_HH
#include "objective_abstract_class.hh"
#include "mex.h"
#include <dynmex.h>
/**
* handles calls to <model>/+objective/static.m
*
**/
class ObjectiveMFile : public ObjectiveAC
{
private:
const std::string ObjectiveMFilename;
static void unpackSparseMatrixAndCopyIntoTwoDMatData(mxArray *sparseMat, TwoDMatrix &tdm);
public:
explicit ObjectiveMFile(const std::string &modName, int ntt_arg);
virtual ~ObjectiveMFile() = default;
void eval(const Vector &y, const Vector &x, const Vector &params,
Vector &residual, std::vector<TwoDMatrix> &md) override;
};
#endif

View File

@ -116,6 +116,8 @@ MODFILES = \
optimal_policy/mult_elimination_test.mod \
optimal_policy/neo_growth.mod \
optimal_policy/neo_growth_ramsey.mod \
optimal_policy/neo_growth_k_order.mod \
optimal_policy/neo_growth_ramsey_k_order.mod \
optimal_policy/Ramsey/ramsey_ex_initval.mod \
optimal_policy/Ramsey/ramsey_ex.mod \
optimal_policy/Ramsey/ramsey_ex_initval_AR2.mod \
@ -597,6 +599,9 @@ optimal_policy/neo_growth_ramsey.o.trs: optimal_policy/neo_growth.o.trs
optimal_policy/neo_growth_ramsey_foresight.m.trs: optimal_policy/neo_growth_foresight.m.trs
optimal_policy/neo_growth_ramsey_foresight.o.trs: optimal_policy/neo_growth_foresight.o.trs
optimal_policy/neo_growth_ramsey_k_order.m.trs: optimal_policy/neo_growth_k_order.m.trs
optimal_policy/neo_growth_ramsey_k_order.o.trs: optimal_policy/neo_growth_k_order.o.trs
example1_use_dll.m.trs: example1.m.trs
example1_use_dll.o.trs: example1.o.trs

View File

@ -0,0 +1,34 @@
/*
* Copyright (C) 2021 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/>.
*/
/*
* This file computes a second-order approximation of the neo-classical growth model.
* It is called by neo_growth_ramsey.mod to compare by-hand calculations of unconditional
* and conditional welfares and the output of the evaluate_planner_objective function.
*/
@#include "neo_growth_common.inc"
shocks;
var e;
stderr 1;
end;
steady;
resid;
stoch_simul(order=6, irf=0);

View File

@ -0,0 +1,60 @@
/*
* Copyright (C) 2021 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/>.
*/
/*
* This file computes a kth-order approximation of the neo-classical growth model.
* It assesses the conditional welfare and the derivatives of the felicity and welfare functions computed by the k_order_welfare function
* and compares them to a by-hand assessment stemming from the results the model neo_growth_k_order.mod incur.
*/
@#include "neo_growth_ramsey_common.inc"
shocks;
var e;
stderr 1;
end;
stoch_simul(order=6, irf=0);
[condWelfare, U_dynpp, W_dynpp, U_dyn, W_dyn] = k_order_welfare(oo_.dr, M_, options_);
if ~exist('neo_growth_k_order_results.mat','file');
error('neo_growth_k_order must be run first');
end;
oo = load('neo_growth_k_order_results','oo_');
M = load('neo_growth_k_order_results','M_');
options = load('neo_growth_k_order_results','options_');
ind_U = strmatch('U', M.M_.endo_names,'exact');
ind_W = strmatch('W', M.M_.endo_names,'exact');
err = -1e6;
for i = 1:options_.order
field_U = strcat('U_', num2str(i));
field_W = strcat('W_', num2str(i));
g_i = eval(strcat('oo.oo_.dr.g_', num2str(i)));
tmp_err = max(U_dynpp.(field_U) - g_i(ind_U, :));
err = max(err, tmp_err);
tmp_err = max(W_dynpp.(field_W) - g_i(ind_W, :));
err = max(err, tmp_err);
end
if err > 1e-10;
error('Inaccurate assessment of the derivatives of the felicity and the welfare functions');
end;