/// <summary>
        /// Run metabolism
        /// </summary>
        /// <param name="gridCellCohorts">The cohorts in the current grid cell</param>
        /// <param name="gridCellStocks">The stocks in the current grid cell</param>
        /// <param name="actingCohort">The position of the acting cohort in the jagged array of grid cell cohorts</param>
        /// <param name="cellEnvironment">The environment in the current grid cell</param>
        /// <param name="deltas">The sorted list to track changes in biomass and abundance of the acting cohort in this grid cell</param>
        /// <param name="madingleyCohortDefinitions">The definitions for cohort functional groups in the model</param>
        /// <param name="madingleyStockDefinitions">The definitions for stock functional groups in the model</param>
        /// <param name="currentTimestep">The current model time step</param>
        /// <param name="trackProcesses">An instance of ProcessTracker to hold diagnostics for metabolism</param>
        /// <param name="partial">Thread-locked variables</param>
        /// <param name="specificLocations">Whether the model is being run for specific locations</param>
        /// <param name="outputDetail">The level of output detail being used for the current model run</param>
        /// <param name="currentMonth">The current model month</param>
        /// <param name="initialisation">The Madingley Model initialisation</param>
        public void RunEcologicalProcess(GridCellCohortHandler gridCellCohorts, GridCellStockHandler gridCellStocks,
                                         int[] actingCohort, SortedList <string, double[]> cellEnvironment, Dictionary <string, Dictionary <string, double> > deltas,
                                         FunctionalGroupDefinitions madingleyCohortDefinitions, FunctionalGroupDefinitions madingleyStockDefinitions,
                                         uint currentTimestep, ProcessTracker trackProcesses, ref ThreadLockedParallelVariables partial,
                                         Boolean specificLocations, string outputDetail, uint currentMonth, MadingleyModelInitialisation initialisation)
        {
            double Realm = cellEnvironment["Realm"][0];

            if (madingleyCohortDefinitions.GetTraitNames("Heterotroph/Autotroph", gridCellCohorts[actingCohort].FunctionalGroupIndex) == "heterotroph")
            {
                if (madingleyCohortDefinitions.GetTraitNames("Endo/Ectotherm", gridCellCohorts[actingCohort].FunctionalGroupIndex) == "endotherm")
                {
                    Implementations["basic endotherm"].RunMetabolism(gridCellCohorts, gridCellStocks, actingCohort, cellEnvironment, deltas, madingleyCohortDefinitions, madingleyStockDefinitions, currentTimestep, currentMonth);
                }
                else
                {
                    Implementations["basic ectotherm"].RunMetabolism(gridCellCohorts, gridCellStocks, actingCohort, cellEnvironment, deltas, madingleyCohortDefinitions, madingleyStockDefinitions, currentTimestep, currentMonth);
                }
            }

            // If the process tracker is on and output detail is set to high and this cohort has not been merged yet, then record
            // the number of individuals that have died
            if (trackProcesses.TrackProcesses && (outputDetail == "high"))
            {
                trackProcesses.TrackTimestepMetabolism((uint)cellEnvironment["LatIndex"][0],
                                                       (uint)cellEnvironment["LonIndex"][0],
                                                       currentTimestep,
                                                       gridCellCohorts[actingCohort].IndividualBodyMass,
                                                       actingCohort[0],
                                                       cellEnvironment["Temperature"][currentMonth],
                                                       deltas["biomass"]["metabolism"]);
            }
        }
Пример #2
0
        /// <summary>
        /// Calculate the proportion of time for which this cohort could be active and assign it to the cohort's properties
        /// </summary>
        /// <param name="actingCohort">The Cohort for which proportion of time active is being calculated</param>
        /// <param name="cellEnvironment">The environmental information for current grid cell</param>
        /// <param name="madingleyCohortDefinitions">Functional group definitions and code to interrogate the cohorts in current grid cell</param>
        /// <param name="currentTimestep">Current timestep index</param>
        /// <param name="currentMonth">Current month</param>
        public void AssignProportionTimeActive(Cohort actingCohort, SortedList <string, double[]> cellEnvironment,
                                               FunctionalGroupDefinitions madingleyCohortDefinitions, uint currentTimestep, uint currentMonth)
        {
            double Realm = cellEnvironment["Realm"][0];

            //Only work on heterotroph cohorts
            if (madingleyCohortDefinitions.GetTraitNames("Heterotroph/Autotroph", actingCohort.FunctionalGroupIndex) == "heterotroph")
            {
                //Check if this is an endotherm or ectotherm
                Boolean Endotherm = madingleyCohortDefinitions.GetTraitNames("Endo/Ectotherm", actingCohort.FunctionalGroupIndex) == "endotherm";
                if (Endotherm)
                {
                    //Assumes the whole timestep is suitable for endotherms to be active - actual time active is therefore the proportion specified for this functional group.
                    actingCohort.ProportionTimeActive = madingleyCohortDefinitions.GetBiologicalPropertyOneFunctionalGroup("proportion suitable time active", actingCohort.FunctionalGroupIndex);
                }
                else
                {
                    //If ectotherm then use realm specific function
                    if (Realm == 1.0)
                    {
                        actingCohort.ProportionTimeActive = CalculateProportionTimeSuitableTerrestrial(cellEnvironment, currentMonth, Endotherm) *
                                                            madingleyCohortDefinitions.GetBiologicalPropertyOneFunctionalGroup("proportion suitable time active", actingCohort.FunctionalGroupIndex);
                    }
                    else
                    {
                        actingCohort.ProportionTimeActive = CalculateProportionTimeSuitableMarine(cellEnvironment, currentMonth, Endotherm) *
                                                            madingleyCohortDefinitions.GetBiologicalPropertyOneFunctionalGroup("proportion suitable time active", actingCohort.FunctionalGroupIndex);
                    }
                }
            }
        }
Пример #3
0
        /// <summary>
        /// Run ecological processes that operate on stocks within a single grid cell
        /// </summary>
        ///<param name="gridCellStocks">The stocks in the current grid cell</param>
        ///<param name="actingStock">The acting stock</param>
        ///<param name="cellEnvironment">The stocks in the current grid cell</param>
        ///<param name="environmentalDataUnits">List of units associated with the environmental variables</param>
        ///<param name="humanNPPScenario">The human appropriation of NPP scenario to apply</param>
        ///<param name="madingleyStockDefinitions">The functional group definitions for stocks in the model</param>
        ///<param name="currentTimeStep">The current model time step</param>
        ///<param name="burninSteps">The number of time steps to spin the model up for before applying human impacts</param>
        ///<param name="impactSteps">The number of time steps to apply human impacts for</param>
        ///<param name="globalModelTimeStepUnit">The time step unit used in the model</param>
        ///<param name="trackProcesses">Whether to track properties of ecological processes</param>
        ///<param name="tracker">An instance of the ecological process tracker</param>
        ///<param name="globalTracker">An instance of the global process tracker</param>
        ///<param name="currentMonth">The current model month</param>
        ///<param name="outputDetail">The level of detail to use in outputs</param>
        ///<param name="specificLocations">Whether to run the model for specific locations</param>
        ///<param name="impactCell">Whether this cell should have human impacts applied</param>
        public void RunWithinCellEcology(GridCellStockHandler gridCellStocks, int[] actingStock, SortedList <string, double[]> cellEnvironment,
                                         SortedList <string, string> environmentalDataUnits, Tuple <string, double, double> humanNPPScenario,
                                         FunctionalGroupDefinitions madingleyStockDefinitions,
                                         uint currentTimeStep, uint burninSteps, uint impactSteps, uint recoverySteps, uint instantStep, uint numInstantSteps, string globalModelTimeStepUnit, Boolean trackProcesses,
                                         ProcessTracker tracker,
                                         GlobalProcessTracker globalTracker, uint currentMonth,
                                         string outputDetail, bool specificLocations, Boolean impactCell)
        {
            int ScenarioYear;

            if (currentTimeStep < burninSteps)
            {
                ScenarioYear = 0;
            }
            else
            {
                ScenarioYear = (int)Math.Floor((currentTimeStep - burninSteps) / 12.0);
            }



            if (madingleyStockDefinitions.GetTraitNames("Realm", actingStock[0]) == "marine")
            {
                // Run the autotroph processor
                MarineNPPtoAutotrophStock.ConvertNPPToAutotroph(cellEnvironment, gridCellStocks, actingStock, environmentalDataUnits["LandNPP"],
                                                                environmentalDataUnits["OceanNPP"], currentTimeStep, globalModelTimeStepUnit, tracker, globalTracker, outputDetail, specificLocations, currentMonth);
            }
            else if (madingleyStockDefinitions.GetTraitNames("Realm", actingStock[0]) == "terrestrial")
            {
                // Run the dynamic plant model to update the leaf stock for this time step
                double WetMatterNPP = DynamicPlantModel.UpdateLeafStock(cellEnvironment, gridCellStocks, actingStock, currentTimeStep, madingleyStockDefinitions.
                                                                        GetTraitNames("leaf strategy", actingStock[0]).Equals("deciduous"), globalModelTimeStepUnit, tracker, globalTracker, currentMonth,
                                                                        outputDetail, specificLocations);

                double fhanpp = HANPP.RemoveHumanAppropriatedMatter(WetMatterNPP, cellEnvironment, humanNPPScenario, gridCellStocks, actingStock,
                                                                    currentTimeStep, ScenarioYear, burninSteps, impactSteps, recoverySteps, instantStep, numInstantSteps, impactCell, globalModelTimeStepUnit);

                // Apply human appropriation of NPP
                gridCellStocks[actingStock].TotalBiomass += WetMatterNPP * (1.0 - fhanpp);
                if (globalTracker.TrackProcesses)
                {
                    globalTracker.RecordHANPP((uint)cellEnvironment["LatIndex"][0], (uint)cellEnvironment["LonIndex"][0], (uint)actingStock[0],
                                              fhanpp);
                }

                if (gridCellStocks[actingStock].TotalBiomass < 0.0)
                {
                    gridCellStocks[actingStock].TotalBiomass = 0.0;
                }
            }
            else
            {
                Debug.Fail("Stock must be classified as belonging to either the marine or terrestrial realm");
            }
        }
Пример #4
0
        /// <summary>
        /// Initialises predation implementation each time step
        /// </summary>
        /// <param name="gridCellCohorts">The cohorts in the current grid cell</param>
        /// <param name="gridCellStocks">The stocks in the current grid cell</param>
        /// <param name="madingleyCohortDefinitions">The definitions for cohorts in the model</param>
        /// <param name="madingleyStockDefinitions">The definitions for stocks in the model</param>
        /// <remarks>This only works if: a) predation is initialised in every grid cell; and b) if parallelisation is done by latitudinal strips
        /// It is critical to run this every time step</remarks>
        public void InitializeEatingPerTimeStep(GridCellCohortHandler gridCellCohorts, GridCellStockHandler gridCellStocks, FunctionalGroupDefinitions madingleyCohortDefinitions, FunctionalGroupDefinitions madingleyStockDefinitions)
        {
            // Get the functional group indices of all heterotroph cohorts (i.e. potential prey)
            _FunctionalGroupIndicesToEat = madingleyCohortDefinitions.GetFunctionalGroupIndex("Heterotroph/Autotroph", "heterotroph", false);

            // Initialise the vector to hold the number of cohorts in each functional group at the start of the time step
            NumberCohortsPerFunctionalGroupNoNewCohorts = new int[gridCellCohorts.Count];

            // Initialise the jagged arrays to hold the potential and actual numbers of prey eaten in each of the grid cell cohorts
            _AbundancesEaten         = new double[gridCellCohorts.Count][];
            _PotentialAbundanceEaten = new double[gridCellCohorts.Count][];

            // Initialise the vector to identify carnivore cohorts
            _CarnivoreFunctionalGroups = new Boolean[_FunctionalGroupIndicesToEat.Length];
            _OmnivoreFunctionalGroups  = new Boolean[_FunctionalGroupIndicesToEat.Length];
            _PlanktonFunctionalGroups  = new Boolean[_FunctionalGroupIndicesToEat.Length];

            // Loop over rows in the jagged arrays, initialise each vector within the jagged arrays, and calculate the current number of cohorts in
            // each functional group
            for (int i = 0; i < gridCellCohorts.Count; i++)
            {
                // Calculate the current number of cohorts in this functional group
                int NumCohortsThisFG = gridCellCohorts[i].Count;
                NumberCohortsPerFunctionalGroupNoNewCohorts[i] = NumCohortsThisFG;
                // Initialise the jagged arrays
                _AbundancesEaten[i]         = new double[NumberCohortsPerFunctionalGroupNoNewCohorts[i]];
                _PotentialAbundanceEaten[i] = new double[NumberCohortsPerFunctionalGroupNoNewCohorts[i]];
            }

            // Loop over functional groups that are potential prey and determine which are carnivores
            foreach (int FunctionalGroup in FunctionalGroupIndicesToEat)
            {
                _CarnivoreFunctionalGroups[FunctionalGroup] = madingleyCohortDefinitions.GetTraitNames("Nutrition source", FunctionalGroup) == "carnivore";
            }

            foreach (int FunctionalGroup in FunctionalGroupIndicesToEat)
            {
                _OmnivoreFunctionalGroups[FunctionalGroup] = madingleyCohortDefinitions.GetTraitNames("Nutrition source", FunctionalGroup) == "omnivore";
            }

            foreach (int FunctionalGroup in FunctionalGroupIndicesToEat)
            {
                _PlanktonFunctionalGroups[FunctionalGroup] = madingleyCohortDefinitions.GetTraitNames("Mobility", FunctionalGroup) == "planktonic";
            }
        }
        /// <summary>
        /// Run ecological processes that operate on stocks within a single grid cell
        /// </summary>
        ///<param name="gridCellStocks">The stocks in the current grid cell</param>
        ///<param name="actingStock">The acting stock</param>
        ///<param name="cellEnvironment">The stocks in the current grid cell</param>
        ///<param name="environmentalDataUnits">List of units associated with the environmental variables</param>
        ///<param name="humanNPPScenario">The human appropriation of NPP scenario to apply</param>
        ///<param name="madingleyStockDefinitions">The functional group definitions for stocks in the model</param>
        ///<param name="currentTimeStep">The current model time step</param>
        ///<param name="burninSteps">The number of time steps to spin the model up for before applying human impacts</param>
        ///<param name="impactSteps">The number of time steps to apply human impacts for</param>
        ///<param name="globalModelTimeStepUnit">The time step unit used in the model</param>
        ///<param name="trackProcesses">Whether to track properties of ecological processes</param>
        ///<param name="tracker">An instance of the ecological process tracker</param>
        ///<param name="globalTracker">An instance of the global process tracker</param>
        ///<param name="currentMonth">The current model month</param>
        ///<param name="outputDetail">The level of detail to use in outputs</param>
        ///<param name="specificLocations">Whether to run the model for specific locations</param>
        ///<param name="impactCell">Whether this cell should have human impacts applied</param>
        public void RunWithinCellEcology(GridCellStockHandler gridCellStocks, int[] actingStock, SortedList<string, double[]> cellEnvironment,
            SortedList<string, string> environmentalDataUnits, Madingley.Common.ScenarioParameter humanNPPScenario,
            FunctionalGroupDefinitions madingleyStockDefinitions,
            uint currentTimeStep, uint burninSteps, uint impactSteps, uint recoverySteps, uint instantStep, uint numInstantSteps, string globalModelTimeStepUnit, Boolean trackProcesses,
            ProcessTracker tracker,
            GlobalProcessTracker globalTracker, uint currentMonth,
            string outputDetail, bool specificLocations, Boolean impactCell)
        {
            if (madingleyStockDefinitions.GetTraitNames("Realm", actingStock[0]) == "marine")
            {
                // Run the autotroph processor
                MarineNPPtoAutotrophStock.ConvertNPPToAutotroph(cellEnvironment, gridCellStocks, actingStock, environmentalDataUnits["LandNPP"],
                    environmentalDataUnits["OceanNPP"], currentTimeStep, globalModelTimeStepUnit, tracker, globalTracker, outputDetail, specificLocations, currentMonth);
            }
            else if (madingleyStockDefinitions.GetTraitNames("Realm", actingStock[0]) == "terrestrial")
            {

                // Run the dynamic plant model to update the leaf stock for this time step
                double WetMatterNPP = DynamicPlantModel.UpdateLeafStock(cellEnvironment, gridCellStocks, actingStock, currentTimeStep, madingleyStockDefinitions.
                    GetTraitNames("leaf strategy", actingStock[0]).Equals("deciduous"), globalModelTimeStepUnit, tracker, globalTracker, currentMonth,
                    outputDetail, specificLocations);
                /// <summary>

                double fhanpp = HANPP.RemoveHumanAppropriatedMatter(WetMatterNPP, cellEnvironment, humanNPPScenario, gridCellStocks, actingStock,
                    currentTimeStep, burninSteps, impactSteps, recoverySteps, instantStep, numInstantSteps, impactCell, globalModelTimeStepUnit);

                // Apply human appropriation of NPP
                gridCellStocks[actingStock].TotalBiomass += WetMatterNPP * (1.0 - fhanpp);

                if (globalTracker.TrackProcesses)
                {
                    globalTracker.RecordHANPP((uint)cellEnvironment["LatIndex"][0], (uint)cellEnvironment["LonIndex"][0], (uint)actingStock[0],
                        fhanpp);
                }

                if (gridCellStocks[actingStock].TotalBiomass < 0.0) gridCellStocks[actingStock].TotalBiomass = 0.0;

            }
            else
            {
                Debug.Fail("Stock must be classified as belonging to either the marine or terrestrial realm");
            }
        }
        /// <summary>
        /// Run metabolism
        /// </summary>
        /// <param name="gridCellCohorts">The cohorts in the current grid cell</param>
        /// <param name="gridCellStocks">The stocks in the current grid cell</param>
        /// <param name="actingCohort">The position of the acting cohort in the jagged array of grid cell cohorts</param>
        /// <param name="cellEnvironment">The environment in the current grid cell</param>
        /// <param name="deltas">The sorted list to track changes in biomass and abundance of the acting cohort in this grid cell</param>
        /// <param name="madingleyCohortDefinitions">The definitions for cohort functional groups in the model</param>
        /// <param name="madingleyStockDefinitions">The definitions for stock functional groups in the model</param>
        /// <param name="currentTimestep">The current model time step</param>
        /// <param name="trackProcesses">An instance of ProcessTracker to hold diagnostics for metabolism</param>
        /// <param name="partial">Thread-locked variables</param>
        /// <param name="specificLocations">Whether the model is being run for specific locations</param>
        /// <param name="outputDetail">The level of output detail being used for the current model run</param>
        /// <param name="currentMonth">The current model month</param>
        /// <param name="initialisation">The Madingley Model initialisation</param>
        public void RunEcologicalProcess(GridCellCohortHandler gridCellCohorts, GridCellStockHandler gridCellStocks, 
            int[] actingCohort, SortedList<string, double[]> cellEnvironment, Dictionary<string, Dictionary<string, double>> deltas, 
            FunctionalGroupDefinitions madingleyCohortDefinitions, FunctionalGroupDefinitions madingleyStockDefinitions, 
            uint currentTimestep, ProcessTracker trackProcesses, ref ThreadLockedParallelVariables partial,
            Boolean specificLocations, string outputDetail, uint currentMonth, MadingleyModelInitialisation initialisation)
        {
            double Realm = cellEnvironment["Realm"][0];
            if (madingleyCohortDefinitions.GetTraitNames("Heterotroph/Autotroph", gridCellCohorts[actingCohort].FunctionalGroupIndex) == "heterotroph")
            {
                if (madingleyCohortDefinitions.GetTraitNames("Endo/Ectotherm", gridCellCohorts[actingCohort].FunctionalGroupIndex) == "endotherm")
                {

                        Implementations["basic endotherm"].RunMetabolism(gridCellCohorts, gridCellStocks, actingCohort, cellEnvironment, deltas, madingleyCohortDefinitions, madingleyStockDefinitions, currentTimestep, currentMonth);
                }
                else
                {
                        Implementations["basic ectotherm"].RunMetabolism(gridCellCohorts, gridCellStocks, actingCohort, cellEnvironment, deltas, madingleyCohortDefinitions, madingleyStockDefinitions, currentTimestep, currentMonth);

                }

            }

            // If the process tracker is on and output detail is set to high and this cohort has not been merged yet, then record
            // the number of individuals that have died
            if (trackProcesses.TrackProcesses && (outputDetail == "high"))
            {

                trackProcesses.TrackTimestepMetabolism((uint)cellEnvironment["LatIndex"][0],
                                                (uint)cellEnvironment["LonIndex"][0],
                                                currentTimestep,
                                                gridCellCohorts[actingCohort].IndividualBodyMass,
                                                actingCohort[0],
                                                cellEnvironment["Temperature"][currentMonth],
                                                deltas["biomass"]["metabolism"]);

            }
        }
