#!/usr/bin/python -tt #======================================================================= # General Documentation """Definition of a generic model (class GenericModel). The GenericModel class is the parent class for all models. The difference is that these subclasses of GenericModel overload spe- cific customized methods. An overview of the programming logic and structure of all models and the GenericModel template is gi- ven here: http://geosci.uchicago.edu/csc/modelutil/doc/man-models.html Example of defining a model class using GenericModel, integrating it 100 timesteps, and returning the result as a new StateSet: class IceModel(GenericModel): def setup(self): [... add extra initialization code ...] def tend1(self): [... add lines to calculate "tendency" ...] [... define Field variables ...] [... define State variables s1, s2, s3 ...] set = StateSet(s1, s2, s3) model = IceModel(set) for i in range(100): model.step1_return() newset = model.asStateSet() """ #----------------------------------------------------------------------- # Additional Documentation # # RCS Revision Code: # $Id: generic_model.py,v 1.1.1.1 2005/01/13 00:15:42 jlin Exp $ # # Modification History: # - 12 Jan 2005: Original by Johnny Lin, Computation Institute, # University of Chicago. Passed passably reasonable tests. # # Notes: # - Written for Python 2.2.2. # - Module docstrings can be tested using the doctest module. To # test, execute "python generic_model.py". Full tests of this # class are conducted by subclasses of GenericModel, and usually # are written using unittest. # - See import statements throughout for non-"built-in" packages and # modules required. # # Copyright (c) 2004-2005 by Johnny Lin. For licensing, distribution # conditions, contact information, and additional documentation see # the URL http://geosci.uchicago.edu/csc/modelutil/doc/. #======================================================================= #---------------- Module General Import and Declarations --------------- #- If you're importing this module in testing mode, or you're running # pydoc on this module via the command line, import user-specific # settings to make sure any non-standard libraries are found: import os, sys if (__name__ == "__main__") or \ ("pydoc" in os.path.basename(sys.argv[0])): import user del os, sys #- Set module version to package version: import package_version __version__ = package_version.version __author__ = package_version.author __date__ = package_version.date __credits__ = package_version.credits del package_version #- Import statements: from stateset import StateSet #--------------------- Class GenericModel: Header --------------------- class GenericModel(StateSet): """A generic model. This is the parent class for all models (thus all models are sub- classes of GenericModel) and defines all methods and attributes common to all models. An object of this class is instantiated with a StateSet as input. This class is just a StateSet class with a few extra methods added; all StateSet methods and attributes exist and apply in GenericModel. The StateSet must have a State object with an id attribute of "t0", as it assumes that the "t0" State object is the state at the current timestep, and that the model calculates starting from that point. All container methods of this class assume that the StateSet data is the data. Depending on what methods are applied, this StateSet data is altered/augmented accordingly. """ #------------- Class GenericModel: __init__ Special Method ------------ def __init__(self, input_StateSet): """Instantiation method. Method argument: * input_StateSet: Input StateSet object. In this instantiation method the two things that happen is that all attributes in input_StateSet (listed in __dict__) become attributes of this model, by reference, and the setup method is executed. """ for akey in input_StateSet.__dict__.keys(): self.__dict__[akey] = input_StateSet.__dict__[akey] self.setup() #---------------- Class GenericModel: add_tend1 Method ---------------- def add_tend1(self, *args, **kwds): """Adds args "tendency" objects to the model data container. A call to this method is generally the final line in the user- written overloaded tend1 method. Note the tend1 method can have more than one call to this method, however. Input Positional Argument(s): * args: Field objects to add to the State object in the model data container that has id "tend1". If such a State object doesn't exist, the State is created. All the Field objects in this list args should have unique ids; an exception is thrown if not. Input Keyword Argument: * kwds: Dictionary of keyword arguments. Currently supports a single keyword: mode. The mode keyword describes whether the "tendency" object will be added to the model data contai- ner by overwriting any previously existing "tendency" value or summed to any previously existing "tendency" value. It is a string with the following possible values: + "replace": Any previously existing "tendency" values in the data container will be overwritten. This is the de- fault value of mode in this method. + "sum_with_previous": Any previously existing "tendency" values will be added to the value given in the input Field object. Most of the time the list of Field objects in args will be local variables calculated in the tend1 method. For models where all the tendencies in the tend1 method are calculated by calling the tend1 methods of submodels, there will likely be no locally calculated "tendencies" to add. In those cases, there's nothing to add to the "tend1" State so add_tend1 will be called with no arguments. """ #- Initialize mode: if 'mode' in kwds.keys(): mode = kwds['mode'] else: mode = 'replace' #- Check list of arguments does not contain Fields with dupli- # cate ids: list_ids = [anarg.id for anarg in args] for anid in list_ids: if list_ids.count(anid) != 1: raise ValueError, "Field list has duplicate ids" #- Create increment ("tendency") State object as needed and # add tendencies: if len(args) < 1: pass else: if 'tend1' not in self.keys(): tend1_State = self['t0'].copymeta() tend1_State.id = 'tend1' self.add( tend1_State ) del tend1_State if mode == 'replace': for anarg in args: self['tend1'].add( anarg ) elif mode == 'sum_with_previous': for anarg in args: if anarg.id in self['tend1'].keys(): tmp = self['tend1'][anarg.id] + anarg tmp.replace_all_meta( self['tend1'][anarg.id] ) self['tend1'].add( tmp ) del tmp else: self['tend1'].add( anarg ) else: raise ValueError, "Bad mode" #---------------- Class GenericModel: asStateSet Method --------------- def asStateSet(self): """Returns model attributes as a StateSet object by reference. Only standard StateSet attributes (i.e. those that would be initialized on instantiation) are part of the returned State- Set object. Any additional attributes in the model object are not returned. """ output_StateSet = StateSet() for akey in output_StateSet.__dict__.keys(): output_StateSet.__dict__[akey] = self.__dict__[akey] return output_StateSet #------------------ Class GenericModel: setup Method ------------------ def setup(self): """Additional tasks to perform on object instantiation. This method is executed upon object instantiation. Its default behavior is to do nothing; i.e. here its a stub. Its use is in subclasses of GenericModel where the method will be overloaded and customized. """ pass #------------------ Class GenericModel: step1 Method ------------------ def step1(self): """Integrate State one timestep forward. If the "tend1" id State object does not exist in the model data, method tend1 is called to calculate that "tendency". The current State (i.e. with id attribute "t0") is then inte- grated forward one timestep by adding the "tend1" id State object to the "t0" State object to get a new State object with id attribute "tp1", which is then added to the model data. The "tend1" State object is deleted from the model data. No other State objects in the model data are altered. """ if 'tend1' not in self.keys(): self.tend1() tp1_State = self['t0'].copy() #- Init. a tp1 State object for akey in self['tend1'].keys(): #- Cycle tend1 Fields if not self['tend1'][akey].extra_meta.istend1: raise ValueError, "Field not a tend1 variable" tp1_State[akey] += self['tend1'][akey] tp1_State[akey].replace_all_meta( self['tend1'][akey] ) tp1_State[akey].extra_meta.istend1 = False del self['tend1'] #- Delete "tendency" from StateSet tp1_State.id = 'tp1' self['tp1'] = tp1_State #- Add tp1 to StateSet #-------------- Class GenericModel: step1_return Method --------------- def step1_return(self): """Integrate one timestep forward and set new timestep to t=0. If the State object with id "tp1" does not exist, the method calculates the state at the next timestep after "t0" (i.e. "tp1"), shifts all State objects one timestep back, deletes the earliest timestep, and deletes any timesteps later than "t0". If the "tp1" object already exists, we assume that object is the results of the integration and the integration step is skipped. The rest of the method tasks are still com- pleted, however. Thus, after this method is executed, the State with id of "t0" in the model data is the newly calculated timestep. The pur- pose of this method is to put the model in a state ready to be integrated another timestep. """ if 'tp1' not in self.keys(): self.step1() self.shift_time_stateid(-1) self.del_earliest() list_id_num = self.stateid2int() if max(list_id_num) > 0: for akey in self.keys(): if akey.startswith('tp'): del self[akey] #------------------ Class GenericModel: tend1 Method ------------------ def tend1(self): """Calculate "tendency" for State for one timestep forward. The "tendency" required to increment all time model calculated prognostic variables in the current State (i.e. with id attri- bute "t0") forward one time step is calculated. This new State has id attribute "tend1"; only Fields that have an increment are in this new "tend1" State object. The "tend1" State object is added to the model container. The "tendency" calculated here is actually the true tendency multiplied by one timestep; thus it is the value to add to the current state to increment one timestep forward. In other con- texts what is called "'tendency'" here is referred to as "in- crement". The code structure of this method should include the following and be in the following order: (1) Calculate "tendency(ies)" as Field objects. These Field objects should have attribute extra_meta.istend1=True. (2) Add these Field objects to the model data container as a State object with id "tend1" using the add_tend1 method. This method call is generally the final line in the tend1 method. The tend1 method is useful if you're only interested in the "tendencies" or if you wish to calculate a number of "tenden- cies" in the same time step with the same initial conditions before completing the integration. The use of this method is in subclasses of GenericModel where this method will be overloaded and customized, as this method is the meat of any model. In GenericModel, this method throws a NotImplementedError exception, which enforces the require- ment to overload the method. """ raise NotImplementedError, "Method not implemented" #-------------------------- Main: Test Module ------------------------- #- Define additional examples for doctest to use: __test__ = {'Additional Examples and Tests': """ >>> set = StateSet(id='variables') >>> model = GenericModel(set) >>> print model.id variables """} #- Execute doctest if module is run from command line: if __name__ == "__main__": """Test the module. Tests the examples in all the module documentation strings, plus __test__. Note: To help ensure that module testing of this file works, the parent directory to the current directory is added to sys.path. """ import doctest, sys, os sys.path.append(os.pardir) doctest.testmod(sys.modules[__name__]) # ===== end file =====