function [F,h,failure] = robustify(F,h,ops,w)
%ROBUSTIFY  Derives robust counterpart.
%
% [Frobust,objrobust,failure] = ROBUSTIFY(F,h,options) is used to derive
% the robust counterpart of an uncertain YALMIP model.
%
%   min        h(x,w)
%   subject to
%           F(x,w) >(=) 0  for all w in W
%
% The constraints and objective have to satisfy a number of conditions for
% the robustification to be possible. Please refer to the YALMIP Wiki for
% the current assumptions.
%
% Some options for the robustification strategies can be altered via the
% solver tag 'robust' in sdpsettings
%
%  'robust.lplp'  : Controls how linear constraints with affine
%                   parameterization in an uncertainty with polytopic
%                   description is handled. Can be either 'duality' or
%                   'enumeration' 
%
%  'robust.auxred': Controls how uncertainty dependent auxiliary variables
%                   are handled
%                   Can be either 'projection' or 'enumeration' (exact),
%                   or 'none' or 'affine' (conservative)
%
%  'robust.reducedual' Controls if the system equality constraints derived
%                   when using the duality filter should be eliminated,
%                   thus reducing the number of variables, possibly
%                   destroying sparsity .
%
%  'robust.polya'  : Controls the relaxation order of polynomials. If set to
%                   NAN, the polynomials will be eliminated by forcing the
%                   coefficients to zero
%
% See also UNCERTAIN

% Author Johan Löfberg
% $Id: robustify.m,v 1.55 2010-03-10 15:19:05 joloef Exp $

failure = 0;
if nargin < 3
    ops = sdpsettings;
elseif isempty(ops)
    ops = sdpsettings;
end

if nargin < 4
    w = [];
end

if nargin>1
    if isa(h,'double')
        h = [];
    end
else
    h = [];
end

% We keep track of auxilliary generated variables
nInitial = yalmip('nvars');

% Find the scenario, extract uncertainty model and classifiy variables
[UncertainModel,Uncertainty,VariableType,ops] = decomposeUncertain(F,h,w,ops);
x = VariableType.x;
w = VariableType.w;


% FIXME: SYNC with expandmodel?
if ~isempty(UncertainModel.F_x)
    nv = yalmip('nvars');
    yalmip('setbounds',1:nv,repmat(-inf,nv,1),repmat(inf,nv,1));
    LU = getbounds(UncertainModel.F_x);
    yalmip('setbounds',1:nv,LU(:,1),LU(:,2));
end
        
% At this point, we have to decide on the algorithm we should use for
% robustifying the constraints. There are a couple of alternatives,
% depending on uncertainty and constraints
% 1. Polya:       Polynomial uncertainty dependence, simplex uncertainty,
%                 can only be applied on LP constraints
% 2. Elimination: Last resort, tries to cancel all nonlinear uncertainties
%                 by setting coefficients to zero
% 3. Explicit:    Linear uncertainty dependence, box-model uncertainty, can
%                 only be applied on LP constraints
% 4. Enumeration: Linear uncertainty dependence, polytopic uncertainty,
%                 arbitrary type of constraints (convex)
% 5. Duality:     Linear uncertainty dependence, conic uncertainty, can
%                 only be applied on LP constraints
% 6. S-procedure  Special case, quadratic dependence in elementwise, one
%                 quadratic constraint in W

% Robust model
F_robust = set([]);

% We begin by checking to see if the user wants to apply Polyas theorem.
% If that is the case, search for simplex structures, and apply Polyas.
if ~isnan(ops.robust.polya) & any(strcmp(Uncertainty.uncertaintyTypes,'simplex')) & ~ops.robust.forced_enumeration
    F_polya = [];
    % Recursively apply Polya relaxation w.r.t each simplex
    for i = find(strcmp(Uncertainty.uncertaintyTypes,'simplex'))
        [UncertainModel.F_xw, F_polya] = filter_polya(UncertainModel.F_xw+F_polya,w(Uncertainty.uncertaintyGroups{i}),ops.robust.polya);
    end
    [UncertainModel.F_xw,F_robust] = pruneCertain(F_polya,F_robust,UncertainModel.F_xw,w);