Пример #7
0
        /// <summary>
        /// Run dispersal
        /// </summary>
        public void RunCrossGridCellEcologicalProcess(uint[] cellIndex, ModelGrid gridForDispersal, bool dispersalOnly, FunctionalGroupDefinitions madingleyCohortDefinitions, FunctionalGroupDefinitions madingleyStockDefinitions, uint currentMonth)
        {

            // Create a temporary handler for grid cell cohorts
            GridCellCohortHandler WorkingGridCellCohorts;

            // Get the lat and lon indices
            uint ii = cellIndex[0];
            uint jj = cellIndex[1];

            // A boolean to check that the environmental layer exists
            bool varExists;

            // Check to see if the cell is marine
            double CellRealm = gridForDispersal.GetEnviroLayer("Realm", 0, ii, jj, out varExists);

            // Go through all of the cohorts in turn and see if they disperse
            WorkingGridCellCohorts = gridForDispersal.GetGridCellCohorts(ii, jj);

            // Loop through cohorts, and perform dispersal according to cohort type and status
            for (int kk = 0; kk < WorkingGridCellCohorts.Count; kk++)
            {
                // Work through the list of cohorts
                for (int ll = 0; ll < WorkingGridCellCohorts[kk].Count; ll++)
                {
                    // Check to see if the cell is marine and the cohort type is planktonic
                    if (CellRealm == 2.0 &&
                        ((madingleyCohortDefinitions.GetTraitNames("Mobility", WorkingGridCellCohorts[kk][ll].FunctionalGroupIndex) == "planktonic") || (WorkingGridCellCohorts[kk][ll].IndividualBodyMass <= PlanktonThreshold)))
                    {
                        // Run advective dispersal
                        Implementations["basic advective dispersal"].RunDispersal(cellIndex, gridForDispersal, WorkingGridCellCohorts[kk][ll], kk, ll, currentMonth);

                    }
                    // Otherwise, if mature do responsive dispersal
                    else if (WorkingGridCellCohorts[kk][ll].MaturityTimeStep < uint.MaxValue)
                    {
                        // Run diffusive dispersal 
                        Implementations["basic responsive dispersal"].RunDispersal(cellIndex, gridForDispersal, WorkingGridCellCohorts[kk][ll], kk, ll, currentMonth);
                    }
                    // If the cohort is immature, run diffusive dispersal
                    else
                    {
                        Implementations["basic diffusive dispersal"].RunDispersal(cellIndex, gridForDispersal, WorkingGridCellCohorts[kk][ll], kk, ll, currentMonth);
                    }
                }
            }
        }
