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 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
+ )