function Result = utest3(hObject, varargin)
%UTEST3 3-steps wrapper for calling uncertain property testing
%
%  ObjectSamples = utest3(hObject, nSamples)
%  ObjectSamples = utest3(hObject, nSamples, VecObj)
%  CritSamples = utest3(hObject, hCrit, nSamples)
%  CritSamples = utest3(hObject, hCrit, nSamples, VecObj)
%  CritSamples = utest3(hObject, hCrit, nSamples, VecObj, VecCrit)
%  OverallResult = utest3(hObject, hCrit, hSecCrit, nSamples)
%  OverallResult = utest3(hObject, hCrit, hSecCrit, nSamples, VecObj, VecCrit)
%
%  Gathers object samples by hObject function, 
%  for each sample calculates value of criterion (by hCrit function)
%  finally checks all the criterion values with over-all function (by hSecCrit)
%  (the last one used for getting worst-case criterion (performance), for example)
%
%  instead of function handle hCrit there can be used built-in function names
%    'ident' - for case when sample of object returned is already value of criterion
%    'hurwitz' - check if sample returned by hObject is a Hurwitz polynomial
%    'schur' - check if sample returned by hObject is a Schur polynomial
%    'posdef' - check if sample returned is a positive definite matrix
%    'possemidef' - check if sample returned is a positive semi-definite matrix
%  
%  instead of function handle hSecCrit can be used built-in function names
%    'ident' - for case when sample of object returned is already value of criterion
%    'maxval' - for maximum value of criterion values (worst-case probability analysis)
%    'meanval' - for mean criterion values (probability calculation)
%    'nonzero' - for number of non-zero criterion values
% 
%  nSamples is the number of samples taken
%
%  VecXXX are flags letting know if corresponding funcion can accept cell array of samples
%
%   See also UBASE, ROOTS.

% $Id$

% parameter check
if ~any([nargin == 2, nargin == 3, nargin == 4, nargin == 5, nargin == 6])
  error(['RACT:utest3:InputSize', ['Wrong number of parameters: ' num2str(nargin)]]);
elseif ~isa(hObject, 'function_handle')
  error(['RACT:utest3:InputType', 'First parameter must be function handle!']);
end

% fill undefined parameters
switch nargin 
  case 2 % (hObject, nSamples)
  nSamples = varargin{1}; % second parameter
  VecObj = -1; % default value - autodetect via try...catch
  hCrit = 'ident';  % default value
  VecCrit = -1; % default autodetect value
  hSecCrit = 'ident'; % default value 
  case 3 % (hObject, hCrit, nSamples) or (hObject, nSamples, VecObj)
    if or(isa(varargin{1}, 'function_handle'), isa(varargin{1}, 'char')) % (hObject, hCrit, nSamples)
      nSamples = varargin{2}; % third parameter
      VecObj = -1; % default value - autodetect via try...catch
      hCrit = varargin{1};  % second parameter
      VecCrit = 1; % default value
      hSecCrit = 'ident'; % default value 
    elseif isa(varargin{1}, 'numeric') % (hObject, nSamples, VecObj)
      nSamples = varargin{1}; % second parameter
      VecObj = varargin{2};  % third parameter
      hCrit = 'ident';  % default value
      VecCrit = -1; % default autodetect value
      hSecCrit = 'ident'; % default value 
    end
  case 4 % (hObject, hCrit, hSecCrit, nSamples) or (hObject, hCrit, nSamples, VecObj)
    if or(isa(varargin{2}, 'function_handle'), isa(varargin{2}, 'char')) % (hObject, hCrit, hSecCrit, nSamples)
      nSamples = varargin{3}; % fourth parameter
      VecObj = -1; % default autodetect value
      hCrit = varargin{1};  % second parameter
      VecCrit = -1; % default autodetect value
      hSecCrit = varargin{2}; % third parameter 
    elseif isa(varargin{2}, 'numeric') % (hObject, hCrit, nSamples, VecObj)
      nSamples = varargin{2}; % third parameter
      VecObj = varargin{3}; % fourth parameter
      hCrit = varargin{1};  % second parameter
      VecCrit = -1; % default autodetect value
      hSecCrit = 'ident'; % default value 
    end
  case 5 % (hObject, hCrit, nSamples, VecObj, VecCrit) 
    nSamples = varargin{2}; % third parameter
    VecObj = varargin{3}; % fourth parameter
    hCrit = varargin{1};  % second parameter
    VecCrit = varargin{4}; % fifth parameter
    hSecCrit = 'ident'; % default value 
  otherwise % (hObject, hCrit, hSecCrit, nSamples, VecObj, VecCrit) - full set
    nSamples = varargin{3}; % forth parameter
    VecObj = varargin{4}; % fifth parameter
    hCrit = varargin{1};  % second parameter
    VecCrit = varargin{5}; % sixth parameter
    hSecCrit = varargin{2}; % third parameter 
