Quintiq file version 2.0 
 | 
#parent: #root 
 | 
Method InitConstraintsForShiftPatternMinDuration ( 
 | 
  CapacityPlanningSuboptimizer_CapacityPlanningAlgorithm program, 
 | 
  const LibOpt_Scope scope, 
 | 
  const UnitPeriodTime up, 
 | 
  const ShiftPattern sp, 
 | 
  Real scalefactor_spisused_const, 
 | 
  Real scalefactor_spisfirst_const, 
 | 
  Real scalefactor_mindurationslack_const, 
 | 
  Real scalefactor_rhs_minimumdurationconstr 
 | 
) const 
 | 
{ 
 | 
  Description: 'Checks that a shift pattern is used is used consecutively on a unit for at least a certain duration.' 
 | 
  TextBody: 
 | 
  [* 
 | 
    /*  This method ensures that a shift pattern is used consecutively on a unit for at least a certain duration. 
 | 
        The base constraint is as follows: 
 | 
        SUM( SPIsUsed [sp][up’] * duration[up’] ) >= minDuration * SPIsFirst[sp][up] 
 | 
     
 | 
        There are 3 possible cases: 
 | 
          1) The unit period is located within the optimizer scope, and the minimum duration ends in the scope as well: 
 | 
             For example, if the mininimum shift duration is 2 months (the optimizer scope is in brackets): 
 | 
                [up_january, up_february, up_march, up_may] -> for january, february and march we can use the regular constraint. 
 | 
          2) The unit period is located before the scope, but the minimum duration ends inside the scope: 
 | 
                16x5_november, 24x7_december, [up_january, up_february...] 
 | 
             Here, we need to create a constraint that starts in december, because it can still be influenced by the next months in scope. 
 | 
             However, the variable SPIsFirst is fixed. 
 | 
          3) The unit period is located right after the optimizer scope: 
 | 
                [..., up_may], 16x5_june, 24x5_july 
 | 
             Here, we have already created a SPIsFirst variable for june, so we need to create a constraint starting in june in order to force 
 | 
             the optimizer to set that variable. Since the minimum duration in this example is 2 months, the optimizer can't set SPIsFirst to true 
 | 
             in june because the shift 16x5 would then last only 1 month. So it has to start that shift in may at the latest. 
 | 
     
 | 
        Note: the optimizer scope is usually not continuous, and might look like this: 
 | 
        [up_january, up_february], 16x5, 24x5, 24x5, [up_june, up_july,...] 
 | 
        In that case, the behavior described above still applies. If the gap is shorter than the minimum duration, this falls into case 1. 
 | 
        If the gap is longer than the minimum duration, this falls into cases 2 and 3. */ 
 | 
     
 | 
    minimumdurationconstr := program.MinShiftPatternDurationConstraints().New( sp, up ); 
 | 
    minimumdurationconstr.Sense( ">=" ); 
 | 
     
 | 
    // Slack variable for minimum shift pattern duration. 
 | 
    // A positive slack variable represents a shift assigned to a unit period to even out the constraint. 
 | 
    minimumdurationconstr.NewTerm( 1.0 * scalefactor_mindurationslack_const, program.ShiftPatternDurationSlackVariables().Get( sp, up ) ); 
 | 
     
 | 
    minduration := up.GetRoundedMinimumShiftPatternDuration( sp.MinimumDuration().HoursAsReal() ); 
 | 
     
 | 
    // Set the following part of the constraint: minDuration * SPIsFirst[sp][up] 
 | 
    if( up.IsInScopeForShiftOptimization( scope )  
 | 
        or guard( up.PreviousPlanningUnitPeriod().IsInScopeForShiftOptimization( scope ), false ) ) 
 | 
    { 
 | 
      minimumdurationconstr.NewTerm( -minduration * scalefactor_spisfirst_const, program.ShiftPatternIsFirstVariables().Get( sp, up ) ); 
 | 
      minimumdurationconstr.RHSValue( 0.0 ); 
 | 
    } 
 | 
    else if( up.ShiftPattern() = sp ) 
 | 
    { 
 | 
      // If up is not in scope and the previous unit period is not in scope either, then up is the start of a sequence of identical shift patterns 
 | 
      // that ends right before the scope. So if that unit period uses sp, we need to update the RHS (because SPIsFirst[sp][up] is fixed and holds true). 
 | 
      minimumdurationconstr.RHSValue( minduration * scalefactor_rhs_minimumdurationconstr ); 
 | 
    } 
 | 
     
 | 
    // Iterate on every unit period, starting from up, to the unit period that ends after 'minduration' is elapsed. 
 | 
    up2 := up; 
 | 
    while( not isnull( up2 ) and ( up2.Start() - up.Start()).HoursAsReal() < minduration ) 
 | 
    { 
 | 
      duration := up2.Duration().HoursAsReal(); 
 | 
       
 | 
      // If the unit period is in scope, add a term with the corresponding variable. 
 | 
      if( up2.IsInScopeForShiftOptimization( scope ) ) 
 | 
      { 
 | 
        minimumdurationconstr.NewTerm( duration * scalefactor_spisused_const, program.ShiftPatternIsUsedVariables().Get( sp, up2 ) ); 
 | 
      } 
 | 
      else if( up2.ShiftPattern() = sp ) 
 | 
      { 
 | 
        // If up2 is not in scope and follows a unit period in scope that has the same shift pattern, update the RHS. 
 | 
        // In this case, the optimizer can still follow the minimum duration constraint using the last unit period of the scope. 
 | 
        newrhs := this.GetConstraintRHS( minimumdurationconstr, scalefactor_rhs_minimumdurationconstr ) - duration; 
 | 
        minimumdurationconstr.RHSValue( newrhs * scalefactor_rhs_minimumdurationconstr ); 
 | 
      } 
 | 
       
 | 
      up2 := guard( up2.NextPlanningUnitPeriod().astype( UnitPeriodTime ), null( UnitPeriodTime ) ); 
 | 
    } 
 | 
  *] 
 | 
  InterfaceProperties { Accessibility: 'Module' } 
 | 
} 
 |