陈清红
2025-04-14 880f3c0257eeb8c37761d484258fdd102a369a19
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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
Quintiq file version 2.0
#parent: #root
Method CapacityPlanningAlgorithmHandleFeasibleCampaignSequencing (
  CapacityPlanningSuboptimizer_CapacityPlanningAlgorithm program,
  const RunContextForCapacityPlanning runcontext,
  LibOpt_Task task
)
{
  Description: 'The method called in "Handle feasible" tab to handle the campaign sequencing part of the optimizer'
  TextBody:
  [*
    //Only call campaign sequencing code if the campaign sequencing optimizer is enabled and we have campaigns in the strategy
    if( runcontext.UseCampaignSequenceOptimizer() and runcontext.UseCampaign() )
    {
      campaignsforwritetransitions := construct( Campaign_MPs ); 
      this.LoggedTotalCampaignUnitPeriodUtilizationSlackVar( this.GetOptimalValue( program.TotalUnitPeriodCampaignUtilizationSlackVariables().Get() ) ); 
      scope := task.Scope(); 
      durZero := Duration::Zero();
      
      //Cleanup all the campaigns we have so far
      traverse( scope.GetUnitInOptimizerRun(), Elements, unit, unit.HasCampaignType() )
      {
        periodsinscope := selectsortedset( scope.GetPeriodInOptimizerRun(), 
                                           Elements, 
                                           p, 
                                           p.GetIsInCampaignTypeHorizonUnit( unit ) 
                                           and not Unit::GetIsPeriodFrozen( unit, p ), 
                                           p.Start() );
        if( periodsinscope.Size() > 0 )
        {
          campaigns := selectset( unit, Campaign_MP, c,c.Start() >= periodsinscope.Element( 0 ).Start() );
          Campaign_MP::Delete( campaigns );
          traverse( unit, OptCampaignUnit.LastCampaignBeforeOptHorizon, last )  // we cannot delete the last campaign before opt horizon, but we should reset to the preprocess duration (for meta iterations)
          {
            last.Duration( last.CampaignOptimizerPreProcessDuration() ); 
          }
        }
      }
      
      currentcampaignfordurationsetting := null( Campaign_MP );
      //Get all the relevant OptCampaignUnitSubPeriods
      ocusps := selectsortedset( scope.GetUnitPeriodInOptimizerRun(), 
                                 Elements.OptCampaignUnitSubPeriod, 
                                 ocusp, 
                                 true, 
                                 ocusp.UnitPeriod().UnitID(), 
                                 ocusp.StartTime(), 
                                 ocusp.EndTime() ) ;
      currentoptunit := null( OptCampaignUnit );
      
      traverse( ocusps, Elements, ocusp, not ocusp.IsFixed() ) 
      {  
        if( not currentoptunit = ocusp.OptCampaignUnit() )
        {
          currentoptunit := ocusp.OptCampaignUnit();
          currentcampaignfordurationsetting := currentoptunit.LastCampaignBeforeOptHorizon();
          initialdurationfornexttransition := Duration::Zero(); 
          if ( not isnull( currentoptunit.LastTransitionBeforeOptHorizon() ) ) 
          {
            assert( isnull( currentcampaignfordurationsetting ), 'not expecting last campaign to be set in case transition was set' ); 
            currentcampaignfordurationsetting := currentoptunit.LastTransitionBeforeOptHorizon().FromCampaign(); 
            initialdurationfornexttransition := ocusp.StartTime() - currentoptunit.LastTransitionBeforeOptHorizon().Start(); 
            
            // init quantities on campaign period task in scope to 0. Add to this below as we go along growing campaigns. 
            traverse( currentcampaignfordurationsetting, CampaignPeriod_MP, cp, cp.Start() >= ocusp.StartTime() ) 
            {
              traverse( cp, PeriodTaskInCampaign, ptc ) 
              {
                ptc.Quantity( 0.0 ); 
              }
            }
          } 
          
          if ( not isnull( currentcampaignfordurationsetting ) ) 
          {
            currentcampaignfordurationsetting.HasNextTransitionDurationByOpt( false ); 
            currentcampaignfordurationsetting.NextTransitionDurationByOpt( initialdurationfornexttransition ); 
          }
        }
        //Go over all the selected combis
        
        selectedcombiset := construct( OptCampaignCombis ); 
        traverse( ocusp, OptCampaignCombiActive, combi ) 
        {
          combi.IsSelectedByOptimizer( program.IsCampaignCombiSelectedVariables().Get( combi ).OptimalValue() > 0.5 ); 
          if ( combi.IsSelectedByOptimizer() ) 
          {
            selectedcombiset.Add( combi ); 
          }
        }
        assert(  selectedcombiset.Size() <= 1, 'Error: expecting at most 1 combi selected per campaign unit sub period' ); 
        
        traverse( selectedcombiset, Elements, combi ) 
        { 
          // example for multi combi: 
          //               subperiodseqnr     name                    obtain duration B as       
          //
          //               0                  B                       start+end                          
          //               1                  B                       start+end                  
          //               2                  B                       start+end                  
          //               3                  B                       start                      
          //               3                  B->C                    end
          //
          // combi: 
          //               0                  B                       start+end                                                  
          //               1                  B                       start                      
          //               1                  B->C                    mid                        
          //               1                  C                       mid                        
          //               1                  C->B                    mid                        
          //               1                  B                       mid                        
          //               1                  B->D                    end 
          //
          // rule: check within subperiod voor 
          // if first within sub period -> start
          // if last within sub period -> end           (special case: both first and last, then start+end) 
          // else not first and not last -> mid 
          // 
          combi.CreatePeriodTaskInCombiElements( program, this );
          starttime := ocusp.StartTime();
          element := combi.FirstCombiElement();
          elspassed := 0;
          stopfordurationzerocampaignlastperiod := false; 
          while( elspassed < combi.NumberOfElements() and not stopfordurationzerocampaignlastperiod )
          {
            elduration := element.GetDurationInCampaignUnitSubPeriod( program, this ); 
            
            if ( element.istype( OptCampaignCombiElementTransition ) ) 
            {
              if ( not isnull( currentcampaignfordurationsetting ) ) 
              {
                // we store the decided duration on the last campaign, because transitions are declarative in the model
                currentcampaignfordurationsetting.HasNextTransitionDurationByOpt( true ); 
                durationsofar := currentcampaignfordurationsetting.NextTransitionDurationByOpt(); 
                currentcampaignfordurationsetting.NextTransitionDurationByOpt( durationsofar + elduration );
                currentcampaignfordurationsetting.OptNextCampaignTypeName( element.astype( OptCampaignCombiElementTransition ).TransitionType_MP().ToCampaignTypeName() ); 
                currentcampaignfordurationsetting.WriteOptCampaignPeriodTaskQuantities( task, element.astype( OptCampaignCombiElementTransition ) ); // store on campaign so we can write below after propagation
                campaignsforwritetransitions.Add( currentcampaignfordurationsetting ); 
              }
              else
              {
                task.Log( 'optimizer decides transition but last campaign is null, unit id=' 
                          + element.OptCampaignCombi().OptCampaignUnitSubPeriod().UnitPeriod().UnitID()
                          + 'elt = ' + element.Name() 
                          + 'combi = ' + combi.Name() ); 
              }
              if ( element.NextIsCampaign() )
              {
                currentcampaignfordurationsetting := null( Campaign_MP ); // finished with transition, ensure for next campaign we will store durations in the freshly created campaign
              } 
            }
            else
            {
              // So it is a campaign
              // only need to plan campaigns
              if( element.IsFirstElementInUnitSubPeriod() 
                  and not isnull( currentcampaignfordurationsetting )
                  and element.OptCampaignElementType().astype( OptCampaignCampaignType ).CampaignType_MP() = currentcampaignfordurationsetting.CampaignType_MP() )
              {
                currentcampaignfordurationsetting.Duration( currentcampaignfordurationsetting.Duration() + elduration );
                currentcampaignfordurationsetting.WriteOptCampaignPeriodTaskQuantities( task, element.astype( OptCampaignCombiElementCampaign ) ); 
              }
              //we don't want to plan in case of 0 duration at the end of the horizon
              else if( not isnull( element.OptCampaignUnitSubPeriod().NextSubPeriod() ) or elduration > durZero )
              {
                campaigntype := element.astype( OptCampaignCombiElementCampaign ).CampaignType_MP();
                unit := ocusp.UnitPeriod().Unit();
                campaign := Campaign_MP::Create(  unit,
                                                  starttime,
                                                  elduration,
                                                  -1,
                                                  campaigntype.DefaultMinQuantity(),
                                                  campaigntype.DefaultMaxQuantity(),
                                                  campaigntype.DefaultMinDuration(),
                                                  campaigntype.DefaultMaxDuration(),
                                                  campaigntype.Name(),
                                                  OS::GenerateGUIDAsString(),
                                                  'Created by optimizer', //JPS1 NEEDS TO CHANGE
                                                  campaigntype.HasInputMaxQuantity(),
                                                  campaigntype.HasInputMaxDuration(),
                                                  false ); 
            
                campaign.SynchronizePeriodTaskInCampaigns();
                currentcampaignfordurationsetting := campaign
                Transaction::Transaction().Propagate( relation(  Campaign_MP, CampaignPeriod_MP) );
                Transaction::Transaction().Propagate( relation(  CampaignPeriod_MP, PeriodTaskInCampaign) );
                
                currentcampaignfordurationsetting.WriteOptCampaignPeriodTaskQuantities( task, element.astype( OptCampaignCombiElementCampaign ) ); 
              }
              else
              {
                // we did not create a new campaign, or add duration to an existing one. 
                // we know: elduration 0 and nextsubperiod = null. Stop processing combi by setting flag to exit loop
                stopfordurationzerocampaignlastperiod := true; 
              }
            }
            
            //storing the value for debugging
            element.OptimizerDuration( elduration )
            //While loop bookkeeping
            starttime := starttime + elduration;
            
            element := element.NextCombiElement();
            elspassed := elspassed + 1;        
          } // end while combi elements
        } //end traverse combiset
      } // end traverse opt campaign unit sub period 
      
      // debug writing attributes 
      if ( this.Optimization().astype( Optimization ).DebugMode() ) 
      {
        traverse( scope.GetUnitPeriodInOptimizerRun(), Elements.OptCampaignUnitSubPeriod, ocusp ) 
        { 
          traverse( ocusp, OptCampaignCombiElementActive, ocelt, ocelt.OptCampaignCombi().DebugIsFixedPlanned() ) 
          {
            ocet := ocelt.OptCampaignElementType(); 
            subperiod := ocelt.OptCampaignUnitSubPeriod(); 
            midvar := program.DurationOfCampaignElementTypeMidVariables().Get( ocet, subperiod );
            endvar := program.DurationOfCampaignElementTypeEndVariables().Get( ocet, subperiod );
            startvar := program.DurationOfCampaignElementTypeStartVariables().Get( ocet, subperiod );
            ocelt.DebugDurationOfCampaignElementTypeEndVariables( endvar.OptimalValue() );          // note that these create duplicates among all combis
            ocelt.DebugDurationOfCampaignElementTypeMidVariables( midvar.OptimalValue() );          // that share the element type for the particular sub period
            ocelt.DebugDurationOfCampaignElementTypeStartVariables( startvar.OptimalValue() );      // if we would not limit to selected combis only
          }
          
          ocusp.DebugOverloadedEnd( 0.0 ); 
          ocusp.DebugOverloadedMid( 0.0 ); 
          ocusp.DebugOverloadedStart( 0.0 ); 
          ocusp.DebugUnderloadedEnd( 0.0 ); 
          ocusp.DebugUnderloadedMid( 0.0 ); 
          ocusp.DebugUnderloadedStart( 0.0 ); 
           
          traverse( ocusp, OptCampaignCombiElement.OptCampaignElementType, ocet )
          {
            varperiodoverloadstart := program.CampaignElementTypePeriodOverloadedStartVariables().Get( ocet, ocusp );
            varperiodoverloadmid := program.CampaignElementTypePeriodOverloadedMidVariables().Get( ocet, ocusp );
            varperiodoverloadend := program.CampaignElementTypePeriodOverloadedEndVariables().Get( ocet, ocusp );
            varperiodunderloadedstart := program.CampaignElementTypePeriodUnderloadedStartVariables().Get( ocet, ocusp );
            varperiodunderloadedmid := program.CampaignElementTypePeriodUnderloadedMidVariables().Get( ocet, ocusp );
            varperiodunderloadedend := program.CampaignElementTypePeriodUnderloadedEndVariables().Get( ocet, ocusp );
            
            ocusp.DebugOverloadedEnd( ocusp.DebugOverloadedEnd() + varperiodoverloadend.OptimalValue() ); 
            ocusp.DebugOverloadedMid( ocusp.DebugOverloadedMid( ) + varperiodoverloadmid.OptimalValue() ); 
            ocusp.DebugOverloadedStart( ocusp.DebugOverloadedStart() + varperiodoverloadstart.OptimalValue() ); 
            ocusp.DebugUnderloadedEnd( ocusp.DebugUnderloadedEnd() + varperiodunderloadedend.OptimalValue()  ); 
            ocusp.DebugUnderloadedMid( ocusp.DebugUnderloadedMid() + varperiodunderloadedmid.OptimalValue() ); 
            ocusp.DebugUnderloadedStart( ocusp.DebugUnderloadedStart() + varperiodunderloadedstart.OptimalValue() ); 
          }
          
          ocusp.CalcDebugUnderloadedTotal(); 
          ocusp.CalcDebugOverloadedTotal(); 
        }    
      }
    
      Transaction::Transaction().Propagate( relation( TransitionType_MP, Transition_MP ) ); 
      Transaction::Transaction().Propagate( relation( Campaign_MP, PlanningCampaignPeriod ) );
      Transaction::Transaction().Propagate( relation( Transition_MP, TransitionPeriod_MP ) );
      Transaction::Transaction().Propagate( relation( TransitionPeriod_MP, PeriodTaskInTransition  ) );
      
      // now the transition and transition period objects are there
      
      campaignsforwritetransitions := campaignsforwritetransitions.Unique(); 
      traverse( campaignsforwritetransitions, Elements, campaign ) 
      {
        transition := campaign.ToTransition(); 
        nonzeroqtyset := selectset( campaign, OptCampaignPeriodTaskInCombiElement, pt, true, pt.Quantity() > 0.0 ); 
        combielements := selectset(  nonzeroqtyset, Elements.OptCampaignCombiElement.astype( OptCampaignCombiElementTransition ), elt, true, true ); 
        combielements := combielements.Unique(); 
        if ( isnull( transition ) and combielements.Size() > 0 ) 
        {
          task.Log( 'CAMPAIGN no next transition to write nonzero qty to' ); 
        }   
        if ( not isnull( transition ) ) 
        {
          // reset all period task in transition object associated already to the transition. Other ones created on the fly below as we will need them
          traverse( transition, TransitionPeriod_MP.PeriodTaskInTransition, pt, not pt.TransitionPeriod_MP().UnitPeriod().IsPeriodFrozen() ) 
          {
            pt.Quantity( 0.0 ); 
          }
        
          traverse( combielements, Elements, elt ) 
          {
            transition.WriteOptCampaignPeriodTaskQuantities( task, elt ); 
          }
        }  
      }
    }
  *]
  InterfaceProperties { Accessibility: 'Module' }
}