scipy / scipy

SciPy library main repository
https://scipy.org
BSD 3-Clause "New" or "Revised" License
13.08k stars 5.19k forks source link

ENH: Determine current fft backend in use (for specified inputs) #14758

Open anntzer opened 3 years ago

anntzer commented 3 years ago

Is your feature request related to a problem? Please describe.

I would like a way to know which backend (e.g. scipy's own fft or pyfftw.interfaces.scipy_fft) would be used when e.g. scipy.fft.fft(some_specified_object) is called. (See below for a proposed API.)

The reason is that I would like my library to use the explicit pyfftw class-based API iff the end user has configured the pyfftw backend on scipy (via scipy.fft.set_backend or a variant thereof), thus reusing the configuration on scipy and avoiding a separate configuration knob on my library.

Describe the solution you'd like.

A possible API would be something like scipy.fft.fft.get_backend(*args, **kwargs) returning the backend that scipy.fft.fft(*args, **kwargs) would dispatch to (as an object compatible with set_backend, so either "scipy" or pyfftw.interfaces.scipy_fft, in the case above).

Describe alternatives you've considered.

No response

Additional context (e.g. screenshots, GIFs)

No response

rgommers commented 3 years ago

Interesting, I don't think this has been requested before (Cc @hameerabbasi, @peterbell10 in case I missed it).

The reason is that I would like my library to use the explicit pyfftw class-based API iff the end user has configured the pyfftw backend on scipy (via scipy.fft.set_backend or a variant thereof), thus reusing the configuration on scipy and avoiding a separate configuration knob on my library.

Is there a reason you want your library's choice depend on the user choice, rather than always using pyfftw if it's installed? Is it not always faster, or is it about deterministic behavior (which should not depend on optional installed packages), or ... ?

anntzer commented 3 years ago

The point is to have deterministic behavior.

hameerabbasi commented 3 years ago

I'm interested... Why not just set the backend to pyfftw manually rather than have a configuration knob for deterministic behaviour?

Use set_backend as a context Manager inside each function. I think there's even a contextlib utility function to turn that into a decorator instead of a context manager.

anntzer commented 3 years ago

I don't want to add a dependency on pyfftw (in particular because they still don't have py39 wheels available, but that's a bit of a side point).

hameerabbasi commented 3 years ago

Even if we were able to return an object with your proposed get_backend, you would need something to compare it to, and doing that cleanly would require a dependency on pyfftw. Is pyfftw an example here, or do you have other variants of your class for other backends? Or do you need any backend but just want it to be the same one every time?

Forgive the questions, I'm just wondering if there's a way to solve your use-case without extending uarray/SciPy, or to do it in a cleaner manner.

anntzer commented 3 years ago

I don't need a dependency on pyfftw; I can check e.g. getattr(backend, "__name__", None) == "pyfftw.interfaces.scipy_fft" which would work for practical purposes (or get_backend can itself directly return a str, but that seems worse -- now you need to spec the mapping between backends and strs). Currently there's only specific support for pyfftw, but that's just because that's the alternate backend I'm most familiar with; there's no reason not to support more of them. Basically, the end result would be something like

backend = get_backend(inputs)  # whatever API we decide on
backend_name = getattr(backend, "__name__", None)
if backend_name == "pyfftw.interfaces.scipy_fft":
    import pyfftw  # OK, we know pyfftw is indeed available, so we can import it
    enable_pyfftw_specific_path()
elif backend_name == "<whatever>":
    import whatever
    enable_whatever_specific_path()
elif ...:
    ...
elif backend == "scipy":  # fallback
    enable_scipy_specific_path()
else:  # some unknown backend, may or may not emit warning
    warnings.warn("Don't know this backend, falling back to scipy.fft")
    enable_scipy_specific_path()
hameerabbasi commented 3 years ago

Ah, in that case, I would add multimethods and register them with the correct backend instead of actually playing around with backends in this manner. See the uarray docs.

cc @peterbell10 do we have a common registration mechanism that was added to SciPy's backends?

anntzer commented 3 years ago

I have a few concerns with that approach:

anntzer commented 2 years ago

Not really in any hurry, but @rgommers' recent email on array type dispatch seems like a good opportunity to re-raise this?

rgommers commented 2 years ago

Ah yes, thanks for pointing this out @anntzer. We should address this and the other open issues related to the fft backend over the next month or two. I added a new uarray label.

hameerabbasi commented 2 years ago

@anntzer I'm in the process of releasing wheels for 3.9 and 3.10. If you need examples, there are many in the Quansight-Labs/unumpy repo.

rgommers commented 2 years ago

There is a separate issue about improving the docs for the scipy.fft backend. We should also take apart one or two actual implementations, and annotate them thoroughly in the developer docs and ensure it's clear what each line of code is for.

That's still separate from @anntzer's third point here:

It is not clear to me whether I need to register a multimethod implementation for each of the backends I want to support, and if so whether I can do that without adding a dependency on the backend (e.g. pyfftw) at the toplevel.

peterbell10 commented 2 years ago

uarray has got an internal determine_backend function which uses __ua_convert__ to figure out which backend will accept input of the given type. I think this should roughly achieve what you're after:

def get_backend(func, *args, **kwargs):
    dispatchables = func.arg_extractor(*args, **kwargs)
    return _uarray.determine_backend(func.domain, dispatchables, True)

One major problem though is that SciPy's backend doesn't implement __ua_convert__, so determine_backend will never pick it even if dispatch might.

anntzer commented 2 years ago

Thanks for the suggestion. However, unless I am mistaken(?), no released version of scipy currently includes determine_backend?

anntzer commented 2 years ago

I tried this with the just released scipy 1.8.0rc1, but I get

In [13]: get_backend(scipy.fft.fft, np.random.rand(10))
---------------------------------------------------------------------------
BackendNotImplementedError                Traceback (most recent call last)
<ipython-input-13-bc4b0a480613> in <module>
----> 1 get_backend(scipy.fft.fft, np.random.rand(10))

<ipython-input-1-fa23ddecdc1c> in get_backend(func, *args, **kwargs)
      1 def get_backend(func, *args, **kwargs):
      2     dispatchables = func.arg_extractor(*args, **kwargs)
----> 3     return _uarray.determine_backend(func.domain, dispatchables, True)
      4 

BackendNotImplementedError: No backends could accept input of this type.

(both when the active backend is the default scipy.fft, and when it is pyfftw)