I have the SAS dataset, attached.
The data structure is below: I have Date[dt] and BLOC[bloc]. Here are 18 treatments [condi_id] for each Date/BLOC[dt/bloc].
The original treatment outcome are _tmin_orig.
Now here are improvement trials[ A/B/C/D] on each treatment [from a new dimension/sampling]. The outcomes are _tmin,
_tmin_ew, _tmin_wt_r, and _tmin_wt_r2 for A/B/C/D respectively.
The Objective is find the best improvement method [ONE out of A/B/C/D] and the best 2-treatment combination
[TWO of the 18 treatment, condi_id=1~18] to ensure that the treatment for EACH DATE/BLOC[dt/bloc] is safe-guarded:
_tmin_XXX [ONE from A/B/C/D with TWO from 18 treatments/condi_id=1~18] is lower than _tmin_orig.
The penalty function is Score 1 if meet the criteria; Score -5 if not.
The GOAL is to Maxime the SUM of TOTAL SCORE for all DATE/BLOC[dt/bloc].
Thanks,
If I understand correctly, the following code does what you want:
data have;
set mm_out_x6_x;
rename _tmin=method1 _tmin_ew=method2 _tmin_wt_r=method3 _tmin_wt_r2=method4;
run;
proc optmodel;
/* read input data */
set METHODS = 1..4;
set <str,num,num> DATE_BLOCK_TREATMENT;
num _tmin_orig {DATE_BLOCK_TREATMENT};
num outcome {DATE_BLOCK_TREATMENT, METHODS};
read data have into DATE_BLOCK_TREATMENT=[dt bloc condi_id] _tmin_orig
{m in METHODS} <outcome[dt,bloc,condi_id,m]=col('method'||m)>;
set TREATMENTS = setof {<d,b,t> in DATE_BLOCK_TREATMENT} t;
set DATE_BLOCK = setof {<d,b,t> in DATE_BLOCK_TREATMENT} <d,b>;
/* define optimization model */
var SelectMethod {METHODS} binary;
var SelectTreatment {TREATMENTS} binary;
var IsGood {DATE_BLOCK} binary;
/* if IsGood[d,b] = 1 then Score[d,b] = 1 else Score[d,b] = -5 */
impvar Score {<d,b> in DATE_BLOCK} = 1 * IsGood[d,b] - 5 * (1 - IsGood[d,b]);
max TotalScore = sum {<d,b> in DATE_BLOCK} Score[d,b];
con CardinalityMethod:
sum {m in METHODS} SelectMethod[m] = 1;
con CardinalityTreatment:
sum {t in TREATMENTS} SelectTreatment[t] = 2;
/* if IsGood[d,b] = SelectMethod[m] = SelectTreatment[t] = 1 then outcome[d,b,t,m] < _tmin_orig[d,b,t] */
con NoGood {<d,b,t> in DATE_BLOCK_TREATMENT, m in METHODS: outcome[d,b,t,m] >= _tmin_orig[d,b,t]}:
IsGood[d,b] + SelectMethod[m] + SelectTreatment[t] <= 2;
/* call MILP solver */
solve;
/* create output data */
create data want_method from [method] SelectMethod;
create data want_treatment from [treatment] SelectTreatment;
create data want_dt_bloc from [dt bloc] IsGood Score;
quit;
An optimal solution selects method 3 and treatments 12 and 15, yielding a maximum total score of -21.
If this does not match your expectation, please provide a sample solution, together with the calculation of its total score.
If I understand correctly, the following code does what you want:
data have;
set mm_out_x6_x;
rename _tmin=method1 _tmin_ew=method2 _tmin_wt_r=method3 _tmin_wt_r2=method4;
run;
proc optmodel;
/* read input data */
set METHODS = 1..4;
set <str,num,num> DATE_BLOCK_TREATMENT;
num _tmin_orig {DATE_BLOCK_TREATMENT};
num outcome {DATE_BLOCK_TREATMENT, METHODS};
read data have into DATE_BLOCK_TREATMENT=[dt bloc condi_id] _tmin_orig
{m in METHODS} <outcome[dt,bloc,condi_id,m]=col('method'||m)>;
set TREATMENTS = setof {<d,b,t> in DATE_BLOCK_TREATMENT} t;
set DATE_BLOCK = setof {<d,b,t> in DATE_BLOCK_TREATMENT} <d,b>;
/* define optimization model */
var SelectMethod {METHODS} binary;
var SelectTreatment {TREATMENTS} binary;
var IsGood {DATE_BLOCK} binary;
/* if IsGood[d,b] = 1 then Score[d,b] = 1 else Score[d,b] = -5 */
impvar Score {<d,b> in DATE_BLOCK} = 1 * IsGood[d,b] - 5 * (1 - IsGood[d,b]);
max TotalScore = sum {<d,b> in DATE_BLOCK} Score[d,b];
con CardinalityMethod:
sum {m in METHODS} SelectMethod[m] = 1;
con CardinalityTreatment:
sum {t in TREATMENTS} SelectTreatment[t] = 2;
/* if IsGood[d,b] = SelectMethod[m] = SelectTreatment[t] = 1 then outcome[d,b,t,m] < _tmin_orig[d,b,t] */
con NoGood {<d,b,t> in DATE_BLOCK_TREATMENT, m in METHODS: outcome[d,b,t,m] >= _tmin_orig[d,b,t]}:
IsGood[d,b] + SelectMethod[m] + SelectTreatment[t] <= 2;
/* call MILP solver */
solve;
/* create output data */
create data want_method from [method] SelectMethod;
create data want_treatment from [treatment] SelectTreatment;
create data want_dt_bloc from [dt bloc] IsGood Score;
quit;
An optimal solution selects method 3 and treatments 12 and 15, yielding a maximum total score of -21.
If this does not match your expectation, please provide a sample solution, together with the calculation of its total score.
Thanks a lot!
How to save-out the top 3/5 two-treatment combinations on DATE/BLOC? want_treatment has the top one.
I need top N to make a judgement call later, the do step 2, to maximize the ABS(_tmin_xxx-_tmin_orig) for DATE/BLOC with constrains.
To get the 5 best pairs of treatments, you can replace the SOLVE and CREATE DATA statements as follows:
/* call MILP solver to find top few solutions for SelectTreatment */
num numSolsWanted = 5;
num numSolsFound init 0;
set SOLS = 1..numSolsFound;
set TREATMENTS_s {SOLS};
con ExcludeSolution {s in SOLS}:
sum {t in TREATMENTS_s[s]} SelectTreatment[t] <= card(TREATMENTS_s[s]) - 1;
for {1..numSolsWanted} do;
put numSolsFound=;
solve;
numSolsFound = numSolsFound + 1;
TREATMENTS_s[numSolsFound] = {t in TREATMENTS: SelectTreatment[t].sol > 0.5};
create data ('want_method'||numSolsFound) from [method] SelectMethod;
create data ('want_treatment'||numSolsFound) from [treatment] SelectTreatment;
create data ('want_dt_bloc'||numSolsFound) from [dt bloc] IsGood Score;
end;
I bet I replace correctly. But the code complains, see below.
Even I tried other ways around, still.
Not sure, is computer setting thing or not.
/* call MILP solver , TOP one only
solve;
create data want_method from [method] SelectMethod;
create data want_treatment from [treatment] SelectTreatment;
create data want_dt_bloc from [dt bloc] IsGood Score;*/
/*solver, top n*/
num numSolsWanted = 5;
num numSolsFound init 0;
set SOLS = 1..numSolsFound;
set TREATMENTS_s {SOLS};
con ExcludeSolution {s in SOLS}:
sum {t in TREATMENTS_s[s]} SelectTreatment[t] <= card(TREATMENTS_s[s]) - 1;
for {1..numSolsWanted} do;
put numSolsFound=;
solve;
numSolsFound = numSolsFound + 1;
TREATMENTS_s[numSolsFound] = {t in TREATMENTS: SelectTreatment[t].sol > 0.5};
create data ('want_method'||numSolsFound) from [method] SelectMethod;
create data ('want_treatment'||numSolsFound) from [treatment] SelectTreatment;
create data ('want_dt_bloc'||numSolsFound) from [dt bloc] IsGood Score;
end;
13623 create data 'want_method'||numSolsFound from [method] SelectMethod;
-------------
22
76
ERROR 22-322: Expecting a name.
ERROR 76-322: Syntax error, statement will be ignored.
13624 create data ('want_treatment'||numSolsFound) from [treatment]
-
22
76
13624! SelectTreatment;
ERROR 22-322: Expecting a name.
ERROR 76-322: Syntax error, statement will be ignored.
13625 create data ('want_dt_bloc'||numSolsFound) from [dt bloc] IsGood Score;
-
22
76
ERROR 22-322: Expecting a name.
ERROR 76-322: Syntax error, statement will be ignored.
13395 create data ('want_method'||numSolsFound) from [method] SelectMethod;
-
22
76
ERROR 22-322: Expecting a name.
ERROR 76-322: Syntax error, statement will be ignored.
13396 create data ('want_treatment'||numSolsFound) from [treatment]
-
22
76
13396! SelectTreatment;
ERROR 22-322: Expecting a name.
ERROR 76-322: Syntax error, statement will be ignored.
13397 create data ('want_dt_bloc'||numSolsFound) from [dt bloc] IsGood Score;
-
22
76
ERROR 22-322: Expecting a name.
ERROR 76-322: Syntax error, statement will be ignored.
13452 create data ("want_method"||numSolsFound) from [method] SelectMethod;
-
22
76
ERROR 22-322: Expecting a name.
ERROR 76-322: Syntax error, statement will be ignored.
13453 create data ("want_treatment"||numSolsFound) from [treatment]
-
22
76
13453! SelectTreatment;
ERROR 22-322: Expecting a name.
ERROR 76-322: Syntax error, statement will be ignored.
13454 create data ("want_dt_bloc"||numSolsFound) from [dt bloc] IsGood Score;
-
22
76
ERROR 22-322: Expecting a name.
ERROR 76-322: Syntax error, statement will be ignored.
13509 create data "want_method"||numSolsFound from [method] SelectMethod;
-------------
22
76
ERROR 22-322: Expecting a name.
ERROR 76-322: Syntax error, statement will be ignored.
13510 create data ("want_treatment"||numSolsFound) from [treatment]
-
22
76
13510! SelectTreatment;
ERROR 22-322: Expecting a name.
ERROR 76-322: Syntax error, statement will be ignored.
13511 create data ("want_dt_bloc"||numSolsFound) from [dt bloc] IsGood Score;
-
22
76
ERROR 22-322: Expecting a name.
ERROR 76-322: Syntax error, statement will be ignored.
13566 create data want_method||numSolsFound from [method] SelectMethod;
-- ---- ------------
22 537 595
200
ERROR 22-322: Syntax error, expecting one of the following: (, FROM.
ERROR 537-782: The symbol 'from' is unknown.
ERROR 595-782: The implied subscript count does not match array 'SelectMethod', 0 NE
1.
ERROR 200-322: The symbol is not recognized and will be ignored.
13566! create data want_method||numSolsFound from [method] SelectMethod;
------
537
ERROR 537-782: The symbol 'method' is unknown.
13567 create data ("want_treatment"||numSolsFound) from [treatment]
-
22
76
13567! SelectTreatment;
ERROR 22-322: Expecting a name.
ERROR 76-322: Syntax error, statement will be ignored.
13568 create data ("want_dt_bloc"||numSolsFound) from [dt bloc] IsGood Score;
-
22
76
ERROR 22-322: Expecting a name.
ERROR 76-322: Syntax error, statement will be ignored.
if I just take this as below, the code does not complain and the dataset is created. Surely ONLY THE # 5.
create data want_m from [method] SelectMethod;
create data want_t from [treatment] SelectTreatment;
create data want_d from [dt bloc] IsGood Score;
numSolsFound=0
numSolsFound=1
numSolsFound=2
numSolsFound=3
numSolsFound=4
It looks like you are using an old version of SAS. That dynamic data set name functionality was introduced in SAS/OR 14.1 in SAS 9.4M3 in 2015:
Data Set Input/Output :: SAS/OR(R) 14.1 User's Guide: Mathematical Programming
Here's a workaround that uses PROC APPEND inside a SUBMIT block:
/* call MILP solver to find top few solutions for SelectTreatment */
num numSolsWanted = 5;
num numSolsFound init 0;
set SOLS = 1..numSolsFound;
set TREATMENTS_s {SOLS};
con ExcludeSolution {s in SOLS}:
sum {t in TREATMENTS_s[s]} SelectTreatment[t] <= card(TREATMENTS_s[s]) - 1;
for {1..numSolsWanted} do;
put numSolsFound=;
solve;
numSolsFound = numSolsFound + 1;
TREATMENTS_s[numSolsFound] = {t in TREATMENTS: SelectTreatment[t].sol > 0.5};
create data want_method from [method] sol=numSolsFound SelectMethod;
create data want_treatment from [treatment] sol=numSolsFound SelectTreatment;
create data want_dt_bloc from [dt bloc] sol=numSolsFound IsGood Score;
submit;
proc append base=want_method_all data=want_method;
run;
proc append base=want_treatment_all data=want_treatment;
run;
proc append base=want_dt_bloc_all data=want_dt_bloc;
run;
endsubmit;
end;
Thanks,
I am playing the codes. I still have one confusion. Sorry OPT Model is quite new to me.
Initially it is to pick 1 out 4 Methods and 2 out 18 Treatments, right?
If loose the constrains, say, 2/4 for Methods and 4/18 for Treatments, the final Result Score should be better off, Right?
BUT the best one is only -45. The before is -21, right? I am confused.
con CardinalityMethod:
sum {m in METHODS} SelectMethod[m] = 2;
con CardinalityTreatment:
sum {t in TREATMENTS} SelectTreatment[t] = 4;
FULL CODE is below, seems nothing altered. If switch back to SelectMethod[m] = 1; and SelectTreatment[t] = 2;, the top
result score is -21.
/*
https://communities.sas.com/t5/Mathematical-Optimization/HOW-TO-Find-the-Best-Combination-Of-Treatments-for-Date-BLOC-dt/m-p/975265#M4360
*/
%let optds=mm_out_x6_x;
data have;
set &optds.;
rename _tmin=method1 _tmin_ew=method2 _tmin_wt_r=method3 _tmin_wt_r2=method4;
run;
title "OPT on &optds.";
proc optmodel;
/* read input data */
set METHODS = 1..4;
set <str,num,num> DATE_BLOCK_TREATMENT;
num _tmin_orig {DATE_BLOCK_TREATMENT};
num outcome {DATE_BLOCK_TREATMENT, METHODS};
read data have into DATE_BLOCK_TREATMENT=[dt bloc condi_id] _tmin_orig
{m in METHODS} <outcome[dt,bloc,condi_id,m]=col('method'||m)>;
set TREATMENTS = setof {<d,b,t> in DATE_BLOCK_TREATMENT} t;
set DATE_BLOCK = setof {<d,b,t> in DATE_BLOCK_TREATMENT} <d,b>;
/* define optimization model */
var SelectMethod {METHODS} binary;
var SelectTreatment {TREATMENTS} binary;
var IsGood {DATE_BLOCK} binary;
/* if IsGood[d,b] = 1 then Score[d,b] = 1 else Score[d,b] = -5 */
impvar Score {<d,b> in DATE_BLOCK} = 1 * IsGood[d,b] - 5 * (1 - IsGood[d,b]);
max TotalScore = sum {<d,b> in DATE_BLOCK} Score[d,b];
con CardinalityMethod:
sum {m in METHODS} SelectMethod[m] = 1;
con CardinalityTreatment:
sum {t in TREATMENTS} SelectTreatment[t] = 2;
/* if IsGood[d,b] = SelectMethod[m] = SelectTreatment[t] = 1 then outcome[d,b,t,m] < _tmin_orig[d,b,t] */
con NoGood {<d,b,t> in DATE_BLOCK_TREATMENT, m in METHODS: outcome[d,b,t,m] >= _tmin_orig[d,b,t]}:
IsGood[d,b] + SelectMethod[m] + SelectTreatment[t] <= 2;
/* call MILP solver , TOP one only
solve;
create data want_method from [method] SelectMethod;
create data want_treatment from [treatment] SelectTreatment;
create data want_dt_bloc from [dt bloc] IsGood Score;*/
/*solver, top n
num numSolsWanted = 5;
num numSolsFound init 0;
set SOLS = 1..numSolsFound;
set TREATMENTS_s {SOLS};
con ExcludeSolution {s in SOLS}:
sum {t in TREATMENTS_s[s]} SelectTreatment[t] <= card(TREATMENTS_s[s]) - 1;
for {1..numSolsWanted} do;
solve;
numSolsFound = numSolsFound + 1;
put numSolsFound=;
TREATMENTS_s[numSolsFound] = {t in TREATMENTS: SelectTreatment[t].sol > 0.5};
create data ('want_m'||numSolsFound) from [method] SelectMethod;
create data ('want_t'||numSolsFound) from [treatment] SelectTreatment;
create data(' want_d'||numSolsFound) from [dt bloc] IsGood Score;
end;*/
num numSolsWanted = 5;
num numSolsFound init 0;
set SOLS = 1..numSolsFound;
set TREATMENTS_s {SOLS};
con ExcludeSolution {s in SOLS}:
sum {t in TREATMENTS_s[s]} SelectTreatment[t] <= card(TREATMENTS_s[s]) - 1;
for {1..numSolsWanted} do;
put numSolsFound=;
solve;
numSolsFound = numSolsFound + 1;
TREATMENTS_s[numSolsFound] = {t in TREATMENTS: SelectTreatment[t].sol > 0.5};
create data want_method from [method] sol=numSolsFound SelectMethod;
create data want_treatment from [treatment] sol=numSolsFound SelectTreatment;
create data want_dt_bloc from [dt bloc] sol=numSolsFound IsGood Score;
submit;
proc append base=want_method_all data=want_method;
run;
proc append base=want_treatment_all data=want_treatment;
run;
proc append base=want_dt_bloc_all data=want_dt_bloc;
run;
endsubmit;
end;
quit;
Let's me try around. See whether I can get it in SOLVER/EXCEL, match or not.
THanks,
I tried out with SOLVER/EXCEL. The result screen copy is below.
Also I attach the EXCEL file here. All are color-coded, I bet it is clear in EXCEL.
I guess I did not clearly expressed somewhere. Would you take a peek and make a check?!
To match the Excel logic, you can change the optimization model as follows:
/* define optimization model */
var SelectTreatment {TREATMENTS} binary;
var SelectMethod {METHODS} binary;
var SelectTreatmentMethod {TREATMENTS, METHODS} binary;
var IsGood {DATE_BLOCK} binary;
/* if IsGood[d,b] = 1 then Score[d,b] = 1 else Score[d,b] = -5 */
impvar Score {<d,b> in DATE_BLOCK} = 1 * IsGood[d,b] - 5 * (1 - IsGood[d,b]);
max TotalScore = sum {<d,b> in DATE_BLOCK} Score[d,b];
con CardinalityTreatment:
sum {t in TREATMENTS} SelectTreatment[t] = 2;
con CardinalityMethod:
sum {m in METHODS} SelectMethod[m] = 1;
/* SelectTreatmentMethod[t,m] <= SelectTreatment[t] * SelectMethod[m] */
con Linearize1 {t in TREATMENTS, m in METHODS}:
SelectTreatmentMethod[t,m] <= SelectTreatment[t];
con Linearize2 {t in TREATMENTS, m in METHODS}:
SelectTreatmentMethod[t,m] <= SelectMethod[m];
con AtLeastOneGood {<d,b> in DATE_BLOCK}:
IsGood[d,b] <= sum {<(d),(b),t> in DATE_BLOCK_TREATMENT, m in METHODS: outcome[d,b,t,m] < _tmin_orig[d,b,t]} SelectTreatmentMethod[t,m];
The idea is that if IsGood[d,b] = 1, the AtLeastOneGood constraint will force SelectTreatmentMethod[t,m] = 1 for at least one t and m such that outcome[d,b,t,m] < _tmin_orig[d,b,t]. And the Linearize1 and Linearize2 constraints together force SelectTreatment[t] = 1 and SelectMethod[m] = 1 for that t and m.
With these changes, the optimal objective value is 3, and there are 13 such optimal solutions for SelectTreatment.
Thanks,
Now SAS is much better than SOLVER/EXCEL, which only gives out one solution. SAS can pop out all the
top solutions.
Somehow why DATE_BLOCK and TREATMENTS cannot be saved out?! All othrs are OK.
create data want_x_dtbloc from [d b] DATE_BLOCK;
create data want_trtmt from [t] TREATMENTS;
64694 create data want_x_dtbloc from [d b] DATE_BLOCK;
-
800
ERROR 800-782: The preceding column type may not be a set.
64695 create data want_trtmt from [t] TREATMENTS;
-
800
ERROR 800-782: The preceding column type may not be a set.
64696 create data want_IsGood from [dt bloc] IsGood;
NOTE: The data set WORK.WANT_ISGOOD has 15 observations and 3 variables.
64697 create data want_score from [dt bloc] score;
NOTE: The data set WORK.WANT_SCORE has 15 observations and 3 variables.
64698 create data want_outcome from [d b t m] outcome;
NOTE: The data set WORK.WANT_OUTCOME has 1080 observations and 5 variables.
64699 create data want_tmin_orig from [d b t] _tmin_orig;
NOTE: The data set WORK.WANT_TMIN_ORIG has 270 observations and 4 variables.
64700 quit;
NOTE: The SAS System stopped processing this step because of errors.
NOTE: PROCEDURE OPTMODEL used (Total process time):
real time 0.28 seconds
cpu time 0.26 seconds
Correct syntax is:
create data want_x_dtbloc from [d b]=DATE_BLOCK;
create data want_trtmt from [t]=TREATMENTS;
Nearly 200 sessions are now available on demand with the SAS Innovate Digital Pass.
Explore Now →