"""MA: a facility for dealing with missing observations
MA is generally used as a numpy.array look-alike.
by Paul F. Dubois.

Copyright 1999, 2000, 2001 Regents of the University of California.
Released for unlimited redistribution.
Adapted for numpy_core 2005 by Travis Oliphant and
(mainly) Paul Dubois.

"""
import types, sys

import numpy.core.umath as umath
import numpy.core.fromnumeric as fromnumeric
from numpy.core.numeric import newaxis, ndarray, inf
from numpy.core.fromnumeric import amax, amin
from numpy.core.numerictypes import bool_, typecodes
import numpy.core.numeric as numeric
import warnings

if sys.version_info[0] >= 3:
    from functools import reduce

# Ufunc domain lookup for __array_wrap__
ufunc_domain = {}
# Ufunc fills lookup for __array__
ufunc_fills = {}

MaskType = bool_
nomask = MaskType(0)
divide_tolerance = 1.e-35

class MAError (Exception):
    def __init__ (self, args=None):
        "Create an exception"

        # The .args attribute must be a tuple.
        if not isinstance(args, tuple):
            args = (args,)
        self.args = args
    def __str__(self):
        "Calculate the string representation"
        return str(self.args[0])
    __repr__ = __str__

class _MaskedPrintOption:
    "One instance of this class, masked_print_option, is created."
    def __init__ (self, display):
        "Create the masked print option object."
        self.set_display(display)
        self._enabled = 1

    def display (self):
        "Show what prints for masked values."
        return self._display

    def set_display (self, s):
        "set_display(s) sets what prints for masked values."
        self._display = s

    def enabled (self):
        "Is the use of the display value enabled?"
        return self._enabled

    def enable(self, flag=1):
        "Set the enabling flag to flag."
        self._enabled = flag

    def __str__ (self):
        return str(self._display)

    __repr__ = __str__

#if you single index into a masked location you get this object.
masked_print_option = _MaskedPrintOption('--')

# Use single element arrays or scalars.
default_real_fill_value = 1.e20
default_complex_fill_value = 1.e20 + 0.0j
default_character_fill_value = '-'
default_integer_fill_value = 999999
default_object_fill_value = '?'

def default_fill_value (obj):
    "Function to calculate default fill value for an object."
    if isinstance(obj, float):
        return default_real_fill_value
    elif isinstance(obj, int) or isinstance(obj, int):
        return default_integer_fill_value
    elif isinstance(obj, bytes):
        return default_character_fill_value
    elif isinstance(obj, complex):
        return default_complex_fill_value
    elif isinstance(obj, MaskedArray) or isinstance(obj, ndarray):
        x = obj.dtype.char
        if x in typecodes['Float']:
            return default_real_fill_value
        if x in typecodes['Integer']:
            return default_integer_fill_value
        if x in typecodes['Complex']:
            return default_complex_fill_value
        if x in typecodes['Character']:
            return default_character_fill_value
        if x in typecodes['UnsignedInteger']:
            return umath.absolute(default_integer_fill_value)
        return default_object_fill_value
    else:
        return default_object_fill_value

def minimum_fill_value (obj):
    "Function to calculate default fill value suitable for taking minima."
    if isinstance(obj, float):
        return numeric.inf
    elif isinstance(obj, int) or isinstance(obj, int):
        return sys.maxsize
    elif isinstance(obj, MaskedArray) or isinstance(obj, ndarray):
        x = obj.dtype.char
        if x in typecodes['Float']:
            return numeric.inf
        if x in typecodes['Integer']:
            return sys.maxsize
        if x in typecodes['UnsignedInteger']:
            return sys.maxsize
    else:
        raise TypeError('Unsuitable type for calculating minimum.')

def maximum_fill_value (obj):
    "Function to calculate default fill value suitable for taking maxima."
    if isinstance(obj, float):
        return -inf
    elif isinstance(obj, int) or isinstance(obj, int):
        return -sys.maxsize
    elif isinstance(obj, MaskedArray) or isinstance(obj, ndarray):
        x = obj.dtype.char
        if x in typecodes['Float']:
            return -inf
        if x in typecodes['Integer']:
            return -sys.maxsize
        if x in typecodes['UnsignedInteger']:
            return 0
    else:
        raise TypeError('Unsuitable type for calculating maximum.')

def set_fill_value (a, fill_value):
    "Set fill value of a if it is a masked array."
    if isMaskedArray(a):
        a.set_fill_value (fill_value)

def getmask (a):
    """Mask of values in a; could be nomask.
       Returns nomask if a is not a masked array.
       To get an array for sure use getmaskarray."""
    if isinstance(a, MaskedArray):
        return a.raw_mask()
    else:
        return nomask

def getmaskarray (a):
    """Mask of values in a; an array of zeros if mask is nomask
     or not a masked array, and is a byte-sized integer.
     Do not try to add up entries, for example.
    """
    m = getmask(a)
    if m is nomask:
        return make_mask_none(shape(a))
    else:
        return m

def is_mask (m):
    """Is m a legal mask? Does not check contents, only type.
    """
    try:
        return m.dtype.type is MaskType
    except AttributeError:
        return False

def make_mask (m, copy=0, flag=0):
    """make_mask(m, copy=0, flag=0)
       return m as a mask, creating a copy if necessary or requested.
       Can accept any sequence of integers or nomask. Does not check
       that contents must be 0s and 1s.
       if flag, return nomask if m contains no true elements.
    """
    if m is nomask:
        return nomask
    elif isinstance(m, ndarray):
        if m.dtype.type is MaskType:
            if copy:
                result = numeric.array(m, dtype=MaskType, copy=copy)
            else:
                result = m
        else:
            result = m.astype(MaskType)
    else:
        result = filled(m, True).astype(MaskType)

    if flag and not fromnumeric.sometrue(fromnumeric.ravel(result)):
        return nomask
    else:
        return result

def make_mask_none (s):
    "Return a mask of all zeros of shape s."
    result = numeric.zeros(s, dtype=MaskType)
    result.shape = s
    return result

def mask_or (m1, m2):
    """Logical or of the mask candidates m1 and m2, treating nomask as false.
       Result may equal m1 or m2 if the other is nomask.
     """
    if m1 is nomask: return make_mask(m2)
    if m2 is nomask: return make_mask(m1)
    if m1 is m2 and is_mask(m1): return m1
    return make_mask(umath.logical_or(m1, m2))

def filled (a, value = None):
    """a as a contiguous numeric array with any masked areas replaced by value
    if value is None or the special element "masked", get_fill_value(a)
    is used instead.

    If a is already a contiguous numeric array, a itself is returned.

    filled(a) can be used to be sure that the result is numeric when
    passing an object a to other software ignorant of MA, in particular to
    numeric itself.
    """
    if isinstance(a, MaskedArray):
        return a.filled(value)
    elif isinstance(a, ndarray) and a.flags['CONTIGUOUS']:
        return a
    elif isinstance(a, dict):
        return numeric.array(a, 'O')
    else:
        return numeric.array(a)

def get_fill_value (a):
    """
    The fill value of a, if it has one; otherwise, the default fill value
    for that type.
    """
    if isMaskedArray(a):
        result = a.fill_value()
    else:
        result = default_fill_value(a)
    return result

def common_fill_value (a, b):
    "The common fill_value of a and b, if there is one, or None"
    t1 = get_fill_value(a)
    t2 = get_fill_value(b)
    if t1 == t2: return t1
    return None

# Domain functions return 1 where the argument(s) are not in the domain.
class domain_check_interval:
    "domain_check_interval(a,b)(x) = true where x < a or y > b"
    def __init__(self, y1, y2):
        "domain_check_interval(a,b)(x) = true where x < a or y > b"
        self.y1 = y1
        self.y2 = y2

    def __call__ (self, x):
        "Execute the call behavior."
        return umath.logical_or(umath.greater (x, self.y2),
                                   umath.less(x, self.y1)
                                  )

class domain_tan:
    "domain_tan(eps) = true where abs(cos(x)) < eps)"
    def __init__(self, eps):
        "domain_tan(eps) = true where abs(cos(x)) < eps)"
        self.eps = eps

    def __call__ (self, x):
        "Execute the call behavior."
        return umath.less(umath.absolute(umath.cos(x)), self.eps)

class domain_greater:
    "domain_greater(v)(x) = true where x <= v"
    def __init__(self, critical_value):
        "domain_greater(v)(x) = true where x <= v"
        self.critical_value = critical_value

    def __call__ (self, x):
        "Execute the call behavior."
        return umath.less_equal (x, self.critical_value)

class domain_greater_equal:
    "domain_greater_equal(v)(x) = true where x < v"
    def __init__(self, critical_value):
        "domain_greater_equal(v)(x) = true where x < v"
        self.critical_value = critical_value

    def __call__ (self, x):
        "Execute the call behavior."
        return umath.less (x, self.critical_value)

