21#include <unordered_map>
30#if EIGEN_MAJOR_VERSION >= 5
31#define EIGEN_LAST Eigen::placeholders::last
33#define EIGEN_LAST Eigen::last
42 Stimulus(
const unsigned int row,
const unsigned int col) : row{row}, col{col} {};
43 Stimulus(
const unsigned int row,
const unsigned int col,
const bool state) : row{row}, col{col}, state{state} {};
44 bool operator==(
const Stimulus &p)
const {
return (row == p.row && col == p.col); }
45 bool operator!=(
const Stimulus &p)
const {
return (row != p.row || col != p.col); }
54 Stimulus &set(
const bool s) {
62 friend std::ostream &operator<<(std::ostream &os,
const Stimulus &stimulus) {
63 os <<
"Stimulus(row: " << stimulus.row <<
", col: " << stimulus.col <<
", state: " << (stimulus.state ?
"on" :
"off") <<
")";
68class Stimuli :
public std::vector<Stimulus> {
69 using std::vector<Stimulus>::vector;
71 static inline uint64_t pack(
int row,
int col) {
72 return (
static_cast<uint64_t
>(
static_cast<uint32_t
>(row)) << 32) |
static_cast<uint32_t
>(col);
77 std::for_each(begin(), end(), [](Stimulus &stimulus) { stimulus.state =
true; });
80 std::for_each(begin(), end(), [](Stimulus &stimulus) { stimulus.state =
false; });
82 void set(
const bool state) {
83 std::for_each(begin(), end(), [state](Stimulus &stimulus) { stimulus.state = state; });
86 std::for_each(begin(), end(), [](Stimulus &stimulus) { stimulus.state = !stimulus.state; });
94 std::vector<Stimulus> out;
95 std::unordered_map<uint64_t, size_t> pos;
97 pos.reserve(size() * 2);
99 for(
const auto &s : *
this) {
100 uint64_t key = pack(s.row, s.col);
101 auto [it, inserted] = pos.emplace(key, out.size());
105 auto &chosen = out[it->second];
106 if(s.state && !chosen.state) {
111 this->assign(out.begin(), out.end());
115using cfloat = std::complex<float>;
116using cfloatmat = Eigen::Matrix<cfloat, Eigen::Dynamic, Eigen::Dynamic>;
118constexpr unsigned int LOG2(
const unsigned int n) {
return ((n < 2) ? 0 : 1 + LOG2(n >> 1U)); }
121#if __has_builtin(__builtin_ffs)
122#define EFFT_HAS_BUILTIN_FFS
125#ifdef EFFT_HAS_BUILTIN_FFS
126inline unsigned int log2i(
const unsigned int n) {
return __builtin_ffs(
static_cast<int>(n)) - 1; }
128inline unsigned int log2i(
const unsigned int n) {
return static_cast<unsigned int>(std::log2f(n)); }
131template <
unsigned int N>
134 static constexpr unsigned int LOG2_N = LOG2(N);
135 std::array<std::vector<cfloatmat>, LOG2_N + 1> tree_;
136 std::vector<cfloat> twiddle_;
138 fftw_complex *fftwInput_{
nullptr};
139 fftw_complex *fftwOutput_{
nullptr};
140 fftw_plan plan_{
nullptr};
145 constexpr float PI = 3.14159265358979323846F;
146 constexpr float MINUS_TWO_PI = -2 * PI;
147 twiddle_.resize(
static_cast<std::size_t
>(N) *
static_cast<std::size_t
>(N + 1));
149 fftwInput_ =
static_cast<fftw_complex *
>(fftw_malloc(
sizeof(fftw_complex) *
static_cast<std::size_t
>(N) *
static_cast<std::size_t
>(N)));
150 fftwOutput_ =
static_cast<fftw_complex *
>(fftw_malloc(
sizeof(fftw_complex) *
static_cast<std::size_t
>(N) *
static_cast<std::size_t
>(N)));
151 if(!fftwInput_ || !fftwOutput_)
throw std::bad_alloc();
153 for(
unsigned int i = 0; i < N; i++) {
154 twiddle_[i] = cfloat{1.0F, 0.0F};
155 for(
unsigned int n = 1; n <= N; n++) {
156 twiddle_[i + N * n] =
static_cast<cfloat
>(std::polar(1.0F, MINUS_TWO_PI *
static_cast<float>(i) /
static_cast<float>(n)));
163 if(plan_) fftw_destroy_plan(plan_);
164 if(fftwInput_) fftw_free(fftwInput_);
165 if(fftwOutput_) fftw_free(fftwOutput_);
169 eFFT(
const eFFT &) =
delete;
170 eFFT(eFFT &&) noexcept = default;
171 eFFT &operator=(const eFFT &) = delete;
172 eFFT &operator=(eFFT &&) noexcept = default;
178 [[nodiscard]] constexpr
unsigned int framesize()
const {
186 cfloatmat f0(cfloatmat::Zero(N, N));
197 const unsigned int n = x.rows();
199 tree_[0].emplace_back(x);
202 const unsigned int ndiv2 = n >> 1U;
203 const unsigned int idx = log2i(ndiv2);
205 cfloatmat s00(x(Eigen::seq(Eigen::fix<0>, EIGEN_LAST, Eigen::fix<2>), Eigen::seq(Eigen::fix<0>, EIGEN_LAST, Eigen::fix<2>)));
206 cfloatmat s01(x(Eigen::seq(Eigen::fix<0>, EIGEN_LAST, Eigen::fix<2>), Eigen::seq(Eigen::fix<1>, EIGEN_LAST, Eigen::fix<2>)));
207 cfloatmat s10(x(Eigen::seq(Eigen::fix<1>, EIGEN_LAST, Eigen::fix<2>), Eigen::seq(Eigen::fix<0>, EIGEN_LAST, Eigen::fix<2>)));
208 cfloatmat s11(x(Eigen::seq(Eigen::fix<1>, EIGEN_LAST, Eigen::fix<2>), Eigen::seq(Eigen::fix<1>, EIGEN_LAST, Eigen::fix<2>)));
214 const cfloatmat &x00 = tree_[idx][offset];
215 const cfloatmat &x01 = tree_[idx][offset + 1];
216 const cfloatmat &x10 = tree_[idx][offset + 2];
217 const cfloatmat &x11 = tree_[idx][offset + 3];
218 const unsigned int Nn = N * n;
219 for(
unsigned int i = 0; i < ndiv2; i++) {
220 for(
unsigned int j = 0; j < ndiv2; j++) {
221 const cfloat tu = twiddle_[j + Nn] * x01(i, j);
222 const cfloat td = twiddle_[(i + j) + Nn] * x11(i, j);
223 const cfloat ts = twiddle_[i + Nn] * x10(i, j);
225 const cfloat a = x00(i, j) + tu;
226 const cfloat b = x00(i, j) - tu;
227 const cfloat c = ts + td;
228 const cfloat d = ts - td;
231 x(i, j + ndiv2) = b + d;
232 x(i + ndiv2, j) = a - c;
233 x(i + ndiv2, j + ndiv2) = b - d;
236 tree_[log2i(n)].emplace_back(x);
255 const unsigned int n = x.rows();
257 return std::exchange(x(0, 0), p.state).real() !=
static_cast<float>(p.state);
259 const unsigned int ndiv2 = n >> 1U;
260 const unsigned int nndiv2 = n * ndiv2;
261 const unsigned int idx = log2i(ndiv2);
263 bool changed =
false;
264 if(
static_cast<bool>(p.row & 1U)) {
265 if(
static_cast<bool>(p.col & 1U)) {
266 changed =
update(tree_[idx][offset + 3], {p.row >> 1U, p.col >> 1U, p.state}, 4 * offset + 12);
268 changed =
update(tree_[idx][offset + 2], {p.row >> 1U, p.col >> 1U, p.state}, 4 * offset + 8);
271 if(
static_cast<bool>(p.col & 1U)) {
272 changed =
update(tree_[idx][offset + 1], {p.row >> 1U, p.col >> 1U, p.state}, 4 * offset + 4);
274 changed =
update(tree_[idx][offset], {p.row >> 1U, p.col >> 1U, p.state}, 4 * offset);
279 const cfloat *x00 = tree_[idx][offset].data();
280 const cfloat *x01 = tree_[idx][offset + 1].data();
281 const cfloat *x10 = tree_[idx][offset + 2].data();
282 const cfloat *x11 = tree_[idx][offset + 3].data();
283 cfloat *xp = x.data();
284 const unsigned int Nn = N * n;
286 for(
unsigned int j = 0; j < ndiv2; j++) {
287 const unsigned int ndiv2j = ndiv2 * j, nj = n * j;
288 for(
unsigned int i = 0; i < ndiv2; i++) {
289 const unsigned int k = i + ndiv2j, k1 = i + nj, k2 = k1 + ndiv2;
291 const cfloat tu = twiddle_[j + Nn] * x01[k];
292 const cfloat td = twiddle_[i + j + Nn] * x11[k];
293 const cfloat ts = twiddle_[i + Nn] * x10[k];
295 const cfloat x00_k = x00[k];
296 const cfloat a = x00_k + tu;
297 const cfloat b = x00_k - tu;
298 const cfloat c = ts + td;
299 const cfloat d = ts - td;
302 xp[k1 + nndiv2] = b + d;
304 xp[k2 + nndiv2] = b - d;
327 bool update(cfloatmat &x, Stimuli::iterator b0, Stimuli::iterator e0,
const unsigned int offset = 0) {
328 const unsigned int n = x.rows();
331 return std::exchange(x(0, 0), b0->state).real() !=
static_cast<float>(b0->state);
333 const bool state = std::any_of(b0, e0, [](
const Stimulus &p) {
return p.state; });
334 return std::exchange(x(0, 0), state).real() !=
static_cast<float>(state);
336 const unsigned int ndiv2 = n >> 1U;
337 const unsigned int nndiv2 = n * ndiv2;
338 const unsigned int idx = log2i(ndiv2);
340 Stimuli::iterator e1, e2, e3;
341 e2 = std::partition(b0, e0, [](
const Stimulus &p) {
return p.row & 1U; });
342 e1 = std::partition(b0, e2, [](
const Stimulus &p) {
return p.col & 1U; });
343 e3 = std::partition(e2, e0, [](
const Stimulus &p) {
return p.col & 1U; });
345 auto transformStimuli = [](Stimuli::iterator begin, Stimuli::iterator end) {
346 for(
auto it = begin; it != end; ++it) {
351 transformStimuli(b0, e1);
352 transformStimuli(e1, e2);
353 transformStimuli(e2, e3);
354 transformStimuli(e3, e0);
356 bool changed =
false;
358 changed =
update(tree_[idx][offset + 3], b0, e1, 4 * offset + 12) || changed;
361 changed =
update(tree_[idx][offset + 2], e1, e2, 4 * offset + 8) || changed;
364 changed =
update(tree_[idx][offset + 1], e2, e3, 4 * offset + 4) || changed;
367 changed =
update(tree_[idx][offset], e3, e0, 4 * offset) || changed;
371 const cfloat *x00 = tree_[idx][offset].data();
372 const cfloat *x01 = tree_[idx][offset + 1].data();
373 const cfloat *x10 = tree_[idx][offset + 2].data();
374 const cfloat *x11 = tree_[idx][offset + 3].data();
375 cfloat *xp = x.data();
376 const unsigned int Nn = N * n;
378 for(
unsigned int j = 0; j < ndiv2; j++) {
379 const unsigned int ndiv2j = ndiv2 * j, nj = n * j;
380 for(
unsigned int i = 0; i < ndiv2; i++) {
381 const unsigned int k = i + ndiv2j, k1 = i + nj, k2 = k1 + ndiv2;
383 const cfloat tu = twiddle_[j + Nn] * x01[k];
384 const cfloat td = twiddle_[i + j + Nn] * x11[k];
385 const cfloat ts = twiddle_[i + Nn] * x10[k];
387 const cfloat x00_k = x00[k];
388 const cfloat a = x00_k + tu;
389 const cfloat b = x00_k - tu;
390 const cfloat c = ts + td;
391 const cfloat d = ts - td;
394 xp[k1 + nndiv2] = b + d;
396 xp[k2 + nndiv2] = b - d;
409 void initializeGroundTruth(
const cfloatmat &image = cfloatmat::Zero(N, N)) {
410 plan_ = fftw_plan_dft_2d(N, N, fftwInput_, fftwOutput_, FFTW_FORWARD, FFTW_ESTIMATE | FFTW_UNALIGNED | FFTW_NO_SIMD | FFTW_PRESERVE_INPUT);
411 for(Eigen::Index i = 0; i < image.rows(); i++) {
412 for(Eigen::Index j = 0; j < image.cols(); j++) {
413 fftwInput_[N * i + j][0] = image(i, j).real();
414 fftwInput_[N * i + j][1] = image(i, j).imag();
425 void updateGroundTruth(
const Stimulus &p) {
426 fftwInput_[N * p.row + p.col][0] =
static_cast<double>(p.state);
427 fftwInput_[N * p.row + p.col][1] = 0;
436 void updateGroundTruth(
const Stimuli &pv) {
437 std::set<std::pair<unsigned int, unsigned int>> activated;
438 for(
const Stimulus &p : pv) {
440 activated.insert({p.row, p.col});
441 }
else if(activated.find({p.row, p.col}) != activated.end()) {
444 fftwInput_[N * p.row + p.col][0] =
static_cast<double>(p.state);
445 fftwInput_[N * p.row + p.col][1] = 0;
456 [[nodiscard]]
inline const cfloatmat &
getFFT()
const {
457 return tree_[LOG2_N][0];
466 [[nodiscard]]
inline cfloatmat getGroundTruthFFT()
const {
467 return Eigen::Map<const Eigen::Matrix<std::complex<double>, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>>(
reinterpret_cast<const std::complex<double> *
>(fftwOutput_), N, N).
template cast<cfloat>();
475 [[nodiscard]]
inline double check()
const {
476 return (
getFFT() - getGroundTruthFFT()).norm();
void filter()
Filters out stimuli.
Definition efft.hpp:93
void initialize()
Initializes the FFT computation with zero matrix.
Definition efft.hpp:185
bool update(const Stimulus &p)
Updates the FFT with a single stimulus.
Definition efft.hpp:245
bool update(Stimuli &pv)
Updates the FFT with multiple stimuli.
Definition efft.hpp:317
bool update(cfloatmat &x, Stimuli::iterator b0, Stimuli::iterator e0, const unsigned int offset=0)
Updates the FFT with multiple stimuli in the specified matrix.
Definition efft.hpp:327
const cfloatmat & getFFT() const
Get the FFT result as an Eigen matrix of complex floats.
Definition efft.hpp:456
constexpr unsigned int framesize() const
Get the frame size of the FFT.
Definition efft.hpp:178
void initialize(cfloatmat &x, const int offset=0)
Initializes the FFT computation with the provided matrix.
Definition efft.hpp:196
bool update(cfloatmat &x, const Stimulus &p, const unsigned int offset=0)
Updates the FFT with a single stimulus in the specified matrix.
Definition efft.hpp:254