| 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' } | 
| } |