! Solve the discrete Lyapunov Equation (X = G·X·Gᵀ + V) using the Doubling Algorithm ! ! Syntax: ! [X, error_flag] = disclyap_fast(G, V, tol, check_flag) ! ! Inputs: ! G [double] (n×n) first input matrix ! V [double] (n×n) second input matrix ! tol [double] scalar, tolerance criterion ! check_flag [boolean] if true: check positive-definiteness (optional) ! max_iter [integer] scalar, maximum number of iterations (optional) ! ! Outputs: ! X [double] solution matrix ! error_flag [boolean] true if solution is found, false otherwise (optional) ! ! If check_flag is true, then the code will check if the resulting X ! is positive definite and generate an error message if it is not. ! ! This is a Fortran translation of a code originally written by Joe Pearlman ! and Alejandro Justiniano. ! Copyright © 2020-2023 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 . subroutine mexFunction(nlhs, plhs, nrhs, prhs) bind(c, name='mexFunction') use iso_fortran_env use ieee_arithmetic use matlab_mex use lapack implicit none (type, external) 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) :: n real(real64) :: tol, max_iter logical :: check_flag real(real64), dimension(:, :), allocatable :: P0, P1, A0, A1, Ptmp real(real64) :: matd integer :: iter integer (blint) :: n_bl real(real64), dimension(:, :), pointer :: X if (nlhs < 1 .or. nlhs > 2 .or. nrhs < 3 .or. nrhs > 5) then call mexErrMsgTxt("disclyap_fast: requires between 3 and 5 input arguments, and 1 or 2 output arguments") end if n = mxGetM(prhs(1)) if (.not. mxIsDouble(prhs(1)) .or. mxIsComplex(prhs(1)) .or. mxIsSparse(prhs(1)) & .or. .not. mxIsDouble(prhs(2)) .or. mxIsComplex(prhs(2)) .or. mxIsSparse(prhs(2)) & .or. mxGetN(prhs(1)) /= n .or. mxGetM(prhs(2)) /= n .or. mxGetN(prhs(2)) /= n) then call mexErrMsgTxt("disclyap_fast: first two arguments should be real dense matrices of the same dimension") end if if (.not. (mxIsScalar(prhs(3)) .and. mxIsNumeric(prhs(3)))) then call mexErrMsgTxt("disclyap_fast: third argument (tol) should be a numeric scalar") end if tol = mxGetScalar(prhs(3)) if (nrhs >= 4) then if (.not. (mxIsLogicalScalar(prhs(4)))) then call mexErrMsgTxt("disclyap_fast: fourth argument (check_flag) should be a logical scalar") end if check_flag = mxGetScalar(prhs(4)) == 1_c_double else check_flag = .false. end if if (nrhs >= 5) then if (.not. (mxIsScalar(prhs(5)) .and. mxIsNumeric(prhs(5)))) then call mexErrMsgTxt("disclyap_fast: fifth argument (max_iter) should be a numeric scalar") end if max_iter = int(mxGetScalar(prhs(5))) else max_iter = 2000 end if ! Allocate and initialize temporary variables allocate(P0(n,n), P1(n,n), A0(n,n), A1(n,n), Ptmp(n,n)) associate (G => mxGetPr(prhs(1)), V => mxGetPr(prhs(2))) P0 = reshape(V, [n, n]) A0 = reshape(G, [n, n]) end associate iter = 1 n_bl = int(n, blint) do ! We don't use matmul() for the time being because -fuse-external-blas does ! not work as expected under gfortran 8 ! Ptmp = A0·P0 call dgemm("N", "N", n_bl, n_bl, n_bl, 1._real64, A0, n_bl, P0, n_bl, 0._real64, Ptmp, n_bl) ! P1 = P0+Ptmp·A0ᵀ P1 = P0 call dgemm("N", "T", n_bl, n_bl, n_bl, 1._real64, Ptmp, n_bl, A0, n_bl, 1._real64, P1, n_bl) ! A1 = A0·A0 call dgemm("N", "N", n_bl, n_bl, n_bl, 1._real64, A0, n_bl, A0, n_bl, 0._real64, A1, n_bl) matd = maxval(abs(P1-P0)) P0 = P1 A0 = A1 iter = iter + 1 if (matd <= tol .or. iter == max_iter) exit end do ! Allocate and set outputs plhs(1) = mxCreateDoubleMatrix(n, n, mxREAL) X(1:n, 1:n) => mxGetPr(plhs(1)) if (nlhs > 1) plhs(2) = mxCreateLogicalScalar(.false._mxLogical) if (iter == max_iter) then X = ieee_value(X, ieee_quiet_nan) if (nlhs > 1) then call mxDestroyArray(plhs(2)) plhs(2) = mxCreateLogicalScalar(.true._mxLogical) end if return end if X = (P0+transpose(P0))/2._real64 ! Check that X is positive definite if (check_flag .and. nlhs > 1) then block real(real64), dimension(n, n) :: X2 integer(blint) :: info ! X2=chol(X) X2 = X call dpotrf("L", n_bl, X2, n_bl, info) if (info /= 0) then call mxDestroyArray(plhs(2)) plhs(2) = mxCreateLogicalScalar(.true._mxLogical) end if end block end if end subroutine mexFunction