Example #1
0
        public override TimeSpan Run()
        {
            if (this.ChangeToExchangeAfter < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(this.ChangeToExchangeAfter));
            }
            if (this.FinalMultiplier < this.IntitialMultiplier)
            {
                throw new ArgumentOutOfRangeException(nameof(this.FinalMultiplier));
            }
            if (this.Objective.HarvestPeriodSelection != HarvestPeriodSelection.NoneOrLast)
            {
                throw new NotSupportedException(nameof(this.Objective.HarvestPeriodSelection));
            }
            if (this.LowerWaterAfter < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(this.LowerWaterAfter));
            }
            if (this.StopAfter < 1)
            {
                throw new ArgumentOutOfRangeException(nameof(this.StopAfter));
            }
            int initialTreeRecordCount = this.GetInitialTreeRecordCount();

            if (initialTreeRecordCount < 2)
            {
                throw new NotSupportedException();
            }

            Stopwatch stopwatch = new Stopwatch();

            stopwatch.Start();

            this.EvaluateInitialSelection(this.Iterations);
            if (this.RainRate.HasValue == false)
            {
                this.RainRate = (this.FinalMultiplier - this.IntitialMultiplier) * this.BestObjectiveFunction / this.Iterations;
            }
            if ((this.RainRate.HasValue == false) || (this.RainRate.Value <= 0.0))
            {
                throw new ArgumentOutOfRangeException(nameof(this.RainRate));
            }

            float acceptedObjectiveFunction = this.BestObjectiveFunction;
            //float harvestPeriodScalingFactor = ((float)this.CurrentTrajectory.HarvestPeriods - Constant.RoundToZeroTolerance) / (float)byte.MaxValue;
            float treeIndexScalingFactor = ((float)initialTreeRecordCount - Constant.RoundTowardsZeroTolerance) / (float)UInt16.MaxValue;

            // initial selection is considered iteration 0, so loop starts with iteration 1
            OrganonStandTrajectory candidateTrajectory = new OrganonStandTrajectory(this.CurrentTrajectory);
            float hillClimbingThreshold = this.BestObjectiveFunction;
            int   iterationsSinceBestObjectiveImproved = 0;
            int   iterationsSinceObjectiveImprovedOrMoveTypeChanged   = 0;
            int   iterationsSinceObjectiveImprovedOrWaterLevelLowered = 0;
            float waterLevel = this.IntitialMultiplier * this.BestObjectiveFunction;

            for (int iteration = 1; iteration < this.Iterations; ++iteration, waterLevel += this.RainRate.Value)
            {
                int firstTreeIndex            = (int)(treeIndexScalingFactor * this.GetTwoPseudorandomBytesAsFloat());
                int firstCurrentHarvestPeriod = this.CurrentTrajectory.GetTreeSelection(firstTreeIndex);
                int firstCandidateHarvestPeriod;
                int secondTreeIndex = -1;
                switch (this.MoveType)
                {
                case MoveType.OneOpt:
                    firstCandidateHarvestPeriod = firstCurrentHarvestPeriod == 0 ? this.CurrentTrajectory.HarvestPeriods - 1 : 0;
                    candidateTrajectory.SetTreeSelection(firstTreeIndex, firstCandidateHarvestPeriod);
                    break;

                case MoveType.TwoOptExchange:
                    secondTreeIndex             = (int)(treeIndexScalingFactor * this.GetTwoPseudorandomBytesAsFloat());
                    firstCandidateHarvestPeriod = this.CurrentTrajectory.GetTreeSelection(secondTreeIndex);
                    while (firstCandidateHarvestPeriod == firstCurrentHarvestPeriod)
                    {
                        // retry until a modifying exchange is found
                        // This also excludes the case where a tree is exchanged with itself.
                        // BUGBUG: infinite loop if all trees have the same selection
                        secondTreeIndex             = (int)(treeIndexScalingFactor * this.GetTwoPseudorandomBytesAsFloat());
                        firstCandidateHarvestPeriod = this.CurrentTrajectory.GetTreeSelection(secondTreeIndex);
                    }
                    candidateTrajectory.SetTreeSelection(firstTreeIndex, firstCandidateHarvestPeriod);
                    candidateTrajectory.SetTreeSelection(secondTreeIndex, firstCurrentHarvestPeriod);
                    break;

                default:
                    throw new NotSupportedException();
                }
                //int candidateHarvestPeriod = (int)(harvestPeriodScalingFactor * this.GetPseudorandomByteAsFloat());
                //while (candidateHarvestPeriod == currentHarvestPeriod)
                //{
                //    candidateHarvestPeriod = (int)(harvestPeriodScalingFactor * this.GetPseudorandomByteAsFloat());
                //}
                Debug.Assert(firstCandidateHarvestPeriod >= 0);

                candidateTrajectory.SetTreeSelection(firstTreeIndex, firstCandidateHarvestPeriod);
                candidateTrajectory.Simulate();
                ++iterationsSinceBestObjectiveImproved;
                ++iterationsSinceObjectiveImprovedOrMoveTypeChanged;
                ++iterationsSinceObjectiveImprovedOrWaterLevelLowered;

                float candidateObjectiveFunction = this.GetObjectiveFunction(candidateTrajectory);
                if ((candidateObjectiveFunction > waterLevel) || (candidateObjectiveFunction > hillClimbingThreshold))
                {
                    // accept move
                    acceptedObjectiveFunction = candidateObjectiveFunction;
                    this.CurrentTrajectory.CopyFrom(candidateTrajectory);
                    hillClimbingThreshold = candidateObjectiveFunction;
                    iterationsSinceObjectiveImprovedOrMoveTypeChanged   = 0;
                    iterationsSinceObjectiveImprovedOrWaterLevelLowered = 0;

                    if (candidateObjectiveFunction > this.BestObjectiveFunction)
                    {
                        this.BestObjectiveFunction = candidateObjectiveFunction;
                        this.BestTrajectory.CopyFrom(this.CurrentTrajectory);

                        iterationsSinceBestObjectiveImproved = 0;
                    }
                }
                else
                {
                    // undo move
                    switch (this.MoveType)
                    {
                    case MoveType.OneOpt:
                        candidateTrajectory.SetTreeSelection(firstTreeIndex, firstCurrentHarvestPeriod);
                        break;

                    case MoveType.TwoOptExchange:
                        candidateTrajectory.SetTreeSelection(firstTreeIndex, firstCurrentHarvestPeriod);
                        candidateTrajectory.SetTreeSelection(secondTreeIndex, firstCandidateHarvestPeriod);
                        break;

                    default:
                        throw new NotSupportedException();
                    }
                }

                this.AcceptedObjectiveFunctionByMove.Add(acceptedObjectiveFunction);
                this.CandidateObjectiveFunctionByMove.Add(candidateObjectiveFunction);
                this.MoveLog.TreeIDByMove.Add(firstTreeIndex);

                if (iterationsSinceBestObjectiveImproved > this.StopAfter)
                {
                    break;
                }
                else if (iterationsSinceObjectiveImprovedOrMoveTypeChanged > this.ChangeToExchangeAfter)
                {
                    // will fire repeatedly but no importa since this is idempotent
                    this.MoveType = MoveType.TwoOptExchange;
                    iterationsSinceObjectiveImprovedOrMoveTypeChanged = 0;
                }
                else if (iterationsSinceObjectiveImprovedOrWaterLevelLowered > this.LowerWaterAfter)
                {
                    // could also adjust rain rate but there but does not seem to be a clear need to do so
                    waterLevel            = (1.0F - this.LowerWaterBy) * this.BestObjectiveFunction;
                    hillClimbingThreshold = waterLevel;
                    iterationsSinceObjectiveImprovedOrWaterLevelLowered = 0;
                }
            }

            stopwatch.Stop();
            return(stopwatch.Elapsed);
        }
