Repository: yt-project/unyt

Issue:
unyt processes units for FFTs incorrectly
* unyt version: 3.0.3
* Python version: 3.12
* Operating System: MacOS 

### Description

When using a fast Fourier transform, I expect the units that are returned to be the same as the original function. Dimensionally, the Fourier transform is a decomposition of the form:
$$
  f(x) ~= \int F(k) e^{-ikx} \cdot dk
$$
However, when using the `np.fft` functions on `unyt` arrays, they return units that are the inverse of the original array.

For example, the minimal working example below:

```python
import matplotlib.pyplot as plt
import unyt
import numpy as np

# Dirac Delta
x = unyt.unyt_array(np.linspace(-5, 5, 128), "s", name="Time")
y = unyt.unyt_array(np.zeros_like(x.v), "K", name="Temperature")
y[len(y) // 2] = 1

# FFT that thing
fft = np.fft.fft(y)
amps = np.sqrt((fft * fft.conj()).real)
dk = 2.0 * np.pi / (10.0 * unyt.s)
k = np.fft.fftfreq(len(x), d=1.0 / dk) * len(x)

k.name = "Wavenumber $k$"
amps.name = "FFT of Temperature"

# Plot it up
with unyt.matplotlib_support:
    fig, (axreal, axfft) = plt.subplots(1, 2, figsize=(6, 2.5))

    axreal.plot(x, y)
    axfft.plot(k, amps)

fig.tight_layout()

plt.show()
```

This produces the following figure:

<img width="879" alt="Screenshot 2024-10-25 at 8 20 19 AM" src="https://github.com/user-attachments/assets/f424a07f-2a50-41b7-996e-164781532950">



Discussion:

Patch:
diff --git a/unyt/_array_functions.py b/unyt/_array_functions.py
index 4b22dd2..646629e 100644
--- a/unyt/_array_functions.py
+++ b/unyt/_array_functions.py
@@ -358,72 +358,72 @@ def block(arrays):
 
 @implements(np.fft.fft)
 def ftt_fft(a, *args, **kwargs):
-    return np.fft.fft._implementation(np.asarray(a), *args, **kwargs) / a.units
+    return np.fft.fft._implementation(np.asarray(a), *args, **kwargs) * a.units
 
 
 @implements(np.fft.fft2)
 def ftt_fft2(a, *args, **kwargs):
-    return np.fft.fft2._implementation(np.asarray(a), *args, **kwargs) / a.units
+    return np.fft.fft2._implementation(np.asarray(a), *args, **kwargs) * a.units
 
 
 @implements(np.fft.fftn)
 def ftt_fftn(a, *args, **kwargs):
-    return np.fft.fftn._implementation(np.asarray(a), *args, **kwargs) / a.units
+    return np.fft.fftn._implementation(np.asarray(a), *args, **kwargs) * a.units
 
 
 @implements(np.fft.hfft)
 def ftt_hfft(a, *args, **kwargs):
-    return np.fft.hfft._implementation(np.asarray(a), *args, **kwargs) / a.units
+    return np.fft.hfft._implementation(np.asarray(a), *args, **kwargs) * a.units
 
 
 @implements(np.fft.rfft)
 def ftt_rfft(a, *args, **kwargs):
-    return np.fft.rfft._implementation(np.asarray(a), *args, **kwargs) / a.units
+    return np.fft.rfft._implementation(np.asarray(a), *args, **kwargs) * a.units
 
 
 @implements(np.fft.rfft2)
 def ftt_rfft2(a, *args, **kwargs):
-    return np.fft.rfft2._implementation(np.asarray(a), *args, **kwargs) / a.units
+    return np.fft.rfft2._implementation(np.asarray(a), *args, **kwargs) * a.units
 
 
 @implements(np.fft.rfftn)
 def ftt_rfftn(a, *args, **kwargs):
-    return np.fft.rfftn._implementation(np.asarray(a), *args, **kwargs) / a.units
+    return np.fft.rfftn._implementation(np.asarray(a), *args, **kwargs) * a.units
 
 
 @implements(np.fft.ifft)
 def ftt_ifft(a, *args, **kwargs):
-    return np.fft.ifft._implementation(np.asarray(a), *args, **kwargs) / a.units
+    return np.fft.ifft._implementation(np.asarray(a), *args, **kwargs) * a.units
 
 
 @implements(np.fft.ifft2)
 def ftt_ifft2(a, *args, **kwargs):
-    return np.fft.ifft2._implementation(np.asarray(a), *args, **kwargs) / a.units
+    return np.fft.ifft2._implementation(np.asarray(a), *args, **kwargs) * a.units
 
 
 @implements(np.fft.ifftn)
 def ftt_ifftn(a, *args, **kwargs):
-    return np.fft.ifftn._implementation(np.asarray(a), *args, **kwargs) / a.units
+    return np.fft.ifftn._implementation(np.asarray(a), *args, **kwargs) * a.units
 
 
 @implements(np.fft.ihfft)
 def ftt_ihfft(a, *args, **kwargs):
-    return np.fft.ihfft._implementation(np.asarray(a), *args, **kwargs) / a.units
+    return np.fft.ihfft._implementation(np.asarray(a), *args, **kwargs) * a.units
 
 
 @implements(np.fft.irfft)
 def ftt_irfft(a, *args, **kwargs):
-    return np.fft.irfft._implementation(np.asarray(a), *args, **kwargs) / a.units
+    return np.fft.irfft._implementation(np.asarray(a), *args, **kwargs) * a.units
 
 
 @implements(np.fft.irfft2)
 def ftt_irfft2(a, *args, **kwargs):
-    return np.fft.irfft2._implementation(np.asarray(a), *args, **kwargs) / a.units
+    return np.fft.irfft2._implementation(np.asarray(a), *args, **kwargs) * a.units
 
 
 @implements(np.fft.irfftn)
 def ftt_irfftn(a, *args, **kwargs):
-    return np.fft.irfftn._implementation(np.asarray(a), *args, **kwargs) / a.units
+    return np.fft.irfftn._implementation(np.asarray(a), *args, **kwargs) * a.units
 
 
 @implements(np.fft.fftshift)


Test patch:
diff --git a/unyt/tests/test_array_functions.py b/unyt/tests/test_array_functions.py
index 4903e93..bfd900a 100644
--- a/unyt/tests/test_array_functions.py
+++ b/unyt/tests/test_array_functions.py
@@ -923,7 +923,7 @@ def test_fft_1D(func):
     x1 = [0, 1, 2] * cm
     res = func(x1)
     assert type(res) is unyt_array
-    assert res.units == (1 / cm).units
+    assert res.units == (1 * cm).units
 
 
 @pytest.mark.parametrize(
@@ -943,7 +943,7 @@ def test_fft_ND(func):
     x1 = [[0, 1, 2], [0, 1, 2], [0, 1, 2]] * cm
     res = func(x1)
     assert type(res) is unyt_array
-    assert res.units == (1 / cm).units
+    assert res.units == (1 * cm).units
 
 
 @pytest.mark.parametrize("func", [np.fft.fftshift, np.fft.ifftshift])