Repository: yt-project/unyt

Issue:
NEP 18 progress tracker
We want to implement NEP 18 progressively on main. #200 will be the first step towards that goal. This issue will be used to keep track of what's left to do in this project.

The full list of functions that need re-implementation is found here https://github.com/numpy/numpy/issues/15544#issuecomment-584453408

Here it is, converted to a TODO list (the `numpy.` namespace is implicit everywhere)

<details> <summary> TODO </summary>

- [ ] allclose
- [ ] amax
- [ ] amin
- [ ] angle
- [ ] any
- [ ] append
- [ ] apply_along_axis
- [ ] apply_over_axes
- [ ] argmax
- [ ] argmin
- [ ] argpartition
- [ ] argsort
- [ ] argwhere
- [ ] around
- [ ] array_equal
- [ ] array_equiv
- [ ] array_repr
- [ ] array_split
- [ ] array_str
- [ ] asfarray
- [ ] asscalar
- [ ] atleast_1d
- [ ] atleast_2d
- [ ] atleast_3d
- [ ] average
- [ ] bincount
- [ ] block
- [ ] broadcast_arrays
- [ ] broadcast_to
- [ ] busday_count
- [ ] busday_offset
- [ ] can_cast
- [ ] choose
- [ ] clip
- [ ] column_stack
- [ ] common_type
- [ ] compress
- [ ] convolve
- [ ] copy
- [ ] copyto
- [ ] corrcoef
- [ ] correlate
- [ ] count_nonzero
- [ ] cov
- [ ] cumprod
- [ ] cumproduct
- [ ] cumsum
- [ ] datetime_as_string
- [ ] delete
- [ ] diag
- [ ] diag_indices_from
- [ ] diagflat
- [ ] diagonal
- [ ] diff
- [ ] digitize
- [ ] dsplit
- [ ] dstack
- [ ] ediff1d
- [ ] einsum
- [ ] einsum_path
- [ ] empty_like
- [ ] expand_dims
- [ ] extract
- [ ] fft.fft
- [ ] fft.fft2
- [ ] fft.fftn
- [ ] fft.fftshift
- [ ] fft.hfft
- [ ] fft.ifft
- [ ] fft.ifft2
- [ ] fft.ifftn
- [ ] fft.ifftshift
- [ ] fft.ihfft
- [ ] fft.irfft
- [ ] fft.irfft2
- [ ] fft.irfftn
- [ ] fft.rfft
- [ ] fft.rfft2
- [ ] fft.rfftn
- [ ] fill_diagonal
- [ ] fix
- [ ] flatnonzero
- [ ] flip
- [ ] fliplr
- [ ] flipud
- [ ] full_like
- [ ] fv
- [ ] geomspace
- [ ] gradient
- [ ] histogram_bin_edges
- [ ] hsplit
- [ ] i0
- [ ] imag
- [ ] in1d
- [ ] insert
- [ ] interp
- [ ] ipmt
- [ ] irr
- [ ] is_busday
- [ ] isclose
- [ ] iscomplex
- [ ] iscomplexobj
- [ ] isin
- [ ] isneginf
- [ ] isposinf
- [ ] isreal
- [ ] isrealobj
- [ ] ix_
- [ ] lexsort
- [ ] lib.scimath.arccos
- [ ] lib.scimath.arcsin
- [ ] lib.scimath.arctanh
- [ ] lib.scimath.log
- [ ] lib.scimath.log10
- [ ] lib.scimath.log2
- [ ] lib.scimath.logn
- [ ] lib.scimath.power
- [ ] lib.scimath.sqrt
- [ ] linalg.cholesky
- [ ] linalg.cond
- [ ] linalg.det
- [ ] linalg.eig
- [ ] linalg.eigh
- [ ] linalg.eigvals
- [ ] linalg.eigvalsh
- [ ] linalg.lstsq
- [ ] linalg.matrix_power
- [ ] linalg.matrix_rank
- [ ] linalg.multi_dot
- [ ] linalg.qr
- [ ] linalg.slogdet
- [ ] linalg.solve
- [ ] linalg.svd
- [ ] linalg.tensorsolve
- [ ] linspace
- [ ] logspace
- [ ] may_share_memory
- [ ] mean
- [ ] median
- [ ] meshgrid
- [ ] min_scalar_type
- [ ] mirr
- [ ] moveaxis
- [ ] msort
- [ ] nan_to_num
- [ ] nanargmax
- [ ] nanargmin
- [ ] nancumprod
- [ ] nancumsum
- [ ] nanmax
- [ ] nanmean
- [ ] nanmedian
- [ ] nanmin
- [ ] nanpercentile
- [ ] nanprod
- [ ] nanquantile
- [ ] nanstd
- [ ] nansum
- [ ] nanvar
- [ ] ndim
- [ ] nonzero
- [ ] nper
- [ ] npv
- [ ] ones_like
- [ ] packbits
- [ ] pad
- [ ] partition
- [ ] percentile
- [ ] piecewise
- [ ] place
- [ ] pmt
- [ ] poly
- [ ] polyadd
- [ ] polyder
- [ ] polydiv
- [ ] polyfit
- [ ] polyint
- [ ] polymul
- [ ] polysub
- [ ] polyval
- [ ] ppmt
- [ ] prod
- [ ] product
- [ ] ptp
- [ ] put
- [ ] put_along_axis
- [ ] putmask
- [ ] pv
- [ ] quantile
- [ ] rate
- [ ] ravel
- [ ] ravel_multi_index
- [ ] real
- [ ] real_if_close
- [ ] repeat
- [ ] reshape
- [ ] resize
- [ ] result_type
- [ ] roll
- [ ] rollaxis
- [ ] roots
- [ ] rot90
- [ ] round_
- [ ] save
- [ ] savetxt
- [ ] savez
- [ ] savez_compressed
- [ ] searchsorted
- [ ] select
- [ ] setdiff1d
- [ ] setxor1d
- [ ] shape
- [ ] shares_memory
- [ ] sinc
- [ ] size
- [ ] sometrue
- [ ] sort
- [ ] sort_complex
- [ ] split
- [ ] squeeze
- [ ] std
- [ ] sum
- [ ] swapaxes
- [ ] take
- [ ] take_along_axis
- [ ] tensordot
- [ ] tile
- [ ] trace
- [ ] transpose
- [ ] trapz
- [ ] tril
- [ ] tril_indices_from
- [ ] trim_zeros
- [ ] triu
- [ ] triu_indices_from
- [ ] unique
- [ ] unpackbits
- [ ] unravel_index
- [ ] unwrap
- [ ] vander
- [ ] var
- [ ] vsplit
- [ ] where
- [ ] zeros_like