class masked_unary_operation:
    def __init__ (self, aufunc, fill=0, domain=None):
        """ masked_unary_operation(aufunc, fill=0, domain=None)
            aufunc(fill) must be defined
            self(x) returns aufunc(x)
            with masked values where domain(x) is true or getmask(x) is true.
        """
        self.f = aufunc
        self.fill = fill
        self.domain = domain
        self.__doc__ = getattr(aufunc, "__doc__", str(aufunc))
        self.__name__ = getattr(aufunc, "__name__", str(aufunc))
        ufunc_domain[aufunc] = domain
        ufunc_fills[aufunc] = fill,

    def __call__ (self, a, *args, **kwargs):
        "Execute the call behavior."
# numeric tries to return scalars rather than arrays when given scalars.
        m = getmask(a)
        d1 = filled(a, self.fill)
        if self.domain is not None:
            m = mask_or(m, self.domain(d1))
        result = self.f(d1, *args, **kwargs)
        return masked_array(result, m)

    def __str__ (self):
        return "Masked version of " + str(self.f)


class domain_safe_divide:
    def __init__ (self, tolerance=divide_tolerance):
        self.tolerance = tolerance
    def __call__ (self, a, b):
        return umath.absolute(a) * self.tolerance >= umath.absolute(b)

class domained_binary_operation:
    """Binary operations that have a domain, like divide. These are complicated
       so they are a separate class. They have no reduce, outer or accumulate.
    """
    def __init__ (self, abfunc, domain, fillx=0, filly=0):
        """abfunc(fillx, filly) must be defined.
           abfunc(x, filly) = x for all x to enable reduce.
        """
        self.f = abfunc
        self.domain = domain
        self.fillx = fillx
        self.filly = filly
        self.__doc__ = getattr(abfunc, "__doc__", str(abfunc))
        self.__name__ = getattr(abfunc, "__name__", str(abfunc))
        ufunc_domain[abfunc] = domain
        ufunc_fills[abfunc] = fillx, filly

    def __call__(self, a, b):
        "Execute the call behavior."
        ma = getmask(a)
        mb = getmask(b)
        d1 = filled(a, self.fillx)
        d2 = filled(b, self.filly)
        t = self.domain(d1, d2)

        if fromnumeric.sometrue(t, None):
            d2 = where(t, self.filly, d2)
            mb = mask_or(mb, t)
        m = mask_or(ma, mb)
        result =  self.f(d1, d2)
        return masked_array(result, m)

    def __str__ (self):
        return "Masked version of " + str(self.f)

class masked_binary_operation:
    def __init__ (self, abfunc, fillx=0, filly=0):
        """abfunc(fillx, filly) must be defined.
           abfunc(x, filly) = x for all x to enable reduce.
        """
        self.f = abfunc
        self.fillx = fillx
        self.filly = filly
        self.__doc__ = getattr(abfunc, "__doc__", str(abfunc))
        ufunc_domain[abfunc] = None
        ufunc_fills[abfunc] = fillx, filly

    def __call__ (self, a, b, *args, **kwargs):
        "Execute the call behavior."
        m = mask_or(getmask(a), getmask(b))
        d1 = filled(a, self.fillx)
        d2 = filled(b, self.filly)
        result = self.f(d1, d2, *args, **kwargs)
        if isinstance(result, ndarray) \
               and m.ndim != 0 \
               and m.shape != result.shape:
            m = mask_or(getmaskarray(a), getmaskarray(b))
        return masked_array(result, m)

    def reduce (self, target, axis=0, dtype=None):
        """Reduce target along the given axis with this function."""
        m = getmask(target)
        t = filled(target, self.filly)
        if t.shape == ():
            t = t.reshape(1)
            if m is not nomask:
                m = make_mask(m, copy=1)
                m.shape = (1,)
        if m is nomask:
            t = self.f.reduce(t, axis)
        else:
            t = masked_array (t, m)
            # XXX: "or t.dtype" below is a workaround for what appears
            # XXX: to be a bug in reduce.
            t = self.f.reduce(filled(t, self.filly), axis,
                              dtype=dtype or t.dtype)
            m = umath.logical_and.reduce(m, axis)
        if isinstance(t, ndarray):
            return masked_array(t, m, get_fill_value(target))
        elif m:
            return masked
        else:
            return t

    def outer (self, a, b):
        "Return the function applied to the outer product of a and b."
        ma = getmask(a)
        mb = getmask(b)
        if ma is nomask and mb is nomask:
            m = nomask
        else:
            ma = getmaskarray(a)
            mb = getmaskarray(b)
            m = logical_or.outer(ma, mb)
        d = self.f.outer(filled(a, self.fillx), filled(b, self.filly))
        return masked_array(d, m)

    def accumulate (self, target, axis=0):
        """Accumulate target along axis after filling with y fill value."""
        t = filled(target, self.filly)
        return masked_array (self.f.accumulate (t, axis))
    def __str__ (self):
        return "Masked version of " + str(self.f)

sqrt = masked_unary_operation(umath.sqrt, 0.0, domain_greater_equal(0.0))
log = masked_unary_operation(umath.log, 1.0, domain_greater(0.0))
log10 = masked_unary_operation(umath.log10, 1.0, domain_greater(0.0))
exp = masked_unary_operation(umath.exp)
conjugate = masked_unary_operation(umath.conjugate)
sin = masked_unary_operation(umath.sin)
cos = masked_unary_operation(umath.cos)
tan = masked_unary_operation(umath.tan, 0.0, domain_tan(1.e-35))
arcsin = masked_unary_operation(umath.arcsin, 0.0, domain_check_interval(-1.0, 1.0))
arccos = masked_unary_operation(umath.arccos, 0.0, domain_check_interval(-1.0, 1.0))
arctan = masked_unary_operation(umath.arctan)
# Missing from numeric
arcsinh = masked_unary_operation(umath.arcsinh)
arccosh = masked_unary_operation(umath.arccosh, 1.0, domain_greater_equal(1.0))
arctanh = masked_unary_operation(umath.arctanh, 0.0, domain_check_interval(-1.0+1e-15, 1.0-1e-15))
sinh = masked_unary_operation(umath.sinh)
cosh = masked_unary_operation(umath.cosh)
tanh = masked_unary_operation(umath.tanh)
absolute = masked_unary_operation(umath.absolute)
fabs = masked_unary_operation(umath.fabs)
negative = masked_unary_operation(umath.negative)

def nonzero(a):
    """returns the indices of the elements of a which are not zero
    and not masked
    """
    return numeric.asarray(filled(a, 0).nonzero())

around = masked_unary_operation(fromnumeric.round_)
floor = masked_unary_operation(umath.floor)
ceil = masked_unary_operation(umath.ceil)
logical_not = masked_unary_operation(umath.logical_not)

add = masked_binary_operation(umath.add)
subtract = masked_binary_operation(umath.subtract)
subtract.reduce = None
multiply = masked_binary_operation(umath.multiply, 1, 1)
divide = domained_binary_operation(umath.divide, domain_safe_divide(), 0, 1)
true_divide = domained_binary_operation(umath.true_divide, domain_safe_divide(), 0, 1)
floor_divide = domained_binary_operation(umath.floor_divide, domain_safe_divide(), 0, 1)
remainder = domained_binary_operation(umath.remainder, domain_safe_divide(), 0, 1)
fmod = domained_binary_operation(umath.fmod, domain_safe_divide(), 0, 1)
hypot = masked_binary_operation(umath.hypot)
arctan2 = masked_binary_operation(umath.arctan2, 0.0, 1.0)
arctan2.reduce = None
equal = masked_binary_operation(umath.equal)
equal.reduce = None
not_equal = masked_binary_operation(umath.not_equal)
not_equal.reduce = None
less_equal = masked_binary_operation(umath.less_equal)
less_equal.reduce = None
greater_equal = masked_binary_operation(umath.greater_equal)
greater_equal.reduce = None
less = masked_binary_operation(umath.less)
less.reduce = None
greater = masked_binary_operation(umath.greater)
greater.reduce = None
logical_and = masked_binary_operation(umath.logical_and)
alltrue = masked_binary_operation(umath.logical_and, 1, 1).reduce
logical_or = masked_binary_operation(umath.logical_or)
sometrue = logical_or.reduce
logical_xor = masked_binary_operation(umath.logical_xor)
bitwise_and = masked_binary_operation(umath.bitwise_and)
bitwise_or = masked_binary_operation(umath.bitwise_or)
bitwise_xor = masked_binary_operation(umath.bitwise_xor)

def rank (object):
    return fromnumeric.rank(filled(object))

def shape (object):
    return fromnumeric.shape(filled(object))

def size (object, axis=None):
    return fromnumeric.size(filled(object), axis)

