from __future__ import annotations

import pytest

from pint import DimensionalityError, UnitRegistry
from pint.compat import np
from pint.testsuite import helpers

# Following http://docs.scipy.org/doc/numpy/reference/ufuncs.html

if np:
    pi = np.pi


@helpers.requires_numpy
class TestUFuncs:
    @classmethod
    def setup_class(cls):
        cls.ureg = UnitRegistry(force_ndarray=True)
        cls.Q_ = cls.ureg.Quantity

    @classmethod
    def teardown_class(cls):
        cls.ureg = None
        cls.Q_ = None

    @property
    def qless(self):
        return np.asarray([1.0, 2.0, 3.0, 4.0]) * self.ureg.dimensionless

    @property
    def qs(self):
        return 8 * self.ureg.J

    @property
    def q1(self):
        return np.asarray([1.0, 2.0, 3.0, 4.0]) * self.ureg.J

    @property
    def q2(self):
        return 2 * self.q1

    @property
    def qm(self):
        return np.asarray([1.0, 2.0, 3.0, 4.0]) * self.ureg.m

    @property
    def qi(self):
        return np.asarray([1 + 1j, 2 + 2j, 3 + 3j, 4 + 4j]) * self.ureg.m

    def _test1(
        self, func, ok_with, raise_with=(), output_units="same", results=None, rtol=1e-6
    ):
        """Test function that takes a single argument and returns Quantity.

        Parameters
        ----------
        func :
            function callable.
        ok_with :
            iterables of values that work fine.
        raise_with :
            iterables of values that raise exceptions. (Default value = ())
        output_units :
            units to be used when building results.
            'same': ok_with[n].units (default).
            is float: ok_with[n].units ** output_units.
            None: no output units, the result should be an ndarray.
            Other value will be parsed as unit.
        results :
            iterable of results.
            If None, the result will be obtained by applying
            func to each ok_with value (Default value = None)
        rtol :
            relative tolerance. (Default value = 1e-6)

        Returns
        -------

        """
        if results is None:
            results = [None] * len(ok_with)
        for x1, res in zip(ok_with, results):
            err_msg = f"At {func.__name__} with {x1}"
            if output_units == "same":
                ou = x1.units
            elif isinstance(output_units, (int, float)):
                ou = x1.units**output_units
            else:
                ou = output_units

            qm = func(x1)
            if res is None:
                res = func(x1.magnitude)
                if ou is not None:
                    res = self.Q_(res, ou)

            helpers.assert_quantity_almost_equal(qm, res, rtol=rtol, msg=err_msg)

        for x1 in raise_with:
            with pytest.raises(DimensionalityError):
                func(x1)

    def _testn(self, func, ok_with, raise_with=(), results=None):
        """Test function that takes a single argument and returns and ndarray (not a Quantity)

        Parameters
        ----------
        func :
            function callable.
        ok_with :
            iterables of values that work fine.
        raise_with :
            iterables of values that raise exceptions. (Default value = ())
        results :
            iterable of results.
            If None, the result will be obtained by applying
            func to each ok_with value (Default value = None)

        Returns
        -------

        """
        self._test1(func, ok_with, raise_with, output_units=None, results=results)

    def _test1_2o(
        self,
        func,
        ok_with,
        raise_with=(),
        output_units=("same", "same"),
        results=None,
        rtol=1e-6,
    ):
        """Test functions that takes a single argument and return two Quantities.

        Parameters
        ----------
        func :
            function callable.
        ok_with :
            iterables of values that work fine.
        raise_with :
            iterables of values that raise exceptions. (Default value = ())
        output_units :
            tuple of units to be used when building the result tuple.
            'same': ok_with[n].units (default).
            is float: ok_with[n].units ** output_units.
            None: no output units, the result should be an ndarray.
            Other value will be parsed as unit.
        results :
            iterable of results.
            If None, the result will be obtained by applying
            func to each ok_with value (Default value = None)
        rtol :
            relative tolerance. (Default value = 1e-6)
        "same") :


        Returns
        -------

        """

        if results is None:
            results = [None] * len(ok_with)
        for x1, res in zip(ok_with, results):
            err_msg = f"At {func.__name__} with {x1}"
            qms = func(x1)
            if res is None:
                res = func(x1.magnitude)

            for ndx, (qm, re, ou) in enumerate(zip(qms, res, output_units)):
                if ou == "same":
                    ou = x1.units
                elif isinstance(ou, (int, float)):
                    ou = x1.units**ou

                if ou is not None:
                    re = self.Q_(re, ou)

                helpers.assert_quantity_almost_equal(qm, re, rtol=rtol, msg=err_msg)

        for x1 in raise_with:
            with pytest.raises(ValueError):
                func(x1)

    def _test2(
        self,
        func,
        x1,
        ok_with,
        raise_with=(),
        output_units="same",
        rtol=1e-6,
        convert2=True,
    ):
        """Test function that takes two arguments and return a Quantity.

        Parameters
        ----------
        func :
            function callable.
        x1 :
            first argument of func.
        ok_with :
            iterables of values that work fine.
        raise_with :
            iterables of values that raise exceptions. (Default value = ())
        output_units :
            units to be used when building results.
            'same': x1.units (default).
            'prod': x1.units * ok_with[n].units
            'div': x1.units / ok_with[n].units
            'second': x1.units * ok_with[n]
            None: no output units, the result should be an ndarray.
            Other value will be parsed as unit.
        rtol :
            relative tolerance. (Default value = 1e-6)
        convert2 :
            if the ok_with[n] should be converted to x1.units. (Default value = True)

        Returns
        -------

        """
        for x2 in ok_with:
            err_msg = f"At {func.__name__} with {x1} and {x2}"
            if output_units == "same":
                ou = x1.units
            elif output_units == "prod":
                ou = x1.units * x2.units
            elif output_units == "div":
                ou = x1.units / x2.units
            elif output_units == "second":
                ou = x1.units**x2
            else:
                ou = output_units

            qm = func(x1, x2)

            if convert2 and hasattr(x2, "magnitude"):
                m2 = x2.to(getattr(x1, "units", "")).magnitude
            else:
                m2 = getattr(x2, "magnitude", x2)

            res = func(x1.magnitude, m2)
            if ou is not None:
                res = self.Q_(res, ou)

            helpers.assert_quantity_almost_equal(qm, res, rtol=rtol, msg=err_msg)

        for x2 in raise_with:
            with pytest.raises(DimensionalityError):
                func(x1, x2)

    def _testn2(self, func, x1, ok_with, raise_with=()):
        """Test function that takes two arguments and return a ndarray.

        Parameters
        ----------
        func :
            function callable.
        x1 :
            first argument of func.
        ok_with :
            iterables of values that work fine.
        raise_with :
            iterables of values that raise exceptions. (Default value = ())

        Returns
        -------

        """
        self._test2(func, x1, ok_with, raise_with, output_units=None)


