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' }
|
}
|