class MaskedArray (object):
    """Arrays with possibly masked values.
       Masked values of 1 exclude the corresponding element from
       any computation.

       Construction:
           x = array(data, dtype=None, copy=True, order=False,
                     mask = nomask, fill_value=None)

       If copy=False, every effort is made not to copy the data:
           If data is a MaskedArray, and argument mask=nomask,
           then the candidate data is data.data and the
           mask used is data.mask. If data is a numeric array,
           it is used as the candidate raw data.
           If dtype is not None and
           is != data.dtype.char then a data copy is required.
           Otherwise, the candidate is used.

       If a data copy is required, raw data stored is the result of:
       numeric.array(data, dtype=dtype.char, copy=copy)

       If mask is nomask there are no masked values. Otherwise mask must
       be convertible to an array of booleans with the same shape as x.

       fill_value is used to fill in masked values when necessary,
       such as when printing and in method/function filled().
       The fill_value is not used for computation within this module.
    """
    __array_priority__ = 10.1
    def __init__(self, data, dtype=None, copy=True, order=False,
                 mask=nomask, fill_value=None):
        """array(data, dtype=None, copy=True, order=False, mask=nomask, fill_value=None)
           If data already a numeric array, its dtype becomes the default value of dtype.
        """
        if dtype is None:
            tc = None
        else:
            tc = numeric.dtype(dtype)
        need_data_copied = copy
        if isinstance(data, MaskedArray):
            c = data.data
            if tc is None:
                tc = c.dtype
            elif tc != c.dtype:
                need_data_copied = True
            if mask is nomask:
                mask = data.mask
            elif mask is not nomask: #attempting to change the mask
                need_data_copied = True

        elif isinstance(data, ndarray):
            c = data
            if tc is None:
                tc = c.dtype
            elif tc != c.dtype:
                need_data_copied = True
        else:
            need_data_copied = False #because I'll do it now
            c = numeric.array(data, dtype=tc, copy=True, order=order)
            tc = c.dtype

        if need_data_copied:
            if tc == c.dtype:
                self._data = numeric.array(c, dtype=tc, copy=True, order=order)
            else:
                self._data = c.astype(tc)
        else:
            self._data = c

        if mask is nomask:
            self._mask = nomask
            self._shared_mask = 0
        else:
            self._mask = make_mask (mask)
            if self._mask is nomask:
                self._shared_mask = 0
            else:
                self._shared_mask = (self._mask is mask)
                nm = size(self._mask)
                nd = size(self._data)
                if nm != nd:
                    if nm == 1:
                        self._mask = fromnumeric.resize(self._mask, self._data.shape)
                        self._shared_mask = 0
                    elif nd == 1:
                        self._data = fromnumeric.resize(self._data, self._mask.shape)
                        self._data.shape = self._mask.shape
                    else:
                        raise MAError("Mask and data not compatible.")
                elif nm == 1 and shape(self._mask) != shape(self._data):
                    self.unshare_mask()
                    self._mask.shape = self._data.shape

        self.set_fill_value(fill_value)

    def __array__ (self, t=None, context=None):
        "Special hook for numeric. Converts to numeric if possible."
        if self._mask is not nomask:
            if fromnumeric.ravel(self._mask).any():
                if context is None:
                    warnings.warn("Cannot automatically convert masked array to "\
                                  "numeric because data\n    is masked in one or "\
                                  "more locations.");
                    return self._data
                    #raise MAError(
                    #      """Cannot automatically convert masked array to numeric because data
                    #      is masked in one or more locations.
                    #      """)
                else:
                    func, args, i = context
                    fills = ufunc_fills.get(func)
                    if fills is None:
                        raise MAError("%s not known to ma" % func)
                    return self.filled(fills[i])
            else:  # Mask is all false
                   # Optimize to avoid future invocations of this section.
                self._mask = nomask
                self._shared_mask = 0
        if t:
            return self._data.astype(t)
        else:
            return self._data

    def __array_wrap__ (self, array, context=None):
        """Special hook for ufuncs.

        Wraps the numpy array and sets the mask according to
        context.
        """
        if context is None:
            return MaskedArray(array, copy=False, mask=nomask)
        func, args = context[:2]
        domain = ufunc_domain[func]
        m = reduce(mask_or, [getmask(a) for a in args])
        if domain is not None:
            m = mask_or(m, domain(*[getattr(a, '_data', a)
                                    for a in args]))
        if m is not nomask:
            try:
                shape = array.shape
            except AttributeError:
                pass
            else:
                if m.shape != shape:
                    m = reduce(mask_or, [getmaskarray(a) for a in args])

        return MaskedArray(array, copy=False, mask=m)

    def _get_shape(self):
        "Return the current shape."
        return self._data.shape

    def _set_shape (self, newshape):
        "Set the array's shape."
        self._data.shape = newshape
        if self._mask is not nomask:
            self._mask = self._mask.copy()
            self._mask.shape = newshape

    def _get_flat(self):
        """Calculate the flat value.
        """
        if self._mask is nomask:
            return masked_array(self._data.ravel(), mask=nomask,
                                fill_value = self.fill_value())
        else:
            return masked_array(self._data.ravel(),
                                mask=self._mask.ravel(),
                                fill_value = self.fill_value())

    def _set_flat (self, value):
        "x.flat = value"
        y = self.ravel()
        y[:] = value

    def _get_real(self):
        "Get the real part of a complex array."
        if self._mask is nomask:
            return masked_array(self._data.real, mask=nomask,
                            fill_value = self.fill_value())
        else:
            return masked_array(self._data.real, mask=self._mask,
                            fill_value = self.fill_value())

    def _set_real (self, value):
        "x.real = value"
        y = self.real
        y[...] = value

    def _get_imaginary(self):
        "Get the imaginary part of a complex array."
        if self._mask is nomask:
            return masked_array(self._data.imag, mask=nomask,
                            fill_value = self.fill_value())
        else:
            return masked_array(self._data.imag, mask=self._mask,
                            fill_value = self.fill_value())

    def _set_imaginary (self, value):
        "x.imaginary = value"
        y = self.imaginary
        y[...] = value

    def __str__(self):
        """Calculate the str representation, using masked for fill if
           it is enabled. Otherwise fill with fill value.
        """
        if masked_print_option.enabled():
            f = masked_print_option
            # XXX: Without the following special case masked
            # XXX: would print as "[--]", not "--". Can we avoid
            # XXX: checks for masked by choosing a different value
            # XXX: for the masked singleton? 2005-01-05 -- sasha
            if self is masked:
                return str(f)
            m = self._mask
            if m is not nomask and m.shape == () and m:
                return str(f)
            # convert to object array to make filled work
            self = self.astype(object)
        else:
            f = self.fill_value()
        res = self.filled(f)
        return str(res)

    def __repr__(self):
        """Calculate the repr representation, using masked for fill if
           it is enabled. Otherwise fill with fill value.
        """
        with_mask = """\
array(data =
 %(data)s,
      mask =
 %(mask)s,
      fill_value=%(fill)s)
"""
        with_mask1 = """\
array(data = %(data)s,
      mask = %(mask)s,
      fill_value=%(fill)s)
"""
        without_mask = """array(
 %(data)s)"""
        without_mask1 = """array(%(data)s)"""

        n = len(self.shape)
        if self._mask is nomask:
            if n <= 1:
                return without_mask1 % {'data':str(self.filled())}
            return without_mask % {'data':str(self.filled())}
        else:
            if n <= 1:
                return with_mask % {
                    'data': str(self.filled()),
                    'mask': str(self._mask),
                    'fill': str(self.fill_value())
                    }
            return with_mask % {
                'data': str(self.filled()),
                'mask': str(self._mask),
                'fill': str(self.fill_value())
                }
        without_mask1 = """array(%(data)s)"""
        if self._mask is nomask:
            return without_mask % {'data':str(self.filled())}
        else:
            return with_mask % {
                'data': str(self.filled()),
                'mask': str(self._mask),
                'fill': str(self.fill_value())
                }

    def __float__(self):
        "Convert self to float."
        self.unmask()
        if self._mask is not nomask:
            raise MAError('Cannot convert masked element to a Python float.')
        return float(self.data.item())

    def __int__(self):
        "Convert self to int."
        self.unmask()
        if self._mask is not nomask:
            raise MAError('Cannot convert masked element to a Python int.')
        return int(self.data.item())

    def __getitem__(self, i):
        "Get item described by i. Not a copy as in previous versions."
        self.unshare_mask()
        m = self._mask
        dout = self._data[i]
        if m is nomask:
            try:
                if dout.size == 1:
                    return dout
                else:
                    return masked_array(dout, fill_value=self._fill_value)
            except AttributeError:
                return dout
        mi = m[i]
        if mi.size == 1:
            if mi:
                return masked
            else:
                return dout
        else:
            return masked_array(dout, mi, fill_value=self._fill_value)