@helpers.requires_numpy
class TestMathUfuncs(TestUFuncs):
    """Universal functions (ufunc) > Math operations

    http://docs.scipy.org/doc/numpy/reference/ufuncs.html#math-operations

    add(x1, x2, /[, out, where, casting, order, ...] Add arguments element-wise.
    subtract(x1, x2, /[, out, where, casting, ...] Subtract arguments, element-wise.
    multiply(x1, x2, /[, out, where, casting, ...] Multiply arguments element-wise.
    matmul(x1, x2, /[, out, casting, order, ...] Matrix product of two arrays.
    divide(x1, x2, /[, out, where, casting, ...] Divide arguments element-wise.
    logaddexp(x1, x2, /[, out, where, casting, ...] Logarithm of the sum of exponentiations of the inputs.
    logaddexp2(x1, x2, /[, out, where, casting, ...] Logarithm of the sum of exponentiations of the inputs in base-2.
    true_divide(x1, x2, /[, out, where, ...] Divide arguments element-wise.
    floor_divide(x1, x2, /[, out, where, ...] Return the largest integer smaller or equal to the division of the inputs.
    negative(x, /[, out, where, casting, order, ...] Numerical negative, element-wise.
    positive(x, /[, out, where, casting, order, ...] Numerical positive, element-wise.
    power(x1, x2, /[, out, where, casting, ...] First array elements raised to powers from second array, element-wise.
    float_power(x1, x2, /[, out, where, ...] First array elements raised to powers from second array, element-wise.
    remainder(x1, x2, /[, out, where, casting, ...] Returns the element-wise remainder of division.
    mod(x1, x2, /[, out, where, casting, order, ...] Returns the element-wise remainder of division.
    fmod(x1, x2, /[, out, where, casting, ...] Returns the element-wise remainder of division.
    divmod(x1, x2[, out1, out2], / [[, out, ...] Return element-wise quotient and remainder simultaneously.
    absolute(x, /[, out, where, casting, order, ...] Calculate the absolute value element-wise.
    fabs(x, /[, out, where, casting, order, ...] Compute the absolute values element-wise.
    rint(x, /[, out, where, casting, order, ...] Round elements of the array to the nearest integer.
    sign(x, /[, out, where, casting, order, ...] Returns an element-wise indication of the sign of a number.
    heaviside(x1, x2, /[, out, where, casting, ...] Compute the Heaviside step function.
    conj(x, /[, out, where, casting, order, ...] Return the complex conjugate, element-wise.
    conjugate(x, /[, out, where, casting, ...] Return the complex conjugate, element-wise.
    exp(x, /[, out, where, casting, order, ...] Calculate the exponential of all elements in the input array.
    exp2(x, /[, out, where, casting, order, ...] Calculate 2**p for all p in the input array.
    log(x, /[, out, where, casting, order, ...] Natural logarithm, element-wise.
    log2(x, /[, out, where, casting, order, ...] Base-2 logarithm of x.
    log10(x, /[, out, where, casting, order, ...] Return the base 10 logarithm of the input array, element-wise.
    expm1(x, /[, out, where, casting, order, ...] Calculate exp(x) - 1 for all elements in the array.
    log1p(x, /[, out, where, casting, order, ...] Return the natural logarithm of one plus the input array, element-wise.
    sqrt(x, /[, out, where, casting, order, ...] Return the non-negative square-root of an array, element-wise.
    square(x, /[, out, where, casting, order, ...] Return the element-wise square of the input.
    cbrt(x, /[, out, where, casting, order, ...] Return the cube-root of an array, element-wise.
    reciprocal(x, /[, out, where, casting, ...] Return the reciprocal of the argument, element-wise.
    gcd(x1, x2, /[, out, where, casting, order, ...] Returns the greatest common divisor of |x1| and |x2|
    lcm(x1, x2, /[, out, where, casting, order, ...] Returns the lowest common multiple of |x1| and |x2|

    Parameters
    ----------

    Returns
    -------

    """

    def test_add(self):
        self._test2(np.add, self.q1, (self.q2, self.qs), (self.qm,))

    def test_subtract(self):
        self._test2(np.subtract, self.q1, (self.q2, self.qs), (self.qm,))

    def test_multiply(self):
        self._test2(np.multiply, self.q1, (self.q2, self.qs), (), "prod")

    def test_divide(self):
        self._test2(
            np.divide,
            self.q1,
            (self.q2, self.qs, self.qless),
            (),
            "div",
            convert2=False,
        )

    def test_logaddexp(self):
        self._test2(np.logaddexp, self.qless, (self.qless,), (self.q1,), "")

    def test_logaddexp2(self):
        self._test2(np.logaddexp2, self.qless, (self.qless,), (self.q1,), "div")

    def test_true_divide(self):
        self._test2(
            np.true_divide,
            self.q1,
            (self.q2, self.qs, self.qless),
            (),
            "div",
            convert2=False,
        )

    def test_floor_divide(self):
        self._test2(
            np.floor_divide,
            self.q1,
            (self.q2, self.qs, self.qless),
            (),
            "div",
            convert2=False,
        )

    def test_negative(self):
        self._test1(np.negative, (self.qless, self.q1), ())

    def test_positive(self):
        self._test1(np.positive, (self.qless, self.q1), ())

    def test_remainder(self):
        self._test2(
            np.remainder,
            self.q1,
            (self.q2, self.qs, self.qless),
            (),
            "same",
            convert2=False,
        )

    def test_mod(self):
        self._test2(
            np.mod, self.q1, (self.q2, self.qs, self.qless), (), "same", convert2=False
        )

    def test_fmod(self):
        self._test2(
            np.fmod, self.q1, (self.q2, self.qs, self.qless), (), "same", convert2=False
        )

    def test_absolute(self):
        self._test1(np.absolute, (self.q2, self.qs, self.qless, self.qi), (), "same")

    def test_rint(self):
        self._test1(np.rint, (self.q2, self.qs, self.qless, self.qi), (), "same")

    def test_conj(self):
        self._test1(np.conj, (self.q2, self.qs, self.qless, self.qi), (), "same")

    def test_exp(self):
        self._test1(np.exp, (self.qless,), (self.q1,), "")

    def test_exp2(self):
        self._test1(np.exp2, (self.qless,), (self.q1,), "")

    def test_log(self):
        self._test1(np.log, (self.qless,), (self.q1,), "")

    def test_log2(self):
        self._test1(np.log2, (self.qless,), (self.q1,), "")

    def test_log10(self):
        self._test1(np.log10, (self.qless,), (self.q1,), "")

    def test_expm1(self):
        self._test1(np.expm1, (self.qless,), (self.q1,), "")

    def test_sqrt(self):
        self._test1(np.sqrt, (self.q2, self.qs, self.qless, self.qi), (), 0.5)

    def test_square(self):
        self._test1(np.square, (self.q2, self.qs, self.qless, self.qi), (), 2)

    def test_reciprocal(self):
        self._test1(np.reciprocal, (self.q2, self.qs, self.qless, self.qi), (), -1)


