Initializing help system before first use

Project scheduling problem with disjunctive resource constraints


Type: Project scheduling
Rating: 3 (intermediate)
Description: Project scheduling problem with disjunctive resource constraints and minimum and maximum delays between tasks, formulated
  • with 'disjunctive' constraints (bridgescheduling.mos), or
  • using task and resources objects (bridgescheduling_alt.mos)
File(s): bridgescheduling.mos, bridgesched_graph.mos, bridgescheduling_alt.mos
Data file(s): bridgescheduling.dat, bridgescheduling_alt.dat


bridgescheduling.mos
(!****************************************************************

   CP example problems
   ===================
   
   file bridgescheduling.mos
   `````````````````````````
   Project scheduling problem with disjunctive resource constraints.         
 
   We wish to schedule a set of tasks for the construction of a     
   bridge. Each task uses a particular resource (excavator,        
   pileDriver, ...) and can only start after the completion of its 
   predecessor(s). A resource can only be used by one task at a    
   time (disjunctive resources).                                   
   The aim is to minimize the total duration of the schedule (PE).    

   *** This model cannot be run with a Community Licence 
       for the provided data instance ***

   Copyright(C) 2008 Artelys S.A. and Fair Isaac Corporation
                     Creation: 2005, rev. Mar. 2013
*****************************************************************!)

model "Bridge Scheduling (CP)"
 uses "kalis"
 
 declarations       
  RESOURCES: set of string            ! Set of resources
  TASKS: set of string                ! Set of tasks   
  DUR  : array(TASKS) of integer      ! Durations of tasks      
  USE  : array(TASKS) of string       ! Resource used by a task
  NPREC: array(TASKS) of integer      ! Number of predecessors of a task
  PRECS: array(TASKS,range) of string ! Predecessors of each task

  SetEE, SetES, SetSS, SetSE: range   ! Index sets of distance data
  DISTEE: array(SetEE,1..2) of string ! End-to-end max. distance
  DISTES: array(SetES,1..2) of string ! End-to-start max. distance
  DISTSS: array(SetSS,1..2) of string ! Start-to-start min. distance
  DISTSE: array(SetSE,1..2) of string ! Start-to-end min. distance
 end-declarations

 initializations from "Data/bridgescheduling.dat"
  [DUR, NPREC, USE] as "TASKDATA"
  PRECS
  DISTEE DISTES DISTSS DISTSE
 end-initializations

 finalize(TASKS)
 forall(t in TASKS | USE(t)<>"") RESOURCES += {USE(t)}

 setparam("KALIS_DEFAULT_LB", 0)
 setparam("KALIS_DEFAULT_UB", sum(t in TASKS) DUR(t))
  
 declarations       
  start: array(TASKS) of cpvar              ! Start times of tasks 
  Disj,Disj_crane,Disj_brick: set of cpctr  ! Disjunction constraints   
  Strategy: array(range) of cpbranching     ! Branching strategy
 end-declarations

!******************************************************************
! **** Create disjunctions between tasks using the same resource ****
 procedure post_disjunctive_constraints(r:string)
  declarations
   usedby: set of cpvar
   durby : array(cpvar) of integer
  end-declarations

  nbr := 0     
  forall (t in TASKS | USE(t) = r) do
   usedby += {start(t)}
   durby(start(t)) := DUR(t)
   nbr += 1
  end-do  
  if (r="crane") then
   disjunctive(usedby,durby,Disj_crane,0)
  elif (r="bricklaying") then 
   disjunctive(usedby,durby,Disj_brick,0)    
  else   
   disjunctive(usedby,durby,Disj,0)
  end-if      
 end-procedure


! **** Distance constraints between starts or ends of certain task pairs ****
 procedure post_distance_constraints
  forall(i in SetEE)                    ! End-to-end max. distance
   start(DISTEE(i,1)) + DUR(DISTEE(i,1)) + 4 >= start(DISTEE(i,2)) +
                                                DUR(DISTEE(i,2))
  forall(i in SetES)                    ! End-to-start max. distance
   start(DISTES(i,1)) + DUR(DISTES(i,1)) + 3 >= start(DISTES(i,2)) 
  forall(i in SetSS)                    ! Start-to-start min. distance
   start(DISTSS(i,1)) + 6 <= start(DISTSS(i,2))
  forall(i in SetSE)                    ! Start-to-end min. distance
   start(DISTSE(i,1)) + DUR(DISTSE(i,1)) - 2 <= start(DISTSE(i,2))
 end-procedure 

!******************************************************************
! **** Model definition *****
 setparam("kalis_auto_propagate", false) ! Do not post constraints at definition

 forall(t in TASKS) setname(start(t),t) ! Set variable names        

! Precedences between a task and its predecessors 
 forall(t in TASKS, i in 1..NPREC(t))
  start(PRECS(t,i)) + DUR(PRECS(t,i)) <= start(t)

! Disjunctions between tasks using the same resource
 forall(r in RESOURCES) post_disjunctive_constraints(r)
  
! Define min/max distance between certain starts and ends of task pairs
 post_distance_constraints

! Post all constraints
 if (not cp_propagate) then
  writeln("Problem is infeasible")
  exit(1)
 end-if 

! Define the branching strategy: branch first on bottleneck resources  
 Strategy(1) := settle_disjunction(Disj_crane)
 Strategy(2) := settle_disjunction(Disj_brick)
 Strategy(3) := split_domain(KALIS_LARGEST_MAX, KALIS_MIN_TO_MAX) 
 cp_set_branching(Strategy) 
 
! Uncomment to limit computation time 
 setparam("KALIS_MAX_COMPUTATION_TIME", 10)

 setparam("KALIS_VERBOSE_LEVEL", 1)

 if not(cp_minimize(start("PE"))) then
  writeln("Problem is infeasible")
  exit(1)
 end-if 
 
 ct:= 0 
 forall(t in TASKS) do
  write(t, "=", getsol(start(t)), if(ct mod 10=9, ",\n", ", "))
  ct+=1
 end-do
 writeln("\nMakespan: ", getsol(start("PE")))
 
 writeln("Proof of optimality in:")
 cp_show_stats
   
end-model 


  

bridgesched_graph.mos
(!****************************************************************

   CP example problems
   ===================
   
   file bridgescheduling.mos
   `````````````````````````
   Project scheduling problem with disjunctive resource constraints. 
   - Graphical solution representation -        
 
   We wish to schedule a set of tasks for the construction of a     
   bridge. Each task uses a particular resource (excavator,        
   pileDriver, ...) and can only start after the completion of its 
   predecessor(s). A resource can only be used by one task at a    
   time (disjunctive resources).                                   
   The aim is to minimize the total duration of the schedule (PE).    

   *** This model cannot be run with a Community Licence 
       for the provided data instance ***

   Copyright(C) 2008 Artelys S.A. and Fair Isaac Corporation
                     Creation: 2005, rev. Sep. 2017
*****************************************************************!)