# --------
# setitem and setslice notes
# note that if value is masked, it means to mask those locations.
# setting a value changes the mask to match the value in those locations.

    def __setitem__(self, index, value):
        "Set item described by index. If value is masked, mask those locations."
        d = self._data
        if self is masked:
            raise MAError('Cannot alter masked elements.')
        if value is masked:
            if self._mask is nomask:
                self._mask = make_mask_none(d.shape)
                self._shared_mask = False
            else:
                self.unshare_mask()
            self._mask[index] = True
            return
        m = getmask(value)
        value = filled(value).astype(d.dtype)
        d[index] = value
        if m is nomask:
            if self._mask is not nomask:
                self.unshare_mask()
                self._mask[index] = False
        else:
            if self._mask is nomask:
                self._mask = make_mask_none(d.shape)
                self._shared_mask = True
            else:
                self.unshare_mask()
            self._mask[index] = m

    def __bool__(self):
        """returns true if any element is non-zero or masked

        """
        # XXX: This changes bool conversion logic from MA.
        # XXX: In MA bool(a) == len(a) != 0, but in numpy
        # XXX: scalars do not have len
        m = self._mask
        d = self._data
        return bool(m is not nomask and m.any()
                    or d is not nomask and d.any())

    def __len__ (self):
        """Return length of first dimension. This is weird but Python's
         slicing behavior depends on it."""
        return len(self._data)

    def __and__(self, other):
        "Return bitwise_and"
        return bitwise_and(self, other)

    def __or__(self, other):
        "Return bitwise_or"
        return bitwise_or(self, other)

    def __xor__(self, other):
        "Return bitwise_xor"
        return bitwise_xor(self, other)

    __rand__ = __and__
    __ror__ = __or__
    __rxor__ = __xor__

    def __abs__(self):
        "Return absolute(self)"
        return absolute(self)

    def __neg__(self):
        "Return negative(self)"
        return negative(self)

    def __pos__(self):
        "Return array(self)"
        return array(self)

    def __add__(self, other):
        "Return add(self, other)"
        return add(self, other)

    __radd__ = __add__

    def __mod__ (self, other):
        "Return remainder(self, other)"
        return remainder(self, other)

    def __rmod__ (self, other):
        "Return remainder(other, self)"
        return remainder(other, self)

    def __lshift__ (self, n):
        return left_shift(self, n)

    def __rshift__ (self, n):
        return right_shift(self, n)

    def __sub__(self, other):
        "Return subtract(self, other)"
        return subtract(self, other)

    def __rsub__(self, other):
        "Return subtract(other, self)"
        return subtract(other, self)

    def __mul__(self, other):
        "Return multiply(self, other)"
        return multiply(self, other)

    __rmul__ = __mul__

    def __div__(self, other):
        "Return divide(self, other)"
        return divide(self, other)

    def __rdiv__(self, other):
        "Return divide(other, self)"
        return divide(other, self)

    def __truediv__(self, other):
        "Return divide(self, other)"
        return true_divide(self, other)

    def __rtruediv__(self, other):
        "Return divide(other, self)"
        return true_divide(other, self)

    def __floordiv__(self, other):
        "Return divide(self, other)"
        return floor_divide(self, other)

    def __rfloordiv__(self, other):
        "Return divide(other, self)"
        return floor_divide(other, self)

    def __pow__(self, other, third=None):
        "Return power(self, other, third)"
        return power(self, other, third)

    def __sqrt__(self):
        "Return sqrt(self)"
        return sqrt(self)

    def __iadd__(self, other):
        "Add other to self in place."
        t = self._data.dtype.char
        f = filled(other, 0)
        t1 = f.dtype.char
        if t == t1:
            pass
        elif t in typecodes['Integer']:
            if t1 in typecodes['Integer']:
                f = f.astype(t)
            else:
                raise TypeError('Incorrect type for in-place operation.')
        elif t in typecodes['Float']:
            if t1 in typecodes['Integer']:
                f = f.astype(t)
            elif t1 in typecodes['Float']:
                f = f.astype(t)
            else:
                raise TypeError('Incorrect type for in-place operation.')
        elif t in typecodes['Complex']:
            if t1 in typecodes['Integer']:
                f = f.astype(t)
            elif t1 in typecodes['Float']:
                f = f.astype(t)
            elif t1 in typecodes['Complex']:
                f = f.astype(t)
            else:
                raise TypeError('Incorrect type for in-place operation.')
        else:
            raise TypeError('Incorrect type for in-place operation.')

        if self._mask is nomask:
            self._data += f
            m = getmask(other)
            self._mask = m
            self._shared_mask = m is not nomask
        else:
            result = add(self, masked_array(f, mask=getmask(other)))
            self._data = result.data
            self._mask = result.mask
            self._shared_mask = 1
        return self

    def __imul__(self, other):
        "Add other to self in place."
        t = self._data.dtype.char
        f = filled(other, 0)
        t1 = f.dtype.char
        if t == t1:
            pass
        elif t in typecodes['Integer']:
            if t1 in typecodes['Integer']:
                f = f.astype(t)
            else:
                raise TypeError('Incorrect type for in-place operation.')
        elif t in typecodes['Float']:
            if t1 in typecodes['Integer']:
                f = f.astype(t)
            elif t1 in typecodes['Float']:
                f = f.astype(t)
            else:
                raise TypeError('Incorrect type for in-place operation.')
        elif t in typecodes['Complex']:
            if t1 in typecodes['Integer']:
                f = f.astype(t)
            elif t1 in typecodes['Float']:
                f = f.astype(t)
            elif t1 in typecodes['Complex']:
                f = f.astype(t)
            else:
                raise TypeError('Incorrect type for in-place operation.')
        else:
            raise TypeError('Incorrect type for in-place operation.')

        if self._mask is nomask:
            self._data *= f
            m = getmask(other)
            self._mask = m
            self._shared_mask = m is not nomask
        else:
            result = multiply(self, masked_array(f, mask=getmask(other)))
            self._data = result.data
            self._mask = result.mask
            self._shared_mask = 1
        return self

    def __isub__(self, other):
        "Subtract other from self in place."
        t = self._data.dtype.char
        f = filled(other, 0)
        t1 = f.dtype.char
        if t == t1:
            pass
        elif t in typecodes['Integer']:
            if t1 in typecodes['Integer']:
                f = f.astype(t)
            else:
                raise TypeError('Incorrect type for in-place operation.')
        elif t in typecodes['Float']:
            if t1 in typecodes['Integer']:
                f = f.astype(t)
            elif t1 in typecodes['Float']:
                f = f.astype(t)
            else:
                raise TypeError('Incorrect type for in-place operation.')
        elif t in typecodes['Complex']:
            if t1 in typecodes['Integer']:
                f = f.astype(t)
            elif t1 in typecodes['Float']:
                f = f.astype(t)
            elif t1 in typecodes['Complex']:
                f = f.astype(t)
            else:
                raise TypeError('Incorrect type for in-place operation.')
        else:
            raise TypeError('Incorrect type for in-place operation.')

        if self._mask is nomask:
            self._data -= f
            m = getmask(other)
            self._mask = m
            self._shared_mask = m is not nomask
        else:
            result = subtract(self, masked_array(f, mask=getmask(other)))
            self._data = result.data
            self._mask = result.mask
            self._shared_mask = 1
        return self



    def __idiv__(self, other):
        "Divide self by other in place."
        t = self._data.dtype.char
        f = filled(other, 0)
        t1 = f.dtype.char
        if t == t1:
            pass
        elif t in typecodes['Integer']:
            if t1 in typecodes['Integer']:
                f = f.astype(t)
            else:
                raise TypeError('Incorrect type for in-place operation.')
        elif t in typecodes['Float']:
            if t1 in typecodes['Integer']:
                f = f.astype(t)
            elif t1 in typecodes['Float']:
                f = f.astype(t)
            else:
                raise TypeError('Incorrect type for in-place operation.')
        elif t in typecodes['Complex']:
            if t1 in typecodes['Integer']:
                f = f.astype(t)
            elif t1 in typecodes['Float']:
                f = f.astype(t)
            elif t1 in typecodes['Complex']:
                f = f.astype(t)
            else:
                raise TypeError('Incorrect type for in-place operation.')
        else:
            raise TypeError('Incorrect type for in-place operation.')
        mo = getmask(other)
        result = divide(self, masked_array(f, mask=mo))
        self._data = result.data
        dm = result.raw_mask()
        if dm is not self._mask:
            self._mask = dm
            self._shared_mask = 1
        return self

    def __eq__(self, other):
        return equal(self,other)

    def __ne__(self, other):
        return not_equal(self,other)

    def __lt__(self, other):
        return less(self,other)

    def __le__(self, other):
        return less_equal(self,other)

    def __gt__(self, other):
        return greater(self,other)

    def __ge__(self, other):
        return greater_equal(self,other)

    def astype (self, tc):
        "return self as array of given type."
        d = self._data.astype(tc)
        return array(d, mask=self._mask)

    def byte_swapped(self):
        """Returns the raw data field, byte_swapped. Included for consistency
         with numeric but doesn't make sense in this context.
        """
        return self._data.byte_swapped()

    def compressed (self):
        "A 1-D array of all the non-masked data."
        d = fromnumeric.ravel(self._data)
        if self._mask is nomask:
            return array(d)
        else:
            m = 1 - fromnumeric.ravel(self._mask)
            c = fromnumeric.compress(m, d)
            return array(c, copy=0)

    def count (self, axis = None):
        "Count of the non-masked elements in a, or along a certain axis."
        m = self._mask
        s = self._data.shape
        ls = len(s)
        if m is nomask:
            if ls == 0:
                return 1
            if ls == 1:
                return s[0]
            if axis is None:
                return reduce(lambda x, y:x*y, s)
            else:
                n = s[axis]
                t = list(s)
                del t[axis]
                return ones(t) * n
        if axis is None:
            w = fromnumeric.ravel(m).astype(int)
            n1 = size(w)
            if n1 == 1:
                n2 = w[0]
            else:
                n2 = umath.add.reduce(w)
            return n1 - n2
        else:
            n1 = size(m, axis)
            n2 = sum(m.astype(int), axis)
            return n1 - n2

    def dot (self, other):
        "s.dot(other) = innerproduct(s, other)"
        return innerproduct(self, other)

    def fill_value(self):
        "Get the current fill value."
        return self._fill_value

    def filled (self, fill_value=None):
        """A numeric array with masked values filled. If fill_value is None,
           use self.fill_value().

           If mask is nomask, copy data only if not contiguous.
           Result is always a contiguous, numeric array.
# Is contiguous really necessary now?
        """
        d = self._data
        m = self._mask
        if m is nomask:
            if d.flags['CONTIGUOUS']:
                return d
            else:
                return d.copy()
        else:
            if fill_value is None:
                value = self._fill_value
            else:
                value = fill_value

            if self is masked:
                result = numeric.array(value)
            else:
                try:
                    result = numeric.array(d, dtype=d.dtype, copy=1)
                    result[m] = value
                except (TypeError, AttributeError):
                    #ok, can't put that value in here
                    value = numeric.array(value, dtype=object)
                    d = d.astype(object)
                    result = fromnumeric.choose(m, (d, value))
            return result

    def ids (self):
        """Return the ids of the data and mask areas"""
        return (id(self._data), id(self._mask))

    def iscontiguous (self):
        "Is the data contiguous?"
        return self._data.flags['CONTIGUOUS']

    def itemsize(self):
        "Item size of each data item."
        return self._data.itemsize


    def outer(self, other):
        "s.outer(other) = outerproduct(s, other)"
        return outerproduct(self, other)

    def put (self, values):
        """Set the non-masked entries of self to filled(values).
           No change to mask
        """
        iota = numeric.arange(self.size)
        d = self._data
        if self._mask is nomask:
            ind = iota
        else:
            ind = fromnumeric.compress(1 - self._mask, iota)
        d[ind] =  filled(values).astype(d.dtype)

    def putmask (self, values):
        """Set the masked entries of self to filled(values).
           Mask changed to nomask.
        """
        d = self._data
        if self._mask is not nomask:
            d[self._mask] = filled(values).astype(d.dtype)
            self._shared_mask = 0
            self._mask = nomask

    def ravel (self):
        """Return a 1-D view of self."""
        if self._mask is nomask:
            return masked_array(self._data.ravel())
        else:
            return masked_array(self._data.ravel(), self._mask.ravel())

    def raw_data (self):
        """ Obsolete; use data property instead.
            The raw data; portions may be meaningless.
            May be noncontiguous. Expert use only."""
        return self._data
    data = property(fget=raw_data,
           doc="The data, but values at masked locations are meaningless.")

    def raw_mask (self):
        """ Obsolete; use mask property instead.
            May be noncontiguous. Expert use only.
        """
        return self._mask
    mask = property(fget=raw_mask,
           doc="The mask, may be nomask. Values where mask true are meaningless.")

    def reshape (self, *s):
        """This array reshaped to shape s"""
        d = self._data.reshape(*s)
        if self._mask is nomask:
            return masked_array(d)
        else:
            m = self._mask.reshape(*s)
        return masked_array(d, m)

    def set_fill_value (self, v=None):
        "Set the fill value to v. Omit v to restore default."
        if v is None:
            v = default_fill_value (self.raw_data())
        self._fill_value = v

    def _get_ndim(self):
        return self._data.ndim
    ndim = property(_get_ndim, doc=numeric.ndarray.ndim.__doc__)

    def _get_size (self):
        return self._data.size
    size = property(fget=_get_size, doc="Number of elements in the array.")
