10000 API: Add outer to numpy.linalg · numpy/numpy@66f8518 · GitHub
[go: up one dir, main page]

Skip to content

Commit 66f8518

Browse files
committed
API: Add outer to numpy.linalg
1 parent 0d28882 commit 66f8518

File tree

11 files changed

+117
-8
lines changed

11 files changed

+117
-8
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
``outer`` for `numpy.linalg`
2+
----------------------------
3+
4+
`numpy.linalg.outer` has been added. It computes the outer product of two vectors.
5+
It differs from `numpy.outer` by accepting one-dimensional arrays only.
6+
This function is compatible with Array API.

doc/source/reference/array_api.rst

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,8 @@ Function instead of method
134134
These functions are in the ``linalg`` sub-namespace in the array API, but are
135135
only in the top-level namespace in NumPy:
136136

137-
- ``diagonal``
138137
- ``matmul`` (*)
139-
- ``outer``
140138
- ``tensordot`` (*)
141-
- ``trace``
142139

143140
(*): These functions are also in the top-level namespace in the array API.
144141

doc/source/reference/routines.linalg.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ Decompositions
7272
:toctree: generated/
7373

7474
linalg.cholesky
75+
linalg.outer
7576
linalg.qr
7677
linalg.svd
7778

numpy/_core/numeric.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,8 @@ def outer(a, b, out=None):
873873
ufunc.outer : A generalization to dimensions other than 1D and other
874874
operations. ``np.multiply.outer(a.ravel(), b.ravel())``
875875
is the equivalent.
876+
linalg.outer : An Array API compatible variation of ``np.outer``,
877+
which accepts 1-dimensional inputs only.
876878
tensordot : ``np.tensordot(a.ravel(), b.ravel(), axes=((), ()))``
877879
is the equivalent.
878880

numpy/linalg/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
--------------
3535
3636
cholesky
37+
outer
3738
qr
3839
svd
3940

numpy/linalg/__init__.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ from numpy.linalg._linalg import (
55
tensorinv as tensorinv,
66
inv as inv,
77
cholesky as cholesky,
8+
outer as outer,
89
eigvals as eigvals,
910
eigvalsh as eigvalsh,
1011
pinv as pinv,

numpy/linalg/_linalg.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
__all__ = ['matrix_power', 'solve', 'tensorsolve', 'tensorinv', 'inv',
1313
'cholesky', 'eigvals', 'eigvalsh', 'pinv', 'slogdet', 'det',
1414
'svd', 'eig', 'eigh', 'lstsq', 'norm', 'qr', 'cond', 'matrix_rank',
15-
'LinAlgError', 'multi_dot', 'trace', 'diagonal', 'cross']
15+
'LinAlgError', 'multi_dot', 'trace', 'diagonal', 'cross', 'outer']
1616

1717
import functools
1818
import operator
@@ -27,7 +27,7 @@
2727
amax, prod, abs, atleast_2d, intp, asanyarray, object_, matmul,
2828
swapaxes, divide, count_nonzero, isnan, sign, argsort, sort,
2929
reciprocal, overrides, diagonal as _core_diagonal, trace as _core_trace,
30-
cross as _core_cross,
30+
cross as _core_cross, outer as _core_outer
3131
)
3232
from numpy.lib._twodim_base_impl import triu, eye
3333
from numpy.lib.array_utils import normalize_axis_index
@@ -814,8 +814,53 @@ def cholesky(a):
814814
return wrap(r.astype(result_t, copy=False))
815815

816816

817+
# outer product
818+
819+
820+
def _outer_dispatcher(x1, x2):
821+
return (x1, x2)
822+
823+
824+
@array_function_dispatch(_outer_dispatcher)
825+
def outer(x1, x2, /):
826+
"""
827+
Compute the outer product of two vectors.
828+
829+
This function is Array API compatible. Compared to ``np.outer``
830+
it accepts 1-dimensional inputs only.
831+
832+
Parameters
833+
----------
834+
x1 : (M,) array_like
835+
One-dimensional input array of size ``N``.
836+
Must have a numeric data type.
837+
x2 : (N,) array_like
838+
One-dimensional input array of size ``M``.
839+
Must have a numeric data type.
840+
841+
Returns
842+
-------
843+
out : (M, N) ndarray
844+
``out[i, j] = a[i] * b[j]``
845+
846+
See also
847+
--------
848+
outer
849+
850+
"""
851+
x1 = asarray(x1)
852+
x2 = asarray(x2)
853+
if x1.ndim != 1 or x2.ndim != 1:
854+
raise ValueError(
855+
"Input arrays must be one-dimensional, but they are "
856+
f"{x1.ndim=} and {x2.ndim=}."
857+
)
858+
return _core_outer(x1, x2, out=None)
859+
860+
817861
# QR decomposition
818862

863+
819864
def _qr_dispatcher(a, mode=None):
820865
return (a,)
821866

numpy/linalg/_linalg.pyi

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@ from typing import (
1010
Generic,
1111
)
1212

