haorenhui
2023-10-30 6d6cc10d9e8e242661da7fd655dec155a09d676c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
Quintiq file version 2.0
#parent: #root
Method InitConstraintsForDemandFulfillmentInPISPIP (
  CapacityPlanningSuboptimizer_CapacityPlanningAlgorithm program,
  const RunContextForCapacityPlanning runcontext,
  const LibOpt_Scope scope,
  const constcontent ProductInStockingPointInPeriodPlannings pispipsinrun,
  Number threadnr
) const
{
  Description: 'Calculate the total demand fulfillment for a pispip'
  TextBody:
  [*
    //   SUM( OperationDemandQty ( operationinput, period ) )
    // + SUM( TripDemandQty(dependentdemand.productintrip) )
    // + SUM( SalesDemandQty ( leaf sales demand ) )
    // + SUM( DelayedSalesDemandQty(delayed leaf sales demand ) )
    // + SUM( DisaggregatedSalesDemandQty ( disaggregated sales demand ) )
    // + SUM( DelayedDisaggregatedSalesDemandQty( delayed disaggregated sales demand ) )
    // – DemandFulfillmentInPISPIP
    // = 0   ∀ pispip where operationinput, period, sales demands, dependentdemand, ∈ pispip
    
    constname := typeof( MPDemandFulfillmentInPISPIPConstraint );
    
    scalefactor_demandfulfillmentinpispip_const := this.ScaleConstraintTerm( typeof( MPDemandFulfillmentInPISPIPVariable ), constname );
    scalefactor_operationdemandqty_const := this.ScaleConstraintTerm( typeof( MPOperationDemandQtyVariable ), constname );
    scalefactor_tripdemandqty_const := this.ScaleConstraintTerm( typeof( MPTripDemandQtyVariable ), constname );
    scalefactor_salesdemandqty_const := this.ScaleConstraintTerm( typeof( MPSalesDemandQtyVariable ), constname );
    scalefactor_delayedsalesdemandqty_const := this.ScaleConstraintTerm( typeof( MPDelayedSalesDemandQtyVariable ), constname );
    
    scalefactor_rhs_const := this.ScaleConstraintRHS( constname, 1.0 );
    
    // The demand fulfillment constraint should be specified for all pispips whose inventory specification in days is considered
    pispips := this.GetPISPIPsForDemandFulfillment( scope, pispipsinrun );
    
    // Calculate the total demand fulfillment for used in inventory specification in days.
    traverse( pispips, Elements, pispip, CapacityPlanningSuboptimizer::GetThreadNr( this.ThreadAParameter(), this.ThreadBParameter(), pispip.PreThreadNr() ) = threadnr  )
    {
      // const constraint UoM: PISP
      const := program.DemandFulfillmentInPISPIPConstraints().New( pispip );
      const.Sense( '=' );
      const.RHSValue( 0.0 * scalefactor_rhs_const );
    
      // Term UoM: PISP
      const.NewTerm( -1.0 * scalefactor_demandfulfillmentinpispip_const,
                     program.DemandFulfillmentInPISPIPVariables().Get( pispip ) );
    
      traverse( pispip.GetLeavesOfProductDimensionConst(), 
                Elements.astype( ProductInStockingPointInPeriodPlanningLeaf ), 
                activepispip,
                pispip.IsLeafPlanning() or not activepispip.ProductInStockingPoint_MP().IsNegativeInventoryAllowed()  )
      {
        // The leaf pispip only have variables if they are part of this optimizer run
        if( activepispip = pispip // we know pispip is in scope so we can skip the check (for leaves this will be the only activepispip)
            or scope.Contains( activepispip.PISPIPInOptimizerRun() ) ) 
        {
          // Dependent demands for operations
          traverse( activepispip, ProductInStockingPoint_MP.OperationInputAvailableForOptimization, input )
          {
            var := program.OperationDemandQtyVariables().Find( input, pispip.Period_MP() );
            // The OperationDemandQty variable will be null if this combination of operation and period are not part of this optimizer run
            // In that case, this variable is not relevant and should not be added to this constraint
            // Also if the product is not part of the optimizer run, we should not add this term
            if( not isnull( var )
                and ( input.HasRegularProductforOptimizer() 
                      or input.GetIsProductInOptimizerRun( runcontext.IsPostProcessing() ) ) )
            {
              // Term UoM: PISP
              const.NewTerm( 1.0 * scalefactor_operationdemandqty_const,
                             var );
            }
            // If the variable is null (and the decision is thus out of scope of the optimizer ), then we need to update the RHS to reflect this frozen quantity
            else
            {
              ptoperation := PeriodTaskOperation::FindPeriodTaskOperationTypeIndex( pispip.Period_MP().Start(), input.Operation().ID() );
              dependentdemand := select( ptoperation, DependentDemand, dd, dd.ProcessInput() = input );
              if( not isnull( dependentdemand ) )
              {
                newrhs := this.GetConstraintRHS( const, scalefactor_rhs_const ) - dependentdemand.FulfilledQuantity();
                const.RHSValue( newrhs * scalefactor_rhs_const );
              }
            }
          }
      
          // Dependent demands for trips
          traverse( activepispip, DependentDemandTrip.ProductInTrip, productintrip )
          {
            if( scope.Contains( productintrip.ProductInTripInOptimizerRun() ) ) 
            {
              // Term UoM: PISP
              const.NewTerm( 1.0 * scalefactor_tripdemandqty_const, program.TripDemandQtyVariables().Get( productintrip ) );
            }
            else if( productintrip.Quantity() > 0.0 )
            {
              fulfilledqty := guard(  productintrip.DependentDemand().FulfilledQuantity(), 0.0 ); 
              newrhs := this.GetConstraintRHS( const, scalefactor_rhs_const ) - fulfilledqty;
              const.RHSValue( newrhs * scalefactor_rhs_const );
            }
          }
    
          // Sales demands
          traverse( activepispip, PlanningBaseSalesDemandInPeriodForOptimization, sd )
          {
            if( sd.istype( LeafSalesDemandInPeriod ) )
            {
              // Term UoM: PISP
              const.NewTerm( 1.0 * scalefactor_salesdemandqty_const, program.SalesDemandQtyVariables().Get( sd.astype( LeafSalesDemandInPeriod ) ) );
            }
            else if( sd.istype( DisaggregatedSalesDemandInPeriod ) )
            {
              const.NewTerm( 1.0 * scalefactor_salesdemandqty_const, program.DisaggregatedSalesDemandQtyVariables().Get( sd.astype( DisaggregatedSalesDemandInPeriod ) ) );
            }                    
          }
          
          // Calculating the postponed sales demand fulfillment
          // E.g., if the current period is period 11, max postponment period = 3, this retrieves postponed sales demands to period 11 from period 10, 9, 8.
          maxpostponedperiod := pispip.ProductInStockingPoint_MP().OptimizerMaxPostponementPeriod(); // set in in init instance per pisp for performance 
          previouspispip := activepispip.PreviousPlanningPISPIP();
    
          for( i := 1;
               i <= maxpostponedperiod                     // within the maximum number of postponement periods
               and not isnull( previouspispip );            // the next pispip exists
               i++ )
          {
            traverse( previouspispip.astype( ProductInStockingPointInPeriodPlanningLeaf ), PlanningBaseSalesDemandInPeriodForOptimizationPostponable, sd )
            {
              var := sd.GetDelayedSalesDemandQtyVariable( program, activepispip.Period_MP() );
    
              if( not isnull( var ) )
              {
                // Term UoM: PISP
                const.NewTerm( 1.0 * scalefactor_delayedsalesdemandqty_const, var );
              }
            }
            previouspispip := previouspispip.PreviousPlanningPISPIP();
          }
        }
        // If the leaf pispip is not in the optimizer run we still need to update the RHS
        else
        {
          // The RHS should be updated for the dependent demands that are related to the pispip that is out of scope of this optimizer run
          traverse( activepispip, DependentDemandOperation, dependentdemand )
          {
            newrhs := this.GetConstraintRHS( const, scalefactor_rhs_const ) - dependentdemand.FulfilledQuantity();
            const.RHSValue( newrhs * scalefactor_rhs_const );
          }
          
          traverse( activepispip, DependentDemandTrip.ProductInTrip, pit, not scope.Contains( pit.ProductInTripInOptimizerRun() ) )
          {
            newrhs := this.GetConstraintRHS( const, scalefactor_rhs_const ) - pit.Quantity()
            const.RHSValue( newrhs * scalefactor_rhs_const );
          }
        }
      }
    }
  *]
  InterfaceProperties { Accessibility: 'Module' }
}