end

% LP constraints with quadratic dependence and quadratic uncertainty region
% can be handled tightly using the S-procedure
if (all(strcmp(Uncertainty.uncertaintyTypes,'2-norm')) | all(strcmp(Uncertainty.uncertaintyTypes,'quadratic'))) & length(Uncertainty.uncertaintyTypes)==1 & ~ops.robust.forced_enumeration
    [UncertainModel.F_xw,F_sprocedure] = filter_sprocedure(UncertainModel.F_xw,w,Uncertainty.separatedZmodel,ops);
    F_robust = F_robust + F_sprocedure;
end

% There might still be nonlinearities left in the model. These have to be
% removed. We remove all terms with w-degree larger than 1
[UncertainModel.F_xw,F_elimination] = filter_eliminatation(UncertainModel.F_xw,w,1,ops);
F_robust = F_robust + F_elimination;

% Equality constraints cannot be part of an uncertain problem. Any
% dependence w.r.t w in equalities has to be removed
F_eq = extractConstraints(UncertainModel.F_xw,'equality');
UncertainModel.F_xw = UncertainModel.F_xw - F_eq;
[F_eq_left,F_eliminate_equality] = filter_eliminatation(F_eq,w,0,ops);
F_robust = F_robust + F_eliminate_equality + F_eq_left;

% The problem should now be linear in the uncertainty, with no uncertain
% equality constraints. Hence, now we apply explicit maximization,
% enumeration or duality-based robustification.

% We start with the norm balls
if ~ops.robust.forced_enumeration
    for i = 1:length(Uncertainty.uncertaintyTypes)
        if ismember(Uncertainty.uncertaintyTypes{i},{'1-norm','2-norm','inf-norm'})
            F_lp = extractConstraints(UncertainModel.F_xw,'elementwise');
            UncertainModel.F_xw = UncertainModel.F_xw - F_lp;
            F_flt = filter_normball(F_lp,Uncertainty.separatedZmodel{i},x,w(Uncertainty.uncertaintyGroups{i}),w,Uncertainty.uncertaintyTypes{i},ops,VariableType);
            [UncertainModel.F_xw,F_robust] = pruneCertain(F_flt,F_robust,UncertainModel.F_xw,w);
        end
    end
end

% Pick out the uncertain linear equalities and robustify using duality if
% user has opted for this or the uncertainty is conic.
conic = ~isequal(Uncertainty.Zmodel.K.s,0) | ~isequal(Uncertainty.Zmodel.K.q,0);
if (conic | isequal(ops.robust.lplp,'duality')) & ~ops.robust.forced_enumeration
    F_lp = extractConstraints(UncertainModel.F_xw,'elementwise');
    UncertainModel.F_xw = UncertainModel.F_xw - F_lp;
    nv = yalmip('nvars');
    F_filter = filter_duality(F_lp,Uncertainty.Zmodel,x,w,ops);
    F_robust = F_robust + F_filter;
    if isa(F_filter,'lmi') & ops.verbose
        newvars = nnz(getvariables(F_filter)>nv);      
        disp([' - Duality introduced ' num2str(newvars) ' variables, ' num2str(nnz(is(F_filter,'equality'))) ' equalities, ' num2str(nnz(is(F_filter,'elementwise'))) ' LP inqualities and ' num2str(nnz(is(F_filter,'sdp'))+nnz(is(F_filter,'socp'))) ' conic constraints']);       
    end
end

% Robustify remaining uncertain LP/SOCP/SDP constraints and robustify by
% enumeration.
F_conic = extractConstraints(UncertainModel.F_xw,{'sdp','socc','elementwise'});
UncertainModel.F_xw = UncertainModel.F_xw - F_conic;
[F_temp,enumerationfailed] = filter_enumeration(F_conic,Uncertainty.Zmodel,x,w,ops,Uncertainty.uncertaintyTypes,Uncertainty.separatedZmodel,VariableType);
if enumerationfailed
    % Reset to previous state
    UncertainModel.F_xw = UncertainModel.F_xw + F_conic;
