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); }
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]); } } }
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); }
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); }
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); }
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); } }
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); }
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];
public TestStandDensity(OrganonStand stand, OrganonVariant variant) : base(stand, variant) { }
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); }
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 }
protected static void Verify(ExpectedTreeChanges expectedGrowth, TestStand stand, OrganonVariant variant) { OrganonTest.Verify(expectedGrowth, OrganonWarnings.None, stand, variant); }
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); }
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); } }