model "Bridge Scheduling (CP)"
 uses "kalis", "mmsvg"
 
 forward procedure show_solution

 declarations       
  RESOURCES: set of string            ! Set of resources
  TASKS: set of string                ! Set of tasks   
  DUR  : array(TASKS) of integer      ! Durations of tasks      
  USE  : array(TASKS) of string       ! Resource used by a task
  NPREC: array(TASKS) of integer      ! Number of predecessors of a task
  PRECS: array(TASKS,range) of string ! Predecessors of each task

  SetEE, SetES, SetSS, SetSE: range   ! Index sets of distance data
  DISTEE: array(SetEE,1..2) of string ! End-to-end max. distance
  DISTES: array(SetES,1..2) of string ! End-to-start max. distance
  DISTSS: array(SetSS,1..2) of string ! Start-to-start min. distance
  DISTSE: array(SetSE,1..2) of string ! Start-to-end min. distance
 end-declarations

 initializations from "Data/bridgescheduling.dat"
  [DUR, NPREC, USE] as "TASKDATA"
  PRECS
  DISTEE DISTES DISTSS DISTSE
 end-initializations

 finalize(TASKS)
 forall(t in TASKS | USE(t)<>"") RESOURCES += {USE(t)}

 setparam("KALIS_DEFAULT_LB", 0)
 setparam("KALIS_DEFAULT_UB", sum(t in TASKS) DUR(t))
  
 declarations       
  start: array(TASKS) of cpvar              ! Start times of tasks 
  Disj,Disj_crane,Disj_brick: set of cpctr  ! Disjunction constraints   
  Strategy: array(range) of cpbranching     ! Branching strategy
 end-declarations