## CHECK THIS: signature of numeric.array.size?

    def _get_dtype(self):
        return self._data.dtype
    dtype = property(fget=_get_dtype, doc="type of the array elements.")

    def item(self, *args):
        "Return Python scalar if possible"
        if self._mask is not nomask:
            m = self._mask.item(*args)
            try:
                if m[0]:
                    return masked
            except IndexError:
                return masked
        return self._data.item(*args)

    def itemset(self, *args):
        "Set Python scalar into array"
        item = args[-1]
        args = args[:-1]
        self[args] = item

    def tolist(self, fill_value=None):
        "Convert to list"
        return self.filled(fill_value).tolist()

    def tostring(self, fill_value=None):
        "Convert to string"
        return self.filled(fill_value).tostring()

    def unmask (self):
        "Replace the mask by nomask if possible."
        if self._mask is nomask: return
        m = make_mask(self._mask, flag=1)
        if m is nomask:
            self._mask = nomask
            self._shared_mask = 0

    def unshare_mask (self):
        "If currently sharing mask, make a copy."
        if self._shared_mask:
            self._mask = make_mask (self._mask, copy=1, flag=0)
            self._shared_mask = 0

    def _get_ctypes(self):
        return self._data.ctypes

    def _get_T(self):
        if (self.ndim < 2):
            return self
        return self.transpose()

    shape = property(_get_shape, _set_shape,
           doc = 'tuple giving the shape of the array')

    flat = property(_get_flat, _set_flat,
           doc = 'Access array in flat form.')

    real = property(_get_real, _set_real,
           doc = 'Access the real part of the array')

    imaginary = property(_get_imaginary, _set_imaginary,
           doc = 'Access the imaginary part of the array')

    imag = imaginary

    ctypes = property(_get_ctypes, None, doc="ctypes")

    T = property(_get_T, None, doc="get transpose")

#end class MaskedArray

array = MaskedArray

def isMaskedArray (x):
    "Is x a masked array, that is, an instance of MaskedArray?"
    return isinstance(x, MaskedArray)

isarray = isMaskedArray
isMA = isMaskedArray  #backward compatibility

def allclose (a, b, fill_value=1, rtol=1.e-5, atol=1.e-8):
    """ Returns true if all components of a and b are equal
        subject to given tolerances.
        If fill_value is 1, masked values considered equal.
        If fill_value is 0, masked values considered unequal.
        The relative error rtol should be positive and << 1.0
        The absolute error atol comes into play for those elements
        of b that are very small or zero; it says how small a must be also.
    """
    m = mask_or(getmask(a), getmask(b))
    d1 = filled(a)
    d2 = filled(b)
    x = filled(array(d1, copy=0, mask=m), fill_value).astype(float)
    y = filled(array(d2, copy=0, mask=m), 1).astype(float)
    d = umath.less_equal(umath.absolute(x-y), atol + rtol * umath.absolute(y))
    return fromnumeric.alltrue(fromnumeric.ravel(d))

def allequal (a, b, fill_value=1):
    """
        True if all entries of  a and b are equal, using
        fill_value as a truth value where either or both are masked.
    """
    m = mask_or(getmask(a), getmask(b))
    if m is nomask:
        x = filled(a)
        y = filled(b)
        d = umath.equal(x, y)
        return fromnumeric.alltrue(fromnumeric.ravel(d))
    elif fill_value:
        x = filled(a)
        y = filled(b)
        d = umath.equal(x, y)
        dm = array(d, mask=m, copy=0)
        return fromnumeric.alltrue(fromnumeric.ravel(filled(dm, 1)))
    else:
        return 0

def masked_values (data, value, rtol=1.e-5, atol=1.e-8, copy=1):
    """
       masked_values(data, value, rtol=1.e-5, atol=1.e-8)
       Create a masked array; mask is nomask if possible.
       If copy==0, and otherwise possible, result
       may share data values with original array.
       Let d = filled(data, value). Returns d
       masked where abs(data-value)<= atol + rtol * abs(value)
       if d is of a floating point type. Otherwise returns
       masked_object(d, value, copy)
    """
    abs = umath.absolute
    d = filled(data, value)
    if issubclass(d.dtype.type, numeric.floating):
        m = umath.less_equal(abs(d-value), atol+rtol*abs(value))
        m = make_mask(m, flag=1)
        return array(d, mask = m, copy=copy,
                      fill_value=value)
    else:
        return masked_object(d, value, copy=copy)

