protected override void ProcessRecord() { if (this.Runs !.Count < 1) { throw new ArgumentOutOfRangeException(nameof(this.Runs)); } using StreamWriter writer = this.GetWriter(); StringBuilder line = new StringBuilder(); if (this.ShouldWriteHeader()) { HeuristicParameters?highestParameters = this.Runs[0].HighestHeuristicParameters; if (highestParameters == null) { throw new NotSupportedException("Cannot generate header because first run is missing highest solution parameters."); } line.Append("stand,heuristic," + highestParameters.GetCsvHeader() + ",thin age,rotation,solution,objective,runtime"); writer.WriteLine(line); } for (int runIndex = 0; runIndex < this.Runs.Count; ++runIndex) { HeuristicSolutionDistribution distribution = this.Runs[runIndex]; Heuristic?highestHeuristic = distribution.HighestSolution; if ((highestHeuristic == null) || (distribution.HighestHeuristicParameters == null)) { throw new NotSupportedException("Run " + runIndex + " is missing a highest solution or highest solution parameters."); } OrganonStandTrajectory highestTrajectory = highestHeuristic.BestTrajectory; string linePrefix = highestTrajectory.Name + "," + highestHeuristic.GetName() + "," + distribution.HighestHeuristicParameters.GetCsvValues() + "," + highestTrajectory.GetFirstHarvestAge() + "," + highestTrajectory.GetRotationLength(); List <float> bestSolutions = distribution.BestObjectiveFunctionBySolution; for (int solutionIndex = 0; solutionIndex < bestSolutions.Count; ++solutionIndex) { line.Clear(); string objectiveFunction = bestSolutions[solutionIndex].ToString(CultureInfo.InvariantCulture); string runtime = distribution.RuntimeBySolution[solutionIndex].TotalSeconds.ToString("0.000", CultureInfo.InvariantCulture); line.Append(linePrefix + "," + solutionIndex + "," + objectiveFunction + "," + runtime); writer.WriteLine(line); } } }
private static void Verify(OrganonStandTrajectory thinnedTrajectory, float[] minimumThinnedVolumeScribner, int thinPeriod) { for (int periodIndex = 0; periodIndex < thinnedTrajectory.PlanningPeriods; ++periodIndex) { if (periodIndex == thinPeriod) { Assert.IsTrue(thinnedTrajectory.BasalAreaRemoved[periodIndex] > 0.0F); Assert.IsTrue(thinnedTrajectory.BasalAreaRemoved[periodIndex] < thinnedTrajectory.DensityByPeriod[periodIndex - 1].BasalAreaPerAcre); // assume <50% thin by volume Assert.IsTrue(thinnedTrajectory.ThinningVolume.ScribnerTotal[periodIndex] > 0.0F); Assert.IsTrue(thinnedTrajectory.ThinningVolume.ScribnerTotal[periodIndex] < minimumThinnedVolumeScribner[periodIndex]); } else { Assert.IsTrue(thinnedTrajectory.BasalAreaRemoved[periodIndex] == 0.0F); Assert.IsTrue(thinnedTrajectory.ThinningVolume.ScribnerTotal[periodIndex] == 0.0F); } } }
public void Plot14ImmediateThin() { int thinPeriod = 1; int lastPeriod = 4; bool useScaledVolume = false; PlotsWithHeight plot14 = PublicApi.GetPlot14(); OrganonConfiguration configuration = OrganonTest.CreateOrganonConfiguration(new OrganonVariantNwo()); OrganonStand stand = plot14.ToOrganonStand(configuration, 30, 130.0F); stand.PlantingDensityInTreesPerHectare = TestConstant.Plot14ReplantingDensityInTreesPerHectare; configuration.Treatments.Harvests.Add(new ThinByPrescription(thinPeriod) { FromAbovePercentage = 0.0F, ProportionalPercentage = 30.0F, FromBelowPercentage = 0.0F }); OrganonStandTrajectory thinnedTrajectory = new OrganonStandTrajectory(stand, configuration, TimberValue.Default, lastPeriod, useScaledVolume); AssertNullable.IsNotNull(thinnedTrajectory.StandByPeriod[0]); Assert.IsTrue(thinnedTrajectory.StandByPeriod[0] !.GetTreeRecordCount() == 222); thinnedTrajectory.Simulate(); // verify thinned trajectory // 0 1 2 3 4 float[] minimumThinnedQmd = new float[] { 9.16F, 10.47F, 11.64F, 12.64F, 13.52F }; // in // 0 1 2 3 4 float[] minimumThinnedTopHeight = new float[] { 92.9F, 101.4F, 110.4F, 118.9F, 126.7F }; // ft float[] minimumThinnedVolume; if (thinnedTrajectory.UseScaledVolume) { minimumThinnedVolume = new float[] { 51.59F, 51.75F, 66.71F, 81.88F, 97.72F }; // Poudel 2018 + Scribner long log net MBF/ha } else { // 0 1 2 3 4 minimumThinnedVolume = new float[] { 43.74F, 49.00F, 68.86F, 87.95F, 105.8F }; // Browning 1977 (FIA) MBF/ha } PublicApi.Verify(thinnedTrajectory, minimumThinnedQmd, minimumThinnedTopHeight, minimumThinnedVolume, thinPeriod, lastPeriod, 65, 70, configuration.Variant.TimeStepInYears); PublicApi.Verify(thinnedTrajectory, minimumThinnedVolume, thinPeriod); Assert.IsTrue(thinnedTrajectory.GetFirstHarvestAge() == 30); }
private static void Verify(OrganonStandTrajectory trajectory, float[] minimumQmd, float[] minimumTopHeight, float[] minimumStandingVolumeScribner, int thinPeriod, int lastPeriod, int minTrees, int maxTrees, int timeStepInYears) { Assert.IsTrue(trajectory.BasalAreaRemoved.Length == lastPeriod + 1); Assert.IsTrue(trajectory.BasalAreaRemoved[0] == 0.0F); Assert.IsTrue(trajectory.HarvestPeriods == thinPeriod + 1); // BUGBUG: clean off by one semantic Assert.IsTrue(trajectory.ThinningVolume.ScribnerTotal[0] == 0.0F); Assert.IsTrue(trajectory.ThinningVolume.ScribnerTotal.Length == lastPeriod + 1); PublicApi.Verify(trajectory.IndividualTreeSelectionBySpecies, thinPeriod, minTrees, maxTrees); Assert.IsTrue(String.IsNullOrEmpty(trajectory.Name) == false); Assert.IsTrue(trajectory.PeriodLengthInYears == timeStepInYears); Assert.IsTrue(trajectory.PlanningPeriods == lastPeriod + 1); // BUGBUG: clean off by one semantic float qmdTolerance = 1.01F; float topHeightTolerance = 1.01F; float volumeTolerance = 1.01F; for (int periodIndex = 0; periodIndex < trajectory.PlanningPeriods; ++periodIndex) { Assert.IsTrue(trajectory.DensityByPeriod[periodIndex].BasalAreaPerAcre > 0.0F); Assert.IsTrue(trajectory.DensityByPeriod[periodIndex].BasalAreaPerAcre <= TestConstant.Maximum.TreeBasalAreaLarger); Assert.IsTrue(trajectory.StandingVolume.ScribnerTotal[periodIndex] > minimumStandingVolumeScribner[periodIndex]); Assert.IsTrue(trajectory.StandingVolume.ScribnerTotal[periodIndex] < volumeTolerance * minimumStandingVolumeScribner[periodIndex]); OrganonStand stand = trajectory.StandByPeriod[periodIndex] ?? throw new NotSupportedException("Stand information missing for period " + periodIndex + "."); float qmd = stand.GetQuadraticMeanDiameter(); float topHeight = stand.GetTopHeight(); int treeRecords = stand.GetTreeRecordCount(); Assert.IsTrue((stand.Name != null) && (trajectory.Name != null) && stand.Name.StartsWith(trajectory.Name)); Assert.IsTrue(qmd > minimumQmd[periodIndex]); Assert.IsTrue(qmd < qmdTolerance * minimumQmd[periodIndex]); Assert.IsTrue(topHeight > minimumTopHeight[periodIndex]); Assert.IsTrue(topHeight < topHeightTolerance * minimumTopHeight[periodIndex]); Assert.IsTrue(treeRecords > 0); Assert.IsTrue(treeRecords < 666); // TODO: check qmd against QMD from basal area } }
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.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 void NelderTrajectory() { int expectedUnthinnedTreeRecordCount = 661; int lastPeriod = 9; bool useScaledVolume = false; PlotsWithHeight nelder = PublicApi.GetNelder(); OrganonConfiguration configuration = OrganonTest.CreateOrganonConfiguration(new OrganonVariantNwo()); OrganonStand stand = nelder.ToOrganonStand(configuration, 20, 130.0F); stand.PlantingDensityInTreesPerHectare = TestConstant.NelderReplantingDensityInTreesPerHectare; OrganonStandTrajectory unthinnedTrajectory = new OrganonStandTrajectory(stand, configuration, TimberValue.Default, lastPeriod, useScaledVolume); unthinnedTrajectory.Simulate(); foreach (Stand?unthinnedStand in unthinnedTrajectory.StandByPeriod) { AssertNullable.IsNotNull(unthinnedStand); Assert.IsTrue(unthinnedStand.GetTreeRecordCount() == expectedUnthinnedTreeRecordCount); } int thinPeriod = 3; configuration.Treatments.Harvests.Add(new ThinByPrescription(thinPeriod) { FromAbovePercentage = 20.0F, ProportionalPercentage = 15.0F, FromBelowPercentage = 10.0F }); OrganonStandTrajectory thinnedTrajectory = new OrganonStandTrajectory(stand, configuration, TimberValue.Default, lastPeriod, useScaledVolume); AssertNullable.IsNotNull(thinnedTrajectory.StandByPeriod[0]); Assert.IsTrue(thinnedTrajectory.StandByPeriod[0] !.GetTreeRecordCount() == expectedUnthinnedTreeRecordCount); thinnedTrajectory.Simulate(); for (int periodIndex = 0; periodIndex < thinPeriod; ++periodIndex) { Stand?unthinnedStand = thinnedTrajectory.StandByPeriod[periodIndex]; AssertNullable.IsNotNull(unthinnedStand); Assert.IsTrue(unthinnedStand.GetTreeRecordCount() == expectedUnthinnedTreeRecordCount); } int expectedThinnedTreeRecordCount = 328; // must be updated if prescription changes for (int periodIndex = thinPeriod; periodIndex < thinnedTrajectory.PlanningPeriods; ++periodIndex) { Stand?thinnedStand = thinnedTrajectory.StandByPeriod[periodIndex]; AssertNullable.IsNotNull(thinnedStand); Assert.IsTrue(thinnedStand.GetTreeRecordCount() == expectedThinnedTreeRecordCount); } // verify unthinned trajectory // 0 1 2 3 4 5 6 7 8 9 float[] minimumUnthinnedQmd = new float[] { 6.61F, 8.17F, 9.52F, 10.67F, 11.68F, 12.56F, 13.34F, 14.05F, 14.69F, 15.28F }; // in // 0 1 2 3 4 5 6 7 8 9 float[] minimumUnthinnedTopHeight = new float[] { 54.1F, 67.9F, 80.3F, 91.6F, 101.9F, 111.3F, 119.9F, 127.8F, 135.0F, 141.7F }; // ft float[] minimumUnthinnedVolume; if (unthinnedTrajectory.UseScaledVolume) { minimumUnthinnedVolume = new float[] { 9.758F, 19.01F, 31.07F, 47.24F, 62.21F, 75.09F, 89.64F, 103.7F, 116.5F, 128.9F }; // Poudel 2018 + Scribner long log net MBF/ha } else { // 0 1 2 3 4 5 6 7 8 9 minimumUnthinnedVolume = new float[] { 4.428F, 15.02F, 30.49F, 48.39F, 66.72F, 84.45F, 101.1F, 116.4F, 130.6F, 143.6F }; // FIA SV6x32 MBF/ha } PublicApi.Verify(unthinnedTrajectory, minimumUnthinnedQmd, minimumUnthinnedTopHeight, minimumUnthinnedVolume, 0, lastPeriod, 0, 0, configuration.Variant.TimeStepInYears); // verify thinned trajectory // 0 1 2 3 4 5 6 7 8 9 float[] minimumThinnedQmd = new float[] { 6.61F, 8.19F, 9.53F, 11.81F, 13.44F, 14.80F, 15.99F, 17.05F, 18.00F, 18.85F }; // in // 0 1 2 3 4 5 6 7 8 9 float[] minimumThinnedTopHeight = new float[] { 54.1F, 67.9F, 80.3F, 88.3F, 98.4F, 108.0F, 116.9F, 125.0F, 132.5F, 139.4F }; // ft float[] minimumThinnedVolume; if (thinnedTrajectory.UseScaledVolume) { minimumThinnedVolume = new float[] { 9.758F, 19.01F, 31.07F, 28.25F, 41.77F, 54.37F, 68.44F, 85.10F, 100.4F, 114.7F }; // Poudel 2018 + Scribner long log net MBF/ha } else { // 0 1 2 3 4 5 6 7 8 9 minimumThinnedVolume = new float[] { 4.428F, 15.02F, 30.50F, 30.64F, 47.26F, 64.81F, 82.54F, 99.87F, 116.3F, 131.7F }; // FIA MBF/ha } PublicApi.Verify(thinnedTrajectory, minimumThinnedQmd, minimumThinnedTopHeight, minimumThinnedVolume, thinPeriod, lastPeriod, 200, 400, configuration.Variant.TimeStepInYears); PublicApi.Verify(thinnedTrajectory, minimumThinnedVolume, thinPeriod); }
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); }
protected override void ProcessRecord() { if (this.Runs !.Count < 1) { throw new ArgumentOutOfRangeException(nameof(this.Runs)); } using StreamWriter writer = this.GetWriter(); // for now, perform no reduction when Object.ReferenceEquals(lowestSolution, highestSolution) is true StringBuilder line = new StringBuilder(); if (this.ShouldWriteHeader()) { if ((this.Runs[0].HighestSolution == null) || (this.Runs[0].HighestHeuristicParameters == null) || (this.Runs[0].LowestSolution == null)) { throw new NotSupportedException("Cannot generate header because first run is missing a highest solution, lowest solution, or highest solution parameters."); } line.Append("stand,heuristic,thin age,rotation," + this.Runs[0].HighestHeuristicParameters !.GetCsvHeader() + ",iteration,count"); string lowestMoveLogHeader = "lowest move log"; IHeuristicMoveLog?lowestMoveLog = this.Runs[0].LowestSolution !.GetMoveLog(); if (lowestMoveLog != null) { lowestMoveLogHeader = lowestMoveLog.GetCsvHeader("lowest "); } line.Append("," + lowestMoveLogHeader); line.Append(",lowest,lowest candidate,min,percentile 2.5,percentile 5,lower quartile,median,mean,upper quartile,percentile 95,percentile 97.5,max"); string highestMoveLogHeader = "highest move log"; IHeuristicMoveLog?highestMoveLog = this.Runs[0].HighestSolution !.GetMoveLog(); if (highestMoveLog != null) { highestMoveLogHeader = highestMoveLog.GetCsvHeader("highest "); } line.Append("," + highestMoveLogHeader); line.Append(",highest,highest candidate"); writer.WriteLine(line); } for (int runIndex = 0; runIndex < this.Runs.Count; ++runIndex) { HeuristicSolutionDistribution distribution = this.Runs[runIndex]; Heuristic?highestHeuristic = distribution.HighestSolution; Heuristic?lowestHeuristic = distribution.LowestSolution; if ((distribution.HighestHeuristicParameters == null) || (highestHeuristic == null) || (lowestHeuristic == null)) { throw new NotSupportedException("Solution distribution for run " + runIndex + " is missing a highest or lowest solution or highest solution parameters."); } IHeuristicMoveLog?highestMoveLog = highestHeuristic.GetMoveLog(); IHeuristicMoveLog?lowestMoveLog = lowestHeuristic.GetMoveLog(); // for now, assume highest and lowest solutions used the same parameters OrganonStandTrajectory highestTrajectory = highestHeuristic.BestTrajectory; string runPrefix = highestTrajectory.Name + "," + highestHeuristic.GetName() + "," + highestTrajectory.GetFirstHarvestAge() + "," + highestTrajectory.GetRotationLength() + "," + distribution.HighestHeuristicParameters.GetCsvValues(); Debug.Assert(distribution.CountByMove.Count >= lowestHeuristic.AcceptedObjectiveFunctionByMove.Count); Debug.Assert(distribution.CountByMove.Count == distribution.MinimumObjectiveFunctionByMove.Count); Debug.Assert(distribution.CountByMove.Count >= distribution.TwoPointFivePercentileByMove.Count); Debug.Assert(distribution.CountByMove.Count >= distribution.FifthPercentileByMove.Count); Debug.Assert(distribution.CountByMove.Count >= distribution.LowerQuartileByMove.Count); Debug.Assert(distribution.CountByMove.Count >= distribution.MedianObjectiveFunctionByMove.Count); Debug.Assert(distribution.CountByMove.Count == distribution.MeanObjectiveFunctionByMove.Count); Debug.Assert(distribution.CountByMove.Count >= distribution.NinetyFifthPercentileByMove.Count); Debug.Assert(distribution.CountByMove.Count >= distribution.NinetySevenPointFivePercentileByMove.Count); Debug.Assert(distribution.CountByMove.Count >= distribution.UpperQuartileByMove.Count); Debug.Assert(distribution.CountByMove.Count == distribution.MaximumObjectiveFunctionByMove.Count); Debug.Assert(distribution.CountByMove.Count >= highestHeuristic.AcceptedObjectiveFunctionByMove.Count); for (int moveIndex = 0; moveIndex < distribution.CountByMove.Count; moveIndex += this.Step) { line.Clear(); string runsWithMoveAtIndex = distribution.CountByMove[moveIndex].ToString(CultureInfo.InvariantCulture); string?lowestMove = null; if ((lowestMoveLog != null) && (lowestMoveLog.Count > moveIndex)) { lowestMove = lowestMoveLog.GetCsvValues(moveIndex); } string?lowestObjectiveFunction = null; if (lowestHeuristic.AcceptedObjectiveFunctionByMove.Count > moveIndex) { lowestObjectiveFunction = lowestHeuristic.AcceptedObjectiveFunctionByMove[moveIndex].ToString(CultureInfo.InvariantCulture); } string?lowestObjectiveFunctionForMove = null; if (lowestHeuristic.CandidateObjectiveFunctionByMove.Count > moveIndex) { lowestObjectiveFunctionForMove = lowestHeuristic.CandidateObjectiveFunctionByMove[moveIndex].ToString(CultureInfo.InvariantCulture); } string minObjectiveFunction = distribution.MinimumObjectiveFunctionByMove[moveIndex].ToString(CultureInfo.InvariantCulture); string?lowerQuartileObjectiveFunction = null; if (moveIndex < distribution.LowerQuartileByMove.Count) { lowerQuartileObjectiveFunction = distribution.LowerQuartileByMove[moveIndex].ToString(CultureInfo.InvariantCulture); } string?twoPointFivePercentileObjectiveFunction = null; if (moveIndex < distribution.TwoPointFivePercentileByMove.Count) { twoPointFivePercentileObjectiveFunction = distribution.TwoPointFivePercentileByMove[moveIndex].ToString(CultureInfo.InvariantCulture); } string?fifthPercentileObjectiveFunction = null; if (moveIndex < distribution.FifthPercentileByMove.Count) { fifthPercentileObjectiveFunction = distribution.FifthPercentileByMove[moveIndex].ToString(CultureInfo.InvariantCulture); } string?medianObjectiveFunction = null; if (moveIndex < distribution.MedianObjectiveFunctionByMove.Count) { medianObjectiveFunction = distribution.MedianObjectiveFunctionByMove[moveIndex].ToString(CultureInfo.InvariantCulture); } string meanObjectiveFunction = distribution.MeanObjectiveFunctionByMove[moveIndex].ToString(CultureInfo.InvariantCulture); string?upperQuartileObjectiveFunction = null; if (moveIndex < distribution.UpperQuartileByMove.Count) { upperQuartileObjectiveFunction = distribution.UpperQuartileByMove[moveIndex].ToString(CultureInfo.InvariantCulture); } string?ninetyFifthPercentileObjectiveFunction = null; if (moveIndex < distribution.NinetyFifthPercentileByMove.Count) { ninetyFifthPercentileObjectiveFunction = distribution.NinetyFifthPercentileByMove[moveIndex].ToString(CultureInfo.InvariantCulture); } string?ninetySevenPointFivePercentileObjectiveFunction = null; if (moveIndex < distribution.NinetySevenPointFivePercentileByMove.Count) { ninetySevenPointFivePercentileObjectiveFunction = distribution.NinetySevenPointFivePercentileByMove[moveIndex].ToString(CultureInfo.InvariantCulture); } string maxObjectiveFunction = distribution.MaximumObjectiveFunctionByMove[moveIndex].ToString(CultureInfo.InvariantCulture); string?highestMove = null; if ((highestMoveLog != null) && (highestMoveLog.Count > moveIndex)) { highestMove = highestMoveLog.GetCsvValues(moveIndex); } string highestObjectiveFunction = String.Empty; if (highestHeuristic.AcceptedObjectiveFunctionByMove.Count > moveIndex) { highestObjectiveFunction = highestHeuristic.AcceptedObjectiveFunctionByMove[moveIndex].ToString(CultureInfo.InvariantCulture); } string?highestObjectiveFunctionForMove = null; if (highestHeuristic.CandidateObjectiveFunctionByMove.Count > moveIndex) { highestObjectiveFunctionForMove = highestHeuristic.CandidateObjectiveFunctionByMove[moveIndex].ToString(CultureInfo.InvariantCulture); } line.Append(runPrefix + "," + moveIndex + "," + runsWithMoveAtIndex + "," + lowestMove + "," + lowestObjectiveFunction + "," + lowestObjectiveFunctionForMove + "," + minObjectiveFunction + "," + twoPointFivePercentileObjectiveFunction + "," + fifthPercentileObjectiveFunction + "," + lowerQuartileObjectiveFunction + "," + medianObjectiveFunction + "," + meanObjectiveFunction + "," + upperQuartileObjectiveFunction + "," + ninetyFifthPercentileObjectiveFunction + "," + ninetySevenPointFivePercentileObjectiveFunction + "," + maxObjectiveFunction + "," + highestMove + "," + highestObjectiveFunction + "," + highestObjectiveFunctionForMove); writer.WriteLine(line); } } }
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); }
protected override void ProcessRecord() { using StreamWriter writer = this.GetWriter(); StringBuilder line = new StringBuilder(); if (this.ShouldWriteHeader()) { line.Append("period"); // harvest volume headers for (int runIndex = 0; runIndex < this.Runs!.Count; ++runIndex) { Heuristic? highestSolution = this.Runs[runIndex].HighestSolution; if (highestSolution == null) { throw new NotSupportedException("Cannot write harvest becaue no heuristic solution was provided for run " + runIndex + "."); } OrganonStandTrajectory bestTrajectory = highestSolution.BestTrajectory; line.Append("," + bestTrajectory.Name + "harvest"); } // standing volume headers for (int runIndex = 0; runIndex < this.Runs.Count; ++runIndex) { OrganonStandTrajectory bestTrajectory = this.Runs[runIndex].HighestSolution!.BestTrajectory; line.Append("," + bestTrajectory.Name + "standing"); } writer.WriteLine(line); } int maxPlanningPeriod = 0; for (int runIndex = 0; runIndex < this.Runs!.Count; ++runIndex) { Heuristic? highestSolution = this.Runs[runIndex].HighestSolution; if (highestSolution == null) { throw new NotSupportedException("Cannot write harvest becaue no heuristic solution was provided for run " + runIndex + "."); } OrganonStandTrajectory bestTrajectory = highestSolution.BestTrajectory; maxPlanningPeriod = Math.Max(maxPlanningPeriod, bestTrajectory.PlanningPeriods); } for (int periodIndex = 0; periodIndex < maxPlanningPeriod; ++periodIndex) { line.Clear(); line.Append(periodIndex); for (int runIndex = 0; runIndex < this.Runs.Count; ++runIndex) { Heuristic heuristic = this.Runs[runIndex].HighestSolution!; float harvestVolumeScibner = heuristic.BestTrajectory.ThinningVolume.ScribnerTotal[periodIndex]; line.Append("," + harvestVolumeScibner.ToString(CultureInfo.InvariantCulture)); } for (int runIndex = 0; runIndex < this.Runs.Count; ++runIndex) { Heuristic heuristic = this.Runs[runIndex].HighestSolution!; float standingVolumeScribner = heuristic.BestTrajectory.StandingVolume.ScribnerTotal[periodIndex]; line.Append("," + standingVolumeScribner.ToString(CultureInfo.InvariantCulture)); } writer.WriteLine(line); } }