Esempio n. 1
0
def idft_map(input_vis,
             shape,
             uv,
             center=(0.0, 0.0) * u.arcsec,
             pixel_size=(1.0, 1.0) * u.arcsec):
    r"""
    Inverse discrete Fourier transform in terms of coordinates returning a 2D real array or image.

    Parameters
    ----------
    input_vis : `numpy.ndarray`
        Array of N `complex` input visibilities
    shape : `float` (m,n)
        The shape of the output arry to create
    uv : `numpy.ndarray`
        Array of 2xN u, v coordinates corresponding to the input visibilities in `input_vis`
    center : `float` (x, y), optional
        Coordinates of the center of the map e.g. ``(0,0)`` or ``[5.0, -2.0]``
    pixel_size : `float` (dx, dy), optional
        The pixel size in x and y directions, need not be square e.g. ``(1, 3)``

    Returns
    -------
    `numpy.ndarray`
        The complex visibilities evaluated at the u, v coordinates

    """
    m, n = shape
    # size = m * n

    y = generate_xy(m, center[1], pixel_size[1])
    x = generate_xy(n, center[0], pixel_size[0])

    x, y = np.meshgrid(x, y)

    # Check units are correct for exp need to be dimensionless and then remove units for speed
    if (uv[0, :] * x[0, 0]).unit == u.dimensionless_unscaled and \
            (uv[1, :] * y[0, 0]).unit == u.dimensionless_unscaled:

        uv = uv.value
        x = x.value
        y = y.value

        image = np.sum(
            (1 / input_vis.size) * input_vis *
            np.exp(2j * np.pi * (x[..., np.newaxis] * uv[np.newaxis, 0, :] +
                                 y[..., np.newaxis] * uv[np.newaxis, 1, :])),
            axis=2)

        return np.real(image)
    else:
        raise UnitsError(
            "Incompatible units on uv {uv.unit} should cancel with xy "
            "to leave a dimensionless quantity")
Esempio n. 2
0
def dft_map(input_array,
            uv,
            center=(0.0, 0.0) * u.arcsec,
            pixel_size=(1.0, 1.0) * u.arcsec):
    r"""
    Discrete Fourier transform in terms of coordinates returning 1-D array complex visibilities.

    Parameters
    ----------
    input_array : `numpy.ndarray`
        Input array to be transformed should be 2D (m, n)
    uv : `numpy.array`
        Array of 2xN u, v coordinates where the visibilities will be evaluated
    center : `float` (x, y), optional
        Coordinates of the center of the map e.g. ``(0,0)`` or ``[5.0, -2.0]``
    pixel_size : `float` (dx, dy), optional
        The pixel size in x and y directions, need not be square e.g. ``(1, 3)``

    Returns
    -------
    `numpy.ndarray`
        Array of N `complex` visibilities evaluated at the u, v coordinates \
        given bu `uv`

    """
    m, n = input_array.shape

    y = generate_xy(m, center[1], pixel_size[1])
    x = generate_xy(n, center[0], pixel_size[0])

    x, y = np.meshgrid(x, y)

    # Check units are correct for exp need to be dimensionless and then remove units for speed
    if (uv[0, :] * x[0, 0]).unit == u.dimensionless_unscaled and \
            (uv[1, :] * y[0, 0]).unit == u.dimensionless_unscaled:

        uv = uv.value
        x = x.value
        y = y.value

        vis = np.sum(input_array[..., np.newaxis] *
                     np.exp(-2j * np.pi *
                            (x[..., np.newaxis] * uv[np.newaxis, 0, :] +
                             y[..., np.newaxis] * uv[np.newaxis, 1, :])),
                     axis=(0, 1))

        return vis
    else:
        raise UnitsError(
            "Incompatible units on uv {uv.unit} should cancel with xy "
            "to leave a dimensionless quantity")
