Operations Research topics: SAS/OR,
SAS Optimization, and SAS Simulation Studio

Resource Constraints using Proc CLP

Reply
New Contributor
Posts: 2

Resource Constraints using Proc CLP

I need some advice.   I am using 14.2

 

I have a scheduling problem which is solved using Proc CLP.  It utilises the ACTDATA,  RESDATA data sets.  I need to add a number of constraints to when resources are available as described below.

 Scenario 1

Resource A is not available on day  2 and therefore Resource A cannot be assigned  to any activity that is scheduled over that period.

Scenario 2.

The resources assigned for use on Activity A must also be assigned to Activity B.  Using the data below, if the resource VANCOVER is assigned to Activity AA2, then resource VANCOVER must also be assigned to activity AB2.

 

ACTIVITY DATA

ACTIVITY             SUCCESSOR        DURATION          RESOURCE          QTY

AA1                        AA3                        12                           CANADA              1

AA2                        AA3                        20                           UK                        1

AA3                        AB2                        30                           CANADA              1

AA4                        AB3                        30                           CANADA              1

AB2                        AB3                        20                           CANADA              1

AB3                                                      10                           UK                        1

 

RESOURCE DATA

RESOURCE          CAPACITY            POOL                     SUBQTY

LONDON              1                              UK                          1

EDINBURGH         1                              UK                          1

VANCOVER          1                              CANADA                1

CALGARY             1                              CANADA                1

 

How can I add these constraints to the problem using Proc CLP or should the model be reworked  using Proc OPTMODEL.

SAS Employee
Posts: 8

Re: Resource Constraints using Proc CLP

Hi!

 

The first scenario can be addressed by introducing an activity "X" with the sole purpose of using up resource "LONDON" on day 2:

 

proc clp schedtime=schedtime schedres=schedres;
activity X = (dur=1 SGE=2 FLE=3);
RESOURCE (LONDON);
REQUIRES X = (LONDON);
run;

 

This scenario can also be handled by proc. CPM, using resource calendars, activity calendars, or the resource input data set to specify the resource level.

 

The second scenario is what we have called a "REQUIRESSAME" constraint. Adding this constraint to CLP is a requirement that is under review for implementation in a future release.

 

Thanks!

Lindsey

 

 

SAS Employee
Posts: 453

Re: Resource Constraints using Proc CLP

[ Edited ]

Here is one way to solve this problem by using the MILP solver in PROC OPTMODEL:

data ACTIVITYDATA;
   input _ACTIVITY_ $ _SUCCESSOR_ $ _DURATION_ _RESOURCE_ $ _QTY_;
   if _ACTIVITY_ = 'CALGVACA' then do;
      _ALIGNTYPE_ = 'seq';
      _ALIGNDATE_ = 2;
   end;
   datalines;
AA1 AA3 12 CANADA 1
AA2 AA3 20 UK 1
AA3 AB2 30 CANADA 1
AA4 AB3 30 CANADA 1
AB2 AB3 20 CANADA 1
AB3 . 10 UK 1
CALGVACA . 1 CALGARY 1 
;

data RESOURCEDATA;
   input _RESOURCE_ $ _CAPACITY_ _POOL_ $ _SUBQTY_;
   datalines;
LONDON 1 UK 1
EDINBURGH 1 UK 1
VANCOVER 1 CANADA 1
CALGARY 1 CANADA 1
;

