! Compute critical paths, earliest and latest start times  (CritPath.lng);
! Keywords: CPM, Critical path, PERT, Project Management, Scheduling, Slack;
SETS:
   TASK: PTIME, ES, LS
       , WORK1, WORK2 ;
! The precedence relations, the first task in the
    precedence relationship needs to be completed before the
    second task can be started;
    PRED( TASK, TASK);
ENDSETS
DATA: ! Case: Prepare a marketing plan for a product; ! The precedence diagram is: ! /FCAST\---SCHED----COSTOUT\ ! / \ \ ! FIRST \ \ ! \ \ \ ! \SURVEY-PRICE-----------FINAL; ! The activities, or tasks; !CaseMP TASK= FIRST, FCAST, SURVEY, PRICE, SCHED, COSTOUT, FINAL; ! and their processing times; !CaseMP PTIME= 0, 14, 3, 3, 7, 4, 10; ! Here are the predecessor -> successor pairs; !CaseMP PRED= FIRST, FCAST, FIRST, SURVEY, FCAST, PRICE, FCAST, SCHED, SURVEY, PRICE, SCHED, COSTOUT, PRICE, FINAL, COSTOUT, FINAL; ! Case Build a Home: Dig basement, Pour Foundation, Pour basement floor, Install floor joists, Wall studding, Rough out interior, Roof, Landscaping; ! The activities, or tasks and their processing times; !CaseBH; TASK PTIME = DIG 3 FOUND 4 POURB 2 JOISTS 3 WALLS 5 RAFTERS 3 FLOOR 4 ROUGH 6 ROOF 7 FINISH 5 SCAPE 2 ; ! Here are the predecessor -> successor pairs; !CaseBH; PRED = DIG FOUND FOUND POURB FOUND JOISTS FOUND WALLS WALLS RAFTERS POURB RAFTERS JOISTS FLOOR FLOOR ROUGH RAFTERS ROOF ROUGH FINISH ROOF FINISH POURB SCAPE WALLS SCAPE ; ENDDATA PROCEDURE ErlStrtGet: ! Compute Earliest start times for each TASK. Inputs: PTIME( j) = processing time for task j, PRED( i, j) = predecessor->successor pairs, ES( j) = initial lower bound on earliest start of task j, Outputs: ErlStrt = 0 if successful, 1 if the PRED graph has cycles ES( j) = earliest start time for j, taking into account PRED; ! Count number of predecessors, WORK1( j) of each task j; @FOR( TASK( j) : WORK1( j) = 0); @FOR( PRED( j, k): WORK1( k) = WORK1( k) +1); ! Put all tasks with 0 preds on the WORK2 stack; stacknum = 0; @FOR( TASK( j) | WORK1( j) #EQ# 0: stacknum = stacknum + 1; WORK2( stacknum ) = j; ); ! While there is a task j on the stack; @WHILE( stacknum #GT# 0: ! Remove task j from stack. We now know its ES; jnow = WORK2( stacknum); stacknum = stacknum - 1; @FOR( PRED( j, k) | j #EQ# jnow: ES( k) = @SMAX( ES( k), ES( j) + PTIME( j)); ! Update ES( k) for all successors k; WORK1( k) = WORK1( k) - 1; ! decrement number unprocessed predecesors; @IFC( WORK1( k) #EQ# 0: ! Is k now ready for the ready stack?; stacknum = stacknum + 1; ! Yes, put on stack; WORK2( stacknum) = k; ); ); ); ENDPROCEDURE PROCEDURE LstStrtGet: ! Compute Latest start times for each TASK. Inputs: PTIME( j) = processing time for task j, PRED( i, j) = predecessor successor pairs, LS( j) = initial upper bound on latest start of task j, Outputs: LstStrt = 0 if successful, 1 if the PRED graph has cycles LS( j) = earliest start time for j, taking into account PRED; ! Count number of successors, WORK1( j) of each task j; @FOR( TASK( j) : WORK1( j) = 0); @FOR( PRED( j, k): WORK1( j) = WORK1( j) +1); ! Put all tasks with 0 successors on the WORK2 stack; stacknum = 0; @FOR( TASK( k) | WORK1( k) #EQ# 0: stacknum = stacknum + 1; WORK2( stacknum ) = k; ); ! While there is a task k on the stack; @WHILE( stacknum #GT# 0: ! Remove task k from stack, we now know its LS; know = WORK2( stacknum); stacknum = stacknum - 1; @FOR( PRED( j, k) | k #EQ# know: LS( j) = @SMIN( LS( j), LS( k) - PTIME( j)); ! Update LS( j) for all predecessors j; WORK1( j) = WORK1( j) - 1; ! decrement number unprocessed successors; @IFC( WORK1( j) #EQ# 0: ! Is j now ready for the ready stack?; stacknum = stacknum + 1; WORK2( stacknum) = j; ); ); ); ENDPROCEDURE CALC: @SET( 'OROUTE',1); ! Buffer size for routing output to window; @SET( 'TERSEO',1); ! Output level (0:verb, 1:terse, 2:only errors, 3:none); @SET( 'WNLINE',10000);! Max command window lines saved (Windows only); ! Begin: critical path, slack analysis; @FOR( TASK( j): ES( j) = 0); ! Initially, assume every task is available at time 0; ErlStrtGet; ! Get earliest start times; ! Compute latest starts; ! Earliest finish for project; PROJLEN = @MAX( TASK( j): ES( j) + PTIME( j)); @FOR( TASK( j): LS( j) = PROJLEN - PTIME( j)); ! Initial latest finish for all tasks; LstStrtGet; ! Get latest start times; ! Compute slack for each task; @FOR( TASK( j): WORK1( j) = LS( j) - ES( j)); ! Write a simple report; ! Put sort order in WORK2( j), sorted 1st on slack, 2nd on early start; WORK2 = @SORT( WORK1, ES); @WRITE(' Project length= ', PROJLEN, @NEWLINE( 1)); @WRITE(' Task Slack EarlyStart LateStart PTime', @NEWLINE( 1)); @FOR( TASK( j): ! Display sorted list; jnow = WORK2( j); xrow = j + 2; @WRITE( @FORMAT( TASK( jnow),'10s'), ' ', @FORMAT( WORK1( jnow) ,'6.1f') , ' ', @FORMAT( ES( jnow),'6.1f'), ' ', @FORMAT( LS( jnow),'6.1f'), ' ', @FORMAT( PTIME( jnow),'6.1f'), @NEWLINE( 1)); ); ! End: critical path, slack analysis; ENDCALC