Quintiq file version 2.0
|
#parent: #root
|
Method InitVariablesForPISPIP (
|
CapacityPlanningSuboptimizer_CapacityPlanningAlgorithm program,
|
const constcontent ProductInStockingPointInPeriods pispipssmartplan,
|
const LibOpt_Scope scope,
|
const constcontent ProductInStockingPointInPeriodPlannings pispipsinrun,
|
constcontent ProductInStockingPointInPeriodPlanningLeafs leafpispipsinrun,
|
const constcontent ProductInStockingPoint_MPs pispsinrun
|
) const
|
{
|
Description: 'Initialize all variables related to pispip, including fulfillment of sales demand, target inventory level, etc'
|
TextBody:
|
[*
|
runcontext := this.GetRunContextConst();
|
info( pispipsinrun.Size() );
|
info( leafpispipsinrun.Size() );
|
|
// Shelf life variable
|
traverse( pispsinrun, Elements, pisp, pisp.IsOptShelfLife() or pisp.IsOptMaturation() )
|
{
|
if ( pisp.IsOptShelfLife() )
|
{
|
program.PISPTotalExpiryVariables().New( pisp );
|
}
|
initpispips := pisp.PISPInOptimizerRun().GetPISPIPForShelfLifeOptimizer( scope );
|
traverse( initpispips, Elements, pispip )
|
{
|
traverse( pispip, ProductInStockingPoint_MP.IncomingShelfLifeDay, islday )
|
{
|
program.CumulativeProductionVariables().New( pispip, islday );
|
}
|
|
if ( pisp.IsOptShelfLife() )
|
{
|
program.ExpiredVariables().New( pispip );
|
program.InvQtyBlockedVariables().New( pispip );
|
}
|
traverse( pispip, ProductInStockingPoint_MP.IncomingShelfLifeDay, islday )
|
{
|
if ( pisp.IsOptMaturation() or pisp.IsOptShelfLife() )
|
{
|
traverse( pisp, OutgoingShelfLifeDay, oslday )
|
{
|
program.DependentDemandInPISPIPShelfLifeVariables().New( pispip, islday, oslday );
|
}
|
}
|
if ( pisp.IsOptShelfLife() )
|
{
|
program.CumulativeDemandVariables().New( pispip, islday );
|
program.CumulativeWasteVariables().New( pispip,islday );
|
program.WasteVariables().New( pispip, islday );
|
program.ExpiredForAgeVariables().New( pispip, islday );
|
// >> split variables for balance
|
|
program.InvQtyShelfLifeVariables().New( pispip, islday );
|
program.DemandSlackShelfLifeVariables().New( pispip, islday );
|
}
|
}
|
|
// Maturation variables
|
if( pispip.ProductInStockingPoint_MP().IsOptMaturation() )
|
{
|
traverse( pispip, ProductInStockingPoint_MP.OutgoingShelfLifeDay, oslday )
|
{
|
program.CumulativeDemandMaturationVariables().New( pispip, oslday );
|
program.MaturationSlackVariables().New( pispip, oslday );
|
}
|
}
|
}
|
}
|
|
traverse( leafpispipsinrun, Elements, pispip )
|
{
|
// DependentDemandInPISPIP variable UoM: PISP
|
program.DependentDemandInPISPIPVariables().New( pispip ); // Total dependent demand quantity in pispip
|
// DemandSlack variable UoM: PISP
|
program.DemandSlackVariables().New( pispip ); // Total decreased quantity of dependent demand to make the balance constraint feasible
|
|
if( pispip.MustInitializeUserSupplyConstraint( runcontext, scope ) )
|
{
|
// UserTotalSupply slack variables UoM: PISP
|
program.UserTotalSupplyUnderVariables().New( pispip );
|
program.UserTotalSupplyOverVariables().New( pispip );
|
}
|
|
// Inventory quantity
|
// InvQty variable UoM: PISP
|
varinvqty := program.InvQtyVariables().New( pispip );
|
|
// If this pispip allows negative inventory then invqty and unallocqty are allowed to be negative
|
if( pispip.ProductInStockingPoint_MP().IsNegativeInventoryAllowed() )
|
{
|
lowerbound := -Real::MaxReal();
|
this.FreezeVariableLowerBound( varinvqty, lowerbound );
|
|
// Positive inventory quantity
|
program.PosInvQtyVariables().New( pispip );
|
}
|
}
|
|
// declare PosInvQtyPastLast in case we need inventory holding costs
|
if ( runcontext.IsMetaIteration() and this.GetInitializeFinancialConstraints( runcontext ) )
|
{
|
traverse( pispsinrun, Elements, pisp )
|
{
|
last := pisp.LatestPISPIPInScope();
|
current := guard( last.Next().astype( ProductInStockingPointInPeriodPlanningLeaf ), null( ProductInStockingPointInPeriodPlanningLeaf ) );
|
|
while ( not isnull( current ) )
|
{
|
program.PosInvQtyPastLastVariables().New( current );
|
current := current.NextPlanningPISPIP().astype( ProductInStockingPointInPeriodPlanningLeaf );
|
}
|
}
|
}
|
|
//Only plan demand for selected PISPIP(s) during smart planning if requested by user.
|
if( runcontext.IsSmartPlan()
|
and this.IsOnlyPlanDemandForSmartPlanPISPIPs() )
|
{
|
castedpispipsmartplan := construct( ProductInStockingPointInPeriodPlanningLeafs, constcontent ) ;
|
traverse( pispipssmartplan, Elements.astype( ProductInStockingPointInPeriodPlanningLeaf ), p )
|
{
|
castedpispipsmartplan.Add( p );
|
}
|
pispipnotselected := leafpispipsinrun.Difference( castedpispipsmartplan );
|
|
//First bound all input pispips sales demand variables.
|
traverse( pispipnotselected, Elements, pispip )
|
{
|
traverse( pispip, PlanningBaseSalesDemandInPeriodForOptimization, sd )
|
{
|
var := null( MPVariable );
|
if( sd.istype( LeafSalesDemandInPeriod ) )
|
{
|
var := program.SalesDemandQtyVariables().Get( sd.astype( LeafSalesDemandInPeriod ) ); //pispipsmartplan elements should be a subset of the direct optimizer PISPIP relation.
|
}
|
else if( sd.istype( DisaggregatedSalesDemandInPeriod ) )
|
{
|
var := program.DisaggregatedSalesDemandQtyVariables().Get( sd.astype( DisaggregatedSalesDemandInPeriod ) ); //pispipsmartplan elements should be a subset of the direct optimizer PISPIP relation.
|
}
|
if( not isnull( var ) )
|
{
|
lowerbound := sd.FulfilledQuantity();
|
upperbound := sd.FulfilledQuantity();
|
this.FreezeVariableLowerUpperBound( var, lowerbound, upperbound );
|
}
|
// Freeze delayed sales demand variables
|
sd.FreezeVariablesForPostponedSalesDemands( this, program, scope );
|
}
|
}
|
}
|
|
// Create slack variables for the inventory specification constraints
|
// These specifications can also be on high level products, so we cannot use the regular traverse over the PISPIPsInOptimizerRun
|
leafpispips := null( ProductInStockingPointInPeriodPlannings, constcontent, owning );
|
pispips := this.GetPISPIPsForInventorySpecifications( scope, false, &leafpispips );
|
|
traverse( leafpispips, Elements, pispip, pispip.GetHasTargetInventory() ) // in case of meta optimizer call these do not include pispips prior to the scope (all in scope)
|
{
|
// TargetInvQty variable UoM: PISP
|
vartarget := program.TargetInvQtyVariables().New( pispip );
|
if( pispip.ProductInStockingPoint_MP().IsNegativeInventoryAllowed() ) // note carriedfwd inventory constraints states TargetInvQty < = InvQty at a pispip
|
{
|
vartarget.LowerBound( Real::MinReal() );
|
}
|
}
|
|
optscopestart := runcontext.FirstPeriod_MP().Start();
|
traverse( pispips, Elements, pispip )
|
{
|
hastargetinventory := pispip.GetHasTargetInventory();
|
hasminlevel := pispip.GetHasMinLevel();
|
hasmaxlevel := pispip.GetHasMaxLevel();
|
hasspec := hasmaxlevel or hasminlevel or hastargetinventory;
|
if ( hasspec and pispip.Start() < optscopestart )
|
{
|
program.InvQtySpecPriorToHorizonSlackVariables().New( pispip ); // slack variable ( e.g. feedback makes we cannot limit violation prior to the horizon by a hard constraint)
|
}
|
if( hastargetinventory )
|
{
|
// TargetInvQtyUnder variable UoM: PISP
|
program.InvQtyUnderTargetVariables().New( pispip ); // Penalty of not reaching the target
|
}
|
|
if( hasminlevel )
|
{
|
// MinInvQtyUnder variable UoM: PISP
|
program.MinInvQtyUnderVariables().New( pispip ); // Penalty for not reaching the minimum inventory level
|
}
|
|
if( hasmaxlevel )
|
{
|
// MaxInvQtyOver variable UoM: PISP
|
program.MaxInvQtyOverVariables().New( pispip ); // Penalty for not reaching the maximum inventory level
|
}
|
}
|
|
pispips := this.GetPISPIPsForDemandFulfillment( scope, pispipsinrun );
|
traverse( pispips, Elements, pispip )
|
{
|
// DemandFulfillmentInPISPIP variable UoM: PISP
|
program.DemandFulfillmentInPISPIPVariables().New( pispip ); // Total demand fulfillment quantity
|
}
|
|
// Define the servicelevel variables for each servicelevel that that has at least one sdip on a pispip in this optimizer run
|
traverse( this.MacroPlan(), AllServiceLevelBase, sl, sl.GetIsInOptimizerRun( scope ) )
|
{
|
// If this is a fulfillment goal related service level then add the related variables
|
if( sl.IsUsedForPlanningFulfillmentSystem() )
|
{
|
program.FulfillmentTargetVariables().New( sl );
|
}
|
else if( sl.IsUsedForSafetyStockCalculation() )
|
{
|
program.ServiceLevelQtyVariables().New( sl );
|
}
|
}
|
|
if( runcontext.UseInventoryMixBalancing() )
|
{
|
allperiods := construct( Period_MPs, constcontent );
|
productcategories := Product_MP::GetProductCategoriesForOptimization( this.MacroPlan(), scope );
|
|
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() ) )
|
// we can avoid scope.Contains( pispip.PISPIPInOptimizerRun() ) ) because we are in the same transaction
|
// Select all periods related to these pispips
|
periods := selectset( pispips, Elements.Period_MP, period, true );
|
|
traverse( periods, Elements, period )
|
{
|
allperiods.Add( period );
|
// Create the variables per product category and period which are used to balance the inventory mix within a category in a period
|
varmax := program.MaxInvInDemandDaysVariables().New( productcategory, period );
|
varmin := program.MinInvInDemandDaysVariables().New( productcategory, period );
|
varmax.LowerBound( Real::MinReal() );
|
varmin.LowerBound( Real::MinReal() );
|
program.DifferenceInDemandDaysVariables().New( productcategory, period );
|
}
|
}
|
allperiods := allperiods.Unique();
|
|
traverse( allperiods, Elements, period )
|
{
|
// Create the min and max inventory in days variables which are used to balance the inventory quantities per product category
|
// Create the overall variables that are used in the balancing of the categories
|
overallmaxvar := program.OverallMaxInvInDemandDaysVariables().New( period );
|
overallmaxvar.LowerBound( Real::MinReal() );
|
overallminvar := program.OverallMinInvInDemandDaysVariables().New( period );
|
overallminvar.LowerBound( Real::MinReal() );
|
program.OverallDiffInvInDemandDaysVariables().New( period );
|
}
|
}
|
|
// for debugging to fix a pispip fulfilled sales demand
|
/*
|
if( runcontext.IsMetaIteration() )
|
{
|
//First bound all input pispips sales demand variables.
|
traverse( leafpispipsinrun, Elements, pispip)
|
{
|
detected := pispip.ProductInStockingPoint_MP().Product_MP().Name() = 'FG-2-24-110-4614-12-0'
|
and pispip.StockingPointInPeriod().StockingPoint_MP().Name() = '624790'
|
and pispip.Start().Date() = Date::Construct( 2020, 7, 1 );
|
|
if ( detected )
|
{
|
traverse( pispip, PlanningBaseSalesDemandInPeriodForOptimization, sd )
|
{
|
var := null( MPVariable );
|
if( sd.istype( LeafSalesDemandInPeriod ) )
|
{
|
var := program.SalesDemandQtyVariables().Get( sd.astype( LeafSalesDemandInPeriod ) ); //pispipsmartplan elements should be a subset of the direct optimizer PISPIP relation.
|
}
|
else if( sd.istype( DisaggregatedSalesDemandInPeriod ) )
|
{
|
var := program.DisaggregatedSalesDemandQtyVariables().Get( sd.astype( DisaggregatedSalesDemandInPeriod ) ); //pispipsmartplan elements should be a subset of the direct optimizer PISPIP relation.
|
}
|
if( not isnull( var ) )
|
{
|
lowerbound := ifexpr( detected, sd.Quantity(), sd.FulfilledQuantity() );
|
upperbound := sd.Quantity();
|
this.FreezeVariableLowerUpperBound( var, lowerbound, upperbound );
|
if( detected )
|
{
|
debuginfo( 'FIXING sales demand - DEBUGGING', pispip.ProductInStockingPoint_MP().Name(), pispip.Start(), ' to ', sd.Quantity() );
|
}
|
}
|
}
|
}
|
}
|
}
|
*]
|
InterfaceProperties { Accessibility: 'Module' }
|
}
|