!******************************************************************
! **** Create disjunctions between tasks using the same resource ****
 procedure post_disjunctive_constraints(r:string)
  declarations
   usedby: set of cpvar
   durby : array(cpvar) of integer
  end-declarations

  nbr := 0     
  forall (t in TASKS | USE(t) = r) do
   usedby += {start(t)}
   durby(start(t)) := DUR(t)
   nbr += 1
  end-do  
  if (r="crane") then
   disjunctive(usedby,durby,Disj_crane,0)
  elif (r="bricklaying") then 
   disjunctive(usedby,durby,Disj_brick,0)    
  else   
   disjunctive(usedby,durby,Disj,0)
  end-if      
 end-procedure


! **** Distance constraints between starts or ends of certain task pairs ****
 procedure post_distance_constraints
  forall(i in SetEE)                    ! End-to-end max. distance
   start(DISTEE(i,1)) + DUR(DISTEE(i,1)) + 4 >= start(DISTEE(i,2)) +
                                                DUR(DISTEE(i,2))
  forall(i in SetES)                    ! End-to-start max. distance
   start(DISTES(i,1)) + DUR(DISTES(i,1)) + 3 >= start(DISTES(i,2)) 
  forall(i in SetSS)                    ! Start-to-start min. distance
   start(DISTSS(i,1)) + 6 <= start(DISTSS(i,2))
  forall(i in SetSE)                    ! Start-to-end min. distance
   start(DISTSE(i,1)) + DUR(DISTSE(i,1)) - 2 <= start(DISTSE(i,2))
 end-procedure 

!******************************************************************
! **** Model definition *****
 setparam("kalis_auto_propagate", false)  ! Do not post constraints at definition

 forall(t in TASKS) setname(start(t),t) ! Set variable names        

! Precedences between a task and its predecessors 
 forall(t in TASKS, i in 1..NPREC(t))
  start(PRECS(t,i)) + DUR(PRECS(t,i)) <= start(t)

! Disjunctions between tasks using the same resource
 forall(r in RESOURCES) post_disjunctive_constraints(r)
  
! Define min/max distance between certain starts and ends of task pairs
 post_distance_constraints

! Post all constraints
 if (not cp_propagate) then
  writeln("Problem is infeasible")
  exit(1)
 end-if 

! Define the branching strategy: branch first on bottleneck resources  
 Strategy(1) := settle_disjunction(Disj_crane)
 Strategy(2) := settle_disjunction(Disj_brick)
 Strategy(3) := split_domain(KALIS_LARGEST_MAX, KALIS_MIN_TO_MAX) 
 cp_set_branching(Strategy) 

 setparam("KALIS_MAX_COMPUTATION_TIME", 5)

 if not(cp_minimize(start("PE"))) then
  writeln("Problem is infeasible")
  exit(1)
 elif getparam("KALIS_SEARCH_LIMIT")=KALIS_SLIM_BY_TIME then
  writeln("Search time limit reached")
 else 
  writeln("Optimal solution found")
 end-if
 
 ct:= 0 
 forall(t in TASKS) do
  write(t, "=", getsol(start(t)), if(ct mod 10=9, ",\n", ", "))
  ct+=1
 end-do
 writeln("\nMakespan: ", getsol(start("PE")))
 
 cp_show_stats
  
 show_solution

!******************************************************************
! **** Graphical representation of solutions ****
 procedure show_solution    
  ind:= 1
  forall(r in RESOURCES) do
   svgaddgroup(r, r)
   svgsetstyle(SVG_FILL,SVG_CURRENT)
   svgsetstyle(SVG_STROKE,SVG_GRAY)
   forall(ind as counter, t in TASKS | USE(t) = r) do      
    svgaddtext((2*getsol(start(t))+DUR(t))/2 -0.5 , ind+1.5, t) 
    svgaddrectangle(getsol(start(t)), ind, if(DUR(t)=0,0.1,DUR(t)), 0.9)
   end-do  
  end-do

  svgsetgraphscale(5)
  svgsetgraphviewbox(0,0,getsol(start("PE"))+10,ind+5)
  svgsetgraphlabels("Time", "Resources")

  svgsave("bridge.svg")
  svgrefresh
  svgwaitclose("Close browser window to terminate model execution.", 1)
 end-procedure
 
