blob: 2b34443716fab8418dadf61e54c8d21411f32cca [file]
// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2009 Mark Borgerding mark a borgerding net
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// SPDX-License-Identifier: MPL-2.0
#ifndef EIGEN_FFT_FFTW_IMPL_H
#define EIGEN_FFT_FFTW_IMPL_H
// IWYU pragma: private
#include "./InternalHeaderCheck.h"
#include <memory>
#include <mutex>
namespace Eigen {
namespace internal {
// FFTW uses non-const arguments,
// so const_cast is needed for all the args it uses.
//
// This should be safe as long as
// 1. we use FFTW_ESTIMATE for all our planning
// see the FFTW docs section 4.3.2 "Planner Flags"
// 2. fftw_complex is compatible with std::complex
// This assumes std::complex<T> layout is array of size 2 with real,imag
template <typename T>
inline T *fftw_cast(const T *p) {
return const_cast<T *>(p);
}
inline fftw_complex *fftw_cast(const std::complex<double> *p) {
return const_cast<fftw_complex *>(reinterpret_cast<const fftw_complex *>(p));
}
inline fftwf_complex *fftw_cast(const std::complex<float> *p) {
return const_cast<fftwf_complex *>(reinterpret_cast<const fftwf_complex *>(p));
}
inline fftwl_complex *fftw_cast(const std::complex<long double> *p) {
return const_cast<fftwl_complex *>(reinterpret_cast<const fftwl_complex *>(p));
}
// The FFTW planner is not thread-safe: fftw_execute and its new-array variants,
// which is what this backend runs transforms through, are the only entry points
// that may be called concurrently (FFTW manual, "Thread safety"), so plan
// creation and destruction are serialized through this mutex. A template
// static gives the header-only definition; std::mutex is
// constexpr-constructible, so the mutex is ready before any thread starts.
// The planner state it stands in for is one per process, so the mutex must be
// too: the explicit default visibility is what keeps the definition from being
// bound locally under -fvisibility=hidden, where each library planning through
// Eigen would get a mutex of its own and serialize nothing between them. The
// module documentation covers what no header can reach, which needs FFTW's own
// fftw_make_planner_thread_safe().
#if EIGEN_HAS_ATTRIBUTE(visibility) && !EIGEN_OS_WIN
#define EIGEN_FFTW_PLANNER_MUTEX_VISIBILITY __attribute__((visibility("default")))
#else
#define EIGEN_FFTW_PLANNER_MUTEX_VISIBILITY
#endif
template <typename Dummy = void>
struct fftw_planner_lock {
static EIGEN_FFTW_PLANNER_MUTEX_VISIBILITY std::mutex mutex;
};
template <typename Dummy>
EIGEN_FFTW_PLANNER_MUTEX_VISIBILITY std::mutex fftw_planner_lock<Dummy>::mutex;
inline std::mutex &fftw_planner_mutex() { return fftw_planner_lock<>::mutex; }
template <typename PlanFactory>
inline decltype(auto) fftw_make_plan(PlanFactory factory) {
std::lock_guard<std::mutex> lock(fftw_planner_mutex());
return factory();
}
template <typename T>
struct fftw_plan {};
template <>
struct fftw_plan<float> {
typedef float scalar_type;
typedef fftwf_complex complex_type;
std::shared_ptr<fftwf_plan_s> m_plan;
fftw_plan() = default;
void set_plan(fftwf_plan p) {
m_plan.reset(p, [](fftwf_plan plan) {
std::lock_guard<std::mutex> lock(fftw_planner_mutex());
fftwf_destroy_plan(plan);
});
}
inline void fwd(complex_type *dst, complex_type *src, int nfft) {
if (!m_plan)
set_plan(fftw_make_plan(
[&] { return fftwf_plan_dft_1d(nfft, src, dst, FFTW_FORWARD, FFTW_ESTIMATE | FFTW_PRESERVE_INPUT); }));
fftwf_execute_dft(m_plan.get(), src, dst);
}
inline void inv(complex_type *dst, complex_type *src, int nfft) {
if (!m_plan)
set_plan(fftw_make_plan(
[&] { return fftwf_plan_dft_1d(nfft, src, dst, FFTW_BACKWARD, FFTW_ESTIMATE | FFTW_PRESERVE_INPUT); }));
fftwf_execute_dft(m_plan.get(), src, dst);
}
inline void fwd(complex_type *dst, scalar_type *src, int nfft) {
if (!m_plan)
set_plan(
fftw_make_plan([&] { return fftwf_plan_dft_r2c_1d(nfft, src, dst, FFTW_ESTIMATE | FFTW_PRESERVE_INPUT); }));
fftwf_execute_dft_r2c(m_plan.get(), src, dst);
}
inline void inv(scalar_type *dst, complex_type *src, int nfft) {
if (!m_plan)
set_plan(
fftw_make_plan([&] { return fftwf_plan_dft_c2r_1d(nfft, src, dst, FFTW_ESTIMATE | FFTW_PRESERVE_INPUT); }));
fftwf_execute_dft_c2r(m_plan.get(), src, dst);
}
inline void fwd2(complex_type *dst, complex_type *src, int n0, int n1) {
if (!m_plan)
set_plan(fftw_make_plan(
[&] { return fftwf_plan_dft_2d(n0, n1, src, dst, FFTW_FORWARD, FFTW_ESTIMATE | FFTW_PRESERVE_INPUT); }));
fftwf_execute_dft(m_plan.get(), src, dst);
}
inline void inv2(complex_type *dst, complex_type *src, int n0, int n1) {
if (!m_plan)
set_plan(fftw_make_plan(
[&] { return fftwf_plan_dft_2d(n0, n1, src, dst, FFTW_BACKWARD, FFTW_ESTIMATE | FFTW_PRESERVE_INPUT); }));
fftwf_execute_dft(m_plan.get(), src, dst);
}
};
template <>
struct fftw_plan<double> {
typedef double scalar_type;
typedef fftw_complex complex_type;
std::shared_ptr<fftw_plan_s> m_plan;
fftw_plan() = default;
void set_plan(::fftw_plan p) {
m_plan.reset(p, [](::fftw_plan plan) {
std::lock_guard<std::mutex> lock(fftw_planner_mutex());
fftw_destroy_plan(plan);
});
}
inline void fwd(complex_type *dst, complex_type *src, int nfft) {
if (!m_plan)
set_plan(fftw_make_plan(
[&] { return fftw_plan_dft_1d(nfft, src, dst, FFTW_FORWARD, FFTW_ESTIMATE | FFTW_PRESERVE_INPUT); }));
fftw_execute_dft(m_plan.get(), src, dst);
}
inline void inv(complex_type *dst, complex_type *src, int nfft) {
if (!m_plan)
set_plan(fftw_make_plan(
[&] { return fftw_plan_dft_1d(nfft, src, dst, FFTW_BACKWARD, FFTW_ESTIMATE | FFTW_PRESERVE_INPUT); }));
fftw_execute_dft(m_plan.get(), src, dst);
}
inline void fwd(complex_type *dst, scalar_type *src, int nfft) {
if (!m_plan)
set_plan(
fftw_make_plan([&] { return fftw_plan_dft_r2c_1d(nfft, src, dst, FFTW_ESTIMATE | FFTW_PRESERVE_INPUT); }));
fftw_execute_dft_r2c(m_plan.get(), src, dst);
}
inline void inv(scalar_type *dst, complex_type *src, int nfft) {
if (!m_plan)
set_plan(
fftw_make_plan([&] { return fftw_plan_dft_c2r_1d(nfft, src, dst, FFTW_ESTIMATE | FFTW_PRESERVE_INPUT); }));
fftw_execute_dft_c2r(m_plan.get(), src, dst);
}
inline void fwd2(complex_type *dst, complex_type *src, int n0, int n1) {
if (!m_plan)
set_plan(fftw_make_plan(
[&] { return fftw_plan_dft_2d(n0, n1, src, dst, FFTW_FORWARD, FFTW_ESTIMATE | FFTW_PRESERVE_INPUT); }));
fftw_execute_dft(m_plan.get(), src, dst);
}
inline void inv2(complex_type *dst, complex_type *src, int n0, int n1) {
if (!m_plan)
set_plan(fftw_make_plan(
[&] { return fftw_plan_dft_2d(n0, n1, src, dst, FFTW_BACKWARD, FFTW_ESTIMATE | FFTW_PRESERVE_INPUT); }));
fftw_execute_dft(m_plan.get(), src, dst);
}
};
template <>
struct fftw_plan<long double> {
typedef long double scalar_type;
typedef fftwl_complex complex_type;
std::shared_ptr<fftwl_plan_s> m_plan;
fftw_plan() = default;
void set_plan(fftwl_plan p) {
m_plan.reset(p, [](fftwl_plan plan) {
std::lock_guard<std::mutex> lock(fftw_planner_mutex());
fftwl_destroy_plan(plan);
});
}
inline void fwd(complex_type *dst, complex_type *src, int nfft) {
if (!m_plan)
set_plan(fftw_make_plan(
[&] { return fftwl_plan_dft_1d(nfft, src, dst, FFTW_FORWARD, FFTW_ESTIMATE | FFTW_PRESERVE_INPUT); }));
fftwl_execute_dft(m_plan.get(), src, dst);
}
inline void inv(complex_type *dst, complex_type *src, int nfft) {
if (!m_plan)
set_plan(fftw_make_plan(
[&] { return fftwl_plan_dft_1d(nfft, src, dst, FFTW_BACKWARD, FFTW_ESTIMATE | FFTW_PRESERVE_INPUT); }));
fftwl_execute_dft(m_plan.get(), src, dst);
}
inline void fwd(complex_type *dst, scalar_type *src, int nfft) {
if (!m_plan)
set_plan(
fftw_make_plan([&] { return fftwl_plan_dft_r2c_1d(nfft, src, dst, FFTW_ESTIMATE | FFTW_PRESERVE_INPUT); }));
fftwl_execute_dft_r2c(m_plan.get(), src, dst);
}
inline void inv(scalar_type *dst, complex_type *src, int nfft) {
if (!m_plan)
set_plan(
fftw_make_plan([&] { return fftwl_plan_dft_c2r_1d(nfft, src, dst, FFTW_ESTIMATE | FFTW_PRESERVE_INPUT); }));
fftwl_execute_dft_c2r(m_plan.get(), src, dst);
}
inline void fwd2(complex_type *dst, complex_type *src, int n0, int n1) {
if (!m_plan)
set_plan(fftw_make_plan(
[&] { return fftwl_plan_dft_2d(n0, n1, src, dst, FFTW_FORWARD, FFTW_ESTIMATE | FFTW_PRESERVE_INPUT); }));
fftwl_execute_dft(m_plan.get(), src, dst);
}
inline void inv2(complex_type *dst, complex_type *src, int n0, int n1) {
if (!m_plan)
set_plan(fftw_make_plan(
[&] { return fftwl_plan_dft_2d(n0, n1, src, dst, FFTW_BACKWARD, FFTW_ESTIMATE | FFTW_PRESERVE_INPUT); }));
fftwl_execute_dft(m_plan.get(), src, dst);
}
};
template <typename Scalar_>
struct fftw_impl {
typedef Scalar_ Scalar;
typedef std::complex<Scalar> Complex;
inline void clear() { m_plans.clear(); }
// complex-to-complex forward FFT
inline void fwd(Complex *dst, const Complex *src, int nfft) {
get_plan(nfft, false, /*real_io=*/false, dst, src).fwd(fftw_cast(dst), fftw_cast(src), nfft);
}
// real-to-complex forward FFT
inline void fwd(Complex *dst, const Scalar *src, int nfft) {
get_plan(nfft, false, /*real_io=*/true, dst, src).fwd(fftw_cast(dst), fftw_cast(src), nfft);
}
// 2-d complex-to-complex
inline void fwd2(Complex *dst, const Complex *src, int n0, int n1) {
get_plan(n0, n1, false, /*real_io=*/false, dst, src).fwd2(fftw_cast(dst), fftw_cast(src), n0, n1);
}
// inverse complex-to-complex
inline void inv(Complex *dst, const Complex *src, int nfft) {
get_plan(nfft, true, /*real_io=*/false, dst, src).inv(fftw_cast(dst), fftw_cast(src), nfft);
}
// half-complex to scalar
inline void inv(Scalar *dst, const Complex *src, int nfft) {
get_plan(nfft, true, /*real_io=*/true, dst, src).inv(fftw_cast(dst), fftw_cast(src), nfft);
}
// 2-d complex-to-complex
inline void inv2(Complex *dst, const Complex *src, int n0, int n1) {
get_plan(n0, n1, true, /*real_io=*/false, dst, src).inv2(fftw_cast(dst), fftw_cast(src), n0, n1);
}
protected:
typedef fftw_plan<Scalar> PlanData;
typedef Eigen::numext::int64_t int64_t;
typedef std::map<int64_t, PlanData> PlanMap;
PlanMap m_plans;
// Pack (inverse, real_io, inplace, aligned) into 4 contiguous low bits of
// the cache key. real_io distinguishes r2c/c2r from c2c so that reusing
// the same FFT object across real-input and complex-input transforms
// doesn't return a cached plan of the wrong kind.
static int64_t plan_flags(bool inverse, bool real_io, void *dst, const void *src) {
bool inplace = (dst == src);
bool aligned = ((reinterpret_cast<size_t>(src) & 15) | (reinterpret_cast<size_t>(dst) & 15)) == 0;
return (inverse << 3) | (real_io << 2) | (inplace << 1) | aligned;
}
inline PlanData &get_plan(int nfft, bool inverse, bool real_io, void *dst, const void *src) {
int64_t key = ((nfft << 4) | plan_flags(inverse, real_io, dst, src)) << 1;
return m_plans[key];
}
inline PlanData &get_plan(int n0, int n1, bool inverse, bool real_io, void *dst, const void *src) {
int64_t key = (((((int64_t)n0) << 31) | (n1 << 4) | plan_flags(inverse, real_io, dst, src)) << 1) + 1;
return m_plans[key];
}
};
} // end namespace internal
} // end namespace Eigen
#endif // EIGEN_FFT_FFTW_IMPL_H