Scheduling with Alternate Resources (clpe10)
/***************************************************************/
/* */
/* S A S S A M P L E L I B R A R Y */
/* */
/* NAME: clpe10 */
/* TITLE: Scheduling with Alternate Resources (clpe10) */
/* PRODUCT: OR */
/* SYSTEM: ALL */
/* KEYS: OR */
/* PROCS: CLP, SQL, GANTT */
/* DATA: */
/* */
/* SUPPORT: UPDATE: */
/* REF: */
/* MISC: Example 10 from the CLP Procedure chapter of the */
/* Constraint Programming book. */
/* */
/***************************************************************/
data proj;
array v{11} $8. j1-j3 m1-m3 o1-o4 dur;
input v{*};
datalines;
1 . . 0 1 . 0 1 2 . 1
2 . . 0 1 2 0 1 2 . 1
3 . . 1 2 3 0 1 2 . 1
4 5 6 3 4 5 2 3 4 5 1
7 . . 6 7 8 5 6 . . 1
8 9 . 6 7 8 . . . . 1
10 . . 7 8 9 . . . . 1
11 . . 1 2 . 0 1 2 3 1
12 13 14 7 8 9 0 1 2 3 1
15 16 . 5 6 . 4 5 6 . 1
17 . . 3 4 . 4 5 6 . 1
18 . . 3 4 . . . . . 1
19 . . 0 1 2 . . . . 1
20 . . 0 1 . . . . . 1
21 22 . 0 1 . 0 1 2 . 2
23 . . 2 . . 0 1 2 . 2
24 25 36 8 9 . . . . . 1
26 35 75 6 7 . . . . . 1
27 34 74 6 7 . 5 6 . . 1
28 . . 4 5 . 5 6 . . 1
29 . . 4 5 . 3 4 . . 1
30 . . 3 . . 3 4 . . 1
31 . . 3 . . 3 4 . . 2
32 . . 4 5 . 3 4 . . 2
33 . . 4 5 . 5 6 . . 2
37 76 77 8 9 . . . . . 1
38 39 . 0 1 . 0 1 . . 1
40 . . 2 . . 2 . . . 1
41 . . 6 7 . 6 . . . 2
42 62 82 6 7 . . . . . 2
43 44 63 8 9 . . . . . 2
45 46 65 0 1 . 4 5 . . 2
47 67 . 2 3 . 2 3 . . 2
48 68 . 2 3 . 2 3 . . 1
49 50 69 4 5 . 0 1 . . 1
51 52 . 3 4 . 1 2 . . 2
53 . . 5 6 . 0 . . . 2
54 . . 5 6 . 6 . . . 1
55 56 57 7 8 9 . . . . 1
58 59 78 0 1 2 3 4 . . 1
60 80 . 0 1 2 5 6 . . 1
61 81 . 6 7 . 5 6 . . 2
64 83 84 8 9 . . . . . 2
66 . . 0 1 . 4 5 . . 2
70 . . 5 . . 0 1 . . 1
71 . . 5 . . 0 1 2 . 2
72 73 . 3 4 . 0 1 2 . 2
79 . . 0 1 2 3 4 . . 1
85 . . 0 . . 3 . . . 1
86 . . 1 . . 4 . . . 1
87 . . 2 . . 5 . . . 1
88 . . 3 . . 0 . . . 1
89 . . 4 . . 1 . . . 1
90 . . 5 . . 2 . . . 1
;
data proj2;
set proj;
array v{11} $8. j1-j3 m1-m3 o1-o4 dur;
keep j1-j3 s_start s_finish segmt_no successor;
successor=.;
segmt_no = 1;
rename j1=Jb1;
rename j2=Jb2;
rename j3=Jb3;
do i = 4 to 10;
if input(v{i},best.) ne . then do;
offset = 0;
if i > 6 then do;
offset = 10;
end;
s_start = offset + input(v{i},best.);
s_finish = s_start + 0.45;
if input(dur,best.) eq 2 then do;
s_finish = s_start + 0.9;
end;
output;
segmt_no = segmt_no + 1;
end;
end;
run;
proc format;
value resource
0 = 'Mach0'
1 = 'Mach1'
2 = 'Mach2'
3 = 'Mach3'
4 = 'Mach4'
5 = 'Mach5'
6 = 'Mach6'
7 = 'Mach7'
8 = 'Mach8'
9 = 'Mach9'
10 = 'Oper0'
11 = 'Oper1'
12 = 'Oper2'
13 = 'Oper3'
14 = 'Oper4'
15 = 'Oper5'
16 = 'Oper6'
17 = ' '
18 = ' '
19 = ' ';
run;
pattern1 c=black v=e r=10;
proc gantt data=proj2;
format s_start resource.;
chart / increment=1 nojobnum
nolegend useformat maxdate=17
chartwidth=90 compress skip=3
between=4
barht = 1.5
height=4
ref = 10;
id Jb1 Jb2 Jb3;
run;
proc clp dom=[0,12] restarts=500 dpr=6 showprogress
schedtime=schedtime_altres schedres=schedres_altres;
schedule start=0 finish=12 actselect=dminls edgefinder;
activity (J1-J20 J24-J30 J34-J40 J48-J50 J54-J60
J68-J70 J74-J80 J85-J90) = (1) /* one day jobs */
(J21-J23 J31-J33 J41-J47 J51-J53 J61-J67
J71-J73 J81-J84) = (2); /* two day jobs */
resource (M0-M9) (OP0-OP6);
requires
/* machine requirements */
(J85) = (M0)
(J1 J20 J21 J22 J38 J39 J45 J46 J65 J66) = (M0, M1)
(J19 J2 J58 J59 J60 J78 J79 J80) = (M0, M1, M2)
(J86) = (M1)
(J11) = (M1, M2)
(J3) = (M1, M2, M3)
(J23 J40 J87) = (M2)
(J47 J48 J67 J68) = (M2, M3)
(J30 J31 J88) = (M3)
(J17 J18 J51 J52 J72 J73) = (M3, M4)
(J4 J5 J6) = (M3, M4, M5)
(J89) = (M4)
(J28 J29 J32 J33 J49 J50 J69) = (M4, M5)
(J70 J71 J90) = (M5)
(J15 J16 J53 J54) = (M5, M6)
(J26 J27 J34 J35 J41 J42 J61 J62 J74 J75 J81 J82) = (M6, M7)
(J7 J8 J9) = (M6, M7, M8)
(J10 J12 J13 J14 J55 J56 J57) = (M7, M8, M9)
(J24 J25 J36 J37 J43 J44 J63 J64 J76 J77 J83 J84) = (M8, M9)
/* operator requirements */
(J53 J88) = (OP0)
(J38 J39 J49 J50 J69 J70) = (OP0, OP1)
(J1 J2 J21 J22 J23 J3 J71 J72 J73) = (OP0, OP1, OP2)
(J11 J12 J13 J14) = (OP0, OP1, OP2, OP3)
(J89) = (OP1)
(J51 J52) = (OP1, OP2)
(J40 J90) = (OP2)
(J47 J48 J67 J68) = (OP2, OP3)
(J4 J5 J6) = (OP2, OP3, OP4, OP5)
(J85) = (OP3)
(J29 J30 J31 J32 J58 J59 J78 J79) = (OP3, OP4)
(J86) = (OP4)
(J45 J46 J65 J66) = (OP4, OP5)
(J15 J16 J17) = (OP4, OP5, OP6)
(J87) = (OP5)
(J27 J28 J33 J34 J60 J61 J7 J74 J80 J81) = (OP5, OP6)
(J41 J54) = (OP6);
run;
data sched_aa;
label activity='JOB' operator='OPERATOR' machine='MACHINE';
set schedres_altres;
if ( index(resource, "M")) then do;
machine = resource;
output;
end;
if (index(resource, "O")) then do;
operator = resource;
output;
end;
run;
proc sql;
create table machine as
select distinct activity, resource as machine
from schedres_altres
where (index(resource, "M"));
create table operator as
select distinct activity, resource as operator
from schedres_altres
where (index(resource, "O"));
create table join1 as
select machine.activity, machine.machine,
case when operator.operator then operator.operator
else 'N/A' end as operator
from machine full join operator
on machine.activity=operator.activity;
create table sched_altres as
select time.*, machine, operator
from schedtime_altres as time full join join1
on time.activity=join1.activity;
quit;
/***************************************************************/
/* Define Utility Macros for Graphical Output */
/***************************************************************/
/* %colorIdex creates resource-pattern-color mapping */
/* %fnLegend uses footnote to add legends according to the mapping */
/* %setPatterns executes pattern statements according to the mapping */
%macro colorIdex(res_var=, proj=, palette=, out=);
proc sql;
create table &out as
select distinct(&res_var) from &proj;
quit;
data &out;
set &out;
drop name_tr color_ct;
format color $10.;
retain _pattern 1;
retain color_ct 1;
name_tr=lowcase(strip(&res_var));
if name_tr eq 'n/a' or name_tr eq '' then color='red';
else do;
color=scan(&palette, color_ct);
color_ct=color_ct+1;
end;
output;
_pattern=_pattern+1;
run;
%mend colorIdex;
/* macro %fnLegend uses footnote to add legends */
/* Title */
/* */
/* color bar 11 text ... color bar 1n text */
/* color bar m1 text ... color bar mn text */
%macro fnLegend(tfact=1.75,h=10,xStart=5,rhs=100,nCol=,nRow=,
title='Resource Used',map=,font='Albany AMT',var=_RESOURCE_);
%let rH = %sysevalf(&h/(&nRow+2)); /* row height */
%let cW = %sysevalf((&rhs-2*&xStart)/&nCol); /* column width */
%let rowsH = %sysevalf(&rH*&nRow); /* height of all legend rows */
%let barH = %sysevalf(&rH*0.35); /* height of legend (colored bars) */
%let rTextH = %sysevalf(&barH*&tfact); /* height of text in legend rows */
%let tiTextH = %sysevalf(&rTextH*1.2); /* height of text in title row */
footnote;
data _null_;
call execute('footnote1 f='||"&font"||' j=c h='||&tiTextH||'pct '||'"'||
&title||'";');
call execute('footnote2 f='||"&font"||' h='||&rH||'pct " ";');
run;
data _null_;
set &map end=finish;
retain count 0;
idxRow = int(count/&nCol);
idxCol = count - idxRow*&nCol;
x = &xStart + idxCol*&cW;
y = &rowsH - idxRow*&rH;
_fnlStr_=strip(&var);
if _fnlStr_ eq '' then _fnlStr_='N/A';
if count eq 0 then call execute('footnote3 h='||&rowsH||'pct " "');
if lowcase(strip(color)) eq 'red' then
call execute(catx(' ','f=',"&font",'h=',&barH,'pct', 'm=(',x,',',y,
') pct','c=',color,
'box=1 bs=0 pct " "'));
else
call execute(catx(' ','f=',"&font",'h=',&barH,'pct', 'm=(',x,',',y,
') pct','c=',color,'bc=',color,
'box=1 bs=0 pct " "'));
call execute(catx(' ','f=',"&font",'h=',&rTextH,'pct', 'm=(+0.5,',y,
') pct','c=black')||' " '||trim(_fnlStr_)||'"');
count=count+1;
if finish then call execute(';');
run;
%mend fnLegend;
%macro setPatterns(map);
data _null_;
set ↦
cstr=lowcase(strip(color));
if cstr eq 'red' then
call execute('pattern'||strip(_pattern)||' v=e color='||
cstr||' repeat=1;');
else
call execute('pattern'||strip(_pattern)||' v=s color='||cstr||
' repeat=1;');
run;
%mend setPatterns;
/***************************************************************/
/* End Macro Definitions */
/***************************************************************/
/* add patterns by operators */
%let _palette='black blue yellow orange cyan brown green';
%colorIdex(res_var=operator,proj=sched_altres,palette=&_palette,
out=res_col_patn);
proc sql;
create table sched_gantt_altres as
select a.*, b._pattern
from sched_altres a, res_col_patn b
where a.operator=b.operator;
quit;
/* start to draw the Gantt charts */
title1 j=c h=6pct 'Schedule for Operator #BYVAL(operator)';
title2 j=c h=4pct 'Machine Identified Above Bar';
%setPatterns(res_col_patn);
options nobyline; /* suppress byline */
data labels;
_y=-1;
_lvar="machine";
_xvar="start";
_hlabel=1.5;
_yoffset = -.3;
_flabel='Albany AMT';
run;
proc sort data=sched_gantt_altres;
by operator start;
run;
proc gantt data=sched_gantt_altres(where=(operator ne 'N/A'))
labdata=labels;
by operator;
chart /
pcompress
nojobnum
nolegend
s_start=start
s_finish=finish
scale=12
between=6
height=1.7
skip=2;
id activity;
run;
proc sql;
update labels set _hlabel=2.5;
quit;
title1 j=c h=3pct 'Schedule for Automated Tasks';
proc gantt data=sched_gantt_altres(where=(operator='N/A'))
labdata=labels;
chart /
compress
nojobnum
nolegend
s_start=start
s_finish=finish
scale=23
between=15
height=3
skip=3;
id activity;
run;
title1 j=c h=5pct 'Scheduling With Alternate Resources';
title2 j=c h=3pct 'Schedule By Machine';
%fnLegend(xStart=20,h=18,map=res_col_patn,title='Operator Required',
nCol=4,nRow=2,var=operator,rhs=110,tfact=1.5);
/* Add segment number */
proc sort data=sched_gantt_altres;
by machine start;
run;
data sched_gantt_altres;
set sched_gantt_altres;
label machine = 'Machine';
retain sn ' ';
retain segmt_no 1;
drop sn;
if sn ne machine then do;
segmt_no = 1;
sn = machine;
end;
else segmt_no = segmt_no + 1;
run;
data labels;
_y=-1;
_lvar="Activity";
_xvar="start";
_hlabel=1.3;
_yoffset = -.2;
run;
proc gantt data=sched_gantt_altres
labdata=labels;
chart /
pcompress
skip=2
height=1.5
scale=13
nojobnum
between=13
nolegend
s_start=start
s_finish=finish;
id machine;
run;
goptions reset=all;