proc optmodel;
   /* activity data */
   set <str,str> ACTIVITY_SUCCESSOR;
   read data ACTIVITYDATA into ACTIVITY_SUCCESSOR=[_ACTIVITY_ _SUCCESSOR_];
   set ACTIVITIES = (union {<i,j> in ACTIVITY_SUCCESSOR} {i,j}) diff {''};
   num duration {ACTIVITIES} init 0;
   str aligntype {ACTIVITIES};
   num aligndate {ACTIVITIES};
   read data ACTIVITYDATA into [_ACTIVITY_]
      duration=_DURATION_ aligntype=_ALIGNTYPE_ aligndate=_ALIGNDATE_;
   set <str,str> ACTIVITY_POOL;
   num quantity {ACTIVITY_POOL} init 1;
   read data ACTIVITYDATA into ACTIVITY_POOL=[_ACTIVITY_ _RESOURCE_]
      quantity=_QTY_;

   /* resource data */
   num maxperiod = sum {a in ACTIVITIES} duration[a] - 1;
   set PERIODS = 0..maxperiod;
   set ACTIVITY_PERIOD = {a in ACTIVITIES, p in PERIODS: p+duration[a]-1 in PERIODS};
   set <str,str> RESOURCE_POOL;
   num subquantity {RESOURCE_POOL} init 1;
   read data RESOURCEDATA(where=(_POOL_ ne '')) into RESOURCE_POOL=[_RESOURCE_ _POOL_] subquantity=_SUBQTY_;
   set  RESOURCES;
   num capacity {RESOURCES};
   read data RESOURCEDATA into RESOURCES=[_RESOURCE_] capacity=_CAPACITY_;
   RESOURCE_POOL = RESOURCE_POOL union setof {r in RESOURCES} <r,r>;
   set ACTIVITY_RESOURCE = setof {<a,pool> in ACTIVITY_POOL, <r,(pool)> in RESOURCE_POOL} <a,r>;
   num consumption {ACTIVITY_RESOURCE};
   for {<a,pool> in ACTIVITY_POOL, <r,(pool)> in RESOURCE_POOL}
      consumption[a,r] = quantity[a,pool]*subquantity[r,pool];
   set ARP = {<a,r> in ACTIVITY_RESOURCE, p in PERIODS: <a,p> in ACTIVITY_PERIOD};

   /* IsStart[a,p] = 1 if activity a starts at period p */
   var IsStart {ACTIVITY_PERIOD} binary;
   con StartOnce {a in ACTIVITIES}:
      sum {<(a),p> in ACTIVITY_PERIOD} IsStart[a,p] = 1;
   impvar Start {a in ACTIVITIES} = sum {<(a),p> in ACTIVITY_PERIOD} p * IsStart[a,p];
   impvar Finish {a in ACTIVITIES} = Start[a] + duration[a];
   con Precedence {<a,s> in ACTIVITY_SUCCESSOR: s ne ''}:
      Finish[a] <= Start[s];

   con Alignment {a in ACTIVITIES: aligntype[a] = 'seq'}:
      Start[a] = aligndate[a];

   var MaxFinish integer >= max {a in ACTIVITIES} duration[a];
   min Makespan = MaxFinish;
   con MaxFinishCon {a in ACTIVITIES}:
      MaxFinish >= Finish[a];

   /* IsStartActivityResource[a,r,p] = 1 if activity a uses resource r and starts at period p */
   var IsStartActivityResource {ARP} binary;
   con ChooseResourceFromPool {<a,pool> in ACTIVITY_POOL}:
      sum {<(a),r,p> in ARP: <r,pool> in RESOURCE_POOL} IsStartActivityResource[a,r,p] = 1;

   /* IsStartActivityResource[a,r,p] = 1 implies IsStart[a,p] = 1 */
   con LinkStarts {<a,r,p> in ARP}:
      IsStartActivityResource[a,r,p] <= IsStart[a,p];

   con ResourceCapacity {r in RESOURCES, p in PERIODS}:
      sum {<a,(r),p2> in ARP: p in p2..p2+duration[a]-1} 
         consumption[a,r] * IsStartActivityResource[a,r,p2] 
   <= capacity[r];

   solve;
   print {<a,p> in ACTIVITY_PERIOD: IsStart[a,p].sol > 0.5} IsStart;
   print {<a,r,p> in ARP: IsStartActivityResource[a,r,p].sol > 0.5} IsStartActivityResource;

   create data schedtime from [activity] duration start finish;
   str assigned_resource {ACTIVITY_POOL};
   for {<a,pool> in ACTIVITY_POOL} do;
      for {<(a),r,p> in ARP: <r,pool> in RESOURCE_POOL and IsStartActivityResource[a,r,p].sol > 0.5} do;
         assigned_resource[a,pool] = r;
         leave;
      end;
   end;
   create data schedres(drop=pool) from [activity pool] resource=assigned_resource quantity;
quit;

proc gantt data=schedtime;
   chart / as=start af=finish compress;
   id activity;
run;

 

Occasional Contributor
Posts: 6

Re: Resource Constraints using Proc CLP

Hi I have been using the code below and it works well.  I want to add other fixed resources (eg. there is no choice) to scheduled activities.  The solver processing time increases considerably.  I would like to remove the need for the variable IsResourcePeriod with an impvar to reduce the search space. what you would suggest? The approaches  I have tried have resulted in errors.  A set expression may not depend on a variable or invalid index?  The final problem would be schedule approx 2000 events with approx 8 resources each.  The majority are fixed, i,e, do not come from a resource pool. Help is needed.    Liam
SAS Employee
Posts: 453