Example #2
0
        public override TimeSpan Run()
        {
            if ((this.Alpha <= 0.0) || (this.Alpha >= 1.0))
            {
                throw new ArgumentOutOfRangeException(nameof(this.Alpha));
            }
            if (this.ChangeToExchangeAfter < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(this.ChangeToExchangeAfter));
            }
            if (this.FinalProbability < 0.0)
            {
                throw new ArgumentOutOfRangeException(nameof(this.FinalProbability));
            }
            if (this.InitialProbability < this.FinalProbability)
            {
                throw new ArgumentOutOfRangeException(nameof(this.InitialProbability));
            }
            if (this.Iterations < 1)
            {
                throw new ArgumentOutOfRangeException(nameof(this.Iterations));
            }
            if (this.IterationsPerTemperature < 1)
            {
                throw new ArgumentOutOfRangeException(nameof(this.IterationsPerTemperature));
            }
            if (this.Objective.HarvestPeriodSelection != HarvestPeriodSelection.NoneOrLast)
            {
                throw new NotSupportedException(nameof(this.Objective.HarvestPeriodSelection));
            }
            if (this.ProbabilityWindowLength < 1)
            {
                throw new ArgumentOutOfRangeException(nameof(this.ProbabilityWindowLength));
            }
            if (this.ReheatAfter < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(this.ReheatAfter));
            }
            if (this.ReheatBy < 0.0F)
            {
                throw new ArgumentOutOfRangeException(nameof(this.ReheatBy));
            }

            Stopwatch stopwatch = new Stopwatch();

            stopwatch.Start();

            this.EvaluateInitialSelection(this.Iterations);

            float acceptedObjectiveFunction = this.BestObjectiveFunction;
            //float harvestPeriodScalingFactor = ((float)this.CurrentTrajectory.HarvestPeriods - Constant.RoundToZeroTolerance) / (float)byte.MaxValue;
            int   iterationsSinceMoveTypeOrObjectiveChange     = 0;
            int   iterationsSinceReheatOrBestObjectiveImproved = 0;
            float meanAcceptanceProbability      = this.InitialProbability;
            float movingAverageOfObjectiveChange = -1.0F;
            float movingAverageMemory            = 1.0F - 1.0F / this.ProbabilityWindowLength;
            float treeIndexScalingFactor         = ((float)this.GetInitialTreeRecordCount() - Constant.RoundTowardsZeroTolerance) / (float)UInt16.MaxValue;

            OrganonStandTrajectory candidateTrajectory = new OrganonStandTrajectory(this.CurrentTrajectory);

            for (int iteration = 1; (iteration < this.Iterations) && (meanAcceptanceProbability >= this.FinalProbability); meanAcceptanceProbability *= this.Alpha)
            {
                float logMeanAcceptanceProbability = Single.NegativeInfinity;
                if (meanAcceptanceProbability > 0.0F)
                {
                    logMeanAcceptanceProbability = MathV.Ln(meanAcceptanceProbability);
                }

                for (int iterationAtTemperature = 0; iterationAtTemperature < this.IterationsPerTemperature; ++iteration, ++iterationAtTemperature)
                {
                    int firstTreeIndex            = (int)(treeIndexScalingFactor * this.GetTwoPseudorandomBytesAsFloat());
                    int firstCurrentHarvestPeriod = this.CurrentTrajectory.GetTreeSelection(firstTreeIndex);
                    int firstCandidateHarvestPeriod;
                    int secondTreeIndex = -1;
                    switch (this.MoveType)
                    {
                    case MoveType.OneOpt:
                        firstCandidateHarvestPeriod = firstCurrentHarvestPeriod == 0 ? this.CurrentTrajectory.HarvestPeriods - 1 : 0;
                        candidateTrajectory.SetTreeSelection(firstTreeIndex, firstCandidateHarvestPeriod);
                        break;

                    case MoveType.TwoOptExchange:
                        secondTreeIndex             = (int)(treeIndexScalingFactor * this.GetTwoPseudorandomBytesAsFloat());
                        firstCandidateHarvestPeriod = this.CurrentTrajectory.GetTreeSelection(secondTreeIndex);
                        while (firstCandidateHarvestPeriod == firstCurrentHarvestPeriod)
                        {
                            // retry until a modifying exchange is found
                            // This also excludes the case where a tree is exchanged with itself.
                            // BUGBUG: infinite loop if all trees have the same selection
                            secondTreeIndex             = (int)(treeIndexScalingFactor * this.GetTwoPseudorandomBytesAsFloat());
                            firstCandidateHarvestPeriod = this.CurrentTrajectory.GetTreeSelection(secondTreeIndex);
                        }
                        candidateTrajectory.SetTreeSelection(firstTreeIndex, firstCandidateHarvestPeriod);
                        candidateTrajectory.SetTreeSelection(secondTreeIndex, firstCurrentHarvestPeriod);
                        break;

                    default:
                        throw new NotSupportedException();
                    }
                    //int candidateHarvestPeriod = (int)(harvestPeriodScalingFactor * this.GetPseudorandomByteAsFloat());
                    //while (candidateHarvestPeriod == currentHarvestPeriod)
                    //{
                    //    candidateHarvestPeriod = (int)(harvestPeriodScalingFactor * this.GetPseudorandomByteAsFloat());
                    //}
                    Debug.Assert(firstCandidateHarvestPeriod >= 0);

                    candidateTrajectory.Simulate();
                    ++iterationsSinceMoveTypeOrObjectiveChange;
                    ++iterationsSinceReheatOrBestObjectiveImproved;

                    float candidateObjectiveFunction = this.GetObjectiveFunction(candidateTrajectory);

                    bool acceptMove = candidateObjectiveFunction > acceptedObjectiveFunction;
                    // require at least one improving move be accepted to set moving average before accepting disimproving moves
                    if ((acceptMove == false) && (logMeanAcceptanceProbability > Single.NegativeInfinity) && (movingAverageOfObjectiveChange > 0.0F))
                    {
                        // objective function increase is negative and log of acceptance probability is negative or zero, so exponent is positive or zero
                        float objectiveFunctionIncrease = candidateObjectiveFunction - acceptedObjectiveFunction;
                        float exponent = logMeanAcceptanceProbability * objectiveFunctionIncrease / movingAverageOfObjectiveChange;
                        Debug.Assert(exponent >= 0.0F);
                        if (exponent < 10.0F)
                        {
                            // exponent is small enough not to round acceptance probabilities down to zero
                            // 1/e^10 accepts 1 in 22,026 moves.
                            float acceptanceProbability = 1.0F / MathV.Exp(exponent);
                            float moveProbability       = this.GetPseudorandomByteAsProbability();
                            if (moveProbability < acceptanceProbability)
                            {
                                acceptMove = true;
                            }
                        }
                    }

                    if (acceptMove)
                    {
                        float objectiveFunctionChange = MathF.Abs(acceptedObjectiveFunction - candidateObjectiveFunction);
                        if (movingAverageOfObjectiveChange < 0.0F)
                        {
                            // acceptance of first move
                            movingAverageOfObjectiveChange = objectiveFunctionChange;
                        }
                        else
                        {
                            // all subsequent acceptances
                            movingAverageOfObjectiveChange = movingAverageMemory * movingAverageOfObjectiveChange + (1.0F - movingAverageMemory) * objectiveFunctionChange;
                        }

                        acceptedObjectiveFunction = candidateObjectiveFunction;
                        this.CurrentTrajectory.CopyFrom(candidateTrajectory);
                        if (acceptedObjectiveFunction > this.BestObjectiveFunction)
                        {
                            this.BestObjectiveFunction = acceptedObjectiveFunction;
                            this.BestTrajectory.CopyFrom(this.CurrentTrajectory);
                            iterationsSinceReheatOrBestObjectiveImproved = 0;
                        }

                        iterationsSinceMoveTypeOrObjectiveChange = 0;
                        Debug.Assert(movingAverageOfObjectiveChange > 0.0F);
                    }
                    else
                    {
                        // undo move
                        switch (this.MoveType)
                        {
                        case MoveType.OneOpt:
                            candidateTrajectory.SetTreeSelection(firstTreeIndex, firstCurrentHarvestPeriod);
                            break;

                        case MoveType.TwoOptExchange:
                            candidateTrajectory.SetTreeSelection(firstTreeIndex, firstCurrentHarvestPeriod);
                            candidateTrajectory.SetTreeSelection(secondTreeIndex, firstCandidateHarvestPeriod);
                            break;

                        default:
                            throw new NotSupportedException();
                        }
                    }

                    this.AcceptedObjectiveFunctionByMove.Add(acceptedObjectiveFunction);
                    this.CandidateObjectiveFunctionByMove.Add(candidateObjectiveFunction);
                    this.MoveLog.TreeIDByMove.Add(firstTreeIndex);

                    if (iterationsSinceMoveTypeOrObjectiveChange > this.ChangeToExchangeAfter)
                    {
                        this.MoveType = MoveType.TwoOptExchange;
                        iterationsSinceMoveTypeOrObjectiveChange = 0;
                    }
                    if (iterationsSinceReheatOrBestObjectiveImproved > this.ReheatAfter)
                    {
                        // while it's unlikely alpha would be close enough to 1 and reheat intervals short enough to drive the acceptance probability
                        // above one, it is possible
                        meanAcceptanceProbability    = Math.Min(meanAcceptanceProbability + this.ReheatBy, 1.0F);
                        logMeanAcceptanceProbability = MathV.Ln(meanAcceptanceProbability);
                        iterationsSinceReheatOrBestObjectiveImproved = 0;
                    }
                }
            }

            stopwatch.Stop();
            return(stopwatch.Elapsed);
        }
