Hi all, this is the first time I am using proc optmodel and I am running into the error stating that NLP solver does not allow integer variables. Any advice or help on how to get around this would be greatly appreciated. I am able to use Excel solver to find a solution but would like to migrate to SAS so I can use more decision variables.
Some context around the problem I am trying to solve (in case my code setup is way off base). I am trying to maximize weighted average roe, by deciding which one of 3 rate changes to select for each segment (only one can be selected per segment). Each rate change carries its own weightings as the price change impacts volume. Ultimately roewgt/wgt = the roe, this is true for any particular segment, but also when you sum them across all segments which is what I am trying to maximize. Please let me know if more details are needed as this is my first time posting.
data elast_input;
input segid $ rtechg_1 rtechg_2 rtechg_3 wgt_1 wgt_2 wgt_3 roewgt_1 roewgt_2 roewgt_3;
datalines;
AAA -0.0020 0.0000 0.0020 7000000 6800000 6000000 530000 560000 600000
AAB -0.0022 0.0000 0.0022 5000000 4900000 4850000 450000 490000 500000
AAC -0.0017 0.0000 0.0017 6500000 6150000 6000000 800000 820000 850000
AAD -0.0010 0.0000 0.0010 8000000 7950000 7900000 250000 270000 300000
;
proc optmodel;
set <string> segment;
set rate = 1..3;
num rtechg {segment, rate};
num wgt {segment, rate};
num roewgt {segment, rate};
read data elast_input into segment=[segid] {j in rate} <rtechg[segid,j]=col('rtechg_'||(j))>;
read data elast_input into segment=[segid] {j in rate} <wgt[segid,j]=col('wgt_'||(j))>;
read data elast_input into segment=[segid] {j in rate} <roewgt[segid,j]=col('roewgt_'||(j))>;
print rtechg wgt roewgt;
var X {segment, rate} binary;
max roe = (sum {segid in segment, j in rate} (roewgt[segid,j]*X[segid,j])) / (sum {segid in segment, j in rate} (wgt[segid,j]*X[segid,j]));
con SelectOne {segid in segment}:
sum {j in rate} X[segid,j] = 1;
solve;
print X;
quit;
Here is code to do the linearization, whereby the MILP solver is called, and print the rtechg output you requested:
proc optmodel;
set <string> segment;
set rate = 1..3;
num rtechg {segment, rate};
num wgt {segment, rate};
num roewgt {segment, rate};
read data elast_input into segment=[segid]
{j in rate} <rtechg[segid,j]=col('rtechg_'||(j))>
{j in rate} <wgt[segid,j]=col('wgt_'||(j))>
{j in rate} <roewgt[segid,j]=col('roewgt_'||(j))>;
num scale = 1e6;
for {segid in segment, j in rate} do;
wgt[segid,j] = wgt[segid,j] / scale;
roewgt[segid,j] = roewgt[segid,j] / scale;
end;
print rtechg wgt roewgt;
var X {segment, rate} binary;
max roe =
(sum {segid in segment, j in rate} (roewgt[segid,j]*X[segid,j]))
/ (sum {segid in segment, j in rate} (wgt[segid,j]*X[segid,j]));
con SelectOne {segid in segment}:
sum {j in rate} X[segid,j] = 1;
/* Charnes-Cooper transformation: scale so that denominator of objective function is 1 */
var T >= 0 <= min {segid in segment, j in rate} (1/wgt[segid,j]);
/* TX[segid,j] = T * X[segid,j] */
var TX {segid in segment, j in rate} >= 0 <= 1/wgt[segid,j];
max roe_linear =
sum {segid in segment, j in rate} roewgt[segid,j]*TX[segid,j];
con DenominatorOne:
sum {segid in segment, j in rate} wgt[segid,j]*TX[segid,j] = 1;
con Linearize {segid in segment, j in rate}:
TX[segid,j] <= TX[segid,j].ub * X[segid,j];
con LinearizeLB {segid in segment, j in rate}:
TX[segid,j] - T >= (TX[segid,j].lb - T.ub) * (1 - X[segid,j]);
con LinearizeUB {segid in segment, j in rate}:
TX[segid,j] - T <= (TX[segid,j].ub - T.lb) * (1 - X[segid,j]);
solve;
print X;
print roe roe_linear;
num rtechg_sol {segment};
for {segid in segment} do;
for {j in rate: X[segid,j].sol > 0.5} do;
rtechg_sol[segid] = rtechg[segid,j];
leave;
end;
end;
print rtechg_sol;
quit;
In order to use both integer (binary) variables and nonlinear expressions, you need to use LSO solver. Add "with lso" to your solve statement.
I also have changed your read data statement. This version should work:
proc optmodel;
set <string> segment;
set rate = 1..3;
num rtechg {segment, rate};
num wgt {segment, rate};
num roewgt {segment, rate};
read data elast_input into segment=[segment] {j in rate} <rtechg[segment,j]=col('rtechg_'||(j))>;
read data elast_input into segment=[segment] {j in rate} <wgt[segment,j]=col('wgt_'||(j))>;
read data elast_input into segment=[segment] {j in rate} <roewgt[segment,j]=col('roewgt_'||(j))>;
print rtechg wgt roewgt;
var X {segment, rate} binary;
max roe = (sum {segid in segment, j in rate} (roewgt[segid,j]*X[segid,j])) / (sum {segid in segment, j in rate} (wgt[segid,j]*X[segid,j]));
con SelectOne {segid in segment}:
sum {j in rate} X[segid,j] = 1;
solve with lso;
print X;
quit;
read data is complaining for me since "segid" is not a column in the table elast_input.
Thanks for the quick reply! Sorry for the confusion with segid, I messed up when creating the dataset as segment should have been segid, I edited that in the post now.
When I add "with LSO" to the solve statement the "solve with" part highlights in red for me. If I run the code anyway I get an error message stating :Unknown TKLSO status 0
My updated code is below, in case I made a mistake and missed something you asked me to do. Below that is the log
data elast_input;
input segid $ rtechg_1 rtechg_2 rtechg_3 wgt_1 wgt_2 wgt_3 roewgt_1 roewgt_2 roewgt_3;
datalines;
AAA -0.0020 0.0000 0.0020 7000000 6800000 6000000 530000 560000 600000
AAB -0.0022 0.0000 0.0022 5000000 4900000 4850000 450000 490000 500000
AAC -0.0017 0.0000 0.0017 6500000 6150000 6000000 800000 820000 850000
AAD -0.0010 0.0000 0.0010 8000000 7950000 7900000 250000 270000 300000
;
proc optmodel;
set <string> segment;
set rate = 1..3;
num rtechg {segment, rate};
num wgt {segment, rate};
num roewgt {segment, rate};
read data elast_input into segment=[segid] {j in rate} <rtechg[segid,j]=col('rtechg_'||(j))>;
read data elast_input into segment=[segid] {j in rate} <wgt[segid,j]=col('wgt_'||(j))>;
read data elast_input into segment=[segid] {j in rate} <roewgt[segid,j]=col('roewgt_'||(j))>;
print rtechg wgt roewgt;
var X {segment, rate} binary;
max roe = (sum {segid in segment, j in rate} (roewgt[segid,j]*X[segid,j])) / (sum {segid in segment, j in rate} (wgt[segid,j]*X[segid,j]));
con SelectOne {segid in segment}:
sum {j in rate} X[segid,j] = 1;
solve with lso;
print X;
quit;
NOTE: Problem generation will use 4 threads.
NOTE: The problem has 12 variables (0 free, 0 fixed).
NOTE: The problem has 12 binary and 0 integer variables.
NOTE: The problem has 4 linear constraints (0 LE, 4 EQ, 0 GE, 0 range).
NOTE: The problem has 12 linear constraint coefficients.
NOTE: The problem has 0 nonlinear constraints (0 LE, 0 EQ, 0 GE, 0 range).
NOTE: The OPTLSO algorithm is using up to 4 threads.
NOTE: The problem has 12 variables (12 integer, 0 continuous).
NOTE: The problem has 0 constraints (0 linear, 0 nonlinear).
NOTE: The problem has 1 FCMP function definitions.
NOTE: The deterministic parallel mode is enabled.
Best
Iteration Objective Infeasibility Evals Time
1 0.10983981693364 0 168 0
2 0.11572700296736 0 184 0
3 0.12442396313364 0 199 0
4 0.14166666666667 0 209 0
5 0.14166666666667 0 220 0
6 0.14166666666667 0 220 0
7 0.14166666666667 0 221 0
ERROR: Unknown TKLSO status 0.
53 print X;
54 quit;
NOTE: The SAS System stopped processing this step because of errors.
NOTE: PROCEDURE OPTMODEL used (Total process time):
real time 0.19 seconds
cpu time 0.09 seconds
You can get by with one READ DATA statement:
read data elast_input into segment=[segid]
{j in rate} <rtechg[segid,j]=col('rtechg_'||(j))>
{j in rate} <wgt[segid,j]=col('wgt_'||(j))>
{j in rate} <roewgt[segid,j]=col('roewgt_'||(j))>;
It looks like you are using an older release of SAS/OR, before the LSO solver was officially supported in SAS/OR 15.1 (SAS 9.4M6, November 2018). I'll work on a linearization of the problem instead.
Are you planning to use rtechg anywhere in the problem?
No, I don't think I will be using rtechg in the optimization. I have it in there in the hopes that I can take the results of the optimization and output the rtechg for each segment. For example, if for segid AAA, X was set to 1 for rate 2, then I would like to return the rtechg associated with that. I have no clue if that's possible of how to do it as I am was just taking this one step at a time for now. Thanks so much for the quick reply and help!
Here is code to do the linearization, whereby the MILP solver is called, and print the rtechg output you requested:
proc optmodel;
set <string> segment;
set rate = 1..3;
num rtechg {segment, rate};
num wgt {segment, rate};
num roewgt {segment, rate};
read data elast_input into segment=[segid]
{j in rate} <rtechg[segid,j]=col('rtechg_'||(j))>
{j in rate} <wgt[segid,j]=col('wgt_'||(j))>
{j in rate} <roewgt[segid,j]=col('roewgt_'||(j))>;
num scale = 1e6;
for {segid in segment, j in rate} do;
wgt[segid,j] = wgt[segid,j] / scale;
roewgt[segid,j] = roewgt[segid,j] / scale;
end;
print rtechg wgt roewgt;
var X {segment, rate} binary;
max roe =
(sum {segid in segment, j in rate} (roewgt[segid,j]*X[segid,j]))
/ (sum {segid in segment, j in rate} (wgt[segid,j]*X[segid,j]));
con SelectOne {segid in segment}:
sum {j in rate} X[segid,j] = 1;
/* Charnes-Cooper transformation: scale so that denominator of objective function is 1 */
var T >= 0 <= min {segid in segment, j in rate} (1/wgt[segid,j]);
/* TX[segid,j] = T * X[segid,j] */
var TX {segid in segment, j in rate} >= 0 <= 1/wgt[segid,j];
max roe_linear =
sum {segid in segment, j in rate} roewgt[segid,j]*TX[segid,j];
con DenominatorOne:
sum {segid in segment, j in rate} wgt[segid,j]*TX[segid,j] = 1;
con Linearize {segid in segment, j in rate}:
TX[segid,j] <= TX[segid,j].ub * X[segid,j];
con LinearizeLB {segid in segment, j in rate}:
TX[segid,j] - T >= (TX[segid,j].lb - T.ub) * (1 - X[segid,j]);
con LinearizeUB {segid in segment, j in rate}:
TX[segid,j] - T <= (TX[segid,j].ub - T.lb) * (1 - X[segid,j]);
solve;
print X;
print roe roe_linear;
num rtechg_sol {segment};
for {segid in segment} do;
for {j in rate: X[segid,j].sol > 0.5} do;
rtechg_sol[segid] = rtechg[segid,j];
leave;
end;
end;
print rtechg_sol;
quit;
This was great and delivered the expected result on my small scale example...thanks! I tried to run it on my larger dataset and got an error for out of memory. Is there a limit to the number of decision variables this can be used on? My current problem has 51 segids, with 3 rate options each, so 153 decision variables, although I was hoping to expand the number of segids and rate options in the future as well.
ERROR: Out of memory.
NOTE: Objective of the best integer solution found = 0.170493763.
There is no hard limit. Can you please send the larger data?
Larger sample data is attached
To get a better linearization that exploits the SelectOne constraints, replace LinearizeLB and LinearizeUB with LinearizeCompact:
/* con LinearizeLB {segid in segment, j in rate}:*/
/* TX[segid,j] - T >= (TX[segid,j].lb - T.ub) * (1 - X[segid,j]);*/
/* con LinearizeUB {segid in segment, j in rate}:*/
/* TX[segid,j] - T <= (TX[segid,j].ub - T.lb) * (1 - X[segid,j]);*/
con LinearizeCompact {segid in segment}:
sum {j in rate} TX[segid,j] = T;
This change yields an optimal solution instantly for your larger data:
NOTE: Problem generation will use 4 threads.
NOTE: The problem has 307 variables (0 free, 0 fixed).
NOTE: The problem has 153 binary and 0 integer variables.
NOTE: The problem has 256 linear constraints (153 LE, 103 EQ, 0 GE, 0 range).
NOTE: The problem has 816 linear constraint coefficients.
NOTE: The problem has 0 nonlinear constraints (0 LE, 0 EQ, 0 GE, 0 range).
NOTE: The OPTMODEL presolver is disabled for linear problems.
NOTE: The initial MILP heuristics are applied.
NOTE: The MILP presolver value AUTOMATIC is applied.
NOTE: The MILP presolver removed 51 variables and 51 constraints.
NOTE: The MILP presolver removed 154 constraint coefficients.
NOTE: The MILP presolver modified 202 constraint coefficients.
NOTE: The presolved problem has 256 variables, 205 constraints, and 662 constraint
coefficients.
NOTE: The MILP solver is called.
NOTE: The parallel Branch and Cut algorithm is used.
NOTE: The Branch and Cut algorithm is using up to 4 threads.
Node Active Sols BestInteger BestBound Gap Time
0 1 1 0.1704294 0.4859814 64.93% 0
0 1 1 0.1704294 0.1704294 0.00% 0
0 0 1 0.1704294 0.1704294 0.00% 0
NOTE: Optimal.
NOTE: Objective = 0.1704294364.
That worked for me with the code you had sent, however when I tried to add another business constraint I still got the out of memory error. I apologize for not thinking of the possibility of adding other constraints upfront, but when I saw the optimal solution, which was moving to the higher rate everywhere, it became obvious to me that I need to add some other constraint. I added the following, which worked on the smaller dataset, but again ran out of memory on the larger dataset.
con MinVol:
sum {segid in segment, j in rate} (vol[segid,j]*X[segid,j]) >= 7350195340;
Right now I have 7350195340 as a constant, although the true purpose is to be the sum of the vol_2 field. There was another similar type constraint I was thinking of adding, but it is based on values I don't have in the original sample I sent you. I can work on making a sample for that too if you think it's best in order to see if we can find a solution that can work within multiple business constraints.
I really appreciate all your help with this!
Full code with the vol field:
proc optmodel;
set <string> segment;
set rate = 1..3;
num rtechg {segment, rate};
num vol {segment, rate};
num wgt {segment, rate};
num roewgt {segment, rate};
read data elast_input into segment=[segid]
{j in rate} <rtechg[segid,j]=col('rtechg_'||(j))>
{j in rate} <vol[segid,j]=col('vol_'||(j))>
{j in rate} <wgt[segid,j]=col('wgt_'||(j))>
{j in rate} <roewgt[segid,j]=col('roewgt_'||(j))>;
num scale = 1e6;
for {segid in segment, j in rate} do;
wgt[segid,j] = wgt[segid,j] / scale;
roewgt[segid,j] = roewgt[segid,j] / scale;
end;
print rtechg wgt roewgt;
var X {segment, rate} binary;
max roe =
(sum {segid in segment, j in rate} (roewgt[segid,j]*X[segid,j]))
/ (sum {segid in segment, j in rate} (wgt[segid,j]*X[segid,j]));
con SelectOne {segid in segment}:
sum {j in rate} X[segid,j] = 1;
con MinVol:
sum {segid in segment, j in rate} (vol[segid,j]*X[segid,j]) >= 7350195340;
/* Charnes-Cooper transformation: scale so that denominator of objective function is 1 */
var T >= 0 <= max {segid in segment, j in rate} (1/wgt[segid,j]);
var TX {segid in segment, j in rate} >= 0 <= 1/wgt[segid,j];
max roe_linear =
sum {segid in segment, j in rate} roewgt[segid,j]*TX[segid,j];
con DenominatorOne:
sum {segid in segment, j in rate} wgt[segid,j]*TX[segid,j] = 1;
con Linearize {segid in segment, j in rate}:
TX[segid,j] <= TX[segid,j].ub * X[segid,j];
con LinearizeCompact {segid in segment}:
sum {j in rate} TX[segid,j] = T;
solve;
print X;
print roe roe_linear;
num rtechg_sol {segid in segment};
for {segid in segment} do;
for {j in rate: X[segid,j].sol > 0.5} do;
rtechg_sol[segid] = rtechg[segid,j];
leave;
end;
end;
print rtechg_sol;
quit;
Your code runs fine for me with SAS/OR 15.1, but it runs faster if you scale the values, like was done for wgt and roewgt:
for {segid in segment, j in rate} do;
vol[segid,j] = vol[segid,j] / scale;
wgt[segid,j] = wgt[segid,j] / scale;
roewgt[segid,j] = roewgt[segid,j] / scale;
end;
And then scale the right hand side of your new constraint:
con MinVol:
sum {segid in segment, j in rate} (vol[segid,j]*X[segid,j]) >= 7350195340 / scale;
NOTE: Problem generation will use 4 threads.
NOTE: The problem has 307 variables (0 free, 0 fixed).
NOTE: The problem has 153 binary and 0 integer variables.
NOTE: The problem has 257 linear constraints (153 LE, 103 EQ, 1 GE, 0 range).
NOTE: The problem has 969 linear constraint coefficients.
NOTE: The problem has 0 nonlinear constraints (0 LE, 0 EQ, 0 GE, 0 range).
NOTE: The OPTMODEL presolver is disabled for linear problems.
NOTE: The initial MILP heuristics are applied.
NOTE: The MILP presolver value AUTOMATIC is applied.
NOTE: The MILP presolver removed 51 variables and 51 constraints.
NOTE: The MILP presolver removed 207 constraint coefficients.
NOTE: The MILP presolver modified 270 constraint coefficients.
NOTE: The presolved problem has 256 variables, 206 constraints, and 762 constraint
coefficients.
NOTE: The MILP solver is called.
NOTE: The parallel Branch and Cut algorithm is used.
NOTE: The Branch and Cut algorithm is using up to 4 threads.
Node Active Sols BestInteger BestBound Gap Time
0 1 0 . 0.1589133 . 0
NOTE: The MILP solver's symmetry detection found 254 orbits. The largest orbit contains 2
variables.
0 1 0 . 0.1589133 . 0
0 1 0 . 0.1589127 . 0
0 1 0 . 0.1589126 . 0
0 1 0 . 0.1589126 . 0
NOTE: The MILP solver added 3 cuts with 177 cut coefficients at the root.
103 101 1 0.1572979 0.1587913 0.94% 0
105 103 2 0.1573459 0.1587913 0.91% 0
705 143 3 0.1578858 0.1584093 0.33% 0
1409 509 4 0.1579025 0.1583040 0.25% 0
11583 3937 5 0.1579183 0.1580484 0.08% 3
13895 3369 6 0.1579213 0.1580238 0.06% 4
15643 2712 6 0.1579213 0.1579974 0.05% 5
17848 1756 7 0.1579220 0.1579775 0.04% 5
20174 199 7 0.1579220 0.1579378 0.01% 6
NOTE: Optimal within relative gap.
NOTE: Objective = 0.157921987.
Even with the scaling I get out of memory message. When I use fullstimer it appears to use 5 mb, which doesn't seem like a lot to be getting that message to me...wonder if has something to do with my organizations' configuration. I appreciate all the time and help. You definitely solved the linearity issue.
I recommend investigating the setting of the SAS MEMSIZE option. For reference, I have MEMSIZE 20G, and here is the result of FULLSTIMER for me:
NOTE: PROCEDURE OPTMODEL used (Total process time):
real time 6.40 seconds
user cpu time 16.71 seconds
system cpu time 3.03 seconds
memory 167586.76k
OS Memory 191116.00k
Timestamp 12/11/2019 08:03:27 PM
Step Count 44 Switch Count 37
Code runs fine on the latest version of SAS/OR. For an older version, adding a small amount to denominator can fix the issue. Can you try this one:
data elast_input; input segid $ rtechg_1 rtechg_2 rtechg_3 wgt_1 wgt_2 wgt_3 roewgt_1 roewgt_2 roewgt_3; datalines; AAA -0.0020 0.0000 0.0020 7000000 6800000 6000000 530000 560000 600000 AAB -0.0022 0.0000 0.0022 5000000 4900000 4850000 450000 490000 500000 AAC -0.0017 0.0000 0.0017 6500000 6150000 6000000 800000 820000 850000 AAD -0.0010 0.0000 0.0010 8000000 7950000 7900000 250000 270000 300000 ; proc optmodel; set <string> segment; set rate = 1..3; num rtechg {segment, rate}; num wgt {segment, rate}; num roewgt {segment, rate}; read data elast_input into segment=[segid] {j in rate} <rtechg[segid,j]=col('rtechg_'||(j))>; read data elast_input into segment=[segid] {j in rate} <wgt[segid,j]=col('wgt_'||(j))>; read data elast_input into segment=[segid] {j in rate} <roewgt[segid,j]=col('roewgt_'||(j))>; print rtechg wgt roewgt; var X {segment, rate} binary; max roe = (sum {segid in segment, j in rate} (roewgt[segid,j]*X[segid,j])) / (sum {segid in segment, j in rate} (wgt[segid,j]*X[segid,j]) + 1e-12); con SelectOne {segid in segment}: sum {j in rate} X[segid,j] = 1; solve with lso; print X; quit;
Notice that I have added 1e-12 to the denominator of roe.
Registration is now open for SAS Innovate 2025 , our biggest and most exciting global event of the year! Join us in Orlando, FL, May 6-9.
Sign up by Dec. 31 to get the 2024 rate of just $495.
Register now!
Learn how to run multiple linear regression models with and without interactions, presented by SAS user Alex Chaplin.
Find more tutorials on the SAS Users YouTube channel.