Re: Resource Constraints using Proc CLP

Please share your code and data.
Occasional Contributor
Posts: 6

Re: Resource Constraints using Proc CLP

data ACTIVITYDATA;
   input _ACTIVITY_ $ _SUCCESSOR_ $ _DURATION_ _TRG_AREA_RESOURCE_ $ _TRG_AREA_QTY_ _CTLEVEL_ $ _PRI_ ;
   if _ACTIVITY_ = 'CALGVACA' then do;
      _ALIGNTYPE_ = 'seq';
      _ALIGNDATE_ = 2;
   end;
   datalines;
AA1 AA3 12 CANADA 1 CT1 23
AA2 AA3 20 UK 1 CT2 12
AA3 AB2 30 CANADA 1 CT1 10
AA4 AB3 30 CANADA 1 CT3 25
AB2 AB3 20 CANADA 1 CT1 8
AB3 . 10 UK 1 CT2 12
AB4 AB3 51 LONDON 1 CT4 21
CALGVACA . 1 CALGARY 1 . .
;


/* Convert this to Hold only Training Areas.  This will allow the code to select only Trg Area */
data TRG_AREA_DATA;  
   input _TRG_AREA_ $ _TRG_AREA_CAPACITY_ _TRG_AREA_POOL_ $ _TRG_AREA_SUBQTY_;
   datalines;
LONDON 1 UK 1
EDINBURGH 1 UK 1
VANCOVER 1 CANADA 1
CALGARY 1 CANADA 1
LONDON 1 CANADA 1
;

data DAILY_RES_REQUIREMENTS;
input _ACTIVITY_ $ _DAILY_RESOURCE_ $ QTY;
datalines;
AA1 SG 1
AA2 IG 1
AA2 WG 1
AA2 SG 1
AA3 GG 1
AA3 SG 1
AA4 SG 1
AB2 IG 1
AB2 SG 1
AB3 IG 1
AB4 GG 1
AA1 WR 23
AA1 CR 12
AA2 CR 10
AA3 BD 34
AA4 WR 21
AB2 BD 32
AB4 CR 10
AA4 CR 6
;

data DAILY_RES_CAPACITY;
input _DAILY_RESOURCE_ $ DAILY_CAP;
datalines;
SG 1
IG 1
WG 1
GG 1
WR 25
CR 12
BD 35
;


