Thanks to visit codestin.com
Credit goes to github.com

Skip to content

ENH: Add Hamilton's FFTLog.#7310

Closed
prisae wants to merge 3 commits intoscipy:masterfrom
prisae:include-fftlog
Closed

ENH: Add Hamilton's FFTLog.#7310
prisae wants to merge 3 commits intoscipy:masterfrom
prisae:include-fftlog

Conversation

@prisae
Copy link
Contributor

@prisae prisae commented Apr 17, 2017

Adding a f2py-wrapper for the logarithmic FFT by A. J. S. Hamilton;
original code available at: http://casa.colorado.edu/~ajsh/FFTLog.


This is an initial pull request to add Hamilton's FFTLog to the fftpack of SciPy, as discussed on the mailing list starting with https://mail.scipy.org/pipermail/scipy-dev/2016-October/021552.html.

Some important points:

  • fftpack/src/fftlog/cdgamma.f: It has to be replaced with scipy.special.loggamma (I am lacking the necessary knowledge to do this) => all calls cdgamma(x, 1) in fftlog.f can be replaced with loggamma(x). See discussion here and in the whole thread in general: https://mail.scipy.org/pipermail/scipy-dev/2016-October/021584.html.

  • fftpack/FFTLog-Example.ipyn: Not part of it, they are just examples of its usage.

I tried to follow the Contributing to SciPy (HACKING.rst.txt) guide as much as possible. In terms of the provided checklist in HACKING.rst.txt, I did all but:

  • Mention new functionality in release notes of next release
  • Adding new functionality to reference guide

I welcome any feedback!
Dieter

Adding a f2py-wrapper for the logarithmic FFT by A. J. S. Hamilton;
original code available at: http://casa.colorado.edu/~ajsh/FFTLog.
@prisae
Copy link
Contributor Author

prisae commented Apr 17, 2017

@rgommers rgommers added enhancement A new feature or improvement scipy.fftpack labels Apr 19, 2017
@rgommers
Copy link
Member

Mention new functionality in release notes of next release

Don't worry about this for now, easier to do right before or after merging.

Adding new functionality to reference guide

The continuous integration will complain about this. I'll have a look at what to add where.

@rgommers
Copy link
Member

There's a couple of doctest failures, and a non-ascii character. Have a look at the bottom of https://travis-ci.org/scipy/scipy/jobs/222840699 for details

del atexit


def fftlog(x, dlogr, mu='sine', q=0.0, kr=1.0, rk=1.0):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are all these parameters really necessary, and if so can they be given more descriptive names? Especially kr and rk seem like parameters that are taken over from the Fortran code but are unintuitive for users.

(the default) then ``n = x.shape[axis]``. If ``n < x.shape[axis]``,
`x` is truncated, if ``n > x.shape[axis]``, `x` is zero-padded.

axis : int, optional
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this parameter as well as overwrite_x is not in the signature of this function

Returns
-------
y : real ndarray
Transformed array Ã(k): a(j) is Ã(k_j) at k_j = k_c exp[(j-jc) dlnr].
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These weird A's are non-ascii and should be removed.