def masked_object (data, value, copy=1):
    "Create array masked where exactly data equal to value"
    d = filled(data, value)
    dm = make_mask(umath.equal(d, value), flag=1)
    return array(d, mask=dm, copy=copy, fill_value=value)

def arange(start, stop=None, step=1, dtype=None):
    """Just like range() except it returns a array whose type can be specified
    by the keyword argument dtype.
    """
    return array(numeric.arange(start, stop, step, dtype))

arrayrange = arange

def fromstring (s, t):
    "Construct a masked array from a string. Result will have no mask."
    return masked_array(numeric.fromstring(s, t))

def left_shift (a, n):
    "Left shift n bits"
    m = getmask(a)
    if m is nomask:
        d = umath.left_shift(filled(a), n)
        return masked_array(d)
    else:
        d = umath.left_shift(filled(a, 0), n)
        return masked_array(d, m)

def right_shift (a, n):
    "Right shift n bits"
    m = getmask(a)
    if m is nomask:
        d = umath.right_shift(filled(a), n)
        return masked_array(d)
    else:
        d = umath.right_shift(filled(a, 0), n)
        return masked_array(d, m)

def resize (a, new_shape):
    """resize(a, new_shape) returns a new array with the specified shape.
    The original array's total size can be any size."""
    m = getmask(a)
    if m is not nomask:
        m = fromnumeric.resize(m, new_shape)
    result = array(fromnumeric.resize(filled(a), new_shape), mask=m)
    result.set_fill_value(get_fill_value(a))
    return result

def new_repeat(a, repeats, axis=None):
    """repeat elements of a repeats times along axis
       repeats is a sequence of length a.shape[axis]
       telling how many times to repeat each element.
    """
    af = filled(a)
    if isinstance(repeats, int):
        if axis is None:
            num = af.size
        else:
            num = af.shape[axis]
        repeats = tuple([repeats]*num)

    m = getmask(a)
    if m is not nomask:
        m = fromnumeric.repeat(m, repeats, axis)
    d = fromnumeric.repeat(af, repeats, axis)
    result = masked_array(d, m)
    result.set_fill_value(get_fill_value(a))
    return result



def identity(n):
    """identity(n) returns the identity matrix of shape n x n.
    """
    return array(numeric.identity(n))

def indices (dimensions, dtype=None):
    """indices(dimensions,dtype=None) returns an array representing a grid
    of indices with row-only, and column-only variation.
    """
    return array(numeric.indices(dimensions, dtype))

def zeros (shape, dtype=float):
    """zeros(n, dtype=float) =
     an array of all zeros of the given length or shape."""
    return array(numeric.zeros(shape, dtype))

def ones (shape, dtype=float):
    """ones(n, dtype=float) =
     an array of all ones of the given length or shape."""
    return array(numeric.ones(shape, dtype))

def count (a, axis = None):
    "Count of the non-masked elements in a, or along a certain axis."
    a = masked_array(a)
    return a.count(axis)

def power (a, b, third=None):
    "a**b"
    if third is not None:
        raise MAError("3-argument power not supported.")
    ma = getmask(a)
    mb = getmask(b)
    m = mask_or(ma, mb)
    fa = filled(a, 1)
    fb = filled(b, 1)
    if fb.dtype.char in typecodes["Integer"]:
        return masked_array(umath.power(fa, fb), m)
    md = make_mask(umath.less(fa, 0), flag=1)
    m = mask_or(m, md)
    if m is nomask:
        return masked_array(umath.power(fa, fb))
    else:
        fa = numeric.where(m, 1, fa)
        return masked_array(umath.power(fa, fb), m)

def masked_array (a, mask=nomask, fill_value=None):
    """masked_array(a, mask=nomask) =
       array(a, mask=mask, copy=0, fill_value=fill_value)
    """
    return array(a, mask=mask, copy=0, fill_value=fill_value)

def sum (target, axis=None, dtype=None):
    if axis is None:
        target = ravel(target)
        axis = 0
    return add.reduce(target, axis, dtype)

def product (target, axis=None, dtype=None):
    if axis is None:
        target = ravel(target)
        axis = 0
    return multiply.reduce(target, axis, dtype)

def new_average (a, axis=None, weights=None, returned = 0):
    """average(a, axis=None, weights=None)
       Computes average along indicated axis.
       If axis is None, average over the entire array
       Inputs can be integer or floating types; result is of type float.

       If weights are given, result is sum(a*weights,axis=0)/(sum(weights,axis=0)*1.0)
       weights must have a's shape or be the 1-d with length the size
       of a in the given axis.

       If returned, return a tuple: the result and the sum of the weights
       or count of values. Results will have the same shape.

       masked values in the weights will be set to 0.0
    """
    a = masked_array(a)
    mask = a.mask
    ash = a.shape
    if ash == ():
        ash = (1,)
    if axis is None:
        if mask is nomask:
            if weights is None:
                n = add.reduce(a.raw_data().ravel())
                d = reduce(lambda x, y: x * y, ash, 1.0)
            else:
                w = filled(weights, 0.0).ravel()
                n = umath.add.reduce(a.raw_data().ravel() * w)
                d = umath.add.reduce(w)
                del w
        else:
            if weights is None:
                n = add.reduce(a.ravel())
                w = fromnumeric.choose(mask, (1.0, 0.0)).ravel()
                d = umath.add.reduce(w)
                del w
            else:
                w = array(filled(weights, 0.0), float, mask=mask).ravel()
                n = add.reduce(a.ravel() * w)
                d = add.reduce(w)
                del w
    else:
        if mask is nomask:
            if weights is None:
                d = ash[axis] * 1.0
                n = umath.add.reduce(a.raw_data(), axis)
            else:
                w = filled(weights, 0.0)
                wsh = w.shape
                if wsh == ():
                    wsh = (1,)
                if wsh == ash:
                    w = numeric.array(w, float, copy=0)
                    n = add.reduce(a*w, axis)
                    d = add.reduce(w, axis)
                    del w
                elif wsh == (ash[axis],):
                    r = [newaxis]*len(ash)
                    r[axis] = slice(None, None, 1)
                    w = eval ("w["+ repr(tuple(r)) + "] * ones(ash, float)")
                    n = add.reduce(a*w, axis)
                    d = add.reduce(w, axis)
                    del w, r
                else:
                    raise ValueError('average: weights wrong shape.')
        else:
            if weights is None:
                n = add.reduce(a, axis)
                w = numeric.choose(mask, (1.0, 0.0))
                d = umath.add.reduce(w, axis)
                del w
            else:
                w = filled(weights, 0.0)
                wsh = w.shape
                if wsh == ():
                    wsh = (1,)
                if wsh == ash:
                    w = array(w, float, mask=mask, copy=0)
                    n = add.reduce(a*w, axis)
                    d = add.reduce(w, axis)
                elif wsh == (ash[axis],):
                    r = [newaxis]*len(ash)
                    r[axis] = slice(None, None, 1)
                    w = eval ("w["+ repr(tuple(r)) + "] * masked_array(ones(ash, float), mask)")
                    n = add.reduce(a*w, axis)
                    d = add.reduce(w, axis)
                else:
                    raise ValueError('average: weights wrong shape.')
                del w
    #print n, d, repr(mask), repr(weights)
    if n is masked or d is masked: return masked
    result = divide (n, d)
    del n

    if isinstance(result, MaskedArray):
        result.unmask()
        if returned:
            if not isinstance(d, MaskedArray):
                d = masked_array(d)
            if not d.shape == result.shape:
                d = ones(result.shape, float) * d
            d.unmask()
    if returned:
        return result, d
    else:
        return result

def where (condition, x, y):
    """where(condition, x, y) is x where condition is nonzero, y otherwise.
       condition must be convertible to an integer array.
       Answer is always the shape of condition.
       The type depends on x and y. It is integer if both x and y are
       the value masked.
    """
    fc = filled(not_equal(condition, 0), 0)
    xv = filled(x)
    xm = getmask(x)
    yv = filled(y)
    ym = getmask(y)
    d = numeric.choose(fc, (yv, xv))
    md = numeric.choose(fc, (ym, xm))
    m = getmask(condition)
    m = make_mask(mask_or(m, md), copy=0, flag=1)
    return masked_array(d, m)

