Resources

Scheduling with Alternate Resources (clp10)

/***************************************************************/
/*                                                             */
/*              S A S   S A M P L E   L I B R A R Y            */
/*                                                             */
/*    NAME: clp10                                              */
/*   TITLE: Scheduling with Alternate Resources (clp10)        */
/* 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;