Dynamic-Calibration/utils/YALMIP-master/modules/global/bnb.m

1535 lines
49 KiB
Matlab
Executable File

function output = bnb(p)
%BNB General branch-and-bound scheme for (primarily) conic programs
%
% BNB applies a branch-and-bound scheme to solve mixed-integer
% convex programs, in particular linear mixed-integer semidefinite
% programs
%
% BNB is never called by the user directly, but is called by
% YALMIP from SOLVESDP, by choosing the solver tag 'bnb' in sdpsettings.
%
% BNB is used if no other mixed-integer solver is found, and
% is only meant to be used for mixed-integer SDP, or maybe general
% nonlinear mixed-integer problems (as there are much better solvers
% available for standard LP/QP/SOCP models)
%
% The behaviour of BNB can be altered using the fields in the field 'bnb'
% in SDPSETTINGS (although defaults are recommended)
%
% solver Solver for the relaxed problems (standard solver tag, see SDPSETTINGS)
%
% maxiter Maximum number of nodes explored
%
% maxtime Maximum time allowed
%
% inttol Tolerance for declaring a variable as integer
%
% feastol Tolerance for declaring constraints as feasible
%
% gaptol Exit when (upper bound-lower bound)/(1e-3+abs(lower bound)) < gaptol
%
% round Round variables smaller than bnb.inttol
%
% plot Plot the upper and lower bound, stack size, and
% histogram of lower bounds in stack
%
% bnb.branchrule Deceides on what variable to branch
% 'max' : Variable furthest away from being integer
% 'min' : Variable closest to be being integer
% 'first' : First variable (lowest variable index in YALMIP)
% 'last' : Last variable (highest variable index in YALMIP)
% 'weight' : See manual
%
% bnb.method Branching strategy
% 'depth' : Depth first
% 'breadth' : Breadth first
% 'best' : Expand branch with lowest lower bound
% 'depthX' : Depth until integer solution found, then X (e.g 'depthbest')
%
%
% See also OPTIMIZE, BINVAR, INTVAR, BINARY, INTEGER
% ********************************
%% INITIALIZE DIAGNOSTICS IN YALMIP
% ********************************
bnbsolvertime = clock;
showprogress('Branch and bound started',p.options.showprogress);
% ********************************
%% Remove options if none has been changed
%% Improves peroformance when calling solver many times
% ********************************
p.options = pruneOptions(p.options);
% ********************************
%% We might have a GP : pre-calc
% ********************************
p.nonlinear = find(~(sum(p.monomtable~=0,2)==1 & sum(p.monomtable,2)==1));
p.nonlinear = union(p.nonlinear,p.evalVariables);
% ********************************
% This field is only used in bmibnb, which uses the same sub-functions as
% bnb
% ********************************
p.high_monom_model = [];
% ********************************
%% Define infinite bounds
% ********************************
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
% ********************************
%% Extract bounds from model
% ********************************
p = extractBounds(p);
% ********************************
%% ADD CONSTRAINTS 0<x<1 FOR BINARY
% ********************************
if ~isempty(p.binary_variables)
p.ub(p.binary_variables) = min(p.ub(p.binary_variables),1);
p.lb(p.binary_variables) = max(p.lb(p.binary_variables),0);
end
p = update_integer_bounds(p);
if ~isempty(p.semicont_variables)
redundant = find(p.lb<=0 & p.ub>=0);
p.semicont_variables = setdiff(p.semicont_variables,redundant);
% Now relax the model and generate hull including 0
p.semibounds.lb = p.lb(p.semicont_variables);
p.semibounds.ub = p.ub(p.semicont_variables);
p.lb(p.semicont_variables) = min(p.lb(p.semicont_variables),0);
p.ub(p.semicont_variables) = max(p.ub(p.semicont_variables),0);
end
% Could be some nonlinear terms (although these problems are recommended to
% be solved using BMIBNB
p = compile_nonlinear_table(p);
p = updatemonomialbounds(p);
% % *******************************
% %% PRE-SOLVE (nothing fancy coded)
% % *******************************
p = simplePresolve(p);
p = propagate_bounds_from_equalities(p);
pss = [];
if isempty(p.nonlinear)
if p.K.f>0
Aeq = -p.F_struc(1:p.K.f,2:end);
beq = p.F_struc(1:p.K.f,1);
A = [Aeq;-Aeq];
b = [beq;-beq];
[p.lb,p.ub,redundant,pss] = tightenbounds(A,b,p.lb,p.ub,p.integer_variables,p.binary_variables,ones(length(p.lb),1));
end
pss=[];
if p.K.l>0
A = -p.F_struc(1+p.K.f:p.K.f+p.K.l,2:end);
b = p.F_struc(1+p.K.f:p.K.f+p.K.l,1);
[p.lb,p.ub,redundant,pss] = tightenbounds(A,b,p.lb,p.ub,p.integer_variables,p.binary_variables,ones(length(p.lb),1));
if length(redundant)>0
pss.AL0A(redundant,:)=[];
pss.AG0A(redundant,:)=[];
p.F_struc(p.K.f+redundant,:)=[];
p.K.l = p.K.l - length(redundant);
end
end
end
% Silly redundancy
p = updatemonomialbounds(p);
p = propagate_bounds_from_equalities(p);
if p.K.l > 0
b = p.F_struc(1+p.K.f:p.K.l+p.K.f,1);
A = -p.F_struc(1+p.K.f:p.K.l+p.K.f,2:end);
redundant = find(((A>0).*A*(p.ub-p.lb) - (b-A*p.lb) <= 0));
if ~isempty(redundant)
p.F_struc(p.K.f + redundant,:) = [];
p.K.l = p.K.l - length(redundant);
end
end
% *******************************
%% Display logics
% 0 : Silent
% 1 : Display branching
% 2 : Display node solver prints
% *******************************
if p.options.verbose ~= fix(p.options.verbose)
p.options.print_interval = ceil(1/p.options.verbose);
p.options.verbose = ceil(p.options.verbose);
else
p.options.print_interval = 1;
end
switch max(min(p.options.verbose,3),0)
case 0
p.options.bnb.verbose = 0;
case 1
p.options.bnb.verbose = 1;
p.options.verbose = 0;
case 2
p.options.bnb.verbose = 2;
p.options.verbose = 0;
case 3
p.options.bnb.verbose = 2;
p.options.verbose = 1;
otherwise
p.options.bnb.verbose = 0;
p.options.verbose = 0;
end
% *******************************
%% Figure out the weights if any
% *******************************
try % Probably buggy first version...
if ~isempty(p.options.bnb.weight)
weightvar = p.options.bnb.weight;
if isa(weightvar,'sdpvar')
if (prod(size(weightvar)) == 1)
weight = ones(length(p.c),1);
for i = 1:length(p.c)
weight(i,1) = full(getbasematrix(weightvar,p.used_variables(i)));
end
p.weight = weight;
else
error('Weight should be an SDPVAR scalar');
end
else
error('Weight should be an SDPVAR scalar');
end
else
p.weight = ones(length(p.c),1);
end
catch
disp('Something wrong with weights. Please report bug');
p.weight = ones(length(p.c),1);
end
% *******************************
%% START BRANCHING
% *******************************
setuptime = etime(clock,bnbsolvertime);
bnbsolvertime = clock;
[x_min,solved_nodes,lower,upper,profile,diagnostics] = branch_and_bound(p,pss);
bnbsolvertime = etime(clock,bnbsolvertime);
output.solvertime = setuptime + bnbsolvertime;
% **********************************
%% CREATE SOLUTION
% **********************************
if diagnostics == -4
output.problem = -4;
else
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
if bnbsolvertime > p.options.bnb.maxtime
output.problem = 3;
end
end
output.solved_nodes = solved_nodes;
output.Primal = x_min;
output.Dual = [];
output.Slack = [];
if output.problem == -4
output.infostr = yalmiperror(output.problem,[p.solver.lower.tag '-' p.solver.lower.version]);
else
output.infostr = yalmiperror(output.problem,'BNB');
end
output.solverinput = 0;
if p.options.savesolveroutput
output.solveroutput.setuptime = setuptime;
output.solveroutput.localsolvertime = profile.local_solver_time;
output.solveroutput.branchingtime = bnbsolvertime;
output.solveroutput.solved_nodes = solved_nodes;
output.solveroutput.lower = lower;
output.solveroutput.upper = upper;
else
output.solveroutput =[];
end
%% --
function [x_min,solved_nodes,lower,upper,profile,diagnostics] = branch_and_bound(p,pss)
% *******************************
% We don't need this
% *******************************
p.options.savesolveroutput = 0;
p.options.saveduals = 0;
p.options.dimacs = 0;
diagnostics = 0;
bnbsolvertime = clock;
% *******************************
% Tracking performance etc
% *******************************
profile.local_solver_time = 0;
% *************************************************************************
% We save this to re-use some stuff in fmincon
% *************************************************************************
p.options.savesolverinput = 1;
% *******************************
%% SET-UP ROOT PROBLEM
% *******************************
p.depth = 0;
p.lower = NaN;
% Does the user want to create his own initial guess
if p.options.usex0
[x_min,upper] = initializesolution(p);
if isinf(upper)
% Try to initialize to lowerbound+upperbound. fmincon really
% doesn't like zero initial guess, despite having bounds available
x_min = zeros(length(p.c),1);
violates_finite_bounds = ((x_min < p.lb) | (x_min < p.ub));
violates_finite_bounds = find(violates_finite_bounds & ~isinf(p.lb) & ~isinf(p.ub));
x_min(violates_finite_bounds) = (p.lb(violates_finite_bounds) + p.ub(violates_finite_bounds))/2;
x_min = setnonlinearvariables(p,x_min);
end
p.x0 = x_min;
else
upper = inf;
x_min = zeros(length(p.c),1);
violates_finite_bounds = ((x_min < p.lb) | (x_min < p.ub));
violates_finite_bounds = find(violates_finite_bounds & ~isinf(p.lb) & ~isinf(p.ub));
x_min(violates_finite_bounds) = (p.lb(violates_finite_bounds) + p.ub(violates_finite_bounds))/2;
x_min = setnonlinearvariables(p,x_min);
p.x0 = x_min;
end
% *******************************
%% Global stuff
% *******************************
lower = NaN;
stack = stackCreate;
% *******************************
%% Create function handle to solver
% *******************************
lowersolver = p.solver.lower.call;
uppersolver = p.options.bnb.uppersolver;
p.corig = p.c;
% *******************************
%% INVARIANT PROBLEM DATA
% *******************************
c = p.corig;
Q = p.Q;
f = p.f;
integer_variables = p.integer_variables;
solved_nodes = 0;
semicont_variables = p.semicont_variables;
gap = inf;
node = 1;
if p.options.bnb.presolve
savec = p.c;
saveQ = p.Q;
p.Q = p.Q*0;
n = length(p.c);
saveBinary = p.binary_variables;
saveInteger = p.integer_variables;
p.binary_variables = [];
p.integer_variables = [];;
for i = 1:length(c)
p.c = eyev(n,i);
output = feval(lowersolver,p);
if output.problem == 0
p.lb(i) = max(p.lb(i),output.Primal(i)-1e-3);
end
p.c = -eyev(n,i);
output = feval(lowersolver,p);
if output.problem == 0
p.ub(i) = min(p.ub(i),output.Primal(i)+1e-3);
end
p.lb(saveBinary) = ceil(p.lb(saveBinary)-1e-3);
p.ub(saveBinary) = floor(p.ub(saveBinary)+1e-3);
end
p.binary_variables = saveBinary;
p.integer_variables = saveInteger;
p.Q = saveQ;
p.c = savec;
end
% ************************************************
% Some hacks to speed up solver calls
% Only track solver-time if user wants profile
% ************************************************
p.getsolvertime = p.options.bnb.profile;
% *******************************
%% DISPLAY HEADER
% *******************************
originalDiscrete = [p.integer_variables(:);p.binary_variables(:)];
originalBinary = p.binary_variables(:);
if nnz(Q)==0 & (nnz(p.c-fix(p.c))==0) & isequal(p.K.m,0)
can_use_ceil_lower = all(ismember(find(p.c),originalDiscrete));
else
can_use_ceil_lower = 0;
end
if p.options.bnb.verbose
pc = p.problemclass;
non_convex_obj = pc.objective.quadratic.nonconvex | pc.objective.polynomial;
non_convex_constraint = pc.constraint.equalities.quadratic | pc.constraint.inequalities.elementwise.quadratic.nonconvex;
non_convex_constraint = non_convex_constraint | pc.constraint.equalities.polynomial | pc.constraint.inequalities.elementwise.polynomial;
possiblynonconvex = non_convex_obj | non_convex_constraint;
if ~isequal(p.solver.lower.version,'')
p.solver.lower.tag = [p.solver.lower.tag '-' p.solver.lower.version];
end
disp('* Starting YALMIP integer branch & bound.');
disp(['* Lower solver : ' p.solver.lower.tag]);
disp(['* Upper solver : ' p.options.bnb.uppersolver]);
disp(['* Max iterations : ' num2str(p.options.bnb.maxiter)]);
if possiblynonconvex & p.options.warning
disp(' ');
disp('Warning : The continuous relaxation may be nonconvex. This means ');
disp('that the branching process is not guaranteed to find a');
disp('globally optimal solution, since the lower bound can be');
disp('invalid. Hence, do not trust the bound or the gap...')
end
end
if p.options.bnb.verbose; disp(' Node Upper Gap(%) Lower Open Elapsed time');end;
if nnz(Q)==0 & nnz(c)==1 & isequal(p.K.m,0)
p.simplecost = 1;
else
p.simplecost = 0;
end
poriginal = p;
p.cuts = [];
p = detectSOS(p);
p = detectAtMost(p);
poriginal.atmost = p.atmost;
pid = 0;
lowerhist = [];
upperhist = [];
stacksizehist = [];
p.fixedvariable = [];
p.fixdir = '';
lastUpper = upper;
oldp = p;
if length(p.integer_variables) == length(p.c)
p.all_integers = 1;
else
p.all_integers = 0;
end
p.noninteger_variables = setdiff(1:length(p.c),[p.integer_variables p.binary_variables p.semicont_variables]);
poriginal.noninteger_variables = p.noninteger_variables;
p = addImpliedSDP(p);
% Resuse some code from cutsdp to add simple cuts required for SDP
% feasibility for problems with some trivial symmetries
% TODO: Clean up, refactor, generalize
if p.K.f == 0 % Still to lazy to fix last insertion
top = 1 + p.K.l + p.K.f + sum(p.K.q);
p.F_struc = p.F_struc';
for i = 1:length(p.K.s)
p.semidefinite{i}.F_struc = p.F_struc(:,top:top+p.K.s(i)^2-1)';
top = top + p.K.s(i)^2;
end
p.F_struc = p.F_struc';
p.sdpsymmetry = [];
p = detect3x3SymmetryGroups(p);
pp = p;pp.F_struc = [];pp.K.l = 0;pp.K.f = 0;pp.K.s = 0;
[p,pp] = addSymmetryCuts(p,pp);
p = addLinearCut(p,pp.F_struc);
p.semidefinite=[];
end
feasibilityHistory = [];
% Not used, deleted feature...
aggresiveprune = 0;
% Save of all optimal solutions
allSolutions = [];
sosgroups = [];
sosvariables = [];
while ~isempty(node) & (etime(clock,bnbsolvertime) < p.options.bnb.maxtime) & (solved_nodes < p.options.bnb.maxiter) & (isinf(lower) | gap>p.options.bnb.gaptol)
% ********************************************
% BINARY VARIABLES ARE FIXED ALONG THE PROCESS
% ********************************************
binary_variables = p.binary_variables;
% ********************************************
% SO ARE SEMI VARIABLES
% ********************************************
semicont_variables = p.semicont_variables;
% ********************************************
% ASSUME THAT WE WON'T FATHOME
% ********************************************
keep_digging = 1;
message = '';
% *************************************
% SOLVE NODE PROBLEM
% *************************************
if any(p.ub<p.lb - 1e-12)
x = zeros(length(p.c),1);
output.Primal = x;
output.problem=1;
else
p.x_min = x_min;
relaxed_p = p;
relaxed_p.integer_variables = [];
relaxed_p.binary_variables = [];
relaxed_p.semicont_variables = [];
relaxed_p.ub(p.ub<p.lb) = relaxed_p.lb(p.ub<p.lb);
% Solve node relaxation
output = bnb_solvelower(lowersolver,relaxed_p,upper,lower,x_min,aggresiveprune,allSolutions);
if (output.problem == 12 || output.problem == 2) && ~isinf(p.lower)
output.problem = 1;
end
if p.options.bnb.profile
profile.local_solver_time = profile.local_solver_time + output.solvertime;
end
% A bit crappy code to exploit computations that were done in the
% call to fmincon...
if isfield(output,'solverinput')
if isfield(output.solverinput,'model')
if isfield(output.solverinput.model,'fastdiff')
p.fastdiff = output.solverinput.model.fastdiff;
end
end
end
if output.problem == -4
diagnostics = -4;
x = nan+zeros(length(p.lb),1);
else
if isempty(output.Primal)
output.Primal = zeros(length(p.c),1);
end
x = setnonlinearvariables(p,output.Primal);
if(p.K.l>0) & any(p.F_struc(p.K.f+1:p.K.f+p.K.l,:)*[1;x]<-1e-5)
output.problem = 1;
elseif output.problem == 5 & ~checkfeasiblefast(p,x,p.options.bnb.feastol)
output.problem = 1;
end
end
end
solved_nodes = solved_nodes+1;
% **************************************
% THIS WILL BE INTIAL GUESS FOR CHILDREN
% **************************************
p.x0 = x;
% *************************************
% ANY INTEGERS? ROUND?
% *************************************
non_integer_binary = abs(x(binary_variables)-round(x(binary_variables)))>p.options.bnb.inttol;
non_integer_integer = abs(x(integer_variables)-round(x(integer_variables)))>p.options.bnb.inttol;
if p.options.bnb.round
x(binary_variables(~non_integer_binary)) = round(x(binary_variables(~non_integer_binary)));
x(integer_variables(~non_integer_integer)) = round(x(integer_variables(~non_integer_integer)));
end
non_integer_binary = find(non_integer_binary);
non_integer_integer = find(non_integer_integer);
if isempty(p.semicont_variables)
non_semivar_semivar=[];
else
non_semivar_semivar = find(~(abs(x(p.semicont_variables))<p.options.bnb.inttol | (x(p.semicont_variables)>p.semibounds.lb & x(p.semicont_variables)<=p.semibounds.ub)));
end
x = setnonlinearvariables(p,x);
TotalIntegerInfeas = sum(abs(round(x(non_integer_integer))-x(non_integer_integer)));
TotalBinaryInfeas = sum(abs(round(x(non_integer_binary))-x(non_integer_binary)));
% *************************************
% NODE HEURISTICS (NOTHING CODED)
% *************************************
should_be_tight = find([p.lb == p.ub]);
if ~isempty(should_be_tight)
% FIX for problems that only report numerical problems but violate
% binary
if max(abs(p.lb(should_be_tight)-x(should_be_tight)))>p.options.bnb.inttol
output.problem = 1;
end
end
if output.problem==0 | output.problem==3 | output.problem==4
cost = computecost(f,c,Q,x,p);
if output.problem~=1
if isnan(lower)
lower = cost;
end
if cost <= upper & ~(isempty(non_integer_binary) & isempty(non_integer_integer) & isempty(non_semivar_semivar))
poriginal.upper = upper;
poriginal.lower = lower;
[upper1,x_min1] = feval(uppersolver,poriginal,output,p);
if upper1 < upper
x_min = x_min1;
allSolutions = x_min;
upper = upper1;
[stack,stacklower] = prune(stack,upper,p.options,solved_nodes,p,allSolutions);
lower = min(lower,stacklower);
elseif ~isinf(upper1) && upper1 == upper && norm(x_min-x_min1) > 1e-4;
% Yet another solution with same value
allSolutions = [allSolutions x_min1];
end
elseif isempty(non_integer_binary) && isempty(non_integer_integer) && isempty(non_semivar_semivar)
end
end
end
p = adaptivestrategy(p,upper,solved_nodes);
% *************************************
% CHECK FATHOMING POSSIBILITIES
% *************************************
feasible = 1;
switch output.problem
case {-1,4}
% Solver behaved weird. Make sure we continue digging
keep_digging = 1;
feasible = 1;
cost = lower;
x = p.lb + (p.ub-p.lb)*(1/pi);
case 0
if can_use_ceil_lower
lower = ceil(lower-1e-8);
end
case {1,12,-4,22}
keep_digging = 0;
cost = inf;
feasible = 0;
case 2
cost = -inf;
otherwise
% This part has to be much more robust because this could be a
% numerical problem leading to a non-trustworthy solution
cost = f+c'*x+x'*Q*x;
end
% **************************************
% YAHOO! INTEGER SOLUTION FOUND
% **************************************
if isempty(non_integer_binary) & isempty(non_integer_integer) & isempty(non_semivar_semivar) & ~(output.problem == -1) & ~(output.problem == 4)
if (cost<upper) & feasible
x_min = x;
upper = cost;
allSolutions = x_min;
[stack,lower] = prune(stack,upper,p.options,solved_nodes,p,allSolutions);
end
p = adaptivestrategy(p,upper,solved_nodes);
keep_digging = 0;
end
% **************************************
% Stop digging if it won't give sufficient improvement anyway
% **************************************
if cost>upper*(1-p.options.bnb.gaptol)
keep_digging = 0;
end
feasibilityHistory(end+1) = feasible;
% **********************************
% CONTINUE SPLITTING?
% **********************************
if keep_digging & (cost<upper)
if solved_nodes == 1
RootNodeInfeas = TotalIntegerInfeas+TotalBinaryInfeas;
RootNodeCost = cost;
end
% **********************************
% BRANCH VARIABLE
% **********************************
[index,whatsplit,globalindex] = branchvariable(x,integer_variables,binary_variables,p.options,x_min,[],p);
% **********************************
% CREATE NEW PROBLEMS
% **********************************
p0_feasible = 1;
p1_feasible = 1;
switch whatsplit
case 'binary'
[p0,p1,index] = binarysplit(p,x,index,cost,[],sosgroups,sosvariables);
case 'integer'
[p0,p1] = integersplit(p,x,index,cost,x_min);
case 'semi'
[p0,p1] = semisplit(p,x,index,cost,x_min);
case 'sos1'
[p0,p1] = sos1split(p,x,index,cost,x_min);
otherwise
end
node1.lb = p1.lb;
node1.ub = p1.ub;
node1.depth = p1.depth;
node1.lower = p1.lower;
node1.fixedvariable = globalindex;
node1.fixdir = 'up';
node1.TotalIntegerInfeas = TotalIntegerInfeas;
node1.TotalBinaryInfeas = TotalBinaryInfeas;
node1.IntInfeas = 1-(x(globalindex)-floor(x(globalindex)));
node1.x0 = p1.x0;
node1.binary_variables = p1.binary_variables;
node1.semicont_variables = p1.semicont_variables;
node1.semibounds = p1.semibounds;
node1.pid = pid;pid = pid + 1;
node1.sosgroups = p1.sosgroups;
node1.sosvariables = p1.sosvariables;
node1.atmost = p1.atmost;
node0.lb = p0.lb;
node0.ub = p0.ub;
node0.depth = p0.depth;
node0.lower = p0.lower;
node0.fixedvariable = index;
node0.fixdir = 'down';
node0.TotalIntegerInfeas = TotalIntegerInfeas;
node0.TotalBinaryInfeas = TotalBinaryInfeas;
node0.IntInfeas = x(globalindex)-floor(x(globalindex));
node0.x0 = p0.x0;
node0.binary_variables = p0.binary_variables;
node0.semicont_variables = p0.semicont_variables;
node0.semibounds = p0.semibounds;
node0.pid = pid;pid = pid + 1;
node0.sosgroups = p0.sosgroups;
node0.sosvariables = p0.sosvariables;
node0.atmost = p0.atmost;
if ismember(globalindex,p.atmost.variables)
for j = 1:length(p.atmost.groups)
xy = p.atmost.groups{j};
if p.atmost.bounds(j)==1 && any(xy == globalindex)
if ~(node0.lb(globalindex)==0 && node0.ub(globalindex)==0)
% The variable has been fixed to a non-zero value
% Hence, its sister has to be set to 0
sisters = xy(xy~=globalindex);
for k = sisters
if node0.lb(k) > 0 || node0.ub(k) < 0
p0_feasible = 0;
break
else
node0.lb(k) = 0;
node0.ub(k) = 0;
end
end
end
if ~(node1.lb(globalindex)==0 && node1.ub(globalindex)==0)
sisters = xy(xy~=globalindex);
for k = sisters
if node1.lb(k) > 0 || node1.ub(k) < 0
p1_feasible = 0;
break
else
node1.lb(k) = 0;
node1.ub(k) = 0;
end
end
end
end
end
end
% Make sure we don't push trivially poor stuff to stack, so reuse
% pruning code by creating temporary stacks first
tempstack = stackCreate;
if p0_feasible
tempstack = push(tempstack,node0);
end
if p1_feasible
tempstack = push(tempstack,node1);
end
tempstack = prune(tempstack,upper,p.options,solved_nodes,p,allSolutions);
stack = mergeStack(stack,tempstack);
end
if stackLength(stack)>0
lower = stackLower(stack);
if can_use_ceil_lower
lower = ceil(lower);
end
end
% Dude, all problems we solve now are infeasible. Start presolving LPs
% before going all in on the full SDP
if length(feasibilityHistory) > 5 && all(feasibilityHistory(end-4:end)==0)
aggresiveprune = 1;
else
aggresiveprune = 0;
end
% **********************************
% Get a new node to solve
% **********************************
[node,stack] = pull(stack,p.options.bnb.method,x_min,upper);
if ~isempty(node)
p = copyNode(p,node);
end
if isempty(node)
% There are no nodes left
if ~isinf(upper)
gap = 0;
end
else
% We pulled a new node from stack, so there are nodes left
gap = abs((upper-lower)/(1e-3+abs(upper)+abs(lower)));
end
if isnan(gap)
gap = inf;
end
lowerhist = [lowerhist lower];
upperhist = [upperhist upper];
stacksizehist = [stacksizehist stackLength(stack)];
if p.options.bnb.verbose;
if mod(solved_nodes-1,p.options.print_interval)==0 || isempty(node) || (gap == 0) || (lastUpper-1e-6 > upper)
if p.options.bnb.plot
hold off
subplot(1,3,1);
l = plot([lowerhist' upperhist']);set(l,'linewidth',2);
title('Upper/lower bounds')
subplot(1,3,2);
l = plot(stacksizehist);set(l,'linewidth',2);
title('Open nodes')
drawnow
subplot(1,3,3);
hist(getStackLowers(stack),25);
title('Histogram lower bounds')
drawnow
end
if lastUpper > upper
fprintf(' %4.0f : %12.3E %7.2f %12.3E %2.0f %8.1f %s \n',solved_nodes,upper,100*gap,lower,stackLength(stack),etime(clock,bnbsolvertime),'-> Found improved solution!');
else
fprintf(' %4.0f : %12.3E %7.2f %12.3E %2.0f %8.1f %s \n',solved_nodes,upper,100*gap,lower,stackLength(stack),etime(clock,bnbsolvertime),yalmiperror(output.problem));
end
end
end
lastUpper = upper;
end
if p.options.bnb.verbose;showprogress([num2str2(solved_nodes,3) ' Finishing. Cost: ' num2str(upper) ],p.options.bnb.verbose);end
% **********************************
%% BRANCH VARIABLE
% **********************************
function [index,whatsplit,globalindex] = branchvariable(x,integer_variables,binary_variables,options,x_min,Weight,p)
all_variables = [integer_variables(:);binary_variables(:)];
if ~isempty(p.sosvariables) && isempty(setdiff(all_variables,p.sosvariables)) & strcmp(options.bnb.branchrule,'sos')
% All variables are in SOS1 constraints
for i = 1:length(p.sosgroups)
dist(i) = (sum(x(p.sosgroups{i}))-max(x(p.sosgroups{i})))/length(p.sosgroups{i});
end
% Which SOS to branch on
[val,index] = max(dist);
whatsplit = 'sos1';
globalindex = index;
end
switch options.bnb.branchrule
case 'weight'
interror = abs(x(all_variables)-round(x(all_variables)));
[val,index] = max(abs(p.weight(all_variables)).*interror);
case 'first'
index = min(find(abs(x(all_variables)-round(x(all_variables)))>options.bnb.inttol));
case 'last'
index = max(find(abs(x(all_variables)-round(x(all_variables)))>options.bnb.inttol));
case 'min'
nint = find(abs(x(all_variables)-round(x(all_variables)))>options.bnb.inttol);
[val,index] = min(abs(x(nint)));
index = nint(index);
case 'max'
%[val,index] = max((abs(x(all_variables)-round(x(all_variables)))));
[val,index] = max((1 + min(10,abs(p.c(all_variables)))).*(abs(x(all_variables)-round(x(all_variables)))));
otherwise
error('Branch-rule not supported')
end
if index<=length(integer_variables)
whatsplit = 'integer';
globalindex = integer_variables(index);
else
index = index-length(integer_variables);
whatsplit = 'binary';
globalindex = binary_variables(index);
end
if isempty(index) | ~isempty(p.semicont_variables)
for i = 1:length(p.semicont_variables)
j = p.semicont_variables(i);
if x(j)>= p.semibounds.lb(i) & x(j)<= p.semibounds.ub(i)
s(i) = 0;
elseif x(j)==0
s(i) = 0;
else
s(i) = min([abs(x(j)-0); abs(x(j)-p.semibounds.lb(i));abs(x(j)-p.semibounds.ub(i))]);
end
end
[val2,index2] = max(s);
if isempty(val)
whatsplit = 'semi';
index = index2;
elseif val2>val
% index = p.semicont_variables(index);
whatsplit = 'semi';
index = index2;
end
end
% **********************************
% SPLIT PROBLEM
% **********************************
function [p0,p1,variable] = binarysplit(p,x,index,lower,options,sosgroups,sosvariables)
p0 = p;
p1 = p;
variable = p.binary_variables(index);
tf = ~(ismembcYALMIP(p0.binary_variables,variable));
new_binary = p0.binary_variables(tf);
friends = [];
if ~isempty(sosvariables)
if ismember(variable,sosvariables)
i = 1;
while i<=length(sosgroups)
if ismember(variable,sosgroups{i})
friends = setdiff(sosgroups{i},variable);
break
else
i = i + 1;
end
end
end
end
p0.ub(variable)=0;
p0.lb(variable)=0;
if length(friends) == 1
p0.ub(friends) = 1;
p0.lb(friends) = 1;
end
p0.lower = lower;
p0.depth = p.depth+1;
p0.binary_variables = new_binary;
p1.ub(variable)=1;
p1.lb(variable)=1;
if length(friends) > 1
p1.ub(friends)=0;
p1.lb(friends)=0;
end
p1.binary_variables = new_binary;%p0.binary_variables;%setdiff1D(p1.binary_variables,variable);
%p1.binary_variables = setdiff(p1.binary_variables,friends);
p1.lower = lower;
p1.depth = p.depth+1;
% % *****************************
% % PROCESS MOST PROMISING FIRST
% % (p0 in top of stack)
% % *****************************
if x(variable)>0.5
pt=p1;
p1=p0;
p0=pt;
end
function [p0,p1] = integersplit(p,x,index,lower,options,x_min)
variable = p.integer_variables(index);
current = x(p.integer_variables(index));
lb = floor(current)+1;
ub = floor(current);
% xi<ub
p0 = p;
p0.lower = lower;
p0.depth = p.depth+1;
p0.x0(variable) = ub;
p0.ub(variable)=min(p0.ub(variable),ub);
% xi>lb
p1 = p;
p1.lower = lower;
p1.depth = p.depth+1;
p1.x0(variable) = lb;
p1.lb(variable)=max(p1.lb(variable),lb);
% *****************************
% PROCESS MOST PROMISING FIRST
% *****************************
if lb-current<0.5
pt=p1;
p1=p0;
p0=pt;
end
function [p0,p1] = sos1split(p,x,index,lower,options,x_min)
v = p.sosgroups{index};
n = ceil(length(v)/2);
v1 = v(randperm(length(v),n));
v2 = setdiff(v,v1);
% In first node, set v2 to 0 and v1 to sosgroup
p0 = p;p0.lower = lower;
p0.sosgroups{index} = v1;
p0.ub(v2) = 0;
% In second node, set v1 to 0 and v1 to sosgroup
p1 = p;p1.lower = lower;
p1.sosgroups{index} = v2;
p1.ub(v1) = 0;
function [p0,p1] = semisplit(p,x,index,lower,options,x_min)
variable = p.semicont_variables(index);
current = x(p.semicont_variables(index));
p0 = p;
p0.lower = lower;
p0.depth = p.depth+1;
p0.x0(variable) = 0;
p0.lb(variable)=0;
p0.ub(variable)=0;
p1 = p;
p1.lower = lower;
p1.depth = p.depth+1;
p1.x0(variable) = p.semibounds.lb(index);
p1.lb(variable) = p.semibounds.lb(index);
p1.ub(variable) = p.semibounds.ub(index);
p0.semicont_variables = setdiff(p.semicont_variables,variable);
p1.semicont_variables = setdiff(p.semicont_variables,variable);
p0.semibounds.lb(index)=[];
p0.semibounds.ub(index)=[];
p1.semibounds.lb(index)=[];
p1.semibounds.ub(index)=[];
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 [stack,lower] = prune(stack,upper,options,solved_nodes,p,allSolutions)
% *********************************
% PRUNE STACK W.R.T NEW UPPER BOUND
% *********************************
if stackLength(stack)>0 && ~isinf(upper)
if length(p.integer_variables) == length(p.c) && all(p.c == fix(p.c)) && nnz(p.Q)==0 && isempty(p.evalMap) && nnz(p.variabletype)==0
L = stack.lower;
tooLarge = find(~isinf(L) & L>=upper-0.999);
else
L = stack.lower;
tooLarge = find(~isinf(L) & L>=upper*(1-options.bnb.prunetol));
end
if ~isempty(tooLarge)
stack.nodeCount = stack.nodeCount - length(tooLarge);
stack.lower(tooLarge) = inf;
end
end
% Prune simple linear model w.r.t bound constraints only
if nnz(p.Q) == 0 && isempty(p.evalMap) && nnz(p.variabletype)==0
tooLarge = [];
for i = find(~isinf(stack.lower))
pi = stack.nodes{i};
neg = find(p.c < 0);
pos = find(p.c > 0);
obj = p.f + p.c(pos)'*pi.lb(pos) + p.c(neg)'*pi.ub(neg);
if obj >= upper
tooLarge = [tooLarge i];
end
end
if ~isempty(tooLarge)
stack.nodeCount = stack.nodeCount - length(tooLarge);
stack.lower(tooLarge) = inf;
end
end
if stack.nodeCount > 0
lower = min(stack.lower);
else
lower = upper;
end
function p = adaptivestrategy(p,upper,solved_nodes)
% **********************************'
% SWITCH NODE SELECTION STRATEGY?
% **********************************'
if strcmp(p.options.bnb.method,'depthproject') & (upper<inf)
p.options.bnb.method = 'project';
end
if strcmp(p.options.bnb.method,'depthbest') & (upper<inf)
p.options.bnb.method = 'best';
end
if strcmp(p.options.bnb.method,'depthprojection') & (upper<inf)
p.options.bnb.method = 'projection';
end
if strcmp(p.options.bnb.method,'depthbreadth') & (upper<inf)
p.options.bnb.method = 'breadth';
end
if strcmp(p.options.bnb.method,'depthest') & (upper<inf)
p.options.bnb.method = 'est';
end
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.q)>1) | p.K.q>0
top = 1+p.K.f+p.K.l;
for i = 1:length(p.K.q)
n = p.K.q(i);
q = p.F_struc(top:top+n-1,:)*[1;x];top = top+n;
res = [res;q(1) - norm(q(2:end))];
end
end
if (length(p.K.s)>1) | p.K.s>0
top = 1+p.K.f+p.K.l+sum(p.K.q);
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 [x_min,upper] = initializesolution(p);
x_min = zeros(length(p.c),1);
upper = inf;
if p.options.usex0
z = p.x0;
residual = resids(p,z);
relaxed_feasible = all(residual(1:p.K.f)>=-1e-8) & all(residual(1+p.K.f:end)>=-1e-6);
if relaxed_feasible & all(z(p.integer_variables)==fix(z(p.integer_variables))) & all(z(p.binary_variables)==fix(z(p.binary_variables)))
upper = computecost(p.f,p.c,p.Q,z,p);
x_min = z;
end
else
p.x0 = zeros(length(p.c),1);
x = p.x0;
z = evaluate_nonlinear(p,x);
residual = resids(p,z);
relaxed_feasible = all(residual(1:p.K.f)>=-p.options.bmibnb.eqtol) & all(residual(1+p.K.f:end)>=p.options.bmibnb.pdtol);
if relaxed_feasible
upper = computecost(p.f,p.c,p.Q,z,p);
x_min = x;
end
end
function p = copyNode(p,node);
p.lb = node.lb;
p.ub = node.ub;
p.depth = node.depth;
p.lower = node.lower;
p.fixedvariable = node.fixedvariable;
p.fixdir = node.fixdir;
p.TotalIntegerInfeas = node.TotalIntegerInfeas;
p.TotalBinaryInfeas = node.TotalBinaryInfeas;
p.IntInfeas = node.IntInfeas;
p.x0 = node.x0;
p.binary_variables = node.binary_variables;
p.semicont_variables = node.semicont_variables;
p.semibounds = node.semibounds;
p.pid = node.pid;
p.sosgroups = node.sosgroups;
p.sosvariables = node.sosvariables;
p.atmost = node.atmost;
function stack = stackCreate
stack.nodes = {};
stack.lower = [];
stack.nodeCount = 0;
function stack = push(stack,p)
stack.nodes{end + 1} = p;
stack.lower(end + 1) = p.lower;
stack.nodeCount = stack.nodeCount + 1;
function stack1 = mergeStack(stack1,stack2)
for i = 1:1:length(stack2.nodes)
if ~isinf(stack2.lower(i))
stack1.nodes{end + 1} = stack2.nodes{i};
stack1.lower(end + 1) = stack2.lower(i);
stack1.nodeCount = stack1.nodeCount + 1;
end
end
function stack = compressStack(stack)
used = find(~isinf(stack.lower));
stack.lower = stack.lower(used);
stack.nodes = {stack.nodes{used}};
function [p,stack] = pull(stack,method,x_min,upper);
if stackLength(stack) > 0
if numel(stack.lower) > 100 && nnz(isinf(stack.lower)) > 0.25*numel(stack.lower)
stack = compressStack(stack);
end
switch method
case {'depth','depthfirst','depthbreadth','depthproject','depthbest'}
depths = getStackDepths(stack);
[i,j] = max(depths);
% Silly for backward testing compatibility. Stack order has
% changed, to be able to compare some examples, make sure we
% traverse the tree in exactly the same was as before on ties
j = max(find(depths == i));
p = getStackNode(stack,j);
stack = removeStackNode(stack,j);
case 'project'
error
[i,j]=min([stack.projection]);
p=stack(j);
stack = stack([1:1:j-1 j+1:1:end]);
case 'breadth'
error
[i,j]=min([stack.depth]);
p=stack(j);
stack = stack([1:1:j-1 j+1:1:end]);
case 'best'
lowers = getStackLowers(stack);
[i,j] = min(lowers);
% Silly for backward testing compatibility. Stack order has
% changed, to be able to compare some examples, make sure we
% traverse the tree in exactly the same was as before on ties
j = max(find(lowers == i));
p = getStackNode(stack,j);
stack = removeStackNode(stack,j);
otherwise
end
else
p = [];
end
function n = stackLength(stack)
n = stack.nodeCount;
function [L,pos,N] = stackLower(stack)
if stack.nodeCount > 0
if nargout == 1
L = min(stack.lower);
elseif nargout == 2
[L,pos] = min(stack.lower);
elseif nargout == 3
[L,pos] = min(stack.lower);
N = stack.nodes(pos);
end
else
L = nan;
end
function D = getStackDepths(stack)
D = -inf(1,length(stack.nodes));
for i = find(~isinf(stack.lower))
D(i) = stack.nodes{i}.depth;
end
function L = getStackLowers(stack)
L = stack.lower;
function N = getStackNode(stack,j)
N = stack.nodes{j};
function stack = removeStackNode(stack,j)
stack.nodeCount = stack.nodeCount - length(j);
stack.lower(j) = inf;
function p = detectSOS(p)
sosgroups = {};
sosvariables = [];
if p.K.f > 0 & ~isempty(p.binary_variables)
nbin = length(p.binary_variables);
Aeq = -p.F_struc(1:p.K.f,2:end);
beq = p.F_struc(1:p.K.f,1);
notbinary_var_index = setdiff(1:length(p.lb),p.binary_variables);
only_binary = ~any(Aeq(:,notbinary_var_index),2);
Aeq_bin = Aeq(find(only_binary),p.binary_variables);
beq_bin = beq(find(only_binary),:);
% Detect groups with constraints sum(d_i) == 1
sosgroups = {};
for i = 1:size(Aeq_bin,1)
if beq_bin(i) == 1
[ix,jx,sx] = find(Aeq_bin(i,:));
if all(sx == 1)
sosgroups{end+1} = p.binary_variables(jx);
sosvariables = [sosvariables p.binary_variables(jx)];
end
end
end
end
p.sosgroups = sosgroups;
p.sosvariables = sosvariables;
function p = simplePresolve(p)
pss=[];
p = propagate_bounds_from_equalities(p);
if p.K.f > 0
pp = p;
r = find(p.lb == p.ub);
pp.F_struc(:,1) = pp.F_struc(:,1) + pp.F_struc(:,r+1)*p.lb(r);
pp.F_struc(:,r+1)=[];
pp.lb(r)=[];
pp.ub(r)=[];
pp.variabletype(r)=[];
% FIXME: This is lazy, should update new list
pp.binary_variables = [];
pp.integer_variables = [];
pp = propagate_bounds_from_equalities(pp);
other = setdiff(1:length(p.lb),r);
p.lb(other) = pp.lb;
p.ub(other) = pp.ub;
p = update_integer_bounds(p);
redundant = find(~any(pp.F_struc(1:p.K.f,2:end),2));
if any(p.F_struc(redundant,1)<0)
p.feasible = 0;
else
p.F_struc(redundant,:)=[];
p.K.f = p.K.f - length(redundant);
end
end
function p = extractBounds(p)
if ~isempty(p.F_struc)
[lb,ub,used_rows_eq,used_rows_lp] = findulb(p.F_struc,p.K);
if ~isempty(used_rows_lp)
used_rows_lp = used_rows_lp(~any(full(p.F_struc(p.K.f + used_rows_lp,1+p.nonlinear)),2));
if ~isempty(used_rows_lp)
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
p.F_struc(p.K.f + used_rows_lp,:)=[];
p.K.l = p.K.l - length(used_rows_lp);
end
end
if ~isempty(used_rows_eq)
used_rows_eq = used_rows_eq(~any(full(p.F_struc(used_rows_eq,1+p.nonlinear)),2));
if ~isempty(used_rows_eq)
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
p.F_struc(used_rows_eq,:)=[];
p.K.f = p.K.f - length(used_rows_eq);
end
end
end
function p = detect3x3SymmetryGroups(p)
good = zeros(1,length(p.c));
good(p.integer_variables) = 1;
good( (p.lb ~= -1) | (p.ub ~=0)) = 0;
if any(good)
groups = {};
for j = 1:length(p.K.s)
if p.K.s(j) >= 3
n = 3;
X = spalloc(p.K.s(j),p.K.s(j),p.K.s(j)^2);
X(1:n,1:n) = 1;
index0 = find(X);
index = index0;
corner = 0;
for block = 1:p.K.s(j)-n
dataBlock = p.semidefinite{j}.F_struc(index,:);
used = find(any(dataBlock,1));
dataBlock = dataBlock(:,used);
if used(1) == 0;dataBlock = [zeros(size(dataBlock,1),1) dataBlock];end
v = used;v = v(v>1)-1;
if all(good(v))
if isempty(groups)
groups{1}.dataBlock = dataBlock;
groups{1}.variables{1} = used;
else
found = 0;
for k = 1:length(groups)
if isequal(length(used),length(groups{1}.variables{1}))
if isequal(groups{1}.dataBlock,dataBlock)
found = 1;
groups{1}.variables{end+1} = used;
else
% TODO: Look for simple scaled versions
% s = groups{1}.dataBlock(:)\dataBlock(:);
% norm(s*groups{1}.dataBlock-dataBlock,inf)
end
end
end
if ~found
groups{end+1}.dataBlock = dataBlock;
groups{end}.variables{1} = used;
end
end
end
index = index + p.K.s(j)+1;
end
end
end
for i = 1:length(groups)
if length(groups{i}.variables) > 1
keep(i) = 1;
else
keep(i) = 0;
end
end
groups = {groups{find(keep)}};
if length(groups) > 0
for i = 1:length(groups)
for j = 1:length(groups{i}.variables);
v = groups{i}.variables{j};
v = v(v>1)-1;
groups{i}.variables{j} = v;
end
end
end
else
groups = {};
end
p.sdpsymmetry = groups;
function p_lp = add3x3sdpsymmetrycut(p,p_lp,x)
for j = 1:length(p.sdpsymmetry)
excludes = [];
n = sqrt(size(p.sdpsymmetry{j}.dataBlock,1));
for i = 1:length(p.sdpsymmetry{j}.variables)
if min(eig(reshape(p.sdpsymmetry{j}.dataBlock*[1;x(p.sdpsymmetry{j}.variables{i})],n,n))) < -abs(p_lp.options.bnb.feastol)
excludes = [excludes x(p.sdpsymmetry{j}.variables{i})];
end
end
if ~isempty(excludes)
newF = [];
infeasible_combinations = unique(excludes','rows')';
for k = 1:size(infeasible_combinations,2)
% Local cut for reduced set
[b,atemp] = exclusionCut(infeasible_combinations(:,k),-1);
% Add that cut for every variable groups
for s = 1:length(p.sdpsymmetry{j}.variables)
a = spalloc(1,length(p_lp.c),1);
a(p.sdpsymmetry{j}.variables{s}) = atemp;
if all(sum(abs(p_lp.F_struc - [b a]),2)<=1e-12)
newF = [newF;b a];
end
end
end
p_lp = addLinearCut(p_lp,newF);
end
end
function [p,p_lp] = addSymmetryCuts(p,p_lp)
for j = 1:length(p.sdpsymmetry)
if length(p.sdpsymmetry{j}.variables{1}) <= 4
% We can enumerate easily infeasibles
excludes = [];
n = sqrt(size(p.sdpsymmetry{j}.dataBlock,1));
combs = -dec2decbin(0:2^length(p.sdpsymmetry{j}.variables{1})-1,length(p.sdpsymmetry{j}.variables{1}))';
for i = 1:size(combs,2)
if min(eig(reshape(p.sdpsymmetry{j}.dataBlock*[1;combs(:,i)],n,n))) < -abs(p_lp.options.bnb.feastol)
excludes = [excludes combs(:,i)];
end
end
if ~isempty(excludes)
newF = [];
infeasible_combinations = unique(excludes','rows')';
for k = 1:size(infeasible_combinations,2)
% Local cut for reduced set
[b,atemp] = exclusionCut(infeasible_combinations(:,k),-1);
% Add that cut for every variable groups
for s = 1:length(p.sdpsymmetry{j}.variables)
a = spalloc(1,length(p_lp.c),1);
a(p.sdpsymmetry{j}.variables{s}) = atemp;
newF = [newF;b a];
end
end
p_lp = addLinearCut(p_lp,newF);
% Delete, won't need these in the future
p.sdpsymmetry{j}.dataBlock = [];
p.sdpsymmetry{j}.variables = [];
end
end
end
function p = addLinearCut(p,row);
if sum(p.K.s) == 0 && sum(p.K.q)==0
% Just append in the end
p.F_struc = [p.F_struc;row];
else
% Insert before conics
p.F_struc = [p.F_struc(1:(p.K.f+p.K.l),:);row;p.F_struc(1+p.K.f+p.K.l:end,:)];
end
p.K.l = p.K.l + size(row,1);