function [y, T, success, max_res, iter] = solve_one_boundary(fh, y, x, params, steady_state, T, ...
                                                             y_index_eq, periods, is_linear, Block_Num, maximum_lag, maxit_, solve_tolf, cutoff, stack_solve_algo, is_forward, is_dynamic, verbose, M_, options_)
% Computes the deterministic simulation or the steady state for a block of equations containing
% only lags or only leads (but not both).
%
% INPUTS
%   fh                  [handle]        function handle to the static/dynamic file for the block
%   y                   [matrix]        All the endogenous variables of the model
%   x                   [matrix]        All the exogenous variables of the model
%   params              [vector]        All the parameters of the model
%   steady_state        [vector]        steady state of the model
%   T                   [matrix]        Temporary terms
%   y_index_eq          [vector of int] The index of the endogenous variables of
%                                       the block
%   periods             [integer]       number of simulation periods
%   is_linear           [logical]       whether the block is linear
%   Block_Num           [integer]       block number
%   maximum_lag         [integer]       maximum number of lag in the model
%   maxit_              [integer]       maximum number of iteration in Newton
%   solve_tolf          [double]        convergence criteria
%   cutoff              [double]        cutoff to correct the direction in Newton in case
%                                       of singular Jacobian matrix
%   stack_solve_algo    [integer]       linear solver method used in the Newton algorithm
%   is_forward          [logical]       Whether the block has to be solved forward
%                                       If false, the block is solved backward
%   is_dynamic          [logical]       Whether this is a deterministic simulation
%   verbose             [logical]       Whether iterations are to be printed
%   M_                  [structure]     storing the model information
%   options_            [structure]     storing the options
%
% OUTPUTS
%   y                   [matrix]        All endogenous variables of the model
%   T                   [matrix]        Temporary terms
%   success             [logical]       Whether a solution was found
%   max_res             [double]        ∞-norm of the residual
%   iter                [integer]       Number of iterations
%
% ALGORITHM
%   Newton with LU or GMRES or BicGstab for dynamic block

% Copyright © 1996-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/>.

Blck_size=size(y_index_eq,2);
correcting_factor=0.01;
max_resa=1e100;
lambda = 1; % Length of Newton step
first_iter_lu = [];
if is_forward
    incr = 1;
    start = maximum_lag+1;
    finish = maximum_lag+periods;
else
    incr = -1;
    start = maximum_lag+periods;
    finish = maximum_lag+1;
end