end-model 


  

bridgescheduling_alt.mos
(!****************************************************************

   CP example problems
   ===================
   
   file bridgescheduling_alt.mos
   `````````````````````````````
   Project scheduling problem with disjunctive resource constraints. 
   - Alternative formulation using resource and task objects -        
 
   We wish to schedule a set of tasks for the construction of a     
   bridge. Each task uses a particular resource (excavator,        
   pileDriver, ...) and can only start after the completion of its 
   predecessor(s). A resource can only be used by one task at a    
   time (disjunctive resources).                                   
   The aim is to minimize the total duration of the schedule (PE).    

   *** This model cannot be run with a Community Licence 
       for the provided data instance ***

   Copyright(C) 2008 Artelys S.A. and Fair Isaac Corporation
                     Creation: 2005
*****************************************************************!)

model "Bridge Construction Schedule"  
 uses "kalis"

 declarations       
  RESOURCES: set of string             ! Set of resources
  TASKS: set of string                 ! Set of tasks   
  DUR  : array(TASKS) of integer       ! Durations of tasks      
  USE  : array(TASKS) of string        ! Resource used by a task
  PRECS: dynamic array(TASKS) of set of string ! Predecessors of each task

  SetEE, SetES, SetSS, SetSE: range    ! Index sets of distance data
  DISTEE: array(SetEE,1..2) of string  ! End-to-end max. distance
  DISTES: array(SetES,1..2) of string  ! End-to-start max. distance
  DISTSS: array(SetSS,1..2) of string  ! Start-to-start min. distance
  DISTSE: array(SetSE,1..2) of string  ! Start-to-end min. distance
 end-declarations

 initializations from "Data/bridgescheduling_alt.dat"
  [DUR, USE] as "TASKDATA"
  PRECS
  DISTEE DISTES DISTSS DISTSE
 end-initializations

 finalize(TASKS)
 forall(t in TASKS | USE(t)<>"") RESOURCES += {USE(t)}
 finalize(RESOURCES)
 
 declarations  
  res: array(RESOURCES) of cpresource ! Declaration of resources
  task: array(TASKS) of cptask        ! Declaration of tasks
 end-declarations

! Setting up disjunctive resources (only one task at a time can be processed)
 forall(r in RESOURCES)
  set_resource_attributes(res(r), KALIS_UNARY_RESOURCE, 1)

! Setting the task attributes (duration, resource use and predecessors)
 forall(t in TASKS) do
  setduration(task(t), DUR(t))
  if USE(t)<>"" then
   requires(task(t), 1, res(USE(t)))
  end-if
  if exists(PRECS(t)) then
   setpredecessors(task(t), union(j in PRECS(t)) {task(j)})
  end-if
 end-do

! Additional distance constraints between tasks
  forall(i in SetEE)                    ! End-to-end max. distance
   getend(task(DISTEE(i,1))) + 4 >= getend(task(DISTEE(i,2)))
  forall(i in SetES)                    ! End-to-start max. distance
   getend(task(DISTES(i,1))) + 3 >= getstart(task(DISTES(i,2))) 
  forall(i in SetSS)                    ! Start-to-start min. distance
   getstart(task(DISTSS(i,1))) + 6 <= getstart(task(DISTSS(i,2)))
  forall(i in SetSE)                    ! Start-to-end min. distance
   getend(task(DISTSE(i,1))) - 2 <= getstart(task(DISTSE(i,2)))

! Find the optimal schedule (minimizing the makespan)
 if cp_schedule(getmakespan) = 0 then
  writeln("Inconsistent schedule")
 else
! Print the solution
  ct:= 0 
  forall(t in TASKS) do
   write(t, "=", getsol(getstart(task(t))), if(ct mod 10=9, ",\n", ", "))
   ct+=1
  end-do
  writeln("\nMakespan: ", getsol(getmakespan))
  !cp_show_sol 
  cp_show_stats
 end-if

end-model