2

I am reading the Matlab user's guide of optimization toolbox. In page 1-15, some codes are provided for creating variables for indexing. Here is the code:

%Combine variables into one vector
variables = {'I1','I2','HE1','HE2','LE1','LE2','C','BF1',...
'BF2','HPS','MPS','LPS','P1','P2','PP','EP'};
N = length(variables);
% create variables for indexing
for v = 1:N
    eval([variables{v},' = ',num2str(v),';']);  %?
end

I know the class of "variables" is cell array. But I cannot understand clearly the function of "eval". To read the following code, It seems to create the index for the elements in variables, so that the elements can be used as the index number used for manipulating matrix or vector. for example:

lb = zeros(size(variables));
lb([P1,P2,MPS,LPS]) = [2500,3000,271536,100623];

I have read the help document, but still cannot get it. So, anyone can explain for me more clearly.

by the way, user's guide suggests avoiding this "eval" function. So, is there any other way to achieve the above feature?

Thanks all

The complete program

% Combine variables into one vector
variables = {'I1','I2','HE1','HE2','LE1','LE2','C','BF1',...
   'BF2','HPS','MPS','LPS','P1','P2','PP','EP'};
N = length(variables);
% create variables for indexing
for v = 1:N
   eval([variables{v},' = ',num2str(v),';']);  %?
end


% Write bound constraints
lb = zeros(size(variables));
lb([P1,P2,MPS,LPS]) = ...
    [2500,3000,271536,100623];

ub = Inf(size(variables));
ub([P1,P2,I1,I2,C,LE2]) = ...
    [6250,9000,192000,244000,62000,142000];

% Write linear inequality constraints
A = zeros(3,N);
A(1,I1) = 1; A(1,HE1) = -1; b(1) = 132000;
A(2,EP) = -1; A(2,PP) = -1; b(2) = -12000;
A(3,[P1,P2,PP]) = [-1,-1,-1]; b(3) = -24550;

% Write linear equality constraints
Aeq = zeros(8,N); beq = zeros(8,1);
Aeq(1,[LE2,HE2,I2]) = [1,1,-1];
Aeq(2,[LE1,LE2,BF2,LPS]) = [1,1,1,-1];
Aeq(3,[I1,I2,BF1,HPS]) = [1,1,1,-1];
Aeq(4,[C,MPS,LPS,HPS]) = [1,1,1,-1];
Aeq(5,[LE1,HE1,C,I1]) = [1,1,1,-1];
Aeq(6,[HE1,HE2,BF1,BF2,MPS]) = [1,1,1,-1,-1];
Aeq(7,[HE1,LE1,C,P1,I1]) = [1267.8,1251.4,192,3413,-1359.8];
Aeq(8,[HE2,LE2,P2,I2]) = [1267.8,1251.4,3413,-1359.8];

% Write the objectvie
f = zeros(size(variables));
f([HPS PP EP]) = [0.002614 0.0239 0.009825];

% Solve the problem
%print out the results in floating-point fromat in a field 12     characters 
%wide, including 2 digits after the decimal point for first data
[x,fval] = linprog(f,A,b,Aeq,beq,lb,ub);
for d = 1:N
    fprintf('%12.2f \t %s \n',x(d),variables{d}); 