proc optmodel;
   set <str,str> ACTIVITY_SUCCESSOR;
   read data ACTIVITYDATA into ACTIVITY_SUCCESSOR=[_ACTIVITY_ _SUCCESSOR_];
   set ACTIVITIES = (union {<i,j> in ACTIVITY_SUCCESSOR} {i,j}) diff {''};
   num duration {ACTIVITIES} init 0;
   str trg_area_resource {ACTIVITIES};
   num trg_area_quantity {ACTIVITIES} init 1;
   str aligntype {ACTIVITIES};
   num aligndate {ACTIVITIES};
   set <str> Daily_Resource;
   set <str,str> ACTIVITY_DAILY_RESOURCES;
   num Daily_Res_Req {ACTIVITIES, Daily_Resource} ;
   num Daily_Res_Cap {Daily_Resource};
   str ct_level {ACTIVITIES};
   num pri {ACTIVITIES};
  

   /* READ ACTIVITY AND LOCTATION REQUIREMENTS DATA */
   read data ACTIVITYDATA into [_ACTIVITY_]
      duration=_DURATION_ trg_area_resource=_TRG_AREA_RESOURCE_ trg_area_quantity=_TRG_AREA_QTY_ aligntype=_ALIGNTYPE_ aligndate=_ALIGNDATE_ ct_level = _CTLEVEL_ pri = _PRI_;

   num maxperiod = sum {a in ACTIVITIES} duration[a] - 1;
   set PERIODS = 0..maxperiod;
   set ACTIVITIES_PERIODS = {a in ACTIVITIES, p in PERIODS: p+duration[a]-1 in PERIODS};
   
   var IsStart {ACTIVITIES_PERIODS} binary;
   var StartDate{ACTIVITIES} integer >= 0 <= maxperiod;
   impvar FinishDate{a in ACTIVITIES} = StartDate[a] + duration[a];
   con StartOnce {a in ACTIVITIES}:
      sum {<(a),p> in ACTIVITIES_PERIODS} IsStart[a,p] = 1;
   impvar Start {a in ACTIVITIES} = sum {<(a),p> in ACTIVITIES_PERIODS} p * IsStart[a,p];
   impvar Finish {a in ACTIVITIES} = Start[a] + duration[a];
   
  
   con Precedence {<a,s> in ACTIVITY_SUCCESSOR: s ne ''}:
      Finish[a] <= Start[s];

   con Alignment {a in ACTIVITIES: aligntype[a] = 'seq'}:
      Start[a] = aligndate[a];

   var MaxFinish integer >= max {a in ACTIVITIES} duration[a];
       /*min Makespan = max{a in ACTIVITIES} Finish[a];*/
   min Makespan = MaxFinish;
   con MaxFinishCon {a in ACTIVITIES}:
      MaxFinish >= Finish[a];


   /* LOCATION INFORMATION */
   set <str,str> TRG_AREA_POOL;
   num trg_area_subquantity {TRG_AREA_POOL} init 1;

   /* READ LOCATION DATA AND CAPACITIES */
   read data TRG_AREA_DATA into TRG_AREA_POOL=[_TRG_AREA_ _TRG_AREA_POOL_] trg_area_subquantity=_TRG_AREA_SUBQTY_;
   set <str> TRG_AREA_RESOURCES;
   num capacity {TRG_AREA_RESOURCES};
   read data TRG_AREA_DATA into TRG_AREA_RESOURCES=[_TRG_AREA_] capacity=_TRG_AREA_CAPACITY_;
   TRG_AREA_POOL = TRG_AREA_POOL union setof {r in TRG_AREA_RESOURCES} <r,r>;
   print trg_area_subquantity;
   set TA_RESOURCES_ACTIVITIES = {r in TRG_AREA_RESOURCES, a in ACTIVITIES: <r,trg_area_resource[a]> in TRG_AREA_POOL};
  /* SELECTS THE RESOURCE(S) TO BE ALLOCATED TO THE ACTIVITY */
   var IsRes {TA_RESOURCES_ACTIVITIES} binary;
   /*var IsDailyRes {ACTIVITY_DAILY_RESOURCES} binary;*/
/* CONSTRAINS ONLY LOCATION TO AN ACTIVITY */
   con OneResourcePerActivity {a in ACTIVITIES}:
      sum {<r,trg_area_resource[a]> in TRG_AREA_POOL} IsRes[r,a] = 1;
   var IsResPeriod {TA_RESOURCES_ACTIVITIES, PERIODS} binary;
   con IsResPeriodImpliesIsRes {<r,a> in TA_RESOURCES_ACTIVITIES, p in PERIODS}:
      IsResPeriod[r,a,p] <= IsRes[r,a];
   con IsStartImpliesResource {<a,p> in ACTIVITIES_PERIODS, p2 in p..p+duration[a]-1}:
      trg_area_quantity[a] * IsStart[a,p] <= sum {<r,trg_area_resource[a]> in TRG_AREA_POOL} trg_area_subquantity[r,trg_area_resource[a]] * IsResPeriod[r,a,p2];
   con ResourceCapacity {r in TRG_AREA_RESOURCES, p in PERIODS}:
      sum {<(r),a> in TA_RESOURCES_ACTIVITIES} IsResPeriod[r,a,p] <= capacity[r];




   /* READ DAILY RESOURCE REQUIREMENTS */
   read data DAILY_RES_REQUIREMENTS into Daily_Resource = [_DAILY_RESOURCE_];
   put Daily_Resource;
   read data DAILY_RES_REQUIREMENTS into ACTIVITY_DAILY_RESOURCES=[_ACTIVITY_ _DAILY_RESOURCE_];
   put ACTIVITY_DAILY_RESOURCES;
   num Is_Act_Daily_Res{ACTIVITY_DAILY_RESOURCES} init 1;
   print Is_Act_Daily_Res;
   read data DAILY_RES_REQUIREMENTS into [_ACTIVITY_ _DAILY_RESOURCE_] Daily_Res_Req=QTY;

   /* READS DAILY RESOURCE CAPACITY */
   read data DAILY_RES_CAPACITY into [_DAILY_RESOURCE_] Daily_Res_Cap=DAILY_CAP;
   print Daily_Res_Cap;


   var IsDailyResPeriod{ACTIVITY_DAILY_RESOURCES, PERIODS} binary;
