Example #1
0
        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))));
        }
Example #2
0
        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);
                }
            }
        }
Example #3
0
        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);
        }