yanweiyuan3
2023-08-09 588bc7829387dfc761cc25f06f77d4c81818bd10
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
Quintiq file version 2.0
#parent: #root
Method InitConstraintsForInventoryMixBalancing (
  CapacityPlanningSuboptimizer_CapacityPlanningAlgorithm program,
  const RunContextForCapacityPlanning runcontext,
  const LibOpt_Scope scope
) const
{
  Description: 'Initialize the constraints that are related to balancing the inventory mix'
  TextBody:
  [*
    if( runcontext.UseInventoryMixBalancing() )
    {       
      overalldiffconstrname := typeof( MPOverallDifferenceInDemandDaysConstraint );
      overallmaxconstrname := typeof( MPOverallMaxInvInDemandDaysConstraint );
      overallminconstrname := typeof( MPOverallMinInvInDemandDaysConstraint );
      diffindaysconstrname := typeof( MPDifferenceInDemandDaysConstraint );
      mininvindaysconstrname := typeof( MPMinInvInDemandDaysConstraint );
      maxinvindaysconstrname := typeof( MPMaxInvInDemandDaysConstraint );
      
      scalefactor_overalldiff_overalldiffconstr := this.ScaleConstraintTerm( typeof( MPOverallDiffInvInDemandDaysVariable ), overalldiffconstrname );
      scalefactor_overallmax_overalldiffconstr := this.ScaleConstraintTerm( typeof( MPOverallMaxInvInDemandDaysVariable ), overalldiffconstrname );
      scalefactor_overallmin_overalldiffconstr := this.ScaleConstraintTerm( typeof( MPOverallMinInvInDemandDaysVariable ), overalldiffconstrname );
      scalefactor_overallmax_overallmaxconstrname := this.ScaleConstraintTerm( typeof( MPOverallMaxInvInDemandDaysVariable ), overallmaxconstrname );
      scalefactor_overallmin_overallminconstrname := this.ScaleConstraintTerm( typeof( MPOverallMinInvInDemandDaysVariable ), overallminconstrname );
      scalefactor_diffindays_diffindaysconstr := this.ScaleConstraintTerm( typeof( MPDifferenceInDemandDaysVariable ), diffindaysconstrname );
      scalefactor_maxinvindays_diffindaysconstr := this.ScaleConstraintTerm( typeof( MPMaxInvInDemandDaysVariable ), diffindaysconstrname );
      scalefactor_mininvindays_diffindaysconstr := this.ScaleConstraintTerm( typeof( MPMinInvInDemandDaysVariable ), diffindaysconstrname );
      scalefactor_maxinvindays_overallmaxconstr := this.ScaleConstraintTerm( typeof( MPMaxInvInDemandDaysVariable ), overallmaxconstrname );
      scalefactor_mininvindays_overallminconstr := this.ScaleConstraintTerm( typeof( MPMinInvInDemandDaysVariable ), overallminconstrname );
      scalefactor_maxinvindays_maxinvindaysconstr := this.ScaleConstraintTerm( typeof( MPMaxInvInDemandDaysVariable ), maxinvindaysconstrname );
      scalefactor_mininvindays_mininvindaysconstr := this.ScaleConstraintTerm( typeof( MPMinInvInDemandDaysVariable ), mininvindaysconstrname );
      scalefactor_invqty_maxinvindaysconstr := this.ScaleConstraintTerm( typeof( MPInvQtyVariable ), maxinvindaysconstrname );
      scalefactor_invqty_mininvindaysconstr := this.ScaleConstraintTerm( typeof( MPInvQtyVariable ), mininvindaysconstrname );
      
      scalefactor_rhs_mininvindaysconstr := this.ScaleConstraintRHS( mininvindaysconstrname, 1.0 );
      scalefactor_rhs_maxinvindaysconstr := this.ScaleConstraintRHS( maxinvindaysconstrname, 1.0 );
      
      productcategories := Product_MP::GetProductCategoriesForOptimization( this.MacroPlan(), scope );
      
      allperiods := construct( Period_MPs, constcontent ); 
      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 );
        
        // The inventory mix will be balanced per period 
        traverse( periods, Elements, period )
        {                           
          allperiods.Add( period );       
    
            // The overall max is equal to the maximum of all max inv in demand days
            // OverallMaxInvInDemandDays(period) ≥ MaxInvInDemandDays( category, period ) ∀category,period
            // overallmaxconstr UoM: Days
            overallmaxconstr := program.OverallMaxInvInDemandDaysConstraints().New( productcategory, period );
            overallmaxconstr.Sense( '>=' );
            overallmaxconstr.RHSValue( 0.0 );
            overallmaxconstr.NewTerm( scalefactor_overallmax_overallmaxconstrname, program.OverallMaxInvInDemandDaysVariables().Get( period ) );
          
            // The overall min is equal to the minimum of all min inv in demand days
            // OverallMinInvInDemandDays ≤ MinInvInDemandDays( category, period ) ∀category,period
            // overallminconstr UoM: Days
            overallminconstr := program.OverallMinInvInDemandDaysConstraints().New( productcategory, period );
            overallminconstr.Sense( '<=' );
            overallminconstr.RHSValue( 0.0 );
            overallminconstr.NewTerm( scalefactor_overallmin_overallminconstrname, program.OverallMinInvInDemandDaysVariables().Get( period ) );        
          
          // The difference in days is equal to the max inventory in days minus the min inventory in days.
          // DifferenceInDays(category,period) = MaxInvInDemandDays(category,period) - MinInvInDemandDays(category,period)  ∀category,period
          // diffindays constraint UoM: Days
          diffindaysconstr := program.DifferenceInDemandDaysConstraints().New( productcategory, period );
          diffindaysconstr.Sense( '=' );
          diffindaysconstr.RHSValue( 0.0 );
          // term UoM: Days
          diffindaysconstr.NewTerm( -1.0 * scalefactor_diffindays_diffindaysconstr, program.DifferenceInDemandDaysVariables().Get( productcategory, period ) );
          diffindaysconstr.NewTerm( scalefactor_maxinvindays_diffindaysconstr, program.MaxInvInDemandDaysVariables().Get( productcategory, period ) );
          diffindaysconstr.NewTerm( -1.0 * scalefactor_mininvindays_diffindaysconstr, program.MinInvInDemandDaysVariables().Get( productcategory, period ) );    
            
          // Add terms to the overall constraints
          // Term UoM: Days
          overallmaxconstr.NewTerm( -1.0 * scalefactor_maxinvindays_overallmaxconstr, program.MaxInvInDemandDaysVariables().Get( productcategory, period ) );
          overallminconstr.NewTerm( -1.0 * scalefactor_mininvindays_overallminconstr, program.MinInvInDemandDaysVariables().Get( productcategory, period ) );
          
          traverse( productcategory.GetLeafPISPIPsConst( period.Start(), period.End() ), Elements.astype( ProductInStockingPointInPeriodPlanning ), pispip, 
                    pispip.ProductInStockingPoint_MP().IsIncludedInBalancing() 
                    and pispip.ProductInStockingPoint_MP().Product_MP().IsIncludedInBalancing() )                               
          {       
            // The inventory of each product in each period is sufficient to cover a certain number of demand days. This is denoted the inventory in days.
            // The minimum inventory in demand days is equal to the minimum of all products within a product category in each period
            // MinInvDemandDays(product category) ≤ ( InvQty(product, period) - TargetInv(product, period) )/ DemandPerDay(product, period)  ∀product category, product ∈ product category, period
            // mininvindays constraint UoM: Days
            mininvindaysconstr := program.MinInvInDemandDaysConstraints().New( productcategory, pispip );
            mininvindaysconstr.Sense( '<=' );
            mininvindaysconstr.RHSValue( 0.0 );
            // Term UoM: Days
            mininvindaysconstr.NewTerm( scalefactor_mininvindays_mininvindaysconstr, program.MinInvInDemandDaysVariables().Get( productcategory, period ) );
      
            // The inventory of each product in each period is sufficient to cover a certain number of demand days. This is denoted the inventory in days.
            // The maximum inventory in demand days is equal to the maximum of the inventory in days of all products within a product category in each period
            // MaxInvDemandDays(product category) ≥ ( InvQty(product, period) - TargetInv(product, period) )/ DemandPerDay(product, period)  ∀product category, product ∈ product category, period
            // maxinvindays constraint UoM: Days
            
            maxinvindaysconstr := program.MaxInvInDemandDaysConstraints().New( productcategory, pispip );
            maxinvindaysconstr.Sense( '>=' );
            maxinvindaysconstr.RHSValue( 0.0 );
            // Term UoM: Days
            maxinvindaysconstr.NewTerm( scalefactor_maxinvindays_maxinvindaysconstr, program.MaxInvInDemandDaysVariables().Get( productcategory, period ) );
                                      
            demandperday := pispip.GetAverageSalesDemandQtyPerDay();
            
            // If the average demand per day is 0 (meaning there is no demand)
            // then we should not balance this pispip
            if( demandperday <> 0 )
            {
              // If this pispip is part of the optimizer run, we need to a constraint term
              if( scope.Contains( pispip.PISPIPInOptimizerRun() ) )
              {  
                // The terms we are adding here are (InvEnd - InvTarget)/Demand per day
                // Therefore, we first add InvEnd/Demand per day as a variable term
                // and then update the RHS with - InvTarget/Demand per day
                
                // Term:  - ( 1 / demandperday )  * InvQty variable
                // UoM:   1 / [ PISP UoM  * Day ] *   [PISP UoM]
                mininvindaysconstr.NewTerm( - ( 1 / demandperday ) * scalefactor_invqty_mininvindaysconstr, program.InvQtyVariables().Get( pispip ) );
                maxinvindaysconstr.NewTerm( - ( 1 / demandperday ) * scalefactor_invqty_maxinvindaysconstr, program.InvQtyVariables().Get( pispip ) );
      
                // Update RHS with - InvTarget/Demand per day
                targetindays := 0.0;
                if( pispip.GetHasTargetInventory() )
                {
                  if( pispip.GetHasTargetInDays() )
                  {
                    targetindays := pispip.TargetInDays();
                  }
                  else
                  {
                    targetindays := pispip.TargetInQuantity() / demandperday;
                  }
                  newrhsmin := this.GetConstraintRHS( mininvindaysconstr, scalefactor_rhs_mininvindaysconstr ) - targetindays;
                  mininvindaysconstr.RHSValue( newrhsmin * scalefactor_rhs_mininvindaysconstr );
                  newrhsmax := this.GetConstraintRHS( maxinvindaysconstr, scalefactor_rhs_maxinvindaysconstr ) - targetindays;
                  maxinvindaysconstr.RHSValue( newrhsmax * scalefactor_rhs_maxinvindaysconstr );
                }                               
              }
              // otherwise, we need to update the RHS since the invqty is fixed
              else
              {
                targetinqty := 0.0;
                if( pispip.GetHasTargetInDays() )
                {
                  targetinqty := pispip.TargetInDays() * demandperday;
                }
                else
                {
                  targetinqty := pispip.TargetInQuantity();
                }
                
                newrhsmin := this.GetConstraintRHS( mininvindaysconstr, scalefactor_rhs_mininvindaysconstr ) + ( ( pispip.InventoryLevelEnd() - targetinqty ) / demandperday );
                mininvindaysconstr.RHSValue( newrhsmin * scalefactor_rhs_mininvindaysconstr );
                newrhsmax := this.GetConstraintRHS( maxinvindaysconstr, scalefactor_rhs_maxinvindaysconstr ) + ( ( pispip.InventoryLevelEnd() - targetinqty ) / demandperday );
                maxinvindaysconstr.RHSValue( newrhsmax * scalefactor_rhs_maxinvindaysconstr );
              }
            }
          }
        } // end traverse period
      } // end traverse product category
      
      allperiods := allperiods.Unique(); 
      
      traverse( allperiods, Elements, period ) 
      {      
        // The overall difference is equal to the overall max minus the overall min
        // OverallDifferenceInDays(period) = OverallMaxInvInDemandDays(period) - OverallMinInvInDemandDays(period) ∀period
        // overalldiffconstr UoM: Days
        overalldiffconstr := program.OverallDifferenceInDemandDaysConstraints().New( period );
        overalldiffconstr.Sense( '=' );
        overalldiffconstr.RHSValue( 0.0 );
        overalldiffconstr.NewTerm( -1.0 * scalefactor_overalldiff_overalldiffconstr, program.OverallDiffInvInDemandDaysVariables().Get( period ) );
        overalldiffconstr.NewTerm( scalefactor_overallmax_overalldiffconstr, program.OverallMaxInvInDemandDaysVariables().Get( period ) );
        overalldiffconstr.NewTerm( -1.0 * scalefactor_overallmin_overalldiffconstr, program.OverallMinInvInDemandDaysVariables().Get( period ) );
      }
    }
  *]
  InterfaceProperties { Accessibility: 'Module' }
}