fftloggin 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fftloggin/__init__.py +15 -0
- fftloggin/fftlog.py +665 -0
- fftloggin/grids.py +379 -0
- fftloggin/kernels.py +341 -0
- fftloggin/utils.py +254 -0
- fftloggin-0.2.0.dist-info/METADATA +120 -0
- fftloggin-0.2.0.dist-info/RECORD +10 -0
- fftloggin-0.2.0.dist-info/WHEEL +4 -0
- fftloggin-0.2.0.dist-info/entry_points.txt +2 -0
- fftloggin-0.2.0.dist-info/licenses/LICENSE +21 -0
fftloggin/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from . import kernels
|
|
2
|
+
from .fftlog import FFTLog
|
|
3
|
+
from .grids import Grid
|
|
4
|
+
from .kernels import BesselJKernel, Derivative, Kernel
|
|
5
|
+
from .utils import prepare_batch_params
|
|
6
|
+
|
|
7
|
+
__all__ = (
|
|
8
|
+
"FFTLog",
|
|
9
|
+
"Grid",
|
|
10
|
+
"Kernel",
|
|
11
|
+
"BesselJKernel",
|
|
12
|
+
"Derivative",
|
|
13
|
+
"prepare_batch_params",
|
|
14
|
+
"kernels",
|
|
15
|
+
)
|
fftloggin/fftlog.py
ADDED
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
from functools import cached_property
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import numpy.typing as npt
|
|
5
|
+
from scipy.fft import irfft, rfft
|
|
6
|
+
|
|
7
|
+
from .grids import Grid
|
|
8
|
+
from .kernels import Kernel
|
|
9
|
+
|
|
10
|
+
LN_2 = np.log(2)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _forward_hankel_transform(
|
|
14
|
+
a: npt.ArrayLike,
|
|
15
|
+
u: npt.ArrayLike,
|
|
16
|
+
logc: npt.ArrayLike,
|
|
17
|
+
dlog: npt.ArrayLike,
|
|
18
|
+
bias: npt.ArrayLike,
|
|
19
|
+
**kwargs,
|
|
20
|
+
):
|
|
21
|
+
"""
|
|
22
|
+
Low-level forward Hankel transform implementation.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
a : array_like
|
|
27
|
+
Input array with shape (n,).
|
|
28
|
+
u : array_like
|
|
29
|
+
FFT coefficients with shape (*batch_shape, ns) where ns = n//2 + 1.
|
|
30
|
+
logc : array_like
|
|
31
|
+
Log-center parameter. Scalar or shape (*batch_shape, 1).
|
|
32
|
+
dlog : array_like
|
|
33
|
+
Logarithmic spacing. Scalar or shape (*batch_shape, 1).
|
|
34
|
+
bias : array_like
|
|
35
|
+
Power-law bias. Scalar or shape (*batch_shape, 1).
|
|
36
|
+
**kwargs
|
|
37
|
+
Additional arguments for scipy.fft.rfft.
|
|
38
|
+
|
|
39
|
+
Returns
|
|
40
|
+
-------
|
|
41
|
+
ak : ndarray
|
|
42
|
+
Transformed array with shape (n,) for scalar params or (*batch_shape, n)
|
|
43
|
+
for batched params.
|
|
44
|
+
"""
|
|
45
|
+
a = np.asarray(a)
|
|
46
|
+
u = np.asarray(u)
|
|
47
|
+
logc = np.asarray(logc)
|
|
48
|
+
bias = np.asarray(bias)
|
|
49
|
+
na = a.shape[-1]
|
|
50
|
+
# Step 1: bias a by (r_n / r_0)^{-q}
|
|
51
|
+
i = np.arange(na).astype(a.dtype)
|
|
52
|
+
ic = (na - 1) / 2
|
|
53
|
+
bias_power_law = np.exp(-bias * (i - ic) * dlog)
|
|
54
|
+
a_biased = a * bias_power_law
|
|
55
|
+
|
|
56
|
+
# Step 2: FFT
|
|
57
|
+
a_biased_fftd = rfft(a_biased, **kwargs)
|
|
58
|
+
|
|
59
|
+
# Step 3: multiply by coefficients
|
|
60
|
+
# coeffs may be batched, while a is not
|
|
61
|
+
ak_biased = irfft(a_biased_fftd * u, na, **kwargs)
|
|
62
|
+
ak_biased = np.flip(ak_biased, axis=-1) # type: ignore
|
|
63
|
+
|
|
64
|
+
# Step 4: unbias ak by (k_0 r_0)^{-q} (k_n / k_0)^{-q}
|
|
65
|
+
ak = ak_biased * bias_power_law * np.exp(-bias * logc)
|
|
66
|
+
return ak
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _inverse_hankel_transform(
|
|
70
|
+
ak: npt.ArrayLike,
|
|
71
|
+
u: npt.ArrayLike,
|
|
72
|
+
logc: npt.ArrayLike,
|
|
73
|
+
dlog: npt.ArrayLike,
|
|
74
|
+
bias: npt.ArrayLike,
|
|
75
|
+
**kwargs,
|
|
76
|
+
):
|
|
77
|
+
"""
|
|
78
|
+
Low-level inverse Hankel transform implementation.
|
|
79
|
+
|
|
80
|
+
Parameters
|
|
81
|
+
----------
|
|
82
|
+
ak : array_like
|
|
83
|
+
Input array with shape (n,).
|
|
84
|
+
u : array_like
|
|
85
|
+
FFT coefficients with shape (*batch_shape, ns) where ns = n//2 + 1.
|
|
86
|
+
logc : array_like
|
|
87
|
+
Log-center parameter. Scalar or shape (*batch_shape, 1).
|
|
88
|
+
dlog : array_like
|
|
89
|
+
Logarithmic spacing. Scalar or shape (*batch_shape, 1).
|
|
90
|
+
bias : array_like
|
|
91
|
+
Power-law bias. Scalar or shape (*batch_shape, 1).
|
|
92
|
+
**kwargs
|
|
93
|
+
Additional arguments for scipy.fft.rfft.
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
a : ndarray
|
|
98
|
+
Inverse transformed array with shape (n,) for scalar params or
|
|
99
|
+
(*batch_shape, n) for batched params.
|
|
100
|
+
"""
|
|
101
|
+
ak = np.asarray(ak)
|
|
102
|
+
u = np.asarray(u)
|
|
103
|
+
logc = np.asarray(logc)
|
|
104
|
+
na = ak.shape[-1]
|
|
105
|
+
# Step 1: bias a by (k_0 r_0)^{q} (k_n / k_0)^{q}
|
|
106
|
+
i = np.arange(na).astype(ak.dtype)
|
|
107
|
+
ic = (na - 1) / 2
|
|
108
|
+
bias_power_law = np.exp(bias * (i - ic) * dlog)
|
|
109
|
+
ak_biased = ak * bias_power_law * np.exp(bias * logc)
|
|
110
|
+
|
|
111
|
+
# Step 2: FFT
|
|
112
|
+
ak_biased_fftd = rfft(ak_biased, **kwargs)
|
|
113
|
+
|
|
114
|
+
# Step 3: divide by coefficients
|
|
115
|
+
# coeffs may be batched, while a is not
|
|
116
|
+
a_biased = irfft(ak_biased_fftd / np.conjugate(u), na, **kwargs)
|
|
117
|
+
a_biased = np.flip(a_biased, axis=-1) # type: ignore
|
|
118
|
+
|
|
119
|
+
# Step 4: unbias ak by (r_n / r_0)^{q}
|
|
120
|
+
a = a_biased * bias_power_law
|
|
121
|
+
return a
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def optimal_logcenter(
|
|
125
|
+
kernel: Kernel, dlog: npt.ArrayLike, bias: npt.ArrayLike = 0.0
|
|
126
|
+
) -> npt.NDArray:
|
|
127
|
+
"""
|
|
128
|
+
Compute optimal log-center parameter to minimize ringing.
|
|
129
|
+
|
|
130
|
+
Implements Eq.(30) of https://jila.colorado.edu/~ajsh/FFTLog/fftlog.pdf
|
|
131
|
+
|
|
132
|
+
Parameters
|
|
133
|
+
----------
|
|
134
|
+
kernel : Kernel
|
|
135
|
+
Mellin transform kernel.
|
|
136
|
+
dlog : array_like
|
|
137
|
+
Logarithmic spacing. Can be scalar or array with shape (*batch_shape, 1).
|
|
138
|
+
bias : array_like, optional
|
|
139
|
+
Power-law bias exponent (default: 0.0). Can be scalar or array with
|
|
140
|
+
shape (*batch_shape, 1).
|
|
141
|
+
|
|
142
|
+
Returns
|
|
143
|
+
-------
|
|
144
|
+
logc : ndarray
|
|
145
|
+
Optimal log-center parameter. Scalar or shape (*batch_shape, 1).
|
|
146
|
+
"""
|
|
147
|
+
dlog = np.asarray(dlog)
|
|
148
|
+
bias = np.asarray(bias)
|
|
149
|
+
s = 1j * np.pi / dlog + 1
|
|
150
|
+
arg = np.angle(kernel.forward(s + bias))
|
|
151
|
+
return dlog * arg / np.pi
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def compute_kernel_coefficients(
|
|
155
|
+
kernel: Kernel,
|
|
156
|
+
n: int,
|
|
157
|
+
kr: npt.ArrayLike,
|
|
158
|
+
dlog: npt.ArrayLike,
|
|
159
|
+
bias: npt.ArrayLike = 0.0,
|
|
160
|
+
):
|
|
161
|
+
"""
|
|
162
|
+
Compute FFT coefficients for FFTLog transform.
|
|
163
|
+
|
|
164
|
+
Implements Eq.(18) of https://jila.colorado.edu/~ajsh/FFTLog/fftlog.pdf
|
|
165
|
+
|
|
166
|
+
Parameters
|
|
167
|
+
----------
|
|
168
|
+
kernel : Kernel
|
|
169
|
+
Mellin transform kernel.
|
|
170
|
+
n : int
|
|
171
|
+
Number of sampling points.
|
|
172
|
+
kr : array_like
|
|
173
|
+
The product k*r at the geometric center of the grid. Can be scalar
|
|
174
|
+
or array with shape (*batch_shape, 1).
|
|
175
|
+
dlog : array_like
|
|
176
|
+
Logarithmic spacing. Can be scalar or array with shape (*batch_shape, 1).
|
|
177
|
+
bias : array_like, optional
|
|
178
|
+
Power-law bias exponent (default: 0.0). Can be scalar or array with
|
|
179
|
+
shape (*batch_shape, 1).
|
|
180
|
+
|
|
181
|
+
Returns
|
|
182
|
+
-------
|
|
183
|
+
coeffs : ndarray
|
|
184
|
+
FFT coefficients with shape (ns,) for scalar inputs or (*batch_shape, ns)
|
|
185
|
+
for batched inputs, where ns = n//2 + 1.
|
|
186
|
+
"""
|
|
187
|
+
dlog = np.asarray(dlog)
|
|
188
|
+
bias = np.asarray(bias)
|
|
189
|
+
# Length of real Fourier transform
|
|
190
|
+
ns = n // 2 + 1
|
|
191
|
+
m = np.arange(0, ns)
|
|
192
|
+
angle = 2 * np.pi * m * 1j / (n * dlog)
|
|
193
|
+
s = angle + 1
|
|
194
|
+
coeffs = kernel.forward(s + bias)
|
|
195
|
+
kr = np.asarray(kr)
|
|
196
|
+
coeffs = coeffs / kr**angle
|
|
197
|
+
# Handle Nyquist frequency for even n
|
|
198
|
+
if n % 2 == 0:
|
|
199
|
+
coeffs[..., -1] = np.real(coeffs[..., -1])
|
|
200
|
+
|
|
201
|
+
return coeffs
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class FFTLog:
|
|
205
|
+
"""
|
|
206
|
+
Pure FFTLog transform algorithm for fast Hankel transforms.
|
|
207
|
+
|
|
208
|
+
FFTLog implements the fast Hankel transform algorithm described in Hamilton (2000).
|
|
209
|
+
This class focuses purely on the transform computation - for coordinate management
|
|
210
|
+
and data storage, use the Grid class from fftloggin.grids.
|
|
211
|
+
|
|
212
|
+
Parameters
|
|
213
|
+
----------
|
|
214
|
+
kernel : Kernel
|
|
215
|
+
Mellin transform kernel instance (e.g., BesselJKernel).
|
|
216
|
+
n : int
|
|
217
|
+
Number of sampling points.
|
|
218
|
+
dlog : array_like
|
|
219
|
+
Uniform logarithmic spacing. Can be scalar or array with shape
|
|
220
|
+
(*batch_shape, 1) for batch transforms.
|
|
221
|
+
bias : array_like, optional
|
|
222
|
+
Exponent of power law bias (default: 0.0). Can be scalar or array
|
|
223
|
+
with shape (*batch_shape, 1) for batch transforms.
|
|
224
|
+
lowring : bool, optional
|
|
225
|
+
Whether to snap kr to low-ringing condition (default: True).
|
|
226
|
+
kr : array_like, optional
|
|
227
|
+
The product k*r at the geometric center of the grid (default: 1.0).
|
|
228
|
+
Can be scalar or array with shape (*batch_shape, 1) for batch transforms.
|
|
229
|
+
|
|
230
|
+
Attributes
|
|
231
|
+
----------
|
|
232
|
+
kernel : Kernel
|
|
233
|
+
The Mellin transform kernel.
|
|
234
|
+
n : int
|
|
235
|
+
Number of sampling points.
|
|
236
|
+
dlog : array_like
|
|
237
|
+
Uniform logarithmic spacing. Scalar or shape (*batch_shape, 1).
|
|
238
|
+
bias : array_like
|
|
239
|
+
Exponent of power law bias. Scalar or shape (*batch_shape, 1).
|
|
240
|
+
lowring : bool
|
|
241
|
+
Whether kr is snapped to minimize ringing.
|
|
242
|
+
kr : array_like
|
|
243
|
+
The product k*r at the geometric center of the grid (cached property).
|
|
244
|
+
Scalar or shape (*batch_shape, 1).
|
|
245
|
+
logc : array_like
|
|
246
|
+
Natural logarithm of kr (cached property). Scalar or shape (*batch_shape, 1).
|
|
247
|
+
kernel_coefficients : ndarray
|
|
248
|
+
Precomputed FFT coefficients (cached property). Shape (*batch_shape, ns)
|
|
249
|
+
where ns = n//2 + 1.
|
|
250
|
+
|
|
251
|
+
Examples
|
|
252
|
+
--------
|
|
253
|
+
Direct FFTLog usage (you manage coordinates separately):
|
|
254
|
+
|
|
255
|
+
>>> import numpy as np
|
|
256
|
+
>>> from fftloggin import FFTLog
|
|
257
|
+
>>> from fftloggin.kernels import BesselJKernel
|
|
258
|
+
>>>
|
|
259
|
+
>>> # Create transform
|
|
260
|
+
>>> fftlog = FFTLog(kernel=BesselJKernel(0), n=128, dlog=0.05)
|
|
261
|
+
>>>
|
|
262
|
+
>>> # Transform data (you manage coordinates separately)
|
|
263
|
+
>>> a = np.random.randn(128)
|
|
264
|
+
>>> A = fftlog.forward(a)
|
|
265
|
+
|
|
266
|
+
For managing coordinates, use FFTLog.create_grid():
|
|
267
|
+
|
|
268
|
+
>>> import numpy as np
|
|
269
|
+
>>> from fftloggin import FFTLog
|
|
270
|
+
>>> from fftloggin.kernels import BesselJKernel
|
|
271
|
+
>>>
|
|
272
|
+
>>> # Create FFTLog from r array
|
|
273
|
+
>>> r = np.logspace(-2, 2, 128)
|
|
274
|
+
>>> fftlog = FFTLog.from_array(r, BesselJKernel(0), kr=1.0)
|
|
275
|
+
>>>
|
|
276
|
+
>>> # Create grid to manage coordinates
|
|
277
|
+
>>> grid = fftlog.create_grid(r=r)
|
|
278
|
+
>>>
|
|
279
|
+
>>> # Access coordinates
|
|
280
|
+
>>> print(grid.k.shape) # Output wavenumbers
|
|
281
|
+
(128,)
|
|
282
|
+
|
|
283
|
+
Batching with Array Parameters
|
|
284
|
+
-------------------------------
|
|
285
|
+
Parameters dlog, bias, and kr can be arrays for batch transforms.
|
|
286
|
+
Arrays must have shape (*batch_shape, 1) for proper broadcasting
|
|
287
|
+
with the sample dimension (n,), producing results with shape (*batch_shape, n).
|
|
288
|
+
|
|
289
|
+
Using the helper function (recommended):
|
|
290
|
+
|
|
291
|
+
>>> from fftloggin import prepare_batch_params
|
|
292
|
+
>>> dlog, bias, kr = prepare_batch_params(0.05, 0.0, [0.5, 1.0, 2.0])
|
|
293
|
+
>>> fftlog = FFTLog(kernel=BesselJKernel(0), n=128, dlog=dlog, bias=bias, kr=kr)
|
|
294
|
+
>>> a = np.random.randn(128)
|
|
295
|
+
>>> result = fftlog.forward(a)
|
|
296
|
+
>>> result.shape
|
|
297
|
+
(3, 128)
|
|
298
|
+
|
|
299
|
+
Manual reshaping for batched parameters:
|
|
300
|
+
|
|
301
|
+
>>> kr = np.array([0.5, 1.0, 2.0]).reshape(-1, 1) # shape (3, 1)
|
|
302
|
+
>>> fftlog = FFTLog(kernel=BesselJKernel(0), n=128, dlog=0.05, kr=kr)
|
|
303
|
+
>>> result = fftlog.forward(a) # shape (3, 128)
|
|
304
|
+
|
|
305
|
+
Multiple batch dimensions via outer product broadcasting:
|
|
306
|
+
|
|
307
|
+
>>> dlog, bias, kr = prepare_batch_params(
|
|
308
|
+
... [0.04, 0.05], # 2 values
|
|
309
|
+
... 0.0, # scalar
|
|
310
|
+
... [0.5, 1.0] # 2 values
|
|
311
|
+
... )
|
|
312
|
+
>>> # After prepare_batch_params: dlog=(2,1), bias=(), kr=(2,1)
|
|
313
|
+
>>> # They broadcast together to shape (2,1)
|
|
314
|
+
>>> fftlog = FFTLog(kernel=BesselJKernel(0), n=128, dlog=dlog, bias=bias, kr=kr)
|
|
315
|
+
>>> result = fftlog.forward(a) # shape (2, 128)
|
|
316
|
+
|
|
317
|
+
Important: Batched kernels can be combined with batched parameters.
|
|
318
|
+
Ensure batch shapes are compatible:
|
|
319
|
+
|
|
320
|
+
>>> mu = np.array([0, 1, 2]).reshape(-1, 1) # shape (3, 1)
|
|
321
|
+
>>> kr = np.array([0.5, 1.0]).reshape(-1, 1) # shape (2, 1)
|
|
322
|
+
>>> # These shapes (3,1) and (2,1) are NOT compatible for broadcasting
|
|
323
|
+
>>> # You must manually broadcast to a compatible shape like (3, 2, 1):
|
|
324
|
+
>>> mu_broadcast = mu.reshape(3, 1, 1) # shape (3, 1, 1)
|
|
325
|
+
>>> kr_broadcast = kr.reshape(1, 2, 1) # shape (1, 2, 1)
|
|
326
|
+
>>> kernel = BesselJKernel(mu_broadcast.squeeze()) # Remove trailing for kernel
|
|
327
|
+
>>> fftlog = FFTLog(kernel=kernel, n=128, dlog=0.05, kr=kr_broadcast)
|
|
328
|
+
>>> # Result would have shape (3, 2, 128)
|
|
329
|
+
|
|
330
|
+
See Also
|
|
331
|
+
--------
|
|
332
|
+
Grid : Workspace class that manages coordinates and data
|
|
333
|
+
BesselJKernel : Standard Hankel transform kernel
|
|
334
|
+
Kernel : Base class for custom kernels
|
|
335
|
+
|
|
336
|
+
References
|
|
337
|
+
----------
|
|
338
|
+
.. [1] Hamilton A. J. S., 2000, MNRAS, 312, 257 (astro-ph/9905191)
|
|
339
|
+
"""
|
|
340
|
+
|
|
341
|
+
def __init__(
|
|
342
|
+
self,
|
|
343
|
+
kernel: Kernel,
|
|
344
|
+
n: int,
|
|
345
|
+
dlog: npt.ArrayLike,
|
|
346
|
+
bias: npt.ArrayLike = 0.0,
|
|
347
|
+
lowring: bool = True,
|
|
348
|
+
kr: npt.ArrayLike = 1,
|
|
349
|
+
) -> None:
|
|
350
|
+
self._kernel = kernel
|
|
351
|
+
self._n = n
|
|
352
|
+
self._dlog = dlog
|
|
353
|
+
self._bias = bias
|
|
354
|
+
self._lowring = lowring
|
|
355
|
+
self._kr = kr
|
|
356
|
+
|
|
357
|
+
def _cleanup(self) -> None:
|
|
358
|
+
try:
|
|
359
|
+
del self.logc
|
|
360
|
+
except AttributeError:
|
|
361
|
+
pass
|
|
362
|
+
try:
|
|
363
|
+
del self.kernel_coefficients
|
|
364
|
+
except AttributeError:
|
|
365
|
+
pass
|
|
366
|
+
|
|
367
|
+
@property
|
|
368
|
+
def kernel(self) -> Kernel:
|
|
369
|
+
return self._kernel
|
|
370
|
+
|
|
371
|
+
@kernel.setter
|
|
372
|
+
def kernel(self, other: Kernel):
|
|
373
|
+
self._kernel = other
|
|
374
|
+
self._cleanup()
|
|
375
|
+
|
|
376
|
+
@property
|
|
377
|
+
def n(self) -> int:
|
|
378
|
+
return self._n
|
|
379
|
+
|
|
380
|
+
@n.setter
|
|
381
|
+
def n(self, other: int):
|
|
382
|
+
self._n = other
|
|
383
|
+
self._cleanup()
|
|
384
|
+
|
|
385
|
+
@property
|
|
386
|
+
def dlog(self) -> npt.ArrayLike:
|
|
387
|
+
return self._dlog
|
|
388
|
+
|
|
389
|
+
@dlog.setter
|
|
390
|
+
def dlog(self, other: npt.ArrayLike):
|
|
391
|
+
self._dlog = other
|
|
392
|
+
self._cleanup()
|
|
393
|
+
|
|
394
|
+
@property
|
|
395
|
+
def bias(self) -> npt.ArrayLike:
|
|
396
|
+
return self._bias
|
|
397
|
+
|
|
398
|
+
@bias.setter
|
|
399
|
+
def bias(self, other: npt.ArrayLike):
|
|
400
|
+
self._bias = other
|
|
401
|
+
self._cleanup()
|
|
402
|
+
|
|
403
|
+
@property
|
|
404
|
+
def lowring(self) -> bool:
|
|
405
|
+
return self._lowring
|
|
406
|
+
|
|
407
|
+
@lowring.setter
|
|
408
|
+
def lowring(self, other: bool):
|
|
409
|
+
self._lowring = other
|
|
410
|
+
self._cleanup()
|
|
411
|
+
|
|
412
|
+
@cached_property
|
|
413
|
+
def kr(self) -> npt.ArrayLike:
|
|
414
|
+
if self.lowring:
|
|
415
|
+
logc_snapped = self.shift_logcenter(np.log(self._kr))
|
|
416
|
+
return np.exp(logc_snapped)
|
|
417
|
+
else:
|
|
418
|
+
return self._kr
|
|
419
|
+
|
|
420
|
+
@cached_property
|
|
421
|
+
def logc(self) -> npt.ArrayLike:
|
|
422
|
+
return np.log(self.kr)
|
|
423
|
+
|
|
424
|
+
@cached_property
|
|
425
|
+
def kernel_coefficients(self) -> npt.NDArray:
|
|
426
|
+
return self._compute_kernel_coefficients()
|
|
427
|
+
|
|
428
|
+
def _compute_kernel_coefficients(self) -> npt.NDArray:
|
|
429
|
+
"""Compute FFT coefficients using instance parameters."""
|
|
430
|
+
return compute_kernel_coefficients(
|
|
431
|
+
self.kernel, self.n, self.kr, self.dlog, self.bias
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
@classmethod
|
|
435
|
+
def from_array(
|
|
436
|
+
cls,
|
|
437
|
+
x: npt.ArrayLike,
|
|
438
|
+
kernel: Kernel,
|
|
439
|
+
bias: npt.ArrayLike = 0.0,
|
|
440
|
+
kr: npt.ArrayLike = 1.0,
|
|
441
|
+
lowring: bool = True,
|
|
442
|
+
) -> "FFTLog":
|
|
443
|
+
"""
|
|
444
|
+
Create FFTLog instance from a log-spaced coordinate array.
|
|
445
|
+
|
|
446
|
+
Parameters
|
|
447
|
+
----------
|
|
448
|
+
x : array_like
|
|
449
|
+
Log-spaced coordinate array (1D).
|
|
450
|
+
kernel : Kernel
|
|
451
|
+
Mellin transform kernel.
|
|
452
|
+
bias : array_like, optional
|
|
453
|
+
Power-law bias exponent. Default is 0.0.
|
|
454
|
+
kr : array_like, optional
|
|
455
|
+
The product k*r at the geometric center of the grid. Default is 1.0.
|
|
456
|
+
lowring : bool, optional
|
|
457
|
+
Whether to snap kr to minimize ringing. Default is True.
|
|
458
|
+
|
|
459
|
+
Returns
|
|
460
|
+
-------
|
|
461
|
+
FFTLog
|
|
462
|
+
Configured FFTLog instance.
|
|
463
|
+
|
|
464
|
+
Examples
|
|
465
|
+
--------
|
|
466
|
+
>>> import numpy as np
|
|
467
|
+
>>> from fftloggin.fftlog import FFTLog
|
|
468
|
+
>>> from fftloggin.kernels import BesselJKernel
|
|
469
|
+
>>> r = np.logspace(-2, 2, 128)
|
|
470
|
+
>>> fftlog = FFTLog.from_array(r, BesselJKernel(0), bias=0.0, kr=1.0)
|
|
471
|
+
"""
|
|
472
|
+
from .grids import infer_dlog
|
|
473
|
+
|
|
474
|
+
x = np.asarray(x)
|
|
475
|
+
n = x.shape[-1]
|
|
476
|
+
dlog = infer_dlog(x)
|
|
477
|
+
|
|
478
|
+
return cls(kernel, n, dlog, bias=bias, lowring=lowring, kr=kr)
|
|
479
|
+
|
|
480
|
+
def create_grid(
|
|
481
|
+
self,
|
|
482
|
+
r: npt.ArrayLike | None = None,
|
|
483
|
+
k: npt.ArrayLike | None = None,
|
|
484
|
+
) -> Grid:
|
|
485
|
+
"""
|
|
486
|
+
Create a Grid from one coordinate array using the FFTLog kr parameter.
|
|
487
|
+
|
|
488
|
+
Exactly one of r or k must be provided. The other coordinate array
|
|
489
|
+
is computed using get_other_array() with the FFTLog instance's kr.
|
|
490
|
+
|
|
491
|
+
Parameters
|
|
492
|
+
----------
|
|
493
|
+
r : array_like, optional
|
|
494
|
+
Input radial coordinates. If provided, k is computed.
|
|
495
|
+
k : array_like, optional
|
|
496
|
+
Output wavenumber coordinates. If provided, r is computed.
|
|
497
|
+
|
|
498
|
+
Returns
|
|
499
|
+
-------
|
|
500
|
+
Grid
|
|
501
|
+
Configured Grid with both r and k arrays.
|
|
502
|
+
|
|
503
|
+
Raises
|
|
504
|
+
------
|
|
505
|
+
ValueError
|
|
506
|
+
If neither r nor k is provided, or if both are provided.
|
|
507
|
+
|
|
508
|
+
Examples
|
|
509
|
+
--------
|
|
510
|
+
>>> import numpy as np
|
|
511
|
+
>>> from fftloggin.fftlog import FFTLog
|
|
512
|
+
>>> from fftloggin.kernels import BesselJKernel
|
|
513
|
+
>>> r = np.logspace(-2, 2, 128)
|
|
514
|
+
>>> fftlog = FFTLog.from_array(r, BesselJKernel(0), kr=1.0)
|
|
515
|
+
>>> grid = fftlog.create_grid(r=r)
|
|
516
|
+
>>> print(f"{grid.k[0]:.6f}") # First k value
|
|
517
|
+
0.010129
|
|
518
|
+
"""
|
|
519
|
+
from .grids import Grid, get_other_array
|
|
520
|
+
|
|
521
|
+
if (r is None and k is None) or (r is not None and k is not None):
|
|
522
|
+
raise ValueError(
|
|
523
|
+
"Exactly one of 'r' or 'k' must be provided. "
|
|
524
|
+
f"Got r={r is not None}, k={k is not None}"
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
if r is not None:
|
|
528
|
+
r_arr = np.asarray(r)
|
|
529
|
+
k_arr = get_other_array(r_arr, self.logc)
|
|
530
|
+
return Grid(r_arr, k_arr)
|
|
531
|
+
else:
|
|
532
|
+
k_arr = np.asarray(k)
|
|
533
|
+
r_arr = get_other_array(k_arr, self.logc)
|
|
534
|
+
return Grid(r_arr, k_arr)
|
|
535
|
+
|
|
536
|
+
def optimal_logcenter(self) -> npt.NDArray:
|
|
537
|
+
"""
|
|
538
|
+
Implements Eq.(30) of https://jila.colorado.edu/~ajsh/FFTLog/fftlog.pdf
|
|
539
|
+
"""
|
|
540
|
+
return optimal_logcenter(self.kernel, self.dlog, self.bias)
|
|
541
|
+
|
|
542
|
+
def shift_logcenter(self, logc: npt.ArrayLike) -> npt.NDArray:
|
|
543
|
+
logc = np.asarray(logc)
|
|
544
|
+
dlog = np.asarray(self.dlog)
|
|
545
|
+
optimal = self.optimal_logcenter()
|
|
546
|
+
# Snap to nearest integer multiple of dlog from optimal
|
|
547
|
+
# This matches Fortran's krgood: krgood = kr * exp((arg - round(arg)) * dlnr)
|
|
548
|
+
shift = (logc - optimal) / dlog
|
|
549
|
+
return optimal + np.round(shift) * dlog
|
|
550
|
+
|
|
551
|
+
def forward(
|
|
552
|
+
self,
|
|
553
|
+
a: npt.ArrayLike,
|
|
554
|
+
**kwargs,
|
|
555
|
+
) -> np.ndarray:
|
|
556
|
+
"""
|
|
557
|
+
Perform forward Hankel transform: a(r) -> A(k).
|
|
558
|
+
|
|
559
|
+
Computes the discrete Hankel transform using the FFTLog algorithm.
|
|
560
|
+
This is a pure computation method - coordinate management should be
|
|
561
|
+
handled separately (typically via the Grid class).
|
|
562
|
+
|
|
563
|
+
Parameters
|
|
564
|
+
----------
|
|
565
|
+
a : array_like
|
|
566
|
+
Real input array to be transformed. Must be sampled on a
|
|
567
|
+
logarithmically-spaced grid with spacing dlog.
|
|
568
|
+
**kwargs
|
|
569
|
+
Additional keyword arguments passed to scipy.fft.rfft.
|
|
570
|
+
|
|
571
|
+
Returns
|
|
572
|
+
-------
|
|
573
|
+
A : ndarray
|
|
574
|
+
The transformed output array, representing the function on
|
|
575
|
+
a logarithmically-spaced wavenumber grid.
|
|
576
|
+
|
|
577
|
+
Notes
|
|
578
|
+
-----
|
|
579
|
+
The array size is automatically adjusted if input size doesn't match
|
|
580
|
+
self.n. The transform assumes input is sampled on a log-spaced grid.
|
|
581
|
+
|
|
582
|
+
Examples
|
|
583
|
+
--------
|
|
584
|
+
>>> import numpy as np
|
|
585
|
+
>>> from fftloggin import FFTLog
|
|
586
|
+
>>> from fftloggin.kernels import BesselJKernel
|
|
587
|
+
>>>
|
|
588
|
+
>>> # Direct usage (you manage coordinates)
|
|
589
|
+
>>> fftlog = FFTLog(kernel=BesselJKernel(0), n=128, dlog=0.05)
|
|
590
|
+
>>> a = np.random.randn(128)
|
|
591
|
+
>>> A = fftlog.forward(a)
|
|
592
|
+
>>> print(A.shape)
|
|
593
|
+
(128,)
|
|
594
|
+
|
|
595
|
+
See Also
|
|
596
|
+
--------
|
|
597
|
+
inverse : Inverse Hankel transform
|
|
598
|
+
Grid.forward : Recommended high-level interface with coordinate management
|
|
599
|
+
"""
|
|
600
|
+
a = np.asarray(a)
|
|
601
|
+
na = a.shape[-1]
|
|
602
|
+
if na != self.n:
|
|
603
|
+
self.n = na
|
|
604
|
+
|
|
605
|
+
return _forward_hankel_transform(
|
|
606
|
+
a, self.kernel_coefficients, self.logc, self.dlog, self.bias, **kwargs
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
def inverse(
|
|
610
|
+
self,
|
|
611
|
+
ak: npt.ArrayLike,
|
|
612
|
+
**kwargs,
|
|
613
|
+
) -> np.ndarray:
|
|
614
|
+
"""
|
|
615
|
+
Perform inverse Hankel transform: A(k) -> a(r).
|
|
616
|
+
|
|
617
|
+
Computes the inverse discrete Hankel transform using the FFTLog algorithm.
|
|
618
|
+
This is a pure computation method - coordinate management should be
|
|
619
|
+
handled separately (typically via the Grid class).
|
|
620
|
+
|
|
621
|
+
Parameters
|
|
622
|
+
----------
|
|
623
|
+
ak : array_like
|
|
624
|
+
Real input array to be inverse transformed. Must be sampled on a
|
|
625
|
+
logarithmically-spaced grid with spacing dlog.
|
|
626
|
+
**kwargs
|
|
627
|
+
Additional keyword arguments passed to scipy.fft.rfft.
|
|
628
|
+
|
|
629
|
+
Returns
|
|
630
|
+
-------
|
|
631
|
+
a : ndarray
|
|
632
|
+
The inverse transformed output array, representing the function on
|
|
633
|
+
a logarithmically-spaced radial grid.
|
|
634
|
+
|
|
635
|
+
Notes
|
|
636
|
+
-----
|
|
637
|
+
The array size is automatically adjusted if input size doesn't match
|
|
638
|
+
self.n. The transform assumes input is sampled on a log-spaced grid.
|
|
639
|
+
|
|
640
|
+
Examples
|
|
641
|
+
--------
|
|
642
|
+
>>> import numpy as np
|
|
643
|
+
>>> from fftloggin import FFTLog
|
|
644
|
+
>>> from fftloggin.kernels import BesselJKernel
|
|
645
|
+
>>>
|
|
646
|
+
>>> # Direct usage (you manage coordinates)
|
|
647
|
+
>>> fftlog = FFTLog(kernel=BesselJKernel(0), n=128, dlog=0.05)
|
|
648
|
+
>>> A = np.random.randn(128)
|
|
649
|
+
>>> a = fftlog.inverse(A)
|
|
650
|
+
>>> print(a.shape)
|
|
651
|
+
(128,)
|
|
652
|
+
|
|
653
|
+
See Also
|
|
654
|
+
--------
|
|
655
|
+
forward : Forward Hankel transform
|
|
656
|
+
Grid.inverse : Recommended high-level interface with coordinate management
|
|
657
|
+
"""
|
|
658
|
+
ak = np.asarray(ak)
|
|
659
|
+
na = ak.shape[-1]
|
|
660
|
+
if na != self.n:
|
|
661
|
+
self.n = na
|
|
662
|
+
|
|
663
|
+
return _inverse_hankel_transform(
|
|
664
|
+
ak, self.kernel_coefficients, self.logc, self.dlog, self.bias, **kwargs
|
|
665
|
+
)
|