#!/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 =====
