public float EstimateInitialCrownRatio(OrganonStandDensity density) { float initialDiameterInInches = this.GetInitialDiameter() / Constant.CentimetersPerInch; float crownCompetition = density.GetCrownCompetitionFactorLarger(initialDiameterInInches); float crownCompetitionMidpoint = this.Species switch { FiaCode.PseudotsugaMenziesii => 125.0F, FiaCode.ThujaPlicata => 250.0F, FiaCode.TsugaHeterophylla => 300.0F, _ => 200.0F }; return(TestConstant.Default.CrownRatio / (1.0F + MathF.Exp(0.015F * (crownCompetition - crownCompetitionMidpoint)))); }
public void DiameterGrowthApi() { foreach (OrganonVariant variant in TestConstant.Variants) { OrganonConfiguration configuration = OrganonTest.CreateOrganonConfiguration(variant); TestStand stand = OrganonTest.CreateDefaultStand(configuration); Dictionary <FiaCode, SpeciesCalibration> calibrationBySpecies = configuration.CreateSpeciesCalibration(); Dictionary <FiaCode, float[]> previousTreeDiametersBySpecies = new Dictionary <FiaCode, float[]>(); foreach (Trees treesOfSpecies in stand.TreesBySpecies.Values) { previousTreeDiametersBySpecies.Add(treesOfSpecies.Species, new float[treesOfSpecies.Capacity]); } for (int simulationStep = 0; simulationStep < TestConstant.Default.SimulationCyclesToRun; ++simulationStep) { OrganonStandDensity treeCompetition = new OrganonStandDensity(stand, variant); foreach (Trees treesOfSpecies in stand.TreesBySpecies.Values) { float[] previousTreeDiameters = previousTreeDiametersBySpecies[treesOfSpecies.Species]; OrganonGrowth.GrowDiameter(configuration, simulationStep, stand, treesOfSpecies, treeCompetition, calibrationBySpecies[treesOfSpecies.Species].Diameter); stand.SetSdiMax(configuration); for (int treeIndex = 0; treeIndex < treesOfSpecies.Count; ++treeIndex) { float dbhInInches = treesOfSpecies.Dbh[treeIndex]; float previousDbhInInches = previousTreeDiameters[treeIndex]; Assert.IsTrue(dbhInInches >= previousDbhInInches); Assert.IsTrue(dbhInInches <= TestConstant.Maximum.DiameterInInches); } } OrganonTest.Verify(ExpectedTreeChanges.DiameterGrowth, stand, variant); OrganonTest.Verify(calibrationBySpecies); } OrganonStandDensity densityForLookup = new OrganonStandDensity(stand, variant); for (float dbhInInches = 0.5F; dbhInInches <= 101.0F; ++dbhInInches) { float basalAreaLarger = densityForLookup.GetBasalAreaLarger(dbhInInches); Assert.IsTrue(basalAreaLarger >= 0.0F); Assert.IsTrue(basalAreaLarger <= densityForLookup.BasalAreaPerAcre); float crownCompetitionLarger = densityForLookup.GetCrownCompetitionFactorLarger(dbhInInches); Assert.IsTrue(crownCompetitionLarger >= 0.0F); Assert.IsTrue(crownCompetitionLarger <= densityForLookup.CrownCompetitionFactor); } } }
public OrganonStand ToOrganonStand(OrganonConfiguration configuration, int ageInYears, float siteIndex, int maximumTreesInStand) { if (maximumTreesInStand < 1) { throw new ArgumentOutOfRangeException(nameof(maximumTreesInStand)); } // copy trees from plot to Organon stand with default crown ratios // For now, when the stand size is limited this just copies the first n trees encountered rather than subsampling the plot. // Can move this to a Trees.CopyFrom() and Trees.ChangeUnits() if needed. Stand plotAtAge = this.byAge[ageInYears]; int maximumTreesToCopy = Math.Min(plotAtAge.GetTreeRecordCount(), maximumTreesInStand); int treesCopied = 0; StringBuilder plotIDsAsString = new StringBuilder(); foreach (int plotID in this.plotIDs) { plotIDsAsString.Append(plotID.ToString(CultureInfo.InvariantCulture)); } OrganonStand stand = new OrganonStand(ageInYears, siteIndex) { Name = plotIDsAsString.ToString() }; foreach (Trees plotTreesOfSpecies in plotAtAge.TreesBySpecies.Values) { if (stand.TreesBySpecies.TryGetValue(plotTreesOfSpecies.Species, out Trees? standTreesOfSpecies) == false) { int minimumSize = Math.Min(maximumTreesToCopy - treesCopied, plotTreesOfSpecies.Count); standTreesOfSpecies = new Trees(plotTreesOfSpecies.Species, minimumSize, Units.English); stand.TreesBySpecies.Add(plotTreesOfSpecies.Species, standTreesOfSpecies); } for (int treeIndex = 0; treeIndex < plotTreesOfSpecies.Count; ++treeIndex) { int tag = plotTreesOfSpecies.Tag[treeIndex]; float dbhInInches = Constant.InchesPerCentimeter * plotTreesOfSpecies.Dbh[treeIndex]; float heightInFeet = Constant.FeetPerMeter * plotTreesOfSpecies.Height[treeIndex]; float liveExpansionFactor = Constant.HectaresPerAcre * plotTreesOfSpecies.LiveExpansionFactor[treeIndex]; if (Single.IsNaN(dbhInInches) || (dbhInInches <= 0.0F) || Single.IsNaN(heightInFeet) || (heightInFeet <= 0.0F)) { throw new NotSupportedException("Tree " + tag + " has a missing, zero, or negative height or diameter at age " + ageInYears + "."); } standTreesOfSpecies.Add(tag, dbhInInches, heightInFeet, defaultCrownRatio, liveExpansionFactor); if (++treesCopied >= maximumTreesToCopy) { break; // break inner for loop } } if (++treesCopied >= maximumTreesToCopy) { break; // break foreach } } // estimate crown ratio if (configuration.Variant.TreeModel == TreeModel.OrganonSwo) { // TODO: if needed, add support for old index for NWO and SMC Pacific madrone throw new NotImplementedException("Old tree index not computed."); } stand.EnsureSiteIndicesSet(configuration.Variant); stand.SetRedAlderSiteIndexAndGrowthEffectiveAge(); stand.SetSdiMax(configuration); float defaultOldIndex = 0.0F; OrganonStandDensity density = new OrganonStandDensity(stand, configuration.Variant); foreach (Trees treesOfSpecies in stand.TreesBySpecies.Values) { // initialize crown ratio from Organon variant for (int treeIndex = 0; treeIndex < treesOfSpecies.Count; ++treeIndex) { float dbhInInches = treesOfSpecies.Dbh[treeIndex]; float heightInFeet = treesOfSpecies.Height[treeIndex]; float crownCompetitionFactorLarger = density.GetCrownCompetitionFactorLarger(dbhInInches); float heightToCrownBase = configuration.Variant.GetHeightToCrownBase(treesOfSpecies.Species, heightInFeet, dbhInInches, crownCompetitionFactorLarger, density.BasalAreaPerAcre, stand.SiteIndex, stand.HemlockSiteIndex, defaultOldIndex); float crownRatio = (heightInFeet - heightToCrownBase) / heightInFeet; Debug.Assert(crownRatio >= 0.0F); Debug.Assert(crownRatio <= 1.0F); treesOfSpecies.CrownRatio[treeIndex] = crownRatio; } // initialize crown ratio from FVS-PN dubbing // https://www.fs.fed.us/fmsc/ftp/fvs/docs/overviews/FVSpn_Overview.pdf, section 4.3.1 // https://sourceforge.net/p/open-fvs/code/HEAD/tree/trunk/pn/crown.f#l67 // for live > 1.0 inch DBH // estimated crown ratio = d0 + d1 * 100.0 * SDI / SDImax // PSME d0 = 5.666442, d1 = -0.025199 if ((stand.TreesBySpecies.Count != 1) || (treesOfSpecies.Species != FiaCode.PseudotsugaMenziesii)) { throw new NotImplementedException(); } // FVS-PN crown ratio dubbing for Douglas-fir // Resulted in 0.28% less volume than Organon NWO on Malcolm Knapp Nelder 1 at stand age 70. // float qmd = stand.GetQuadraticMeanDiameter(); // float reinekeSdi = density.TreesPerAcre * MathF.Pow(0.1F * qmd, 1.605F); // float reinekeSdiMax = MathF.Exp((stand.A1 - Constant.NaturalLogOf10) / stand.A2); // float meanCrownRatioFvs = 5.666442F - 0.025199F * 100.0F * reinekeSdi / reinekeSdiMax; // Debug.Assert(meanCrownRatioFvs >= 0.0F); // Debug.Assert(meanCrownRatioFvs <= 10.0F); // FVS uses a 0 to 10 range, so 10 = 100% crown ratio // float weibullA = 0.0F; // float weibullB = -0.012061F + 1.119712F * meanCrownRatioFvs; // float weibullC = 3.2126F; // int[] dbhOrder = treesOfSpecies.GetDbhSortOrder(); // for (int treeIndex = 0; treeIndex < treesOfSpecies.Count; ++treeIndex) // { // float dbhFraction = (float)dbhOrder[treeIndex] / (float)treesOfSpecies.Count; // float fvsCrownRatio = weibullA + weibullB * MathV.Pow(-1.0F * MathV.Ln(1.0F - dbhFraction), 1.0F / weibullC); // Debug.Assert(fvsCrownRatio >= 0.0F); // Debug.Assert(fvsCrownRatio <= 10.0F); // treesOfSpecies.CrownRatio[treeIndex] = 0.1F * fvsCrownRatio; // } } // used for checking sensitivity to data order // Ordering not currently advantageous, so disabled for now. // foreach (Trees treesOfSpecies in stand.TreesBySpecies.Values) // { // treesOfSpecies.SortByDbh(); // } return(stand); }