/* Checks that Trg Area is not allocated to a period with requirement with resource */
/*expand IsStartImpliesResource;*/
   
       con IsDailyResPeriodImpliesIsRes {<a,r> in ACTIVITY_DAILY_RESOURCES, p in PERIODS}:
      IsDailyResPeriod[a,r,p] <= Is_Act_Daily_Res[a,r];

con IsStartImpliesDailyResource {<a,p> in ACTIVITIES_PERIODS, p2 in p..p+duration[a]-1,<(a), r> in ACTIVITY_DAILY_RESOURCES  }:
      Daily_Res_Req[a,r]*IsStart[a,p] <=   Daily_Res_Req[a,r]*IsDailyResPeriod[a,r,p2]; 

/*expand IsStartImpliesDailyResource;*/

   con DailyResourceCapacity {r in Daily_Resource, p in PERIODS}:
      sum {<a,(r)> in ACTIVITY_DAILY_RESOURCES} IsDailyResPeriod[a,r,p]*Daily_Res_Req[a,r] <= Daily_Res_Cap[r];


/* Trying to remove the need for Var IsDailyResPeriod 
     I am trying to reduce the number of variables to try and improve its peformance 
This part is currently included in the final problem as I cannoit get to work.
       */
impvar Busy {<a,r> in ACTIVITY_DAILY_RESOURCES, <(a),p> in ACTIVITIES_PERIODS, p2 in p..p+duration[a]: IsStart[(a),p].sol>0.5} = Daily_Res_Req[a,r];
/* con busy2 {r in Daily_Resource, p in PERIODS}: sum {a in ACTIVITIES, p2 in Periods} Busy[a,r,p2,p] <= Daily_Res_Cap[r];
impvar busy1{<a,r> in ACTIVITY_DAILY_RESOURCES, p in PERIODS : p in StartDate[a]..FinishDate[a]} = 1; */
solve;


quit;

Rob, 

 

in the code above I have additional resources which are always required if a activity is scheduled.  In an effort to improve performance I have tried to reduce the need for IsResPeriod Var but have failed.  Below is amended code to uses only one source of activities and resources.  I tried to get the program to select a resource from each pool that has a resource requirement.  I would be grateful for your suggestions on the best approach and advice to resolve the problems.  

 

as I said previously, the end problem will be approx 2400 activities with approx 10 resources.

data ACTIVITYDATA;
   input _ACTIVITY_ $ _SUCCESSOR_ $ _DURATION_ _TRG_AREA_RESOURCE_ $ _TRG_AREA_QTY_ _CTLEVEL_ $ _PRI_ ;
   if _ACTIVITY_ = 'CALGVACA' then do;
      _ALIGNTYPE_ = 'seq';
      _ALIGNDATE_ = 2;
   end;
   datalines;
AA1 AA3 12 CANADA 1 CT1 23
AA2 AA3 20 UK 1 CT2 12
AA3 AB2 30 CANADA 1 CT1 10
AA4 AB3 30 CANADA 1 CT3 25
AB2 AB3 20 CANADA 1 CT1 8
AB3 . 10 UK 1 CT2 12
AB4 AB3 51 LONDON 1 CT4 21
CALGVACA . 1 CALGARY 1 . .
AA1 AA3 12 SG 1 CT1 23
AA2 AA3 20 IG 1 CT2 12
AA2 AA3 20 WG 1 CT2 12
AA2 AA3 20 SG 1 CT2 12
AA3 AB2 30 GG 1 CT1 10
AA3 AB2 30 SG 1 CT1 10
AA4 AB3 30 SG 1 CT3 25
AB2 AB3 30 IG 1 CT1 8
AB2 AB3 30 SG 1 CT1 8
AB3 . 10 IG 1 CT2 12
AB4 AB3 51 GG 1 CT4 21
AA1 AA3 12 WR 23 CT1 23
AA1 AA3 12 CR 12 CT1 23
AA2 AA3 20 CR 10 CT2 12
AA3 AB2 30 BD 34 CT3 10
AA4 AB3 30 WR 21 CT3 25
AB2 AB3 20 BD 32 CT1 8
AB4 AB3 51 CR 10 CT4 21
AA4 AB3 30 CR 6 CT3 25
;