end

if ~isa(hCrit, 'function_handle')
  if isa(hCrit, 'char') % check if second parameter a name of built-in criterion
    switch hCrit
      case 'ident'
       hCrit = @ident;
       VecCrit = 1; 
      case 'hurwitz'
        hCrit = @ishurwitz;
        VecCrit = 1; 
      case 'schur'
        hCrit = @isschur;
        VecCrit = 1; 
      case 'posdef'
        hCrit = @isposdef;
        VecCrit = 1; 
      case 'possemidef'
        hCrit = @ispossemidef;
        VecCrit = 1; 
      otherwise
        error(['RACT:utest3:InputType', ['Unknown built-in criterion name: ' hCrit]]);
    end
  else
    error(['RACT:utest3:InputType', 'Second parameter must be function handle or built-in criterion name!']);
  end
end

if ~isa(hSecCrit, 'function_handle')
  if isa(hSecCrit, 'char') % check if second parameter a name of built-in criterion
    switch hSecCrit
      case 'ident'
        hSecCrit = @ident;
      case 'maxval'
        hSecCrit = @maxval; % use internal function
      case 'meanval'
        hSecCrit = @meanval; % use internal function
      case 'nonzero'
        hSecCrit = @nonzero;
      otherwise
        error(['RACT:utest3:InputType', ['Unknown built-in criterion name: ' hSecCrit]]);
    end
  else
    error(['RACT:utest3:InputType', 'Third parameter must be function handle or built-in criterion name!']);
  end
end

if ~(nSamples > 0)
  error(['RACT:utest3:InputType', ['Invalid number of samples: ' num2str(nSamples)]]);
end

% get samples of user-defined objects as array of cells
uObjects = uevaluate(hObject, nSamples, VecObj, 'cell');

% vectorization for criterion
if VecCrit == 1 
  CritVals = feval(hCrit, uObjects);
elseif VecCrit == 0
  CritVals = cell(size(uObjects));
  for k = 1:nSamples
    CritVals{k} = feval(hCrit, uObjects{k});
  end
elseif VecCrit == -1 % autodetect
  try
    CritVals = feval(hCrit, uObjects);
  catch
    CritVals = cell(size(uObjects));
    for k = 1:nSamples
      CritVals{k} = feval(hCrit, uObjects{k});
    end
  end
end

if isa(hSecCrit, 'char')
  if strcmp(hSecCrit, 'ident')
    Result = CritVals;
    return;
  end
end

% SecCrit should allow input, packed in cells
Result = feval(hSecCrit, CritVals);

return;
%% END OF THE FUNCTION

% Some built-in functions:
% 1 - 'ident'
% just return the argument
function CritVals = ident(Var)
CritVals = Var;