Пример #8
0
        /// <summary>
        /// Run dispersal
        /// </summary>
        public void RunCrossGridCellEcologicalProcess(uint[] cellIndex, ModelGrid gridForDispersal, bool dispersalOnly, FunctionalGroupDefinitions madingleyCohortDefinitions, FunctionalGroupDefinitions madingleyStockDefinitions, uint currentMonth)
        {
            // Create a temporary handler for grid cell cohorts
            GridCellCohortHandler WorkingGridCellCohorts;

            // Get the lat and lon indices
            uint ii = cellIndex[0];
            uint jj = cellIndex[1];

            // A boolean to check that the environmental layer exists
            bool varExists;

            // Check to see if the cell is marine
            double CellRealm = gridForDispersal.GetEnviroLayer("Realm", 0, ii, jj, out varExists);

            // Go through all of the cohorts in turn and see if they disperse
            WorkingGridCellCohorts = gridForDispersal.GetGridCellCohorts(ii, jj);

            // Loop through cohorts, and perform dispersal according to cohort type and status
            for (int kk = 0; kk < WorkingGridCellCohorts.Count; kk++)
            {
                // Work through the list of cohorts
                for (int ll = 0; ll < WorkingGridCellCohorts[kk].Count; ll++)
                {
                    // Check to see if the cell is marine and the cohort type is planktonic
                    if (CellRealm == 2.0 &&
                        ((madingleyCohortDefinitions.GetTraitNames("Mobility", WorkingGridCellCohorts[kk][ll].FunctionalGroupIndex) == "planktonic") || (WorkingGridCellCohorts[kk][ll].IndividualBodyMass <= PlanktonThreshold)))
                    {
                        // Run advective dispersal
                        Implementations["basic advective dispersal"].RunDispersal(cellIndex, gridForDispersal, WorkingGridCellCohorts[kk][ll], kk, ll, currentMonth);
                    }
                    // Otherwise, if mature do responsive dispersal
                    else if (WorkingGridCellCohorts[kk][ll].MaturityTimeStep < uint.MaxValue)
                    {
                        // Run diffusive dispersal
                        Implementations["basic responsive dispersal"].RunDispersal(cellIndex, gridForDispersal, WorkingGridCellCohorts[kk][ll], kk, ll, currentMonth);
                    }
                    // If the cohort is immature, run diffusive dispersal
                    else
                    {
                        Implementations["basic diffusive dispersal"].RunDispersal(cellIndex, gridForDispersal, WorkingGridCellCohorts[kk][ll], kk, ll, currentMonth);
                    }
                }
            }
        }
        /// <summary>
        /// Run reproduction
        /// </summary>
        /// <param name="gridCellCohorts">The cohorts in the current grid cell</param>
        /// <param name="gridCellStocks">The stocks in the current grid cell</param>
        /// <param name="actingCohort">The position of the acting cohort in the jagged array of grid cell cohorts</param>
        /// <param name="cellEnvironment">The environment in the current grid cell</param>
        /// <param name="deltas">The sorted list to track changes in biomass and abundance of the acting cohort in this grid cell</param>
        /// <param name="madingleyCohortDefinitions">The definitions for cohort functional groups in the model</param>
        /// <param name="madingleyStockDefinitions">The definitions for stock functional groups in the model</param>
        /// <param name="currentTimeStep">The current model time step</param>
        /// <param name="processTracker">An instance of ProcessTracker to hold diagnostics for eating</param>
        /// <param name="partial">Thread-locked variables for the parallelised version</param>
        /// <param name="specificLocations">Whether the model is being run for specific locations</param>
        /// <param name="outputDetail">The level of output detail being used for this model run</param>
        /// <param name="currentMonth">The current model month</param>
        /// <param name="initialisation">The Madingley Model initialisation</param>
        public void RunEcologicalProcess(GridCellCohortHandler gridCellCohorts, GridCellStockHandler gridCellStocks,
                                         int[] actingCohort, SortedList <string, double[]> cellEnvironment, Dictionary <string, Dictionary <string, double> > deltas,
                                         FunctionalGroupDefinitions madingleyCohortDefinitions, FunctionalGroupDefinitions madingleyStockDefinitions,
                                         uint currentTimeStep, ProcessTracker processTracker, ref ThreadLockedParallelVariables partial,
                                         Boolean specificLocations, string outputDetail, uint currentMonth, MadingleyModelInitialisation initialisation)
        {
            // Holds the reproductive strategy of a cohort
            bool _Iteroparous = madingleyCohortDefinitions.GetTraitNames("reproductive strategy", actingCohort[0]) == "iteroparity";

            // Assign mass to reproductive potential
            Implementations["reproduction basic"].RunReproductiveMassAssignment(gridCellCohorts, gridCellStocks, actingCohort, cellEnvironment, deltas,
                                                                                madingleyCohortDefinitions, madingleyStockDefinitions, currentTimeStep, processTracker);

            // Run reproductive events. Note that we can't skip juveniles here as they could conceivably grow to adulthood and get enough biomass to reproduce in a single time step
            // due to other ecological processes
            Implementations["reproduction basic"].RunReproductionEvents(gridCellCohorts, gridCellStocks, actingCohort, cellEnvironment,
                                                                        deltas, madingleyCohortDefinitions, madingleyStockDefinitions, currentTimeStep, processTracker, ref partial, _Iteroparous, currentMonth);
        }
        /// <summary>
        /// Run reproduction
        /// </summary>
        /// <param name="gridCellCohorts">The cohorts in the current grid cell</param>
        /// <param name="gridCellStocks">The stocks in the current grid cell</param>
        /// <param name="actingCohort">The position of the acting cohort in the jagged array of grid cell cohorts</param>
        /// <param name="cellEnvironment">The environment in the current grid cell</param>
        /// <param name="deltas">The sorted list to track changes in biomass and abundance of the acting cohort in this grid cell</param>
        /// <param name="madingleyCohortDefinitions">The definitions for cohort functional groups in the model</param>
        /// <param name="madingleyStockDefinitions">The definitions for stock functional groups in the model</param>
        /// <param name="currentTimeStep">The current model time step</param>
        /// <param name="processTracker">An instance of ProcessTracker to hold diagnostics for eating</param>
        /// <param name="partial">Thread-locked variables for the parallelised version</param>
        /// <param name="specificLocations">Whether the model is being run for specific locations</param>
        /// <param name="outputDetail">The level of output detail being used for this model run</param>
        /// <param name="currentMonth">The current model month</param>
        /// <param name="initialisation">The Madingley Model initialisation</param>
        public void RunEcologicalProcess(GridCellCohortHandler gridCellCohorts, GridCellStockHandler gridCellStocks, 
            int[] actingCohort, SortedList<string, double[]> cellEnvironment, Dictionary<string,Dictionary<string,double>> deltas , 
            FunctionalGroupDefinitions madingleyCohortDefinitions, FunctionalGroupDefinitions madingleyStockDefinitions,
            uint currentTimeStep, ProcessTracker processTracker, ref ThreadLockedParallelVariables partial,
            Boolean specificLocations, string outputDetail, uint currentMonth, MadingleyModelInitialisation initialisation)
        {
            // Holds the reproductive strategy of a cohort
            bool _Iteroparous = madingleyCohortDefinitions.GetTraitNames("reproductive strategy", actingCohort[0])=="iteroparity";

            // Assign mass to reproductive potential
            Implementations["reproduction basic"].RunReproductiveMassAssignment(gridCellCohorts, gridCellStocks, actingCohort, cellEnvironment, deltas,
                madingleyCohortDefinitions, madingleyStockDefinitions, currentTimeStep, processTracker);

            // Run reproductive events. Note that we can't skip juveniles here as they could conceivably grow to adulthood and get enough biomass to reproduce in a single time step
            // due to other ecological processes
            Implementations["reproduction basic"].RunReproductionEvents(gridCellCohorts, gridCellStocks, actingCohort, cellEnvironment,
                    deltas, madingleyCohortDefinitions, madingleyStockDefinitions, currentTimeStep, processTracker, ref partial, _Iteroparous, currentMonth);
        }
        /// <summary>
        /// Sets up the outputs associated with the high level of output detail
        /// </summary>
        /// <param name="ecosystemModelGrid">The model grid</param>
        /// <param name="cellIndices">The indices of active cells in the model grid</param>
        /// <param name="cellNumber">The index of the current cell in the list of active cells</param>
        /// <param name="cohortFunctionalGroupDefinitions">The functional group definitions for cohorts in the model</param>
        /// <param name="marineCell">Whether the current cell is a marine cell</param>
        private void SetUpHighLevelOutputs(ModelGrid ecosystemModelGrid, List<uint[]> cellIndices, int cellNumber,
            FunctionalGroupDefinitions cohortFunctionalGroupDefinitions, Boolean marineCell)
        {
            // Create an SDS object for outputs by mass bin
            // MassBinsOutput = SDSCreator.CreateSDS("netCDF", "MassBins" + _OutputSuffix, _OutputPath);
            MassBinsOutputMemory = SDSCreator.CreateSDSInMemory(true);

            // Add relevant output variables to the mass bin output file
            string[] MassBinDimensions = { "Time step", "Mass bin" };
            string[] DoubleMassBinDimensions = new string[] { "Adult Mass bin", "Juvenile Mass bin", "Time step" };

            if (OutputMetrics)
            {
                DataConverter.AddVariable(MassBinsOutputMemory, "Trophic Index Distribution", 2, new string[] {"Time step","Trophic Index Bins"}, ecosystemModelGrid.GlobalMissingValue, TimeSteps, Metrics.TrophicIndexBinValues);
            }

            if (marineCell)
            {
                foreach (string TraitValue in CohortTraitIndicesMarine.Keys)
                {
                    DataConverter.AddVariable(MassBinsOutputMemory, "Log " + TraitValue + " abundance in mass bins", 2, MassBinDimensions, ecosystemModelGrid.GlobalMissingValue, TimeSteps, MassBins);
                    DataConverter.AddVariable(MassBinsOutputMemory, "Log " + TraitValue + " biomass in mass bins", 2, MassBinDimensions, ecosystemModelGrid.GlobalMissingValue, TimeSteps, MassBins);
                    DataConverter.AddVariable(MassBinsOutputMemory, "Log " + TraitValue + " abundance in juvenile vs adult bins", 3, DoubleMassBinDimensions, ecosystemModelGrid.GlobalMissingValue, MassBins, MassBins, TimeSteps);
                    DataConverter.AddVariable(MassBinsOutputMemory, "Log " + TraitValue + " biomass in juvenile vs adult bins", 3, DoubleMassBinDimensions, ecosystemModelGrid.GlobalMissingValue, MassBins, MassBins, TimeSteps);
                }
            }
            else
            {
                foreach (string TraitValue in CohortTraitIndices.Keys)
                {
                    DataConverter.AddVariable(MassBinsOutputMemory, "Log " + TraitValue + " abundance in mass bins", 2, MassBinDimensions, ecosystemModelGrid.GlobalMissingValue, TimeSteps, MassBins);
                    DataConverter.AddVariable(MassBinsOutputMemory, "Log " + TraitValue + " biomass in mass bins", 2, MassBinDimensions, ecosystemModelGrid.GlobalMissingValue, TimeSteps, MassBins);
                    DataConverter.AddVariable(MassBinsOutputMemory, "Log " + TraitValue + " abundance in juvenile vs adult bins", 3, DoubleMassBinDimensions, ecosystemModelGrid.GlobalMissingValue, MassBins, MassBins, TimeSteps);
                    DataConverter.AddVariable(MassBinsOutputMemory, "Log " + TraitValue + " biomass in juvenile vs adult bins", 3, DoubleMassBinDimensions, ecosystemModelGrid.GlobalMissingValue, MassBins, MassBins, TimeSteps);
                }
            }

            // Create an SDS object in memory for tracked cohorts outputs
            // TrackedCohortsOutput = SDSCreator.CreateSDS("netCDF", "TrackedCohorts" + _OutputSuffix, _OutputPath);
            TrackedCohortsOutputMemory = SDSCreator.CreateSDSInMemory(true);

            // Initialise list to hold tracked cohorts
            TrackedCohorts = new List<uint>();

            // Identify cohorts to track
            GridCellCohortHandler TempCohorts = null;
            bool FoundCohorts = false;

            // Get a local copy of the cohorts in the grid cell
            TempCohorts = ecosystemModelGrid.GetGridCellCohorts(cellIndices[cellNumber][0], cellIndices[cellNumber][1]);

            // Loop over functional groups and check whether any cohorts exist in this grid cell
            foreach (var CohortList in TempCohorts)
            {
                if (CohortList.Count > 0)
                {
                    FoundCohorts = true;
                    break;
                }
            }

            // If there are some cohorts in the grid cell, then setup the tracked cohorts
            if (FoundCohorts)
            {
                // Initialise stream writer to hold details of tracked cohorts
                StreamWriter sw = new StreamWriter(_OutputPath + "TrackedCohortProperties" + _OutputSuffix + ".txt");
                sw.WriteLine("Output ID\tCohort ID\tFunctional group index\tNutrition source\tDiet\tRealm\tMobility\tJuvenile mass\tAdult mass");

                // Counter for tracked cohorts
                int TrackedCohortCounter = 0;

                for (int i = 0; i < TempCohorts.Count; i++)
                {
                    if (TempCohorts[i].Count > 0)
                    {
                        for (int j = 0; j < TempCohorts[i].Count; j++)
                        {
                            // Write out properties of the selected cohort
                            sw.WriteLine(Convert.ToString(TrackedCohortCounter) + '\t' + Convert.ToString(TempCohorts[i][j].CohortID[0]) + '\t' + i + '\t' +
                                cohortFunctionalGroupDefinitions.GetTraitNames("Nutrition source", i) + '\t' + cohortFunctionalGroupDefinitions.
                                GetTraitNames("Diet", i) + '\t' + cohortFunctionalGroupDefinitions.GetTraitNames("Realm", i) + '\t' +
                                cohortFunctionalGroupDefinitions.GetTraitNames("Mobility", i) + '\t' + TempCohorts[i][j].JuvenileMass + '\t' +
                                TempCohorts[i][j].AdultMass);

                            // Add the ID of the cohort to the list of tracked cohorts
                            TrackedCohorts.Add(TempCohorts[i][j].CohortID[0]);

                            // Increment the counter of tracked cohorts
                            TrackedCohortCounter++;
                        }
                    }
                }

                // Generate an array of floating points to index the tracked cohorts in the output file
                float[] OutTrackedCohortIDs = new float[TrackedCohortCounter];
                for (int i = 0; i < TrackedCohortCounter; i++)
                {
                    OutTrackedCohortIDs[i] = i;
                }

                // Set up outputs for tracked cohorts
                string[] TrackedCohortsDimensions = { "Time step", "Cohort ID" };

                // Add output variables for the tracked cohorts output
                DataConverter.AddVariable(TrackedCohortsOutputMemory, "Individual body mass", 2, TrackedCohortsDimensions,
                ecosystemModelGrid.GlobalMissingValue, TimeSteps, OutTrackedCohortIDs);
                DataConverter.AddVariable(TrackedCohortsOutputMemory, "Number of individuals", 2, TrackedCohortsDimensions,
                ecosystemModelGrid.GlobalMissingValue, TimeSteps, OutTrackedCohortIDs);

                // Dispose of the streamwriter
                sw.Dispose();
            }

                // Get a list of all possible combinations of trait values as a jagged array
            string[][] TraitValueSearch;

                if (marineCell)
                    TraitValueSearch = CalculateAllCombinations(CohortTraitValuesMarine[CohortTraits[0]], CohortTraitValuesMarine[CohortTraits[1]]);
                else
                    TraitValueSearch = CalculateAllCombinations(CohortTraitValues[CohortTraits[0]], CohortTraitValues[CohortTraits[1]]);

                // Add the functional group indices of these trait combinations to the list of indices of the trait values to consider,
                // keyed with a concatenated version of the trait values
                string TraitValueJoin = "";
                string[] TimeDimension = { "Time step" };
                for (int i = 0; i < TraitValueSearch.Count(); i++)
                {
                    TraitValueJoin = "";
                    foreach (string TraitValue in TraitValueSearch[i])
                    {
                        TraitValueJoin += TraitValue + " ";
                    }

                    if (marineCell)
                    {
                        // Only add indices of marine functional groups
                        int[] TempIndices = cohortFunctionalGroupDefinitions.GetFunctionalGroupIndex(CohortTraits, TraitValueSearch[i], true);
                        Boolean[] TempIndices2 = new Boolean[TempIndices.GetLength(0)];
                        for (int ii = 0; ii < TempIndices.GetLength(0); ii++)
                        {
                            if (cohortFunctionalGroupDefinitions.GetTraitNames("Realm", TempIndices[ii]).Equals("Marine", StringComparison.OrdinalIgnoreCase))
                            {
                                TempIndices2[ii] = true;
                            }
                        }

                        // Extract only the indices which are marine
                        int[] TempIndices3 = Enumerable.Range(0, TempIndices2.Length).Where(zz => TempIndices2[zz]).ToArray();

                        if (TempIndices3.Length > 0)
                        {
                            // Extract the values at these indices
                            for (int ii = 0; ii < TempIndices3.Length; ii++)
                            {
                                TempIndices3[ii] = TempIndices[TempIndices3[ii]];
                            }

                            // Add in the indices for this functional group and this realm
                            CohortTraitIndices.Add(TraitValueJoin, TempIndices3);

                            DataConverter.AddVariable(BasicOutputMemory, TraitValueJoin + " density", "Individuals / km^2", 1, TimeDimension, ecosystemModelGrid.GlobalMissingValue, TimeSteps);
                            DataConverter.AddVariable(BasicOutputMemory, TraitValueJoin + " biomass density", "Kg / km^2", 1, TimeDimension, ecosystemModelGrid.GlobalMissingValue, TimeSteps);
                            DataConverter.AddVariable(MassBinsOutputMemory, "Log " + TraitValueJoin + " abundance in mass bins", 2, MassBinDimensions, ecosystemModelGrid.GlobalMissingValue, TimeSteps, MassBins);
                            DataConverter.AddVariable(MassBinsOutputMemory, "Log " + TraitValueJoin + " biomass in mass bins", 2, MassBinDimensions, ecosystemModelGrid.GlobalMissingValue, TimeSteps, MassBins);
                            DataConverter.AddVariable(MassBinsOutputMemory, "Log " + TraitValueJoin + " abundance in juvenile vs adult bins", 3, DoubleMassBinDimensions, ecosystemModelGrid.GlobalMissingValue, MassBins, MassBins, TimeSteps);
                            DataConverter.AddVariable(MassBinsOutputMemory, "Log " + TraitValueJoin + " biomass in juvenile vs adult bins", 3, DoubleMassBinDimensions, ecosystemModelGrid.GlobalMissingValue, MassBins, MassBins, TimeSteps);

                            TotalBiomassDensitiesOut.Add(TraitValueJoin, 0.0);
                            TotalDensitiesOut.Add(TraitValueJoin, 0.0);
                        }
                    }
                    else
                    {
                        // Only add indices of terrestrial functional groups
                        int[] TempIndices = cohortFunctionalGroupDefinitions.GetFunctionalGroupIndex(CohortTraits, TraitValueSearch[i], true);
                        Boolean[] TempIndices2 = new Boolean[TempIndices.GetLength(0)];
                        for (int ii = 0; ii < TempIndices.GetLength(0); ii++)
                        {
                            if (cohortFunctionalGroupDefinitions.GetTraitNames("Realm", TempIndices[ii]).Equals("Terrestrial", StringComparison.OrdinalIgnoreCase))
                            {
                                TempIndices2[ii] = true;
                            }
                        }

                        // Extract only the indices which are terrestrial
                        int[] TempIndices3 = Enumerable.Range(0, TempIndices2.Length).Where(zz => TempIndices2[zz]).ToArray();

                        if (TempIndices3.Length > 0)
                        {
                            // Extract the values at these indices
                            for (int ii = 0; ii < TempIndices3.Length; ii++)
                            {
                                TempIndices3[ii] = TempIndices[TempIndices3[ii]];
                            }

                            // Add in the indices for this functional group and this realm
                            CohortTraitIndices.Add(TraitValueJoin, TempIndices3);

                            DataConverter.AddVariable(BasicOutputMemory, TraitValueJoin + " density", "Individuals / km^2", 1, TimeDimension, ecosystemModelGrid.GlobalMissingValue, TimeSteps);
                            DataConverter.AddVariable(BasicOutputMemory, TraitValueJoin + " biomass density", "Kg / km^2", 1, TimeDimension, ecosystemModelGrid.GlobalMissingValue, TimeSteps);
                            DataConverter.AddVariable(MassBinsOutputMemory, "Log " + TraitValueJoin + " abundance in mass bins", 2, MassBinDimensions, ecosystemModelGrid.GlobalMissingValue, TimeSteps, MassBins);
                            DataConverter.AddVariable(MassBinsOutputMemory, "Log " + TraitValueJoin + " biomass in mass bins", 2, MassBinDimensions, ecosystemModelGrid.GlobalMissingValue, TimeSteps, MassBins);
                            DataConverter.AddVariable(MassBinsOutputMemory, "Log " + TraitValueJoin + " abundance in juvenile vs adult bins", 3, DoubleMassBinDimensions, ecosystemModelGrid.GlobalMissingValue, MassBins, MassBins, TimeSteps);
                            DataConverter.AddVariable(MassBinsOutputMemory, "Log " + TraitValueJoin + " biomass in juvenile vs adult bins", 3, DoubleMassBinDimensions, ecosystemModelGrid.GlobalMissingValue, MassBins, MassBins, TimeSteps);

                            TotalBiomassDensitiesOut.Add(TraitValueJoin, 0.0);
                            TotalDensitiesOut.Add(TraitValueJoin, 0.0);
                        }
                    }
                }
        }
        /// <summary>
        /// Seed grid cell with stocks, as specified in the model input files
        /// </summary>
        /// <param name="functionalGroups">A reference to the stock functional group handler</param>
        /// <param name="cellEnvironment">The environment in the grid cell</param>
        /// <param name="globalDiagnostics">A list of global diagnostic variables for the model grid</param>
        private void SeedGridCellStocks(ref FunctionalGroupDefinitions functionalGroups, ref SortedList<string, double[]> 
            cellEnvironment, SortedList<string, double> globalDiagnostics)
        {
            // Set the seed for the random number generator from the system time
            RandomNumberGenerator.SetSeedFromSystemTime();

            Stock NewStock;

            // Define local variables
            int[] FunctionalGroupsToUse;

            // Get the individual body masses for organisms in each stock functional group
            double[] IndividualMass = functionalGroups.GetBiologicalPropertyAllFunctionalGroups("individual mass");

            // Check which realm the cell is in
            if (cellEnvironment["Realm"][0] == 1.0 && _CellEnvironment["Precipitation"][0] != _CellEnvironment["Missing Value"][0] && _CellEnvironment["Temperature"][0] != _CellEnvironment["Missing Value"][0])
            {
                // Get the indices of all terrestrial functional groups
                FunctionalGroupsToUse = functionalGroups.GetFunctionalGroupIndex("realm", "terrestrial", true);
            }
            else if (cellEnvironment["Realm"][0] == 2.0 && _CellEnvironment["NPP"][0] != _CellEnvironment["Missing Value"][0])
            {
                // Get the indices of all marine functional groups
                FunctionalGroupsToUse = functionalGroups.GetFunctionalGroupIndex("realm", "marine", true);
            }
            else
            {
                // For cells without a realm designation, no functional groups will be used
                FunctionalGroupsToUse = new int[0];
            }

            // Loop over all functional groups in the model
            for (int FunctionalGroup = 0; FunctionalGroup < functionalGroups.GetNumberOfFunctionalGroups(); FunctionalGroup++)
            {
                // Create a new list to hold the stocks in the grid cell
                _GridCellStocks[FunctionalGroup] = new List<Stock>();

                // If it is a functional group that corresponds to the current realm, then seed the stock
                if (FunctionalGroupsToUse.Contains(FunctionalGroup))
                {
                    if (_CellEnvironment["Realm"][0] == 1.0)
                    {
                        // An instance of the terrestrial carbon model class
                        RevisedTerrestrialPlantModel PlantModel = new RevisedTerrestrialPlantModel();

                        // Calculate predicted leaf mass at equilibrium for this stock
                        double LeafMass = PlantModel.CalculateEquilibriumLeafMass(_CellEnvironment, functionalGroups.GetTraitNames("leaf strategy", FunctionalGroup) == "deciduous");

                        // Initialise the new stock with the relevant properties
                        NewStock = new Stock((byte)FunctionalGroup, IndividualMass[FunctionalGroup], LeafMass);

                        // Add the new stock to the list of grid cell stocks
                        _GridCellStocks[FunctionalGroup].Add(NewStock);

                        // Increment the variable tracking the total number of stocks in the model
                        globalDiagnostics["NumberOfStocksInModel"]++;

                    }
                    else if (FunctionalGroupsToUse.Contains(FunctionalGroup))
                    {
                        // Initialise the new stock with the relevant properties
                        NewStock = new Stock((byte)FunctionalGroup, IndividualMass[FunctionalGroup], 1e12);

                        // Add the new stock to the list of grid cell stocks
                        _GridCellStocks[FunctionalGroup].Add(NewStock);

                        // Increment the variable tracking the total number of stocks in the model
                        globalDiagnostics["NumberOfStocksInModel"]++;

                    }
                    else
                    {
                    }

                }

            }
        }
        /// <summary>
        /// Set up the necessary architecture for generating outputs arranged by trait value
        /// </summary>
        /// <param name="cohortFunctionalGroupDefinitions">Functional group definitions for cohorts in the model</param>
        /// <param name="stockFunctionalGroupDefinitions">Functional group definitions for stocks in the model</param>
        /// <param name="marineCell">Whether the current cell is a marine cell</param>
        private void InitialiseTraitBasedOutputs(FunctionalGroupDefinitions cohortFunctionalGroupDefinitions, FunctionalGroupDefinitions 
            stockFunctionalGroupDefinitions, Boolean marineCell)
        {
            // Define the cohort traits that will be used to separate outputs
            CohortTraits = new string[2] { "Nutrition source", "Endo/Ectotherm"};

            // Declare a sorted dictionary to hold all unique trait values
            CohortTraitValues = new SortedDictionary<string, string[]>();

            // Declare a sorted dictionary to hold all unique trait values for marine systems
            CohortTraitValuesMarine = new SortedDictionary<string, string[]>();

            // Get the list of functional group indices corresponding to each unique trait value
            if (marineCell)
            {
                // Add all unique trait values to the sorted dictionary
                foreach (string Trait in CohortTraits)
                {
                    CohortTraitValuesMarine.Add(Trait, cohortFunctionalGroupDefinitions.GetUniqueTraitValues(Trait));
                }

                foreach (string Trait in CohortTraits)
                {
                    foreach (string TraitValue in CohortTraitValuesMarine[Trait])
                    {
                        // Only add indices of marine functional groups
                        int[] TempIndices = cohortFunctionalGroupDefinitions.GetFunctionalGroupIndex(Trait, TraitValue, false);
                        Boolean[] TempIndices2 = new Boolean[TempIndices.GetLength(0)];
                        for (int ii = 0; ii < TempIndices.GetLength(0); ii++)
                        {
                            if (cohortFunctionalGroupDefinitions.GetTraitNames("Realm", TempIndices[ii]).Equals("Marine", StringComparison.OrdinalIgnoreCase))
                            {
                                TempIndices2[ii] = true;
                            }
                        }

                        // Extract only the indices which are marine
                        int[] TempIndices3 = Enumerable.Range(0, TempIndices2.Length).Where(i => TempIndices2[i]).ToArray();

                        if (TempIndices3.Length > 0)
                        {
                            // Extract the values at these indices
                            for (int ii = 0; ii < TempIndices3.Length; ii++)
                            {
                                TempIndices3[ii] = TempIndices[TempIndices3[ii]];
                            }

                            // Add in the indices for this functional group and this realm
                            CohortTraitIndicesMarine.Add(TraitValue, TempIndices3);
                        }
                    }
                }

                if (TrackMarineSpecifics)
                {
                    // Add in the specific classes of zooplankton and baleen whales

                    // There are functional groups representing obligate zooplankton
                    string[] TempString = new string[1] { "Obligate zooplankton" };
                    CohortTraitValuesMarine.Add("Obligate zooplankton", TempString);
                    CohortTraitIndicesMarine.Add("Obligate zooplankton", cohortFunctionalGroupDefinitions.GetFunctionalGroupIndex("Mobility", "planktonic", false));

                    // Whales have a special dietary index
                    TempString = new string[1] { "Baleen whales" };
                    CohortTraitValuesMarine.Add("Baleen whales", TempString);
                    CohortTraitIndicesMarine.Add("Baleen whales", cohortFunctionalGroupDefinitions.GetFunctionalGroupIndex("Diet", "allspecial", false));

                    // But we also want all zooplankton, including larval/juvenile stages of other cohorts
                    int[] ZooplanktonIndices1 = cohortFunctionalGroupDefinitions.GetFunctionalGroupIndex("Mobility", "planktonic", false);

                    // Then there are all of the other groups which may have planktonic juveniles. In the ModelGrid.cs class, these cohorts are checked to see
                    // if they have a weight of less than the planktonic dispersal threshold.
                    TempString = new string[3] { "Realm", "Endo/Ectotherm", "Mobility" };
                    string[] TempString2 = new string[3] { "marine", "ectotherm", "mobile" };
                    int[] ZooplanktonIndices2 = cohortFunctionalGroupDefinitions.GetFunctionalGroupIndex(TempString, TempString2, true);
                    CohortTraitIndicesMarine.Add("Zooplankton (all)", ZooplanktonIndices2.Concat(ZooplanktonIndices1).ToArray());
                }

                // Add unique trait values to each of the lists that will contain output data arranged by trait value
                foreach (string TraitValue in CohortTraitIndicesMarine.Keys)
                {
                    TotalBiomassDensitiesMarineOut.Add(TraitValue, 0.0);
                    TotalDensitiesMarineOut.Add(TraitValue, 0.0);
                }
            }
            else
            {
                // Add all unique trait values to the sorted dictionary
                foreach (string Trait in CohortTraits)
                {
                    CohortTraitValues.Add(Trait, cohortFunctionalGroupDefinitions.GetUniqueTraitValues(Trait));
                }

                foreach (string Trait in CohortTraits)
                {
                    foreach (string TraitValue in CohortTraitValues[Trait])
                    {
                        // Only add indices of terrestrial functional groups
                        int[] TempIndices = cohortFunctionalGroupDefinitions.GetFunctionalGroupIndex(Trait, TraitValue, false);
                        Boolean[] TempIndices2 = new Boolean[TempIndices.GetLength(0)];
                        for (int ii = 0; ii < TempIndices.GetLength(0); ii++)
                        {
                            if (cohortFunctionalGroupDefinitions.GetTraitNames("Realm", TempIndices[ii]).Equals("Terrestrial", StringComparison.OrdinalIgnoreCase))
                            {
                                TempIndices2[ii] = true;
                            }
                        }

                        // Extract only the indices which are terrestrial
                        int[] TempIndices3 = Enumerable.Range(0, TempIndices2.Length).Where(i => TempIndices2[i]).ToArray();

                        if (TempIndices3.Length > 0)
                        {
                            // Extract the values at these indices
                            for (int ii = 0; ii < TempIndices3.Length; ii++)
                            {
                                TempIndices3[ii] = TempIndices[TempIndices3[ii]];
                            }

                            // Add in the indices for this functional group and this realm
                            CohortTraitIndices.Add(TraitValue, TempIndices3);
                        }
                    }
                }

                // Add unique trait values to each of the lists that will contain output data arranged by trait value
                foreach (string TraitValue in CohortTraitIndices.Keys)
                {
                    TotalBiomassDensitiesOut.Add(TraitValue, 0.0);
                    TotalDensitiesOut.Add(TraitValue, 0.0);
                }
            }

            if (marineCell)
            {
                // Define the stock traits that will be used to separate outputs
                StockTraitsMarine = new string[1] { "Heterotroph/Autotroph"};

                // Re-initialise the sorted dictionary to hold all unique trait values
                StockTraitValuesMarine = new SortedDictionary<string, string[]>();

                // Add all unique stock trait values to the sorted dictionary
                foreach (string Trait in StockTraitsMarine)
                {
                    StockTraitValuesMarine.Add(Trait, stockFunctionalGroupDefinitions.GetUniqueTraitValues(Trait));
                }

                // Get the list of functional group indices corresponding to each unique marine trait value
                foreach (string Trait in StockTraitsMarine)
                {
                    foreach (string TraitValue in StockTraitValuesMarine[Trait])
                    {
                        StockTraitIndicesMarine.Add(TraitValue, stockFunctionalGroupDefinitions.GetFunctionalGroupIndex(Trait, TraitValue, false));
                    }
                }

                // Add unique trait values to each of the lists that will contain output data arranged by trait value
                foreach (string TraitValue in StockTraitIndicesMarine.Keys)
                {
                    TotalBiomassDensitiesOut.Add(TraitValue, 0.0);
                }
            }
            else
            {
                // Define the stock traits that will be used to separate outputs
                StockTraits = new string[2] { "Heterotroph/Autotroph", "Leaf strategy" };

                // Re-initialise the sorted dictionary to hold all unique trait values
                StockTraitValues = new SortedDictionary<string, string[]>();

                // Add all unique marine stock trait values to the sorted dictionary
                foreach (string Trait in StockTraits)
                {
                    StockTraitValues.Add(Trait, stockFunctionalGroupDefinitions.GetUniqueTraitValues(Trait));
                }

                // Get the list of functional group indices corresponding to each unique  trait value
                foreach (string Trait in StockTraits)
                {
                    foreach (string TraitValue in StockTraitValues[Trait])
                    {
                        StockTraitIndices.Add(TraitValue, stockFunctionalGroupDefinitions.GetFunctionalGroupIndex(Trait, TraitValue, false));
                    }
                }

                // Add unique trait values to each of the lists that will contain output data arranged by trait value
                foreach (string TraitValue in StockTraitIndices.Keys)
                {
                    TotalBiomassDensitiesOut.Add(TraitValue, 0.0);
                }
            }
        }
        /// <summary>
        /// Record the flow of biomass between trophic levels during predation
        /// </summary>
        /// <param name="latIndex">The latitudinal index of the current grid cell</param>
        /// <param name="lonIndex">The longitudinal index of the current grid cell</param>
        /// <param name="fromFunctionalGroup">The index of the functional group that the biomass is flowing from (i.e. the prey)</param>
        /// <param name="toFunctionalGroup">The index of the functional group that the biomass is flowing to (i.e. the predator)</param>
        /// <param name="cohortFunctionalGroupDefinitions">The functional group definitions of cohorts in the model</param>
        /// <param name="massEaten">The total biomass eaten by the predator cohort</param>
        /// <param name="predatorBodyMass">The body mass of the predator doing the eating</param>
        /// <param name="preyBodyMass">The body mass of the prey being eaten</param>
        /// <param name="initialisation">The Madingley Model initialisation</param>
        /// <param name="MarineCell">Whether the current cell is a marine cell</param>
        public void RecordPredationTrophicFlow(uint latIndex, uint lonIndex, int fromFunctionalGroup, int toFunctionalGroup,
            FunctionalGroupDefinitions cohortFunctionalGroupDefinitions, double massEaten, double predatorBodyMass, double preyBodyMass,
            MadingleyModelInitialisation initialisation, Boolean MarineCell)
        {
            int fromIndex = 0;
            int toIndex = 0;
            if (initialisation.TrackMarineSpecifics && MarineCell)
            {
                // Get the trophic level index of the functional group that mass is flowing from
                switch (cohortFunctionalGroupDefinitions.GetTraitNames("nutrition source", fromFunctionalGroup))
                {
                    case "herbivore":
                        switch (cohortFunctionalGroupDefinitions.GetTraitNames("mobility", fromFunctionalGroup))
                        {
                            case "planktonic":
                                fromIndex = 4;
                                break;
                            default:
                                switch (cohortFunctionalGroupDefinitions.GetTraitNames("endo/ectotherm", fromFunctionalGroup))
                                {
                                    case "endotherm":
                                        switch (cohortFunctionalGroupDefinitions.GetTraitNames("diet", fromFunctionalGroup))
                                        {
                                            case "allspecial":
                                                fromIndex = 6;
                                                break;
                                            default:
                                                fromIndex = 1;
                                                break;
                                        }
                                        break;
                                    default:
                                        if (preyBodyMass <= initialisation.PlanktonDispersalThreshold)
                                            fromIndex = 5;
                                        else
                                            fromIndex = 1;
                                        break;
                                }
                                break;
                        }
                        break;
                    case "omnivore":
                        switch (cohortFunctionalGroupDefinitions.GetTraitNames("mobility", fromFunctionalGroup))
                        {
                            case "planktonic":
                                fromIndex = 4;
                                break;
                            default:
                                switch (cohortFunctionalGroupDefinitions.GetTraitNames("endo/ectotherm", fromFunctionalGroup))
                                {
                                    case "endotherm":
                                        switch (cohortFunctionalGroupDefinitions.GetTraitNames("diet", fromFunctionalGroup))
                                        {
                                            case "allspecial":
                                                fromIndex = 6;
                                                break;
                                            default:
                                                fromIndex = 2;
                                                break;
                                        }
                                        break;
                                    default:
                                        if (preyBodyMass <= initialisation.PlanktonDispersalThreshold)
                                            fromIndex = 5;
                                        else
                                            fromIndex = 2;
                                        break;
                                }
                                break;
                        }
                        break;
                    case "carnivore":
                        switch (cohortFunctionalGroupDefinitions.GetTraitNames("mobility", fromFunctionalGroup))
                        {
                            case "planktonic":
                                fromIndex = 4;
                                break;
                            default:
                                switch (cohortFunctionalGroupDefinitions.GetTraitNames("endo/ectotherm", fromFunctionalGroup))
                                {
                                    case "endotherm":
                                        switch (cohortFunctionalGroupDefinitions.GetTraitNames("diet", fromFunctionalGroup))
                                        {
                                            case "allspecial":
                                                fromIndex = 6;
                                                break;
                                            default:
                                                fromIndex = 3;
                                                break;
                                        }
                                        break;
                                    default:
                                        if (preyBodyMass <= initialisation.PlanktonDispersalThreshold)
                                            fromIndex = 5;
                                        else
                                            fromIndex = 3;
                                        break;
                                }
                                break;
                        }
                        break;
                    default:
                        Debug.Fail("Specified nutrition source is not supported");
                        break;
                }

                // Get the trophic level index of the functional group that mass is flowing to
                switch (cohortFunctionalGroupDefinitions.GetTraitNames("nutrition source", toFunctionalGroup))
                {
                    case "omnivore":
                        switch (cohortFunctionalGroupDefinitions.GetTraitNames("mobility", toFunctionalGroup))
                        {
                            case "planktonic":
                                toIndex = 4;
                                break;
                            default:
                                switch (cohortFunctionalGroupDefinitions.GetTraitNames("endo/ectotherm", toFunctionalGroup))
                                {
                                    case "endotherm":
                                        switch (cohortFunctionalGroupDefinitions.GetTraitNames("diet", toFunctionalGroup))
                                        {
                                            case "allspecial":
                                                toIndex = 6;
                                                break;
                                            default:
                                                toIndex = 2;
                                                break;
                                        }
                                        break;
                                    default:
                                        if (predatorBodyMass <= initialisation.PlanktonDispersalThreshold)
                                            toIndex = 5;
                                        else
                                            toIndex = 2;
                                        break;
                                }
                                break;
                        }
                        break;
                    case "carnivore":
                        switch (cohortFunctionalGroupDefinitions.GetTraitNames("mobility", toFunctionalGroup))
                        {
                            case "planktonic":
                                toIndex = 4;
                                break;
                            default:
                                switch (cohortFunctionalGroupDefinitions.GetTraitNames("endo/ectotherm", toFunctionalGroup))
                                {
                                    case "endotherm":
                                        switch (cohortFunctionalGroupDefinitions.GetTraitNames("diet", toFunctionalGroup))
                                        {
                                            case "allspecial":
                                                toIndex = 6;
                                                break;
                                            default:
                                                toIndex = 3;
                                                break;
                                        }
                                        break;
                                    default:
                                        if (predatorBodyMass <= initialisation.PlanktonDispersalThreshold)
                                            toIndex = 5;
                                        else
                                            toIndex = 3;
                                        break;
                                }
                                break;
                        }
                        break;
                    default:
                        Debug.Fail("Specified nutrition source is not supported");
                        break;
                }
            }
            else
            {
                // Get the trophic level index of the functional group that mass is flowing from
                switch (cohortFunctionalGroupDefinitions.GetTraitNames("nutrition source", fromFunctionalGroup))
                {
                    case "herbivore":
                        fromIndex = 1;
                        break;
                    case "omnivore":
                        fromIndex = 2;
                        break;
                    case "carnivore":
                        fromIndex = 3;
                        break;
                    default:
                        Debug.Fail("Specified nutrition source is not supported");
                        break;
                }

                // Get the trophic level index of the functional group that mass is flowing to
                switch (cohortFunctionalGroupDefinitions.GetTraitNames("nutrition source", toFunctionalGroup))
                {
                    case "herbivore":
                        toIndex = 1;
                        break;
                    case "omnivore":
                        toIndex = 2;
                        break;
                    case "carnivore":
                        toIndex = 3;
                        break;
                    default:
                        Debug.Fail("Specified nutrition source is not supported");
                        break;
                }
            }

            // Add the flow of matter to the matrix of mass flows
            TrophicMassFlows[latIndex, lonIndex, fromIndex, toIndex] += massEaten;

        }
        /// <summary>
        /// Seed grid cell with cohorts, as specified in the model input files
        /// </summary>
        /// <param name="functionalGroups">The functional group definitions for cohorts in the grid cell</param>
        /// <param name="cellEnvironment">The environment in the grid cell</param>
        /// <param name="globalDiagnostics">A list of global diagnostic variables</param>
        /// <param name="nextCohortID">YThe unique ID to assign to the next cohort produced</param>
        /// <param name="tracking">boolean to indicate if cohorts are to be tracked in this model</param>
        /// <param name="totalCellTerrestrialCohorts">The total number of cohorts to be seeded in each terrestrial grid cell</param>
        /// <param name="totalCellMarineCohorts">The total number of cohorts to be seeded in each marine grid cell</param>
        /// <param name="DrawRandomly">Whether the model is set to use random draws</param>
        /// <param name="ZeroAbundance">Set this parameter to 'true' if you want to seed the cohorts with zero abundance</param>
        private void SeedGridCellCohorts(ref FunctionalGroupDefinitions functionalGroups, ref SortedList<string, double[]>
            cellEnvironment, SortedList<string, double> globalDiagnostics, Int64 nextCohortID, Boolean tracking, double totalCellTerrestrialCohorts, 
            double totalCellMarineCohorts, Boolean DrawRandomly, Boolean ZeroAbundance)
        {
            // Set the seed for the random number generator from the system time
            RandomNumberGenerator.SetSeedFromSystemTime();

            // StreamWriter tempsw = new StreamWriter("C://Temp//adult_juvenile_masses.txt");
            // tempsw.WriteLine("adult mass\tjuvenilemass");

            // Define local variables
            double CohortJuvenileMass;
            double CohortAdultMassRatio;
            double CohortAdultMass;
            double ExpectedLnAdultMassRatio;
            int[] FunctionalGroupsToUse;
            double NumCohortsThisCell;
            double TotalNewBiomass =0.0;

            // Get the minimum and maximum possible body masses for organisms in each functional group
            double[] MassMinima = functionalGroups.GetBiologicalPropertyAllFunctionalGroups("minimum mass");
            double[] MassMaxima = functionalGroups.GetBiologicalPropertyAllFunctionalGroups("maximum mass");
            string[] NutritionSource = functionalGroups.GetTraitValuesAllFunctionalGroups("nutrition source");

            double[] ProportionTimeActive = functionalGroups.GetBiologicalPropertyAllFunctionalGroups("proportion suitable time active");

            //Variable for altering the juvenile to adult mass ratio for marine cells when handling certain functional groups eg baleen whales
            double Scaling = 0.0;

            Int64 CohortIDIncrementer = nextCohortID;

            // Check which realm the cell is in
            if (cellEnvironment["Realm"][0] == 1.0)
            {
                // Get the indices of all terrestrial functional groups
                FunctionalGroupsToUse = functionalGroups.GetFunctionalGroupIndex("realm", "terrestrial", true);
                NumCohortsThisCell = totalCellTerrestrialCohorts;
            }
            else
            {
                // Get the indices of all marine functional groups
                FunctionalGroupsToUse = functionalGroups.GetFunctionalGroupIndex("realm", "marine", true);
                NumCohortsThisCell = totalCellMarineCohorts;
            }
            Debug.Assert(cellEnvironment["Realm"][0] > 0.0, "Missing realm for grid cell");

            if (NumCohortsThisCell > 0)
            {
                // Loop over all functional groups in the model
                for (int FunctionalGroup = 0; FunctionalGroup < functionalGroups.GetNumberOfFunctionalGroups(); FunctionalGroup++)
                {

                    // Create a new list to hold the cohorts in the grid cell
                    _GridCellCohorts[FunctionalGroup] = new List<Cohort>();

                    // If it is a functional group that corresponds to the current realm, then seed cohorts
                    if (FunctionalGroupsToUse.Contains(FunctionalGroup))
                    {
                        // Loop over the initial number of cohorts
                        double NumberOfCohortsInThisFunctionalGroup = 1.0;
                        if (!ZeroAbundance)
                        {
                            NumberOfCohortsInThisFunctionalGroup = functionalGroups.GetBiologicalPropertyOneFunctionalGroup("initial number of gridcellcohorts", FunctionalGroup);
                        }
                        for (int jj = 0; jj < NumberOfCohortsInThisFunctionalGroup; jj++)
                        {
                            // Check whether the model is set to randomly draw the body masses of new cohorts
                            if (DrawRandomly)
                            {
                                // Draw adult mass from a log-normal distribution with mean -6.9 and standard deviation 10.0,
                                // within the bounds of the minimum and maximum body masses for the functional group
                                CohortAdultMass = Math.Pow(10, (RandomNumberGenerator.GetUniform() * (Math.Log10(MassMaxima[FunctionalGroup]) - Math.Log10(50 * MassMinima[FunctionalGroup])) + Math.Log10(50 * MassMinima[FunctionalGroup])));

                                // Terrestrial and marine organisms have different optimal prey/predator body mass ratios
                                if (cellEnvironment["Realm"][0] == 1.0)
                                    // Optimal prey body size 10%
                                    OptimalPreyBodySizeRatio = Math.Max(0.01, RandomNumberGenerator.GetNormal(0.1, 0.02));
                                else
                                {
                                    if (functionalGroups.GetTraitNames("Diet", FunctionalGroup) == "allspecial")
                                    {
                                        // Note that for this group
                                        // it is actually (despite the name) not an optimal prey body size ratio, but an actual body size.
                                        // This is because it is invariant as the predator (filter-feeding baleen whale) grows.
                                        // See also the predation classes.
                                        OptimalPreyBodySizeRatio = Math.Max(0.00001, RandomNumberGenerator.GetNormal(0.0001, 0.1));
                                    }
                                    else
                                    {
                                        // Optimal prey body size or marine organisms is 10%
                                        OptimalPreyBodySizeRatio = Math.Max(0.01, RandomNumberGenerator.GetNormal(0.1, 0.02));
                                    }

                                }

                                // Draw from a log-normal distribution with mean 10.0 and standard deviation 5.0, then add one to obtain
                                // the ratio of adult to juvenile body mass, and then calculate juvenile mass based on this ratio and within the
                                // bounds of the minimum and maximum body masses for this functional group
                                if (cellEnvironment["Realm"][0] == 1.0)
                                {
                                    do
                                    {
                                        ExpectedLnAdultMassRatio = 2.24 + 0.13 * Math.Log(CohortAdultMass);
                                        CohortAdultMassRatio = 1.0 + RandomNumberGenerator.GetLogNormal(ExpectedLnAdultMassRatio, 0.5);
                                        CohortJuvenileMass = CohortAdultMass * 1.0 / CohortAdultMassRatio;
                                    } while (CohortAdultMass <= CohortJuvenileMass || CohortJuvenileMass < MassMinima[FunctionalGroup]);
                                }
                                // In the marine realm, have a greater difference between the adult and juvenile body masses, on average
                                else
                                {
                                    uint Counter = 0;
                                    Scaling = 0.2;
                                    // Use the scaling to deal with baleen whales not having such a great difference
                                    do
                                    {

                                        ExpectedLnAdultMassRatio = 2.5 + Scaling * Math.Log(CohortAdultMass);
                                        CohortAdultMassRatio = 1.0 + 10 * RandomNumberGenerator.GetLogNormal(ExpectedLnAdultMassRatio, 0.5);
                                        CohortJuvenileMass = CohortAdultMass * 1.0 / CohortAdultMassRatio;
                                        Counter++;
                                        if (Counter > 10)
                                        {
                                            Scaling -= 0.01;
                                            Counter = 0;
                                        }
                                    } while (CohortAdultMass <= CohortJuvenileMass || CohortJuvenileMass < MassMinima[FunctionalGroup]);
                                }
                            }
                            else
                            {
                                // Use the same seed for the random number generator every time
                                RandomNumberGenerator.SetSeed((uint)(jj + 1), (uint)((jj + 1) * 3));

                                // Draw adult mass from a log-normal distribution with mean -6.9 and standard deviation 10.0,
                                // within the bounds of the minimum and maximum body masses for the functional group
                                CohortAdultMass = Math.Pow(10, (RandomNumberGenerator.GetUniform() * (Math.Log10(MassMaxima[FunctionalGroup]) - Math.Log10(50 * MassMinima[FunctionalGroup])) + Math.Log10(50 * MassMinima[FunctionalGroup])));

                                OptimalPreyBodySizeRatio = Math.Max(0.01, RandomNumberGenerator.GetNormal(0.1, 0.02));

                                // Draw from a log-normal distribution with mean 10.0 and standard deviation 5.0, then add one to obtain
                                // the ratio of adult to juvenile body mass, and then calculate juvenile mass based on this ratio and within the
                                // bounds of the minimum and maximum body masses for this functional group
                                if (cellEnvironment["Realm"][0] == 1.0)
                                {
                                    do
                                    {
                                        ExpectedLnAdultMassRatio = 2.24 + 0.13 * Math.Log(CohortAdultMass);
                                        CohortAdultMassRatio = 1.0 + RandomNumberGenerator.GetLogNormal(ExpectedLnAdultMassRatio, 0.5);
                                        CohortJuvenileMass = CohortAdultMass * 1.0 / CohortAdultMassRatio;
                                    } while (CohortAdultMass <= CohortJuvenileMass || CohortJuvenileMass < MassMinima[FunctionalGroup]);

                                }
                                // In the marine realm, have a greater difference between the adult and juvenile body masses, on average
                                else
                                {
                                    do
                                    {
                                        ExpectedLnAdultMassRatio = 2.24 + 0.13 * Math.Log(CohortAdultMass);
                                        CohortAdultMassRatio = 1.0 + 10 * RandomNumberGenerator.GetLogNormal(ExpectedLnAdultMassRatio, 0.5);
                                        CohortJuvenileMass = CohortAdultMass * 1.0 / CohortAdultMassRatio;
                                    } while (CohortAdultMass <= CohortJuvenileMass || CohortJuvenileMass < MassMinima[FunctionalGroup]);
                                }
                            }

                            // An instance of Cohort to hold the new cohort
                            Cohort NewCohort;
                            //double NewBiomass = Math.Pow(0.2, (Math.Log10(CohortAdultMass))) * (1.0E9 * _CellEnvironment["Cell Area"][0]) / NumCohortsThisCell;
                            // 3000*(0.6^log(mass)) gives individual cohort biomass density in g ha-1
                            // * 100 to give g km-2
                            // * cell area to give g grid cell
                            //*3300/NumCohortsThisCell scales total initial biomass in the cell to some approximately reasonable mass
                            double NewBiomass = (3300 / NumCohortsThisCell) * 100 * 3000 *
                                Math.Pow(0.6, (Math.Log10(CohortJuvenileMass))) * (_CellEnvironment["Cell Area"][0]);
                            TotalNewBiomass += NewBiomass;
                            double NewAbund = 0.0;
                            if (!ZeroAbundance)
                            {
                                NewAbund = NewBiomass / CohortJuvenileMass;
                            }

                            /*
                            // TEMPORARILY MARINE ONLY
                            if (cellEnvironment["Realm"][0] == 1)
                            {
                                NewAbund = 0.0;
                            }
                            */
                            double TrophicIndex;
                            switch (NutritionSource[FunctionalGroup])
                            {
                                case "herbivore":
                                    TrophicIndex = 2;
                                    break;
                                case "omnivore":
                                    TrophicIndex = 2.5;
                                    break;
                                case "carnivore":
                                    TrophicIndex = 3;
                                    break;
                                default:
                                    Debug.Fail("Unexpected nutrition source trait value when assigning trophic index");
                                    TrophicIndex = 0.0;
                                    break;
                            }

                            // Initialise the new cohort with the relevant properties
                            NewCohort = new Cohort((byte)FunctionalGroup, CohortJuvenileMass, CohortAdultMass, CohortJuvenileMass, NewAbund,
                            OptimalPreyBodySizeRatio, (ushort)0, ProportionTimeActive[FunctionalGroup], ref CohortIDIncrementer,TrophicIndex, tracking);

                            // Add the new cohort to the list of grid cell cohorts
                            _GridCellCohorts[FunctionalGroup].Add(NewCohort);

                            // TEMPORARY
                            /*
                            // Check whether the model is set to randomly draw the body masses of new cohorts
                            if ((Longitude % 4 == 0) && (Latitude % 4 == 0))
                            {
                                if (DrawRandomly)
                                {
                                    CohortAdultMass = 100000;
                                    CohortJuvenileMass = 100000;
                                }
                                else
                                {
                                    CohortAdultMass = 100000;
                                    CohortJuvenileMass = 100000;
                                }

                                // An instance of Cohort to hold the new cohort
                                Cohort NewCohort;
                                double NewBiomass = (1.0E7 * _CellEnvironment["Cell Area"][0]) / NumCohortsThisCell;
                                double NewAbund = 0.0;
                                NewAbund = 3000;

                                // Initialise the new cohort with the relevant properties
                                NewCohort = new Cohort((byte)FunctionalGroup, CohortJuvenileMass, CohortAdultMass, CohortJuvenileMass, NewAbund,
                                    (ushort)0, ref nextCohortID, tracking);

                                // Add the new cohort to the list of grid cell cohorts
                                _GridCellCohorts[FunctionalGroup].Add(NewCohort);
                            }
                            */

                            // Incrememt the variable tracking the total number of cohorts in the model
                            globalDiagnostics["NumberOfCohortsInModel"]++;

                        }

                    }
                }

            }
            else
            {
                                // Loop over all functional groups in the model
                for (int FunctionalGroup = 0; FunctionalGroup < functionalGroups.GetNumberOfFunctionalGroups(); FunctionalGroup++)
                {

                    // Create a new list to hold the cohorts in the grid cell
                    _GridCellCohorts[FunctionalGroup] = new List<Cohort>();
                }
            }

            // tempsw.Dispose();
        }
