352 lines
13 KiB
C++
352 lines
13 KiB
C++
// Copyright 2005, Ondra Kamenik
|
|
|
|
#include <utility>
|
|
|
|
#include "kord_exception.hh"
|
|
#include "approximation.hh"
|
|
#include "first_order.hh"
|
|
#include "korder_stoch.hh"
|
|
|
|
ZAuxContainer::ZAuxContainer(const _Ctype *gss, int ngss, int ng, int ny, int nu)
|
|
: StackContainer<FGSTensor>(4, 1)
|
|
{
|
|
stack_sizes = { ngss, ng, ny, nu };
|
|
conts[0] = gss;
|
|
calculateOffsets();
|
|
}
|
|
|
|
/* The |getType| method corresponds to
|
|
$f(g^{**}(y^*,u',\sigma),0,0,0)$. For the first argument we return
|
|
|matrix|, for other three we return |zero|. */
|
|
|
|
ZAuxContainer::itype
|
|
ZAuxContainer::getType(int i, const Symmetry &s) const
|
|
{
|
|
if (i == 0)
|
|
if (s[2] > 0)
|
|
return itype::zero;
|
|
else
|
|
return itype::matrix;
|
|
return itype::zero;
|
|
}
|
|
|
|
Approximation::Approximation(DynamicModel &m, Journal &j, int ns, bool dr_centr, double qz_crit)
|
|
: model(m), journal(j),
|
|
ypart(model.nstat(), model.npred(), model.nboth(), model.nforw()),
|
|
mom(UNormalMoments(model.order(), model.getVcov())),
|
|
nvs{ypart.nys(), model.nexog(), model.nexog(), 1 },
|
|
steps(ns),
|
|
dr_centralize(dr_centr), qz_criterium(qz_crit), ss(ypart.ny(), steps+1)
|
|
{
|
|
ss.nans();
|
|
}
|
|
|
|
/* This just returns |fdr| with a check that it is created. */
|
|
const FoldDecisionRule &
|
|
Approximation::getFoldDecisionRule() const
|
|
{
|
|
KORD_RAISE_IF(!fdr,
|
|
"Folded decision rule has not been created in Approximation::getFoldDecisionRule");
|
|
return *fdr;
|
|
}
|
|
|
|
/* This just returns |udr| with a check that it is created. */
|
|
const UnfoldDecisionRule &
|
|
Approximation::getUnfoldDecisionRule() const
|
|
{
|
|
KORD_RAISE_IF(!udr,
|
|
"Unfolded decision rule has not been created in Approximation::getUnfoldDecisionRule");
|
|
return *udr;
|
|
}
|
|
|
|
/* 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 $\sigma=0$. */
|
|
void
|
|
Approximation::approxAtSteady()
|
|
{
|
|
model.calcDerivativesAtSteady();
|
|
FirstOrder fo(model.nstat(), model.npred(), model.nboth(), model.nforw(),
|
|
model.nexog(), model.getModelDerivatives().get(Symmetry{1}),
|
|
journal, qz_criterium);
|
|
KORD_RAISE_IF_X(!fo.isStable(),
|
|
"The model is not Blanchard-Kahn stable",
|
|
KORD_MD_NOT_STABLE);
|
|
|
|
if (model.order() >= 2)
|
|
{
|
|
KOrder korder(model.nstat(), model.npred(), model.nboth(), model.nforw(),
|
|
model.getModelDerivatives(), fo.getGy(), fo.getGu(),
|
|
model.getVcov(), journal);
|
|
korder.switchToFolded();
|
|
for (int k = 2; k <= model.order(); k++)
|
|
korder.performStep<Storage::fold>(k);
|
|
|
|
saveRuleDerivs(korder.getFoldDers());
|
|
}
|
|
else
|
|
{
|
|
FirstOrderDerivs<Storage::fold> fo_ders(fo);
|
|
saveRuleDerivs(fo_ders);
|
|
}
|
|
check(0.0);
|
|
}
|
|
|
|
/* This is the core routine of |Approximation| class.
|
|
|
|
First we solve for the approximation about the deterministic steady
|
|
state. Then we perform |steps| cycles toward the stochastic steady
|
|
state. Each cycle moves the size of shocks by |dsigma=1.0/steps|. At
|
|
the end of a cycle, we have |rule_ders| being the derivatives at
|
|
stochastic steady state for $\sigma=sigma\_so\_far+dsigma$ and
|
|
|model.getSteady()| being the steady state.
|
|
|
|
If the number of |steps| is zero, the decision rule |dr| at the bottom
|
|
is created from derivatives about deterministic steady state, with
|
|
size of $\sigma=1$. Otherwise, the |dr| is created from the
|
|
approximation about stochastic steady state with $\sigma=0$.
|
|
|
|
Within each cycle, we first make a backup of the last steady (from
|
|
initialization or from a previous cycle), then we calculate the fix
|
|
point of the last rule with $\sigma=dsigma$. This becomes a new steady
|
|
state at the $\sigma=sigma\_so\_far+dsigma$. We calculate expectations
|
|
of $g^{**}(y,\sigma\eta_{t+1},\sigma$ expressed as a Taylor expansion
|
|
around the new $\sigma$ and the new steady state. Then we solve for
|
|
the decision rule with explicit $g^{**}$ at $t+1$ and save the rule.
|
|
|
|
After we reached $\sigma=1$, the decision rule is formed.
|
|
|
|
The biproduct of this method is the matrix |ss|, whose columns are
|
|
steady states for subsequent $\sigma$s. The first column is the
|
|
deterministic steady state, the last column is the stochastic steady
|
|
state for a full size of shocks ($\sigma=1$). There are |steps+1|
|
|
columns. */
|
|
|
|
void
|
|
Approximation::walkStochSteady()
|
|
{
|
|
// initial approximation at deterministic steady
|
|
/* Here we solve for the deterministic steady state, calculate
|
|
approximation at the deterministic steady and save the steady state
|
|
to |ss|. */
|
|
model.solveDeterministicSteady();
|
|
approxAtSteady();
|
|
Vector steady0{ss.getCol(0)};
|
|
steady0 = model.getSteady();
|
|
|
|
double sigma_so_far = 0.0;
|
|
double dsigma = (steps == 0) ? 0.0 : 1.0/steps;
|
|
for (int i = 1; i <= steps; i++)
|
|
{
|
|
JournalRecordPair pa(journal);
|
|
pa << "Approximation about stochastic steady for sigma=" << sigma_so_far+dsigma << endrec;
|
|
|
|
Vector last_steady(model.getSteady());
|
|
|
|
// calculate fix-point of the last rule for |dsigma|
|
|
/* We form the |DRFixPoint| object from the last rule with
|
|
$\sigma=dsigma$. Then we save the steady state to |ss|. The new steady
|
|
is also put to |model.getSteady()|. */
|
|
DRFixPoint<Storage::fold> fp(*rule_ders, ypart, model.getSteady(), dsigma);
|
|
bool converged = fp.calcFixPoint(DecisionRule::emethod::horner, model.getSteady());
|
|
JournalRecord rec(journal);
|
|
rec << "Fix point calcs: iter=" << fp.getNumIter() << ", newton_iter="
|
|
<< fp.getNewtonTotalIter() << ", last_newton_iter=" << fp.getNewtonLastIter() << ".";
|
|
if (converged)
|
|
rec << " Converged." << endrec;
|
|
else
|
|
{
|
|
rec << " Not converged!!" << endrec;
|
|
KORD_RAISE_X("Fix point calculation not converged", KORD_FP_NOT_CONV);
|
|
}
|
|
Vector steadyi{ss.getCol(i)};
|
|
steadyi = model.getSteady();
|
|
|
|
// calculate |hh| as expectations of the last $g^{**}$
|
|
/* We form the steady state shift |dy|, which is the new steady state
|
|
minus the old steady state. Then we create |StochForwardDerivs|
|
|
object, which calculates the derivatives of $g^{**}$ expectations at
|
|
new sigma and new steady. */
|
|
Vector dy(model.getSteady());
|
|
dy.add(-1.0, last_steady);
|
|
|
|
StochForwardDerivs<Storage::fold> hh(ypart, model.nexog(), *rule_ders_ss, mom, dy,
|
|
dsigma, sigma_so_far);
|
|
JournalRecord rec1(journal);
|
|
rec1 << "Calculation of g** expectations done" << endrec;
|
|
|
|
// form |KOrderStoch|, solve and save
|
|
/* We calculate derivatives of the model at the new steady, form
|
|
|KOrderStoch| object and solve, and save the rule. */
|
|
model.calcDerivativesAtSteady();
|
|
KOrderStoch korder_stoch(ypart, model.nexog(), model.getModelDerivatives(),
|
|
hh, journal);
|
|
for (int d = 1; d <= model.order(); d++)
|
|
korder_stoch.performStep<Storage::fold>(d);
|
|
|
|
saveRuleDerivs(korder_stoch.getFoldDers());
|
|
|
|
check(sigma_so_far+dsigma);
|
|
sigma_so_far += dsigma;
|
|
}
|
|
|
|
// construct the resulting decision rules
|
|
udr.reset();
|
|
fdr = std::make_unique<FoldDecisionRule>(*rule_ders, ypart, model.nexog(),
|
|
model.getSteady(), 1.0-sigma_so_far);
|
|
if (steps == 0 && dr_centralize)
|
|
{
|
|
// centralize decision rule for zero steps
|
|
DRFixPoint<Storage::fold> fp(*rule_ders, ypart, model.getSteady(), 1.0);
|
|
bool converged = fp.calcFixPoint(DecisionRule::emethod::horner, model.getSteady());
|
|
JournalRecord rec(journal);
|
|
rec << "Fix point calcs: iter=" << fp.getNumIter() << ", newton_iter="
|
|
<< fp.getNewtonTotalIter() << ", last_newton_iter=" << fp.getNewtonLastIter() << ".";
|
|
if (converged)
|
|
rec << " Converged." << endrec;
|
|
else
|
|
{
|
|
rec << " Not converged!!" << endrec;
|
|
KORD_RAISE_X("Fix point calculation not converged", KORD_FP_NOT_CONV);
|
|
}
|
|
|
|
{
|
|
JournalRecordPair recp(journal);
|
|
recp << "Centralizing about fix-point." << endrec;
|
|
fdr = std::make_unique<FoldDecisionRule>(*fdr, model.getSteady());
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Here we simply make a new hardcopy of the given rule |rule_ders|,
|
|
and make a new container of in-place subtensors of the derivatives
|
|
corresponding to forward looking variables. The given container comes
|
|
from a temporary object and will be destroyed. */
|
|
|
|
void
|
|
Approximation::saveRuleDerivs(const FGSContainer &g)
|
|
{
|
|
rule_ders = std::make_unique<FGSContainer>(g);
|
|
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));
|
|
}
|
|
}
|
|
|
|
/* This method calculates a shift of the system equations due to
|
|
integrating shocks at a given $\sigma$ and current steady state. More precisely, if
|
|
$$F(y,u,u',\sigma)=f(g^{**}(g^*(y,u,\sigma),u',\sigma),g(y,u,\sigma),y,u)$$
|
|
then the method returns a vector
|
|
$$\sum_{d=1}{1\over d!}\sigma^d\left[F_{u'^d}\right]_{\alpha_1\ldots\alpha_d}
|
|
\Sigma^{\alpha_1\ldots\alpha_d}$$
|
|
|
|
For a calculation of $\left[F_{u'^d}\right]$ we use |@<|ZAuxContainer|
|
|
class declaration@>|, so we create its object. In each cycle we
|
|
calculate $\left[F_{u'^d}\right]$@q'@>, and then multiply with the shocks,
|
|
and add the ${\sigma^d\over d!}$ multiple to the result. */
|
|
|
|
void
|
|
Approximation::calcStochShift(Vector &out, double at_sigma) const
|
|
{
|
|
KORD_RAISE_IF(out.length() != ypart.ny(),
|
|
"Wrong length of output vector for Approximation::calcStochShift");
|
|
out.zeros();
|
|
|
|
ZAuxContainer zaux(rule_ders_ss.get(), ypart.nyss(), ypart.ny(),
|
|
ypart.nys(), model.nexog());
|
|
|
|
int dfac = 1;
|
|
for (int d = 1; d <= rule_ders->getMaxDim(); d++, dfac *= d)
|
|
{
|
|
if (KOrder::is_even(d))
|
|
{
|
|
Symmetry sym{0, d, 0, 0};
|
|
|
|
// calculate $F_{u'^d}$ via |ZAuxContainer|
|
|
auto ten = std::make_unique<FGSTensor>(ypart.ny(), TensorDimens(sym, nvs));
|
|
ten->zeros();
|
|
for (int l = 1; l <= d; l++)
|
|
{
|
|
const FSSparseTensor &f = model.getModelDerivatives().get(Symmetry{l});
|
|
zaux.multAndAdd(f, *ten);
|
|
}
|
|
|
|
// multiply with shocks and add to result
|
|
auto tmp = std::make_unique<FGSTensor>(ypart.ny(), TensorDimens(Symmetry{0, 0, 0, 0}, nvs));
|
|
tmp->zeros();
|
|
ten->contractAndAdd(1, *tmp, mom.get(Symmetry{d}));
|
|
|
|
out.add(pow(at_sigma, d)/dfac, tmp->getData());
|
|
}
|
|
}
|
|
}
|
|
|
|
/* This method calculates and reports
|
|
$$f(\bar y)+\sum_{d=1}{1\over d!}\sigma^d\left[F_{u'^d}\right]_{\alpha_1\ldots\alpha_d}
|
|
\Sigma^{\alpha_1\ldots\alpha_d}$$
|
|
at $\bar y$, zero shocks and $\sigma$. This number should be zero.
|
|
|
|
We evaluate the error both at a given $\sigma$ and $\sigma=1.0$. */
|
|
|
|
void
|
|
Approximation::check(double at_sigma) const
|
|
{
|
|
Vector stoch_shift(ypart.ny());
|
|
Vector system_resid(ypart.ny());
|
|
Vector xx(model.nexog());
|
|
xx.zeros();
|
|
model.evaluateSystem(system_resid, model.getSteady(), xx);
|
|
calcStochShift(stoch_shift, at_sigma);
|
|
stoch_shift.add(1.0, system_resid);
|
|
JournalRecord rec1(journal);
|
|
rec1 << "Error of current approximation for shocks at sigma " << at_sigma
|
|
<< " is " << stoch_shift.getMax() << endrec;
|
|
calcStochShift(stoch_shift, 1.0);
|
|
stoch_shift.add(1.0, system_resid);
|
|
JournalRecord rec2(journal);
|
|
rec2 << "Error of current approximation for full shocks is " << stoch_shift.getMax() << endrec;
|
|
}
|
|
|
|
/* The method returns unconditional variance of endogenous variables
|
|
based on the first order. The first order approximation looks like
|
|
$$\hat y_t=g_{y^*}\hat y^*_{t-1}+g_uu_t$$
|
|
where $\hat y$ denotes a deviation from the steady state. It can be written as
|
|
$$\hat y_t=\left[0\, g_{y^*}\, 0\right]\hat y_{t-1}+g_uu_t$$
|
|
which yields unconditional covariance $V$ for which
|
|
$$V=GVG^T + g_u\Sigma g_u^T,$$
|
|
where $G=[0\, g_{y^*}\, 0]$ and $\Sigma$ is the covariance of the shocks.
|
|
|
|
For solving this Lyapunov equation we use the Sylvester module, which
|
|
solves equation of the type
|
|
$$AX+BX(C\otimes\cdots\otimes C)=D$$
|
|
So we invoke the Sylvester solver for the first dimension with $A=I$,
|
|
$B=-G$, $C=G^T$ and $D=g_u\Sigma g_u^T$. */
|
|
|
|
TwoDMatrix
|
|
Approximation::calcYCov() const
|
|
{
|
|
const TwoDMatrix &gy = rule_ders->get(Symmetry{1, 0, 0, 0});
|
|
const TwoDMatrix &gu = rule_ders->get(Symmetry{0, 1, 0, 0});
|
|
TwoDMatrix G(model.numeq(), model.numeq());
|
|
G.zeros();
|
|
G.place(gy, 0, model.nstat());
|
|
TwoDMatrix B(const_cast<const TwoDMatrix &>(G));
|
|
B.mult(-1.0);
|
|
TwoDMatrix C(transpose(G));
|
|
TwoDMatrix A(model.numeq(), model.numeq());
|
|
A.zeros();
|
|
for (int i = 0; i < model.numeq(); i++)
|
|
A.get(i, i) = 1.0;
|
|
|
|
TwoDMatrix X((gu * model.getVcov()) * transpose(gu));
|
|
|
|
GeneralSylvester gs(1, model.numeq(), model.numeq(), 0,
|
|
A.getData(), B.getData(), C.getData(), X.getData());
|
|
gs.solve();
|
|
|
|
return X;
|
|
}
|