/* * Copyright © 2021-2022 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 "dynamic_dll.hh" #include "dynamic_m.hh" #include "objective_m.hh" #include "SylvException.hh" #include "approximation.hh" #include "approximation_welfare.hh" #include "dynare_exception.hh" #include "exception.hh" #include "kord_exception.hh" #include "tl_exception.hh" #include #include #include "dynmex.h" /* Convert MATLAB Dynare endo and exo names cell array to a vector array of string pointers. */ std::vector DynareMxArrayToString(const mxArray* mxFldp) { assert(mxIsCell(mxFldp)); std::vector 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 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[]) { if (nlhs != 1 || nrhs != 3) mexErrMsgTxt("Must have exactly 3 input arguments and 1 output argument"); 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(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(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(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(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(mxGetScalar(debug_mx)); const mxArray* pruning_mx = mxGetField(options_mx, 0, "pruning"); if (!(pruning_mx && mxIsLogicalScalar(pruning_mx))) mexErrMsgTxt("options_.pruning should be a logical scalar"); bool pruning = static_cast(mxGetScalar(pruning_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 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(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) && !mxIsComplex(nnzderivatives_obj_mx) && !mxIsSparse(nnzderivatives_obj_mx))) mexErrMsgTxt("M_.NNZDerivatives should be a real dense 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(nEndo))) mexErrMsgTxt("M_.endo_names should be a cell array of M_.endo_nbr elements"); std::vector 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(nExog))) mexErrMsgTxt("M_.exo_names should be a cell array of M_.exo_nbr elements"); std::vector 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) && !mxIsComplex(dynamic_tmp_nbr_mx) && !mxIsSparse(dynamic_tmp_nbr_mx) && mxGetNumberOfElements(dynamic_tmp_nbr_mx) >= static_cast(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) && !mxIsComplex(ys_mx) && !mxIsSparse(ys_mx))) mexErrMsgTxt("dr.ys should be a real dense 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) && !mxIsComplex(order_var_mx) && !mxIsSparse(order_var_mx) && mxGetNumberOfElements(order_var_mx) == static_cast(nEndo))) mexErrMsgTxt("dr.order_var should be a real dense array of M_.endo_nbr elements"); std::vector dr_order(nEndo); std::transform(mxGetPr(order_var_mx), mxGetPr(order_var_mx) + nEndo, dr_order.begin(), [](double x) { return static_cast(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 dynamicModelFile; if (use_dll) dynamicModelFile = std::make_unique(fname, ntt, kOrder); else dynamicModelFile = std::make_unique(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, pruning, 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) && !mxIsComplex(objective_tmp_nbr_mx) && !mxIsSparse(objective_tmp_nbr_mx) && mxGetNumberOfElements(objective_tmp_nbr_mx) >= static_cast(kOrder + 1))) mexErrMsgTxt("M_.objective_tmp_nbr should be a real dense 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 objectiveFile; objectiveFile = std::make_unique(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(); const FoldDecisionRule& cond_fdr = appwel.getFoldCondWel(); // Add possibly missing field names for (int i = static_cast(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[0] = 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[0], 0, ("W_" + std::to_string(i)).c_str(), tmp); } } // end of mexFunction() } // end of extern C