BookmarkSubscribeRSS Feed
☑ This topic is solved. Need further help from the community? Please sign in and ask a new question.
hellohere
Pyrite | Level 9

@RobPratt @Ksharp

 

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,

 

 

 

sc.png

1 ACCEPTED SOLUTION

Accepted Solutions
RobPratt
SAS Super FREQ

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.

View solution in original post

21 REPLIES 21
RobPratt
SAS Super FREQ

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.

hellohere
Pyrite | Level 9

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. 

RobPratt
SAS Super FREQ

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;
hellohere
Pyrite | Level 9

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.

hellohere
Pyrite | Level 9

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
RobPratt
SAS Super FREQ

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;
hellohere
Pyrite | Level 9

@RobPratt 

 

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;

 

RobPratt
SAS Super FREQ
Because the two cardinality constraints are equalities, increasing the right-hand side values does not make the problem looser. If the = were instead <=, then increasing the right-hand side would indeed yield a relaxation. Do you want to select exactly the specified numbers or at most the specified numbers?
hellohere
Pyrite | Level 9

Let's me try around. See whether I can get it in SOLVER/EXCEL, match or not. 

THanks, 

hellohere
Pyrite | Level 9

@RobPratt 

 

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?!

 

SOLVER.jpg

RobPratt
SAS Super FREQ

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.

hellohere
Pyrite | Level 9

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
RobPratt
SAS Super FREQ

Correct syntax is:

   create data want_x_dtbloc from [d b]=DATE_BLOCK;
   create data want_trtmt from [t]=TREATMENTS;

Catch up on SAS Innovate 2026

Nearly 200 sessions are now available on demand with the SAS Innovate Digital Pass.

Explore Now →
Discussion stats
  • 21 replies
  • 4562 views
  • 2 likes
  • 2 in conversation