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

I don't think I made myself clear.  Let me try to do a better job. BTW, that you so much for your generosity in working this.

 

In my work I could have for SKU A 100 different stocks (10 thru 110), For SKU B 15 different stocks (1 thrugh 15) and SKU C 10 different ones (32-42).  The reason for it, is that I have Min stock and Max Stock values that are determined by business rules outside of optimization.

 

To use the code that you offered,  would I need SKU A, B and C each have 100 different stocks instead of 100 for A, 14 for B and 10 for C?

Ksharp
Super User

@AramF wrote:

I don't think I made myself clear.  Let me try to do a better job. BTW, that you so much for your generosity in working this.

 

In my work I could have for SKU A 100 different stocks (10 thru 110), For SKU B 15 different stocks (1 thrugh 15) and SKU C 10 different ones (32-42).  The reason for it, is that I have Min stock and Max Stock values that are determined by business rules outside of optimization.

 

To use the code that you offered,  would I need SKU A, B and C each have 100 different stocks instead of 100 for A, 14 for B and 10 for C?


Sure. Of course you can. Just change the constraint condition in code:

data have;
infile cards expandtabs truncover;
input sku $ stock	Cost	Contribution;
cards;
A	1	10	1
A	2	20	1.9
A	3	30	2.8
A	4	40	3.75
A	5	50	4.7
B	1	20	1.7
B	2	40	2.6
B	3	60	3.4
B	4	80	4.0
B	5	100	5.1
C	6	210	3.0
C	7	245	4.2
C	8	280	4.8
C	9	315	6.3
C	10	350	6.7
;

data have2;
 set have(rename=(stock=old_stock));
 by sku;
 if first.sku then stock=0;
 stock+1;
run;
proc sql;
create table have3 as
select a.*,coalesce(b.Cost,0) as cost,coalesce(b.Contribution,0) as Contribution
 from (select * from (select distinct sku from have2),(select distinct stock from have2)) as a
 natural left join
 have2 as b ;
quit;

proc sort data=have3 out=sku nodupkey;by sku;run;
proc sort data=have3 out=stock nodupkey;by stock;run;
proc optmodel;
set<str> sku;
set stock;
num cost{sku, stock};
num contribution{sku, stock};

read data sku into sku=[sku];
read data stock into stock=[stock];
read data have3 into [sku stock] cost contribution;

var v{sku, stock} binary;

min obj=sum{i in sku,j in stock} cost[i,j]*v[i,j];
con con1 :11<=sum{i in sku,j in stock} contribution[i,j]*v[i,j]<=14  ;
con con2{m in sku}:sum{n in stock} v[m,n]=2;  /*each SKU have two stock*/  /*  >=1  means at least one stock from each SKU*/
solve with clp / findallsolns;
create data want from [solution sku2 stock2]={s in 1.._NSOL_,i in sku,j in stock} 
                      cost2=cost[i,j] contribution2=contribution[i,j] flag=v[i,j].sol[s];
quit;
proc sort data=want;by sku2 stock2;run;
data want2;
 length sku2 $ 100;
 merge want have2(keep=sku stock  old_stock rename=(sku=sku2 stock=stock2));
 by sku2 stock2;
run;
proc sort data=want2;by solution sku2 stock2;run;
proc print data=want2 noobs;
run;

Base on your orginal data, you can not set solution, you need to change this sample data . 

I have to leave now!

AramF
Fluorite | Level 6

I just realized that my reply had wrong counts.  Let me redo the  numbers:

 

In my work I could have for SKU A 100 different stocks (10 thru 109), For SKU B 15 different stocks (1 thru 15) and SKU C 10 different ones (32-41).  The reason for it, is that I have Min stock and Max Stock values that are determined by business rules outside of optimization.

 

To use the code that you offered,  would I need SKU A, B and C each have 100 different stocks instead of 100 for A, 15 for B and 10 for C?

AramF
Fluorite | Level 6
con con2{m in sku}:sum{n in stock} v[m,n]=2;  /*each SKU have two stock*/  /*  >=1  means at least one stock from each SKU*/

I was afraid that this is what you understood.  I did not mean that.

What I am asking is can my starting dataset be Table A instead of original Table B.  In Table A there is an unequal number of stocking possibilities in each SKU.  I still want only 1 outcome to be used in the solution per SKU, but for SKU A it is 1 out 2, for SKU B it is 1 out 4 and for SKU C it is 1 out 5?  I will never want to get 2 stock possibilities out of a particular SKU.

