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