function output = mpcvx(p)
%BMIBNB          Branch-and-bound scheme for bilinear programs
%
% BMIBNB is never called by the user directly, but is called by 
% YALMIP from SOLVESDP, by choosing the solver tag 'bmibnb' in sdpsettings
%
% The behaviour of BMIBNB can be altered using the fields
% in the field 'bmibnb' in SDPSETTINGS
%
% WARNING: THIS IS EXPERIMENTAL CODE
%
% bmibnb.lowersolver    - Solver for lower bound [standard solver tag ('')]
% bmibnb.uppersolver    - Solver for upper bound [standard solver tag ('')]
% bmibnb.lpsolver       - Solver for LP bound tightening [standard solver tag ('')]
% bmibnb.branchmethod   - Branch strategy ['maxvol' | 'best' ('best')]
% bmibnb.branchrule     - Branch position ['omega' | 'bisect' ('omega')]
% bmibnb.lpreduce       - Improve variable bounds using LP [ real [0,1] (0 means no reduction, 1 means all variables)
% bmibnb.lowrank        - partition variables into two disjoint sets and branch on smallest [ 0|1 (0)]
% bmibnb.target         - Exit if upper found<target [double (-inf)]
% bmibnb.roottight      - Improve variable bounds in root using full problem [ 0|1 (1)]
% bmibnb.vartol         - Cut tree when x_U-x_L < vartol on all branching variables
% bmibnb.relgaptol      - Tolerance on relative objective error (UPPER-LOWER)/(1+|UPPER|) [real (0.01)]
% bmibnb.absgaptol      - Tolerance on objective error (UPPER-LOWER) [real (0.01)]
% bmibnb.pdtol          - A number is declared non-negative if larger than...[ double (-1e-6)]
% bmibnb.eqtol          - A number is declared zero if abs(x) smaller than...[ double (1e-6)]
% bmibnb.maxiter        - Maximum number of solved nodes [int (100)]
% bmibnb.maxtime        - Maximum CPU time (sec.) [int (3600)]

% Author Johan Löfberg 
% $Id: callmpcvx.m,v 1.2 2005/05/07 13:53:20 joloef Exp $

% ********************************
% INITIALIZE DIAGNOSTICS IN YALMIP
% ********************************
bnbsolvertime = clock; 
showprogress('Branch and bound started',p.options.showprogress);

% *******************************
% Display-logics
% *******************************
switch max(min(p.options.verbose,3),0)
    case 0
        p.options.bmibnb.verbose = 0;
    case 1
        p.options.bmibnb.verbose = 1;
        p.options.verbose = 0;
    case 2
        p.options.bmibnb.verbose = 2;
        p.options.verbose = 0;
    case 3
        p.options.bmibnb.verbose = 2;
        p.options.verbose = 1;
    otherwise 
        p.options.bmibnb.verbose = 0;
        p.options.verbose = 0;    
end

% *******************************
% Actual linear variables
% *******************************
p.linears = find(sum(p.monomtable,2)==1);

% *******************************
% PRE-CALCULATE INDICIES
% FOR BILNEAR VARIABLES
% *******************************
nonlinear = find(~(sum(p.monomtable~=0,2)==1 & sum(p.monomtable,2)==1));
nonlins   = [];
for i = 1:length(nonlinear)
    z = find(p.monomtable(nonlinear(i),:));
    if length(z)==1
        nonlins = [nonlins;nonlinear(i) z z];
    else
        nonlins = [nonlins;nonlinear(i) z(1) z(2)];
    end
end
p.nonlins = nonlins;

p.options.saveduals = 0;

% *******************************
% Select branch variables
% *******************************
p.branch_variables = decide_branch_variables(p);

% ********************************
% Extract bounds from model
% ********************************
if isempty(p.ub)
    p.ub = repmat(inf,length(p.c),1);
end
if isempty(p.lb)
    p.lb = repmat(-inf,length(p.c),1);
end
if ~isempty(p.F_struc)
    [lb,ub,used_rows] = findulb(p.F_struc,p.K);
    if ~isempty(used_rows)
        lower_defined = find(~isinf(lb));
        if ~isempty(lower_defined)
            p.lb(lower_defined) = max(p.lb(lower_defined),lb(lower_defined));
        end
        upper_defined = find(~isinf(ub));
        if ~isempty(upper_defined)
            p.ub(upper_defined) = min(p.ub(upper_defined),ub(upper_defined));
        end   
        % Remove linear bounds
        used_rows = used_rows(find(~any(p.F_struc(p.K.f+used_rows,1+nonlinear),2)));
        not_used_rows = setdiff(1:p.K.l,used_rows);
        for i = 1:length(p.KCut.l)
            p.KCut.l(i) = find(not_used_rows==p.KCut.l(i) );
        end
        if ~isempty(used_rows)
            p.F_struc(p.K.f+used_rows,:)=[];
            p.K.l = p.K.l - length(used_rows);
        end       
    end
end
p = clean_bounds(p);
p = updatenonlinearbounds(p);
feasible = all(p.lb<=p.ub);

% ********************************
% Remove empty linear rows
% ********************************
if p.K.l > 0
    empty_rows = find(~any(p.F_struc(p.K.f+1:p.K.f+p.K.l,2:end),2));
    if ~isempty(empty_rows)
        if all(p.F_struc(p.K.f+empty_rows,1)>=0)            
            p.F_struc(p.K.f+empty_rows,:)=[];
            p.K.l = p.K.l - length(empty_rows);         
        else
            feasible = 0;
        end
    end
end

% ********************************
% Tighten bounds at root
% ********************************
if p.options.bmibnb.roottight & feasible
    lowersolver = eval(['@' p.solver.lowercall]);
    c = p.c;
    Q = p.Q;
    mt = p.monomtable;
    p.monomtable = eye(length(c));
    i = 1;
    while i<=length(p.linears) & feasible   
        j = p.linears(i);  
        p.c = eyev(length(p.c),j);
        output = feval(lowersolver,p);  
        if (output.problem == 0) & (output.Primal(j)>p.lb(j))
            p.lb(j) = output.Primal(j);  
            p = updateonenonlinearbound(p,j);
            p = clean_bounds(p);
        end
        if output.problem == 1
            feasible = 0;
        else
            % p = updatenonlinearbounds(p,0,1);
            p.c = -eyev(length(p.c),j);
            output = feval(lowersolver,p);
            if (output.problem == 0) & (output.Primal(j) < p.ub(j))
                p.ub(j) = output.Primal(j);
                p = updateonenonlinearbound(p,j);
                p = clean_bounds(p);
            end    
            if output.problem == 1
                feasible = 0;
            end             
            i = i+1;
        end
    end
    p.lb(p.lb<-1e10) = -inf;
    p.ub(p.ub>1e10) = inf;
    p.c = c;
    p.Q = Q;
    p.monomtable = mt;
end

if feasible
    
    % *******************************
    % Bounded domain?
    % *******************************
    involved = unique(p.nonlins(:,2:3));
    if isinf(p.lb(involved)) | isinf(p.ub(involved))    
        error('You have to bound all complicating variables explicitely (I cannot deduce bounds on all variables)')
        output.Primal = [];
        output.problem = -1;
    end
        
    % *******************************
    % We don't need to save node data
    % *******************************
    p.options.savesolverinput  = 0;
    p.options.savesolveroutput = 0;
    
    % *******************************
    % RUN BRANCH & BOUND
    % *******************************
    [x_min,solved_nodes,lower,upper] = branch_and_bound(p);
    
    % **********************************
    % CREATE SOLUTION
    % **********************************
    output.problem = 0;
    if isinf(upper)
        output.problem = 1;
    end
    if isinf(-lower)
        output.problem = 2;
    end
    if solved_nodes == p.options.bnb.maxiter
        output.problem = 3;
    end
else
    output.problem = 1;
    x_min = repmat(nan,length(p.c),1);
    solved_nodes = 0;
end

output.solved_nodes = solved_nodes;
output.Primal      = x_min;
output.Dual        = [];
output.Slack       = [];
output.infostr      = yalmiperror(output.problem,'BNB');
output.solverinput  = 0;
output.solveroutput =[];
output.solvertime   = etime(clock,bnbsolvertime);

function [x_min,solved_nodes,lower,upper] = branch_and_bound(p)

% ***************************************
% LPs ARE USED IN  BOX-REDUCTION 
% (this is essentially a cutting plane pool)
% ***************************************
p.lpcuts = p.F_struc(1+p.K.f:1:p.K.l,:);

% ***************************************
% Create function handles to solvers
% ***************************************
try
    lowersolver = eval(['@' p.solver.lowercall]); % Local LMI solver
    uppersolver = eval(['@' p.solver.uppercall]); % Local BMI solver
    lpsolver    = eval(['@' p.solver.lpcall]);    % LP solver
catch
    disp(' ');
    disp('The internal branch & bound solver requires MATLAB 6')
    disp('I am too lazy too do the changes to make it compatible')
    disp('with MATLAB 5. If you really need it, contact me...');
    disp(' ');    
    error(lasterr);
end

% ************************************************
% GLOBAL PROBLEM DATA
% ************************************************
c       = p.c;
Q       = p.Q;
f       = p.f;
K       = p.K;
p.options.saveduals = 0;
options = p.options;

% ************************************************
% ORIGINAL PROBLEM (used in LOCAL BMI solver)
% ************************************************
p_upper = p;

% ************************************************
% Remove linear cutting planes from problem
% ************************************************
p_upper.F_struc(p_upper.K.f+p_upper.KCut.l,:)=[];
p_upper.K.l = p_upper.K.l - length(p_upper.KCut.l);

% ************************************************
% Remove sdp cutting planes from problem
% ************************************************
if length(p_upper.KCut.s)>0
    starts = p_upper.K.f+p_upper.K.l + [1 1+cumsum((p_upper.K.s).^2)];
    remove_these = [];
    for i = 1:length(p_upper.KCut.s)
        j = p_upper.KCut.s(i);
        remove_these = [remove_these;(starts(j):starts(j+1)-1)'];
    end
    p_upper.F_struc(remove_these,:)=[];
    p_upper.K.s(p_upper.KCut.s) = [];
end

% ************************************************
% INITILIZATION
% ************************************************
p.depth = 0;
p.dpos = 0; 
p.lower = NaN;
upper   = inf;
lower   = NaN;
gap = inf;
x_min   = zeros(length(p.c),1);
stack   = [];
solved_nodes = 0;
solved_lower = 0;
solved_upper = 0;
solved_lp = 0;

if isempty(p.x0)
    p.x0    = zeros(length(p.c),1);
end

x0 = evaluate_nonlinear(p,p.x0);
upper_residual = resids(p,x0);
x0_feasible = all(upper_residual(1:p.K.f)>=-options.bmibnb.eqtol) & all(upper_residual(1+p.K.f:end)>=options.bmibnb.pdtol);
if p.options.usex0 & x0_feasible
    x_min = x0;
    upper = p.f+p.c'*x0+x0'*Q*x0;
end

% ************************************************
% Branch & bound loop
% ************************************************
if options.bmibnb.verbose>0
    fprintf('******************************************************************************************************************\n')
    fprintf('#node          Was'' up                         gap      upper      node    lower  dpth  stk     Memory Vol-red\n')
    fprintf('******************************************************************************************************************\n')
end

doplot = 0;
if doplot
    close all;
    hold on;
end

t_start = cputime;
go_on  = 1;
while go_on
    
    if doplot;ellipplot(diag([200 50]),1,'y',[p.dpos;-p.depth]);drawnow;end;
    % ********************************************
    % ASSUME THAT WE WON'T FATHOME
    % ********************************************
    keep_digging = 1;
    % ********************************************
    % REDUCE BOX
    % ******************************************** 
    if ~options.bmibnb.lpreduce
        % [p.lb,p.ub] = tightenbounds(-p.F_struc(1+p.K.f:p.K.f+p.K.l,2:end),p.F_struc(1+p.K.f:p.K.f+p.K.l,1),p.lb,p.ub,[]);  
        vol_reduction = 1;
        feasible = 1;
    else     
        [p,feasible,vol_reduction] =  boxreduce(p,upper,lower,lpsolver,options);  
    end
    
    % ********************************************
    % SOLVE LOWER AND UPPER
    % ********************************************
    if feasible               
        
        output = solvelower(p,options,lowersolver); 
        
        info_text = '';
        switch output.problem
            case 1
                if doplot;ellipplot(diag([200 25]),1,'r',[p.dpos;-p.depth]);drawnow;end;
                info_text = 'Infeasible node';
                keep_digging = 0;
                cost = inf;
                feasible = 0;
                
            case 2
                cost = -inf;
                
            case {0,3,4}
                
                x = output.Primal; 
                
                cost = f+c'*x+x'*Q*x;
                
                z = evaluate_nonlinear(p,x);
                p = addsdpcut(p,z);        
                
                % Maybe the relaxed solution is feasible
                relaxed_residual = resids(p_upper,z);
                relaxed_feasible = all(relaxed_residual(1:p.K.f)>=-options.bmibnb.eqtol) & all(relaxed_residual(1+p.K.f:end)>=options.bmibnb.pdtol);
                if relaxed_feasible
                    this_upper = f+c'*z+z'*Q*z;
                    if (this_upper < (1-1e-5)*upper) & (this_upper < upper - 1e-5)
                        x_min = z;
                        upper = this_upper;
                        info_text = 'Improved upper bound';
                        cost = cost-1e-10; % Otherwise we'll fathome!
                    end        
                end
                
                % UPDATE THE LOWER BOUND                
                if isnan(lower)
                    lower = cost;
                end
                if ~isempty(stack)
                    lower =min(cost,min([stack.lower]));
                else
                    lower = min(lower,cost);             
                end
                
                if cost<upper 
                    output = solveupper(p,p_upper,x,options,uppersolver);
                    xu = evaluate_nonlinear(p,output.Primal);
                    upper_residual = resids(p_upper,xu);                   
                    if output.problem == 0 | (all(upper_residual(1:p_upper.K.f)>=-options.bmibnb.eqtol) & all(upper_residual(1+p_upper.K.f:end)>=options.bmibnb.pdtol))
                        this_upper = f+c'*xu+xu'*Q*xu;
                        if (this_upper < (1-1e-5)*upper) & (this_upper < upper - 1e-5)
                            x_min = xu;
                            upper = this_upper;
                            info_text = 'Improved upper bound';
                        end                    
                    end
                else
                    if doplot;ellipplot(diag([200 25]),1,'b',[p.dpos;-p.depth]);drawnow;end
                    info_text = 'Poor lower bound';
                    keep_digging = 0;
                end
            otherwise
            end
        else
            if doplot;ellipplot(diag([200 25]),1,'r',[p.dpos;-p.depth]);drawnow;end
            info_text = 'Infeasible during box-reduction';
            keep_digging = 0;
            cost = inf;
            feasible = 0;          
        end
        solved_nodes = solved_nodes+1;
        
        if ~isempty(stack)
            [stack,lower] = prune(stack,upper,options,solved_nodes); 
        end            
        lower = min(lower,cost); 
        
        % **********************************
        % CONTINUE SPLITTING?
        % **********************************   
        if keep_digging & max(p.ub(p.branch_variables)-p.lb(p.branch_variables))>options.bmibnb.vartol
            spliton = branchvariable(p,options,x);
            bounds  = partition(p,options,spliton,x_min); 
            
            node_1 = savetonode(p,spliton,bounds(1),bounds(2),-1,x,cost);
            node_2 = savetonode(p,spliton,bounds(2),bounds(3),1,x,cost);
            stack = push(stack,node_1);
            stack = push(stack,node_2);        
        end
        
        % Pick and create a suitable node to continue on
        [p,stack] = selectbranch(p,options,stack,x_min,upper);
        
        if isempty(p)
            if ~isinf(upper)
                relgap = 0;
            end
            depth = 0;          
        else      
            relgap = 100*(upper-lower)/(1+abs(upper));
            depth = p.depth;
        end    
        if options.bmibnb.verbose>0
            ws = whos; %Work-space
            Mb = sum([ws(:).bytes])/1024^2; %Megs
            showprogress(sprintf(['%3d ' info_text repmat(' ',1,35-length(info_text)) ' %8.2f%%  %8.4f  %8.4f  %8.4f  %3d  %3d    %5.2fMB    %4.1f%%  '],solved_nodes,relgap,upper,cost,lower,depth,length(stack),Mb,100-vol_reduction*100),options.bmibnb.verbose)
        end                            
        
        absgap = upper-lower;
        % **************************************
        % Continue?
        % **************************************    
        time_ok = cputime-t_start < options.bmibnb.maxtime;
        iter_ok = solved_nodes < options.bmibnb.maxiter;
        any_nodes = ~isempty(p);   
        relgap_too_big = (isinf(lower) | isnan(relgap) | relgap>100*options.bmibnb.relgaptol);
        absgap_too_big = (isinf(lower) | isnan(absgap) | absgap>options.bmibnb.absgaptol);
        taget_not_met = upper>options.bmibnb.target;
        go_on = taget_not_met & time_ok & any_nodes & iter_ok & relgap_too_big & absgap_too_big ;
    end
    if options.bmibnb.verbose>0
        fprintf('******************************************************************************************************************\n')
        if options.bmibnb.verbose;showprogress([num2str2(solved_nodes,3)  ' Finishing.  Cost: ' num2str(upper) ' Gap: ' num2str(relgap) '%'],options.bnb.verbose);end
        fprintf('******************************************************************************************************************\n')
    end
    
    
    
    function stack = push(stackin,p)
    if ~isempty(stackin)
        stack = [p;stackin];
    else
        stack(1)=p;
    end
    
    function [p,stack] = pull(stack,method,x_min,upper);
    if ~isempty(stack)
        switch method
            case 'maxvol'
                for i = 1:length(stack)
                    vol(i) = sum(stack(i).ub(stack(i).branch_variables)-stack(i).lb(stack(i).branch_variables));
                end
                [i,j] = max(vol);
                p=stack(j);  
                stack = stack([1:1:j-1 j+1:1:end]); 
                
            case 'best'
                [i,j]=min([stack.lower]);
                p=stack(j);  
                stack = stack([1:1:j-1 j+1:1:end]);  
                
            otherwise        
        end
    else
        p = [];
    end
    
    
    function s = num2str2(x,d,c);
    if nargin==3
        s = num2str(x,c);
    else
        s = num2str(x);
    end
    s = [repmat(' ',1,d-length(s)) s];
    
    
    function res = resids(p,x)
    res= [];
    if p.K.f>0
        res = -abs(p.F_struc(1:p.K.f,:)*[1;x]); 
    end
    if p.K.l>0
        res = [res;p.F_struc(p.K.f+1:p.K.f+p.K.l,:)*[1;x]];
    end
    if (length(p.K.s)>1) | p.K.s>0
        top = 1+p.K.f+p.K.l;
        for i = 1:length(p.K.s)
            n = p.K.s(i);
            X = p.F_struc(top:top+n^2-1,:)*[1;x];top = top+n^2;
            X = reshape(X,n,n);
            res = [res;min(eig(X))];
        end
    end
    
    res = [res;min([p.ub-x;x-p.lb])];
    
    
    function [stack,lower] = prune(stack,upper,options,solved_nodes)
    % *********************************
    % PRUNE STACK W.R.T NEW UPPER BOUND
    % *********************************
    if ~isempty(stack)
        toolarge = find([stack.lower]>upper*(1-1e-4));
        if ~isempty(toolarge)
            if options.bnb.verbose;showprogress([num2str2(solved_nodes,3) ' Pruned ' num2str(length(toolarge))  '  nodes'],options.bnb.verbose-1);end
            stack(toolarge)=[];
        end
    end
    if ~isempty(stack)
        lower = min([stack.lower]);
    else
        lower = upper;
    end
    
    function pcut = addmcgormick(p)
    
    pcut = p;
    top = 0;
    row = [];
    col = [];
    val = [];
    F_temp = [];
    for i = 1:size(p.nonlins,1)
        z = p.nonlins(i,1);
        x = p.nonlins(i,2);
        y = p.nonlins(i,3);
        x_lb = p.lb(x);
        x_ub = p.ub(x);
        y_lb = p.lb(y);
        y_ub = p.ub(y);
        top = 0;
        row = [];
        col = [];
        val = [];
        
        if x~=y
            row = [1;1;1;1;2;2;2;2;3;3;3;3;4;4;4;4];
            col = [1 ; z+1 ; x+1 ; y+1 ; 1 ; z+1 ; x+1 ; y+1 ; 1 ; z+1 ; x+1 ; y+1 ; 1 ; z+1 ; x+1 ; y+1];
            val = [x_lb*y_lb;1;-y_lb;-x_lb;x_ub*y_ub;1;-y_ub;-x_ub;-x_ub*y_lb;-1;y_lb;x_ub;-x_lb*y_ub;-1;y_ub;x_lb];
            F_temp = [F_temp;sparse(row,col,val,4,size(pcut.F_struc,2))];
        else
            
            nr = 3;
            row = [1;1;1;2;2 ;2; 3; 3; 3];
            col = [1 ;z+1 ;x+1 ;1 ;z+1 ;x+1 ;1 ;z+1 ;x+1];
            val = [-x_ub*x_lb;-1;x_lb+x_ub;x_lb*y_lb;1;-y_lb-x_lb;x_ub*y_ub;1;-y_ub-x_ub];
            
            F_temp = [F_temp;sparse(row,col,val,nr,1+length(p.c))];
        end
        bounds = [x_lb*y_lb x_lb*y_ub x_ub*y_lb x_ub*y_ub];
        if x==y
            pcut.lb(pcut.nonlins(i,1)) = max(pcut.lb(pcut.nonlins(i,1)),max(0,min(bounds)));
        else
            pcut.lb(pcut.nonlins(i,1)) = max(pcut.lb(pcut.nonlins(i,1)),min(bounds));
        end
        pcut.ub(pcut.nonlins(i,1)) = min(pcut.ub(pcut.nonlins(i,1)),max(bounds));
    end
    
    keep = find(~isinf(F_temp(:,1)));
    F_temp = F_temp(keep,:);
    pcut.F_struc = [F_temp;pcut.F_struc];
    pcut.K.l = pcut.K.l+size(F_temp,1);  
    
    function [p,feasible,lower] = lpbmitighten(p,lower,upper,lpsolver)
    
    % Construct problem with only linear terms
    % and add cuts from lower/ upper bounds
    c = p.c;
    p_test = p;
    p_test.K.s = 0;
    p_test.F_struc = p_test.F_struc(1+p_test.K.f:1:p_test.K.l+p_test.K.f,:);
    
    if ~isnan(lower)
        p_test.F_struc = [-(p.lower-abs(p.lower)*0.01) p_test.c';p_test.F_struc];
    end
    if upper < inf
        p_test.F_struc = [upper+abs(upper)*0.01 -p_test.c';p_test.F_struc];
    end
    
    p_test.F_struc = [p_test.lpcuts;p_test.F_struc];
    p_test.K.l = size(p_test.F_struc,1);
    
    % Add cuts for nonlinear terms
    p_test = addmcgormick(p_test);
    
    p_test.F_struc = [p.F_struc(1:1:p.K.f,:);p_test.F_struc];
    
    
    feasible = 1;
    i = 1;
    
    p_test = clean_bounds(p_test);
    
    j = 1;
    n = ceil(max(p.options.bmibnb.lpreduce*length(p_test.linears),1));
    res = zeros(length(p.lb),1);
    for i = 1:size(p.nonlins,1)
        res(p.nonlins(i,2)) = res(p.nonlins(i,2)) + abs( p.x0(p.nonlins(i,1))-p.x0(p.nonlins(i,2)).*p.x0(p.nonlins(i,3)));
        res(p.nonlins(i,3)) = res(p.nonlins(i,3)) + abs( p.x0(p.nonlins(i,1))-p.x0(p.nonlins(i,2)).*p.x0(p.nonlins(i,3)));
    end
    res = res(p.linears);
    [ii,jj] = sort(abs(res));               
    jj = jj(end-n+1:end);    
    
    while feasible & j<=length(jj)
        i = p_test.linears(jj(j));
        if abs(p.ub(i)-p.lb(i)>0.1)
            p_test.c = eyev(length(p_test.c),i); 
            
            output = feval(lpsolver,p_test);
            if output.problem == 0
                if p_test.lb(i) < output.Primal(i)-1e-5
                    p_test.lb(i) = output.Primal(i);  
                    p_test = updateonenonlinearbound(p_test,i);
                end     
                p_test.c = -eyev(length(p_test.c),i);
                output = feval(lpsolver,p_test);
                if output.problem == 0
                    if p_test.ub(i) > output.Primal(i)+1e-5
                        p_test.ub(i) = output.Primal(i);
                        p_test = updateonenonlinearbound(p_test,i);        
                    end
                end
                if output.problem == 1
                    feasible = 0;
                end   
            end
            if output.problem == 1
                feasible = 0;
            end  
            p_test = clean_bounds(p_test);
        end
        j = j + 1;
    end
    p.lb = p_test.lb;
    p.ub = p_test.ub;
    
    
    function p = updateonenonlinearbound(p,changed_var);
    for i = 1:size(p.nonlins,1)
        x = p.nonlins(i,2);
        y = p.nonlins(i,3); 
        if (x==changed_var) | (y==changed_var)
            z = p.nonlins(i,1);        
            x_lb = p.lb(x);
            x_ub = p.ub(x);
            y_lb = p.lb(y);
            y_ub = p.ub(y);
            bounds = [x_lb*y_lb x_lb*y_ub x_ub*y_lb x_ub*y_ub];    
            if x==y        
                p.lb(p.nonlins(i,1)) = max([p.lb(z) 0 min(bounds)]);
                p.ub(p.nonlins(i,1)) = min(p.ub(z),max(bounds));
            else
                p.lb(p.nonlins(i,1)) = max(p.lb(z),min(bounds));
                p.ub(p.nonlins(i,1)) = min(p.ub(z),max(bounds));    
            end 
        end
    end
    
    
    function p = updatenonlinearbounds(p,changed_var,keepbest);
    % if nargin>1
    %     changed_var
    % else
    %     i = 1:size(p.nonlins,1);
    % end
    for i = 1:size(p.nonlins,1)
        z = p.nonlins(i,1);
        x = p.nonlins(i,2);
        y = p.nonlins(i,3);    
        x_lb = p.lb(x);
        x_ub = p.ub(x);
        y_lb = p.lb(y);
        y_ub = p.ub(y);
        bounds = [x_lb*y_lb x_lb*y_ub x_ub*y_lb x_ub*y_ub];    
        if x==y        
            p.lb(p.nonlins(i,1)) = max([p.lb(z) 0 min(bounds)]);
            p.ub(p.nonlins(i,1)) = min(p.ub(z),max(bounds));
        else
            p.lb(p.nonlins(i,1)) = max(p.lb(z),min(bounds));
            p.ub(p.nonlins(i,1)) = min(p.ub(z),max(bounds));    
        end 
    end
    return
    
    if nargin > 1
        
        for i = 1:size(p.nonlins,1)
            z = p.nonlins(i,1);
            x = p.nonlins(i,2);
            y = p.nonlins(i,3);
            if isempty(changed_var) | (x==changed_var) | (y == changed_var) | nargin==3
                bound_x1 = [p.lb(p.nonlins(i,2));p.ub(p.nonlins(i,2))];
                bound_x2 = [p.lb(p.nonlins(i,3));p.ub(p.nonlins(i,3))];
                bounds = [bound_x1(1)*bound_x2(1) bound_x1(1)*bound_x2(2) bound_x1(2)*bound_x2(1) bound_x1(2)*bound_x2(2)];
                if nargin==3
                    if x==y
                        p.lb(p.nonlins(i,1)) = max([p.lb(p.nonlins(i,1)) 0 min(bounds)]);
                        p.ub(p.nonlins(i,1)) = min(p.ub(p.nonlins(i,1)),max(bounds));
                    else
                        p.lb(p.nonlins(i,1)) = max(p.lb(p.nonlins(i,1)),min(bounds));
                        p.ub(p.nonlins(i,1)) = min(p.ub(p.nonlins(i,1)),max(bounds));    
                    end 
                else
                    if x==y
                        p.lb(p.nonlins(i,1)) = max(0,min(bounds));
                        p.ub(p.nonlins(i,1)) = max(bounds);
                    else
                        p.lb(p.nonlins(i,1)) = min(bounds);
                        p.ub(p.nonlins(i,1)) = max(bounds);    
                    end
                end
            end
        end
    else    
        for i = 1:size(p.nonlins,1)
            z = p.nonlins(i,1);
            x = p.nonlins(i,2);
            y = p.nonlins(i,3);
            bound_x1 = [p.lb(p.nonlins(i,2));p.ub(p.nonlins(i,2))];
            bound_x2 = [p.lb(p.nonlins(i,3));p.ub(p.nonlins(i,3))];
            bounds = [bound_x1(1)*bound_x2(1) bound_x1(1)*bound_x2(2) bound_x1(2)*bound_x2(1) bound_x1(2)*bound_x2(2)];
            if x==y
                p.lb(p.nonlins(i,1)) = max( p.lb(p.nonlins(i,1)) ,max(0,min(bounds)));
                p.ub(p.nonlins(i,1)) = min( p.ub(p.nonlins(i,1)) ,max(bounds));
            else
                p.lb(p.nonlins(i,1)) = max(p.lb(p.nonlins(i,1)),min(bounds));
                p.ub(p.nonlins(i,1)) = min(p.ub(p.nonlins(i,1)),max(bounds));    
            end
        end
    end
    
    % *************************************
    % DERIVE LINEAR CUTS FROM SDPs
    % THESE ARE ONLY USED IN BOXREDUCE
    % *************************************
    function p = addsdpcut(p,x)
    if p.K.s > 0
        top = p.K.f+p.K.l+1;
        newcuts = 1;
        newF = [];
        for i = 1:length(p.K.s)
            n = p.K.s(i);
            X = p.F_struc(top:top+n^2-1,:)*[1;x];
            X = reshape(X,n,n);
            [d,v] = eig(X);
            for m = 1:length(v)
                if v(m,m)<0
                    for j = 1:length(x)+1;
                        newF(newcuts,j)= d(:,m)'*reshape(p.F_struc(top:top+n^2-1,j),n,n)*d(:,m);
                    end
                    % max(abs(newF(:,2:end)),[],2)
                    newF(newcuts,1)=newF(newcuts,1)+1e-6;
                    newcuts = newcuts + 1;
                    if size(p.lpcuts,1)>0
                        dist = p.lpcuts*newF(newcuts-1,:)'/(newF(newcuts-1,:)*newF(newcuts-1,:)');
                        if any(abs(dist-1)<1e-3)
                            newF = newF(1:end-1,:);
                            newcuts = newcuts - 1;
                        end
                    end
                end
            end
            top = top+n^2;
        end
        
        if ~isempty(newF)
            % Don't keep all
            m = size(newF,2);
            %  size(p.lpcuts)
            p.lpcuts = [newF;p.lpcuts];
            violations = p.lpcuts*[1;x];
            p.lpcuts = p.lpcuts(violations<0.1,:);
            
            if size(p.lpcuts,1)>15*m
                violations = p.lpcuts*[1;x];
                [i,j] = sort(violations);
                p.lpcuts = p.lpcuts(j(1:15*m),:);
                p.lpcuts = p.lpcuts(end-15*m+1:end,:);
            end
        end
    end
    
    
    function spliton = branchvariable(p,options,x)
    
    % Split if box is to narrow
    width = abs(p.ub(p.branch_variables)-p.lb(p.branch_variables));
    if min(width)/max(width) < 0.1
        [i,j] = max(width);
        spliton = p.branch_variables(j);
    else
        %     res = zeros(length(p.lb),1);
        %     for i = 1:size(p.nonlins,1)
        %         res(p.nonlins(i,2)) = res(p.nonlins(i,2)) + abs( x(p.nonlins(i,1))-x(p.nonlins(i,2)).*x(p.nonlins(i,3)));
        %         res(p.nonlins(i,3)) = res(p.nonlins(i,3)) + abs( x(p.nonlins(i,1))-x(p.nonlins(i,2)).*x(p.nonlins(i,3)));
        %     end
        %     
        %      [ii,jj] = sort(abs(res));               
        %      spliton = jj(end);    
        
        res = x(p.nonlins(:,1))-x(p.nonlins(:,2)).*x(p.nonlins(:,3));
        [ii,jj] = sort(abs(res));               
        v1 = p.nonlins(jj(end),2);
        v2 = p.nonlins(jj(end),3);
        
        acc_res1 = sum(abs(res(find((p.nonlins(:,2)==v1) |  p.nonlins(:,3)==v1))));
        acc_res2 = sum(abs(res(find((p.nonlins(:,2)==v2) |  p.nonlins(:,3)==v2))));
        
        if (acc_res1>acc_res2) & ismember(v1,p.branch_variables)
            spliton = v1;
        else
            spliton = v2;
        end         
    end
    
    function bounds = partition(p,options,spliton,x_min)
    
    switch options.bmibnb.branchrule
        case 'omega'
            if ~isempty(x_min)
                bounds = [p.lb(spliton) 0.5*max(p.lb(spliton),min(x_min(spliton),p.ub(spliton)))+0.5*(p.lb(spliton)+p.ub(spliton))/2 p.ub(spliton)];                                          
            else
                bounds = [p.lb(spliton) (p.lb(spliton)+p.ub(spliton))/2 p.ub(spliton)];                                          
            end
        case 'bisect'
            bounds = [p.lb(spliton) (p.lb(spliton)+p.ub(spliton))/2 p.ub(spliton)];                                          
        otherwise
            bounds = [p.lb(spliton) (p.lb(spliton)+p.ub(spliton))/2 p.ub(spliton)]; 
    end
    
    
    
    function [p,feasible,vol_reduction] = boxreduce(p,upper,lower,lpsolver,options);
    
    if options.bmibnb.lpreduce
        
        vol_start    = prod(p.ub(p.branch_variables)-p.lb(p.branch_variables));
        diag_before  =  sum(p.ub(p.branch_variables)-p.lb(p.branch_variables));
        diag_before0 = diag_before;
        
        [pcut,feasible,lower] = lpbmitighten(p,lower,upper,lpsolver);
        diag_after = sum(pcut.ub(p.branch_variables)-pcut.lb(p.branch_variables));      
        iterations = 0;
        while (diag_after/(1e-18+diag_before) < 0.75    ) & feasible & iterations<4
            [pcut,feasible,lower] = lpbmitighten(pcut,lower,upper,lpsolver);
            diag_before = diag_after;
            diag_after = sum(pcut.ub(p.branch_variables)-pcut.lb(p.branch_variables));        
            iterations = iterations + 1;
        end        
        
        % Clean up...
        for i = 1:length(pcut.lb)
            if (pcut.lb(i)>pcut.ub(i)) & (pcut.lb-pcut.ub < 1e-3)
                pcut.lb(i)=pcut.ub(i);
                pcut = updatenonlinearbounds(pcut,i);            
            end
        end
        p.lb = pcut.lb;
        p.ub = pcut.ub;    
        
        % Metric = (V0/V)^(1/n)
        vol_reduction = max(0,min(1,(prod(p.ub(p.branch_variables)-p.lb(p.branch_variables))/(1e-12+vol_start))^(1/length(p.branch_variables))));
        p.lb(p.lb<-1e12) = -inf;
        p.ub(p.ub>1e12) = inf;
    else
        vol_reduction = 1;
        feasible = 1;
    end
    
    function output = solvelower(p,options,lowersolver)
    
    % ********************************************
    % Convex envelope
    % ********************************************
    %p.binary_variables = [];
    p_with_bilinear_cuts = p;
    p_with_bilinear_cuts.F_struc(1:p.K.f,:)=[];
    p_with_bilinear_cuts = addmcgormick(p_with_bilinear_cuts);   
    p_with_bilinear_cuts.F_struc = [p.F_struc(1:p.K.f,:);p_with_bilinear_cuts.F_struc];    
    
    % **************************************
    % SOLVE NODE PROBLEM 
    % **************************************
    if any(p_with_bilinear_cuts.ub<p_with_bilinear_cuts.lb) 
        output.problem=1;
    else
        % We are solving relaxed problem (penbmi might be local solver)
        p_with_bilinear_cuts.monomtable = eye(length(p_with_bilinear_cuts.c));
        
        % fix implied  from mccormick
        % p_with_bilinear_cuts.lb(p.linears) = -inf;
        % p_with_bilinear_cuts.ub(p.linears) = inf;
        % p_with_bilinear_cuts.lb(p.nonlins(:,1)) = -inf;
        % p_with_bilinear_cuts.ub(p.nonlins(:,1)) = inf;
        
        output = feval(lowersolver,p_with_bilinear_cuts); 
        
        relaxed_residual = resids(p_with_bilinear_cuts,output.Primal);
        % Minor clean-up
        output.Primal(output.Primal<p.lb) = p.lb(output.Primal<p.lb);
        output.Primal(output.Primal>p.ub) = p.ub(output.Primal>p.ub);    
    end 
    
    function [p,stack] = selectbranch(p,options,stack,x_min,upper);
    switch options.bmibnb.branchmethod
        case 'maxvol'
            [node,stack] = pull(stack,'maxvol',x_min,upper); 
        case 'best'
            [node,stack] = pull(stack,'best',x_min,upper); 
        otherwise
            [node,stack] = pull(stack,'best',x_min,upper); 
    end
    % Copy node data to p
    if isempty(node)
        p = [];
    else
        p.depth = node.depth;
        p.dpos = node.dpos;
        p.lb = node.lb;
        p.ub = node.ub;
        p.lower = node.lower;
        p.lpcuts = node.lpcuts;
        p.x0 = node.x0;
    end
    
    
    
    function output = solveupper(p,p_original,x,options,uppersolver)
    
    p_upper = p_original;
    
    % Pick an initial point (this can be a bit tricky...)
    % Use relaxed point, shifted towards center of box
    if all(x<=p.ub) & all(x>=p.lb)
        p_upper.x0 = 0.1*x + 0.9*(p.lb+p.ub)/2;
    else
        p_upper.x0 = (p.lb+p.ub)/2;
    end
    % Shift towards interior for variables with unbounded lower or upper
    lbinfbounds = find(isinf(p.lb));
    ubinfbounds = find(isinf(p.ub));
    p_upper.x0(ubinfbounds) = x(ubinfbounds)+0.01;
    p_upper.x0(lbinfbounds) = x(lbinfbounds)-0.01;
    ublbinfbounds = find(isinf(p.lb) & isinf(p.ub));
    p_upper.x0(ublbinfbounds) = x(ublbinfbounds);
    % ...expand the current node just slightly
    p_upper.lb = p.lb;
    p_upper.ub = p.ub;
    p_upper.lb(~isinf(p_original.lb)) = 0.99*p.lb(~isinf(p_original.lb))+p_original.lb(~isinf(p_original.lb))*0.01;
    p_upper.ub(~isinf(p_original.ub)) = 0.99*p.ub(~isinf(p_original.ub))+p_original.ub(~isinf(p_original.ub))*0.01;
    p_upper.lb(isinf(p_original.lb)) = p_upper.lb(isinf(p_original.lb)) - 0.001;
    p_upper.ub(isinf(p_original.ub)) = p_upper.ub(isinf(p_original.ub)) + 0.001;
    p_upper.options.saveduals = 0;
    
    % Solve upper bounding problem
    p_upper.options.usex0 = 1;
    output = feval(uppersolver,p_upper);
    % Project into the box (numerical issue)
    output.Primal(output.Primal<p_upper.lb) = p_upper.lb(output.Primal<p_upper.lb);
    output.Primal(output.Primal>p_upper.ub) = p_upper.ub(output.Primal>p_upper.ub);
    
    
    % This one needs a lot of work
    function p = nonlinear_constraint_propagation(p)
    
    for i = 1:size(p.nonlins,1)
        x = p.nonlins(i,2);
        y = p.nonlins(i,3);
        z = p.nonlins(i,1);
        
        if y==x & p.ub(z)>0
            p.ub(x) = min(p.ub(x),sqrt(p.ub(z)));
            p.lb(x) = max(p.lb(x),-sqrt(p.ub(z)));
        end
        
        if p.lb(y)>0 & p.ub(z)>0 & p.ub(x)>0
            p.ub(x) = min(p.ub(x),p.ub(z)/p.lb(y));
        end
        if p.lb(x)>0 & p.ub(z)>0 & p.ub(y)>0
            p.ub(y) = min(p.ub(y),p.ub(z)/p.lb(x));
        end
        
        if p.ub(y)>0 & p.lb(y)>0 & p.lb(z)>0 
            p.lb(x) = max(p.lb(x),p.lb(z)/p.ub(y));
        end
        if p.ub(x)>0 & p.lb(x)>0 & p.lb(z)>0 
            p.lb(y) = max(p.lb(y),p.lb(z)/p.ub(x));
        end    
    end
    
    
    function vars = decide_branch_variables(p)
    
    if p.options.bmibnb.lowrank==0
        nonlinear = find(~(sum(p.monomtable~=0,2)==1 & sum(p.monomtable,2)==1));
        vars      =  find(sum(abs(full(p.monomtable(nonlinear,:))),1));
    else
        pool1 = p.nonlins(1,2);
        pool2 = p.nonlins(1,3);
        
        for i = 2:size(p.nonlins,1)
            v1 = p.nonlins(i,2);
            v2 = p.nonlins(i,3);
            if v1==v2
                % We are fucked
                pool1 = [pool1 v1];
                pool2 = [pool2 v2];
            else
                if ismember(v1,pool1)
                    pool2 = [pool2 v2];
                elseif ismember(v1,pool2)
                    pool1 = [pool1 v2];
                elseif ismember(v2,pool1)
                    pool2 = [pool2 v1];
                elseif ismember(v2,pool2)
                    pool1 = [pool1 v1];
                else
                    % No member yet
                    pool1 = [pool1 v1];
                    pool2 = [pool2 v2];
                end
            end
        end
        pool1 = unique(pool1);
        pool2 = unique(pool2);
        if isempty(intersect(pool1,pool2))
            if length(pool1)<=length(pool2)
                vars = pool1;
            else
                vars = pool2;
            end
        else
            nonlinear          = find(~(sum(p.monomtable~=0,2)==1 & sum(p.monomtable,2)==1));
            vars =  find(sum(abs(full(p.monomtable(nonlinear,:))),1));  
        end
    end
    
    
    function x = evaluate_nonlinear(p,x);
    x(p.nonlins(:,1)) = x(p.nonlins(:,2)).*x(p.nonlins(:,3));
    
    function p = clean_bounds(p)
    % Fix to improve numerica with integer bounds
    %close = find(1e-6>abs(p.ub - round(p.ub)));
    %p.ub(close) = round(p.ub(close));
    close = 1e-6>abs(p.ub - round(p.ub));
    p.ub(close) = round(p.ub(close));
    
    close = 1e-6>abs(p.lb - round(p.lb));
    p.lb(close) = round(p.lb(close));
    
    p.ub(p.binary_variables) = floor(p.ub(p.binary_variables) + 1e-2);
    %p.lb(p.binary_variables) =  ceil(p.lb(p.binary_variables) - 1e-2);
    %p = updatenonlinearbounds(p);
    
    % Nothing coded to do non-linear propagation
    %p = nonlinear_constraint_propagation(p);
    p.lb(p.lb<-1e12) = -inf;
    p.ub(p.ub>1e12) = inf;
    
    
    
    function node = savetonode(p,spliton,bounds1,bounds2,direction,x,cost);
    
    node.lb = p.lb;
    node.ub = p.ub;
    node.lb(spliton) = bounds1;
    node.ub(spliton) = bounds2;
    if direction == -1
        node.dpos = p.dpos-1/(2^sqrt(p.depth));
    else
        node.dpos = p.dpos+1/(2^sqrt(p.depth));
    end
    node.depth = p.depth+1;
    node.x0 = x;
    node.lpcuts = p.lpcuts;
    node.lower = cost;
    
