%SVO Support Vector Optimiser, low-level routine
%
%   [V,J,C,NU] = SVO(K,NLAB,C,OPTIONS)
%
% INPUT
%   K     Similarity matrix
%   NLAB  Label list consisting of -1/+1
%   C     Scalar for weighting the errors (optional; default: 1)
%   OPTIONS
%    .PD_CHECK          force positive definiteness of the kernel by adding a small constant 
%                       to a kernel diagonal (default: 1)
%    .BIAS_IN_ADMREG    it may happen that bias of svc (b term) is not defined, then 
%                       if BIAS_IN_ADMREG == 1, b will be taken from the midpoint of its admissible
%                       region, otherwise (BIAS_IN_ADMREG == 0) the situation will be considered 
%                       as an optimisation failure and treated accordingly (deafault: 1)
%    .PF_ON_FAILURE     if optimisation is failed (or bias is undefined and BIAS_IN_ADMREG is 0)
%                       and PF_ON_FAILURE == 1, then Pseudo Fisher classifier will be computed, 
%                       otherwise (PF_ON_FAILURE == 0) an error will be issued (default: 1)
%
% OUTPUT
%   V     Vector of weights for the support vectors
%   J     Index vector pointing to the support vectors
%   C     C which was actually used for optimisation
%   NU    NU parameter of NUSVC algorithm, which gives the same classifier
%
% DESCRIPTION
% A low level routine that optimises the set of support vectors for a 2-class
% classification problem based on the similarity matrix K computed from the
% training set. SVO is called directly from SVC. The labels NLAB should indicate 
% the two classes by +1 and -1. Optimisation is done by a quadratic programming. 
% If available, the QLD function is used, otherwise an appropriate Matlab routine.
%
% SEE ALSO (<a href="http://37steps.com/prtools">PRTools Guide</a>)
% SVC

% Copyright: D.M.J. Tax, D. de Ridder, R.P.W. Duin, duin@ph.tn.tudelft.nl
% Faculty of Applied Sciences, Delft University of Technology
% P.O. Box 5046, 2600 GA Delft, The Netherlands

% $Id: svo.m,v 1.6 2010/02/08 15:29:48 duin Exp $

function [v,J,C,nu] = svo(K,y,C,Options)

	  if nargin < 4
    Options = [];
  end
  
  DefOptions.pd_check       = 1;
  DefOptions.bias_in_admreg = 1;
  DefOptions.pf_on_failure  = 1;
  
  Options = updstruct(DefOptions, Options,1);

	if nargin < 3 || isempty(C)
		prwarning(3,'The regularisation parameter C is not specified, assuming 1.');
    C = 1;
	end

	vmin = 1e-9;		% Accuracy to determine when an object becomes the support object.

  vmin1 = min(1,C)*vmin; % controls if an object is the support object
  vmin2 = C*vmin;        % controls if a support object is the boundary support object

	% Set up the variables for the optimisation.
	n  = size(K,1);
	D  = (y*y').*K;
	f  = -ones(1,n);
	A  = y';
	b  = 0;
	lb = zeros(n,1);
	ub = repmat(C,n,1);
	p  = rand(n,1);
  D = (D+D')/2; % guarantee symmetry
	% Make the kernel matrix K positive definite.
	if Options.pd_check
    i = -30;
  	while (pd_check (D + (10.0^i) * eye(n)) == 0)
	  	i = i + 1;
	  end
	  if (i > -30),
	  	prwarning(3,'K is not positive definite. The kernel is regularised by adding 10.0^(%d)*I',i);
	  end
	  i = i+2;
	  D = D + (10.0^(i)) * eye(n);
  end

	% Minimisation procedure initialisation:
	% 'qp' minimises:   0.5 x' K x + f' x
	% subject to:       Ax <= b
	%
	if (exist('qld') == 3)
		v = qld (D, f, -A, b, lb, ub, p, 1);
  elseif isoctave
    %options.MaxIter = 3;
    %starttime = clock;
    %v = qp([],D, f, A, b, lb, ub,options);
    v = qp([],D, f, A, b, lb, ub);
%     runtime = etime(clock,starttime);
%     options.MaxIter = round(prtime*3/runtime)
%     disp(runtime)
%     v = qp(v,D, f, A, b, lb, ub,options);
%     disp(etime(clock,starttime))
	elseif (exist('quadprog') == 2)
		prwarning(2,'QLD not found, the Matlab routine QUADPROG is used instead.')
		opt = optimset; opt.LargeScale='off'; opt.Display='off';
		v = quadprog(D, f, [], [], A, b, lb, ub,[],opt);
	else 
		prwarning(2,'QLD not found, the Matlab routine QP is used instead.')
		verbos = 0;
		negdef = 0;
		normalize = 1;
		v = qp(D, f, A, b, lb, ub, p, 1, verbos, negdef, normalize);
	end

  try
    % check if the optimiser returned anything
    if isempty(v)
      error('Optimisation did not converge.');
    end

   % check if we don't have a zero-weight vector
   if var(K*v)<1e-5
      prwarning(3,'Output of the SVM is (almost) constant. Please increase C or rescale your data.');
   end
   
  	% Find all the support vectors.
	 J  = find(v > vmin1);
    Jp = J(y(J) ==  1);
    Jm = J(y(J) == -1);

    % Sanity check: there are support objects from both classes
    if isempty(J)
      error('There are no support objects.');
    elseif isempty(Jp)
      error('There are no support objects from the positive class.');
    elseif isempty(Jm)
      error('There are no support objects from the negative class.');
    end

    % compute nu parameter
	  nu = sum(v(J),1)/(C*n);	

   % Find the SV on the boundary
	  I = find((v > vmin1) & (v < C-vmin2));

    % Include class information into object weights
    v = y.*v;  

    % There are boundary support objects we can use them to find a bias term
    if ~isempty(I)
      b = mean(y(I)-K(I,J)*v(J));

    elseif Options.bias_in_admreg
      % There are no boundary support objects
      % We try to put the bias into the middle of admissible region

      % non SV
      J0    = (1:n)';
      J0(J) = [];
      J0p = J0(y(J0) ==  1);
      J0m = J0(y(J0) == -1);

      % Jp and Jm are all margin errors 
      lb = max(y([J0p;Jm]) - K([J0p;Jm],J)*v(J));
      ub = min(y([J0m;Jp]) - K([J0m;Jp],J)*v(J));

      if lb > ub
        error('The admissible region of the bias term is empty.');    
      end

      prwarning(3,['The bias term is undefined. The midpoint of its admissible region is used.']);    
      b = (lb+ub)/2;

    else
      error('The bias term is undefined.');
    end

	  v = [v(J); b];

  catch
    err.message = '##';
    lasterror(err);  % avoid problems with prwaitbar
    if Options.pf_on_failure
      prwarning(2,[lasterr ' Pseudo-Fisher is computed instead.']);
      n = size(K,1);
      %v = prpinv([K ones(n,1)])*y;
      v = prpinv([K ones(n,1); ones(1,n) 0])*[y; 0];
      J = [1:n]';
	    nu = nan;		  
    else
      rethrow(lasterror);
    end  
  end

return;
