diff --git a/mex/sources/perfect_foresight_problem/DynamicModelCaller.cc b/mex/sources/perfect_foresight_problem/DynamicModelCaller.cc index f85f1616e025b08b6c049a125c37c058c1f4b17f..a3148239041b5a2fd3371aed03d5cd625bca7387 100644 --- a/mex/sources/perfect_foresight_problem/DynamicModelCaller.cc +++ b/mex/sources/perfect_foresight_problem/DynamicModelCaller.cc @@ -1,5 +1,5 @@ /* - * Copyright © 2019-2022 Dynare Team + * Copyright © 2019-2023 Dynare Team * * This file is part of Dynare. * @@ -22,44 +22,51 @@ #include "DynamicModelCaller.hh" #include <algorithm> +#include <filesystem> + +using namespace std::literals::string_literals; std::string DynamicModelCaller::error_msg; -#if defined(_WIN32) || defined(__CYGWIN32__) -HINSTANCE DynamicModelDllCaller::dynamic_mex{nullptr}; +#if !defined(_WIN32) && !defined(__CYGWIN32__) +void *DynamicModelDllCaller::resid_mex{nullptr}; +void *DynamicModelDllCaller::g1_mex{nullptr}; #else -void *DynamicModelDllCaller::dynamic_mex{nullptr}; +HINSTANCE DynamicModelDllCaller::resid_mex{nullptr}; +HINSTANCE DynamicModelDllCaller::g1_mex{nullptr}; #endif DynamicModelDllCaller::dynamic_tt_fct DynamicModelDllCaller::residual_tt_fct{nullptr}, DynamicModelDllCaller::g1_tt_fct{nullptr}; -DynamicModelDllCaller::dynamic_deriv_fct DynamicModelDllCaller::residual_fct{nullptr}, DynamicModelDllCaller::g1_fct{nullptr}; +DynamicModelDllCaller::dynamic_fct DynamicModelDllCaller::residual_fct{nullptr}, DynamicModelDllCaller::g1_fct{nullptr}; void DynamicModelDllCaller::load_dll(const std::string &basename) { // Load symbols from dynamic MEX - std::string mex_name; -#if !defined(__CYGWIN32__) && !defined(_WIN32) - mex_name = "./"; -#endif - mex_name += "+" + basename + "/dynamic" + MEXEXT; + const std::filesystem::path sparse_dir {"+" + basename + "/+sparse/"}; + const std::filesystem::path resid_mex_name {sparse_dir / ("dynamic_resid"s + MEXEXT)}, + g1_mex_name {sparse_dir / ("dynamic_g1"s + MEXEXT)}; #if !defined(__CYGWIN32__) && !defined(_WIN32) - dynamic_mex = dlopen(mex_name.c_str(), RTLD_NOW); + resid_mex = dlopen(resid_mex_name.c_str(), RTLD_NOW); + g1_mex = dlopen(g1_mex_name.c_str(), RTLD_NOW); #else - dynamic_mex = LoadLibrary(mex_name.c_str()); + resid_mex = LoadLibraryW(resid_mex_name.c_str()); + g1_mex = LoadLibraryW(g1_mex_name.c_str()); #endif - if (!dynamic_mex) - mexErrMsgTxt("Can't load dynamic MEX file"); + if (!resid_mex) + mexErrMsgTxt("Can't load dynamic_resid MEX file"); + if (!g1_mex) + mexErrMsgTxt("Can't load dynamic_g1 MEX file"); #if !defined(__CYGWIN32__) && !defined(_WIN32) - residual_tt_fct = reinterpret_cast<dynamic_tt_fct>(dlsym(dynamic_mex, "dynamic_resid_tt")); - residual_fct = reinterpret_cast<dynamic_deriv_fct>(dlsym(dynamic_mex, "dynamic_resid")); - g1_tt_fct = reinterpret_cast<dynamic_tt_fct>(dlsym(dynamic_mex, "dynamic_g1_tt")); - g1_fct = reinterpret_cast<dynamic_deriv_fct>(dlsym(dynamic_mex, "dynamic_g1")); + residual_tt_fct = reinterpret_cast<dynamic_tt_fct>(dlsym(resid_mex, "dynamic_resid_tt")); + residual_fct = reinterpret_cast<dynamic_fct>(dlsym(resid_mex, "dynamic_resid")); + g1_tt_fct = reinterpret_cast<dynamic_tt_fct>(dlsym(g1_mex, "dynamic_g1_tt")); + g1_fct = reinterpret_cast<dynamic_fct>(dlsym(g1_mex, "dynamic_g1")); #else - residual_tt_fct = reinterpret_cast<dynamic_tt_fct>(GetProcAddress(dynamic_mex, "dynamic_resid_tt")); - residual_fct = reinterpret_cast<dynamic_deriv_fct>(GetProcAddress(dynamic_mex, "dynamic_resid")); - g1_tt_fct = reinterpret_cast<dynamic_tt_fct>(GetProcAddress(dynamic_mex, "dynamic_g1_tt")); - g1_fct = reinterpret_cast<dynamic_deriv_fct>(GetProcAddress(dynamic_mex, "dynamic_g1")); + residual_tt_fct = reinterpret_cast<dynamic_tt_fct>(GetProcAddress(resid_mex, "dynamic_resid_tt")); + residual_fct = reinterpret_cast<dynamic_fct>(GetProcAddress(resid_mex, "dynamic_resid")); + g1_tt_fct = reinterpret_cast<dynamic_tt_fct>(GetProcAddress(g1_mex, "dynamic_g1_tt")); + g1_fct = reinterpret_cast<dynamic_fct>(GetProcAddress(g1_mex, "dynamic_g1")); #endif if (!residual_tt_fct || !residual_fct || !g1_tt_fct || !g1_fct) mexErrMsgTxt("Can't load functions in dynamic MEX file"); @@ -69,91 +76,120 @@ void DynamicModelDllCaller::unload_dll() { #if !defined(__CYGWIN32__) && !defined(_WIN32) - dlclose(dynamic_mex); + dlclose(resid_mex); + dlclose(g1_mex); #else - FreeLibrary(dynamic_mex); + FreeLibrary(resid_mex); + FreeLibrary(g1_mex); #endif } -DynamicModelDllCaller::DynamicModelDllCaller(size_t ntt, mwIndex nx, mwIndex ny, size_t ndynvars, const double *x_arg, size_t nb_row_x_arg, const double *params_arg, const double *steady_state_arg, bool linear_arg, bool compute_jacobian_arg) : +DynamicModelDllCaller::DynamicModelDllCaller(size_t ntt, mwIndex ny, mwIndex nx, const double *params_arg, const double *steady_state_arg, const int32_T *g1_sparse_colptr_arg, bool linear_arg, bool compute_jacobian_arg) : DynamicModelCaller{linear_arg, compute_jacobian_arg}, - nb_row_x{nb_row_x_arg}, x{x_arg}, params{params_arg}, steady_state{steady_state_arg} + params{params_arg}, steady_state{steady_state_arg}, + g1_sparse_colptr{g1_sparse_colptr_arg} { tt = std::make_unique<double[]>(ntt); - y_p = std::make_unique<double[]>(ndynvars); + y_p = std::make_unique<double[]>(3*ny); + x_p = std::make_unique<double[]>(nx); if (compute_jacobian) - jacobian_p = std::make_unique<double[]>((ndynvars+nx)*ny); + jacobian_p = std::make_unique<double[]>(g1_sparse_colptr[3*ny+nx]-1); } void -DynamicModelDllCaller::eval(int it, double *resid) +DynamicModelDllCaller::copy_jacobian_column(mwIndex col, double *dest) const { - residual_tt_fct(y_p.get(), x, nb_row_x, params, steady_state, it, tt.get()); - residual_fct(y_p.get(), x, nb_row_x, params, steady_state, it, tt.get(), resid); + std::copy_n(jacobian_p.get() + g1_sparse_colptr[col]-1, + g1_sparse_colptr[col+1] - g1_sparse_colptr[col], dest); +} + +void +DynamicModelDllCaller::eval(double *resid) +{ + residual_tt_fct(y_p.get(), x_p.get(), params, steady_state, tt.get()); + residual_fct(y_p.get(), x_p.get(), params, steady_state, tt.get(), resid); if (compute_jacobian) { - g1_tt_fct(y_p.get(), x, nb_row_x, params, steady_state, it, tt.get()); - g1_fct(y_p.get(), x, nb_row_x, params, steady_state, it, tt.get(), jacobian_p.get()); + g1_tt_fct(y_p.get(), x_p.get(), params, steady_state, tt.get()); + g1_fct(y_p.get(), x_p.get(), params, steady_state, tt.get(), jacobian_p.get()); if (linear) compute_jacobian = false; // If model is linear, no need to recompute Jacobian later } } -DynamicModelMatlabCaller::DynamicModelMatlabCaller(std::string basename_arg, size_t ntt, size_t ndynvars, const mxArray *x_mx_arg, const mxArray *params_mx_arg, const mxArray *steady_state_mx_arg, bool linear_arg, bool compute_jacobian_arg) : +DynamicModelMatlabCaller::DynamicModelMatlabCaller(std::string basename_arg, mwIndex ny, mwIndex nx, const mxArray *params_mx_arg, const mxArray *steady_state_mx_arg, const mxArray *g1_sparse_rowval_mx_arg, const mxArray *g1_sparse_colval_mx_arg, const mxArray *g1_sparse_colptr_mx_arg, bool linear_arg, bool compute_jacobian_arg) : DynamicModelCaller{linear_arg, compute_jacobian_arg}, basename{std::move(basename_arg)}, - T_mx{mxCreateDoubleMatrix(ntt, 1, mxREAL)}, - y_mx{mxCreateDoubleMatrix(ndynvars, 1, mxREAL)}, - it_mx{mxCreateDoubleScalar(0)}, - T_flag_mx{mxCreateLogicalScalar(false)}, + y_mx{mxCreateDoubleMatrix(3*ny, 1, mxREAL)}, + x_mx{mxCreateDoubleMatrix(nx, 1, mxREAL)}, jacobian_mx{nullptr}, - x_mx{mxDuplicateArray(x_mx_arg)}, params_mx{mxDuplicateArray(params_mx_arg)}, - steady_state_mx{mxDuplicateArray(steady_state_mx_arg)} + steady_state_mx{mxDuplicateArray(steady_state_mx_arg)}, + g1_sparse_rowval_mx{mxDuplicateArray(g1_sparse_rowval_mx_arg)}, + g1_sparse_colval_mx{mxDuplicateArray(g1_sparse_colval_mx_arg)}, + g1_sparse_colptr_mx{mxDuplicateArray(g1_sparse_colptr_mx_arg)} { } DynamicModelMatlabCaller::~DynamicModelMatlabCaller() { - mxDestroyArray(T_mx); mxDestroyArray(y_mx); - mxDestroyArray(it_mx); - mxDestroyArray(T_flag_mx); + mxDestroyArray(x_mx); if (jacobian_mx) mxDestroyArray(jacobian_mx); - mxDestroyArray(x_mx); mxDestroyArray(params_mx); mxDestroyArray(steady_state_mx); + mxDestroyArray(g1_sparse_rowval_mx); + mxDestroyArray(g1_sparse_colval_mx); + mxDestroyArray(g1_sparse_colptr_mx); } void -DynamicModelMatlabCaller::eval(int it, double *resid) +DynamicModelMatlabCaller::copy_jacobian_column(mwIndex col, double *dest) const { - *mxGetPr(it_mx) = it + 1; - - { - // Compute temporary terms - std::string funcname = basename + (compute_jacobian ? ".dynamic_g1_tt" : ".dynamic_resid_tt"); - mxArray *plhs[1], *prhs[] = { T_mx, y_mx, x_mx, params_mx, steady_state_mx, it_mx }; + if (jacobian_mx) + { +#if MX_HAS_INTERLEAVED_COMPLEX + const int32_T *g1_sparse_rowval {mxGetInt32s(g1_sparse_rowval_mx)}; + const int32_T *g1_sparse_colptr {mxGetInt32s(g1_sparse_colptr_mx)}; +#else + const int32_T *g1_sparse_rowval {static_cast<const int32_T *>(mxGetData(g1_sparse_rowval_mx))}; + const int32_T *g1_sparse_colptr {static_cast<const int32_T *>(mxGetData(g1_sparse_colptr_mx))}; +#endif - mxArray *exception = mexCallMATLABWithTrap(1, plhs, 6, prhs, funcname.c_str()); - if (exception) - { - error_msg = "An error occurred when calling " + funcname; - return; // Avoid manipulating null pointers in plhs, see #1832 - } + /* We cannot assume that jacobian_mx internally uses + g1_sparse_{rowval,colval,colptr}, because the call to sparse() in + dynamic_g1.m may have further compressed the matrix by removing + elements that are numerically zero, despite being symbolically + non-zero. */ + mwIndex *ir {mxGetIr(jacobian_mx)}, *jc {mxGetJc(jacobian_mx)}; + mwIndex isrc {jc[col]}; // Index in value array of source Jacobian + for (mwIndex idest {0}; // Index in value array of destination Jacobian + idest < static_cast<mwIndex>(g1_sparse_colptr[col+1]-g1_sparse_colptr[col]); idest++) + { + mwIndex row {static_cast<mwIndex>(g1_sparse_rowval[idest+g1_sparse_colptr[col]-1]-1)}; + while (isrc < jc[col+1] && ir[isrc] < row) + isrc++; + if (isrc < jc[col+1] && ir[isrc] == row) + dest[idest] = mxGetPr(jacobian_mx)[isrc]; + else + dest[idest] = 0.0; + } + } +} - mxDestroyArray(T_mx); - T_mx = plhs[0]; - } +void +DynamicModelMatlabCaller::eval(double *resid) +{ + mxArray *T_order_mx, *T_mx; { // Compute residuals - std::string funcname = basename + ".dynamic_resid"; - mxArray *plhs[1], *prhs[] = { T_mx, y_mx, x_mx, params_mx, steady_state_mx, it_mx, T_flag_mx }; + std::string funcname {basename + ".sparse.dynamic_resid"}; + mxArray *plhs[3], *prhs[] = { y_mx, x_mx, params_mx, steady_state_mx }; - mxArray *exception = mexCallMATLABWithTrap(1, plhs, 7, prhs, funcname.c_str()); + mxArray *exception { mexCallMATLABWithTrap(3, plhs, 4, prhs, funcname.c_str()) }; if (exception) { error_msg = "An error occurred when calling " + funcname; @@ -167,19 +203,22 @@ DynamicModelMatlabCaller::eval(int it, double *resid) } if (mxIsComplex(plhs[0])) - plhs[0] = cmplxToReal(plhs[0]); + plhs[0] = cmplxToReal<false>(plhs[0]); std::copy_n(mxGetPr(plhs[0]), mxGetNumberOfElements(plhs[0]), resid); mxDestroyArray(plhs[0]); + + T_order_mx = plhs[1]; + T_mx = plhs[2]; } if (compute_jacobian) { // Compute Jacobian - std::string funcname = basename + ".dynamic_g1"; - mxArray *plhs[1], *prhs[] = { T_mx, y_mx, x_mx, params_mx, steady_state_mx, it_mx, T_flag_mx }; + std::string funcname {basename + ".sparse.dynamic_g1"}; + mxArray *plhs[1], *prhs[] = { y_mx, x_mx, params_mx, steady_state_mx, g1_sparse_rowval_mx, g1_sparse_colval_mx, g1_sparse_colptr_mx, T_order_mx, T_mx }; - mxArray *exception = mexCallMATLABWithTrap(1, plhs, 7, prhs, funcname.c_str()); + mxArray *exception { mexCallMATLABWithTrap(1, plhs, 9, prhs, funcname.c_str()) }; if (exception) { error_msg = "An error occurred when calling " + funcname; @@ -192,48 +231,21 @@ DynamicModelMatlabCaller::eval(int it, double *resid) jacobian_mx = nullptr; } - if (!mxIsDouble(plhs[0]) || mxIsSparse(plhs[0])) + if (!mxIsDouble(plhs[0]) || !mxIsSparse(plhs[0])) { - error_msg = "Jacobian should be a dense array of double floats"; + error_msg = "Jacobian should be a sparse array of double floats"; return; } if (mxIsComplex(plhs[0])) - jacobian_mx = cmplxToReal(plhs[0]); + jacobian_mx = cmplxToReal<true>(plhs[0]); else jacobian_mx = plhs[0]; if (linear) compute_jacobian = false; // If model is linear, no need to recompute Jacobian later } -} -/* NB: This is a duplicate of DynamicModelMFile::cmplxToReal() in - k_order_perturbation MEX */ -mxArray * -DynamicModelMatlabCaller::cmplxToReal(mxArray *cmplx_mx) -{ - mxArray *real_mx = mxCreateDoubleMatrix(mxGetM(cmplx_mx), mxGetN(cmplx_mx), mxREAL); - -#if MX_HAS_INTERLEAVED_COMPLEX - mxComplexDouble *cmplx = mxGetComplexDoubles(cmplx_mx); -#else - double *cmplx_real = mxGetPr(cmplx_mx); - double *cmplx_imag = mxGetPi(cmplx_mx); -#endif - double *real = mxGetPr(real_mx); - - for (size_t i = 0; i < mxGetNumberOfElements(cmplx_mx); i++) -#if MX_HAS_INTERLEAVED_COMPLEX - if (cmplx[i].imag == 0.0) - real[i] = cmplx[i].real; -#else - if (cmplx_imag[i] == 0.0) - real[i] = cmplx_real[i]; -#endif - else - real[i] = std::numeric_limits<double>::quiet_NaN(); - - mxDestroyArray(cmplx_mx); - return real_mx; + mxDestroyArray(T_order_mx); + mxDestroyArray(T_mx); } diff --git a/mex/sources/perfect_foresight_problem/DynamicModelCaller.hh b/mex/sources/perfect_foresight_problem/DynamicModelCaller.hh index 2cf682895d3fee4a0deb261d969c21ea6c4a9591..9bbc7246e82c31b4951c53a1840cc9457cb3d578 100644 --- a/mex/sources/perfect_foresight_problem/DynamicModelCaller.hh +++ b/mex/sources/perfect_foresight_problem/DynamicModelCaller.hh @@ -1,5 +1,5 @@ /* - * Copyright © 2019-2022 Dynare Team + * Copyright © 2019-2023 Dynare Team * * This file is part of Dynare. * @@ -20,6 +20,8 @@ #include <string> #include <memory> #include <limits> +#include <type_traits> +#include <algorithm> #if defined(_WIN32) || defined(__CYGWIN32__) # ifndef NOMINMAX @@ -41,43 +43,47 @@ public: DynamicModelCaller(bool linear_arg, bool compute_jacobian_arg) : linear{linear_arg}, compute_jacobian{compute_jacobian_arg} { - }; + } virtual ~DynamicModelCaller() = default; - virtual double &y(size_t i) const = 0; - virtual double jacobian(size_t i) const = 0; - virtual void eval(int it, double *resid) = 0; + virtual double *y() const = 0; + virtual double *x() const = 0; + /* Copy a column of the Jacobian to dest. + Only copies non-zero elements, according to g1_sparse_{rowval,colval,colptr}. */ + virtual void copy_jacobian_column(mwIndex col, double *dest) const = 0; + virtual void eval(double *resid) = 0; }; class DynamicModelDllCaller : public DynamicModelCaller { private: #if defined(_WIN32) || defined(__CYGWIN32__) - static HINSTANCE dynamic_mex; + static HINSTANCE resid_mex, g1_mex; #else - static void *dynamic_mex; + static void *resid_mex, *g1_mex; #endif - using dynamic_tt_fct = void (*)(const double *y, const double *x, int nb_row_x, const double *params, const double *steady_state, int it_, double *T); - using dynamic_deriv_fct = void (*)(const double *y, const double *x, int nb_row_x, const double *params, const double *steady_state, int it_, const double *T, double *deriv); + using dynamic_tt_fct = void (*)(const double *y, const double *x, const double *params, const double *steady_state, double *T); + using dynamic_fct = void (*)(const double *y, const double *x, const double *params, const double *steady_state, const double *T, double *value); static dynamic_tt_fct residual_tt_fct, g1_tt_fct; - static dynamic_deriv_fct residual_fct, g1_fct; - size_t nb_row_x; - const double *x, *params, *steady_state; - std::unique_ptr<double[]> tt, y_p, jacobian_p; + static dynamic_fct residual_fct, g1_fct; + const double *params, *steady_state; + std::unique_ptr<double[]> tt, y_p, x_p, jacobian_p; + const int32_T *g1_sparse_colptr; public: - DynamicModelDllCaller(size_t ntt, mwIndex nx, mwIndex ny, size_t ndynvars, const double *x_arg, size_t nb_row_x_arg, const double *params_arg, const double *steady_state_arg, bool linear_arg, bool compute_jacobian_arg); + DynamicModelDllCaller(size_t ntt, mwIndex ny, mwIndex nx, const double *params_arg, const double *steady_state_arg, const int32_T *g1_sparse_colptr_arg, bool linear_arg, bool compute_jacobian_arg); virtual ~DynamicModelDllCaller() = default; - double & - y(size_t i) const override + double * + y() const override { - return y_p[i]; - }; - double - jacobian(size_t i) const override + return y_p.get(); + } + double * + x() const override { - return jacobian_p[i]; - }; - void eval(int it, double *resid) override; + return x_p.get(); + } + void copy_jacobian_column(mwIndex col, double *dest) const override; + void eval(double *resid) override; static void load_dll(const std::string &basename); static void unload_dll(); }; @@ -86,32 +92,74 @@ class DynamicModelMatlabCaller : public DynamicModelCaller { private: std::string basename; - mxArray *T_mx, *y_mx, *it_mx, *T_flag_mx, *jacobian_mx, *x_mx, *params_mx, *steady_state_mx; - /* Given a complex dense matrix (of double floats), returns a real dense matrix of same size. + mxArray *y_mx, *x_mx, *jacobian_mx, *params_mx, *steady_state_mx, *g1_sparse_rowval_mx, *g1_sparse_colval_mx, *g1_sparse_colptr_mx; + /* Given a complex matrix (of double floats), returns a sparse matrix of same size. Real elements of the original matrix are copied as-is to the new one. Complex elements are replaced by NaNs. - Destroys the original matrix. */ - static mxArray *cmplxToReal(mxArray *m); + Destroys the original matrix. + There are two versions, one for dense matrices, another for sparse + matrices. */ + template<bool sparse> + static mxArray *cmplxToReal(mxArray *cmplx_mx); public: - DynamicModelMatlabCaller(std::string basename_arg, size_t ntt, size_t ndynvars, const mxArray *x_mx_arg, const mxArray *params_mx_arg, const mxArray *steady_state_mx_arg, bool linear_arg, bool compute_jacobian_arg); + DynamicModelMatlabCaller(std::string basename_arg, mwIndex ny, mwIndex nx, const mxArray *params_mx_arg, const mxArray *steady_state_mx_arg, const mxArray *g1_sparse_rowval_mx_arg, const mxArray *g1_sparse_colval_mx_arg, const mxArray *g1_sparse_colptr_mx_arg, bool linear_arg, bool compute_jacobian_arg); ~DynamicModelMatlabCaller() override; - double & - y(size_t i) const override + double * + y() const override { - return mxGetPr(y_mx)[i]; - }; - double - jacobian(size_t i) const override + return mxGetPr(y_mx); + } + double * + x() const override { - return jacobian_mx ? mxGetPr(jacobian_mx)[i] : std::numeric_limits<double>::quiet_NaN(); - }; - void eval(int it, double *resid) override; + return mxGetPr(x_mx); + } + void copy_jacobian_column(mwIndex col, double *dest) const override; + void eval(double *resid) override; class Exception { public: const std::string msg; Exception(std::string msg_arg) : msg{std::move(msg_arg)} { - }; + } }; }; + +template<bool sparse> +mxArray * +DynamicModelMatlabCaller::cmplxToReal(mxArray *cmplx_mx) +{ + mxArray *real_mx {sparse ? + mxCreateSparse(mxGetM(cmplx_mx), mxGetN(cmplx_mx), mxGetNzmax(cmplx_mx), mxREAL) : + mxCreateDoubleMatrix(mxGetM(cmplx_mx), mxGetN(cmplx_mx), mxREAL)}; + + if constexpr(sparse) + { + std::copy_n(mxGetIr(cmplx_mx), mxGetNzmax(cmplx_mx), mxGetIr(real_mx)); + std::copy_n(mxGetJc(cmplx_mx), mxGetN(cmplx_mx)+1, mxGetJc(real_mx)); + } + +#if MX_HAS_INTERLEAVED_COMPLEX + mxComplexDouble *cmplx {mxGetComplexDoubles(cmplx_mx)}; +#else + double *cmplx_real {mxGetPr(cmplx_mx)}; + double *cmplx_imag {mxGetPi(cmplx_mx)}; +#endif + double *real {mxGetPr(real_mx)}; + for (std::conditional_t<sparse, mwSize, size_t> i {0}; + i < [&]{ if constexpr(sparse) return mxGetNzmax(cmplx_mx); else return mxGetNumberOfElements(cmplx_mx); }(); // Use a lambda instead of the ternary operator to have the right type (there is no constexpr ternary operator) + i++) +#if MX_HAS_INTERLEAVED_COMPLEX + if (cmplx[i].imag == 0.0) + real[i] = cmplx[i].real; +#else + if (cmplx_imag[i] == 0.0) + real[i] = cmplx_real[i]; +#endif + else + real[i] = std::numeric_limits<double>::quiet_NaN(); + + mxDestroyArray(cmplx_mx); + return real_mx; +} diff --git a/mex/sources/perfect_foresight_problem/perfect_foresight_problem.cc b/mex/sources/perfect_foresight_problem/perfect_foresight_problem.cc index a9bb4bcf299c0d52da56dccca3a7a396c1e2f2f6..ff7eb159c94802be592523e96875c69fc0b47b92 100644 --- a/mex/sources/perfect_foresight_problem/perfect_foresight_problem.cc +++ b/mex/sources/perfect_foresight_problem/perfect_foresight_problem.cc @@ -1,5 +1,5 @@ /* - * Copyright © 2019-2022 Dynare Team + * Copyright © 2019-2023 Dynare Team * * This file is part of Dynare. * @@ -59,23 +59,12 @@ mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) mexErrMsgTxt("M_.maximum_lag should be a numeric scalar"); mwIndex maximum_lag = static_cast<mwIndex>(mxGetScalar(maximum_lag_mx)); - const mxArray *maximum_endo_lag_mx = mxGetField(M_mx, 0, "maximum_endo_lag"); - if (!(maximum_endo_lag_mx && mxIsScalar(maximum_endo_lag_mx) && mxIsNumeric(maximum_endo_lag_mx))) - mexErrMsgTxt("M_.maximum_endo_lag should be a numeric scalar"); - mwIndex maximum_endo_lag = static_cast<mwIndex>(mxGetScalar(maximum_endo_lag_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) >= 2) || mxIsComplex(dynamic_tmp_nbr_mx) || mxIsSparse(dynamic_tmp_nbr_mx)) mexErrMsgTxt("M_.dynamic_tmp_nbr should be a real dense array of at least 2 elements"); - size_t ntt = mxGetPr(dynamic_tmp_nbr_mx)[0] + mxGetPr(dynamic_tmp_nbr_mx)[1]; - - 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) == static_cast<size_t>(2+maximum_endo_lag) - && mxGetN(lead_lag_incidence_mx) == static_cast<size_t>(ny)) - || mxIsComplex(lead_lag_incidence_mx) || mxIsSparse(lead_lag_incidence_mx)) - mexErrMsgTxt("M_.lead_lag_incidence should be a real dense matrix with 2+M_.maximum_endo_lag rows and M_.endo_nbr columns"); - const double *lead_lag_incidence = mxGetPr(lead_lag_incidence_mx); + size_t ntt {static_cast<size_t>(mxGetPr(dynamic_tmp_nbr_mx)[0]) + + (compute_jacobian ? static_cast<size_t>(mxGetPr(dynamic_tmp_nbr_mx)[1]) : 0)}; const mxArray *has_external_function_mx = mxGetField(M_mx, 0, "has_external_function"); if (!(has_external_function_mx && mxIsLogicalScalar(has_external_function_mx))) @@ -101,41 +90,6 @@ mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) mexErrMsgTxt("options_.threads.perfect_foresight_problem should be a numeric scalar"); int num_threads = static_cast<int>(mxGetScalar(num_threads_mx)); - // Call <model>.dynamic_g1_nz - mxArray *g1_nz_plhs[3]; - if (mexCallMATLAB(3, g1_nz_plhs, 0, nullptr, (basename + ".dynamic_g1_nz").c_str()) != 0) - mexErrMsgTxt(("Could not call " + basename + ".dynamic_g1_nz").c_str()); - const mxArray *nzij_pred_mx = g1_nz_plhs[0]; - const mxArray *nzij_current_mx = g1_nz_plhs[1]; - const mxArray *nzij_fwrd_mx = g1_nz_plhs[2]; - - if (!(mxIsInt32(nzij_pred_mx) && mxGetN(nzij_pred_mx) == 2)) - mexErrMsgTxt("nzij_pred should be an int32 matrix with 2 columns"); - size_t nnz_pred = mxGetM(nzij_pred_mx); -#if MX_HAS_INTERLEAVED_COMPLEX - const int32_T *nzij_pred = mxGetInt32s(nzij_pred_mx); -#else - const int32_T *nzij_pred = static_cast<const int32_T *>(mxGetData(nzij_pred_mx)); -#endif - - if (!(mxIsInt32(nzij_current_mx) && mxGetN(nzij_current_mx) == 2)) - mexErrMsgTxt("nzij_current should be an int32 matrix with 2 columns"); - size_t nnz_current = mxGetM(nzij_current_mx); -#if MX_HAS_INTERLEAVED_COMPLEX - const int32_T *nzij_current = mxGetInt32s(nzij_current_mx); -#else - const int32_T *nzij_current = static_cast<const int32_T *>(mxGetData(nzij_current_mx)); -#endif - - if (!(mxIsInt32(nzij_fwrd_mx) && mxGetN(nzij_fwrd_mx) == 2)) - mexErrMsgTxt("nzij_fwrd should be an int32 matrix with 2 columns"); - size_t nnz_fwrd = mxGetM(nzij_fwrd_mx); -#if MX_HAS_INTERLEAVED_COMPLEX - const int32_T *nzij_fwrd = mxGetInt32s(nzij_fwrd_mx); -#else - const int32_T *nzij_fwrd = static_cast<const int32_T *>(mxGetData(nzij_fwrd_mx)); -#endif - // Check other input and map it to local variables if (!(mxIsScalar(periods_mx) && mxIsNumeric(periods_mx))) mexErrMsgTxt("periods should be a numeric scalar"); @@ -159,6 +113,32 @@ mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) size_t nb_row_x = mxGetM(exo_path_mx); const double *exo_path = mxGetPr(exo_path_mx); + const mxArray *g1_sparse_rowval_mx {mxGetField(M_mx, 0, "dynamic_g1_sparse_rowval")}; + if (!(mxIsInt32(g1_sparse_rowval_mx))) + mexErrMsgTxt("M_.dynamic_g1_sparse_rowval should be an int32 vector"); +#if MX_HAS_INTERLEAVED_COMPLEX + const int32_T *g1_sparse_rowval {mxGetInt32s(g1_sparse_rowval_mx)}; +#else + const int32_T *g1_sparse_rowval {static_cast<const int32_T *>(mxGetData(g1_sparse_rowval_mx))}; +#endif + + const mxArray *g1_sparse_colval_mx {mxGetField(M_mx, 0, "dynamic_g1_sparse_colval")}; + if (!(mxIsInt32(g1_sparse_colval_mx))) + mexErrMsgTxt("M_.dynamic_g1_sparse_colval should be an int32 vector"); + if (mxGetNumberOfElements(g1_sparse_colval_mx) != mxGetNumberOfElements(g1_sparse_rowval_mx)) + mexErrMsgTxt("M_.dynamic_g1_sparse_colval should have the same length as M_.dynamic_g1_sparse_rowval"); + + const mxArray *g1_sparse_colptr_mx {mxGetField(M_mx, 0, "dynamic_g1_sparse_colptr")}; + if (!(mxIsInt32(g1_sparse_colptr_mx) && mxGetNumberOfElements(g1_sparse_colptr_mx) != 3*static_cast<size_t>(ny)+1)) + mexErrMsgTxt(("M_.dynamic_g1_sparse_colptr should be an int32 vector with " + std::to_string(3*ny+1) + " elements").c_str()); +#if MX_HAS_INTERLEAVED_COMPLEX + const int32_T *g1_sparse_colptr {mxGetInt32s(g1_sparse_colptr_mx)}; +#else + const int32_T *g1_sparse_colptr {static_cast<const int32_T *>(mxGetData(g1_sparse_colptr_mx))}; +#endif + if (static_cast<size_t>(g1_sparse_colptr[3*ny+nx])-1 != mxGetNumberOfElements(g1_sparse_rowval_mx)) + mexErrMsgTxt("The size of M_.dynamic_g1_sparse_rowval is not consistent with the last element of M_.dynamic_g1_sparse_colptr"); + if (!(mxIsDouble(params_mx) && mxGetN(params_mx) == 1)) mexErrMsgTxt("params should be a double precision column-vector"); const double *params = mxGetPr(params_mx); @@ -171,7 +151,10 @@ mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) plhs[0] = mxCreateDoubleMatrix(periods*ny, 1, mxREAL); double *stacked_residual = mxGetPr(plhs[0]); - mwIndex nzmax = periods*nnz_current+(periods-1)*(nnz_pred+nnz_fwrd); + /* Number of non-zero values in the stacked Jacobian. + Contemporaneous derivatives appear at all periods, while lag or lead + derivatives appear at all periods except one. */ + mwSize nzmax {static_cast<mwSize>((g1_sparse_colptr[3*ny]-1)*(periods-1) + (g1_sparse_colptr[2*ny]-g1_sparse_colptr[ny]))}; double *stacked_jacobian = nullptr; mwIndex *ir = nullptr, *jc = nullptr; @@ -188,25 +171,20 @@ mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) mwIndex k = 0; jc[0] = 0; for (mwIndex T = 0; T < periods; T++) - { - size_t row_pred = 0, row_current = 0, row_fwrd = 0; - for (int32_T j = 0; j < static_cast<int32_T>(ny); j++) - { - if (T != 0) - while (row_fwrd < nnz_fwrd && nzij_fwrd[row_fwrd+nnz_fwrd]-1 == j) - ir[k++] = (T-1)*ny + nzij_fwrd[row_fwrd++]-1; - while (row_current < nnz_current && nzij_current[row_current+nnz_current]-1 == j) - ir[k++] = T*ny + nzij_current[row_current++]-1; - if (T != periods-1) - while (row_pred < nnz_pred && nzij_pred[row_pred+nnz_pred]-1 == j) - ir[k++] = (T+1)*ny + nzij_pred[row_pred++]-1; - jc[T*ny+j+1] = k; - } - } + for (mwIndex j {0}; j < ny; j++) // Column within the period (i.e. variable) + { + if (T > 0) + for (int32_T idx {g1_sparse_colptr[2*ny+j]-1}; idx < g1_sparse_colptr[2*ny+j+1]-1; idx++) + ir[k++] = (T-1)*ny + g1_sparse_rowval[idx]-1; // Derivatives w.r.t. y_{t+1} in T-1 + for (int32_T idx {g1_sparse_colptr[ny+j]-1}; idx < g1_sparse_colptr[ny+j+1]-1; idx++) + ir[k++] = T*ny + g1_sparse_rowval[idx]-1; // Derivatives w.r.t. y_t in T + if (T < periods-1) + for (int32_T idx {g1_sparse_colptr[j]-1}; idx < g1_sparse_colptr[j+1]-1; idx++) + ir[k++] = (T+1)*ny + g1_sparse_rowval[idx]-1; // Derivatives w.r.t. y_{t-1} in T+1 + jc[T*ny+j+1] = k; + } } - size_t ndynvars = static_cast<size_t>(*std::max_element(lead_lag_incidence, lead_lag_incidence+(maximum_endo_lag+2)*ny)); - if (use_dll) DynamicModelDllCaller::load_dll(basename); @@ -219,55 +197,61 @@ mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) // Allocate (thread-private) model evaluator (which allocates space for temporaries) std::unique_ptr<DynamicModelCaller> m; if (use_dll) - m = std::make_unique<DynamicModelDllCaller>(ntt, nx, ny, ndynvars, exo_path, nb_row_x, params, steady_state, linear, compute_jacobian); + m = std::make_unique<DynamicModelDllCaller>(ntt, ny, nx, params, steady_state, g1_sparse_colptr, linear, compute_jacobian); else - m = std::make_unique<DynamicModelMatlabCaller>(basename, ntt, ndynvars, exo_path_mx, params_mx, steady_state_mx, linear, compute_jacobian); + m = std::make_unique<DynamicModelMatlabCaller>(basename, ny, nx, params_mx, steady_state_mx, g1_sparse_rowval_mx, g1_sparse_colval_mx, g1_sparse_colptr_mx, linear, compute_jacobian); // Main computing loop #pragma omp for for (mwIndex T = 0; T < periods; T++) { // Fill vector of dynamic variables - for (mwIndex j = 0; j < maximum_endo_lag+2; j++) - for (mwIndex i = 0; i < ny; i++) - { - int idx = static_cast<int>(lead_lag_incidence[j+i*(2+maximum_endo_lag)])-1; - if (idx != -1) - { - if (T+j == maximum_endo_lag-1) - m->y(idx) = y0[i]; - else if (T+j == maximum_endo_lag+periods) - m->y(idx) = yT[i]; - else - m->y(idx) = y[i+(T+j-maximum_endo_lag)*ny]; - } - } + if (T > 0 && T < periods-1) + std::copy_n(y+(T-1)*ny, 3*ny, m->y()); + else if (T > 0) // Last simulation period + { + std::copy_n(y+(T-1)*ny, 2*ny, m->y()); + std::copy_n(yT, ny, m->y() + 2*ny); + } + else if (T < periods-1) // First simulation period + { + std::copy_n(y0, ny, m->y()); + std::copy_n(y+T*ny, 2*ny, m->y() + ny); + } + else // Special case: periods=1 (and so T=0) + { + std::copy_n(y0, ny, m->y()); + std::copy_n(y, ny, m->y() + ny); + std::copy_n(yT, ny, m->y() + 2*ny); + } + + // Fill exogenous + for (mwIndex j {0}; j < nx; j++) + m->x()[j] = exo_path[T+maximum_lag + nb_row_x*j]; // Compute the residual and Jacobian, and fill the stacked residual - m->eval(T+maximum_lag, stacked_residual+T*ny); + m->eval(stacked_residual+T*ny); if (compute_jacobian) { // Fill the stacked jacobian - for (mwIndex col = T > maximum_endo_lag ? (T-maximum_endo_lag)*ny : 0; // We can't use std::max() here, because mwIndex is unsigned under MATLAB + for (mwIndex col { T > 1 ? (T-1)*ny : 0 }; // We can't use std::max() here, because mwIndex is unsigned under MATLAB col < std::min(periods*ny, (T+2)*ny); col++) { mwIndex k = jc[col]; while (k < jc[col+1]) { - if (ir[k] < T*ny) + if (ir[k] >= (T+1)*ny) + break; // Nothing to copy for this column + if (ir[k] >= T*ny) { - k++; - continue; + /* Within the current column, this is the first line of + the stacked Jacobian that contains elements from the + (small) Jacobian just computed, so copy the whole + column of the latter to the former. */ + m->copy_jacobian_column(col - (T-1)*ny, stacked_jacobian + k); + break; } - if (ir[k] >= (T+1)*ny) - break; - - mwIndex eq = ir[k]-T*ny; - mwIndex lli_row = col/ny-(T-maximum_endo_lag); // 0, 1 or 2 - mwIndex lli_col = col%ny; - mwIndex dynvar = static_cast<mwIndex>(lead_lag_incidence[lli_row+lli_col*(2+maximum_endo_lag)])-1; - stacked_jacobian[k] = m->jacobian(eq+dynvar*ny); k++; } }