diff --git a/docs/source/changes.rst b/docs/source/changes.rst index bdd15e6f..479f176a 100644 --- a/docs/source/changes.rst +++ b/docs/source/changes.rst @@ -6,6 +6,50 @@ This is a record of all past mygrad releases and what went into them, in reverse chronological order. All previous releases should still be available on pip. +.. _v2.0.1: + +------------------ +2.0.1 - 2021-04-03 +------------------ + +Bug Fixes +--------- + +- :func:`~mygrad.matmul` and :func:`~mygrad.multi_matmul` were missing from the top-level namespace + +New Functions +------------- +The following non-differentiable NumPy functions now work on mygrad tensors (and return ndarrays). +Aliases of these are available at the top-level namespace of ``mygrad`` + +- np.isnan +- np.isfinite +- np.isinf +- np.isnat +- np.signbit +- np.logical_not +- np.logical_and +- np.logical_or +- np.logical_xor +- np.greater +- np.greater_equal +- np.less +- np.less_equal +- np.equal +- np.not_equal +- np.floor_divide +- np.remainder +- np.mod +- np.fmod +- np.divmod +- np.rint +- np.sign +- np.floor +- np.ceil +- np.trunc +- np.isclose + + .. _v2.0.0: ------------------ diff --git a/src/mygrad/math/misc/funcs.py b/src/mygrad/math/misc/funcs.py index 5563113c..d2df6dc5 100644 --- a/src/mygrad/math/misc/funcs.py +++ b/src/mygrad/math/misc/funcs.py @@ -11,7 +11,17 @@ from .ops import Abs, Cbrt, Maximum, Minimum, Sqrt -__all__ = ["abs", "absolute", "cbrt", "clip", "sqrt", "maximum", "minimum"] +__all__ = [ + "abs", + "absolute", + "cbrt", + "clip", + "sqrt", + "maximum", + "minimum", + "matmul", + "multi_matmul", +] @ufunc_creator(Abs) diff --git a/src/mygrad/no_grad_funcs/__init__.py b/src/mygrad/no_grad_funcs/__init__.py index 5cbe63ba..b362c241 100644 --- a/src/mygrad/no_grad_funcs/__init__.py +++ b/src/mygrad/no_grad_funcs/__init__.py @@ -1,25 +1,68 @@ +import itertools import sys from typing import TYPE_CHECKING, Optional, Tuple, Union -from numpy import dtype, ndarray +import numpy -from mygrad.tensor_base import _REGISTERED_NO_DIFF_NUMPY_FUNCS -from mygrad.typing import ArrayLike +from mygrad.tensor_base import ( + _REGISTERED_BOOL_ONLY_UFUNC, + _REGISTERED_CONST_ONLY_UFUNC, + _REGISTERED_NO_DIFF_NUMPY_FUNCS, +) +from mygrad.typing import ArrayLike, DTypeLike, Mask __all__ = [ "allclose", "bincount", "can_cast", + "ceil", "copyto", + "divmod", + "equal", + "floor", + "floor_divide", + "fmod", + "greater", + "greater_equal", + "isclose", + "isfinite", + "isinf", + "isnan", + "isnat", + "less", + "less_equal", + "logical_and", + "logical_not", + "logical_or", + "logical_xor", "may_share_memory", "min_scalar_type", + "mod", + "not_equal", + "remainder", "result_type", - "shares_memory", + "rint", "shape", + "shares_memory", + "sign", + "signbit", + "trunc", ] _module = sys.modules[__name__] + +for _func in itertools.chain.from_iterable( + ( + _REGISTERED_NO_DIFF_NUMPY_FUNCS, + _REGISTERED_BOOL_ONLY_UFUNC, + _REGISTERED_CONST_ONLY_UFUNC, + ) +): + setattr(_module, _func.__name__, _func) + +mod = numpy.remainder + if TYPE_CHECKING: # pragma: no cover def allclose( @@ -31,9 +74,18 @@ def allclose( ) -> bool: pass + def isclose( + a: ArrayLike, + b: ArrayLike, + rtol: float = 1e-05, + atol: float = 1e-08, + equal_nan: bool = False, + ) -> bool: + pass + def bincount( x: ArrayLike, weights: Optional[ArrayLike] = None, minlength: int = 0 - ) -> ndarray: + ) -> numpy.ndarray: pass def can_cast(from_, to, casting="safe") -> bool: @@ -49,10 +101,10 @@ def may_share_memory( ) -> bool: pass - def min_scalar_type(a: ArrayLike) -> dtype: + def min_scalar_type(a: ArrayLike) -> numpy.dtype: pass - def result_type(*arrays_and_dtypes: Union[ArrayLike, dtype]) -> dtype: + def result_type(*arrays_and_dtypes: Union[ArrayLike, numpy.dtype]) -> numpy.dtype: pass def shares_memory( @@ -63,6 +115,317 @@ def shares_memory( def shape(a: ArrayLike) -> Tuple[int, ...]: pass + def isnan( + x: ArrayLike, + out: Optional[ArrayLike] = None, + *, + casting: str = "same_kind", + where: Mask = True, + order: str = "K", + dtype: DTypeLike = None, + subok: bool = True + ) -> Union[numpy.ndarray, bool]: + ... -for _func in _REGISTERED_NO_DIFF_NUMPY_FUNCS: - setattr(_module, _func.__name__, _func) + def isfinite( + x: ArrayLike, + out: Optional[ArrayLike] = None, + *, + casting: str = "same_kind", + where: Mask = True, + order: str = "K", + dtype: DTypeLike = None, + subok: bool = True + ) -> Union[numpy.ndarray, bool]: + ... + + def isinf( + x: ArrayLike, + out: Optional[ArrayLike] = None, + *, + casting: str = "same_kind", + where: Mask = True, + order: str = "K", + dtype: DTypeLike = None, + subok: bool = True + ) -> Union[numpy.ndarray, bool]: + ... + + def isnat( + x: ArrayLike, + out: Optional[ArrayLike] = None, + *, + casting: str = "same_kind", + where: Mask = True, + order: str = "K", + dtype: DTypeLike = None, + subok: bool = True + ) -> Union[numpy.ndarray, bool]: + ... + + def signbit( + x: ArrayLike, + out: Optional[ArrayLike] = None, + *, + casting: str = "same_kind", + where: Mask = True, + order: str = "K", + dtype: DTypeLike = None, + subok: bool = True + ) -> Union[numpy.ndarray, bool]: + ... + + def logical_not( + x1: ArrayLike, + x2: ArrayLike, + out: Optional[ArrayLike] = None, + *, + casting: str = "same_kind", + where: Mask = True, + order: str = "K", + dtype: DTypeLike = None, + subok: bool = True + ) -> Union[numpy.ndarray, bool]: + ... + + def logical_and( + x1: ArrayLike, + x2: ArrayLike, + out: Optional[ArrayLike] = None, + *, + casting: str = "same_kind", + where: Mask = True, + order: str = "K", + dtype: DTypeLike = None, + subok: bool = True + ) -> Union[numpy.ndarray, bool]: + ... + + def logical_or( + x1: ArrayLike, + x2: ArrayLike, + out: Optional[ArrayLike] = None, + *, + casting: str = "same_kind", + where: Mask = True, + order: str = "K", + dtype: DTypeLike = None, + subok: bool = True + ) -> Union[numpy.ndarray, bool]: + ... + + def logical_xor( + x1: ArrayLike, + x2: ArrayLike, + out: Optional[ArrayLike] = None, + *, + casting: str = "same_kind", + where: Mask = True, + order: str = "K", + dtype: DTypeLike = None, + subok: bool = True + ) -> Union[numpy.ndarray, bool]: + ... + + def greater( + x1: ArrayLike, + x2: ArrayLike, + out: Optional[ArrayLike] = None, + *, + casting: str = "same_kind", + where: Mask = True, + order: str = "K", + dtype: DTypeLike = None, + subok: bool = True + ) -> Union[numpy.ndarray, bool]: + ... + + def greater_equal( + x1: ArrayLike, + x2: ArrayLike, + out: Optional[ArrayLike] = None, + *, + casting: str = "same_kind", + where: Mask = True, + order: str = "K", + dtype: DTypeLike = None, + subok: bool = True + ) -> Union[numpy.ndarray, bool]: + ... + + def less( + x1: ArrayLike, + x2: ArrayLike, + out: Optional[ArrayLike] = None, + *, + casting: str = "same_kind", + where: Mask = True, + order: str = "K", + dtype: DTypeLike = None, + subok: bool = True + ) -> Union[numpy.ndarray, bool]: + ... + + def less_equal( + x1: ArrayLike, + x2: ArrayLike, + out: Optional[ArrayLike] = None, + *, + casting: str = "same_kind", + where: Mask = True, + order: str = "K", + dtype: DTypeLike = None, + subok: bool = True + ) -> Union[numpy.ndarray, bool]: + ... + + def equal( + x1: ArrayLike, + x2: ArrayLike, + out: Optional[ArrayLike] = None, + *, + casting: str = "same_kind", + where: Mask = True, + order: str = "K", + dtype: DTypeLike = None, + subok: bool = True + ) -> Union[numpy.ndarray, bool]: + ... + + def not_equal( + x1: ArrayLike, + x2: ArrayLike, + out: Optional[ArrayLike] = None, + *, + casting: str = "same_kind", + where: Mask = True, + order: str = "K", + dtype: DTypeLike = None, + subok: bool = True + ) -> Union[numpy.ndarray, bool]: + ... + + def floor_divide( + x1: ArrayLike, + x2: ArrayLike, + out: Optional[ArrayLike] = None, + *, + casting: str = "same_kind", + where: Mask = True, + order: str = "K", + dtype: DTypeLike = None, + subok: bool = True + ) -> numpy.ndarray: + ... + + def remainder( + x1: ArrayLike, + x2: ArrayLike, + out: Optional[ArrayLike] = None, + *, + casting: str = "same_kind", + where: Mask = True, + order: str = "K", + dtype: DTypeLike = None, + subok: bool = True + ) -> numpy.ndarray: + ... + + def divmod( + x1: ArrayLike, + x2: ArrayLike, + out: Optional[ArrayLike] = None, + *, + casting: str = "same_kind", + where: Mask = True, + order: str = "K", + dtype: DTypeLike = None, + subok: bool = True + ) -> Tuple[numpy.ndarray, numpy.ndarray]: + ... + + def mod( + x1: ArrayLike, + x2: ArrayLike, + out: Optional[ArrayLike] = None, + *, + casting: str = "same_kind", + where: Mask = True, + order: str = "K", + dtype: DTypeLike = None, + subok: bool = True + ) -> Tuple[numpy.ndarray, numpy.ndarray]: + ... + + def fmod( + x1: ArrayLike, + x2: ArrayLike, + out: Optional[ArrayLike] = None, + *, + casting: str = "same_kind", + where: Mask = True, + order: str = "K", + dtype: DTypeLike = None, + subok: bool = True + ) -> Tuple[numpy.ndarray, numpy.ndarray]: + ... + + def rint( + x: ArrayLike, + out: Optional[ArrayLike] = None, + *, + casting: str = "same_kind", + where: Mask = True, + order: str = "K", + dtype: DTypeLike = None, + subok: bool = True + ) -> numpy.ndarray: + ... + + def sign( + x: ArrayLike, + out: Optional[ArrayLike] = None, + *, + casting: str = "same_kind", + where: Mask = True, + order: str = "K", + dtype: DTypeLike = None, + subok: bool = True + ) -> numpy.ndarray: + ... + + def floor( + x: ArrayLike, + out: Optional[ArrayLike] = None, + *, + casting: str = "same_kind", + where: Mask = True, + order: str = "K", + dtype: DTypeLike = None, + subok: bool = True + ) -> numpy.ndarray: + ... + + def ceil( + x: ArrayLike, + out: Optional[ArrayLike] = None, + *, + casting: str = "same_kind", + where: Mask = True, + order: str = "K", + dtype: DTypeLike = None, + subok: bool = True + ) -> numpy.ndarray: + ... + + def trunc( + x: ArrayLike, + out: Optional[ArrayLike] = None, + *, + casting: str = "same_kind", + where: Mask = True, + order: str = "K", + dtype: DTypeLike = None, + subok: bool = True + ) -> numpy.ndarray: + ... diff --git a/src/mygrad/tensor_base.py b/src/mygrad/tensor_base.py index 841c1234..e6f3cea2 100644 --- a/src/mygrad/tensor_base.py +++ b/src/mygrad/tensor_base.py @@ -33,7 +33,6 @@ collect_all_operations_and_clear_grads, ) from mygrad.errors import DisconnectedView -from mygrad.math.misc.ops import MatMul from mygrad.math.arithmetic.ops import ( Add, Divide, @@ -44,6 +43,7 @@ Square, Subtract, ) +from mygrad.math.misc.ops import MatMul from mygrad.math.sequential.ops import ( CumProd, CumSum, @@ -376,6 +376,7 @@ def astensor( np.less, np.less_equal, np.equal, + np.not_equal, } # These are ufuncs that users might mistake for being differentiable functions; @@ -400,6 +401,7 @@ def astensor( np.bincount, np.can_cast, np.copyto, + np.isclose, np.may_share_memory, np.min_scalar_type, np.result_type, diff --git a/tests/linalg/test_matmul.py b/tests/linalg/test_matmul.py index 746bc4f6..8f97d411 100644 --- a/tests/linalg/test_matmul.py +++ b/tests/linalg/test_matmul.py @@ -1,11 +1,11 @@ import hypothesis.extra.numpy as hnp -import mygrad.math.misc.funcs import numpy as np from hypothesis import settings from numpy.testing import assert_allclose import mygrad as mg -from mygrad.math.misc.funcs import matmul +import mygrad.math.misc.funcs +from mygrad import matmul from tests.wrappers.uber import backprop_test_factory, fwdprop_test_factory diff --git a/tests/test_numpy_overrides.py b/tests/test_numpy_overrides.py index e2aaec44..1876ff40 100644 --- a/tests/test_numpy_overrides.py +++ b/tests/test_numpy_overrides.py @@ -1,16 +1,32 @@ +import itertools + import numpy as np import pytest from numpy.testing import assert_array_equal import mygrad as mg -from mygrad.tensor_base import _REGISTERED_NO_DIFF_NUMPY_FUNCS +from mygrad.tensor_base import ( + _REGISTERED_BOOL_ONLY_UFUNC, + _REGISTERED_CONST_ONLY_UFUNC, + _REGISTERED_NO_DIFF_NUMPY_FUNCS, +) def test_no_autodiff_all_matches_registered_numpy_funcs(): from mygrad.no_grad_funcs import __all__ as all_no_autodiffs - assert set(all_no_autodiffs) >= set( - k.__name__ for k in _REGISTERED_NO_DIFF_NUMPY_FUNCS + all_no_autodiffs = set(all_no_autodiffs) + all_no_autodiffs.remove("mod") + + assert set(all_no_autodiffs) == set( + k.__name__ + for k in itertools.chain.from_iterable( + ( + _REGISTERED_NO_DIFF_NUMPY_FUNCS, + _REGISTERED_BOOL_ONLY_UFUNC, + _REGISTERED_CONST_ONLY_UFUNC, + ) + ) )