Esempio n. 3
0
        def wrapper(*func_args, **func_kwargs):
            # Bind the arguments to our new function to the
            # signature of the original.
            bound_args = wrapped_signature.bind(*func_args, **func_kwargs)

            # Iterate through the parameters of the original signature
            for param in wrapped_signature.parameters.values():
                # We do not support variable arguments (*args, **kwargs)
                if param.kind in (funcsigs.Parameter.VAR_KEYWORD,
                                  funcsigs.Parameter.VAR_POSITIONAL):
                    continue
                # Catch the (never triggered) case where bind relied on
                #  a default value.
                if (param.name not in bound_args.arguments
                        and param.default is not param.empty):
                    bound_args.arguments[param.name] = param.default

                # Get the value of this parameter (argument to new function)
                arg = bound_args.arguments[param.name]

                # Get target unit, either from decorator kwargs or annotations
                if param.name in self.decorator_kwargs:
                    (target_min, target_max,
                     target_unit) = self.decorator_kwargs[param.name]
                else:
                    continue

                # If the target unit is empty, then no unit was specified
                # so we move past it
                if target_unit is not funcsigs.Parameter.empty:

                    # skip over None values, if desired
                    if arg is None and self.allow_none:
                        continue

                    try:
                        equivalent = arg.unit.is_equivalent(
                            target_unit, equivalencies=self.equivalencies)

                        if not equivalent:
                            raise UnitsError("Argument '{0}' to function '{1}'"
                                             " must be in units convertible to"
                                             " '{2}'.".format(
                                                 param.name,
                                                 wrapped_function.__name__,
                                                 target_unit.to_string()))

                    # Either there is no .unit or no .is_equivalent
                    except AttributeError:
                        if hasattr(arg, "unit"):
                            error_msg = ("a 'unit' attribute without an "
                                         "'is_equivalent' method")
                        else:
                            error_msg = "no 'unit' attribute"
                        raise TypeError(
                            "Argument '{0}' to function '{1}' has {2}. You "
                            "may want to pass in an astropy Quantity "
                            "instead.".format(param.name,
                                              wrapped_function.__name__,
                                              error_msg))

                    # test value range
                    if target_min is not None:
                        quantity = bound_args.arguments[param.name]
                        value = quantity.to(target_unit).value
                        if np.any(value < target_min):
                            raise ValueError(
                                "Argument '{0}' to function '{1}' out of "
                                "range (allowed {2} to {3} {4}).".format(
                                    param.name,
                                    wrapped_function.__name__,
                                    target_min,
                                    target_max,
                                    target_unit,
                                ))

                    if target_max is not None:
                        quantity = bound_args.arguments[param.name]
                        value = quantity.to(target_unit).value
                        if np.any(value > target_max):
                            raise ValueError(
                                "Argument '{0}' to function '{1}' out of "
                                "range (allowed {2} to {3} {4}).".format(
                                    param.name,
                                    wrapped_function.__name__,
                                    target_min,
                                    target_max,
                                    target_unit,
                                ))
                    if self.strip_input_units:
                        bound_args.arguments[param.name] = (
                            bound_args.arguments[param.name].to(
                                target_unit).value)

            # Call the original function with any equivalencies in force.
            with add_enabled_equivalencies(self.equivalencies):
                # result = wrapped_function(*func_args, **func_kwargs)
                result = wrapped_function(*bound_args.args,
                                          **bound_args.kwargs)

            if self.output_unit is not None:
                # test, if return values are tuple-like
                try:
                    # make namedtuples work (as well as tuples)
                    if hasattr(result, '_fields'):
                        cls = result.__class__
                        return cls(*(
                            # r if u is None else Quantity(r, u, subok=True)
                            r if u is None else r * u  # deal with astropy bug
                            for r, u in zip(result, self.output_unit)))
                    else:
                        return tuple(
                            # r if u is None else Quantity(r, u, subok=True)
                            r if u is None else r * u  # deal with astropy bug
                            for r, u in zip(result, self.output_unit))
                except TypeError:

                    return (result if self.output_unit is None else
                            # Quantity(result, self.output_unit, subok=True)
                            result * self.output_unit  # deal with astropy bug
                            )
            else:
                return result