Пример #16
0
        /// <summary>
        /// Calculate the potential number of prey that could be gained through predation on each cohort in the grid cell
        /// </summary>
        /// <param name="gridCellCohorts">The cohorts in the current grid cell</param>
        /// <param name="gridCellStocks">The stocks in the current grid cell</param>
        /// <param name="actingCohort">The acting cohort</param>
        /// <param name="cellEnvironment">The environment in the current grid cell</param>
        /// <param name="madingleyCohortDefinitions">The functional group definitions for cohorts in the model</param>
        /// <param name="madingleyStockDefinitions">The functional group definitions for stocks  in the model</param>
        public void GetEatingPotentialMarine(GridCellCohortHandler gridCellCohorts, GridCellStockHandler gridCellStocks, int[] actingCohort,
                                             SortedList <string, double[]> cellEnvironment, FunctionalGroupDefinitions madingleyCohortDefinitions, FunctionalGroupDefinitions
                                             madingleyStockDefinitions)
        {
            BinnedPreyDensities = new double[gridCellCohorts.Count, NumberOfBins];

            // Set the total eaten by the acting cohort to zero
            _TotalBiomassEatenByCohort = 0.0;

            // Set the total number of units to handle all potential prey individuals eaten to zero
            _TimeUnitsToHandlePotentialFoodItems = 0.0;

            // Get the individual body mass of the acting (predator) cohort
            _BodyMassPredator = gridCellCohorts[actingCohort].IndividualBodyMass;

            // Get the abundance of the acting (predator) cohort
            _AbundancePredator = gridCellCohorts[actingCohort].CohortAbundance;

            // Pre-calculate individual values for this predator to speed things up
            _SpecificPredatorKillRateConstant = _KillRateConstant * Math.Pow(_BodyMassPredator, (_KillRateConstantMassExponent));
            _SpecificPredatorTimeUnitsEatingPerGlobalTimeStep = _DeltaT * _ProportionOfTimeEating;
            _PredatorAssimilationEfficiency = _AssimilationEfficiency;
            _PredatorNonAssimilation        = (1 - _AssimilationEfficiency);

            _DietIsAllSpecial = madingleyCohortDefinitions.GetTraitNames("Diet", actingCohort[0]) == "allspecial";

            _PredatorLogOptimalPreyBodySizeRatio = gridCellCohorts[actingCohort[0]][actingCohort[1]].LogOptimalPreyBodySizeRatio;

            // If a filter feeder, then optimal body size is a value not a ratio: convert it to a ratio to ensure that all calculations work correctly
            if (_DietIsAllSpecial)
            {
                // Optimal body size is actually a value, not a ratio, so convert it to a ratio based on the present body size
                _PredatorLogOptimalPreyBodySizeRatio = Math.Log(
                    Math.Exp(gridCellCohorts[actingCohort[0]][actingCohort[1]].LogOptimalPreyBodySizeRatio) / gridCellCohorts[actingCohort[0]][actingCohort[1]].IndividualBodyMass);
            }


            // Calculate the reference mass scaling ratio
            _ReferenceMassRatioScalingMarine = HandlingTimeScalarMarine * Math.Pow(_ReferenceMass / _BodyMassPredator, _HandlingTimeExponentMarine);

            _PredatorAbundanceMultipliedByTimeEating = _AbundancePredator * _SpecificPredatorTimeUnitsEatingPerGlobalTimeStep;

            LogPredatorMassPlusPredatorLogOptimalPreyBodySizeRatio = Math.Log(_BodyMassPredator) + _PredatorLogOptimalPreyBodySizeRatio;

            // Calculate the abundance of prey in each of the prey mass bins
            PopulateBinnedPreyAbundance(gridCellCohorts, actingCohort, FunctionalGroupIndicesToEat, LogPredatorMassPlusPredatorLogOptimalPreyBodySizeRatio);

            // Loop over potential prey functional groups
            foreach (int FunctionalGroup in FunctionalGroupIndicesToEat)
            {
                // Eating operates differently for planktivores
                // This can certainly be sped up
                if (_DietIsAllSpecial)
                {
                    // Loop over cohorts within the functional group
                    for (int i = 0; i < NumberCohortsPerFunctionalGroupNoNewCohorts[FunctionalGroup]; i++)
                    {
                        // Get the body mass of individuals in this cohort
                        _BodyMassPrey = gridCellCohorts[FunctionalGroup][i].IndividualBodyMass;

                        // Get the bin number of this prey cohort
                        PreyMassBinNumber = GetBinNumber(_BodyMassPrey, LogPredatorMassPlusPredatorLogOptimalPreyBodySizeRatio);


                        // Check whether
                        // The prey cohort is within the feeding range of the predator
                        // the prey cohort still exists in the model (i.e. body mass > 0)
                        // Currently having whales etc eat everything, but preferentially feed on very small things (i.e. filter feeders)
                        if ((_PlanktonFunctionalGroups[FunctionalGroup]) && (0 < PreyMassBinNumber) &&
                            (PreyMassBinNumber < NumberOfBins) && (_BodyMassPrey > 0))
                        {
                            // Calculate the potential abundance from this cohort eaten by the acting cohort
                            _PotentialAbundanceEaten[FunctionalGroup][i] = CalculateExpectedNumberKilledMarine(
                                gridCellCohorts[FunctionalGroup][i].CohortAbundance, _BodyMassPrey, PreyMassBinNumber, FunctionalGroup,
                                _BodyMassPredator, _CarnivoreFunctionalGroups[FunctionalGroup], _OmnivoreFunctionalGroups[FunctionalGroup],
                                _OmnivoreFunctionalGroups[actingCohort[0]], _PredatorLogOptimalPreyBodySizeRatio);

                            // Add the time required to handle the potential abundance eaten from this cohort to the cumulative total for all cohorts
                            _TimeUnitsToHandlePotentialFoodItems += _PotentialAbundanceEaten[FunctionalGroup][i] *
                                                                    CalculateHandlingTimeMarine(_BodyMassPrey);
                        }
                        else
                        {
                            // Assign a potential abundance eaten of zero
                            _PotentialAbundanceEaten[FunctionalGroup][i] = 0.0;
                        }
                    }
                }
                else
                {
                    // Loop over cohorts within the functional group
                    for (int i = 0; i < NumberCohortsPerFunctionalGroupNoNewCohorts[FunctionalGroup]; i++)
                    {
                        // Get the body mass of individuals in this cohort
                        _BodyMassPrey = gridCellCohorts[FunctionalGroup][i].IndividualBodyMass;

                        // Get the bin number of this prey cohort
                        PreyMassBinNumber = GetBinNumber(_BodyMassPrey, LogPredatorMassPlusPredatorLogOptimalPreyBodySizeRatio);

                        // Check whether
                        // The prey cohort is within the feeding range of the predator
                        // the prey cohort still exists in the model (i.e. body mass > 0)
                        if ((0 < PreyMassBinNumber) && (PreyMassBinNumber < NumberOfBins) && (_BodyMassPrey > 0))
                        {
                            // Calculate the potential abundance from this cohort eaten by the acting cohort
                            _PotentialAbundanceEaten[FunctionalGroup][i] = CalculateExpectedNumberKilledMarine(
                                gridCellCohorts[FunctionalGroup][i].CohortAbundance, _BodyMassPrey, PreyMassBinNumber, FunctionalGroup,
                                _BodyMassPredator, _CarnivoreFunctionalGroups[FunctionalGroup], _OmnivoreFunctionalGroups[FunctionalGroup],
                                _OmnivoreFunctionalGroups[actingCohort[0]], _PredatorLogOptimalPreyBodySizeRatio);

                            // Add the time required to handle the potential abundance eaten from this cohort to the cumulative total for all cohorts
                            _TimeUnitsToHandlePotentialFoodItems += _PotentialAbundanceEaten[FunctionalGroup][i] *
                                                                    CalculateHandlingTimeMarine(_BodyMassPrey);
                        }
                        else
                        {
                            // Assign a potential abundance eaten of zero
                            _PotentialAbundanceEaten[FunctionalGroup][i] = 0.0;
                        }
                    }
                }
            }

            // No cannibalism; do this outside the loop to speed up the calculations
            _TimeUnitsToHandlePotentialFoodItems -= PotentialAbundanceEaten[actingCohort[0]][actingCohort[1]] *
                                                    CalculateHandlingTimeMarine(_BodyMassPredator);
            PotentialAbundanceEaten[actingCohort[0]][actingCohort[1]] = 0.0;
        }
