#!/usr/bin/python -tt #======================================================================= # General Documentation """Define object and mathematical functions for model fields. The Field container class is the fundamental building block for all fields that are part of models supported by package modelutil. Any quantity can be a Field object, whether a parameter, diagnostic var- iable, prognostic variable, or axis variable. Collections of Field objects describe the state of the model at a particular time. This module defines the Field class and functions that operate on or with Field objects. Much of the functionality of Numeric/num- array and MA/ma is reproduced for Field objects. Use the interpre- ter dir() and help() commands to list all the functions in the mo- dule. Supporting classes defined in this module include: BinaryFunction, BinaryOperation, ExtraMeta, NullaryOperation, UnaryFunction, and UnaryOperation. ExtraMeta holds all metadata of a Field object besides field id, long_name, and units. The other supporting clas- ses are explained as follows. The underlying functions of *aryOperation and *aryFunction field functions are either Numeric/numarray or MA/ma functions of the same name. The *aryOperation and *aryFunction classes enable the module to make intelligent choices as whether to use the Numeric/ numarray or MA/ma version of a function. As long the two versions of the function have the same name and can take the same number of Field objects (replacing array objects) as arguments, these suppor- ting classes can be used. Note however field functions should not be assumed to have the same methods as Numeric/numarray ufuncs. The terms nullary, unary, and binary refer to the number of Field objects that are function arguments (in the binary case, one of the arguments can be a non-Field array or scalar). The *ary ob- jects are assumed to be at the beginning of the parameter list. The *aryOperation class is for functions that return a Field ob- ject. This includes functions that sometimes return numeric sca- lars or masks (e.g. where), since such masks are often used in arithmetic operations with model variables (to turn on/off a par- ticular element). The returned object never shares any storage with its input arguments and all non-data attributes are empty. The *aryFunction class is for functions that always return non- array (and thus non-Field) objects, Booleans or scalars under- stood to be Boolean, or array objects that should not function as Field objects (e.g. shape tuples, list of indices, strings) and it follows the same rules as its underlying Numeric/numarray or MA/ma function as to whether its return value shares storage with its input arguments (generally not). There are no Nullary- Functions. In order to improve speed, *aryOperation and *aryFunction objects apply the Numeric/numarray function whenever the data in any *ary inputs are not a masked MA/ma array. If an exception is returned, the corresponding MA/ma function is tried; arrays output by that function will be MA/ma. Thus, if you want to force use of the MA/ma function in the field version, make sure the data of one of the *ary inputs is an MA/ma array. Numeric/numarray and MA/ma have callable attributes associated with every binary universal function (ufunc), such as accumulate, outer, etc. The *aryOperation and *aryFunction objects in this module do not support those callable attributes. Note that the usage in field of "*ary operation" and "*ary func- tion" is slightly different than in Numeric/numarray and MA/ma. """ #----------------------------------------------------------------------- # Additional Documentation # # RCS Revision Code: # $Id: field.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. # - Docstrings give results assuming numarray/ma is used, on a GNU/ # Linux machine running kernel release 2.4.24-1-k7. # - See import statements throughout module for dependencies. # # 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 --------------- #- 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 #- 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 #- Import Numeric/numarray and MA/ma as appropriate (see module # gemath.num_settings for details as to what gets imported). # Import other default functions, modules, and packages: from gemath.num_settings import * import gemath.where_close gemath_where_close = gemath.where_close.where_close import copy #--------------------------- Module Variables -------------------------- # # _key_meta: A tuple of the attribute names for key meta data # for a Field object. # _ok_field_attrib: A list of all accepted private and public attri- # butes for a Field object. # # Additional module variables (that require the classes to be defined # first) are found after the class definitions, before the initializa- # tion of module functions. _key_meta = ('id', 'long_name', 'units') _ok_field_attrib = ( 'id', 'long_name', 'units', 'extra_meta', '_data' ) #------------------------- BinaryFunction Class ------------------------- class BinaryFunction(object): """Binary functions on/with Field objects. See the module docstring for details. """ def __init__(self, op_name): """Initialization function. Input Argument: * op_name: String that specifies the name of a Numeric/numarray and MA/ma function (e.g. allclose). The name of the function must be the same for both Numeric/numarray and MA/ma. """ self.op_name = op_name op_name_in_both = (self.op_name in N.__dict__.keys()) and \ (self.op_name in MA.__dict__.keys()) if not op_name_in_both: raise ValueError, op_name + " not both masked and unmasked" def __call__(self, x, y, *more_args, **kwds): """Calling function. Returns non-numeric objects given two inputs (see argument de- scription below). Method Arguments: * x: Positional argument. 1st Field object, Numeric/numarray or MA/ma array or scalar being operated on. * y: Positional argument. 2nd Field object, Numeric/numarray or MA/ma array or scalar being operated on. * more_args: Any additional positional arguments. Passed to the underlying Numeric/numarray or MA/ma function without change. * kwds: Any keyword arguments. Passed to the underlying Numer- ic/numarray or MA/ma function without change. """ if isinstance(x, _typeField): xdata = x.refdata() x_is_MA = x.isMA() else: xdata = x x_is_MA = MA.isMA(x) if isinstance(y, _typeField): ydata = y.refdata() y_is_MA = y.isMA() else: ydata = y y_is_MA = MA.isMA(y) if x_is_MA or y_is_MA: return MA.__dict__[self.op_name]( \ xdata, ydata, *more_args, **kwds ) else: try: return N.__dict__[self.op_name]( \ xdata, ydata, *more_args, **kwds ) except: return MA.__dict__[self.op_name]( \ xdata, ydata, *more_args, **kwds ) #------------------------ BinaryOperation Class ------------------------- class BinaryOperation(object): """Binary operations on Field objects. See the module docstring for details. """ def __init__(self, op_name): """Initialization function. Input Argument: * op_name: String that specifies the name of a Numeric/numarray and MA/ma function (e.g. add). The name of the function must be the same for both Numeric/numarray and MA/ma. """ self.op_name = op_name op_name_in_both = (self.op_name in N.__dict__.keys()) and \ (self.op_name in MA.__dict__.keys()) if not op_name_in_both: raise ValueError, op_name + " not both masked and unmasked" def __call__(self, x, y, *more_args, **kwds): """Calling function. Returns a Field object whose data is the result of the binary operation on x and y. The returned Field object has empty non- data attributes. The return Field object does not share any data with the input arguments. Method arguments: * x: Positional argument. 1st Field object, Numeric/numarray or MA/ma array or scalar being operated on. * y: Positional argument. 2nd Field object, Numeric/numarray or MA/ma array or scalar being operated on. * more_args: Any additional positional arguments. Passed to the underlying Numeric/numarray or MA/ma function without change. * kwds: Any keyword arguments. Passed to the underlying Numer- ic/numarray or MA/ma function without change. """ tmp = Field([0,]) if isinstance(x, _typeField): xdata = x.refdata() x_is_MA = x.isMA() else: xdata = x x_is_MA = MA.isMA(x) if isinstance(y, _typeField): ydata = y.refdata() y_is_MA = y.isMA() else: ydata = y y_is_MA = MA.isMA(y) if x_is_MA or y_is_MA: tmpdata = MA.__dict__[self.op_name]( \ xdata, ydata, *more_args, **kwds ) else: try: tmpdata = N.__dict__[self.op_name]( \ xdata, ydata, *more_args, **kwds ) except: tmpdata = MA.__dict__[self.op_name]( \ xdata, ydata, *more_args, **kwds ) tmp.setdata( copy.deepcopy(tmpdata) ) del xdata, ydata, tmpdata return tmp #---------------------------- ExtraMeta Class --------------------------- class ExtraMeta(object): """Object of all metadata besides id, long_name, and units. Attributes of the object are the names of the metadata and the values of the attributes are the values of the metadata. The class ignores attempts to create or add attributes id, long_name, and units. Metadata are usually strings, lists, or dictionaries. Objects are instantiated by any number of keyword inputs in the calling line. Keywords and their values should correspond to a standard list of possible values, which are specific to a given model. For more information see: http://geosci.uchicago.edu/csc/modelutil/doc/fieldmeta.html Additional metadata is added by setting the attributes of this object directly. If one of the keywords is extra_meta (set to an ExtraMeta object), on instantiation of an ExtraMeta object the extra_meta setting is used as the basis of the ExtraMeta object, and additional keywords are added to those values. Examples: >>> a = ExtraMeta(id='u', axis=['Pressure units']) >>> print a.__dict__ {'axis': ['Pressure units']} >>> print a.axis[0] Pressure units >>> print a.id Traceback (most recent call last): ... AttributeError: 'ExtraMeta' object has no attribute 'id' >>> a.axis = ['Latitude'] >>> print a.axis[0] Latitude >>> a.id = 'u' >>> print a.id Traceback (most recent call last): ... AttributeError: 'ExtraMeta' object has no attribute 'id' """ def __init__(self, **keywords): if keywords.has_key('extra_meta') and \ ( type(keywords['extra_meta']) == type(ExtraMeta()) ): for akey in keywords['extra_meta'].__dict__.keys(): self.__dict__[akey] = keywords['extra_meta'].__dict__[akey] for akey in keywords.keys(): if akey in _key_meta + ('extra_meta',): pass else: self.__dict__[akey] = keywords[akey] def __setattr__(self, name, value): if name in _key_meta: pass else: self.__dict__[name] = value #------------------------ NullaryOperation Class ------------------------ class NullaryOperation(object): """Nullary operations on Field objects. See the module docstring for details. """ def __init__(self, op_name): """Initialization function. Input Argument: * op_name: String that specifies the name of a Numeric/numarray and MA/ma function (e.g. zeros). The name of the function must be the same for both Numeric/numarray and MA/ma. """ self.op_name = op_name op_name_in_both = (self.op_name in N.__dict__.keys()) and \ (self.op_name in MA.__dict__.keys()) if not op_name_in_both: raise ValueError, op_name + " not both masked and unmasked" def __call__(self, *more_args, **kwds): """Calling function. Returns a Field object whose data is the result of the operation based on the input arguments. The object has empty non-data attributes and shares no memory with inputs. Method Arguments: * more_args: Positional arguments. Passed to the underlying Numeric/numarray or MA/ma function without change. * kwds: Keyword arguments. Passed to the underlying Numeric/ numarray or MA/ma function without change. """ tmp = Field([0,]) try: tmp.setdata( N.__dict__[self.op_name](*more_args, **kwds) ) except: tmp.setdata( MA.__dict__[self.op_name](*more_args, **kwds) ) return tmp #-------------------------- UnaryFunction Class ------------------------- class UnaryFunction(object): """Unary functions on/with Field objects. See the module docstring for details. """ def __init__(self, op_name): """Initialization function. Input Argument: * op_name: String that specifies the name of a Numeric/numarray and MA/ma function (e.g. shape). The name of the function must be the same for both Numeric/numarray and MA/ma. """ self.op_name = op_name op_name_in_both = (self.op_name in N.__dict__.keys()) and \ (self.op_name in MA.__dict__.keys()) if not op_name_in_both: raise ValueError, op_name + " not both masked and unmasked" def __call__(self, x, *more_args, **kwds): """Calling function. Returns non-numeric objects given operations on one input (see argument description below). Method arguments: * x: Positional argument. Field object being operated on/with. * more_args: Any additional positional arguments. Passed to the underlying Numeric/numarray or MA/ma function without change. * kwds: Any keyword arguments. Passed to the underlying Numer- ic/numarray or MA/ma function without change. """ if x.isMA(): return MA.__dict__[self.op_name]( \ x.refdata(), *more_args, **kwds ) else: try: return N.__dict__[self.op_name]( \ x.refdata(), *more_args, **kwds ) except: return MA.__dict__[self.op_name]( \ x.refdata(), *more_args, **kwds ) #------------------------- UnaryOperation Class ------------------------- class UnaryOperation(object): """Unary operations on Field objects. See the module docstring for details. """ def __init__(self, op_name): """Initialization function. Input Argument: * op_name: String that specifies the name of a Numeric/numarray and MA/ma function (e.g. sinh). The name of the function must be the same for both Numeric/numarray and MA/ma. """ self.op_name = op_name op_name_in_both = (self.op_name in N.__dict__.keys()) and \ (self.op_name in MA.__dict__.keys()) if not op_name_in_both: raise ValueError, op_name + " not both masked and unmasked" def __call__(self, x, *more_args, **kwds): """Calling function. Returns a Field object is returned with empty non-data attri- butes, whose data is the result of the unary operation. The returned object does not share data memory with input object. Method Arguments: * x: Positional argument. Field object being operated on. * more_args: Any additional positional arguments. Passed to the underlying Numeric/numarray or MA/ma function without change. * kwds: Any keyword arguments. Passed to the underlying Numer- ic/numarray or MA/ma function without change. """ tmp = Field( x.copydata() ) if x.isMA(): tmp.setdata( MA.__dict__[self.op_name]( \ tmp.refdata(), *more_args, **kwds ) ) else: try: tmp.setdata( N.__dict__[self.op_name]( \ tmp.refdata(), *more_args, **kwds ) ) except: tmp.setdata( MA.__dict__[self.op_name]( \ tmp.refdata(), *more_args, **kwds ) ) return tmp #------------------------- Field Class: Header ------------------------ class Field(object): """Class for a single model field. Container object for a model parameter or quantity. The data for a Field object is imported from either a Numeric/numarray or MA/ma array or scalar. Public Instance Attributes: * extra_meta: Object containing all metadata associated with the field besides id, long_name, and units. Instance of class Ex- traMeta. Default is an empty ExtraMeta object. * id: Unique name of the field. String. Default value is None. * long_name: Description of the field. String. Default value is None. * units: Units of the field. String. Default value is None. The names of all accepted public and private instance attributes is given in the field module tuple _ok_field_attrib. All metadata for the object (especially self.id and self.units) should follow an accepted convention with an appropriate mapping. Such a mapping is specific to a given model. For more information see: http://geosci.uchicago.edu/csc/modelutil/doc/fieldmeta.html See the docstring for __init__ for details about object instanti- ation. Note that the data for the Field container is stored in private attribute self._data, which is either a Numeric/numarray or MA/ma array or scalar. (Some of the Numeric/numarray and MA/ma methods are also defined for Field objects.) Please do not access the _data attribute directly but rather use the predefined methods for the Field class (e.g. setdata and refdata). The field module also contains functions that operate on Field objects. (1) Example without metadata: >>> data = [1., 2., 5., -3., -4.4] >>> a = Field(data) >>> ['%.6g' % a[i] for i in range(len(a))] ['1', '2', '5', '-3', '-4.4'] >>> print a.typecode() d >>> print a.id None >>> print a.extra_meta.__dict__ {} (2) Example setting data as Float32 and with some metadata: >>> data = N.array([1., 2., 5., -3., -4.4]) >>> a = Field(data.astype(N.Float32), id='u', axis=['Latitude']) >>> ['%.6g' % a[i] for i in range(len(a))] ['1', '2', '5', '-3', '-4.4'] >>> print a.typecode() f >>> print a.id u >>> print a.extra_meta.axis[0] Latitude >>> a[2:4] = N.array([-3.4, 55.32]) >>> ['%.6g' % a[i] for i in range(len(a))] ['1', '2', '-3.4', '55.32', '-4.4'] (3) Example of using a field function on a Field object: >>> b = sin(a) >>> print b.id None >>> print b.extra_meta.__dict__ {} >>> ['%.6g' % b[i] for i in range(len(b))] ['0.841471', '0.909297', '0.255541', '-0.942043', '0.951602'] """ #------------- Field Class: Initialization Special Method ------------- def __init__(self, data, **keywords): """Initialize Field class object. Method arguments: * data: A list, tuple, scalar, Numeric/numarray array, MA/ ma array, or Field object of field data. If data is a list, tuple, or scalar (a scalar is defined as an object with an empty shape tuple), the input is converted into a Numeric/ numarray object (or MA/ma object if the scalar is the masked value) and set by value. If data is a Numeric/numarray ar- ray, MA/ma array, or Field object, the data is passed into the new Field object by reference. If data is a Field object, the metadata in the new Field object is taken from the in- stantiation argument line as opposed to the input Field ob- ject; only the data comes from the input Field object. * keywords: A dictionary of keywords and values that specify metadata attributes of the object to set. Keywords id, long_name, and units are set as attributes while all other keywords are set as attributes of the ExtraMeta object sto- red in attribute extra_meta. Attribute names (either in the Field or ExtraMeta objects) are the keyword keys and their values the keyword values. If one of the keywords is extra_meta (set to an ExtraMeta object), that object is used as the basis of the extra_meta attribute, and additional keywords are added in to that key- word specified ExtraMeta object. See class header for additional details of instance attributes of this class. """ if isinstance(data, type(self)): self.setdata(data.refdata()) elif isinstance(data, type([])) or isinstance(data, type(())) or \ (N.shape(data) == ()): if MA.isMA(data): self.setdata(MA.array(data)) else: self.setdata(N.array(data)) else: self.setdata(data) for ameta in _key_meta: if keywords.has_key(ameta): self.__dict__[ameta] = keywords[ameta] else: self.__dict__[ameta] = None self.extra_meta = ExtraMeta(**keywords) #------------- Field Class: Set Attribute Special Method -------------- def __setattr__(self, name, value): """Set attribute for Field class. The Field class should only have the attributes defined in the module tuple _ok_field_attrib. This overloaded __setattr__ function ensures no other attributes are set, raising an excep- tion if you attempt to add an unaccepted attribute. """ if name in _ok_field_attrib: self.__dict__[name] = value else: raise AttributeError, "Bad attribute" #-------------- Field Class: Comparison Special Methods --------------- # def __eq__(self, other): return _compare2bool( equal(self, other) ) def __ge__(self, other): return _compare2bool( greater_equal(self, other) ) def __gt__(self, other): return _compare2bool( greater(self, other) ) def __le__(self, other): return _compare2bool( less_equal(self, other) ) def __lt__(self, other): return _compare2bool( less(self, other) ) def __ne__(self, other): return _compare2bool( not_equal(self, other) ) #--------- Field Class: Other General-Purpose Special Methods --------- def __call__(self): raise NotImplementedError, "Special method not implemented" def __str__(self): """Return human-readable representation of Field object. Method returns a string representation of the data in the Field object, using the __repr__ method in Numeric/numarray or MA/ma. Metadata attributes in the Field object are not printed. """ return self.refdata().__repr__() #--------------- Field Class: Container Special Methods --------------- # # Note: __iter__ is not explicitly defined for this class. Instead # we're using the default definition. The reason is that in-place # operations require this method (at least for numarray data) and the # complexity is such that it seems better to just use the default. # Comprehensive docstring unit tests of in-place operations of both # 1-D and 2-D Field objects suggest this strategy will work. def __contains__(self, item): """Result of Boolean test on data in Field object.""" return item in self.refdata() def __delitem__(self, key): del self.refdata()[key] def __getitem__(self, key): return self.refdata()[key] def __len__(self): """Result of len() on data in Field object.""" return len(self.refdata()) def __setitem__(self, key, value): """Set item in container. If value is a Field object, self._data[key] is set to the _data attribute of value. Otherwise, value is set to self._data. """ if isinstance(value, type(self)): self.refdata()[key] = value.refdata() else: self.refdata()[key] = value #-------------- Field Class: Numeric Special Methods (1) -------------- # # Grouping and order of list follows Martelli (2003, pp. 97-99), except # in-place operations are listed in the following section. Note that # numeric special methods that are undefined for this class and which # automatically return an "operand type" error are left out. def __abs__(self): return absolute(self) def __neg__(self): return negative(self) def __pos__(self): return array(self) def __add__(self, other): return add(self, other) def __div__(self, other): return divide(self, other) def __floordiv__(self, other): return floor_divide(self, other) def __mod__(self, other): return remainder(self, other) def __mul__(self, other): return multiply(self, other) def __sub__(self, other): return subtract(self, other) def __truediv__(self, other): return true_divide(self, other) def __divmod__(self, other): return (self//other, self%other) def __pow__(self, other): return power(self, other) __radd__ = __add__ def __rdiv__(self, other): return divide(other, self) def __rmod__(self, other): return remainder(other, self) __rmul__ = __mul__ def __rsub__(self, other): return subtract(other, self) #-------------- Field Class: Numeric Special Methods (2) -------------- # # These in-place operations use a try/except exception handler to deal # with the case where the LHS data is unmasked while the RHS is masked, # and the case when other is a Field object. def __iadd__(self, other): if isinstance(other, _typeField): try: self._data += other._data except: self._data = self._data + other._data else: try: self._data += other except: self._data = self._data + other return self def __idiv__(self, other): if isinstance(other, _typeField): try: self._data /= other._data except: self._data = self._data / other._data else: try: self._data /= other except: self._data = self._data / other return self def __ifloordiv__(self, other): if isinstance(other, _typeField): try: self._data //= other._data except: self._data = self._data // other._data else: try: self._data //= other except: self._data = self._data // other return self def __imod__(self, other): if isinstance(other, _typeField): try: self._data %= other._data except: self._data = self._data % other._data else: try: self._data %= other except: self._data = self._data % other return self def __imul__(self, other): if isinstance(other, _typeField): try: self._data *= other._data except: self._data = self._data * other._data else: try: self._data *= other except: self._data = self._data * other return self def __isub__(self, other): if isinstance(other, _typeField): try: self._data -= other._data except: self._data = self._data - other._data else: try: self._data -= other except: self._data = self._data - other return self def __itruediv__(self, other): if isinstance(other, _typeField): try: self._data /= other._data except: self._data = self._data / other._data else: try: self._data /= other except: self._data = self._data / other return self #-------------------- Field Class: Copying Methods -------------------- def copy(self): """Returns deepcopy of Field object.""" return copy.deepcopy(self) def copydata(self): """Returns deepcopy (if possible) of data of Field object. Result returned is a Numeric/numarray or MA/ma array object. Deepcopy returns a TypeError when executed on a deepcopy of a Field object, and thus in the TypeError case a shallow copy is returned. """ try: return copy.deepcopy(self.refdata()) except TypeError: return copy.copy(self.refdata()) def copymeta(self): """Returns deepcopy of Field object metadata. The entire Field object is copied, except for the data held in the container (i.e. in private variable _data). Instead, the returned Field object has an empty array for the object's data, in addition to a copy of the original Field object's metadata. """ tmp = Field([]) for akey in self.__dict__.keys(): if akey != '_data': tmp.__dict__[akey] = copy.deepcopy(self.__dict__[akey]) else: pass return tmp #-------------- Field Class: meta_ok_to_interface Method -------------- def meta_ok_to_interface(self, IMobj, check_long_name=False): """Returns True if metadata passes checks for interfaces. Tests whether the Field object metadata passes consistency checks in accordance with the description given in Interface- Meta object IMobj. Positional Input Argument: * IMobj: InterfaceMeta object that defines the accepted metadata that you wish to check this Field variable's metadata against. For details see: http://geosci.uchicago.edu/csc/modelutil/doc/imeta.html Keyword Input Arguments: * check_long_name: If set to True, the method not only checks units but also long_name. Default is False. For this method to return True the Field variable does not have to be one defined in the IMobj definition, but if it is defined, it has to have the same metadata as given in IMobj. The consistency checks are simple and include: * If this Field variable's id is found in IMobj.axis, the isaxis attribute must be true and the units must match the id in IMobj. * If this Field variable's id is found in IMobj.id, the units must match the id in IMobj. * If this Field variable's id is found in IMobj.id, and the extra_meta attribute axis exists, that the value is in IMobj, that the corresponding values of axis_units match (if axis_units exists), and whether the number of dimen- sions of the data matches the number of axis and axis_units labels. If isaxis is True and extra_meta.axis (or related attributes) exist an exception is raised. * An exception is raised if axis is defined but axis_units is not (or vice versa). * If the check_long_name flag is True, the above tests on units and axis_units are also applied to long_name and axis_long_name. Each test, of course, is only conducted when long_name or axis_long_name, respectively, are defined. Also, if long_name is None, the test of that attribute is ignored. """ if self.id in IMobj.axis: try: if not self.extra_meta.isaxis: return False except AttributeError: raise AttributeError, "isaxis must be defined" if self.units != IMobj.axis_units[self.id]: return False if check_long_name and hasattr(self, 'long_name') and \ (self.long_name != None): if self.long_name != IMobj.axis_long_name[self.id]: return False if self.id in IMobj.id: if self.units != IMobj.units[self.id]: return False if check_long_name and hasattr(self, 'long_name') and \ (self.long_name != None): if self.long_name != IMobj.long_name[self.id]: return False if ('axis' in self.extra_meta.__dict__.keys()) or \ ('axis_units' in self.extra_meta.__dict__.keys()): if 'isaxis' in self.extra_meta.__dict__.keys(): if self.extra_meta.isaxis: raise AttributeError, "axis variables cannot have " \ + "axis related attributes" if 'axis' not in self.extra_meta.__dict__.keys(): raise AttributeError, "axis attribute missing" if (self.id in IMobj.id) and hasattr(self.extra_meta, 'axis'): ndims = len(shape(self)) for iax in range(len(self.extra_meta.axis)): if self.extra_meta.axis[iax] not in IMobj.axis: return False if 'axis_units' in self.extra_meta.__dict__.keys(): if self.extra_meta.axis_units[iax] != \ IMobj.axis_units[self.extra_meta.axis[iax]]: return False if check_long_name and \ hasattr(self.extra_meta, 'axis_long_name'): if self.extra_meta.axis_long_name[iax] != \ IMobj.axis_long_name[self.extra_meta.axis[iax]]: return False if len(self.extra_meta.axis) != ndims: return False if hasattr(self.extra_meta, 'axis_units'): if len(self.extra_meta.axis_units) != ndims: return False if check_long_name and \ hasattr(self.extra_meta, 'axis_long_name'): if len(self.extra_meta.axis_long_name) != ndims: return False return True #----------- Field Class: A Few Metadata Management Methods ----------- def clear_all_meta(self): """Clears all metadata fields. Sets metadata attributes (listed in _key_meta) to None and extra_meta attribute to an empty ExtraMeta object. """ for ameta in _key_meta: self.__dict__[ameta] = None self.extra_meta = ExtraMeta() def clear_extra_meta(self): """Sets the extra_meta attribute to an empty ExtraMeta object.""" self.extra_meta = ExtraMeta() def replace_all_meta(self, *arg, **keywords): """Clear all metadata fields and replace. Erases all metadata values. Sets metadata to the metadata val- ues given in Field object *arg or by the input keyword para- meters given in **keywords. The data in the Field container object is unaltered. Method arguments: * arg: A single argument specifying a Field object whose metadata will be copied in its entirety and used to replace the metadata of the current Field object. If arg is set, any keywords specified are ignored. * keywords: A dictionary of keywords and values that specify metadata attributes of the object to set. Keywords id, long_name, and units are set as attributes (default value of None) while all other keywords are set as attributes of the extra_meta object. Attribute names are the keyword keys and their values the keyword values. See class header for additional details of instance attributes of the Field class. Examples: >>> data = [1., 2., 5., -3., -4.4] >>> a = Field( data, id='Ts', long_name='Surface temperature' \ , units='K' ) >>> print a.id Ts >>> print a.extra_meta.__dict__ {} >>> a.replace_all_meta( id='F_sens', long_name='Sensible heat' \ , units='W/m**2', axis=['Latitude'] ) >>> print a.id F_sens >>> print a.extra_meta.__dict__ {'axis': ['Latitude']} >>> b = Field([4.3, -6.45], id='FL') >>> a.replace_all_meta(b) >>> print a.id FL >>> print a.extra_meta.__dict__ {} """ if len(arg) == 0: for ameta in _key_meta: if keywords.has_key(ameta): self.__dict__[ameta] = keywords[ameta] else: self.__dict__[ameta] = None self.extra_meta = ExtraMeta(**keywords) elif len(arg) == 1: self.clear_all_meta() for ameta in _key_meta: self.__dict__[ameta] = copy.copy(arg[0].__dict__[ameta]) self.extra_meta = copy.deepcopy(arg[0].extra_meta) else: raise ValueError, "Bad argument list" #------------------ Field Class: slice_fixed Method ------------------- def slice_fixed(self, axes_State, **kwds): """Return slice of Field object with selected fixed axis(es). The general array slicing syntax used in Numeric/numarray and MA/ma arrays is supported in Field objects and is appropriate for most purposes. However, one common slicing operation is to fix one or more axis and extract the resulting subarray. For instance, if you have an array dimensioned (level, lati- tude, longitude), you might want to extract a latitude-longi- tude slice at a given level. This method enables you to do this by specifying the level value (as opposed to index) you wish to fix and the State object which contains the Field var- iables that describe the axes of the present Field object. The method accepts both a single fixed axis value to apply to all the other elements in the data, or an array of values to make the slice at. The examples below will help describe these two types of slicing capabilities. The returned subarray is a Field object and has all the meta- data from the original Field object, with the axis attribute (and related attributes like axis_units) altered as appropriate. The returned slice is a reference to or copy of the original data based upon the following criteria: If: Field data and Kwd Size 1 | Then: subarray is --------------------------------------------------------- masked | False | Copy of orig. data not masked | False | Copy of orig. data masked | True | Copy of orig. data not masked | True | References orig. data Where "Kwd Size 1" is True if all keywords in the calling line are scalars/arrays of size 1. In all cases, the metadata is a copy. If the Field object is a scalar or 1-element array (i.e. self.is_size1() returns True), the above chart does not apply and this method always returns a reference to the Field object. Note that this method will only work if self has the axis attribute in extra_meta. It will also alter the axis_units and axis_long_name attributes, as appropriate, if they exist in extra_meta, but those two attributes are not required for this method to work. If you want to set a fixed slice of data to a different value, use the slice_fixed_setdata method. Positional Input Parameter: * axes_State: A State object that contains the Field objects that describe the axes for self. axes_State can contain other Field objects too. Keyword Input Parameters: * kwds: Dictionary of axes to fix in taking the subarray of self. The keys are the Field ids of the axes for self and the values are the values of where to fix the axes to ex- tract the subarray. Those values should be scalars or 1- element arrays, or arrays with the shape of the slice if all the axes labeled by the keywords were removed from the data array (masked or unmasked arrays are both ok, although if the array is masked it cannot have any elements where the mask is True/1). Keywords that are not in the list of axis ids for the Field object are ignored in making the slice. The metadata (e.g. list of axis ids) attached to the Field object is assumed correct: There are no explicit checks for this done by this method (e.g. it does not check whether the number of dimensions of the data equals the number of elements in extra_meta.axis). Examples: (1) The simplest case is making a slice at a constant level for all elements of the array. Take for instance the ex- ample of a slice at pressure level 500 hPa: >>> from state import State >>> data = N.reshape(N.arange(36.)*0.2+290., (3,3,4)) >>> lon = N.arange(4.)*90. >>> lat = N.array([-90., 0., 90.]) >>> lev = N.array([850., 500., 250.]) >>> f1 = Field( data, id='Ts', units='K' \ , axis=['lev', 'lat', 'lon'] ) >>> f2 = Field(lon, id='lon', units='degrees_east', isaxis=True) >>> f3 = Field(lat, id='lat', units='degrees_north', isaxis=True) >>> f4 = Field(lev, id='lev', units='hPa', isaxis=True) >>> s = State(f1, f2, f3, f4, id='t0') >>> print shape(f1) (3, 3, 4) >>> print f1.extra_meta.axis ['lev', 'lat', 'lon'] >>> data[1,:,:] array([[ 292.4, 292.6, 292.8, 293. ], [ 293.2, 293.4, 293.6, 293.8], [ 294. , 294.2, 294.4, 294.6]]) >>> subarray = f1.slice_fixed(s, lev=500) >>> print subarray array([[ 292.4, 292.6, 292.8, 293. ], [ 293.2, 293.4, 293.6, 293.8], [ 294. , 294.2, 294.4, 294.6]]) >>> print shape(subarray) (3, 4) >>> print subarray.extra_meta.axis ['lat', 'lon'] The slicing operation that created subarray is equivalent to the slicing operation f1.refdata()[1,:,:] (which in turn is equivalent to slicing data[1,:,:], since f1 refer- ences data. The result subarray is of shape (3,4); the first axis in data is removed since the slice occurs at a fixed value of the axis. (2) Using the same data f1 above, here we give the case of making a slice in pressure levels, but at different levels at each lat-lon location: >>> levels = \ N.array([[ 500, 850, 850, 250 ], \ [ 500, 250, 500, 500 ], \ [ 250, 500, 850, 250 ]]) >>> subarray = f1.slice_fixed(s, lev=levels) >>> print subarray array([[ 292.4, 290.2, 290.4, 295.4], [ 293.2, 295.8, 293.6, 293.8], [ 296.4, 294.2, 292. , 297. ]]) >>> print shape(subarray) (3, 4) >>> print subarray.extra_meta.axis ['lat', 'lon'] Note again the result subarray has shape (3,4), which is the same shape as the array used to specify levels. (3) When you make a slice so only a single element remains, the returned Field object is a scalar with axis related metadata attributes removed. Using f1 from above: >>> subarray = f1.slice_fixed(s, lev=500, lat=-90, lon=0) >>> print subarray array(292.39999999999998) >>> print shape(subarray) () >>> print hasattr(subarray.extra_meta, 'axis') False (4) There is an exception to (3) above. If you slice a single element out of an array which has axes of length 1, the returned sliced array will retain those axes. Thus, if f1 has shape (4,1,1), f1.extra_meta.axis equals the list ['x','y','z'], the axis are each equal to N.arange, and the axis are in State object s, then: subarray = f1.slice_fixed(s, x=2, y=0, z=0) will return subarray of shape (1,1), and the value of subarray.extra_meta.axis = ['y', 'z']. """ #- If self is of size 1, any slice will be itself. Return # self if so and exit from this method: if self.is_size1(): return self #- Check if all the axis setting keyword values are scalars/1- # element arrays, non-1-element arrays, or a combination of # the two. If any of the keywords are arrays, save one of the # shapes as an example: all_keys_are_size1 = True all_keys_are_notsize1 = True for akey in kwds.keys(): if MA.size(kwds[akey]) == 1: all_keys_are_notsize1 = False else: all_keys_are_size1 = False akwd_shape = MA.shape(kwds[akey]) #- Extract the slice and return. Make sure the keywords are of # the correct form for the private method called: if all_keys_are_size1: return self.__slice_fixed_scalar(axes_State, **kwds) elif all_keys_are_notsize1: return self.__slice_fixed_array(axes_State, **kwds) else: kwds_all_notsize1 = kwds.copy() for akey in kwds_all_notsize1.keys(): if MA.size(kwds_all_notsize1[akey]) == 1: kwds_all_notsize1[akey] = \ N.zeros(akwd_shape, N.Float) + kwds[akey] return self.__slice_fixed_array(axes_State, **kwds_all_notsize1) #--------------- Field Class: slice_fixed_setdata Method --------------- def slice_fixed_setdata(self, axes_State, setvalue, **kwds): """Set data in a selected fixed axis slice of Field object. The general array slicing syntax used in Numeric/numarray and MA/ma arrays is supported in Field objects and is appropriate for most purposes. However, one common slicing operation is to fix one or more axis and extract the resulting subarray. The slice_fixed method accomplishes that task. Sometimes you wish to replace the elements selected by the slice_fixed method with values from another array or a scalar. This method (slice_fixed_setdata) accomplishes that. See the docstring for slice_fixed for a fuller description of what kind of slicing (i.e. selection for replacement) is done. Only the data in self is changed, and the elements of the slice specified by the keywords are set to the elements of po- sitional input parameter setvalue. In all cases the data in self are set to setvalue by value, not reference, due to how the Numeric/numarray and MA/ma packages handle setting slices. If the data in self is of size 1, any slicing keywords and the value of self is set to setvalue (which of course must also be of size 1). Also note that this method will only work if self has the axis attribute in extra_meta. Positional Input Parameter: * axes_State: A State object that contains the Field objects that describe the axes for self. axes_State can contain other Field objects too. * setvalue: A scalar, list, array, or Field object whose data values the data slice of self will be set to. setvalue must be conformable to the data in self; if setvalue is a scalar and the slicing rules result in an array, all elements of that slice are set to the setvalue value. Keyword Input Parameters: * kwds: Dictionary of axes to fix in specifying the subarray of self. The keys are the Field ids of the axes for self and the values are the values of where to fix the axes to set the data to setvalue. kwds values should be scalars or 1-element arrays, or arrays with the shape of the slice if all the axes labeled by the keywords were removed from the data array (masked or unmasked arrays are both ok, although if the array is masked it cannot have any elements where the mask is True/1). Keywords that are not in the list of axis ids for the Field object are ignored in making the slice. The metadata (e.g. list of axis ids) attached to the Field object is assumed correct: There are no explicit checks for this done by this method (e.g. it does not check whether the number of dimensions of the data equals the number of elements in extra_meta.axis). Examples: (1) Simple case of replacing a slice at pressure level 500 hPa using scalar keywords: >>> from state import State >>> data = N.reshape(N.arange(36.)*0.2+290., (3,3,4)) >>> lon = N.arange(4.)*90. >>> lat = N.array([-90., 0., 90.]) >>> lev = N.array([850., 500., 250.]) >>> f1 = Field( data, id='Ts', units='K' \ , axis=['lev', 'lat', 'lon'] ) >>> f2 = Field( lon, id='lon', units='degrees_east' \ , isaxis=True ) >>> f3 = Field( lat, id='lat', units='degrees_north' \ , isaxis=True ) >>> f4 = Field( lev, id='lev', units='hPa', isaxis=True ) >>> s = State(f1, f2, f3, f4, id='t0') >>> print shape(f1) (3, 3, 4) >>> print f1.extra_meta.axis ['lev', 'lat', 'lon'] >>> data[1,:,:] array([[ 292.4, 292.6, 292.8, 293. ], [ 293.2, 293.4, 293.6, 293.8], [ 294. , 294.2, 294.4, 294.6]]) >>> subarray = f1.slice_fixed(s, lev=500) >>> print subarray array([[ 292.4, 292.6, 292.8, 293. ], [ 293.2, 293.4, 293.6, 293.8], [ 294. , 294.2, 294.4, 294.6]]) >>> print shape(subarray) (3, 4) >>> print subarray.extra_meta.axis ['lat', 'lon'] >>> tmp = N.array([[ 282.4, 282.6, 293.8, 294. ], \ [ 313.2, 293.9, 299.6, 293.2], \ [ 295. , 294.1, 292.4, 291.6]]) >>> f1.slice_fixed_setdata(s, tmp, lev=500) >>> f1[1,:,:] array([[ 282.4, 282.6, 293.8, 294. ], [ 313.2, 293.9, 299.6, 293.2], [ 295. , 294.1, 292.4, 291.6]]) >>> print subarray array([[ 282.4, 282.6, 293.8, 294. ], [ 313.2, 293.9, 299.6, 293.2], [ 295. , 294.1, 292.4, 291.6]]) (2) Using the same data f1 above, here we give the case of re- placing a slice in pressure levels, but at different le- vels at each lat-lon location: >>> f1 = Field( data, id='Ts', units='K' \ , axis=['lev', 'lat', 'lon'] ) >>> tmp = N.array([[ 282.4, 282.6, 293.8, 294. ], \ [ 313.2, 293.9, 299.6, 293.2], \ [ 295. , 294.1, 292.4, 291.6]]) >>> levels = \ N.array([[ 500, 850, 850, 250 ], \ [ 500, 250, 500, 500 ], \ [ 250, 500, 850, 250 ]]) >>> subarray = f1.slice_fixed(s, lev=levels) >>> f1.slice_fixed_setdata(s, tmp, lev=levels) >>> N.array([f1[1,0,0], f1[0,0,1], f1[0,0,2], f1[2,0,3]]) array([ 282.4, 282.6, 293.8, 294. ]) """ #- Test if first positional parameter is a State object; test if # setvalue is of size 1 or not and specify use_setvalue, the # value to set the slice to, accordingly. Specification is # different depending on whether setvalue is a Field object or # not: from state import State if not isinstance( axes_State, State ): raise ValueError, "First parameter must be State object" if isinstance( setvalue, type(self) ): if setvalue.is_size1(): if setvalue.isMA(): use_setvalue = MA.array(setvalue.copydata()) else: use_setvalue = N.array(setvalue.copydata()) else: use_setvalue = setvalue.refdata() else: if MA.size(setvalue) == 1: if MA.isMA(setvalue): use_setvalue = MA.array(copy.deepcopy(setvalue)) else: use_setvalue = N.array(copy.deepcopy(setvalue)) else: use_setvalue = setvalue #- Set data if self is size 1 and return: if self.is_size1(): tmp_setvalue = Field(setvalue) if not tmp_setvalue.is_size1(): raise ValueError, "setvalue not of size 1" self.setdata( tmp_setvalue.refdata() ) return #- Check if all the axis setting keyword values are scalars/1- # element arrays, non-1-element arrays, or a combination of # the two. If any of the keywords are arrays, save one of the # shapes as an example: all_keys_are_size1 = True all_keys_are_notsize1 = True for akey in kwds.keys(): if MA.size(kwds[akey]) == 1: all_keys_are_notsize1 = False else: all_keys_are_size1 = False akwd_shape = MA.shape(kwds[akey]) #- Make sure use_setvalue is of correct shape (note that if # all_keys_are_size1 is True, __slice_fixed_setdata_scalar # is used, and that method automatically requires setvalue # conformity to the slice, so we don't have to make a shape # check in that case): if all_keys_are_size1: pass else: if MA.size(use_setvalue) == 1: if MA.isMA(use_setvalue): use_setvalue = MA.zeros(akwd_shape) + use_setvalue else: use_setvalue = N.zeros(akwd_shape) + use_setvalue else: if MA.shape(use_setvalue) != akwd_shape: raise ValueError, "Bad setvalue shape" #- Set the slice into self. Make sure the keywords are of the # correct form for the private method called: if all_keys_are_size1: self.__slice_fixed_setdata_scalar(axes_State, use_setvalue, **kwds) elif all_keys_are_notsize1: self.__slice_fixed_setdata_array(axes_State, use_setvalue, **kwds) else: kwds_all_notsize1 = kwds.copy() for akey in kwds_all_notsize1.keys(): if MA.size(kwds_all_notsize1[akey]) == 1: kwds_all_notsize1[akey] = \ N.zeros(akwd_shape, N.Float) + kwds[akey] self.__slice_fixed_setdata_array( axes_State, use_setvalue \ , **kwds_all_notsize1 ) #-------- Field Class: slice_fixed, slice_fixed_setdata Support ------- # # These private methods are used in supporting the slice_fixed and # slice_fixed_setdata methods. In general, none of these methods # will be used outside of this class definition. However, I can # think of some situations where __slice_fixed_1d_indices may be # of use, though not many which is why it's a private method. def __slice_fixed_1d_indices(self, axes_State, **kwds): """Returns the 1-D indices that make the result of slice_fixed. This private method returns a Field object that is the same as the result of the method slice_fixed except that the contents of the data of the object are N.Int integers, each element of which give the 1-D index in the unsliced array that holds the data value for that element. (If the data in self is size 1, the returned Field object is the same as self except the data value is the scalar value of 0.) In this method the 1-D index of an element in array a is de- fined as the value at the corresponding element of idx where: idx = N.reshape( N.arange(N.size(a)), a.shape ) The input parameters are exactly the same as described in the docstring for the slice_fixed method. The output of this me- thod shares no memory with self. The output metadata is correctly formatted to match the result of the slice. Note: This method checks that the keyword values all have the correct shape for the slicing they are attempting to accomplish, and correctly handles the condition if kwds is a mix of size 1 and non-size 1 values. """ #- If self is of size 1, any slice will be itself. Return # data with scalar value of 0 and exit from this method: if self.is_size1(): tmp = self.copy() tmp.setdata(0) return tmp #- Create new axis list (new_axis_list) of the slice (for the # extra_meta.axis attribute) and the shape of the slice (new_ # shape_list). Create tuple version of new_shape_list and # set local variables to store the rank and size of the new # (i.e. sliced) array. Note that if the sliced array is a # scalar, we set new_shape_list to [1,] in order for all the # Numeric/numarray routines to work correctly, but we set a # flag new_isscalar to True so we can compensate at the end # of the method before output: new_axis_list = [] new_shape_list = [] for anaxis in self.extra_meta.axis: if anaxis not in kwds.keys(): new_axis_list.append( anaxis ) new_shape_list.append( len(axes_State[anaxis]) ) if len(new_shape_list) == 0: new_shape_list = [1,] new_isscalar = True else: new_isscalar = False new_shape_tuple = tuple(new_shape_list) new_rank = len(new_shape_tuple) new_size = N.product(new_shape_tuple) #- Check keyword values all have the correct shape. Create new # keywords list (usekwds) to use instead of kwds from this point # in the method on. (This is done because if kwds is a mix of # size 1 and non-size 1 values, we need to make them all the # same.): all_shape_ok = True for akwd in kwds.keys(): if MA.size(kwds[akwd]) == 1: if new_size != 1: all_shape_ok = False break else: if kwds[akwd].shape != new_shape_tuple: all_shape_ok = False break if all_shape_ok: usekwds = kwds else: usekwds = kwds.copy() for akwd in usekwds.keys(): if MA.size(usekwds[akwd]) == 1: usekwds[akwd] = N.zeros(new_shape_tuple) + usekwds[akwd] else: if usekwds[akwd].shape != new_shape_tuple: raise ValueError, "Bad keyword shape" #- Create list of indices (i,j,k...) of all elements and dimen- # sions in the keyword arrays, raveled. Remember this is the # same as the indices for the new (i.e. sliced) array that the # method returns. Create 1-D indices based on the same shape # as the data in self and stored in an array the same shape as # the data in self: kwd_indices = N.indices(new_shape_tuple) kwd_indices_flat_list = [ N.ravel(kwd_indices[i]) \ for i in xrange(new_rank) ] int_size_self = int( size(self).refdata() ) data_1d_idx = N.reshape( N.arange(int_size_self), shape(self) ) #- Slice the indices. The current algorithm is slow due to # lots of for loops. For future reference, what i want to do # ultimately is to imitate this using array notation: Assume # shape of self.refdata() = (4, 5, 3, 2) and we're slicing # fixed axis 1 and 3. Then sliced_array (the slice we'd like # to output) is something like: # for i in range(len(x)): # for j in range(len(y)): # sliced_array[i,j] = \ # data_1d_idx[i,pts1[i,j],j,pts3[i,j]] if self.isMA(): sliced_array = MA.zeros(new_shape_tuple, MA.Int) else: sliced_array = N.zeros(new_shape_tuple, N.Int) for i in xrange(new_size): loc_out_list = [ kwd_indices_flat_list[idim][i] \ for idim in xrange(new_rank) ] loc_out_tuple = tuple(loc_out_list) loc_data = [] for anaxis in self.extra_meta.axis: if anaxis in usekwds.keys(): idx = gemath_where_close( \ axes_State[anaxis], usekwds[anaxis][loc_out_tuple] \ ).tolist().index(1) else: idx = loc_out_list.pop(0) loc_data.append(idx) sliced_array[loc_out_tuple] = data_1d_idx[tuple(loc_data)] #- Create sliced axis_units and axis_long_name attributes, if # appropriate: new_axis_units_list = [] new_axis_long_name_list = [] for idx in xrange(len(self.extra_meta.axis)): if self.extra_meta.axis[idx] not in usekwds.keys(): if hasattr(self.extra_meta, 'axis_units'): new_axis_units_list.append( \ self.extra_meta.axis_units[idx] ) if hasattr(self.extra_meta, 'axis_long_name'): new_axis_long_name_list.append( \ self.extra_meta.axis_long_name[idx] ) #- Form the Field object to return. Note that for scalar # Field objects, axis* attributes in extra_meta should not # exist. Also, to make the code more readable, I make an # alias to out_field.extra_meta (axis_meta) for this section: if new_isscalar: sliced_array = sliced_array[0] out_field = self.copymeta() out_field.setdata( sliced_array ) axis_meta = out_field.extra_meta if shape(out_field) == (): if hasattr(axis_meta, 'axis'): delattr(axis_meta, 'axis') if hasattr(axis_meta, 'axis_units'): delattr(axis_meta, 'axis_units') if hasattr(axis_meta, 'axis_long_name'): delattr(axis_meta, 'axis_long_name') else: axis_meta.axis = new_axis_list if hasattr(axis_meta, 'axis_units'): axis_meta.axis_units = new_axis_units_list if hasattr(axis_meta, 'axis_long_name'): axis_meta.axis_long_name = new_axis_long_name_list return out_field def __slice_fixed_array(self, axes_State, **kwds): """Return slice of Field object with array specified fixed axis. This private method implements the functions of method slice_- fixed for the case where fixed axis are not all scalars, but are all arrays. Masked or unmasked arrays both work. It will also work when self is of size 1 (in which case it doesn't ma- tter what the slicing keywords are). In all other cases, i.e. if the keywords are all scalars or mixed arrays and scalars, the method will not necessarily work. See the doctring for slice_fixed for details, as the parameter list for this private method is the same as in the public slice_fixed method. Note: The current version of this method always returns a slice that is a copy (not sharing memory) of the data in self. """ #- If self is of size 1, any slice will be itself. Return # self if so and exit from this method: if self.is_size1(): return self #- Set alias A to MA if self.isMA() and N otherwise: if self.isMA(): A = MA else: A = N #- Calculate indices for the slice. Get the data corresponding # to the slice: dataflat_1d = A.ravel(self.copydata()) dataflat_1d_idx = N.arange(MA.size(dataflat_1d)) sliced_1d_indices = self.__slice_fixed_1d_indices(axes_State, **kwds) sliced_1d_indicesflat = A.ravel(sliced_1d_indices.copydata()) sliced_dataflat = A.zeros( int(size(sliced_1d_indices).refdata()) \ , self.typecode() ) for i in xrange( A.size(sliced_dataflat) ): sliced_dataflat[i] = \ A.compress( N.where( \ dataflat_1d_idx == sliced_1d_indicesflat[i] \ , 1, 0 ) \ , dataflat_1d )[0] #- Form the Field object to/and return: out_field = sliced_1d_indices.copymeta() out_field.setdata( A.reshape( sliced_dataflat \ , shape(sliced_1d_indices) ) ) return out_field def __slice_fixed_scalar(self, axes_State, **kwds): """Return slice of Field object with fixed scalar axis. This private method implements the functions of method slice_- fixed for the case where fixed axis are all scalars, or self is of size 1 (in which case it doesn't matter what the slicing keywords are). Note though this method doesn't check to make sure all the keywords are scalar. See the doctring for slice_fixed for details, as the parameter list for this private method is the same as in the public slice_fixed method. Note: The current version of this method returns a slice that is a reference to the data in self (if the data in self is an unmasked array) or a copy (if the data in self is a masked array). """ #- If self is of size 1, any slice will be itself. Return # self if so and exit from this method: if self.is_size1(): return self #- Otherwise slice. Here we create the slice tuple and figure # out what the new axis list looks like: slice_list = [] new_axis_list = [] for anaxis in self.extra_meta.axis: if anaxis in kwds.keys(): idx = gemath_where_close( axes_State[anaxis] \ , kwds[anaxis] ).tolist().index(1) else: idx = slice(None) new_axis_list.append(anaxis) slice_list.append(idx) slice_tuple = tuple(slice_list) #- Create sliced axis_units and axis_long_name attributes, if # appropriate: new_axis_units_list = [] new_axis_long_name_list = [] for idx in xrange(len(self.extra_meta.axis)): if self.extra_meta.axis[idx] not in kwds.keys(): if hasattr(self.extra_meta, 'axis_units'): new_axis_units_list.append( \ self.extra_meta.axis_units[idx] ) if hasattr(self.extra_meta, 'axis_long_name'): new_axis_long_name_list.append( \ self.extra_meta.axis_long_name[idx] ) #- Form the Field object to return. Note that for scalar # Field objects, axis* attributes in extra_meta should not # exist. Also, to make the code more readable, I make an # alias to sliced_array.extra_meta (axis_meta) for this # section: sliced_array = self.copymeta() sliced_array.setdata( self.refdata()[slice_tuple] ) axis_meta = sliced_array.extra_meta if shape(sliced_array) == (): if hasattr(axis_meta, 'axis'): delattr(axis_meta, 'axis') if hasattr(axis_meta, 'axis_units'): delattr(axis_meta, 'axis_units') if hasattr(axis_meta, 'axis_long_name'): delattr(axis_meta, 'axis_long_name') else: axis_meta.axis = new_axis_list if hasattr(axis_meta, 'axis_units'): axis_meta.axis_units = new_axis_units_list if hasattr(axis_meta, 'axis_long_name'): axis_meta.axis_long_name = new_axis_long_name_list return sliced_array def __slice_fixed_setdata_array(self, axes_State, setvalue, **kwds): """Set data in a fixed non-scalar axis slice of Field object. This private method implements the functions of method slice_- fixed_setdata for the case where fixed axis are all non-scalars. Note though this method doesn't check to make sure all the keywords are non-scalars. See the doctring for slice_fixed_setdata for details, as the parameter list for this private method is the same as in the public slice_fixed_setdata method; the exception is that set- value in this private method does not accept Field objects. setvalue and the values of kwds must all be the same shape. This method does not test for that condition, but will throw an exception if the size of setvalue is different from the size of the sliced array. Note: The current version of this method always sets the slice by value, due to how Numeric/numarray handles setting slices. """ #- Set data if self is size 1: if self.is_size1(): tmp_setvalue = Field(setvalue) if not tmp_setvalue.is_size1(): raise ValueError, "setvalue not of size 1" self.setdata( tmp_setvalue.refdata() ) return #- Set alias A to MA if self.isMA() and N otherwise: if self.isMA(): A = MA else: A = N #- Create a copy of the data in a flat version. Calculate indices # for full data flat version as well as the setvalue locations. # Set data to data container in self: dataflat_1d = A.ravel(self.refdata()) sliced_1d_indices = self.__slice_fixed_1d_indices(axes_State, **kwds) sliced_1d_indicesflat = A.ravel(sliced_1d_indices) setvalueflat = A.ravel(setvalue) if MA.size(setvalueflat) != MA.size(sliced_1d_indicesflat): raise ValueError, "setvalue is not the same size as slice" for i in xrange( A.size(sliced_1d_indicesflat) ): dataflat_1d[sliced_1d_indicesflat[i]] = setvalueflat[i] self.setdata( A.reshape(dataflat_1d, shape(self)) ) def __slice_fixed_setdata_scalar(self, axes_State, setvalue, **kwds): """Set data in a fixed scalar axis slice of Field object. This private method implements the functions of method slice_- fixed_setdata for the case where fixed axis are all scalars. Note though this method doesn't check to make sure all the keywords are scalar. See the doctring for slice_fixed_setdata for details, as the parameter list for this private method is the same as in the public slice_fixed_setdata method; the exception is that set- value in this private method does not accept Field objects. Note: The current version of this method always sets the slice by value, due to how Numeric/numarray handles setting slices. """ #- Calculate slice and set data to the slice. The code in this # section is written convolutedly in the self.is_size1()=True # case because self.refdata()[:] = setvalue[:] gives a segmen- # tation fault for some unknown reason: if self.is_size1(): tmp_setvalue = Field(setvalue) if not tmp_setvalue.is_size1(): raise ValueError, "setvalue not of size 1" self.setdata( tmp_setvalue.refdata() ) return else: slice_list = [] new_shape_list = [] for anaxis in self.extra_meta.axis: if anaxis in kwds.keys(): idx = gemath_where_close( axes_State[anaxis] \ , kwds[anaxis] ).tolist().index(1) else: idx = slice(None) new_shape_list.append( len(axes_State[anaxis].refdata()) ) slice_list.append(idx) if MA.size(setvalue) != 1: if MA.shape(setvalue) != tuple(new_shape_list): raise ValueError, "setvalue not conformable" self.refdata()[tuple(slice_list)] = setvalue #---------------------- Field Class: wrap Method ---------------------- def wrap(self): """Connect the MPI parallel subdomains of Field variable. Currently this is just a placeholder function and doesn't do anything. """ pass #------------------ Field Class: More Public Methods ------------------ def asMA(self): """Returns deepcopy of Field object with data as ma/MA array. """ tmp = self.copy() tmp.setdata( MA.masked_array(tmp.refdata()) ) return tmp def astype(self, typecode): """Returns deepcopy of Field object with data cast to typecode.""" tmp = self.copy() tmp.setdata( tmp.refdata().astype(typecode) ) return tmp def filled(self, fill_value=None): """Returns deepcopy of Field object with data as Numeric/numarray array. Masked values are filled in by fill_value. If fill_value is None, self.fill_value() is used. """ tmp = self.copy() tmp.setdata( MA.filled(tmp.refdata(), value=fill_value) ) return tmp def fill_value(self): """Returns fill value of Field object data if data is MA. If the data fill value is None or if the data is not an MA/ma array, None is returned. """ if self.isMA(): return self.refdata().fill_value() else: return None def isMA(self): """Returns True if data of Field object is MA.""" return MA.isMA(self.refdata()) def is_scalar(self): """Returns True if data of Field object has shape == ().""" if MA.shape(self.refdata()) == (): return True else: return False def is_size1(self): """Returns True if data of Field object has size == 1. This is True for scalars (empty shape tuple) and one element arrays (of any shape). """ if MA.size(self.refdata()) == 1: return True else: return False def refdata(self): """Returns reference to data of Field object. This returns a reference to the private attribute _data, which is a Numeric/numarray or MA/ma array. """ return self._data def setdata(self, data): """Set private _data attribute to equal Field object data. Positional input argument: * data: If data is a Numeric/numarray or MA/ma array, the method makes a reference of data to the private attribute _data. If data is a list, tuple, or scalar (defined as having an empty shape tuple), the private attribute _data is set to N.array(data). Any other input throws an excep- tion. All occurrences in this module that set _data to reference the specified data use this method. Other types of set co- mmands (e.g. in-place operations) may not use this method, instead operating on _data directly. Note when the setdata method is used in setting the Field in- stance to data the data is set by reference. """ if isinstance( data, type(N.array(0)) ) or \ isinstance( data, type(MA.masked_array(0)) ): self._data = data elif isinstance( data, type([]) ) or \ isinstance( data, type(()) ) or \ ( N.shape(data) == () ): self._data = N.array(data) else: raise ValueError, "Bad data to set to _data" def typecode(self): """Returns typecode of the data in the Field object.""" return self.refdata().typecode() #--------------------- Additional Module Variables ---------------------- # # These module variables require the classes to be defined first. Mo- # dule variables that do not require classes to be defined first are # found near the top of this module. _typeField = type(Field(0)) #- Type of Field class objects #-------- Module Functions To Operate On/With Field Objects (1) -------- # # These functions are defined by the *aryFunction and *aryOperation # classes. Order of this list of functions is alphabetical, grouped # by class. # # Note: The following functions are purposesly left undefined because # the their behavior between Numeric/numarray and MA/ma is too differ- # ent: choose. arange = NullaryOperation('arange') ones = NullaryOperation('ones') zeros = NullaryOperation('zeros') nonzero = UnaryFunction('nonzero') shape = UnaryFunction('shape') absolute = UnaryOperation('absolute') alltrue = UnaryOperation('alltrue') array = UnaryOperation('array') arccos = UnaryOperation('arccos') arcsin = UnaryOperation('arcsin') arctan = UnaryOperation('arctan') ceil = UnaryOperation('ceil') conjugate = UnaryOperation('conjugate') cos = UnaryOperation('cos') exp = UnaryOperation('exp') fabs = UnaryOperation('fabs') floor = UnaryOperation('floor') log = UnaryOperation('log') log10 = UnaryOperation('log10') logical_not = UnaryOperation('logical_not') negative = UnaryOperation('negative') rank = UnaryOperation('rank') resize = UnaryOperation('resize') sin = UnaryOperation('sin') size = UnaryOperation('size') sometrue = UnaryOperation('sometrue') sort = UnaryOperation('sort') sqrt = UnaryOperation('sqrt') tan = UnaryOperation('tan') tanh = UnaryOperation('tanh') transpose = UnaryOperation('transpose') allclose = BinaryFunction('allclose') add = BinaryOperation('add') arctan2 = BinaryOperation('arctan2') divide = BinaryOperation('divide') dot = BinaryOperation('dot') equal = BinaryOperation('equal') floor_divide = BinaryOperation('floor_divide') fmod = BinaryOperation('fmod') greater = BinaryOperation('greater') greater_equal = BinaryOperation('greater_equal') innerproduct = BinaryOperation('innerproduct') less = BinaryOperation('less') less_equal = BinaryOperation('less_equal') logical_and = BinaryOperation('logical_and') logical_or = BinaryOperation('logical_or') logical_xor = BinaryOperation('logical_xor') maximum = BinaryOperation('maximum') minimum = BinaryOperation('minimum') multiply = BinaryOperation('multiply') not_equal = BinaryOperation('not_equal') outerproduct = BinaryOperation('outerproduct') power = BinaryOperation('power') remainder = BinaryOperation('remainder') subtract = BinaryOperation('subtract') true_divide = BinaryOperation('true_divide') #-------- Module Functions To Operate On/With Field Objects (2) -------- # # These additional functions return Field objects and are defined # specially, since they don't fit the *aryOperation and *aryFunction # classes. For all practical intents and purposes they duplicate # functionality found in the Numeric/numarray and MA/ma packages. def average(*args, **kwds): raise NotImplementedError, "Method not yet implemented" def clip(*args, **kwds): raise NotImplementedError, "Method not yet implemented" def compress(*args, **kwds): raise NotImplementedError, "Method not yet implemented" def concatenate(*args, **kwds): raise NotImplementedError, "Method not yet implemented" def put(*args, **kwds): raise NotImplementedError, "Method not yet implemented" def ravel(a): """Ravel data in Field object a to flat form. Returns a Field object whose metadata (except for the metadata in extra_meta) is a deep copy of the metadata in a, but whose data is a raveled reference of the data in Field object a. The extra_meta attribute of the returned Field object is set to an empty ExtraMeta object, since the ExtraMeta object often holds information about the axis of the Field object. Input Argument: * a: Field object. Unchanged by function. Example: >>> data = N.array([[-0.11, 2.1, 5.3], [3.9, 4.4, -2.0]]) >>> x = Field( data, id='u', long_name='Zonal wind' \ , axis=['lat', 'lon'] ) >>> print x.extra_meta.__dict__ {'axis': ['lat', 'lon']} >>> print shape(x) (2, 3) >>> y = ravel(x) >>> print y array([-0.11, 2.1 , 5.3 , 3.9 , 4.4 , -2. ]) >>> print y.extra_meta.__dict__ {} >>> print shape(y) (6,) >>> print shape(x) (2, 3) """ tmp = a.copymeta() if a.isMA(): tmp.setdata( MA.ravel(a.refdata()) ) else: try: tmp.setdata( N.ravel(a.refdata()) ) except: tmp.setdata( MA.ravel(a.refdata()) ) tmp.clear_extra_meta() return tmp def reshape(a, shapetuple): """Reshape data in Field object a to shapetuple. Returns a Field object whose metadata (except for the metadata in extra_meta) is a deep copy of the metadata in a, but whose data is a reference to the data in Field object a, reshaped to shapetuple. The extra_meta attribute of the returned Field ob- ject is set to an empty ExtraMeta object, since the ExtraMeta object often holds information about the axis of the Field ob- ject. Input Arguments: * a: Field object. Unchanged by function. * shapetuple: Tuple describing the new shape of the data. Example: >>> data = N.array([-0.11, 2.1, 5.3, 3.9, 4.4, -2.0]) >>> x = Field(data, id='u', long_name='Zonal wind', axis=['Latitude']) >>> print x.extra_meta.__dict__ {'axis': ['Latitude']} >>> print shape(x) (6,) >>> y = reshape(x, (3,2)) >>> print x array([-0.11, 2.1 , 5.3 , 3.9 , 4.4 , -2. ]) >>> print y array([[-0.11, 2.1 ], [ 5.3 , 3.9 ], [ 4.4 , -2. ]]) >>> print shape(y) (3, 2) >>> print x.extra_meta.__dict__ {'axis': ['Latitude']} >>> print y.extra_meta.__dict__ {} """ tmp = a.copymeta() if a.isMA(): tmp.setdata( MA.reshape(a.refdata(), shapetuple) ) else: try: tmp.setdata( N.reshape(a.refdata(), shapetuple) ) except: tmp.setdata( MA.reshape(a.refdata(), shapetuple) ) tmp.clear_extra_meta() return tmp def take(*args, **kwds): raise NotImplementedError, "Method not yet implemented" def where(condition, x, y): """Returns Field object with x where condition is true, else y. Positional input parameters: * condition: A condition expressed as a Numeric/numarray, MA/ma masked array, or Field object with array data of True (1) or False (0) elements. * x: Value for elements where condition is True. * y: Value for elements where condition is not True. Result returned is of the same shape as condition. x and y are either the same shape as condition or scalars, and can be Numeric/ numarray arrays, MA/ma arrays, or Field objects. Result returned is Field object with data as Numeric/numarray or MA/ma masked ar- ray; which one is dependent on whether condition is the result of operating on data that is of type Numeric/numarray or MA/ma. The result is masked if either the condition is masked, or x or y is masked. Result shares no memory with inputs and all metadata attributes are empty. Examples: >>> a = Field(N.array([1., 2., 3.])) >>> b = where(a > 1.5, 1, 0) >>> ['%.6g' % b[i] for i in range(len(b))] ['0', '1', '1'] >>> print b.isMA() False >>> a = Field(MA.masked_array([1., 2., 3.])) >>> y = Field([-999., -999., -999.]) >>> b = where(a > 1.5, 1, y) >>> ['%.6g' % b[i] for i in range(len(b))] ['-999', '1', '1'] >>> print b.isMA() True """ if isinstance(condition, _typeField): cond_isMA = condition.isMA() cond_data = condition.refdata() else: cond_isMA = MA.isMA(condition) cond_data = condition if isinstance(x, _typeField): x_isMA = x.isMA() xdata = x.refdata() else: x_isMA = MA.isMA(x) xdata = x if isinstance(y, _typeField): y_isMA = y.isMA() ydata = y.refdata() else: y_isMA = MA.isMA(y) ydata = y tmp = Field(0) if cond_isMA or x_isMA or y_isMA: tmp.setdata( MA.where(cond_data, xdata, ydata) ) else: tmp.setdata( N.where(cond_data, xdata, ydata) ) return tmp def where_close(x, y, rtol=1.e-5, atol=1.e-8): """Mask of where x and y are element-wise "equal" to each other. Returns an integer Field object with elements equal to 1 where x and y are "equal", and 0 otherwise. If x or y are floating point, "equal" means where abs(x-y) <= atol + rtol * abs(y). This is essentially the same algorithm used in the Numeric/numarray func- tion allclose. If x and y are integer, "equal" means strict equal- ity. Shape and size of output is the same as x and y; if one is an array and the other is scalar, shape and size of the output is the same as the array. Data of the output Field object is an MA/ma masked array, unless the data for both objects are not MA/ma objects, in which case the output Field object's data is a Numeric/numarray array. If inputs are both unmasked scalars the output is a scalar Field object. Result shares no memory with inputs and all metadata attributes are empty. Positional Input Arguments: * x: Scalar, Numeric/numarray array, MA/ma array, Python list/ tuple of any size and shape, or Field object. Floating or inte- ger type. * y: Scalar, Numeric/numarray array, MA/ma array, Python list/ tuple of any size and shape, or Field object. Floating or inte- ger type. Keyword Input Arguments: * rtol: "Relative" tolerance. Default is 1.e-5. Used in the comparison between x and y only if the two are floating point. * atol: "Absolute" tolerance. Default is 1.e-8. Used in the comparison between x and y only if the two are floating point. If either of the inputs are MA/ma masked objects, this function uses the MA/ma default algorithm for comparison, i.e. masked val- ues are always considered equal. Examples: >>> x = Field([20., -32., -1., 2. , 5., 29.]) >>> y = Field([20.1, -31., -1., 2.01, 3., 28.99]) >>> ind = where_close(x, y) >>> ['%.1g' % ind[i] for i in range(len(ind))] ['0', '0', '1', '0', '0', '0'] >>> x = Field([20., -32., -1., 2. , 5., 29.]) >>> y = Field([20.1, -31., -1., 2.000000000001, 3., 28.99]) >>> ind = where_close(x, y) >>> ['%.1g' % ind[i] for i in range(len(ind))] ['0', '0', '1', '1', '0', '0'] >>> x = N.array([1, 5, 7, -2, 10]) >>> y = N.array([1, -5, 17, -2, 0]) >>> ind = where_close(x, y) >>> ['%.1g' % ind[i] for i in range(len(ind))] ['1', '0', '0', '1', '0'] >>> x = -2 >>> y = Field([1, 5, 7, -2, 10]) >>> ind = where_close(x, y) >>> ['%.1g' % ind[i] for i in range(len(ind))] ['0', '0', '0', '1', '0'] """ if isinstance(x, _typeField): xdata = x.refdata() else: xdata = x if isinstance(y, _typeField): ydata = y.refdata() else: ydata = y tmp = Field(0) tmp.setdata( gemath_where_close(xdata, ydata, rtol=rtol, atol=atol) ) return tmp #-------- Module Functions To Operate On/With Field Objects (3) -------- # # This section includes specially defined public methods that may not # return a Field object and do not fit the first 2 categories of module # functions above. def copymeta_largest(*args): """Returns deepcopy of metadata from the "largest" object in args. The function finds the "largest" Field object in args, creates a Field object that is a copy of all the metadata and only the meta- data (i.e. the entire object except the data), and returns that Field object. "Largest" in this case is defined as the Field ob- ject whose data array has: 1) The largest number of total elements. 2) The largest number of dimensions (rank). 3) The largest number of elements in each dimension, starting with the last dimension, and comparing backwards in dimension until the first dimension. 4) If there are more than one objects that tie after going through criteria 1-3, the metadata returned is from the earliest object (i.e. earliest in the order in the args list) that meet those criteria. The order of criteria given above is the order in which evaluation of "largest" is made. Evaluation of criteria continues only as long as needed to converge to a single candidate. Thus, a (3,2) array will be "larger" than a (6,) array, a (7,) array larger than the (3,2) array, and a (2,3) array is larger than a (3,2) array. Input Positional Parameter(s): * args: Field objects. All Field objects must have unique ids (this condition is not checked for, however, by this function). Example: >>> f1 = Field(N.array([[2., 4., 1.],[4., 2., -5.]]), id='u') >>> f2 = Field(N.array([2., 4., 1., 4., 2., -5.]), id='v') >>> f3 = Field(N.array(3.), id='w') >>> tmp = copymeta_largest(f1, f2, f3) >>> print tmp.__class__.__name__ Field >>> print tmp.id u >>> print tmp.units None >>> print tmp.refdata() [] """ #- Create dictionaries of all the args and all the sizes: dict_of_args = {} dict_of_sizes = {} for anarg in args: dict_of_args[anarg.id] = anarg dict_of_sizes[anarg.id] = MA.size(anarg.refdata()) #- Select the keys of those in args with the largest sizes: max_size = max(dict_of_sizes.values()) list_max_size = [] for akey in dict_of_sizes.keys(): if dict_of_sizes[akey] == max_size: list_max_size.append(akey) #- Create dictionary of ranks, for those of the largest sizes. # Select the keys of those in args with the largest sizes that # have the largest ranks: dict_of_ranks = {} for akey in list_max_size: dict_of_ranks[akey] = MA.rank(dict_of_args[akey].refdata()) max_rank = max(dict_of_ranks.values()) list_max_rank = [] for akey in list_max_size: if dict_of_ranks[akey] == max_rank: list_max_rank.append(akey) #- Create dictionary of the shapes for those in args with the # largest sizes and that have the largest ranks. Select the # keys of those in args with the largest sizes and that have # the largest ranks that have the largest elements, comparing # from the last dimension back to the first: dict_of_shapes = {} for akey in list_max_rank: dict_of_shapes[akey] = MA.shape(dict_of_args[akey].refdata()) list_dims = range(max_rank) list_dims.reverse() list_remaining_candidates = list_max_rank for i in list_dims: dict_nelem_i = {} for akey in list_remaining_candidates: dict_nelem_i[akey] = dict_of_shapes[akey][i] max_nelem_i = max(dict_nelem_i.values()) list_max_nelem_i = [] for akey in list_remaining_candidates: if dict_nelem_i[akey] == max_nelem_i: list_max_nelem_i.append(akey) list_remaining_candidates = list_max_nelem_i #- Return metadata of the first Field object in args that fits the # "largest" criteria. If the return doesn't happen, throw an # exception: for anarg in args: if anarg.id in list_remaining_candidates: return anarg.copymeta() raise ValueError, "Something very wrong happened" #-------- Module Functions To Operate On/With Field Objects (4) -------- # # This section contains support private methods: # # * _compare2bool: Convert the results of a comparison (which produces # Field objects) into a boolean value. It produces the correct # exceptions if the conversion is unable to be processed. def _compare2bool(arg): """Convert result of field comparison operation to Boolean. Argument arg is the output of a field module comparison oper- ation. Specifically, it's the output of: equal, greater_equal, greater, less_equal, less, or not_equal, which all return Field objects. The function returns a Boolean evaluation of arg (i.e. True or False) if the data in arg is a scalar or a non-scalar of size 1. """ if arg.is_scalar(): return bool( float(arg.refdata()) ) elif arg.is_size1() and not arg.is_scalar(): return bool( arg.refdata()[0] ) else: return arg.refdata() #-------------------------- Main: Test Module ------------------------- # # In general, doctest is used for more foundational code testing. More # complicated testing (especially of operations and functions on Field # variables) use unittest and are found in the modelutil/test directory. # There are some exceptions: doctest is used here to test reshape, for # instance, as well as in-place operations. __test__ = { 'Additional Test 1: Simple array': """ #- Test Field initialization and access data: * Empty list: >>> a = Field([], id='Ts', long_name='Surface temperature', units='K') >>> print a array([]) >>> print a.typecode() l >>> print shape(a) (0,) >>> print a.id Ts >>> print a.long_name Surface temperature >>> print a.units K >>> print a.extra_meta.__dict__ {} * Scalar data (and check data is passed by value not reference): >>> data = 4 >>> a = Field(data, id='Ts', long_name='Surface temperature', units='K') >>> print data 4 >>> print a array(4) >>> print a.typecode() l >>> print shape(a) () >>> data = -3 >>> print data -3 >>> print a array(4) * Simple list (the data should be passed by value): >>> data = [1., 2., 5., -3., -4.4] >>> a = Field(data, id='Ts', long_name='Surface temperature', units='K') >>> ['%.6g' % data[i] for i in range(len(data))] ['1', '2', '5', '-3', '-4.4'] >>> ['%.6g' % a[i] for i in range(len(a))] ['1', '2', '5', '-3', '-4.4'] >>> print a.typecode() d >>> print shape(a) (5,) >>> print a.id Ts >>> print a.long_name Surface temperature >>> print a.units K >>> print a.extra_meta.__dict__ {} >>> data[1] = -999. >>> ['%.6g' % data[i] for i in range(len(data))] ['1', '-999', '5', '-3', '-4.4'] >>> ['%.6g' % a[i] for i in range(len(a))] ['1', '2', '5', '-3', '-4.4'] * Simple tuple (the data should be passed by value): >>> data = (1., 2., 5., -3., -4.4) >>> a = Field(data, id='Ts', long_name='Surface temperature', units='K') >>> ['%.6g' % data[i] for i in range(len(data))] ['1', '2', '5', '-3', '-4.4'] >>> ['%.6g' % a[i] for i in range(len(a))] ['1', '2', '5', '-3', '-4.4'] >>> data = (5., -3., -4.4) >>> ['%.6g' % data[i] for i in range(len(data))] ['5', '-3', '-4.4'] >>> ['%.6g' % a[i] for i in range(len(a))] ['1', '2', '5', '-3', '-4.4'] * Numeric/numarray array (the data should be passed by reference): >>> data = N.array([1., 2., 5., -3., -4.4]) >>> a = Field(data, id='Ts', long_name='Surface temperature', units='K') >>> ['%.6g' % data[i] for i in range(len(data))] ['1', '2', '5', '-3', '-4.4'] >>> ['%.6g' % a[i] for i in range(len(a))] ['1', '2', '5', '-3', '-4.4'] >>> data[1] = -999. >>> ['%.6g' % data[i] for i in range(len(data))] ['1', '-999', '5', '-3', '-4.4'] >>> ['%.6g' % a[i] for i in range(len(a))] ['1', '-999', '5', '-3', '-4.4'] >>> a[1] = -1999. >>> ['%.6g' % data[i] for i in range(len(data))] ['1', '-1999', '5', '-3', '-4.4'] >>> ['%.6g' % a[i] for i in range(len(a))] ['1', '-1999', '5', '-3', '-4.4'] * MA/ma array (the data should be passed by reference): >>> data = MA.masked_array([1., 2., 5., -3., -4.4]) >>> MA.isMA(data) True >>> a = Field(data, id='Ts', long_name='Surface temperature', units='K') >>> a.isMA() True >>> ['%.6g' % data[i] for i in range(len(data))] ['1', '2', '5', '-3', '-4.4'] >>> ['%.6g' % a[i] for i in range(len(a))] ['1', '2', '5', '-3', '-4.4'] >>> data[1] = -999. >>> ['%.6g' % data[i] for i in range(len(data))] ['1', '-999', '5', '-3', '-4.4'] >>> ['%.6g' % a[i] for i in range(len(a))] ['1', '-999', '5', '-3', '-4.4'] >>> a[3] = -1999. >>> ['%.6g' % data[i] for i in range(len(data))] ['1', '-999', '5', '-1999', '-4.4'] >>> ['%.6g' % a[i] for i in range(len(a))] ['1', '-999', '5', '-1999', '-4.4'] * Field object (the data should be passed by reference): >>> data = Field([1., 2., 5., -3., -4.4], id='FL') >>> print data.id FL >>> print data.long_name None >>> a = Field(data, id='Ts', long_name='Surface temperature', units='K') >>> print a.id Ts >>> print a.long_name Surface temperature >>> ['%.6g' % data[i] for i in range(len(data))] ['1', '2', '5', '-3', '-4.4'] >>> ['%.6g' % a[i] for i in range(len(a))] ['1', '2', '5', '-3', '-4.4'] >>> data[1] = -999. >>> ['%.6g' % data[i] for i in range(len(data))] ['1', '-999', '5', '-3', '-4.4'] >>> ['%.6g' % a[i] for i in range(len(a))] ['1', '-999', '5', '-3', '-4.4'] >>> a[3] = -1999. >>> ['%.6g' % data[i] for i in range(len(data))] ['1', '-999', '5', '-1999', '-4.4'] >>> ['%.6g' % a[i] for i in range(len(a))] ['1', '-999', '5', '-1999', '-4.4'] * Check the extra_meta keyword in the instantiation line: >>> data = [1., 2., 5., -3., -4.4] >>> em = ExtraMeta(axis=['Latitude'], axis_units=['degrees_north']) >>> a = Field( data, id='Ts', long_name='Surface temperature', units='K' \ , extra_meta=em ) >>> ['%.6g' % a[i] for i in range(len(a))] ['1', '2', '5', '-3', '-4.4'] >>> print a.id Ts >>> print a.long_name Surface temperature >>> print em.__dict__ {'axis_units': ['degrees_north'], 'axis': ['Latitude']} >>> print a.extra_meta.__dict__ {'axis_units': ['degrees_north'], 'axis': ['Latitude']} >>> data = [1., 2., 5., -3., -4.4] >>> em = ExtraMeta(axis=['Latitude'], axis_units=['degrees_north']) >>> a = Field( data, id='Ts', long_name='Surface temperature', units='K' \ , extra_meta=em, source='model' ) >>> ['%.6g' % a[i] for i in range(len(a))] ['1', '2', '5', '-3', '-4.4'] >>> print a.id Ts >>> print a.long_name Surface temperature >>> print em.__dict__ {'axis_units': ['degrees_north'], 'axis': ['Latitude']} >>> print a.extra_meta.__dict__ {'source': 'model', 'axis_units': ['degrees_north'], 'axis': ['Latitude']} #- Test Field __setattr__ function: >>> a.units = 'C' >>> print a.units C >>> a.axis = ['Pressure levels'] Traceback (most recent call last): ... AttributeError: Bad attribute #- Test extra_meta attribute: >>> a.extra_meta.axis = ['Pressure levels'] >>> print a.extra_meta.axis[0] Pressure levels >>> print a.extra_meta.id Traceback (most recent call last): ... AttributeError: 'ExtraMeta' object has no attribute 'id' #- Test in-place numeric binary functions and methods: * Between two Field objects: >>> data = N.array([1., 2., 5., -3., -4.4, 2.2]) >>> data1 = N.array([1., 2., 5., -3., -4.4, 2.2]) * 0.32 >>> a = Field(data, id='u', long_name='Zonal wind', units='m/s') >>> b = Field(data1, id='v', long_name='Zonal wind', units='m/s') >>> a = a + b >>> ['%.6g' % a[i] for i in range(len(a))] ['1.32', '2.64', '6.6', '-3.96', '-5.808', '2.904'] >>> a = Field(data, id='u', long_name='Zonal wind', units='m/s') >>> a += b >>> ['%.6g' % a[i] for i in range(len(a))] ['1.32', '2.64', '6.6', '-3.96', '-5.808', '2.904'] * Between a Field object and a Numeric/numarray array: >>> data = N.array([1., 2., 5., -3., -4.4, 2.2]) >>> data1 = N.array([1., 2., 5., -3., -4.4, 2.2]) * 0.32 >>> a = Field(data, id='u', long_name='Zonal wind', units='m/s') >>> a = a + data1 >>> ['%.6g' % a[i] for i in range(len(a))] ['1.32', '2.64', '6.6', '-3.96', '-5.808', '2.904'] >>> a = Field(data, id='u', long_name='Zonal wind', units='m/s') >>> a = data1 + a >>> ['%.6g' % a[i] for i in range(len(a))] ['1.32', '2.64', '6.6', '-3.96', '-5.808', '2.904'] >>> a = Field(data, id='u', long_name='Zonal wind', units='m/s') >>> a += data1 >>> ['%.6g' % a[i] for i in range(len(a))] ['1.32', '2.64', '6.6', '-3.96', '-5.808', '2.904'] * Between a Field object and an MA/ma array: >>> data = N.array([1., 2., 5., -3., -4.4, 2.2]) >>> data1 = MA.masked_array([1., 2., 5., -3., -4.4, 2.2]) * 0.32 >>> print MA.isMA(data1) True >>> a = Field(data, id='u', long_name='Zonal wind', units='m/s') >>> a = a + data1 >>> ['%.6g' % a[i] for i in range(len(a))] ['1.32', '2.64', '6.6', '-3.96', '-5.808', '2.904'] >>> a = Field(data, id='u', long_name='Zonal wind', units='m/s') >>> a = data1 + a >>> ['%.6g' % a[i] for i in range(len(a))] ['1.32', '2.64', '6.6', '-3.96', '-5.808', '2.904'] >>> a = Field(data, id='u', long_name='Zonal wind', units='m/s') >>> a += data1 >>> ['%.6g' % a[i] for i in range(len(a))] ['1.32', '2.64', '6.6', '-3.96', '-5.808', '2.904'] * Between a Field object and a scalar: >>> data = N.array([1., 2., 5., -3., -4.4, 2.2]) >>> data1 = 0.32 >>> a = Field(data, id='u', long_name='Zonal wind', units='m/s') >>> a = a + data1 >>> ['%.6g' % a[i] for i in range(len(a))] ['1.32', '2.32', '5.32', '-2.68', '-4.08', '2.52'] >>> a = Field(data, id='u', long_name='Zonal wind', units='m/s') >>> a = data1 + a >>> ['%.6g' % a[i] for i in range(len(a))] ['1.32', '2.32', '5.32', '-2.68', '-4.08', '2.52'] >>> a = Field(data, id='u', long_name='Zonal wind', units='m/s') >>> a += data1 >>> ['%.6g' % a[i] for i in range(len(a))] ['1.32', '2.32', '5.32', '-2.68', '-4.08', '2.52'] #- Test copying methods: >>> data = [1., 2., 5., -3., -4.4] >>> em = ExtraMeta(axis=['Latitude'], axis_units=['degrees_north']) >>> a = Field( data, id='Ts', long_name='Surface temperature', units='K' \ , extra_meta=em ) >>> ['%.6g' % a[i] for i in range(len(a))] ['1', '2', '5', '-3', '-4.4'] >>> print a.id Ts >>> print a.units K >>> print a.extra_meta.__dict__ {'axis_units': ['degrees_north'], 'axis': ['Latitude']} * copy method: >>> b = a.copy() >>> ['%.6g' % b[i] for i in range(len(b))] ['1', '2', '5', '-3', '-4.4'] >>> a.id = 'Tsnew' >>> print b.id Ts >>> print b.units K >>> print b.extra_meta.__dict__ {'axis_units': ['degrees_north'], 'axis': ['Latitude']} >>> a.id = 'Ts' * copydata method: >>> b = a.copydata() >>> ['%.6g' % b[i] for i in range(len(b))] ['1', '2', '5', '-3', '-4.4'] >>> print b.id Traceback (most recent call last): ... AttributeError: 'NumArray' object has no attribute 'id' * copymeta method: >>> c = a.copymeta() >>> a.id = 'Tsnew' >>> print c array([]) >>> print c.id Ts >>> print c.units K >>> print c.extra_meta.__dict__ {'axis_units': ['degrees_north'], 'axis': ['Latitude']} >>> a.id = 'Ts' #- Test meta management method replace_all_meta: >>> data = [1., 2., 5., -3., -4.4] >>> a = Field( data, id='Ts', long_name='Surface temperature', units='K' ) >>> print a.id Ts >>> print a.long_name Surface temperature >>> print a.extra_meta.__dict__ {} >>> a.replace_all_meta( id='F_sens', long_name='Sensible heat', units='W/m**2' \ , axis=['Latitude'] ) >>> print a.id F_sens >>> print a.long_name Sensible heat >>> print a.extra_meta.__dict__ {'axis': ['Latitude']} >>> print a array([ 1. , 2. , 5. , -3. , -4.4]) >>> b = Field([4.3, -6.45], id='FL') >>> a.replace_all_meta(b) >>> print a.id FL >>> print a.long_name None >>> print a array([ 1. , 2. , 5. , -3. , -4.4]) >>> a.replace_all_meta(b, id='T0') >>> print a.id FL >>> print a.long_name None >>> a.replace_all_meta(b, units='J') >>> print a.units None >>> c = Field([1.2, 0.3, -23.45], id='F_latent') >>> a.replace_all_meta(b, c, id='T0') Traceback (most recent call last): ... ValueError: Bad argument list #- Test other public methods: >>> data = [1., 2., 5., -3., -4.4] >>> a = Field( data, id='Ts', long_name='Surface temperature', units='K' ) >>> print a.typecode() d >>> anew = a.astype('f') >>> print anew.typecode() f >>> print a.is_scalar() False >>> print a.is_size1() False >>> b = Field(300., id='Ts', long_name='Surface temperature', units='K') >>> print b.is_scalar() True >>> print b.is_size1() True >>> c = Field([300.], id='Ts', long_name='Surface temperature', units='K') >>> print c.is_scalar() False >>> print c.is_size1() True >>> d = Field([[[300.]]], id='Ts', long_name='Surface temperature', units='K') >>> print shape(d) (1, 1, 1) >>> print d.is_scalar() False >>> print d.is_size1() True >>> data = [1., 2., 5., -3., -4.4] >>> a = Field( data, id='Ts', long_name='Surface temperature', units='K' ) >>> a.setdata(data) >>> ['%.6g' % a[i] for i in range(len(a))] ['1', '2', '5', '-3', '-4.4'] >>> data[1] = 2.99 >>> ['%.6g' % a[i] for i in range(len(a))] ['1', '2', '5', '-3', '-4.4'] >>> data[1] = 2. >>> b = N.array(data)+0.1 >>> a.setdata(b) >>> ['%.6g' % a[i] for i in range(len(a))] ['1.1', '2.1', '5.1', '-2.9', '-4.3'] >>> b[1] = 2.99 >>> ['%.6g' % a[i] for i in range(len(a))] ['1.1', '2.99', '5.1', '-2.9', '-4.3'] >>> a.setdata(MA.masked_array(data)+0.1) >>> ['%.6g' % a[i] for i in range(len(a))] ['1.1', '2.1', '5.1', '-2.9', '-4.3'] >>> print a.typecode() d """, 'Additional Test 2: Complex arrays': """ #- Test in-place numeric binary functions and methods: * Between two Field objects: >>> import field >>> data = N.reshape( N.array([1., 2., 5., -3., -4.4, 2.2]), (2,3) ) >>> print N.shape(data) (2, 3) >>> data1 = N.reshape( N.array([1., 2., 5., -3., -4.4, 2.2]) * 0.32, (2,3) ) >>> print N.shape(data1) (2, 3) >>> a = Field(data, id='u', long_name='Zonal wind', units='m/s') >>> b = Field(data1, id='v', long_name='Zonal wind', units='m/s') >>> a = a + b >>> ['%.6g' % field.ravel(a)[i] for i in range(len(field.ravel(a)))] ['1.32', '2.64', '6.6', '-3.96', '-5.808', '2.904'] >>> a = Field(data, id='u', long_name='Zonal wind', units='m/s') >>> a += b >>> ['%.6g' % field.ravel(a)[i] for i in range(len(field.ravel(a)))] ['1.32', '2.64', '6.6', '-3.96', '-5.808', '2.904'] * Between a Field object and a Numeric/numarray array: >>> data = N.reshape( N.array([1., 2., 5., -3., -4.4, 2.2]), (2,3) ) >>> print N.shape(data) (2, 3) >>> data1 = N.reshape( N.array([1., 2., 5., -3., -4.4, 2.2]) * 0.32, (2,3) ) >>> print N.shape(data1) (2, 3) >>> a = Field(data, id='u', long_name='Zonal wind', units='m/s') >>> a = a + data1 >>> print isinstance( a, type(Field(0)) ) True >>> ['%.6g' % field.ravel(a)[i] for i in range(len(field.ravel(a)))] ['1.32', '2.64', '6.6', '-3.96', '-5.808', '2.904'] >>> a = Field(data, id='u', long_name='Zonal wind', units='m/s') >>> a = data1 + a >>> print isinstance( a, type(N.array(0)) ) True >>> ['%.6g' % N.ravel(a)[i] for i in range(len(N.ravel(a)))] ['1.32', '2.64', '6.6', '-3.96', '-5.808', '2.904'] >>> a = Field(data, id='u', long_name='Zonal wind', units='m/s') >>> a += data1 >>> print isinstance( a, type(Field(0)) ) True >>> ['%.6g' % field.ravel(a)[i] for i in range(len(field.ravel(a)))] ['1.32', '2.64', '6.6', '-3.96', '-5.808', '2.904'] * Between a Field object and an MA/ma array: >>> data = N.reshape( N.array([1., 2., 5., -3., -4.4, 2.2]), (2,3) ) >>> print N.shape(data) (2, 3) >>> data1 = MA.reshape( MA.masked_array([1., 2., 5., -3., -4.4, 2.2]) \ * 0.32, (2,3) ) >>> print N.shape(data1) (2, 3) >>> print MA.isMA(data1) True >>> a = Field(data, id='u', long_name='Zonal wind', units='m/s') >>> a = a + data1 >>> print isinstance( a, type(Field(0)) ) True >>> ['%.6g' % field.ravel(a)[i] for i in range(len(field.ravel(a)))] ['1.32', '2.64', '6.6', '-3.96', '-5.808', '2.904'] >>> a = Field(data, id='u', long_name='Zonal wind', units='m/s') >>> a = data1 + a >>> print isinstance( a, type(MA.array(0)) ) True >>> ['%.6g' % MA.ravel(a)[i] for i in range(len(MA.ravel(a)))] ['1.32', '2.64', '6.6', '-3.96', '-5.808', '2.904'] >>> a = Field(data, id='u', long_name='Zonal wind', units='m/s') >>> a += data1 >>> print isinstance( a, type(Field(0)) ) True >>> ['%.6g' % field.ravel(a)[i] for i in range(len(field.ravel(a)))] ['1.32', '2.64', '6.6', '-3.96', '-5.808', '2.904'] * Between a Field object and a scalar: >>> data = N.reshape( N.array([1., 2., 5., -3., -4.4, 2.2]), (2,3) ) >>> print N.shape(data) (2, 3) >>> data1 = 0.32 >>> a = Field(data, id='u', long_name='Zonal wind', units='m/s') >>> a = a + data1 >>> ['%.6g' % field.ravel(a)[i] for i in range(len(field.ravel(a)))] ['1.32', '2.32', '5.32', '-2.68', '-4.08', '2.52'] >>> a = Field(data, id='u', long_name='Zonal wind', units='m/s') >>> a = data1 + a >>> ['%.6g' % field.ravel(a)[i] for i in range(len(field.ravel(a)))] ['1.32', '2.32', '5.32', '-2.68', '-4.08', '2.52'] >>> a = Field(data, id='u', long_name='Zonal wind', units='m/s') >>> a += data1 >>> ['%.6g' % field.ravel(a)[i] for i in range(len(field.ravel(a)))] ['1.32', '2.32', '5.32', '-2.68', '-4.08', '2.52'] """, 'Additional Test 3: Check functions/operations for results sharing memory': """ #- Check nullary operations results: * arange results should not share memory: >>> a = 4 >>> b = arange(a) >>> print b array([0, 1, 2, 3]) >>> a = 6 >>> print b array([0, 1, 2, 3]) #- Check unary functions results: * alltrue results should not share memory: >>> data = N.array([[1., 2., 5.], [-3., -4.4, 2.2]]) >>> a = Field(data, id='u', long_name='Zonal wind', units='m/s') >>> c = alltrue(a) >>> ['%.6g' % c[i] for i in range(len(c))] ['1', '1', '1'] >>> b = ones(4) >>> d = alltrue(b) >>> print float(d.refdata()) 1.0 >>> data[:,0] = 0. >>> ['%.6g' % c[i] for i in range(len(c))] ['1', '1', '1'] >>> c = alltrue(a) >>> ['%.6g' % c[i] for i in range(len(c))] ['0', '1', '1'] #- Check unary operations results: * cos results should not share memory: >>> data = N.array([N.pi, -3., -4.4, 2.2]) >>> a = Field(data, id='u', long_name='Zonal wind', units='m/s') >>> c = cos(a) >>> ['%.6g' % c[i] for i in range(len(c))] ['-1', '-0.989992', '-0.307333', '-0.588501'] >>> data[-1] = N.pi >>> ['%.6g' % c[i] for i in range(len(c))] ['-1', '-0.989992', '-0.307333', '-0.588501'] #- Test binary functions results: * allclose results should not share memory: >>> data = N.array([1., -3., -3.]) >>> a = Field(data, id='u', long_name='Zonal wind', units='m/s') >>> c = allclose(a, -3.) >>> print c 0 >>> data[0] = -3. >>> print c 0 >>> c = allclose(a, -3.) >>> print c 1 #- Test binary operations results: * add results should not share memory: >>> data = N.array([1., 2., 5., -3., -4.4, 2.2]) >>> a = Field(data, id='u', long_name='Zonal wind', units='m/s') >>> b = Field(data*0.32, id='v', long_name='Zonal wind', units='m/s') >>> ['%.6g' % a[i] for i in range(len(a))] ['1', '2', '5', '-3', '-4.4', '2.2'] >>> ['%.6g' % b[i] for i in range(len(b))] ['0.32', '0.64', '1.6', '-0.96', '-1.408', '0.704'] >>> c = add(a, b) >>> ['%.6g' % c[i] for i in range(len(c))] ['1.32', '2.64', '6.6', '-3.96', '-5.808', '2.904'] >>> data[2] = -99. >>> ['%.6g' % a[i] for i in range(len(a))] ['1', '2', '-99', '-3', '-4.4', '2.2'] >>> ['%.6g' % b[i] for i in range(len(b))] ['0.32', '0.64', '1.6', '-0.96', '-1.408', '0.704'] >>> ['%.6g' % c[i] for i in range(len(c))] ['1.32', '2.64', '6.6', '-3.96', '-5.808', '2.904'] #- Test ravel (whose result should share memory with input data): >>> data = N.array([[-0.11, 2.1, 5.3], [3.9, 4.4, -2.0]]) >>> x = Field( data, id='u', long_name='Zonal wind' \ , axis=['lat', 'lon'] ) >>> print x.extra_meta.__dict__ {'axis': ['lat', 'lon']} >>> print shape(x) (2, 3) >>> y = ravel(x) >>> print y array([-0.11, 2.1 , 5.3 , 3.9 , 4.4 , -2. ]) >>> print y.extra_meta.__dict__ {} >>> print shape(x) (2, 3) >>> print shape(y) (6,) >>> print id(a) == id(b) False >>> x[1,:] = -9999. >>> print x array([[ -1.10000000e-01, 2.10000000e+00, 5.30000000e+00], [ -9.99900000e+03, -9.99900000e+03, -9.99900000e+03]]) >>> print y array([ -1.10000000e-01, 2.10000000e+00, 5.30000000e+00, -9.99900000e+03, -9.99900000e+03, -9.99900000e+03]) >>> y[1] = -8888. >>> print x array([[ -1.10000000e-01, -8.88800000e+03, 5.30000000e+00], [ -9.99900000e+03, -9.99900000e+03, -9.99900000e+03]]) >>> print y array([ -1.10000000e-01, -8.88800000e+03, 5.30000000e+00, -9.99900000e+03, -9.99900000e+03, -9.99900000e+03]) #- Test reshape (whose result should share memory with input data): >>> data = N.array([-0.11, 2.1, 5.3, 3.9, 4.4, -2.0]) >>> x = Field(data, id='u', long_name='Zonal wind', axis=['Latitude']) >>> print x.extra_meta.__dict__ {'axis': ['Latitude']} >>> print shape(x) (6,) >>> y = reshape(x, (3,2)) >>> print x array([-0.11, 2.1 , 5.3 , 3.9 , 4.4 , -2. ]) >>> print y array([[-0.11, 2.1 ], [ 5.3 , 3.9 ], [ 4.4 , -2. ]]) >>> print shape(y) (3, 2) >>> print x.extra_meta.__dict__ {'axis': ['Latitude']} >>> print y.extra_meta.__dict__ {} >>> x[1] = -99. >>> print x array([ -0.11, -99. , 5.3 , 3.9 , 4.4 , -2. ]) >>> print y array([[ -0.11, -99. ], [ 5.3 , 3.9 ], [ 4.4 , -2. ]]) >>> y[2,1] = -88. >>> print x array([ -0.11, -99. , 5.3 , 3.9 , 4.4 , -88. ]) >>> print y array([[ -0.11, -99. ], [ 5.3 , 3.9 ], [ 4.4 , -88. ]]) """} if __name__ == "__main__": """Test the module documentation strings and __test__. """ import doctest, sys, os sys.path.append(os.pardir) doctest.testmod(sys.modules[__name__]) # ===== end file =====