/* Convert this to Hold only Training Areas.  This will allow the code to select only Trg Area */
data TRG_AREA_DATA;  
   input _TRG_AREA_ $ _TRG_AREA_CAPACITY_ _TRG_AREA_POOL_ $ _TRG_AREA_SUBQTY_;
   datalines;
LONDON 1 UK 1
EDINBURGH 1 UK 1
VANCOVER 1 CANADA 1
CALGARY 1 CANADA 1
LONDON 1 CANADA 1
SG 1 SG 1
IG 1 IG 1
WG 1 WG 1
GG 1 GG 1
WR 25 WR 25
CR 12 CR 12
BD 35 BD 35
;



proc optmodel;
   set <str,str> ACTIVITY_SUCCESSOR;
   read data ACTIVITYDATA into ACTIVITY_SUCCESSOR=[_ACTIVITY_ _SUCCESSOR_];
   set ACTIVITIES = (union {<i,j> in ACTIVITY_SUCCESSOR} {i,j}) diff {''};
   num duration {ACTIVITIES} init 0;
   str trg_area_resource {ACTIVITIES};
   num trg_area_quantity {ACTIVITIES} init 1;
   str aligntype {ACTIVITIES};
   num aligndate {ACTIVITIES};
   set <str> Daily_Resource;
   set <str,str> ACTIVITY_DAILY_RESOURCES;
   num Daily_Res_Req {ACTIVITIES, Daily_Resource} ;
   num Daily_Res_Cap {Daily_Resource};
   str ct_level {ACTIVITIES};
   num pri {ACTIVITIES};
  

   /* READ ACTIVITY AND LOCTATION REQUIREMENTS DATA */
   read data ACTIVITYDATA into [_ACTIVITY_]
      duration=_DURATION_ trg_area_resource=_TRG_AREA_RESOURCE_ trg_area_quantity=_TRG_AREA_QTY_ aligntype=_ALIGNTYPE_ aligndate=_ALIGNDATE_ ct_level = _CTLEVEL_ pri = _PRI_;

   num maxperiod = sum {a in ACTIVITIES} duration[a] - 1;
   set PERIODS = 0..maxperiod;
   set ACTIVITIES_PERIODS = {a in ACTIVITIES, p in PERIODS: p+duration[a]-1 in PERIODS};
   
   var IsStart {ACTIVITIES_PERIODS} binary;
   var StartDate{ACTIVITIES} integer >= 0 <= maxperiod;
   impvar FinishDate{a in ACTIVITIES} = StartDate[a] + duration[a];
   con StartOnce {a in ACTIVITIES}:
      sum {<(a),p> in ACTIVITIES_PERIODS} IsStart[a,p] = 1;
   impvar Start {a in ACTIVITIES} = sum {<(a),p> in ACTIVITIES_PERIODS} p * IsStart[a,p];
   impvar Finish {a in ACTIVITIES} = Start[a] + duration[a];
   
  
   con Precedence {<a,s> in ACTIVITY_SUCCESSOR: s ne ''}:
      Finish[a] <= Start[s];

   con Alignment {a in ACTIVITIES: aligntype[a] = 'seq'}:
      Start[a] = aligndate[a];

   var MaxFinish integer >= max {a in ACTIVITIES} duration[a];
       /*min Makespan = max{a in ACTIVITIES} Finish[a];*/
   min Makespan = MaxFinish;
   con MaxFinishCon {a in ACTIVITIES}:
      MaxFinish >= Finish[a];


   /* LOCATION INFORMATION */
   set <str,str> TRG_AREA_POOL;
   num trg_area_subquantity {TRG_AREA_POOL} init 1;

   /* READ LOCATION DATA AND CAPACITIES */
   read data TRG_AREA_DATA into TRG_AREA_POOL=[_TRG_AREA_ _TRG_AREA_POOL_] trg_area_subquantity=_TRG_AREA_SUBQTY_;
   set <str> TRG_AREA_RESOURCES;
   num capacity {TRG_AREA_RESOURCES};
   read data TRG_AREA_DATA into TRG_AREA_RESOURCES=[_TRG_AREA_] capacity=_TRG_AREA_CAPACITY_;
   put TRG_AREA_RESOURCES;
   TRG_AREA_POOL = TRG_AREA_POOL union setof {r in TRG_AREA_RESOURCES} <r,r>;
   put TRG_AREA_POOL;
   print trg_area_subquantity;
   set TA_RESOURCES_ACTIVITIES = {r in TRG_AREA_RESOURCES, a in ACTIVITIES: <r,trg_area_resource[a]> in TRG_AREA_POOL};
   put TA_RESOURCES_ACTIVITIES;
  /* SELECTS THE RESOURCE(S) TO BE ALLOCATED TO THE ACTIVITY */
   var IsRes {TA_RESOURCES_ACTIVITIES} binary;
   /*var IsDailyRes {ACTIVITY_DAILY_RESOURCES} binary;*/
