dynare/dynare++/kord/first_order.cc

292 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* Copyright © 2004 Ondra Kamenik
* Copyright © 2019 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 "kord_exception.hh"
#include "first_order.hh"
#include <dynlapack.h>
double FirstOrder::qz_criterium_global;
std::mutex FirstOrder::mut;
/* This is a function which selects the eigenvalues pair used by LAPACKs
dgges. Here we want to select the pairs for which α<β (up to the QZ
criterium). */
lapack_int
FirstOrder::order_eigs(const double *alphar, const double *alphai, const double *beta)
{
return (*alphar **alphar + *alphai **alphai < *beta **beta * qz_criterium_global * qz_criterium_global);
}
/* Here we solve the linear approximation. The result are the matrices
g_y* and gᵤ. The method solves the first derivatives of g so
that the following equation would be true:
𝔼ₜ[F(y*ₜ₋₁,uₜ,uₜ₊₁,σ)] = 𝔼ₜ[f(g**(g*(y*ₜ₋₁,uₜ,σ), uₜ₊₁, σ), g(y*ₜ₋₁,uₜ,σ), y*ₜ₋₁,uₜ)]=0
where f is a given system of equations.
It is known that g_y* is given by F_y*=0, gᵤ is given by Fᵤ=0, and g_σ is
zero. The only input to the method are the derivatives fd of the system f,
and partitioning of the vector y (from object). */
void
FirstOrder::solve(const TwoDMatrix &fd)
{
JournalRecordPair pa(journal);
pa << "Recovering first order derivatives " << endrec;
// Solve derivatives gy
/* The derivatives g_y* are retrieved from the equation F_y*=0. The
calculation proceeds as follows:
1. For each variable appearing at both t-1 and t+1 we add a dummy
variable, so that the predetermined variables and forward looking would
be disjoint. This is, the matrix of the first derivatives of the
system written as:
[ f_y**₊ f_ys f_yp f_yb f_yf f_y*₋ ]
where f_ys, f_yp, f_yb, and f_yf are derivatives w.r.t. static,
predetermined, both, forward looking at time t, is rewritten as:
⎡ f_y**₊ f_ys f_yp f_yb 0 f_yf f_y*₋ ⎤
⎣ 0 0 0 I I 0 0 ⎦
where the second line has number of rows equal to the number of both
variables.
2. Next, provided that forward looking and predetermined are disjoint, the
equation F_y*=0 is written as:
[f_y**₊][g**_y*][g*_y*] + [f_ys][gˢ_y*] + [f_y*][g*_y*] + [f_y**][g**_y*] + [f_y*₋] = 0
This is rewritten as
⎡ I ⎤ ⎡ I ⎤
[f_y* 0 f_y**₊]⎢ gˢ_y*⎥[g*_y*] + [f_y*₋ f_ys f_y**]⎢ gˢ_y*⎥ = 0
⎣g**_y*⎦ ⎣g**_y*⎦
Now, in the above equation, there are the auxiliary variables standing
for copies of both variables at time t+1. This equation is then
rewritten as:
⎡ I ⎤ ⎡ I ⎤
⎡f_yp f_yb 0 f_y**₊⎤⎢ gˢ_y*⎥[g*_y*] + ⎡f_y*₋ f_ys 0 f_yf⎤⎢ gˢ_y*⎥ = 0
⎣ 0 I 0 0 ⎦⎣g**_y*⎦ ⎣ 0 0 I 0 ⎦⎣g**_y*⎦
The two matrices are denoted as D and E, so the equation takes the form:
⎡ I ⎤ ⎡ I ⎤
D ⎢ gˢ_y*⎥[g*_y*] = E ⎢ gˢ_y*⎥
⎣g**_y*⎦ ⎣g**_y*⎦
3. Next we solve the equation by Generalized Schur decomposition:
⎡T₁₁ T₁₂⎤⎡Z₁₁ᵀ Z₂₁ᵀ⎤⎡I⎤ ⎡S₁₁ S₁₂⎤⎡Z₁₁ᵀ Z₂₁ᵀ⎤⎡I⎤
⎣ 0 T₂₂⎦⎣Z₁₂ᵀ Z₂₂ᵀ⎦⎣X⎦[g*_y*] = ⎣ 0 S₂₂⎦⎣Z₁₂ᵀ Z₂₂ᵀ⎦⎣X⎦
We reorder the eigenvalue pair so that Sᵢᵢ/Tᵢᵢ with modulus less than
one would be in the left-upper part.
4. The Blanchard-Kahn stability argument implies that the pairs with
modulus less that one will be in and only in S₁₁/T₁₁. The exploding
paths will be then eliminated when
⎡Z₁₁ᵀ Z₂₁ᵀ⎤⎡I⎤ ⎡Y⎤
⎣Z₁₂ᵀ Z₂₂ᵀ⎦⎣X⎦ = ⎣0⎦
From this we have, Y=Z₁₁⁻¹, and X=Z₂₁Y, or equivalently X=Z₂₂⁻ᵀZ₁₂ᵀ.
From the equation, we get [g*_y*]=Y⁻¹T₁₁⁻¹S₁₁Y, which is
Z₁₁T₁₁⁻¹S₁₁Z₁₁⁻¹.
5. We then copy the derivatives to storage gy. Note that the derivatives
of both variables are in X and in [g*_y*], so we check whether the two
submatrices are the same. The difference is only numerical error.
*/
// Setup submatrices of f
/* Here we setup submatrices of the derivatives fd. */
int off = 0;
ConstTwoDMatrix fyplus(fd, off, ypart.nyss());
off += ypart.nyss();
ConstTwoDMatrix fyszero(fd, off, ypart.nstat);
off += ypart.nstat;
ConstTwoDMatrix fypzero(fd, off, ypart.npred);
off += ypart.npred;
ConstTwoDMatrix fybzero(fd, off, ypart.nboth);
off += ypart.nboth;
ConstTwoDMatrix fyfzero(fd, off, ypart.nforw);
off += ypart.nforw;
ConstTwoDMatrix fymins(fd, off, ypart.nys());
off += ypart.nys();
ConstTwoDMatrix fuzero(fd, off, nu);
off += nu;
// Form matrix D
lapack_int n = ypart.ny()+ypart.nboth;
TwoDMatrix matD(n, n);
matD.zeros();
matD.place(fypzero, 0, 0);
matD.place(fybzero, 0, ypart.npred);
matD.place(fyplus, 0, ypart.nys()+ypart.nstat);
for (int i = 0; i < ypart.nboth; i++)
matD.get(ypart.ny()+i, ypart.npred+i) = 1.0;
lapack_int ldb = matD.getLD();
// Form matrix E
TwoDMatrix matE(n, n);
matE.zeros();
matE.place(fymins, 0, 0);
matE.place(fyszero, 0, ypart.nys());
matE.place(fyfzero, 0, ypart.nys()+ypart.nstat+ypart.nboth);
for (int i = 0; i < ypart.nboth; i++)
matE.get(ypart.ny()+i, ypart.nys()+ypart.nstat+i) = -1.0;
matE.mult(-1.0);
lapack_int lda = matE.getLD();
// Solve generalized Schur decomposition
TwoDMatrix vsl(n, n);
TwoDMatrix vsr(n, n);
lapack_int ldvsl = vsl.getLD(), ldvsr = vsr.getLD();
lapack_int lwork = 100*n+16;
Vector work(lwork);
auto bwork = std::make_unique<lapack_int[]>(n);
lapack_int info;
lapack_int sdim2 = sdim;
{
std::lock_guard<std::mutex> lk{mut};
qz_criterium_global = qz_criterium;
dgges("N", "V", "S", order_eigs, &n, matE.getData().base(), &lda,
matD.getData().base(), &ldb, &sdim2, alphar.base(), alphai.base(),
beta.base(), vsl.getData().base(), &ldvsl, vsr.getData().base(), &ldvsr,
work.base(), &lwork, bwork.get(), &info);
}
if (info)
throw KordException(__FILE__, __LINE__,
"DGGES returns an error in FirstOrder::solve");
sdim = sdim2;
bk_cond = (sdim == ypart.nys());
// Setup submatrices of Z
ConstGeneralMatrix z11(vsr, 0, 0, ypart.nys(), ypart.nys());
ConstGeneralMatrix z12(vsr, 0, ypart.nys(), ypart.nys(), n-ypart.nys());
ConstGeneralMatrix z21(vsr, ypart.nys(), 0, n-ypart.nys(), ypart.nys());
ConstGeneralMatrix z22(vsr, ypart.nys(), ypart.nys(), n-ypart.nys(), n-ypart.nys());
// Calculate derivatives of static and forward
/* Here we calculate X=Z₂₂⁻ᵀZ₁₂ᵀ, where X is sfder in the code. */
GeneralMatrix sfder(transpose(z12));
z22.multInvLeftTrans(sfder);
sfder.mult(-1);
// Calculate derivatives of predetermined
/* Here we calculate g*_y* = Z₁₁T₁₁⁻¹S₁₁Z₁₁⁻¹ = Z₁₁T₁₁⁻¹(Z₁₁⁻ᵀS₁₁ᵀ)ᵀ. */
ConstGeneralMatrix s11(matE, 0, 0, ypart.nys(), ypart.nys());
ConstGeneralMatrix t11(matD, 0, 0, ypart.nys(), ypart.nys());
GeneralMatrix dumm(transpose(s11));
z11.multInvLeftTrans(dumm);
GeneralMatrix preder(transpose(dumm));
t11.multInvLeft(preder);
preder.multLeft(z11);
// Copy derivatives to gy
gy.place(preder, ypart.nstat, 0);
GeneralMatrix sder(sfder, 0, 0, ypart.nstat, ypart.nys());
gy.place(sder, 0, 0);
GeneralMatrix fder(sfder, ypart.nstat+ypart.nboth, 0, ypart.nforw, ypart.nys());
gy.place(fder, ypart.nstat+ypart.nys(), 0);
// Check difference for derivatives of both
GeneralMatrix bder(const_cast<const GeneralMatrix &>(sfder), ypart.nstat, 0, ypart.nboth, ypart.nys());
GeneralMatrix bder2(preder, ypart.npred, 0, ypart.nboth, ypart.nys());
bder.add(-1, bder2);
b_error = bder.getData().getMax();
// Solve derivatives gu
/* The equation Fᵤ=0 can be written as
[f_y**₊][g**_y*][gᵤ*] + [f_y][gᵤ] + [fᵤ] = 0
and rewritten as
[f_y + [0 f_y**₊·g**_y* 0] ] gᵤ = fᵤ
This is exactly what is done here. The matrix [f_y + [0 f_y**₊·g**_y* 0] ]
is matA in the code. */
GeneralMatrix matA(ypart.ny(), ypart.ny());
matA.zeros();
ConstGeneralMatrix gss(gy, ypart.nstat+ypart.npred, 0, ypart.nyss(), ypart.nys());
matA.place(fyplus * gss, 0, ypart.nstat);
ConstGeneralMatrix fyzero(fd, 0, ypart.nyss(), ypart.ny(), ypart.ny());
matA.add(1.0, fyzero);
gu.zeros();
gu.add(-1.0, fuzero);
ConstGeneralMatrix(matA).multInvLeft(gu);
journalEigs();
KORD_RAISE_IF_X(!bk_cond,
"The model is not Blanchard-Kahn stable",
KORD_MD_NOT_STABLE);
if (!gy.isFinite() || !gu.isFinite())
throw KordException(__FILE__, __LINE__,
"NaN or Inf asserted in first order derivatives in FirstOrder::solve()");
}
void
FirstOrder::journalEigs()
{
if (bk_cond)
{
JournalRecord jr(journal);
jr << "Blanchard-Kahn conditition satisfied, model stable" << endrec;
}
else
{
JournalRecord jr(journal);
jr << "Blanchard-Kahn condition not satisfied, model not stable: sdim=" << sdim
<< " npred=" << ypart.nys() << endrec;
}
if (!bk_cond)
for (int i = 0; i < alphar.length(); i++)
{
if (i == sdim || i == ypart.nys())
{
JournalRecord jr(journal);
jr << u8"──────────────────────────────────────────────────── ";
if (i == sdim)
jr << "sdim";
else
jr << "npred";
jr << endrec;
}
JournalRecord jr(journal);
double mod = std::sqrt(alphar[i]*alphar[i]+alphai[i]*alphai[i]);
mod = mod/std::round(100000*std::abs(beta[i]))*100000;
jr << i << "\t(" << alphar[i] << "," << alphai[i] << ") / " << beta[i]
<< " \t" << mod << endrec;
}
}