diff --git a/doc/manual/source/the-model-file.rst b/doc/manual/source/the-model-file.rst index 183c916a3f3e7c33db25fd9180e83ac8a58cb904..cd36b3ecb6e5ea501ebfb5f441b8b3a3ce6b8cbb 100644 --- a/doc/manual/source/the-model-file.rst +++ b/doc/manual/source/the-model-file.rst @@ -3785,13 +3785,20 @@ speed-up on large models. Use a Newton algorithm with a Generalized Minimal Residual (GMRES) solver at each iteration, applied on the stacked system - of all equations in all periods. + of all equations in all periods. The following options can be + used to control the behaviour of the algorithm: + :opt:`preconditioner <preconditioner = OPTION>`, :opt:`iter_tol + <iter_tol = DOUBLE>`, :opt:`iter_maxit <iter_maxit = INTEGER>`, + :opt:`gmres_restart <gmres_restart = INTEGER>`. ``3`` Use a Newton algorithm with a Stabilized Bi-Conjugate Gradient (BiCGStab) solver at each iteration, applied on the stacked - system of all equations in all periods. + system of all equations in all periods. The following options + can be used to control the behaviour of the algorithm: + :opt:`preconditioner <preconditioner = OPTION>`, :opt:`iter_tol + <iter_tol = DOUBLE>`, :opt:`iter_maxit <iter_maxit = INTEGER>`. ``4`` @@ -3832,6 +3839,109 @@ speed-up on large models. trigger the computation of the solution with a trust region algorithm. + .. option:: preconditioner = OPTION + + When :opt:`stack_solve_algo <stack_solve_algo = INTEGER>` is equal to + ``2`` or ``3``, this option specifies which preconditioner will be used + in combination with the iterative sparse linear solver (either GMRES or + BiCGStab). Possible values for OPTION are: + + ``umfiter`` + + At the first iteration of the nonlinear Newton solver, compute + the full LU decomposition with complete pivoting of the linear + system (and use it to solve that first iteration rather than + using the iterative solver). This LU decomposition is then used + as the preconditioner in further Newton iterations. Inspired + from TROLL’s option with the same name. + + ``iterstack`` + + Compute the LU decomposition with complete pivoting for only a + few simulation periods within the stacked Jacobian (which is a + block tridiagonal matrix). Then repeat that LU decomposition + over the block diagonal to construct a preconditioner for the + linear system with all simulation periods. If the total number + of simulations periods is not a multiple of the number of + periods used for the small LU, then an additional LU is computed + for the remainder. The following options can be used to control + the construction of this preconditioner: + :opt:`iterstack_maxlu <iterstack_maxlu = INTEGER>`, + :opt:`iterstack_nperiods <iterstack_nperiods = INTEGER>`, + :opt:`iterstack_nlu <iterstack_nlu = INTEGER>`, + :opt:`iterstack_relu <iterstack_relu = DOUBLE>`. + Inspired from TROLL’s solver with the same name. + + ``ilu`` + + Use an incomple LU decomposition as the preconditioner, + recomputed at every iteration of the nonlinear Newton solver. + + |br| Default value is ``umfiter``. + + .. option:: iter_tol = DOUBLE + + When :opt:`stack_solve_algo <stack_solve_algo = INTEGER>` is equal to + ``2`` or ``3``, this option controls the relative tolerance of the + iterative linear solver (either GMRES or BiCGStab). It corresponds to + the ``tol`` option of the ``gmres`` and ``bicgstab`` MATLAB/Octave + functions. Note that the perfect foresight solver uses an *absolute* + tolerance for determining convergence, so this option should be used + with care, and the default is meant to suit most situations. + Default: the value of the :opt:``tolf <tolf = DOUBLE>`` option, divided + by 10 times the infinite norm of the right-hand side of the linear system. + + .. option:: iter_maxit = INTEGER + + When :opt:`stack_solve_algo <stack_solve_algo = INTEGER>` is equal to + ``2`` or ``3``, this option controls the maximum number of iterations of + the iterative linear solver (either GMRES or BiCGStab). It corresponds + to the ``maxit`` option of the ``gmres`` and ``bicgstab`` MATLAB/Octave + functions. It should not be confused with the :opt:`maxit <maxit = INTEGER>` + option, which controls the (outer) nonlinear Newton loop, while + ``iter_maxit`` controls the (inner) linear loop. Default: ``500``. + + .. option:: gmres_restart = INTEGER + + When :opt:`stack_solve_algo <stack_solve_algo = INTEGER>` is equal to + ``2``, this option controls the number of iterations before restart of + the GMRES algorithm. It corresponds to the ``restart`` option of the + ``gmres`` MATLAB/Octave function. Default: ``100``. + + .. option:: iterstack_maxlu = INTEGER + + When :opt:`preconditioner <preconditioner = OPTION>` is equal to + ``iterstack``, controls the maximum size of the matrix for which the + small LU will computed. The actual size of the matrix will be determined + by the largest number of periods that, multiplied by the number of + equations, is less than the value of the option. Note that when combined + with block decomposition (see :opt:`block`), blocks for which the + whole stacked system is less than this option will be solved using a + regular LU decomposition instead of the iterative linear solver. + Default: ``20000``. + + .. option:: iterstack_nperiods = INTEGER + + When :opt:`preconditioner <preconditioner = OPTION>` is equal to + ``iterstack``, controls the number of periods used for the small LU. + If nonzero, this option overrides the :opt:`iterstack_maxlu <iterstack_maxlu = INTEGER>` + and :opt:`iterstack_nlu <iterstack_nlu = INTEGER>` options. Default: ``0``. + + .. option:: iterstack_nlu = INTEGER + + When :opt:`preconditioner <preconditioner = OPTION>` is equal to + ``iterstack``, specifies the number of times that the small LU should be + repeated in the large preconditioner. If nonzero, this option overrides + the :opt:`iterstack_maxlu <iterstack_maxlu = INTEGER>` option. Default: + ``0``. + + .. option:: iterstack_relu = DOUBLE + + When :opt:`preconditioner <preconditioner = OPTION>` is equal to + ``iterstack``, controls the relative position of the small LU within the + whole stacked system. Must be a number between ``0`` and ``1``. Default: + ``0.5``. + .. option:: robust_lin_solve Triggers the use of a robust linear solver for the default diff --git a/matlab/default_option_values.m b/matlab/default_option_values.m index 7147d8de24bf223ff92c2b0504e6e7a0dab25800..febf0223e76b6026637a954bebdaac99a823bd1c 100644 --- a/matlab/default_option_values.m +++ b/matlab/default_option_values.m @@ -58,6 +58,9 @@ options_.dr_display_tol=1e-6; options_.dp.maxit = 3000; options_.steady.maxit = 50; options_.steady.non_zero = false; +options_.steady.ilu.type = 'ilutp'; +options_.steady.ilu.droptol = 1e-10; +options_.steady.ilu.udiag = true; options_.simul.maxit = 50; options_.simul.robust_lin_solve = false; @@ -328,6 +331,19 @@ options_.simul.endval_steady = false; options_.simul.first_simulation_period = dates(); options_.simul.last_simulation_period = dates(); +% For stack_solve_algo={2,3} +options_.simul.preconditioner = 'umfiter'; +options_.simul.iter_tol = []; % See perfect-foresight-models/iter_solver_params.m for details +options_.simul.iter_maxit = 500; +options_.simul.gmres_restart = 100; +options_.simul.iterstack_maxlu = 20000; +options_.simul.iterstack_nperiods = 0; +options_.simul.iterstack_nlu = 0; +options_.simul.iterstack_relu = 0.5; +options_.simul.ilu.type = 'ilutp'; +options_.simul.ilu.droptol = 1e-12; +options_.simul.ilu.udiag = true; + options_.simul.homotopy_max_completion_share = 1; options_.simul.homotopy_min_step_size = 1e-3; options_.simul.homotopy_step_size_increase_success_count = 3; diff --git a/matlab/optimization/dynare_solve.m b/matlab/optimization/dynare_solve.m index 3779aae7c091a15a2c0e63895bc4e89d8b377700..ebd305e7728b4f5591b8fd66c2fb911c9d81795d 100644 --- a/matlab/optimization/dynare_solve.m +++ b/matlab/optimization/dynare_solve.m @@ -22,7 +22,7 @@ function [x, errorflag, fvec, fjac, errorcode] = dynare_solve(f, x, maxit, tolf, % -10 -> System of equation ill-behaved at the initial guess (Inf, Nans or complex numbers). % -11 -> Initial guess is a solution of the system of equations. -% Copyright © 2001-2024 Dynare Team +% Copyright © 2001-2025 Dynare Team % % This file is part of Dynare. % @@ -294,7 +294,7 @@ elseif options_.solve_algo==3 end [fvec, fjac] = feval(f, x, varargin{:}); elseif ismember(options_.solve_algo, [6 7 8]) - [x, errorflag, errorcode] = newton_solve(f, x, jacobian_flag, options_.gstep, tolf, tolx, maxit, options_.solve_algo, varargin{:}); + [x, errorflag, errorcode] = newton_solve(f, x, jacobian_flag, options_.gstep, tolf, tolx, maxit, options_.solve_algo, options_.steady.ilu, varargin{:}); [fvec, fjac] = feval(f, x, varargin{:}); elseif options_.solve_algo==10 % LMMCP diff --git a/matlab/optimization/newton_solve.m b/matlab/optimization/newton_solve.m index de1d2e48286ebd95973d632d1d7d3873e7f9a3bb..cdfc953a73e7543cc66952b8ff561498f962d60c 100644 --- a/matlab/optimization/newton_solve.m +++ b/matlab/optimization/newton_solve.m @@ -1,4 +1,4 @@ -function [x, errorflag, errorcode] = newton_solve(func, x, jacobian_flag, gstep, tolf, tolx, maxit, solve_algo, varargin) +function [x, errorflag, errorcode] = newton_solve(func, x, jacobian_flag, gstep, tolf, tolx, maxit, solve_algo, ilu_opts, varargin) % Solves systems of non linear equations of several variables using a Newton solver, with three % variants for the inner linear solver: @@ -17,13 +17,14 @@ function [x, errorflag, errorcode] = newton_solve(func, x, jacobian_flag, gstep, % tolx tolerance for solution variation % maxit maximum number of iterations % solve_algo from options_ +% ilu_opts structure of options for ilu % varargin: list of extra arguments to the function % % OUTPUTS % x: results % errorflag=true: the model can not be solved -% Copyright © 2024 Dynare Team +% Copyright © 2024-2025 Dynare Team % % This file is part of Dynare. % @@ -103,9 +104,8 @@ for it = 1:maxit if solve_algo == 6 p = fjac\fvec; else - ilu_setup.type = 'ilutp'; - ilu_setup.droptol = 1e-10; - [L1, U1] = ilu(fjac, ilu_setup); + % Should be the same options as in solve_one_boundary.m and bytecode/Interpreter.cc (static case) + [L1, U1] = ilu(fjac, ilu_opts); if solve_algo == 7 p = gmres(fjac, fvec, [], [], [], L1, U1); elseif solve_algo == 8 diff --git a/matlab/optimization/solve_one_boundary.m b/matlab/optimization/solve_one_boundary.m index e9177db7d02acb3d508cc5eb9a7ef8d566b17c3d..f73f29b6eaa76dac6b8dbceea2b6ce45225ee50b 100644 --- a/matlab/optimization/solve_one_boundary.m +++ b/matlab/optimization/solve_one_boundary.m @@ -40,7 +40,7 @@ function [y, T, success, max_res, iter] = solve_one_boundary(fh, y, x, params, s % ALGORITHM % Newton with LU or GMRES or BicGstab for dynamic block -% Copyright © 1996-2023 Dynare Team +% Copyright © 1996-2025 Dynare Team % % This file is part of Dynare. % @@ -59,11 +59,8 @@ function [y, T, success, max_res, iter] = solve_one_boundary(fh, y, x, params, s Blck_size=size(y_index_eq,2); correcting_factor=0.01; -ilu_setup.type='crout'; -ilu_setup.droptol=1e-10; max_resa=1e100; lambda = 1; % Length of Newton step -reduced = 0; if is_forward incr = 1; start = y_kmin+1; @@ -141,7 +138,6 @@ for it_=start:incr:finish end elseif lambda>1e-8 lambda=lambda/2; - reduced = 1; if verbose disp(['reducing the path length: lambda=' num2str(lambda,'%f')]) end @@ -192,7 +188,9 @@ for it_=start:incr:finish M_.block_structure.block(Block_Num).g1_sparse_rowval, ... M_.block_structure.block(Block_Num).g1_sparse_colval, ... M_.block_structure.block(Block_Num).g1_sparse_colptr, T(:, it_)); - elseif (is_dynamic && (stack_solve_algo==1 || stack_solve_algo==0 || stack_solve_algo==6)) || (~is_dynamic && options_.solve_algo==6) + elseif (is_dynamic && (ismember(stack_solve_algo, [0 1 6]) || ... + (ismember(stack_solve_algo, [2 3]) && strcmp(options_.simul.preconditioner, 'iterstack')))) ... % Iterstack does not make sense for one boundary problems + || (~is_dynamic && options_.solve_algo==6) if verbose && ~is_dynamic disp('steady: Sparse LU ') end @@ -203,64 +201,43 @@ for it_=start:incr:finish else y(y_index_eq) = ya; end - elseif (stack_solve_algo==2 && is_dynamic) || (options_.solve_algo==7 && ~is_dynamic) - flag1=1; - if verbose && ~is_dynamic - disp('steady: GMRES ') + elseif is_dynamic && ismember(stack_solve_algo, [2 3]) + if strcmp(options_.simul.preconditioner, 'umfiter') && iter == 0 + [L, U, P, Q] = lu(g1); + zc = L\(P*r); + zb = U\zc; + elseif strcmp(options_.simul.preconditioner, 'ilu') + [L, U, P] = ilu(g1, options_.simul.ilu); + Q = speye(size(g1)); end - while flag1>0 - [L1, U1]=ilu(g1,ilu_setup); - [dx,flag1] = gmres(g1,-r,Blck_size,1e-6,Blck_size,L1,U1); - if flag1>0 || reduced - if verbose - if flag1==1 - disp(['Error in simul: No convergence inside GMRES after ' num2str(iter,'%6d') ' iterations, in block' num2str(Block_Num,'%3d')]) - elseif(flag1==2) - disp(['Error in simul: Preconditioner is ill-conditioned, in block' num2str(Block_Num,'%3d')]) - elseif(flag1==3) - disp(['Error in simul: GMRES stagnated (Two consecutive iterates were the same.), in block' num2str(Block_Num,'%3d')]) - end - end - ilu_setup.droptol = ilu_setup.droptol/10; - reduced = 0; + if ~(strcmp(options_.simul.preconditioner, 'umfiter') && iter == 0) + [iter_tol, iter_maxit, gmres_restart] = iter_solver_params(options_, g1, r); + if stack_solve_algo==2 + [zb, flag] = gmres(P*g1*Q, P*r, gmres_restart, iter_tol, iter_maxit, L, U); else - ya = ya + lambda*dx; - if is_dynamic - y(y_index_eq, it_) = ya; - else - y(y_index_eq) = ya'; - end + [zb, flag] = bicgstab(P*g1*Q, P*r, iter_tol, iter_maxit, L, U); end + iter_solver_error_flag(flag) end - elseif (stack_solve_algo==3 && is_dynamic) || (options_.solve_algo==8 && ~is_dynamic) - flag1=1; - if verbose && ~is_dynamic - disp('steady: BiCGStab') - end - while flag1>0 - [L1, U1]=ilu(g1,ilu_setup); - [dx,flag1] = bicgstab(g1,-r,1e-6,Blck_size,L1,U1); - if flag1>0 || reduced - if verbose - if(flag1==1) - disp(['Error in simul: No convergence inside BiCGStab after ' num2str(iter,'%6d') ' iterations, in block' num2str(Block_Num,'%3d')]) - elseif(flag1==2) - disp(['Error in simul: Preconditioner is ill-conditioned, in block' num2str(Block_Num,'%3d')]) - elseif(flag1==3) - disp(['Error in simul: BiCGStab stagnated (Two consecutive iterates were the same.), in block' num2str(Block_Num,'%3d')]) - end - end - ilu_setup.droptol = ilu_setup.droptol/10; - reduced = 0; + ya = ya - lambda*Q*zb; + y(y_index_eq, it_) = ya; + elseif ~is_dynamic && ismember(options_.solve_algo, [7 8]) + if verbose + if options_.solve_algo == 7 + disp('steady: GMRES ') else - ya = ya + lambda*dx; - if is_dynamic - y(y_index_eq, it_) = ya; - else - y(y_index_eq) = ya'; - end + disp('steady: BiCGStab') end end + %% Should be the same options as in newton_solve.m and bytecode/Interpreter.cc (static case) + [L, U] = ilu(g1, options_.steady.ilu); + if options_.solve_algo == 7 + zb = gmres(g1, r, [], [], [], L, U); + else + zb = bicgstab(g1, r, [], [], L, U); + end + ya = ya - lambda*zb; + y(y_index_eq) = ya; else if is_dynamic error(['options_.stack_solve_algo = ' num2str(stack_solve_algo) ' not implemented']) diff --git a/matlab/perfect-foresight-models/iter_solver_error_flag.m b/matlab/perfect-foresight-models/iter_solver_error_flag.m new file mode 100644 index 0000000000000000000000000000000000000000..f7e61dbb791987e030238784f8fe05c6bbac381a --- /dev/null +++ b/matlab/perfect-foresight-models/iter_solver_error_flag.m @@ -0,0 +1,30 @@ +function iter_solver_error_flag(flag) +% Interprets error flag from gmres or bicgstab functions + +% Copyright © 2025 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/>. + +switch flag + case 1 + error('Maximum number of iterations exceeded in GMRES/BiCGStab. You may want to increase the iter_maxit option or the gmres_restart option (if using stack_solve_algo=2 for the latter), or decrease iter_tol.') + case 2 + error('The preconditioner matrix is ill conditioned in GMRES/BiCGStab. You should try another preconditioner.') + case 3 + error('No progress between two successive iterations in GMRES/BiCGStab. You may want to increase the iter_tol option.') + case 4 + error('One of the scalar quantities calculated by GMRES/BiCGStab became too small or too large. Try another preconditioner or algorithm.') +end diff --git a/matlab/perfect-foresight-models/iter_solver_params.m b/matlab/perfect-foresight-models/iter_solver_params.m new file mode 100644 index 0000000000000000000000000000000000000000..8f1dc94c480a53b9d11142a4ef341218899fb245 --- /dev/null +++ b/matlab/perfect-foresight-models/iter_solver_params.m @@ -0,0 +1,33 @@ +function [iter_tol, iter_maxit, gmres_restart] = iter_solver_params(options_, A, b) +% Computes tolerance used for gmres or bicgstab functions in perfect foresight solver. +% b is the RHS of the linear equation to solve. Also ensures that maxit and restart +% are within acceptable range. +% +% The tolerance passed to gmres and bicgstab is a relative one (‖Ax−b‖/‖b‖). +% However, we test the convergence of algorithms via options_.dynatol.f, which is an +% absolute error (‖Ax−b‖). Hence the need to rescale by the norm of the RHS (‖b‖). + +% Copyright © 2025 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/>. + +iter_tol = options_.simul.iter_tol; +if isempty(iter_tol) + iter_tol = options_.dynatol.f / max(abs(b)) / 10; +end + +iter_maxit = min(options_.simul.iter_maxit, size(A, 1)); +gmres_restart = min(options_.simul.gmres_restart, size(A, 1)); diff --git a/matlab/perfect-foresight-models/iterstack_preconditioner.m b/matlab/perfect-foresight-models/iterstack_preconditioner.m new file mode 100644 index 0000000000000000000000000000000000000000..8c69e9de476dcf53b58581bb323208934b792f96 --- /dev/null +++ b/matlab/perfect-foresight-models/iterstack_preconditioner.m @@ -0,0 +1,66 @@ +function [L, U, P, Q] = iterstack_preconditioner(A, options_) +% Copyright © 2025 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/>. + +ny = size(A, 1) / options_.periods; + +if options_.simul.iterstack_nperiods > 0 + if options_.simul.iterstack_nperiods > options_.periods + error('iterstack preconditionner: iterstack_nperiods option is greater than the periods options') + end + lusize = ny * options_.simul.iterstack_nperiods; +elseif options_.simul.iterstack_nlu ~= 0 + if options_.simul.iterstack_nlu < 0 % To avoid misleading TROLL users + error('iterstack preconditionner: negative value of iterstack_nlu option is not supported') + end + lusize = floor(floor(size(A, 1) / options_.simul.iterstack_nlu) / ny) * ny; + if lusize == 0 + error('iterstack preconditionner: iterstack_nlu option is too large') + end +else + if size(A, 1) < options_.simul.iterstack_maxlu + error('iterstack preconditionner: too small problem; either decrease iterstack_maxlu option, or use iterstack_nperiods or iterstack_nlu options') + end + if ny > options_.simul.iterstack_maxlu + error('iterstack preconditionner: too large problem; either increase iterstack_maxlu option, or use iterstack_nperiods or iterstack_nlu options') + end + lusize = floor(options_.simul.iterstack_maxlu / ny) * ny; +end + +if options_.simul.iterstack_relu < 0 || options_.simul.iterstack_relu > 1 + error('iterstack preconditionner: iterstack_relu option must be between 0 and 1') +end + +luidx = floor((floor(size(A, 1) / lusize) - 1) * options_.simul.iterstack_relu) * lusize + (1:lusize); +[L1, U1, P1, Q1] = lu(A(luidx,luidx)); +eyek = speye(floor(size(A, 1) / lusize)); +L = kron(eyek, L1); +U = kron(eyek, U1); +P = kron(eyek, P1); +Q = kron(eyek, Q1); +r = rem(size(A, 1), lusize); +if r > 0 % Compute additional smaller LU for remainder, if any + [L2, U2, P2, Q2] = lu(A(end-r+1:end,end-r+1:end)); + L = blkdiag(L, L2); + U = blkdiag(U, U2); + P = blkdiag(P, P2); + Q = blkdiag(Q, Q2); +end + +if options_.debug + fprintf('iterstack preconditionner: main LU size = %d, remainder LU size = %d\n', lusize, r) +end diff --git a/matlab/perfect-foresight-models/sim1.m b/matlab/perfect-foresight-models/sim1.m index 974cda2d5ccdabf650ba0a9384e0b187b6383db6..bc3c70131278c381b1d9ac606e4f35cf2ab7fe5f 100644 --- a/matlab/perfect-foresight-models/sim1.m +++ b/matlab/perfect-foresight-models/sim1.m @@ -82,6 +82,7 @@ end h1 = clock; iter = 1; converged = false; +umfiter_precond = []; while ~(converged || iter > options_.simul.maxit) h2 = clock; @@ -148,7 +149,8 @@ while ~(converged || iter > options_.simul.maxit) if options_.simul.robust_lin_solve dy = -lin_solve_robust(A, res, verbose, options_); else - dy = -lin_solve(A, res, verbose, options_); + [mdy, umfiter_precond] = lin_solve(A, res, verbose, options_, umfiter_precond); + dy = -mdy; end if any(isnan(dy)) || any(isinf(dy)) if verbose @@ -217,7 +219,8 @@ if verbose skipline(); end -function x = lin_solve(A, b, verbose, options_) +function [x, umfiter_precond] = lin_solve(A, b, verbose, options_, umfiter_precond) + if norm(b) < sqrt(eps) % then x = 0 is a solution x = 0; return @@ -226,16 +229,39 @@ end if options_.stack_solve_algo == 0 x = A\b; else - ilu_setup.type = 'ilutp'; - ilu_setup.droptol = 1e-10; - [L1, U1] = ilu(A, ilu_setup); - if options_.stack_solve_algo == 2 - x = gmres(A, b, [], [], [], L1, U1); - elseif options_.stack_solve_algo == 3 - x = bicgstab(A, b, [], [], L1, U1); + if strcmp(options_.simul.preconditioner, 'umfiter') + if isempty(umfiter_precond) + [L, U, P, Q] = lu(A); + else + L = umfiter_precond.L; + U = umfiter_precond.U; + P = umfiter_precond.P; + Q = umfiter_precond.Q; + end + elseif strcmp(options_.simul.preconditioner, 'iterstack') + [L, U, P, Q] = iterstack_preconditioner(A, options_); + elseif strcmp(options_.simul.preconditioner, 'ilu') + [L, U, P] = ilu(A, options_.simul.ilu); + Q = speye(size(A)); + end + + if strcmp(options_.simul.preconditioner, 'umfiter') && isempty(umfiter_precond) + z = L\(P*b); + y = U\z; + umfiter_precond = struct('L', L, 'U', U, 'P', P, 'Q', Q); else - error('sim1: invalid value for options_.stack_solve_algo') + [iter_tol, iter_maxit, gmres_restart] = iter_solver_params(options_, A, b); + if options_.stack_solve_algo == 2 + [y, flag] = gmres(P*A*Q, P*b, gmres_restart, iter_tol, iter_maxit, L, U); + elseif options_.stack_solve_algo == 3 + [y, flag] = bicgstab(P*A*Q, P*b, iter_tol, iter_maxit, L, U); + else + error('sim1: invalid value for options_.stack_solve_algo') + end + iter_solver_error_flag(flag) end + + x = Q*y; end x(~isfinite(x)) = 0; relres = norm(b - A*x) / norm(b); diff --git a/matlab/perfect-foresight-models/solve_block_decomposed_problem.m b/matlab/perfect-foresight-models/solve_block_decomposed_problem.m index 14db6dee693ee24108b535248d5c5b5147395361..cbfd37b82a3fef5395f3a021cb2e416270108c80 100644 --- a/matlab/perfect-foresight-models/solve_block_decomposed_problem.m +++ b/matlab/perfect-foresight-models/solve_block_decomposed_problem.m @@ -14,7 +14,7 @@ function [y, success, maxerror, per_block_status] = solve_block_decomposed_probl % maxerror [double] ∞-norm of the residual % per_block_status [struct] vector structure with per-block information about convergence -% Copyright © 2020-2024 Dynare Team +% Copyright © 2020-2025 Dynare Team % % This file is part of Dynare. % @@ -40,9 +40,9 @@ switch options_.stack_solve_algo case {1,6} mthd='LBJ with LU solver'; case 2 - mthd='GMRES on stacked system'; + mthd=sprintf('GMRES on stacked system with ''%s'' preconditioner', options_.simul.preconditioner); case 3 - mthd='BiCGStab on stacked system'; + mthd=sprintf('BiCGStab on stacked system with ''%s'' preconditioner', options_.simul.preconditioner); case 4 mthd='Sparse LU solver with optimal path length on stacked system'; case 7 diff --git a/matlab/perfect-foresight-models/solve_two_boundaries_stacked.m b/matlab/perfect-foresight-models/solve_two_boundaries_stacked.m index e3934e2579cefe5092d4b7f6f0bf59559cf56302..2c09bb1e4644b9726a2bbb7ce5cc616ec3b6cae6 100644 --- a/matlab/perfect-foresight-models/solve_two_boundaries_stacked.m +++ b/matlab/perfect-foresight-models/solve_two_boundaries_stacked.m @@ -25,7 +25,7 @@ function [y, T, success, max_res, iter] = solve_two_boundaries_stacked(fh, y, x, % ALGORITHM % Newton with LU or GMRES or BiCGStab -% Copyright © 1996-2024 Dynare Team +% Copyright © 1996-2025 Dynare Team % % This file is part of Dynare. % @@ -57,11 +57,8 @@ verbose = options_.verbosity; cvg=false; iter=0; correcting_factor=0.01; -ilu_setup.droptol=1e-10; -ilu_setup.type = 'ilutp'; max_resa=1e100; lambda = 1; % Length of Newton step (unused for stack_solve_algo=4) -reduced = 0; while ~(cvg || iter > options_.simul.maxit) r = NaN(Blck_size, periods); g1a = spalloc(Blck_size*periods, Blck_size*periods, M_.block_structure.block(Block_Num).NNZDerivatives*periods); @@ -81,7 +78,6 @@ while ~(cvg || iter > options_.simul.maxit) g1a((it_-y_kmin-1)*Blck_size+(1:Blck_size), (it_-y_kmin-2)*Blck_size+(1:3*Blck_size)) = g1; end end - preconditioner = 2; ya = reshape(y(y_index, y_kmin+(1:periods)), 1, periods*Blck_size)'; ra = reshape(r, periods*Blck_size, 1); b=-ra+g1a*ya; @@ -98,7 +94,7 @@ while ~(cvg || iter > options_.simul.maxit) end if ~cvg if iter>0 - if ~isreal(max_res) || isnan(max_res) || (max_resa<max_res && iter>1) + if ~isreal(max_res) || isnan(max_res) %|| (max_resa<max_res && iter>1) if verbose && ~isreal(max_res) disp(['Variable ' M_.endo_names{max_indx} ' (' int2str(max_indx) ') returns an undefined value']); end @@ -128,7 +124,6 @@ while ~(cvg || iter > options_.simul.maxit) end elseif lambda>1e-8 && stack_solve_algo ~= 4 lambda=lambda/2; - reduced = 1; if verbose disp(['reducing the path length: lambda=' num2str(lambda,'%f')]); end @@ -155,102 +150,34 @@ while ~(cvg || iter > options_.simul.maxit) g1aa=g1a; ba=b; max_resa=max_res; - if stack_solve_algo==0 + if stack_solve_algo==0 || (ismember(stack_solve_algo, [2 3]) && strcmp(options_.simul.preconditioner, 'iterstack') ... + && options_.simul.iterstack_nperiods == 0 && options_.simul.iterstack_nlu == 0 && size(g1a, 1) < options_.simul.iterstack_maxlu) % Fallback to LU if block too small for iterstack dx = g1a\b- ya; ya = ya + lambda*dx; y(y_index, y_kmin+(1:periods))=reshape(ya',length(y_index),periods); - elseif stack_solve_algo==2 - flag1=1; - while flag1>0 - if preconditioner==2 - [L1, U1]=ilu(g1a,ilu_setup); - elseif preconditioner==3 - Size = Blck_size; - gss1 = g1a(Size + 1: 2*Size,Size + 1: 2*Size) + g1a(Size + 1: 2*Size,2*Size+1: 3*Size); - [L1, U1]=lu(gss1); - L(1:Size,1:Size) = L1; - U(1:Size,1:Size) = U1; - gss2 = g1a(Size + 1: 2*Size,1: Size) + g1a(Size + 1: 2*Size,Size+1: 2*Size) + g1a(Size + 1: 2*Size,2*Size+1: 3*Size); - [L2, U2]=lu(gss2); - L(Size+1:(periods-1)*Size,Size+1:(periods-1)*Size) = kron(eye(periods-2), L2); - U(Size+1:(periods-1)*Size,Size+1:(periods-1)*Size) = kron(eye(periods-2), U2); - gss2 = g1a(Size + 1: 2*Size,1: Size) + g1a(Size + 1: 2*Size,Size+1: 2*Size); - [L3, U3]=lu(gss2); - L((periods-1)*Size+1:periods*Size,(periods-1)*Size+1:periods*Size) = L3; - U((periods-1)*Size+1:periods*Size,(periods-1)*Size+1:periods*Size) = U3; - L1 = L; - U1 = U; - elseif preconditioner==4 - Size = Blck_size; - gss1 = g1a(1: 3*Size, 1: 3*Size); - [L, U] = lu(gss1); - L1 = kron(eye(ceil(periods/3)),L); - U1 = kron(eye(ceil(periods/3)),U); - L1 = L1(1:periods * Size, 1:periods * Size); - U1 = U1(1:periods * Size, 1:periods * Size); - end - [za,flag1] = gmres(g1a,b,Blck_size,1e-6,Blck_size*periods,L1,U1); - if (flag1>0 || reduced) - if verbose - if flag1==1 - disp(['Error in simul: No convergence inside GMRES after ' num2str(periods*10,'%6d') ' iterations, in block ' num2str(Blck_size,'%3d')]); - elseif flag1==2 - disp(['Error in simul: Preconditioner is ill-conditioned, in block ' num2str(Blck_size,'%3d')]); - elseif flag1==3 - disp(['Error in simul: GMRES stagnated (Two consecutive iterates were the same.), in block ' num2str(Blck_size,'%3d')]); - end - end - ilu_setup.droptol = ilu_setup.droptol/10; - reduced = 0; - else - dx = za - ya; - ya = ya + lambda*dx; - y(y_index, y_kmin+(1:periods))=reshape(ya',length(y_index),periods); - end + elseif ismember(stack_solve_algo, [2, 3]) + if strcmp(options_.simul.preconditioner, 'umfiter') && iter == 0 + [L, U, P, Q] = lu(g1a); + zc = L\(P*b); + zb = U\zc; + elseif strcmp(options_.simul.preconditioner, 'iterstack') + [L, U, P, Q] = iterstack_preconditioner(g1a, options_); + elseif strcmp(options_.simul.preconditioner, 'ilu') + [L, U, P] = ilu(g1a, options_.simul.ilu); + Q = speye(size(g1a)); end - elseif stack_solve_algo==3 - flag1=1; - while flag1>0 - if preconditioner==2 - [L1, U1]=ilu(g1a,ilu_setup); - [za,flag1] = bicgstab(g1a,b,1e-7,Blck_size*periods,L1,U1); - elseif preconditioner==3 - Size = Blck_size; - gss0 = g1a(Size + 1: 2*Size,1: Size) + g1a(Size + 1: 2*Size,Size+1: 2*Size) + g1a(Size + 1: 2*Size,2*Size+1: 3*Size); - [L1, U1]=lu(gss0); - P1 = eye(size(gss0)); - Q1 = eye(size(gss0)); - L = kron(eye(periods),L1); - U = kron(eye(periods),U1); - P = kron(eye(periods),P1); - Q = kron(eye(periods),Q1); - [za,flag1] = bicgstab1(g1a,b,1e-7,Blck_size*periods,L,U, P, Q); + if ~(strcmp(options_.simul.preconditioner, 'umfiter') && iter == 0) + [iter_tol, iter_maxit, gmres_restart] = iter_solver_params(options_, g1a, b); + if stack_solve_algo == 2 + [zb, flag] = gmres(P*g1a*Q, P*b, gmres_restart, iter_tol, iter_maxit, L, U); else - Size = Blck_size; - gss0 = g1a(Size + 1: 2*Size,1: Size) + g1a(Size + 1: 2*Size,Size+1: 2*Size) + g1a(Size + 1: 2*Size,2*Size+1: 3*Size); - [L1, U1]=lu(gss0); - L1 = kron(eye(periods),L1); - U1 = kron(eye(periods),U1); - [za,flag1] = bicgstab(g1a,b,1e-7,Blck_size*periods,L1,U1); - end - if flag1>0 || reduced - if verbose - if flag1==1 - disp(['Error in simul: No convergence inside BICGSTAB after ' num2str(periods*10,'%6d') ' iterations, in block ' num2str(Blck_size,'%3d')]); - elseif flag1==2 - disp(['Error in simul: Preconditioner is ill-conditioned, in block ' num2str(Blck_size,'%3d')]); - elseif flag1==3 - disp(['Error in simul: GMRES stagnated (Two consecutive iterates were the same.), in block ' num2str(Blck_size,'%3d')]); - end - end - ilu_setup.droptol = ilu_setup.droptol/10; - reduced = 0; - else - dx = za - ya; - ya = ya + lambda*dx; - y(y_index, y_kmin+(1:periods))=reshape(ya',length(y_index),periods); + [zb, flag] = bicgstab(P*g1a*Q, P*b, iter_tol, iter_maxit, L, U); end + iter_solver_error_flag(flag) end + dx = Q*zb - ya; + ya = ya + lambda*dx; + y(y_index, y_kmin+(1:periods))=reshape(ya',length(y_index),periods); elseif stack_solve_algo==4 stpmx = 100 ; stpmax = stpmx*max([sqrt(ya'*ya);size(y_index,2)]); diff --git a/mex/sources/bytecode/Interpreter.cc b/mex/sources/bytecode/Interpreter.cc index c7d318d94c3fdb7ef911a75c3ae1feff9f1a68ed..1b361f0ffcb52e6d90d9e69a0a9dcf83823a8217 100644 --- a/mex/sources/bytecode/Interpreter.cc +++ b/mex/sources/bytecode/Interpreter.cc @@ -30,6 +30,54 @@ #include "Interpreter.hh" +void +iter_solver_opts_t::set_static_values(const mxArray* options_) +{ + // Should be the same options as in newton_solve.m and solve_one_boundary.m (static case) + iter_tol = mxCreateDoubleMatrix(0, 0, mxREAL); + iter_maxit = mxCreateDoubleMatrix(0, 0, mxREAL); + gmres_restart = mxCreateDoubleMatrix(0, 0, mxREAL); + + int field {mxGetFieldNumber(options_, "steady")}; + if (field < 0) + mexErrMsgTxt("steady is not a field of options_"); + mxArray* steady {mxGetFieldByNumber(options_, 0, field)}; + + field = mxGetFieldNumber(steady, "ilu"); + if (field < 0) + mexErrMsgTxt("ilu is not a field of options_.steady"); + ilu = mxGetFieldByNumber(steady, 0, field); +} + +void +iter_solver_opts_t::set_dynamic_values(const mxArray* options_) +{ + int field {mxGetFieldNumber(options_, "simul")}; + if (field < 0) + mexErrMsgTxt("simul is not a field of options_"); + mxArray* simul {mxGetFieldByNumber(options_, 0, field)}; + + field = mxGetFieldNumber(simul, "iter_tol"); + if (field < 0) + mexErrMsgTxt("iter_tol is not a field of options_.simul"); + iter_tol = mxGetFieldByNumber(simul, 0, field); + + field = mxGetFieldNumber(simul, "iter_maxit"); + if (field < 0) + mexErrMsgTxt("iter_maxit is not a field of options_.simul"); + iter_maxit = mxGetFieldByNumber(simul, 0, field); + + field = mxGetFieldNumber(simul, "gmres_restart"); + if (field < 0) + mexErrMsgTxt("gmres_restart is not a field of options_.simul"); + gmres_restart = mxGetFieldByNumber(simul, 0, field); + + field = mxGetFieldNumber(simul, "ilu"); + if (field < 0) + mexErrMsgTxt("ilu is not a field of options_.simul"); + ilu = mxGetFieldByNumber(simul, 0, field); +} + Interpreter::Interpreter(Evaluate& evaluator_arg, double* params_arg, double* y_arg, double* ya_arg, double* x_arg, double* steady_y_arg, double* direction_arg, int y_size_arg, int nb_row_x_arg, int periods_arg, int y_kmin_arg, int y_kmax_arg, @@ -38,11 +86,13 @@ Interpreter::Interpreter(Evaluate& evaluator_arg, double* params_arg, double* y_ int solve_algo_arg, bool print_arg, const mxArray* GlobalTemporaryTerms_arg, bool steady_state_arg, bool block_decomposed_arg, int col_x_arg, int col_y_arg, - const BasicSymbolTable& symbol_table_arg, int verbosity_arg) : + const BasicSymbolTable& symbol_table_arg, int verbosity_arg, + iter_solver_opts_t iter_solver_opts_arg) : symbol_table {symbol_table_arg}, steady_state {steady_state_arg}, block_decomposed {block_decomposed_arg}, evaluator {evaluator_arg}, + iter_solver_opts {iter_solver_opts_arg}, minimal_solving_periods {minimal_solving_periods_arg}, y_size {y_size_arg}, y_kmin {y_kmin_arg}, @@ -60,7 +110,6 @@ Interpreter::Interpreter(Evaluate& evaluator_arg, double* params_arg, double* y_ start_compare = 0; restart = 0; IM_i.clear(); - lu_inc_tol = 1e-10; Symbolic = nullptr; Numeric = nullptr; params = params_arg; @@ -2260,65 +2309,6 @@ Interpreter::simple_bksub() } } -mxArray* -Interpreter::mult_SAT_B(const mxArray* A_m, const mxArray* B_m) -{ - size_t n_A = mxGetN(A_m); - mwIndex* A_i = mxGetIr(A_m); - mwIndex* A_j = mxGetJc(A_m); - double* A_d = mxGetPr(A_m); - size_t n_B = mxGetN(B_m); - double* B_d = mxGetPr(B_m); - mxArray* C_m = mxCreateDoubleMatrix(n_A, n_B, mxREAL); - double* C_d = mxGetPr(C_m); - for (int j = 0; j < static_cast<int>(n_B); j++) - for (unsigned int i = 0; i < n_A; i++) - { - double sum = 0; - size_t nze_A = A_j[i]; - while (nze_A < static_cast<unsigned int>(A_j[i + 1])) - { - size_t i_A = A_i[nze_A]; - sum += A_d[nze_A++] * B_d[i_A]; - } - C_d[j * n_A + i] = sum; - } - return C_m; -} - -mxArray* -Interpreter::Sparse_transpose(const mxArray* A_m) -{ - size_t n_A = mxGetN(A_m); - size_t m_A = mxGetM(A_m); - mwIndex* A_i = mxGetIr(A_m); - mwIndex* A_j = mxGetJc(A_m); - size_t total_nze_A = A_j[n_A]; - double* A_d = mxGetPr(A_m); - mxArray* C_m = mxCreateSparse(n_A, m_A, total_nze_A, mxREAL); - mwIndex* C_i = mxGetIr(C_m); - mwIndex* C_j = mxGetJc(C_m); - double* C_d = mxGetPr(C_m); - unsigned int nze_C = 0, nze_A = 0; - ranges::fill_n(C_j, m_A + 1, 0); - map<pair<mwIndex, unsigned int>, double> B2; - for (unsigned int i = 0; i < n_A; i++) - while (nze_A < static_cast<unsigned int>(A_j[i + 1])) - { - C_j[A_i[nze_A] + 1]++; - B2[{A_i[nze_A], i}] = A_d[nze_A]; - nze_A++; - } - for (unsigned int i = 0; i < m_A; i++) - C_j[i + 1] += C_j[i]; - for (auto& [key, val] : B2) - { - C_d[nze_C] = val; - C_i[nze_C++] = key.second; - } - return C_m; -} - void Interpreter::compute_block_time(int my_Per_u_, bool evaluate, bool no_derivatives) { @@ -2750,24 +2740,19 @@ void Interpreter::Solve_Matlab_GMRES(mxArray* A_m, mxArray* b_m, bool is_two_boundaries, mxArray* x0_m) { size_t n = mxGetM(A_m); - std::array field_names {"droptol", "type"}; - std::array dims {static_cast<mwSize>(1)}; - mxArray* Setup - = mxCreateStructArray(dims.size(), dims.data(), field_names.size(), field_names.data()); - mxSetFieldByNumber(Setup, 0, 0, mxCreateDoubleScalar(lu_inc_tol)); - mxSetFieldByNumber(Setup, 0, 1, mxCreateString("ilutp")); + std::array<mxArray*, 2> lhs0; - std::array rhs0 {A_m, Setup}; + std::array rhs0 {A_m, iter_solver_opts.ilu}; if (mexCallMATLAB(lhs0.size(), lhs0.data(), rhs0.size(), rhs0.data(), "ilu")) throw FatalException("In GMRES, the incomplete LU decomposition (ilu) has failed"); mxArray* L1 = lhs0[0]; mxArray* U1 = lhs0[1]; - /*[za,flag1] = gmres(g1a,b,Blck_size,1e-6,Blck_size*periods,L1,U1);*/ + std::array rhs {A_m, b_m, - mxCreateDoubleScalar(size), - mxCreateDoubleScalar(1e-6), - mxCreateDoubleScalar(static_cast<double>(n)), + iter_solver_opts.gmres_restart, + iter_solver_opts.iter_tol, + iter_solver_opts.iter_maxit, L1, U1, x0_m}; @@ -2776,28 +2761,24 @@ Interpreter::Solve_Matlab_GMRES(mxArray* A_m, mxArray* b_m, bool is_two_boundari mxArray* z = lhs[0]; mxArray* flag = lhs[1]; double flag1 {mxGetScalar(flag)}; - mxDestroyArray(rhs0[1]); - mxDestroyArray(rhs[2]); - mxDestroyArray(rhs[3]); - mxDestroyArray(rhs[4]); mxDestroyArray(rhs[5]); mxDestroyArray(rhs[6]); + if (flag1 > 0) { if (flag1 == 1) - mexWarnMsgTxt( + mexErrMsgTxt( ("Error in bytecode: No convergence inside GMRES, in block " + to_string(block_num + 1)) .c_str()); else if (flag1 == 2) - mexWarnMsgTxt(("Error in bytecode: Preconditioner is ill-conditioned, in block " - + to_string(block_num + 1)) - .c_str()); + mexErrMsgTxt(("Error in bytecode: Preconditioner is ill-conditioned, in block " + + to_string(block_num + 1)) + .c_str()); else if (flag1 == 3) - mexWarnMsgTxt(("Error in bytecode: GMRES stagnated (Two consecutive iterates were the " - "same.), in block " - + to_string(block_num + 1)) - .c_str()); - lu_inc_tol /= 10; + mexErrMsgTxt(("Error in bytecode: GMRES stagnated (Two consecutive iterates were the " + "same.), in block " + + to_string(block_num + 1)) + .c_str()); } else { @@ -2819,6 +2800,7 @@ Interpreter::Solve_Matlab_GMRES(mxArray* A_m, mxArray* b_m, bool is_two_boundari y[eq + it_ * y_size] += slowc * yy; } } + mxDestroyArray(A_m); mxDestroyArray(b_m); mxDestroyArray(x0_m); @@ -2828,143 +2810,42 @@ Interpreter::Solve_Matlab_GMRES(mxArray* A_m, mxArray* b_m, bool is_two_boundari void Interpreter::Solve_Matlab_BiCGStab(mxArray* A_m, mxArray* b_m, bool is_two_boundaries, - mxArray* x0_m, int preconditioner) + mxArray* x0_m) { - /* precond = 0 => Jacobi - precond = 1 => Incomplet LU decomposition*/ size_t n = mxGetM(A_m); - mxArray *L1 = nullptr, *U1 = nullptr, *Diag = nullptr; - if (preconditioner == 0) - { - std::array<mxArray*, 1> lhs0; - std::array rhs0 {A_m, mxCreateDoubleScalar(0)}; - mexCallMATLAB(lhs0.size(), lhs0.data(), rhs0.size(), rhs0.data(), "spdiags"); - mxArray* tmp = lhs0[0]; - double* tmp_val = mxGetPr(tmp); - Diag = mxCreateSparse(n, n, n, mxREAL); - mwIndex* Diag_i = mxGetIr(Diag); - mwIndex* Diag_j = mxGetJc(Diag); - double* Diag_val = mxGetPr(Diag); - for (size_t i = 0; i < n; i++) - { - Diag_val[i] = tmp_val[i]; - Diag_j[i] = i; - Diag_i[i] = i; - } - Diag_j[n] = n; - } - else if (preconditioner == 1) - { - /*[L1, U1] = ilu(g1a=;*/ - std::array field_names {"type", "droptol"}; - const int type {0}, droptol {1}; - std::array dims {static_cast<mwSize>(1)}; - mxArray* Setup - = mxCreateStructArray(dims.size(), dims.data(), field_names.size(), field_names.data()); - mxSetFieldByNumber(Setup, 0, type, mxCreateString("ilutp")); - mxSetFieldByNumber(Setup, 0, droptol, mxCreateDoubleScalar(lu_inc_tol)); - std::array<mxArray*, 2> lhs0; - std::array rhs0 {A_m, Setup}; - if (mexCallMATLAB(lhs0.size(), lhs0.data(), rhs0.size(), rhs0.data(), "ilu")) - throw FatalException {"In BiCGStab, the incomplete LU decomposition (ilu) has failed"}; - L1 = lhs0[0]; - U1 = lhs0[1]; - mxDestroyArray(Setup); - } - double flags = 2; - mxArray* z = nullptr; - if (steady_state) /*Octave BicStab algorihtm involves a 0 division in case of a preconditionner - equal to the LU decomposition of A matrix*/ - { - mxArray* res = mult_SAT_B(Sparse_transpose(A_m), x0_m); - double* resid = mxGetPr(res); - double* b = mxGetPr(b_m); - for (int i = 0; i < static_cast<int>(n); i++) - resid[i] = b[i] - resid[i]; - std::array<mxArray*, 1> lhs; - std::array rhs {L1, res}; - mexCallMATLAB(lhs.size(), lhs.data(), rhs.size(), rhs.data(), "mldivide"); - std::array rhs2 {U1, lhs[0]}; - mexCallMATLAB(lhs.size(), lhs.data(), rhs2.size(), rhs2.data(), "mldivide"); - z = lhs[0]; - double* phat = mxGetPr(z); - double* x0 = mxGetPr(x0_m); - for (int i = 0; i < static_cast<int>(n); i++) - phat[i] = x0[i] + phat[i]; - - /*Check the solution*/ - res = mult_SAT_B(Sparse_transpose(A_m), z); - resid = mxGetPr(res); - double cum_abs = 0; - for (int i = 0; i < static_cast<int>(n); i++) - { - resid[i] = b[i] - resid[i]; - cum_abs += fabs(resid[i]); - } - if (cum_abs > 1e-7) - flags = 2; - else - flags = 0; - mxDestroyArray(res); - } + std::array<mxArray*, 2> lhs0; + std::array rhs0 {A_m, iter_solver_opts.ilu}; + if (mexCallMATLAB(lhs0.size(), lhs0.data(), rhs0.size(), rhs0.data(), "ilu")) + throw FatalException {"In BiCGStab, the incomplete LU decomposition (ilu) has failed"}; + mxArray* L1 = lhs0[0]; + mxArray* U1 = lhs0[1]; - if (flags == 2) - { - if (preconditioner == 0) - { - /*[za,flag1] = bicgstab(g1a,b,1e-6,Blck_size*periods,L1,U1);*/ - std::array rhs {A_m, b_m, mxCreateDoubleScalar(1e-6), - mxCreateDoubleScalar(static_cast<double>(n)), Diag}; - std::array<mxArray*, 2> lhs; - mexCallMATLAB(lhs.size(), lhs.data(), rhs.size(), rhs.data(), "bicgstab"); - z = lhs[0]; - mxArray* flag = lhs[1]; - flags = mxGetScalar(flag); - mxDestroyArray(flag); - mxDestroyArray(rhs[2]); - mxDestroyArray(rhs[3]); - mxDestroyArray(rhs[4]); - } - else if (preconditioner == 1) - { - /*[za,flag1] = bicgstab(g1a,b,1e-6,Blck_size*periods,L1,U1);*/ - std::array rhs {A_m, - b_m, - mxCreateDoubleScalar(1e-6), - mxCreateDoubleScalar(static_cast<double>(n)), - L1, - U1, - x0_m}; - std::array<mxArray*, 2> lhs; - mexCallMATLAB(lhs.size(), lhs.data(), rhs.size(), rhs.data(), "bicgstab"); - z = lhs[0]; - mxArray* flag = lhs[1]; - flags = mxGetScalar(flag); - mxDestroyArray(flag); - mxDestroyArray(rhs[2]); - mxDestroyArray(rhs[3]); - mxDestroyArray(rhs[4]); - mxDestroyArray(rhs[5]); - } - } + std::array rhs {A_m, b_m, iter_solver_opts.iter_tol, iter_solver_opts.iter_maxit, L1, U1, x0_m}; + std::array<mxArray*, 2> lhs; + mexCallMATLAB(lhs.size(), lhs.data(), rhs.size(), rhs.data(), "bicgstab"); + mxArray* z = lhs[0]; + mxArray* flag = lhs[1]; + double flags {mxGetScalar(flag)}; + mxDestroyArray(flag); + mxDestroyArray(rhs[4]); + mxDestroyArray(rhs[5]); if (flags > 0) { if (flags == 1) - mexWarnMsgTxt(("Error in bytecode: No convergence inside BiCGStab, in block " - + to_string(block_num + 1)) - .c_str()); + mexErrMsgTxt(("Error in bytecode: No convergence inside BiCGStab, in block " + + to_string(block_num + 1)) + .c_str()); else if (flags == 2) - mexWarnMsgTxt(("Error in bytecode: Preconditioner is ill-conditioned, in block " - + to_string(block_num + 1)) - .c_str()); + mexErrMsgTxt(("Error in bytecode: Preconditioner is ill-conditioned, in block " + + to_string(block_num + 1)) + .c_str()); else if (flags == 3) - mexWarnMsgTxt(("Error in bytecode: BiCGStab stagnated (Two consecutive iterates were the " - "same.), in block " - + to_string(block_num + 1)) - .c_str()); - lu_inc_tol /= 10; + mexErrMsgTxt(("Error in bytecode: BiCGStab stagnated (Two consecutive iterates were the " + "same.), in block " + + to_string(block_num + 1)) + .c_str()); } else { @@ -2986,6 +2867,7 @@ Interpreter::Solve_Matlab_BiCGStab(mxArray* A_m, mxArray* b_m, bool is_two_bound y[eq + it_ * y_size] += slowc * yy; } } + mxDestroyArray(A_m); mxDestroyArray(b_m); mxDestroyArray(x0_m); @@ -3941,7 +3823,6 @@ Interpreter::Simulate_One_Boundary() mxArray *b_m = nullptr, *A_m = nullptr, *x0_m = nullptr; SuiteSparse_long *Ap = nullptr, *Ai = nullptr; double *Ax = nullptr, *b = nullptr; - int preconditioner = 1; try_at_iteration = 0; Clear_u(); @@ -4010,14 +3891,10 @@ Interpreter::Simulate_One_Boundary() mexPrintf("MODEL STEADY STATE: (method=Sparse LU)\n"); break; case 7: - mexPrintf(preconditioner_print_out("MODEL STEADY STATE: (method=GMRES)\n", - preconditioner, true) - .c_str()); + mexPrintf("MODEL STEADY STATE: (method=GMRES with 'ilu' preconditioner)\n"); break; case 8: - mexPrintf(preconditioner_print_out("MODEL STEADY STATE: (method=BiCGStab)\n", - preconditioner, true) - .c_str()); + mexPrintf("MODEL STEADY STATE: (method=BiCGStab with 'ilu' preconditioner)\n"); break; } } @@ -4087,7 +3964,7 @@ Interpreter::Simulate_One_Boundary() else if ((solve_algo == 7 && steady_state) || (stack_solve_algo == 2 && !steady_state)) Solve_Matlab_GMRES(A_m, b_m, false, x0_m); else if ((solve_algo == 8 && steady_state) || (stack_solve_algo == 3 && !steady_state)) - Solve_Matlab_BiCGStab(A_m, b_m, false, x0_m, preconditioner); + Solve_Matlab_BiCGStab(A_m, b_m, false, x0_m); else if ((solve_algo == 6 && steady_state) || ((stack_solve_algo == 0 || stack_solve_algo == 1 || stack_solve_algo == 4 || stack_solve_algo == 6) @@ -4204,43 +4081,12 @@ Interpreter::Simulate_Newton_One_Boundary(bool forward) mxFree(r); } -string -Interpreter::preconditioner_print_out(string s, int preconditioner, bool ss) -{ - int n = s.length(); - string tmp = ", preconditioner="; - switch (preconditioner) - { - case 0: - if (ss) - tmp.append("Jacobi on static jacobian"); - else - tmp.append("Jacobi on dynamic jacobian"); - break; - case 1: - if (ss) - tmp.append("incomplete lutp on static jacobian"); - else - tmp.append("incomplete lu0 on dynamic jacobian"); - break; - case 2: - tmp.append("incomplete lutp on dynamic jacobian"); - break; - case 3: - tmp.append("lu on static jacobian"); - break; - } - s.insert(n - 2, tmp); - return s; -} - void Interpreter::Simulate_Newton_Two_Boundaries( bool cvg, const vector_table_conditional_local_type& vector_table_conditional_local) { double top = 0.5; double bottom = 0.1; - int preconditioner = 2; if (start_compare == 0) start_compare = y_kmin; u_count_alloc_save = u_count_alloc; @@ -4394,15 +4240,11 @@ Interpreter::Simulate_Newton_Two_Boundaries( break; case 2: mexPrintf( - preconditioner_print_out("MODEL SIMULATION: (method=GMRES on stacked system)\n", - preconditioner, false) - .c_str()); + "MODEL SIMULATION: (method=GMRES on stacked system with 'ilu' preconditioner)\n"); break; case 3: - mexPrintf(preconditioner_print_out( - "MODEL SIMULATION: (method=BiCGStab on stacked system)\n", - preconditioner, false) - .c_str()); + mexPrintf("MODEL SIMULATION: (method=BiCGStab on stacked system with 'ilu' " + "preconditioner)\n"); break; case 4: mexPrintf("MODEL SIMULATION: (method=Sparse LU solver with optimal path length on " @@ -4460,7 +4302,7 @@ Interpreter::Simulate_Newton_Two_Boundaries( else if (stack_solve_algo == 2) Solve_Matlab_GMRES(A_m, b_m, true, x0_m); else if (stack_solve_algo == 3) - Solve_Matlab_BiCGStab(A_m, b_m, true, x0_m, 1); + Solve_Matlab_BiCGStab(A_m, b_m, true, x0_m); else if (stack_solve_algo == 5) Solve_ByteCode_Symbolic_Sparse_GaussianElimination(symbolic); } diff --git a/mex/sources/bytecode/Interpreter.hh b/mex/sources/bytecode/Interpreter.hh index 0259d5e8e0b3e95a8ce427a8ef615b8e95431948..393bbaeaba63aed3ff168b3f2b13ae932c988b2b 100644 --- a/mex/sources/bytecode/Interpreter.hh +++ b/mex/sources/bytecode/Interpreter.hh @@ -69,6 +69,19 @@ constexpr double mem_increasing_factor = 1.1; constexpr int NO_ERROR_ON_EXIT {0}, ERROR_ON_EXIT {1}; +// Stores various options for iterative solvers (GMRES, BiCGStab), from options_.simul +struct iter_solver_opts_t +{ + const char* preconditioner; + mxArray* iter_tol; + mxArray* iter_maxit; + mxArray* gmres_restart; + mxArray* ilu; + + void set_static_values(const mxArray* options_); + void set_dynamic_values(const mxArray* options_); +}; + class Interpreter { private: @@ -110,7 +123,7 @@ private: int iter; int start_compare; int restart; - double lu_inc_tol; + iter_solver_opts_t iter_solver_opts; SuiteSparse_long *Ap_save, *Ai_save; double *Ax_save, *b_save; @@ -199,13 +212,11 @@ private: double* b); void Solve_Matlab_GMRES(mxArray* A_m, mxArray* b_m, bool is_two_boundaries, mxArray* x0_m); - void Solve_Matlab_BiCGStab(mxArray* A_m, mxArray* b_m, bool is_two_boundaries, mxArray* x0_m, - int precond); + void Solve_Matlab_BiCGStab(mxArray* A_m, mxArray* b_m, bool is_two_boundaries, mxArray* x0_m); void Check_and_Correct_Previous_Iteration(); bool Simulate_One_Boundary(); bool solve_linear(bool do_check_and_correct); void solve_non_linear(); - string preconditioner_print_out(string s, int preconditioner, bool ss); bool compare(int* save_op, int* save_opa, int* save_opaa, int beg_t, long nop4); void Insert(int r, int c, int u_index, int lag_index); void Delete(int r, int c); @@ -222,10 +233,6 @@ private: int complete(int beg_t); void bksub(int tbreak, int last_period); void simple_bksub(); - // Computes Aᵀ where A is are sparse. The result is sparse. - static mxArray* Sparse_transpose(const mxArray* A_m); - // Computes Aᵀ·B where A is sparse and B is dense. The result is dense. - static mxArray* mult_SAT_B(const mxArray* A_m, const mxArray* B_m); void compute_block_time(int my_Per_u_, bool evaluate, bool no_derivatives); bool compute_complete(bool no_derivatives); @@ -240,7 +247,8 @@ public: int stack_solve_algo_arg, int solve_algo_arg, bool print_arg, const mxArray* GlobalTemporaryTerms_arg, bool steady_state_arg, bool block_decomposed_arg, int col_x_arg, int col_y_arg, - const BasicSymbolTable& symbol_table_arg, int verbosity_arg); + const BasicSymbolTable& symbol_table_arg, int verbosity_arg, + iter_solver_opts_t iter_solver_opts_arg); pair<bool, vector<int>> extended_path(const string& file_name, bool evaluate, int block, int nb_periods, const vector<s_plan>& sextended_path, diff --git a/mex/sources/bytecode/bytecode.cc b/mex/sources/bytecode/bytecode.cc index 86bbda4d57c59b6231b295f178c2f98e2e0950a1..1733afe30028235993862af7e60968145a4cf756 100644 --- a/mex/sources/bytecode/bytecode.cc +++ b/mex/sources/bytecode/bytecode.cc @@ -1,5 +1,5 @@ /* - * Copyright © 2007-2024 Dynare Team + * Copyright © 2007-2025 Dynare Team * * This file is part of Dynare. * @@ -19,6 +19,7 @@ #include <algorithm> #include <cmath> +#include <cstring> #include <type_traits> #include "ErrorHandling.hh" @@ -480,6 +481,34 @@ mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) } }()}; + iter_solver_opts_t iter_solver_opts; + if (steady_state) + iter_solver_opts.set_static_values(options_); + else + { + if (stack_solve_algo == 2 || stack_solve_algo == 3) + { + // Check that a preconditioner other than 'ilu' has not been requested + int field {mxGetFieldNumber(options_, "simul")}; + if (field < 0) + mexErrMsgTxt("simul is not a field of options_"); + mxArray* simul {mxGetFieldByNumber(options_, 0, field)}; + field = mxGetFieldNumber(simul, "preconditioner"); + if (field < 0) + mexErrMsgTxt("preconditioner is not a field of options_.simul"); + mxArray* preconditioner {mxGetFieldByNumber(simul, 0, field)}; + if (!mxIsChar(preconditioner)) + mexErrMsgTxt("options_.simul.preconditioner should be a character array"); + char* preconditioner_str {mxArrayToString(preconditioner)}; + if (std::strcmp(preconditioner_str, "ilu")) + mexErrMsgTxt( + "The 'bytecode' option is not compatible with a preconditioner other than 'ilu'"); + mxFree(preconditioner_str); + } + + iter_solver_opts.set_dynamic_values(options_); + } + field = mxGetFieldNumber(M_, "fname"); if (field < 0) mexErrMsgTxt("fname is not a field of M_"); @@ -549,7 +578,8 @@ mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) static_cast<int>(col_x), static_cast<int>(col_y), symbol_table, - verbosity}; + verbosity, + iter_solver_opts}; bool r; vector<int> blocks; diff --git a/preprocessor b/preprocessor index 5c75643443ab51e49e18f7bc6546e85cd3f55536..a7d747aedac558d2d70067a284439e0130e40fa9 160000 --- a/preprocessor +++ b/preprocessor @@ -1 +1 @@ -Subproject commit 5c75643443ab51e49e18f7bc6546e85cd3f55536 +Subproject commit a7d747aedac558d2d70067a284439e0130e40fa9 diff --git a/tests/block_bytecode/ls2003.mod b/tests/block_bytecode/ls2003.mod index 3fa05a9c265ff7d4a052b557e4147eeec57c7724..db48f2058eb16ad007caaf75bbdf07b2fe34baba 100644 --- a/tests/block_bytecode/ls2003.mod +++ b/tests/block_bytecode/ls2003.mod @@ -215,4 +215,18 @@ shocks; end; perfect_foresight_setup(periods=20); -perfect_foresight_solver(no_homotopy, stack_solve_algo = @{stack_solve_algo}); +perfect_foresight_solver(no_homotopy, stack_solve_algo = @{stack_solve_algo} +@#if length(preconditioner) > 0 +, preconditioner = @{preconditioner} +@#endif +@#if preconditioner == "iterstack" +@# if !(block && stack_solve_algo == 3) +// Ensure that problem is not too small for iterstack, and also that there is a “residual” in the block diagonal matrix (since 3 does not divide 20) +, iterstack_nperiods = 3 +@# else +// For some reason iterstack can’t solve this model with block decomposition + BiCGStab +// Hence do the equivalent of a full LU +, iterstack_nperiods = 20 +@# endif +@#endif +); diff --git a/tests/block_bytecode/run_ls2003.m b/tests/block_bytecode/run_ls2003.m index fd7018f2a629b9ce183284b5607c1e588f850331..a68c4e97828ac705aa1c0da9659f5f7e239f897e 100644 --- a/tests/block_bytecode/run_ls2003.m +++ b/tests/block_bytecode/run_ls2003.m @@ -1,6 +1,6 @@ -function run_ls2003(block, storage, solve_algo, stack_solve_algo) +function run_ls2003(block, storage, solve_algo, stack_solve_algo, preconditioner) -% Copyright © 2010-2013 Dynare Team +% Copyright © 2010-2025 Dynare Team % % This file is part of Dynare. % @@ -17,17 +17,23 @@ function run_ls2003(block, storage, solve_algo, stack_solve_algo) % You should have received a copy of the GNU General Public License % along with Dynare. If not, see <https://www.gnu.org/licenses/>. - disp(['TEST: ls2003 (block=' num2str(block) ', bytecode=' ... - num2str(storage==2) ', use_dll=' num2str(storage==1) ... - ', solve_algo=' num2str(solve_algo) ... - ', stack_solve_algo=' num2str(stack_solve_algo) ')...']); + if nargin < 5 + preconditioner = ''; + end + + fprintf('TEST: ls2003 (block=%d, bytecode=%d, use_dll=%d, solve_algo=%d, stack_solve_algo=%d', block, storage==2, storage==1, solve_algo, stack_solve_algo) + if ~isempty(preconditioner) + fprintf(', preconditioner=%s', preconditioner) + end + fprintf(')...\n') fid = fopen('ls2003_tmp.mod', 'w'); assert(fid > 0); fprintf(fid, ['@#define block = %d\n@#define bytecode = %d\n' ... '@#define use_dll = %d\n' ... '@#define solve_algo = %d\n@#define stack_solve_algo = %d\n' ... + '@#define preconditioner = "%s"\n' ... '@#include \"ls2003.mod\"\n'], block, storage==2, storage==1, ... - solve_algo, stack_solve_algo); + solve_algo, stack_solve_algo, preconditioner); fclose(fid); dynare('ls2003_tmp.mod','console') end diff --git a/tests/run_block_bytecode_tests.m b/tests/run_block_bytecode_tests.m index 0c95b794ab539939b7c58eefbb4d0d51b730c674..cf65a1868ba99e68d08e7d72879850feeaf5877f 100644 --- a/tests/run_block_bytecode_tests.m +++ b/tests/run_block_bytecode_tests.m @@ -1,4 +1,4 @@ -% Copyright © 2011-2024 Dynare Team +% Copyright © 2011-2025 Dynare Team % % This file is part of Dynare. % @@ -111,32 +111,43 @@ for blockFlag = 0:1 end end for i = 1:length(stack_solve_algos) - try - old_path = path; - clear oo_ % Ensure that oo_.endo_simul won’t be overwritten when loading wsMat - save wsMat - run_ls2003(blockFlag, storageFlag, default_solve_algo, stack_solve_algos(i)) - load wsMat - path(old_path); - % Test against the reference simulation path - load('test.mat','y_ref'); - diff = oo_.endo_simul - y_ref; - if max(max(abs(diff))) > options_.dynatol.x - failedBlock{size(failedBlock,2)+1} = ['block_bytecode' filesep 'run_ls2003.m(' num2str(blockFlag) ',' num2str(storageFlag) ',' num2str(default_solve_algo) ',' num2str(stack_solve_algos(i)) ')']; - if isoctave - exception.message = 'ERROR: simulation path differs from the reference path'; - else - exception = MException('Dynare:simerr', 'ERROR: simulation path difers from the reference path'); + if ismember(stack_solve_algos(i), [2 3]) + if storageFlag ~= 2 + preconditioners = {'umfiter', 'iterstack', 'ilu'}; + else % bytecode + preconditioners = {'ilu'}; + end + else + preconditioners = {''}; + end + for j = 1:length(preconditioners) + try + old_path = path; + clear oo_ % Ensure that oo_.endo_simul won’t be overwritten when loading wsMat + save wsMat + run_ls2003(blockFlag, storageFlag, default_solve_algo, stack_solve_algos(i), preconditioners{j}) + load wsMat + path(old_path); + % Test against the reference simulation path + load('test.mat','y_ref'); + diff = oo_.endo_simul - y_ref; + if max(max(abs(diff))) > options_.dynatol.x + failedBlock{size(failedBlock,2)+1} = ['block_bytecode' filesep 'run_ls2003.m(' num2str(blockFlag) ',' num2str(storageFlag) ',' num2str(default_solve_algo) ',' num2str(stack_solve_algos(i)) ',' preconditioners{j} ')']; + if isoctave + exception.message = 'ERROR: simulation path differs from the reference path'; + else + exception = MException('Dynare:simerr', 'ERROR: simulation path difers from the reference path'); + end + printTestError(['block_bytecode' filesep 'run_ls2003.m(' num2str(blockFlag) ',' num2str(storageFlag) ',' num2str(default_solve_algo) ',' num2str(stack_solve_algos(i)) ',' preconditioners{j} ')'], exception); + clear exception end - printTestError(['block_bytecode' filesep 'run_ls2003.m(' num2str(blockFlag) ',' num2str(storageFlag) ',' num2str(default_solve_algo) ',' num2str(stack_solve_algos(i)) ')'], exception); + catch exception + load wsMat + path(old_path); + failedBlock{size(failedBlock,2)+1} = ['block_bytecode' filesep 'run_ls2003.m(' num2str(blockFlag) ',' num2str(storageFlag) ',' num2str(default_solve_algo) ',' num2str(stack_solve_algos(i)) ',' preconditioners{j} ')']; + printTestError(['block_bytecode' filesep 'run_ls2003.m(' num2str(blockFlag) ',' num2str(storageFlag) ',' num2str(default_solve_algo) ',' num2str(stack_solve_algos(i)) ',' preconditioners{j} ')'], exception); clear exception end - catch exception - load wsMat - path(old_path); - failedBlock{size(failedBlock,2)+1} = ['block_bytecode' filesep 'run_ls2003.m(' num2str(blockFlag) ',' num2str(storageFlag) ',' num2str(default_solve_algo) ',' num2str(stack_solve_algos(i)) ')']; - printTestError(['block_bytecode' filesep 'run_ls2003.m(' num2str(blockFlag) ',' num2str(storageFlag) ',' num2str(default_solve_algo) ',' num2str(stack_solve_algos(i)) ')'], exception); - clear exception end end end