Skip to content
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

fourier: new package for fourier analysis and related functions #412

Merged
merged 15 commits into from Apr 14, 2018

Conversation

kortschak
Copy link
Member

Please take a look.

So far I have only added the backing code. I want to add CFFT[IFB] and then I will dismantle some of the array.go support (remove 1-based indexing and the linear support types). It makes sense to add the API in this PR, so suggestions welcome here.

@kortschak
Copy link
Member Author

kortschak commented Feb 4, 2018

From an API perspective, I think that given there are work arrays kept, a type holding those that can be reused would be the most sensible approach:

type FFT struct

// NewFFT returns an FFT initialized for work on sequences of length n.
func NewFFT(n int) *FFT

// Len returns the length of the acceptable input.
func (t *FFT) Len() int

// Reset reinitializes the FFT for work on sequences of length n.
func (t *FFT) Reset(n int)

// FFT computes the Fourier coefficients of the input sequence, s,
// placing the result in dst and returning it.
// If the length of s is not t.Len(), FFT will panic.
// If dst is nil, a new slice is allocated and returned. If dst is not nil and
// the length of dst does not equal the length of s, FFT will panic.
func (t *FFT) FFT(dst, s []float64) []float64

// IFFT computes the real perodic sequence from the Fourier coefficients,
// c, placing the result in dst and returning it.
// If the length of s is not t.Len(), IFFT will panic.
// If dst is nil, a new slice is allocated and returned. If dst is not nil and
// the length of dst does not equal the length of c, IFFT will panic.
func (t *FFT) IFFT(dst, c []float64) []float64

I'm not wildly happy about the method names FFT and IFFT but they seem to be what people generally use (I'd prefer Coefficients and Sequence).

