Showing preview only (354K chars total). Download the full file or copy to clipboard to get everything.
Repository: JuliaApproximation/FastTransforms.jl
Branch: master
Commit: 14a311816ffb
Files: 59
Total size: 331.9 KB
Directory structure:
gitextract_cc1mg2bt/
├── .github/
│ └── workflows/
│ ├── CIWindows.yml
│ ├── CompatHelper.yml
│ ├── TagBot.yml
│ ├── ci.yml
│ ├── docs.yml
│ └── downstream.yml
├── .gitignore
├── LICENSE.md
├── Project.toml
├── README.md
├── deps/
│ └── build.jl
├── docs/
│ ├── Project.toml
│ ├── make.jl
│ └── src/
│ ├── dev.md
│ └── index.md
├── examples/
│ ├── annulus.jl
│ ├── automaticdifferentiation.jl
│ ├── chebyshev.jl
│ ├── disk.jl
│ ├── halfrange.jl
│ ├── nonlocaldiffusion.jl
│ ├── padua.jl
│ ├── sphere.jl
│ ├── sphericalisometries.jl
│ ├── spinweighted.jl
│ ├── subspaceangles.jl
│ └── triangle.jl
├── src/
│ ├── FastTransforms.jl
│ ├── GramMatrix.jl
│ ├── PaduaTransform.jl
│ ├── ToeplitzPlusHankel.jl
│ ├── arrays.jl
│ ├── chebyshevtransform.jl
│ ├── clenshawcurtis.jl
│ ├── docstrings.jl
│ ├── elliptic.jl
│ ├── fejer.jl
│ ├── gaunt.jl
│ ├── hermite.jl
│ ├── inufft.jl
│ ├── libfasttransforms.jl
│ ├── nufft.jl
│ ├── specialfunctions.jl
│ ├── toeplitzhankel.jl
│ └── toeplitzplans.jl
└── test/
├── arraystests.jl
├── chebyshevtests.jl
├── gaunttests.jl
├── grammatrixtests.jl
├── hermitetests.jl
├── libfasttransformstests.jl
├── nuffttests.jl
├── paduatests.jl
├── quadraturetests.jl
├── runtests.jl
├── specialfunctionstests.jl
├── toeplitzhankeltests.jl
├── toeplitzplanstests.jl
└── toeplitzplushankeltests.jl
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/CIWindows.yml
================================================
name: CI Windows
on:
- push
- pull_request
jobs:
testwindows:
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
version:
- '1'
os:
- windows-latest
arch:
- x86
- x64
steps:
- uses: actions/checkout@v3
- uses: julia-actions/setup-julia@v1
with:
version: ${{ matrix.version }}
arch: ${{ matrix.arch }}
show-versioninfo: true
- uses: actions/cache@v3
env:
cache-name: cache-artifacts
with:
path: ~/.julia/artifacts
key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}
restore-keys: |
${{ runner.os }}-test-${{ env.cache-name }}-
${{ runner.os }}-test-
${{ runner.os }}-
- uses: julia-actions/julia-buildpkg@latest
- uses: julia-actions/julia-runtest@latest
- uses: julia-actions/julia-processcoverage@v1
- uses: codecov/codecov-action@v3
with:
file: lcov.info
================================================
FILE: .github/workflows/CompatHelper.yml
================================================
name: CompatHelper
on:
schedule:
- cron: 0 0 * * *
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
CompatHelper:
runs-on: ubuntu-latest
steps:
- name: Check if Julia is already available in the PATH
id: julia_in_path
run: which julia
continue-on-error: true
- name: Install Julia, but only if it is not already available in the PATH
uses: julia-actions/setup-julia@v1
with:
version: '1'
arch: ${{ runner.arch }}
if: steps.julia_in_path.outcome != 'success'
- name: "Add the General registry via Git"
run: |
import Pkg
ENV["JULIA_PKG_SERVER"] = ""
Pkg.Registry.add("General")
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 }}
================================================
FILE: .github/workflows/TagBot.yml
================================================
name: TagBot
on:
issue_comment:
types:
- created
workflow_dispatch:
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 }}
ssh: ${{ secrets.DOCUMENTER_KEY }}
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
- push
- pull_request
jobs:
test:
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
version:
- 'lts'
- '1'
os:
- ubuntu-latest
# - macOS-latest
arch:
- x86
- x64
exclude:
- os: macOS-latest
arch: x86
steps:
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v2
with:
version: ${{ matrix.version }}
arch: ${{ matrix.arch }}
show-versioninfo: true
- uses: actions/cache@v3
env:
cache-name: cache-artifacts
with:
path: ~/.julia/artifacts
key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}
restore-keys: |
${{ runner.os }}-test-${{ env.cache-name }}-
${{ runner.os }}-test-
${{ runner.os }}-
- uses: julia-actions/julia-buildpkg@latest
- uses: julia-actions/julia-runtest@latest
- uses: julia-actions/julia-processcoverage@v1
- uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: lcov.info
================================================
FILE: .github/workflows/docs.yml
================================================
name: Documentation
on:
- push
- pull_request
jobs:
docs:
name: Documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: julia-actions/setup-julia@v1
with:
version: '1'
- uses: julia-actions/julia-docdeploy@releases/v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}
================================================
FILE: .github/workflows/downstream.yml
================================================
name: IntegrationTest
on:
push:
branches: [master]
tags: [v*]
paths-ignore:
- 'LICENSE'
- 'README.md'
- '.github/workflows/TagBot.yml'
pull_request:
paths-ignore:
- 'LICENSE'
- 'README.md'
- '.github/workflows/TagBot.yml'
concurrency:
group: build-${{ github.event.pull_request.number || github.ref }}-${{ github.workflow }}
cancel-in-progress: true
jobs:
pre_job:
# continue-on-error: true # Uncomment once integration is finished
runs-on: ubuntu-latest
# Map a step output to a job output
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
test:
needs: pre_job
if: needs.pre_job.outputs.should_skip != 'true'
name: ${{ matrix.package.group }}/${{ matrix.package.repo }}/${{ matrix.julia-version }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
julia-version: ['1']
os: [ubuntu-latest]
package:
- {repo: ClassicalOrthogonalPolynomials.jl, group: JuliaApproximation}
- {repo: MultivariateOrthogonalPolynomials.jl, group: JuliaApproximation}
- {repo: ApproxFun.jl, group: JuliaApproximation}
steps:
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v2
with:
version: ${{ matrix.julia-version }}
arch: x64
- uses: julia-actions/julia-buildpkg@latest
- name: Clone Downstream
uses: actions/checkout@v4
with:
repository: ${{ matrix.package.group }}/${{ matrix.package.repo }}
path: downstream
- name: Load this and run the downstream tests
shell: julia --color=yes --project=downstream {0}
run: |
using Pkg
try
# force it to use this PR's version of the package
Pkg.develop(PackageSpec(path=".")) # resolver may fail with main deps
Pkg.update()
Pkg.test(; coverage = true) # resolver may fail with test time deps
catch err
err isa Pkg.Resolve.ResolverError || rethrow()
# If we can't resolve that means this is incompatible by SemVer and this is fine
# It means we marked this as a breaking change, so we don't need to worry about
# Mistakenly introducing a breaking change, as we have intentionally made one
@info "Not compatible with this release. No problem." exception=err
exit(0) # Exit immediately, as a success
end
- uses: julia-actions/julia-processcoverage@v1
- uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: lcov.info
================================================
FILE: .gitignore
================================================
docs/build/
docs/src/generated
deps/build.log
deps/libfasttransforms.*
.DS_Store
deps/FastTransforms/
Manifest.toml
================================================
FILE: LICENSE.md
================================================
The FastTransforms.jl package is licensed under the MIT "Expat" License:
> Copyright (c) 2016-2019: Richard Mikael Slevinsky and other contributors:
>
> https://github.com/JuliaApproximation/FastTransforms.jl/graphs/contributors
>
> 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.
================================================
FILE: Project.toml
================================================
name = "FastTransforms"
uuid = "057dd010-8810-581a-b7be-e3fc3b93f78c"
version = "0.17.1"
[deps]
AbstractFFTs = "621f4979-c628-5d54-868e-fcf4e3e8185c"
ArrayLayouts = "4c555306-a7a7-4459-81d9-ec55ddd5c99a"
BandedMatrices = "aae01518-5342-5314-be14-df237901396f"
FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341"
FastGaussQuadrature = "442a2c76-b920-505d-bb47-c5924d526838"
FastTransforms_jll = "34b6f7d7-08f9-5794-9e10-3819e4c7e49a"
FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b"
GenericFFT = "a8297547-1b15-4a5a-a998-a2ac5f1cef28"
LazyArrays = "5078a376-72f3-5289-bfd5-ec5146d43c02"
Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
RecurrenceRelationships = "807425ed-42ea-44d6-a357-6771516d7b2c"
SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"
ToeplitzMatrices = "c751599d-da0a-543b-9d20-d0a503d91d24"
[compat]
AbstractFFTs = "1.0"
ArrayLayouts = "1.10"
BandedMatrices = "1.5"
FFTW = "1.7"
FastGaussQuadrature = "0.4, 0.5, 1"
FastTransforms_jll = "0.6.2"
FillArrays = "0.9, 0.10, 0.11, 0.12, 0.13, 1"
GenericFFT = "0.1"
LazyArrays = "2.2"
RecurrenceRelationships = "0.2"
SpecialFunctions = "0.10, 1, 2"
ToeplitzMatrices = "0.7.1, 0.8"
julia = "1.7"
[extras]
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[targets]
test = ["Test", "Random"]
================================================
FILE: README.md
================================================
# FastTransforms.jl
[](https://github.com/JuliaApproximation/FastTransforms.jl/actions?query=workflow%3ACI) [](https://codecov.io/gh/JuliaApproximation/FastTransforms.jl) [](https://JuliaApproximation.github.io/FastTransforms.jl/stable) [](https://JuliaApproximation.github.io/FastTransforms.jl/dev)
[](https://juliaci.github.io/NanosoldierReports/pkgeval_badges/report.html)
`FastTransforms.jl` allows the user to conveniently work with orthogonal polynomials with degrees well into the millions.
This package provides a Julia wrapper for the [C library](https://github.com/MikaelSlevinsky/FastTransforms) of the same name. Additionally, all three types of nonuniform fast Fourier transforms are available, as well as the Padua transform.
## Installation
Installation, which uses [BinaryBuilder](https://github.com/JuliaPackaging/BinaryBuilder.jl) for all of Julia's supported platforms (in particular Sandybridge Intel processors and beyond), may be as straightforward as:
```julia
pkg> add FastTransforms
julia> using FastTransforms, LinearAlgebra
```
## Fast orthogonal polynomial transforms
The orthogonal polynomial transforms are listed in `FastTransforms.Transforms` or `FastTransforms.kind2string.(instances(FastTransforms.Transforms))`. Univariate transforms may be planned with the standard normalization or with orthonormalization. For multivariate transforms, the standard normalization may be too severe for floating-point computations, so it is omitted. Here are two examples:
### The Chebyshev--Legendre transform
```julia
julia> c = rand(8192);
julia> leg2cheb(c);
julia> cheb2leg(c);
julia> norm(cheb2leg(leg2cheb(c; normcheb=true); normcheb=true)-c)/norm(c)
1.1866591414786334e-14
```
The implementation separates pre-computation into an `FTPlan`. This type is constructed with either `plan_leg2cheb` or `plan_cheb2leg`. Let's see how much faster it is if we pre-compute.
```julia
julia> p1 = plan_leg2cheb(c);
julia> p2 = plan_cheb2leg(c);
julia> @time leg2cheb(c);
0.433938 seconds (9 allocations: 64.641 KiB)
julia> @time p1*c;
0.005713 seconds (8 allocations: 64.594 KiB)
julia> @time cheb2leg(c);
0.423865 seconds (9 allocations: 64.641 KiB)
julia> @time p2*c;
0.005829 seconds (8 allocations: 64.594 KiB)
```
Furthermore, for orthogonal polynomial connection problems that are degree-preserving, we should expect to be able to apply the transforms in-place:
```julia
julia> lmul!(p1, c);
julia> lmul!(p2, c);
julia> ldiv!(p1, c);
julia> ldiv!(p2, c);
```
### The spherical harmonic transform
Let `F` be an array of spherical harmonic expansion coefficients with columns arranged by increasing order in absolute value, alternating between negative and positive orders. Then `sph2fourier` converts the representation into a bivariate Fourier series, and `fourier2sph` converts it back. Once in a bivariate Fourier series on the sphere, `plan_sph_synthesis` converts the coefficients to function samples on an equiangular grid that does not sample the poles, and `plan_sph_analysis` converts them back.
```julia
julia> F = sphrandn(Float64, 1024, 2047); # convenience method
julia> P = plan_sph2fourier(F);
julia> PS = plan_sph_synthesis(F);
julia> PA = plan_sph_analysis(F);
julia> @time G = PS*(P*F);
0.090767 seconds (12 allocations: 31.985 MiB, 1.46% gc time)
julia> @time H = P\(PA*G);
0.092726 seconds (12 allocations: 31.985 MiB, 1.63% gc time)
julia> norm(F-H)/norm(F)
2.1541073345177038e-15
```
Due to the structure of the spherical harmonic connection problem, these transforms may also be performed in-place with `lmul!` and `ldiv!`.
See also [FastSphericalHarmonics.jl](https://github.com/eschnett/FastSphericalHarmonics.jl) for a simpler interface to the spherical harmonic transforms defined in this package.
## Nonuniform fast Fourier transforms
The NUFFTs are implemented thanks to [Alex Townsend](https://github.com/ajt60gaibb):
- `nufft1` assumes uniform samples and noninteger frequencies;
- `nufft2` assumes nonuniform samples and integer frequencies;
- `nufft3 ( = nufft)` assumes nonuniform samples and noninteger frequencies;
- `inufft1` inverts an `nufft1`; and,
- `inufft2` inverts an `nufft2`.
Here is an example:
```julia
julia> n = 10^4;
julia> c = complex(rand(n));
julia> ω = collect(0:n-1) + rand(n);
julia> nufft1(c, ω, eps());
julia> p1 = plan_nufft1(ω, eps());
julia> @time p1*c;
0.002383 seconds (6 allocations: 156.484 KiB)
julia> x = (collect(0:n-1) + 3rand(n))/n;
julia> nufft2(c, x, eps());
julia> p2 = plan_nufft2(x, eps());
julia> @time p2*c;
0.001478 seconds (6 allocations: 156.484 KiB)
julia> nufft3(c, x, ω, eps());
julia> p3 = plan_nufft3(x, ω, eps());
julia> @time p3*c;
0.058999 seconds (6 allocations: 156.484 KiB)
```
## The Padua Transform
The Padua transform and its inverse are implemented thanks to [Michael Clarke](https://github.com/MikeAClarke). These are optimized methods designed for computing the bivariate Chebyshev coefficients by interpolating a bivariate function at the Padua points on `[-1,1]^2`.
```julia
julia> n = 200;
julia> N = div((n+1)*(n+2), 2);
julia> v = rand(N); # The length of v is the number of Padua points
julia> @time norm(ipaduatransform(paduatransform(v)) - v)/norm(v)
0.007373 seconds (543 allocations: 1.733 MiB)
3.925164683252905e-16
```
# References
[1] D. Ruiz—Antolín and A. Townsend, [A nonuniform fast Fourier transform based on low rank approximation](https://doi.org/10.1137/17M1134822), *SIAM J. Sci. Comput.*, **40**:A529–A547, 2018.
[2] K. Gumerov, S. Rigg, and R. M. Slevinsky, [Fast measure modification of orthogonal polynomials via matrices with displacement structure](https://arxiv.org/abs/2412.17663), arXiv:2412.17663, 2024.
[3] T. S. Gutleb, S. Olver and R. M. Slevinsky, [Polynomial and rational measure modifications of orthogonal polynomials via infinite-dimensional banded matrix factorizations](https://arxiv.org/abs/2302.08448), arXiv:2302.08448, 2023.
[4] S. Olver, R. M. Slevinsky, and A. Townsend, [Fast algorithms using orthogonal polynomials](https://doi.org/10.1017/S0962492920000045), *Acta Numerica*, **29**:573—699, 2020.
[5] R. M. Slevinsky, [Fast and backward stable transforms between spherical harmonic expansions and bivariate Fourier series](https://doi.org/10.1016/j.acha.2017.11.001), *Appl. Comput. Harmon. Anal.*, **47**:585—606, 2019.
[6] R. M. Slevinsky, [Conquering the pre-computation in two-dimensional harmonic polynomial transforms](https://arxiv.org/abs/1711.07866), arXiv:1711.07866, 2017.
================================================
FILE: deps/build.jl
================================================
if get(ENV, "FT_BUILD_FROM_SOURCE", "false") == "true"
extension = Sys.isapple() ? "dylib" : Sys.islinux() ? "so" : Sys.iswindows() ? "dll" : ""
make = Sys.iswindows() ? "mingw32-make" : "make"
flags = Sys.isapple() ? "FT_USE_APPLEBLAS=1" : Sys.iswindows() ? "FT_FFTW_WITH_COMBINED_THREADS=1" : ""
script = """
set -e
set -x
if [ -d "FastTransforms" ]; then
cd FastTransforms
git fetch
git checkout master
git pull
cd ..
else
git clone https://github.com/MikaelSlevinsky/FastTransforms.git FastTransforms
fi
cd FastTransforms
$make assembly
$make lib $flags
cd ..
mv -f FastTransforms/libfasttransforms.$extension libfasttransforms.$extension
"""
try
run(`bash -c $(script)`)
catch
error(
"FastTransforms could not be properly installed.\n Please check that you have all dependencies installed. " *
"Sample installation of dependencies:\n" *
(Sys.isapple() ? "On MacOS\n\tbrew install libomp fftw mpfr\n" :
Sys.islinux() ? "On Linux\n\tsudo apt-get install libomp-dev libblas-dev libopenblas-base libfftw3-dev libmpfr-dev\n" :
Sys.iswindows() ? "On Windows\n\tvcpkg install openblas:x64-windows fftw3[core,threads]:x64-windows mpir:x64-windows mpfr:x64-windows\n" :
"On your platform, please consider opening a pull request to add support to build from source.\n")
)
end
println("FastTransforms built from source.")
else
println("FastTransforms using precompiled binaries.")
end
================================================
FILE: docs/Project.toml
================================================
[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
FastTransforms = "057dd010-8810-581a-b7be-e3fc3b93f78c"
LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
LazyArrays = "5078a376-72f3-5289-bfd5-ec5146d43c02"
Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306"
PlotlyJS = "f0f68f2c-4968-5e81-91da-67840de0976a"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
[compat]
Documenter = "~0.24"
Literate = "~2.8"
================================================
FILE: docs/make.jl
================================================
using Documenter, FastTransforms, Literate, Plots
plotlyjs()
const EXAMPLES_DIR = joinpath(@__DIR__, "..", "examples")
const OUTPUT_DIR = joinpath(@__DIR__, "src/generated")
examples = [
"annulus.jl",
"automaticdifferentiation.jl",
"chebyshev.jl",
"disk.jl",
"halfrange.jl",
"nonlocaldiffusion.jl",
"padua.jl",
"sphere.jl",
"spinweighted.jl",
"subspaceangles.jl",
"triangle.jl",
]
function uncomment_objects(str)
str = replace(str, "###```@raw" => "```\n\n```@raw")
str = replace(str, "###<object" => "<object")
str = replace(str, "###```\n```" => "```")
str
end
for example in examples
example_filepath = joinpath(EXAMPLES_DIR, example)
Literate.markdown(example_filepath, OUTPUT_DIR; execute=true, postprocess = uncomment_objects)
end
makedocs(
doctest = false,
format = Documenter.HTML(),
sitename = "FastTransforms.jl",
authors = "Richard Mikael Slevinsky",
pages = Any[
"Home" => "index.md",
"Development" => "dev.md",
"Examples" => [
"generated/annulus.md",
"generated/automaticdifferentiation.md",
"generated/chebyshev.md",
"generated/disk.md",
"generated/halfrange.md",
"generated/nonlocaldiffusion.md",
"generated/padua.md",
"generated/sphere.md",
"generated/spinweighted.md",
"generated/subspaceangles.md",
"generated/triangle.md",
],
]
)
deploydocs(
repo = "github.com/JuliaApproximation/FastTransforms.jl.git",
)
================================================
FILE: docs/src/dev.md
================================================
# Development Documentation
The core of [`FastTransforms.jl`](https://github.com/JuliaApproximation/FastTransforms.jl) is developed in parallel with the [C library](https://github.com/MikaelSlevinsky/FastTransforms) of the same name. Julia and C interoperability is enhanced by the [BinaryBuilder](https://github.com/JuliaPackaging/BinaryBuilder.jl) infrastructure, which provides the user a safe and seamless experience using a package in a different language.
## Why two packages?
Orthogonal polynomial transforms are performance-sensitive imperative tasks. Yet, many of Julia's rich and evolving language features are simply unnecessary for defining these computational routines. Moreover, rapid language changes in Julia (as compared to C) have been more than a perturbation to this repository in the past.
The C library generates assembly for vectorized operations such as single instruction multiple data (SIMD) that is more efficient than that generated by a compiler without human intervention. It also uses OpenMP to introduce shared memory parallelism for large tasks. Finally, calling into precompiled binaries reduces the Julia package's pre-compilation and dependencies, improving the user experience. Some of these capabilities also exist in Julia, but with C there is frankly more control over performance.
C libraries are easier to call from any other language, partly explaining why the Python package manager Spack [already supports the C library](https://spack.readthedocs.io/en/latest/package_list.html#fasttransforms) through third-party efforts.
In Julia, a parametric composite type with unrestricted type parameters is just about as big as `Any`. Such a type allows the Julia API to far exceed the C API in its ability to unify all of the orthogonal polynomial transforms and present them as linear operators. The `mutable struct FTPlan{T, N, K}`, together with `AdjointFTPlan` and `TransposeFTPlan`, are the core Julia types in this repository. Whereas `T` is understood to represent element type of the plan and `N` represents the number of leading dimensions of the array on which it operates, `K` is a mere enumeration which serves to distinguish the orthogonal polynomials at play. For example, `FTPlan{Float64, 1, LEG2CHEB}` represents the necessary pre-computations to convert 64-bit Legendre series to Chebyshev series (of the first kind). `N == 1` because Chebyshev and Legendre series are naturally represented with vectors of coefficients. However, this particular plan may operate not only on vectors but also on matrices, column-by-column.
## The developer's right to build from source
Precompiled binaries are important for users, but development in C may be greatly accelerated by coupling it with a dynamic language such as Julia. For this reason, the repository preserves the developer's right to build the C library from source by setting an environment variable to trigger the build script:
```julia
julia> ENV["FT_BUILD_FROM_SOURCE"] = "true"
"true"
(@v1.5) pkg> build FastTransforms
Building FFTW ──────────→ `~/.julia/packages/FFTW/ayqyZ/deps/build.log`
Building TimeZones ─────→ `~/.julia/packages/TimeZones/K98G0/deps/build.log`
Building FastTransforms → `~/.julia/dev/FastTransforms/deps/build.log`
julia> using FastTransforms
[ Info: Precompiling FastTransforms [057dd010-8810-581a-b7be-e3fc3b93f78c]
```
This lets the developer experiment with new features through `ccall`ing into bleeding edge source code. Customizing the build script further allows the developer to track a different branch or even a fork.
## From release to release to release
To get from a C library release to a Julia package release, the developer needs to update Yggdrasil's [build_tarballs.jl](https://github.com/JuliaPackaging/Yggdrasil/blob/master/F/FastTransforms/build_tarballs.jl) script for the new version and its 256-bit SHA. On macOS, the SHA can be found by:
```julia
shell> curl https://codeload.github.com/MikaelSlevinsky/FastTransforms/tar.gz/v0.6.2 --output FastTransforms.tar.gz
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 168k 0 168k 0 0 429k 0 --:--:-- --:--:-- --:--:-- 429k
shell> shasum -a 256 FastTransforms.tar.gz
fd00befcb0c20ba962a8744a7b9139355071ee95be70420de005b7c0f6e023aa FastTransforms.tar.gz
shell> rm -f FastTransforms.tar.gz
```
Using [SHA.jl](https://github.com/JuliaCrypto/SHA.jl), the SHA can also be found by:
```julia
shell> curl https://codeload.github.com/MikaelSlevinsky/FastTransforms/tar.gz/v0.6.2 --output FastTransforms.tar.gz
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 168k 0 168k 0 0 442k 0 --:--:-- --:--:-- --:--:-- 443k
julia> using SHA
julia> open("FastTransforms.tar.gz") do f
bytes2hex(sha256(f))
end
"fd00befcb0c20ba962a8744a7b9139355071ee95be70420de005b7c0f6e023aa"
shell> rm -f FastTransforms.tar.gz
```
Then we wait for the friendly folks at [JuliaPackaging](https://github.com/JuliaPackaging) to merge the pull request to Yggdrasil, triggering a new release of the [FastTransforms_jll.jl](https://github.com/JuliaBinaryWrappers/FastTransforms_jll.jl) meta package that stores all precompiled binaries. With this release, we update the FastTransforms.jl [Project.toml](https://github.com/JuliaApproximation/FastTransforms.jl/blob/master/Project.toml) to point to the latest release and register the new version.
Since development of Yggdrasil is quite rapid, a fork may easily become stale. Git permits the developer to forcibly make a master branch on a fork even with upstream master:
```
git fetch upstream
git checkout master
git reset --hard upstream/master
git push origin master --force
```
================================================
FILE: docs/src/index.md
================================================
# FastTransforms.jl Documentation
## Introduction
[`FastTransforms.jl`](https://github.com/JuliaApproximation/FastTransforms.jl) allows the user to conveniently work with orthogonal polynomials with degrees well into the millions.
This package provides a Julia wrapper for the [C library](https://github.com/MikaelSlevinsky/FastTransforms) of the same name. Additionally, all three types of nonuniform fast Fourier transforms available, as well as the Padua transform.
## Fast orthogonal polynomial transforms
For this documentation, please see the documentation for [FastTransforms](https://github.com/MikaelSlevinsky/FastTransforms). Most transforms have separate forward and inverse plans. In some instances, however, the inverse is in the sense of least-squares, and therefore only the forward transform is planned.
### Modified orthogonal polynomials via fast Cholesky factorization of the Gram matrix
```@docs
GramMatrix
ChebyshevGramMatrix
```
## Nonuniform fast Fourier transforms
```@docs
nufft1
nufft2
nufft3
inufft1
inufft2
paduatransform
ipaduatransform
```
## Other Exported Methods
```@docs
gaunt
paduapoints
sphevaluate
```
## Internal Methods
### Miscellaneous Special Functions
```@docs
FastTransforms.half
FastTransforms.two
FastTransforms.δ
FastTransforms.Λ
FastTransforms.lambertw
FastTransforms.pochhammer
FastTransforms.stirlingseries
```
### Modified Chebyshev Moment-Based Quadrature
```@docs
FastTransforms.clenshawcurtisnodes
FastTransforms.clenshawcurtisweights
FastTransforms.fejernodes1
FastTransforms.fejerweights1
FastTransforms.fejernodes2
FastTransforms.fejerweights2
FastTransforms.chebyshevmoments1
FastTransforms.chebyshevjacobimoments1
FastTransforms.chebyshevlogmoments1
FastTransforms.chebyshevmoments2
FastTransforms.chebyshevjacobimoments2
FastTransforms.chebyshevlogmoments2
```
### Elliptic Submodule
```@docs
FastTransforms.Elliptic
```
================================================
FILE: examples/annulus.jl
================================================
# # Integration on an annulus
# In this example, we explore integration of the function:
# ```math
# f(x,y) = \frac{x^3}{x^2+y^2-\frac{1}{4}},
# ```
# over the annulus defined by $\{(r,\theta) : \rho < r < 1, 0 < \theta < 2\pi\}$
# with parameter $\rho = \frac{2}{3}$. We will calculate the integral:
# ```math
# \int_0^{2\pi}\int_{\frac{2}{3}}^1 f(r\cos\theta,r\sin\theta)^2r{\rm\,d}r{\rm\,d}\theta,
# ```
# by analyzing the function in an annulus polynomial series.
# We analyze the function on an $N\times M$ tensor product grid defined by:
# ```math
# \begin{aligned}
# r_n & = \sqrt{\cos^2\left[(n+\tfrac{1}{2})\pi/2N\right] + \rho^2 \sin^2\left[(n+\tfrac{1}{2})\pi/2N\right]},\quad{\rm for}\quad 0\le n < N,\quad{\rm and}\\
# \theta_m & = 2\pi m/M,\quad{\rm for}\quad 0\le m < M;
# \end{aligned}
# ```
# we convert the function samples to Chebyshev×Fourier coefficients using
# `plan_annulus_analysis`; and finally, we transform the Chebyshev×Fourier
# coefficients to annulus polynomial coefficients using `plan_ann2cxf`.
#
# For the storage pattern of the arrays, please consult the
# [documentation](https://MikaelSlevinsky.github.io/FastTransforms).
using FastTransforms, LinearAlgebra, Plots
const GENFIGS = joinpath(pkgdir(FastTransforms), "docs/src/generated")
!isdir(GENFIGS) && mkdir(GENFIGS)
plotlyjs()
# Our function $f$ on the annulus:
f = (x,y) -> x^3/(x^2+y^2-1/4)
# The annulus polynomial degree:
N = 8
M = 4N-3
# The annulus inner radius:
ρ = 2/3
# The radial grid:
r = [begin t = (N-n-0.5)/(2N); ct = sinpi(t); st = cospi(t); sqrt(ct^2+ρ^2*st^2) end for n in 0:N-1]
# The angular grid (mod $\pi$):
θ = (0:M-1)*2/M
# On the mapped tensor product grid, our function samples are:
F = [f(r*cospi(θ), r*sinpi(θ)) for r in r, θ in θ]
# We superpose a surface plot of $f$ on top of the grid:
X = [r*cospi(θ) for r in r, θ in θ]
Y = [r*sinpi(θ) for r in r, θ in θ]
scatter3d(vec(X), vec(Y), vec(0F); markersize=0.75, markercolor=:red)
surface!(X, Y, F; legend=false, xlabel="x", ylabel="y", zlabel="f")
savefig(joinpath(GENFIGS, "annulus.html"))
###```@raw html
###<object type="text/html" data="../annulus.html" style="width:100%;height:400px;"></object>
###```
# We precompute an Annulus--Chebyshev×Fourier plan:
α, β, γ = 0, 0, 0
P = plan_ann2cxf(F, α, β, γ, ρ)
# And an FFTW Chebyshev×Fourier analysis plan on the annulus:
PA = plan_annulus_analysis(F, ρ)
# Its annulus coefficients are:
U = P\(PA*F)
# The annulus coefficients are useful for integration.
# The integral of $[f(x,y)]^2$ over the annulus is
# approximately the square of the 2-norm of the coefficients:
norm(U)^2, 5π/8*(1675/4536+9*log(3)/32-3*log(7)/32)
================================================
FILE: examples/automaticdifferentiation.jl
================================================
# # Automatic differentiation through spherical harmonic transforms
# This example finds a positive value of $\lambda$ in:
# ```math
# f(r) = \sin[\lambda (k\cdot r)],
# ```
# for some $k,r\in\mathbb{S}^2$ such that $\int_{\mathbb{S}^2} f^2 {\rm\,d}\Omega = 1$.
# We do this by using derivative information through:
# ```math
# \dfrac{\partial f}{\partial \lambda} = (k\cdot r) \cos[\lambda (k\cdot r)].
# ```
using FastTransforms, LinearAlgebra
# The colatitudinal grid (mod $\pi$):
N = 15
θ = (0.5:N-0.5)/N
# The longitudinal grid (mod $\pi$):
M = 2*N-1
φ = (0:M-1)*2/M
# We precompute a spherical harmonic--Fourier plan:
P = plan_sph2fourier(Float64, N)
# And an FFTW Fourier analysis plan on $\mathbb{S}^2$:
PA = plan_sph_analysis(Float64, N, M)
# Our choice of $k$ and angular parametrization of $r$:
k = [2/7, 3/7, 6/7]
r = (θ,φ) -> [sinpi(θ)*cospi(φ), sinpi(θ)*sinpi(φ), cospi(θ)]
# Our initial guess for $\lambda$:
λ = 1.0
# Then we run Newton iteration and grab an espresso:
for _ in 1:7
F = [sin(λ*(k⋅r(θ,φ))) for θ in θ, φ in φ]
Fλ = [(k⋅r(θ,φ))*cos(λ*(k⋅r(θ,φ))) for θ in θ, φ in φ]
U = P\(PA*F)
Uλ = P\(PA*Fλ)
global λ = λ - (norm(U)^2-1)/(2*sum(U.*Uλ))
println("λ: $(rpad(λ, 18)) and the 2-norm: $(rpad(norm(U), 18))")
end
================================================
FILE: examples/chebyshev.jl
================================================
# # Chebyshev transform
# This demonstrates the Chebyshev transform and inverse transform,
# explaining precisely the normalization and points
using FastTransforms
n = 20
# First kind points $\to$ first kind polynomials
p_1 = chebyshevpoints(Float64, n, Val(1))
f = exp.(p_1)
f̌ = chebyshevtransform(f, Val(1))
f̃ = x -> [cos(k*acos(x)) for k=0:n-1]' * f̌
f̃(0.1) ≈ exp(0.1)
# First kind polynomials $\to$ first kind points
ichebyshevtransform(f̌, Val(1)) ≈ exp.(p_1)
# Second kind points $\to$ first kind polynomials
p_2 = chebyshevpoints(Float64, n, Val(2))
f = exp.(p_2)
f̌ = chebyshevtransform(f, Val(2))
f̃ = x -> [cos(k*acos(x)) for k=0:n-1]' * f̌
f̃(0.1) ≈ exp(0.1)
# First kind polynomials $\to$ second kind points
ichebyshevtransform(f̌, Val(2)) ≈ exp.(p_2)
# First kind points $\to$ second kind polynomials
p_1 = chebyshevpoints(Float64, n, Val(1))
f = exp.(p_1)
f̌ = chebyshevutransform(f, Val(1))
f̃ = x -> [sin((k+1)*acos(x))/sin(acos(x)) for k=0:n-1]' * f̌
f̃(0.1) ≈ exp(0.1)
# Second kind polynomials $\to$ first kind points
ichebyshevutransform(f̌, Val(1)) ≈ exp.(p_1)
# Second kind points $\to$ second kind polynomials
p_2 = chebyshevpoints(Float64, n, Val(2))[2:n-1]
f = exp.(p_2)
f̌ = chebyshevutransform(f, Val(2))
f̃ = x -> [sin((k+1)*acos(x))/sin(acos(x)) for k=0:n-3]' * f̌
f̃(0.1) ≈ exp(0.1)
# Second kind polynomials $\to$ second kind points
ichebyshevutransform(f̌, Val(2)) ≈ exp.(p_2)
================================================
FILE: examples/disk.jl
================================================
# # Holomorphic integration on the unit disk
# In this example, we explore integration of a harmonic function:
# ```math
# f(x,y) = \frac{x^2-y^2+1}{(x^2-y^2+1)^2+(2xy+1)^2},
# ```
# over the unit disk. In this case, we know from complex analysis that the
# integral of a holomorphic function is equal to $\pi \times f(0,0)$.
# We analyze the function on an $N\times M$ tensor product grid defined by:
# ```math
# \begin{aligned}
# r_n & = \cos\left[(n+\tfrac{1}{2})\pi/2N\right],\quad{\rm for}\quad 0\le n < N,\quad{\rm and}\\
# \theta_m & = 2\pi m/M,\quad{\rm for}\quad 0\le m < M;
# \end{aligned}
# ```
# we convert the function samples to Chebyshev×Fourier coefficients using
# `plan_disk_analysis`; and finally, we transform the Chebyshev×Fourier
# coefficients to Zernike polynomial coefficients using `plan_disk2cxf`.
#
# For the storage pattern of the arrays, please consult the
# [documentation](https://MikaelSlevinsky.github.io/FastTransforms).
using FastTransforms, LinearAlgebra, Plots
const GENFIGS = joinpath(pkgdir(FastTransforms), "docs/src/generated")
!isdir(GENFIGS) && mkdir(GENFIGS)
plotlyjs()
# Our function $f$ on the disk:
f = (x,y) -> (x^2-y^2+1)/((x^2-y^2+1)^2+(2x*y+1)^2)
# The Zernike polynomial degree:
N = 15
M = 4N-3
# The radial grid:
r = [sinpi((N-n-0.5)/(2N)) for n in 0:N-1]
# The angular grid (mod $\pi$):
θ = (0:M-1)*2/M
# On the mapped tensor product grid, our function samples are:
F = [f(r*cospi(θ), r*sinpi(θ)) for r in r, θ in θ]
# We superpose a surface plot of $f$ on top of the grid:
X = [r*cospi(θ) for r in r, θ in θ]
Y = [r*sinpi(θ) for r in r, θ in θ]
scatter3d(vec(X), vec(Y), vec(0F); markersize=0.75, markercolor=:red)
surface!(X, Y, F; legend=false, xlabel="x", ylabel="y", zlabel="f")
savefig(joinpath(GENFIGS, "zernike.html"))
###```@raw html
###<object type="text/html" data="../zernike.html" style="width:100%;height:400px;"></object>
###```
# We precompute a (generalized) Zernike--Chebyshev×Fourier plan:
α, β = 0, 0
P = plan_disk2cxf(F, α, β)
# And an FFTW Chebyshev×Fourier analysis plan on the disk:
PA = plan_disk_analysis(F)
# Its Zernike coefficients are:
U = P\(PA*F)
# The Zernike coefficients are useful for integration. The integral of $f(x,y)$
# over the disk should be $\pi/2$ by harmonicity. The coefficient of $Z_{0,0}$
# multiplied by `√π` is:
U[1, 1]*sqrt(π)
# Using an orthonormal basis, the integral of $[f(x,y)]^2$ over the disk is
# approximately the square of the 2-norm of the coefficients:
norm(U)^2, π/(2*sqrt(2))*log1p(sqrt(2))
# But there's more! Next, we repeat the experiment using the Dunkl-Xu
# orthonormal polynomials supported on the rectangularized disk.
N = 2N
M = N
# We analyze the function on an $N\times M$ mapped tensor product $xy$-grid defined by:
# ```math
# \begin{aligned}
# x_n & = \cos\left(\frac{2n+1}{2N}\pi\right) = \sin\left(\frac{N-2n-1}{2N}\pi\right),\quad {\rm for} \quad 0 \le n < N,\quad{\rm and}\\
# z_m & = \cos\left(\frac{2m+1}{2M}\pi\right) = \sin\left(\frac{M-2m-1}{2M}\pi\right),\quad {\rm for} \quad 0 \le m < M,\\
# y_{n,m} & = \sqrt{1-x_n^2}z_m.
# \end{aligned}
# ```
# Slightly more accuracy can be expected by using an auxiliary array:
# ```math
# w_n = \sin\left(\frac{2n+1}{2N}\pi\right),\quad {\rm for} \quad 0 \le n < N,
# ```
# so that $y_{n,m} = w_nz_m$.
#
# The x grid
w = [sinpi((n+0.5)/N) for n in 0:N-1]
x = [sinpi((N-2n-1)/(2N)) for n in 0:N-1]
# The z grid
z = [sinpi((M-2m-1)/(2M)) for m in 0:M-1]
# On the mapped tensor product grid, our function samples are:
F = [f(x[n], w[n]*z) for n in 1:N, z in z]
# We superpose a surface plot of $f$ on top of the grid:
X = [x for x in x, z in z]
Y = [w*z for w in w, z in z]
scatter3d(vec(X), vec(Y), vec(0F); markersize=0.75, markercolor=:green)
surface!(X, Y, F; legend=false, xlabel="x", ylabel="y", zlabel="f")
savefig(joinpath(GENFIGS, "dunklxu.html"))
###```@raw html
###<object type="text/html" data="../dunklxu.html" style="width:100%;height:400px;"></object>
###```
# We precompute a Dunkl-Xu--Chebyshev plan:
P = plan_rectdisk2cheb(F, β)
# And an FFTW Chebyshev² analysis plan on the rectangularized disk:
PA = plan_rectdisk_analysis(F)
# Its Dunkl-Xu coefficients are:
U = P\(PA*F)
# The Dunkl-Xu coefficients are useful for integration. The integral of $f(x,y)$
# over the disk should be $\pi/2$ by harmonicity. The coefficient of $P_{0,0}$
# multiplied by `√π` is:
U[1, 1]*sqrt(π)
# Using an orthonormal basis, the integral of $[f(x,y)]^2$ over the disk is
# approximately the square of the 2-norm of the coefficients:
norm(U)^2, π/(2*sqrt(2))*log1p(sqrt(2))
================================================
FILE: examples/halfrange.jl
================================================
# # Half-range Chebyshev polynomials
# In [this paper](https://doi.org/10.1137/090752456), [Daan Huybrechs](https://github.com/daanhb) introduced the so-called half-range Chebyshev polynomials
# as the semi-classical orthogonal polynomials with respect to the inner product:
# ```math
# \langle f, g \rangle = \int_0^1 f(x) g(x)\frac{{\rm d} x}{\sqrt{1-x^2}}.
# ```
# By the variable transformation $y = 2x-1$, the resulting polynomials can be related to
# orthogonal polynomials on $(-1,1)$ with the Jacobi weight $(1-y)^{-\frac{1}{2}}$ modified by the weight $(3+y)^{-\frac{1}{2}}$.
#
# We shall use the fact that:
# ```math
# \frac{1}{\sqrt{3+y}} = \sqrt{\frac{2}{3+\sqrt{8}}}\sum_{n=0}^\infty P_n(y) \left(\frac{-1}{3+\sqrt{8}}\right)^n,
# ```
# and results from [this paper](https://arxiv.org/abs/2302.08448) to consider the half-range Chebyshev polynomials as
# modifications of the Jacobi polynomials $P_n^{(-\frac{1}{2},0)}(y)$.
using FastTransforms, LinearAlgebra, Plots, LaTeXStrings
const GENFIGS = joinpath(pkgdir(FastTransforms), "docs/src/generated")
!isdir(GENFIGS) && mkdir(GENFIGS)
plotlyjs()
# We truncate the generating function to ensure a relative error less than `eps()` in the uniform norm on $(-1,1)$:
z = -1/(3+sqrt(8))
K = sqrt(-2z)
N = ceil(Int, log(abs(z), eps()/2*(1-abs(z))/K) - 1)
d = K .* z .^(0:N)
# Then, we convert this representation to the expansion in Jacobi polynomials $P_n^{(-\frac{1}{2}, 0)}(y)$:
u = jac2jac(d, 0.0, 0.0, -0.5, 0.0; norm1 = false, norm2 = true)
# Our working polynomial degree will be:
n = 100
# We compute the connection coefficients between the modified orthogonal polynomials and the Jacobi polynomials:
P = plan_modifiedjac2jac(Float64, n+1, -0.5, 0.0, u)
# We store the connection to first kind Chebyshev polynomials:
P1 = plan_jac2cheb(Float64, n+1, -0.5, 0.0; normjac = true)
# We compute the Chebyshev series for the degree-$k\le n$ modified polynomial and its values at the Chebyshev points:
q = k -> lmul!(P1, lmul!(P, [zeros(k); 1.0; zeros(n-k)]))
qvals = k-> ichebyshevtransform(q(k))
# With the symmetric Jacobi matrix for $P_n^{(-\frac{1}{2}, 0)}(y)$ and the modified plan, we may compute the modified Jacobi matrix and the corresponding roots (as eigenvalues):
XP = SymTridiagonal([-inv((4n-1)*(4n-5)) for n in 1:n+1], [4n*(2n-1)/(4n-1)/sqrt((4n-3)*(4n+1)) for n in 1:n])
XQ = FastTransforms.modified_jacobi_matrix(P, XP)
SymTridiagonal(XQ.dv[1:10], XQ.ev[1:9])
# And we plot:
x = (chebyshevpoints(Float64, n+1, Val(1)) .+ 1 ) ./ 2
p = plot(x, qvals(0); linewidth=2.0, legend = false, xlim=(0,1), xlabel=L"x",
ylabel=L"T^h_n(x)", title="Half-Range Chebyshev Polynomials and Their Roots",
extra_plot_kwargs = KW(:include_mathjax => "cdn"))
for k in 1:10
λ = (eigvals(SymTridiagonal(XQ.dv[1:k], XQ.ev[1:k-1])) .+ 1) ./ 2
plot!(x, qvals(k); linewidth=2.0, color=palette(:default)[k+1])
scatter!(λ, zero(λ); markersize=2.5, color=palette(:default)[k+1])
end
p
savefig(joinpath(GENFIGS, "halfrange.html"))
###```@raw html
###<object type="text/html" data="../halfrange.html" style="width:100%;height:400px;"></object>
###```
# By [Theorem 2.20](https://arxiv.org/abs/2302.08448) it turns out that the *derivatives* of the half-range Chebyshev polynomials are a linear combination of at most two polynomials orthogonal with respect to $\sqrt{(3+y)(1-y)}(1+y)$ on $(-1,1)$. This fact enables us to compute the banded differentiation matrix:
v̂ = 3*[u; 0]+XP[1:N+2, 1:N+1]*u
v = jac2jac(v̂, -0.5, 0.0, 0.5, 1.0; norm1 = true, norm2 = true)
function threshold!(A::AbstractArray, ϵ)
for i in eachindex(A)
if abs(A[i]) < ϵ A[i] = 0 end
end
A
end
P′ = plan_modifiedjac2jac(Float64, n+1, 0.5, 1.0, v)
DP = UpperTriangular(diagm(1=>[sqrt(n*(n+1/2)) for n in 1:n])) # The classical differentiation matrix representing 𝒟 P^{(-1/2,0)}(y) = P^{(1/2,1)}(y) D_P.
DQ = UpperTriangular(threshold!(P′\(DP*(P*I)), 100eps())) # The semi-classical differentiation matrix representing 𝒟 Q(y) = Q̂(y) D_Q.
UpperTriangular(DQ[1:10,1:10])
================================================
FILE: examples/nonlocaldiffusion.jl
================================================
# # Nonlocal diffusion on $\mathbb{S}^2$
# This example calculates the spectrum of the nonlocal diffusion operator:
# ```math
# \mathcal{L}_\delta u = \int_{\mathbb{S}^2} \rho_\delta(|\mathbf{x}-\mathbf{y}|)\left[u(\mathbf{x}) - u(\mathbf{y})\right] \,\mathrm{d}\Omega(\mathbf{y}),
# ```
# defined in Eq. (2) of
#
# R. M. Slevinsky, H. Montanelli, and Q. Du, [A spectral method for nonlocal diffusion operators on the sphere](https://doi.org/10.1016/j.jcp.2018.06.024), *J. Comp. Phys.*, **372**:893--911, 2018.
#
# In the above, $0<\delta<2$, $-1<\alpha<1$, and the kernel:
# ```math
# \rho_\delta(|\mathbf{x}-\mathbf{y}|) = \frac{4(1+\alpha)}{\pi \delta^{2+2\alpha}} \frac{\chi_{[0,\delta]}(|\mathbf{x}-\mathbf{y}|)}{|\mathbf{x}-\mathbf{y}|^{2-2\alpha}},
# ```
# where $\chi_I(\cdot)$ is the indicator function on the set $I$.
#
# This nonlocal operator is diagonalized by spherical harmonics:
# ```math
# \mathcal{L}_\delta Y_\ell^m(\mathbf{x}) = \lambda_\ell(\alpha, \delta) Y_\ell^m(\mathbf{x}),
# ```
# and its eigenfunctions are given by the generalized Funk--Hecke formula:
# ```math
# \lambda_\ell(\alpha, \delta) = \frac{(1+\alpha) 2^{2+\alpha}}{\delta^{2+2\alpha}}\int_{1-\delta^2/2}^1 \left[P_\ell(t)-1\right] (1-t)^{\alpha-1} \,\mathrm{d} t.
# ```
# In the paper, the authors use Clenshaw--Curtis quadrature and asymptotic evaluation of Legendre polynomials to achieve $\mathcal{O}(n^2\log n)$ complexity for the evaluation of the first $n$ eigenvalues. With a change of basis, this complexity can be reduced to $\mathcal{O}(n\log n)$.
#
# First, we represent:
# ```math
# P_n(t) - 1 = \sum_{j=0}^{n-1} \left[P_{j+1}(t) - P_j(t)\right] = -\sum_{j=0}^{n-1} (1-t) P_j^{(1,0)}(t).
# ```
# Then, we represent $P_j^{(1,0)}(t)$ with Jacobi polynomials $P_i^{(\alpha,0)}(t)$ and we integrate using [DLMF 18.9.16](https://dlmf.nist.gov/18.9.16):
# ```math
# \int_x^1 P_i^{(\alpha,0)}(t)(1-t)^\alpha\,\mathrm{d}t = \left\{ \begin{array}{cc} \frac{(1-x)^{\alpha+1}}{\alpha+1} & \mathrm{for~}i=0,\\ \frac{1}{2i}(1-x)^{\alpha+1}(1+x)P_{i-1}^{(\alpha+1,1)}(x), & \mathrm{for~}i>0.\end{array}\right.
# ```
# The code below implements this algorithm, making use of the Jacobi--Jacobi transform `plan_jac2jac`.
# For numerical stability, the conversion from Jacobi polynomials $P_j^{(1,0)}(t)$ to $P_i^{(\alpha,0)}(t)$ is divided into conversion from $P_j^{(1,0)}(t)$ to $P_k^{(0,0)}(t)$, before conversion from $P_k^{(0,0)}(t)$ to $P_i^{(\alpha,0)}(t)$.
using FastTransforms, LinearAlgebra
function oprec!(n::Integer, v::AbstractVector, alpha::Real, delta2::Real)
if n > 0
v[1] = 1
end
if n > 1
v[2] = (4*alpha+8-(alpha+4)*delta2)/4
end
for i = 1:n-2
v[i+2] = (((2*i+alpha+2)*(2*i+alpha+4)+alpha*(alpha+2))/(2*(i+1)*(2*i+alpha+2))*(2*i+alpha+3)/(i+alpha+3) - delta2/4*(2*i+alpha+3)/(i+1)*(2*i+alpha+4)/(i+alpha+3))*v[i+1] - (i+alpha+1)/(i+alpha+3)*(2*i+alpha+4)/(2*i+alpha+2)*v[i]
end
return v
end
function evaluate_lambda(n::Integer, alpha::T, delta::T) where T
delta2 = delta*delta
scl = (1+alpha)*(2-delta2/2)
lambda = Vector{T}(undef, n)
if n > 0
lambda[1] = 0
end
if n > 1
lambda[2] = -2
end
oprec!(n-2, view(lambda, 3:n), alpha, delta2)
for i = 2:n-1
lambda[i+1] *= -scl/(i-1)
end
p = plan_jac2jac(T, n-1, zero(T), zero(T), alpha, zero(T))
lmul!(p', view(lambda, 2:n))
for i = 2:n-1
lambda[i+1] = ((2i-1)*lambda[i+1] + (i-1)*lambda[i])/i
end
for i = 2:n-1
lambda[i+1] += lambda[i]
end
return lambda
end
# The spectrum in `Float64`:
lambda = evaluate_lambda(10, -0.5, 1.0)
# The spectrum in `BigFloat`:
lambdabf = evaluate_lambda(10, parse(BigFloat, "-0.5"), parse(BigFloat, "1.0"))
# The $\infty$-norm relative error:
norm(lambda-lambdabf, Inf)/norm(lambda, Inf)
================================================
FILE: examples/padua.jl
================================================
# # Padua transform
# This demonstrates the Padua transform and inverse transform,
# explaining precisely the normalization and points
using FastTransforms
# We define the Padua points and extract Cartesian components:
N = 15
pts = paduapoints(N)
x = pts[:,1]
y = pts[:,2];
# We take the Padua transform of the function:
f = (x,y) -> exp(x + cos(y))
f̌ = paduatransform(f.(x , y));
# and use the coefficients to create an approximation to the function $f$:
f̃ = (x,y) -> begin
j = 1
ret = 0.0
for n in 0:N, k in 0:n
ret += f̌[j]*cos((n-k)*acos(x)) * cos(k*acos(y))
j += 1
end
ret
end
# At a particular point, is the function well-approximated?
f̃(0.1,0.2) ≈ f(0.1,0.2)
# Does the inverse transform bring us back to the grid?
ipaduatransform(f̌) ≈ f̃.(x,y)
================================================
FILE: examples/sphere.jl
================================================
# # Spherical harmonic addition theorem
# This example confirms numerically that
# ```math
# f(z) = \frac{P_n(z\cdot y) - P_n(x\cdot y)}{z\cdot y - x\cdot y},
# ```
# is actually a degree-$(n-1)$ polynomial on $\mathbb{S}^2$, where $P_n$ is the degree-$n$
# Legendre polynomial, and $x,y,z \in \mathbb{S}^2$.
# To verify, we sample the function on a $N\times M$ equiangular grid
# defined by:
# ```math
# \begin{aligned}
# \theta_n & = (n+\tfrac{1}{2})\pi/N,\quad{\rm for}\quad 0\le n < N,\quad{\rm and}\\
# \varphi_m & = 2\pi m/M,\quad{\rm for}\quad 0\le m < M;
# \end{aligned}
# ```
# we convert the function samples to Fourier coefficients using
# `plan_sph_analysis`; and finally, we transform
# the Fourier coefficients to spherical harmonic coefficients using
# `plan_sph2fourier`.
#
# In the basis of spherical harmonics, it is plain to see the
# addition theorem in action, since $P_n(x\cdot y)$ should only consist of
# exact-degree-$n$ harmonics.
#
# For the storage pattern of the arrays, please consult the
# [documentation](https://MikaelSlevinsky.github.io/FastTransforms).
function threshold!(A::AbstractArray, ϵ)
for i in eachindex(A)
if abs(A[i]) < ϵ A[i] = 0 end
end
A
end
using FastTransforms, LinearAlgebra, Plots
const GENFIGS = joinpath(pkgdir(FastTransforms), "docs/src/generated")
!isdir(GENFIGS) && mkdir(GENFIGS)
plotlyjs()
# The colatitudinal grid (mod $\pi$):
N = 15
θ = (0.5:N-0.5)/N
# The longitudinal grid (mod $\pi$):
M = 2*N-1
φ = (0:M-1)*2/M
# Arbitrarily, we place $x$ at the North pole:
x = [0,0,1]
# Another vector is completely free:
y = normalize([.123,.456,.789])
# Thus $z \in \mathbb{S}^2$ is our variable vector, parameterized in spherical coordinates:
z = (θ,φ) -> [sinpi(θ)*cospi(φ), sinpi(θ)*sinpi(φ), cospi(θ)]
# On the tensor product grid, the Legendre polynomial $P_n(z\cdot y)$ is:
A = [(2k+1)/(k+1) for k in 0:N-1]
B = zeros(N)
C = [k/(k+1) for k in 0:N]
c = zeros(N); c[N] = 1
pts = vec([z(θ, φ)⋅y for θ in θ, φ in φ])
phi0 = ones(N*M)
F = reshape(FastTransforms.clenshaw!(zeros(N*M), c, A, B, C, pts, phi0), N, M)
# We superpose a surface plot of $f$ on top of the grid:
X = [sinpi(θ)*cospi(φ) for θ in θ, φ in φ]
Y = [sinpi(θ)*sinpi(φ) for θ in θ, φ in φ]
Z = [cospi(θ) for θ in θ, φ in φ]
scatter3d(vec(X), vec(Y), vec(Z); markersize=1.25, markercolor=:violetred)
surface!(X, Y, Z; surfacecolor=F, legend=false, xlabel="x", ylabel="y", zlabel="f")
savefig(joinpath(GENFIGS, "sphere1.html"))
###```@raw html
###<object type="text/html" data="../sphere1.html" style="width:100%;height:400px;"></object>
###```
# We show the cut in the surface to help illustrate the definition of the grid.
# In particular, we do not sample the poles.
#
# We precompute a spherical harmonic--Fourier plan:
P = plan_sph2fourier(F)
# And an FFTW Fourier analysis plan on $\mathbb{S}^2$:
PA = plan_sph_analysis(F)
# Its spherical harmonic coefficients demonstrate that it is exact-degree-$n$:
V = PA*F
U = threshold!(P\V, 400*eps())
# The $L^2(\mathbb{S}^2)$ norm of the function is:
nrm1 = norm(U)
# Similarly, on the tensor product grid, our function samples are:
Pnxy = FastTransforms.clenshaw!([0.0], c, A, B, C, [x⋅y], [1.0])[1]
F = [(F[n, m] - Pnxy)/(z(θ[n], φ[m])⋅y - x⋅y) for n in 1:N, m in 1:M]
# We superpose a surface plot of $f$ on top of the grid:
scatter3d(vec(X), vec(Y), vec(Z); markersize=1.25, markercolor=:violetred)
surface!(X, Y, Z; surfacecolor=F, legend=false, xlabel="x", ylabel="y", zlabel="f")
savefig(joinpath(GENFIGS, "sphere2.html"))
###```@raw html
###<object type="text/html" data="../sphere2.html" style="width:100%;height:400px;"></object>
###```
# Its spherical harmonic coefficients demonstrate that it is degree-$(n-1)$:
V = PA*F
U = threshold!(P\V, 400*eps())
# Finally, the Legendre polynomial $P_n(z\cdot x)$ is aligned with the grid:
pts = vec([z(θ, φ)⋅x for θ in θ, φ in φ])
F = reshape(FastTransforms.clenshaw!(zeros(N*M), c, A, B, C, pts, phi0), N, M)
# We superpose a surface plot of $f$ on top of the grid:
scatter3d(vec(X), vec(Y), vec(Z); markersize=1.25, markercolor=:violetred)
surface!(X, Y, Z; surfacecolor=F, legend=false, xlabel="x", ylabel="y", zlabel="f")
savefig(joinpath(GENFIGS, "sphere3.html"))
###```@raw html
###<object type="text/html" data="../sphere3.html" style="width:100%;height:400px;"></object>
###```
# It only has one nonnegligible spherical harmonic coefficient.
# Can you spot it?
V = PA*F
U = threshold!(P\V, 400*eps())
# That nonnegligible coefficient should be
ret = eval("√(2π/($(N-1)+1/2))")
# which is approximately
eval(Meta.parse(ret))
# since the convention in this library is to orthonormalize.
nrm2 = norm(U)
# Note that the integrals of both functions $P_n(z\cdot y)$ and $P_n(z\cdot x)$ and their
# $L^2(\mathbb{S}^2)$ norms are the same because of rotational invariance. The integral of
# either is perhaps not interesting as it is mathematically zero, but the norms
# of either should be approximately the same.
nrm1 ≈ nrm2
================================================
FILE: examples/sphericalisometries.jl
================================================
function threshold!(A::AbstractArray, ϵ)
for i in eachindex(A)
if abs(A[i]) < ϵ A[i] = 0 end
end
A
end
using FastTransforms, LinearAlgebra, Random, Test
# The colatitudinal grid (mod π):
N = 10
θ = (0.5:N-0.5)/N
# The longitudinal grid (mod π):
M = 2*N-1
φ = (0:M-1)*2/M
x = [cospi(φ)*sinpi(θ) for θ in θ, φ in φ]
y = [sinpi(φ)*sinpi(θ) for θ in θ, φ in φ]
z = [cospi(θ) for θ in θ, φ in φ]
P = plan_sph2fourier(Float64, N)
PA = plan_sph_analysis(Float64, N, M)
J = FastTransforms.plan_sph_isometry(Float64, N)
f = (x, y, z) -> x^2+y^4+x^2*y*z^3-x*y*z^2
F = f.(x, y, z)
V = PA*F
U = threshold!(P\V, 100eps())
FastTransforms.execute_sph_yz_axis_exchange!(J, U)
FR = f.(x, -z, -y)
VR = PA*FR
UR = threshold!(P\VR, 100eps())
@test U ≈ UR
norm(U-UR)
α, β, γ = 0.123, 0.456, 0.789
# Isometry built up from ZYZR
A = [cos(α) -sin(α) 0; sin(α) cos(α) 0; 0 0 1]
B = [cos(β) 0 -sin(β); 0 1 0; sin(β) 0 cos(β)]
C = [cos(γ) -sin(γ) 0; sin(γ) cos(γ) 0; 0 0 1]
R = diagm([1, 1, 1.0])
Q = A*B*C*R
# Transform the sampling grid. Note that `Q` is transposed here.
u = Q[1,1]*x + Q[2,1]*y + Q[3,1]*z
v = Q[1,2]*x + Q[2,2]*y + Q[3,2]*z
w = Q[1,3]*x + Q[2,3]*y + Q[3,3]*z
F = f.(x, y, z)
V = PA*F
U = threshold!(P\V, 100eps())
FastTransforms.execute_sph_rotation!(J, α, β, γ, U)
FR = f.(u, v, w)
VR = PA*FR
UR = threshold!(P\VR, 100eps())
@test U ≈ UR
norm(U-UR)
F = f.(x, y, z)
V = PA*F
U = threshold!(P\V, 100eps())
FastTransforms.execute_sph_polar_reflection!(U)
FR = f.(x, y, -z)
VR = PA*FR
UR = threshold!(P\VR, 100eps())
@test U ≈ UR
norm(U-UR)
# Isometry built up from planar reflection
W = [0.123, 0.456, 0.789]
H = w -> I - 2/(w'w)*w*w'
Q = H(W)
# Transform the sampling grid. Note that `Q` is transposed here.
u = Q[1,1]*x + Q[2,1]*y + Q[3,1]*z
v = Q[1,2]*x + Q[2,2]*y + Q[3,2]*z
w = Q[1,3]*x + Q[2,3]*y + Q[3,3]*z
F = f.(x, y, z)
V = PA*F
U = threshold!(P\V, 100eps())
FastTransforms.execute_sph_reflection!(J, W, U)
FR = f.(u, v, w)
VR = PA*FR
UR = threshold!(P\VR, 100eps())
@test U ≈ UR
norm(U-UR)
F = f.(x, y, z)
V = PA*F
U = threshold!(P\V, 100eps())
FastTransforms.execute_sph_reflection!(J, (W[1], W[2], W[3]), U)
FR = f.(u, v, w)
VR = PA*FR
UR = threshold!(P\VR, 100eps())
@test U ≈ UR
norm(U-UR)
# Random orthogonal transformation
Random.seed!(0)
Q = qr(rand(3, 3)).Q
# Transform the sampling grid, note that `Q` is transposed here.
u = Q[1,1]*x + Q[2,1]*y + Q[3,1]*z
v = Q[1,2]*x + Q[2,2]*y + Q[3,2]*z
w = Q[1,3]*x + Q[2,3]*y + Q[3,3]*z
F = f.(x, y, z)
V = PA*F
U = threshold!(P\V, 100eps())
FastTransforms.execute_sph_orthogonal_transformation!(J, Q, U)
FR = f.(u, v, w)
VR = PA*FR
UR = threshold!(P\VR, 100eps())
@test U ≈ UR
norm(U-UR)
================================================
FILE: examples/spinweighted.jl
================================================
# # Spin-weighted spherical harmonics
# This example plays with analysis of:
# ```math
# f(r) = e^{{\rm i} k\cdot r},
# ```
# for some $k\in\mathbb{R}^3$ and where $r\in\mathbb{S}^2$, using spin-$0$ spherical harmonics.
#
# It applies ð, the spin-raising operator,
# both on the spin-$0$ coefficients as well as the original function,
# followed by a spin-$1$ analysis to compare coefficients.
#
# For the storage pattern of the arrays, please consult the
# [documentation](https://MikaelSlevinsky.github.io/FastTransforms).
using FastTransforms, LinearAlgebra
# The colatitudinal grid (mod $\pi$):
N = 10
θ = (0.5:N-0.5)/N
# The longitudinal grid (mod $\pi$):
M = 2*N-1
φ = (0:M-1)*2/M
# Our choice of $k$ and angular parametrization of $r$:
k = [2/7, 3/7, 6/7]
r = (θ,φ) -> [sinpi(θ)*cospi(φ), sinpi(θ)*sinpi(φ), cospi(θ)]
# On the tensor product grid, our function samples are:
F = [exp(im*(k⋅r(θ,φ))) for θ in θ, φ in φ]
# We precompute a spin-$0$ spherical harmonic--Fourier plan:
P = plan_spinsph2fourier(F, 0)
# And an FFTW Fourier analysis plan on $\mathbb{S}^2$:
PA = plan_spinsph_analysis(F, 0)
# Its spin-$0$ spherical harmonic coefficients are:
U⁰ = P\(PA*F)
# We can check its $L^2(\mathbb{S}^2)$ norm against an exact result:
norm(U⁰) ≈ sqrt(4π)
# Spin can be incremented by applying ð, either on the spin-$0$ coefficients:
U¹c = zero(U⁰)
for n in 1:N-1
U¹c[n, 1] = sqrt(n*(n+1))*U⁰[n+1, 1]
end
for m in 1:M÷2
for n in 0:N-1
U¹c[n+1, 2m] = -sqrt((n+m)*(n+m+1))*U⁰[n+1, 2m]
U¹c[n+1, 2m+1] = sqrt((n+m)*(n+m+1))*U⁰[n+1, 2m+1]
end
end
# or on the original function through analysis with spin-$1$ spherical harmonics:
F = [-(k[1]*(im*cospi(θ)*cospi(φ) + sinpi(φ)) + k[2]*(im*cospi(θ)*sinpi(φ)-cospi(φ)) - im*k[3]*sinpi(θ))*exp(im*(k⋅r(θ,φ))) for θ in θ, φ in φ]
# We change plans with spin-$1$ now and reanalyze:
P = plan_spinsph2fourier(F, 1)
PA = plan_spinsph_analysis(F, 1)
U¹s = P\(PA*F)
# Finally, we check $L^2(\mathbb{S}^2)$ norms against another exact result:
norm(U¹c) ≈ norm(U¹s) ≈ sqrt(8π/3*(k⋅k))
================================================
FILE: examples/subspaceangles.jl
================================================
# # Subspace angles
# This example considers the angles between neighbouring Laguerre polynomials with a perturbed measure:
# ```math
# \cos\theta_n = \frac{\langle L_n, L_{n+k}\rangle}{\|L_n\|_2 \|L_{n+k}\|_2},\quad{\rm for}\quad 0\le n < N-k,
# ```
# where the inner product is defined by $\langle f, g\rangle = \int_0^\infty f(x) g(x) x^\beta e^{-x}{\rm\,d}x$.
#
# We do so by connecting Laguerre polynomials to the normalized generalized Laguerre polynomials associated with the perturbed measure. It follows by the inner product of the connection coefficients that:
# ```math
# \cos\theta_n = \frac{(V^\top V)_{n, n+k}}{\sqrt{(V^\top V)_{n, n}(V^\top V)_{n+k, n+k}}}.
# ```
#
using FastTransforms, LinearAlgebra
# The neighbouring index `k` and the maximum degree `N-1`:
k, N = 1, 11
# The Laguerre connection parameters:
α, β = 0.0, 0.125
# We precompute a Laguerre--Laguerre plan:
P = plan_lag2lag(Float64, N, α, β; norm2=true)
# We apply the plan to the identity, followed by the adjoint plan:
VtV = parent(P*I)
lmul!(P', VtV)
# From this matrix, the angles are recovered from:
θ = [acos(VtV[n, n+k]/sqrt(VtV[n, n]*VtV[n+k, n+k])) for n in 1:N-k]
================================================
FILE: examples/triangle.jl
================================================
# # Calculus on the reference triangle
# In this example, we sample a bivariate function:
# ```math
# f(x,y) = \frac{1}{1+x^2+y^2},
# ```
# on the reference triangle with vertices $(0,0)$, $(0,1)$, and $(1,0)$ and analyze it
# in a Proriol series. Then, we find Proriol series for each component of its
# gradient by term-by-term differentiation of our expansion, and we compare them
# with the true Proriol series by sampling an exact expression for the gradient.
#
# We analyze $f(x,y)$ on an $N\times M$ mapped tensor product grid defined by:
# ```math
# \begin{aligned}
# x & = (1+u)/2,\quad{\rm and}\quad y = (1-u)(1+v)/4,\quad {\rm where:}\\
# u_n & = \cos\left[(n+\tfrac{1}{2})\pi/N\right],\quad{\rm for}\quad 0\le n < N,\quad{\rm and}\\
# v_m & = \cos\left[(m+\tfrac{1}{2})\pi/M\right],\quad{\rm for}\quad 0\le m < M;
# \end{aligned}
# ```
# we convert the function samples to mapped Chebyshev² coefficients using
# `plan_tri_analysis`; and finally, we transform the mapped Chebyshev²
# coefficients to Proriol coefficients using `plan_tri2cheb`.
#
# For the storage pattern of the arrays, please consult the
# [documentation](https://MikaelSlevinsky.github.io/FastTransforms).
using FastTransforms, LinearAlgebra, Plots
const GENFIGS = joinpath(pkgdir(FastTransforms), "docs/src/generated")
!isdir(GENFIGS) && mkdir(GENFIGS)
plotlyjs()
# Our function $f$ and the Cartesian components of its gradient:
f = (x,y) -> 1/(1+x^2+y^2)
fx = (x,y) -> -2x/(1+x^2+y^2)^2
fy = (x,y) -> -2y/(1+x^2+y^2)^2
# The polynomial degree:
N = 15
M = N
# The parameters of the Proriol series:
α, β, γ = 0, 0, 0
# The $u$ grid:
u = [sinpi((N-2n-1)/(2N)) for n in 0:N-1]
# And the $v$ grid:
v = [sinpi((M-2m-1)/(2M)) for m in 0:M-1]
# Instead of using the $u\times v$ grid, we use one with more accuracy near the origin.
# Defining $x$ by:
x = [sinpi((2N-2n-1)/(4N))^2 for n in 0:N-1]
# And $w$ by:
w = [sinpi((2M-2m-1)/(4M))^2 for m in 0:M-1]
# We see how the two grids are related:
((1 .+ u)./2 ≈ x) * ((1 .- u).*(1 .+ v')/4 ≈ reverse(x).*w')
# On the mapped tensor product grid, our function samples are:
F = [f(x[n+1], x[N-n]*w[m+1]) for n in 0:N-1, m in 0:M-1]
# We superpose a surface plot of $f$ on top of the grid:
X = [x for x in x, w in w]
Y = [x[N-n]*w[m+1] for n in 0:N-1, m in 0:M-1]
scatter3d(vec(X), vec(Y), vec(0F); markersize=0.75, markercolor=:blue)
surface!(X, Y, F; legend=false, xlabel="x", ylabel="y", zlabel="f")
savefig(joinpath(GENFIGS, "proriol.html"))
###```@raw html
###<object type="text/html" data="../proriol.html" style="width:100%;height:400px;"></object>
###```
# We precompute a Proriol--Chebyshev² plan:
P = plan_tri2cheb(F, α, β, γ)
# And an FFTW Chebyshev² plan on the triangle:
PA = plan_tri_analysis(F)
# Its Proriol-$(α,β,γ)$ coefficients are:
U = P\(PA*F)
# Similarly, our function's gradient samples are:
Fx = [fx(x[n+1], x[N-n]*w[m+1]) for n in 0:N-1, m in 0:M-1]
# and:
Fy = [fy(x[n+1], x[N-n]*w[m+1]) for n in 0:N-1, m in 0:M-1]
# For the partial derivative with respect to $x$, [Olver et al.](https://doi.org/10.1137/19M1245888)
# derive simple expressions for the representation of this component
# using a Proriol-$(α+1,β,γ+1)$ series.
Gx = zeros(Float64, N, M)
for m = 0:M-2
for n = 0:N-2
cf1 = m == 0 ? sqrt((n+1)*(n+2m+α+β+γ+3)/(2m+β+γ+2)*(m+γ+1)*8) : sqrt((n+1)*(n+2m+α+β+γ+3)/(2m+β+γ+1)*(m+β+γ+1)/(2m+β+γ+2)*(m+γ+1)*8)
cf2 = sqrt((n+α+1)*(m+1)/(2m+β+γ+2)*(m+β+1)/(2m+β+γ+3)*(n+2m+β+γ+3)*8)
Gx[n+1, m+1] = cf1*U[n+2, m+1] + cf2*U[n+1, m+2]
end
end
Px = plan_tri2cheb(Fx, α+1, β, γ+1)
Ux = Px\(PA*Fx)
# For the partial derivative with respect to y, the analogous formulae result
# in a Proriol-$(α,β+1,γ+1)$ series.
Gy = zeros(Float64, N, M)
for m = 0:M-2
for n = 0:N-2
Gy[n+1, m+1] = 4*sqrt((m+1)*(m+β+γ+2))*U[n+1, m+2]
end
end
Py = plan_tri2cheb(Fy, α, β+1, γ+1)
Uy = Py\(PA*Fy)
# The $2$-norm relative error in differentiating the Proriol series
# for $f(x,y)$ term-by-term and its sampled gradient is:
hypot(norm(Ux-Gx), norm(Uy-Gy))/hypot(norm(Ux), norm(Uy))
# This error can be improved upon by increasing $N$ and $M$.
================================================
FILE: src/FastTransforms.jl
================================================
module FastTransforms
using ArrayLayouts, BandedMatrices, FastGaussQuadrature, FillArrays, LazyArrays, LinearAlgebra,
SpecialFunctions, ToeplitzMatrices, RecurrenceRelationships
using AbstractFFTs
using FFTW
using GenericFFT
import Base: convert, unsafe_convert, eltype, ndims, adjoint, transpose, show,
*, \, inv, length, size, view, getindex, tail, OneTo
import Base.GMP: Limb
import AbstractFFTs: Plan, ScaledPlan,
fft, ifft, bfft, fft!, ifft!, bfft!, rfft, irfft, brfft,
plan_fft, plan_ifft, plan_bfft, plan_fft!, plan_ifft!,
plan_bfft!, plan_rfft, plan_irfft, plan_brfft,
fftshift, ifftshift, rfft_output_size, brfft_output_size,
normalization
import ArrayLayouts: rowsupport, colsupport, LayoutMatrix, MemoryLayout, AbstractBandedLayout
import BandedMatrices: bandwidths, BandedLayout
import FFTW: dct, dct!, idct, idct!, plan_dct!, plan_idct!,
plan_dct, plan_idct, fftwNumber
import FastGaussQuadrature: unweightedgausshermite
import FillArrays: AbstractFill, getindex_value
import LinearAlgebra: cholesky, issymmetric, isposdef, mul!, lmul!, ldiv!
import GenericFFT: interlace # imported in downstream packages
import RecurrenceRelationships: check_clenshaw_recurrences
export leg2cheb, cheb2leg, ultra2ultra, jac2jac,
lag2lag, jac2ultra, ultra2jac, jac2cheb,
cheb2jac, ultra2cheb, cheb2ultra, associatedjac2jac,
modifiedjac2jac, modifiedlag2lag, modifiedherm2herm,
sph2fourier, sphv2fourier, disk2cxf, ann2cxf, rectdisk2cheb,
tri2cheb, tet2cheb,fourier2sph, fourier2sphv, cxf2disk, cxf2ann,
cheb2rectdisk, cheb2tri, cheb2tet
export plan_leg2cheb, plan_cheb2leg, plan_ultra2ultra, plan_jac2jac,
plan_lag2lag, plan_jac2ultra, plan_ultra2jac, plan_jac2cheb,
plan_cheb2jac, plan_ultra2cheb, plan_cheb2ultra, plan_associatedjac2jac,
plan_modifiedjac2jac, plan_modifiedlag2lag, plan_modifiedherm2herm,
plan_sph2fourier, plan_sph_synthesis, plan_sph_analysis,
plan_sphv2fourier, plan_sphv_synthesis, plan_sphv_analysis,
plan_disk2cxf, plan_disk_synthesis, plan_disk_analysis,
plan_ann2cxf, plan_annulus_synthesis, plan_annulus_analysis,
plan_rectdisk2cheb, plan_rectdisk_synthesis, plan_rectdisk_analysis,
plan_tri2cheb, plan_tri_synthesis, plan_tri_analysis,
plan_tet2cheb, plan_tet_synthesis, plan_tet_analysis,
plan_spinsph2fourier, plan_spinsph_synthesis, plan_spinsph_analysis
include("libfasttransforms.jl")
include("elliptic.jl")
export nufft, nufft1, nufft2, nufft3, inufft1, inufft2
export plan_nufft, plan_nufft1, plan_nufft2, plan_nufft3,
plan_inufft1, plan_inufft2
include("nufft.jl")
include("inufft.jl")
export paduatransform, ipaduatransform, paduatransform!, ipaduatransform!,
paduapoints
export plan_paduatransform!, plan_ipaduatransform!
include("PaduaTransform.jl")
export chebyshevtransform, ichebyshevtransform,
chebyshevtransform!, ichebyshevtransform!,
chebyshevutransform, ichebyshevutransform,
chebyshevutransform!, ichebyshevutransform!, chebyshevpoints
export plan_chebyshevtransform, plan_ichebyshevtransform,
plan_chebyshevtransform!, plan_ichebyshevtransform!,
plan_chebyshevutransform, plan_ichebyshevutransform,
plan_chebyshevutransform!, plan_ichebyshevutransform!
include("chebyshevtransform.jl")
export clenshawcurtisnodes, clenshawcurtisweights, fejernodes1, fejerweights1,
fejernodes2, fejerweights2
export plan_clenshawcurtis, plan_fejer1, plan_fejer2
include("clenshawcurtis.jl")
include("fejer.jl")
export gaunt
include("gaunt.jl")
export GramMatrix, ChebyshevGramMatrix
include("GramMatrix.jl")
export weightedhermitetransform, iweightedhermitetransform
include("hermite.jl")
export sphones, sphzeros, sphrand, sphrandn, sphevaluate,
sphvones, sphvzeros, sphvrand, sphvrandn,
diskones, diskzeros, diskrand, diskrandn,
rectdiskones, rectdiskzeros, rectdiskrand, rectdiskrandn,
triones, trizeros, trirand, trirandn, trievaluate,
tetones, tetzeros, tetrand, tetrandn,
spinsphones, spinsphzeros, spinsphrand, spinsphrandn
include("specialfunctions.jl")
include("toeplitzplans.jl")
include("toeplitzhankel.jl")
export ToeplitzPlusHankel
include("ToeplitzPlusHankel.jl")
# following use libfasttransforms by default
for f in (:jac2jac,
:lag2lag, :jac2ultra, :ultra2jac, :jac2cheb,
:cheb2jac, :ultra2cheb, :cheb2ultra, :associatedjac2jac,
:modifiedjac2jac, :modifiedlag2lag, :modifiedherm2herm,
:sph2fourier, :sphv2fourier, :disk2cxf, :ann2cxf,
:rectdisk2cheb, :tri2cheb, :tet2cheb,
:leg2cheb, :cheb2leg, :ultra2ultra)
lib_f = Symbol("lib_", f)
@eval $f(x::AbstractArray, y...; z...) = $lib_f(x, y...; z...)
end
include("arrays.jl")
# following use Toeplitz-Hankel to avoid expensive plans
# for f in (:leg2cheb, :cheb2leg, :ultra2ultra)
# th_f = Symbol("th_", f)
# lib_f = Symbol("lib_", f)
# @eval begin
# $f(x::AbstractArray, y...; z...) = $th_f(x, y...; z...)
# # $f(x::AbstractArray, y...; z...) = $lib_f(x, y...; z...)
# end
# end
include("docstrings.jl")
end # module
================================================
FILE: src/GramMatrix.jl
================================================
abstract type AbstractGramMatrix{T} <: LayoutMatrix{T} end
@inline issymmetric(G::AbstractGramMatrix) = true
@inline isposdef(G::AbstractGramMatrix) = true
struct GramMatrix{T, WT <: AbstractMatrix{T}, XT <: AbstractMatrix{T}} <: AbstractGramMatrix{T}
W::WT
X::XT
function GramMatrix{T, WT, XT}(W::WT, X::XT) where {T, WT, XT}
if size(W) ≠ size(X)
throw(ArgumentError("Cannot construct a GramMatrix with W and X of different sizes."))
end
if !issymmetric(W)
throw(ArgumentError("Cannot construct a GramMatrix with a nonsymmetric W."))
end
if bandwidths(X) ≠ (1, 1)
throw(ArgumentError("Cannot construct a GramMatrix with a nontridiagonal X."))
end
new{T, WT, XT}(W, X)
end
end
"""
GramMatrix(W::AbstractMatrix, X::AbstractMatrix)
Construct a symmetric positive-definite Gram matrix with data stored in ``W``.
Given a family of orthogonal polynomials ``𝐏(x) = {p₀(x), p₁(x),…}``
and a continuous inner product ``⟨f, g⟩``, the Gram matrix is defined by:
```math
W[i, j] = ⟨p_{i-1}, p_{j-1}⟩.
```
Moreover, given ``X``, the transposed Jacobi matrix that satisfies ``x 𝐏(x) = 𝐏(x) X``,
the Gram matrix satisfies the skew-symmetric rank-2 displacement equation (``X = X[1:n, 1:n]``):
```math
XᵀW - WX = GJGᵀ,
```
where ``J = [0 1; -1 0]`` and where:
```math
G[:, 1] = 𝐞_n, \\quad G[:, 2] = W[n-1, :]X[n-1, n] - Xᵀ W[:, n].
```
Fast (``O(n^2)``) Cholesky factorization of the Gram matrix returns the
connection coefficients between ``𝐏(x)`` and the polynomials ``𝐐(x)``
orthogonal in the modified inner product, ``𝐏(x) = 𝐐(x) R``.
See also [`ChebyshevGramMatrix`](@ref) for a special case.
> K. Gumerov, S. Rigg, and R. M. Slevinsky, [Fast measure modification of orthogonal polynomials via matrices with displacement structure](https://arxiv.org/abs/2412.17663), arXiv:2412.17663, 2024.
"""
GramMatrix(W::WT, X::XT) where {T, WT <: AbstractMatrix{T}, XT <: AbstractMatrix{T}} = GramMatrix{T, WT, XT}(W, X)
@inline size(G::GramMatrix) = size(G.W)
@inline getindex(G::GramMatrix, i::Integer, j::Integer) = G.W[i, j]
@inline bandwidths(G::GramMatrix) = bandwidths(G.W)
@inline MemoryLayout(G::GramMatrix) = MemoryLayout(G.W)
@inline rowsupport(G::GramMatrix, j) = rowsupport(MemoryLayout(G), G.W, j)
@inline colsupport(G::GramMatrix, j) = colsupport(MemoryLayout(G), G.W, j)
"""
GramMatrix(μ::AbstractVector, X::AbstractMatrix)
Construct a GramMatrix from modified orthogonal polynomial moments and the multiplication operator.
In the standard (classical) normalization, ``p₀(x) = 1``, so that the moments
``µ[n] = ⟨ pₙ₋₁, 1⟩`` are in fact the first column of the Gram matrix.
The recurrence is built from ``XᵀW = WX``.
"""
GramMatrix(μ::AbstractVector{T}, X::XT) where {T, XT <: AbstractMatrix{T}} = GramMatrix(μ, X, one(T))
function GramMatrix(μ::AbstractVector{T}, X::XT, p0::T) where {T, XT <: AbstractMatrix{T}}
N = length(μ)
n = (N+1)÷2
@assert N == size(X, 1) == size(X, 2)
@assert bandwidths(X) == (1, 1)
W = LowerTriangular(Matrix{T}(undef, N, N))
if n > 0
@inbounds for m in 1:N
W[m, 1] = p0*μ[m]
end
end
if n > 1
@inbounds for m in 2:N-1
W[m, 2] = (X[m-1, m]*W[m-1, 1] + (X[m, m]-X[1, 1])*W[m, 1] + X[m+1, m]*W[m+1, 1])/X[2, 1]
end
end
@inbounds @simd for n in 3:n
for m in n:N-n+1
W[m, n] = (X[m-1, m]*W[m-1, n-1] + (X[m, m]-X[n-1, n-1])*W[m, n-1] + X[m+1, m]*W[m+1, n-1] - X[n-2, n-1]*W[m, n-2])/X[n, n-1]
end
end
return GramMatrix(Symmetric(W[1:n, 1:n], :L), eval(XT.name.name)(view(X, 1:n, 1:n)))
end
function GramMatrix(μ::PaddedVector{T}, X::XT, p0::T) where {T, XT <: AbstractMatrix{T}}
N = length(μ)
b = length(μ.args[2])-1
n = (N+1)÷2
@assert N == size(X, 1) == size(X, 2)
@assert bandwidths(X) == (1, 1)
W = BandedMatrix{T}(undef, (N, N), (b, 0))
if n > 0
@inbounds for m in 1:min(N, b+1)
W[m, 1] = p0*μ[m]
end
end
if n > 1
@inbounds for m in 2:min(N-1, b+2)
W[m, 2] = (X[m-1, m]*W[m-1, 1] + (X[m, m]-X[1, 1])*W[m, 1] + X[m+1, m]*W[m+1, 1])/X[2, 1]
end
end
@inbounds @simd for n in 3:n
for m in n:min(N-n+1, b+n)
W[m, n] = (X[m-1, m]*W[m-1, n-1] + (X[m, m]-X[n-1, n-1])*W[m, n-1] + X[m+1, m]*W[m+1, n-1] - X[n-2, n-1]*W[m, n-2])/X[n, n-1]
end
end
return GramMatrix(Symmetric(W[1:n, 1:n], :L), eval(XT.name.name)(view(X, 1:n, 1:n)))
end
"""
GramMatrix(cnm1::AbstractVector, cn::AbstractVector, X::AbstractMatrix)
Construct a GramMatrix from its last two columns and the multiplication operator.
The recurrence is built from ``XᵀW = WX`` and is used in case the moment method is unstable (such as with Laguerre).
"""
function GramMatrix(cnm1::AbstractVector{T}, cn::AbstractVector{T}, X::XT) where {T, XT <: AbstractMatrix{T}}
N = length(cn)
@assert N == length(cnm1) == size(X, 1) == size(X, 2)
@assert bandwidths(X) == (1, 1)
W = Matrix{T}(undef, N, N)
if N > 0
@inbounds for m in 1:N
W[N, m] = W[m, N] = cn[m]
end
end
if N > 1
@inbounds for m in 1:N
W[N-1, m] = W[m, N-1] = cnm1[m]
end
end
@inbounds @simd for n in N:-1:3
W[1, n-2] = ((X[1, 1]-X[n-1, n-1])*W[1, n-1] + X[2, 1]*W[2, n-1] - X[n, n-1]*W[1, n])/X[n-2, n-1]
for m in 2:n-2
W[m, n-2] = (X[m-1, m]*W[m-1, n-1] + (X[m, m]-X[n-1, n-1])*W[m, n-1] + X[m+1, m]*W[m+1, n-1] - X[n, n-1]*W[m, n])/X[n-2, n-1]
end
for m in n-1:N-2
W[m, n-2] = W[n-2, m]
end
end
return GramMatrix(W, X)
end
#
# X'W-W*X = G*J*G'
# This returns G, where J = [0 1; -1 0], respecting the skew-symmetry of the right-hand side.
#
function compute_skew_generators(W::GramMatrix{T}) where T
X = W.X
n = size(W, 1)
G = zeros(T, n, 2)
G[n, 1] = one(T)
G[:, 2] .= W[:, n-1]*X[n-1, n] + W[:, n]*X[n, n] - X'W[:, n]
return G
end
function cholesky(W::GramMatrix{T}) where T
cholesky(MemoryLayout(W), W)
end
function cholesky(_, W::GramMatrix{T}) where T
n = size(W, 1)
G = compute_skew_generators(W)
L = zeros(T, n, n)
c = W[:, 1]
ĉ = zeros(T, n)
l = zeros(T, n)
v = zeros(T, n)
row1 = zeros(T, n)
fastcholesky!(L, W.X, G, c, ĉ, l, v, row1, n)
return Cholesky(L, 'L', 0)
end
function fastcholesky!(L::Matrix{T}, X, G, c, ĉ, l, v, row1, n) where T
@inbounds @simd for k in 1:n-1
d = sqrt(c[k])
for j in k:n
L[j, k] = l[j] = c[j]/d
end
for j in k:n
v[j] = G[j, 1]*G[k, 2] - G[j, 2]*G[k, 1]
end
for j in k+1:n-1
ĉ[j] = (X[j-1, j]*c[j-1] + (X[j, j]-X[k, k])*c[j] + X[j+1, j]*c[j+1] + c[k]*row1[j] - row1[k]*c[j] - v[j])/X[k+1, k]
end
ĉ[n] = (X[n-1, n]*c[n-1] + (X[n, n]-X[k, k])*c[n] + c[k]*row1[n] - row1[k]*c[n] - v[n])/X[k+1, k]
cst = X[k+1, k]/d
for j in k+1:n
row1[j] = -cst*l[j]
end
cst = c[k+1]/d
for j in k:n
c[j] = ĉ[j] - cst*l[j]
end
gd1 = G[k, 1]/d
gd2 = G[k, 2]/d
for j in k:n
G[j, 1] -= l[j]*gd1
G[j, 2] -= l[j]*gd2
end
end
L[n, n] = sqrt(c[n])
end
function cholesky(::Union{AbstractBandedLayout, SymmetricLayout{<: AbstractBandedLayout}}, W::GramMatrix{T}) where T
n = size(W, 1)
G = compute_skew_generators(W)
L = BandedMatrix{T}(undef, (n, n), (bandwidth(W, 1), 0))
c = W[:, 1]
ĉ = zeros(T, n)
l = zeros(T, n)
v = zeros(T, n)
row1 = zeros(T, n)
fastcholesky!(L, W.X, G, c, ĉ, l, v, row1, n)
return Cholesky(L, 'L', 0)
end
function fastcholesky!(L::BandedMatrix{T}, X, G, c, ĉ, l, v, row1, n) where T
b = bandwidth(L, 1)
@inbounds @simd for k in 1:n-1
d = sqrt(c[k])
for j in k:min(k+b, n)
L[j, k] = l[j] = c[j]/d
end
for j in max(k, n-b-1):n
v[j] = G[j, 1]*G[k, 2] - G[j, 2]*G[k, 1]
end
for j in k+1:min(k+b+1, n-1)
ĉ[j] = (X[j-1, j]*c[j-1] + (X[j, j]-X[k, k])*c[j] + X[j+1, j]*c[j+1] + c[k]*row1[j] - row1[k]*c[j] - v[j])/X[k+1, k]
end
if k ≥ n-b-1
ĉ[n] = (X[n-1, n]*c[n-1] + (X[n, n]-X[k, k])*c[n] + c[k]*row1[n] - row1[k]*c[n] - v[n])/X[k+1, k]
end
cst = X[k+1, k]/d
for j in k+1:min(k+b+1, n)
row1[j] = -cst*l[j]
end
cst = c[k+1]/d
for j in k:min(k+b+1, n)
c[j] = ĉ[j] - cst*l[j]
end
gd1 = G[k, 1]/d
gd2 = G[k, 2]/d
for j in max(k, n-b-1):n
G[j, 1] -= l[j]*gd1
G[j, 2] -= l[j]*gd2
end
end
L[n, n] = sqrt(c[n])
end
struct ChebyshevGramMatrix{T, V <: AbstractVector{T}} <: AbstractGramMatrix{T}
μ::V
n::Int
end
"""
ChebyshevGramMatrix(μ::AbstractVector)
Construct a Chebyshev--Gram matrix of size `(length(μ)+1)÷2` with entries:
```math
2 W[i, j] = µ[|i-j|+1] + µ[i+j-1].
```
Due to the linearization of a product of two first-kind Chebyshev polynomials,
the Chebyshev--Gram matrix can be constructed from modified Chebyshev moments:
```math
µ[n] = ⟨ Tₙ₋₁, 1⟩.
```
Specialized construction and Cholesky factorization is given for this type.
See also [`GramMatrix`](@ref) for the general case.
"""
function ChebyshevGramMatrix(μ::V) where {T, V <: AbstractVector{T}}
n = (length(μ)+1)÷2
ChebyshevGramMatrix{T, V}(μ, n)
end
@inline size(G::ChebyshevGramMatrix) = (G.n, G.n)
@inline getindex(G::ChebyshevGramMatrix, i::Integer, j::Integer) = (G.μ[abs(i-j)+1] + G.μ[i+j-1])/2
@inline bandwidths(G::ChebyshevGramMatrix{T, <: PaddedVector{T}}) where T = (length(G.μ.args[2])-1, length(G.μ.args[2])-1)
@inline MemoryLayout(G::ChebyshevGramMatrix{T, <: PaddedVector{T}}) where T = BandedLayout()
#
# 2X'W-W*2X = G*J*G'
# This returns G, where J = [0 1; -1 0], respecting the skew-symmetry of the right-hand side.
# We use twice the Chebybshev Jacobi matrix so that subsequent arithmetic is easier.
#
function compute_skew_generators(W::ChebyshevGramMatrix{T}) where T
μ = W.μ
n = size(W, 1)
G = zeros(T, n, 2)
G[n, 1] = one(T)
@inbounds @simd for j in 1:n-1
G[j, 2] = -(μ[n+2-j] + μ[n+j])/2
end
G
end
function cholesky(W::ChebyshevGramMatrix{T}) where T
n = size(W, 1)
G = compute_skew_generators(W)
L = zeros(T, n, n)
c = W[:, 1]
ĉ = zeros(T, n)
l = zeros(T, n)
v = zeros(T, n)
row1 = zeros(T, n)
fastcholesky!(L, G, c, ĉ, l, v, row1, n)
return Cholesky(L, 'L', 0)
end
function fastcholesky!(L::Matrix{T}, G, c, ĉ, l, v, row1, n) where T
@inbounds @simd for k in 1:n-1
d = sqrt(c[k])
for j in k:n
L[j, k] = l[j] = c[j]/d
end
for j in k:n
v[j] = G[j, 1]*G[k, 2] - G[j, 2]*G[k, 1]
end
if k == 1
for j in 2:n-1
ĉ[j] = (c[j+1] + c[j-1] + c[1]*row1[j] - row1[1]*c[j] - v[j])/2
end
ĉ[n] = (c[n-1] + c[1]*row1[n] - row1[1]*c[n] - v[n])/2
cst = 2/d
else
for j in k+1:n-1
ĉ[j] = c[j+1] + c[j-1] + c[k]*row1[j] - row1[k]*c[j] - v[j]
end
ĉ[n] = c[n-1] + c[k]*row1[n] - row1[k]*c[n] - v[n]
cst = 1/d
end
for j in k+1:n
row1[j] = -cst*l[j]
end
cst = c[k+1]/d
for j in k:n
c[j] = ĉ[j] - cst*l[j]
end
gd1 = G[k, 1]/d
gd2 = G[k, 2]/d
for j in k:n
G[j, 1] -= l[j]*gd1
G[j, 2] -= l[j]*gd2
end
end
L[n, n] = sqrt(c[n])
end
function cholesky(W::ChebyshevGramMatrix{T, <: PaddedVector{T}}) where T
n = size(W, 1)
G = compute_skew_generators(W)
L = BandedMatrix{T}(undef, (n, n), (bandwidth(W, 1), 0))
c = W[:, 1]
ĉ = zeros(T, n)
l = zeros(T, n)
v = zeros(T, n)
row1 = zeros(T, n)
fastcholesky!(L, G, c, ĉ, l, v, row1, n)
return Cholesky(L, 'L', 0)
end
function fastcholesky!(L::BandedMatrix{T}, G, c, ĉ, l, v, row1, n) where T
b = bandwidth(L, 1)
@inbounds @simd for k in 1:n-1
d = sqrt(c[k])
for j in k:min(k+b, n)
L[j, k] = l[j] = c[j]/d
end
for j in max(k, n-b-1):n
v[j] = G[j, 1]*G[k, 2] - G[j, 2]*G[k, 1]
end
if k == 1
for j in 2:min(b+2, n-1)
ĉ[j] = (c[j+1] + c[j-1] + c[1]*row1[j] - row1[1]*c[j] - v[j])/2
end
if 1 ≥ n-b-1
ĉ[n] = (c[n-1] + c[1]*row1[n] - row1[1]*c[n] - v[n])/2
end
cst = 2/d
else
for j in k+1:min(k+b+1, n-1)
ĉ[j] = c[j+1] + c[j-1] + c[k]*row1[j] - row1[k]*c[j] - v[j]
end
if k ≥ n-b-1
ĉ[n] = c[n-1] + c[k]*row1[n] - row1[k]*c[n] - v[n]
end
cst = 1/d
end
for j in k+1:min(k+b+1, n)
row1[j] = -cst*l[j]
end
cst = c[k+1]/d
for j in k:min(k+b+1, n)
c[j] = ĉ[j] - cst*l[j]
end
gd1 = G[k, 1]/d
gd2 = G[k, 2]/d
for j in max(k, n-b-1):n
G[j, 1] -= l[j]*gd1
G[j, 2] -= l[j]*gd2
end
end
L[n, n] = sqrt(c[n])
end
================================================
FILE: src/PaduaTransform.jl
================================================
# lex indicates if its lexigraphical (i.e., x, y) or reverse (y, x)
# If in lexigraphical order the coefficient vector's entries
# corrrespond to the following basis polynomials:
# [T0(x) * T0(y), T1(x) * T0(y), T0(x) * T1(y), T2(x) * T0(y), T1(x) * T1(y), T0(x) * T2(y), ...]
# else, if not in lexigraphical order:
# [T0(x) * T0(y), T0(x) * T1(y), T1(x) * T0(y), T0(x) * T2(y), T1(x) * T1(y), T2(x) * T0(y), ...]
"""
Pre-plan an Inverse Padua Transform.
"""
struct IPaduaTransformPlan{lex,IDCTPLAN,T}
cfsmat::Matrix{T}
idctplan::IDCTPLAN
end
IPaduaTransformPlan(cfsmat::Matrix{T},idctplan,::Type{Val{lex}}) where {T,lex} =
IPaduaTransformPlan{lex,typeof(idctplan),T}(cfsmat,idctplan)
"""
Pre-plan an Inverse Padua Transform.
"""
function plan_ipaduatransform!(::Type{T},N::Integer,lex) where T
n=Int(cld(-3+sqrt(1+8N),2))
if N ≠ div((n+1)*(n+2),2)
error("Padua transforms can only be applied to vectors of length (n+1)*(n+2)/2.")
end
IPaduaTransformPlan(Array{T}(undef,n+2,n+1),FFTW.plan_r2r!(Array{T}(undef,n+2,n+1),FFTW.REDFT00),lex)
end
plan_ipaduatransform!(::Type{T},N::Integer) where {T} = plan_ipaduatransform!(T,N,Val{true})
plan_ipaduatransform!(v::AbstractVector{T},lex...) where {T} = plan_ipaduatransform!(eltype(v),length(v),lex...)
function *(P::IPaduaTransformPlan,v::AbstractVector{T}) where T
cfsmat=trianglecfsmat(P,v)
n,m=size(cfsmat)
rmul!(view(cfsmat,:,2:m-1),0.5)
rmul!(view(cfsmat,2:n-1,:),0.5)
tensorvals=P.idctplan*cfsmat
paduavec!(v,P,tensorvals)
end
ipaduatransform!(v::AbstractVector,lex...) = plan_ipaduatransform!(v,lex...)*v
"""
Inverse Padua Transform maps the 2D Chebyshev coefficients to the values of the interpolation polynomial at the Padua points.
"""
ipaduatransform(v::AbstractVector,lex...) = plan_ipaduatransform!(v,lex...)*copy(v)
"""
Creates ``(n+2)x(n+1)`` Chebyshev coefficient matrix from triangle coefficients.
"""
function trianglecfsmat(P::IPaduaTransformPlan{true},cfs::AbstractVector)
N=length(cfs)
n=Int(cld(-3+sqrt(1+8N),2))
cfsmat=fill!(P.cfsmat,0)
m=1
for d=1:n+1
@inbounds for k=1:d
j=d-k+1
cfsmat[k,j]=cfs[m]
if m==N
return cfsmat
else
m+=1
end
end
end
return cfsmat
end
function trianglecfsmat(P::IPaduaTransformPlan{false},cfs::AbstractVector)
N=length(cfs)
n=Int(cld(-3+sqrt(1+8N),2))
cfsmat=fill!(P.cfsmat,0)
m=1
for d=1:n+1
@inbounds for k=d:-1:1
j=d-k+1
cfsmat[k,j]=cfs[m]
if m==N
return cfsmat
else
m+=1
end
end
end
return cfsmat
end
"""
Vectorizes the function values at the Padua points.
"""
function paduavec!(v,P::IPaduaTransformPlan,padmat::Matrix)
n=size(padmat,2)-1
N=(n+1)*(n+2)
if iseven(n)>0
d=div(n+2,2)
m=0
@inbounds for i=1:n+1
v[m+1:m+d]=view(padmat,1+mod(i,2):2:n+1+mod(i,2),i)
m+=d
end
else
@inbounds v[:]=view(padmat,1:2:N-1)
end
return v
end
"""
Pre-plan a Padua Transform.
"""
struct PaduaTransformPlan{lex,DCTPLAN,T}
vals::Matrix{T}
dctplan::DCTPLAN
end
PaduaTransformPlan(vals::Matrix{T},dctplan,::Type{Val{lex}}) where {T,lex} =
PaduaTransformPlan{lex,typeof(dctplan),T}(vals,dctplan)
"""
Pre-plan a Padua Transform.
"""
function plan_paduatransform!(::Type{T},N::Integer,lex) where T
n=Int(cld(-3+sqrt(1+8N),2))
if N ≠ ((n+1)*(n+2))÷2
error("Padua transforms can only be applied to vectors of length (n+1)*(n+2)/2.")
end
PaduaTransformPlan(Array{T}(undef,n+2,n+1),FFTW.plan_r2r!(Array{T}(undef,n+2,n+1),FFTW.REDFT00),lex)
end
plan_paduatransform!(::Type{T},N::Integer) where {T} = plan_paduatransform!(T,N,Val{true})
plan_paduatransform!(v::AbstractVector{T},lex...) where {T} = plan_paduatransform!(eltype(v),length(v),lex...)
function *(P::PaduaTransformPlan,v::AbstractVector{T}) where T
N=length(v)
n=Int(cld(-3+sqrt(1+8N),2))
vals=paduavalsmat(P,v)
tensorcfs=P.dctplan*vals
m,l=size(tensorcfs)
rmul!(tensorcfs,T(2)/(n*(n+1)))
rmul!(view(tensorcfs,1,:),0.5)
rmul!(view(tensorcfs,:,1),0.5)
rmul!(view(tensorcfs,m,:),0.5)
rmul!(view(tensorcfs,:,l),0.5)
trianglecfsvec!(v,P,tensorcfs)
end
paduatransform!(v::AbstractVector,lex...) = plan_paduatransform!(v,lex...)*v
"""
Padua Transform maps from interpolant values at the Padua points to the 2D Chebyshev coefficients.
"""
paduatransform(v::AbstractVector,lex...) = plan_paduatransform!(v,lex...)*copy(v)
"""
Creates ``(n+2)x(n+1)`` matrix of interpolant values on the tensor grid at the ``(n+1)(n+2)/2`` Padua points.
"""
function paduavalsmat(P::PaduaTransformPlan,v::AbstractVector)
N=length(v)
n=Int(cld(-3+sqrt(1+8N),2))
vals=fill!(P.vals,0.)
if iseven(n)>0
d=div(n+2,2)
m=0
@inbounds for i=1:n+1
vals[1+mod(i,2):2:n+1+mod(i,2),i]=view(v,m+1:m+d)
m+=d
end
else
@inbounds vals[1:2:end]=view(v,:)
end
return vals
end
"""
Creates length ``(n+1)(n+2)/2`` vector from matrix of triangle Chebyshev coefficients.
"""
function trianglecfsvec!(v,P::PaduaTransformPlan{true},cfs::Matrix)
m=size(cfs,2)
l=1
for d=1:m
@inbounds for k=1:d
j=d-k+1
v[l]=cfs[k,j]
l+=1
end
end
return v
end
function trianglecfsvec!(v,P::PaduaTransformPlan{false},cfs::Matrix)
m=size(cfs,2)
l=1
for d=1:m
@inbounds for k=d:-1:1
j=d-k+1
v[l]=cfs[k,j]
l+=1
end
end
return v
end
"""
Returns coordinates of the ``(n+1)(n+2)/2`` Padua points.
"""
function paduapoints(::Type{T}, n::Integer) where T
N=div((n+1)*(n+2),2)
MM=Matrix{T}(undef,N,2)
m=0
delta=0
NN=div(n,2)+1
# x coordinates
for k=n:-1:0
if isodd(n)
delta = Int(isodd(k))
end
x = -cospi(T(k)/n)
@inbounds for j=NN+delta:-1:1
m+=1
MM[m,1]=x
end
end
# y coordinates
# populate the first two sets, and copy the rest
m=0
for k=n:-1:n-1
if isodd(n)
delta = Int(isodd(k))
end
for j=NN+delta:-1:1
m+=1
@inbounds if isodd(n-k)
MM[m,2]=-cospi((2j-one(T))/(n+1))
else
MM[m,2]=-cospi(T(2j-2)/(n+1))
end
end
end
m += 1
# number of y coordinates between k=n and k=n-2
Ny_shift = 2NN+isodd(n)
for k in n-2:-1:0
if isodd(n)
delta = Int(isodd(k))
end
for j in range(m, length=NN+delta)
@inbounds MM[j,2] = MM[j-Ny_shift,2]
end
m += NN+delta
end
return MM
end
paduapoints(n::Integer) = paduapoints(Float64,n)
================================================
FILE: src/ToeplitzPlusHankel.jl
================================================
struct ToeplitzPlusHankel{T, S, P1 <: Plan{S}, P2 <: Plan{S}} <: AbstractMatrix{T}
tc::Vector{T}
tr::Vector{T}
h::Vector{T}
th_dft::Matrix{S}
tht_dft::Matrix{S}
temp::Matrix{S}
plan::P1
iplan::P2
size::NTuple{2, Int}
end
# enforces tr[1] == tc[1]
function ToeplitzPlusHankel(tc::Vector{T}, tr::Vector{T}, h::Vector{T}) where T
m = length(tc)
n = length(tr)
@assert length(h) == m+n-1
tr[1] = tc[1]
mn = m+n
S = promote_type(float(T), Complex{Float32})
th_dft = Matrix{S}(undef, mn, 2)
copyto!(th_dft, 1, tc, 1, m)
th_dft[m+1, 1] = zero(T)
copyto!(th_dft, m+2, Iterators.reverse(tr), 1, n-1)
copyto!(th_dft, mn+1, h, n, m)
th_dft[m+1, 2] = zero(T)
copyto!(th_dft, mn+m+2, h, 1, n-1)
tht_dft = Matrix{S}(undef, mn, 2)
copyto!(tht_dft, 1, tr, 1, n)
tht_dft[n+1, 1] = zero(T)
copyto!(tht_dft, n+2, Iterators.reverse(tc), 1, m-1)
copyto!(tht_dft, mn+1, h, m, n)
tht_dft[n+1, 2] = zero(T)
copyto!(tht_dft, mn+n+2, h, 1, m-1)
plan = plan_fft!(th_dft, 1)
plan*th_dft
plan*tht_dft
temp = zeros(S, mn, 2)
iplan = inv(plan)
ToeplitzPlusHankel{T, S, typeof(plan), typeof(iplan)}(tc, tr, h, th_dft, tht_dft, temp, plan, iplan, (m, n))
end
# A ChebyshevGramMatrix isa (symmetric positive-definite) ToeplitzPlusHankel matrix.
function ToeplitzPlusHankel(G::ChebyshevGramMatrix)
n = size(G, 1)
ToeplitzPlusHankel(G.μ[1:n]/2, G.μ[1:n]/2, G.μ/2)
end
size(A::ToeplitzPlusHankel) = A.size
getindex(A::ToeplitzPlusHankel, i::Integer, j::Integer) = (i ≥ j ? A.tc[i-j+1] : A.tr[j-i+1]) + A.h[i+j-1]
# A view of a T+H is also T+H.
function getindex(A::ToeplitzPlusHankel, ir::UnitRange{Int}, jr::UnitRange{Int})
fir, lir = first(ir), last(ir)
fjr, ljr = first(jr), last(jr)
if fir ≥ fjr
tc = A.tc[fir-fjr+1:lir-fjr+1]
tr = [A.tc[fir-fjr+1:-1:max(1, fir-ljr+1)]; A.tr[2:ljr-fir+1]]
else
tc = [A.tr[fjr-fir+1:-1:max(1, fjr-lir+1)]; A.tc[2:lir-fjr+1]]
tr = A.tr[fjr-fir+1:ljr-fir+1]
end
ToeplitzPlusHankel(tc, tr, A.h[fir+fjr-1:lir+ljr-1])
end
# y ← A x α + y β
function mul!(y::StridedVector{T}, A::ToeplitzPlusHankel{T}, x::StridedVector{T}, α::S, β::S) where {T <: Real, S <: Real}
m, n = size(A)
@assert m == length(y)
@assert n == length(x)
mn = m+n
th_dft = A.th_dft
temp = A.temp
plan = A.plan
iplan = A.iplan
copyto!(temp, 1, x, 1, n)
copyto!(temp, mn+1, Iterators.reverse(x), 1, n)
@inbounds for j in n+1:mn
temp[j, 1] = zero(T)
temp[j, 2] = zero(T)
end
plan*temp
temp .*= th_dft
iplan*temp
if iszero(β)
@inbounds @simd for i in 1:m
y[i] = α * (real(temp[i, 1])+real(temp[i, 2]))
end
else
@inbounds @simd for i in 1:m
y[i] = α * (real(temp[i, 1])+real(temp[i, 2])) + β*y[i]
end
end
return y
end
# y ← A' x α + y β
function mul!(y::StridedVector{T}, A::Adjoint{T, <:ToeplitzPlusHankel{T}}, x::StridedVector{T}, α::S, β::S) where {T <: Real, S <: Real}
m, n = size(A)
@assert m == length(y)
@assert n == length(x)
mn = m+n
AP = A.parent
tht_dft = AP.tht_dft
temp = AP.temp
plan = AP.plan
iplan = AP.iplan
copyto!(temp, 1, x, 1, n)
copyto!(temp, mn+1, Iterators.reverse(x), 1, n)
@inbounds for j in n+1:mn
temp[j, 1] = zero(T)
temp[j, 2] = zero(T)
end
plan*temp
temp .*= tht_dft
iplan*temp
if iszero(β)
@inbounds @simd for i in 1:m
y[i] = α * (real(temp[i, 1])+real(temp[i, 2]))
end
else
@inbounds @simd for i in 1:m
y[i] = α * (real(temp[i, 1])+real(temp[i, 2])) + β*y[i]
end
end
return y
end
# C ← A B α + C β
function mul!(C::StridedMatrix{T}, A::ToeplitzPlusHankel{T}, B::StridedMatrix{T}, α::S, β::S) where {T <: Real, S <: Real}
m, n = size(A)
@assert m == size(C, 1)
@assert n == size(B, 1)
p = size(B, 2)
if size(C, 2) != p
throw(DimensionMismatch("input and output matrices must have same number of columns"))
end
th_dft = A.th_dft
TC = promote_type(float(T), Complex{Float32})
temp = zeros(TC, m+n, 2p)
plan = plan_fft!(temp, 1)
for k in 1:p
copyto!(view(temp, :, 2k-1), 1, view(B, :, k), 1, n)
copyto!(view(temp, :, 2k), 1, Iterators.reverse(view(B, :, k)), 1, n)
end
plan*temp
for k in 1:p
vt = view(temp, :, 2k-1:2k)
vt .*= th_dft
end
plan\temp
if iszero(β)
@inbounds for k in 1:p
for i in 1:m
C[i, k] = α * (real(temp[i, 2k-1])+real(temp[i, 2k]))
end
end
else
@inbounds for k in 1:p
for i in 1:m
C[i, k] = α * (real(temp[i, 2k-1])+real(temp[i, 2k])) + β*C[i, k]
end
end
end
return C
end
# Morally equivalent to mul!(C', B', A', α, β)' with StridedMatrix replaced by AbstractMatrix below
function mul!(C::StridedMatrix{T}, A::StridedMatrix{T}, B::ToeplitzPlusHankel{T}, α::S, β::S) where {T <: Real, S <: Real}
n, m = size(B)
@assert m == size(C, 2)
@assert n == size(A, 2)
p = size(A, 1)
if size(C, 1) != p
throw(DimensionMismatch("input and output matrices must have same number of rows"))
end
tht_dft = B.tht_dft
TC = promote_type(float(T), Complex{Float32})
temp = zeros(TC, m+n, 2p)
plan = plan_fft!(temp, 1)
for k in 1:p
copyto!(view(temp, :, 2k-1), 1, view(A, k, :), 1, n)
copyto!(view(temp, :, 2k), 1, Iterators.reverse(view(A, k, :)), 1, n)
end
plan*temp
for k in 1:p
vt = view(temp, :, 2k-1:2k)
vt .*= tht_dft
end
plan\temp
if iszero(β)
@inbounds for k in 1:p
for i in 1:m
C[k, i] = α * (real(temp[i, 2k-1])+real(temp[i, 2k]))
end
end
else
@inbounds for k in 1:p
for i in 1:m
C[k, i] = α * (real(temp[i, 2k-1])+real(temp[i, 2k])) + β*C[k, i]
end
end
end
return C
end
# C ← A' B α + C β
function mul!(C::StridedMatrix{T}, A::Adjoint{T, <:ToeplitzPlusHankel{T}}, B::StridedMatrix{T}, α::S, β::S) where {T <: Real, S <: Real}
m, n = size(A)
@assert m == size(C, 1)
@assert n == size(B, 1)
p = size(B, 2)
if size(C, 2) != p
throw(DimensionMismatch("input and output matrices must have same number of columns"))
end
tht_dft = A.parent.tht_dft
TC = promote_type(float(T), Complex{Float32})
temp = zeros(TC, m+n, 2p)
plan = plan_fft!(temp, 1)
for k in 1:p
copyto!(view(temp, :, 2k-1), 1, view(B, :, k), 1, n)
copyto!(view(temp, :, 2k), 1, Iterators.reverse(view(B, :, k)), 1, n)
end
plan*temp
for k in 1:p
vt = view(temp, :, 2k-1:2k)
vt .*= tht_dft
end
plan\temp
if iszero(β)
@inbounds for k in 1:p
for i in 1:m
C[i, k] = α * (real(temp[i, 2k-1])+real(temp[i, 2k]))
end
end
else
@inbounds for k in 1:p
for i in 1:m
C[i, k] = α * (real(temp[i, 2k-1])+real(temp[i, 2k])) + β*C[i, k]
end
end
end
return C
end
# Estimate the Frobenius norm of the Toeplitz-plus-Hankel matrix by working with the symbols.
function normest(A::ToeplitzPlusHankel{T}) where T
m, n = size(A)
tc = A.tc
tr = A.tr
h = A.h
ret1 = zero(T)
ret2 = zero(T)
if m == min(m, n)
for i = 1:m
ret1 += (m+1-i)*abs2(tc[i])
end
for i = 2:n-m
ret1 += m*abs2(tr[i])
end
for i = max(n-m+1, 2):n
ret1 += (n+1-i)*abs2(tr[i])
end
for i = 1:m
ret2 += i*abs2(h[i])
end
for i = m+1:n
ret2 += m*abs2(h[i])
end
for i = n+1:m+n-1
ret2 += (m+n-i)*abs2(h[i])
end
else
for i = 1:n
ret1 += (n+1-i)*abs2(tr[i])
end
for i = 2:m-n
ret1 += n*abs2(tc[i])
end
for i = max(m-n+1, 2):m
ret1 += (m+1-i)*abs2(tc[i])
end
for i = 1:n
ret2 += i*abs2(h[i])
end
for i = n+1:m
ret2 += n*abs2(h[i])
end
for i = m+1:m+n-1
ret2 += (m+n-i)*abs2(h[i])
end
end
sqrt(ret1) + sqrt(ret2)
end
normest(A::Symmetric{T, <: ToeplitzPlusHankel{T}}) where T = normest(parent(A))
normest(A::Hermitian{T, <: ToeplitzPlusHankel{T}}) where T = normest(parent(A))
normest(A::ChebyshevGramMatrix{T}) where T = normest(ToeplitzPlusHankel(A))
================================================
FILE: src/arrays.jl
================================================
struct ArrayPlan{T, FF<:FTPlan{<:T}, Szs<:Tuple, Dims<:Tuple{<:Int}} <: Plan{T}
F::FF
szs::Szs
dims::Dims
end
size(P::ArrayPlan) = P.szs
function ArrayPlan(F::FTPlan{<:T}, c::AbstractArray{T}, dims::Tuple{<:Int}=(1,)) where T
szs = size(c)
@assert F.n == szs[dims[1]]
ArrayPlan(F, size(c), dims)
end
function *(P::ArrayPlan, f::AbstractArray)
F, dims, szs = P.F, P.dims, P.szs
@assert length(dims) == 1
@assert szs == size(f)
d = first(dims)
perm = (d, ntuple(i-> i + (i >= d), ndims(f) -1)...)
fp = permutedims(f, perm)
fr = reshape(fp, size(fp,1), :)
permutedims(reshape(F*fr, size(fp)...), invperm(perm))
end
function \(P::ArrayPlan, f::AbstractArray)
F, dims, szs = P.F, P.dims, P.szs
@assert length(dims) == 1
@assert szs == size(f)
d = first(dims)
perm = (d, ntuple(i-> i + (i >= d), ndims(f) -1)...)
fp = permutedims(f, perm)
fr = reshape(fp, size(fp,1), :)
permutedims(reshape(F\fr, size(fp)...), invperm(perm))
end
struct NDimsPlan{T, FF<:ArrayPlan{<:T}, Szs<:Tuple, Dims<:Tuple} <: Plan{T}
F::FF
szs::Szs
dims::Dims
function NDimsPlan(F, szs, dims)
if length(Set(szs[[dims...]])) > 1
error("Different size in dims axes not yet implemented in N-dimensional transform.")
end
new{eltype(F), typeof(F), typeof(szs), typeof(dims)}(F, szs, dims)
end
end
size(P::NDimsPlan) = P.szs
function NDimsPlan(F::FTPlan, szs::Tuple, dims::Tuple)
NDimsPlan(ArrayPlan(F, szs, (first(dims),)), szs, dims)
end
function *(P::NDimsPlan, f::AbstractArray)
F, dims = P.F, P.dims
@assert size(P) == size(f)
g = copy(f)
t = 1:ndims(g)
d1 = dims[1]
for d in dims
perm = ntuple(k -> k == d1 ? t[d] : k == d ? t[d1] : t[k], ndims(g))
gp = permutedims(g, perm)
g = permutedims(F*gp, invperm(perm))
end
return g
end
function \(P::NDimsPlan, f::AbstractArray)
F, dims = P.F, P.dims
@assert size(P) == size(f)
g = copy(f)
t = 1:ndims(g)
d1 = dims[1]
for d in dims
perm = ntuple(k -> k == d1 ? t[d] : k == d ? t[d1] : t[k], ndims(g))
gp = permutedims(g, perm)
g = permutedims(F\gp, invperm(perm))
end
return g
end
================================================
FILE: src/chebyshevtransform.jl
================================================
## Transforms take values at Chebyshev points of the first and second kinds and produce Chebyshev coefficients
abstract type ChebyshevPlan{T} <: Plan{T} end
*(P::ChebyshevPlan{T}, x::AbstractArray{T}) where T = error("Plan applied to wrong size array")
size(P::ChebyshevPlan) = isdefined(P, :plan) ? size(P.plan) : (0,)
length(P::ChebyshevPlan) = isdefined(P, :plan) ? length(P.plan) : 0
const FIRSTKIND = FFTW.REDFT10
const SECONDKIND = FFTW.REDFT00
struct ChebyshevTransformPlan{T,kind,K,inplace,N,R} <: ChebyshevPlan{T}
plan::FFTW.r2rFFTWPlan{T,K,inplace,N,R}
ChebyshevTransformPlan{T,kind,K,inplace,N,R}(plan) where {T,kind,K,inplace,N,R} = new{T,kind,K,inplace,N,R}(plan)
ChebyshevTransformPlan{T,kind,K,inplace,N,R}() where {T,kind,K,inplace,N,R} = new{T,kind,K,inplace,N,R}()
end
ChebyshevTransformPlan{T,kind}(plan::FFTW.r2rFFTWPlan{T,K,inplace,N,R}) where {T,kind,K,inplace,N,R} =
ChebyshevTransformPlan{T,kind,K,inplace,N,R}(plan)
# jump through some hoops to make inferrable
function plan_chebyshevtransform!(x::AbstractArray{T,N}, ::Val{1}, dims...; kws...) where {T<:fftwNumber,N}
if isempty(x)
ChebyshevTransformPlan{T,1,Vector{Int32},true,N,isempty(dims) ? NTuple{N,Int} : typeof(dims[1])}()
else
ChebyshevTransformPlan{T,1}(FFTW.plan_r2r!(x, FIRSTKIND, dims...; kws...))
end
end
function plan_chebyshevtransform!(x::AbstractArray{T,N}, ::Val{2}, dims...; kws...) where {T<:fftwNumber,N}
any(≤(1),size(x)) && throw(ArgumentError("Array must contain at least 2 entries"))
ChebyshevTransformPlan{T,2}(FFTW.plan_r2r!(x, SECONDKIND, dims...; kws...))
end
function plan_chebyshevtransform(x::AbstractArray{T,N}, ::Val{1}, dims...; kws...) where {T<:fftwNumber,N}
if isempty(x)
ChebyshevTransformPlan{T,1,Vector{Int32},false,N,isempty(dims) ? NTuple{N,Int} : typeof(dims[1])}()
else
ChebyshevTransformPlan{T,1}(FFTW.plan_r2r(x, FIRSTKIND, dims...; kws...))
end
end
function plan_chebyshevtransform(x::AbstractArray{T,N}, ::Val{2}, dims...; kws...) where {T<:fftwNumber,N}
any(≤(1),size(x)) && throw(ArgumentError("Array must contain at least 2 entries"))
ChebyshevTransformPlan{T,2}(FFTW.plan_r2r(x, SECONDKIND, dims...; kws...))
end
# convert x if necessary
_maybemutablecopy(x::StridedArray{T}, ::Type{T}) where {T} = x
_maybemutablecopy(x, T) = Array{T}(x)
@inline _plan_mul!(y::AbstractArray{T}, P::Plan{T}, x::AbstractArray) where T = mul!(y, P, _maybemutablecopy(x, T))
function applydim!(op!, X::AbstractArray, Rpre, Rpost, ind)
for Ipost in Rpost, Ipre in Rpre
v = view(X, Ipre, ind, Ipost)
op!(v)
end
X
end
function applydim!(op!, X::AbstractArray, d::Integer, ind)
Rpre = CartesianIndices(axes(X)[1:d-1])
Rpost = CartesianIndices(axes(X)[d+1:end])
applydim!(op!, X, Rpre, Rpost, ind)
end
for op in (:ldiv, :lmul)
op_dim_begin! = Symbol(op, :_dim_begin!)
op_dim_end! = Symbol(op, :_dim_end!)
op! = Symbol(op, :!)
@eval begin
function $op_dim_begin!(α, d::Number, y::AbstractArray)
# scale just the d-th dimension by permuting it to the first
d ∈ 1:ndims(y) || throw(ArgumentError("dimension $d must lie between 1 and $(ndims(y))"))
applydim!(v -> $op!(α, v), y, d, 1)
end
function $op_dim_end!(α, d::Number, y::AbstractArray)
# scale just the d-th dimension by permuting it to the first
d ∈ 1:ndims(y) || throw(ArgumentError("dimension $d must lie between 1 and $(ndims(y))"))
applydim!(v -> $op!(α, v), y, d, size(y, d))
end
end
end
@inline function _cheb1_rescale!(d::Number, y::AbstractArray)
ldiv_dim_begin!(2, d, y)
ldiv!(size(y,d), y)
end
function _prod_size(sz, d)
ret = 1
for k in d
ret *= sz[k]
end
ret
end
@inline function _cheb1_rescale!(d, y::AbstractArray)
for k in d
ldiv_dim_begin!(2, k, y)
end
ldiv!(_prod_size(size(y), d), y)
end
function *(P::ChebyshevTransformPlan{T,1,K,true,N}, x::AbstractArray{T,N}) where {T,K,N}
isempty(x) && return x
y = P.plan*x # will be === x if in-place
_cheb1_rescale!(P.plan.region, y)
end
function mul!(y::AbstractArray{T,N}, P::ChebyshevTransformPlan{T,1,K,false,N}, x::AbstractArray{<:Any,N}) where {T,K,N}
size(y) == size(x) || throw(DimensionMismatch("output must match dimension"))
isempty(x) && return y
_plan_mul!(y, P.plan, x)
_cheb1_rescale!(P.plan.region, y)
end
function _cheb2_rescale!(d::Number, y::AbstractArray)
ldiv_dim_begin!(2, d, y)
ldiv_dim_end!(2, d, y)
ldiv!(size(y,d)-1, y)
end
# TODO: higher dimensional arrays
function _cheb2_rescale!(d, y::AbstractArray)
for k in d
ldiv_dim_begin!(2, k, y)
ldiv_dim_end!(2, k, y)
end
ldiv!(_prod_size(size(y) .- 1, d), y)
end
function *(P::ChebyshevTransformPlan{T,2,K,true,N}, x::AbstractArray{T,N}) where {T,K,N}
n = length(x)
y = P.plan*x # will be === x if in-place
_cheb2_rescale!(P.plan.region, y)
end
function mul!(y::AbstractArray{T,N}, P::ChebyshevTransformPlan{T,2,K,false,N}, x::AbstractArray{<:Any,N}) where {T,K,N}
n = length(x)
length(y) == n || throw(DimensionMismatch("output must match dimension"))
_plan_mul!(y, P.plan, x)
_cheb2_rescale!(P.plan.region, y)
end
*(P::ChebyshevTransformPlan{T,kind,K,false,N}, x::AbstractArray{T,N}) where {T,kind,K,N} =
mul!(similar(x), P, x)
"""
chebyshevtransform!(x, kind=Val(1))
transforms from values on a Chebyshev grid of the first or second kind to Chebyshev
coefficients, in-place
"""
chebyshevtransform!(x, dims...; kws...) = plan_chebyshevtransform!(x, dims...; kws...)*x
"""
chebyshevtransform(x, kind=Val(1))
transforms from values on a Chebyshev grid of the first or second kind to Chebyshev
coefficients.
"""
chebyshevtransform(x, dims...; kws...) = plan_chebyshevtransform(x, dims...; kws...) * x
## Inverse transforms take Chebyshev coefficients and produce values at Chebyshev points of the first and second kinds
const IFIRSTKIND = FFTW.REDFT01
struct IChebyshevTransformPlan{T,kind,K,inplace,N,R} <: ChebyshevPlan{T}
plan::FFTW.r2rFFTWPlan{T,K,inplace,N,R}
IChebyshevTransformPlan{T,kind,K,inplace,N,R}(plan) where {T,kind,K,inplace,N,R} = new{T,kind,K,inplace,N,R}(plan)
IChebyshevTransformPlan{T,kind,K,inplace,N,R}() where {T,kind,K,inplace,N,R} = new{T,kind,K,inplace,N,R}()
end
IChebyshevTransformPlan{T,kind}(F::FFTW.r2rFFTWPlan{T,K,inplace,N,R}) where {T,kind,K,inplace,N,R} =
IChebyshevTransformPlan{T,kind,K,inplace,N,R}(F)
# second kind Chebyshev transforms share a plan with their inverse
# so we support this via inv
inv(P::ChebyshevTransformPlan{T,2}) where {T} = IChebyshevTransformPlan{T,2}(P.plan)
inv(P::IChebyshevTransformPlan{T,2}) where {T} = ChebyshevTransformPlan{T,2}(P.plan)
inv(P::ChebyshevTransformPlan{T,1}) where {T} = IChebyshevTransformPlan{T,1}(inv(P.plan).p)
inv(P::IChebyshevTransformPlan{T,1}) where {T} = ChebyshevTransformPlan{T,1}(inv(P.plan).p)
\(P::ChebyshevTransformPlan, x::AbstractArray) = inv(P) * x
\(P::IChebyshevTransformPlan, x::AbstractArray) = inv(P) * x
function plan_ichebyshevtransform!(x::AbstractArray{T,N}, ::Val{1}, dims...; kws...) where {T<:fftwNumber,N}
if isempty(x)
IChebyshevTransformPlan{T,1,Vector{Int32},true,N,isempty(dims) ? NTuple{N,Int} : typeof(dims[1])}()
else
IChebyshevTransformPlan{T,1}(FFTW.plan_r2r!(x, IFIRSTKIND, dims...; kws...))
end
end
function plan_ichebyshevtransform!(x::AbstractArray{T}, ::Val{2}, dims...; kws...) where T<:fftwNumber
inv(plan_chebyshevtransform!(x, Val(2), dims...; kws...))
end
function plan_ichebyshevtransform(x::AbstractArray{T,N}, ::Val{1}, dims...; kws...) where {T<:fftwNumber,N}
if isempty(x)
IChebyshevTransformPlan{T,1,Vector{Int32},false,N,isempty(dims) ? NTuple{N,Int} : typeof(dims[1])}()
else
IChebyshevTransformPlan{T,1}(FFTW.plan_r2r(x, IFIRSTKIND, dims...; kws...))
end
end
function plan_ichebyshevtransform(x::AbstractArray{T}, ::Val{2}, dims...; kws...) where T<:fftwNumber
inv(plan_chebyshevtransform(x, Val(2), dims...; kws...))
end
@inline function _icheb1_prescale!(d::Number, x::AbstractArray)
lmul_dim_begin!(2, d, x)
x
end
@inline function _icheb1_prescale!(d, x::AbstractArray)
for k in d
_icheb1_prescale!(k, x)
end
x
end
@inline function _icheb1_postscale!(d::Number, x::AbstractArray)
ldiv_dim_begin!(2, d, x)
x
end
@inline function _icheb1_postscale!(d, x::AbstractArray)
for k in d
_icheb1_postscale!(k, x)
end
x
end
function *(P::IChebyshevTransformPlan{T,1,K,true,N}, x::AbstractArray{T,N}) where {T<:fftwNumber,K,N}
n = length(x)
n == 0 && return x
_icheb1_prescale!(P.plan.region, x)
x = ldiv!(2^length(P.plan.region), P.plan*x)
x
end
function mul!(y::AbstractArray{T,N}, P::IChebyshevTransformPlan{T,1,K,false,N}, x::AbstractArray{T,N}) where {T<:fftwNumber,K,N}
size(y) == size(x) || throw(DimensionMismatch("output must match dimension"))
isempty(x) && return y
_icheb1_prescale!(P.plan.region, x) # TODO: don't mutate x
_plan_mul!(y, P.plan, x)
_icheb1_postscale!(P.plan.region, x)
ldiv!(2^length(P.plan.region), y)
end
@inline function _icheb2_prescale!(d::Number, x::AbstractArray)
lmul_dim_begin!(2, d, x)
lmul_dim_end!(2, d, x)
x
end
@inline function _icheb2_prescale!(d, x::AbstractArray)
for k in d
_icheb2_prescale!(k, x)
end
x
end
@inline function _icheb2_postrescale!(d::Number, x::AbstractArray)
ldiv_dim_begin!(2, d, x)
ldiv_dim_end!(2, d, x)
x
end
@inline function _icheb2_postrescale!(d, x::AbstractArray)
for k in d
_icheb2_postrescale!(k, x)
end
x
end
@inline function _icheb2_rescale!(d::Number, y::AbstractArray{T}) where T
_icheb2_prescale!(d, y)
lmul!(convert(T, size(y,d) - 1)/2, y)
y
end
@inline function _icheb2_rescale!(d, y::AbstractArray{T}) where T
_icheb2_prescale!(d, y)
lmul!(_prod_size(convert.(T, size(y) .- 1)./2, d), y)
y
end
function *(P::IChebyshevTransformPlan{T,2,K,true,N}, x::AbstractArray{T,N}) where {T<:fftwNumber,K,N}
n = length(x)
_icheb2_prescale!(P.plan.region, x)
x = inv(P)*x
_icheb2_rescale!(P.plan.region, x)
end
function mul!(y::AbstractArray{T,N}, P::IChebyshevTransformPlan{T,2,K,false,N}, x::AbstractArray{<:Any,N}) where {T<:fftwNumber,K,N}
n = length(x)
length(y) == n || throw(DimensionMismatch("output must match dimension"))
_icheb2_prescale!(P.plan.region, x)
_plan_mul!(y, inv(P), x)
_icheb2_postrescale!(P.plan.region, x)
_icheb2_rescale!(P.plan.region, y)
end
*(P::IChebyshevTransformPlan{T,kind,K,false,N}, x::AbstractArray{T,N}) where {T,kind,K,N} =
mul!(similar(x), P, _maybemutablecopy(x, T))
ichebyshevtransform!(x::AbstractArray, dims...; kwds...) = plan_ichebyshevtransform!(x, dims...; kwds...)*x
ichebyshevtransform(x, dims...; kwds...) = plan_ichebyshevtransform(x, dims...; kwds...)*x
#######
# Chebyshev U
#######
const UFIRSTKIND = FFTW.RODFT10
const USECONDKIND = FFTW.RODFT00
struct ChebyshevUTransformPlan{T,kind,K,inplace,N,R} <: ChebyshevPlan{T}
plan::FFTW.r2rFFTWPlan{T,K,inplace,N,R}
ChebyshevUTransformPlan{T,kind,K,inplace,N,R}(plan) where {T,kind,K,inplace,N,R} = new{T,kind,K,inplace,N,R}(plan)
ChebyshevUTransformPlan{T,kind,K,inplace,N,R}() where {T,kind,K,inplace,N,R} = new{T,kind,K,inplace,N,R}()
end
ChebyshevUTransformPlan{T,kind}(plan::FFTW.r2rFFTWPlan{T,K,inplace,N,R}) where {T,kind,K,inplace,N,R} =
ChebyshevUTransformPlan{T,kind,K,inplace,N,R}(plan)
function plan_chebyshevutransform!(x::AbstractArray{T,N}, ::Val{1}, dims...; kws...) where {T<:fftwNumber,N}
if isempty(x)
ChebyshevUTransformPlan{T,1,Vector{Int32},true,N,isempty(dims) ? NTuple{N,Int} : typeof(dims[1])}()
else
ChebyshevUTransformPlan{T,1}(FFTW.plan_r2r!(x, UFIRSTKIND, dims...; kws...))
end
end
function plan_chebyshevutransform!(x::AbstractArray{T,N}, ::Val{2}, dims...; kws...) where {T<:fftwNumber,N}
any(≤(1),size(x)) && throw(ArgumentError("Array must contain at least 2 entries"))
ChebyshevUTransformPlan{T,2}(FFTW.plan_r2r!(x, USECONDKIND, dims...; kws...))
end
function plan_chebyshevutransform(x::AbstractArray{T,N}, ::Val{1}, dims...; kws...) where {T<:fftwNumber,N}
if isempty(x)
ChebyshevUTransformPlan{T,1,Vector{Int32},false,N,isempty(dims) ? NTuple{N,Int} : typeof(dims[1])}()
else
ChebyshevUTransformPlan{T,1}(FFTW.plan_r2r(x, UFIRSTKIND, dims...; kws...))
end
end
function plan_chebyshevutransform(x::AbstractArray{T,N}, ::Val{2}, dims...; kws...) where {T<:fftwNumber,N}
if isempty(dims)
any(≤(1), size(x)) && throw(ArgumentError("Array must contain at least 2 entries"))
else
for d in dims[1]
size(x,d) ≤ 1 && throw(ArgumentError("Array must contain at least 2 entries"))
end
end
ChebyshevUTransformPlan{T,2}(FFTW.plan_r2r(x, USECONDKIND, dims...; kws...))
end
for f in [:_chebu1_prescale!, :_chebu1_postscale!, :_chebu2_prescale!, :_chebu2_postscale!,
:_ichebu1_postscale!]
_f = Symbol(:_, f)
@eval begin
@inline function $f(d::Number, X::AbstractArray)
d ∈ 1:ndims(X) || throw("dimension $d must lie between 1 and $(ndims(X))")
$_f(d, X)
X
end
@inline function $f(d, y::AbstractArray)
for k in d
$f(k, y)
end
y
end
end
end
function __chebu1_prescale!(d::Number, X::AbstractArray{T}) where {T}
m = size(X,d)
r = one(T)/(2m) .+ ((1:m) .- one(T))./m
applydim!(v -> v .*= sinpi.(r) ./ m, X, d, :)
end
@inline function __chebu1_postscale!(d::Number, X::AbstractArray{T}) where {T}
m = size(X,d)
r = one(T)/(2m) .+ ((1:m) .- one(T))./m
applydim!(v -> v ./= sinpi.(r) ./ m, X, d, :)
end
function *(P::ChebyshevUTransformPlan{T,1,K,true,N}, x::AbstractArray{T,N}) where {T,K,N}
length(x) ≤ 1 && return x
_chebu1_prescale!(P.plan.region, x)
P.plan * x
end
function mul!(y::AbstractArray{T}, P::ChebyshevUTransformPlan{T,1,K,false}, x::AbstractArray{T}) where {T,K}
size(y) == size(x) || throw(DimensionMismatch("output must match dimension"))
isempty(x) && return y
_chebu1_prescale!(P.plan.region, x) # Todo don't mutate x
_plan_mul!(y, P.plan, x)
_chebu1_postscale!(P.plan.region, x)
for d in P.plan.region
size(y,d) == 1 && ldiv!(2, y) # fix doubling
end
y
end
@inline function __chebu2_prescale!(d, X::AbstractArray{T}) where {T}
m = size(X,d)
c = one(T)/ (m+1)
r = (1:m) .* c
applydim!(v -> v .*= sinpi.(r), X, d, :)
end
@inline function __chebu2_postscale!(d::Number, X::AbstractArray{T}) where {T}
m = size(X,d)
c = one(T)/ (m+1)
r = (1:m) .* c
applydim!(v -> v ./= sinpi.(r), X, d, :)
end
function *(P::ChebyshevUTransformPlan{T,2,K,true,N}, x::AbstractArray{T,N}) where {T,K,N}
sc = one(T)
for d in P.plan.region
sc *= one(T)/(size(x,d)+1)
end
_chebu2_prescale!(P.plan.region, x)
lmul!(sc, P.plan * x)
end
function mul!(y::AbstractArray{T}, P::ChebyshevUTransformPlan{T,2,K,false}, x::AbstractArray{T}) where {T,K}
sc = one(T)
for d in P.plan.region
sc *= one(T)/(size(x,d)+1)
end
_chebu2_prescale!(P.plan.region, x) # TODO don't mutate x
_plan_mul!(y, P.plan, x)
_chebu2_postscale!(P.plan.region, x)
lmul!(sc, y)
end
*(P::ChebyshevUTransformPlan{T,kind,K,false,N}, x::AbstractArray{T,N}) where {T,kind,K,N} =
mul!(similar(x), P, x)
chebyshevutransform!(x::AbstractArray{T}, dims...; kws...) where {T<:fftwNumber} =
plan_chebyshevutransform!(x, dims...; kws...)*x
"""
chebyshevutransform(x, ::Val{kind}=Val(1))
transforms from values on a Chebyshev grid of the first or second kind to Chebyshev
coefficients of the 2nd kind (Chebyshev U expansion).
"""
chebyshevutransform(x, dims...; kws...) = plan_chebyshevutransform(x, dims...; kws...)*x
## Inverse transforms take ChebyshevU coefficients and produce values at ChebyshevU points of the first and second kinds
const IUFIRSTKIND = FFTW.RODFT01
struct IChebyshevUTransformPlan{T,kind,K,inplace,N,R} <: ChebyshevPlan{T}
plan::FFTW.r2rFFTWPlan{T,K,inplace,N,R}
IChebyshevUTransformPlan{T,kind,K,inplace,N,R}(plan) where {T,kind,K,inplace,N,R} = new{T,kind,K,inplace,N,R}(plan)
IChebyshevUTransformPlan{T,kind,K,inplace,N,R}() where {T,kind,K,inplace,N,R} = new{T,kind,K,inplace,N,R}()
end
IChebyshevUTransformPlan{T,kind}(F::FFTW.r2rFFTWPlan{T,K,inplace,N,R}) where {T,kind,K,inplace,N,R} =
IChebyshevUTransformPlan{T,kind,K,inplace,N,R}(F)
function plan_ichebyshevutransform!(x::AbstractArray{T,N}, ::Val{1}, dims...; kws...) where {T<:fftwNumber,N}
if isempty(x)
IChebyshevUTransformPlan{T,1,Vector{Int32},true,N,isempty(dims) ? NTuple{N,Int} : typeof(dims[1])}()
else
IChebyshevUTransformPlan{T,1}(FFTW.plan_r2r!(x, IUFIRSTKIND, dims...; kws...))
end
end
function plan_ichebyshevutransform!(x::AbstractArray{T,N}, ::Val{2}, dims...; kws...) where {T<:fftwNumber,N}
any(≤(1),size(x)) && throw(ArgumentError("Array must contain at least 2 entries"))
IChebyshevUTransformPlan{T,2}(FFTW.plan_r2r!(x, USECONDKIND, dims...))
end
function plan_ichebyshevutransform(x::AbstractArray{T,N}, ::Val{1}, dims...; kws...) where {T<:fftwNumber,N}
if isempty(x)
IChebyshevUTransformPlan{T,1,Vector{Int32},false,N,isempty(dims) ? NTuple{N,Int} : typeof(dims[1])}()
else
IChebyshevUTransformPlan{T,1}(FFTW.plan_r2r(x, IUFIRSTKIND, dims...; kws...))
end
end
function plan_ichebyshevutransform(x::AbstractArray{T,N}, ::Val{2}, dims...; kws...) where {T<:fftwNumber,N}
any(≤(1),size(x)) && throw(ArgumentError("Array must contain at least 2 entries"))
IChebyshevUTransformPlan{T,2}(FFTW.plan_r2r(x, USECONDKIND, dims...; kws...))
end
# second kind Chebyshev transforms share a plan with their inverse
# so we support this via inv
inv(P::ChebyshevUTransformPlan{T,2}) where {T} = IChebyshevUTransformPlan{T,2}(P.plan)
inv(P::IChebyshevUTransformPlan{T,2}) where {T} = ChebyshevUTransformPlan{T,2}(P.plan)
inv(P::ChebyshevUTransformPlan{T,1}) where {T} = IChebyshevUTransformPlan{T,1}(inv(P.plan).p)
inv(P::IChebyshevUTransformPlan{T,1}) where {T} = ChebyshevUTransformPlan{T,1}(inv(P.plan).p)
@inline function __ichebu1_postscale!(d::Number, X::AbstractArray{T}) where {T}
m = size(X,d)
r = one(T)/(2m) .+ ((1:m) .- one(T))/m
applydim!(v -> v ./= 2 .* sinpi.(r), X, d, :)
end
function *(P::IChebyshevUTransformPlan{T,1,K,true}, x::AbstractArray{T}) where {T<:fftwNumber,K}
length(x) ≤ 1 && return x
x = P.plan * x
_ichebu1_postscale!(P.plan.region, x)
end
function mul!(y::AbstractArray{T}, P::IChebyshevUTransformPlan{T,1,K,false}, x::AbstractArray{T}) where {T<:fftwNumber,K}
size(y) == size(x) || throw(DimensionMismatch("output must match dimension"))
isempty(x) && return y
_plan_mul!(y, P.plan, x)
_ichebu1_postscale!(P.plan.region, y)
for d in P.plan.region
size(y,d) == 1 && lmul!(2, y) # fix doubling
end
y
end
function _ichebu2_rescale!(d::Number, x::AbstractArray{T}) where T
_chebu2_postscale!(d, x)
ldiv!(2, x)
x
end
@inline function _ichebu2_rescale!(d, y::AbstractArray)
for k in d
_ichebu2_rescale!(k, y)
end
y
end
function *(P::IChebyshevUTransformPlan{T,2,K,true}, x::AbstractArray{T}) where {T<:fftwNumber,K}
n = length(x)
n ≤ 1 && return x
x = P.plan * x
_ichebu2_rescale!(P.plan.region, x)
end
function mul!(y::AbstractArray{T}, P::IChebyshevUTransformPlan{T,2,K,false}, x::AbstractArray{T}) where {T<:fftwNumber,K}
size(y) == size(x) || throw(DimensionMismatch("output must match dimension"))
length(x) ≤ 1 && return x
_plan_mul!(y, P.plan, x)
_ichebu2_rescale!(P.plan.region, y)
end
ichebyshevutransform!(x::AbstractArray{T}, dims...; kwds...) where {T<:fftwNumber} =
plan_ichebyshevutransform!(x, dims...; kwds...)*x
ichebyshevutransform(x, dims...; kwds...) = plan_ichebyshevutransform(x, dims...; kwds...)*x
*(P::IChebyshevUTransformPlan{T,k,K,false,N}, x::AbstractArray{T,N}) where {T,k,K,N} =
mul!(similar(x), P, x)
## Code generation for integer inputs
for func in (:chebyshevtransform,:ichebyshevtransform,:chebyshevutransform,:ichebyshevutransform)
@eval $func(x::AbstractVector{T}, dims...; kwds...) where {T<:Integer} = $func(convert(AbstractVector{float(T)},x), dims...; kwds...)
end
## points
struct ChebyshevGrid{kind,T} <: AbstractVector{T}
n::Int
function ChebyshevGrid{1,T}(n::Int) where T
n ≥ 0 || throw(ArgumentError("Number of points must be nonnehative"))
new{1,T}(n)
end
function ChebyshevGrid{2,T}(n::Int) where T
n ≥ 2 || throw(ArgumentError("Number of points must be greater than 2"))
new{2,T}(n)
end
end
ChebyshevGrid{kind}(n::Integer) where kind = ChebyshevGrid{kind,Float64}(n)
size(g::ChebyshevGrid) = (g.n,)
getindex(g::ChebyshevGrid{1,T}, k::Integer) where T =
sinpi(convert(T,g.n-2k+1)/(2g.n))
getindex(g::ChebyshevGrid{2,T}, k::Integer) where T =
sinpi(convert(T,g.n-2k+1)/(2g.n-2))
chebyshevpoints(::Type{T}, n::Integer, ::Val{kind}) where {T<:Number,kind} = ChebyshevGrid{kind,T}(n)
chebyshevpoints(::Type{T}, n::Integer) where T = chebyshevpoints(T, n, Val(1))
chebyshevpoints(n::Integer, kind=Val(1)) = chebyshevpoints(Float64, n, kind)
# sin(nθ) coefficients to values at Clenshaw-Curtis nodes except ±1
#
# struct DSTPlan{T,kind,inplace,P} <: Plan{T}
# plan::P
# end
#
# DSTPlan{k,inp}(plan) where {k,inp} =
# DSTPlan{eltype(plan),k,inp,typeof(plan)}(plan)
#
#
# plan_DSTI!(x) = length(x) > 0 ? DSTPlan{1,true}(FFTW.FFTW.plan_r2r!(x, FFTW.FFTW.RODFT00)) :
# fill(one(T),1,length(x))
#
# function *(P::DSTPlan{T,1}, x::AbstractArray) where {T}
# x = P.plan*x
# rmul!(x,half(T))
# end
###
# BigFloat
# Use `Nothing` and fall back to FFT
###
plan_chebyshevtransform(x::AbstractArray{T,N}, ::Val{kind}, dims...; kws...) where {T,N,kind} =
ChebyshevTransformPlan{T,kind,Nothing,false,N,UnitRange{Int}}()
plan_ichebyshevtransform(x::AbstractArray{T,N}, ::Val{kind}, dims...; kws...) where {T,N,kind} =
IChebyshevTransformPlan{T,kind,Nothing,false,N,UnitRange{Int}}()
plan_chebyshevtransform!(x::AbstractArray{T,N}, ::Val{kind}, dims...; kws...) where {T,N,kind} =
ChebyshevTransformPlan{T,kind,Nothing,true,N,UnitRange{Int}}()
plan_ichebyshevtransform!(x::AbstractArray{T,N}, ::Val{kind}, dims...; kws...) where {T,N,kind} =
IChebyshevTransformPlan{T,kind,Nothing,true,N,UnitRange{Int}}()
#following Chebfun's @Chebtech1/vals2coeffs.m and @Chebtech2/vals2coeffs.m
function *(P::ChebyshevTransformPlan{T,1,Nothing,false}, x::AbstractVector{T}) where T
n = length(x)
if n == 1
x
else
w = [2exp(im*convert(T,π)*k/2n) for k=0:n-1]
ret = w.*ifft([x;reverse(x)])[1:n]
ret = T<:Real ? real(ret) : ret
ret[1] /= 2
ret
end
end
# function *(P::ChebyshevTransformPlan{T,1,K,Nothing,false}, x::AbstractVector{T}) where {T,K}
# n = length(x)
# if n == 1
# x
# else
# ret = ifft([x;x[end:-1:2]])[1:n]
# ret = T<:Real ? real(ret) : ret
# ret[2:n-1] *= 2
# ret
# end
# end
*(P::ChebyshevTransformPlan{T,1,Nothing,true,N,R}, x::AbstractVector{T}) where {T,N,R} =
copyto!(x, ChebyshevTransformPlan{T,1,Nothing,false,N,R}() * x)
# *(P::ChebyshevTransformPlan{T,2,true,Nothing}, x::AbstractVector{T}) where T =
# copyto!(x, ChebyshevTransformPlan{T,2,false,Nothing}() * x)
#following Chebfun's @Chebtech1/vals2coeffs.m and @Chebtech2/vals2coeffs.m
function *(P::IChebyshevTransformPlan{T,1,Nothing,false}, x::AbstractVector{T}) where T
n = length(x)
if n == 1
x
else
w = [exp(-im*convert(T,π)*k/2n)/2 for k=0:2n-1]
w[1] *= 2;w[n+1] *= 0;w[n+2:end] *= -1
ret = fft(w.*[x;one(T);x[end:-1:2]])
ret = T<:Real ? real(ret) : ret
ret[1:n]
end
end
# function *(P::IChebyshevTransformPlan{T,2,K,Nothing,true}, x::AbstractVector{T}) where {T,K}
# n = length(x)
# if n == 1
# x
# else
# x[1] *= 2; x[end] *= 2
# chebyshevtransform!(x, Val(2))
# x[1] *= 2; x[end] *= 2
# lmul!(convert(T,n-1)/2, x)
# x
# end
# end
*(P::IChebyshevTransformPlan{T,1,Nothing,true,N,R}, x::AbstractVector{T}) where {T,N,R} =
copyto!(x, IChebyshevTransformPlan{T,1,Nothing,false,N,R}() * x)
# *(P::IChebyshevTransformPlan{T,SECONDKIND,false,Nothing}, x::AbstractVector{T}) where T =
# IChebyshevTransformPlan{T,SECONDKIND,true,Nothing}() * copy(x)
for pln in (:plan_chebyshevtransform!, :plan_chebyshevtransform,
:plan_chebyshevutransform!, :plan_chebyshevutransform,
:plan_ichebyshevutransform, :plan_ichebyshevutransform!,
:plan_ichebyshevtransform, :plan_ichebyshevtransform!)
@eval begin
$pln(x::AbstractArray, dims...; kws...) = $pln(x, Val(1), dims...; kws...)
$pln(::Type{T}, szs, dims...; kwds...) where T = $pln(Array{T}(undef, szs...), dims...; kwds...)
end
end
================================================
FILE: src/clenshawcurtis.jl
================================================
plan_clenshawcurtis(μ) = length(μ) > 1 ? FFTW.plan_r2r!(μ, FFTW.REDFT00) : fill!(similar(μ),1)'
"""
Compute nodes of the Clenshaw—Curtis quadrature rule.
"""
clenshawcurtisnodes(::Type{T}, N::Int) where T = chebyshevpoints(T, N, Val(2))
"""
Compute weights of the Clenshaw—Curtis quadrature rule with modified Chebyshev moments of the first kind ``\\mu``.
"""
clenshawcurtisweights(μ::Vector) = clenshawcurtisweights!(copy(μ))
clenshawcurtisweights!(μ::Vector) = clenshawcurtisweights!(μ, plan_clenshawcurtis(μ))
function clenshawcurtisweights!(μ::Vector{T}, plan) where T
N = length(μ)
rmul!(μ, inv(N-one(T)))
plan*μ
μ[1] *= half(T); μ[N] *= half(T)
return μ
end
================================================
FILE: src/docstrings.jl
================================================
"""
leg2cheb(v::AbstractVector; normleg::Bool=false, normcheb::Bool=false)
Convert the vector of expansions coefficients `v` from a Legendre to a Chebyshev basis.
The keyword arguments denote whether the bases are normalized.
"""
leg2cheb
"""
cheb2leg(v::AbstractVector; normcheb::Bool=false, normleg::Bool=false)
Convert the vector of expansions coefficients `v` from a Chebyshev to a Legendre basis.
The keyword arguments denote whether the bases are normalized.
"""
cheb2leg
"""
ultra2ultra(v::AbstractVector, λ, μ; norm1::Bool=false, norm2::Bool=false)
Convert the vector of expansions coefficients `v` from an Ultraspherical basis of
order `λ` to an Ultraspherical basis of order `μ`.
The keyword arguments denote whether the bases are normalized.
"""
ultra2ultra
"""
jac2jac(v::AbstractVector, α, β, γ, δ; norm1::Bool=false, norm2::Bool=false)
Convert the vector of expansions coefficients `v` from a Jacobi basis of
order `(α,β)` to a Jacobi basis of order `(γ,δ)`.
The keyword arguments denote whether the bases are normalized.
"""
jac2jac
"""
lag2lag(v::AbstractVector, α, β; norm1::Bool=false, norm2::Bool=false)
Convert the vector of expansions coefficients `v` from a Laguerre basis of
order `α` to a La basis of order `β`.
The keyword arguments denote whether the bases are normalized."""
lag2lag
"""
jac2ultra(v::AbstractVector, α, β, λ; normjac::Bool=false, normultra::Bool=false)
Convert the vector of expansions coefficients `v` from a Jacobi basis of
order `(α,β)` to an Ultraspherical basis of order `λ`.
The keyword arguments denote whether the bases are normalized."""
jac2ultra
"""
ultra2jac(v::AbstractVector, λ, α, β; normultra::Bool=false, normjac::Bool=false)
Convert the vector of expansions coefficients `v` from an Ultraspherical basis of
order `λ` to a Jacobi basis of order `(α,β)`.
The keyword arguments denote whether the bases are normalized.
"""
ultra2jac
"""
jac2cheb(v::AbstractVector, α, β; normjac::Bool=false, normcheb::Bool=false)
Convert the vector of expansions coefficients `v` from a Jacobi basis of
order `(α,β)` to a Chebyshev basis.
The keyword arguments denote whether the bases are normalized.
"""
jac2cheb
"""
cheb2jac(v::AbstractVector, α, β; normcheb::Bool=false, normjac::Bool=false)
Convert the vector of expansions coefficients `v` from a Chebyshev basis to a
Jacobi basis of order `(α,β)`.
The keyword arguments denote whether the bases are normalized.
"""
cheb2jac
"""
ultra2cheb(v::AbstractVector, λ; normultra::Bool=false, normcheb::Bool=false)
Convert the vector of expansions coefficients `v` from an Ultraspherical basis of
order `λ` to a Chebyshev basis.
The keyword arguments denote whether the bases are normalized.
"""
ultra2cheb
"""
cheb2ultra(v::AbstractVector, λ; normcheb::Bool=false, normultra::Bool=false)
Convert the vector of expansions coefficients `v` from a Chebyshev basis
to an Ultraspherical basis of order `λ`.
The keyword arguments denote whether the bases are normalized.
"""
cheb2ultra
"""
associatedjac2jac(v::AbstractVector, c::Integer, α, β, γ, δ; norm1::Bool=false, norm2::Bool=false)
Convert the vector of expansions coefficients `v` from an associated Jacobi basis
of orders `(α,β)` to a Jacobi basis of order `(γ,δ)`.
The keyword arguments denote whether the bases are normalized.
"""
associatedjac2jac
"""
modifiedjac2jac(v::AbstractVector{T}, α, β, u::Vector{T}; verbose::Bool=false) where {T}
modifiedjac2jac(v::AbstractVector{T}, α, β, u::Vector{T}, v::Vector{T}; verbose::Bool=false) where {T}
"""
modifiedjac2jac
"""
modifiedlag2lag(v::AbstractVector{T}, α, u::Vector{T}; verbose::Bool=false)
modifiedlag2lag(v::AbstractVector{T}, α, u::Vector{T}, v::Vector{T}; verbose::Bool=false) where {T}
"""
modifiedlag2lag
"""
modifiedherm2herm(v::AbstractVector{T}, u::Vector{T}; verbose::Bool=false)
modifiedherm2herm(v::AbstractVector{T}, u::Vector{T}, v::Vector{T}; verbose::Bool=false) where {T}
"""
modifiedherm2herm
================================================
FILE: src/elliptic.jl
================================================
"""
`FastTransforms` submodule for the computation of some elliptic integrals and functions.
Complete elliptic integrals of the first and second kinds:
```math
K(k) = \\int_0^{\\frac{\\pi}{2}} \\frac{{\\rm d}\\theta}{\\sqrt{1-k^2\\sin^2\\theta}},\\quad{\\rm and},
```
```math
E(k) = \\int_0^{\\frac{\\pi}{2}} \\sqrt{1-k^2\\sin^2\\theta} {\\rm\\,d}\\theta.
```
Jacobian elliptic functions:
```math
x = \\int_0^{\\operatorname{sn}(x,k)} \\frac{{\\rm d}t}{\\sqrt{(1-t^2)(1-k^2t^2)}},
```
```math
x = \\int_{\\operatorname{cn}(x,k)}^1 \\frac{{\\rm d}t}{\\sqrt{(1-t^2)[1-k^2(1-t^2)]}},
```
```math
x = \\int_{\\operatorname{dn}(x,k)}^1 \\frac{{\\rm d}t}{\\sqrt{(1-t^2)(t^2-1+k^2)}},
```
and the remaining nine are defined by:
```math
\\operatorname{pq}(x,k) = \\frac{\\operatorname{pr}(x,k)}{\\operatorname{qr}(x,k)} = \\frac{1}{\\operatorname{qp}(x,k)}.
```
"""
module Elliptic
import FastTransforms: libfasttransforms
export K, E,
sn, cn, dn, ns, nc, nd,
sc, cs, sd, ds, cd, dc
for (fC, elty) in ((:ft_complete_elliptic_integralf, :Float32), (:ft_complete_elliptic_integral, :Float64))
@eval begin
function K(k::$elty)
return ccall(($(string(fC)), libfasttransforms), $elty, (Cint, $elty), '1', k)
end
function E(k::$elty)
return ccall(($(string(fC)), libfasttransforms), $elty, (Cint, $elty), '2', k)
end
end
end
const SN = UInt(1)
const CN = UInt(2)
const DN = UInt(4)
for (fC, elty) in ((:ft_jacobian_elliptic_functionsf, :Float32), (:ft_jacobian_elliptic_functions, :Float64))
@eval begin
function sn(x::$elty, k::$elty)
retsn = Ref{$elty}()
ccall(($(string(fC)), libfasttransforms), Cvoid, ($elty, $elty, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, UInt), x, k, retsn, C_NULL, C_NULL, SN)
retsn[]
end
function cn(x::$elty, k::$elty)
retcn = Ref{$elty}()
ccall(($(string(fC)), libfasttransforms), Cvoid, ($elty, $elty, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, UInt), x, k, C_NULL, retcn, C_NULL, CN)
retcn[]
end
function dn(x::$elty, k::$elty)
retdn = Ref{$elty}()
ccall(($(string(fC)), libfasttransforms), Cvoid, ($elty, $elty, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, UInt), x, k, C_NULL, C_NULL, retdn, DN)
retdn[]
end
function ns(x::$elty, k::$elty)
retsn = Ref{$elty}()
ccall(($(string(fC)), libfasttransforms), Cvoid, ($elty, $elty, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, UInt), x, k, retsn, C_NULL, C_NULL, SN)
inv(retsn[])
end
function nc(x::$elty, k::$elty)
retcn = Ref{$elty}()
ccall(($(string(fC)), libfasttransforms), Cvoid, ($elty, $elty, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, UInt), x, k, C_NULL, retcn, C_NULL, CN)
inv(retcn[])
end
function nd(x::$elty, k::$elty)
retdn = Ref{$elty}()
ccall(($(string(fC)), libfasttransforms), Cvoid, ($elty, $elty, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, UInt), x, k, C_NULL, C_NULL, retdn, DN)
inv(retdn[])
end
function sc(x::$elty, k::$elty)
retsn = Ref{$elty}()
retcn = Ref{$elty}()
ccall(($(string(fC)), libfasttransforms), Cvoid, ($elty, $elty, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, UInt), x, k, retsn, retcn, C_NULL, SN & CN)
retsn[]/retcn[]
end
function cs(x::$elty, k::$elty)
retsn = Ref{$elty}()
retcn = Ref{$elty}()
ccall(($(string(fC)), libfasttransforms), Cvoid, ($elty, $elty, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, UInt), x, k, retsn, retcn, C_NULL, SN & CN)
retcn[]/retsn[]
end
function sd(x::$elty, k::$elty)
retsn = Ref{$elty}()
retdn = Ref{$elty}()
ccall(($(string(fC)), libfasttransforms), Cvoid, ($elty, $elty, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, UInt), x, k, retsn, C_NULL, retdn, SN & DN)
retsn[]/retdn[]
end
function ds(x::$elty, k::$elty)
retsn = Ref{$elty}()
retdn = Ref{$elty}()
ccall(($(string(fC)), libfasttransforms), Cvoid, ($elty, $elty, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, UInt), x, k, retsn, C_NULL, retdn, SN & DN)
retdn[]/retsn[]
end
function cd(x::$elty, k::$elty)
retcn = Ref{$elty}()
retdn = Ref{$elty}()
ccall(($(string(fC)), libfasttransforms), Cvoid, ($elty, $elty, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, UInt), x, k, C_NULL, retcn, retdn, CN & DN)
retcn[]/retdn[]
end
function dc(x::$elty, k::$elty)
retcn = Ref{$elty}()
retdn = Ref{$elty}()
ccall(($(string(fC)), libfasttransforms), Cvoid, ($elty, $elty, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, UInt), x, k, C_NULL, retcn, retdn, CN & DN)
retdn[]/retcn[]
end
end
end
end # module
================================================
FILE: src/fejer.jl
================================================
plan_fejer1(μ) = FFTW.plan_r2r!(μ, FFTW.REDFT01)
"""
Compute nodes of Fejer's first quadrature rule.
"""
fejernodes1(::Type{T}, N::Int) where T = chebyshevpoints(T, N, Val(1))
"""
Compute weights of Fejer's first quadrature rule with modified Chebyshev moments of the first kind ``\\mu``.
"""
fejerweights1(μ::Vector) = fejerweights1!(copy(μ))
fejerweights1!(μ::Vector) = fejerweights1!(μ, plan_fejer1(μ))
function fejerweights1!(μ::Vector{T}, plan) where T
N = length(μ)
rmul!(μ, inv(T(N)))
return plan*μ
end
plan_fejer2(μ) = FFTW.plan_r2r!(μ, FFTW.RODFT00)
"""
Compute nodes of Fejer's second quadrature rule.
"""
fejernodes2(::Type{T}, N::Int) where T = T[sinpi((N-2k-one(T))/(2N+two(T))) for k=0:N-1]
"""
Compute weights of Fejer's second quadrature rule with modified Chebyshev moments of the second kind ``\\mu``.
"""
fejerweights2(μ::Vector) = fejerweights2!(copy(μ))
fejerweights2!(μ::Vector) = fejerweights2!(μ, plan_fejer2(μ))
function fejerweights2!(μ::Vector{T}, plan) where T
N = length(μ)
Np1 = N+one(T)
rmul!(μ, inv(Np1))
plan*μ
@inbounds for i=1:N μ[i] = sinpi(i/Np1)*μ[i] end
return μ
end
================================================
FILE: src/gaunt.jl
================================================
"""
Calculates the Gaunt coefficients, defined by:
```math
a(m,n,\\mu,\\nu,q) = \\frac{2(n+\\nu-2q)+1}{2} \\frac{(n+\\nu-2q-m-\\mu)!}{(n+\\nu-2q+m+\\mu)!} \\int_{-1}^{+1} P_n^m(x) P_\\nu^\\mu(x) P_{n+\\nu-2q}^{m+\\mu}(x) {\\rm\\,d}x.
```
or defined by:
```math
P_n^m(x) P_\\nu^\\mu(x) = \\sum_{q=0}^{q_{\\rm max}} a(m,n,\\mu,\\nu,q) P_{n+\\nu-2q}^{m+\\mu}(x)
```
This is a Julia implementation of the stable recurrence described in:
Y.-l. Xu, Fast evaluation of Gaunt coefficients: recursive approach, *J. Comp. Appl. Math.*, **85**:53–65, 1997.
"""
function gaunt(::Type{T},m::Integer,n::Integer,μ::Integer,ν::Integer;normalized::Bool=false) where T
if normalized
normalizedgaunt(T,m,n,μ,ν)
else
lmul!(normalization(T,m,n,μ,ν),gaunt(T,m,n,μ,ν;normalized=true))
end
end
"""
Calculates the Gaunt coefficients in 64-bit floating-point arithmetic.
"""
gaunt(m::Integer,n::Integer,μ::Integer,ν::Integer;kwds...) = gaunt(Float64,m,n,μ,ν;kwds...)
gaunt(::Type{T},m::Int32,n::Int32,μ::Int32,ν::Int32;normalized::Bool=false) where T =
gaunt(T,Int64(m),Int64(n),Int64(μ),Int64(ν);normalized=normalized)
function normalization(::Type{T},m::Integer,n::Integer,μ::Integer,ν::Integer) where T
pochhammer(n+one(T),n)*pochhammer(ν+one(T),ν)/pochhammer(n+ν+one(T),n+ν)*gamma(n+ν-m-μ+one(T))/gamma(n-m+one(T))/gamma(ν-μ+one(T))
end
normalization(::Type{Float64},m::Integer,n::Integer,μ::Integer,ν::Integer) = normalization1(Float64,n,ν)*normalization2(Float64,n-m,ν-μ)
function normalization1(::Type{Float64},n::Integer,ν::Integer)
if n ≥ 8
if ν ≥ 8
return exp((n+0.5)*log1p(n/(n+1))+(ν+0.5)*log1p(ν/(ν+1))+(n+ν+0.5)*log1p(-(n+ν)/(2n+2ν+1))+n*log1p(-2ν/(2n+2ν+1))+ν*log1p(-2n/(2n+2ν+1)))*stirlingseries(2n+1.0)*stirlingseries(2ν+1.0)*stirlingseries(n+ν+1.0)/stirlingseries(n+1.0)/stirlingseries(ν+1.0)/stirlingseries(2n+2ν+1.0)
else
return pochhammer(ν+1.0,ν)/(2n+2ν+1.0)^ν*exp(ν+(n+0.5)*log1p(n/(n+1))+(n+ν+0.5)*log1p(-(n+ν)/(2n+2ν+1))+n*log1p(-2ν/(2n+2ν+1)))*stirlingseries(2n+1.0)*stirlingseries(n+ν+1.0)/stirlingseries(n+1.0)/stirlingseries(2n+2ν+1.0)
end
elseif ν ≥ 8
return pochhammer(n+1.0,n)/(2n+2ν+1.0)^n*exp(n+(ν+0.5)*log1p(ν/(ν+1))+(n+ν+0.5)*log1p(-(n+ν)/(2n+2ν+1))+ν*log1p(-2n/(2n+2ν+1)))*stirlingseries(2ν+1.0)*stirlingseries(n+ν+1.0)/stirlingseries(ν+1.0)/stirlingseries(2n+2ν+1.0)
else
return pochhammer(n+1.0,n)*pochhammer(ν+1.0,ν)/pochhammer(n+ν+1.0,n+ν)
end
end
function normalization2(::Type{Float64},nm::Integer,νμ::Integer)
if nm ≥ 8
if νμ ≥ 8
return edivsqrt2pi*exp((nm+0.5)*log1p(νμ/(nm+1))+(νμ+0.5)*log1p(nm/(νμ+1)))/sqrt(nm+νμ+1.0)*stirlingseries(nm+νμ+1.0)/stirlingseries(nm+1.0)/stirlingseries(νμ+1.0)
else
return (nm+νμ+1.0)^νμ*exp(-νμ+(nm+0.5)*log1p(νμ/(nm+1)))*stirlingseries(nm+νμ+1.0)/stirlingseries(nm+1.0)/gamma(νμ+1.0)
end
elseif νμ ≥ 8
return (nm+νμ+1.0)^nm*exp(-nm+(νμ+0.5)*log1p(nm/(νμ+1)))*stirlingseries(nm+νμ+1.0)/stirlingseries(νμ+1.0)/gamma(nm+1.0)
else
return gamma(nm+νμ+1.0)/gamma(nm+1.0)/gamma(νμ+1.0)
end
end
function normalizedgaunt(::Type{T},m::Integer,n::Integer,μ::Integer,ν::Integer) where T
qmax = min(n,ν,(n+ν-abs(m+μ))÷2)
a = Vector{T}(undef, qmax+1)
a[1] = one(T)
if μ == m && ν == n # zero class (i) of Aₚ
if μ == m == 0
for q = 1:qmax
p = n+ν-2q
a[q+1] = α(T,n,ν,p+2)/α(T,n,ν,p+1)*a[q]
end
else
for q = 1:qmax
p = n+ν-2q
p₁,p₂ = p-m-μ,p+m+μ
a[q+1] = (p+1)*(p₂+2)*α(T,n,ν,p+2)/(p+2)/(p₁+1)/α(T,n,ν,p+1)*a[q]
end
end
else
qmax > 0 && (a[2] = secondinitialcondition(T,m,n,μ,ν))
q = 2
if qmax > 1
p = n+ν-2q
p₁,p₂ = p-m-μ,p+m+μ
if A(T,m,n,μ,ν,p+4) != 0
a[q+1] = (c₁(T,m,n,μ,ν,p,p₁,p₂)*a[q] + c₂(T,m,n,μ,ν,p,p₂)*a[q-1])/c₀(T,m,n,μ,ν,p,p₁)
else
a[q+1] = thirdinitialcondition(T,m,n,μ,ν)
end
q+=1
end
while q ≤ qmax
p = n+ν-2q
p₁,p₂ = p-m-μ,p+m+μ
if A(T,m,n,μ,ν,p+4) != 0
a[q+1] = (c₁(T,m,n,μ,ν,p,p₁,p₂)*a[q] + c₂(T,m,n,μ,ν,p,p₂)*a[q-1])/c₀(T,m,n,μ,ν,p,p₁)
elseif A(T,m,n,μ,ν,p+6) != 0
a[q+1] = (d₁(T,m,n,μ,ν,p,p₁,p₂)*a[q] + d₂(T,m,n,μ,ν,p,p₁,p₂)*a[q-1] + d₃(T,m,n,μ,ν,p,p₂)*a[q-2])/d₀(T,m,n,μ,ν,p,p₁)
else
a[q+1] = (p+1)*(p₂+2)*α(T,n,ν,p+2)/(p+2)/(p₁+1)/α(T,n,ν,p+1)*a[q]
end
q+=1
end
end
a
end
function secondinitialcondition(::Type{T},m::Integer,n::Integer,μ::Integer,ν::Integer) where T
n₄ = n+ν-m-μ
mn = m-n
μν = μ-ν
temp = 2n+2ν-one(T)
return (temp-2)/2*(1-temp/n₄/(n₄-1)*(mn*(mn+one(T))/(2n-1)+μν*(μν+one(T))/(2ν-1)))
end
function thirdinitialcondition(::Type{T},m::Integer,n::Integer,μ::Integer,ν::Integer) where T
n₄ = n+ν-m-μ
mn = m-n
μν = μ-ν
temp = 2n+2ν-one(T)
temp1 = mn*(mn+one(T))*(mn+2)*(mn+3)/(2n-1)/(2n-3) + 2mn*(mn+one(T))*μν*(μν+one(T))/(2n-1)/(2ν-1) + μν*(μν+one(T))*(μν+2)*(μν+3)/(2ν-1)/(2ν-3)
temp2 = (temp-4)/(2(n₄-2)*(n₄-3))*temp1 - mn*(mn+one(T))/(2n-1)-μν*(μν+one(T))/(2ν-1)
return temp*(temp-6)/4*( (temp-2)/n₄/(n₄-1)*temp2 + one(T)/2 )
end
α(::Type{T},n::Integer,ν::Integer,p::Integer) where T =
(p^2-(n+ν+1)^2)*(p^2-(n-ν)^2)/(4p^2-one(T))
A(::Type{T},m::Integer,n::Integer,μ::Integer,ν::Integer,p::Integer) where T =
p*(p-one(T))*(m-μ)-(m+μ)*(n-ν)*(n+ν+one(T))
c₀(::Type{T},m::Integer,n::Integer,μ::Integer,ν::Integer,p::Integer,p₁::Integer) where T =
(p+2)*(p+3)*(p₁+1)*(p₁+2)*A(T,m,n,μ,ν,p+4)*α(T,n,ν,p+1)
c₁(::Type{T},m::Integer,n::Integer,μ::Integer,ν::Integer,p::Integer,p₁::Integer,p₂::Integer) where T =
A(T,m,n,μ,ν,p+2)*A(T,m,n,μ,ν,p+3)*A(T,m,n,μ,ν,p+4) + (p+1)*(p+3)*(p₁+2)*(p₂+2)*A(T,m,n,μ,ν,p+4)*α(T,n,ν,p+2) + (p+2)*(p+4)*(p₁+3)*(p₂+3)*A(T,m,n,μ,ν,p+2)*α(T,n,ν,p+3)
c₂(::Type{T},m::Integer,n::Integer,μ::Integer,ν::Integer,p::Integer,p₂::Integer) where T =
-(p+2)*(p+3)*(p₂+3)*(p₂+4)*A(T,m,n,μ,ν,p+2)*α(T,n,ν,p+4)
d₀(::Type{T},m::Integer,n::Integer,μ::Integer,ν::Integer,p::Integer,p₁::Integer) where T =
(p+2)*(p+3)*(p+5)*(p₁+2)*(p₁+4)*A(T,m,n,μ,ν,p+6)*α(T,n,ν,p+1)
d₁(::Type{T},m::Integer,n::Integer,μ::Integer,ν::Integer,p::Integer,p₁::Integer,p₂::Integer) where T =
(p+5)*(p₁+4)*A(T,m,n,μ,ν,p+6)*( A(T,m,n,μ,ν,p+2)*A(T,m,n,μ,ν,p+3) + (p+1)*(p+3)*(p₁+2)*(p₂+2)*α(T,n,ν,p+2) )
d₂(::Type{T},m::Integer,n::Integer,μ::Integer,ν::Integer,p::Integer,p₁::Integer,p₂::Integer) where T =
(p+2)*(p₂+3)*A(T,m,n,μ,ν,p+2)*( A(T,m,n,μ,ν,p+5)*A(T,m,n,μ,ν,p+6) + (p+4)*(p+6)*(p₁+5)*(p₂+5)*α(T,n,ν,p+5) )
d₃(::Type{T},m::Integer,n::Integer,μ::Integer,ν::Integer,p::Integer,p₂::Integer) where T =
-(p+2)*(p+4)*(p+5)*(p₂+3)*(p₂+5)*(p₂+6)*A(T,m,n,μ,ν,p+2)*α(T,n,ν,p+6)
================================================
FILE: src/hermite.jl
================================================
# exp(-x^2/2) H_n(x) / sqrt(π*prod(1:n))
struct ForwardWeightedHermitePlan{T}
Vtw::Matrix{T} # vandermonde
end
struct BackwardWeightedHermitePlan{T}
V::Matrix{T} # vandermonde
end
function _weightedhermite_vandermonde(n)
V = Array{Float64}(undef, n, n)
x,w = unweightedgausshermite(n)
for k=1:n
V[k,:] = FastGaussQuadrature.hermpoly_rec(0:n-1, sqrt(2)*x[k])
end
V,w
end
function ForwardWeightedHermitePlan(n::Integer)
V,w = _weightedhermite_vandermonde(n)
ForwardWeightedHermitePlan(V' * Diagonal(w / sqrt(π)))
end
BackwardWeightedHermitePlan(n::Integer) = BackwardWeightedHermitePlan(_weightedhermite_vandermonde(n)[1])
*(P::ForwardWeightedHermitePlan, v::AbstractVector) = P.Vtw*v
*(P::BackwardWeightedHermitePlan, v::AbstractVector) = P.V*v
weightedhermitetransform(v) = ForwardWeightedHermitePlan(length(v))*v
iweightedhermitetransform(v) = BackwardWeightedHermitePlan(length(v))*v
================================================
FILE: src/inufft.jl
================================================
"""
Pre-computes an inverse nonuniform fast Fourier transform of type `N`.
For best performance, choose the right number of threads by `FFTW.set_num_threads(4)`, for example.
"""
struct iNUFFTPlan{N,T,S,PT,TF} <: Plan{T}
pt::PT
TP::TF
r::Vector{T}
p::Vector{T}
Ap::Vector{T}
ϵ::S
end
"""
Pre-computes an inverse nonuniform fast Fourier transform of type I.
"""
function plan_inufft1(ω::AbstractVector{T}, ϵ::T) where T<:AbstractFloat
N = length(ω)
p = plan_nufft1(ω, ϵ)
pt = plan_nufft2(ω/N, ϵ)
c = p*ones(Complex{T}, N)
r = conj(c)
avg = (r[1]+c[1])/2
r[1] = avg
c[1] = avg
TP = factorize(Toeplitz(c, r))
r = zero(c)
p = zero(c)
Ap = zero(c)
iNUFFTPlan{1, eltype(TP), typeof(ϵ), typeof(pt), typeof(TP)}(pt, TP, r, p, Ap, ϵ)
end
"""
Pre-computes an inverse nonuniform fast Fourier transform of type II.
"""
function plan_inufft2(x::AbstractVector{T}, ϵ::T) where T<:AbstractFloat
N = length(x)
pt = plan_nufft1(N*x, ϵ)
r = pt*ones(Complex{T}, N)
c = conj(r)
avg = (r[1]+c[1])/2
r[1] = avg
c[1] = avg
TP = factorize(Toeplitz(c, r))
r = zero(c)
p = zero(c)
Ap = zero(c)
iNUFFTPlan{2, eltype(TP), typeof(ϵ), typeof(pt), typeof(TP)}(pt, TP, r, p, Ap, ϵ)
end
function (*)(p::iNUFFTPlan{N,T}, x::AbstractVector{V}) where {N,T,V}
mul!(zeros(promote_type(T,V), length(x)), p, x)
end
function mul!(c::AbstractVector{T}, P::iNUFFTPlan{1,T}, f::AbstractVector{T}) where T
pt, TP, r, p, Ap, ϵ = P.pt, P.TP, P.r, P.p, P.Ap, P.ϵ
cg_for_inufft(TP, c, f, r, p, Ap, 50, 100ϵ)
conj!(mul!(c, pt, conj!(c)))
end
function mul!(c::AbstractVector{T}, P::iNUFFTPlan{2,T}, f::AbstractVector{T}) where T
pt, TP, r, p, Ap, ϵ = P.pt, P.TP, P.r, P.p, P.Ap, P.ϵ
cg_for_inufft(TP, c, conj!(pt*conj!(f)), r, p, Ap, 50, 100ϵ)
conj!(f)
c
end
"""
Computes an inverse nonuniform fast Fourier transform of type I.
"""
inufft1(c::AbstractVector, ω::AbstractVector{T}, ϵ::T) where {T<:AbstractFloat} = plan_inufft1(ω, ϵ)*c
"""
Computes an inverse nonuniform fast Fourier transform of type II.
"""
inufft2(c::AbstractVector, x::AbstractVector{T}, ϵ::T) where {T<:AbstractFloat} = plan_inufft2(x, ϵ)*c
function cg_for_inufft(A::ToeplitzMatrices.ToeplitzFactorization{T}, x::AbstractVector{T}, b::AbstractVector{T}, r::AbstractVector{T}, p::AbstractVector{T}, Ap::AbstractVector{T}, max_it::Integer, tol::Real) where T
n = length(b)
nrmb = norm(b)
if nrmb == 0 nrmb = one(typeof(nrmb)) end
copyto!(x, b)
fill!(r, zero(T))
fill!(p, zero(T))
fill!(Ap, zero(T))
# r = b - A*x
copyto!(r, b)
mul!(r, A, x, -one(T), one(T))
copyto!(p, r)
nrm2 = r⋅r
for k = 1:max_it
# Ap = A*p
mul!(Ap, A, p)
α = nrm2/(p⋅Ap)
@inbounds @simd for l = 1:n
x[l] += α*p[l]
r[l] -= α*Ap[l]
end
nrm2new = r⋅r
cst = nrm2new/nrm2
@inbounds @simd for l = 1:n
p[l] = muladd(cst, p[l], r[l])
end
nrm2 = nrm2new
if sqrt(abs(nrm2)) ≤ tol*nrmb break end
end
return x
end
================================================
FILE: src/libfasttransforms.jl
================================================
if get(ENV, "FT_BUILD_FROM_SOURCE", "false") == "true"
using Libdl
const libfasttransforms = find_library("libfasttransforms", [joinpath(dirname(@__DIR__), "deps")])
if libfasttransforms ≡ nothing || length(libfasttransforms) == 0
error("FastTransforms is not properly installed. Please run Pkg.build(\"FastTransforms\") ",
"and restart Julia.")
end
else
using FastTransforms_jll
end
ft_set_num_threads(n::Integer) = ccall((:ft_set_num_threads, libfasttransforms), Cvoid, (Cint, ), n)
ft_fftw_plan_with_nthreads(n::Integer) = ccall((:ft_fftw_plan_with_nthreads, libfasttransforms), Cvoid, (Cint, ), n)
function __init__()
n = ceil(Int, Sys.CPU_THREADS/2)
ft_set_num_threads(n)
ccall((:ft_fftw_init_threads, libfasttransforms), Cint, ())
ft_fftw_plan_with_nthreads(n)
end
"""
mpfr_t <: AbstractFloat
A Julia struct that exactly matches `mpfr_t`.
"""
struct mpfr_t <: AbstractFloat
prec::Clong
sign::Cint
exp::Clong
d::Ptr{Limb}
end
"""
`BigFloat` is a mutable struct and there is no guarantee that each entry in an
`AbstractArray{BigFloat}` is unique. For example, looking at the `Limb`s,
Id = Matrix{BigFloat}(I, 3, 3)
map(x->x.d, Id)
shows that the ones and the zeros all share the same pointers. If a C function
assumes unicity of each datum, then the array must be renewed with a `deepcopy`.
"""
function renew!(x::AbstractArray{BigFloat})
for i in eachindex(x)
@inbounds x[i] = deepcopy(x[i])
end
return x
end
function horner!(f::Vector{Float64}, c::StridedVector{Float64}, x::Vector{Float64})
@assert length(x) == length(f)
ccall((:ft_horner, libfasttransforms), Cvoid, (Cint, Ptr{Float64}, Cint, Cint, Ptr{Float64}, Ptr{Float64}), length(c), c, stride(c, 1), length(x), x, f)
f
end
function horner!(f::Vector{Float32}, c::StridedVector{Float32}, x::Vector{Float32})
@assert length(x) == length(f)
ccall((:ft_hornerf, libfasttransforms), Cvoid, (Cint, Ptr{Float32}, Cint, Cint, Ptr{Float32}, Ptr{Float32}), length(c), c, stride(c, 1), length(x), x, f)
f
end
function check_clenshaw_points(x, ϕ₀, f)
length(x) == length(ϕ₀) == length(f) || throw(ArgumentError("Dimensions must match"))
end
function check_clenshaw_points(x, f)
length(x) == length(f) || throw(ArgumentError("Dimensions must match"))
end
function clenshaw!(f::Vector{Float64}, c::StridedVector{Float64}, x::Vector{Float64})
@boundscheck check_clenshaw_points(x, f)
ccall((:ft_clenshaw, libfasttransforms), Cvoid, (Cint, Ptr{Float64}, Cint, Cint, Ptr{Float64}, Ptr{Float64}), length(c), c, stride(c, 1), length(x), x, f)
f
end
function clenshaw!(f::Vector{Float32}, c::StridedVector{Float32}, x::Vector{Float32})
@boundscheck check_clenshaw_points(x, f)
ccall((:ft_clenshawf, libfasttransforms), Cvoid, (Cint, Ptr{Float32}, Cint, Cint, Ptr{Float32}, Ptr{Float32}), length(c), c, stride(c, 1), length(x), x, f)
f
end
function clenshaw!(f::Vector{Float64}, c::StridedVector{Float64}, A::Vector{Float64}, B::Vector{Float64}, C::Vector{Float64}, x::Vector{Float64}, ϕ₀::Vector{Float64})
N = length(c)
@boundscheck check_clenshaw_recurrences(N, A, B, C)
@boundscheck check_clenshaw_points(x, ϕ₀, f)
ccall((:ft_orthogonal_polynomial_clenshaw, libfasttransforms), Cvoid, (Cint, Ptr{Float64}, Cint, Ptr{Float64}, Ptr{Float64}, Ptr{Float64}, Cint, Ptr{Float64}, Ptr{Float64}, Ptr{Float64}), N, c, stride(c, 1), A, B, C, length(x), x, ϕ₀, f)
f
end
function clenshaw!(f::Vector{Float32}, c::StridedVector{Float32}, A::Vector{Float32}, B::Vector{Float32}, C::Vector{Float32}, x::Vector{Float32}, ϕ₀::Vector{Float32})
N = length(c)
@boundscheck check_clenshaw_recurrences(N, A, B, C)
@boundscheck check_clenshaw_points(x, ϕ₀, f)
ccall((:ft_orthogonal_polynomial_clenshawf, libfasttransforms), Cvoid, (Cint, Ptr{Float32}, Cint, Ptr{Float32}, Ptr{Float32}, Ptr{Float32}, Cint, Ptr{Float32}, Ptr{Float32}, Ptr{Float32}), N, c, stride(c, 1), A, B, C, length(x), x, ϕ₀, f)
f
end
@enum Transforms::Cint begin
LEG2CHEB=0
CHEB2LEG
ULTRA2ULTRA
JAC2JAC
LAG2LAG
JAC2ULTRA
ULTRA2JAC
JAC2CHEB
CHEB2JAC
ULTRA2CHEB
CHEB2ULTRA
ASSOCIATEDJAC2JAC
MODIFIEDJAC2JAC
MODIFIEDLAG2LAG
MODIFIEDHERM2HERM
SPHERE
SPHEREV
DISK
ANNULUS
RECTDISK
TRIANGLE
TETRAHEDRON
SPINSPHERE
SPHERESYNTHESIS
SPHEREANALYSIS
SPHEREVSYNTHESIS
SPHEREVANALYSIS
DISKSYNTHESIS
DISKANALYSIS
ANNULUSSYNTHESIS
ANNULUSANALYSIS
RECTDISKSYNTHESIS
RECTDISKANALYSIS
TRIANGLESYNTHESIS
TRIANGLEANALYSIS
TETRAHEDRONSYNTHESIS
TETRAHEDRONANALYSIS
SPINSPHERESYNTHESIS
SPINSPHEREANALYSIS
SPHERICALISOMETRY
end
Transforms(t::Transforms) = t
let k2s = Dict(LEG2CHEB => "Legendre--Chebyshev",
CHEB2LEG => "Chebyshev--Legendre",
ULTRA2ULTRA => "ultraspherical--ultraspherical",
JAC2JAC => "Jacobi--Jacobi",
LAG2LAG => "Laguerre--Laguerre",
JAC2ULTRA => "Jacobi--ultraspherical",
ULTRA2JAC => "ultraspherical--Jacobi",
JAC2CHEB => "Jacobi--Chebyshev",
CHEB2JAC => "Chebyshev--Jacobi",
ULTRA2CHEB => "ultraspherical--Chebyshev",
CHEB2ULTRA => "Chebyshev--ultraspherical",
ASSOCIATEDJAC2JAC => "Associated Jacobi--Jacobi",
MODIFIEDJAC2JAC => "Modified Jacobi--Jacobi",
MODIFIEDLAG2LAG => "Modified Laguerre--Laguerre",
MODIFIEDHERM2HERM => "Modified Hermite--Hermite",
SPHERE => "Spherical harmonic--Fourier",
SPHEREV => "Spherical vector field--Fourier",
DISK => "Zernike--Chebyshev×Fourier",
ANNULUS => "Annulus--Chebyshev×Fourier",
RECTDISK => "Dunkl-Xu--Chebyshev²",
TRIANGLE => "Proriol--Chebyshev²",
TETRAHEDRON => "Proriol--Chebyshev³",
SPINSPHERE => "Spin-weighted spherical harmonic--Fourier",
SPHERESYNTHESIS => "FFTW Fourier synthesis on the sphere",
SPHEREANALYSIS => "FFTW Fourier analysis on the sphere",
SPHEREVSYNTHESIS => "FFTW Fourier synthesis on the sphere (vector field)",
SPHEREVANALYSIS => "FFTW Fourier analysis on the sphere (vector field)",
DISKSYNTHESIS => "FFTW Chebyshev×Fourier synthesis on the disk",
DISKANALYSIS => "FFTW Chebyshev×Fourier analysis on the disk",
ANNULUSSYNTHESIS => "FFTW Chebyshev×Fourier synthesis on the annulus",
ANNULUSANALYSIS => "FFTW Chebyshev×Fourier analysis on the annulus",
RECTDISKSYNTHESIS => "FFTW Chebyshev synthesis on the rectangularized disk",
RECTDISKANALYSIS => "FFTW Chebyshev analysis on the rectangularized disk",
TRIANGLESYNTHESIS => "FFTW Chebyshev synthesis on the triangle",
TRIANGLEANALYSIS => "FFTW Chebyshev analysis on the triangle",
TETRAHEDRONSYNTHESIS => "FFTW Chebyshev synthesis on the tetrahedron",
TETRAHEDRONANALYSIS => "FFTW Chebyshev analysis on the tetrahedron",
SPINSPHERESYNTHESIS => "FFTW Fourier synthesis on the sphere (spin-weighted)",
SPINSPHEREANALYSIS => "FFTW Fourier analysis on the sphere (spin-weighted)",
SPHERICALISOMETRY => "Spherical isometry")
global kind2string
kind2string(k::Union{Integer, Transforms}) = k2s[Transforms(k)]
end
struct ft_plan_struct end
mutable struct FTPlan{T, N, K}
plan::Ptr{ft_plan_struct}
n::Int
l::Int
m::Int
function FTPlan{T, N, K}(plan::Ptr{ft_plan_struct}, n::Int) where {T, N, K}
p = new(plan, n)
finalizer(destroy_plan, p)
p
end
function FTPlan{T, N, K}(plan::Ptr{ft_plan_struct}, n::Int, m::Int) where {T, N, K}
p = new(plan, n, -1, m)
finalizer(destroy_plan, p)
p
end
function FTPlan{T, N, K}(plan::Ptr{ft_plan_struct}, n::Int, l::Int, m::Int) where {T, N, K}
p = new(plan, n, l, m)
finalizer(destroy_plan, p)
p
end
end
eltype(p::FTPlan{T}) where {T} = T
ndims(p::FTPlan{T, N}) where {T, N} = N
show(io::IO, p::FTPlan{T, 1, K}) where {T, K} = print(io, "FastTransforms ", kind2string(K), " plan for $(p.n)-element array of ", T)
show(io::IO, p::FTPlan{T, 2, SPHERE}) where T = print(io, "FastTransforms ", kind2string(SPHERE), " plan for $(p.n)×$(2p.n-1)-element array of ", T)
show(io::IO, p::FTPlan{T, 2, SPHEREV}) where T = print(io, "FastTransforms ", kind2string(SPHEREV), " plan for $(p.n)×$(2p.n-1)-element array of ", T)
show(io::IO, p::FTPlan{T, 2, DISK}) where T = print(io, "FastTransforms ", kind2string(DISK), " plan for $(p.n)×$(4p.n-3)-element array of ", T)
show(io::IO, p::FTPlan{T, 2, ANNULUS}) where T = print(io, "FastTransforms ", kind2string(ANNULUS), " plan for $(p.n)×$(4p.n-3)-element array of ", T)
show(io::IO, p::FTPlan{T, 2, RECTDISK}) where T = print(io, "FastTransforms ", kind2string(RECTDISK), " plan for $(p.n)×$(p.n)-element array of ", T)
show(io::IO, p::FTPlan{T, 2, TRIANGLE}) where T = print(io, "FastTransforms ", kind2string(TRIANGLE), " plan for $(p.n)×$(p.n)-element array of ", T)
show(io::IO, p::FTPlan{T, 3, TETRAHEDRON}) where T = print(io, "FastTransforms ", kind2string(TETRAHEDRON), " plan for $(p.n)×$(p.n)×$(p.n)-element array of ", T)
show(io::IO, p::FTPlan{T, 2, SPINSPHERE}) where T = print(io, "FastTransforms ", kind2string(SPINSPHERE), " plan for $(p.n)×$(2p.n-1)-element array of ", T)
show(io::IO, p::FTPlan{T, 2, K}) where {T, K} = print(io, "FastTransforms plan for ", kind2string(K), " for $(p.n)×$(p.m)-element array of ", T)
show(io::IO, p::FTPlan{T, 3, K}) where {T, K} = print(io, "FastTransforms plan for ", kind2string(K), " for $(p.n)×$(p.l)×$(p.m)-element array of ", T)
show(io::IO, p::FTPlan{T, 2, SPHERICALISOMETRY}) where T = print(io, "FastTransforms ", kind2string(SPHERICALISOMETRY), " plan for $(p.n)×$(2p.n-1)-element array of ", T)
function checksize(p::FTPlan{T, 1}, x::StridedArray{T}) where T
if p.n != size(x, 1)
throw(DimensionMismatch("FTPlan has dimensions $(p.n) × $(p.n), x has leading dimension $(size(x, 1))"))
end
end
function checkstride(p::FTPlan{T, 1}, x::StridedArray{T}) where T
if stride(x, 1) != 1
error("FTPlan requires unit stride in the leading dimension, x has stride $(stride(x, 1)) in the leading dimension.")
end
end
for (N, K) in ((2, RECTDISK), (2, TRIANGLE), (3, TETRAHEDRON))
@eval function checksize(p::FTPlan{T, $N, $K}, x::Array{T, $N}) where T
if p.n != size(x, 1)
throw(DimensionMismatch("FTPlan has dimensions $(p.n) × $(p.n), x has leading dimension $(size(x, 1))"))
end
end
end
for K in (SPHERE, SPHEREV, DISK, ANNULUS, SPINSPHERE)
@eval function checksize(p::FTPlan{T, 2, $K}, x::Matrix{T}) where T
if p.n != size(x, 1)
throw(DimensionMismatch("FTPlan has dimensions $(p.n) × $(p.n), x has leading dimension $(size(x, 1))"))
end
if iseven(size(x, 2))
throw(DimensionMismatch("This FTPlan only operates on arrays with an odd number of columns."))
end
end
end
function checksize(p::FTPlan{T, 2}, x::Array{T, 2}) where T
if p.n != size(x, 1) || p.m != size(x, 2)
throw(DimensionMismatch("FTPlan has dimensions $(p.n) × $(p.m), x has dimensions $(size(x, 1)) × $(size(x, 2))"))
end
end
function checksize(p::FTPlan{T, 3}, x::Array{T, 3}) where T
if p.n != size(x, 1) || p.l != size(x, 2) || p.m != size(x, 3)
throw(DimensionMismatch("FTPlan has dimensions $(p.n) × $(p.l) × $(p.m), x has dimensions $(size(x, 1)) × $(size(x, 2)) × $(size(x, 3))"))
end
end
function checksize(p::FTPlan{T, 2, SPHERICALISOMETRY}, x::Matrix{T}) where T
if p.n != size(x, 1) || 2p.n-1 != size(x, 2)
throw(DimensionMismatch("This FTPlan must operate on arrays of size $(p.n) × $(2p.n-1)."))
end
end
unsafe_convert(::Type{Ptr{ft_plan_struct}}, p::FTPlan) = p.plan
unsafe_convert(::Type{Ptr{mpfr_t}}, p::FTPlan) = unsafe_convert(Ptr{mpfr_t}, p.plan)
destroy_plan(p::FTPlan{Float32, 1}) = ccall((:ft_destroy_tb_eigen_FMMf, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Float64, 1}) = ccall((:ft_destroy_tb_eigen_FMM, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{BigFloat, 1}) = ccall((:ft_mpfr_destroy_plan, libfasttransforms), Cvoid, (Ptr{mpfr_t}, Cint), p, p.n)
destroy_plan(p::FTPlan{Float32, 1, ASSOCIATEDJAC2JAC}) = ccall((:ft_destroy_btb_eigen_FMMf, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Float64, 1, ASSOCIATEDJAC2JAC}) = ccall((:ft_destroy_btb_eigen_FMM, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Float32, 1, MODIFIEDJAC2JAC}) = ccall((:ft_destroy_modified_planf, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Float64, 1, MODIFIEDJAC2JAC}) = ccall((:ft_destroy_modified_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Float32, 1, MODIFIEDLAG2LAG}) = ccall((:ft_destroy_modified_planf, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Float64, 1, MODIFIEDLAG2LAG}) = ccall((:ft_destroy_modified_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Float32, 1, MODIFIEDHERM2HERM}) = ccall((:ft_destroy_modified_planf, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Float64, 1, MODIFIEDHERM2HERM}) = ccall((:ft_destroy_modified_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Float64}) = ccall((:ft_destroy_harmonic_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Complex{Float64}, 2, SPINSPHERE}) = ccall((:ft_destroy_spin_harmonic_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Float64, 2, SPHERESYNTHESIS}) = ccall((:ft_destroy_sphere_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Float64, 2, SPHEREANALYSIS}) = ccall((:ft_destroy_sphere_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Float64, 2, SPHEREVSYNTHESIS}) = ccall((:ft_destroy_sphere_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Float64, 2, SPHEREVANALYSIS}) = ccall((:ft_destroy_sphere_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Float64, 2, DISKSYNTHESIS}) = ccall((:ft_destroy_disk_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Float64, 2, DISKANALYSIS}) = ccall((:ft_destroy_disk_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Float64, 2, ANNULUSSYNTHESIS}) = ccall((:ft_destroy_annulus_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Float64, 2, ANNULUSANALYSIS}) = ccall((:ft_destroy_annulus_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Float64, 2, RECTDISKSYNTHESIS}) = ccall((:ft_destroy_rectdisk_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Float64, 2, RECTDISKANALYSIS}) = ccall((:ft_destroy_rectdisk_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Float64, 2, TRIANGLESYNTHESIS}) = ccall((:ft_destroy_triangle_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Float64, 2, TRIANGLEANALYSIS}) = ccall((:ft_destroy_triangle_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Float64, 3, TETRAHEDRONSYNTHESIS}) = ccall((:ft_destroy_tetrahedron_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Float64, 3, TETRAHEDRONANALYSIS}) = ccall((:ft_destroy_tetrahedron_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Complex{Float64}, 2, SPINSPHERESYNTHESIS}) = ccall((:ft_destroy_spinsphere_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Complex{Float64}, 2, SPINSPHEREANALYSIS}) = ccall((:ft_destroy_spinsphere_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
destroy_plan(p::FTPlan{Float64, 2, SPHERICALISOMETRY}) = ccall((:ft_destroy_sph_isometry_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)
struct AdjointFTPlan{T, S, R}
parent::S
adjoint::R
function AdjointFTPlan{T, S, R}(parent::S) where {T, S, R}
new(parent)
end
function AdjointFTPlan{T, S, R}(parent::S, adjoint::R) where {T, S, R}
new(parent, adjoint)
end
end
AdjointFTPlan(p::FTPlan) = AdjointFTPlan{eltype(p), typeof(p), typeof(p)}(p)
AdjointFTPlan(p::FTPlan, q::FTPlan) = AdjointFTPlan{eltype(q), typeof(p), typeof(q)}(p, q)
adjoint(p::FTPlan) = AdjointFTPlan(p)
adjoint(p::AdjointFTPlan) = p.parent
eltype(p::AdjointFTPlan{T}) where T = T
ndims(p::AdjointFTPlan) = ndims(p.parent)
function show(io::IO, p::AdjointFTPlan)
print(io, "Adjoint ")
show(io, p.parent)
end
function checksize(p::AdjointFTPlan, x)
try
checksize(p.adjoint, x)
catch
checksize(p.parent, x)
end
end
function checkstride(p::AdjointFTPlan, x)
try
checkstride(p.adjoint, x)
catch
checkstride(p.parent, x)
end
end
function unsafe_convert(::Type{Ptr{ft_plan_struct}}, p::AdjointFTPlan)
try
unsafe_convert(Ptr{ft_plan_struct}, p.adjoint)
catch
unsafe_convert(Ptr{ft_plan_struct}, p.parent)
end
end
function unsafe_convert(::Type{Ptr{mpfr_t}}, p::AdjointFTPlan)
try
unsafe_convert(Ptr{mpfr_t}, p.adjoint)
catch
unsafe_convert(Ptr{mpfr_t}, p.parent)
end
end
struct TransposeFTPlan{T, S, R}
parent::S
transpose::R
function TransposeFTPlan{T, S, R}(parent::S) where {T, S, R}
new(parent)
end
function TransposeFTPlan{T, S, R}(parent::S, transpose::R) where {T, S, R}
new(parent, transpose)
end
end
TransposeFTPlan(p::FTPlan) = TransposeFTPlan{eltype(p), typeof(p), typeof(p)}(p)
TransposeFTPlan(p::FTPlan, q::FTPlan) = TransposeFTPlan{eltype(q), typeof(p), typeof(q)}(p, q)
transpose(p::FTPlan) = TransposeFTPlan(p)
transpose(p::TransposeFTPlan) = p.parent
eltype(p::TransposeFTPlan{T}) where T = T
ndims(p::TransposeFTPlan) = ndims(p.parent)
function show(io::IO, p::TransposeFTPlan)
print(io, "Transpose ")
show(io, p.parent)
end
function checksize(p::TransposeFTPlan, x)
try
checksize(p.transpose, x)
catch
checksize(p.parent, x)
end
end
function checkstride(p::TransposeFTPlan, x)
try
checkstride(p.transpose, x)
catch
checkstride(p.parent, x)
end
end
function unsafe_convert(::Type{Ptr{ft_plan_struct}}, p::TransposeFTPlan)
try
unsafe_convert(Ptr{ft_plan_struct}, p.transpose)
catch
unsafe_convert(Ptr{ft_plan_struct}, p.parent)
end
end
function unsafe_convert(::Type{Ptr{mpfr_t}}, p::TransposeFTPlan)
try
unsafe_convert(Ptr{mpfr_t}, p.transpose)
catch
unsafe_convert(Ptr{mpfr_t}, p.parent)
end
end
const ModifiedFTPlan{T} = Union{FTPlan{T, 1, MODIFIEDJAC2JAC}, FTPlan{T, 1, MODIFIEDLAG2LAG}, FTPlan{T, 1, MODIFIEDHERM2HERM}}
for f in (:leg2cheb, :cheb2leg, :ultra2ultra, :jac2jac,
:lag2lag, :jac2ultra, :ultra2jac, :jac2cheb,
:cheb2jac, :ultra2cheb, :cheb2ultra, :associatedjac2jac,
:modifiedjac2jac, :modifiedlag2lag, :modifiedherm2herm,
:sph2fourier, :sphv2fourier, :disk2cxf, :ann2cxf,
:rectdisk2cheb, :tri2cheb, :tet2cheb)
plan_f = Symbol("plan_", f)
lib_f = Symbol("lib_", f)
@eval begin
$plan_f(x::AbstractArray{T}, y...; z...) where T = $plan_f(T, size(x, 1), y...; z...)
$plan_f(::Type{Complex{T}}, y...; z...) where T <: Real = $plan_f(T, y...; z...)
$lib_f(x::AbstractArray, y...; z...) = $plan_f(x, y...; z...)*x
end
end
for (f, plan_f) in ((:fourier2sph, :plan_sph2fourier), (:fourier2sphv, :plan_sphv2fourier),
(:cxf2disk, :plan_disk2cxf), (:cxf2ann, :plan_ann2cxf),
(:cheb2rectdisk, :plan_rectdisk2cheb), (:cheb2tri, :plan_tri2cheb),
(:cheb2tet, :plan_tet2cheb))
@eval begin
$f(x::AbstractArray, y...; z...) = $plan_f(x, y...; z...)\x
end
end
plan_spinsph2fourier(x::AbstractArray{T}, y...; z...) where T = plan_spinsph2fourier(T, size(x, 1), y...; z...)
spinsph2fourier(x::AbstractArray, y...; z...) = plan_spinsph2fourier(x, y...; z...)*x
fourier2spinsph(x::AbstractArray, y...; z...) = plan_spinsph2fourier(x, y...; z...)\x
function plan_leg2cheb(::Type{Float32}, n::Integer; normleg::Bool=false, normcheb::Bool=false)
plan = ccall((:ft_plan_legendre_to_chebyshevf, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint), normleg, normcheb, n)
return FTPlan{Float32, 1, LEG2CHEB}(plan, n)
end
function plan_cheb2leg(::Type{Float32}, n::Integer; normcheb::Bool=false, normleg::Bool=false)
plan = ccall((:ft_plan_chebyshev_to_legendref, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint), normcheb, normleg, n)
return FTPlan{Float32, 1, CHEB2LEG}(plan, n)
end
function plan_ultra2ultra(::Type{Float32}, n::Integer, λ, μ; norm1::Bool=false, norm2::Bool=false)
plan = ccall((:ft_plan_ultraspherical_to_ultrasphericalf, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float32, Float32), norm1, norm2, n, λ, μ)
return FTPlan{Float32, 1, ULTRA2ULTRA}(plan, n)
end
function plan_jac2jac(::Type{Float32}, n::Integer, α, β, γ, δ; norm1::Bool=false, norm2::Bool=false)
plan = ccall((:ft_plan_jacobi_to_jacobif, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float32, Float32, Float32, Float32), norm1, norm2, n, α, β, γ, δ)
return FTPlan{Float32, 1, JAC2JAC}(plan, n)
end
function plan_lag2lag(::Type{Float32}, n::Integer, α, β; norm1::Bool=false, norm2::Bool=false)
plan = ccall((:ft_plan_laguerre_to_laguerref, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float32, Float32), norm1, norm2, n, α, β)
return FTPlan{Float32, 1, LAG2LAG}(plan, n)
end
function plan_jac2ultra(::Type{Float32}, n::Integer, α, β, λ; normjac::Bool=false, normultra::Bool=false)
plan = ccall((:ft_plan_jacobi_to_ultrasphericalf, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float32, Float32, Float32), normjac, normultra, n, α, β, λ)
return FTPlan{Float32, 1, JAC2ULTRA}(plan, n)
end
function plan_ultra2jac(::Type{Float32}, n::Integer, λ, α, β; normultra::Bool=false, normjac::Bool=false)
plan = ccall((:ft_plan_ultraspherical_to_jacobif, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float32, Float32, Float32), normultra, normjac, n, λ, α, β)
return FTPlan{Float32, 1, ULTRA2JAC}(plan, n)
end
function plan_jac2cheb(::Type{Float32}, n::Integer, α, β; normjac::Bool=false, normcheb::Bool=false)
plan = ccall((:ft_plan_jacobi_to_chebyshevf, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float32, Float32), normjac, normcheb, n, α, β)
return FTPlan{Float32, 1, JAC2CHEB}(plan, n)
end
function plan_cheb2jac(::Type{Float32}, n::Integer, α, β; normcheb::Bool=false, normjac::Bool=false)
plan = ccall((:ft_plan_chebyshev_to_jacobif, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float32, Float32), normcheb, normjac, n, α, β)
return FTPlan{Float32, 1, CHEB2JAC}(plan, n)
end
function plan_ultra2cheb(::Type{Float32}, n::Integer, λ; normultra::Bool=false, normcheb::Bool=false)
plan = ccall((:ft_plan_ultraspherical_to_chebyshevf, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float32), normultra, normcheb, n, λ)
return FTPlan{Float32, 1, ULTRA2CHEB}(plan, n)
end
function plan_cheb2ultra(::Type{Float32}, n::Integer, λ; normcheb::Bool=false, normultra::Bool=false)
plan = ccall((:ft_plan_chebyshev_to_ultrasphericalf, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float32), normcheb, normultra, n, λ)
return FTPlan{Float32, 1, CHEB2ULTRA}(plan, n)
end
function plan_associatedjac2jac(::Type{Float32}, n::Integer, c::Integer, α, β, γ, δ; norm1::Bool=false, norm2::Bool=false)
plan = ccall((:ft_plan_associated_jacobi_to_jacobif, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Cint, Float32, Float32, Float32, Float32), norm1, norm2, n, c, α, β, γ, δ)
return FTPlan{Float32, 1, ASSOCIATEDJAC2JAC}(plan, n)
end
function plan_modifiedjac2jac(::Type{Float32}, n::Integer, α, β, u::Vector{Float32}; verbose::Bool=false)
plan = ccall((:ft_plan_modified_jacobi_to_jacobif, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Float32, Float32, Cint, Ptr{Float32}, Cint, Ptr{Float32}, Cint), n, α, β, length(u), u, 0, C_NULL, verbose)
return FTPlan{Float32, 1, MODIFIEDJAC2JAC}(plan, n)
end
function plan_modifiedjac2jac(::Type{Float32}, n::Integer, α, β, u::Vector{Float32}, v::Vector{Float32}; verbose::Bool=false)
plan = ccall((:ft_plan_modified_jacobi_to_jacobif, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Float32, Float32, Cint, Ptr{Float32}, Cint, Ptr{Float32}, Cint), n, α, β, length(u), u, length(v), v, verbose)
return FTPlan{Float32, 1, MODIFIEDJAC2JAC}(plan, n)
end
function plan_modifiedlag2lag(::Type{Float32}, n::Integer, α, u::Vector{Float32}; verbose::Bool=false)
plan = ccall((:ft_plan_modified_laguerre_to_laguerref, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Float32, Cint, Ptr{Float32}, Cint, Ptr{Float32}, Cint), n, α, length(u), u, 0, C_NULL, verbose)
return FTPlan{Float32, 1, MODIFIEDLAG2LAG}(plan, n)
end
function plan_modifiedlag2lag(::Type{Float32}, n::Integer, α, u::Vector{Float32}, v::Vector{Float32}; verbose::Bool=false)
plan = ccall((:ft_plan_modified_laguerre_to_laguerref, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Float32, Cint, Ptr{Float32}, Cint, Ptr{Float32}, Cint), n, α, length(u), u, length(v), v, verbose)
return FTPlan{Float32, 1, MODIFIEDLAG2LAG}(plan, n)
end
function plan_modifiedherm2herm(::Type{Float32}, n::Integer, u::Vector{Float32}; verbose::Bool=false)
plan = ccall((:ft_plan_modified_hermite_to_hermitef, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Ptr{Float32}, Cint, Ptr{Float32}, Cint), n, length(u), u, 0, C_NULL, verbose)
return FTPlan{Float32, 1, MODIFIEDHERM2HERM}(plan, n)
end
function plan_modifiedherm2herm(::Type{Float32}, n::Integer, u::Vector{Float32}, v::Vector{Float32}; verbose::Bool=false)
plan = ccall((:ft_plan_modified_hermite_to_hermitef, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Ptr{Float32}, Cint, Ptr{Float32}, Cint), n, length(u), u, length(v), v, verbose)
return FTPlan{Float32, 1, MODIFIEDHERM2HERM}(plan, n)
end
function plan_leg2cheb(::Type{Float64}, n::Integer; normleg::Bool=false, normcheb::Bool=false)
plan = ccall((:ft_plan_legendre_to_chebyshev, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint), normleg, normcheb, n)
return FTPlan{Float64, 1, LEG2CHEB}(plan, n)
end
function plan_cheb2leg(::Type{Float64}, n::Integer; normcheb::Bool=false, normleg::Bool=false)
plan = ccall((:ft_plan_chebyshev_to_legendre, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint), normcheb, normleg, n)
return FTPlan{Float64, 1, CHEB2LEG}(plan, n)
end
function plan_ultra2ultra(::Type{Float64}, n::Integer, λ, μ; norm1::Bool=false, norm2::Bool=false)
plan = ccall((:ft_plan_ultraspherical_to_ultraspherical, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float64, Float64), norm1, norm2, n, λ, μ)
return FTPlan{Float64, 1, ULTRA2ULTRA}(plan, n)
end
function plan_jac2jac(::Type{Float64}, n::Integer, α, β, γ, δ; norm1::Bool=false, norm2::Bool=false)
plan = ccall((:ft_plan_jacobi_to_jacobi, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float64, Float64, Float64, Float64), norm1, norm2, n, α, β, γ, δ)
return FTPlan{Float64, 1, JAC2JAC}(plan, n)
end
function plan_lag2lag(::Type{Float64}, n::Integer, α, β; norm1::Bool=false, norm2::Bool=false)
plan = ccall((:ft_plan_laguerre_to_laguerre, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float64, Float64), norm1, norm2, n, α, β)
return FTPlan{Float64, 1, LAG2LAG}(plan, n)
end
function plan_jac2ultra(::Type{Float64}, n::Integer, α, β, λ; normjac::Bool=false, normultra::Bool=false)
plan = ccall((:ft_plan_jacobi_to_ultraspherical, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float64, Float64, Float64), normjac, normultra, n, α, β, λ)
return FTPlan{Float64, 1, JAC2ULTRA}(plan, n)
end
function plan_ultra2jac(::Type{Float64}, n::Integer, λ, α, β; normultra::Bool=false, normjac::Bool=false)
plan = ccall((:ft_plan_ultraspherical_to_jacobi, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float64, Float64, Float64), normultra, normjac, n, λ, α, β)
return FTPlan{Float64, 1, ULTRA2JAC}(plan, n)
end
function plan_jac2cheb(::Type{Float64}, n::Integer, α, β; normjac::Bool=false, normcheb::Bool=false)
plan = ccall((:ft_plan_jacobi_to_chebyshev, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float64, Float64), normjac, normcheb, n, α, β)
return FTPlan{Float64, 1, JAC2CHEB}(plan, n)
end
function plan_cheb2jac(::Type{Float64}, n::Integer, α, β; normcheb::Bool=false, normjac::Bool=false)
plan = ccall((:ft_plan_chebyshev_to_jacobi, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float64, Float64), normcheb, normjac, n, α, β)
return FTPlan{Float64, 1, CHEB2JAC}(plan, n)
end
function plan_ultra2cheb(::Type{Float64}, n::Integer, λ; normultra::Bool=false, normcheb::Bool=false)
plan = ccall((:ft_plan_ultraspherical_to_chebyshev, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float64), normultra, normcheb, n, λ)
return FTPlan{Float64, 1, ULTRA2CHEB}(plan, n)
end
function plan_cheb2ultra(::Type{Float64}, n::Integer, λ; normcheb::Bool=false, normultra::Bool=false)
plan = ccall((:ft_plan_chebyshev_to_ultraspherical, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float64), normcheb, normultra, n, λ)
return FTPlan{Float64, 1, CHEB2ULTRA}(plan, n)
end
function plan_associatedjac2jac(::Type{Float64}, n::Integer, c::Integer, α, β, γ, δ; norm1::Bool=false, norm2::Bool=false)
plan = ccall((:ft_plan_associated_jacobi_to_jacobi, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Cint, Float64, Float64, Float64, Float64), norm1, norm2, n, c, α, β, γ, δ)
return FTPlan{Float64, 1, ASSOCIATEDJAC2JAC}(plan, n)
end
function plan_modifiedjac2jac(::Type{Float64}, n::Integer, α, β, u::Vector{Float64}; verbose::Bool=false)
plan = ccall((:ft_plan_modified_jacobi_to_jacobi, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Float64, Float64, Cint, Ptr{Float64}, Cint, Ptr{Float64}, Cint), n, α, β, length(u), u, 0, C_NULL, verbose)
return FTPlan{Float64, 1, MODIFIEDJAC2JAC}(plan, n)
end
function plan_modifiedjac2jac(::Type{Float64}, n::Integer, α, β, u::Vector{Float64}, v::Vector{Float64}; verbose::Bool=false)
plan = ccall((:ft_plan_modified_jacobi_to_jacobi, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Float64, Float64, Cint, Ptr{Float64}, Cint, Ptr{Float64}, Cint), n, α, β, length(u), u, length(v), v, verbose)
return FTPlan{Float64, 1, MODIFIEDJAC2JAC}(plan, n)
end
function plan_modifiedlag2lag(::Type{Float64}, n::Integer, α, u::Vector{Float64}; verbose::Bool=false)
plan = ccall((:ft_plan_modified_laguerre_to_laguerre, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Float64, Cint, Ptr{Float64}, Cint, Ptr{Float64}, Cint), n, α, length(u), u, 0, C_NULL, verbose)
return FTPlan{Float64, 1, MODIFIEDLAG2LAG}(plan, n)
end
function plan_modifiedlag2lag(::Type{Float64}, n::Integer, α, u::Vector{Float64}, v::Vector{Float64}; verbose::Bool=false)
plan = ccall((:ft_plan_modified_laguerre_to_laguerre, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Float64, Cint, Ptr{Float64}, Cint, Ptr{Float64}, Cint), n, α, length(u), u, length(v), v, verbose)
return FTPlan{Float64, 1, MODIFIEDLAG2LAG}(plan, n)
end
function plan_modifiedherm2herm(::Type{Float64}, n::Integer, u::Vector{Float64}; verbose::Bool=false)
plan = ccall((:ft_plan_modified_hermite_to_hermite, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Ptr{Float64}, Cint, Ptr{Float64}, Cint), n, length(u), u, 0, C_NULL, verbose)
return FTPlan{Float64, 1, MODIFIEDHERM2HERM}(plan, n)
end
function plan_modifiedherm2herm(::Type{Float64}, n::Integer, u::Vector{Float64}, v::Vector{Float64}; verbose::Bool=false)
plan = ccall((:ft_plan_modified_hermite_to_hermite, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Ptr{Float64}, Cint, Ptr{Float64}, Cint), n, length(u), u, length(v), v, verbose)
return FTPlan{Float64, 1, MODIFIEDHERM2HERM}(plan, n)
end
function plan_leg2cheb(::Type{BigFloat}, n::Integer; normleg::Bool=false, normcheb::Bool=false)
plan = ccall((:ft_mpfr_plan_legendre_to_chebyshev, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Clong, Int32), normleg, normcheb, n, precision(BigFloat), Base.MPFR.ROUNDING_MODE[])
return FTPlan{BigFloat, 1, LEG2CHEB}(plan, n)
end
function plan_cheb2leg(::Type{BigFloat}, n::Integer; normcheb::Bool=false, normleg::Bool=false)
plan = ccall((:ft_mpfr_plan_chebyshev_to_legendre, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Clong, Int32), normcheb, normleg, n, precision(BigFloat), Base.MPFR.ROUNDING_MODE[])
return FTPlan{BigFloat, 1, CHEB2LEG}(plan, n)
end
function plan_ultra2ultra(::Type{BigFloat}, n::Integer, λ, μ; norm1::Bool=false, norm2::Bool=false)
plan = ccall((:ft_mpfr_plan_ultraspherical_to_ultraspherical, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Ref{BigFloat}, Ref{BigFloat}, Clong, Int32), norm1, norm2, n, λ, μ, precision(BigFloat), Base.MPFR.ROUNDING_MODE[])
return FTPlan{BigFloat, 1, ULTRA2ULTRA}(plan, n)
end
function plan_jac2jac(::Type{BigFloat}, n::Integer, α, β, γ, δ; norm1::Bool=false, norm2::Bool=false)
plan = ccall((:ft_mpfr_plan_jacobi_to_jacobi, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, Clong, Int32), norm1, norm2, n, α, β, γ, δ, precision(BigFloat), Base.MPFR.ROUNDING_MODE[])
return FTPlan{BigFloat, 1, JAC2JAC}(plan, n)
end
function plan_lag2lag(::Type{BigFloat}, n::Integer, α, β; norm1::Bool=false, norm2::Bool=false)
plan = ccall((:ft_mpfr_plan_laguerre_to_laguerre, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Ref{BigFloat}, Ref{BigFloat}, Clong, Int32), norm1, norm2, n, α, β, precision(BigFloat), Base.MPFR.ROUNDING_MODE[])
return FTPlan{BigFloat, 1, LAG2LAG}(plan, n)
end
function plan_jac2ultra(::Type{BigFloat}, n::Integer, α, β, λ; normjac::Bool=false, normultra::Bool=false)
plan = ccall((:ft_mpfr_plan_jacobi_to_ultraspherical, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, Clong, Int32), normjac, normultra, n, α, β, λ, precision(BigFloat), Base.MPFR.ROUNDING_MODE[])
return FTPlan{BigFloat, 1, JAC2ULTRA}(plan, n)
end
function plan_ultra2jac(::Type{BigFloat}, n::Integer, λ, α, β; normultra::Bool=false, normjac::Bool=false)
plan = ccall((:ft_mpfr_plan_ultraspherical_to_jacobi, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Ref{BigFloat}, Ref{BigFloat}, Ref{BigFloat}, Clong, Int32), normultra, normjac, n, λ, α, β, precision(BigFloat), Base.MPFR.ROUNDING_MODE[])
return FTPlan{BigFloat, 1, ULTRA2JAC}(plan, n)
end
function plan_jac2cheb(::Type{BigFloat}, n::Integer, α, β; normjac::Bool=false, normcheb::Bool=false)
plan = ccall((:ft_mpfr_plan_jacobi_to_chebyshev, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Ref{BigFloat}, Ref{BigFloat}, Clong, Int32), normjac, normcheb, n, α, β, precision(BigFloat), Base.MPFR.ROUNDING_MODE[])
return FTPlan{BigFloat, 1, JAC2CHEB}(plan, n)
end
function plan_cheb2jac(::Type{BigFloat}, n::Integer, α, β; normcheb::Bool=false, normjac::Bool=false)
plan = ccall((:ft_mpfr_plan_chebyshev_to_jacobi, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Ref{BigFloat}, Ref{BigFloat}, Clong, Int32), normcheb, normjac, n, α, β, precision(BigFloat), Base.MPFR.ROUNDING_MODE[])
return FTPlan{BigFloat, 1, CHEB2JAC}(plan, n)
end
function plan_ultra2cheb(::Type{BigFloat}, n::Integer, λ; normultra::Bool=false, normcheb::Bool=false)
plan = ccall((:ft_mpfr_plan_ultraspherical_to_chebyshev, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Ref{BigFloat}, Clong, Int32), normultra, normcheb, n, λ, precision(BigFloat), Base.MPFR.ROUNDING_MODE[])
return FTPlan{BigFloat, 1, ULTRA2CHEB}(plan, n)
end
function plan_cheb2ultra(::Type{BigFloat}, n::Integer, λ; normcheb::Bool=false, normultra::Bool=false)
plan = ccall((:ft_mpfr_plan_chebyshev_to_ultraspherical, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Ref{BigFloat}, Clong, Int32), normcheb, normultra, n, λ, precision(BigFloat), Base.MPFR.ROUNDING_MODE[])
return FTPlan{BigFloat, 1, CHEB2ULTRA}(plan, n)
end
function plan_sph2fourier(::Type{Float64}, n::Integer)
plan = ccall((:ft_plan_sph2fourier, libfasttransforms), Ptr{ft_plan_struct}, (Cint, ), n)
return FTPlan{Float64, 2, SPHERE}(plan, n)
end
function plan_sphv2fourier(::Type{Float64}, n::Integer)
plan = ccall((:ft_plan_sph2fourier, libfasttransforms), Ptr{ft_plan_struct}, (Cint, ), n)
return FTPlan{Float64, 2, SPHEREV}(plan, n)
end
function plan_disk2cxf(::Type{Float64}, n::Integer, α, β)
plan = ccall((:ft_plan_disk2cxf, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Float64, Float64), n, α, β)
return FTPlan{Float64, 2, DISK}(plan, n)
end
function plan_ann2cxf(::Type{Float64}, n::Integer, α, β, γ, ρ)
plan = ccall((:ft_plan_ann2cxf, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Float64, Float64, Float64, Float64), n, α, β, γ, ρ)
return FTPlan{Float64, 2, ANNULUS}(plan, n)
end
function plan_rectdisk2cheb(::Type{Float64}, n::Integer, β)
plan = ccall((:ft_plan_rectdisk2cheb, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Float64), n, β)
return FTPlan{Float64, 2, RECTDISK}(plan, n)
end
function plan_tri2cheb(::Type{Float64}, n::Integer, α, β, γ)
plan = ccall((:ft_plan_tri2cheb, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Float64, Float64, Float64), n, α, β, γ)
return FTPlan{Float64, 2, TRIANGLE}(plan, n)
end
function plan_tet2cheb(::Type{Float64}, n::Integer, α, β, γ, δ)
plan = ccall((:ft_plan_tet2cheb, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Float64, Float64, Float64, Float64), n, α, β, γ, δ)
return FTPlan{Float64, 3, TETRAHEDRON}(plan, n)
end
function plan_spinsph2fourier(::Type{Complex{Float64}}, n::Integer, s::Integer)
plan = ccall((:ft_plan_spinsph2fourier, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint), n, s)
return FTPlan{Complex{Float64}, 2, SPINSPHERE}(plan, n)
end
plan_disk2cxf(::Type{Float64}, n::Integer, α) = plan_disk2cxf(Float64, n, α, 0)
plan_disk2cxf(::Type{Float64}, n::Integer) = plan_disk2cxf(Float64, n, 0)
plan_ann2cxf(::Type{Float64}, n::Integer, α, β, γ) = plan_ann2cxf(Float64, n, α, β, γ, 0)
plan_ann2cxf(::Type{Float64}, n::Integer, α, β) = plan_disk2cxf(Float64, n, α, β)
plan_ann2cxf(::Type{Float64}, n::Integer, α) = plan_disk2cxf(Float64, n, α)
plan_ann2cxf(::Type{Float64}, n::Integer) = plan_disk2cxf(Float64, n)
plan_rectdisk2cheb(::Type{Float64}, n::Integer) = plan_rectdisk2cheb(Float64, n, 0)
plan_tri2cheb(::Type{Float64}, n::Integer, α, β) = plan_tri2cheb(Float64, n, α, β, 0)
plan_tri2cheb(::Type{Float64}, n::Integer, α) = plan_tri2cheb(Float64, n, α, 0)
plan_tri2cheb(::Type{Float64}, n::Integer) = plan_tri2cheb(Float64, n, 0)
plan_tet2cheb(::Type{Float64}, n::Integer, α, β, γ) = plan_tet2cheb(Float64, n, α, β, γ, 0)
plan_tet2cheb(::Type{Float64}, n::Integer, α, β) = plan_tet2cheb(Float64, n, α, β, 0)
plan_tet2cheb(::Type{Float64}, n::Integer, α) = plan_tet2cheb(Float64, n, α, 0)
plan_tet2cheb(::Type{Float64}, n::Integer) = plan_tet2cheb(Float64, n, 0)
for (fJ, fadJ, fC, fE, K) in ((:plan_sph_synthesis, :plan_sph_analysis, :ft_plan_sph_synthesis, :ft_execute_sph_synthesis, SPHERESYNTHESIS),
(:plan_sph_analysis, :plan_sph_synthesis, :ft_plan_sph_analysis, :ft_execute_sph_analysis, SPHEREANALYSIS),
(:plan_sphv_synthesis, :plan_sphv_analysis, :ft_plan_sphv_synthesis, :ft_execute_sphv_synthesis, SPHEREVSYNTHESIS),
(:plan_sphv_analysis, :plan_sphv_synthesis, :ft_plan_sphv_analysis, :ft_execute_sphv_analysis, SPHEREVANALYSIS),
(:plan_disk_synthesis, :plan_disk_analysis, :ft_plan_disk_synthesis, :ft_execute_disk_synthesis, DISKSYNTHESIS),
(:plan_disk_analysis, :plan_disk_synthesis, :ft_plan_disk_analysis, :ft_execute_disk_analysis, DISKANALYSIS),
(:plan_rectdisk_synthesis, :plan_rectdisk_analysis, :ft_plan_rectdisk_synthesis, :ft_execute_rectdisk_synthesis, RECTDISKSYNTHESIS),
(:plan_rectdisk_analysis, :plan_rectdisk_synthesis, :ft_plan_rectdisk_analysis, :ft_execute_rectdisk_analysis, RECTDISKANALYSIS),
(:plan_tri_synthesis, :plan_tri_analysis, :ft_plan_tri_synthesis, :ft_execute_tri_synthesis, TRIANGLESYNTHESIS),
(:plan_tri_analysis, :plan_tri_synthesis, :ft_plan_tri_analysis, :ft_execute_tri_analysis, TRIANGLEANALYSIS))
@eval begin
$fJ(x::Matrix{T}; y...) where T = $fJ(T, size(x, 1), size(x, 2); y...)
$fJ(::Type{Complex{T}}, x...; y...) where T <: Real = $fJ(T, x...; y...)
function $fJ(::Type{Float64}, n::Integer, m::Integer; flags::Integer=FFTW.ESTIMATE)
plan = ccall(($(string(fC)), libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cuint), n, m, flags)
return FTPlan{Float64, 2, $K}(plan, n, m)
end
adjoint(p::FTPlan{T, 2, $K}) where T = AdjointFTPlan(p, $fadJ(T, p.n, p.m))
transpose(p::FTPlan{T, 2, $K}) where T = TransposeFTPlan(p, $fadJ(T, p.n, p.m))
function lmul!(p::FTPlan{Float64, 2, $K}, x::Matrix{Float64})
checksize(p, x)
ccall(($(string(fE)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{Float64}, Cint, Cint), 'N', p, x, size(x, 1), size(x, 2))
return x
end
function lmul!(p::AdjointFTPlan{Float64, FTPlan{Float64, 2, $K}}, x::Matrix{Float64})
checksize(p, x)
ccall(($(string(fE)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{Float64}, Cint, Cint), 'T', p, x, size(x, 1), size(x, 2))
return x
end
function lmul!(p::TransposeFTPlan{Float64, FTPlan{Float64, 2, $K}}, x::Matrix{Float64})
checksize(p, x)
ccall(($(string(fE)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{Float64}, Cint, Cint), 'T', p, x, size(x, 1), size(x, 2))
return x
end
end
end
ft_get_rho_annulus_fftw_plan(p::FTPlan{Float64, 2, ANNULUSSYNTHESIS}) = ccall((:ft_get_rho_annulus_fftw_plan, libfasttransforms), Float64, (Ptr{ft_plan_struct}, ), p)
ft_get_rho_annulus_fftw_plan(p::FTPlan{Float64, 2, ANNULUSANALYSIS}) = ccall((:ft_get_rho_annulus_fftw_plan, libfasttransforms), Float64, (Ptr{ft_plan_struct}, ), p)
for (fJ, fadJ, fC, fE, K) in ((:plan_annulus_synthesis, :plan_annulus_analysis, :ft_plan_annulus_synthesis, :ft_execute_annulus_synthesis, ANNULUSSYNTHESIS),
(:plan_annulus_analysis, :plan_annulus_synthesis, :ft_plan_annulus_analysis, :ft_execute_annulus_analysis, ANNULUSANALYSIS))
@eval begin
$fJ(x::Matrix{T}, ρ; y...) where T = $fJ(T, size(x, 1), size(x, 2), ρ; y...)
$fJ(::Type{Complex{T}}, x...; y...) where T <: Real = $fJ(T, x...; y...)
function $fJ(::Type{Float64}, n::Integer, m::Integer, ρ; flags::Integer=FFTW.ESTIMATE)
plan = ccall(($(string(fC)), libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Float64, Cuint), n, m, ρ, flags)
return FTPlan{Float64, 2, $K}(plan, n, m)
end
adjoint(p::FTPlan{T, 2, $K}) where T = AdjointFTPlan(p, $fadJ(T, p.n, p.m, ft_get_rho_annulus_fftw_plan(p)))
transpose(p::FTPlan{T, 2, $K}) where T = TransposeFTPlan(p, $fadJ(T, p.n, p.m, ft_get_rho_annulus_fftw_plan(p)))
function lmul!(p::FTPlan{Float64, 2, $K}, x::Matrix{Float64})
checksize(p, x)
ccall(($(string(fE)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{Float64}, Cint, Cint), 'N', p, x, size(x, 1), size(x, 2))
return x
end
function lmul!(p::AdjointFTPlan{Float64, FTPlan{Float64, 2, $K}}, x::Matrix{Float64})
checksize(p, x)
ccall(($(string(fE)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{Float64}, Cint, Cint), 'T', p, x, size(x, 1), size(x, 2))
return x
end
function lmul!(p::TransposeFTPlan{Float64, FTPlan{Float64, 2, $K}}, x::Matrix{Float64})
checksize(p, x)
ccall(($(string(fE)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{Float64}, Cint, Cint), 'T', p, x, size(x, 1), size(x, 2))
return x
end
end
end
for (fJ, fadJ, fC, fE, K) in ((:plan_tet_synthesis, :plan_tet_analysis, :ft_plan_tet_synthesis, :ft_execute_tet_synthesis, TETRAHEDRONSYNTHESIS),
(:plan_tet_analysis, :plan_tet_synthesis, :ft_plan_tet_analysis, :ft_execute_tet_analysis, TETRAHEDRONANALYSIS))
@eval begin
$fJ(x::Array{T, 3}; y...) where T = $fJ(T, size(x, 1), size(x, 2), size(x, 3); y...)
$fJ(::Type{Complex{T}}, x...; y...) where T <: Real = $fJ(T, x...; y...)
function $fJ(::Type{Float64}, n::Integer, l::Integer, m::Integer; flags::Integer=FFTW.ESTIMATE)
plan = ccall(($(string(fC)), libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Cuint), n, l, m, flags)
return FTPlan{Float64, 3, $K}(plan, n, l, m)
end
adjoint(p::FTPlan{T, 3, $K}) where T = AdjointFTPlan(p, $fadJ(T, p.n, p.l, p.m))
transpose(p::FTPlan{T, 3, $K}) where T = TransposeFTPlan(p, $fadJ(T, p.n, p.l, p.m))
function lmul!(p::FTPlan{Float64, 3, $K}, x::Array{Float64, 3})
checksize(p, x)
ccall(($(string(fE)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{Float64}, Cint, Cint, Cint), 'N', p, x, size(x, 1), size(x, 2), size(x, 3))
return x
end
function lmul!(p::AdjointFTPlan{Float64, FTPlan{Float64, 3, $K}}, x::Array{Float64, 3})
checksize(p, x)
ccall(($(string(fE)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{Float64}, Cint, Cint, Cint), 'T', p, x, size(x, 1), size(x, 2), size(x, 3))
return x
end
function lmul!(p::TransposeFTPlan{Float64, FTPlan{Float64, 3, $K}}, x::Array{Float64, 3})
checksize(p, x)
ccall(($(string(fE)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{Float64}, Cint, Cint, Cint), 'T', p, x, size(x, 1), size(x, 2), size(x, 3))
return x
end
end
end
for (fJ, fadJ, fC, fE, K) in ((:plan_spinsph_synthesis, :plan_spinsph_analysis, :ft_plan_spinsph_synthesis, :ft_execute_spinsph_synthesis, SPINSPHERESYNTHESIS),
(:plan_spinsph_analysis, :plan_spinsph_synthesis, :ft_plan_spinsph_analysis, :ft_execute_spinsph_analysis, SPINSPHEREANALYSIS))
@eval begin
$fJ(x::Matrix{T}, s::Integer; y...) where T = $fJ(T, size(x, 1), size(x, 2), s; y...)
function $fJ(::Type{Complex{Float64}}, n::Integer, m::Integer,
gitextract_cc1mg2bt/
├── .github/
│ └── workflows/
│ ├── CIWindows.yml
│ ├── CompatHelper.yml
│ ├── TagBot.yml
│ ├── ci.yml
│ ├── docs.yml
│ └── downstream.yml
├── .gitignore
├── LICENSE.md
├── Project.toml
├── README.md
├── deps/
│ └── build.jl
├── docs/
│ ├── Project.toml
│ ├── make.jl
│ └── src/
│ ├── dev.md
│ └── index.md
├── examples/
│ ├── annulus.jl
│ ├── automaticdifferentiation.jl
│ ├── chebyshev.jl
│ ├── disk.jl
│ ├── halfrange.jl
│ ├── nonlocaldiffusion.jl
│ ├── padua.jl
│ ├── sphere.jl
│ ├── sphericalisometries.jl
│ ├── spinweighted.jl
│ ├── subspaceangles.jl
│ └── triangle.jl
├── src/
│ ├── FastTransforms.jl
│ ├── GramMatrix.jl
│ ├── PaduaTransform.jl
│ ├── ToeplitzPlusHankel.jl
│ ├── arrays.jl
│ ├── chebyshevtransform.jl
│ ├── clenshawcurtis.jl
│ ├── docstrings.jl
│ ├── elliptic.jl
│ ├── fejer.jl
│ ├── gaunt.jl
│ ├── hermite.jl
│ ├── inufft.jl
│ ├── libfasttransforms.jl
│ ├── nufft.jl
│ ├── specialfunctions.jl
│ ├── toeplitzhankel.jl
│ └── toeplitzplans.jl
└── test/
├── arraystests.jl
├── chebyshevtests.jl
├── gaunttests.jl
├── grammatrixtests.jl
├── hermitetests.jl
├── libfasttransformstests.jl
├── nuffttests.jl
├── paduatests.jl
├── quadraturetests.jl
├── runtests.jl
├── specialfunctionstests.jl
├── toeplitzhankeltests.jl
├── toeplitzplanstests.jl
└── toeplitzplushankeltests.jl
Condensed preview — 59 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (358K chars).
[
{
"path": ".github/workflows/CIWindows.yml",
"chars": 1139,
"preview": "name: CI Windows\non:\n - push\n - pull_request\njobs:\n testwindows:\n name: Julia ${{ matrix.version }} - ${{ matrix.o"
},
{
"path": ".github/workflows/CompatHelper.yml",
"chars": 1381,
"preview": "name: CompatHelper\non:\n schedule:\n - cron: 0 0 * * *\n workflow_dispatch:\npermissions:\n contents: write\n pull-requ"
},
{
"path": ".github/workflows/TagBot.yml",
"chars": 362,
"preview": "name: TagBot\non:\n issue_comment:\n types:\n - created\n workflow_dispatch:\njobs:\n TagBot:\n if: github.event_n"
},
{
"path": ".github/workflows/ci.yml",
"chars": 1304,
"preview": "name: CI\non:\n - push\n - pull_request\njobs:\n test:\n name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matr"
},
{
"path": ".github/workflows/docs.yml",
"chars": 417,
"preview": "name: Documentation\non:\n - push\n - pull_request\njobs:\n docs:\n name: Documentation\n runs-on: ubuntu-latest\n s"
},
{
"path": ".github/workflows/downstream.yml",
"chars": 2772,
"preview": "name: IntegrationTest\non:\n push:\n branches: [master]\n tags: [v*]\n paths-ignore:\n - 'LICENSE'\n - 'REA"
},
{
"path": ".gitignore",
"chars": 116,
"preview": "docs/build/\ndocs/src/generated\ndeps/build.log\ndeps/libfasttransforms.*\n.DS_Store\ndeps/FastTransforms/\nManifest.toml\n"
},
{
"path": "LICENSE.md",
"chars": 1289,
"preview": "The FastTransforms.jl package is licensed under the MIT \"Expat\" License:\n\n> Copyright (c) 2016-2019: Richard Mikael Slev"
},
{
"path": "Project.toml",
"chars": 1357,
"preview": "name = \"FastTransforms\"\nuuid = \"057dd010-8810-581a-b7be-e3fc3b93f78c\"\nversion = \"0.17.1\"\n\n\n[deps]\nAbstractFFTs = \"621f49"
},
{
"path": "README.md",
"chars": 6951,
"preview": "# FastTransforms.jl\n\n[](h"
},
{
"path": "deps/build.jl",
"chars": 1670,
"preview": "if get(ENV, \"FT_BUILD_FROM_SOURCE\", \"false\") == \"true\"\n extension = Sys.isapple() ? \"dylib\" : Sys.islinux() ? \"so\" : "
},
{
"path": "docs/Project.toml",
"chars": 417,
"preview": "[deps]\nDocumenter = \"e30172f5-a6a5-5a46-863b-614d45cd2de4\"\nFastTransforms = \"057dd010-8810-581a-b7be-e3fc3b93f78c\"\nLaTeX"
},
{
"path": "docs/make.jl",
"chars": 1849,
"preview": "using Documenter, FastTransforms, Literate, Plots\n\nplotlyjs()\n\nconst EXAMPLES_DIR = joinpath(@__DIR__, \"..\", \"examples\")"
},
{
"path": "docs/src/dev.md",
"chars": 5915,
"preview": "# Development Documentation\n\nThe core of [`FastTransforms.jl`](https://github.com/JuliaApproximation/FastTransforms.jl) "
},
{
"path": "docs/src/index.md",
"chars": 1899,
"preview": "# FastTransforms.jl Documentation\n\n## Introduction\n\n[`FastTransforms.jl`](https://github.com/JuliaApproximation/FastTran"
},
{
"path": "examples/annulus.jl",
"chars": 2656,
"preview": "# # Integration on an annulus\n# In this example, we explore integration of the function:\n# ```math\n# f(x,y) = \\frac{x^"
},
{
"path": "examples/automaticdifferentiation.jl",
"chars": 1269,
"preview": "# # Automatic differentiation through spherical harmonic transforms\n# This example finds a positive value of $\\lambda$ i"
},
{
"path": "examples/chebyshev.jl",
"chars": 1421,
"preview": "# # Chebyshev transform\n# This demonstrates the Chebyshev transform and inverse transform,\n# explaining precisely the no"
},
{
"path": "examples/disk.jl",
"chars": 4578,
"preview": "# # Holomorphic integration on the unit disk\n# In this example, we explore integration of a harmonic function:\n# ```math"
},
{
"path": "examples/halfrange.jl",
"chars": 4043,
"preview": "# # Half-range Chebyshev polynomials\n# In [this paper](https://doi.org/10.1137/090752456), [Daan Huybrechs](https://gith"
},
{
"path": "examples/nonlocaldiffusion.jl",
"chars": 3842,
"preview": "# # Nonlocal diffusion on $\\mathbb{S}^2$\n# This example calculates the spectrum of the nonlocal diffusion operator:\n# ``"
},
{
"path": "examples/padua.jl",
"chars": 798,
"preview": "# # Padua transform\n# This demonstrates the Padua transform and inverse transform,\n# explaining precisely the normalizat"
},
{
"path": "examples/sphere.jl",
"chars": 4988,
"preview": "# # Spherical harmonic addition theorem\n# This example confirms numerically that\n# ```math\n# f(z) = \\frac{P_n(z\\cdot y) "
},
{
"path": "examples/sphericalisometries.jl",
"chars": 2684,
"preview": "function threshold!(A::AbstractArray, ϵ)\n for i in eachindex(A)\n if abs(A[i]) < ϵ A[i] = 0 end\n end\n A\ne"
},
{
"path": "examples/spinweighted.jl",
"chars": 2060,
"preview": "# # Spin-weighted spherical harmonics\n# This example plays with analysis of:\n# ```math\n# f(r) = e^{{\\rm i} k\\cdot r},\n# "
},
{
"path": "examples/subspaceangles.jl",
"chars": 1160,
"preview": "# # Subspace angles\n# This example considers the angles between neighbouring Laguerre polynomials with a perturbed measu"
},
{
"path": "examples/triangle.jl",
"chars": 4129,
"preview": "# # Calculus on the reference triangle\n# In this example, we sample a bivariate function:\n# ```math\n# f(x,y) = \\frac{1}{"
},
{
"path": "src/FastTransforms.jl",
"chars": 5281,
"preview": "module FastTransforms\n\nusing ArrayLayouts, BandedMatrices, FastGaussQuadrature, FillArrays, LazyArrays, LinearAlgebra,\n "
},
{
"path": "src/GramMatrix.jl",
"chars": 13461,
"preview": "abstract type AbstractGramMatrix{T} <: LayoutMatrix{T} end\n\n@inline issymmetric(G::AbstractGramMatrix) = true\n@inline is"
},
{
"path": "src/PaduaTransform.jl",
"chars": 6957,
"preview": "\n# lex indicates if its lexigraphical (i.e., x, y) or reverse (y, x)\n# If in lexigraphical order the coefficient vector'"
},
{
"path": "src/ToeplitzPlusHankel.jl",
"chars": 8744,
"preview": "struct ToeplitzPlusHankel{T, S, P1 <: Plan{S}, P2 <: Plan{S}} <: AbstractMatrix{T}\n tc::Vector{T}\n tr::Vector{T}\n "
},
{
"path": "src/arrays.jl",
"chars": 2348,
"preview": "struct ArrayPlan{T, FF<:FTPlan{<:T}, Szs<:Tuple, Dims<:Tuple{<:Int}} <: Plan{T}\r\n F::FF\r\n szs::Szs\r\n dims::Dims"
},
{
"path": "src/chebyshevtransform.jl",
"chars": 25436,
"preview": "## Transforms take values at Chebyshev points of the first and second kinds and produce Chebyshev coefficients\n\nabstract"
},
{
"path": "src/clenshawcurtis.jl",
"chars": 686,
"preview": "plan_clenshawcurtis(μ) = length(μ) > 1 ? FFTW.plan_r2r!(μ, FFTW.REDFT00) : fill!(similar(μ),1)'\n\n\"\"\"\nCompute nodes of th"
},
{
"path": "src/docstrings.jl",
"chars": 3955,
"preview": "\"\"\"\n\tleg2cheb(v::AbstractVector; normleg::Bool=false, normcheb::Bool=false)\n\nConvert the vector of expansions coefficien"
},
{
"path": "src/elliptic.jl",
"chars": 4974,
"preview": "\"\"\"\n`FastTransforms` submodule for the computation of some elliptic integrals and functions.\n\nComplete elliptic integral"
},
{
"path": "src/fejer.jl",
"chars": 1148,
"preview": "plan_fejer1(μ) = FFTW.plan_r2r!(μ, FFTW.REDFT01)\n\n\"\"\"\nCompute nodes of Fejer's first quadrature rule.\n\"\"\"\nfejernodes1(::"
},
{
"path": "src/gaunt.jl",
"chars": 6994,
"preview": "\"\"\"\nCalculates the Gaunt coefficients, defined by:\n\n```math\na(m,n,\\\\mu,\\\\nu,q) = \\\\frac{2(n+\\\\nu-2q)+1}{2} \\\\frac{(n+\\\\n"
},
{
"path": "src/hermite.jl",
"chars": 937,
"preview": "# exp(-x^2/2) H_n(x) / sqrt(π*prod(1:n))\n\nstruct ForwardWeightedHermitePlan{T}\n Vtw::Matrix{T} # vandermonde\nend\n\nstr"
},
{
"path": "src/inufft.jl",
"chars": 3111,
"preview": "\"\"\"\nPre-computes an inverse nonuniform fast Fourier transform of type `N`.\n\nFor best performance, choose the right numbe"
},
{
"path": "src/libfasttransforms.jl",
"chars": 69551,
"preview": "if get(ENV, \"FT_BUILD_FROM_SOURCE\", \"false\") == \"true\"\n using Libdl\n const libfasttransforms = find_library(\"libfa"
},
{
"path": "src/nufft.jl",
"chars": 11222,
"preview": "\"\"\"\nPre-computes a nonuniform fast Fourier transform of type `N`.\n\nFor best performance, choose the right number of thre"
},
{
"path": "src/specialfunctions.jl",
"chars": 21290,
"preview": "import Base.Math: @horner\n\nconst FORWARD = true\nconst BACKWARD = false\n\nconst sqrtpi = 1.772453850905516027298\nconst e"
},
{
"path": "src/toeplitzhankel.jl",
"chars": 21553,
"preview": "\"\"\"\nRepresent a scaled Toeplitz∘Hankel matrix:\n\n DL(T∘H)DR\n\nwhere the Hankel matrix `H` is non-negative definite, via"
},
{
"path": "src/toeplitzplans.jl",
"chars": 3096,
"preview": "using FFTW\nimport FFTW: plan_r2r!\n\n\n\"\"\"\n ToeplitzPlan\n\napplies Toeplitz matrices fast along each dimension.\n\"\"\"\n\nstru"
},
{
"path": "test/arraystests.jl",
"chars": 1687,
"preview": "using FastTransforms, Test\r\nimport FastTransforms: ArrayPlan, NDimsPlan\r\n\r\n@testset \"Array transform\" begin\r\n @tests"
},
{
"path": "test/chebyshevtests.jl",
"chars": 26142,
"preview": "using FastTransforms, Test\n\n@testset \"Chebyshev transform\" begin\n @testset \"Chebyshev points\" begin\n @test @i"
},
{
"path": "test/gaunttests.jl",
"chars": 927,
"preview": "using FastTransforms, LinearAlgebra, Test\n\nimport FastTransforms: δ\n\n@testset \"Gaunt coefficients\" begin\n # Table 2 o"
},
{
"path": "test/grammatrixtests.jl",
"chars": 3817,
"preview": "using FastTransforms, BandedMatrices, LazyArrays, LinearAlgebra, Test\n\n@testset \"GramMatrix\" begin\n n = 128\n for T"
},
{
"path": "test/hermitetests.jl",
"chars": 1336,
"preview": "using FastTransforms, FastGaussQuadrature, Test\n\nhermitepoints(n) = FastGaussQuadrature.unweightedgausshermite( n )[1]\n\n"
},
{
"path": "test/libfasttransformstests.jl",
"chars": 8434,
"preview": "using FastTransforms, Test\n\nFastTransforms.ft_set_num_threads(ceil(Int, Base.Sys.CPU_THREADS/2))\n\n@testset \"libfasttrans"
},
{
"path": "test/nuffttests.jl",
"chars": 4121,
"preview": "using FFTW, FastTransforms, LinearAlgebra, Test\n\nFFTW.set_num_threads(ceil(Int, Sys.CPU_THREADS/2))\n\n@testset \"Nonunifor"
},
{
"path": "test/paduatests.jl",
"chars": 1924,
"preview": "using FastTransforms, Test\n\n@testset \"Padua transform and its inverse\" begin\n n=200\n N=div((n+1)*(n+2),2)\n v=ra"
},
{
"path": "test/quadraturetests.jl",
"chars": 1571,
"preview": "using FastTransforms, LinearAlgebra, Test\n\nimport FastTransforms: chebyshevmoments1, chebyshevmoments2,\n "
},
{
"path": "test/runtests.jl",
"chars": 438,
"preview": "using FastTransforms, LinearAlgebra, Test\n\ninclude(\"specialfunctionstests.jl\")\ninclude(\"chebyshevtests.jl\")\ninclude(\"qua"
},
{
"path": "test/specialfunctionstests.jl",
"chars": 1874,
"preview": "using FastTransforms, LinearAlgebra, Test\n\nimport FastTransforms: pochhammer, sqrtpi, gamma, lgamma\nimport FastTransform"
},
{
"path": "test/toeplitzhankeltests.jl",
"chars": 10211,
"preview": "using FastTransforms, Test, Random\nimport FastTransforms: th_leg2cheb, th_cheb2leg, th_leg2chebu, th_ultra2ultra,th_jac2"
},
{
"path": "test/toeplitzplanstests.jl",
"chars": 3800,
"preview": "using FastTransforms, Test\nimport FastTransforms: plan_uppertoeplitz!\n\n@testset \"ToeplitzPlan\" begin\n @testset \"Vecto"
},
{
"path": "test/toeplitzplushankeltests.jl",
"chars": 394,
"preview": "using FastTransforms, LinearAlgebra, Test\n\nimport FastTransforms: normest\n\n@testset \"ToeplitzPlusHankel\" begin\n n = 1"
}
]
About this extraction
This page contains the full source code of the JuliaApproximation/FastTransforms.jl GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 59 files (331.9 KB), approximately 125.9k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.