Skip to content

File fft_core.h

FileList > doppler > fft > fft_core.h

Go to the source code of this file

Per-instance 1-D FFT using pocketfft directly. More...

  • #include "doppler/clib_common.h"
  • #include "doppler/pocketfft/pocketfft.h"

Classes

Type Name
struct dp_fft_state_t

Public Functions

Type Name
dp_fft_state_t * dp_fft_create (size_t n, int sign, int nthreads)
Allocate a reusable 1-D FFT engine for a fixed length and sign. Two pocketfft plans are created at construction time — one for CF64 and one for CF32 — so execute calls carry no plan-setup overhead. The same instance may be called repeatedly for independent input vectors of the same length. nthreads is accepted for API parity but is ignored; pocketfft plans are single-threaded.
void dp_fft_destroy (dp_fft_state_t * state)
Destroy and free an fft instance.
size_t dp_fft_execute_cf32 (dp_fft_state_t * state, const float _Complex * in, size_t n_in, float _Complex * out, size_t max_out)
Compute an out-of-place 1-D DFT on a single-precision complex input. Identical to dp_fft_execute_cf64() but operates on float _Complex (CF32) buffers, halving memory bandwidth relative to the double-precision variant. Output is unnormalised;in andout must not alias.
size_t dp_fft_execute_cf32_max_out (dp_fft_state_t * state)
Maximum output samples for CF32 execute (always == n).
size_t dp_fft_execute_cf64 (dp_fft_state_t * state, const double _Complex * in, size_t n_in, double _Complex * out, size_t max_out)
Compute an out-of-place 1-D DFT on a double-precision complex input. The output is written to a fresh caller-supplied buffer; in andout must not alias. The transform is unnormalised: the inverse DFT (sign=+1) does NOT divide by n. Both buffers must be exactly state->n elements long.
size_t dp_fft_execute_cf64_max_out (dp_fft_state_t * state)
Maximum output samples per execute call (always == n).
size_t dp_fft_execute_ci16 (dp_fft_state_t * state, const int16_t * in, size_t n_in, float _Complex * out)
Compute an out-of-place 1-D DFT directly on integer IQ (ci16). in is interleaved int16 I/Q (2 ints per complex sample, length 2*n); the result is float _Complex (CF32). The int->float scale (v/32768, full-scale ±1.0, matching the cvt module) is folded into the transform's input read, so this is a single fused pass — faster than a separate i16_to_f32 conversion followed bydp_fft_execute_cf32() . Output is unnormalised.
size_t dp_fft_execute_ci16_max_out (dp_fft_state_t * state)
Maximum output samples for the ci16 execute (always == n).
size_t dp_fft_execute_ci8 (dp_fft_state_t * state, const int8_t * in, size_t n_in, float _Complex * out)
Compute an out-of-place 1-D DFT directly on integer IQ (ci8). As dp_fft_execute_ci16() butin is interleaved int8 I/Q (scale v/128).
size_t dp_fft_execute_ci8_max_out (dp_fft_state_t * state)
Maximum output samples for the ci8 execute (always == n).
size_t dp_fft_execute_inplace_cf32 (dp_fft_state_t * state, const float _Complex * in, size_t n_in, float _Complex * out, size_t max_out)
Copy in intoout , then transformout in-place (CF32). Single-precision variant ofdp_fft_execute_inplace_cf64() . Copies state->n CF32 samples fromin toout , then transformsout with the CF32 pocketfft plan.in is left unmodified.
size_t dp_fft_execute_inplace_cf32_max_out (dp_fft_state_t * state)
Maximum output samples for inplace CF32 (always == n).
size_t dp_fft_execute_inplace_cf64 (dp_fft_state_t * state, const double _Complex * in, size_t n_in, double _Complex * out, size_t max_out)
Copy in intoout , then transformout in-place (CF64). The copy step lets callers preserve their input while keeping the output buffer hot in cache. Semantically identical todp_fft_execute_cf64() for separatein /out pointers; use this variant when the caller already ownsout and wants the result there without a second allocation.
size_t dp_fft_execute_inplace_cf64_max_out (dp_fft_state_t * state)
Maximum output samples for inplace CF64 (always == n).
void dp_fft_reset (dp_fft_state_t * state)
No-op reset (plans are immutable after creation).
size_t dp_fft_smooth_floor (size_t n)
The largest 7-smooth length at most n: the longest transform no longer thann whose only prime factors are 2, 3, 5 and 7.

