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); }
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); }
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); }
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); }