master 14a311816ffb cached
59 files
331.9 KB
125.9k tokens
1 requests
Download .txt
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

[![Build Status](https://github.com/JuliaApproximation/FastTransforms.jl/workflows/CI/badge.svg)](https://github.com/JuliaApproximation/FastTransforms.jl/actions?query=workflow%3ACI) [![codecov](https://codecov.io/gh/JuliaApproximation/FastTransforms.jl/branch/master/graph/badge.svg?token=BxTvSNgmLL)](https://codecov.io/gh/JuliaApproximation/FastTransforms.jl) [![](https://img.shields.io/badge/docs-stable-blue.svg)](https://JuliaApproximation.github.io/FastTransforms.jl/stable) [![](https://img.shields.io/badge/docs-dev-blue.svg)](https://JuliaApproximation.github.io/FastTransforms.jl/dev)
[![pkgeval](https://juliahub.com/docs/General/FastTransforms/stable/pkgeval.svg)](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,
Download .txt
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[![Build Status](https://github.com/JuliaApproximation/FastTransforms.jl/workflows/CI/badge.svg)](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.

Copied to clipboard!