#!/usr/bin/env python ##\file ##\brief Top level file for the ecological modeling. ##\detail This file describes the model proper and the major # model-specific coding elements used to build this model. ##\section Includes Included Libraries # The file makes use of the following libraries # - Standard Python Libraries # - copy # - exceptions # - itertools # - re # - StringIO # - sys # - time # - Third Party Libraries # - pandas version 0.13.1 # - xlrd # - Model defined libraries # - config # - event # - function # - landscape # STD Python modules import copy import csv import exceptions import itertools import math import numpy from collections import OrderedDict import re import StringIO import sys import time # Third party modules import pandas import xlrd # This model's modules import config import event import landscape import function ##\class SpeciesModel ##\brief Base class for all species models. ##\detail This is a generic base class defining the functionality # of a single species model for a single location in the landscape. # All species models should inherit # from this class to make sure they get the basic elements. ##\role{Ecology/Machinery} class SpeciesModel(object): ##\brief A reference to the model Params object params = None ##\brief A reference to the model DispersalModel object dspModel = None ## Class constructor. #\param [in] self The object's reference to itself. #\param [in] index The species index. #\param [in] abr The species USDA alpha-numeric vegetation code. #\param [in] name The full species name. #\param [in] modelType The type of a model. #\param [in] cover The fraction of model cell that is covered by a species. #\param [in] loc The x,y location of a cell. Given as the row,column index with in the model grid. def __init__(self, index=0, name='', abr='', modelType='', cover=0, cellID=0): ##\brief Species index ##\details An integer that is a unique numerical species index. ## - Type: integer ## - Range: [0, inf) self.index = index ##\brief The long name for a species ##\details A string used to make output pretty ## - Type: string self.name = name ##\brief Species code ##\details A string abbreviation for the species. The value is the species' USDA alpha-numeric vegetation code. # This is an important member value in the model. It is used as the key in the dictionary that stores the list # of species in a patch. ## - Type: string self.abr = abr ##\brief The model type ##\details A string containing the model type. ##\n ## This member should one of the following values (as a string):\n # - BarrierIslandSpeciesModel # - EmergentWetlandModel # - UplandForestModel # - SAVModel # - NullModel ##\n self.modelType = modelType ##\brief The area occupied by a species ##\details This is the fraction of a patch covered by a species. ## - Type: float ## - Range: [0, inf) self.cover = cover ##\brief Species Location ##\details This is the location of a species. It is stored as a the index of a cell. ## - Type: int self.cellID = cellID ##\brief Object configuration. #\param self The object's reference to itself. #\param index The species index. #\param abr The species USDA alpha-numeric vegetation code. #\param name The full species name. #\param modelType The type of a model. #\param cover The fraction of model cell that is covered by a species. #\param loc The x,y location of a cell. Given as the row,column index with in the model grid. ##\detail This function is used to configure an object after it has been created. # this is used SpeciesModelList when it is creating the list of species from # the species growth/senescence tables and by PatchModel when creating the # individual species model for each location. def config(self, index=0, name='', abr='', modelType='', cover=0, cellID=0): self.index = index self.name = name self.abr = abr self.modelType = modelType self.cover = cover self.cellID = cellID ##\brief Computes the probability of senescence. ##\detail This is a generic function that returns the probability of plants senesencing. # This function should be redefined by each class that inherits form SpeciesModel. def senescence(self): return 0.0 ##\brief Computes the probability of establishment ##\details This is a generic function that returns the probability of plats growing. # This function should be redefined by each class that inherits from SpeciesModel. def growth(self): return 0.0 def __float__(self): return self.cover def __str__(self): ret = str(self.index) + ', ' ret += self.name + ', ' ret += self.abr + ', ' ret += self.modelType + ', ' ret += str(self.cover) + ', ' ret += str(self.cellID) return ret ##\class NullModel ##\brief A place holder. ##\role{Machinery} ##\detail A place holder for habitat and vegetation types that # do not really do anything. Having this class makes # the rest of the model code simpler. class NullModel(SpeciesModel): def __init__(self, index=0, name='', abr='', cover=0, cellID=0): SpeciesModel.__init__(self, index, name, abr, 'NullModel', cover, cellID) ##\brief ##\detail def senescence(self): return 0.0 ##\brief ##\detail def growth(self): return 0.0 ##\class UplandForestModel ##\brief This class handles the ecology for the upland forest species. ##\role{Ecology} ##\detail Upland forest types change state based on the # height of the habitat above mean water surface. class UplandForestModel(SpeciesModel): def __init__(self, index=0, name='', abr='', cover=0, cellID=0, Pdata=None, Ddata=None): SpeciesModel.__init__(self, index, name, abr, 'UplandForestModel', cover, cellID) self.P = function.Function(Pdata['elvValue'], Pdata['rate']) self.D = function.Function(Ddata['elvValue'], Ddata['rate']) ## Senescence function # This function determines the probability that an upland forest species # will experience senescence. The probability of senescence is a function # of the elevation of the local habitat above the mean water surface. The # exact functional relationship between height above water and the probability # of senescence is determined by the senescence table for each species. # The senescence tables are defined in a model input file. def senescence(self): try: #elv = SpeciesModel.params.heighAboveWater[self.cellID] #sal = SpeciesModel.params.meanSal[self.cellID] #bi = SpeciesModel.params.biEstCond[self.cellID] #if bi or sal > 1.0: #return 1.0 #return self.D[elv] return 0.0 except exceptions.RuntimeError as error: msg = 'UplandForestModel.senescence(): Error: location out of range. Additional info follows\n' msg += str(error) raise exceptions.RuntimeError(msg) ## Growth function # This function determines the probability that an upland forest species will become # established at the current location. The probability of an upland forest species becoming # established is depended on three major factors. First, the salinity must be below 1.0 ppt. # Second, there must be a period of 14 days with no flooding followed by a period of 14 with # water depths below 14 cm. Finally, the height of the habitat above mean water level determines # the final probability. # I'm not entirely happy with the current state of this function because the computation # of the basic establishment conditions (14 day no flood, 14 days water depth < 14 cm) is # handled external to the model. The problem is that the current approach divides # the responsibilities for representing the ecology of upland forest species. An external program # determines the establishment conditions while the model proper handles the final computation of the # probability of establishment. I would like to fix this. def growth(self): try: #elv = SpeciesModel.params.heighAboveWater[self.cellID] #sal = SpeciesModel.params.meanSal[self.cellID] #est = SpeciesModel.params.treeEstCond[self.cellID] #bi = SpeciesModel.params.biEstCond[self.cellID] #dsp = SpeciesModel.dspModel[self.cellID][self.abr] #dsp = 1.0 #if bi or est == 0 or sal > 1.0: #return 0 #return self.P[elv] * dsp return 0.0 except exceptions.RuntimeError as error: msg = 'UplandForestModel.growth(): Error: location out of range. Additional info follows\n' msg += str(error) raise exceptions.RuntimeError(msg) ##\class EmergentWetlandModel ##\brief This class handles the ecology for the emergent wetland species. ##\role{Ecology} ##\detail # class EmergentWetlandModel(SpeciesModel): def __init__(self, index=0, name='', abr='', cover=0, cellID=0, Pdata=None, Ddata=None): SpeciesModel.__init__(self, index, name, abr, 'EmergentWetlandModel', cover, cellID) self.P = function.Function2DFast(Pdata['waValue'], Pdata['salValue'], Pdata['rate']) self.D = function.Function2DFast(Ddata['waValue'], Ddata['salValue'], Ddata['rate']) ## Senescence function # This function determines the probability that an emergent wetland species # will experience senescence. The probability of senescence is a function # of variation in water stage height (computed as the standard deviation of stage) # and salinity. The exact relationship between the probability of senescence and # the environmental factors is describe the by scenescence table for each species. # The senescence tables are defined in a model input file. def senescence(self): waveAmp = SpeciesModel.params.hydrology[self.cellID][SpeciesModel.params.WAVEAMP] meanSal = SpeciesModel.params.hydrology[self.cellID][SpeciesModel.params.SAL] return self.D[waveAmp,meanSal] ## Growth function # This function determines the probability that an emergent wetland species will become # established at the current location. The probability of an wetland species becoming # established is depended on # three major factors. First, the salinity must be below 1.0 ppt. # Second, there must be a period of 14 days with no flooding followed by a period of 14 with # water depths below 14 cm. Finally, the height of the habitat above mean water level determines # the final probability. def growth(self): waveAmp = SpeciesModel.params.hydrology[self.cellID][SpeciesModel.params.WAVEAMP] meanSal = SpeciesModel.params.hydrology[self.cellID][SpeciesModel.params.SAL] return self.P[waveAmp, meanSal] ##\class SAVModel ##\brief This class handles the ecology for the SAV species. ##\role{Ecology} ##\detail # class SAVModel(SpeciesModel): def __init__(self, index=0, name='', abr='', cover=0, cellID=0, SAVData=None): SpeciesModel.__init__(self, index, name, abr, 'SAVModel', cover, cellID) if SAVData == None: errorMessage = 'SAVModel: Error: No SAVData object passed to constructor (i.e. __init__() )\n' raise exceptions.RuntimeError(errorMessage) self.betaIntercept = SAVData['Intercept'] self.betaTemp = SAVData['Temp'] self.betaSal = SAVData['Sal'] self.betaDepth = SAVData['Depth'] def senescence(self): return 0; def growth(self): d = SpeciesModel.params.hydrology[self.cellID][SpeciesModel.params.SUMMERWD] s = SpeciesModel.params.hydrology[self.cellID][SpeciesModel.params.SUMMERSAL] t = SpeciesModel.params.hydrology[self.cellID][SpeciesModel.params.SUMMERTEMP] return min( 1.0, max(0.0, self.betaIntercept + self.betaTemp*t + self.betaSal*s + self.betaDepth*d ) ) ##\class BarrierIslandSpeciesModel ##\brief This class handles the ecology for the barrier island species. ##\role{Ecology} ##\detail # class BarrierIslandSpeciesModel(SpeciesModel): def __init__(self, index=0, name='', abr='', cover=0, cellID=0, Pdata=None, Ddata=None): SpeciesModel.__init__(self, index, name, abr, 'BarrierIslandSpeciesModel', cover, cellID) self.P = function.Function(Pdata['elvValue'], Pdata['rate']) self.D = function.Function(Ddata['elvValue'], Ddata['rate']) def senescence(self): #elv = SpeciesModel.params.heighAboveWater[self.cellID] #bi = SpeciesModel.params.biEstCond[self.cellID] #if not bi: #return 1.0 #return self.D[elv] return 0.0 def growth(self): #elv = SpeciesModel.params.heighAboveWater[self.cellID] #bi = SpeciesModel.params.biEstCond[self.cellID] #dsp = SpeciesModel.dspModel[self.cellID][self.abr] #dsp = 1.0 #if not bi: #return 0.0 #return self.P[elv] * dsp return 0.0 ##\brief Growth/Senescence table storage ##\detail This class stores the growth and senescence tables # for each species. The class is structured as a dictionary. # Each species is identified by a unique abbreviation. For # example, _Phragmities australis_ (Roseau cane) is represented # by the abbreviation PAHU7. The abbreviations are based on the # USDA species abbreviations. The abbreviations for each of the species # used in this model are defined in the establishment table file. # # Each unique abbreviation is associated with the growth and senescence # tables. These tables are defined in the establishment and senescence file. # # This class' job is to make the data from the establishment and senescence # tables available to the other model components. # class SpeciesModelList(dict): ##\brief Class constructor def __init__(self): dict.__init__(self) ##\brief Load data from establishment and senescence files. ##@param [in] estFilename The name of the file containing the establishment tables. ##@param [in] mortFilename The name of the file containing the senescence tables. ##\details This member function loads the establishment and senescence tables # from the filenames passed in as arguments. The files are expected to be # MSExcel spreadsheets stored in the XLSX format. The data in each file # should be divided into a set of separate tabbed sheets. One sheet should # be named "VegTypeNames". This sheet should list all the species to include # in the model. There should also be one table for each of the species included in the # model. There are three exceptions to this: SAV, the Upland forest species and the # barrier island species. SAV parameters are read from the model configuration file. # The barrier island species and the upland forest species are each represented by a single # sheet. # # The function performs the following steps:\n def config(self, estFilename, mortFilename): errorMessage = '' ################################################### ## # - __Step 1__: Read the VegTypeNames sheet from the # establishment and senescence files. # # The VegTypeNames file is where we get the # names of the species in the model. If this # sheet does not exist in the Excel files, # or if the files do not exist, raise and # exception. ################################################### try: estData = pandas.read_excel(estFilename, 'VegTypeNames', index_col=None) except exceptions.IOError as error: errorMessage += 'SpeciesModelList: Error: Could not open file for reading: ' + str(estFilename) + '\n' errorMessage += 'SpeciesModelList: Error: Additional error info: ' + str(error) + '\n'; except xlrd.biffh.XLRDError as error: errorMessage += 'SpeciesModelList: Error: While opening : ' + str(estFilename) + '\n' errorMessage += 'SpeciesModelList: Error: Additional error info: ' + str(error) + '\n'; try: mortData = pandas.read_excel(mortFilename, 'VegTypeNames', index_col=None) except exceptions.IOError as error: errorMessage += 'SpeciesModelList: Error: Could not open file for reading: ' + str(estFilename) + '\n' errorMessage += 'SpeciesModelList: Error: Additional error info: ' + str(error) + '\n'; except xlrd.biffh.XLRDError as error: errorMessage += 'SpeciesModelList: Error: While opening : ' + str(estFilename) + '\n' errorMessage += 'SpeciesModelList: Error: Additional error info: ' + str(error) + '\n'; if len(errorMessage): raise exceptions.RuntimeError(errorMessage); ################################################### ## # - __Step 2__: Get the list of species from the 'Symbol' # column. Raise exceptions as needed. # ################################################### try: speciesList = estData['Symbol'] except exceptions.KeyError as error: errorMessage += 'SpeciesModelList: Error: Symbol column not defined in establishment tables \n' raise exceptions.RuntimeError(errorMessage); ################################################### ## # - __Step 3__: Read the establishment and senescence tables. # # For each species, configure the P & D # Functions for each species and push everything # into a dictionary (dict). # ################################################### for spSymbol in speciesList: print 'SpeciesModelList: Msg: Configuring model for species ' + str(spSymbol) spInfo = estData[ estData['Symbol'] == spSymbol ].to_dict('record')[0] spID = spInfo['ID'] spName = spInfo['Common Name'] spModelType = spInfo['ModelType'] ########################################### # Upland Forest Model # ########################################### if ( spModelType == 'UplandForestModel'): reader = function.ReadVegTable1D() Pdata = None Ddata = None try: Pdata = reader.read(estFilename, 'UplandForest', spSymbol) except exceptions.RuntimeError as error: errorMessage += str(error) + '\n' errorMessage += 'SpeciesModelList: Error: error while working on species ' + str(spSymbol) + '\n' except exceptions.ValueError as error: errorMessage += str(error) + '\n' errorMessage += 'SpeciesModelList: Error: error while working on species ' + str(spSymbol) + '\n' try: Ddata = reader.read(mortFilename, 'UplandForest', spSymbol) except exceptions.RuntimeError as error: errorMessage += str(error) + '\n' errorMessage += 'SpeciesModelList: Error: error while working on species ' + str(spSymbol) + '\n' except exceptions.ValueError as error: errorMessage += str(error) + '\n' errorMessage += 'SpeciesModelList: Error: error while working on species ' + str(spSymbol) + '\n' spModel = UplandForestModel(index=spID, name=spName, abr=spSymbol, cover=0, cellID=-1, Pdata=Pdata, Ddata=Ddata) self.__setitem__(spSymbol, spModel) ########################################### # Emergent Wetland Model # ########################################### elif (spModelType == 'EmergentWetlandModel'): reader = function.ReadVegTable2D() Pdata = None Ddata = None try: Pdata = reader.read(estFilename, spSymbol) except exceptions.RuntimeError as error: errorMessage += str(error) + '\n'; errorMessage += 'SpeciesModelList: Error: error while working on species ' + str(spSymbol) + '\n' except exceptions.ValueError as error: errorMessage += str(error) + '\n' errorMessage += 'SpeciesModelList: Error: error while working on species ' + str(spSymbol) + '\n' try: Ddata = reader.read(mortFilename, spSymbol) except exceptions.RuntimeError as error: errorMessage += str(error) + '\n' errorMessage += 'SpeciesModelList: Error: error while working on species ' + str(species) + '\n' except exceptions.ValueError as error: errorMessage += str(error) + '\n' errorMessage += 'SpeciesModelList: Error: error while working on species ' + str(species) + '\n' spModel = EmergentWetlandModel(index=spID, name=spName, abr=spSymbol, cover=0, cellID=-1, Pdata=Pdata, Ddata=Ddata) self.__setitem__(spSymbol, spModel) ########################################### # Submerged Aquatic Vegetation Model # ########################################### elif ( spModelType == 'SAVModel'): savData = None try: savData = pandas.read_excel(estFilename, spSymbol, index_col=0 ).to_dict('dict')['Value'] except exceptions.IOError as error: errorMessage += 'SpeciesModelList: Error : Could not open file for reading : ' + estFilename + '\n' errorMessage += 'SpeciesModelList: Extra error info: ' + str(error) + '\n' except xlrd.biffh.XLRDError as error: errorMessage += 'SpeciesModelList: Error : Could not find sheet name ' + species + ' in file ' + estFilename + '\n' errorMessage += 'SpeciesModelList: Extra error info: ' + str(error) + '\n' except exceptions.KeyError as error: errorMessage += 'SpeciesModelList: Error : Dictionary key error. SAV table probably has wrong column headings\n' errorMessage += 'SpeciesModelList: Error : ' spModel = SAVModel(index=spID, name=spName, abr=spSymbol, cover=0, cellID=-1, SAVData=savData) self.__setitem__(spSymbol, spModel) ########################################### # Barrier Island Model # ########################################### elif ( spModelType == 'BarrierIslandSpeciesModel'): reader = function.ReadVegTable1D() Pdata = None Ddata = None try: Pdata = reader.read(estFilename, 'BarrierIsland', spSymbol) except exceptions.RuntimeError as error: errorMessage += str(error) + '\n' errorMessage += 'SpeciesModelList: Error: error while working on species ' + str(spSymbol) + '\n' except exceptions.ValueError as error: errorMessage += str(error) + '\n' errorMessage += 'SpeciesModelList: Error: error while working on species ' + str(spSymbol) + '\n' try: Ddata = reader.read(mortFilename, 'BarrierIsland', spSymbol) except exceptions.RuntimeError as error: errorMessage += str(error) + '\n' errorMessage += 'SpeciesModelList: Error: error while working on species ' + str(spSymbol) + '\n' except exceptions.ValueError as error: errorMessage += str(error) + '\n' errorMessage += 'SpeciesModelList: Error: error while working on species ' + str(spSymbol) + '\n' spModel = BarrierIslandSpeciesModel(index=spID, name=spName, abr=spSymbol, cover=0, cellID=-1, Pdata=Pdata, Ddata=Ddata) self.__setitem__(spSymbol, spModel) ########################################### # Null Model # ########################################### elif ( spModelType == 'NullModel'): spModel = NullModel(index=spID, name=spName, abr=spSymbol, cover=0, cellID=-1) self.__setitem__(spSymbol, spModel) ########################################### # Error State # ########################################### else: errorMessage += 'SpeciesModelList: Error: Unknown model type specified : ' + spModelType + '\n' if len(errorMessage): raise exceptions.RuntimeError(errorMessage) ##\class Params ##\brief This class is the interface between the outside world and the model. ##\role{Machinery} ##\detail This class is an interface between the internal components of the model # and the outside world. That is, everything that is read in from outside the # model is stored in an object of class Params. Params then makes all of this # information available to the other model components. # # It is important to note that # there is only meant to be ONE object derived from this class, and that object # is owned by the object of class Model. # # Params handles reading values from the model configuration file. Params # also manages all of the model IO streams, as well as the landscape.Landscape # objects that hold the spatial data files used by the model, including the # hydrology, salinity, elevation, initial conditions and other spatial data sets. Params also # holds a special object of type model.SpeciesModelList. This class is # used to store the the growth and senescence tables for each species along with # any other species specific information. class Params(object): ##\brief Class constructor def __init__(self): ##\brief Holds the configuration information self.configDict = config.Config() ##\brief Model start year self.startYear = 0 ##\brief Model end year self.endYear = 0 ##\brief List of model input filenames. # self.inputStrm = {'HydrologyFile' :None} ##\brief List of model output filenames. # self.outputStrm = { 'OutputFile' :None} ##\brief The annual hydrology data self.hydrology = landscape.Landscape() ##\brief The model initial conditions. # self.initCond = landscape.Landscape() # InitialConditionFile ##\brief The species model list (SpeciesModelList) # self.spModelList = SpeciesModelList() ##\brief The elevation threshold for tree establishment (soon to be deprecated). # self.elevationThreshold = 0.1525 self.numLocs = 0 ##\brief These are column names used in the hydrology file. The values # assigned here are the default values. However, if they change, you can # reset them in the configuration file. There is error checking associated # with these names at the end of the params.config() self.YEAR = 'year' self.CELLID = 'CellID' self.SAL = 'mean(sal_year)' self.WAVEAMP = 'std(dep_year)' self.SUMMERWD = 'mean(dep_summer)' self.SUMMERSAL = 'mean(sal_summer)' self.SUMMERTEMP = 'mean(temp_summer)' self.LAND = 'land/water' ##\brief Open input/output files ##\param key The keyword name for the file as it appears in the configuration file. ##\param mode Should the file be opened for reading or writing. ##\details This is a convenience function. It checks to make sure the # file actually exists and opens if it does. If any problems are encountered, # such as file not existing or cannot be opened, an exception is raised. def open_file(self, key, mode): try: filename = self.configDict[key]; except exceptions.KeyError as error: errorMessage = 'Params: Error: ' + str(key) + ' is not defined in the configuration file.\n' errorMessage += 'Params: Error: Additional error info : ' + str(error) + '\n' raise exceptions.RuntimeError(errorMessage); try: strm = open(filename, mode) except exceptions.IOError as error: errorMessage = 'Params: Error: Could not open the file ' + filename + (' for reading' if mode == 'r' else ' for writing') +'\n' errorMessage += 'Params: Error: Additional error info : ' + str(error) + '\n' raise exceptions.RuntimeError(errorMessage); return strm ##\brief Read the model configuration information. ##\param argv A object containing the name of the configuration file. ##\details This member function reads in the information # contained in the model configuration file and sets up # a number of program elements based on this information # # The function performs the follwing steps: # def config(self, argv): errorMessage = '' ################################################### ## # - __Step 1__: Read in the configuration data from the # configuration file. # ################################################### try: self.configDict.config(argv); except exceptions.RuntimeError as error: errorMessage = 'Params: Error: An error occurred reading the configuration file.\n' + str(error) raise exceptions.RuntimeError(errorMessage) ################################################### ## # - __Step 2__: Get the start end end year of the simulation # ################################################### try: self.startYear = int( self.configDict['StartYear'] ) print 'Params: Msg: StartYear = ' + str(self.startYear) except exceptions.KeyError as error: errorMessage += 'Params: Error: StartYear is not defined in the configuration file\n' try: self.endYear = int( self.configDict['EndYear']) print 'Params: Msg: EndYear = ' + str(self.endYear) except exceptions.KeyError as error: errorMessage += 'Params: Error: EndYear is not defined in the configuration file\n' ################################################### ## # - __Step 3__: Read the initial conditions file # ################################################### try: print 'Params: Msg: Reading initial conditions from ' + self.configDict['InitialConditionFile'] + '\n' initCondStrm = self.open_file('InitialConditionFile','r') reader = landscape.ReadLandscape() reader.read(initCondStrm, self.initCond) initCondStrm.close(); except exceptions.RuntimeError as error: print(error) errorMessage += str(error) ################################################### ## # - __Step 4__: Configure the SpeciesModelList object # ################################################### try: estFilename = self.configDict['EstFilename'] except exceptions.KeyError as error: errorMessage += 'Params: Error: ' + str(error) + '\n' except exceptions.RuntimeError as error: errorMessage += str(error); try: mortFilename = self.configDict['MortFilename'] except exceptions.KeyError as error: errorMessage += 'Params: Error: ' + str(error) + '\n' except exceptions.RuntimeError as error: errorMessage += str(error); try: self.spModelList.config(estFilename, mortFilename) except exceptions.KeyError as error: errorMessage += 'Params: Error: ' + str(error) + '\n' except exceptions.RuntimeError as error: errorMessage += str(error); ################################################### ## # - __Step 5__: Open all the input files for reading. # ################################################### for key in self.inputStrm.iterkeys(): try: self.inputStrm[key] = self.open_file(key, 'r') print 'Params: Msg: Opened file for reading: ' + str(key) + ' = ' + self.configDict[key] except exceptions.RuntimeError as error: errorMessage += str(error) ################################################### ## # - __Step 6__: Open all the output files for writing. # ################################################### # Steps 6.1 to 6.3: Set up the single year files. # Step 6.1: Get the filename template for the single year files. try: outputTemplate = None outputTemplate = self.configDict['OutputTemplate'] print 'Params: Msg: OutputTemplate = ' + self.configDict['OutputTemplate'] except exceptions.KeyError as error: errorMessage += 'Params: Error: OutputTemplate is not defined in the configuration file\n' errorMessage += 'Params: Error: Additional error info: ' + str(error) # Step 6.2: Get the years we want output stored in separate files outputYear = range(self.startYear, self.endYear+1) #try: # outputYear = None # outputYear = self.configDict['OutputYears'] # outputYear = [ int(x) for x in outputYear.split(',')] # print 'Params: Msg: OutputYears = ' + str(outputYear) #except exceptions.KeyError as error: # errorMessage += 'Params: Error: OutputYears is not defined in the configuration file\n' # errorMessage += 'Params: Error: Additional error info: ' + str(error) # Step 6.3: Build the file names for the separate files. if outputTemplate != None and outputYear != None: for year in outputYear: filename = re.sub(r'', str(year), outputTemplate); self.configDict['__Single_'+str(year)] = filename self.outputStrm['__Single_'+str(year)] = None # Step 6.7: Open all the output files. for key in self.outputStrm.iterkeys(): try: self.outputStrm[key] = self.open_file(key, 'w+') print 'Params: Msg: Output file opened ' + str(self.configDict[key]) except exceptions.RuntimeError as error: errorMessage += str(error) errorMessage += 'Params: Error: This is very odd\n' ################################################### ## # - __Step 7__: If any errors have occurred, raise an # exception # ################################################### if len(errorMessage): raise exceptions.RuntimeError(errorMessage) ################################################### ## # - __Step X__: Find the number of mesh cells # ################################################### self.numLocs = len(self.initCond.data) ################################################### ## # - __Step 8__: Give all of the landscape objects their # correct size ################################################### print 'Params: Msg: Giving all data layers their proper size' reader = landscape.ReadLandscape() reader.read(self.inputStrm['HydrologyFile'], self.hydrology, self.numLocs) print 'Params: Msg: Rewinding all the input streams' for strm in self.inputStrm.itervalues(): strm.seek(0,0) ################################################### ## # - __Step 9__: Read the column names for the # hydrology file. They the column names have not # been redefined in the configuration file, they # just use the defaults (already set in __init___(). # # Check that the column names actually appear in the # hydrology file. If they do not, throw and exception # ################################################### if 'YEAR' in self.configDict.keys(): self.YEAR = config['YEAR'] if 'CELLID' in self.configDict.keys(): self.CELLID = config['CELLID'] if 'SAL' in self.configDict.keys(): self.SAL = config['SAL'] if 'WAVEAMP' in self.configDict.keys(): self.WAVEAMP = config['WAVEAMP'] if 'SUMMERWD' in self.configDict.keys(): self.SUMMERWD = config['SUMMERWD'] if 'SUMMERSAL' in self.configDict.keys(): self.SUMMERSAL = config['SUMMERSAL'] if 'SUMMERTEMP' in self.configDict.keys(): self.SUMMERTEMP = config['SUMMERTEMP'] for name in [self.YEAR, self.SAL, self.WAVEAMP, self.SUMMERWD, self.SUMMERSAL, self.SUMMERTEMP]: if not name in self.hydrology.data.columns: errorMessage += 'Params: Error: Column name ' + name + ' is not defined in the hydrology file.\n' if len(errorMessage): raise exceptions.RuntimeError(errorMessage) ##\brief Actions take at the end of the simulation. ##\details The primary responsibility of this function # is to close all of the open file streams. It is # called at the end of the simulation as the program # is getting ready to exit. def done(self): for iter in self.inputStrm.itervalues(): iter.close(); print 'Params: Msg: Fixing output file format' columnOrder = self.initCond.columns index = pandas.DataFrame( self.initCond.data['index'] ) for strm in self.outputStrm.itervalues(): filename = strm.name strm.close() data = pandas.read_csv(filename, delimiter='\s*,\s*') data = pandas.merge(index, data, left_index=True, right_on='CellID') data = data[columnOrder] data.to_csv(filename, index=False, quoting=csv.QUOTE_NONNUMERIC) strm.close() def __del__(self): pass ##\class PatchModel ##\brief This class handles the plant community dynamics at a single location ##\role {Ecology/Machinery} ##\detail This class handles the ecology of a single location in the landscape. # The information for each location is stored as a Python dictionary. They # keys for the dictionary are the abbreviated names, stored as a string, for # each of the species. The value for each entry in the dictionary is an object # instantiated from a class that inherits from SpeciesModel. That is, each value # is an object of one of the following classes: UplandForestModel, EmergentWetlandModel, # SAVModel, BarierIslandModel, or NullModel. class PatchModel(dict): params = None def __init__(self, cellID, initCond, spModelList): dict.__init__(self) self.cellID = cellID for spName,spCover in initCond.iteritems(): spModel = copy.copy(spModelList[spName]) spModel.cover = spCover spModel.cellID = cellID dict.__setitem__(self, spName, spModel) def update(self): occupied = 0.0 lost = 0.0 growthLikelihood = 0.0 ####################################################### # WARNING: The order of steps here is important. Additional # care is required here because you can change # the order without actually causing the model to break. # Changing the order will change the way the model simulates # the ecology the plants. Unless you really know what # you are doing, do not change the order. Even if # you think you know what you are doing, think about # what you are going to do carefully. Perhaps you should # go take and nap and think things over before you make # changes here. If you break things, it will be on your head. # ####################################################### ####################################################### # Step 1: Work out the increase and decrease in cover # for all types except, SAV, WATER and # Those types have to be # handled in a special way. # ####################################################### # Step 1.0: The first thing we need to do is workout the effects of added or lost land # There are four cases. # Case 1: Land/Land: The cell is currently classified as land and the next state (obtained from the input file) # is also land. In this case, we do not need to do anything. # # Case 2: Water/Water: The cell is currently classified as water and the next state is also water. # In this case we do not need to do anything. # # Case 3: Land/Water: The cell is currently classified as land and the next state is water. # In this case we need to set the cover of all plant species to zero and set the cover of water to 1.0 # the proceed as normal # # Case 4: Water/Land: The cell is currently classified as water and the next state is land. # In this case we need to set the cover of water to zero (?) and set BAREGRND to 1.0. The proceed as # normal. # # A cell that has some vegetation present will be classified as land. nextLandState = PatchModel.params.hydrology[self.cellID][PatchModel.params.LAND] # Step 1.0 a: Figure out if the cell is currently classified as land or water. isLand = 1 #SAVModel = dict.__getitem__(self, 'SAV') #waterModel = dict.__getitem__(self, 'WATER') #totalWater = SAVModel.cover + waterModel.cover totalWater = self['WATER'].cover + self['SAV'].cover if totalWater == 1.0: isLand = 0 if isLand == 1 and nextLandState == 0: # Case 3 for spName, spModel in self.iteritems(): spModel.cover = 0.0 self['WATER'].cover = 1.0 #waterModel = dict.__getitem__(self, 'WATER') #waterModel.cover = 1.0 elif isLand == 0 and nextLandState == 1: # Case 4 self['BAREGRND'].cover = 1.0 self['WATER'].cover = 0.0 self['SAV'].cover = 0.0 else: # Case 1 and Case 2 pass # Step 1.1: Work out the loss of cover for all species unoccupied = self['BAREGRND'].cover for spName, spModel in itertools.ifilterfalse(lambda (k,v): k == 'BAREGRND' or k =='SAV' or k == 'WATER', self.iteritems()): cover = spModel.cover death = spModel.senescence() #occupied += cover lost += death * cover spModel.cover -= death * cover growthLikelihood += spModel.growth() #unoccupied = max( 0, min( (1.0-occupied)+lost, 1.0) ) unoccupied += lost available = copy.copy(unoccupied) # Step 1.2: Work out the gain in cover for all species # The rounding of growth is complicated because what I really want to happen is for the # digits below the thousandths place to be dropped. This isn't really rounding. The reason # for this approach is to make growth slightly smaller than it might be. this should # prevent unoccupied from becoming negative. This is a rather crude way to achieve the # desired result, but it is simple and requires a minimum of recoding and new coding. if growthLikelihood: for spName, spModel in itertools.ifilterfalse(lambda (k,v): k == 'BAREGRND' or k=='SAV' or k=='WATER', self.iteritems()): #spModel.cover += spModel.growth()/growthLikelihood * unoccupied growth = math.floor( spModel.growth()/growthLikelihood * 1000.0 )/1000.0 #spModel.cover += growth * unoccupied #unoccupied -= growth * unoccupied spModel.cover += growth * available unoccupied -= growth * available self['BAREGRND'].cover = unoccupied ####################################################### # Step 3: Work out the change in cover for SAV and WATER # ####################################################### SAVModel = dict.__getitem__(self, 'SAV') waterModel = dict.__getitem__(self, 'WATER') totalWater = SAVModel.cover + waterModel.cover SAVModel.cover = SAVModel.growth() * totalWater waterModel.cover = totalWater - SAVModel.cover ####################################################### # Step 4: Check sum # ####################################################### errorMsg = str() checkSum = 0.0 for spName, spModel in self.iteritems(): checkSum += spModel.cover if spModel.cover < 0.0: errorMsg += 'PatchModel: Error: Species cover < 0.0. This should not happen.\n' errorMsg += 'PatchModel: Error: Species = ' + spName + '\n' tol = 0.005 if not( (1.0 - tol) < checkSum and checkSum < (1.0 + tol) ): errorMsg += 'PatchMod: Error: checkSum is out of range. checkSum = ' + str(checkSum) + ' should be 1.0\n' if len(errorMsg) != 0: raise exceptions.RuntimeError(errorMsg) def write_to_str(self, dataFormat='{0}'): ret = '' sep = '' for value in self.itervalues(): ret += sep + dataFormat.format(value.cover) sep = ', ' return(ret) def write_to_stream(self, stream = sys.stdout, dataFormat='{0}'): sep='' for key, value in self.iteritems(): print >> stream, sep + dataFormat.format(value) sep = ', ' ##\class DynamicsModel ##\brief This class coordinates the updating of plant community dynamics ##\role{Ecology} ##\detail This class handles updating the spatial distribution of species # in response to environmental conditions and plant dispersal ability. # This class defines the spatial structure of the model using a # raster grid. The grid structure is inherited from landscape.LandscapePlus. # Each element of the raster contains an object of type PatchModel. # Local dynamics, those taking place within each raster cell, are # computed by objects of class PatchModel. class DynamicsModel(dict): def __init__(self): dict.__init__(self) self.locInfo = None def config(self, params): # Get the species codes (e.g. 'SPPA', 'NYAQ2', etc.. ) from the # from the species model list object. speciesList = params.spModelList.keys() # Pull off just the location information from the # initial conditions files. This should be the # the row and column indicies (m and n), the # actual geographic coordinates (x and y) and the # location index (CellID). try: self.locInfo = params.initCond.data[ ['m','n','x','y'] ] except exceptions.KeyError: errMsg = 'DynamicsModel: Error: One or more spatial location columns are not defined in the initialization\n' errMsg += 'DynamicsModel: Error: file. Columns should be CellID, m, n, x and y (order does not matter)\n' raise exceptions.RuntimeError(errMsg) # Cycle over the locations in the initial conditions. # Each row in the initial conditions csv file represents # a single location/patch. for cellID, patch in params.initCond.iterlocs(): # Get just the cover values for the current patch. patchInitCond = patch[speciesList] # Make a PatchModel object for the patch and initialize it newPatch = PatchModel(cellID, patchInitCond, params.spModelList) # Place the patch model in a dictionary referenced by the cellID. dict.__setitem__(self, cellID, newPatch) def __getitem__(self, cellID): dict.__getitem__(self, cellID) def update(self): for index,patch in self.iteritems(): patch.update() def write_to_stream(self, stream, header=True, time=None): #if time is not None: #cols = self.locInfo.columns.insert(0, 'year') #self.locInfo['year'] = numpy.full( len(self.locInfo), time) #self.locInfo = self.locInfo[ cols ] if header: line = 'CellID' for name in self.locInfo.columns: line += ', ' + str(name) for name in self.itervalues().next().iterkeys(): line += ', ' + str(name) print >> stream, line for cellID,patch in self.iteritems(): try: pandas.DataFrame( self.locInfo.ix[ cellID ] ).T.to_csv(stream, header=False, line_terminator='') print >> stream, ', ' + patch.write_to_str() except exceptions.IndexError as error: print cellID raise error #if time is not None: #del self.locInfo['year'] ##\class DispersalModel ##\brief Computes the dispersal of each species over space ##\role{Ecology} ##\detail This class computes the dispersal kernel for each species. class DispersalModel(object): dynModel = None def __init__(self): self.patchIndexLandscape = landscape.Landscape() self.patchDict = dict() def config(self, params): self.patchIndexLandscape.copy(params.initCond) #for row in range(0, int(params.initCond.nrow) ): #for col in range(0, int(params.initCond.ncol) ): # if params.initCond.has_data_at(row,col): for row,col in itertools.ifilter( lambda (r,c): params.initCond.has_data_at(r,c), itertools.product( range(0,int(params.initCond.nrow)), range(0,int(params.initCond.ncol)) ) ): patchIndex = params.initCond.data[row,col] patchInitCond = params.initCond[(row,col)] neighborList = [ (nRow,nCol) for nRow,nCol in itertools.ifilter( lambda (r,c): params.initCond.has_data_at(r,c), itertools.product(range(row-1,row+2), range(col-1,col+2)) ) ] # This is slick as hell. I like python. newPatch = { 'loc':(row,col), 'spFreq':patchInitCond, 'neighborList':neighborList } self.patchDict[patchIndex] = newPatch patchInitCond = params.initCond.table.itervalues().next() for key in patchInitCond.iterkeys(): patchInitCond[key] = 0.0 newPatch = {'loc':(-1,-1), 'spFreq':patchInitCond, 'neighborList':[]} self.patchDict[params.initCond.nodata_value] = newPatch def summary(self): print 'DispersalModel: Msg: Summary info start' print self.patchIndexLandscape.header() print 'len(patchDict) = ' + str(len(self.patchDict)) print next( self.patchDict.itervalues() ) patchAddress = self.patchIndexLandscape.data[241,44] print 'DispersalModel: patchAddress = ' + str(patchAddress) print 'DispersalModel: Stuff at patchAddress = ' + str( self.patchDict[patchAddress] ) print 'DispersalModel: Stuff at patchAddress[\'spFreq\'] = ' + str( self.patchDict[patchAddress]['spFreq']) print 'DispersalModel: Msg: Summary info end' def __getitem__(self, item): patchAddress = self.patchIndexLandscape.data[item] return self.patchDict[patchAddress]['spFreq'] def has_data_at(self,row,col): return self.patchIndexLandscape.has_data_at(row,col) def _compute_local_dsp(self, ret): #row,col = ret['loc'] ptr = ret['spFreq'] neighborList = ret['neighborList'] total = 0.0 for sp in ptr.iterkeys(): ptr[sp] = 0 for neighbor in neighborList: patchModel = DispersalModel.dynModel[neighbor] for sp,spModel in patchModel.iteritems(): ptr[sp] += spModel.cover total += spModel.cover #for offsetRow in range(-1,2): # for offsetCol in range(-1,2): # neighborRow = row + offsetRow # neighborCol = col + offsetCol # if DispersalModel.dynModel.has_data_at(neighborRow, neighborCol): # patchModel = DispersalModel.dynModel[neighborRow,neighborCol] # for sp,spModel in patchModel.iteritems(): # try: # ptr[sp] += spModel.cover # total += spModel.cover # except exceptions.TypeError as error: # print 'DispersalModel::_compute_local_dsp(): Error: ret = ' + str(ret) # print 'DispersalModel::_compute_local_dsp(): Error: row = ' + str(row) # print 'DispersalModel::_compute_local_dsp(): Error: col = ' + str(col) # print 'DispersalModel::_compute_local_dsp(): Error: ptr = ' + str(ptr) # print 'DispersalModel::_compute_local_dsp(): Error: total = ' + str(total) # print 'DispersalModel::_compute_local_dsp(): Error: offsetRow = ' + str(offsetRow) # print 'DispersalModel::_compute_local_dsp(): Error: offsetCol = ' + str(offsetCol) # print 'DispersalModel::_compute_local_dsp(): Error: neighborRow = ' + str(neighborRow) # print 'DispersalModel::_compute_local_dsp(): Error: neighborCol = ' + str(neighborCol) # print 'DispersalModel::_compute_local_dsp(): Error: patch = ' + str(patch) # print 'DispersalModel::_compute_local_dsp(): Error: sp = ' + str(sp) # print 'DispersalModel::_compute_local_dsp(): Error: cover = ' + str(cover) # raise error if total: for sp in ptr.iterkeys(): ptr[sp] /= total def update(self): for loc in itertools.ifilter( lambda key: key != self.patchIndexLandscape.nodata_value, self.patchDict.iterkeys()): self._compute_local_dsp(self.patchDict[loc]) ##\class ModelUpdateEvent ##\brief An event class to update the dynamics model and the dispersal model. ##\role{Machinery} ##\detail This class will likely be removed in an upcoming version of the model. # class ModelUpdateEvent(event.Event): def __init__(self, time, name, model): event.Event.__init__(self, time, name) self.model = model def act(self): print self.name self.model.update() ##\class StopWatch ##\brief A class to measure elapsed wall clock time. ##\role{Machinery} ##\detail This class is used to measure how much time # (wall clock time) is used for different aspects of the model. # This provides some rough profiling information so we can estimate # how long future runs of the model might take. class StopWatch: def __init__(self): self.running = 0 self._start = 0 self._end = 0 def start(self): self.running = 1 self._start = time.time() self._stop = 0 def stop(self): self._end = time.time() self.running = 0 print 'StopWatch: Msg: Delta t = ' + str(self._end - self._start) def act(self): if self.running: self.stop() else: self.start() def __str__(self): ret = str(id(self)) + ' ' ret += str(self.running) + ' ' ret += str(self._start) + ' ' ret += str(self._end) + ' ' ret += str(self._end - self._start) return ret #class StopWatchEvent(event.Event): #def __init__(self, time, name='StopWatchEvent', stopwatch=None): event.Event.__init__(self, time, name) self.stopwatch = stopwatch # #def act(self): #if self.stopwatch.running: #self.stopwatch.stop() #else: #self.stopwatch.start() # This is the top level class for the LAVegMod. To get the model going you need # to do four things. # 1) Include the model module in the Python file that will be used # to coordinate the running of the various models. # The include statement should look something like: # import model # Note that this assumes that the LAVegMod code is in the same # directory as the toplevel script. # # 2) Instantiate an object of class Model # This will create a copy of the model and all of its subcomponents. # Instantiating model should look something like: # # laVegMod = model.Model() # # # 3) Call Model.config(self, argv) for the instantated object # Model.config takes a single (non-self) argument that contains the # name of the configuration file for the model. The call to Model.config() # should look something like: # # laVegMod.config( ) # # where is a Python string containing the full path # and filename of the configuration file for the LAVegMod. # # The call to Model.config() will bring the model up to a state where it # is ready to run. It is possible that more or more errors may occur while # configuring the model. You can capture these and processes them yourself # or you can just let them go and they will halt the code. To capture the # errors from the LAVegMod you need code that looks something like: # # try: # laVegMod.config( ) # except exception.RunTimeError as error: # # # 4) Call Model.step() repeatedly for the instantated object. # Each time you call model.step() the model will advance by one year. # # The call to Model.step() should look something like: # # laVegMod.step() # # Again, this code may throw exceptions if something goes wrong. You can # capture these errors using code that looks like this: # # try: # laVegMod.step() # except exception.RunTimeError as error: # # # Note that the LAVegMod only throws, (or should only throw) # exception.RunTimeError() # # The thrown error contains one or error messages stored in a string that describe what # went wrong. You can print these with something like: # # print error # # If the model has thrown an exception, then something is broken and # there is no way to fix it during run time. You or I, or someone, will # have to chase down the error and fix it. Often the error is going to be # in the input files. So I would check those first. # # I have tried to make the error checking and reporting as comprehensive # and as detailed as possible. # # class Model(object): def __init__(self): self.currentTime = event.Time(0,0) self.params = Params() self.eventQueue = event.EventQueue() self.dynModel = DynamicsModel() #self.dspModel = DispersalModel() SpeciesModel.params = self.params # I'm not sure I like this. PatchModel.params = self.params #SpeciesModel.dspModel = self.dspModel # DispersalModel.dynModel = self.dynModel # def config(self, argv): print 'Model: Msg: Reading configuration information' self.params.config(argv) print 'Model: Msg: Configuring the dynamics model' self.dynModel.config(self.params) print 'Model: Msg: Configuring the dispersal model' #self.dspModel.config(self.params) # self.dspModel.summary() print 'Model: Msg: Configuring the update queue' self.eventQueue.clear() perYearSW = StopWatch() totalSW = StopWatch() self.eventQueue.add_event( event.GenericEvent(event.Time(self.params.startYear, 0), name='StopWatch', callable=totalSW) ) self.eventQueue.add_event( landscape.WriteLandscape(event.Time(self.params.startYear, 200), name='WriteASCIIGrid: Msg: Writing model output', stream=self.params.outputStrm['OutputFile'], landscape=self.dynModel, header=True)) for year in range(self.params.startYear+1, self.params.endYear+1): self.eventQueue.add_event( event.GenericEvent(event.Time(year, 100), name='StopWatch', callable=perYearSW ) ) self.eventQueue.add_event( event.MsgEvent(event.Time(year, 200), name='MsgEvent', msg='Year = ' + str(year)) ) self.eventQueue.add_event( landscape.ReadLandscape(event.Time(year, 300), name='ReadASCIIGrid: Msg: Reading wave amp data', stream=self.params.inputStrm['HydrologyFile'], landscape=self.params.hydrology, numLocs=self.params.numLocs)) self.eventQueue.add_event( ModelUpdateEvent(event.Time(year, 1100), name='ModelUpdateEvent: Msg: Updating veg dynamics', model=self.dynModel ) ) self.eventQueue.add_event( landscape.WriteLandscape(event.Time(year, 1400), name='WriteLandscape: Msg: Writing model output', stream=self.params.outputStrm['OutputFile'], landscape=self.dynModel, header=False)) self.eventQueue.add_event( event.GenericEvent(event.Time(year, 1600), name='StopWatch', callable=perYearSW ) ) for yearKey,yearStream in itertools.ifilter( lambda (k,v): re.match(r'^__Single_',k) != None, self.params.outputStrm.iteritems() ): year = int( yearKey.replace('__Single_','') ) self.eventQueue.add_event(landscape.WriteLandscape(event.Time(year, 1500), name='WriteLandscape: Msg: Writing model output for year ' + str(year), stream=yearStream, landscape=self.dynModel)) self.eventQueue.add_event( event.GenericEvent(event.Time(self.params.endYear,3000), name='StopWatch', callable=totalSW) ) def step(self): self.eventQueue.run(while_condition=(lambda arg: arg.name != 'PauseEvent')) return 0 def run(self): try: self.config(sys.argv) except exceptions.RuntimeError as error: print error return 1 #except: #print 'Model: Error: Caught an unknown error : ' , sys.exc_info()[0] #return 1 self.eventQueue.run() self.params.done() return 0;