admin
2025-01-22 7e31442f0e9b07764e9c6a9680d3d4aeba5fe1de
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
Quintiq file version 2.0
#parent: #root
Method InitConstraintsGoalsForDriverInventoryHolding (
  CapacityPlanningSuboptimizer_CapacityPlanningAlgorithm program,
  const RunContextForCapacityPlanning runcontext,
  const LibOpt_Scope scope,
  const constcontent ProductInStockingPoint_MPs pispsinrun
) const
{
  Description: 'Init constraints goals for PISPIPs'
  TextBody:
  [*
    // Inventory holding accounts
    
    icconstname := typeof( MPDriverInventoryHoldingConstraint );
    driverinventoryholding_varname := typeof( MPDriverInventoryHoldingVariable );
    invqty_varname := typeof( MPInvQtyVariable );
    partialoperationdemandqty_varname := typeof( MPPartialOperationDemandQtyVariable );
    tripdemandqty_varname := typeof( MPTripDemandQtyVariable );
    
    scalefactor_driver_icconst := this.ScaleConstraintTerm( driverinventoryholding_varname, icconstname );
    scalefactor_invqty_icconst := this.ScaleConstraintTerm( invqty_varname, icconstname );
    scalefactor_partialoperationdemandqty_icconst := this.ScaleConstraintTerm( partialoperationdemandqty_varname, icconstname );
    scalefactor_tripdemandqty_icconst := this.ScaleConstraintTerm( tripdemandqty_varname, icconstname );
    
    scalefactor_rhs_icconst := this.ScaleConstraintRHS( icconstname, 1.0 );
    
    ispostprocessing := runcontext.IsPostProcessing(); 
    
    driver := select( this, MacroPlan.AccountCostDriver, driver, driver.Name() = Translations::MP_AccountAssignmentCostDriverInventoryHolding() ); // unique
    traverse( scope.GetAccountsInOptimizerRunConst(), Elements, account, account.HasInventoryHoldingAssignment() ) // condition holds implies driver not null
    {
      // Inventory cost of each account = inventory quantity * cost + pre-processing quantity * day * cost per day + post-processing quantity * day * cost per day
      // icconst UoM: Monetary
      icconst := program.DriverInventoryHoldingConstraints().New( account, driver );
      icconst.Sense( '=' ); 
      icconst.RHSValue( 0.0 * scalefactor_rhs_icconst );
      // Term UoM: Monetary
      icconst.NewTerm( 1.0 * scalefactor_driver_icconst, program.DriverInventoryHoldingVariables().Get( account, driver ) );
    
      traverse( pispsinrun, Elements, pisp ) 
      {
        traverse( pisp, PISPAccountForInventoryHoldingOptimizer, aa, aa.AccountCostDriver() = driver and aa.Account_MP() = account ) 
        // Only consider those pispips that are part of this optimizer run
        // Only include those pispips where the product is included.
        { 
          if ( runcontext.IsMetaIteration() ) 
          {
            // correction for pispip past scope
            last := pisp.LatestPISPIPInScope(); 
            current := guard( last.Next().astype(  ProductInStockingPointInPeriodPlanningLeaf ), null(  ProductInStockingPointInPeriodPlanningLeaf ) ); 
            
            while ( not isnull( current ) ) 
            {
              var := program.PosInvQtyPastLastVariables().Get(  current ); 
              icconst.NewTerm( -current.GetBaseInventoryHoldingCostPerQuantity( aa ) * scalefactor_invqty_icconst, 
                                 var );               // Inventory cost 
              current := current.NextPlanningPISPIP().astype( ProductInStockingPointInPeriodPlanningLeaf );                            
            }  
          }
             
          traverse( aa.GetLeafPISPIPsInScope(), 
                    Elements, 
                    pispip ) 
          {
      
            if( pispip.ProductInStockingPoint_MP().IsNegativeInventoryAllowed() )
            {
              // Negative inventory allowed case: only consider positive inventory
              // Term: -holdingcostperqty * (Pos)InvQty variable
              // UoM:    [Monetary/PISP]    *   [PISP]
              icconst.NewTerm( -pispip.GetBaseInventoryHoldingCostPerQuantity( aa ) * scalefactor_invqty_icconst, 
                               program.PosInvQtyVariables().Get( pispip ) );               // Inventory cost 
            }
            else
            {
              // Regular case: no negative inventory
              icconst.NewTerm( -pispip.GetBaseInventoryHoldingCostPerQuantity( aa ) * scalefactor_invqty_icconst, 
                                program.InvQtyVariables().Get( pispip ) );               // Inventory cost   
            }
        
            pispipperiod := pispip.Period_MP();
      
            // Dependent demands WIP cost for operations that are part of this optimizer run
            if ( this.GetPeriodsFromPeriodTaskOperation() ) 
            { 
              traverse( pispip, DependentDemandOperation, dd ) // better path for performance
              {
                pto := dd.PeriodTaskOperation(); 
                operation := pto.Operation(); 
                if ( operation.HasLeadTime() ) 
                {
                  input := dd.ProcessInput().astype( OperationInput ); 
                  if ( ( input.HasRegularProductforOptimizer() or input.GetIsProductInOptimizerRun( ispostprocessing ) )
                       and not isnull( input.PISPwhenAvailableForOptimization() ) 
                       and scope.Contains( pto.PeriodTaskOperationInOptimizerRun() ) )  
                  {
                    cost := pispip.GetBaseWIPCostPerQuantityPerDay( aa ) * operation.LeadTime().DaysAsReal();
                    traverse( pto, NewSupply.ProductInStockingPointInPeriodPlanningLeaf.Period_MP, nsperiod, not nsperiod = pispipperiod ) 
                    {
                      partialoperationdemandqtyvar := program.PartialOperationDemandQtyVariables().Find( input, pispipperiod, nsperiod );
                      // If the variable does not exist, then this combination of operation and period is not considered by the optimizer
                      // Therefore, this term should then also not exist
                      if( not isnull( partialoperationdemandqtyvar ) )
                      {
                        // Term:    -cost      * PartialOperationDemandQty variable
                        // UoM:  [Monetary/PISP] *           [PISP]
                        icconst.NewTerm( -cost * scalefactor_partialoperationdemandqty_icconst, partialoperationdemandqtyvar );
                      }
                    }
                  }
                }
              }
            }
            else
            {
              traverse( pispip, 
                        ProductInStockingPoint_MP.OperationInputAvailableForOptimization, 
                        input,
                        guard( scope.Contains( input.Operation().OperationInOptimizerRun() ), false )
                        and input.HasRegularProductforOptimizer() or input.GetIsProductInOptimizerRun( ispostprocessing ) )
              {
                operation := input.Operation();
        
                if( operation.HasLeadTime() )
                {
                  cost := pispip.GetBaseWIPCostPerQuantityPerDay( aa )
                          * operation.LeadTime().DaysAsReal();
                          
                  ptperiods := CapacityPlanningSuboptimizer::GetOperationPeriodTaskPeriodsForPreprocessing( pispipperiod, operation);
                              
                  traverse( ptperiods, Elements, ptperiod,
                            ptperiod <> pispipperiod )              // WIP cost incurred for dependent demand that happens earlier than the period task.
                  {
                    partialoperationdemandqtyvar := program.PartialOperationDemandQtyVariables().Find( input, pispipperiod, ptperiod );
                    // If the variable does not exist, then this combination of operation and period is not considered by the optimizer
                    // Therefore, this term should then also not exist
                    if( not isnull( partialoperationdemandqtyvar ) )
                    {
                      // Term:    -cost      * PartialOperationDemandQty variable
                      // UoM:  [Monetary/PISP] *           [PISP]
                      icconst.NewTerm( -cost * scalefactor_partialoperationdemandqty_icconst, partialoperationdemandqtyvar );
                    }
                  }
                }
              }
            }
      
            // Inventory cost for trips that are part of this optimizer run
            pits := selectset(  pispip, DependentDemandTrip.ProductInTrip, productintrip, 
                      scope.Contains( productintrip.ProductInTripInOptimizerRun() ) ) ; 
            basecostperquantityperday := ifexpr(  pits.Size() > 0, pispip.GetBaseWIPCostPerQuantityPerDay( aa ),  0.0 ); 
                                   
            traverse( pits, Elements, productintrip )
            {
              cost := basecostperquantityperday * productintrip.Trip().LeadTime().DaysAsReal();
              // Term:   -cost      * TripDemandQty variable
              // UoM: [Monetary/PISP] *         [PISP]
              icconst.NewTerm( -cost * scalefactor_tripdemandqty_icconst, program.TripDemandQtyVariables().Get( productintrip ) );
            }
          } // end traverse leaf pispips
        } // end traverse account assignment (PISPAccount)
      } // end traverse pisp in scope
    } // end traverse accounts
  *]
  InterfaceProperties { Accessibility: 'Module' }
}