diff --git a/mex/build/mjdgges.am b/mex/build/mjdgges.am index 6a238c9c6..d9e05a2d2 100644 --- a/mex/build/mjdgges.am +++ b/mex/build/mjdgges.am @@ -1,9 +1,11 @@ mex_PROGRAMS = mjdgges -nodist_mjdgges_SOURCES = mjdgges.cc +nodist_mjdgges_SOURCES = mjdgges.F08 matlab_mex.F08 blas_lapack.F08 BUILT_SOURCES = $(nodist_mjdgges_SOURCES) CLEANFILES = $(nodist_mjdgges_SOURCES) -%.cc: $(top_srcdir)/../../sources/mjdgges/%.cc +mjdgges.o : matlab_mex.mod lapack.mod + +%.F08: $(top_srcdir)/../../sources/mjdgges/%.F08 $(LN_S) -f $< $@ diff --git a/mex/sources/blas_lapack.F08 b/mex/sources/blas_lapack.F08 index 440e10513..a93aff118 100644 --- a/mex/sources/blas_lapack.F08 +++ b/mex/sources/blas_lapack.F08 @@ -1,4 +1,4 @@ -! Copyright © 2019 Dynare Team +! Copyright © 2019-2020 Dynare Team ! ! This file is part of Dynare. ! @@ -20,8 +20,10 @@ module blas #if defined(MATLAB_MEX_FILE) && __SIZEOF_POINTER__ == 8 integer, parameter :: blint = int64 + integer, parameter :: bllog = 8 ! Logical kind, gfortran-specific #else integer, parameter :: blint = int32 + integer, parameter :: bllog = 4 ! Logical kind, gfortran-specific #endif interface @@ -43,10 +45,29 @@ module lapack subroutine dgesv(n, nrhs, a, lda, ipiv, b, ldb, info) import :: blint, real64 integer(blint), intent(in) :: n, nrhs, lda, ldb - real(real64), dimension(*), intent(inout) :: a - real(real64), dimension(*), intent(inout) :: b + real(real64), dimension(*), intent(inout) :: a, b integer(blint), dimension(*), intent(out) :: ipiv integer(blint), intent(out) :: info end subroutine dgesv end interface + + interface + subroutine dgges(jobvsl, jobvsr, sort, selctg, n, a, lda, b, ldb, sdim, & + alphar, alphai, beta, vsl, ldvsl, vsr, ldvsr, work, lwork, bwork, & + info) + import :: blint, bllog, real64 + character :: jobvsl, jobvsr, sort + interface + logical(bllog) function selctg(alphar, alphai, beta) + import :: bllog, real64 + real(real64), intent(in) :: alphar, alphai, beta + end function selctg + end interface + integer(blint), intent(in) :: n, lda, ldb, ldvsl, ldvsr, lwork + real(real64), dimension(*), intent(inout) :: a, b + real(real64), dimension(*), intent(out) :: alphar, alphai, beta, vsl, vsr, work + logical(bllog), dimension(*), intent(out) :: bwork + integer(blint), intent(out) :: sdim, info + end subroutine dgges + end interface end module lapack diff --git a/mex/sources/defines.F08 b/mex/sources/defines.F08 new file mode 100644 index 000000000..80d607f63 --- /dev/null +++ b/mex/sources/defines.F08 @@ -0,0 +1 @@ +#define MX_HAS_INTERLEAVED_COMPLEX (defined(MATLAB_MEX_FILE) && MATLAB_VERSION >= 0x0904) diff --git a/mex/sources/matlab_mex.F08 b/mex/sources/matlab_mex.F08 index 1fbbf8ba9..7a853f710 100644 --- a/mex/sources/matlab_mex.F08 +++ b/mex/sources/matlab_mex.F08 @@ -52,7 +52,7 @@ # define API_VER2 "" #endif -#define MX_HAS_INTERLEAVED_COMPLEX (defined(MATLAB_MEX_FILE) && MATLAB_VERSION >= 0x0904) +#include "defines.F08" !!! C Matrix API !!! Listed in same order as https://fr.mathworks.com/help/matlab/cc-mx-matrix-library.html diff --git a/mex/sources/mjdgges/mjdgges.F08 b/mex/sources/mjdgges/mjdgges.F08 new file mode 100644 index 000000000..dbc5da9d1 --- /dev/null +++ b/mex/sources/mjdgges/mjdgges.F08 @@ -0,0 +1,167 @@ +! Copyright © 2006-2020 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 "defines.F08" + +module select_fct_mod + use iso_fortran_env + implicit none + + real(real64) :: criterium +contains + logical(bllog) function select_fct(alpha_r, alpha_i, beta) + use blas + + real(real64), intent(in) :: alpha_r, alpha_i, beta + + select_fct = alpha_r**2 + alpha_i**2 < criterium**2 * beta**2 + end function select_fct +end module select_fct_mod + +subroutine mexFunction(nlhs, plhs, nrhs, prhs) bind(c, name='mexFunction') + use iso_fortran_env + use iso_c_binding + use select_fct_mod + use matlab_mex + use lapack + implicit none + + type(c_ptr), dimension(*), intent(in), target :: prhs + type(c_ptr), dimension(*), intent(out) :: plhs + integer(c_int), intent(in), value :: nlhs, nrhs + + integer(c_size_t) :: m1, n1, m2, n2 + real(real64) :: zhreshold + integer(blint) :: n_bl, lwork, info_bl, sdim_bl + real(real64), dimension(:), allocatable :: alpha_r, alpha_i, beta, work + logical(bllog), dimension(:), allocatable :: bwork + real(real64), dimension(:), pointer :: s, t, z, info, sdim, vsl +#if MX_HAS_INTERLEAVED_COMPLEX + complex(real64), dimension(:), pointer :: gev +#else + real(real64), dimension(:), pointer :: gev_r, gev_i +#endif +#if defined(MATLAB_MEX_FILE) && MATLAB_VERSION < 0x0904 + ! Workaround for MKL bug, see below + real(real64), dimension(:), allocatable, target :: vsl_target +#endif + + if (nrhs < 2 .or. nrhs > 4 .or. nlhs /= 7) then + call mexErrMsgTxt("MJDGGES: takes 2, 3 or 4 input arguments and exactly 7 output arguments.") + return + end if + + m1 = mxGetM(prhs(1)) + n1 = mxGetN(prhs(1)) + m2 = mxGetM(prhs(2)) + n2 = mxGetN(prhs(2)) + + if (.not. mxIsDouble(prhs(1)) .or. mxIsComplex(prhs(1)) & + .or. .not. mxIsDouble(prhs(2)) .or. mxIsComplex(prhs(2)) & + .or. m1 /= n1 .or. m2 /= n1 .or. m2 /= n2) then + call mexErrMsgTxt("MJDGGES requires two square real matrices of the same dimension.") + return + end if + + ! Set criterium for stable eigenvalues + if (nrhs >= 3 .and. mxGetM(prhs(3)) > 0) then + associate (crit_arg => mxGetPr(prhs(3))) + criterium = crit_arg(1) + end associate + else + criterium = 1_real64 + 1e-6_real64 + end if + + ! set criterium for 0/0 generalized eigenvalues */ + if (nrhs == 4 .and. mxGetM(prhs(4)) > 0) then + associate (zhresh_arg => mxGetPr(prhs(4))) + zhreshold = zhresh_arg(1) + end associate + else + zhreshold = 1e-6_real64 + end if + + plhs(2) = mxCreateDoubleMatrix(n1, n1, mxREAL) + plhs(3) = mxCreateDoubleMatrix(n1, n1, mxREAL) + plhs(4) = mxCreateDoubleMatrix(n1, n1, mxREAL) + plhs(5) = mxCreateDoubleMatrix(1_mwSize, 1_mwSize, mxREAL) + plhs(6) = mxCreateDoubleMatrix(n1, 1_mwSize, mxCOMPLEX) + plhs(7) = mxCreateDoubleMatrix(1_mwSize, 1_mwSize, mxREAL) + + s => mxGetPr(plhs(2)) + t => mxGetPr(plhs(3)) + sdim => mxGetPr(plhs(5)) +#if MX_HAS_INTERLEAVED_COMPLEX + gev => mxGetComplexDoubles(plhs(6)) +#else + gev_r => mxGetPr(plhs(6)) + gev_i => mxGetPi(plhs(6)) +#endif + info => mxGetPr(plhs(7)) + z => mxGetPr(plhs(4)) +#if defined(MATLAB_MEX_FILE) && MATLAB_VERSION < 0x0904 + ! The left Schur vectors (VSL) are normally not computed, since JOBVSL="N". + ! But old MKL versions (at least the one shipped with MATLAB R2009b/7.9, + ! which is MKL 10.1) are buggy, and passing nullptr for VSL leads to a crash. + ! Hence we need to allocate space for it. + ! The bug seems to be fixed in MATLAB R2010a/7.10 (MKL 10.2), but we use the + ! workaround for all versions < R2018a/9.4, since those share the same + ! ABI and hence the same executables. + allocate(vsl_target(n1*n1)) + vsl => vsl_target +#else + vsl => null() +#endif + + ! Copy input matrices, since we can’t modify them + associate (a => mxGetPr(prhs(1)), b => mxGetPr(prhs(2))) + s = a + t = b + end associate + + n_bl = int(n1, blint) + lwork = 16*n_bl + 16 + allocate(alpha_r(n_bl), alpha_i(n_bl), beta(n_bl), bwork(n_bl), work(lwork)) + + call dgges("N", "V", "S", select_fct, n_bl, s, n_bl, t, n_bl, sdim_bl, & + alpha_r, alpha_i, beta, vsl, n_bl, z, n_bl, work, lwork, bwork, info_bl) + + info = info_bl + sdim = sdim_bl + +#if MX_HAS_INTERLEAVED_COMPLEX + where (alpha_i == 0_real64 .and. beta == 0_real64) + gev = alpha_r / beta + elsewhere + gev = cmplx(alpha_r, alpha_i, real64) / beta + end where +#else + gev_r = alpha_r / beta + where (alpha_i == 0_real64 .and. beta == 0_real64) + gev_i = 0_real64 + elsewhere + gev_i = alpha_i / beta + end where +#endif + + ! If the ratio of some eigenvalue is too close to 0/0, return specific + ! error number (only if no other error) + if (any(abs(alpha_r) <= zhreshold .and. abs(beta) <= zhreshold) .and. info_bl == 0) & + info = 30 + + plhs(1) = mxCreateDoubleScalar(0._real64) +end subroutine mexFunction diff --git a/mex/sources/mjdgges/mjdgges.cc b/mex/sources/mjdgges/mjdgges.cc deleted file mode 100644 index b3873a90a..000000000 --- a/mex/sources/mjdgges/mjdgges.cc +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright © 2006-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 -#include -#include - -#include -#include - -double criterium; - -lapack_int -my_criteria(const double *alpha_r, const double *alpha_i, const double *beta) -{ - return *alpha_r * *alpha_r + *alpha_i * *alpha_i < criterium * criterium * *beta * *beta; -} - -/* MATLAB interface */ -void -mexFunction(int nlhs, mxArray *plhs[], - int nrhs, const mxArray *prhs[]) -{ - /* Check for proper number of arguments */ - if (nrhs < 2 || nrhs > 4 || nlhs == 0 || nlhs > 7) - DYN_MEX_FUNC_ERR_MSG_TXT("MJDGGES: takes 2, 3 or 4 input arguments and between 1 and 7 output arguments."); - - /* Check that A and B are real matrices of the same dimension.*/ - size_t m1 = mxGetM(prhs[0]); - size_t n1 = mxGetN(prhs[0]); - size_t m2 = mxGetM(prhs[1]); - size_t n2 = mxGetN(prhs[1]); - if (!mxIsDouble(prhs[0]) || mxIsComplex(prhs[0]) - || !mxIsDouble(prhs[1]) || mxIsComplex(prhs[1]) - || m1 != n1 || m2 != n1 || m2 != n2) - DYN_MEX_FUNC_ERR_MSG_TXT("MJDGGES requires two square real matrices of the same dimension."); - - /* Create a matrix for the return argument */ - plhs[1] = mxCreateDoubleMatrix(n1, n1, mxREAL); - plhs[2] = mxCreateDoubleMatrix(n1, n1, mxREAL); - plhs[3] = mxCreateDoubleMatrix(n1, n1, mxREAL); - plhs[4] = mxCreateDoubleMatrix(1, 1, mxREAL); - plhs[5] = mxCreateDoubleMatrix(n1, 1, mxCOMPLEX); - plhs[6] = mxCreateDoubleMatrix(1, 1, mxREAL); - - /* Assign pointers to the various parameters */ - double *s = mxGetPr(plhs[1]); - double *t = mxGetPr(plhs[2]); - double *z = mxGetPr(plhs[3]); - double *sdim = mxGetPr(plhs[4]); -#if MX_HAS_INTERLEAVED_COMPLEX - mxComplexDouble *gev = mxGetComplexDoubles(plhs[5]); -#else - double *gev_r = mxGetPr(plhs[5]); - double *gev_i = mxGetPi(plhs[5]); -#endif - double *info = mxGetPr(plhs[6]); - - const double *a = mxGetPr(prhs[0]); - const double *b = mxGetPr(prhs[1]); - - /* set criterium for stable eigenvalues */ - if (nrhs >= 3 && mxGetM(prhs[2]) > 0) - criterium = *mxGetPr(prhs[2]); - else - criterium = 1+1e-6; - - /* set criterium for 0/0 generalized eigenvalues */ - double zhreshold; - if (nrhs == 4 && mxGetM(prhs[3]) > 0) - zhreshold = *mxGetPr(prhs[3]); - else - zhreshold = 1e-6; - - /* keep a and b intact */ - std::copy_n(a, n1*n1, s); - std::copy_n(b, n1*n1, t); - - lapack_int i_n = static_cast(n1); - auto alpha_r = std::make_unique(n1); - auto alpha_i = std::make_unique(n1); - auto beta = std::make_unique(n1); - lapack_int lwork = 16*i_n+16; - auto work = std::make_unique(lwork); - auto bwork = std::make_unique(i_n); - lapack_int i_info, i_sdim; -#if defined(MATLAB_MEX_FILE) && MATLAB_VERSION < 0x0904 - /* The left Schur vectors (VSL) are normally not computed, since JOBVSL="N". - But old MKL versions (at least the one shipped with MATLAB R2009b/7.9, which - is MKL 10.1) are - buggy, and passing nullptr for VSL leads to a crash. Hence we need to - allocate space for it. - The bug seems to be fixed in MATLAB R2010a/7.10 (MKL 10.2), but we use the - workaround for all versions < R2018a/9.4, since those share the same - ABI and hence the same executables. */ - auto vsl = std::make_unique(n1*n1); -#endif - - dgges("N", "V", "S", my_criteria, &i_n, s, &i_n, t, &i_n, &i_sdim, alpha_r.get(), alpha_i.get(), - beta.get(), -#if defined(MATLAB_MEX_FILE) && MATLAB_VERSION < 0x0904 - vsl.get(), -#else - nullptr, -#endif - &i_n, z, &i_n, work.get(), &lwork, bwork.get(), &i_info); - - *sdim = static_cast(i_sdim); - *info = static_cast(i_info); - - for (size_t i = 0; i < n1; i++) - { - if (std::abs(alpha_r[i]) > zhreshold || std::abs(beta[i]) > zhreshold) -#if MX_HAS_INTERLEAVED_COMPLEX - gev[i].real = alpha_r[i] / beta[i]; -#else - gev_r[i] = alpha_r[i] / beta[i]; -#endif - else - { - /* the ratio is too close to 0/0; - returns specific error number only if no other error */ - if (*info == 0) - *info = -30; - } - if (alpha_i[i] == 0.0 && beta[i] == 0.0) -#if MX_HAS_INTERLEAVED_COMPLEX - gev[i].imag = 0.0; -#else - gev_i[i] = 0.0; -#endif - else -#if MX_HAS_INTERLEAVED_COMPLEX - gev[i].imag = alpha_i[i] / beta[i]; -#else - gev_i[i] = alpha_i[i] / beta[i]; -#endif - } - - plhs[0] = mxCreateDoubleScalar(0); -}