Detailed Description

Holds two pocketfft plans — one for CF64, one for CF32 — allocated at create time for the requested transform length and sign. nthreads is accepted for API compatibility but ignored; pocketfft is single-threaded.

Lifecycle:

dp_fft_state_t *fft = dp_fft_create(1024, -1, 1);
double _Complex out[1024];
dp_fft_execute_cf64(fft, in, 1024, out, 1024);
dp_fft_destroy(fft);

Public Functions Documentation

function dp_fft_create

Allocate a reusable 1-D FFT engine for a fixed length and sign. Two pocketfft plans are created at construction time — one for CF64 and one for CF32 — so execute calls carry no plan-setup overhead. The same instance may be called repeatedly for independent input vectors of the same length. nthreads is accepted for API parity but is ignored; pocketfft plans are single-threaded.

dp_fft_state_t * dp_fft_create (
    size_t n,
    int sign,
    int nthreads
) 

Parameters:

  • n Transform length in samples (power of two recommended).
  • sign -1 for the forward DFT, +1 for the inverse DFT.
  • nthreads Accepted for API compatibility; ignored.

Returns:

Heap-allocated state, or NULL on allocation failure.

>>> from doppler.spectral import FFT
>>> import numpy as np
>>> fft = FFT(n=4, sign=-1, nthreads=1)
>>> fft.n, fft.sign
(4, -1)
>>> x = np.array([1, 0, 0, 0], dtype=np.complex64)
>>> fft.execute_cf32(x).tolist()
[(1+0j), (1+0j), (1+0j), (1+0j)]


function dp_fft_destroy

Destroy and free an fft instance.

void dp_fft_destroy (
    dp_fft_state_t * state
) 

Parameters:

  • state May be NULL.

function dp_fft_execute_cf32

Compute an out-of-place 1-D DFT on a single-precision complex input. Identical to dp_fft_execute_cf64() but operates on float _Complex (CF32) buffers, halving memory bandwidth relative to the double-precision variant. Output is unnormalised;in andout must not alias.

size_t dp_fft_execute_cf32 (
    dp_fft_state_t * state,
    const float _Complex * in,
    size_t n_in,
    float _Complex * out,
    size_t max_out
) 

Parameters:

  • state Allocated FFT engine (non-NULL).
  • in Input buffer of length state->n (CF32, row-major).
  • n_in Number of input samples; must equal state->n; else REFUSED.
  • out Output buffer of length >= state->n (CF32, caller-allocated).
  • max_out Capacity of out in samples. Normally state->n; if it is smaller the first max_out bins are written and the rest of the transform is discarded.

Returns:

min(state->n, max_out) bins. If n_in is not the required length the call is REFUSED with nothing read, written or counted: SIZE_MAX in C, ValueError in Python (#1925).

>>> from doppler.spectral import FFT
>>> import numpy as np
>>> fft = FFT(n=4, sign=-1)
>>> x = np.ones(4, dtype=np.complex64)
>>> fft.execute_cf32(x).tolist()
[(4+0j), 0j, 0j, 0j]


function dp_fft_execute_cf32_max_out

Maximum output samples for CF32 execute (always == n).

size_t dp_fft_execute_cf32_max_out (
    dp_fft_state_t * state
) 


function dp_fft_execute_cf64

Compute an out-of-place 1-D DFT on a double-precision complex input. The output is written to a fresh caller-supplied buffer; in andout must not alias. The transform is unnormalised: the inverse DFT (sign=+1) does NOT divide by n. Both buffers must be exactly state->n elements long.

size_t dp_fft_execute_cf64 (
    dp_fft_state_t * state,
    const double _Complex * in,
    size_t n_in,
    double _Complex * out,
    size_t max_out
) 

Parameters:

  • state Allocated FFT engine (non-NULL).
  • in Input buffer of length state->n (CF64, row-major).
  • n_in Number of input samples; must equal state->n; else REFUSED.
  • out Output buffer of length >= state->n (CF64, caller-allocated).
  • max_out Capacity of out in samples. Normally state->n; if it is smaller the first max_out bins are written and the rest of the transform is discarded.

Returns:

min(state->n, max_out) bins. If n_in is not the required length the call is REFUSED with nothing read, written or counted: SIZE_MAX in C, ValueError in Python (#1925).

>>> from doppler.spectral import FFT
>>> import numpy as np
>>> fft = FFT(n=4, sign=-1)
>>> x = np.array([1, 0, 0, 0], dtype=np.complex128)
>>> fft.execute_cf64(x).tolist()
[(1+0j), (1+0j), (1+0j), (1+0j)]


function dp_fft_execute_cf64_max_out

Maximum output samples per execute call (always == n).

size_t dp_fft_execute_cf64_max_out (
    dp_fft_state_t * state
) 


function dp_fft_execute_ci16

Compute an out-of-place 1-D DFT directly on integer IQ (ci16). in is interleaved int16 I/Q (2 ints per complex sample, length 2*n); the result is float _Complex (CF32). The int->float scale (v/32768, full-scale ±1.0, matching the cvt module) is folded into the transform's input read, so this is a single fused pass — faster than a separate i16_to_f32 conversion followed bydp_fft_execute_cf32() . Output is unnormalised.

size_t dp_fft_execute_ci16 (
    dp_fft_state_t * state,
    const int16_t * in,
    size_t n_in,
    float _Complex * out
) 

Parameters:

  • state Allocated FFT engine (non-NULL).
  • in Interleaved int16 I/Q, 2*state->n samples.
  • n_in Number of complex samples; must equal state->n; else REFUSED.
  • out Output buffer of length >= state->n (CF32, caller-allocated).

Returns:

n (number of complex samples written). If n_in is not the required length the call is REFUSED with nothing read, written or counted: SIZE_MAX in C, ValueError in Python (#1925).

>>> import numpy as np
>>> from doppler.spectral import FFT
>>> fft = FFT(n=4, sign=-1)
>>> iq = np.full(8, 32768 // 4, dtype=np.int16)   # ~0.25 + 0.25j
>>> np.round(fft.execute_ci16(iq).real, 3).tolist()
[1.0, 0.0, 0.0, 0.0]


function dp_fft_execute_ci16_max_out

Maximum output samples for the ci16 execute (always == n).

size_t dp_fft_execute_ci16_max_out (
    dp_fft_state_t * state
) 


function dp_fft_execute_ci8

Compute an out-of-place 1-D DFT directly on integer IQ (ci8). As dp_fft_execute_ci16() butin is interleaved int8 I/Q (scale v/128).

size_t dp_fft_execute_ci8 (
    dp_fft_state_t * state,
    const int8_t * in,
    size_t n_in,
    float _Complex * out
) 

Parameters:

  • state Allocated FFT engine (non-NULL).
  • in Interleaved int8 I/Q, 2*state->n samples.
  • n_in Number of complex samples; must equal state->n; else REFUSED.
  • out Output buffer of length >= state->n (CF32, caller-allocated).

Returns:

n (number of complex samples written). If n_in is not the required length the call is REFUSED with nothing read, written or counted: SIZE_MAX in C, ValueError in Python (#1925).

>>> import numpy as np
>>> from doppler.spectral import FFT
>>> fft = FFT(n=4, sign=-1)
>>> iq = np.full(8, 32, dtype=np.int8)            # 0.25 + 0.25j
>>> np.round(fft.execute_ci8(iq).real, 3).tolist()
[1.0, 0.0, 0.0, 0.0]


function dp_fft_execute_ci8_max_out

Maximum output samples for the ci8 execute (always == n).

size_t dp_fft_execute_ci8_max_out (
    dp_fft_state_t * state
) 


function dp_fft_execute_inplace_cf32

Copy in intoout , then transformout in-place (CF32). Single-precision variant ofdp_fft_execute_inplace_cf64() . Copies state->n CF32 samples fromin toout , then transformsout with the CF32 pocketfft plan.in is left unmodified.

size_t dp_fft_execute_inplace_cf32 (
    dp_fft_state_t * state,
    const float _Complex * in,
    size_t n_in,
    float _Complex * out,
    size_t max_out
) 

Parameters:

  • state Allocated FFT engine (non-NULL).
  • in Source buffer, state->n CF32 samples; not modified.
  • n_in Number of input samples; must equal state->n; else REFUSED.
  • out Destination buffer, length >= state->n; must not alias in.
  • max_out Capacity of out in samples. Normally state->n; if it is smaller the copy-and-transform happens in scratch and only the first max_out bins reach out.

Returns:

min(state->n, max_out) bins. If n_in is not the required length the call is REFUSED with nothing read, written or counted: SIZE_MAX in C, ValueError in Python (#1925).

>>> from doppler.spectral import FFT
>>> import numpy as np
>>> fft = FFT(n=4, sign=-1)
>>> x = np.array([1, 0, 0, 0], dtype=np.complex64)
>>> fft.execute_inplace_cf32(x).tolist()
[(1+0j), (1+0j), (1+0j), (1+0j)]


function dp_fft_execute_inplace_cf32_max_out

Maximum output samples for inplace CF32 (always == n).

size_t dp_fft_execute_inplace_cf32_max_out (
    dp_fft_state_t * state
) 


function dp_fft_execute_inplace_cf64

Copy in intoout , then transformout in-place (CF64). The copy step lets callers preserve their input while keeping the output buffer hot in cache. Semantically identical todp_fft_execute_cf64() for separatein /out pointers; use this variant when the caller already ownsout and wants the result there without a second allocation.

size_t dp_fft_execute_inplace_cf64 (
    dp_fft_state_t * state,
    const double _Complex * in,
    size_t n_in,
    double _Complex * out,
    size_t max_out
) 

Parameters:

  • state Allocated FFT engine (non-NULL).
  • in Source buffer, state->n CF64 samples; not modified.
  • n_in Number of input samples; must equal state->n; else REFUSED.
  • out Destination buffer, length >= state->n; must not alias in.
  • max_out Capacity of out in samples. Normally state->n; if it is smaller the copy-and-transform happens in scratch and only the first max_out bins reach out.

Returns:

min(state->n, max_out) bins. If n_in is not the required length the call is REFUSED with nothing read, written or counted: SIZE_MAX in C, ValueError in Python (#1925).

>>> from doppler.spectral import FFT
>>> import numpy as np
>>> fft = FFT(n=4, sign=-1)
>>> x = np.array([1, 0, 0, 0], dtype=np.complex128)
>>> fft.execute_inplace_cf64(x).tolist()
[(1+0j), (1+0j), (1+0j), (1+0j)]


function dp_fft_execute_inplace_cf64_max_out

Maximum output samples for inplace CF64 (always == n).

size_t dp_fft_execute_inplace_cf64_max_out (
    dp_fft_state_t * state
) 


function dp_fft_reset

No-op reset (plans are immutable after creation).

void dp_fft_reset (
    dp_fft_state_t * state
) 


function dp_fft_smooth_floor

The largest 7-smooth length at most n: the longest transform no longer thann whose only prime factors are 2, 3, 5 and 7.

size_t dp_fft_smooth_floor (
    size_t n
) 

pocketfft's cost tracks the factorization of the length, not its size. It has its own passes for 2, 3, 4, 5, 7 and 11; a prime factor of 13 or more goes the generic way. Measured at the async-DSSS design point (a slow-time axis of 2D points, one thread, doppler#2457): D = 13 cost 1.22x D = 12, D = 23 1.35x D = 21, and D = 94 (2 x 47) 1.46x D = 90, while a factor of 7 cost 3-5% over its 5-smooth neighbours. A caller free to choose a length at or below a bound a coherent depth, a block size rounds the bound down with this. The round-down gives up at most about 9% of the length (11 -> 10) and less as n grows. A caller whose length is fixed by the signal (a code period) cannot use it.

Parameters:

  • n Upper bound on the length.

Returns:

The largest m <= n with m = 2^a 3^b 5^c 7^d; 0 when n is 0.

#include <assert.h>
#include "doppler/fft/fft_core.h"
int main (void)
{
  assert (dp_fft_smooth_floor (94) == 90);   // 2 x 47 -> 2 x 3^2 x 5
  assert (dp_fft_smooth_floor (96) == 96);   // already smooth
  assert (dp_fft_smooth_floor (14) == 14);   // 2 x 7 is kept
  assert (dp_fft_smooth_floor (13) == 12);   // a prime of 13 is not
  assert (dp_fft_smooth_floor (407) == 405); // 11 x 37 -> 3^4 x 5
  assert (dp_fft_smooth_floor (1) == 1);
  return 0;
}



The documentation for this class was generated from the following file native/inc/doppler/fft/fft_core.h