function [ Q , varargout ] = genQf( n , varargin )

%function [ Q , q , v ] = genQf( n , seed , rank , conv , ecc , dom , box )
%
% Produces the data structure encoding a Quadratic function
%
%  f( x ) = (1/2) x^T * Q * x + q * x
%
% Input:
%
% - n (integer, scalar): the size of the problem
%
% - seed (integer, default 0): the seed for the random number generator
%
% - rank (real, scalar, default 1.1): Q will be obtained as Q = G^T G, with
%   G a m \times n random matrix with m = rank * n (almost, see "conv"
%   below). If rank > 1 then Q can be expected to be full-rank, if
%   rank < 1 it will not
%
% - conv (real, scalar, default 1): a parameter controlling whether Q is
%   positive (semi)definite, negative (semi)definite, or indefinite. This
%   is done by actually computing Q as Q = G_+^T G_+ - G_-^T G_-. The m
%   rows of G are partitioned according to conv, i.e., m * conv rows are
%   put in G_+ while m * ( 1 - conv ) rows are put in G_-. Hence, conv = 1
%   means that Q is positive (semi)definite, conv = 1 means that Q is
%   negative (semi)definite, and any value in the middle means that Q is
%   indefinite, with "close to 1" meaning "almost positive (semi)definite",
%   and "close to 0" meaning "almost negative (semi)definite"
%
% - ecc (real, scalar, default 0.99): the eccentricity of Q, i.e., the
%   ratio ( \lambda_1 - \lambda_n ) / ( \lambda_1 + \lambda_n ), with
%   \lambda_1 the largest eigenvalue and \lambda_n the smallest one. Note
%   that this makes sense only if \lambda_n > 0, for otherwise the
%   eccentricity is always 1; hence, this setting is ignored if
%   \lambda_n = 0, i.e., Q is not full-rank (see above). An eccentricity of
%   0 means that all eigenvalues are equal, as eccentricity -> 1 the
%   largest eigenvalue gets larger and larger w.r.t. the smallest one
%
% - dom (real, scalar, default 1): possibly modifies the matrix by making
%   it more or less diagonally dominant. This is obtained by multiplying
%   each diagonal element by a random number uniformly picked in the
%   interval [ 2 * diag / 3 , 4 * diag / 3 ] (and therefore with average
%   value diag). If diag >> 1 this increases the diagonal dominance, if
%   diag << 1 this decreases it. The operation is only performed if
%   diag ~= 1.
%
% - box (real, scalar, default 1): this parameter controls the generation
%   of q. This is done by first randomly generating one (tentatively)
%   optimal solution x_* to the unconstrained minimization of f( x ) in the
%   symmetric hypercube of size 2 * abs( box ), i.e., by uniformly drawing
%   each of its entries in the interval [ - abs( box ) , abs( box ) ];
%   then, q is generated as - Q * x_*, so that Q * x_* + q = 0. This
%   ensures that f( x ) is bounded below if Q is positive semidefinite, and
%   bounded above if Q is negative semidefinite. However, we also want to
%   be able to create problems that are positive semidefinite but not
%   bounded below, which means that the system Q x + q = 0 must not have a
%   solution. This first of all requires Q to be low-rank (some of the
%   eigenvalues actually = 0), which can be obtained by putting rank < 1.
%   Then, if box < 0 the vector q obtained as above is modified by
%   multiplying each of its entries by a random number uniformly picked in
%   the interval [ 2 / 3 , 4 / 3 ], which makes it unlikely that the system
%   Q x = q still has a solution. However, this makes sense only if Q is
%   rank-deficient, hence this is done only if Q is not strictly positive
%   definite (for otherwise the system always has a solution).
%
% Output:
%
% - Q: n \times n symmetric real matrix, which is either positive
%      (semi)definite, negative (semi)definite, or indefinite according
%      to the value of the conv parameter
%
% - q: n \times 1 real vector, optional
%
% - v: real, optional: the optimal value of the optimizattion problem
%      min{ f( x ) : x \in \R^n }. This is -\infty if Q is not positive
%      semidefinite (see conv above). It is -\infty even if Q is positive
%      semidefinite but rank deficient (see rank above) and the system
%      Q x + q = 0 has no solution. If instead Q is positive semidefinite
%      (whatever its rank) and the system Q x + q = 0 has a solution x_*,
%      then x_* is an optimal solution to the problem and
%
%        v = f( x_* ) = (1/2) x_*^T * Q * x_* + q * x_*
%          = (1/2) [ x_*^T * Q * x_* + q * x_* ] + (1/2) q * x_*
%          = (1/2) x_*^T ( Q * x_* + q * ) + (1/2) q * x_*
%          = (1/2) q * x_*
%
%{
 =======================================
 Author: Antonio Frangioni
 Date: 15-08-22
 Version 0.10
 Copyright Antonio Frangioni
 =======================================
%}

if ~ isscalar( n ) || ~ isreal( n )
   error( 'n not a real scalar' );