Example #3
0
        public override TimeSpan Run()
        {
            if (this.MaximumIterations < 1)
            {
                throw new ArgumentOutOfRangeException(nameof(this.MaximumIterations));
            }
            if (this.Objective.HarvestPeriodSelection != HarvestPeriodSelection.NoneOrLast)
            {
                throw new NotSupportedException(nameof(this.Objective.HarvestPeriodSelection));
            }

            Stopwatch stopwatch = new Stopwatch();

            stopwatch.Start();

            int initialTreeRecordCount = this.GetInitialTreeRecordCount();

            this.EvaluateInitialSelection(this.MaximumIterations * initialTreeRecordCount);

            float acceptedObjectiveFunction            = this.BestObjectiveFunction;
            float previousBestObjectiveFunction        = this.BestObjectiveFunction;
            OrganonStandTrajectory candidateTrajectory = new OrganonStandTrajectory(this.CurrentTrajectory);

            int[] treeIndices = Heuristic.CreateSequentialArray(initialTreeRecordCount);
            for (int iteration = 0; iteration < this.MaximumIterations; ++iteration)
            {
                // randomize on every iteration since a single randomization against the order of the data has little effect
                if (this.IsStochastic)
                {
                    this.Pseudorandom.Shuffle(treeIndices);
                }

                for (int iterationMoveIndex = 0; iterationMoveIndex < initialTreeRecordCount; ++iterationMoveIndex)
                {
                    // evaluate other cut option
                    int treeIndex              = treeIndices[iterationMoveIndex];
                    int currentHarvestPeriod   = this.CurrentTrajectory.GetTreeSelection(treeIndex);
                    int candidateHarvestPeriod = currentHarvestPeriod == 0 ? this.CurrentTrajectory.HarvestPeriods - 1 : 0;
                    candidateTrajectory.SetTreeSelection(treeIndex, candidateHarvestPeriod);
                    candidateTrajectory.Simulate();

                    float candidateObjectiveFunction = this.GetObjectiveFunction(candidateTrajectory);
                    if (candidateObjectiveFunction > acceptedObjectiveFunction)
                    {
                        // accept change of no cut-cut decision if it improves upon the best solution
                        acceptedObjectiveFunction = candidateObjectiveFunction;
                        this.CurrentTrajectory.CopyFrom(candidateTrajectory);
                    }
                    else
                    {
                        // otherwise, revert changes candidate trajectory for considering next tree's move
                        candidateTrajectory.SetTreeSelection(treeIndex, currentHarvestPeriod);
                    }

                    this.AcceptedObjectiveFunctionByMove.Add(acceptedObjectiveFunction);
                    this.CandidateObjectiveFunctionByMove.Add(candidateObjectiveFunction);
                    this.MoveLog.TreeIDByMove.Add(treeIndex);
                }

                if (acceptedObjectiveFunction <= previousBestObjectiveFunction)
                {
                    // convergence: stop if no improvement
                    break;
                }
                previousBestObjectiveFunction = acceptedObjectiveFunction;
            }

            this.BestObjectiveFunction = acceptedObjectiveFunction;
            this.BestTrajectory.CopyFrom(this.CurrentTrajectory);

            stopwatch.Stop();
            return(stopwatch.Elapsed);
        }