@helpers.requires_numpy
class TestTrigUfuncs(TestUFuncs):
    """Universal functions (ufunc) > Trigonometric functions

    http://docs.scipy.org/doc/numpy/reference/ufuncs.html#trigonometric-functions

    sin(x[, out]) Trigonometric sine, element-wise.
    cos(x[, out]) Cosine elementwise.
    tan(x[, out]) Compute tangent element-wise.
    arcsin(x[, out]) Inverse sine, element-wise.
    arccos(x[, out]) Trigonometric inverse cosine, element-wise.
    arctan(x[, out]) Trigonometric inverse tangent, element-wise.
    arctan2(x1, x2[, out]) Element-wise arc tangent of x1/x2 choosing the quadrant correctly.
    hypot(x1, x2[, out]) Given the “legs” of a right triangle, return its hypotenuse.
    sinh(x[, out]) Hyperbolic sine, element-wise.
    cosh(x[, out]) Hyperbolic cosine, element-wise.
    tanh(x[, out]) Compute hyperbolic tangent element-wise.
    arcsinh(x[, out]) Inverse hyperbolic sine elementwise.
    arccosh(x[, out]) Inverse hyperbolic cosine, elementwise.
    arctanh(x[, out]) Inverse hyperbolic tangent elementwise.
    deg2rad(x[, out]) Convert angles from degrees to radians.
    rad2deg(x[, out]) Convert angles from radians to degrees.

    Parameters
    ----------

    Returns
    -------

    """

    def test_sin(self):
        self._test1(
            np.sin,
            (
                np.arange(0, pi / 2, pi / 4) * self.ureg.dimensionless,
                np.arange(0, pi / 2, pi / 4) * self.ureg.radian,
                np.arange(0, pi / 2, pi / 4) * self.ureg.mm / self.ureg.m,
            ),
            (1 * self.ureg.m,),
            "",
            results=(None, None, np.sin(np.arange(0, pi / 2, pi / 4) * 0.001)),
        )
        self._test1(
            np.sin,
            (np.rad2deg(np.arange(0, pi / 2, pi / 4)) * self.ureg.degrees,),
            results=(np.sin(np.arange(0, pi / 2, pi / 4)),),
        )

    def test_cos(self):
        self._test1(
            np.cos,
            (
                np.arange(0, pi / 2, pi / 4) * self.ureg.dimensionless,
                np.arange(0, pi / 2, pi / 4) * self.ureg.radian,
                np.arange(0, pi / 2, pi / 4) * self.ureg.mm / self.ureg.m,
            ),
            (1 * self.ureg.m,),
            "",
            results=(None, None, np.cos(np.arange(0, pi / 2, pi / 4) * 0.001)),
        )
        self._test1(
            np.cos,
            (np.rad2deg(np.arange(0, pi / 2, pi / 4)) * self.ureg.degrees,),
            results=(np.cos(np.arange(0, pi / 2, pi / 4)),),
        )

    def test_tan(self):
        self._test1(
            np.tan,
            (
                np.arange(0, pi / 2, pi / 4) * self.ureg.dimensionless,
                np.arange(0, pi / 2, pi / 4) * self.ureg.radian,
                np.arange(0, pi / 2, pi / 4) * self.ureg.mm / self.ureg.m,
            ),
            (1 * self.ureg.m,),
            "",
            results=(None, None, np.tan(np.arange(0, pi / 2, pi / 4) * 0.001)),
        )
        self._test1(
            np.tan,
            (np.rad2deg(np.arange(0, pi / 2, pi / 4)) * self.ureg.degrees,),
            results=(np.tan(np.arange(0, pi / 2, pi / 4)),),
        )

    def test_arcsin(self):
        self._test1(
            np.arcsin,
            (
                np.arange(0, 0.9, 0.1) * self.ureg.dimensionless,
                np.arange(0, 0.9, 0.1) * self.ureg.m / self.ureg.m,
            ),
            (1 * self.ureg.m,),
            "radian",
        )

    def test_arccos(self):
        self._test1(
            np.arccos,
            (
                np.arange(0, 0.9, 0.1) * self.ureg.dimensionless,
                np.arange(0, 0.9, 0.1) * self.ureg.m / self.ureg.m,
            ),
            (1 * self.ureg.m,),
            "radian",
        )

    def test_arctan(self):
        self._test1(
            np.arctan,
            (
                np.arange(0, 0.9, 0.1) * self.ureg.dimensionless,
                np.arange(0, 0.9, 0.1) * self.ureg.m / self.ureg.m,
            ),
            (1 * self.ureg.m,),
            "radian",
        )

    def test_arctan2(self):
        m = self.ureg.m
        j = self.ureg.J
        km = self.ureg.km
        self._test2(
            np.arctan2,
            np.arange(0, 0.9, 0.1) * m,
            (
                np.arange(0, 0.9, 0.1) * m,
                np.arange(0.9, 0.0, -0.1) * m,
                np.arange(0, 0.9, 0.1) * km,
                np.arange(0.9, 0.0, -0.1) * km,
            ),
            raise_with=np.arange(0, 0.9, 0.1) * j,
            output_units="radian",
        )

    def test_hypot(self):
        assert np.hypot(3.0 * self.ureg.m, 4.0 * self.ureg.m) == 5.0 * self.ureg.m
        assert np.hypot(3.0 * self.ureg.m, 400.0 * self.ureg.cm) == 5.0 * self.ureg.m
        with pytest.raises(DimensionalityError):
            np.hypot(1.0 * self.ureg.m, 2.0 * self.ureg.J)

    def test_sinh(self):
        self._test1(
            np.sinh,
            (
                np.arange(0, pi / 2, pi / 4) * self.ureg.dimensionless,
                np.arange(0, pi / 2, pi / 4) * self.ureg.radian,
                np.arange(0, pi / 2, pi / 4) * self.ureg.mm / self.ureg.m,
            ),
            (1 * self.ureg.m,),
            "",
            results=(None, None, np.sinh(np.arange(0, pi / 2, pi / 4) * 0.001)),
        )
        self._test1(
            np.sinh,
            (np.rad2deg(np.arange(0, pi / 2, pi / 4)) * self.ureg.degrees,),
            results=(np.sinh(np.arange(0, pi / 2, pi / 4)),),
        )

    def test_cosh(self):
        self._test1(
            np.cosh,
            (
                np.arange(0, pi / 2, pi / 4) * self.ureg.dimensionless,
                np.arange(0, pi / 2, pi / 4) * self.ureg.radian,
                np.arange(0, pi / 2, pi / 4) * self.ureg.mm / self.ureg.m,
            ),
            (1 * self.ureg.m,),
            "",
            results=(None, None, np.cosh(np.arange(0, pi / 2, pi / 4) * 0.001)),
        )
        self._test1(
            np.cosh,
            (np.rad2deg(np.arange(0, pi / 2, pi / 4)) * self.ureg.degrees,),
            results=(np.cosh(np.arange(0, pi / 2, pi / 4)),),
        )

    def test_tanh(self):
        self._test1(
            np.tanh,
            (
                np.arange(0, pi / 2, pi / 4) * self.ureg.dimensionless,
                np.arange(0, pi / 2, pi / 4) * self.ureg.radian,
                np.arange(0, pi / 2, pi / 4) * self.ureg.mm / self.ureg.m,
            ),
            (1 * self.ureg.m,),
            "",
            results=(None, None, np.tanh(np.arange(0, pi / 2, pi / 4) * 0.001)),
        )
        self._test1(
            np.tanh,
            (np.rad2deg(np.arange(0, pi / 2, pi / 4)) * self.ureg.degrees,),
            results=(np.tanh(np.arange(0, pi / 2, pi / 4)),),
        )

    def test_arcsinh(self):
        self._test1(
            np.arcsinh,
            (
                np.arange(0, 0.9, 0.1) * self.ureg.dimensionless,
                np.arange(0, 0.9, 0.1) * self.ureg.m / self.ureg.m,
            ),
            (1 * self.ureg.m,),
            "radian",
        )

    def test_arccosh(self):
        self._test1(
            np.arccosh,
            (
                np.arange(1.0, 1.9, 0.1) * self.ureg.dimensionless,
                np.arange(1.0, 1.9, 0.1) * self.ureg.m / self.ureg.m,
            ),
            (1 * self.ureg.m,),
            "radian",
        )

    def test_arctanh(self):
        self._test1(
            np.arctanh,
            (
                np.arange(0, 0.9, 0.1) * self.ureg.dimensionless,
                np.arange(0, 0.9, 0.1) * self.ureg.m / self.ureg.m,
            ),
            (0.1 * self.ureg.m,),
            "radian",
        )

    def test_deg2rad(self):
        self._test1(
            np.deg2rad,
            (np.arange(0, pi / 2, pi / 4) * self.ureg.degrees,),
            (1 * self.ureg.m,),
            "radians",
        )

    def test_rad2deg(self):
        self._test1(
            np.rad2deg,
            (
                np.arange(0, pi / 2, pi / 4) * self.ureg.dimensionless,
                np.arange(0, pi / 2, pi / 4) * self.ureg.radian,
                np.arange(0, pi / 2, pi / 4) * self.ureg.mm / self.ureg.m,
            ),
            (1 * self.ureg.m,),
            "degree",
            results=(
                None,
                None,
                np.rad2deg(np.arange(0, pi / 2, pi / 4) * 0.001) * self.ureg.degree,
            ),
        )