end
n = round( n );
if n <= 0
   error( 'n must be > 0' );
end

if isempty( varargin )
   seed = 0;
else
   seed = varargin{ 1 };
   if ~ isscalar( seed ) || ~ isreal( seed )
      error( 'actv not a real scalar' );
   end

   seed = round( seed );
end

if length( varargin ) > 1
   rank = varargin{ 2 };
   if ~ isscalar( rank ) || ~ isreal( rank )
      error( 'rank not a real scalar' );
   end

   if rank <= 0
      error( 'rank must be > 0' );
   end
else
   rank = 1.1;
end

if length( varargin ) > 2
   conv = varargin{ 3 };
   if ~ isscalar( conv ) || ~ isreal( conv )
      error( 'conv not a real scalar' );
   end

   if ( conv < 0 ) || ( conv > 1 )
      error( 'conv must be in [0, 1)' );
   end
else
   conv = 1;
end

if length( varargin ) > 3
   ecc = varargin{ 4 };
   if ~ isscalar( ecc ) || ~ isreal( ecc )
      error( 'ecc not a real scalar' );
   end

   if ecc < 0 || ecc >= 1
      error( 'ecc must be in [0, 1)' );
   end
else
   ecc = 0.99;
end

if length( varargin ) > 4
   dom = varargin{ 5 };
   if ~ isscalar( dom )
      error( 'dom not a scalar' );
   end

   if dom < 0
      error( 'dom must be >= 0' );
   end
else
   dom = 1;
end

if length( varargin ) > 5
   box = varargin{ 6 };
   if ~ isscalar( box )
      error( 'box not a scalar' );
   end

   if box == 0
      error( 'box must not be 0' );
   end
else
   box = 1;
end

rng( seed );

% generate Q- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
% first step: generate the appropriate rank and positive / negative
%             definiteness

r = round( rank * n );
p = round( r * conv );

if p > 0
   G = rand( p , n );
   Q = G' * G;
else
   Q = zeros( n , n );
end

if r > p
   G = rand( r - p , n );
   Q = Q - G' * G;
end

% second step: if dom ~= 1, modify the diagonal
% increase or decrease randomly each element by [ - 1/3 , 1/3 ] of its
% initial value, then multiply it by dom
if dom ~= 1
   D = diag( Q );
   D = D .* ( 1 + ( 2 * rand( n , 1 ) - 1 ) / 3 );
   D = dom * D;
   Q = spdiags( D , 0 , Q );
   Q = full( Q );
end

% compute eigenvalue decomposition
[ V , D ] = eig( Q );  % V * D * V' = Q , D( 1 , 1 ) = \lambda_n

if D( 1 , 1 ) > 1e-14
   % modify eccentricity only if \lambda_n > 0, for when \lambda_n = 0 the
   % eccentricity is 1 by default
   %
   % the formula is:
   %
   %                         \lambda_i - \lambda_n             2 * ecc
   % \lambda_i = \lambda_n + --------------------- * \lambda_n -------
   %                         \lambda_1 - \lambda_n             1 - ecc
   %
   % This leaves \lambda_n unchanged, and modifies all the other ones
   % proportionally so that
   %
   %   \lambda_1 - \lambda_n
   %   --------------------- = ecc      (exercise: check)
   %   \lambda_1 - \lambda_n

   d = diag( D );
   l = d( 1 ) * ones( n , 1 ) + ( d( 1 ) / ( d( n ) - d( 1 ) ) ) * ...
				( 2 * ecc / ( 1 - ecc ) ) * ...
				( d - d( 1 ) * ones( n , 1 ) );

   Q = V * diag( l ) * V';
end

if nargout > 0
   % if so required generate q- - - - - - - - - - - - - - - - - - - - - - -
   %
   % we first generate the unconstrained minimum x_* of the problem in the
   % box [ - abs( box ) , abs( box ) ] and then we set q = - Q * x_*

   x = 2 * abs( box ) * rand( n , 1 ) - abs( box );

   q = - Q * x;

   % if so required, we now randomly destroy the alignment between q and
   % the image of Q so as to make it hard to solve Q x = q

   if ( box < 0 ) && ( D( 1 , 1 ) <= 1e-14 )
      q = q .* ( ( 4 / 3 ) * rand( n , 1 ) - 2 / 3 );
   end

   varargout{ 1 } = q;

   if nargout > 1
      % if so required compute v. - - - - - - - - - - - - - - - - - - - - -
      % v is finite-valued only if either Q is strictly positive definite
      % or it is positive semidefinite but q has been constructed in such
      % a was that Q * x + q = 0 has a solution (that is the x we have)

      if ( D( 1 , 1 ) > 1e-14 ) || ...
	 ( ( D( 1 , 1 ) > -1e-14 ) && ( box > 0 ) )
	 v = q' * x / 2;
      else
	 v = - Inf;
      end

      varargout{ 2 } = v;
   end
end

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

end
