1436 lines
62 KiB
Python
1436 lines
62 KiB
Python
#!/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'<YEAR>', 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( <configFilename> )
|
|
#
|
|
# where <configFilename> 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( <configFilename> )
|
|
# except exception.RunTimeError as error:
|
|
# <do something with the 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:
|
|
# <do something with the 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;
|
|
|