class TestComparisonUfuncs(TestUFuncs):
    """Universal functions (ufunc) > Comparison functions

    http://docs.scipy.org/doc/numpy/reference/ufuncs.html#comparison-functions

    greater(x1, x2[, out]) Return the truth value of (x1 > x2) element-wise.
    greater_equal(x1, x2[, out]) Return the truth value of (x1 >= x2) element-wise.
    less(x1, x2[, out]) Return the truth value of (x1 < x2) element-wise.
    less_equal(x1, x2[, out]) Return the truth value of (x1 =< x2) element-wise.
    not_equal(x1, x2[, out]) Return (x1 != x2) element-wise.
    equal(x1, x2[, out]) Return (x1 == x2) element-wise.

    Parameters
    ----------

    Returns
    -------

    """

    def test_greater(self):
        self._testn2(np.greater, self.q1, (self.q2,), (self.qm,))

    def test_greater_equal(self):
        self._testn2(np.greater_equal, self.q1, (self.q2,), (self.qm,))

    def test_less(self):
        self._testn2(np.less, self.q1, (self.q2,), (self.qm,))

    def test_less_equal(self):
        self._testn2(np.less_equal, self.q1, (self.q2,), (self.qm,))

    def test_not_equal(self):
        self._testn2(np.not_equal, self.q1, (self.q2,), (self.qm,))

    def test_equal(self):
        self._testn2(np.equal, self.q1, (self.q2,), (self.qm,))