end
fval
Rolf_Zhang
  • 23
  • 4
  • 2
    For those wondering, yes this *is* [in the official documentation](https://www.mathworks.com/help/optim/ug/example-linear-programming.html#bsmt5lz). – sco1 Oct 10 '16 at 14:05
  • Yes, it is. Maybe I didn't express myself obviously. Put another way, " eval" is to assign values to the elements in cell array here. is that right? If so, is there any other way to do the same thing? – Rolf_Zhang Oct 10 '16 at 15:53
  • 1
    @Rolf_Zhang sorry for leaving you hanging here with no answer: some of us discussed your question and got distracted by the shock caused by this horrible monster being present in the official documentation:) – Andras Deak -- Слава Україні Oct 10 '16 at 17:33
  • I changed the tags on your question: the [matlab-guide] which you used refers to GUIDE, the GUI design environment of MATLAB (you generally shouldn't use any tags that are related to guides or tutorials). – Andras Deak -- Слава Україні Oct 12 '16 at 17:15

1 Answers1

6

A few of us discussed your question and we're still in shock from the fact that this horrifying monster is in the official documentation:) What that example is doing is an obscene anti-pattern, that is not only unsafe but also highly inefficient. Your suspicions are correct, one should almost never use eval. When possible, one should do the job without eval and dynamic variable names. When it is not possible, one should refactor whatever code they are facing so that it can be solved in a nice, safe, fast and idiomatic way.

The problem here is that the construction itself mandates the use of eval. This is bad. Very bad. So bad that I hardly believed my own eyes when I saw this in the documentation. See this answer and references therein for why eval should be avoided like the plague. Generally speaking, eval executes arbitrary strings which poses a potential entry point for attackers, but honestly most use cases are not accessible to outsiders. However, just-in-time compiling in MATLAB can't optimize anything that is inside dynamic code. Finally, starting to work with dynamic variable names will lead you down an eval rabbit hole from where it is hard to escape.

So, what is the usual alternative to eval, especially in terms of dynamic field names? Cells, or more importantly, structs. I prefer the latter. The main obstacle in making people use structs instead of dynamic variable names is that there's a not-too-widely known feature of structs known as dynamic access of field names. The following two are identical:

% static version
mystruct1 = [];
mystruct1.field1 = 3;

% dynamic version
fname = 'field1';
mystruct2 = [];
mystruct2.(fname) = 3;

isequal(mystruct1,mystruct2)
% yes

So the usual solution to eval problems is using structs with dynamic field names.1

In your case this admittedly leads to difficulties. The notation will get more cumbersome, understandably. But in principle you could ditch the call to eval, and instead set the fields of a single index struct is:

is = [];
for v = 1:N
   % nope eval nope nope nope nope
   is.(variables{v}) = v;
end

The cost is that you have to be a bit less concise later:

Aeq(6,[is.HE1,is.HE2,is.BF1,is.BF2,is.MPS]) = [1,1,1,-1,-1];

I understand that you're reluctant to do so, and the toolbox that suggests using eval this way might hold other surprises for you, but I would probably take this route. The sake of one's mental hygiene and avoiding horrible anti-patterns should be highly motivating.

 

1this also implies that there's a way out from an eval rabbit hole: save your workspace as a .mat file, then load it with dat = load('tmp.mat');: the result will be a struct dat that you can readily access in the way you need to.

Community
  • 1
  • 1
  • @Rolf_Zhang I can't see any `Aeq(6, HE1)` in your original code. Are you sure you changed every occurence of these indices in your original? If that didn't throw an error for you, the new one shouldn't either. – Andras Deak -- Слава Україні Oct 11 '16 at 10:45
  • It is short one for Aeq(6,[is.HE1,is.HE2,is.BF1,is.BF2,is.MPS]) = [1,1,1,-1,-1]; I modified every patient element in my code as same as you told. But something is wrong. It displays error:in an assignment A(:) = B, the number of elements in A and B must be the same. I check the document relative to structure array, I discover some interest thing. In the above command line, [is.HE1,...] does not stand for the six separate indexing for matrix Aeq. Instead, it is just one number. So the left of above assignment stands for the sixth row and 348911th column. But this is not I want. – Rolf_Zhang Oct 11 '16 at 11:10
  • @Rolf_Zhang I don't think I understand. Are you saying that `Aeq(6,[is.HE1,is.HE2,is.BF1,is.BF2,is.MPS])` and `Aeq(6,[HE1,HE2,BF1,BF2,MPS])` are different? Can you check that `HE1==is.HE1` etc.? The error you're describing says that you have a different number of elements on the left and right hand side. That just doesn't make sense. – Andras Deak -- Слава Україні Oct 11 '16 at 11:14
  • The value of HE1 is equal to is.HE1. But the mean of [is.HE1,is.HE2,is.BF1,is.BF2,is.MPS] distinct from original one. I think "is" is structure array causing that. – Rolf_Zhang Oct 11 '16 at 11:21
  • In the original code, I want to assign six different number to the the matrix Aeq in sixth row but different six columns. But the modified code cannot do that. – Rolf_Zhang Oct 11 '16 at 11:23
  • @Rolf_Zhang I find all this *very* odd. I tried defining a dummy example: `is=[]; is.f1=2; is.f2=4; is.f5=2; is.f6=1; A=rand(5,5); A(3,[is.f1, is.f2, is.f6, is.f5])` works fine, so does `[is.f1, is.f2, is.f6, is.f5]` which is a four-element vector. Can you add a few lines of the updated code to the end of your question, where `is` is defined and a line where you're trying to use it? It just doesn't add up. – Andras Deak -- Слава Україні Oct 11 '16 at 11:51
  • 1
    thanks a lot, I am using stack app on my phone, I will add my code as soon as I get home. – Rolf_Zhang Oct 11 '16 at 11:55
  • 1
    I think I found where the problem is. Modifying the following code: `is = []; for v = 1:N; is.(variables{v}) = num2str(v); end to is = []; for v = 1:N; is.(variables{v}) = v`; end will be working. When compile the unmodified code. `[is.*,….]` will be one character, rather several separate numbers. For example, `[is.HE1,is.HE2,is.BF1,is.BF2,is.MPS] = 348911`. But based on the modified code, `[is.HE1,is.HE2,is.BF1,is.BF2,is.MPS] = [3 4 8 9 11]`. So I still be confused why it work based on the user’s guide, But not here. – Rolf_Zhang Oct 12 '16 at 06:10
  • @Rolf sorry, I messed that up:) You're right, you need to assign `v` only. The `num2str` was there to build up a string that can be passed to `eval`, strings which look like `'HE1=3;'` and so on. I'll fix my answer from home:) – Andras Deak -- Слава Україні Oct 12 '16 at 12:20
  • @Rolf_Zhang I updated my answer, thanks for the feedback. So the problem was that I mistakenly stored strings in the struct fields instead of integers. But then `[is.HE1, is.HE2]` was something like `['3', '4']` which is interpreted as a string, in your case `'348911'`. Then this string was interpreted as an index in some weird way that I still don't understand. Bottom line is that you don't need `num2str` if you avoid using `eval`:) – Andras Deak -- Слава Україні Oct 12 '16 at 17:13