Quintiq file version 2.0
|
#parent: #root
|
Method InitConstraintsGoalsForPISPIPs (
|
CapacityPlanningSuboptimizer_CapacityPlanningAlgorithm program,
|
const RunContextForCapacityPlanning runcontext,
|
const LibOpt_Scope scope,
|
const constcontent ProductInStockingPointInPeriodPlanningLeafs leafpispipsinrun,
|
const constcontent ProductInStockingPoint_MPs pispsinrun
|
) const
|
{
|
Description: 'Init constraints goals for PISPIPs'
|
TextBody:
|
[*
|
collectvaluesmodel := runcontext.IsMetaIteration();
|
|
// calculate customer satisfaction
|
// ffconst constraint UoM: Default
|
ffconst := program.TotalFulfillmentConstraints().New();
|
ffconst.Sense( '=' );
|
ffconst.RHSValue( this.ScaleConstraintRHS( typeofexpression( ffconst ), 0.0 ));
|
// Term UoM: Default
|
ffconst.NewTerm( 1.0 * this.ScaleConstraintTerm( typeof( MPTotalFulfillmentVariable ), typeofexpression( ffconst ))
|
, program.TotalFulfillmentVariables().Get() );
|
|
// calculate target inventory level penalty
|
// ssconst constraint UoM: PISP --> please note that this means that if different product in stocking points have different UoM,
|
// their target inventory will have a different weight in the goal
|
ssconst := program.TotalTargetInvLevelConstraints().New();
|
ssconst.Sense( '=' );
|
ssconst.RHSValue( this.ScaleConstraintRHS( typeofexpression( ssconst ), 0.0 ) );
|
// Term UoM: ~PISP
|
ssconst.NewTerm( 1.0 * this.ScaleConstraintTerm( typeof( MPTotalTargetInvLevelVariable ), typeofexpression( ssconst ))
|
, program.TotalTargetInvLevelVariables().Get() );
|
|
// calculate minimum inventory level penalty
|
// minlevelconst constraint UoM: PISP --> please note that this means that if different product in stocking points have different UoM,
|
// their minimum inventory will have a different weight in the goal
|
minlevelconst := program.TotalMinimumInventoryLevelConstraints().New();
|
minlevelconst.Sense( '=' );
|
minlevelconst.RHSValue( this.ScaleConstraintRHS( typeofexpression( minlevelconst ), 0.0 ) );
|
// Term UoM: ~PISP
|
minlevelconst.NewTerm( 1.0 * this.ScaleConstraintTerm( typeof( MPTotalMinimumInventoryLevelVariable ), typeofexpression( minlevelconst ) )
|
, program.TotalMinimumInventoryLevelVariables().Get() );
|
|
// calculate maximum inventory level penalty
|
// maxlevelconst constraint UoM: PISP --> please note that this means that if different product in stocking points have different UoM,
|
// their maximum inventory will have a different weight in the goal
|
maxlevelconst := program.TotalMaximumInventoryLevelConstraints().New();
|
maxlevelconst.Sense( '=' );
|
maxlevelconst.RHSValue( this.ScaleConstraintRHS( typeofexpression( maxlevelconst ), 0.0 ) );
|
// Term UoM: ~PISP
|
maxlevelconst.NewTerm( 1.0 * this.ScaleConstraintTerm( typeof( MPTotalMaximumInventoryLevelVariable ), typeofexpression( maxlevelconst ) )
|
, program.TotalMaximumInventoryLevelVariables().Get() );
|
|
// calculate priority
|
// pconst constraint UoM: Default
|
pconst := program.TotalSalesDemandPriorityConstraints().New();
|
pconst.Sense( '=' );
|
pconst.RHSValue( this.ScaleConstraintRHS( typeofexpression( pconst ), 0.0 ) );
|
// Term UoM: Default
|
pconst.NewTerm( 1.0 * this.ScaleConstraintTerm( typeof( MPTotalSalesDemandPriorityVariable ), typeofexpression( pconst ))
|
, program.TotalSalesDemandPriorityVariables().Get() );
|
|
// calculate postponed sales demand penalty
|
// ppconst constraint UoM: Monetary
|
ppconst := program.TotalPostponementPenaltyConstraints().New();
|
ppconst.Sense( '=' );
|
ppconst.RHSValue( this.ScaleConstraintRHS( typeofexpression( ppconst ), 0.0 ) );
|
// Term UoM: Monetary
|
ppconst.NewTerm( 1.0 * this.ScaleConstraintTerm( typeof( MPTotalPostponementPenaltyVariable ), typeofexpression( ppconst ) )
|
, program.TotalPostponementPenaltyVariables().Get() );
|
|
// Calculate the total inventory mix balancing KPI
|
// The total inventory mix balancing KPI is equal to sum of the overall maximum difference in days over all periods
|
// plus the sum of all difference in demand days over all categories and periods
|
// TotalDiffInDays = ∑_period〖 OverallDiffInDays( period ) ã€ + ∑_category ∑_period 〖 DifferenceInDays( category, period ) ã€â€â€
|
|
// imbconstr UoM: Days
|
imbconstr := program.TotalInventoryMixBalancingConstraints().New();
|
imbconstr.Sense( '=' );
|
imbconstr.RHSValue( this.ScaleConstraintRHS( typeofexpression( imbconstr ), 0.0 ) );
|
// term UoM: Days
|
imbconstr.NewTerm( this.ScaleConstraintTerm( typeof( MPTotalInventoryMixBalancingVariable ), typeofexpression( imbconstr ) )
|
, program.TotalInventoryMixBalancingVariables().Get() );
|
|
// Calculate total expiry
|
expconst := program.TotalExpiredQtyConstraints().New();
|
expconst.Sense( '=' );
|
expconst.RHSValue( this.ScaleConstraintRHS( typeofexpression( expconst ), 0.0 ) );
|
|
scalefactor_salesdemandqty_ffconst := this.ScaleConstraintTerm( typeof( MPSalesDemandQtyVariable ), typeofexpression( ffconst ) );
|
scalefactor_salesdemandqty_pconst := this.ScaleConstraintTerm( typeof( MPSalesDemandQtyVariable ), typeofexpression( pconst ) );
|
scalefactor_invqtyundertarget_ssconst := this.ScaleConstraintTerm( typeof( MPInvQtyUnderTargetVariable ), typeofexpression( ssconst ) );
|
scalefactor_mininvqtyunder_minlevelconst := this.ScaleConstraintTerm( typeof( MPMinInvQtyUnderVariable ), typeofexpression( minlevelconst ) );
|
scalefactor_maxinvqtyover_maxlevelconst := this.ScaleConstraintTerm( typeof( MPMaxInvQtyOverVariable ), typeofexpression( maxlevelconst ) );
|
scalefactor_delayedsalesdemandqty_ffconst := this.ScaleConstraintTerm( typeof( MPDelayedSalesDemandQtyVariable ), typeofexpression( ffconst ) );
|
scalefactor_delayedsalesdemandqty_ppconst := this.ScaleConstraintTerm( typeof( MPDelayedSalesDemandQtyVariable ), typeofexpression( ppconst ) );
|
scalefactor_delayedsalesdemandqty_pconst := this.ScaleConstraintTerm( typeof( MPDelayedSalesDemandQtyVariable ), typeofexpression( pconst ) );
|
scalefactor_diffindays_imbconstr := this.ScaleConstraintTerm( typeof( MPDifferenceInDemandDaysVariable ), typeofexpression( imbconstr ) );
|
scalefactor_overalldd_imbconstr := this.ScaleConstraintTerm( typeof( MPOverallDiffInvInDemandDaysVariable ), typeofexpression( imbconstr ) );
|
scalefactor_totalexpired_expconst := this.ScaleConstraintTerm( typeof( MPTotalExpiredQtyVariable ), typeofexpression( expconst ));
|
scalefactor_expired_expconst := this.ScaleConstraintTerm( typeof( MPExpiredVariable ), typeofexpression( expconst ) );
|
scalefactor_salesdemandqtyvar := CapacityPlanningSuboptimizer::GetVariableScaleFactor( typeof( MPSalesDemandQtyVariable ) );
|
scalefactor_undertargetvar := CapacityPlanningSuboptimizer::GetVariableScaleFactor( typeof( MPInvQtyUnderTargetVariable ) );
|
scalefactor_underminvar := CapacityPlanningSuboptimizer::GetVariableScaleFactor( typeof( MPMinInvQtyUnderVariable ) );
|
scalefactor_overmaxvar := CapacityPlanningSuboptimizer::GetVariableScaleFactor( typeof( MPMaxInvQtyOverVariable ) );
|
|
// Consider the leaf pispips in this optimizer run
|
totalfulfillmentvariablesvalue := 0.0;
|
totalsalesdemandpriovariablesvalue := 0.0;
|
totalpostponementvariablesvalue := 0.0;
|
|
isdefinesdp := runcontext.WeightLevelNonFinancial().SalesDemandPriorityWeight() <> 0.0 and runcontext.WeightLevelNonFinancial().SalesDemandPriorityLevel() >= 1;
|
isdefinedpostpenalty := runcontext.WeightLevelNonFinancial().PostponementPenaltyWeight() <> 0.0 and runcontext.WeightLevelNonFinancial().PostponementPenaltyLevel() >= 1;
|
isdefinedff := runcontext.WeightLevelNonFinancial().FulfillmentWeight() <> 0.0 and runcontext.WeightLevelNonFinancial().FulfillmentLevel() >= 1;
|
|
traverse( leafpispipsinrun, Elements, pispip )
|
{
|
// note the following relation path does not inlude postponed sales demand
|
traverse( pispip, PlanningBaseSalesDemandInPeriodForOptimization, sd, not sd.MasterSalesDemand().IsExcludedFromFulfillmentKPI() )
|
{
|
uomconversion := sd.DefaultUOMConversionFactor();
|
ffconst_coefficient := ifexpr( isdefinedff, uomconversion * scalefactor_salesdemandqty_ffconst, 0.0 );
|
pconst_coefficient := ifexpr( isdefinesdp, uomconversion * guard( -sd.Priority().Weight(), 0 ) * scalefactor_salesdemandqty_pconst, 0.0 );
|
|
var := sd.GetSalesDemandQtyVariable( program );
|
if ( not isnull( var ) )
|
{
|
// Term: uomconversion * SalesDemandQty variable
|
// UoM: [PISP to Default] * [PISP]
|
ffconst.NewTerm( -ffconst_coefficient, var );
|
// Term: uomconversion * -sd.Priority.Weight * SalesDemandQty variable
|
// UoM: [PISP to Default] * [-] * [PISP]
|
pconst.NewTerm( pconst_coefficient, var );
|
}
|
|
if ( collectvaluesmodel and not isnull( var ) )
|
{
|
totalfulfillmentvariablesvalue := totalfulfillmentvariablesvalue + (sd.FulfilledQuantity() * ffconst_coefficient / scalefactor_salesdemandqtyvar );
|
totalsalesdemandpriovariablesvalue := totalsalesdemandpriovariablesvalue + ( sd.FulfilledQuantity() * (-pconst_coefficient) / scalefactor_salesdemandqtyvar );
|
}
|
}
|
}
|
|
// Goal has to take postponed sales demands into account
|
traverse( leafpispipsinrun, Elements, pispip )
|
{
|
maxpostponementperiod := pispip.ProductInStockingPoint_MP().OptimizerMaxPostponementPeriod();
|
previouspispip := pispip.PreviousPlanningPISPIP();
|
|
for( i := 1;
|
i <= maxpostponementperiod // within the maximum number of postponement periods
|
and not isnull( previouspispip ); // the previous pispip exists
|
i++
|
)
|
{
|
// this path contains postponable salesdemand, but excludes actual postponed sales demand
|
traverse( previouspispip, astype( ProductInStockingPointInPeriodPlanningLeaf ).PlanningBaseSalesDemandInPeriodForOptimizationPostponable, sd,
|
not sd.MasterSalesDemand().IsExcludedFromFulfillmentKPI() )
|
{
|
penalty := sd.GetPenalty();
|
|
sdpostponed := select( sd, PostponedSalesDemand, p, true, p.ProductInStockingPointInPeriodPlanning() = pispip and not p.IsManuallyPostponed() );
|
fulfilledqty := guard( sdpostponed.FulfilledQuantity(), 0.0 );
|
|
uomconversion := sd.DefaultUOMConversionFactor();
|
coeffactor_ffconst := ifexpr( isdefinedff, uomconversion * scalefactor_delayedsalesdemandqty_ffconst, 0.0 );
|
coeffactor_ppconst := ifexpr( isdefinedpostpenalty, penalty * i * scalefactor_delayedsalesdemandqty_ppconst, 0.0 );
|
coeffactor_pconst := ifexpr( isdefinesdp, uomconversion * guard( sd.Priority().Weight(), 0 ) * scalefactor_delayedsalesdemandqty_pconst, 0.0 );
|
|
var := sd.GetDelayedSalesDemandQtyVariable( program, pispip.Period_MP() );
|
|
if( not isnull( var ) )
|
{
|
// Term: uomconversion * DelayedSalesDemandQty variable
|
// UoM: [PISP to Default] * [PISP]
|
ffconst.NewTerm( -coeffactor_ffconst, var ); // Fulfillment
|
// Term: penalty * i * DelayedSalesDemandQty
|
// Uom: [Monetary/PISP] * [-] * [PISP]
|
ppconst.NewTerm( -coeffactor_ppconst, var ); // Penalty
|
|
pconst.NewTerm( -coeffactor_pconst, var ); // sales demand priority
|
|
if ( collectvaluesmodel )
|
{
|
totalfulfillmentvariablesvalue := totalfulfillmentvariablesvalue + ( fulfilledqty * coeffactor_ffconst / CapacityPlanningSuboptimizer::GetVariableScaleFactor( typeofexpression( var ) ) );
|
totalpostponementvariablesvalue := totalpostponementvariablesvalue + ( fulfilledqty * coeffactor_ppconst / CapacityPlanningSuboptimizer::GetVariableScaleFactor( typeofexpression( var ) ) );
|
totalsalesdemandpriovariablesvalue := totalsalesdemandpriovariablesvalue + ( fulfilledqty * coeffactor_pconst / CapacityPlanningSuboptimizer::GetVariableScaleFactor( typeofexpression( var ) ) );
|
}
|
}
|
}
|
|
previouspispip := previouspispip.PreviousPlanningPISPIP();
|
}
|
}
|
|
this.StoreValueInProgram( program, 'collect_values_model_MPTotalFulfillmentVariables', totalfulfillmentvariablesvalue );
|
this.StoreValueInProgram( program, 'collect_values_model_MPTotalSalesDemandPriorityVariable', totalsalesdemandpriovariablesvalue );
|
this.StoreValueInProgram( program, 'collect_values_model_MPTotalPostponementPenaltyVariables', totalpostponementvariablesvalue );
|
|
|
// Consider the non-leaf pispips for the high level inventory specifications
|
leafpispips := null( ProductInStockingPointInPeriodPlannings, constcontent, owning );
|
pispips := this.GetPISPIPsForInventorySpecifications( scope, false, &leafpispips );
|
|
totalinvundertarget := 0.0;
|
totalinvundermin := 0.0;
|
totalinvvovermax := 0.0;
|
traverse( pispips, Elements, pispip )
|
{
|
// target inventory level
|
targetunder_defined := pispip.GetTargetUnderDefined( scope, runcontext );
|
if( targetunder_defined )
|
{
|
// Term UoM: PISP
|
coeffactor := scalefactor_invqtyundertarget_ssconst * pispip.ProductInStockingPoint_MP().DefaultUOMConversionFactor();
|
targetundervar := program.InvQtyUnderTargetVariables().Get( pispip )
|
ssconst.NewTerm( -1.0 * coeffactor, targetundervar );
|
|
violation := (coeffactor / scalefactor_undertargetvar ) * maxvalue( 0.0, pispip.TargetInventoryLevel() - pispip.InventoryLevelEnd() );
|
totalinvundertarget := totalinvundertarget + violation;
|
|
if ( pispip.Start() < runcontext.FirstPeriod_MP().Start() and pispip.GetHasTargetInDays() ) // make hard constraint for pispip prior to optimizer horizon in meta
|
{
|
violationatstartrun := (coeffactor / scalefactor_undertargetvar ) * pispip.ViolationTargetInventoryRunStart();
|
boundcons := program.BoundTargetInventoryViolationPriorToHorizonConstraints().New( pispip ); // need constraint because of parallel thread init
|
boundcons.Sense( '<=' );
|
boundcons.RHSValue( violationatstartrun );
|
boundcons.NewTerm( 1.0, targetundervar );
|
boundcons.NewTerm( -1.0, program.InvQtySpecPriorToHorizonSlackVariables().Get( pispip ) );
|
}
|
}
|
|
// Penalty for not reaching the minimum inventory level
|
minunder_defined := pispip.GetMinUnderDefined( scope, runcontext );
|
if( minunder_defined )
|
{
|
// Term UoM: PISP
|
coeffactor := scalefactor_mininvqtyunder_minlevelconst * pispip.ProductInStockingPoint_MP().DefaultUOMConversionFactor();
|
minundervar := program.MinInvQtyUnderVariables().Get( pispip );
|
minlevelconst.NewTerm( -1.0 * coeffactor, minundervar );
|
|
violation := ( coeffactor/ scalefactor_underminvar ) * maxvalue( 0.0, pispip.MinInventoryLevel() - pispip.InventoryLevelEnd() );
|
totalinvundermin := totalinvundermin + violation ;
|
|
if ( pispip.Start() < runcontext.FirstPeriod_MP().Start() and pispip.GetHasMinLevelInDays() ) // make hard constraint for pispip prior to optimizer horizon in meta
|
{
|
violationatstartrun := ( coeffactor/ scalefactor_underminvar ) * pispip.ViolationMinInventoryRunStart();
|
boundcons := program.BoundMinInventoryViolationPriorToHorizonConstraints().New( pispip ); // need constraint because of parallel thread init
|
boundcons.Sense( '<=' );
|
boundcons.RHSValue( violationatstartrun );
|
boundcons.NewTerm( 1.0, minundervar );
|
boundcons.NewTerm( -1.0, program.InvQtySpecPriorToHorizonSlackVariables().Get( pispip ) );
|
}
|
}
|
|
// Penalty for exceeding the maximum inventory level
|
maxover_defined := pispip.GetMaxOverDefined( scope, runcontext );
|
if( maxover_defined )
|
{
|
// Term UoM: PISP
|
coeffactor := scalefactor_maxinvqtyover_maxlevelconst * pispip.ProductInStockingPoint_MP().DefaultUOMConversionFactor();
|
maxovervar := program.MaxInvQtyOverVariables().Get( pispip );
|
maxlevelconst.NewTerm( -1.0 * coeffactor, maxovervar );
|
|
violation := ( coeffactor/ scalefactor_overmaxvar ) * maxvalue( 0.0, pispip.InventoryLevelEnd() - pispip.MaxInventoryLevel() );
|
totalinvvovermax := totalinvvovermax + violation ;
|
|
if ( pispip.Start() < runcontext.FirstPeriod_MP().Start() and pispip.GetHasMaxLevelInDays() ) // make hard constraint for pispip prior to optimizer horizon in meta
|
{
|
violationatstartrun := ( coeffactor/ scalefactor_overmaxvar ) * pispip.ViolationMaxInventoryRunStart();
|
boundcons := program.BoundMaxInventoryViolationPriorToHorizonConstraints().New( pispip ); // need constraint because of parallel thread init
|
boundcons.Sense( '<=' );
|
boundcons.RHSValue( violationatstartrun );
|
boundcons.NewTerm( 1.0, maxovervar );
|
boundcons.NewTerm( -1.0, program.InvQtySpecPriorToHorizonSlackVariables().Get( pispip ) );
|
}
|
}
|
}
|
|
// workaround due to not being able to write to attribute
|
this.StoreValueInProgram( program, 'collect_values_model_MPTotalMaximumInventoryLevelVariable', totalinvvovermax );
|
this.StoreValueInProgram( program, 'collect_values_model_MPTotalMinimumInventoryLevelVariable', totalinvundermin );
|
this.StoreValueInProgram( program, 'collect_values_model_MPTotalTargetInventoryLevelVariable', totalinvundertarget );
|
|
if( runcontext.UseInventoryMixBalancing() )
|
{
|
productcategories := Product_MP::GetProductCategoriesForOptimization( this.MacroPlan(), scope );
|
isfirstcategory := true;
|
|
traverse( productcategories, Elements, productcategory )
|
{
|
// Retrieve all pispips that are part of this product category and that are part of the optimizer run
|
pispips := selectset( productcategory.GetLeavesConst(), Elements.ProductInStockingPoint_MP.ProductInStockingPointInPeriodPlanning, pispip,
|
scope.Contains( pispip.PISPIPInOptimizerRun() ) )
|
// Select all periods related to these pispips
|
periods := selectset( pispips, Elements.Period_MP, period, true );
|
|
traverse( periods, Elements, period )
|
{
|
imbconstr.NewTerm( -1.0 * scalefactor_diffindays_imbconstr, program.DifferenceInDemandDaysVariables().Get( productcategory, period ) );
|
|
// The overall difference in demand days does not depend on the category
|
// Therefore, we should only add a term if this is the first category (without this check we would add one term per category)
|
if( isfirstcategory )
|
{
|
imbconstr.NewTerm( -1.0 * scalefactor_overalldd_imbconstr, program.OverallDiffInvInDemandDaysVariables().Get( period ) );
|
}
|
}
|
isfirstcategory := false;
|
}
|
}
|
|
if( runcontext.UseExpiredQty() )
|
{
|
// PISPExpire[PISP]: PISPExpire[PISP] - SUM(( 1.0 + ( offset from last period * 0.01 ) ) * Expired[pispip]) = 0
|
// TotalExpire: TotalExpire - SUM(PISPExpire[PISP]) = 0
|
expconst.NewTerm( 1.0 * scalefactor_totalexpired_expconst
|
, program.TotalExpiredQtyVariables().Get() );
|
|
|
traverse( pispsinrun, Elements, pisp, pisp.IsOptShelfLife() )
|
{
|
|
pispexpvar := program.PISPTotalExpiryVariables().Get( pisp );
|
expconst.NewTerm( -1.0 * scalefactor_expired_expconst,
|
pispexpvar
|
);
|
|
pispexpconst := program.PISPTotalExpiryConstraints().New( pisp );
|
pispexpconst.Sense("=" );
|
pispexpconst.RHSValue( 0.0 )
|
pispexpconst.NewTerm( 1.0 * scalefactor_expired_expconst,
|
pispexpvar
|
);
|
|
traverse( pisp, ProductInStockingPointInPeriod.astype( ProductInStockingPointInPeriodPlanningLeaf ), pispip, scope.Contains( pispip.PISPIPInOptimizerRun() ) )
|
// we can avoid scope.Contains( pispip.PISPIPInOptimizerRun() ) ) - same transaction
|
{
|
pispexpconst.NewTerm( -1.0 * scalefactor_expired_expconst,
|
program.ExpiredVariables().Get( pispip )
|
);
|
}
|
}
|
}
|
*]
|
InterfaceProperties { Accessibility: 'Module' }
|
}
|