//---------------------------------------------------------------------

        protected SpeciesParameters[] ReadSpeciesParameters()
        {
            speciesLineNumbers.Clear();  // for re-use during unit testing

            SpeciesParameters[] allSpeciesParameters = new SpeciesParameters[speciesDataset.Count];

            InputVar <string> speciesName      = new InputVar <string>("Species");
            InputVar <int>    minSeeds         = new InputVar <int>("Minimum Seeds Produced");
            InputVar <int>    maxSeeds         = new InputVar <int>("Maximum Seeds Produced");
            InputVar <double> leafArea         = new InputVar <double>("Seedling Leaf Area");
            InputVar <double> dispersalMean1   = new InputVar <double>("Dispersal Mean1");
            InputVar <double> dispersalMean2   = new InputVar <double>("Dispersal Mean2");
            InputVar <double> dispersalWeight1 = new InputVar <double>("Dispersal Weight1");

            string lastColumn = "the " + dispersalWeight1.Name + " column";

            while (!AtEndOfInput && CurrentName != Names.EmergenceProbabilities)
            {
                StringReader currentLine = new StringReader(CurrentLine);

                ReadValue(speciesName, currentLine);
                ISpecies species = ValidateSpeciesName(speciesName);

                SpeciesParameters parameters = new SpeciesParameters();

                ReadValue(minSeeds, currentLine);
                parameters.MinSeedsProduced = minSeeds.Value;

                ReadValue(maxSeeds, currentLine);
                parameters.MaxSeedsProduced = maxSeeds.Value;

                ReadValue(leafArea, currentLine);
                parameters.LeafArea = leafArea.Value;

                ReadValue(dispersalMean1, currentLine);
                parameters.DispersalMean1 = dispersalMean1.Value;

                ReadValue(dispersalMean2, currentLine);
                parameters.DispersalMean2 = dispersalMean2.Value;

                ReadValue(dispersalWeight1, currentLine);
                parameters.DispersalWeight1 = dispersalWeight1.Value;

                CheckNoDataAfter(lastColumn, currentLine);
                allSpeciesParameters[species.Index] = parameters;
                GetNextLine();
            }

            if (speciesLineNumbers.Count == 0)
            {
                throw NewParseException("Expected a line starting with a species name");
            }

            return(allSpeciesParameters);
        }
        //---------------------------------------------------------------------

        protected void ReadProbabilities(string tableName,
                                         string probabilityName,
                                         string nameAfterTable,
                                         SpeciesParameters[] allSpeciesParameters,
                                         GetProbabilities getProbabilities)
        {
            ReadName(tableName);

            speciesLineNumbers.Clear();

            InputVar <string> speciesName = new InputVar <string>("Species");
            InputVar <double> probability = new InputVar <double>(probabilityName);

            IEcoregion lastEcoregion = Model.Core.Ecoregions[Model.Core.Ecoregions.Count - 1];
            string     lastColumn    = "the " + lastEcoregion.Name + " ecoregion column";

            while (!AtEndOfInput && CurrentName != nameAfterTable)
            {
                StringReader currentLine = new StringReader(CurrentLine);

                ReadValue(speciesName, currentLine);
                ISpecies species = ValidateSpeciesName(speciesName);

                SpeciesParameters parameters    = allSpeciesParameters[species.Index];
                double[]          probabilities = getProbabilities(parameters);

                foreach (IEcoregion ecoregion in Model.Core.Ecoregions)
                {
                    ReadValue(probability, currentLine);
                    if (probability.Value < 0.0 || probability.Value > 1.0)
                    {
                        throw new InputValueException(probability.Value.String,
                                                      "Probability for ecoregion " + ecoregion.Name + " is not between 0.0 and 1.0");
                    }
                    probabilities[ecoregion.Index] = probability.Value;
                }

                CheckNoDataAfter(lastColumn, currentLine);
                GetNextLine();
            }
        }
        //---------------------------------------------------------------------

        /// <summary>
        /// Initializes the demographic seeding algorithm
        /// </summary>
        /// <param name="successionTimestep">
        /// The length of the succession extension's timestep (units: years).
        /// </param>
        public Algorithm(int successionTimestep)
        {
            int numTimeSteps;  // the number of succession time steps to loop over
            int maxCohortAge;  // maximum age allowed for any species, in years

            numTimeSteps = (Model.Core.EndTime - Model.Core.StartTime) / successionTimestep;
            maxCohortAge = 0;
            foreach (ISpecies species in Model.Core.Species)
            {
                if (species.Longevity > maxCohortAge)
                {
                    maxCohortAge = species.Longevity;
                }
            }

            // The library's code comments say max_age_steps represents
            // "maximum age allowed for any species, in years", but it's
            // used in the code as though it represents the maximum age of
            // any species' cohort IN NUMBER OF SUCCESSION TIMESTEPS.  So if
            // the oldest species' Longevity is 2,000 years, and the succession
            // timestep is 10 years, then max_age_steps is 200 timesteps.
            int max_age_steps = maxCohortAge / successionTimestep;

            seedingData = new Seed_Dispersal.Map(
                Model.Core.Landscape.Columns,
                Model.Core.Landscape.Rows,
                Model.Core.Species.Count,
                numTimeSteps,
                successionTimestep,
                Model.Core.Ecoregions.Count,
                max_age_steps);
            seedingData.pixel_size = Model.Core.CellLength;

            // Initialize some species parameters from the core.
            foreach (ISpecies species in Model.Core.Species)
            {
                seedingData.all_species[species.Index].shade_tolerance  = species.ShadeTolerance;
                seedingData.all_species[species.Index].reproductive_age = species.Maturity;
            }

            // Load user-specified parameters
            string path = "demographic-seeding.txt";  // hard-wired for now, so no changes required to succession extensions

            Model.Core.UI.WriteLine("Reading demographic seeding parameters from {0} ...", path);
            ParameterParser parser     = new ParameterParser(Model.Core.Species);
            Parameters      parameters = Landis.Data.Load <Parameters>(path, parser);

            seedingData.dispersal_model  = parameters.Kernel;
            seedingData.seed_model       = parameters.SeedProductionModel;
            seedingData.mc_draws         = parameters.MonteCarloDraws;
            seedingData.max_leaf_area    = parameters.MaxLeafArea;
            seedingData.cohort_threshold = parameters.CohortThreshold;

            seedRainMaps          = parameters.SeedRainMaps;
            seedlingEmergenceMaps = parameters.SeedlingEmergenceMaps;

            dispersalProbabilitiesFilename = parameters.DispersalProbabilitiesLog;
            if (dispersalProbabilitiesFilename != null)
            {
                // Truncate DispersalProbabilitiesLog file and write header
                using (System.IO.StreamWriter file = new System.IO.StreamWriter(dispersalProbabilitiesFilename, false))
                {
                    file.WriteLine("Timestep, Species, Distance, Probability");
                }
            }

            foreach (ISpecies species in Model.Core.Species)
            {
                SpeciesParameters speciesParameters = parameters.SpeciesParameters[species.Index];
                seedingData.all_species[species.Index].min_seed  = speciesParameters.MinSeedsProduced;
                seedingData.all_species[species.Index].max_seed  = speciesParameters.MaxSeedsProduced;
                seedingData.all_species[species.Index].leaf_area = speciesParameters.LeafArea;
                CopyArray(speciesParameters.DispersalParameters,
                          seedingData.all_species[species.Index].dispersal_parameters);
                CopyArray(speciesParameters.EmergenceProbabilities,
                          seedingData.emergence_probability[species.Index]);
                CopyArray(speciesParameters.SurvivalProbabilities,
                          seedingData.survival_probability[species.Index]);
            }

            seedingData.Initialize();
            WriteProbabilities();
        }