Table A    Table B    
SKU
StockCostContribution SKUStockCostContribution 
A1101 A1101 
A2201.9 A2201.9 
B1201.7 A3302.8removed
B2402.6 A4403.75removed
B3603.4 A5504.7removed
B4804 B1201.7 
C62103 B2402.6 
C72454.2 B3603.4 
C82804.8 B4804 
C93156.3 B51005.1removed
C103506.7
 
 C62103 
   
 
 C72454.2 
     C82804.8 
     C93156.3 
     C103506.7 
ChanceTGardener
SAS Employee

This model will accommodate an unequal number of possibilities (stocks) per SKU. Additionally, the stocks can, but are not required to increment by 1. See SKU C as an example in the input data below.

 

The output data set shows the optimal number of each SKU to stock.

 

Feel free to change the input data to accommodate the different scenarios you may face to confirm it performs well across all of them. 

 

data input_data;
 input sku $ stock cost contribution;
datalines;
A 1 10 1
A 2 20 1.9
A 3 30 2.8
A 4 40 3.75
A 5 50 4.7
B 1 20 1.7
B 2 40 2.6
B 3 60 3.4
B 4 80 4.0
B 5 100 5.1
C 1 210 3.0
C 17 245 4.2
C 18 280 4.8
C 30 315 6.3
C 100 350 6.7
;

proc optmodel;

set <str,num> ARCS;

set SKUS = setof{<i,j> in ARCS} i;
set STOCK = setof{<i,j> in ARCS} j;

num cost{ARCS};
num contribution{ARCS};

read data input_data into ARCS=[sku stock] cost contribution;

var Assign{ARCS} binary;

impvar TotalContribution = sum{<i,j> in ARCS} Assign[i,j] * contribution[i,j];

min TotalCost = sum{<i,j> in ARCS} Assign[i,j]*cost[i,j];

con contribuion_lb: 11 <= TotalContribution <= 14;

con sku_minimum{i in SKUS}: sum{j in STOCK: <i,j> in ARCS} Assign[i,j] = 1;

solve;

print TotalContribution;
print TotalCost.sol;

create data output_data from [sku stock] = {<i,j> in ARCS: Assign[i,j] > 0.5};

quit;
Ksharp
Super User

Sorry. The code I posted before is NOT right.

Try the following code for unbalance data.

 

1)  one stock for each SKU:

data have;
infile cards expandtabs truncover;
input sku $ stock	Cost	Contribution;
cards;
A	1	10	1
A	2	20	1.9
A	3	30	2.8
A	4	40	3.75
A	5	50	4.7
B	1	20	1.7
B	2	40	2.6
B	3	60	3.4
B	4	80	4.0
B	5	100	5.1
C	6	210	3.0
C	7	245	4.2
C	8	280	4.8
C	9	315	6.3
C	10	350	6.7
;


proc sort data=have;by sku;run;
data have;
 set have;
 obs+1;
run;
data idx;
 set have;
 by sku;
 retain start end;
 if first.sku then start=obs;
 if last.sku then do;end=obs;output;end;
 keep start end;
run;
data idx;
 set idx;
 n+1;
run;
filename cond temp;
data _null_;
set idx;
file cond;
put 'con con'  n ':sum{c'  n ' in ' start '..' end '} v[c' n ']=1;' ;
run;
proc optmodel;
set idx;
num cost{idx};
num contribution{idx};
read data have into idx=[obs] cost contribution;

var v{idx} binary;

min obj=sum{i in idx} cost[i]*v[i];

con con :11<=sum{i in idx} contribution[i]*v[i]<=14  ;
%include cond;

solve with clp / findallsolns;

create data want from [solution obs]={s in 1.._NSOL_,i in idx} 
                      cost2=cost[i] contribution2=contribution[i] flag=v[i].sol[s];
quit;
proc sql;
create table want2 as 
select a.*,b.sku,b.stock
 from want as a,have as b
  where a.obs=b.obs
   order by 1,2 ;
quit;
proc print noobs;run;

Ksharp_0-1733916411268.png

 

Ksharp_1-1733916444327.png

 

 

2) at least one stock for each SKU:

