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
Quintiq file version 2.0
#parent: #root
Method GetInventoryRHSForBalanceConstraint (
  const ProductInStockingPointInPeriodPlanningLeaf pispip,
  const RunContextForCapacityPlanning runcontext,
  const LibOpt_Scope scope
) const as Real
{
  Description: 'Calculate and returns the rhs of the balance constraints (=starting inventory of a pispip considering all decisions outside the optimizer horizon)'
  TextBody:
  [*
    // The RHS of this constraint is the initial inventory based on all decisions outside the scope of the optimizer, it includes:
    // Inventory supply that is greater than 0. If the supply is less than 0, it will be treated as a "must be fulfilled" demands
    supplyquantity := ifexpr( pispip.InventorySupplyQuantity() > 0, pispip.InventorySupplyQuantity(), 0 );
    
    // Inventory of the previous period if that period exists and is not part of the optimizer horizon
    invstart := 0.0;
    if( pispip.ProductInStockingPoint_MP().EarliestPISPIPInScope() = pispip
        and not isnull( pispip.PreviousPlanningPISPIP() ) ) 
    {
      invstart := pispip.InventoryLevelStart(); // this ensures expired quantity is also taken out of the balance 
    }
    
    
    // The sum of the new supply that starts outside the optimizer scope
    sumnewsupply := 0.0; 
    // Add the new supply quantity if it has a dd on a pispip outside the optimizer scope
    traverse( pispip, NewSupplyOperation, newsupply, not newsupply.Quantity() = 0.0 ) 
               
    {
      if ( ( isnull(  newsupply.PeriodTaskOperation().UnitPeriodWhenInScope() )                               // either the period task is not in scope
                or not newsupply.GetHasAllDDInOptimizerScope( runcontext, scope) ) )                          // or if it is check whether some dependent demand is taking from a pispip not in scope
      {
        sumnewsupply := sumnewsupply + newsupply.Quantity();
      }
    }
    
    traverse( pispip, NewSupplyTrip.ProductInTrip, pit, not pit.Quantity() = 0.0 ) 
    {
      if ( not scope.Contains( pit.ProductInTripInOptimizerRun() ) )
      {
        sumnewsupply := sumnewsupply + pit.Quantity();
      }
    }
    
    // Subtract any dependent demands that have a new supply outside the optimizer scope
    sumdd := 0.0;
    if ( this.GetPeriodsFromPeriodTaskOperation() ) 
    {
      traverse( pispip, DependentDemandOperation, dd, not dd.Quantity() = 0.0 ) 
      {
        if( ( isnull( dd.PeriodTaskOperation().UnitPeriodWhenInScope() ) ) )  // check if period task is not in scope,  
        {
          sumdd := sumdd + dd.Quantity();
        }
      }
    }
    else
    {
      traverse( pispip, DependentDemandOperation, dd, not dd.Quantity() = 0.0 )
      {
        if( ( isnull( dd.PeriodTaskOperation().UnitPeriodWhenInScope() )  // either the period task is not in scope,  
                  or not dd.GetHasAllNewSupplyInOptimizerScope( runcontext, scope ) ) )                          // or if it is then check whether some new supply is delivering to a pispip not in scope
        {
          sumdd := sumdd + dd.Quantity();
        }
      }
    }
    
    traverse( pispip, DependentDemandTrip.ProductInTrip, pit, not pit.Quantity() = 0.0 ) 
    {
      if ( not scope.Contains( pit.ProductInTripInOptimizerRun() ) )
      {
        sumdd := sumdd + pit.Quantity();
      }  
    }
    
    // subtract any postponed sales demand with original demand not in scope. Note we both handle LeafSalesDemandInPeriod and AggregatedSalesDemandInPeriod here. 
    // if the original is not in scope the postponed sd will not be allowed to be replanned
    postponedsd_outscope := 0.0; 
    traverse( pispip, PlanningBaseSalesDemandInPeriodIsPostponed.astype( SalesDemandInPeriod ),
              sdip,
              not sdip.GetInScope( scope )
              and guard( not sdip.OriginalSalesDemand().astype( SalesDemandInPeriod ).GetInScope( scope ), false )
              and not scope.Contains( sdip.OriginalSalesDemand().astype( SalesDemandInPeriod ).SDIPBeforeScopeInRun() ) )
    
    {
      assert(  sdip.IsPostponed() and not sdip.IsManuallyPostponed(), 'sales demand taken out of balance must be postponed object and not mamually postponed' );
      postponedsd_outscope := postponedsd_outscope + sdip.FulfilledQuantity();   
    }
    
    // If the user runs a downstream smart plan, then the user may specify a total available supply
    // This total available supply will overrule the real total supply of the pispip
    // Therefore, we will have to update the RHS of the balance constraint to account for the difference
    magicalusersupply := 0.0;
    if( pispip.GetHasMisMatchAvailableUserSupply() )
    {
      magicalusersupply := pispip.TotalAvailableSupplyUser() - pispip.SupplyQuantity();
    }
    
    // Calculate the total RHS
    rhs := -( supplyquantity + invstart + sumnewsupply - ( sumdd + postponedsd_outscope ) + magicalusersupply );
    return rhs
  *]
  InterfaceProperties { Accessibility: 'Module' }
}