示例#1
0
        protected override void ProcessRecord()
        {
            PlotsWithHeight plot;

            if (this.ExpansionFactor.HasValue)
            {
                plot = new PlotsWithHeight(this.Plots !, this.ExpansionFactor.Value);
            }
            else
            {
                plot = new PlotsWithHeight(this.Plots !);
            }
            plot.Read(this.Xlsx !, this.XlsxSheet);

            OrganonConfiguration configuration = new OrganonConfiguration(OrganonVariant.Create(this.Model));
            OrganonStand         stand;

            if (this.Trees.HasValue)
            {
                stand = plot.ToOrganonStand(configuration, this.Age, this.SiteIndex, this.Trees.Value);
            }
            else
            {
                stand = plot.ToOrganonStand(configuration, this.Age, this.SiteIndex);
            }
            if (this.PlantingDensity.HasValue)
            {
                stand.PlantingDensityInTreesPerHectare = this.PlantingDensity.Value;
            }
            this.WriteObject(stand);
        }
示例#2
0
        public void WriteToCsv(StreamWriter writer, OrganonVariant variant, int year)
        {
            foreach (KeyValuePair <FiaCode, float[]> meanDbhInCmForSpecies in this.MeanDbhInCmBySpecies)
            {
                FiaCode species     = meanDbhInCmForSpecies.Key;
                string  speciesCode = species.ToFourLetterCode();
                // BUGBUG: Huffman Peak hack
                if (species == FiaCode.AbiesConcolor)
                {
                    speciesCode = FiaCode.AbiesProcera.ToFourLetterCode();
                }
                else if (species == FiaCode.AbiesGrandis)
                {
                    speciesCode = FiaCode.AbiesAmabalis.ToFourLetterCode();
                }
                float[] deadExpansionFactors = this.DeadExpansionFactorBySpecies[species];
                float[] liveExpansionFactors = this.LiveExpansionFactorBySpecies[species];
                float[] maxDbh          = this.MaxDbhInCmBySpecies[species];
                float[] meanCrownRatios = this.MeanCrownRatioBySpecies[species];
                float[] meanHeight      = this.MeanHeightInMetersBySpecies[species];
                float[] minDbh          = this.MinDbhInCmBySpecies[species];

                for (int quantile = 0; quantile < TestConstant.DbhQuantiles; ++quantile)
                {
                    writer.WriteLine("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10}", variant.TreeModel, year, speciesCode, quantile,
                                     meanDbhInCmForSpecies.Value[quantile], meanHeight[quantile], liveExpansionFactors[quantile],
                                     deadExpansionFactors[quantile], meanCrownRatios[quantile], minDbh[quantile], maxDbh[quantile]);
                }
            }
        }
示例#3
0
        public StreamWriter WriteToCsv(string filePath, OrganonVariant variant, int year)
        {
            FileStream   stream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Read);
            StreamWriter writer = new StreamWriter(stream);

            writer.WriteLine("variant,year,TPH,basal area,CCF");
            this.WriteToCsv(writer, variant, year);
            return(writer);
        }
示例#4
0
        public StreamWriter WriteToCsv(string filePath, OrganonVariant variant, int year)
        {
            FileStream   stream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Read);
            StreamWriter writer = new StreamWriter(stream);

            writer.WriteLine("variant,year,species,quantile,mean DBH,mean height,expansion factor,dead expansion factor,crown ratio,min DBH,max DBH");
            this.WriteToCsv(writer, variant, year);
            return(writer);
        }
示例#5
0
        protected static OrganonConfiguration CreateOrganonConfiguration(OrganonVariant variant)
        {
            OrganonConfiguration configuration = new OrganonConfiguration(variant)
            {
                DefaultMaximumSdi = TestConstant.Default.MaximumReinekeStandDensityIndex,
                TrueFirMaximumSdi = TestConstant.Default.MaximumReinekeStandDensityIndex,
                HemlockMaximumSdi = TestConstant.Default.MaximumReinekeStandDensityIndex,
            };

            return(configuration);
        }