else
    F_robust = F_robust + F_temp;
end

if enumerationfailed
    % Enumeration failed, probably due to lack of MPT. If problem is conic,
    % we are in trouble. If simple LP, we can resort to duality approach
    if conic
        if ops.verbose
            disp(' - Enumeration of uncertainty polytope failed. Missing Multiparametric Toolbox?')
        end
        error('Enumeration failed (lacking MPT?),  and due to conic constraints, duality cannot be used');
    else
        F_lp = extractConstraints(UncertainModel.F_xw,'elementwise');
        UncertainModel.F_xw = UncertainModel.F_xw - F_lp;
        nv = yalmip('nvars');
        F_filter = filter_duality(F_lp,Uncertainty.Zmodel,x,w,ops);
        if ops.verbose
            if isa(F_filter','lmi')
             disp([' - Duality introduced ' num2str(yalmip('nvars')-nv') ' variables, ' num2str(nnz(is(F_filter,'equality'))) ' equalities, ' num2str(nnz(is(F_filter,'elementwise'))) ' LP inqualities and ' num2str(nnz(is(F_filter,'sdp'))+nnz(is(F_filter,'socp'))) ' conic constraints']);
            end
        end
        F_robust = F_robust + F_filter;
    end
end

% If there is anything left now, it means that we do not support it (such
% as conic uncertainty in conic constraint)
if length(UncertainModel.F_xw) > 0
    if any(~islinear(UncertainModel.F_xw))
        error('There are some uncertain constraints which cannot be robustified by YALMIP')
    else
        F_robust = F_robust + UncertainModel.F_xw;
    end
end

% Return the robustfied model
F = F_robust+UncertainModel.F_x;
h = UncertainModel.h;

% The model has been expanded, so we have to remember this (trying to
% expand an expanded model leads to nonconvexity error)
F = expanded(F,1); % This is actually done already in expandmodel
h = expanded(h,1); % But this one has to be done manually

nNow = yalmip('nvars');
if nNow > nInitial
    % YALMIP has introduced auxilliary variables
    % We mark these as auxilliary
    yalmip('addauxvariables',nInitial+1:nNow);
end

if ops.verbose
    disp('***** Derivation of robust counterpart done ***********************');
end


function [F_xw,F_robust] = pruneCertain(F_new,F_robust,F_xw,w);
for i = 1:length(F_new)
    if ~isempty(intersect(depends(F_new(i)),depends(w)))
        F_xw = F_xw + F_new(i);
    else
        F_robust = F_robust + F_new(i);
    end
end

function p = indexIn(x,y)
if ~isempty(x)
    for i = 1:length(x)
        p(i) = find(x(i)==y);
    end
else
    p = [];
end

function [F_x,F_w,F_xw,VariableType] = partitionModel(F,F_original,VariableType);

F_x = [];
F_w = [];
F_xw = [];

% x-var w_var aux_xw aux_w
if ~(isempty(VariableType.aux_with_w_dependence) & isempty(VariableType.aux_with_only_w_dependence))
    Dependency = spalloc(length(F_original),4,length(F));
    
    for i = 1:length(F_original)
        varF = depends(F_original(i));
        Dependency(i,1) = any(ismember(varF,VariableType.x_variables));
        Dependency(i,2) = any(ismember(varF,VariableType.w_variables));
        Dependency(i,3) = any(ismember(varF,VariableType.aux_with_w_dependence));
        Dependency(i,4) = any(ismember(varF,VariableType.aux_with_only_w_dependence));
    end
    
    LiftedUncertaintiesDescription = find(Dependency(:,1) == 0 & Dependency(:,3)==0);
    if ~isempty(LiftedUncertaintiesDescription)
        %     for i = LiftedUncertaintiesDescription(:)'
        %        vars = depends(F_original(i));
        %        vars = intersect(vars,VariableType.aux_with_only_w_dependence);
        %
        %     end
        reclassifyAsUncertain = depends(F_original(LiftedUncertaintiesDescription));
        [notused,reclassifyAsUncertain] = find(VariableType.Graph(reclassifyAsUncertain,:));
        reclassifyAsUncertain = unique(reclassifyAsUncertain);
        reclassifyAsUncertain = intersect(unique(reclassifyAsUncertain),getvariables(F));
        VariableType.aux_with_only_w_dependence = setdiff(VariableType.aux_with_only_w_dependence,reclassifyAsUncertain);
        VariableType.w_variables = union(VariableType.w_variables,reclassifyAsUncertain);
        
        VariableType.aux_with_w_dependence = union(VariableType.aux_with_w_dependence,VariableType.aux_with_only_w_dependence);
        VariableType.aux_with_only_w_dependence = [];
    end
end


% x-var w_var aux_xw aux_w
Dependency = spalloc(length(F),4,length(F));

for i = 1:length(F)
    varF = depends(F(i));
    Dependency(i,1) = any(ismember(varF,VariableType.x_variables));
    Dependency(i,2) = any(ismember(varF,VariableType.w_variables));
    Dependency(i,3) = any(ismember(varF,VariableType.aux_with_w_dependence));
    Dependency(i,4) = any(ismember(varF,VariableType.aux_with_only_w_dependence));
end

pureX = find(Dependency*[1;2;4;8] == 1);
pureW = find(Dependency(:,1) == 0 & Dependency(:,3)==0);
mixedXW = find(Dependency(:,1) | Dependency(:,3));
%mixedXW = find((Dependency(:,1) &  | Dependency(:,3));
mixedXW = setdiff(1:size(Dependency,1),union(pureW,pureX));

F_x = F(pureX);
F_w = F(pureW);
F_xw = F(mixedXW);

reclassifyAsUncertain = depends(F_w);
VariableType.aux_with_only_w_dependence = setdiff(VariableType.aux_with_only_w_dependence,reclassifyAsUncertain);
VariableType.w_variables = union(VariableType.w_variables,reclassifyAsUncertain);

function [VariableType,h_fixed,F_xw] = reformatObjective(h,F_xw,VariableType)
% Some pre-calc
x = recover(VariableType.x_variables);
w = recover(VariableType.w_variables);
xw = [x;w];
xind = find(ismembc(getvariables(xw),getvariables(x)));
wind = find(ismembc(getvariables(xw),getvariables(w)));
% Analyze the objective and try to rewrite any uncertainty into the format
% assumed by YALMIP
if ~isempty(h)
    
    [Q,c,f,dummy,nonquadratic] = vecquaddecomp(h,xw);
    Q = Q{1};
    c = c{1};
    f = f{1};
    
    if nonquadratic
        error('Objective can be at most quadratic, with the linear term uncertain');
    end
    
    Q_ww = Q(wind,wind);
    Q_xw = Q(xind,wind);
    Q_xx = Q(xind,xind);
    c_x = c(xind);
    c_w = c(wind);
    
    if nnz(Q_ww) > 0
        error('Objective can be at most quadratic, with the linear term uncertain');
    end
    % Separate certain and uncertain terms, place uncertain terms in the
    % constraints instead
    if is(h,'linear')
        if isempty(intersect(getvariables(w),getvariables(h)))
            h_fixed = h;
        else
            sdpvar t
            F_xw = F_xw + set(h < t);
            h_fixed = t;
            x = [x;t];
        end
    else
        h_fixed     = x'*Q_xx*x + c_x'*x + f;
        h_uncertain = 2*w'*Q_xw'*x + c_w'*w;
        if ~isa(h_uncertain,'double')
            sdpvar t
            F_xw = F_xw + set(h_uncertain < t);
            h_fixed = h_fixed + t;
            x = [x;t];
        end
    end
else
    h_fixed = [];
end
VariableType.x_variables = getvariables(x);