Skip to content

Add pyfftw sdp#1132

Open
NimaSarajpoor wants to merge 2 commits intostumpy-dev:mainfrom
NimaSarajpoor:add_pyfftw_sdp
Open

Add pyfftw sdp#1132
NimaSarajpoor wants to merge 2 commits intostumpy-dev:mainfrom
NimaSarajpoor:add_pyfftw_sdp

Conversation

@NimaSarajpoor
Copy link
Collaborator

This is to address PR 3 described in #1118 (comment). Have copied the corresponding notes below:

  1. Add _PYFFTW_SLIDING_DOT_PRODUCT to sdp.py
  2. Add unit tests that demonstrate that _PYFFTW_SLIDING_DOT_PRODUCT matches the results of naive_rolling_window_dot_product
  3. Handle the test.sh check_fftw_pyfftw stuff here too

@gitnotebooks
Copy link

gitnotebooks bot commented Feb 17, 2026

Review these changes at https://app.gitnotebooks.com/stumpy-dev/stumpy/pull/1132

try: # pragma: no cover
import pyfftw

FFTW_IS_AVAILABLE = True
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Suggested change
FFTW_IS_AVAILABLE = True
PYFFTW_IS_AVAILABLE = True

Copy link
Contributor

Choose a reason for hiding this comment

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

What happens when pyfftw is available but fftw is not installed? A user might think that they are both the same thing and that fftw is installed with pyfftw. We probably want a way to detect this to be safe

Copy link
Contributor

Choose a reason for hiding this comment

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

It is okay to set PYFFTW_IS_AVAILABLE assuming that we also check that fftw is also installed

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

What happens when pyfftw is available but fftw is not installed? We probably want a way to detect this to be safe

It is okay to set PYFFTW_IS_AVAILABLE assuming that we also check that fftw is also installed

Got the point 👍

Notes
-----
The class maintains internal caches of FFTW objects to avoid redundant planning
operations when called multiple times with similar-sized inputs and parameters.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Maybe add:

When `planning_flag=="FFTW_ESTIMATE"`, there will be no planning operation. 
However, caching FFTW objects is still beneficial as the overhead of 
creating those objects can be avoided. 

Copy link
Contributor

Choose a reason for hiding this comment

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

But planning_flag is not an input parameter to this class and seems irrelevant? i.e., the user has no control over it

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, it looks like you can __call__ the class instance with the planning_flag

if func_name.endswith("sliding_dot_product"):
out.append(func_name)

if sdp.FFTW_IS_AVAILABLE: # pragma: no cover
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Here, in test_sdp.py, I am checking sdp.FFTW_IS_AVAILABLE instead of adding

try:
    import pyfftw

    FFTW_IS_AVAILABLE = True
except ImportError:  
    FFTW_IS_AVAILABLE = False

Note that if I use the try-except block here, flake8 will complain that pyfftw is imported but not used.

Copy link
Contributor

@seanlaw seanlaw left a comment

Choose a reason for hiding this comment

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

@NimaSarajpoor I've left some comments for you to address. I think we can afford to be clearer since all of this pyfftw stuff will be hard to maintain. We should probably be as verbose (and add more comments cross referencing their docs as possible). I'll do another few passes after you've responded and made modifications. I think this pyfftw stuff needs to be crystal clear

try: # pragma: no cover
import pyfftw

FFTW_IS_AVAILABLE = True
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens when pyfftw is available but fftw is not installed? A user might think that they are both the same thing and that fftw is installed with pyfftw. We probably want a way to detect this to be safe

try: # pragma: no cover
import pyfftw

FFTW_IS_AVAILABLE = True
Copy link
Contributor

Choose a reason for hiding this comment

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

It is okay to set PYFFTW_IS_AVAILABLE assuming that we also check that fftw is also installed

A class to compute the sliding dot product using FFTW via pyfftw.

This class uses FFTW (via pyfftw) to efficiently compute the sliding dot product
between a query sequence Q and a time series T. It preallocates arrays and caches
Copy link
Contributor

Choose a reason for hiding this comment

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

"a query sequence, Q, and a time series, T"

Preallocated complex-valued array for FFTW computations.

rfft_objects : dict
Cache of FFTW forward transform objects, keyed by
Copy link
Contributor

Choose a reason for hiding this comment

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

"transform object swith (next_fast_n, n_threads, planning_flag) as lookup keys"

Copy link
Contributor

Choose a reason for hiding this comment

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

The arguments for these keys seem opaque to the user of the class because they would have no control over them

Copy link
Collaborator Author

@NimaSarajpoor NimaSarajpoor Feb 18, 2026

Choose a reason for hiding this comment

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

The arguments for these keys seem opaque to the user of the class because they would have no control over them

n_threads and planning_flag are parameters of __call__. I need to add the Methods section to the docstring of class and add a description there. The tricky one is next_fast_n since users need to calculate that based on n=len(T).

Notes
-----
The class maintains internal caches of FFTW objects to avoid redundant planning
operations when called multiple times with similar-sized inputs and parameters.
Copy link
Contributor

Choose a reason for hiding this comment

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

But planning_flag is not an input parameter to this class and seems irrelevant? i.e., the user has no control over it

Notes
-----
The class maintains internal caches of FFTW objects to avoid redundant planning
operations when called multiple times with similar-sized inputs and parameters.
Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, it looks like you can __call__ the class instance with the planning_flag

)
self.rfft_objects[key] = rfft_obj
else:
rfft_obj.update_arrays(real_arr, complex_arr)
Copy link
Contributor

Choose a reason for hiding this comment

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

What does this do? It looks like it's a pyfftw thing

# Get or create FFTW objects
key = (next_fast_n, n_threads, planning_flag)

rfft_obj = self.rfft_objects.get(key, None)
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it make sense to combine both:

key = (next_fast_n, n_threads, planning_flag)

rfft_obj = self.rfft_objects.get(key, None)
irfft_obj = self.irfft_objects.get(key, None)

if rfft_obj is None or irfft_obj is None:
    rfft_obj = pyfftw.FFTW(
        input_array=real_arr,
        output_array=complex_arr,
        direction="FFTW_FORWARD",
        flags=(planning_flag,),
        threads=n_threads,
    )
    irfft_obj = pyfftw.FFTW(
        input_array=complex_arr,
        output_array=real_arr,
        direction="FFTW_BACKWARD",
        flags=(planning_flag, "FFTW_DESTROY_INPUT"),
        threads=n_threads,
    )
    self.rfft_objects[key] = rfft_obj
    self.irfft_objects[key] = irfft_obj
else:
    rfft_obj.update_arrays(real_arr, complex_arr)
    irfft_obj.update_arrays(complex_arr, real_arr)

This also assumes that things MUST be done in pairs. There shouldn't be a case where rfft_obj is defined but irfft_obj is not defined, right? That would be suspect

real_arr[:n] = T
real_arr[n:] = 0.0
rfft_obj.execute() # output is in complex_arr
complex_arr_T = complex_arr.copy()
Copy link
Contributor

Choose a reason for hiding this comment

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

This line is confusing. It's not clear what complext_arr_T is and why a copy would make complex_arr be related to T

rfft_obj.execute() # output is in complex_arr

# RFFT(T) * RFFT(Q)
np.multiply(complex_arr, complex_arr_T, out=complex_arr)
Copy link
Contributor

Choose a reason for hiding this comment

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

Here, your comment says RFFT(T) * RFFT*Q) but it is confusing to see complex_arr, complex_arr_T. So:

  1. Both are complex and not "real"
  2. Q is not referenced

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.

2 participants