function [ x , status ] =  GRS( f , varargin )

%function [ x , status ] = GRS( f , range , delta , MaxFeval )
%
% Apply the classical Golden Ratio Search for the minimization of the
% provided one-dimensional function f, which must have the following
% interface:
%
%   [ v , varargout ] = f( x )
%
% Input:
%
% - x is either a scalar real denoting the input of f(), or [] (empty).
%
% Output:
%
% - v (real, scalar): if x == [] this is the best known lower bound on
%   the global optimum of f() on the standard interval in which f() is
%   supposed to be minimised (see next). If x ~= [] then v = f(x).
%
% - g (real, either scalar or a [ 2 x 1 ] matrix denoting an interval) is
%   the first optional argument. This also depends on x. if x == [] then
%   g is a [ 2 x 1 ] matrix denoting the standard interval in which f()
%   is supposed to be minimised (into which v is the minimum). f() is
%   never called with x ~= [].
%
% The other [optional] input parameters are:
%
% - range: (either [ 2 x 1 ] real vector or [], default []): the range
%   in which the local minimum has to be seached; if range == [], the
%   default range point provided by f() is used.
%
% - delta (real scalar, default value 1e-6): the accuracy in the stopping
%   criterion: the algorithm is stopped when the diameter of the
%   restricted range is less than or equal to delta.
%
% - MaxFeval (integer scalar, default value 100): the maximum number of
%   function evaluations (hence, iterations will be not more than
%   MaxFeval - 2 because at each iteration one function evaluation is
%   performed, except in the first one when two are).
%
% Output:
%
% - x (real scalar): the best solution found so far.
%
% - status (string): a string describing the status of the algorithm at
%   termination
%
%   = 'optimal': the algorithm terminated having proven that x is a(n
%     approximately) optimal solution, i.e., the diameter of the
%     restricted range is less than or equal to delta.
%
%   = 'empty': the provided range is empty (x_- > x_+) and therefore
%     such is the optimization problem
%
%   = 'stopped': the algorithm terminated having exhausted the maximum
%     number of iterations: x is the best solution found so far, but not
%     necessarily the optimal one
%
%   = 'error': the algorithm found a numerical error that prevents it from
%     continuing optimization
%
%{
 =======================================
 Author: Antonio Frangioni
 Date: 09-08-21
 Version 0.10
 Copyright Antonio Frangioni
 =======================================
%}

Plotg = 1;
% 0 = nothing is plotted
% 1 = the function value / gap are plotted
% 2 = the function and the test points used are plotted

Interactive = true;  % if we pause at every iteration

% reading and checking input- - - - - - - - - - - - - - - - - - - - - - - -
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

if ~ isa( f , 'function_handle' )
   error( 'f not a function' );
end

if isempty( varargin ) || isempty( varargin{ 1 } )
   [ fStar , range ] = f( [] );
else
   fStar = - Inf;  % if the range is not the standard one, we can't trust
		   % the standard global minima
   range = varargin{ 1 };
end

if ~ isreal( range )
   error( 'range not a real vector' );
end

if ( size( range , 1 ) ~= 1 ) || ( size( range , 2 ) ~= 2 )
   error( 'range is not a [ 1 x 2 ] vector' );
end

xm = range( 1 );  % x_-
xp = range( 2 );  % x_+

if xm > xp
   fprintf( 'range is empty\n' );
   x = NaN;
   status = 'empty';
   return;
end

if length( varargin ) > 1
   delta = varargin{ 2 };
   if ~ isreal( delta ) || ~ isscalar( delta )
      error( 'delta is not a real scalar' );
   end
else
   delta = 1e-6;
end

if length( varargin ) > 2
   MaxFeval = round( varargin{ 3 } );
   if ~ isscalar( MaxFeval )
      error( 'MaxFeval is not an integer scalar' );
   end
   if MaxFeval < 2
      error( 'at least two function computations are required' );
   end
else
   MaxFeval = 100;
end

% initializations - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

r = ( sqrt( 5 ) - 1 ) / 2;

xmp = xm + ( 1 - r ) * ( xp - xm );  % x'_-
xpp = xm + r * ( xp - xm );          % x'_+

fxmp = f( xmp );                     % f( x'_- )
fxpp = f( xpp );                     % f( x'_+ )

feval = 2;

if fxmp <= fxpp
   fx = fxmp;
else
   fx = fxpp;
end

status = 'optimal';

if Plotg
   gap = [];
end

fprintf( 'Golden ratio search\n');
if fStar > - Inf
   fprintf( 'feval\trel gap\t\tx_-\t\tx_+\n');
else
   fprintf( 'feval\tfbest\t\tx_-\t\tx_+\n');
end

% main loop - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

while xp - xm > delta

    % output statistics - - - - - - - - - - - - - - - - - - - - - - - - - -

    if fStar > - Inf
       gapk = ( fx - fStar ) / max( [ abs( fStar ) 1 ] );

       if Plotg == 1
	  gap( end + 1 ) = gapk;
	  semilogy( gap , 'Color' , 'k' , 'LineWidth' , 2 );
	  xlim( [ 0 35 ] );
	  ylim( [ 1e-15 inf ] );
	  ax = gca;
	  ax.FontSize = 16;
	  ax.Position = [ 0.03 0.07 0.95 0.92 ];
	  ax.Toolbar.Visible = 'off';
       end
    else
       gapk = fx;
    end
    fprintf( '%4d\t%1.4e\t%1.8e\t%1.8e\n' , feval , gapk , xm , xp );

    if Plotg == 2
       xbot = xm - ( xp - xm ) / 20;
       xtop = xp + ( xp - xm ) / 20;

       warning( 'off' , 'all' );
       fplot( @(x) f( x ) , [ xbot xtop ] , 'Color' , 'k' , ...
	      'LineWidth' , 1 );

       xlim( [ xbot xtop ] );
       yticks( [] );
       ax = gca;
       ax.FontSize = 16;
       ax.Position = [ 0.025 0.05 0.95 0.95 ];
       ax.Toolbar.Visible = 'off';
       xticks( [ xbot xm xmp xpp xp xtop ] );
       xticklabels( { num2str( xbot , '%1.1g' ) , 'x-' , 'x''-' , ...
		    'x''+' , 'x+' , num2str( xtop , '%1.1g' ) } );
       warning( 'on' , 'all' );
    end


    % stopping criteria - - - - - - - - - - - - - - - - - - - - - - - - - -

    if feval > MaxFeval
       status = 'stopped';
       break;
    end

    % main logic- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    if fxmp <= fxpp
       xp = xpp; xpp = xmp; xmp = xm + ( 1 - r ) * ( xp - xm );
       fxpp = fxmp; fx = fxmp; fxmp = f( xmp );
    else
       xm = xmp; xmp = xpp; xpp = xm + r * ( xp - xm );
       fxmp = fxpp; fx = fxpp; fxpp = f( xpp );
    end

    feval = feval + 1;

    if Interactive
       pause;
    end

    % iterate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

end

% end of main loop- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

% select final answer

if fxmp <= fxpp
   x = xmp;
else
   x = xpp;
end

% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

end  % the end- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