/* CONSTRAINS ONLY LOCATION TO AN ACTIVITY */
   con OneResourcePerActivity {a in ACTIVITIES}:
      sum {<r,trg_area_resource[a]> in TRG_AREA_POOL} IsRes[r,a] = 1;
   var IsResPeriod {TA_RESOURCES_ACTIVITIES, PERIODS} binary;
   con IsResPeriodImpliesIsRes {<r,a> in TA_RESOURCES_ACTIVITIES, p in PERIODS}:
      IsResPeriod[r,a,p] <= IsRes[r,a];
   con IsStartImpliesResource {<a,p> in ACTIVITIES_PERIODS, p2 in p..p+duration[a]-1}:
      trg_area_quantity[a] * IsStart[a,p] <= sum {<r,trg_area_resource[a]> in TRG_AREA_POOL} trg_area_subquantity[r,trg_area_resource[a]] * IsResPeriod[r,a,p2];
   con ResourceCapacity {r in TRG_AREA_RESOURCES, p in PERIODS}:
      sum {<(r),a> in TA_RESOURCES_ACTIVITIES} IsResPeriod[r,a,p] <= capacity[r];


solve;

quit;


 

SAS Employee
Posts: 453

Re: Resource Constraints using Proc CLP

[ Edited ]

I edited my code to properly account for multiple resource requirements per activity.  Please try it with the following form of your data and see if it yields correct results:

data ACTIVITYDATA;
   input _ACTIVITY_ $ _SUCCESSOR_ $ _DURATION_ _RESOURCE_ $ _QTY_ _CTLEVEL_ $ _PRI_ ;
   if _ACTIVITY_ = 'CALGVACA' then do;
      _ALIGNTYPE_ = 'seq';
      _ALIGNDATE_ = 2;
   end;
   datalines;
AA1 AA3 12 CANADA 1 CT1 23
AA2 AA3 20 UK 1 CT2 12
AA3 AB2 30 CANADA 1 CT1 10
AA4 AB3 30 CANADA 1 CT3 25
AB2 AB3 20 CANADA 1 CT1 8
AB3 . 10 UK 1 CT2 12
AB4 AB3 51 LONDON 1 CT4 21
CALGVACA . 1 CALGARY 1 . .
AA1 AA3 12 SG 1 CT1 23
AA2 AA3 20 IG 1 CT2 12
AA2 AA3 20 WG 1 CT2 12
AA2 AA3 20 SG 1 CT2 12
AA3 AB2 30 GG 1 CT1 10
AA3 AB2 30 SG 1 CT1 10
AA4 AB3 30 SG 1 CT3 25
AB2 AB3 30 IG 1 CT1 8
AB2 AB3 30 SG 1 CT1 8
AB3 . 10 IG 1 CT2 12
AB4 AB3 51 GG 1 CT4 21
AA1 AA3 12 WR 23 CT1 23
AA1 AA3 12 CR 12 CT1 23
AA2 AA3 20 CR 10 CT2 12
AA3 AB2 30 BD 34 CT3 10
AA4 AB3 30 WR 21 CT3 25
AB2 AB3 20 BD 32 CT1 8
AB4 AB3 51 CR 10 CT4 21
AA4 AB3 30 CR 6 CT3 25
;

data RESOURCEDATA;
infile datalines truncover; input _RESOURCE_ $ _CAPACITY_ _POOL_ $ _SUBQTY_; datalines; LONDON 1 UK 1 EDINBURGH 1 UK 1 VANCOVER 1 CANADA 1 CALGARY 1 CANADA 1 LONDON 1 CANADA 1 SG 1
IG 1 WG 1 GG 1 WR 25 CR 12 BD 35 ;

By the way, you have specified two different durations (20 and 30) for AB2.  Only the first one is used. 

Highlighted
Occasional Contributor
Posts: 6

Re: Resource Constraints using Proc CLP

Thanks very Much.  It produces the results required.

Ask a Question
Discussion stats
  • 7 replies
  • 520 views
  • 0 likes
  • 4 in conversation