Esempio n. 4
0
def converters_and_unit(function, method, *args):
    """Determine the required converters and the unit of the ufunc result.

    Converters are functions required to convert to a ufunc's expected unit,
    e.g., radian for np.sin; or to ensure units of two inputs are consistent,
    e.g., for np.add.  In these examples, the unit of the result would be
    dimensionless_unscaled for np.sin, and the same consistent unit for np.add.

    Parameters
    ----------
    function : `~numpy.ufunc`
        Numpy universal function
    method : str
        Method with which the function is evaluated, e.g.,
        '__call__', 'reduce', etc.
    *args : Quantity or other ndarray subclass
        Input arguments to the function

    Raises
    ------
    TypeError : when the specified function cannot be used with Quantities
        (e.g., np.logical_or), or when the routine does not know how to handle
        the specified function (in which case an issue should be raised on
        https://github.com/astropy/astropy).
    UnitTypeError : when the conversion to the required (or consistent) units
        is not possible.
    """

    # Check whether we support this ufunc, by getting the helper function
    # (defined in helpers) which returns a list of function(s) that convert the
    # input(s) to the unit required for the ufunc, as well as the unit the
    # result will have (a tuple of units if there are multiple outputs).
    ufunc_helper = UFUNC_HELPERS[function]

    if method == '__call__' or (method == 'outer' and function.nin == 2):
        # Find out the units of the arguments passed to the ufunc; usually,
        # at least one is a quantity, but for two-argument ufuncs, the second
        # could also be a Numpy array, etc.  These are given unit=None.
        units = [getattr(arg, 'unit', None) for arg in args]

        # Determine possible conversion functions, and the result unit.
        converters, result_unit = ufunc_helper(function, *units)

        if any(converter is False for converter in converters):
            # for multi-argument ufuncs with a quantity and a non-quantity,
            # the quantity normally needs to be dimensionless, *except*
            # if the non-quantity can have arbitrary unit, i.e., when it
            # is all zero, infinity or NaN.  In that case, the non-quantity
            # can just have the unit of the quantity
            # (this allows, e.g., `q > 0.` independent of unit)
            try:
                # Don't fold this loop in the test above: this rare case
                # should not make the common case slower.
                for i, converter in enumerate(converters):
                    if converter is not False:
                        continue
                    if can_have_arbitrary_unit(args[i]):
                        converters[i] = None
                    else:
                        raise UnitConversionError(
                            "Can only apply '{}' function to "
                            "dimensionless quantities when other "
                            "argument is not a quantity (unless the "
                            "latter is all zero/infinity/nan)".format(
                                function.__name__))
            except TypeError:
                # _can_have_arbitrary_unit failed: arg could not be compared
                # with zero or checked to be finite. Then, ufunc will fail too.
                raise TypeError(
                    "Unsupported operand type(s) for ufunc {}: "
                    "'{}'".format(
                        function.__name__,
                        ','.join([arg.__class__.__name__ for arg in args])))

        # In the case of np.power and np.float_power, the unit itself needs to
        # be modified by an amount that depends on one of the input values,
        # so we need to treat this as a special case.
        # TODO: find a better way to deal with this.
        if result_unit is False:
            if units[0] is None or units[0] == dimensionless_unscaled:
                result_unit = dimensionless_unscaled
            else:
                if units[1] is None:
                    p = args[1]
                else:
                    p = args[1].to(dimensionless_unscaled).value

                try:
                    result_unit = units[0]**p
                except ValueError as exc:
                    # Changing the unit does not work for, e.g., array-shaped
                    # power, but this is OK if we're (scaled) dimensionless.
                    try:
                        converters[0] = units[0]._get_converter(
                            dimensionless_unscaled)
                    except UnitConversionError:
                        raise exc
                    else:
                        result_unit = dimensionless_unscaled

    else:  # methods for which the unit should stay the same
        nin = function.nin
        unit = getattr(args[0], 'unit', None)
        if method == 'at' and nin <= 2:
            if nin == 1:
                units = [unit]
            else:
                units = [unit, getattr(args[2], 'unit', None)]

            converters, result_unit = ufunc_helper(function, *units)

            # ensure there is no 'converter' for indices (2nd argument)
            converters.insert(1, None)

        elif method in {'reduce', 'accumulate', 'reduceat'} and nin == 2:
            converters, result_unit = ufunc_helper(function, unit, unit)
            converters = converters[:1]
            if method == 'reduceat':
                # add 'scale' for indices (2nd argument)
                converters += [None]

        else:
            if method in {'reduce', 'accumulate', 'reduceat', 'outer'
                          } and nin != 2:
                raise ValueError(
                    f"{method} only supported for binary functions")

            raise TypeError(
                "Unexpected ufunc method {}.  If this should "
                "work, please raise an issue on"
                "https://github.com/astropy/astropy".format(method))

        # for all but __call__ method, scaling is not allowed
        if unit is not None and result_unit is None:
            raise TypeError("Cannot use '{1}' method on ufunc {0} with a "
                            "Quantity instance as the result is not a "
                            "Quantity.".format(function.__name__, method))

        if (converters[0] is not None
                or (unit is not None and unit is not result_unit and
                    (not result_unit.is_equivalent(unit)
                     or result_unit.to(unit) != 1.))):
            # NOTE: this cannot be the more logical UnitTypeError, since
            # then things like np.cumprod will not longer fail (they check
            # for TypeError).
            raise UnitsError(
                "Cannot use '{1}' method on ufunc {0} with a "
                "Quantity instance as it would change the unit.".format(
                    function.__name__, method))

    return converters, result_unit