Skip to content

Commit 84d401b

Browse files
committed
add fastdfa c wrapper and scaling module
1 parent bfa44bd commit 84d401b

5 files changed

Lines changed: 199 additions & 76 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Scaling:
2+
FastDFA:
3+
base_name: fastdfa
4+
labels:
5+
- scaling
6+
configs:
7+
- zscore: True
8+
hctsa_name: SC_fastdfa
9+

pyhctsa/Operations/Scaling.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import numpy as np
2+
from numpy.typing import ArrayLike
3+
from ..Toolboxes.Max_Little import fastdfa
4+
5+
def FastDFA(y: ArrayLike) -> float:
6+
"""
7+
Measures the scaling exponent of the time series using a fast implementation
8+
of detrended fluctuation analysis (DFA).
9+
10+
This is a Python wrapper for Max Little's ML_fastdfa code.
11+
The original fastdfa code is by Max A. Little and publicly available at:
12+
http://www.maxlittle.net/software/index.php
13+
14+
Parameters
15+
----------
16+
y : array-like
17+
Input time series (1D array), fed straight into the fastdfa script.
18+
19+
Returns
20+
-------
21+
float
22+
Estimated scaling exponent from log-log linear fit of fluctuation vs interval.
23+
"""
24+
y = np.asarray(y)
25+
intervals, flucts = fastdfa.fastdfa(y)
26+
idx = np.argsort(intervals)
27+
intervals_sorted = intervals[idx]
28+
flucts_sorted = flucts[idx]
29+
30+
# Log-log linear fit
31+
coeffs = np.polyfit(np.log10(intervals_sorted), np.log10(flucts_sorted), 1)
32+
alpha = coeffs[0]
33+
return alpha

pyhctsa/Toolboxes/Max_Little/ML_fastdfa_core.c

Lines changed: 145 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,17 @@
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 */
2732
static 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 */
140145
void 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+
}

pyhctsa/Toolboxes/Max_Little/fastdfa_wrapper.py

Lines changed: 0 additions & 72 deletions
This file was deleted.

setup.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,24 @@ def get_compile_args():
1212
return ['/O2']
1313
else:
1414
# GCC/Clang flags for Unix-like systems
15-
return ['-O3', '-fPIC', '-std=c99']
15+
return ['-O3', '-fPIC', '-std=c99', '-ffast-math']
1616

1717
def get_libraries():
1818
"""Get platform-specific libraries to link."""
1919
if platform.system() == 'Windows':
2020
return []
2121
else:
2222
return ['m'] # Math library for Unix-like systems
23+
24+
fastdfa_extension = Extension(
25+
'pyhctsa.Toolboxes.Max_Little.fastdfa',
26+
sources=['pyhctsa/Toolboxes/Max_Little/ML_fastdfa_core.c'],
27+
include_dirs=["pyhctsa/Toolboxes/Max_Little", np.get_include()],
28+
extra_compile_args=get_compile_args(),
29+
libraries=get_libraries(),
30+
extra_link_args=[],
31+
define_macros=[],
32+
)
2333

2434

2535
sampen_extension = Extension(
@@ -88,7 +98,7 @@ def read_requirements(path):
8898
long_description=read("README.md"),
8999
author="Joshua B. Moore",
90100
packages=find_packages(exclude=["tests", ".github"]),
91-
ext_modules=[periodicity_wang_module, close_returns_extension, sampen_extension],
101+
ext_modules=[periodicity_wang_module, close_returns_extension, sampen_extension, fastdfa_extension],
92102
install_requires=read_requirements("requirements.txt"),
93103
zip_safe=False,
94104
)

0 commit comments

Comments
 (0)