suprisingly complicated optimization problem
    26 vues (au cours des 30 derniers jours)
  
       Afficher commentaires plus anciens
    
I have the following constrained (global) optimization problem:
For a  user defined sorted real values vector:
xi = [xi(1), ... , xi(N+1)]
I need to find unknown vector:
x = [x(1), ..., x(L)]
where the integer L is the unknown length of the vector x (L>=0, L = 0 is the trivial case)
and the unknown vector x = [x(1), ..., x(L)] must satisfy the following specific conditions:
===============================================================
0. The new "refined" sorted vector xo = union(xi,x), where length(xo) = L+N+1
should to fulfil the following set of conditions:
1. min(xo) = xo(1) = xi(1), max(xo) = xo(L+N+1) = xi(N+1)
2. q = max(z(j)/z(j+1),z(j+1)/z(j)) < q_max, for j = 1, 2, ... L+N 
   where   z(j) = xo(j+1) - xo(j) , z = diff(xo), length(z) = L+N
and q_max is user defined max ratio, where q_max > 1 (typically q_max ~ 1.05 - 1.2)
3. min(z) -> maximal, minimal distance between xo vector elements should be maximized. 
It is obvious that for small L the constraint conditions (2) is not possible to satisfy. 
===============================================================
The motivation of this problem is the creation of the so called, "homogenized" 1-D grid, where consecutive distances between elements of vector xo are relatively "slowly" changing. 
I will be very happy for any recommendation how to effectively solve this problem using MATLAB + (global) optimization toolbox.
See additional function to generate "realistic" (in my case) test input data xi:
function [x,q,x_base,x_aux] = nodegridtest(shift,show)
%
% input:
% shift ... discrete shift of aux nodes 0<=shift<=125 
% show  ... logical flag for visualization
%
% output:
% x      ... test vector
% q      ... nodes ratio vector
% x_base ... fixed part 
% x_aux  ... shifted part
% check input
if ~(0 <= shift && shift <= 125.0)
    error('shift must be in the interval [0 , 125]')
else
    shift = 2*fix(shift);
end
if nargin < 2
    show = false;
end
% realistic data generation
z_base = [7.0,7.0, ...                                    
          2.0, ...                                        
          3.4, ...                                        
          0.6, ...                                        
          7.25,7.25,7.25,7.25,7.25,7.25,7.25,7.25, ...    
          7.25,7.25,7.25,7.25,7.25,7.25,7.25,7.25, ...    
          7.25,7.25,7.25,7.25,7.25,7.25,7.25,7.25, ...    
          7.25,7.25,7.25,7.25,7.25,7.25,7.25,7.25];       
z_aux  = [3.4, ...                                       
          0.6, ...                                       
          6.0, ...                                       
          1.0, ...                                       
          8.0, ...                                       
          7.0, ...                                       
         17.5, ...                                       
          3.6, ...                                       
          6.4, ...                                       
          240.0];                                        
% upper limit of x 
xmax = sum(z_base) + sum(z_aux(1:6));   
% 
x_base        = [0, cumsum(z_base)];
x_aux         = x_base(end)+ [0, cumsum(z_aux)];
x_aux_actual  = x_aux - shift;
x_full        = union(x_base,x_aux_actual);
x             = [x_full(x_full < xmax),xmax];
x_aux         = [x_aux_actual(x_aux_actual < xmax),xmax];
z             = diff(x);
q             = max([z(1:end-1)./z(2:end);z(2:end)./z(1:end-1)],[],1);
if show
    plot(x(2:end-1),q,'bo-','LineWidth',3)
    title(['q distribution for shift = ' num2str(shift)])
    xlabel('position');ylabel('q ratio');
    hold on
    xline(x_base,'k-')
    xline(x_aux,'k--')
    hold off
end
then simple generate test data for any integer 0<=shift<=125 like:
xi = nodegridtest(69,1)
to get

and
xi'
ans =
         0
    7.0000
   14.0000
   16.0000
   19.4000
   20.0000
   27.2500
   34.5000
   41.7500
   49.0000
   56.2500
   63.5000
   70.7500
   78.0000
   85.2500
   92.5000
   99.7500
  107.0000
  114.0000
  114.2500
  117.4000
  118.0000
  121.5000
  124.0000
  125.0000
  128.7500
  133.0000
  136.0000
  140.0000
  143.2500
  150.5000
  157.5000
  157.7500
  161.1000
  165.0000
  167.5000
  172.2500
  179.5000
  186.7500
  194.0000
  201.2500
  208.5000
  215.7500
  223.0000
  230.2500
  237.5000
  244.7500
  252.0000
  278.0000
