mirror of
https://github.com/jkl1337/SwiftPFFFT.git
synced 2026-01-02 03:34:31 -06:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ba20b23d26 | |||
| 52d5bcd96d | |||
| fa3bce9237 | |||
| 437f984b11 | |||
| 795003432e | |||
| 2ffe635962 | |||
| ab36848004 | |||
| 157b187116 | |||
| 01c48650e9 | |||
| f52a4147b8 |
52
.github/workflows/test.yml
vendored
Normal file
52
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
name: test
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
test-linux:
|
||||
name: Tests Linux
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ toJSON(matrix) }}
|
||||
cancel-in-progress: true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
swift: ["5.9", "5.10", "6.0"]
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
container: swift:${{ matrix.swift }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build
|
||||
run: swift build
|
||||
|
||||
- name: Run tests
|
||||
run: swift test
|
||||
|
||||
test-macos:
|
||||
name: Tests MacOS
|
||||
runs-on: macos-14
|
||||
|
||||
steps:
|
||||
- name: Select toolchain
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with: { xcode-version: latest-stable }
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build
|
||||
run: swift build
|
||||
|
||||
- name: Run tests
|
||||
run: swift test
|
||||
5
.sourcekit-lsp/config.json
Normal file
5
.sourcekit-lsp/config.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"swiftPM": {
|
||||
"cCompilerFlags:" ["-DPFFFT_SCALVEC_ENABLED=1", "-DPFFFT_ENABLE_NEON", "_USE_MATH_DEFINES", "NDEBUG"],
|
||||
}
|
||||
}
|
||||
32
LICENSE
Normal file
32
LICENSE
Normal file
@@ -0,0 +1,32 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2024 John K. Luebs
|
||||
Copyright (c) 2020 Dario Mambro (dario.mambro@gmail.com)
|
||||
Copyright (c) 2019 Hayati Ayguen (h_ayguen@web.de)
|
||||
Copyright (c) 2013 Julien Pommier (pommier@modartt.com)
|
||||
Copyright (c) 2004 the University Corporation for Atmospheric Research ("UCAR"). All rights reserved. Developed by NCAR's Computational and Information Systems Laboratory, UCAR, www.cisl.ucar.edu.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
37
LICENSE.txt
37
LICENSE.txt
@@ -1,37 +0,0 @@
|
||||
Copyright (c) 2024 John K. Luebs
|
||||
Copyright (c) 2020 Dario Mambro ( dario.mambro@gmail.com )
|
||||
Copyright (c) 2019 Hayati Ayguen ( h_ayguen@web.de )
|
||||
Copyright (c) 2013 Julien Pommier ( pommier@modartt.com )
|
||||
|
||||
Copyright (c) 2004 the University Corporation for Atmospheric
|
||||
Research ("UCAR"). All rights reserved. Developed by NCAR's
|
||||
Computational and Information Systems Laboratory, UCAR,
|
||||
www.cisl.ucar.edu.
|
||||
|
||||
Redistribution and use of the Software in source and binary forms,
|
||||
with or without modification, is permitted provided that the
|
||||
following conditions are met:
|
||||
|
||||
- Neither the names of NCAR's Computational and Information Systems
|
||||
Laboratory, the University Corporation for Atmospheric Research,
|
||||
nor the names of its sponsors or contributors may be used to
|
||||
endorse or promote products derived from this Software without
|
||||
specific prior written permission.
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notices, this list of conditions, and the disclaimer below.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions, and the disclaimer below in the
|
||||
documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
|
||||
SOFTWARE.
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"originHash" : "d50b9049eb671b1ad14bc8ba592c78735f9e357a6b59835343af6e20e8be4701",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "swift-numerics",
|
||||
@@ -11,5 +10,5 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
"version" : 2
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// swift-tools-version: 6.0
|
||||
// swift-tools-version: 5.9
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
@@ -20,14 +20,15 @@ let package = Package(
|
||||
publicHeadersPath: "include",
|
||||
cSettings: [
|
||||
.define("PFFFT_SCALVEC_ENABLED", to: "1"),
|
||||
.define("PFFFT_ENABLE_NEON"),
|
||||
.define("_USE_MATH_DEFINES"),
|
||||
.define("NDEBUG"),
|
||||
.unsafeFlags(["-O3"]),
|
||||
]
|
||||
),
|
||||
.target(
|
||||
name: "PFFFT",
|
||||
dependencies: ["PFFFTLib", .product(name: "Numerics", package: "swift-numerics")]
|
||||
dependencies: ["PFFFTLib", .product(name: "Numerics", package: "swift-numerics")],
|
||||
swiftSettings: [.enableExperimentalFeature("AccessLevelOnImport")]
|
||||
),
|
||||
.testTarget(
|
||||
name: "PFFFTTests",
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
[](https://github.com/jkl1337/SwiftPFFFT/actions/workflows/swift.yml)
|
||||
[](https://swiftpackageindex.com/jkl1337/SwiftPFFFT)
|
||||
[](https://swiftpackageindex.com/jkl1337/SwiftPFFFT)
|
||||
|
||||
# SwiftPFFFT
|
||||
|
||||
Swift package providing a PFFFT (Pretty Fast, Fast Fourier Transform) library with wrapper.
|
||||
@@ -21,7 +25,7 @@ performance with much simpler usage and a permissive 3 clause BSD license.
|
||||
let fft = try FFT<Complex<Float>>(n: 16)
|
||||
let signal = fft.makeSignalBuffer()
|
||||
|
||||
signal.mutateEach { (i, v) in
|
||||
signal.enumerateInPlace { (i, v) in
|
||||
v = Complex(Float(i) + 1.0, Float(i) - 2.0)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,10 @@ import RealModule
|
||||
|
||||
let bufferAlignment = 32
|
||||
|
||||
/// Thin wrapper around `UnsafeMutableBufferPointer` providing correct alignment.
|
||||
/// PFFFT internally assumes all buffers passed are aligned to 16 or 32 bytes
|
||||
/// depending on the platform. Thie type provides correctly aligned buffers
|
||||
/// and provides some in place mutating methods.
|
||||
@frozen
|
||||
public struct Buffer<T>: ~Copyable {
|
||||
public let buffer: UnsafeMutableBufferPointer<T>
|
||||
@@ -32,13 +36,21 @@ public struct Buffer<T>: ~Copyable {
|
||||
try body(UnsafeMutableRawBufferPointer(buffer))
|
||||
}
|
||||
|
||||
/// Return an array with results of mapping given closure over buffer elements.
|
||||
/// - Parameter transform: A mapping closure. `transform` accepts an element of the buffer
|
||||
/// as its parameter and returns a transformed value of any type.
|
||||
/// - Returns: An array containing the transformed elements of the buffer.
|
||||
@inlinable public func map<U>(_ transform: (T) throws -> U) rethrows -> [U] {
|
||||
try buffer.map(transform)
|
||||
}
|
||||
|
||||
@inlinable public func mutateEach(_ body: (Int, inout T) throws -> Void) rethrows {
|
||||
/// Calls the given closure on each element in the buffer for mutation in place.
|
||||
/// - Parameter body: A closure that accepts a zero-based enumeration index.
|
||||
/// and must return a new value for the element at that index.
|
||||
/// `body` may throw and the error will be propagated to the caller.
|
||||
@inlinable public func mapInPlace(_ body: (Int) throws -> T) rethrows {
|
||||
for i in 0 ..< buffer.count {
|
||||
try body(i, &buffer[i])
|
||||
try buffer[i] = body(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,9 +64,23 @@ public protocol ComplexType {
|
||||
extension Complex: ComplexType {}
|
||||
|
||||
public extension Buffer where T: ComplexType {
|
||||
@inlinable public func mutateEachSwapLast(_ body: (Int, inout T) throws -> Void) rethrows {
|
||||
/// Calls the given closure on each space in the buffer for mutation in place with
|
||||
/// Nyquist replacement at the end.
|
||||
///
|
||||
/// When operating on Complex->Real transforms PFFFT internally uses a slightly more compact
|
||||
/// but less common encoding of the DC (0) and Nyquist (n/2) components. Since these two
|
||||
/// spectral components are always real, PFFFT places the DC (0) component
|
||||
/// in the real part of the 0th element as expected, but places the Nyquist `(n/2)` component
|
||||
/// in the imaginary part of the 0th element.
|
||||
/// This enumerator works like `mapInPlace` but at the end places the real part of the
|
||||
/// n/2 component into the imaginary part of the 0th element. In normal use it is expected
|
||||
/// that a spectral buffer of 1 extra element is created such that `count == (n/2 + 1)`.
|
||||
/// - Parameter body: A closure that accepts a zero-based enumeration index.
|
||||
/// and must return a new value for the element at that index.
|
||||
/// `body` may throw and the error will be propagated to the caller.
|
||||
@inlinable func mapInPlaceSwapLast(_ body: (Int) throws -> T) rethrows {
|
||||
for i in 0 ..< buffer.count {
|
||||
try body(i, &buffer[i])
|
||||
try buffer[i] = body(i)
|
||||
}
|
||||
buffer[0].imaginary = buffer[buffer.count - 1].real
|
||||
}
|
||||
|
||||
@@ -159,14 +159,20 @@ public struct FFT<T: FFTElement>: ~Copyable {
|
||||
|
||||
let ptr: OpaquePointer
|
||||
let n: Int
|
||||
let work: Buffer<ScalarType>?
|
||||
let work: Buffer<ScalarType>
|
||||
let setup: Setup
|
||||
|
||||
public init(setup: Setup) {
|
||||
self.setup = setup
|
||||
ptr = setup.ptr
|
||||
n = setup.n
|
||||
work = n > 4096 ? Buffer<ScalarType>(capacity: T.self == ComplexType.self ? 2 * n : n) : nil
|
||||
|
||||
let workCapacity = if n > 4096 {
|
||||
T.self == ComplexType.self ? 2 * n : n
|
||||
} else {
|
||||
0
|
||||
}
|
||||
work = Buffer(capacity: workCapacity)
|
||||
}
|
||||
|
||||
/// Initialize the FFT implementation with the given size and type.
|
||||
@@ -202,10 +208,11 @@ public struct FFT<T: FFTElement>: ~Copyable {
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
func toAddress(_ work: borrowing Buffer<ScalarType>?) -> UnsafeMutablePointer<ScalarType>? {
|
||||
switch work {
|
||||
case let .some(b): return b.baseAddress
|
||||
case .none: return nil
|
||||
var workPtr: UnsafeMutablePointer<ScalarType>? {
|
||||
if work.count > 0 {
|
||||
return work.baseAddress
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,12 +278,12 @@ public struct FFT<T: FFTElement>: ~Copyable {
|
||||
/// - sign: The direction of the FFT.
|
||||
public func forward(signal: borrowing Buffer<T>, spectrum: borrowing Buffer<ComplexType>) {
|
||||
checkFftBufferCounts(signal: signal, spectrum: spectrum)
|
||||
ScalarType.pffftTransformOrdered(ptr, rebind(signal), rebind(spectrum), toAddress(work), .forward)
|
||||
ScalarType.pffftTransformOrdered(ptr, rebind(signal), rebind(spectrum), workPtr, .forward)
|
||||
}
|
||||
|
||||
public func inverse(spectrum: borrowing Buffer<ComplexType>, signal: borrowing Buffer<T>) {
|
||||
checkFftBufferCounts(signal: signal, spectrum: spectrum)
|
||||
ScalarType.pffftTransformOrdered(ptr, rebind(spectrum), rebind(signal), toAddress(work), .backward)
|
||||
ScalarType.pffftTransformOrdered(ptr, rebind(spectrum), rebind(signal), workPtr, .backward)
|
||||
}
|
||||
|
||||
/// Perform a forward FFT on the input buffer, with implementation defined order.
|
||||
@@ -290,12 +297,12 @@ public struct FFT<T: FFTElement>: ~Copyable {
|
||||
/// - sign: The direction of the FFT.
|
||||
public func forwardToInternalLayout(signal: borrowing Buffer<T>, spectrum: borrowing Buffer<ScalarType>) {
|
||||
checkFftInternalLayoutBufferCounts(signal: signal, spectrum: spectrum)
|
||||
ScalarType.pffftTransform(ptr, rebind(signal), spectrum.baseAddress, toAddress(work), .forward)
|
||||
ScalarType.pffftTransform(ptr, rebind(signal), spectrum.baseAddress, workPtr, .forward)
|
||||
}
|
||||
|
||||
public func inverseFromInternalLayout(spectrum: borrowing Buffer<ScalarType>, signal: borrowing Buffer<T>) {
|
||||
checkFftInternalLayoutBufferCounts(signal: signal, spectrum: spectrum)
|
||||
ScalarType.pffftTransform(ptr, spectrum.baseAddress, rebind(signal), toAddress(work), .backward)
|
||||
ScalarType.pffftTransform(ptr, spectrum.baseAddress, rebind(signal), workPtr, .backward)
|
||||
}
|
||||
|
||||
public func reorder(spectrum: borrowing Buffer<ScalarType>, output: borrowing Buffer<ComplexType>) {
|
||||
|
||||
@@ -1061,12 +1061,14 @@ SETUP_STRUCT *FUNC_NEW_SETUP(int N, pffft_transform_t transform) {
|
||||
if (transform == PFFFT_REAL) { if ((N%(2*SIMD_SZ*SIMD_SZ)) || N<=0) return s; }
|
||||
if (transform == PFFFT_COMPLEX) { if ((N%( SIMD_SZ*SIMD_SZ)) || N<=0) return s; }
|
||||
s = (SETUP_STRUCT*)malloc(sizeof(SETUP_STRUCT));
|
||||
if (!s) return s;
|
||||
/* assert((N % 32) == 0); */
|
||||
s->N = N;
|
||||
s->transform = transform;
|
||||
/* nb of complex simd vectors */
|
||||
s->Ncvec = (transform == PFFFT_REAL ? N/2 : N)/SIMD_SZ;
|
||||
s->data = (v4sf*)FUNC_ALIGNED_MALLOC(2*s->Ncvec * sizeof(v4sf));
|
||||
if (!s->data) { free(s); return 0; }
|
||||
s->e = (float*)s->data;
|
||||
s->twiddle = (float*)(s->data + (2*s->Ncvec*(SIMD_SZ-1))/SIMD_SZ);
|
||||
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import Testing
|
||||
import ComplexModule
|
||||
@testable import PFFFT
|
||||
import XCTest
|
||||
|
||||
@Test func fftFloat() async throws {
|
||||
|
||||
final class FFTTests: XCTestCase {
|
||||
func testFftComplexFloat() throws {
|
||||
let fft = try FFT<Complex<Float>>(n: 16)
|
||||
let signal = fft.makeSignalBuffer()
|
||||
let spectrum = fft.makeSpectrumBuffer()
|
||||
|
||||
signal.mutateEach { (i, v) in
|
||||
v = Complex(Float(i) + 1.0, Float(i) - 2.0)
|
||||
}
|
||||
signal.mapInPlace { Complex(Float($0) + 1.0, Float($0) - 2.0) }
|
||||
|
||||
fft.forward(signal: signal, spectrum: spectrum)
|
||||
|
||||
@@ -33,8 +31,8 @@ import ComplexModule
|
||||
.init(11.313707, -27.31371),
|
||||
.init(32.218716, -48.218716),
|
||||
]
|
||||
zip(result, expected).forEach { r, e in
|
||||
#expect(r.isApproximatelyEqual(to: e))
|
||||
for (r, e) in zip(result, expected) {
|
||||
XCTAssert(r.isApproximatelyEqual(to: e))
|
||||
}
|
||||
|
||||
fft.inverse(spectrum: spectrum, signal: signal)
|
||||
@@ -44,7 +42,8 @@ import ComplexModule
|
||||
Complex(Float(i) + 1.0, Float(i) - 2.0) * 16
|
||||
}
|
||||
|
||||
zip(signalResult, signalExpected).forEach { r, e in
|
||||
#expect(r.isApproximatelyEqual(to: e))
|
||||
for (r, e) in zip(signalResult, signalExpected) {
|
||||
XCTAssert(r.isApproximatelyEqual(to: e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user