import collections
from copy import deepcopy
from re import sub as re_sub
from re import search as re_search
from ast import literal_eval as ast_literal_eval
from .functions import equals
from .functions import inspect as cf_inspect
from .data.data import Data
from . import _found_ESMF
_collapse_cell_methods = {
'max' : 'maximum',
'mean' : 'mean',
'mid_range' : 'mid_range',
'min' : 'minimum',
'range' : 'range',
'sd' : 'standard_deviation',
'sum' : 'sum',
'var' : 'variance',
'sample_size' : None,
'sum_of_weights' : None,
'sum_of_weights2': None,
}
# ====================================================================
#
# _CellMethod object
#
# ====================================================================
class _CellMethod(object):
'''**Attributes**
============ ========================================================
Attribute Description
============ ========================================================
`!names`
`!intervals`
`!method`
`!over`
`!where`
`!within`
`!comment`
`!axes`
============ ========================================================
'''
def __init__(self):
'''
'''
self.axes = ()
self.names = ()
self.intervals = ()
self.method = None
self.comment = None
self.where = None
self.within = None
self.over = None
#--- End: def
def __deepcopy__(self, memo):
'''
Used if copy.deepcopy is called on the variable.
'''
return self.copy()
#--- End: def
def __hash__(self):
'''
x.__hash__() <==> hash(x)
'''
return hash(str(self))
#--- End: if
def __repr__(self):
'''
x.__repr__() <==> repr(x)
'''
return '<CF _CellMethod: %s>' % str(self)
#--- End: def
def __str__(self):
'''
x.__str__() <==> str(x)
Return a CF-netCDF-like string of the cell method.
Note that if the intention use this string in a CF-netCDF cell_methods
attribute then the cell method's `!name` attribute may need to be
modified, where appropriate, to reflect netCDF variable names.
'''
string = []
x = []
for axis, name in zip(self.axes, self.names):
if name is None:
if axis is not None:
name = axis
else:
name = '?'
x.append('%s:' % name)
#--- End: for
string.extend(x)
method = self.method
if method is None:
method = ''
string.append(method)
for portion in ('within', 'where', 'over'):
p = getattr(self, portion, None)
if p is not None:
string.extend((portion, p))
#--- End: for
intervals = self.intervals
if intervals:
x = ['(']
y = ['interval: %s' % data for data in intervals]
x.append(' '.join(y))
if self.comment is not None:
x.append(' comment: %s' % self.comment)
x.append(')')
string.append(''.join(x))
elif self.comment is not None:
string.append('(%s)' % self.comment)
return ' '.join(string)
#--- End: def
def __eq__(self):
'''
x.__eq__(y) <==> x==y
'''
return self.equals(y)
#--- End: def
def __ne__(self, other):
'''
x.__ne__(y) <==> x!=y
'''
return not self.__eq__(other)
#--- End: def
def copy(self):
'''
Return a deep copy.
``c.copy()`` is equivalent to ``copy.deepcopy(c)``.
:Returns:
out :
The deep copy.
:Examples:
>>> d = c.copy()
'''
new = _CellMethod.__new__(_CellMethod)
new.axes = self.axes
new.names = self.names
new.method = self.method
new.comment = self.comment
new.where = self.where
new.within = self.within
new.over = self.over
new.intervals = tuple([data.copy() for data in self.intervals])
return new
#--- End: def
def equals(self, other, rtol=None, atol=None,
ignore_fill_value=False, traceback=False):
'''
True if two cell methods are equal, False otherwise.
The `!axes` attribute is ignored in the comparison.
:Parameters:
other :
The object to compare for equality.
atol : float, optional
The absolute tolerance for all numerical comparisons, By
default the value returned by the `ATOL` function is used.
rtol : float, optional
The relative tolerance for all numerical comparisons, By
default the value returned by the `RTOL` function is used.
ignore_fill_value : bool, optional
If True then data arrays with different fill values are
considered equal. By default they are considered unequal.
traceback : bool, optional
If True then print a traceback highlighting where the two
instances differ.
:Returns:
out : bool
Whether or not the two instances are equal.
:Examples:
'''
if self is other:
return True
# Check that each instance is the same type
if self.__class__ != other.__class__:
if traceback:
print("%s: Different types: %s != %s" %
(self.__class__.__name__,
self.__class__.__name__,
other.__class__.__name__))
return False
#--- End: if
names0 = self.names
names1 = other.names
# indices0 = sorted(range(len(names0)), key=names0.__getitem__)
# indices1 = sorted(range(len(names1)), key=names1.__getitem__)
# names0 = sorted(names0)
# names1 = sorted(names1)
if names0 != names1:
if traceback:
print("%s: Different names: %s != %s" %
(CellMethods.__name__, names0, names1))
return False
if None in names0 or None in names0:
if traceback:
print("%s: Missing names: %s, %s" %
(CellMethods.__name__, names0, names1))
return False
#--- End: if
for attr in ('method', 'within', 'over', 'where', 'comment'):
x = getattr(self, attr)
y = getattr(other, attr)
if x != y:
if traceback:
print("%s: Different %s: %r != %r" %
(CellMethods.__name__, attr, x, y))
return False
intervals0 = self.intervals
intervals1 = other.intervals
if intervals0:
if not intervals1:
if traceback:
print("%s: Different intervals: %r != %r" %
(CellMethods.__name__, intervals0, intervals1))
return False
#--- End: if
if len(intervals0) != len(intervals1):
if traceback:
print("%s: Different intervals: %r != %r" %
(CellMethods.__name__, intervals0, intervals1))
return False
#--- End: if
for data0, data1 in zip(intervals0, intervals1):
if not data0.equals(data1, rtol=rtol, atol=atol,
ignore_fill_value=ignore_fill_value,
traceback=traceback):
if traceback:
print("%s: Different intervals: %r != %r" %
(CellMethods.__name__, data0, data1))
return False
elif intervals1:
if traceback:
print("%s: Different intervals: %r != %r" %
(CellMethods.__name__, intervals0, intervals1))
return False
#--- End: if
return True
#--- End: def
def equivalent(self, other, rtol=None, atol=None, traceback=False):
'''
True if two cell methods are equivalent, False otherwise.
The `axes` attribute is ignored in the comparison.
:Parameters:
other :
The object to compare for equality.
atol : float, optional
The absolute tolerance for all numerical comparisons, By
default the value returned by the `ATOL` function is used.
rtol : float, optional
The relative tolerance for all numerical comparisons, By
default the value returned by the `RTOL` function is used.
:Returns:
out : bool
Whether or not the two instances are equivalent.
:Examples:
'''
if self is other:
return True
# Check that each instance is the same type
if self.__class__ != other.__class__:
if traceback:
print("%s: Different types: %s != %s" %
(CellMethods.__name__,
self.__class__.__name__,
other.__class__.__name__))
return False
#--- End: if
names0 = self.names
names1 = other.names
indices0 = sorted(range(len(names0)), key=names0.__getitem__)
indices1 = sorted(range(len(names1)), key=names1.__getitem__)
names0 = sorted(names0)
names1 = sorted(names1)
if None in names0 or None in names1 or names0 != names1:
if traceback:
print("%s: Nonequivalent names: %r, %r" %
(CellMethods.__name__, names0, names1))
return False
#--- End: if
for attr in ('method', 'within', 'over', 'where', 'comment'):
x = getattr(self, attr)
y = getattr(other, attr)
if x != y:
if traceback:
print("%s: Nonequivalent %s: %r, %r" %
(CellMethods.__name__, attr, x, y))
return False
#--- End: if
intervals0 = self.intervals
intervals1 = other.intervals
if intervals0:
if not intervals1:
if traceback:
print("%s: Nonequivalent intervals: %r, %r" %
(CellMethods.__name__, intervals0, intervals1))
return False
#--- End: if
if len(intervals0) == 1:
intervals0 = intervals0 * len(names0)
if len(intervals1) == 1:
intervals1 = intervals1 * len(names1)
if len(intervals0) != len(intervals1):
if traceback:
print("%s: Nonequivalent intervals: %r, %r" %
(CellMethods.__name__, intervals0, intervals1))
return False
#--- End: if
# Sort the intervals
intervals0 = [intervals0[i] for i in indices0]
intervals1 = [intervals1[i] for i in indices1]
for data0, data1 in zip(intervals0, intervals1):
if not data0.allclose(data1, rtol=rtol, atol=atol):
if traceback:
print("%s: Nonequivalent intervals: %r, %r" %
(CellMethods.__name__, data0, data1))
return False
elif intervals1:
if traceback:
print("%s: Nonequivalent intervals: %r, %r" %
(CellMethods.__name__, intervals0, intervals1))
return False
#--- End: if
return True
#--- End: def
def inspect(self):
'''
Inspect the attributes.
.. seealso:: `cf.inspect`
:Returns:
None
'''
print cf_inspect(self)
#--- End: def
#--- End: def
# ====================================================================
#
# CellMethods object
#
# ====================================================================
[docs]class CellMethods(collections.MutableSequence):
'''
A CF cell methods object to describe the characteristic of a field
that is represented by cell values.
'''
def __init__(self, cell_methods=None):
'''
**Initialization**
:Parameters:
string : str, optional
Initialize new instance from a CF-netCDF-like cell methods
string. See the `parse` method for details. By default an
empty cell methods is created.
:Examples:
>>> c = cf.CellMethods()
>>> c = cf.CellMethods('time: max: height: mean')
'''
if not cell_methods:
self._list = []
elif isinstance(cell_methods, basestring):
self._list = []
self._parse(cell_methods)
else:
self._list = list(cell_methods)
#--- End: def
def __delitem__(self, index):
'''
x.__delitem__(index) <==> del x[index]
'''
del self._list[index]
#--- End: def
def __deepcopy__(self, memo):
'''
Used if copy.deepcopy is called on the variable.
'''
return self.copy()
#--- End: def
def __getitem__(self, index):
'''
x.__getitem__(index) <==> x[index]
s
'''
if isinstance(index, (int, long)):
return type(self)((self._list[index],))
else:
return type(self)(self._list[index])
#--- End: def
def __hash__(self):
'''
x.__hash__() <==> hash(x)
'''
return hash(str(self))
#--- End: if
def __len__(self):
'''
x.__len__() <==> len(x)
'''
return len(self._list)
#--- End: def
def __repr__(self):
'''
x.__repr__() <==> repr(x)
'''
return '<CF %s: %s>' % (self.__class__.__name__, str(self))
#--- End: def
def __setitem__(self, index, value):
'''
x.__setitem__(index, value) <==> x[index]=value
'''
if not isinstance(value, self.__class__):
raise ValueError(
"Can't assign %s to %s[%s]" %
(value.__class__.__name__, self.__class__.__name__, index))
if isinstance(index, (int, long)):
index = slice(index, index+1)
self._list[index] = value._list
#--- End: def
def __str__(self):
'''
x.__str__() <==> str(x)
'''
return ' '.join([str(cm) for cm in self._list])
#--- End: def
def __eq__(self, other):
'''
x.__eq__(y) <==> x==y
'''
return self.equals(other)
#--- End: def
def __ne__(self, other):
'''
x.__ne__(y) <==> x!=y
'''
return not self.__eq__(other)
#--- End: def
def __add__(self, other):
'''
x.__add__(y) <==> x+y
'''
new = self.copy()
new.extend(other)
return new
#--- End: def
def __mul__(self, other):
'''
x.__mul__(n) <==> x*n
'''
return type(self)(self._list * other)
#--- End: def
def __rmul__(self, other):
'''
x.__rmul__(n) <==> n*x
'''
return self * other
#--- End: def
def __iadd__(self, other):
'''
x.__iadd__(y) <==> x+=y
'''
self.extend(other)
return self
#--- End: def
def __imul__(self, other):
'''
x.__imul__(n) <==> x*=n
'''
self._list = self._list * other
return self
#--- End: def
def _parse(self, string=None):
'''
Parse a CF cell_methods string into this `cf.CellMethods` instance in
place.
:Parameters:
string : str, optional
The CF cell_methods string to be parsed into the
`cf.CellMethods` object. By default the cell methods will be
empty.
:Returns:
None
:Examples:
>>> c = cf.CellMethods()
>>> c = c._parse('time: minimum within years time: mean over years (ENSO years)')
>>> print c
Cell methods : time: minimum within years
time: mean over years (ENSO years)
'''
if not string:
self._list[:] = []
return
# Split the cell_methods string into a list of strings ready
# for parsing into the result list. E.g.
# 'lat: mean (interval: 1 hour)'
# maps to
# ['lat:', 'mean', '(', 'interval:', '1', 'hour', ')']
cell_methods = re_sub('\((?=[^\s])' , '( ', string)
cell_methods = re_sub('(?<=[^\s])\)', ' )', cell_methods).split()
while cell_methods:
cm = _CellMethod()
axes = []
names = []
while cell_methods:
if not cell_methods[0].endswith(':'):
break
# Check that "name" ebds with colon? How? ('lat: mean (area-weighted) or lat: mean (interval: 1 degree_north comment: area-weighted)')
names.append(cell_methods.pop(0)[:-1])
axes.append(None)
#--- End: while
cm.axes = tuple(axes)
cm.names = tuple(names)
if not cell_methods:
self.append(cm)
break
# Method
cm.method = cell_methods.pop(0)
if not cell_methods:
self.append(cm)
break
# Climatological statistics and statistics which apply to
# portions of cells
while cell_methods[0] in ('within', 'where', 'over'):
attr = cell_methods.pop(0)
setattr(cm, attr, cell_methods.pop(0))
if not cell_methods:
break
#--- End: while
if not cell_methods:
self.append(cm)
break
# interval and comment
intervals = []
if cell_methods[0].endswith('('):
cell_methods.pop(0)
if not (re_search('^(interval|comment):$', cell_methods[0])):
cell_methods.insert(0, 'comment:')
while not re_search('^\)$', cell_methods[0]):
term = cell_methods.pop(0)[:-1]
if term == 'interval':
interval = cell_methods.pop(0)
if cell_methods[0] != ')':
units = cell_methods.pop(0)
else:
units = None
try:
# parsed_interval = float(ast_literal_eval(interval))
parsed_interval = ast_literal_eval(interval)
except:
raise ValueError(
"Unparseable cell methods interval: {0!r}".format(
interval+' '+units if units is not None else interval))
try:
intervals.append(Data(parsed_interval, units))
except:
raise ValueError(
"Unparseable cell methods interval: {0!r}".format(
interval+' '+units if units is not None else interval))
continue
#--- End: if
if term == 'comment':
comment = []
while cell_methods:
if cell_methods[0].endswith(')'):
break
if cell_methods[0].endswith(':'):
break
comment.append(cell_methods.pop(0))
#--- End: while
cm.comment = ' '.join(comment)
#--- End: if
#--- End: while
if cell_methods[0].endswith(')'):
cell_methods.pop(0)
#--- End: if
n_intervals = len(intervals)
if n_intervals > 1 and n_intervals != len(names):
raise ValueError("0798798 ")
cm.intervals = tuple(intervals)
self.append(cm)
#--- End: while
#--- End: def
@property
def axes(self):
return tuple([cm.axes for cm in self._list])
@axes.setter
def axes(self, value):
if len(self._list) != 1:
raise ValueError(
"Must select a %s element to update. Consider c[i].axes=value" %
self.__class__.__name__)
if not isinstance(value, (tuple, list)):
raise ValueError("%s axes attribute must be a tuple or list" %
self.__class__.__name__)
self._list[0].axes = tuple(value)
#--- End: def
@axes.deleter
def axes(self):
if len(self._list) != 1:
raise ValueError(
"Must select a %s element to update. Consider del c[i].axes" %
self.__class__.__name__)
self._list[0].axes = ()
#--- End: def
@property
def comment(self):
'''
Each cell method's comment keyword.
'''
return tuple([cm.comment for cm in self._list])
@comment.deleter
def comment(self):
if len(self._list) != 1:
raise ValueError(
"Must select a %s element to update. Consider del c[i].comment" %
self.__class__.__name__)
self._list[0].comment = None
#--- End: def
@property
def method(self):
'''
Each cell method's method keyword.
These describe how the cell values of field have been determined or
derived.
:Examples:
>>> c = cf.CellMethods('time: minimum area: mean')
>>> c
<CF CellMethods: time: minimum area: mean>
>>> c.method
['minimum', 'mean']
>>> c[1].method = 'variance'
>>> c.method
['minimum', 'variance']
>>> c
<CF CellMethods: time: minimum area: variance>
>>> d = c[1]
>>> d
<CF CellMethods: area: variance>
>>> d.method
['variance']
>>> d.method = 'maximum'
>>> d.method
['maximum']
>>> c
<CF CellMethods: time: minimum area: maximum>
'''
return tuple([cm.method for cm in self._list])
#--- End: def
@method.setter
def method(self, value):
if len(self._list) != 1:
raise ValueError(
"Must select a %s element to update. Consider c[i].method=value" %
self.__class__.__name__)
self._list[0].method = value
#--- End: def
@method.deleter
def method(self):
if len(self._list) != 1:
raise ValueError(
"Must select a %s element to update. Consider del c[i].method" %
self.__class__.__name__)
self._list[0].method = None
#--- End: def
@property
def names(self):
'''
Each cell method's name keyword(s).
:Examples:
>>> c = cf.CellMethods('time: minimum area: mean')
>>> c
<CF CellMethods: time: minimum area: mean>
>>> c.names
(('time',), ('area',))
>>> c[1].names = ['lat', 'lon']
>>> c.names
(('time',), ('lat', 'lon'))
>>> c
<CF CellMethods: time: minimum lat: lon: mean>
>>> d = c[1]
>>> d
<CF CellMethods: lat: lon: mean>
>>> d.names
(('lat', 'lon'),)
>>> d.names = ('area',)
>>> d.names
(('area',),)
>>> c
<CF CellMethods: time: minimum area: mean>
'''
return tuple([cm.names for cm in self._list])
@names.setter
def names(self, value):
if len(self._list) != 1:
raise ValueError(
"Must select a %s element to update. Consider c[i].names=value" %
self.__class__.__name__)
if not isinstance(value, (list, tuple)):
raise ValueError("%s names attribute must be a tuple or list" %
self.__class__.__name__)
self._list[0].names = tuple(value)
# Make sure that axes has the same number of elements as names
len_value = len(value)
if len_value != len(self.axes[0]):
self.axes = (None,) * len_value
#--- End: def
@names.deleter
def names(self):
if len(self._list) != 1:
raise ValueError(
"Must select a %s element to update. Consider del c[i].names" %
self.__class__.__name__)
self._list[0].names = ()
#--- End: def
@property
def intervals(self):
'''
Each cell method's interval keyword(s).
:Examples:
>>> c = cf.CellMethods('time: minimum (interval: 1 hr) lat: lon: mean (interval: 0.1 degree_N interval: 0.2 degree_E)')
>>> c
<CF CellMethods: time: minimum (interval: 1 hr) lat: lon: mean (interval: 0.1 degree_N interval: 0.2 degree_E)>
>>> c.intervals
[[<CF Data: 1 hr>], [<CF Data: 0.1 degree_N>, <CF Data: 0.2 degree_E>]]
>>> c[0].intervals = ['3600 seconds']
>>> c.intervals
>>> c[0].intervals
[[<CF Data: 3600 seconds>]]
>>> c[0].intervals = [cf.Data(60, 'minutes')]
>>> c[0].intervals
[[<CF Data: 60 minutes>]]
>>> c[0].intervals = [1]
>>> c[0].intervals
[[<CF Data: 1 >]]
>>> del c[0].intervals
>>> c.intervals
>>> [[], [<CF Data: 0.1 degree_N>, <CF Data: 0.2 degree_E>]]
>>> c
<CF CellMethods: time: minimum lat: lon: mean (interval: 0.1 degree_N interval: 0.2 degree_E)>
'''
return tuple([cm.intervals for cm in self._list])
@intervals.setter
def intervals(self, value):
if len(self._list) != 1:
raise ValueError(
"Must select a %s element to update. Consider c[i].intervals=value" %
self.__class__.__name__)
if not isinstance(value, (tuple, list)):
raise ValueError(
"%s intervals attribute must be a tuple or list, not a %s" %
(self.__class__.__name__, value.__class__.__name__))
# Parse the intervals
values = []
for interval in value:
if isinstance(interval, basestring):
i = interval.split()
try:
x = ast_literal_eval(i.pop(0))
except:
raise ValueError(
"Unparseable cell methods interval: %r" % interval)
if interval:
units = ' '.join(i)
else:
units = None
try:
d = Data(x, units)
except:
raise ValueError(
"Unparseable cell methods interval: %r" % interval)
else:
try:
d = Data.asdata(interval, copy=True)
except:
raise ValueError(
"Unparseable cell methods interval: %r" % interval)
#--- End: if
if d.size != 1:
raise ValueError(
"Unparseable cell methods interval: %r" % interval)
if d.ndim > 1:
d.squeeze(i=True)
values.append(d)
#--- End: for
self._list[0].intervals = tuple(values)
#--- End: def
@intervals.deleter
def intervals(self):
if len(self._list) != 1:
raise ValueError(
"Must select a %s element to update. Consider del c[i].intervals" %
self.__class__.__name__)
self._list[0].intervals = ()
#--- End: def
@property
def over(self):
'''
Each cell method's over keyword.
These describe how climatological statistics have been derived.
.. seealso:: `within`
:Examples:
>>> c = cf.CellMethods('time: minimum area: mean')
>>> c
<CF CellMethods: time: minimum time: mean>
>>> c.over
[None, None]
>>> c[0].within = 'years'
>>> c[1].over = 'years'
>>> c.over
>>> [None, 'years']
>>> c
<CF CellMethods: time: minimum within years time: mean over years>
>>> d = c[1]
>>> d
<CF CellMethods: time: mean over years>
>>> del d.over
>>> d.over
[None]
>>> d
<CF CellMethods: time: mean>
>>> del c[0].within
>>> c.within
()
>>> c
<CF CellMethods: time: minimum time: mean>
'''
return tuple([cm.over for cm in self._list])
#--- End: def
@over.setter
def over(self, value):
if len(self._list) != 1:
raise ValueError(
"Must select a %s element to update. Consider c[i].over=value" %
self.__class__.__name__)
self._list[0].over = value
#--- End: def
@over.deleter
def over(self):
if len(self._list) != 1:
raise ValueError(
"Must select a %s element to update. Consider del c[i].over" %
self.__class__.__name__)
self._list[0].over = None
#--- End: def
@property
def where(self):
'''
Each cell method's where keyword.
'''
return tuple([cm.where for cm in self._list])
#--- End: def
@where.setter
def where(self, value):
if len(self._list) != 1:
raise ValueError("Must select a %s element to update. Consider c[i].where=value" %
self.__class__.__name__)
self._list[0].where = value
#--- End: def
@where.deleter
def where(self):
if len(self._list) != 1:
raise ValueError(
"Must select a %s element to update. Consider del c[i].where" %
self.__class__.__name__)
self._list[0].where = None
#--- End: def
@property
def within(self):
'''
Each cell method's within keyword.
These describe how climatological statistics have been derived.
.. seealso:: `over`
:Examples:
>>> c = cf.CellMethods('time: minimum area: mean')
>>> c
<CF CellMethods: time: minimum time: mean>
>>> c.within
(None, None)
>>> c[0].within = 'years'
>>> c[1].over = 'years'
>>> c.within
>>> ('years', None)
>>> c
<CF CellMethods: time: minimum within years time: mean over years>
>>> d = c[0]
>>> d
<CF CellMethods: time: minimum within years>
>>> del d.within
>>> d.within
(None,)
>>> d
<CF CellMethods: time: minimum>
>>> del c[1].over
>>> c
<CF CellMethods: time: minimum time: mean>
'''
return tuple([cm.within for cm in self._list])
#--- End: def
@within.setter
def within(self, value):
if len(self._list) != 1:
raise ValueError(
"Must select a %s element to update. Consider c[i].within=value" %
self.__class__.__name__)
self._list[0].within = value
#--- End: def
@within.deleter
def within(self):
if len(self._list) != 1:
raise ValueError(
"Must select a %s element to update. Consider del c[i].within" %
self.__class__.__name__)
self._list[0].within = None
#--- End: def
[docs] def copy(self):
'''
Return a deep copy.
``c.copy()`` is equivalent to ``copy.deepcopy(c)``.
:Returns:
out :
The deep copy.
:Examples:
>>> d = c.copy()
'''
new = CellMethods.__new__(CellMethods)
new._list = [cm.copy() for cm in self._list]
return new
#--- End: def
[docs] def dump(self, display=True, prefix=None):
'''
Return a string containing a full description of the instance.
If a cell methods 'name' is followed by a '*' then that cell method is
relevant to the data in a way which may not be precisely defined its
corresponding dimension or dimensions.
:Parameters:
display : bool, optional
If False then return the description as a string. By default
the description is printed, i.e. ``c.dump()`` is equivalent to
``print c.dump(display=False)``.
prefix : str, optional
Set the common prefix of component names. By default the
instance's class name is used.
:Returns:
out : None or str
A string containing the description.
:Examples:
'''
if prefix is None:
prefix = self.__class__.__name__
string = []
for i, cm in enumerate(self._list):
string.append('%s[%d] -> %s' % (prefix, i, cm))
string = '\n'.join(string)
if display:
print string
else:
return string
#--- End: def
[docs] def equals(self, other, rtol=None, atol=None,
ignore_fill_value=False, traceback=False):
'''
True if two cell methods are equal, False otherwise.
The `axes` attribute is ignored in the comparison.
:Parameters:
other :
The object to compare for equality.
atol : float, optional
The absolute tolerance for all numerical comparisons, By
default the value returned by the `ATOL` function is used.
rtol : float, optional
The relative tolerance for all numerical comparisons, By
default the value returned by the `RTOL` function is used.
ignore_fill_value : bool, optional
If True then data arrays with different fill values are
considered equal. By default they are considered unequal.
traceback : bool, optional
If True then print a traceback highlighting where the two
instances differ.
:Returns:
out : bool
Whether or not the two instances are equal.
:Examples:
'''
if self is other:
return True
# Check that each instance is the same type
if self.__class__ != other.__class__:
if traceback:
print("%s: Different types: %s != %s" %
(self.__class__.__name__,
self.__class__.__name__,
other.__class__.__name__))
return False
#--- End: if
if len(self._list) != len(other._list):
if traceback:
print("%s: Different numbers of methods: %d != %d" %
(self.__class__.__name__,
len(self._list), len(other._list)))
return False
#--- End: if
for cm0, cm1 in zip(self._list, other._list):
if not cm0.equals(cm1, rtol=rtol, atol=atol,
ignore_fill_value=ignore_fill_value,
traceback=traceback):
return False
#--- End: for
return True
#--- End: def
[docs] def equivalent(self, other, rtol=None, atol=None, traceback=False):
'''
True if two cell methods are equivalent, False otherwise.
The `axes` attributes are ignored in the comparison.
:Parameters:
other :
The object to compare for equality.
atol : float, optional
The absolute tolerance for all numerical comparisons, By
default the value returned by the `ATOL` function is used.
rtol : float, optional
The relative tolerance for all numerical comparisons, By
default the value returned by the `RTOL` function is used.
:Returns:
out : bool
Whether or not the two instances are equivalent.
:Examples:
'''
if self is other:
return True
# Check that each instance is the same type
if self.__class__ != other.__class__:
if traceback:
print("%s: Different types: %s != %s" %
(self.__class__.__name__,
self.__class__.__name__,
other.__class__.__name__))
return False
#--- End: if
if len(self._list) != len(other._list):
if traceback:
print("%s: Different numbers of methods: %d != %d" %
(self.__class__.__name__,
len(self._list), len(other._list)))
return False
#--- End: if
for cm0, cm1 in zip(self._list, other._list):
if not cm0.equivalent(cm1, rtol=rtol, atol=atol,
traceback=traceback):
return False
#--- End: for
return True
#--- End: def
[docs] def has_cellmethod(self, other):
'''
Return True if and only if this cell methods is a super set of another.
:Parameters:
other : cf.CellMethods
The other cell methods for comparison.
:Returns:
out : bool
Whether or not this cell methods is a super set of the other.
:Examples:
'''
if len(other) != 1:
return False
found_match = False
cm1 = other._list[0]
for cm in self._list:
if cm.equivalent(cm1):
found_match = True
break
#--- End: for
return found_match
#--- End: def
[docs] def extend(self, value):
self._list.extend(value._list)
#--- End: def
[docs] def insert(self, index, value):
self._list.insert(index, value)
#--- End: def
[docs] def inspect(self):
'''
Inspect the attributes.
.. seealso:: `cf.inspect`
:Returns:
None
'''
print cf_inspect(self)
#--- End: def
[docs] def netcdf_translation(self, f):
'''
Translate netCDF variable names stored in the `!names` attribute into
`axes` and `names` attributes.
:Parameters:
f : cf.Field
The field which provides the translation.
:Returns:
out : cf.CellMethods
A new cell methods instance with translated names.
:Examples:
>>> c = cf.CellMethods('t: mean lon: mean')
>>> c.names = (('t',), ('lon',))
>>> c.axes = ((None,), (None,))
>>> d = c.netcdf_translation(f)
>>> d.names = (('time',), ('longitude',))
>>> d.axes = (('dim0',), ('dim2',))
>>> d
<CF CellMethods: 'time: mean longitude: mean')
'''
cell_methods = self.copy()
domain = f.domain
# Change each names value to a standard_name (or domain
# coordinate identifier) and create the axes attribute.
# From the CF conventions (1.5): In the specification of this
# attribute, name can be a dimension of the variable, a scalar
# coordinate variable, a valid standard name, or the word
# 'area'.
for cm in cell_methods._list:
names = cm.names
if names == ('area',):
cm.axes = (None,)
continue
#--- End: if
names = list(names)
axes = []
dim_coords = f.dims()
# Still here?
for i, name in enumerate(names):
axis = None
for axis, ncdim in domain.nc_dimensions.iteritems():
if name == ncdim:
break
axis = None
#--- End: for
if axis is not None:
# name is a netCDF dimension name (including
# scalar coordinates).
axes.append(axis)
if axis in dim_coords:
names[i] = dim_coords[axis].name('domain:%s' % axis)
else:
names[i] = None
else:
# name must be a standard name
axes.append(domain.axis({'standard_name': name},
role='d', exact=True))
#--- End: for
cm.names = tuple(names)
cm.axes = tuple(axes)
#--- End: for
return cell_methods
#--- End: def
def netcdf_names(self, axis_to_ncdim, axis_to_ncscalar):
'''
Translate `names` to CF-netCDF names.
:Parameters:
axis_to_ncdim: dict
The first dictionary which provides the translation.
axis_to_ncscalar: dict
The alternative dictionary which provides the translation.
:Returns:
out : cf.CellMethods
A new cell methods instance with translated names.
:Examples:
>>> c = cf.CellMethods('t: mean lon: mean')
>>> c.names = (('t',), ('lon',))
>>> c.axes = ((None,), (None,))
>>> d = c.netcdf_translation(f)
>>> d.names = (('time',), ('longitude',))
>>> d.axes = (('dim0',), ('dim2',))
>>> d
<CF CellMethods: 'time: mean longitude: mean')
'''
new = self.copy()
for cm in new._list:
if cm.names == ('area',):
continue
names = []
for axis, name in zip(cm.axes, cm.names):
names.append(
axis_to_ncdim.get(axis,
axis_to_ncscalar.get(axis,
name)))
#--- End: for
cm.names = tuple(names)
#--- End: for
return new
#--- End: def
def set_axes(self, f, override=False):
'''Create new cell methods with `axes` inferred from `names`.
:Parameters:
f : cf.Field
The field providing the translation.
override : bool, optional
If True then change existing `axes` elements. By default
exisiting `axes` elements are not changed.
:Returns:
out : cf.CellMethods
A new cell methods instance
:Examples:
>>> c = cf.CellMethods('t: mean lon: mean')
>>> c.names = (('t',), ('lon',))
>>> c.axes = ((None,), (None,))
>>> d = c.netcdf_translation(f)
>>> d.names = (('time',), ('longitude',))
>>> d.axes = (('dim0',), ('dim2',))
>>> d
<CF CellMethods: 'time: mean longitude: mean')
'''
new = self.copy()
for cm in new._list:
names = cm.names
if names == ('area',):
cm.axes = (None,)
continue
cm.axes = tuple([(f.domain.axis(name)
if axis is None or override else
axis)
for axis, name in zip(cm.axes, names)])
#--- End: for
return new
#--- End: def
#--- End: class