/* * Copyright © 2006 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 . */ #include "planner_builder.hh" #include "dynare_exception.hh" #include "dynare_model.hh" #include #include using namespace ogdyn; const IntegerMatrix & IntegerMatrix::operator=(const IntegerMatrix &im) { if (nr != im.nr || nc != im.nc) throw DynareException(__FILE__, __LINE__, "Matrices have different dimensions in IntegerMatrix::operator="); std::copy_n(im.data.get(), nr*nc, data.get()); return *this; } const IntegerArray3 & IntegerArray3::operator=(const IntegerArray3 &ia3) { if (n1 != ia3.n1 || n2 != ia3.n2 || n3 != ia3.n3) throw DynareException(__FILE__, __LINE__, "Arrays have different dimensions in IntegerArray3::operator="); std::copy_n(ia3.data.get(), n1*n2*n3, data.get()); return *this; } PlannerBuilder::PlannerBuilder(DynareModel &m, const Tvarset &yyset, Teqset ffset) : yset(), fset(std::move(ffset)), model(m), tb(model.t_plobjective), tbeta(model.t_pldiscount), maxlead(model.atoms.get_maxlead()), minlag(model.atoms.get_minlag()), diff_b(yyset.size(), 1-minlag), diff_f(yyset.size(), fset.size(), 1+maxlead-minlag), static_atoms(), static_tree(), diff_b_static(yyset.size(), 1-minlag), diff_f_static(yyset.size(), fset.size(), 1+maxlead-minlag) { info.num_new_terms -= model.getParser().getTree().get_num_op(); fill_yset(m.atoms.get_name_storage(), yyset); add_derivatives_of_b(); add_derivatives_of_f(); shift_derivatives_of_b(); shift_derivatives_of_f(); beta_multiply_b(); beta_multiply_f(); make_static_version(); lagrange_mult_f(); form_equations(); info.num_new_terms += model.getParser().getTree().get_num_op(); } PlannerBuilder::PlannerBuilder(const PlannerBuilder &pb, ogdyn::DynareModel &m) : yset(), fset(pb.fset), model(m), tb(pb.tb), tbeta(pb.tbeta), maxlead(pb.maxlead), minlag(pb.minlag), diff_b(pb.diff_b), diff_f(pb.diff_f), static_atoms(pb.static_atoms), static_tree(pb.static_tree), diff_b_static(pb.diff_b_static), diff_f_static(pb.diff_f_static), aux_map(), static_aux_map() { fill_yset(m.atoms.get_name_storage(), pb.yset); fill_aux_map(m.atoms.get_name_storage(), pb.aux_map, pb.static_aux_map); } void PlannerBuilder::add_derivatives_of_b() { int yi = 0; for (auto yname = yset.begin(); yname != yset.end(); ++yname, yi++) for (int ll = minlag; ll <= 0; ll++) { int yt = model.atoms.index(*yname, ll); if (yt != -1) diff_b(yi, ll-minlag) = model.eqs.add_derivative(tb, yt); else diff_b(yi, ll-minlag) = ogp::OperationTree::zero; } } void PlannerBuilder::add_derivatives_of_f() { int yi = 0; for (auto yname = yset.begin(); yname != yset.end(); ++yname, yi++) for (unsigned int fi = 0; fi < fset.size(); fi++) for (int ll = minlag; ll <= maxlead; ll++) { int yt = model.atoms.index(*yname, ll); if (yt != -1) diff_f(yi, fi, ll-minlag) = model.eqs.add_derivative(model.eqs.formula(fset[fi]), yt); else diff_f(yi, fi, ll-minlag) = ogp::OperationTree::zero; } } void PlannerBuilder::shift_derivatives_of_b() { map subst; for (int yi = 0; yi < diff_b.nrows(); yi++) for (int ll = minlag; ll < 0; ll++) if (diff_b(yi, ll-minlag) != ogp::OperationTree::zero) { model.variable_shift_map(model.eqs.nulary_of_term(diff_b(yi, ll-minlag)), -ll, subst); diff_b(yi, ll-minlag) = model.eqs.add_substitution(diff_b(yi, ll-minlag), subst); } } void PlannerBuilder::shift_derivatives_of_f() { map subst; for (int yi = 0; yi < diff_f.dim1(); yi++) for (int fi = 0; fi < diff_f.dim2(); fi++) { // first do it leads which are put under expectation before t: no problem for (int ll = 0; ll <= maxlead; ll++) if (diff_f(yi, fi, ll-minlag) != ogp::OperationTree::zero) { model.variable_shift_map(model.eqs.nulary_of_term(diff_f(yi, fi, ll-minlag)), -ll, subst); diff_f(yi, fi, ll-minlag) = model.eqs.add_substitution(diff_f(yi, fi, ll-minlag), subst); } /* now do it for lags, these are put as leads under expectations after time t, so we have to introduce auxiliary variables at time t, and make leads of them here */ for (int ll = minlag; ll < 0; ll++) { int ft = diff_f(yi, fi, ll-minlag); if (ft != ogp::OperationTree::zero) { /* if the ft term has a lead, than we need to introduce an auxiliary variable zₜ, define it as 𝔼ₜ[ft] and put z_{t-ll} to the equation. Otherwise, we just put leaded ft to the equation directly. */ int ft_maxlead, ft_minlag; model.termspan(ft, ft_maxlead, ft_minlag); if (ft_maxlead > 0) { // make an auxiliary variable std::string name; name = "AUX_" + std::to_string(yi) + '_' + std::to_string(fset[fi]) + '_' + std::to_string(-ll); model.atoms.register_uniq_endo(name); info.num_aux_variables++; int taux = model.eqs.add_nulary(name); name = "AUX_" + std::to_string(yi) + '_' + std::to_string(fset[fi]) + '_' + std::to_string(-ll) + '(' + std::to_string(-ll) + ')'; int taux_leaded = model.eqs.add_nulary(name); // put aux_leaded to the equation diff_f(yi, fi, ll-minlag) = taux_leaded; // save auxiliary variable and the term aux_map.emplace(model.atoms.name(taux), ft); } else { /* no auxiliary variable is needed and the term ft can be leaded in place */ model.variable_shift_map(model.eqs.nulary_of_term(ft), -ll, subst); diff_f(yi, fi, ll-minlag) = model.eqs.add_substitution(ft, subst); } } } } } void PlannerBuilder::beta_multiply_b() { int beta_pow = ogp::OperationTree::one; for (int ll = 0; ll >= minlag; ll--, beta_pow = model.eqs.add_binary(ogp::code_t::TIMES, beta_pow, tbeta)) for (int yi = 0; yi < diff_b.nrows(); yi++) if (diff_b(yi, ll-minlag) != ogp::OperationTree::zero) diff_b(yi, ll-minlag) = model.eqs.add_binary(ogp::code_t::TIMES, beta_pow, diff_b(yi, ll-minlag)); } void PlannerBuilder::beta_multiply_f() { int beta_pow = ogp::OperationTree::one; for (int ll = 0; ll <= maxlead; ll++, beta_pow = model.eqs.add_binary(ogp::code_t::DIVIDE, beta_pow, tbeta)) for (int yi = 0; yi < diff_f.dim1(); yi++) for (int fi = 0; fi < diff_f.dim2(); fi++) if (diff_f(yi, fi, ll-minlag) != ogp::OperationTree::zero) diff_f(yi, fi, ll-minlag) = model.eqs.add_binary(ogp::code_t::TIMES, beta_pow, diff_f(yi, fi, ll-minlag)); beta_pow = ogp::OperationTree::one; for (int ll = 0; ll >= minlag; ll--, beta_pow = model.eqs.add_binary(ogp::code_t::TIMES, beta_pow, tbeta)) for (int yi = 0; yi < diff_f.dim1(); yi++) for (int fi = 0; fi < diff_f.dim2(); fi++) if (diff_f(yi, fi, ll-minlag) != ogp::OperationTree::zero) diff_f(yi, fi, ll-minlag) = model.eqs.add_binary(ogp::code_t::TIMES, beta_pow, diff_f(yi, fi, ll-minlag)); } void PlannerBuilder::make_static_version() { // map holding substitutions from dynamic to static ogp::StaticFineAtoms::Tintintmap tmap; // fill static atoms with outer ordering static_atoms.import_atoms(model.atoms, static_tree, tmap); // go through diff_b and fill diff_b_static for (int ll = minlag; ll <= 0; ll++) for (int yi = 0; yi < diff_b.nrows(); yi++) diff_b_static(yi, ll-minlag) = static_tree.add_substitution(diff_b(yi, ll-minlag), tmap, model.eqs.getTree()); // go through diff_f and fill diff_f_static for (int ll = minlag; ll <= maxlead; ll++) for (int yi = 0; yi < diff_f.dim1(); yi++) for (int fi = 0; fi < diff_f.dim2(); fi++) diff_f_static(yi, fi, ll-minlag) = static_tree.add_substitution(diff_f(yi, fi, ll-minlag), tmap, model.eqs.getTree()); // go through aux_map and fill static_aux_map for (const auto &it : aux_map) { int tstatic = static_tree.add_substitution(it.second, tmap, model.eqs.getTree()); static_aux_map.emplace(it.first, tstatic); } } void PlannerBuilder::lagrange_mult_f() { // register multipliers std::string mult_name; for (int fi = 0; fi < diff_f.dim2(); fi++) { mult_name = "MULT" + std::to_string(fset[fi]); model.atoms.register_uniq_endo(mult_name); info.num_lagrange_mults++; } // multiply with the multipliers for (int yi = 0; yi < diff_f.dim1(); yi++) for (int fi = 0; fi < diff_f.dim2(); fi++) for (int ll = minlag; ll <= maxlead; ll++) if (diff_f(yi, fi, ll-minlag) != ogp::OperationTree::zero) { mult_name = "MULT" + std::to_string(fset[fi]) + '(' + std::to_string(-ll) + ')'; int tm = model.eqs.add_nulary(mult_name); diff_f(yi, fi, ll-minlag) = model.eqs.add_binary(ogp::code_t::TIMES, tm, diff_f(yi, fi, ll-minlag)); } } void PlannerBuilder::form_equations() { // add planner’s FOCs for (int yi = 0; yi < diff_f.dim1(); yi++) { int eq = ogp::OperationTree::zero; for (int ll = minlag; ll <= 0; ll++) eq = model.eqs.add_binary(ogp::code_t::PLUS, eq, diff_b(yi, ll-minlag)); for (int fi = 0; fi < diff_f.dim2(); fi++) for (int ll = minlag; ll <= maxlead; ll++) eq = model.eqs.add_binary(ogp::code_t::PLUS, eq, diff_f(yi, fi, ll-minlag)); model.eqs.add_formula(eq); } // add equations for auxiliary variables for (const auto &it : aux_map) { int t = model.atoms.index(it.first, 0); model.eqs.add_formula(model.eqs.add_binary(ogp::code_t::MINUS, t, it.second)); } } void PlannerBuilder::fill_yset(const ogp::NameStorage &ns, const PlannerBuilder::Tvarset &yyset) { for (auto it : yyset) yset.insert(it); } void PlannerBuilder::fill_aux_map(const ogp::NameStorage &ns, const Tsubstmap &aaux_map, const Tsubstmap &astatic_aux_map) { // fill aux_map for (auto it : aaux_map) aux_map.insert(it); // fill static_aux_map for (auto it : astatic_aux_map) static_aux_map.insert(it); } MultInitSS::MultInitSS(const PlannerBuilder &pb, const Vector &pvals, Vector &yy) : builder(pb), b(builder.diff_b_static.nrows()), F(builder.diff_f_static.dim1(), builder.diff_f_static.dim2()) { b.zeros(); F.zeros(); // first evaluate substitutions (auxiliary variables) from the builder ogdyn::DynareStaticSteadySubstitutions dss(builder.model.atoms, builder.static_atoms, builder.static_tree, builder.static_aux_map, pvals, yy); /* gather all the terms from builder.diff_b_static and builder.diff_f_static to the vector, the ordering is important, since the index of this vector will have to be decoded to the position in b and F. */ vector terms; for (int yi = 0; yi < builder.diff_b_static.nrows(); yi++) for (int l = 0; l < builder.diff_b_static.ncols(); l++) terms.push_back(builder.diff_b_static(yi, l)); for (int yi = 0; yi < builder.diff_f_static.dim1(); yi++) for (int fi = 0; fi < builder.diff_f_static.dim2(); fi++) for (int l = 0; l < builder.diff_f_static.dim3(); l++) terms.push_back(builder.diff_f_static(yi, fi, l)); /* evaluate the terms, it will call a series of load(i,res), which sum the results through lags/leads to b and F */ DynareStaticSteadyAtomValues dssav(builder.model.atoms, builder.static_atoms, pvals, yy); ogp::FormulaCustomEvaluator fe(builder.static_tree, terms); fe.eval(dssav, *this); // solve overdetermined system b+F*lambda=0 using SVD decomposition SVDDecomp decomp(F); Vector lambda(builder.diff_f_static.dim2()); decomp.solve(b, lambda); lambda.mult(-1); // take values of lambda and put it to yy for (int fi = 0; fi < builder.diff_f_static.dim2(); fi++) { std::string mult_name = "MULT" + std::to_string(builder.fset[fi]); int iouter = builder.model.atoms.name2outer_endo(mult_name); int iy = builder.model.atoms.outer2y_endo()[iouter]; if (!std::isfinite(yy[iy])) yy[iy] = lambda[fi]; /* go through all substitutions of the multiplier and set them as well */ if (builder.model.atom_substs) { const ogp::AtomSubstitutions::Toldnamemap &old2new = builder.model.atom_substs->get_old2new(); auto it = old2new.find(mult_name); if (it != old2new.end()) { const ogp::AtomSubstitutions::Tshiftnameset &sset = it->second; for (const auto &itt : sset) { const std::string &newname = itt.first; int iouter = builder.model.atoms.name2outer_endo(newname); int iy = builder.model.atoms.outer2y_endo()[iouter]; if (!std::isfinite(yy[iy])) yy[iy] = lambda[fi]; } } } } } void MultInitSS::load(int i, double res) { /* we can afford it, since the evaluator sets res to exact zero if the term is zero */ if (res == 0) return; // decode i and add to either b or F if (i < builder.diff_b_static.nrows()*builder.diff_b_static.ncols()) // add to b b[i / builder.diff_b_static.ncols()] += res; else { // add to F i -= builder.diff_b_static.nrows()*builder.diff_b_static.ncols(); int yifi = i / builder.diff_f_static.dim3(); int yi = yifi / builder.diff_f_static.dim2(); int fi = yifi % builder.diff_f_static.dim2(); F.get(yi, fi) += res; } }