for it_=start:incr:finish
    cvg=false;
    iter=0;
    while ~(cvg || iter>maxit_)
        if is_dynamic
            [yy, T(:, it_), r, g1] = fh(dynendo(y, it_, M_), x(it_, :), params, steady_state, ...
                                        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_));
            y(:, it_) = yy(M_.endo_nbr+(1:M_.endo_nbr));
        else
            [y, T, r, g1] = fh(y, x, params, M_.block_structure_stat.block(Block_Num).g1_sparse_rowval, ...
                               M_.block_structure_stat.block(Block_Num).g1_sparse_colval, ...
                               M_.block_structure_stat.block(Block_Num).g1_sparse_colptr, T);
        end
        if ~isreal(r)
            max_res=(-(norm(vec(r), 'Inf'))^2)^0.5;
        else
            max_res = norm(vec(r), 'Inf'); % Do not use max(max(abs(…))) because it omits NaN
        end
        if verbose
            disp(['iteration : ' int2str(iter+1) ' => ' num2str(max_res) ' time = ' int2str(it_)])
            if is_dynamic
                disp([char(M_.endo_names{y_index_eq}) repmat(' ', numel(y_index_eq), 1) num2str([y(y_index_eq, it_) r g1])])
            else
                disp([char(M_.endo_names{y_index_eq}) repmat(' ', numel(y_index_eq), 1) num2str([y(y_index_eq) r g1])])
            end
        end
        if ~isreal(max_res) || isnan(max_res)
            cvg = false;
        elseif is_linear && iter>0
            cvg = true;
        else
            cvg=(max_res<solve_tolf);
        end
        if ~cvg
            if iter>0
                if ~isreal(max_res) || isnan(max_res) || (max_resa<max_res && iter>1)
                    if isnan(max_res) || (max_resa<max_res && iter>1)
                        detJ=det(g1a);
                        if(abs(detJ)<1e-7)
                            max_factor=max(max(abs(g1a)));
                            ze_elem=sum(diag(g1a)<cutoff);
                            if verbose
                                disp([num2str(full(ze_elem),'%d') ' elements on the Jacobian diagonal are below the cutoff (' num2str(cutoff,'%f') ')'])
                            end
                            if correcting_factor<max_factor
                                correcting_factor=correcting_factor*4;
                                if verbose
                                    disp(['The Jacobian matrix is singular, det(Jacobian)=' num2str(detJ,'%f') '.'])
                                    disp('    trying to correct the Jacobian matrix:')
                                    disp(['    correcting_factor=' num2str(correcting_factor,'%f') ' max(Jacobian)=' num2str(full(max_factor),'%f')])
                                end
                                dx = (g1+correcting_factor*speye(Blck_size))\r;
                                y(y_index_eq, it_)=ya_save-lambda*dx;
                                continue
                            else
                                if verbose
                                    disp('The singularity of the Jacobian matrix could not be corrected')
                                end
                                success = false;
                                return
                            end
                        end
                    elseif lambda>1e-8
                        lambda=lambda/2;
                        if verbose
                            disp(['reducing the path length: lambda=' num2str(lambda,'%f')])
                        end
                        if is_dynamic
                            y(y_index_eq, it_)=ya_save-lambda*dx;
                        else
                            y(y_index_eq)=ya_save-lambda*dx;
                        end
                        continue
                    else
                        if verbose
                            if cutoff==0
                                fprintf('Convergence not achieved in block %d, at time %d, after %d iterations.\n Increase "maxit".\n',Block_Num, it_, iter);
                            else
                                fprintf('Convergence not achieved in block %d, at time %d, after %d iterations.\n Increase "maxit" or set "cutoff=0" in model options.\n',Block_Num, it_, iter);
                            end
                        end
                        success = false;
                        return
                    end
                else
                    if lambda<1
                        lambda=max(lambda*2, 1);
                    end
                end
            end
            if is_dynamic
                ya = y(y_index_eq, it_);
            else
                ya = y(y_index_eq);
            end
            ya_save=ya;
            g1a=g1;
            if is_dynamic && stack_solve_algo==4
                stpmx = 100 ;
                stpmax = stpmx*max([sqrt(ya'*ya);size(y_index_eq,2)]);
                nn=1:size(y_index_eq,2);
                g = (r'*g1)';
                f = 0.5*(r'*r);
                p = -g1\r ;
                ya = lnsrch1(ya,f,g,p,stpmax, ...
                             @lnsrch1_wrapper_one_boundary,nn, ...
                                       nn, options_.solve_tolx, y_index_eq, fh, Block_Num, y, x, params, steady_state, T(:, it_), it_, M_);
                dx = ya - y(y_index_eq, it_);
                y(y_index_eq, it_) = ya;
                %% Recompute temporary terms, since they are not given as output of lnsrch1
                [~, T(:, it_)] = fh(dynendo(y, it_, M_), x(it_, :), params, steady_state, ...
                                    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 && ismember(stack_solve_algo, [0:3 6 8:10])
                % Block diagonal LU preconditioner and LBJ do not make sense for one boundary problems, hence use LU.
                force_lu = (ismember(stack_solve_algo, [2 3]) ...
                            && strcmp(options_.simul.preconditioner, 'block_diagonal_lu')) ...
                            || ismember(stack_solve_algo, [1 6]);
                [dx, first_iter_lu] = lin_solve(g1, r, options_, first_iter_lu, force_lu);
                ya = ya - lambda*dx;
                y(y_index_eq, it_) = ya;
            elseif ~is_dynamic && options_.solve_algo == 6
                if verbose
                    disp('steady: Sparse LU ')
                end
                dx =  g1\r;
                ya = ya - lambda*dx;
                y(y_index_eq) = ya;
            elseif ~is_dynamic && ismember(options_.solve_algo, [7 8])
                if verbose
                    if options_.solve_algo == 7
                        disp('steady: GMRES ')
                    else
                        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.incomplete_lu);
                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'])
                else
                    error(['options_.solve_algo = ' num2str(options_.solve_algo) ' not implemented'])
                end
            end
            iter=iter+1;
            max_resa = max_res;
        end
    end
    if ~cvg
        if verbose
            if cutoff == 0
                fprintf('Convergence not achieved in block %d, at time %d, after %d iterations.\n Increase "maxit".\n',Block_Num, it_,iter);
            else
                fprintf('Convergence not achieved in block %d, at time %d, after %d iterations.\n Increase "maxit" or set "cutoff=0" in model options.\n',Block_Num, it_,iter);
            end
        end
        success = false;
        return
    end
end

if any(any(isnan(y(y_index_eq)))) || any(any(isinf(y(y_index_eq))))
    success = false;
    return
end

success = true;


function y3n = dynendo(y, it_, M_)
    if it_ > 1 && it_ < size(y, 2)
        y3n = reshape(y(:, it_+(-1:1)), 3*M_.endo_nbr, 1);
    elseif it_ > 1 % Purely backward model (in last period)
        y3n = [ reshape(y(:, it_+(-1:0)), 2*M_.endo_nbr, 1); NaN(M_.endo_nbr, 1) ];
    elseif it_ < size(y, 2) % Purely forward model (in first period)
        y3n = [ NaN(M_.endo_nbr, 1); reshape(y(:, it_+(0:1)), 2*M_.endo_nbr, 1) ];
    else % Static model
        y3n = [ NaN(M_.endo_nbr, 1); y(:, it_); NaN(M_.endo_nbr, 1) ];
    end

function r = lnsrch1_wrapper_one_boundary(ya, y_index, fh, Block_Num, y, x, params, steady_state, T, it_, M_)
    y(y_index, it_) = ya;
    [~, ~, r] = fh(dynendo(y, it_, M_), x(it_, :), params, steady_state, ...
                   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);
