diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..63964b9 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,37 @@ +name: CI +on: + pull_request: + push: + branches: + - main + tags: '*' +jobs: + test: + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + version: + - '1.6' + - '1' + os: + - ubuntu-latest + - windows-latest + - macOS-latest + arch: + - x64 + steps: + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v1 + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - uses: julia-actions/cache@v1 + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-runtest@v1 + - uses: julia-actions/julia-processcoverage@v1 + - uses: codecov/codecov-action@v4 + with: + file: lcov.info + token: ${{ secrets.CODECOV_TOKEN }} # required diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml new file mode 100644 index 0000000..b116979 --- /dev/null +++ b/.github/workflows/CompatHelper.yml @@ -0,0 +1,33 @@ +name: CompatHelper +on: + schedule: + - cron: 0 0 * * * + workflow_dispatch: +jobs: + CompatHelper: + runs-on: ubuntu-latest + steps: + - name: "Add the General registry via Git" + run: | + using Pkg + ENV["JULIA_PKG_SERVER"] = "" + Pkg.Registry.add("General") + Pkg.Registry.add(RegistrySpec(url="https://github.com/HolyLab/HolyLabRegistry.git")) + shell: julia --color=yes {0} + - name: "Install CompatHelper" + run: | + import Pkg + name = "CompatHelper" + uuid = "aa819f21-2bde-4658-8897-bab36330d9b7" + version = "3" + Pkg.add(; name, uuid, version) + shell: julia --color=yes {0} + - name: "Run CompatHelper" + run: | + import CompatHelper + CompatHelper.main() + shell: julia --color=yes {0} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} + # COMPATHELPER_PRIV: ${{ secrets.COMPATHELPER_PRIV }} diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml new file mode 100644 index 0000000..11c0586 --- /dev/null +++ b/.github/workflows/TagBot.yml @@ -0,0 +1,34 @@ +name: TagBot +on: + issue_comment: + types: + - created + workflow_dispatch: + inputs: + lookback: + default: 3 +permissions: + actions: read + checks: read + contents: write + deployments: read + issues: read + discussions: read + packages: read + pages: read + pull-requests: read + repository-projects: read + security-events: read + statuses: read +jobs: + TagBot: + if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' + runs-on: ubuntu-latest + steps: + - uses: JuliaRegistries/TagBot@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + # Edit the following line to reflect the actual name of the GitHub Secret containing your private key + ssh: ${{ secrets.DOCUMENTER_KEY }} + # ssh: ${{ secrets.NAME_OF_MY_SSH_PRIVATE_KEY_SECRET }} + registry: HolyLab/HolyLabRegistry diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..06b95c1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ + +Manifest.toml +.DS_Store diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..32e3083 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,22 @@ +The RFFT.jl package is licensed under the MIT "Expat" License: + +> Copyright (c) 2013-2021: Tim Holy. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE 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 +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 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 IN THE +> SOFTWARE. +> diff --git a/Project.toml b/Project.toml new file mode 100644 index 0000000..75370b3 --- /dev/null +++ b/Project.toml @@ -0,0 +1,17 @@ +name = "RFFT" +uuid = "3645faec-0534-4e91-afef-021db0981eec" +authors = ["Tim Holy "] +version = "1.0.0" + +[deps] +FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341" +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" + +[compat] +FFTW = "1" + +[extras] +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[targets] +test = ["Test"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..7242989 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# RFFT.jl + +This is a fork of https://github.com/HolyLab/RFFT.jl with a new UUID such that it can be registered on the General registry. + +---- + +In-place real FFTs for Julia + +For example +```julia +import RFFT + +a = rand(Float64, 100, 150) + +# initialize a buffer 'RCpair' that contains a real and complex space +buf = RFFT.RCpair{Float64}(undef, size(a)) + +# create the plan +plan = RFFT.plan_rfft!(buf; flags=FFTW.MEASURE) + +# use the plan and buffer on a new array +new = rand(Float64, 100, 150) +copy!(buf, new) +new_fft = plan(buf) + +``` diff --git a/src/RFFT.jl b/src/RFFT.jl new file mode 100644 index 0000000..93eee73 --- /dev/null +++ b/src/RFFT.jl @@ -0,0 +1,73 @@ +module RFFT + +using FFTW, LinearAlgebra + +export RCpair, plan_rfft!, plan_irfft!, rfft!, irfft!, normalization + +import Base: real, complex, copy, copy! + +mutable struct RCpair{T<:AbstractFloat,N,RType<:AbstractArray{T,N},CType<:AbstractArray{Complex{T},N}} + R::RType + C::CType + dims::Vector{Int} +end + +function RCpair{T}(::UndefInitializer, realsize::Dims{N}, dims=1:length(realsize)) where {T<:AbstractFloat,N} + sz = [realsize...] + firstdim = dims[1] + sz[firstdim] = realsize[firstdim]>>1 + 1 + sz2 = copy(sz) + sz2[firstdim] *= 2 + R = Array{T,N}(undef, (sz2...,)::Dims{N}) + C = unsafe_wrap(Array, convert(Ptr{Complex{T}}, pointer(R)), (sz...,)::Dims{N}) # work around performance problems of reinterpretarray + RCpair(view(R, map(n->1:n, realsize)...), C, [dims...]) +end + +RCpair(A::Array{T}, dims=1:ndims(A)) where {T<:AbstractFloat} = copy!(RCpair{T}(undef, size(A), dims), A) + +real(RC::RCpair) = RC.R +complex(RC::RCpair) = RC.C + +copy!(RC::RCpair, A::AbstractArray{T}) where {T<:Real} = (copy!(RC.R, A); RC) +copy!(RC::RCpair, A::AbstractArray{T}) where {T<:Complex} = (copy!(RC.C, A); RC) +function copy(RC::RCpair{T,N}) where {T,N} + C = copy(RC.C) + R = reshape(reinterpret(T, C), size(parent(RC.R))) + RCpair(view(R, RC.R.indices...), C, copy(RC.dims)) +end + +# New API +rplan_fwd(R, C, dims, flags, tlim) = + FFTW.rFFTWPlan{eltype(R),FFTW.FORWARD,true,ndims(R)}(R, C, dims, flags, tlim) +rplan_inv(R, C, dims, flags, tlim) = + FFTW.rFFTWPlan{eltype(R),FFTW.BACKWARD,true,ndims(R)}(R, C, dims, flags, tlim) +function plan_rfft!(RC::RCpair{T}; flags::Integer = FFTW.ESTIMATE, timelimit::Real = FFTW.NO_TIMELIMIT) where T + p = rplan_fwd(RC.R, RC.C, RC.dims, flags, timelimit) + return Z::RCpair -> begin + FFTW.assert_applicable(p, Z.R, Z.C) + FFTW.unsafe_execute!(p, Z.R, Z.C) + return Z + end +end +function plan_irfft!(RC::RCpair{T}; flags::Integer = FFTW.ESTIMATE, timelimit::Real = FFTW.NO_TIMELIMIT) where T + p = rplan_inv(RC.C, RC.R, RC.dims, flags, timelimit) + return Z::RCpair -> begin + FFTW.assert_applicable(p, Z.C, Z.R) + FFTW.unsafe_execute!(p, Z.C, Z.R) + rmul!(Z.R, 1 / prod(size(Z.R)[Z.dims])) + return Z + end +end +function rfft!(RC::RCpair{T}) where T + p = rplan_fwd(RC.R, RC.C, RC.dims, FFTW.ESTIMATE, FFTW.NO_TIMELIMIT) + FFTW.unsafe_execute!(p, RC.R, RC.C) + return RC +end +function irfft!(RC::RCpair{T}) where T + p = rplan_inv(RC.C, RC.R, RC.dims, FFTW.ESTIMATE, FFTW.NO_TIMELIMIT) + FFTW.unsafe_execute!(p, RC.C, RC.R) + rmul!(RC.R, 1 / prod(size(RC.R)[RC.dims])) + return RC +end + +end diff --git a/test/runtests.jl b/test/runtests.jl new file mode 100644 index 0000000..e049cd2 --- /dev/null +++ b/test/runtests.jl @@ -0,0 +1,27 @@ +import RFFT +using Test, FFTW, LinearAlgebra + +@testset "RFFT.jl" begin + for dims in (1:2, 1, 2) + for sz in ((5,6), (6,5)) + pair = RFFT.RCpair{Float64}(undef, sz, dims) + r = @inferred(real(pair)) + c = @inferred(complex(pair)) + b = rand(eltype(r), size(r)) + pair = RFFT.RCpair(b, dims) + copyto!(r, b) + copy!(pair, c) # for coverage + RFFT.rfft!(pair) + RFFT.irfft!(pair) + @test r ≈ b + pfwd = RFFT.plan_rfft!(pair) + pinv = RFFT.plan_irfft!(pair) + pinv(pfwd(pair)) + @test r ≈ b + + pair2 = copy(pair) + @test real(pair2) == real(pair) + @test complex(pair2) == complex(pair) + end + end +end