def choose (indices, t, out=None, mode='raise'):
    "Returns array shaped like indices with elements chosen from t"
    def fmask (x):
        if x is masked: return 1
        return filled(x)
    def nmask (x):
        if x is masked: return 1
        m = getmask(x)
        if m is nomask: return 0
        return m
    c = filled(indices, 0)
    masks = [nmask(x) for x in t]
    a = [fmask(x) for x in t]
    d = numeric.choose(c, a)
    m = numeric.choose(c, masks)
    m = make_mask(mask_or(m, getmask(indices)), copy=0, flag=1)
    return masked_array(d, m)

def masked_where(condition, x, copy=1):
    """Return x as an array masked where condition is true.
       Also masked where x or condition masked.
    """
    cm = filled(condition,1)
    m = mask_or(getmask(x), cm)
    return array(filled(x), copy=copy, mask=m)

def masked_greater(x, value, copy=1):
    "masked_greater(x, value) = x masked where x > value"
    return masked_where(greater(x, value), x, copy)

def masked_greater_equal(x, value, copy=1):
    "masked_greater_equal(x, value) = x masked where x >= value"
    return masked_where(greater_equal(x, value), x, copy)

def masked_less(x, value, copy=1):
    "masked_less(x, value) = x masked where x < value"
    return masked_where(less(x, value), x, copy)

def masked_less_equal(x, value, copy=1):
    "masked_less_equal(x, value) = x masked where x <= value"
    return masked_where(less_equal(x, value), x, copy)

def masked_not_equal(x, value, copy=1):
    "masked_not_equal(x, value) = x masked where x != value"
    d = filled(x, 0)
    c = umath.not_equal(d, value)
    m = mask_or(c, getmask(x))
    return array(d, mask=m, copy=copy)

def masked_equal(x, value, copy=1):
    """masked_equal(x, value) = x masked where x == value
       For floating point consider masked_values(x, value) instead.
    """
    d = filled(x, 0)
    c = umath.equal(d, value)
    m = mask_or(c, getmask(x))
    return array(d, mask=m, copy=copy)

def masked_inside(x, v1, v2, copy=1):
    """x with mask of all values of x that are inside [v1,v2]
       v1 and v2 can be given in either order.
    """
    if v2 < v1:
        t = v2
        v2 = v1
        v1 = t
    d = filled(x, 0)
    c = umath.logical_and(umath.less_equal(d, v2), umath.greater_equal(d, v1))
    m = mask_or(c, getmask(x))
    return array(d, mask = m, copy=copy)

def masked_outside(x, v1, v2, copy=1):
    """x with mask of all values of x that are outside [v1,v2]
       v1 and v2 can be given in either order.
    """
    if v2 < v1:
        t = v2
        v2 = v1
        v1 = t
    d = filled(x, 0)
    c = umath.logical_or(umath.less(d, v1), umath.greater(d, v2))
    m = mask_or(c, getmask(x))
    return array(d, mask = m, copy=copy)

def reshape (a, *newshape):
    "Copy of a with a new shape."
    m = getmask(a)
    d = filled(a).reshape(*newshape)
    if m is nomask:
        return masked_array(d)
    else:
        return masked_array(d, mask=numeric.reshape(m, *newshape))

def ravel (a):
    "a as one-dimensional, may share data and mask"
    m = getmask(a)
    d = fromnumeric.ravel(filled(a))
    if m is nomask:
        return masked_array(d)
    else:
        return masked_array(d, mask=numeric.ravel(m))

def concatenate (arrays, axis=0):
    "Concatenate the arrays along the given axis"
    d = []
    for x in arrays:
        d.append(filled(x))
    d = numeric.concatenate(d, axis)
    for x in arrays:
        if getmask(x) is not nomask: break
    else:
        return masked_array(d)
    dm = []
    for x in arrays:
        dm.append(getmaskarray(x))
    dm = numeric.concatenate(dm, axis)
    return masked_array(d, mask=dm)

def swapaxes (a, axis1, axis2):
    m = getmask(a)
    d = masked_array(a).data
    if m is nomask:
        return masked_array(data=numeric.swapaxes(d, axis1, axis2))
    else:
        return masked_array(data=numeric.swapaxes(d, axis1, axis2),
                            mask=numeric.swapaxes(m, axis1, axis2),)


def new_take (a, indices, axis=None, out=None, mode='raise'):
    "returns selection of items from a."
    m = getmask(a)
    # d = masked_array(a).raw_data()
    d = masked_array(a).data
    if m is nomask:
        return masked_array(numeric.take(d, indices, axis))
    else:
        return masked_array(numeric.take(d, indices, axis),
                     mask = numeric.take(m, indices, axis))

def transpose(a, axes=None):
    "reorder dimensions per tuple axes"
    m = getmask(a)
    d = filled(a)
    if m is nomask:
        return masked_array(numeric.transpose(d, axes))
    else:
        return masked_array(numeric.transpose(d, axes),
                     mask = numeric.transpose(m, axes))


def put(a, indices, values, mode='raise'):
    """sets storage-indexed locations to corresponding values.

    Values and indices are filled if necessary.

    """
    d = a.raw_data()
    ind = filled(indices)
    v = filled(values)
    numeric.put (d, ind, v)
    m = getmask(a)
    if m is not nomask:
        a.unshare_mask()
        numeric.put(a.raw_mask(), ind, 0)

def putmask(a, mask, values):
    "putmask(a, mask, values) sets a where mask is true."
    if mask is nomask:
        return
    numeric.putmask(a.raw_data(), mask, values)
    m = getmask(a)
    if m is nomask: return
    a.unshare_mask()
    numeric.putmask(a.raw_mask(), mask, 0)

def inner(a, b):
    """inner(a,b) returns the dot product of two arrays, which has
    shape a.shape[:-1] + b.shape[:-1] with elements computed by summing the
    product of the elements from the last dimensions of a and b.
    Masked elements are replace by zeros.
    """
    fa = filled(a, 0)
    fb = filled(b, 0)
    if len(fa.shape) == 0: fa.shape = (1,)
    if len(fb.shape) == 0: fb.shape = (1,)
    return masked_array(numeric.inner(fa, fb))

innerproduct = inner

def outer(a, b):
    """outer(a,b) = {a[i]*b[j]}, has shape (len(a),len(b))"""
    fa = filled(a, 0).ravel()
    fb = filled(b, 0).ravel()
    d = numeric.outer(fa, fb)
    ma = getmask(a)
    mb = getmask(b)
    if ma is nomask and mb is nomask:
        return masked_array(d)
    ma = getmaskarray(a)
    mb = getmaskarray(b)
    m = make_mask(1-numeric.outer(1-ma, 1-mb), copy=0)
    return masked_array(d, m)

outerproduct = outer

def dot(a, b):
    """dot(a,b) returns matrix-multiplication between a and b.  The product-sum
    is over the last dimension of a and the second-to-last dimension of b.
    Masked values are replaced by zeros. See also innerproduct.
    """
    return innerproduct(filled(a, 0), numeric.swapaxes(filled(b, 0), -1, -2))

def compress(condition, x, dimension=-1, out=None):
    """Select those parts of x for which condition is true.
       Masked values in condition are considered false.
    """
    c = filled(condition, 0)
    m = getmask(x)
    if m is not nomask:
        m = numeric.compress(c, m, dimension)
    d = numeric.compress(c, filled(x), dimension)
    return masked_array(d, m)

class _minimum_operation:
    "Object to calculate minima"
    def __init__ (self):
        """minimum(a, b) or minimum(a)
           In one argument case returns the scalar minimum.
        """
        pass

    def __call__ (self, a, b=None):
        "Execute the call behavior."
        if b is None:
            m = getmask(a)
            if m is nomask:
                d = amin(filled(a).ravel())
                return d
            ac = a.compressed()
            if len(ac) == 0:
                return masked
            else:
                return amin(ac.raw_data())
        else:
            return where(less(a, b), a, b)

    def reduce (self, target, axis=0):
        """Reduce target along the given axis."""
        m = getmask(target)
        if m is nomask:
            t = filled(target)
            return masked_array (umath.minimum.reduce (t, axis))
        else:
            t = umath.minimum.reduce(filled(target, minimum_fill_value(target)), axis)
            m = umath.logical_and.reduce(m, axis)
            return masked_array(t, m, get_fill_value(target))

    def outer (self, a, b):
        "Return the function applied to the outer product of a and b."
        ma = getmask(a)
        mb = getmask(b)
        if ma is nomask and mb is nomask:
            m = nomask
        else:
            ma = getmaskarray(a)
            mb = getmaskarray(b)
            m = logical_or.outer(ma, mb)
        d = umath.minimum.outer(filled(a), filled(b))
        return masked_array(d, m)

minimum = _minimum_operation ()