% 2 - 'hurwitz'
% check if polynomial(s) is Hurwitz
function CritVals = ishurwitz(Var)
CritVals = cell(size(Var));
N0 = max(size(Var));
N = length(Var{1}); % N = n + 1
N2 = ceil(N/2); % (n+1)/2 for odd degree, n/2 + 1 for even
N_add = N-2*N2; % for even/odd degree diff: = -1 if N is odd
for n=1:N0
  p = Var{n}; % should be row to the Routh algorihtm
	% make first coefficient positive
	if p(1) < 0 
    p = -p;
	end
	% neccessary condition check
	if ~all(p > 0)
    CritVals{n} = 0;
    continue;
	end
	% full table 
	T = zeros(N, N2);
	T(1, 1:N2) = p(1:2:end);
	T(2, 1:N2+N_add) = p(2:2:end+N_add); % N-2*N2 
	for k=3:N
    if T(k-1, 1) <= 0 % a zero or negative value is found in first line
      CritVals{n} = 0; 
      continue;
    else
     r = T(k-2, 1)/T(k-1, 1);
    end
    for m = 1:N2-1
      T(k, m) = T(k-2, m+1) - r*T(k-1, m+1);
    end
	end
  CritVals{n} = (T(N, 1) > 0); % other elems are already checked
%   if max(real(roots(Var{k}))) < 0 - old straightforward realization
%     CritVals{k} = 1;
%   else
%     CritVals{k} = 0;
%   end
end

% 3 - 'schur'
function CritVals = isschur(Var)
CritVals = cell(size(Var));
N0 = max(size(Var));
N = size(Var{1}, 2);
for k=1:N0
  CritVals{k} = 1;
  p = Var{k}; % should be row to the Routh algorihtm
  T1 = p;
  T2 = conj(fliplr(p)); % z^n conj(P(1/z))
  for n=N:-1:2
    if abs(T1(end)) >= abs(T1(1));
      CritVals{k} = 0;
      break;
    else
      r = T1(end)/conj(T1(1));
      T1 = T1 - r*T2;
      T1 = T1(1:end-1); % free element should be zero 
      T2 = conj(fliplr(T1)); % z^n conj(P(1/z))
    end
  end
%  if max(abs(roots(Var{k}))) < 1 - old straightforward realization
%    CritVals{k} = 1;
%  else
%    CritVals{k} = 0;
%  end
end

% 4 - 'posdef'
function CritVals = isposdef(cellA)
% check if matrix A is positive definite
Eps1 = 1e-8;
Eps2 = 1e-7;
if size(cellA{1},1) ~= size(cellA{1},2)
  error(['RACT:utest3:posdef:InputType', 'The argument should be a square matrix cell array.']);
end
CritVals = cell(size(cellA));
N = max(size(cellA));
for k=1:N
  A = cellA{k};
  if norm(A - A', 'fro') > Eps1
    warning('RACT:utest3:posdef:InputType', 'The matrix A should be symmetric (or ... for complex?AT? ).');
  end
  T = schur(A); % A = U*T*U.' decomposition
  t = diag(T);
  if all(t >= Eps2)
    CritVals{k} = 1;
  else
    CritVals{k} = 0;
  end
end

% 5 - 'possemidef'
function CritVals = ispossemidef(cellA)
% check if matrix A is positive semi-definite
Eps1 = 1e-8;
if size(cellA{1},1) ~= size(cellA{1},2)
  error(['RACT:utest3:possemidef:InputSize', 'The argument should be a square matrix cell array.']);
end
CritVals = cell(size(cellA));
N = max(size(cellA));
for k=1:N
  A = cellA{k};
  if norm(A - A', 'fro') > Eps1
    warning('RACT:utest3:possemidef:InputSize', 'The matrix A should be symmetric (or ... for complex?AT? ).');
  end
  T = schur(A); % A = U*T*U.' decomposition
  t = diag(T);
  if all(t >= 0)
    CritVals{k} = 1;
  else
    CritVals{k} = 0;
  end
end

% auxilary - if numeric content of cell array is a-priori known, 
% convert to numeric matrix. No error check.
function NumArray = cell2num(CellArray)
NumArray = zeros(size(CellArray));
for k=1:max(size(NumArray))
  NumArray(k) = CellArray{k};
end

% 6 - 'maxval'
function MaxVal = maxval(Var)
MaxVal = max(cell2num(Var));

% 7 - 'meanval'
function Mean = meanval(Var)
Mean = mean(cell2num(Var));

% 8 - 'nonzero'
function NZ = nonzero(Var)
NZ = sum(cell2num(Var) ~= 0);