示例#6
0
文件: PspStand.cs 项目: OSU-MARS/SEEM
        private static FiaCode MaybeRemapToSupportedSpecies(FiaCode species, OrganonVariant variant)
        {
            if (variant.IsSpeciesSupported(species))
            {
                return(species);
            }

            if (species == FiaCode.ChrysolepisChrysophyllaVarChrysophylla)
            {
                return(FiaCode.CornusNuttallii);
            }
            else
            {
                throw Trees.CreateUnhandledSpeciesException(species);
            }
        }
示例#7
0
        protected static void GrowPspStand(PspStand huffmanPeak, TestStand stand, OrganonVariant variant, int startYear, int endYear, string baseFileName)
        {
            OrganonConfiguration configuration   = OrganonTest.CreateOrganonConfiguration(variant);
            TestStand            initialTreeData = new TestStand(stand);
            TreeLifeAndDeath     treeGrowth      = new TreeLifeAndDeath();

            Dictionary <FiaCode, SpeciesCalibration> calibrationBySpecies = configuration.CreateSpeciesCalibration();

            if (configuration.IsEvenAge)
            {
                // stand error if less than one year to grow to breast height
                stand.AgeInYears = stand.BreastHeightAgeInYears + 2;
            }

            TestStandDensity density = new TestStandDensity(stand, variant);

            using StreamWriter densityWriter = density.WriteToCsv(baseFileName + " density.csv", variant, startYear);
            TreeQuantiles quantiles = new TreeQuantiles(stand);

            using StreamWriter quantileWriter   = quantiles.WriteToCsv(baseFileName + " quantiles.csv", variant, startYear);
            using StreamWriter treeGrowthWriter = stand.WriteTreesToCsv(baseFileName + " tree growth.csv", variant, startYear);
            for (int simulationStep = 0, year = startYear + variant.TimeStepInYears; year <= endYear; year += variant.TimeStepInYears, ++simulationStep)
            {
                OrganonGrowth.Grow(simulationStep, configuration, stand, calibrationBySpecies);
                treeGrowth.AccumulateGrowthAndMortality(stand);
                huffmanPeak.AddIngrowth(year, stand, density);
                OrganonTest.Verify(ExpectedTreeChanges.DiameterGrowthOrNoChange | ExpectedTreeChanges.HeightGrowthOrNoChange, stand, variant);

                density = new TestStandDensity(stand, variant);
                density.WriteToCsv(densityWriter, variant, year);
                quantiles = new TreeQuantiles(stand);
                quantiles.WriteToCsv(quantileWriter, variant, year);
                stand.WriteTreesToCsv(treeGrowthWriter, variant, year);
            }

            OrganonTest.Verify(ExpectedTreeChanges.ExpansionFactorConservedOrIncreased | ExpectedTreeChanges.DiameterGrowthOrNoChange | ExpectedTreeChanges.HeightGrowthOrNoChange, treeGrowth, initialTreeData, stand);
            OrganonTest.Verify(calibrationBySpecies);
        }