class _maximum_operation:
    "Object to calculate maxima"
    def __init__ (self):
        """maximum(a, b) or maximum(a)
           In one argument case returns the scalar maximum.
        """
        pass

    def __call__ (self, a, b=None):
        "Execute the call behavior."
        if b is None:
            m = getmask(a)
            if m is nomask:
                d = amax(filled(a).ravel())
                return d
            ac = a.compressed()
            if len(ac) == 0:
                return masked
            else:
                return amax(ac.raw_data())
        else:
            return where(greater(a, b), a, b)

    def reduce (self, target, axis=0):
        """Reduce target along the given axis."""
        m = getmask(target)
        if m is nomask:
            t = filled(target)
            return masked_array (umath.maximum.reduce (t, axis))
        else:
            t = umath.maximum.reduce(filled(target, maximum_fill_value(target)), axis)
            m = umath.logical_and.reduce(m, axis)
            return masked_array(t, m, get_fill_value(target))

    def outer (self, a, b):
        "Return the function applied to the outer product of a and b."
        ma = getmask(a)
        mb = getmask(b)
        if ma is nomask and mb is nomask:
            m = nomask
        else:
            ma = getmaskarray(a)
            mb = getmaskarray(b)
            m = logical_or.outer(ma, mb)
        d = umath.maximum.outer(filled(a), filled(b))
        return masked_array(d, m)

maximum = _maximum_operation ()

def sort (x, axis = -1, fill_value=None):
    """If x does not have a mask, return a masked array formed from the
       result of numeric.sort(x, axis).
       Otherwise, fill x with fill_value. Sort it.
       Set a mask where the result is equal to fill_value.
       Note that this may have unintended consequences if the data contains the
       fill value at a non-masked site.

       If fill_value is not given the default fill value for x's type will be
       used.
    """
    if fill_value is None:
        fill_value = default_fill_value (x)
    d = filled(x, fill_value)
    s = fromnumeric.sort(d, axis)
    if getmask(x) is nomask:
        return masked_array(s)
    return masked_values(s, fill_value, copy=0)

def diagonal(a, k = 0, axis1=0, axis2=1):
    """diagonal(a,k=0,axis1=0, axis2=1) = the k'th diagonal of a"""
    d = fromnumeric.diagonal(filled(a), k, axis1, axis2)
    m = getmask(a)
    if m is nomask:
        return masked_array(d, m)
    else:
        return masked_array(d, fromnumeric.diagonal(m, k, axis1, axis2))

def trace (a, offset=0, axis1=0, axis2=1, dtype=None, out=None):
    """trace(a,offset=0, axis1=0, axis2=1) returns the sum along diagonals
    (defined by the last two dimenions) of the array.
    """
    return diagonal(a, offset, axis1, axis2).sum(dtype=dtype)

def argsort (x, axis = -1, out=None, fill_value=None):
    """Treating masked values as if they have the value fill_value,
       return sort indices for sorting along given axis.
       if fill_value is None, use get_fill_value(x)
       Returns a numpy array.
    """
    d = filled(x, fill_value)
    return fromnumeric.argsort(d, axis)

def argmin (x, axis = -1, out=None, fill_value=None):
    """Treating masked values as if they have the value fill_value,
       return indices for minimum values along given axis.
       if fill_value is None, use get_fill_value(x).
       Returns a numpy array if x has more than one dimension.
       Otherwise, returns a scalar index.
    """
    d = filled(x, fill_value)
    return fromnumeric.argmin(d, axis)

def argmax (x, axis = -1, out=None, fill_value=None):
    """Treating masked values as if they have the value fill_value,
       return sort indices for maximum along given axis.
       if fill_value is None, use -get_fill_value(x) if it exists.
       Returns a numpy array if x has more than one dimension.
       Otherwise, returns a scalar index.
    """
    if fill_value is None:
        fill_value = default_fill_value (x)
        try:
            fill_value = - fill_value
        except:
            pass
    d = filled(x, fill_value)
    return fromnumeric.argmax(d, axis)

def fromfunction (f, s):
    """apply f to s to create array as in umath."""
    return masked_array(numeric.fromfunction(f, s))

def asarray(data, dtype=None):
    """asarray(data, dtype) = array(data, dtype, copy=0)
    """
    if isinstance(data, MaskedArray) and \
        (dtype is None or dtype == data.dtype):
        return data
    return array(data, dtype=dtype, copy=0)

# Add methods to support ndarray interface
# XXX: I is better to to change the masked_*_operation adaptors
# XXX: to wrap ndarray methods directly to create ma.array methods.
from types import MethodType
def _m(f):
    return MethodType(f, None, array)
def not_implemented(*args, **kwds):
    raise NotImplementedError("not yet implemented for numpy.ma arrays")
array.all = _m(alltrue)
array.any = _m(sometrue)
array.argmax = _m(argmax)
array.argmin = _m(argmin)
array.argsort = _m(argsort)
array.base = property(_m(not_implemented))
array.byteswap = _m(not_implemented)

def _choose(self, *args, **kwds):
    return choose(self, args)
array.choose = _m(_choose)
del _choose

def _clip(self,a_min,a_max,out=None):
    return MaskedArray(data = self.data.clip(asarray(a_min).data,
                                             asarray(a_max).data),
                       mask = mask_or(self.mask,
                                      mask_or(getmask(a_min),getmask(a_max))))
array.clip = _m(_clip)

def _compress(self, cond, axis=None, out=None):
    return compress(cond, self, axis)
array.compress = _m(_compress)
del _compress

array.conj = array.conjugate = _m(conjugate)
array.copy = _m(not_implemented)

def _cumprod(self, axis=None, dtype=None, out=None):
    m = self.mask
    if m is not nomask:
        m = umath.logical_or.accumulate(self.mask, axis)
    return MaskedArray(data = self.filled(1).cumprod(axis, dtype), mask=m)
array.cumprod = _m(_cumprod)

def _cumsum(self, axis=None, dtype=None, out=None):
    m = self.mask
    if m is not nomask:
        m = umath.logical_or.accumulate(self.mask, axis)
    return MaskedArray(data=self.filled(0).cumsum(axis, dtype), mask=m)
array.cumsum = _m(_cumsum)

array.diagonal = _m(diagonal)
array.dump = _m(not_implemented)
array.dumps = _m(not_implemented)
array.fill = _m(not_implemented)
array.flags = property(_m(not_implemented))
array.flatten = _m(ravel)
array.getfield = _m(not_implemented)

def _max(a, axis=None, out=None):
    if out is not None:
        raise TypeError("Output arrays Unsupported for masked arrays")
    if axis is None:
        return maximum(a)
    else:
        return maximum.reduce(a, axis)
array.max = _m(_max)
del _max
def _min(a, axis=None, out=None):
    if out is not None:
        raise TypeError("Output arrays Unsupported for masked arrays")
    if axis is None:
        return minimum(a)
    else:
        return minimum.reduce(a, axis)
array.min = _m(_min)
del _min
array.mean = _m(new_average)
array.nbytes = property(_m(not_implemented))
array.newbyteorder = _m(not_implemented)
array.nonzero = _m(nonzero)
array.prod = _m(product)

def _ptp(a,axis=None,out=None):
    return a.max(axis,out)-a.min(axis)
array.ptp = _m(_ptp)
array.repeat = _m(new_repeat)
array.resize = _m(resize)
array.searchsorted = _m(not_implemented)
array.setfield = _m(not_implemented)
array.setflags = _m(not_implemented)
array.sort = _m(not_implemented)  # NB: ndarray.sort is inplace

def _squeeze(self):
    try:
        result = MaskedArray(data = self.data.squeeze(),
                             mask = self.mask.squeeze())
    except AttributeError:
        result = _wrapit(self, 'squeeze')
    return result
array.squeeze = _m(_squeeze)

array.strides = property(_m(not_implemented))
array.sum = _m(sum)
def _swapaxes(self,axis1,axis2):
    return MaskedArray(data = self.data.swapaxes(axis1, axis2),
                       mask = self.mask.swapaxes(axis1, axis2))
array.swapaxes = _m(_swapaxes)
array.take = _m(new_take)
array.tofile = _m(not_implemented)
array.trace = _m(trace)
array.transpose = _m(transpose)

def _var(self,axis=None,dtype=None, out=None):
    if axis is None:
        return numeric.asarray(self.compressed()).var()
    a = self.swapaxes(axis,0)
    a = a - a.mean(axis=0)
    a *= a
    a /= a.count(axis=0)
    return a.swapaxes(0,axis).sum(axis)
def _std(self,axis=None, dtype=None, out=None):
    return (self.var(axis,dtype))**0.5
array.var = _m(_var)
array.std = _m(_std)

array.view =  _m(not_implemented)
array.round = _m(around)
del _m, MethodType, not_implemented


masked = MaskedArray(0, int, mask=1)

def repeat(a, repeats, axis=0):
    return new_repeat(a, repeats, axis)

def average(a, axis=0, weights=None, returned=0):
    return new_average(a, axis, weights, returned)

def take(a, indices, axis=0):
    return new_take(a, indices, axis)
