|  | 
| 3 | 3 | import numpy as np | 
| 4 | 4 | import operator | 
| 5 | 5 | 
 | 
|  | 6 | +from mpmath import mp | 
|  | 7 | + | 
| 6 | 8 | import numpy_quaddtype | 
| 7 | 9 | from numpy_quaddtype import QuadPrecDType, QuadPrecision | 
|  | 10 | +from numpy_quaddtype import pi as quad_pi | 
| 8 | 11 | 
 | 
| 9 | 12 | 
 | 
| 10 | 13 | def test_create_scalar_simple(): | 
| @@ -1735,6 +1738,222 @@ def test_divmod_broadcasting(): | 
| 1735 | 1738 |         np.testing.assert_allclose(float(quotients[i]), expected_quotients[i], rtol=1e-14) | 
| 1736 | 1739 |         np.testing.assert_allclose(float(remainders[i]), expected_remainders[i], rtol=1e-14) | 
| 1737 | 1740 | 
 | 
|  | 1741 | +class TestTrignometricFunctions: | 
|  | 1742 | +  @pytest.mark.parametrize("op", ["sin", "cos", "tan", "atan"]) | 
|  | 1743 | +  @pytest.mark.parametrize("val", [ | 
|  | 1744 | +      # Basic cases | 
|  | 1745 | +      "0.0", "-0.0", "1.0", "-1.0", "2.0", "-2.0", | 
|  | 1746 | +      # pi multiples | 
|  | 1747 | +      str(quad_pi), str(-quad_pi), str(2*quad_pi), str(-2*quad_pi), str(quad_pi/2), str(-quad_pi/2), str(3*quad_pi/2), str(-3*quad_pi/2), | 
|  | 1748 | +      # Small values | 
|  | 1749 | +      "1e-10", "-1e-10", "1e-15", "-1e-15", | 
|  | 1750 | +      # Values near one | 
|  | 1751 | +      "0.9", "-0.9", "0.9999", "-0.9999", | 
|  | 1752 | +      "1.1", "-1.1", "1.0001", "-1.0001", | 
|  | 1753 | +      # Medium values | 
|  | 1754 | +      "10.0", "-10.0", "20.0", "-20.0", | 
|  | 1755 | +      # Large values | 
|  | 1756 | +      "100.0", "200.0", "700.0", "1000.0", "1e100", "1e308", | 
|  | 1757 | +      "-100.0", "-200.0", "-700.0", "-1000.0", "-1e100", "-1e308", | 
|  | 1758 | +      # Fractional values | 
|  | 1759 | +      "0.5", "-0.5", "1.5", "-1.5", "2.5", "-2.5", | 
|  | 1760 | +      # Special values | 
|  | 1761 | +      "inf", "-inf", "nan", | 
|  | 1762 | +  ]) | 
|  | 1763 | +  def test_sin_cos_tan(self, op, val): | 
|  | 1764 | +    mp.prec = 113  # Set precision to 113 bits (~34 decimal digits) | 
|  | 1765 | +    numpy_op = getattr(np, op) | 
|  | 1766 | +    mpmath_op = getattr(mp, op) | 
|  | 1767 | +     | 
|  | 1768 | +    quad_val = QuadPrecision(val) | 
|  | 1769 | +    mpf_val = mp.mpf(val) | 
|  | 1770 | + | 
|  | 1771 | +    quad_result = numpy_op(quad_val) | 
|  | 1772 | +    mpmath_result = mpmath_op(mpf_val) | 
|  | 1773 | +    # convert mpmath result to quad for comparison | 
|  | 1774 | +    mpmath_result = QuadPrecision(str(mpmath_result)) | 
|  | 1775 | + | 
|  | 1776 | +    # Handle NaN cases | 
|  | 1777 | +    if np.isnan(mpmath_result): | 
|  | 1778 | +        assert np.isnan(quad_result), f"Expected NaN for {op}({val}), got {quad_result}" | 
|  | 1779 | +        return | 
|  | 1780 | + | 
|  | 1781 | +    # Handle infinity cases | 
|  | 1782 | +    if np.isinf(mpmath_result): | 
|  | 1783 | +        assert np.isinf(quad_result), f"Expected inf for {op}({val}), got {quad_result}" | 
|  | 1784 | +        assert np.sign(mpmath_result) == np.sign(quad_result), f"Infinity sign mismatch for {op}({val})" | 
|  | 1785 | +        return | 
|  | 1786 | + | 
|  | 1787 | +    # For finite non-zero results | 
|  | 1788 | +    np.testing.assert_allclose(quad_result, mpmath_result, rtol=1e-32, atol=1e-34, | 
|  | 1789 | +                              err_msg=f"Value mismatch for {op}({val}), expected {mpmath_result}, got {quad_result}") | 
|  | 1790 | + | 
|  | 1791 | +  # their domain is [-1 , 1] | 
|  | 1792 | +  @pytest.mark.parametrize("op", ["asin", "acos"]) | 
|  | 1793 | +  @pytest.mark.parametrize("val", [ | 
|  | 1794 | +    # Basic cases (valid domain) | 
|  | 1795 | +    "0.0", "-0.0", "1.0", "-1.0", | 
|  | 1796 | +    # Small values | 
|  | 1797 | +    "1e-10", "-1e-10", "1e-15", "-1e-15", | 
|  | 1798 | +    # Values near domain boundaries | 
|  | 1799 | +    "0.9", "-0.9", "0.9999", "-0.9999", | 
|  | 1800 | +    "0.99999999", "-0.99999999", | 
|  | 1801 | +    "0.999999999999", "-0.999999999999", | 
|  | 1802 | +    # Fractional values (within domain) | 
|  | 1803 | +    "0.5", "-0.5", | 
|  | 1804 | +    # Special values | 
|  | 1805 | +    "nan" | 
|  | 1806 | +  ]) | 
|  | 1807 | +  def test_inverse_sin_cos(self, op, val): | 
|  | 1808 | +    mp.prec = 113  # Set precision to 113 bits (~34 decimal digits) | 
|  | 1809 | +    numpy_op = getattr(np, op) | 
|  | 1810 | +    mpmath_op = getattr(mp, op) | 
|  | 1811 | +     | 
|  | 1812 | +    quad_val = QuadPrecision(val) | 
|  | 1813 | +    mpf_val = mp.mpf(val) | 
|  | 1814 | + | 
|  | 1815 | +    quad_result = numpy_op(quad_val) | 
|  | 1816 | +    mpmath_result = mpmath_op(mpf_val) | 
|  | 1817 | +    # convert mpmath result to quad for comparison | 
|  | 1818 | +    mpmath_result = QuadPrecision(str(mpmath_result)) | 
|  | 1819 | + | 
|  | 1820 | +    # Handle NaN cases | 
|  | 1821 | +    if np.isnan(mpmath_result): | 
|  | 1822 | +        assert np.isnan(quad_result), f"Expected NaN for {op}({val}), got {quad_result}" | 
|  | 1823 | +        return | 
|  | 1824 | + | 
|  | 1825 | +    # For finite non-zero results | 
|  | 1826 | +    np.testing.assert_allclose(quad_result, mpmath_result, rtol=1e-32, atol=1e-34, | 
|  | 1827 | +                              err_msg=f"Value mismatch for {op}({val}), expected {mpmath_result}, got {quad_result}") | 
|  | 1828 | +   | 
|  | 1829 | +  # mpmath's atan2 does not follow IEEE standards so hardcoding the edge cases | 
|  | 1830 | +  # for special edge cases check reference here: https://en.cppreference.com/w/cpp/numeric/math/atan2.html | 
|  | 1831 | +  # atan2: [Real x Real] -> [-pi , pi] | 
|  | 1832 | +  @pytest.mark.parametrize("y", [ | 
|  | 1833 | +    # Basic cases | 
|  | 1834 | +    "0.0", "-0.0", "1.0", "-1.0",  | 
|  | 1835 | +    # Small values | 
|  | 1836 | +    "1e-10", "-1e-10", "1e-15", "-1e-15", | 
|  | 1837 | +    # Medium/Large values | 
|  | 1838 | +    "10.0", "-10.0", "100.0", "-100.0", "1000.0", "-1000.0", | 
|  | 1839 | +    # Fractional | 
|  | 1840 | +    "0.5", "-0.5", "2.5", "-2.5", | 
|  | 1841 | +    # Special | 
|  | 1842 | +    "inf", "-inf", "nan", | 
|  | 1843 | +  ]) | 
|  | 1844 | +  @pytest.mark.parametrize("x", [ | 
|  | 1845 | +      "0.0", "-0.0", "1.0", "-1.0", | 
|  | 1846 | +      "1e-10", "-1e-10", | 
|  | 1847 | +      "10.0", "-10.0", "100.0", "-100.0", | 
|  | 1848 | +      "0.5", "-0.5", | 
|  | 1849 | +      "inf", "-inf", "nan", | 
|  | 1850 | +  ]) | 
|  | 1851 | +  def test_atan2(self, y, x): | 
|  | 1852 | +    mp.prec = 113 | 
|  | 1853 | +     | 
|  | 1854 | +    quad_y = QuadPrecision(y) | 
|  | 1855 | +    quad_x = QuadPrecision(x) | 
|  | 1856 | +    mpf_y = mp.mpf(y) | 
|  | 1857 | +    mpf_x = mp.mpf(x) | 
|  | 1858 | + | 
|  | 1859 | +    quad_result = np.arctan2(quad_y, quad_x) | 
|  | 1860 | +     | 
|  | 1861 | +    # IEEE 754 special cases - hardcoded expectations | 
|  | 1862 | +    y_val = float(y) | 
|  | 1863 | +    x_val = float(x) | 
|  | 1864 | +     | 
|  | 1865 | +    # If either x is NaN or y is NaN, NaN is returned | 
|  | 1866 | +    if np.isnan(y_val) or np.isnan(x_val): | 
|  | 1867 | +        assert np.isnan(quad_result), f"Expected NaN for atan2({y}, {x}), got {quad_result}" | 
|  | 1868 | +        return | 
|  | 1869 | +     | 
|  | 1870 | +    # If y is ±0 and x is negative or -0, ±π is returned | 
|  | 1871 | +    if y_val == 0.0 and (x_val < 0.0 or (x_val == 0.0 and np.signbit(x_val))): | 
|  | 1872 | +        expected = quad_pi if not np.signbit(y_val) else -quad_pi | 
|  | 1873 | +        np.testing.assert_allclose(quad_result, expected, rtol=1e-32, atol=1e-34, | 
|  | 1874 | +                                  err_msg=f"Value mismatch for atan2({y}, {x}), expected {expected}, got {quad_result}") | 
|  | 1875 | +        return | 
|  | 1876 | +     | 
|  | 1877 | +    # If y is ±0 and x is positive or +0, ±0 is returned | 
|  | 1878 | +    if y_val == 0.0 and (x_val > 0.0 or (x_val == 0.0 and not np.signbit(x_val))): | 
|  | 1879 | +        assert quad_result == 0.0, f"Expected ±0 for atan2({y}, {x}), got {quad_result}" | 
|  | 1880 | +        assert np.signbit(quad_result) == np.signbit(y_val), f"Sign mismatch for atan2({y}, {x})" | 
|  | 1881 | +        return | 
|  | 1882 | +     | 
|  | 1883 | +    # If y is ±∞ and x is finite, ±π/2 is returned | 
|  | 1884 | +    if np.isinf(y_val) and np.isfinite(x_val): | 
|  | 1885 | +        expected = quad_pi / 2 if y_val > 0 else -quad_pi / 2 | 
|  | 1886 | +        np.testing.assert_allclose(quad_result, expected, rtol=1e-32, atol=1e-34, | 
|  | 1887 | +                                  err_msg=f"Value mismatch for atan2({y}, {x}), expected {expected}, got {quad_result}") | 
|  | 1888 | +        return | 
|  | 1889 | +     | 
|  | 1890 | +    # If y is ±∞ and x is -∞, ±3π/4 is returned | 
|  | 1891 | +    if np.isinf(y_val) and np.isinf(x_val) and x_val < 0: | 
|  | 1892 | +        expected = 3 * quad_pi / 4 if y_val > 0 else -3 * quad_pi / 4 | 
|  | 1893 | +        np.testing.assert_allclose(quad_result, expected, rtol=1e-32, atol=1e-34, | 
|  | 1894 | +                                  err_msg=f"Value mismatch for atan2({y}, {x}), expected {expected}, got {quad_result}") | 
|  | 1895 | +        return | 
|  | 1896 | +     | 
|  | 1897 | +    # If y is ±∞ and x is +∞, ±π/4 is returned | 
|  | 1898 | +    if np.isinf(y_val) and np.isinf(x_val) and x_val > 0: | 
|  | 1899 | +        expected = quad_pi / 4 if y_val > 0 else -quad_pi / 4 | 
|  | 1900 | +        np.testing.assert_allclose(quad_result, expected, rtol=1e-32, atol=1e-34, | 
|  | 1901 | +                                  err_msg=f"Value mismatch for atan2({y}, {x}), expected {expected}, got {quad_result}") | 
|  | 1902 | +        return | 
|  | 1903 | +     | 
|  | 1904 | +    # If x is ±0 and y is negative, -π/2 is returned | 
|  | 1905 | +    if x_val == 0.0 and y_val < 0.0: | 
|  | 1906 | +        expected = -quad_pi / 2 | 
|  | 1907 | +        np.testing.assert_allclose(quad_result, expected, rtol=1e-32, atol=1e-34, | 
|  | 1908 | +                                  err_msg=f"Value mismatch for atan2({y}, {x}), expected {expected}, got {quad_result}") | 
|  | 1909 | +        return | 
|  | 1910 | +     | 
|  | 1911 | +    # If x is ±0 and y is positive, +π/2 is returned | 
|  | 1912 | +    if x_val == 0.0 and y_val > 0.0: | 
|  | 1913 | +        expected = quad_pi / 2 | 
|  | 1914 | +        np.testing.assert_allclose(quad_result, expected, rtol=1e-32, atol=1e-34, | 
|  | 1915 | +                                  err_msg=f"Value mismatch for atan2({y}, {x}), expected {expected}, got {quad_result}") | 
|  | 1916 | +        return | 
|  | 1917 | +     | 
|  | 1918 | +    # If x is -∞ and y is finite and positive, +π is returned | 
|  | 1919 | +    if np.isinf(x_val) and x_val < 0 and np.isfinite(y_val) and y_val > 0.0: | 
|  | 1920 | +        expected = quad_pi | 
|  | 1921 | +        np.testing.assert_allclose(quad_result, expected, rtol=1e-32, atol=1e-34, | 
|  | 1922 | +                                  err_msg=f"Value mismatch for atan2({y}, {x}), expected {expected}, got {quad_result}") | 
|  | 1923 | +        return | 
|  | 1924 | +     | 
|  | 1925 | +    # If x is -∞ and y is finite and negative, -π is returned | 
|  | 1926 | +    if np.isinf(x_val) and x_val < 0 and np.isfinite(y_val) and y_val < 0.0: | 
|  | 1927 | +        expected = -quad_pi | 
|  | 1928 | +        np.testing.assert_allclose(quad_result, expected, rtol=1e-32, atol=1e-34, | 
|  | 1929 | +                                  err_msg=f"Value mismatch for atan2({y}, {x}), expected {expected}, got {quad_result}") | 
|  | 1930 | +        return | 
|  | 1931 | +     | 
|  | 1932 | +    # If x is +∞ and y is finite and positive, +0 is returned | 
|  | 1933 | +    if np.isinf(x_val) and x_val > 0 and np.isfinite(y_val) and y_val > 0.0: | 
|  | 1934 | +        assert quad_result == 0.0 and not np.signbit(quad_result), f"Expected +0 for atan2({y}, {x}), got {quad_result}" | 
|  | 1935 | +        return | 
|  | 1936 | +     | 
|  | 1937 | +    # If x is +∞ and y is finite and negative, -0 is returned | 
|  | 1938 | +    if np.isinf(x_val) and x_val > 0 and np.isfinite(y_val) and y_val < 0.0: | 
|  | 1939 | +        assert quad_result == 0.0 and np.signbit(quad_result), f"Expected -0 for atan2({y}, {x}), got {quad_result}" | 
|  | 1940 | +        return | 
|  | 1941 | +     | 
|  | 1942 | +    # For all other cases, compare with mpmath | 
|  | 1943 | +    mpmath_result = mp.atan2(mpf_y, mpf_x) | 
|  | 1944 | +    mpmath_result = QuadPrecision(str(mpmath_result)) | 
|  | 1945 | + | 
|  | 1946 | +    if np.isnan(mpmath_result): | 
|  | 1947 | +        assert np.isnan(quad_result), f"Expected NaN for atan2({y}, {x}), got {quad_result}" | 
|  | 1948 | +        return | 
|  | 1949 | + | 
|  | 1950 | +    if np.isinf(mpmath_result): | 
|  | 1951 | +        assert np.isinf(quad_result), f"Expected inf for atan2({y}, {x}), got {quad_result}" | 
|  | 1952 | +        assert np.sign(mpmath_result) == np.sign(quad_result), f"Infinity sign mismatch for atan2({y}, {x})" | 
|  | 1953 | +        return | 
|  | 1954 | + | 
|  | 1955 | +    np.testing.assert_allclose(quad_result, mpmath_result, rtol=1e-32, atol=1e-34, | 
|  | 1956 | +                              err_msg=f"Value mismatch for atan2({y}, {x}), expected {mpmath_result}, got {quad_result}") | 
| 1738 | 1957 | 
 | 
| 1739 | 1958 | @pytest.mark.parametrize("op", ["sinh", "cosh", "tanh", "arcsinh", "arccosh", "arctanh"]) | 
| 1740 | 1959 | @pytest.mark.parametrize("val", [ | 
|  | 
0 commit comments