Пример #17
0
        /// <summary>
        /// Record the flow of biomass between trophic levels during predation
        /// </summary>
        /// <param name="latIndex">The latitudinal index of the current grid cell</param>
        /// <param name="lonIndex">The longitudinal index of the current grid cell</param>
        /// <param name="fromFunctionalGroup">The index of the functional group that the biomass is flowing from (i.e. the prey)</param>
        /// <param name="toFunctionalGroup">The index of the functional group that the biomass is flowing to (i.e. the predator)</param>
        /// <param name="cohortFunctionalGroupDefinitions">The functional group definitions of cohorts in the model</param>
        /// <param name="massEaten">The total biomass eaten by the predator cohort</param>
        /// <param name="predatorBodyMass">The body mass of the predator doing the eating</param>
        /// <param name="preyBodyMass">The body mass of the prey being eaten</param>
        /// <param name="initialisation">The Madingley Model initialisation</param>
        /// <param name="MarineCell">Whether the current cell is a marine cell</param>
        public void RecordPredationTrophicFlow(uint latIndex, uint lonIndex, int fromFunctionalGroup, int toFunctionalGroup,
                                               FunctionalGroupDefinitions cohortFunctionalGroupDefinitions, double massEaten, double predatorBodyMass, double preyBodyMass,
                                               MadingleyModelInitialisation initialisation, Boolean MarineCell)
        {
            int fromIndex = 0;
            int toIndex   = 0;

            if (initialisation.TrackMarineSpecifics && MarineCell)
            {
                // Get the trophic level index of the functional group that mass is flowing from
                switch (cohortFunctionalGroupDefinitions.GetTraitNames("nutrition source", fromFunctionalGroup))
                {
                case "herbivore":
                    switch (cohortFunctionalGroupDefinitions.GetTraitNames("mobility", fromFunctionalGroup))
                    {
                    case "planktonic":
                        fromIndex = 4;
                        break;

                    default:
                        switch (cohortFunctionalGroupDefinitions.GetTraitNames("endo/ectotherm", fromFunctionalGroup))
                        {
                        case "endotherm":
                            switch (cohortFunctionalGroupDefinitions.GetTraitNames("diet", fromFunctionalGroup))
                            {
                            case "allspecial":
                                fromIndex = 6;
                                break;

                            default:
                                fromIndex = 1;
                                break;
                            }
                            break;

                        default:
                            if (preyBodyMass <= initialisation.PlanktonDispersalThreshold)
                            {
                                fromIndex = 5;
                            }
                            else
                            {
                                fromIndex = 1;
                            }
                            break;
                        }
                        break;
                    }
                    break;

                case "omnivore":
                    switch (cohortFunctionalGroupDefinitions.GetTraitNames("mobility", fromFunctionalGroup))
                    {
                    case "planktonic":
                        fromIndex = 4;
                        break;

                    default:
                        switch (cohortFunctionalGroupDefinitions.GetTraitNames("endo/ectotherm", fromFunctionalGroup))
                        {
                        case "endotherm":
                            switch (cohortFunctionalGroupDefinitions.GetTraitNames("diet", fromFunctionalGroup))
                            {
                            case "allspecial":
                                fromIndex = 6;
                                break;

                            default:
                                fromIndex = 2;
                                break;
                            }
                            break;

                        default:
                            if (preyBodyMass <= initialisation.PlanktonDispersalThreshold)
                            {
                                fromIndex = 5;
                            }
                            else
                            {
                                fromIndex = 2;
                            }
                            break;
                        }
                        break;
                    }
                    break;

                case "carnivore":
                    switch (cohortFunctionalGroupDefinitions.GetTraitNames("mobility", fromFunctionalGroup))
                    {
                    case "planktonic":
                        fromIndex = 4;
                        break;

                    default:
                        switch (cohortFunctionalGroupDefinitions.GetTraitNames("endo/ectotherm", fromFunctionalGroup))
                        {
                        case "endotherm":
                            switch (cohortFunctionalGroupDefinitions.GetTraitNames("diet", fromFunctionalGroup))
                            {
                            case "allspecial":
                                fromIndex = 6;
                                break;

                            default:
                                fromIndex = 3;
                                break;
                            }
                            break;

                        default:
                            if (preyBodyMass <= initialisation.PlanktonDispersalThreshold)
                            {
                                fromIndex = 5;
                            }
                            else
                            {
                                fromIndex = 3;
                            }
                            break;
                        }
                        break;
                    }
                    break;

                default:
                    Debug.Fail("Specified nutrition source is not supported");
                    break;
                }

                // Get the trophic level index of the functional group that mass is flowing to
                switch (cohortFunctionalGroupDefinitions.GetTraitNames("nutrition source", toFunctionalGroup))
                {
                case "omnivore":
                    switch (cohortFunctionalGroupDefinitions.GetTraitNames("mobility", toFunctionalGroup))
                    {
                    case "planktonic":
                        toIndex = 4;
                        break;

                    default:
                        switch (cohortFunctionalGroupDefinitions.GetTraitNames("endo/ectotherm", toFunctionalGroup))
                        {
                        case "endotherm":
                            switch (cohortFunctionalGroupDefinitions.GetTraitNames("diet", toFunctionalGroup))
                            {
                            case "allspecial":
                                toIndex = 6;
                                break;

                            default:
                                toIndex = 2;
                                break;
                            }
                            break;

                        default:
                            if (predatorBodyMass <= initialisation.PlanktonDispersalThreshold)
                            {
                                toIndex = 5;
                            }
                            else
                            {
                                toIndex = 2;
                            }
                            break;
                        }
                        break;
                    }
                    break;

                case "carnivore":
                    switch (cohortFunctionalGroupDefinitions.GetTraitNames("mobility", toFunctionalGroup))
                    {
                    case "planktonic":
                        toIndex = 4;
                        break;

                    default:
                        switch (cohortFunctionalGroupDefinitions.GetTraitNames("endo/ectotherm", toFunctionalGroup))
                        {
                        case "endotherm":
                            switch (cohortFunctionalGroupDefinitions.GetTraitNames("diet", toFunctionalGroup))
                            {
                            case "allspecial":
                                toIndex = 6;
                                break;

                            default:
                                toIndex = 3;
                                break;
                            }
                            break;

                        default:
                            if (predatorBodyMass <= initialisation.PlanktonDispersalThreshold)
                            {
                                toIndex = 5;
                            }
                            else
                            {
                                toIndex = 3;
                            }
                            break;
                        }
                        break;
                    }
                    break;

                default:
                    Debug.Fail("Specified nutrition source is not supported");
                    break;
                }
            }
            else
            {
                // Get the trophic level index of the functional group that mass is flowing from
                switch (cohortFunctionalGroupDefinitions.GetTraitNames("nutrition source", fromFunctionalGroup))
                {
                case "herbivore":
                    fromIndex = 1;
                    break;

                case "omnivore":
                    fromIndex = 2;
                    break;

                case "carnivore":
                    fromIndex = 3;
                    break;

                default:
                    Debug.Fail("Specified nutrition source is not supported");
                    break;
                }

                // Get the trophic level index of the functional group that mass is flowing to
                switch (cohortFunctionalGroupDefinitions.GetTraitNames("nutrition source", toFunctionalGroup))
                {
                case "herbivore":
                    toIndex = 1;
                    break;

                case "omnivore":
                    toIndex = 2;
                    break;

                case "carnivore":
                    toIndex = 3;
                    break;

                default:
                    Debug.Fail("Specified nutrition source is not supported");
                    break;
                }
            }

            // Add the flow of matter to the matrix of mass flows
            TrophicMassFlows[latIndex, lonIndex, fromIndex, toIndex] += massEaten;
        }
        /// <summary>
        /// Calculate the proportion of time for which this cohort could be active and assign it to the cohort's properties
        /// </summary>
        /// <param name="actingCohort">The Cohort for which proportion of time active is being calculated</param>
        /// <param name="cellEnvironment">The environmental information for current grid cell</param>
        /// <param name="madingleyCohortDefinitions">Functional group definitions and code to interrogate the cohorts in current grid cell</param>
        /// <param name="currentTimestep">Current timestep index</param>
        /// <param name="currentMonth">Current month</param>
        public void AssignProportionTimeActive(Cohort actingCohort, SortedList<string, double[]> cellEnvironment,
            FunctionalGroupDefinitions madingleyCohortDefinitions,uint currentTimestep, uint currentMonth)
        {
            double Realm = cellEnvironment["Realm"][0];

            //Only work on heterotroph cohorts
            if (madingleyCohortDefinitions.GetTraitNames("Heterotroph/Autotroph", actingCohort.FunctionalGroupIndex) == "heterotroph")
            {
                //Check if this is an endotherm or ectotherm
                Boolean Endotherm = madingleyCohortDefinitions.GetTraitNames("Endo/Ectotherm", actingCohort.FunctionalGroupIndex) == "endotherm";
                if (Endotherm)
                {
                    //Assumes the whole timestep is suitable for endotherms to be active - actual time active is therefore the proportion specified for this functional group.
                    actingCohort.ProportionTimeActive = madingleyCohortDefinitions.GetBiologicalPropertyOneFunctionalGroup("proportion suitable time active", actingCohort.FunctionalGroupIndex);
                }
                else
                {
                    //If ectotherm then use realm specific function
                    if (Realm == 1.0)
                    {
                        actingCohort.ProportionTimeActive = CalculateProportionTimeSuitableTerrestrial(cellEnvironment, currentMonth, Endotherm) *
                            madingleyCohortDefinitions.GetBiologicalPropertyOneFunctionalGroup("proportion suitable time active", actingCohort.FunctionalGroupIndex);
                    }
                    else
                    {
                        actingCohort.ProportionTimeActive = CalculateProportionTimeSuitableMarine(cellEnvironment, currentMonth, Endotherm) *
                            madingleyCohortDefinitions.GetBiologicalPropertyOneFunctionalGroup("proportion suitable time active", actingCohort.FunctionalGroupIndex);
                    }

                }

            }
        }