示例#8
0
        protected static void Verify(float[] crownCompetitionByHeight, OrganonVariant variant)
        {
            float ccfInStrataImmediatelyBelow = TestConstant.Maximum.CrownCompetitionFactor;

            for (int ccfIndex = 0; ccfIndex < crownCompetitionByHeight.Length - 1; ++ccfIndex)
            {
                float ccfAtHeight = crownCompetitionByHeight[ccfIndex];
                Assert.IsTrue(ccfAtHeight >= 0.0F);
                Assert.IsTrue(ccfAtHeight < TestConstant.Maximum.CrownCompetitionFactor);
                if (variant.TreeModel == TreeModel.OrganonRap)
                {
                    // red alder coefficients in OrganonVariantRap.GetCrownWidth() result in nonmonotonic crown widths and therefore nonmonotonic CCF
                    Assert.IsTrue(ccfAtHeight <= 2.0 * ccfInStrataImmediatelyBelow);
                }
                else
                {
                    Assert.IsTrue(ccfAtHeight <= ccfInStrataImmediatelyBelow);
                }

                ccfInStrataImmediatelyBelow = ccfAtHeight;
            }

            float tallestTreeHeight = crownCompetitionByHeight[^ 1];
示例#9
0
 public TestStandDensity(OrganonStand stand, OrganonVariant variant)
     : base(stand, variant)
 {
 }
示例#10
0
 public void WriteToCsv(StreamWriter writer, OrganonVariant variant, int year)
 {
     writer.WriteLine("{0},{1},{2},{3},{4}",
                      variant.TreeModel, year, 2.47105F * this.TreesPerAcre, 2.47105F * 0.092903F * this.BasalAreaPerAcre,
                      this.CrownCompetitionFactor);
 }
示例#11
0
        protected static void Verify(ExpectedTreeChanges expectedGrowth, OrganonWarnings expectedWarnings, TestStand stand, OrganonVariant variant)
        {
            Assert.IsTrue(stand.AgeInYears >= 0);
            Assert.IsTrue(stand.AgeInYears <= TestConstant.Maximum.StandAgeInYears);
            Assert.IsTrue(stand.BreastHeightAgeInYears >= 0);
            Assert.IsTrue(stand.BreastHeightAgeInYears <= TestConstant.Maximum.StandAgeInYears);
            Assert.IsTrue(stand.NumberOfPlots >= 1);
            Assert.IsTrue(stand.NumberOfPlots <= 36);
            Assert.IsTrue(stand.TreesBySpecies.Count > 0);
            Assert.IsTrue(stand.GetTreeRecordCount() > 0);

            foreach (Trees treesOfSpecies in stand.TreesBySpecies.Values)
            {
                FiaCode species = treesOfSpecies.Species;
                Assert.IsTrue(Enum.IsDefined(typeof(FiaCode), species));

                for (int treeIndex = 0; treeIndex < treesOfSpecies.Count; ++treeIndex)
                {
                    // primary tree data
                    float crownRatio = treesOfSpecies.CrownRatio[treeIndex];
                    Assert.IsTrue(crownRatio >= 0.0F);
                    Assert.IsTrue(crownRatio <= 1.0F);
                    float dbhInInches = treesOfSpecies.Dbh[treeIndex];
                    Assert.IsTrue(dbhInInches >= 0.0F);
                    Assert.IsTrue(dbhInInches <= TestConstant.Maximum.DiameterInInches);
                    float expansionFactor = treesOfSpecies.LiveExpansionFactor[treeIndex];
                    Assert.IsTrue(expansionFactor >= 0.0F);
                    Assert.IsTrue(expansionFactor <= TestConstant.Maximum.ExpansionFactor);
                    float heightInFeet = treesOfSpecies.Height[treeIndex];
                    Assert.IsTrue(heightInFeet >= 0.0F);
                    Assert.IsTrue(heightInFeet <= TestConstant.Maximum.HeightInFeet);

                    float deadExpansionFactor = treesOfSpecies.DeadExpansionFactor[treeIndex];
                    Assert.IsTrue(deadExpansionFactor >= 0.0F);
                    Assert.IsTrue(deadExpansionFactor <= TestConstant.Maximum.ExpansionFactor);
                    Assert.IsTrue(expansionFactor + deadExpansionFactor <= TestConstant.Maximum.ExpansionFactor);

                    // diameter and height growth
                    float diameterGrowthInInches = treesOfSpecies.DbhGrowth[treeIndex];
                    if (expectedGrowth.HasFlag(ExpectedTreeChanges.DiameterGrowth))
                    {
                        Assert.IsTrue(diameterGrowthInInches > 0.0F, "{0}: {1} {2} did not grow in diameter.", variant.TreeModel, treesOfSpecies.Species, treeIndex);
                        Assert.IsTrue(diameterGrowthInInches <= 0.1F * TestConstant.Maximum.DiameterInInches);
                    }
                    else if (expectedGrowth.HasFlag(ExpectedTreeChanges.DiameterGrowthOrNoChange))
                    {
                        Assert.IsTrue(diameterGrowthInInches >= 0.0F);
                        Assert.IsTrue(diameterGrowthInInches <= 0.1F * TestConstant.Maximum.DiameterInInches);
                    }
                    else
                    {
                        Assert.IsTrue(diameterGrowthInInches == 0.0F);
                    }
                    float heightGrowthInFeet = treesOfSpecies.HeightGrowth[treeIndex];
                    if (expectedGrowth.HasFlag(ExpectedTreeChanges.HeightGrowth))
                    {
                        Assert.IsTrue(heightGrowthInFeet > 0.0F, "{0}: {1} {2} did not grow in height.", variant.TreeModel, treesOfSpecies.Species, treeIndex);
                        Assert.IsTrue(heightGrowthInFeet <= 0.1F * TestConstant.Maximum.HeightInFeet);
                    }
                    else if (expectedGrowth.HasFlag(ExpectedTreeChanges.HeightGrowthOrNoChange))
                    {
                        Assert.IsTrue(heightGrowthInFeet >= 0.0F, "{0}: {1} {2} decreased in height.", variant.TreeModel, treesOfSpecies.Species, treeIndex);
                        Assert.IsTrue(heightGrowthInFeet <= 0.1F * TestConstant.Maximum.HeightInFeet);
                    }
                    else
                    {
                        Assert.IsTrue(heightGrowthInFeet == 0.0F);
                    }

                    // for now, ignore warnings on height exceeding potential height
                    // Assert.IsTrue(stand.TreeWarnings[treeWarningIndex] == 0);
                }

                for (int treeIndex = treesOfSpecies.Count; treeIndex < treesOfSpecies.Capacity; ++treeIndex)
                {
                    Assert.IsTrue(treesOfSpecies.CrownRatio[treeIndex] == 0.0F);
                    Assert.IsTrue(treesOfSpecies.Dbh[treeIndex] == 0.0F);
                    Assert.IsTrue(treesOfSpecies.DeadExpansionFactor[treeIndex] == 0.0F);
                    Assert.IsTrue(treesOfSpecies.Height[treeIndex] == 0.0F);
                    Assert.IsTrue(treesOfSpecies.LiveExpansionFactor[treeIndex] == 0.0F);
                }
            }

            Assert.IsTrue(stand.Warnings.BigSixHeightAbovePotential == false);
            Assert.IsTrue(stand.Warnings.LessThan50TreeRecords == expectedWarnings.HasFlag(OrganonWarnings.LessThan50TreeRecords));
            Assert.IsTrue(stand.Warnings.HemlockSiteIndexOutOfRange == expectedWarnings.HasFlag(OrganonWarnings.HemlockSiteIndex));
            Assert.IsTrue(stand.Warnings.OtherSpeciesBasalAreaTooHigh == false);
            Assert.IsTrue(stand.Warnings.SiteIndexOutOfRange == false);
            if (variant.TreeModel != TreeModel.OrganonSmc)
            {
                // for now, ignore SMC warning for breast height age < 10
                Assert.IsTrue(stand.Warnings.TreesOld == false);
            }
            // for now, ignore stand.Warnings.TreesYoung
        }
示例#12
0
 protected static void Verify(ExpectedTreeChanges expectedGrowth, TestStand stand, OrganonVariant variant)
 {
     OrganonTest.Verify(expectedGrowth, OrganonWarnings.None, stand, variant);
 }
示例#13
0
        protected static TestStand CreateDefaultStand(OrganonConfiguration configuration)
        {
            // TODO: cover cases with more than one SIMD width per species
            TestStand stand = new TestStand(configuration.Variant, 0, TestConstant.Default.SiteIndex)
            {
                PlantingDensityInTreesPerHectare = 939.0F // 380 trees per acre
            };

            switch (configuration.Variant.TreeModel)
            {
            case TreeModel.OrganonNwo:
            case TreeModel.OrganonSmc:
                stand.Add(new TreeRecord(1, FiaCode.PseudotsugaMenziesii, 0.1F, 0.4F, 10.0F));
                stand.Add(new TreeRecord(2, FiaCode.PseudotsugaMenziesii, 0.2F, 0.5F, 20.0F));
                stand.Add(new TreeRecord(3, FiaCode.PseudotsugaMenziesii, 0.3F, 0.6F, 10.0F));
                stand.Add(new TreeRecord(4, FiaCode.PseudotsugaMenziesii, 10.0F, 0.5F, 10.0F));
                stand.Add(new TreeRecord(5, FiaCode.AbiesGrandis, 0.1F, 0.6F, 1.0F));
                stand.Add(new TreeRecord(6, FiaCode.AbiesGrandis, 1.0F, 0.7F, 2.0F));
                stand.Add(new TreeRecord(7, FiaCode.TsugaHeterophylla, 0.1F, 0.6F, 5.0F));
                stand.Add(new TreeRecord(8, FiaCode.TsugaHeterophylla, 0.5F, 0.7F, 10.0F));
                stand.Add(new TreeRecord(9, FiaCode.ThujaPlicata, 0.1F, 0.4F, 10.0F));
                stand.Add(new TreeRecord(10, FiaCode.ThujaPlicata, 1.0F, 0.5F, 15.0F));

                stand.Add(new TreeRecord(11, FiaCode.TaxusBrevifolia, 0.1F, 0.7F, 2.0F));
                stand.Add(new TreeRecord(12, FiaCode.ArbutusMenziesii, 1.0F, 0.5F, 2.0F));
                stand.Add(new TreeRecord(13, FiaCode.AcerMacrophyllum, 0.1F, 0.5F, 2.0F));
                stand.Add(new TreeRecord(14, FiaCode.QuercusGarryana, 10.0F, 0.5F, 2.0F));
                stand.Add(new TreeRecord(15, FiaCode.AlnusRubra, 0.1F, 0.5F, 2.0F));
                stand.Add(new TreeRecord(16, FiaCode.CornusNuttallii, 0.1F, 0.5F, 2.0F));
                stand.Add(new TreeRecord(17, FiaCode.Salix, 0.1F, 0.5F, 2.0F));
                break;

            case TreeModel.OrganonRap:
                stand.Add(new TreeRecord(1, FiaCode.AlnusRubra, 0.1F, 0.3F, 30.0F));
                stand.Add(new TreeRecord(2, FiaCode.AlnusRubra, 0.2F, 0.4F, 40.0F));
                stand.Add(new TreeRecord(3, FiaCode.AlnusRubra, 0.3F, 0.5F, 30.0F));

                stand.Add(new TreeRecord(4, FiaCode.PseudotsugaMenziesii, 0.1F, 0.5F, 1.0F));
                stand.Add(new TreeRecord(5, FiaCode.TsugaHeterophylla, 0.1F, 0.5F, 1.0F));
                stand.Add(new TreeRecord(6, FiaCode.ThujaPlicata, 0.1F, 0.5F, 1.0F));
                stand.Add(new TreeRecord(7, FiaCode.AcerMacrophyllum, 0.1F, 0.5F, 1.0F));
                stand.Add(new TreeRecord(8, FiaCode.CornusNuttallii, 0.1F, 0.5F, 1.0F));
                stand.Add(new TreeRecord(9, FiaCode.Salix, 0.1F, 0.5F, 1.0F));
                break;

            case TreeModel.OrganonSwo:
                stand.Add(new TreeRecord(1, FiaCode.PseudotsugaMenziesii, 0.1F, 0.5F, 5.0F));
                stand.Add(new TreeRecord(2, FiaCode.AbiesConcolor, 0.1F, 0.5F, 5.0F));
                stand.Add(new TreeRecord(3, FiaCode.AbiesGrandis, 0.1F, 0.5F, 5.0F));
                stand.Add(new TreeRecord(4, FiaCode.PinusPonderosa, 0.1F, 0.5F, 10.0F));
                stand.Add(new TreeRecord(5, FiaCode.PinusLambertiana, 0.1F, 0.5F, 10.0F));
                stand.Add(new TreeRecord(6, FiaCode.CalocedrusDecurrens, 0.1F, 0.5F, 10.0F));

                stand.Add(new TreeRecord(7, FiaCode.TsugaHeterophylla, 0.1F, 0.5F, 1.0F));
                stand.Add(new TreeRecord(8, FiaCode.ThujaPlicata, 0.1F, 0.5F, 1.0F));
                stand.Add(new TreeRecord(9, FiaCode.TaxusBrevifolia, 0.1F, 0.5F, 1.0F));
                stand.Add(new TreeRecord(10, FiaCode.ArbutusMenziesii, 0.1F, 0.5F, 1.0F));
                stand.Add(new TreeRecord(11, FiaCode.ChrysolepisChrysophyllaVarChrysophylla, 0.1F, 0.5F, 1.0F));
                stand.Add(new TreeRecord(12, FiaCode.NotholithocarpusDensiflorus, 0.1F, 0.5F, 1.0F));
                stand.Add(new TreeRecord(13, FiaCode.QuercusChrysolepis, 0.1F, 0.5F, 1.0F));
                stand.Add(new TreeRecord(14, FiaCode.AcerMacrophyllum, 0.1F, 0.5F, 1.0F));
                stand.Add(new TreeRecord(15, FiaCode.QuercusGarryana, 0.1F, 0.5F, 1.0F));
                stand.Add(new TreeRecord(16, FiaCode.QuercusKelloggii, 0.1F, 0.5F, 1.0F));
                stand.Add(new TreeRecord(17, FiaCode.AlnusRubra, 0.1F, 0.5F, 1.0F));
                stand.Add(new TreeRecord(18, FiaCode.CornusNuttallii, 0.1F, 0.5F, 1.0F));
                stand.Add(new TreeRecord(19, FiaCode.Salix, 0.1F, 0.5F, 1.0F));
                break;

            default:
                throw OrganonVariant.CreateUnhandledModelException(configuration.Variant.TreeModel);
            }

            stand.SetRedAlderSiteIndexAndGrowthEffectiveAge();
            stand.SetSdiMax(configuration);
            return(stand);
        }
示例#14
0
        protected override void ProcessRecord()
        {
            if (this.HarvestPeriods.Count < 1)
            {
                throw new ArgumentOutOfRangeException(nameof(this.HarvestPeriods));
            }
            if ((this.PerturbBy < 0.0F) || (this.PerturbBy > 1.0F))
            {
                throw new ArgumentOutOfRangeException(nameof(this.PerturbBy));
            }
            if (this.PlanningPeriods.Count < 1)
            {
                throw new ArgumentOutOfRangeException(nameof(this.PlanningPeriods));
            }
            if (this.ProportionalPercentage.Count < 1)
            {
                throw new ArgumentOutOfRangeException(nameof(this.ProportionalPercentage));
            }

            Stopwatch stopwatch = new Stopwatch();

            stopwatch.Start();

            IList <TParameters> parameterCombinations = this.GetParameterCombinations();
            int treeCount = this.Stand !.GetTreeRecordCount();
            List <HeuristicSolutionDistribution> distributions = new List <HeuristicSolutionDistribution>(parameterCombinations.Count * this.HarvestPeriods.Count * this.PlanningPeriods.Count);

            for (int planningPeriodIndex = 0; planningPeriodIndex < this.PlanningPeriods.Count; ++planningPeriodIndex)
            {
                for (int harvestPeriodIndex = 0; harvestPeriodIndex < this.HarvestPeriods.Count; ++harvestPeriodIndex)
                {
                    int planningPeriods = this.PlanningPeriods[planningPeriodIndex];
                    int harvestPeriods  = this.HarvestPeriods[harvestPeriodIndex];
                    if (harvestPeriods >= planningPeriods) // minimum 10 years between thinning and final harvest (if five year time step)
                    {
                        continue;
                    }

                    for (int parameterIndex = 0; parameterIndex < parameterCombinations.Count; ++parameterIndex)
                    {
                        distributions.Add(new HeuristicSolutionDistribution(1, harvestPeriods, treeCount)
                        {
                            HarvestPeriodIndex  = harvestPeriodIndex,
                            ParameterIndex      = parameterIndex,
                            PlanningPeriodIndex = planningPeriodIndex
                        });
                    }
                }
            }

            ParallelOptions parallelOptions = new ParallelOptions()
            {
                MaxDegreeOfParallelism = this.Threads
            };
            int  totalRuns     = distributions.Count * this.BestOf;
            int  runsCompleted = 0;
            Task runs          = Task.Run(() =>
            {
                Parallel.For(0, totalRuns, parallelOptions, (int iteration, ParallelLoopState loopState) =>
                {
                    if (loopState.ShouldExitCurrentIteration)
                    {
                        return;
                    }

                    int distributionIndex = iteration / this.BestOf;
                    HeuristicSolutionDistribution distribution = distributions[distributionIndex];
                    OrganonConfiguration organonConfiguration  = new OrganonConfiguration(OrganonVariant.Create(this.TreeModel));
                    organonConfiguration.Treatments.Harvests.Add(this.CreateHarvest(distribution.HarvestPeriodIndex));

                    Objective objective = new Objective()
                    {
                        IsLandExpectationValue = this.LandExpectationValue,
                        PlanningPeriods        = this.PlanningPeriods[distribution.PlanningPeriodIndex]
                    };
                    TParameters runParameters  = parameterCombinations[distribution.ParameterIndex];
                    Heuristic currentHeuristic = this.CreateHeuristic(organonConfiguration, objective, runParameters);
                    if (runParameters.PerturbBy > 0.0F)
                    {
                        if ((runParameters.PerturbBy == 1.0F) || (distribution.EliteSolutions.NewIndividuals == 0))
                        {
                            // minor optimization point: save a few time steps by by re-using pre-thin results
                            // minor optimization point: save one loop over stand by skipping this for genetic algorithms
                            currentHeuristic.RandomizeTreeSelection(runParameters.ProportionalPercentage);
                        }
                        else
                        {
                            // TODO: support initialization from unperturbed elite solutions
                            // TODO: intialize genetic algorithm population from elite solutions?
                            // TODO: how to define generation statistics?
                            // TODO: more granular locking?
                            lock (distributions)
                            {
                                currentHeuristic.RandomizeTreeSelectionFrom(runParameters.PerturbBy, distribution.EliteSolutions);
                            }
                        }
                    }
                    TimeSpan runTime = currentHeuristic.Run();

                    lock (distributions)
                    {
                        distribution.AddRun(currentHeuristic, runTime, runParameters);
                        ++runsCompleted;
                    }

                    if (this.Stopping)
                    {
                        loopState.Stop();
                    }
                });
            });

            string name = this.GetName();
            int    sleepsSinceLastStatusUpdate = 0;

            while (runs.IsCompleted == false)
            {
                Thread.Sleep(TimeSpan.FromSeconds(1.0));
                ++sleepsSinceLastStatusUpdate;

                if (runs.IsFaulted)
                {
                    Debug.Assert(runs.Exception != null && runs.Exception.InnerException != null);
                    // per https://stackoverflow.com/questions/20170527/how-to-correctly-rethrow-an-exception-of-task-already-in-faulted-state
                    ExceptionDispatchInfo.Capture(runs.Exception.InnerException).Throw();
                }
                if (sleepsSinceLastStatusUpdate > 30)
                {
                    double fractionComplete = (double)runsCompleted / (double)totalRuns;
                    double secondsElapsed   = stopwatch.Elapsed.TotalSeconds;
                    double secondsRemaining = secondsElapsed * (1.0 / fractionComplete - 1.0);
                    this.WriteProgress(new ProgressRecord(0, name, String.Format(runsCompleted + " of " + totalRuns + " runs completed by " + this.Threads + " threads."))
                    {
                        PercentComplete  = (int)(100.0 * fractionComplete),
                        SecondsRemaining = (int)Math.Round(secondsRemaining)
                    });
                    sleepsSinceLastStatusUpdate = 0;
                }
            }
            runs.GetAwaiter().GetResult(); // propagate any exceptions since last IsFaulted check

            foreach (HeuristicSolutionDistribution distribution in distributions)
            {
                distribution.OnRunsComplete();
            }
            stopwatch.Stop();

            this.WriteObject(distributions);
            if (distributions.Count == 1)
            {
                this.WriteSingleDistributionSummary(distributions[0], stopwatch.Elapsed);
            }
            else
            {
                this.WriteMultipleDistributionSummary(distributions, stopwatch.Elapsed);
            }
        }