13+
import numpy as np
1314
from numpy import (
1415
generic,
1516
floating,
1617
complexfloating,
1718
signedinteger,
1819
unsignedinteger,
20+
timedelta64,
21+
object_,
1922
int32,
2023
float64,
2124
complex128,
@@ -26,6 +29,8 @@ from numpy.linalg import LinAlgError as LinAlgError
2629
from numpy._typing import (
2730
NDArray,
2831
ArrayLike,
32+
_ArrayLikeUnknown,
33+
_ArrayLikeBool_co,
2934
_ArrayLikeInt_co,
3035
_ArrayLikeUInt_co,
3136
_ArrayLikeFloat_co,
@@ -140,6 +145,35 @@ def cholesky(a: _ArrayLikeFloat_co) -> NDArray[floating[Any]]: ...
140145
@overload
141146
def cholesky(a: _ArrayLikeComplex_co) -> NDArray[complexfloating[Any, Any]]: ...
142147

148+
@overload
149+
def outer(x1: _ArrayLikeUnknown, x2: _ArrayLikeUnknown) -> NDArray[Any]: ...
150+
@overload
151+
def outer(x1: _ArrayLikeBool_co, x2: _ArrayLikeBool_co) -> NDArray[np.bool]: ...
152+
@overload
153+
def outer(x1: _ArrayLikeUInt_co, x2: _ArrayLikeUInt_co) -> NDArray[unsignedinteger[Any]]: ...
154+
@overload
155+
def outer(x1: _ArrayLikeInt_co, x2: _ArrayLikeInt_co) -> NDArray[signedinteger[Any]]: ...
156+
@overload
157+
def outer(x1: _ArrayLikeFloat_co, x2: _ArrayLikeFloat_co) -> NDArray[floating[Any]]: ...
158+
@overload
159+
def outer(
160+
x1: _ArrayLikeComplex_co,
161+
x2: _ArrayLikeComplex_co,
162+
) -> NDArray[complexfloating[Any, Any]]: ...
163+
@overload
164+
def outer(
165+
x1: _ArrayLikeTD64_co,
166+
x2: _ArrayLikeTD64_co,
167+
out: None = ...,
168+
) -> NDArray[timedelta64]: ...
169+
@overload
170+
def outer(x1: _ArrayLikeObject_co, x2: _ArrayLikeObject_co) -> NDArray[object_]: ...
171+
@overload
172+
def outer(
173+
x1: _ArrayLikeComplex_co | _ArrayLikeTD64_co | _ArrayLikeObject_co,
174+
x2: _ArrayLikeComplex_co | _ArrayLikeTD64_co | _ArrayLikeObject_co,
175+
) -> _ArrayType: ...
176+
143177
@overload
144178
def qr(a: _ArrayLikeInt_co, mode: _ModeKind = ...) -> QRResult: ...
145179
@overload

numpy/linalg/tests/test_linalg.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1842,6 +1842,23 @@ class ArraySubclass(np.ndarray):
18421842
assert_(isinstance(res, np.ndarray))
18431843

18441844

1845+
class TestOuter:
1846+
arr1 = np.arange(3)
1847+
arr2 = np.arange(3)
1848+
expected = np.array(
1849+
[[0, 0, 0],
1850+
[0, 1, 2],
1851+
[0, 2, 4]]
1852+
)
1853+
1854+
assert_array_equal(np.linalg.outer(arr1, arr2), expected)
1855+
1856+
with assert_raises_regex(
1857+
ValueError, "Input arrays must be one-dimensional"
1858+
):
1859+
np.linalg.outer(arr1[:, np.newaxis], arr2)
1860+
1861+
18451862
def test_byteorder_check():
18461863
# Byte order check should pass for native order
18471864
if sys.byteorder == 'little':

numpy/typing/tests/data/reveal/linalg.pyi

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ AR_c16: npt.NDArray[np.complex128]
1818
AR_O: npt.NDArray[np.object_]
1919
AR_m: npt.NDArray[np.timedelta64]
2020
AR_S: npt.NDArray[np.str_]
21+
AR_b: npt.NDArray[np.bool]
2122

2223
assert_type(np.linalg.tensorsolve(AR_i8, AR_i8), npt.NDArray[np.float64])
2324
assert_type(np.linalg.tensorsolve(AR_i8, AR_f8), npt.NDArray[np.floating[Any]])
@@ -44,6 +45,13 @@ assert_type(np.linalg.cholesky(AR_i8), npt.NDArray[np.float64])
4445
assert_type(np.linalg.cholesky(AR_f8), npt.NDArray[np.floating[Any]])
4546
assert_type(np.linalg.cholesky(AR_c16), npt.NDArray[np.complexfloating[Any, Any]])
4647

48+
assert_type(np.linalg.outer(AR_i8, AR_i8), npt.NDArray[np.signedinteger[Any]])
49+
assert_type(np.linalg.outer(AR_f8, AR_f8), npt.NDArray[np.floating[Any]])
50+
assert_type(np.linalg.outer(AR_c16, AR_c16), npt.NDArray[np.complexfloating[Any, Any]])
51+
assert_type(np.linalg.outer(AR_b, AR_b), npt.NDArray[np.bool])
52+
assert_type(np.linalg.outer(AR_O, AR_O), npt.NDArray[np.object_])
53+
assert_type(np.linalg.outer(AR_i8, AR_m), npt.NDArray[np.timedelta64])
54+
4755
assert_type(np.linalg.qr(AR_i8), QRResult)
4856
assert_type(np.linalg.qr(AR_f8), QRResult)
4957
assert_type(np.linalg.qr(AR_c16), QRResult)

0 commit comments

Comments
 (0)
0