Пример #19
0
        /// <summary>
        /// Generate new cohorts from reproductive potential mass
        /// </summary>
        /// <param name="gridCellCohorts">The cohorts in the current grid cell</param>
        /// <param name="gridCellStocks">The stocks in the current grid cell</param>
        /// <param name="actingCohort">The position of the acting cohort in the jagged array of grid cell cohorts</param>
        /// <param name="cellEnvironment">The environment of the current grid cell</param>
        /// <param name="deltas">The sorted list to track changes in biomass and abundance of the acting cohort in this grid cell</param>
        /// <param name="madingleyCohortDefinitions">The definitions of cohort functional groups in the model</param>
        /// <param name="madingleyStockDefinitions">The definitions of stock functional groups in the model</param>
        /// <param name="currentTimestep">The current model time step</param>
        /// <param name="tracker">An instance of ProcessTracker to hold diagnostics for reproduction</param>
        /// <param name="partial">Thread-locked variables</param>
        /// <param name="iteroparous">Whether the acting cohort is iteroparous, as opposed to semelparous</param>
        /// <param name="currentMonth">The current model month</param>
        public void RunReproductionEvents(GridCellCohortHandler gridCellCohorts, GridCellStockHandler gridCellStocks,
                                          int[] actingCohort, SortedList <string, double[]> cellEnvironment, Dictionary <string, Dictionary <string, double> >
                                          deltas, FunctionalGroupDefinitions madingleyCohortDefinitions, FunctionalGroupDefinitions madingleyStockDefinitions,
                                          uint currentTimestep, ProcessTracker tracker, ref ThreadLockedParallelVariables partial, bool iteroparous, uint currentMonth)
        {
            // Adult non-reproductive biomass lost by semelparous organisms
            double AdultMassLost;

            // Offspring cohort abundance
            double _OffspringCohortAbundance;

            // Mass ratio of body mass + reproductive mass to adult body mass
            double CurrentMassRatio;

            // Individual body mass including change this time step as a result of other ecological processes
            double BodyMassIncludingChangeThisTimeStep;

            // Offspring juvenile and adult body masses
            double[] OffspringJuvenileAndAdultBodyMasses = new double[2];

            // Offspring cohort
            Cohort OffspringCohort;

            // Individual reproductive mass including change this time step as a result of other ecological processes
            double ReproductiveMassIncludingChangeThisTimeStep;


            // Calculate the biomass of an individual in this cohort including changes this time step from other ecological processes
            BodyMassIncludingChangeThisTimeStep = 0.0;

            foreach (var Biomass in deltas["biomass"])
            {
                // Add the delta biomass to net biomass
                BodyMassIncludingChangeThisTimeStep += Biomass.Value;
            }

            BodyMassIncludingChangeThisTimeStep += gridCellCohorts[actingCohort].IndividualBodyMass;

            // Calculate the reproductive biomass of an individual in this cohort including changes this time step from other ecological processes
            ReproductiveMassIncludingChangeThisTimeStep = 0.0;

            foreach (var ReproBiomass in deltas["reproductivebiomass"])
            {
                // Add the delta reproductive biomass to net biomass
                ReproductiveMassIncludingChangeThisTimeStep += ReproBiomass.Value;
            }

            ReproductiveMassIncludingChangeThisTimeStep += gridCellCohorts[actingCohort].IndividualReproductivePotentialMass;

            // Get the current ratio of total individual mass (including reproductive potential) to adult body mass
            CurrentMassRatio = (BodyMassIncludingChangeThisTimeStep + ReproductiveMassIncludingChangeThisTimeStep) / gridCellCohorts[actingCohort].AdultMass;

            // Must have enough mass to hit reproduction threshold criterion, and either (1) be in breeding season, or (2) be a marine cell (no breeding season in marine cells)
            if ((CurrentMassRatio > _MassRatioThreshold) && ((cellEnvironment["Breeding Season"][currentMonth] == 1.0) || ((cellEnvironment["Realm"][0] == 2.0))))
            {
                // Iteroparous and semelparous organisms have different strategies
                if (iteroparous)
                {
                    // Iteroparous organisms do not allocate any of their current non-reproductive biomass to reproduction
                    AdultMassLost = 0.0;

                    // Calculate the number of offspring that could be produced given the reproductive potential mass of individuals
                    _OffspringCohortAbundance = gridCellCohorts[actingCohort].CohortAbundance * ReproductiveMassIncludingChangeThisTimeStep /
                                                gridCellCohorts[actingCohort].JuvenileMass;
                }
                else
                {
                    // Semelparous organisms allocate a proportion of their current non-reproductive biomass (including the effects of other ecological processes) to reproduction
                    AdultMassLost = _SemelparityAdultMassAllocation * BodyMassIncludingChangeThisTimeStep;

                    // Calculate the number of offspring that could be produced given the reproductive potential mass of individuals
                    _OffspringCohortAbundance = gridCellCohorts[actingCohort].CohortAbundance * (AdultMassLost + ReproductiveMassIncludingChangeThisTimeStep) /
                                                gridCellCohorts[actingCohort].JuvenileMass;
                }

                // Check that the abundance in the cohort to produce is greater than or equal to zero
                Debug.Assert(_OffspringCohortAbundance >= 0.0, "Offspring abundance < 0");

                // Get the adult and juvenile masses of the offspring cohort
                OffspringJuvenileAndAdultBodyMasses = GetOffspringCohortProperties(gridCellCohorts, actingCohort, madingleyCohortDefinitions);

                // Update cohort abundance in case juvenile mass has been altered through 'evolution'
                _OffspringCohortAbundance = (_OffspringCohortAbundance * gridCellCohorts[actingCohort].JuvenileMass) / OffspringJuvenileAndAdultBodyMasses[0];

                double TrophicIndex;
                switch (madingleyCohortDefinitions.GetTraitNames("nutrition source", actingCohort[0]))
                {
                case "herbivore":
                    TrophicIndex = 2;
                    break;

                case "omnivore":
                    TrophicIndex = 2.5;
                    break;

                case "carnivore":
                    TrophicIndex = 3;
                    break;

                default:
                    Debug.Fail("Unexpected nutrition source trait value when assigning trophic index");
                    TrophicIndex = 0.0;
                    break;
                }

                // Create the offspring cohort
                OffspringCohort = new Cohort((byte)actingCohort[0], OffspringJuvenileAndAdultBodyMasses[0], OffspringJuvenileAndAdultBodyMasses[1], OffspringJuvenileAndAdultBodyMasses[0],
                                             _OffspringCohortAbundance, Math.Exp(gridCellCohorts[actingCohort].LogOptimalPreyBodySizeRatio),
                                             (ushort)currentTimestep, gridCellCohorts[actingCohort].ProportionTimeActive, ref partial.NextCohortIDThreadLocked, TrophicIndex, tracker.TrackProcesses);

                // Add the offspring cohort to the grid cell cohorts array
                gridCellCohorts[actingCohort[0]].Add(OffspringCohort);

                // If track processes has been specified then add the new cohort to the process tracker
                if (tracker.TrackProcesses)
                {
                    tracker.RecordNewCohort((uint)cellEnvironment["LatIndex"][0], (uint)cellEnvironment["LonIndex"][0],
                                            currentTimestep, _OffspringCohortAbundance, gridCellCohorts[actingCohort].AdultMass, gridCellCohorts[actingCohort].FunctionalGroupIndex,
                                            gridCellCohorts[actingCohort].CohortID, (uint)partial.NextCohortIDThreadLocked);
                }

                // Subtract all of the reproductive potential mass of the parent cohort, which has been used to generate the new
                // cohort, from the delta reproductive potential mass and delta adult body mass
                deltas["reproductivebiomass"]["reproduction"] -= ReproductiveMassIncludingChangeThisTimeStep;
                deltas["biomass"]["reproduction"]             -= AdultMassLost;
            }
            else
            {
                // Organism is not large enough, or it is not the breeding season, so take no action
            }
        }
        /// <summary>
        /// Generate new cohorts from reproductive potential mass
        /// </summary>
        /// <param name="gridCellCohorts">The cohorts in the current grid cell</param>
        /// <param name="gridCellStocks">The stocks in the current grid cell</param>
        /// <param name="actingCohort">The position of the acting cohort in the jagged array of grid cell cohorts</param>
        /// <param name="cellEnvironment">The environment of the current grid cell</param>
        /// <param name="deltas">The sorted list to track changes in biomass and abundance of the acting cohort in this grid cell</param>
        /// <param name="madingleyCohortDefinitions">The definitions of cohort functional groups in the model</param>
        /// <param name="madingleyStockDefinitions">The definitions of stock functional groups in the model</param>
        /// <param name="currentTimestep">The current model time step</param>
        /// <param name="tracker">An instance of ProcessTracker to hold diagnostics for reproduction</param>
        /// <param name="partial">Thread-locked variables</param>
        /// <param name="iteroparous">Whether the acting cohort is iteroparous, as opposed to semelparous</param>
        /// <param name="currentMonth">The current model month</param>
        public void RunReproductionEvents(GridCellCohortHandler gridCellCohorts, GridCellStockHandler gridCellStocks,
            int[] actingCohort, SortedList<string, double[]> cellEnvironment, Dictionary<string, Dictionary<string, double>>
            deltas, FunctionalGroupDefinitions madingleyCohortDefinitions, FunctionalGroupDefinitions madingleyStockDefinitions,
            uint currentTimestep, ProcessTracker tracker, ref ThreadLockedParallelVariables partial, bool iteroparous, uint currentMonth)
        {
            // Adult non-reproductive biomass lost by semelparous organisms
            double AdultMassLost;

            // Offspring cohort abundance
            double _OffspringCohortAbundance;

            // Mass ratio of body mass + reproductive mass to adult body mass
            double CurrentMassRatio;

            // Individual body mass including change this time step as a result of other ecological processes
            double BodyMassIncludingChangeThisTimeStep;

            // Offspring juvenile and adult body masses
            double[] OffspringJuvenileAndAdultBodyMasses = new double[2];

            // Offspring cohort
            Cohort OffspringCohort;

            // Individual reproductive mass including change this time step as a result of other ecological processes
            double ReproductiveMassIncludingChangeThisTimeStep;

            // Calculate the biomass of an individual in this cohort including changes this time step from other ecological processes  
            BodyMassIncludingChangeThisTimeStep = 0.0;

            foreach (var Biomass in deltas["biomass"])
            {
                // Add the delta biomass to net biomass
                BodyMassIncludingChangeThisTimeStep += Biomass.Value;

            }

            BodyMassIncludingChangeThisTimeStep += gridCellCohorts[actingCohort].IndividualBodyMass;

            // Calculate the reproductive biomass of an individual in this cohort including changes this time step from other ecological processes  
            ReproductiveMassIncludingChangeThisTimeStep = 0.0;

            foreach (var ReproBiomass in deltas["reproductivebiomass"])
            {
                // Add the delta reproductive biomass to net biomass
                ReproductiveMassIncludingChangeThisTimeStep += ReproBiomass.Value;
            }

            ReproductiveMassIncludingChangeThisTimeStep += gridCellCohorts[actingCohort].IndividualReproductivePotentialMass;

            // Get the current ratio of total individual mass (including reproductive potential) to adult body mass
            CurrentMassRatio = (BodyMassIncludingChangeThisTimeStep + ReproductiveMassIncludingChangeThisTimeStep) / gridCellCohorts[actingCohort].AdultMass;

            // Must have enough mass to hit reproduction threshold criterion, and either (1) be in breeding season, or (2) be a marine cell (no breeding season in marine cells)
            if ((CurrentMassRatio > _MassRatioThreshold) && ((cellEnvironment["Breeding Season"][currentMonth] == 1.0) || ((cellEnvironment["Realm"][0] == 2.0))))
            {
                // Iteroparous and semelparous organisms have different strategies
                if (iteroparous)
                {
                    // Iteroparous organisms do not allocate any of their current non-reproductive biomass to reproduction
                    AdultMassLost = 0.0;

                    // Calculate the number of offspring that could be produced given the reproductive potential mass of individuals
                    _OffspringCohortAbundance = gridCellCohorts[actingCohort].CohortAbundance * ReproductiveMassIncludingChangeThisTimeStep /
                        gridCellCohorts[actingCohort].JuvenileMass;
                }
                else
                {
                    // Semelparous organisms allocate a proportion of their current non-reproductive biomass (including the effects of other ecological processes) to reproduction
                    AdultMassLost = _SemelparityAdultMassAllocation * BodyMassIncludingChangeThisTimeStep;

                    // Calculate the number of offspring that could be produced given the reproductive potential mass of individuals
                    _OffspringCohortAbundance = gridCellCohorts[actingCohort].CohortAbundance * (AdultMassLost + ReproductiveMassIncludingChangeThisTimeStep) /
                        gridCellCohorts[actingCohort].JuvenileMass;
                }

                // Check that the abundance in the cohort to produce is greater than or equal to zero
                Debug.Assert(_OffspringCohortAbundance >= 0.0, "Offspring abundance < 0");

                // Get the adult and juvenile masses of the offspring cohort
                OffspringJuvenileAndAdultBodyMasses = GetOffspringCohortProperties(gridCellCohorts, actingCohort, madingleyCohortDefinitions);

                // Update cohort abundance in case juvenile mass has been altered through 'evolution'
                _OffspringCohortAbundance = (_OffspringCohortAbundance * gridCellCohorts[actingCohort].JuvenileMass) / OffspringJuvenileAndAdultBodyMasses[0];

                double TrophicIndex;
                switch (madingleyCohortDefinitions.GetTraitNames("nutrition source", actingCohort[0]))
                {
                    case "herbivore":
                        TrophicIndex = 2;
                        break;
                    case "omnivore":
                        TrophicIndex = 2.5;
                        break;
                    case "carnivore":
                        TrophicIndex = 3;
                        break;
                    default:
                        Debug.Fail("Unexpected nutrition source trait value when assigning trophic index");
                        TrophicIndex = 0.0;
                        break;
                }

                // Create the offspring cohort
                OffspringCohort = new Cohort((byte)actingCohort[0], OffspringJuvenileAndAdultBodyMasses[0], OffspringJuvenileAndAdultBodyMasses[1], OffspringJuvenileAndAdultBodyMasses[0],
                                                    _OffspringCohortAbundance, Math.Exp(gridCellCohorts[actingCohort].LogOptimalPreyBodySizeRatio),
                                                    (ushort)currentTimestep, gridCellCohorts[actingCohort].ProportionTimeActive, ref partial.NextCohortIDThreadLocked, TrophicIndex, tracker.TrackProcesses);

                // Add the offspring cohort to the grid cell cohorts array
                gridCellCohorts[actingCohort[0]].Add(OffspringCohort);

                // If track processes has been specified then add the new cohort to the process tracker 
                if (tracker.TrackProcesses)
                    tracker.RecordNewCohort((uint)cellEnvironment["LatIndex"][0], (uint)cellEnvironment["LonIndex"][0],
                        currentTimestep, _OffspringCohortAbundance, gridCellCohorts[actingCohort].AdultMass, gridCellCohorts[actingCohort].FunctionalGroupIndex,
                        gridCellCohorts[actingCohort].CohortID, (uint)partial.NextCohortIDThreadLocked);

                // Subtract all of the reproductive potential mass of the parent cohort, which has been used to generate the new
                // cohort, from the delta reproductive potential mass and delta adult body mass
                deltas["reproductivebiomass"]["reproduction"] -= ReproductiveMassIncludingChangeThisTimeStep;
                deltas["biomass"]["reproduction"] -= AdultMassLost;

            }
            else
            {
                // Organism is not large enough, or it is not the breeding season, so take no action
            }

        }
        /// <summary>
        /// Calculate the potential number of prey that could be gained through predation on each cohort in the grid cell
        /// </summary>
        /// <param name="gridCellCohorts">The cohorts in the current grid cell</param>
        /// <param name="gridCellStocks">The stocks in the current grid cell</param>
        /// <param name="actingCohort">The acting cohort</param>
        /// <param name="cellEnvironment">The environment in the current grid cell</param>
        /// <param name="madingleyCohortDefinitions">The functional group definitions for cohorts in the model</param>
        /// <param name="madingleyStockDefinitions">The functional group definitions for stocks  in the model</param>
        public void GetEatingPotentialMarine(GridCellCohortHandler gridCellCohorts, GridCellStockHandler gridCellStocks, int[] actingCohort, 
            SortedList<string, double[]> cellEnvironment, FunctionalGroupDefinitions madingleyCohortDefinitions, FunctionalGroupDefinitions 
            madingleyStockDefinitions)
        {
            BinnedPreyDensities = new double[gridCellCohorts.Count, NumberOfBins];

            // Set the total eaten by the acting cohort to zero
            _TotalBiomassEatenByCohort = 0.0;

            // Set the total number of units to handle all potential prey individuals eaten to zero
            _TimeUnitsToHandlePotentialFoodItems = 0.0;

            // Get the individual body mass of the acting (predator) cohort
            _BodyMassPredator = gridCellCohorts[actingCohort].IndividualBodyMass;

            // Get the abundance of the acting (predator) cohort
            _AbundancePredator = gridCellCohorts[actingCohort].CohortAbundance;

            // Pre-calculate individual values for this predator to speed things up
            _SpecificPredatorKillRateConstant = _KillRateConstant * Math.Pow(_BodyMassPredator, (_KillRateConstantMassExponent));
            _SpecificPredatorTimeUnitsEatingPerGlobalTimeStep = _DeltaT * _ProportionOfTimeEating;
            _PredatorAssimilationEfficiency = _AssimilationEfficiency;
            _PredatorNonAssimilation = (1 - _AssimilationEfficiency);

            _DietIsAllSpecial = madingleyCohortDefinitions.GetTraitNames("Diet", actingCohort[0]) == "allspecial";

            _PredatorLogOptimalPreyBodySizeRatio = gridCellCohorts[actingCohort[0]][actingCohort[1]].LogOptimalPreyBodySizeRatio;

            // If a filter feeder, then optimal body size is a value not a ratio: convert it to a ratio to ensure that all calculations work correctly
            if (_DietIsAllSpecial)
            {
                // Optimal body size is actually a value, not a ratio, so convert it to a ratio based on the present body size
                _PredatorLogOptimalPreyBodySizeRatio = Math.Log(
                    Math.Exp(gridCellCohorts[actingCohort[0]][actingCohort[1]].LogOptimalPreyBodySizeRatio) / gridCellCohorts[actingCohort[0]][actingCohort[1]].IndividualBodyMass);
            }

            // Calculate the reference mass scaling ratio
            _ReferenceMassRatioScalingMarine = HandlingTimeScalarMarine * Math.Pow(_ReferenceMass / _BodyMassPredator, _HandlingTimeExponentMarine);

            _PredatorAbundanceMultipliedByTimeEating = _AbundancePredator * _SpecificPredatorTimeUnitsEatingPerGlobalTimeStep;

            LogPredatorMassPlusPredatorLogOptimalPreyBodySizeRatio = Math.Log(_BodyMassPredator) + _PredatorLogOptimalPreyBodySizeRatio;

            // Calculate the abundance of prey in each of the prey mass bins
            PopulateBinnedPreyAbundance(gridCellCohorts, actingCohort, FunctionalGroupIndicesToEat, LogPredatorMassPlusPredatorLogOptimalPreyBodySizeRatio);

            // Loop over potential prey functional groups
            foreach (int FunctionalGroup in FunctionalGroupIndicesToEat)
            {
                // Eating operates differently for planktivores
                // This can certainly be sped up
                if (_DietIsAllSpecial)
                {
                    // Loop over cohorts within the functional group
                    for (int i = 0; i < NumberCohortsPerFunctionalGroupNoNewCohorts[FunctionalGroup]; i++)
                    {
                        // Get the body mass of individuals in this cohort
                        _BodyMassPrey = gridCellCohorts[FunctionalGroup][i].IndividualBodyMass;

                        // Get the bin number of this prey cohort
                        PreyMassBinNumber = GetBinNumber(_BodyMassPrey, LogPredatorMassPlusPredatorLogOptimalPreyBodySizeRatio);

                        // Check whether
                        // The prey cohort is within the feeding range of the predator
                        // the prey cohort still exists in the model (i.e. body mass > 0)
                        // Currently having whales etc eat everything, but preferentially feed on very small things (i.e. filter feeders)
                    if ((_PlanktonFunctionalGroups[FunctionalGroup]) && (0 < PreyMassBinNumber) &&
                        (PreyMassBinNumber < NumberOfBins) && (_BodyMassPrey > 0))
                        {
                            // Calculate the potential abundance from this cohort eaten by the acting cohort
                            _PotentialAbundanceEaten[FunctionalGroup][i] = CalculateExpectedNumberKilledMarine(
                                gridCellCohorts[FunctionalGroup][i].CohortAbundance, _BodyMassPrey, PreyMassBinNumber, FunctionalGroup,
                                _BodyMassPredator, _CarnivoreFunctionalGroups[FunctionalGroup], _OmnivoreFunctionalGroups[FunctionalGroup],
                                _OmnivoreFunctionalGroups[actingCohort[0]], _PredatorLogOptimalPreyBodySizeRatio);

                            // Add the time required to handle the potential abundance eaten from this cohort to the cumulative total for all cohorts
                            _TimeUnitsToHandlePotentialFoodItems += _PotentialAbundanceEaten[FunctionalGroup][i] *
                                CalculateHandlingTimeMarine(_BodyMassPrey);
                        }
                        else
                        {
                            // Assign a potential abundance eaten of zero
                            _PotentialAbundanceEaten[FunctionalGroup][i] = 0.0;
                        }
                    }
                }
                else
                {

                    // Loop over cohorts within the functional group
                    for (int i = 0; i < NumberCohortsPerFunctionalGroupNoNewCohorts[FunctionalGroup]; i++)
                    {
                        // Get the body mass of individuals in this cohort
                        _BodyMassPrey = gridCellCohorts[FunctionalGroup][i].IndividualBodyMass;

                        // Get the bin number of this prey cohort
                        PreyMassBinNumber = GetBinNumber(_BodyMassPrey, LogPredatorMassPlusPredatorLogOptimalPreyBodySizeRatio);

                        // Check whether
                        // The prey cohort is within the feeding range of the predator
                        // the prey cohort still exists in the model (i.e. body mass > 0)
                        if ((0 < PreyMassBinNumber) && (PreyMassBinNumber < NumberOfBins) && (_BodyMassPrey > 0))
                        {
                            // Calculate the potential abundance from this cohort eaten by the acting cohort
                            _PotentialAbundanceEaten[FunctionalGroup][i] = CalculateExpectedNumberKilledMarine(
                               gridCellCohorts[FunctionalGroup][i].CohortAbundance, _BodyMassPrey, PreyMassBinNumber, FunctionalGroup,
                               _BodyMassPredator, _CarnivoreFunctionalGroups[FunctionalGroup], _OmnivoreFunctionalGroups[FunctionalGroup],
                               _OmnivoreFunctionalGroups[actingCohort[0]], _PredatorLogOptimalPreyBodySizeRatio);

                            // Add the time required to handle the potential abundance eaten from this cohort to the cumulative total for all cohorts
                            _TimeUnitsToHandlePotentialFoodItems += _PotentialAbundanceEaten[FunctionalGroup][i] *
                                CalculateHandlingTimeMarine(_BodyMassPrey);
                        }
                        else
                        {
                            // Assign a potential abundance eaten of zero
                            _PotentialAbundanceEaten[FunctionalGroup][i] = 0.0;
                        }
                    }
                }

            }

            // No cannibalism; do this outside the loop to speed up the calculations
            _TimeUnitsToHandlePotentialFoodItems -= PotentialAbundanceEaten[actingCohort[0]][actingCohort[1]] *
                CalculateHandlingTimeMarine(_BodyMassPredator);
            PotentialAbundanceEaten[actingCohort[0]][actingCohort[1]] = 0.0;
        }
        /// <summary>
        /// Run eating
        /// </summary>
        /// <param name="gridCellCohorts">The cohorts in the current grid cell</param>
        /// <param name="gridCellStocks">The stocks in the current grid cell</param>
        /// <param name="actingCohort">The position of the acting cohort in the jagged array of grid cell cohorts</param>
        /// <param name="cellEnvironment">The environment in the current grid cell</param>
        /// <param name="deltas">The sorted list to track changes in biomass and abundance of the acting cohort in this grid cell</param>
        /// <param name="madingleyCohortDefinitions">The definitions for cohort functional groups in the model</param>
        /// <param name="madingleyStockDefinitions">The definitions for stock functional groups in the model</param>
        /// <param name="currentTimestep">The current model time step</param>
        /// <param name="trackProcesses">An instance of ProcessTracker to hold diagnostics for eating</param>
        /// <param name="partial">Thread-locked variables</param>
        /// <param name="specificLocations">Whether the model is being run for specific locations</param>
        /// <param name="outputDetail">The level of output detail being used for the current model run</param>
        /// <param name="currentMonth">The current model month</param>
        /// <param name="initialisation">The Madingley Model initialisation</param>
        public void RunEcologicalProcess(GridCellCohortHandler gridCellCohorts, 
            GridCellStockHandler gridCellStocks, int[] actingCohort, 
            SortedList<string, double[]> cellEnvironment, 
            Dictionary<string, Dictionary<string, double>> deltas, 
            FunctionalGroupDefinitions madingleyCohortDefinitions, 
            FunctionalGroupDefinitions madingleyStockDefinitions, 
            uint currentTimestep, ProcessTracker trackProcesses, 
            ref ThreadLockedParallelVariables partial, Boolean specificLocations,
            string outputDetail, uint currentMonth, MadingleyModelInitialisation initialisation)
        {
            PreviousTrophicIndex = gridCellCohorts[actingCohort].TrophicIndex;
            //Reset this cohort's trohic index ready for calculation across its feeding this timetsstep
            gridCellCohorts[actingCohort].TrophicIndex = 0.0;

            // Get the nutrition source (herbivory, carnivory or omnivory) of the acting cohort
            string NutritionSource = madingleyCohortDefinitions.GetTraitNames("Nutrition source", gridCellCohorts[actingCohort].FunctionalGroupIndex);

            // Switch to the appropriate eating process(es) given the cohort's nutrition source
            switch (NutritionSource)
            {
                case "herbivore":

                    // Get the assimilation efficiency for herbivory for this cohort from the functional group definitions
                    Implementations["revised herbivory"].AssimilationEfficiency =
                        madingleyCohortDefinitions.GetBiologicalPropertyOneFunctionalGroup
                        ("herbivory assimilation", gridCellCohorts[actingCohort].FunctionalGroupIndex);

                    // Get the proportion of time spent eating for this cohort from the functional group definitions
                    Implementations["revised herbivory"].ProportionTimeEating = gridCellCohorts[actingCohort].ProportionTimeActive;

                    // Calculate the potential biomass available from herbivory
                    if (cellEnvironment["Realm"][0] == 2.0)
                        Implementations["revised herbivory"].GetEatingPotentialMarine
                        (gridCellCohorts, gridCellStocks, actingCohort,
                        cellEnvironment, madingleyCohortDefinitions, madingleyStockDefinitions);
                    else

                        Implementations["revised herbivory"].GetEatingPotentialTerrestrial
                        (gridCellCohorts, gridCellStocks, actingCohort,
                        cellEnvironment, madingleyCohortDefinitions, madingleyStockDefinitions);

                    // Run herbivory to apply changes in autotroph biomass from herbivory and add biomass eaten to the delta arrays
                    Implementations["revised herbivory"].RunEating
                        (gridCellCohorts, gridCellStocks, actingCohort,
                        cellEnvironment, deltas, madingleyCohortDefinitions,
                        madingleyStockDefinitions, trackProcesses,
                        currentTimestep, specificLocations,outputDetail, initialisation);

                    break;

                case "carnivore":

                    // Get the assimilation efficiency for predation for this cohort from the functional group definitions
                    Implementations["revised predation"].AssimilationEfficiency =
                        madingleyCohortDefinitions.GetBiologicalPropertyOneFunctionalGroup
                        ("carnivory assimilation", gridCellCohorts[actingCohort].FunctionalGroupIndex);

                    Implementations["revised predation"].ProportionTimeEating = gridCellCohorts[actingCohort].ProportionTimeActive;

                    // Calculate the potential biomass available from predation
                    if (cellEnvironment["Realm"][0] == 2.0)
                        Implementations["revised predation"].GetEatingPotentialMarine
                        (gridCellCohorts, gridCellStocks, actingCohort,
                        cellEnvironment, madingleyCohortDefinitions, madingleyStockDefinitions);
                    else
                        Implementations["revised predation"].GetEatingPotentialTerrestrial
                        (gridCellCohorts, gridCellStocks, actingCohort,
                        cellEnvironment, madingleyCohortDefinitions, madingleyStockDefinitions);
                    // Run predation to apply changes in prey biomass from predation and add biomass eaten to the delta arrays
                    Implementations["revised predation"].RunEating
                        (gridCellCohorts, gridCellStocks, actingCohort, cellEnvironment, deltas,
                        madingleyCohortDefinitions, madingleyStockDefinitions, trackProcesses,
                        currentTimestep, specificLocations,outputDetail, initialisation);

                    break;

                case "omnivore":

                    // Get the assimilation efficiency for predation for this cohort from the functional group definitions
                    Implementations["revised predation"].AssimilationEfficiency =
                        madingleyCohortDefinitions.GetBiologicalPropertyOneFunctionalGroup
                        ("carnivory assimilation", gridCellCohorts[actingCohort].FunctionalGroupIndex);

                    // Get the assimilation efficiency for herbivory for this cohort from the functional group definitions
                    Implementations["revised herbivory"].AssimilationEfficiency =
                        madingleyCohortDefinitions.GetBiologicalPropertyOneFunctionalGroup
                        ("herbivory assimilation", gridCellCohorts[actingCohort].FunctionalGroupIndex);

                    // Get the proportion of time spent eating and assign to both the herbivory and predation implementations
                    double ProportionTimeEating = gridCellCohorts[actingCohort].ProportionTimeActive;
                    Implementations["revised predation"].ProportionTimeEating = ProportionTimeEating;
                    Implementations["revised herbivory"].ProportionTimeEating = ProportionTimeEating;

                    // Calculate the potential biomass available from herbivory
                    if (cellEnvironment["Realm"][0] == 2.0)
                        Implementations["revised herbivory"].GetEatingPotentialMarine
                        (gridCellCohorts, gridCellStocks, actingCohort,
                        cellEnvironment, madingleyCohortDefinitions,
                        madingleyStockDefinitions);
                    else
                        Implementations["revised herbivory"].GetEatingPotentialTerrestrial
                        (gridCellCohorts, gridCellStocks, actingCohort,
                        cellEnvironment, madingleyCohortDefinitions,
                        madingleyStockDefinitions);

                    // Calculate the potential biomass available from predation
                    if (cellEnvironment["Realm"][0] == 2.0)
                        Implementations["revised predation"].GetEatingPotentialMarine
                        (gridCellCohorts, gridCellStocks, actingCohort,
                        cellEnvironment, madingleyCohortDefinitions,
                        madingleyStockDefinitions);
                    else
                        Implementations["revised predation"].GetEatingPotentialTerrestrial
                        (gridCellCohorts, gridCellStocks, actingCohort,
                        cellEnvironment, madingleyCohortDefinitions,
                        madingleyStockDefinitions);

                    // Calculate the total handling time for all expected kills from predation and expected plant matter eaten in herbivory
                    TotalTimeToEatForOmnivores =
                        Implementations["revised herbivory"].TimeUnitsToHandlePotentialFoodItems +
                        Implementations["revised predation"].TimeUnitsToHandlePotentialFoodItems;

                    // Assign this total time to the relevant variables in both herbviory and predation, so that actual amounts eaten are calculated correctly
                    Implementations["revised herbivory"].TimeUnitsToHandlePotentialFoodItems = TotalTimeToEatForOmnivores;
                    Implementations["revised predation"].TimeUnitsToHandlePotentialFoodItems = TotalTimeToEatForOmnivores;

                    // Run predation to update prey cohorts and delta biomasses for the acting cohort
                    Implementations["revised predation"].RunEating
                        (gridCellCohorts, gridCellStocks, actingCohort,
                        cellEnvironment, deltas, madingleyCohortDefinitions,
                        madingleyStockDefinitions, trackProcesses,
                        currentTimestep, specificLocations,outputDetail, initialisation);

                    // Run herbivory to update autotroph biomass and delta biomasses for the acting cohort
                    Implementations["revised herbivory"].RunEating
                        (gridCellCohorts, gridCellStocks, actingCohort,
                        cellEnvironment, deltas, madingleyCohortDefinitions,
                        madingleyStockDefinitions, trackProcesses,
                        currentTimestep, specificLocations,outputDetail, initialisation);

                    break;

                default:

                    // For nutrition source that are not supported, throw an error
                    Debug.Fail("The model currently does not contain an eating model for nutrition source:" + NutritionSource);

                    break;

            }

            // Check that the biomasses from predation and herbivory in the deltas is a number
            Debug.Assert(!double.IsNaN(deltas["biomass"]["predation"]), "BiomassFromEating is NaN");
            Debug.Assert(!double.IsNaN(deltas["biomass"]["herbivory"]), "BiomassFromEating is NaN");

            double biomassEaten = 0.0;
            if (madingleyCohortDefinitions.GetBiologicalPropertyOneFunctionalGroup("carnivory assimilation",
                gridCellCohorts[actingCohort].FunctionalGroupIndex) > 0)
            {
                biomassEaten += (deltas["biomass"]["predation"] / madingleyCohortDefinitions.GetBiologicalPropertyOneFunctionalGroup("carnivory assimilation",
                gridCellCohorts[actingCohort].FunctionalGroupIndex));
            }
            if (madingleyCohortDefinitions.GetBiologicalPropertyOneFunctionalGroup("herbivory assimilation",
                gridCellCohorts[actingCohort].FunctionalGroupIndex) > 0)
            {
                biomassEaten += (deltas["biomass"]["herbivory"]/madingleyCohortDefinitions.GetBiologicalPropertyOneFunctionalGroup("herbivory assimilation",
                gridCellCohorts[actingCohort].FunctionalGroupIndex));
            }

            if (biomassEaten > 0.0)
            {
                gridCellCohorts[actingCohort].TrophicIndex = 1 +
                    (gridCellCohorts[actingCohort].TrophicIndex / (biomassEaten * gridCellCohorts[actingCohort].CohortAbundance));
            }
            else
            {
                gridCellCohorts[actingCohort].TrophicIndex = PreviousTrophicIndex;
            }
        }
        /// <summary>
        /// Initialises predation implementation each time step
        /// </summary>
        /// <param name="gridCellCohorts">The cohorts in the current grid cell</param>
        /// <param name="gridCellStocks">The stocks in the current grid cell</param>
        /// <param name="madingleyCohortDefinitions">The definitions for cohorts in the model</param>
        /// <param name="madingleyStockDefinitions">The definitions for stocks in the model</param>
        /// <remarks>This only works if: a) predation is initialised in every grid cell; and b) if parallelisation is done by latitudinal strips
        /// It is critical to run this every time step</remarks>
        public void InitializeEatingPerTimeStep(GridCellCohortHandler gridCellCohorts, GridCellStockHandler gridCellStocks, FunctionalGroupDefinitions madingleyCohortDefinitions, FunctionalGroupDefinitions madingleyStockDefinitions)
        {
            // Get the functional group indices of all heterotroph cohorts (i.e. potential prey)
            _FunctionalGroupIndicesToEat = madingleyCohortDefinitions.GetFunctionalGroupIndex("Heterotroph/Autotroph", "heterotroph", false);

            // Initialise the vector to hold the number of cohorts in each functional group at the start of the time step
            NumberCohortsPerFunctionalGroupNoNewCohorts = new int[gridCellCohorts.Count];

            // Initialise the jagged arrays to hold the potential and actual numbers of prey eaten in each of the grid cell cohorts
            _AbundancesEaten = new double[gridCellCohorts.Count][];
            _PotentialAbundanceEaten = new double[gridCellCohorts.Count][];

            // Initialise the vector to identify carnivore cohorts
            _CarnivoreFunctionalGroups = new Boolean[_FunctionalGroupIndicesToEat.Length];
            _OmnivoreFunctionalGroups = new Boolean[_FunctionalGroupIndicesToEat.Length];
            _PlanktonFunctionalGroups = new Boolean[_FunctionalGroupIndicesToEat.Length];

            // Loop over rows in the jagged arrays, initialise each vector within the jagged arrays, and calculate the current number of cohorts in
            // each functional group
            for (int i = 0; i < gridCellCohorts.Count; i++)
            {
                // Calculate the current number of cohorts in this functional group
                int NumCohortsThisFG = gridCellCohorts[i].Count;
                NumberCohortsPerFunctionalGroupNoNewCohorts[i] = NumCohortsThisFG;
                // Initialise the jagged arrays
                _AbundancesEaten[i] = new double[NumberCohortsPerFunctionalGroupNoNewCohorts[i]];
                _PotentialAbundanceEaten[i] = new double[NumberCohortsPerFunctionalGroupNoNewCohorts[i]];
            }

            // Loop over functional groups that are potential prey and determine which are carnivores
            foreach (int FunctionalGroup in FunctionalGroupIndicesToEat)
                _CarnivoreFunctionalGroups[FunctionalGroup] = madingleyCohortDefinitions.GetTraitNames("Nutrition source", FunctionalGroup) == "carnivore";

            foreach (int FunctionalGroup in FunctionalGroupIndicesToEat)
                _OmnivoreFunctionalGroups[FunctionalGroup] = madingleyCohortDefinitions.GetTraitNames("Nutrition source", FunctionalGroup) == "omnivore";

            foreach (int FunctionalGroup in FunctionalGroupIndicesToEat)
                _PlanktonFunctionalGroups[FunctionalGroup] = madingleyCohortDefinitions.GetTraitNames("Mobility", FunctionalGroup) == "planktonic";
        }
