function [sol,info] = solvebilevel(OuterConstraints,OuterObjective,InnerConstraints,InnerObjective,InnerVariables,options) %SOLVEBILEVEL Simple global bilevel solver % % min OO(x,y) % subject to CO(x,y)>=0 % y = arg min OI(x,y) % subject to CI(x,y)>=0 % % [DIAGNOSTIC,INFO] = SOLVEBILEVEL(CO, OO, CI, OI, y, options) % % diagnostic : Struct with standard YALMIP diagnostics % info : Bilevel solver specific information % % Input % CO : Outer constraints (linear elementwise) % OO : Outer objective (convex quadratic) % CI : Inner constraints (linear elementwise) % OI : Inner objective (convex quadratic) % y : Inner variables % options : solver options from SDPSETTINGS. % % The behaviour of the bilevel solver can be controlled % using the field 'bilevel' in SDPSETTINGS % % bilevel.outersolver : Solver for outer problems with inner KKT removed % bilevel.innersolver : Solver for inner problem % bilevel.rootcut : Number of cuts (based on complementary % constraints) added in root (experimental) % bilevel.relgaptol : Termination tolerance % bilevel.compslacktol: Tolerance for accepting complementary slackness % bilevel.feastol : Tolerance for feasibility in outer problem % % % See also SDPVAR, SDPSETTINGS, SOLVESDP % min f(x,y) s.t g(x,y)<0, y = argmin [x;y]'*H*[x;y]+e'[x;y]+f, E[x;y]0 stationary = [stationary -inner_p.F_struc(1+inner_p.K.f:inner_p.K.f+inner_p.K.l,1+y_var)']; end if length(eqdual_var)>0 stationary = [stationary -inner_p.F_struc(1:inner_p.K.f,1+y_var)']; end p.F_struc = [stationary;p.F_struc spalloc(size(p.F_struc,1),length(dual_var) + length(eqdual_var),0)]; p.K.f = p.K.f + length(y_var); % Add dual>0 to outer model p.F_struc = [p.F_struc(1:p.K.f,:);spalloc(ninequalities,length(x_var)+length(y_var)+1,0) speye(ninequalities) spalloc(ninequalities,nequalities,0);p.F_struc(1+p.K.f:end,:)]; p.K.l = p.K.l + ninequalities; % Add inner level constraints to outer model p.F_struc = [p.F_struc(1:p.K.f,:);inner_p.F_struc spalloc(ninequalities+nequalities,ninequalities+nequalities,0);p.F_struc(1+p.K.f:end,:)]; p.K.f = p.K.f + inner_p.K.f; p.K.l = p.K.l + inner_p.K.l; slack_index = p.K.f+1:+p.K.f+ninequalities; %p.lb = outerinner_p.lb; %p.ub = outerinner_p.ub; p.lb(dual_var) = 0; p.ub(dual_var) = inf; p.lb(eqdual_var) = -inf; p.ub(eqdual_var) = inf; p.x0 = []; %p.variabletype = outerinner_p.variabletype; %p.monomtable = outerinner_p.monomtable; %p.evalMap = outerinner_p.evalMap; %p.evalVariables = outerinner_p.evalVariables; for i = 1:length(dual_var) p.monomtable(dual_var(i),dual_var(i))=1; p.variabletype(dual_var(i)) = 0; end for i = 1:length(eqdual_var) p.monomtable(eqdual_var(i),eqdual_var(i))=1; p.variabletype(eqdual_var(i)) = 0; end % xy = sdpvar(length(x_var)+length(y_var),1); % z = sdpvar(length(dual_var),1); % res = p.F_struc*[1;xy;z] % % F_bilevel = [res(1:p.K.f) == 0,res(p.K.f+1:end)>0] % % Enable outer problem to be nonconvex etc p = build_recursive_scheme(p); % Turned off, generates crash. Unit test in test_bilevel_1 % p = compress_evaluation_scheme(p); p.lower = -inf; p.options.verbose = max([0 options.verbose-1]); p.level = 0; p.as_free = true(ninequalities,1); list{1} = p; lower = -inf; upper = inf; iter = 0; tol = 1e-8; ndomcuts = 0; ninfeascuts = 0; % Extract the inequalities in the inner problem. These are really the % interesting ones inner_p.F_struc = [inner_p.F_struc(1+inner_p.K.f:end,:) spalloc(inner_p.K.l,ninequalities+nequalities,0)]; if options.verbose disp('* Starting YALMIP bilevel solver.'); disp(['* Outer solver : ' outer_p.solver.tag]); disp(['* Inner solver : ' inner_p.solver.tag]); disp(['* Max iterations : ' num2str(p.options.bnb.maxiter)]); disp(' Node Upper Gap(%) Lower Open'); end gap = inf; xsol = []; sol.problem = 0; iter = 0; inner_p = detectdisjoint(inner_p); while length(list)>0 & gap > options.bilevel.relgaptol & iter < options.bilevel.maxiter iter = iter + 1; [p,list,lower] = select(list); Comment = ''; if p.lower continue']; sol.problem = 4; cost = p.lower; end end if costcost upper = cost; xsol = xi; zsol = yi; dualsol = output.Primal(dual_var); end elseif cost>upper-1e-10 ndomcuts = ndomcuts + 1; else % No official code, just playing around if ActuallyFeasible & options.bilevel.solvefrp FRP = FRP0; if 0 FRP = fixvariables(FRP0,x_var,xi,y_var); else FRP.F_struc = [xi -sparse(1:length(x_var),x_var,ones(length(x_var),1),length(x_var),length(x_var)+length(y_var));FRP.F_struc]; FRP.K.f = FRP.K.f + length(xi); FRP.options.verbose = 0; QQ = sparse(FRP0.Q); cc = sparse(FRP0.c); FRP.c(y_var) = FRP.c(y_var) + 2*FRP.Q(x_var,y_var)'*xi; FRP.Q(x_var,y_var)=0; FRP.Q(y_var,x_var)=0; FRP.Q(x_var,x_var)=0; end outputFRP = feval(inner_p.solver.call,FRP); if outputFRP.problem == 0 if 0 z = zeros(length(outer_p.c),1); z(x_var) = xi; z(y_var) = outputFRP.Primal; z2 = apply_recursive_evaluation(p,z); else z2 = apply_recursive_evaluation(p,outputFRP.Primal); end costFRP = z2'*outer_p.Q*z2 + outer_p.c'*z2 + outer_p.f; if costFRP < upper & isfeasible(outer_p,z2) upper = costFRP; xsol = z2(x_var); zsol = z2(y_var); end end end [ii,jj_tmp] = max(res(p.as_free)); ind_tmp = (1:length(res))'; ind_tmp = ind_tmp(p.as_free); jj = ind_tmp(jj_tmp); if strcmp(p.options.solver,'bmibnb') % Since BMIBNB solves a relaxation of relaxation, it % can generate a lower bound which is lower than % the lower bound before a compl. slack constraint % was added. p.lower = max(output.lower,lower); else p.lower = cost; end if iter<=options.bilevel.rootcuts % Add a disjunction cut p = disjunction(p,dual_var(jj),inner_p.F_struc(jj,:),output.Primal); % Put in queuee, it will be pulled back immediately list = {list{:},p}; else p1 = p; p2 = p; % Add dual == 0 on p1 p1.K.f = p1.K.f + 1; p1.F_struc = [zeros(1,size(p1.F_struc,2));p1.F_struc]; p1.F_struc(1,1+dual_var(jj))=1; p1.lb(dual_var(jj)) = -inf; p1.ub(dual_var(jj)) = inf; newequality = p1.F_struc(1,:); redundantinequality = findrows(p1.F_struc(p1.K.f+1:end,:),newequality); if ~isempty(redundantinequality) p1.F_struc(p1.K.f+redundantinequality,:)=[]; p1.K.l = p1.K.l-length(redundantinequality); end % Add slack == 0 p2.K.f = p2.K.f + 1; newequality = inner_p.F_struc(jj,:); p2.F_struc = [newequality;p2.F_struc]; redundantinequality = findrows(p2.F_struc(p2.K.f+1:end,:),newequality); if ~isempty(redundantinequality) p2.F_struc(p2.K.f+redundantinequality,:)=[]; p2.K.l = p2.K.l-length(redundantinequality); end p1.as_free(jj) = false; p2.as_free(jj) = false; if ~isempty(inner_p.disjoints) here = find(inner_p.disjoints(:,1) == j); if ~isempty(here) p1.as_free(inner_p.disjoints(here,2))=false; p2.as_free(inner_p.disjoints(here,2))=false; else here = find(inner_p.disjoints(:,2) == j); if ~isempty(here) p1.as_free(inner_p.disjoints(here,1))=false; p2.as_free(inner_p.disjoints(here,1))=false; end end end p1.level = p.level+1; p2.level = p.level+1; list = {list{:},p1}; list = {list{:},p2}; end end end end else ndomcuts = ndomcuts + 1; end [list,lower] = prune(list,upper); gap = abs((upper-lower)/(1e-3+abs(upper)+abs(lower))); if isnan(gap) gap = inf; end if options.verbose fprintf(' %4.0f : %12.3E %7.2f %12.3E %2.0f %s\n',iter,full(upper),100*full(gap),full(lower),length(list),Comment) end end info.upper = upper; info.iter = iter; info.ninfeascuts = ninfeascuts; info.ndomcuts = ndomcuts; if ~isempty(xsol) assign(recover(all_variables(x_var)),xsol); assign(recover(all_variables(y_var)),zsol); else sol.problem = 1; end function [list,lower] = prune(list,upper) l = []; for i = 1:length(list) l = [l list{i}.lower]; end j = find(upper > l+1e-10); list = {list{j}}; if length(list) == 0 lower = upper; else lower = min(l(j)); end function [p,list,lower] = select(list) l = []; for i = 1:length(list) l = [l list{i}.lower]; end [i,j] = min(l); p = list{j}; list = {list{1:j-1},list{j+1:end}}; lower = min(l); function p = addzero(p,i); p.K.f = p.K.f + 1; p.F_struc = [zeros(1,size(p.F_struc,2));p.F_struc]; p.F_struc(1,1+i)=1; function outer_p = pad(outer_p,all_variables) [i,loc] = find(ismember(all_variables,outer_p.used_variables)); p = outer_p; % Set all bounds to infinite, and then place the known bounds p.lb = -inf(length(all_variables),1); p.lb(loc) = outer_p.lb; p.ub = inf(length(all_variables),1); p.ub(loc) = outer_p.ub; % Set all variables as linear p.variabletype = zeros(1,length(all_variables)); p.variabletype(loc) = outer_p.variabletype; p.c = spalloc(length(all_variables),1,0); p.c(loc) = outer_p.c; if ~isempty(p.F_struc) p.F_struc = spalloc(size(p.F_struc,1),length(all_variables)+1,nnz(p.F_struc)); p.F_struc(:,1) = outer_p.F_struc(:,1); p.F_struc(:,1+loc) = outer_p.F_struc(:,2:end); end % if ~isempty(p.binary_variables) % end p.Q = spalloc(length(all_variables),length(all_variables),nnz(outer_p.Q)); p.Q(loc,loc) = outer_p.Q; outer_p = p; function p = disjunction(p,variable,const,xstar) neq = p.K.f+1; x = sdpvar(length(p.c),1); e = p.F_struc*[1;x]; Model1 = [x(variable)==0,-e(1:p.K.f)==0, e(1+p.K.f:end)>=0]; Model2 = [const*[1;x]==0,-e(1:p.K.f)==0, e(1+p.K.f:end)>=0]; Ab1 = getbase(sdpvar(Model1)); Ab2 = getbase(sdpvar(Model2)); b1 = -Ab1(:,1); A1 = Ab1(:,2:end); b2 = -Ab2(:,1); A2 = Ab2(:,2:end); % b1c = [0;-p.F_struc(:,1)]; % b2c = [const(1);-p.F_struc(:,1)]; % A1c = [-eyev(length(p.c),variable)';p.F_struc(:,2:end)]; % A2c = [-const(2:end);p.F_struc(:,2:end)]; %norm(b1-b1c) %norm(b2-b2c) %norm(A1-A1c,inf) %norm(A2-A2c,inf) alpha = sdpvar(length(xstar),1); beta = sdpvar(1); mu1 = sdpvar(length(b1),1); mu2 = sdpvar(length(b2),1); Objective = alpha'*xstar-beta; Constraint = [alpha' == mu1'*A1,alpha' == mu2'*A2,beta <= mu1'*b1, beta <= mu2'*b2,mu1(neq+1:end)>0,mu2(neq+1:end)>0]; %Constraint = [alpha' == mu1'*A1,alpha' == mu2'*A2,beta == mu1'*b1, beta == mu2'*b2,mu1(neq+1:end)>0,mu2(neq+1:end)>0]; %Constraint = [Constraint,-100,mu2(neq+1:end)>0]; % Constraint = [Constraint,-1