class TestFloatingUfuncs(TestUFuncs):
    """Universal functions (ufunc) > Floating functions

    http://docs.scipy.org/doc/numpy/reference/ufuncs.html#floating-functions

    isreal(x) Returns a bool array, where True if input element is real.
    iscomplex(x) Returns a bool array, where True if input element is complex.
    isfinite(x[, out]) Test element-wise for finite-ness (not infinity or not Not a Number).
    isinf(x[, out]) Test element-wise for positive or negative infinity.
    isnan(x[, out]) Test element-wise for Not a Number (NaN), return result as a bool array.
    signbit(x[, out]) Returns element-wise True where signbit is set (less than zero).
    copysign(x1, x2[, out]) Change the sign of x1 to that of x2, element-wise.
    nextafter(x1, x2[, out]) Return the next representable floating-point value after x1 in the direction of x2 element-wise.
    modf(x[, out1, out2]) Return the fractional and integral parts of an array, element-wise.
    ldexp(x1, x2[, out]) Compute y = x1 * 2**x2.
    frexp(x[, out1, out2]) Split the number, x, into a normalized fraction (y1) and exponent (y2)
    fmod(x1, x2[, out]) Return the element-wise remainder of division.
    floor(x[, out]) Return the floor of the input, element-wise.
    ceil(x[, out]) Return the ceiling of the input, element-wise.
    trunc(x[, out]) Return the truncated value of the input, element-wise.

    Parameters
    ----------

    Returns
    -------

    """

    def test_isreal(self):
        self._testn(np.isreal, (self.q1, self.qm, self.qless))

    def test_iscomplex(self):
        self._testn(np.iscomplex, (self.q1, self.qm, self.qless))

    def test_isfinite(self):
        self._testn(np.isfinite, (self.q1, self.qm, self.qless))

    def test_isinf(self):
        self._testn(np.isinf, (self.q1, self.qm, self.qless))

    def test_isnan(self):
        self._testn(np.isnan, (self.q1, self.qm, self.qless))

    def test_signbit(self):
        self._testn(np.signbit, (self.q1, self.qm, self.qless))

    def test_copysign(self):
        self._test2(np.copysign, self.q1, (self.q2, self.qs), (self.qm,))

    def test_nextafter(self):
        self._test2(np.nextafter, self.q1, (self.q2, self.qs), (self.qm,))

    def test_modf(self):
        self._test1_2o(np.modf, (self.q2, self.qs))

    def test_ldexp(self):
        x1, x2 = np.frexp(self.q2)
        self._test2(np.ldexp, x1, (x2,))

    def test_frexp(self):
        self._test1_2o(np.frexp, (self.q2, self.qs), output_units=("same", None))

    def test_fmod(self):
        # See TestMathUfuncs.test_fmod
        pass

    def test_floor(self):
        self._test1(np.floor, (self.q1, self.qm, self.qless))

    def test_ceil(self):
        self._test1(np.ceil, (self.q1, self.qm, self.qless))

    def test_trunc(self):
        self._test1(np.trunc, (self.q1, self.qm, self.qless))