Examples
--------
>>> from scipy.fftpack import fftlog, fftlogargs
>>> # Get fftlog-arguments
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of comments you should use normal text (i.e. remove >>> # ) with blank lines above and below.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that goes for all comments in here

>>> print('Input :', fw)
>>> print('Analytical :', ft)
>>> print('fftlog :', fftl)
Input : [ 1.48956664 1.32757767 1.18320484 1.05453243]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the output should be straight under each print statement

return y


def fftlogargs(n, dlogr=0.01, logrc=0.0, mu='sine', q=0, kr=1, kropt=0):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to my API question for fftlog: why is this function needed at all? Can you instead use this internally in fftlog?

>>> print('outpts :', outpts[1])
>>> print('kr :', outpts[2])
>>> print('rk :', outpts[3])
intpts : [ 0.96605088 0.98855309 1.01157945 1.03514217]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as for fftlog, put print outputs straight under each print statement

- Replace non-ascii characters
- Name input parameters more intuitively
- Correct examples for doctest
@prisae
Copy link
Contributor Author

prisae commented Apr 20, 2017

Thanks for reviewing it Ralf. Few things:

  • Doctest-problems are hopefully resolved.
  • I thought non-ascii problem is resolved too, but apparently not. I will have another look.
  • Yes, all the variable-names were straight from the fortran code. I renamed the following variables:
    • dlogr -> spacing
    • mu -> transform
    • q -> bias
    • logrc -> center

kr and rk are somewhat difficult to rename. They are the product of the center points of k and r, and r and k respectively (taking into account where center is located). Now this leads to the function fftlogargs: FFTLog has the possibility to find the best, low-ringing values for kr and rk.

fftlogargs could be included into fftlog, and I like the idea. However, fftlog would then not only have to return the transformed values, but also the array (abscissae) where these values are. Because if FFTLog calculates the low-ringing values for kr and rk, but you don't know these values, there is no way for you to know the position of the returned values.

Required changes in order to include fftlogargs into fftlog:

  • fftlog(x, spacing, transform, bias, kr, rk) => fftlog(x, spacing, transform, bias, center, kropt, kr, rk)
  • Output would be the transformed array and additionally:
    • output-pts (abscissae) of transformed array
    • kr, rk (optional, but you might want to use the same values again)

fftlogargs could then be reduced to an optional helper function:

    return 10**(center + (np.arange(n)+1 - (n + 1)/2.0)*spacing)

to only calculated the required input points for given center, spacing, and points n.

Do you prefer to keep it as it is or shall I make the changes to include fftlogargs into fftlog?

@prisae
Copy link
Contributor Author

prisae commented Apr 21, 2017

Turns out that the ü in my surname was the ascii-problem...

@rgommers rgommers added this to the 1.1.0 milestone Sep 16, 2017
@prisae
Copy link
Contributor Author

prisae commented Sep 17, 2017

Glad to see it added to the 1.1.0 milestone. I assume it still needs work, but I am happy to iterate over it.

@rgommers
Copy link
Member

I assume it still needs work

Not sure right now. We're short on reviewer power unfortunately, and fftpack isn't the most active submodule. I'm adding PRs with enhancements that seem ready to the milestone for the next release so we make sure to go over those and try to get them merged.

@pv pv modified the milestones: 1.1.0, 1.2.0 Apr 12, 2018
@rgommers rgommers modified the milestones: 1.2.0, 1.3.0 Oct 29, 2018
@rgommers
Copy link
Member

I apologize for bumping this again. Looks like we're getting some more momentum on FFTs with the Pocketfft work in NumPy, however no reviewer in time for 1.2.0

@tylerjereddy
Copy link
Contributor

Bumping again -- I'm not the right person to review FFT Fortran code at this time, but hopefully we will regain momentum as Ralf suggests.

@tylerjereddy tylerjereddy modified the milestones: 1.3.0, 1.4.0 Apr 23, 2019
@mreineck
Copy link
Contributor

I'm sorry ... I've inadvertently sabotaged this PR by pushing for the switch to pocketfft :( There is now no longer any Fortran FFT code in scipy, and so integrating this addition will probably be much harder.

I don't have a feeling how hard it would be to implement FFTLog on top of the new C++ module, but I volunteer to assist, if I can.

@prisae
Copy link
Contributor Author

prisae commented Aug 16, 2019

Thanks @mreineck for picking this up. It all depends. Mainly,

  • if there is enough interest to have FFTLog in SciPy, and
  • if there is an experienced FFT-person willing to review my modest go at it...

In the meantime since I made this PR I created a pure Python-Version of it, which could be implemented instead. It can be found here: https://github.com/prisae/pyfftlog

I never benchmarked FFTLog versus pyfftlog. But there are no loops, and most work is carried out within scipy.fftpack._fftpack.drfft and scipy.special.loggamma, so I would not expect massive differences.

@larsoner larsoner added the needs-work Items that are pending response from the author label Oct 22, 2019
@peterbell10
Copy link
Member

I don't think I'll have enough time to work on this in the near future.

@ntessore
Copy link
Contributor

I would like to revive the effort to get FFTLog into scipy, as it seems that people are reimplementing this constantly.

Instead of using the original Fortran FFTLog implementation, why not use a Python implementation that calls whatever FFT routine in scipy is fastest. Here is an example of the main routine using numpy.fft. As you can see the whole FFTLog algorithm reduces to a couple of lines of code. As someone anticipated correctly, it threads nicely over multidimensional input arrays.

@prisae
Copy link
Contributor Author

prisae commented Nov 13, 2020

If someone picks it up that would be great @ntessore.

For what its worth, my implementations can now be installed via pip and via conda:

pip install pyfftlog

or

conda install -c conda-forge pyfftlog

for the pure Python version, or

pip install fftlog

or

conda install -c conda-forge fftlog

for the Python-wrapped Fortran version.

@prisae
Copy link
Contributor Author

prisae commented Nov 13, 2020

The FFTLog algorithm contains possibilities for a Hankel transform and a Fourier transform. As far as I can see, the gist you posted contains only the Hankel transform, right? The name "FFTLog" is a bit misleading, unfortunately.

@ntessore
Copy link
Contributor

The gist I posted only contains the main routine as an example of how simple the implementation would be. Every other routine in the original FFTLog is then essentially a one-liner to provide a more convenient interface for special cases. Note that I also only show the forward transform, since the backward transform should be its own function, in the spirit of rfft/irfft etc.

@prisae
Copy link
Contributor Author

prisae commented Nov 13, 2020

I would certainly love to have it in SciPy.

@mreineck
Copy link
Contributor

Thanks, @ntessore, for the demo! This certainly looks like a good way of having the functionality in scipy.
Only one minor comment: I suggest using scipy.fft instead of numpy.fft as the underlying Fourier transform implementation, since it is typically much faster for multidimensional arrays. And since the code will be part of scipy, we know that it will always be available.

@prisae
Copy link
Contributor Author

prisae commented Nov 13, 2020

(Just FWIW: in my experience the Fortran-wrapped version is usually faster by a margin, so it would be nice to have that one. However, I'd be happy to have a pure Python version over not having anything at all.)

@mreineck
Copy link
Contributor

I don't remember exactly, but my recollection is that the Fortran code for FFTlog was fairly closely linked to FFTPACK, which is no longer available in Scipy. So if there should be a compiled version of FFTLog, it would require substantial rewriting. At least for the moment I'd lean towards a Python implementation, since it really appears to be very easy to do. If a compiled implementation follows at a later time, not much effort would be lost.

@prisae
Copy link
Contributor Author

prisae commented Nov 13, 2020

Yes, I think that was one of the reason why this PR stalled in the end @mreineck - given your insights a pure Python version sounds more feasible indeed.

@ntessore
Copy link
Contributor

ntessore commented Nov 13, 2020

Using your fftlog module on my machine, I see that the Fortran implementation is roughly 10x faster than the pure Python version, not too bad for an unoptimised example. And a line profile shows that there might be potential for improvement:

% Time  Line Contents
==============================
 ...
          # Hankel transform via real FFT
 6.3      g = scipy.fft.rfft(f, axis=-1)
 0.6      g *= u
51.0      g = scipy.fft.irfft(g, n, axis=-1)
36.2      g[..., :] = g[..., ::-1]
...

Either way, it looks like there it not much in the Python code that is inherently slow. The FFT implementation will make the difference. And for very large input lengths, I expect the numpy array operations to perform well compared to the Fortran loops.

@mreineck
Copy link
Contributor

Any idea why the irfft does so much worse than the rfft? That should never happen...

@mreineck
Copy link
Contributor

That should never happen...

Hmm, maybe that depends on the transform length. Which length did you use for this run?

@ntessore
Copy link
Contributor

ntessore commented Nov 13, 2020

Any idea why the irfft does so much worse than the rfft? That should never happen...

Apologies, that was the profiler being flaky due to insufficient time resolution for an input size of 128. For larger inputs, the picture is different. Here's n = 1024:

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
...
    60         1        160.0    160.0     15.3      loggamma(u, out=u)
    61         1        277.0    277.0     26.6      loggamma(v, out=v)
...
    75                                               # Hankel transform via real FFT
    76         1        154.0    154.0     14.8      g = rfft(f, axis=-1)
    77         1         13.0     13.0      1.2      g *= u
    78         1        106.0    106.0     10.2      g = irfft(g, n, axis=-1)
    79         1          9.0      9.0      0.9      g[..., :] = g[..., ::-1]
...

And pure Python is only about 1/3 as fast as Fotran. For n = 8192:

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
...
    60         1       1569.0   1569.0     19.7      loggamma(u, out=u)
    61         1       3964.0   3964.0     49.9      loggamma(v, out=v)
...
    75                                               # Hankel transform via real FFT
    76         1        469.0    469.0      5.9      g = rfft(f, axis=-1)
    77         1         66.0     66.0      0.8      g *= u
    78         1        369.0    369.0      4.6      g = irfft(g, n, axis=-1)
    79         1         30.0     30.0      0.4      g[..., :] = g[..., ::-1]
...

Now pure Python is 2/3 as fast as Fortran. So I guess tweaking the loggamma calls would make more sense than eventually going back to Fortran.

@mreineck
Copy link
Contributor

Interesting, thanks! It almost seems like loggamma is having a harder and harder job computing its result with increasing length, since its time consumption increase is superlinear.

@ntessore
Copy link
Contributor

ntessore commented Nov 13, 2020

I suspect that it could do with a lot less precision here than in the general purpose case. If speed is really that important, perhaps it would make sense to port the cdgamma Fortran implementation that FFTLog comes with, since some consideration about the necessary precision would have gone into that.

Lastly, I wanted to point out that the Fortran implementation is really not any more complicated than the Python example. Almost all of the complexity comes from setting up the FFT, and in particular dealing with the last element of the array, which the Python version just hands over to rfft/irfft.

@mreineck
Copy link
Contributor

The complexity (or let's say the maintenance burden) will come mostly from the interface, I think. If I look at the amount of glue code that comes between the pure C++ FFT implementation in scipy and the actual user-visible functions in scipy.fft, this is comparable in size to the code which actually does the numerical work.

But I should stress that this is only my personal opinion - I'm only involved in scipy as a code contributor and don't get to decide how new components should be added :-)

@larsoner
Copy link
Member

+1 for a pure-Python solution. I say let's get something that works properly with a good/suitable interface first. That is enough work to start. After that, we can have follow-up PRs to make it more efficient where there are pain points. For example the line g[..., :] = g[..., ::-1] can probably just be g = g[..., ::-1] (unless we need C-contiguity for some reason) and it's essentially free because it's just creating a view. But these sorts of things are better to do after it already works -- it's easier for other people to contribute or even move parts or all of it to Cythton once there's a working API, implementation, and tests all in place.

@ntessore
Copy link
Contributor

ntessore commented Nov 13, 2020

Please let me know how I can help. I can provide a pure Python implementation of the classical FFTLog functionality which has the following interface:

  • dctl, idctl
    computes the discrete Fourier cosine transform of a logarithmically spaced periodic sequence.
    This is a driver routine that calls fhtq.
  • dstl, idstl
    computes the discrete Fourier sine transform of a logarithmically spaced periodic sequence.
    This is a driver routine that calls fhtq.
  • fht, ifht (dhtl, idhtl?)
    computes the discrete Hankel transform of a logarithmically spaced periodic sequence.
    This is a driver routine that calls fhtq.
  • fhtq, ifhtq
    computes the biased discrete Hankel transform of a logarithmically spaced periodic sequence.
    This is the basic FFTLog routine.

@larsoner
Copy link
Member

larsoner commented Nov 13, 2020

That looks reasonable to me -- @ntessore @prisae thoughts? (EDIT: Sorry @ntessore I didn't mean to tag you in your own API proposal...)

@mreineck @peterbell10 I guess these should live in scipy.fft?

@mreineck
Copy link
Contributor

@mreineck @peterbell10 I guess these should live in scipy.fft?

Feels like the most natural place, yes.

Thanks a lot, @ntessore, for taking care of this!

@prisae
Copy link
Contributor Author

prisae commented Nov 13, 2020

Sounds OK to me. I think the best is if you simply provide the functionality and then we can iterate over it. Feel free to use this PR and replace all the files, or start a new one. We can then link to this one and close this. Either way is fine. I am happy to review with what you come up.

@larsoner
Copy link
Member

Given how much of this PR has to do with wrapping to Fortran, I would probably just keep parts of the scipy/fftpack/tests/test_logarithmic.py test after moving it to scipy/fft/tests/test_logarithmic.py. But @ntessore I'll let you decide.

I'll close this but you should be able to pull commits from @prisae's branch if need be

@larsoner larsoner closed this Nov 13, 2020
@ntessore
Copy link
Contributor

Ok thanks. I will pull in prisae/include-fftlog and move stuff around, that should credit @prisae as well. The I will open a new PR. Give a thumbs up to this and I will ping you in the new PR.

@larsoner
Copy link
Member

Yes feel free to do that. In the end I will probably merge your PR with squash+merge and let the "Co-authored by" take care of dual code credit. The alternative is to git rebase -i HEAD~# and squash @prisae's exsiting commits, then at the end of your PR you squash all of yours, but this is less than ideal because once those are merged to master the 3 squashed commits from his PR are essentially in a broken state, which is not good if someone has to do a git bisect later.

@prisae
Copy link
Contributor Author

prisae commented Nov 13, 2020

Thanks @ntessore , looking forward to it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement A new feature or improvement needs-work Items that are pending response from the author scipy.fftpack

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants