1717 in Proceedings of ICASSP 2006, IEEE Publishers: Toulouse, France.
1818*/
1919
20+ #define PY_SSIZE_T_CLEAN
21+ #include <Python.h>
22+ #include <numpy/arrayobject.h>
2023#include <math.h>
2124#include <stdlib.h>
2225#include <stdio.h>
2326
2427#define REAL double
2528
29+ /* Include the original FastDFA code exactly as provided */
30+
2631/* Calculate accumulated sum signal */
2732static void cumulativeSum (
2833 unsigned long elements ,
@@ -136,7 +141,7 @@ static void dfa(
136141 free (trend );
137142}
138143
139- /* Main C-callable entry point */
144+ /* Main C-callable entry point - EXACT copy from original */
140145void fastdfa_core (
141146 const double * x ,
142147 unsigned long elements ,
@@ -180,4 +185,142 @@ void fastdfa_core(
180185 dfa (y_in , elements , * intervals , flucts , n_scales_local );
181186
182187 free (y_in );
183- }
188+ }
189+
190+ /* Python wrapper function - simplified to match your ctypes approach */
191+ static PyObject * py_fastdfa (PyObject * self , PyObject * args , PyObject * kwargs ) {
192+ PyArrayObject * input_array = NULL ;
193+ PyObject * intervals_obj = NULL ;
194+
195+ static char * kwlist [] = {"x" , "intervals" , NULL };
196+
197+ // Parse arguments
198+ if (!PyArg_ParseTupleAndKeywords (args , kwargs , "O!|O" , kwlist ,
199+ & PyArray_Type , & input_array ,
200+ & intervals_obj )) {
201+ return NULL ;
202+ }
203+
204+ // Convert input to contiguous double array
205+ PyArrayObject * x_array = (PyArrayObject * )PyArray_FROM_OTF ((PyObject * )input_array ,
206+ NPY_DOUBLE ,
207+ NPY_ARRAY_IN_ARRAY );
208+ if (x_array == NULL ) {
209+ return NULL ;
210+ }
211+
212+ // Check input array
213+ if (PyArray_NDIM (x_array ) != 1 ) {
214+ PyErr_SetString (PyExc_ValueError , "Input array must be 1-dimensional" );
215+ Py_DECREF (x_array );
216+ return NULL ;
217+ }
218+
219+ unsigned long elements = (unsigned long )PyArray_DIM (x_array , 0 );
220+ if (elements < 10 ) {
221+ PyErr_SetString (PyExc_ValueError , "Input signal must have at least 10 elements" );
222+ Py_DECREF (x_array );
223+ return NULL ;
224+ }
225+
226+ double * x_data = (double * )PyArray_DATA (x_array );
227+
228+ // Estimate maximum possible scales
229+ unsigned long max_scales = (unsigned long )(log10 ((double )elements ) / log10 (2.0 )) + 2 ;
230+
231+ // Allocate flucts array
232+ double * flucts = (double * )calloc (max_scales , sizeof (double ));
233+ if (!flucts ) {
234+ PyErr_SetString (PyExc_MemoryError , "Failed to allocate memory for fluctuations" );
235+ Py_DECREF (x_array );
236+ return NULL ;
237+ }
238+
239+ // Initialize for automatic interval calculation
240+ unsigned long * intervals_ptr = NULL ;
241+ unsigned long N_scales = 0 ;
242+
243+ // Call the core function - it will allocate intervals internally
244+ fastdfa_core (x_data , elements , & intervals_ptr , flucts , & N_scales );
245+
246+ // Clean up input array
247+ Py_DECREF (x_array );
248+
249+ // Check if the function succeeded
250+ if (intervals_ptr == NULL || N_scales == 0 ) {
251+ free (flucts );
252+ PyErr_SetString (PyExc_RuntimeError , "FastDFA computation failed" );
253+ return NULL ;
254+ }
255+
256+ // Create output numpy arrays
257+ npy_intp dims [1 ] = {(npy_intp )N_scales };
258+
259+ PyObject * intervals_out = PyArray_SimpleNew (1 , dims , NPY_ULONG );
260+ PyObject * flucts_out = PyArray_SimpleNew (1 , dims , NPY_DOUBLE );
261+
262+ if (!intervals_out || !flucts_out ) {
263+ Py_XDECREF (intervals_out );
264+ Py_XDECREF (flucts_out );
265+ free (intervals_ptr );
266+ free (flucts );
267+ return NULL ;
268+ }
269+
270+ // Copy data to output arrays
271+ unsigned long * intervals_out_data = (unsigned long * )PyArray_DATA ((PyArrayObject * )intervals_out );
272+ double * flucts_out_data = (double * )PyArray_DATA ((PyArrayObject * )flucts_out );
273+
274+ memcpy (intervals_out_data , intervals_ptr , N_scales * sizeof (unsigned long ));
275+ memcpy (flucts_out_data , flucts , N_scales * sizeof (double ));
276+
277+ // Clean up C arrays - the function allocated intervals_ptr for us
278+ free (intervals_ptr );
279+ free (flucts );
280+
281+ // Return tuple (intervals, flucts)
282+ return PyTuple_Pack (2 , intervals_out , flucts_out );
283+ }
284+
285+ /* Method definitions */
286+ static PyMethodDef FastDFAMethods [] = {
287+ {"fastdfa" , (PyCFunction )py_fastdfa , METH_VARARGS | METH_KEYWORDS ,
288+ "Perform fast detrended fluctuation analysis\n\n"
289+ "Parameters:\n"
290+ " x : array_like\n"
291+ " Input signal (1D array)\n"
292+ " intervals : array_like, optional\n"
293+ " List of sample interval widths at each scale (not yet implemented)\n"
294+ " If not specified, binary subdivision is used\n\n"
295+ "Returns:\n"
296+ " intervals : ndarray\n"
297+ " Sample interval widths at each scale\n"
298+ " flucts : ndarray\n"
299+ " Fluctuation amplitudes at each scale\n" },
300+ {NULL , NULL , 0 , NULL }
301+ };
302+
303+ /* Module definition */
304+ static struct PyModuleDef fastdfa_module = {
305+ PyModuleDef_HEAD_INIT ,
306+ "fastdfa" ,
307+ "Fast Detrended Fluctuation Analysis module" ,
308+ -1 ,
309+ FastDFAMethods
310+ };
311+
312+ /* Module initialization */
313+ PyMODINIT_FUNC PyInit_fastdfa (void ) {
314+ PyObject * module ;
315+
316+ module = PyModule_Create (& fastdfa_module );
317+ if (module == NULL )
318+ return NULL ;
319+
320+ // Import numpy
321+ import_array ();
322+ if (PyErr_Occurred ())
323+ return NULL ;
324+
325+ return module ;
326+ }
0 commit comments