Implements `local_state_space_iteration_fortran`, the Fortran replacement of `local_state_space_iteration_k` (Ref #1802)

- Creates the library `libkordersim` with all the relevant Fortran routines to `folded_to_unfolded_dr` and `local_state_space_iteration_fortran`
   - Implements `folded_to_unfolded_dr`, which converts folded decision rule matrices to their unfolded counterparts
pac-components
NormannR 2021-08-27 13:55:51 +02:00
parent 5a21b11d77
commit cab65dabb5
23 changed files with 957 additions and 2 deletions

View File

@ -0,0 +1,14 @@
mex_PROGRAMS = folded_to_unfolded_dr
folded_to_unfolded_dr_FCFLAGS = $(AM_FCFLAGS) -Warray-temporaries -I../libkordersim
nodist_folded_to_unfolded_dr_SOURCES = \
mexFunction.f08
folded_to_unfolded_dr_LDADD = ../libkordersim/libkordersim.a
BUILT_SOURCES = $(nodist_folded_to_unfolded_dr_SOURCES)
CLEANFILES = $(nodist_folded_to_unfolded_dr_SOURCES)
%.f08: $(top_srcdir)/../../sources/folded_to_unfolded_dr/%.f08
$(LN_S) -f $< $@

29
mex/build/libkordersim.am Normal file
View File

@ -0,0 +1,29 @@
noinst_LIBRARIES = libkordersim.a
nodist_libkordersim_a_SOURCES = \
blas_lapack.F08 \
matlab_mex.F08 \
pascal.f08 \
sort.f08 \
partitions.f08 \
simulation.f08 \
struct.f08
BUILT_SOURCES = $(nodist_libkordersim_a_SOURCES)
CLEANFILES = $(nodist_libkordersim_a_SOURCES)
struct.o: matlab_mex.mod
struct.mod: struct.o
pascal.mod: pascal.o
sort.mod: sort.o
partitions.o: sort.mod pascal.mod
partitions.mod: partitions.o
simulation.o: partitions.mod lapack.mod
simulation.mod: simulation.o
%.f08: $(top_srcdir)/../../sources/libkordersim/%.f08
$(LN_S) -f $< $@

View File

@ -0,0 +1,14 @@
mex_PROGRAMS = local_state_space_iteration_fortran
local_state_space_iteration_fortran_FCFLAGS = $(AM_FCFLAGS) -Warray-temporaries -I../libkordersim
nodist_local_state_space_iteration_fortran_SOURCES = \
mexFunction.f08
local_state_space_iteration_fortran_LDADD = ../libkordersim/libkordersim.a
BUILT_SOURCES = $(nodist_local_state_space_iteration_fortran_SOURCES)
CLEANFILES = $(nodist_local_state_space_iteration_fortran_SOURCES)
%.f08: $(top_srcdir)/../../sources/local_state_space_iteration_fortran/%.f08
$(LN_S) -f $< $@

View File

@ -4,7 +4,7 @@ SUBDIRS = mjdgges kronecker bytecode block_kalman_filter sobol perfect_foresight
# libdynare++ must come before gensylv, k_order_perturbation, dynare_simul_
if ENABLE_MEX_DYNAREPLUSPLUS
SUBDIRS += libdynare++ gensylv libkorder dynare_simul_ k_order_perturbation k_order_welfare local_state_space_iterations
SUBDIRS += libdynare++ gensylv libkorder dynare_simul_ k_order_perturbation k_order_welfare local_state_space_iterations libkordersim folded_to_unfolded_dr local_state_space_iteration_fortran
endif
if ENABLE_MEX_MS_SBVAR

View File

@ -162,6 +162,9 @@ AC_CONFIG_FILES([Makefile
block_kalman_filter/Makefile
sobol/Makefile
local_state_space_iterations/Makefile
libkordersim/Makefile
folded_to_unfolded_dr/Makefile
local_state_space_iteration_fortran/Makefile
perfect_foresight_problem/Makefile
num_procs/Makefile
block_trust_region/Makefile

View File

@ -0,0 +1,2 @@
include ../mex.am
include ../../folded_to_unfolded_dr.am

View File

@ -0,0 +1,2 @@
include ../mex.am
include ../../libkordersim.am

View File

@ -0,0 +1,2 @@
include ../mex.am
include ../../local_state_space_iteration_fortran.am

View File

@ -4,7 +4,7 @@ SUBDIRS = mjdgges kronecker bytecode block_kalman_filter sobol perfect_foresight
# libdynare++ must come before gensylv, k_order_perturbation, dynare_simul_
if ENABLE_MEX_DYNAREPLUSPLUS
SUBDIRS += libdynare++ gensylv libkorder dynare_simul_ k_order_perturbation k_order_welfare local_state_space_iterations
SUBDIRS += libdynare++ gensylv libkorder dynare_simul_ k_order_perturbation k_order_welfare local_state_space_iterations libkordersim folded_to_unfolded_dr local_state_space_iteration_fortran
endif
if ENABLE_MEX_MS_SBVAR

View File

@ -144,6 +144,9 @@ AC_CONFIG_FILES([Makefile
block_kalman_filter/Makefile
sobol/Makefile
local_state_space_iterations/Makefile
libkordersim/Makefile
folded_to_unfolded_dr/Makefile
local_state_space_iteration_fortran/Makefile
perfect_foresight_problem/Makefile
num_procs/Makefile
block_trust_region/Makefile

View File

@ -0,0 +1,3 @@
EXEEXT = .mex
include ../mex.am
include ../../folded_to_unfolded_dr.am

View File

@ -0,0 +1,3 @@
EXEEXT = .mex
include ../mex.am
include ../../libkordersim.am

View File

@ -0,0 +1,3 @@
EXEEXT = .mex
include ../mex.am
include ../../local_state_space_iteration_fortran.am

View File

@ -16,6 +16,9 @@ EXTRA_DIST = \
block_kalman_filter \
sobol \
local_state_space_iterations \
libkordersim \
folded_to_unfolded_dr \
local_state_space_iteration_fortran \
gensylv \
dynare_simul_ \
perfect_foresight_problem \

View File

@ -0,0 +1,101 @@
! Copyright © 2021 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 <https://www.gnu.org/licenses/>.
subroutine mexFunction(nlhs, plhs, nrhs, prhs) bind(c, name='mexFunction')
use iso_fortran_env
use iso_c_binding
use struct
use simulation
use matlab_mex
use partitions
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
type(c_ptr) :: M_mx, options_mx, dr_mx, tmp, g
type(pol), dimension(:), allocatable, target :: fdr
integer :: order, npred, nboth, nstatic, nfwrd, endo_nbr, exo_nbr, nys, nvar
type(pascal_triangle) :: p
type(uf_matching), dimension(:), allocatable :: matching
character(kind=c_char, len=10), dimension(:), allocatable :: fieldnames
integer :: d, m, n
dr_mx = prhs(1)
M_mx = prhs(2)
options_mx = prhs(3)
! Checking the consistence and validity of input arguments
if (nrhs /= 3 .or. nlhs /= 1) then
call mexErrMsgTxt("Must have exactly 3 inputs and 1 output")
end if
if (.not. mxIsStruct(dr_mx)) then
call mexErrMsgTxt("1st argument (dr) should be a struct")
end if
if (.not. mxIsStruct(M_mx)) then
call mexErrMsgTxt("2nd argument (M) should be a struct")
end if
if (.not. mxIsStruct(options_mx)) then
call mexErrMsgTxt("3rd argument (options) should be a struct")
end if
nstatic = get_int_field(M_mx, "nstatic")
npred = get_int_field(M_mx, "npred")
nboth = get_int_field(M_mx, "nboth")
nfwrd = get_int_field(M_mx, "nfwrd")
endo_nbr = nstatic+npred+nboth+nfwrd
exo_nbr = get_int_field(M_mx, "exo_nbr")
order = get_int_field(options_mx, "order")
nys = npred+nboth
nvar = nys+exo_nbr
allocate(fdr(0:order), fieldnames(0:order))
do d = 0, order
write (fieldnames(d), '(a2, i1)') "g_", d
tmp = mxGetField(dr_mx, 1_mwIndex, trim(fieldnames(d)))
if (.not. (c_associated(tmp) .and. mxIsDouble(tmp))) then
call mexErrMsgTxt(trim(fieldnames(d))//" is not allocated in dr")
end if
m = int(mxGetM(tmp))
n = int(mxGetN(tmp))
allocate(fdr(d)%g(m,n))
fdr(d)%g = reshape(mxGetPr(tmp), [m,n])
end do
plhs(1) = mxCreateStructMatrix(1_mwSize, 1_mwSize, fieldnames)
g = mxCreateDoubleMatrix(int(endo_nbr, mwSize), 1_mwSize, mxREAL)
mxGetPr(g) = reshape(fdr(0)%g, [size(fdr(0)%g)])
call mxSetField(plhs(1), 1_mwIndex, "g_0", g)
g = mxCreateDoubleMatrix(int(endo_nbr, mwSize), int(nvar, mwSize), mxREAL)
mxGetPr(g) = reshape(fdr(1)%g, [size(fdr(1)%g)])
call mxSetField(plhs(1), 1_mwIndex, "g_1", g)
if (order > 1) then
! Compute the useful binomial coefficients from Pascal's triangle
p = pascal_triangle(nvar+order-1)
allocate(matching(2:order))
! Pinpointing the corresponding offsets between folded and unfolded tensors
do d=2,order
allocate(matching(d)%folded(nvar**d))
call fill_folded_indices(matching(d)%folded, nvar, d, p)
g = mxCreateDoubleMatrix(int(endo_nbr, mwSize), int(nvar**d, mwSize), mxREAL)
mxGetPr(g) = reshape(fdr(d)%g(:,matching(d)%folded), [size(fdr(d)%g(:,matching(d)%folded))])
call mxSetField(plhs(1), 1_mwIndex, trim(fieldnames(d)), g)
end do
end if
end subroutine mexFunction

View File

@ -0,0 +1,256 @@
! Provides subroutines to manipulate indexes representing elements of
! a partition for a given integer
! i.e. elements p = (α₁,…,αₘ) where each αᵢ ∈ { 0, ..., n-1 }
!
! Copyright © 2021 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 <https://www.gnu.org/licenses/>.
module partitions
use pascal
use sort
implicit none
! index represents the aforementioned (α₁,…,αₘ) objects
type index
integer, dimension(:), allocatable :: ind
end type index
interface index
module procedure :: init_index
end interface index
! a dictionary that matches folded indices with folded offsets
type dict
integer :: pr ! pointer to the last added element in indices and offsets
type(index), dimension(:), allocatable :: indices ! list of folded indices
integer, dimension(:), allocatable :: offsets ! list of the associated offsets
end type dict
interface dict
module procedure :: init_dict
end interface dict
interface operator(/=)
module procedure :: diff_indices
end interface operator(/=)
! A type to contain the correspondence unfolded and folded offsets i.e.
! folded(i) shall contain the folded offset corresponding to the unfolded offset i
type uf_matching
type(integer), dimension(:), allocatable :: folded
end type
contains
! Constructor for the index type
type(index) function init_index(d, ind)
integer, intent(in) :: d
integer, dimension(d), intent(in) :: ind
allocate(init_index%ind(d))
init_index%ind = ind
end function init_index
! Comparison for the index type. Returns true if the two indices are different
type(logical) function diff_indices(i1,i2)
type(index), intent(in) :: i1, i2
if (size(i1%ind) /= size(i2%ind) .or. any(i1%ind /= i2%ind)) then
diff_indices = .true.
else
diff_indices = .false.
end if
end function diff_indices
! Constructor of the dict type
type(dict) function init_dict(n, d, p)
integer, intent(in) :: n, d
type(pascal_triangle), intent(in) :: p
integer :: size
size = get(d, n+d-1, p)
allocate(init_dict%indices(size), init_dict%offsets(size))
init_dict%pr = 0
end function init_dict
! Count the number of coordinates similar to the the first one for a given index
type(integer) function get_prefix_length(idx, d)
integer, intent(in) :: d
type(index), intent(in) :: idx
integer :: i
i = 1
if (d>1) then
do while ((i < d) .and. (idx%ind(i+1) == idx%ind(1)))
i = i+1
end do
end if
get_prefix_length = i
end function get_prefix_length
! Gets the folded index associated with an unfolded index
type(index) function u_index_to_f_index(idx, d)
type(index), intent(in) :: idx
integer, intent(in) :: d
u_index_to_f_index = index(d, idx%ind)
call sort_int(u_index_to_f_index%ind)
end function u_index_to_f_index
! Converts the offset of an unfolded tensor to the associated unfolded tensor index
! Note that the index (α₁,…,αₘ) is such that αᵢ ∈ { 0, ..., n-1 }
! and the offset is such that j ∈ {1, ..., nᵈ}
type(index) function u_offset_to_u_index(j, n, d)
integer, intent(in) :: j, n, d ! offset, number of variables and dimensions respectively
integer :: i, tmp, r
allocate(u_offset_to_u_index%ind(d))
tmp = j-1 ! We substract 1 as j ∈ {1, ..., n} so that tmp ∈ {0, ..., n-1} and our modular operations work
do i=d,1,-1
r = mod(tmp, n)
u_offset_to_u_index%ind(i) = r
tmp = (tmp-r)/n
end do
end function u_offset_to_u_index
! Converts a folded tensor index to the associated folded tensor offset
! See the explanation in dynare++/tl/cc/tensor.cc for the function FTensor::getOffsetRecurse
! Note that the index (α₁,…,αₘ) is such that αᵢ ∈ { 0, ..., n-1 }
! and the offset is such that j ∈ {1, ..., ⎛n+d-1⎞ }
! ⎝ d ⎠
recursive function f_index_to_f_offset(idx, n, d, p) result(j)
type(index), intent(in) :: idx ! folded index
integer, intent(in) :: n, d ! number of variables and dimensions
type(pascal_triangle) :: p ! Pascal's triangle containing the relevant binomial coefficients
integer :: j, prefix
type(index) :: tmp
if (d == 0) then
j = 1
else
prefix = get_prefix_length(idx,d)
tmp = index(d-prefix, idx%ind(prefix+1:) - idx%ind(1))
j = get(d, n+d-1, p) - get(d, n-idx%ind(1)+d-1, p) + f_index_to_f_offset(tmp, n-idx%ind(1), d-prefix, p)
end if
end function f_index_to_f_offset
! Function that searches a value in an array of a given length
type(integer) function find(a, v, l)
integer, intent(in) :: l ! length of the array
type(index), dimension(l), intent(in) :: a ! array of indices
type(index) :: v ! element to be found
integer :: i
if (l == 0) then
find = 0
else
i = 1
do while (i <= l .and. a(i) /= v)
i = i+1
end do
if (i == l+1) then
find = 0
else
find = i
end if
end if
end function find
! Fills the folded offset array:
! folded(i) shall contain the folded offset corresponding to the unfolded offset i
! For each unfolded tensor offset
! (a) compute the associated unfolded index (u_offset_to_u_index)
! (b) compute the associated folded index (u_index_to_f_index)
! (c) has the folded offset already been computed ?
! (i) If yes, get the corresponding offset
! (ii) If no, compute it (f_index_to_f_offset) and store it for reuse and as a result
subroutine fill_folded_indices(folded, n, d, p)
integer, intent(in) :: n, d
integer, dimension(n**d), intent(inout) :: folded
type(pascal_triangle), intent(in) :: p
type(dict) :: c
type(index) :: tmp
integer :: j, found
c = dict(n, d, p)
do j=1,n**d
tmp = u_offset_to_u_index(j,n,d)
tmp = u_index_to_f_index(tmp, d)
found = find(c%indices, tmp, c%pr)
if (found == 0) then
c%pr = c%pr+1
c%indices(c%pr) = tmp
c%offsets(c%pr) = f_index_to_f_offset(tmp,n,d,p)
folded(j) = c%offsets(c%pr)
else
folded(j) = c%offsets(found)
end if
end do
end subroutine fill_folded_indices
end module partitions
! gfortran -o partitions partitions.f08 pascal.f08 sort.f08
! ./partitions
! program test
! use partitions
! use pascal
! implicit none
! type(index) :: uidx, fidx, i1, i2
! integer, dimension(:), allocatable :: folded
! integer :: i, uj, n, d
! type(pascal_triangle) :: p
! ! Unfolded indices and offsets
! ! 0,0,0 1 1,0,0 10 2,0,0 19
! ! 0,0,1 2 1,0,1 11 2,0,1 20
! ! 0,0,2 3 1,0,2 12 2,0,2 21
! ! 0,1,0 4 1,1,0 13 2,1,0 22
! ! 0,1,1 5 1,1,1 14 2,1,1 23
! ! 0,1,2 6 1,1,2 15 2,1,2 24
! ! 0,2,0 7 1,2,0 16 2,2,0 25
! ! 0,2,1 8 1,2,1 17 2,2,1 26
! ! 0,2,2 9 1,2,2 18 2,2,2 27
! ! Folded indices and offsets
! ! 0,0,0 1 1,1,1 7 2,2,2 10
! ! 0,0,1 2 1,1,2 8
! ! 0,0,2 3 1,2,2 9
! ! 0,1,1 4
! ! 0,1,2 5
! ! 0,2,2 6
! n = 3
! d = 3
! uj = 8
! p = pascal_triangle(n+d-1)
! ! u_offset_to_u_index
! uidx = u_offset_to_u_index(uj,n,d)
! print '(3i2)', (uidx%ind(i), i=1,d) ! should display 0 2 1
! ! f_index_to_f_offset
! fidx = u_index_to_f_index(uidx, d)
! print '(i2)', f_index_to_f_offset(fidx, n, d, p) ! should display 5
! ! /=
! i1 = index(5, (/1,2,3,4,5/))
! i2 = index(5, (/1,2,3,4,6/))
! if (i1 /= i2) then
! print *, "Same!"
! else
! print *, "Different!"
! end if
! ! fill_folded_indices
! allocate(folded(n**d))
! call fill_folded_indices(folded,n,d)
! print *, "Matching offsets unfolded -> folded"
! print '(1000i4)', (i, i=1,n**d)
! print '(1000i4)', (folded(i), i=1,n**d)
! end program test

View File

@ -0,0 +1,89 @@
! Provides a subroutine to build Pascal's triangle
!
! Copyright © 2021 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 <https://www.gnu.org/licenses/>.
module pascal
implicit none
type line
integer, dimension(:), allocatable :: coeffs
end type line
type pascal_triangle
integer :: d
type(line), dimension(:), allocatable :: lines
end type pascal_triangle
interface pascal_triangle
module procedure :: init_pascal_triangle
end interface pascal_triangle
contains
! Fills a pascal_triangle structure with the associated coefficients up to a given order
type(pascal_triangle) function init_pascal_triangle(d)
integer, intent(in) :: d
integer :: i, j
init_pascal_triangle%d = d
allocate(init_pascal_triangle%lines(d))
! Initializing the first line
allocate(init_pascal_triangle%lines(1)%coeffs(2))
init_pascal_triangle%lines(1)%coeffs = 1
! Iterating Pascal's triangle formula
if (d > 1) then
do i=2,d
allocate(init_pascal_triangle%lines(i)%coeffs(i+1))
init_pascal_triangle%lines(i)%coeffs(1) = 1
init_pascal_triangle%lines(i)%coeffs(i+1) = 1
do j=2,i
init_pascal_triangle%lines(i)%coeffs(j) = init_pascal_triangle%lines(i-1)%coeffs(j-1) &
+ init_pascal_triangle%lines(i-1)%coeffs(j)
end do
end do
end if
end function init_pascal_triangle
! Returns ⎛n⎞ stored in pascal_triangle p
! ⎝k⎠
type(integer) function get(k,n,p)
integer, intent(in) :: k, n
type(pascal_triangle), intent(in) :: p
get = p%lines(n)%coeffs(k+1)
end function get
end module pascal
! gfortran -o pascal pascal.f08
! ./pascal
! program test
! use pascal
! type(pascal_triangle) :: p
! integer :: d
! read *, d
! p = pascal_triangle(d)
! do n=1,d
! do k=0,n
! if (k < n) then
! write (*,'(i2," ")', advance="no") get(k,n,p)
! else
! write (*,'(i2," ")') get(k,n,p)
! end if
! end do
! end do
! end program test

View File

@ -0,0 +1,127 @@
! Necessary routines and functions to carry out simulations
!
! A first step is to get the associated
! Copyright © 2021 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 <https://www.gnu.org/licenses/>.
module simulation
use iso_fortran_env
use partitions
use lapack
implicit none
! Used to store the folded decision rule tensors
type :: pol
real(real64), allocatable :: g(:,:)
end type pol
! Used to store the different contracted tensors used in the Horner algorithm
! type :: horner
! real(real64), pointer, contiguous :: c(:,:), c_flat(:)
! end type horner
type :: horner
real(real64), allocatable :: c(:,:)
end type horner
contains
! ! With MATMUL
! ! Horner evaluation of the polynomial with coefficients stored in udr at the point dyu
! subroutine eval(h, dyu, udr, ny, nvar, order)
! type(horner), dimension(0:order), intent(inout) :: h
! real(real64), dimension(nvar), intent(in) :: dyu
! type(pol), intent(in) :: udr(0:order)
! integer, intent(in) :: ny, nvar, order
! integer :: d
! if (order == 1) then
! h(1)%c = udr(1)%g
! else
! call contract(h(order-1)%c, udr(order)%g, dyu, ny, nvar, order)
! end if
! do d=order-1,1,-1
! if (d > 1) then
! h(d)%c = h(d)%c + udr(d)%g
! call contract(h(d-1)%c, h(d)%c, dyu, ny, nvar, d)
! else
! h(d)%c = h(d)%c + udr(1)%g
! end if
! end do
! h(0)%c(:,1) = matmul(h(1)%c, dyu) + udr(0)%g(:,1)
! end subroutine eval
! ! Contracts the unfolded tensor t with respect to the vector x and stores the result in c
! subroutine contract(c, t, x, nrows, nvar, d)
! integer, intent(in) :: nrows, nvar, d
! real(real64), dimension(nrows, nvar**d), intent(in) :: t
! real(real64), dimension(nvar), intent(in) :: x
! real(real64), dimension(nrows, nvar**(d-1)), intent(inout) :: c(:,:)
! real(real64), dimension(nrows) :: tmp
! integer :: i
! do i=1,nvar**(d-1)
! tmp = matmul(t(:,(i-1)*nvar+1:i*nvar), x)
! c(:,i) = tmp
! end do
! end subroutine contract
! With DGEMV
! Modifies y such that y := alpha*A*x + beta*y, where alpha, beta are scalars,
! x and y are vectors, A is a m-by-n matrix
subroutine mult_vec(alpha, A, x, beta, y)
real(real64), intent(in) :: alpha, beta, x(:), A(:,:)
real(real64), intent(inout) :: y(:)
call dgemv("N", int(size(A,1), blint), int(size(A,2), blint), alpha, A, int(size(A,1), blint), x, 1_blint, beta, y, 1_blint)
end subroutine mult_vec
! Horner evaluation of the polynomial with coefficients stored in udr at the point dyu
subroutine eval(h, dyu, udr, ny, nvar, order)
type(horner), dimension(0:order), intent(inout) :: h
real(real64), dimension(nvar), intent(in) :: dyu
type(pol), intent(in) :: udr(0:order)
integer, intent(in) :: ny, nvar, order
integer :: d
if (order == 1) then
h(1)%c = udr(1)%g
else
call contract(h(order-1)%c, udr(order)%g, dyu, ny, nvar, order)
end if
do d=order-1,1,-1
if (d > 1) then
h(d)%c = h(d)%c + udr(d)%g
call contract(h(d-1)%c, h(d)%c, dyu, ny, nvar, d)
else
h(d)%c = h(d)%c + udr(1)%g
end if
end do
h(0)%c = udr(0)%g
call mult_vec(1.0_real64, h(1)%c, dyu, 1.0_real64, h(0)%c(:,1))
end subroutine eval
! Contracts the unfolded tensor t with respect to the vector x and stores the result in c
subroutine contract(c, t, x, nrows, nvar, d)
integer, intent(in) :: nrows, nvar, d
real(real64), dimension(nrows, nvar**d), intent(in) :: t
real(real64), dimension(nvar), intent(in) :: x
real(real64), dimension(nrows, nvar**(d-1)), intent(inout) :: c
integer :: i
do i=1,nvar**(d-1)
call mult_vec(1.0_real64, t(:,(i-1)*nvar+1:i*nvar), x, 0.0_real64, c(:,i))
end do
end subroutine contract
end module simulation

View File

@ -0,0 +1,39 @@
! Provides a subroutine to sort integer arrays in ascending order
! As the addressed arrays are small, I use the insertion sort algorithm
!
! Copyright © 2021 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 <https://www.gnu.org/licenses/>.
module sort
implicit none
contains
subroutine sort_int(l)
integer, dimension(:), intent(inout) :: l
integer :: i, j, x
do i=2,size(l)
x = l(i)
j = i
do while (j > 1 .and. l(j-1) > x)
l(j) = l(j-1)
j = j-1
end do
l(j) = x
end do
end subroutine sort_int
end module sort

View File

@ -0,0 +1,36 @@
! Copyright © 2021 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 <https://www.gnu.org/licenses/>.
module struct
use iso_fortran_env
use iso_c_binding
use matlab_mex
implicit none
contains
type(integer) function get_int_field(struct, field)
type(c_ptr), intent(in) :: struct
character(*), intent(in) :: field
type(c_ptr) :: tmp
tmp = mxGetField(struct, 1_mwIndex, field)
if (.not. (c_associated(tmp) .and. mxIsScalar(tmp) .and. mxIsNumeric(tmp))) then
call mexErrMsgTxt("Field "//field//" should be a numeric scalar")
end if
get_int_field = int(mxGetScalar(tmp))
end function get_int_field
end module struct

View File

@ -0,0 +1,173 @@
! Copyright © 2021 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 <https://www.gnu.org/licenses/>.
! input:
! order the order of approximation, needs order+1 derivatives
! nstat
! npred
! nboth
! nforw
! nexog
! ystart starting value (full vector of endogenous)
! shocks matrix of shocks (nexog x number of period)
! vcov covariance matrix of shocks (nexog x nexog)
! seed integer seed
! ysteady full vector of decision rule's steady
! dr structure containing matrices of derivatives (g_0, g_1,…)
! output:
! res simulated results
subroutine mexFunction(nlhs, plhs, nrhs, prhs) bind(c, name='mexFunction')
use iso_fortran_env
use iso_c_binding
use struct
use matlab_mex
use partitions
use simulation
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
type(c_ptr) :: M_mx, options_mx, dr_mx, yhat_mx, epsilon_mx, udr_mx, tmp
type(pol), dimension(:), allocatable, target :: fdr, udr
integer :: order, nstatic, npred, nboth, nfwrd, exo_nbr, endo_nbr, nparticles, nys, nvar, nrestricted
real(real64), dimension(:), allocatable :: order_var, ys, ys_reordered, restrict_var_list, dyu
real(real64), dimension(:,:), allocatable :: yhat, e, ynext, ynext_all
type(pascal_triangle) :: p
type(uf_matching), dimension(:), allocatable :: matching
type(horner), dimension(:), allocatable :: h
integer :: i, d, j, m, n
character(kind=c_char, len=10) :: fieldname
yhat_mx = prhs(1)
epsilon_mx = prhs(2)
dr_mx = prhs(3)
M_mx = prhs(4)
options_mx = prhs(5)
udr_mx = prhs(6)
! Checking the consistence and validity of input arguments
! if (nrhs /= 5 .or. nlhs /= 1) then
! call mexErrMsgTxt("Must have exactly 5 inputs and 1 output")
! end if
if (nrhs /= 6 .or. nlhs /= 1) then
call mexErrMsgTxt("Must have exactly 5 inputs and 1 output")
end if
if (.not. (mxIsDouble(yhat_mx) .and. mxGetM(yhat_mx) >= 1 .and. mxGetN(yhat_mx) >= 1)) then
call mexErrMsgTxt("1st argument (yhat) should be a real vector")
end if
if (.not. (mxIsDouble(epsilon_mx) .and. mxGetM(epsilon_mx) >= 1 .or. mxGetN(epsilon_mx) == 1)) then
call mexErrMsgTxt("2nd argument (epsilon) should be a real vector")
end if
if (.not. mxIsStruct(dr_mx)) then
call mexErrMsgTxt("3rd argument (dr) should be a struct")
end if
if (.not. mxIsStruct(M_mx)) then
call mexErrMsgTxt("4th argument (M) should be a struct")
end if
if (.not. mxIsStruct(options_mx)) then
call mexErrMsgTxt("5th argument (options) should be a struct")
end if
if (.not. mxIsStruct(udr_mx)) then
call mexErrMsgTxt("6th argument (udr) should be a struct")
end if
nstatic = get_int_field(M_mx, "nstatic")
npred = get_int_field(M_mx, "npred")
nboth = get_int_field(M_mx, "nboth")
nfwrd = get_int_field(M_mx, "nfwrd")
endo_nbr = nstatic + npred + nboth + nfwrd
exo_nbr = get_int_field(M_mx, "exo_nbr")
order = get_int_field(options_mx, "order")
nys = npred+nboth
nvar = nys + exo_nbr
associate (order_var_mx => mxGetField(dr_mx, 1_mwIndex, "order_var"))
if (.not. (mxIsDouble(order_var_mx) .and. int(mxGetNumberOfElements(order_var_mx)) == endo_nbr)) then
call mexErrMsgTxt("Field dr.order_var should be a double precision vector with endo_nbr elements")
end if
allocate(order_var(endo_nbr))
order_var = mxGetPr(order_var_mx)
end associate
associate (ys_mx => mxGetField(dr_mx, 1_mwIndex, "ys"))
if (.not. (mxIsDouble(ys_mx) .and. int(mxGetNumberOfElements(ys_mx)) == endo_nbr)) then
call mexErrMsgTxt("Field dr.ys should be a double precision vector with endo_nbr elements")
end if
allocate(ys(endo_nbr), ys_reordered(endo_nbr))
ys = mxGetPr(ys_mx)
! Construct the reordered steady state
do i=1, endo_nbr
ys_reordered(i) = ys(int(order_var(i)))
end do
end associate
associate (restrict_var_list_mx => mxGetField(dr_mx, 1_mwIndex, "restrict_var_list"))
if (.not. (mxIsDouble(restrict_var_list_mx))) then
call mexErrMsgTxt("Field dr.restrict_var_list should be a double precision vector")
end if
nrestricted = size(mxGetPr(restrict_var_list_mx))
allocate(restrict_var_list(nrestricted))
restrict_var_list = mxGetPr(restrict_var_list_mx)
end associate
nparticles = int(mxGetN(yhat_mx));
if (int(mxGetN(epsilon_mx)) /= nparticles) then
call mexErrMsgTxt("epsilon and yhat don't have the same number of columns")
end if
if (.not. (mxIsDouble(yhat_mx) .and. int(mxGetM(yhat_mx)) == npred + nboth)) then
call mexErrMsgTxt("yhat should be a double precision matrix with npred+nboth rows")
end if
if (.not. (mxIsDouble(epsilon_mx) .and. int(mxGetM(epsilon_mx)) == exo_nbr)) then
call mexErrMsgTxt("epsilon should be a double precision matrix with exo_nbr rows")
end if
allocate(yhat(nys, nparticles), e(exo_nbr, nparticles), ynext(nrestricted, nparticles), ynext_all(endo_nbr, nparticles))
yhat = reshape(mxGetPr(yhat_mx), [nys, nparticles])
e = reshape(mxGetPr(epsilon_mx), [exo_nbr, nparticles])
allocate(h(0:order), fdr(0:order), udr(0:order))
do i = 0, order
write (fieldname, '(a2, i1)') "g_", i
tmp = mxGetField(udr_mx, 1_mwIndex, trim(fieldname))
if (.not. (c_associated(tmp) .and. mxIsDouble(tmp))) then
call mexErrMsgTxt(trim(fieldname)//" is not allocated in dr")
end if
m = int(mxGetM(tmp))
n = int(mxGetN(tmp))
allocate(udr(i)%g(m,n), h(i)%c(endo_nbr, nvar**i))
udr(i)%g(1:m,1:n) = reshape(mxGetPr(tmp), [m,n])
end do
! Using the Horner algorithm to evaluate the decision rule at the chosen yhat and epsilon
allocate(dyu(nvar))
do j=1,nparticles
dyu(1:nys) = yhat(:,j)
dyu(nys+1:) = e(:,j)
call eval(h, dyu, udr, endo_nbr, nvar, order)
ynext_all(:,j) = h(0)%c(:,1) + ys_reordered
do i=1,nrestricted
ynext(i,j) = ynext_all(int(restrict_var_list(i)),j)
end do
end do
plhs(1) = mxCreateDoubleMatrix(int(size(restrict_var_list), mwSize), int(nparticles, mwSize), mxREAL)
mxGetPr(plhs(1)) = reshape(ynext, [size(ynext)])
end subroutine mexFunction

View File

@ -250,6 +250,15 @@ module matlab_mat
character(c_char), dimension(*), intent(in) :: fieldname
end function mxGetField_internal
subroutine mxSetField_internal(pm, index, fieldname, pvalue) bind(c, name="mxSetField"//API_VER2)
use iso_c_binding
import :: mwIndex
type(c_ptr), intent(in), value :: pm
type(c_ptr), intent(in), value :: pvalue
integer(mwIndex), intent(in), value :: index
character(c_char), dimension(*), intent(in) :: fieldname
end subroutine mxSetField_internal
integer(c_int) function mxGetNumberOfFields(pm) bind(c, name="mxGetNumberOfFields"//API_VER)
use iso_c_binding
type(c_ptr), intent(in), value :: pm
@ -359,6 +368,14 @@ contains
mxGetField = mxGetField_internal(pm, index-1, fieldname // c_null_char)
end function mxGetField
subroutine mxSetField(pm, index, fieldname, pvalue)
type(c_ptr), intent(in) :: pm
type(c_ptr), intent(in) :: pvalue
integer(mwIndex), intent(in) :: index
character(kind=c_char, len=*), intent(in) :: fieldname
call mxSetField_internal(pm, index-1, fieldname // c_null_char, pvalue)
end subroutine mxSetField
type(c_ptr) function mxGetCell(pm, index)
type(c_ptr), intent(in) :: pm
integer(mwIndex), intent(in) :: index

View File

@ -48,9 +48,15 @@ tStart1 = tic; for i=1:10000, ynext1 = local_state_space_iteration_2(yhat, epsil
tStart2 = tic; for i=1:10000, ynext2 = local_state_space_iteration_k(yhat, epsilon, dr, M_, options_); end, tElapsed2 = toc(tStart2);
tStart3 = tic; [udr] = folded_to_unfolded_dr(dr, M_, options_); for i=1:10000, ynext3 = local_state_space_iteration_fortran(yhat, epsilon, dr, M_, options_, udr); end, tElapsed3 = toc(tStart3);
if max(max(abs(ynext1-ynext2))) > 1e-14
error('Inconsistency between local_state_space_iteration_2 and local_state_space_iteration_k')
end
if max(max(abs(ynext1-ynext3))) > 1e-14
error('Inconsistency between local_state_space_iteration_2 and local_state_space_iteration_fortran')
end
if tElapsed1<tElapsed2
skipline()
@ -61,3 +67,33 @@ else
dprintf('local_state_space_iteration_2 is %5.2f times slower than local_state_space_iteration_k', tElapsed1/tElapsed2)
skipline()
end
if tElapsed1<tElapsed3
skipline()
dprintf('local_state_space_iteration_2 is %5.2f times faster than local_state_space_iteration_fortran', tElapsed3/tElapsed1)
skipline()
else
skipline()
dprintf('local_state_space_iteration_2 is %5.2f times slower than local_state_space_iteration_fortran', tElapsed1/tElapsed3)
skipline()
end
stoch_simul(order=3, periods=200, irf=0, k_order_solver);
tStart31 = tic; for i=1:10000, ynext31 = local_state_space_iteration_k(yhat, epsilon, oo_.dr, M_, options_); end, tElapsed31 = toc(tStart31);
tStart32 = tic; [udr] = folded_to_unfolded_dr(oo_.dr, M_, options_); for i=1:10000, ynext32 = local_state_space_iteration_fortran(yhat, epsilon, oo_.dr, M_, options_, udr); end, tElapsed32 = toc(tStart32);
if max(max(abs(ynext31-ynext32))) > 1e-14
error('Inconsistency between local_state_space_iteration_2 and local_state_space_iteration_fortran')
end
if tElapsed32<tElapsed31
skipline()
dprintf('local_state_space_iteration_fortran is %5.2f times faster than local_state_space_iteration_k', tElapsed2/tElapsed1)
skipline()
else
skipline()
dprintf('local_state_space_iteration_fortran is %5.2f times slower than local_state_space_iteration_k', tElapsed1/tElapsed2)
skipline()
end