data have;
infile cards expandtabs truncover;
input sku $ stock	Cost	Contribution;
cards;
A	1	10	1
A	2	20	1.9
A	3	30	2.8
A	4	40	3.75
A	5	50	4.7
B	1	20	1.7
B	2	40	2.6
B	3	60	3.4
B	4	80	4.0
B	5	100	5.1
C	6	210	3.0
C	7	245	4.2
C	8	280	4.8
C	9	315	6.3
C	10	350	6.7
;


proc sort data=have;by sku;run;
data have;
 set have;
 obs+1;
run;
data idx;
 set have;
 by sku;
 retain start end;
 if first.sku then start=obs;
 if last.sku then do;end=obs;output;end;
 keep start end;
run;
data idx;
 set idx;
 n+1;
run;
filename cond temp;
data _null_;
set idx;
file cond;
put 'con con'  n ':sum{c'  n ' in ' start '..' end '} v[c' n ']>=1;' ;
run;
proc optmodel;
set idx;
num cost{idx};
num contribution{idx};
read data have into idx=[obs] cost contribution;

var v{idx} binary;

min obj=sum{i in idx} cost[i]*v[i];

con con :11<=sum{i in idx} contribution[i]*v[i]<=14  ;
%include cond;

solve with clp / findallsolns;

create data want from [solution obs]={s in 1.._NSOL_,i in idx} 
                      cost2=cost[i] contribution2=contribution[i] flag=v[i].sol[s];
quit;
proc sql;
create table want2 as 
select a.*,b.sku,b.stock
 from want as a,have as b
  where a.obs=b.obs
   order by 1,2 ;
quit;
proc print noobs;run;

You yield THREE solution.

Ksharp_2-1733916532532.pngKsharp_3-1733916579755.png

 

3) the TWO stocks for each SKU:

data have;
infile cards expandtabs truncover;
input sku $ stock	Cost	Contribution;
cards;
A	1	10	1
A	2	20	1.9
A	3	30	2.8
A	4	40	3.75
A	5	50	4.7
B	1	20	1.7
B	2	40	2.6
B	3	60	3.4
B	4	80	4.0
B	5	100	5.1
C	6	210	3.0
C	7	245	4.2
C	8	280	4.8
C	9	315	6.3
C	10	350	6.7
;


proc sort data=have;by sku;run;
data have;
 set have;
 obs+1;
run;
data idx;
 set have;
 by sku;
 retain start end;
 if first.sku then start=obs;
 if last.sku then do;end=obs;output;end;
 keep start end;
run;
data idx;
 set idx;
 n+1;
run;
filename cond temp;
data _null_;
set idx;
file cond;
put 'con con'  n ':sum{c'  n ' in ' start '..' end '} v[c' n ']=2;' ;
run;
proc optmodel;
set idx;
num cost{idx};
num contribution{idx};
read data have into idx=[obs] cost contribution;

var v{idx} binary;

min obj=sum{i in idx} cost[i]*v[i];

con con :11<=sum{i in idx} contribution[i]*v[i]<=14  ;
%include cond;

solve with clp / findallsolns;

create data want from [solution obs]={s in 1.._NSOL_,i in idx} 
                      cost2=cost[i] contribution2=contribution[i] flag=v[i].sol[s];
quit;
proc sql;
create table want2 as 
select a.*,b.sku,b.stock
 from want as a,have as b
  where a.obs=b.obs
   order by 1,2 ;
quit;
proc print noobs;run;

You yield none solution. Maybe you use other dataset to run the code.

Ksharp_4-1733916801978.png

 

 

AramF
Fluorite | Level 6

Ksharp and ChaseTGardner,

Thank you both so much for putting together multiple iterations of code to help me solve this.  I've been sitting on this untapped potential that you have both made available for me overnight.  2 thumbs and 2 toes up for your awesome effort.  I think I can tinker with the code from here.  Although I did not initially request it, I like Ksharp's idea that maybe I do want to see multiple combinations of stocksXSKU if they all yield the same contribution and cost.  I am very grateful for your help.

SAS Innovate 2025: Register Now

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!

Multiple Linear Regression in SAS

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.

From The DO Loop
Want more? Visit our blog for more articles like these.
Discussion stats
  • 21 replies
  • 1391 views
  • 4 likes
  • 4 in conversation