-
Notifications
You must be signed in to change notification settings - Fork 24
fix: norm scaling #370
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
fix: norm scaling #370
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -78,6 +78,43 @@ def _compute_fwd_scale(norm, n, shape): | |||||||||
| return np.sqrt(fsc) | ||||||||||
|
|
||||||||||
|
|
||||||||||
| def _compute_nd_scale_shape(x, s, axes, norm=None, invreal=False): | ||||||||||
| """ | ||||||||||
| Resolve the lengths that a norm-scaled N-D transform normalizes over. | ||||||||||
|
|
||||||||||
| ``_compute_fwd_scale`` falls back to the full array shape when ``s`` is | ||||||||||
| None. That over-normalizes when only a subset of axes is transformed, and | ||||||||||
| for c2r transforms the basis is the *output* length along the last | ||||||||||
| transformed axis rather than the input one. | ||||||||||
|
|
||||||||||
| This mirrors what the ``numpy_fft`` and ``scipy_fft`` interfaces already | ||||||||||
| do by calling ``_cook_nd_args`` before delegating. Only the scale basis is | ||||||||||
| resolved here; ``s`` itself is left alone so that dispatch in | ||||||||||
| ``_c2c_fftnd_impl`` is unchanged. | ||||||||||
|
|
||||||||||
| ``norm`` is accepted only to skip the work for the unscaled norms, whose | ||||||||||
| scale is 1.0 regardless of shape. Invalid values fall through to | ||||||||||
| ``_compute_fwd_scale``, which validates them. | ||||||||||
| """ | ||||||||||
|
|
||||||||||
| if s is not None or norm in (None, "backward"): | ||||||||||
| return s | ||||||||||
| try: | ||||||||||
| if axes is None: | ||||||||||
| ss = list(x.shape) | ||||||||||
| last = len(ss) - 1 | ||||||||||
| else: | ||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Empty axis set → scale 1.0, matches numpy:
Suggested change
It probably would be good to cover that in tests |
||||||||||
| ss = [x.shape[ai] for ai in axes] | ||||||||||
| last = axes[-1] | ||||||||||
| if invreal: | ||||||||||
| ss[-1] = 2 * (x.shape[last] - 1) | ||||||||||
| except (IndexError, TypeError): | ||||||||||
| # invalid or empty axes; leave the scale alone and let the | ||||||||||
| # transform itself raise | ||||||||||
| return s | ||||||||||
| return tuple(ss) | ||||||||||
|
|
||||||||||
|
|
||||||||||
| def _cook_nd_args(a, s=None, axes=None, invreal=False): | ||||||||||
| if s is None: | ||||||||||
| shapeless = True | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,230 @@ | ||||||||||||||||||||||||||||||||
| """Cross-library equivalence checks for axis and axes dispatch. | ||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to add copyright header? |
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| ``third_party/scipy/test_basic.py::test_fft_with_order`` already checks that | ||||||||||||||||||||||||||||||||
| mkl_fft agrees with *itself* across C, Fortran, and non-contiguous layouts. It | ||||||||||||||||||||||||||||||||
| does not compare against an external reference, so a dispatch change that is | ||||||||||||||||||||||||||||||||
| consistently wrong in every layout passes it. | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| The defect recorded in ``_fft_utils._iter_complementary`` was exactly that | ||||||||||||||||||||||||||||||||
| kind: values correct, but an element placed in the other half of the output | ||||||||||||||||||||||||||||||||
| relative to NumPy. These tests therefore use ``numpy.fft`` as the reference. | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Two deliberate choices: | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| * Every axis length differs, so an axis permutation cannot produce a | ||||||||||||||||||||||||||||||||
| correctly shaped result and hide behind a shape assertion. | ||||||||||||||||||||||||||||||||
| * Output dtype is asserted alongside values, so a dispatch change cannot | ||||||||||||||||||||||||||||||||
| silently upcast. | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| These cover the paths that dispatch on *which* axes are requested: full-axes | ||||||||||||||||||||||||||||||||
| transforms reach the batched N-D descriptor, strict subsets iterate the | ||||||||||||||||||||||||||||||||
| complementary axes, and 1-D transforms of rank > 2 arrays are batched only for | ||||||||||||||||||||||||||||||||
| the first and last axis. | ||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| import itertools | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| import numpy as np | ||||||||||||||||||||||||||||||||
| import pytest | ||||||||||||||||||||||||||||||||
| from numpy.testing import assert_allclose | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| import mkl_fft | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| _SHAPE_3D = (8, 7, 13) | ||||||||||||||||||||||||||||||||
| _SHAPE_4D = (4, 5, 6, 7) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| _DTYPES = ["float32", "float64", "complex64", "complex128"] | ||||||||||||||||||||||||||||||||
| _REAL_DTYPES = ["float32", "float64"] | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| _ORDERS = ["C", "F", "non-contiguous"] | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Relative tolerance by input precision. Single-precision transforms of | ||||||||||||||||||||||||||||||||
| # random data over these lengths stay well inside 2e-5. | ||||||||||||||||||||||||||||||||
| _TOL = { | ||||||||||||||||||||||||||||||||
| "float32": 2e-5, | ||||||||||||||||||||||||||||||||
| "complex64": 2e-5, | ||||||||||||||||||||||||||||||||
| "float64": 1e-12, | ||||||||||||||||||||||||||||||||
| "complex128": 1e-12, | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # every non-empty subset of the axes of a 3-D array, plus None | ||||||||||||||||||||||||||||||||
| _AXES_3D = [ | ||||||||||||||||||||||||||||||||
| ax for n in (1, 2, 3) for ax in itertools.combinations(range(3), n) | ||||||||||||||||||||||||||||||||
| ] + [None] | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def _make(shape, dtype, seed=42): | ||||||||||||||||||||||||||||||||
| rng = np.random.default_rng(seed) | ||||||||||||||||||||||||||||||||
| dt = np.dtype(dtype) | ||||||||||||||||||||||||||||||||
| if dt.kind == "c": | ||||||||||||||||||||||||||||||||
| x = rng.standard_normal(shape) + 1j * rng.standard_normal(shape) | ||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||
| x = rng.standard_normal(shape) | ||||||||||||||||||||||||||||||||
| return x.astype(dt) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def _relayout(x, order): | ||||||||||||||||||||||||||||||||
| """Return *x* laid out as requested; data content may differ by order.""" | ||||||||||||||||||||||||||||||||
| if order == "F": | ||||||||||||||||||||||||||||||||
| return np.asfortranarray(x) | ||||||||||||||||||||||||||||||||
| if order == "non-contiguous": | ||||||||||||||||||||||||||||||||
| return x[::-1] | ||||||||||||||||||||||||||||||||
| return np.ascontiguousarray(x) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def _check(got, want, dtype): | ||||||||||||||||||||||||||||||||
| assert got.dtype == want.dtype, f"dtype {got.dtype} != {want.dtype}" | ||||||||||||||||||||||||||||||||
| assert got.shape == want.shape, f"shape {got.shape} != {want.shape}" | ||||||||||||||||||||||||||||||||
| tol = _TOL[dtype] | ||||||||||||||||||||||||||||||||
| assert_allclose( | ||||||||||||||||||||||||||||||||
| got, want, rtol=tol, atol=tol * max(1.0, float(np.abs(want).max())) | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # --------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||
| # N-D complex transforms over a subset of axes | ||||||||||||||||||||||||||||||||
| # --------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("func", ["fftn", "ifftn"]) | ||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("dtype", _DTYPES) | ||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("axes", _AXES_3D) | ||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("order", _ORDERS) | ||||||||||||||||||||||||||||||||
| def test_fftn_axes_subset(func, dtype, axes, order): | ||||||||||||||||||||||||||||||||
| x = _relayout(_make(_SHAPE_3D, dtype), order) | ||||||||||||||||||||||||||||||||
| got = getattr(mkl_fft, func)(x, axes=axes) | ||||||||||||||||||||||||||||||||
| want = getattr(np.fft, func)(x, axes=axes) | ||||||||||||||||||||||||||||||||
| _check(got, want, dtype) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("func", ["rfftn", "irfftn"]) | ||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("dtype", _DTYPES) | ||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("axes", _AXES_3D) | ||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("order", _ORDERS) | ||||||||||||||||||||||||||||||||
| def test_rfftn_axes_subset(func, dtype, axes, order): | ||||||||||||||||||||||||||||||||
| if func == "rfftn" and dtype not in _REAL_DTYPES: | ||||||||||||||||||||||||||||||||
| pytest.skip("rfftn takes real input") | ||||||||||||||||||||||||||||||||
| x = _relayout(_make(_SHAPE_3D, dtype), order) | ||||||||||||||||||||||||||||||||
| got = getattr(mkl_fft, func)(x, axes=axes) | ||||||||||||||||||||||||||||||||
| want = getattr(np.fft, func)(x, axes=axes) | ||||||||||||||||||||||||||||||||
| _check(got, want, dtype) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # --------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||
| # 1-D transforms along each axis of a higher-rank array | ||||||||||||||||||||||||||||||||
| # --------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("func", ["fft", "ifft"]) | ||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("dtype", _DTYPES) | ||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("axis", range(len(_SHAPE_3D))) | ||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("order", _ORDERS) | ||||||||||||||||||||||||||||||||
| def test_fft_axis_3d(func, dtype, axis, order): | ||||||||||||||||||||||||||||||||
| x = _relayout(_make(_SHAPE_3D, dtype), order) | ||||||||||||||||||||||||||||||||
| got = getattr(mkl_fft, func)(x, axis=axis) | ||||||||||||||||||||||||||||||||
| want = getattr(np.fft, func)(x, axis=axis) | ||||||||||||||||||||||||||||||||
| _check(got, want, dtype) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("func", ["fft", "ifft", "rfft"]) | ||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("dtype", ["float64", "complex128"]) | ||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("axis", range(len(_SHAPE_4D))) | ||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("order", _ORDERS) | ||||||||||||||||||||||||||||||||
| def test_fft_axis_4d(func, dtype, axis, order): | ||||||||||||||||||||||||||||||||
| """A rank-4 array has two interior axes, so the per-vector fallback in the | ||||||||||||||||||||||||||||||||
| C backend is exercised twice within one sweep. | ||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||
| if func == "rfft" and dtype != "float64": | ||||||||||||||||||||||||||||||||
| pytest.skip("rfft takes real input") | ||||||||||||||||||||||||||||||||
| x = _relayout(_make(_SHAPE_4D, dtype), order) | ||||||||||||||||||||||||||||||||
| got = getattr(mkl_fft, func)(x, axis=axis) | ||||||||||||||||||||||||||||||||
| want = getattr(np.fft, func)(x, axis=axis) | ||||||||||||||||||||||||||||||||
| _check(got, want, dtype) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("func", ["rfft", "irfft"]) | ||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("dtype", _DTYPES) | ||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("axis", range(len(_SHAPE_3D))) | ||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("order", _ORDERS) | ||||||||||||||||||||||||||||||||
| def test_rfft_axis_3d(func, dtype, axis, order): | ||||||||||||||||||||||||||||||||
| if func == "rfft" and dtype not in _REAL_DTYPES: | ||||||||||||||||||||||||||||||||
| pytest.skip("rfft takes real input") | ||||||||||||||||||||||||||||||||
| x = _relayout(_make(_SHAPE_3D, dtype), order) | ||||||||||||||||||||||||||||||||
| got = getattr(mkl_fft, func)(x, axis=axis) | ||||||||||||||||||||||||||||||||
| want = getattr(np.fft, func)(x, axis=axis) | ||||||||||||||||||||||||||||||||
| _check(got, want, dtype) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # --------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||
| # norm interacts with the scale factor applied at dispatch time | ||||||||||||||||||||||||||||||||
| # --------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("func", ["fftn", "ifftn"]) | ||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("dtype", ["float64", "complex128"]) | ||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("axes", [(0,), (1,), (2,), (1, 2), None]) | ||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("norm", [None, "backward", "forward", "ortho"]) | ||||||||||||||||||||||||||||||||
| def test_fftn_axes_subset_norm(func, dtype, axes, norm): | ||||||||||||||||||||||||||||||||
| x = _make(_SHAPE_3D, dtype) | ||||||||||||||||||||||||||||||||
| got = getattr(mkl_fft, func)(x, axes=axes, norm=norm) | ||||||||||||||||||||||||||||||||
| want = getattr(np.fft, func)(x, axes=axes, norm=norm) | ||||||||||||||||||||||||||||||||
| _check(got, want, dtype) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("func", ["rfftn", "irfftn"]) | ||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("dtype", ["float64", "complex128"]) | ||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("axes", [(0,), (1,), (2,), (1, 2), None]) | ||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("norm", [None, "backward", "forward", "ortho"]) | ||||||||||||||||||||||||||||||||
| def test_rfftn_axes_subset_norm(func, dtype, axes, norm): | ||||||||||||||||||||||||||||||||
| """Includes ``axes=None``: for c2r the scale basis is the *output* length | ||||||||||||||||||||||||||||||||
| along the last transformed axis, so a full-axes irfftn is normalized over | ||||||||||||||||||||||||||||||||
| ``2 * (n - 1)`` rather than ``n``. | ||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||
| if func == "rfftn" and dtype != "float64": | ||||||||||||||||||||||||||||||||
| pytest.skip("rfftn takes real input") | ||||||||||||||||||||||||||||||||
| x = _make(_SHAPE_3D, dtype) | ||||||||||||||||||||||||||||||||
| got = getattr(mkl_fft, func)(x, axes=axes, norm=norm) | ||||||||||||||||||||||||||||||||
| want = getattr(np.fft, func)(x, axes=axes, norm=norm) | ||||||||||||||||||||||||||||||||
| _check(got, want, dtype) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("func", ["fft2", "ifft2", "rfft2", "irfft2"]) | ||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("dtype", ["float64", "complex128"]) | ||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("norm", [None, "backward", "forward", "ortho"]) | ||||||||||||||||||||||||||||||||
| def test_fft2_on_3d_norm(func, dtype, norm): | ||||||||||||||||||||||||||||||||
| """``fft2`` on a rank-3 array transforms 2 of 3 axes, so it is a subset | ||||||||||||||||||||||||||||||||
| transform even though the caller passed no ``axes``. | ||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||
| if func == "rfft2" and dtype != "float64": | ||||||||||||||||||||||||||||||||
| pytest.skip("rfft2 takes real input") | ||||||||||||||||||||||||||||||||
| x = _make(_SHAPE_3D, dtype) | ||||||||||||||||||||||||||||||||
| got = getattr(mkl_fft, func)(x, norm=norm) | ||||||||||||||||||||||||||||||||
| want = getattr(np.fft, func)(x, norm=norm) | ||||||||||||||||||||||||||||||||
| _check(got, want, dtype) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("func", ["fft", "ifft"]) | ||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("dtype", ["float64", "complex128"]) | ||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("axis", range(len(_SHAPE_3D))) | ||||||||||||||||||||||||||||||||
| @pytest.mark.parametrize("norm", [None, "backward", "forward", "ortho"]) | ||||||||||||||||||||||||||||||||
| def test_fft_axis_norm(func, dtype, axis, norm): | ||||||||||||||||||||||||||||||||
| x = _make(_SHAPE_3D, dtype) | ||||||||||||||||||||||||||||||||
| got = getattr(mkl_fft, func)(x, axis=axis, norm=norm) | ||||||||||||||||||||||||||||||||
| want = getattr(np.fft, func)(x, axis=axis, norm=norm) | ||||||||||||||||||||||||||||||||
| _check(got, want, dtype) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # --------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||
| # out= must not change results on any dispatch path | ||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice fix — the norm×subset-axes and c2r output-length bugs are correctly resolved, and the new Gap A —
|
||||||||||||||||||||||||||||||||
| Missing case | Why it matters | Example |
|---|---|---|
out= + scaled norm= |
The PR's whole subject — the scale is written into out; nothing checks the scaled-write path |
mkl_fft.fftn(x, axes=(0,), norm="forward", out=out) |
out= on ifftn |
Shares the out path but never exercised with it |
mkl_fft.ifftn(x, out=out) |
out= on rfftn |
r2c: out is complex with reduced last axis n//2+1 — different allocation than c2c |
out = np.empty((8,7,7), complex128); mkl_fft.rfftn(xr, out=out) |
out= on irfftn (+ norm) |
c2r: out is real with the expanded 2*(n-1) last axis — exactly the length the invreal branch computes |
out = np.empty((8,7,24), float64); mkl_fft.irfftn(xc, norm="forward", out=out) |
Gap B — s= combined with a scaled norm
The helper deliberately early-returns when s is not None, so _compute_fwd_scale uses prod(s). That branch is only tested without norm; the s= × scaled-norm intersection is untested.
| Missing case | Why it matters | Example |
|---|---|---|
s= pads a subset axis + forward/ortho |
Scale must be prod(s) (padded length), not the original axis length |
mkl_fft.fftn(x, s=(16,), axes=(0,), norm="forward") |
s= truncates + scaled norm |
Same, downward | mkl_fft.fftn(x, s=(4,), axes=(0,), norm="ortho") |
irfftn with explicit s + scaled norm |
When s is given, the invreal doubling 2*(n-1) must not apply — a regression that double-applies it only surfaces here |
mkl_fft.irfftn(xc, s=(8,7,20), norm="forward") → normalizes over 20, not 2*(13-1) |
rfftn with s= on a subset + scaled norm |
Locks the r2c s-given path with scaling |
mkl_fft.rfftn(xr, s=(10,), axes=(1,), norm="ortho") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Links to PR are missing