9 commentaires
Réponses (4)
  Matt J
      
      
 le 24 Sep 2025
        
      Modifié(e) : Matt J
      
      
 le 25 Sep 2025
  
      My thought is that you,
(1) Do not include L in your list of unknowns. Solve a series of optimization problems for fixed L=1,2,3,...
(2) Treat xo, not x, as your independent, unknown vector.
With (1) and (2), all of your listed constraints become linear, recognizing that
max(A/B,C/D)<=E
is the same as the linear constraints,
A<=B*E
C<=D*E
The only complicated constraint to express is that xi ⊂ xo. This can be done by introducing an unknown binary N+1 x (N+1+L) matrix variable P with the linear constraints,
|xi - x0|<=2*max(abs(xi)) .* (1-P)
sum(P,1)==1
sum(P,2)>=2
The objective would be rewritten as,
max q
s.t.
  q<=z  %additional linear constraints
This whole thing is an MILP, and can be solved with intlinprog. No need for a global solver. I would recommend setting it up with optimproblem.
13 commentaires
  Matt J
      
      
 le 24 Sep 2025
				
      Modifié(e) : Matt J
      
      
 le 24 Sep 2025
  
			1. condition (1) is not satisfied reliably when I create the data differently 
It's because xo.Lower was hardcoded to 0. I've fixed it in the revision below.
Any idea how to speed up solver?
Not sure. Maybe by providing initial guesses for the unknowns? The example below includes one way of doing this, but you would have to try it out on realistic data sizes.
%Problem data
xi=[sort(rand(1,5))]';
L=10;
qmax=1.2;
N=numel(xi)-1;
xiMax=max(xi);
xiMin=min(xi);
U=max(abs(xiMin), abs(xiMax));
eNL=ones(N+L+1,1);
eN=ones(N+1,1);
%Unknowns
q =  optimvar('q');
P =  optimvar('P',N+1, L+N+1, Type='integer', Lower=0,Upper=1);
xo = optimvar('xo',L+N+1, Lower=xiMin,Upper=xiMax);
z=diff(xo);
%Constraints
Constraints.con_mono=z>=0; %monotonicity of xo
Constraints.conq= q<=z;    %defines objective value q 
Constraints.con2a_1=z(1:end-1)<=qmax*z(2:end);  %qmax constraints
Constraints.con2a_2=z(2:end)<=qmax*z(1:end-1);
Constraints.conP_1=xi*eNL'- eN*xo' <=2*U*(1-P); %constraints on P
Constraints.conP_2=eN*xo' - xi*eNL'<=2*U*(1-P);
Constraints.conP_3=sum(P,2)==1;
Constraints.conP_4=sum(P,1)<=1;
%Solve
prob=optimproblem('Objective',q,'ObjectiveSense','max','Constraints', Constraints);
%Possible initial guess
y=linspace(xiMin,xiMax,L+2); y([1,end])=[];
sol0.xo=union(xi',y)';
sol0.P=double(xi==sol0.xo');
sol0.q=min(diff(sol0.xo));
sol = solve(prob,sol0)
xo=sol.xo'
xi'
  Matt J
      
      
 le 25 Sep 2025
        
      Modifié(e) : Matt J
      
      
 le 25 Sep 2025
  
      Here's another formulation, closer to your original one. It uses fmincon and is really fast (when the problem is feasible), but one of the constraints is nonconvex and only piecewise differentiable. You will probably have to incorporate MultiStart to help ensure global optimality.
%Problem data
xi=[sort(rand(1,5))]';
L=10;
qmax=1.2;
N=numel(xi)-1;
xiMax=max(xi);
xiMin=min(xi);
U=max(abs(xiMin), abs(xiMax));
eNL=ones(N+L+1,1);
eN=ones(N+1,1);
%Unknowns
x = optimvar('x', L, Lower=xiMin,Upper=xiMax);
z = optimvar('z', L+N, Lower=0,Upper=xiMax-xiMin);
fobj = optimvar('fobj', Lower=0);
%Constraints
fcn=@(c)diff(sort([c(:);xi(:)]));
Constraints.con_zdef= z(:) == fcn2optimexpr(fcn,x);
Constraints.con2a_1=z(1:end-1)<=qmax*z(2:end);  %qmax constraints
Constraints.con2a_2=z(2:end)<=qmax*z(1:end-1);
Constraints.obj=fobj<=z;
%Solve
prob=optimproblem('Objective',fobj,'ObjectiveSense','max','Constraints', Constraints);
sol0.x=linspace(xiMin,xiMax,L)';
sol0.z=fcn(sol0.x);
sol0.fobj=min(sol0.z);
opts=optimoptions('fmincon','StepTol',1e-10,'OptimalityTol',1e-10,'FunctionTol',1e-10, ...
                        'MaxFunEvals',1e7,'MaxIterations',1e5);
sol = solve(prob, sol0,Options=opts)
xo=union(xi,sol.x)'
xi'
3 commentaires
  Matt J
      
      
 le 25 Sep 2025
				No, the output above took a blink of an eye for me. The problem is that sometimes the randomly generated problem data doesn't give an optimization problem with a feasible solution. It can take fmincon a very long time to figure out that the optimization problem is infeasible.
  Matt J
      
      
 le 26 Sep 2025
				It can take fmincon a very long time to figure out that the optimization problem is infeasible.
You can abort the optimization if it runs to long using an OuputFcn,
  Matt J
      
      
 le 29 Sep 2025
        
      Modifié(e) : Matt J
      
      
 le 30 Sep 2025
  
      This is another suggested strategy: to find the minimum possible qmax, given L. This seems at the very least like an important first step, since you need to see if the qmax values you're pursuing are even achievable.
I demonstrate using the xi data generated in your posted example,
xi = nodegridtest(69,1)
clear,clc,close all
%Problem data
load xiData
tic;
[~,qmax]=runOptimization(xi, numel(xi))
toc;
tic;
[~,qmax]=runOptimization(xi, 5*numel(xi))
toc;
function [xo,qmax]=runOptimization(xi,L)
% Inputs:
%     xi - Description of input xi
%     L - Description of input L
%
% Outputs:
%     xo - Description of output xo
%     qmax - Description of output qmax
arguments
 xi   (:,1) double
 L    (1,1) double {mustBeInteger}
end
    N=numel(xi)-1;
    xiMax=max(xi);
    xiMin=min(xi);
    fcn=@(c)log(diff(sort([c(:);xi(:)])));
    %Unknowns
    x = optimvar('x', L, Lower=xiMin,Upper=xiMax);
    logz = optimvar('logz', L+N, Lower=-inf,Upper=log(xiMax-xiMin));
    y = optimvar('y', Lower=0); %log(qmax)
    %Constraints
    Constraints.qmax_1=diff(logz)<=y;  %qmax constraints
    Constraints.qmax_2=-diff(logz)<=y;
    Constraints.nlcon=x(1)^2<=1; %dummy
    %Solve
    prob=optimproblem('Objective',y,'ObjectiveSense','min','Constraints', Constraints);
    sol0.x=initialX(L,xi);
    sol0.logz=fcn(sol0.x);
    sol0.y=norm(sol0.logz,inf);
    opts=optimoptions('fmincon','StepTol',1e-6,'OptimalityTol',1e-6,'FunctionTol',1e-6, ...
                            'MaxFunEvals',1e7,'MaxIterations',1e3,'SpecifyObjectiveGradient',true, ...
                            'SpecifyConstraintGradient',true,'Display','final','Algorithm','sqp');
    Prob=prob2struct(prob,sol0,'Solver','fmincon');
    Prob.nonlcon=@(vars)nlcon(vars,xi,N,L); %
    Prob.options=opts;
    vars = fmincon(Prob);
       m=N+L+1;
       vars=vars(:);
       logz=vars(1:m-1); %#ok<NASGU>
       x=vars(m+1:m+L);
       y=vars(m);
       qmax=exp(y);
    xo=union(xi,x)';
end
function [c,ceq,cgrad, ceqgrad] = nlcon(vars,xi,N,L)
   c=[]; cgrad=[];
   m=N+L+1;
   vars=vars(:);
   logz=vars(1:m-1);
   x=vars(m+1:m+L);
   [xo,is]=sort([x;xi]);
   z=diff(xo);
   ceq = logz - log(z);
   if nargout>3
       P=sparse(1:m,is,1, m,m);
       P=P(:,1:L);
       Jxo=diff(P,1,1)./z;
       ceqgrad=[speye(m-1,m), -Jxo].';
   end
end
function x=initialX(L,xi)
 N=numel(xi)-1;
 xiMax=max(xi);
 xiMin=min(xi);
 dL0 = diff(xi)./(xiMax-xiMin)*L;
 opts=optimoptions('intlinprog','Display','none');
 dL=minL1intlin(speye(N),round(dL0(:)),1:N,[],[],ones(1,N),L,zeros(N,1),...
                [],[],opts );
 K=numel(xi)-1;
 xo=cell(1,K);
 for k=1:numel(xi)-1
  tmp=linspace(xi(k), xi(k+1),dL(k)+2);
  xo{k}=tmp(1:end-1);
 end
 xo=[cell2mat(xo),xi(end)];
 x=setdiff(xo,xi);
end
  Matt J
      
      
 le 25 Sep 2025
        
      Modifié(e) : Matt J
      
      
 le 25 Sep 2025
  
      The motivation of this problem is the creation of the so called, "homogenized" 1-D grid, where consecutive distances between elements of vector xo are relatively "slowly" changing. 
If so, you might consider a simplified solution using minL1intlin from this File Exchange submission,
Basically, this chooses a number of points dL(k) to place between each xi, with sum(dL)==L and  in proportion to the spacing xi(k+1)-xi(k). I think this is almost what you want, though there is no qmax parameter to let you bound the z ratios. If nothing else, it may give you a good way to form an initial guess for one of the other solution methods.
%Problem data
xi=[sort(rand(1,7))]';
L=12;
% qmax=1.2;
N=numel(xi)-1;
xiMax=max(xi);
xiMin=min(xi);
dL0 = diff(xi)./(xiMax-xiMin)*L;
dL=minL1intlin(speye(N),round(dL0(:)),1:N,[],[],ones(1,N),L,zeros(N,1) );
xo=getxo(dL,xi);
check = numel(xo) == L+N+1
h=plot(xi,0*xi,'x',xo,0*xo,'o'); axis padded
set(h,'MarkerSize',8);
legend xi xo
function xo=getxo(dL,xi)
 K=numel(xi)-1;
 xo=cell(1,K);
 for k=1:numel(xi)-1
  tmp=linspace(xi(k), xi(k+1),dL(k)+2);
  xo{k}=tmp(1:end-1);
 end
 xo=[cell2mat(xo),xi(end)];
end
4 commentaires
  Matt J
      
      
 le 1 Oct 2025 à 11:40
				
      Modifié(e) : Matt J
      
      
 le 1 Oct 2025 à 12:07
  
			And qmax is really important for me!
As shown below, (with your posted xi data, and L=3*N) even though the max q ratio is high with this strategy, the majority of the ratios are very low, and the distribution of spacings z(i) has a low variance. Are you sure that's not enough?
%Problem data
load xiData
N=numel(xi)-1;
L=3*N;
xiMax=max(xi);
xiMin=min(xi);
dL0 = diff(xi)./(xiMax-xiMin)*L;
opts=optimoptions('intlinprog','Display','none');
dL=minL1intlin(speye(N),round(dL0(:)),1:N,[],[],ones(1,N),L,zeros(N,1),[],[],opts );
xo=getxo(dL,xi);
z=diff(xo);
q=z(2:end)./z(1:end-1);
q=max(q,1./q);
tiledlayout('h');
nexttile; histogram(q,100); title q-Histogram; axis square
nexttile; histogram(z,100); title z-Histogram; axis square
function xo=getxo(dL,xi)
 K=numel(xi)-1;
 xo=cell(1,K);
 for k=1:numel(xi)-1
  tmp=linspace(xi(k), xi(k+1),dL(k)+2);
  xo{k}=tmp(1:end-1);
 end
 xo=[cell2mat(xo),xi(end)];
end
Voir également
Catégories
				En savoir plus sur Linear Least Squares dans Help Center et File Exchange
			
	Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!