fourier/rfft.go Outdated
ai2 = dc2*ai2 + ds2*ar2
ar2 = ar2h
for ik := 1; ik <= idl1; ik++ {
ch2m.set(ik, l, ch2m.at(ik, l)+ar2*c2m.at(ik, j))
Copy link
Member Author

@kortschak kortschak Feb 4, 2018

Choose a reason for hiding this comment

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

These two lines suggest that a twoArray.add(i, j int, v float64) method would be a worthwhile optimisation.

Copy link
Member Author

@kortschak kortschak Feb 6, 2018

Choose a reason for hiding this comment

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

These are done.

fourier/rfft.go Outdated
}
for j := 2; j <= ipph; j++ {
for ik := 1; ik <= idl1; ik++ {
ch2m.set(ik, 1, ch2m.at(ik, 1)+c2m.at(ik, j))
Copy link
Member Author

Choose a reason for hiding this comment

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

And here.

fourier/rfft.go Outdated
ai2 = dc2*ai2 + ds2*ar2
ar2 = ar2h
for ik := 1; ik <= idl1; ik++ {
c2m.set(ik, l, c2m.at(ik, l)+ar2*ch2m.at(ik, j))
Copy link
Member Author

Choose a reason for hiding this comment

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

And here.

fourier/rfft.go Outdated

for j := 2; j <= ipph; j++ {
for ik := 1; ik <= idl1; ik++ {
ch2m.set(ik, 1, ch2m.at(ik, 1)+ch2m.at(ik, j))
Copy link
Member Author

Choose a reason for hiding this comment

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

And here.

@kortschak
Copy link
Member Author

kortschak commented Feb 5, 2018

Damn. Not quite correct yet.

@kortschak kortschak force-pushed the fourier branch 8 times, most recently from dd0e55e to 2c4ea68 Compare February 6, 2018 05:25
@btracey
Copy link
Member

btracey commented Feb 7, 2018

I'll get to this when I can, I'm on travel at the moment.

@kortschak
Copy link
Member Author

kortschak commented Feb 7, 2018

No worries. Despite my best efforts, I managed to sneak some off-by-one errors into this - when you read the subtle one-based indexing removal, also read the fix off-by-one bugs commit. The sign bug goes with the use of the sign parameter commit. Rebased away.

@kortschak kortschak force-pushed the fourier branch 7 times, most recently from 2a1a53f to 31ce9e8 Compare February 14, 2018 22:10
@kortschak
Copy link
Member Author

kortschak commented Feb 14, 2018

Apologies for the unhelpful ordering - I revised the author name, and github insists on presenting PR changes in chronological order in a way that made that revision show differences that were not otherwise there.

@btracey
Copy link
Member

btracey commented Feb 27, 2018

I understand the desire for having the types to reuse the work arrays. From what I understand, this is how FFTW works as well. Do you think it's desirable to also have some high level functions for ease of use? I haven't looked at all of the intended functions, but I wonder if we could get away with Float64 and Complex128 as types that hold the temporary memory, and the could have FFT be a function that allocates the temporary memory.

Just a thought. I'll take a more detailed look soon hopefully.

@kortschak
Copy link
Member Author

They are an easy addition, so I think we should leave that for when someone asks for it.

@kortschak
Copy link
Member Author

kortschak commented Mar 4, 2018

I have put together the implementation and API for DCT, DST and QW-FFT (tests will come later), but I won't include them in this PR. However, after seeing how the API looks I'm going to push a little harder towards English for the method names. I have two proposals (in preference order):

  1. FFT -> Coefficients and IFFT -> Sequence.
  2. FFT -> Analysis and IFFT -> Synthesis (or the verbs).

This gives us nicer API for the new routines mentioned above, e.g. for QW-FFT, SinCoefficients and CosCoefficients (the methods are on the same type, QuarterWaveFFT).

fourier/rfft.go Outdated
rffti1(n, work[n:2*n], ifac[:15])
}

func rffti1(n int, wa []float64, ifac []int) {
Copy link
Member

Choose a reason for hiding this comment

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

Checked this one and looks good.

fourier/rfft.go Outdated
rfftf1(n, r[:n], work[:n], work[n:2*n], ifac[:15])
}

func rfftf1(n int, c, ch, wa []float64, ifac []int) {
Copy link
Member

Choose a reason for hiding this comment

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

This looks good too

// freq=0.375 cycles/period, magnitude=3, phase=3.142
}

func Example_fFT2() {
Copy link
Member

Choose a reason for hiding this comment

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

Lower-case f in fFT2

Copy link
Member

Choose a reason for hiding this comment

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

That's to give it package scope. See the example names section https://blog.golang.org/examples

Copy link
Member

Choose a reason for hiding this comment

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

Oh, there's always something to learn. Thanks.

@kortschak
Copy link
Member Author

kortschak commented Apr 12, 2018

Thanks. There are a few of things to do; I forgot the comment that the cfft backend code is refactored to allow direction being set by sign(done), the change s/since/;/g (@btracey are you happy with this?) and a lot of delicate history revision including creating internal/fftpack (I'm not confident about whether this is possible in this PR)(done).

On the subject of the internal/fftpack package: I know we've done this kind of thing in mathext, but I'm really not convinced it's a good idea. The package boundary is to define separation of concerns and modularity. I don't see that division here except as a consequence of history. Though maybe it does help in debugging with other implementations. Comments?

@kortschak
Copy link
Member Author

I've done as much history condensation as I think is achievable while retaining the capacity to identify bugs later.

@btracey
Copy link
Member

btracey commented Apr 13, 2018

Semicolon sounds great.

I think it's a separation of concerns. The code base isn't really "native go", just translated fortran, while the main package is novel code. The FFTPack code has additional "license" documentation that goes with it that the other code does not have. I think it's also good to separate for other implementations, in particular it would be nice to clean up a lot of the FFTPack code (i.e. using complex128 where appropriate) at which point those alternate functions could live in the main package, preferably with comment styles that match gonum rather than matching netlib. In short, the FFTPACK code feels out of place, and putting it in internal is a clear signal that it's somehow different.

@kortschak
Copy link
Member Author

OK. I'll try to rebase it into a new package and at the same time add the semicolons. I'll ping when it's done since that push won't alert you.

@kortschak
Copy link
Member Author

Actually, the history revision is non-trivial to the extent that I think I will just add another commit moving the fftpack code out to the internal package. There is too much breakage opportunity doing it the way I initially wanted.

@kortschak
Copy link
Member Author

PTAL

@kortschak kortschak merged commit 7b0fe87 into master Apr 14, 2018
@kortschak kortschak deleted the fourier branch April 14, 2018 22:15
@kortschak
Copy link
Member Author

Thanks for the review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants