| Quintiq file version 2.0 | 
| #parent: #root | 
| Method HasConvergencedForFocusLevel ( | 
|   LibOpt_Task task | 
| ) as Boolean | 
| { | 
|   TextBody: | 
|   [* | 
|     value := false;  | 
|     window := this.ConvergenceWindowSize();  | 
|     snapshotserror := selectset( task,  | 
|                                 Run.Snapshot.astype( LibOpt_SnapshotError ),  | 
|                                 s, | 
|                                 true,  | 
|                                 true );  | 
|      | 
|     value := snapshotserror.Size() > this.MaxError();  | 
|      | 
|     if ( not value and not task.Scope().IsGeneratedByPreProductionSelector() )  | 
|     { | 
|       snapshots := selectsortedset( task, Run.SnapshotMacroPlannerOptimizerAccepted, s, s.FocusLevel() = this.FocusLevel(), -s.SequenceNr() ).SelectFirst( window ); | 
|          | 
|       if ( snapshots.Size() > 0 )  | 
|       { | 
|         runcontext := RunContextForCapacityPlanning::GetRunContextCapacityPlanning( task.Run() ); | 
|      | 
|         islimitingforshiftoptimization := false;  | 
|         if ( runcontext.UseShiftOptimization() )  | 
|         { | 
|           traverse( task.Run().Optimization().astype( Optimization ).MacroPlan(),  | 
|                     Unit,  | 
|                     unit,  | 
|                     unit.IsInOptimizerPuzzle() )  | 
|           { | 
|             islimitingforshiftoptimization := islimitingforshiftoptimization or exists( unit, UnitShiftPatternAllowed, usa, true, not usa.IsActive() );  | 
|             if ( not islimitingforshiftoptimization )  | 
|             { | 
|               traverse( unit.GetAllChildren(), Elements, child )  | 
|               { | 
|                 islimitingforshiftoptimization := islimitingforshiftoptimization or exists( child, UnitShiftPatternAllowed, usa, true, not usa.IsActive() );  | 
|               } | 
|             } | 
|           } | 
|         }     | 
|          | 
|         iscampaignsequencing_or_shiftopt := runcontext.UseCampaignSequenceOptimizer() or islimitingforshiftoptimization; // don't take shortcuts because we limit binaries  | 
|          | 
|         lastsnapshots := snapshots.SelectFirst( minvalue(  snapshots.Size(), window ) );  | 
|         lastsnapshot := snapshots.Element( 0 );    | 
|          | 
|         maxbestscorelevel := max( lastsnapshots, Elements, s, true, RealVector::Construct( s.BestFoundScoresForLevels() ).Get( this.FocusLevel() ) );  | 
|         minbestscorelevel := min( lastsnapshots, Elements, s, true, RealVector::Construct( s.BestFoundScoresForLevels() ).Get( this.FocusLevel() ) );  | 
|         movement_relative := guard( ( maxbestscorelevel - minbestscorelevel ) / ( 0.5 * abs( maxbestscorelevel) + 0.5* abs( minbestscorelevel )), 0.0 );   | 
|         absolute_movement := abs( maxbestscorelevel - minbestscorelevel ); // around 0 relative movement becomes noisy | 
|         movement := minvalue(  movement_relative, absolute_movement );  | 
|         lastsnapshot.MaxScoreConvergenceWindow( maxbestscorelevel );  | 
|         lastsnapshot.MinScoreConvergenceWindow( minbestscorelevel );  | 
|         lastsnapshot.MovementScoreConvergenceWindow( movement );  | 
|         debuginfo( 'maxscore=', maxbestscorelevel, 'minscore = ', minbestscorelevel, 'movement=', movement, '( stop when < ', this.ConvergenceThreshold(), ')' );  | 
|         avgsubopttime := average( lastsnapshots, Elements, s, true, s.DurationSecondsSubOptimizer() );  | 
|                                 | 
|         if ( movement < this.ConvergenceThreshold() and snapshots.Size() >= window )   | 
|         { | 
|           value := true;  | 
|           lastsnapshot.StopCriteriumInfo( lastsnapshot.StopCriteriumInfo() + 'Converged. Avg time=' + [String] avgsubopttime );   | 
|         } | 
|          | 
|         if ( not value and this.HasTimedOutLocalDuration( task ) )  | 
|         { | 
|           lastsnapshot.StopCriteriumInfo( lastsnapshot.StopCriteriumInfo() + 'iteration time out' );   | 
|           value := true;  | 
|         } | 
|          | 
|         if ( not value and this.HasTimedOutGlobalDuration( task ) )  | 
|         { | 
|           lastsnapshot.StopCriteriumInfo( lastsnapshot.StopCriteriumInfo() + 'global time out' );   | 
|           value := true;  | 
|         } | 
|          | 
|         // check for some shortcuts to exit the level | 
|         if ( not value )  | 
|         { | 
|           mp := task.Run().Optimization().astype( Optimization ).MacroPlan();  | 
|           enoughscalinginfocollected := not this.IsEnabledAutoScaling()  | 
|                                         or counter( mp, Optimization.AlgorithmScaling.ScaleFactorsSuggestedForLevel, s, true, s.FocusLevel() = this.FocusLevel() ) > 0;  | 
|      | 
|           // check if at best possible KPI (if it is known) | 
|           level := select(  mp, StrategyMacroPlan.StrategyLevelMacroPlan, slm, true, slm.Level() = this.FocusLevel() );  | 
|           lastsnapshot := maxselect(  task, Run.Snapshot.astype( SnapshotMacroPlannerOptimizer ), s, s.IsAccepted() and s.FocusLevel() = this.FocusLevel(), s.TimeStamp() ); | 
|           if ( not isnull( lastsnapshot ) )  | 
|           {  | 
|             rv := RealVector::Construct( lastsnapshot.RollbackKPI() );  | 
|             lastscore := rv.Get( this.FocusLevel() );   | 
|             value := guard( abs( level.KPIUpperBound() - lastscore) < this.CutOffForOptimal(), false ) and enoughscalinginfocollected;  | 
|             if ( value )  | 
|             { | 
|                 lastsnapshot.StopCriteriumInfo( lastsnapshot.StopCriteriumInfo() + 'level kpi (' + [String] lastscore + ')within threhold (' + [String]this.CutOffForOptimal() + ') of best possible value ( ' + [String] level.KPIUpperBound() + ')' );   | 
|             } | 
|             else  | 
|             { | 
|                | 
|               noincrease := guard( snapshots.Element( 1 ).NrOperationPeriodTaskInOptimizerRun() = lastsnapshot.NrOperationPeriodTaskInOptimizerRun()  | 
|                                    and snapshots.Element( 1 ).NrProductInTripOptimizerRun() = lastsnapshot.NrProductInTripOptimizerRun(),  | 
|                                    false );  | 
|               // check if last was full puzzle size and optimal cplex | 
|               value := lastsnapshot.NrCampaignSequenceCombisTotal() = lastsnapshot.NrCampaignSequenceCombisActive() | 
|                        and lastsnapshot.NumberOfPISPIPPlanningGlobal() = lastsnapshot.NrPISPIPPlanningOptimizerRun() | 
|                        and lastsnapshot.NrOperationPeriodTaskInOptimizerRun() >= lastsnapshot.NrOperationPeriodTaskInGlobal()  | 
|                        and lastsnapshot.NrProductInTripOptimizerRun() >= lastsnapshot.NrProductInTripGlobal() | 
|                        and lastsnapshot.AllLevelsOptimal() | 
|                        and lastsnapshot.IsAccepted() | 
|                        and this.FocusLevel() = lastsnapshot.FocusLevel() | 
|                        and noincrease | 
|                        and enoughscalinginfocollected | 
|                        and not iscampaignsequencing_or_shiftopt | 
|                        and not lastsnapshot.Comment() ~ Translations::LibOpt_Rollback();  | 
|               if ( value )  | 
|               { | 
|                   lastsnapshot.StopCriteriumInfo( lastsnapshot.StopCriteriumInfo() + 'neighborhood at max size and cplex optimal' );   | 
|               } | 
|               else | 
|               { | 
|                 value := this.StopForBenchmarking( task, lastsnapshot, runcontext, lastscore ) and enoughscalinginfocollected;    | 
|                 if ( value )  | 
|                 { | 
|                   lastsnapshot.StopCriteriumInfo( lastsnapshot.StopCriteriumInfo() + 'stopping for benchmark purpose' );    | 
|                 } | 
|               } | 
|             } | 
|           } | 
|         } // end look for shortcuts  | 
|       }     | 
|     } | 
|      | 
|     return value; | 
|   *] | 
|   InterfaceProperties { Accessibility: 'Module' } | 
| } |