Пример #24
0
        /// <summary>
        /// Run eating
        /// </summary>
        /// <param name="gridCellCohorts">The cohorts in the current grid cell</param>
        /// <param name="gridCellStocks">The stocks in the current grid cell</param>
        /// <param name="actingCohort">The position of the acting cohort in the jagged array of grid cell cohorts</param>
        /// <param name="cellEnvironment">The environment in the current grid cell</param>
        /// <param name="deltas">The sorted list to track changes in biomass and abundance of the acting cohort in this grid cell</param>
        /// <param name="madingleyCohortDefinitions">The definitions for cohort functional groups in the model</param>
        /// <param name="madingleyStockDefinitions">The definitions for stock functional groups in the model</param>
        /// <param name="currentTimestep">The current model time step</param>
        /// <param name="trackProcesses">An instance of ProcessTracker to hold diagnostics for eating</param>
        /// <param name="partial">Thread-locked variables</param>
        /// <param name="specificLocations">Whether the model is being run for specific locations</param>
        /// <param name="outputDetail">The level of output detail being used for the current model run</param>
        /// <param name="currentMonth">The current model month</param>
        /// <param name="initialisation">The Madingley Model initialisation</param>
        public void RunEcologicalProcess(GridCellCohortHandler gridCellCohorts,
                                         GridCellStockHandler gridCellStocks, int[] actingCohort,
                                         SortedList <string, double[]> cellEnvironment,
                                         Dictionary <string, Dictionary <string, double> > deltas,
                                         FunctionalGroupDefinitions madingleyCohortDefinitions,
                                         FunctionalGroupDefinitions madingleyStockDefinitions,
                                         uint currentTimestep, ProcessTracker trackProcesses,
                                         ref ThreadLockedParallelVariables partial, Boolean specificLocations,
                                         string outputDetail, uint currentMonth, MadingleyModelInitialisation initialisation)
        {
            PreviousTrophicIndex = gridCellCohorts[actingCohort].TrophicIndex;
            //Reset this cohort's trohic index ready for calculation across its feeding this timetsstep
            gridCellCohorts[actingCohort].TrophicIndex = 0.0;

            // Get the nutrition source (herbivory, carnivory or omnivory) of the acting cohort
            string NutritionSource = madingleyCohortDefinitions.GetTraitNames("Nutrition source", gridCellCohorts[actingCohort].FunctionalGroupIndex);

            // Switch to the appropriate eating process(es) given the cohort's nutrition source
            switch (NutritionSource)
            {
            case "herbivore":

                // Get the assimilation efficiency for herbivory for this cohort from the functional group definitions
                Implementations["revised herbivory"].AssimilationEfficiency =
                    madingleyCohortDefinitions.GetBiologicalPropertyOneFunctionalGroup
                        ("herbivory assimilation", gridCellCohorts[actingCohort].FunctionalGroupIndex);

                // Get the proportion of time spent eating for this cohort from the functional group definitions
                Implementations["revised herbivory"].ProportionTimeEating = gridCellCohorts[actingCohort].ProportionTimeActive;

                // Calculate the potential biomass available from herbivory
                if (cellEnvironment["Realm"][0] == 2.0)
                {
                    Implementations["revised herbivory"].GetEatingPotentialMarine
                        (gridCellCohorts, gridCellStocks, actingCohort,
                        cellEnvironment, madingleyCohortDefinitions, madingleyStockDefinitions);
                }
                else
                {
                    Implementations["revised herbivory"].GetEatingPotentialTerrestrial
                        (gridCellCohorts, gridCellStocks, actingCohort,
                        cellEnvironment, madingleyCohortDefinitions, madingleyStockDefinitions);
                }

                // Run herbivory to apply changes in autotroph biomass from herbivory and add biomass eaten to the delta arrays
                Implementations["revised herbivory"].RunEating
                    (gridCellCohorts, gridCellStocks, actingCohort,
                    cellEnvironment, deltas, madingleyCohortDefinitions,
                    madingleyStockDefinitions, trackProcesses,
                    currentTimestep, specificLocations, outputDetail, initialisation);

                break;

            case "carnivore":

                // Get the assimilation efficiency for predation for this cohort from the functional group definitions
                Implementations["revised predation"].AssimilationEfficiency =
                    madingleyCohortDefinitions.GetBiologicalPropertyOneFunctionalGroup
                        ("carnivory assimilation", gridCellCohorts[actingCohort].FunctionalGroupIndex);

                Implementations["revised predation"].ProportionTimeEating = gridCellCohorts[actingCohort].ProportionTimeActive;

                // Calculate the potential biomass available from predation
                if (cellEnvironment["Realm"][0] == 2.0)
                {
                    Implementations["revised predation"].GetEatingPotentialMarine
                        (gridCellCohorts, gridCellStocks, actingCohort,
                        cellEnvironment, madingleyCohortDefinitions, madingleyStockDefinitions);
                }
                else
                {
                    Implementations["revised predation"].GetEatingPotentialTerrestrial
                        (gridCellCohorts, gridCellStocks, actingCohort,
                        cellEnvironment, madingleyCohortDefinitions, madingleyStockDefinitions);
                }
                // Run predation to apply changes in prey biomass from predation and add biomass eaten to the delta arrays
                Implementations["revised predation"].RunEating
                    (gridCellCohorts, gridCellStocks, actingCohort, cellEnvironment, deltas,
                    madingleyCohortDefinitions, madingleyStockDefinitions, trackProcesses,
                    currentTimestep, specificLocations, outputDetail, initialisation);


                break;

            case "omnivore":

                // Get the assimilation efficiency for predation for this cohort from the functional group definitions
                Implementations["revised predation"].AssimilationEfficiency =
                    madingleyCohortDefinitions.GetBiologicalPropertyOneFunctionalGroup
                        ("carnivory assimilation", gridCellCohorts[actingCohort].FunctionalGroupIndex);

                // Get the assimilation efficiency for herbivory for this cohort from the functional group definitions
                Implementations["revised herbivory"].AssimilationEfficiency =
                    madingleyCohortDefinitions.GetBiologicalPropertyOneFunctionalGroup
                        ("herbivory assimilation", gridCellCohorts[actingCohort].FunctionalGroupIndex);

                // Get the proportion of time spent eating and assign to both the herbivory and predation implementations
                double ProportionTimeEating = gridCellCohorts[actingCohort].ProportionTimeActive;
                Implementations["revised predation"].ProportionTimeEating = ProportionTimeEating;
                Implementations["revised herbivory"].ProportionTimeEating = ProportionTimeEating;

                // Calculate the potential biomass available from herbivory
                if (cellEnvironment["Realm"][0] == 2.0)
                {
                    Implementations["revised herbivory"].GetEatingPotentialMarine
                        (gridCellCohorts, gridCellStocks, actingCohort,
                        cellEnvironment, madingleyCohortDefinitions,
                        madingleyStockDefinitions);
                }
                else
                {
                    Implementations["revised herbivory"].GetEatingPotentialTerrestrial
                        (gridCellCohorts, gridCellStocks, actingCohort,
                        cellEnvironment, madingleyCohortDefinitions,
                        madingleyStockDefinitions);
                }

                // Calculate the potential biomass available from predation
                if (cellEnvironment["Realm"][0] == 2.0)
                {
                    Implementations["revised predation"].GetEatingPotentialMarine
                        (gridCellCohorts, gridCellStocks, actingCohort,
                        cellEnvironment, madingleyCohortDefinitions,
                        madingleyStockDefinitions);
                }
                else
                {
                    Implementations["revised predation"].GetEatingPotentialTerrestrial
                        (gridCellCohorts, gridCellStocks, actingCohort,
                        cellEnvironment, madingleyCohortDefinitions,
                        madingleyStockDefinitions);
                }

                // Calculate the total handling time for all expected kills from predation and expected plant matter eaten in herbivory
                TotalTimeToEatForOmnivores =
                    Implementations["revised herbivory"].TimeUnitsToHandlePotentialFoodItems +
                    Implementations["revised predation"].TimeUnitsToHandlePotentialFoodItems;

                // Assign this total time to the relevant variables in both herbviory and predation, so that actual amounts eaten are calculated correctly
                Implementations["revised herbivory"].TimeUnitsToHandlePotentialFoodItems = TotalTimeToEatForOmnivores;
                Implementations["revised predation"].TimeUnitsToHandlePotentialFoodItems = TotalTimeToEatForOmnivores;

                // Run predation to update prey cohorts and delta biomasses for the acting cohort
                Implementations["revised predation"].RunEating
                    (gridCellCohorts, gridCellStocks, actingCohort,
                    cellEnvironment, deltas, madingleyCohortDefinitions,
                    madingleyStockDefinitions, trackProcesses,
                    currentTimestep, specificLocations, outputDetail, initialisation);

                // Run herbivory to update autotroph biomass and delta biomasses for the acting cohort
                Implementations["revised herbivory"].RunEating
                    (gridCellCohorts, gridCellStocks, actingCohort,
                    cellEnvironment, deltas, madingleyCohortDefinitions,
                    madingleyStockDefinitions, trackProcesses,
                    currentTimestep, specificLocations, outputDetail, initialisation);

                break;

            default:

                // For nutrition source that are not supported, throw an error
                Debug.Fail("The model currently does not contain an eating model for nutrition source:" + NutritionSource);

                break;
            }

            // Check that the biomasses from predation and herbivory in the deltas is a number
            Debug.Assert(!double.IsNaN(deltas["biomass"]["predation"]), "BiomassFromEating is NaN");
            Debug.Assert(!double.IsNaN(deltas["biomass"]["herbivory"]), "BiomassFromEating is NaN");

            double biomassEaten = 0.0;

            if (madingleyCohortDefinitions.GetBiologicalPropertyOneFunctionalGroup("carnivory assimilation",
                                                                                   gridCellCohorts[actingCohort].FunctionalGroupIndex) > 0)
            {
                biomassEaten += (deltas["biomass"]["predation"] / madingleyCohortDefinitions.GetBiologicalPropertyOneFunctionalGroup("carnivory assimilation",
                                                                                                                                     gridCellCohorts[actingCohort].FunctionalGroupIndex));
            }
            if (madingleyCohortDefinitions.GetBiologicalPropertyOneFunctionalGroup("herbivory assimilation",
                                                                                   gridCellCohorts[actingCohort].FunctionalGroupIndex) > 0)
            {
                biomassEaten += (deltas["biomass"]["herbivory"] / madingleyCohortDefinitions.GetBiologicalPropertyOneFunctionalGroup("herbivory assimilation",
                                                                                                                                     gridCellCohorts[actingCohort].FunctionalGroupIndex));
            }

            if (biomassEaten > 0.0)
            {
                gridCellCohorts[actingCohort].TrophicIndex = 1 +
                                                             (gridCellCohorts[actingCohort].TrophicIndex / (biomassEaten * gridCellCohorts[actingCohort].CohortAbundance));
            }
            else
            {
                gridCellCohorts[actingCohort].TrophicIndex = PreviousTrophicIndex;
            }
        }