[
  {
    "path": ".github/workflows/CIWindows.yml",
    "content": "name: CI Windows\non:\n  - push\n  - pull_request\njobs:\n  testwindows:\n    name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        version:\n          - '1'\n        os:\n          - windows-latest\n        arch:\n          - x86\n          - x64\n    steps:\n      - uses: actions/checkout@v3\n      - uses: julia-actions/setup-julia@v1\n        with:\n          version: ${{ matrix.version }}\n          arch: ${{ matrix.arch }}\n          show-versioninfo: true\n      - uses: actions/cache@v3\n        env:\n          cache-name: cache-artifacts\n        with:\n          path: ~/.julia/artifacts\n          key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}\n          restore-keys: |\n            ${{ runner.os }}-test-${{ env.cache-name }}-\n            ${{ runner.os }}-test-\n            ${{ runner.os }}-\n      - uses: julia-actions/julia-buildpkg@latest\n      - uses: julia-actions/julia-runtest@latest\n      - uses: julia-actions/julia-processcoverage@v1\n      - uses: codecov/codecov-action@v3\n        with:\n          file: lcov.info\n"
  },
  {
    "path": ".github/workflows/CompatHelper.yml",
    "content": "name: CompatHelper\non:\n  schedule:\n    - cron: 0 0 * * *\n  workflow_dispatch:\npermissions:\n  contents: write\n  pull-requests: write\njobs:\n  CompatHelper:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Check if Julia is already available in the PATH\n        id: julia_in_path\n        run: which julia\n        continue-on-error: true\n      - name: Install Julia, but only if it is not already available in the PATH\n        uses: julia-actions/setup-julia@v1\n        with:\n          version: '1'\n          arch: ${{ runner.arch }}\n        if: steps.julia_in_path.outcome != 'success'\n      - name: \"Add the General registry via Git\"\n        run: |\n          import Pkg\n          ENV[\"JULIA_PKG_SERVER\"] = \"\"\n          Pkg.Registry.add(\"General\")\n        shell: julia --color=yes {0}\n      - name: \"Install CompatHelper\"\n        run: |\n          import Pkg\n          name = \"CompatHelper\"\n          uuid = \"aa819f21-2bde-4658-8897-bab36330d9b7\"\n          version = \"3\"\n          Pkg.add(; name, uuid, version)\n        shell: julia --color=yes {0}\n      - name: \"Run CompatHelper\"\n        run: |\n          import CompatHelper\n          CompatHelper.main()\n        shell: julia --color=yes {0}\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }}\n          # COMPATHELPER_PRIV: ${{ secrets.COMPATHELPER_PRIV }}\n"
  },
  {
    "path": ".github/workflows/TagBot.yml",
    "content": "name: TagBot\non:\n  issue_comment:\n    types:\n      - created\n  workflow_dispatch:\njobs:\n  TagBot:\n    if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: JuliaRegistries/TagBot@v1\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n          ssh: ${{ secrets.DOCUMENTER_KEY }}\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\non:\n  - push\n  - pull_request\njobs:\n  test:\n    name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        version:\n          - 'lts'\n          - '1'\n        os:\n          - ubuntu-latest\n#          - macOS-latest\n        arch:\n          - x86\n          - x64\n        exclude:\n          - os: macOS-latest\n            arch: x86\n    steps:\n      - uses: actions/checkout@v4\n      - uses: julia-actions/setup-julia@v2\n        with:\n          version: ${{ matrix.version }}\n          arch: ${{ matrix.arch }}\n          show-versioninfo: true\n      - uses: actions/cache@v3\n        env:\n          cache-name: cache-artifacts\n        with:\n          path: ~/.julia/artifacts\n          key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}\n          restore-keys: |\n            ${{ runner.os }}-test-${{ env.cache-name }}-\n            ${{ runner.os }}-test-\n            ${{ runner.os }}-\n      - uses: julia-actions/julia-buildpkg@latest\n      - uses: julia-actions/julia-runtest@latest\n      - uses: julia-actions/julia-processcoverage@v1\n      - uses: codecov/codecov-action@v4\n        with:\n          token: ${{ secrets.CODECOV_TOKEN }}\n          file: lcov.info\n"
  },
  {
    "path": ".github/workflows/docs.yml",
    "content": "name: Documentation\non:\n  - push\n  - pull_request\njobs:\n  docs:\n    name: Documentation\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - uses: julia-actions/setup-julia@v1\n        with:\n          version: '1'\n      - uses: julia-actions/julia-docdeploy@releases/v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}\n"
  },
  {
    "path": ".github/workflows/downstream.yml",
    "content": "name: IntegrationTest\non:\n  push:\n    branches: [master]\n    tags: [v*]\n    paths-ignore:\n      - 'LICENSE'\n      - 'README.md'\n      - '.github/workflows/TagBot.yml'\n  pull_request:\n    paths-ignore:\n      - 'LICENSE'\n      - 'README.md'\n      - '.github/workflows/TagBot.yml'\n\nconcurrency:\n  group: build-${{ github.event.pull_request.number || github.ref }}-${{ github.workflow }}\n  cancel-in-progress: true\n\njobs:\n  pre_job:\n    # continue-on-error: true # Uncomment once integration is finished\n    runs-on: ubuntu-latest\n    # Map a step output to a job output\n    outputs:\n      should_skip: ${{ steps.skip_check.outputs.should_skip }}\n    steps:\n      - id: skip_check\n        uses: fkirc/skip-duplicate-actions@v5\n  test:\n    needs: pre_job\n    if: needs.pre_job.outputs.should_skip != 'true'\n    name: ${{ matrix.package.group }}/${{ matrix.package.repo }}/${{ matrix.julia-version }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        julia-version: ['1']\n        os: [ubuntu-latest]\n        package:\n          - {repo: ClassicalOrthogonalPolynomials.jl, group: JuliaApproximation}\n          - {repo: MultivariateOrthogonalPolynomials.jl, group: JuliaApproximation}\n          - {repo: ApproxFun.jl, group: JuliaApproximation}\n\n    steps:\n      - uses: actions/checkout@v4\n      - uses: julia-actions/setup-julia@v2\n        with:\n          version: ${{ matrix.julia-version }}\n          arch: x64\n      - uses: julia-actions/julia-buildpkg@latest\n      - name: Clone Downstream\n        uses: actions/checkout@v4\n        with:\n          repository: ${{ matrix.package.group }}/${{ matrix.package.repo }}\n          path: downstream\n      - name: Load this and run the downstream tests\n        shell: julia --color=yes --project=downstream {0}\n        run: |\n          using Pkg\n          try\n            # force it to use this PR's version of the package\n            Pkg.develop(PackageSpec(path=\".\"))  # resolver may fail with main deps\n            Pkg.update()\n            Pkg.test(; coverage = true)  # resolver may fail with test time deps\n          catch err\n            err isa Pkg.Resolve.ResolverError || rethrow()\n            # If we can't resolve that means this is incompatible by SemVer and this is fine\n            # It means we marked this as a breaking change, so we don't need to worry about\n            # Mistakenly introducing a breaking change, as we have intentionally made one\n            @info \"Not compatible with this release. No problem.\" exception=err\n            exit(0)  # Exit immediately, as a success\n          end\n      - uses: julia-actions/julia-processcoverage@v1\n      - uses: codecov/codecov-action@v4\n        with:\n          token: ${{ secrets.CODECOV_TOKEN }}\n          files: lcov.info\n"
  },
  {
    "path": ".gitignore",
    "content": "docs/build/\ndocs/src/generated\ndeps/build.log\ndeps/libfasttransforms.*\n.DS_Store\ndeps/FastTransforms/\nManifest.toml\n"
  },
  {
    "path": "LICENSE.md",
    "content": "The FastTransforms.jl package is licensed under the MIT \"Expat\" License:\n\n> Copyright (c) 2016-2019: Richard Mikael Slevinsky and other contributors:\n>\n> https://github.com/JuliaApproximation/FastTransforms.jl/graphs/contributors\n>\n> Permission is hereby granted, free of charge, to any person obtaining\n> a copy of this software and associated documentation files (the\n> \"Software\"), to deal in the Software without restriction, including\n> without limitation the rights to use, copy, modify, merge, publish,\n> distribute, sublicense, and/or sell copies of the Software, and to\n> permit persons to whom the Software is furnished to do so, subject to\n> the following conditions:\n>\n> The above copyright notice and this permission notice shall be\n> included in all copies or substantial portions of the Software.\n>\n> THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "Project.toml",
    "content": "name = \"FastTransforms\"\nuuid = \"057dd010-8810-581a-b7be-e3fc3b93f78c\"\nversion = \"0.17.1\"\n\n\n[deps]\nAbstractFFTs = \"621f4979-c628-5d54-868e-fcf4e3e8185c\"\nArrayLayouts = \"4c555306-a7a7-4459-81d9-ec55ddd5c99a\"\nBandedMatrices = \"aae01518-5342-5314-be14-df237901396f\"\nFFTW = \"7a1cc6ca-52ef-59f5-83cd-3a7055c09341\"\nFastGaussQuadrature = \"442a2c76-b920-505d-bb47-c5924d526838\"\nFastTransforms_jll = \"34b6f7d7-08f9-5794-9e10-3819e4c7e49a\"\nFillArrays = \"1a297f60-69ca-5386-bcde-b61e274b549b\"\nGenericFFT = \"a8297547-1b15-4a5a-a998-a2ac5f1cef28\"\nLazyArrays = \"5078a376-72f3-5289-bfd5-ec5146d43c02\"\nLibdl = \"8f399da3-3557-5675-b5ff-fb832c97cbdb\"\nLinearAlgebra = \"37e2e46d-f89d-539d-b4ee-838fcccc9c8e\"\nRecurrenceRelationships = \"807425ed-42ea-44d6-a357-6771516d7b2c\"\nSpecialFunctions = \"276daf66-3868-5448-9aa4-cd146d93841b\"\nToeplitzMatrices = \"c751599d-da0a-543b-9d20-d0a503d91d24\"\n\n[compat]\nAbstractFFTs = \"1.0\"\nArrayLayouts = \"1.10\"\nBandedMatrices = \"1.5\"\nFFTW = \"1.7\"\nFastGaussQuadrature = \"0.4, 0.5, 1\"\nFastTransforms_jll = \"0.6.2\"\nFillArrays = \"0.9, 0.10, 0.11, 0.12, 0.13, 1\"\nGenericFFT = \"0.1\"\nLazyArrays = \"2.2\"\nRecurrenceRelationships = \"0.2\"\nSpecialFunctions = \"0.10, 1, 2\"\nToeplitzMatrices = \"0.7.1, 0.8\"\njulia = \"1.7\"\n\n[extras]\nRandom = \"9a3f8284-a2c9-5f02-9a11-845980a1fd5c\"\nTest = \"8dfed614-e22c-5e08-85e1-65c5234f0b40\"\n\n[targets]\ntest = [\"Test\", \"Random\"]\n"
  },
  {
    "path": "README.md",
    "content": "# FastTransforms.jl\n\n[![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)\n[![pkgeval](https://juliahub.com/docs/General/FastTransforms/stable/pkgeval.svg)](https://juliaci.github.io/NanosoldierReports/pkgeval_badges/report.html)\n\n`FastTransforms.jl` allows the user to conveniently work with orthogonal polynomials with degrees well into the millions.\n\nThis 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.\n\n## Installation\n\nInstallation, 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:\n\n```julia\npkg> add FastTransforms\n\njulia> using FastTransforms, LinearAlgebra\n\n```\n\n## Fast orthogonal polynomial transforms\n\nThe 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:\n\n### The Chebyshev--Legendre transform\n\n```julia\njulia> c = rand(8192);\n\njulia> leg2cheb(c);\n\njulia> cheb2leg(c);\n\njulia> norm(cheb2leg(leg2cheb(c; normcheb=true); normcheb=true)-c)/norm(c)\n1.1866591414786334e-14\n\n```\n\nThe 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.\n\n```julia\njulia> p1 = plan_leg2cheb(c);\n\njulia> p2 = plan_cheb2leg(c);\n\njulia> @time leg2cheb(c);\n  0.433938 seconds (9 allocations: 64.641 KiB)\n\njulia> @time p1*c;\n  0.005713 seconds (8 allocations: 64.594 KiB)\n\njulia> @time cheb2leg(c);\n  0.423865 seconds (9 allocations: 64.641 KiB)\n\njulia> @time p2*c;\n  0.005829 seconds (8 allocations: 64.594 KiB)\n\n```\n\nFurthermore, for orthogonal polynomial connection problems that are degree-preserving, we should expect to be able to apply the transforms in-place:\n\n```julia\njulia> lmul!(p1, c);\n\njulia> lmul!(p2, c);\n\njulia> ldiv!(p1, c);\n\njulia> ldiv!(p2, c);\n\n```\n\n### The spherical harmonic transform\n\nLet `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.\n\n```julia\njulia> F = sphrandn(Float64, 1024, 2047); # convenience method\n\njulia> P = plan_sph2fourier(F);\n\njulia> PS = plan_sph_synthesis(F);\n\njulia> PA = plan_sph_analysis(F);\n\njulia> @time G = PS*(P*F);\n  0.090767 seconds (12 allocations: 31.985 MiB, 1.46% gc time)\n\njulia> @time H = P\\(PA*G);\n  0.092726 seconds (12 allocations: 31.985 MiB, 1.63% gc time)\n\njulia> norm(F-H)/norm(F)\n2.1541073345177038e-15\n\n```\n\nDue to the structure of the spherical harmonic connection problem, these transforms may also be performed in-place with `lmul!` and `ldiv!`.\n\nSee also [FastSphericalHarmonics.jl](https://github.com/eschnett/FastSphericalHarmonics.jl) for a simpler interface to the spherical harmonic transforms defined in this package.\n\n## Nonuniform fast Fourier transforms\n\nThe NUFFTs are implemented thanks to [Alex Townsend](https://github.com/ajt60gaibb):\n - `nufft1` assumes uniform samples and noninteger frequencies;\n - `nufft2` assumes nonuniform samples and integer frequencies;\n - `nufft3 ( = nufft)` assumes nonuniform samples and noninteger frequencies;\n - `inufft1` inverts an `nufft1`; and,\n - `inufft2` inverts an `nufft2`.\n\nHere is an example:\n\n```julia\njulia> n = 10^4;\n\njulia> c = complex(rand(n));\n\njulia> ω = collect(0:n-1) + rand(n);\n\njulia> nufft1(c, ω, eps());\n\njulia> p1 = plan_nufft1(ω, eps());\n\njulia> @time p1*c;\n  0.002383 seconds (6 allocations: 156.484 KiB)\n\njulia> x = (collect(0:n-1) + 3rand(n))/n;\n\njulia> nufft2(c, x, eps());\n\njulia> p2 = plan_nufft2(x, eps());\n\njulia> @time p2*c;\n  0.001478 seconds (6 allocations: 156.484 KiB)\n\njulia> nufft3(c, x, ω, eps());\n\njulia> p3 = plan_nufft3(x, ω, eps());\n\njulia> @time p3*c;\n  0.058999 seconds (6 allocations: 156.484 KiB)\n\n```\n\n## The Padua Transform\n\nThe 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`.\n\n```julia\njulia> n = 200;\n\njulia> N = div((n+1)*(n+2), 2);\n\njulia> v = rand(N); # The length of v is the number of Padua points\n\njulia> @time norm(ipaduatransform(paduatransform(v)) - v)/norm(v)\n  0.007373 seconds (543 allocations: 1.733 MiB)\n3.925164683252905e-16\n\n```\n\n# References\n\n[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.\n\n[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.\n\n[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.\n\n[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.\n\n[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.\n\n[6]  R. M. Slevinsky, [Conquering the pre-computation in two-dimensional harmonic polynomial transforms](https://arxiv.org/abs/1711.07866), arXiv:1711.07866, 2017.\n"
  },
  {
    "path": "deps/build.jl",
    "content": "if get(ENV, \"FT_BUILD_FROM_SOURCE\", \"false\") == \"true\"\n    extension = Sys.isapple() ? \"dylib\" : Sys.islinux() ? \"so\" : Sys.iswindows() ? \"dll\" : \"\"\n    make = Sys.iswindows() ? \"mingw32-make\" : \"make\"\n    flags = Sys.isapple() ? \"FT_USE_APPLEBLAS=1\" : Sys.iswindows() ? \"FT_FFTW_WITH_COMBINED_THREADS=1\" : \"\"\n    script = \"\"\"\n        set -e\n        set -x\n        if [ -d \"FastTransforms\" ]; then\n            cd FastTransforms\n            git fetch\n            git checkout master\n            git pull\n            cd ..\n        else\n            git clone https://github.com/MikaelSlevinsky/FastTransforms.git FastTransforms\n        fi\n        cd FastTransforms\n        $make assembly\n        $make lib $flags\n        cd ..\n        mv -f FastTransforms/libfasttransforms.$extension libfasttransforms.$extension\n    \"\"\"\n    try\n        run(`bash -c $(script)`)\n    catch\n        error(\n            \"FastTransforms could not be properly installed.\\n Please check that you have all dependencies installed. \" *\n            \"Sample installation of dependencies:\\n\" *\n            (Sys.isapple() ? \"On MacOS\\n\\tbrew install libomp fftw mpfr\\n\" :\n             Sys.islinux() ? \"On Linux\\n\\tsudo apt-get install libomp-dev libblas-dev libopenblas-base libfftw3-dev libmpfr-dev\\n\" :\n             Sys.iswindows() ? \"On Windows\\n\\tvcpkg install openblas:x64-windows fftw3[core,threads]:x64-windows mpir:x64-windows mpfr:x64-windows\\n\" :\n             \"On your platform, please consider opening a pull request to add support to build from source.\\n\")\n        )\n    end\n    println(\"FastTransforms built from source.\")\nelse\n    println(\"FastTransforms using precompiled binaries.\")\nend\n"
  },
  {
    "path": "docs/Project.toml",
    "content": "[deps]\nDocumenter = \"e30172f5-a6a5-5a46-863b-614d45cd2de4\"\nFastTransforms = \"057dd010-8810-581a-b7be-e3fc3b93f78c\"\nLaTeXStrings = \"b964fa9f-0449-5b57-a5c2-d3ea65f4040f\"\nLazyArrays = \"5078a376-72f3-5289-bfd5-ec5146d43c02\"\nLiterate = \"98b081ad-f1c9-55d3-8b20-4c87d4299306\"\nPlotlyJS = \"f0f68f2c-4968-5e81-91da-67840de0976a\"\nPlots = \"91a5bcdd-55d7-5caf-9e0b-520d859cae80\"\n\n[compat]\nDocumenter = \"~0.24\"\nLiterate = \"~2.8\"\n"
  },
  {
    "path": "docs/make.jl",
    "content": "using Documenter, FastTransforms, Literate, Plots\n\nplotlyjs()\n\nconst EXAMPLES_DIR = joinpath(@__DIR__, \"..\", \"examples\")\nconst OUTPUT_DIR   = joinpath(@__DIR__, \"src/generated\")\n\nexamples = [\n    \"annulus.jl\",\n    \"automaticdifferentiation.jl\",\n    \"chebyshev.jl\",\n    \"disk.jl\",\n    \"halfrange.jl\",\n    \"nonlocaldiffusion.jl\",\n    \"padua.jl\",\n    \"sphere.jl\",\n    \"spinweighted.jl\",\n    \"subspaceangles.jl\",\n    \"triangle.jl\",\n]\n\nfunction uncomment_objects(str)\n    str = replace(str, \"###```@raw\" => \"```\\n\\n```@raw\")\n    str = replace(str, \"###<object\" => \"<object\")\n    str = replace(str, \"###```\\n```\" => \"```\")\n    str\nend\n\nfor example in examples\n    example_filepath = joinpath(EXAMPLES_DIR, example)\n    Literate.markdown(example_filepath, OUTPUT_DIR; execute=true, postprocess = uncomment_objects)\nend\n\nmakedocs(\n            doctest = false,\n            format = Documenter.HTML(),\n            sitename = \"FastTransforms.jl\",\n            authors = \"Richard Mikael Slevinsky\",\n            pages = Any[\n                    \"Home\" => \"index.md\",\n                    \"Development\" => \"dev.md\",\n                    \"Examples\" => [\n                        \"generated/annulus.md\",\n                        \"generated/automaticdifferentiation.md\",\n                        \"generated/chebyshev.md\",\n                        \"generated/disk.md\",\n                        \"generated/halfrange.md\",\n                        \"generated/nonlocaldiffusion.md\",\n                        \"generated/padua.md\",\n                        \"generated/sphere.md\",\n                        \"generated/spinweighted.md\",\n                        \"generated/subspaceangles.md\",\n                        \"generated/triangle.md\",\n                        ],\n                    ]\n        )\n\n\ndeploydocs(\n    repo   = \"github.com/JuliaApproximation/FastTransforms.jl.git\",\n    )\n"
  },
  {
    "path": "docs/src/dev.md",
    "content": "# Development Documentation\n\nThe 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.\n\n## Why two packages?\n\nOrthogonal 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.\n\nThe 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.\n\nC 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.\n\nIn 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.\n\n## The developer's right to build from source\n\nPrecompiled 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:\n\n```julia\njulia> ENV[\"FT_BUILD_FROM_SOURCE\"] = \"true\"\n\"true\"\n\n(@v1.5) pkg> build FastTransforms\n   Building FFTW ──────────→ `~/.julia/packages/FFTW/ayqyZ/deps/build.log`\n   Building TimeZones ─────→ `~/.julia/packages/TimeZones/K98G0/deps/build.log`\n   Building FastTransforms → `~/.julia/dev/FastTransforms/deps/build.log`\n\njulia> using FastTransforms\n[ Info: Precompiling FastTransforms [057dd010-8810-581a-b7be-e3fc3b93f78c]\n\n```\n\nThis 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.\n\n## From release to release to release\n\nTo 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:\n\n```julia\nshell> curl https://codeload.github.com/MikaelSlevinsky/FastTransforms/tar.gz/v0.6.2 --output FastTransforms.tar.gz\n  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current\n                                 Dload  Upload   Total   Spent    Left  Speed\n100  168k    0  168k    0     0   429k      0 --:--:-- --:--:-- --:--:--  429k\n\nshell> shasum -a 256 FastTransforms.tar.gz\nfd00befcb0c20ba962a8744a7b9139355071ee95be70420de005b7c0f6e023aa  FastTransforms.tar.gz\n\nshell> rm -f FastTransforms.tar.gz\n\n```\n\nUsing [SHA.jl](https://github.com/JuliaCrypto/SHA.jl), the SHA can also be found by:\n\n```julia\nshell> curl https://codeload.github.com/MikaelSlevinsky/FastTransforms/tar.gz/v0.6.2 --output FastTransforms.tar.gz\n  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current\n                                 Dload  Upload   Total   Spent    Left  Speed\n100  168k    0  168k    0     0   442k      0 --:--:-- --:--:-- --:--:--  443k\n\njulia> using SHA\n\njulia> open(\"FastTransforms.tar.gz\") do f\n           bytes2hex(sha256(f))\n       end\n\"fd00befcb0c20ba962a8744a7b9139355071ee95be70420de005b7c0f6e023aa\"\n\nshell> rm -f FastTransforms.tar.gz\n\n```\n\nThen 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.\n\nSince 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:\n\n```\ngit fetch upstream\ngit checkout master\ngit reset --hard upstream/master\ngit push origin master --force\n```\n"
  },
  {
    "path": "docs/src/index.md",
    "content": "# FastTransforms.jl Documentation\n\n## Introduction\n\n[`FastTransforms.jl`](https://github.com/JuliaApproximation/FastTransforms.jl) allows the user to conveniently work with orthogonal polynomials with degrees well into the millions.\n\nThis 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.\n\n## Fast orthogonal polynomial transforms\n\nFor 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.\n\n### Modified orthogonal polynomials via fast Cholesky factorization of the Gram matrix\n\n```@docs\nGramMatrix\nChebyshevGramMatrix\n```\n\n## Nonuniform fast Fourier transforms\n\n```@docs\nnufft1\nnufft2\nnufft3\ninufft1\ninufft2\npaduatransform\nipaduatransform\n```\n\n## Other Exported Methods\n\n```@docs\ngaunt\npaduapoints\nsphevaluate\n```\n\n## Internal Methods\n\n### Miscellaneous Special Functions\n\n```@docs\nFastTransforms.half\nFastTransforms.two\nFastTransforms.δ\nFastTransforms.Λ\nFastTransforms.lambertw\nFastTransforms.pochhammer\nFastTransforms.stirlingseries\n```\n\n### Modified Chebyshev Moment-Based Quadrature\n\n```@docs\nFastTransforms.clenshawcurtisnodes\nFastTransforms.clenshawcurtisweights\nFastTransforms.fejernodes1\nFastTransforms.fejerweights1\nFastTransforms.fejernodes2\nFastTransforms.fejerweights2\nFastTransforms.chebyshevmoments1\nFastTransforms.chebyshevjacobimoments1\nFastTransforms.chebyshevlogmoments1\nFastTransforms.chebyshevmoments2\nFastTransforms.chebyshevjacobimoments2\nFastTransforms.chebyshevlogmoments2\n```\n\n### Elliptic Submodule\n\n```@docs\nFastTransforms.Elliptic\n```\n"
  },
  {
    "path": "examples/annulus.jl",
    "content": "# # Integration on an annulus\n# In this example, we explore integration of the function:\n# ```math\n#   f(x,y) = \\frac{x^3}{x^2+y^2-\\frac{1}{4}},\n# ```\n# over the annulus defined by $\\{(r,\\theta) : \\rho < r < 1, 0 < \\theta < 2\\pi\\}$\n# with parameter $\\rho = \\frac{2}{3}$. We will calculate the integral:\n# ```math\n#   \\int_0^{2\\pi}\\int_{\\frac{2}{3}}^1 f(r\\cos\\theta,r\\sin\\theta)^2r{\\rm\\,d}r{\\rm\\,d}\\theta,\n# ```\n# by analyzing the function in an annulus polynomial series.\n# We analyze the function on an $N\\times M$ tensor product grid defined by:\n# ```math\n# \\begin{aligned}\n# 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}\\\\\n# \\theta_m & = 2\\pi m/M,\\quad{\\rm for}\\quad 0\\le m < M;\n# \\end{aligned}\n# ```\n# we convert the function samples to Chebyshev×Fourier coefficients using\n# `plan_annulus_analysis`; and finally, we transform the Chebyshev×Fourier\n# coefficients to annulus polynomial coefficients using `plan_ann2cxf`.\n#\n# For the storage pattern of the arrays, please consult the\n# [documentation](https://MikaelSlevinsky.github.io/FastTransforms).\n\nusing FastTransforms, LinearAlgebra, Plots\nconst GENFIGS = joinpath(pkgdir(FastTransforms), \"docs/src/generated\")\n!isdir(GENFIGS) && mkdir(GENFIGS)\nplotlyjs()\n\n# Our function $f$ on the annulus:\nf = (x,y) -> x^3/(x^2+y^2-1/4)\n\n# The annulus polynomial degree:\nN = 8\nM = 4N-3\n\n# The annulus inner radius:\nρ = 2/3\n\n# The radial grid:\nr = [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]\n\n# The angular grid (mod $\\pi$):\nθ = (0:M-1)*2/M\n\n# On the mapped tensor product grid, our function samples are:\nF = [f(r*cospi(θ), r*sinpi(θ)) for r in r, θ in θ]\n\n# We superpose a surface plot of $f$ on top of the grid:\nX = [r*cospi(θ) for r in r, θ in θ]\nY = [r*sinpi(θ) for r in r, θ in θ]\nscatter3d(vec(X), vec(Y), vec(0F); markersize=0.75, markercolor=:red)\nsurface!(X, Y, F; legend=false, xlabel=\"x\", ylabel=\"y\", zlabel=\"f\")\nsavefig(joinpath(GENFIGS, \"annulus.html\"))\n###```@raw html\n###<object type=\"text/html\" data=\"../annulus.html\" style=\"width:100%;height:400px;\"></object>\n###```\n\n# We precompute an Annulus--Chebyshev×Fourier plan:\nα, β, γ = 0, 0, 0\nP = plan_ann2cxf(F, α, β, γ, ρ)\n\n# And an FFTW Chebyshev×Fourier analysis plan on the annulus:\nPA = plan_annulus_analysis(F, ρ)\n\n# Its annulus coefficients are:\nU = P\\(PA*F)\n\n# The annulus coefficients are useful for integration.\n# The integral of $[f(x,y)]^2$ over the annulus is\n# approximately the square of the 2-norm of the coefficients:\nnorm(U)^2, 5π/8*(1675/4536+9*log(3)/32-3*log(7)/32)\n"
  },
  {
    "path": "examples/automaticdifferentiation.jl",
    "content": "# # Automatic differentiation through spherical harmonic transforms\n# This example finds a positive value of $\\lambda$ in:\n# ```math\n# f(r) = \\sin[\\lambda (k\\cdot r)],\n# ```\n# for some $k,r\\in\\mathbb{S}^2$ such that $\\int_{\\mathbb{S}^2} f^2 {\\rm\\,d}\\Omega = 1$.\n# We do this by using derivative information through:\n# ```math\n# \\dfrac{\\partial f}{\\partial \\lambda} = (k\\cdot r) \\cos[\\lambda (k\\cdot r)].\n# ```\n\nusing FastTransforms, LinearAlgebra\n\n# The colatitudinal grid (mod $\\pi$):\nN = 15\nθ = (0.5:N-0.5)/N\n\n# The longitudinal grid (mod $\\pi$):\nM = 2*N-1\nφ = (0:M-1)*2/M\n\n# We precompute a spherical harmonic--Fourier plan:\nP = plan_sph2fourier(Float64, N)\n\n# And an FFTW Fourier analysis plan on $\\mathbb{S}^2$:\nPA = plan_sph_analysis(Float64, N, M)\n\n# Our choice of $k$ and angular parametrization of $r$:\nk = [2/7, 3/7, 6/7]\nr = (θ,φ) -> [sinpi(θ)*cospi(φ), sinpi(θ)*sinpi(φ), cospi(θ)]\n\n# Our initial guess for $\\lambda$:\nλ = 1.0\n\n# Then we run Newton iteration and grab an espresso:\nfor _ in 1:7\n    F = [sin(λ*(k⋅r(θ,φ))) for θ in θ, φ in φ]\n    Fλ = [(k⋅r(θ,φ))*cos(λ*(k⋅r(θ,φ))) for θ in θ, φ in φ]\n    U = P\\(PA*F)\n    Uλ = P\\(PA*Fλ)\n    global λ = λ - (norm(U)^2-1)/(2*sum(U.*Uλ))\n    println(\"λ: $(rpad(λ, 18)) and the 2-norm: $(rpad(norm(U), 18))\")\nend\n"
  },
  {
    "path": "examples/chebyshev.jl",
    "content": "# # Chebyshev transform\n# This demonstrates the Chebyshev transform and inverse transform,\n# explaining precisely the normalization and points\n\nusing FastTransforms\nn = 20\n\n# First kind points $\\to$ first kind polynomials\np_1 = chebyshevpoints(Float64, n, Val(1))\nf = exp.(p_1)\nf̌ = chebyshevtransform(f, Val(1))\nf̃ = x -> [cos(k*acos(x)) for k=0:n-1]' * f̌\nf̃(0.1) ≈ exp(0.1)\n\n# First kind polynomials $\\to$ first kind points\nichebyshevtransform(f̌, Val(1)) ≈ exp.(p_1)\n\n# Second kind points $\\to$ first kind polynomials\np_2 = chebyshevpoints(Float64, n, Val(2))\nf = exp.(p_2)\nf̌ = chebyshevtransform(f, Val(2))\nf̃ = x -> [cos(k*acos(x)) for k=0:n-1]' * f̌\nf̃(0.1) ≈ exp(0.1)\n\n# First kind polynomials $\\to$ second kind points\nichebyshevtransform(f̌, Val(2)) ≈ exp.(p_2)\n\n# First kind points $\\to$ second kind polynomials\np_1 = chebyshevpoints(Float64, n, Val(1))\nf = exp.(p_1)\nf̌ = chebyshevutransform(f, Val(1))\nf̃ = x -> [sin((k+1)*acos(x))/sin(acos(x)) for k=0:n-1]' * f̌\nf̃(0.1) ≈ exp(0.1)\n\n# Second kind polynomials $\\to$ first kind points\nichebyshevutransform(f̌, Val(1)) ≈ exp.(p_1)\n\n# Second kind points $\\to$ second kind polynomials\np_2 = chebyshevpoints(Float64, n, Val(2))[2:n-1]\nf = exp.(p_2)\nf̌ = chebyshevutransform(f, Val(2))\nf̃ = x -> [sin((k+1)*acos(x))/sin(acos(x)) for k=0:n-3]' * f̌\nf̃(0.1) ≈ exp(0.1)\n\n# Second kind polynomials $\\to$ second kind points\nichebyshevutransform(f̌, Val(2)) ≈ exp.(p_2)\n"
  },
  {
    "path": "examples/disk.jl",
    "content": "# # Holomorphic integration on the unit disk\n# In this example, we explore integration of a harmonic function:\n# ```math\n#   f(x,y) = \\frac{x^2-y^2+1}{(x^2-y^2+1)^2+(2xy+1)^2},\n# ```\n# over the unit disk. In this case, we know from complex analysis that the\n# integral of a holomorphic function is equal to $\\pi \\times f(0,0)$.\n# We analyze the function on an $N\\times M$ tensor product grid defined by:\n# ```math\n# \\begin{aligned}\n# r_n & = \\cos\\left[(n+\\tfrac{1}{2})\\pi/2N\\right],\\quad{\\rm for}\\quad 0\\le n < N,\\quad{\\rm and}\\\\\n# \\theta_m & = 2\\pi m/M,\\quad{\\rm for}\\quad 0\\le m < M;\n# \\end{aligned}\n# ```\n# we convert the function samples to Chebyshev×Fourier coefficients using\n# `plan_disk_analysis`; and finally, we transform the Chebyshev×Fourier\n# coefficients to Zernike polynomial coefficients using `plan_disk2cxf`.\n#\n# For the storage pattern of the arrays, please consult the\n# [documentation](https://MikaelSlevinsky.github.io/FastTransforms).\n\nusing FastTransforms, LinearAlgebra, Plots\nconst GENFIGS = joinpath(pkgdir(FastTransforms), \"docs/src/generated\")\n!isdir(GENFIGS) && mkdir(GENFIGS)\nplotlyjs()\n\n# Our function $f$ on the disk:\nf = (x,y) -> (x^2-y^2+1)/((x^2-y^2+1)^2+(2x*y+1)^2)\n\n# The Zernike polynomial degree:\nN = 15\nM = 4N-3\n\n# The radial grid:\nr = [sinpi((N-n-0.5)/(2N)) for n in 0:N-1]\n\n# The angular grid (mod $\\pi$):\nθ = (0:M-1)*2/M\n\n# On the mapped tensor product grid, our function samples are:\nF = [f(r*cospi(θ), r*sinpi(θ)) for r in r, θ in θ]\n\n# We superpose a surface plot of $f$ on top of the grid:\nX = [r*cospi(θ) for r in r, θ in θ]\nY = [r*sinpi(θ) for r in r, θ in θ]\nscatter3d(vec(X), vec(Y), vec(0F); markersize=0.75, markercolor=:red)\nsurface!(X, Y, F; legend=false, xlabel=\"x\", ylabel=\"y\", zlabel=\"f\")\nsavefig(joinpath(GENFIGS, \"zernike.html\"))\n###```@raw html\n###<object type=\"text/html\" data=\"../zernike.html\" style=\"width:100%;height:400px;\"></object>\n###```\n\n# We precompute a (generalized) Zernike--Chebyshev×Fourier plan:\nα, β = 0, 0\nP = plan_disk2cxf(F, α, β)\n\n# And an FFTW Chebyshev×Fourier analysis plan on the disk:\nPA = plan_disk_analysis(F)\n\n# Its Zernike coefficients are:\nU = P\\(PA*F)\n\n# The Zernike coefficients are useful for integration. The integral of $f(x,y)$\n# over the disk should be $\\pi/2$ by harmonicity. The coefficient of $Z_{0,0}$\n# multiplied by `√π` is:\nU[1, 1]*sqrt(π)\n\n# Using an orthonormal basis, the integral of $[f(x,y)]^2$ over the disk is\n# approximately the square of the 2-norm of the coefficients:\nnorm(U)^2, π/(2*sqrt(2))*log1p(sqrt(2))\n\n# But there's more! Next, we repeat the experiment using the Dunkl-Xu\n# orthonormal polynomials supported on the rectangularized disk.\nN = 2N\nM = N\n\n# We analyze the function on an $N\\times M$ mapped tensor product $xy$-grid defined by:\n# ```math\n# \\begin{aligned}\n# 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}\\\\\n# 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,\\\\\n# y_{n,m} & = \\sqrt{1-x_n^2}z_m.\n# \\end{aligned}\n# ```\n# Slightly more accuracy can be expected by using an auxiliary array:\n# ```math\n#   w_n = \\sin\\left(\\frac{2n+1}{2N}\\pi\\right),\\quad {\\rm for} \\quad 0 \\le n < N,\n# ```\n# so that $y_{n,m} = w_nz_m$.\n#\n# The x grid\nw = [sinpi((n+0.5)/N) for n in 0:N-1]\nx = [sinpi((N-2n-1)/(2N)) for n in 0:N-1]\n\n# The z grid\nz = [sinpi((M-2m-1)/(2M)) for m in 0:M-1]\n\n# On the mapped tensor product grid, our function samples are:\nF = [f(x[n], w[n]*z) for n in 1:N, z in z]\n\n# We superpose a surface plot of $f$ on top of the grid:\nX = [x for x in x, z in z]\nY = [w*z for w in w, z in z]\nscatter3d(vec(X), vec(Y), vec(0F); markersize=0.75, markercolor=:green)\nsurface!(X, Y, F; legend=false, xlabel=\"x\", ylabel=\"y\", zlabel=\"f\")\nsavefig(joinpath(GENFIGS, \"dunklxu.html\"))\n###```@raw html\n###<object type=\"text/html\" data=\"../dunklxu.html\" style=\"width:100%;height:400px;\"></object>\n###```\n\n# We precompute a Dunkl-Xu--Chebyshev plan:\nP = plan_rectdisk2cheb(F, β)\n\n# And an FFTW Chebyshev² analysis plan on the rectangularized disk:\nPA = plan_rectdisk_analysis(F)\n\n# Its Dunkl-Xu coefficients are:\nU = P\\(PA*F)\n\n# The Dunkl-Xu coefficients are useful for integration. The integral of $f(x,y)$\n# over the disk should be $\\pi/2$ by harmonicity. The coefficient of $P_{0,0}$\n# multiplied by `√π` is:\nU[1, 1]*sqrt(π)\n\n# Using an orthonormal basis, the integral of $[f(x,y)]^2$ over the disk is\n# approximately the square of the 2-norm of the coefficients:\nnorm(U)^2, π/(2*sqrt(2))*log1p(sqrt(2))\n"
  },
  {
    "path": "examples/halfrange.jl",
    "content": "# # Half-range Chebyshev polynomials\n# In [this paper](https://doi.org/10.1137/090752456), [Daan Huybrechs](https://github.com/daanhb) introduced the so-called half-range Chebyshev polynomials\n# as the semi-classical orthogonal polynomials with respect to the inner product:\n# ```math\n# \\langle f, g \\rangle = \\int_0^1 f(x) g(x)\\frac{{\\rm d} x}{\\sqrt{1-x^2}}.\n# ```\n# By the variable transformation $y = 2x-1$, the resulting polynomials can be related to\n# orthogonal polynomials on $(-1,1)$ with the Jacobi weight $(1-y)^{-\\frac{1}{2}}$ modified by the weight $(3+y)^{-\\frac{1}{2}}$.\n#\n# We shall use the fact that:\n# ```math\n# \\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,\n# ```\n# and results from [this paper](https://arxiv.org/abs/2302.08448) to consider the half-range Chebyshev polynomials as\n# modifications of the Jacobi polynomials $P_n^{(-\\frac{1}{2},0)}(y)$.\n\nusing FastTransforms, LinearAlgebra, Plots, LaTeXStrings\nconst GENFIGS = joinpath(pkgdir(FastTransforms), \"docs/src/generated\")\n!isdir(GENFIGS) && mkdir(GENFIGS)\nplotlyjs()\n\n# We truncate the generating function to ensure a relative error less than `eps()` in the uniform norm on $(-1,1)$:\nz = -1/(3+sqrt(8))\nK = sqrt(-2z)\nN = ceil(Int, log(abs(z), eps()/2*(1-abs(z))/K) - 1)\nd = K .* z .^(0:N)\n\n# Then, we convert this representation to the expansion in Jacobi polynomials $P_n^{(-\\frac{1}{2}, 0)}(y)$:\nu = jac2jac(d, 0.0, 0.0, -0.5, 0.0; norm1 = false, norm2 = true)\n\n# Our working polynomial degree will be:\nn = 100\n\n# We compute the connection coefficients between the modified orthogonal polynomials and the Jacobi polynomials:\nP = plan_modifiedjac2jac(Float64, n+1, -0.5, 0.0, u)\n\n# We store the connection to first kind Chebyshev polynomials:\nP1 = plan_jac2cheb(Float64, n+1, -0.5, 0.0; normjac = true)\n\n# We compute the Chebyshev series for the degree-$k\\le n$ modified polynomial and its values at the Chebyshev points:\nq = k -> lmul!(P1, lmul!(P, [zeros(k); 1.0; zeros(n-k)]))\nqvals = k-> ichebyshevtransform(q(k))\n\n# 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):\nXP = 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])\nXQ = FastTransforms.modified_jacobi_matrix(P, XP)\nSymTridiagonal(XQ.dv[1:10], XQ.ev[1:9])\n\n# And we plot:\nx = (chebyshevpoints(Float64, n+1, Val(1)) .+ 1 ) ./ 2\np = plot(x, qvals(0); linewidth=2.0, legend = false, xlim=(0,1), xlabel=L\"x\",\n         ylabel=L\"T^h_n(x)\", title=\"Half-Range Chebyshev Polynomials and Their Roots\",\n         extra_plot_kwargs = KW(:include_mathjax => \"cdn\"))\nfor k in 1:10\n    λ = (eigvals(SymTridiagonal(XQ.dv[1:k], XQ.ev[1:k-1])) .+ 1) ./ 2\n    plot!(x, qvals(k); linewidth=2.0, color=palette(:default)[k+1])\n    scatter!(λ, zero(λ); markersize=2.5, color=palette(:default)[k+1])\nend\np\nsavefig(joinpath(GENFIGS, \"halfrange.html\"))\n###```@raw html\n###<object type=\"text/html\" data=\"../halfrange.html\" style=\"width:100%;height:400px;\"></object>\n###```\n\n# 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:\nv̂ = 3*[u; 0]+XP[1:N+2, 1:N+1]*u\nv = jac2jac(v̂, -0.5, 0.0, 0.5, 1.0; norm1 = true, norm2 = true)\nfunction threshold!(A::AbstractArray, ϵ)\n    for i in eachindex(A)\n        if abs(A[i]) < ϵ A[i] = 0 end\n    end\n    A\nend\nP′ = plan_modifiedjac2jac(Float64, n+1, 0.5, 1.0, v)\nDP = 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.\nDQ = UpperTriangular(threshold!(P′\\(DP*(P*I)), 100eps())) # The semi-classical differentiation matrix representing 𝒟 Q(y) = Q̂(y) D_Q.\nUpperTriangular(DQ[1:10,1:10])\n"
  },
  {
    "path": "examples/nonlocaldiffusion.jl",
    "content": "# # Nonlocal diffusion on $\\mathbb{S}^2$\n# This example calculates the spectrum of the nonlocal diffusion operator:\n# ```math\n# \\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}),\n# ```\n# defined in Eq. (2) of\n#\n# 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.\n#\n# In the above, $0<\\delta<2$, $-1<\\alpha<1$, and the kernel:\n# ```math\n# \\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}},\n# ```\n# where $\\chi_I(\\cdot)$ is the indicator function on the set $I$.\n#\n# This nonlocal operator is diagonalized by spherical harmonics:\n# ```math\n# \\mathcal{L}_\\delta Y_\\ell^m(\\mathbf{x}) = \\lambda_\\ell(\\alpha, \\delta) Y_\\ell^m(\\mathbf{x}),\n# ```\n# and its eigenfunctions are given by the generalized Funk--Hecke formula:\n# ```math\n# \\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.\n# ```\n# 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)$.\n#\n# First, we represent:\n# ```math\n# 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).\n# ```\n# 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):\n# ```math\n# \\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.\n# ```\n# The code below implements this algorithm, making use of the Jacobi--Jacobi transform `plan_jac2jac`.\n# 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)$.\n\nusing FastTransforms, LinearAlgebra\n\nfunction oprec!(n::Integer, v::AbstractVector, alpha::Real, delta2::Real)\n    if n > 0\n        v[1] = 1\n    end\n    if n > 1\n        v[2] = (4*alpha+8-(alpha+4)*delta2)/4\n    end\n    for i = 1:n-2\n        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]\n    end\n    return v\nend\n\nfunction evaluate_lambda(n::Integer, alpha::T, delta::T) where T\n    delta2 = delta*delta\n    scl = (1+alpha)*(2-delta2/2)\n\n    lambda = Vector{T}(undef, n)\n\n    if n > 0\n        lambda[1] = 0\n    end\n    if n > 1\n        lambda[2] = -2\n    end\n\n    oprec!(n-2, view(lambda, 3:n), alpha, delta2)\n\n    for i = 2:n-1\n        lambda[i+1] *= -scl/(i-1)\n    end\n\n    p = plan_jac2jac(T, n-1, zero(T), zero(T), alpha, zero(T))\n\n    lmul!(p', view(lambda, 2:n))\n\n    for i = 2:n-1\n        lambda[i+1] = ((2i-1)*lambda[i+1] + (i-1)*lambda[i])/i\n    end\n\n    for i = 2:n-1\n        lambda[i+1] += lambda[i]\n    end\n\n    return lambda\nend\n\n# The spectrum in `Float64`:\nlambda = evaluate_lambda(10, -0.5, 1.0)\n\n# The spectrum in `BigFloat`:\nlambdabf = evaluate_lambda(10, parse(BigFloat, \"-0.5\"), parse(BigFloat, \"1.0\"))\n\n# The $\\infty$-norm relative error:\nnorm(lambda-lambdabf, Inf)/norm(lambda, Inf)\n"
  },
  {
    "path": "examples/padua.jl",
    "content": "# # Padua transform\n# This demonstrates the Padua transform and inverse transform,\n# explaining precisely the normalization and points\n\nusing FastTransforms\n\n# We define the Padua points and extract Cartesian components:\nN = 15\npts = paduapoints(N)\nx = pts[:,1]\ny = pts[:,2];\n\n# We take the Padua transform of the function:\nf = (x,y) -> exp(x + cos(y))\nf̌ = paduatransform(f.(x , y));\n\n# and use the coefficients to create an approximation to the function $f$:\nf̃ = (x,y) -> begin\n    j = 1\n    ret = 0.0\n    for n in 0:N, k in 0:n\n        ret += f̌[j]*cos((n-k)*acos(x)) * cos(k*acos(y))\n        j += 1\n    end\n    ret\nend\n\n# At a particular point, is the function well-approximated?\nf̃(0.1,0.2) ≈ f(0.1,0.2)\n\n# Does the inverse transform bring us back to the grid?\nipaduatransform(f̌) ≈ f̃.(x,y)\n"
  },
  {
    "path": "examples/sphere.jl",
    "content": "# # Spherical harmonic addition theorem\n# This example confirms numerically that\n# ```math\n# f(z) = \\frac{P_n(z\\cdot y) - P_n(x\\cdot y)}{z\\cdot y - x\\cdot y},\n# ```\n# is actually a degree-$(n-1)$ polynomial on $\\mathbb{S}^2$, where $P_n$ is the degree-$n$\n# Legendre polynomial, and $x,y,z \\in \\mathbb{S}^2$.\n# To verify, we sample the function on a $N\\times M$ equiangular grid\n# defined by:\n# ```math\n# \\begin{aligned}\n# \\theta_n & = (n+\\tfrac{1}{2})\\pi/N,\\quad{\\rm for}\\quad 0\\le n < N,\\quad{\\rm and}\\\\\n# \\varphi_m & = 2\\pi m/M,\\quad{\\rm for}\\quad 0\\le m < M;\n# \\end{aligned}\n# ```\n# we convert the function samples to Fourier coefficients using\n# `plan_sph_analysis`; and finally, we transform\n# the Fourier coefficients to spherical harmonic coefficients using\n# `plan_sph2fourier`.\n#\n# In the basis of spherical harmonics, it is plain to see the\n# addition theorem in action, since $P_n(x\\cdot y)$ should only consist of\n# exact-degree-$n$ harmonics.\n#\n# For the storage pattern of the arrays, please consult the\n# [documentation](https://MikaelSlevinsky.github.io/FastTransforms).\n\nfunction threshold!(A::AbstractArray, ϵ)\n    for i in eachindex(A)\n        if abs(A[i]) < ϵ A[i] = 0 end\n    end\n    A\nend\n\nusing FastTransforms, LinearAlgebra, Plots\nconst GENFIGS = joinpath(pkgdir(FastTransforms), \"docs/src/generated\")\n!isdir(GENFIGS) && mkdir(GENFIGS)\nplotlyjs()\n\n# The colatitudinal grid (mod $\\pi$):\nN = 15\nθ = (0.5:N-0.5)/N\n\n# The longitudinal grid (mod $\\pi$):\nM = 2*N-1\nφ = (0:M-1)*2/M\n\n# Arbitrarily, we place $x$ at the North pole:\nx = [0,0,1]\n\n# Another vector is completely free:\ny = normalize([.123,.456,.789])\n\n# Thus $z \\in \\mathbb{S}^2$ is our variable vector, parameterized in spherical coordinates:\nz = (θ,φ) -> [sinpi(θ)*cospi(φ), sinpi(θ)*sinpi(φ), cospi(θ)]\n\n# On the tensor product grid, the Legendre polynomial $P_n(z\\cdot y)$ is:\nA = [(2k+1)/(k+1) for k in 0:N-1]\nB = zeros(N)\nC = [k/(k+1) for k in 0:N]\nc = zeros(N); c[N] = 1\npts = vec([z(θ, φ)⋅y for θ in θ, φ in φ])\nphi0 = ones(N*M)\nF = reshape(FastTransforms.clenshaw!(zeros(N*M), c, A, B, C, pts, phi0), N, M)\n\n# We superpose a surface plot of $f$ on top of the grid:\nX = [sinpi(θ)*cospi(φ) for θ in θ, φ in φ]\nY = [sinpi(θ)*sinpi(φ) for θ in θ, φ in φ]\nZ = [cospi(θ) for θ in θ, φ in φ]\nscatter3d(vec(X), vec(Y), vec(Z); markersize=1.25, markercolor=:violetred)\nsurface!(X, Y, Z; surfacecolor=F, legend=false, xlabel=\"x\", ylabel=\"y\", zlabel=\"f\")\nsavefig(joinpath(GENFIGS, \"sphere1.html\"))\n###```@raw html\n###<object type=\"text/html\" data=\"../sphere1.html\" style=\"width:100%;height:400px;\"></object>\n###```\n\n# We show the cut in the surface to help illustrate the definition of the grid.\n# In particular, we do not sample the poles.\n#\n# We precompute a spherical harmonic--Fourier plan:\nP = plan_sph2fourier(F)\n\n# And an FFTW Fourier analysis plan on $\\mathbb{S}^2$:\nPA = plan_sph_analysis(F)\n\n# Its spherical harmonic coefficients demonstrate that it is exact-degree-$n$:\nV = PA*F\nU = threshold!(P\\V, 400*eps())\n\n# The $L^2(\\mathbb{S}^2)$ norm of the function is:\nnrm1 = norm(U)\n\n# Similarly, on the tensor product grid, our function samples are:\nPnxy = FastTransforms.clenshaw!([0.0], c, A, B, C, [x⋅y], [1.0])[1]\nF = [(F[n, m] - Pnxy)/(z(θ[n], φ[m])⋅y - x⋅y) for n in 1:N, m in 1:M]\n\n# We superpose a surface plot of $f$ on top of the grid:\nscatter3d(vec(X), vec(Y), vec(Z); markersize=1.25, markercolor=:violetred)\nsurface!(X, Y, Z; surfacecolor=F, legend=false, xlabel=\"x\", ylabel=\"y\", zlabel=\"f\")\nsavefig(joinpath(GENFIGS, \"sphere2.html\"))\n###```@raw html\n###<object type=\"text/html\" data=\"../sphere2.html\" style=\"width:100%;height:400px;\"></object>\n###```\n\n# Its spherical harmonic coefficients demonstrate that it is degree-$(n-1)$:\nV = PA*F\nU = threshold!(P\\V, 400*eps())\n\n# Finally, the Legendre polynomial $P_n(z\\cdot x)$ is aligned with the grid:\npts = vec([z(θ, φ)⋅x for θ in θ, φ in φ])\nF = reshape(FastTransforms.clenshaw!(zeros(N*M), c, A, B, C, pts, phi0), N, M)\n\n# We superpose a surface plot of $f$ on top of the grid:\nscatter3d(vec(X), vec(Y), vec(Z); markersize=1.25, markercolor=:violetred)\nsurface!(X, Y, Z; surfacecolor=F, legend=false, xlabel=\"x\", ylabel=\"y\", zlabel=\"f\")\nsavefig(joinpath(GENFIGS, \"sphere3.html\"))\n###```@raw html\n###<object type=\"text/html\" data=\"../sphere3.html\" style=\"width:100%;height:400px;\"></object>\n###```\n\n# It only has one nonnegligible spherical harmonic coefficient.\n# Can you spot it?\nV = PA*F\nU = threshold!(P\\V, 400*eps())\n\n# That nonnegligible coefficient should be\nret = eval(\"√(2π/($(N-1)+1/2))\")\n\n# which is approximately\neval(Meta.parse(ret))\n\n# since the convention in this library is to orthonormalize.\nnrm2 = norm(U)\n\n# Note that the integrals of both functions $P_n(z\\cdot y)$ and $P_n(z\\cdot x)$ and their\n# $L^2(\\mathbb{S}^2)$ norms are the same because of rotational invariance. The integral of\n# either is perhaps not interesting as it is mathematically zero, but the norms\n# of either should be approximately the same.\nnrm1 ≈ nrm2\n"
  },
  {
    "path": "examples/sphericalisometries.jl",
    "content": "function threshold!(A::AbstractArray, ϵ)\n    for i in eachindex(A)\n        if abs(A[i]) < ϵ A[i] = 0 end\n    end\n    A\nend\n\nusing FastTransforms, LinearAlgebra, Random, Test\n\n# The colatitudinal grid (mod π):\nN = 10\nθ = (0.5:N-0.5)/N\n\n# The longitudinal grid (mod π):\nM = 2*N-1\nφ = (0:M-1)*2/M\n\nx = [cospi(φ)*sinpi(θ) for θ in θ, φ in φ]\ny = [sinpi(φ)*sinpi(θ) for θ in θ, φ in φ]\nz = [cospi(θ) for θ in θ, φ in φ]\n\nP = plan_sph2fourier(Float64, N)\nPA = plan_sph_analysis(Float64, N, M)\nJ = FastTransforms.plan_sph_isometry(Float64, N)\n\n\nf = (x, y, z) -> x^2+y^4+x^2*y*z^3-x*y*z^2\n\n\nF = f.(x, y, z)\nV = PA*F\nU = threshold!(P\\V, 100eps())\nFastTransforms.execute_sph_yz_axis_exchange!(J, U)\nFR = f.(x, -z, -y)\nVR = PA*FR\nUR = threshold!(P\\VR, 100eps())\n@test U ≈ UR\nnorm(U-UR)\n\n\nα, β, γ = 0.123, 0.456, 0.789\n\n# Isometry built up from ZYZR\nA = [cos(α) -sin(α) 0; sin(α) cos(α) 0; 0 0 1]\nB = [cos(β) 0 -sin(β); 0 1 0; sin(β) 0 cos(β)]\nC = [cos(γ) -sin(γ) 0; sin(γ) cos(γ) 0; 0 0 1]\nR = diagm([1, 1, 1.0])\nQ = A*B*C*R\n\n# Transform the sampling grid. Note that `Q` is transposed here.\nu = Q[1,1]*x + Q[2,1]*y + Q[3,1]*z\nv = Q[1,2]*x + Q[2,2]*y + Q[3,2]*z\nw = Q[1,3]*x + Q[2,3]*y + Q[3,3]*z\n\nF = f.(x, y, z)\nV = PA*F\nU = threshold!(P\\V, 100eps())\nFastTransforms.execute_sph_rotation!(J, α, β, γ, U)\nFR = f.(u, v, w)\nVR = PA*FR\nUR = threshold!(P\\VR, 100eps())\n@test U ≈ UR\nnorm(U-UR)\n\n\nF = f.(x, y, z)\nV = PA*F\nU = threshold!(P\\V, 100eps())\nFastTransforms.execute_sph_polar_reflection!(U)\nFR = f.(x, y, -z)\nVR = PA*FR\nUR = threshold!(P\\VR, 100eps())\n@test U ≈ UR\nnorm(U-UR)\n\n\n# Isometry built up from planar reflection\nW = [0.123, 0.456, 0.789]\nH = w -> I - 2/(w'w)*w*w'\nQ = H(W)\n\n# Transform the sampling grid. Note that `Q` is transposed here.\nu = Q[1,1]*x + Q[2,1]*y + Q[3,1]*z\nv = Q[1,2]*x + Q[2,2]*y + Q[3,2]*z\nw = Q[1,3]*x + Q[2,3]*y + Q[3,3]*z\n\nF = f.(x, y, z)\nV = PA*F\nU = threshold!(P\\V, 100eps())\nFastTransforms.execute_sph_reflection!(J, W, U)\nFR = f.(u, v, w)\nVR = PA*FR\nUR = threshold!(P\\VR, 100eps())\n@test U ≈ UR\nnorm(U-UR)\n\nF = f.(x, y, z)\nV = PA*F\nU = threshold!(P\\V, 100eps())\nFastTransforms.execute_sph_reflection!(J, (W[1], W[2], W[3]), U)\nFR = f.(u, v, w)\nVR = PA*FR\nUR = threshold!(P\\VR, 100eps())\n@test U ≈ UR\nnorm(U-UR)\n\n# Random orthogonal transformation\nRandom.seed!(0)\nQ = qr(rand(3, 3)).Q\n\n# Transform the sampling grid, note that `Q` is transposed here.\nu = Q[1,1]*x + Q[2,1]*y + Q[3,1]*z\nv = Q[1,2]*x + Q[2,2]*y + Q[3,2]*z\nw = Q[1,3]*x + Q[2,3]*y + Q[3,3]*z\n\nF = f.(x, y, z)\nV = PA*F\nU = threshold!(P\\V, 100eps())\nFastTransforms.execute_sph_orthogonal_transformation!(J, Q, U)\nFR = f.(u, v, w)\nVR = PA*FR\nUR = threshold!(P\\VR, 100eps())\n@test U ≈ UR\nnorm(U-UR)\n"
  },
  {
    "path": "examples/spinweighted.jl",
    "content": "# # Spin-weighted spherical harmonics\n# This example plays with analysis of:\n# ```math\n# f(r) = e^{{\\rm i} k\\cdot r},\n# ```\n# for some $k\\in\\mathbb{R}^3$ and where $r\\in\\mathbb{S}^2$, using spin-$0$ spherical harmonics.\n#\n# It applies ð, the spin-raising operator,\n# both on the spin-$0$ coefficients as well as the original function,\n# followed by a spin-$1$ analysis to compare coefficients.\n#\n# For the storage pattern of the arrays, please consult the\n# [documentation](https://MikaelSlevinsky.github.io/FastTransforms).\n\nusing FastTransforms, LinearAlgebra\n\n# The colatitudinal grid (mod $\\pi$):\nN = 10\nθ = (0.5:N-0.5)/N\n\n# The longitudinal grid (mod $\\pi$):\nM = 2*N-1\nφ = (0:M-1)*2/M\n\n# Our choice of $k$ and angular parametrization of $r$:\nk = [2/7, 3/7, 6/7]\nr = (θ,φ) -> [sinpi(θ)*cospi(φ), sinpi(θ)*sinpi(φ), cospi(θ)]\n\n# On the tensor product grid, our function samples are:\nF = [exp(im*(k⋅r(θ,φ))) for θ in θ, φ in φ]\n\n# We precompute a spin-$0$ spherical harmonic--Fourier plan:\nP = plan_spinsph2fourier(F, 0)\n\n# And an FFTW Fourier analysis plan on $\\mathbb{S}^2$:\nPA = plan_spinsph_analysis(F, 0)\n\n# Its spin-$0$ spherical harmonic coefficients are:\nU⁰ = P\\(PA*F)\n\n# We can check its $L^2(\\mathbb{S}^2)$ norm against an exact result:\nnorm(U⁰) ≈ sqrt(4π)\n\n# Spin can be incremented by applying ð, either on the spin-$0$ coefficients:\nU¹c = zero(U⁰)\nfor n in 1:N-1\n    U¹c[n, 1] = sqrt(n*(n+1))*U⁰[n+1, 1]\nend\nfor m in 1:M÷2\n    for n in 0:N-1\n        U¹c[n+1, 2m] = -sqrt((n+m)*(n+m+1))*U⁰[n+1, 2m]\n        U¹c[n+1, 2m+1] = sqrt((n+m)*(n+m+1))*U⁰[n+1, 2m+1]\n    end\nend\n\n# or on the original function through analysis with spin-$1$ spherical harmonics:\nF = [-(k[1]*(im*cospi(θ)*cospi(φ) + sinpi(φ)) + k[2]*(im*cospi(θ)*sinpi(φ)-cospi(φ)) - im*k[3]*sinpi(θ))*exp(im*(k⋅r(θ,φ))) for θ in θ, φ in φ]\n\n# We change plans with spin-$1$ now and reanalyze:\nP = plan_spinsph2fourier(F, 1)\nPA = plan_spinsph_analysis(F, 1)\nU¹s = P\\(PA*F)\n\n# Finally, we check $L^2(\\mathbb{S}^2)$ norms against another exact result:\nnorm(U¹c) ≈ norm(U¹s) ≈ sqrt(8π/3*(k⋅k))\n"
  },
  {
    "path": "examples/subspaceangles.jl",
    "content": "# # Subspace angles\n# This example considers the angles between neighbouring Laguerre polynomials with a perturbed measure:\n# ```math\n# \\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,\n# ```\n# 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$.\n#\n# 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:\n# ```math\n# \\cos\\theta_n = \\frac{(V^\\top V)_{n, n+k}}{\\sqrt{(V^\\top V)_{n, n}(V^\\top V)_{n+k, n+k}}}.\n# ```\n#\nusing FastTransforms, LinearAlgebra\n\n# The neighbouring index `k` and the maximum degree `N-1`:\nk, N = 1, 11\n\n# The Laguerre connection parameters:\nα, β = 0.0, 0.125\n\n# We precompute a Laguerre--Laguerre plan:\nP = plan_lag2lag(Float64, N, α, β; norm2=true)\n\n# We apply the plan to the identity, followed by the adjoint plan:\nVtV = parent(P*I)\nlmul!(P', VtV)\n\n# From this matrix, the angles are recovered from:\nθ = [acos(VtV[n, n+k]/sqrt(VtV[n, n]*VtV[n+k, n+k])) for n in 1:N-k]\n"
  },
  {
    "path": "examples/triangle.jl",
    "content": "# # Calculus on the reference triangle\n# In this example, we sample a bivariate function:\n# ```math\n# f(x,y) = \\frac{1}{1+x^2+y^2},\n# ```\n# on the reference triangle with vertices $(0,0)$, $(0,1)$, and $(1,0)$ and analyze it\n# in a Proriol series. Then, we find Proriol series for each component of its\n# gradient by term-by-term differentiation of our expansion, and we compare them\n# with the true Proriol series by sampling an exact expression for the gradient.\n#\n# We analyze $f(x,y)$ on an $N\\times M$ mapped tensor product grid defined by:\n# ```math\n# \\begin{aligned}\n# x & = (1+u)/2,\\quad{\\rm and}\\quad y = (1-u)(1+v)/4,\\quad {\\rm where:}\\\\\n# u_n & = \\cos\\left[(n+\\tfrac{1}{2})\\pi/N\\right],\\quad{\\rm for}\\quad 0\\le n < N,\\quad{\\rm and}\\\\\n# v_m & = \\cos\\left[(m+\\tfrac{1}{2})\\pi/M\\right],\\quad{\\rm for}\\quad 0\\le m < M;\n# \\end{aligned}\n# ```\n# we convert the function samples to mapped Chebyshev² coefficients using\n# `plan_tri_analysis`; and finally, we transform the mapped Chebyshev²\n# coefficients to Proriol coefficients using `plan_tri2cheb`.\n#\n# For the storage pattern of the arrays, please consult the\n# [documentation](https://MikaelSlevinsky.github.io/FastTransforms).\n\nusing FastTransforms, LinearAlgebra, Plots\nconst GENFIGS = joinpath(pkgdir(FastTransforms), \"docs/src/generated\")\n!isdir(GENFIGS) && mkdir(GENFIGS)\nplotlyjs()\n\n# Our function $f$ and the Cartesian components of its gradient:\nf = (x,y) -> 1/(1+x^2+y^2)\nfx = (x,y) -> -2x/(1+x^2+y^2)^2\nfy = (x,y) -> -2y/(1+x^2+y^2)^2\n\n# The polynomial degree:\nN = 15\nM = N\n\n# The parameters of the Proriol series:\nα, β, γ = 0, 0, 0\n\n# The $u$ grid:\nu = [sinpi((N-2n-1)/(2N)) for n in 0:N-1]\n\n# And the $v$ grid:\nv = [sinpi((M-2m-1)/(2M)) for m in 0:M-1]\n\n# Instead of using the $u\\times v$ grid, we use one with more accuracy near the origin.\n# Defining $x$ by:\nx = [sinpi((2N-2n-1)/(4N))^2 for n in 0:N-1]\n\n# And $w$ by:\nw = [sinpi((2M-2m-1)/(4M))^2 for m in 0:M-1]\n\n# We see how the two grids are related:\n((1 .+ u)./2 ≈ x) * ((1 .- u).*(1 .+ v')/4 ≈ reverse(x).*w')\n\n# On the mapped tensor product grid, our function samples are:\nF = [f(x[n+1], x[N-n]*w[m+1]) for n in 0:N-1, m in 0:M-1]\n\n# We superpose a surface plot of $f$ on top of the grid:\nX = [x for x in x, w in w]\nY = [x[N-n]*w[m+1] for n in 0:N-1, m in 0:M-1]\nscatter3d(vec(X), vec(Y), vec(0F); markersize=0.75, markercolor=:blue)\nsurface!(X, Y, F; legend=false, xlabel=\"x\", ylabel=\"y\", zlabel=\"f\")\nsavefig(joinpath(GENFIGS, \"proriol.html\"))\n###```@raw html\n###<object type=\"text/html\" data=\"../proriol.html\" style=\"width:100%;height:400px;\"></object>\n###```\n\n# We precompute a Proriol--Chebyshev² plan:\nP = plan_tri2cheb(F, α, β, γ)\n\n# And an FFTW Chebyshev² plan on the triangle:\nPA = plan_tri_analysis(F)\n\n# Its Proriol-$(α,β,γ)$ coefficients are:\nU = P\\(PA*F)\n\n# Similarly, our function's gradient samples are:\nFx = [fx(x[n+1], x[N-n]*w[m+1]) for n in 0:N-1, m in 0:M-1]\n\n# and:\nFy = [fy(x[n+1], x[N-n]*w[m+1]) for n in 0:N-1, m in 0:M-1]\n\n# For the partial derivative with respect to $x$, [Olver et al.](https://doi.org/10.1137/19M1245888)\n# derive simple expressions for the representation of this component\n# using a Proriol-$(α+1,β,γ+1)$ series.\nGx = zeros(Float64, N, M)\nfor m = 0:M-2\n    for n = 0:N-2\n        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)\n        cf2 = sqrt((n+α+1)*(m+1)/(2m+β+γ+2)*(m+β+1)/(2m+β+γ+3)*(n+2m+β+γ+3)*8)\n        Gx[n+1, m+1] = cf1*U[n+2, m+1] + cf2*U[n+1, m+2]\n    end\nend\nPx = plan_tri2cheb(Fx, α+1, β, γ+1)\nUx = Px\\(PA*Fx)\n\n# For the partial derivative with respect to y, the analogous formulae result\n# in a Proriol-$(α,β+1,γ+1)$ series.\nGy = zeros(Float64, N, M)\nfor m = 0:M-2\n    for n = 0:N-2\n        Gy[n+1, m+1] = 4*sqrt((m+1)*(m+β+γ+2))*U[n+1, m+2]\n    end\nend\nPy = plan_tri2cheb(Fy, α, β+1, γ+1)\nUy = Py\\(PA*Fy)\n\n# The $2$-norm relative error in differentiating the Proriol series\n# for $f(x,y)$ term-by-term and its sampled gradient is:\nhypot(norm(Ux-Gx), norm(Uy-Gy))/hypot(norm(Ux), norm(Uy))\n\n# This error can be improved upon by increasing $N$ and $M$.\n"
  },
  {
    "path": "src/FastTransforms.jl",
    "content": "module FastTransforms\n\nusing ArrayLayouts, BandedMatrices, FastGaussQuadrature, FillArrays, LazyArrays, LinearAlgebra,\n      SpecialFunctions, ToeplitzMatrices, RecurrenceRelationships\n\nusing AbstractFFTs\nusing FFTW\nusing GenericFFT\n\nimport Base: convert, unsafe_convert, eltype, ndims, adjoint, transpose, show,\n             *, \\, inv, length, size, view, getindex, tail, OneTo\n\nimport Base.GMP: Limb\n\nimport AbstractFFTs: Plan, ScaledPlan,\n                     fft, ifft, bfft, fft!, ifft!, bfft!, rfft, irfft, brfft,\n                     plan_fft, plan_ifft, plan_bfft, plan_fft!, plan_ifft!,\n                     plan_bfft!, plan_rfft, plan_irfft, plan_brfft,\n                     fftshift, ifftshift, rfft_output_size, brfft_output_size,\n                     normalization\n\nimport ArrayLayouts: rowsupport, colsupport, LayoutMatrix, MemoryLayout, AbstractBandedLayout\n\nimport BandedMatrices: bandwidths, BandedLayout\n\nimport FFTW: dct, dct!, idct, idct!, plan_dct!, plan_idct!,\n             plan_dct, plan_idct, fftwNumber\n\nimport FastGaussQuadrature: unweightedgausshermite\n\nimport FillArrays: AbstractFill, getindex_value\n\nimport LinearAlgebra: cholesky, issymmetric, isposdef, mul!, lmul!, ldiv!\n\nimport GenericFFT: interlace # imported in downstream packages\n\nimport RecurrenceRelationships: check_clenshaw_recurrences\n\n\nexport leg2cheb, cheb2leg, ultra2ultra, jac2jac,\n       lag2lag, jac2ultra, ultra2jac, jac2cheb,\n       cheb2jac, ultra2cheb, cheb2ultra, associatedjac2jac,\n       modifiedjac2jac, modifiedlag2lag, modifiedherm2herm,\n       sph2fourier, sphv2fourier, disk2cxf, ann2cxf, rectdisk2cheb,\n       tri2cheb, tet2cheb,fourier2sph, fourier2sphv, cxf2disk, cxf2ann,\n       cheb2rectdisk, cheb2tri, cheb2tet\n\nexport plan_leg2cheb, plan_cheb2leg, plan_ultra2ultra, plan_jac2jac,\n       plan_lag2lag, plan_jac2ultra, plan_ultra2jac, plan_jac2cheb,\n       plan_cheb2jac, plan_ultra2cheb, plan_cheb2ultra, plan_associatedjac2jac,\n       plan_modifiedjac2jac, plan_modifiedlag2lag, plan_modifiedherm2herm,\n       plan_sph2fourier, plan_sph_synthesis, plan_sph_analysis,\n       plan_sphv2fourier, plan_sphv_synthesis, plan_sphv_analysis,\n       plan_disk2cxf, plan_disk_synthesis, plan_disk_analysis,\n       plan_ann2cxf, plan_annulus_synthesis, plan_annulus_analysis,\n       plan_rectdisk2cheb, plan_rectdisk_synthesis, plan_rectdisk_analysis,\n       plan_tri2cheb, plan_tri_synthesis, plan_tri_analysis,\n       plan_tet2cheb, plan_tet_synthesis, plan_tet_analysis,\n       plan_spinsph2fourier, plan_spinsph_synthesis, plan_spinsph_analysis\n\n\ninclude(\"libfasttransforms.jl\")\ninclude(\"elliptic.jl\")\n\nexport nufft, nufft1, nufft2, nufft3, inufft1, inufft2\n\nexport plan_nufft, plan_nufft1, plan_nufft2, plan_nufft3,\n       plan_inufft1, plan_inufft2\n\ninclude(\"nufft.jl\")\ninclude(\"inufft.jl\")\n\nexport paduatransform, ipaduatransform, paduatransform!, ipaduatransform!,\n       paduapoints\n\nexport plan_paduatransform!, plan_ipaduatransform!\n\ninclude(\"PaduaTransform.jl\")\n\nexport chebyshevtransform, ichebyshevtransform,\n       chebyshevtransform!, ichebyshevtransform!,\n       chebyshevutransform, ichebyshevutransform,\n       chebyshevutransform!, ichebyshevutransform!, chebyshevpoints\n\nexport plan_chebyshevtransform, plan_ichebyshevtransform,\n       plan_chebyshevtransform!, plan_ichebyshevtransform!,\n       plan_chebyshevutransform, plan_ichebyshevutransform,\n       plan_chebyshevutransform!, plan_ichebyshevutransform!\n\ninclude(\"chebyshevtransform.jl\")\n\nexport clenshawcurtisnodes, clenshawcurtisweights, fejernodes1, fejerweights1,\n       fejernodes2, fejerweights2\n\nexport plan_clenshawcurtis, plan_fejer1, plan_fejer2\n\ninclude(\"clenshawcurtis.jl\")\ninclude(\"fejer.jl\")\n\nexport gaunt\n\ninclude(\"gaunt.jl\")\n\nexport GramMatrix, ChebyshevGramMatrix\n\ninclude(\"GramMatrix.jl\")\n\nexport weightedhermitetransform, iweightedhermitetransform\n\ninclude(\"hermite.jl\")\n\nexport sphones, sphzeros, sphrand, sphrandn, sphevaluate,\n       sphvones, sphvzeros, sphvrand, sphvrandn,\n       diskones, diskzeros, diskrand, diskrandn,\n       rectdiskones, rectdiskzeros, rectdiskrand, rectdiskrandn,\n       triones, trizeros, trirand, trirandn, trievaluate,\n       tetones, tetzeros, tetrand, tetrandn,\n       spinsphones, spinsphzeros, spinsphrand, spinsphrandn\n\ninclude(\"specialfunctions.jl\")\n\ninclude(\"toeplitzplans.jl\")\ninclude(\"toeplitzhankel.jl\")\n\nexport ToeplitzPlusHankel\n\ninclude(\"ToeplitzPlusHankel.jl\")\n\n# following use libfasttransforms by default\nfor f in (:jac2jac,\n    :lag2lag, :jac2ultra, :ultra2jac, :jac2cheb,\n    :cheb2jac, :ultra2cheb, :cheb2ultra, :associatedjac2jac,\n    :modifiedjac2jac, :modifiedlag2lag, :modifiedherm2herm,\n    :sph2fourier, :sphv2fourier, :disk2cxf, :ann2cxf,\n    :rectdisk2cheb, :tri2cheb, :tet2cheb,\n    :leg2cheb, :cheb2leg, :ultra2ultra)\n    lib_f = Symbol(\"lib_\", f)\n    @eval $f(x::AbstractArray, y...; z...) = $lib_f(x, y...; z...)\nend\n\ninclude(\"arrays.jl\")\n# following use Toeplitz-Hankel to avoid expensive plans\n# for f in (:leg2cheb, :cheb2leg, :ultra2ultra)\n#     th_f = Symbol(\"th_\", f)\n#     lib_f = Symbol(\"lib_\", f)\n#     @eval begin\n#         $f(x::AbstractArray, y...; z...) = $th_f(x, y...; z...)\n#         # $f(x::AbstractArray, y...; z...) = $lib_f(x, y...; z...)\n#     end\n# end\n\ninclude(\"docstrings.jl\")\n\nend # module\n"
  },
  {
    "path": "src/GramMatrix.jl",
    "content": "abstract type AbstractGramMatrix{T} <: LayoutMatrix{T} end\n\n@inline issymmetric(G::AbstractGramMatrix) = true\n@inline isposdef(G::AbstractGramMatrix) = true\n\nstruct GramMatrix{T, WT <: AbstractMatrix{T}, XT <: AbstractMatrix{T}} <: AbstractGramMatrix{T}\n    W::WT\n    X::XT\n    function GramMatrix{T, WT, XT}(W::WT, X::XT) where {T, WT, XT}\n        if size(W) ≠ size(X)\n            throw(ArgumentError(\"Cannot construct a GramMatrix with W and X of different sizes.\"))\n        end\n        if !issymmetric(W)\n            throw(ArgumentError(\"Cannot construct a GramMatrix with a nonsymmetric W.\"))\n        end\n        if bandwidths(X) ≠ (1, 1)\n            throw(ArgumentError(\"Cannot construct a GramMatrix with a nontridiagonal X.\"))\n        end\n        new{T, WT, XT}(W, X)\n    end\nend\n\n\"\"\"\n    GramMatrix(W::AbstractMatrix, X::AbstractMatrix)\n\nConstruct a symmetric positive-definite Gram matrix with data stored in ``W``.\nGiven a family of orthogonal polynomials ``𝐏(x) = {p₀(x), p₁(x),…}``\nand a continuous inner product ``⟨f, g⟩``, the Gram matrix is defined by:\n```math\nW[i, j] = ⟨p_{i-1}, p_{j-1}⟩.\n```\nMoreover, given ``X``, the transposed Jacobi matrix that satisfies ``x 𝐏(x) = 𝐏(x) X``,\nthe Gram matrix satisfies the skew-symmetric rank-2 displacement equation (``X = X[1:n, 1:n]``):\n```math\nXᵀW - WX = GJGᵀ,\n```\nwhere ``J = [0 1; -1 0]`` and where:\n```math\nG[:, 1] = 𝐞_n, \\\\quad  G[:, 2] = W[n-1, :]X[n-1, n] - Xᵀ W[:, n].\n```\nFast (``O(n^2)``) Cholesky factorization of the Gram matrix returns the\nconnection coefficients between ``𝐏(x)`` and the polynomials ``𝐐(x)``\northogonal in the modified inner product, ``𝐏(x) = 𝐐(x) R``.\n\nSee also [`ChebyshevGramMatrix`](@ref) for a special case.\n\n> 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.\n\"\"\"\nGramMatrix(W::WT, X::XT) where {T, WT <: AbstractMatrix{T}, XT <: AbstractMatrix{T}} = GramMatrix{T, WT, XT}(W, X)\n\n@inline size(G::GramMatrix) = size(G.W)\n@inline getindex(G::GramMatrix, i::Integer, j::Integer) = G.W[i, j]\n@inline bandwidths(G::GramMatrix) = bandwidths(G.W)\n@inline MemoryLayout(G::GramMatrix) = MemoryLayout(G.W)\n@inline rowsupport(G::GramMatrix, j) = rowsupport(MemoryLayout(G), G.W, j)\n@inline colsupport(G::GramMatrix, j) = colsupport(MemoryLayout(G), G.W, j)\n\n\"\"\"\n    GramMatrix(μ::AbstractVector, X::AbstractMatrix)\n\nConstruct a GramMatrix from modified orthogonal polynomial moments and the multiplication operator.\nIn the standard (classical) normalization, ``p₀(x) = 1``, so that the moments\n``µ[n] = ⟨ pₙ₋₁, 1⟩`` are in fact the first column of the Gram matrix.\nThe recurrence is built from ``XᵀW = WX``.\n\"\"\"\nGramMatrix(μ::AbstractVector{T}, X::XT) where {T, XT <: AbstractMatrix{T}} = GramMatrix(μ, X, one(T))\nfunction GramMatrix(μ::AbstractVector{T}, X::XT, p0::T) where {T, XT <: AbstractMatrix{T}}\n    N = length(μ)\n    n = (N+1)÷2\n    @assert N == size(X, 1) == size(X, 2)\n    @assert bandwidths(X) == (1, 1)\n    W = LowerTriangular(Matrix{T}(undef, N, N))\n    if n > 0\n        @inbounds for m in 1:N\n            W[m, 1] = p0*μ[m]\n        end\n    end\n    if n > 1\n        @inbounds for m in 2:N-1\n            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]\n        end\n    end\n    @inbounds @simd for n in 3:n\n        for m in n:N-n+1\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]\n        end\n    end\n    return GramMatrix(Symmetric(W[1:n, 1:n], :L), eval(XT.name.name)(view(X, 1:n, 1:n)))\nend\n\nfunction GramMatrix(μ::PaddedVector{T}, X::XT, p0::T) where {T, XT <: AbstractMatrix{T}}\n    N = length(μ)\n    b = length(μ.args[2])-1\n    n = (N+1)÷2\n    @assert N == size(X, 1) == size(X, 2)\n    @assert bandwidths(X) == (1, 1)\n    W = BandedMatrix{T}(undef, (N, N), (b, 0))\n    if n > 0\n        @inbounds for m in 1:min(N, b+1)\n            W[m, 1] = p0*μ[m]\n        end\n    end\n    if n > 1\n        @inbounds for m in 2:min(N-1, b+2)\n            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]\n        end\n    end\n    @inbounds @simd for n in 3:n\n        for m in n:min(N-n+1, b+n)\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]\n        end\n    end\n    return GramMatrix(Symmetric(W[1:n, 1:n], :L), eval(XT.name.name)(view(X, 1:n, 1:n)))\nend\n\n\"\"\"\n    GramMatrix(cnm1::AbstractVector, cn::AbstractVector, X::AbstractMatrix)\n\nConstruct a GramMatrix from its last two columns and the multiplication operator.\nThe recurrence is built from ``XᵀW = WX`` and is used in case the moment method is unstable (such as with Laguerre).\n\"\"\"\nfunction GramMatrix(cnm1::AbstractVector{T}, cn::AbstractVector{T}, X::XT) where {T, XT <: AbstractMatrix{T}}\n    N = length(cn)\n    @assert N == length(cnm1) == size(X, 1) == size(X, 2)\n    @assert bandwidths(X) == (1, 1)\n    W = Matrix{T}(undef, N, N)\n    if N > 0\n        @inbounds for m in 1:N\n            W[N, m] = W[m, N] = cn[m]\n        end\n    end\n    if N > 1\n        @inbounds for m in 1:N\n            W[N-1, m] = W[m, N-1] = cnm1[m]\n        end\n    end\n    @inbounds @simd for n in N:-1:3\n        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]\n        for m in 2:n-2\n            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]\n        end\n        for m in n-1:N-2\n            W[m, n-2] = W[n-2, m]\n        end\n    end\n    return GramMatrix(W, X)\nend\n\n#\n# X'W-W*X = G*J*G'\n# This returns G, where J = [0 1; -1 0], respecting the skew-symmetry of the right-hand side.\n#\nfunction compute_skew_generators(W::GramMatrix{T}) where T\n    X = W.X\n    n = size(W, 1)\n    G = zeros(T, n, 2)\n    G[n, 1] = one(T)\n    G[:, 2] .= W[:, n-1]*X[n-1, n] + W[:, n]*X[n, n] - X'W[:, n]\n    return G\nend\n\nfunction cholesky(W::GramMatrix{T}) where T\n    cholesky(MemoryLayout(W), W)\nend\n\nfunction cholesky(_, W::GramMatrix{T}) where T\n    n = size(W, 1)\n    G = compute_skew_generators(W)\n    L = zeros(T, n, n)\n    c = W[:, 1]\n    ĉ = zeros(T, n)\n    l = zeros(T, n)\n    v = zeros(T, n)\n    row1 = zeros(T, n)\n    fastcholesky!(L, W.X, G, c, ĉ, l, v, row1, n)\n    return Cholesky(L, 'L', 0)\nend\n\nfunction fastcholesky!(L::Matrix{T}, X, G, c, ĉ, l, v, row1, n) where T\n    @inbounds @simd for k in 1:n-1\n        d = sqrt(c[k])\n        for j in k:n\n            L[j, k] = l[j] = c[j]/d\n        end\n        for j in k:n\n            v[j] = G[j, 1]*G[k, 2] - G[j, 2]*G[k, 1]\n        end\n        for j in k+1:n-1\n            ĉ[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]\n        end\n        ĉ[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]\n        cst = X[k+1, k]/d\n        for j in k+1:n\n            row1[j] = -cst*l[j]\n        end\n        cst = c[k+1]/d\n        for j in k:n\n            c[j] = ĉ[j] - cst*l[j]\n        end\n        gd1 = G[k, 1]/d\n        gd2 = G[k, 2]/d\n        for j in k:n\n            G[j, 1] -= l[j]*gd1\n            G[j, 2] -= l[j]*gd2\n        end\n    end\n    L[n, n] = sqrt(c[n])\nend\n\nfunction cholesky(::Union{AbstractBandedLayout, SymmetricLayout{<: AbstractBandedLayout}}, W::GramMatrix{T}) where T\n    n = size(W, 1)\n    G = compute_skew_generators(W)\n    L = BandedMatrix{T}(undef, (n, n), (bandwidth(W, 1), 0))\n    c = W[:, 1]\n    ĉ = zeros(T, n)\n    l = zeros(T, n)\n    v = zeros(T, n)\n    row1 = zeros(T, n)\n    fastcholesky!(L, W.X, G, c, ĉ, l, v, row1, n)\n    return Cholesky(L, 'L', 0)\nend\n\nfunction fastcholesky!(L::BandedMatrix{T}, X, G, c, ĉ, l, v, row1, n) where T\n    b = bandwidth(L, 1)\n    @inbounds @simd for k in 1:n-1\n        d = sqrt(c[k])\n        for j in k:min(k+b, n)\n            L[j, k] = l[j] = c[j]/d\n        end\n        for j in max(k, n-b-1):n\n            v[j] = G[j, 1]*G[k, 2] - G[j, 2]*G[k, 1]\n        end\n        for j in k+1:min(k+b+1, n-1)\n            ĉ[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]\n        end\n        if k ≥ n-b-1\n            ĉ[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]\n        end\n        cst = X[k+1, k]/d\n        for j in k+1:min(k+b+1, n)\n            row1[j] = -cst*l[j]\n        end\n        cst = c[k+1]/d\n        for j in k:min(k+b+1, n)\n            c[j] = ĉ[j] - cst*l[j]\n        end\n        gd1 = G[k, 1]/d\n        gd2 = G[k, 2]/d\n        for j in max(k, n-b-1):n\n            G[j, 1] -= l[j]*gd1\n            G[j, 2] -= l[j]*gd2\n        end\n    end\n    L[n, n] = sqrt(c[n])\nend\n\nstruct ChebyshevGramMatrix{T, V <: AbstractVector{T}} <: AbstractGramMatrix{T}\n    μ::V\n    n::Int\nend\n\n\"\"\"\n    ChebyshevGramMatrix(μ::AbstractVector)\n\nConstruct a Chebyshev--Gram matrix of size `(length(μ)+1)÷2` with entries:\n```math\n2 W[i, j] = µ[|i-j|+1] + µ[i+j-1].\n```\nDue to the linearization of a product of two first-kind Chebyshev polynomials,\nthe Chebyshev--Gram matrix can be constructed from modified Chebyshev moments:\n```math\nµ[n] = ⟨ Tₙ₋₁, 1⟩.\n```\nSpecialized construction and Cholesky factorization is given for this type.\n\nSee also [`GramMatrix`](@ref) for the general case.\n\"\"\"\nfunction ChebyshevGramMatrix(μ::V) where {T, V <: AbstractVector{T}}\n    n = (length(μ)+1)÷2\n    ChebyshevGramMatrix{T, V}(μ, n)\nend\n\n@inline size(G::ChebyshevGramMatrix) = (G.n, G.n)\n@inline getindex(G::ChebyshevGramMatrix, i::Integer, j::Integer) = (G.μ[abs(i-j)+1] + G.μ[i+j-1])/2\n@inline bandwidths(G::ChebyshevGramMatrix{T, <: PaddedVector{T}}) where T = (length(G.μ.args[2])-1, length(G.μ.args[2])-1)\n@inline MemoryLayout(G::ChebyshevGramMatrix{T, <: PaddedVector{T}}) where T = BandedLayout()\n\n#\n# 2X'W-W*2X = G*J*G'\n# This returns G, where J = [0 1; -1 0], respecting the skew-symmetry of the right-hand side.\n# We use twice the Chebybshev Jacobi matrix so that subsequent arithmetic is easier.\n#\nfunction compute_skew_generators(W::ChebyshevGramMatrix{T}) where T\n    μ = W.μ\n    n = size(W, 1)\n    G = zeros(T, n, 2)\n    G[n, 1] = one(T)\n    @inbounds @simd for j in 1:n-1\n        G[j, 2] = -(μ[n+2-j] + μ[n+j])/2\n    end\n    G\nend\n\nfunction cholesky(W::ChebyshevGramMatrix{T}) where T\n    n = size(W, 1)\n    G = compute_skew_generators(W)\n    L = zeros(T, n, n)\n    c = W[:, 1]\n    ĉ = zeros(T, n)\n    l = zeros(T, n)\n    v = zeros(T, n)\n    row1 = zeros(T, n)\n    fastcholesky!(L, G, c, ĉ, l, v, row1, n)\n    return Cholesky(L, 'L', 0)\nend\n\nfunction fastcholesky!(L::Matrix{T}, G, c, ĉ, l, v, row1, n) where T\n    @inbounds @simd for k in 1:n-1\n        d = sqrt(c[k])\n        for j in k:n\n            L[j, k] = l[j] = c[j]/d\n        end\n        for j in k:n\n            v[j] = G[j, 1]*G[k, 2] - G[j, 2]*G[k, 1]\n        end\n        if k == 1\n            for j in 2:n-1\n                ĉ[j] = (c[j+1] + c[j-1] + c[1]*row1[j] - row1[1]*c[j] - v[j])/2\n            end\n            ĉ[n] = (c[n-1] + c[1]*row1[n] - row1[1]*c[n] - v[n])/2\n            cst = 2/d\n        else\n            for j in k+1:n-1\n                ĉ[j] = c[j+1] + c[j-1] + c[k]*row1[j] - row1[k]*c[j] - v[j]\n            end\n            ĉ[n] = c[n-1] + c[k]*row1[n] - row1[k]*c[n] - v[n]\n            cst = 1/d\n        end\n        for j in k+1:n\n            row1[j] = -cst*l[j]\n        end\n        cst = c[k+1]/d\n        for j in k:n\n            c[j] = ĉ[j] - cst*l[j]\n        end\n        gd1 = G[k, 1]/d\n        gd2 = G[k, 2]/d\n        for j in k:n\n            G[j, 1] -= l[j]*gd1\n            G[j, 2] -= l[j]*gd2\n        end\n    end\n    L[n, n] = sqrt(c[n])\nend\n\nfunction cholesky(W::ChebyshevGramMatrix{T, <: PaddedVector{T}}) where T\n    n = size(W, 1)\n    G = compute_skew_generators(W)\n    L = BandedMatrix{T}(undef, (n, n), (bandwidth(W, 1), 0))\n    c = W[:, 1]\n    ĉ = zeros(T, n)\n    l = zeros(T, n)\n    v = zeros(T, n)\n    row1 = zeros(T, n)\n    fastcholesky!(L, G, c, ĉ, l, v, row1, n)\n    return Cholesky(L, 'L', 0)\nend\n\nfunction fastcholesky!(L::BandedMatrix{T}, G, c, ĉ, l, v, row1, n) where T\n    b = bandwidth(L, 1)\n    @inbounds @simd for k in 1:n-1\n        d = sqrt(c[k])\n        for j in k:min(k+b, n)\n            L[j, k] = l[j] = c[j]/d\n        end\n        for j in max(k, n-b-1):n\n            v[j] = G[j, 1]*G[k, 2] - G[j, 2]*G[k, 1]\n        end\n        if k == 1\n            for j in 2:min(b+2, n-1)\n                ĉ[j] = (c[j+1] + c[j-1] + c[1]*row1[j] - row1[1]*c[j] - v[j])/2\n            end\n            if 1 ≥ n-b-1\n                ĉ[n] = (c[n-1] + c[1]*row1[n] - row1[1]*c[n] - v[n])/2\n            end\n            cst = 2/d\n        else\n            for j in k+1:min(k+b+1, n-1)\n                ĉ[j] = c[j+1] + c[j-1] + c[k]*row1[j] - row1[k]*c[j] - v[j]\n            end\n            if k ≥ n-b-1\n                ĉ[n] = c[n-1] + c[k]*row1[n] - row1[k]*c[n] - v[n]\n            end\n            cst = 1/d\n        end\n        for j in k+1:min(k+b+1, n)\n            row1[j] = -cst*l[j]\n        end\n        cst = c[k+1]/d\n        for j in k:min(k+b+1, n)\n            c[j] = ĉ[j] - cst*l[j]\n        end\n        gd1 = G[k, 1]/d\n        gd2 = G[k, 2]/d\n        for j in max(k, n-b-1):n\n            G[j, 1] -= l[j]*gd1\n            G[j, 2] -= l[j]*gd2\n        end\n    end\n    L[n, n] = sqrt(c[n])\nend\n"
  },
  {
    "path": "src/PaduaTransform.jl",
    "content": "\n# lex indicates if its lexigraphical (i.e., x, y) or reverse (y, x)\n# If in lexigraphical order the coefficient vector's entries\n# corrrespond to the following basis polynomials:\n# [T0(x) * T0(y), T1(x) * T0(y), T0(x) * T1(y), T2(x) * T0(y), T1(x) * T1(y), T0(x) * T2(y), ...]\n# else, if not in lexigraphical order:\n# [T0(x) * T0(y), T0(x) * T1(y), T1(x) * T0(y), T0(x) * T2(y), T1(x) * T1(y), T2(x) * T0(y), ...]\n\"\"\"\nPre-plan an Inverse Padua Transform.\n\"\"\"\nstruct IPaduaTransformPlan{lex,IDCTPLAN,T}\n    cfsmat::Matrix{T}\n    idctplan::IDCTPLAN\nend\n\nIPaduaTransformPlan(cfsmat::Matrix{T},idctplan,::Type{Val{lex}}) where {T,lex} =\n    IPaduaTransformPlan{lex,typeof(idctplan),T}(cfsmat,idctplan)\n\n\"\"\"\nPre-plan an Inverse Padua Transform.\n\"\"\"\nfunction plan_ipaduatransform!(::Type{T},N::Integer,lex) where T\n    n=Int(cld(-3+sqrt(1+8N),2))\n    if N ≠ div((n+1)*(n+2),2)\n        error(\"Padua transforms can only be applied to vectors of length (n+1)*(n+2)/2.\")\n    end\n    IPaduaTransformPlan(Array{T}(undef,n+2,n+1),FFTW.plan_r2r!(Array{T}(undef,n+2,n+1),FFTW.REDFT00),lex)\nend\n\n\nplan_ipaduatransform!(::Type{T},N::Integer) where {T} = plan_ipaduatransform!(T,N,Val{true})\nplan_ipaduatransform!(v::AbstractVector{T},lex...) where {T} = plan_ipaduatransform!(eltype(v),length(v),lex...)\n\n\nfunction *(P::IPaduaTransformPlan,v::AbstractVector{T}) where T\n    cfsmat=trianglecfsmat(P,v)\n    n,m=size(cfsmat)\n    rmul!(view(cfsmat,:,2:m-1),0.5)\n    rmul!(view(cfsmat,2:n-1,:),0.5)\n    tensorvals=P.idctplan*cfsmat\n    paduavec!(v,P,tensorvals)\nend\n\nipaduatransform!(v::AbstractVector,lex...) = plan_ipaduatransform!(v,lex...)*v\n\"\"\"\nInverse Padua Transform maps the 2D Chebyshev coefficients to the values of the interpolation polynomial at the Padua points.\n\"\"\"\nipaduatransform(v::AbstractVector,lex...) = plan_ipaduatransform!(v,lex...)*copy(v)\n\n\"\"\"\nCreates ``(n+2)x(n+1)`` Chebyshev coefficient matrix from triangle coefficients.\n\"\"\"\nfunction trianglecfsmat(P::IPaduaTransformPlan{true},cfs::AbstractVector)\n    N=length(cfs)\n    n=Int(cld(-3+sqrt(1+8N),2))\n    cfsmat=fill!(P.cfsmat,0)\n    m=1\n    for d=1:n+1\n        @inbounds for k=1:d\n            j=d-k+1\n            cfsmat[k,j]=cfs[m]\n            if m==N\n                return cfsmat\n            else\n                m+=1\n            end\n        end\n    end\n    return cfsmat\nend\n\nfunction trianglecfsmat(P::IPaduaTransformPlan{false},cfs::AbstractVector)\n    N=length(cfs)\n    n=Int(cld(-3+sqrt(1+8N),2))\n    cfsmat=fill!(P.cfsmat,0)\n    m=1\n    for d=1:n+1\n        @inbounds for k=d:-1:1\n            j=d-k+1\n            cfsmat[k,j]=cfs[m]\n            if m==N\n                return cfsmat\n            else\n                m+=1\n            end\n        end\n    end\n    return cfsmat\nend\n\n\"\"\"\nVectorizes the function values at the Padua points.\n\"\"\"\nfunction paduavec!(v,P::IPaduaTransformPlan,padmat::Matrix)\n    n=size(padmat,2)-1\n    N=(n+1)*(n+2)\n    if iseven(n)>0\n        d=div(n+2,2)\n        m=0\n        @inbounds for i=1:n+1\n            v[m+1:m+d]=view(padmat,1+mod(i,2):2:n+1+mod(i,2),i)\n            m+=d\n        end\n    else\n        @inbounds v[:]=view(padmat,1:2:N-1)\n    end\n    return v\nend\n\n\"\"\"\nPre-plan a Padua Transform.\n\"\"\"\nstruct PaduaTransformPlan{lex,DCTPLAN,T}\n    vals::Matrix{T}\n    dctplan::DCTPLAN\nend\n\nPaduaTransformPlan(vals::Matrix{T},dctplan,::Type{Val{lex}}) where {T,lex} =\n    PaduaTransformPlan{lex,typeof(dctplan),T}(vals,dctplan)\n\n\"\"\"\nPre-plan a Padua Transform.\n\"\"\"\nfunction plan_paduatransform!(::Type{T},N::Integer,lex) where T\n    n=Int(cld(-3+sqrt(1+8N),2))\n    if N ≠ ((n+1)*(n+2))÷2\n        error(\"Padua transforms can only be applied to vectors of length (n+1)*(n+2)/2.\")\n    end\n    PaduaTransformPlan(Array{T}(undef,n+2,n+1),FFTW.plan_r2r!(Array{T}(undef,n+2,n+1),FFTW.REDFT00),lex)\nend\n\nplan_paduatransform!(::Type{T},N::Integer) where {T} = plan_paduatransform!(T,N,Val{true})\nplan_paduatransform!(v::AbstractVector{T},lex...) where {T} = plan_paduatransform!(eltype(v),length(v),lex...)\n\nfunction *(P::PaduaTransformPlan,v::AbstractVector{T}) where T\n    N=length(v)\n    n=Int(cld(-3+sqrt(1+8N),2))\n    vals=paduavalsmat(P,v)\n    tensorcfs=P.dctplan*vals\n    m,l=size(tensorcfs)\n    rmul!(tensorcfs,T(2)/(n*(n+1)))\n    rmul!(view(tensorcfs,1,:),0.5)\n    rmul!(view(tensorcfs,:,1),0.5)\n    rmul!(view(tensorcfs,m,:),0.5)\n    rmul!(view(tensorcfs,:,l),0.5)\n    trianglecfsvec!(v,P,tensorcfs)\nend\n\npaduatransform!(v::AbstractVector,lex...) = plan_paduatransform!(v,lex...)*v\n\"\"\"\nPadua Transform maps from interpolant values at the Padua points to the 2D Chebyshev coefficients.\n\"\"\"\npaduatransform(v::AbstractVector,lex...) = plan_paduatransform!(v,lex...)*copy(v)\n\n\"\"\"\nCreates ``(n+2)x(n+1)`` matrix of interpolant values on the tensor grid at the ``(n+1)(n+2)/2`` Padua points.\n\"\"\"\nfunction paduavalsmat(P::PaduaTransformPlan,v::AbstractVector)\n    N=length(v)\n    n=Int(cld(-3+sqrt(1+8N),2))\n    vals=fill!(P.vals,0.)\n    if iseven(n)>0\n        d=div(n+2,2)\n        m=0\n        @inbounds for i=1:n+1\n            vals[1+mod(i,2):2:n+1+mod(i,2),i]=view(v,m+1:m+d)\n            m+=d\n        end\n    else\n        @inbounds vals[1:2:end]=view(v,:)\n    end\n    return vals\nend\n\n\"\"\"\nCreates length ``(n+1)(n+2)/2`` vector from matrix of triangle Chebyshev coefficients.\n\"\"\"\nfunction trianglecfsvec!(v,P::PaduaTransformPlan{true},cfs::Matrix)\n    m=size(cfs,2)\n    l=1\n    for d=1:m\n        @inbounds for k=1:d\n            j=d-k+1\n            v[l]=cfs[k,j]\n            l+=1\n        end\n    end\n    return v\nend\n\nfunction trianglecfsvec!(v,P::PaduaTransformPlan{false},cfs::Matrix)\n    m=size(cfs,2)\n    l=1\n    for d=1:m\n        @inbounds for k=d:-1:1\n            j=d-k+1\n            v[l]=cfs[k,j]\n            l+=1\n        end\n    end\n    return v\nend\n\n\"\"\"\nReturns coordinates of the ``(n+1)(n+2)/2`` Padua points.\n\"\"\"\nfunction paduapoints(::Type{T}, n::Integer) where T\n    N=div((n+1)*(n+2),2)\n    MM=Matrix{T}(undef,N,2)\n    m=0\n    delta=0\n    NN=div(n,2)+1\n    # x coordinates\n    for k=n:-1:0\n        if isodd(n)\n            delta = Int(isodd(k))\n        end\n        x = -cospi(T(k)/n)\n        @inbounds for j=NN+delta:-1:1\n            m+=1\n            MM[m,1]=x\n        end\n    end\n    # y coordinates\n    # populate the first two sets, and copy the rest\n    m=0\n    for k=n:-1:n-1\n        if isodd(n)\n            delta = Int(isodd(k))\n        end\n        for j=NN+delta:-1:1\n            m+=1\n            @inbounds if isodd(n-k)\n                MM[m,2]=-cospi((2j-one(T))/(n+1))\n            else\n                MM[m,2]=-cospi(T(2j-2)/(n+1))\n            end\n        end\n    end\n    m += 1\n    # number of y coordinates between k=n and k=n-2\n    Ny_shift = 2NN+isodd(n)\n    for k in n-2:-1:0\n        if isodd(n)\n            delta = Int(isodd(k))\n        end\n        for j in range(m, length=NN+delta)\n            @inbounds MM[j,2] = MM[j-Ny_shift,2]\n        end\n        m += NN+delta\n    end\n    return MM\nend\n\npaduapoints(n::Integer) = paduapoints(Float64,n)\n"
  },
  {
    "path": "src/ToeplitzPlusHankel.jl",
    "content": "struct ToeplitzPlusHankel{T, S, P1 <: Plan{S}, P2 <: Plan{S}} <: AbstractMatrix{T}\n    tc::Vector{T}\n    tr::Vector{T}\n    h::Vector{T}\n    th_dft::Matrix{S}\n    tht_dft::Matrix{S}\n    temp::Matrix{S}\n    plan::P1\n    iplan::P2\n    size::NTuple{2, Int}\nend\n\n# enforces tr[1] == tc[1]\nfunction ToeplitzPlusHankel(tc::Vector{T}, tr::Vector{T}, h::Vector{T}) where T\n    m = length(tc)\n    n = length(tr)\n    @assert length(h) == m+n-1\n    tr[1] = tc[1]\n    mn = m+n\n    S = promote_type(float(T), Complex{Float32})\n    th_dft = Matrix{S}(undef, mn, 2)\n    copyto!(th_dft, 1, tc, 1, m)\n    th_dft[m+1, 1] = zero(T)\n    copyto!(th_dft, m+2, Iterators.reverse(tr), 1, n-1)\n    copyto!(th_dft, mn+1, h, n, m)\n    th_dft[m+1, 2] = zero(T)\n    copyto!(th_dft, mn+m+2, h, 1, n-1)\n    tht_dft = Matrix{S}(undef, mn, 2)\n    copyto!(tht_dft, 1, tr, 1, n)\n    tht_dft[n+1, 1] = zero(T)\n    copyto!(tht_dft, n+2, Iterators.reverse(tc), 1, m-1)\n    copyto!(tht_dft, mn+1, h, m, n)\n    tht_dft[n+1, 2] = zero(T)\n    copyto!(tht_dft, mn+n+2, h, 1, m-1)\n\n    plan = plan_fft!(th_dft, 1)\n    plan*th_dft\n    plan*tht_dft\n    temp = zeros(S, mn, 2)\n    iplan = inv(plan)\n\n    ToeplitzPlusHankel{T, S, typeof(plan), typeof(iplan)}(tc, tr, h, th_dft, tht_dft, temp, plan, iplan, (m, n))\nend\n\n# A ChebyshevGramMatrix isa (symmetric positive-definite) ToeplitzPlusHankel matrix.\nfunction ToeplitzPlusHankel(G::ChebyshevGramMatrix)\n    n = size(G, 1)\n    ToeplitzPlusHankel(G.μ[1:n]/2, G.μ[1:n]/2, G.μ/2)\nend\n\nsize(A::ToeplitzPlusHankel) = A.size\ngetindex(A::ToeplitzPlusHankel, i::Integer, j::Integer) = (i ≥ j ? A.tc[i-j+1] : A.tr[j-i+1]) + A.h[i+j-1]\n\n# A view of a T+H is also T+H.\nfunction getindex(A::ToeplitzPlusHankel, ir::UnitRange{Int}, jr::UnitRange{Int})\n    fir, lir = first(ir), last(ir)\n    fjr, ljr = first(jr), last(jr)\n    if fir ≥ fjr\n        tc = A.tc[fir-fjr+1:lir-fjr+1]\n        tr = [A.tc[fir-fjr+1:-1:max(1, fir-ljr+1)]; A.tr[2:ljr-fir+1]]\n    else\n        tc = [A.tr[fjr-fir+1:-1:max(1, fjr-lir+1)]; A.tc[2:lir-fjr+1]]\n        tr = A.tr[fjr-fir+1:ljr-fir+1]\n    end\n    ToeplitzPlusHankel(tc, tr, A.h[fir+fjr-1:lir+ljr-1])\nend\n\n\n# y ← A x α + y β\nfunction mul!(y::StridedVector{T}, A::ToeplitzPlusHankel{T}, x::StridedVector{T}, α::S, β::S) where {T <: Real, S <: Real}\n    m, n = size(A)\n    @assert m == length(y)\n    @assert n == length(x)\n    mn = m+n\n    th_dft = A.th_dft\n    temp = A.temp\n    plan = A.plan\n    iplan = A.iplan\n\n    copyto!(temp, 1, x, 1, n)\n    copyto!(temp, mn+1, Iterators.reverse(x), 1, n)\n    @inbounds for j in n+1:mn\n        temp[j, 1] = zero(T)\n        temp[j, 2] = zero(T)\n    end\n    plan*temp\n    temp .*= th_dft\n    iplan*temp\n\n    if iszero(β)\n        @inbounds @simd for i in 1:m\n            y[i] = α * (real(temp[i, 1])+real(temp[i, 2]))\n        end\n    else\n        @inbounds @simd for i in 1:m\n            y[i] = α * (real(temp[i, 1])+real(temp[i, 2])) + β*y[i]\n        end\n    end\n    return y\nend\n\n# y ← A' x α + y β\nfunction mul!(y::StridedVector{T}, A::Adjoint{T, <:ToeplitzPlusHankel{T}}, x::StridedVector{T}, α::S, β::S) where {T <: Real, S <: Real}\n    m, n = size(A)\n    @assert m == length(y)\n    @assert n == length(x)\n    mn = m+n\n    AP = A.parent\n    tht_dft = AP.tht_dft\n    temp = AP.temp\n    plan = AP.plan\n    iplan = AP.iplan\n\n    copyto!(temp, 1, x, 1, n)\n    copyto!(temp, mn+1, Iterators.reverse(x), 1, n)\n    @inbounds for j in n+1:mn\n        temp[j, 1] = zero(T)\n        temp[j, 2] = zero(T)\n    end\n    plan*temp\n    temp .*= tht_dft\n    iplan*temp\n\n    if iszero(β)\n        @inbounds @simd for i in 1:m\n            y[i] = α * (real(temp[i, 1])+real(temp[i, 2]))\n        end\n    else\n        @inbounds @simd for i in 1:m\n            y[i] = α * (real(temp[i, 1])+real(temp[i, 2])) + β*y[i]\n        end\n    end\n    return y\nend\n\n\n# C ← A B α + C β\nfunction mul!(C::StridedMatrix{T}, A::ToeplitzPlusHankel{T}, B::StridedMatrix{T}, α::S, β::S) where {T <: Real, S <: Real}\n    m, n = size(A)\n    @assert m == size(C, 1)\n    @assert n == size(B, 1)\n    p = size(B, 2)\n    if size(C, 2) != p\n        throw(DimensionMismatch(\"input and output matrices must have same number of columns\"))\n    end\n\n    th_dft = A.th_dft\n    TC = promote_type(float(T), Complex{Float32})\n    temp = zeros(TC, m+n, 2p)\n    plan = plan_fft!(temp, 1)\n\n    for k in 1:p\n        copyto!(view(temp, :, 2k-1), 1, view(B, :, k), 1, n)\n        copyto!(view(temp, :, 2k), 1, Iterators.reverse(view(B, :, k)), 1, n)\n    end\n    plan*temp\n    for k in 1:p\n        vt = view(temp, :, 2k-1:2k)\n        vt .*= th_dft\n    end\n    plan\\temp\n\n    if iszero(β)\n        @inbounds for k in 1:p\n            for i in 1:m\n                C[i, k] = α * (real(temp[i, 2k-1])+real(temp[i, 2k]))\n            end\n        end\n    else\n        @inbounds for k in 1:p\n            for i in 1:m\n                C[i, k] = α * (real(temp[i, 2k-1])+real(temp[i, 2k])) + β*C[i, k]\n            end\n        end\n    end\n    return C\nend\n\n# Morally equivalent to mul!(C', B', A', α, β)' with StridedMatrix replaced by AbstractMatrix below\nfunction mul!(C::StridedMatrix{T}, A::StridedMatrix{T}, B::ToeplitzPlusHankel{T}, α::S, β::S) where {T <: Real, S <: Real}\n    n, m = size(B)\n    @assert m == size(C, 2)\n    @assert n == size(A, 2)\n    p = size(A, 1)\n    if size(C, 1) != p\n        throw(DimensionMismatch(\"input and output matrices must have same number of rows\"))\n    end\n\n    tht_dft = B.tht_dft\n    TC = promote_type(float(T), Complex{Float32})\n    temp = zeros(TC, m+n, 2p)\n    plan = plan_fft!(temp, 1)\n\n    for k in 1:p\n        copyto!(view(temp, :, 2k-1), 1, view(A, k, :), 1, n)\n        copyto!(view(temp, :, 2k), 1, Iterators.reverse(view(A, k, :)), 1, n)\n    end\n    plan*temp\n    for k in 1:p\n        vt = view(temp, :, 2k-1:2k)\n        vt .*= tht_dft\n    end\n    plan\\temp\n\n    if iszero(β)\n        @inbounds for k in 1:p\n            for i in 1:m\n                C[k, i] = α * (real(temp[i, 2k-1])+real(temp[i, 2k]))\n            end\n        end\n    else\n        @inbounds for k in 1:p\n            for i in 1:m\n                C[k, i] = α * (real(temp[i, 2k-1])+real(temp[i, 2k])) + β*C[k, i]\n            end\n        end\n    end\n    return C\nend\n\n# C ← A' B α + C β\nfunction mul!(C::StridedMatrix{T}, A::Adjoint{T, <:ToeplitzPlusHankel{T}}, B::StridedMatrix{T}, α::S, β::S) where {T <: Real, S <: Real}\n    m, n = size(A)\n    @assert m == size(C, 1)\n    @assert n == size(B, 1)\n    p = size(B, 2)\n    if size(C, 2) != p\n        throw(DimensionMismatch(\"input and output matrices must have same number of columns\"))\n    end\n\n    tht_dft = A.parent.tht_dft\n    TC = promote_type(float(T), Complex{Float32})\n    temp = zeros(TC, m+n, 2p)\n    plan = plan_fft!(temp, 1)\n\n    for k in 1:p\n        copyto!(view(temp, :, 2k-1), 1, view(B, :, k), 1, n)\n        copyto!(view(temp, :, 2k), 1, Iterators.reverse(view(B, :, k)), 1, n)\n    end\n    plan*temp\n    for k in 1:p\n        vt = view(temp, :, 2k-1:2k)\n        vt .*= tht_dft\n    end\n    plan\\temp\n\n    if iszero(β)\n        @inbounds for k in 1:p\n            for i in 1:m\n                C[i, k] = α * (real(temp[i, 2k-1])+real(temp[i, 2k]))\n            end\n        end\n    else\n        @inbounds for k in 1:p\n            for i in 1:m\n                C[i, k] = α * (real(temp[i, 2k-1])+real(temp[i, 2k])) + β*C[i, k]\n            end\n        end\n    end\n    return C\nend\n\n# Estimate the Frobenius norm of the Toeplitz-plus-Hankel matrix by working with the symbols.\nfunction normest(A::ToeplitzPlusHankel{T}) where T\n    m, n = size(A)\n    tc = A.tc\n    tr = A.tr\n    h = A.h\n    ret1 = zero(T)\n    ret2 = zero(T)\n    if m == min(m, n)\n        for i = 1:m\n            ret1 += (m+1-i)*abs2(tc[i])\n        end\n        for i = 2:n-m\n            ret1 += m*abs2(tr[i])\n        end\n        for i = max(n-m+1, 2):n\n            ret1 += (n+1-i)*abs2(tr[i])\n        end\n        for i = 1:m\n            ret2 += i*abs2(h[i])\n        end\n        for i = m+1:n\n            ret2 += m*abs2(h[i])\n        end\n        for i = n+1:m+n-1\n            ret2 += (m+n-i)*abs2(h[i])\n        end\n    else\n        for i = 1:n\n            ret1 += (n+1-i)*abs2(tr[i])\n        end\n        for i = 2:m-n\n            ret1 += n*abs2(tc[i])\n        end\n        for i = max(m-n+1, 2):m\n            ret1 += (m+1-i)*abs2(tc[i])\n        end\n        for i = 1:n\n            ret2 += i*abs2(h[i])\n        end\n        for i = n+1:m\n            ret2 += n*abs2(h[i])\n        end\n        for i = m+1:m+n-1\n            ret2 += (m+n-i)*abs2(h[i])\n        end\n    end\n    sqrt(ret1) + sqrt(ret2)\nend\n\nnormest(A::Symmetric{T, <: ToeplitzPlusHankel{T}}) where T = normest(parent(A))\nnormest(A::Hermitian{T, <: ToeplitzPlusHankel{T}}) where T = normest(parent(A))\nnormest(A::ChebyshevGramMatrix{T}) where T = normest(ToeplitzPlusHankel(A))\n"
  },
  {
    "path": "src/arrays.jl",
    "content": "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\r\nend\r\nsize(P::ArrayPlan) = P.szs\r\n\r\nfunction ArrayPlan(F::FTPlan{<:T}, c::AbstractArray{T}, dims::Tuple{<:Int}=(1,)) where T\r\n    szs = size(c)\r\n    @assert F.n == szs[dims[1]]\r\n    ArrayPlan(F, size(c), dims)\r\nend\r\n\r\nfunction *(P::ArrayPlan, f::AbstractArray)\r\n    F, dims, szs = P.F, P.dims, P.szs\r\n    @assert length(dims) == 1\r\n    @assert szs == size(f)\r\n    d = first(dims)\r\n\r\n    perm = (d, ntuple(i-> i + (i >= d), ndims(f) -1)...)\r\n    fp = permutedims(f, perm)\r\n\r\n    fr = reshape(fp, size(fp,1), :)\r\n\r\n    permutedims(reshape(F*fr, size(fp)...), invperm(perm))\r\nend\r\n\r\nfunction \\(P::ArrayPlan, f::AbstractArray)\r\n    F, dims, szs = P.F, P.dims, P.szs\r\n    @assert length(dims) == 1\r\n    @assert szs == size(f)\r\n    d = first(dims)\r\n\r\n    perm = (d, ntuple(i-> i + (i >= d), ndims(f) -1)...)\r\n    fp = permutedims(f, perm)\r\n\r\n    fr = reshape(fp, size(fp,1), :)\r\n\r\n    permutedims(reshape(F\\fr, size(fp)...), invperm(perm))\r\nend\r\n\r\nstruct NDimsPlan{T, FF<:ArrayPlan{<:T}, Szs<:Tuple, Dims<:Tuple} <: Plan{T}\r\n    F::FF\r\n    szs::Szs\r\n    dims::Dims\r\n    function NDimsPlan(F, szs, dims)\r\n        if length(Set(szs[[dims...]])) > 1\r\n            error(\"Different size in dims axes not yet implemented in N-dimensional transform.\")\r\n        end\r\n        new{eltype(F), typeof(F), typeof(szs), typeof(dims)}(F, szs, dims)\r\n    end\r\nend\r\n\r\nsize(P::NDimsPlan) = P.szs\r\n\r\nfunction NDimsPlan(F::FTPlan, szs::Tuple, dims::Tuple)\r\n    NDimsPlan(ArrayPlan(F, szs, (first(dims),)), szs, dims)\r\nend\r\n\r\nfunction *(P::NDimsPlan, f::AbstractArray)\r\n    F, dims = P.F, P.dims\r\n    @assert size(P) == size(f)\r\n    g = copy(f)\r\n    t = 1:ndims(g)\r\n    d1 = dims[1]\r\n    for d in dims\r\n        perm = ntuple(k -> k == d1 ? t[d] : k == d ? t[d1] : t[k], ndims(g))\r\n        gp = permutedims(g, perm)\r\n        g = permutedims(F*gp, invperm(perm))\r\n    end\r\n    return g\r\nend\r\n\r\nfunction \\(P::NDimsPlan, f::AbstractArray)\r\n    F, dims = P.F, P.dims\r\n    @assert size(P) == size(f)\r\n    g = copy(f)\r\n    t = 1:ndims(g)\r\n    d1 = dims[1]\r\n    for d in dims\r\n        perm = ntuple(k -> k == d1 ? t[d] : k == d ? t[d1] : t[k], ndims(g))\r\n        gp = permutedims(g, perm)\r\n        g = permutedims(F\\gp, invperm(perm))\r\n    end\r\n    return g\r\nend"
  },
  {
    "path": "src/chebyshevtransform.jl",
    "content": "## Transforms take values at Chebyshev points of the first and second kinds and produce Chebyshev coefficients\n\nabstract type ChebyshevPlan{T} <: Plan{T} end\n\n*(P::ChebyshevPlan{T}, x::AbstractArray{T}) where T = error(\"Plan applied to wrong size array\")\n\nsize(P::ChebyshevPlan) = isdefined(P, :plan) ? size(P.plan) : (0,)\nlength(P::ChebyshevPlan) = isdefined(P, :plan) ? length(P.plan) : 0\n\n\nconst FIRSTKIND = FFTW.REDFT10\nconst SECONDKIND = FFTW.REDFT00\n\nstruct ChebyshevTransformPlan{T,kind,K,inplace,N,R} <: ChebyshevPlan{T}\n    plan::FFTW.r2rFFTWPlan{T,K,inplace,N,R}\n    ChebyshevTransformPlan{T,kind,K,inplace,N,R}(plan) where {T,kind,K,inplace,N,R} = new{T,kind,K,inplace,N,R}(plan)\n    ChebyshevTransformPlan{T,kind,K,inplace,N,R}() where {T,kind,K,inplace,N,R} = new{T,kind,K,inplace,N,R}()\nend\n\nChebyshevTransformPlan{T,kind}(plan::FFTW.r2rFFTWPlan{T,K,inplace,N,R}) where {T,kind,K,inplace,N,R} =\n    ChebyshevTransformPlan{T,kind,K,inplace,N,R}(plan)\n\n# jump through some hoops to make inferrable\n\nfunction plan_chebyshevtransform!(x::AbstractArray{T,N}, ::Val{1}, dims...; kws...) where {T<:fftwNumber,N}\n    if isempty(x)\n        ChebyshevTransformPlan{T,1,Vector{Int32},true,N,isempty(dims) ? NTuple{N,Int} : typeof(dims[1])}()\n    else\n        ChebyshevTransformPlan{T,1}(FFTW.plan_r2r!(x, FIRSTKIND, dims...; kws...))\n    end\nend\nfunction plan_chebyshevtransform!(x::AbstractArray{T,N}, ::Val{2}, dims...; kws...) where {T<:fftwNumber,N}\n    any(≤(1),size(x)) && throw(ArgumentError(\"Array must contain at least 2 entries\"))\n    ChebyshevTransformPlan{T,2}(FFTW.plan_r2r!(x, SECONDKIND, dims...; kws...))\nend\n\n\nfunction plan_chebyshevtransform(x::AbstractArray{T,N}, ::Val{1}, dims...; kws...) where {T<:fftwNumber,N}\n    if isempty(x)\n        ChebyshevTransformPlan{T,1,Vector{Int32},false,N,isempty(dims) ? NTuple{N,Int} : typeof(dims[1])}()\n    else\n        ChebyshevTransformPlan{T,1}(FFTW.plan_r2r(x, FIRSTKIND, dims...; kws...))\n    end\nend\nfunction plan_chebyshevtransform(x::AbstractArray{T,N}, ::Val{2}, dims...; kws...) where {T<:fftwNumber,N}\n    any(≤(1),size(x)) && throw(ArgumentError(\"Array must contain at least 2 entries\"))\n    ChebyshevTransformPlan{T,2}(FFTW.plan_r2r(x, SECONDKIND, dims...; kws...))\nend\n\n\n# convert x if necessary\n_maybemutablecopy(x::StridedArray{T}, ::Type{T}) where {T} = x\n_maybemutablecopy(x, T) = Array{T}(x)\n@inline _plan_mul!(y::AbstractArray{T}, P::Plan{T}, x::AbstractArray) where T = mul!(y, P, _maybemutablecopy(x, T))\n\nfunction applydim!(op!, X::AbstractArray, Rpre, Rpost, ind)\n    for Ipost in Rpost, Ipre in Rpre\n        v = view(X, Ipre, ind, Ipost)\n        op!(v)\n    end\n    X\nend\nfunction applydim!(op!, X::AbstractArray, d::Integer, ind)\n    Rpre = CartesianIndices(axes(X)[1:d-1])\n    Rpost = CartesianIndices(axes(X)[d+1:end])\n    applydim!(op!, X, Rpre, Rpost, ind)\nend\n\nfor op in (:ldiv, :lmul)\n    op_dim_begin! = Symbol(op, :_dim_begin!)\n    op_dim_end! = Symbol(op, :_dim_end!)\n    op! = Symbol(op, :!)\n    @eval begin\n        function $op_dim_begin!(α, d::Number, y::AbstractArray)\n            # scale just the d-th dimension by permuting it to the first\n            d ∈ 1:ndims(y) || throw(ArgumentError(\"dimension $d must lie between 1 and $(ndims(y))\"))\n            applydim!(v -> $op!(α, v), y, d, 1)\n        end\n\n        function $op_dim_end!(α, d::Number, y::AbstractArray)\n            # scale just the d-th dimension by permuting it to the first\n            d ∈ 1:ndims(y) || throw(ArgumentError(\"dimension $d must lie between 1 and $(ndims(y))\"))\n            applydim!(v -> $op!(α, v), y, d, size(y, d))\n        end\n    end\nend\n\n\n@inline function _cheb1_rescale!(d::Number, y::AbstractArray)\n    ldiv_dim_begin!(2, d, y)\n    ldiv!(size(y,d), y)\nend\n\nfunction _prod_size(sz, d)\n    ret = 1\n    for k in d\n        ret *= sz[k]\n    end\n    ret\nend\n\n\n@inline function _cheb1_rescale!(d, y::AbstractArray)\n    for k in d\n        ldiv_dim_begin!(2, k, y)\n    end\n    ldiv!(_prod_size(size(y), d), y)\nend\n\n\n\nfunction *(P::ChebyshevTransformPlan{T,1,K,true,N}, x::AbstractArray{T,N}) where {T,K,N}\n    isempty(x) && return x\n\n    y = P.plan*x # will be  === x if in-place\n    _cheb1_rescale!(P.plan.region, y)\nend\n\nfunction mul!(y::AbstractArray{T,N}, P::ChebyshevTransformPlan{T,1,K,false,N}, x::AbstractArray{<:Any,N}) where {T,K,N}\n    size(y) == size(x) || throw(DimensionMismatch(\"output must match dimension\"))\n    isempty(x) && return y\n    _plan_mul!(y, P.plan, x)\n    _cheb1_rescale!(P.plan.region, y)\nend\n\n\n\nfunction _cheb2_rescale!(d::Number, y::AbstractArray)\n    ldiv_dim_begin!(2, d, y)\n    ldiv_dim_end!(2, d, y)\n    ldiv!(size(y,d)-1, y)\nend\n\n# TODO: higher dimensional arrays\nfunction _cheb2_rescale!(d, y::AbstractArray)\n    for k in d\n        ldiv_dim_begin!(2, k, y)\n        ldiv_dim_end!(2, k, y)\n    end\n\n    ldiv!(_prod_size(size(y) .- 1, d), y)\nend\n\nfunction *(P::ChebyshevTransformPlan{T,2,K,true,N}, x::AbstractArray{T,N}) where {T,K,N}\n    n = length(x)\n    y = P.plan*x # will be  === x if in-place\n    _cheb2_rescale!(P.plan.region, y)\nend\n\nfunction mul!(y::AbstractArray{T,N}, P::ChebyshevTransformPlan{T,2,K,false,N}, x::AbstractArray{<:Any,N}) where {T,K,N}\n    n = length(x)\n    length(y) == n || throw(DimensionMismatch(\"output must match dimension\"))\n    _plan_mul!(y, P.plan, x)\n    _cheb2_rescale!(P.plan.region, y)\nend\n\n*(P::ChebyshevTransformPlan{T,kind,K,false,N}, x::AbstractArray{T,N}) where {T,kind,K,N} =\n    mul!(similar(x), P, x)\n\n\"\"\"\n    chebyshevtransform!(x, kind=Val(1))\n\ntransforms from values on a Chebyshev grid of the first or second kind to Chebyshev\ncoefficients, in-place\n\"\"\"\nchebyshevtransform!(x, dims...; kws...) = plan_chebyshevtransform!(x, dims...; kws...)*x\n\n\n\"\"\"\n    chebyshevtransform(x, kind=Val(1))\n\ntransforms from values on a Chebyshev grid of the first or second kind to Chebyshev\ncoefficients.\n\"\"\"\nchebyshevtransform(x, dims...; kws...) = plan_chebyshevtransform(x, dims...; kws...) * x\n\n\n## Inverse transforms take Chebyshev coefficients and produce values at Chebyshev points of the first and second kinds\n\n\nconst IFIRSTKIND = FFTW.REDFT01\n\nstruct IChebyshevTransformPlan{T,kind,K,inplace,N,R} <: ChebyshevPlan{T}\n    plan::FFTW.r2rFFTWPlan{T,K,inplace,N,R}\n    IChebyshevTransformPlan{T,kind,K,inplace,N,R}(plan) where {T,kind,K,inplace,N,R} = new{T,kind,K,inplace,N,R}(plan)\n    IChebyshevTransformPlan{T,kind,K,inplace,N,R}() where {T,kind,K,inplace,N,R} = new{T,kind,K,inplace,N,R}()\nend\n\nIChebyshevTransformPlan{T,kind}(F::FFTW.r2rFFTWPlan{T,K,inplace,N,R}) where {T,kind,K,inplace,N,R} =\n    IChebyshevTransformPlan{T,kind,K,inplace,N,R}(F)\n\n\n\n# second kind Chebyshev transforms share a plan with their inverse\n# so we support this via inv\ninv(P::ChebyshevTransformPlan{T,2}) where {T} = IChebyshevTransformPlan{T,2}(P.plan)\ninv(P::IChebyshevTransformPlan{T,2}) where {T} = ChebyshevTransformPlan{T,2}(P.plan)\n\ninv(P::ChebyshevTransformPlan{T,1}) where {T} = IChebyshevTransformPlan{T,1}(inv(P.plan).p)\ninv(P::IChebyshevTransformPlan{T,1}) where {T} = ChebyshevTransformPlan{T,1}(inv(P.plan).p)\n\n\n\n\\(P::ChebyshevTransformPlan, x::AbstractArray) = inv(P) * x\n\\(P::IChebyshevTransformPlan, x::AbstractArray) = inv(P) * x\n\n\nfunction plan_ichebyshevtransform!(x::AbstractArray{T,N}, ::Val{1}, dims...; kws...) where {T<:fftwNumber,N}\n    if isempty(x)\n        IChebyshevTransformPlan{T,1,Vector{Int32},true,N,isempty(dims) ? NTuple{N,Int} : typeof(dims[1])}()\n    else\n        IChebyshevTransformPlan{T,1}(FFTW.plan_r2r!(x, IFIRSTKIND, dims...; kws...))\n    end\nend\n\nfunction plan_ichebyshevtransform!(x::AbstractArray{T}, ::Val{2}, dims...; kws...) where T<:fftwNumber\n    inv(plan_chebyshevtransform!(x, Val(2), dims...; kws...))\nend\n\nfunction plan_ichebyshevtransform(x::AbstractArray{T,N}, ::Val{1}, dims...; kws...) where {T<:fftwNumber,N}\n    if isempty(x)\n        IChebyshevTransformPlan{T,1,Vector{Int32},false,N,isempty(dims) ? NTuple{N,Int} : typeof(dims[1])}()\n    else\n        IChebyshevTransformPlan{T,1}(FFTW.plan_r2r(x, IFIRSTKIND, dims...; kws...))\n    end\nend\n\nfunction plan_ichebyshevtransform(x::AbstractArray{T}, ::Val{2}, dims...; kws...) where T<:fftwNumber\n    inv(plan_chebyshevtransform(x, Val(2), dims...; kws...))\nend\n\n@inline function _icheb1_prescale!(d::Number, x::AbstractArray)\n    lmul_dim_begin!(2, d, x)\n    x\nend\n@inline function _icheb1_prescale!(d, x::AbstractArray)\n    for k in d\n        _icheb1_prescale!(k, x)\n    end\n    x\nend\n@inline function _icheb1_postscale!(d::Number, x::AbstractArray)\n    ldiv_dim_begin!(2, d, x)\n    x\nend\n\n@inline function _icheb1_postscale!(d, x::AbstractArray)\n    for k in d\n        _icheb1_postscale!(k, x)\n    end\n    x\nend\n\nfunction *(P::IChebyshevTransformPlan{T,1,K,true,N}, x::AbstractArray{T,N}) where {T<:fftwNumber,K,N}\n    n = length(x)\n    n == 0 && return x\n\n    _icheb1_prescale!(P.plan.region, x)\n    x = ldiv!(2^length(P.plan.region), P.plan*x)\n    x\nend\n\nfunction mul!(y::AbstractArray{T,N}, P::IChebyshevTransformPlan{T,1,K,false,N}, x::AbstractArray{T,N}) where {T<:fftwNumber,K,N}\n    size(y) == size(x) || throw(DimensionMismatch(\"output must match dimension\"))\n    isempty(x) && return y\n\n    _icheb1_prescale!(P.plan.region, x) # TODO: don't mutate x\n    _plan_mul!(y, P.plan, x)\n    _icheb1_postscale!(P.plan.region, x)\n    ldiv!(2^length(P.plan.region), y)\nend\n\n@inline function _icheb2_prescale!(d::Number, x::AbstractArray)\n    lmul_dim_begin!(2, d, x)\n    lmul_dim_end!(2, d, x)\n    x\nend\n@inline function _icheb2_prescale!(d, x::AbstractArray)\n    for k in d\n        _icheb2_prescale!(k, x)\n    end\n    x\nend\n\n@inline function _icheb2_postrescale!(d::Number, x::AbstractArray)\n    ldiv_dim_begin!(2, d, x)\n    ldiv_dim_end!(2, d, x)\n    x\nend\n@inline function _icheb2_postrescale!(d, x::AbstractArray)\n    for k in d\n        _icheb2_postrescale!(k, x)\n    end\n    x\nend\n@inline function _icheb2_rescale!(d::Number, y::AbstractArray{T}) where T\n    _icheb2_prescale!(d, y)\n    lmul!(convert(T, size(y,d) - 1)/2, y)\n    y\nend\n@inline function _icheb2_rescale!(d, y::AbstractArray{T}) where T\n    _icheb2_prescale!(d, y)\n    lmul!(_prod_size(convert.(T, size(y) .- 1)./2, d), y)\n    y\nend\n\nfunction *(P::IChebyshevTransformPlan{T,2,K,true,N}, x::AbstractArray{T,N}) where {T<:fftwNumber,K,N}\n    n = length(x)\n\n    _icheb2_prescale!(P.plan.region, x)\n    x = inv(P)*x\n    _icheb2_rescale!(P.plan.region, x)\nend\n\nfunction mul!(y::AbstractArray{T,N}, P::IChebyshevTransformPlan{T,2,K,false,N}, x::AbstractArray{<:Any,N}) where {T<:fftwNumber,K,N}\n    n = length(x)\n    length(y) == n || throw(DimensionMismatch(\"output must match dimension\"))\n\n    _icheb2_prescale!(P.plan.region, x)\n    _plan_mul!(y, inv(P), x)\n    _icheb2_postrescale!(P.plan.region, x)\n    _icheb2_rescale!(P.plan.region, y)\nend\n\n*(P::IChebyshevTransformPlan{T,kind,K,false,N}, x::AbstractArray{T,N}) where {T,kind,K,N} =\n    mul!(similar(x), P, _maybemutablecopy(x, T))\nichebyshevtransform!(x::AbstractArray, dims...; kwds...) = plan_ichebyshevtransform!(x, dims...; kwds...)*x\nichebyshevtransform(x, dims...; kwds...) = plan_ichebyshevtransform(x, dims...; kwds...)*x\n\n\n#######\n# Chebyshev U\n#######\n\nconst UFIRSTKIND = FFTW.RODFT10\nconst USECONDKIND = FFTW.RODFT00\n\nstruct ChebyshevUTransformPlan{T,kind,K,inplace,N,R} <: ChebyshevPlan{T}\n    plan::FFTW.r2rFFTWPlan{T,K,inplace,N,R}\n    ChebyshevUTransformPlan{T,kind,K,inplace,N,R}(plan) where {T,kind,K,inplace,N,R} = new{T,kind,K,inplace,N,R}(plan)\n    ChebyshevUTransformPlan{T,kind,K,inplace,N,R}() where {T,kind,K,inplace,N,R} = new{T,kind,K,inplace,N,R}()\nend\n\nChebyshevUTransformPlan{T,kind}(plan::FFTW.r2rFFTWPlan{T,K,inplace,N,R}) where {T,kind,K,inplace,N,R} =\n    ChebyshevUTransformPlan{T,kind,K,inplace,N,R}(plan)\n\n\nfunction plan_chebyshevutransform!(x::AbstractArray{T,N}, ::Val{1}, dims...; kws...) where {T<:fftwNumber,N}\n    if isempty(x)\n        ChebyshevUTransformPlan{T,1,Vector{Int32},true,N,isempty(dims) ? NTuple{N,Int} : typeof(dims[1])}()\n    else\n        ChebyshevUTransformPlan{T,1}(FFTW.plan_r2r!(x, UFIRSTKIND, dims...; kws...))\n    end\nend\nfunction plan_chebyshevutransform!(x::AbstractArray{T,N}, ::Val{2}, dims...; kws...) where {T<:fftwNumber,N}\n    any(≤(1),size(x)) && throw(ArgumentError(\"Array must contain at least 2 entries\"))\n    ChebyshevUTransformPlan{T,2}(FFTW.plan_r2r!(x, USECONDKIND, dims...; kws...))\nend\n\nfunction plan_chebyshevutransform(x::AbstractArray{T,N}, ::Val{1}, dims...; kws...) where {T<:fftwNumber,N}\n    if isempty(x)\n        ChebyshevUTransformPlan{T,1,Vector{Int32},false,N,isempty(dims) ? NTuple{N,Int} : typeof(dims[1])}()\n    else\n        ChebyshevUTransformPlan{T,1}(FFTW.plan_r2r(x, UFIRSTKIND, dims...; kws...))\n    end\nend\nfunction plan_chebyshevutransform(x::AbstractArray{T,N}, ::Val{2}, dims...; kws...) where {T<:fftwNumber,N}\n    if isempty(dims)\n        any(≤(1), size(x)) && throw(ArgumentError(\"Array must contain at least 2 entries\"))\n    else\n        for d in dims[1]\n            size(x,d) ≤ 1 && throw(ArgumentError(\"Array must contain at least 2 entries\"))\n        end\n    end\n    ChebyshevUTransformPlan{T,2}(FFTW.plan_r2r(x, USECONDKIND, dims...; kws...))\nend\n\nfor f in [:_chebu1_prescale!, :_chebu1_postscale!, :_chebu2_prescale!, :_chebu2_postscale!,\n            :_ichebu1_postscale!]\n    _f = Symbol(:_, f)\n    @eval begin\n        @inline function $f(d::Number, X::AbstractArray)\n            d ∈ 1:ndims(X) || throw(\"dimension $d must lie between 1 and $(ndims(X))\")\n            $_f(d, X)\n            X\n        end\n        @inline function $f(d, y::AbstractArray)\n            for k in d\n                $f(k, y)\n            end\n            y\n        end\n    end\nend\n\nfunction __chebu1_prescale!(d::Number, X::AbstractArray{T}) where {T}\n    m = size(X,d)\n    r = one(T)/(2m) .+ ((1:m) .- one(T))./m\n    applydim!(v -> v .*= sinpi.(r) ./ m, X, d, :)\nend\n\n@inline function __chebu1_postscale!(d::Number, X::AbstractArray{T}) where {T}\n    m = size(X,d)\n    r = one(T)/(2m) .+ ((1:m) .- one(T))./m\n    applydim!(v -> v ./= sinpi.(r) ./ m, X, d, :)\nend\n\nfunction *(P::ChebyshevUTransformPlan{T,1,K,true,N}, x::AbstractArray{T,N}) where {T,K,N}\n    length(x) ≤ 1 && return x\n    _chebu1_prescale!(P.plan.region, x)\n    P.plan * x\nend\n\nfunction mul!(y::AbstractArray{T}, P::ChebyshevUTransformPlan{T,1,K,false}, x::AbstractArray{T}) where {T,K}\n    size(y) == size(x) || throw(DimensionMismatch(\"output must match dimension\"))\n    isempty(x) && return y\n    _chebu1_prescale!(P.plan.region, x) # Todo don't mutate x\n    _plan_mul!(y, P.plan, x)\n    _chebu1_postscale!(P.plan.region, x)\n    for d in P.plan.region\n        size(y,d) == 1 && ldiv!(2, y) # fix doubling\n    end\n    y\nend\n\n\n@inline function __chebu2_prescale!(d, X::AbstractArray{T}) where {T}\n    m = size(X,d)\n    c = one(T)/ (m+1)\n    r = (1:m) .* c\n    applydim!(v -> v .*= sinpi.(r), X, d, :)\nend\n\n@inline function __chebu2_postscale!(d::Number, X::AbstractArray{T}) where {T}\n    m = size(X,d)\n    c = one(T)/ (m+1)\n    r = (1:m) .* c\n    applydim!(v -> v ./= sinpi.(r), X, d, :)\nend\n\nfunction *(P::ChebyshevUTransformPlan{T,2,K,true,N}, x::AbstractArray{T,N}) where {T,K,N}\n    sc = one(T)\n    for d in P.plan.region\n        sc *= one(T)/(size(x,d)+1)\n    end\n    _chebu2_prescale!(P.plan.region, x)\n    lmul!(sc, P.plan * x)\nend\n\nfunction mul!(y::AbstractArray{T}, P::ChebyshevUTransformPlan{T,2,K,false}, x::AbstractArray{T}) where {T,K}\n    sc = one(T)\n    for d in P.plan.region\n        sc *= one(T)/(size(x,d)+1)\n    end\n    _chebu2_prescale!(P.plan.region, x) # TODO don't mutate x\n    _plan_mul!(y, P.plan, x)\n    _chebu2_postscale!(P.plan.region, x)\n    lmul!(sc, y)\nend\n\n*(P::ChebyshevUTransformPlan{T,kind,K,false,N}, x::AbstractArray{T,N}) where {T,kind,K,N} =\n    mul!(similar(x), P, x)\n\nchebyshevutransform!(x::AbstractArray{T}, dims...; kws...) where {T<:fftwNumber} =\n    plan_chebyshevutransform!(x, dims...; kws...)*x\n\n\n\"\"\"\n    chebyshevutransform(x, ::Val{kind}=Val(1))\n\ntransforms from values on a Chebyshev grid of the first or second kind to Chebyshev\ncoefficients of the 2nd kind (Chebyshev U expansion).\n\"\"\"\nchebyshevutransform(x, dims...; kws...) = plan_chebyshevutransform(x, dims...; kws...)*x\n\n\n## Inverse transforms take ChebyshevU coefficients and produce values at ChebyshevU points of the first and second kinds\nconst IUFIRSTKIND = FFTW.RODFT01\n\nstruct IChebyshevUTransformPlan{T,kind,K,inplace,N,R} <: ChebyshevPlan{T}\n    plan::FFTW.r2rFFTWPlan{T,K,inplace,N,R}\n    IChebyshevUTransformPlan{T,kind,K,inplace,N,R}(plan) where {T,kind,K,inplace,N,R} = new{T,kind,K,inplace,N,R}(plan)\n    IChebyshevUTransformPlan{T,kind,K,inplace,N,R}() where {T,kind,K,inplace,N,R} = new{T,kind,K,inplace,N,R}()\nend\n\nIChebyshevUTransformPlan{T,kind}(F::FFTW.r2rFFTWPlan{T,K,inplace,N,R}) where {T,kind,K,inplace,N,R} =\n    IChebyshevUTransformPlan{T,kind,K,inplace,N,R}(F)\n\nfunction plan_ichebyshevutransform!(x::AbstractArray{T,N}, ::Val{1}, dims...; kws...) where {T<:fftwNumber,N}\n    if isempty(x)\n        IChebyshevUTransformPlan{T,1,Vector{Int32},true,N,isempty(dims) ? NTuple{N,Int} : typeof(dims[1])}()\n    else\n        IChebyshevUTransformPlan{T,1}(FFTW.plan_r2r!(x, IUFIRSTKIND, dims...; kws...))\n    end\nend\nfunction plan_ichebyshevutransform!(x::AbstractArray{T,N}, ::Val{2}, dims...; kws...) where {T<:fftwNumber,N}\n    any(≤(1),size(x)) && throw(ArgumentError(\"Array must contain at least 2 entries\"))\n    IChebyshevUTransformPlan{T,2}(FFTW.plan_r2r!(x, USECONDKIND, dims...))\nend\n\nfunction plan_ichebyshevutransform(x::AbstractArray{T,N}, ::Val{1}, dims...; kws...) where {T<:fftwNumber,N}\n    if isempty(x)\n        IChebyshevUTransformPlan{T,1,Vector{Int32},false,N,isempty(dims) ? NTuple{N,Int} : typeof(dims[1])}()\n    else\n        IChebyshevUTransformPlan{T,1}(FFTW.plan_r2r(x, IUFIRSTKIND, dims...; kws...))\n    end\nend\nfunction plan_ichebyshevutransform(x::AbstractArray{T,N}, ::Val{2}, dims...; kws...) where {T<:fftwNumber,N}\n    any(≤(1),size(x)) && throw(ArgumentError(\"Array must contain at least 2 entries\"))\n    IChebyshevUTransformPlan{T,2}(FFTW.plan_r2r(x, USECONDKIND, dims...; kws...))\nend\n\n\n# second kind Chebyshev transforms share a plan with their inverse\n# so we support this via inv\ninv(P::ChebyshevUTransformPlan{T,2}) where {T} = IChebyshevUTransformPlan{T,2}(P.plan)\ninv(P::IChebyshevUTransformPlan{T,2}) where {T} = ChebyshevUTransformPlan{T,2}(P.plan)\n\ninv(P::ChebyshevUTransformPlan{T,1}) where {T} = IChebyshevUTransformPlan{T,1}(inv(P.plan).p)\ninv(P::IChebyshevUTransformPlan{T,1}) where {T} = ChebyshevUTransformPlan{T,1}(inv(P.plan).p)\n\n@inline function __ichebu1_postscale!(d::Number, X::AbstractArray{T}) where {T}\n    m = size(X,d)\n    r = one(T)/(2m) .+ ((1:m) .- one(T))/m\n    applydim!(v -> v ./= 2 .* sinpi.(r), X, d, :)\nend\n\nfunction *(P::IChebyshevUTransformPlan{T,1,K,true}, x::AbstractArray{T}) where {T<:fftwNumber,K}\n    length(x) ≤ 1 && return x\n    x = P.plan * x\n    _ichebu1_postscale!(P.plan.region, x)\nend\n\nfunction mul!(y::AbstractArray{T}, P::IChebyshevUTransformPlan{T,1,K,false}, x::AbstractArray{T}) where {T<:fftwNumber,K}\n    size(y) == size(x) || throw(DimensionMismatch(\"output must match dimension\"))\n    isempty(x) && return y\n    _plan_mul!(y, P.plan, x)\n    _ichebu1_postscale!(P.plan.region, y)\n    for d in P.plan.region\n        size(y,d) == 1 && lmul!(2, y) # fix doubling\n    end\n    y\nend\n\nfunction _ichebu2_rescale!(d::Number, x::AbstractArray{T}) where T\n    _chebu2_postscale!(d, x)\n    ldiv!(2, x)\n    x\nend\n\n@inline function _ichebu2_rescale!(d, y::AbstractArray)\n    for k in d\n        _ichebu2_rescale!(k, y)\n    end\n    y\nend\n\nfunction *(P::IChebyshevUTransformPlan{T,2,K,true}, x::AbstractArray{T}) where {T<:fftwNumber,K}\n    n = length(x)\n    n ≤ 1 && return x\n\n    x = P.plan * x\n    _ichebu2_rescale!(P.plan.region, x)\nend\n\nfunction mul!(y::AbstractArray{T}, P::IChebyshevUTransformPlan{T,2,K,false}, x::AbstractArray{T}) where {T<:fftwNumber,K}\n    size(y) == size(x) || throw(DimensionMismatch(\"output must match dimension\"))\n    length(x) ≤ 1 && return x\n\n    _plan_mul!(y, P.plan, x)\n    _ichebu2_rescale!(P.plan.region, y)\nend\n\nichebyshevutransform!(x::AbstractArray{T}, dims...; kwds...) where {T<:fftwNumber} =\n    plan_ichebyshevutransform!(x, dims...; kwds...)*x\n\nichebyshevutransform(x, dims...; kwds...) = plan_ichebyshevutransform(x, dims...; kwds...)*x\n\n*(P::IChebyshevUTransformPlan{T,k,K,false,N}, x::AbstractArray{T,N}) where {T,k,K,N} =\n    mul!(similar(x), P, x)\n\n\n## Code generation for integer inputs\n\nfor func in (:chebyshevtransform,:ichebyshevtransform,:chebyshevutransform,:ichebyshevutransform)\n    @eval $func(x::AbstractVector{T}, dims...; kwds...) where {T<:Integer} = $func(convert(AbstractVector{float(T)},x), dims...; kwds...)\nend\n\n\n\n## points\n\nstruct ChebyshevGrid{kind,T} <: AbstractVector{T}\n    n::Int\n    function ChebyshevGrid{1,T}(n::Int) where T\n        n ≥ 0 || throw(ArgumentError(\"Number of points must be nonnehative\"))\n        new{1,T}(n)\n    end\n    function ChebyshevGrid{2,T}(n::Int) where T\n        n ≥ 2 || throw(ArgumentError(\"Number of points must be greater than 2\"))\n        new{2,T}(n)\n    end\nend\n\nChebyshevGrid{kind}(n::Integer) where kind = ChebyshevGrid{kind,Float64}(n)\n\nsize(g::ChebyshevGrid) = (g.n,)\ngetindex(g::ChebyshevGrid{1,T}, k::Integer) where T =\n    sinpi(convert(T,g.n-2k+1)/(2g.n))\n\ngetindex(g::ChebyshevGrid{2,T}, k::Integer) where T =\n    sinpi(convert(T,g.n-2k+1)/(2g.n-2))\n\nchebyshevpoints(::Type{T}, n::Integer, ::Val{kind}) where {T<:Number,kind} = ChebyshevGrid{kind,T}(n)\nchebyshevpoints(::Type{T}, n::Integer) where T = chebyshevpoints(T, n, Val(1))\nchebyshevpoints(n::Integer, kind=Val(1)) = chebyshevpoints(Float64, n, kind)\n\n\n# sin(nθ) coefficients to values at Clenshaw-Curtis nodes except ±1\n#\n# struct DSTPlan{T,kind,inplace,P} <: Plan{T}\n#     plan::P\n# end\n#\n# DSTPlan{k,inp}(plan) where {k,inp} =\n#     DSTPlan{eltype(plan),k,inp,typeof(plan)}(plan)\n#\n#\n# plan_DSTI!(x) = length(x) > 0 ? DSTPlan{1,true}(FFTW.FFTW.plan_r2r!(x, FFTW.FFTW.RODFT00)) :\n#                                 fill(one(T),1,length(x))\n#\n# function *(P::DSTPlan{T,1}, x::AbstractArray) where {T}\n#     x = P.plan*x\n#     rmul!(x,half(T))\n# end\n\n\n###\n# BigFloat\n# Use `Nothing` and fall back to FFT\n###\n\n\nplan_chebyshevtransform(x::AbstractArray{T,N}, ::Val{kind}, dims...; kws...) where {T,N,kind} =\n    ChebyshevTransformPlan{T,kind,Nothing,false,N,UnitRange{Int}}()\nplan_ichebyshevtransform(x::AbstractArray{T,N}, ::Val{kind}, dims...; kws...) where {T,N,kind} =\n    IChebyshevTransformPlan{T,kind,Nothing,false,N,UnitRange{Int}}()\n\nplan_chebyshevtransform!(x::AbstractArray{T,N}, ::Val{kind}, dims...; kws...) where {T,N,kind} =\n    ChebyshevTransformPlan{T,kind,Nothing,true,N,UnitRange{Int}}()\nplan_ichebyshevtransform!(x::AbstractArray{T,N}, ::Val{kind}, dims...; kws...) where {T,N,kind} =\n    IChebyshevTransformPlan{T,kind,Nothing,true,N,UnitRange{Int}}()\n\n\n#following Chebfun's @Chebtech1/vals2coeffs.m and @Chebtech2/vals2coeffs.m\nfunction *(P::ChebyshevTransformPlan{T,1,Nothing,false}, x::AbstractVector{T}) where T\n    n = length(x)\n    if n == 1\n        x\n    else\n        w = [2exp(im*convert(T,π)*k/2n) for k=0:n-1]\n        ret = w.*ifft([x;reverse(x)])[1:n]\n        ret = T<:Real ? real(ret) : ret\n        ret[1] /= 2\n        ret\n    end\nend\n\n\n# function *(P::ChebyshevTransformPlan{T,1,K,Nothing,false}, x::AbstractVector{T}) where {T,K}\n#     n = length(x)\n#     if n == 1\n#         x\n#     else\n#         ret = ifft([x;x[end:-1:2]])[1:n]\n#         ret = T<:Real ? real(ret) : ret\n#         ret[2:n-1] *= 2\n#         ret\n#     end\n# end\n\n\n*(P::ChebyshevTransformPlan{T,1,Nothing,true,N,R}, x::AbstractVector{T}) where {T,N,R} =\n    copyto!(x, ChebyshevTransformPlan{T,1,Nothing,false,N,R}() * x)\n# *(P::ChebyshevTransformPlan{T,2,true,Nothing}, x::AbstractVector{T}) where T =\n#     copyto!(x, ChebyshevTransformPlan{T,2,false,Nothing}() * x)\n\n\n#following Chebfun's @Chebtech1/vals2coeffs.m and @Chebtech2/vals2coeffs.m\nfunction *(P::IChebyshevTransformPlan{T,1,Nothing,false}, x::AbstractVector{T}) where T\n    n = length(x)\n    if n == 1\n        x\n    else\n        w = [exp(-im*convert(T,π)*k/2n)/2 for k=0:2n-1]\n        w[1] *= 2;w[n+1] *= 0;w[n+2:end] *= -1\n        ret = fft(w.*[x;one(T);x[end:-1:2]])\n        ret = T<:Real ? real(ret) : ret\n        ret[1:n]\n    end\nend\n\n# function *(P::IChebyshevTransformPlan{T,2,K,Nothing,true}, x::AbstractVector{T}) where {T,K}\n#     n = length(x)\n#     if n == 1\n#         x\n#     else\n#         x[1] *= 2; x[end] *= 2\n#         chebyshevtransform!(x, Val(2))\n#         x[1] *= 2; x[end] *= 2\n#         lmul!(convert(T,n-1)/2, x)\n#         x\n#     end\n# end\n\n*(P::IChebyshevTransformPlan{T,1,Nothing,true,N,R}, x::AbstractVector{T}) where {T,N,R} =\n    copyto!(x, IChebyshevTransformPlan{T,1,Nothing,false,N,R}() * x)\n# *(P::IChebyshevTransformPlan{T,SECONDKIND,false,Nothing}, x::AbstractVector{T}) where T =\n#     IChebyshevTransformPlan{T,SECONDKIND,true,Nothing}() * copy(x)\n\n\nfor pln in (:plan_chebyshevtransform!, :plan_chebyshevtransform, \n            :plan_chebyshevutransform!, :plan_chebyshevutransform, \n            :plan_ichebyshevutransform, :plan_ichebyshevutransform!, \n            :plan_ichebyshevtransform, :plan_ichebyshevtransform!)\n    @eval begin\n        $pln(x::AbstractArray, dims...; kws...) = $pln(x, Val(1), dims...; kws...)\n        $pln(::Type{T}, szs, dims...; kwds...) where T = $pln(Array{T}(undef, szs...), dims...; kwds...)\n    end\nend\n"
  },
  {
    "path": "src/clenshawcurtis.jl",
    "content": "plan_clenshawcurtis(μ) = length(μ) > 1 ? FFTW.plan_r2r!(μ, FFTW.REDFT00) : fill!(similar(μ),1)'\n\n\"\"\"\nCompute nodes of the Clenshaw—Curtis quadrature rule.\n\"\"\"\nclenshawcurtisnodes(::Type{T}, N::Int) where T = chebyshevpoints(T, N, Val(2))\n\n\"\"\"\nCompute weights of the Clenshaw—Curtis quadrature rule with modified Chebyshev moments of the first kind ``\\\\mu``.\n\"\"\"\nclenshawcurtisweights(μ::Vector) = clenshawcurtisweights!(copy(μ))\nclenshawcurtisweights!(μ::Vector) = clenshawcurtisweights!(μ, plan_clenshawcurtis(μ))\nfunction clenshawcurtisweights!(μ::Vector{T}, plan) where T\n    N = length(μ)\n    rmul!(μ, inv(N-one(T)))\n    plan*μ\n    μ[1] *= half(T); μ[N] *= half(T)\n    return μ\nend\n"
  },
  {
    "path": "src/docstrings.jl",
    "content": "\"\"\"\n\tleg2cheb(v::AbstractVector; normleg::Bool=false, normcheb::Bool=false)\n\nConvert the vector of expansions coefficients `v` from a Legendre to a Chebyshev basis.\nThe keyword arguments denote whether the bases are normalized.\n\"\"\"\nleg2cheb\n\n\"\"\"\n\tcheb2leg(v::AbstractVector; normcheb::Bool=false, normleg::Bool=false)\n\nConvert the vector of expansions coefficients `v` from a Chebyshev to a Legendre basis.\nThe keyword arguments denote whether the bases are normalized.\n\"\"\"\ncheb2leg\n\n\"\"\"\n\tultra2ultra(v::AbstractVector, λ, μ; norm1::Bool=false, norm2::Bool=false)\n\nConvert the vector of expansions coefficients `v` from an Ultraspherical basis of\norder `λ` to an Ultraspherical basis of order `μ`.\nThe keyword arguments denote whether the bases are normalized.\n\"\"\"\nultra2ultra\n\n\"\"\"\n\tjac2jac(v::AbstractVector, α, β, γ, δ; norm1::Bool=false, norm2::Bool=false)\n\nConvert the vector of expansions coefficients `v` from a Jacobi basis of\norder `(α,β)` to a Jacobi basis of order `(γ,δ)`.\nThe keyword arguments denote whether the bases are normalized.\n\"\"\"\njac2jac\n\n\"\"\"\n\tlag2lag(v::AbstractVector, α, β; norm1::Bool=false, norm2::Bool=false)\n\nConvert the vector of expansions coefficients `v` from a Laguerre basis of\norder `α` to a La basis of order `β`.\nThe keyword arguments denote whether the bases are normalized.\"\"\"\nlag2lag\n\n\"\"\"\n\tjac2ultra(v::AbstractVector, α, β, λ; normjac::Bool=false, normultra::Bool=false)\n\nConvert the vector of expansions coefficients `v` from a Jacobi basis of\norder `(α,β)` to an Ultraspherical basis of order `λ`.\nThe keyword arguments denote whether the bases are normalized.\"\"\"\njac2ultra\n\n\"\"\"\n\tultra2jac(v::AbstractVector, λ, α, β; normultra::Bool=false, normjac::Bool=false)\n\nConvert the vector of expansions coefficients `v` from an Ultraspherical basis of\norder `λ` to a Jacobi basis of order `(α,β)`.\nThe keyword arguments denote whether the bases are normalized.\n\"\"\"\nultra2jac\n\n\"\"\"\n\tjac2cheb(v::AbstractVector, α, β; normjac::Bool=false, normcheb::Bool=false)\n\nConvert the vector of expansions coefficients `v` from a Jacobi basis of\norder `(α,β)` to a Chebyshev basis.\nThe keyword arguments denote whether the bases are normalized.\n\"\"\"\njac2cheb\n\n\"\"\"\n\tcheb2jac(v::AbstractVector, α, β; normcheb::Bool=false, normjac::Bool=false)\n\nConvert the vector of expansions coefficients `v` from a Chebyshev basis to a\nJacobi basis of order `(α,β)`.\nThe keyword arguments denote whether the bases are normalized.\n\"\"\"\ncheb2jac\n\n\"\"\"\n\tultra2cheb(v::AbstractVector, λ; normultra::Bool=false, normcheb::Bool=false)\n\nConvert the vector of expansions coefficients `v` from an Ultraspherical basis of\norder `λ` to a Chebyshev basis.\nThe keyword arguments denote whether the bases are normalized.\n\"\"\"\nultra2cheb\n\n\"\"\"\n\tcheb2ultra(v::AbstractVector, λ; normcheb::Bool=false, normultra::Bool=false)\n\nConvert the vector of expansions coefficients `v` from a Chebyshev basis\nto an Ultraspherical basis of order `λ`.\nThe keyword arguments denote whether the bases are normalized.\n\"\"\"\ncheb2ultra\n\n\"\"\"\n\tassociatedjac2jac(v::AbstractVector, c::Integer, α, β, γ, δ; norm1::Bool=false, norm2::Bool=false)\n\nConvert the vector of expansions coefficients `v` from an associated Jacobi basis\nof orders `(α,β)` to a Jacobi basis of order `(γ,δ)`.\nThe keyword arguments denote whether the bases are normalized.\n\"\"\"\nassociatedjac2jac\n\n\"\"\"\n\tmodifiedjac2jac(v::AbstractVector{T}, α, β, u::Vector{T}; verbose::Bool=false) where {T}\n\tmodifiedjac2jac(v::AbstractVector{T}, α, β, u::Vector{T}, v::Vector{T}; verbose::Bool=false) where {T}\n\"\"\"\nmodifiedjac2jac\n\n\"\"\"\n\tmodifiedlag2lag(v::AbstractVector{T}, α, u::Vector{T}; verbose::Bool=false)\n\tmodifiedlag2lag(v::AbstractVector{T}, α, u::Vector{T}, v::Vector{T}; verbose::Bool=false) where {T}\n\"\"\"\nmodifiedlag2lag\n\n\"\"\"\n\tmodifiedherm2herm(v::AbstractVector{T}, u::Vector{T}; verbose::Bool=false)\n\tmodifiedherm2herm(v::AbstractVector{T}, u::Vector{T}, v::Vector{T}; verbose::Bool=false) where {T}\n\"\"\"\nmodifiedherm2herm\n"
  },
  {
    "path": "src/elliptic.jl",
    "content": "\"\"\"\n`FastTransforms` submodule for the computation of some elliptic integrals and functions.\n\nComplete elliptic integrals of the first and second kinds:\n```math\nK(k) = \\\\int_0^{\\\\frac{\\\\pi}{2}} \\\\frac{{\\\\rm d}\\\\theta}{\\\\sqrt{1-k^2\\\\sin^2\\\\theta}},\\\\quad{\\\\rm and},\n```\n```math\nE(k) = \\\\int_0^{\\\\frac{\\\\pi}{2}} \\\\sqrt{1-k^2\\\\sin^2\\\\theta} {\\\\rm\\\\,d}\\\\theta.\n```\n\nJacobian elliptic functions:\n```math\nx = \\\\int_0^{\\\\operatorname{sn}(x,k)} \\\\frac{{\\\\rm d}t}{\\\\sqrt{(1-t^2)(1-k^2t^2)}},\n```\n```math\nx = \\\\int_{\\\\operatorname{cn}(x,k)}^1 \\\\frac{{\\\\rm d}t}{\\\\sqrt{(1-t^2)[1-k^2(1-t^2)]}},\n```\n```math\nx = \\\\int_{\\\\operatorname{dn}(x,k)}^1 \\\\frac{{\\\\rm d}t}{\\\\sqrt{(1-t^2)(t^2-1+k^2)}},\n```\nand the remaining nine are defined by:\n```math\n\\\\operatorname{pq}(x,k) = \\\\frac{\\\\operatorname{pr}(x,k)}{\\\\operatorname{qr}(x,k)} = \\\\frac{1}{\\\\operatorname{qp}(x,k)}.\n```\n\"\"\"\nmodule Elliptic\n\nimport FastTransforms: libfasttransforms\n\nexport K, E,\n       sn, cn, dn, ns, nc, nd,\n       sc, cs, sd, ds, cd, dc\n\nfor (fC, elty) in ((:ft_complete_elliptic_integralf, :Float32), (:ft_complete_elliptic_integral, :Float64))\n    @eval begin\n        function K(k::$elty)\n            return ccall(($(string(fC)), libfasttransforms), $elty, (Cint, $elty), '1', k)\n        end\n        function E(k::$elty)\n            return ccall(($(string(fC)), libfasttransforms), $elty, (Cint, $elty), '2', k)\n        end\n    end\nend\n\nconst SN = UInt(1)\nconst CN = UInt(2)\nconst DN = UInt(4)\n\nfor (fC, elty) in ((:ft_jacobian_elliptic_functionsf, :Float32), (:ft_jacobian_elliptic_functions, :Float64))\n    @eval begin\n        function sn(x::$elty, k::$elty)\n            retsn = Ref{$elty}()\n            ccall(($(string(fC)), libfasttransforms), Cvoid, ($elty, $elty, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, UInt), x, k, retsn, C_NULL, C_NULL, SN)\n            retsn[]\n        end\n        function cn(x::$elty, k::$elty)\n            retcn = Ref{$elty}()\n            ccall(($(string(fC)), libfasttransforms), Cvoid, ($elty, $elty, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, UInt), x, k, C_NULL, retcn, C_NULL, CN)\n            retcn[]\n        end\n        function dn(x::$elty, k::$elty)\n            retdn = Ref{$elty}()\n            ccall(($(string(fC)), libfasttransforms), Cvoid, ($elty, $elty, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, UInt), x, k, C_NULL, C_NULL, retdn, DN)\n            retdn[]\n        end\n        function ns(x::$elty, k::$elty)\n            retsn = Ref{$elty}()\n            ccall(($(string(fC)), libfasttransforms), Cvoid, ($elty, $elty, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, UInt), x, k, retsn, C_NULL, C_NULL, SN)\n            inv(retsn[])\n        end\n        function nc(x::$elty, k::$elty)\n            retcn = Ref{$elty}()\n            ccall(($(string(fC)), libfasttransforms), Cvoid, ($elty, $elty, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, UInt), x, k, C_NULL, retcn, C_NULL, CN)\n            inv(retcn[])\n        end\n        function nd(x::$elty, k::$elty)\n            retdn = Ref{$elty}()\n            ccall(($(string(fC)), libfasttransforms), Cvoid, ($elty, $elty, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, UInt), x, k, C_NULL, C_NULL, retdn, DN)\n            inv(retdn[])\n        end\n        function sc(x::$elty, k::$elty)\n            retsn = Ref{$elty}()\n            retcn = Ref{$elty}()\n            ccall(($(string(fC)), libfasttransforms), Cvoid, ($elty, $elty, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, UInt), x, k, retsn, retcn, C_NULL, SN & CN)\n            retsn[]/retcn[]\n        end\n        function cs(x::$elty, k::$elty)\n            retsn = Ref{$elty}()\n            retcn = Ref{$elty}()\n            ccall(($(string(fC)), libfasttransforms), Cvoid, ($elty, $elty, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, UInt), x, k, retsn, retcn, C_NULL, SN & CN)\n            retcn[]/retsn[]\n        end\n        function sd(x::$elty, k::$elty)\n            retsn = Ref{$elty}()\n            retdn = Ref{$elty}()\n            ccall(($(string(fC)), libfasttransforms), Cvoid, ($elty, $elty, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, UInt), x, k, retsn, C_NULL, retdn, SN & DN)\n            retsn[]/retdn[]\n        end\n        function ds(x::$elty, k::$elty)\n            retsn = Ref{$elty}()\n            retdn = Ref{$elty}()\n            ccall(($(string(fC)), libfasttransforms), Cvoid, ($elty, $elty, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, UInt), x, k, retsn, C_NULL, retdn, SN & DN)\n            retdn[]/retsn[]\n        end\n        function cd(x::$elty, k::$elty)\n            retcn = Ref{$elty}()\n            retdn = Ref{$elty}()\n            ccall(($(string(fC)), libfasttransforms), Cvoid, ($elty, $elty, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, UInt), x, k, C_NULL, retcn, retdn, CN & DN)\n            retcn[]/retdn[]\n        end\n        function dc(x::$elty, k::$elty)\n            retcn = Ref{$elty}()\n            retdn = Ref{$elty}()\n            ccall(($(string(fC)), libfasttransforms), Cvoid, ($elty, $elty, Ptr{$elty}, Ptr{$elty}, Ptr{$elty}, UInt), x, k, C_NULL, retcn, retdn, CN & DN)\n            retdn[]/retcn[]\n        end\n    end\nend\n\nend # module\n"
  },
  {
    "path": "src/fejer.jl",
    "content": "plan_fejer1(μ) = FFTW.plan_r2r!(μ, FFTW.REDFT01)\n\n\"\"\"\nCompute nodes of Fejer's first quadrature rule.\n\"\"\"\nfejernodes1(::Type{T}, N::Int) where T = chebyshevpoints(T, N, Val(1))\n\n\"\"\"\nCompute weights of Fejer's first quadrature rule with modified Chebyshev moments of the first kind ``\\\\mu``.\n\"\"\"\nfejerweights1(μ::Vector) = fejerweights1!(copy(μ))\nfejerweights1!(μ::Vector) = fejerweights1!(μ, plan_fejer1(μ))\nfunction fejerweights1!(μ::Vector{T}, plan) where T\n    N = length(μ)\n    rmul!(μ, inv(T(N)))\n    return plan*μ\nend\n\n\nplan_fejer2(μ) = FFTW.plan_r2r!(μ, FFTW.RODFT00)\n\n\"\"\"\nCompute nodes of Fejer's second quadrature rule.\n\"\"\"\nfejernodes2(::Type{T}, N::Int) where T = T[sinpi((N-2k-one(T))/(2N+two(T))) for k=0:N-1]\n\n\"\"\"\nCompute weights of Fejer's second quadrature rule with modified Chebyshev moments of the second kind ``\\\\mu``.\n\"\"\"\nfejerweights2(μ::Vector) = fejerweights2!(copy(μ))\nfejerweights2!(μ::Vector) = fejerweights2!(μ, plan_fejer2(μ))\nfunction fejerweights2!(μ::Vector{T}, plan) where T\n    N = length(μ)\n    Np1 = N+one(T)\n    rmul!(μ, inv(Np1))\n    plan*μ\n    @inbounds for i=1:N μ[i] = sinpi(i/Np1)*μ[i] end\n    return μ\nend\n"
  },
  {
    "path": "src/gaunt.jl",
    "content": "\"\"\"\nCalculates the Gaunt coefficients, defined by:\n\n```math\na(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.\n```\nor defined by:\n\n```math\nP_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)\n```\n\nThis is a Julia implementation of the stable recurrence described in:\n\nY.-l. Xu, Fast evaluation of Gaunt coefficients: recursive approach, *J. Comp. Appl. Math.*, **85**:53–65, 1997.\n\"\"\"\nfunction gaunt(::Type{T},m::Integer,n::Integer,μ::Integer,ν::Integer;normalized::Bool=false) where T\n    if normalized\n        normalizedgaunt(T,m,n,μ,ν)\n    else\n        lmul!(normalization(T,m,n,μ,ν),gaunt(T,m,n,μ,ν;normalized=true))\n    end\nend\n\"\"\"\nCalculates the Gaunt coefficients in 64-bit floating-point arithmetic.\n\"\"\"\ngaunt(m::Integer,n::Integer,μ::Integer,ν::Integer;kwds...) = gaunt(Float64,m,n,μ,ν;kwds...)\n\ngaunt(::Type{T},m::Int32,n::Int32,μ::Int32,ν::Int32;normalized::Bool=false) where T =\n    gaunt(T,Int64(m),Int64(n),Int64(μ),Int64(ν);normalized=normalized)\n\n\nfunction normalization(::Type{T},m::Integer,n::Integer,μ::Integer,ν::Integer) where T\n    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))\nend\n\nnormalization(::Type{Float64},m::Integer,n::Integer,μ::Integer,ν::Integer) = normalization1(Float64,n,ν)*normalization2(Float64,n-m,ν-μ)\n\nfunction normalization1(::Type{Float64},n::Integer,ν::Integer)\n    if n ≥ 8\n        if ν ≥ 8\n            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)\n        else\n            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)\n        end\n    elseif ν ≥ 8\n        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)\n    else\n        return pochhammer(n+1.0,n)*pochhammer(ν+1.0,ν)/pochhammer(n+ν+1.0,n+ν)\n    end\nend\n\nfunction normalization2(::Type{Float64},nm::Integer,νμ::Integer)\n    if nm ≥ 8\n        if νμ ≥ 8\n            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)\n        else\n            return (nm+νμ+1.0)^νμ*exp(-νμ+(nm+0.5)*log1p(νμ/(nm+1)))*stirlingseries(nm+νμ+1.0)/stirlingseries(nm+1.0)/gamma(νμ+1.0)\n        end\n    elseif νμ ≥ 8\n        return (nm+νμ+1.0)^nm*exp(-nm+(νμ+0.5)*log1p(nm/(νμ+1)))*stirlingseries(nm+νμ+1.0)/stirlingseries(νμ+1.0)/gamma(nm+1.0)\n    else\n        return gamma(nm+νμ+1.0)/gamma(nm+1.0)/gamma(νμ+1.0)\n    end\nend\n\nfunction normalizedgaunt(::Type{T},m::Integer,n::Integer,μ::Integer,ν::Integer) where T\n    qmax = min(n,ν,(n+ν-abs(m+μ))÷2)\n    a = Vector{T}(undef, qmax+1)\n    a[1] = one(T)\n    if μ == m && ν == n # zero class (i) of Aₚ\n        if μ == m == 0\n            for q = 1:qmax\n                p = n+ν-2q\n                a[q+1] = α(T,n,ν,p+2)/α(T,n,ν,p+1)*a[q]\n            end\n        else\n            for q = 1:qmax\n                p = n+ν-2q\n                p₁,p₂ = p-m-μ,p+m+μ\n                a[q+1] = (p+1)*(p₂+2)*α(T,n,ν,p+2)/(p+2)/(p₁+1)/α(T,n,ν,p+1)*a[q]\n            end\n        end\n    else\n        qmax > 0 && (a[2] = secondinitialcondition(T,m,n,μ,ν))\n        q = 2\n        if qmax > 1\n            p = n+ν-2q\n            p₁,p₂ = p-m-μ,p+m+μ\n            if A(T,m,n,μ,ν,p+4) != 0\n                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₁)\n            else\n                a[q+1] = thirdinitialcondition(T,m,n,μ,ν)\n            end\n            q+=1\n        end\n        while q ≤ qmax\n            p = n+ν-2q\n            p₁,p₂ = p-m-μ,p+m+μ\n            if A(T,m,n,μ,ν,p+4) != 0\n                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₁)\n            elseif A(T,m,n,μ,ν,p+6) != 0\n                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₁)\n            else\n                a[q+1] = (p+1)*(p₂+2)*α(T,n,ν,p+2)/(p+2)/(p₁+1)/α(T,n,ν,p+1)*a[q]\n            end\n            q+=1\n        end\n    end\n    a\nend\n\nfunction secondinitialcondition(::Type{T},m::Integer,n::Integer,μ::Integer,ν::Integer) where T\n    n₄ = n+ν-m-μ\n    mn = m-n\n    μν = μ-ν\n    temp = 2n+2ν-one(T)\n    return (temp-2)/2*(1-temp/n₄/(n₄-1)*(mn*(mn+one(T))/(2n-1)+μν*(μν+one(T))/(2ν-1)))\nend\n\nfunction thirdinitialcondition(::Type{T},m::Integer,n::Integer,μ::Integer,ν::Integer) where T\n    n₄ = n+ν-m-μ\n    mn = m-n\n    μν = μ-ν\n    temp = 2n+2ν-one(T)\n    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)\n    temp2 = (temp-4)/(2(n₄-2)*(n₄-3))*temp1 - mn*(mn+one(T))/(2n-1)-μν*(μν+one(T))/(2ν-1)\n    return temp*(temp-6)/4*( (temp-2)/n₄/(n₄-1)*temp2 + one(T)/2 )\nend\n\nα(::Type{T},n::Integer,ν::Integer,p::Integer) where T =\n    (p^2-(n+ν+1)^2)*(p^2-(n-ν)^2)/(4p^2-one(T))\nA(::Type{T},m::Integer,n::Integer,μ::Integer,ν::Integer,p::Integer) where T =\n    p*(p-one(T))*(m-μ)-(m+μ)*(n-ν)*(n+ν+one(T))\n\nc₀(::Type{T},m::Integer,n::Integer,μ::Integer,ν::Integer,p::Integer,p₁::Integer)  where T =\n    (p+2)*(p+3)*(p₁+1)*(p₁+2)*A(T,m,n,μ,ν,p+4)*α(T,n,ν,p+1)\nc₁(::Type{T},m::Integer,n::Integer,μ::Integer,ν::Integer,p::Integer,p₁::Integer,p₂::Integer)  where T =\n    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)\nc₂(::Type{T},m::Integer,n::Integer,μ::Integer,ν::Integer,p::Integer,p₂::Integer)  where T =\n    -(p+2)*(p+3)*(p₂+3)*(p₂+4)*A(T,m,n,μ,ν,p+2)*α(T,n,ν,p+4)\n\nd₀(::Type{T},m::Integer,n::Integer,μ::Integer,ν::Integer,p::Integer,p₁::Integer)  where T =\n    (p+2)*(p+3)*(p+5)*(p₁+2)*(p₁+4)*A(T,m,n,μ,ν,p+6)*α(T,n,ν,p+1)\nd₁(::Type{T},m::Integer,n::Integer,μ::Integer,ν::Integer,p::Integer,p₁::Integer,p₂::Integer) where T =\n    (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) )\nd₂(::Type{T},m::Integer,n::Integer,μ::Integer,ν::Integer,p::Integer,p₁::Integer,p₂::Integer) where T =\n    (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) )\nd₃(::Type{T},m::Integer,n::Integer,μ::Integer,ν::Integer,p::Integer,p₂::Integer) where T =\n    -(p+2)*(p+4)*(p+5)*(p₂+3)*(p₂+5)*(p₂+6)*A(T,m,n,μ,ν,p+2)*α(T,n,ν,p+6)\n"
  },
  {
    "path": "src/hermite.jl",
    "content": "# exp(-x^2/2) H_n(x) / sqrt(π*prod(1:n))\n\nstruct ForwardWeightedHermitePlan{T}\n    Vtw::Matrix{T} # vandermonde\nend\n\nstruct BackwardWeightedHermitePlan{T}\n    V::Matrix{T} # vandermonde\nend\n\nfunction _weightedhermite_vandermonde(n)\n    V = Array{Float64}(undef, n, n)\n    x,w = unweightedgausshermite(n)\n    for k=1:n\n        V[k,:] = FastGaussQuadrature.hermpoly_rec(0:n-1, sqrt(2)*x[k])\n    end\n    V,w\nend\n\nfunction ForwardWeightedHermitePlan(n::Integer)\n    V,w = _weightedhermite_vandermonde(n)\n    ForwardWeightedHermitePlan(V' * Diagonal(w / sqrt(π)))\nend\n\nBackwardWeightedHermitePlan(n::Integer) = BackwardWeightedHermitePlan(_weightedhermite_vandermonde(n)[1])\n\n*(P::ForwardWeightedHermitePlan, v::AbstractVector) = P.Vtw*v\n*(P::BackwardWeightedHermitePlan, v::AbstractVector) = P.V*v\n\nweightedhermitetransform(v) = ForwardWeightedHermitePlan(length(v))*v\niweightedhermitetransform(v) = BackwardWeightedHermitePlan(length(v))*v\n"
  },
  {
    "path": "src/inufft.jl",
    "content": "\"\"\"\nPre-computes an inverse nonuniform fast Fourier transform of type `N`.\n\nFor best performance, choose the right number of threads by `FFTW.set_num_threads(4)`, for example.\n\"\"\"\nstruct iNUFFTPlan{N,T,S,PT,TF} <: Plan{T}\n    pt::PT\n    TP::TF\n    r::Vector{T}\n    p::Vector{T}\n    Ap::Vector{T}\n    ϵ::S\nend\n\n\"\"\"\nPre-computes an inverse nonuniform fast Fourier transform of type I.\n\"\"\"\nfunction plan_inufft1(ω::AbstractVector{T}, ϵ::T) where T<:AbstractFloat\n    N = length(ω)\n    p = plan_nufft1(ω, ϵ)\n    pt = plan_nufft2(ω/N, ϵ)\n    c = p*ones(Complex{T}, N)\n    r = conj(c)\n    avg = (r[1]+c[1])/2\n    r[1] = avg\n    c[1] = avg\n    TP = factorize(Toeplitz(c, r))\n    r = zero(c)\n    p = zero(c)\n    Ap = zero(c)\n\n    iNUFFTPlan{1, eltype(TP), typeof(ϵ), typeof(pt), typeof(TP)}(pt, TP, r, p, Ap, ϵ)\nend\n\n\"\"\"\nPre-computes an inverse nonuniform fast Fourier transform of type II.\n\"\"\"\nfunction plan_inufft2(x::AbstractVector{T}, ϵ::T) where T<:AbstractFloat\n    N = length(x)\n    pt = plan_nufft1(N*x, ϵ)\n    r = pt*ones(Complex{T}, N)\n    c = conj(r)\n    avg = (r[1]+c[1])/2\n    r[1] = avg\n    c[1] = avg\n    TP = factorize(Toeplitz(c, r))\n    r = zero(c)\n    p = zero(c)\n    Ap = zero(c)\n\n    iNUFFTPlan{2, eltype(TP), typeof(ϵ), typeof(pt), typeof(TP)}(pt, TP, r, p, Ap, ϵ)\nend\n\n\nfunction (*)(p::iNUFFTPlan{N,T}, x::AbstractVector{V}) where {N,T,V}\n    mul!(zeros(promote_type(T,V), length(x)), p, x)\nend\n\nfunction mul!(c::AbstractVector{T}, P::iNUFFTPlan{1,T}, f::AbstractVector{T}) where T\n    pt, TP, r, p, Ap, ϵ = P.pt, P.TP, P.r, P.p, P.Ap, P.ϵ\n    cg_for_inufft(TP, c, f, r, p, Ap, 50, 100ϵ)\n    conj!(mul!(c, pt, conj!(c)))\nend\n\n\nfunction mul!(c::AbstractVector{T}, P::iNUFFTPlan{2,T}, f::AbstractVector{T}) where T\n    pt, TP, r, p, Ap, ϵ = P.pt, P.TP, P.r, P.p, P.Ap, P.ϵ\n    cg_for_inufft(TP, c, conj!(pt*conj!(f)), r, p, Ap, 50, 100ϵ)\n    conj!(f)\n    c\nend\n\n\"\"\"\nComputes an inverse nonuniform fast Fourier transform of type I.\n\"\"\"\ninufft1(c::AbstractVector, ω::AbstractVector{T}, ϵ::T) where {T<:AbstractFloat} = plan_inufft1(ω, ϵ)*c\n\n\"\"\"\nComputes an inverse nonuniform fast Fourier transform of type II.\n\"\"\"\ninufft2(c::AbstractVector, x::AbstractVector{T}, ϵ::T) where {T<:AbstractFloat} = plan_inufft2(x, ϵ)*c\n\nfunction 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\tn = length(b)\n    nrmb = norm(b)\n    if nrmb == 0 nrmb = one(typeof(nrmb)) end\n\tcopyto!(x, b)\n    fill!(r, zero(T))\n    fill!(p, zero(T))\n    fill!(Ap, zero(T))\n    # r = b - A*x\n    copyto!(r, b)\n    mul!(r, A, x, -one(T), one(T))\n\tcopyto!(p, r)\n\tnrm2 = r⋅r\n    for k = 1:max_it\n        # Ap = A*p\n        mul!(Ap, A, p)\n\t\tα = nrm2/(p⋅Ap)\n        @inbounds @simd for l = 1:n\n            x[l] += α*p[l]\n            r[l] -= α*Ap[l]\n        end\n\t\tnrm2new = r⋅r\n        cst = nrm2new/nrm2\n        @inbounds @simd for l = 1:n\n            p[l] = muladd(cst, p[l], r[l])\n        end\n\t\tnrm2 = nrm2new\n        if sqrt(abs(nrm2)) ≤ tol*nrmb break end\n\tend\n    return x\nend\n"
  },
  {
    "path": "src/libfasttransforms.jl",
    "content": "if get(ENV, \"FT_BUILD_FROM_SOURCE\", \"false\") == \"true\"\n    using Libdl\n    const libfasttransforms = find_library(\"libfasttransforms\", [joinpath(dirname(@__DIR__), \"deps\")])\n    if libfasttransforms ≡ nothing || length(libfasttransforms) == 0\n        error(\"FastTransforms is not properly installed. Please run Pkg.build(\\\"FastTransforms\\\") \",\n              \"and restart Julia.\")\n    end\nelse\n    using FastTransforms_jll\nend\n\nft_set_num_threads(n::Integer) = ccall((:ft_set_num_threads, libfasttransforms), Cvoid, (Cint, ), n)\nft_fftw_plan_with_nthreads(n::Integer) = ccall((:ft_fftw_plan_with_nthreads, libfasttransforms), Cvoid, (Cint, ), n)\n\nfunction __init__()\n    n = ceil(Int, Sys.CPU_THREADS/2)\n    ft_set_num_threads(n)\n    ccall((:ft_fftw_init_threads, libfasttransforms), Cint, ())\n    ft_fftw_plan_with_nthreads(n)\nend\n\n\n\"\"\"\n    mpfr_t <: AbstractFloat\n\nA Julia struct that exactly matches `mpfr_t`.\n\"\"\"\nstruct mpfr_t <: AbstractFloat\n    prec::Clong\n    sign::Cint\n    exp::Clong\n    d::Ptr{Limb}\nend\n\n\"\"\"\n`BigFloat` is a mutable struct and there is no guarantee that each entry in an\n`AbstractArray{BigFloat}` is unique. For example, looking at the `Limb`s,\n\n    Id = Matrix{BigFloat}(I, 3, 3)\n    map(x->x.d, Id)\n\nshows that the ones and the zeros all share the same pointers. If a C function\nassumes unicity of each datum, then the array must be renewed with a `deepcopy`.\n\"\"\"\nfunction renew!(x::AbstractArray{BigFloat})\n    for i in eachindex(x)\n        @inbounds x[i] = deepcopy(x[i])\n    end\n    return x\nend\n\nfunction horner!(f::Vector{Float64}, c::StridedVector{Float64}, x::Vector{Float64})\n    @assert length(x) == length(f)\n    ccall((:ft_horner, libfasttransforms), Cvoid, (Cint, Ptr{Float64}, Cint, Cint, Ptr{Float64}, Ptr{Float64}), length(c), c, stride(c, 1), length(x), x, f)\n    f\nend\n\nfunction horner!(f::Vector{Float32}, c::StridedVector{Float32}, x::Vector{Float32})\n    @assert length(x) == length(f)\n    ccall((:ft_hornerf, libfasttransforms), Cvoid, (Cint, Ptr{Float32}, Cint, Cint, Ptr{Float32}, Ptr{Float32}), length(c), c, stride(c, 1), length(x), x, f)\n    f\nend\n\nfunction check_clenshaw_points(x, ϕ₀, f)\n    length(x) == length(ϕ₀) == length(f) || throw(ArgumentError(\"Dimensions must match\"))\nend\n\nfunction check_clenshaw_points(x, f)\n    length(x) == length(f) || throw(ArgumentError(\"Dimensions must match\"))\nend\n\nfunction clenshaw!(f::Vector{Float64}, c::StridedVector{Float64}, x::Vector{Float64})\n    @boundscheck check_clenshaw_points(x, f)\n    ccall((:ft_clenshaw, libfasttransforms), Cvoid, (Cint, Ptr{Float64}, Cint, Cint, Ptr{Float64}, Ptr{Float64}), length(c), c, stride(c, 1), length(x), x, f)\n    f\nend\n\nfunction clenshaw!(f::Vector{Float32}, c::StridedVector{Float32}, x::Vector{Float32})\n    @boundscheck check_clenshaw_points(x, f)\n    ccall((:ft_clenshawf, libfasttransforms), Cvoid, (Cint, Ptr{Float32}, Cint, Cint, Ptr{Float32}, Ptr{Float32}), length(c), c, stride(c, 1), length(x), x, f)\n    f\nend\n\nfunction clenshaw!(f::Vector{Float64}, c::StridedVector{Float64}, A::Vector{Float64}, B::Vector{Float64}, C::Vector{Float64}, x::Vector{Float64}, ϕ₀::Vector{Float64})\n    N = length(c)\n    @boundscheck check_clenshaw_recurrences(N, A, B, C)\n    @boundscheck check_clenshaw_points(x, ϕ₀, f)\n    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)\n    f\nend\n\nfunction clenshaw!(f::Vector{Float32}, c::StridedVector{Float32}, A::Vector{Float32}, B::Vector{Float32}, C::Vector{Float32}, x::Vector{Float32}, ϕ₀::Vector{Float32})\n    N = length(c)\n    @boundscheck check_clenshaw_recurrences(N, A, B, C)\n    @boundscheck check_clenshaw_points(x, ϕ₀, f)\n    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)\n    f\nend\n\n@enum Transforms::Cint begin\n    LEG2CHEB=0\n    CHEB2LEG\n    ULTRA2ULTRA\n    JAC2JAC\n    LAG2LAG\n    JAC2ULTRA\n    ULTRA2JAC\n    JAC2CHEB\n    CHEB2JAC\n    ULTRA2CHEB\n    CHEB2ULTRA\n    ASSOCIATEDJAC2JAC\n    MODIFIEDJAC2JAC\n    MODIFIEDLAG2LAG\n    MODIFIEDHERM2HERM\n    SPHERE\n    SPHEREV\n    DISK\n    ANNULUS\n    RECTDISK\n    TRIANGLE\n    TETRAHEDRON\n    SPINSPHERE\n    SPHERESYNTHESIS\n    SPHEREANALYSIS\n    SPHEREVSYNTHESIS\n    SPHEREVANALYSIS\n    DISKSYNTHESIS\n    DISKANALYSIS\n    ANNULUSSYNTHESIS\n    ANNULUSANALYSIS\n    RECTDISKSYNTHESIS\n    RECTDISKANALYSIS\n    TRIANGLESYNTHESIS\n    TRIANGLEANALYSIS\n    TETRAHEDRONSYNTHESIS\n    TETRAHEDRONANALYSIS\n    SPINSPHERESYNTHESIS\n    SPINSPHEREANALYSIS\n    SPHERICALISOMETRY\nend\n\nTransforms(t::Transforms) = t\n\nlet k2s = Dict(LEG2CHEB             => \"Legendre--Chebyshev\",\n               CHEB2LEG             => \"Chebyshev--Legendre\",\n               ULTRA2ULTRA          => \"ultraspherical--ultraspherical\",\n               JAC2JAC              => \"Jacobi--Jacobi\",\n               LAG2LAG              => \"Laguerre--Laguerre\",\n               JAC2ULTRA            => \"Jacobi--ultraspherical\",\n               ULTRA2JAC            => \"ultraspherical--Jacobi\",\n               JAC2CHEB             => \"Jacobi--Chebyshev\",\n               CHEB2JAC             => \"Chebyshev--Jacobi\",\n               ULTRA2CHEB           => \"ultraspherical--Chebyshev\",\n               CHEB2ULTRA           => \"Chebyshev--ultraspherical\",\n               ASSOCIATEDJAC2JAC    => \"Associated Jacobi--Jacobi\",\n               MODIFIEDJAC2JAC      => \"Modified Jacobi--Jacobi\",\n               MODIFIEDLAG2LAG      => \"Modified Laguerre--Laguerre\",\n               MODIFIEDHERM2HERM    => \"Modified Hermite--Hermite\",\n               SPHERE               => \"Spherical harmonic--Fourier\",\n               SPHEREV              => \"Spherical vector field--Fourier\",\n               DISK                 => \"Zernike--Chebyshev×Fourier\",\n               ANNULUS              => \"Annulus--Chebyshev×Fourier\",\n               RECTDISK             => \"Dunkl-Xu--Chebyshev²\",\n               TRIANGLE             => \"Proriol--Chebyshev²\",\n               TETRAHEDRON          => \"Proriol--Chebyshev³\",\n               SPINSPHERE           => \"Spin-weighted spherical harmonic--Fourier\",\n               SPHERESYNTHESIS      => \"FFTW Fourier synthesis on the sphere\",\n               SPHEREANALYSIS       => \"FFTW Fourier analysis on the sphere\",\n               SPHEREVSYNTHESIS     => \"FFTW Fourier synthesis on the sphere (vector field)\",\n               SPHEREVANALYSIS      => \"FFTW Fourier analysis on the sphere (vector field)\",\n               DISKSYNTHESIS        => \"FFTW Chebyshev×Fourier synthesis on the disk\",\n               DISKANALYSIS         => \"FFTW Chebyshev×Fourier analysis on the disk\",\n               ANNULUSSYNTHESIS     => \"FFTW Chebyshev×Fourier synthesis on the annulus\",\n               ANNULUSANALYSIS      => \"FFTW Chebyshev×Fourier analysis on the annulus\",\n               RECTDISKSYNTHESIS    => \"FFTW Chebyshev synthesis on the rectangularized disk\",\n               RECTDISKANALYSIS     => \"FFTW Chebyshev analysis on the rectangularized disk\",\n               TRIANGLESYNTHESIS    => \"FFTW Chebyshev synthesis on the triangle\",\n               TRIANGLEANALYSIS     => \"FFTW Chebyshev analysis on the triangle\",\n               TETRAHEDRONSYNTHESIS => \"FFTW Chebyshev synthesis on the tetrahedron\",\n               TETRAHEDRONANALYSIS  => \"FFTW Chebyshev analysis on the tetrahedron\",\n               SPINSPHERESYNTHESIS  => \"FFTW Fourier synthesis on the sphere (spin-weighted)\",\n               SPINSPHEREANALYSIS   => \"FFTW Fourier analysis on the sphere (spin-weighted)\",\n               SPHERICALISOMETRY    => \"Spherical isometry\")\n    global kind2string\n    kind2string(k::Union{Integer, Transforms}) = k2s[Transforms(k)]\nend\n\nstruct ft_plan_struct end\n\nmutable struct FTPlan{T, N, K}\n    plan::Ptr{ft_plan_struct}\n    n::Int\n    l::Int\n    m::Int\n    function FTPlan{T, N, K}(plan::Ptr{ft_plan_struct}, n::Int) where {T, N, K}\n        p = new(plan, n)\n        finalizer(destroy_plan, p)\n        p\n    end\n    function FTPlan{T, N, K}(plan::Ptr{ft_plan_struct}, n::Int, m::Int) where {T, N, K}\n        p = new(plan, n, -1, m)\n        finalizer(destroy_plan, p)\n        p\n    end\n    function FTPlan{T, N, K}(plan::Ptr{ft_plan_struct}, n::Int, l::Int, m::Int) where {T, N, K}\n        p = new(plan, n, l, m)\n        finalizer(destroy_plan, p)\n        p\n    end\nend\n\neltype(p::FTPlan{T}) where {T} = T\nndims(p::FTPlan{T, N}) where {T, N} = N\nshow(io::IO, p::FTPlan{T, 1, K}) where {T, K} = print(io, \"FastTransforms \", kind2string(K), \" plan for $(p.n)-element array of \", T)\nshow(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)\nshow(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)\nshow(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)\nshow(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)\nshow(io::IO, p::FTPlan{T, 2, RECTDISK}) where T = print(io, \"FastTransforms \", kind2string(RECTDISK), \" plan for $(p.n)×$(p.n)-element array of \", T)\nshow(io::IO, p::FTPlan{T, 2, TRIANGLE}) where T = print(io, \"FastTransforms \", kind2string(TRIANGLE), \" plan for $(p.n)×$(p.n)-element array of \", T)\nshow(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)\nshow(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)\nshow(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)\nshow(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)\nshow(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)\n\nfunction checksize(p::FTPlan{T, 1}, x::StridedArray{T}) where T\n    if p.n != size(x, 1)\n        throw(DimensionMismatch(\"FTPlan has dimensions $(p.n) × $(p.n), x has leading dimension $(size(x, 1))\"))\n    end\nend\n\nfunction checkstride(p::FTPlan{T, 1}, x::StridedArray{T}) where T\n    if stride(x, 1) != 1\n        error(\"FTPlan requires unit stride in the leading dimension, x has stride $(stride(x, 1)) in the leading dimension.\")\n    end\nend\n\nfor (N, K) in ((2, RECTDISK), (2, TRIANGLE), (3, TETRAHEDRON))\n    @eval function checksize(p::FTPlan{T, $N, $K}, x::Array{T, $N}) where T\n        if p.n != size(x, 1)\n            throw(DimensionMismatch(\"FTPlan has dimensions $(p.n) × $(p.n), x has leading dimension $(size(x, 1))\"))\n        end\n    end\nend\n\nfor K in (SPHERE, SPHEREV, DISK, ANNULUS, SPINSPHERE)\n    @eval function checksize(p::FTPlan{T, 2, $K}, x::Matrix{T}) where T\n        if p.n != size(x, 1)\n            throw(DimensionMismatch(\"FTPlan has dimensions $(p.n) × $(p.n), x has leading dimension $(size(x, 1))\"))\n        end\n        if iseven(size(x, 2))\n            throw(DimensionMismatch(\"This FTPlan only operates on arrays with an odd number of columns.\"))\n        end\n    end\nend\n\nfunction checksize(p::FTPlan{T, 2}, x::Array{T, 2}) where T\n    if p.n != size(x, 1) || p.m != size(x, 2)\n        throw(DimensionMismatch(\"FTPlan has dimensions $(p.n) × $(p.m), x has dimensions $(size(x, 1)) × $(size(x, 2))\"))\n    end\nend\n\nfunction checksize(p::FTPlan{T, 3}, x::Array{T, 3}) where T\n    if p.n != size(x, 1) || p.l != size(x, 2) || p.m != size(x, 3)\n        throw(DimensionMismatch(\"FTPlan has dimensions $(p.n) × $(p.l) × $(p.m), x has dimensions $(size(x, 1)) × $(size(x, 2)) × $(size(x, 3))\"))\n    end\nend\n\nfunction checksize(p::FTPlan{T, 2, SPHERICALISOMETRY}, x::Matrix{T}) where T\n    if p.n != size(x, 1) || 2p.n-1 != size(x, 2)\n        throw(DimensionMismatch(\"This FTPlan must operate on arrays of size $(p.n) × $(2p.n-1).\"))\n    end\nend\n\nunsafe_convert(::Type{Ptr{ft_plan_struct}}, p::FTPlan) = p.plan\nunsafe_convert(::Type{Ptr{mpfr_t}}, p::FTPlan) = unsafe_convert(Ptr{mpfr_t}, p.plan)\n\ndestroy_plan(p::FTPlan{Float32, 1}) = ccall((:ft_destroy_tb_eigen_FMMf, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Float64, 1}) = ccall((:ft_destroy_tb_eigen_FMM, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{BigFloat, 1}) = ccall((:ft_mpfr_destroy_plan, libfasttransforms), Cvoid, (Ptr{mpfr_t}, Cint), p, p.n)\ndestroy_plan(p::FTPlan{Float32, 1, ASSOCIATEDJAC2JAC}) = ccall((:ft_destroy_btb_eigen_FMMf, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Float64, 1, ASSOCIATEDJAC2JAC}) = ccall((:ft_destroy_btb_eigen_FMM, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Float32, 1, MODIFIEDJAC2JAC}) = ccall((:ft_destroy_modified_planf, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Float64, 1, MODIFIEDJAC2JAC}) = ccall((:ft_destroy_modified_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Float32, 1, MODIFIEDLAG2LAG}) = ccall((:ft_destroy_modified_planf, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Float64, 1, MODIFIEDLAG2LAG}) = ccall((:ft_destroy_modified_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Float32, 1, MODIFIEDHERM2HERM}) = ccall((:ft_destroy_modified_planf, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Float64, 1, MODIFIEDHERM2HERM}) = ccall((:ft_destroy_modified_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Float64}) = ccall((:ft_destroy_harmonic_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Complex{Float64}, 2, SPINSPHERE}) = ccall((:ft_destroy_spin_harmonic_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Float64, 2, SPHERESYNTHESIS}) = ccall((:ft_destroy_sphere_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Float64, 2, SPHEREANALYSIS}) = ccall((:ft_destroy_sphere_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Float64, 2, SPHEREVSYNTHESIS}) = ccall((:ft_destroy_sphere_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Float64, 2, SPHEREVANALYSIS}) = ccall((:ft_destroy_sphere_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Float64, 2, DISKSYNTHESIS}) = ccall((:ft_destroy_disk_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Float64, 2, DISKANALYSIS}) = ccall((:ft_destroy_disk_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Float64, 2, ANNULUSSYNTHESIS}) = ccall((:ft_destroy_annulus_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Float64, 2, ANNULUSANALYSIS}) = ccall((:ft_destroy_annulus_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Float64, 2, RECTDISKSYNTHESIS}) = ccall((:ft_destroy_rectdisk_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Float64, 2, RECTDISKANALYSIS}) = ccall((:ft_destroy_rectdisk_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Float64, 2, TRIANGLESYNTHESIS}) = ccall((:ft_destroy_triangle_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Float64, 2, TRIANGLEANALYSIS}) = ccall((:ft_destroy_triangle_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Float64, 3, TETRAHEDRONSYNTHESIS}) = ccall((:ft_destroy_tetrahedron_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Float64, 3, TETRAHEDRONANALYSIS}) = ccall((:ft_destroy_tetrahedron_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Complex{Float64}, 2, SPINSPHERESYNTHESIS}) = ccall((:ft_destroy_spinsphere_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Complex{Float64}, 2, SPINSPHEREANALYSIS}) = ccall((:ft_destroy_spinsphere_fftw_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\ndestroy_plan(p::FTPlan{Float64, 2, SPHERICALISOMETRY}) = ccall((:ft_destroy_sph_isometry_plan, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ), p)\n\nstruct AdjointFTPlan{T, S, R}\n    parent::S\n    adjoint::R\n    function AdjointFTPlan{T, S, R}(parent::S) where {T, S, R}\n        new(parent)\n    end\n    function AdjointFTPlan{T, S, R}(parent::S, adjoint::R) where {T, S, R}\n        new(parent, adjoint)\n    end\nend\n\nAdjointFTPlan(p::FTPlan) = AdjointFTPlan{eltype(p), typeof(p), typeof(p)}(p)\nAdjointFTPlan(p::FTPlan, q::FTPlan) = AdjointFTPlan{eltype(q), typeof(p), typeof(q)}(p, q)\n\nadjoint(p::FTPlan) = AdjointFTPlan(p)\nadjoint(p::AdjointFTPlan) = p.parent\n\neltype(p::AdjointFTPlan{T}) where T = T\nndims(p::AdjointFTPlan) = ndims(p.parent)\nfunction show(io::IO, p::AdjointFTPlan)\n    print(io, \"Adjoint \")\n    show(io, p.parent)\nend\n\nfunction checksize(p::AdjointFTPlan, x)\n    try\n        checksize(p.adjoint, x)\n    catch\n        checksize(p.parent, x)\n    end\nend\n\nfunction checkstride(p::AdjointFTPlan, x)\n    try\n        checkstride(p.adjoint, x)\n    catch\n        checkstride(p.parent, x)\n    end\nend\n\nfunction unsafe_convert(::Type{Ptr{ft_plan_struct}}, p::AdjointFTPlan)\n    try\n        unsafe_convert(Ptr{ft_plan_struct}, p.adjoint)\n    catch\n        unsafe_convert(Ptr{ft_plan_struct}, p.parent)\n    end\nend\n\nfunction unsafe_convert(::Type{Ptr{mpfr_t}}, p::AdjointFTPlan)\n    try\n        unsafe_convert(Ptr{mpfr_t}, p.adjoint)\n    catch\n        unsafe_convert(Ptr{mpfr_t}, p.parent)\n    end\nend\n\nstruct TransposeFTPlan{T, S, R}\n    parent::S\n    transpose::R\n    function TransposeFTPlan{T, S, R}(parent::S) where {T, S, R}\n        new(parent)\n    end\n    function TransposeFTPlan{T, S, R}(parent::S, transpose::R) where {T, S, R}\n        new(parent, transpose)\n    end\nend\n\nTransposeFTPlan(p::FTPlan) = TransposeFTPlan{eltype(p), typeof(p), typeof(p)}(p)\nTransposeFTPlan(p::FTPlan, q::FTPlan) = TransposeFTPlan{eltype(q), typeof(p), typeof(q)}(p, q)\n\ntranspose(p::FTPlan) = TransposeFTPlan(p)\ntranspose(p::TransposeFTPlan) = p.parent\n\neltype(p::TransposeFTPlan{T}) where T = T\nndims(p::TransposeFTPlan) = ndims(p.parent)\nfunction show(io::IO, p::TransposeFTPlan)\n    print(io, \"Transpose \")\n    show(io, p.parent)\nend\n\nfunction checksize(p::TransposeFTPlan, x)\n    try\n        checksize(p.transpose, x)\n    catch\n        checksize(p.parent, x)\n    end\nend\n\nfunction checkstride(p::TransposeFTPlan, x)\n    try\n        checkstride(p.transpose, x)\n    catch\n        checkstride(p.parent, x)\n    end\nend\n\nfunction unsafe_convert(::Type{Ptr{ft_plan_struct}}, p::TransposeFTPlan)\n    try\n        unsafe_convert(Ptr{ft_plan_struct}, p.transpose)\n    catch\n        unsafe_convert(Ptr{ft_plan_struct}, p.parent)\n    end\nend\n\nfunction unsafe_convert(::Type{Ptr{mpfr_t}}, p::TransposeFTPlan)\n    try\n        unsafe_convert(Ptr{mpfr_t}, p.transpose)\n    catch\n        unsafe_convert(Ptr{mpfr_t}, p.parent)\n    end\nend\n\nconst ModifiedFTPlan{T} = Union{FTPlan{T, 1, MODIFIEDJAC2JAC}, FTPlan{T, 1, MODIFIEDLAG2LAG}, FTPlan{T, 1, MODIFIEDHERM2HERM}}\n\nfor f in (:leg2cheb, :cheb2leg, :ultra2ultra, :jac2jac,\n          :lag2lag, :jac2ultra, :ultra2jac, :jac2cheb,\n          :cheb2jac, :ultra2cheb, :cheb2ultra, :associatedjac2jac,\n          :modifiedjac2jac, :modifiedlag2lag, :modifiedherm2herm,\n          :sph2fourier, :sphv2fourier, :disk2cxf, :ann2cxf,\n          :rectdisk2cheb, :tri2cheb, :tet2cheb)\n    plan_f = Symbol(\"plan_\", f)\n    lib_f = Symbol(\"lib_\", f)\n    @eval begin\n        $plan_f(x::AbstractArray{T}, y...; z...) where T = $plan_f(T, size(x, 1), y...; z...)\n        $plan_f(::Type{Complex{T}}, y...; z...) where T <: Real = $plan_f(T, y...; z...)\n        $lib_f(x::AbstractArray, y...; z...) = $plan_f(x, y...; z...)*x\n    end\nend\n\nfor (f, plan_f) in ((:fourier2sph, :plan_sph2fourier), (:fourier2sphv, :plan_sphv2fourier),\n                    (:cxf2disk, :plan_disk2cxf), (:cxf2ann, :plan_ann2cxf),\n                    (:cheb2rectdisk, :plan_rectdisk2cheb), (:cheb2tri, :plan_tri2cheb),\n                    (:cheb2tet, :plan_tet2cheb))\n    @eval begin\n        $f(x::AbstractArray, y...; z...) = $plan_f(x, y...; z...)\\x\n    end\nend\n\nplan_spinsph2fourier(x::AbstractArray{T}, y...; z...) where T = plan_spinsph2fourier(T, size(x, 1), y...; z...)\nspinsph2fourier(x::AbstractArray, y...; z...) = plan_spinsph2fourier(x, y...; z...)*x\nfourier2spinsph(x::AbstractArray, y...; z...) = plan_spinsph2fourier(x, y...; z...)\\x\n\nfunction plan_leg2cheb(::Type{Float32}, n::Integer; normleg::Bool=false, normcheb::Bool=false)\n    plan = ccall((:ft_plan_legendre_to_chebyshevf, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint), normleg, normcheb, n)\n    return FTPlan{Float32, 1, LEG2CHEB}(plan, n)\nend\n\nfunction plan_cheb2leg(::Type{Float32}, n::Integer; normcheb::Bool=false, normleg::Bool=false)\n    plan = ccall((:ft_plan_chebyshev_to_legendref, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint), normcheb, normleg, n)\n    return FTPlan{Float32, 1, CHEB2LEG}(plan, n)\nend\n\nfunction plan_ultra2ultra(::Type{Float32}, n::Integer, λ, μ; norm1::Bool=false, norm2::Bool=false)\n    plan = ccall((:ft_plan_ultraspherical_to_ultrasphericalf, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float32, Float32), norm1, norm2, n, λ, μ)\n    return FTPlan{Float32, 1, ULTRA2ULTRA}(plan, n)\nend\n\nfunction plan_jac2jac(::Type{Float32}, n::Integer, α, β, γ, δ; norm1::Bool=false, norm2::Bool=false)\n    plan = ccall((:ft_plan_jacobi_to_jacobif, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float32, Float32, Float32, Float32), norm1, norm2, n, α, β, γ, δ)\n    return FTPlan{Float32, 1, JAC2JAC}(plan, n)\nend\n\nfunction plan_lag2lag(::Type{Float32}, n::Integer, α, β; norm1::Bool=false, norm2::Bool=false)\n    plan = ccall((:ft_plan_laguerre_to_laguerref, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float32, Float32), norm1, norm2, n, α, β)\n    return FTPlan{Float32, 1, LAG2LAG}(plan, n)\nend\n\nfunction plan_jac2ultra(::Type{Float32}, n::Integer, α, β, λ; normjac::Bool=false, normultra::Bool=false)\n    plan = ccall((:ft_plan_jacobi_to_ultrasphericalf, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float32, Float32, Float32), normjac, normultra, n, α, β, λ)\n    return FTPlan{Float32, 1, JAC2ULTRA}(plan, n)\nend\n\nfunction plan_ultra2jac(::Type{Float32}, n::Integer, λ, α, β; normultra::Bool=false, normjac::Bool=false)\n    plan = ccall((:ft_plan_ultraspherical_to_jacobif, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float32, Float32, Float32), normultra, normjac, n, λ, α, β)\n    return FTPlan{Float32, 1, ULTRA2JAC}(plan, n)\nend\n\nfunction plan_jac2cheb(::Type{Float32}, n::Integer, α, β; normjac::Bool=false, normcheb::Bool=false)\n    plan = ccall((:ft_plan_jacobi_to_chebyshevf, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float32, Float32), normjac, normcheb, n, α, β)\n    return FTPlan{Float32, 1, JAC2CHEB}(plan, n)\nend\n\nfunction plan_cheb2jac(::Type{Float32}, n::Integer, α, β; normcheb::Bool=false, normjac::Bool=false)\n    plan = ccall((:ft_plan_chebyshev_to_jacobif, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float32, Float32), normcheb, normjac, n, α, β)\n    return FTPlan{Float32, 1, CHEB2JAC}(plan, n)\nend\n\nfunction plan_ultra2cheb(::Type{Float32}, n::Integer, λ; normultra::Bool=false, normcheb::Bool=false)\n    plan = ccall((:ft_plan_ultraspherical_to_chebyshevf, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float32), normultra, normcheb, n, λ)\n    return FTPlan{Float32, 1, ULTRA2CHEB}(plan, n)\nend\n\nfunction plan_cheb2ultra(::Type{Float32}, n::Integer, λ; normcheb::Bool=false, normultra::Bool=false)\n    plan = ccall((:ft_plan_chebyshev_to_ultrasphericalf, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float32), normcheb, normultra, n, λ)\n    return FTPlan{Float32, 1, CHEB2ULTRA}(plan, n)\nend\n\nfunction plan_associatedjac2jac(::Type{Float32}, n::Integer, c::Integer, α, β, γ, δ; norm1::Bool=false, norm2::Bool=false)\n    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, α, β, γ, δ)\n    return FTPlan{Float32, 1, ASSOCIATEDJAC2JAC}(plan, n)\nend\n\nfunction plan_modifiedjac2jac(::Type{Float32}, n::Integer, α, β, u::Vector{Float32}; verbose::Bool=false)\n    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)\n    return FTPlan{Float32, 1, MODIFIEDJAC2JAC}(plan, n)\nend\n\nfunction plan_modifiedjac2jac(::Type{Float32}, n::Integer, α, β, u::Vector{Float32}, v::Vector{Float32}; verbose::Bool=false)\n    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)\n    return FTPlan{Float32, 1, MODIFIEDJAC2JAC}(plan, n)\nend\n\nfunction plan_modifiedlag2lag(::Type{Float32}, n::Integer, α, u::Vector{Float32}; verbose::Bool=false)\n    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)\n    return FTPlan{Float32, 1, MODIFIEDLAG2LAG}(plan, n)\nend\n\nfunction plan_modifiedlag2lag(::Type{Float32}, n::Integer, α, u::Vector{Float32}, v::Vector{Float32}; verbose::Bool=false)\n    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)\n    return FTPlan{Float32, 1, MODIFIEDLAG2LAG}(plan, n)\nend\n\nfunction plan_modifiedherm2herm(::Type{Float32}, n::Integer, u::Vector{Float32}; verbose::Bool=false)\n    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)\n    return FTPlan{Float32, 1, MODIFIEDHERM2HERM}(plan, n)\nend\n\nfunction plan_modifiedherm2herm(::Type{Float32}, n::Integer, u::Vector{Float32}, v::Vector{Float32}; verbose::Bool=false)\n    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)\n    return FTPlan{Float32, 1, MODIFIEDHERM2HERM}(plan, n)\nend\n\n\nfunction plan_leg2cheb(::Type{Float64}, n::Integer; normleg::Bool=false, normcheb::Bool=false)\n    plan = ccall((:ft_plan_legendre_to_chebyshev, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint), normleg, normcheb, n)\n    return FTPlan{Float64, 1, LEG2CHEB}(plan, n)\nend\n\nfunction plan_cheb2leg(::Type{Float64}, n::Integer; normcheb::Bool=false, normleg::Bool=false)\n    plan = ccall((:ft_plan_chebyshev_to_legendre, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint), normcheb, normleg, n)\n    return FTPlan{Float64, 1, CHEB2LEG}(plan, n)\nend\n\nfunction plan_ultra2ultra(::Type{Float64}, n::Integer, λ, μ; norm1::Bool=false, norm2::Bool=false)\n    plan = ccall((:ft_plan_ultraspherical_to_ultraspherical, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float64, Float64), norm1, norm2, n, λ, μ)\n    return FTPlan{Float64, 1, ULTRA2ULTRA}(plan, n)\nend\n\nfunction plan_jac2jac(::Type{Float64}, n::Integer, α, β, γ, δ; norm1::Bool=false, norm2::Bool=false)\n    plan = ccall((:ft_plan_jacobi_to_jacobi, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float64, Float64, Float64, Float64), norm1, norm2, n, α, β, γ, δ)\n    return FTPlan{Float64, 1, JAC2JAC}(plan, n)\nend\n\nfunction plan_lag2lag(::Type{Float64}, n::Integer, α, β; norm1::Bool=false, norm2::Bool=false)\n    plan = ccall((:ft_plan_laguerre_to_laguerre, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float64, Float64), norm1, norm2, n, α, β)\n    return FTPlan{Float64, 1, LAG2LAG}(plan, n)\nend\n\nfunction plan_jac2ultra(::Type{Float64}, n::Integer, α, β, λ; normjac::Bool=false, normultra::Bool=false)\n    plan = ccall((:ft_plan_jacobi_to_ultraspherical, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float64, Float64, Float64), normjac, normultra, n, α, β, λ)\n    return FTPlan{Float64, 1, JAC2ULTRA}(plan, n)\nend\n\nfunction plan_ultra2jac(::Type{Float64}, n::Integer, λ, α, β; normultra::Bool=false, normjac::Bool=false)\n    plan = ccall((:ft_plan_ultraspherical_to_jacobi, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float64, Float64, Float64), normultra, normjac, n, λ, α, β)\n    return FTPlan{Float64, 1, ULTRA2JAC}(plan, n)\nend\n\nfunction plan_jac2cheb(::Type{Float64}, n::Integer, α, β; normjac::Bool=false, normcheb::Bool=false)\n    plan = ccall((:ft_plan_jacobi_to_chebyshev, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float64, Float64), normjac, normcheb, n, α, β)\n    return FTPlan{Float64, 1, JAC2CHEB}(plan, n)\nend\n\nfunction plan_cheb2jac(::Type{Float64}, n::Integer, α, β; normcheb::Bool=false, normjac::Bool=false)\n    plan = ccall((:ft_plan_chebyshev_to_jacobi, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float64, Float64), normcheb, normjac, n, α, β)\n    return FTPlan{Float64, 1, CHEB2JAC}(plan, n)\nend\n\nfunction plan_ultra2cheb(::Type{Float64}, n::Integer, λ; normultra::Bool=false, normcheb::Bool=false)\n    plan = ccall((:ft_plan_ultraspherical_to_chebyshev, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float64), normultra, normcheb, n, λ)\n    return FTPlan{Float64, 1, ULTRA2CHEB}(plan, n)\nend\n\nfunction plan_cheb2ultra(::Type{Float64}, n::Integer, λ; normcheb::Bool=false, normultra::Bool=false)\n    plan = ccall((:ft_plan_chebyshev_to_ultraspherical, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Float64), normcheb, normultra, n, λ)\n    return FTPlan{Float64, 1, CHEB2ULTRA}(plan, n)\nend\n\nfunction plan_associatedjac2jac(::Type{Float64}, n::Integer, c::Integer, α, β, γ, δ; norm1::Bool=false, norm2::Bool=false)\n    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, α, β, γ, δ)\n    return FTPlan{Float64, 1, ASSOCIATEDJAC2JAC}(plan, n)\nend\n\nfunction plan_modifiedjac2jac(::Type{Float64}, n::Integer, α, β, u::Vector{Float64}; verbose::Bool=false)\n    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)\n    return FTPlan{Float64, 1, MODIFIEDJAC2JAC}(plan, n)\nend\n\nfunction plan_modifiedjac2jac(::Type{Float64}, n::Integer, α, β, u::Vector{Float64}, v::Vector{Float64}; verbose::Bool=false)\n    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)\n    return FTPlan{Float64, 1, MODIFIEDJAC2JAC}(plan, n)\nend\n\nfunction plan_modifiedlag2lag(::Type{Float64}, n::Integer, α, u::Vector{Float64}; verbose::Bool=false)\n    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)\n    return FTPlan{Float64, 1, MODIFIEDLAG2LAG}(plan, n)\nend\n\nfunction plan_modifiedlag2lag(::Type{Float64}, n::Integer, α, u::Vector{Float64}, v::Vector{Float64}; verbose::Bool=false)\n    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)\n    return FTPlan{Float64, 1, MODIFIEDLAG2LAG}(plan, n)\nend\n\nfunction plan_modifiedherm2herm(::Type{Float64}, n::Integer, u::Vector{Float64}; verbose::Bool=false)\n    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)\n    return FTPlan{Float64, 1, MODIFIEDHERM2HERM}(plan, n)\nend\n\nfunction plan_modifiedherm2herm(::Type{Float64}, n::Integer, u::Vector{Float64}, v::Vector{Float64}; verbose::Bool=false)\n    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)\n    return FTPlan{Float64, 1, MODIFIEDHERM2HERM}(plan, n)\nend\n\n\nfunction plan_leg2cheb(::Type{BigFloat}, n::Integer; normleg::Bool=false, normcheb::Bool=false)\n    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[])\n    return FTPlan{BigFloat, 1, LEG2CHEB}(plan, n)\nend\n\nfunction plan_cheb2leg(::Type{BigFloat}, n::Integer; normcheb::Bool=false, normleg::Bool=false)\n    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[])\n    return FTPlan{BigFloat, 1, CHEB2LEG}(plan, n)\nend\n\nfunction plan_ultra2ultra(::Type{BigFloat}, n::Integer, λ, μ; norm1::Bool=false, norm2::Bool=false)\n    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[])\n    return FTPlan{BigFloat, 1, ULTRA2ULTRA}(plan, n)\nend\n\nfunction plan_jac2jac(::Type{BigFloat}, n::Integer, α, β, γ, δ; norm1::Bool=false, norm2::Bool=false)\n    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[])\n    return FTPlan{BigFloat, 1, JAC2JAC}(plan, n)\nend\n\nfunction plan_lag2lag(::Type{BigFloat}, n::Integer, α, β; norm1::Bool=false, norm2::Bool=false)\n    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[])\n    return FTPlan{BigFloat, 1, LAG2LAG}(plan, n)\nend\n\nfunction plan_jac2ultra(::Type{BigFloat}, n::Integer, α, β, λ; normjac::Bool=false, normultra::Bool=false)\n    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[])\n    return FTPlan{BigFloat, 1, JAC2ULTRA}(plan, n)\nend\n\nfunction plan_ultra2jac(::Type{BigFloat}, n::Integer, λ, α, β; normultra::Bool=false, normjac::Bool=false)\n    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[])\n    return FTPlan{BigFloat, 1, ULTRA2JAC}(plan, n)\nend\n\nfunction plan_jac2cheb(::Type{BigFloat}, n::Integer, α, β; normjac::Bool=false, normcheb::Bool=false)\n    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[])\n    return FTPlan{BigFloat, 1, JAC2CHEB}(plan, n)\nend\n\nfunction plan_cheb2jac(::Type{BigFloat}, n::Integer, α, β; normcheb::Bool=false, normjac::Bool=false)\n    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[])\n    return FTPlan{BigFloat, 1, CHEB2JAC}(plan, n)\nend\n\nfunction plan_ultra2cheb(::Type{BigFloat}, n::Integer, λ; normultra::Bool=false, normcheb::Bool=false)\n    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[])\n    return FTPlan{BigFloat, 1, ULTRA2CHEB}(plan, n)\nend\n\nfunction plan_cheb2ultra(::Type{BigFloat}, n::Integer, λ; normcheb::Bool=false, normultra::Bool=false)\n    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[])\n    return FTPlan{BigFloat, 1, CHEB2ULTRA}(plan, n)\nend\n\n\nfunction plan_sph2fourier(::Type{Float64}, n::Integer)\n    plan = ccall((:ft_plan_sph2fourier, libfasttransforms), Ptr{ft_plan_struct}, (Cint, ), n)\n    return FTPlan{Float64, 2, SPHERE}(plan, n)\nend\n\nfunction plan_sphv2fourier(::Type{Float64}, n::Integer)\n    plan = ccall((:ft_plan_sph2fourier, libfasttransforms), Ptr{ft_plan_struct}, (Cint, ), n)\n    return FTPlan{Float64, 2, SPHEREV}(plan, n)\nend\n\nfunction plan_disk2cxf(::Type{Float64}, n::Integer, α, β)\n    plan = ccall((:ft_plan_disk2cxf, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Float64, Float64), n, α, β)\n    return FTPlan{Float64, 2, DISK}(plan, n)\nend\n\nfunction plan_ann2cxf(::Type{Float64}, n::Integer, α, β, γ, ρ)\n    plan = ccall((:ft_plan_ann2cxf, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Float64, Float64, Float64, Float64), n, α, β, γ, ρ)\n    return FTPlan{Float64, 2, ANNULUS}(plan, n)\nend\n\nfunction plan_rectdisk2cheb(::Type{Float64}, n::Integer, β)\n    plan = ccall((:ft_plan_rectdisk2cheb, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Float64), n, β)\n    return FTPlan{Float64, 2, RECTDISK}(plan, n)\nend\n\nfunction plan_tri2cheb(::Type{Float64}, n::Integer, α, β, γ)\n    plan = ccall((:ft_plan_tri2cheb, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Float64, Float64, Float64), n, α, β, γ)\n    return FTPlan{Float64, 2, TRIANGLE}(plan, n)\nend\n\nfunction plan_tet2cheb(::Type{Float64}, n::Integer, α, β, γ, δ)\n    plan = ccall((:ft_plan_tet2cheb, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Float64, Float64, Float64, Float64), n, α, β, γ, δ)\n    return FTPlan{Float64, 3, TETRAHEDRON}(plan, n)\nend\n\nfunction plan_spinsph2fourier(::Type{Complex{Float64}}, n::Integer, s::Integer)\n    plan = ccall((:ft_plan_spinsph2fourier, libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint), n, s)\n    return FTPlan{Complex{Float64}, 2, SPINSPHERE}(plan, n)\nend\n\nplan_disk2cxf(::Type{Float64}, n::Integer, α) = plan_disk2cxf(Float64, n, α, 0)\nplan_disk2cxf(::Type{Float64}, n::Integer) = plan_disk2cxf(Float64, n, 0)\nplan_ann2cxf(::Type{Float64}, n::Integer, α, β, γ) = plan_ann2cxf(Float64, n, α, β, γ, 0)\nplan_ann2cxf(::Type{Float64}, n::Integer, α, β) = plan_disk2cxf(Float64, n, α, β)\nplan_ann2cxf(::Type{Float64}, n::Integer, α) = plan_disk2cxf(Float64, n, α)\nplan_ann2cxf(::Type{Float64}, n::Integer) = plan_disk2cxf(Float64, n)\nplan_rectdisk2cheb(::Type{Float64}, n::Integer) = plan_rectdisk2cheb(Float64, n, 0)\nplan_tri2cheb(::Type{Float64}, n::Integer, α, β) = plan_tri2cheb(Float64, n, α, β, 0)\nplan_tri2cheb(::Type{Float64}, n::Integer, α) = plan_tri2cheb(Float64, n, α, 0)\nplan_tri2cheb(::Type{Float64}, n::Integer) = plan_tri2cheb(Float64, n, 0)\nplan_tet2cheb(::Type{Float64}, n::Integer, α, β, γ) = plan_tet2cheb(Float64, n, α, β, γ, 0)\nplan_tet2cheb(::Type{Float64}, n::Integer, α, β) = plan_tet2cheb(Float64, n, α, β, 0)\nplan_tet2cheb(::Type{Float64}, n::Integer, α) = plan_tet2cheb(Float64, n, α, 0)\nplan_tet2cheb(::Type{Float64}, n::Integer) = plan_tet2cheb(Float64, n, 0)\n\nfor (fJ, fadJ, fC, fE, K) in ((:plan_sph_synthesis, :plan_sph_analysis, :ft_plan_sph_synthesis, :ft_execute_sph_synthesis, SPHERESYNTHESIS),\n                              (:plan_sph_analysis, :plan_sph_synthesis, :ft_plan_sph_analysis, :ft_execute_sph_analysis, SPHEREANALYSIS),\n                              (:plan_sphv_synthesis, :plan_sphv_analysis, :ft_plan_sphv_synthesis, :ft_execute_sphv_synthesis, SPHEREVSYNTHESIS),\n                              (:plan_sphv_analysis, :plan_sphv_synthesis, :ft_plan_sphv_analysis, :ft_execute_sphv_analysis, SPHEREVANALYSIS),\n                              (:plan_disk_synthesis, :plan_disk_analysis, :ft_plan_disk_synthesis, :ft_execute_disk_synthesis, DISKSYNTHESIS),\n                              (:plan_disk_analysis, :plan_disk_synthesis, :ft_plan_disk_analysis, :ft_execute_disk_analysis, DISKANALYSIS),\n                              (:plan_rectdisk_synthesis, :plan_rectdisk_analysis, :ft_plan_rectdisk_synthesis, :ft_execute_rectdisk_synthesis, RECTDISKSYNTHESIS),\n                              (:plan_rectdisk_analysis, :plan_rectdisk_synthesis, :ft_plan_rectdisk_analysis, :ft_execute_rectdisk_analysis, RECTDISKANALYSIS),\n                              (:plan_tri_synthesis, :plan_tri_analysis, :ft_plan_tri_synthesis, :ft_execute_tri_synthesis, TRIANGLESYNTHESIS),\n                              (:plan_tri_analysis, :plan_tri_synthesis, :ft_plan_tri_analysis, :ft_execute_tri_analysis, TRIANGLEANALYSIS))\n    @eval begin\n        $fJ(x::Matrix{T}; y...) where T = $fJ(T, size(x, 1), size(x, 2); y...)\n        $fJ(::Type{Complex{T}}, x...; y...) where T <: Real = $fJ(T, x...; y...)\n        function $fJ(::Type{Float64}, n::Integer, m::Integer; flags::Integer=FFTW.ESTIMATE)\n            plan = ccall(($(string(fC)), libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cuint), n, m, flags)\n            return FTPlan{Float64, 2, $K}(plan, n, m)\n        end\n        adjoint(p::FTPlan{T, 2, $K}) where T = AdjointFTPlan(p, $fadJ(T, p.n, p.m))\n        transpose(p::FTPlan{T, 2, $K}) where T = TransposeFTPlan(p, $fadJ(T, p.n, p.m))\n        function lmul!(p::FTPlan{Float64, 2, $K}, x::Matrix{Float64})\n            checksize(p, x)\n            ccall(($(string(fE)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{Float64}, Cint, Cint), 'N', p, x, size(x, 1), size(x, 2))\n            return x\n        end\n        function lmul!(p::AdjointFTPlan{Float64, FTPlan{Float64, 2, $K}}, x::Matrix{Float64})\n            checksize(p, x)\n            ccall(($(string(fE)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{Float64}, Cint, Cint), 'T', p, x, size(x, 1), size(x, 2))\n            return x\n        end\n        function lmul!(p::TransposeFTPlan{Float64, FTPlan{Float64, 2, $K}}, x::Matrix{Float64})\n            checksize(p, x)\n            ccall(($(string(fE)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{Float64}, Cint, Cint), 'T', p, x, size(x, 1), size(x, 2))\n            return x\n        end\n    end\nend\n\nft_get_rho_annulus_fftw_plan(p::FTPlan{Float64, 2, ANNULUSSYNTHESIS}) = ccall((:ft_get_rho_annulus_fftw_plan, libfasttransforms), Float64, (Ptr{ft_plan_struct}, ), p)\nft_get_rho_annulus_fftw_plan(p::FTPlan{Float64, 2, ANNULUSANALYSIS}) = ccall((:ft_get_rho_annulus_fftw_plan, libfasttransforms), Float64, (Ptr{ft_plan_struct}, ), p)\n\nfor (fJ, fadJ, fC, fE, K) in ((:plan_annulus_synthesis, :plan_annulus_analysis, :ft_plan_annulus_synthesis, :ft_execute_annulus_synthesis, ANNULUSSYNTHESIS),\n                              (:plan_annulus_analysis, :plan_annulus_synthesis, :ft_plan_annulus_analysis, :ft_execute_annulus_analysis, ANNULUSANALYSIS))\n    @eval begin\n        $fJ(x::Matrix{T}, ρ; y...) where T = $fJ(T, size(x, 1), size(x, 2), ρ; y...)\n        $fJ(::Type{Complex{T}}, x...; y...) where T <: Real = $fJ(T, x...; y...)\n        function $fJ(::Type{Float64}, n::Integer, m::Integer, ρ; flags::Integer=FFTW.ESTIMATE)\n            plan = ccall(($(string(fC)), libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Float64, Cuint), n, m, ρ, flags)\n            return FTPlan{Float64, 2, $K}(plan, n, m)\n        end\n        adjoint(p::FTPlan{T, 2, $K}) where T = AdjointFTPlan(p, $fadJ(T, p.n, p.m, ft_get_rho_annulus_fftw_plan(p)))\n        transpose(p::FTPlan{T, 2, $K}) where T = TransposeFTPlan(p, $fadJ(T, p.n, p.m, ft_get_rho_annulus_fftw_plan(p)))\n        function lmul!(p::FTPlan{Float64, 2, $K}, x::Matrix{Float64})\n            checksize(p, x)\n            ccall(($(string(fE)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{Float64}, Cint, Cint), 'N', p, x, size(x, 1), size(x, 2))\n            return x\n        end\n        function lmul!(p::AdjointFTPlan{Float64, FTPlan{Float64, 2, $K}}, x::Matrix{Float64})\n            checksize(p, x)\n            ccall(($(string(fE)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{Float64}, Cint, Cint), 'T', p, x, size(x, 1), size(x, 2))\n            return x\n        end\n        function lmul!(p::TransposeFTPlan{Float64, FTPlan{Float64, 2, $K}}, x::Matrix{Float64})\n            checksize(p, x)\n            ccall(($(string(fE)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{Float64}, Cint, Cint), 'T', p, x, size(x, 1), size(x, 2))\n            return x\n        end\n    end\nend\n\nfor (fJ, fadJ, fC, fE, K) in ((:plan_tet_synthesis, :plan_tet_analysis, :ft_plan_tet_synthesis, :ft_execute_tet_synthesis, TETRAHEDRONSYNTHESIS),\n                              (:plan_tet_analysis, :plan_tet_synthesis, :ft_plan_tet_analysis, :ft_execute_tet_analysis, TETRAHEDRONANALYSIS))\n    @eval begin\n        $fJ(x::Array{T, 3}; y...) where T = $fJ(T, size(x, 1), size(x, 2), size(x, 3); y...)\n        $fJ(::Type{Complex{T}}, x...; y...) where T <: Real = $fJ(T, x...; y...)\n        function $fJ(::Type{Float64}, n::Integer, l::Integer, m::Integer; flags::Integer=FFTW.ESTIMATE)\n            plan = ccall(($(string(fC)), libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Cuint), n, l, m, flags)\n            return FTPlan{Float64, 3, $K}(plan, n, l, m)\n        end\n        adjoint(p::FTPlan{T, 3, $K}) where T = AdjointFTPlan(p, $fadJ(T, p.n, p.l, p.m))\n        transpose(p::FTPlan{T, 3, $K}) where T = TransposeFTPlan(p, $fadJ(T, p.n, p.l, p.m))\n        function lmul!(p::FTPlan{Float64, 3, $K}, x::Array{Float64, 3})\n            checksize(p, x)\n            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))\n            return x\n        end\n        function lmul!(p::AdjointFTPlan{Float64, FTPlan{Float64, 3, $K}}, x::Array{Float64, 3})\n            checksize(p, x)\n            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))\n            return x\n        end\n        function lmul!(p::TransposeFTPlan{Float64, FTPlan{Float64, 3, $K}}, x::Array{Float64, 3})\n            checksize(p, x)\n            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))\n            return x\n        end\n    end\nend\n\nfor (fJ, fadJ, fC, fE, K) in ((:plan_spinsph_synthesis, :plan_spinsph_analysis, :ft_plan_spinsph_synthesis, :ft_execute_spinsph_synthesis, SPINSPHERESYNTHESIS),\n                              (:plan_spinsph_analysis, :plan_spinsph_synthesis, :ft_plan_spinsph_analysis, :ft_execute_spinsph_analysis, SPINSPHEREANALYSIS))\n    @eval begin\n        $fJ(x::Matrix{T}, s::Integer; y...) where T = $fJ(T, size(x, 1), size(x, 2), s; y...)\n        function $fJ(::Type{Complex{Float64}}, n::Integer, m::Integer, s::Integer; flags::Integer=FFTW.ESTIMATE)\n            plan = ccall(($(string(fC)), libfasttransforms), Ptr{ft_plan_struct}, (Cint, Cint, Cint, Cuint), n, m, s, flags)\n            return FTPlan{Complex{Float64}, 2, $K}(plan, n, m)\n        end\n        get_spin(p::FTPlan{T, 2, $K}) where T = ccall((:ft_get_spin_spinsphere_fftw_plan, libfasttransforms), Cint, (Ptr{ft_plan_struct},), p)\n        adjoint(p::FTPlan{T, 2, $K}) where T = AdjointFTPlan(p, $fadJ(T, p.n, p.m, get_spin(p)))\n        transpose(p::FTPlan{T, 2, $K}) where T = TransposeFTPlan(p, $fadJ(T, p.n, p.m, get_spin(p)))\n        function lmul!(p::FTPlan{Complex{Float64}, 2, $K}, x::Matrix{Complex{Float64}})\n            checksize(p, x)\n            ccall(($(string(fE)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{Float64}, Cint, Cint), 'N', p, x, size(x, 1), size(x, 2))\n            return x\n        end\n        function lmul!(p::AdjointFTPlan{Complex{Float64}, FTPlan{Complex{Float64}, 2, $K}}, x::Matrix{Complex{Float64}})\n            checksize(p, x)\n            ccall(($(string(fE)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{Float64}, Cint, Cint), 'C', p, x, size(x, 1), size(x, 2))\n            return x\n        end\n        function lmul!(p::TransposeFTPlan{Complex{Float64}, FTPlan{Complex{Float64}, 2, $K}}, x::Matrix{Complex{Float64}})\n            checksize(p, x)\n            conj!(x)\n            ccall(($(string(fE)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{Float64}, Cint, Cint), 'C', p, x, size(x, 1), size(x, 2))\n            conj!(x)\n            return x\n        end\n    end\nend\n\nfunction plan_sph_isometry(::Type{Float64}, n::Integer)\n    plan = ccall((:ft_plan_sph_isometry, libfasttransforms), Ptr{ft_plan_struct}, (Cint, ), n)\n    return FTPlan{Float64, 2, SPHERICALISOMETRY}(plan, n)\nend\n\n*(p::FTPlan{T}, x::AbstractArray{T}) where T = lmul!(p, Array(x))\n*(p::AdjointFTPlan{T}, x::AbstractArray{T}) where T = lmul!(p, Array(x))\n*(p::TransposeFTPlan{T}, x::AbstractArray{T}) where T = lmul!(p, Array(x))\n\\(p::FTPlan{T}, x::AbstractArray{T}) where T = ldiv!(p, Array(x))\n\\(p::AdjointFTPlan{T}, x::AbstractArray{T}) where T = ldiv!(p, Array(x))\n\\(p::TransposeFTPlan{T}, x::AbstractArray{T}) where T = ldiv!(p, Array(x))\n\n*(p::FTPlan{T, 1}, x::UniformScaling{S}) where {T, S} = UpperTriangular(lmul!(p, Matrix{promote_type(T, S)}(x, p.n, p.n)))\n*(p::AdjointFTPlan{T, FTPlan{T, 1, K}}, x::UniformScaling{S}) where {T, S, K} = LowerTriangular(lmul!(p, Matrix{promote_type(T, S)}(x, p.parent.n, p.parent.n)))\n*(p::TransposeFTPlan{T, FTPlan{T, 1, K}}, x::UniformScaling{S}) where {T, S, K} = LowerTriangular(lmul!(p, Matrix{promote_type(T, S)}(x, p.parent.n, p.parent.n)))\n\\(p::FTPlan{T, 1}, x::UniformScaling{S}) where {T, S} = UpperTriangular(ldiv!(p, Matrix{promote_type(T, S)}(x, p.n, p.n)))\n\\(p::AdjointFTPlan{T, FTPlan{T, 1, K}}, x::UniformScaling{S}) where {T, S, K} = LowerTriangular(ldiv!(p, Matrix{promote_type(T, S)}(x, p.parent.n, p.parent.n)))\n\\(p::TransposeFTPlan{T, FTPlan{T, 1, K}}, x::UniformScaling{S}) where {T, S, K} = LowerTriangular(ldiv!(p, Matrix{promote_type(T, S)}(x, p.parent.n, p.parent.n)))\n\nconst AbstractUpperTriangular{T, S <: AbstractMatrix} = Union{UpperTriangular{T, S}, UnitUpperTriangular{T, S}}\nconst AbstractLowerTriangular{T, S <: AbstractMatrix} = Union{LowerTriangular{T, S}, UnitLowerTriangular{T, S}}\n\n*(p::FTPlan{T, 1}, x::AbstractUpperTriangular) where T = UpperTriangular(lmul!(p, Array(x)))\n*(p::AdjointFTPlan{T, 1}, x::AbstractLowerTriangular) where T = LowerTriangular(lmul!(p, Array(x)))\n*(p::TransposeFTPlan{T, 1}, x::AbstractLowerTriangular) where T = LowerTriangular(lmul!(p, Array(x)))\n\\(p::FTPlan{T, 1}, x::AbstractUpperTriangular) where T = UpperTriangular(ldiv!(p, Array(x)))\n\\(p::AdjointFTPlan{T, 1}, x::AbstractLowerTriangular) where T = LowerTriangular(ldiv!(p, Array(x)))\n\\(p::TransposeFTPlan{T, 1}, x::AbstractLowerTriangular) where T = LowerTriangular(ldiv!(p, Array(x)))\n\nfor (fJ, fC, elty) in ((:lmul!, :ft_bfmvf, :Float32),\n                       (:ldiv!, :ft_bfsvf, :Float32),\n                       (:lmul!, :ft_bfmv , :Float64),\n                       (:ldiv!, :ft_bfsv , :Float64))\n    @eval begin\n        function $fJ(p::FTPlan{$elty, 1}, x::StridedVector{$elty})\n            checksize(p, x)\n            checkstride(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{$elty}), 'N', p, x)\n            return x\n        end\n        function $fJ(p::AdjointFTPlan{$elty, FTPlan{$elty, 1, K}}, x::StridedVector{$elty}) where K\n            checksize(p, x)\n            checkstride(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{$elty}), 'T', p, x)\n            return x\n        end\n        function $fJ(p::TransposeFTPlan{$elty, FTPlan{$elty, 1, K}}, x::StridedVector{$elty}) where K\n            checksize(p, x)\n            checkstride(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{$elty}), 'T', p, x)\n            return x\n        end\n    end\nend\n\nfor (fJ, fC, elty) in ((:lmul!, :ft_bbbfmvf, :Float32),\n                       (:lmul!, :ft_bbbfmv , :Float64))\n    @eval begin\n        function $fJ(p::FTPlan{$elty, 1, ASSOCIATEDJAC2JAC}, x::StridedVector{$elty})\n            checksize(p, x)\n            checkstride(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Cint, Cint, Ptr{ft_plan_struct}, Ptr{$elty}), 'N', '2', '1', p, x)\n            return x\n        end\n        function $fJ(p::AdjointFTPlan{$elty, FTPlan{$elty, 1, ASSOCIATEDJAC2JAC}}, x::StridedVector{$elty})\n            checksize(p, x)\n            checkstride(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Cint, Cint, Ptr{ft_plan_struct}, Ptr{$elty}), 'T', '1', '2', p, x)\n            return x\n        end\n        function $fJ(p::TransposeFTPlan{$elty, FTPlan{$elty, 1, ASSOCIATEDJAC2JAC}}, x::StridedVector{$elty})\n            checksize(p, x)\n            checkstride(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Cint, Cint, Ptr{ft_plan_struct}, Ptr{$elty}), 'T', '1', '2', p, x)\n            return x\n        end\n    end\nend\n\nfor (fJ, fC, elty) in ((:lmul!, :ft_mpmvf, :Float32),\n                       (:ldiv!, :ft_mpsvf, :Float32),\n                       (:lmul!, :ft_mpmv , :Float64),\n                       (:ldiv!, :ft_mpsv , :Float64))\n    @eval begin\n        function $fJ(p::ModifiedFTPlan{$elty}, x::StridedVector{$elty})\n            checksize(p, x)\n            checkstride(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{$elty}), 'N', p, x)\n            return x\n        end\n        function $fJ(p::AdjointFTPlan{$elty, ModifiedFTPlan{$elty}}, x::StridedVector{$elty})\n            checksize(p, x)\n            checkstride(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{$elty}), 'T', p, x)\n            return x\n        end\n        function $fJ(p::TransposeFTPlan{$elty, ModifiedFTPlan{$elty}}, x::StridedVector{$elty})\n            checksize(p, x)\n            checkstride(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{$elty}), 'T', p, x)\n            return x\n        end\n    end\nend\n\nfor (fJ, fC) in ((:lmul!, :ft_mpfr_trmv_ptr),\n                 (:ldiv!, :ft_mpfr_trsv_ptr))\n    @eval begin\n        function $fJ(p::FTPlan{BigFloat, 1}, x::StridedVector{BigFloat})\n            checksize(p, x)\n            checkstride(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Cint, Ptr{mpfr_t}, Cint, Ptr{BigFloat}, Int32), 'N', p.n, p, p.n, renew!(x), Base.MPFR.ROUNDING_MODE[])\n            return x\n        end\n        function $fJ(p::AdjointFTPlan{BigFloat, FTPlan{BigFloat, 1, K}}, x::StridedVector{BigFloat}) where K\n            checksize(p, x)\n            checkstride(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Cint, Ptr{mpfr_t}, Cint, Ptr{BigFloat}, Int32), 'T', p.parent.n, p, p.parent.n, renew!(x), Base.MPFR.ROUNDING_MODE[])\n            return x\n        end\n        function $fJ(p::TransposeFTPlan{BigFloat, FTPlan{BigFloat, 1, K}}, x::StridedVector{BigFloat}) where K\n            checksize(p, x)\n            checkstride(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Cint, Ptr{mpfr_t}, Cint, Ptr{BigFloat}, Int32), 'T', p.parent.n, p, p.parent.n, renew!(x), Base.MPFR.ROUNDING_MODE[])\n            return x\n        end\n    end\nend\n\nfor (fJ, fC, elty) in ((:lmul!, :ft_bfmmf, :Float32),\n                       (:ldiv!, :ft_bfsmf, :Float32),\n                       (:lmul!, :ft_bfmm , :Float64),\n                       (:ldiv!, :ft_bfsm , :Float64))\n    @eval begin\n        function $fJ(p::FTPlan{$elty, 1}, x::StridedMatrix{$elty})\n            checksize(p, x)\n            checkstride(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{$elty}, Cint, Cint), 'N', p, x, stride(x, 2), size(x, 2))\n            return x\n        end\n        function $fJ(p::AdjointFTPlan{$elty, FTPlan{$elty, 1, K}}, x::StridedMatrix{$elty}) where K\n            checksize(p, x)\n            checkstride(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{$elty}, Cint, Cint), 'T', p, x, stride(x, 2), size(x, 2))\n            return x\n        end\n        function $fJ(p::TransposeFTPlan{$elty, FTPlan{$elty, 1, K}}, x::StridedMatrix{$elty}) where K\n            checksize(p, x)\n            checkstride(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{$elty}, Cint, Cint), 'T', p, x, stride(x, 2), size(x, 2))\n            return x\n        end\n    end\nend\n\nfor (fJ, fC, elty) in ((:lmul!, :ft_bbbfmmf, :Float32),\n                       (:lmul!, :ft_bbbfmm , :Float64))\n    @eval begin\n        function $fJ(p::FTPlan{$elty, 1, ASSOCIATEDJAC2JAC}, x::StridedMatrix{$elty})\n            checksize(p, x)\n            checkstride(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Cint, Cint, Ptr{ft_plan_struct}, Ptr{$elty}, Cint, Cint), 'N', '2', '1', p, x, stride(x, 2), size(x, 2))\n            return x\n        end\n        function $fJ(p::AdjointFTPlan{$elty, FTPlan{$elty, 1, ASSOCIATEDJAC2JAC}}, x::StridedMatrix{$elty})\n            checksize(p, x)\n            checkstride(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Cint, Cint, Ptr{ft_plan_struct}, Ptr{$elty}, Cint, Cint), 'T', '1', '2', p, x, stride(x, 2), size(x, 2))\n            return x\n        end\n        function $fJ(p::TransposeFTPlan{$elty, FTPlan{$elty, 1, ASSOCIATEDJAC2JAC}}, x::StridedMatrix{$elty})\n            checksize(p, x)\n            checkstride(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Cint, Cint, Ptr{ft_plan_struct}, Ptr{$elty}, Cint, Cint), 'T', '1', '2', p, x, stride(x, 2), size(x, 2))\n            return x\n        end\n    end\nend\n\nfor (fJ, fC, elty) in ((:lmul!, :ft_mpmmf, :Float32),\n                       (:ldiv!, :ft_mpsmf, :Float32),\n                       (:lmul!, :ft_mpmm , :Float64),\n                       (:ldiv!, :ft_mpsm , :Float64))\n    @eval begin\n        function $fJ(p::ModifiedFTPlan{$elty}, x::StridedMatrix{$elty})\n            checksize(p, x)\n            checkstride(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{$elty}, Cint, Cint), 'N', p, x, stride(x, 2), size(x, 2))\n            return x\n        end\n        function $fJ(p::AdjointFTPlan{$elty, ModifiedFTPlan{$elty}}, x::StridedMatrix{$elty})\n            checksize(p, x)\n            checkstride(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{$elty}, Cint, Cint), 'T', p, x, stride(x, 2), size(x, 2))\n            return x\n        end\n        function $fJ(p::TransposeFTPlan{$elty, ModifiedFTPlan{$elty}}, x::StridedMatrix{$elty})\n            checksize(p, x)\n            checkstride(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{$elty}, Cint, Cint), 'T', p, x, stride(x, 2), size(x, 2))\n            return x\n        end\n    end\nend\n\nfor (fJ, fC) in ((:lmul!, :ft_mpfr_trmm_ptr),\n                 (:ldiv!, :ft_mpfr_trsm_ptr))\n    @eval begin\n        function $fJ(p::FTPlan{BigFloat, 1}, x::StridedMatrix{BigFloat})\n            checksize(p, x)\n            checkstride(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Cint, Ptr{mpfr_t}, Cint, Ptr{BigFloat}, Cint, Cint, Int32), 'N', p.n, p, p.n, renew!(x), stride(x, 2), size(x, 2), Base.MPFR.ROUNDING_MODE[])\n            return x\n        end\n        function $fJ(p::AdjointFTPlan{BigFloat, FTPlan{BigFloat, 1, K}}, x::StridedMatrix{BigFloat}) where K\n            checksize(p, x)\n            checkstride(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Cint, Ptr{mpfr_t}, Cint, Ptr{BigFloat}, Cint, Cint, Int32), 'T', p.parent.n, p, p.parent.n, renew!(x), stride(x, 2), size(x, 2), Base.MPFR.ROUNDING_MODE[])\n            return x\n        end\n        function $fJ(p::TransposeFTPlan{BigFloat, FTPlan{BigFloat, 1, K}}, x::StridedMatrix{BigFloat}) where K\n            checksize(p, x)\n            checkstride(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Cint, Ptr{mpfr_t}, Cint, Ptr{BigFloat}, Cint, Cint, Int32), 'T', p.parent.n, p, p.parent.n, renew!(x), stride(x, 2), size(x, 2), Base.MPFR.ROUNDING_MODE[])\n            return x\n        end\n    end\nend\n\nfor (fJ, fC, T, N, K) in ((:lmul!, :ft_execute_sph2fourier, Float64, 2, SPHERE),\n                          (:ldiv!, :ft_execute_fourier2sph, Float64, 2, SPHERE),\n                          (:lmul!, :ft_execute_sphv2fourier, Float64, 2, SPHEREV),\n                          (:ldiv!, :ft_execute_fourier2sphv, Float64, 2, SPHEREV),\n                          (:lmul!, :ft_execute_spinsph2fourier, Complex{Float64}, 2, SPINSPHERE),\n                          (:ldiv!, :ft_execute_fourier2spinsph, Complex{Float64}, 2, SPINSPHERE),\n                          (:lmul!, :ft_execute_disk2cxf, Float64, 2, DISK),\n                          (:ldiv!, :ft_execute_cxf2disk, Float64, 2, DISK),\n                          (:lmul!, :ft_execute_ann2cxf, Float64, 2, ANNULUS),\n                          (:ldiv!, :ft_execute_cxf2ann, Float64, 2, ANNULUS),\n                          (:lmul!, :ft_execute_rectdisk2cheb, Float64, 2, RECTDISK),\n                          (:ldiv!, :ft_execute_cheb2rectdisk, Float64, 2, RECTDISK),\n                          (:lmul!, :ft_execute_tri2cheb, Float64, 2, TRIANGLE),\n                          (:ldiv!, :ft_execute_cheb2tri, Float64, 2, TRIANGLE))\n    @eval begin\n        function $fJ(p::FTPlan{$T, $N, $K}, x::Array{$T, $N})\n            checksize(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{$T}, Cint, Cint), 'N', p, x, size(x, 1), size(x, 2))\n            return x\n        end\n        function $fJ(p::AdjointFTPlan{$T, FTPlan{$T, $N, $K}}, x::Array{$T, $N})\n            checksize(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{$T}, Cint, Cint), 'T', p, x, size(x, 1), size(x, 2))\n            return x\n        end\n        function $fJ(p::TransposeFTPlan{$T, FTPlan{$T, $N, $K}}, x::Array{$T, $N})\n            checksize(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{$T}, Cint, Cint), 'T', p, x, size(x, 1), size(x, 2))\n            return x\n        end\n    end\nend\n\nfor (fJ, fC) in ((:lmul!, :ft_execute_tet2cheb),\n                 (:ldiv!, :ft_execute_cheb2tet))\n    @eval begin\n        function $fJ(p::FTPlan{Float64, 3, TETRAHEDRON}, x::Array{Float64, 3})\n            checksize(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{Float64}, Cint, Cint, Cint), 'N', p, x, size(x, 1), size(x, 2), size(x, 3))\n            return x\n        end\n        function $fJ(p::AdjointFTPlan{Float64, FTPlan{Float64, 3, TETRAHEDRON}}, x::Array{Float64, 3})\n            checksize(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{Float64}, Cint, Cint, Cint), 'T', p, x, size(x, 1), size(x, 2), size(x, 3))\n            return x\n        end\n        function $fJ(p::TransposeFTPlan{Float64, FTPlan{Float64, 3, TETRAHEDRON}}, x::Array{Float64, 3})\n            checksize(p, x)\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Cint, Ptr{ft_plan_struct}, Ptr{Float64}, Cint, Cint, Cint), 'T', p, x, size(x, 1), size(x, 2), size(x, 3))\n            return x\n        end\n    end\nend\n\nfunction execute_sph_polar_rotation!(x::Matrix{Float64}, α)\n    ccall((:ft_execute_sph_polar_rotation, libfasttransforms), Cvoid, (Ptr{Float64}, Cint, Cint, Float64, Float64), x, size(x, 1), size(x, 2), sin(α), cos(α))\n    return x\nend\n\nfunction execute_sph_polar_reflection!(x::Matrix{Float64})\n    ccall((:ft_execute_sph_polar_reflection, libfasttransforms), Cvoid, (Ptr{Float64}, Cint, Cint), x, size(x, 1), size(x, 2))\n    return x\nend\n\nstruct ft_orthogonal_transformation\n    Q::NTuple{9, Float64}\nend\n\nfunction convert(::Type{ft_orthogonal_transformation}, Q::AbstractMatrix)\n    @assert size(Q, 1) ≥ 3 && size(Q, 2) ≥ 3\n    return ft_orthogonal_transformation((Q[1, 1], Q[2, 1], Q[3, 1], Q[1, 2], Q[2, 2], Q[3, 2], Q[1, 3], Q[2, 3], Q[3, 3]))\nend\nconvert(::Type{ft_orthogonal_transformation}, Q::NTuple{9, Float64}) = ft_orthogonal_transformation(Q)\n\nfunction execute_sph_orthogonal_transformation!(p::FTPlan{Float64, 2, SPHERICALISOMETRY}, Q, x::Matrix{Float64})\n    checksize(p, x)\n    ccall((:ft_execute_sph_orthogonal_transformation, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ft_orthogonal_transformation, Ptr{Float64}, Cint, Cint), p, Q, x, size(x, 1), size(x, 2))\n    return x\nend\n\nfunction execute_sph_yz_axis_exchange!(p::FTPlan{Float64, 2, SPHERICALISOMETRY}, x::Matrix{Float64})\n    checksize(p, x)\n    ccall((:ft_execute_sph_yz_axis_exchange, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, Ptr{Float64}, Cint, Cint), p, x, size(x, 1), size(x, 2))\n    return x\nend\n\nfunction execute_sph_rotation!(p::FTPlan{Float64, 2, SPHERICALISOMETRY}, α, β, γ, x::Matrix{Float64})\n    checksize(p, x)\n    ccall((:ft_execute_sph_rotation, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, Float64, Float64, Float64, Ptr{Float64}, Cint, Cint), p, α, β, γ, x, size(x, 1), size(x, 2))\n    return x\nend\n\nstruct ft_reflection\n    w::NTuple{3, Float64}\nend\n\nfunction convert(::Type{ft_reflection}, w::AbstractVector)\n    @assert length(w) ≥ 3\n    return ft_reflection((w[1], w[2], w[3]))\nend\nconvert(::Type{ft_reflection}, w::NTuple{3, Float64}) = ft_reflection(w)\n\nfunction execute_sph_reflection!(p::FTPlan{Float64, 2, SPHERICALISOMETRY}, w, x::Matrix{Float64})\n    checksize(p, x)\n    ccall((:ft_execute_sph_reflection, libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, ft_reflection, Ptr{Float64}, Cint, Cint), p, w, x, size(x, 1), size(x, 2))\n    return x\nend\nexecute_sph_reflection!(p::FTPlan{Float64, 2, SPHERICALISOMETRY}, w1, w2, w3, x::Matrix{Float64}) = execute_sph_reflection!(p, ft_reflection(w1, w2, w3), x)\n\n*(p::FTPlan{T}, x::AbstractArray{Complex{T}}) where T = lmul!(p, Array(x))\n*(p::AdjointFTPlan{T}, x::AbstractArray{Complex{T}}) where T = lmul!(p, Array(x))\n*(p::TransposeFTPlan{T}, x::AbstractArray{Complex{T}}) where T = lmul!(p, Array(x))\n\\(p::FTPlan{T}, x::AbstractArray{Complex{T}}) where T = ldiv!(p, Array(x))\n\\(p::AdjointFTPlan{T}, x::AbstractArray{Complex{T}}) where T = ldiv!(p, Array(x))\n\\(p::TransposeFTPlan{T}, x::AbstractArray{Complex{T}}) where T = ldiv!(p, Array(x))\n\nfor fJ in (:lmul!, :ldiv!)\n    @eval begin\n        function $fJ(p::FTPlan{T}, x::AbstractArray{Complex{T}}) where T\n            x .= complex.($fJ(p, real(x)), $fJ(p, imag(x)))\n            return x\n        end\n        function $fJ(p::AdjointFTPlan{T}, x::AbstractArray{Complex{T}}) where T\n            x .= complex.($fJ(p, real(x)), $fJ(p, imag(x)))\n            return x\n        end\n        function $fJ(p::TransposeFTPlan{T}, x::AbstractArray{Complex{T}}) where T\n            x .= complex.($fJ(p, real(x)), $fJ(p, imag(x)))\n            return x\n        end\n    end\nend\n\nfor (fC, T) in ((:execute_jacobi_similarityf, Float32), (:execute_jacobi_similarity, Float64))\n    @eval begin\n        function modified_jacobi_matrix(P::ModifiedFTPlan{$T}, XP::SymTridiagonal{$T, Vector{$T}})\n            n = min(P.n, size(XP, 1))\n            XQ = SymTridiagonal(Vector{$T}(undef, n-1), Vector{$T}(undef, n-2))\n            ccall(($(string(fC)), libfasttransforms), Cvoid, (Ptr{ft_plan_struct}, Cint, Ptr{$T}, Ptr{$T}, Ptr{$T}, Ptr{$T}), P, n, XP.dv, XP.ev, XQ.dv, XQ.ev)\n            return XQ\n        end\n    end\nend\n\nfunction modified_jacobi_matrix(R, XP)\n    n = size(R, 1) - 1\n    XQ = SymTridiagonal(zeros(n), zeros(n-1))\n    XQ.dv[1] = (R[1, 1]*XP[1, 1] + R[1, 2]*XP[2, 1])/R[1, 1]\n    for i in 1:n-1\n        XQ.ev[i] = R[i+1, i+1]*XP[i+1, i]/R[i, i]\n    end\n    for i in 2:n\n        XQ.dv[i] = (R[i, i]*XP[i,i] + R[i, i+1]*XP[i+1, i] - XQ[i, i-1]*R[i-1, i])/R[i, i]\n    end\n    return XQ\nend\n"
  },
  {
    "path": "src/nufft.jl",
    "content": "\"\"\"\nPre-computes a nonuniform fast Fourier transform of type `N`.\n\nFor best performance, choose the right number of threads by `FFTW.set_num_threads(4)`, for example.\n\"\"\"\nstruct NUFFTPlan{N,T,FFT} <: Plan{T}\n    U::Matrix{T}\n    V::Matrix{T}\n    p::FFT\n    t::Vector{Int}\n    temp::Matrix{T}\n    temp2::Matrix{T}\n    Ones::Vector{T}\nend\n\n\"\"\"\nPre-computes a nonuniform fast Fourier transform of type I.\n\"\"\"\nfunction plan_nufft1(ω::AbstractVector{T}, ϵ::T) where {T<:AbstractFloat}\n    N = length(ω)\n    ωdN = ω/N\n    t = AssignClosestEquispacedFFTpoint(ωdN)\n    γ = PerturbationParameter(ωdN, AssignClosestEquispacedGridpoint(ωdN))\n    K = FindK(γ, ϵ)\n    U = constructU(ωdN, K)\n    V = constructV(range(zero(T), stop=N-1, length=N), K)\n    p = plan_bfft!(V, 1)\n    temp = zeros(Complex{T}, N, K)\n    temp2 = zeros(Complex{T}, N, K)\n    Ones = ones(Complex{T}, K)\n\n    NUFFTPlan{1, eltype(U), typeof(p)}(U, V, p, t, temp, temp2, Ones)\nend\n\n\"\"\"\nPre-computes a nonuniform fast Fourier transform of type II.\n\"\"\"\nfunction plan_nufft2(x::AbstractVector{T}, ϵ::T) where T<:AbstractFloat\n    N = length(x)\n    t = AssignClosestEquispacedFFTpoint(x)\n    γ = PerturbationParameter(x, AssignClosestEquispacedGridpoint(x))\n    K = FindK(γ, ϵ)\n    U = constructU(x, K)\n    V = constructV(range(zero(T), stop=N-1, length=N), K)\n    p = plan_fft!(U, 1)\n    temp = zeros(Complex{T}, N, K)\n    temp2 = zeros(Complex{T}, N, K)\n    Ones = ones(Complex{T}, K)\n\n    NUFFTPlan{2, eltype(U), typeof(p)}(U, V, p, t, temp, temp2, Ones)\nend\n\n\"\"\"\nPre-computes a nonuniform fast Fourier transform of type III.\n\"\"\"\nfunction plan_nufft3(x::AbstractVector{T}, ω::AbstractVector{T}, ϵ::T) where T<:AbstractFloat\n    N = length(x)\n    s = AssignClosestEquispacedGridpoint(x)\n    t = AssignClosestEquispacedFFTpoint(x)\n    γ = PerturbationParameter(x, s)\n    K = FindK(γ, ϵ)\n    u = constructU(x, K)\n    v = constructV(ω, K)\n\n    p = plan_nufft1(ω, ϵ)\n\n    D1 = Diagonal(1 .- (s .- t .+ 1)./N)\n    D2 = Diagonal((s .- t .+ 1)./N)\n    D3 = Diagonal(exp.(-2 .* im .* T(π) .* ω ))\n    U = hcat(D1*u, D2*u)\n    V = hcat(v, D3*v)\n\n    temp = zeros(Complex{T}, N, 2K)\n    temp2 = zeros(Complex{T}, N, 2K)\n    Ones = ones(Complex{T}, 2K)\n\n    NUFFTPlan{3, eltype(U), typeof(p)}(U, V, p, t, temp, temp2, Ones)\nend\n\nfunction (*)(p::NUFFTPlan{N,T}, c::AbstractArray{V}) where {N,T,V}\n    mul!(zeros(promote_type(T,V), size(c)), p, c)\nend\n\nfunction mul!(f::AbstractVector{T}, P::NUFFTPlan{1,T}, c::AbstractVector{T}) where {T}\n    U, V, p, t, temp, temp2, Ones = P.U, P.V, P.p, P.t, P.temp, P.temp2, P.Ones\n\n    broadcast!(*, temp, c, U)\n    conj!(temp)\n    fill!(temp2, zero(T))\n    recombine_rows!(temp, t, temp2)\n    p*temp2\n    conj!(temp2)\n    broadcast!(*, temp, V, temp2)\n    mul!(f, temp, Ones)\n\n    f\nend\n\nfunction mul!(F::Matrix{T}, P::NUFFTPlan{N,T}, C::Matrix{T}) where {N,T}\n    for J = 1:size(F, 2)\n        mul_col_J!(F, P, C, J)\n    end\n    F\nend\n\nfunction broadcast_col_J!(f, temp::Matrix, C::Matrix, U::Matrix, J::Int)\n    N = size(C, 1)\n    COLSHIFT = N*(J-1)\n    @inbounds for j = 1:size(temp, 2)\n        for i = 1:N\n            temp[i,j] = f(C[i+COLSHIFT],U[i,j])\n        end\n    end\n    temp\nend\n\nfunction mul_col_J!(F::Matrix{T}, P::NUFFTPlan{1,T}, C::Matrix{T}, J::Int) where {T}\n    U, V, p, t, temp, temp2, Ones = P.U, P.V, P.p, P.t, P.temp, P.temp2, P.Ones\n\n    broadcast_col_J!(*, temp, C, U, J)\n    conj!(temp)\n    fill!(temp2, zero(T))\n    recombine_rows!(temp, t, temp2)\n    p*temp2\n    conj!(temp2)\n    broadcast!(*, temp, V, temp2)\n    COLSHIFT = size(C, 1)*(J-1)\n    mul_for_col_J!(F, temp, Ones, 1+COLSHIFT, 1)\n\n    F\nend\n\nfunction mul!(f::AbstractVector{T}, P::NUFFTPlan{2,T}, c::AbstractVector{T}) where {T}\n    U, V, p, t, temp, temp2, Ones = P.U, P.V, P.p, P.t, P.temp, P.temp2, P.Ones\n\n    broadcast!(*, temp, c, V)\n    p*temp\n    reindex_temp!(temp, t, temp2)\n    broadcast!(*, temp, U, temp2)\n    mul!(f, temp, Ones)\n\n    f\nend\n\nfunction mul_col_J!(F::Matrix{T}, P::NUFFTPlan{2,T}, C::Matrix{T}, J::Int) where {T}\n    U, V, p, t, temp, temp2, Ones = P.U, P.V, P.p, P.t, P.temp, P.temp2, P.Ones\n\n    broadcast_col_J!(*, temp, C, V, J)\n    p*temp\n    reindex_temp!(temp, t, temp2)\n    broadcast!(*, temp, U, temp2)\n    COLSHIFT = size(C, 1)*(J-1)\n    mul_for_col_J!(F, temp, Ones, 1+COLSHIFT, 1)\n\n    F\nend\n\nfunction mul!(f::AbstractVector{T}, P::NUFFTPlan{3,T}, c::AbstractVector{T}) where {T}\n    U, V, p, t, temp, temp2, Ones = P.U, P.V, P.p, P.t, P.temp, P.temp2, P.Ones\n\n    broadcast!(*, temp2, c, V)\n    mul!(temp, p, temp2)\n    reindex_temp!(temp, t, temp2)\n    broadcast!(*, temp, U, temp2)\n    mul!(f, temp, Ones)\n\n    f\nend\n\n\nfunction mul_col_J!(F::Matrix{T}, P::NUFFTPlan{3,T}, C::Matrix{T}, J::Int) where {T}\n    U, V, p, t, temp, temp2, Ones = P.U, P.V, P.p, P.t, P.temp, P.temp2, P.Ones\n\n    broadcast_col_J!(*, temp2, C, V, J)\n    mul!(temp, p, temp2)\n    reindex_temp!(temp, t, temp2)\n    broadcast!(*, temp, U, temp2)\n    COLSHIFT = size(C, 1)*(J-1)\n    mul_for_col_J!(F, temp, Ones, 1+COLSHIFT, 1)\n\n    F\nend\n\nmul_for_col_J!(y::AbstractVecOrMat{T}, A::AbstractMatrix{T}, x::AbstractVecOrMat{T}, istart::Int, jstart::Int) where T =\n    mul_for_col_J!(y, A, x, istart, jstart, 1, 1)\n\nfunction mul_for_col_J!(y::AbstractVecOrMat{T}, A::AbstractMatrix{T}, x::AbstractVecOrMat{T}, istart::Int, jstart::Int, INCX::Int, INCY::Int) where T\n    m, n = size(A)\n    ishift, jshift = istart-INCY, jstart-INCX\n    @inbounds for i = 1:m\n        y[ishift+i*INCY] = zero(T)\n    end\n    @inbounds for j = 1:n\n        xj = x[jshift+j*INCX]\n        for i = 1:m\n            y[ishift+i*INCY] += A[i,j]*xj\n        end\n    end\n\n    y\nend\n\nfunction reindex_temp!(temp::Matrix{T}, t::Vector{Int}, temp2::Matrix{T}) where {T}\n    @inbounds for j = 1:size(temp, 2)\n        for i = 1:size(temp, 1)\n            temp2[i, j] = temp[t[i], j]\n        end\n    end\n    temp2\nend\n\nfunction recombine_rows!(temp::Matrix{T}, t::Vector{Int}, temp2::Matrix{T}) where {T}\n    @inbounds for j = 1:size(temp, 2)\n        for i = 1:size(temp, 1)\n            temp2[t[i], j] += temp[i, j]\n        end\n    end\n    temp2\nend\n\n\"\"\"\nComputes a nonuniform fast Fourier transform of type I:\n\n```math\nf_j = \\\\sum_{k=0}^{N-1} c_k e^{-2\\\\pi{\\\\rm i} \\\\frac{j}{N} \\\\omega_k},\\\\quad{\\\\rm for}\\\\quad 0 \\\\le j \\\\le N-1.\n```\n\"\"\"\nnufft1(c::AbstractVector, ω::AbstractVector{T}, ϵ::T) where {T<:AbstractFloat} = plan_nufft1(ω, ϵ)*c\n\n\"\"\"\nComputes a nonuniform fast Fourier transform of type II:\n\n```math\nf_j = \\\\sum_{k=0}^{N-1} c_k e^{-2\\\\pi{\\\\rm i} x_j k},\\\\quad{\\\\rm for}\\\\quad 0 \\\\le j \\\\le N-1.\n```\n\"\"\"\nnufft2(c::AbstractVector, x::AbstractVector{T}, ϵ::T) where {T<:AbstractFloat}  = plan_nufft2(x, ϵ)*c\n\n\"\"\"\nComputes a nonuniform fast Fourier transform of type III:\n\n```math\nf_j = \\\\sum_{k=0}^{N-1} c_k e^{-2\\\\pi{\\\\rm i} x_j \\\\omega_k},\\\\quad{\\\\rm for}\\\\quad 0 \\\\le j \\\\le N-1.\n```\n\"\"\"\nnufft3(c::AbstractVector, x::AbstractVector{T}, ω::AbstractVector{T}, ϵ::T) where {T<:AbstractFloat} = plan_nufft3(x, ω, ϵ)*c\n\nconst nufft = nufft3\nconst plan_nufft = plan_nufft3\n\n\n\"\"\"\nPre-computes a 2D nonuniform fast Fourier transform.\n\nFor best performance, choose the right number of threads by `FFTW.set_num_threads(4)`, for example.\n\"\"\"\nstruct NUFFT2DPlan{T,P1,P2} <: Plan{T}\n    p1::P1\n    p2::P2\n    temp::Vector{T}\nend\n\n\"\"\"\nPre-computes a 2D nonuniform fast Fourier transform of type I-I.\n\"\"\"\nfunction plan_nufft1(ω::AbstractVector{T}, π::AbstractVector{T}, ϵ::T) where T<:AbstractFloat\n    p1 = plan_nufft1(ω, ϵ)\n    p2 = plan_nufft1(π, ϵ)\n    temp = zeros(Complex{T}, length(π))\n\n    NUFFT2DPlan(p1, p2, temp)\nend\n\n\"\"\"\nPre-computes a 2D nonuniform fast Fourier transform of type II-II.\n\"\"\"\nfunction plan_nufft2(x::AbstractVector{T}, y::AbstractVector{T}, ϵ::T) where T<:AbstractFloat\n    p1 = plan_nufft2(x, ϵ)\n    p2 = plan_nufft2(y, ϵ)\n    temp = zeros(Complex{T}, length(y))\n\n    NUFFT2DPlan(p1, p2, temp)\nend\n\nfunction (*)(p::NUFFT2DPlan{T}, C::Matrix{V}) where {T,V}\n    mul!(zeros(promote_type(T,V), size(C)), p, C)\nend\n\nfunction mul!(F::Matrix{T}, P::NUFFT2DPlan{T}, C::Matrix{T}) where {T}\n    p1, p2, temp = P.p1, P.p2, P.temp\n\n    # Apply 1D x-plan to all columns\n    mul!(F, p1, C)\n\n    # Apply 1D y-plan to all rows\n    for i = 1:size(C, 1)\n        @inbounds for j = 1:size(F, 2) temp[j] = F[i,j] end\n        mul!(temp, p2, temp)\n        @inbounds for j = 1:size(F, 2) F[i,j] = temp[j] end\n    end\n\n    F\nend\n\n\"\"\"\nComputes a 2D nonuniform fast Fourier transform of type I-I:\n\n```math\nF_{i,j} = \\\\sum_{k=0}^{M-1}\\\\sum_{\\\\ell=0}^{N-1} C_{k,\\\\ell} e^{-2\\\\pi{\\\\rm i} (\\\\frac{i}{M} \\\\omega_k + \\\\frac{j}{N} \\\\pi_{\\\\ell})},\\\\quad{\\\\rm for}\\\\quad 0 \\\\le i \\\\le M-1,\\\\quad 0 \\\\le j \\\\le N-1.\n```\n\"\"\"\nnufft1(C::Matrix, ω::AbstractVector{T}, π::AbstractVector{T}, ϵ::T) where {T<:AbstractFloat} = plan_nufft1(ω, π, ϵ)*C\n\n\"\"\"\nComputes a 2D nonuniform fast Fourier transform of type II-II:\n\n```math\nF_{i,j} = \\\\sum_{k=0}^{M-1}\\\\sum_{\\\\ell=0}^{N-1} C_{k,\\\\ell} e^{-2\\\\pi{\\\\rm i} (x_i k + y_j \\\\ell)},\\\\quad{\\\\rm for}\\\\quad 0 \\\\le i \\\\le M-1,\\\\quad 0 \\\\le j \\\\le N-1.\n```\n\"\"\"\nnufft2(C::Matrix, x::AbstractVector{T}, y::AbstractVector{T}, ϵ::T) where {T<:AbstractFloat} = plan_nufft2(x, y, ϵ)*C\n\n\nFindK(γ::T, ϵ::T) where {T<:AbstractFloat} = γ ≤ ϵ ? 1 : Int(ceil(5*γ*exp(lambertw(log(10/ϵ)/γ/7))))\n\n(AssignClosestEquispacedGridpoint(x::AbstractVector{T})::AbstractVector{T}) where {T<:AbstractFloat} = round.([Int], size(x, 1)*x)\n(AssignClosestEquispacedFFTpoint(x::AbstractVector{T})::Array{Int,1}) where {T<:AbstractFloat} = mod.(round.([Int], size(x, 1)*x), size(x, 1)) .+ 1\n(PerturbationParameter(x::AbstractVector{T}, s_vec::AbstractVector{T})::AbstractFloat) where {T<:AbstractFloat} = norm(size(x, 1)*x - s_vec, Inf)\n\nfunction constructU(x::AbstractVector{T}, K::Int) where {T<:AbstractFloat}\n    # Construct a low rank approximation, using Chebyshev expansions\n    # for AK = exp(-2*pi*1im*(x[j]-j/N)*k):\n    N = length(x)\n    s = AssignClosestEquispacedGridpoint(x)\n    er = N*x - s\n    γ = norm(er, Inf)\n    Diagonal(exp.(-im*(π*er)))*ChebyshevP(K-1, er/γ)*Bessel_coeffs(K, γ)\nend\n\nfunction constructV(ω::AbstractVector{T}, K::Int) where {T<:AbstractFloat}\n    complex(ChebyshevP(K-1, ω.*(two(T)/length(ω)) .- 1))\nend\n\nfunction Bessel_coeffs(K::Int, γ::T) where {T<:AbstractFloat}\n    # Calculate the Chebyshev coefficients of exp(-2*pi*1im*x*y) on [-gam,gam]x[0,1]\n    cfs = zeros(Complex{T}, K, K)\n    arg = -γ*π/two(T)\n    for p = 0:K-1\n     \tfor q = mod(p,2):2:K-1\n    \t\tcfs[p+1,q+1] = 4*(1im)^q*besselj((p+q)/2,arg).*besselj((q-p)/2,arg)\n    \tend\n    end\n    cfs[1,:] = cfs[1,:]/two(T)\n    cfs[:,1] = cfs[:,1]/two(T)\n    return cfs\nend\n\nfunction ChebyshevP(n::Int, x::AbstractVector{T}) where T<:AbstractFloat\n    # Evaluate Chebyshev polynomials of degree 0,...,n at x:\n    N = size(x, 1)\n    Tcheb = Matrix{T}(undef, N, n+1)\n\n    # T_0(x) = 1.0\n    One = convert(eltype(x),1.0)\n    @inbounds for j = 1:N\n        Tcheb[j, 1] = One\n    end\n    # T_1(x) = x\n    if ( n > 0 )\n        @inbounds for j = 1:N\n            Tcheb[j, 2] = x[j]\n        end\n    end\n    # 3-term recurrence relation:\n    twoX = 2x\n    @inbounds for k = 2:n\n        @simd for j = 1:N\n            Tcheb[j, k+1] = twoX[j]*Tcheb[j, k] - Tcheb[j, k-1]\n        end\n    end\n    return Tcheb\nend\n"
  },
  {
    "path": "src/specialfunctions.jl",
    "content": "import Base.Math: @horner\n\nconst FORWARD  =  true\nconst BACKWARD = false\n\nconst sqrtpi = 1.772453850905516027298\nconst edivsqrt2pi = 1.084437551419227546612\n\n\"\"\"\nCompute a typed 0.5.\n\"\"\"\nhalf(x::Number) = oftype(x,0.5)\nhalf(x::Integer) = half(float(x))\nhalf(::Type{T}) where {T<:Number} = convert(T,0.5)\nhalf(::Type{T}) where {T<:Integer} = half(AbstractFloat)\n\n\"\"\"\nCompute a typed 2.\n\"\"\"\ntwo(x::Number) = oftype(x,2)\ntwo(::Type{T}) where {T<:Number} = convert(T,2)\n\n\"\"\"\nThe Kronecker ``\\\\delta`` function:\n\n```math\n\\\\delta_{k,j} = \\\\left\\\\{\\\\begin{array}{ccc} 1 & {\\\\rm for} & k = j,\\\\\\\\ 0 & {\\\\rm for} & k \\\\ne j.\\\\end{array}\\\\right.\n```\n\"\"\"\nδ(k::Integer,j::Integer) = k == j ? 1 : 0\n\n\n\"\"\"\nPochhammer symbol ``(x)_n = \\\\frac{\\\\Gamma(x+n)}{\\\\Gamma(x)}`` for the rising factorial.\n\"\"\"\nfunction pochhammer(x::Number,n::Integer)\n    ret = one(x)\n    if n≥0\n        for i=0:n-1\n            ret *= x+i\n        end\n    else\n        ret /= pochhammer(x+n,-n)\n    end\n    ret\nend\n\npochhammer(x::Number,n::Number) = isinteger(n) ? pochhammer(x,Int(n)) : ogamma(x)/ogamma(x+n)\n\nfunction pochhammer(x::Number,n::UnitRange{T}) where T<:Real\n    ret = Vector{promote_type(typeof(x),T)}(length(n))\n    ret[1] = pochhammer(x,first(n))\n    for i=2:length(n)\n        ret[i] = (x+n[i]-1)*ret[i-1]\n    end\n    ret\nend\n\nlgamma(x) = logabsgamma(x)[1]\n\nogamma(x::Number) = (isinteger(x) && x<0) ? zero(float(x)) : inv(gamma(x))\n\n\"\"\"\nStirling's asymptotic series for ``\\\\Gamma(z)``.\n\"\"\"\nstirlingseries(z) = gamma(z)*sqrt((z/π)/2)*exp(z)/z^z\n\nfunction stirlingseries(z::Float64)\n    if z ≥ 3274.12075200175       # N =  4\n        @horner(inv(z),1.0,0.08333333333333333,0.003472222222222222,-0.0026813271604938273)\n    elseif z ≥ 590.1021805526798  # N =  5\n        @horner(inv(z),1.0,0.08333333333333333,0.003472222222222222,-0.0026813271604938273,-0.00022947209362139917)\n    elseif z ≥ 195.81733962412835 # N =  6\n        @horner(inv(z),1.0,0.08333333333333333,0.003472222222222222,-0.0026813271604938273,-0.00022947209362139917,0.0007840392217200666)\n    elseif z ≥ 91.4692823071966   # N =  7\n        @horner(inv(z),1.0,0.08333333333333333,0.003472222222222222,-0.0026813271604938273,-0.00022947209362139917,0.0007840392217200666,6.972813758365857e-5)\n    elseif z ≥ 52.70218954633605  # N =  8\n        @horner(inv(z),1.0,0.08333333333333333,0.003472222222222222,-0.0026813271604938273,-0.00022947209362139917,0.0007840392217200666,6.972813758365857e-5,-0.0005921664373536939)\n    elseif z ≥ 34.84031591198865  # N =  9\n        @horner(inv(z),1.0,0.08333333333333333,0.003472222222222222,-0.0026813271604938273,-0.00022947209362139917,0.0007840392217200666,6.972813758365857e-5,-0.0005921664373536939,-5.171790908260592e-5)\n    elseif z ≥ 25.3173982783047   # N = 10\n        @horner(inv(z),1.0,0.08333333333333333,0.003472222222222222,-0.0026813271604938273,-0.00022947209362139917,0.0007840392217200666,6.972813758365857e-5,-0.0005921664373536939,-5.171790908260592e-5,0.0008394987206720873)\n    elseif z ≥ 19.685015283078513 # N = 11\n        @horner(inv(z),1.0,0.08333333333333333,0.003472222222222222,-0.0026813271604938273,-0.00022947209362139917,0.0007840392217200666,6.972813758365857e-5,-0.0005921664373536939,-5.171790908260592e-5,0.0008394987206720873,7.204895416020011e-5)\n    elseif z ≥ 16.088669099569266 # N = 12\n        @horner(inv(z),1.0,0.08333333333333333,0.003472222222222222,-0.0026813271604938273,-0.00022947209362139917,0.0007840392217200666,6.972813758365857e-5,-0.0005921664373536939,-5.171790908260592e-5,0.0008394987206720873,7.204895416020011e-5,-0.0019144384985654776)\n    elseif z ≥ 13.655055978888104 # N = 13\n        @horner(inv(z),1.0,0.08333333333333333,0.003472222222222222,-0.0026813271604938273,-0.00022947209362139917,0.0007840392217200666,6.972813758365857e-5,-0.0005921664373536939,-5.171790908260592e-5,0.0008394987206720873,7.204895416020011e-5,-0.0019144384985654776,-0.00016251626278391583)\n    elseif z ≥ 11.93238782087875  # N = 14\n        @horner(inv(z),1.0,0.08333333333333333,0.003472222222222222,-0.0026813271604938273,-0.00022947209362139917,0.0007840392217200666,6.972813758365857e-5,-0.0005921664373536939,-5.171790908260592e-5,0.0008394987206720873,7.204895416020011e-5,-0.0019144384985654776,-0.00016251626278391583,0.00640336283380807)\n    elseif z ≥ 10.668852439197263 # N = 15\n        @horner(inv(z),1.0,0.08333333333333333,0.003472222222222222,-0.0026813271604938273,-0.00022947209362139917,0.0007840392217200666,6.972813758365857e-5,-0.0005921664373536939,-5.171790908260592e-5,0.0008394987206720873,7.204895416020011e-5,-0.0019144384985654776,-0.00016251626278391583,0.00640336283380807,0.0005401647678926045)\n    elseif z ≥ 9.715358216638403  # N = 16\n        @horner(inv(z),1.0,0.08333333333333333,0.003472222222222222,-0.0026813271604938273,-0.00022947209362139917,0.0007840392217200666,6.972813758365857e-5,-0.0005921664373536939,-5.171790908260592e-5,0.0008394987206720873,7.204895416020011e-5,-0.0019144384985654776,-0.00016251626278391583,0.00640336283380807,0.0005401647678926045,-0.02952788094569912)\n    elseif z ≥ 8.979120323411497  # N = 17\n        @horner(inv(z),1.0,0.08333333333333333,0.003472222222222222,-0.0026813271604938273,-0.00022947209362139917,0.0007840392217200666,6.972813758365857e-5,-0.0005921664373536939,-5.171790908260592e-5,0.0008394987206720873,7.204895416020011e-5,-0.0019144384985654776,-0.00016251626278391583,0.00640336283380807,0.0005401647678926045,-0.02952788094569912,-0.002481743600264998)\n    else\n        gamma(z)*sqrt(z/2π)*exp(z)/z^z\n    end\nend\n\n\n\nstirlingremainder(z::Number,N::Int) = (1+zeta(N))*gamma(N)/((2π)^(N+1)*z^N)/stirlingseries(z)\n\nAratio(n::Int,α::Float64,β::Float64) = exp((n/2+α+1/4)*log1p(-β/(n+α+β+1))+(n/2+β+1/4)*log1p(-α/(n+α+β+1))+(n/2+1/4)*log1p(α/(n+1))+(n/2+1/4)*log1p(β/(n+1)))\nAratio(n::Number,α::Number,β::Number) = (1+(α+1)/n)^(n+α+1/2)*(1+(β+1)/n)^(n+β+1/2)/(1+(α+β+1)/n)^(n+α+β+1/2)/(1+(zero(α)+zero(β))/n)^(n+1/2)\n\nCratio(n::Int,α::Float64,β::Float64) = exp((n+α+1/2)*log1p((α-β)/(2n+α+β+2))+(n+β+1/2)*log1p((β-α)/(2n+α+β+2))-log1p((α+β+2)/2n)/2)/sqrt(n)\nCratio(n::Number,α::Number,β::Number) = n^(-1/2)*(1+(α+1)/n)^(n+α+1/2)*(1+(β+1)/n)^(n+β+1/2)/(1+(α+β+2)/2n)^(2n+α+β+3/2)\n\n\nAnαβ(n::Number,α::Number,β::Number) = 2^(α+β+1)/(2n+α+β+1)*exp(lgamma(n+α+1)-lgamma(n+α+β+1)+lgamma(n+β+1)-lgamma(n+1))\nfunction Anαβ(n::Integer,α::Number,β::Number)\n    if n==0\n        2^(α+β+1)*beta(α+1,β+1)\n    else\n        val = Anαβ(0,α,β)\n        for i=1:n\n            val *= (i+α)*(i+β)/(i+α+β)/i*(2i+α+β-1)/(2i+α+β+1)\n        end\n        val\n    end\nend\n\nfunction Anαβ(n::Integer,α::Float64,β::Float64)\n    if n+min(α,β,α+β,0) ≥ 7.979120323411497\n        2 ^ (α+β+1)/(2n+α+β+1)*stirlingseries(n+α+1)*Aratio(n,α,β)/stirlingseries(n+α+β+1)*stirlingseries(n+β+1)/stirlingseries(n+one(Float64))\n    else\n        (n+1)*(n+α+β+1)/(n+α+1)/(n+β+1)*Anαβ(n+1,α,β)*((2n+α+β+3)/(2n+α+β+1))\n    end\nend\n\n\n\"\"\"\nThe Lambda function ``\\\\Lambda(z) = \\\\frac{\\\\Gamma(z+\\\\frac{1}{2})}{\\\\Gamma(z+1)}`` for the ratio of gamma functions.\n\"\"\"\nΛ(z::Number) = Λ(z, half(z), one(z))\n\n\"\"\"\nFor 64-bit floating-point arithmetic, the Lambda function uses the asymptotic series for ``\\\\tau`` in Appendix B of\n\nI. Bogaert and B. Michiels and J. Fostier, 𝒪(1) computation of Legendre polynomials and Gauss–Legendre nodes and weights for parallel computing, *SIAM J. Sci. Comput.*, **34**:C83–C101, 2012.\n\"\"\"\nfunction Λ(x::Float64)\n    if x > 9.84475\n        xp = x+0.25\n        @horner(inv(xp^2),1.0,-1.5625e-02,2.5634765625e-03,-1.2798309326171875e-03,1.343511044979095458984375e-03,-2.432896639220416545867919921875e-03,6.7542375336415716446936130523681640625e-03)/sqrt(xp)\n    else\n        (x+1.0)*Λ(x+1.0)/(x+0.5)\n    end\nend\n\n\"\"\"\nThe Lambda function ``\\\\Lambda(z,λ₁,λ₂) = \\\\frac{\\\\Gamma(z+\\\\lambda_1)}{Γ(z+\\\\lambda_2)}`` for the ratio of gamma functions.\n\"\"\"\nfunction Λ(z::Real, λ₁::Real, λ₂::Real)\n    if z+λ₁ > 0 && z+λ₂ > 0\n        exp(lgamma(z+λ₁)-lgamma(z+λ₂))\n    else\n        gamma(z+λ₁)/gamma(z+λ₂)\n    end\nend\nfunction Λ(x::Float64, λ₁::Float64, λ₂::Float64)\n    if min(x+λ₁,x+λ₂) ≥ 8.979120323411497\n        exp(λ₂-λ₁+(x-.5)*log1p((λ₁-λ₂)/(x+λ₂)))*(x+λ₁)^λ₁/(x+λ₂)^λ₂*stirlingseries(x+λ₁)/stirlingseries(x+λ₂)\n    else\n        (x+λ₂)/(x+λ₁)*Λ(x + 1.0, λ₁, λ₂)\n    end\nend\n\n## TODO: deprecate when Lambert-W is supported in a mainstream repository such as SpecialFunctions.jl\n\"\"\"\nThe principal branch of the Lambert-W function, defined by ``x = W_0(x) e^{W_0(x)}``, computed using Halley's method for ``x \\\\in [-e^{-1},\\\\infty)``.\n\"\"\"\nfunction lambertw(x::AbstractFloat)\n    if x < -exp(-one(x))\n        return throw(DomainError())\n    elseif x == -exp(-one(x))\n        return -one(x)\n    elseif x < 0\n        w0 = ℯ*x/(1+inv(inv(sqrt(2*ℯ*x+2))+inv(ℯ-1)-inv(sqrt(2))))\n    else\n        log1px = log1p(x)\n        w0 = log1px*(1-log1p(log1px)/(2+log1px))\n    end\n    expw0 = exp(w0)\n    w1 = w0 - (w0*expw0 - x)/((w0 + 1)*expw0 -\n        (w0 + 2) * (w0*expw0 - x)/(2w0 + 2))\n    while abs(w1/w0 - 1) > 2eps(typeof(x))\n        w0 = w1\n        expw0 = exp(w0)\n        w1 = w0 - (w0*expw0 - x)/((w0 + 1)*expw0 -\n            (w0 + 2) * (w0*expw0 - x)/(2w0 + 2))\n    end\n    return w1\nend\nlambertw(x::Real) = lambertw(float(x))\n\n\nCnλ(n::Integer,λ::Float64) = 2^λ/sqrtpi*Λ(n+λ)\nCnλ(n::Integer,λ::Number) = 2^λ/sqrt(oftype(λ,π))*Λ(n+λ)\nfunction Cnλ(n::UnitRange{T},λ::Number) where T<:Integer\n    ret = Vector{typeof(λ)}(undef, length(n))\n    ret[1] = Cnλ(first(n),λ)\n    for i=2:length(n)\n        ret[i] = (n[i]+λ-half(λ))/(n[i]+λ)*ret[i-1]\n    end\n    ret\nend\n\nfunction Cnmλ(n::Integer,m::Integer,λ::Number)\n    if m == 0\n        Cnλ(n,λ)\n    else\n        (λ+m-1)/2/m*(m-λ)/(n+λ+m)*Cnmλ(n,m-1,λ)\n    end\nend\n\n\nfunction Cnαβ(n::Integer,α::Number,β::Number)\n    if n==0\n        2^(α+β+1)*beta(α+1,β+1)/π\n    else\n        val = Cnαβ(0,α,β)\n        for i=1:n\n            val *= (i+α)*(i+β)/(i+(α+β+1)/2)/(i+(α+β)/2)\n        end\n        val\n    end\nend\n\nfunction Cnαβ(n::Integer,α::Float64,β::Float64)\n    if n+min(α,β) ≥ 7.979120323411497\n        stirlingseries(n+α+1)/sqrtpi/stirlingseries(2n+α+β+2)*Cratio(n,α,β)*stirlingseries(n+β+1)\n    else\n        (n+(α+β+3)/2)/(n+β+1)*(n+(α+β+2)/2)/(n+α+1)*Cnαβ(n+1,α,β)\n    end\nend\n\nfunction Cnmαβ(n::Integer,m::Integer,α::Number,β::Number)\n    if m == 0\n        Cnαβ(n,α,β)\n    else\n        Cnmαβ(n,m-1,α,β)/2(2n+α+β+m+1)\n    end\nend\n\n\nfunction Cnmαβ(n::Integer,m::Integer,α::AbstractArray{T},β::AbstractArray{T}) where T<:Number\n    shp = promote_shape(size(α),size(β))\n    reshape([ Cnmαβ(n,m,α[i],β[i]) for i in eachindex(α,β) ], shp)\nend\n\n\n\"\"\"\nModified Chebyshev moments of the first kind:\n\n```math\n    \\\\int_{-1}^{+1} T_n(x) {\\\\rm\\\\,d}x.\n```\n\"\"\"\nfunction chebyshevmoments1(::Type{T}, N::Int) where T\n    μ = zeros(T, N)\n    for i = 0:2:N-1\n        @inbounds μ[i+1] = two(T)/T(1-i^2)\n    end\n    μ\nend\n\n\"\"\"\nModified Chebyshev moments of the first kind:\n\n```math\n    \\\\int_^a T_n(x) {\\\\rm\\\\,d}x.\n```\n\"\"\"\nfunction chebyshevmoments1(::Type{T}, N::Int, a::T) where T\n    μ = zeros(T, N)\n    μ[1] = a\n    μ[2] = a^2/2\n    θ = acos(a)\n    for i = 2:N-1\n        @inbounds μ[i+1] = (cos((i+1)*θ)/(i+1) - cos((i-1)*θ)/(i-1))/2\n    end\n    μ\nend\n\nfunction chebyshevmoments1(::Type{T}, N::Int, a::NTuple{L, T}, w::NTuple{M, T}) where {T, L, M}\n    @assert L == M+1\n    @assert M > 0\n    μ = zeros(T, N)\n    for k in 1:M\n        μ .+= w[k]*(chebyshevmoments1(T, N, a[k+1]) - chebyshevmoments1(T, N, a[k]))\n    end\n    μ\nend\n\n\"\"\"\nModified Chebyshev moments of the first kind with respect to the Jacobi weight:\n\n```math\n    \\\\int_{-1}^{+1} T_n(x) (1-x)^\\\\alpha(1+x)^\\\\beta{\\\\rm\\\\,d}x.\n```\n\"\"\"\nfunction chebyshevjacobimoments1(::Type{T}, N::Int, α, β) where T\n    μ = zeros(T, N)\n    N > 0 && (μ[1] = 2 .^ (α+β+1)*beta(α+1,β+1))\n    if N > 1\n        μ[2] = μ[1]*(β-α)/(α+β+2)\n        for i=1:N-2\n            @inbounds μ[i+2] = (2(β-α)*μ[i+1]-(α+β-i+2)*μ[i])/(α+β+i+2)\n        end\n    end\n    μ\nend\n\n\"\"\"\nModified Chebyshev moments of the first kind with respect to the logarithmic weight:\n\n```math\n    \\\\int_{-1}^{+1} T_n(x) \\\\log\\\\left(\\\\frac{2}{1-x}\\\\right){\\\\rm\\\\,d}x.\n```\n\"\"\"\nfunction chebyshevlogmoments1(::Type{T}, N::Int) where T\n    μ = zeros(T, N)\n    N > 0 && (μ[1] = two(T))\n    if N > 1\n        μ[2] = one(T)\n        for i=1:N-2\n            cst = isodd(i) ? T(4)/T(4-i^2) : T(4)/T(1-i^2)\n            @inbounds μ[i+2] = ((i-2)*μ[i]+cst)/(i+2)\n        end\n    end\n    μ\nend\n\n\"\"\"\nModified Chebyshev moments of the first kind with respect to the log-Chebyshev weight:\n\n```math\n    \\\\int_{-1}^{+1} T_n(x) \\\\log\\\\left(\\\\frac{2}{1-x}\\\\right)\\\\frac{{\\\\rm d}x}{\\\\sqrt{1-x^2}}.\n```\n\"\"\"\nfunction chebyshevlogchebyshevmoments1(::Type{T}, N::Int) where T\n    μ = zeros(T, N)\n    N > 0 && (μ[1] = 2*log(T(2))*π)\n    if N > 1\n        for i=1:N-1\n            @inbounds μ[i+1] = T(π)/i\n        end\n    end\n    μ\nend\n\n\"\"\"\nModified Chebyshev moments of the first kind with respect to the absolute value weight:\n\n```math\n    \\\\int_{-1}^{+1} T_n(x) |x|{\\\\rm\\\\,d}x.\n```\n\"\"\"\nfunction chebyshevabsmoments1(::Type{T}, N::Int) where T\n    μ = zeros(T, N)\n    if N > 0\n        for i=0:4:N-1\n            @inbounds μ[i+1] = -T(1)/T((i÷2)^2-1)\n        end\n    end\n    μ\nend\n\n\"\"\"\nModified Chebyshev moments of the second kind:\n\n```math\n    \\\\int_{-1}^{+1} U_n(x) {\\\\rm\\\\,d}x.\n```\n\"\"\"\nfunction chebyshevmoments2(::Type{T}, N::Int) where T\n    μ = zeros(T, N)\n    for i = 0:2:N-1\n        @inbounds μ[i+1] = two(T)/T(i+1)\n    end\n    μ\nend\n\n\"\"\"\nModified Chebyshev moments of the second kind with respect to the Jacobi weight:\n\n```math\n    \\\\int_{-1}^{+1} U_n(x) (1-x)^\\\\alpha(1+x)^\\\\beta{\\\\rm\\\\,d}x.\n```\n\"\"\"\nfunction chebyshevjacobimoments2(::Type{T}, N::Int, α, β) where T\n    μ = zeros(T, N)\n    N > 0 && (μ[1] = 2 .^ (α+β+1)*beta(α+1,β+1))\n    if N > 1\n        μ[2] = 2μ[1]*(β-α)/(α+β+2)\n        for i=1:N-2\n            @inbounds μ[i+2] = (2(β-α)*μ[i+1]-(α+β-i)*μ[i])/(α+β+i+2)\n        end\n    end\n    μ\nend\n\n\"\"\"\nModified Chebyshev moments of the second kind with respect to the logarithmic weight:\n\n```math\n    \\\\int_{-1}^{+1} U_n(x) \\\\log\\\\left(\\\\frac{2}{1-x}\\\\right){\\\\rm\\\\,d}x.\n```\n\"\"\"\nfunction chebyshevlogmoments2(::Type{T}, N::Int) where T\n    μ = chebyshevlogmoments1(T, N)\n    if N > 1\n        μ[2] *= two(T)\n        for i=1:N-2\n            @inbounds μ[i+2] = 2μ[i+2] + μ[i]\n        end\n    end\n    μ\nend\n\n\"\"\"\nModified Chebyshev moments of the second kind with respect to the absolute value weight:\n\n```math\n    \\\\int_{-1}^{+1} U_n(x) |x|{\\\\rm\\\\,d}x.\n```\n\"\"\"\nfunction chebyshevabsmoments2(::Type{T}, N::Int) where T\n    μ = chebyshevabsmoments1(T, N)\n    if N > 1\n        μ[2] *= two(T)\n        for i=1:N-2\n            @inbounds μ[i+2] = 2μ[i+2] + μ[i]\n        end\n    end\n    μ\nend\n\nfunction sphrand(::Type{T}, m::Int, n::Int) where T\n    A = zeros(T, m, n)\n    for i = 1:m\n        A[i,1] = rand(T)\n    end\n    for j = 1:n÷2\n        for i = 1:m-j\n            A[i,2j] = rand(T)\n            A[i,2j+1] = rand(T)\n        end\n    end\n    A\nend\n\nfunction sphrandn(::Type{T}, m::Int, n::Int) where T\n    A = zeros(T, m, n)\n    for i = 1:m\n        A[i,1] = randn(T)\n    end\n    for j = 1:n÷2\n        for i = 1:m-j\n            A[i,2j] = randn(T)\n            A[i,2j+1] = randn(T)\n        end\n    end\n    A\nend\n\nfunction sphones(::Type{T}, m::Int, n::Int) where T\n    A = zeros(T, m, n)\n    for i = 1:m\n        A[i,1] = one(T)\n    end\n    for j = 1:n÷2\n        for i = 1:m-j\n            A[i,2j] = one(T)\n            A[i,2j+1] = one(T)\n        end\n    end\n    A\nend\n\nsphzeros(::Type{T}, m::Int, n::Int) where T = zeros(T, m, n)\n\n\"\"\"\nPointwise evaluation of real orthonormal spherical harmonic:\n\n```math\nY_\\\\ell^m(\\\\theta,\\\\varphi) = (-1)^{|m|}\\\\sqrt{(\\\\ell+\\\\frac{1}{2})\\\\frac{(\\\\ell-|m|)!}{(\\\\ell+|m|)!}} P_\\\\ell^{|m|}(\\\\cos\\\\theta) \\\\sqrt{\\\\frac{2-\\\\delta_{m,0}}{2\\\\pi}} \\\\left\\\\{\\\\begin{array}{ccc} \\\\cos m\\\\varphi & {\\\\rm for} & m \\\\ge 0,\\\\\\\\ \\\\sin(-m\\\\varphi) & {\\\\rm for} & m < 0.\\\\end{array}\\\\right.\n```\n\"\"\"\nsphevaluate(θ, φ, L, M) = sphevaluatepi(θ/π, φ/π, L, M)\n\nsphevaluatepi(θ::Number, φ::Number, L::Integer, M::Integer) = sphevaluatepi(θ, L, M)*sphevaluatepi(φ, M)\n\nfunction sphevaluatepi(θ::Number, L::Integer, M::Integer)\n    ret = one(θ)/sqrt(two(θ))\n    if M < 0 M = -M end\n    c, s = cospi(θ), sinpi(θ)\n    for m = 1:M\n        ret *= sqrt((m+half(θ))/m)*s\n    end\n    tc = two(c)*c\n\n    if L == M\n        return ret\n    elseif L == M+1\n        return sqrt(two(θ)*M+3)*c*ret\n    else\n        temp = ret\n        ret *= sqrt(two(θ)*M+3)*c\n        for l = M+1:L-1\n            ret, temp = (sqrt(l+half(θ))*tc*ret - sqrt((l-M)*(l+M)/(l-half(θ)))*temp)/sqrt((l-M+1)*(l+M+1)/(l+3half(θ))), ret\n        end\n        return ret\n    end\nend\n\nsphevaluatepi(φ::Number, M::Integer) = sqrt((two(φ)-δ(M, 0))/(two(φ)*π))*(M ≥ 0 ? cospi(M*φ) : sinpi(-M*φ))\n\nfunction sphvrand(::Type{T}, m::Int, n::Int) where T\n    A = zeros(T, m, n)\n    for i = 1:m-1\n        A[i,1] = rand(T)\n    end\n    for j = 1:n÷2\n        for i = 1:m-j+1\n            A[i,2j] = rand(T)\n            A[i,2j+1] = rand(T)\n        end\n    end\n    A\nend\n\nfunction sphvrandn(::Type{T}, m::Int, n::Int) where T\n    A = zeros(T, m, n)\n    for i = 1:m-1\n        A[i,1] = randn(T)\n    end\n    for j = 1:n÷2\n        for i = 1:m-j+1\n            A[i,2j] = randn(T)\n            A[i,2j+1] = randn(T)\n        end\n    end\n    A\nend\n\nfunction sphvones(::Type{T}, m::Int, n::Int) where T\n    A = zeros(T, m, n)\n    for i = 1:m-1\n        A[i,1] = one(T)\n    end\n    for j = 1:n÷2\n        for i = 1:m-j+1\n            A[i,2j] = one(T)\n            A[i,2j+1] = one(T)\n        end\n    end\n    A\nend\n\nsphvzeros(::Type{T}, m::Int, n::Int) where T = sphzeros(T, m, n)\n\nfunction diskrand(::Type{T}, m::Int, n::Int) where T\n    A = zeros(T, m, n)\n    for i = 1:m\n        A[i,1] = rand(T)\n    end\n    for j = 1:n÷2\n        for i = 1:m-(j+1)÷2\n            A[i,2j] = rand(T)\n            A[i,2j+1] = rand(T)\n        end\n    end\n    A\nend\n\nfunction diskrandn(::Type{T}, m::Int, n::Int) where T\n    A = zeros(T, m, n)\n    for i = 1:m\n        A[i,1] = randn(T)\n    end\n    for j = 1:n÷2\n        for i = 1:m-(j+1)÷2\n            A[i,2j] = randn(T)\n            A[i,2j+1] = randn(T)\n        end\n    end\n    A\nend\n\nfunction diskones(::Type{T}, m::Int, n::Int) where T\n    A = zeros(T, m, n)\n    for i = 1:m\n        A[i,1] = one(T)\n    end\n    for j = 1:n÷2\n        for i = 1:m-(j+1)÷2\n            A[i,2j] = one(T)\n            A[i,2j+1] = one(T)\n        end\n    end\n    A\nend\n\ndiskzeros(::Type{T}, m::Int, n::Int) where T = zeros(T, m, n)\n\nfunction trirand(::Type{T}, m::Int, n::Int) where T\n    A = zeros(T, m, n)\n    for j = 1:n\n        for i = 1:m+1-j\n            A[i,j] = rand(T)\n        end\n    end\n    A\nend\n\nfunction trirandn(::Type{T}, m::Int, n::Int) where T\n    A = zeros(T, m, n)\n    for j = 1:n\n        for i = 1:m+1-j\n            A[i,j] = randn(T)\n        end\n    end\n    A\nend\n\nfunction triones(::Type{T}, m::Int, n::Int) where T\n    A = zeros(T, m, n)\n    for j = 1:n\n        for i = 1:m+1-j\n            A[i,j] = one(T)\n        end\n    end\n    A\nend\n\ntrizeros(::Type{T}, m::Int, n::Int) where T = zeros(T, m, n)\n\nconst rectdiskrand = trirand\nconst rectdiskrandn = trirandn\nconst rectdiskones = triones\nconst rectdiskzeros = trizeros\n\n\"\"\"\nPointwise evaluation of triangular harmonic:\n\n```math\n\\\\tilde{P}_{\\\\ell,m}^{(\\\\alpha,\\\\beta,\\\\gamma)}(x,y).\n```\n\"\"\"\ntrievaluate(x, y, L, M, α, β, γ) = trievaluate(x, L, M, α, β, γ)*trievaluate(x, y, M, β, γ)\n\nfunction trievaluate(x::Number, L::Integer, M::Integer, α::Number, β::Number, γ::Number)\n\nend\n\nfunction trievaluate(x::Number, y::Number, M::Integer, β::Number, γ::Number)\n\nend\n\nfunction tetrand(::Type{T}, l::Int, m::Int, n::Int) where T\n    A = zeros(T, l, m, n)\n    for k = 1:n\n        for j = 1:m+1-k\n            for i = 1:l+2-k-j\n                A[i,j,k] = rand(T)\n            end\n        end\n    end\n    A\nend\n\nfunction tetrandn(::Type{T}, l::Int, m::Int, n::Int) where T\n    A = zeros(T, l, m, n)\n    for k = 1:n\n        for j = 1:m+1-k\n            for i = 1:l+2-k-j\n                A[i,j,k] = randn(T)\n            end\n        end\n    end\n    A\nend\n\nfunction tetones(::Type{T}, l::Int, m::Int, n::Int) where T\n    A = zeros(T, l, m, n)\n    for k = 1:n\n        for j = 1:m+1-k\n            for i = 1:l+2-k-j\n                A[i,j,k] = one(T)\n            end\n        end\n    end\n    A\nend\n\ntetzeros(::Type{T}, l::Int, m::Int, n::Int) where T = zeros(T, l, m, n)\n\nfunction spinsphrand(::Type{T}, m::Int, n::Int, s::Int) where T\n    A = zeros(T, m, n)\n    as = abs(s)\n    for i = 1:m-as\n        A[i,1] = rand(T)\n    end\n    for j = 1:n÷2\n        for i = 1:m-max(j, as)\n            A[i,2j] = rand(T)\n            A[i,2j+1] = rand(T)\n        end\n    end\n    A\nend\n\nfunction spinsphrandn(::Type{T}, m::Int, n::Int, s::Int) where T\n    A = zeros(T, m, n)\n    as = abs(s)\n    for i = 1:m-as\n        A[i,1] = randn(T)\n    end\n    for j = 1:n÷2\n        for i = 1:m-max(j, as)\n            A[i,2j] = randn(T)\n            A[i,2j+1] = randn(T)\n        end\n    end\n    A\nend\n\nfunction spinsphones(::Type{T}, m::Int, n::Int, s::Int) where T\n    A = zeros(T, m, n)\n    as = abs(s)\n    for i = 1:m-as\n        A[i,1] = one(T)\n    end\n    for j = 1:n÷2\n        for i = 1:m-max(j, as)\n            A[i,2j] = one(T)\n            A[i,2j+1] = one(T)\n        end\n    end\n    A\nend\n\nspinsphzeros(::Type{T}, m::Int, n::Int) where T = zeros(T, m, n)\n"
  },
  {
    "path": "src/toeplitzhankel.jl",
    "content": "\"\"\"\nRepresent a scaled Toeplitz∘Hankel matrix:\n\n    DL(T∘H)DR\n\nwhere the Hankel matrix `H` is non-negative definite, via\n\n    ∑_{k=1}^r Diagonal(L[:,k])*T*Diagonal(R[:,k])\n\nwhere `L` and `R` are determined by doing a rank-r pivoted Cholesky decomposition of `H`, which in low rank form is\n\n    H ≈ ∑_{k=1}^r C[:,k]C[:,k]'\n\nso that `L[:,k] = DL*C[:,k]` and `R[:,k] = DR*C[:,k]`.\n\nThis allows a Cholesky decomposition in 𝒪(K²N) operations and 𝒪(KN) storage, K = log N log ɛ⁻¹.\nThe tuple storage allows plans applied to each dimension.\n\"\"\"\nstruct ToeplitzHankelPlan{S, N, N1, LowR, TP, Dims} <: Plan{S}\n    T::TP # A length M Vector or Tuple of ToeplitzPlan\n    L::LowR  # A length M Vector or Tuple of Matrices storing low rank factors of L\n    R::LowR # A length M Vector or Tuple of Matrices storing low rank factors of R\n    tmp::Array{S,N1} # A larger dimensional array to transform each scaled array all-at-once\n    dims::Dims # A length M Vector or Tuple of Int storing the dimensions acted on\n    function ToeplitzHankelPlan{S,N,N1,LowR,TP,Dims}(T::TP, L::LowR, R::LowR, dims) where {S,N,N1,LowR,TP,Dims}\n        tmp = Array{S}(undef, max.(size.(T)...)...)\n        new{S,N,N1,LowR,TP,Dims}(T, L, R, tmp, dims)\n    end\nend\n\n\nToeplitzHankelPlan{S,N,M}(T::TP, L::LowR, R::LowR, dims::Dims) where {S,N,M,LowR,TP,Dims} = ToeplitzHankelPlan{S,N,M,LowR,TP,Dims}(T, L, R, dims)\nToeplitzHankelPlan{S,N}(T, L, R, dims) where {S,N} = ToeplitzHankelPlan{S,N,N+1}(T, L, R, dims)\nToeplitzHankelPlan(T::ToeplitzPlan{S,M}, L::Matrix, R::Matrix, dims=1) where {S,M} = ToeplitzHankelPlan{S,M-1,M}((T,), (L,), (R,), dims)\n\nsize(TH::ToeplitzHankelPlan) = size(first(TH.T))\n\n\n_reshape_broadcast(d, R, ::Val{N}, M) where N = reshape(R,ntuple(k -> k == d ? size(R,1) : 1, Val(N))...,M)\nfunction _th_applymul!(d, v::AbstractArray{<:Any,N}, T, L, R, tmp) where N\n    M = size(R,2)\n    ax = (axes(v)..., OneTo(M))\n    tmp[ax...] .=  _reshape_broadcast(d, R, Val(N), M) .* v\n    T * view(tmp, ax...)\n    view(tmp,ax...) .*= _reshape_broadcast(d, L, Val(N), M)\n    sum!(v, view(tmp,ax...))\nend\n\n\nfunction *(P::ToeplitzHankelPlan{<:Any,N}, v::AbstractArray{<:Any,N}) where N\n    for (R,L,T,d) in zip(P.R,P.L,P.T,P.dims)\n        _th_applymul!(d, v, T, L, R, P.tmp)\n    end\n    v\nend\n\n*(P::ToeplitzHankelPlan, v::AbstractArray) = error(\"plan applied to wrong-sized array\")\n\n\n# partial cholesky for a Hankel matrix\n\nfunction hankel_partialchol(v::Vector{T}) where T\n    # Assumes positive definite\n    σ = T[]\n    n = isempty(v) ? 0 : (length(v)+2) ÷ 2\n    C = Matrix{T}(undef, n, n)\n    d = v[1:2:end] # diag of H\n    @assert length(v) ≥ 2n-1\n    reltol = maximum(abs,d)*eps(T)*log(n)\n    r = 0\n    for k = 1:n\n        mx,idx = findmax(d)\n        if mx ≤ reltol break end\n        push!(σ, inv(mx))\n        C[:,k] .= view(v,idx:n+idx-1)\n        for j = 1:k-1\n            nCjidxσj = -C[idx,j]*σ[j]\n            LinearAlgebra.axpy!(nCjidxσj, view(C,:,j), view(C,:,k))\n        end\n        @inbounds for p=1:n\n            d[p] -= C[p,k]^2/mx\n        end\n        r += 1\n    end\n    for k=1:length(σ) rmul!(view(C,:,k), sqrt(σ[k])) end\n    C[:,1:r]\nend\n\n# cholesky for D .* H .* D'\nfunction hankel_partialchol(v::Vector, D::AbstractVector)\n    T = promote_type(eltype(v), eltype(D))\n    # Assumes positive definite\n    σ = T[]\n    n = isempty(v) ? 0 : (length(v)+2) ÷ 2\n    C = Matrix{T}(undef, n, 100)\n    d = v[1:2:end] .* D.^2 # diag of D .* H .* D'\n    @assert length(v) ≥ 2n-1\n    reltol = maximum(abs,d)*eps(T)*log(n)\n    r = 0\n    for k = 1:n\n        mx,idx = findmax(d)\n        if mx ≤ reltol break end\n        push!(σ, inv(mx))\n        C[:,k] .= view(v,idx:n+idx-1) .*D.*D[idx]\n        for j = 1:k-1\n            nCjidxσj = -C[idx,j]*σ[j]\n            LinearAlgebra.axpy!(nCjidxσj, view(C,:,j), view(C,:,k))\n        end\n        @inbounds for p=1:n\n            d[p] -= C[p,k]^2/mx\n        end\n        r += 1\n    end\n    r == 100 && error(\"ranks more than 100 not yet supported\")\n    for k=1:length(σ) rmul!(view(C,:,k), sqrt(σ[k])) end\n    C[:,1:r]\nend\n\n\n\n# Diagonally-scaled Toeplitz∘Hankel polynomial transforms\n\n\n\nstruct ChebyshevToLegendrePlanTH{S,TH<:ToeplitzHankelPlan{S}} <: Plan{S}\n    toeplitzhankel::TH\nend\n\nfunction *(P::ChebyshevToLegendrePlanTH, v::AbstractVector{S}) where S\n    n = length(v)\n    iszero(n) && return v\n    ret = zero(S)\n    @inbounds for k = 1:2:n\n        ret += -v[k]/(k*(k-2))\n    end\n    v[1] = ret\n    P.toeplitzhankel*view(v,2:n)\n    v\nend\n\nfunction _cheb2leg_rescale1!(V::AbstractArray{S}, Rpre, Rpost, d) where S\n    m = size(V,d)\n    for Ipost in Rpost, Ipre in Rpre\n        ret = zero(S)\n        @inbounds for k = 1:2:m\n            ret += -V[Ipre,k,Ipost]/(k*(k-2))\n        end\n        V[Ipre,1,Ipost] = ret\n    end\n    V\nend\n\n_dropfirstdim(d::Int) = ()\n_dropfirstdim(d::Int, m, szs...) = ((d == 1 ? 2 : 1):m, _dropfirstdim(d-1, szs...)...)\n\nfunction *(P::ChebyshevToLegendrePlanTH, V::AbstractArray)\n    m,n = size(V)\n    tmp = P.toeplitzhankel.tmp\n    for (d,R,L,T) in zip(P.toeplitzhankel.dims,P.toeplitzhankel.R,P.toeplitzhankel.L,P.toeplitzhankel.T)\n        Rpre = CartesianIndices(axes(V)[1:d-1])\n        Rpost = CartesianIndices(axes(V)[d+1:end])\n        _cheb2leg_rescale1!(V, Rpre, Rpost, d)\n        _th_applymul!(d, view(V, _dropfirstdim(d, size(V)...)...), T, L, R, tmp)\n    end\n    V\nend\n\n_add1tod(d::Integer, a, b...) = d == 1 ? (a+1, b...) : (a, _add1tod(d-1, b...)...)\n_add1tod(d, a, b...) = _add1tod(first(d), a, b...)\nsize(P::ChebyshevToLegendrePlanTH) = Base.front(_add1tod(P.toeplitzhankel.dims, size(first(P.toeplitzhankel.T))...))\ninv(P::ChebyshevToLegendrePlanTH{T}) where T = plan_th_leg2cheb!(T, size(P), P.toeplitzhankel.dims)\n\n\nfunction _leg2chebTH_TLC(::Type{S}, mn, d) where S\n    n = mn[d]\n    λ = Λ.(0:half(real(S)):n-1)\n    t = zeros(S,n)\n    t[1:2:end] .= 2 .* view(λ, 1:2:n) ./ π\n    C = hankel_partialchol(λ)\n    T = plan_uppertoeplitz!(t, (mn..., size(C,2)), d)\n    L = copy(C)\n    if n > 0\n        L[1,:] ./= 2\n    end\n    T,L,C\nend\n\nfunction _leg2chebuTH_TLC(::Type{S}, mn, d) where {S}\n    n = mn[d]\n    S̃ = real(S)\n    λ = Λ.(0:half(S̃):n-1)\n    t = zeros(S,n)\n    t[1:2:end] = λ[1:2:n]./(((1:2:n).-2))\n    h = λ./((1:2n-1).+1)\n    C = hankel_partialchol(h)\n    T = plan_uppertoeplitz!(-2t/π, (mn..., size(C,2)), d)\n    (T, (1:n) .* C, C)\nend\n\nfor f in (:leg2cheb, :leg2chebu)\n    plan = Symbol(\"plan_th_\", f, \"!\")\n    TLC = Symbol(\"_\", f, \"TH_TLC\")\n    @eval begin\n        $plan(::Type{S}, mn::NTuple{N,Int}, dims::Int) where {S,N} = ToeplitzHankelPlan($TLC(S, mn, dims)..., dims)\n        function $plan(::Type{S}, mn::NTuple{N,Int}, dims) where {S,N}\n            TLCs = $TLC.(S, Ref(mn), dims)\n            ToeplitzHankelPlan{S,N}(map(first, TLCs), map(TLC -> TLC[2], TLCs), map(last, TLCs), dims)\n        end\n    end\nend\n\n###\n# th_cheb2leg\n###\n\n_sub_dim_by_one(d) = ()\n_sub_dim_by_one(d, m, n...) = (isone(d) ? m-1 : m, _sub_dim_by_one(d-1, n...)...)\n\nfunction _cheb2legTH_TLC(::Type{S}, mn, d) where S\n    n = mn[d]\n    t = zeros(S,max(0,n-1))\n    S̃ = real(S)\n    if n > 1\n        t[1:2:end] = Λ.(0:one(S̃):div(n-2,2), -half(S̃), one(S̃))\n    end\n    h = Λ.(1:half(S̃):n-1, zero(S̃), 3half(S̃))\n    D = 1:n-1\n    DL = (3half(S̃):n-half(S̃)) ./ D\n    DR = -(one(S̃):n-one(S̃)) ./ (4 .* D)\n    C = hankel_partialchol(h, D)\n    T = plan_uppertoeplitz!(t, (_sub_dim_by_one(d, mn...)..., size(C,2)), d)\n    T, DL .* C, DR .* C\nend\n\nplan_th_cheb2leg!(::Type{S}, mn::NTuple{N,Int}, dims::Int) where {S,N} = ChebyshevToLegendrePlanTH(ToeplitzHankelPlan(_cheb2legTH_TLC(S, mn, dims)..., dims))\n\nfunction plan_th_cheb2leg!(::Type{S}, mn::NTuple{N,Int}, dims) where {S,N}\n    TLCs = _cheb2legTH_TLC.(S, Ref(mn), dims)\n    ChebyshevToLegendrePlanTH(ToeplitzHankelPlan{S,N}(map(first, TLCs), map(TLC -> TLC[2], TLCs), map(last, TLCs), dims))\nend\n\n\n###\n# th_ultra2ultra\n###\n\n# The second case handles zero\nisapproxinteger(::Integer) = true\nisapproxinteger(x) = isinteger(x) || x ≈ round(Int,x)  || x+1 ≈ round(Int,x+1)\n\n\"\"\"\n  _nearest_jacobi_par(α, γ)\n\nreturns a number that is an integer different than γ but less than 1 away from α.\n\"\"\"\nfunction _nearest_jacobi_par(α::T, γ::T) where T\n    ret = isapproxinteger(α-γ) ? α : round(Int,α,RoundDown) + mod(γ,1)\n    ret ≤ -1 ? ret + 1 : ret\nend\n_nearest_jacobi_par(α::T, ::T) where T<:Integer = α\n_nearest_jacobi_par(α, γ) = _nearest_jacobi_par(promote(α,γ)...)\n\n\nstruct Ultra2UltraPlanTH{T, Plans, Dims} <: Plan{T}\n    plans::Plans\n    λ₁::T\n    λ₂::T\n    dims::Dims\nend\n\nfunction *(P::Ultra2UltraPlanTH, A::AbstractArray)\n    ret = A\n    if isapproxinteger(P.λ₂ - P.λ₁)\n        _ultra2ultra_integerinc!(ret, P.λ₁, P.λ₂, P.dims)\n    else\n        for p in P.plans\n            ret = p*ret\n        end\n        c = _nearest_jacobi_par(P.λ₁, P.λ₂)\n\n        _ultra2ultra_integerinc!(ret, c, P.λ₂, P.dims)\n    end\nend\n\nfunction _ultra2ultraTH_TLC(::Type{S}, mn, λ₁, λ₂, d) where {S}\n    n = mn[d]\n    @assert abs(λ₁-λ₂) < 1\n    S̃ = real(S)\n    DL = (zero(S̃):n-one(S̃)) .+ λ₂\n    jk = 0:half(S̃):n-1\n    t = zeros(S,n)\n    t[1:2:n] = Λ.(jk,λ₁-λ₂,one(S̃))[1:2:n]\n    h = Λ.(jk,λ₁,λ₂+one(S̃))\n    lmul!(gamma(λ₂)/gamma(λ₁),h)\n    C = hankel_partialchol(h)\n    T = plan_uppertoeplitz!(lmul!(inv(gamma(λ₁-λ₂)),t), (mn..., size(C,2)), d)\n    T, DL .* C, C\nend\n\n_good_plan_th_ultra2ultra!(::Type{S}, mn, λ₁, λ₂, dims::Int) where S = ToeplitzHankelPlan(_ultra2ultraTH_TLC(S, mn, λ₁, λ₂, dims)..., dims)\n\nfunction _good_plan_th_ultra2ultra!(::Type{S}, mn::NTuple{2,Int}, λ₁, λ₂, dims::NTuple{2,Int}) where S\n    T1,L1,C1 = _ultra2ultraTH_TLC(S, mn, λ₁, λ₂, 1)\n    T2,L2,C2 = _ultra2ultraTH_TLC(S, mn, λ₁, λ₂, 2)\n    ToeplitzHankelPlan{S,2}((T1,T2), (L1,L2), (C1,C2), dims)\nend\n\n\n\nfunction plan_th_ultra2ultra!(::Type{S}, mn, λ₁, λ₂, dims) where {S}\n    c = _nearest_jacobi_par(λ₁, λ₂)\n\n    if isapproxinteger(λ₂ - λ₁)\n        # TODO: don't make extra plan\n        plans = typeof(_good_plan_th_ultra2ultra!(S, mn, λ₂+0.1, λ₂, dims))[]\n    else\n        plans = [_good_plan_th_ultra2ultra!(S, mn, λ₁, c, dims)]\n    end\n\n    Ultra2UltraPlanTH(plans, λ₁, λ₂, dims)\nend\n\nfunction _ultra_raise!(B, λ)\n    m, n = size(B, 1), size(B, 2)\n\n    if m > 1\n        @inbounds for j = 1:n\n            for i = 1:m-2\n                Bij = λ / (i+λ-1) * B[i,j]\n                Bij += -λ / (i+λ+1) * B[i+2,j]\n                B[i,j] = Bij\n            end\n            B[m-1,j] = λ / (m+λ-2)*B[m-1,j]\n            B[m,j] = λ / (m+λ-1)*B[m,j]\n        end\n    end\n    B\nend\n\nfunction _ultra_lower!(B, λ)\n    m, n = size(B, 1), size(B, 2)\n\n    if m > 1\n        @inbounds for j = 1:n\n            B[m,j] = (m+λ-1)/λ * B[m,j]\n            B[m-1,j] = (m+λ-2)/λ *B[m-1,j]\n            for i = m-2:-1:1\n                Bij = B[i,j] + λ / (i+λ+1) * B[i+2,j]\n                B[i,j] = (i+λ-1)/λ * Bij\n            end  \n        end\n    end\n    B\nend\n\n\n\nfunction _ultra_raise!(x, λ, dims)\n    for d in dims\n        if d == 1\n            _ultra_raise!(x, λ)\n        else\n            _ultra_raise!(x', λ)\n        end\n    end\n    x\nend\n\nfunction _ultra_lower!(x, λ, dims)\n    for d in dims\n        if d == 1\n            _ultra_lower!(x, λ-1)\n        else\n            _ultra_lower!(x', λ-1)\n        end\n    end\n    x\nend\n\nfunction _ultra2ultra_integerinc!(x, λ₁, λ₂, dims)\n    while !(λ₁ ≈ λ₂)\n        if λ₂ > λ₁\n            _ultra_raise!(x, λ₁, dims)\n            λ₁ += 1\n        else\n            _ultra_lower!(x, λ₁, dims)\n            λ₁ -= 1\n        end\n    end\n    x\nend\n\n###\n# th_jac2jac\n###\n\n\nfunction _lmul!(A::Bidiagonal, B::AbstractVecOrMat)\n    @assert A.uplo == 'U'\n    \n    m, n = size(B, 1), size(B, 2)\n    if m != size(A, 1)\n        throw(DimensionMismatch(\"right hand side B needs first dimension of size $(size(A,1)), has size $m\"))\n    end\n    @inbounds for j = 1:n\n        for i = 1:m-1\n            Bij = A.dv[i]*B[i,j]\n            Bij += A.ev[i]*B[i+1,j]\n            B[i,j] = Bij\n        end\n        B[m,j] = A.dv[m]*B[m,j]\n    end\n    B\nend\n\nstruct Jac2JacPlanTH{T, Plans, Dims} <: Plan{T}\n    plans::Plans\n    α::T\n    β::T\n    γ::T\n    δ::T\n    dims::Dims\nend\n\nJac2JacPlanTH(plans, α, β, γ, δ, dims) = Jac2JacPlanTH(plans, promote(α, β, γ, δ)..., dims)\n\nfunction *(P::Jac2JacPlanTH, A::AbstractArray)\n    if P.α + P.β ≤ -1\n        _jacobi_raise_a!(A, P.α, P.β, P.dims)\n        c,d = _nearest_jacobi_par(P.α+1, P.γ), _nearest_jacobi_par(P.β, P.δ)\n    else\n        c,d = _nearest_jacobi_par(P.α, P.γ), _nearest_jacobi_par(P.β, P.δ)\n    end\n\n    ret = A\n    for p in P.plans\n        ret = p*ret\n    end\n\n    _jac2jac_integerinc!(ret, c, d, P.γ, P.δ, P.dims)\nend\n\nfunction alternatesign!(v)\n    @inbounds for k = 2:2:length(v)\n        v[k] = -v[k]\n    end\n    v\nend\n\nfunction _jac2jacTH_TLC(::Type{S}, mn, α, β, γ, δ, d) where {S}\n    n = mn[d]\n    @assert α+β > -1\n    if β == δ\n        @assert abs(α-γ) < 1\n        jk = 0:n-1\n        DL = (2jk .+ γ .+ β .+ 1).*Λ.(jk,γ+β+1,β+1)\n        t = convert(AbstractVector{S}, Λ.(jk, α-γ,1))\n        h = Λ.(0:2n-2,α+β+1,γ+β+2)\n        DR = Λ.(jk,β+1,α+β+1)./gamma(α-γ)\n        C = hankel_partialchol(h)\n        T = plan_uppertoeplitz!(t, (mn..., size(C,2)), d)\n    elseif α == γ\n        @assert abs(β-δ) < 1\n        jk = 0:n-1\n        DL = (2jk .+ δ .+ α .+ 1).*Λ.(jk,δ+α+1,α+1)\n        h = Λ.(0:2n-2,α+β+1,δ+α+2)\n        DR = Λ.(jk,α+1,α+β+1)./gamma(β-δ)\n        t = alternatesign!(convert(AbstractVector{S}, Λ.(jk,β-δ,1)))\n        C = hankel_partialchol(h)\n        T = plan_uppertoeplitz!(t, (mn..., size(C,2)), d)\n    else\n        throw(ArgumentError(\"Cannot create Toeplitz dot Hankel, use a sequence of plans.\"))\n    end\n\n    (T, DL .* C, DR .* C)\nend\n\n_good_plan_th_jac2jac!(::Type{S}, mn, α, β, γ, δ, dims::Int) where S = ToeplitzHankelPlan(_jac2jacTH_TLC(S, mn, α, β, γ, δ, dims)..., dims)\n\nfunction _good_plan_th_jac2jac!(::Type{S}, mn::NTuple{2,Int}, α, β, γ, δ, dims::NTuple{2,Int}) where S\n    T1,L1,C1 = _jac2jacTH_TLC(S, mn, α, β, γ, δ, 1)\n    T2,L2,C2 = _jac2jacTH_TLC(S, mn, α, β, γ, δ, 2)\n    ToeplitzHankelPlan{S,2}((T1,T2), (L1,L2), (C1,C2), dims)\nend\n\n\n\nfunction plan_th_jac2jac!(::Type{S}, mn, α, β, γ, δ, dims) where {S}\n    if α + β ≤ -1\n        c,d = _nearest_jacobi_par(α+1, γ), _nearest_jacobi_par(β, δ)\n    else\n        c,d = _nearest_jacobi_par(α, γ), _nearest_jacobi_par(β, δ)\n    end\n\n    if isapproxinteger(β - δ) && isapproxinteger(α-γ)\n        # TODO: don't make extra plan\n        plans = typeof(_good_plan_th_jac2jac!(S, mn, α+0.1, β, α, β, dims))[]\n    elseif isapproxinteger(α - γ) || isapproxinteger(β - δ)\n        if α + β ≤ -1\n            # avoid degenerecies\n            plans = [_good_plan_th_jac2jac!(S, mn, α+1, β, c, d, dims)]\n        else\n            plans = [_good_plan_th_jac2jac!(S, mn, α, β, c, d, dims)]\n        end\n    else\n        if α + β ≤ -1\n            plans = [_good_plan_th_jac2jac!(S, mn, α+1, β, α+1, d, dims), _good_plan_th_jac2jac!(S, mn, α+1, d, c, d, dims)]\n        else\n            plans = [_good_plan_th_jac2jac!(S, mn, α, β, α, d, dims), _good_plan_th_jac2jac!(S, mn, α, d, c, d, dims)]\n        end\n    end\n\n    Jac2JacPlanTH(plans, α, β, γ, δ, dims)\nend\n\n\nfunction _jacobi_raise_a!(B, a, b)\n    m, n = size(B, 1), size(B, 2)\n    if m > 1\n        @inbounds for j = 1:n\n            B[1,j] = B[1,j] - (1+b) / (a+b+3) * B[2,j]\n            for i = 2:m-1\n                B[i,j] = (i+a+b)/(a+b-1+2i) * B[i,j] - (i+b) / (a+b+2i+1) * B[i+1,j]\n            end\n            B[m,j] = (m+a+b)/(a+b-1+2m)*B[m,j]\n        end\n    end\n    B\nend\n\nfunction _jacobi_lower_a!(B, a, b)\n    m, n = size(B, 1), size(B, 2)\n\n    if m > 1\n        @inbounds for j = 1:n\n            B[m,j] = (a+b-1+2m)/(m+a+b) * B[m,j]\n            for i = m-1:-1:2\n                Bij = B[i,j] + (i+b) / (a+b+2i+1) * B[i+1,j]\n                B[i,j] = (a+b-1+2i)/(i+a+b)  * Bij\n            end\n            B[1,j] = B[1,j] + (1+b) / (a+b+3) * B[2,j]\n        end\n    end\n    B\nend\n\n\n\nfunction _jacobi_raise_b!(B, a, b)\n    m, n = size(B, 1), size(B, 2)\n    if m > 1\n        @inbounds for j = 1:n\n            B[1,j] = B[1,j] + (1+a) / (a+b+3) * B[2,j]\n            \n            for i = 2:m-1\n                B[i,j] = (i+a+b)/(a+b-1+2i) * B[i,j] + (i+a) / (a+b+2i+1) * B[i+1,j]\n            end\n            B[m,j] = (m+a+b)/(a+b-1+2m)*B[m,j]\n        end\n    end\n    B\nend\n\nfunction _jacobi_lower_b!(B, a, b)\n    m, n = size(B, 1), size(B, 2)\n\n    if m > 1\n        @inbounds for j = 1:n\n            B[m,j] = (a+b-1+2m)/(m+a+b) * B[m,j]\n            for i = m-1:-1:2\n                Bij = B[i,j] - (i+a) / (a+b+2i+1) * B[i+1,j]\n                B[i,j] = (a+b-1+2i)/(i+a+b)  * Bij\n            end\n            B[1,j] = B[1,j] - (1+a) / (a+b+3) * B[2,j]\n        end\n    end\n    B\nend\n\n\n\nfunction _jacobi_raise_b!(x, α, β, dims)\n    for d in dims\n        if d == 1\n            _jacobi_raise_b!(x, α, β)\n        else\n            _jacobi_raise_b!(x', α, β)\n        end\n    end\n    x\nend\nfunction _jacobi_raise_a!(x, α, β, dims)\n    for d in dims\n        if d == 1\n            _jacobi_raise_a!(x, α, β)\n        else\n            _jacobi_raise_a!(x', α, β)\n        end\n    end\n    x\nend\n\nfunction _jacobi_lower_b!(x, α, β, dims)\n    for d in dims\n        if d == 1\n            _jacobi_lower_b!(x, α, β-1)\n        else\n            _jacobi_lower_b!(x', α, β-1)\n        end\n    end\n    x\nend\nfunction _jacobi_lower_a!(x, α, β, dims)\n    for d in dims\n        if d == 1\n            _jacobi_lower_a!(x, α-1, β)\n        else\n            _jacobi_lower_a!(x', α-1, β)\n        end\n    end\n    x\nend\n\n\nfunction _jac2jac_integerinc!(x, α, β, γ, δ, dims)\n    while !(α ≈ γ && β ≈ δ)\n        if !(δ ≈ β) && δ > β\n            _jacobi_raise_b!(x, α, β, dims)\n            β += 1\n        elseif !(δ ≈ β) && δ < β\n            _jacobi_lower_b!(x, α, β, dims)\n            β -= 1\n        elseif !(γ ≈ α) && γ > α\n            _jacobi_raise_a!(x, α, β, dims)\n            α += 1\n        else\n            @assert γ < α\n            _jacobi_lower_a!(x, α, β, dims)\n            α -= 1\n        end\n    end\n    x\nend\n\n\n###\n# other routines\n###\n\nfor f in (:th_leg2cheb, :th_cheb2leg, :th_leg2chebu)\n    plan = Symbol(\"plan_\", f, \"!\")\n    @eval begin\n        $plan(arr::AbstractArray{T}, dims...) where T = $plan(T, size(arr), dims...)\n        $plan(::Type{S}, mn::NTuple{N,Int}) where {S,N} = $plan(S, mn, ntuple(identity,Val(N)))\n        $f(v, dims...) = $plan(eltype(v), size(v), dims...)*copy(v)\n    end\nend\n\nplan_th_ultra2ultra!(::Type{S}, mn::NTuple{N,Int}, λ₁, λ₂, dims::UnitRange) where {N,S} = plan_th_ultra2ultra!(S, mn, λ₁, λ₂, tuple(dims...))\nplan_th_ultra2ultra!(::Type{S}, mn::Tuple{Int}, λ₁, λ₂, dims::Tuple{Int}=(1,)) where {S} = plan_th_ultra2ultra!(S, mn, λ₁, λ₂, dims...)\nplan_th_ultra2ultra!(::Type{S}, (m,n)::NTuple{2,Int}, λ₁, λ₂) where {S} = plan_th_ultra2ultra!(S, (m,n), λ₁, λ₂, (1,2))\nplan_th_ultra2ultra!(arr::AbstractArray{T}, λ₁, λ₂, dims...) where T = plan_th_ultra2ultra!(T, size(arr), λ₁, λ₂, dims...)\nth_ultra2ultra(v, λ₁, λ₂, dims...) = plan_th_ultra2ultra!(eltype(v), size(v), λ₁, λ₂, dims...)*copy(v)\n\nplan_th_jac2jac!(::Type{S}, mn::NTuple{N,Int}, α, β, γ, δ, dims::UnitRange) where {N,S} = plan_th_jac2jac!(S, mn, α, β, γ, δ, tuple(dims...))\nplan_th_jac2jac!(::Type{S}, mn::Tuple{Int}, α, β, γ, δ, dims::Tuple{Int}=(1,)) where {S} = plan_th_jac2jac!(S, mn, α, β, γ, δ, dims...)\nplan_th_jac2jac!(::Type{S}, (m,n)::NTuple{2,Int}, α, β, γ, δ) where {S} = plan_th_jac2jac!(S, (m,n), α, β, γ, δ, (1,2))\nplan_th_jac2jac!(arr::AbstractArray{T}, α, β, γ, δ, dims...) where T = plan_th_jac2jac!(T, size(arr), α, β, γ, δ, dims...)\nth_jac2jac(v, α, β, γ, δ, dims...) = plan_th_jac2jac!(eltype(v), size(v), α, β, γ, δ, dims...)*copy(v)\n\n\n####\n# cheb2jac\n####\n\nstruct Cheb2JacPlanTH{T, Pl<:Jac2JacPlanTH{T}} <: Plan{T}\n    jac2jac::Pl\nend\n\n\nstruct Jac2ChebPlanTH{T, Pl<:Jac2JacPlanTH{T}} <: Plan{T}\n    jac2jac::Pl\nend\n\n\nfunction jac_cheb_recurrencecoefficients(T, N)\n    n = 0:N\n    h = one(T)/2\n    A = (2n .+ one(T)) ./ (n .+ one(T))\n    A[1] /= 2\n    A, Zeros(n), \n    ((n .- h) .* (n .- h) .* (2n .+ one(T))) ./ ((n .+ one(T)) .* n .* (2n .- one(T)))\nend\n\n\nfunction *(P::Cheb2JacPlanTH{T}, X::AbstractArray) where T\n    A,B,C = jac_cheb_recurrencecoefficients(T, max(size(X)...))\n\n    for d in P.jac2jac.dims\n        if d == 1\n            p = forwardrecurrence(size(X,1), A,B,C, one(T))\n            X .= p .\\ X\n        else\n            @assert d == 2\n            n = size(X,2)\n            p = forwardrecurrence(size(X,2), A,B,C, one(T))\n            X .= X ./ transpose(p)\n        end\n    end\n    P.jac2jac*X\nend\n\nfunction *(P::Jac2ChebPlanTH{T}, X::AbstractArray) where T\n    X = P.jac2jac*X\n    A,B,C = jac_cheb_recurrencecoefficients(T, max(size(X)...))\n\n    for d in P.jac2jac.dims\n        if d == 1\n            p = forwardrecurrence(size(X,1), A,B,C, one(T))\n            X .= p .* X\n        else\n            @assert d == 2\n            n = size(X,2)\n            p = forwardrecurrence(size(X,2), A,B,C, one(T))\n            X .= X .* transpose(p)\n        end\n    end\n    X\nend\n\nplan_th_cheb2jac!(::Type{T}, mn, α, β, dims...) where T = Cheb2JacPlanTH(plan_th_jac2jac!(T, mn, -one(α)/2, -one(α)/2, α, β, dims...))\nplan_th_cheb2jac!(arr::AbstractArray{T}, α, β, dims...) where T = plan_th_cheb2jac!(T, size(arr), α, β, dims...)\nth_cheb2jac(v, α, β, dims...) = plan_th_cheb2jac!(eltype(v), size(v), α, β, dims...)*copy(v)\n\nplan_th_jac2cheb!(::Type{T}, mn, α, β, dims...) where T = Jac2ChebPlanTH(plan_th_jac2jac!(T, mn, α, β, -one(α)/2, -one(α)/2, dims...))\nplan_th_jac2cheb!(arr::AbstractArray{T}, α, β, dims...) where T = plan_th_jac2cheb!(T, size(arr), α, β, dims...)\nth_jac2cheb(v, α, β, dims...) = plan_th_jac2cheb!(eltype(v), size(v), α, β, dims...)*copy(v)\n"
  },
  {
    "path": "src/toeplitzplans.jl",
    "content": "using FFTW\nimport FFTW: plan_r2r!\n\n\n\"\"\"\n    ToeplitzPlan\n\napplies Toeplitz matrices fast along each dimension.\n\"\"\"\n\nstruct ToeplitzPlan{T, N, Dims, S, VECS, P<:Plan{S}, Pi<:Plan{S}} <: Plan{T}\n    vectors::VECS # Vector or Tuple of storage\n    tmp::Array{S,N}\n    dft::P\n    idft::Pi\n    dims::Dims\nend\n\nToeplitzPlan{T}(v, tmp::Array{S,N}, dft::Plan{S}, idft::Plan{S}, dims) where {T,S,N} = ToeplitzPlan{T,N,typeof(dims),S,typeof(v),typeof(dft), typeof(idft)}(v, tmp, dft, idft, dims)\n\n\ndivdimby2(d::Int, sz1, szs...) = isone(d) ? ((sz1 + 1) ÷ 2, szs...) : (sz1, divdimby2(d-1, szs...)...)\nmuldimby2(d::Int, sz1, szs...) = isone(d) ? (max(0,2sz1 - 1), szs...) : (sz1, muldimby2(d-1, szs...)...)\n\nfunction toeplitzplan_size(dims, szs)\n    ret = szs\n    for d in dims\n        ret = divdimby2(d, ret...)\n    end\n    ret\nend\n\nfunction to_toeplitzplan_size(dims, szs)\n    ret = szs\n    for d in dims\n        ret = muldimby2(d, ret...)\n    end\n    ret\nend\n\n\nsize(A::ToeplitzPlan) = toeplitzplan_size(A.dims, size(A.tmp))\n\n\n# based on ToeplitzMatrices.jl\n\"\"\"\n    maybereal(::Type{T}, x)\n\nReturn real-valued part of `x` if `T` is a type of a real number, and `x` otherwise.\n\"\"\"\nmaybereal(::Type, x) = x\nmaybereal(::Type{<:Real}, x) = real(x)\n\nfunction *(A::ToeplitzPlan{T,N}, X::AbstractArray{T,N}) where {T,N}\n    vcs,Y,dft,idft,dims = A.vectors,A.tmp, A.dft,A.idft,A.dims\n\n    isempty(X) && return X\n\n    fill!(Y, zero(eltype(Y)))\n    copyto!(view(Y, axes(X)...), X)\n\n    # Fourier transform each dimension\n    dft * Y\n\n    # Multiply by a diagonal matrix along each dimension by permuting\n    # to first dimension\n    for (vc,d) in zip(vcs,dims)\n        applydim!(v -> v .= vc .* v, Y, d, :)\n    end\n\n    # Transform back\n    idft * Y\n\n    X .= maybereal.(T, view(Y, axes(X)...))\n    X\nend\n\n\nfunction uppertoeplitz_padvec(v::AbstractVector{T}) where T\n    n = length(v)\n    S = complex(float(T))\n    tmp = zeros(S, max(0,2n-1))\n    if n ≠ 0\n        tmp[1] = v[1]\n        copyto!(tmp, n+1, Iterators.reverse(v), 1, n-1)\n    end\n    tmp\nend\n\nsafe_fft!(A) = isempty(A) ? A : fft!(A)\n\nuppertoeplitz_vecs(v, dims::AbstractVector, szs) = [safe_fft!(uppertoeplitz_padvec(v[1:szs[d]])) for d in dims]\nuppertoeplitz_vecs(v, dims::Tuple{}, szs) = ()\nuppertoeplitz_vecs(v, dims::Tuple, szs) = (safe_fft!(uppertoeplitz_padvec(v[1:szs[first(dims)]])), uppertoeplitz_vecs(v, tail(dims), szs)...)\nuppertoeplitz_vecs(v, d::Int, szs) = (safe_fft!(uppertoeplitz_padvec(v[1:szs[d]])),)\n\n\n# allow FFT to work by making sure tmp is non-empty\nsafe_tmp(tmp::AbstractArray{<:Any,N}) where N = isempty(tmp) ? similar(tmp, ntuple(_ -> 1, Val(N))...) : tmp\n\nfunction plan_uppertoeplitz!(v::AbstractVector{T}, szs::NTuple{N,Int}, dim=ntuple(identity,Val(N))) where {T,N}\n    S = complex(float(T))\n    \n    tmp = zeros(S, to_toeplitzplan_size(dim, szs)...)\n    dft = plan_fft!(safe_tmp(tmp), dim)\n    idft = plan_ifft!(safe_tmp(similar(tmp)), dim)\n    \n    return ToeplitzPlan{float(T)}(uppertoeplitz_vecs(v, dim, szs), tmp, dft, idft, dim)\nend\n\nplan_uppertoeplitz!(v::AbstractVector{T}) where T = plan_uppertoeplitz!(v, size(v))\n"
  },
  {
    "path": "test/arraystests.jl",
    "content": "using FastTransforms, Test\r\nimport FastTransforms: ArrayPlan, NDimsPlan\r\n\r\n@testset \"Array transform\"  begin\r\n    @testset \"ArrayPlan\" begin\r\n        c = randn(5,20,10)\r\n        F = plan_cheb2leg(c)\r\n        FT = ArrayPlan(F, c)\r\n\r\n        @test size(FT) == size(c)\r\n\r\n        f = similar(c);\r\n        for k in axes(c,3)\r\n            f[:,:,k] = (F*c[:,:,k])\r\n        end\r\n        @test f ≈ FT*c\r\n        @test c ≈ FT\\f\r\n\r\n        F = plan_cheb2leg(Vector{Float64}(axes(c,2)))\r\n        FT = ArrayPlan(F, c, (2,))\r\n        for k in axes(c,3)\r\n            f[:,:,k] = (F*c[:,:,k]')'\r\n        end\r\n        @test f ≈ FT*c\r\n        @test c ≈ FT\\f\r\n    end\r\n\r\n    @testset \"NDimsPlan\" begin\r\n        c = randn(20,10,20)\r\n        @test_throws ErrorException(\"Different size in dims axes not yet implemented in N-dimensional transform.\") NDimsPlan(ArrayPlan(plan_cheb2leg(c), c), size(c), (1,2))        \r\n\r\n        c = randn(5,20)\r\n        F = plan_cheb2leg(c)\r\n        FT = ArrayPlan(F, c)\r\n        P = NDimsPlan(F, size(c), (1,))\r\n        @test F*c ≈ FT*c ≈ P*c\r\n\r\n        c = randn(20,20,5);\r\n        F = plan_cheb2leg(c)\r\n        FT = ArrayPlan(F, c)\r\n        P = NDimsPlan(FT, size(c), (1,2))\r\n\r\n        @test size(P) == size(c)\r\n\r\n        f = similar(c);\r\n        for k in axes(f,3)\r\n            f[:,:,k] = (F*(F*c[:,:,k])')'\r\n        end\r\n        @test f ≈ P*c\r\n        @test c ≈ P\\f\r\n\r\n        c = randn(5,10,10,60)\r\n        F = plan_cheb2leg(randn(10))\r\n        P = NDimsPlan(F, size(c), (2,3))\r\n        f = similar(c)\r\n        for i in axes(f,1), j in axes(f,4)\r\n            f[i,:,:,j] = (F*(F*c[i,:,:,j])')'\r\n        end\r\n        @test f ≈ P*c\r\n        @test c ≈ P\\f\r\n    end\r\nend\r\n\r\n\r\n"
  },
  {
    "path": "test/chebyshevtests.jl",
    "content": "using FastTransforms, Test\n\n@testset \"Chebyshev transform\"  begin\n    @testset \"Chebyshev points\" begin\n        @test @inferred(chebyshevpoints(10)) == @inferred(chebyshevpoints(Float64, 10))\n        @test @inferred(chebyshevpoints(10, Val(2))) == @inferred(chebyshevpoints(Float64, 10, Val(2)))\n        for T in (Float32, Float64, ComplexF32, ComplexF64)\n            @test chebyshevpoints(T, 0) == T[]\n            @test chebyshevpoints(T, 1) == T[0]\n\n            n = 20\n            @test @inferred(chebyshevpoints(T, n)) == [sinpi(convert(T,n-2k+1)/(2n)) for k=1:n]\n            @test @inferred(chebyshevpoints(T, n, Val(2))) == [sinpi(convert(T,n-2k+1)/(2n-2)) for k=1:n]\n\n            @test_throws MethodError chebyshevpoints(n, Val(-1))\n            @test_throws ArgumentError chebyshevpoints(T, 0, Val(2))\n            @test_throws ArgumentError chebyshevpoints(T, 1, Val(2))\n        end\n    end\n\n    @testset \"Chebyshev first kind points <-> first kind coefficients\" begin\n        for T in (Float32, Float64, ComplexF32, ComplexF64)\n            n = 20\n            p_1 = chebyshevpoints(T, n)\n            f = exp.(p_1)\n            g = @inferred(chebyshevtransform(f))\n            @test g == chebyshevtransform!(copy(f))\n\n            f̃ = x -> [cos(k*acos(x)) for k=0:n-1]' * g\n            @test f̃(0.1) ≈ exp(T(0.1))\n            @test @inferred(ichebyshevtransform(g)) ≈ ichebyshevtransform!(copy(g)) ≈ exp.(p_1)\n\n            fcopy = copy(f)\n            gcopy = copy(g)\n            P = @inferred(plan_chebyshevtransform(f))\n            @test @inferred(P*f) == g\n            @test f == fcopy\n            @test_throws ArgumentError P * T[1,2]\n            P2 = @inferred(plan_chebyshevtransform(f, Val(1), 1:1))\n            @test @inferred(P2*f) == g\n            @test_throws ArgumentError P * T[1,2]\n\n            P = @inferred(plan_chebyshevtransform!(f))\n            @test @inferred(P*f) == g\n            @test f == g\n            @test_throws ArgumentError P * T[1,2]\n            f .= fcopy\n            P2 = @inferred(plan_chebyshevtransform!(f, 1:1))\n            @test @inferred(P2*f) == g\n            @test f == g\n            @test_throws ArgumentError P * T[1,2]\n\n            Pi = @inferred(plan_ichebyshevtransform(g))\n            @test @inferred(Pi*g) ≈ fcopy\n            @test g == gcopy\n            @test_throws ArgumentError Pi * T[1,2]\n            Pi2 = @inferred(plan_ichebyshevtransform(g, 1:1))\n            @test @inferred(Pi2*g) ≈ fcopy\n            @test g == gcopy\n            @test_throws ArgumentError Pi * T[1,2]\n\n            Pi = @inferred(plan_ichebyshevtransform!(g))\n            @test @inferred(Pi*g) ≈ fcopy\n            @test g ≈ fcopy\n            g .= gcopy\n            @test_throws ArgumentError Pi * T[1,2]\n            Pi2 = @inferred(plan_ichebyshevtransform!(g, 1:1))\n            @test @inferred(Pi2*g) ≈ fcopy\n            @test g ≈ fcopy\n            @test_throws ArgumentError Pi * T[1,2]\n\n            v = T[1]\n            @test chebyshevtransform(v) == v\n            @test ichebyshevtransform(v) == v\n            @test chebyshevtransform!(v) === v\n            @test ichebyshevtransform!(v) === v\n\n            v = T[]\n            @test chebyshevtransform(v) == v\n            @test ichebyshevtransform(v) == v\n            @test chebyshevtransform!(v) === v\n            @test ichebyshevtransform!(v) === v\n        end\n    end\n    @testset \"Chebyshev second kind points <-> first kind coefficients\" begin\n        for T in (Float32, Float64, ComplexF32, ComplexF64)\n            n = 20\n            p_2 = chebyshevpoints(T, n, Val(2))\n            f = exp.(p_2)\n            g = @inferred(chebyshevtransform(f, Val(2)))\n            @test g == chebyshevtransform!(copy(f), Val(2))\n\n            f̃ = x -> [cos(k*acos(x)) for k=0:n-1]' * g\n            @test f̃(0.1) ≈ exp(T(0.1))\n            @test @inferred(ichebyshevtransform(g, Val(2))) ≈ ichebyshevtransform!(copy(g), Val(2)) ≈ exp.(p_2)\n\n            P = @inferred(plan_chebyshevtransform!(f, Val(2)))\n            Pi = @inferred(plan_ichebyshevtransform!(f, Val(2)))\n            @test all(@inferred(P \\ copy(f)) .=== Pi * copy(f))\n            @test all(@inferred(Pi \\ copy(g)) .=== P * copy(g))\n            @test f ≈ P \\ (P*copy(f)) ≈ P * (P\\copy(f)) ≈ Pi \\ (Pi*copy(f)) ≈ Pi * (Pi \\ copy(f))\n\n            fcopy = copy(f)\n            gcopy = copy(g)\n\n            P = @inferred(plan_chebyshevtransform(f, Val(2)))\n            @test P*f == g\n            @test f == fcopy\n            @test_throws ArgumentError P * T[1,2]\n            P = @inferred(plan_chebyshevtransform(f, Val(2), 1:1))\n            @test P*f == g\n            @test f == fcopy\n            @test_throws ArgumentError P * T[1,2]\n\n            P = @inferred(plan_chebyshevtransform!(f, Val(2)))\n            @test P*f == g\n            @test f == g\n            @test_throws ArgumentError P * T[1,2]\n            f .= fcopy\n            P = @inferred(plan_chebyshevtransform!(f, Val(2), 1:1))\n            @test P*f == g\n            @test f == g\n            @test_throws ArgumentError P * T[1,2]\n\n            Pi = @inferred(plan_ichebyshevtransform(g, Val(2)))\n            @test Pi*g ≈ fcopy\n            @test g == gcopy\n            @test_throws ArgumentError Pi * T[1,2]\n            Pi = @inferred(plan_ichebyshevtransform(g, Val(2), 1:1))\n            @test Pi*g ≈ fcopy\n            @test g == gcopy\n            @test_throws ArgumentError Pi * T[1,2]\n\n            Pi = @inferred(plan_ichebyshevtransform!(g, Val(2)))\n            @test Pi*g ≈ fcopy\n            @test g ≈ fcopy\n            @test_throws ArgumentError Pi * T[1,2]\n            g .= gcopy\n            Pi = @inferred(plan_ichebyshevtransform!(g, Val(2), 1:1))\n            @test Pi*g ≈ fcopy\n            @test g ≈ fcopy\n            @test_throws ArgumentError Pi * T[1,2]\n\n            @test_throws ArgumentError chebyshevtransform(T[1], Val(2))\n            @test_throws ArgumentError ichebyshevtransform(T[1], Val(2))\n            @test_throws ArgumentError chebyshevtransform(T[], Val(2))\n            @test_throws ArgumentError ichebyshevtransform(T[], Val(2))\n        end\n    end\n\n    @testset \"Chebyshev first kind points <-> second kind coefficients\" begin\n        for T in (Float32, Float64, ComplexF32, ComplexF64)\n            n = 20\n            p_1 = chebyshevpoints(T, n)\n            f = exp.(p_1)\n            g = @inferred(chebyshevutransform(f))\n            @test f ≈ exp.(p_1)\n\n            f̃ = x -> [sin((k+1)*acos(x))/sin(acos(x)) for k=0:n-1]' * g\n            @test f̃(0.1) ≈ exp(T(0.1))\n            @test ichebyshevutransform(g) ≈ exp.(p_1)\n\n            fcopy = copy(f)\n            gcopy = copy(g)\n            P = @inferred(plan_chebyshevutransform(f))\n            @test P*f ≈ g\n            @test f ≈ fcopy\n            @test_throws ArgumentError P * T[1,2]\n            P = @inferred(plan_chebyshevutransform(f, 1:1))\n            @test P*f ≈ g\n            @test f ≈ fcopy\n            @test_throws ArgumentError P * T[1,2]\n\n            P = @inferred(plan_chebyshevutransform!(f))\n            @test P*f ≈ g\n            @test f ≈ g\n            @test_throws ArgumentError P * T[1,2]\n            f .= fcopy\n            P = @inferred(plan_chebyshevutransform!(f))\n            @test P*f ≈ g\n            @test f ≈ g\n            @test_throws ArgumentError P * T[1,2]\n\n            Pi = @inferred(plan_ichebyshevutransform(g))\n            @test Pi*g ≈ fcopy\n            @test g == gcopy\n            @test_throws ArgumentError Pi * T[1,2]\n            Pi = @inferred(plan_ichebyshevutransform(g, 1:1))\n            @test Pi*g ≈ fcopy\n            @test g == gcopy\n            @test_throws ArgumentError Pi * T[1,2]\n\n            Pi = @inferred(plan_ichebyshevutransform!(g))\n            @test Pi*g ≈ fcopy\n            @test g ≈ fcopy\n            @test_throws ArgumentError Pi * T[1,2]\n            g .= gcopy\n            Pi = @inferred(plan_ichebyshevutransform!(g))\n            @test Pi*g ≈ fcopy\n            @test g ≈ fcopy\n            @test_throws ArgumentError Pi * T[1,2]\n\n            v = T[1]\n            @test chebyshevutransform(v) == v\n            @test ichebyshevutransform(v) == v\n            @test chebyshevutransform!(v) === v\n            @test ichebyshevutransform!(v) === v\n\n            v = T[]\n            @test chebyshevutransform(v) == v\n            @test ichebyshevutransform(v) == v\n            @test chebyshevutransform!(v) === v\n            @test ichebyshevutransform!(v) === v\n        end\n    end\n    @testset \"Chebyshev second kind points <-> second kind coefficients\" begin\n        for T in (Float32, Float64, ComplexF32, ComplexF64)\n            n = 20\n            p_2 = chebyshevpoints(T, n, Val(2))[2:end-1]\n            f = exp.(p_2)\n            g = @inferred(chebyshevutransform(f, Val(2)))\n\n            f̃ = x -> [sin((k+1)*acos(x))/sin(acos(x)) for k=0:n-3]' * g\n            @test f̃(0.1) ≈ exp(T(0.1))\n            @test @inferred(ichebyshevutransform(g, Val(2))) ≈ f ≈ exp.(p_2)\n\n            fcopy = copy(f)\n            gcopy = copy(g)\n            P = @inferred(plan_chebyshevutransform(f, Val(2)))\n            @test @inferred(P*f) ≈ g\n            @test f ≈ fcopy\n            @test_throws ArgumentError P * T[1,2]\n            P = @inferred(plan_chebyshevutransform(f, Val(2), 1:1))\n            @test @inferred(P*f) ≈ g\n            @test f ≈ fcopy\n            @test_throws ArgumentError P * T[1,2]\n\n            P = @inferred(plan_chebyshevutransform!(f, Val(2)))\n            @test @inferred(P*f) ≈ g\n            @test f ≈ g\n            @test_throws ArgumentError P * T[1,2]\n            f .= fcopy\n            P = @inferred(plan_chebyshevutransform!(f, Val(2), 1:1))\n            @test @inferred(P*f) ≈ g\n            @test f ≈ g\n            @test_throws ArgumentError P * T[1,2]\n\n            Pi = @inferred(plan_ichebyshevutransform(g, Val(2)))\n            @test @inferred(Pi*g) ≈ fcopy\n            @test g ≈ gcopy\n            @test_throws ArgumentError Pi * T[1,2]\n\n            Pi = @inferred(plan_ichebyshevutransform!(g, Val(2)))\n            @test @inferred(Pi*g) ≈ fcopy\n            @test g ≈ fcopy\n            @test_throws ArgumentError Pi * T[1,2]\n            g .= gcopy\n            Pi = @inferred(plan_ichebyshevutransform!(g, Val(2)))\n            @test @inferred(Pi*g) ≈ fcopy\n            @test g ≈ fcopy\n            @test_throws ArgumentError Pi * T[1,2]\n\n            @test_throws ArgumentError chebyshevutransform(T[1], Val(2))\n            @test_throws ArgumentError ichebyshevutransform(T[1], Val(2))\n            @test_throws ArgumentError chebyshevutransform(T[], Val(2))\n            @test_throws ArgumentError ichebyshevutransform(T[], Val(2))\n        end\n    end\n\n    @testset \"matrix\" begin\n        X = randn(4,5)\n        @testset \"chebyshevtransform\" begin\n            @test @inferred(chebyshevtransform(X,1)) ≈ @inferred(chebyshevtransform!(copy(X),1)) ≈ hcat(chebyshevtransform.([X[:,k] for k=axes(X,2)])...)\n            @test chebyshevtransform(X,2) ≈ chebyshevtransform!(copy(X),2) ≈ hcat(chebyshevtransform.([X[k,:] for k=axes(X,1)])...)'\n            @test @inferred(chebyshevtransform(X,Val(2),1)) ≈ @inferred(chebyshevtransform!(copy(X),Val(2),1)) ≈ hcat(chebyshevtransform.([X[:,k] for k=axes(X,2)],Val(2))...)\n            @test chebyshevtransform(X,Val(2),2) ≈ chebyshevtransform!(copy(X),Val(2),2) ≈ hcat(chebyshevtransform.([X[k,:] for k=axes(X,1)],Val(2))...)'\n\n            @test @inferred(chebyshevtransform(X)) ≈ @inferred(chebyshevtransform!(copy(X))) ≈ chebyshevtransform(chebyshevtransform(X,1),2)\n            @test @inferred(chebyshevtransform(X,Val(2))) ≈ @inferred(chebyshevtransform!(copy(X),Val(2))) ≈ chebyshevtransform(chebyshevtransform(X,Val(2),1),Val(2),2)\n        end\n\n        @testset \"ichebyshevtransform\" begin\n            @test @inferred(ichebyshevtransform(X,1)) ≈ @inferred(ichebyshevtransform!(copy(X),1)) ≈ hcat(ichebyshevtransform.([X[:,k] for k=axes(X,2)])...)\n            @test ichebyshevtransform(X,2) ≈ ichebyshevtransform!(copy(X),2) ≈ hcat(ichebyshevtransform.([X[k,:] for k=axes(X,1)])...)'\n            @test @inferred(ichebyshevtransform(X,Val(2),1)) ≈ @inferred(ichebyshevtransform!(copy(X),Val(2),1)) ≈ hcat(ichebyshevtransform.([X[:,k] for k=axes(X,2)],Val(2))...)\n            @test ichebyshevtransform(X,Val(2),2) ≈ ichebyshevtransform!(copy(X),Val(2),2) ≈ hcat(ichebyshevtransform.([X[k,:] for k=axes(X,1)],Val(2))...)'\n\n            @test @inferred(ichebyshevtransform(X)) ≈ @inferred(ichebyshevtransform!(copy(X))) ≈ ichebyshevtransform(ichebyshevtransform(X,1),2)\n            @test @inferred(ichebyshevtransform(X,Val(2))) ≈ @inferred(ichebyshevtransform!(copy(X),Val(2))) ≈ ichebyshevtransform(ichebyshevtransform(X,Val(2),1),Val(2),2)\n\n            @test ichebyshevtransform(chebyshevtransform(X)) ≈ X\n            @test chebyshevtransform(ichebyshevtransform(X)) ≈ X\n        end\n\n        @testset \"chebyshevutransform\" begin\n            @test @inferred(chebyshevutransform(X,1)) ≈ @inferred(chebyshevutransform!(copy(X),1)) ≈ hcat(chebyshevutransform.([X[:,k] for k=axes(X,2)])...)\n            @test chebyshevutransform(X,2) ≈ chebyshevutransform!(copy(X),2) ≈ hcat(chebyshevutransform.([X[k,:] for k=axes(X,1)])...)'\n            @test @inferred(chebyshevutransform(X,Val(2),1)) ≈ @inferred(chebyshevutransform!(copy(X),Val(2),1)) ≈ hcat(chebyshevutransform.([X[:,k] for k=axes(X,2)],Val(2))...)\n            @test chebyshevutransform(X,Val(2),2) ≈ chebyshevutransform!(copy(X),Val(2),2) ≈ hcat(chebyshevutransform.([X[k,:] for k=axes(X,1)],Val(2))...)'\n\n            @test @inferred(chebyshevutransform(X)) ≈ @inferred(chebyshevutransform!(copy(X))) ≈ chebyshevutransform(chebyshevutransform(X,1),2)\n            @test @inferred(chebyshevutransform(X,Val(2))) ≈ @inferred(chebyshevutransform!(copy(X),Val(2))) ≈ chebyshevutransform(chebyshevutransform(X,Val(2),1),Val(2),2)\n        end\n\n        @testset \"ichebyshevutransform\" begin\n            @test @inferred(ichebyshevutransform(X,1)) ≈ @inferred(ichebyshevutransform!(copy(X),1)) ≈ hcat(ichebyshevutransform.([X[:,k] for k=axes(X,2)])...)\n            @test ichebyshevutransform(X,2) ≈ ichebyshevutransform!(copy(X),2) ≈ hcat(ichebyshevutransform.([X[k,:] for k=axes(X,1)])...)'\n            @test @inferred(ichebyshevutransform(X,Val(2),1)) ≈ @inferred(ichebyshevutransform!(copy(X),Val(2),1)) ≈ hcat(ichebyshevutransform.([X[:,k] for k=axes(X,2)],Val(2))...)\n            @test ichebyshevutransform(X,Val(2),2) ≈ ichebyshevutransform!(copy(X),Val(2),2) ≈ hcat(ichebyshevutransform.([X[k,:] for k=axes(X,1)],Val(2))...)'\n\n            @test @inferred(ichebyshevutransform(X)) ≈ @inferred(ichebyshevutransform!(copy(X))) ≈ ichebyshevutransform(ichebyshevutransform(X,1),2)\n            @test @inferred(ichebyshevutransform(X,Val(2))) ≈ @inferred(ichebyshevutransform!(copy(X),Val(2))) ≈ ichebyshevutransform(ichebyshevutransform(X,Val(2),1),Val(2),2)\n\n            @test ichebyshevutransform(chebyshevutransform(X)) ≈ X\n            @test chebyshevutransform(ichebyshevutransform(X)) ≈ X\n        end\n\n        X = randn(1,1)\n        @test chebyshevtransform!(copy(X), Val(1)) == ichebyshevtransform!(copy(X), Val(1)) == X\n        @test_throws ArgumentError chebyshevtransform!(copy(X), Val(2))\n        @test_throws ArgumentError ichebyshevtransform!(copy(X), Val(2))\n    end\n\n    @testset \"tensor\" begin\n        @testset \"3D\" begin\n            X = randn(4,5,6)\n            X̃ = similar(X)\n            @testset \"chebyshevtransform\" begin\n                for k = axes(X,2), j = axes(X,3) X̃[:,k,j] = chebyshevtransform(X[:,k,j]) end\n                @test @inferred(chebyshevtransform(X,1)) ≈ @inferred(chebyshevtransform!(copy(X),1)) ≈ X̃\n                for k = axes(X,1), j = axes(X,3) X̃[k,:,j] = chebyshevtransform(X[k,:,j]) end\n                @test chebyshevtransform(X,2) ≈ chebyshevtransform!(copy(X),2) ≈ X̃\n                for k = axes(X,1), j = axes(X,2) X̃[k,j,:] = chebyshevtransform(X[k,j,:]) end\n                @test chebyshevtransform(X,3) ≈ chebyshevtransform!(copy(X),3) ≈ X̃\n\n                for k = axes(X,2), j = axes(X,3) X̃[:,k,j] = chebyshevtransform(X[:,k,j],Val(2)) end\n                @test @inferred(chebyshevtransform(X,Val(2),1)) ≈ @inferred(chebyshevtransform!(copy(X),Val(2),1)) ≈ X̃\n                for k = axes(X,1), j = axes(X,3) X̃[k,:,j] = chebyshevtransform(X[k,:,j],Val(2)) end\n                @test chebyshevtransform(X,Val(2),2) ≈ chebyshevtransform!(copy(X),Val(2),2) ≈ X̃\n                for k = axes(X,1), j = axes(X,2) X̃[k,j,:] = chebyshevtransform(X[k,j,:],Val(2)) end\n                @test chebyshevtransform(X,Val(2),3) ≈ chebyshevtransform!(copy(X),Val(2),3) ≈ X̃\n\n                @test @inferred(chebyshevtransform(X)) ≈ @inferred(chebyshevtransform!(copy(X))) ≈ chebyshevtransform(chebyshevtransform(chebyshevtransform(X,1),2),3)\n                @test @inferred(chebyshevtransform(X,Val(2))) ≈ @inferred(chebyshevtransform!(copy(X),Val(2))) ≈ chebyshevtransform(chebyshevtransform(chebyshevtransform(X,Val(2),1),Val(2),2),Val(2),3)\n            end\n\n            @testset \"ichebyshevtransform\" begin\n                for k = axes(X,2), j = axes(X,3) X̃[:,k,j] = ichebyshevtransform(X[:,k,j]) end\n                @test @inferred(ichebyshevtransform(X,1)) ≈ @inferred(ichebyshevtransform!(copy(X),1)) ≈ X̃\n                for k = axes(X,1), j = axes(X,3) X̃[k,:,j] = ichebyshevtransform(X[k,:,j]) end\n                @test ichebyshevtransform(X,2) ≈ ichebyshevtransform!(copy(X),2) ≈ X̃\n                for k = axes(X,1), j = axes(X,2) X̃[k,j,:] = ichebyshevtransform(X[k,j,:]) end\n                @test ichebyshevtransform(X,3) ≈ ichebyshevtransform!(copy(X),3) ≈ X̃\n\n                for k = axes(X,2), j = axes(X,3) X̃[:,k,j] = ichebyshevtransform(X[:,k,j],Val(2)) end\n                @test @inferred(ichebyshevtransform(X,Val(2),1)) ≈ @inferred(ichebyshevtransform!(copy(X),Val(2),1)) ≈ X̃\n                for k = axes(X,1), j = axes(X,3) X̃[k,:,j] = ichebyshevtransform(X[k,:,j],Val(2)) end\n                @test ichebyshevtransform(X,Val(2),2) ≈ ichebyshevtransform!(copy(X),Val(2),2) ≈ X̃\n                for k = axes(X,1), j = axes(X,2) X̃[k,j,:] = ichebyshevtransform(X[k,j,:],Val(2)) end\n                @test ichebyshevtransform(X,Val(2),3) ≈ ichebyshevtransform!(copy(X),Val(2),3) ≈ X̃\n\n                @test @inferred(ichebyshevtransform(X)) ≈ @inferred(ichebyshevtransform!(copy(X))) ≈ ichebyshevtransform(ichebyshevtransform(ichebyshevtransform(X,1),2),3)\n                @test @inferred(ichebyshevtransform(X,Val(2))) ≈ @inferred(ichebyshevtransform!(copy(X),Val(2))) ≈ ichebyshevtransform(ichebyshevtransform(ichebyshevtransform(X,Val(2),1),Val(2),2),Val(2),3)\n\n                @test ichebyshevtransform(chebyshevtransform(X)) ≈ X\n                @test chebyshevtransform(ichebyshevtransform(X)) ≈ X\n            end\n        \n            @testset \"chebyshevutransform\" begin\n                for k = axes(X,2), j = axes(X,3) X̃[:,k,j] = chebyshevutransform(X[:,k,j]) end\n                @test @inferred(chebyshevutransform(X,1)) ≈ @inferred(chebyshevutransform!(copy(X),1)) ≈ X̃\n                for k = axes(X,1), j = axes(X,3) X̃[k,:,j] = chebyshevutransform(X[k,:,j]) end\n                @test chebyshevutransform(X,2) ≈ chebyshevutransform!(copy(X),2) ≈ X̃\n                for k = axes(X,1), j = axes(X,2) X̃[k,j,:] = chebyshevutransform(X[k,j,:]) end\n                @test chebyshevutransform(X,3) ≈ chebyshevutransform!(copy(X),3) ≈ X̃\n\n                for k = axes(X,2), j = axes(X,3) X̃[:,k,j] = chebyshevutransform(X[:,k,j],Val(2)) end\n                @test @inferred(chebyshevutransform(X,Val(2),1)) ≈ @inferred(chebyshevutransform!(copy(X),Val(2),1)) ≈ X̃\n                for k = axes(X,1), j = axes(X,3) X̃[k,:,j] = chebyshevutransform(X[k,:,j],Val(2)) end\n                @test chebyshevutransform(X,Val(2),2) ≈ chebyshevutransform!(copy(X),Val(2),2) ≈ X̃\n                for k = axes(X,1), j = axes(X,2) X̃[k,j,:] = chebyshevutransform(X[k,j,:],Val(2)) end\n                @test chebyshevutransform(X,Val(2),3) ≈ chebyshevutransform!(copy(X),Val(2),3) ≈ X̃\n\n                @test @inferred(chebyshevutransform(X)) ≈ @inferred(chebyshevutransform!(copy(X))) ≈ chebyshevutransform(chebyshevutransform(chebyshevutransform(X,1),2),3)\n                @test @inferred(chebyshevutransform(X,Val(2))) ≈ @inferred(chebyshevutransform!(copy(X),Val(2))) ≈ chebyshevutransform(chebyshevutransform(chebyshevutransform(X,Val(2),1),Val(2),2),Val(2),3)\n            end\n\n            @testset \"ichebyshevutransform\" begin\n                for k = axes(X,2), j = axes(X,3) X̃[:,k,j] = ichebyshevutransform(X[:,k,j]) end\n                @test @inferred(ichebyshevutransform(X,1)) ≈ @inferred(ichebyshevutransform!(copy(X),1)) ≈ X̃\n                for k = axes(X,1), j = axes(X,3) X̃[k,:,j] = ichebyshevutransform(X[k,:,j]) end\n                @test ichebyshevutransform(X,2) ≈ ichebyshevutransform!(copy(X),2) ≈ X̃\n                for k = axes(X,1), j = axes(X,2) X̃[k,j,:] = ichebyshevutransform(X[k,j,:]) end\n                @test ichebyshevutransform(X,3) ≈ ichebyshevutransform!(copy(X),3) ≈ X̃\n\n                for k = axes(X,2), j = axes(X,3) X̃[:,k,j] = ichebyshevutransform(X[:,k,j],Val(2)) end\n                @test @inferred(ichebyshevutransform(X,Val(2),1)) ≈ @inferred(ichebyshevutransform!(copy(X),Val(2),1)) ≈ X̃\n                for k = axes(X,1), j = axes(X,3) X̃[k,:,j] = ichebyshevutransform(X[k,:,j],Val(2)) end\n                @test ichebyshevutransform(X,Val(2),2) ≈ ichebyshevutransform!(copy(X),Val(2),2) ≈ X̃\n                for k = axes(X,1), j = axes(X,2) X̃[k,j,:] = ichebyshevutransform(X[k,j,:],Val(2)) end\n                @test ichebyshevutransform(X,Val(2),3) ≈ ichebyshevutransform!(copy(X),Val(2),3) ≈ X̃\n\n                @test @inferred(ichebyshevutransform(X)) ≈ @inferred(ichebyshevutransform!(copy(X))) ≈ ichebyshevutransform(ichebyshevutransform(ichebyshevutransform(X,1),2),3)\n                @test @inferred(ichebyshevutransform(X,Val(2))) ≈ @inferred(ichebyshevutransform!(copy(X),Val(2))) ≈ ichebyshevutransform(ichebyshevutransform(ichebyshevutransform(X,Val(2),1),Val(2),2),Val(2),3)\n\n                @test ichebyshevutransform(chebyshevutransform(X)) ≈ X\n                @test chebyshevutransform(ichebyshevutransform(X)) ≈ X\n            end\n\n            X = randn(1,1,1)\n            @test chebyshevtransform!(copy(X), Val(1)) == ichebyshevtransform!(copy(X), Val(1)) == X\n            @test_throws ArgumentError chebyshevtransform!(copy(X), Val(2))\n            @test_throws ArgumentError ichebyshevtransform!(copy(X), Val(2))\n        end\n\n        @testset \"4D\" begin\n            X = randn(2,3,4,5)\n            X̃ = similar(X)\n            for trans in (chebyshevtransform, ichebyshevtransform, chebyshevutransform, ichebyshevutransform)\n                for k = axes(X,2), j = axes(X,3), l = axes(X,4) X̃[:,k,j,l] = trans(X[:,k,j,l]) end\n                @test @inferred(trans(X,1)) ≈ X̃\n                @test @inferred(trans(X)) ≈ trans(trans(trans(trans(X,1),2),3),4)\n            end\n        end\n    end\n    @testset \"Integer\" begin\n        @test chebyshevtransform([1,2,3]) == chebyshevtransform([1.,2,3])\n        @test chebyshevtransform([1,2,3], Val(2)) == chebyshevtransform([1.,2,3], Val(2))\n        @test ichebyshevtransform([1,2,3]) == ichebyshevtransform([1.,2,3])\n        @test ichebyshevtransform([1,2,3], Val(2)) == ichebyshevtransform([1.,2,3], Val(2))\n\n        @test chebyshevutransform([1,2,3]) == chebyshevutransform([1.,2,3])\n        @test chebyshevutransform([1,2,3], Val(2)) == chebyshevutransform([1.,2,3], Val(2))\n        @test ichebyshevutransform([1,2,3]) == ichebyshevutransform([1.,2,3])\n        @test ichebyshevutransform([1,2,3], Val(2)) == ichebyshevutransform([1.,2,3], Val(2))\n    end\n\n    @testset \"BigFloat\" begin\n        x = BigFloat[1,2,3]\n        @test ichebyshevtransform(chebyshevtransform(x)) ≈ x\n        @test plan_chebyshevtransform(x)x ≈ chebyshevtransform(x)\n        @test plan_ichebyshevtransform(x)x ≈ ichebyshevtransform(x)\n        @test plan_chebyshevtransform!(x)copy(x) ≈ chebyshevtransform(x)\n        @test plan_ichebyshevtransform!(x)copy(x) ≈ ichebyshevtransform(x)\n    end\n    @testset \"BigInt\" begin\n        x = big(10)^400 .+ BigInt[1,2,3]\n        @test ichebyshevtransform(chebyshevtransform(x)) ≈ x\n    end\n\n    @testset \"immutable vectors\" begin\n        F = plan_chebyshevtransform([1.,2,3])\n        @test chebyshevtransform(1.0:3) == F * (1:3)\n        @test ichebyshevtransform(1.0:3) == ichebyshevtransform([1.0:3;])\n    end\n\n    @testset \"inv\" begin\n        x = randn(5)\n        for F in (plan_chebyshevtransform(x), plan_chebyshevtransform(x, Val(2)),\n                  plan_chebyshevutransform(x), plan_chebyshevutransform(x, Val(2)),\n                  plan_ichebyshevtransform(x), plan_ichebyshevtransform(x, Val(2)),\n                  plan_ichebyshevutransform(x), plan_ichebyshevutransform(x, Val(2)))\n            @test F \\ (F*x) ≈ F * (F\\x) ≈ x\n        end\n\n        X = randn(5,4)\n        for F in (plan_chebyshevtransform(X,Val(1),1), plan_chebyshevtransform(X, Val(2),1),\n            plan_chebyshevtransform(X,Val(1),2), plan_chebyshevtransform(X, Val(2),2),\n            plan_ichebyshevtransform(X,Val(1),1), plan_ichebyshevtransform(X, Val(2),1),\n            plan_ichebyshevtransform(X,Val(1),2), plan_ichebyshevtransform(X, Val(2),2))\n            @test F \\ (F*X) ≈ F * (F\\X) ≈ X\n        end\n        # Matrix isn't implemented for chebyshevu\n        for F in (plan_chebyshevutransform(X,Val(1),1), plan_chebyshevutransform(X, Val(2),1),\n            plan_chebyshevutransform(X,Val(1),2), plan_chebyshevutransform(X, Val(2),2),\n            plan_ichebyshevutransform(X,Val(1),1), plan_ichebyshevutransform(X, Val(2),1),\n            plan_ichebyshevutransform(X,Val(1),2), plan_ichebyshevutransform(X, Val(2),2))\n            @test F \\ (F*X) ≈ F * (F\\X) ≈ X\n        end\n    end\n\n    @testset \"incompatible shapes\" begin\n        @test_throws ErrorException plan_chebyshevtransform(randn(5)) * randn(5,5)\n        @test_throws ErrorException plan_ichebyshevtransform(randn(5)) * randn(5,5)\n    end\n\n    @testset \"plan via size\" begin\n        X = randn(3,4)\n        p = plan_chebyshevtransform(Float64, (3,4))\n        @test p * X == chebyshevtransform(X)\n    end\nend\n"
  },
  {
    "path": "test/gaunttests.jl",
    "content": "using FastTransforms, LinearAlgebra, Test\n\nimport FastTransforms: δ\n\n@testset \"Gaunt coefficients\" begin\n    # Table 2 of Y.-l. Xu, JCAM 85:53–65, 1997.\n    for (m,n) in ((0,2),(1,2),(1,8),(6,8),(3,18),\n                  (10,18),(5,25),(-23,25),(2,40),(-35,40),\n                  (28,62),(-42,62),(1,99),(90,99),(10,120),\n                  (80,120),(23,150),(88,150))\n        @test norm(gaunt(m,n,-m,n)[end]./(big(-1.0)^m/(2n+1))-1, Inf) < 400eps()\n    end\n    # Table 3 of Y.-l. Xu, JCAM 85:53–65, 1997.\n    for (m,n,μ,ν) in ((0,1,0,5),(0,5,0,10),(0,9,0,10),(0,10,0,12),\n                      (0,11,0,15),(0,12,0,20),(0,20,0,45),(0,40,0,80),\n                      (0,45,0,100),(3,5,-3,6),(4,9,-4,15),(-8,18,8,23),\n                      (-10,20,10,30),(5,25,-5,45),(15,50,-15,60),(-28,68,28,75),\n                      (32,78,-32,88),(45,82,-45,100))\n        @test norm(sum(gaunt(m,n,μ,ν))-δ(m,0), Inf) < 15000eps()\n    end\nend\n"
  },
  {
    "path": "test/grammatrixtests.jl",
    "content": "using FastTransforms, BandedMatrices, LazyArrays, LinearAlgebra, Test\n\n@testset \"GramMatrix\" begin\n    n = 128\n    for T in (Float32, Float64, BigFloat)\n        R = plan_leg2cheb(T, n; normcheb=true)*I\n        X = Tridiagonal([T(n)/(2n-1) for n in 1:n-1], zeros(T, n), [T(n)/(2n+1) for n in 1:n-1]) # Legendre X\n        W = GramMatrix(Symmetric(R'R), X)\n        @test issymmetric(W)\n        @test isposdef(W)\n        F = cholesky(W)\n        @test F.L*F.L' ≈ Symmetric(R'R)\n        @test F.U ≈ R\n\n        R = plan_leg2cheb(T, n; normcheb=true, normleg=true)*I\n        X = SymTridiagonal(zeros(T, n), [sqrt(T(n)^2/(4*n^2-1)) for n in 1:n-1]) # normalized Legendre X\n        W = GramMatrix(Symmetric(R'R), X)\n        F = cholesky(W)\n        @test F.L*F.L' ≈ Symmetric(R'R)\n        @test F.U ≈ R\n\n        b = 4\n        X = BandedMatrix(SymTridiagonal(zeros(T, n+b), [sqrt(T(n)^2/(4*n^2-1)) for n in 1:n+b-1])) # normalized Legendre X\n        M = Symmetric((I+X^2+X^4)[1:n, 1:n])\n        X = BandedMatrix(SymTridiagonal(zeros(T, n), [sqrt(T(n)^2/(4*n^2-1)) for n in 1:n-1])) # normalized Legendre X\n        W = GramMatrix(M, X)\n        @test bandwidths(W) == (b, b)\n        F = cholesky(W)\n        @test F.L*F.L' ≈ M\n\n        X = BandedMatrix(SymTridiagonal(T[2n-1 for n in 1:n+b], T[-n for n in 1:n+b-1])) # Laguerre X, tests nonzero diagonal\n        M = Symmetric((I+X^2+X^4)[1:n, 1:n])\n        X = BandedMatrix(SymTridiagonal(T[2n-1 for n in 1:n], T[-n for n in 1:n-1])) # Laguerre X, tests nonzero diagonal\n        W = GramMatrix(M, X)\n        @test bandwidths(W) == (b, b)\n        F = cholesky(W)\n        @test F.L*F.L' ≈ M\n\n        for μ in (PaddedVector([T(4)/3;0;-T(4)/15], 2n-1), # w(x) = 1-x^2\n                  PaddedVector([T(26)/15;0;-T(4)/105;0;T(16)/315], 2n-1), # w(x) = 1-x^2+x^4\n                  T(1) ./ (1:2n-1)) # Related to a log weight\n            X = Tridiagonal([T(n)/(2n-1) for n in 1:2n-2], zeros(T, 2n-1), [T(n)/(2n+1) for n in 1:2n-2]) # Legendre X\n            W = GramMatrix(μ, X)\n            X = Tridiagonal(X[1:n, 1:n])\n            G = FastTransforms.compute_skew_generators(W)\n            J = T[0 1; -1 0]\n            @test X'W-W*X ≈ G*J*G'\n        end\n    end\n    W = reshape([i for i in 1.0:n^2], n, n)\n    X = reshape([i for i in 1.0:4n^2], 2n, 2n)\n    @test_throws \"different sizes\" GramMatrix(W, X)\n    X = X[1:n, 1:n]\n    @test_throws \"nonsymmetric\" GramMatrix(W, X)\n    @test_throws \"nontridiagonal\" GramMatrix(Symmetric(W), X)\nend\n\n@testset \"ChebyshevGramMatrix\" begin\n    n = 128\n    for T in (Float32, Float64, BigFloat)\n        μ = FastTransforms.chebyshevmoments1(T, 2n-1)\n        W = ChebyshevGramMatrix(μ)\n        F = cholesky(W)\n        @test F.L*F.L' ≈ W\n        R = plan_cheb2leg(T, n; normleg=true)*I\n        @test F.U ≈ R\n\n        α, β = (T(0.123), T(0.456))\n        μ = FastTransforms.chebyshevjacobimoments1(T, 2n-1, α, β)\n        W = ChebyshevGramMatrix(μ)\n        F = cholesky(W)\n        @test F.L*F.L' ≈ W\n        R = plan_cheb2jac(T, n, α, β; normjac=true)*I\n        @test F.U ≈ R\n\n        μ = FastTransforms.chebyshevlogmoments1(T, 2n-1)\n        W = ChebyshevGramMatrix(μ)\n        F = cholesky(W)\n        @test F.L*F.L' ≈ W\n\n        μ = FastTransforms.chebyshevabsmoments1(T, 2n-1)\n        W = ChebyshevGramMatrix(μ)\n        F = cholesky(W)\n        @test F.L*F.L' ≈ W\n\n        μ = PaddedVector(T(1) ./ [1,2,3,4,5], 2n-1)\n        W = ChebyshevGramMatrix(μ)\n        @test bandwidths(W) == (4, 4)\n        F = cholesky(W)\n        @test F.L*F.L' ≈ W\n        μd = Vector{T}(μ)\n        Wd = ChebyshevGramMatrix(μd)\n        Fd = cholesky(Wd)\n        @test F.L ≈ Fd.L\n\n        X = Tridiagonal([T(1); ones(T, n-2)/2], zeros(T, n), ones(T, n-1)/2)\n        G = FastTransforms.compute_skew_generators(W)\n        J = T[0 1; -1 0]\n        @test 2*(X'W-W*X) ≈ G*J*G'\n    end\nend\n"
  },
  {
    "path": "test/hermitetests.jl",
    "content": "using FastTransforms, FastGaussQuadrature, Test\n\nhermitepoints(n) = FastGaussQuadrature.unweightedgausshermite( n )[1]\n\n@testset \"Hermite\" begin\n    @test hermitepoints(1) == [0.0]\n    @test hermitepoints(100_000)[end] ≈ 446.9720305443094\n\n    @test weightedhermitetransform([1.0]) == [1.0]\n    @test weightedhermitetransform(exp.(-hermitepoints(2).^2/2)) ≈ [1.0,0.0]\n    @test weightedhermitetransform(exp.(-hermitepoints(3).^2/2)) ≈ [1.0,0.0,0.0]\n    @test weightedhermitetransform(exp.(-hermitepoints(1000).^2/2)) ≈ [1.0; zeros(999)]\n    @test weightedhermitetransform(exp.(-hermitepoints(3000).^2/2)) ≈ [1.0; zeros(2999)]\n\n    for n in (1, 5,100)\n        x = randn(n)\n        @test iweightedhermitetransform(weightedhermitetransform(x)) ≈ x\n        @test weightedhermitetransform(iweightedhermitetransform(x)) ≈ x\n    end\n\n    x = hermitepoints(100)\n    @test iweightedhermitetransform([0.0; 1.0; zeros(98)]) ≈ (exp.(-x.^2 ./ 2) .* 2x/sqrt(2))\n    @test iweightedhermitetransform([0.0; 0; 1.0; zeros(97)]) ≈ (exp.(-x.^2 ./ 2) .* (4x.^2 .- 2)/(sqrt(2)*2^(2/2)))\n    @test iweightedhermitetransform([0.0; 0; 0; 1.0; zeros(96)]) ≈ (exp.(-x.^2 ./ 2) .* (-12x + 8x.^3) / (sqrt(2*3)*2^(3/2)))\n    @test iweightedhermitetransform([0.0; 0; 0; 0; 1.0; zeros(95)]) ≈ (exp.(-x.^2 ./ 2) .* (12 .- 48x.^2 .+ 16x.^4) / (sqrt(2*3*4)*2^(4/2)))\nend\n"
  },
  {
    "path": "test/libfasttransformstests.jl",
    "content": "using FastTransforms, Test\n\nFastTransforms.ft_set_num_threads(ceil(Int, Base.Sys.CPU_THREADS/2))\n\n@testset \"libfasttransforms\" begin\n    n = 64\n    for T in (Float32, Float64)\n        c = one(T) ./ (1:n)\n        x = collect(-1 .+ 2*(0:n-1)/T(n))\n        f = similar(x)\n        @test FastTransforms.horner!(f, c, x) == f\n        fd = T[sum(c[k]*x^(k-1) for k in 1:length(c)) for x in x]\n        @test f ≈ fd\n        @test FastTransforms.clenshaw!(f, c, x) == f\n        fd = T[sum(c[k]*cos((k-1)*acos(x)) for k in 1:length(c)) for x in x]\n        @test f ≈ fd\n        A = T[(2k+one(T))/(k+one(T)) for k in 0:length(c)-1]\n        B = T[zero(T) for k in 0:length(c)-1]\n        C = T[k/(k+one(T)) for k in 0:length(c)]\n        phi0 = ones(T, length(x))\n        c = FastTransforms.lib_cheb2leg(c)\n        @test FastTransforms.clenshaw!(f, c, A, B, C, x, phi0) == f\n        @test f ≈ fd\n    end\n\n    α, β, γ, δ, λ, μ, ρ = 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7\n    function test_1d_plans(p1, p2, x)\n        y = p1*x\n        z = p2*y\n        @test z ≈ x\n        y = p1*view(x, :)\n        z = p2*view(y, :)\n        @test z ≈ x\n        y = p1*x\n        z = p1'y\n        y = transpose(p1)*z\n        z = transpose(p1)\\y\n        y = p1'\\z\n        z = p1\\y\n        @test z ≈ x\n        y = p1*view(x, :)\n        z = p1'view(y, :)\n        y = transpose(p1)*view(z, :)\n        z = transpose(p1)\\view(y, :)\n        y = p1'\\view(z, :)\n        z = p1\\view(y, :)\n        @test z ≈ x\n        y = p2*x\n        z = p2'y\n        y = transpose(p2)*z\n        z = transpose(p2)\\y\n        y = p2'\\z\n        z = p2\\y\n        @test z ≈ x\n        y = p2*view(x, :)\n        z = p2'view(y, :)\n        y = transpose(p2)*view(z, :)\n        z = transpose(p2)\\view(y, :)\n        y = p2'\\view(z, :)\n        z = p2\\view(y, :)\n        @test z ≈ x\n        P = p1*I\n        Q = p2*P\n        @test Q ≈ I\n        P = p1*I\n        Q = p1'P\n        P = transpose(p1)*Q\n        Q = transpose(p1)\\P\n        P = p1'\\Q\n        Q = p1\\P\n        @test Q ≈ I\n        P = p2*I\n        Q = p2'P\n        P = transpose(p2)*Q\n        Q = transpose(p2)\\P\n        P = p2'\\Q\n        Q = p2\\P\n        @test Q ≈ I\n    end\n\n    for T in (Float32, Float64, Complex{Float32}, Complex{Float64}, BigFloat, Complex{BigFloat})\n        x = T(1)./(1:n)\n        Id = Matrix{T}(I, n, n)\n        for (p1, p2) in ((plan_leg2cheb(Id), plan_cheb2leg(Id)),\n                         (plan_ultra2ultra(Id, λ, μ), plan_ultra2ultra(Id, μ, λ)),\n                         (plan_jac2jac(Id, α, β, γ, δ), plan_jac2jac(Id, γ, δ, α, β)),\n                         (plan_lag2lag(Id, α, β), plan_lag2lag(Id, β, α)),\n                         (plan_jac2ultra(Id, α, β, λ), plan_ultra2jac(Id, λ, α, β)),\n                         (plan_jac2cheb(Id, α, β), plan_cheb2jac(Id, α, β)),\n                         (plan_ultra2cheb(Id, λ), plan_cheb2ultra(Id, λ)))\n            test_1d_plans(p1, p2, x)\n        end\n    end\n\n    for T in (Float32, Float64, Complex{Float32}, Complex{Float64})\n        x = T(1)./(1:n)\n        Id = Matrix{T}(I, n, n)\n        p = plan_associatedjac2jac(Id, 1, α, β, γ, δ)\n        V = p*I\n        @test V ≈ p*Id\n        y = p*x\n        @test V\\y ≈ x\n    end\n\n    @testset \"Modified classical orthonormal polynomial transforms\" begin\n        (n, α, β) = (16, 0, 0)\n        for T in (Float32, Float64)\n            P1 = plan_modifiedjac2jac(T, n, α, β, T[0.9428090415820636, -0.32659863237109055, -0.42163702135578396, 0.2138089935299396]) # u1(x) = (1-x)^2*(1+x)\n            P2 = plan_modifiedjac2jac(T, n, α, β, T[0.9428090415820636, -0.32659863237109055, -0.42163702135578396, 0.2138089935299396], T[1.4142135623730951]) # u2(x) = (1-x)^2*(1+x)\n            P3 = plan_modifiedjac2jac(T, n, α, β, T[-0.9428090415820636, 0.32659863237109055, 0.42163702135578396, -0.2138089935299396], T[-5.185449728701348, 0.0, 0.42163702135578374]) # u3(x) = -(1-x)^2*(1+x), v3(x) = -(2-x)*(2+x)\n            P4 = plan_modifiedjac2jac(T, n, α+2, β+1, T[1.1547005383792517], T[4.387862045841156, 0.1319657758147716, -0.20865621238292037]) # v4(x) = (2-x)*(2+x)\n\n            @test P1*I ≈ P2*I\n            @test P1\\I ≈ P2\\I\n            @test P3*I ≈ P2*(P4*I)\n            @test P3\\I ≈ P4\\(P2\\I)\n\n            P5 = plan_modifiedlag2lag(T, n, α, T[2.0, -4.0, 2.0]) # u5(x) = x^2\n            P6 = plan_modifiedlag2lag(T, n, α, T[2.0, -4.0, 2.0], T[1.0]) # u6(x) = x^2\n            P7 = plan_modifiedlag2lag(T, n, α, T[2.0, -4.0, 2.0], T[7.0, -7.0, 2.0]) # u7(x) = x^2, v7(x) = (1+x)*(2+x)\n            P8 = plan_modifiedlag2lag(T, n, α+2, T[sqrt(2.0)], T[sqrt(1058.0), -sqrt(726.0), sqrt(48.0)]) # v8(x) = (1+x)*(2+x)\n\n            @test P5*I ≈ P6*I\n            @test P5\\I ≈ P6\\I\n            @test isapprox(P7*I, P6*(P8*I); rtol = eps(T)^(1/4))\n            @test isapprox(P7\\I, P8\\(P6\\I); rtol = eps(T)^(1/4))\n\n            P9 = plan_modifiedherm2herm(T, n, T[2.995504568550877, 0.0, 3.7655850551068593, 0.0, 1.6305461589167827], T[2.995504568550877, 0.0, 3.7655850551068593, 0.0, 1.6305461589167827]) # u9(x) = 1+x^2+x^4, v9(x) = 1+x^2+x^4\n\n            @test P9*I ≈ P9\\I\n        end\n    end\n\n    function test_nd_plans(p, ps, pa, A)\n        B = copy(A)\n        C = ps*(p*A)\n        A = p\\(pa*C)\n        @test A ≈ B\n        C = ps'*(p'A)\n        A = p'\\(pa'C)\n        @test A ≈ B\n        C = transpose(ps)*(transpose(p)*A)\n        A = transpose(p)\\(transpose(pa)*C)\n        @test A ≈ B\n    end\n\n    A = sphones(Float64, n, 2n-1)\n    p = plan_sph2fourier(A)\n    ps = plan_sph_synthesis(A)\n    pa = plan_sph_analysis(A)\n    test_nd_plans(p, ps, pa, A)\n    A = sphones(Float64, n, 2n-1) + im*sphones(Float64, n, 2n-1)\n    p = plan_sph2fourier(A)\n    ps = plan_sph_synthesis(A)\n    pa = plan_sph_analysis(A)\n    test_nd_plans(p, ps, pa, A)\n\n    A = sphvones(Float64, n, 2n-1)\n    p = plan_sphv2fourier(A)\n    ps = plan_sphv_synthesis(A)\n    pa = plan_sphv_analysis(A)\n    test_nd_plans(p, ps, pa, A)\n    A = sphvones(Float64, n, 2n-1) + im*sphvones(Float64, n, 2n-1)\n    p = plan_sphv2fourier(A)\n    ps = plan_sphv_synthesis(A)\n    pa = plan_sphv_analysis(A)\n    test_nd_plans(p, ps, pa, A)\n\n    A = diskones(Float64, n, 4n-3)\n    p = plan_disk2cxf(A, α, β)\n    ps = plan_disk_synthesis(A)\n    pa = plan_disk_analysis(A)\n    test_nd_plans(p, ps, pa, A)\n    A = diskones(Float64, n, 4n-3) + im*diskones(Float64, n, 4n-3)\n    p = plan_disk2cxf(A, α, β)\n    ps = plan_disk_synthesis(A)\n    pa = plan_disk_analysis(A)\n    test_nd_plans(p, ps, pa, A)\n\n    A = diskones(Float64, n, 4n-3)\n    p = plan_ann2cxf(A, α, β, 0, ρ)\n    ps = plan_annulus_synthesis(A, ρ)\n    pa = plan_annulus_analysis(A, ρ)\n    test_nd_plans(p, ps, pa, A)\n    A = diskones(Float64, n, 4n-3) + im*diskones(Float64, n, 4n-3)\n    p = plan_ann2cxf(A, α, β, 0, ρ)\n    ps = plan_annulus_synthesis(A, ρ)\n    pa = plan_annulus_analysis(A, ρ)\n    test_nd_plans(p, ps, pa, A)\n\n    A = rectdiskones(Float64, n, n)\n    p = plan_rectdisk2cheb(A, β)\n    ps = plan_rectdisk_synthesis(A)\n    pa = plan_rectdisk_analysis(A)\n    test_nd_plans(p, ps, pa, A)\n    A = rectdiskones(Float64, n, n) + im*rectdiskones(Float64, n, n)\n    p = plan_rectdisk2cheb(A, β)\n    ps = plan_rectdisk_synthesis(A)\n    pa = plan_rectdisk_analysis(A)\n    test_nd_plans(p, ps, pa, A)\n\n    A = triones(Float64, n, n)\n    p = plan_tri2cheb(A, α, β, γ)\n    ps = plan_tri_synthesis(A)\n    pa = plan_tri_analysis(A)\n    test_nd_plans(p, ps, pa, A)\n    A = triones(Float64, n, n) + im*triones(Float64, n, n)\n    p = plan_tri2cheb(A, α, β, γ)\n    ps = plan_tri_synthesis(A)\n    pa = plan_tri_analysis(A)\n    test_nd_plans(p, ps, pa, A)\n\n    α, β, γ, δ = -0.1, -0.2, -0.3, -0.4\n    A = tetones(Float64, n, n, n)\n    p = plan_tet2cheb(A, α, β, γ, δ)\n    ps = plan_tet_synthesis(A)\n    pa = plan_tet_analysis(A)\n    test_nd_plans(p, ps, pa, A)\n    A = tetones(Float64, n, n, n) + im*tetones(Float64, n, n, n)\n    p = plan_tet2cheb(A, α, β, γ, δ)\n    ps = plan_tet_synthesis(A)\n    pa = plan_tet_analysis(A)\n    test_nd_plans(p, ps, pa, A)\n\n    A = spinsphones(Complex{Float64}, n, 2n-1, 2) + im*spinsphones(Complex{Float64}, n, 2n-1, 2)\n    p = plan_spinsph2fourier(A, 2)\n    ps = plan_spinsph_synthesis(A, 2)\n    pa = plan_spinsph_analysis(A, 2)\n    test_nd_plans(p, ps, pa, A)\nend\n\n@testset \"ultra2ulta bug and cheb2leg normalisation (#202, #203)\" begin\n    @test ultra2ultra([0.0, 1.0], 1, 1) == [0,1]\n    @test cheb2leg([0.0, 1.0], normcheb=true) ≈ [0.,sqrt(2/π)]\n    @test cheb2leg([0.0, 1.0], normleg=true) ≈ [0.,sqrt(2/3)]\nend"
  },
  {
    "path": "test/nuffttests.jl",
    "content": "using FFTW, FastTransforms, LinearAlgebra, Test\n\nFFTW.set_num_threads(ceil(Int, Sys.CPU_THREADS/2))\n\n@testset \"Nonuniform fast Fourier transforms\" begin\n    function nudft1(c::AbstractVector, ω::AbstractVector{T}) where {T<:AbstractFloat}\n        # Nonuniform discrete Fourier transform of type I\n\n        N = size(ω, 1)\n        output = zero(c)\n        for j = 1:N\n        \toutput[j] = dot(exp.(2*T(π)*im*(j-1)/N*ω), c)\n        end\n\n        return output\n    end\n\n    function nudft2(c::AbstractVector, x::AbstractVector{T}) where {T<:AbstractFloat}\n        # Nonuniform discrete Fourier transform of type II\n\n        N = size(x, 1)\n        output = zero(c)\n        ω = collect(0:N-1)\n        for j = 1:N\n        \toutput[j] = dot(exp.(2*T(π)*im*x[j]*ω), c)\n        end\n\n        return output\n    end\n\n    function nudft3(c::AbstractVector, x::AbstractVector{T}, ω::AbstractVector{T}) where {T<:AbstractFloat}\n        # Nonuniform discrete Fourier transform of type III\n\n        N = size(x, 1)\n        output = zero(c)\n        for j = 1:N\n            output[j] = dot(exp.(2*T(π)*im*x[j]*ω), c)\n        end\n\n        return output\n    end\n\n    N = round.([Int],10 .^ range(1,stop=3,length=10))\n\n    for n in N, ϵ in (1e-4, 1e-8, 1e-12, eps(Float64))\n        c = complex(rand(n))\n        err_bnd = 500*ϵ*n*norm(c)\n\n        ω = collect(0:n-1) + 0.25*rand(n)\n        exact = nudft1(c, ω)\n        fast = nufft1(c, ω, ϵ)\n        @test norm(exact - fast, Inf) < err_bnd\n\n        d = inufft1(fast, ω, ϵ)\n        @test norm(c - d, Inf) < err_bnd\n\n        x = (collect(0:n-1) + 0.25*rand(n))/n\n        exact = nudft2(c, x)\n        fast = nufft2(c, x, ϵ)\n        @test norm(exact - fast, Inf) < err_bnd\n\n        d = inufft2(fast, x, ϵ)\n        @test norm(c - d, Inf) < err_bnd\n\n        exact = nudft3(c, x, ω)\n        fast = nufft3(c, x, ω, ϵ)\n        @test norm(exact - fast, Inf) < err_bnd\n    end\n\n    # Check that if points/frequencies are indeed uniform, then it's equal to the fft.\n    for n in (1000,), ϵ in (eps(Float64), 0.0)\n        c = complex(rand(n))\n        ω = collect(0.0:n-1)\n        x = ω/n\n        fftc = fft(c)\n        if Sys.WORD_SIZE == 64\n            @test_skip norm(nufft1(c, ω, ϵ) - fftc) == 0 # skip because fftw3 seems to change this\n            @test norm(nufft2(c, x, ϵ) - fftc) == 0\n            @test_skip norm(nufft3(c, x, ω, ϵ) - fftc) == 0 # skip because fftw3 seems to change this\n        end\n        err_bnd = 500*eps(Float64)*norm(c)\n        @test norm(nufft1(c, ω, ϵ) - fftc) < err_bnd\n        @test norm(nufft2(c, x, ϵ) - fftc) < err_bnd\n        @test norm(nufft3(c, x, ω, ϵ) - fftc) < err_bnd\n    end\n\n    function nudft1(C::Matrix{Complex{T}}, ω1::AbstractVector{T}, ω2::AbstractVector{T}) where {T<:AbstractFloat}\n        # Nonuniform discrete Fourier transform of type I-I\n\n        M, N = size(C)\n        output = zero(C)\n        @inbounds for j1 = 1:M, j2 = 1:N\n            for k1 = 1:M, k2 = 1:N\n                output[j1,j2] += exp(-2*T(π)*im*((j1-1)/M*ω1[k1]+(j2-1)/N*ω2[k2]))*C[k1,k2]\n            end\n        end\n        return output\n    end\n\n    function nudft2(C::Matrix{Complex{T}}, x::AbstractVector{T}, y::AbstractVector{T}) where {T<:AbstractFloat}\n        # Nonuniform discrete Fourier transform of type II-II\n\n        M, N = size(C)\n        output = zero(C)\n        @inbounds for j1 = 1:M, j2 = 1:N\n            for k1 = 1:M, k2 = 1:N\n                output[j1,j2] += exp(-2*T(π)*im*(x[j1]*(k1-1)+y[j2]*(k2-1)))*C[k1,k2]\n            end\n        end\n        return output\n    end\n\n    N = round.([Int],10 .^ range(1,stop=1.7,length=5))\n\n    for n in N, ϵ in (1e-4,1e-8,1e-12,eps(Float64))\n        C = complex(rand(n,n))\n        err_bnd = 500*ϵ*n*norm(C)\n\n        x = (collect(0:n-1) + 0.25*rand(n))/n\n        y = (collect(0:n-1) + 0.25*rand(n))/n\n        ω1 = collect(0:n-1) + 0.25*rand(n)\n        ω2 = collect(0:n-1) + 0.25*rand(n)\n\n        exact = nudft1(C, ω1, ω2)\n        fast = nufft1(C, ω1, ω2, ϵ)\n        @test norm(exact - fast, Inf) < err_bnd\n\n        exact = nudft2(C, x, y)\n        fast = nufft2(C, x, y, ϵ)\n        @test norm(exact - fast, Inf) < err_bnd\n    end\nend\n"
  },
  {
    "path": "test/paduatests.jl",
    "content": "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=rand(N)  #Length of v is the no. of Padua points\n    Pl=plan_paduatransform!(v)\n    IPl=plan_ipaduatransform!(v)\n    @test Pl*(IPl*copy(v)) ≈ v\n    @test IPl*(Pl*copy(v)) ≈ v\n    @test Pl*copy(v) ≈ paduatransform(v)\n    @test IPl*copy(v) ≈ ipaduatransform(v)\n\n    # check that the return vector is NOT reused\n    Pl=plan_paduatransform!(v)\n    x=Pl*v\n    y=Pl*rand(N)\n    @test x ≠ y\n\n    IPl=plan_ipaduatransform!(v)\n    x=IPl*v\n    y=IPl*rand(N)\n    @test x ≠ y\n\n    # Accuracy of 2d function interpolation at a point\n\n    \"\"\"\n    Interpolates a 2d function at a given point using 2d Chebyshev series.\n    \"\"\"\n    function paduaeval(f::Function,x::AbstractFloat,y::AbstractFloat,m::Integer,lex)\n        T=promote_type(typeof(x),typeof(y))\n        M=div((m+1)*(m+2),2)\n        pvals=Vector{T}(undef,M)\n        p=paduapoints(T,m)\n        map!(f,pvals,p[:,1],p[:,2])\n        coeffs=paduatransform(pvals,lex)\n        plan=plan_ipaduatransform!(pvals,lex)\n        cfs_mat=FastTransforms.trianglecfsmat(plan,coeffs)\n        f_x=sum([cfs_mat[k,j]*cos((j-1)*acos(x))*cos((k-1)*acos(y)) for k=1:m+1, j=1:m+1])\n        return f_x\n    end\n    f_xy = (x,y) ->x^2*y+x^3\n    g_xy = (x,y) ->cos(exp(2*x+y))*sin(y)\n    x=0.1;y=0.2\n    m=130\n    l=80\n    f_m=paduaeval(f_xy,x,y,m,Val{true})\n    g_l=paduaeval(g_xy,x,y,l,Val{true})\n    @test f_xy(x,y) ≈ f_m\n    @test g_xy(x,y) ≈ g_l\n\n    f_m=paduaeval(f_xy,x,y,m,Val{false})\n    g_l=paduaeval(g_xy,x,y,l,Val{false})\n    @test f_xy(x,y) ≈ f_m\n    @test g_xy(x,y) ≈ g_l\n\n    # odd n\n    m=135\n    l=85\n    f_m=paduaeval(f_xy,x,y,m,Val{true})\n    g_l=paduaeval(g_xy,x,y,l,Val{true})\n    @test f_xy(x,y) ≈ f_m\n    @test g_xy(x,y) ≈ g_l\n\n    f_m=paduaeval(f_xy,x,y,m,Val{false})\n    g_l=paduaeval(g_xy,x,y,l,Val{false})\n    @test f_xy(x,y) ≈ f_m\n    @test g_xy(x,y) ≈ g_l\nend\n"
  },
  {
    "path": "test/quadraturetests.jl",
    "content": "using FastTransforms, LinearAlgebra, Test\n\nimport FastTransforms: chebyshevmoments1, chebyshevmoments2,\n                       chebyshevjacobimoments1, chebyshevjacobimoments2,\n                       chebyshevlogmoments1, chebyshevlogmoments2\n\n@testset \"Fejér and Clenshaw–Curtis quadrature\" begin\n    N = 20\n    f = x -> exp(x)\n\n    x = clenshawcurtisnodes(Float64, N)\n    μ = chebyshevmoments1(Float64, N)\n    w = clenshawcurtisweights(μ)\n    @test norm(dot(f.(x), w)-2sinh(1)) ≤ 4eps()\n\n    μ = chebyshevjacobimoments1(Float64, N, 0.25, 0.35)\n    w = clenshawcurtisweights(μ)\n    @test norm(dot(f.(x), w)-2.0351088204147243) ≤ 4eps()\n\n    μ = chebyshevlogmoments1(Float64, N)\n    w = clenshawcurtisweights(μ)\n    @test norm(sum(w./(3 .- x)) - π^2/12) ≤ 4eps()\n\n    x = fejernodes1(Float64, N)\n    μ = chebyshevmoments1(Float64, N)\n    w = fejerweights1(μ)\n    @test norm(dot(f.(x), w)-2sinh(1)) ≤ 4eps()\n\n    μ = chebyshevjacobimoments1(Float64, N, 0.25, 0.35)\n    w = fejerweights1(μ)\n    @test norm(dot(f.(x), w)-2.0351088204147243) ≤ 4eps()\n\n    μ = chebyshevlogmoments1(Float64, N)\n    w = fejerweights1(μ)\n    @test norm(sum(w./(3 .- x)) - π^2/12) ≤ 4eps()\n\n    x = fejernodes2(Float64, N)\n    μ = chebyshevmoments2(Float64, N)\n    w = fejerweights2(μ)\n    @test norm(dot(f.(x), w)-2sinh(1)) ≤ 4eps()\n\n    μ = chebyshevjacobimoments2(Float64, N, 0.25, 0.35)\n    w = fejerweights2(μ)\n    @test norm(dot(f.(x), w)-2.0351088204147243) ≤ 4eps()\n\n    μ = chebyshevlogmoments2(Float64, N)\n    w = fejerweights2(μ)\n    @test norm(sum(w./(3 .- x)) - π^2/12) ≤ 4eps()\nend\n"
  },
  {
    "path": "test/runtests.jl",
    "content": "using FastTransforms, LinearAlgebra, Test\n\ninclude(\"specialfunctionstests.jl\")\ninclude(\"chebyshevtests.jl\")\ninclude(\"quadraturetests.jl\")\ninclude(\"libfasttransformstests.jl\")\ninclude(\"nuffttests.jl\")\ninclude(\"paduatests.jl\")\ninclude(\"gaunttests.jl\")\ninclude(\"hermitetests.jl\")\ninclude(\"toeplitzplanstests.jl\")\ninclude(\"toeplitzhankeltests.jl\")\ninclude(\"toeplitzplushankeltests.jl\")\ninclude(\"grammatrixtests.jl\")\ninclude(\"arraystests.jl\")\n"
  },
  {
    "path": "test/specialfunctionstests.jl",
    "content": "using FastTransforms, LinearAlgebra, Test\n\nimport FastTransforms: pochhammer, sqrtpi, gamma, lgamma\nimport FastTransforms: Cnλ, Λ, lambertw, Cnαβ, Anαβ\nimport FastTransforms: chebyshevmoments1, chebyshevmoments2, chebyshevjacobimoments1, chebyshevjacobimoments2, chebyshevlogmoments1, chebyshevlogmoments2\n\n@testset \"Special functions\" begin\n    @test pochhammer(2,3) == 24\n    @test pochhammer(0.5,3) == 0.5*1.5*2.5\n    @test pochhammer(0.5,0.5) == 1/sqrtpi\n    @test pochhammer(0,1) == 0\n    @test pochhammer(-1,2) == 0\n    @test pochhammer(-5,3) == -60\n    @test pochhammer(-1,-0.5) == 0\n    @test 1.0/pochhammer(-0.5,-0.5) == 0\n    @test pochhammer(-1+0im,-1) == -0.5\n    @test pochhammer(2,1) == pochhammer(2,1.0) == pochhammer(2.0,1) == 2\n    @test pochhammer(1.1,2.2) ≈ gamma(3.3)/gamma(1.1)\n    @test pochhammer(-2,1) == pochhammer(-2,1.0) == pochhammer(-2.0,1) == -2\n\n    n = 0:1000\n    λ = 0.125\n    @test norm(Cnλ.(n, λ) ./ Cnλ.(n, big(λ)) .- 1, Inf) < 3eps()\n\n    x = range(0, stop=20, length=81)\n    @test norm((Λ.(x) .- Λ.(big.(x)))./Λ.(x), Inf) < 2eps()\n    @test norm((lambertw.(x) .- lambertw.(big.(x)))./max.(lambertw.(x), 1), Inf) < 2eps()\n\n    x = 0:0.5:1000\n    λ₁, λ₂ = 0.125, 0.875\n    @test norm((Λ.(x,λ₁,λ₂) .- Λ.(big.(x),big(λ₁),big(λ₂)))./Λ.(big.(x),big(λ₁),big(λ₂)), Inf) < 4eps()\n    λ₁, λ₂ = 1//3, 2//3\n    @test norm((Λ.(x,Float64(λ₁),Float64(λ₂)) .- Λ.(big.(x),big(λ₁),big(λ₂))) ./ Λ.(big.(x),big(λ₁),big(λ₂)), Inf) < 4eps()\n\n    α, β = 0.125, 0.375\n\n    @test norm(Cnαβ.(n,α,β) ./ Cnαβ.(n,big(α),big(β)) .- 1, Inf) < 3eps()\n    @test norm(Anαβ.(n,α,β) ./ Anαβ.(n,big(α),big(β)) .- 1, Inf) < 4eps()\n\n    @testset \"BigFloat bug\" begin\n        @test Λ(0.0, -1/2, 1.0) ≈ -exp(lgamma(-1/2)-lgamma(1.0))\n        @test Λ(1.0, -1/2, 1.0) ≈ exp(lgamma(1-1/2)-lgamma(2.0))\n        @test Float64(Λ(big(0.0), -1/2, 1.0)) ≈ Λ(0.0, -1/2, 1.0)\n    end\nend\n"
  },
  {
    "path": "test/toeplitzhankeltests.jl",
    "content": "using FastTransforms, Test, Random\nimport FastTransforms: th_leg2cheb, th_cheb2leg, th_leg2chebu, th_ultra2ultra,th_jac2jac, th_leg2chebu,\n                        lib_leg2cheb, lib_cheb2leg, lib_ultra2ultra, lib_jac2jac,\n                        plan_th_cheb2leg!, plan_th_leg2chebu!, plan_th_leg2cheb!, plan_th_ultra2ultra!, plan_th_jac2jac!,\n                        th_cheb2jac, th_jac2cheb\n\nRandom.seed!(0)\n\n@testset \"ToeplitzHankel\" begin\n    for x in ([1.0], [1.0,2,3,4,5], [1.0+im,2-3im,3+4im,4-5im,5+10im], collect(1.0:1000))\n        @test th_leg2cheb(x) ≈ lib_leg2cheb(x)\n        @test th_cheb2leg(x) ≈ lib_cheb2leg(x)\n        @test th_leg2chebu(x) ≈ lib_ultra2ultra(x, 0.5, 1.0)\n        @test th_ultra2ultra(x,0.1, 0.2) ≈ lib_ultra2ultra(x, 0.1, 0.2)\n        @test th_ultra2ultra(x,1, 2) ≈ lib_ultra2ultra(x, 1, 2)\n        @test th_ultra2ultra(x,0.1, 2.2) ≈ lib_ultra2ultra(x, 0.1, 2.2)\n        @test th_ultra2ultra(x, 2.2, 0.1) ≈ lib_ultra2ultra(x, 2.2, 0.1)\n        @test th_ultra2ultra(x, 1, 3) ≈ lib_ultra2ultra(x, 1, 3)\n        @test @inferred(th_jac2jac(x,0.1, 0.2,0.1,0.4)) ≈ lib_jac2jac(x, 0.1, 0.2,0.1,0.4)\n        @test th_jac2jac(x,0.1, 0.2,0.3,0.2) ≈ lib_jac2jac(x, 0.1, 0.2,0.3,0.2)\n        @test th_jac2jac(x,0.1, 0.2,0.3,0.4) ≈ lib_jac2jac(x, 0.1, 0.2,0.3,0.4)\n        @test @inferred(th_jac2jac(x,0.1, 0.2,1.3,0.4)) ≈ lib_jac2jac(x, 0.1, 0.2,1.3,0.4)\n        @test th_jac2jac(x,0.1, 0.2,1.3,2.4) ≈ lib_jac2jac(x, 0.1, 0.2,1.3,2.4)\n        @test th_jac2jac(x,1.3,2.4, 0.1, 0.2) ≈ lib_jac2jac(x,1.3,2.4, 0.1, 0.2)\n        @test th_jac2jac(x,1.3, 1.2,-0.1,-0.2) ≈ lib_jac2jac(x, 1.3, 1.2,-0.1,-0.2)\n        @test @inferred(th_jac2jac(x,-0.5, -0.5, -0.5,-0.5)) ≈ lib_jac2jac(x, -0.5, -0.5, -0.5,-0.5)\n        @test th_jac2jac(x,-0.5, -0.5, 0.5,0.5) ≈ lib_jac2jac(x, -0.5, -0.5, 0.5,0.5)\n        @test th_jac2jac(x,0.5,0.5,-0.5, -0.5) ≈ lib_jac2jac(x, 0.5,0.5,-0.5, -0.5)\n        @test th_jac2jac(x,-0.5, -0.5, 0.5,-0.5) ≈ lib_jac2jac(x, -0.5, -0.5, 0.5,-0.5)\n        @test th_jac2jac(x, -1/2,-1/2,1/2,0) ≈ lib_jac2jac(x, -1/2,-1/2,1/2,0)\n        @test th_jac2jac(x, -1/2,-1/2,0,1/2) ≈ lib_jac2jac(x, -1/2,-1/2,0,1/2)\n        @test th_jac2jac(x, -3/4,-3/4,0,3/4) ≈ lib_jac2jac(x, -3/4,-3/4,0,3/4)\n        if length(x) < 10\n            @test th_jac2jac(x,0, 0, 5, 5) ≈ lib_jac2jac(x, 0, 0, 5, 5)\n            @test th_jac2jac(x, 5, 5, 0, 0) ≈ lib_jac2jac(x,  5, 5, 0, 0)\n        end\n\n        @test th_cheb2jac(x, 0.2, 0.3) ≈ cheb2jac(x, 0.2, 0.3)\n        @test th_jac2cheb(x, 0.2, 0.3) ≈ jac2cheb(x, 0.2, 0.3)\n        @test th_cheb2jac(x, 1, 1) ≈ cheb2jac(x, 1, 1)\n        @test th_jac2cheb(x, 1, 1) ≈ jac2cheb(x, 1, 1)\n\n        @test th_cheb2leg(th_leg2cheb(x)) ≈ x\n        @test th_leg2cheb(th_cheb2leg(x)) ≈ x\n        @test th_ultra2ultra(th_ultra2ultra(x, 0.1, 0.6), 0.6, 0.1) ≈ x\n        @test th_jac2jac(th_jac2jac(x, 0.1, 0.6, 0.1, 0.8), 0.1, 0.8, 0.1, 0.6) ≈ x\n        @test th_jac2jac(th_jac2jac(x, 0.1, 0.6, 0.2, 0.8), 0.2, 0.8, 0.1, 0.6) ≈ x\n    end\n\n    for X in (randn(5,4), randn(5,4) + im*randn(5,4))\n        @test th_leg2cheb(X, 1) ≈ hcat([leg2cheb(X[:,j]) for j=1:size(X,2)]...)\n        @test_broken th_leg2cheb(X, 1) ≈ leg2cheb(X, 1) # matrices not supported in FastTransforms\n        @test th_leg2cheb(X, 2) ≈ vcat([permutedims(leg2cheb(X[k,:])) for k=1:size(X,1)]...)\n        @test_broken th_leg2cheb(X, 2) ≈ leg2cheb(X, 2)\n        @test th_leg2cheb(X) ≈ th_leg2cheb(th_leg2cheb(X, 1), 2)\n        @test_broken th_leg2cheb(X) ≈ leg2cheb(X)\n\n        @test th_cheb2leg(X, 1) ≈ hcat([cheb2leg(X[:,j]) for j=1:size(X,2)]...)\n        @test th_cheb2leg(X, 2) ≈ vcat([permutedims(cheb2leg(X[k,:])) for k=1:size(X,1)]...)\n        @test th_cheb2leg(X) ≈ th_cheb2leg(th_cheb2leg(X, 1), 2)\n\n        @test th_cheb2leg(X) == plan_th_cheb2leg!(X, 1:2)*copy(X)\n        @test th_leg2cheb(X) == plan_th_leg2cheb!(X, 1:2)*copy(X)\n\n        @test th_leg2cheb(th_cheb2leg(X)) ≈ X\n\n        @test th_leg2chebu(X, 1) ≈ hcat([ultra2ultra(X[:,j], 0.5, 1.0) for j=1:size(X,2)]...)\n        @test th_leg2chebu(X, 2) ≈ vcat([permutedims(ultra2ultra(X[k,:], 0.5, 1.0)) for k=1:size(X,1)]...)\n        @test th_leg2chebu(X) ≈ th_leg2chebu(th_leg2chebu(X, 1), 2)\n\n        @test th_leg2chebu(X) == plan_th_leg2chebu!(X, 1:2)*copy(X)\n\n        @test th_ultra2ultra(X, 0.1, 0.6, 1) ≈ hcat([ultra2ultra(X[:,j], 0.1, 0.6) for j=1:size(X,2)]...)\n        @test th_ultra2ultra(X, 0.1, 0.6, 2) ≈ vcat([permutedims(ultra2ultra(X[k,:], 0.1, 0.6)) for k=1:size(X,1)]...)\n        @test th_ultra2ultra(X, 0.1, 0.6) ≈ th_ultra2ultra(th_ultra2ultra(X, 0.1, 0.6, 1), 0.1, 0.6, 2)\n\n        @test th_ultra2ultra(X, 0.1, 2.6, 1) ≈ hcat([ultra2ultra(X[:,j], 0.1, 2.6) for j=1:size(X,2)]...)\n        @test th_ultra2ultra(X, 0.1, 2.6, 2) ≈ vcat([permutedims(ultra2ultra(X[k,:], 0.1, 2.6)) for k=1:size(X,1)]...)\n        @test th_ultra2ultra(X, 0.1, 2.6) ≈ th_ultra2ultra(th_ultra2ultra(X, 0.1, 2.6, 1), 0.1, 2.6, 2)\n\n        @test th_ultra2ultra(X, 2.6, 0.1, 1) ≈ hcat([ultra2ultra(X[:,j], 2.6, 0.1) for j=1:size(X,2)]...)\n        @test th_ultra2ultra(X, 2.6, 0.1, 2) ≈ vcat([permutedims(ultra2ultra(X[k,:], 2.6, 0.1)) for k=1:size(X,1)]...)\n        @test th_ultra2ultra(X, 2.6, 0.1) ≈ th_ultra2ultra(th_ultra2ultra(X, 2.6, 0.1, 1), 2.6, 0.1, 2)\n\n        @test th_ultra2ultra(X, 0.1, 0.6) == plan_th_ultra2ultra!(X, 0.1, 0.6, 1:2)*copy(X)\n        @test th_ultra2ultra(X, 0.1, 0.6) == plan_th_ultra2ultra!(X, 0.1, 0.6, 1:2)*copy(X)\n\n        @test th_ultra2ultra(th_ultra2ultra(X, 0.1, 0.6), 0.6, 0.1) ≈ X\n\n        @test th_jac2jac(X, 0.1, 0.6, 0.1, 0.8, 1) ≈ hcat([jac2jac(X[:,j], 0.1, 0.6, 0.1, 0.8) for j=1:size(X,2)]...)\n        @test th_jac2jac(X, 0.1, 0.6, 0.1, 0.8, 2) ≈ vcat([permutedims(jac2jac(X[k,:], 0.1, 0.6, 0.1, 0.8)) for k=1:size(X,1)]...)\n        @test th_jac2jac(X, 0.1, 0.6, 0.1, 0.8) ≈ th_jac2jac(th_jac2jac(X, 0.1, 0.6, 0.1, 0.8, 1), 0.1, 0.6, 0.1, 0.8, 2)\n\n        @test th_jac2jac(X, 0.1, 0.6, 0.2, 0.8, 1) ≈ hcat([jac2jac(X[:,j], 0.1, 0.6, 0.2, 0.8) for j=1:size(X,2)]...)\n        @test th_jac2jac(X, 0.1, 0.6, 0.2, 0.8, 2) ≈ vcat([permutedims(jac2jac(X[k,:], 0.1, 0.6, 0.2, 0.8)) for k=1:size(X,1)]...)\n\n        @test th_jac2jac(X, 0.1, 0.6, 0.1, 0.8) == plan_th_jac2jac!(X, 0.1, 0.6, 0.1, 0.8, 1:2)*copy(X)\n        @test th_jac2jac(X, 0.1, 0.6, 0.1, 0.8) == plan_th_jac2jac!(X, 0.1, 0.6, 0.1, 0.8, 1:2)*copy(X)\n\n        @test th_jac2jac(th_jac2jac(X, 0.1, 0.6, 0.1, 0.8), 0.1, 0.8, 0.1, 0.6) ≈ X\n\n        @test th_jac2jac(X, 0.1, 0.6, 3.1, 2.8, 1) ≈ hcat([jac2jac(X[:,j], 0.1, 0.6, 3.1, 2.8) for j=1:size(X,2)]...)\n        @test th_jac2jac(X, 0.1, 0.6, 3.1, 2.8, 2) ≈ vcat([permutedims(jac2jac(X[k,:], 0.1, 0.6, 3.1, 2.8)) for k=1:size(X,1)]...)\n        @test th_jac2jac(X, 0.1, 0.6, 3.1, 2.8) ≈ th_jac2jac(th_jac2jac(X, 0.1, 0.6, 3.1, 2.8, 1), 0.1, 0.6, 3.1, 2.8, 2)\n\n        @test th_jac2jac(X, -0.5, -0.5, 3.1, 2.8, 1) ≈ hcat([jac2jac(X[:,j], -0.5, -0.5, 3.1, 2.8) for j=1:size(X,2)]...)\n        @test th_jac2jac(X, -0.5, -0.5, 3.1, 2.8, 2) ≈ vcat([permutedims(jac2jac(X[k,:], -0.5, -0.5, 3.1, 2.8)) for k=1:size(X,1)]...)\n        @test th_jac2jac(X, -0.5, -0.5, 3.1, 2.8) ≈ th_jac2jac(th_jac2jac(X, -0.5, -0.5, 3.1, 2.8, 1), -0.5, -0.5, 3.1, 2.8, 2)\n\n        @test th_cheb2jac(X, 3.1, 2.8, 1) ≈ hcat([cheb2jac(X[:,j], 3.1, 2.8) for j=1:size(X,2)]...)\n        @test th_cheb2jac(X, 3.1, 2.8, 2) ≈ vcat([permutedims(cheb2jac(X[k,:], 3.1, 2.8)) for k=1:size(X,1)]...)\n        @test th_cheb2jac(X, 3.1, 2.8) ≈ th_cheb2jac(th_cheb2jac(X, 3.1, 2.8, 1), 3.1, 2.8, 2)\n\n        @test th_jac2cheb(X, 3.1, 2.8, 1) ≈ hcat([jac2cheb(X[:,j], 3.1, 2.8) for j=1:size(X,2)]...)\n        @test th_jac2cheb(X, 3.1, 2.8, 2) ≈ vcat([permutedims(jac2cheb(X[k,:], 3.1, 2.8)) for k=1:size(X,1)]...)\n        @test th_jac2cheb(X, 3.1, 2.8) ≈ th_jac2cheb(th_jac2cheb(X, 3.1, 2.8, 1), 3.1, 2.8, 2)\n    end\n\n    @testset \"BigFloat\" begin\n        n = 10\n        x = big.(collect(1.0:n))\n        @test th_leg2cheb(x) ≈ lib_leg2cheb(x)\n        @test th_cheb2leg(x) ≈ lib_cheb2leg(x)\n    end\n\n    @testset \"jishnub example\" begin\n        x = chebyshevpoints(4096);\n        f = x -> cospi(1000x);  \n        y = f.(x);\n        v = th_cheb2leg(chebyshevtransform(y))\n        @test norm(v - th_cheb2leg(th_leg2cheb(v)), Inf) ≤ 1E-13\n        @test norm(v - th_cheb2leg(th_leg2cheb(v)))/norm(v) ≤ 1E-14\n    end\n\n    @testset \"tensor\" begin\n        X = randn(5,4,3)\n        for trans in (th_leg2cheb, th_cheb2leg)\n            Y = trans(X, 1)\n            for ℓ = 1:size(X,3)\n                @test Y[:,:,ℓ] ≈ trans(X[:,:,ℓ],1)\n            end\n            Y = trans(X, 2)\n            for ℓ = 1:size(X,3)\n                @test Y[:,:,ℓ] ≈ trans(X[:,:,ℓ],2)\n            end\n            Y = trans(X, 3)\n            for j = 1:size(X,2)\n                @test Y[:,j,:] ≈ trans(X[:,j,:],2)\n            end\n\n            Y = trans(X, (1,3))\n            for j = 1:size(X,2)\n                @test Y[:,j,:] ≈ trans(X[:,j,:])\n            end \n\n            Y = trans(X, 1:3)\n            M = copy(X)\n            for j = 1:size(X,3)\n                M[:,:,j] = trans(M[:,:,j])\n            end\n            for k = 1:size(X,1), j=1:size(X,2)\n                M[k,j,:] = trans(M[k,j,:])\n            end\n            @test M ≈ Y\n        end\n    end\n\n    @testset \"inv\" begin\n        x = randn(10)\n        pl = plan_th_cheb2leg!(x)\n        @test size(pl) == (10,)\n        @test pl\\(pl*x) ≈ x\n\n        X = randn(10,3)\n        for pl in (plan_th_cheb2leg!(X), plan_th_cheb2leg!(X, 1), plan_th_cheb2leg!(X, 2))\n            @test size(pl) == (10,3)\n            @test pl\\(pl*copy(X)) ≈ X\n        end\n\n        X = randn(10,3,5)\n        for pl in (plan_th_cheb2leg!(X), plan_th_cheb2leg!(X, 1), plan_th_cheb2leg!(X, 2),  plan_th_cheb2leg!(X, 3))\n            @test size(pl) == (10,3,5)\n            @test pl\\(pl*copy(X)) ≈ X\n        end\n    end\n\n    @testset \"empty\" begin\n        @test isempty(FastTransforms.th_cheb2leg(Float64[]))\n        @test isempty(FastTransforms.th_leg2cheb(Float64[]))\n        @test isempty(FastTransforms.th_leg2chebu(Float64[]))\n        @test isempty(FastTransforms.th_cheb2jac(Float64[], 0.1, 0.2))\n        @test isempty(FastTransforms.th_jac2cheb(Float64[], 0.1, 0.2))\n        @test isempty(FastTransforms.th_ultra2ultra(Float64[], 0.1, 0.2))\n    end\nend"
  },
  {
    "path": "test/toeplitzplanstests.jl",
    "content": "using FastTransforms, Test\nimport FastTransforms: plan_uppertoeplitz!\n\n@testset \"ToeplitzPlan\" begin\n    @testset \"Vector\" begin\n        P = plan_uppertoeplitz!([1,2,3])\n        T = [1 2 3; 0 1 2; 0 0 1]\n        x = randn(3)\n        @test P * copy(x) ≈ T * x\n    end\n\n    @testset \"Matrix\" begin\n        T = [1 2 3; 0 1 2; 0 0 1]\n\n        X = randn(3,3)\n        P = plan_uppertoeplitz!([1,2,3], size(X), 1)\n        @test P * copy(X) ≈ T * X\n        P = plan_uppertoeplitz!([1,2,3], size(X), 2)\n        @test P * copy(X) ≈ X * T'\n\n        P = plan_uppertoeplitz!([1,2,3], size(X))\n        @test P * copy(X) ≈ T * X * T'\n\n        X = randn(3,4)\n        P1 = plan_uppertoeplitz!([1,2,3], size(X), 1)\n        @test P1 * copy(X) ≈ T * X\n        P2 = plan_uppertoeplitz!([1,2,3,4], size(X), 2)\n        T̃ = [1 2 3 4; 0 1 2 3; 0 0 1 2; 0 0 0 1]\n        @test P2 * copy(X) ≈ X * T̃'\n        P = plan_uppertoeplitz!([1,2,3,4], size(X))\n        @test P * copy(X) ≈ T * X * T̃'\n    end\n\n    @testset \"Tensor\" begin\n        T = [1 2 3; 0 1 2; 0 0 1]\n        \n        @testset \"3D\" begin\n            X = randn(3,3,3)\n            P = plan_uppertoeplitz!([1,2,3], size(X), 1)\n            PX = P * copy(X)\n            for ℓ = 1:size(X,3)\n                @test PX[:,:,ℓ] ≈ T*X[:,:,ℓ]\n            end\n\n            P = plan_uppertoeplitz!([1,2,3], size(X), 2)\n            PX = P * copy(X)\n            for ℓ = 1:size(X,3)\n                @test PX[:,:,ℓ] ≈ X[:,:,ℓ]*T'\n            end\n\n            P = plan_uppertoeplitz!([1,2,3], size(X), 3)\n            PX = P * copy(X)\n            for j = 1:size(X,2)\n                @test PX[:,j,:] ≈ X[:,j,:]*T'\n            end\n\n            P = plan_uppertoeplitz!([1,2,3], size(X), (1,3))\n            PX = P * copy(X)\n            for j = 1:size(X,2)\n                @test PX[:,j,:] ≈ T*X[:,j,:]*T'\n            end\n\n            P = plan_uppertoeplitz!([1,2,3], size(X), 1:3)\n            PX = P * copy(X)\n            M = copy(X)\n            for j = 1:size(X,3)\n                M[:,:,j] = T*M[:,:,j]*T'\n            end\n            for k = 1:size(X,1)\n                M[k,:,:] = M[k,:,:]*T'\n            end\n            @test M ≈ PX\n        end\n\n        @testset \"4D\" begin\n            X = randn(3,3,3,3)\n            P = plan_uppertoeplitz!([1,2,3], size(X), 1)\n            PX = P * copy(X)\n            for ℓ = 1:size(X,3), m = 1:size(X,4)\n                @test PX[:,:,ℓ,m] ≈ T*X[:,:,ℓ,m]\n            end\n\n            P = plan_uppertoeplitz!([1,2,3], size(X), 2)\n            PX = P * copy(X)\n            for ℓ = 1:size(X,3), m = 1:size(X,4)\n                @test PX[:,:,ℓ,m] ≈ X[:,:,ℓ,m]*T'\n            end\n\n            P = plan_uppertoeplitz!([1,2,3], size(X), 3)\n            PX = P * copy(X)\n            for j = 1:size(X,2), m = 1:size(X,4)\n                @test PX[:,j,:,m] ≈ X[:,j,:,m]*T'\n            end\n\n            P = plan_uppertoeplitz!([1,2,3], size(X), 4)\n            PX = P * copy(X)\n            for k = 1:size(X,1), j = 1:size(X,2)\n                @test PX[k,j,:,:] ≈ X[k,j,:,:]*T'\n            end\n\n            P = plan_uppertoeplitz!([1,2,3], size(X), (1,3))\n            PX = P * copy(X)\n            for j = 1:size(X,2), m=1:size(X,4)\n                @test PX[:,j,:,m] ≈ T*X[:,j,:,m]*T'\n            end\n\n            P = plan_uppertoeplitz!([1,2,3], size(X), 1:4)\n            PX = P * copy(X)\n            M = copy(X)\n            for ℓ = 1:size(X,3), m = 1:size(X,4)\n                M[:,:,ℓ,m] = T*M[:,:,ℓ,m]*T'\n            end\n            for k = 1:size(X,1), j = 1:size(X,2)\n                M[k,j,:,:] = T*M[k,j,:,:]*T'\n            end\n            @test M ≈ PX\n        end\n    end\n\n    @testset \"BigFloat\" begin\n        P = plan_uppertoeplitz!([big(π),2,3])\n        T = [big(π) 2 3; 0 big(π) 2; 0 0 big(π)]\n        x = randn(3)\n        @test P * copy(x) ≈ T * x\n    end\nend"
  },
  {
    "path": "test/toeplitzplushankeltests.jl",
    "content": "using FastTransforms, LinearAlgebra, Test\n\nimport FastTransforms: normest\n\n@testset \"ToeplitzPlusHankel\" begin\n    n = 128\n    for T in (Float32, Float64)\n        μ = FastTransforms.chebyshevmoments1(T, 2n-1)\n        G = ChebyshevGramMatrix(μ)\n        TpH = ToeplitzPlusHankel(G)\n        @test TpH ≈ G\n        @test norm(TpH) ≤ normest(TpH)\n        @test normest(TpH) == normest(G)\n    end\nend\n"
  }
]