def arctangent(y, x=None, null=0, broadcast=False, rtol=default_rtol, atol=default_atol):
    '''
    arctangent(x) is equivalent to atan(x) except that it also works on sparse arrays.
    arctangent(y,x) is equivalent to atan2(y,x) except that it also works on sparse arrays.

    The optional argument null (default: 0) specifies the result found when y and x both equal 0. If
    null is None, then an error is raised on this condition. Note that if null is not 0, then it is
    more likely that sparse arrays will have to be reified. If null is set to None, then no attempt
    is made to detect null values.

    The optional argument broadcast (default: False) specifies whether numpy-like (True) or
    Mathematica-like (False) broadcasting should be used. Broadcasting resolves ambiguous calls to
    arctangent, such as artangent([a,b,c], [[d,e,f],[g,h,i],[j,k,l]]). If broadcasting is True, 
    arctangent(y,x) behaves like numpy.arctan2(y,x), so [a,b,c] is interpreted like [[a,b,c],
    [a,b,c], [a,b,c]]. If broadcasting is False, [a,b,c] is interpreted like [[a,a,a], [b,b,b],
    [c,c,c]].
    '''
    if sps.issparse(y):
        if x is None:
            y = y.copy()
            y.data = np.arctan(y.data)
            return y
        elif null is not None and null != 0:
            # we need to reify anyway...
            y = y.toarray()
            if sps.issparse(x): x = x.toarray()
        else:
            # anywhere that y is zero must have an arctan of 0 or null (which is 0), so we only have
            # to look at those values that are non-zero in y
            (yr,yc,yv) = sps.find(y)
            xv = np.asarray(x[rr,rc].flat)
            res = y.copy()
            res.data = arctangent(yv, xv, null=null)
            res.eliminate_zeros()
            return res
    elif sps.issparse(x): x = x.toarray()
    # we should start by broadcasting if need be...
    if x is None: res = np.arctan(y)
    else:
        if not broadcast: (y,x) = unbroadcast(y,x)
        res = np.arctan2(y, x)
        # find the zeros, if need-be
        if null is not None:
            if rtol is None: rtol = default_rtol
            if atol is None: atol = default_atol
            # even if null is none, we do this because the rtol and atol may be more lenient than
            # the tolerance used by arctan2.
            z = np.isclose(y, 0, rtol=rtol, atol=atol) & np.isclose(x, 0, rtol=rtol, atol=atol)
            res[z] = null
    return res