</details>

### Invalid (nothing to be done)

#### deprecated API
- alen
- alltrue

#### API that applies to non-numeric types

- all
- `numpy.char` namespace
<details> <summary> full list (numpy.char) </summary>

-  char.add
-  char.capitalize
-  char.center
-  char.count
-  char.decode
-  char.encode
-  char.endswith
-  char.equal
-  char.expandtabs
-  char.find
-  char.greater
-  char.greater_equal
-  char.index
-  char.isalnum
-  char.isalpha
-  char.isdecimal
-  char.isdigit
-  char.islower
-  char.isnumeric
-  char.isspace
-  char.istitle
-  char.isupper
-  char.join
-  char.less
-  char.less_equal
-  char.ljust
-  char.lower
-  char.lstrip
-  char.mod
-  char.multiply
-  char.not_equal
-  char.partition
-  char.replace
-  char.rfind
-  char.rindex
-  char.rjust
-  char.rpartition
-  char.rsplit
-  char.rstrip
-  char.split
-  char.splitlines
-  char.startswith
-  char.str_len
-  char.strip
-  char.swapcase
-  char.title
-  char.translate
-  char.upper
-  char.zfill

</details>


### In progress

### DONE

<details> <summary> #200 </summary>

- [x] array2string
- [x] dot
- [x] histogram
- [x] histogram2d
- [x] histogramdd
- [x] inner
- [x] kron
- [x] linalg.inv
- [x] linalg.pinv
- [x] linalg.tensorinv
- [x] outer
- [x] vdot