Example #4
0
        public override TimeSpan Run()
        {
            if ((this.Alpha < 0.0F) || (this.Alpha > 1.0F))
            {
                throw new ArgumentOutOfRangeException(nameof(this.Alpha));
            }
            if (this.ChangeToExchangeAfter < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(this.ChangeToExchangeAfter));
            }
            if (this.FixedDeviation < 0.0F)
            {
                throw new ArgumentOutOfRangeException(nameof(this.FixedDeviation));
            }
            if (this.FixedIncrease < 0.0F)
            {
                throw new ArgumentOutOfRangeException(nameof(this.FixedIncrease));
            }
            if (this.IncreaseAfter < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(this.IncreaseAfter));
            }
            if (this.Objective.HarvestPeriodSelection != HarvestPeriodSelection.NoneOrLast)
            {
                throw new NotSupportedException(nameof(this.Objective.HarvestPeriodSelection));
            }
            if ((this.RelativeDeviation < 0.0) || (this.RelativeDeviation > 1.0))
            {
                throw new ArgumentOutOfRangeException(nameof(this.RelativeDeviation));
            }
            if ((this.RelativeIncrease < 0.0) || (this.RelativeIncrease > 1.0))
            {
                throw new ArgumentOutOfRangeException(nameof(this.RelativeIncrease));
            }
            if (this.StopAfter < 1)
            {
                throw new ArgumentOutOfRangeException(nameof(this.StopAfter));
            }

            Stopwatch stopwatch = new Stopwatch();

            stopwatch.Start();

            this.EvaluateInitialSelection(this.Iterations);

            float acceptedObjectiveFunction = this.BestObjectiveFunction;
            //float harvestPeriodScalingFactor = ((float)this.CurrentTrajectory.HarvestPeriods - Constant.RoundToZeroTolerance) / (float)byte.MaxValue;
            int   iterationsSinceBestObjectiveImproved     = 0;
            int   iterationsSinceObjectiveImprovedOrReheat = 0;
            float previousObjectiveFunction = Single.MinValue;
            float treeIndexScalingFactor    = ((float)this.GetInitialTreeRecordCount() - Constant.RoundTowardsZeroTolerance) / (float)UInt16.MaxValue;

            OrganonStandTrajectory candidateTrajectory = new OrganonStandTrajectory(this.CurrentTrajectory);
            float deviation = this.RelativeDeviation * MathF.Abs(this.BestObjectiveFunction) + this.FixedDeviation;

            for (int iteration = 1; (iteration < this.Iterations) && (iterationsSinceBestObjectiveImproved < this.StopAfter); deviation *= this.Alpha, ++iteration)
            {
                int firstTreeIndex            = (int)(treeIndexScalingFactor * this.GetTwoPseudorandomBytesAsFloat());
                int firstCurrentHarvestPeriod = this.CurrentTrajectory.GetTreeSelection(firstTreeIndex);
                int firstCandidateHarvestPeriod;
                int secondTreeIndex = -1;
                switch (this.MoveType)
                {
                case MoveType.OneOpt:
                    firstCandidateHarvestPeriod = firstCurrentHarvestPeriod == 0 ? this.CurrentTrajectory.HarvestPeriods - 1 : 0;
                    candidateTrajectory.SetTreeSelection(firstTreeIndex, firstCandidateHarvestPeriod);
                    break;

                case MoveType.TwoOptExchange:
                    secondTreeIndex             = (int)(treeIndexScalingFactor * this.GetTwoPseudorandomBytesAsFloat());
                    firstCandidateHarvestPeriod = this.CurrentTrajectory.GetTreeSelection(secondTreeIndex);
                    while (firstCandidateHarvestPeriod == firstCurrentHarvestPeriod)
                    {
                        // retry until a modifying exchange is found
                        // This also excludes the case where a tree is exchanged with itself.
                        // BUGBUG: infinite loop if all trees have the same selection
                        secondTreeIndex             = (int)(treeIndexScalingFactor * this.GetTwoPseudorandomBytesAsFloat());
                        firstCandidateHarvestPeriod = this.CurrentTrajectory.GetTreeSelection(secondTreeIndex);
                    }
                    candidateTrajectory.SetTreeSelection(firstTreeIndex, firstCandidateHarvestPeriod);
                    candidateTrajectory.SetTreeSelection(secondTreeIndex, firstCurrentHarvestPeriod);
                    break;

                default:
                    throw new NotSupportedException();
                }
                //int candidateHarvestPeriod = (int)(harvestPeriodScalingFactor * this.GetPseudorandomByteAsFloat());
                //while (candidateHarvestPeriod == currentHarvestPeriod)
                //{
                //    candidateHarvestPeriod = (int)(harvestPeriodScalingFactor * this.GetPseudorandomByteAsFloat());
                //}
                Debug.Assert(firstCandidateHarvestPeriod >= 0);

                candidateTrajectory.SetTreeSelection(firstTreeIndex, firstCandidateHarvestPeriod);
                candidateTrajectory.Simulate();
                ++iterationsSinceBestObjectiveImproved;
                ++iterationsSinceObjectiveImprovedOrReheat;

                float candidateObjectiveFunction         = this.GetObjectiveFunction(candidateTrajectory);
                float minimumAcceptableObjectiveFunction = this.BestObjectiveFunction - deviation;
                if ((candidateObjectiveFunction > minimumAcceptableObjectiveFunction) || (candidateObjectiveFunction > previousObjectiveFunction))
                {
                    // accept move
                    acceptedObjectiveFunction = candidateObjectiveFunction;
                    this.CurrentTrajectory.CopyFrom(candidateTrajectory);
                    iterationsSinceObjectiveImprovedOrReheat = 0;

                    if (acceptedObjectiveFunction > this.BestObjectiveFunction)
                    {
                        this.BestObjectiveFunction = acceptedObjectiveFunction;
                        this.BestTrajectory.CopyFrom(this.CurrentTrajectory);
                        iterationsSinceBestObjectiveImproved = 0;
                    }
                }
                else
                {
                    // undo move
                    switch (this.MoveType)
                    {
                    case MoveType.OneOpt:
                        candidateTrajectory.SetTreeSelection(firstTreeIndex, firstCurrentHarvestPeriod);
                        break;

                    case MoveType.TwoOptExchange:
                        candidateTrajectory.SetTreeSelection(firstTreeIndex, firstCurrentHarvestPeriod);
                        candidateTrajectory.SetTreeSelection(secondTreeIndex, firstCandidateHarvestPeriod);
                        break;

                    default:
                        throw new NotSupportedException();
                    }
                }

                this.AcceptedObjectiveFunctionByMove.Add(acceptedObjectiveFunction);
                this.CandidateObjectiveFunctionByMove.Add(candidateObjectiveFunction);
                this.MoveLog.TreeIDByMove.Add(firstTreeIndex);

                if (iterationsSinceBestObjectiveImproved > this.ChangeToExchangeAfter)
                {
                    this.MoveType = MoveType.TwoOptExchange;
                }
                if (iterationsSinceObjectiveImprovedOrReheat == this.IncreaseAfter)
                {
                    deviation += this.RelativeIncrease * MathF.Abs(this.BestObjectiveFunction) + this.FixedIncrease;
                    iterationsSinceObjectiveImprovedOrReheat = 0;
                }
                previousObjectiveFunction = acceptedObjectiveFunction;
            }

            stopwatch.Stop();
            return(stopwatch.Elapsed);
        }