422 lines
15 KiB
Matlab
Executable File
422 lines
15 KiB
Matlab
Executable File
function [F,failure,cause,ALREADY_MODELLED] = expandmodel(F,h,options,w)
|
|
|
|
% FIX : Current code experimental, complex, conservative, has issues with
|
|
% nonlinearities and is slow...
|
|
%
|
|
% If it wasn't for polynomials and sigmonials, it would be trivial, but the
|
|
% code is extremely messy since we want to have this functionality too
|
|
|
|
global LUbounds
|
|
global ALREADY_MODELLED
|
|
global MARKER_VARIABLES
|
|
global DUDE_ITS_A_GP
|
|
global ALREADY_MODELLED_INDEX
|
|
global REMOVE_THESE_IN_THE_END
|
|
global OPERATOR_IN_POLYNOM
|
|
global CONSTRAINTCUTSTATE
|
|
|
|
% All extended variables in the problem. It is expensive to extract this
|
|
% one so we will keep it and pass it along in the recursion
|
|
extendedvariables = yalmip('extvariables');
|
|
|
|
integers = yalmip('intvariables');
|
|
binaries = yalmip('binvariables');
|
|
if ~isempty(F)
|
|
declareI = find(is(F,'integer'));
|
|
declareB = find(is(F,'binary'));
|
|
if ~isempty(declareI)
|
|
integers = [integers depends(F(declareI))];
|
|
end
|
|
if ~isempty(declareB)
|
|
binaries = [binaries depends(F(declareB))];
|
|
end
|
|
end
|
|
yalmip('settempintvariables',integers);
|
|
yalmip('settempbinvariables',binaries);
|
|
|
|
% We keep track of all auxilliary variables introduced by YALMIP
|
|
nInitial = yalmip('nvars');
|
|
|
|
boundsAlreadySet = 0;
|
|
% Meta constraints are expanded first (iff, implies, alldifferent etc)
|
|
if ~isempty(F)
|
|
meta = find(is(F,'meta'));
|
|
if ~isempty(meta)
|
|
LUbounds=setupBounds(F,options,extendedvariables);
|
|
boundsAlreadySet = 1;
|
|
F = expandmeta(F);
|
|
end
|
|
end
|
|
|
|
if isa(F,'constraint')
|
|
F = lmi(F);
|
|
end
|
|
|
|
if nargin < 3
|
|
options = sdpsettings;
|
|
end
|
|
|
|
if isempty(options)
|
|
options = sdpsettings;
|
|
end
|
|
|
|
if isfield(options,'reusemodel')
|
|
|
|
else
|
|
|
|
ALREADY_MODELLED = [];
|
|
|
|
% Temporary hack to deal with a bug in CPLEX. For the implies operator (and
|
|
% some more) YALMIP creates a dummy variable x with (x==1). Cplex fails
|
|
% to solve problem with these stupid variables kept, hence we need to
|
|
% remove these variables and constraints...
|
|
MARKER_VARIABLES = [];
|
|
|
|
% Temporary hack to deal with geometric programs. GPs are messy here,
|
|
% becasue we can by mistake claim nonconvexity, since we may have no
|
|
% sigmonial terms but indefinite quadratic term, but the whole problem is
|
|
% meant to be solved using a GP solver. YES, globals suck, but this is
|
|
% only temporary...hrm.
|
|
DUDE_ITS_A_GP = 0;
|
|
|
|
% Keep track of expressions that already have been modelled. Note that if a
|
|
% graph-model already has been constructed but we now require a milp, for
|
|
% numerical reasons, we should remove the old graph descriptions (important
|
|
% for MPT models in particular)
|
|
% FIX: Pre-parse the whole problem etc (solves the issues with GP also)
|
|
ALREADY_MODELLED = {};
|
|
ALREADY_MODELLED_INDEX = [];
|
|
REMOVE_THESE_IN_THE_END = [];
|
|
|
|
% Nonlinear operator variables are not allowed to be used in polynomial
|
|
% expressions, except if they are exactly modelled, i.e. modelled using
|
|
% MILP models. We will expand the model and collect variables that are in
|
|
% polynomials, and check in the end if they are exaclty modelled
|
|
OPERATOR_IN_POLYNOM = [];
|
|
end
|
|
% Assume success
|
|
failure = 0;
|
|
cause = '';
|
|
|
|
% Early bail out
|
|
if isempty(extendedvariables)
|
|
return
|
|
end
|
|
|
|
if nargin < 4
|
|
w = [];
|
|
end
|
|
|
|
if isempty(F) & isempty(h)
|
|
return
|
|
end
|
|
|
|
% Check if it already has ben expanded in robustify or similar
|
|
F_alreadyexpanded = [];
|
|
if ~isempty(F)
|
|
F_alreadyexpanded = [];
|
|
already_expanded = expanded(F);
|
|
if all(already_expanded)
|
|
if isempty(setdiff(getvariables(h),expanded(h)))
|
|
return
|
|
end
|
|
elseif any(already_expanded)
|
|
F_alreadyexpanded = F(find(already_expanded));
|
|
F = F(find(~already_expanded));
|
|
end
|
|
end
|
|
|
|
if ~isempty(F)
|
|
% Extract all simple bounds from the model, and update the internal bounds
|
|
% in YALMIP. This is done in order to get tighter big-M models
|
|
|
|
if boundsAlreadySet == 0;
|
|
LUbounds = setupBounds(F,options,extendedvariables);
|
|
end
|
|
|
|
% Expand equalities first, since these might generate nonconvex models,
|
|
% thus making it unnecessaryu to generate epigraphs etc
|
|
equalities = is(F,'equality');
|
|
if any(equalities)
|
|
F = [F(find(equalities));F(find(~equalities))];
|
|
end
|
|
end
|
|
|
|
% All variable indicies used in the problem
|
|
v1 = getvariables(F);
|
|
v2 = depends(F);
|
|
v3 = getvariables(h);
|
|
v4 = depends(h);
|
|
|
|
% HACK: Performance for LARGE-scale dualizations
|
|
if isequal(v3,v4) & isequal(v1,v2)
|
|
variables = uniquestripped([v1 v3]);
|
|
else
|
|
variables = uniquestripped([v1 v2 v3 v4]);
|
|
end
|
|
|
|
% Index to variables modeling operators
|
|
extended = find(ismembcYALMIP(variables,extendedvariables));
|
|
|
|
if nargin < 3
|
|
options = sdpsettings;
|
|
end
|
|
|
|
% This is a tweak to allow epxansion of bilinear terms in robust problems,
|
|
% is expression such as abs(x*w) < 1 for all -1 < w < 1
|
|
% This field is set to 1 in robustify and tells YALMIP to skip checking for
|
|
% polynomial nonconvexity in the propagation
|
|
if ~isfield(options,'expandbilinear')
|
|
options.expandbilinear = 0;
|
|
end
|
|
|
|
% Monomial information. Expensive to retrieve, so we pass this along
|
|
[monomtable,variabletype] = yalmip('monomtable');
|
|
|
|
% Is this trivially a GP, or meant to be at least?
|
|
if strcmpi(options.solver,'gpposy') | strcmpi(options.solver,'fmincon-geometric') | strcmpi(options.solver,'mosek-geometric')
|
|
DUDE_ITS_A_GP = 1;
|
|
else
|
|
if ~isequal(options.solver,'fmincon') & ~isequal(options.solver,'snopt') & ~isequal(options.solver,'ipopt') & ~isequal(options.solver,'') & ~isequal(options.solver,'mosek')
|
|
% User has specified some other solver, which does not
|
|
% support GPs, hence it cannot be intended to be a GP
|
|
DUDE_ITS_A_GP = 0;
|
|
else
|
|
% Check to see if there are any sigmonial terms on top-level
|
|
DUDE_ITS_A_GP = ~isempty(find(variabletype(variables) == 4));
|
|
end
|
|
end
|
|
|
|
% Constraints generated during recursive process to model operators
|
|
F_expand = ([]);
|
|
|
|
if isempty(F)
|
|
F = ([]);
|
|
end
|
|
|
|
% First, check the objective
|
|
variables = uniquestripped([depends(h) getvariables(h)]);
|
|
monomtable = monomtable(:,extendedvariables);
|
|
|
|
% However, some of the variables are already expanded (expand can be called
|
|
% sequentially from solvemp and solverobust)
|
|
variables = setdiff1D(variables,expanded(h));
|
|
|
|
% Determine if we should aim for MILP/EXACT model directly
|
|
if options.allowmilp == 2
|
|
method = 'exact';
|
|
else
|
|
method = 'graph';
|
|
end
|
|
|
|
if DUDE_ITS_A_GP == 1
|
|
options.allowmilp = 0;
|
|
method = 'graph';
|
|
end
|
|
|
|
% Test for very common special case with only norm expression
|
|
ExtendedMap = yalmip('extendedmap');
|
|
fail = 0;
|
|
if 0%length(ExtendedMap) > 0 && all(strcmp('norm',{ExtendedMap.fcn}))
|
|
for i = 1:length(ExtendedMap)
|
|
if ~isequal(ExtendedMap(i).arg{2},2)
|
|
fail = 1;
|
|
break;
|
|
end
|
|
if ~isreal(ExtendedMap(i).arg{1})
|
|
fail = 1;
|
|
break;
|
|
end
|
|
if any(ismembcYALMIP(getvariables(ExtendedMap(i).arg{1}),extendedvariables))
|
|
fail = 1;
|
|
break;
|
|
end
|
|
end
|
|
for i = 1:length(ExtendedMap)
|
|
F_expand = [F_expand, cone(ExtendedMap(i).arg{1},ExtendedMap(i).var)];
|
|
end
|
|
F = F + lifted(F_expand,1);
|
|
return
|
|
end
|
|
% *************************************************************************
|
|
% OK, looks good. Apply recursive expansion on the objective
|
|
% *************************************************************************
|
|
index_in_extended = find(ismembcYALMIP(variables,extendedvariables));
|
|
allExtStructs = yalmip('extstruct');
|
|
if ~isempty(index_in_extended)
|
|
[F_expand,failure,cause] = expand(index_in_extended,variables,h,F_expand,extendedvariables,monomtable,variabletype,'objective',0,options,method,[],allExtStructs,w);
|
|
end
|
|
|
|
% *************************************************************************
|
|
% Continue with constraints
|
|
% *************************************************************************
|
|
constraint = 1;
|
|
all_extstruct = yalmip('extstruct');
|
|
while constraint <=length(F) & ~failure
|
|
|
|
if ~already_expanded(constraint)
|
|
Fconstraint = F(constraint);
|
|
variables = uniquestripped([depends(Fconstraint) getvariables(Fconstraint)]);
|
|
|
|
% If constraint is a cut, all generated constraints must be marked
|
|
% as cuts too
|
|
CONSTRAINTCUTSTATE = getcutflag(Fconstraint);
|
|
[ix,jx,kx] = find(monomtable(variables,:));
|
|
if ~isempty(jx) % Bug in 6.1
|
|
if any(kx>1)
|
|
OPERATOR_IN_POLYNOM = [OPERATOR_IN_POLYNOM extendedvariables(jx(find(kx>1)))];
|
|
end
|
|
end
|
|
|
|
index_in_extended = find(ismembcYALMIP(variables,extendedvariables));
|
|
if ~isempty(index_in_extended)
|
|
if is(Fconstraint,'equality')
|
|
if options.allowmilp | options.allownonconvex
|
|
[F_expand,failure,cause] = expand(index_in_extended,variables,-sdpvar(Fconstraint),F_expand,extendedvariables,monomtable,variabletype,['constraint #' num2str(constraint)],0,options,'exact',[],allExtStructs,w);
|
|
else
|
|
failure = 1;
|
|
cause = ['integer model required for equality in constraint #' num2str(constraint)];
|
|
end
|
|
else
|
|
[F_expand,failure,cause] = expand(index_in_extended,variables,-sdpvar(Fconstraint),F_expand,extendedvariables,monomtable,variabletype,['constraint #' num2str(constraint)],0,options,method,[],allExtStructs,w);
|
|
end
|
|
end
|
|
end
|
|
constraint = constraint+1;
|
|
end
|
|
|
|
CONSTRAINTCUTSTATE = 0;
|
|
|
|
% *************************************************************************
|
|
% Temporary hack to fix the implies operator (cplex has some problem on
|
|
% these trivial models where a variable only is used in x==1
|
|
% FIX: Automatically support this type of nonlinear operators
|
|
% *************************************************************************
|
|
if ~isempty(MARKER_VARIABLES)
|
|
MARKER_VARIABLES = sort(MARKER_VARIABLES);
|
|
equalities = find(is(F,'equality'));
|
|
equalities = equalities(:)';
|
|
remove = [];
|
|
for j = equalities
|
|
v = getvariables(F(j));
|
|
if length(v)==1
|
|
if ismembcYALMIP(v,MARKER_VARIABLES)
|
|
remove = [remove j];
|
|
end
|
|
end
|
|
end
|
|
if ~isempty(remove)
|
|
F(remove) = [];
|
|
end
|
|
end
|
|
|
|
nNow = yalmip('nvars');
|
|
if nNow > nInitial
|
|
% YALMIP has introduced auxilliary variables
|
|
% We mark these as auxilliary
|
|
yalmip('addauxvariables',nInitial+1:nNow);
|
|
end
|
|
|
|
|
|
F_expand = lifted(F_expand,1);
|
|
% *************************************************************************
|
|
% We are done. We might have generated some stuff more than once, but
|
|
% luckily we keep track of these mistakes and remove them in the end (this
|
|
% happens if we have constraints like (max(x)<1) + (max(x)>0) where
|
|
% the first constraint would genrate a graph-model but the second set
|
|
% creates a integer model.
|
|
% *************************************************************************
|
|
if ~failure
|
|
F = F + F_expand;
|
|
if length(REMOVE_THESE_IN_THE_END) > 0
|
|
F = F(find(~ismember(getlmiid(F),REMOVE_THESE_IN_THE_END)));
|
|
end
|
|
end
|
|
|
|
% *************************************************************************
|
|
% Normally, operators are not allowed in polynomial expressions. We do
|
|
% however allow this if the variable has been modelled with an exact MILP
|
|
% model.
|
|
% *************************************************************************
|
|
if ~failure
|
|
dummy = unique(OPERATOR_IN_POLYNOM);
|
|
if ~isempty(dummy)
|
|
for i = 1:length(dummy)
|
|
aux(i,1) = find(ALREADY_MODELLED_INDEX == dummy(i));
|
|
end
|
|
% Final_model = {ALREADY_MODELLED{unique(OPERATOR_IN_POLYNOM)}};
|
|
Final_model = {ALREADY_MODELLED{aux}};
|
|
for i = 1:length(Final_model)
|
|
if ~(strcmp(Final_model{i}.method,'integer') | strcmp(Final_model{i}.method,'exact') | options.allownonconvex)
|
|
failure = 1;
|
|
cause = 'Nonlinear operator in polynomial expression.';
|
|
return
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
% Append the previously appended
|
|
F = F + F_alreadyexpanded;
|
|
% Declare this model as expanded
|
|
F = expanded(F,1);
|
|
|
|
function [F_expand,failure,cause] = expand(index_in_extended,variables,expression,F_expand,extendedvariables,monomtable,variabletype,where,level,options,method,extstruct,allExtStruct,w)
|
|
global DUDE_ITS_A_GP ALREADY_MODELLED ALREADY_MODELLED_INDEX REMOVE_THESE_IN_THE_END OPERATOR_IN_POLYNOM
|
|
|
|
% *************************************************************************
|
|
% Go through all parts of expression to check for convexity/concavity
|
|
% First, a small gateway function before calling the recursive stuff
|
|
% *************************************************************************
|
|
if ~DUDE_ITS_A_GP
|
|
[ix,jx,kx] = find(monomtable(variables,:));
|
|
if ~isempty(jx) % Bug in 6.1
|
|
if any(kx>1)
|
|
OPERATOR_IN_POLYNOM = [OPERATOR_IN_POLYNOM extendedvariables(jx(find(kx>1)))];
|
|
end
|
|
end
|
|
end
|
|
|
|
failure = 0;
|
|
j = 1;
|
|
|
|
try
|
|
% Optimized for 6.5 and higher (location in ismember). If user has
|
|
% older version, it will crash and go to code below
|
|
expression_basis = getbase(expression);
|
|
expression_vars = getvariables(expression);
|
|
[yesno,location] = ismember(variables(index_in_extended),expression_vars);
|
|
ztemp = recover(variables(index_in_extended));
|
|
while j<=length(index_in_extended) & ~failure
|
|
% i = index_in_extended(j);
|
|
% zi = recover(variables(i));
|
|
zi = ztemp(j);%recover(variables(i));
|
|
basis = expression_basis(:,1 + location(j));
|
|
if all(basis == 0) % The nonlinear term is inside a monomial
|
|
if options.allownonconvex
|
|
[F_expand,failure,cause] = expandrecursive(zi,F_expand,extendedvariables,monomtable,variabletype,where,level+1,options,'exact',[],'exact',allExtStruct,w);
|
|
else
|
|
failure = 1;
|
|
cause = 'Possible nonconvexity due to operator in monomial';
|
|
end
|
|
%[F_expand,failure,cause] = expandrecursive(zi,F_expand,extendedvariables,monomtable,variabletype,where,level+1,options,method,[],'exact',allExtStruct,w);
|
|
elseif all(basis >= 0)
|
|
[F_expand,failure,cause] = expandrecursive(zi,F_expand,extendedvariables,monomtable,variabletype,where,level+1,options,method,[],'convex',allExtStruct,w);
|
|
else
|
|
[F_expand,failure,cause] = expandrecursive(zi,F_expand,extendedvariables,monomtable,variabletype,where,level+1,options,method,[],'concave',allExtStruct,w);
|
|
end
|
|
j=j+1;
|
|
end
|
|
catch
|
|
while j<=length(index_in_extended) & ~failure
|
|
i = index_in_extended(j);
|
|
basis = getbasematrix(expression,variables(i));
|
|
if all(basis >= 0)
|
|
[F_expand,failure,cause] = expandrecursive(recover(variables(i)),F_expand,extendedvariables,monomtable,variabletype,where,level+1,options,method,[],'convex',allExtStruct,w);
|
|
else
|
|
[F_expand,failure,cause] = expandrecursive(recover(variables(i)),F_expand,extendedvariables,monomtable,variabletype,where,level+1,options,method,[],'concave',allExtStruct,w);
|
|
end
|
|
j=j+1;
|
|
end
|
|
end
|