</details>

<details> <summary> #293 </summary>

- [x] concatenate
- [x] cross
- [x] hstack
- [x] intersect1d
- [x] linalg.norm
- [x] stack
- [x] union1d
- [x] vstack

</details>

Discussion:

Patch:
diff --git a/unyt/_array_functions.py b/unyt/_array_functions.py
index 06232e4..ae2e811 100644
--- a/unyt/_array_functions.py
+++ b/unyt/_array_functions.py
@@ -37,6 +37,7 @@ _UNSUPPORTED_FUNCTIONS = {
     np.piecewise,  # astropy.units doens't have a simple implementation either
     np.packbits,
     np.unpackbits,
+    np.ix_,
 }
 
 _HANDLED_FUNCTIONS = {}
@@ -119,6 +120,19 @@ def linalg_pinv(a, *args, **kwargs):
     return np.linalg.pinv._implementation(a, *args, **kwargs).view(np.ndarray) / a.units
 
 
+@implements(np.linalg.svd)
+def linalg_svd(a, full_matrices=True, compute_uv=True, *args, **kwargs):
+    ret_units = a.units
+    retv = np.linalg.svd._implementation(
+        a.view(np.ndarray), full_matrices, compute_uv, *args, **kwargs
+    )
+    if compute_uv:
+        u, s, vh = retv
+        return (u, s * ret_units, vh)
+    else:
+        return retv * ret_units
+
+
 def _sanitize_range(_range, units):
     # helper function to histogram* functions
     ndim = len(units)


Test patch:
diff --git a/unyt/tests/test_array_functions.py b/unyt/tests/test_array_functions.py
index b75e6a0..c2f1a12 100644
--- a/unyt/tests/test_array_functions.py
+++ b/unyt/tests/test_array_functions.py
@@ -163,13 +163,6 @@ IGNORED_FUNCTIONS = {
 }
 
 
-# this set represents all functions that need inspection, tests, or both
-# it is always possible that some of its elements belong in NOOP_FUNCTIONS
-TODO_FUNCTIONS = {
-    np.ix_,
-    np.linalg.svd,
-}
-
 DEPRECATED_FUNCTIONS = {
     "alen",  # deprecated in numpy 1.18, removed in 1.22
     "asscalar",  # deprecated in numpy 1.18, removed in 1.22
@@ -189,9 +182,7 @@ DEPRECATED_FUNCTIONS = {
     "cumproduct",  # deprecated in numpy 1.25
 }
 
-NOT_HANDLED_FUNCTIONS = (
-    NOOP_FUNCTIONS | TODO_FUNCTIONS | UNSUPPORTED_FUNCTIONS | IGNORED_FUNCTIONS
-)
+NOT_HANDLED_FUNCTIONS = NOOP_FUNCTIONS | UNSUPPORTED_FUNCTIONS | IGNORED_FUNCTIONS
 
 for func in DEPRECATED_FUNCTIONS:
     if hasattr(np, func):
@@ -1166,6 +1157,19 @@ def test_eigvals(func):
     assert w.units == cm
 
 
+def test_linalg_svd():
+    a = (np.random.randn(9, 6) + 1j * np.random.randn(9, 6)) * cm
+    u, s, vh = np.linalg.svd(a)
+    assert type(u) is np.ndarray
+    assert type(vh) is np.ndarray
+    assert type(s) is unyt_array
+    assert s.units == cm
+
+    s = np.linalg.svd(a, compute_uv=False)
+    assert type(s) is unyt_array
+    assert s.units == cm
+
+
 def test_savetxt(tmp_path):
     a = [1, 2, 3] * cm
     with pytest.raises(